Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zombie Mode [New Game Mode] #8501

Merged
merged 64 commits into from
Jul 6, 2022
Merged
Show file tree
Hide file tree
Changes from 56 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
1c46b73
Merge branch 'space-wizards-master'
EmoGarbage404 Apr 25, 2022
404cb6d
Merge branch 'space-wizards:master' into master
EmoGarbage404 Apr 27, 2022
bc28856
Merge branch 'space-wizards:master' into master
EmoGarbage404 Apr 28, 2022
b78709a
Merge branch 'space-wizards:master' into master
EmoGarbage404 May 1, 2022
c8f849f
Merge branch 'space-wizards:master' into master
EmoGarbage404 May 5, 2022
6ba6110
Merge branch 'space-wizards:master' into master
EmoGarbage404 May 7, 2022
968f756
Merge branch 'space-wizards:master' into master
EmoGarbage404 May 9, 2022
52cca51
Merge branch 'space-wizards:master' into master
EmoGarbage404 May 11, 2022
71d3cb9
Merge branch 'space-wizards:master' into master
EmoGarbage404 May 12, 2022
0a39235
Merge branch 'space-wizards:master' into master
EmoGarbage404 May 13, 2022
f240894
Merge branch 'space-wizards:master' into master
EmoGarbage404 May 13, 2022
df20146
Merge branch 'master' of https://github.com/space-wizards/space-stati…
EmoGarbage404 May 27, 2022
1442766
Merge branch 'space-wizards-master'
EmoGarbage404 May 27, 2022
8e87aab
beginning
EmoGarbage404 May 28, 2022
f45274c
tweakies
EmoGarbage404 May 29, 2022
fa7b5dd
Merge branch 'space-wizards:master' into master
EmoGarbage404 May 29, 2022
df52dd2
Merge https://github.com/EmoGarbage404/space-station-14 into zombie-mode
EmoGarbage404 May 29, 2022
475ad60
cburn + more stuff
EmoGarbage404 May 29, 2022
5c5291c
cburn outfit
EmoGarbage404 May 29, 2022
b077645
json momento
EmoGarbage404 May 29, 2022
9e8e4f8
Merge branch 'space-wizards:master' into master
EmoGarbage404 May 29, 2022
6aafc13
nuke arm event
EmoGarbage404 May 29, 2022
c47ebea
shuttle spawning
EmoGarbage404 May 30, 2022
12d08aa
mini
EmoGarbage404 May 30, 2022
4c218ae
beginning
EmoGarbage404 May 28, 2022
65a8ef4
tweakies
EmoGarbage404 May 29, 2022
e74ba8f
cburn + more stuff
EmoGarbage404 May 29, 2022
f492786
cburn outfit
EmoGarbage404 May 29, 2022
33c90bb
json momento
EmoGarbage404 May 29, 2022
4b7f1ef
nuke arm event
EmoGarbage404 May 29, 2022
67e5625
shuttle spawning
EmoGarbage404 May 30, 2022
f375d20
mini
EmoGarbage404 May 30, 2022
0b905da
Merge branch 'space-wizards:master' into zombie-mode
EmoGarbage404 May 30, 2022
6452615
Merge remote-tracking branch 'origin/zombie-mode' into zombie-mode
EmoGarbage404 May 30, 2022
bd70614
events + weighted prototype
EmoGarbage404 May 30, 2022
4e32420
Update ZombieRuleSystem.cs
EmoGarbage404 May 31, 2022
55f1ba0
ert+ cburn gon
EmoGarbage404 Jun 1, 2022
cd06eeb
more shit
EmoGarbage404 Jun 6, 2022
5b3046d
Merge branch 'master' into zombie-mode
EmoGarbage404 Jun 12, 2022
8e11686
Merge remote-tracking branch 'origin/zombie-mode' into zombie-mode
EmoGarbage404 Jun 12, 2022
67ae8ea
zombies do some more shit now idk
EmoGarbage404 Jun 13, 2022
6b4d0d5
Merge branch 'space-wizards:master' into zombie-mode
EmoGarbage404 Jun 13, 2022
f87b76f
big shit goin on
EmoGarbage404 Jun 17, 2022
d4db04d
Merge remote-tracking branch 'origin/zombie-mode' into zombie-mode
EmoGarbage404 Jun 17, 2022
d02cd0f
prototype fix
EmoGarbage404 Jun 17, 2022
11f432f
zombie code cleanup + warnings + my life
EmoGarbage404 Jun 17, 2022
1447f7a
fixes and shit
EmoGarbage404 Jun 18, 2022
135fb69
aaaa
EmoGarbage404 Jun 18, 2022
d8142b3
Merge branch 'master' into zombie-mode
EmoGarbage404 Jun 19, 2022
96d1679
the finale
EmoGarbage404 Jun 19, 2022
f708ca7
secret mode
EmoGarbage404 Jun 19, 2022
1efd8b7
meta momento
EmoGarbage404 Jun 19, 2022
ea07db2
myassmar
EmoGarbage404 Jun 19, 2022
f84389d
Update secret_weights.yml
EmoGarbage404 Jun 20, 2022
c9bde36
Update BodyReassembleSystem.cs
EmoGarbage404 Jun 20, 2022
d8aa659
Update modifier_sets.yml
EmoGarbage404 Jun 21, 2022
f0b1ece
Update Content.Server/Zombies/ZombieSystem.cs
EmoGarbage404 Jun 24, 2022
1de475f
Update Resources/Locale/en-US/game-ticking/game-presets/preset-zombie…
EmoGarbage404 Jun 24, 2022
d418b2e
mirror reviews
EmoGarbage404 Jun 24, 2022
0e7bdd5
Merge branch 'space-wizards:master' into zombie-mode
EmoGarbage404 Jul 4, 2022
92afd63
sloth review
EmoGarbage404 Jul 4, 2022
1b52a86
make infection slots not shit
EmoGarbage404 Jul 6, 2022
c671dfd
Update ZombieSystem.cs
EmoGarbage404 Jul 6, 2022
6ba29aa
Explicit flags
metalgearsloth Jul 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Content.Server/Chat/Managers/ChatManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,14 +207,18 @@ private void SendAdminChat(IPlayerSession player, string message)

