From 41a8b1bef8fbcb5c6314b534ade930f11a775318 Mon Sep 17 00:00:00 2001 From: Peptide90 <78795277+Peptide90@users.noreply.github.com> Date: Sat, 14 Sep 2024 21:06:39 +0100 Subject: [PATCH 1/6] Weather Block Marker, Rad Markers and Invisible Walls (#862) Ported from Nuclear14 by request of @OldDanceJacket as they wanted the weather blocking markers. Tought you may as well get the rest of the stuff for rad markers and invisible walls too. Useful for planets. --------- Signed-off-by: Peptide90 <78795277+Peptide90@users.noreply.github.com> Signed-off-by: VMSolidus Co-authored-by: VMSolidus Co-authored-by: DEATHB4DEFEAT <77995199+DEATHB4DEFEAT@users.noreply.github.com> --- .../Entities/Markers/environmental.yml | 100 ++++++++++++++++++ .../Markers/environment.rsi/base-blue.png | Bin 0 -> 250 bytes .../Markers/environment.rsi/base-green.png | Bin 0 -> 198 bytes .../Markers/environment.rsi/base-red.png | Bin 0 -> 255 bytes .../Textures/Markers/environment.rsi/fire.png | Bin 0 -> 227 bytes .../Markers/environment.rsi/meta.json | 32 ++++++ .../Textures/Markers/environment.rsi/rad.png | Bin 0 -> 201 bytes .../Textures/Markers/environment.rsi/wall.png | Bin 0 -> 265 bytes .../Markers/environment.rsi/weather.png | Bin 0 -> 161 bytes 9 files changed, 132 insertions(+) create mode 100644 Resources/Prototypes/Entities/Markers/environmental.yml create mode 100644 Resources/Textures/Markers/environment.rsi/base-blue.png create mode 100644 Resources/Textures/Markers/environment.rsi/base-green.png create mode 100644 Resources/Textures/Markers/environment.rsi/base-red.png create mode 100644 Resources/Textures/Markers/environment.rsi/fire.png create mode 100644 Resources/Textures/Markers/environment.rsi/meta.json create mode 100644 Resources/Textures/Markers/environment.rsi/rad.png create mode 100644 Resources/Textures/Markers/environment.rsi/wall.png create mode 100644 Resources/Textures/Markers/environment.rsi/weather.png diff --git a/Resources/Prototypes/Entities/Markers/environmental.yml b/Resources/Prototypes/Entities/Markers/environmental.yml new file mode 100644 index 0000000000..0642518356 --- /dev/null +++ b/Resources/Prototypes/Entities/Markers/environmental.yml @@ -0,0 +1,100 @@ +# Radiation +- type: entity + name: Marker Radiation + id: MarkerRadiation1 + parent: MarkerBase + suffix: intensity 1 + components: + - type: Sprite + layers: + - sprite: Markers/environment.rsi + state: base-green + shader: unshaded + - sprite: Markers/environment.rsi + shader: unshaded + state: rad + - type: RadiationSource + intensity: 1 + +- type: entity + parent: MarkerRadiation1 + id: MarkerRadiation2 + suffix: intensity 2 + components: + - type: RadiationSource + intensity: 2 + +- type: entity + parent: MarkerRadiation1 + id: MarkerRadiation3 + suffix: intensity 3 + components: + - type: RadiationSource + intensity: 3 + +- type: entity + parent: MarkerRadiation1 + id: MarkerRadiation4 + suffix: intensity 4 + components: + - type: RadiationSource + intensity: 4 + +- type: entity + parent: MarkerRadiation1 + id: MarkerRadiation5 + suffix: intensity 5 + components: + - type: RadiationSource + intensity: 5 + +- type: entity + parent: MarkerRadiation1 + id: MarkerRadiation10 + suffix: intensity 10 + components: + - type: RadiationSource + intensity: 10 + +# Invisible Walls +- type: entity + name: Marker Blocker + id: MarkerBlocker + parent: MarkerBase + suffix: invisible wall + components: + - type: Sprite + layers: + - sprite: Markers/environment.rsi + state: base-blue + shader: unshaded + - sprite: Markers/environment.rsi + shader: unshaded + state: wall + - type: PlacementReplacement + key: blocker + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.5,-0.5,0.5,0.5" + mask: + - FullTileMask + layer: + - WallLayer + density: 1000 + - type: Physics + bodyType: Static + + +# Weather Blocker +- type: entity + name: Marker Weather Blocker + id: MarkerWeatherblocker + parent: MarkerBase + components: + - type: Sprite + sprite: Markers/environment.rsi + state: weather + - type: BlockWeather diff --git a/Resources/Textures/Markers/environment.rsi/base-blue.png b/Resources/Textures/Markers/environment.rsi/base-blue.png new file mode 100644 index 0000000000000000000000000000000000000000..ee77bb448aca3d323b31aa128d8ba2832b121ae9 GIT binary patch literal 250 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzwj^(N7lsBP{NFO^{{x^1XMsm# zF$061G6*wPEVVBK3bL1Y`ns||!MP|ku_QG` zp**uBL&4qCHz2%`PaLQy$l=9+40u>Ca-Z^uY_M~#Tye!9N<)Bofx?|T z@(1cB#ok!DHF?qut_#2CU8?E%%*Nm{WoeS&=iEll4HIP;UpDO8;L_*Nz`)4F!XcpG n(9n>7{?qgD-`1+XZTB;HckoTPJ9nxQ&^887S3j3^P6aDG!CxKcSJYD@<);T3K0RS;VHRAvP literal 0 HcmV?d00001 diff --git a/Resources/Textures/Markers/environment.rsi/base-red.png b/Resources/Textures/Markers/environment.rsi/base-red.png new file mode 100644 index 0000000000000000000000000000000000000000..e0d68f8b9e52b47e75c9a8f158e1eece39c501f9 GIT binary patch literal 255 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzwj^(N7l!{JxM1({$v_d#0*}aI z1_nK45N51cYF`EvWH0gbb!C6b!z3WdDIK@{I#5Wm#5JPCIX^cyHLrxhxhOTUBsE2$ zJhLQ2!QIn0AiR-J9H=PW)5S3);_%z+8+jWHcvvrTpYqUg;7k%NJHfZ6Nh5JffC8I? zWU=bsSd5KwSv rU|?io;Sl)nHtOGU9-nVZbkR9}!xfMvUl?3?(|7Z9Q1R*zjKoXNZT^vI!dRs4b^ED`Num(tc z{$FqD9PmUj_3Ya_k=L27B=4AcXJKK>F-wPHHxCz^;+3Kn&wcK#E@-$mV7&(Ht;w0Ux?vdKE@$f`?^zTG%v z`Ah6xZTEw9u3vV$K4O;It{Y^oTV!5e`fj)D)8FgY$=LgJvISm!EXfCS9)qW=pUXO@ GgeCxf5^ZPz literal 0 HcmV?d00001 diff --git a/Resources/Textures/Markers/environment.rsi/weather.png b/Resources/Textures/Markers/environment.rsi/weather.png new file mode 100644 index 0000000000000000000000000000000000000000..adb9f53a4739a2f117e9348c5af8d9a0a3f1ae4e GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}(Vi}jArY-_ zFI?nmP~d5O7|g$|{ryqD@L9ZPSYwa=KPXU|=gcYIp5tkI%kGKX&ze2*_ols_y2SZ* z?%bzGcHjHVsMT8JccPzp8hb`-nDdjg`F|ar?q&oU{2<)#!gJk)5^p&t19=Rdu6{1- HoD!M Date: Sun, 15 Sep 2024 10:04:09 +0300 Subject: [PATCH 2/6] =?UTF-8?q?=D1=84=D1=8B=D0=B8=D1=83=D0=BA=D1=81=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ru-RU/preferences/ui/markings-picker.ftl | 2 +- .../structures/machines/mailTeleporter.ftl | 2 +- .../prototypes/simplestation14/benches.ftl | 2 +- Resources/Locale/ru-RU/traits/traits.ftl | 6 ++--- .../Guidebook/Epistemics/Psionics.xml | 26 +++++++++---------- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Resources/Locale/ru-RU/preferences/ui/markings-picker.ftl b/Resources/Locale/ru-RU/preferences/ui/markings-picker.ftl index d8cadc3631..6c2932c8a1 100644 --- a/Resources/Locale/ru-RU/preferences/ui/markings-picker.ftl +++ b/Resources/Locale/ru-RU/preferences/ui/markings-picker.ftl @@ -21,7 +21,7 @@ markings-category-HeadSide = Голова (бок) markings-category-Snout = Морда markings-category-Chest = Грудь markings-category-RightArm = Правая Рука -markings-category-RightHand = Правая Ккисть +markings-category-RightHand = Правая Кисть markings-category-LeftArm = Левая Рука markings-category-LeftHand = Левая Кисть markings-category-RightLeg = Права Нога diff --git a/Resources/Locale/ru-RU/ss14-ru/prototypes/nyanotrasen/entities/structures/machines/mailTeleporter.ftl b/Resources/Locale/ru-RU/ss14-ru/prototypes/nyanotrasen/entities/structures/machines/mailTeleporter.ftl index c322424cdf..2bc15cfd51 100644 --- a/Resources/Locale/ru-RU/ss14-ru/prototypes/nyanotrasen/entities/structures/machines/mailTeleporter.ftl +++ b/Resources/Locale/ru-RU/ss14-ru/prototypes/nyanotrasen/entities/structures/machines/mailTeleporter.ftl @@ -1,2 +1,2 @@ ent-MailTeleporter = телепортер почты - .desc = Телепортирует почту по станции. + .desc = Телепортирует почту на станции. diff --git a/Resources/Locale/ru-RU/ss14-ru/prototypes/simplestation14/benches.ftl b/Resources/Locale/ru-RU/ss14-ru/prototypes/simplestation14/benches.ftl index b4be680e90..7062ecc506 100644 --- a/Resources/Locale/ru-RU/ss14-ru/prototypes/simplestation14/benches.ftl +++ b/Resources/Locale/ru-RU/ss14-ru/prototypes/simplestation14/benches.ftl @@ -1,5 +1,5 @@ ent-BenchBaseMiddle = скамейка - .desc = Multiple seats spanning a single object. Truly a marvel of science. + .desc = Несколько мест, охватывающих один объект. Действительно чудо науки. .suffix = Середина ent-BenchParkMiddle = парковая скамейка .desc = { ent-BenchBaseMiddle.desc } diff --git a/Resources/Locale/ru-RU/traits/traits.ftl b/Resources/Locale/ru-RU/traits/traits.ftl index 945dd5740c..a916f3bf66 100644 --- a/Resources/Locale/ru-RU/traits/traits.ftl +++ b/Resources/Locale/ru-RU/traits/traits.ftl @@ -103,9 +103,9 @@ trait-description-LowPainTolerance = При получении урона выносливости ваш урон в ближнем бою уменьшается еще на 15%. trait-name-MartialArtist = Мастер боевых искусств trait-description-MartialArtist = - Вы прошли формальную подготовку по рукопашному бою, будь то на кулаках, ногах или когтях. - Ваши атаки в ближнем бою без оружия немного увеличивают дальность и наносят на 50% больше урона. - Это не относится ни к какому виду рукопашного боя, а только к оружию, с которым вы родились. + Вы прошли подготовку по рукопашному бою, будь то на кулаках, ногах или когтях. + Ваши кулаки бьют дальше и наносят на 50% больше урона. + Это не относится ни к какому виду ручного оружия, а только к оружию, с которым вы родились. trait-name-Vigor = Сила trait-description-Vigor = Ваша выносливость повышается благодаря целеустремленности, физической подготовке или бионическим усилениям. diff --git a/Resources/ServerInfo/Nyanotrasen/Guidebook/Epistemics/Psionics.xml b/Resources/ServerInfo/Nyanotrasen/Guidebook/Epistemics/Psionics.xml index 3e04a977d7..3a9f1ed692 100644 --- a/Resources/ServerInfo/Nyanotrasen/Guidebook/Epistemics/Psionics.xml +++ b/Resources/ServerInfo/Nyanotrasen/Guidebook/Epistemics/Psionics.xml @@ -1,6 +1,6 @@ # Псионика - [color=#a4885c]Псионика[/color] это ментальная способность, которая позволяет взаимодействовать с [color=#a4885c]ноосферой[/color]. Люди имеют ограниченный доступ к полному потенциалу ноосферы, что, в прочем, не огнаичевает их в находении способов управлять ею. + [color=#a4885c]Псионика[/color] это ментальная способность, которая позволяет взаимодействовать с [color=#a4885c]ноосферой[/color]. Люди имеют ограниченный доступ к полному потенциалу ноосферы, что, в прочем, не ограничивает их в нахождении способов управлять ею. Способности возникают в результате придания ноосферному отпечатку определенной формы, немного похожей на замок и ключ. Это означает, что люди могут обладать только одной способностью одновременно. Отпечаток, созданный для метапсихологии, не подойдет для невидимости, и наоборот. @@ -10,22 +10,22 @@ ## Телепатия Все псионические сущности имеют общий телепатический канал связи. Он анонимный, поэтому вы никогда не знаете, с кем разговариваете. По статистике, это, вероятно, не человек. (= сообщение) - ## Глимер - [color=#a4885c]Глимер[/color] - это разговорный термин, обозначающий [color=#a4885c]давление ноосферы[/color],измеряющееся в [color=#a4885c]мили-Пси (mΨ)[/color].Ноосферу можно рассматривать как аналог атмосферы. В локализованной области все будет выравниваться до определенного давления, которое зависит от общей энергии в системе. + ## Глиммер + [color=#a4885c]Глиммер[/color] - это разговорный термин, обозначающий [color=#a4885c]давление ноосферы[/color],измеряющееся в [color=#a4885c]мили-Пси (mΨ)[/color].Ноосферу можно рассматривать как аналог атмосферы. В локализованной области все будет выравниваться до определенного давления, которое зависит от общей энергии в системе. Обратите внимание, что, в отличие от атмосфер, области с ноосферным давлением воздействуют не физически, а ментально. Это означает, что области с переплетенными судьбами или находящеся за стенами будут испытывать одинаковое "тепло", независимо от физического разделения. - [color=#a4885c]София[/color] Позволяет отслеживать уровень глимера. + [color=#a4885c]София[/color] Позволяет отслеживать уровень глиммера. - Специалисты по эпистемологии могут создавать устройства для взаимодействия с глимером. Обычно именно они определяют направление развития глимера. - [color=#a4885c]Пробник глимера[/color] увеличивают количество гримера, но непосредственно генерируют точки исследования за счёт него же. [color=#a4885c]Уменьшитель глимера[/color] просто уменьшает уровень глимера за счет электроэнергии. + Специалисты по эпистемологии могут создавать устройства для взаимодействия с глиммером. Обычно именно они определяют направление развития глиммера. + [color=#a4885c]Пробник глиммера[/color] увеличивают количество гримера, но непосредственно генерируют точки исследования за счёт него же. [color=#a4885c]Уменьшитель глиммера[/color] просто уменьшает уровень глиммера за счет электроэнергии. - Использование псионики увеличит уровень глимера, а также может вызвать [color=#a4885c]ноосферные бури[/color]. + Использование псионики увеличит уровень глиммера, а также может вызвать [color=#a4885c]ноосферные бури[/color]. ## Разряды Глиммер время от времени разряжается, если его плотность превышает 100 мили-Пси, вызывая широкий спектр эффектов, в зависимости от того, насколько он высок. Наиболее распространенным является небольшой всплеск и одарение всех сущностей с псионическим потенциалом, @@ -34,7 +34,7 @@ ## Овладение псионикой - Псионику можно приобрести несколькими способами. Иногда оракул раздает странную жидкость под названием [color=#a4885c]масло лотофага[/color], которое при высоком уровне глимера гарантированно дает псионические свойства. + Псионику можно приобрести несколькими способами. Иногда оракул раздает странную жидкость под названием [color=#a4885c]масло лотофага[/color], которое при высоком уровне глиммера гарантированно дает псионические свойства. Случайные всплески могут вызвать у вас псионическую реакцию, поскольку приступ приводит ваш ноосферный отпечаток к нужной форме. @@ -51,21 +51,21 @@ У службы безопасности есть более надежный вариант использования изолирующей одежды. - ## Нейтрализации проишествия с Глимером + ## Нейтрализации проишествия с глиммером - Пробник глимера следует выключить до того, как уровень мерцания превысит 500 mΨ. + Пробник глиммера следует выключить до того, как уровень мерцания превысит 500 mΨ. - Уменьшитель глимера достаточно эффективны для слива глимера, извлекая его так же быстро, как это делает Пробник. + Уменьшитель глиммера достаточно эффективны для слива глиммера, извлекая его так же быстро, как это делает Пробник. Принесение в жертву экстрасенсов мгновенно уменьшит глиммера на приличную величину. - Употребление 20 единиц или более [color=#a4885c]разрушителяразума[/color] лишит человека псионических способностей, а это значит, что они больше не будут создавать глимер. + Употребление 20 единиц или более [color=#a4885c]разрушителяразума[/color] лишит человека псионических способностей, а это значит, что они больше не будут создавать глиммер. - [color=#a4885c]разрушитель душы[/color]это снаряды, способные удаляют псионику у экстрасенсов, в которых они попадут. + [color=#a4885c]разрушитель души[/color]это снаряды, способные удаляют псионику у экстрасенсов, в которых они попадут. [color=#a4885c]Эктоплазма[/color], смешанная в мензурке с равными частями воды, золы, крови и плазмы, при достаточном нагревании образует обычный кристалл. [color=#fcdf03]Используйте для этого горячую плиту.[/color] From b77ca57c7f09b305051f5e14fc88e77a68a2b9c6 Mon Sep 17 00:00:00 2001 From: TAZIKLIK <73418250+Evgencheg@users.noreply.github.com> Date: Sun, 15 Sep 2024 21:58:24 +0300 Subject: [PATCH 3/6] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=B2=D0=BE=D0=B4=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Resources/Locale/ru-RU/execution/execution.ftl | 2 +- .../nyanotrasen/entities/clothing/uniforms/costumes.ftl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Locale/ru-RU/execution/execution.ftl b/Resources/Locale/ru-RU/execution/execution.ftl index e0fe3bf9b1..872a3dd6bc 100644 --- a/Resources/Locale/ru-RU/execution/execution.ftl +++ b/Resources/Locale/ru-RU/execution/execution.ftl @@ -7,7 +7,7 @@ execution-verb-message = Используйте свое оружие, чтоб # weapon (the weapon used for the execution) execution-popup-gun-initial-internal = Вы направляете дуло { THE($weapon) } на голову { $victim }. -execution-popup-gun-initial-external = { $attacker } плжносит дуло { THE($weapon) } к глове { $victim }. +execution-popup-gun-initial-external = { $attacker } подносит дуло { THE($weapon) } к глове { $victim }. execution-popup-gun-complete-internal = Вы стреляете в голову { $victim }! execution-popup-gun-complete-external = { $attacker } сьреляет в голову { $victim }! execution-popup-gun-clumsy-internal = Вы промахиваетесь мимо головы { $victim }, и стреляете себе в ногу! diff --git a/Resources/Locale/ru-RU/ss14-ru/prototypes/nyanotrasen/entities/clothing/uniforms/costumes.ftl b/Resources/Locale/ru-RU/ss14-ru/prototypes/nyanotrasen/entities/clothing/uniforms/costumes.ftl index 5cd3c2dcba..3c58d5d616 100644 --- a/Resources/Locale/ru-RU/ss14-ru/prototypes/nyanotrasen/entities/clothing/uniforms/costumes.ftl +++ b/Resources/Locale/ru-RU/ss14-ru/prototypes/nyanotrasen/entities/clothing/uniforms/costumes.ftl @@ -12,7 +12,7 @@ ent-ClothingCostumeBunnySuit = костюм зайчика .desc = Готовое платье с чулками и галстуком-бабочкой. ent-ClothingCostumeMioSkirt = желтая юбка с розовой толстовкой .desc = Мягкий и удобный, он имеет яркую расцветку и аккуратный желтый бантик на шее. -ent-ClothingUniformSkirtTurtle = юбка с декоротивной черепашкой +ent-ClothingUniformSkirtTurtle = юбка с декоративной черепашкой .desc = Юбка-черепаха с желтыми бретелями и мягкой тканью. ent-ClothingCostumeNaota = бирюзовая толстовка и шорты .desc = Толстовка с капюшоном и небольшим карманом-кенгуру. From 82deba401a5d230071bba099a7b991f35392be3e Mon Sep 17 00:00:00 2001 From: gluesniffler <159397573+gluesniffler@users.noreply.github.com> Date: Mon, 16 Sep 2024 00:41:44 -0400 Subject: [PATCH 4/6] Harpy Flight System (#919) # Description This PR adds a generic system which gives an entity the ability to fly. Optionally increasing their speed in exchange for a continuous stamina drain, which can, and **will** stamcrit them if left unchecked. --- # Technical Details? We normally dont have this section but I'd like to outline the changes since I messed with quite a few systems: - Introduces a `FlightComponent` which can be added to any entity in YML, needs to be tied to an action with an event of type `ToggleFlightEvent` This component holds properties for: - Toggling animations on and off, either at the entity level or the layer level. - Altering shader animation properties - Altering speed, stamina drain, sounds played, delay between sounds, etc etc. - Adds a `FlyingVisualizerSystem` that can take a given `AnimationKey` which points to a shader, and optionally can apply it to either the entire sprite, or a given layer. - Adds a check in `SharedGravitySystem` for making the entity weightless when it has the `FlightComponent` and is flying. - Adds a check in `SharedCuffableSystem` to disable cuffing when the target has the `FlightComponent` and is flying. - Introduces a new field in the `StaminaComponent` which serves as a dictionary for persistent drains, with the key being the source (UID) of where it came from. The drains can also indicate if they should apply the stamina slowdown or not (relevant for both this PR, and for an eventual sprinting PR) ---

Media

[![Flight Demo](https://i.ytimg.com/vi/Wndv9hYaZ_s/maxresdefault.jpg)](https://youtu.be/Wndv9hYaZ_s "Flight Demo")

--- # Changelog :cl: Mocho - add: Harpies are now able to fly on station for limited periods of time, moving faster at the cost of stamina. --------- Signed-off-by: gluesniffler <159397573+gluesniffler@users.noreply.github.com> Co-authored-by: VMSolidus --- .../Components/FlightVisualsComponent.cs | 40 +++++ Content.Client/Flight/FlightSystem.cs | 67 ++++++++ .../Flight/FlyingVisualizerSystem.cs | 64 +++++++ Content.Server/Flight/FlightSystem.cs | 158 ++++++++++++++++++ Content.Shared/Cuffs/SharedCuffableSystem.cs | 10 +- .../Damage/Components/StaminaComponent.cs | 9 +- .../Damage/Systems/StaminaSystem.cs | 48 ++++-- Content.Shared/Flight/Events.cs | 24 +++ Content.Shared/Flight/FlightComponent.cs | 101 +++++++++++ Content.Shared/Flight/SharedFlightSystem.cs | 104 ++++++++++++ .../Gravity/SharedFloatingVisualizerSystem.cs | 18 +- Content.Shared/Gravity/SharedGravitySystem.cs | 6 +- Resources/Audio/Effects/Flight/wingflap1.ogg | Bin 0 -> 10302 bytes Resources/Audio/Effects/Flight/wingflap2.ogg | Bin 0 -> 13035 bytes Resources/Audio/Effects/Flight/wingflap3.ogg | Bin 0 -> 10881 bytes .../cuffs/components/handcuff-component.ftl | 1 + .../Locale/en-US/flight/flight_system.ftl | 2 + .../Entities/Mobs/Species/harpy.yml | 16 ++ Resources/Prototypes/Shaders/shaders.yml | 7 + .../Prototypes/SoundCollections/flight.yml | 6 + .../Actions/flight.rsi/flight_off.png | Bin 0 -> 15819 bytes .../Actions/flight.rsi/flight_on.png | Bin 0 -> 16238 bytes .../Interface/Actions/flight.rsi/meta.json | 17 ++ Resources/Textures/Shaders/flap.swsl | 35 ++++ 24 files changed, 718 insertions(+), 15 deletions(-) create mode 100644 Content.Client/Flight/Components/FlightVisualsComponent.cs create mode 100644 Content.Client/Flight/FlightSystem.cs create mode 100644 Content.Client/Flight/FlyingVisualizerSystem.cs create mode 100644 Content.Server/Flight/FlightSystem.cs create mode 100644 Content.Shared/Flight/Events.cs create mode 100644 Content.Shared/Flight/FlightComponent.cs create mode 100644 Content.Shared/Flight/SharedFlightSystem.cs create mode 100644 Resources/Audio/Effects/Flight/wingflap1.ogg create mode 100644 Resources/Audio/Effects/Flight/wingflap2.ogg create mode 100644 Resources/Audio/Effects/Flight/wingflap3.ogg create mode 100644 Resources/Locale/en-US/flight/flight_system.ftl create mode 100644 Resources/Prototypes/SoundCollections/flight.yml create mode 100644 Resources/Textures/Interface/Actions/flight.rsi/flight_off.png create mode 100644 Resources/Textures/Interface/Actions/flight.rsi/flight_on.png create mode 100644 Resources/Textures/Interface/Actions/flight.rsi/meta.json create mode 100644 Resources/Textures/Shaders/flap.swsl diff --git a/Content.Client/Flight/Components/FlightVisualsComponent.cs b/Content.Client/Flight/Components/FlightVisualsComponent.cs new file mode 100644 index 0000000000..3f378f60ef --- /dev/null +++ b/Content.Client/Flight/Components/FlightVisualsComponent.cs @@ -0,0 +1,40 @@ +using Robust.Client.Graphics; +using Robust.Shared.GameStates; + +namespace Content.Client.Flight.Components; + +[RegisterComponent] +public sealed partial class FlightVisualsComponent : Component +{ + /// + /// How long does the animation last + /// + [DataField] + public float Speed; + + /// + /// How far it goes in any direction. + /// + [DataField] + public float Multiplier; + + /// + /// How much the limbs (if there are any) rotate. + /// + [DataField] + public float Offset; + + /// + /// Are we animating layers or the entire sprite? + /// + public bool AnimateLayer = false; + public int? TargetLayer; + + [DataField] + public string AnimationKey = "default"; + + [ViewVariables(VVAccess.ReadWrite)] + public ShaderInstance Shader = default!; + + +} \ No newline at end of file diff --git a/Content.Client/Flight/FlightSystem.cs b/Content.Client/Flight/FlightSystem.cs new file mode 100644 index 0000000000..bd1a6767bd --- /dev/null +++ b/Content.Client/Flight/FlightSystem.cs @@ -0,0 +1,67 @@ +using Robust.Client.GameObjects; +using Content.Shared.Flight; +using Content.Shared.Flight.Events; +using Content.Client.Flight.Components; + +namespace Content.Client.Flight; +public sealed class FlightSystem : SharedFlightSystem +{ + [Dependency] private readonly IEntityManager _entityManager = default!; + public override void Initialize() + { + base.Initialize(); + + SubscribeNetworkEvent(OnFlight); + + } + + private void OnFlight(FlightEvent args) + { + var uid = GetEntity(args.Uid); + if (!_entityManager.TryGetComponent(uid, out SpriteComponent? sprite) + || !args.IsAnimated + || !_entityManager.TryGetComponent(uid, out FlightComponent? flight)) + return; + + + int? targetLayer = null; + if (flight.IsLayerAnimated && flight.Layer is not null) + { + targetLayer = GetAnimatedLayer(uid, flight.Layer, sprite); + if (targetLayer == null) + return; + } + + if (args.IsFlying && args.IsAnimated && flight.AnimationKey != "default") + { + var comp = new FlightVisualsComponent + { + AnimateLayer = flight.IsLayerAnimated, + AnimationKey = flight.AnimationKey, + Multiplier = flight.ShaderMultiplier, + Offset = flight.ShaderOffset, + Speed = flight.ShaderSpeed, + TargetLayer = targetLayer, + }; + AddComp(uid, comp); + } + if (!args.IsFlying) + RemComp(uid); + } + + public int? GetAnimatedLayer(EntityUid uid, string targetLayer, SpriteComponent? sprite = null) + { + if (!Resolve(uid, ref sprite)) + return null; + + int index = 0; + foreach (var layer in sprite.AllLayers) + { + // This feels like absolute shitcode, isn't there a better way to check for it? + if (layer.Rsi?.Path.ToString() == targetLayer) + return index; + index++; + } + return null; + } +} \ No newline at end of file diff --git a/Content.Client/Flight/FlyingVisualizerSystem.cs b/Content.Client/Flight/FlyingVisualizerSystem.cs new file mode 100644 index 0000000000..6dde6cf563 --- /dev/null +++ b/Content.Client/Flight/FlyingVisualizerSystem.cs @@ -0,0 +1,64 @@ +using Content.Client.Flight.Components; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.Prototypes; + +namespace Content.Client.Flight; + +/// +/// Handles offsetting an entity while flying +/// +public sealed class FlyingVisualizerSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _protoMan = default!; + [Dependency] private readonly SpriteSystem _spriteSystem = default!; + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnBeforeShaderPost); + } + + private void OnStartup(EntityUid uid, FlightVisualsComponent comp, ComponentStartup args) + { + comp.Shader = _protoMan.Index(comp.AnimationKey).InstanceUnique(); + AddShader(uid, comp.Shader, comp.AnimateLayer, comp.TargetLayer); + SetValues(comp, comp.Speed, comp.Offset, comp.Multiplier); + } + + private void OnShutdown(EntityUid uid, FlightVisualsComponent comp, ComponentShutdown args) + { + AddShader(uid, null, comp.AnimateLayer, comp.TargetLayer); + } + + private void AddShader(Entity entity, ShaderInstance? shader, bool animateLayer, int? layer) + { + if (!Resolve(entity, ref entity.Comp, false)) + return; + + if (!animateLayer) + entity.Comp.PostShader = shader; + + if (animateLayer && layer is not null) + entity.Comp.LayerSetShader(layer.Value, shader); + + entity.Comp.GetScreenTexture = shader is not null; + entity.Comp.RaiseShaderEvent = shader is not null; + } + + /// + /// This function can be used to modify the shader's values while its running. + /// + private void OnBeforeShaderPost(EntityUid uid, FlightVisualsComponent comp, ref BeforePostShaderRenderEvent args) + { + SetValues(comp, comp.Speed, comp.Offset, comp.Multiplier); + } + + private void SetValues(FlightVisualsComponent comp, float speed, float offset, float multiplier) + { + comp.Shader.SetParameter("Speed", speed); + comp.Shader.SetParameter("Offset", offset); + comp.Shader.SetParameter("Multiplier", multiplier); + } +} \ No newline at end of file diff --git a/Content.Server/Flight/FlightSystem.cs b/Content.Server/Flight/FlightSystem.cs new file mode 100644 index 0000000000..e056fc24ec --- /dev/null +++ b/Content.Server/Flight/FlightSystem.cs @@ -0,0 +1,158 @@ + +using Content.Shared.Cuffs.Components; +using Content.Shared.Damage.Components; +using Content.Shared.DoAfter; +using Content.Shared.Flight; +using Content.Shared.Flight.Events; +using Content.Shared.Mobs; +using Content.Shared.Popups; +using Content.Shared.Stunnable; +using Content.Shared.Zombies; +using Robust.Shared.Audio.Systems; + +namespace Content.Server.Flight; +public sealed class FlightSystem : SharedFlightSystem +{ + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnToggleFlight); + SubscribeLocalEvent(OnFlightDoAfter); + SubscribeLocalEvent(OnMobStateChangedEvent); + SubscribeLocalEvent(OnZombified); + SubscribeLocalEvent(OnKnockedDown); + SubscribeLocalEvent(OnStunned); + SubscribeLocalEvent(OnSleep); + } + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var component)) + { + if (!component.On) + continue; + + component.TimeUntilFlap -= frameTime; + + if (component.TimeUntilFlap > 0f) + continue; + + _audio.PlayPvs(component.FlapSound, uid); + component.TimeUntilFlap = component.FlapInterval; + + } + } + + #region Core Functions + private void OnToggleFlight(EntityUid uid, FlightComponent component, ToggleFlightEvent args) + { + // If the user isnt flying, we check for conditionals and initiate a doafter. + if (!component.On) + { + if (!CanFly(uid, component)) + return; + + var doAfterArgs = new DoAfterArgs(EntityManager, + uid, component.ActivationDelay, + new FlightDoAfterEvent(), uid, target: uid) + { + BlockDuplicate = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + BreakOnDamage = true, + NeedHand = true + }; + + if (!_doAfter.TryStartDoAfter(doAfterArgs)) + return; + } + else + ToggleActive(uid, false, component); + } + + private void OnFlightDoAfter(EntityUid uid, FlightComponent component, FlightDoAfterEvent args) + { + if (args.Handled || args.Cancelled) + return; + + ToggleActive(uid, true, component); + args.Handled = true; + } + + #endregion + + #region Conditionals + + private bool CanFly(EntityUid uid, FlightComponent component) + { + if (TryComp(uid, out var cuffableComp) && !cuffableComp.CanStillInteract) + { + _popupSystem.PopupEntity(Loc.GetString("no-flight-while-restrained"), uid, uid, PopupType.Medium); + return false; + } + + if (HasComp(uid)) + { + _popupSystem.PopupEntity(Loc.GetString("no-flight-while-zombified"), uid, uid, PopupType.Medium); + return false; + } + return true; + } + + private void OnMobStateChangedEvent(EntityUid uid, FlightComponent component, MobStateChangedEvent args) + { + if (!component.On + || args.NewMobState is MobState.Critical or MobState.Dead) + return; + + ToggleActive(args.Target, false, component); + } + + private void OnZombified(EntityUid uid, FlightComponent component, ref EntityZombifiedEvent args) + { + if (!component.On) + return; + + ToggleActive(args.Target, false, component); + if (!TryComp(uid, out var stamina)) + return; + Dirty(uid, stamina); + } + + private void OnKnockedDown(EntityUid uid, FlightComponent component, ref KnockedDownEvent args) + { + if (!component.On) + return; + + ToggleActive(uid, false, component); + } + + private void OnStunned(EntityUid uid, FlightComponent component, ref StunnedEvent args) + { + if (!component.On) + return; + + ToggleActive(uid, false, component); + } + + private void OnSleep(EntityUid uid, FlightComponent component, ref SleepStateChangedEvent args) + { + if (!component.On + || !args.FellAsleep) + return; + + ToggleActive(uid, false, component); + if (!TryComp(uid, out var stamina)) + return; + + Dirty(uid, stamina); + } + #endregion +} \ No newline at end of file diff --git a/Content.Shared/Cuffs/SharedCuffableSystem.cs b/Content.Shared/Cuffs/SharedCuffableSystem.cs index ebbafef7f0..9777b23988 100644 --- a/Content.Shared/Cuffs/SharedCuffableSystem.cs +++ b/Content.Shared/Cuffs/SharedCuffableSystem.cs @@ -7,6 +7,7 @@ using Content.Shared.Contests; using Content.Shared.Cuffs.Components; using Content.Shared.Database; +using Content.Shared.Flight; using Content.Shared.DoAfter; using Content.Shared.Hands; using Content.Shared.Hands.Components; @@ -479,6 +480,13 @@ public bool TryCuffing(EntityUid user, EntityUid target, EntityUid handcuff, Han return true; } + if (TryComp(target, out var flight) && flight.On) + { + _popup.PopupClient(Loc.GetString("handcuff-component-target-flying-error", + ("targetName", Identity.Name(target, EntityManager, user))), user, user); + return true; + } + var cuffTime = handcuffComponent.CuffTime; if (HasComp(target)) @@ -731,4 +739,4 @@ private sealed partial class AddCuffDoAfterEvent : SimpleDoAfterEvent { } } -} +} \ No newline at end of file diff --git a/Content.Shared/Damage/Components/StaminaComponent.cs b/Content.Shared/Damage/Components/StaminaComponent.cs index 65c025c3ad..b78fe97809 100644 --- a/Content.Shared/Damage/Components/StaminaComponent.cs +++ b/Content.Shared/Damage/Components/StaminaComponent.cs @@ -39,6 +39,13 @@ public sealed partial class StaminaComponent : Component [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] public float CritThreshold = 100f; + /// + /// A dictionary of active stamina drains, with the key being the source of the drain, + /// DrainRate how much it changes per tick, and ModifiesSpeed if it should slow down the user. + /// + [DataField, AutoNetworkedField] + public Dictionary ActiveDrains = new(); + /// /// How long will this mob be stunned for? /// @@ -63,4 +70,4 @@ public sealed partial class StaminaComponent : Component /// [DataField, AutoNetworkedField] public float SlowdownMultiplier = 0.75f; -} +} \ No newline at end of file diff --git a/Content.Shared/Damage/Systems/StaminaSystem.cs b/Content.Shared/Damage/Systems/StaminaSystem.cs index f8a0f7c62b..e4840a6630 100644 --- a/Content.Shared/Damage/Systems/StaminaSystem.cs +++ b/Content.Shared/Damage/Systems/StaminaSystem.cs @@ -258,7 +258,7 @@ public bool TryTakeStamina(EntityUid uid, float value, StaminaComponent? compone } public void TakeStaminaDamage(EntityUid uid, float value, StaminaComponent? component = null, - EntityUid? source = null, EntityUid? with = null, bool visual = true, SoundSpecifier? sound = null) + EntityUid? source = null, EntityUid? with = null, bool visual = true, SoundSpecifier? sound = null, bool? allowsSlowdown = true) { if (!Resolve(uid, ref component, false) || value == 0) @@ -284,8 +284,8 @@ public void TakeStaminaDamage(EntityUid uid, float value, StaminaComponent? comp if (component.NextUpdate < nextUpdate) component.NextUpdate = nextUpdate; } - - _movementSpeed.RefreshMovementSpeedModifiers(uid); + if (allowsSlowdown == true) + _movementSpeed.RefreshMovementSpeedModifiers(uid); SetStaminaAlert(uid, component); if (!component.Critical) @@ -328,27 +328,51 @@ public void TakeStaminaDamage(EntityUid uid, float value, StaminaComponent? comp } } + public void ToggleStaminaDrain(EntityUid target, float drainRate, bool enabled, bool modifiesSpeed, EntityUid? source = null) + { + if (!TryComp(target, out var stamina)) + return; + + // If theres no source, we assume its the target that caused the drain. + var actualSource = source ?? target; + + if (enabled) + { + stamina.ActiveDrains[actualSource] = (drainRate, modifiesSpeed); + EnsureComp(target); + } + else + stamina.ActiveDrains.Remove(actualSource); + + Dirty(target, stamina); + } + public override void Update(float frameTime) { base.Update(frameTime); - if (!_timing.IsFirstTimePredicted) return; var stamQuery = GetEntityQuery(); var query = EntityQueryEnumerator(); var curTime = _timing.CurTime; - while (query.MoveNext(out var uid, out _)) { // Just in case we have active but not stamina we'll check and account for it. if (!stamQuery.TryGetComponent(uid, out var comp) || - comp.StaminaDamage <= 0f && !comp.Critical) + comp.StaminaDamage <= 0f && !comp.Critical && comp.ActiveDrains.Count == 0) { RemComp(uid); continue; } - + if (comp.ActiveDrains.Count > 0) + foreach (var (source, (drainRate, modifiesSpeed)) in comp.ActiveDrains) + TakeStaminaDamage(uid, + drainRate * frameTime, + comp, + source: source, + visual: false, + allowsSlowdown: modifiesSpeed); // Shouldn't need to consider paused time as we're only iterating non-paused stamina components. var nextUpdate = comp.NextUpdate; @@ -363,8 +387,11 @@ public override void Update(float frameTime) } comp.NextUpdate += TimeSpan.FromSeconds(1f); - TakeStaminaDamage(uid, -comp.Decay, comp); - Dirty(comp); + // If theres no active drains, recover stamina. + if (comp.ActiveDrains.Count == 0) + TakeStaminaDamage(uid, -comp.Decay, comp); + + Dirty(uid, comp); } } @@ -380,7 +407,6 @@ private void EnterStamCrit(EntityUid uid, StaminaComponent? component = null) component.StaminaDamage = component.CritThreshold; _stunSystem.TryParalyze(uid, component.StunTime, true); - // Give them buffer before being able to be re-stunned component.NextUpdate = _timing.CurTime + component.StunTime + StamCritBufferTime; EnsureComp(uid); @@ -407,4 +433,4 @@ private void ExitStamCrit(EntityUid uid, StaminaComponent? component = null) /// Raised before stamina damage is dealt to allow other systems to cancel it. /// [ByRefEvent] -public record struct BeforeStaminaDamageEvent(float Value, bool Cancelled = false); +public record struct BeforeStaminaDamageEvent(float Value, bool Cancelled = false); \ No newline at end of file diff --git a/Content.Shared/Flight/Events.cs b/Content.Shared/Flight/Events.cs new file mode 100644 index 0000000000..6666971b53 --- /dev/null +++ b/Content.Shared/Flight/Events.cs @@ -0,0 +1,24 @@ +using Robust.Shared.Serialization; +using Content.Shared.DoAfter; + +namespace Content.Shared.Flight.Events; + +[Serializable, NetSerializable] +public sealed partial class DashDoAfterEvent : SimpleDoAfterEvent { } + +[Serializable, NetSerializable] +public sealed partial class FlightDoAfterEvent : SimpleDoAfterEvent { } + +[Serializable, NetSerializable] +public sealed class FlightEvent : EntityEventArgs +{ + public NetEntity Uid { get; } + public bool IsFlying { get; } + public bool IsAnimated { get; } + public FlightEvent(NetEntity uid, bool isFlying, bool isAnimated) + { + Uid = uid; + IsFlying = isFlying; + IsAnimated = isAnimated; + } +} diff --git a/Content.Shared/Flight/FlightComponent.cs b/Content.Shared/Flight/FlightComponent.cs new file mode 100644 index 0000000000..d250744544 --- /dev/null +++ b/Content.Shared/Flight/FlightComponent.cs @@ -0,0 +1,101 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared.Flight; + +/// +/// Adds an action that allows the user to become temporarily +/// weightless at the cost of stamina and hand usage. +/// +[RegisterComponent, NetworkedComponent(), AutoGenerateComponentState] +public sealed partial class FlightComponent : Component +{ + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string? ToggleAction = "ActionToggleFlight"; + + [DataField, AutoNetworkedField] + public EntityUid? ToggleActionEntity; + + /// + /// Is the user flying right now? + /// + [DataField, AutoNetworkedField] + public bool On; + + /// + /// Stamina drain per second when flying + /// + [DataField, AutoNetworkedField] + public float StaminaDrainRate = 6.0f; + + /// + /// DoAfter delay until the user becomes weightless. + /// + [DataField, AutoNetworkedField] + public float ActivationDelay = 1.0f; + + /// + /// Speed modifier while in flight + /// + [DataField, AutoNetworkedField] + public float SpeedModifier = 2.0f; + + /// + /// Path to a sound specifier or collection for the noises made during flight + /// + [DataField] + public SoundSpecifier FlapSound = new SoundCollectionSpecifier("WingFlaps"); + + /// + /// Is the flight animated? + /// + [DataField] + public bool IsAnimated = true; + + /// + /// Does the animation animate a layer?. + /// + [DataField] + public bool IsLayerAnimated; + + /// + /// Which RSI layer path does this animate? + /// + [DataField] + public string? Layer; + + /// + /// Whats the speed of the shader? + /// + [DataField] + public float ShaderSpeed = 6.0f; + + /// + /// How much are the values in the shader's calculations multiplied by? + /// + [DataField] + public float ShaderMultiplier = 0.01f; + + /// + /// What is the offset on the shader? + /// + [DataField] + public float ShaderOffset = 0.25f; + + /// + /// What animation does the flight use? + /// + + [DataField] + public string AnimationKey = "default"; + + /// + /// Time between sounds being played + /// + [DataField] + public float FlapInterval = 1.0f; + + public float TimeUntilFlap; +} diff --git a/Content.Shared/Flight/SharedFlightSystem.cs b/Content.Shared/Flight/SharedFlightSystem.cs new file mode 100644 index 0000000000..281c6d70f0 --- /dev/null +++ b/Content.Shared/Flight/SharedFlightSystem.cs @@ -0,0 +1,104 @@ +using Content.Shared.Actions; +using Content.Shared.Movement.Systems; +using Content.Shared.Damage.Systems; +using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Interaction.Components; +using Content.Shared.Inventory.VirtualItem; +using Content.Shared.Flight.Events; + +namespace Content.Shared.Flight; +public abstract class SharedFlightSystem : EntitySystem +{ + [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; + [Dependency] private readonly SharedVirtualItemSystem _virtualItem = default!; + [Dependency] private readonly StaminaSystem _staminaSystem = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnRefreshMoveSpeed); + } + + #region Core Functions + private void OnStartup(EntityUid uid, FlightComponent component, ComponentStartup args) + { + _actionsSystem.AddAction(uid, ref component.ToggleActionEntity, component.ToggleAction); + } + + private void OnShutdown(EntityUid uid, FlightComponent component, ComponentShutdown args) + { + _actionsSystem.RemoveAction(uid, component.ToggleActionEntity); + } + + public void ToggleActive(EntityUid uid, bool active, FlightComponent component) + { + component.On = active; + component.TimeUntilFlap = 0f; + _actionsSystem.SetToggled(component.ToggleActionEntity, component.On); + RaiseNetworkEvent(new FlightEvent(GetNetEntity(uid), component.On, component.IsAnimated)); + _staminaSystem.ToggleStaminaDrain(uid, component.StaminaDrainRate, active, false); + _movementSpeed.RefreshMovementSpeedModifiers(uid); + UpdateHands(uid, active); + Dirty(uid, component); + } + + private void UpdateHands(EntityUid uid, bool flying) + { + if (!TryComp(uid, out var handsComponent)) + return; + + if (flying) + BlockHands(uid, handsComponent); + else + FreeHands(uid); + } + + private void BlockHands(EntityUid uid, HandsComponent handsComponent) + { + var freeHands = 0; + foreach (var hand in _hands.EnumerateHands(uid, handsComponent)) + { + if (hand.HeldEntity == null) + { + freeHands++; + continue; + } + + // Is this entity removable? (they might have handcuffs on) + if (HasComp(hand.HeldEntity) && hand.HeldEntity != uid) + continue; + + _hands.DoDrop(uid, hand, true, handsComponent); + freeHands++; + if (freeHands == 2) + break; + } + if (_virtualItem.TrySpawnVirtualItemInHand(uid, uid, out var virtItem1)) + EnsureComp(virtItem1.Value); + + if (_virtualItem.TrySpawnVirtualItemInHand(uid, uid, out var virtItem2)) + EnsureComp(virtItem2.Value); + } + + private void FreeHands(EntityUid uid) + { + _virtualItem.DeleteInHandsMatching(uid, uid); + } + + private void OnRefreshMoveSpeed(EntityUid uid, FlightComponent component, RefreshMovementSpeedModifiersEvent args) + { + if (!component.On) + return; + + args.ModifySpeed(component.SpeedModifier, component.SpeedModifier); + } + + #endregion +} +public sealed partial class ToggleFlightEvent : InstantActionEvent { } diff --git a/Content.Shared/Gravity/SharedFloatingVisualizerSystem.cs b/Content.Shared/Gravity/SharedFloatingVisualizerSystem.cs index 57136116ca..8fe9e00e7e 100644 --- a/Content.Shared/Gravity/SharedFloatingVisualizerSystem.cs +++ b/Content.Shared/Gravity/SharedFloatingVisualizerSystem.cs @@ -1,5 +1,6 @@ using System.Numerics; using Robust.Shared.Map; +using Content.Shared.Flight.Events; namespace Content.Shared.Gravity; @@ -17,6 +18,7 @@ public override void Initialize() SubscribeLocalEvent(OnComponentStartup); SubscribeLocalEvent(OnGravityChanged); SubscribeLocalEvent(OnEntParentChanged); + SubscribeNetworkEvent(OnFlight); } /// @@ -62,10 +64,24 @@ private void OnGravityChanged(ref GravityChangedEvent args) } } + private void OnFlight(FlightEvent args) + { + var uid = GetEntity(args.Uid); + if (!TryComp(uid, out var floating)) + return; + floating.CanFloat = args.IsFlying; + + if (!args.IsFlying + || !args.IsAnimated) + return; + + FloatAnimation(uid, floating.Offset, floating.AnimationKey, floating.AnimationTime); + } + private void OnEntParentChanged(EntityUid uid, FloatingVisualsComponent component, ref EntParentChangedMessage args) { var transform = args.Transform; if (CanFloat(uid, component, transform)) FloatAnimation(uid, component.Offset, component.AnimationKey, component.AnimationTime); } -} +} \ No newline at end of file diff --git a/Content.Shared/Gravity/SharedGravitySystem.cs b/Content.Shared/Gravity/SharedGravitySystem.cs index 100d2ee74f..55187bf14a 100644 --- a/Content.Shared/Gravity/SharedGravitySystem.cs +++ b/Content.Shared/Gravity/SharedGravitySystem.cs @@ -8,6 +8,7 @@ using Robust.Shared.Physics.Components; using Robust.Shared.Serialization; using Robust.Shared.Timing; +using Content.Shared.Flight; namespace Content.Shared.Gravity { @@ -24,6 +25,9 @@ public bool IsWeightless(EntityUid uid, PhysicsComponent? body = null, Transform if ((body?.BodyType & (BodyType.Static | BodyType.Kinematic)) != 0) return false; + if (TryComp(uid, out var flying) && flying.On) + return true; + if (TryComp(uid, out var ignoreGravityComponent)) return ignoreGravityComponent.Weightless; @@ -142,4 +146,4 @@ public GravityComponentState(bool enabled) } } } -} +} \ No newline at end of file diff --git a/Resources/Audio/Effects/Flight/wingflap1.ogg b/Resources/Audio/Effects/Flight/wingflap1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..724ac3ecd204decb15d6e33758fbaef885afd800 GIT binary patch literal 10302 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 literal 0 HcmV?d00001 diff --git a/Resources/Audio/Effects/Flight/wingflap2.ogg b/Resources/Audio/Effects/Flight/wingflap2.ogg new file mode 100644 index 0000000000000000000000000000000000000000..b0264a13170816a51da02b40af98ee349b17be61 GIT binary patch literal 13035 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 literal 0 HcmV?d00001 diff --git a/Resources/Audio/Effects/Flight/wingflap3.ogg b/Resources/Audio/Effects/Flight/wingflap3.ogg new file mode 100644 index 0000000000000000000000000000000000000000..aeb0e21acf6818fd352ccf6a343a4fe5d08963fc GIT binary patch literal 10881 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 literal 0 HcmV?d00001 diff --git a/Resources/Locale/en-US/cuffs/components/handcuff-component.ftl b/Resources/Locale/en-US/cuffs/components/handcuff-component.ftl index 16447f4251..1f8a895164 100644 --- a/Resources/Locale/en-US/cuffs/components/handcuff-component.ftl +++ b/Resources/Locale/en-US/cuffs/components/handcuff-component.ftl @@ -3,6 +3,7 @@ handcuff-component-cuffs-broken-error = The restraints are broken! handcuff-component-target-has-no-hands-error = {$targetName} has no hands! handcuff-component-target-has-no-free-hands-error = {$targetName} has no free hands! handcuff-component-too-far-away-error = You are too far away to use the restraints! +handcuff-component-target-flying-error = You cannot reach {$targetName}'s hands! handcuff-component-start-cuffing-observer = {$user} starts restraining {$target}! handcuff-component-start-cuffing-target-message = You start restraining {$targetName}. handcuff-component-start-cuffing-by-other-message = {$otherName} starts restraining you! diff --git a/Resources/Locale/en-US/flight/flight_system.ftl b/Resources/Locale/en-US/flight/flight_system.ftl new file mode 100644 index 0000000000..12693cc846 --- /dev/null +++ b/Resources/Locale/en-US/flight/flight_system.ftl @@ -0,0 +1,2 @@ +no-flight-while-restrained = You can't fly right now. +no-flight-while-zombified = You can't use your wings right now. \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Species/harpy.yml b/Resources/Prototypes/Entities/Mobs/Species/harpy.yml index 788b21eafb..05ac3de8bb 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/harpy.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/harpy.yml @@ -5,6 +5,10 @@ id: MobHarpyBase abstract: true components: + - type: Flight + isLayerAnimated: true + layer: "/Textures/Mobs/Customization/Harpy/harpy_wings.rsi" + animationKey: "Flap" - type: Singer proto: HarpySinger - type: Sprite @@ -197,3 +201,15 @@ icon: DeltaV/Interface/Actions/harpy_syrinx.png itemIconStyle: BigAction event: !type:VoiceMaskSetNameEvent + +- type: entity + id: ActionToggleFlight + name: Fly + description: Make use of your wings to fly. Beat the flightless bird allegations. + noSpawn: true + components: + - type: InstantAction + checkCanInteract: false + icon: { sprite: Interface/Actions/flight.rsi, state: flight_off } + iconOn: { sprite : Interface/Actions/flight.rsi, state: flight_on } + event: !type:ToggleFlightEvent \ No newline at end of file diff --git a/Resources/Prototypes/Shaders/shaders.yml b/Resources/Prototypes/Shaders/shaders.yml index b495490201..3f0cb5ae1f 100644 --- a/Resources/Prototypes/Shaders/shaders.yml +++ b/Resources/Prototypes/Shaders/shaders.yml @@ -104,3 +104,10 @@ id: SaturationScale kind: source path: "/Textures/Shaders/saturationscale.swsl" + + # Flight shaders + +- type: shader + id: Flap + kind: source + path: "/Textures/Shaders/flap.swsl" \ No newline at end of file diff --git a/Resources/Prototypes/SoundCollections/flight.yml b/Resources/Prototypes/SoundCollections/flight.yml new file mode 100644 index 0000000000..cca2cfb014 --- /dev/null +++ b/Resources/Prototypes/SoundCollections/flight.yml @@ -0,0 +1,6 @@ +- type: soundCollection + id: WingFlaps + files: + - /Audio/Effects/Flight/wingflap1.ogg + - /Audio/Effects/Flight/wingflap2.ogg + - /Audio/Effects/Flight/wingflap3.ogg diff --git a/Resources/Textures/Interface/Actions/flight.rsi/flight_off.png b/Resources/Textures/Interface/Actions/flight.rsi/flight_off.png new file mode 100644 index 0000000000000000000000000000000000000000..852dd300e919fe5740d947caa63d110e07e2a55b GIT binary patch literal 15819 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 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/flight.rsi/flight_on.png b/Resources/Textures/Interface/Actions/flight.rsi/flight_on.png new file mode 100644 index 0000000000000000000000000000000000000000..c47b923a6810a11e66ab6b3a4a859849d71d704b GIT binary patch literal 16238 zcmeI3c|25mAIFb1O&g`sEg6$5w;MCYU`&HBif$;oQjD2{VK%cE%k8mLE)hu!T6C{R ziwu>UC3|(XqD3hQbwes?p-szk262+^x%YY9KhL~gGw18~`+UCN-+BKQ=bw|X%5nJ= z%y$?70H)a6(VU?xR`yjf z$AHe{`GE#u96qE50IFpepUwydMOZ&DfWuI&coP&;J~*516g(v0?;MG z(UlPq%rIl(EG;n9Fbb4_1B&R_FbLL;By`{M9N3UP(Q>>!HxFghP^Xh6V^XJRr&JbbZ$ zJvcNb0}rx64#*V=A)n#6K7Jri#1jVczEN_d`GfRcy}*i`HxP{7nx)|-yAy_#`=a$)-z-qTna-oM1}5^ zVj}?QBA&pN$75Rzt+G|05n^p@23HkU&x6Hf@+86^sQ9s#(fJ+M`W3v3i$ID?tF($%uz?5TiK>LSD7t#M#7PuCc zhsmJ$^8_5a$b!Y82Y`4!H-L&CZG6_&}0arg9F_DRcacK zA(riy;nd83m>OpPDz%e<1)X$sHcTTM{XZ=+PMWVu*|CJs`V5DyTWIwTo@Fjz$d|33 zJJ_tjGl@?Z2tnD7Gsk`2awCV>$QdQ`4DMVCogq8TEEuv40WzuhFIq>3HR2q$vHsr) z9Lf)~f4hbx5aj;Vh8jvWsQPviA^i;g;^k=iaoQR>; zFXv})+e0h%>-nV04mAq27NBv_2j{?$ivOx@wDOSNQ#Pk;@u1$6NPs?gL)q`J72ZA8 z>h;%Fc=uTAsMC$h5?K(3tBq-fxnw8r(6QhGJtTk@CdS4_##H>+=CO`dtT2%6Nn=6# zS14PRMo^_e&)AM}jb7g}!a9bHLsJF&cRo0iL#@!m0`#1MA9+yua*{_j!oM7h-qF9D z7@iI-0;ho(qqq1Q(#V;4~0p6c-{QxB$fkr-2xwxDXM+ z1t=~!4a6A5g@_0)KykroAjT*zL_}}_iVIEyF-CDAB7zH0TyPqQF^UTj5nO=cg3~~Z zQCx_K-~tpEoCacy;zC3O7ofP{G!SDH7a}6K0L2BTff%E>5D~!zC@we+#2CeehzKq~ zalvUI#wadCL~sF$3r+(u{!3h#(O3UKF7&>i1bVTraP87M=%qj`!_L_O03yBzfar|? z@b(jQ?FIlb0RSHP0sti)0A};H`~0yC0F=+#)2v*>n$PZD%hS9uCoYR8H8vUuT=FVe z@?_IqAaRwIc2G{P(rUdU&Cbp1i!2Wm+a8{EE^R!$vhWCIt>aVM)@cznX>xcK#XHm{?<_d`kB^~@VaQ8|kj z>Ub$A>EXg%X`6Q4{LS}Xl>DtyaR=M-x<42abZX#TqGivMUdQ7RojkIZU14p}ZCaeh zNABGC=}9f;;wzs>Q|h0tdt3Js-yg76|7_$9f7&QJMB$?YR>TX?a?|Dq(gC>W$NB zo3)8KN?k#ifF_*xrvhL}xWT?2(z=gZ z^lg_$af1Ey-%T`dy2==^q;rP+m7PnkSm&xwF}*LX>A3Ij_KCgdLf+5rYER>NjWazv0|`u>&Yp#fuvOBnEmRmCp)D5DRVa8 z%Dl8DYO3Kwl@z(s(yEdGp>yA~hJKGW@^e9y#U;=GbaA%$B^fHy_=}X(wrd<-y|-|V zn{*q8o~^$5m$bP}8h55IjN{=KAE{{8dVgu_r3dv#B5}{Op4)$V*7E*QLDfs^D4)W* zPUhr`zALSRie5xs@ORbIu~vK%T7?fvl%IDxQ{z={xJrWW?*}tT5A8Oy>n6WGh*dqZ z_pw%YT|wmwY1g}_@4(jZPm6y^*6eD(Hp9E3Bvu;yyIOJvW*S>@;{-qT##c2>8)6l3 z)~2i$wMNCbBAx2Y>SxyhV*b&O>_fs%NkiqW*G-lWvYkwp{+RDg-g+}mb-{|@$*Bi1 z$JPa=^{6-M>Cog4$Z5CRv>f+w-Ypb-ICJuJ=^>Y#vg$z7rE7h^H}bMv9ao2&ASLM5 zxX;|R{jvsa;#3Fqnseu?f5fGlbtJs$NnNRmle?Fe9&;_}(ZoaC^_ne39oZR>a6;ThV7oj!kOzCMMY)@FsCz|_2jnxmP+!xlAII|%rCh&#Y!TWABT-a1F71Nuwa;MjX=4g!^Vf*Xqn%sIfw=VHfGYQ#&5}c*1 zp=4cr3@kS+Pvz}UiqgotE*!LPO2Fd& zT=Gi33fK|9rS;B~;6$o@vt%IJol>h&S`uO#C;m?!=i0sVx9cyqS9Bb7EKBL1Dvn*j zznTqXlQtKZo0V*L@;1)34$HmMT$aD-T;c_3T6M^_qjzTU`yC4st!Z~9*o@rroL8X^ ziIoR!SK$`Se(&6U@#)(=yLa0@Y&m|cXWs7C`O|9J(z&k#jStQaT4J^kcUFU!c)lwt z^G54bJjXj#^-M^1U-Fp~`_7cV3S|ZCYWslsX=i=5ovETE&?83kaL*ebpNAY3Dzjqi zn(nI`Ot~>Fypy(#M^4q3_2?^scHfcDj%l1$*I9z_C~zDts^bp H+Bf!pP?ZtD literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/flight.rsi/meta.json b/Resources/Textures/Interface/Actions/flight.rsi/meta.json new file mode 100644 index 0000000000..b4a013190d --- /dev/null +++ b/Resources/Textures/Interface/Actions/flight.rsi/meta.json @@ -0,0 +1,17 @@ +{ + "copyright" : "Made by dootythefrooty (273243513800622090)", + "license" : "CC-BY-SA-3.0", + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "flight_off" + }, + { + "name": "flight_on" + } + ] + } \ No newline at end of file diff --git a/Resources/Textures/Shaders/flap.swsl b/Resources/Textures/Shaders/flap.swsl new file mode 100644 index 0000000000..3082e19b49 --- /dev/null +++ b/Resources/Textures/Shaders/flap.swsl @@ -0,0 +1,35 @@ +preset raw; + +varying highp vec4 VtxModulate; +varying highp vec2 Pos; + +uniform highp float Speed; +uniform highp float Multiplier; +uniform highp float Offset; + +void fragment() { + highp vec4 texColor = zTexture(UV); + lowp vec3 lightSample = texture2D(lightMap, Pos).rgb; + COLOR = texColor * VtxModulate * vec4(lightSample, 1.0); +} + +void vertex() { + vec2 pos = aPos; + + // Apply MVP transformation first + vec2 transformedPos = apply_mvp(pos); + + // Calculate vertical movement in screen space + float verticalOffset = (sin(TIME * Speed) + Offset) * Multiplier; + + // Apply vertical movement after MVP transformation + transformedPos.y += verticalOffset; + + // Assign the final position + VERTEX = transformedPos; + + // Keep the original UV coordinates + UV = mix(modifyUV.xy, modifyUV.zw, tCoord); + Pos = (VERTEX + 1.0) / 2.0; + VtxModulate = zFromSrgb(modulate); +} \ No newline at end of file From 2f5e697bc85d0d2a55165d429e293e3ae0a79bd7 Mon Sep 17 00:00:00 2001 From: SimpleStation Changelogs Date: Mon, 16 Sep 2024 04:42:11 +0000 Subject: [PATCH 5/6] Automatic Changelog Update (#919) --- Resources/Changelog/Changelog.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 0be6b68d7e..6a8111cec5 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -6372,3 +6372,12 @@ Entries: id: 6362 time: '2024-09-14T18:01:21.0000000+00:00' url: https://github.com/Simple-Station/Einstein-Engines/pull/868 +- author: Mocho + changes: + - type: Add + message: >- + Harpies are now able to fly on station for limited periods of time, + moving faster at the cost of stamina. + id: 6363 + time: '2024-09-16T04:41:44.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/919 From 9a22fd0f558deef92f0a4a94cd70a1efa60f0435 Mon Sep 17 00:00:00 2001 From: TAZIKLIK <73418250+Evgencheg@users.noreply.github.com> Date: Mon, 16 Sep 2024 23:20:20 +0300 Subject: [PATCH 6/6] =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=B2=D0=BE=D0=B4?= =?UTF-8?q?=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Resources/Locale/ru-RU/cuffs/components/handcuff-component.ftl | 1 + Resources/Locale/ru-RU/flight/flight_system.ftl | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 Resources/Locale/ru-RU/flight/flight_system.ftl diff --git a/Resources/Locale/ru-RU/cuffs/components/handcuff-component.ftl b/Resources/Locale/ru-RU/cuffs/components/handcuff-component.ftl index ac64b2369a..7629b7cde5 100644 --- a/Resources/Locale/ru-RU/cuffs/components/handcuff-component.ftl +++ b/Resources/Locale/ru-RU/cuffs/components/handcuff-component.ftl @@ -3,6 +3,7 @@ handcuff-component-cuffs-broken-error = Наручники сломаны! handcuff-component-target-has-no-hands-error = { $targetName } не имеет рук! handcuff-component-target-has-no-free-hands-error = { $targetName } не имеет свободных рук! handcuff-component-too-far-away-error = Вы слишком далеко, чтобы использовать наручники! +handcuff-component-target-flying-error = Вы не можете достать до рук { $targetName }! handcuff-component-start-cuffing-observer = { $user } начинает заковывать { $target }! handcuff-component-start-cuffing-target-message = Вы начинаете заковывать { $targetName }. handcuff-component-start-cuffing-by-other-message = { $otherName } начинает заковывать вас! diff --git a/Resources/Locale/ru-RU/flight/flight_system.ftl b/Resources/Locale/ru-RU/flight/flight_system.ftl new file mode 100644 index 0000000000..4dd35401c6 --- /dev/null +++ b/Resources/Locale/ru-RU/flight/flight_system.ftl @@ -0,0 +1,2 @@ +no-flight-while-restrained = Вы не можете взлететь. +no-flight-while-zombified = Вы не можете использовать ваши крылья.