--- /dev/null
+using Content.Shared.StatusIcon;
+using Content.Shared.StatusIcon.Components;
+using Robust.Shared.Prototypes;
+using Content.Shared.Ghost;
+using Robust.Client.Player;
+
+namespace Content.Client.Antag;
+
+/// <summary>
+/// Used for assigning specified icons for antags.
+/// </summary>
+public abstract class AntagStatusIconSystem<T> : SharedStatusIconSystem
+ where T : Component
+{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+ [Dependency] private readonly IPlayerManager _player = default!;
+
+ /// <summary>
+ /// Will check if the local player has the same component as the one who called it and give the status icon.
+ /// </summary>
+ /// <param name="antagStatusIcon">The status icon that your antag uses</param>
+ /// <param name="args">The GetStatusIcon event.</param>
+ protected virtual void GetStatusIcon(string antagStatusIcon, ref GetStatusIconsEvent args)
+ {
+ var ent = _player.LocalPlayer?.ControlledEntity;
+
+ if (!HasComp<T>(ent) && !HasComp<GhostComponent>(ent))
+ return;
+
+ args.StatusIcons.Add(_prototype.Index<StatusIconPrototype>(antagStatusIcon));
+ }
+}
--- /dev/null
+using Content.Shared.Revolutionary.Components;
+using Content.Client.Antag;
+using Content.Shared.StatusIcon.Components;
+
+namespace Content.Client.Revolutionary;
+
+/// <summary>
+/// Used for the client to get status icons from other revs.
+/// </summary>
+public sealed class RevolutionarySystem : AntagStatusIconSystem<RevolutionaryComponent>
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<RevolutionaryComponent, GetStatusIconsEvent>(GetRevIcon);
+ SubscribeLocalEvent<HeadRevolutionaryComponent, GetStatusIconsEvent>(GetHeadRevIcon);
+ }
+
+ /// <summary>
+ /// Checks if the person who triggers the GetStatusIcon event is also a Rev or a HeadRev.
+ /// </summary>
+ private void GetRevIcon(EntityUid uid, RevolutionaryComponent comp, ref GetStatusIconsEvent args)
+ {
+ if (!HasComp<HeadRevolutionaryComponent>(uid))
+ {
+ GetStatusIcon(comp.RevStatusIcon, ref args);
+ }
+ }
+
+ private void GetHeadRevIcon(EntityUid uid, HeadRevolutionaryComponent comp, ref GetStatusIconsEvent args)
+ {
+ GetStatusIcon(comp.HeadRevStatusIcon, ref args);
+ }
+}
-using System.Linq;
+using System.Linq;
+using Content.Client.Antag;
using Content.Shared.Humanoid;
-using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
using Content.Shared.Zombies;
using Robust.Client.GameObjects;
-using Robust.Client.Player;
-using Robust.Shared.Prototypes;
namespace Content.Client.Zombies;
-public sealed class ZombieSystem : SharedZombieSystem
+public sealed class ZombieSystem : AntagStatusIconSystem<ZombieComponent>
{
- [Dependency] private readonly IPlayerManager _player = default!;
- [Dependency] private readonly IPrototypeManager _prototype = default!;
public override void Initialize()
{
private void OnGetStatusIcon(EntityUid uid, ZombieComponent component, ref GetStatusIconsEvent args)
{
- if (!HasComp<ZombieComponent>(_player.LocalPlayer?.ControlledEntity))
- return;
-
- args.StatusIcons.Add(_prototype.Index<StatusIconPrototype>(component.ZombieStatusIcon));
+ GetStatusIcon(component.ZombieStatusIcon, ref args);
}
}
+using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules;
using Content.Server.Zombies;
using Content.Shared.Administration;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Robust.Shared.Utility;
+using Content.Server.GameTicking.Rules.Components;
+using System.Linq;
namespace Content.Server.Administration.Systems;
[Dependency] private readonly TraitorRuleSystem _traitorRule = default!;
[Dependency] private readonly NukeopsRuleSystem _nukeopsRule = default!;
[Dependency] private readonly PiratesRuleSystem _piratesRule = default!;
+ [Dependency] private readonly RevolutionaryRuleSystem _revolutionaryRule = default!;
[Dependency] private readonly SharedMindSystem _minds = default!;
+ [Dependency] private readonly GameTicker _gameTicker = default!;
// All antag verbs have names so invokeverb works.
private void AddAntagVerbs(GetVerbsEvent<Verb> args)
Message = Loc.GetString("admin-verb-make-pirate"),
};
args.Verbs.Add(pirate);
+
+ //todo come here at some point dear lort.
+ Verb headRev = new()
+ {
+ Text = Loc.GetString("admin-verb-text-make-head-rev"),
+ Category = VerbCategory.Antag,
+ Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/Misc/job_icons.rsi/HeadRevolutionary.png")),
+ Act = () =>
+ {
+ if (!_minds.TryGetMind(args.Target, out var mindId, out var mind))
+ return;
+ _revolutionaryRule.OnHeadRevAdmin(mindId, mind);
+ },
+ Impact = LogImpact.High,
+ Message = Loc.GetString("admin-verb-make-head-rev"),
+ };
+ args.Verbs.Add(headRev);
}
}
--- /dev/null
+using Content.Server.GameTicking.Rules;
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.Roles.Jobs;
+using Content.Server.Preferences.Managers;
+using Content.Shared.Humanoid;
+using Content.Shared.Preferences;
+using Robust.Server.Player;
+using System.Linq;
+using Content.Server.Mind;
+using Robust.Shared.Random;
+using Robust.Shared.Map;
+using System.Numerics;
+using Content.Shared.Inventory;
+using Content.Server.Storage.EntitySystems;
+using Robust.Shared.Audio;
+using Robust.Server.GameObjects;
+using Content.Server.Chat.Managers;
+using Content.Server.GameTicking;
+using Robust.Shared.Containers;
+using Content.Shared.Mobs.Components;
+using Content.Server.Station.Systems;
+using Content.Server.Shuttles.Systems;
+using Content.Shared.Mobs;
+using Robust.Server.Containers;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Antag;
+
+public sealed class AntagSelectionSystem : GameRuleSystem<GameRuleComponent>
+{
+ [Dependency] private readonly IChatManager _chatManager = default!;
+ [Dependency] private readonly IServerPreferencesManager _prefs = default!;
+ [Dependency] private readonly IPlayerManager _playerSystem = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly AudioSystem _audioSystem = default!;
+ [Dependency] private readonly ContainerSystem _containerSystem = default!;
+ [Dependency] private readonly JobSystem _jobs = default!;
+ [Dependency] private readonly MindSystem _mindSystem = default!;
+ [Dependency] private readonly InventorySystem _inventory = default!;
+ [Dependency] private readonly StorageSystem _storageSystem = default!;
+ [Dependency] private readonly StationSystem _stationSystem = default!;
+ [Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!;
+
+ /// <summary>
+ /// Attempts to start the game rule by checking if there are enough players in lobby and readied.
+ /// </summary>
+ /// <param name="ev">The roundstart attempt event</param>
+ /// <param name="uid">The entity the gamerule you are using is on</param>
+ /// <param name="minPlayers">The minimum amount of players needed for you gamerule to start.</param>
+ /// <param name="gameRule">The gamerule component.</param>
+
+ public void AttemptStartGameRule(RoundStartAttemptEvent ev, EntityUid uid, int minPlayers, GameRuleComponent gameRule)
+ {
+ if (GameTicker.IsGameRuleAdded(uid, gameRule))
+ {
+ if (!ev.Forced && ev.Players.Length < minPlayers)
+ {
+ _chatManager.SendAdminAnnouncement(Loc.GetString("rev-not-enough-ready-players",
+ ("readyPlayersCount", ev.Players.Length),
+ ("minimumPlayers", minPlayers)));
+ ev.Cancel();
+ }
+ else if (ev.Players.Length == 0)
+ {
+ _chatManager.DispatchServerAnnouncement(Loc.GetString("rev-no-one-ready"));
+ ev.Cancel();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Will check which players are eligible to be chosen for antagonist and give them the given antag.
+ /// </summary>
+ /// <param name="antagPrototype">The antag prototype from your rule component.</param>
+ /// <param name="maxAntags">How many antags can be present in any given round.</param>
+ /// <param name="antagsPerPlayer">How many players you need to spawn an additional antag.</param>
+ /// <param name="antagSound">The intro sound that plays when the antag is chosen.</param>
+ /// <param name="antagGreeting">The antag message you want shown when the antag is chosen.</param>
+ /// <param name="greetingColor">The color of the message for the antag greeting in hex.</param>
+ /// <param name="chosen">A list of all the antags chosen in case you need to add stuff after.</param>
+ /// <param name="includeHeads">Whether or not heads can be chosen as antags for this gamemode.</param>
+ public void EligiblePlayers(string antagPrototype,
+ int maxAntags,
+ int antagsPerPlayer,
+ SoundSpecifier? antagSound,
+ string antagGreeting,
+ string greetingColor,
+ out List<EntityUid> chosen,
+ bool includeHeads = false)
+ {
+ var allPlayers = _playerSystem.ServerSessions.ToList();
+ var playerList = new List<IPlayerSession>();
+ var prefList = new List<IPlayerSession>();
+ chosen = new List<EntityUid>();
+ foreach (var player in allPlayers)
+ {
+ if (includeHeads == false)
+ {
+ if (!_jobs.CanBeAntag(player))
+ continue;
+ }
+
+ if (player.AttachedEntity == null || HasComp<HumanoidAppearanceComponent>(player.AttachedEntity))
+ playerList.Add(player);
+ else
+ continue;
+
+ var pref = (HumanoidCharacterProfile) _prefs.GetPreferences(player.UserId).SelectedCharacter;
+ if (pref.AntagPreferences.Contains(antagPrototype))
+ prefList.Add(player);
+ }
+
+ if (playerList.Count == 0)
+ return;
+
+ var antags = Math.Clamp(allPlayers.Count / antagsPerPlayer, 1, maxAntags);
+ for (var antag = 0; antag < antags; antag++)
+ {
+ IPlayerSession chosenPlayer;
+ if (prefList.Count == 0)
+ {
+ if (playerList.Count == 0)
+ {
+ break;
+ }
+ chosenPlayer = _random.PickAndTake(playerList);
+ }
+ else
+ {
+ chosenPlayer = _random.PickAndTake(prefList);
+ playerList.Remove(chosenPlayer);
+ }
+
+ if (!_mindSystem.TryGetMind(chosenPlayer, out _, out var mind) ||
+ mind.OwnedEntity is not { } ownedEntity)
+ {
+ continue;
+ }
+
+ chosen.Add(ownedEntity);
+ _audioSystem.PlayGlobal(antagSound, ownedEntity);
+ if (mind.Session != null)
+ {
+ var message = Loc.GetString(antagGreeting);
+ var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
+ _chatManager.ChatMessageToOne(Shared.Chat.ChatChannel.Server, message, wrappedMessage, default, false, mind.Session.ConnectedClient, Color.FromHex(greetingColor));
+ }
+ }
+ }
+
+ /// <summary>
+ /// Will take a group of entities and check if they are all alive or dead
+ /// </summary>
+ /// <param name="list">The list of the entities</param>
+ /// <param name="checkOffStation">Bool for if you want to check if someone is in space and consider them dead. (Won't check when emergency shuttle arrives just in case)</param>
+ /// <returns></returns>
+ public bool IsGroupDead(List<EntityUid> list, bool checkOffStation)
+ {
+ var dead = 0;
+ foreach (var entity in list)
+ {
+ if (TryComp<MobStateComponent>(entity, out var state))
+ {
+ if (state.CurrentState == MobState.Dead || state.CurrentState == MobState.Invalid)
+ {
+ dead++;
+ }
+ else if (checkOffStation && _stationSystem.GetOwningStation(entity) == null && !_emergencyShuttle.EmergencyShuttleArrived)
+ {
+ dead++;
+ }
+ }
+ //If they don't have the MobStateComponent they might as well be dead.
+ else
+ {
+ dead++;
+ }
+ }
+
+ return dead == list.Count || list.Count == 0;
+ }
+
+ /// <summary>
+ /// Will attempt to spawn an item inside of a persons bag and then pockets.
+ /// </summary>
+ /// <param name="antag">The entity that you want to spawn an item on</param>
+ /// <param name="items">A list of prototype IDs that you want to spawn in the bag.</param>
+ public void GiveAntagBagGear(EntityUid antag, List<EntProtoId> items)
+ {
+ foreach (var item in items)
+ {
+ GiveAntagBagGear(antag, item);
+ }
+ }
+
+ /// <summary>
+ /// Will attempt to spawn an item inside of a persons bag and then pockets.
+ /// </summary>
+ /// <param name="antag">The entity that you want to spawn an item on</param>
+ /// <param name="item">The prototype ID that you want to spawn in the bag.</param>
+ public void GiveAntagBagGear(EntityUid antag, string item)
+ {
+ var itemToSpawn = Spawn(item, new EntityCoordinates(antag, Vector2.Zero));
+ if (!_inventory.TryGetSlotContainer(antag, "back", out var backSlot, out _))
+ return;
+
+ var bag = backSlot.ContainedEntity;
+ if (bag != null && HasComp<ContainerManagerComponent>(bag) && _storageSystem.CanInsert(bag.Value, itemToSpawn, out _))
+ {
+ _storageSystem.Insert(bag.Value, itemToSpawn, out _);
+ }
+ else if (_inventory.TryGetSlotContainer(antag, "jumpsuit", out var jumpsuit, out _) && jumpsuit.ContainedEntity != null)
+ {
+ if (_inventory.TryGetSlotContainer(antag, "pocket1", out var pocket1Slot, out _))
+ {
+ if (pocket1Slot.ContainedEntity == null)
+ {
+ if (_containerSystem.CanInsert(itemToSpawn, pocket1Slot))
+ {
+ pocket1Slot.Insert(itemToSpawn);
+ }
+ }
+ else if (_inventory.TryGetSlotContainer(antag, "pocket2", out var pocket2Slot, out _))
+ {
+ if (pocket2Slot.ContainedEntity == null)
+ {
+ if (_containerSystem.CanInsert(itemToSpawn, pocket2Slot))
+ {
+ pocket2Slot.Insert(itemToSpawn);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
using Content.Server.Stunnable;
using Content.Shared.Charges.Components;
using Content.Shared.Charges.Systems;
-using Content.Shared.Damage;
using Content.Shared.Eye.Blinding.Components;
using Content.Shared.Flash;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory;
using Content.Shared.Physics;
-using Content.Shared.Popups;
using Content.Shared.Tag;
using Content.Shared.Traits.Assorted;
using Content.Shared.Weapons.Melee.Events;
public override void Initialize()
{
base.Initialize();
+
SubscribeLocalEvent<FlashComponent, MeleeHitEvent>(OnFlashMeleeHit);
// ran before toggling light for extra-bright lantern
SubscribeLocalEvent<FlashComponent, UseInHandEvent>(OnFlashUseInHand, before: new []{ typeof(HandheldLightSystem) });
SubscribeLocalEvent<InventoryComponent, FlashAttemptEvent>(OnInventoryFlashAttempt);
-
SubscribeLocalEvent<FlashImmunityComponent, FlashAttemptEvent>(OnFlashImmunityFlashAttempt);
SubscribeLocalEvent<PermanentBlindnessComponent, FlashAttemptEvent>(OnPermanentBlindnessFlashAttempt);
SubscribeLocalEvent<TemporaryBlindnessComponent, FlashAttemptEvent>(OnTemporaryBlindnessFlashAttempt);
args.Handled = true;
foreach (var e in args.HitEntities)
{
- Flash(e, args.User, uid, comp.FlashDuration, comp.SlowTo);
+ Flash(e, args.User, uid, comp.FlashDuration, comp.SlowTo, melee: true);
}
}
return true;
}
- public void Flash(EntityUid target, EntityUid? user, EntityUid? used, float flashDuration, float slowTo, bool displayPopup = true, FlashableComponent? flashable = null)
+ public void Flash(EntityUid target,
+ EntityUid? user,
+ EntityUid? used,
+ float flashDuration,
+ float slowTo,
+ bool displayPopup = true,
+ FlashableComponent? flashable = null,
+ bool melee = false)
{
- if (!Resolve(target, ref flashable, false)) return;
+ if (!Resolve(target, ref flashable, false))
+ return;
var attempt = new FlashAttemptEvent(target, user, used);
RaiseLocalEvent(target, attempt, true);
if (attempt.Cancelled)
return;
+ if (melee)
+ {
+ var ev = new AfterFlashedEvent(target, user, used);
+ if (user != null)
+ RaiseLocalEvent(user.Value, ref ev);
+ if (used != null)
+ RaiseLocalEvent(used.Value, ref ev);
+ }
+
flashable.LastFlash = _timing.CurTime;
flashable.Duration = flashDuration / 1000f; // TODO: Make this sane...
- Dirty(flashable);
+ Dirty(target, flashable);
_stun.TrySlowdown(target, TimeSpan.FromSeconds(flashDuration/1000f), true,
slowTo, slowTo);
- if (displayPopup && user != null && target != user && EntityManager.EntityExists(user.Value))
+ if (displayPopup && user != null && target != user && Exists(user.Value))
{
- user.Value.PopupMessage(target, Loc.GetString("flash-component-user-blinds-you",
- ("user", Identity.Entity(user.Value, EntityManager))));
+ _popup.PopupEntity(Loc.GetString("flash-component-user-blinds-you",
+ ("user", Identity.Entity(user.Value, EntityManager))), target, target);
}
+
}
public void FlashArea(EntityUid source, EntityUid? user, float range, float duration, float slowTo = 0.8f, bool displayPopup = false, SoundSpecifier? sound = null)
Used = used;
}
}
+ /// <summary>
+ /// Called after a flash is used via melee on another person to check for rev conversion.
+ /// Raised on the user of the flash, the target hit by the flash, and the flash used.
+ /// </summary>
+ [ByRefEvent]
+ public readonly struct AfterFlashedEvent
+ {
+ public readonly EntityUid Target;
+ public readonly EntityUid? User;
+ public readonly EntityUid? Used;
+
+ public AfterFlashedEvent(EntityUid target, EntityUid? user, EntityUid? used)
+ {
+ Target = target;
+ User = user;
+ Used = used;
+ }
+ }
+
+
}
--- /dev/null
+using Content.Shared.Roles;
+using Robust.Shared.Audio;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Server.GameTicking.Rules.Components;
+
+/// <summary>
+/// Component for the RevolutionaryRuleSystem that stores info about winning/losing, player counts required for starting, as well as prototypes for Revolutionaries and their gear.
+/// </summary>
+[RegisterComponent, Access(typeof(RevolutionaryRuleSystem))]
+public sealed partial class RevolutionaryRuleComponent : Component
+{
+ /// <summary>
+ /// When the round will if all the command are dead (Incase they are in space)
+ /// </summary>
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ public TimeSpan CommandCheck;
+
+ /// <summary>
+ /// The amount of time between each check for command check.
+ /// </summary>
+ [DataField]
+ public TimeSpan TimerWait = TimeSpan.FromSeconds(20);
+
+ /// <summary>
+ /// Stores players minds
+ /// </summary>
+ [DataField]
+ public Dictionary<string, EntityUid> HeadRevs = new();
+
+ [DataField]
+ public ProtoId<AntagPrototype> RevPrototypeId = "Rev";
+
+ /// <summary>
+ /// Sound that plays when you are chosen as Rev. (Placeholder until I find something cool I guess)
+ /// </summary>
+ [DataField]
+ public SoundSpecifier HeadRevStartSound = new SoundPathSpecifier("/Audio/Ambience/Antag/traitor_start.ogg");
+
+ /// <summary>
+ /// Min players needed for Revolutionary gamemode to start.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public int MinPlayers = 15;
+
+ /// <summary>
+ /// Max Head Revs allowed during selection.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public int MaxHeadRevs = 3;
+
+ /// <summary>
+ /// The amount of Head Revs that will spawn per this amount of players.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public int PlayersPerHeadRev = 15;
+
+ /// <summary>
+ /// The gear head revolutionaries are given on spawn.
+ /// </summary>
+ [DataField]
+ public List<EntProtoId> StartingGear = new()
+ {
+ "Flash",
+ "ClothingEyesGlassesSunglasses"
+ };
+
+ /// <summary>
+ /// The time it takes after the last head is killed for the shuttle to arrive.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan ShuttleCallTime = TimeSpan.FromMinutes(5);
+}
--- /dev/null
+using System.Linq;
+using Content.Server.Chat.Managers;
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.Mind;
+using Content.Server.NPC.Systems;
+using Content.Server.Roles;
+using Content.Shared.Humanoid;
+using Content.Shared.Mobs;
+using Content.Shared.Mobs.Components;
+using Content.Shared.Revolutionary.Components;
+using Content.Shared.Roles;
+using Content.Shared.Stunnable;
+using Robust.Shared.Timing;
+using Content.Server.Popups;
+using Content.Server.Revolutionary.Components;
+using Content.Shared.IdentityManagement;
+using Content.Server.Flash;
+using Content.Shared.Mindshield.Components;
+using Content.Server.Administration.Logs;
+using Content.Shared.Database;
+using Content.Server.Antag;
+using Content.Server.NPC.Components;
+using Content.Server.RoundEnd;
+using Content.Shared.Chat;
+using Content.Shared.Mind;
+using Content.Shared.Mobs.Systems;
+using Content.Shared.Zombies;
+
+namespace Content.Server.GameTicking.Rules;
+
+/// <summary>
+/// Where all the main stuff for Revolutionaries happens (Assigning Head Revs, Command on station, and checking for the game to end.)
+/// </summary>
+public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleComponent>
+{
+ [Dependency] private readonly IAdminLogManager _adminLogManager = default!;
+ [Dependency] private readonly IChatManager _chatManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly AntagSelectionSystem _antagSelection = default!;
+ [Dependency] private readonly MindSystem _mind = default!;
+ [Dependency] private readonly MobStateSystem _mobState = default!;
+ [Dependency] private readonly NpcFactionSystem _npcFaction = default!;
+ [Dependency] private readonly PopupSystem _popup = default!;
+ [Dependency] private readonly RoleSystem _role = default!;
+ [Dependency] private readonly SharedStunSystem _stun = default!;
+ [Dependency] private readonly RoundEndSystem _roundEnd = default!;
+
+ [ValidatePrototypeId<NpcFactionPrototype>]
+ public const string RevolutionaryNpcFaction = "Revolutionary";
+ [ValidatePrototypeId<AntagPrototype>]
+ public const string RevolutionaryAntagRole = "Rev";
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent<RoundStartAttemptEvent>(OnStartAttempt);
+ SubscribeLocalEvent<RulePlayerJobsAssignedEvent>(OnPlayerJobAssigned);
+ SubscribeLocalEvent<CommandStaffComponent, MobStateChangedEvent>(OnCommandMobStateChanged);
+ SubscribeLocalEvent<HeadRevolutionaryComponent, MobStateChangedEvent>(OnHeadRevMobStateChanged);
+ SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndText);
+ SubscribeLocalEvent<HeadRevolutionaryComponent, AfterFlashedEvent>(OnPostFlash);
+ }
+
+ protected override void Started(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
+ {
+ base.Started(uid, component, gameRule, args);
+ component.CommandCheck = _timing.CurTime + component.TimerWait;
+ }
+
+ /// <summary>
+ /// Checks if the round should end and also checks who has a mindshield.
+ /// </summary>
+ protected override void ActiveTick(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, float frameTime)
+ {
+ base.ActiveTick(uid, component, gameRule, frameTime);
+ if (component.CommandCheck <= _timing.CurTime)
+ {
+ component.CommandCheck = _timing.CurTime + component.TimerWait;
+
+ if (CheckCommandLose())
+ {
+ _roundEnd.DoRoundEndBehavior(RoundEndBehavior.ShuttleCall, component.ShuttleCallTime);
+ GameTicker.EndGameRule(uid, gameRule);
+ }
+ }
+ }
+
+ private void OnRoundEndText(RoundEndTextAppendEvent ev)
+ {
+ var revsLost = CheckRevsLose();
+ var commandLost = CheckCommandLose();
+ var query = AllEntityQuery<RevolutionaryRuleComponent>();
+ while (query.MoveNext(out var headrev))
+ {
+ // This is (revsLost, commandsLost) concatted together
+ // (moony wrote this comment idk what it means)
+ var index = (commandLost ? 1 : 0) | (revsLost ? 2 : 0);
+ ev.AddLine(Loc.GetString(Outcomes[index]));
+
+ ev.AddLine(Loc.GetString("head-rev-initial-count", ("initialCount", headrev.HeadRevs.Count)));
+ foreach (var player in headrev.HeadRevs)
+ {
+ _mind.TryGetSession(player.Value, out var session);
+ var username = session?.Name;
+ if (username != null)
+ {
+ ev.AddLine(Loc.GetString("head-rev-initial",
+ ("name", player.Key),
+ ("username", username)));
+ }
+ else
+ {
+ ev.AddLine(Loc.GetString("head-rev-initial",
+ ("name", player.Key)));
+ }
+ }
+ break;
+ }
+ }
+
+ private void OnStartAttempt(RoundStartAttemptEvent ev)
+ {
+ var query = AllEntityQuery<RevolutionaryRuleComponent, GameRuleComponent>();
+ while (query.MoveNext(out var uid, out var comp, out var gameRule))
+ {
+ _antagSelection.AttemptStartGameRule(ev, uid, comp.MinPlayers, gameRule);
+ }
+ }
+
+ private void OnPlayerJobAssigned(RulePlayerJobsAssignedEvent ev)
+ {
+ var query = QueryActiveRules();
+ while (query.MoveNext(out _, out var comp, out _))
+ {
+ _antagSelection.EligiblePlayers(comp.RevPrototypeId, comp.MaxHeadRevs, comp.PlayersPerHeadRev, comp.HeadRevStartSound,
+ "head-rev-role-greeting", "#5e9cff", out var chosen);
+ GiveHeadRev(chosen, comp.RevPrototypeId, comp);
+ }
+ }
+
+ private void GiveHeadRev(List<EntityUid> chosen, string antagProto, RevolutionaryRuleComponent comp)
+ {
+ foreach (var headRev in chosen)
+ {
+ RemComp<CommandStaffComponent>(headRev);
+
+ var inCharacterName = MetaData(headRev).EntityName;
+ if (_mind.TryGetMind(headRev, out var mindId, out var mind))
+ {
+ if (!_role.MindHasRole<RevolutionaryRoleComponent>(mindId))
+ {
+ _role.MindAddRole(mindId, new RevolutionaryRoleComponent { PrototypeId = antagProto });
+ }
+ if (mind.Session != null)
+ {
+ comp.HeadRevs.Add(inCharacterName, mindId);
+ }
+ }
+
+ _antagSelection.GiveAntagBagGear(headRev, comp.StartingGear);
+ EnsureComp<RevolutionaryComponent>(headRev);
+ EnsureComp<HeadRevolutionaryComponent>(headRev);
+ }
+ }
+
+ /// <summary>
+ /// Called when a Head Rev uses a flash in melee to convert somebody else.
+ /// </summary>
+ public void OnPostFlash(EntityUid uid, HeadRevolutionaryComponent comp, ref AfterFlashedEvent ev)
+ {
+ TryComp<AlwaysRevolutionaryConvertibleComponent>(ev.Target, out var alwaysConvertibleComp);
+ var alwaysConvertible = alwaysConvertibleComp != null;
+
+ if (!_mind.TryGetMind(ev.Target, out var mindId, out var mind) && !alwaysConvertible)
+ return;
+
+ if (HasComp<RevolutionaryComponent>(ev.Target) ||
+ HasComp<MindShieldComponent>(ev.Target) ||
+ !HasComp<HumanoidAppearanceComponent>(ev.Target) &&
+ !alwaysConvertible ||
+ !_mobState.IsAlive(ev.Target) ||
+ HasComp<ZombieComponent>(ev.Target))
+ {
+ return;
+ }
+
+ _npcFaction.AddFaction(ev.Target, RevolutionaryNpcFaction);
+ EnsureComp<RevolutionaryComponent>(ev.Target);
+ _stun.TryParalyze(ev.Target, comp.StunTime, true);
+ if (ev.User != null)
+ {
+ _adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(ev.User.Value)} converted {ToPrettyString(ev.Target)} into a Revolutionary");
+ }
+
+ if (mindId == default || !_role.MindHasRole<RevolutionaryRoleComponent>(mindId))
+ {
+ _role.MindAddRole(mindId, new RevolutionaryRoleComponent { PrototypeId = RevolutionaryAntagRole });
+ }
+ if (mind?.Session != null)
+ {
+ var message = Loc.GetString("rev-role-greeting");
+ var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
+ _chatManager.ChatMessageToOne(ChatChannel.Server, message, wrappedMessage, default, false, mind.Session.ConnectedClient, Color.Red);
+ }
+ }
+
+ public void OnHeadRevAdmin(EntityUid mindId, MindComponent? mind = null)
+ {
+ if (!Resolve(mindId, ref mind))
+ return;
+
+ var revRule = EntityQuery<RevolutionaryRuleComponent>().FirstOrDefault();
+ if (revRule == null)
+ {
+ GameTicker.StartGameRule("Revolutionary", out var ruleEnt);
+ revRule = Comp<RevolutionaryRuleComponent>(ruleEnt);
+ }
+
+ if (!HasComp<HeadRevolutionaryComponent>(mind.OwnedEntity))
+ {
+ if (mind.OwnedEntity != null)
+ {
+ var player = new List<EntityUid>
+ {
+ mind.OwnedEntity.Value
+ };
+ GiveHeadRev(player, RevolutionaryAntagRole, revRule);
+ }
+ if (mind.Session != null)
+ {
+ var message = Loc.GetString("head-rev-role-greeting");
+ var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
+ _chatManager.ChatMessageToOne(ChatChannel.Server, message, wrappedMessage, default, false, mind.Session.ConnectedClient, Color.FromHex("#5e9cff"));
+ }
+ }
+ }
+ private void OnCommandMobStateChanged(EntityUid uid, CommandStaffComponent comp, MobStateChangedEvent ev)
+ {
+ if (ev.NewMobState == MobState.Dead || ev.NewMobState == MobState.Invalid)
+ CheckCommandLose();
+ }
+
+ /// <summary>
+ /// Checks if all of command is dead and if so will remove all sec and command jobs if there were any left.
+ /// </summary>
+ private bool CheckCommandLose()
+ {
+ var commandList = new List<EntityUid>();
+
+ var heads = AllEntityQuery<CommandStaffComponent>();
+ while (heads.MoveNext(out var id, out _))
+ {
+ commandList.Add(id);
+ }
+
+ return _antagSelection.IsGroupDead(commandList, true);
+ }
+
+ private void OnHeadRevMobStateChanged(EntityUid uid, HeadRevolutionaryComponent comp, MobStateChangedEvent ev)
+ {
+ if (ev.NewMobState == MobState.Dead || ev.NewMobState == MobState.Invalid)
+ CheckRevsLose();
+ }
+
+ /// <summary>
+ /// Checks if all the Head Revs are dead and if so will deconvert all regular revs.
+ /// </summary>
+ private bool CheckRevsLose()
+ {
+ var stunTime = TimeSpan.FromSeconds(4);
+ var headRevList = new List<EntityUid>();
+
+ var headRevs = AllEntityQuery<HeadRevolutionaryComponent, MobStateComponent>();
+ while (headRevs.MoveNext(out var uid, out _, out _))
+ {
+ headRevList.Add(uid);
+ }
+
+ // If no Head Revs are alive all normal Revs will lose their Rev status and rejoin Nanotrasen
+ if (_antagSelection.IsGroupDead(headRevList, false))
+ {
+ var rev = AllEntityQuery<RevolutionaryComponent>();
+ while (rev.MoveNext(out var uid, out _))
+ {
+ if (!HasComp<HeadRevolutionaryComponent>(uid))
+ {
+ _npcFaction.RemoveFaction(uid, RevolutionaryNpcFaction);
+ _stun.TryParalyze(uid, stunTime, true);
+ RemCompDeferred<RevolutionaryComponent>(uid);
+ _popup.PopupEntity(Loc.GetString("rev-break-control", ("name", Identity.Entity(uid, EntityManager))), uid);
+ _adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(uid)} was deconverted due to all Head Revolutionaries dying.");
+ }
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ private static readonly string[] Outcomes =
+ {
+ // revs survived and heads survived... how
+ "rev-reverse-stalemate",
+ // revs won and heads died
+ "rev-won",
+ // revs lost and heads survived
+ "rev-lost",
+ // revs lost and heads died
+ "rev-stalemate"
+ };
+}
--- /dev/null
+using Content.Shared.Mindshield.Components;
+using Content.Shared.Revolutionary.Components;
+using Content.Server.Popups;
+using Content.Shared.Database;
+using Content.Server.Administration.Logs;
+using Content.Server.Mind;
+using Content.Shared.Implants;
+using Content.Shared.Tag;
+using Content.Server.Roles;
+using Content.Shared.Implants.Components;
+
+namespace Content.Server.Mindshield;
+
+/// <summary>
+/// System used for checking if the implanted is a Rev or Head Rev.
+/// </summary>
+public sealed class MindShieldSystem : EntitySystem
+{
+ [Dependency] private readonly IAdminLogManager _adminLogManager = default!;
+ [Dependency] private readonly RoleSystem _roleSystem = default!;
+ [Dependency] private readonly MindSystem _mindSystem = default!;
+ [Dependency] private readonly TagSystem _tag = default!;
+ [Dependency] private readonly PopupSystem _popupSystem = default!;
+
+ [ValidatePrototypeId<TagPrototype>]
+ public const string MindShieldTag = "MindShield";
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent<SubdermalImplantComponent, ImplantImplantedEvent>(ImplantCheck);
+ }
+
+ /// <summary>
+ /// Checks if the implant was a mindshield or not
+ /// </summary>
+ public void ImplantCheck(EntityUid uid, SubdermalImplantComponent comp, ref ImplantImplantedEvent ev)
+ {
+ if (_tag.HasTag(ev.Implant, MindShieldTag) && ev.Implanted != null)
+ {
+ EnsureComp<MindShieldComponent>(ev.Implanted.Value);
+ MindShieldRemovalCheck(ev.Implanted, ev.Implant);
+ }
+ }
+
+ /// <summary>
+ /// Checks if the implanted person was a Rev or Head Rev and remove role or destroy mindshield respectively.
+ /// </summary>
+ public void MindShieldRemovalCheck(EntityUid? implanted, EntityUid implant)
+ {
+ if (HasComp<RevolutionaryComponent>(implanted) && !HasComp<HeadRevolutionaryComponent>(implanted))
+ {
+ _mindSystem.TryGetMind(implanted.Value, out var mindId, out _);
+ _adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(implanted.Value)} was deconverted due to being implanted with a Mindshield.");
+ _roleSystem.MindTryRemoveRole<RevolutionaryRoleComponent>(mindId);
+ }
+ else if (HasComp<RevolutionaryComponent>(implanted))
+ {
+ _popupSystem.PopupEntity(Loc.GetString("head-rev-break-mindshield"), implanted.Value);
+ QueueDel(implant);
+ }
+ }
+}
--- /dev/null
+using Content.Server.GameTicking.Rules;
+
+namespace Content.Server.Revolutionary.Components;
+
+/// <summary>
+/// Given to heads at round start for Revs. Used for tracking if heads died or not.
+/// </summary>
+[RegisterComponent, Access(typeof(RevolutionaryRuleSystem))]
+public sealed partial class CommandStaffComponent : Component
+{
+
+}
--- /dev/null
+using Content.Shared.Roles;
+
+namespace Content.Server.Roles;
+
+/// <summary>
+/// Added to mind entities to tag that they are a Revolutionary.
+/// </summary>
+[RegisterComponent]
+public sealed partial class RevolutionaryRoleComponent : AntagonistRoleComponent
+{
+
+}
Timer.Spawn(countdownTime.Value, AfterEndRoundRestart, _countdownTokenSource.Token);
}
- public void DoRoundEndBehavior(RoundEndBehavior behavior, TimeSpan time, string sender, string textCall, string textAnnounce)
+ /// <summary>
+ /// Starts a behavior to end the round
+ /// </summary>
+ /// <param name="behavior">The way in which the round will end</param>
+ /// <param name="time"></param>
+ /// <param name="sender"></param>
+ /// <param name="textCall"></param>
+ /// <param name="textAnnounce"></param>
+ public void DoRoundEndBehavior(RoundEndBehavior behavior,
+ TimeSpan time,
+ string sender = "comms-console-announcement-title-centcom",
+ string textCall = "round-end-system-shuttle-called-announcement",
+ string textAnnounce = "round-end-system-shuttle-already-called-announcement")
{
switch (behavior)
{
-using System.Linq;
+using System.Linq;
using Content.Shared.Actions;
using Content.Shared.Implants.Components;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Mobs;
using Content.Shared.Tag;
+using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.Network;
}
}
}
+
+ var ev = new ImplantImplantedEvent(uid, component.ImplantedEntity.Value);
+ RaiseLocalEvent(uid, ref ev);
}
private void OnRemoveAttempt(EntityUid uid, SubdermalImplantComponent component, ContainerGettingRemovedAttemptEvent args)
/// </summary>
/// <param name="target">the implanted entity</param>
/// <param name="implant">the implant</param>
- /// <param name="component">the implant component</param>
+ [PublicAPI]
public void ForceRemove(EntityUid target, EntityUid implant)
{
if (!TryComp<ImplantedComponent>(target, out var implanted))
/// Removes and deletes implants by force
/// </summary>
/// <param name="target">The entity to have implants removed</param>
+ [PublicAPI]
public void WipeImplants(EntityUid target)
{
if (!TryComp<ImplantedComponent>(target, out var implanted))
Event = ev;
}
}
+
+/// <summary>
+/// Event that is raised whenever someone is implanted with any given implant.
+/// Raised on the the implant entity.
+/// </summary>
+/// <remarks>
+/// implant implant implant implant
+/// </remarks>
+[ByRefEvent]
+public readonly struct ImplantImplantedEvent
+{
+ public readonly EntityUid Implant;
+ public readonly EntityUid? Implanted;
+
+ public ImplantImplantedEvent(EntityUid implant, EntityUid? implanted)
+ {
+ Implant = implant;
+ Implanted = implanted;
+ }
+}
--- /dev/null
+using Content.Shared.Revolutionary;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Mindshield.Components;
+
+/// <summary>
+/// If a player has a Mindshield they will get this component to prevent conversion.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedRevolutionarySystem))]
+public sealed partial class MindShieldComponent : Component
+{
+}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Revolutionary.Components;
+
+/// <summary>
+/// Component used for allowing non-humans to be converted. (Mainly monkeys)
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedRevolutionarySystem))]
+public sealed partial class AlwaysRevolutionaryConvertibleComponent : Component
+{
+
+}
--- /dev/null
+using Robust.Shared.GameStates;
+using Content.Shared.StatusIcon;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Revolutionary.Components;
+
+/// <summary>
+/// Component used for marking a Head Rev for conversion and winning/losing.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedRevolutionarySystem))]
+public sealed partial class HeadRevolutionaryComponent : Component
+{
+ /// <summary>
+ /// The status icon corresponding to the head revolutionary.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId<StatusIconPrototype> HeadRevStatusIcon = "HeadRevolutionaryFaction";
+
+ /// <summary>
+ /// How long the stun will last after the user is converted.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan StunTime = TimeSpan.FromSeconds(3);
+}
--- /dev/null
+using Robust.Shared.GameStates;
+using Content.Shared.StatusIcon;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Revolutionary.Components;
+
+/// <summary>
+/// Used for marking regular revs as well as storing icon prototypes so you can see fellow revs.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedRevolutionarySystem))]
+public sealed partial class RevolutionaryComponent : Component
+{
+ /// <summary>
+ /// The status icon prototype displayed for revolutionaries
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId<StatusIconPrototype> RevStatusIcon = "RevolutionaryFaction";
+}
--- /dev/null
+using Content.Shared.Revolutionary.Components;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Mindshield.Components;
+using Content.Shared.Popups;
+using Content.Shared.Stunnable;
+
+namespace Content.Shared.Revolutionary;
+
+public sealed class SharedRevolutionarySystem : EntitySystem
+{
+ [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+ [Dependency] private readonly SharedStunSystem _sharedStun = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent<MindShieldComponent, ComponentInit>(MindShieldImplanted);
+ }
+
+ /// <summary>
+ /// When the mindshield is implanted in the rev it will popup saying they were deconverted. In Head Revs it will remove the mindshield component.
+ /// </summary>
+ private void MindShieldImplanted(EntityUid uid, MindShieldComponent comp, ComponentInit init)
+ {
+ if (HasComp<RevolutionaryComponent>(uid) && !HasComp<HeadRevolutionaryComponent>(uid))
+ {
+ var stunTime = TimeSpan.FromSeconds(4);
+ var name = Identity.Entity(uid, EntityManager);
+ RemComp<RevolutionaryComponent>(uid);
+ _sharedStun.TryParalyze(uid, stunTime, true);
+ _popupSystem.PopupEntity(Loc.GetString("rev-break-control", ("name", name)), uid);
+ }
+ else if (HasComp<HeadRevolutionaryComponent>(uid))
+ {
+ RemCompDeferred<MindShieldComponent>(uid);
+ }
+ }
+}
admin-verb-make-traitor = Make the target into a traitor.
admin-verb-make-zombie = Zombifies the target immediately.
admin-verb-make-nuclear-operative = Make target a into lone Nuclear Operative.
-admin-verb-make-pirate = Make the target into a pirate. Note that this doesn't configure the game rule.
+admin-verb-make-pirate = Make the target into a pirate. Note this doesn't configure the game rule.
+admin-verb-make-head-rev = Make the target into a Head Revolutionary.
admin-verb-text-make-traitor = Make Traitor
admin-verb-text-make-zombie = Make Zombie
admin-verb-text-make-nuclear-operative = Make Nuclear Operative
admin-verb-text-make-pirate = Make Pirate
+admin-verb-text-make-head-rev = Make Head Rev
--- /dev/null
+## Rev Head
+
+roles-antag-rev-head-name = Head Revolutionary
+roles-antag-rev-head-objective = Your objective is to take over the station by converting people to your cause and kill all Command staff on station.
+
+head-rev-role-greeting =
+ You are a Head Revolutionary.
+ You are tasked with taking over the station by any means necessary.
+ The Syndicate has sponsored you with a flash that converts the crew to your side.
+ Beware, this won't work on Security, Command, or those wearing sunglasses.
+ Viva la revolución!
+
+head-rev-initial = [color=#5e9cff]{$name}[/color] ([color=gray]{$username}[/color]) was one of the Head Revolutionaries.
+
+head-rev-initial-count = {$initialCount ->
+ [one] There was one Head Revolutionary:
+ *[other] There were {$initialCount} Head Revolutionaries:
+}
+
+head-rev-break-mindshield = The Mindshield was destroyed!
+
+## Rev
+
+roles-antag-rev-name = Revolutionary
+roles-antag-rev-objective = Your objective is to ensure the safety and follow the orders of the Head Revolutionaries as well as killing all Command staff on station.
+
+rev-break-control = {$name} has remembered their true allegiance!
+
+rev-role-greeting =
+ You are a Revolutionary.
+ You are tasked with taking over the station and protecting the Head Revolutionaries.
+ Eliminate all of the command staff.
+ Viva la revolución!
+
+## General
+
+rev-title = Revolutionaries
+rev-description = Revolutionaries are among us.
+
+rev-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 a Revolution.
+rev-no-one-ready = No players readied up! Can't start a Revolution.
+
+rev-all-heads-dead = All the heads are dead, now finish up the rest of the crew!
+
+rev-won = The Head Revs survived and killed all of command.
+
+rev-lost = Command survived and killed all of the Head Revs.
+
+rev-stalemate = All of the Head Revs died and so did all of command. We'll call it a draw.
+
+rev-reverse-stalemate = I think the Head Revs and command forgot to fight because they are both still alive.
+
+
ent-MedicalChemistrySupplies = { ent-CrateChemistrySupplies }
.desc = { ent-CrateChemistrySupplies.desc }
+ent-MedicalMindShieldImplants = { ent-MedicalMindShieldImplants }
+ .desc = { ent-MedicalMindShieldImplants.desc }
+
ent-EmergencyBurnKit = { ent-CrateEmergencyBurnKit }
.desc = { ent-CrateEmergencyBurnKit.desc }
.desc = { ent-CrateChemistryS.desc }
ent-ChemistryD = { ent-CrateChemistryD }
- .desc = { ent-CrateChemistryD.desc }
\ No newline at end of file
+ .desc = { ent-CrateChemistryD.desc }
ent-CrateChemistrySupplies = Chemistry supplies crate
.desc = Basic chemistry supplies.
+ent-CrateMindShieldImplants = MindShield implant crate
+ .desc = Crate filled with 3 MindShield implants.
+
ent-CrateMedicalSurgery = Surgical supplies crate
.desc = Surgical instruments.
## RoundEndSystem
round-end-system-shuttle-called-announcement = An emergency shuttle has been sent. ETA: {$time} {$units}.
+round-end-system-shuttle-already-called-announcement = An emergency shuttle has already been sent.
round-end-system-shuttle-auto-called-announcement = An automatic crew shift change shuttle has been sent. ETA: {$time} {$units}. Recall the shuttle to extend the shift.
round-end-system-shuttle-recalled-announcement = The emergency shuttle has been recalled.
round-end-system-round-restart-eta-announcement = Restarting the round in {$time} {$units}...
category: Medical
group: market
+- type: cargoProduct
+ id: MedicalMindShieldImplants
+ icon:
+ sprite: Objects/Specific/Chemistry/syringe.rsi
+ state: syringe_base0
+ product: CrateMindShieldImplants
+ cost: 5000
+ category: Medical
+ group: market
+
- type: cargoProduct
id: ChemistryP
icon:
- id: BoxBottle
amount: 2
+- type: entity
+ id: CrateMindShieldImplants
+ parent: CrateMedical
+ components:
+ - type: StorageFill
+ contents:
+ - id: MindShieldImplanter
+ amount: 3
+
- type: entity
id: CrateMedicalSurgery
parent: CrateSurgery
clumsySound:
path: /Audio/Animals/monkey_scream.ogg
- type: IdExaminable
+ - type: AlwaysRevolutionaryConvertible
- type: entity
name: guidebook monkey
components:
- type: Implanter
implant: DeathRattleImplant
+
+# Security and Command implanters
+
+- type: entity
+ id: MindShieldImplanter
+ name: mind-shield implanter
+ parent: BaseImplantOnlyImplanter
+ components:
+ - type: Implanter
+ implant: MindShieldImplant
- Dead
- type: Rattle
+# Sec and Command implants
+
+- type: entity
+ parent: BaseSubdermalImplant
+ id: MindShieldImplant
+ name: mind-shield implant
+ description: This implant will ensure loyalty to Nanotrasen and prevent mind control devices.
+ noSpawn: true
+ components:
+ - type: SubdermalImplant
+ permanent: true
+ - type: Tag
+ tags:
+ - MindShield
components:
- type: TraitorRule
+- type: entity
+ id: Revolutionary
+ parent: BaseGameRule
+ noSpawn: true
+ components:
+ - type: RevolutionaryRule
+
- type: entity
id: Sandbox
parent: BaseGameRule
--- /dev/null
+- type: antag
+ id: HeadRev
+ name: roles-antag-rev-head-name
+ antagonist: true
+ setPreference: true
+ objective: roles-antag-rev-head-objective
+
+- type: antag
+ id: Rev
+ name: roles-antag-rev-name
+ antagonist: true
+ setPreference: false
+ objective: roles-antag-rev-objective
department: Cargo
time: 36000 #10 hours
- !type:OverallPlaytimeRequirement
- time: 144000 #40 hrs
+ time: 144000 #40 hrs
weight: 10
startingGear: QuartermasterGear
icon: "JobIconQuarterMaster"
- Maintenance
- External
- Command
+ special:
+ - !type:AddImplantSpecial
+ implants: [ MindShieldImplant ]
+ - !type:AddComponentSpecial
+ components:
+ - type: CommandStaff
- type: startingGear
id: QuartermasterGear
canBeAntag: false
accessGroups:
- AllAccess
+ special:
+ - !type:AddImplantSpecial
+ implants: [ MindShieldImplant ]
+ - !type:AddComponentSpecial
+ components:
+ - type: CommandStaff
- type: startingGear
id: CaptainGear
- Cargo
- Atmospherics
- Medical
+ special:
+ - !type:AddImplantSpecial
+ implants: [ MindShieldImplant ]
+ - !type:AddComponentSpecial
+ components:
+ - type: CommandStaff
- type: startingGear
id: HoPGear
department: Engineering
time: 36000 #10 hrs
- !type:OverallPlaytimeRequirement
- time: 144000 #40 hrs
+ time: 144000 #40 hrs
weight: 10
startingGear: ChiefEngineerGear
icon: "JobIconChiefEngineer"
- External
- ChiefEngineer
- Atmospherics
+ special:
+ - !type:AddImplantSpecial
+ implants: [ MindShieldImplant ]
+ - !type:AddComponentSpecial
+ components:
+ - type: CommandStaff
- type: startingGear
id: ChiefEngineerGear
satchel: ClothingBackpackSatchelBrigmedicFilled
duffelbag: ClothingBackpackDuffelBrigmedicFilled
- #Gladiator with spear
+#Head Rev Gear
+- type: startingGear
+ id: HeadRevGear
+ equipment:
+ pocket2: Flash
+
+#Gladiator with spear
- type: startingGear
id: GladiatorGear
equipment:
head: ClothingHeadHatGladiator
shoes: ClothingShoesCult
- #Ash Walker
+#Ash Walker
- type: startingGear
id: AshWalker
equipment:
department: Medical
time: 36000 #10 hrs
- !type:OverallPlaytimeRequirement
- time: 144000 #40 hrs
+ time: 144000 #40 hrs
weight: 10
startingGear: CMOGear
icon: "JobIconChiefMedicalOfficer"
- Maintenance
- Chemistry
- ChiefMedicalOfficer
+ special:
+ - !type:AddImplantSpecial
+ implants: [ MindShieldImplant ]
+ - !type:AddComponentSpecial
+ components:
+ - type: CommandStaff
- type: startingGear
id: CMOGear
department: Science
time: 36000 #10 hrs
- !type:OverallPlaytimeRequirement
- time: 144000 #40 hrs
+ time: 144000 #40 hrs
weight: 10
startingGear: ResearchDirectorGear
icon: "JobIconResearchDirector"
- Command
- Maintenance
- ResearchDirector
+ special:
+ - !type:AddImplantSpecial
+ implants: [ MindShieldImplant ]
+ - !type:AddComponentSpecial
+ components:
+ - type: CommandStaff
- type: startingGear
id: ResearchDirectorGear
- Maintenance\r
- Service\r
- Detective\r
+ special:\r
+ - !type:AddImplantSpecial\r
+ implants: [ MindShieldImplant ]\r
\r
- type: startingGear\r
id: DetectiveGear\r
department: Security
time: 108000 # 30 hrs
- !type:OverallPlaytimeRequirement
- time: 144000 #40 hrs
+ time: 144000 #40 hrs
weight: 10
startingGear: HoSGear
icon: "JobIconHeadOfSecurity"
- Service
- External
- Detective
+ special:
+ - !type:AddImplantSpecial
+ implants: [ MindShieldImplant ]
+ - !type:AddComponentSpecial
+ components:
+ - type: CommandStaff
- type: startingGear
id: HoSGear
-- type: job
+- type: job
id: SecurityCadet
name: job-name-cadet
description: job-description-cadet
- Security
- Brig
- Maintenance
+ special:
+ - !type:AddImplantSpecial
+ implants: [ MindShieldImplant ]
- type: startingGear
id: SecurityCadetGear
- Maintenance
- Service
- External
+ special:
+ - !type:AddImplantSpecial
+ implants: [ MindShieldImplant ]
- type: startingGear
id: SecurityOfficerGear
- Maintenance
- Service
- External
+ special:
+ - !type:AddImplantSpecial
+ implants: [ MindShieldImplant ]
- type: startingGear
id: SeniorOfficerGear
- Brig
- External
- Detective
+ special:
+ - !type:AddImplantSpecial
+ implants: [ MindShieldImplant ]
- type: startingGear
id: WardenGear
-- type: statusIcon
+- type: statusIcon
id: ZombieFaction
priority: 11
icon:
sprite: Interface/Misc/job_icons.rsi
state: Zombie
+
+- type: statusIcon
+ id: RevolutionaryFaction
+ priority: 11
+ icon:
+ sprite: Interface/Misc/job_icons.rsi
+ state: Revolutionary
+
+- type: statusIcon
+ id: HeadRevolutionaryFaction
+ priority: 11
+ icon:
+ sprite: Interface/Misc/job_icons.rsi
+ state: HeadRevolutionary
- Xeno
- PetsNT
- Zombie
+ - Revolutionary
- type: npcFaction
id: NanoTrasen
- Syndicate
- Xeno
- Zombie
+ - Revolutionary
- type: npcFaction
id: Mouse
- Passive
- PetsNT
- Zombie
+ - Revolutionary
- type: npcFaction
id: SimpleNeutral
- Passive
- PetsNT
- Zombie
+ - Revolutionary
- type: npcFaction
id: Zombie
- Syndicate
- Passive
- PetsNT
+ - Revolutionary
+
+- type: npcFaction
+ id: Revolutionary
+ hostile:
+ - NanoTrasen
+ - Zombie
+ - SimpleHostile
+ - Dragon
- Nukeops
- BasicStationEventScheduler
+- type: gamePreset
+ id: Revolutionary
+ alias:
+ - rev
+ - revs
+ - revolutionaries
+ name: rev-title
+ description: rev-description
+ showInVote: false
+ rules:
+ - Revolutionary
+ - BasicStationEventScheduler
+
- type: gamePreset
id: Zombie
alias:
- type: weightedRandom
id: Secret
weights:
- Nukeops: 0.25
- Traitor: 0.65
+ Nukeops: 0.15
+ Traitor: 0.60
Zombie: 0.10
+ Revolutionary: 0.15
+
\ No newline at end of file
- type: Tag
id: ModularReceiver
+- type: Tag
+ id: MindShield
{
"version": 1,
"license": "CC-BY-SA-3.0",
- "copyright": "Taken from https://github.com/vgstation-coders/vgstation13/blob/e71d6c4fba5a51f99b81c295dcaec4fc2f58fb19/icons/mob/screen1.dmi | Brigmedic icon made by PuroSlavKing (Github) | Zombie icon made by RamZ | Zookeper by netwy (discort)",
+ "copyright": "Taken from https://github.com/vgstation-coders/vgstation13/blob/e71d6c4fba5a51f99b81c295dcaec4fc2f58fb19/icons/mob/screen1.dmi | Brigmedic icon made by PuroSlavKing (Github) | Zombie icon made by RamZ | Zookeper by netwy (discort) | Rev and Head Rev icon taken from https://tgstation13.org/wiki/HUD and edited by coolmankid12345 (Discord)",
+
"size": {
"x": 8,
"y": 8
},
{
"name": "SeniorOfficer"
+ },
+ {
+ "name": "Revolutionary"
+ },
+ {
+ "name": "HeadRevolutionary"
}
]
}