#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;
msg.Message = message;
msg.MessageWrap = messageWrap;
msg.SenderEntity = source;
msg.HideChat = hideChat;
if (colorOverride != null)
{
msg.MessageColorOverride = colorOverride.Value;
}
_netManager.ServerSendMessage(msg, client);
}

Expand Down
2 changes: 1 addition & 1 deletion Content.Server/Chat/Managers/IChatManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public interface IChatManager
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);
Expand Down
2 changes: 1 addition & 1 deletion Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using Content.Server.CharacterAppearance.Components;
using Content.Server.Chat.Managers;
using Content.Server.GameTicking.Rules.Configurations;
Expand Down
309 changes: 309 additions & 0 deletions Content.Server/GameTicking/Rules/ZombieRuleSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
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> _initialInfected = 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 = GetInfectedPercentage(out var livingHumans);

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", _initialInfected.Count)));
foreach (var player in _initialInfected)
{
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 (percent > 0 && livingHumans.Count < _initialInfected.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;

_initialInfected = 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 left
_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 FixedPoint2 GetInfectedPercentage(out List<EntityUid> livingHumans)
{
var allPlayers = EntityQuery<HumanoidAppearanceComponent, MobStateComponent>();
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 (HasComp<ZombieComponent>(ent.Item1.Owner))
livingZombies.Add(ent.Item2.Owner);
else
livingHumans.Add(ent.Item2.Owner);
}
}
return (FixedPoint2) livingZombies.Count / (FixedPoint2) 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.ZombieMaxInfected);

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)
_initialInfected.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);
}
}
}
}
44 changes: 43 additions & 1 deletion Content.Server/Zombies/ZombieComponent.cs
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -7,6 +11,44 @@ public sealed class ZombieComponent : Component
/// 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";
}
}
Loading