diff --git a/Content.Server/Stunnable/Components/StunOnCollideComponent.cs b/Content.Server/Stunnable/Components/StunOnCollideComponent.cs index 1ce1cbea575..cec99f0df72 100644 --- a/Content.Server/Stunnable/Components/StunOnCollideComponent.cs +++ b/Content.Server/Stunnable/Components/StunOnCollideComponent.cs @@ -1,32 +1,40 @@ -namespace Content.Server.Stunnable.Components +using Content.Server.Stunnable.Systems; +using Content.Shared.Whitelist; + +namespace Content.Server.Stunnable.Components; + +/// +/// Adds stun when it collides with an entity +/// +[RegisterComponent, Access(typeof(StunOnCollideSystem))] +public sealed partial class StunOnCollideComponent : Component { - /// - /// Adds stun when it collides with an entity - /// - [RegisterComponent, Access(typeof(StunOnCollideSystem))] - public sealed partial class StunOnCollideComponent : Component - { - // TODO: Can probably predict this. + // TODO: Can probably predict this. - // See stunsystem for what these do - [DataField("stunAmount")] - public int StunAmount; + [DataField] + public TimeSpan StunAmount = TimeSpan.FromSeconds(5); - [DataField("knockdownAmount")] - public int KnockdownAmount; + [DataField] + public TimeSpan KnockdownAmount = TimeSpan.FromSeconds(5); - [DataField("slowdownAmount")] - public int SlowdownAmount; + [DataField] + public TimeSpan SlowdownAmount = TimeSpan.FromSeconds(10); - [DataField("walkSpeedMultiplier")] - public float WalkSpeedMultiplier = 1f; + [DataField] + public float WalkSpeedMultiplier = 1f; - [DataField("runSpeedMultiplier")] - public float RunSpeedMultiplier = 1f; + [DataField] + public float RunSpeedMultiplier = 1f; - /// - /// Fixture we track for the collision. - /// - [DataField("fixture")] public string FixtureID = "projectile"; - } + /// + /// Fixture we track for the collision. + /// + [DataField] + public string FixtureId = "projectile"; + + /// + /// Entities excluded from collision check. + /// + [DataField] + public EntityWhitelist? Blacklist; } diff --git a/Content.Server/Stunnable/Systems/StunOnCollideSystem.cs b/Content.Server/Stunnable/Systems/StunOnCollideSystem.cs index 52e3cab79c5..b7c23477074 100644 --- a/Content.Server/Stunnable/Systems/StunOnCollideSystem.cs +++ b/Content.Server/Stunnable/Systems/StunOnCollideSystem.cs @@ -1,50 +1,51 @@ using Content.Server.Stunnable.Components; -using Content.Shared.Standing; using Content.Shared.StatusEffect; using JetBrains.Annotations; -using Robust.Shared.Physics.Dynamics; using Content.Shared.Throwing; +using Content.Shared.Whitelist; using Robust.Shared.Physics.Events; -namespace Content.Server.Stunnable +namespace Content.Server.Stunnable.Systems; + +[UsedImplicitly] +internal sealed class StunOnCollideSystem : EntitySystem { - [UsedImplicitly] - internal sealed class StunOnCollideSystem : EntitySystem + [Dependency] private readonly StunSystem _stunSystem = default!; + [Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(HandleCollide); + SubscribeLocalEvent(HandleThrow); + } + + private void TryDoCollideStun(Entity ent, EntityUid target) { - [Dependency] private readonly StunSystem _stunSystem = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(HandleCollide); - SubscribeLocalEvent(HandleThrow); - } - - private void TryDoCollideStun(EntityUid uid, StunOnCollideComponent component, EntityUid target) - { - - if (EntityManager.TryGetComponent(target, out var status)) - { - _stunSystem.TryStun(target, TimeSpan.FromSeconds(component.StunAmount), true, status); - - _stunSystem.TryKnockdown(target, TimeSpan.FromSeconds(component.KnockdownAmount), true, - status); - - _stunSystem.TrySlowdown(target, TimeSpan.FromSeconds(component.SlowdownAmount), true, - component.WalkSpeedMultiplier, component.RunSpeedMultiplier, status); - } - } - private void HandleCollide(EntityUid uid, StunOnCollideComponent component, ref StartCollideEvent args) - { - if (args.OurFixtureId != component.FixtureID) - return; - - TryDoCollideStun(uid, component, args.OtherEntity); - } - - private void HandleThrow(EntityUid uid, StunOnCollideComponent component, ThrowDoHitEvent args) - { - TryDoCollideStun(uid, component, args.Target); - } + if (!EntityManager.TryGetComponent(target, out var status) || + ent.Comp.Blacklist is { } blacklist && _entityWhitelist.IsValid(blacklist, target)) + return; + + _stunSystem.TryStun(target, ent.Comp.StunAmount, true, status); + _stunSystem.TryKnockdown(target, ent.Comp.KnockdownAmount, true, status); + + _stunSystem.TrySlowdown( + target, + ent.Comp.SlowdownAmount, + true, + ent.Comp.WalkSpeedMultiplier, + ent.Comp.RunSpeedMultiplier, + status); } + + private void HandleCollide(Entity ent, ref StartCollideEvent args) + { + if (args.OurFixtureId != ent.Comp.FixtureId) + return; + + TryDoCollideStun(ent, args.OtherEntity); + } + + private void HandleThrow(Entity ent, ref ThrowDoHitEvent args) => + TryDoCollideStun(ent, args.Target); } diff --git a/Content.Shared/Blocking/BlockingSystem.User.cs b/Content.Shared/Blocking/BlockingSystem.User.cs index 2cd1db7f1fe..584df3a3ffc 100644 --- a/Content.Shared/Blocking/BlockingSystem.User.cs +++ b/Content.Shared/Blocking/BlockingSystem.User.cs @@ -27,7 +27,11 @@ private void OnParentChanged(EntityUid uid, BlockingUserComponent component, ref UserStopBlocking(uid, component); } - private void OnInsertAttempt(EntityUid uid, BlockingUserComponent component, ContainerGettingInsertedAttemptEvent args) + private void OnInsertAttempt( + EntityUid uid, + BlockingUserComponent component, + ContainerGettingInsertedAttemptEvent args + ) { UserStopBlocking(uid, component); } @@ -42,32 +46,27 @@ private void OnAnchorChanged(EntityUid uid, BlockingUserComponent component, ref private void OnUserDamageModified(EntityUid uid, BlockingUserComponent component, DamageModifyEvent args) { - if (TryComp(component.BlockingItem, out var blocking)) - { - if (args.Damage.GetTotal() <= 0) - return; - - // A shield should only block damage it can itself absorb. To determine that we need the Damageable component on it. - if (!TryComp(component.BlockingItem, out var dmgComp)) - return; + // A shield should only block damage it can itself absorb. To determine that we need the Damageable component on it. + if (!TryComp(component.BlockingItem, out var blocking) || args.Damage.GetTotal() <= 0 || + !TryComp(component.BlockingItem, out var dmgComp)) + return; - var blockFraction = blocking.IsBlocking ? blocking.ActiveBlockFraction : blocking.PassiveBlockFraction; - blockFraction = Math.Clamp(blockFraction, 0, 1); - _damageable.TryChangeDamage(component.BlockingItem, blockFraction * args.OriginalDamage); + var ev = new BeforeBlockingEvent(uid, args.Origin); + RaiseLocalEvent(component.BlockingItem.Value, ev); + if (ev.Cancelled) + return; - var modify = new DamageModifierSet(); - foreach (var key in dmgComp.Damage.DamageDict.Keys) - { - modify.Coefficients.TryAdd(key, 1 - blockFraction); - } + var blockFraction = blocking.IsBlocking ? blocking.ActiveBlockFraction : blocking.PassiveBlockFraction; + blockFraction = Math.Clamp(blockFraction, 0, 1); + _damageable.TryChangeDamage(component.BlockingItem, blockFraction * args.OriginalDamage); - args.Damage = DamageSpecifier.ApplyModifierSet(args.Damage, modify); + var modify = new DamageModifierSet(); + foreach (var key in dmgComp.Damage.DamageDict.Keys) + modify.Coefficients.TryAdd(key, 1 - blockFraction); - if (blocking.IsBlocking && !args.Damage.Equals(args.OriginalDamage)) - { - _audio.PlayPvs(blocking.BlockSound, uid); - } - } + args.Damage = DamageSpecifier.ApplyModifierSet(args.Damage, modify); + if (blocking.IsBlocking && !args.Damage.Equals(args.OriginalDamage)) + _audio.PlayPvs(blocking.BlockSound, uid); } private void OnDamageModified(EntityUid uid, BlockingComponent component, DamageModifyEvent args) @@ -87,7 +86,6 @@ private void OnEntityTerminating(EntityUid uid, BlockingUserComponent component, return; StopBlockingHelper(component.BlockingItem.Value, blockingComponent, uid); - } /// diff --git a/Content.Shared/Blocking/Components/BlockingComponent.cs b/Content.Shared/Blocking/Components/BlockingComponent.cs index f869c20679d..43162049b60 100644 --- a/Content.Shared/Blocking/Components/BlockingComponent.cs +++ b/Content.Shared/Blocking/Components/BlockingComponent.cs @@ -77,3 +77,12 @@ public sealed partial class BlockingComponent : Component [DataField("activeBlockFraction"), ViewVariables(VVAccess.ReadWrite)] public float ActiveBlockFraction = 1.0f; } + +/// +/// Raised directed on the blocking object when attempting to block. +/// +public sealed class BeforeBlockingEvent(EntityUid user, EntityUid? origin) : CancellableEntityEventArgs +{ + public EntityUid User = user; + public EntityUid? Origin = origin; +} diff --git a/Content.Shared/Weapons/Reflect/ReflectSystem.cs b/Content.Shared/Weapons/Reflect/ReflectSystem.cs index 03ad97edff2..76c98a427f5 100644 --- a/Content.Shared/Weapons/Reflect/ReflectSystem.cs +++ b/Content.Shared/Weapons/Reflect/ReflectSystem.cs @@ -15,6 +15,8 @@ using Content.Shared.Standing; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; +using Content.Shared.WhiteDream.BloodCult.BloodCultist; +using Content.Shared.WhiteDream.BloodCult.Items; using Robust.Shared.Audio.Systems; using Robust.Shared.Network; using Robust.Shared.Physics.Components; @@ -112,6 +114,10 @@ private bool TryReflectProjectile(EntityUid user, EntityUid reflector, EntityUid ) return false; + // Non cultists can't use cult items to reflect anything. + if (HasComp(reflector) && !HasComp(user)) + return false; + if (!_random.Prob(CalcReflectChance(reflector, reflect))) return false; @@ -202,20 +208,19 @@ private bool TryReflectHitscan( Vector2 direction, [NotNullWhen(true)] out Vector2? newDirection) { + newDirection = null; if (!TryComp(reflector, out var reflect) || !reflect.Enabled || TryComp(reflector, out var staminaComponent) && staminaComponent.Critical || _standing.IsDown(reflector)) - { - newDirection = null; return false; - } + + // Non cultists can't use cult items to reflect anything. + if (HasComp(reflector) && !HasComp(user)) + return false; if (!_random.Prob(CalcReflectChance(reflector, reflect))) - { - newDirection = null; return false; - } if (_netManager.IsServer) { diff --git a/Content.Shared/WhiteDream/BloodCult/Items/CultItemSystem.cs b/Content.Shared/WhiteDream/BloodCult/Items/CultItemSystem.cs index 4eb4acd77b6..1dbbb769aad 100644 --- a/Content.Shared/WhiteDream/BloodCult/Items/CultItemSystem.cs +++ b/Content.Shared/WhiteDream/BloodCult/Items/CultItemSystem.cs @@ -1,4 +1,5 @@ -using Content.Shared.Ghost; +using Content.Shared.Blocking; +using Content.Shared.Ghost; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Inventory.Events; @@ -18,12 +19,11 @@ public sealed class CultItemSystem : EntitySystem public override void Initialize() { - base.Initialize(); - SubscribeLocalEvent(OnActivate); SubscribeLocalEvent(OnBeforeThrow); SubscribeLocalEvent(OnEquipAttempt); SubscribeLocalEvent(OnMeleeAttempt); + SubscribeLocalEvent(OnBeforeBlocking); } private void OnActivate(Entity item, ref ActivateInWorldEvent args) @@ -35,6 +35,15 @@ private void OnActivate(Entity item, ref ActivateInWorldEvent KnockdownAndDropItem(item, args.User, Loc.GetString("cult-item-component-generic")); } + private void OnBeforeThrow(Entity item, ref BeforeThrowEvent args) + { + if (CanUse(args.PlayerUid)) + return; + + args.Cancelled = true; + KnockdownAndDropItem(item, args.PlayerUid, Loc.GetString("cult-item-component-throw-fail")); + } + private void OnEquipAttempt(Entity item, ref BeingEquippedAttemptEvent args) { if (CanUse(args.EquipTarget)) @@ -53,13 +62,13 @@ private void OnMeleeAttempt(Entity item, ref AttemptMeleeEven KnockdownAndDropItem(item, args.PlayerUid, Loc.GetString("cult-item-component-attack-fail")); } - private void OnBeforeThrow(Entity item, ref BeforeThrowEvent args) + private void OnBeforeBlocking(Entity item, ref BeforeBlockingEvent args) { - if (CanUse(args.PlayerUid)) + if (CanUse(args.User)) return; - args.Cancelled = true; - KnockdownAndDropItem(item, args.PlayerUid, Loc.GetString("cult-item-component-throw-fail")); + args.Cancel(); + KnockdownAndDropItem(item, args.User, Loc.GetString("cult-item-component-block-fail")); } private void KnockdownAndDropItem(Entity item, EntityUid user, string message) @@ -69,8 +78,5 @@ private void KnockdownAndDropItem(Entity item, EntityUid user _hands.TryDrop(user); } - private bool CanUse(EntityUid? uid) - { - return HasComp(uid) || HasComp(uid); - } + private bool CanUse(EntityUid? uid) => HasComp(uid) || HasComp(uid); } diff --git a/Resources/Locale/en-US/white-dream/cult/items/general.ftl b/Resources/Locale/en-US/white-dream/cult/items/general.ftl index 26f9e2dd46f..6ad4938adea 100644 --- a/Resources/Locale/en-US/white-dream/cult/items/general.ftl +++ b/Resources/Locale/en-US/white-dream/cult/items/general.ftl @@ -2,6 +2,7 @@ cult-item-component-attack-fail = The weapon refuses to obey your will. You can't attack with it. cult-item-component-equip-fail = The armor refuses to obey your will. You can't equip it. cult-item-component-throw-fail = The weapon refuses to obey your will. You can't throw it. +cult-item-component-block-fail = The shield betrays you! soul-shard-try-insert-no-soul = The shard has no soul. soul-shard-selector-form = Select form. diff --git a/Resources/Prototypes/Entities/Objects/Shields/shields.yml b/Resources/Prototypes/Entities/Objects/Shields/shields.yml index e7ebb1b98d4..f0d9a7c0b1b 100644 --- a/Resources/Prototypes/Entities/Objects/Shields/shields.yml +++ b/Resources/Prototypes/Entities/Objects/Shields/shields.yml @@ -309,55 +309,6 @@ Piercing: 1 #Have it break into brass when clock cult is in -- type: entity - name: mirror shield - parent: BaseShield - id: MirrorShield - description: Glows an eerie red. You hear the Geometer whispering... - components: - - type: Sprite - state: mirror-icon - - type: Item - heldPrefix: mirror - - type: Reflect - reflectProb: 0.95 - innate: true - reflects: - - Energy - - type: Blocking #Mirror shield reflects heat/laser, but is relatively weak to everything else. - passiveBlockModifier: - coefficients: - Blunt: 1.2 - Slash: 1.2 - Piercing: 1.2 - Heat: .7 - activeBlockModifier: - coefficients: - Blunt: 1.2 - Slash: 1.2 - Piercing: 1.2 - Heat: .6 - flatReductions: - Heat: 1 - blockSound: !type:SoundPathSpecifier - path: /Audio/Effects/glass_step.ogg - - type: Destructible - thresholds: - - trigger: - !type:DamageTrigger - damage: 40 - behaviors: - - !type:DoActsBehavior - acts: [ "Destruction" ] - - !type:PlaySoundBehavior - sound: - collection: GlassBreak - - !type:SpawnEntitiesBehavior - spawn: - SheetGlass: - min: 5 - max: 5 - - type: entity name: energy shield parent: BaseItem diff --git a/Resources/Prototypes/WhiteDream/Entities/Objects/Cult/constructs.yml b/Resources/Prototypes/WhiteDream/Entities/Objects/Cult/constructs.yml index dc2d99e87bf..93806feb0a9 100644 --- a/Resources/Prototypes/WhiteDream/Entities/Objects/Cult/constructs.yml +++ b/Resources/Prototypes/WhiteDream/Entities/Objects/Cult/constructs.yml @@ -75,6 +75,7 @@ - Alive - Dead - type: CombatMode + canDisarm: false - type: Internals - type: Examiner - type: Speech @@ -87,7 +88,6 @@ - type: ContainerContainer containers: actions: !type:Container - canDisarm: false - type: Visibility - type: ContentEye - type: Actions diff --git a/Resources/Prototypes/WhiteDream/Entities/Objects/Items/cult.yml b/Resources/Prototypes/WhiteDream/Entities/Objects/Items/cult.yml index b0d2a8feb7b..e85ee49f20e 100644 --- a/Resources/Prototypes/WhiteDream/Entities/Objects/Items/cult.yml +++ b/Resources/Prototypes/WhiteDream/Entities/Objects/Items/cult.yml @@ -128,7 +128,7 @@ - type: entity parent: BaseItem id: ShadowShackles - name: Shadow Shackles + name: shadow shackles description: Shackles that bind the wrists with sinister magic. components: - type: Item @@ -143,3 +143,58 @@ - type: Sprite sprite: WhiteDream/BloodCult/actions.rsi state: cuff + +- type: entity + parent: BaseItem + id: MirrorShieldCult + name: mirror shield + description: An infamous shield used by Nar'Sien sects to confuse and disorient their enemies. Its edges are weighted for use as a throwing weapon - capable of disabling multiple foes with preternatural accuracy. + components: + - type: Sprite + sprite: WhiteDream/BloodCult/Entities/Items/mirror_shield.rsi + state: icon + - type: Item + size: Ginormous + - type: CultItem + - type: StunOnCollide + blacklist: + components: + - BloodCultist + - type: Reflect + reflectProb: 0.75 + innate: true + reflects: + - Energy + - type: Blocking + passiveBlockModifier: + coefficients: + Blunt: 0.8 + Slash: 0.8 + Piercing: 0.8 + Heat: 0.8 + activeBlockModifier: + coefficients: + Blunt: 0.8 + Slash: 0.8 + Piercing: 0.8 + Heat: 0.8 + flatReductions: + Blunt: 1 + Slash: 1 + Piercing: 1 + Heat: 1 + blockSound: !type:SoundPathSpecifier + path: /Audio/Effects/glass_step.ogg + - type: Damageable + damageContainer: Shield + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 100 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - !type:PlaySoundBehavior + sound: + collection: GlassBreak diff --git a/Resources/Prototypes/WhiteDream/Entities/Objects/Structures/Cult/factories.yml b/Resources/Prototypes/WhiteDream/Entities/Objects/Structures/Cult/factories.yml index 5250f8f2ccc..052d92158d2 100644 --- a/Resources/Prototypes/WhiteDream/Entities/Objects/Structures/Cult/factories.yml +++ b/Resources/Prototypes/WhiteDream/Entities/Objects/Structures/Cult/factories.yml @@ -68,8 +68,6 @@ entries: - prototype: ConstructShell - prototype: WhetstoneCult - # TODO: Add prototypes - # - Flask of Unholy Water - type: Construction graph: CultFactoryAltar node: altar @@ -84,6 +82,7 @@ sprite: WhiteDream/BloodCult/Entities/Structures/forge.rsi - type: TimedFactory entries: + - prototype: MirrorShieldCult - prototype: EldritchLongsword - prototype: ClothingOuterCultArmor - prototype: ClothingOuterRobesCultTrue diff --git a/Resources/Prototypes/WhiteDream/Recipes/Construction/cult_graphs.yml b/Resources/Prototypes/WhiteDream/Recipes/Construction/cult_graphs.yml index 4f639f16698..8bb2dd84213 100644 --- a/Resources/Prototypes/WhiteDream/Recipes/Construction/cult_graphs.yml +++ b/Resources/Prototypes/WhiteDream/Recipes/Construction/cult_graphs.yml @@ -35,9 +35,6 @@ - type: constructionGraph id: CultFactoryForge start: start - icon: - sprite: WhiteDream/BloodCult/Entities/Structures/forge.rsi - state: icon_off graph: - node: start edges: @@ -54,9 +51,6 @@ - type: constructionGraph id: CultFactoryArchives - icon: - sprite: WhiteDream/BloodCult/Entities/Structures/archives.rsi - state: icon_off start: start graph: - node: start @@ -74,9 +68,6 @@ - type: constructionGraph id: CultDoor - icon: - sprite: WhiteDream/BloodCult/Entities/Structures/cult_airlock.rsi - state: closed start: start graph: - node: start @@ -95,9 +86,6 @@ - type: constructionGraph id: CultGirder start: start - icon: - sprite: WhiteDream/BloodCult/Entities/Structures/cult_girder.rsi - state: icon graph: - node: start edges: diff --git a/Resources/Prototypes/ai_factions.yml b/Resources/Prototypes/ai_factions.yml index cdbbf868662..c0f7c7da6a0 100644 --- a/Resources/Prototypes/ai_factions.yml +++ b/Resources/Prototypes/ai_factions.yml @@ -44,6 +44,7 @@ - PetsNT - Zombie - Revolutionary + - GeometerOfBlood - type: npcFaction id: SimpleNeutral diff --git a/Resources/Textures/WhiteDream/BloodCult/Entities/Items/mirror_shield.rsi/icon-off.png b/Resources/Textures/WhiteDream/BloodCult/Entities/Items/mirror_shield.rsi/icon-off.png deleted file mode 100644 index 42872ed07db..00000000000 Binary files a/Resources/Textures/WhiteDream/BloodCult/Entities/Items/mirror_shield.rsi/icon-off.png and /dev/null differ diff --git a/Resources/Textures/WhiteDream/BloodCult/Entities/Items/mirror_shield.rsi/meta.json b/Resources/Textures/WhiteDream/BloodCult/Entities/Items/mirror_shield.rsi/meta.json index 4a45df73f6c..244e0b78906 100644 --- a/Resources/Textures/WhiteDream/BloodCult/Entities/Items/mirror_shield.rsi/meta.json +++ b/Resources/Textures/WhiteDream/BloodCult/Entities/Items/mirror_shield.rsi/meta.json @@ -77,9 +77,6 @@ 0.1 ] ] - }, - { - "name": "icon-off" } ] }