* Laws

* positronic brain and PAI rewrite

* MMI

* MMI pt. 2

* borg brain transfer

* Roleban support, Borg job (WIP), the end of mind shenaniganry

* battery drain, item slot cleanup, alerts

* visuals

* fix this pt1

* fix this pt2

* Modules, Lingering Stacks, Better borg flashlight

* Start on UI, fix battery alerts, expand activation/deactivation, low movement speed on no power.

* sprotes

* no zombie borgs

* oh fuck yeah i love a good relay

* charger

* fix the tiniest of sprite issues

* adjustable names

* a functional UI????

* foobar

* more modules

* this shit for some reason

* upstream

* genericize selectable borg modules

* upstream again

* holy fucking shit

* i love christ

* proper construction

* da job

* AA borgs

* and boom more shit

* admin logs

* laws redux

* ok just do this rq

* oh boy that looks like modules

* oh shit research

* testos passo

* so much shit holy fuck

* fuckit we SHIP

* last minute snags

* should've gotten me on a better day
This commit is contained in:
Nemanja
2023-08-12 17:39:58 -04:00
committed by GitHub
parent ac4f496535
commit 98fa00a21f
314 changed files with 7094 additions and 484 deletions

View File

@@ -6,6 +6,7 @@ using Content.Server.Cargo.Systems;
using Robust.Shared.Prototypes;
using Content.Shared.Damage.Prototypes;
using Content.Shared.Inventory;
using Content.Shared.Silicons.Borgs;
namespace Content.Server.Armor
{
@@ -22,6 +23,7 @@ namespace Content.Server.Armor
base.Initialize();
SubscribeLocalEvent<ArmorComponent, InventoryRelayedEvent<DamageModifyEvent>>(OnDamageModify);
SubscribeLocalEvent<ArmorComponent, BorgModuleRelayedEvent<DamageModifyEvent>>(OnBorgDamageModify);
SubscribeLocalEvent<ArmorComponent, GetVerbsEvent<ExamineVerb>>(OnArmorVerbExamine);
SubscribeLocalEvent<ArmorComponent, PriceCalculationEvent>(GetArmorPrice);
}
@@ -67,6 +69,11 @@ namespace Content.Server.Armor
args.Args.Damage = DamageSpecifier.ApplyModifierSet(args.Args.Damage, component.Modifiers);
}
private void OnBorgDamageModify(EntityUid uid, ArmorComponent component, ref BorgModuleRelayedEvent<DamageModifyEvent> args)
{
args.Args.Damage = DamageSpecifier.ApplyModifierSet(args.Args.Damage, component.Modifiers);
}
private void OnArmorVerbExamine(EntityUid uid, ArmorComponent component, GetVerbsEvent<ExamineVerb> args)
{
if (!args.CanInteract || !args.CanAccess)

View File

@@ -40,12 +40,12 @@ namespace Content.Server.Body.Systems
private void HandleMind(EntityUid newEntity, EntityUid oldEntity)
{
EntityManager.EnsureComponent<MindContainerComponent>(newEntity);
var oldMind = EntityManager.EnsureComponent<MindContainerComponent>(oldEntity);
EnsureComp<MindContainerComponent>(newEntity);
var oldMind = EnsureComp<MindContainerComponent>(oldEntity);
EnsureComp<GhostOnMoveComponent>(newEntity);
var ghostOnMove = EnsureComp<GhostOnMoveComponent>(newEntity);
if (HasComp<BodyComponent>(newEntity))
Comp<GhostOnMoveComponent>(newEntity).MustBeDead = true;
ghostOnMove.MustBeDead = true;
// TODO: This is an awful solution.
// Our greatest minds still can't figure out how to allow brains/heads to ghost without giving them the

View File

@@ -357,8 +357,12 @@ namespace Content.Server.Construction
if (!_container.TryGetContainer(uid, container, out var ourContainer, containerManager))
continue;
// NOTE: Only Container is supported by Construction!
var otherContainer = _container.EnsureContainer<Container>(newUid, container, newContainerManager);
if (!_container.TryGetContainer(newUid, container, out var otherContainer, newContainerManager))
{
// NOTE: Only Container is supported by Construction!
// todo: one day, the ensured container should be the same type as ourContainer
otherContainer = _container.EnsureContainer<Container>(newUid, container, newContainerManager);
}
for (var i = ourContainer.ContainedEntities.Count - 1; i >= 0; i--)
{

View File

@@ -4,6 +4,7 @@ using Content.Server.Construction.Components;
using Content.Server.Temperature.Components;
using Content.Server.Temperature.Systems;
using Content.Shared.Construction;
using Content.Shared.Construction.Components;
using Content.Shared.Construction.EntitySystems;
using Content.Shared.Construction.Steps;
using Content.Shared.DoAfter;
@@ -39,6 +40,7 @@ namespace Content.Server.Construction
new []{typeof(AnchorableSystem)},
new []{typeof(EncryptionKeySystem)});
SubscribeLocalEvent<ConstructionComponent, OnTemperatureChangeEvent>(EnqueueEvent);
SubscribeLocalEvent<ConstructionComponent, PartAssemblyPartInsertedEvent>(EnqueueEvent);
}
/// <summary>
@@ -388,6 +390,16 @@ namespace Content.Server.Construction
}
case PartAssemblyConstructionGraphStep partAssemblyStep:
{
if (ev is not PartAssemblyPartInsertedEvent)
break;
if (partAssemblyStep.Condition(uid, EntityManager))
return HandleResult.True;
return HandleResult.False;
}
#endregion
// --- CONSTRUCTION STEP EVENT HANDLING FINISH ---

View File

@@ -0,0 +1,40 @@
using Content.Shared.Random.Helpers;
using Robust.Server.Containers;
using Robust.Shared.Containers;
namespace Content.Server.Destructible.Thresholds.Behaviors
{
/// <summary>
/// Drop all items from specified containers
/// </summary>
[DataDefinition]
public sealed class EmptyContainersBehaviour : IThresholdBehavior
{
[DataField("containers")]
public List<string> Containers = new();
[DataField("randomOffset")]
public float RandomOffset = 0.25f;
public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null)
{
if (!system.EntityManager.TryGetComponent<ContainerManagerComponent>(owner, out var containerManager))
return;
var containerSys = system.EntityManager.System<ContainerSystem>();
foreach (var containerId in Containers)
{
if (!containerSys.TryGetContainer(owner, containerId, out var container, containerManager))
continue;
var entities = containerSys.EmptyContainer(container, true);
foreach (var ent in entities)
{
ent.RandomOffset(RandomOffset);
}
}
}
}
}

View File

