Zombie Mode [New Game Mode] (#8501)

Co-authored-by: Kara <lunarautomaton6@gmail.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
Nemanja
2022-07-05 23:42:51 -04:00
committed by GitHub
parent ab12345168
commit 836c0bb1c4
43 changed files with 821 additions and 51 deletions

View File

@@ -207,7 +207,7 @@ namespace Content.Server.Chat.Managers
#region Utility
public void ChatMessageToOne(ChatChannel channel, string message, string messageWrap, EntityUid source, bool hideChat, INetChannel client)
public void ChatMessageToOne(ChatChannel channel, string message, string messageWrap, EntityUid source, bool hideChat, INetChannel client, Color? colorOverride = null)
{
var msg = new MsgChatMessage();
msg.Channel = channel;
@@ -215,6 +215,10 @@ namespace Content.Server.Chat.Managers
msg.MessageWrap = messageWrap;
msg.SenderEntity = source;
msg.HideChat = hideChat;
if (colorOverride != null)
{
msg.MessageColorOverride = colorOverride.Value;
}
_netManager.ServerSendMessage(msg, client);
}

View File

@@ -24,7 +24,7 @@ namespace Content.Server.Chat.Managers
void SendAdminAnnouncement(string message);
void ChatMessageToOne(ChatChannel channel, string message, string messageWrap, EntityUid source, bool hideChat,
INetChannel client);
INetChannel client, Color? colorOverride = null);
void ChatMessageToMany(ChatChannel channel, string message, string messageWrap, EntityUid source, bool hideChat,
List<INetChannel> clients, Color? colorOverride = null);
void ChatMessageToManyFiltered(Filter filter, ChatChannel channel, string message, string messageWrap, EntityUid source, bool hideChat, Color? colorOverride);

View File

