Merge pull request #490 from crystallpunk-14/ed-15-10-2024-upstream

Ed 15 10 2024 upstream
This commit is contained in:
Ed
2024-10-15 15:41:02 +03:00
committed by GitHub
147 changed files with 11991 additions and 20003 deletions

View File

@@ -5,8 +5,8 @@ concurrency:
on:
workflow_dispatch:
schedule:
- cron: '0 10 * * *'
# schedule:
# - cron: '0 10 * * *'
jobs:
build:

View File

@@ -1,149 +0,0 @@
using System.Numerics;
using Content.Client.Buckle;
using Content.Client.Gravity;
using Content.Shared.ActionBlocker;
using Content.Shared.Mobs.Systems;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Shared.Animations;
namespace Content.Client.Movement.Systems;
public sealed class WaddleAnimationSystem : SharedWaddleAnimationSystem
{
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
[Dependency] private readonly GravitySystem _gravity = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
[Dependency] private readonly BuckleSystem _buckle = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
public override void Initialize()
{
base.Initialize();
SubscribeAllEvent<StartedWaddlingEvent>(OnStartWaddling);
SubscribeLocalEvent<WaddleAnimationComponent, AnimationCompletedEvent>(OnAnimationCompleted);
SubscribeAllEvent<StoppedWaddlingEvent>(OnStopWaddling);
}
private void OnStartWaddling(StartedWaddlingEvent msg, EntitySessionEventArgs args)
{
if (TryComp<WaddleAnimationComponent>(GetEntity(msg.Entity), out var comp))
StartWaddling((GetEntity(msg.Entity), comp));
}
private void OnStopWaddling(StoppedWaddlingEvent msg, EntitySessionEventArgs args)
{
if (TryComp<WaddleAnimationComponent>(GetEntity(msg.Entity), out var comp))
StopWaddling((GetEntity(msg.Entity), comp));
}
private void StartWaddling(Entity<WaddleAnimationComponent> entity)
{
if (_animation.HasRunningAnimation(entity.Owner, entity.Comp.KeyName))
return;
if (!TryComp<InputMoverComponent>(entity.Owner, out var mover))
return;
if (_gravity.IsWeightless(entity.Owner))
return;
if (!_actionBlocker.CanMove(entity.Owner, mover))
return;
// Do nothing if buckled in
if (_buckle.IsBuckled(entity.Owner))
return;
// Do nothing if crit or dead (for obvious reasons)
if (_mobState.IsIncapacitated(entity.Owner))
return;
PlayWaddleAnimationUsing(
(entity.Owner, entity.Comp),
CalculateAnimationLength(entity.Comp, mover),
CalculateTumbleIntensity(entity.Comp)
);
}
private static float CalculateTumbleIntensity(WaddleAnimationComponent component)
{
return component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity;
}
private static float CalculateAnimationLength(WaddleAnimationComponent component, InputMoverComponent mover)
{
return mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength;
}
private void OnAnimationCompleted(Entity<WaddleAnimationComponent> entity, ref AnimationCompletedEvent args)
{
if (args.Key != entity.Comp.KeyName)
return;
if (!TryComp<InputMoverComponent>(entity.Owner, out var mover))
return;
PlayWaddleAnimationUsing(
(entity.Owner, entity.Comp),
CalculateAnimationLength(entity.Comp, mover),
CalculateTumbleIntensity(entity.Comp)
);
}
private void StopWaddling(Entity<WaddleAnimationComponent> entity)
{
if (!_animation.HasRunningAnimation(entity.Owner, entity.Comp.KeyName))
return;
_animation.Stop(entity.Owner, entity.Comp.KeyName);
if (!TryComp<SpriteComponent>(entity.Owner, out var sprite))
return;
sprite.Offset = new Vector2();
sprite.Rotation = Angle.FromDegrees(0);
}
private void PlayWaddleAnimationUsing(Entity<WaddleAnimationComponent> entity, float len, float tumbleIntensity)
{
entity.Comp.LastStep = !entity.Comp.LastStep;
var anim = new Animation()
{
Length = TimeSpan.FromSeconds(len),
AnimationTracks =
{
new AnimationTrackComponentProperty()
{
ComponentType = typeof(SpriteComponent),
Property = nameof(SpriteComponent.Rotation),
InterpolationMode = AnimationInterpolationMode.Linear,
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), 0),
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(tumbleIntensity), len/2),
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), len/2),
}
},
new AnimationTrackComponentProperty()
{
ComponentType = typeof(SpriteComponent),
Property = nameof(SpriteComponent.Offset),
InterpolationMode = AnimationInterpolationMode.Linear,
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(new Vector2(), 0),
new AnimationTrackProperty.KeyFrame(entity.Comp.HopIntensity, len/2),
new AnimationTrackProperty.KeyFrame(new Vector2(), len/2),
}
}
}
};
_animation.Play(entity.Owner, anim, entity.Comp.KeyName);
}
}

View File

@@ -1,4 +1,5 @@
#nullable enable
using System.Collections.Generic;
using System.Linq;
using Content.Server.Body.Components;
using Content.Server.GameTicking;
@@ -120,8 +121,8 @@ public sealed class NukeOpsTest
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mind));
Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True);
Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False);
var roles = roleSys.MindGetAllRoles(mind);
var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander" && x.Component is NukeopsRoleComponent);
var roles = roleSys.MindGetAllRoleInfo(mind);
var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander");
Assert.That(cmdRoles.Count(), Is.EqualTo(1));
// The second dummy player should be a medic
@@ -131,8 +132,8 @@ public sealed class NukeOpsTest
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(dummyMind));
Assert.That(factionSys.IsMember(dummyEnts[1], "Syndicate"), Is.True);
Assert.That(factionSys.IsMember(dummyEnts[1], "NanoTrasen"), Is.False);
roles = roleSys.MindGetAllRoles(dummyMind);
cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic" && x.Component is NukeopsRoleComponent);
roles = roleSys.MindGetAllRoleInfo(dummyMind);
cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic");
Assert.That(cmdRoles.Count(), Is.EqualTo(1));
// The other two players should have just spawned in as normal.
@@ -141,13 +142,14 @@ public sealed class NukeOpsTest
void CheckDummy(int i)
{
var ent = dummyEnts[i];
var mind = mindSys.GetMind(ent)!.Value;
var mindCrew = mindSys.GetMind(ent)!.Value;
Assert.That(entMan.HasComponent<NukeOperativeComponent>(ent), Is.False);
Assert.That(roleSys.MindIsAntagonist(mind), Is.False);
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mind), Is.False);
Assert.That(roleSys.MindIsAntagonist(mindCrew), Is.False);
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mindCrew), Is.False);
Assert.That(factionSys.IsMember(ent, "Syndicate"), Is.False);
Assert.That(factionSys.IsMember(ent, "NanoTrasen"), Is.True);
Assert.That(roleSys.MindGetAllRoles(mind).Any(x => x.Component is NukeopsRoleComponent), Is.False);
var nukeroles = new List<string>() { "Nukeops", "NukeopsMedic", "NukeopsCommander" };
Assert.That(roleSys.MindGetAllRoleInfo(mindCrew).Any(x => nukeroles.Contains(x.Prototype)), Is.False);
}
// The game rule exists, and all the stations/shuttles/maps are properly initialized
@@ -238,7 +240,8 @@ public sealed class NukeOpsTest
for (var i = 0; i < nukies.Length - 1; i++)
{
entMan.DeleteEntity(nukies[i]);
Assert.That(roundEndSys.IsRoundEndRequested, Is.False,
Assert.That(roundEndSys.IsRoundEndRequested,
Is.False,
$"The round ended, but {nukies.Length - i - 1} nukies are still alive!");
}
// Delete the last nukie and make sure the round ends.

View File

@@ -2,7 +2,6 @@ using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Systems;
using Content.Server.Station.Systems;
using Content.Shared.Preferences;
using Content.Shared.Roles.Jobs;
namespace Content.IntegrationTests.Tests.Internals;
@@ -25,10 +24,7 @@ public sealed class AutoInternalsTests
await server.WaitAssertion(() =>
{
var profile = new HumanoidCharacterProfile();
var dummy = stationSpawning.SpawnPlayerMob(testMap.GridCoords, new JobComponent()
{
Prototype = "TestInternalsDummy"
}, profile, station: null);
var dummy = stationSpawning.SpawnPlayerMob(testMap.GridCoords, "TestInternalsDummy", profile, station: null);
Assert.That(atmos.HasAtmosphere(testMap.Grid), Is.False, "Test map has atmosphere - test needs adjustment!");
Assert.That(internals.AreInternalsWorking(dummy), "Internals did not automatically connect!");

View File

@@ -1,10 +1,8 @@
#nullable enable
using System.Linq;
using Content.Server.Ghost;
using Content.Server.Ghost.Roles;
using Content.Server.Ghost.Roles.Components;
using Content.Server.Mind.Commands;
using Content.Server.Players;
using Content.Server.Roles;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
@@ -18,7 +16,6 @@ using Robust.Server.Console;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
@@ -287,27 +284,27 @@ public sealed partial class MindTests
Assert.Multiple(() =>
{
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False);
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId), Is.False);
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId), Is.False);
});
var traitorRole = new TraitorRoleComponent();
var traitorRole = "MindRoleTraitor";
roleSystem.MindAddRole(mindId, traitorRole);
Assert.Multiple(() =>
{
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId));
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId), Is.False);
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId), Is.False);
});
var jobRole = new JobComponent();
var jobRole = "";
roleSystem.MindAddRole(mindId, jobRole);
roleSystem.MindAddJobRole(mindId, jobPrototype:jobRole);
Assert.Multiple(() =>
{
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId));
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId));
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId));
});
roleSystem.MindRemoveRole<TraitorRoleComponent>(mindId);
@@ -315,15 +312,15 @@ public sealed partial class MindTests
Assert.Multiple(() =>
{
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False);
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId));
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId));
});
roleSystem.MindRemoveRole<JobComponent>(mindId);
roleSystem.MindRemoveRole<JobRoleComponent>(mindId);
Assert.Multiple(() =>
{
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False);
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId), Is.False);
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId), Is.False);
});
});

View File

@@ -3,7 +3,6 @@ using Content.Server.Station.Systems;
using Content.Shared.Inventory;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles.Jobs;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
@@ -68,10 +67,7 @@ public sealed class LoadoutTests
profile.SetLoadout(new RoleLoadout("LoadoutTester"));
var tester = stationSystem.SpawnPlayerMob(testMap.GridCoords, job: new JobComponent()
{
Prototype = "LoadoutTester"
}, profile, station: null);
var tester = stationSystem.SpawnPlayerMob(testMap.GridCoords, job: "LoadoutTester", profile, station: null);
var slotQuery = inventorySystem.GetSlotEnumerator(tester);
var checkedCount = 0;

View File

@@ -67,7 +67,7 @@ namespace Content.Server.Access.Systems
if (!TryComp<IdCardComponent>(uid, out var idCard))
return;
var state = new AgentIDCardBoundUserInterfaceState(idCard.FullName ?? "", idCard.JobTitle ?? "", idCard.JobIcon);
var state = new AgentIDCardBoundUserInterfaceState(idCard.FullName ?? "", idCard.LocalizedJobTitle ?? "", idCard.JobIcon);
_uiSystem.SetUiState(uid, AgentIDCardUiKey.Key, state);
}

View File

@@ -96,7 +96,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
PrivilegedIdIsAuthorized(uid, component),
true,
targetIdComponent.FullName,
targetIdComponent.JobTitle,
targetIdComponent.LocalizedJobTitle,
targetAccessComponent.Tags.ToList(),
possibleAccess,
jobProto,

View File

