using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
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<EntityPrototype>))]
- public string? Prototype { get; private set; }
+ [DataField]
+ public EntProtoId? Prototype;
+
+ /// <summary>
+ /// If this ghostrole spawner has multiple selectable ghostrole prototypes.
+ /// </summary>
+ [DataField]
+ public List<string> SelectablePrototypes = [];
}
}
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
{
[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;
SubscribeLocalEvent<GhostRoleComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<GhostRoleMobSpawnerComponent, TakeGhostRoleEvent>(OnSpawnerTakeRole);
SubscribeLocalEvent<GhostTakeoverAvailableComponent, TakeGhostRoleEvent>(OnTakeoverTakeRole);
+ SubscribeLocalEvent<GhostRoleMobSpawnerComponent, GetVerbsEvent<Verb>>(OnVerb);
_playerManager.PlayerStatusChanged += PlayerStatusChanged;
}
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));
public void OpenEui(ICommonSession session)
{
- if (session.AttachedEntity is not {Valid: true} attached ||
+ if (session.AttachedEntity is not { Valid: true } attached ||
!EntityManager.HasComponent<GhostComponent>(attached))
return;
- if(_openUis.ContainsKey(session))
+ if (_openUis.ContainsKey(session))
CloseEui(session);
var eui = _openUis[session] = new GhostRolesEui();
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();
args.TookRole = true;
}
+
+ private void OnVerb(EntityUid uid, GhostRoleMobSpawnerComponent component, GetVerbsEvent<Verb> args)
+ {
+ var prototypes = component.SelectablePrototypes;
+ if (prototypes.Count < 1)
+ return;
+
+ if (!args.CanAccess || !args.CanInteract || args.Hands == null)
+ return;
+
+ var verbs = new ValueList<Verb>();
+
+ foreach (var prototypeID in prototypes)
+ {
+ if (_prototype.TryIndex<GhostRolePrototype>(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<GhostRoleComponent>(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]
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
- if(shell.Player != null)
+ if (shell.Player != null)
EntitySystem.Get<GhostRoleSystem>().OpenEui(shell.Player);
else
shell.WriteLine("You can only open the ghost roles UI on a client.");
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Ghost.Roles;
+
+/// <summary>
+/// For selectable ghostrole prototypes in ghostrole spawners.
+/// </summary>
+[Prototype]
+public sealed partial class GhostRolePrototype : IPrototype
+{
+ [ViewVariables]
+ [IdDataField]
+ public string ID { get; private set; } = default!;
+
+ /// <summary>
+ /// The name of the ghostrole.
+ /// </summary>
+ [DataField]
+ public string Name { get; set; } = default!;
+
+ /// <summary>
+ /// The description of the ghostrole.
+ /// </summary>
+ [DataField]
+ public string Description { get; set; } = default!;
+
+ /// <summary>
+ /// The entity prototype of the ghostrole
+ /// </summary>
+ [DataField]
+ public string EntityPrototype = default!;
+
+ /// <summary>
+ /// Rules of the ghostrole
+ /// </summary>
+ [DataField]
+ public string Rules = default!;
+}
\ No newline at end of file
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.
--- /dev/null
+ghostrole-spawner-select = Selected: {$mode}
\ No newline at end of file
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.
- 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
- 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
- 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
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
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
- 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
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
--- /dev/null
+- 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
WeaponSubMachineGunVectorRubber: WeaponSubMachineGunVector
WeaponSubMachineGunDrozdRubber: WeaponSubMachineGunDrozd
WeaponRifleLecterRubber: WeaponRifleLecter
+ReinforcementRadioSyndicateMonkey: ReinforcementRadioSyndicateAncestor
+ReinforcementRadioSyndicateMonkeyNukeops: ReinforcementRadioSyndicateAncestorNukeops