Refactor antag rule code (#23445)

* Initial Pass, Rev, Thief

* Zombie initial pass

* Rebase, Traitor

* Nukeops, More overloads

* Revert RevolutionaryRuleComponent

* Use TryRoundStartAttempt, Rewrite nukie spawning

* Comments, Add task scheduler to GameRuleSystem

* Zombie initial testing done

* Sort methods, rework GameRuleTask

* Add CCVar, Initial testing continues

* Might as well get rid of the obsolete logging

* Oops, i dont know how to log apparently

* Suggested formatting fixes

* Suggested changes

* Fix merge issues

* Minor optimisation

* Allowed thief to choose other antags

* Review changes

* Spawn items on floor first, then inserting

* minor tweaks

* Shift as much as possible to ProtoId<>

* Remove unneeded

* Add exclusive antag attribute

* Fix merge issues

* Minor formatting fix

* Convert to struct

* Cleanup

* Review cleanup (need to test a lot)

* Some fixes, (mostly) tested

* oop

* Pass tests (for real)

---------

Co-authored-by: Rainfall <rainfey0+git@gmail.com>
Co-authored-by: AJCM <AJCM@tutanota.com>
This commit is contained in:
Rainfey
2024-02-29 06:25:10 +00:00
committed by GitHub
parent 3966a65c65
commit 4e6c59cfe5
53 changed files with 22454 additions and 22396 deletions

View File

@@ -0,0 +1,22 @@
namespace Content.Shared.Antag;
/// <summary>
/// Used by AntagSelectionSystem to indicate which types of antag roles are allowed to choose the same entity
/// For example, Thief HeadRev
/// </summary>
public enum AntagAcceptability
{
/// <summary>
/// Dont choose anyone who already has an antag role
/// </summary>
None,
/// <summary>
/// Dont choose anyone who has an exclusive antag role
/// </summary>
NotExclusive,
/// <summary>
/// Choose anyone
/// </summary>
All
}

View File

@@ -487,6 +487,13 @@ namespace Content.Shared.CCVar
public static readonly CVarDef<int> PiratesPlayersPerOp =
CVarDef.Create("pirates.players_per_pirate", 5);
/*
* Nukeops
*/
public static readonly CVarDef<bool> NukeopsSpawnGhostRoles =
CVarDef.Create("nukeops.spawn_ghost_roles", false);
/*
* Tips
*/

View File

@@ -1,12 +1,16 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.Hands.Components;
using Content.Shared.Storage.EntitySystems;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
namespace Content.Shared.Inventory;
public partial class InventorySystem
{
[Dependency] private readonly SharedStorageSystem _storageSystem = default!;
/// <summary>
/// Yields all entities in hands or inventory slots with the specific flags.
/// </summary>
@@ -86,4 +90,55 @@ public partial class InventorySystem
// We finally try to equip the item, otherwise we delete it.
return TryEquip(uid, item, slot, silent, force) || DeleteItem();
}
/// <summary>
/// Will attempt to spawn a list of items inside of an entities bag, pockets, hands or nearby
/// </summary>
/// <param name="entity">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 SpawnItemsOnEntity(EntityUid entity, List<EntProtoId> items)
{
foreach (var item in items)
{
SpawnItemOnEntity(entity, item);
}
}
/// <summary>
/// Will attempt to spawn an item inside of an entities bag, pockets, hands or nearby
/// </summary>
/// <param name="entity">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 SpawnItemOnEntity(EntityUid entity, EntProtoId item)
{
//Transform() throws error if TransformComponent doesnt exist
if (!HasComp<TransformComponent>(entity))
return;
var xform = Transform(entity);
var mapCoords = _transform.GetMapCoordinates(xform);
var itemToSpawn = Spawn(item, mapCoords);
//Try insert into the backpack
if (TryGetSlotContainer(entity, "back", out var backSlot, out _)
&& backSlot.ContainedEntity.HasValue
&& _storageSystem.Insert(backSlot.ContainedEntity.Value, itemToSpawn, out _)
)
return;
//Try insert into pockets
if (TryGetSlotContainer(entity, "pocket1", out var pocket1, out _)
&& _containerSystem.Insert(itemToSpawn, pocket1)
)
return;
if (TryGetSlotContainer(entity, "pocket2", out var pocket2, out _)
&& _containerSystem.Insert(itemToSpawn, pocket2)
)
return;
//Try insert into hands, or drop on the floor
_handsSystem.PickupOrDrop(entity, itemToSpawn, false);
}
}

View File