@@ -0,0 +1,312 @@
using System.Linq;
using Content.Server.Actions;
using Content.Server.Chat.Managers;
using Content.Server.Disease;
using Content.Server.GameTicking.Rules.Configurations;
using Content.Server.Mind.Components;
using Content.Server.Players;
using Content.Server.Popups;
using Content.Server.Preferences.Managers;
using Content.Server.RoundEnd;
using Content.Server.Traitor;
using Content.Server.Zombies;
using Content.Shared.Actions.ActionTypes;
using Content.Shared.CCVar;
using Content.Shared.CharacterAppearance.Components;
using Content.Shared.FixedPoint;
using Content.Shared.MobState;
using Content.Shared.MobState.Components;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Content.Shared.Zombies;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Server.GameTicking.Rules;
public sealed class ZombieRuleSystem : GameRuleSystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IServerPreferencesManager _prefs = default!;
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
[Dependency] private readonly DiseaseSystem _diseaseSystem = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly ActionsSystem _action = default!;
[Dependency] private readonly ZombifyOnDeathSystem _zombify = default!;
private Dictionary<string, string> _initialInfectedNames = new();
public override string Prototype => "Zombie";
private const string PatientZeroPrototypeID = "InitialInfected";
private const string InitialZombieVirusPrototype = "PassiveZombieVirus";
private const string ZombifySelfActionPrototype = "TurnUndead";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RoundStartAttemptEvent>(OnStartAttempt);
SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndText);
SubscribeLocalEvent<RulePlayerJobsAssignedEvent>(OnJobAssigned);
SubscribeLocalEvent<EntityZombifiedEvent>(OnEntityZombified);
SubscribeLocalEvent<ZombifyOnDeathComponent, ZombifySelfActionEvent>(OnZombifySelf);
}
private void OnRoundEndText(RoundEndTextAppendEvent ev)
{
if (!Enabled)
return;
//this is just the general condition thing used for determining the win/lose text
var percent = Math.Round(GetInfectedPercentage(out var livingHumans), 2);
if (percent <= 0)
ev.AddLine(Loc.GetString("zombie-round-end-amount-none"));
else if (percent <= 0.25)
ev.AddLine(Loc.GetString("zombie-round-end-amount-low"));
else if (percent <= 0.5)
ev.AddLine(Loc.GetString("zombie-round-end-amount-medium", ("percent", (percent * 100).ToString())));
else if (percent < 1)
ev.AddLine(Loc.GetString("zombie-round-end-amount-high", ("percent", (percent * 100).ToString())));
else
ev.AddLine(Loc.GetString("zombie-round-end-amount-all"));
ev.AddLine(Loc.GetString("zombie-round-end-initial-count", ("initialCount", _initialInfectedNames.Count)));
foreach (var player in _initialInfectedNames)
{
ev.AddLine(Loc.GetString("zombie-round-end-user-was-initial",
("name", player.Key),
("username", player.Value)));
}
///Gets a bunch of the living players and displays them if they're under a threshold.
///InitialInfected is used for the threshold because it scales with the player count well.
if (livingHumans.Count > 0 && livingHumans.Count <= _initialInfectedNames.Count)
{
ev.AddLine("");
ev.AddLine(Loc.GetString("zombie-round-end-survivor-count", ("count", livingHumans.Count)));
foreach (var survivor in livingHumans)
{
var meta = MetaData(survivor);
var username = string.Empty;
if (TryComp<MindComponent>(survivor, out var mindcomp))
if (mindcomp.Mind != null && mindcomp.Mind.Session != null)
username = mindcomp.Mind.Session.Name;
ev.AddLine(Loc.GetString("zombie-round-end-user-was-survivor",
("name", meta.EntityName),
("username", username)));
}
}
}
private void OnJobAssigned(RulePlayerJobsAssignedEvent ev)
{
if (!Enabled)
return;
_initialInfectedNames = new();
InfectInitialPlayers();
}
/// <remarks>
/// This is just checked if the last human somehow dies
/// by starving or flying off into space.
/// </remarks>
private void OnMobStateChanged(MobStateChangedEvent ev)
{
if (!Enabled)
return;
CheckRoundEnd(ev.Entity);
}
private void OnEntityZombified(EntityZombifiedEvent ev)
{
if (!Enabled)
return;
CheckRoundEnd(ev.Target);
}
/// <summary>
/// The big kahoona function for checking if the round is gonna end
/// </summary>
/// <param name="target">depending on this uid, we should care about the round ending</param>
private void CheckRoundEnd(EntityUid target)
{
//we only care about players, not monkeys and such.
if (!HasComp<HumanoidAppearanceComponent>(target))
return;
var percent = GetInfectedPercentage(out var num);
if (num.Count == 1) //only one human left. spooky
_popup.PopupEntity(Loc.GetString("zombie-alone"), num[0], Filter.Entities(num[0]));
if (percent >= 1) //oops, all zombies
_roundEndSystem.EndRound();
}
private void OnStartAttempt(RoundStartAttemptEvent ev)
{
if (!Enabled)
return;
var minPlayers = _cfg.GetCVar(CCVars.ZombieMinPlayers);
if (!ev.Forced && ev.Players.Length < minPlayers)
{
_chatManager.DispatchServerAnnouncement(Loc.GetString("zombie-not-enough-ready-players", ("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers)));
ev.Cancel();
return;
}
if (ev.Players.Length == 0)
{
_chatManager.DispatchServerAnnouncement(Loc.GetString("zombie-no-one-ready"));
ev.Cancel();
return;
}
}
public override void Started(GameRuleConfiguration configuration)
{
//this technically will run twice with zombies on roundstart, but it doesn't matter because it fails instantly
InfectInitialPlayers();
}
public override void Ended(GameRuleConfiguration configuration) { }
private void OnZombifySelf(EntityUid uid, ZombifyOnDeathComponent component, ZombifySelfActionEvent args)
{
_zombify.ZombifyEntity(uid);
var action = new InstantAction(_prototypeManager.Index<InstantActionPrototype>(ZombifySelfActionPrototype));
_action.RemoveAction(uid, action);
}
private float GetInfectedPercentage(out List<EntityUid> livingHumans)
{
var allPlayers = EntityQuery<HumanoidAppearanceComponent, MobStateComponent>(true);
var allZombers = GetEntityQuery<ZombieComponent>();
var totalPlayers = new List<EntityUid>();
var livingZombies = new List<EntityUid>();
livingHumans = new();
foreach (var ent in allPlayers)
{
if (ent.Item2.IsAlive())
{
totalPlayers.Add(ent.Item2.Owner);
if (allZombers.HasComponent(ent.Item1.Owner))
livingZombies.Add(ent.Item2.Owner);
else
livingHumans.Add(ent.Item2.Owner);
}
}
return ((float) livingZombies.Count) / (float) totalPlayers.Count;
}
/// <summary>
/// Infects the first players with the passive zombie virus.
/// Also records their names for the end of round screen.
/// </summary>
/// <remarks>
/// The reason this code is written separately is to facilitate
/// allowing this gamemode to be started midround. As such, it doesn't need
/// any information besides just running.
/// </remarks>
private void InfectInitialPlayers()
{
var allPlayers = _playerManager.ServerSessions.ToList();
var playerList = new List<IPlayerSession>();
var prefList = new List<IPlayerSession>();
foreach (var player in allPlayers)
{
if (player.AttachedEntity != null)
{
playerList.Add(player);
var pref = (HumanoidCharacterProfile) _prefs.GetPreferences(player.UserId).SelectedCharacter;
if (pref.AntagPreferences.Contains(PatientZeroPrototypeID))
prefList.Add(player);
}
}
if (playerList.Count == 0)
return;
var playersPerInfected = _cfg.GetCVar(CCVars.ZombiePlayersPerInfected);
var maxInfected = _cfg.GetCVar(CCVars.ZombieMaxInitialInfected);
var numInfected = Math.Max(1,
(int) Math.Min(
Math.Floor((double) playerList.Count / playersPerInfected), maxInfected));
for (var i = 0; i < numInfected; i++)
{
IPlayerSession zombie;
if (prefList.Count == 0)
{
if (playerList.Count == 0)
{
Logger.InfoS("preset", "Insufficient number of players. stopping selection.");
break;
}
zombie = _random.PickAndTake(playerList);
Logger.InfoS("preset", "Insufficient preferred patient 0, picking at random.");
}
else
{
zombie = _random.PickAndTake(prefList);
playerList.Remove(zombie);
Logger.InfoS("preset", "Selected a patient 0.");
}
var mind = zombie.Data.ContentData()?.Mind;
if (mind == null)
{
Logger.ErrorS("preset", "Failed getting mind for picked patient 0.");
continue;
}
DebugTools.AssertNotNull(mind.OwnedEntity);
mind.AddRole(new TraitorRole(mind, _prototypeManager.Index<AntagPrototype>(PatientZeroPrototypeID)));
var inCharacterName = string.Empty;
if (mind.OwnedEntity != null)
{
_diseaseSystem.TryAddDisease(mind.OwnedEntity.Value, InitialZombieVirusPrototype);
inCharacterName = MetaData(mind.OwnedEntity.Value).EntityName;
var action = new InstantAction(_prototypeManager.Index<InstantActionPrototype>(ZombifySelfActionPrototype));
_action.AddAction(mind.OwnedEntity.Value, action, null);
}
if (mind.Session != null)
{
var messageWrapper = Loc.GetString("chat-manager-server-wrap-message");
//gets the names now in case the players leave.
if (inCharacterName != null)
_initialInfectedNames.Add(inCharacterName, mind.Session.Name);
// I went all the way to ChatManager.cs and all i got was this lousy T-shirt
_chatManager.ChatMessageToOne(Shared.Chat.ChatChannel.Server, Loc.GetString("zombie-patientzero-role-greeting"),
messageWrapper, default, false, mind.Session.ConnectedClient, Color.Plum);
}
}
}
}

View File

@@ -1,3 +1,7 @@
using Content.Shared.Roles;
using Content.Shared.Weapons.Melee;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Zombies
{
[RegisterComponent]
@@ -7,6 +11,44 @@ namespace Content.Server.Zombies
/// The coefficient of the damage reduction applied when a zombie
/// attacks another zombie. longe name
/// </summary>
public float OtherZombieDamageCoefficient = 0.75f;
[ViewVariables]
public float OtherZombieDamageCoefficient = 0.5f;
/// <summary>
/// The baseline infection chance you have if you are completely nude
/// </summary>
[ViewVariables]
public float MaxZombieInfectionChance = 0.75f;
/// <summary>
/// The minimum infection chance possible. This is simply to prevent
/// being invincible by bundling up.
/// </summary>
[ViewVariables]
public float MinZombieInfectionChance = 0.1f;
/// <summary>
/// The skin color of the zombie
/// </summary>
[ViewVariables, DataField("skinColor")]
public Color SkinColor = new(0.45f, 0.51f, 0.29f);
/// <summary>
/// The eye color of the zombie
/// </summary>
[ViewVariables, DataField("eyeColor")]
public Color EyeColor = new(0.96f, 0.13f, 0.24f);
/// <summary>
/// The attack arc of the zombie
/// </summary>
[ViewVariables, DataField("attackArc", customTypeSerializer: typeof(PrototypeIdSerializer<MeleeWeaponAnimationPrototype>))]
public string AttackArc = "claw";
/// <summary>
/// The role prototype of the zombie antag role
/// </summary>
[ViewVariables, DataField("zombieRoldId", customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
public readonly string ZombieRoleId = "Zombie";
}
}

View File

@@ -7,6 +7,11 @@ using Content.Server.Weapon.Melee;
using Content.Shared.Chemistry.Components;
using Content.Shared.MobState.Components;
using Content.Server.Disease;
using Content.Shared.Inventory;
using Content.Server.Popups;
using Robust.Shared.Player;
using Content.Server.Inventory;
using Robust.Shared.Prototypes;
namespace Content.Server.Zombies
{
@@ -15,13 +20,52 @@ namespace Content.Server.Zombies
[Dependency] private readonly DiseaseSystem _disease = default!;
[Dependency] private readonly BloodstreamSystem _bloodstream = default!;
[Dependency] private readonly ZombifyOnDeathSystem _zombify = default!;
[Dependency] private readonly ServerInventorySystem _inv = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ZombieComponent, MeleeHitEvent>(OnMeleeHit);
}
private float GetZombieInfectionChance(EntityUid uid, ZombieComponent component)
{
float baseChance = component.MaxZombieInfectionChance;
if (!TryComp<InventoryComponent>(uid, out var inventoryComponent))
return baseChance;
var enumerator =
new InventorySystem.ContainerSlotEnumerator(uid, inventoryComponent.TemplateId, _protoManager, _inv,
SlotFlags.FEET |
SlotFlags.HEAD |
SlotFlags.EYES |
SlotFlags.GLOVES |
SlotFlags.MASK |
SlotFlags.NECK |
SlotFlags.INNERCLOTHING |
SlotFlags.OUTERCLOTHING);
var items = 0f;
var total = 0f;
while (enumerator.MoveNext(out var con))
{
total++;
if (con.ContainedEntity != null)
items++;
}
var max = component.MaxZombieInfectionChance;
var min = component.MinZombieInfectionChance;
//gets a value between the max and min based on how many items the entity is wearing
float chance = (max-min) * ((total - items)/total) + min;
return chance;
}
private void OnMeleeHit(EntityUid uid, ZombieComponent component, MeleeHitEvent args)
{
if (!EntityManager.TryGetComponent<ZombieComponent>(args.User, out var zombieComp))
@@ -38,11 +82,11 @@ namespace Content.Server.Zombies
if (!TryComp<MobStateComponent>(entity, out var mobState) || HasComp<DroneComponent>(entity))
continue;
if (_robustRandom.Prob(0.5f) && HasComp<DiseaseCarrierComponent>(entity))
if (HasComp<DiseaseCarrierComponent>(entity) && _robustRandom.Prob(GetZombieInfectionChance(entity, component)))
_disease.TryAddDisease(entity, "ActiveZombieVirus");
if (HasComp<ZombieComponent>(entity))
args.BonusDamage = args.BaseDamage * zombieComp.OtherZombieDamageCoefficient;
args.BonusDamage = -args.BaseDamage * zombieComp.OtherZombieDamageCoefficient;
if ((mobState.IsDead() || mobState.IsCritical())
&& !HasComp<ZombieComponent>(entity))

View File

@@ -1,15 +1,8 @@
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<MeleeWeaponAnimationPrototype>))]
public string AttackArc = "claw";
//this is not the component you are looking for
}
}

