diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index 8f10608e6f..0c758af678 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -123,6 +123,8 @@ namespace Content.Client.Entry "CursedEntityStorage", "Radio", "GasArtifact", + "SentienceTarget", + "VentCritterSpawnLocation", "RadiateArtifact", "TemperatureArtifact", "DisposalHolder", diff --git a/Content.Server/Chat/Managers/ChatManager.cs b/Content.Server/Chat/Managers/ChatManager.cs index b2d87b7f6a..f29211c860 100644 --- a/Content.Server/Chat/Managers/ChatManager.cs +++ b/Content.Server/Chat/Managers/ChatManager.cs @@ -118,7 +118,7 @@ namespace Content.Server.Chat.Managers _logs.Add(LogType.Chat, LogImpact.Low, $"Server announcement: {message}"); } - public void DispatchStationAnnouncement(string message, string sender = "CentComm", bool playDefaultSound = true) + public void DispatchStationAnnouncement(string message, string sender = "Central Command", bool playDefaultSound = true) { var messageWrap = Loc.GetString("chat-manager-sender-announcement-wrap-message", ("sender", sender)); NetMessageToAll(ChatChannel.Radio, message, messageWrap); diff --git a/Content.Server/StationEvents/Components/SentienceTargetComponent.cs b/Content.Server/StationEvents/Components/SentienceTargetComponent.cs new file mode 100644 index 0000000000..bf40d50249 --- /dev/null +++ b/Content.Server/StationEvents/Components/SentienceTargetComponent.cs @@ -0,0 +1,8 @@ +namespace Content.Server.StationEvents.Components; + +[RegisterComponent] +public sealed class SentienceTargetComponent : Component +{ + [DataField("flavorKind", required: true)] + public string FlavorKind = default!; +} diff --git a/Content.Server/StationEvents/Components/VentCritterSpawnLocationComponent.cs b/Content.Server/StationEvents/Components/VentCritterSpawnLocationComponent.cs new file mode 100644 index 0000000000..3dc46642e7 --- /dev/null +++ b/Content.Server/StationEvents/Components/VentCritterSpawnLocationComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Server.StationEvents.Components; + +[RegisterComponent] +public sealed class VentCritterSpawnLocationComponent : Component +{ + +} diff --git a/Content.Server/StationEvents/Events/BureaucraticError.cs b/Content.Server/StationEvents/Events/BureaucraticError.cs new file mode 100644 index 0000000000..5d92d844a8 --- /dev/null +++ b/Content.Server/StationEvents/Events/BureaucraticError.cs @@ -0,0 +1,63 @@ +using System.Linq; +using Content.Server.Station; +using Content.Shared.Roles; +using JetBrains.Annotations; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.StationEvents.Events; + +[UsedImplicitly] +public sealed class BureaucraticError : StationEvent +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + public override string? StartAnnouncement => + Loc.GetString("station-event-bureaucratic-error-announcement"); + public override string Name => "BureaucraticError"; + + public override string? StartAudio => "/Audio/Announcements/announce.ogg"; + + public override int MinimumPlayers => 25; + + public override float Weight => WeightLow; + + public override int? MaxOccurrences => 2; + + protected override float EndAfter => 1f; + + public override void Startup() + { + base.Startup(); + var chosenStation = _random.Pick(EntitySystem.Get().StationInfo.Values.ToList()); + var jobList = chosenStation.JobList.Keys.Where(x => !_prototypeManager.Index(x).IsHead).ToList(); + + // Low chance to completely change up the late-join landscape by closing all positions except infinite slots. + // Lower chance than the /tg/ equivalent of this event. + if (_random.Prob(0.25f)) + { + var chosenJob = _random.PickAndTake(jobList); + chosenStation.AdjustJobAmount(chosenJob, -1); // INFINITE chaos. + foreach (var job in jobList) + { + if (chosenStation.JobList[job] == -1) + continue; + chosenStation.AdjustJobAmount(job, 0); + } + } + else + { + // Changing every role is maybe a bit too chaotic so instead change 20-30% of them. + for (var i = 0; i < _random.Next((int)(jobList.Count * 0.20), (int)(jobList.Count * 0.30)); i++) + { + var chosenJob = _random.PickAndTake(jobList); + if (chosenStation.JobList[chosenJob] == -1) + continue; + + var adj = Math.Max(chosenStation.JobList[chosenJob] + _random.Next(-3, 6), 0); + chosenStation.AdjustJobAmount(chosenJob, adj); + } + } + } + +} diff --git a/Content.Server/StationEvents/Events/KudzuGrowth.cs b/Content.Server/StationEvents/Events/KudzuGrowth.cs index ef6b3194df..1622c8c886 100644 --- a/Content.Server/StationEvents/Events/KudzuGrowth.cs +++ b/Content.Server/StationEvents/Events/KudzuGrowth.cs @@ -10,8 +10,8 @@ namespace Content.Server.StationEvents.Events; public sealed class KudzuGrowth : StationEvent { - [Dependency] private IRobustRandom _robustRandom = default!; - [Dependency] private IEntityManager _entityManager = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; public override string Name => "KudzuGrowth"; diff --git a/Content.Server/StationEvents/Events/RadiationStorm.cs b/Content.Server/StationEvents/Events/RadiationStorm.cs index 4685a0229c..3278bec24f 100644 --- a/Content.Server/StationEvents/Events/RadiationStorm.cs +++ b/Content.Server/StationEvents/Events/RadiationStorm.cs @@ -18,8 +18,8 @@ namespace Content.Server.StationEvents.Events { // Based on Goonstation style radiation storm with some TG elements (announcer, etc.) - [Dependency] private IEntityManager _entityManager = default!; - [Dependency] private IRobustRandom _robustRandom = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; public override string Name => "RadiationStorm"; public override string StartAnnouncement => Loc.GetString("station-event-radiation-storm-start-announcement"); diff --git a/Content.Server/StationEvents/Events/RandomSentience.cs b/Content.Server/StationEvents/Events/RandomSentience.cs new file mode 100644 index 0000000000..83c43f088f --- /dev/null +++ b/Content.Server/StationEvents/Events/RandomSentience.cs @@ -0,0 +1,55 @@ +using System.Linq; +using Content.Server.Chat.Managers; +using Content.Server.Mind.Commands; +using Content.Server.StationEvents.Components; +using Robust.Shared.Random; + +namespace Content.Server.StationEvents.Events; + +public sealed class RandomSentience : StationEvent +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + + public override string Name => "RandomSentience"; + + public override float Weight => WeightNormal; + + protected override float EndAfter => 1.0f; + + public override void Startup() + { + base.Startup(); + + var targetList = _entityManager.EntityQuery().ToList(); + _random.Shuffle(targetList); + + var toMakeSentient = _random.Next(2, 5); + var groups = new HashSet(); + + foreach (var target in targetList) + { + if (toMakeSentient-- == 0) + break; + + MakeSentientCommand.MakeSentient(target.Owner, _entityManager); + _entityManager.RemoveComponent(target.Owner); + groups.Add(target.FlavorKind); + } + + if (groups.Count == 0) + return; + + var groupList = groups.ToList(); + var kind1 = groupList.Count > 0 ? groupList[0] : "???"; + var kind2 = groupList.Count > 1 ? groupList[1] : "???"; + var kind3 = groupList.Count > 2 ? groupList[2] : "???"; + _chatManager.DispatchStationAnnouncement( + Loc.GetString("station-event-random-sentience-announcement", + ("kind1", kind1), ("kind2", kind2), ("kind3", kind3), ("amount", groupList.Count), + ("data", Loc.GetString($"random-sentience-event-data-{_random.Next(1, 6)}")), + ("strength", Loc.GetString($"random-sentience-event-strength-{_random.Next(1, 8)}"))) + ); + } +} diff --git a/Content.Server/StationEvents/Events/VentCritters.cs b/Content.Server/StationEvents/Events/VentCritters.cs new file mode 100644 index 0000000000..c92e8dc095 --- /dev/null +++ b/Content.Server/StationEvents/Events/VentCritters.cs @@ -0,0 +1,52 @@ +using System.Linq; +using Content.Server.StationEvents.Components; +using Robust.Shared.Random; + +namespace Content.Server.StationEvents.Events; + +public sealed class VentCritters : StationEvent +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + + public static List SpawnedPrototypeChoices = new List() + {"MobGiantSpiderAngry", "MobMouse", "MobMouse1", "MobMouse2"}; + + public override string Name => "VentCritters"; + + public override string? StartAnnouncement => + Loc.GetString("station-event-vent-spiders-start-announcement", ("data", Loc.GetString(Loc.GetString($"random-sentience-event-data-{_random.Next(1, 6)}")))); + + public override string? StartAudio => "/Audio/Announcements/bloblarm.ogg"; + + public override int EarliestStart => 15; + + public override int MinimumPlayers => 15; + + public override float Weight => WeightLow; + + public override int? MaxOccurrences => 2; + + protected override float StartAfter => 30f; + + protected override float EndAfter => 60; + + public override void Startup() + { + base.Startup(); + var spawnChoice = _random.Pick(SpawnedPrototypeChoices); + var spawnLocations = _entityManager.EntityQuery().ToList(); + _random.Shuffle(spawnLocations); + + var spawnAmount = _random.Next(4, 12); // A small colony of critters. + foreach (var location in spawnLocations) + { + if (spawnAmount-- == 0) + break; + + var coords = _entityManager.GetComponent(location.Owner); + + _entityManager.SpawnEntity(spawnChoice, coords.Coordinates); + } + } +} diff --git a/Resources/Locale/en-US/station-events/events/bureaucratic-error.ftl b/Resources/Locale/en-US/station-events/events/bureaucratic-error.ftl new file mode 100644 index 0000000000..8042ac871e --- /dev/null +++ b/Resources/Locale/en-US/station-events/events/bureaucratic-error.ftl @@ -0,0 +1 @@ +station-event-bureaucratic-error-announcement = A recent bureaucratic error in the Organic Resources Department may result in personnel shortages in some departments and redundant staffing in others. diff --git a/Resources/Locale/en-US/station-events/events/random-sentience.ftl b/Resources/Locale/en-US/station-events/events/random-sentience.ftl new file mode 100644 index 0000000000..1ca1f7bd95 --- /dev/null +++ b/Resources/Locale/en-US/station-events/events/random-sentience.ftl @@ -0,0 +1,24 @@ +## Phrases used for where central command got this information. +random-sentience-event-data-1 = scans from our long-range sensors +random-sentience-event-data-2 = our sophisticated probabilistic models +random-sentience-event-data-3 = our omnipotence +random-sentience-event-data-4 = the communications traffic on your station +random-sentience-event-data-5 = energy emissions we detected +random-sentience-event-data-6 = [REDACTED] + +## Phrases used to describe the level of intelligence, though it doesn't actually affect anything. +random-sentience-event-strength-1 = human +random-sentience-event-strength-2 = primate +random-sentience-event-strength-3 = moderate +random-sentience-event-strength-4 = security +random-sentience-event-strength-5 = command +random-sentience-event-strength-6 = clown +random-sentience-event-strength-7 = low +random-sentience-event-strength-8 = AI + +station-event-random-sentience-announcement = Based on { $data }, we believe that some of the station's { $amount -> + [1] { $kind1 } + [2] { $kind1 } and { $kind2 } + [3] { $kind1 }, { $kind2 }, and { $kind3 } + *[other] { $kind1 }, { $kind2 }, { $kind3 }, etc. +} beings have developed { $strength } level intelligence, and the ability to communicate. diff --git a/Resources/Locale/en-US/station-events/events/vent-critters.ftl b/Resources/Locale/en-US/station-events/events/vent-critters.ftl new file mode 100644 index 0000000000..5379bfca03 --- /dev/null +++ b/Resources/Locale/en-US/station-events/events/vent-critters.ftl @@ -0,0 +1 @@ +station-event-vent-spiders-start-announcement = Based on { $data }, we believe a small colony of unknown organisms have taken residence inside the station's ventilation and have taken action to drive them out. diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 8fabc0c9b7..8bfb235801 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -42,6 +42,8 @@ successChance: 0.2 interactSuccessString: petting-success-soft-floofy interactFailureString: petting-failure-bat + - type: SentienceTarget + flavorKind: organic - type: entity name: bee @@ -546,13 +548,8 @@ components: - type: NameIdentifier group: Monkey - - type: GhostTakeoverAvailable - makeSentient: true - name: monkey - description: Ook ook! - rules: | - Being a monkey does NOT give you a free pass to kill, RDM or self-antag. - + - type: SentienceTarget + flavorKind: primate - type: Sprite drawdepth: Mobs layers: @@ -938,6 +935,19 @@ interactSuccessString: petting-success-tarantula interactFailureString: petting-failure-generic +- type: entity + name: tarantula + parent: MobGiantSpider + id: MobGiantSpiderAngry + components: + - type: AiFactionTag + factions: + - Xeno + - type: UtilityAI + behaviorSets: + - Idle + - UnarmedAttackHostiles + - type: entity name: possum parent: SimpleMobBase diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml index 95e8fe09ab..ea416cee97 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml @@ -45,6 +45,8 @@ - type: Grammar attributes: gender: epicene + - type: SentienceTarget + flavorKind: corgi - type: entity name: corrupted corgi diff --git a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml index 8b54b9d8a6..7ff3b991f4 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml @@ -55,7 +55,8 @@ - type: ApcPowerReceiver powerLoad: 200 priority: Low - + - type: SentienceTarget + flavorKind: mechanical - type: entity parent: VendingMachine diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml index 1a0a1c8c45..dc1db9ba7b 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml @@ -50,6 +50,7 @@ - type: Construction graph: GasUnary node: ventpump + - type: VentCritterSpawnLocation - type: entity parent: GasUnaryBase diff --git a/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml b/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml index e854df3d81..9924ab706d 100644 --- a/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml +++ b/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml @@ -1,6 +1,7 @@ - type: job id: Quartermaster name: "quartermaster" + head: true startingGear: QuartermasterGear departments: - Cargo diff --git a/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml b/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml index 33574f8932..e167362640 100644 --- a/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml +++ b/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml @@ -1,6 +1,7 @@ - type: job id: HeadOfPersonnel name: "head of personnel" + head: true startingGear: HoPGear departments: - Command