diff --git a/Content.Client/_White/Animations/FlipOnHitSystem.cs b/Content.Client/_White/Animations/FlipOnHitSystem.cs new file mode 100644 index 0000000000..d501bd75fe --- /dev/null +++ b/Content.Client/_White/Animations/FlipOnHitSystem.cs @@ -0,0 +1,75 @@ +using Content.Shared._White.Animations; +using Robust.Client.Animations; +using Robust.Client.GameObjects; +using Robust.Shared.Animations; +using Robust.Shared.Timing; + +namespace Content.Client._White.Animations; + +public sealed class FlipOnHitSystem : SharedFlipOnHitSystem +{ + [Dependency] private readonly AnimationPlayerSystem _animationSystem = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAnimationComplete); + SubscribeAllEvent(ev => PlayAnimation(GetEntity(ev.User))); + } + + private void OnAnimationComplete(Entity ent, ref AnimationCompletedEvent args) + { + if (args.Key != FlippingComponent.AnimationKey) + return; + + PlayAnimation(ent); + } + + protected override void PlayAnimation(EntityUid user) + { + if (!_timing.IsFirstTimePredicted) + return; + + if (TerminatingOrDeleted(user)) + return; + + if (_animationSystem.HasRunningAnimation(user, FlippingComponent.AnimationKey)) + { + EnsureComp(user); + return; + } + + RemComp(user); + + var baseAngle = Angle.Zero; + if (EntityManager.TryGetComponent(user, out SpriteComponent? sprite)) + baseAngle = sprite.Rotation; + + var degrees = baseAngle.Degrees; + + var animation = new Animation + { + Length = TimeSpan.FromMilliseconds(400), + AnimationTracks = + { + new AnimationTrackComponentProperty + { + ComponentType = typeof(SpriteComponent), + Property = nameof(SpriteComponent.Rotation), + InterpolationMode = AnimationInterpolationMode.Linear, + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(degrees - 10), 0f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(degrees + 180), 0.2f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(degrees + 360), 0.2f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(degrees), 0f) + } + } + } + }; + + _animationSystem.Play(user, animation, FlippingComponent.AnimationKey); + } +} diff --git a/Content.Client/_White/Animations/FlippingComponent.cs b/Content.Client/_White/Animations/FlippingComponent.cs new file mode 100644 index 0000000000..504c5f7938 --- /dev/null +++ b/Content.Client/_White/Animations/FlippingComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Client._White.Animations; + +[RegisterComponent] +public sealed partial class FlippingComponent : Component +{ + public const string AnimationKey = "flip"; +} diff --git a/Content.Server/Construction/ConstructionSystem.Graph.cs b/Content.Server/Construction/ConstructionSystem.Graph.cs index 570360bf09..f612f3e7e4 100644 --- a/Content.Server/Construction/ConstructionSystem.Graph.cs +++ b/Content.Server/Construction/ConstructionSystem.Graph.cs @@ -9,6 +9,7 @@ using Robust.Shared.Containers; using Robust.Shared.Prototypes; using System.Linq; +using Content.Shared.Hands.Components; namespace Content.Server.Construction { @@ -304,8 +305,8 @@ public bool ChangeNode(EntityUid uid, EntityUid? userUid, string id, bool perfor return null; // [Optional] Exit if the new entity's prototype is a parent of the original - // E.g., if an entity with the 'AirlockCommand' prototype was to be replaced with a new entity that - // had the 'Airlock' prototype, and DoNotReplaceInheritingEntities was true, the code block would + // E.g., if an entity with the 'AirlockCommand' prototype was to be replaced with a new entity that + // had the 'Airlock' prototype, and DoNotReplaceInheritingEntities was true, the code block would // exit here because 'AirlockCommand' is derived from 'Airlock' if (GetCurrentNode(uid, construction)?.DoNotReplaceInheritingEntities == true && metaData.EntityPrototype?.ID != null) @@ -394,6 +395,17 @@ public bool ChangeNode(EntityUid uid, EntityUid? userUid, string id, bool perfor } } + // WD EDIT START + if (userUid != null && IsTransformParentOf(userUid.Value, transform) && TryComp(userUid, out HandsComponent? hands)) + { + var hand = hands.Hands.Values.FirstOrDefault(h => h.HeldEntity == uid); + if (hand != null) + _handsSystem.TryDrop(userUid.Value, hand, handsComp: hands); + + _handsSystem.PickupOrDrop(userUid, newUid, handsComp: hands); + } + // WD EDIT END + var entChangeEv = new ConstructionChangeEntityEvent(newUid, uid); RaiseLocalEvent(uid, entChangeEv); RaiseLocalEvent(newUid, entChangeEv, broadcast: true); @@ -410,6 +422,13 @@ public bool ChangeNode(EntityUid uid, EntityUid? userUid, string id, bool perfor return newUid; } + private bool IsTransformParentOf(EntityUid uid, TransformComponent target) // WD EDIT + { + var parentUid = target.ParentUid; + + return parentUid == uid || TryComp(parentUid, out TransformComponent? trans) && IsTransformParentOf(uid, trans); + } + /// /// Performs a construction graph change on a construction entity, also changing the node to a valid one on /// the new graph. diff --git a/Content.Server/_White/Animations/FlipOnHitSystem.cs b/Content.Server/_White/Animations/FlipOnHitSystem.cs new file mode 100644 index 0000000000..714cad5bc8 --- /dev/null +++ b/Content.Server/_White/Animations/FlipOnHitSystem.cs @@ -0,0 +1,17 @@ +using Content.Shared._White.Animations; +using Robust.Shared.Player; + +namespace Content.Server._White.Animations; + +public sealed class FlipOnHitSystem : SharedFlipOnHitSystem +{ + protected override void PlayAnimation(EntityUid user) + { + var filter = Filter.Pvs(user, entityManager: EntityManager); + + if (TryComp(user, out var actor)) + filter.RemovePlayer(actor.PlayerSession); + + RaiseNetworkEvent(new FlipOnHitEvent(GetNetEntity(user)), filter); + } +} diff --git a/Content.Shared/Item/ItemToggle/SharedItemToggleSystem.cs b/Content.Shared/Item/ItemToggle/SharedItemToggleSystem.cs index 523f67bac3..84ef3f8ca6 100644 --- a/Content.Shared/Item/ItemToggle/SharedItemToggleSystem.cs +++ b/Content.Shared/Item/ItemToggle/SharedItemToggleSystem.cs @@ -169,11 +169,11 @@ private void Activate(EntityUid uid, ItemToggleComponent itemToggle, bool predic /// private void Deactivate(EntityUid uid, ItemToggleComponent itemToggle, bool predicted, EntityUid? user = null) { + if (_netManager.IsClient) // WD EDIT + return; + var soundToPlay = itemToggle.SoundDeactivate; - if (predicted) - _audio.PlayPredicted(soundToPlay, uid, user); - else - _audio.PlayPvs(soundToPlay, uid); + _audio.PlayPvs(soundToPlay, uid); // END FIX HARDCODING var toggleUsed = new ItemToggledEvent(predicted, Activated: false, user); @@ -240,14 +240,14 @@ private void OnIsHotEvent(EntityUid uid, ItemToggleHotComponent itemToggleHot, I /// private void UpdateActiveSound(EntityUid uid, ItemToggleActiveSoundComponent activeSound, ref ItemToggledEvent args) { + if (_netManager.IsClient) // WD EDIT + return; + if (args.Activated) { if (activeSound.ActiveSound != null && activeSound.PlayingStream == null) { - if (args.Predicted) - activeSound.PlayingStream = _audio.PlayPredicted(activeSound.ActiveSound, uid, args.User, AudioParams.Default.WithLoop(true)).Value.Entity; - else - activeSound.PlayingStream = _audio.PlayPvs(activeSound.ActiveSound, uid, AudioParams.Default.WithLoop(true)).Value.Entity; + activeSound.PlayingStream = _audio.PlayPvs(activeSound.ActiveSound, uid, AudioParams.Default.WithLoop(true)).Value.Entity; } } else diff --git a/Content.Shared/_White/Animations/FlipOnHitComponent.cs b/Content.Shared/_White/Animations/FlipOnHitComponent.cs new file mode 100644 index 0000000000..b997a8e393 --- /dev/null +++ b/Content.Shared/_White/Animations/FlipOnHitComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._White.Animations; + +[RegisterComponent, NetworkedComponent] +public sealed partial class FlipOnHitComponent : Component; diff --git a/Content.Shared/_White/Animations/SharedFlipOnHitSystem.cs b/Content.Shared/_White/Animations/SharedFlipOnHitSystem.cs new file mode 100644 index 0000000000..e461e88877 --- /dev/null +++ b/Content.Shared/_White/Animations/SharedFlipOnHitSystem.cs @@ -0,0 +1,45 @@ +using Content.Shared.Item.ItemToggle.Components; +using Content.Shared.Standing; +using Content.Shared.Weapons.Melee.Events; +using Robust.Shared.Serialization; +using Robust.Shared.Timing; + +namespace Content.Shared._White.Animations; + +public abstract class SharedFlipOnHitSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly StandingStateSystem _standingState = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnHit); + } + + private void OnHit(Entity ent, ref MeleeHitEvent args) + { + if (!_timing.IsFirstTimePredicted) + return; + + if (args.HitEntities.Count == 0) + return; + + if (TryComp(ent, out ItemToggleComponent? itemToggle) && !itemToggle.Activated) + return; + + if (_standingState.IsDown(args.User)) + return; + + PlayAnimation(args.User); + } + + protected abstract void PlayAnimation(EntityUid user); +} + +[Serializable, NetSerializable] +public sealed class FlipOnHitEvent(NetEntity user) : EntityEventArgs +{ + public NetEntity User = user; +} diff --git a/Content.Shared/_White/Wield/ToggleableWieldedComponent.cs b/Content.Shared/_White/Wield/ToggleableWieldedComponent.cs new file mode 100644 index 0000000000..2d4e80241b --- /dev/null +++ b/Content.Shared/_White/Wield/ToggleableWieldedComponent.cs @@ -0,0 +1,4 @@ +namespace Content.Shared._White.Wield; + +[RegisterComponent] +public sealed partial class ToggleableWieldedComponent : Component; diff --git a/Content.Shared/_White/Wield/ToggleableWieldedSystem.cs b/Content.Shared/_White/Wield/ToggleableWieldedSystem.cs new file mode 100644 index 0000000000..bf25f2a7e4 --- /dev/null +++ b/Content.Shared/_White/Wield/ToggleableWieldedSystem.cs @@ -0,0 +1,20 @@ +using Content.Shared.Item.ItemToggle.Components; +using Content.Shared.Wieldable.Components; + +namespace Content.Shared._White.Wield; + +public sealed class ToggleableWieldedSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(AttemptActivate); + } + + private void AttemptActivate(Entity ent, ref ItemToggleActivateAttemptEvent args) + { + if (TryComp(ent, out WieldableComponent? wieldable) && !wieldable.Wielded) + args.Cancelled = true; + } +} diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml index 0cbc824365..723d950835 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml @@ -1,8 +1,9 @@ - type: entity name: energy sword parent: BaseItem - id: EnergySword + id: EnergySwordBase description: A very loud & dangerous sword with a beam made of pure, concentrated plasma. Cuts through unarmored targets like butter. + abstract: true components: - type: EnergySword - type: ItemToggle @@ -35,27 +36,15 @@ variation: 0.125 activatedDamage: types: - Slash: 8 - Heat: 10 + Slash: 15 + Heat: 15 Structural: 20 - - type: Sprite - sprite: Objects/Weapons/Melee/e_sword.rsi - layers: - - state: e_sword - - state: e_sword_blade - color: "#FFFFFF" - visible: false - shader: unshaded - map: [ "blade" ] - type: MeleeWeapon wideAnimationRotation: -135 - attackRate: 1.25 + attackRate: 1 damage: types: Blunt: 4.5 - - type: Item - size: Small - sprite: DeltaV/Objects/Weapons/Melee/e_sword.rsi # Delta-V - type: UseDelay delay: 1.0 - type: PointLight @@ -76,19 +65,59 @@ shader: unshaded - type: DisarmMalus malus: 0 - - type: Reflect - enabled: false - type: IgnitionSource temperature: 700 +- type: entity + name: energy sword + parent: EnergySwordBase + id: EnergySword + description: A very loud & dangerous sword with a beam made of pure, concentrated plasma. Cuts through unarmored targets like butter. + components: + - type: Sprite + sprite: Objects/Weapons/Melee/e_sword.rsi + layers: + - state: e_sword + - state: e_sword_blade + color: "#FFFFFF" + visible: false + shader: unshaded + map: [ "blade" ] + - type: Item + size: Small + sprite: DeltaV/Objects/Weapons/Melee/e_sword.rsi # Delta-V + - type: Reflect # WD EDIT + reflectProb: .5 + spread: 75 + reflects: + - Energy #DeltaV: + - type: Construction # WD EDIT + deconstructionTarget: null + graph: EnergyDoubleSwordGraph + node: esword + - type: Tag # WD EDIT + tags: + - EnergySword + - type: entity name: pen - parent: EnergySword + parent: EnergySwordBase id: EnergyDagger suffix: E-Dagger description: 'A dark ink pen.' components: - - type: EnergySword + - type: Sprite + sprite: Objects/Weapons/Melee/e_dagger.rsi + layers: + - state: e_sword + - state: e_sword_blade + color: "#FFFFFF" + visible: false + shader: unshaded + map: [ "blade" ] + - type: Item + size: Tiny + sprite: Objects/Weapons/Melee/e_dagger.rsi - type: ItemToggle soundActivate: path: /Audio/Weapons/ebladeon.ogg @@ -116,15 +145,6 @@ volume: -6 - type: ItemToggleDisarmMalus activatedDisarmMalus: 0.4 - - type: Sprite - sprite: Objects/Weapons/Melee/e_dagger.rsi - layers: - - state: e_sword - - state: e_sword_blade - color: "#FFFFFF" - visible: false - shader: unshaded - map: [ "blade" ] - type: MeleeWeapon wideAnimationRotation: -135 attackRate: 1 @@ -132,11 +152,11 @@ damage: types: Blunt: 1 - - type: Item - size: Tiny - sprite: Objects/Weapons/Melee/e_dagger.rsi - - type: UseDelay - delay: 1.0 + - type: Reflect # WD EDIT + reflectProb: .25 + spread: 75 + reflects: + - Energy - type: PointLight enabled: false radius: 1.5 @@ -144,20 +164,9 @@ color: white netsync: false - type: Appearance - - type: ToggleableLightVisuals - spriteLayer: blade - inhandVisuals: - left: - - state: inhand-left-blade - shader: unshaded - right: - - state: inhand-right-blade - shader: unshaded - type: Tag tags: - Write - - type: DisarmMalus - malus: 0 - type: entity parent: BaseItem @@ -166,11 +175,11 @@ suffix: E-Dagger description: A small box containing an e-dagger. Packaging disintegrates when opened, leaving no evidence behind. components: - - type: Item - size: Tiny - type: Sprite sprite: Objects/Storage/penbox.rsi state: e_dagger + - type: Item + size: Tiny - type: SpawnItemsOnUse items: - id: EnergyDagger @@ -179,19 +188,10 @@ - type: entity name: energy cutlass - parent: EnergySword + parent: EnergySwordBase id: EnergyCutlass description: An exotic energy weapon, brutal blade crackling with crudely harnessed plasma. #DeltaV - nicer description. components: - - type: EnergySword - - type: ItemToggleMeleeWeapon - activatedDamage: - types: - Slash: 10 - Heat: 12 - deactivatedSecret: true - - type: ItemToggleDisarmMalus - activatedDisarmMalus: 0.6 - type: Sprite sprite: DeltaV/Objects/Weapons/Melee/e_cutlass.rsi # DeltaV layers: @@ -201,6 +201,15 @@ visible: false shader: unshaded map: [ "blade" ] + - type: Item + size: Small + sprite: DeltaV/Objects/Weapons/Melee/e_cutlass.rsi #DeltaV + - type: ItemToggleMeleeWeapon + activatedDamage: + types: + Slash: 10 + Heat: 12 + deactivatedSecret: true - type: MeleeWeapon # DeltaV - reduced attack rate of e-cutlass; slower, more brutal swings attackRate: 0.75 soundHit: @@ -208,61 +217,13 @@ damage: types: Blunt: 6 - - type: Item - size: Small - sprite: DeltaV/Objects/Weapons/Melee/e_cutlass.rsi #DeltaV - - type: ToggleableLightVisuals - spriteLayer: blade - inhandVisuals: - left: - - state: inhand-left-blade - shader: unshaded - right: - - state: inhand-right-blade - shader: unshaded - type: entity name: double-bladed energy sword - parent: EnergySword + parent: EnergySwordBase id: EnergySwordDouble description: Syndicate Command Interns thought that having one blade on the energy sword was not enough. This can be stored in pockets. components: - - type: EnergySword - - type: ItemToggle - soundActivate: - path: /Audio/Weapons/ebladeon.ogg - params: - volume: 3 - soundDeactivate: - path: /Audio/Weapons/ebladeoff.ogg - params: - volume: 3 - - type: ItemToggleMeleeWeapon - activatedSoundOnSwing: - path: /Audio/Weapons/eblademiss.ogg - params: - volume: 3 - variation: 0.250 - activatedDamage: - types: - Slash: 8 - Heat: 13 - Structural: 15 - - type: ItemToggleActiveSound - activeSound: - path: /Audio/Weapons/ebladehum.ogg - params: - volume: 3 - - type: ItemToggleDisarmMalus - activatedDisarmMalus: 0.7 - - type: Wieldable - - type: MeleeWeapon - wideAnimationRotation: -135 - attackRate: 1.5 - angle: 100 - damage: - types: - Blunt: 4.5 - type: Sprite sprite: Objects/Weapons/Melee/e_sword_double.rsi layers: @@ -275,19 +236,20 @@ - type: Item size: Small sprite: Objects/Weapons/Melee/e_sword_double-inhands.rsi - - type: Reflect - reflectProb: .80 #DeltaV: 80% Energy Reflection but no ballistics. + - type: Wieldable + - type: IncreaseDamageOnWield + damage: + types: + Slash: 15 + Heat: 15 + - type: Reflect # WD EDIT + reflectProb: 1 spread: 75 reflects: - - Energy #DeltaV: 80% Energy Reflection but no ballistics. - - type: UseDelay - delay: 1 - - type: ToggleableLightVisuals - spriteLayer: blade - inhandVisuals: - left: - - state: inhand-left-blade - shader: unshaded - right: - - state: inhand-right-blade - shader: unshaded + - Energy + - type: ToggleableWielded # WD EDIT + - type: FlipOnHit # WD EDIT + - type: Construction # WD EDIT + deconstructionTarget: null + graph: EnergyDoubleSwordGraph + node: desword diff --git a/Resources/Prototypes/_White/Recipes/hidden_crafts.yml b/Resources/Prototypes/_White/Recipes/hidden_crafts.yml new file mode 100644 index 0000000000..c55b8e544d --- /dev/null +++ b/Resources/Prototypes/_White/Recipes/hidden_crafts.yml @@ -0,0 +1,11 @@ +- type: constructionGraph + id: EnergyDoubleSwordGraph + start: esword + graph: + - node: esword + edges: + - to: desword + steps: + - tag: EnergySword + - node: desword + entity: EnergySwordDouble \ No newline at end of file diff --git a/Resources/Prototypes/_White/tags.yml b/Resources/Prototypes/_White/tags.yml new file mode 100644 index 0000000000..62e70f42a9 --- /dev/null +++ b/Resources/Prototypes/_White/tags.yml @@ -0,0 +1,2 @@ +- type: Tag + id: EnergySword \ No newline at end of file