View File

@@ -20,16 +20,23 @@ 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;
using Robust.Shared.Prototypes;
using Content.Shared.Roles;
using Content.Server.Traitor;
using Content.Shared.Zombies;
using Content.Server.Atmos.Miasma;
namespace Content.Server.Zombies
{
/// <summary>
/// Handles zombie propagation and inherent zombie traits
/// </summary>
/// <remarks>
/// Don't Shitcode Open Inside
/// </remarks>
public sealed class ZombifyOnDeathSystem : EntitySystem
{
[Dependency] private readonly SharedHandsSystem _sharedHands = default!;
@@ -37,9 +44,9 @@ namespace Content.Server.Zombies
[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!;
[Dependency] private readonly IPrototypeManager _proto = default!;
public override void Initialize()
{
@@ -53,11 +60,8 @@ namespace Content.Server.Zombies
/// </summary>
private void OnDamageChanged(EntityUid uid, ZombifyOnDeathComponent component, MobStateChangedEvent args)
{
if (!TryComp<MobStateComponent>(uid, out var mobstate))
return;
if (mobstate.IsDead() ||
mobstate.IsCritical())
if (args.CurrentMobState.IsDead() ||
args.CurrentMobState.IsCritical())
{
ZombifyEntity(uid);
}
@@ -65,83 +69,126 @@ namespace Content.Server.Zombies
/// <summary>
/// This is the general purpose function to call if you want to zombify an entity.
/// It handles both humanoid and nonhumanoid transformation.
/// It handles both humanoid and nonhumanoid transformation and everything should be called through it.
/// </summary>
/// <param name="target">the entity being zombified</param>
/// <remarks>
/// ALRIGHT BIG BOY. YOU'VE COME TO THE LAYER OF THE BEAST. THIS IS YOUR WARNING.
/// This function is the god function for zombie stuff, and it is cursed. I have
/// attempted to label everything thouroughly for your sanity. I have attempted to
/// rewrite this, but this is how it shall lie eternal. Turn back now.
/// -emo
/// </remarks>
public void ZombifyEntity(EntityUid target)
{
//Don't zombfiy zombies
if (HasComp<ZombieComponent>(target))
return;
_disease.CureAllDiseases(target);
//you're a real zombie now, son.
var zombiecomp = AddComp<ZombieComponent>(target);
///we need to basically remove all of these because zombies shouldn't
///get diseases, breath, be thirst, be hungry, or die in space
RemComp<DiseaseCarrierComponent>(target);
RemComp<RespiratorComponent>(target);
RemComp<BarotraumaComponent>(target);
RemComp<HungerComponent>(target);
RemComp<ThirstComponent>(target);
var zombiecomp = EnsureComp<ZombifyOnDeathComponent>(target);
if (TryComp<HumanoidAppearanceComponent>(target, out var huApComp))
{
var appearance = huApComp.Appearance;
_sharedHuApp.UpdateAppearance(target, appearance.WithSkinColor(zombiecomp.SkinColor), huApComp);
_sharedHuApp.ForceAppearanceUpdate(target, huApComp);
}
if (!HasComp<SharedDummyInputMoverComponent>(target))
MakeSentientCommand.MakeSentient(target, EntityManager);
//funny voice
EnsureComp<ReplacementAccentComponent>(target).Accent = "zombie";
EnsureComp<RottingComponent>(target);
//funny add delet go brrr
///This is needed for stupid entities that fuck up combat mode component
///in an attempt to make an entity not attack. This is the easiest way to do it.
RemComp<CombatModeComponent>(target);
AddComp<CombatModeComponent>(target);
///This is the actual damage of the zombie. We assign the visual appearance
///and range here because of stuff we'll find out later
var melee = EnsureComp<MeleeWeaponComponent>(target);
melee.Arc = zombiecomp.AttackArc;
melee.ClickArc = zombiecomp.AttackArc;
//lord forgive me for the hardcoded damage
melee.Range = 0.75f;
//We have specific stuff for humanoid zombies because they matter more
if (TryComp<HumanoidAppearanceComponent>(target, out var huApComp)) //huapcomp
{
//this bs is done because you can't directly update humanoid appearances
var appearance = huApComp.Appearance;
appearance = appearance.WithSkinColor(zombiecomp.SkinColor).WithEyeColor(zombiecomp.EyeColor);
_sharedHuApp.UpdateAppearance(target, appearance, huApComp);
_sharedHuApp.ForceAppearanceUpdate(target, huApComp);
///This is done here because non-humanoids shouldn't get baller damage
///lord forgive me for the hardcoded damage
DamageSpecifier dspec = new();
dspec.DamageDict.Add("Slash", 13);
dspec.DamageDict.Add("Piercing", 7);
melee.Damage = dspec;
}
//The zombie gets the assigned damage weaknesses and strengths
_damageable.SetDamageModifierSetId(target, "Zombie");
///This makes it so the zombie doesn't take bloodloss damage.
///NOTE: they are supposed to bleed, just not take damage
_bloodstream.SetBloodLossThreshold(target, 0f);
_popupSystem.PopupEntity(Loc.GetString("zombie-transform", ("target", target)), target, Filter.Pvs(target));
//This is specifically here to combat insuls, because frying zombies on grilles is funny as shit.
_serverInventory.TryUnequip(target, "gloves", true, true);
//popup
_popupSystem.PopupEntity(Loc.GetString("zombie-transform", ("target", target)), target, Filter.Pvs(target));
//Make it sentient if it's an animal or something
if (!HasComp<SharedDummyInputMoverComponent>(target)) //this component is cursed and fucks shit up
MakeSentientCommand.MakeSentient(target, EntityManager);
//Make the zombie not die in the cold. Good for space zombies
if (TryComp<TemperatureComponent>(target, out var tempComp))
tempComp.ColdDamage.ClampMax(0);
//Heals the zombie from all the damage it took while human
if (TryComp<DamageableComponent>(target, out var damageablecomp))
_damageable.SetAllDamage(damageablecomp, 0);
//gives it the funny "Zombie ___" name.
if (TryComp<MetaDataComponent>(target, out var meta))
meta.EntityName = Loc.GetString("zombie-name-prefix", ("target", meta.EntityName));
//He's gotta have a mind
var mindcomp = EnsureComp<MindComponent>(target);
if (mindcomp.Mind != null && mindcomp.Mind.TryGetSession(out var session))
{
//Zombie role for player manifest
mindcomp.Mind.AddRole(new TraitorRole(mindcomp.Mind, _proto.Index<AntagPrototype>(zombiecomp.ZombieRoleId)));
//Greeting message for new bebe zombers
_chatMan.DispatchServerMessage(session, Loc.GetString("zombie-infection-greeting"));
}
if (!HasComp<GhostRoleMobSpawnerComponent>(target) && !mindcomp.HasMind) //this specific component gives build test trouble so pop off, ig
{
//yet more hardcoding. Visit zombie.ftl for more information.
EntityManager.EnsureComponent<GhostTakeoverAvailableComponent>(target, out var ghostcomp);
ghostcomp.RoleName = Loc.GetString("zombie-generic");
ghostcomp.RoleDescription = Loc.GetString("zombie-role-desc");
ghostcomp.RoleRules = Loc.GetString("zombie-role-rules");
}
///Goes through every hand, drops the items in it, then removes the hand
///may become the source of various bugs.
foreach (var hand in _sharedHands.EnumerateHands(target))
{
_sharedHands.SetActiveHand(target, hand);
hand.Container?.EmptyContainer();
_sharedHands.DoDrop(target, hand);
_sharedHands.RemoveHand(target, hand.Name);
}
RemComp<HandsComponent>(target);
EnsureComp<ZombieComponent>(target);
//zombie gamemode stuff
RaiseLocalEvent(new EntityZombifiedEvent(target));
}
}
}

View File

@@ -299,6 +299,19 @@ namespace Content.Shared.CCVar
public static readonly CVarDef<int> NukeopsPlayersPerOp =
CVarDef.Create("nukeops.players_per_op", 5);
/*
* Zombie
*/
public static readonly CVarDef<int> ZombieMinPlayers =
CVarDef.Create("zombie.min_players", 20);
public static readonly CVarDef<int> ZombieMaxInitialInfected =
CVarDef.Create("zombie.max_initial_infected", 6);
public static readonly CVarDef<int> ZombiePlayersPerInfected =
CVarDef.Create("zombie.players_per_infected", 10);
/*
* Pirates
*/

View File

@@ -25,7 +25,6 @@ public abstract partial class InventorySystem
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly INetManager _netMan = default!;

View File

@@ -0,0 +1,25 @@
using Content.Shared.Actions;
namespace Content.Shared.Zombies;
/// <summary>
/// Event that is broadcast whenever an entity is zombified.
/// Used by the zombie gamemode to track total infections.
/// </summary>
public readonly struct EntityZombifiedEvent
{
/// <summary>
/// The entity that was zombified.
/// </summary>
public readonly EntityUid Target;
public EntityZombifiedEvent(EntityUid target)
{
Target = target;
}
};
/// <summary>
/// Event raised when a player zombifies themself using the "turn" action
/// </summary>
public sealed class ZombifySelfActionEvent : InstantActionEvent { };

View File

@@ -0,0 +1,2 @@
turn-undead-action-name = Turn Undead
turn-undead-action-description = Succumb to your infection and become a zombie.

View File

@@ -0,0 +1,27 @@
zombie-title = Zombies
zombie-description = A virus able to animate the dead has been unleashed unto the station! Work with your crewmates to contain the outbreak and survive.
zombie-not-enough-ready-players = Not enough players readied up for the game! There were {$readyPlayersCount} players readied up out of {$minimumPlayers} needed. Can't start Zombies.
zombie-no-one-ready = No players readied up! Can't start Zombies.
zombie-patientzero-role-greeting = You are patient 0. Hide your infection, get supplies, and be prepared to turn once you die.
zombie-alone = You feel entirely alone.
zombie-round-end-initial-count = {$initialCount ->
[one] There was one initial infected:
*[other] There were {$initialCount} initial infected:
}
zombie-round-end-user-was-initial = - [color=plum]{$name}[/color] ([color=gray]{$username}[/color]) was one of the initial infected.
zombie-round-end-amount-none = [color=green]All of the zombies were eradicated![/color]
zombie-round-end-amount-low = [color=green]Almost all of the zombies were exterminated.[/color]
zombie-round-end-amount-medium = [color=yellow]{$percent}% of the crew were turned into zombies.[/color]
zombie-round-end-amount-high = [color=crimson]{$percent}% of the crew were turned into zombies.[/color]
zombie-round-end-amount-all = [color=darkred]The entire crew became zombies![/color]
zombie-round-end-survivor-count = {$count ->
[one] There was only one survivor left:
*[other] There were only {$count} survivors left:
}
zombie-round-end-user-was-survivor = - [color=White]{$name}[/color] ([color=gray]{$username}[/color]) survived the outbreak.

View File

@@ -2,6 +2,6 @@ zombie-transform = {CAPITALIZE(THE($target))} turned into a zombie!
zombie-infection-greeting = You have become a zombie. Your goal is to seek out the living and to try to infect them. Work together with your fellow zombies to overpower the remaining crewmates.
zombie-generic = zombie
zombie-name-prefix = zombified {$target}
zombie-name-prefix = Zombified {$target}
zombie-role-desc = A malevolent creature of the dead.
zombie-role-rules = You are an antagonist. Search out the living and bite them in order to infect them and turn them into zombies. Work together with other the zombies to overtake the station.

View File

@@ -7,6 +7,13 @@
serverEvent: !type:ScreamActionEvent
checkCanInteract: false
- type: instantAction
id: TurnUndead
name: turn-undead-action-name
description: turn-undead-action-description
icon: Interface/Actions/zombie-turn.png
event: !type:ZombifySelfActionEvent
- type: instantAction
id: ToggleLight
name: action-name-toggle-light

View File

@@ -13,6 +13,22 @@
- id: Retractor
- id: Scalpel
- type: entity
id: ClothingBackpackDuffelCBURN
parent: ClothingBackpackDuffel
name: CBURN duffel bag
description: A duffel bag containing a variety of biological containment equipment.
components:
- type: StorageFill
contents:
- id: WeaponShotgunDoubleBarreled
- id: BoxShotgunFlare
amount: 2
- id: PillRomerol
amount: 5
- id: GrenadeFlash
amount: 2
- type: entity
parent: ClothingBackpackDuffelSyndicateMedical
id: ClothingBackpackDuffelSyndicateFilledMedical

View File

@@ -117,8 +117,11 @@
Piercing: 0.9
Shock: 1.5
Cold: 0.2
Heat: 3.0
Heat: 2.5
Poison: 0.0
flatReductions: #offsets rotting damage
Blunt: 0.3
Cellular: 0.3
# immune to everything except physical and heat damage
- type: damageModifierSet

View File

@@ -24,3 +24,12 @@
- !type:DiseaseReagentCure
reagent: Romerol
min: 5
- type: disease
id: PassiveZombieVirus
name: Zombie Virus
infectious: false
cureResist: 1 #no cure. Death is your cure.
effects:
- !type:DiseaseAddComponent
comp: ZombifyOnDeath

View File

@@ -405,6 +405,56 @@
Heat: 0.2
Radiation: 0.5
- type: entity
parent: ClothingHeadHardsuitWithLightBase
id: ClothingHeadHelmetCBURN
noSpawn: true
name: syndicate elite helmet
description: A pressure resistant and fireproof hood worn by special cleanup units.
components:
- type: Sprite
netsync: false
sprite: Clothing/Head/Hardsuits/cburn.rsi
layers:
- state: icon
- state: icon-unshaded
shader: unshaded
- state: light-overlay
visible: false
shader: unshaded
map: [ "light" ]
- type: Clothing
clothingVisuals:
head:
- state: equipped-head
- state: equipped-head-unshaded
shader: unshaded
inhandVisuals:
left:
- state: inhand-left
- state: inhand-left-unshaded
shader: unshaded
right:
- state: inhand-right
- state: inhand-right-unshaded
shader: unshaded
- type: PointLight
color: orange
- type: PressureProtection
highPressureMultiplier: 0.08
lowPressureMultiplier: 1000
- type: TemperatureProtection
coefficient: 0.005
- type: Armor
modifiers:
coefficients:
Blunt: 0.9
Slash: 0.9
Piercing: 0.9
Heat: 0.1
Shock: 0.1
Cold: 0.2
Radiation: 0.2
#ERT
- type: entity

View File

@@ -571,6 +571,38 @@
- type: ToggleableClothing
clothingPrototype: ClothingHeadHelmetHardsuitSyndieElite
- type: entity
parent: ClothingOuterHardsuitBase
id: ClothingOuterHardsuitCBURN
name: CBURN exosuit
description: A lightweight yet strong exosuit used for special cleanup operations.
components:
- type: Sprite
sprite: Clothing/OuterClothing/Hardsuits/cburn.rsi
- type: Clothing
sprite: Clothing/OuterClothing/Hardsuits/cburn.rsi
- type: PressureProtection
highPressureMultiplier: 0.02
lowPressureMultiplier: 1000
- type: ClothingSpeedModifier
walkModifier: 1.0
sprintModifier: 1.0
- type: TemperatureProtection
coefficient: 0.001
- type: Armor
modifiers:
coefficients:
Blunt: 0.7
Slash: 0.7
Piercing: 0.6
Heat: 0.05
Cold: 0.1
Shock: 0.1
Radiation: 0.1
- type: ExplosionResistance
resistance: 0.7
- type: ToggleableClothing
clothingPrototype: ClothingHeadHelmetCBURN
#ERT
- type: entity

View File

@@ -34,6 +34,20 @@
- Idle
- Spirate
- type: entity
parent: MobHuman
id: MobCBURNUnit
name: CBURN Agent
description: A miserable pile of secrets
components:
- type: RandomHumanoidAppearance
- type: Loadout
prototype: CBURNGear
- type: GhostTakeoverAvailable
makeSentient: false
name: CBURN Agent
description: A highly trained Centcomm agent, capable of dealing with various threats.
- type: entity
parent: MobHumanBase
suffix: Dead

View File

@@ -0,0 +1,13 @@
- type: antag
id: InitialInfected
name: "Initial Infected"
antagonist: true
setPreference: true
objective: "Once you turn, infect as many other crew members as possible"
- type: antag
id: Zombie
name: "Zombie"
antagonist: true
setPreference: false
objective: "Turn as many humans as possible into zombies."

View File

@@ -137,3 +137,24 @@
innerclothingskirt: ClothingUniformJumpskirtOperative
satchel: ClothingBackpackDuffelSyndicateOperativeMedic
duffelbag: ClothingBackpackDuffelSyndicateOperativeMedic
#CBURN Unit Gear - Full Kit
- type: startingGear
id: CBURNGear
equipment:
jumpsuit: ClothingUniformJumpsuitColorBrown
back: ClothingBackpackDuffelCBURN
mask: ClothingMaskGas
eyes: ClothingEyesGlassesSecurity
ears: ClothingHeadsetService
gloves: ClothingHandsGlovesFingerless
outerClothing: ClothingOuterHardsuitCBURN
shoes: ClothingShoesBootsJack
id: CentcomPDA
pocket1: CombatKnife
pocket2: WeaponLaserGun
suitstorage: YellowOxygenTankFilled
belt: ClothingBeltBandolier
innerclothingskirt: ClothingUniformJumpsuitColorBrown
satchel: ClothingBackpackDuffelCBURN
duffelbag: ClothingBackpackDuffelCBURN

View File

@@ -80,6 +80,20 @@
rules:
- Nukeops
- type: gamePreset
id: Zombie
alias:
- zombie
- zombies
- Zombies
- zz14
- zomber
name: zombie-title
description: zombie-description
showInVote: true
rules:
- Zombie
- type: gamePreset
id: Pirates
alias:

View File

@@ -59,3 +59,9 @@
config:
!type:GenericGameRuleConfiguration
id: Secret
- type: gameRule
id: Zombie
config:
!type:GenericGameRuleConfiguration
id: Zombie

View File

@@ -4,3 +4,4 @@
Extended: 0.25
Nukeops: 0.25
Traitor: 0.75
Zombie: 0.05

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

View File

@@ -0,0 +1,47 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Made by EmoGarbage404",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "icon-unshaded"
},
{
"name": "icon-flash"
},
{
"name": "light-overlay"
},
{
"name": "equipped-head",
"directions": 4
},
{
"name": "equipped-head-unshaded",
"directions": 4
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-left-unshaded",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "inhand-right-unshaded",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,26 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Made by EmoGarbage404",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "equipped-OUTERCLOTHING",
"directions": 4
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
}
]
}

View File

@@ -36,6 +36,9 @@
},
{
"name": "ratKingDomain"
},
{
"name": "zombie-turn"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B