@@ -274,7 +274,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
var prefList = new List<IPlayerSession>();
foreach (var player in allPlayers)
{
if (player.AttachedEntity == null || !HasComp<HumanoidAppearanceComponent>(player.AttachedEntity))
if (player.AttachedEntity == null || !HasComp<HumanoidAppearanceComponent>(player.AttachedEntity) || HasComp<ZombieImmuneComponent>(player.AttachedEntity))
continue;
playerList.Add(player);

View File

@@ -0,0 +1,38 @@
namespace Content.Server.Ghost.Roles.Components;
/// <summary>
/// This is used for a ghost role which can be toggled on and off at will, like a PAI.
/// </summary>
[RegisterComponent]
public sealed class ToggleableGhostRoleComponent : Component
{
[DataField("examineTextMindPresent")]
public string ExamineTextMindPresent = string.Empty;
[DataField("examineTextMindSearching")]
public string ExamineTextMindSearching = string.Empty;
[DataField("examineTextNoMind")]
public string ExamineTextNoMind = string.Empty;
[DataField("beginSearchingText")]
public string BeginSearchingText = string.Empty;
[DataField("roleName")]
public string RoleName = string.Empty;
[DataField("roleDescription")]
public string RoleDescription = string.Empty;
[DataField("wipeVerbText")]
public string WipeVerbText = string.Empty;
[DataField("wipeVerbPopup")]
public string WipeVerbPopup = string.Empty;
[DataField("stopSearchVerbText")]
public string StopSearchVerbText = string.Empty;
[DataField("stopSearchVerbPopup")]
public string StopSearchVerbPopup = string.Empty;
}

View File

@@ -0,0 +1,140 @@
using Content.Server.Ghost.Roles.Components;
using Content.Server.Mind.Components;
using Content.Server.PAI;
using Content.Shared.Examine;
using Content.Shared.Interaction.Events;
using Content.Shared.Mind;
using Content.Shared.Popups;
using Content.Shared.Verbs;
namespace Content.Server.Ghost.Roles;
/// <summary>
/// This handles logic and interaction related to <see cref="ToggleableGhostRoleComponent"/>
/// </summary>
public sealed class ToggleableGhostRoleSystem : EntitySystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
//todo this really shouldn't be in here but this system was converted from PAIs
[Dependency] private readonly PAISystem _pai = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<ToggleableGhostRoleComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<ToggleableGhostRoleComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<ToggleableGhostRoleComponent, MindAddedMessage>(OnMindAdded);
SubscribeLocalEvent<ToggleableGhostRoleComponent, MindRemovedMessage>(OnMindRemoved);
SubscribeLocalEvent<ToggleableGhostRoleComponent, GetVerbsEvent<ActivationVerb>>(AddWipeVerb);
}
private void OnUseInHand(EntityUid uid, ToggleableGhostRoleComponent component, UseInHandEvent args)
{
if (args.Handled)
return;
args.Handled = true;
// check if a mind is present
if (TryComp<MindContainerComponent>(uid, out var mind) && mind.HasMind)
{
_popup.PopupEntity(Loc.GetString(component.ExamineTextMindPresent), uid, args.User, PopupType.Large);
return;
}
if (HasComp<GhostTakeoverAvailableComponent>(uid))
{
_popup.PopupEntity(Loc.GetString(component.ExamineTextMindSearching), uid, args.User);
return;
}
_popup.PopupEntity(Loc.GetString(component.BeginSearchingText), uid, args.User);
UpdateAppearance(uid, ToggleableGhostRoleStatus.Searching);
var ghostRole = EnsureComp<GhostRoleComponent>(uid);
EnsureComp<GhostTakeoverAvailableComponent>(uid);
ghostRole.RoleName = Loc.GetString(component.RoleName);
ghostRole.RoleDescription = Loc.GetString(component.RoleDescription);
}
private void OnExamined(EntityUid uid, ToggleableGhostRoleComponent component, ExaminedEvent args)
{
if (!args.IsInDetailsRange)
return;
if (TryComp<MindContainerComponent>(uid, out var mind) && mind.HasMind)
{
args.PushMarkup(Loc.GetString(component.ExamineTextMindPresent));
}
else if (HasComp<GhostTakeoverAvailableComponent>(uid))
{
args.PushMarkup(Loc.GetString(component.ExamineTextMindSearching));
}
else
{
args.PushMarkup(Loc.GetString(component.ExamineTextNoMind));
}
}
private void OnMindAdded(EntityUid uid, ToggleableGhostRoleComponent pai, MindAddedMessage args)
{
// Mind was added, shutdown the ghost role stuff so it won't get in the way
RemComp<GhostTakeoverAvailableComponent>(uid);
UpdateAppearance(uid, ToggleableGhostRoleStatus.On);
}
private void OnMindRemoved(EntityUid uid, ToggleableGhostRoleComponent component, MindRemovedMessage args)
{
UpdateAppearance(uid, ToggleableGhostRoleStatus.Off);
}
private void UpdateAppearance(EntityUid uid, ToggleableGhostRoleStatus status)
{
_appearance.SetData(uid, ToggleableGhostRoleVisuals.Status, status);
}
private void AddWipeVerb(EntityUid uid, ToggleableGhostRoleComponent component, GetVerbsEvent<ActivationVerb> args)
{
if (!args.CanAccess || !args.CanInteract)
return;
if (TryComp<MindContainerComponent>(uid, out var mind) && mind.HasMind)
{
ActivationVerb verb = new()
{
Text = Loc.GetString(component.WipeVerbText),
Act = () =>
{
if (component.Deleted || !HasComp<MindContainerComponent>(uid))
return;
// Wiping device :(
// The shutdown of the Mind should cause automatic reset of the pAI during OnMindRemoved
// EDIT: But it doesn't!!!! Wtf? Do stuff manually
RemComp<MindContainerComponent>(uid);
_popup.PopupEntity(Loc.GetString(component.WipeVerbPopup), uid, args.User, PopupType.Large);
UpdateAppearance(uid, ToggleableGhostRoleStatus.Off);
_pai.PAITurningOff(uid);
}
};
args.Verbs.Add(verb);
}
else if (HasComp<GhostTakeoverAvailableComponent>(uid))
{
ActivationVerb verb = new()
{
Text = Loc.GetString(component.StopSearchVerbText),
Act = () =>
{
if (component.Deleted || !HasComp<GhostTakeoverAvailableComponent>(uid))
return;
RemComp<GhostTakeoverAvailableComponent>(uid);
RemComp<GhostRoleComponent>(uid);
_popup.PopupEntity(Loc.GetString(component.StopSearchVerbPopup), uid, args.User);
UpdateAppearance(uid, ToggleableGhostRoleStatus.Off);
_pai.PAITurningOff(uid);
}
};
args.Verbs.Add(verb);
}
}
}

View File

