diff --git a/Content.Server/StationEvents/Components/RandomSentienceRuleComponent.cs b/Content.Server/StationEvents/Components/RandomSentienceRuleComponent.cs index 98ebf06595..369fce9725 100644 --- a/Content.Server/StationEvents/Components/RandomSentienceRuleComponent.cs +++ b/Content.Server/StationEvents/Components/RandomSentienceRuleComponent.cs @@ -1,9 +1,13 @@ -using Content.Server.StationEvents.Events; +using Content.Server.StationEvents.Events; namespace Content.Server.StationEvents.Components; [RegisterComponent, Access(typeof(RandomSentienceRule))] public sealed partial class RandomSentienceRuleComponent : Component { + [DataField] + public int MinSentiences = 1; + [DataField] + public int MaxSentiences = 1; } diff --git a/Content.Server/StationEvents/Components/SentienceTargetComponent.cs b/Content.Server/StationEvents/Components/SentienceTargetComponent.cs index f8f7e587c1..4fb5e4be59 100644 --- a/Content.Server/StationEvents/Components/SentienceTargetComponent.cs +++ b/Content.Server/StationEvents/Components/SentienceTargetComponent.cs @@ -1,10 +1,13 @@ -using Content.Server.StationEvents.Events; +using Content.Server.StationEvents.Events; namespace Content.Server.StationEvents.Components; [RegisterComponent, Access(typeof(RandomSentienceRule))] public sealed partial class SentienceTargetComponent : Component { - [DataField("flavorKind", required: true)] + [DataField(required: true)] public string FlavorKind = default!; + + [DataField] + public float Weight = 1.0f; } diff --git a/Content.Server/StationEvents/Events/RandomSentienceRule.cs b/Content.Server/StationEvents/Events/RandomSentienceRule.cs index 3869f227bd..3d2e457a34 100644 --- a/Content.Server/StationEvents/Events/RandomSentienceRule.cs +++ b/Content.Server/StationEvents/Events/RandomSentienceRule.cs @@ -1,33 +1,56 @@ -using System.Linq; -using Content.Server.GameTicking.Rules.Components; +using System.Linq; +using Content.Shared.Dataset; using Content.Server.Ghost.Roles.Components; using Content.Server.StationEvents.Components; using Content.Shared.GameTicking.Components; +using Content.Shared.Random.Helpers; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; namespace Content.Server.StationEvents.Events; public sealed class RandomSentienceRule : StationEventSystem { + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly IRobustRandom _random = default!; protected override void Started(EntityUid uid, RandomSentienceRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) { - HashSet stationsToNotify = new(); + if (!TryGetRandomStation(out var station)) + return; var targetList = new List>(); - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var targetUid, out var target)) + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var targetUid, out var target, out var xform)) { + if (StationSystem.GetOwningStation(targetUid, xform) != station) + continue; + targetList.Add((targetUid, target)); } - RobustRandom.Shuffle(targetList); + var toMakeSentient = _random.Next(component.MinSentiences, component.MaxSentiences); - var toMakeSentient = RobustRandom.Next(2, 5); var groups = new HashSet(); - foreach (var target in targetList) + for (var i = 0; i < toMakeSentient && targetList.Count > 0; i++) { - if (toMakeSentient-- == 0) - break; + // weighted random to pick a sentience target + var totalWeight = targetList.Sum(x => x.Comp.Weight); + // This initial target should never be picked. + // It's just so that target doesn't need to be nullable and as a safety fallback for id floating point errors ever mess up the comparison in the foreach. + var target = targetList[0]; + var chosenWeight = _random.NextFloat(totalWeight); + var currentWeight = 0.0; + foreach (var potentialTarget in targetList) + { + currentWeight += potentialTarget.Comp.Weight; + if (currentWeight > chosenWeight) + { + target = potentialTarget; + break; + } + } + targetList.Remove(target); RemComp(target); var ghostRole = EnsureComp(target); @@ -45,24 +68,15 @@ public sealed class RandomSentienceRule : StationEventSystem 1 ? groupList[1] : "???"; var kind3 = groupList.Count > 2 ? groupList[2] : "???"; - foreach (var target in targetList) - { - var station = StationSystem.GetOwningStation(target); - if(station == null) - continue; - stationsToNotify.Add((EntityUid) station); - } - foreach (var station in stationsToNotify) - { - ChatSystem.DispatchStationAnnouncement( - station, - Loc.GetString("station-event-random-sentience-announcement", - ("kind1", kind1), ("kind2", kind2), ("kind3", kind3), ("amount", groupList.Count), - ("data", Loc.GetString($"random-sentience-event-data-{RobustRandom.Next(1, 6)}")), - ("strength", Loc.GetString($"random-sentience-event-strength-{RobustRandom.Next(1, 8)}"))), - playDefaultSound: false, - colorOverride: Color.Gold - ); - } + ChatSystem.DispatchStationAnnouncement( + station.Value, + Loc.GetString("station-event-random-sentience-announcement", + ("kind1", kind1), ("kind2", kind2), ("kind3", kind3), ("amount", groupList.Count), + ("data", _random.Pick(_prototype.Index("RandomSentienceEventData"))), + ("strength", _random.Pick(_prototype.Index("RandomSentienceEventStrength"))) + ), + playDefaultSound: false, + colorOverride: Color.Gold + ); } } diff --git a/Content.Shared/Interaction/Components/BlockMovementComponent.cs b/Content.Shared/Interaction/Components/BlockMovementComponent.cs index e308e84960..2125f16efe 100644 --- a/Content.Shared/Interaction/Components/BlockMovementComponent.cs +++ b/Content.Shared/Interaction/Components/BlockMovementComponent.cs @@ -1,4 +1,4 @@ -using Robust.Shared.GameStates; +using Robust.Shared.GameStates; namespace Content.Shared.Interaction.Components; @@ -8,5 +8,6 @@ namespace Content.Shared.Interaction.Components; [RegisterComponent, NetworkedComponent] public sealed partial class BlockMovementComponent : Component { - + [DataField] + public bool BlockInteraction = true; } diff --git a/Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs b/Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs index a682bf9815..52c40477c9 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs @@ -28,7 +28,8 @@ public partial class SharedInteractionSystem private void CancelInteractEvent(Entity ent, ref InteractionAttemptEvent args) { - args.Cancelled = true; + if (ent.Comp.BlockInteraction) + args.Cancelled = true; } private void OnMoveAttempt(EntityUid uid, BlockMovementComponent component, UpdateCanMoveEvent args) diff --git a/Resources/Locale/en-US/station-events/events/random-sentience.ftl b/Resources/Locale/en-US/station-events/events/random-sentience.ftl index 47f0e317a6..f14a020d29 100644 --- a/Resources/Locale/en-US/station-events/events/random-sentience.ftl +++ b/Resources/Locale/en-US/station-events/events/random-sentience.ftl @@ -36,3 +36,4 @@ station-event-random-sentience-flavor-corgi = corgi station-event-random-sentience-flavor-primate = primate station-event-random-sentience-flavor-kobold = kobold station-event-random-sentience-flavor-slime = slime +station-event-random-sentience-flavor-inanimate = inanimate \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Clothing/Head/hats.yml b/Resources/Prototypes/Entities/Clothing/Head/hats.yml index 0ca55f7614..02275d5a2f 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hats.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hats.yml @@ -869,6 +869,10 @@ - ClothMade - WhitelistChameleon - HamsterWearable + - type: SentienceTarget + flavorKind: station-event-random-sentience-flavor-inanimate + weight: 0.0002 # 5,000 times less likely than 1 regular animal + - type: BlockMovement - type: entity parent: ClothingHeadBase diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 0ea345bb6f..a403486338 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -376,6 +376,8 @@ behaviors: - !type:GibBehavior { } - type: NonSpreaderZombie + - type: SentienceTarget + flavorKind: station-event-random-sentience-flavor-organic - type: entity name: glockroach diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index 9c9499808d..9ba0fdd873 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -286,6 +286,8 @@ - type: Construction graph: MediBot node: bot + - type: SentienceTarget + flavorKind: station-event-random-sentience-flavor-mechanical - type: Anchorable - type: InteractionPopup interactSuccessString: petting-success-medibot diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index faad961ebd..65639ecf11 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -103,6 +103,15 @@ - DoorBumpOpener - type: Input context: "human" + - type: SentienceTarget # sentient PDA = pAI lite + flavorKind: station-event-random-sentience-flavor-mechanical + weight: 0.001 # 1,000 PDAs = as likely to be picked as 1 regular animal + - type: BlockMovement + blockInteraction: false # lets the PDA toggle its own flashlight + - type: TypingIndicator + proto: robot + - type: Speech + speechVerb: Robotic - type: entity parent: BasePDA @@ -220,6 +229,8 @@ borderColor: "#d7d7d0" - type: Icon state: pda-cook + - type: ReplacementAccent # for random sentience event + accent: italian - type: entity parent: BasePDA @@ -299,6 +310,7 @@ accentHColor: "#333333" - type: Icon state: pda-mime + - type: Muted # for random sentience event - type: entity name: chaplain PDA diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml index 715905a533..5b8130567b 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml @@ -584,6 +584,10 @@ price: 750 - type: StealTarget stealGroup: WeaponAntiqueLaser + - type: SentienceTarget # I hope this is only the captain's gun + flavorKind: station-event-random-sentience-flavor-inanimate + weight: 0.0002 # 5,000 times less likely than 1 regular animal + # not putting a BlockMovement component here cause that's funny. - type: entity name: advanced laser pistol diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml index 586247bb3a..1784eae810 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml @@ -40,6 +40,11 @@ tags: - CaptainSabre - type: DisarmMalus + - type: SentienceTarget + flavorKind: station-event-random-sentience-flavor-inanimate + weight: 0.0002 # 5,000 times less likely than 1 regular animal + - type: PirateAccent + # not putting a BlockMovement component here cause that's funny. - type: entity name: katana diff --git a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml index 74e5a7a052..3e931aeb33 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml @@ -96,6 +96,7 @@ - type: Actions - type: SentienceTarget flavorKind: station-event-random-sentience-flavor-mechanical + weight: 0.025 # fuck you in particular (it now needs 40 vending machines to be as likely as 1 interesting animal) - type: StaticPrice price: 100 - type: Appearance diff --git a/Resources/Prototypes/GameRules/random_sentience.yml b/Resources/Prototypes/GameRules/random_sentience.yml new file mode 100644 index 0000000000..a2c749000a --- /dev/null +++ b/Resources/Prototypes/GameRules/random_sentience.yml @@ -0,0 +1,25 @@ +- type: entity + id: RandomSentience + parent: BaseGameRule + components: + - type: StationEvent + weight: 6 + duration: 1 + maxOccurrences: 1 # this event has diminishing returns on interesting-ness, so we cap it + startAudio: + path: /Audio/Announcements/attention.ogg + - type: RandomSentienceRule + minSentiences: 2 + maxSentiences: 5 + +- type: localizedDataset + id: RandomSentienceEventData + values: + prefix: random-sentience-event-data- + count: 6 + +- type: localizedDataset + id: RandomSentienceEventStrength + values: + prefix: random-sentience-event-strength- + count: 8 \ No newline at end of file