diff --git a/Content.Server/Disease/Components/DiseaseBuildupComponent.cs b/Content.Server/Disease/Components/DiseaseBuildupComponent.cs deleted file mode 100644 index 8c562b0662..0000000000 --- a/Content.Server/Disease/Components/DiseaseBuildupComponent.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Content.Server.Disease.Components -{ - /// - /// The component which records the buildup/progression of an infection - /// - [RegisterComponent] - public sealed class DiseaseBuildupComponent : Component - { - /// This could be served to be generalized to allow for multiple - /// diseases to build up at once, but it doesn't matter too much. - - /// - /// The current amount of progression that has built up. - /// - [DataField("progression")] - [ViewVariables(VVAccess.ReadWrite)] - public float Progression = 0.00f; - } -} diff --git a/Content.Server/Disease/Effects/DiseaseAddComponent.cs b/Content.Server/Disease/Effects/DiseaseAddComponent.cs new file mode 100644 index 0000000000..7e38480592 --- /dev/null +++ b/Content.Server/Disease/Effects/DiseaseAddComponent.cs @@ -0,0 +1,30 @@ +using JetBrains.Annotations; +using Content.Shared.Disease; + +namespace Content.Server.Disease.Effects +{ + /// + /// Adds a component to the diseased entity + /// + [UsedImplicitly] + public sealed class DiseaseAddComponent : DiseaseEffect + { + /// + /// The component that is added at the end of build up + /// + [DataField("comp")] + public string? Comp = null; + + public override void Effect(DiseaseEffectArgs args) + { + if (Comp == null) + return; + + EntityUid uid = args.DiseasedEntity; + var newComponent = (Component) IoCManager.Resolve().GetComponent(Comp); + newComponent.Owner = uid; + if (!args.EntityManager.HasComponent(uid, newComponent.GetType())) + args.EntityManager.AddComponent(uid, newComponent); + } + } +} diff --git a/Content.Server/Disease/Effects/DiseaseProgression.cs b/Content.Server/Disease/Effects/DiseaseProgression.cs deleted file mode 100644 index db2dcedaca..0000000000 --- a/Content.Server/Disease/Effects/DiseaseProgression.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Content.Server.Disease.Components; -using JetBrains.Annotations; -using Content.Shared.Disease; - -namespace Content.Server.Disease.Effects -{ - /// - /// Handles a disease which incubates over a period of time - /// before adding another component to the infected entity - /// currently used for zombie virus - /// - [UsedImplicitly] - public sealed class DiseaseProgression : DiseaseEffect - { - /// - /// The rate that's increased over time. Defaults to 1% so the probability can be varied in yaml - /// - [DataField("rate")] - [ViewVariables(VVAccess.ReadWrite)] - public float Rate = 0.01f; - - /// - /// The component that is added at the end of build up - /// - [DataField("comp")] - public string? Comp = null; - - public override void Effect(DiseaseEffectArgs args) - { - args.EntityManager.EnsureComponent(args.DiseasedEntity, out var buildup); - if (buildup.Progression < 1) //increases steadily until 100% - { - buildup.Progression += Rate; - } - else if (Comp != null)//adds the component for the later stage of the disease. - { - EntityUid uid = args.DiseasedEntity; - var newComponent = (Component) IoCManager.Resolve().GetComponent(Comp); - newComponent.Owner = uid; - if (!args.EntityManager.HasComponent(uid, newComponent.GetType())) - args.EntityManager.AddComponent(uid, newComponent); - } - } - } -} diff --git a/Content.Server/Disease/Zombie/Components/DiseaseZombieComponent.cs b/Content.Server/Disease/Zombie/Components/DiseaseZombieComponent.cs deleted file mode 100644 index 14cfcf7970..0000000000 --- a/Content.Server/Disease/Zombie/Components/DiseaseZombieComponent.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace Content.Server.Disease.Zombie.Components -{ - /// - /// The component which gives an entity zombie traits. - /// - [RegisterComponent] - public sealed class DiseaseZombieComponent : Component - { - /// - /// The probability that a given bite will infect a player. - /// zombie infection is not based on disease resist items like masks or gloves. - /// - [DataField("probability")] - [ViewVariables(VVAccess.ReadWrite)] - public float Probability = 0.33f; - - /// - /// A multiplier on the movement speed that zombies recieve. - /// - [DataField("slowAmount")] - [ViewVariables(VVAccess.ReadWrite)] - public float SlowAmount = 0.75f; - - /// - /// Whether or not the zombie needs all the zombie traits initialized upon component init - /// useful for entities that already are zombies and do not need the additional traits. - /// - [DataField("applyZombieTraits")] - public bool ApplyZombieTraits = true; - - /// - /// The color of the zombie's skin - /// - [DataField("skinColor")] - public readonly Color SkinColor = (0.70f, 0.72f, 0.48f, 1); - } -} diff --git a/Content.Server/Disease/Zombie/DiseaseZombieSystem.cs b/Content.Server/Disease/Zombie/DiseaseZombieSystem.cs deleted file mode 100644 index 08e36317f3..0000000000 --- a/Content.Server/Disease/Zombie/DiseaseZombieSystem.cs +++ /dev/null @@ -1,130 +0,0 @@ -using Robust.Shared.Player; -using Content.Server.Speech.Components; -using Content.Server.Ghost.Roles.Components; -using Content.Server.Disease.Components; -using Content.Server.Disease.Zombie.Components; -using Content.Server.Body.Components; -using Content.Server.Body.Systems; -using Content.Server.Popups; -using Content.Server.Atmos.Components; -using Content.Server.Hands.Components; -using Content.Server.Nutrition.Components; -using Content.Server.Mind.Components; -using Content.Server.Chat.Managers; -using Content.Server.Inventory; -using Content.Shared.Damage; -using Content.Shared.MobState.Components; -using Content.Shared.Hands.EntitySystems; -using Content.Shared.Movement.EntitySystems; -using Content.Shared.CharacterAppearance.Components; -using Content.Shared.CharacterAppearance.Systems; -using Content.Server.Weapons.Melee.ZombieTransfer.Components; - -namespace Content.Server.Disease.Zombie -{ - /// - /// Handles zombie propagation and inherent zombie traits - /// - public sealed class DiseaseZombieSystem : EntitySystem - { - [Dependency] private readonly DamageableSystem _damageable = default!; - [Dependency] private readonly ServerInventorySystem _serverInventory = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly BloodstreamSystem _bloodstream = default!; - [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!; - [Dependency] private readonly SharedHandsSystem _sharedHands = default!; - [Dependency] private readonly SharedHumanoidAppearanceSystem _sharedHumanoidAppearance = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnComponentInit); - SubscribeLocalEvent(OnRefreshMovementSpeedModifiers); - } - - /// - /// I would imagine that if this component got assigned to something other than a mob, it would throw hella errors. - /// - private void OnComponentInit(EntityUid uid, DiseaseZombieComponent component, ComponentInit args) - { - if (!component.ApplyZombieTraits || !HasComp(uid)) - return; - - RemComp(uid); - RemComp(uid); - RemComp(uid); - RemComp(uid); - RemComp(uid); - RemComp(uid); - - EntityManager.EnsureComponent(uid, out var bloodstream); //zoms need bloodstream anyway for healing - _bloodstream.SetBloodLossThreshold(uid, 0f, bloodstream); - _bloodstream.TryModifyBleedAmount(uid, -bloodstream.BleedAmount, bloodstream); - _movementSpeedModifier.RefreshMovementSpeedModifiers(uid); - - EntityManager.EnsureComponent(uid).Accent = "zombie"; - - if (TryComp(uid, out var comp)) - { - _damageable.SetDamageModifierSetId(uid, "Zombie", comp); - _damageable.SetAllDamage(comp, 0); - } - - if (TryComp(uid, out var spritecomp)) - { - var oldapp = spritecomp.Appearance; - var newapp = oldapp.WithSkinColor(component.SkinColor); - _sharedHumanoidAppearance.UpdateAppearance(uid, newapp); - - _sharedHumanoidAppearance.ForceAppearanceUpdate(uid); - } - - if (TryComp(uid, out var handcomp)) - { - foreach (var hand in handcomp.Hands) - { - _sharedHands.TrySetActiveHand(uid, hand.Key); - _sharedHands.TryDrop(uid); - - var pos = EntityManager.GetComponent(uid).Coordinates; - var claw = EntityManager.SpawnEntity("ZombieClaw", pos); - _sharedHands.DoPickup(uid, hand.Value, claw); - } - } - else - { - EnsureComp(uid); - } - - if (TryComp(uid, out var servInvComp)) - _serverInventory.TryUnequip(uid, "gloves", true, true, predicted: false, servInvComp); - - _popupSystem.PopupEntity(Loc.GetString("zombie-transform", ("target", uid)), uid, Filter.Pvs(uid)); - - if (TryComp(uid, out var metacomp)) - metacomp.EntityName = Loc.GetString("zombie-name-prefix", ("target", metacomp.EntityName)); - - var mindcomp = EnsureComp(uid); - - if (mindcomp.Mind != null && mindcomp.Mind.TryGetSession(out var session)) - { - var chatMgr = IoCManager.Resolve(); - chatMgr.DispatchServerMessage(session, Loc.GetString("zombie-infection-greeting")); - } - - if (!HasComp(uid) && !mindcomp.HasMind) //this specific component gives build test trouble so pop off, ig - { - EntityManager.EnsureComponent(uid, out var ghostcomp); - ghostcomp.RoleName = Loc.GetString("zombie-generic"); - ghostcomp.RoleDescription = Loc.GetString("zombie-role-desc"); - ghostcomp.RoleRules = Loc.GetString("zombie-role-rules"); - } - } - - private void OnRefreshMovementSpeedModifiers(EntityUid uid, DiseaseZombieComponent component, RefreshMovementSpeedModifiersEvent args) - { - args.ModifySpeed(component.SlowAmount, component.SlowAmount); - } - } -} diff --git a/Content.Server/Inventory/ServerInventorySystem.cs b/Content.Server/Inventory/ServerInventorySystem.cs index 03fba80533..97ecdfd2c7 100644 --- a/Content.Server/Inventory/ServerInventorySystem.cs +++ b/Content.Server/Inventory/ServerInventorySystem.cs @@ -5,6 +5,7 @@ using Content.Server.Temperature.Systems; using Content.Shared.Interaction.Events; using Content.Shared.Inventory; using Content.Shared.Inventory.Events; +using Robust.Shared.Containers; using InventoryComponent = Content.Shared.Inventory.InventoryComponent; namespace Content.Server.Inventory @@ -42,5 +43,35 @@ namespace Content.Server.Inventory _storageSystem.OpenStorageUI(entityUid.Value, uid, storageComponent); } } + + public void TransferEntityInventories(EntityUid uid, EntityUid target) + { + if (TryGetContainerSlotEnumerator(uid, out var enumerator)) + { + Dictionary inventoryEntities = new(); + var slots = GetSlots(uid); + while (enumerator.MoveNext(out var containerSlot)) + { + //records all the entities stored in each of the target's slots + foreach (var slot in slots) + { + if (TryGetSlotContainer(target, slot.Name, out var conslot, out var _) && + conslot.ID == containerSlot.ID) + { + inventoryEntities.Add(slot.Name, containerSlot.ContainedEntity); + } + } + //drops everything in the target's inventory on the ground + containerSlot.EmptyContainer(); + } + /// This takes the objects we removed and stored earlier + /// and actually equips all of it to the new entity + foreach (var item in inventoryEntities) + { + if (item.Value != null) + TryEquip(target, item.Value.Value, item.Key, true); + } + } + } } } diff --git a/Content.Server/Polymorph/Systems/PolymorphableSystem.cs b/Content.Server/Polymorph/Systems/PolymorphableSystem.cs index 3b16dcbecc..a2d2771dc7 100644 --- a/Content.Server/Polymorph/Systems/PolymorphableSystem.cs +++ b/Content.Server/Polymorph/Systems/PolymorphableSystem.cs @@ -4,15 +4,16 @@ using Content.Server.Inventory; using Content.Server.Mind.Commands; using Content.Server.Mind.Components; using Content.Server.Polymorph.Components; -using Content.Server.Popups; using Content.Shared.Actions; using Content.Shared.Actions.ActionTypes; +using Content.Shared.CharacterAppearance.Components; +using Content.Shared.CharacterAppearance.Systems; using Content.Shared.Damage; using Content.Shared.Hands.EntitySystems; using Content.Shared.Polymorph; +using Robust.Server.Containers; using Robust.Shared.Containers; using Robust.Shared.Map; -using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Utility; @@ -27,8 +28,9 @@ namespace Content.Server.Polymorph.Systems [Dependency] private readonly ServerInventorySystem _inventory = default!; [Dependency] private readonly SharedHandsSystem _sharedHands = default!; [Dependency] private readonly DamageableSystem _damageable = default!; - [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly SharedHumanoidAppearanceSystem _sharedHuApp = default!; + [Dependency] private readonly ContainerSystem _container = default!; public override void Initialize() { @@ -61,15 +63,15 @@ namespace Content.Server.Polymorph.Systems /// /// The entity that will be transformed /// The id of the polymorph prototype - public void PolymorphEntity(EntityUid target, String id) + public EntityUid? PolymorphEntity(EntityUid target, String id) { if (!_proto.TryIndex(id, out var proto)) { _saw.Error("Invalid polymorph prototype"); - return; + return null; } - PolymorphEntity(target, proto); + return PolymorphEntity(target, proto); } /// @@ -77,8 +79,12 @@ namespace Content.Server.Polymorph.Systems /// /// The entity that will be transformed /// The polymorph prototype - public void PolymorphEntity(EntityUid target, PolymorphPrototype proto) + public EntityUid? PolymorphEntity(EntityUid target, PolymorphPrototype proto) { + /// This is the big papa function. This handles the transformation, moving the old entity + /// logic and conditions specified in the prototype, and everything else that may be needed. + /// I am clinically insane - emo + // mostly just for vehicles if (TryComp(target, out var buckle)) buckle.TryUnbuckle(target, true); @@ -93,42 +99,72 @@ namespace Content.Server.Polymorph.Systems comp.Prototype = proto; RaiseLocalEvent(child, new PolymorphComponentSetupEvent()); + var targetXform = Transform(target); + var childXform = Transform(child); + childXform.LocalRotation = targetXform.LocalRotation; + + if (_container.TryGetContainingContainer(target, out var cont)) + cont.Insert(child); + //Transfers all damage from the original to the new one - if (TryComp(child, out var damageParent) && + if (proto.TransferDamage && + TryComp(child, out var damageParent) && _damageable.GetScaledDamage(target, child, out var damage) && damage != null) { _damageable.SetDamage(damageParent, damage); } - if (proto.DropInventory) + if (proto.Inventory == PolymorphInventoryChange.Transfer) { - //drops everything in the user's inventory - if (_inventory.TryGetContainerSlotEnumerator(target, out var enumerator)) - { - while (enumerator.MoveNext(out var containerSlot)) - { - containerSlot.EmptyContainer(); - } - } - //drops everything in the user's hands + _inventory.TransferEntityInventories(target, child); foreach (var hand in _sharedHands.EnumerateHeld(target)) { hand.TryRemoveFromContainer(); + _sharedHands.TryPickupAnyHand(child, hand); } } + else if (proto.Inventory == PolymorphInventoryChange.Drop) + { + if(_inventory.TryGetContainerSlotEnumerator(target, out var enumerator)) + while (enumerator.MoveNext(out var slot)) + slot.EmptyContainer(); + + foreach (var hand in _sharedHands.EnumerateHeld(target)) + hand.TryRemoveFromContainer(); + } + + if (proto.TransferName && + TryComp(target, out var targetMeta) && + TryComp(child, out var childMeta)) + { + childMeta.EntityName = targetMeta.EntityName; + } + + if (proto.TransferHumanoidAppearance && + TryComp(target, out var targetHuApp) && + TryComp(child, out var childHuApp)) + { + _sharedHuApp.UpdateAppearance(child, targetHuApp.Appearance); + _sharedHuApp.ForceAppearanceUpdate(child); + } if (TryComp(target, out var mind) && mind.Mind != null) - mind.Mind.TransferTo(child); + mind.Mind.TransferTo(child); //Ensures a map to banish the entity to EnsurePausesdMap(); - if(PausedMap != null) + if (PausedMap != null) targetTransformComp.AttachParent(Transform(PausedMap.Value)); - - _popup.PopupEntity(Loc.GetString("polymorph-popup-generic", ("parent", target), ("child", child)), child, Filter.Pvs(child)); + + return child; } + /// + /// Creates a sidebar action for an entity to be able to polymorph at will + /// + /// The string of the id of the polymorph action + /// The entity that will be gaining the action public void CreatePolymorphAction(string id, EntityUid target) { if (!_proto.TryIndex(id, out var polyproto)) @@ -136,7 +172,7 @@ namespace Content.Server.Polymorph.Systems _saw.Error("Invalid polymorph prototype"); return; } - + if (!TryComp(target, out var polycomp)) return; diff --git a/Content.Server/Polymorph/Systems/PolymorphedEntitySystem.cs b/Content.Server/Polymorph/Systems/PolymorphedEntitySystem.cs index 94ada93c6a..b14354acef 100644 --- a/Content.Server/Polymorph/Systems/PolymorphedEntitySystem.cs +++ b/Content.Server/Polymorph/Systems/PolymorphedEntitySystem.cs @@ -1,11 +1,16 @@ using Content.Server.Actions; +using Content.Server.Inventory; using Content.Server.Mind.Components; using Content.Server.Polymorph.Components; using Content.Server.Popups; using Content.Shared.Actions; using Content.Shared.Actions.ActionTypes; using Content.Shared.Damage; +using Content.Shared.Hands.EntitySystems; using Content.Shared.MobState.Components; +using Content.Shared.Polymorph; +using Robust.Server.Containers; +using Robust.Shared.Containers; using Robust.Shared.Player; namespace Content.Server.Polymorph.Systems @@ -15,6 +20,9 @@ namespace Content.Server.Polymorph.Systems [Dependency] private readonly ActionsSystem _actions = default!; [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly ServerInventorySystem _inventory = default!; + [Dependency] private readonly SharedHandsSystem _sharedHands = default!; + [Dependency] private readonly ContainerSystem _container = default!; public override void Initialize() { @@ -35,22 +43,51 @@ namespace Content.Server.Polymorph.Systems /// The entityuid of the entity being reverted public void Revert(EntityUid uid) { + if (Deleted(uid)) + return; + if (!TryComp(uid, out var component)) return; + var proto = component.Prototype; + var uidXform = Transform(uid); var parentXform = Transform(component.Parent); parentXform.AttachParent(uidXform.ParentUid); parentXform.Coordinates = uidXform.Coordinates; + parentXform.LocalRotation = uidXform.LocalRotation; - if (TryComp(component.Parent, out var damageParent) && + if (_container.TryGetContainingContainer(uid, out var cont)) + cont.Insert(component.Parent); + + if (component.Prototype.TransferDamage && + TryComp(component.Parent, out var damageParent) && _damageable.GetScaledDamage(uid, component.Parent, out var damage) && damage != null) { _damageable.SetDamage(damageParent, damage); } + if (proto.Inventory == PolymorphInventoryChange.Transfer) + { + _inventory.TransferEntityInventories(uid, component.Parent); + foreach (var hand in _sharedHands.EnumerateHeld(component.Parent)) + { + hand.TryRemoveFromContainer(); + _sharedHands.TryPickupAnyHand(component.Parent, hand); + } + } + else if (proto.Inventory == PolymorphInventoryChange.Drop) + { + if (_inventory.TryGetContainerSlotEnumerator(uid, out var enumerator)) + while (enumerator.MoveNext(out var slot)) + slot.EmptyContainer(); + + foreach (var hand in _sharedHands.EnumerateHeld(uid)) + hand.TryRemoveFromContainer(); + } + if (TryComp(uid, out var mind) && mind.Mind != null) { mind.Mind.TransferTo(component.Parent); diff --git a/Content.Server/Speech/VocalSystem.cs b/Content.Server/Speech/VocalSystem.cs index c54f441705..e78c799353 100644 --- a/Content.Server/Speech/VocalSystem.cs +++ b/Content.Server/Speech/VocalSystem.cs @@ -67,9 +67,9 @@ public sealed class VocalSystem : EntitySystem if (!_blocker.CanSpeak(uid)) return false; - // Currently this requires humanoid appearance & doesn't have any sort of fall-back or gender-neutral scream. - if (!TryComp(uid, out HumanoidAppearanceComponent? humanoid)) - return false; + var sex = Sex.Male; //the default is male because requiring humanoid appearance for this is dogshit + if (TryComp(uid, out HumanoidAppearanceComponent? humanoid)) + sex = humanoid.Sex; if (_random.Prob(component.WilhelmProbability)) { @@ -80,7 +80,7 @@ public sealed class VocalSystem : EntitySystem var scale = (float) _random.NextGaussian(1, VocalComponent.Variation); var pitchedParams = component.AudioParams.WithPitchScale(scale); - switch (humanoid.Sex) + switch (sex) { case Sex.Male: SoundSystem.Play(Filter.Pvs(uid), component.MaleScream.GetSound(), uid, pitchedParams); diff --git a/Content.Server/StationEvents/Events/ZombieOutbreak.cs b/Content.Server/StationEvents/Events/ZombieOutbreak.cs index 4eb4b43f53..891171833b 100644 --- a/Content.Server/StationEvents/Events/ZombieOutbreak.cs +++ b/Content.Server/StationEvents/Events/ZombieOutbreak.cs @@ -1,9 +1,10 @@ using Content.Server.Chat; using Robust.Shared.Random; -using Content.Server.Disease.Zombie.Components; +using Content.Server.Chat.Managers; using Content.Server.Station.Systems; using Content.Shared.MobState.Components; using Content.Shared.Sound; +using Content.Server.Zombies; namespace Content.Server.StationEvents.Events { @@ -41,17 +42,20 @@ namespace Content.Server.StationEvents.Events _random.Shuffle(deadList); var toInfect = _random.Next(1, 3); - + + var zombifysys = _entityManager.EntitySysManager.GetEntitySystem(); + // Now we give it to people in the list of dead entities earlier. var entSysMgr = IoCManager.Resolve(); var stationSystem = entSysMgr.GetEntitySystem(); var chatSystem = entSysMgr.GetEntitySystem(); + foreach (var target in deadList) { if (toInfect-- == 0) break; - - _entityManager.EnsureComponent(target.Owner); + + zombifysys.ZombifyEntity(target.Owner); var station = stationSystem.GetOwningStation(target.Owner); if(station == null) continue; diff --git a/Content.Server/Weapon/Melee/MeleeWeaponSystem.cs b/Content.Server/Weapon/Melee/MeleeWeaponSystem.cs index adc06f82b5..e6ec023ba8 100644 --- a/Content.Server/Weapon/Melee/MeleeWeaponSystem.cs +++ b/Content.Server/Weapon/Melee/MeleeWeaponSystem.cs @@ -84,7 +84,7 @@ namespace Content.Server.Weapon.Melee if (args.Target is {Valid: true} target) { // Raise event before doing damage so we can cancel damage if the event is handled - var hitEvent = new MeleeHitEvent(new List() { target }, args.User); + var hitEvent = new MeleeHitEvent(new List() { target }, args.User, comp.Damage); RaiseLocalEvent(owner, hitEvent, false); if (!hitEvent.Handled) @@ -152,7 +152,7 @@ namespace Content.Server.Weapon.Melee } // Raise event before doing damage so we can cancel damage if handled - var hitEvent = new MeleeHitEvent(hitEntities, args.User); + var hitEvent = new MeleeHitEvent(hitEntities, args.User, comp.Damage); RaiseLocalEvent(owner, hitEvent, false); SendAnimation(comp.Arc, angle, args.User, owner, hitEntities); @@ -352,6 +352,11 @@ namespace Content.Server.Weapon.Melee /// public sealed class MeleeHitEvent : HandledEntityEventArgs { + /// + /// The base amount of damage dealt by the melee hit. + /// + public readonly DamageSpecifier BaseDamage = new(); + /// /// Modifier sets to apply to the hit event when it's all said and done. /// This should be modified by adding a new entry to the list. @@ -382,10 +387,11 @@ namespace Content.Server.Weapon.Melee /// public EntityUid User { get; } - public MeleeHitEvent(List hitEntities, EntityUid user) + public MeleeHitEvent(List hitEntities, EntityUid user, DamageSpecifier baseDamage) { HitEntities = hitEntities; User = user; + BaseDamage = baseDamage; } } } diff --git a/Content.Server/Weapon/Melee/ZombieTransfer/Components/ZombieTransferComponent.cs b/Content.Server/Weapon/Melee/ZombieTransfer/Components/ZombieTransferComponent.cs deleted file mode 100644 index f0e95efb04..0000000000 --- a/Content.Server/Weapon/Melee/ZombieTransfer/Components/ZombieTransferComponent.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Content.Server.Weapons.Melee.ZombieTransfer.Components -{ - [RegisterComponent] - public sealed class ZombieTransferComponent : Component - { - } -} diff --git a/Content.Server/Weapon/Melee/ZombieTransfer/ZombieTransferSystem.cs b/Content.Server/Weapon/Melee/ZombieTransfer/ZombieTransferSystem.cs deleted file mode 100644 index e3069860f2..0000000000 --- a/Content.Server/Weapon/Melee/ZombieTransfer/ZombieTransferSystem.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Linq; -using Robust.Shared.Random; -using Content.Server.Body.Systems; -using Content.Server.Disease.Components; -using Content.Server.Disease.Zombie.Components; -using Content.Server.Drone.Components; -using Content.Server.Weapon.Melee; -using Content.Shared.Chemistry.Components; -using Content.Shared.Damage; -using Content.Shared.MobState.Components; -using Content.Server.Disease; -using Content.Server.Weapons.Melee.ZombieTransfer.Components; - -namespace Content.Server.Weapons.Melee.ZombieTransfer -{ - public sealed class ZombieTransferSystem : EntitySystem - { - [Dependency] private readonly DiseaseSystem _disease = default!; - [Dependency] private readonly BloodstreamSystem _bloodstream = default!; - [Dependency] private readonly IRobustRandom _robustRandom = default!; - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnMeleeHit); - } - - private void OnMeleeHit(EntityUid uid, ZombieTransferComponent component, MeleeHitEvent args) - { - if (!EntityManager.TryGetComponent(args.User, out var diseaseZombieComp)) - return; - - if (!args.HitEntities.Any()) - return; - - foreach (EntityUid entity in args.HitEntities) - { - if (args.User == entity) - continue; - - if (!HasComp(entity) || HasComp(entity)) - continue; - - if (_robustRandom.Prob(diseaseZombieComp.Probability) && HasComp(entity)) - { - _disease.TryAddDisease(entity, "ZombieInfection"); - } - - EntityManager.EnsureComponent(entity, out var mobState); - if ((mobState.IsDead() || mobState.IsCritical()) && !HasComp(entity)) //dead entities are eautomatically infected. MAYBE: have activated infect ability? - { - EntityManager.AddComponent(entity); - var dspec = new DamageSpecifier(); - //these damages match the zombie claw - dspec.DamageDict.TryAdd("Slash", -12); - dspec.DamageDict.TryAdd("Piercing", -7); - args.BonusDamage += dspec; - } - else if (mobState.IsAlive()) //heals when zombies bite live entities - { - var healingSolution = new Solution(); - healingSolution.AddReagent("Bicaridine", 1.00); //if OP, reduce/change chem - _bloodstream.TryAddToChemicals(args.User, healingSolution); - } - } - } - } -} diff --git a/Content.Server/Zombies/ZombieComponent.cs b/Content.Server/Zombies/ZombieComponent.cs new file mode 100644 index 0000000000..57f59c5b5f --- /dev/null +++ b/Content.Server/Zombies/ZombieComponent.cs @@ -0,0 +1,12 @@ +namespace Content.Server.Zombies +{ + [RegisterComponent] + public sealed class ZombieComponent : Component + { + /// + /// The coefficient of the damage reduction applied when a zombie + /// attacks another zombie. longe name + /// + public float OtherZombieDamageCoefficient = 0.75f; + } +} diff --git a/Content.Server/Zombies/ZombieSystem.cs b/Content.Server/Zombies/ZombieSystem.cs new file mode 100644 index 0000000000..818e3941b4 --- /dev/null +++ b/Content.Server/Zombies/ZombieSystem.cs @@ -0,0 +1,62 @@ +using System.Linq; +using Robust.Shared.Random; +using Content.Server.Body.Systems; +using Content.Server.Disease.Components; +using Content.Server.Drone.Components; +using Content.Server.Weapon.Melee; +using Content.Shared.Chemistry.Components; +using Content.Shared.MobState.Components; +using Content.Server.Disease; + +namespace Content.Server.Zombies +{ + public sealed class ZombieSystem : EntitySystem + { + [Dependency] private readonly DiseaseSystem _disease = default!; + [Dependency] private readonly BloodstreamSystem _bloodstream = default!; + [Dependency] private readonly ZombifyOnDeathSystem _zombify = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnMeleeHit); + } + + private void OnMeleeHit(EntityUid uid, ZombieComponent component, MeleeHitEvent args) + { + if (!EntityManager.TryGetComponent(args.User, out var zombieComp)) + return; + + if (!args.HitEntities.Any()) + return; + + foreach (EntityUid entity in args.HitEntities) + { + if (args.User == entity) + continue; + + if (!TryComp(entity, out var mobState) || HasComp(entity)) + continue; + + if (_robustRandom.Prob(0.5f) && HasComp(entity)) + _disease.TryAddDisease(entity, "ActiveZombieVirus"); + + if (HasComp(entity)) + args.BonusDamage = args.BaseDamage * zombieComp.OtherZombieDamageCoefficient; + + if ((mobState.IsDead() || mobState.IsCritical()) + && !HasComp(entity)) + { + _zombify.ZombifyEntity(entity); + args.BonusDamage = -args.BaseDamage; + } + else if (mobState.IsAlive()) //heals when zombies bite live entities + { + var healingSolution = new Solution(); + healingSolution.AddReagent("Bicaridine", 1.00); //if OP, reduce/change chem + _bloodstream.TryAddToChemicals(args.User, healingSolution); + } + } + } + } +} diff --git a/Content.Server/Zombies/ZombifyOnDeathComponent.cs b/Content.Server/Zombies/ZombifyOnDeathComponent.cs new file mode 100644 index 0000000000..c43ad57f51 --- /dev/null +++ b/Content.Server/Zombies/ZombifyOnDeathComponent.cs @@ -0,0 +1,15 @@ +using Content.Shared.Weapons.Melee; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Zombies +{ + [RegisterComponent] + public sealed class ZombifyOnDeathComponent : Component + { + [DataField("skinColor")] + public Color SkinColor = new Color(0.70f, 0.72f, 0.48f, 1); + + [DataField("attackArc", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string AttackArc = "claw"; + } +} diff --git a/Content.Server/Zombies/ZombifyOnDeathSystem.cs b/Content.Server/Zombies/ZombifyOnDeathSystem.cs new file mode 100644 index 0000000000..580bfb9ced --- /dev/null +++ b/Content.Server/Zombies/ZombifyOnDeathSystem.cs @@ -0,0 +1,147 @@ +using Content.Shared.Damage; +using Content.Shared.MobState.Components; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.CharacterAppearance.Components; +using Content.Shared.CharacterAppearance.Systems; +using Content.Server.Disease.Components; +using Content.Server.Body.Components; +using Content.Server.Atmos.Components; +using Content.Server.Nutrition.Components; +using Robust.Shared.Player; +using Content.Server.Popups; +using Content.Server.Speech.Components; +using Content.Server.Body.Systems; +using Content.Server.CombatMode; +using Content.Server.Inventory; +using Content.Server.Mind.Components; +using Content.Server.Chat.Managers; +using Content.Server.Ghost.Roles.Components; +using Content.Server.Hands.Components; +using Content.Server.Mind.Commands; +using Content.Server.Temperature.Components; +using Content.Server.Weapon.Melee.Components; +using Content.Server.Disease; +using Robust.Shared.Containers; +using Content.Shared.Movement.Components; +using Content.Shared.MobState; + +namespace Content.Server.Zombies +{ + /// + /// Handles zombie propagation and inherent zombie traits + /// + public sealed class ZombifyOnDeathSystem : EntitySystem + { + [Dependency] private readonly SharedHandsSystem _sharedHands = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly BloodstreamSystem _bloodstream = default!; + [Dependency] private readonly ServerInventorySystem _serverInventory = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly DiseaseSystem _disease = default!; + [Dependency] private readonly SharedHumanoidAppearanceSystem _sharedHuApp = default!; + [Dependency] private readonly IChatManager _chatMan = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnDamageChanged); + } + + /// + /// Handles an entity turning into a zombie when they die or go into crit + /// + private void OnDamageChanged(EntityUid uid, ZombifyOnDeathComponent component, MobStateChangedEvent args) + { + if (!TryComp(uid, out var mobstate)) + return; + + if (mobstate.IsDead() || + mobstate.IsCritical()) + { + ZombifyEntity(uid); + } + } + + /// + /// This is the general purpose function to call if you want to zombify an entity. + /// It handles both humanoid and nonhumanoid transformation. + /// + /// the entity being zombified + public void ZombifyEntity(EntityUid target) + { + if (HasComp(target)) + return; + + _disease.CureAllDiseases(target); + RemComp(target); + RemComp(target); + RemComp(target); + RemComp(target); + RemComp(target); + + var zombiecomp = EnsureComp(target); + if (TryComp(target, out var huApComp)) + { + var appearance = huApComp.Appearance; + _sharedHuApp.UpdateAppearance(target, appearance.WithSkinColor(zombiecomp.SkinColor), huApComp); + _sharedHuApp.ForceAppearanceUpdate(target, huApComp); + } + + if (!HasComp(target)) + MakeSentientCommand.MakeSentient(target, EntityManager); + + EnsureComp(target).Accent = "zombie"; + + //funny add delet go brrr + RemComp(target); + AddComp(target); + + var melee = EnsureComp(target); + melee.Arc = zombiecomp.AttackArc; + melee.ClickArc = zombiecomp.AttackArc; + //lord forgive me for the hardcoded damage + DamageSpecifier dspec = new(); + dspec.DamageDict.Add("Slash", 13); + dspec.DamageDict.Add("Piercing", 7); + melee.Damage = dspec; + + _damageable.SetDamageModifierSetId(target, "Zombie"); + _bloodstream.SetBloodLossThreshold(target, 0f); + + _popupSystem.PopupEntity(Loc.GetString("zombie-transform", ("target", target)), target, Filter.Pvs(target)); + _serverInventory.TryUnequip(target, "gloves", true, true); + + if (TryComp(target, out var tempComp)) + tempComp.ColdDamage.ClampMax(0); + + if (TryComp(target, out var damageablecomp)) + _damageable.SetAllDamage(damageablecomp, 0); + + if (TryComp(target, out var meta)) + meta.EntityName = Loc.GetString("zombie-name-prefix", ("target", meta.EntityName)); + + var mindcomp = EnsureComp(target); + if (mindcomp.Mind != null && mindcomp.Mind.TryGetSession(out var session)) + _chatMan.DispatchServerMessage(session, Loc.GetString("zombie-infection-greeting")); + + if (!HasComp(target) && !mindcomp.HasMind) //this specific component gives build test trouble so pop off, ig + { + EntityManager.EnsureComponent(target, out var ghostcomp); + ghostcomp.RoleName = Loc.GetString("zombie-generic"); + ghostcomp.RoleDescription = Loc.GetString("zombie-role-desc"); + ghostcomp.RoleRules = Loc.GetString("zombie-role-rules"); + } + + foreach (var hand in _sharedHands.EnumerateHands(target)) + { + _sharedHands.SetActiveHand(target, hand); + hand.Container?.EmptyContainer(); + _sharedHands.RemoveHand(target, hand.Name); + } + RemComp(target); + + EnsureComp(target); + } + } +} diff --git a/Content.Shared/Polymorph/PolymorphPrototype.cs b/Content.Shared/Polymorph/PolymorphPrototype.cs index 31cdf22933..53891eb8ea 100644 --- a/Content.Shared/Polymorph/PolymorphPrototype.cs +++ b/Content.Shared/Polymorph/PolymorphPrototype.cs @@ -53,11 +53,28 @@ namespace Content.Shared.Polymorph public bool Forced = false; /// - /// Whether or not the target will drop their inventory - /// when they are polymorphed (includes items in hands) + /// Whether or not the entity transfers its damage between forms. /// - [DataField("dropInventory", serverOnly: true)] - public bool DropInventory = false; + [DataField("transferDamage", serverOnly: true)] + public bool TransferDamage = true; + + /// + /// Whether or not the entity transfers its name between forms. + /// + [DataField("transferName", serverOnly: true)] + public bool TransferName = false; + + /// + /// Whether or not the entity transfers its hair, skin color, hair color, etc. + /// + [DataField("transferHumanoidAppearance", serverOnly: true)] + public bool TransferHumanoidAppearance = false; + + /// + /// Whether or not the entity transfers its inventory and equipment between forms. + /// + [DataField("inventory", serverOnly: true)] + public PolymorphInventoryChange Inventory = PolymorphInventoryChange.None; /// /// Whether or not the polymorph reverts when the entity goes into crit. @@ -71,4 +88,11 @@ namespace Content.Shared.Polymorph [DataField("revertOnDeath", serverOnly: true)] public bool RevertOnDeath = true; } + + public enum PolymorphInventoryChange : byte + { + None, + Drop, + Transfer, + } } diff --git a/Resources/Locale/en-US/disease/zombie.ftl b/Resources/Locale/en-US/zombies/zombie.ftl similarity index 100% rename from Resources/Locale/en-US/disease/zombie.ftl rename to Resources/Locale/en-US/zombies/zombie.ftl diff --git a/Resources/Prototypes/Diseases/noninfectious.yml b/Resources/Prototypes/Diseases/noninfectious.yml index 753bdcc858..4ed05a3f12 100644 --- a/Resources/Prototypes/Diseases/noninfectious.yml +++ b/Resources/Prototypes/Diseases/noninfectious.yml @@ -18,7 +18,6 @@ reagent: Phalanximine min: 15 - - type: disease id: StageIIIALungCancer name: Stage IIIA Lung Cancer @@ -42,27 +41,3 @@ ### Once radiation is refactored I want it to have a small chance of giving you regular cancer - -- type: disease - id: ZombieInfection - name: Zombie Infection #This is the incubation period of the zombie disease. - infectious: false - cureResist: 0.2 - effects: - - !type:DiseaseHealthChange - probability: 0.01 - damage: - types: - Blunt: 2 #this is low but it goes for like 5 minutes so it's fine - - !type:DiseaseSnough - probability: 0.05 - snoughMessage: disease-cough - snoughSound: - collection: Coughs - - !type:DiseaseProgression - probability: 0.33 - comp: DiseaseZombie - cures: - - !type:DiseaseReagentCure - reagent: Romerol - min: 10 diff --git a/Resources/Prototypes/Diseases/zombie.yml b/Resources/Prototypes/Diseases/zombie.yml new file mode 100644 index 0000000000..cbdca7bbaf --- /dev/null +++ b/Resources/Prototypes/Diseases/zombie.yml @@ -0,0 +1,26 @@ +- type: disease + id: ActiveZombieVirus + name: Zombie Virus + infectious: false + cureResist: 0.2 + effects: + - !type:DiseaseHealthChange + probability: 0.075 + damage: + types: + Blunt: 4 + - !type:DiseaseAdjustReagent + probability: 0.05 + reagent: Toxin + amount: 1 + - !type:DiseaseSnough + probability: 0.01 + snoughMessage: disease-cough + snoughSound: + collection: Coughs + - !type:DiseaseAddComponent + comp: ZombifyOnDeath + cures: + - !type:DiseaseReagentCure + reagent: Romerol + min: 5 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 7773438083..f54f9648bb 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -728,7 +728,6 @@ damage: types: Blunt: 0.1 - - type: HumanoidAppearance # no this doesnt make sense but its needed for vocal - type: Vocal # mice are gender neutral who cares maleScream: /Audio/Animals/mouse_squeak.ogg diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml index c9d4220cb5..a4cc92a4a7 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml @@ -40,8 +40,8 @@ parent: HandheldHealthAnalyzer id: HandheldHealthAnalyzerZombie name: Zombie Infector - suffix: DEBUG + suffix: Active components: - type: HealthAnalyzer fake: true - disease: ZombieInfection \ No newline at end of file + disease: ActiveZombieVirus \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/zombieclaw.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/zombieclaw.yml deleted file mode 100644 index b1bdd295fb..0000000000 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/zombieclaw.yml +++ /dev/null @@ -1,22 +0,0 @@ -# Special entity used to fill zombie's hands when they turn undead -- type: entity - id: ZombieClaw - name: Zombie Claw - noSpawn: true - components: - - type: Sprite - sprite: Objects/Weapons/Melee/zombie_claw.rsi - state: icon - - type: Item - - type: Sharp - - type: ZombieTransfer - - type: Unremoveable - - type: MeleeWeapon - damage: - types: - Slash: 13 - Piercing: 7 - range: 1 - arcwidth: 0 - arc: claw - diff --git a/Resources/Prototypes/Polymorphs/polymorph.yml b/Resources/Prototypes/Polymorphs/polymorph.yml index c7013ea795..aec7946591 100644 --- a/Resources/Prototypes/Polymorphs/polymorph.yml +++ b/Resources/Prototypes/Polymorphs/polymorph.yml @@ -8,30 +8,38 @@ id: Chicken entity: MobChicken forced: true - dropInventory: true + inventory: Drop - type: polymorph id: Monkey entity: MobMonkey forced: true - dropInventory: true + inventory: Drop revertOnCrit: true revertOnDeath: true +# this is a test for transferring some visual appearance stuff +- type: polymorph + id: TestHumanMorph + entity: MobHuman + transferName: true + transferHumanoidAppearance: true + inventory: Transfer + - type: polymorph id: AdminMonkeySmite entity: MobMonkey forced: true - dropInventory: true + inventory: Drop - type: polymorph id: AdminBreadSmite entity: FoodBreadPlain forced: true - dropInventory: true + inventory: Drop - type: polymorph id: AdminInstrumentSmite entity: SuperSynthesizerInstrument forced: true - dropInventory: true + inventory: Drop diff --git a/Resources/Prototypes/Reagents/toxins.yml b/Resources/Prototypes/Reagents/toxins.yml index fef7ffc2ec..b0a5485dfa 100644 --- a/Resources/Prototypes/Reagents/toxins.yml +++ b/Resources/Prototypes/Reagents/toxins.yml @@ -309,4 +309,4 @@ Cellular: 1 - !type:ChemCauseDisease causeChance: 1 - disease: ZombieInfection + disease: ActiveZombieVirus diff --git a/Resources/Textures/Objects/Weapons/Melee/zombie_claw.rsi/icon.png b/Resources/Textures/Objects/Weapons/Melee/zombie_claw.rsi/icon.png deleted file mode 100644 index 361dfbf844..0000000000 Binary files a/Resources/Textures/Objects/Weapons/Melee/zombie_claw.rsi/icon.png and /dev/null differ diff --git a/Resources/Textures/Objects/Weapons/Melee/zombie_claw.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Melee/zombie_claw.rsi/inhand-left.png deleted file mode 100644 index 7cb0ee924a..0000000000 Binary files a/Resources/Textures/Objects/Weapons/Melee/zombie_claw.rsi/inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Objects/Weapons/Melee/zombie_claw.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Melee/zombie_claw.rsi/inhand-right.png deleted file mode 100644 index 69a0bef26f..0000000000 Binary files a/Resources/Textures/Objects/Weapons/Melee/zombie_claw.rsi/inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Objects/Weapons/Melee/zombie_claw.rsi/meta.json b/Resources/Textures/Objects/Weapons/Melee/zombie_claw.rsi/meta.json deleted file mode 100644 index a0d45845d1..0000000000 --- a/Resources/Textures/Objects/Weapons/Melee/zombie_claw.rsi/meta.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Illustrated by EmoGarbage", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] -}