@@ -23,6 +23,7 @@ namespace Content.Server.Light.EntitySystems
[UsedImplicitly]
public sealed class HandheldLightSystem : SharedHandheldLightSystem
{
[Dependency] private readonly ActionsSystem _actions = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
@@ -40,6 +41,9 @@ namespace Content.Server.Light.EntitySystems
SubscribeLocalEvent<HandheldLightComponent, ComponentRemove>(OnRemove);
SubscribeLocalEvent<HandheldLightComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<HandheldLightComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<HandheldLightComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<HandheldLightComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<HandheldLightComponent, GetVerbsEvent<ActivationVerb>>(AddToggleLightVerb);
@@ -100,6 +104,24 @@ namespace Content.Server.Light.EntitySystems
args.State = new HandheldLightComponent.HandheldLightComponentState(component.Activated, GetLevel(uid, component));
}
private void OnMapInit(EntityUid uid, HandheldLightComponent component, MapInitEvent args)
{
if (component.ToggleAction == null
&& _proto.TryIndex(component.ToggleActionId, out InstantActionPrototype? act))
{
component.ToggleAction = new(act);
}
if (component.ToggleAction != null)
_actions.AddAction(uid, component.ToggleAction, null);
}
private void OnShutdown(EntityUid uid, HandheldLightComponent component, ComponentShutdown args)
{
if (component.ToggleAction != null)
_actions.RemoveAction(uid, component.ToggleAction);
}
private byte? GetLevel(EntityUid uid, HandheldLightComponent component)
{
// Curently every single flashlight has the same number of levels for status and that's all it uses the charge for
@@ -121,7 +143,7 @@ namespace Content.Server.Light.EntitySystems
private void OnActivate(EntityUid uid, HandheldLightComponent component, ActivateInWorldEvent args)
{
if (args.Handled)
if (args.Handled || !component.ToggleOnInteract)
return;
if (ToggleStatus(args.User, uid, component))
@@ -176,7 +198,7 @@ namespace Content.Server.Light.EntitySystems
private void AddToggleLightVerb(EntityUid uid, HandheldLightComponent component, GetVerbsEvent<ActivationVerb> args)
{
if (!args.CanAccess || !args.CanInteract)
if (!args.CanAccess || !args.CanInteract || !component.ToggleOnInteract)
return;
ActivationVerb verb = new()

View File

@@ -86,6 +86,9 @@ public sealed class HealingSystem : EntitySystem
// Re-verify that we can heal the damage.
_stacks.Use(args.Used.Value, 1);
if (_stacks.GetCount(args.Used.Value) <= 0)
dontRepeat = true;
if (uid != args.User)
{
_adminLogger.Add(LogType.Healed,
@@ -166,11 +169,8 @@ public sealed class HealingSystem : EntitySystem
return false;
}
if (component.HealingBeginSound != null)
{
_audio.PlayPvs(component.HealingBeginSound, uid,
_audio.PlayPvs(component.HealingBeginSound, uid,
AudioHelpers.WithVariation(0.125f, _random).WithVolume(-5f));
}
var isNotSelf = user != target;

View File

@@ -27,11 +27,11 @@ public sealed class MindSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;
[Dependency] private readonly ActorSystem _actor = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly GhostSystem _ghostSystem = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly ActorSystem _actor = default!;
// This is dictionary is required to track the minds of disconnected players that may have had their entity deleted.
private readonly Dictionary<NetUserId, Mind> _userMinds = new();
@@ -128,8 +128,8 @@ public sealed class MindSystem : EntitySystem
if (!Resolve(uid, ref mind, false))
return;
RaiseLocalEvent(uid, new MindRemovedMessage(), true);
mind.Mind = null;
RaiseLocalEvent(uid, new MindRemovedMessage(), true);
}
private void OnVisitingTerminating(EntityUid uid, VisitingMindComponent component, ref EntityTerminatingEvent args)
@@ -414,10 +414,6 @@ public sealed class MindSystem : EntitySystem
InternalEjectMind(oldEntity.Value, oldComp);
SetOwnedEntity(mind, entity, component);
if (mind.OwnedComponent != null){
InternalAssignMind(mind.OwnedEntity!.Value, mind, mind.OwnedComponent);
mind.OriginalOwnedEntity ??= mind.OwnedEntity;
}
// Don't do the full deletion cleanup if we're transferring to our VisitingEntity
if (alreadyAttached)
@@ -438,9 +434,15 @@ public sealed class MindSystem : EntitySystem
// Player is CURRENTLY connected.
if (mind.Session != null && !alreadyAttached && mind.VisitingEntity == null)
{
mind.Session.AttachToEntity(entity);
_actor.Attach(entity, mind.Session, true);
Log.Info($"Session {mind.Session.Name} transferred to entity {entity}.");
}
if (mind.OwnedComponent != null)
{
InternalAssignMind(mind.OwnedEntity!.Value, mind, mind.OwnedComponent);
mind.OriginalOwnedEntity ??= mind.OwnedEntity;
}
}
/// <summary>
@@ -559,7 +561,7 @@ public sealed class MindSystem : EntitySystem
public bool TryGetMind(EntityUid uid, [NotNullWhen(true)] out Mind? mind, MindContainerComponent? mindContainerComponent = null)
{
mind = null;
if (!Resolve(uid, ref mindContainerComponent))
if (!Resolve(uid, ref mindContainerComponent, false))
return false;
if (!mindContainerComponent.HasMind)

View File

@@ -1,17 +0,0 @@
using Content.Shared.NameIdentifier;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.NameIdentifier;
[RegisterComponent]
public sealed class NameIdentifierComponent : Component
{
[DataField("group", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<NameIdentifierGroupPrototype>))]
public string Group = string.Empty;
/// <summary>
/// The randomly generated ID for this entity.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("identifier")]
public int Identifier = -1;
}

View File

@@ -1,10 +1,8 @@
using System.Linq;
using Content.Shared.GameTicking;
using Content.Shared.GameTicking;
using Content.Shared.NameIdentifier;
using Robust.Shared.Collections;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Server.NameIdentifier;
@@ -15,12 +13,13 @@ public sealed class NameIdentifierSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
/// <summary>
/// Free IDs available per <see cref="NameIdentifierGroupPrototype"/>.
/// </summary>
[ViewVariables]
public Dictionary<string, List<int>> CurrentIds = new();
public readonly Dictionary<string, List<int>> CurrentIds = new();
public override void Initialize()
{
@@ -103,11 +102,16 @@ public sealed class NameIdentifierSystem : EntitySystem
component.Identifier = id;
}
component.FullIdentifier = group.FullName
? uniqueName
: $"({uniqueName})";
var meta = MetaData(uid);
// "DR-1234" as opposed to "drone (DR-1234)"
meta.EntityName = group.FullName
_metaData.SetEntityName(uid, group.FullName
? uniqueName
: $"{meta.EntityName} ({uniqueName})";
: $"{meta.EntityName} ({uniqueName})", meta);
Dirty(component);
}
private void InitialSetupPrototypes()

View File

@@ -1,91 +1,45 @@
using Content.Server.Ghost.Roles.Components;
using Content.Server.Instruments;
using Content.Server.Mind.Components;
using Content.Server.Popups;
using Content.Shared.Examine;
using Content.Shared.Interaction.Events;
using Content.Shared.PAI;
using Content.Shared.Popups;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
namespace Content.Server.PAI
{
public sealed class PAISystem : SharedPAISystem
{
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly InstrumentSystem _instrumentSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PAIComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<PAIComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<PAIComponent, MindAddedMessage>(OnMindAdded);
SubscribeLocalEvent<PAIComponent, MindRemovedMessage>(OnMindRemoved);
SubscribeLocalEvent<PAIComponent, GetVerbsEvent<ActivationVerb>>(AddWipeVerb);
}
private void OnExamined(EntityUid uid, PAIComponent component, ExaminedEvent args)
{
if (args.IsInDetailsRange)
{
if (EntityManager.TryGetComponent<MindContainerComponent>(uid, out var mind) && mind.HasMind)
{
args.PushMarkup(Loc.GetString("pai-system-pai-installed"));
}
else if (EntityManager.HasComponent<GhostTakeoverAvailableComponent>(uid))
{
args.PushMarkup(Loc.GetString("pai-system-still-searching"));
}
else
{
args.PushMarkup(Loc.GetString("pai-system-off"));
}
}
}
private void OnUseInHand(EntityUid uid, PAIComponent component, UseInHandEvent args)
{
if (args.Handled)
return;
if (!TryComp<MindContainerComponent>(uid, out var mind) || !mind.HasMind)
component.LastUser = args.User;
}
// Placeholder PAIs are essentially portable ghost role generators.
args.Handled = true;
// Check for pAI activation
if (EntityManager.TryGetComponent<MindContainerComponent>(uid, out var mind) && mind.HasMind)
{
_popupSystem.PopupEntity(Loc.GetString("pai-system-pai-installed"), uid, args.User, PopupType.Large);
private void OnMindAdded(EntityUid uid, PAIComponent component, MindAddedMessage args)
{
if (component.LastUser == null)
return;
}
else if (EntityManager.HasComponent<GhostTakeoverAvailableComponent>(uid))
{
_popupSystem.PopupEntity(Loc.GetString("pai-system-still-searching"), uid, args.User);
return;
}
// Ownership tag
string val = Loc.GetString("pai-system-pai-name", ("owner", args.User));
var val = Loc.GetString("pai-system-pai-name", ("owner", component.LastUser));
// TODO Identity? People shouldn't dox-themselves by carrying around a PAI.
// But having the pda's name permanently be "old lady's PAI" is weird.
// Changing the PAI's identity in a way that ties it to the owner's identity also seems weird.
// Cause then you could remotely figure out information about the owner's equipped items.
EntityManager.GetComponent<MetaDataComponent>(component.Owner).EntityName = val;
var ghostRole = EnsureComp<GhostRoleComponent>(uid);
EnsureComp<GhostTakeoverAvailableComponent>(uid);
ghostRole.RoleName = Loc.GetString("pai-system-role-name");
ghostRole.RoleDescription = Loc.GetString("pai-system-role-description");
_popupSystem.PopupEntity(Loc.GetString("pai-system-searching"), uid, args.User);
UpdatePAIAppearance(uid, PAIStatus.Searching);
_metaData.SetEntityName(uid, val);
}
private void OnMindRemoved(EntityUid uid, PAIComponent component, MindRemovedMessage args)
@@ -94,18 +48,8 @@ namespace Content.Server.PAI
PAITurningOff(uid);
}
private void OnMindAdded(EntityUid uid, PAIComponent pai, MindAddedMessage args)
public void PAITurningOff(EntityUid uid)
{
// Mind was added, shutdown the ghost role stuff so it won't get in the way
if (EntityManager.HasComponent<GhostTakeoverAvailableComponent>(uid))
EntityManager.RemoveComponent<GhostTakeoverAvailableComponent>(uid);
UpdatePAIAppearance(uid, PAIStatus.On);
}
private void PAITurningOff(EntityUid uid)
{
UpdatePAIAppearance(uid, PAIStatus.Off);
// Close the instrument interface if it was open
// before closing
if (HasComp<ActiveInstrumentComponent>(uid) && TryComp<ActorComponent>(uid, out var actor))
@@ -114,63 +58,12 @@ namespace Content.Server.PAI
}
// Stop instrument
if (EntityManager.TryGetComponent<InstrumentComponent>(uid, out var instrument)) _instrumentSystem.Clean(uid, instrument);
if (EntityManager.TryGetComponent<MetaDataComponent>(uid, out var metadata))
if (TryComp<InstrumentComponent>(uid, out var instrument)) _instrumentSystem.Clean(uid, instrument);
if (TryComp<MetaDataComponent>(uid, out var metadata))
{
var proto = metadata.EntityPrototype;
if (proto != null)
metadata.EntityName = proto.Name;
}
}
private void UpdatePAIAppearance(EntityUid uid, PAIStatus status)
{
if (EntityManager.TryGetComponent<AppearanceComponent>(uid, out var appearance))
{
_appearance.SetData(uid, PAIVisuals.Status, status, appearance);
}
}
private void AddWipeVerb(EntityUid uid, PAIComponent pai, GetVerbsEvent<ActivationVerb> args)
{
if (!args.CanAccess || !args.CanInteract)
return;
if (EntityManager.TryGetComponent<MindContainerComponent>(uid, out var mind) && mind.HasMind)
{
ActivationVerb verb = new();
verb.Text = Loc.GetString("pai-system-wipe-device-verb-text");
verb.Act = () => {
if (pai.Deleted)
return;
// Wiping device :(
// The shutdown of the Mind should cause automatic reset of the pAI during OnMindRemoved
// EDIT: But it doesn't!!!! Wtf? Do stuff manually
if (EntityManager.HasComponent<MindContainerComponent>(uid))
{
EntityManager.RemoveComponent<MindContainerComponent>(uid);
_popupSystem.PopupEntity(Loc.GetString("pai-system-wiped-device"), uid, args.User, PopupType.Large);
PAITurningOff(uid);
}
};
args.Verbs.Add(verb);
}
else if (EntityManager.HasComponent<GhostTakeoverAvailableComponent>(uid))
{
ActivationVerb verb = new();
verb.Text = Loc.GetString("pai-system-stop-searching-verb-text");
verb.Act = () => {
if (pai.Deleted)
return;
if (EntityManager.HasComponent<GhostTakeoverAvailableComponent>(uid))
{
EntityManager.RemoveComponent<GhostTakeoverAvailableComponent>(uid);
EntityManager.RemoveComponent<GhostRoleComponent>(uid);
_popupSystem.PopupEntity(Loc.GetString("pai-system-stopped-searching"), uid, args.User);
PAITurningOff(uid);
}
};
args.Verbs.Add(verb);
_metaData.SetEntityName(uid, proto.Name);
}
}
}

