--- /dev/null
+using Content.Shared.Ghost.Roles;
+using Robust.Client.UserInterface;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Ghost;
+
+public sealed class GhostRoleRadioBoundUserInterface : BoundUserInterface
+{
+ private GhostRoleRadioMenu? _ghostRoleRadioMenu;
+
+ public GhostRoleRadioBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ IoCManager.InjectDependencies(this);
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+
+ _ghostRoleRadioMenu = this.CreateWindow<GhostRoleRadioMenu>();
+ _ghostRoleRadioMenu.SetEntity(Owner);
+ _ghostRoleRadioMenu.SendGhostRoleRadioMessageAction += SendGhostRoleRadioMessage;
+ }
+
+ public void SendGhostRoleRadioMessage(ProtoId<GhostRolePrototype> protoId)
+ {
+ SendMessage(new GhostRoleRadioMessage(protoId));
+ }
+}
--- /dev/null
+<ui:RadialMenu
+ xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
+ CloseButtonStyleClass="RadialMenuCloseButton"
+ VerticalExpand="True"
+ HorizontalExpand="True">
+ <ui:RadialContainer Name="Main">
+ </ui:RadialContainer>
+</ui:RadialMenu>
--- /dev/null
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Ghost.Roles;
+using Content.Shared.Ghost.Roles.Components;
+using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using System.Numerics;
+
+namespace Content.Client.Ghost;
+
+public sealed partial class GhostRoleRadioMenu : RadialMenu
+{
+ [Dependency] private readonly EntityManager _entityManager = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+
+ public event Action<ProtoId<GhostRolePrototype>>? SendGhostRoleRadioMessageAction;
+
+ public EntityUid Entity { get; set; }
+
+ public GhostRoleRadioMenu()
+ {
+ IoCManager.InjectDependencies(this);
+ RobustXamlLoader.Load(this);
+ }
+
+ public void SetEntity(EntityUid uid)
+ {
+ Entity = uid;
+ RefreshUI();
+ }
+
+ private void RefreshUI()
+ {
+ // The main control that will contain all of the clickable options
+ var main = FindControl<RadialContainer>("Main");
+
+ // The purpose of this radial UI is for ghost role radios that allow you to select
+ // more than one potential option, such as with kobolds/lizards.
+ // This means that it won't show anything if SelectablePrototypes is empty.
+ if (!_entityManager.TryGetComponent<GhostRoleMobSpawnerComponent>(Entity, out var comp))
+ return;
+
+ foreach (var ghostRoleProtoString in comp.SelectablePrototypes)
+ {
+ // For each prototype we find we want to create a button that uses the name of the ghost role
+ // as the hover tooltip, and the icon is taken from either the ghost role entityprototype
+ // or the indicated icon entityprototype.
+ if (!_prototypeManager.TryIndex<GhostRolePrototype>(ghostRoleProtoString, out var ghostRoleProto))
+ continue;
+
+ var button = new GhostRoleRadioMenuButton()
+ {
+ StyleClasses = { "RadialMenuButton" },
+ SetSize = new Vector2(64, 64),
+ ToolTip = Loc.GetString(ghostRoleProto.Name),
+ ProtoId = ghostRoleProto.ID,
+ };
+
+ var entProtoView = new EntityPrototypeView()
+ {
+ SetSize = new Vector2(48, 48),
+ VerticalAlignment = VAlignment.Center,
+ HorizontalAlignment = HAlignment.Center,
+ Stretch = SpriteView.StretchMode.Fill
+ };
+
+ // pick the icon if it exists, otherwise fallback to the ghost role's entity
+ if (_prototypeManager.TryIndex(ghostRoleProto.IconPrototype, out var iconProto))
+ entProtoView.SetPrototype(iconProto);
+ else
+ entProtoView.SetPrototype(comp.Prototype);
+
+ button.AddChild(entProtoView);
+ main.AddChild(button);
+ AddGhostRoleRadioMenuButtonOnClickActions(main);
+ }
+ }
+
+ private void AddGhostRoleRadioMenuButtonOnClickActions(Control control)
+ {
+ var mainControl = control as RadialContainer;
+
+ if (mainControl == null)
+ return;
+
+ foreach (var child in mainControl.Children)
+ {
+ var castChild = child as GhostRoleRadioMenuButton;
+
+ if (castChild == null)
+ continue;
+
+ castChild.OnButtonUp += _ =>
+ {
+ SendGhostRoleRadioMessageAction?.Invoke(castChild.ProtoId);
+ Close();
+ };
+ }
+ }
+}
+
+public sealed class GhostRoleRadioMenuButton : RadialMenuTextureButton
+{
+ public ProtoId<GhostRolePrototype> ProtoId { get; set; }
+}
using Content.Server.Bible.Components;
-using Content.Server.Ghost.Roles.Components;
using Content.Server.Ghost.Roles.Events;
using Content.Server.Popups;
using Content.Shared.ActionBlocker;
using Content.Shared.Actions;
using Content.Shared.Bible;
using Content.Shared.Damage;
+using Content.Shared.Ghost.Roles.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Inventory;
using Content.Server.EUI;
using Content.Server.Ghost.Roles.Components;
using Content.Server.Ghost.Roles.Events;
-using Content.Server.Ghost.Roles.Raffles;
using Content.Shared.Ghost.Roles.Raffles;
using Content.Server.Ghost.Roles.UI;
using Content.Server.Mind.Commands;
using Content.Server.Popups;
using Content.Shared.Verbs;
using Robust.Shared.Collections;
+using Content.Shared.Ghost.Roles.Components;
namespace Content.Server.Ghost.Roles
{
SubscribeLocalEvent<GhostRoleMobSpawnerComponent, TakeGhostRoleEvent>(OnSpawnerTakeRole);
SubscribeLocalEvent<GhostTakeoverAvailableComponent, TakeGhostRoleEvent>(OnTakeoverTakeRole);
SubscribeLocalEvent<GhostRoleMobSpawnerComponent, GetVerbsEvent<Verb>>(OnVerb);
+ SubscribeLocalEvent<GhostRoleMobSpawnerComponent, GhostRoleRadioMessage>(OnGhostRoleRadioMessage);
_playerManager.PlayerStatusChanged += PlayerStatusChanged;
}
_popupSystem.PopupEntity(msg, uid, userUid.Value);
}
}
+
+ public void OnGhostRoleRadioMessage(Entity<GhostRoleMobSpawnerComponent> entity, ref GhostRoleRadioMessage args)
+ {
+ if (!_prototype.TryIndex(args.ProtoId, out var ghostRoleProto))
+ return;
+
+ // if the prototype chosen isn't actually part of the selectable options, ignore it
+ foreach (var selectableProto in entity.Comp.SelectablePrototypes)
+ {
+ if (selectableProto == ghostRoleProto.EntityPrototype.Id)
+ return;
+ }
+
+ SetMode(entity.Owner, ghostRoleProto, ghostRoleProto.Name, entity.Comp);
+ }
}
[AnyCommand]
using Content.Server.Mind;
using Content.Server.Mind.Commands;
using Content.Server.NPC;
-using Content.Server.NPC.Components;
using Content.Server.NPC.HTN;
using Content.Server.NPC.Systems;
using Content.Server.Roles;
using Content.Shared.Interaction.Components;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
-using Content.Shared.Mobs.Systems;
using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Systems;
-using Content.Shared.NPC.Components;
using Content.Shared.NPC.Systems;
using Content.Shared.Nutrition.AnimalHusbandry;
using Content.Shared.Nutrition.Components;
using Content.Shared.Prying.Components;
using Content.Shared.Traits.Assorted;
using Robust.Shared.Audio.Systems;
+using Content.Shared.Ghost.Roles.Components;
namespace Content.Server.Zombies
{
--- /dev/null
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Ghost.Roles;
+
+[Serializable, NetSerializable]
+public sealed class GhostRoleRadioMessage : BoundUserInterfaceMessage
+{
+ public ProtoId<GhostRolePrototype> ProtoId;
+
+ public GhostRoleRadioMessage(ProtoId<GhostRolePrototype> protoId)
+ {
+ ProtoId = protoId;
+ }
+}
+
+[Serializable, NetSerializable]
+public enum GhostRoleRadioUiKey : byte
+{
+ Key
+}
-using Robust.Shared.Prototypes;
+using Robust.Shared.Prototypes;
-namespace Content.Server.Ghost.Roles.Components
+namespace Content.Shared.Ghost.Roles.Components
{
/// <summary>
/// Allows a ghost to take this role, spawning a new entity.
/// </summary>
[RegisterComponent, EntityCategory("Spawner")]
- [Access(typeof(GhostRoleSystem))]
public sealed partial class GhostRoleMobSpawnerComponent : Component
{
[DataField]
[DataField(required: true)]
public EntProtoId EntityPrototype;
+ /// <summary>
+ /// The entity prototype's sprite to use to represent the ghost role
+ /// Use this if you don't want to use the entity itself
+ /// </summary>
+ [DataField]
+ public EntProtoId? IconPrototype = null;
+
/// <summary>
/// Rules of the ghostrole
/// </summary>
ghost-role-information-syndicate-reinforcement-description = Someone needs reinforcements. You, the first person the syndicate could find, will help them.
ghost-role-information-syndicate-reinforcement-rules = You are a [color=red][bold]Team Antagonist[/bold][/color] with the agent who summoned you.
+ghost-role-information-syndicate-reinforcement-medic-name = Syndicate Medic
+ghost-role-information-syndicate-reinforcement-medic-description = Someone needs reinforcements. Your task is to keep the agent who called you alive.
+
+ghost-role-information-syndicate-reinforcement-spy-name = Syndicate Spy
+ghost-role-information-syndicate-reinforcement-spy-description = Someone needs reinforcements. Your speciality lies in espionage, do not be discovered.
+
+ghost-role-information-syndicate-reinforcement-thief-name = Syndicate Thief
+ghost-role-information-syndicate-reinforcement-thief-description = Someone needs reinforcements. Your job is to break in and retrieve something valuable for your agent.
+
+
ghost-role-information-syndicate-monkey-reinforcement-name = Syndicate Monkey Agent
ghost-role-information-syndicate-monkey-reinforcement-description = Someone needs reinforcements. You, a trained monkey, will help them.
ghost-role-information-syndicate-monkey-reinforcement-rules = You are a [color=red][bold]Team Antagonist[/bold][/color] with the agent who summoned you.
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.
+uplink-reinforcement-radio-traitor-desc = Radio in a reinforcement agent of extremely questionable quality. No off button, buy this if you're ready to party. Call in a medic or spy or thief to help you out. Good luck.
+uplink-reinforcement-radio-nukeops-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.
uplink-reinforcement-radio-cyborg-assault-name = Syndicate Assault Cyborg Teleporter
uplink-reinforcement-radio-cyborg-assault-desc = A lean, mean killing machine with access to an Energy Sword, LMG, Cryptographic Sequencer, and a Pinpointer.
- type: listing
id: UplinkReinforcementRadioSyndicate
name: uplink-reinforcement-radio-name
- description: uplink-reinforcement-radio-desc
+ description: uplink-reinforcement-radio-traitor-desc
productEntity: ReinforcementRadioSyndicate
icon: { sprite: Objects/Devices/communication.rsi, state: old-radio-urist }
cost:
- type: listing
id: UplinkReinforcementRadioSyndicateNukeops # Version for Nukeops that spawns an agent with the NukeOperative component.
name: uplink-reinforcement-radio-name
- description: uplink-reinforcement-radio-desc
+ description: uplink-reinforcement-radio-nukeops-desc
productEntity: ReinforcementRadioSyndicateNukeops
icon: { sprite: Objects/Devices/communication.rsi, state: old-radio-urist }
cost:
giveUplink: false
giveObjectives: false
+- type: entity
+ parent: MobHumanSyndicateAgent
+ id: MobHumanSyndicateAgentMedic
+ name: syndicate medic
+ components:
+ - type: Loadout
+ prototypes: [SyndicateReinforcementMedic]
+
+- type: entity
+ parent: MobHumanSyndicateAgent
+ id: MobHumanSyndicateAgentSpy
+ name: syndicate spy
+ components:
+ - type: Loadout
+ prototypes: [SyndicateReinforcementSpy]
+
+- type: entity
+ parent: MobHumanSyndicateAgent
+ id: MobHumanSyndicateAgentThief
+ name: syndicate thief
+ components:
+ - type: Loadout
+ prototypes: [SyndicateReinforcementThief]
+
- type: entity
parent: MobHumanSyndicateAgentBase
id: MobHumanSyndicateAgentNukeops # Reinforcement exclusive to nukeops uplink
- type: entity
parent: BaseItem
- id: ReinforcementRadioSyndicate
+ abstract: true
+ id: ReinforcementRadio
name: syndicate reinforcement radio
description: Call in a syndicate agent of questionable quality, instantly! Only basic equipment provided.
components:
sprite: Objects/Devices/communication.rsi
layers:
- state: old-radio
+ - type: UserInterface
+ interfaces:
+ enum.GhostRoleRadioUiKey.Key:
+ type: GhostRoleRadioBoundUserInterface
+ - type: ActivatableUI
+ key: enum.GhostRoleRadioUiKey.Key
+
+- type: entity
+ parent: ReinforcementRadio
+ id: ReinforcementRadioSyndicate
+ name: syndicate reinforcement radio
+ description: Call in a syndicate agent of questionable quality, instantly!
+ components:
- type: GhostRole
- name: ghost-role-information-syndicate-reinforcement-name
+ name: ghost-role-information-syndicate-reinforcement-spy-name
description: ghost-role-information-syndicate-reinforcement-description
rules: ghost-role-information-syndicate-reinforcement-rules
raffle:
settings: default
- type: GhostRoleMobSpawner
- prototype: MobHumanSyndicateAgent
- - type: EmitSoundOnUse
- sound: /Audio/Effects/Emotes/parp1.ogg
- - type: UseDelay
- delay: 300
+ prototype: MobHumanSyndicateAgentSpy
+ selectablePrototypes: ["SyndicateAgentMedic", "SyndicateAgentSpy", "SyndicateAgentThief"]
- type: entity
- parent: ReinforcementRadioSyndicate
+ parent: ReinforcementRadio
id: ReinforcementRadioSyndicateNukeops # Reinforcement radio exclusive to nukeops uplink
suffix: NukeOps
components:
+ - type: GhostRole
+ name: ghost-role-information-syndicate-reinforcement-name
+ description: ghost-role-information-syndicate-reinforcement-description
+ rules: ghost-role-information-syndicate-reinforcement-rules
- type: GhostRoleMobSpawner
prototype: MobHumanSyndicateAgentNukeops
- type: entity
- parent: ReinforcementRadioSyndicate
+ parent: ReinforcementRadio
id: ReinforcementRadioSyndicateAncestor
name: syndicate genetic ancestor reinforcement radio
description: Calls in a specially trained ancestor of your choosing to assist you.
selectablePrototypes: ["SyndicateMonkeyNukeops", "SyndicateKoboldNukeops"]
- type: entity
- parent: ReinforcementRadioSyndicate
+ parent: ReinforcementRadio
id: ReinforcementRadioSyndicateSyndiCat
name: syndicat reinforcement radio
description: Calls in a faithfully trained cat with a microbomb to assist you.
sound: /Audio/Animals/cat_meow.ogg
- type: entity
- parent: ReinforcementRadioSyndicate
+ parent: ReinforcementRadio
id: ReinforcementRadioSyndicateCyborgAssault # Reinforcement radio exclusive to nukeops uplink
name: syndicate assault cyborg reinforcement radio
description: Call in a well armed assault cyborg, instantly!
- PinpointerSyndicateNuclear
- DeathAcidifierImplanter
+- type: startingGear
+ id: SyndicateOperativeClothing
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitOperative
+ back: ClothingBackpackSyndicate
+ shoes: ClothingShoesBootsCombatFilled
+ gloves: ClothingHandsGlovesColorBlack
+
+- type: startingGear
+ id: SyndicateReinforcementMedic
+ parent: SyndicateOperativeClothing
+ equipment:
+ pocket1: WeaponPistolViper
+ inhand:
+ - MedkitCombatFilled
+ storage:
+ back:
+ - BoxSurvivalSyndicate
+
+- type: startingGear
+ id: SyndicateReinforcementSpy
+ parent: SyndicateOperativeClothing
+ equipment:
+ id: AgentIDCard
+ mask: ClothingMaskGasVoiceChameleon
+ pocket1: WeaponPistolViper
+ storage:
+ back:
+ - BoxSurvivalSyndicate
+
+- type: startingGear
+ id: SyndicateReinforcementThief
+ parent: SyndicateOperativeClothing
+ equipment:
+ pocket1: WeaponPistolViper
+ inhand:
+ - ToolboxSyndicateFilled
+ storage:
+ back:
+ - BoxSurvivalSyndicate
+ - SyndicateJawsOfLife
+
# Syndicate Reinforcement NukeOps
- type: startingGear
id: SyndicateOperativeGearReinforcementNukeOps
equipment:
id: SyndiPDA #Do not give a PDA to the normal Reinforcement - it will spawn with a 20TC uplink
+
#Syndicate Operative Outfit - Basic
- type: startingGear
id: SyndicateOperativeGearBasic
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
+ entityPrototype: MobMonkeySyndicateAgentNukeops
+
+- type: ghostRole
+ id: SyndicateAgentMedic
+ name: ghost-role-information-syndicate-reinforcement-medic-name
+ description: ghost-role-information-syndicate-reinforcement-medic-description
+ rules: ghost-role-information-syndicate-monkey-reinforcement-rules
+ entityPrototype: MobHumanSyndicateAgentMedic
+ iconPrototype: MedkitCombat
+
+- type: ghostRole
+ id: SyndicateAgentSpy
+ name: ghost-role-information-syndicate-reinforcement-spy-name
+ description: ghost-role-information-syndicate-reinforcement-spy-description
+ rules: ghost-role-information-syndicate-monkey-reinforcement-rules
+ entityPrototype: MobHumanSyndicateAgentSpy
+ iconPrototype: ClothingMaskGasVoiceChameleon
+
+- type: ghostRole
+ id: SyndicateAgentThief
+ name: ghost-role-information-syndicate-reinforcement-thief-name
+ description: ghost-role-information-syndicate-reinforcement-thief-description
+ rules: ghost-role-information-syndicate-monkey-reinforcement-rules
+ entityPrototype: MobHumanSyndicateAgentThief
+ iconPrototype: SyndicateJawsOfLife