@@ -10,30 +10,28 @@ public enum WarDeclaratorUiKey
public enum WarConditionStatus : byte
{
WAR_READY,
WAR_DELAY,
YES_WAR,
NO_WAR_UNKNOWN,
NO_WAR_TIMEOUT,
NO_WAR_SMALL_CREW,
NO_WAR_SHUTTLE_DEPARTED
WarReady,
YesWar,
NoWarUnknown,
NoWarTimeout,
NoWarSmallCrew,
NoWarShuttleDeparted
}
[Serializable, NetSerializable]
public sealed class WarDeclaratorBoundUserInterfaceState : BoundUserInterfaceState
{
public WarConditionStatus Status;
public int MinCrew;
public TimeSpan Delay;
public WarConditionStatus? Status;
public TimeSpan ShuttleDisabledTime;
public TimeSpan EndTime;
public WarDeclaratorBoundUserInterfaceState(WarConditionStatus status, int minCrew, TimeSpan delay, TimeSpan endTime)
public WarDeclaratorBoundUserInterfaceState(WarConditionStatus? status, TimeSpan endTime, TimeSpan shuttleDisabledTime)
{
Status = status;
MinCrew = minCrew;
Delay = delay;
EndTime = endTime;
ShuttleDisabledTime = shuttleDisabledTime;
}
}
[Serializable, NetSerializable]

View File

@@ -2,6 +2,7 @@ using Content.Shared.Antag;
using Robust.Shared.GameStates;
using Content.Shared.StatusIcon;
using Robust.Shared.Prototypes;
using Robust.Shared.Audio;
namespace Content.Shared.Revolutionary.Components;
@@ -17,8 +18,14 @@ public sealed partial class RevolutionaryComponent : Component, IAntagStatusIcon
[DataField, ViewVariables(VVAccess.ReadWrite)]
public ProtoId<StatusIconPrototype> StatusIcon { get; set; } = "RevolutionaryFaction";
/// <summary>
/// Sound that plays when you are chosen as Rev. (Placeholder until I find something cool I guess)
/// </summary>
[DataField]
public SoundSpecifier RevStartSound = new SoundPathSpecifier("/Audio/Ambience/Antag/headrev_start.ogg");
public override bool SessionSpecific => true;
[DataField]
public bool IconVisibleToGhost { get; set; } = true;
public bool IconVisibleToGhost { get; set; } = true;
}

View File

@@ -1,4 +1,5 @@
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using JetBrains.Annotations;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Roles;
@@ -7,3 +8,13 @@ public abstract partial class AntagonistRoleComponent : Component
[DataField("prototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
public string? PrototypeId;
}
/// <summary>
/// Mark the antagonist role component as being exclusive
/// IE by default other antagonists should refuse to select the same entity for a different antag role
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
[BaseTypeRequired(typeof(AntagonistRoleComponent))]
public sealed partial class ExclusiveAntagonistAttribute : Attribute
{
}

View File

@@ -1,9 +1,10 @@
namespace Content.Shared.Roles;
namespace Content.Shared.Roles;
/// <summary>
/// Event raised on a mind entity id to get whether or not the player is considered an antagonist,
/// depending on their roles.
/// </summary>
/// <param name="IsAntagonist">Whether or not the player is an antagonist.</param>
/// <param name="IsExclusiveAntagonist">Whether or not AntagSelectionSystem should exclude this player from other antag roles</param
[ByRefEvent]
public record struct MindIsAntagonistEvent(bool IsAntagonist);
public record struct MindIsAntagonistEvent(bool IsAntagonist, bool IsExclusiveAntagonist);

View File

@@ -1,10 +1,11 @@
using Content.Shared.Administration.Logs;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
using Content.Shared.Mind;
using Content.Shared.Roles.Jobs;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared.Roles;
@@ -57,7 +58,7 @@ public abstract class SharedRoleSystem : EntitySystem
args.Roles.Add(new RoleInfo(component, name, true, null, prototype));
});
SubscribeLocalEvent((EntityUid _, T _, ref MindIsAntagonistEvent args) => args.IsAntagonist = true);
SubscribeLocalEvent((EntityUid _, T _, ref MindIsAntagonistEvent args) => { args.IsAntagonist = true; args.IsExclusiveAntagonist |= typeof(T).TryGetCustomAttribute<ExclusiveAntagonistAttribute>(out _); });
_antagTypes.Add(typeof(T));
}
@@ -85,7 +86,7 @@ public abstract class SharedRoleSystem : EntitySystem
AddComp(mindId, component);
var antagonist = IsAntagonistRole<T>();
var mindEv = new MindRoleAddedEvent();
var mindEv = new MindRoleAddedEvent(silent);
RaiseLocalEvent(mindId, ref mindEv);
var message = new RoleAddedEvent(mindId, mind, antagonist, silent);
@@ -156,6 +157,21 @@ public abstract class SharedRoleSystem : EntitySystem
return ev.IsAntagonist;
}
/// <summary>
/// Does this mind possess an exclusive antagonist role
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <returns>True if the mind possesses an exclusive antag role</returns>
public bool MindIsExclusiveAntagonist(EntityUid? mindId)
{
if (mindId == null)
return false;
var ev = new MindIsAntagonistEvent();
RaiseLocalEvent(mindId.Value, ref ev);
return ev.IsExclusiveAntagonist;
}
public bool IsAntagonistRole<T>()
{
return _antagTypes.Contains(typeof(T));