View File

@@ -1,22 +1,24 @@
using Content.Server.Construction;
using Content.Server.Power.Components;
using Content.Server.PowerCell;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Examine;
using Content.Shared.Power;
using Content.Shared.PowerCell.Components;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.Storage.Components;
using Robust.Server.Containers;
namespace Content.Server.Power.EntitySystems;
[UsedImplicitly]
internal sealed class ChargerSystem : EntitySystem
{
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
[Dependency] private readonly PowerCellSystem _cellSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _sharedAppearanceSystem = default!;
[Dependency] private readonly ContainerSystem _container = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
public override void Initialize()
{
@@ -27,6 +29,7 @@ internal sealed class ChargerSystem : EntitySystem
SubscribeLocalEvent<ChargerComponent, EntInsertedIntoContainerMessage>(OnInserted);
SubscribeLocalEvent<ChargerComponent, EntRemovedFromContainerMessage>(OnRemoved);
SubscribeLocalEvent<ChargerComponent, ContainerIsInsertingAttemptEvent>(OnInsertAttempt);
SubscribeLocalEvent<ChargerComponent, InsertIntoEntityStorageAttemptEvent>(OnEntityStorageInsertAttempt);
SubscribeLocalEvent<ChargerComponent, ExaminedEvent>(OnChargerExamine);
}
@@ -42,15 +45,19 @@ internal sealed class ChargerSystem : EntitySystem
public override void Update(float frameTime)
{
foreach (var (_, charger, slotComp) in EntityManager.EntityQuery<ActiveChargerComponent, ChargerComponent, ItemSlotsComponent>())
var query = EntityQueryEnumerator<ActiveChargerComponent, ChargerComponent, ContainerManagerComponent>();
while (query.MoveNext(out var uid, out _, out var charger, out var containerComp))
{
if (!_itemSlotsSystem.TryGetSlot(charger.Owner, charger.SlotId, out ItemSlot? slot, slotComp))
if (!_container.TryGetContainer(uid, charger.SlotId, out var container, containerComp))
continue;
if (charger.Status == CellChargerStatus.Empty || charger.Status == CellChargerStatus.Charged || !slot.HasItem)
if (charger.Status == CellChargerStatus.Empty || charger.Status == CellChargerStatus.Charged || container.ContainedEntities.Count == 0)
continue;
TransferPower(charger.Owner, slot.Item!.Value, charger, frameTime);
foreach (var contained in container.ContainedEntities)
{
TransferPower(uid, contained, charger, frameTime);
}
}
}
@@ -100,23 +107,32 @@ internal sealed class ChargerSystem : EntitySystem
if (args.Container.ID != component.SlotId)
return;
if (!TryComp(args.EntityUid, out PowerCellSlotComponent? cellSlot))
if (!TryComp<PowerCellSlotComponent>(args.EntityUid, out var cellSlot))
return;
if (!_itemSlotsSystem.TryGetSlot(args.EntityUid, cellSlot.CellSlotId, out ItemSlot? itemSlot))
return;
if (!cellSlot.FitsInCharger || !itemSlot.HasItem)
if (!cellSlot.FitsInCharger)
args.Cancel();
}
private void OnEntityStorageInsertAttempt(EntityUid uid, ChargerComponent component, ref InsertIntoEntityStorageAttemptEvent args)
{
if (!component.Initialized || args.Cancelled)
return;
if (!TryComp<PowerCellSlotComponent>(uid, out var cellSlot))
return;
if (!cellSlot.FitsInCharger)
args.Cancelled = true;
}
private void UpdateStatus(EntityUid uid, ChargerComponent component)
{
var status = GetStatus(uid, component);
if (component.Status == status || !TryComp(uid, out ApcPowerReceiverComponent? receiver))
return;
if (!_itemSlotsSystem.TryGetSlot(uid, component.SlotId, out ItemSlot? slot))
if (!_container.TryGetContainer(uid, component.SlotId, out var container))
return;
TryComp(uid, out AppearanceComponent? appearance);
@@ -136,25 +152,25 @@ internal sealed class ChargerSystem : EntitySystem
{
case CellChargerStatus.Off:
receiver.Load = 0;
_sharedAppearanceSystem.SetData(uid, CellVisual.Light, CellChargerStatus.Off, appearance);
_appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Off, appearance);
break;
case CellChargerStatus.Empty:
receiver.Load = 0;
_sharedAppearanceSystem.SetData(uid, CellVisual.Light, CellChargerStatus.Empty, appearance);
_appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Empty, appearance);
break;
case CellChargerStatus.Charging:
receiver.Load = component.ChargeRate;
_sharedAppearanceSystem.SetData(uid, CellVisual.Light, CellChargerStatus.Charging, appearance);
_appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Charging, appearance);
break;
case CellChargerStatus.Charged:
receiver.Load = 0;
_sharedAppearanceSystem.SetData(uid, CellVisual.Light, CellChargerStatus.Charged, appearance);
_appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Charged, appearance);
break;
default:
throw new ArgumentOutOfRangeException();
}
_sharedAppearanceSystem.SetData(uid, CellVisual.Occupied, slot.HasItem, appearance);
_appearance.SetData(uid, CellVisual.Occupied, container.ContainedEntities.Count != 0, appearance);
}
private CellChargerStatus GetStatus(EntityUid uid, ChargerComponent component)
@@ -171,16 +187,16 @@ internal sealed class ChargerSystem : EntitySystem
if (!apcPowerReceiverComponent.Powered)
return CellChargerStatus.Off;
if (!_itemSlotsSystem.TryGetSlot(uid, component.SlotId, out ItemSlot? slot))
if (!_container.TryGetContainer(uid, component.SlotId, out var container))
return CellChargerStatus.Off;
if (!slot.HasItem)
if (container.ContainedEntities.Count == 0)
return CellChargerStatus.Empty;
if (!SearchForBattery(slot.Item!.Value, out BatteryComponent? heldBattery))
if (!SearchForBattery(container.ContainedEntities.First(), out var heldBattery))
return CellChargerStatus.Off;
if (heldBattery != null && Math.Abs(heldBattery.MaxCharge - heldBattery.CurrentCharge) < 0.01)
if (Math.Abs(heldBattery.MaxCharge - heldBattery.CurrentCharge) < 0.01)
return CellChargerStatus.Charged;
return CellChargerStatus.Charging;
@@ -194,7 +210,7 @@ internal sealed class ChargerSystem : EntitySystem
if (!receiverComponent.Powered)
return;
if (!SearchForBattery(targetEntity, out BatteryComponent? heldBattery))
if (!SearchForBattery(targetEntity, out var heldBattery))
return;
heldBattery.CurrentCharge += component.ChargeRate * frameTime;
@@ -213,7 +229,7 @@ internal sealed class ChargerSystem : EntitySystem
if (!TryComp(uid, out component))
{
// or by checking for a power cell slot on the inserted entity
return _cellSystem.TryGetBatteryFromSlot(uid, out component);
return _powerCell.TryGetBatteryFromSlot(uid, out component);
}
return true;
}