@@ -22,11 +22,17 @@ public sealed class TechAnomalySystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<TechAnomalyComponent, MapInitEvent>(OnTechMapInit);
SubscribeLocalEvent<TechAnomalyComponent, AnomalyPulseEvent>(OnPulse);
SubscribeLocalEvent<TechAnomalyComponent, AnomalySupercriticalEvent>(OnSupercritical);
SubscribeLocalEvent<TechAnomalyComponent, AnomalyStabilityChangedEvent>(OnStabilityChanged);
}
private void OnTechMapInit(Entity<TechAnomalyComponent> ent, ref MapInitEvent args)
{
ent.Comp.NextTimer = _timing.CurTime;
}
public override void Update(float frameTime)
{
base.Update(frameTime);

View File

@@ -11,7 +11,6 @@ using Content.Server.Preferences.Managers;
using Content.Server.Roles;
using Content.Server.Roles.Jobs;
using Content.Server.Shuttles.Components;
using Content.Server.Station.Systems;
using Content.Shared.Antag;
using Content.Shared.Clothing;
using Content.Shared.GameTicking;
@@ -20,7 +19,6 @@ using Content.Shared.Ghost;
using Content.Shared.Humanoid;
using Content.Shared.Mind;
using Content.Shared.Players;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles;
using Content.Shared.Whitelist;
using Robust.Server.Audio;
@@ -37,14 +35,14 @@ namespace Content.Server.Antag;
public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelectionComponent>
{
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IServerPreferencesManager _pref = default!;
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly GhostRoleSystem _ghostRole = default!;
[Dependency] private readonly JobSystem _jobs = default!;
[Dependency] private readonly LoadoutSystem _loadout = default!;
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IServerPreferencesManager _pref = default!;
[Dependency] private readonly RoleSystem _role = default!;
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
@@ -193,6 +191,9 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
/// <summary>
/// Chooses antagonists from the given selection of players
/// </summary>
/// <param name="ent">The antagonist rule entity</param>
/// <param name="pool">The players to choose from</param>
/// <param name="midround">Disable picking players for pre-spawn antags in the middle of a round</param>
public void ChooseAntags(Entity<AntagSelectionComponent> ent, IList<ICommonSession> pool, bool midround = false)
{
if (ent.Comp.SelectionsComplete)
@@ -209,8 +210,14 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
/// <summary>
/// Chooses antagonists from the given selection of players for the given antag definition.
/// </summary>
/// <param name="ent">The antagonist rule entity</param>
/// <param name="pool">The players to choose from</param>
/// <param name="def">The antagonist selection parameters and criteria</param>
/// <param name="midround">Disable picking players for pre-spawn antags in the middle of a round</param>
public void ChooseAntags(Entity<AntagSelectionComponent> ent, IList<ICommonSession> pool, AntagSelectionDefinition def, bool midround = false)
public void ChooseAntags(Entity<AntagSelectionComponent> ent,
IList<ICommonSession> pool,
AntagSelectionDefinition def,
bool midround = false)
{
var playerPool = GetPlayerPool(ent, pool, def);
var count = GetTargetAntagCount(ent, GetTotalPlayerCount(pool), def);
@@ -331,7 +338,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
EntityManager.AddComponents(player, def.Components);
// Equip the entity's RoleLoadout and LoadoutGroup
List<ProtoId<StartingGearPrototype>>? gear = new();
List<ProtoId<StartingGearPrototype>> gear = new();
if (def.StartingGear is not null)
gear.Add(def.StartingGear.Value);
@@ -340,8 +347,8 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
if (session != null)
{
var curMind = session.GetMind();
if (curMind == null ||
if (curMind == null ||
!TryComp<MindComponent>(curMind.Value, out var mindComp) ||
mindComp.OwnedEntity != antagEnt)
{
@@ -350,7 +357,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
}
_mind.TransferTo(curMind.Value, antagEnt, ghostCheckOverride: true);
_role.MindAddRoles(curMind.Value, def.MindComponents, null, true);
_role.MindAddRoles(curMind.Value, def.MindRoles, null, true);
ent.Comp.SelectedMinds.Add((curMind.Value, Name(player)));
SendBriefing(session, def.Briefing);
}

View File

@@ -3,7 +3,6 @@ using Content.Shared.Antag;
using Content.Shared.Destructible.Thresholds;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles;
using Content.Shared.Storage;
using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.Player;
@@ -145,10 +144,17 @@ public partial struct AntagSelectionDefinition()
/// <summary>
/// Components added to the player's mind.
/// Do NOT use this to add role-type components. Add those as MindRoles instead
/// </summary>
[DataField]
public ComponentRegistry MindComponents = new();
/// <summary>
/// List of Mind Role Prototypes to be added to the player's mind.
/// </summary>
[DataField]
public List<ProtoId<EntityPrototype>>? MindRoles;
/// <summary>
/// A set of starting gear that's equipped to the player.
/// </summary>

View File

@@ -239,7 +239,7 @@ public sealed class CryostorageSystem : SharedCryostorageSystem
Loc.GetString(
"earlyleave-cryo-announcement",
("character", name),
("entity", ent.Owner),
("entity", ent.Owner), // gender things for supporting downstreams with other languages
("job", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(jobName))
), Loc.GetString("earlyleave-cryo-sender"),
playDefaultSound: false

View File

@@ -420,6 +420,13 @@ public sealed partial class CargoSystem
return false;
_nameIdentifier.GenerateUniqueName(uid, BountyNameIdentifierGroup, out var randomVal);
var newBounty = new CargoBountyData(bounty, randomVal);
// This bounty id already exists! Probably because NameIdentifierSystem ran out of ids.
if (component.Bounties.Any(b => b.Id == newBounty.Id))
{
Log.Error("Failed to add bounty {ID} because another one with the same ID already existed!", newBounty.Id);
return false;
}
component.Bounties.Add(new CargoBountyData(bounty, randomVal));
_adminLogger.Add(LogType.Action, LogImpact.Low, $"Added bounty \"{bounty.ID}\" (id:{component.TotalBounties}) to station {ToPrettyString(uid)}");
component.TotalBounties++;

View File

@@ -231,7 +231,7 @@ namespace Content.Server.Cloning
// TODO: Ideally, components like this should be components on the mind entity so this isn't necessary.
// Add on special job components to the mob.
if (_jobs.MindTryGetJob(mindEnt, out _, out var prototype))
if (_jobs.MindTryGetJob(mindEnt, out var prototype))
{
foreach (var special in prototype.Special)
{

View File

@@ -0,0 +1,10 @@
namespace Content.Server.Destructible.Thresholds.Behaviors;
[DataDefinition]
public sealed partial class TimerStartBehavior : IThresholdBehavior
{
public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null)
{
system.TriggerSystem.StartTimer(owner, cause);
}
}

View File

@@ -1,49 +1,45 @@
using System.Linq;
using Content.Shared.EntityEffects;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Localizations;
using Robust.Shared.Prototypes;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Roles;
using Content.Shared.Roles.Jobs;
using Content.Shared.Station;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.EffectConditions;
public sealed partial class JobCondition : EntityEffectCondition
{
[DataField(required: true)] public List<ProtoId<JobPrototype>> Job;
public override bool Condition(EntityEffectBaseArgs args)
{
{
args.EntityManager.TryGetComponent<MindContainerComponent>(args.TargetEntity, out var mindContainer);
if (mindContainer != null && mindContainer.Mind != null)
if ( mindContainer is null
|| !args.EntityManager.TryGetComponent<MindComponent>(mindContainer.Mind, out var mind))
return false;
foreach (var roleId in mind.MindRoles)
{
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
if (args.EntityManager.TryGetComponent<JobComponent>(mindContainer?.Mind, out var comp) && prototypeManager.TryIndex(comp.Prototype, out var prototype))
{
foreach (var jobId in Job)
{
if (prototype.ID == jobId)
{
return true;
}
}
}
if(!args.EntityManager.HasComponent<JobRoleComponent>(roleId))
continue;
if(!args.EntityManager.TryGetComponent<MindRoleComponent>(roleId, out var mindRole)
|| mindRole.JobPrototype is null)
continue;
if (Job.Contains(mindRole.JobPrototype.Value))
return true;
}
return false;
}
public override string GuidebookExplanation(IPrototypeManager prototype)
{
var localizedNames = Job.Select(jobId => prototype.Index(jobId).LocalizedName).ToList();
return Loc.GetString("reagent-effect-condition-guidebook-job-condition", ("job", ContentLocalizationManager.FormatListToOr(localizedNames)));
}
}

View File

@@ -0,0 +1,15 @@
using Content.Shared.DeviceLinking;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Explosion.Components
{
/// <summary>
/// Sends a trigger when signal is received.
/// </summary>
[RegisterComponent]
public sealed partial class TimerStartOnSignalComponent : Component
{
[DataField("port", customTypeSerializer: typeof(PrototypeIdSerializer<SinkPortPrototype>))]
public string Port = "Timer";
}
}

View File

@@ -488,9 +488,12 @@ public sealed partial class ExplosionSystem
&& physics.BodyType == BodyType.Dynamic)
{
var pos = _transformSystem.GetWorldPosition(xform);
var dir = pos - epicenter.Position;
if (dir.IsLengthZero())
dir = _robustRandom.NextVector2().Normalized();
_throwingSystem.TryThrow(
uid,
pos - epicenter.Position,
dir,
physics,
xform,
_projectileQuery,

View File

@@ -11,6 +11,9 @@ namespace Content.Server.Explosion.EntitySystems
{
SubscribeLocalEvent<TriggerOnSignalComponent,SignalReceivedEvent>(OnSignalReceived);
SubscribeLocalEvent<TriggerOnSignalComponent,ComponentInit>(OnInit);
SubscribeLocalEvent<TimerStartOnSignalComponent,SignalReceivedEvent>(OnTimerSignalReceived);
SubscribeLocalEvent<TimerStartOnSignalComponent,ComponentInit>(OnTimerSignalInit);
}
private void OnSignalReceived(EntityUid uid, TriggerOnSignalComponent component, ref SignalReceivedEvent args)
@@ -24,5 +27,17 @@ namespace Content.Server.Explosion.EntitySystems
{
_signalSystem.EnsureSinkPorts(uid, component.Port);
}
private void OnTimerSignalReceived(EntityUid uid, TimerStartOnSignalComponent component, ref SignalReceivedEvent args)
{
if (args.Port != component.Port)
return;
StartTimer(uid, args.Trigger);
}
private void OnTimerSignalInit(EntityUid uid, TimerStartOnSignalComponent component, ComponentInit args)
{
_signalSystem.EnsureSinkPorts(uid, component.Port);
}
}
}

View File

@@ -4,6 +4,7 @@ using Content.Server.Discord;
using Content.Server.GameTicking.Events;
using Content.Server.Ghost;
using Content.Server.Maps;
using Content.Server.Roles;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.GameTicking;
@@ -26,6 +27,7 @@ namespace Content.Server.GameTicking
public sealed partial class GameTicker
{
[Dependency] private readonly DiscordWebhook _discord = default!;
[Dependency] private readonly RoleSystem _role = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
private static readonly Counter RoundNumberMetric = Metrics.CreateCounter(
@@ -339,8 +341,23 @@ namespace Content.Server.GameTicking
RunLevel = GameRunLevel.PostRound;
ShowRoundEndScoreboard(text);
SendRoundEndDiscordMessage();
try
{
ShowRoundEndScoreboard(text);
}
catch (Exception e)
{
Log.Error($"Error while showing round end scoreboard: {e}");
}
try
{
SendRoundEndDiscordMessage();
}
catch (Exception e)
{
Log.Error($"Error while sending round end Discord message: {e}");
}
}
public void ShowRoundEndScoreboard(string text = "")
@@ -373,7 +390,7 @@ namespace Content.Server.GameTicking
var userId = mind.UserId ?? mind.OriginalOwnerUserId;
var connected = false;
var observer = HasComp<ObserverRoleComponent>(mindId);
var observer = _role.MindHasRole<ObserverRoleComponent>(mindId);
// Continuing
if (userId != null && _playerManager.ValidSessionId(userId.Value))
{
@@ -400,7 +417,7 @@ namespace Content.Server.GameTicking
_pvsOverride.AddGlobalOverride(GetNetEntity(entity.Value), recursive: true);
}
var roles = _roles.MindGetAllRoles(mindId);
var roles = _roles.MindGetAllRoleInfo(mindId);
var playerEndRoundInfo = new RoundEndMessageEvent.RoundEndPlayerInfo()
{

View File

@@ -3,8 +3,6 @@ using System.Linq;
using System.Numerics;
using Content.Server.Administration.Managers;
using Content.Server.GameTicking.Events;
using Content.Server.Ghost;
using Content.Server.Shuttles.Components;
using Content.Server.Spawners.Components;
using Content.Server.Speech.Components;
using Content.Server.Station.Components;
@@ -224,13 +222,12 @@ namespace Content.Server.GameTicking
_mind.SetUserId(newMind, data.UserId);
var jobPrototype = _prototypeManager.Index<JobPrototype>(jobId);
var job = new JobComponent {Prototype = jobId};
_roles.MindAddRole(newMind, job, silent: silent);
_roles.MindAddJobRole(newMind, silent: silent, jobPrototype:jobId);
var jobName = _jobs.MindTryGetJobName(newMind);
_playTimeTrackings.PlayerRolesChanged(player);
var mobMaybe = _stationSpawning.SpawnPlayerCharacterOnStation(station, job, character);
var mobMaybe = _stationSpawning.SpawnPlayerCharacterOnStation(station, jobId, character);
DebugTools.AssertNotNull(mobMaybe);
var mob = mobMaybe!.Value;
@@ -271,13 +268,17 @@ namespace Content.Server.GameTicking
_stationJobs.TryAssignJob(station, jobPrototype, player.UserId);
if (lateJoin)
{
_adminLogger.Add(LogType.LateJoin,
LogImpact.Medium,
$"Player {player.Name} late joined as {character.Name:characterName} on station {Name(station):stationName} with {ToPrettyString(mob):entity} as a {jobName:jobName}.");
}
else
{
_adminLogger.Add(LogType.RoundStartJoin,
LogImpact.Medium,
$"Player {player.Name} joined as {character.Name:characterName} on station {Name(station):stationName} with {ToPrettyString(mob):entity} as a {jobName:jobName}.");
}
// Make sure they're aware of extended access.
if (Comp<StationJobsComponent>(station).ExtendedAccess
@@ -363,7 +364,7 @@ namespace Content.Server.GameTicking
var (mindId, mindComp) = _mind.CreateMind(player.UserId, name);
mind = (mindId, mindComp);
_mind.SetUserId(mind.Value, player.UserId);
_roles.MindAddRole(mind.Value, new ObserverRoleComponent());
_roles.MindAddRole(mind.Value, "MindRoleObserver");
}
var ghost = _ghost.SpawnGhost(mind.Value);

View File

@@ -9,7 +9,6 @@ using Content.Server.RoundEnd;
using Content.Server.Shuttles.Events;
using Content.Server.Shuttles.Systems;
using Content.Server.Station.Components;
using Content.Server.Store.Components;
using Content.Server.Store.Systems;
using Content.Shared.GameTicking.Components;
using Content.Shared.Mobs;
@@ -31,9 +30,9 @@ namespace Content.Server.GameTicking.Rules;
public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
{
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly EmergencyShuttleSystem _emergency = default!;
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
[Dependency] private readonly StoreSystem _store = default!;
@@ -57,6 +56,8 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
SubscribeLocalEvent<NukeOperativeComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<NukeOperativeComponent, EntityZombifiedEvent>(OnOperativeZombified);
SubscribeLocalEvent<NukeopsRoleComponent, GetBriefingEvent>(OnGetBriefing);
SubscribeLocalEvent<ConsoleFTLAttemptEvent>(OnShuttleFTLAttempt);
SubscribeLocalEvent<WarDeclaredEvent>(OnWarDeclared);
SubscribeLocalEvent<CommunicationConsoleCallShuttleAttemptEvent>(OnShuttleCallAttempt);
@@ -65,7 +66,9 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
SubscribeLocalEvent<NukeopsRuleComponent, RuleLoadedGridsEvent>(OnRuleLoadedGrids);
}
protected override void Started(EntityUid uid, NukeopsRuleComponent component, GameRuleComponent gameRule,
protected override void Started(EntityUid uid,
NukeopsRuleComponent component,
GameRuleComponent gameRule,
GameRuleStartedEvent args)
{
var eligible = new List<Entity<StationEventEligibleComponent, NpcFactionMemberComponent>>();
@@ -85,7 +88,9 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
}
#region Event Handlers
protected override void AppendRoundEndText(EntityUid uid, NukeopsRuleComponent component, GameRuleComponent gameRule,
protected override void AppendRoundEndText(EntityUid uid,
NukeopsRuleComponent component,
GameRuleComponent gameRule,
ref RoundEndTextAppendEvent args)
{
var winText = Loc.GetString($"nukeops-{component.WinType.ToString().ToLower()}");
@@ -227,7 +232,8 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
// If the disk is currently at Central Command, the crew wins - just slightly.
// This also implies that some nuclear operatives have died.
SetWinType(ent, diskAtCentCom
SetWinType(ent,
diskAtCentCom
? WinType.CrewMinor
: WinType.OpsMinor);
ent.Comp.WinConditions.Add(diskAtCentCom
@@ -456,8 +462,11 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
: WinCondition.AllNukiesDead);
SetWinType(ent, WinType.CrewMajor, false);
_roundEndSystem.DoRoundEndBehavior(
nukeops.RoundEndBehavior, nukeops.EvacShuttleTime, nukeops.RoundEndTextSender, nukeops.RoundEndTextShuttleCall, nukeops.RoundEndTextAnnouncement);
_roundEndSystem.DoRoundEndBehavior(nukeops.RoundEndBehavior,
nukeops.EvacShuttleTime,
nukeops.RoundEndTextSender,
nukeops.RoundEndTextShuttleCall,
nukeops.RoundEndTextAnnouncement);
// prevent it called multiple times
nukeops.RoundEndBehavior = RoundEndBehavior.Nothing;
@@ -465,16 +474,22 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
private void OnAfterAntagEntSelected(Entity<NukeopsRuleComponent> ent, ref AfterAntagEntitySelectedEvent args)
{
if (ent.Comp.TargetStation is not { } station)
return;
var target = (ent.Comp.TargetStation is not null) ? Name(ent.Comp.TargetStation.Value) : "the target";
_antag.SendBriefing(args.Session, Loc.GetString("nukeops-welcome",
("station", station),
_antag.SendBriefing(args.Session,
Loc.GetString("nukeops-welcome",
("station", target),
("name", Name(ent))),
Color.Red,
ent.Comp.GreetSoundNotification);
}
private void OnGetBriefing(Entity<NukeopsRoleComponent> role, ref GetBriefingEvent args)
{
// TODO Different character screen briefing for the 3 nukie types
args.Append(Loc.GetString("nukeops-briefing"));
}
/// <remarks>
/// Is this method the shitty glue holding together the last of my sanity? yes.
/// Do i have a better solution? not presently.

View File

@@ -15,7 +15,6 @@ using Content.Shared.Database;
using Content.Shared.GameTicking.Components;
using Content.Shared.Humanoid;
using Content.Shared.IdentityManagement;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Mindshield.Components;
using Content.Shared.Mobs;
@@ -38,8 +37,8 @@ namespace Content.Server.GameTicking.Rules;
public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleComponent>
{
[Dependency] private readonly IAdminLogManager _adminLogManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!;
[Dependency] private readonly EuiManager _euiMan = default!;
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
@@ -49,7 +48,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
[Dependency] private readonly SharedStunSystem _stun = default!;
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!;
[Dependency] private readonly IGameTiming _timing = default!;
//Used in OnPostFlash, no reference to the rule component is available
public readonly ProtoId<NpcFactionPrototype> RevolutionaryNpcFaction = "Revolutionary";
@@ -59,9 +58,12 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
{
base.Initialize();
SubscribeLocalEvent<CommandStaffComponent, MobStateChangedEvent>(OnCommandMobStateChanged);
SubscribeLocalEvent<HeadRevolutionaryComponent, MobStateChangedEvent>(OnHeadRevMobStateChanged);
SubscribeLocalEvent<RevolutionaryRoleComponent, GetBriefingEvent>(OnGetBriefing);
SubscribeLocalEvent<HeadRevolutionaryComponent, AfterFlashedEvent>(OnPostFlash);
SubscribeLocalEvent<HeadRevolutionaryComponent, MobStateChangedEvent>(OnHeadRevMobStateChanged);
SubscribeLocalEvent<RevolutionaryRoleComponent, GetBriefingEvent>(OnGetBriefing);
}
protected override void Started(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
@@ -85,7 +87,9 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
}
}
protected override void AppendRoundEndText(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule,
protected override void AppendRoundEndText(EntityUid uid,
RevolutionaryRuleComponent component,
GameRuleComponent gameRule,
ref RoundEndTextAppendEvent args)
{
base.AppendRoundEndText(uid, component, gameRule, ref args);
@@ -101,7 +105,9 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
args.AddLine(Loc.GetString("rev-headrev-count", ("initialCount", sessionData.Count)));
foreach (var (mind, data, name) in sessionData)
{
var count = CompOrNull<RevolutionaryRoleComponent>(mind)?.ConvertedCount ?? 0;
_role.MindHasRole<RevolutionaryRoleComponent>(mind, out var role);
var count = CompOrNull<RevolutionaryRoleComponent>(role)?.ConvertedCount ?? 0;
args.AddLine(Loc.GetString("rev-headrev-name-user",
("name", name),
("username", data.UserName),
@@ -113,10 +119,8 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
private void OnGetBriefing(EntityUid uid, RevolutionaryRoleComponent comp, ref GetBriefingEvent args)
{
if (!TryComp<MindComponent>(uid, out var mind) || mind.OwnedEntity == null)
return;
var head = HasComp<HeadRevolutionaryComponent>(mind.OwnedEntity);
var ent = args.Mind.Comp.OwnedEntity;
var head = HasComp<HeadRevolutionaryComponent>(ent);
args.Append(Loc.GetString(head ? "head-rev-briefing" : "rev-briefing"));
}
@@ -145,15 +149,20 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
if (ev.User != null)
{
_adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(ev.User.Value)} converted {ToPrettyString(ev.Target)} into a Revolutionary");
_adminLogManager.Add(LogType.Mind,
LogImpact.Medium,
$"{ToPrettyString(ev.User.Value)} converted {ToPrettyString(ev.Target)} into a Revolutionary");
if (_mind.TryGetRole<RevolutionaryRoleComponent>(ev.User.Value, out var headrev))
headrev.ConvertedCount++;
if (_mind.TryGetMind(ev.User.Value, out var revMindId, out _))
{
if (_role.MindHasRole<RevolutionaryRoleComponent>(revMindId, out _, out var role))
role.Value.Comp.ConvertedCount++;
}
}
if (mindId == default || !_role.MindHasRole<RevolutionaryRoleComponent>(mindId))
{
_role.MindAddRole(mindId, new RevolutionaryRoleComponent { PrototypeId = RevPrototypeId });
_role.MindAddRole(mindId, "MindRoleRevolutionary");
}
if (mind?.Session != null)

View File

@@ -1,18 +1,12 @@
using Content.Server.Antag;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Mind;
using Content.Server.Objectives;
using Content.Server.Roles;
using Content.Shared.Humanoid;
using Content.Shared.Mind;
using Content.Shared.Objectives.Components;
using Robust.Shared.Random;
namespace Content.Server.GameTicking.Rules;
public sealed class ThiefRuleSystem : GameRuleSystem<ThiefRuleComponent>
{
[Dependency] private readonly MindSystem _mindSystem = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!;
public override void Initialize()
@@ -24,32 +18,33 @@ public sealed class ThiefRuleSystem : GameRuleSystem<ThiefRuleComponent>
SubscribeLocalEvent<ThiefRoleComponent, GetBriefingEvent>(OnGetBriefing);
}
private void AfterAntagSelected(Entity<ThiefRuleComponent> ent, ref AfterAntagEntitySelectedEvent args)
// Greeting upon thief activation
private void AfterAntagSelected(Entity<ThiefRuleComponent> mindId, ref AfterAntagEntitySelectedEvent args)
{
if (!_mindSystem.TryGetMind(args.EntityUid, out var mindId, out var mind))
return;
//Generate objectives
_antag.SendBriefing(args.EntityUid, MakeBriefing(args.EntityUid), null, null);
var ent = args.EntityUid;
_antag.SendBriefing(ent, MakeBriefing(ent), null, null);
}
//Add mind briefing
private void OnGetBriefing(Entity<ThiefRoleComponent> thief, ref GetBriefingEvent args)
// Character screen briefing
private void OnGetBriefing(Entity<ThiefRoleComponent> role, ref GetBriefingEvent args)
{
if (!TryComp<MindComponent>(thief.Owner, out var mind) || mind.OwnedEntity == null)
return;
var ent = args.Mind.Comp.OwnedEntity;
args.Append(MakeBriefing(mind.OwnedEntity.Value));
if (ent is null)
return;
args.Append(MakeBriefing(ent.Value));
}
private string MakeBriefing(EntityUid thief)
private string MakeBriefing(EntityUid ent)
{
var isHuman = HasComp<HumanoidAppearanceComponent>(thief);
var isHuman = HasComp<HumanoidAppearanceComponent>(ent);
var briefing = isHuman
? Loc.GetString("thief-role-greeting-human")
: Loc.GetString("thief-role-greeting-animal");
briefing += "\n \n" + Loc.GetString("thief-role-greeting-equipment") + "\n";
if (isHuman)
briefing += "\n \n" + Loc.GetString("thief-role-greeting-equipment") + "\n";
return briefing;
}
}

View File

@@ -1,3 +1,4 @@
using Content.Server.Administration.Logs;
using Content.Server.Antag;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Mind;
@@ -5,12 +6,11 @@ using Content.Server.Objectives;
using Content.Server.PDA.Ringer;
using Content.Server.Roles;
using Content.Server.Traitor.Uplink;
using Content.Shared.Database;
using Content.Shared.GameTicking.Components;
using Content.Shared.Mind;
using Content.Shared.NPC.Systems;
using Content.Shared.Objectives.Components;
using Content.Shared.PDA;
using Content.Shared.Radio;
using Content.Shared.Roles;
using Content.Shared.Roles.Jobs;
using Content.Shared.Roles.RoleCodeword;
@@ -25,29 +25,29 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
{
private static readonly Color TraitorCodewordColor = Color.FromHex("#cc3b3b");
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly SharedJobSystem _jobs = default!;
[Dependency] private readonly MindSystem _mindSystem = default!;
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly UplinkSystem _uplink = default!;
[Dependency] private readonly MindSystem _mindSystem = default!;
[Dependency] private readonly SharedRoleSystem _roleSystem = default!;
[Dependency] private readonly SharedJobSystem _jobs = default!;
[Dependency] private readonly SharedRoleCodewordSystem _roleCodewordSystem = default!;
[Dependency] private readonly SharedRoleSystem _roleSystem = default!;
[Dependency] private readonly UplinkSystem _uplink = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<TraitorRuleComponent, AfterAntagEntitySelectedEvent>(AfterEntitySelected);
SubscribeLocalEvent<TraitorRuleComponent, ObjectivesTextPrependEvent>(OnObjectivesTextPrepend);
}
protected override void Added(EntityUid uid, TraitorRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
{
base.Added(uid, component, gameRule, args);
SetCodewords(component);
SetCodewords(component, args.RuleEntity);
}
private void AfterEntitySelected(Entity<TraitorRuleComponent> ent, ref AfterAntagEntitySelectedEvent args)
@@ -55,9 +55,10 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
MakeTraitor(args.EntityUid, ent);
}
private void SetCodewords(TraitorRuleComponent component)
private void SetCodewords(TraitorRuleComponent component, EntityUid ruleEntity)
{
component.Codewords = GenerateTraitorCodewords(component);
_adminLogger.Add(LogType.EventStarted, LogImpact.Low, $"Codewords generated for game rule {ToPrettyString(ruleEntity)}: {string.Join(", ", component.Codewords)}");
}
public string[] GenerateTraitorCodewords(TraitorRuleComponent component)
@@ -76,7 +77,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool giveUplink = true)
{
//Grab the mind if it wasnt provided
//Grab the mind if it wasn't provided
if (!_mindSystem.TryGetMind(traitor, out var mindId, out var mind))
return false;
@@ -88,7 +89,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
{
// Calculate the amount of currency on the uplink.
var startingBalance = component.StartingBalance;
if (_jobs.MindTryGetJob(mindId, out _, out var prototype))
if (_jobs.MindTryGetJob(mindId, out var prototype))
startingBalance = Math.Max(startingBalance - prototype.AntagAdvantage, 0);
// creadth: we need to create uplink for the antag.
@@ -107,14 +108,18 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
_antag.SendBriefing(traitor, GenerateBriefing(component.Codewords, code, issuer), null, component.GreetSoundNotification);
component.TraitorMinds.Add(mindId);
// Assign briefing
_roleSystem.MindAddRole(mindId, new RoleBriefingComponent
//Since this provides neither an antag/job prototype, nor antag status/roletype,
//and is intrinsically related to the traitor role
//it does not need to be a separate Mind Role Entity
_roleSystem.MindHasRole<TraitorRoleComponent>(mindId, out var traitorRole);
if (traitorRole is not null)
{
Briefing = briefing
}, mind, true);
AddComp<RoleBriefingComponent>(traitorRole.Value.Owner);
Comp<RoleBriefingComponent>(traitorRole.Value.Owner).Briefing = briefing;
}
// Send codewords to only the traitor client
var color = TraitorCodewordColor; // Fall back to a dark red Syndicate color if a prototype is not found

View File

@@ -13,6 +13,7 @@ using Content.Shared.Mind;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Roles;
using Content.Shared.Zombies;
using Robust.Shared.Player;
using Robust.Shared.Timing;
@@ -22,15 +23,16 @@ namespace Content.Server.GameTicking.Rules;
public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
{
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly ZombieSystem _zombie = default!;
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ZombieSystem _zombie = default!;
public override void Initialize()
{
@@ -41,23 +43,20 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
SubscribeLocalEvent<IncurableZombieComponent, ZombifySelfActionEvent>(OnZombifySelf);
}
private void OnGetBriefing(EntityUid uid, InitialInfectedRoleComponent component, ref GetBriefingEvent args)
private void OnGetBriefing(Entity<InitialInfectedRoleComponent> role, ref GetBriefingEvent args)
{
if (!TryComp<MindComponent>(uid, out var mind) || mind.OwnedEntity == null)
return;
if (HasComp<ZombieRoleComponent>(uid)) // don't show both briefings
return;
args.Append(Loc.GetString("zombie-patientzero-role-greeting"));
if (!_roles.MindHasRole<ZombieRoleComponent>(args.Mind.Owner))
args.Append(Loc.GetString("zombie-patientzero-role-greeting"));
}
private void OnGetBriefing(EntityUid uid, ZombieRoleComponent component, ref GetBriefingEvent args)
private void OnGetBriefing(Entity<ZombieRoleComponent> role, ref GetBriefingEvent args)
{
if (!TryComp<MindComponent>(uid, out var mind) || mind.OwnedEntity == null)
return;
args.Append(Loc.GetString("zombie-infection-greeting"));
}
protected override void AppendRoundEndText(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule,
protected override void AppendRoundEndText(EntityUid uid,
ZombieRuleComponent component,
GameRuleComponent gameRule,
ref RoundEndTextAppendEvent args)
{
base.AppendRoundEndText(uid, component, gameRule, ref args);

View File

@@ -1,11 +1,13 @@
namespace Content.Server.Ghost.Roles;
using Content.Shared.Roles;
namespace Content.Server.Ghost.Roles;
/// <summary>
/// This is used for round end display of ghost roles.
/// It may also be used to ensure some ghost roles count as antagonists in future.
/// </summary>
[RegisterComponent]
public sealed partial class GhostRoleMarkerRoleComponent : Component
public sealed partial class GhostRoleMarkerRoleComponent : BaseMindRoleComponent
{
[DataField("name")] public string? Name;
}

View File

@@ -71,18 +71,22 @@ public sealed class GhostRoleSystem : EntitySystem
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<GhostTakeoverAvailableComponent, MindAddedMessage>(OnMindAdded);
SubscribeLocalEvent<GhostTakeoverAvailableComponent, MindRemovedMessage>(OnMindRemoved);
SubscribeLocalEvent<GhostTakeoverAvailableComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<GhostTakeoverAvailableComponent, TakeGhostRoleEvent>(OnTakeoverTakeRole);
SubscribeLocalEvent<GhostRoleComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<GhostRoleComponent, ComponentStartup>(OnRoleStartup);
SubscribeLocalEvent<GhostRoleComponent, ComponentShutdown>(OnRoleShutdown);
SubscribeLocalEvent<GhostRoleComponent, EntityPausedEvent>(OnPaused);
SubscribeLocalEvent<GhostRoleComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<GhostRoleRaffleComponent, ComponentInit>(OnRaffleInit);
SubscribeLocalEvent<GhostRoleRaffleComponent, ComponentShutdown>(OnRaffleShutdown);
SubscribeLocalEvent<GhostRoleMobSpawnerComponent, TakeGhostRoleEvent>(OnSpawnerTakeRole);
SubscribeLocalEvent<GhostTakeoverAvailableComponent, TakeGhostRoleEvent>(OnTakeoverTakeRole);
SubscribeLocalEvent<GhostRoleMobSpawnerComponent, GetVerbsEvent<Verb>>(OnVerb);
SubscribeLocalEvent<GhostRoleMobSpawnerComponent, GhostRoleRadioMessage>(OnGhostRoleRadioMessage);
_playerManager.PlayerStatusChanged += PlayerStatusChanged;
@@ -509,7 +513,11 @@ public sealed class GhostRoleSystem : EntitySystem
var newMind = _mindSystem.CreateMind(player.UserId,
EntityManager.GetComponent<MetaDataComponent>(mob).EntityName);
_roleSystem.MindAddRole(newMind, new GhostRoleMarkerRoleComponent { Name = role.RoleName });
_roleSystem.MindAddRole(newMind, "MindRoleGhostMarker");
if(_roleSystem.MindHasRole<GhostRoleMarkerRoleComponent>(newMind, out _, out var markerRole))
markerRole.Value.Comp.Name = role.RoleName;
_mindSystem.SetUserId(newMind, player.UserId);
_mindSystem.TransferTo(newMind, mob);
@@ -602,10 +610,7 @@ public sealed class GhostRoleSystem : EntitySystem
if (ghostRole.JobProto != null)
{
if (HasComp<JobComponent>(args.Mind))
_roleSystem.MindRemoveRole<JobComponent>(args.Mind);
_roleSystem.MindAddRole(args.Mind, new JobComponent { Prototype = ghostRole.JobProto });
_roleSystem.MindAddJobRole(args.Mind, args.Mind, silent:false,ghostRole.JobProto);
}
ghostRole.Taken = true;

View File

@@ -166,7 +166,7 @@ public sealed class IdentitySystem : SharedIdentitySystem
if (_idCard.TryFindIdCard(target, out var id))
{
presumedName = string.IsNullOrWhiteSpace(id.Comp.FullName) ? null : id.Comp.FullName;
presumedJob = id.Comp.JobTitle?.ToLowerInvariant();
presumedJob = id.Comp.LocalizedJobTitle?.ToLowerInvariant();
}
// If it didn't find a job, that's fine.

View File

@@ -5,6 +5,7 @@ using Content.Server.Stack;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Destructible;
using Content.Shared.FixedPoint;
using Content.Shared.Interaction;
using Content.Shared.Kitchen;
@@ -122,6 +123,9 @@ namespace Content.Server.Kitchen.EntitySystems
if (solution.Volume > containerSolution.AvailableVolume)
continue;
var dev = new DestructionEventArgs();
RaiseLocalEvent(item, dev);
QueueDel(item);
}

View File

@@ -175,6 +175,10 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem
var materialPerStack = composition.MaterialComposition[materialProto.ID];
var amountToSpawn = amount / materialPerStack;
overflowMaterial = amount - amountToSpawn * materialPerStack;
if (amountToSpawn == 0)
return new List<EntityUid>();
return _stackSystem.SpawnMultiple(materialProto.StackEntity, amountToSpawn, coordinates);
}

View File

@@ -363,8 +363,8 @@ public sealed class SuitSensorSystem : EntitySystem
{
if (card.Comp.FullName != null)
userName = card.Comp.FullName;
if (card.Comp.JobTitle != null)
userJob = card.Comp.JobTitle;
if (card.Comp.LocalizedJobTitle != null)
userJob = card.Comp.LocalizedJobTitle;
userJobIcon = card.Comp.JobIcon;
foreach (var department in card.Comp.JobDepartments)

View File

@@ -43,7 +43,7 @@ namespace Content.Server.Mind.Commands
builder.AppendFormat("player: {0}, mob: {1}\nroles: ", mind.UserId, mind.OwnedEntity);
var roles = _entities.System<SharedRoleSystem>();
foreach (var role in roles.MindGetAllRoles(mindId))
foreach (var role in roles.MindGetAllRoleInfo(mindId))
{
builder.AppendFormat("{0} ", role.Name);
}

View File

@@ -1,5 +0,0 @@
using Content.Shared.Movement.Systems;
namespace Content.Server.Movement.Systems;
public sealed class WaddleAnimationSystem : SharedWaddleAnimationSystem;

View File

@@ -14,6 +14,7 @@ public sealed class NameIdentifierSystem : EntitySystem
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly ILogManager _logManager = default!;
/// <summary>
/// Free IDs available per <see cref="NameIdentifierGroupPrototype"/>.
@@ -118,23 +119,39 @@ public sealed class NameIdentifierSystem : EntitySystem
private void InitialSetupPrototypes()
{
foreach (var proto in _prototypeManager.EnumeratePrototypes<NameIdentifierGroupPrototype>())
{
AddGroup(proto);
}
EnsureIds();
}
private void AddGroup(NameIdentifierGroupPrototype proto)
private void FillGroup(NameIdentifierGroupPrototype proto, List<int> values)
{
var values = new List<int>(proto.MaxValue - proto.MinValue);
values.Clear();
for (var i = proto.MinValue; i < proto.MaxValue; i++)
{
values.Add(i);
}
_robustRandom.Shuffle(values);
CurrentIds.Add(proto.ID, values);
}
private List<int> GetOrCreateIdList(NameIdentifierGroupPrototype proto)
{
if (!CurrentIds.TryGetValue(proto.ID, out var ids))
{
ids = new List<int>(proto.MaxValue - proto.MinValue);
CurrentIds.Add(proto.ID, ids);
}
return ids;
}
private void EnsureIds()
{
foreach (var proto in _prototypeManager.EnumeratePrototypes<NameIdentifierGroupPrototype>())
{
var ids = GetOrCreateIdList(proto);
FillGroup(proto, ids);
}
}
private void OnReloadPrototypes(PrototypesReloadedEventArgs ev)
@@ -159,19 +176,20 @@ public sealed class NameIdentifierSystem : EntitySystem
foreach (var proto in set.Modified.Values)
{
var name_proto = (NameIdentifierGroupPrototype) proto;
// Only bother adding new ones.
if (CurrentIds.ContainsKey(proto.ID))
continue;
AddGroup((NameIdentifierGroupPrototype) proto);
var ids = GetOrCreateIdList(name_proto);
FillGroup(name_proto, ids);
}
}
private void CleanupIds(RoundRestartCleanupEvent ev)
{
foreach (var values in CurrentIds.Values)
{
_robustRandom.Shuffle(values);
}
EnsureIds();
}
}

View File

@@ -33,6 +33,7 @@ using System.Linq;
using Content.Shared.Containers.ItemSlots;
using Robust.Server.GameObjects;
using Content.Shared.Whitelist;
using Content.Shared.Destructible;
namespace Content.Server.Nutrition.EntitySystems;
@@ -335,6 +336,9 @@ public sealed class FoodSystem : EntitySystem
if (ev.Cancelled)
return;
var dev = new DestructionEventArgs();
RaiseLocalEvent(food, dev);
if (component.Trash.Count == 0)
{
QueueDel(food);

View File

@@ -89,7 +89,7 @@ public sealed class KillPersonConditionSystem : EntitySystem
foreach (var mind in allHumans)
{
// RequireAdminNotify used as a cheap way to check for command department
if (_job.MindTryGetJob(mind, out _, out var prototype) && prototype.RequireAdminNotify)
if (_job.MindTryGetJob(mind, out var prototype) && prototype.RequireAdminNotify)
allHeads.Add(mind);
}

View File

@@ -1,9 +1,10 @@
using Content.Server.Objectives.Components;
using Content.Server.Roles;
using Content.Server.Warps;
using Content.Shared.Objectives.Components;
using Content.Shared.Ninja.Components;
using Content.Shared.Roles;
using Robust.Shared.Random;
using Content.Server.Roles;
namespace Content.Server.Objectives.Systems;
@@ -16,6 +17,7 @@ public sealed class NinjaConditionsSystem : EntitySystem
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly NumberObjectiveSystem _number = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
public override void Initialize()
{
@@ -46,10 +48,8 @@ public sealed class NinjaConditionsSystem : EntitySystem
// spider charge
private void OnSpiderChargeRequirementCheck(EntityUid uid, SpiderChargeConditionComponent comp, ref RequirementCheckEvent args)
{
if (args.Cancelled || !HasComp<NinjaRoleComponent>(args.MindId))
{
if (args.Cancelled || !_roles.MindHasRole<NinjaRoleComponent>(args.MindId))
return;
}
// choose spider charge detonation point
var warps = new List<EntityUid>();

View File

@@ -21,7 +21,7 @@ public sealed class NotCommandRequirementSystem : EntitySystem
return;
// cheap equivalent to checking that job department is command, since all command members require admin notification when leaving
if (_job.MindTryGetJob(args.MindId, out _, out var prototype) && prototype.RequireAdminNotify)
if (_job.MindTryGetJob(args.MindId, out var prototype) && prototype.RequireAdminNotify)
args.Cancelled = true;
}
}

View File

@@ -8,6 +8,8 @@ namespace Content.Server.Objectives.Systems;
/// </summary>
public sealed class NotJobRequirementSystem : EntitySystem
{
[Dependency] private readonly SharedJobSystem _jobs = default!;
public override void Initialize()
{
base.Initialize();
@@ -20,11 +22,10 @@ public sealed class NotJobRequirementSystem : EntitySystem
if (args.Cancelled)
return;
// if player has no job then don't care
if (!TryComp<JobComponent>(args.MindId, out var job))
return;
_jobs.MindTryGetJob(args.MindId, out var proto);
if (job.Prototype == comp.Job)
// if player has no job then don't care
if (proto is not null && proto.ID == comp.Job)
args.Cancelled = true;
}
}

View File

@@ -22,8 +22,6 @@ public sealed class RoleRequirementSystem : EntitySystem
if (args.Cancelled)
return;
// this whitelist trick only works because roles are components on the mind and not entities
// if that gets reworked then this will need changing
if (_whitelistSystem.IsWhitelistFail(comp.Roles, args.MindId))
args.Cancelled = true;
}

View File

@@ -192,7 +192,7 @@ namespace Content.Server.PDA
{
ActualOwnerName = pda.OwnerName,
IdOwner = id?.FullName,
JobTitle = id?.JobTitle,
JobTitle = id?.LocalizedJobTitle,
StationAlertLevel = pda.StationAlertLevel,
StationAlertColor = pda.StationAlertColor
},

View File

@@ -30,14 +30,15 @@ namespace Content.Server.Players.PlayTimeTracking;
/// </summary>
public sealed class PlayTimeTrackingSystem : EntitySystem
{
[Dependency] private readonly IAdminManager _adminManager = default!;
[Dependency] private readonly IAfkManager _afk = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly MindSystem _minds = default!;
[Dependency] private readonly PlayTimeTrackingManager _tracking = default!;
[Dependency] private readonly IAdminManager _adminManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IServerPreferencesManager _preferencesManager = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
[Dependency] private readonly PlayTimeTrackingManager _tracking = default!;
public override void Initialize()
{
@@ -101,10 +102,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
public IEnumerable<string> GetTimedRoles(EntityUid mindId)
{
var ev = new MindGetAllRolesEvent(new List<RoleInfo>());
RaiseLocalEvent(mindId, ref ev);
foreach (var role in ev.Roles)
foreach (var role in _roles.MindGetAllRoleInfo(mindId))
{
if (string.IsNullOrWhiteSpace(role.PlayTimeTrackerId))
continue;

View File

@@ -10,3 +10,5 @@ public sealed partial class CommandStaffComponent : Component
{
}
//TODO this should probably be on a mind role, not the mob

View File

@@ -4,9 +4,9 @@ using Content.Shared.Roles;
namespace Content.Server.Roles;
/// <summary>
/// Role used to keep track of space dragons for antag purposes.
/// Added to mind role entities to tag that they are a space dragon.
/// </summary>
[RegisterComponent, Access(typeof(DragonSystem)), ExclusiveAntagonist]
public sealed partial class DragonRoleComponent : AntagonistRoleComponent
[RegisterComponent, Access(typeof(DragonSystem))]
public sealed partial class DragonRoleComponent : BaseMindRoleComponent
{
}

View File

@@ -2,8 +2,11 @@ using Content.Shared.Roles;
namespace Content.Server.Roles;
[RegisterComponent, ExclusiveAntagonist]
public sealed partial class InitialInfectedRoleComponent : AntagonistRoleComponent
/// <summary>
/// Added to mind role entities to tag that they are an initial infected.
/// </summary>
[RegisterComponent]
public sealed partial class InitialInfectedRoleComponent : BaseMindRoleComponent
{
}

View File

@@ -30,7 +30,7 @@ public sealed class JobSystem : SharedJobSystem
if (!_mind.TryGetSession(mindId, out var session))
return;
if (!MindTryGetJob(mindId, out _, out var prototype))
if (!MindTryGetJob(mindId, out var prototype))
return;
_chat.DispatchServerMessage(session, Loc.GetString("job-greet-introduce-job-name",
@@ -47,6 +47,6 @@ public sealed class JobSystem : SharedJobSystem
if (MindHasJobWithId(mindId, jobPrototypeId))
return;
_roles.MindAddRole(mindId, new JobComponent { Prototype = jobPrototypeId });
_roles.MindAddJobRole(mindId, null, false, jobPrototypeId);
}
}

View File

@@ -2,7 +2,10 @@ using Content.Shared.Roles;
namespace Content.Server.Roles;
[RegisterComponent, ExclusiveAntagonist]
public sealed partial class NinjaRoleComponent : AntagonistRoleComponent
/// <summary>
/// Added to mind role entities to tag that they are a space ninja.
/// </summary>
[RegisterComponent]
public sealed partial class NinjaRoleComponent : BaseMindRoleComponent
{
}

View File

@@ -3,9 +3,9 @@ using Content.Shared.Roles;
namespace Content.Server.Roles;
/// <summary>
/// Added to mind entities to tag that they are a nuke operative.
/// Added to mind role entities to tag that they are a nuke operative.
/// </summary>
[RegisterComponent, ExclusiveAntagonist]
public sealed partial class NukeopsRoleComponent : AntagonistRoleComponent
[RegisterComponent]
public sealed partial class NukeopsRoleComponent : BaseMindRoleComponent
{
}

View File

@@ -45,7 +45,7 @@ namespace Content.Server.Roles
var roles = _entityManager.System<SharedRoleSystem>();
var jobs = _entityManager.System<SharedJobSystem>();
if (jobs.MindHasJobWithId(mind, args[1]))
roles.MindRemoveRole<JobComponent>(mind.Value);
roles.MindTryRemoveRole<JobRoleComponent>(mind.Value);
}
}
}

View File

@@ -3,10 +3,10 @@ using Content.Shared.Roles;
namespace Content.Server.Roles;
/// <summary>
/// Added to mind entities to tag that they are a Revolutionary.
/// Added to mind role entities to tag that they are a Revolutionary.
/// </summary>
[RegisterComponent, ExclusiveAntagonist]
public sealed partial class RevolutionaryRoleComponent : AntagonistRoleComponent
[RegisterComponent]
public sealed partial class RevolutionaryRoleComponent : BaseMindRoleComponent
{
/// <summary>
/// For headrevs, how many people you have converted.

View File

@@ -1,32 +1,40 @@
using Content.Shared.Mind;
using Content.Shared.Roles;
namespace Content.Server.Roles;
public sealed class RoleSystem : SharedRoleSystem
{
public override void Initialize()
{
// TODO make roles entities
base.Initialize();
SubscribeAntagEvents<DragonRoleComponent>();
SubscribeAntagEvents<InitialInfectedRoleComponent>();
SubscribeAntagEvents<NinjaRoleComponent>();
SubscribeAntagEvents<NukeopsRoleComponent>();
SubscribeAntagEvents<RevolutionaryRoleComponent>();
SubscribeAntagEvents<SubvertedSiliconRoleComponent>();
SubscribeAntagEvents<TraitorRoleComponent>();
SubscribeAntagEvents<ZombieRoleComponent>();
SubscribeAntagEvents<ThiefRoleComponent>();
}
public string? MindGetBriefing(EntityUid? mindId)
{
if (mindId == null)
{
Log.Error($"MingGetBriefing failed for mind {mindId}");
return null;
}
TryComp<MindComponent>(mindId.Value, out var mindComp);
if (mindComp is null)
{
Log.Error($"MingGetBriefing failed for mind {mindId}");
return null;
}
var ev = new GetBriefingEvent();
RaiseLocalEvent(mindId.Value, ref ev);
// This is on the event because while this Entity<T> is also present on every Mind Role Entity's MindRoleComp
// getting to there from a GetBriefing event subscription can be somewhat boilerplate
// and this needs to be looked up for the event anyway so why calculate it again later
ev.Mind = (mindId.Value, mindComp);
// Briefing is no longer raised on the mind entity itself
// because all the components that briefings subscribe to should be on Mind Role Entities
foreach(var role in mindComp.MindRoles)
{
RaiseLocalEvent(role, ref ev);
}
return ev.Briefing;
}
}
@@ -38,8 +46,16 @@ public sealed class RoleSystem : SharedRoleSystem
[ByRefEvent]
public sealed class GetBriefingEvent
{
/// <summary>
/// The text that will be shown on the Character Screen
/// </summary>
public string? Briefing;
/// <summary>
/// The Mind to whose Mind Role Entities the briefing is sent to
/// </summary>
public Entity<MindComponent> Mind;
public GetBriefingEvent(string? briefing = null)
{
Briefing = briefing;

View File

@@ -2,7 +2,10 @@
namespace Content.Server.Roles;
/// <summary>
/// Added to mind role entities to tag that they are a hacked borg.
/// </summary>
[RegisterComponent]
public sealed partial class SubvertedSiliconRoleComponent : AntagonistRoleComponent
public sealed partial class SubvertedSiliconRoleComponent : BaseMindRoleComponent
{
}

View File

@@ -2,7 +2,10 @@ using Content.Shared.Roles;
namespace Content.Server.Roles;
/// <summary>
/// Added to mind role entities to tag that they are a thief.
/// </summary>
[RegisterComponent]
public sealed partial class ThiefRoleComponent : AntagonistRoleComponent
public sealed partial class ThiefRoleComponent : BaseMindRoleComponent
{
}

View File

@@ -2,7 +2,10 @@ using Content.Shared.Roles;
namespace Content.Server.Roles;
[RegisterComponent, ExclusiveAntagonist]
public sealed partial class TraitorRoleComponent : AntagonistRoleComponent
/// <summary>
/// Added to mind role entities to tag that they are a syndicate traitor.
/// </summary>
[RegisterComponent]
public sealed partial class TraitorRoleComponent : BaseMindRoleComponent
{
}

View File

@@ -2,7 +2,10 @@ using Content.Shared.Roles;
namespace Content.Server.Roles;
[RegisterComponent, ExclusiveAntagonist]
public sealed partial class ZombieRoleComponent : AntagonistRoleComponent
/// <summary>
/// Added to mind role entities to tag that they are a zombie.
/// </summary>
[RegisterComponent]
public sealed partial class ZombieRoleComponent : BaseMindRoleComponent
{
}

View File

@@ -124,6 +124,9 @@ public sealed partial class ShuttleConsoleSystem
if (!TryComp(shuttleUid, out ShuttleComponent? shuttleComp))
return;
if (shuttleComp.Enabled == false)
return;
// Check shuttle can even FTL
if (!_shuttle.CanFTL(shuttleUid.Value, out var reason))
{

View File

@@ -244,18 +244,28 @@ public sealed partial class ShuttleSystem
/// </summary>
public bool CanFTL(EntityUid shuttleUid, [NotNullWhen(false)] out string? reason)
{
// Currently in FTL already
if (HasComp<FTLComponent>(shuttleUid))
{
reason = Loc.GetString("shuttle-console-in-ftl");
return false;
}
if (FTLMassLimit > 0 &&
TryComp(shuttleUid, out PhysicsComponent? shuttlePhysics) &&
shuttlePhysics.Mass > FTLMassLimit)
if (TryComp<PhysicsComponent>(shuttleUid, out var shuttlePhysics))
{
reason = Loc.GetString("shuttle-console-mass");
return false;
// Static physics type is set when station anchor is enabled
if (shuttlePhysics.BodyType == BodyType.Static)
{
reason = Loc.GetString("shuttle-console-static");
return false;
}
// Too large to FTL
if (FTLMassLimit > 0 && shuttlePhysics.Mass > FTLMassLimit)
{
reason = Loc.GetString("shuttle-console-mass");
return false;
}
}
if (HasComp<PreventPilotComponent>(shuttleUid))

View File

@@ -28,12 +28,12 @@ namespace Content.Server.Silicons.Laws;
public sealed class SiliconLawSystem : SharedSiliconLawSystem
{
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
/// <inheritdoc/>
public override void Initialize()
@@ -178,10 +178,8 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
if (component.AntagonistRole == null || !_mind.TryGetMind(uid, out var mindId, out _))
return;
if (_roles.MindHasRole<SubvertedSiliconRoleComponent>(mindId))
return;
_roles.MindAddRole(mindId, new SubvertedSiliconRoleComponent { PrototypeId = component.AntagonistRole });
if (!_roles.MindHasRole<SubvertedSiliconRoleComponent>(mindId))
_roles.MindAddRole(mindId, "MindRoleSubvertedSilicon");
}
public SiliconLawset GetLaws(EntityUid uid, SiliconLawBoundComponent? component = null)

View File

@@ -12,10 +12,10 @@ namespace Content.Server.Spawners.EntitySystems;
public sealed class ContainerSpawnPointSystem : EntitySystem
{
[Dependency] private readonly GameTicker _gameTicker = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly ContainerSystem _container = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly StationSpawningSystem _stationSpawning = default!;
@@ -32,7 +32,7 @@ public sealed class ContainerSpawnPointSystem : EntitySystem
// If it's just a spawn pref check if it's for cryo (silly).
if (args.HumanoidCharacterProfile?.SpawnPriority != SpawnPriorityPreference.Cryosleep &&
(!_proto.TryIndex(args.Job?.Prototype, out var jobProto) || jobProto.JobEntity == null))
(!_proto.TryIndex(args.Job, out var jobProto) || jobProto.JobEntity == null))
{
return;
}
@@ -49,7 +49,7 @@ public sealed class ContainerSpawnPointSystem : EntitySystem
if (spawnPoint.SpawnType == SpawnPointType.Unset)
{
// make sure we also check the job here for various reasons.
if (spawnPoint.Job == null || spawnPoint.Job == args.Job?.Prototype)
if (spawnPoint.Job == null || spawnPoint.Job == args.Job)
possibleContainers.Add((uid, spawnPoint, container, xform));
continue;
}
@@ -61,7 +61,7 @@ public sealed class ContainerSpawnPointSystem : EntitySystem
if (_gameTicker.RunLevel != GameRunLevel.InRound &&
spawnPoint.SpawnType == SpawnPointType.Job &&
(args.Job == null || spawnPoint.Job == args.Job.Prototype))
(args.Job == null || spawnPoint.Job == args.Job))
{
possibleContainers.Add((uid, spawnPoint, container, xform));
}

View File

@@ -39,7 +39,7 @@ public sealed class SpawnPointSystem : EntitySystem
if (_gameTicker.RunLevel != GameRunLevel.InRound &&
spawnPoint.SpawnType == SpawnPointType.Job &&
(args.Job == null || spawnPoint.Job == args.Job.Prototype))
(args.Job == null || spawnPoint.Job == args.Job))
{
possiblePositions.Add(xform.Coordinates);
}

View File

@@ -18,13 +18,10 @@ using Content.Shared.Humanoid.Prototypes;
using Content.Shared.PDA;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Preferences.Loadouts.Effects;
using Content.Shared.Random;
using Content.Shared.Random.Helpers;
using Content.Shared.Roles;
using Content.Shared.Roles.Jobs;
using Content.Shared.Station;
using Content.Shared.StatusIcon;
using JetBrains.Annotations;
using Robust.Shared.Configuration;
using Robust.Shared.Map;
@@ -42,19 +39,19 @@ namespace Content.Server.Station.Systems;
[PublicAPI]
public sealed class StationSpawningSystem : SharedStationSpawningSystem
{
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedAccessSystem _accessSystem = default!;
[Dependency] private readonly ActorSystem _actors = default!;
[Dependency] private readonly ArrivalsSystem _arrivalsSystem = default!;
[Dependency] private readonly CP14ExpeditionSystem _CP14expedition = default!;
[Dependency] private readonly IdCardSystem _cardSystem = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly ContainerSpawnPointSystem _containerSpawnPointSystem = default!;
[Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
[Dependency] private readonly IdCardSystem _cardSystem = default!;
[Dependency] private readonly IdentitySystem _identity = default!;
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
[Dependency] private readonly PdaSystem _pdaSystem = default!;
[Dependency] private readonly SharedAccessSystem _accessSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
private bool _randomizeCharacters;
@@ -77,7 +74,7 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
/// <remarks>
/// This only spawns the character, and does none of the mind-related setup you'd need for it to be playable.
/// </remarks>
public EntityUid? SpawnPlayerCharacterOnStation(EntityUid? station, JobComponent? job, HumanoidCharacterProfile? profile, StationSpawningComponent? stationSpawning = null)
public EntityUid? SpawnPlayerCharacterOnStation(EntityUid? station, ProtoId<JobPrototype>? job, HumanoidCharacterProfile? profile, StationSpawningComponent? stationSpawning = null)
{
if (station != null && !Resolve(station.Value, ref stationSpawning))
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
@@ -105,12 +102,12 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
/// <returns>The spawned entity</returns>
public EntityUid SpawnPlayerMob(
EntityCoordinates coordinates,
JobComponent? job,
ProtoId<JobPrototype>? job,
HumanoidCharacterProfile? profile,
EntityUid? station,
EntityUid? entity = null)
{
_prototypeManager.TryIndex(job?.Prototype ?? string.Empty, out var prototype);
_prototypeManager.TryIndex(job ?? string.Empty, out var prototype);
RoleLoadout? loadout = null;
// Need to get the loadout up-front to handle names if we use an entity spawn override.
@@ -204,9 +201,9 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
return entity.Value;
}
private void DoJobSpecials(JobComponent? job, EntityUid entity)
private void DoJobSpecials(ProtoId<JobPrototype>? job, EntityUid entity)
{
if (!_prototypeManager.TryIndex(job?.Prototype ?? string.Empty, out JobPrototype? prototype))
if (!_prototypeManager.TryIndex(job ?? string.Empty, out JobPrototype? prototype))
return;
foreach (var jobSpecial in prototype.Special)
@@ -273,7 +270,7 @@ public sealed class PlayerSpawningEvent : EntityEventArgs
/// <summary>
/// The job to use, if any.
/// </summary>
public readonly JobComponent? Job;
public readonly ProtoId<JobPrototype>? Job;
/// <summary>
/// The profile to use, if any.
/// </summary>
@@ -283,7 +280,7 @@ public sealed class PlayerSpawningEvent : EntityEventArgs
/// </summary>
public readonly EntityUid? Station;
public PlayerSpawningEvent(JobComponent? job, HumanoidCharacterProfile? humanoidCharacterProfile, EntityUid? station)
public PlayerSpawningEvent(ProtoId<JobPrototype>? job, HumanoidCharacterProfile? humanoidCharacterProfile, EntityUid? station)
{
Job = job;
HumanoidCharacterProfile = humanoidCharacterProfile;

View File

@@ -33,16 +33,16 @@ public sealed partial class BuyerAntagCondition : ListingCondition
return true;
var roleSystem = ent.System<SharedRoleSystem>();
var roles = roleSystem.MindGetAllRoles(mindId);
var roles = roleSystem.MindGetAllRoleInfo(mindId);
if (Blacklist != null)
{
foreach (var role in roles)
{
if (role.Component is not AntagonistRoleComponent blacklistantag)
if (!role.Antagonist || string.IsNullOrEmpty(role.Prototype))
continue;
if (blacklistantag.PrototypeId != null && Blacklist.Contains(blacklistantag.PrototypeId))
if (Blacklist.Contains(role.Prototype))
return false;
}
}
@@ -52,10 +52,11 @@ public sealed partial class BuyerAntagCondition : ListingCondition
var found = false;
foreach (var role in roles)
{
if (role.Component is not AntagonistRoleComponent antag)
if (!role.Antagonist || string.IsNullOrEmpty(role.Prototype))
continue;
if (antag.PrototypeId != null && Whitelist.Contains(antag.PrototypeId))
if (Whitelist.Contains(role.Prototype))
found = true;
}
if (!found)

View File

@@ -37,13 +37,13 @@ public sealed partial class BuyerDepartmentCondition : ListingCondition
return true;
var jobs = ent.System<SharedJobSystem>();
jobs.MindTryGetJob(mindId, out var job, out _);
jobs.MindTryGetJob(mindId, out var job);
if (Blacklist != null && job?.Prototype != null)
if (Blacklist != null && job != null)
{
foreach (var department in prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
{
if (department.Roles.Contains(job.Prototype.Value) && Blacklist.Contains(department.ID))
if (department.Roles.Contains(job.ID) && Blacklist.Contains(department.ID))
return false;
}
}
@@ -52,11 +52,11 @@ public sealed partial class BuyerDepartmentCondition : ListingCondition
{
var found = false;
if (job?.Prototype != null)
if (job != null)
{
foreach (var department in prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
{
if (department.Roles.Contains(job.Prototype.Value) && Whitelist.Contains(department.ID))
if (department.Roles.Contains(job.ID) && Whitelist.Contains(department.ID))
{
found = true;
break;

View File

@@ -34,17 +34,17 @@ public sealed partial class BuyerJobCondition : ListingCondition
return true;
var jobs = ent.System<SharedJobSystem>();
jobs.MindTryGetJob(mindId, out var job, out _);
jobs.MindTryGetJob(mindId, out var job);
if (Blacklist != null)
{
if (job?.Prototype != null && Blacklist.Contains(job.Prototype))
if (job is not null && Blacklist.Contains(job.ID))
return false;
}
if (Whitelist != null)
{
if (job?.Prototype == null || !Whitelist.Contains(job.Prototype))
if (job == null || !Whitelist.Contains(job.ID))
return false;
}

View File

@@ -6,6 +6,7 @@ using Content.Shared.Examine;
using Content.Shared.Foldable;
using Content.Shared.Popups;
using Content.Shared.Verbs;
using Content.Shared.Roles;
using Robust.Shared.Audio.Systems;
namespace Content.Server.Thief.Systems;
@@ -18,7 +19,7 @@ public sealed class ThiefBeaconSystem : EntitySystem
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
public override void Initialize()
{
base.Initialize();
@@ -37,7 +38,7 @@ public sealed class ThiefBeaconSystem : EntitySystem
return;
var mind = _mind.GetMind(args.User);
if (!HasComp<ThiefRoleComponent>(mind))
if (mind == null || !_roles.MindHasRole<ThiefRoleComponent>(mind.Value))
return;
var user = args.User;

View File

@@ -11,7 +11,6 @@ using Content.Server.Mind.Commands;
using Content.Server.NPC;
using Content.Server.NPC.HTN;
using Content.Server.NPC.Systems;
using Content.Server.Roles;
using Content.Server.Speech.Components;
using Content.Server.Temperature.Components;
using Content.Shared.CombatMode;
@@ -47,18 +46,18 @@ namespace Content.Server.Zombies;
/// </remarks>
public sealed partial class ZombieSystem
{
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly ServerInventorySystem _inventory = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly IChatManager _chatMan = default!;
[Dependency] private readonly SharedCombatModeSystem _combat = default!;
[Dependency] private readonly NpcFactionSystem _faction = default!;
[Dependency] private readonly NPCSystem _npc = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly HumanoidAppearanceSystem _humanoidAppearance = default!;
[Dependency] private readonly IdentitySystem _identity = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
[Dependency] private readonly SharedCombatModeSystem _combat = default!;
[Dependency] private readonly IChatManager _chatMan = default!;
[Dependency] private readonly ServerInventorySystem _inventory = default!;
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
[Dependency] private readonly NPCSystem _npc = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
/// <summary>
/// Handles an entity turning into a zombie when they die or go into crit
@@ -234,7 +233,7 @@ public sealed partial class ZombieSystem
if (hasMind && _mind.TryGetSession(mindId, out var session))
{
//Zombie role for player manifest
_roles.MindAddRole(mindId, new ZombieRoleComponent { PrototypeId = zombiecomp.ZombieRoleId });
_roles.MindAddRole(mindId, "MindRoleZombie", mind: null, silent: true);
//Greeting message for new bebe zombers
_chatMan.DispatchServerMessage(session, Loc.GetString("zombie-infection-greeting"));

View File

@@ -130,7 +130,7 @@ public sealed class CP14ExpeditionSystem : EntitySystem
var possiblePositions = new List<EntityCoordinates>();
while (points.MoveNext(out var uid, out var spawnPoint, out var xform))
{
if (ev.Job != null && spawnPoint.Job != ev.Job.Prototype)
if (ev.Job != null && spawnPoint.Job != ev.Job.Value)
continue;
if (xform.GridUid != gridUid)

View File

@@ -20,7 +20,12 @@ public sealed partial class IdCardComponent : Component
[DataField]
[AutoNetworkedField]
[Access(typeof(SharedIdCardSystem), typeof(SharedPdaSystem), typeof(SharedAgentIdCardSystem), Other = AccessPermissions.ReadWrite)]
public string? JobTitle;
public LocId? JobTitle;
private string? _jobTitle;
[Access(typeof(SharedIdCardSystem), typeof(SharedPdaSystem), typeof(SharedAgentIdCardSystem), Other = AccessPermissions.ReadWriteExecute)]
public string? LocalizedJobTitle { set => _jobTitle = value; get => _jobTitle ?? Loc.GetString(JobTitle ?? string.Empty); }
/// <summary>
/// The state of the job icon rsi.

View File

@@ -67,7 +67,7 @@ public sealed class IdExaminableSystem : EntitySystem
private string GetNameAndJob(IdCardComponent id)
{
var jobSuffix = string.IsNullOrWhiteSpace(id.JobTitle) ? string.Empty : $" ({id.JobTitle})";
var jobSuffix = string.IsNullOrWhiteSpace(id.LocalizedJobTitle) ? string.Empty : $" ({id.LocalizedJobTitle})";
var val = string.IsNullOrWhiteSpace(id.FullName)
? Loc.GetString(id.NameLocId,

View File

@@ -116,6 +116,7 @@ public abstract class SharedIdCardSystem : EntitySystem
/// </summary>
/// <remarks>
/// If provided with a player's EntityUid to the player parameter, adds the change to the admin logs.
/// Actually works with the LocalizedJobTitle DataField and not with JobTitle.
/// </remarks>
public bool TryChangeJobTitle(EntityUid uid, string? jobTitle, IdCardComponent? id = null, EntityUid? player = null)
{
@@ -134,9 +135,9 @@ public abstract class SharedIdCardSystem : EntitySystem
jobTitle = null;
}
if (id.JobTitle == jobTitle)
if (id.LocalizedJobTitle == jobTitle)
return true;
id.JobTitle = jobTitle;
id.LocalizedJobTitle = jobTitle;
Dirty(uid, id);
UpdateEntityName(uid, id);
@@ -238,7 +239,7 @@ public abstract class SharedIdCardSystem : EntitySystem
if (!Resolve(uid, ref id))
return;
var jobSuffix = string.IsNullOrWhiteSpace(id.JobTitle) ? string.Empty : $" ({id.JobTitle})";
var jobSuffix = string.IsNullOrWhiteSpace(id.LocalizedJobTitle) ? string.Empty : $" ({id.LocalizedJobTitle})";
var val = string.IsNullOrWhiteSpace(id.FullName)
? Loc.GetString(id.NameLocId,
@@ -251,7 +252,7 @@ public abstract class SharedIdCardSystem : EntitySystem
private static string ExtractFullTitle(IdCardComponent idCardComponent)
{
return $"{idCardComponent.FullName} ({CultureInfo.CurrentCulture.TextInfo.ToTitleCase(idCardComponent.JobTitle ?? string.Empty)})"
return $"{idCardComponent.FullName} ({CultureInfo.CurrentCulture.TextInfo.ToTitleCase(idCardComponent.LocalizedJobTitle ?? string.Empty)})"
.Trim();
}
}

View File

@@ -0,0 +1,18 @@
using Content.Shared.Whitelist;
using Robust.Shared.GameStates;
namespace Content.Shared.ChangeNameInContainer;
/// <summary>
/// An entity with this component will get its name and verb chaned to the container it's inside of. E.g, if your a
/// pAI that has this component and are inside a lizard plushie, your name when talking will be "lizard plushie".
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(ChangeNameInContainerSystem))]
public sealed partial class ChangeVoiceInContainerComponent : Component
{
/// <summary>
/// A whitelist of containers that will change the name.
/// </summary>
[DataField]
public EntityWhitelist? Whitelist;
}

View File

@@ -0,0 +1,30 @@
using Content.Shared.Chat;
using Robust.Shared.Containers;
using Content.Shared.Whitelist;
using Content.Shared.Speech;
namespace Content.Shared.ChangeNameInContainer;
public sealed partial class ChangeNameInContainerSystem : EntitySystem
{
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ChangeVoiceInContainerComponent, TransformSpeakerNameEvent>(OnTransformSpeakerName);
}
private void OnTransformSpeakerName(Entity<ChangeVoiceInContainerComponent> ent, ref TransformSpeakerNameEvent args)
{
if (!_container.TryGetContainingContainer((ent, null, null), out var container)
|| _whitelist.IsWhitelistFail(ent.Comp.Whitelist, container.Owner))
return;
args.VoiceName = Name(container.Owner);
if (TryComp<SpeechComponent>(container.Owner, out var speechComp))
args.SpeechVerb = speechComp.SpeechVerb;
}
}

View File

@@ -1,36 +0,0 @@
using System.Numerics;
using Robust.Shared.GameStates;
namespace Content.Shared.Clothing.Components;
/// <summary>
/// Defines something as causing waddling when worn.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class WaddleWhenWornComponent : Component
{
///<summary>
/// How high should they hop during the waddle? Higher hop = more energy.
/// </summary>
[DataField, AutoNetworkedField]
public Vector2 HopIntensity = new(0, 0.25f);
/// <summary>
/// How far should they rock backward and forward during the waddle?
/// Each step will alternate between this being a positive and negative rotation. More rock = more scary.
/// </summary>
[DataField, AutoNetworkedField]
public float TumbleIntensity = 20.0f;
/// <summary>
/// How long should a complete step take? Less time = more chaos.
/// </summary>
[DataField, AutoNetworkedField]
public float AnimationLength = 0.66f;
/// <summary>
/// How much shorter should the animation be when running?
/// </summary>
[DataField, AutoNetworkedField]
public float RunAnimationLengthMultiplier = 0.568f;
}

View File

@@ -1,32 +0,0 @@
using Content.Shared.Clothing;
using Content.Shared.Clothing.Components;
using Content.Shared.Movement.Components;
using Content.Shared.Inventory.Events;
namespace Content.Shared.Clothing.EntitySystems;
public sealed class WaddleClothingSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<WaddleWhenWornComponent, ClothingGotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<WaddleWhenWornComponent, ClothingGotUnequippedEvent>(OnGotUnequipped);
}
private void OnGotEquipped(EntityUid entity, WaddleWhenWornComponent comp, ClothingGotEquippedEvent args)
{
var waddleAnimComp = EnsureComp<WaddleAnimationComponent>(args.Wearer);
waddleAnimComp.AnimationLength = comp.AnimationLength;
waddleAnimComp.HopIntensity = comp.HopIntensity;
waddleAnimComp.RunAnimationLengthMultiplier = comp.RunAnimationLengthMultiplier;
waddleAnimComp.TumbleIntensity = comp.TumbleIntensity;
}
private void OnGotUnequipped(EntityUid entity, WaddleWhenWornComponent comp, ClothingGotUnequippedEvent args)
{
RemComp<WaddleAnimationComponent>(args.Wearer);
}
}

View File

@@ -14,7 +14,6 @@ public sealed partial class MindContainerComponent : Component
/// The mind controlling this mob. Can be null.
/// </summary>
[DataField, AutoNetworkedField]
[Access(typeof(SharedMindSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
public EntityUid? Mind { get; set; }
/// <summary>
@@ -35,7 +34,6 @@ public sealed partial class MindContainerComponent : Component
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("ghostOnShutdown")]
[Access(typeof(SharedMindSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
public bool GhostOnShutdown { get; set; } = true;
}

View File

@@ -1,4 +1,3 @@
using Content.Shared.Actions;
using Content.Shared.GameTicking;
using Content.Shared.Mind.Components;
using Robust.Shared.GameStates;
@@ -87,17 +86,21 @@ public sealed partial class MindComponent : Component
/// <summary>
/// Prevents user from ghosting out
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("preventGhosting")]
[DataField]
public bool PreventGhosting { get; set; }
/// <summary>
/// Prevents user from suiciding
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("preventSuicide")]
[DataField]
public bool PreventSuicide { get; set; }
/// <summary>
/// Mind Role Entities belonging to this Mind
/// </summary>
[DataField, AutoNetworkedField]
public List<EntityUid> MindRoles = new List<EntityUid>();
/// <summary>
/// The session of the player owning this mind.
/// Can be null, in which case the player is currently not logged in.

View File

@@ -1,74 +0,0 @@
using System.Numerics;
using Robust.Shared.Serialization;
namespace Content.Shared.Movement.Components;
/// <summary>
/// Declares that an entity has started to waddle like a duck/clown.
/// </summary>
/// <param name="entity">The newly be-waddled.</param>
[Serializable, NetSerializable]
public sealed class StartedWaddlingEvent(NetEntity entity) : EntityEventArgs
{
public NetEntity Entity = entity;
}
/// <summary>
/// Declares that an entity has stopped waddling like a duck/clown.
/// </summary>
/// <param name="entity">The former waddle-er.</param>
[Serializable, NetSerializable]
public sealed class StoppedWaddlingEvent(NetEntity entity) : EntityEventArgs
{
public NetEntity Entity = entity;
}
/// <summary>
/// Defines something as having a waddle animation when it moves.
/// </summary>
[RegisterComponent, AutoGenerateComponentState]
public sealed partial class WaddleAnimationComponent : Component
{
/// <summary>
/// What's the name of this animation? Make sure it's unique so it can play along side other animations.
/// This prevents someone accidentally causing two identical waddling effects to play on someone at the same time.
/// </summary>
[DataField]
public string KeyName = "Waddle";
///<summary>
/// How high should they hop during the waddle? Higher hop = more energy.
/// </summary>
[DataField, AutoNetworkedField]
public Vector2 HopIntensity = new(0, 0.25f);
/// <summary>
/// How far should they rock backward and forward during the waddle?
/// Each step will alternate between this being a positive and negative rotation. More rock = more scary.
/// </summary>
[DataField, AutoNetworkedField]
public float TumbleIntensity = 20.0f;
/// <summary>
/// How long should a complete step take? Less time = more chaos.
/// </summary>
[DataField, AutoNetworkedField]
public float AnimationLength = 0.66f;
/// <summary>
/// How much shorter should the animation be when running?
/// </summary>
[DataField, AutoNetworkedField]
public float RunAnimationLengthMultiplier = 0.568f;
/// <summary>
/// Stores which step we made last, so if someone cancels out of the animation mid-step then restarts it looks more natural.
/// </summary>
public bool LastStep;
/// <summary>
/// Stores if we're currently waddling so we can start/stop as appropriate and can tell other systems our state.
/// </summary>
[AutoNetworkedField]
public bool IsCurrentlyWaddling;
}

View File

@@ -1,106 +0,0 @@
using Content.Shared.Buckle.Components;
using Content.Shared.Gravity;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Events;
using Content.Shared.Movement.Systems;
using Content.Shared.Standing;
using Content.Shared.Stunnable;
using Robust.Shared.Timing;
namespace Content.Shared.Movement.Systems;
public abstract class SharedWaddleAnimationSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
public override void Initialize()
{
// Startup
SubscribeLocalEvent<WaddleAnimationComponent, ComponentStartup>(OnComponentStartup);
// Start moving possibilities
SubscribeLocalEvent<WaddleAnimationComponent, MoveInputEvent>(OnMovementInput);
SubscribeLocalEvent<WaddleAnimationComponent, StoodEvent>(OnStood);
// Stop moving possibilities
SubscribeLocalEvent((Entity<WaddleAnimationComponent> ent, ref StunnedEvent _) => StopWaddling(ent));
SubscribeLocalEvent((Entity<WaddleAnimationComponent> ent, ref DownedEvent _) => StopWaddling(ent));
SubscribeLocalEvent((Entity<WaddleAnimationComponent> ent, ref BuckledEvent _) => StopWaddling(ent));
SubscribeLocalEvent<WaddleAnimationComponent, GravityChangedEvent>(OnGravityChanged);
}
private void OnGravityChanged(Entity<WaddleAnimationComponent> ent, ref GravityChangedEvent args)
{
if (!args.HasGravity && ent.Comp.IsCurrentlyWaddling)
StopWaddling(ent);
}
private void OnComponentStartup(Entity<WaddleAnimationComponent> entity, ref ComponentStartup args)
{
if (!TryComp<InputMoverComponent>(entity.Owner, out var moverComponent))
return;
// If the waddler is currently moving, make them start waddling
if ((moverComponent.HeldMoveButtons & MoveButtons.AnyDirection) == MoveButtons.AnyDirection)
{
RaiseNetworkEvent(new StartedWaddlingEvent(GetNetEntity(entity.Owner)));
}
}
private void OnMovementInput(Entity<WaddleAnimationComponent> entity, ref MoveInputEvent args)
{
// Prediction mitigation. Prediction means that MoveInputEvents are spammed repeatedly, even though you'd assume
// they're once-only for the user actually doing something. As such do nothing if we're just repeating this FoR.
if (!_timing.IsFirstTimePredicted)
{
return;
}
if (!args.HasDirectionalMovement && entity.Comp.IsCurrentlyWaddling)
{
StopWaddling(entity);
return;
}
// Only start waddling if we're not currently AND we're actually moving.
if (entity.Comp.IsCurrentlyWaddling || !args.HasDirectionalMovement)
return;
entity.Comp.IsCurrentlyWaddling = true;
RaiseNetworkEvent(new StartedWaddlingEvent(GetNetEntity(entity.Owner)));
}
private void OnStood(Entity<WaddleAnimationComponent> entity, ref StoodEvent args)
{
// Prediction mitigation. Prediction means that MoveInputEvents are spammed repeatedly, even though you'd assume
// they're once-only for the user actually doing something. As such do nothing if we're just repeating this FoR.
if (!_timing.IsFirstTimePredicted)
{
return;
}
if (!TryComp<InputMoverComponent>(entity.Owner, out var mover))
{
return;
}
if ((mover.HeldMoveButtons & MoveButtons.AnyDirection) == MoveButtons.None)
return;
if (entity.Comp.IsCurrentlyWaddling)
return;
entity.Comp.IsCurrentlyWaddling = true;
RaiseNetworkEvent(new StartedWaddlingEvent(GetNetEntity(entity.Owner)));
}
private void StopWaddling(Entity<WaddleAnimationComponent> entity)
{
entity.Comp.IsCurrentlyWaddling = false;
RaiseNetworkEvent(new StoppedWaddlingEvent(GetNetEntity(entity.Owner)));
}
}

View File

@@ -1,20 +0,0 @@
using JetBrains.Annotations;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Roles;
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,14 +0,0 @@
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.Roles.Jobs;
/// <summary>
/// Added to mind entities to hold the data for the player's current job.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class JobComponent : Component
{
[DataField(required: true), AutoNetworkedField]
public ProtoId<JobPrototype>? Prototype;
}

View File

@@ -0,0 +1,12 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Roles.Jobs;
/// <summary>
/// Added to mind role entities to mark them as a job role entity.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class JobRoleComponent : BaseMindRoleComponent
{
}

View File

@@ -13,8 +13,10 @@ namespace Content.Shared.Roles.Jobs;
/// </summary>
public abstract class SharedJobSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[Dependency] private readonly SharedPlayerSystem _playerSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
private readonly Dictionary<string, string> _inverseTrackerLookup = new();
public override void Initialize()
@@ -100,32 +102,44 @@ public abstract class SharedJobSystem : EntitySystem
public bool MindHasJobWithId(EntityUid? mindId, string prototypeId)
{
return CompOrNull<JobComponent>(mindId)?.Prototype == prototypeId;
MindRoleComponent? comp = null;
if (mindId is null)
return false;
_roles.MindHasRole<JobRoleComponent>(mindId.Value, out var role);
if (role is null)
return false;
comp = role.Value.Comp;
return (comp.JobPrototype == prototypeId);
}
public bool MindTryGetJob(
[NotNullWhen(true)] EntityUid? mindId,
[NotNullWhen(true)] out JobComponent? comp,
[NotNullWhen(true)] out JobPrototype? prototype)
{
comp = null;
prototype = null;
MindTryGetJobId(mindId, out var protoId);
return TryComp(mindId, out comp) &&
comp.Prototype != null &&
_prototypes.TryIndex(comp.Prototype, out prototype);
return (_prototypes.TryIndex<JobPrototype>(protoId, out prototype) || prototype is not null);
}
public bool MindTryGetJobId([NotNullWhen(true)] EntityUid? mindId, out ProtoId<JobPrototype>? job)
public bool MindTryGetJobId(
[NotNullWhen(true)] EntityUid? mindId,
out ProtoId<JobPrototype>? job)
{
if (!TryComp(mindId, out JobComponent? comp))
{
job = null;
return false;
}
job = null;
job = comp.Prototype;
return true;
if (mindId is null)
return false;
if (_roles.MindHasRole<JobRoleComponent>(mindId.Value, out var role))
job = role.Value.Comp.JobPrototype;
return (job is not null);
}
/// <summary>
@@ -134,7 +148,7 @@ public abstract class SharedJobSystem : EntitySystem
/// </summary>
public bool MindTryGetJobName([NotNullWhen(true)] EntityUid? mindId, out string name)
{
if (MindTryGetJob(mindId, out _, out var prototype))
if (MindTryGetJob(mindId, out var prototype))
{
name = prototype.LocalizedName;
return true;
@@ -161,7 +175,7 @@ public abstract class SharedJobSystem : EntitySystem
if (_playerSystem.ContentData(player) is not { Mind: { } mindId })
return true;
if (!MindTryGetJob(mindId, out _, out var prototype))
if (!MindTryGetJob(mindId, out var prototype))
return true;
return prototype.CanBeAntag;

View File

@@ -7,7 +7,7 @@ namespace Content.Shared.Roles;
/// </summary>
/// <param name="Roles">The list of roles on the player.</param>
[ByRefEvent]
public readonly record struct MindGetAllRolesEvent(List<RoleInfo> Roles);
public readonly record struct MindGetAllRoleInfoEvent(List<RoleInfo> Roles);
/// <summary>
/// Returned by <see cref="MindGetAllRolesEvent"/> to give some information about a player's role.
@@ -17,4 +17,4 @@ public readonly record struct MindGetAllRolesEvent(List<RoleInfo> Roles);
/// <param name="Antagonist">Whether or not this role makes this player an antagonist.</param>
/// <param name="PlayTimeTrackerId">The <see cref="PlayTimeTrackerPrototype"/> id associated with the role.</param>
/// <param name="Prototype">The prototype ID of the role</param>
public readonly record struct RoleInfo(Component Component, string Name, bool Antagonist, string? PlayTimeTrackerId, string Prototype);
public readonly record struct RoleInfo(string Name, bool Antagonist, string? PlayTimeTrackerId, string Prototype);

View File

@@ -0,0 +1,48 @@
using Content.Shared.Mind;
using JetBrains.Annotations;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.Roles;
/// <summary>
/// This holds data for, and indicates, a Mind Role entity
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class MindRoleComponent : BaseMindRoleComponent
{
/// <summary>
/// Marks this Mind Role as Antagonist
/// A single antag Mind Role is enough to make the owner mind count as Antagonist.
/// </summary>
[DataField]
public bool Antag { get; set; } = false;
/// <summary>
/// True if this mindrole is an exclusive antagonist. Antag setting is not checked if this is True.
/// </summary>
[DataField]
public bool ExclusiveAntag { get; set; } = false;
/// <summary>
/// The Mind that this role belongs to
/// </summary>
public Entity<MindComponent> Mind { get; set; }
/// <summary>
/// The Antagonist prototype of this role
/// </summary>
[DataField]
public ProtoId<AntagPrototype>? AntagPrototype { get; set; }
/// <summary>
/// The Job prototype of this role
/// </summary>
[DataField]
public ProtoId<JobPrototype>? JobPrototype { get; set; }
}
public abstract partial class BaseMindRoleComponent : Component
{
}

View File

@@ -1,34 +1,31 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Administration.Logs;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.Ghost.Roles;
using Content.Shared.GameTicking;
using Content.Shared.Mind;
using Content.Shared.Roles.Jobs;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared.Roles;
public abstract class SharedRoleSystem : EntitySystem
{
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedMindSystem _minds = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
// TODO please lord make role entities
private readonly HashSet<Type> _antagTypes = new();
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly SharedGameTicker _gameTicker = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
private JobRequirementOverridePrototype? _requirementOverride;
public override void Initialize()
{
// TODO make roles entities
SubscribeLocalEvent<JobComponent, MindGetAllRolesEvent>(OnJobGetAllRoles);
Subs.CVar(_cfg, CCVars.GameRoleTimerOverride, SetRequirementOverride, true);
}
@@ -44,124 +41,117 @@ public abstract class SharedRoleSystem : EntitySystem
Log.Error($"Unknown JobRequirementOverridePrototype: {value}");
}
private void OnJobGetAllRoles(EntityUid uid, JobComponent component, ref MindGetAllRolesEvent args)
{
var name = "game-ticker-unknown-role";
var prototype = "";
string? playTimeTracker = null;
if (component.Prototype != null && _prototypes.TryIndex(component.Prototype, out JobPrototype? job))
{
name = job.Name;
prototype = job.ID;
playTimeTracker = job.PlayTimeTracker;
}
name = Loc.GetString(name);
args.Roles.Add(new RoleInfo(component, name, false, playTimeTracker, prototype));
}
protected void SubscribeAntagEvents<T>() where T : AntagonistRoleComponent
{
SubscribeLocalEvent((EntityUid _, T component, ref MindGetAllRolesEvent args) =>
{
var name = "game-ticker-unknown-role";
var prototype = "";
if (component.PrototypeId != null && _prototypes.TryIndex(component.PrototypeId, out AntagPrototype? antag))
{
name = antag.Name;
prototype = antag.ID;
}
name = Loc.GetString(name);
args.Roles.Add(new RoleInfo(component, name, true, null, prototype));
});
SubscribeLocalEvent((EntityUid _, T _, ref MindIsAntagonistEvent args) => { args.IsAntagonist = true; args.IsExclusiveAntagonist |= typeof(T).TryGetCustomAttribute<ExclusiveAntagonistAttribute>(out _); });
_antagTypes.Add(typeof(T));
}
public void MindAddRoles(EntityUid mindId, ComponentRegistry components, MindComponent? mind = null, bool silent = false)
{
if (!Resolve(mindId, ref mind))
return;
EntityManager.AddComponents(mindId, components);
var antagonist = false;
foreach (var compReg in components.Values)
{
var compType = compReg.Component.GetType();
var comp = EntityManager.ComponentFactory.GetComponent(compType);
if (IsAntagonistRole(comp.GetType()))
{
antagonist = true;
break;
}
}
var mindEv = new MindRoleAddedEvent(silent);
RaiseLocalEvent(mindId, ref mindEv);
var message = new RoleAddedEvent(mindId, mind, antagonist, silent);
if (mind.OwnedEntity != null)
{
RaiseLocalEvent(mind.OwnedEntity.Value, message, true);
}
_adminLogger.Add(LogType.Mind, LogImpact.Low,
$"Role components {string.Join(components.Keys.ToString(), ", ")} added to mind of {_minds.MindOwnerLoggingString(mind)}");
}
public void MindAddRole(EntityUid mindId, Component component, MindComponent? mind = null, bool silent = false)
{
if (!Resolve(mindId, ref mind))
return;
if (HasComp(mindId, component.GetType()))
{
throw new ArgumentException($"We already have this role: {component}");
}
EntityManager.AddComponent(mindId, component);
var antagonist = IsAntagonistRole(component.GetType());
var mindEv = new MindRoleAddedEvent(silent);
RaiseLocalEvent(mindId, ref mindEv);
var message = new RoleAddedEvent(mindId, mind, antagonist, silent);
if (mind.OwnedEntity != null)
{
RaiseLocalEvent(mind.OwnedEntity.Value, message, true);
}
_adminLogger.Add(LogType.Mind, LogImpact.Low,
$"'Role {component}' added to mind of {_minds.MindOwnerLoggingString(mind)}");
}
/// <summary>
/// Gives this mind a new role.
/// Adds multiple mind roles to a mind
/// </summary>
/// <param name="mindId">The mind to add the role to.</param>
/// <param name="component">The role instance to add.</param>
/// <typeparam name="T">The role type to add.</typeparam>
/// <param name="silent">Whether or not the role should be added silently</param>
/// <returns>The instance of the role.</returns>
/// <exception cref="ArgumentException">
/// Thrown if we already have a role with this type.
/// </exception>
public void MindAddRole<T>(EntityUid mindId, T component, MindComponent? mind = null, bool silent = false) where T : IComponent, new()
/// <param name="mindId">The mind entity to add the role to</param>
/// <param name="roles">The list of mind roles to add</param>
/// <param name="mind">If the mind component is provided, it will be checked if it belongs to the mind entity</param>
/// <param name="silent">If true, no briefing will be generated upon receiving the mind role</param>
public void MindAddRoles(EntityUid mindId,
List<ProtoId<EntityPrototype>>? roles,
MindComponent? mind = null,
bool silent = false)
{
if (!Resolve(mindId, ref mind))
if (roles is null || roles.Count == 0)
return;
if (HasComp<T>(mindId))
foreach (var proto in roles)
{
throw new ArgumentException($"We already have this role: {typeof(T)}");
MindAddRole(mindId, proto, mind, silent);
}
}
/// <summary>
/// Adds a mind role to a mind
/// </summary>
/// <param name="mindId">The mind entity to add the role to</param>
/// <param name="protoId">The mind role to add</param>
/// <param name="mind">If the mind component is provided, it will be checked if it belongs to the mind entity</param>
/// <param name="silent">If true, no briefing will be generated upon receiving the mind role</param>
public void MindAddRole(EntityUid mindId,
ProtoId<EntityPrototype> protoId,
MindComponent? mind = null,
bool silent = false)
{
if (protoId == "MindRoleJob")
MindAddJobRole(mindId, mind, silent, "");
else
MindAddRoleDo(mindId, protoId, mind, silent);
}
/// <summary>
/// Adds a Job mind role with the specified job prototype
/// </summary>
/// /// <param name="mindId">The mind entity to add the job role to</param>
/// <param name="mind">If the mind component is provided, it will be checked if it belongs to the mind entity</param>
/// <param name="silent">If true, no briefing will be generated upon receiving the mind role</param>
/// <param name="jobPrototype">The Job prototype for the new role</param>
public void MindAddJobRole(EntityUid mindId,
MindComponent? mind = null,
bool silent = false,
string? jobPrototype = null)
{
// Can't have someone get paid for two jobs now, can we
if (MindHasRole<JobRoleComponent>(mindId, out var jobRole)
&& jobRole.Value.Comp.JobPrototype != jobPrototype)
{
Resolve(mindId, ref mind);
if (mind is not null)
{
_adminLogger.Add(LogType.Mind,
LogImpact.Low,
$"Job Role of {ToPrettyString(mind.OwnedEntity)} changed from '{jobRole.Value.Comp.JobPrototype}' to '{jobPrototype}'");
}
jobRole.Value.Comp.JobPrototype = jobPrototype;
}
else
MindAddRoleDo(mindId, "MindRoleJob", mind, silent, jobPrototype);
}
/// <summary>
/// Creates a Mind Role
/// </summary>
private void MindAddRoleDo(EntityUid mindId,
ProtoId<EntityPrototype> protoId,
MindComponent? mind = null,
bool silent = false,
string? jobPrototype = null)
{
if (!Resolve(mindId, ref mind))
{
Log.Error($"Failed to add role {protoId} to mind {mindId} : Mind does not match provided mind component");
return;
}
AddComp(mindId, component);
var antagonist = IsAntagonistRole<T>();
var antagonist = false;
if (!_prototypes.TryIndex(protoId, out var protoEnt))
{
Log.Error($"Failed to add role {protoId} to mind {mindId} : Role prototype does not exist");
return;
}
//TODO don't let a prototype being added a second time
//If that was somehow to occur, a second mindrole for that comp would be created
//Meaning any mind role checks could return wrong results, since they just return the first match they find
var mindRoleId = Spawn(protoId, MapCoordinates.Nullspace);
EnsureComp<MindRoleComponent>(mindRoleId);
var mindRoleComp = Comp<MindRoleComponent>(mindRoleId);
mindRoleComp.Mind = (mindId,mind);
if (jobPrototype is not null)
{
mindRoleComp.JobPrototype = jobPrototype;
EnsureComp<JobRoleComponent>(mindRoleId);
}
if (mindRoleComp.Antag || mindRoleComp.ExclusiveAntag)
antagonist = true;
mind.MindRoles.Add(mindRoleId);
var mindEv = new MindRoleAddedEvent(silent);
RaiseLocalEvent(mindId, ref mindEv);
@@ -172,94 +162,336 @@ public abstract class SharedRoleSystem : EntitySystem
RaiseLocalEvent(mind.OwnedEntity.Value, message, true);
}
_adminLogger.Add(LogType.Mind, LogImpact.Low,
$"'Role {typeof(T).Name}' added to mind of {_minds.MindOwnerLoggingString(mind)}");
var name = Loc.GetString(protoEnt.Name);
if (mind.OwnedEntity is not null)
{
_adminLogger.Add(LogType.Mind,
LogImpact.Low,
$"{name} added to mind of {ToPrettyString(mind.OwnedEntity)}");
}
else
{
//TODO: This is not tied to the player on the Admin Log filters.
//Probably only happens when Job Role is added on initial spawn, before the mind entity is put in a mob
_adminLogger.Add(LogType.Mind,
LogImpact.Low,
$"{name} added to {ToPrettyString(mindId)}");
}
}
/// <summary>
/// Removes a role from this mind.
/// Removes all instances of a specific role from this mind.
/// </summary>
/// <param name="mindId">The mind to remove the role from.</param>
/// <typeparam name="T">The type of the role to remove.</typeparam>
/// <exception cref="ArgumentException">
/// Thrown if we do not have this role.
/// </exception>
public void MindRemoveRole<T>(EntityUid mindId) where T : IComponent
/// <exception cref="ArgumentException">Thrown if the mind does not exist or does not have this role.</exception>
/// <returns>Returns False if there was something wrong with the mind or the removal. True if successful</returns>>
public bool MindRemoveRole<T>(EntityUid mindId) where T : IComponent
{
if (!RemComp<T>(mindId))
if (!TryComp<MindComponent>(mindId, out var mind) )
throw new ArgumentException($"{mindId} does not exist or does not have mind component");
var found = false;
var antagonist = false;
var delete = new List<EntityUid>();
foreach (var role in mind.MindRoles)
{
throw new ArgumentException($"We do not have this role: {typeof(T)}");
if (!HasComp<T>(role))
continue;
var roleComp = Comp<MindRoleComponent>(role);
antagonist = roleComp.Antag;
_entityManager.DeleteEntity(role);
delete.Add(role);
found = true;
}
foreach (var role in delete)
{
mind.MindRoles.Remove(role);
}
if (!found)
{
throw new ArgumentException($"{mindId} does not have this role: {typeof(T)}");
}
var mind = Comp<MindComponent>(mindId);
var antagonist = IsAntagonistRole<T>();
var message = new RoleRemovedEvent(mindId, mind, antagonist);
if (mind.OwnedEntity != null)
{
RaiseLocalEvent(mind.OwnedEntity.Value, message, true);
}
_adminLogger.Add(LogType.Mind, LogImpact.Low,
$"'Role {typeof(T).Name}' removed from mind of {_minds.MindOwnerLoggingString(mind)}");
}
public bool MindTryRemoveRole<T>(EntityUid mindId) where T : IComponent
{
if (!MindHasRole<T>(mindId))
return false;
MindRemoveRole<T>(mindId);
_adminLogger.Add(LogType.Mind,
LogImpact.Low,
$"'Role {typeof(T).Name}' removed from mind of {ToPrettyString(mind.OwnedEntity)}");
return true;
}
public bool MindHasRole<T>(EntityUid mindId) where T : IComponent
/// <summary>
/// Finds and removes all mind roles of a specific type
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <typeparam name="T">The type of the role to remove.</typeparam>
/// <returns>True if the role existed and was removed</returns>
public bool MindTryRemoveRole<T>(EntityUid mindId) where T : IComponent
{
DebugTools.Assert(HasComp<MindComponent>(mindId));
return HasComp<T>(mindId);
}
if (!MindHasRole<T>(mindId))
{
Log.Warning($"Failed to remove role {typeof(T)} from {mindId} : mind does not have role ");
return false;
}
public List<RoleInfo> MindGetAllRoles(EntityUid mindId)
{
DebugTools.Assert(HasComp<MindComponent>(mindId));
var ev = new MindGetAllRolesEvent(new List<RoleInfo>());
RaiseLocalEvent(mindId, ref ev);
return ev.Roles;
}
public bool MindIsAntagonist(EntityUid? mindId)
{
if (mindId == null)
if (typeof(T) == typeof(MindRoleComponent))
return false;
DebugTools.Assert(HasComp<MindComponent>(mindId));
var ev = new MindIsAntagonistEvent();
RaiseLocalEvent(mindId.Value, ref ev);
return ev.IsAntagonist;
return MindRemoveRole<T>(mindId);
}
/// <summary>
/// Finds the first mind role of a specific T type on a mind entity.
/// Outputs entity components for the mind role's MindRoleComponent and for T
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <typeparam name="T">The type of the role to find.</typeparam>
/// <param name="role">The Mind Role entity component</param>
/// <param name="roleT">The Mind Role's entity component for T</param>
/// <returns>True if the role is found</returns>
public bool MindHasRole<T>(EntityUid mindId,
[NotNullWhen(true)] out Entity<MindRoleComponent>? role,
[NotNullWhen(true)] out Entity<T>? roleT) where T : IComponent
{
role = null;
roleT = null;
if (!TryComp<MindComponent>(mindId, out var mind))
return false;
var found = false;
foreach (var roleEnt in mind.MindRoles)
{
if (!HasComp<T>(roleEnt))
continue;
role = (roleEnt,Comp<MindRoleComponent>(roleEnt));
roleT = (roleEnt,Comp<T>(roleEnt));
found = true;
break;
}
return found;
}
/// <summary>
/// Finds the first mind role of a specific type on a mind entity.
/// Outputs an entity component for the mind role's MindRoleComponent
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <param name="type">The Type to look for</param>
/// <param name="role">The output role</param>
/// <returns>True if the role is found</returns>
public bool MindHasRole(EntityUid mindId,
Type type,
[NotNullWhen(true)] out Entity<MindRoleComponent>? role)
{
role = null;
// All MindRoles have this component, it would just return the first one.
// Order might not be what is expected.
// Better to report null
if (type == Type.GetType("MindRoleComponent"))
{
Log.Error($"Something attempted to query mind role 'MindRoleComponent' on mind {mindId}. This component is present on every single mind role.");
return false;
}
if (!TryComp<MindComponent>(mindId, out var mind))
return false;
var found = false;
foreach (var roleEnt in mind.MindRoles)
{
if (!HasComp(roleEnt, type))
continue;
role = (roleEnt,Comp<MindRoleComponent>(roleEnt));
found = true;
break;
}
return found;
}
/// <summary>
/// Finds the first mind role of a specific type on a mind entity.
/// Outputs an entity component for the mind role's MindRoleComponent
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <param name="role">The Mind Role entity component</param>
/// <typeparam name="T">The type of the role to find.</typeparam>
/// <returns>True if the role is found</returns>
public bool MindHasRole<T>(EntityUid mindId,
[NotNullWhen(true)] out Entity<MindRoleComponent>? role) where T : IComponent
{
return MindHasRole<T>(mindId, out role, out _);
}
/// <summary>
/// Finds the first mind role of a specific type on a mind entity.
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <typeparam name="T">The type of the role to find.</typeparam>
/// <returns>True if the role is found</returns>
public bool MindHasRole<T>(EntityUid mindId) where T : IComponent
{
return MindHasRole<T>(mindId, out _, out _);
}
//TODO: Delete this later
/// <summary>
/// Returns the first mind role of a specific type
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <returns>Entity Component of the mind role</returns>
[Obsolete("Use MindHasRole's output value")]
public Entity<MindRoleComponent>? MindGetRole<T>(EntityUid mindId) where T : IComponent
{
Entity<MindRoleComponent>? result = null;
var mind = Comp<MindComponent>(mindId);
foreach (var uid in mind.MindRoles)
{
if (HasComp<T>(uid) && TryComp<MindRoleComponent>(uid, out var comp))
result = (uid,comp);
}
return result;
}
/// <summary>
/// Reads all Roles of a mind Entity and returns their data as RoleInfo
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <returns>RoleInfo list</returns>
public List<RoleInfo> MindGetAllRoleInfo(EntityUid mindId)
{
var roleInfo = new List<RoleInfo>();
if (!TryComp<MindComponent>(mindId, out var mind))
return roleInfo;
foreach (var role in mind.MindRoles)
{
var valid = false;
var name = "game-ticker-unknown-role";
var prototype = "";
string? playTimeTracker = null;
var comp = Comp<MindRoleComponent>(role);
if (comp.AntagPrototype is not null)
{
prototype = comp.AntagPrototype;
}
if (comp.JobPrototype is not null && comp.AntagPrototype is null)
{
prototype = comp.JobPrototype;
if (_prototypes.TryIndex(comp.JobPrototype, out var job))
{
playTimeTracker = job.PlayTimeTracker;
name = job.Name;
valid = true;
}
else
{
Log.Error($" Mind Role Prototype '{role.Id}' contains invalid Job prototype: '{comp.JobPrototype}'");
}
}
else if (comp.AntagPrototype is not null && comp.JobPrototype is null)
{
prototype = comp.AntagPrototype;
if (_prototypes.TryIndex(comp.AntagPrototype, out var antag))
{
name = antag.Name;
valid = true;
}
else
{
Log.Error($" Mind Role Prototype '{role.Id}' contains invalid Antagonist prototype: '{comp.AntagPrototype}'");
}
}
else if (comp.JobPrototype is not null && comp.AntagPrototype is not null)
{
Log.Error($" Mind Role Prototype '{role.Id}' contains both Job and Antagonist prototypes");
}
if (valid)
roleInfo.Add(new RoleInfo(name, comp.Antag || comp.ExclusiveAntag , playTimeTracker, prototype));
}
return roleInfo;
}
/// <summary>
/// Does this mind possess an antagonist role
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <returns>True if the mind possesses any antag roles</returns>
public bool MindIsAntagonist(EntityUid? mindId)
{
if (mindId is null)
{
Log.Warning($"Antagonist status of mind entity {mindId} could not be determined - mind entity not found");
return false;
}
return CheckAntagonistStatus(mindId.Value).Item1;
}
/// <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>
/// <returns>True if the mind possesses any exclusive antag roles</returns>
public bool MindIsExclusiveAntagonist(EntityUid? mindId)
{
if (mindId == null)
if (mindId is null)
{
Log.Warning($"Antagonist status of mind entity {mindId} could not be determined - mind entity not found");
return false;
}
var ev = new MindIsAntagonistEvent();
RaiseLocalEvent(mindId.Value, ref ev);
return ev.IsExclusiveAntagonist;
return CheckAntagonistStatus(mindId.Value).Item2;
}
public bool IsAntagonistRole<T>()
{
return _antagTypes.Contains(typeof(T));
}
private (bool, bool) CheckAntagonistStatus(EntityUid mindId)
{
if (!TryComp<MindComponent>(mindId, out var mind))
{
Log.Warning($"Antagonist status of mind entity {mindId} could not be determined - mind component not found");
return (false, false);
}
public bool IsAntagonistRole(Type component)
{
return _antagTypes.Contains(component);
var antagonist = false;
var exclusiveAntag = false;
foreach (var role in mind.MindRoles)
{
if (!TryComp<MindRoleComponent>(role, out var roleComp))
{
//If this ever shows up outside of an integration test, then we need to look into this further.
Log.Warning($"Mind Role Entity {role} does not have MindRoleComponent!");
continue;
}
if (roleComp.Antag || exclusiveAntag)
antagonist = true;
if (roleComp.ExclusiveAntag)
exclusiveAntag = true;
}
return (antagonist, exclusiveAntag);
}
/// <summary>

View File

@@ -8,6 +8,7 @@ using Robust.Shared.GameStates;
using Content.Shared.DoAfter;
using Robust.Shared.Serialization;
using Robust.Shared.Audio;
using Content.Shared.Whitelist;
namespace Content.Shared.Storage.Components
{
@@ -26,6 +27,12 @@ namespace Content.Shared.Storage.Components
[DataField("maxItemSize")]
public ProtoId<ItemSizePrototype> MaxItemSize = "Small";
/// <summary>
/// Entity blacklist for secret stashes.
/// </summary>
[DataField]
public EntityWhitelist? Blacklist;
/// <summary>
/// This sound will be played when you try to insert an item in the stash.
/// The sound will be played whether or not the item is actually inserted.

View File

@@ -13,6 +13,7 @@ using Robust.Shared.Audio.Systems;
using Content.Shared.Verbs;
using Content.Shared.IdentityManagement;
using Content.Shared.Tools.EntitySystems;
using Content.Shared.Whitelist;
namespace Content.Shared.Storage.EntitySystems;
@@ -27,7 +28,7 @@ public sealed class SecretStashSystem : EntitySystem
[Dependency] private readonly SharedItemSystem _item = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly ToolOpenableSystem _toolOpenableSystem = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
public override void Initialize()
{
@@ -90,8 +91,9 @@ public sealed class SecretStashSystem : EntitySystem
return false;
}
// check if item is too big to fit into secret stash
if (_item.GetSizePrototype(itemComp.Size) > _item.GetSizePrototype(entity.Comp.MaxItemSize))
// check if item is too big to fit into secret stash or is in the blacklist
if (_item.GetSizePrototype(itemComp.Size) > _item.GetSizePrototype(entity.Comp.MaxItemSize) ||
_whitelistSystem.IsBlacklistPass(entity.Comp.Blacklist, itemToHideUid))
{
var msg = Loc.GetString("comp-secret-stash-action-hide-item-too-big",
("item", itemToHideUid), ("stashname", GetStashName(entity)));

View File

@@ -32,6 +32,12 @@ public sealed partial class EntityWhitelist
[DataField] public string[]? Components;
// TODO yaml validation
/// <summary>
/// Mind Role Prototype names that are allowed in the whitelist.
/// </summary>
[DataField] public string[]? MindRoles;
// TODO yaml validation
/// <summary>
/// Item sizes that are allowed in the whitelist.
/// </summary>

View File

@@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Item;
using Content.Shared.Roles;
using Content.Shared.Tag;
namespace Content.Shared.Whitelist;
@@ -7,6 +8,7 @@ namespace Content.Shared.Whitelist;
public sealed class EntityWhitelistSystem : EntitySystem
{
[Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
[Dependency] private readonly TagSystem _tag = default!;
private EntityQuery<ItemComponent> _itemQuery;
@@ -46,9 +48,30 @@ public sealed class EntityWhitelistSystem : EntitySystem
public bool IsValid(EntityWhitelist list, EntityUid uid)
{
if (list.Components != null)
EnsureRegistrations(list);
{
var regs = StringsToRegs(list.Components);
if (list.Registrations != null)
list.Registrations ??= new List<ComponentRegistration>();
list.Registrations.AddRange(regs);
}
if (list.MindRoles != null)
{
var regs = StringsToRegs(list.MindRoles);
foreach (var role in regs)
{
if ( _roles.MindHasRole(uid, role.Type, out _))
{
if (!list.RequireAll)
return true;
}
else if (list.RequireAll)
return false;
}
}
if (list.Registrations != null && list.Registrations.Count > 0)
{
foreach (var reg in list.Registrations)
{
@@ -153,7 +176,7 @@ public sealed class EntityWhitelistSystem : EntitySystem
return IsWhitelistPassOrNull(blacklist, uid);
}
/// <summary>
/// <summary>
/// Helper function to determine if Blacklist is either null or the entity is not on the list
/// Duplicate of equivalent Whitelist function
/// </summary>
@@ -162,24 +185,27 @@ public sealed class EntityWhitelistSystem : EntitySystem
return IsWhitelistFailOrNull(blacklist, uid);
}
private void EnsureRegistrations(EntityWhitelist list)
private List<ComponentRegistration> StringsToRegs(string[]? input)
{
if (list.Components == null)
return;
var list = new List<ComponentRegistration>();
list.Registrations = new List<ComponentRegistration>();
foreach (var name in list.Components)
if (input == null || input.Length == 0)
return list;
foreach (var name in input)
{
var availability = _factory.GetComponentAvailability(name);
if (_factory.TryGetRegistration(name, out var registration)
&& availability == ComponentAvailability.Available)
{
list.Registrations.Add(registration);
list.Add(registration);
}
else if (availability == ComponentAvailability.Unknown)
{
Log.Warning($"Unknown component name {name} passed to EntityWhitelist!");
Log.Error($"StringsToRegs failed: Unknown component name {name} passed to EntityWhitelist!");
}
}
return list;
}
}

View File

@@ -552,5 +552,12 @@ Entries:
id: 68
time: '2024-09-23T07:28:42.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32395
- author: SlamBamActionman
changes:
- message: Added admin logging for generated codewords.
type: Add
id: 69
time: '2024-10-09T11:55:49.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32531
Name: Admin
Order: 1

View File

@@ -1,126 +1,4 @@
Entries:
- author: Moomoobeef
changes:
- message: Some radio channel colors have been tweaked in order to be more easily
distinguishable.
type: Tweak
id: 6984
time: '2024-07-26T06:47:20.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/30133
- author: Errant
changes:
- message: Replay ghosts now actually spawn on the proper station, take two.
type: Fix
id: 6985
time: '2024-07-26T12:59:43.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/30273
- author: themias
changes:
- message: Arcade machines are functional again
type: Fix
id: 6986
time: '2024-07-26T17:30:50.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/30376
- author: themias
changes:
- message: Zombies now get uncuffed upon transformation
type: Fix
id: 6987
time: '2024-07-26T18:48:03.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/30321
- author: metalgearsloth
changes:
- message: Fix grid labels getting spammed from VGRoid.
type: Fix
id: 6988
time: '2024-07-27T01:54:38.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/29946
- author: GoldenCan
changes:
- message: Added a Security Clown Mask which is obtainable by hacking a SecDrobe.
type: Add
id: 6989
time: '2024-07-27T04:09:24.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/30249
- author: Plykiya
changes:
- message: Thief game rule now properly selects more than one thief.
type: Fix
id: 6990
time: '2024-07-27T07:27:21.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/30393
- author: BombasterDS
changes:
- message: Added new plant mutations for apple, sugarcane and galaxythistle
type: Add
id: 6991
time: '2024-07-27T15:08:49.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/28993
- author: Spessmann
changes:
- message: Thief objectives for figurines and stamps now require less items
type: Tweak
id: 6992
time: '2024-07-27T23:11:27.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/30390
- author: metalgearsloth
changes:
- message: Moved VGRoid from 1,000m away to ~500m.
type: Tweak
id: 6993
time: '2024-07-28T03:14:18.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/29943
- author: lzk228
changes:
- message: Fixed pancakes stacks. Before it, splitting not default pancakes stacks
would give you default pancakes.
type: Fix
id: 6994
time: '2024-07-28T03:49:06.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/30270
- author: Plykiya
changes:
- message: Fixed the client mispredicting people slipping with their magboots turned
on
type: Fix
id: 6995
time: '2024-07-28T06:17:06.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/30425
- author: Katzenminer
changes:
- message: Pun and similar pets are no longer firemune
type: Fix
id: 6996
time: '2024-07-28T08:32:27.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/30424
- author: lzk228
changes:
- message: Fixed permanent absence of the approver string in cargo invoice.
type: Fix
id: 6997
time: '2024-07-29T06:19:43.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/29690
- author: JIPDawg
changes:
- message: F9 is correctly bound to the Round End Summary window by default now.
type: Fix
id: 6998
time: '2024-07-29T06:49:28.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/30438
- author: githubuser508
changes:
- message: Candles crate and the ability for Cargo to order it.
type: Add
id: 6999
time: '2024-07-29T08:29:27.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/29736
- author: Blackern5000
changes:
- message: Emergency oxygen and fire lockers now generally contain more supplies
type: Tweak
id: 7000
time: '2024-07-29T09:57:04.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/29230
- author: Moomoobeef
changes:
- message: Added the ability to wear lizard plushies on your head!
@@ -3937,3 +3815,140 @@
id: 7483
time: '2024-10-03T14:01:01.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32552
- author: Southbridge
changes:
- message: Box station's recycler has been properly connected.
type: Fix
- message: Box station's singlo substation has been rewired to the station-wide
HV network.
type: Tweak
- message: Box station now has AI law boards in the upload room.
type: Add
id: 7484
time: '2024-10-04T01:12:59.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32608
- author: Plykiya
changes:
- message: You can no longer FTL the station.
type: Fix
id: 7485
time: '2024-10-04T02:55:36.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32558
- author: SoulFN
changes:
- message: Dragon can now pull things using tail.
type: Add
id: 7486
time: '2024-10-04T05:39:50.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32568
- author: SaphireLattice
changes:
- message: Explosives throw a container they are in
type: Tweak
id: 7487
time: '2024-10-04T08:43:45.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32428
- author: SaphireLattice
changes:
- message: Syndicate C4 now starts a countdown on signal, rather than exploding
instantly.
type: Tweak
id: 7488
time: '2024-10-04T09:34:49.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32423
- author: slarticodefast
changes:
- message: Light bulbs now fit into the trash bag again.
type: Fix
id: 7489
time: '2024-10-05T18:44:47.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32452
- author: Just_Art
changes:
- message: Added a flower wreath and hairflower to loadout!
type: Add
id: 7490
time: '2024-10-06T12:48:49.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32097
- author: Golinth
changes:
- message: Removed clown waddling until implemented properly
type: Remove
id: 7491
time: '2024-10-06T13:33:02.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32652
- author: SaphireLattice
changes:
- message: Minibomb is now explosion resistant and will start counting down if damaged
by a C4
type: Tweak
id: 7492
time: '2024-10-07T22:42:43.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32429
- author: shampunj
changes:
- message: When curtains are destroyed, only 1 cloth drops out
type: Tweak
id: 7493
time: '2024-10-08T09:51:23.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32685
- author: Plykiya
changes:
- message: Librarians now start with a D20.
type: Tweak
id: 7494
time: '2024-10-08T09:53:50.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32648
- author: K-Dynamic
changes:
- message: CHIMP and APE particles should be almost as fast as before
type: Tweak
id: 7495
time: '2024-10-08T18:29:37.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32690
- author: ss14nekow
changes:
- message: Added pumpkin pie! Can be made in the microwave with 1 pie tin, 1 pie
dough, and 1 pumpkin.
type: Add
id: 7496
time: '2024-10-08T23:29:41.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32623
- author: Beck Thompson
changes:
- message: The radio jammers price has been decreased from 4 -> 3 TC.
type: Tweak
id: 7497
time: '2024-10-09T12:44:38.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32472
- author: beck-thompson, Moomoobeef
changes:
- message: Plushies now can have small items stuffed inside of them! To open, click
on the plush with a sharp item. You can then insert a small item into the plush.
To close, just click on it again with any sharp item!
type: Add
- message: When pAIs are inserted into plushies, their names will be updated to
the plushies name. E.g when inside a lizard plushie the pAI's name will appear
as "lizard plushie" when speaking.
type: Add
id: 7498
time: '2024-10-09T18:01:32.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/30805
- author: Errant
changes:
- message: Nukies now have role briefing on their character screen.
type: Tweak
- message: Animals who are made Thief no longer get told in their briefing about
the thief gear that they don't actually have and couldn't possibly use.
type: Fix
id: 7499
time: '2024-10-10T08:48:57.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/31318
- author: nikthechampiongr
changes:
- message: It is no longer impossible to do bounties other than the first one if
the server has been up for a long period of time.
type: Fix
id: 7500
time: '2024-10-10T14:35:36.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32700

View File

@@ -2,5 +2,6 @@
### Announcement
earlyleave-cryo-job-unknown = Unknown
earlyleave-cryo-announcement = {$character} ({$job}) { CONJUGATE-HAVE($entity) } entered cryogenic storage!
# {$entity} available for GENDER function purposes
earlyleave-cryo-announcement = {$character} ({$job}) has entered cryogenic storage!
earlyleave-cryo-sender = Station

View File

@@ -4,6 +4,7 @@ nukeops-description = Nuclear operatives have targeted the station. Try to keep
nukeops-welcome =
You are a nuclear operative. Your goal is to blow up {$station}, and ensure that it is nothing but a pile of rubble. Your bosses, the Syndicate, have provided you with the tools you'll need for the task.
Operation {$name} is a go ! Death to Nanotrasen!
nukeops-briefing = Your objectives are simple. Deliver the payload and make sure it detonates. Begin mission.
nukeops-opsmajor = [color=crimson]Syndicate major victory![/color]
nukeops-opsminor = [color=crimson]Syndicate minor victory![/color]

View File

@@ -62,6 +62,11 @@ job-name-unknown = Unknown
job-name-virologist = Virologist
job-name-zombie = Zombie
# Job titles
job-title-visitor = Visitor
job-title-cluwne = Cluwne
job-title-universal = Universal
# Role timers - Make these alphabetical or I cut you
JobAtmosphericTechnician = Atmospheric Technician
JobBartender = Bartender

View File

@@ -1,15 +1,15 @@
job-supervisors-centcom = Central Command
job-supervisors-captain = the captain
job-supervisors-hop = the head of personnel
job-supervisors-hos = the head of security
job-supervisors-ce = the chief engineer
job-supervisors-cmo = the chief medical officer
job-supervisors-rd = the research director
job-supervisors-qm = the quartermaster
job-supervisors-service = chefs, botanists, the bartender, and the head of personnel
job-supervisors-engineering = station engineers, atmospheric technicians, and the chief engineer
job-supervisors-medicine = medical doctors, chemists, and the chief medical officer
job-supervisors-security = security officers, the warden, and the head of security
job-supervisors-science = scientists, and the research director
job-supervisors-captain = the Captain
job-supervisors-hop = the Head of Personnel
job-supervisors-hos = the Head of Security
job-supervisors-ce = the Chief Engineer
job-supervisors-cmo = the Chief Medical Officer
job-supervisors-rd = the Research Director
job-supervisors-qm = the Quartermaster
job-supervisors-service = Chefs, Botanists, the Bartender, and the Head of Personnel
job-supervisors-engineering = Station Engineers, Atmospheric Technicians, and the Chief Engineer
job-supervisors-medicine = Medical Doctors, Paramedics, Chemists, and the Chief Medical Officer
job-supervisors-security = Security Officers, the Warden, and the Head of Security
job-supervisors-science = Scientists and the Research Director
job-supervisors-hire = whoever hires you
job-supervisors-everyone = absolutely everyone

View File

@@ -28,6 +28,9 @@ signal-port-description-doorbolt = Bolts door when HIGH.
signal-port-name-trigger = Trigger
signal-port-description-trigger = Triggers some mechanism on the device.
signal-port-name-timer = Timer
signal-port-description-timer = Starts the timer countdown of the device.
signal-port-name-order-sender = Order sender
signal-port-description-order-sender = Cargo console order sender

View File

@@ -4,6 +4,7 @@ shuttle-pilot-end = Stopped piloting
shuttle-console-in-ftl = Currently in FTL
shuttle-console-mass = Too large to FTL
shuttle-console-prevent = You are unable to pilot this ship
shuttle-console-static = Grid is static
# NAV

View File

@@ -22,3 +22,4 @@ comp-secret-stash-verb-open = Open
### Stash names
secret-stash-plant = plant
secret-stash-toilet = toilet cistern
secret-stash-plushie = plushie

View File

@@ -11579,7 +11579,7 @@ entities:
pos: 24.5,16.5
parent: 8364
- type: Door
secondsUntilStateChange: -15146.963
secondsUntilStateChange: -16708.217
state: Opening
- type: DeviceLinkSource
lastSignals:
@@ -14682,6 +14682,13 @@ entities:
rot: 1.5707963267948966 rad
pos: 8.5,-46.5
parent: 8364
- proto: ArtistCircuitBoard
entities:
- uid: 27915
components:
- type: Transform
pos: -4.5121183,-14.413322
parent: 8364
- proto: Ash
entities:
- uid: 13307
@@ -14689,6 +14696,13 @@ entities:
- type: Transform
pos: -36.5,20.5
parent: 8364
- proto: AsimovCircuitBoard
entities:
- uid: 27912
components:
- type: Transform
pos: 3.2198467,-12.066172
parent: 8364
- proto: AsteroidRock
entities:
- uid: 2042
@@ -39720,12 +39734,12 @@ entities:
- uid: 3869
components:
- type: Transform
pos: 13.5,-74.5
pos: 15.5,-77.5
parent: 8364
- uid: 4050
components:
- type: Transform
pos: 8.5,-74.5
pos: 10.5,-80.5
parent: 8364
- uid: 4519
components:
@@ -39735,7 +39749,7 @@ entities:
- uid: 4553
components:
- type: Transform
pos: 15.5,-74.5
pos: 11.5,-80.5
parent: 8364
- uid: 4585
components:
@@ -39785,7 +39799,7 @@ entities:
- uid: 4973
components:
- type: Transform
pos: 7.5,-74.5
pos: 9.5,-80.5
parent: 8364
- uid: 4988
components:
@@ -41467,11 +41481,6 @@ entities:
- type: Transform
pos: 23.5,23.5
parent: 8364
- uid: 8185
components:
- type: Transform
pos: 5.5,-74.5
parent: 8364
- uid: 8434
components:
- type: Transform
@@ -42860,7 +42869,7 @@ entities:
- uid: 10762
components:
- type: Transform
pos: 6.5,-74.5
pos: 8.5,-80.5
parent: 8364
- uid: 10783
components:
@@ -44167,6 +44176,16 @@ entities:
- type: Transform
pos: -6.5,-50.5
parent: 8364
- uid: 15804
components:
- type: Transform
pos: 10.5,-81.5
parent: 8364
- uid: 15810
components:
- type: Transform
pos: -0.5,-79.5
parent: 8364
- uid: 15815
components:
- type: Transform
@@ -44965,12 +44984,7 @@ entities:
- uid: 17602
components:
- type: Transform
pos: 14.5,-74.5
parent: 8364
- uid: 17697
components:
- type: Transform
pos: 12.5,-74.5
pos: 15.5,-76.5
parent: 8364
- uid: 17698
components:
@@ -46147,15 +46161,10 @@ entities:
- type: Transform
pos: 25.5,-109.5
parent: 8364
- uid: 23221
components:
- type: Transform
pos: 11.5,-74.5
parent: 8364
- uid: 23223
components:
- type: Transform
pos: 10.5,-74.5
pos: 12.5,-80.5
parent: 8364
- uid: 23238
components:
@@ -46172,6 +46181,11 @@ entities:
- type: Transform
pos: 23.5,-109.5
parent: 8364
- uid: 24687
components:
- type: Transform
pos: 13.5,-80.5
parent: 8364
- uid: 25012
components:
- type: Transform
@@ -46805,17 +46819,17 @@ entities:
- uid: 26589
components:
- type: Transform
pos: 9.5,-81.5
pos: 14.5,-80.5
parent: 8364
- uid: 26591
components:
- type: Transform
pos: 9.5,-80.5
pos: 15.5,-80.5
parent: 8364
- uid: 26608
components:
- type: Transform
pos: 9.5,-79.5
pos: 15.5,-79.5
parent: 8364
- uid: 26627
components:
@@ -47000,7 +47014,7 @@ entities:
- uid: 27230
components:
- type: Transform
pos: 7.5,-81.5
pos: 15.5,-78.5
parent: 8364
- uid: 27461
components:
@@ -47192,6 +47206,31 @@ entities:
- type: Transform
pos: 9.5,-68.5
parent: 8364
- uid: 27905
components:
- type: Transform
pos: 9.5,-71.5
parent: 8364
- uid: 27906
components:
- type: Transform
pos: 9.5,-72.5
parent: 8364
- uid: 27907
components:
- type: Transform
pos: 9.5,-70.5
parent: 8364
- uid: 27908
components:
- type: Transform
pos: 9.5,-69.5
parent: 8364
- uid: 27909
components:
- type: Transform
pos: 9.5,-73.5
parent: 8364
- proto: CableHVStack
entities:
- uid: 1683
@@ -64174,6 +64213,11 @@ entities:
- type: Transform
pos: 1.5,28.5
parent: 8364
- uid: 27919
components:
- type: Transform
pos: 2.7203026,-11.488351
parent: 8364
- proto: ChairOfficeLight
entities:
- uid: 5220
@@ -69939,6 +69983,13 @@ entities:
rot: 3.141592653589793 rad
pos: 22.5,-3.5
parent: 8364
- proto: CommandmentCircuitBoard
entities:
- uid: 27910
components:
- type: Transform
pos: -4.600172,-11.893632
parent: 8364
- proto: CommsComputerCircuitboard
entities:
- uid: 16499
@@ -70504,65 +70555,87 @@ entities:
rot: -1.5707963267948966 rad
pos: -60.5,-15.5
parent: 8364
- type: DeviceLinkSink
invokeCounter: 4
- uid: 6227
components:
- type: Transform
rot: -1.5707963267948966 rad
pos: -59.5,-15.5
parent: 8364
- type: DeviceLinkSink
invokeCounter: 4
- uid: 6228
components:
- type: Transform
pos: -57.5,-15.5
parent: 8364
- type: DeviceLinkSink
invokeCounter: 4
- uid: 6229
components:
- type: Transform
rot: -1.5707963267948966 rad
pos: -57.5,-16.5
parent: 8364
- type: DeviceLinkSink
invokeCounter: 4
- uid: 6230
components:
- type: Transform
rot: -1.5707963267948966 rad
pos: -58.5,-16.5
parent: 8364
- type: DeviceLinkSink
invokeCounter: 4
- uid: 6231
components:
- type: Transform
rot: -1.5707963267948966 rad
pos: -59.5,-16.5
parent: 8364
- type: DeviceLinkSink
invokeCounter: 4
- uid: 6232
components:
- type: Transform
rot: -1.5707963267948966 rad
pos: -60.5,-16.5
parent: 8364
- type: DeviceLinkSink
invokeCounter: 4
- uid: 6233
components:
- type: Transform
rot: 3.141592653589793 rad
pos: -61.5,-16.5
parent: 8364
- type: DeviceLinkSink
invokeCounter: 4
- uid: 6234
components:
- type: Transform
rot: 3.141592653589793 rad
pos: -61.5,-17.5
parent: 8364
- type: DeviceLinkSink
invokeCounter: 4
- uid: 6235
components:
- type: Transform
rot: 3.141592653589793 rad
pos: -61.5,-18.5
parent: 8364
- type: DeviceLinkSink
invokeCounter: 4
- uid: 6236
components:
- type: Transform
rot: 3.141592653589793 rad
pos: -61.5,-19.5
parent: 8364
- type: DeviceLinkSink
invokeCounter: 4
- uid: 7162
components:
- type: Transform
@@ -70647,6 +70720,8 @@ entities:
rot: 3.141592653589793 rad
pos: -61.5,-20.5
parent: 8364
- type: DeviceLinkSink
invokeCounter: 4
- uid: 17202
components:
- type: Transform
@@ -70724,6 +70799,13 @@ entities:
- type: Transform
pos: 90.5,-27.5
parent: 8364
- proto: CorporateCircuitBoard
entities:
- uid: 27914
components:
- type: Transform
pos: -4.441743,-12.321369
parent: 8364
- proto: CowToolboxFilled
entities:
- uid: 13853
@@ -81392,6 +81474,13 @@ entities:
- type: Transform
pos: -33.51487,5.3832693
parent: 8364
- proto: DrinkHotCoffee
entities:
- uid: 27918
components:
- type: Transform
pos: 3.3560345,-11.363264
parent: 8364
- proto: DrinkMugBlack
entities:
- uid: 18382
@@ -81446,6 +81535,13 @@ entities:
- type: Transform
pos: 74.3515,-18.443996
parent: 8364
- proto: DungeonMasterCircuitBoard
entities:
- uid: 27917
components:
- type: Transform
pos: 3.5477152,-12.389968
parent: 8364
- proto: EmergencyLight
entities:
- uid: 21308
@@ -84322,7 +84418,7 @@ entities:
pos: -34.5,-14.5
parent: 8364
- type: Door
secondsUntilStateChange: -9335.5
secondsUntilStateChange: -10896.755
state: Closing
- uid: 15010
components:
@@ -84814,6 +84910,9 @@ entities:
- type: Transform
pos: -4.5,-71.5
parent: 8364
- type: Door
secondsUntilStateChange: -2644.4858
state: Closing
- proto: Fireplace
entities:
- uid: 11559
@@ -119851,6 +119950,12 @@ entities:
- type: Transform
pos: 15.5,-41.5
parent: 8364
- uid: 21746
components:
- type: Transform
rot: 3.141592653589793 rad
pos: 10.5,-80.5
parent: 8364
- uid: 21751
components:
- type: Transform
@@ -120086,6 +120191,12 @@ entities:
- type: Transform
pos: 17.5,-52.5
parent: 8364
- uid: 23221
components:
- type: Transform
rot: 3.141592653589793 rad
pos: 10.5,-81.5
parent: 8364
- uid: 23222
components:
- type: Transform
@@ -124581,6 +124692,13 @@ entities:
- type: Transform
pos: 32.5,-6.5
parent: 8364
- proto: NTDefaultCircuitBoard
entities:
- uid: 27913
components:
- type: Transform
pos: -4.5442195,-11.268198
parent: 8364
- proto: NuclearBomb
entities:
- uid: 12086
@@ -124595,6 +124713,13 @@ entities:
- type: Transform
pos: 8.5,-27.5
parent: 8364
- proto: NutimovCircuitBoard
entities:
- uid: 27916
components:
- type: Transform
pos: 3.6621494,-11.814552
parent: 8364
- proto: OperatingTable
entities:
- uid: 2385
@@ -124868,6 +124993,13 @@ entities:
- type: Transform
pos: 58.5,-11.5
parent: 8364
- proto: PaladinCircuitBoard
entities:
- uid: 27911
components:
- type: Transform
pos: -4.443845,-11.67473
parent: 8364
- proto: Paper
entities:
- uid: 1779
@@ -137083,6 +137215,12 @@ entities:
- type: Transform
pos: -3.5,-20.5
parent: 8364
- uid: 8185
components:
- type: Transform
rot: 3.141592653589793 rad
pos: 10.5,-81.5
parent: 8364
- uid: 8207
components:
- type: Transform
@@ -137907,6 +138045,12 @@ entities:
- type: Transform
pos: 25.5,-75.5
parent: 8364
- uid: 17697
components:
- type: Transform
rot: 3.141592653589793 rad
pos: 10.5,-80.5
parent: 8364
- uid: 17787
components:
- type: Transform
@@ -138440,6 +138584,13 @@ entities:
- type: Transform
pos: 0.2510581,39.68831
parent: 8364
- proto: RobocopCircuitBoard
entities:
- uid: 22443
components:
- type: Transform
pos: 3.4364183,-14.443886
parent: 8364
- proto: RobustHarvestChemistryBottle
entities:
- uid: 13505
@@ -143986,6 +144137,13 @@ entities:
- type: Transform
pos: -20.5,-74.5
parent: 8364
- proto: StationEfficiencyCircuitBoard
entities:
- uid: 27536
components:
- type: Transform
pos: -4.631007,-12.460042
parent: 8364
- proto: StationMap
entities:
- uid: 27690
@@ -151539,8 +151697,12 @@ entities:
- Right: Reverse
- Middle: Off
6120:
- Left: Forward
- Right: Reverse
- Left: Forward
- Middle: Off
16588:
- Right: Reverse
- Left: Forward
- Middle: Off
- uid: 16780
components:
@@ -161682,16 +161844,6 @@ entities:
- type: Transform
pos: 10.5,-79.5
parent: 8364
- uid: 15804
components:
- type: Transform
pos: 10.5,-81.5
parent: 8364
- uid: 15810
components:
- type: Transform
pos: 10.5,-80.5
parent: 8364
- uid: 15813
components:
- type: Transform
@@ -172478,7 +172630,7 @@ entities:
rot: 1.5707963267948966 rad
pos: 8.5,-18.5
parent: 8364
- uid: 21746
- uid: 31746
components:
- type: Transform
rot: 3.141592653589793 rad

Some files were not shown because too many files have changed in this diff Show More