--- /dev/null
+namespace Content.Server.GameTicking.Rules.Components;
+
+/// <summary>
+/// Component for the SurvivorRuleSystem. Game rule that turns everyone into a survivor and gives them the objective to escape centcom alive.
+/// Started by Wizard Summon Guns/Magic spells.
+/// </summary>
+[RegisterComponent, Access(typeof(SurvivorRuleSystem))]
+public sealed partial class SurvivorRuleComponent : Component;
--- /dev/null
+using Content.Server.Antag;
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.Mind;
+using Content.Server.Roles;
+using Content.Server.Shuttles.Systems;
+using Content.Shared.GameTicking.Components;
+using Content.Shared.Mind;
+using Content.Shared.Mobs.Systems;
+using Content.Shared.Survivor.Components;
+using Content.Shared.Tag;
+using Robust.Server.GameObjects;
+
+namespace Content.Server.GameTicking.Rules;
+
+public sealed class SurvivorRuleSystem : GameRuleSystem<SurvivorRuleComponent>
+{
+ [Dependency] private readonly RoleSystem _role = default!;
+ [Dependency] private readonly MindSystem _mind = default!;
+ [Dependency] private readonly AntagSelectionSystem _antag = default!;
+ [Dependency] private readonly TransformSystem _xform = default!;
+ [Dependency] private readonly EmergencyShuttleSystem _eShuttle = default!;
+ [Dependency] private readonly TagSystem _tag = default!;
+ [Dependency] private readonly MobStateSystem _mobState = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<SurvivorRoleComponent, GetBriefingEvent>(OnGetBriefing);
+ }
+
+ // TODO: Planned rework post wizard release when RandomGlobalSpawnSpell becomes a gamerule
+ protected override void Started(EntityUid uid, SurvivorRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
+ {
+ base.Started(uid, component, gameRule, args);
+
+ var allAliveHumanMinds = _mind.GetAliveHumans();
+
+ foreach (var humanMind in allAliveHumanMinds)
+ {
+ if (!humanMind.Comp.OwnedEntity.HasValue)
+ continue;
+
+ var mind = humanMind.Owner;
+ var ent = humanMind.Comp.OwnedEntity.Value;
+
+ if (HasComp<SurvivorComponent>(mind) || _tag.HasTag(mind, "InvalidForSurvivorAntag"))
+ continue;
+
+ EnsureComp<SurvivorComponent>(mind);
+ _role.MindAddRole(mind, "MindRoleSurvivor");
+ _antag.SendBriefing(ent, Loc.GetString("survivor-role-greeting"), Color.Olive, null);
+ }
+ }
+
+ private void OnGetBriefing(Entity<SurvivorRoleComponent> ent, ref GetBriefingEvent args)
+ {
+ args.Append(Loc.GetString("survivor-role-greeting"));
+ }
+
+ protected override void AppendRoundEndText(EntityUid uid,
+ SurvivorRuleComponent component,
+ GameRuleComponent gameRule,
+ ref RoundEndTextAppendEvent args)
+ {
+ base.AppendRoundEndText(uid, component, gameRule, ref args);
+
+ // Using this instead of alive antagonists to make checking for shuttle & if the ent is alive easier
+ var existingSurvivors = AllEntityQuery<SurvivorComponent, MindComponent>();
+
+ var deadSurvivors = 0;
+ var aliveMarooned = 0;
+ var aliveOnShuttle = 0;
+ var eShuttle = _eShuttle.GetShuttle();
+
+ while (existingSurvivors.MoveNext(out _, out _, out var mindComp))
+ {
+ // If their brain is gone or they respawned/became a ghost role
+ if (mindComp.CurrentEntity is null)
+ {
+ deadSurvivors++;
+ continue;
+ }
+
+ var survivor = mindComp.CurrentEntity.Value;
+
+ if (!_mobState.IsAlive(survivor))
+ {
+ deadSurvivors++;
+ continue;
+ }
+
+ if (eShuttle != null && eShuttle.Value.IsValid() && (Transform(eShuttle.Value).MapID == _xform.GetMapCoordinates(survivor).MapId))
+ {
+ aliveOnShuttle++;
+ continue;
+ }
+
+ aliveMarooned++;
+ }
+
+ args.AddLine(Loc.GetString("survivor-round-end-dead-count", ("deadCount", deadSurvivors)));
+ args.AddLine(Loc.GetString("survivor-round-end-alive-count", ("aliveCount", aliveMarooned)));
+ args.AddLine(Loc.GetString("survivor-round-end-alive-on-shuttle-count", ("aliveCount", aliveOnShuttle)));
+
+ // Player manifest at EOR shows who's a survivor so no need for extra info here.
+ }
+}
using Content.Server.Chat.Systems;
+using Content.Server.GameTicking;
+using Content.Server.GameTicking.Rules.Components;
using Content.Shared.Magic;
using Content.Shared.Magic.Events;
+using Content.Shared.Mind;
+using Content.Shared.Tag;
+using Robust.Shared.Prototypes;
namespace Content.Server.Magic;
public sealed class MagicSystem : SharedMagicSystem
{
[Dependency] private readonly ChatSystem _chat = default!;
+ [Dependency] private readonly GameTicker _gameTicker = default!;
+ [Dependency] private readonly TagSystem _tag = default!;
+ [Dependency] private readonly SharedMindSystem _mind = default!;
public override void Initialize()
{
Spawn(ev.Effect, perfXForm.Coordinates);
Spawn(ev.Effect, targetXForm.Coordinates);
}
+
+ protected override void OnRandomGlobalSpawnSpell(RandomGlobalSpawnSpellEvent ev)
+ {
+ base.OnRandomGlobalSpawnSpell(ev);
+
+ if (!ev.MakeSurvivorAntagonist)
+ return;
+
+ if (_mind.TryGetMind(ev.Performer, out var mind, out _) && !_tag.HasTag(mind, "InvalidForSurvivorAntag"))
+ _tag.AddTag(mind, "InvalidForSurvivorAntag");
+
+ EntProtoId survivorRule = "Survivor";
+
+ if (!_gameTicker.IsGameRuleActive<SurvivorRuleComponent>())
+ _gameTicker.StartGameRule(survivorRule);
+ }
}
--- /dev/null
+using Content.Shared.Roles;
+
+namespace Content.Server.Roles;
+
+/// <summary>
+/// Adds to a mind role ent to tag they're a Survivor
+/// </summary>
+[RegisterComponent]
+public sealed partial class SurvivorRoleComponent : BaseMindRoleComponent;
using Content.Shared.Traits.Assorted;
using Robust.Shared.Audio.Systems;
using Content.Shared.Ghost.Roles.Components;
+using Content.Shared.Tag;
namespace Content.Server.Zombies;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
[Dependency] private readonly NPCSystem _npc = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
+ [Dependency] private readonly TagSystem _tag = default!;
/// <summary>
/// Handles an entity turning into a zombie when they die or go into crit
RaiseLocalEvent(target, ref ev, true);
//zombies get slowdown once they convert
_movementSpeedModifier.RefreshMovementSpeedModifiers(target);
+
+ //Need to prevent them from getting an item, they have no hands.
+ // Also prevents them from becoming a Survivor. They're undead.
+ _tag.AddTag(target, "InvalidForGlobalSpawnSpell");
}
}
[DataField]
public string? Speech { get; private set; }
+
+ /// <summary>
+ /// Should this Global spawn spell turn its targets into a Survivor Antagonist?
+ /// Ignores the caster for this.
+ /// </summary>
+ [DataField]
+ public bool MakeSurvivorAntagonist = false;
}
#endregion
#region Global Spells
- private void OnRandomGlobalSpawnSpell(RandomGlobalSpawnSpellEvent ev)
+ // TODO: Change this into a "StartRuleAction" when actions with multiple events are supported
+ protected virtual void OnRandomGlobalSpawnSpell(RandomGlobalSpawnSpellEvent ev)
{
if (!_net.IsServer || ev.Handled || !PassesSpellPrerequisites(ev.Action, ev.Performer) || ev.Spawns is not { } spawns)
return;
var ent = human.Comp.OwnedEntity.Value;
+ if (_tag.HasTag(ent, "InvalidForGlobalSpawnSpell"))
+ continue;
+
var mapCoords = _transform.GetMapCoordinates(ent);
foreach (var spawn in EntitySpawnCollection.GetSpawns(spawns, _random))
{
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Survivor.Components;
+
+/// <summary>
+/// Component to keep track of which entities are a Survivor antag.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class SurvivorComponent : Component;
--- /dev/null
+## Survivor
+
+roles-antag-survivor-name = Survivor
+# It's a Halo reference
+roles-antag-survivor-objective = Current Objective: Survive
+
+survivor-role-greeting =
+ You are a Survivor.
+ Above all you need to make it back to CentComm alive.
+ Collect as much firepower as needed to guarantee your survival.
+ Trust no one.
+
+survivor-round-end-dead-count =
+{
+ $deadCount ->
+ [one] [color=red]{$deadCount}[/color] survivor died.
+ *[other] [color=red]{$deadCount}[/color] survivors died.
+}
+
+survivor-round-end-alive-count =
+{
+ $aliveCount ->
+ [one] [color=yellow]{$aliveCount}[/color] survivor was marooned on the station.
+ *[other] [color=yellow]{$aliveCount}[/color] survivors were marooned on the station.
+}
+
+survivor-round-end-alive-on-shuttle-count =
+{
+ $aliveCount ->
+ [one] [color=green]{$aliveCount}[/color] survivor made it out alive.
+ *[other] [color=green]{$aliveCount}[/color] survivors made it out alive.
+}
+
+## TODO: Wizard
+
+## TODO: Wizard Apprentice (Coming sometime post-wizard release)
--- /dev/null
+- type: entity
+ id: Survivor
+ parent: BaseGameRule
+ components:
+ - type: SurvivorRule
sprite: Objects/Weapons/Guns/Rifles/ak.rsi
state: base
event: !type:RandomGlobalSpawnSpellEvent
+ makeSurvivorAntagonist: true
spawns:
- id: WeaponPistolViper
orGroup: Guns
- id: RevolverCapGunFake
orGroup: Guns
speech: action-speech-spell-summon-guns
-
+
- type: entity
id: ActionSummonMagic
name: Summon Magic
sprite: Objects/Magic/magicactions.rsi
state: magicmissile
event: !type:RandomGlobalSpawnSpellEvent
+ makeSurvivorAntagonist: true
spawns:
- id: SpawnSpellbook
orGroup: Magics
--- /dev/null
+# TODO: Actual wizard coming later, this is just for the survival antags
+
+- type: antag
+ id: Survivor
+ name: roles-antag-survivor-name
+ antagonist: true
+ objective: roles-antag-survivor-objective
+ # guides: [ ]
- type: MindRole
antagPrototype: Rev
+# Survivors (Wizard)
+- type: entity
+ parent: BaseMindRoleAntag
+ id: MindRoleSurvivor
+ name: Survivor Role
+ components:
+ - type: MindRole
+ antagPrototype: Survivor
+ roleType: FreeAgent
+ - type: SurvivorRole
+
# Thief
- type: entity
parent: BaseMindRoleAntag
id: SiliconAntagonist
name: role-type-silicon-antagonist-name
color: '#c832e6'
+
- type: Tag
id: IntercomElectronics
+- type: Tag
+ id: InvalidForGlobalSpawnSpell
+
+- type: Tag
+ id: InvalidForSurvivorAntag
+
- type: Tag
id: JawsOfLife