View File

@@ -88,6 +88,9 @@ public sealed partial class PowerCellSystem : SharedPowerCellSystem
{
base.OnCellRemoved(uid, component, args);
if (args.Container.ID != component.CellSlotId)
return;
var ev = new PowerCellSlotEmptyEvent();
RaiseLocalEvent(uid, ref ev);
}
@@ -135,7 +138,7 @@ public sealed partial class PowerCellSystem : SharedPowerCellSystem
if (!Resolve(uid, ref battery, ref cell, false))
return true;
return HasCharge(uid, float.MinValue, cell, user);
return HasCharge(uid, battery.DrawRate, cell, user);
}
#endregion
@@ -229,19 +232,26 @@ public sealed partial class PowerCellSystem : SharedPowerCellSystem
private void OnCellExamined(EntityUid uid, PowerCellComponent component, ExaminedEvent args)
{
if (TryComp<BatteryComponent>(uid, out var battery))
OnBatteryExamined(uid, battery, args);
TryComp<BatteryComponent>(uid, out var battery);
OnBatteryExamined(uid, battery, args);
}
private void OnCellSlotExamined(EntityUid uid, PowerCellSlotComponent component, ExaminedEvent args)
{
if (TryGetBatteryFromSlot(uid, out var battery))
OnBatteryExamined(uid, battery, args);
TryGetBatteryFromSlot(uid, out var battery);
OnBatteryExamined(uid, battery, args);
}
private void OnBatteryExamined(EntityUid uid, BatteryComponent component, ExaminedEvent args)
private void OnBatteryExamined(EntityUid uid, BatteryComponent? component, ExaminedEvent args)
{
var charge = component.CurrentCharge / component.MaxCharge * 100;
args.PushMarkup(Loc.GetString("power-cell-component-examine-details", ("currentCharge", $"{charge:F0}")));
if (component != null)
{
var charge = component.CurrentCharge / component.MaxCharge * 100;
args.PushMarkup(Loc.GetString("power-cell-component-examine-details", ("currentCharge", $"{charge:F0}")));
}
else
{
args.PushMarkup(Loc.GetString("power-cell-component-examine-details-no-battery"));
}
}
}

View File

@@ -31,5 +31,11 @@ namespace Content.Server.Repairable
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] [DataField("selfRepairPenalty")]
public float SelfRepairPenalty = 3f;
/// <summary>
/// Whether or not an entity is allowed to repair itself.
/// </summary>
[DataField("allowSelfRepair")]
public bool AllowSelfRepair = true;
}
}

View File

@@ -62,7 +62,12 @@ namespace Content.Server.Repairable
// Add a penalty to how long it takes if the user is repairing itself
if (args.User == args.Target)
{
if (!component.AllowSelfRepair)
return;
delay *= component.SelfRepairPenalty;
}
// Run the repairing doafter
args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, delay, component.QualityNeeded, new RepairFinishedEvent());

View File

@@ -0,0 +1,79 @@
using Content.Server.Mind.Components;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Silicons.Borgs.Components;
using Robust.Shared.Containers;
namespace Content.Server.Silicons.Borgs;
/// <inheritdoc/>
public sealed partial class BorgSystem
{
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;
if (_mind.TryGetMind(ent, out var mind))
_mind.TransferTo(mind, uid, 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 mind) || component.LinkedMMI == null)
return;
_mind.TransferTo(mind, component.LinkedMMI, true);
}
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 mind))
_mind.TransferTo(mind, uid, true);
_appearance.SetData(linked, MMIVisuals.BrainPresent, false);
}
}

View File

