using Content.Shared.Silicons.Borgs;
using JetBrains.Annotations;
-using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
namespace Content.Client.Silicons.Borgs;
_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();
}
}
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;
[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;
IoCManager.InjectDependencies(this);
_nameModifier = _entity.System<NameModifierSystem>();
+ _powerCell = _entity.System<PowerCellSystem>();
+ _battery = _entity.System<PredictedBatterySystem>();
_maxNameLength = _cfgManager.GetCVar(CCVars.MaxNameLength);
NameLineEdit.OnTextChanged += OnNameChanged;
NameLineEdit.OnTextEntered += OnNameEntered;
NameLineEdit.OnFocusExit += OnNameFocusExit;
-
- UpdateBrainButton();
}
public void SetEntity(EntityUid entity)
NameIdentifierLabel.Visible = false;
NameLineEdit.Text = _entity.GetComponent<MetaDataComponent>(Entity).EntityName;
}
+
+ UpdateBatteryButton();
+ UpdateBrainButton();
+ UpdateModulePanel();
}
protected override void FrameUpdate(FrameEventArgs args)
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)));
+ ("charge", (int)MathF.Round(chargeFraction * 100)));
+ }
- UpdateBrainButton();
- UpdateModulePanel();
+ public void UpdateBatteryButton()
+ {
+ EjectBatteryButton.Disabled = !_powerCell.HasBattery(Entity);
}
- private void UpdateBrainButton()
+ public void UpdateBrainButton()
{
if (_entity.TryGetComponent(Entity, out BorgChassisComponent? chassis) && chassis.BrainEntity is { } brain)
{
}
}
- private void UpdateModulePanel()
+ public void UpdateModulePanel()
{
if (!_entity.TryGetComponent(Entity, out BorgChassisComponent? chassis))
return;
--- /dev/null
+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));
+ }
+}
-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)
borg.Comp.HasMindState = hasMindState;
borg.Comp.NoMindState = noMindState;
}
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+ UpdateBattery(frameTime);
+ }
}
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;
SubscribeLocalEvent<HandheldLightComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<HandheldLightComponent, ComponentShutdown>(OnShutdown);
- SubscribeLocalEvent<HandheldLightComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<HandheldLightComponent, ActivateInWorldEvent>(OnActivate);
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();
+++ /dev/null
-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);
- }
-}
+++ /dev/null
-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;
- }
-}
-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;
/// <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);
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);
}
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;
}
}
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
{
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;
}
+++ /dev/null
-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);
- }
- }
-}
-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;
/// <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";
{
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;
base.Update(frameTime);
UpdateTransponder(frameTime);
- UpdateBattery(frameTime);
}
}
/// </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
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
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
public sealed partial class HandheldLightComponent : Component
{
public byte? Level;
+
+ [DataField]
public bool Activated;
[ViewVariables(VVAccess.ReadWrite)]
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;
base.Initialize();
SubscribeLocalEvent<HandheldLightComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<HandheldLightComponent, ComponentHandleState>(OnHandleState);
-
+ SubscribeLocalEvent<HandheldLightComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<HandheldLightComponent, GetVerbsEvent<ActivationVerb>>(AddToggleLightVerb);
}
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))
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>
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;
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>
/// <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>
/// <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)
/// <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)
Key
}
+/// <summary>
+/// Send when a player uses the borg BUI to eject a brain.
+/// </summary>
[Serializable, NetSerializable]
-public sealed class BorgBuiState : BoundUserInterfaceState
-{
- public float ChargePercent;
-
- public bool HasBattery;
-
- public BorgBuiState(float chargePercent, bool hasBattery)
- {
- ChargePercent = chargePercent;
- HasBattery = hasBattery;
- }
-}
-
-[Serializable, NetSerializable]
-public sealed class BorgEjectBrainBuiMessage : BoundUserInterfaceMessage
-{
-
-}
+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
-{
-
-}
+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 : BoundUserInterfaceMessage
+public sealed class BorgSetNameBuiMessage(string name) : BoundUserInterfaceMessage
{
- public string Name;
-
- public BorgSetNameBuiMessage(string name)
- {
- Name = name;
- }
+ /// <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 BorgRemoveModuleBuiMessage : BoundUserInterfaceMessage
+public sealed class BorgRemoveModuleBuiMessage(NetEntity module) : BoundUserInterfaceMessage
{
- public NetEntity Module;
-
- public BorgRemoveModuleBuiMessage(NetEntity module)
- {
- Module = module;
- }
+ /// <summary>
+ /// The module to eject.
+ /// </summary>
+ public NetEntity Module = module;
}
using Content.Shared.Alert;
using Content.Shared.Whitelist;
+using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
/// "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>
[Serializable, NetSerializable]
public enum BorgVisuals : byte
{
- HasPlayer
+ HasPlayer,
+ Powered,
}
[Serializable, NetSerializable]
using Robust.Shared.GameStates;
-using Robust.Shared.Prototypes;
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>
/// <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);
using Content.Shared.Hands.Components;
-using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
/// <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>
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.
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();
/// 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";
}
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))]
/// <summary>
/// The MMI this entity is linked to.
/// </summary>
- [DataField("linkedMMI"), AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public EntityUid? LinkedMMI;
}
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
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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);
+ }
+}
+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;
/// </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);
-
- InitializeRelay();
- }
-
- private void OnModuleExamine(Entity<BorgModuleComponent> ent, ref ExaminedEvent args)
- {
- if (ent.Comp.BorgFitTypes == null)
- return;
-
- if (ent.Comp.BorgFitTypes.Count == 0)
- return;
+ SubscribeLocalEvent<BorgChassisComponent, MobStateChangedEvent>(OnMobStateChanged);
+ SubscribeLocalEvent<BorgChassisComponent, BeingGibbedEvent>(OnBeingGibbed);
+ SubscribeLocalEvent<BorgChassisComponent, GetCharactedDeadIcEvent>(OnGetDeadIC);
+ SubscribeLocalEvent<BorgChassisComponent, GetCharacterUnrevivableIcEvent>(OnGetUnrevivableIC);
+ SubscribeLocalEvent<BorgChassisComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
+ SubscribeLocalEvent<BorgChassisComponent, PowerCellChangedEvent>(OnPowerCellChanged);
- var typeList = new List<string>();
+ SubscribeLocalEvent<BorgBrainComponent, MindAddedMessage>(OnBrainMindAdded);
+ SubscribeLocalEvent<BorgBrainComponent, PointAttemptEvent>(OnBrainPointAttempt);
- 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)
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;
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
+
+ if (args.Container != chassis.Comp.BrainContainer)
+ return;
+
+ if (HasComp<BorgBrainComponent>(args.Entity) && _mind.TryGetMind(chassis.Owner, out var mindId, out var mind))
+ {
+ _mind.TransferTo(mindId, args.Entity, mind: mind);
+ }
}
- protected virtual void OnInserted(EntityUid uid, BorgChassisComponent component, EntInsertedIntoContainerMessage args)
+ 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);
}
- protected virtual void OnRemoved(EntityUid uid, BorgChassisComponent component, EntRemovedFromContainerMessage args)
+ 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;
+ }
}
- private void OnRefreshMovementSpeedModifiers(EntityUid uid, BorgChassisComponent component, RefreshMovementSpeedModifiersEvent args)
+ // Make the borg slower without power.
+ private void OnRefreshMovementSpeedModifiers(Entity<BorgChassisComponent> chassis, ref RefreshMovementSpeedModifiersEvent args)
{
- if (Toggle.IsActivated(uid))
+ if (chassis.Comp.Active)
return;
- if (!TryComp<MovementSpeedModifierComponent>(uid, out var movement))
+ 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);
+ }
}
}
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
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
- 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:
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
startValue: 0.1
endValue: 2.0
isLooped: true
- - type: ToggleableVisuals
- spriteLayer: light
- type: PointLight
enabled: false
mask: /Textures/Effects/LightMasks/cone.png
factions:
- NanoTrasen
- type: Access
- enabled: false
groups:
- AllAccess
tags:
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
- Robotics
- Xenoborgs
- type: Access
- enabled: false
tags:
- Xenoborg
- type: AccessReader
- type: Input
context: human
- type: MMI
+ brainSlot:
+ name: positronic-brain-slot-component-slot-name-brain
+ whitelist:
+ components:
+ - Brain
- type: BorgBrain
- type: BlockMovement
- type: Examiner
- 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
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