mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-10 19:01:13 +03:00
* What sitting on code does to a branch + no mo owner v2 * Replacing ViewVariables with AutoNetworkedField + Missed One V2 * Removing Virtual Methods & Renaming ForensicsSystem to SharedForensicsSystem (Also made it abstract) V2 * IProduceWidgets Review Changes & some of my own V2 * Lil Cleanup V2 * Part 1 of Review Changes (Only thing left is the BUI stuff and the constructor) V2 * It now WORKS!!! (Still needs the BUI and constuctor changes, ill figure it out later) * Test (Removal of ServerSide VomitSystem.cs) * Ok... * Moved System BloodstreamSystem.cs to SharedBloodstreamSystem.cs & deleted client BloodStreamSystem.cs to make SharedBloodstreamSystem.cs sealed. (No Unregistered errors) * Prediction * Fix? * No mo log error? * Revert "No mo log error?" This reverts commit 50c15a3deab6af9dffd7baed45820bae5558e41f. * Constructor going up * No ForensicScannerBoundUserInterface.cs (It doesnt wanna scan anymore but it doesnt crash) * For Conflicts * Guh * Yea * Organization & no ErrorHell (maybe) * Dirtying * Dirtying take 2 * Revert "Dirtying take 2" This reverts commit 5735012e21f43808db3ae010fce268eac24d0583. * the dirties dont do much here * For Conflict * Readded * Another Conflict Commit Fix * Conflict Fix Thrice * Empty Space * Keep it simple * Add it back in * Move BloodstreamSystem stuff thats causing the error back to system. This'll prob fix the conflict * Conflict fix? * Trust the fix * Looked at other prediction PR's, they had better code. * To not mix up the system in client * Removing Conflict * For conflict * For the conflic fix, will be removed later * Scuffed Fix * Removal & Adding the Decouple * Took some HEAVY pointers from SirWarocks work. The Roadblock has been CLEARED, DOAFTERS WORK NOW!! * Cleanup & Optimizing, mostly comps. * More Optimization, most of this is just imported Warock Code, his QOL included. Did not want to remove SolutionDNA's tho, mostly due to a Log.Debug in OnPrint. * Forgot that the Print Refreshes, more of Warocks work. * Removing Loc.GetString * All of BloodStream to Shared, hopefully no errors. * Sketchy Dirty Removed * Removed Timer.Spawn, added FrameUpdate cause anything else only updated when the window opened, & a bit of cleanup. * No mo Mispredict * GenerateDnaEvent Predicted * Forgot the user * I knew it was real, bloodstutter is REAL and now GONE. * Cant dirty NewEntityBloodData due to potential stutter, so its safer to put it all back into server. again... * Clearer * Thank you leahcat, no more identity leak. * Routine Maintenance * sealing the class * it checks for this now?! * While im here, FileScope Namespaces & removing unused namespaces * Update Attribute Type (SirWarock Import), it works and is much easier to read. * Forever Fixing Conflicts * Readonly Genocide & Partial Maxing * LGTM, BloodstreamSystem moved to SharedBloodstreamSystem! * Sohn * If OG Github back push this commit * One Billionth Commit Fix * Removed client/server stuff * Made implicit * NewLine & comment on why AutoGenCompState is true * Updatin the rest of the Popups to use Entity * I think that's what they meant? It looks cleaner at least. * Mass Attribute for LocalEvents that are safe to change. * Putting it back as abstract * maybe? * Guhh, rolling back empty client/server of BloodStream * Removed Client/Server BloodstreamSystems, changed access modifier from protected to private, & renamed SharedBloodstreamSystem to BloodstreamSystem * From Protected to Private * Removed comment & made it explicit on what changed to true * Guh, hopefully thats all of it... * Forgot * Review * Test Push * Line 316 in SharedForensicsSystem was causing the errors, better to play it safe * Review Changes * Update Content.Shared/Forensics/Events.cs Co-authored-by: Whatstone <166147148+whatston3@users.noreply.github.com> * The only thing SolutionDNAs does is search and clears itself. I see no reason to keep it * The print.disabled didnt properly disable when cleared. So ill just use the old if check * Figured out the rest of the SubscribeLocalEvents and networking BloodData cause its fully in Shared --------- Co-authored-by: Whatstone <166147148+whatston3@users.noreply.github.com>
348 lines
14 KiB
C#
348 lines
14 KiB
C#
using Content.Shared.NPC.Prototypes;
|
|
using Content.Server.Actions;
|
|
using Content.Server.Body.Systems;
|
|
using Content.Server.Chat;
|
|
using Content.Server.Chat.Systems;
|
|
using Content.Server.Emoting.Systems;
|
|
using Content.Shared.Speech.EntitySystems;
|
|
using Content.Shared.Anomaly.Components;
|
|
using Content.Shared.Armor;
|
|
using Content.Shared.Bed.Sleep;
|
|
using Content.Shared.Body.Systems;
|
|
using Content.Shared.Cloning.Events;
|
|
using Content.Shared.Chat;
|
|
using Content.Shared.Damage.Systems;
|
|
using Content.Shared.Inventory;
|
|
using Content.Shared.Mind;
|
|
using Content.Shared.Mind.Components;
|
|
using Content.Shared.Mobs;
|
|
using Content.Shared.Mobs.Components;
|
|
using Content.Shared.Mobs.Systems;
|
|
using Content.Shared.Popups;
|
|
using Content.Shared.Revolutionary;
|
|
using Content.Shared.Roles;
|
|
using Content.Shared.Roles.Components;
|
|
using Content.Shared.Weapons.Melee.Events;
|
|
using Content.Shared.Zombies;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Random;
|
|
using Robust.Shared.Timing;
|
|
using Content.Server.Ghost.Roles.Components;
|
|
|
|
namespace Content.Server.Zombies
|
|
{
|
|
public sealed partial class ZombieSystem : SharedZombieSystem
|
|
{
|
|
[Dependency] private IGameTiming _timing = default!;
|
|
[Dependency] private IRobustRandom _random = default!;
|
|
[Dependency] private BloodstreamSystem _bloodstream = default!;
|
|
[Dependency] private DamageableSystem _damageable = default!;
|
|
[Dependency] private ChatSystem _chat = default!;
|
|
[Dependency] private ActionsSystem _actions = default!;
|
|
[Dependency] private AutoEmoteSystem _autoEmote = default!;
|
|
[Dependency] private EmoteOnDamageSystem _emoteOnDamage = default!;
|
|
[Dependency] private MobStateSystem _mobState = default!;
|
|
[Dependency] private SharedPopupSystem _popup = default!;
|
|
[Dependency] private SharedRoleSystem _role = default!;
|
|
|
|
public readonly ProtoId<NpcFactionPrototype> Faction = "Zombie";
|
|
|
|
public const SlotFlags ProtectiveSlots =
|
|
SlotFlags.FEET |
|
|
SlotFlags.HEAD |
|
|
SlotFlags.EYES |
|
|
SlotFlags.GLOVES |
|
|
SlotFlags.MASK |
|
|
SlotFlags.NECK |
|
|
SlotFlags.INNERCLOTHING |
|
|
SlotFlags.OUTERCLOTHING;
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
SubscribeLocalEvent<ZombieComponent, EmoteEvent>(OnEmote, before:
|
|
new[] { typeof(VocalSystem), typeof(BodyEmotesSystem) });
|
|
|
|
SubscribeLocalEvent<ZombieComponent, MeleeHitEvent>(OnMeleeHit);
|
|
SubscribeLocalEvent<ZombieComponent, MobStateChangedEvent>(OnMobState);
|
|
SubscribeLocalEvent<ZombieComponent, CloningEvent>(OnZombieCloning);
|
|
SubscribeLocalEvent<ZombieComponent, TryingToSleepEvent>(OnSleepAttempt);
|
|
SubscribeLocalEvent<ZombieComponent, GetCharactedDeadIcEvent>(OnGetCharacterDeadIC);
|
|
SubscribeLocalEvent<ZombieComponent, GetCharacterUnrevivableIcEvent>(OnGetCharacterUnrevivableIC);
|
|
SubscribeLocalEvent<ZombieComponent, MindAddedMessage>(OnMindAdded);
|
|
SubscribeLocalEvent<ZombieComponent, MindRemovedMessage>(OnMindRemoved);
|
|
SubscribeLocalEvent<ZombieComponent, AttemptConvertRevolutionaryEvent>(OnAttemptConvert);
|
|
|
|
SubscribeLocalEvent<PendingZombieComponent, MapInitEvent>(OnPendingMapInit);
|
|
SubscribeLocalEvent<PendingZombieComponent, BeforeRemoveAnomalyOnDeathEvent>(OnBeforeRemoveAnomalyOnDeath);
|
|
|
|
SubscribeLocalEvent<IncurableZombieComponent, MapInitEvent>(OnPendingMapInit);
|
|
|
|
SubscribeLocalEvent<ZombifyOnDeathComponent, MobStateChangedEvent>(OnDamageChanged);
|
|
}
|
|
|
|
private void OnBeforeRemoveAnomalyOnDeath(Entity<PendingZombieComponent> ent, ref BeforeRemoveAnomalyOnDeathEvent args)
|
|
{
|
|
// Pending zombies (e.g. infected non-zombies) do not remove their hosted anomaly on death.
|
|
// Current zombies DO remove the anomaly on death.
|
|
args.Cancelled = true;
|
|
}
|
|
|
|
private void OnPendingMapInit(EntityUid uid, IncurableZombieComponent component, MapInitEvent args)
|
|
{
|
|
_actions.AddAction(uid, ref component.Action, component.ZombifySelfActionPrototype);
|
|
_faction.AddFaction(uid, Faction);
|
|
|
|
if (HasComp<ZombieComponent>(uid) || HasComp<ZombieImmuneComponent>(uid))
|
|
return;
|
|
|
|
EnsureComp<PendingZombieComponent>(uid, out PendingZombieComponent pendingComp);
|
|
|
|
pendingComp.GracePeriod = _random.Next(pendingComp.MinInitialInfectedGrace, pendingComp.MaxInitialInfectedGrace);
|
|
}
|
|
|
|
private void OnPendingMapInit(EntityUid uid, PendingZombieComponent component, MapInitEvent args)
|
|
{
|
|
if (_mobState.IsDead(uid))
|
|
{
|
|
ZombifyEntity(uid);
|
|
return;
|
|
}
|
|
|
|
component.NextTick = _timing.CurTime + TimeSpan.FromSeconds(1f);
|
|
}
|
|
|
|
public override void Update(float frameTime)
|
|
{
|
|
base.Update(frameTime);
|
|
var curTime = _timing.CurTime;
|
|
|
|
// Hurt the living infected
|
|
var query = EntityQueryEnumerator<PendingZombieComponent, Shared.Damage.Components.DamageableComponent, MobStateComponent>();
|
|
while (query.MoveNext(out var uid, out var comp, out var damage, out var mobState))
|
|
{
|
|
// Process only once per second
|
|
if (comp.NextTick > curTime)
|
|
continue;
|
|
|
|
comp.NextTick = curTime + TimeSpan.FromSeconds(1f);
|
|
|
|
comp.GracePeriod -= TimeSpan.FromSeconds(1f);
|
|
if (comp.GracePeriod > TimeSpan.Zero)
|
|
continue;
|
|
|
|
if (_random.Prob(comp.InfectionWarningChance))
|
|
_popup.PopupEntity(Loc.GetString(_random.Pick(comp.InfectionWarnings)), uid, uid);
|
|
|
|
var multiplier = _mobState.IsCritical(uid, mobState)
|
|
? comp.CritDamageMultiplier
|
|
: 1f;
|
|
|
|
_damageable.ChangeDamage((uid, damage), comp.Damage * multiplier, true, false);
|
|
}
|
|
|
|
// Heal the zombified
|
|
var zombQuery = EntityQueryEnumerator<ZombieComponent, Shared.Damage.Components.DamageableComponent, MobStateComponent>();
|
|
while (zombQuery.MoveNext(out var uid, out var comp, out var damage, out var mobState))
|
|
{
|
|
// Process only once per second
|
|
if (comp.NextTick + TimeSpan.FromSeconds(1) > curTime)
|
|
continue;
|
|
|
|
comp.NextTick = curTime;
|
|
|
|
if (_mobState.IsDead(uid, mobState))
|
|
continue;
|
|
|
|
var multiplier = _mobState.IsCritical(uid, mobState)
|
|
? comp.PassiveHealingCritMultiplier
|
|
: 1f;
|
|
|
|
// Gradual healing for living zombies.
|
|
_damageable.ChangeDamage((uid, damage), comp.PassiveHealing * multiplier, true, false);
|
|
}
|
|
}
|
|
|
|
private void OnSleepAttempt(EntityUid uid, ZombieComponent component, ref TryingToSleepEvent args)
|
|
{
|
|
args.Cancelled = true;
|
|
}
|
|
|
|
private void OnGetCharacterDeadIC(EntityUid uid, ZombieComponent component, ref GetCharactedDeadIcEvent args)
|
|
{
|
|
args.Dead = true;
|
|
}
|
|
|
|
private void OnGetCharacterUnrevivableIC(EntityUid uid, ZombieComponent component, ref GetCharacterUnrevivableIcEvent args)
|
|
{
|
|
args.Unrevivable = true;
|
|
}
|
|
|
|
private void OnEmote(EntityUid uid, ZombieComponent component, ref EmoteEvent args)
|
|
{
|
|
// always play zombie emote sounds and ignore others
|
|
if (args.Handled)
|
|
return;
|
|
|
|
ProtoMan.Resolve(component.EmoteSoundsId, out var sounds);
|
|
|
|
args.Handled = _chat.TryPlayEmoteSound(uid, sounds, args.Emote);
|
|
}
|
|
|
|
private void OnMobState(EntityUid uid, ZombieComponent component, MobStateChangedEvent args)
|
|
{
|
|
if (args.NewMobState == MobState.Alive)
|
|
{
|
|
// Groaning when damaged
|
|
EnsureComp<EmoteOnDamageComponent>(uid);
|
|
_emoteOnDamage.AddEmote(uid, "Scream");
|
|
|
|
// Random groaning
|
|
EnsureComp<AutoEmoteComponent>(uid);
|
|
_autoEmote.AddEmote(uid, "ZombieGroan");
|
|
}
|
|
else
|
|
{
|
|
// Stop groaning when damaged
|
|
_emoteOnDamage.RemoveEmote(uid, "Scream");
|
|
|
|
// Stop random groaning
|
|
_autoEmote.RemoveEmote(uid, "ZombieGroan");
|
|
}
|
|
}
|
|
|
|
private float GetZombieInfectionChance(EntityUid uid, ZombieComponent zombieComponent)
|
|
{
|
|
var chance = zombieComponent.BaseZombieInfectionChance;
|
|
|
|
var armorEv = new CoefficientQueryEvent(ProtectiveSlots);
|
|
RaiseLocalEvent(uid, armorEv);
|
|
foreach (var resistanceEffectiveness in zombieComponent.ResistanceEffectiveness.DamageDict)
|
|
{
|
|
if (armorEv.DamageModifiers.Coefficients.TryGetValue(resistanceEffectiveness.Key, out var coefficient))
|
|
{
|
|
// Scale the coefficient by the resistance effectiveness, very descriptive I know
|
|
// For example. With 30% slash resist (0.7 coeff), but only a 60% resistance effectiveness for slash,
|
|
// you'll end up with 1 - (0.3 * 0.6) = 0.82 coefficient, or a 18% resistance
|
|
var adjustedCoefficient = 1 - ((1 - coefficient) * resistanceEffectiveness.Value.Float());
|
|
chance *= adjustedCoefficient;
|
|
}
|
|
}
|
|
|
|
var zombificationResistanceEv = new ZombificationResistanceQueryEvent(ProtectiveSlots);
|
|
RaiseLocalEvent(uid, zombificationResistanceEv);
|
|
chance *= zombificationResistanceEv.TotalCoefficient;
|
|
|
|
return MathF.Max(chance, zombieComponent.MinZombieInfectionChance);
|
|
}
|
|
|
|
private void OnMeleeHit(Entity<ZombieComponent> entity, ref MeleeHitEvent args)
|
|
{
|
|
if (!args.IsHit)
|
|
return;
|
|
|
|
var cannotSpread = HasComp<NonSpreaderZombieComponent>(args.User);
|
|
|
|
foreach (var uid in args.HitEntities)
|
|
{
|
|
if (args.User == uid)
|
|
continue;
|
|
|
|
if (!TryComp<MobStateComponent>(uid, out var mobState))
|
|
continue;
|
|
|
|
if (HasComp<ZombieComponent>(uid) || HasComp<IncurableZombieComponent>(uid))
|
|
{
|
|
// Don't infect, don't deal damage, do not heal from bites, don't pass go!
|
|
args.Handled = true;
|
|
continue;
|
|
}
|
|
|
|
if (_mobState.IsAlive(uid, mobState))
|
|
{
|
|
_damageable.TryChangeDamage(args.User, entity.Comp.HealingOnBite, true, false);
|
|
|
|
// If we cannot infect the living target, the zed will just heal itself.
|
|
if (HasComp<ZombieImmuneComponent>(uid) || cannotSpread || !_random.Prob(GetZombieInfectionChance(uid, entity.Comp)))
|
|
continue;
|
|
|
|
EnsureComp<PendingZombieComponent>(uid);
|
|
EnsureComp<ZombifyOnDeathComponent>(uid);
|
|
}
|
|
else
|
|
{
|
|
if (HasComp<ZombieImmuneComponent>(uid) || cannotSpread)
|
|
continue;
|
|
|
|
// If the target is dead and can be infected, infect.
|
|
ZombifyEntity(uid);
|
|
args.Handled = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is the function to call if you want to unzombify an entity.
|
|
/// </summary>
|
|
/// <param name="source">the entity having the ZombieComponent</param>
|
|
/// <param name="target">the entity you want to unzombify (different from source in case of cloning, for example)</param>
|
|
/// <param name="zombiecomp"></param>
|
|
/// <remarks>
|
|
/// this currently only restore the skin/eye color from before zombified
|
|
/// TODO: completely rethink how zombies are done to allow reversal.
|
|
/// </remarks>
|
|
public bool UnZombify(EntityUid source, EntityUid target, ZombieComponent? zombiecomp)
|
|
{
|
|
if (!Resolve(source, ref zombiecomp))
|
|
return false;
|
|
|
|
_visualBody.ApplyProfiles(target, zombiecomp.BeforeZombifiedProfiles);
|
|
_visualBody.ApplyMarkings(target, zombiecomp.BeforeZombifiedMarkings);
|
|
|
|
_bloodstream.ChangeBloodReagents(target, zombiecomp.BeforeZombifiedBloodReagents);
|
|
|
|
return true;
|
|
}
|
|
|
|
private void OnZombieCloning(Entity<ZombieComponent> ent, ref CloningEvent args)
|
|
{
|
|
UnZombify(ent.Owner, args.CloneUid, ent.Comp);
|
|
}
|
|
|
|
// Make sure players that enter a zombie (for example via a ghost role or the mind swap spell) count as an antagonist.
|
|
private void OnMindAdded(Entity<ZombieComponent> ent, ref MindAddedMessage args)
|
|
{
|
|
if (!_role.MindHasRole<ZombieRoleComponent>(args.Mind))
|
|
_role.MindAddRole(args.Mind, "MindRoleZombie", mind: args.Mind.Comp);
|
|
}
|
|
|
|
// Remove the role when getting cloned, getting gibbed and borged, or leaving the body via any other method.
|
|
// We also need to make sure the zombie is a ghost role because zombies with minds do not get a ghostrolecomponent
|
|
private void OnMindRemoved(Entity<ZombieComponent> ent, ref MindRemovedMessage args)
|
|
{
|
|
_role.MindRemoveRole<ZombieRoleComponent>((args.Mind.Owner, args.Mind.Comp));
|
|
MakeGhostRole(ent.Owner);
|
|
}
|
|
|
|
private void OnAttemptConvert(Entity<ZombieComponent> ent, ref AttemptConvertRevolutionaryEvent args)
|
|
{
|
|
args.Cancelled = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Makes the target entity a zombie ghost role. Should only be fired when the entity does not have a mind.
|
|
/// </summary>
|
|
private void MakeGhostRole(EntityUid ent)
|
|
{
|
|
//yet more hardcoding. Visit zombie.ftl for more information.
|
|
var ghostRole = EnsureComp<GhostRoleComponent>(ent);
|
|
EnsureComp<GhostTakeoverAvailableComponent>(ent);
|
|
|
|
ghostRole.RoleName = Loc.GetString("zombie-generic");
|
|
ghostRole.RoleDescription = Loc.GetString("zombie-role-desc");
|
|
ghostRole.RoleRules = Loc.GetString("zombie-role-rules");
|
|
ghostRole.MindRoles.Add(MindRoleZombie);
|
|
}
|
|
}
|
|
}
|