@@ -0,0 +1,316 @@
using System.Linq;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction.Components;
using Content.Shared.Silicons.Borgs.Components;
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 ||
!chassisComp.Activated)
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 (Terminating(chassis))
return;
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)
{
component.ProvidedContainer = Container.EnsureContainer<Container>(uid, component.ProvidedContainerId);
}
private void OnSelectableInstalled(EntityUid uid, SelectableBorgModuleComponent component, ref BorgModuleInstalledEvent args)
{
var chassis = args.ChassisEnt;
component.ModuleSwapAction.EntityIcon = uid;
_actions.AddAction(chassis, component.ModuleSwapAction, uid);
SelectModule(chassis, uid, moduleComp: component);
}
private void OnSelectableUninstalled(EntityUid uid, SelectableBorgModuleComponent component, ref BorgModuleUninstalledEvent args)
{
var chassis = args.ChassisEnt;
_actions.RemoveProvidedActions(chassis, uid);
UnselectModule(chassis, uid, moduleComp: component);
}
private void OnSelectableAction(EntityUid uid, SelectableBorgModuleComponent component, BorgModuleActionSelectedEvent args)
{
var chassis = args.Performer;
if (!TryComp<BorgChassisComponent>(chassis, out var chassisComp))
return;
if (chassisComp.SelectedModule == uid)
{
UnselectModule(chassis, chassisComp.SelectedModule, chassisComp);
args.Handled = true;
return;
}
UnselectModule(chassis, chassisComp.SelectedModule, chassisComp);
SelectModule(chassis, uid, chassisComp, component);
args.Handled = true;
}
/// <summary>
/// Selects a module, enablind the borg to use its provided abilities.
/// </summary>
public void SelectModule(EntityUid chassis,
EntityUid moduleUid,
BorgChassisComponent? chassisComp = null,
SelectableBorgModuleComponent? moduleComp = null)
{
if (Terminating(chassis) || Deleted(chassis))
return;
if (!Resolve(chassis, ref chassisComp))
return;
if (chassisComp.SelectedModule != null)
return;
if (chassisComp.SelectedModule == moduleUid)
return;
if (!Resolve(moduleUid, ref moduleComp, false))
return;
var ev = new BorgModuleSelectedEvent(chassis);
RaiseLocalEvent(moduleUid, ref ev);
chassisComp.SelectedModule = moduleUid;
}
/// <summary>
/// Unselects a module, removing its provided abilities
/// </summary>
public void UnselectModule(EntityUid chassis,
EntityUid? moduleUid,
BorgChassisComponent? chassisComp = null,
SelectableBorgModuleComponent? moduleComp = null)
{
if (Terminating(chassis) || Deleted(chassis))
return;
if (!Resolve(chassis, ref chassisComp))
return;
if (moduleUid == null)
return;
if (chassisComp.SelectedModule != moduleUid)
return;
if (!Resolve(moduleUid.Value, ref moduleComp, false))
return;
var ev = new BorgModuleUnselectedEvent(chassis);
RaiseLocalEvent(moduleUid.Value, ref ev);
chassisComp.SelectedModule = null;
}
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;
var xform = Transform(chassis);
foreach (var itemProto in component.Items)
{
EntityUid item;
if (!component.ItemsCreated)
{
item = Spawn(itemProto, xform.Coordinates);
}
else
{
item = component.ProvidedContainer.ContainedEntities
.FirstOrDefault(ent => Prototype(ent)?.ID == itemProto);
if (!item.IsValid())
{
Log.Debug($"no items found: {component.ProvidedContainer.ContainedEntities.Count}");
continue;
}
component.ProvidedContainer.Remove(item, EntityManager, force: true);
}
if (!item.IsValid())
{
Log.Debug("no valid item");
continue;
}
var handId = $"{uid}-item{component.HandCounter}";
component.HandCounter++;
_hands.AddHand(chassis, handId, HandLocation.Middle, hands);
_hands.DoPickup(chassis, hands.Hands[handId], item, hands);
EnsureComp<UnremoveableComponent>(item);
component.ProvidedItems.Add(handId, item);
}
component.ItemsCreated = true;
}
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;
foreach (var (handId, item) in component.ProvidedItems)
{
if (!Deleted(item) && !Terminating(item))
{
RemComp<UnremoveableComponent>(item);
component.ProvidedContainer.Insert(item, EntityManager);
}
_hands.RemoveHand(chassis, handId, hands);
}
component.ProvidedItems.Clear();
}
/// <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)
return false;
if (component.ModuleWhitelist?.IsValid(module, EntityManager) == false)
{
if (user != null)
Popup.PopupEntity(Loc.GetString("borg-module-whitelist-deny"), uid, user.Value);
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);
}
}

View File

@@ -0,0 +1,108 @@
using System.Linq;
using Content.Server.UserInterface;
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;
namespace Content.Server.Silicons.Borgs;
/// <inheritdoc/>
public sealed partial class BorgSystem
{
public void InitializeUI()
{
SubscribeLocalEvent<BorgChassisComponent, BeforeActivatableUIOpenEvent>(OnBeforeBorgUiOpen);
SubscribeLocalEvent<BorgChassisComponent, BorgEjectBrainBuiMessage>(OnEjectBrainBuiMessage);
SubscribeLocalEvent<BorgChassisComponent, BorgEjectBatteryBuiMessage>(OnEjectBatteryBuiMessage);
SubscribeLocalEvent<BorgChassisComponent, BorgSetNameBuiMessage>(OnSetNameBuiMessage);
SubscribeLocalEvent<BorgChassisComponent, BorgRemoveModuleBuiMessage>(OnRemoveModuleBuiMessage);
}
private void OnBeforeBorgUiOpen(EntityUid uid, BorgChassisComponent component, BeforeActivatableUIOpenEvent args)
{
UpdateUI(uid, component);
}
private void OnEjectBrainBuiMessage(EntityUid uid, BorgChassisComponent component, BorgEjectBrainBuiMessage args)
{
if (args.Session.AttachedEntity is not { } attachedEntity || component.BrainEntity is not { } brain)
return;
_adminLog.Add(LogType.Action, LogImpact.Medium,
$"{ToPrettyString(attachedEntity):player} removed brain {ToPrettyString(brain)} from borg {ToPrettyString(uid)}");
component.BrainContainer.Remove(brain, EntityManager);
_hands.TryPickupAnyHand(attachedEntity, brain);
UpdateUI(uid, component);
}
private void OnEjectBatteryBuiMessage(EntityUid uid, BorgChassisComponent component, BorgEjectBatteryBuiMessage args)
{
if (args.Session.AttachedEntity is not { } attachedEntity ||
!TryComp<PowerCellSlotComponent>(uid, out var slotComp) ||
!Container.TryGetContainer(uid, slotComp.CellSlotId, out var container) ||
!container.ContainedEntities.Any())
{
return;
}
var ents = Container.EmptyContainer(container);
_hands.TryPickupAnyHand(attachedEntity, ents.First());
}
private void OnSetNameBuiMessage(EntityUid uid, BorgChassisComponent component, BorgSetNameBuiMessage args)
{
if (args.Session.AttachedEntity is not { } attachedEntity)
return;
if (args.Name.Length > HumanoidCharacterProfile.MaxNameLength ||
args.Name.Length == 0 ||
string.IsNullOrWhiteSpace(args.Name) ||
string.IsNullOrEmpty(args.Name))
{
return;
}
var name = args.Name.Trim();
if (TryComp<NameIdentifierComponent>(uid, out var identifier))
name = $"{name} {identifier.FullIdentifier}";
_metaData.SetEntityName(uid, name);
_adminLog.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(attachedEntity):player} set borg \"{ToPrettyString(uid)}\"'s name to: {name}");
}
private void OnRemoveModuleBuiMessage(EntityUid uid, BorgChassisComponent component, BorgRemoveModuleBuiMessage args)
{
if (args.Session.AttachedEntity is not { } attachedEntity)
return;
if (!component.ModuleContainer.Contains(args.Module))
return;
_adminLog.Add(LogType.Action, LogImpact.Medium,
$"{ToPrettyString(attachedEntity):player} removed module {ToPrettyString(args.Module)} from borg {ToPrettyString(uid)}");
component.ModuleContainer.Remove(args.Module);
_hands.TryPickupAnyHand(attachedEntity, args.Module);
UpdateUI(uid, component);
}
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.Charge / battery.MaxCharge;
}
var state = new BorgBuiState(chargePercent, hasBattery);
_ui.TrySetUiState(uid, BorgUiKey.Key, state);
}
}

View File

