mirror of
https://github.com/space-wizards/space-station-14.git
synced 2026-02-14 19:29:53 +01:00
Predict borgs (#41600)
* predict borgs * small fix * fix MMI item slot serialization * fix movement speed for mothership core * review and minor improvement * fix resolve * review
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
using Content.Shared.Silicons.Borgs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Silicons.Borgs;
|
||||
@@ -24,31 +23,29 @@ public sealed class BorgBoundUserInterface : BoundUserInterface
|
||||
|
||||
_menu.BrainButtonPressed += () =>
|
||||
{
|
||||
SendMessage(new BorgEjectBrainBuiMessage());
|
||||
SendPredictedMessage(new BorgEjectBrainBuiMessage());
|
||||
};
|
||||
|
||||
_menu.EjectBatteryButtonPressed += () =>
|
||||
{
|
||||
SendMessage(new BorgEjectBatteryBuiMessage());
|
||||
SendPredictedMessage(new BorgEjectBatteryBuiMessage());
|
||||
};
|
||||
|
||||
_menu.NameChanged += name =>
|
||||
{
|
||||
SendMessage(new BorgSetNameBuiMessage(name));
|
||||
SendPredictedMessage(new BorgSetNameBuiMessage(name));
|
||||
};
|
||||
|
||||
_menu.RemoveModuleButtonPressed += module =>
|
||||
{
|
||||
SendMessage(new BorgRemoveModuleBuiMessage(EntMan.GetNetEntity(module)));
|
||||
SendPredictedMessage(new BorgRemoveModuleBuiMessage(EntMan.GetNetEntity(module)));
|
||||
};
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
public override void Update()
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (state is not BorgBuiState msg)
|
||||
return;
|
||||
_menu?.UpdateState(msg);
|
||||
_menu?.UpdateBatteryButton();
|
||||
_menu?.UpdateBrainButton();
|
||||
_menu?.UpdateModulePanel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.NameIdentifier;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Silicons.Borgs;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
using Content.Shared.PowerCell;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
@@ -20,6 +20,8 @@ public sealed partial class BorgMenu : FancyWindow
|
||||
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entity = default!;
|
||||
private readonly NameModifierSystem _nameModifier;
|
||||
private readonly PowerCellSystem _powerCell;
|
||||
private readonly PredictedBatterySystem _battery;
|
||||
|
||||
public Action? BrainButtonPressed;
|
||||
public Action? EjectBatteryButtonPressed;
|
||||
@@ -41,6 +43,8 @@ public sealed partial class BorgMenu : FancyWindow
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_nameModifier = _entity.System<NameModifierSystem>();
|
||||
_powerCell = _entity.System<PowerCellSystem>();
|
||||
_battery = _entity.System<PredictedBatterySystem>();
|
||||
|
||||
_maxNameLength = _cfgManager.GetCVar(CCVars.MaxNameLength);
|
||||
|
||||
@@ -52,8 +56,6 @@ public sealed partial class BorgMenu : FancyWindow
|
||||
NameLineEdit.OnTextChanged += OnNameChanged;
|
||||
NameLineEdit.OnTextEntered += OnNameEntered;
|
||||
NameLineEdit.OnFocusExit += OnNameFocusExit;
|
||||
|
||||
UpdateBrainButton();
|
||||
}
|
||||
|
||||
public void SetEntity(EntityUid entity)
|
||||
@@ -73,6 +75,10 @@ public sealed partial class BorgMenu : FancyWindow
|
||||
NameIdentifierLabel.Visible = false;
|
||||
NameLineEdit.Text = _entity.GetComponent<MetaDataComponent>(Entity).EntityName;
|
||||
}
|
||||
|
||||
UpdateBatteryButton();
|
||||
UpdateBrainButton();
|
||||
UpdateModulePanel();
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
@@ -80,21 +86,24 @@ public sealed partial class BorgMenu : FancyWindow
|
||||
base.FrameUpdate(args);
|
||||
|
||||
AccumulatedTime += args.DeltaSeconds;
|
||||
BorgSprite.OverrideDirection = (Direction) ((int) AccumulatedTime % 4 * 2);
|
||||
}
|
||||
BorgSprite.OverrideDirection = (Direction)((int)AccumulatedTime % 4 * 2);
|
||||
|
||||
public void UpdateState(BorgBuiState state)
|
||||
{
|
||||
EjectBatteryButton.Disabled = !state.HasBattery;
|
||||
ChargeBar.Value = state.ChargePercent;
|
||||
var chargeFraction = 0f;
|
||||
|
||||
if (_powerCell.TryGetBatteryFromSlot(Entity, out var battery))
|
||||
chargeFraction = _battery.GetCharge(battery.Value.AsNullable()) / battery.Value.Comp.MaxCharge;
|
||||
|
||||
ChargeBar.Value = chargeFraction;
|
||||
ChargeLabel.Text = Loc.GetString("borg-ui-charge-label",
|
||||
("charge", (int) MathF.Round(state.ChargePercent * 100)));
|
||||
|
||||
UpdateBrainButton();
|
||||
UpdateModulePanel();
|
||||
("charge", (int)MathF.Round(chargeFraction * 100)));
|
||||
}
|
||||
|
||||
private void UpdateBrainButton()
|
||||
public void UpdateBatteryButton()
|
||||
{
|
||||
EjectBatteryButton.Disabled = !_powerCell.HasBattery(Entity);
|
||||
}
|
||||
|
||||
public void UpdateBrainButton()
|
||||
{
|
||||
if (_entity.TryGetComponent(Entity, out BorgChassisComponent? chassis) && chassis.BrainEntity is { } brain)
|
||||
{
|
||||
@@ -113,7 +122,7 @@ public sealed partial class BorgMenu : FancyWindow
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateModulePanel()
|
||||
public void UpdateModulePanel()
|
||||
{
|
||||
if (!_entity.TryGetComponent(Entity, out BorgChassisComponent? chassis))
|
||||
return;
|
||||
|
||||
83
Content.Client/Silicons/Borgs/BorgSystem.Battery.cs
Normal file
83
Content.Client/Silicons/Borgs/BorgSystem.Battery.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using Content.Shared.PowerCell.Components;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Client.Silicons.Borgs;
|
||||
|
||||
public sealed partial class BorgSystem
|
||||
{
|
||||
// How often to update the battery alert.
|
||||
// Also gets updated instantly when switching bodies or a battery is inserted or removed.
|
||||
private static readonly TimeSpan AlertUpdateDelay = TimeSpan.FromSeconds(0.5f);
|
||||
|
||||
// Don't put this on the component because we only need to track the time for a single entity
|
||||
// and we don't want to TryComp it every single tick.
|
||||
private TimeSpan _nextAlertUpdate = TimeSpan.Zero;
|
||||
private EntityQuery<BorgChassisComponent> _chassisQuery;
|
||||
private EntityQuery<PowerCellSlotComponent> _slotQuery;
|
||||
|
||||
public void InitializeBattery()
|
||||
{
|
||||
SubscribeLocalEvent<BorgChassisComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<BorgChassisComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
|
||||
|
||||
_chassisQuery = GetEntityQuery<BorgChassisComponent>();
|
||||
_slotQuery = GetEntityQuery<PowerCellSlotComponent>();
|
||||
}
|
||||
|
||||
private void OnPlayerAttached(Entity<BorgChassisComponent> ent, ref LocalPlayerAttachedEvent args)
|
||||
{
|
||||
UpdateBatteryAlert((ent.Owner, ent.Comp, null));
|
||||
}
|
||||
|
||||
private void OnPlayerDetached(Entity<BorgChassisComponent> ent, ref LocalPlayerDetachedEvent args)
|
||||
{
|
||||
// Remove all borg related alerts.
|
||||
_alerts.ClearAlert(ent.Owner, ent.Comp.BatteryAlert);
|
||||
_alerts.ClearAlert(ent.Owner, ent.Comp.NoBatteryAlert);
|
||||
}
|
||||
|
||||
private void UpdateBatteryAlert(Entity<BorgChassisComponent, PowerCellSlotComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp2, false))
|
||||
return;
|
||||
|
||||
if (!_powerCell.TryGetBatteryFromSlot((ent.Owner, ent.Comp2), out var battery))
|
||||
{
|
||||
_alerts.ShowAlert(ent.Owner, ent.Comp1.NoBatteryAlert);
|
||||
return;
|
||||
}
|
||||
|
||||
// Alert levels from 0 to 10.
|
||||
var chargeLevel = (short)MathF.Round(_battery.GetChargeLevel(battery.Value.AsNullable()) * 10f);
|
||||
|
||||
// we make sure 0 only shows if they have absolutely no battery.
|
||||
// also account for floating point imprecision
|
||||
if (chargeLevel == 0 && _powerCell.HasDrawCharge((ent.Owner, null, ent.Comp2)))
|
||||
{
|
||||
chargeLevel = 1;
|
||||
}
|
||||
|
||||
_alerts.ShowAlert(ent.Owner, ent.Comp1.BatteryAlert, chargeLevel);
|
||||
}
|
||||
|
||||
// Periodically update the charge indicator.
|
||||
// We do this with a client-side alert so that we don't have to network the charge level.
|
||||
public void UpdateBattery(float frameTime)
|
||||
{
|
||||
if (_player.LocalEntity is not { } localPlayer)
|
||||
return;
|
||||
|
||||
var curTime = _timing.CurTime;
|
||||
|
||||
if (curTime < _nextAlertUpdate)
|
||||
return;
|
||||
|
||||
_nextAlertUpdate = curTime + AlertUpdateDelay;
|
||||
|
||||
if (!_chassisQuery.TryComp(localPlayer, out var chassis) || !_slotQuery.TryComp(localPlayer, out var slot))
|
||||
return;
|
||||
|
||||
UpdateBatteryAlert((localPlayer, chassis, slot));
|
||||
}
|
||||
}
|
||||
@@ -1,72 +1,93 @@
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
using Content.Shared.PowerCell;
|
||||
using Content.Shared.Silicons.Borgs;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Silicons.Borgs;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class BorgSystem : SharedBorgSystem
|
||||
public sealed partial class BorgSystem : SharedBorgSystem
|
||||
{
|
||||
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
[Dependency] private readonly PowerCellSystem _powerCell = default!;
|
||||
[Dependency] private readonly PredictedBatterySystem _battery = default!;
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
InitializeBattery();
|
||||
|
||||
SubscribeLocalEvent<BorgChassisComponent, AppearanceChangeEvent>(OnBorgAppearanceChanged);
|
||||
SubscribeLocalEvent<MMIComponent, AppearanceChangeEvent>(OnMMIAppearanceChanged);
|
||||
}
|
||||
|
||||
private void OnBorgAppearanceChanged(EntityUid uid, BorgChassisComponent component, ref AppearanceChangeEvent args)
|
||||
public override void UpdateUI(Entity<BorgChassisComponent?> chassis)
|
||||
{
|
||||
if (_ui.TryGetOpenUi(chassis.Owner, BorgUiKey.Key, out var bui))
|
||||
bui.Update();
|
||||
}
|
||||
|
||||
private void OnBorgAppearanceChanged(Entity<BorgChassisComponent> chassis, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
UpdateBorgAppearance(uid, component, args.Component, args.Sprite);
|
||||
|
||||
UpdateBorgAppearance((chassis.Owner, chassis.Comp, args.Component, args.Sprite));
|
||||
}
|
||||
|
||||
protected override void OnInserted(EntityUid uid, BorgChassisComponent component, EntInsertedIntoContainerMessage args)
|
||||
protected override void OnInserted(Entity<BorgChassisComponent> chassis, ref EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (!component.Initialized)
|
||||
if (!chassis.Comp.Initialized)
|
||||
return;
|
||||
|
||||
base.OnInserted(uid, component, args);
|
||||
UpdateBorgAppearance(uid, component);
|
||||
base.OnInserted(chassis, ref args);
|
||||
UpdateUI(chassis.AsNullable());
|
||||
UpdateBorgAppearance((chassis, chassis.Comp));
|
||||
UpdateBatteryAlert((chassis.Owner, chassis.Comp, null));
|
||||
}
|
||||
|
||||
protected override void OnRemoved(EntityUid uid, BorgChassisComponent component, EntRemovedFromContainerMessage args)
|
||||
protected override void OnRemoved(Entity<BorgChassisComponent> chassis, ref EntRemovedFromContainerMessage args)
|
||||
{
|
||||
if (!component.Initialized)
|
||||
if (!chassis.Comp.Initialized)
|
||||
return;
|
||||
|
||||
base.OnRemoved(uid, component, args);
|
||||
UpdateBorgAppearance(uid, component);
|
||||
base.OnRemoved(chassis, ref args);
|
||||
UpdateUI(chassis.AsNullable());
|
||||
UpdateBorgAppearance((chassis, chassis.Comp));
|
||||
UpdateBatteryAlert((chassis.Owner, chassis.Comp, null));
|
||||
}
|
||||
|
||||
private void UpdateBorgAppearance(EntityUid uid,
|
||||
BorgChassisComponent? component = null,
|
||||
AppearanceComponent? appearance = null,
|
||||
SpriteComponent? sprite = null)
|
||||
private void UpdateBorgAppearance(Entity<BorgChassisComponent?, AppearanceComponent?, SpriteComponent?> ent)
|
||||
{
|
||||
if (!Resolve(uid, ref component, ref appearance, ref sprite))
|
||||
if (!Resolve(ent, ref ent.Comp1, ref ent.Comp2, ref ent.Comp3))
|
||||
return;
|
||||
|
||||
if (_appearance.TryGetData<MobState>(uid, MobStateVisuals.State, out var state, appearance))
|
||||
if (_appearance.TryGetData<MobState>(ent.Owner, MobStateVisuals.State, out var state, ent.Comp2))
|
||||
{
|
||||
if (state != MobState.Alive)
|
||||
{
|
||||
_sprite.LayerSetVisible((uid, sprite), BorgVisualLayers.Light, false);
|
||||
_sprite.LayerSetVisible((ent.Owner, ent.Comp3), BorgVisualLayers.Light, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_appearance.TryGetData<bool>(uid, BorgVisuals.HasPlayer, out var hasPlayer, appearance))
|
||||
if (!_appearance.TryGetData<bool>(ent.Owner, BorgVisuals.HasPlayer, out var hasPlayer, ent.Comp2))
|
||||
hasPlayer = false;
|
||||
|
||||
_sprite.LayerSetVisible((uid, sprite), BorgVisualLayers.Light, component.BrainEntity != null || hasPlayer);
|
||||
_sprite.LayerSetRsiState((uid, sprite), BorgVisualLayers.Light, hasPlayer ? component.HasMindState : component.NoMindState);
|
||||
_sprite.LayerSetVisible((ent.Owner, ent.Comp3), BorgVisualLayers.Light, ent.Comp1.BrainEntity != null || hasPlayer);
|
||||
_sprite.LayerSetRsiState((ent.Owner, ent.Comp3), BorgVisualLayers.Light, hasPlayer ? ent.Comp1.HasMindState : ent.Comp1.NoMindState);
|
||||
}
|
||||
|
||||
private void OnMMIAppearanceChanged(EntityUid uid, MMIComponent component, ref AppearanceChangeEvent args)
|
||||
@@ -107,4 +128,10 @@ public sealed class BorgSystem : SharedBorgSystem
|
||||
borg.Comp.HasMindState = hasMindState;
|
||||
borg.Comp.NoMindState = noMindState;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
UpdateBattery(frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Light;
|
||||
using Content.Shared.Light.Components;
|
||||
@@ -44,7 +43,6 @@ namespace Content.Server.Light.EntitySystems
|
||||
SubscribeLocalEvent<HandheldLightComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<HandheldLightComponent, ComponentShutdown>(OnShutdown);
|
||||
|
||||
SubscribeLocalEvent<HandheldLightComponent, ExaminedEvent>(OnExamine);
|
||||
|
||||
SubscribeLocalEvent<HandheldLightComponent, ActivateInWorldEvent>(OnActivate);
|
||||
|
||||
@@ -142,13 +140,6 @@ namespace Content.Server.Light.EntitySystems
|
||||
return ent.Comp.Activated ? TurnOff(ent) : TurnOn(user, ent);
|
||||
}
|
||||
|
||||
private void OnExamine(EntityUid uid, HandheldLightComponent component, ExaminedEvent args)
|
||||
{
|
||||
args.PushMarkup(component.Activated
|
||||
? Loc.GetString("handheld-light-component-on-examine-is-on-message")
|
||||
: Loc.GetString("handheld-light-component-on-examine-is-off-message"));
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Silicons.Borgs;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed partial class BorgSystem
|
||||
{
|
||||
|
||||
[Dependency] private readonly SharedRoleSystem _roles = default!;
|
||||
|
||||
public void InitializeMMI()
|
||||
{
|
||||
SubscribeLocalEvent<MMIComponent, ComponentInit>(OnMMIInit);
|
||||
SubscribeLocalEvent<MMIComponent, EntInsertedIntoContainerMessage>(OnMMIEntityInserted);
|
||||
SubscribeLocalEvent<MMIComponent, MindAddedMessage>(OnMMIMindAdded);
|
||||
SubscribeLocalEvent<MMIComponent, MindRemovedMessage>(OnMMIMindRemoved);
|
||||
|
||||
SubscribeLocalEvent<MMILinkedComponent, MindAddedMessage>(OnMMILinkedMindAdded);
|
||||
SubscribeLocalEvent<MMILinkedComponent, EntGotRemovedFromContainerMessage>(OnMMILinkedRemoved);
|
||||
}
|
||||
|
||||
private void OnMMIInit(EntityUid uid, MMIComponent component, ComponentInit args)
|
||||
{
|
||||
if (!TryComp<ItemSlotsComponent>(uid, out var itemSlots))
|
||||
return;
|
||||
|
||||
if (ItemSlots.TryGetSlot(uid, component.BrainSlotId, out var slot, itemSlots))
|
||||
component.BrainSlot = slot;
|
||||
else
|
||||
ItemSlots.AddItemSlot(uid, component.BrainSlotId, component.BrainSlot, itemSlots);
|
||||
}
|
||||
|
||||
private void OnMMIEntityInserted(EntityUid uid, MMIComponent component, EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != component.BrainSlotId)
|
||||
return;
|
||||
|
||||
var ent = args.Entity;
|
||||
var linked = EnsureComp<MMILinkedComponent>(ent);
|
||||
linked.LinkedMMI = uid;
|
||||
Dirty(uid, component);
|
||||
|
||||
if (_mind.TryGetMind(ent, out var mindId, out var mind))
|
||||
{
|
||||
_mind.TransferTo(mindId, uid, true, mind: mind);
|
||||
|
||||
if (!_roles.MindHasRole<SiliconBrainRoleComponent>(mindId))
|
||||
_roles.MindAddRole(mindId, "MindRoleSiliconBrain", silent: true);
|
||||
}
|
||||
|
||||
_appearance.SetData(uid, MMIVisuals.BrainPresent, true);
|
||||
}
|
||||
|
||||
private void OnMMIMindAdded(EntityUid uid, MMIComponent component, MindAddedMessage args)
|
||||
{
|
||||
_appearance.SetData(uid, MMIVisuals.HasMind, true);
|
||||
}
|
||||
|
||||
private void OnMMIMindRemoved(EntityUid uid, MMIComponent component, MindRemovedMessage args)
|
||||
{
|
||||
_appearance.SetData(uid, MMIVisuals.HasMind, false);
|
||||
}
|
||||
|
||||
private void OnMMILinkedMindAdded(EntityUid uid, MMILinkedComponent component, MindAddedMessage args)
|
||||
{
|
||||
if (!_mind.TryGetMind(uid, out var mindId, out var mind) ||
|
||||
component.LinkedMMI == null)
|
||||
return;
|
||||
|
||||
_mind.TransferTo(mindId, component.LinkedMMI, true, mind: mind);
|
||||
}
|
||||
|
||||
private void OnMMILinkedRemoved(EntityUid uid, MMILinkedComponent component, EntGotRemovedFromContainerMessage args)
|
||||
{
|
||||
if (Terminating(uid))
|
||||
return;
|
||||
|
||||
if (component.LinkedMMI is not { } linked)
|
||||
return;
|
||||
RemComp(uid, component);
|
||||
|
||||
if (_mind.TryGetMind(linked, out var mindId, out var mind))
|
||||
{
|
||||
if (_roles.MindHasRole<SiliconBrainRoleComponent>(mindId))
|
||||
_roles.MindRemoveRole<SiliconBrainRoleComponent>(mindId);
|
||||
|
||||
_mind.TransferTo(mindId, uid, true, mind: mind);
|
||||
}
|
||||
|
||||
_appearance.SetData(linked, MMIVisuals.BrainPresent, false);
|
||||
}
|
||||
}
|
||||
@@ -1,425 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Silicons.Borgs;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed partial class BorgSystem
|
||||
{
|
||||
public void InitializeModules()
|
||||
{
|
||||
SubscribeLocalEvent<BorgModuleComponent, EntGotInsertedIntoContainerMessage>(OnModuleGotInserted);
|
||||
SubscribeLocalEvent<BorgModuleComponent, EntGotRemovedFromContainerMessage>(OnModuleGotRemoved);
|
||||
|
||||
SubscribeLocalEvent<SelectableBorgModuleComponent, BorgModuleInstalledEvent>(OnSelectableInstalled);
|
||||
SubscribeLocalEvent<SelectableBorgModuleComponent, BorgModuleUninstalledEvent>(OnSelectableUninstalled);
|
||||
SubscribeLocalEvent<SelectableBorgModuleComponent, BorgModuleActionSelectedEvent>(OnSelectableAction);
|
||||
|
||||
SubscribeLocalEvent<ItemBorgModuleComponent, ComponentStartup>(OnProvideItemStartup);
|
||||
SubscribeLocalEvent<ItemBorgModuleComponent, BorgModuleSelectedEvent>(OnItemModuleSelected);
|
||||
SubscribeLocalEvent<ItemBorgModuleComponent, BorgModuleUnselectedEvent>(OnItemModuleUnselected);
|
||||
}
|
||||
|
||||
private void OnModuleGotInserted(EntityUid uid, BorgModuleComponent component, EntGotInsertedIntoContainerMessage args)
|
||||
{
|
||||
var chassis = args.Container.Owner;
|
||||
|
||||
if (!TryComp<BorgChassisComponent>(chassis, out var chassisComp) ||
|
||||
args.Container != chassisComp.ModuleContainer ||
|
||||
!Toggle.IsActivated(chassis))
|
||||
return;
|
||||
|
||||
if (!_powerCell.HasDrawCharge(uid))
|
||||
return;
|
||||
|
||||
InstallModule(chassis, uid, chassisComp, component);
|
||||
}
|
||||
|
||||
private void OnModuleGotRemoved(EntityUid uid, BorgModuleComponent component, EntGotRemovedFromContainerMessage args)
|
||||
{
|
||||
var chassis = args.Container.Owner;
|
||||
|
||||
if (!TryComp<BorgChassisComponent>(chassis, out var chassisComp) ||
|
||||
args.Container != chassisComp.ModuleContainer)
|
||||
return;
|
||||
|
||||
UninstallModule(chassis, uid, chassisComp, component);
|
||||
}
|
||||
|
||||
private void OnProvideItemStartup(EntityUid uid, ItemBorgModuleComponent component, ComponentStartup args)
|
||||
{
|
||||
Container.EnsureContainer<Container>(uid, component.HoldingContainer);
|
||||
}
|
||||
|
||||
private void OnSelectableInstalled(EntityUid uid, SelectableBorgModuleComponent component, ref BorgModuleInstalledEvent args)
|
||||
{
|
||||
var chassis = args.ChassisEnt;
|
||||
|
||||
if (_actions.AddAction(chassis, ref component.ModuleSwapActionEntity, out var action, component.ModuleSwapActionId, uid))
|
||||
{
|
||||
var actEnt = (component.ModuleSwapActionEntity.Value, action);
|
||||
_actions.SetEntityIcon(actEnt, uid);
|
||||
if (TryComp<BorgModuleIconComponent>(uid, out var moduleIconComp))
|
||||
_actions.SetIcon(actEnt, moduleIconComp.Icon);
|
||||
|
||||
/// Set a custom name and description on the action. The borg module action prototypes are shared across
|
||||
/// all modules. Extract localized names, then populate variables with the info from the module itself.
|
||||
var moduleName = Name(uid);
|
||||
var actionMetaData = MetaData(component.ModuleSwapActionEntity.Value);
|
||||
|
||||
var instanceName = Loc.GetString("borg-module-action-name", ("moduleName", moduleName));
|
||||
_metaData.SetEntityName(component.ModuleSwapActionEntity.Value, instanceName, actionMetaData);
|
||||
var instanceDesc = Loc.GetString("borg-module-action-description", ("moduleName", moduleName));
|
||||
_metaData.SetEntityDescription(component.ModuleSwapActionEntity.Value, instanceDesc, actionMetaData);
|
||||
}
|
||||
|
||||
if (!TryComp(chassis, out BorgChassisComponent? chassisComp))
|
||||
return;
|
||||
|
||||
if (chassisComp.SelectedModule == null)
|
||||
SelectModule(chassis, uid, chassisComp, component);
|
||||
}
|
||||
|
||||
private void OnSelectableUninstalled(EntityUid uid, SelectableBorgModuleComponent component, ref BorgModuleUninstalledEvent args)
|
||||
{
|
||||
var chassis = args.ChassisEnt;
|
||||
_actions.RemoveProvidedActions(chassis, uid);
|
||||
if (!TryComp(chassis, out BorgChassisComponent? chassisComp))
|
||||
return;
|
||||
|
||||
if (chassisComp.SelectedModule == uid)
|
||||
UnselectModule(chassis, chassisComp);
|
||||
}
|
||||
|
||||
private void OnSelectableAction(EntityUid uid, SelectableBorgModuleComponent component, BorgModuleActionSelectedEvent args)
|
||||
{
|
||||
var chassis = args.Performer;
|
||||
if (!TryComp<BorgChassisComponent>(chassis, out var chassisComp))
|
||||
return;
|
||||
|
||||
var selected = chassisComp.SelectedModule;
|
||||
|
||||
args.Handled = true;
|
||||
UnselectModule(chassis, chassisComp);
|
||||
|
||||
if (selected != uid)
|
||||
{
|
||||
SelectModule(chassis, uid, chassisComp, component);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects a module, enabling the borg to use its provided abilities.
|
||||
/// </summary>
|
||||
public void SelectModule(EntityUid chassis,
|
||||
EntityUid moduleUid,
|
||||
BorgChassisComponent? chassisComp = null,
|
||||
SelectableBorgModuleComponent? selectable = null,
|
||||
BorgModuleComponent? moduleComp = null)
|
||||
{
|
||||
if (LifeStage(chassis) >= EntityLifeStage.Terminating)
|
||||
return;
|
||||
|
||||
if (!Resolve(chassis, ref chassisComp))
|
||||
return;
|
||||
|
||||
if (!Resolve(moduleUid, ref moduleComp) || !moduleComp.Installed || moduleComp.InstalledEntity != chassis)
|
||||
{
|
||||
Log.Error($"{ToPrettyString(chassis)} attempted to select uninstalled module {ToPrettyString(moduleUid)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectable == null && !HasComp<SelectableBorgModuleComponent>(moduleUid))
|
||||
{
|
||||
Log.Error($"{ToPrettyString(chassis)} attempted to select invalid module {ToPrettyString(moduleUid)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!chassisComp.ModuleContainer.Contains(moduleUid))
|
||||
{
|
||||
Log.Error($"{ToPrettyString(chassis)} does not contain the installed module {ToPrettyString(moduleUid)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (chassisComp.SelectedModule != null)
|
||||
return;
|
||||
|
||||
if (chassisComp.SelectedModule == moduleUid)
|
||||
return;
|
||||
|
||||
UnselectModule(chassis, chassisComp);
|
||||
|
||||
var ev = new BorgModuleSelectedEvent(chassis);
|
||||
RaiseLocalEvent(moduleUid, ref ev);
|
||||
chassisComp.SelectedModule = moduleUid;
|
||||
Dirty(chassis, chassisComp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unselects a module, removing its provided abilities
|
||||
/// </summary>
|
||||
public void UnselectModule(EntityUid chassis, BorgChassisComponent? chassisComp = null)
|
||||
{
|
||||
if (LifeStage(chassis) >= EntityLifeStage.Terminating)
|
||||
return;
|
||||
|
||||
if (!Resolve(chassis, ref chassisComp))
|
||||
return;
|
||||
|
||||
if (chassisComp.SelectedModule == null)
|
||||
return;
|
||||
|
||||
var ev = new BorgModuleUnselectedEvent(chassis);
|
||||
RaiseLocalEvent(chassisComp.SelectedModule.Value, ref ev);
|
||||
chassisComp.SelectedModule = null;
|
||||
Dirty(chassis, chassisComp);
|
||||
}
|
||||
|
||||
private void OnItemModuleSelected(EntityUid uid, ItemBorgModuleComponent component, ref BorgModuleSelectedEvent args)
|
||||
{
|
||||
ProvideItems(args.Chassis, uid, component: component);
|
||||
}
|
||||
|
||||
private void OnItemModuleUnselected(EntityUid uid, ItemBorgModuleComponent component, ref BorgModuleUnselectedEvent args)
|
||||
{
|
||||
RemoveProvidedItems(args.Chassis, uid, component: component);
|
||||
}
|
||||
|
||||
private void ProvideItems(EntityUid chassis, EntityUid uid, BorgChassisComponent? chassisComponent = null, ItemBorgModuleComponent? component = null)
|
||||
{
|
||||
if (!Resolve(chassis, ref chassisComponent) || !Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (!TryComp<HandsComponent>(chassis, out var hands))
|
||||
return;
|
||||
|
||||
if (!_container.TryGetContainer(uid, component.HoldingContainer, out var container))
|
||||
return;
|
||||
|
||||
var xform = Transform(chassis);
|
||||
|
||||
for (var i = 0; i < component.Hands.Count; i++)
|
||||
{
|
||||
var hand = component.Hands[i];
|
||||
var handId = $"{uid}-hand-{i}";
|
||||
|
||||
_hands.AddHand((chassis, hands), handId, hand.Hand);
|
||||
EntityUid? item = null;
|
||||
|
||||
if (component.StoredItems is not null)
|
||||
{
|
||||
if (component.StoredItems.TryGetValue(handId, out var storedItem))
|
||||
{
|
||||
item = storedItem;
|
||||
_container.Remove(storedItem, container, force: true);
|
||||
}
|
||||
}
|
||||
else if (hand.Item is { } itemProto)
|
||||
{
|
||||
item = Spawn(itemProto, xform.Coordinates);
|
||||
}
|
||||
|
||||
if (item is { } pickUp)
|
||||
{
|
||||
_hands.DoPickup(chassis, handId, pickUp, hands);
|
||||
if (!hand.ForceRemovable && hand.Hand.Whitelist == null && hand.Hand.Blacklist == null)
|
||||
{
|
||||
EnsureComp<UnremoveableComponent>(pickUp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
private void RemoveProvidedItems(EntityUid chassis, EntityUid uid, BorgChassisComponent? chassisComponent = null, ItemBorgModuleComponent? component = null)
|
||||
{
|
||||
if (!Resolve(chassis, ref chassisComponent) || !Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (!TryComp<HandsComponent>(chassis, out var hands))
|
||||
return;
|
||||
|
||||
if (!_container.TryGetContainer(uid, component.HoldingContainer, out var container))
|
||||
return;
|
||||
|
||||
if (TerminatingOrDeleted(uid))
|
||||
return;
|
||||
|
||||
component.StoredItems ??= new();
|
||||
|
||||
for (var i = 0; i < component.Hands.Count; i++)
|
||||
{
|
||||
var handId = $"{uid}-hand-{i}";
|
||||
|
||||
if (_hands.TryGetHeldItem(chassis, handId, out var held))
|
||||
{
|
||||
RemComp<UnremoveableComponent>(held.Value);
|
||||
_container.Insert(held.Value, container);
|
||||
component.StoredItems[handId] = held.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
component.StoredItems.Remove(handId);
|
||||
}
|
||||
|
||||
_hands.RemoveHand(chassis, handId);
|
||||
}
|
||||
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given module can be inserted into a borg
|
||||
/// </summary>
|
||||
public bool CanInsertModule(EntityUid uid, EntityUid module, BorgChassisComponent? component = null, BorgModuleComponent? moduleComponent = null, EntityUid? user = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component) || !Resolve(module, ref moduleComponent))
|
||||
return false;
|
||||
|
||||
if (component.ModuleContainer.ContainedEntities.Count >= component.MaxModules)
|
||||
{
|
||||
if (user != null)
|
||||
Popup.PopupEntity(Loc.GetString("borg-module-too-many"), uid, user.Value);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_whitelistSystem.IsWhitelistFail(component.ModuleWhitelist, module))
|
||||
{
|
||||
if (user != null)
|
||||
Popup.PopupEntity(Loc.GetString("borg-module-whitelist-deny"), uid, user.Value);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TryComp<ItemBorgModuleComponent>(module, out var itemModuleComp))
|
||||
{
|
||||
foreach (var containedModuleUid in component.ModuleContainer.ContainedEntities)
|
||||
{
|
||||
if (!TryComp<ItemBorgModuleComponent>(containedModuleUid, out var containedItemModuleComp))
|
||||
continue;
|
||||
|
||||
if (containedItemModuleComp.Hands.Count == itemModuleComp.Hands.Count &&
|
||||
containedItemModuleComp.Hands.All(itemModuleComp.Hands.Contains))
|
||||
{
|
||||
if (user != null)
|
||||
Popup.PopupEntity(Loc.GetString("borg-module-duplicate"), uid, user.Value);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a module can be removed from a borg.
|
||||
/// </summary>
|
||||
/// <param name="borg">The borg that the module is being removed from.</param>
|
||||
/// <param name="module">The module to remove from the borg.</param>
|
||||
/// <param name="user">The user attempting to remove the module.</param>
|
||||
/// <returns>True if the module can be removed.</returns>
|
||||
public bool CanRemoveModule(
|
||||
Entity<BorgChassisComponent> borg,
|
||||
Entity<BorgModuleComponent> module,
|
||||
EntityUid? user = null)
|
||||
{
|
||||
if (module.Comp.DefaultModule)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Installs and activates all modules currently inside the borg's module container
|
||||
/// </summary>
|
||||
public void InstallAllModules(EntityUid uid, BorgChassisComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
var query = GetEntityQuery<BorgModuleComponent>();
|
||||
foreach (var moduleEnt in new List<EntityUid>(component.ModuleContainer.ContainedEntities))
|
||||
{
|
||||
if (!query.TryGetComponent(moduleEnt, out var moduleComp))
|
||||
continue;
|
||||
|
||||
InstallModule(uid, moduleEnt, component, moduleComp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivates all modules currently inside the borg's module container
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
public void DisableAllModules(EntityUid uid, BorgChassisComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
var query = GetEntityQuery<BorgModuleComponent>();
|
||||
foreach (var moduleEnt in new List<EntityUid>(component.ModuleContainer.ContainedEntities))
|
||||
{
|
||||
if (!query.TryGetComponent(moduleEnt, out var moduleComp))
|
||||
continue;
|
||||
|
||||
UninstallModule(uid, moduleEnt, component, moduleComp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Installs a single module into a borg.
|
||||
/// </summary>
|
||||
public void InstallModule(EntityUid uid, EntityUid module, BorgChassisComponent? component, BorgModuleComponent? moduleComponent = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component) || !Resolve(module, ref moduleComponent))
|
||||
return;
|
||||
|
||||
if (moduleComponent.Installed)
|
||||
return;
|
||||
|
||||
moduleComponent.InstalledEntity = uid;
|
||||
var ev = new BorgModuleInstalledEvent(uid);
|
||||
RaiseLocalEvent(module, ref ev);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uninstalls a single module from a borg.
|
||||
/// </summary>
|
||||
public void UninstallModule(EntityUid uid, EntityUid module, BorgChassisComponent? component, BorgModuleComponent? moduleComponent = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component) || !Resolve(module, ref moduleComponent))
|
||||
return;
|
||||
|
||||
if (!moduleComponent.Installed)
|
||||
return;
|
||||
|
||||
moduleComponent.InstalledEntity = null;
|
||||
var ev = new BorgModuleUninstalledEvent(uid);
|
||||
RaiseLocalEvent(module, ref ev);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="BorgChassisComponent.MaxModules"/>.
|
||||
/// </summary>
|
||||
/// <param name="ent">The borg to modify.</param>
|
||||
/// <param name="maxModules">The new max module count.</param>
|
||||
public void SetMaxModules(Entity<BorgChassisComponent> ent, int maxModules)
|
||||
{
|
||||
ent.Comp.MaxModules = maxModules;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="BorgChassisComponent.ModuleWhitelist"/>.
|
||||
/// </summary>
|
||||
/// <param name="ent">The borg to modify.</param>
|
||||
/// <param name="whitelist">The new module whitelist.</param>
|
||||
public void SetModuleWhitelist(Entity<BorgChassisComponent> ent, EntityWhitelist? whitelist)
|
||||
{
|
||||
ent.Comp.ModuleWhitelist = whitelist;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Robotics;
|
||||
@@ -18,10 +16,6 @@ namespace Content.Server.Silicons.Borgs;
|
||||
/// <inheritdoc/>
|
||||
public sealed partial class BorgSystem
|
||||
{
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
|
||||
|
||||
private void InitializeTransponder()
|
||||
{
|
||||
SubscribeLocalEvent<BorgTransponderComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
|
||||
@@ -83,7 +77,7 @@ public sealed partial class BorgSystem
|
||||
return;
|
||||
|
||||
var message = Loc.GetString(ent.Comp1.DisabledPopup, ("name", Name(ent, ent.Comp3)));
|
||||
Popup.PopupEntity(message, ent);
|
||||
_popup.PopupEntity(message, ent);
|
||||
_container.Remove(brain, ent.Comp2.BrainContainer);
|
||||
}
|
||||
|
||||
@@ -111,7 +105,7 @@ public sealed partial class BorgSystem
|
||||
if (CheckEmagged(ent, "disabled"))
|
||||
ent.Comp1.FakeDisabling = true;
|
||||
else
|
||||
Popup.PopupEntity(Loc.GetString(ent.Comp1.DisablingPopup), ent);
|
||||
_popup.PopupEntity(Loc.GetString(ent.Comp1.DisablingPopup), ent);
|
||||
|
||||
ent.Comp1.NextDisable = _timing.CurTime + ent.Comp1.DisableDelay;
|
||||
}
|
||||
@@ -134,7 +128,7 @@ public sealed partial class BorgSystem
|
||||
}
|
||||
|
||||
var message = Loc.GetString(ent.Comp.DestroyingPopup, ("name", Name(ent)));
|
||||
Popup.PopupEntity(message, ent);
|
||||
_popup.PopupEntity(message, ent);
|
||||
_trigger.ActivateTimerTrigger(ent.Owner);
|
||||
|
||||
// prevent a shitter borg running into people
|
||||
@@ -145,7 +139,7 @@ public sealed partial class BorgSystem
|
||||
{
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString($"borg-transponder-emagged-{name}-popup"), uid, uid, PopupType.LargeCaution);
|
||||
_popup.PopupEntity(Loc.GetString($"borg-transponder-emagged-{name}-popup"), uid, uid, PopupType.LargeCaution);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.NameIdentifier;
|
||||
using Content.Shared.PowerCell.Components;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Silicons.Borgs;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Server.Silicons.Borgs;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed partial class BorgSystem
|
||||
{
|
||||
// CCvar.
|
||||
private int _maxNameLength;
|
||||
|
||||
public void InitializeUI()
|
||||
{
|
||||
SubscribeLocalEvent<BorgChassisComponent, BeforeActivatableUIOpenEvent>(OnBeforeBorgUiOpen);
|
||||
SubscribeLocalEvent<BorgChassisComponent, BorgEjectBrainBuiMessage>(OnEjectBrainBuiMessage);
|
||||
SubscribeLocalEvent<BorgChassisComponent, BorgEjectBatteryBuiMessage>(OnEjectBatteryBuiMessage);
|
||||
SubscribeLocalEvent<BorgChassisComponent, BorgSetNameBuiMessage>(OnSetNameBuiMessage);
|
||||
SubscribeLocalEvent<BorgChassisComponent, BorgRemoveModuleBuiMessage>(OnRemoveModuleBuiMessage);
|
||||
|
||||
Subs.CVar(_cfgManager, CCVars.MaxNameLength, value => _maxNameLength = value, true);
|
||||
}
|
||||
|
||||
private void OnBeforeBorgUiOpen(EntityUid uid, BorgChassisComponent component, BeforeActivatableUIOpenEvent args)
|
||||
{
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
|
||||
private void OnEjectBrainBuiMessage(EntityUid uid, BorgChassisComponent component, BorgEjectBrainBuiMessage args)
|
||||
{
|
||||
if (component.BrainEntity is not { } brain)
|
||||
return;
|
||||
|
||||
_adminLog.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{ToPrettyString(args.Actor):player} removed brain {ToPrettyString(brain)} from borg {ToPrettyString(uid)}");
|
||||
_container.Remove(brain, component.BrainContainer);
|
||||
_hands.TryPickupAnyHand(args.Actor, brain);
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
|
||||
private void OnEjectBatteryBuiMessage(EntityUid uid, BorgChassisComponent component, BorgEjectBatteryBuiMessage args)
|
||||
{
|
||||
if (TryEjectPowerCell(uid, component, out var ents))
|
||||
_hands.TryPickupAnyHand(args.Actor, ents.First());
|
||||
}
|
||||
|
||||
private void OnSetNameBuiMessage(EntityUid uid, BorgChassisComponent component, BorgSetNameBuiMessage args)
|
||||
{
|
||||
if (args.Name.Length > _maxNameLength ||
|
||||
args.Name.Length == 0 ||
|
||||
string.IsNullOrWhiteSpace(args.Name) ||
|
||||
string.IsNullOrEmpty(args.Name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var name = args.Name.Trim();
|
||||
|
||||
var metaData = MetaData(uid);
|
||||
|
||||
// don't change the name if the value doesn't actually change
|
||||
if (metaData.EntityName.Equals(name, StringComparison.InvariantCulture))
|
||||
return;
|
||||
|
||||
_adminLog.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(args.Actor):player} set borg \"{ToPrettyString(uid)}\"'s name to: {name}");
|
||||
_metaData.SetEntityName(uid, name, metaData);
|
||||
}
|
||||
|
||||
private void OnRemoveModuleBuiMessage(EntityUid uid, BorgChassisComponent component, BorgRemoveModuleBuiMessage args)
|
||||
{
|
||||
var module = GetEntity(args.Module);
|
||||
|
||||
if (!component.ModuleContainer.Contains(module))
|
||||
return;
|
||||
|
||||
if (!CanRemoveModule((uid, component), (module, Comp<BorgModuleComponent>(module)), args.Actor))
|
||||
return;
|
||||
|
||||
_adminLog.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{ToPrettyString(args.Actor):player} removed module {ToPrettyString(module)} from borg {ToPrettyString(uid)}");
|
||||
_container.Remove(module, component.ModuleContainer);
|
||||
_hands.TryPickupAnyHand(args.Actor, module);
|
||||
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
|
||||
public void UpdateBattery(Entity<BorgChassisComponent> ent)
|
||||
{
|
||||
UpdateBatteryAlert(ent);
|
||||
|
||||
// if we aren't drawing and suddenly get enough power to draw again, reeanble.
|
||||
if (_powerCell.HasDrawCharge(ent.Owner))
|
||||
{
|
||||
Toggle.TryActivate(ent.Owner);
|
||||
}
|
||||
|
||||
UpdateUI(ent, ent);
|
||||
}
|
||||
|
||||
// TODO: Move to client so we don't have to network this periodically.
|
||||
private void UpdateBatteryAlert(Entity<BorgChassisComponent> ent, PowerCellSlotComponent? slotComponent = null)
|
||||
{
|
||||
if (!_powerCell.TryGetBatteryFromSlot((ent.Owner, slotComponent), out var battery))
|
||||
{
|
||||
_alerts.ClearAlert(ent.Owner, ent.Comp.BatteryAlert);
|
||||
_alerts.ShowAlert(ent.Owner, ent.Comp.NoBatteryAlert);
|
||||
return;
|
||||
}
|
||||
|
||||
var chargePercent = (short)MathF.Round(_battery.GetChargeLevel(battery.Value.AsNullable()) * 10f);
|
||||
|
||||
// we make sure 0 only shows if they have absolutely no battery.
|
||||
// also account for floating point imprecision
|
||||
if (chargePercent == 0 && _powerCell.HasDrawCharge((ent.Owner, null, slotComponent)))
|
||||
{
|
||||
chargePercent = 1;
|
||||
}
|
||||
|
||||
_alerts.ClearAlert(ent.Owner, ent.Comp.NoBatteryAlert);
|
||||
_alerts.ShowAlert(ent.Owner, ent.Comp.BatteryAlert, chargePercent);
|
||||
}
|
||||
|
||||
// TODO: Component states and update this on the client
|
||||
public void UpdateUI(EntityUid uid, BorgChassisComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
var chargePercent = 0f;
|
||||
var hasBattery = false;
|
||||
if (_powerCell.TryGetBatteryFromSlot(uid, out var battery))
|
||||
{
|
||||
hasBattery = true;
|
||||
chargePercent = _battery.GetCharge(battery.Value.AsNullable()) / battery.Value.Comp.MaxCharge;
|
||||
}
|
||||
|
||||
var state = new BorgBuiState(chargePercent, hasBattery);
|
||||
_ui.SetUiState(uid, BorgUiKey.Key, state);
|
||||
}
|
||||
|
||||
// periodically update the charge indicator
|
||||
// TODO: Move this to the client.
|
||||
public void UpdateBattery(float frameTime)
|
||||
{
|
||||
var curTime = _timing.CurTime;
|
||||
var query = EntityQueryEnumerator<BorgChassisComponent>();
|
||||
while (query.MoveNext(out var uid, out var borgChassis))
|
||||
{
|
||||
if (curTime < borgChassis.NextBatteryUpdate)
|
||||
continue;
|
||||
|
||||
UpdateBattery((uid, borgChassis));
|
||||
borgChassis.NextBatteryUpdate = curTime + TimeSpan.FromSeconds(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,17 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Body.Events;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Item.ItemToggle.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Pointing;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
using Content.Shared.PowerCell;
|
||||
using Content.Shared.PowerCell.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Silicons.Borgs;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.Trigger.Systems;
|
||||
using Content.Shared.Whitelist;
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Silicons.Borgs;
|
||||
@@ -41,28 +19,18 @@ namespace Content.Server.Silicons.Borgs;
|
||||
/// <inheritdoc/>
|
||||
public sealed partial class BorgSystem : SharedBorgSystem
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly IBanManager _banManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly TriggerSystem _trigger = default!;
|
||||
[Dependency] private readonly HandsSystem _hands = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
|
||||
[Dependency] private readonly PowerCellSystem _powerCell = default!;
|
||||
[Dependency] private readonly ThrowingSystem _throwing = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _player = default!;
|
||||
[Dependency] private readonly PredictedBatterySystem _battery = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly PowerCellSystem _powerCell = default!;
|
||||
|
||||
public static readonly ProtoId<JobPrototype> BorgJobId = "Borg";
|
||||
|
||||
@@ -71,264 +39,10 @@ public sealed partial class BorgSystem : SharedBorgSystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BorgChassisComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<BorgChassisComponent, AfterInteractUsingEvent>(OnChassisInteractUsing);
|
||||
SubscribeLocalEvent<BorgChassisComponent, MindAddedMessage>(OnMindAdded);
|
||||
SubscribeLocalEvent<BorgChassisComponent, MindRemovedMessage>(OnMindRemoved);
|
||||
SubscribeLocalEvent<BorgChassisComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
SubscribeLocalEvent<BorgChassisComponent, BeingGibbedEvent>(OnBeingGibbed);
|
||||
SubscribeLocalEvent<BorgChassisComponent, PowerCellChangedEvent>(OnPowerCellChanged);
|
||||
SubscribeLocalEvent<BorgChassisComponent, PredictedBatteryChargeChangedEvent>(OnBatteryChargeChanged);
|
||||
SubscribeLocalEvent<BorgChassisComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
|
||||
SubscribeLocalEvent<BorgChassisComponent, GetCharactedDeadIcEvent>(OnGetDeadIC);
|
||||
SubscribeLocalEvent<BorgChassisComponent, GetCharacterUnrevivableIcEvent>(OnGetUnrevivableIC);
|
||||
SubscribeLocalEvent<BorgChassisComponent, ItemToggledEvent>(OnToggled);
|
||||
|
||||
SubscribeLocalEvent<BorgBrainComponent, MindAddedMessage>(OnBrainMindAdded);
|
||||
SubscribeLocalEvent<BorgBrainComponent, PointAttemptEvent>(OnBrainPointAttempt);
|
||||
|
||||
InitializeModules();
|
||||
InitializeMMI();
|
||||
InitializeUI();
|
||||
InitializeTransponder();
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, BorgChassisComponent component, MapInitEvent args)
|
||||
{
|
||||
UpdateBatteryAlert((uid, component));
|
||||
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
|
||||
}
|
||||
|
||||
private void OnChassisInteractUsing(EntityUid uid, BorgChassisComponent component, AfterInteractUsingEvent args)
|
||||
{
|
||||
if (!args.CanReach || args.Handled || uid == args.User)
|
||||
return;
|
||||
|
||||
var used = args.Used;
|
||||
TryComp<BorgBrainComponent>(used, out var brain);
|
||||
TryComp<BorgModuleComponent>(used, out var module);
|
||||
|
||||
if (TryComp<WiresPanelComponent>(uid, out var panel) && !panel.Open)
|
||||
{
|
||||
if (brain != null || module != null)
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString("borg-panel-not-open"), uid, args.User);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.BrainEntity == null && brain != null &&
|
||||
_whitelistSystem.IsWhitelistPassOrNull(component.BrainWhitelist, used))
|
||||
{
|
||||
if (_mind.TryGetMind(used, out _, out var mind) &&
|
||||
_player.TryGetSessionById(mind.UserId, out var session))
|
||||
{
|
||||
if (!CanPlayerBeBorged(session))
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString("borg-player-not-allowed"), used, args.User);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_container.Insert(used, component.BrainContainer);
|
||||
_adminLog.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{ToPrettyString(args.User):player} installed brain {ToPrettyString(used)} into borg {ToPrettyString(uid)}");
|
||||
args.Handled = true;
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
|
||||
if (module != null && CanInsertModule(uid, used, component, module, args.User))
|
||||
{
|
||||
InsertModule((uid, component), used);
|
||||
_adminLog.Add(LogType.Action, LogImpact.Low,
|
||||
$"{ToPrettyString(args.User):player} installed module {ToPrettyString(used)} into borg {ToPrettyString(uid)}");
|
||||
args.Handled = true;
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a new module into a borg, the same as if a player inserted it manually.
|
||||
/// </summary>
|
||||
/// <para>
|
||||
/// This does not run checks to see if the borg is actually allowed to be inserted, such as whitelists.
|
||||
/// </para>
|
||||
/// <param name="ent">The borg to insert into.</param>
|
||||
/// <param name="module">The module to insert.</param>
|
||||
public void InsertModule(Entity<BorgChassisComponent> ent, EntityUid module)
|
||||
{
|
||||
_container.Insert(module, ent.Comp.ModuleContainer);
|
||||
}
|
||||
|
||||
// todo: consider transferring over the ghost role? managing that might suck.
|
||||
protected override void OnInserted(EntityUid uid, BorgChassisComponent component, EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
base.OnInserted(uid, component, args);
|
||||
|
||||
if (HasComp<BorgBrainComponent>(args.Entity) && _mind.TryGetMind(args.Entity, out var mindId, out var mind) && args.Container == component.BrainContainer)
|
||||
{
|
||||
_mind.TransferTo(mindId, uid, mind: mind);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnRemoved(EntityUid uid, BorgChassisComponent component, EntRemovedFromContainerMessage args)
|
||||
{
|
||||
base.OnRemoved(uid, component, args);
|
||||
|
||||
if (HasComp<BorgBrainComponent>(args.Entity) && _mind.TryGetMind(uid, out var mindId, out var mind) && args.Container == component.BrainContainer)
|
||||
{
|
||||
_mind.TransferTo(mindId, args.Entity, mind: mind);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMindAdded(EntityUid uid, BorgChassisComponent component, MindAddedMessage args)
|
||||
{
|
||||
BorgActivate(uid, component);
|
||||
}
|
||||
|
||||
private void OnMindRemoved(EntityUid uid, BorgChassisComponent component, MindRemovedMessage args)
|
||||
{
|
||||
BorgDeactivate(uid, component);
|
||||
}
|
||||
|
||||
private void OnMobStateChanged(EntityUid uid, BorgChassisComponent component, MobStateChangedEvent args)
|
||||
{
|
||||
if (args.NewMobState == MobState.Alive)
|
||||
{
|
||||
if (_mind.TryGetMind(uid, out _, out _))
|
||||
_powerCell.SetDrawEnabled(uid, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_powerCell.SetDrawEnabled(uid, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBeingGibbed(EntityUid uid, BorgChassisComponent component, ref BeingGibbedEvent args)
|
||||
{
|
||||
TryEjectPowerCell(uid, component, out var _);
|
||||
|
||||
_container.EmptyContainer(component.BrainContainer);
|
||||
_container.EmptyContainer(component.ModuleContainer);
|
||||
}
|
||||
|
||||
private void OnPowerCellChanged(Entity<BorgChassisComponent> ent, ref PowerCellChangedEvent args)
|
||||
{
|
||||
UpdateBattery(ent);
|
||||
}
|
||||
|
||||
private void OnBatteryChargeChanged(Entity<BorgChassisComponent> ent, ref PredictedBatteryChargeChangedEvent args)
|
||||
{
|
||||
UpdateBattery(ent);
|
||||
}
|
||||
|
||||
private void OnPowerCellSlotEmpty(EntityUid uid, BorgChassisComponent component, ref PowerCellSlotEmptyEvent args)
|
||||
{
|
||||
Toggle.TryDeactivate(uid);
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
|
||||
private void OnGetDeadIC(EntityUid uid, BorgChassisComponent component, ref GetCharactedDeadIcEvent args)
|
||||
{
|
||||
args.Dead = true;
|
||||
}
|
||||
|
||||
private void OnGetUnrevivableIC(EntityUid uid, BorgChassisComponent component, ref GetCharacterUnrevivableIcEvent args)
|
||||
{
|
||||
args.Unrevivable = true;
|
||||
}
|
||||
|
||||
private void OnToggled(Entity<BorgChassisComponent> ent, ref ItemToggledEvent args)
|
||||
{
|
||||
var (uid, comp) = ent;
|
||||
if (args.Activated)
|
||||
InstallAllModules(uid, comp);
|
||||
else
|
||||
DisableAllModules(uid, comp);
|
||||
|
||||
// only enable the powerdraw if there is a player in the chassis
|
||||
var drawing = _mind.TryGetMind(uid, out _, out _) && _mobState.IsAlive(ent);
|
||||
_powerCell.SetDrawEnabled(uid, drawing);
|
||||
|
||||
UpdateUI(uid, comp);
|
||||
|
||||
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
|
||||
}
|
||||
|
||||
private void OnBrainMindAdded(EntityUid uid, BorgBrainComponent component, MindAddedMessage args)
|
||||
{
|
||||
if (!Container.TryGetOuterContainer(uid, Transform(uid), out var container))
|
||||
return;
|
||||
|
||||
var containerEnt = container.Owner;
|
||||
|
||||
if (!TryComp<BorgChassisComponent>(containerEnt, out var chassisComponent) ||
|
||||
container.ID != chassisComponent.BrainContainerId)
|
||||
return;
|
||||
|
||||
if (!_mind.TryGetMind(uid, out var mindId, out var mind) ||
|
||||
!_player.TryGetSessionById(mind.UserId, out var session))
|
||||
return;
|
||||
|
||||
if (!CanPlayerBeBorged(session))
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString("borg-player-not-allowed-eject"), uid);
|
||||
Container.RemoveEntity(containerEnt, uid);
|
||||
_throwing.TryThrow(uid, _random.NextVector2() * 5, 5f);
|
||||
return;
|
||||
}
|
||||
|
||||
_mind.TransferTo(mindId, containerEnt, mind: mind);
|
||||
}
|
||||
|
||||
private void OnBrainPointAttempt(EntityUid uid, BorgBrainComponent component, PointAttemptEvent args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
public bool TryEjectPowerCell(EntityUid uid, BorgChassisComponent component, [NotNullWhen(true)] out List<EntityUid>? ents)
|
||||
{
|
||||
ents = null;
|
||||
|
||||
if (!TryComp<PowerCellSlotComponent>(uid, out var slotComp) ||
|
||||
!Container.TryGetContainer(uid, slotComp.CellSlotId, out var container) ||
|
||||
!container.ContainedEntities.Any())
|
||||
return false;
|
||||
|
||||
ents = Container.EmptyContainer(container);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates a borg when a player occupies it
|
||||
/// </summary>
|
||||
public void BorgActivate(EntityUid uid, BorgChassisComponent component)
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString("borg-mind-added", ("name", Identity.Name(uid, EntityManager))), uid);
|
||||
if (_powerCell.HasDrawCharge(uid))
|
||||
{
|
||||
Toggle.TryActivate(uid);
|
||||
_powerCell.SetDrawEnabled(uid, _mobState.IsAlive(uid));
|
||||
}
|
||||
_appearance.SetData(uid, BorgVisuals.HasPlayer, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivates a borg when a player leaves it.
|
||||
/// </summary>
|
||||
public void BorgDeactivate(EntityUid uid, BorgChassisComponent component)
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString("borg-mind-removed", ("name", Identity.Name(uid, EntityManager))), uid);
|
||||
Toggle.TryDeactivate(uid);
|
||||
_powerCell.SetDrawEnabled(uid, false);
|
||||
_appearance.SetData(uid, BorgVisuals.HasPlayer, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that a player has fulfilled the requirements for the borg job.
|
||||
/// </summary>
|
||||
public bool CanPlayerBeBorged(ICommonSession session)
|
||||
public override bool CanPlayerBeBorged(ICommonSession session)
|
||||
{
|
||||
if (_banManager.GetJobBans(session.UserId)?.Contains(BorgJobId) == true)
|
||||
return false;
|
||||
@@ -341,6 +55,5 @@ public sealed partial class BorgSystem : SharedBorgSystem
|
||||
base.Update(frameTime);
|
||||
|
||||
UpdateTransponder(frameTime);
|
||||
UpdateBattery(frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,6 +233,9 @@ namespace Content.Shared.Interaction
|
||||
/// </summary>
|
||||
private void OnUnequip(EntityUid uid, UnremoveableComponent item, GotUnequippedEvent args)
|
||||
{
|
||||
if (_gameTiming.ApplyingState)
|
||||
return; // The changes are already networked with the same gamestate as the container event.
|
||||
|
||||
if (!item.DeleteOnDrop)
|
||||
RemCompDeferred<UnremoveableComponent>(uid);
|
||||
else
|
||||
@@ -241,6 +244,9 @@ namespace Content.Shared.Interaction
|
||||
|
||||
private void OnUnequipHand(EntityUid uid, UnremoveableComponent item, GotUnequippedHandEvent args)
|
||||
{
|
||||
if (_gameTiming.ApplyingState)
|
||||
return; // The changes are already networked with the same gamestate as the container event.
|
||||
|
||||
if (!item.DeleteOnDrop)
|
||||
RemCompDeferred<UnremoveableComponent>(uid);
|
||||
else
|
||||
@@ -249,6 +255,11 @@ namespace Content.Shared.Interaction
|
||||
|
||||
private void OnDropped(EntityUid uid, UnremoveableComponent item, DroppedEvent args)
|
||||
{
|
||||
if (_gameTiming.ApplyingState)
|
||||
return; // The changes are already networked with the same gamestate as the container event.
|
||||
// Other than the two cases above this is not a container event, but adding and removing hands is networked similarly
|
||||
// and removing hands causes items to be dropped.
|
||||
|
||||
if (!item.DeleteOnDrop)
|
||||
RemCompDeferred<UnremoveableComponent>(uid);
|
||||
else
|
||||
|
||||
@@ -10,6 +10,8 @@ namespace Content.Shared.Light.Components;
|
||||
public sealed partial class HandheldLightComponent : Component
|
||||
{
|
||||
public byte? Level;
|
||||
|
||||
[DataField]
|
||||
public bool Activated;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Clothing.EntitySystems;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Light;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Toggleable;
|
||||
using Content.Shared.Verbs;
|
||||
@@ -24,7 +24,7 @@ public abstract class SharedHandheldLightSystem : EntitySystem
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<HandheldLightComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<HandheldLightComponent, ComponentHandleState>(OnHandleState);
|
||||
|
||||
SubscribeLocalEvent<HandheldLightComponent, ExaminedEvent>(OnExamine);
|
||||
SubscribeLocalEvent<HandheldLightComponent, GetVerbsEvent<ActivationVerb>>(AddToggleLightVerb);
|
||||
}
|
||||
|
||||
@@ -45,6 +45,13 @@ public abstract class SharedHandheldLightSystem : EntitySystem
|
||||
SetActivated(uid, state.Activated, component, false);
|
||||
}
|
||||
|
||||
private void OnExamine(EntityUid uid, HandheldLightComponent component, ExaminedEvent args)
|
||||
{
|
||||
args.PushMarkup(component.Activated
|
||||
? Loc.GetString("handheld-light-component-on-examine-is-on-message")
|
||||
: Loc.GetString("handheld-light-component-on-examine-is-off-message"));
|
||||
}
|
||||
|
||||
public void SetActivated(EntityUid uid, bool activated, HandheldLightComponent? component = null, bool makeNoise = true)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
|
||||
@@ -8,6 +8,23 @@ namespace Content.Shared.PowerCell;
|
||||
|
||||
public sealed partial class PowerCellSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if a power cell slot has a battery inside.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public bool HasBattery(Entity<PowerCellSlotComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
if (!_itemSlots.TryGetSlot(ent.Owner, ent.Comp.CellSlotId, out var slot))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return slot.Item != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the power cell battery inside a power cell slot.
|
||||
/// </summary>
|
||||
@@ -22,7 +39,7 @@ public sealed partial class PowerCellSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_itemSlots.TryGetSlot(ent.Owner, ent.Comp.CellSlotId, out ItemSlot? slot))
|
||||
if (!_itemSlots.TryGetSlot(ent.Owner, ent.Comp.CellSlotId, out var slot))
|
||||
{
|
||||
battery = null;
|
||||
return false;
|
||||
@@ -77,10 +94,39 @@ public sealed partial class PowerCellSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to eject the power cell battery inside a power cell slot.
|
||||
/// This checks if the user has a free hand to do the ejection and if the slot is locked.
|
||||
/// </summary>
|
||||
/// <param name="ent">The entity with the power cell slot.</param>
|
||||
/// <param name="battery">The power cell that was ejected.</param>
|
||||
/// <param name="user">The player trying to eject the power cell from the slot.</param>
|
||||
/// <returns>If a power cell was sucessfully ejected.</returns>
|
||||
[PublicAPI]
|
||||
public bool TryEjectBatteryFromSlot(
|
||||
Entity<PowerCellSlotComponent?> ent,
|
||||
[NotNullWhen(true)] out EntityUid? battery,
|
||||
EntityUid? user = null)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
{
|
||||
battery = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_itemSlots.TryEject(ent.Owner, ent.Comp.CellSlotId, user, out battery, excludeUserAudio: true))
|
||||
{
|
||||
battery = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the entity has a slotted battery and charge for the requested action.
|
||||
/// </summary>
|
||||
/// <param name="ent">The power cell.</param>
|
||||
/// <param name="ent">The entity with the power cell slot.</param>
|
||||
/// <param name="charge">The charge that is needed.</param>
|
||||
/// <param name="user">Show a popup to this user with the relevant details if specified.</param>
|
||||
/// <param name="predicted">Whether to predict the popup or not.</param>
|
||||
@@ -119,7 +165,7 @@ public sealed partial class PowerCellSystem
|
||||
/// <summary>
|
||||
/// Tries to use charge from a slotted battery.
|
||||
/// </summary>
|
||||
/// <param name="ent">The power cell.</param>
|
||||
/// <param name="ent">The entity with the power cell slot.</param>
|
||||
/// <param name="charge">The charge that is needed.</param>
|
||||
/// <param name="user">Show a popup to this user with the relevant details if specified.</param>
|
||||
/// <param name="predicted">Whether to predict the popup or not.</param>
|
||||
@@ -157,7 +203,7 @@ public sealed partial class PowerCellSystem
|
||||
/// <summary>
|
||||
/// Gets number of remaining uses for the given charge cost.
|
||||
/// </summary>
|
||||
/// <param name="ent">The power cell.</param>
|
||||
/// <param name="ent">The entity with the power cell slot.</param>
|
||||
/// <param name="cost">The cost per use.</param>
|
||||
[PublicAPI]
|
||||
public int GetRemainingUses(Entity<PowerCellSlotComponent?> ent, float cost)
|
||||
@@ -171,7 +217,7 @@ public sealed partial class PowerCellSystem
|
||||
/// <summary>
|
||||
/// Gets number of maximum uses at full charge for the given charge cost.
|
||||
/// </summary>
|
||||
/// <param name="ent">The power cell.</param>
|
||||
/// <param name="ent">The entity with the power cell slot.</param>
|
||||
/// <param name="cost">The cost per use.</param>
|
||||
[PublicAPI]
|
||||
public int GetMaxUses(Entity<PowerCellSlotComponent?> ent, float cost)
|
||||
|
||||
@@ -8,50 +8,38 @@ public enum BorgUiKey : byte
|
||||
Key
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send when a player uses the borg BUI to eject a brain.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BorgBuiState : BoundUserInterfaceState
|
||||
public sealed class BorgEjectBrainBuiMessage : BoundUserInterfaceMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Send when a player uses the borg BUI to eject a power cell.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BorgEjectBatteryBuiMessage : BoundUserInterfaceMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Send when a player uses the borg BUI to change a borg's name.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BorgSetNameBuiMessage(string name) : BoundUserInterfaceMessage
|
||||
{
|
||||
public float ChargePercent;
|
||||
|
||||
public bool HasBattery;
|
||||
|
||||
public BorgBuiState(float chargePercent, bool hasBattery)
|
||||
{
|
||||
ChargePercent = chargePercent;
|
||||
HasBattery = hasBattery;
|
||||
}
|
||||
/// <summary>
|
||||
/// The new name.
|
||||
/// </summary>
|
||||
public string Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send when a player uses the borg BUI to remove a borg module.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BorgEjectBrainBuiMessage : BoundUserInterfaceMessage
|
||||
public sealed class BorgRemoveModuleBuiMessage(NetEntity module) : BoundUserInterfaceMessage
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BorgEjectBatteryBuiMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BorgSetNameBuiMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public string Name;
|
||||
|
||||
public BorgSetNameBuiMessage(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BorgRemoveModuleBuiMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public NetEntity Module;
|
||||
|
||||
public BorgRemoveModuleBuiMessage(NetEntity module)
|
||||
{
|
||||
Module = module;
|
||||
}
|
||||
/// <summary>
|
||||
/// The module to eject.
|
||||
/// </summary>
|
||||
public NetEntity Module = module;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -13,80 +14,132 @@ namespace Content.Shared.Silicons.Borgs.Components;
|
||||
/// "brain", legs, modules, and battery. Essentially the master component
|
||||
/// for borg logic.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem))]
|
||||
[AutoGenerateComponentState, AutoGenerateComponentPause]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
|
||||
[Access(typeof(SharedBorgSystem))]
|
||||
public sealed partial class BorgChassisComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Is this borg currently activated?
|
||||
/// If activated the borg
|
||||
/// - has door access,
|
||||
/// - can use modules and
|
||||
/// - has full movement speed.
|
||||
/// To be activated the borg
|
||||
/// - needs to have a player controlling it (a mind),
|
||||
/// - needs enough charge in its power cell and
|
||||
/// - needs to be alive (not crit or dead).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Don't try to use ItemToggle for this.
|
||||
/// It used that before and it had a ton of conflicts with other item toggling behavior from the billion components that use it somehow.
|
||||
/// </remarks>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Active;
|
||||
|
||||
/// <summary>
|
||||
/// The sound to play when the borg activates.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The same as the flashlight. This playing used to be a bug, but the sound is nostalgic at this point, so I'm keeping it.
|
||||
/// </remarks>
|
||||
[DataField]
|
||||
public SoundSpecifier ActivateSound = new SoundPathSpecifier("/Audio/Items/flashlight_on.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// The sound to play when the borg deactivates.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier DeactivateSound = new SoundPathSpecifier("/Audio/Items/flashlight_off.ogg");
|
||||
|
||||
#region Brain
|
||||
/// <summary>
|
||||
/// A whitelist for which entities count as valid brains
|
||||
/// A whitelist for which entities count as valid brains.
|
||||
/// </summary>
|
||||
[DataField("brainWhitelist")]
|
||||
[DataField]
|
||||
public EntityWhitelist? BrainWhitelist;
|
||||
|
||||
/// <summary>
|
||||
/// The container ID for the brain
|
||||
/// The container ID for the posibrain or MMI.
|
||||
/// </summary>
|
||||
[DataField("brainContainerId")]
|
||||
[DataField]
|
||||
public string BrainContainerId = "borg_brain";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
/// <summary>
|
||||
/// The container for the posibrain or MMI.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public ContainerSlot BrainContainer = default!;
|
||||
|
||||
public EntityUid? BrainEntity => BrainContainer.ContainedEntity;
|
||||
/// <summary>
|
||||
/// The posibrain or MMI inserted into this borg, if any.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? BrainEntity => BrainContainer?.ContainedEntity;
|
||||
#endregion
|
||||
|
||||
#region Modules
|
||||
/// <summary>
|
||||
/// A whitelist for what types of modules can be installed into this borg
|
||||
/// A whitelist for what types of modules can be installed into this borg.
|
||||
/// </summary>
|
||||
[DataField("moduleWhitelist")]
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityWhitelist? ModuleWhitelist;
|
||||
|
||||
/// <summary>
|
||||
/// How many modules can be installed in this borg
|
||||
/// How many modules can be installed in this borg?
|
||||
/// </summary>
|
||||
[DataField("maxModules"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField, AutoNetworkedField]
|
||||
public int MaxModules = 3;
|
||||
|
||||
/// <summary>
|
||||
/// The ID for the module container
|
||||
/// The ID for the module container.
|
||||
/// </summary>
|
||||
[DataField("moduleContainerId")]
|
||||
[DataField]
|
||||
public string ModuleContainerId = "borg_module";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
/// <summary>
|
||||
/// The module container.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Container ModuleContainer = default!;
|
||||
|
||||
/// <summary>
|
||||
/// How many modules are currently installed?
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int ModuleCount => ModuleContainer.ContainedEntities.Count;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// The currently selected module
|
||||
/// The currently selected module.
|
||||
/// </summary>
|
||||
[DataField("selectedModule"), AutoNetworkedField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? SelectedModule;
|
||||
|
||||
#region Visuals
|
||||
[DataField("hasMindState")]
|
||||
[DataField]
|
||||
public string HasMindState = string.Empty;
|
||||
|
||||
[DataField("noMindState")]
|
||||
[DataField]
|
||||
public string NoMindState = string.Empty;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// The battery charge alert.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> BatteryAlert = "BorgBattery";
|
||||
|
||||
/// <summary>
|
||||
/// The alert for a missing battery.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> NoBatteryAlert = "BorgBatteryNone";
|
||||
|
||||
/// <summary>
|
||||
/// The next update time for the battery charge level.
|
||||
/// Used for the alert and borg UI.
|
||||
/// The next update time the battery is checked for automatic reactivation.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField]
|
||||
[AutoNetworkedField, AutoPausedField]
|
||||
public TimeSpan NextBatteryUpdate = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
@@ -99,7 +152,8 @@ public sealed partial class BorgChassisComponent : Component
|
||||
[Serializable, NetSerializable]
|
||||
public enum BorgVisuals : byte
|
||||
{
|
||||
HasPlayer
|
||||
HasPlayer,
|
||||
Powered,
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Silicons.Borgs.Components;
|
||||
|
||||
@@ -7,23 +6,26 @@ namespace Content.Shared.Silicons.Borgs.Components;
|
||||
/// This is used for modules that can be inserted into borgs
|
||||
/// to give them unique abilities and attributes.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem))]
|
||||
[AutoGenerateComponentState]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(SharedBorgSystem))]
|
||||
public sealed partial class BorgModuleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity this module is installed into
|
||||
/// The entity this module is installed into.
|
||||
/// </summary>
|
||||
[DataField("installedEntity")]
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? InstalledEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Is this module currently installed?
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool Installed => InstalledEntity != null;
|
||||
|
||||
/// <summary>
|
||||
/// If true, this is a "default" module that cannot be removed from a borg.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[AutoNetworkedField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool DefaultModule;
|
||||
|
||||
/// <summary>
|
||||
@@ -37,13 +39,13 @@ public sealed partial class BorgModuleComponent : Component
|
||||
/// <summary>
|
||||
/// Raised on a module when it is installed in order to add specific behavior to an entity.
|
||||
/// </summary>
|
||||
/// <param name="ChassisEnt"></param>
|
||||
/// <param name="ChassisEnt">The borg the module is being installed in.</param>
|
||||
[ByRefEvent]
|
||||
public readonly record struct BorgModuleInstalledEvent(EntityUid ChassisEnt);
|
||||
|
||||
/// <summary>
|
||||
/// Raised on a module when it's uninstalled in order to
|
||||
/// </summary>
|
||||
/// <param name="ChassisEnt"></param>
|
||||
/// <param name="ChassisEnt">The borg the module is being uninstalled from.</param>
|
||||
[ByRefEvent]
|
||||
public readonly record struct BorgModuleUninstalledEvent(EntityUid ChassisEnt);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Shared.Hands.Components;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -9,7 +8,8 @@ namespace Content.Shared.Silicons.Borgs.Components;
|
||||
/// <summary>
|
||||
/// This is used for a <see cref="BorgModuleComponent"/> that provides items to the entity it's installed into.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem))]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(SharedBorgSystem))]
|
||||
public sealed partial class ItemBorgModuleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
@@ -19,10 +19,17 @@ public sealed partial class ItemBorgModuleComponent : Component
|
||||
public List<BorgHand> Hands = new();
|
||||
|
||||
/// <summary>
|
||||
/// The items stored within the hands. Null until the first time items are stored.
|
||||
/// The items stored within the hands.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Dictionary<string, EntityUid>? StoredItems;
|
||||
[DataField, AutoNetworkedField]
|
||||
public Dictionary<string, EntityUid> StoredItems = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether the provided items have been spawned.
|
||||
/// This happens the first time the module is used.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Spawned;
|
||||
|
||||
/// <summary>
|
||||
/// An ID for the container where items are stored when not in use.
|
||||
@@ -31,12 +38,21 @@ public sealed partial class ItemBorgModuleComponent : Component
|
||||
public string HoldingContainer = "holding_container";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A single hand provided by the module.
|
||||
/// </summary>
|
||||
[DataDefinition, Serializable, NetSerializable]
|
||||
public partial record struct BorgHand
|
||||
{
|
||||
/// <summary>
|
||||
/// The item to spawn in the hand, if any.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId? Item;
|
||||
|
||||
/// <summary>
|
||||
/// The settings for the hand, including a whitelist.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Hand Hand = new();
|
||||
|
||||
|
||||
@@ -9,37 +9,38 @@ namespace Content.Shared.Silicons.Borgs.Components;
|
||||
/// in an item slot before transferring consciousness.
|
||||
/// Used for borg stuff.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem))]
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(SharedBorgSystem))]
|
||||
public sealed partial class MMIComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID of the itemslot that holds the brain.
|
||||
/// </summary>
|
||||
[DataField("brainSlotId")]
|
||||
[DataField]
|
||||
public string BrainSlotId = "brain_slot";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ItemSlot"/> for this implanter
|
||||
/// The <see cref="ItemSlot"/> for this MMI. Holds the brain.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ItemSlot BrainSlot = default!;
|
||||
[DataField(required: true)]
|
||||
public ItemSlot BrainSlot = new();
|
||||
|
||||
/// <summary>
|
||||
/// The sprite state when the brain inserted has a mind.
|
||||
/// </summary>
|
||||
[DataField("hasMindState")]
|
||||
[DataField]
|
||||
public string HasMindState = "mmi_alive";
|
||||
|
||||
/// <summary>
|
||||
/// The sprite state when the brain inserted doesn't have a mind.
|
||||
/// </summary>
|
||||
[DataField("noMindState")]
|
||||
[DataField]
|
||||
public string NoMindState = "mmi_dead";
|
||||
|
||||
/// <summary>
|
||||
/// The sprite state when there is no brain inserted.
|
||||
/// </summary>
|
||||
[DataField("noBrainState")]
|
||||
[DataField]
|
||||
public string NoBrainState = "mmi_off";
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace Content.Shared.Silicons.Borgs.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for an entity that is linked to an MMI.
|
||||
/// This is used for an entity that is linked to an MMI, usually a brain.
|
||||
/// Mostly for receiving events.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem))]
|
||||
@@ -13,6 +13,6 @@ public sealed partial class MMILinkedComponent : Component
|
||||
/// <summary>
|
||||
/// The MMI this entity is linked to.
|
||||
/// </summary>
|
||||
[DataField("linkedMMI"), AutoNetworkedField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? LinkedMMI;
|
||||
}
|
||||
|
||||
@@ -1,28 +1,33 @@
|
||||
using Content.Shared.Actions;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Silicons.Borgs.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for <see cref="BorgModuleComponent"/>s that can be "swapped" to, as opposed to having passive effects.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem))]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(SharedBorgSystem))]
|
||||
public sealed partial class SelectableBorgModuleComponent : Component
|
||||
{
|
||||
[DataField("moduleSwapAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? ModuleSwapActionId = "ActionBorgSwapModule";
|
||||
/// <summary>
|
||||
/// The sidebar action prototype for swapping to this module.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId ModuleSwapAction = "ActionBorgSwapModule";
|
||||
|
||||
/// <summary>
|
||||
/// The sidebar action for swapping to this module.
|
||||
/// The sidebar action entity for swapping to this module.
|
||||
/// </summary>
|
||||
[DataField("moduleSwapActionEntity")] public EntityUid? ModuleSwapActionEntity;
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? ModuleSwapActionEntity;
|
||||
}
|
||||
|
||||
public sealed partial class BorgModuleActionSelectedEvent : InstantActionEvent
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Raised on a borg module entity with <see cref="SelectableBorgModuleComponent"/> when a player uses the action provided by it.
|
||||
/// </summary>
|
||||
public sealed partial class BorgModuleActionSelectedEvent : InstantActionEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised by-ref on a module when it is selected
|
||||
|
||||
318
Content.Shared/Silicons/Borgs/SharedBorgSystem.API.cs
Normal file
318
Content.Shared/Silicons/Borgs/SharedBorgSystem.API.cs
Normal file
@@ -0,0 +1,318 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Shared.Silicons.Borgs;
|
||||
|
||||
public abstract partial class SharedBorgSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Can this borg currently activate it's <see cref="ItemToggleComponent"/>?
|
||||
/// The requirements for this are
|
||||
/// - Having enough power in its power cell
|
||||
/// - Having a player mind attached
|
||||
/// - The borg is alive (not crit or dead).
|
||||
/// </summary>
|
||||
public bool CanActivate(Entity<BorgChassisComponent> chassis)
|
||||
{
|
||||
if (!_powerCell.HasDrawCharge(chassis.Owner))
|
||||
return false;
|
||||
|
||||
// TODO: Replace this with something else, only the client's own mind is networked to them,
|
||||
// so this will always be false for the minds of other clients.
|
||||
if (!_mind.TryGetMind(chassis.Owner, out _, out _))
|
||||
return false;
|
||||
|
||||
if (!_mobState.IsAlive(chassis.Owner))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates the borg if the conditions are met.
|
||||
/// Returns true if the borg was activated.
|
||||
/// </summary>
|
||||
public bool TryActivate(Entity<BorgChassisComponent> chassis, EntityUid? user = null)
|
||||
{
|
||||
if (chassis.Comp.Active)
|
||||
return false; // Already active.
|
||||
|
||||
if (CanActivate(chassis))
|
||||
{
|
||||
SetActive(chassis, true, user);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates or deactivates a borg.
|
||||
/// If active the borg
|
||||
/// - has door access,
|
||||
/// - can use modules and
|
||||
/// - has full movement speed.
|
||||
/// </summary>
|
||||
public void SetActive(Entity<BorgChassisComponent> chassis, bool active, EntityUid? user = null)
|
||||
{
|
||||
if (chassis.Comp.Active == active)
|
||||
return;
|
||||
|
||||
chassis.Comp.Active = active;
|
||||
Dirty(chassis);
|
||||
|
||||
if (active)
|
||||
InstallAllModules(chassis.AsNullable());
|
||||
else
|
||||
DisableAllModules(chassis.AsNullable());
|
||||
|
||||
_access.SetAccessEnabled(chassis.Owner, active); // Needs a player so that scientists can't drag around an empty borg for free AA.
|
||||
_powerCell.SetDrawEnabled(chassis.Owner, active);
|
||||
_movementSpeedModifier.RefreshMovementSpeedModifiers(chassis);
|
||||
|
||||
var sound = active ? chassis.Comp.ActivateSound : chassis.Comp.DeactivateSound;
|
||||
// If a user is given predict the audio for them, if not keep it unpredicted.
|
||||
if (user != null)
|
||||
_audio.PlayPredicted(sound, chassis.Owner, user);
|
||||
else if (_net.IsServer)
|
||||
_audio.PlayPvs(sound, chassis.Owner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a new module into a borg, the same as if a player inserted it manually.
|
||||
/// This does not run checks to see if the borg is actually allowed to be inserted, such as whitelists.
|
||||
/// </summary>
|
||||
/// <param name="ent">The borg to insert into.</param>
|
||||
/// <param name="module">The module to insert.</param>
|
||||
public void InsertModule(Entity<BorgChassisComponent> ent, EntityUid module)
|
||||
{
|
||||
_container.Insert(module, ent.Comp.ModuleContainer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="BorgChassisComponent.ModuleWhitelist"/>.
|
||||
/// </summary>
|
||||
/// <param name="ent">The borg to modify.</param>
|
||||
/// <param name="whitelist">The new module whitelist.</param>
|
||||
public void SetModuleWhitelist(Entity<BorgChassisComponent> ent, EntityWhitelist? whitelist)
|
||||
{
|
||||
ent.Comp.ModuleWhitelist = whitelist;
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="BorgChassisComponent.MaxModules"/>.
|
||||
/// </summary>
|
||||
/// <param name="ent">The borg to modify.</param>
|
||||
/// <param name="maxModules">The new max module count.</param>
|
||||
public void SetMaxModules(Entity<BorgChassisComponent> ent, int maxModules)
|
||||
{
|
||||
ent.Comp.MaxModules = maxModules;
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that a player has fulfilled the requirements for the borg job, i.e. they are not banned from that role.
|
||||
/// Always true on the client.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// TODO: This currently causes mispredicts, but we have no way of knowing on the client if a player is banned.
|
||||
/// Maybe solve this by giving banned players an unborgable trait instead?
|
||||
/// </remarks>
|
||||
public virtual bool CanPlayerBeBorged(ICommonSession session)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Installs a single module into a borg.
|
||||
/// </summary>
|
||||
public void InstallModule(Entity<BorgChassisComponent?> borg, Entity<BorgModuleComponent?> module)
|
||||
{
|
||||
if (!Resolve(borg, ref borg.Comp) || !Resolve(module, ref module.Comp))
|
||||
return;
|
||||
|
||||
if (module.Comp.Installed)
|
||||
return;
|
||||
|
||||
module.Comp.InstalledEntity = borg.Owner;
|
||||
Dirty(module);
|
||||
var ev = new BorgModuleInstalledEvent(borg.Owner);
|
||||
RaiseLocalEvent(module, ref ev);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uninstalls a single module from a borg.
|
||||
/// </summary>
|
||||
public void UninstallModule(Entity<BorgChassisComponent?> borg, Entity<BorgModuleComponent?> module)
|
||||
{
|
||||
if (!Resolve(borg, ref borg.Comp) || !Resolve(module, ref module.Comp))
|
||||
return;
|
||||
|
||||
if (!module.Comp.Installed)
|
||||
return;
|
||||
|
||||
module.Comp.InstalledEntity = null;
|
||||
Dirty(module);
|
||||
var ev = new BorgModuleUninstalledEvent(borg.Owner);
|
||||
RaiseLocalEvent(module, ref ev);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Installs and activates all modules currently inside the borg's module container.
|
||||
/// </summary>
|
||||
public void InstallAllModules(Entity<BorgChassisComponent?> borg)
|
||||
{
|
||||
if (!Resolve(borg, ref borg.Comp))
|
||||
return;
|
||||
|
||||
foreach (var moduleEnt in new List<EntityUid>(borg.Comp.ModuleContainer.ContainedEntities))
|
||||
{
|
||||
if (!_moduleQuery.TryGetComponent(moduleEnt, out var moduleComp))
|
||||
continue;
|
||||
|
||||
InstallModule(borg, (moduleEnt, moduleComp));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivates all modules currently inside the borg's module container.
|
||||
/// </summary>
|
||||
public void DisableAllModules(Entity<BorgChassisComponent?> borg)
|
||||
{
|
||||
if (!Resolve(borg, ref borg.Comp))
|
||||
return;
|
||||
|
||||
foreach (var moduleEnt in new List<EntityUid>(borg.Comp.ModuleContainer.ContainedEntities))
|
||||
{
|
||||
if (!_moduleQuery.TryGetComponent(moduleEnt, out var moduleComp))
|
||||
continue;
|
||||
|
||||
UninstallModule(borg, (moduleEnt, moduleComp));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="BorgModuleComponent.DefaultModule"/>.
|
||||
/// </summary>
|
||||
public void SetBorgModuleDefault(Entity<BorgModuleComponent> ent, bool newDefault)
|
||||
{
|
||||
ent.Comp.DefaultModule = newDefault;
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given module can be inserted into a borg.
|
||||
/// </summary>
|
||||
public bool CanInsertModule(Entity<BorgChassisComponent?> chassis, Entity<BorgModuleComponent?> module, EntityUid? user = null)
|
||||
{
|
||||
if (!Resolve(chassis, ref chassis.Comp) || !Resolve(module, ref module.Comp))
|
||||
return false;
|
||||
|
||||
if (chassis.Comp.ModuleContainer.ContainedEntities.Count >= chassis.Comp.MaxModules)
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("borg-module-too-many"), chassis.Owner, user);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_whitelist.IsWhitelistFail(chassis.Comp.ModuleWhitelist, module))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("borg-module-whitelist-deny"), chassis.Owner, user);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TryComp<ItemBorgModuleComponent>(module, out var itemModuleComp))
|
||||
{
|
||||
foreach (var containedModuleUid in chassis.Comp.ModuleContainer.ContainedEntities)
|
||||
{
|
||||
if (!TryComp<ItemBorgModuleComponent>(containedModuleUid, out var containedItemModuleComp))
|
||||
continue;
|
||||
|
||||
if (containedItemModuleComp.Hands.Count == itemModuleComp.Hands.Count &&
|
||||
containedItemModuleComp.Hands.All(itemModuleComp.Hands.Contains))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("borg-module-duplicate"), chassis.Owner, user);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a module can be removed from a borg.
|
||||
/// </summary>
|
||||
/// <param name="module">The module to remove from the borg.</param>
|
||||
/// <returns>True if the module can be removed.</returns>
|
||||
public bool CanRemoveModule(Entity<BorgModuleComponent> module)
|
||||
{
|
||||
if (module.Comp.DefaultModule)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects a module, enabling the borg to use its provided abilities.
|
||||
/// </summary>
|
||||
public void SelectModule(Entity<BorgChassisComponent?> chassis,
|
||||
Entity<BorgModuleComponent?> module)
|
||||
{
|
||||
if (LifeStage(chassis) >= EntityLifeStage.Terminating)
|
||||
return;
|
||||
|
||||
if (!Resolve(chassis, ref chassis.Comp))
|
||||
return;
|
||||
|
||||
if (!Resolve(module, ref module.Comp) || !module.Comp.Installed || module.Comp.InstalledEntity != chassis.Owner)
|
||||
{
|
||||
Log.Error($"{ToPrettyString(chassis)} attempted to select uninstalled module {ToPrettyString(module)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HasComp<SelectableBorgModuleComponent>(module))
|
||||
{
|
||||
Log.Error($"{ToPrettyString(chassis)} attempted to select invalid module {ToPrettyString(module)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!chassis.Comp.ModuleContainer.Contains(module))
|
||||
{
|
||||
Log.Error($"{ToPrettyString(chassis)} does not contain the installed module {ToPrettyString(module)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (chassis.Comp.SelectedModule == module.Owner)
|
||||
return;
|
||||
|
||||
UnselectModule(chassis);
|
||||
|
||||
var ev = new BorgModuleSelectedEvent(chassis);
|
||||
RaiseLocalEvent(module, ref ev);
|
||||
chassis.Comp.SelectedModule = module.Owner;
|
||||
Dirty(chassis);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unselects a module, removing its provided abilities.
|
||||
/// </summary>
|
||||
public void UnselectModule(Entity<BorgChassisComponent?> chassis)
|
||||
{
|
||||
if (LifeStage(chassis) >= EntityLifeStage.Terminating)
|
||||
return;
|
||||
|
||||
if (!Resolve(chassis, ref chassis.Comp))
|
||||
return;
|
||||
|
||||
if (chassis.Comp.SelectedModule == null)
|
||||
return;
|
||||
|
||||
var ev = new BorgModuleUnselectedEvent(chassis);
|
||||
RaiseLocalEvent(chassis.Comp.SelectedModule.Value, ref ev);
|
||||
chassis.Comp.SelectedModule = null;
|
||||
Dirty(chassis);
|
||||
}
|
||||
}
|
||||
94
Content.Shared/Silicons/Borgs/SharedBorgSystem.MMI.cs
Normal file
94
Content.Shared/Silicons/Borgs/SharedBorgSystem.MMI.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Silicons.Borgs;
|
||||
|
||||
public abstract partial class SharedBorgSystem
|
||||
{
|
||||
private static readonly EntProtoId SiliconBrainRole = "MindRoleSiliconBrain";
|
||||
|
||||
public void InitializeMMI()
|
||||
{
|
||||
SubscribeLocalEvent<MMIComponent, ComponentInit>(OnMMIInit);
|
||||
SubscribeLocalEvent<MMIComponent, EntInsertedIntoContainerMessage>(OnMMIEntityInserted);
|
||||
SubscribeLocalEvent<MMIComponent, MindAddedMessage>(OnMMIMindAdded);
|
||||
SubscribeLocalEvent<MMIComponent, MindRemovedMessage>(OnMMIMindRemoved);
|
||||
|
||||
SubscribeLocalEvent<MMILinkedComponent, MindAddedMessage>(OnMMILinkedMindAdded);
|
||||
SubscribeLocalEvent<MMILinkedComponent, EntGotRemovedFromContainerMessage>(OnMMILinkedRemoved);
|
||||
}
|
||||
|
||||
private void OnMMIInit(Entity<MMIComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
_itemSlots.AddItemSlot(ent.Owner, ent.Comp.BrainSlotId, ent.Comp.BrainSlot);
|
||||
}
|
||||
|
||||
private void OnMMIEntityInserted(Entity<MMIComponent> ent, ref EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (_timing.ApplyingState)
|
||||
return; // The changes are already networked with the same game state
|
||||
|
||||
if (args.Container.ID != ent.Comp.BrainSlotId)
|
||||
return;
|
||||
|
||||
var brain = args.Entity;
|
||||
var linked = EnsureComp<MMILinkedComponent>(brain);
|
||||
linked.LinkedMMI = ent.Owner;
|
||||
Dirty(brain, linked);
|
||||
|
||||
if (_mind.TryGetMind(brain, out var mindId, out var mindComp))
|
||||
{
|
||||
_mind.TransferTo(mindId, ent.Owner, true, mind: mindComp);
|
||||
|
||||
if (!_roles.MindHasRole<SiliconBrainRoleComponent>(mindId))
|
||||
_roles.MindAddRole(mindId, SiliconBrainRole, silent: true);
|
||||
}
|
||||
|
||||
_appearance.SetData(ent.Owner, MMIVisuals.BrainPresent, true);
|
||||
}
|
||||
|
||||
private void OnMMIMindAdded(Entity<MMIComponent> ent, ref MindAddedMessage args)
|
||||
{
|
||||
_appearance.SetData(ent.Owner, MMIVisuals.HasMind, true);
|
||||
}
|
||||
|
||||
private void OnMMIMindRemoved(Entity<MMIComponent> ent, ref MindRemovedMessage args)
|
||||
{
|
||||
_appearance.SetData(ent.Owner, MMIVisuals.HasMind, false);
|
||||
}
|
||||
|
||||
private void OnMMILinkedMindAdded(Entity<MMILinkedComponent> ent, ref MindAddedMessage args)
|
||||
{
|
||||
if (ent.Comp.LinkedMMI == null || !_mind.TryGetMind(ent.Owner, out var mindId, out var mindComp))
|
||||
return;
|
||||
|
||||
_mind.TransferTo(mindId, ent.Comp.LinkedMMI, true, mind: mindComp);
|
||||
}
|
||||
|
||||
private void OnMMILinkedRemoved(Entity<MMILinkedComponent> ent, ref EntGotRemovedFromContainerMessage args)
|
||||
{
|
||||
if (_timing.ApplyingState)
|
||||
return; // The changes are already networked with the same game state
|
||||
|
||||
if (Terminating(ent.Owner))
|
||||
return;
|
||||
|
||||
if (ent.Comp.LinkedMMI is not { } linked)
|
||||
return;
|
||||
|
||||
RemCompDeferred<MMILinkedComponent>(ent.Owner);
|
||||
|
||||
if (_mind.TryGetMind(linked, out var mindId, out var mindComp))
|
||||
{
|
||||
if (_roles.MindHasRole<SiliconBrainRoleComponent>(mindId))
|
||||
_roles.MindRemoveRole<SiliconBrainRoleComponent>(mindId);
|
||||
|
||||
_mind.TransferTo(mindId, ent.Owner, true, mind: mindComp);
|
||||
}
|
||||
|
||||
_appearance.SetData(linked, MMIVisuals.BrainPresent, false);
|
||||
}
|
||||
}
|
||||
234
Content.Shared/Silicons/Borgs/SharedBorgSystem.Module.cs
Normal file
234
Content.Shared/Silicons/Borgs/SharedBorgSystem.Module.cs
Normal file
@@ -0,0 +1,234 @@
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Localizations;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Shared.Silicons.Borgs;
|
||||
|
||||
public abstract partial class SharedBorgSystem
|
||||
{
|
||||
private EntityQuery<BorgModuleComponent> _moduleQuery;
|
||||
|
||||
public void InitializeModule()
|
||||
{
|
||||
SubscribeLocalEvent<BorgModuleComponent, ExaminedEvent>(OnModuleExamine);
|
||||
SubscribeLocalEvent<BorgModuleComponent, EntGotInsertedIntoContainerMessage>(OnModuleGotInserted);
|
||||
SubscribeLocalEvent<BorgModuleComponent, EntGotRemovedFromContainerMessage>(OnModuleGotRemoved);
|
||||
|
||||
SubscribeLocalEvent<SelectableBorgModuleComponent, BorgModuleInstalledEvent>(OnSelectableInstalled);
|
||||
SubscribeLocalEvent<SelectableBorgModuleComponent, BorgModuleUninstalledEvent>(OnSelectableUninstalled);
|
||||
SubscribeLocalEvent<SelectableBorgModuleComponent, BorgModuleActionSelectedEvent>(OnSelectableAction);
|
||||
|
||||
SubscribeLocalEvent<ItemBorgModuleComponent, ComponentStartup>(OnProvideItemStartup);
|
||||
SubscribeLocalEvent<ItemBorgModuleComponent, BorgModuleSelectedEvent>(OnItemModuleSelected);
|
||||
SubscribeLocalEvent<ItemBorgModuleComponent, BorgModuleUnselectedEvent>(OnItemModuleUnselected);
|
||||
|
||||
_moduleQuery = GetEntityQuery<BorgModuleComponent>();
|
||||
}
|
||||
|
||||
private void OnModuleExamine(Entity<BorgModuleComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
if (ent.Comp.BorgFitTypes == null)
|
||||
return;
|
||||
|
||||
if (ent.Comp.BorgFitTypes.Count == 0)
|
||||
return;
|
||||
|
||||
var typeList = new List<string>();
|
||||
|
||||
foreach (var type in ent.Comp.BorgFitTypes)
|
||||
{
|
||||
typeList.Add(Loc.GetString(type));
|
||||
}
|
||||
|
||||
var types = ContentLocalizationManager.FormatList(typeList);
|
||||
args.PushMarkup(Loc.GetString("borg-module-fit", ("types", types)));
|
||||
}
|
||||
|
||||
private void OnModuleGotInserted(Entity<BorgModuleComponent> module, ref EntGotInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (_timing.ApplyingState)
|
||||
return; // The changes are already networked with the same game state
|
||||
|
||||
var chassis = args.Container.Owner;
|
||||
|
||||
if (!TryComp<BorgChassisComponent>(chassis, out var chassisComp) ||
|
||||
args.Container != chassisComp.ModuleContainer ||
|
||||
!chassisComp.Active)
|
||||
return;
|
||||
|
||||
InstallModule((chassis, chassisComp), module.AsNullable());
|
||||
}
|
||||
|
||||
private void OnModuleGotRemoved(Entity<BorgModuleComponent> module, ref EntGotRemovedFromContainerMessage args)
|
||||
{
|
||||
if (_timing.ApplyingState)
|
||||
return; // The changes are already networked with the same game state
|
||||
|
||||
var chassis = args.Container.Owner;
|
||||
|
||||
if (!TryComp<BorgChassisComponent>(chassis, out var chassisComp) ||
|
||||
args.Container != chassisComp.ModuleContainer)
|
||||
return;
|
||||
|
||||
UninstallModule((chassis, chassisComp), module.AsNullable());
|
||||
}
|
||||
|
||||
private void OnSelectableInstalled(Entity<SelectableBorgModuleComponent> module, ref BorgModuleInstalledEvent args)
|
||||
{
|
||||
var chassis = args.ChassisEnt;
|
||||
|
||||
if (_actions.AddAction(chassis, ref module.Comp.ModuleSwapActionEntity, out var action, module.Comp.ModuleSwapAction, module.Owner))
|
||||
{
|
||||
Dirty(module); // for ModuleSwapActionEntity after the action has been spawned
|
||||
var actEnt = (module.Comp.ModuleSwapActionEntity.Value, action);
|
||||
_actions.SetEntityIcon(actEnt, module.Owner);
|
||||
if (TryComp<BorgModuleIconComponent>(module, out var moduleIconComp))
|
||||
_actions.SetIcon(actEnt, moduleIconComp.Icon);
|
||||
|
||||
/// Set a custom name and description on the action. The borg module action prototypes are shared across
|
||||
/// all modules. Extract localized names, then populate variables with the info from the module itself.
|
||||
var moduleName = Name(module);
|
||||
var actionMetaData = MetaData(module.Comp.ModuleSwapActionEntity.Value);
|
||||
|
||||
var instanceName = Loc.GetString("borg-module-action-name", ("moduleName", moduleName));
|
||||
_metaData.SetEntityName(module.Comp.ModuleSwapActionEntity.Value, instanceName, actionMetaData);
|
||||
var instanceDesc = Loc.GetString("borg-module-action-description", ("moduleName", moduleName));
|
||||
_metaData.SetEntityDescription(module.Comp.ModuleSwapActionEntity.Value, instanceDesc, actionMetaData);
|
||||
}
|
||||
|
||||
if (!TryComp<BorgChassisComponent>(chassis, out var chassisComp))
|
||||
return;
|
||||
|
||||
if (chassisComp.SelectedModule == null)
|
||||
SelectModule((chassis, chassisComp), module.Owner);
|
||||
}
|
||||
|
||||
private void OnSelectableUninstalled(Entity<SelectableBorgModuleComponent> module, ref BorgModuleUninstalledEvent args)
|
||||
{
|
||||
var chassis = args.ChassisEnt;
|
||||
_actions.RemoveProvidedActions(chassis, module.Owner);
|
||||
if (!TryComp<BorgChassisComponent>(chassis, out var chassisComp))
|
||||
return;
|
||||
|
||||
if (chassisComp.SelectedModule == module.Owner)
|
||||
UnselectModule((chassis, chassisComp));
|
||||
}
|
||||
|
||||
private void OnSelectableAction(Entity<SelectableBorgModuleComponent> module, ref BorgModuleActionSelectedEvent args)
|
||||
{
|
||||
var chassis = args.Performer;
|
||||
if (!TryComp<BorgChassisComponent>(chassis, out var chassisComp))
|
||||
return;
|
||||
|
||||
var selected = chassisComp.SelectedModule;
|
||||
|
||||
args.Handled = true;
|
||||
UnselectModule((chassis, chassisComp));
|
||||
|
||||
if (selected != module.Owner)
|
||||
{
|
||||
SelectModule((chassis, chassisComp), module.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnProvideItemStartup(Entity<ItemBorgModuleComponent> module, ref ComponentStartup args)
|
||||
{
|
||||
_container.EnsureContainer<Container>(module.Owner, module.Comp.HoldingContainer);
|
||||
}
|
||||
|
||||
private void OnItemModuleSelected(Entity<ItemBorgModuleComponent> module, ref BorgModuleSelectedEvent args)
|
||||
{
|
||||
ProvideItems(args.Chassis, module.AsNullable());
|
||||
}
|
||||
|
||||
private void OnItemModuleUnselected(Entity<ItemBorgModuleComponent> module, ref BorgModuleUnselectedEvent args)
|
||||
{
|
||||
RemoveProvidedItems(args.Chassis, module.AsNullable());
|
||||
}
|
||||
|
||||
private void ProvideItems(Entity<BorgChassisComponent?> chassis, Entity<ItemBorgModuleComponent?> module)
|
||||
{
|
||||
if (!Resolve(chassis, ref chassis.Comp) || !Resolve(module, ref module.Comp))
|
||||
return;
|
||||
|
||||
if (!TryComp<HandsComponent>(chassis, out var hands))
|
||||
return;
|
||||
|
||||
if (!_container.TryGetContainer(module, module.Comp.HoldingContainer, out var container))
|
||||
return;
|
||||
|
||||
var xform = Transform(chassis);
|
||||
|
||||
for (var i = 0; i < module.Comp.Hands.Count; i++)
|
||||
{
|
||||
var hand = module.Comp.Hands[i];
|
||||
var handId = $"{GetNetEntity(module.Owner)}-hand-{i}";
|
||||
|
||||
_hands.AddHand((chassis.Owner, hands), handId, hand.Hand);
|
||||
EntityUid? item = null;
|
||||
|
||||
if (module.Comp.Spawned)
|
||||
{
|
||||
if (module.Comp.StoredItems.TryGetValue(handId, out var storedItem))
|
||||
{
|
||||
item = storedItem;
|
||||
// DoPickup handles removing the item from the container.
|
||||
}
|
||||
}
|
||||
else if (hand.Item is { } itemProto)
|
||||
{
|
||||
item = PredictedSpawnAtPosition(itemProto, xform.Coordinates);
|
||||
}
|
||||
|
||||
if (item is { } pickUp)
|
||||
{
|
||||
_hands.DoPickup(chassis, handId, pickUp, hands);
|
||||
if (!hand.ForceRemovable && hand.Hand.Whitelist == null && hand.Hand.Blacklist == null)
|
||||
{
|
||||
EnsureComp<UnremoveableComponent>(pickUp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.Comp.Spawned = true;
|
||||
Dirty(module);
|
||||
}
|
||||
|
||||
private void RemoveProvidedItems(Entity<BorgChassisComponent?> chassis, Entity<ItemBorgModuleComponent?> module)
|
||||
{
|
||||
if (!Resolve(chassis, ref chassis.Comp) || !Resolve(module, ref module.Comp))
|
||||
return;
|
||||
|
||||
if (!TryComp<HandsComponent>(chassis, out var hands))
|
||||
return;
|
||||
|
||||
if (!_container.TryGetContainer(module, module.Comp.HoldingContainer, out var container))
|
||||
return;
|
||||
|
||||
if (TerminatingOrDeleted(module))
|
||||
return;
|
||||
|
||||
for (var i = 0; i < module.Comp.Hands.Count; i++)
|
||||
{
|
||||
var handId = $"{GetNetEntity(module.Owner)}-hand-{i}";
|
||||
|
||||
if (_hands.TryGetHeldItem((chassis.Owner, hands), handId, out var held))
|
||||
{
|
||||
RemComp<UnremoveableComponent>(held.Value);
|
||||
_container.Insert(held.Value, container);
|
||||
module.Comp.StoredItems[handId] = held.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
module.Comp.StoredItems.Remove(handId);
|
||||
}
|
||||
|
||||
_hands.RemoveHand((chassis.Owner, hands), handId);
|
||||
}
|
||||
|
||||
Dirty(module);
|
||||
}
|
||||
}
|
||||
79
Content.Shared/Silicons/Borgs/SharedBorgSystem.Ui.cs
Normal file
79
Content.Shared/Silicons/Borgs/SharedBorgSystem.Ui.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.PowerCell.Components;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
|
||||
namespace Content.Shared.Silicons.Borgs;
|
||||
|
||||
public abstract partial class SharedBorgSystem
|
||||
{
|
||||
// CCvar
|
||||
private int _maxNameLength;
|
||||
|
||||
public void InitializeUI()
|
||||
{
|
||||
SubscribeLocalEvent<BorgChassisComponent, BorgEjectBrainBuiMessage>(OnEjectBrainBuiMessage);
|
||||
SubscribeLocalEvent<BorgChassisComponent, BorgEjectBatteryBuiMessage>(OnEjectBatteryBuiMessage);
|
||||
SubscribeLocalEvent<BorgChassisComponent, BorgSetNameBuiMessage>(OnSetNameBuiMessage);
|
||||
SubscribeLocalEvent<BorgChassisComponent, BorgRemoveModuleBuiMessage>(OnRemoveModuleBuiMessage);
|
||||
|
||||
Subs.CVar(_configuration, CCVars.MaxNameLength, value => _maxNameLength = value, true);
|
||||
}
|
||||
|
||||
public virtual void UpdateUI(Entity<BorgChassisComponent?> chassis) { }
|
||||
|
||||
private void OnEjectBrainBuiMessage(Entity<BorgChassisComponent> chassis, ref BorgEjectBrainBuiMessage args)
|
||||
{
|
||||
if (chassis.Comp.BrainEntity is not { } brain)
|
||||
return;
|
||||
|
||||
_adminLog.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{args.Actor} removed brain {brain} from borg {chassis.Owner}");
|
||||
_container.Remove(brain, chassis.Comp.BrainContainer);
|
||||
_hands.TryPickupAnyHand(args.Actor, brain);
|
||||
}
|
||||
|
||||
private void OnEjectBatteryBuiMessage(Entity<BorgChassisComponent> chassis, ref BorgEjectBatteryBuiMessage args)
|
||||
{
|
||||
if (_powerCell.TryEjectBatteryFromSlot(chassis.Owner, out var powerCell, args.Actor))
|
||||
_hands.TryPickupAnyHand(args.Actor, powerCell.Value);
|
||||
}
|
||||
|
||||
private void OnSetNameBuiMessage(Entity<BorgChassisComponent> chassis, ref BorgSetNameBuiMessage args)
|
||||
{
|
||||
if (args.Name.Length > _maxNameLength ||
|
||||
args.Name.Length == 0 ||
|
||||
string.IsNullOrWhiteSpace(args.Name) ||
|
||||
string.IsNullOrEmpty(args.Name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var name = args.Name.Trim();
|
||||
|
||||
var metaData = MetaData(chassis);
|
||||
|
||||
// don't change the name if the value doesn't actually change
|
||||
if (metaData.EntityName.Equals(name, StringComparison.InvariantCulture))
|
||||
return;
|
||||
|
||||
_adminLog.Add(LogType.Action, LogImpact.High, $"{args.Actor} set borg \"{chassis.Owner}\"'s name to: {name}");
|
||||
_metaData.SetEntityName(chassis, name, metaData);
|
||||
}
|
||||
|
||||
private void OnRemoveModuleBuiMessage(Entity<BorgChassisComponent> chassis, ref BorgRemoveModuleBuiMessage args)
|
||||
{
|
||||
var module = GetEntity(args.Module);
|
||||
|
||||
if (!chassis.Comp.ModuleContainer.Contains(module))
|
||||
return;
|
||||
|
||||
if (!CanRemoveModule((module, Comp<BorgModuleComponent>(module))))
|
||||
return;
|
||||
|
||||
_adminLog.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{args.Actor} removed module {module} from borg {chassis.Owner}");
|
||||
_container.Remove(module, chassis.Comp.ModuleContainer);
|
||||
_hands.TryPickupAnyHand(args.Actor, module);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,37 @@
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Body.Events;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Item.ItemToggle;
|
||||
using Content.Shared.Localizations;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Light;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Pointing;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.PowerCell;
|
||||
using Content.Shared.PowerCell.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.Wires;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Silicons.Borgs;
|
||||
|
||||
@@ -19,46 +40,63 @@ namespace Content.Shared.Silicons.Borgs;
|
||||
/// </summary>
|
||||
public abstract partial class SharedBorgSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly SharedContainerSystem Container = default!;
|
||||
[Dependency] protected readonly ItemSlotsSystem ItemSlots = default!;
|
||||
[Dependency] protected readonly ItemToggleSystem Toggle = default!;
|
||||
[Dependency] protected readonly SharedPopupSystem Popup = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _roles = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
|
||||
[Dependency] private readonly PowerCellSystem _powerCell = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly ThrowingSystem _throwing = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _player = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configuration = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly SharedHandheldLightSystem _handheldLight = default!;
|
||||
[Dependency] private readonly SharedAccessSystem _access = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
InitializeMMI();
|
||||
InitializeModule();
|
||||
InitializeRelay();
|
||||
InitializeUI();
|
||||
|
||||
SubscribeLocalEvent<TryGetIdentityShortInfoEvent>(OnTryGetIdentityShortInfo);
|
||||
|
||||
SubscribeLocalEvent<BorgChassisComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<BorgChassisComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<BorgChassisComponent, ItemSlotInsertAttemptEvent>(OnItemSlotInsertAttempt);
|
||||
SubscribeLocalEvent<BorgChassisComponent, ItemSlotEjectAttemptEvent>(OnItemSlotEjectAttempt);
|
||||
SubscribeLocalEvent<BorgChassisComponent, EntInsertedIntoContainerMessage>(OnInserted);
|
||||
SubscribeLocalEvent<BorgChassisComponent, EntRemovedFromContainerMessage>(OnRemoved);
|
||||
SubscribeLocalEvent<BorgChassisComponent, MindAddedMessage>(OnMindAdded);
|
||||
SubscribeLocalEvent<BorgChassisComponent, MindRemovedMessage>(OnMindRemoved);
|
||||
SubscribeLocalEvent<BorgChassisComponent, AfterInteractUsingEvent>(OnChassisInteractUsing);
|
||||
SubscribeLocalEvent<BorgChassisComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovementSpeedModifiers);
|
||||
SubscribeLocalEvent<BorgChassisComponent, ActivatableUIOpenAttemptEvent>(OnUIOpenAttempt);
|
||||
SubscribeLocalEvent<TryGetIdentityShortInfoEvent>(OnTryGetIdentityShortInfo);
|
||||
SubscribeLocalEvent<BorgModuleComponent, ExaminedEvent>(OnModuleExamine);
|
||||
SubscribeLocalEvent<BorgChassisComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
SubscribeLocalEvent<BorgChassisComponent, BeingGibbedEvent>(OnBeingGibbed);
|
||||
SubscribeLocalEvent<BorgChassisComponent, GetCharactedDeadIcEvent>(OnGetDeadIC);
|
||||
SubscribeLocalEvent<BorgChassisComponent, GetCharacterUnrevivableIcEvent>(OnGetUnrevivableIC);
|
||||
SubscribeLocalEvent<BorgChassisComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
|
||||
SubscribeLocalEvent<BorgChassisComponent, PowerCellChangedEvent>(OnPowerCellChanged);
|
||||
|
||||
InitializeRelay();
|
||||
}
|
||||
SubscribeLocalEvent<BorgBrainComponent, MindAddedMessage>(OnBrainMindAdded);
|
||||
SubscribeLocalEvent<BorgBrainComponent, PointAttemptEvent>(OnBrainPointAttempt);
|
||||
|
||||
private void OnModuleExamine(Entity<BorgModuleComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
if (ent.Comp.BorgFitTypes == null)
|
||||
return;
|
||||
|
||||
if (ent.Comp.BorgFitTypes.Count == 0)
|
||||
return;
|
||||
|
||||
var typeList = new List<string>();
|
||||
|
||||
foreach (var type in ent.Comp.BorgFitTypes)
|
||||
{
|
||||
typeList.Add(Loc.GetString(type));
|
||||
}
|
||||
|
||||
var types = ContentLocalizationManager.FormatList(typeList);
|
||||
args.PushMarkup(Loc.GetString("borg-module-fit", ("types", types)));
|
||||
}
|
||||
|
||||
private void OnTryGetIdentityShortInfo(TryGetIdentityShortInfoEvent args)
|
||||
@@ -68,6 +106,8 @@ public abstract partial class SharedBorgSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Why the hell is this only broadcasted and not raised directed on the entity?
|
||||
// This is doing a ton of HasComps/TryComps.
|
||||
if (!HasComp<BorgChassisComponent>(args.ForActor))
|
||||
{
|
||||
return;
|
||||
@@ -77,82 +117,265 @@ public abstract partial class SharedBorgSystem : EntitySystem
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnItemSlotInsertAttempt(EntityUid uid, BorgChassisComponent component, ref ItemSlotInsertAttemptEvent args)
|
||||
private void OnStartup(Entity<BorgChassisComponent> chassis, ref ComponentStartup args)
|
||||
{
|
||||
if (!TryComp<ContainerManagerComponent>(chassis, out var containerManager))
|
||||
return;
|
||||
|
||||
chassis.Comp.BrainContainer = _container.EnsureContainer<ContainerSlot>(chassis.Owner, chassis.Comp.BrainContainerId, containerManager);
|
||||
chassis.Comp.ModuleContainer = _container.EnsureContainer<Container>(chassis.Owner, chassis.Comp.ModuleContainerId, containerManager);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<BorgChassisComponent> chassis, ref MapInitEvent args)
|
||||
{
|
||||
_movementSpeedModifier.RefreshMovementSpeedModifiers(chassis.Owner);
|
||||
}
|
||||
|
||||
private void OnItemSlotInsertAttempt(Entity<BorgChassisComponent> chassis, ref ItemSlotInsertAttemptEvent args)
|
||||
{
|
||||
if (args.Cancelled)
|
||||
return;
|
||||
|
||||
if (!TryComp<PowerCellSlotComponent>(uid, out var cellSlotComp) ||
|
||||
!TryComp<WiresPanelComponent>(uid, out var panel))
|
||||
if (!TryComp<PowerCellSlotComponent>(chassis, out var cellSlotComp) ||
|
||||
!TryComp<WiresPanelComponent>(chassis, out var panelComp))
|
||||
return;
|
||||
|
||||
if (!ItemSlots.TryGetSlot(uid, cellSlotComp.CellSlotId, out var cellSlot) || cellSlot != args.Slot)
|
||||
if (!_itemSlots.TryGetSlot(chassis.Owner, cellSlotComp.CellSlotId, out var cellSlot) || cellSlot != args.Slot)
|
||||
return;
|
||||
|
||||
if (!panel.Open || args.User == uid)
|
||||
if (!panelComp.Open || args.User == chassis.Owner)
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void OnItemSlotEjectAttempt(EntityUid uid, BorgChassisComponent component, ref ItemSlotEjectAttemptEvent args)
|
||||
private void OnItemSlotEjectAttempt(Entity<BorgChassisComponent> chassis, ref ItemSlotEjectAttemptEvent args)
|
||||
{
|
||||
if (args.Cancelled)
|
||||
return;
|
||||
|
||||
if (!TryComp<PowerCellSlotComponent>(uid, out var cellSlotComp) ||
|
||||
!TryComp<WiresPanelComponent>(uid, out var panel))
|
||||
if (!TryComp<PowerCellSlotComponent>(chassis, out var cellSlotComp) ||
|
||||
!TryComp<WiresPanelComponent>(chassis, out var panel))
|
||||
return;
|
||||
|
||||
if (!ItemSlots.TryGetSlot(uid, cellSlotComp.CellSlotId, out var cellSlot) || cellSlot != args.Slot)
|
||||
if (!_itemSlots.TryGetSlot(chassis.Owner, cellSlotComp.CellSlotId, out var cellSlot) || cellSlot != args.Slot)
|
||||
return;
|
||||
|
||||
if (!panel.Open || args.User == uid)
|
||||
if (!panel.Open || args.User == chassis.Owner)
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, BorgChassisComponent component, ComponentStartup args)
|
||||
// TODO: consider transferring over the ghost role? managing that might suck.
|
||||
protected virtual void OnInserted(Entity<BorgChassisComponent> chassis, ref EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (!TryComp<ContainerManagerComponent>(uid, out var containerManager))
|
||||
if (_timing.ApplyingState)
|
||||
return; // The changes are already networked with the same game state
|
||||
|
||||
if (args.Container != chassis.Comp.BrainContainer)
|
||||
return;
|
||||
|
||||
component.BrainContainer = Container.EnsureContainer<ContainerSlot>(uid, component.BrainContainerId, containerManager);
|
||||
component.ModuleContainer = Container.EnsureContainer<Container>(uid, component.ModuleContainerId, containerManager);
|
||||
if (HasComp<BorgBrainComponent>(args.Entity) && _mind.TryGetMind(args.Entity, out var mindId, out var mind))
|
||||
{
|
||||
_mind.TransferTo(mindId, chassis.Owner, mind: mind);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUIOpenAttempt(EntityUid uid, BorgChassisComponent component, ActivatableUIOpenAttemptEvent args)
|
||||
protected virtual void OnRemoved(Entity<BorgChassisComponent> chassis, ref EntRemovedFromContainerMessage args)
|
||||
{
|
||||
// borgs generaly can't view their own ui
|
||||
if (args.User == uid && !component.CanOpenSelfUi)
|
||||
args.Cancel();
|
||||
}
|
||||
if (_timing.ApplyingState)
|
||||
return; // The changes are already networked with the same game state
|
||||
|
||||
protected virtual void OnInserted(EntityUid uid, BorgChassisComponent component, EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void OnRemoved(EntityUid uid, BorgChassisComponent component, EntRemovedFromContainerMessage args)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void OnRefreshMovementSpeedModifiers(EntityUid uid, BorgChassisComponent component, RefreshMovementSpeedModifiersEvent args)
|
||||
{
|
||||
if (Toggle.IsActivated(uid))
|
||||
if (args.Container != chassis.Comp.BrainContainer)
|
||||
return;
|
||||
|
||||
if (!TryComp<MovementSpeedModifierComponent>(uid, out var movement))
|
||||
if (HasComp<BorgBrainComponent>(args.Entity) && _mind.TryGetMind(chassis.Owner, out var mindId, out var mind))
|
||||
{
|
||||
_mind.TransferTo(mindId, args.Entity, mind: mind);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMindAdded(Entity<BorgChassisComponent> chassis, ref MindAddedMessage args)
|
||||
{
|
||||
// Unpredicted because the event is raised on the server.
|
||||
_popup.PopupEntity(Loc.GetString("borg-mind-added", ("name", Identity.Name(chassis.Owner, EntityManager))), chassis.Owner);
|
||||
|
||||
if (CanActivate(chassis))
|
||||
SetActive(chassis, true);
|
||||
_appearance.SetData(chassis.Owner, BorgVisuals.HasPlayer, true);
|
||||
}
|
||||
|
||||
private void OnMindRemoved(Entity<BorgChassisComponent> chassis, ref MindRemovedMessage args)
|
||||
{
|
||||
// Unpredicted because the event is raised on the server.
|
||||
_popup.PopupEntity(Loc.GetString("borg-mind-removed", ("name", Identity.Name(chassis.Owner, EntityManager))), chassis.Owner);
|
||||
|
||||
SetActive(chassis, false);
|
||||
// Turn off the light so that the no-player visuals can be seen.
|
||||
if (TryComp<HandheldLightComponent>(chassis.Owner, out var light))
|
||||
_handheldLight.TurnOff((chassis.Owner, light), makeNoise: false); // Already plays a sound when toggling the borg off.
|
||||
_appearance.SetData(chassis.Owner, BorgVisuals.HasPlayer, false);
|
||||
}
|
||||
|
||||
private void OnChassisInteractUsing(Entity<BorgChassisComponent> chassis, ref AfterInteractUsingEvent args)
|
||||
{
|
||||
if (!args.CanReach || args.Handled || chassis.Owner == args.User)
|
||||
return;
|
||||
|
||||
var used = args.Used;
|
||||
TryComp<BorgBrainComponent>(used, out var brain);
|
||||
TryComp<BorgModuleComponent>(used, out var module);
|
||||
|
||||
if (TryComp<WiresPanelComponent>(chassis, out var panel) && !panel.Open)
|
||||
{
|
||||
if (brain != null || module != null)
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("borg-panel-not-open"), chassis, args.User);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (chassis.Comp.BrainEntity == null && brain != null &&
|
||||
_whitelist.IsWhitelistPassOrNull(chassis.Comp.BrainWhitelist, used))
|
||||
{
|
||||
if (TryComp<ActorComponent>(used, out var actor) && !CanPlayerBeBorged(actor.PlayerSession))
|
||||
{
|
||||
// Don't use PopupClient because CanPlayerBeBorged is not predicted.
|
||||
_popup.PopupEntity(Loc.GetString("borg-player-not-allowed"), used, args.User);
|
||||
return;
|
||||
}
|
||||
|
||||
_container.Insert(used, chassis.Comp.BrainContainer);
|
||||
_adminLog.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{args.User} installed brain {used} into borg {chassis.Owner}");
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (module != null && CanInsertModule(chassis.AsNullable(), (used, module), args.User))
|
||||
{
|
||||
InsertModule(chassis, used);
|
||||
_adminLog.Add(LogType.Action, LogImpact.Low,
|
||||
$"{args.User} installed module {used} into borg {chassis.Owner}");
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Make the borg slower without power.
|
||||
private void OnRefreshMovementSpeedModifiers(Entity<BorgChassisComponent> chassis, ref RefreshMovementSpeedModifiersEvent args)
|
||||
{
|
||||
if (chassis.Comp.Active)
|
||||
return;
|
||||
|
||||
if (!TryComp<MovementSpeedModifierComponent>(chassis, out var movement))
|
||||
return;
|
||||
|
||||
if (movement.BaseSprintSpeed == 0f)
|
||||
return; // We already cannot move.
|
||||
|
||||
// Slow down to walk speed.
|
||||
var sprintDif = movement.BaseWalkSpeed / movement.BaseSprintSpeed;
|
||||
args.ModifySpeed(1f, sprintDif);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="BorgModuleComponent.DefaultModule"/>.
|
||||
/// </summary>
|
||||
public void SetBorgModuleDefault(Entity<BorgModuleComponent> ent, bool newDefault)
|
||||
private void OnUIOpenAttempt(Entity<BorgChassisComponent> chassis, ref ActivatableUIOpenAttemptEvent args)
|
||||
{
|
||||
ent.Comp.DefaultModule = newDefault;
|
||||
Dirty(ent);
|
||||
// Borgs generally can't view their own UI.
|
||||
if (args.User == chassis.Owner && !chassis.Comp.CanOpenSelfUi)
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnMobStateChanged(Entity<BorgChassisComponent> chassis, ref MobStateChangedEvent args)
|
||||
{
|
||||
if (args.NewMobState == MobState.Alive)
|
||||
{
|
||||
if (CanActivate(chassis))
|
||||
SetActive(chassis, true, user: args.Origin);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetActive(chassis, false, user: args.Origin);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBeingGibbed(Entity<BorgChassisComponent> chassis, ref BeingGibbedEvent args)
|
||||
{
|
||||
// Don't use the ItemSlotsSystem eject method since we don't want to play a sound and want we to eject the battery even if the slot is locked.
|
||||
if (TryComp<PowerCellSlotComponent>(chassis, out var slotComp) &&
|
||||
_container.TryGetContainer(chassis, slotComp.CellSlotId, out var slotContainer))
|
||||
_container.EmptyContainer(slotContainer);
|
||||
|
||||
_container.EmptyContainer(chassis.Comp.BrainContainer);
|
||||
_container.EmptyContainer(chassis.Comp.ModuleContainer);
|
||||
}
|
||||
|
||||
private void OnGetDeadIC(Entity<BorgChassisComponent> chassis, ref GetCharactedDeadIcEvent args)
|
||||
{
|
||||
args.Dead = true;
|
||||
}
|
||||
|
||||
private void OnGetUnrevivableIC(Entity<BorgChassisComponent> chassis, ref GetCharacterUnrevivableIcEvent args)
|
||||
{
|
||||
args.Unrevivable = true;
|
||||
}
|
||||
|
||||
private void OnBrainMindAdded(Entity<BorgBrainComponent> brain, ref MindAddedMessage args)
|
||||
{
|
||||
if (!_container.TryGetContainingContainer(brain.Owner, out var container))
|
||||
return;
|
||||
|
||||
var borg = container.Owner;
|
||||
|
||||
if (!TryComp<BorgChassisComponent>(borg, out var chassisComponent) ||
|
||||
container.ID != chassisComponent.BrainContainerId)
|
||||
return;
|
||||
|
||||
if (!_mind.TryGetMind(brain.Owner, out var mindId, out var mind) ||
|
||||
!_player.TryGetSessionById(mind.UserId, out var session))
|
||||
return;
|
||||
|
||||
if (!CanPlayerBeBorged(session))
|
||||
{
|
||||
// Don't use PopupClient because MindAddedMessage and CanPlayerBeBorged are not predicted.
|
||||
_popup.PopupEntity(Loc.GetString("borg-player-not-allowed-eject"), brain);
|
||||
_container.RemoveEntity(borg, brain);
|
||||
_throwing.TryThrow(brain, _random.NextVector2() * 5, 5f);
|
||||
return;
|
||||
}
|
||||
|
||||
_mind.TransferTo(mindId, borg, mind: mind);
|
||||
}
|
||||
|
||||
private void OnBrainPointAttempt(Entity<BorgBrainComponent> brain, ref PointAttemptEvent args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
// Raised when the power cell is empty or removed from the borg.
|
||||
private void OnPowerCellSlotEmpty(Entity<BorgChassisComponent> chassis, ref PowerCellSlotEmptyEvent args)
|
||||
{
|
||||
SetActive(chassis, false);
|
||||
}
|
||||
|
||||
// Raised when a power cell is inserted.
|
||||
private void OnPowerCellChanged(Entity<BorgChassisComponent> chassis, ref PowerCellChangedEvent args)
|
||||
{
|
||||
if (CanActivate(chassis))
|
||||
SetActive(chassis, true);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
var curTime = _timing.CurTime;
|
||||
var query = EntityQueryEnumerator<BorgChassisComponent>();
|
||||
while (query.MoveNext(out var uid, out var borgChassis))
|
||||
{
|
||||
if (curTime < borgChassis.NextBatteryUpdate)
|
||||
continue;
|
||||
|
||||
borgChassis.NextBatteryUpdate = curTime + TimeSpan.FromSeconds(1);
|
||||
Dirty(uid, borgChassis);
|
||||
|
||||
// If we aren't drawing and suddenly get enough power to draw again, reenable.
|
||||
if (CanActivate((uid, borgChassis)))
|
||||
SetActive((uid, borgChassis), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,6 +283,7 @@
|
||||
description: alerts-battery-desc
|
||||
minSeverity: 0
|
||||
maxSeverity: 10
|
||||
clientHandled: true # the power cell is read on the client so that we don't have to periodically network the charge
|
||||
|
||||
- type: alert
|
||||
id: BorgBatteryNone
|
||||
@@ -292,6 +293,7 @@
|
||||
state: battery-none
|
||||
name: alerts-no-battery-name
|
||||
description: alerts-no-battery-desc
|
||||
clientHandled: true # the power cell battery is read on the client so that we don't have to periodically network the charge
|
||||
|
||||
- type: alert
|
||||
id: Internals
|
||||
|
||||
@@ -125,17 +125,11 @@
|
||||
- type: PowerCellSlot
|
||||
cellSlotId: cell_slot
|
||||
fitsInCharger: true
|
||||
- type: ItemToggle
|
||||
onActivate: false # You should not be able to turn off a borg temporarily.
|
||||
activated: false # gets activated when a mind is added
|
||||
onUse: false # no item-borg toggling sorry
|
||||
- type: ItemTogglePointLight
|
||||
- type: AccessToggle
|
||||
# TODO: refactor movement to just be based on toggle like speedboots but for the boots themselves
|
||||
# TODO: or just have sentient speedboots be fast idk
|
||||
- type: Access
|
||||
enabled: false # needs a player so that scientists can't drag around an empty borg for free AA
|
||||
- type: PowerCellDraw
|
||||
drawRate: 0.6
|
||||
# no ToggleCellDraw since dont want to lose access when power is gone
|
||||
enabled: false # the borg is only activated when a player takes over, otherwise the battery is likely empty
|
||||
- type: ItemSlots
|
||||
slots:
|
||||
cell_slot:
|
||||
@@ -203,6 +197,11 @@
|
||||
wattage: 0.2
|
||||
blinkingBehaviourId: blinking
|
||||
radiatingBehaviourId: radiating
|
||||
# These two components are required to make HandheldLight work, even though we don't even have ItemToggle.
|
||||
# The code is a total mess and needs a complete rewrite.
|
||||
- type: ItemTogglePointLight
|
||||
- type: ToggleableVisuals
|
||||
spriteLayer: light
|
||||
- type: LightBehaviour
|
||||
behaviours:
|
||||
- !type:FadeBehaviour
|
||||
@@ -219,8 +218,6 @@
|
||||
startValue: 0.1
|
||||
endValue: 2.0
|
||||
isLooped: true
|
||||
- type: ToggleableVisuals
|
||||
spriteLayer: light
|
||||
- type: PointLight
|
||||
enabled: false
|
||||
mask: /Textures/Effects/LightMasks/cone.png
|
||||
@@ -318,7 +315,6 @@
|
||||
factions:
|
||||
- NanoTrasen
|
||||
- type: Access
|
||||
enabled: false
|
||||
groups:
|
||||
- AllAccess
|
||||
tags:
|
||||
@@ -376,7 +372,6 @@
|
||||
factions:
|
||||
- NanoTrasen #The seemingly best fit. It was a regular NT cyborg once, after all.
|
||||
- type: Access
|
||||
enabled: false
|
||||
groups:
|
||||
- AllAccess #Randomized access would be fun. AllAccess is the best i can think of right now that does make it too hard for it to enter the station or navigate it..
|
||||
- type: AccessReader
|
||||
@@ -533,7 +528,6 @@
|
||||
- Robotics
|
||||
- Xenoborgs
|
||||
- type: Access
|
||||
enabled: false
|
||||
tags:
|
||||
- Xenoborg
|
||||
- type: AccessReader
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
- type: Input
|
||||
context: human
|
||||
- type: MMI
|
||||
brainSlot:
|
||||
name: positronic-brain-slot-component-slot-name-brain
|
||||
whitelist:
|
||||
components:
|
||||
- Brain
|
||||
- type: BorgBrain
|
||||
- type: BlockMovement
|
||||
- type: Examiner
|
||||
@@ -35,12 +40,6 @@
|
||||
- type: Speech
|
||||
speechSounds: Pai
|
||||
- type: ItemSlots
|
||||
slots:
|
||||
brain_slot:
|
||||
name: positronic-brain-slot-component-slot-name-brain
|
||||
whitelist:
|
||||
components:
|
||||
- Brain
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
brain_slot: !type:ContainerSlot
|
||||
@@ -59,14 +58,13 @@
|
||||
id: MMIFilled
|
||||
suffix: Filled
|
||||
components:
|
||||
- type: ItemSlots
|
||||
slots:
|
||||
brain_slot:
|
||||
name: "Brain"
|
||||
startingItem: OrganHumanBrain
|
||||
whitelist:
|
||||
components:
|
||||
- Brain
|
||||
- type: MMI
|
||||
brainSlot:
|
||||
name: positronic-brain-slot-component-slot-name-brain
|
||||
startingItem: OrganHumanBrain
|
||||
whitelist:
|
||||
components:
|
||||
- Brain
|
||||
|
||||
- type: entity
|
||||
parent: BaseItem
|
||||
|
||||
Reference in New Issue
Block a user