diff --git a/Content.Server/SS220/ReactiveTeleportArmor/ReactiveTeleportArmorComponent.cs b/Content.Server/SS220/ReactiveTeleportArmor/ReactiveTeleportArmorComponent.cs new file mode 100644 index 00000000000000..c891e460a5bbc1 --- /dev/null +++ b/Content.Server/SS220/ReactiveTeleportArmor/ReactiveTeleportArmorComponent.cs @@ -0,0 +1,12 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.tx +namespace Content.Server.SS220.ReactiveTeleportArmor +{ + /// + /// Intermediate component to work with TeleportOnDamageComponent + /// + [RegisterComponent] + public sealed partial class ReactiveTeleportArmorComponent : Component + { + + } +} diff --git a/Content.Server/SS220/ReactiveTeleportArmor/ReactiveTeleportArmorSystem.cs b/Content.Server/SS220/ReactiveTeleportArmor/ReactiveTeleportArmorSystem.cs new file mode 100644 index 00000000000000..bbfe5a3ece13b3 --- /dev/null +++ b/Content.Server/SS220/ReactiveTeleportArmor/ReactiveTeleportArmorSystem.cs @@ -0,0 +1,177 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt +using Robust.Shared.Random; +using Content.Shared.Damage; +using Content.Shared.Movement.Pulling.Systems; +using Content.Shared.Movement.Pulling.Components; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Map; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; +using Robust.Shared.Map.Components; +using System.Numerics; +using Content.Shared.Physics; +using Robust.Shared.Collections; +using Content.Shared.Clothing; +using Content.Shared.Clothing.EntitySystems; +using Content.Server.Explosion.EntitySystems; +using Content.Shared.Item; +using Content.Shared.Item.ItemToggle; +using Content.Shared.Item.ItemToggle.Components; +using Timer = Robust.Shared.Timing.Timer; + +namespace Content.Server.SS220.ReactiveTeleportArmor +{ + internal class ReactiveTeleportArmorSystem : EntitySystem + { + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedTransformSystem _xform = default!; + [Dependency] private readonly PullingSystem _pullingSystem = default!; + [Dependency] private readonly EntityLookupSystem _lookupSystem = default!; + [Dependency] private readonly SharedMapSystem _mapSystem = default!; + [Dependency] private readonly ExplosionSystem _explosion = default!; + [Dependency] private readonly SharedItemSystem _item = default!; + [Dependency] private readonly ClothingSystem _clothing = default!; + [Dependency] private readonly ItemToggleSystem _toggle = default!; + + private EntityQuery _physicsQuery; + private HashSet> _targetGrids = []; + + public override void Initialize() + { + _physicsQuery = GetEntityQuery(); + + base.Initialize(); + SubscribeLocalEvent(TeleporWhenDamaged); + SubscribeLocalEvent(OnEquip); + SubscribeLocalEvent(OnUnequip); + SubscribeLocalEvent(ToggleDone); + } + + private void OnEquip(Entity ent, ref ClothingGotEquippedEvent args) + { + EnsureComp(args.Wearer, out var comp); + comp.SavedUid = ent; + } + + private void OnUnequip(Entity ent, ref ClothingGotUnequippedEvent args) + { + RemComp(args.Wearer); + } + + private void ToggleDone(Entity ent, ref ItemToggledEvent args) + { + var prefix = args.Activated ? "on" : null; + _item.SetHeldPrefix(ent, prefix); + _clothing.SetEquippedPrefix(ent, prefix); + } + + private void TeleporWhenDamaged(Entity ent, ref DamageChangedEvent args) + { + if (!TryComp(ent, out var armor)) + return; + + var xform = Transform(ent.Owner); + var targetCoords = SelectRandomTileInRange(xform, armor.TeleportRadius); + + if (!args.DamageIncreased || args.DamageDelta == null) + return; + ///teleport entity if taken damage && coord = !null && armor is on && !cooldown + if (args.DamageDelta.GetTotal() >= ent.Comp.WakeThreshold && targetCoords != null && _toggle.IsActivated(ent.Comp.SavedUid) && !ent.Comp.OnCoolDown) + { + ent.Comp.OnCoolDown = true; + + // We need stop the user from being pulled so they don't just get "attached" with whoever is pulling them. + // This can for example happen when the user is cuffed and being pulled. + if (TryComp(ent.Owner, out var pull) && _pullingSystem.IsPulled(ent.Owner, pull)) + _pullingSystem.TryStopPull(ent.Owner, pull); + + if (_random.Prob(ent.Comp.TeleportChance)) + { + _xform.SetCoordinates(ent.Owner, targetCoords.Value); + _audio.PlayPvs(armor.TeleportSound, ent.Owner); + SelectRandomTileInRange(xform, armor.TeleportRadius); + } + else + { + _explosion.TriggerExplosive(ent.Comp.SavedUid); + } + Timer.Spawn(ent.Comp.CoolDownTime, () => ent.Comp.OnCoolDown = false); + } + } + + private EntityCoordinates? SelectRandomTileInRange(TransformComponent userXform, float radius) + { + var userCoords = userXform.Coordinates.ToMap(EntityManager, _xform); + _targetGrids.Clear(); + _lookupSystem.GetEntitiesInRange(userCoords, radius, _targetGrids); + Entity? targetGrid = null; + + if (_targetGrids.Count == 0) + return null; + + // Give preference to the grid the entity is currently on. + // This does not guarantee that if the probability fails that the owner's grid won't be picked. + // In reality the probability is higher and depends on the number of grids. + if (userXform.GridUid != null && TryComp(userXform.GridUid, out var gridComp)) + { + var userGrid = new Entity(userXform.GridUid.Value, gridComp); + if (_random.Prob(0.5f)) + { + _targetGrids.Remove(userGrid); + targetGrid = userGrid; + } + } + + if (targetGrid == null) + targetGrid = _random.GetRandom().PickAndTake(_targetGrids); + + EntityCoordinates? targetCoords = null; + + var valid = false; + + var range = (float)Math.Sqrt(radius); + var box = Box2.CenteredAround(userCoords.Position, new Vector2(range, range)); + var tilesInRange = _mapSystem.GetTilesEnumerator(targetGrid.Value.Owner, targetGrid.Value.Comp, box, false); + var tileList = new ValueList(); + + while (tilesInRange.MoveNext(out var tile)) + { + tileList.Add(tile.GridIndices); + } + + while (tileList.Count != 0) + { + var tile = tileList.RemoveSwap(_random.Next(tileList.Count)); + valid = true; + foreach (var entity in _mapSystem.GetAnchoredEntities(targetGrid.Value.Owner, targetGrid.Value.Comp, + tile)) + { + if (!_physicsQuery.TryGetComponent(entity, out var body)) + continue; + + if (body.BodyType != BodyType.Static || + !body.Hard || + (body.CollisionLayer & (int)CollisionGroup.MobMask) == 0) + continue; + + valid = false; + break; + } + + if (valid) + { + targetCoords = new EntityCoordinates(targetGrid.Value.Owner, + _mapSystem.TileCenterToVector(targetGrid.Value, tile)); + break; + } + } + + if (!valid || _targetGrids.Count != 0) // if we don't do the check here then PickAndTake will blow up on an empty set. + targetGrid = _random.GetRandom().PickAndTake(_targetGrids); + + return targetCoords; + + } + } +} diff --git a/Content.Server/SS220/ReactiveTeleportArmor/TeleportOnDamageComponent.cs b/Content.Server/SS220/ReactiveTeleportArmor/TeleportOnDamageComponent.cs new file mode 100644 index 00000000000000..91959708df33ea --- /dev/null +++ b/Content.Server/SS220/ReactiveTeleportArmor/TeleportOnDamageComponent.cs @@ -0,0 +1,41 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +using Robust.Shared.Audio; +using Content.Shared.FixedPoint; + +namespace Content.Server.SS220.ReactiveTeleportArmor; + +/// +/// Randomly teleports entity when damaged. +/// +[RegisterComponent] +public sealed partial class TeleportOnDamageComponent : Component +{ + /// + /// Up to how far to teleport the user + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float TeleportRadius = 30f; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public SoundSpecifier TeleportSound = new SoundPathSpecifier("/Audio/Effects/teleport_arrival.ogg"); + + /// + /// How much damage of any type it takes to wake this entity. + /// + [DataField] + public FixedPoint2 WakeThreshold = FixedPoint2.New(4); + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float TeleportChance = .9f; + + [ViewVariables] + public EntityUid SavedUid; + + public bool OnCoolDown = false; + + [DataField] + public TimeSpan CoolDownTime = TimeSpan.FromSeconds(5); +} + + diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml index e8d967f4d01579..a085671a8d0aa4 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml @@ -252,6 +252,7 @@ - id: ClothingHeadsetAltScience - id: EncryptionKeyBinary - id: ClothingWristWatchGold # SS220 Wrist Watch + - id: ClothingOuterReactiveArmor # SS220 - type: entity id: LockerResearchDirectorFilled @@ -272,6 +273,7 @@ - id: ClothingHeadsetAltScience - id: EncryptionKeyBinary - id: ClothingWristWatchGold # SS220 Wrist Watch + - id: ClothingOuterReactiveArmor # SS220 - type: entity id: LockerHeadOfSecurityFilledHardsuit diff --git a/Resources/Prototypes/SS220/Entities/Clothing/OuterClothing/reactive_armor_rd.yml b/Resources/Prototypes/SS220/Entities/Clothing/OuterClothing/reactive_armor_rd.yml new file mode 100644 index 00000000000000..85b56277ff4a00 --- /dev/null +++ b/Resources/Prototypes/SS220/Entities/Clothing/OuterClothing/reactive_armor_rd.yml @@ -0,0 +1,42 @@ +- type: entity + parent: [ClothingOuterBaseLarge, AllowSuitStorageClothing, BaseToggleClothing] + id: ClothingOuterReactiveArmor + name: "name1" # да-да, сделаю #.ftl + description: "Desc2." # да............ точно сделаю, кстати всем ревьюевирам дарова #.ftl + components: + - type: Sprite + sprite: Clothing/OuterClothing/Armor/captain_carapace.rsi #спрайтеры будут ныть как бляди если это будут спрайты с 13 + - type: Clothing + sprite: Clothing/OuterClothing/Armor/captain_carapace.rsi #инфа по борщу не БУДЕТ + - type: Armor + modifiers: + coefficients: + Blunt: 0.5 + Slash: 0.5 + Piercing: 0.6 + Heat: 0.5 + Caustic: 0.9 + - type: HeldSpeedModifier + damageCoefficient: 0.65 + - type: ToggleClothing + action: ActionToggleReactiveArmor + - type: GroupExamine + - type: ReactiveTeleportArmor + - type: Explosive + explosionType: Default + maxIntensity: 2 + totalIntensity: 1.5 + intensitySlope: 1 + canCreateVacuum: false + deleteAfterExplosion: false + repeatable: true + + +- type: entity + id: ActionToggleReactiveArmor + name: Переключение реактивной брони #.ftl + description: Включает или выключает реактивную броню. #.ftl + components: + - type: InstantAction + itemIconStyle: BigItem + event: !type:ToggleActionEvent \ No newline at end of file diff --git a/Resources/Prototypes/SS220/Objectives/stealTargetGroups.yml b/Resources/Prototypes/SS220/Objectives/stealTargetGroups.yml index 6ad456102bb32c..c39ce2790807c7 100644 --- a/Resources/Prototypes/SS220/Objectives/stealTargetGroups.yml +++ b/Resources/Prototypes/SS220/Objectives/stealTargetGroups.yml @@ -18,3 +18,10 @@ sprite: sprite: SS220/Objects/Weapons/Guns/multiphase_energy_gun.rsi state: icon + +- type: stealTargetGroup + id: ClothingOuterReactiveArmor + name: experimental reactive armor + sprite: + sprite: Clothing/OuterClothing/Hardsuits/rd.rsi #да-да + state: icon \ No newline at end of file