@@ -0,0 +1,318 @@
using Content.Server.Actions;
using Content.Server.Administration.Logs;
using Content.Server.Administration.Managers;
using Content.Server.Hands.Systems;
using Content.Server.Mind;
using Content.Server.Mind.Components;
using Content.Server.Players.PlayTimeTracking;
using Content.Server.PowerCell;
using Content.Server.UserInterface;
using Content.Shared.Alert;
using Content.Shared.Database;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Movement.Systems;
using Content.Shared.PowerCell;
using Content.Shared.PowerCell.Components;
using Content.Shared.Silicons.Borgs;
using Content.Shared.Silicons.Borgs.Components;
using Content.Shared.Throwing;
using Content.Shared.Wires;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Containers;
using Robust.Shared.Random;
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 IRobustRandom _random = default!;
[Dependency] private readonly ActionsSystem _actions = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
[Dependency] private readonly PlayTimeTrackingSystem _playTimeTracking = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!;
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BorgChassisComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<BorgChassisComponent, AfterInteractUsingEvent>(OnChassisInteractUsing);
SubscribeLocalEvent<BorgChassisComponent, MindAddedMessage>(OnMindAdded);
SubscribeLocalEvent<BorgChassisComponent, MindRemovedMessage>(OnMindRemoved);
SubscribeLocalEvent<BorgChassisComponent, PowerCellChangedEvent>(OnPowerCellChanged);
SubscribeLocalEvent<BorgChassisComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
SubscribeLocalEvent<BorgChassisComponent, ActivatableUIOpenAttemptEvent>(OnUIOpenAttempt);
SubscribeLocalEvent<BorgBrainComponent, MindAddedMessage>(OnBrainMindAdded);
InitializeModules();
InitializeMMI();
InitializeUI();
}
private void OnMapInit(EntityUid uid, BorgChassisComponent component, MapInitEvent args)
{
UpdateBatteryAlert(uid);
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
var coordinates = Transform(uid).Coordinates;
if (component.StartingBrain != null)
{
component.BrainContainer.Insert(Spawn(component.StartingBrain, coordinates), EntityManager);
}
foreach (var startingModule in component.StartingModules)
{
component.ModuleContainer.Insert(Spawn(startingModule, coordinates), EntityManager);
}
}
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 &&
component.BrainWhitelist?.IsValid(used) != false)
{
if (_mind.TryGetMind(used, out var mind) && mind.Session != null)
{
if (!CanPlayerBeBorgged(mind.Session, component))
{
Popup.PopupEntity(Loc.GetString("borg-player-not-allowed"), used, args.User);
return;
}
}
component.BrainContainer.Insert(used);
_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))
{
component.ModuleContainer.Insert(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);
}
}
// 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 mind))
{
_mind.TransferTo(mind, uid);
}
}
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 mind))
{
_mind.TransferTo(mind, args.Entity);
}
}
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 OnPowerCellChanged(EntityUid uid, BorgChassisComponent component, PowerCellChangedEvent args)
{
UpdateBatteryAlert(uid);
if (!TryComp<PowerCellDrawComponent>(uid, out var draw))
return;
// if we eject the battery or run out of charge, then disable
if (args.Ejected || !_powerCell.HasDrawCharge(uid))
{
DisableBorgAbilities(uid, component);
return;
}
// if we aren't drawing and suddenly get enough power to draw again, reeanble.
if (_powerCell.HasDrawCharge(uid, draw))
{
// only reenable the powerdraw if a player has the role.
if (!draw.Drawing && _mind.TryGetMind(uid, out _))
_powerCell.SetPowerCellDrawEnabled(uid, true);
EnableBorgAbilities(uid, component);
}
UpdateUI(uid, component);
}
private void OnPowerCellSlotEmpty(EntityUid uid, BorgChassisComponent component, ref PowerCellSlotEmptyEvent args)
{
DisableBorgAbilities(uid, component);
UpdateUI(uid, component);
}
private void OnUIOpenAttempt(EntityUid uid, BorgChassisComponent component, ActivatableUIOpenAttemptEvent args)
{
// borgs can't view their own ui
if (args.User == uid)
args.Cancel();
}
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 mind) || mind.Session == null)
return;
if (!CanPlayerBeBorgged(mind.Session, chassisComponent))
{
Popup.PopupEntity(Loc.GetString("borg-player-not-allowed-eject"), uid);
Container.RemoveEntity(containerEnt, uid);
_throwing.TryThrow(uid, _random.NextVector2() * 5, 5f);
return;
}
_mind.TransferTo(mind, containerEnt);
}
private void UpdateBatteryAlert(EntityUid uid, PowerCellSlotComponent? slotComponent = null)
{
if (!_powerCell.TryGetBatteryFromSlot(uid, out var battery, slotComponent))
{
_alerts.ClearAlert(uid, AlertType.BorgBattery);
_alerts.ShowAlert(uid, AlertType.BorgBatteryNone);
return;
}
var chargePercent = (short) MathF.Round(battery.CurrentCharge / battery.MaxCharge * 10f);
// we make sure 0 only shows if they have absolutely no battery.
// also account for floating point imprecision
if (chargePercent == 0 && _powerCell.HasDrawCharge(uid, cell: slotComponent))
{
chargePercent = 1;
}
_alerts.ClearAlert(uid, AlertType.BorgBatteryNone);
_alerts.ShowAlert(uid, AlertType.BorgBattery, chargePercent);
}
/// <summary>
/// Activates the borg, enabling all of its modules.
/// </summary>
public void EnableBorgAbilities(EntityUid uid, BorgChassisComponent component)
{
if (component.Activated)
return;
component.Activated = true;
InstallAllModules(uid, component);
Dirty(component);
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
}
/// <summary>
/// Deactivates the borg, disabling all of its modules and decreasing its speed.
/// </summary>
public void DisableBorgAbilities(EntityUid uid, BorgChassisComponent component)
{
if (!component.Activated)
return;
component.Activated = false;
DisableAllModules(uid, component);
Dirty(component);
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
}
/// <summary>
/// Activates a borg when a player occupies it
/// </summary>
public void BorgActivate(EntityUid uid, BorgChassisComponent component)
{
component.HasPlayer = true;
Popup.PopupEntity(Loc.GetString("borg-mind-added", ("name", Identity.Name(uid, EntityManager))), uid);
_powerCell.SetPowerCellDrawEnabled(uid, true);
_appearance.SetData(uid, BorgVisuals.HasPlayer, true);
Dirty(uid, component);
}
/// <summary>
/// Deactivates a borg when a player leaves it.
/// </summary>
public void BorgDeactivate(EntityUid uid, BorgChassisComponent component)
{
component.HasPlayer = false;
Popup.PopupEntity(Loc.GetString("borg-mind-removed", ("name", Identity.Name(uid, EntityManager))), uid);
_powerCell.SetPowerCellDrawEnabled(uid, false);
_appearance.SetData(uid, BorgVisuals.HasPlayer, false);
Dirty(uid, component);
}
/// <summary>
/// Checks that a player has fulfilled the requirements for the borg job.
/// If they don't have enough hours, they cannot be placed into a chassis.
/// </summary>
public bool CanPlayerBeBorgged(IPlayerSession session, BorgChassisComponent component)
{
var disallowedJobs = _playTimeTracking.GetDisallowedJobs(session);
if (disallowedJobs.Contains(component.BorgJobId))
return false;
if (_banManager.GetJobBans(session.UserId)?.Contains(component.BorgJobId) == true)
return false;
return true;
}
}

View File

