diff --git a/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs b/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs index 4cdab6ce07..6c2a6986fc 100644 --- a/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs +++ b/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs @@ -1,5 +1,4 @@ using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Ghost.Roles.Components { @@ -10,17 +9,22 @@ namespace Content.Server.Ghost.Roles.Components [Access(typeof(GhostRoleSystem))] public sealed partial class GhostRoleMobSpawnerComponent : Component { - [ViewVariables(VVAccess.ReadWrite)] [DataField("deleteOnSpawn")] + [DataField] public bool DeleteOnSpawn = true; - [ViewVariables(VVAccess.ReadWrite)] [DataField("availableTakeovers")] + [DataField] public int AvailableTakeovers = 1; [ViewVariables] public int CurrentTakeovers = 0; - [ViewVariables(VVAccess.ReadWrite)] - [DataField("prototype", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? Prototype { get; private set; } + [DataField] + public EntProtoId? Prototype; + + /// + /// If this ghostrole spawner has multiple selectable ghostrole prototypes. + /// + [DataField] + public List SelectablePrototypes = []; } } diff --git a/Content.Server/Ghost/Roles/GhostRoleSystem.cs b/Content.Server/Ghost/Roles/GhostRoleSystem.cs index f603416d00..f1719146c6 100644 --- a/Content.Server/Ghost/Roles/GhostRoleSystem.cs +++ b/Content.Server/Ghost/Roles/GhostRoleSystem.cs @@ -23,6 +23,10 @@ using Robust.Shared.Enums; using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Utility; +using Content.Server.Popups; +using Content.Shared.Verbs; +using Robust.Shared.Prototypes; +using Robust.Shared.Collections; namespace Content.Server.Ghost.Roles { @@ -37,6 +41,8 @@ namespace Content.Server.Ghost.Roles [Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly SharedMindSystem _mindSystem = default!; [Dependency] private readonly SharedRoleSystem _roleSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; private uint _nextRoleIdentifier; private bool _needsUpdateGhostRoleCount = true; @@ -64,6 +70,7 @@ namespace Content.Server.Ghost.Roles SubscribeLocalEvent(OnSpawnerTakeRole); SubscribeLocalEvent(OnTakeoverTakeRole); SubscribeLocalEvent(OnSpawnerTakeCharacter); // DeltaV - Character ghost roles, see Content.Server/DeltaV/Ghost/Roles/GhostRoleSystem.Character.cs + SubscribeLocalEvent>(OnVerb); _playerManager.PlayerStatusChanged += PlayerStatusChanged; } @@ -75,11 +82,11 @@ namespace Content.Server.Ghost.Roles switch (args.NewMobState) { case MobState.Alive: - { - if (!ghostRole.Taken) - RegisterGhostRole((component, ghostRole)); - break; - } + { + if (!ghostRole.Taken) + RegisterGhostRole((component, ghostRole)); + break; + } case MobState.Critical: case MobState.Dead: UnregisterGhostRole((component, ghostRole)); @@ -101,11 +108,11 @@ namespace Content.Server.Ghost.Roles public void OpenEui(ICommonSession session) { - if (session.AttachedEntity is not {Valid: true} attached || + if (session.AttachedEntity is not { Valid: true } attached || !EntityManager.HasComponent(attached)) return; - if(_openUis.ContainsKey(session)) + if (_openUis.ContainsKey(session)) CloseEui(session); var eui = _openUis[session] = new GhostRolesEui(); @@ -251,7 +258,7 @@ namespace Content.Server.Ghost.Roles if (metaQuery.GetComponent(uid).EntityPaused) continue; - roles.Add(new GhostRoleInfo {Identifier = id, Name = role.RoleName, Description = role.RoleDescription, Rules = role.RoleRules, Requirements = role.Requirements}); + roles.Add(new GhostRoleInfo { Identifier = id, Name = role.RoleName, Description = role.RoleDescription, Rules = role.RoleRules, Requirements = role.Requirements }); } return roles.ToArray(); @@ -408,6 +415,63 @@ namespace Content.Server.Ghost.Roles args.TookRole = true; } + + private void OnVerb(EntityUid uid, GhostRoleMobSpawnerComponent component, GetVerbsEvent args) + { + var prototypes = component.SelectablePrototypes; + if (prototypes.Count < 1) + return; + + if (!args.CanAccess || !args.CanInteract || args.Hands == null) + return; + + var verbs = new ValueList(); + + foreach (var prototypeID in prototypes) + { + if (_prototype.TryIndex(prototypeID, out var prototype)) + { + var verb = CreateVerb(uid, component, args.User, prototype); + verbs.Add(verb); + } + } + + args.Verbs.UnionWith(verbs); + } + + private Verb CreateVerb(EntityUid uid, GhostRoleMobSpawnerComponent component, EntityUid userUid, GhostRolePrototype prototype) + { + var verbText = Loc.GetString(prototype.Name); + + return new Verb() + { + Text = verbText, + Disabled = component.Prototype == prototype.EntityPrototype, + Category = VerbCategory.SelectType, + Act = () => SetMode(uid, prototype, verbText, component, userUid) + }; + } + + public void SetMode(EntityUid uid, GhostRolePrototype prototype, string verbText, GhostRoleMobSpawnerComponent? component, EntityUid? userUid = null) + { + if (!Resolve(uid, ref component)) + return; + + var ghostrolecomp = EnsureComp(uid); + + component.Prototype = prototype.EntityPrototype; + ghostrolecomp.RoleName = verbText; + ghostrolecomp.RoleDescription = prototype.Description; + ghostrolecomp.RoleRules = prototype.Rules; + + // Dirty(ghostrolecomp); + + if (userUid != null) + { + var msg = Loc.GetString("ghostrole-spawner-select", ("mode", verbText)); + _popupSystem.PopupEntity(msg, uid, userUid.Value); + } + } } [AnyCommand] @@ -418,7 +482,7 @@ namespace Content.Server.Ghost.Roles public string Help => $"{Command}"; public void Execute(IConsoleShell shell, string argStr, string[] args) { - if(shell.Player != null) + if (shell.Player != null) EntitySystem.Get().OpenEui(shell.Player); else shell.WriteLine("You can only open the ghost roles UI on a client."); diff --git a/Content.Shared/Ghost/Roles/GhostRolePrototype.cs b/Content.Shared/Ghost/Roles/GhostRolePrototype.cs new file mode 100644 index 0000000000..43d6432250 --- /dev/null +++ b/Content.Shared/Ghost/Roles/GhostRolePrototype.cs @@ -0,0 +1,38 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Ghost.Roles; + +/// +/// For selectable ghostrole prototypes in ghostrole spawners. +/// +[Prototype] +public sealed partial class GhostRolePrototype : IPrototype +{ + [ViewVariables] + [IdDataField] + public string ID { get; private set; } = default!; + + /// + /// The name of the ghostrole. + /// + [DataField] + public string Name { get; set; } = default!; + + /// + /// The description of the ghostrole. + /// + [DataField] + public string Description { get; set; } = default!; + + /// + /// The entity prototype of the ghostrole + /// + [DataField] + public string EntityPrototype = default!; + + /// + /// Rules of the ghostrole + /// + [DataField] + public string Rules = default!; +} \ No newline at end of file diff --git a/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl b/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl index 7b25eb660b..9680960b6d 100644 --- a/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl +++ b/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl @@ -246,6 +246,10 @@ ghost-role-information-syndicate-monkey-reinforcement-name = Syndicate Monkey Ag ghost-role-information-syndicate-monkey-reinforcement-description = Someone needs reinforcements. You, a trained monkey, will help them. ghost-role-information-syndicate-monkey-reinforcement-rules = Normal syndicate antagonist rules apply. Work with whoever called you in, and don't harm them. +ghost-role-information-syndicate-kobold-reinforcement-name = Syndicate Kobold Agent +ghost-role-information-syndicate-kobold-reinforcement-description = Someone needs reinforcements. You, a trained kobold, will help them. +ghost-role-information-syndicate-kobold-reinforcement-rules = Normal syndicate antagonist rules apply. Work with whoever called you in, and don't harm them. + ghost-role-information-artifact-name = Sentient Artifact ghost-role-information-artifact-description = Enact your eldritch whims. diff --git a/Resources/Locale/en-US/ghost/roles/ghostrole-spawner-verb-selectable.ftl b/Resources/Locale/en-US/ghost/roles/ghostrole-spawner-verb-selectable.ftl new file mode 100644 index 0000000000..9d649a5e07 --- /dev/null +++ b/Resources/Locale/en-US/ghost/roles/ghostrole-spawner-verb-selectable.ftl @@ -0,0 +1 @@ +ghostrole-spawner-select = Selected: {$mode} \ No newline at end of file diff --git a/Resources/Locale/en-US/store/uplink-catalog.ftl b/Resources/Locale/en-US/store/uplink-catalog.ftl index c7e6bf2e3f..b2dfa9e3f6 100644 --- a/Resources/Locale/en-US/store/uplink-catalog.ftl +++ b/Resources/Locale/en-US/store/uplink-catalog.ftl @@ -121,8 +121,8 @@ uplink-agent-id-card-desc = A modified ID card that can copy accesses from other uplink-black-jetpack-name = Black Jetpack uplink-black-jetpack-desc = A black jetpack. It allows you to fly around in space. Refills not included, use your fuel wisely. -uplink-reinforcement-radio-monkey-name = Monkey Reinforcement Teleporter -uplink-reinforcement-radio-monkey-desc = Call in a trained monkey to assist you. Comes with a single syndicate cigarette. +uplink-reinforcement-radio-ancestor-name = Genetic Ancestor Reinforcement Teleporter +uplink-reinforcement-radio-ancestor-desc = Call in a trained ancestor of your choosing to assist you. Comes with a single syndicate cigarette. uplink-reinforcement-radio-name = Reinforcement Teleporter uplink-reinforcement-radio-desc = Radio in a reinforcement agent of extremely questionable quality. No off button, buy this if you're ready to party. They have a pistol with no reserve ammo, and a knife. That's it. diff --git a/Resources/Migrations/migration.yml b/Resources/Migrations/migration.yml index 01fea95c03..8ddc826baa 100644 --- a/Resources/Migrations/migration.yml +++ b/Resources/Migrations/migration.yml @@ -264,3 +264,49 @@ BriefcaseSyndieBase: null # 2024-04-08 BodyBag_Container: BodyBag BodyBag_Folded: BodyBagFolded + +# 2024-04-26 +#BaseBulletRubber: null # DeltaV - Skipped +#BulletPistolRubber: BulletPistol +#BulletMagnumRubber: BulletMagnum +#BulletLightRifleRubber: BulletLightRifle +#BulletRifleRubber: BulletRifle +#BulletCaselessRifleRubber: BulletCaselessRifle +#CartridgePistolRubber: CartridgePistol +#CartridgeMagnumRubber: CartridgeMagnum +#CartridgeLightRifleRubber: CartridgeLightRifle +#CartridgeRifleRubber: CartridgeRifle +#CartridgeCaselessRifleRubber: CartridgeCaselessRifle +#MagazinePistolRubber: MagazinePistol +#MagazinePistolSubMachineGunRubber: MagazinePistolSubMachineGun +#MagazinePistolCaselessRifleRubber: MagazinePistolCaselessRifle +#MagazineMagnumRubber: MagazineMagnum +#MagazineMagnumSubMachineGunRubber: MagazineMagnumSubMachineGun +#MagazineLightRifleRubber: MagazineLightRifle +#MagazineRifleRubber: MagazineRifle +#MagazineCaselessRifleRubber: MagazineCaselessRifle +#MagazineCaselessRifleShortRubber: MagazineCaselessRifleShort +#SpeedLoaderPistolRubber: SpeedLoaderPistol +#SpeedLoaderMagnumRubber: SpeedLoaderMagnum +#MagazineBoxPistolRubber: MagazineBoxPistol +## RIP box of magnum rubbers 202X to 2024 +#MagazineBoxMagnumRubber: MagazineBoxMagnum +#MagazineBoxLightRifleRubber: MagazineBoxLightRifle +#MagazineBoxRifleRubber: MagazineBoxRifle +#MagazineBoxRifleBigRubber: MagazineBoxRifleBig +#MagazineBoxCaselessRifleRubber: MagazineBoxCaselessRifle +#MagazineBoxCaselessRifleBigRubber: MagazineBoxCaselessRifle10x24 +#BoxMagazinePistolRubber: BoxMagazinePistol +#BoxMagazinePistolHighCapacityRubber: BoxMagazinePistolHighCapacity +#BoxMagazinePistolSubMachineGunRubber: BoxMagazinePistolSubMachineGun +#BoxMagazineLightRifleRubber: BoxMagazineLightRifle +#BoxMagazineRifleRubber: BoxMagazineRifle +#BoxMagazineCaselessRifleRubber: BoxMagazinePistolCaselessRifle +#BoxMagazineMagnumSubMachineGunRubber: BoxMagazineMagnumSubMachineGun +#WeaponPistolMk58Nonlethal: WeaponPistolMk58 +#WeaponPistolN1984Nonlethal: WeaponPistolN1984 +#WeaponSubMachineGunVectorRubber: WeaponSubMachineGunVector +#WeaponSubMachineGunDrozdRubber: WeaponSubMachineGunDrozd +#WeaponRifleLecterRubber: WeaponRifleLecter +ReinforcementRadioSyndicateMonkey: ReinforcementRadioSyndicateAncestor +ReinforcementRadioSyndicateMonkeyNukeops: ReinforcementRadioSyndicateAncestorNukeops diff --git a/Resources/Prototypes/Catalog/uplink_catalog.yml b/Resources/Prototypes/Catalog/uplink_catalog.yml index c599a24290..c699ccc1b0 100644 --- a/Resources/Prototypes/Catalog/uplink_catalog.yml +++ b/Resources/Prototypes/Catalog/uplink_catalog.yml @@ -937,10 +937,10 @@ - NukeOpsUplink - type: listing - id: UplinkReinforcementRadioSyndicateMonkey - name: uplink-reinforcement-radio-monkey-name - description: uplink-reinforcement-radio-monkey-desc - productEntity: ReinforcementRadioSyndicateMonkey + id: UplinkReinforcementRadioSyndicateAncestor + name: uplink-reinforcement-radio-ancestor-name + description: uplink-reinforcement-radio-ancestor-desc + productEntity: ReinforcementRadioSyndicateAncestor icon: { sprite: Objects/Devices/communication.rsi, state: old-radio } cost: Telecrystal: 6 @@ -953,10 +953,10 @@ - NukeOpsUplink - type: listing - id: UplinkReinforcementRadioSyndicateMonkeyNukeops # Version for Nukeops that spawns a syndicate monkey with the NukeOperative component. - name: uplink-reinforcement-radio-monkey-name - description: uplink-reinforcement-radio-monkey-desc - productEntity: ReinforcementRadioSyndicateMonkeyNukeops + id: UplinkReinforcementRadioSyndicateAncestorNukeops # Version for Nukeops that spawns a syndicate monkey with the NukeOperative component. + name: uplink-reinforcement-radio-ancestor-name + description: uplink-reinforcement-radio-ancestor-desc + productEntity: ReinforcementRadioSyndicateAncestorNukeops icon: { sprite: Objects/Devices/communication.rsi, state: old-radio } cost: Telecrystal: 6 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index c6404ab370..4f3136fcbb 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -1336,9 +1336,10 @@ - type: entity name: kobold - id: MobKobold + id: MobBaseKobold parent: MobBaseAncestor description: Cousins to the sentient race of lizard people, kobolds blend in with their natural habitat and are as nasty as monkeys; ready to pull out your hair and stab you to death. + abstract: true components: - type: NameIdentifier group: Kobold @@ -1432,15 +1433,6 @@ spawned: - id: FoodMeat amount: 2 - - type: Clumsy - clumsyDamage: - types: - Blunt: 2 - Piercing: 7 - groups: - Burn: 3 - clumsySound: - path: /Audio/Voice/Reptilian/reptilian_scream.ogg - type: AlwaysRevolutionaryConvertible - type: GhostTakeoverAvailable - type: SentienceTarget @@ -1451,6 +1443,55 @@ name: ghost-role-information-kobold-name description: ghost-role-information-kobold-description +- type: entity + name: kobold + id: MobKobold + parent: MobBaseKobold + description: Cousins to the sentient race of lizard people, kobolds blend in with their natural habitat and are as nasty as monkeys; ready to pull out your hair and stab you to death. + components: + - type: Clumsy + clumsyDamage: + types: + Blunt: 2 + Piercing: 7 + groups: + Burn: 3 + clumsySound: + path: /Audio/Voice/Reptilian/reptilian_scream.ogg + +- type: entity + id: MobBaseSyndicateKobold + parent: MobBaseKobold + suffix: syndicate base + components: + - type: MobThresholds + thresholds: + 0: Alive + 75: Critical + 200: Dead + - type: NpcFactionMember + factions: + - Syndicate + - type: Loadout + prototypes: [SyndicateOperativeGearMonkey] + +- type: entity + id: MobKoboldSyndicateAgent + parent: MobBaseSyndicateKobold + suffix: syndicate agent + components: + # make the player a traitor once its taken + - type: AutoTraitor + giveUplink: false + giveObjectives: false + +- type: entity + id: MobKoboldSyndicateAgentNukeops # Reinforcement exclusive to nukeops uplink + parent: MobBaseSyndicateKobold + suffix: NukeOps + components: + - type: NukeOperative + - type: entity name: guidebook monkey parent: MobMonkey diff --git a/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml b/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml index f6d57f53a5..3ce39afceb 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml @@ -29,9 +29,9 @@ - type: entity parent: ReinforcementRadioSyndicate - id: ReinforcementRadioSyndicateMonkey - name: syndicate monkey reinforcement radio - description: Calls in a specially trained monkey to assist you. + id: ReinforcementRadioSyndicateAncestor + name: syndicate genetic ancestor reinforcement radio + description: Calls in a specially trained ancestor of your choosing to assist you. components: - type: GhostRole name: ghost-role-information-syndicate-monkey-reinforcement-name @@ -39,14 +39,16 @@ rules: ghost-role-information-syndicate-monkey-reinforcement-rules - type: GhostRoleMobSpawner prototype: MobMonkeySyndicateAgent + selectablePrototypes: ["SyndicateMonkey", "SyndicateKobold"] - type: entity - parent: ReinforcementRadioSyndicateMonkey - id: ReinforcementRadioSyndicateMonkeyNukeops # Reinforcement radio exclusive to nukeops uplink + parent: ReinforcementRadioSyndicateAncestor + id: ReinforcementRadioSyndicateAncestorNukeops # Reinforcement radio exclusive to nukeops uplink suffix: NukeOps components: - type: GhostRoleMobSpawner prototype: MobMonkeySyndicateAgentNukeops + selectablePrototypes: ["SyndicateMonkeyNukeops", "SyndicateKoboldNukeops"] - type: entity parent: ReinforcementRadioSyndicate diff --git a/Resources/Prototypes/Roles/Ghostroles/syndicate.yml b/Resources/Prototypes/Roles/Ghostroles/syndicate.yml new file mode 100644 index 0000000000..24c0d8b3e3 --- /dev/null +++ b/Resources/Prototypes/Roles/Ghostroles/syndicate.yml @@ -0,0 +1,27 @@ +- type: ghostRole + id: SyndicateKobold + name: ghost-role-information-syndicate-kobold-reinforcement-name + description: ghost-role-information-syndicate-kobold-reinforcement-description + rules: ghost-role-information-syndicate-kobold-reinforcement-rules + entityPrototype: MobKoboldSyndicateAgent + +- type: ghostRole + id: SyndicateKoboldNukeops + name: ghost-role-information-syndicate-kobold-reinforcement-name + description: ghost-role-information-syndicate-kobold-reinforcement-description + rules: ghost-role-information-syndicate-kobold-reinforcement-rules + entityPrototype: MobKoboldSyndicateAgentNukeops + +- type: ghostRole + id: SyndicateMonkey + name: ghost-role-information-syndicate-monkey-reinforcement-name + description: ghost-role-information-syndicate-monkey-reinforcement-description + rules: ghost-role-information-syndicate-monkey-reinforcement-name + entityPrototype: MobMonkeySyndicateAgent + +- type: ghostRole + id: SyndicateMonkeyNukeops + name: ghost-role-information-syndicate-monkey-reinforcement-name + description: ghost-role-information-syndicate-monkey-reinforcement-description + rules: ghost-role-information-syndicate-monkey-reinforcement-name + entityPrototype: MobMonkeySyndicateAgentNukeops \ No newline at end of file diff --git a/RobustToolbox b/RobustToolbox index 123d0ae6ac..eb63809999 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 123d0ae6acf397654c135fe2b8f5268ff80c9bcf +Subproject commit eb638099999dce3a43d90772ca976ae010d649c0