@@ -0,0 +1,157 @@
using Content.Server.Chat.Managers;
using Content.Server.Mind.Components;
using Content.Server.Station.Systems;
using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
using Content.Shared.Chat;
using Content.Shared.Emag.Components;
using Content.Shared.Emag.Systems;
using Content.Shared.Examine;
using Content.Shared.Silicons.Laws;
using Content.Shared.Silicons.Laws.Components;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Prototypes;
namespace Content.Server.Silicons.Laws;
/// <inheritdoc/>
public sealed class SiliconLawSystem : SharedSiliconLawSystem
{
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SiliconLawBoundComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<SiliconLawBoundComponent, ComponentShutdown>(OnComponentShutdown);
SubscribeLocalEvent<SiliconLawBoundComponent, MindAddedMessage>(OnMindAdded);
SubscribeLocalEvent<SiliconLawBoundComponent, ToggleLawsScreenEvent>(OnToggleLawsScreen);
SubscribeLocalEvent<SiliconLawBoundComponent, BoundUIOpenedEvent>(OnBoundUIOpened);
SubscribeLocalEvent<SiliconLawProviderComponent, GetSiliconLawsEvent>(OnDirectedGetLaws);
SubscribeLocalEvent<EmagSiliconLawComponent, GetSiliconLawsEvent>(OnDirectedEmagGetLaws);
SubscribeLocalEvent<EmagSiliconLawComponent, ExaminedEvent>(OnExamined);
}
private void OnComponentStartup(EntityUid uid, SiliconLawBoundComponent component, ComponentStartup args)
{
component.ProvidedAction = new (_prototype.Index<InstantActionPrototype>(component.ViewLawsAction));
_actions.AddAction(uid, component.ProvidedAction, null);
}
private void OnComponentShutdown(EntityUid uid, SiliconLawBoundComponent component, ComponentShutdown args)
{
if (component.ProvidedAction != null)
_actions.RemoveAction(uid, component.ProvidedAction);
}
private void OnMindAdded(EntityUid uid, SiliconLawBoundComponent component, MindAddedMessage args)
{
if (!TryComp<ActorComponent>(uid, out var actor))
return;
var msg = Loc.GetString("laws-notify");
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", msg));
_chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMessage, default, false,
actor.PlayerSession.ConnectedClient, colorOverride: Color.FromHex("#2ed2fd"));
}
private void OnToggleLawsScreen(EntityUid uid, SiliconLawBoundComponent component, ToggleLawsScreenEvent args)
{
if (args.Handled || !TryComp<ActorComponent>(uid, out var actor))
return;
args.Handled = true;
_userInterface.TryToggleUi(uid, SiliconLawsUiKey.Key, actor.PlayerSession);
}
private void OnBoundUIOpened(EntityUid uid, SiliconLawBoundComponent component, BoundUIOpenedEvent args)
{
var state = new SiliconLawBuiState(GetLaws(uid));
_userInterface.TrySetUiState(args.Entity, SiliconLawsUiKey.Key, state, (IPlayerSession) args.Session);
}
private void OnDirectedGetLaws(EntityUid uid, SiliconLawProviderComponent component, ref GetSiliconLawsEvent args)
{
if (args.Handled || HasComp<EmaggedComponent>(uid) || component.Laws.Count == 0)
return;
foreach (var law in component.Laws)
{
args.Laws.Add(_prototype.Index<SiliconLawPrototype>(law));
}
args.Handled = true;
}
private void OnDirectedEmagGetLaws(EntityUid uid, EmagSiliconLawComponent component, ref GetSiliconLawsEvent args)
{
if (args.Handled || !HasComp<EmaggedComponent>(uid) || component.OwnerName == null)
return;
args.Laws.Add(new SiliconLaw
{
LawString = Loc.GetString("law-emag-custom", ("name", component.OwnerName)),
Order = 0
});
}
private void OnExamined(EntityUid uid, EmagSiliconLawComponent component, ExaminedEvent args)
{
if (!args.IsInDetailsRange || !HasComp<EmaggedComponent>(uid))
return;
args.PushMarkup(Loc.GetString("laws-compromised-examine"));
}
protected override void OnGotEmagged(EntityUid uid, EmagSiliconLawComponent component, ref GotEmaggedEvent args)
{
base.OnGotEmagged(uid, component, ref args);
NotifyLawsChanged(uid);
}
public List<SiliconLaw> GetLaws(EntityUid uid)
{
var xform = Transform(uid);
var ev = new GetSiliconLawsEvent(uid);
RaiseLocalEvent(uid, ref ev);
if (ev.Handled)
return ev.Laws;
if (_station.GetOwningStation(uid, xform) is { } station)
{
RaiseLocalEvent(station, ref ev);
if (ev.Handled)
return ev.Laws;
}
if (xform.GridUid is { } grid)
{
RaiseLocalEvent(grid, ref ev);
if (ev.Handled)
return ev.Laws;
}
RaiseLocalEvent(ref ev);
return ev.Laws;
}
public void NotifyLawsChanged(EntityUid uid)
{
if (!TryComp<ActorComponent>(uid, out var actor))
return;
var msg = Loc.GetString("laws-update-notify");
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", msg));
_chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMessage, default, false, actor.PlayerSession.ConnectedClient, colorOverride: Color.FromHex("#2ed2fd"));
}
}

View File

@@ -38,7 +38,7 @@ namespace Content.Server.Stack
base.SetCount(uid, amount, component);
// Queue delete stack if count reaches zero.
if (component.Count <= 0)
if (component.Count <= 0 && !component.Lingering)
QueueDel(uid);
}

View File

@@ -331,7 +331,7 @@ namespace Content.Server.Strip
if (!_inventorySystem.CanUnequip(user, target, slot, out var reason))
{
_popup.PopupCursor(reason, user);
_popup.PopupCursor(Loc.GetString(reason), user);
return false;
}

View File

@@ -0,0 +1,15 @@
namespace Content.Server.Wires;
/// <summary>
/// This is used for activatable UIs that require the entity to have a panel in a certain state.
/// </summary>
[RegisterComponent]
public sealed class ActivatableUIRequiresPanelComponent : Component
{
/// <summary>
/// TRUE: the panel must be open to access the UI.
/// FALSE: the panel must be closed to access the UI.
/// </summary>
[DataField("requireOpen"), ViewVariables(VVAccess.ReadWrite)]
public bool RequireOpen = true;
}

View File

@@ -3,6 +3,7 @@ using System.Linq;
using System.Threading;
using Content.Server.Administration.Logs;
using Content.Server.Power.Components;
using Content.Server.UserInterface;
using Content.Shared.Database;
using Content.Shared.DoAfter;
using Content.Shared.GameTicking;
@@ -55,6 +56,7 @@ public sealed class WiresSystem : SharedWiresSystem
SubscribeLocalEvent<WiresComponent, TimedWireEvent>(OnTimedWire);
SubscribeLocalEvent<WiresComponent, PowerChangedEvent>(OnWiresPowered);
SubscribeLocalEvent<WiresComponent, WireDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<ActivatableUIRequiresPanelComponent, ActivatableUIOpenAttemptEvent>(OnAttemptOpenActivatableUI);
}
private void SetOrCreateWireLayout(EntityUid uid, WiresComponent? wires = null)
@@ -494,6 +496,15 @@ public sealed class WiresSystem : SharedWiresSystem
}
}
private void OnAttemptOpenActivatableUI(EntityUid uid, ActivatableUIRequiresPanelComponent component, ActivatableUIOpenAttemptEvent args)
{
if (args.Cancelled || !TryComp<WiresPanelComponent>(uid, out var wires))
return;
if (component.RequireOpen != wires.Open)
args.Cancel();
}
private void OnMapInit(EntityUid uid, WiresComponent component, MapInitEvent args)
{
if (!string.IsNullOrEmpty(component.LayoutId))

View File

@@ -21,7 +21,6 @@ using Content.Server.NPC;
using Content.Server.NPC.Components;
using Content.Server.NPC.HTN;
using Content.Server.NPC.Systems;
using Content.Server.RoundEnd;
using Content.Shared.Humanoid;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
@@ -30,12 +29,10 @@ using Content.Shared.Movement.Systems;
using Content.Shared.Nutrition.Components;
using Content.Shared.Popups;
using Content.Shared.Roles;
using Content.Shared.Tools;
using Content.Shared.Tools.Components;
using Content.Shared.Weapons.Melee;
using Content.Shared.Zombies;
using Robust.Shared.Audio;
using Robust.Shared.Utility;
namespace Content.Server.Zombies
{
@@ -87,7 +84,7 @@ namespace Content.Server.Zombies
public void ZombifyEntity(EntityUid target, MobStateComponent? mobState = null)
{
//Don't zombfiy zombies
if (HasComp<ZombieComponent>(target))
if (HasComp<ZombieComponent>(target) || HasComp<ZombieImmuneComponent>(target))
return;
if (!Resolve(target, ref mobState, logMissing: false))