Merge remote-tracking branch 'upstream/stable' into ed-30-04-2025-upstream-sync
# Conflicts: # Content.Client/Parallax/ParallaxControl.cs # Content.Client/UserInterface/Systems/Storage/Controls/ItemGridPiece.cs # Content.IntegrationTests/Tests/PostMapInitTest.cs # Content.Server/Chat/Managers/ChatManager.cs # Content.Server/Fluids/EntitySystems/PuddleSystem.Evaporation.cs # Content.Server/Labels/Label/LabelSystem.cs # Content.Shared/Actions/SharedActionsSystem.cs # Content.Shared/Fluids/Components/EvaporationComponent.cs # Content.Shared/Labels/EntitySystems/SharedLabelSystem.cs # README.md # Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml # Resources/Prototypes/Maps/Pools/deathmatch.yml # Resources/Prototypes/Maps/arenas.yml
This commit is contained in:
14
Content.Server/Access/Components/IdBindComponent.cs
Normal file
14
Content.Server/Access/Components/IdBindComponent.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Server.Access.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class IdBindComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// If true, also tries to get the PDA and set the owner to the entity
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool BindPDAOwner = true;
|
||||
}
|
||||
|
||||
51
Content.Server/Access/Systems/IdBindSystem.cs
Normal file
51
Content.Server/Access/Systems/IdBindSystem.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Content.Server.Access.Components;
|
||||
using Content.Server.PDA;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server.Access.Systems;
|
||||
|
||||
public sealed class IdBindSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IdCardSystem _cardSystem = default!;
|
||||
[Dependency] private readonly PdaSystem _pdaSystem = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
//Activate on mind being added
|
||||
SubscribeLocalEvent<IdBindComponent, MindAddedMessage>(TryBind);
|
||||
}
|
||||
|
||||
private void TryBind(Entity<IdBindComponent> ent, ref MindAddedMessage args)
|
||||
{
|
||||
if (!_cardSystem.TryFindIdCard(ent, out var cardId))
|
||||
return;
|
||||
|
||||
var data = MetaData(ent);
|
||||
|
||||
_cardSystem.TryChangeFullName(cardId, data.EntityName, cardId);
|
||||
|
||||
if (!ent.Comp.BindPDAOwner)
|
||||
{
|
||||
//Remove after running once
|
||||
RemCompDeferred<IdBindComponent>(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
//Get PDA from main slot and set us as owner
|
||||
if (!_inventory.TryGetSlotEntity(ent, "id", out var uPda))
|
||||
return;
|
||||
|
||||
if (!TryComp<PdaComponent>(uPda, out var pDA))
|
||||
return;
|
||||
|
||||
_pdaSystem.SetOwner(uPda.Value, pDA, ent, data.EntityName);
|
||||
//Remove after running once
|
||||
RemCompDeferred<IdBindComponent>(ent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Containers;
|
||||
using Content.Server.StationRecords.Systems;
|
||||
using Content.Shared.Access.Components;
|
||||
using static Content.Shared.Access.Components.IdCardConsoleComponent;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Access;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.StationRecords;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Content.Shared.Throwing;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Linq;
|
||||
using static Content.Shared.Access.Components.IdCardConsoleComponent;
|
||||
using Content.Shared.Access;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Access.Systems;
|
||||
|
||||
@@ -26,6 +32,10 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
||||
[Dependency] private readonly AccessSystem _access = default!;
|
||||
[Dependency] private readonly IdCardSystem _idCard = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly ThrowingSystem _throwing = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -37,6 +47,11 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
||||
SubscribeLocalEvent<IdCardConsoleComponent, ComponentStartup>(UpdateUserInterface);
|
||||
SubscribeLocalEvent<IdCardConsoleComponent, EntInsertedIntoContainerMessage>(UpdateUserInterface);
|
||||
SubscribeLocalEvent<IdCardConsoleComponent, EntRemovedFromContainerMessage>(UpdateUserInterface);
|
||||
SubscribeLocalEvent<IdCardConsoleComponent, DamageChangedEvent>(OnDamageChanged);
|
||||
|
||||
// Intercept the event before anyone can do anything with it!
|
||||
SubscribeLocalEvent<IdCardConsoleComponent, MachineDeconstructedEvent>(OnMachineDeconstructed,
|
||||
before: [typeof(EmptyOnMachineDeconstructSystem), typeof(ItemSlotsSystem)]);
|
||||
}
|
||||
|
||||
private void OnWriteToTargetIdMessage(EntityUid uid, IdCardConsoleComponent component, WriteToTargetIdMessage args)
|
||||
@@ -83,9 +98,9 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
||||
var targetIdComponent = EntityManager.GetComponent<IdCardComponent>(targetId);
|
||||
var targetAccessComponent = EntityManager.GetComponent<AccessComponent>(targetId);
|
||||
|
||||
var jobProto = new ProtoId<AccessLevelPrototype>(string.Empty);
|
||||
var jobProto = targetIdComponent.JobPrototype ?? new ProtoId<AccessLevelPrototype>(string.Empty);
|
||||
if (TryComp<StationRecordKeyStorageComponent>(targetId, out var keyStorage)
|
||||
&& keyStorage.Key is {} key
|
||||
&& keyStorage.Key is { } key
|
||||
&& _record.TryGetRecord<GeneralStationRecord>(key, out var record))
|
||||
{
|
||||
jobProto = record.JobPrototype;
|
||||
@@ -136,6 +151,13 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
||||
}
|
||||
|
||||
UpdateStationRecord(uid, targetId, newFullName, newJobTitle, job);
|
||||
if ((!TryComp<StationRecordKeyStorageComponent>(targetId, out var keyStorage)
|
||||
|| keyStorage.Key is not { } key
|
||||
|| !_record.TryGetRecord<GeneralStationRecord>(key, out _))
|
||||
&& newJobProto != string.Empty)
|
||||
{
|
||||
Comp<IdCardComponent>(targetId).JobPrototype = newJobProto;
|
||||
}
|
||||
|
||||
if (!newAccessList.TrueForAll(x => component.AccessLevels.Contains(x)))
|
||||
{
|
||||
@@ -210,4 +232,46 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
||||
|
||||
_record.Synchronize(key);
|
||||
}
|
||||
|
||||
private void OnMachineDeconstructed(Entity<IdCardConsoleComponent> entity, ref MachineDeconstructedEvent args)
|
||||
{
|
||||
TryDropAndThrowIds(entity.AsNullable());
|
||||
}
|
||||
|
||||
private void OnDamageChanged(Entity<IdCardConsoleComponent> entity, ref DamageChangedEvent args)
|
||||
{
|
||||
if (TryDropAndThrowIds(entity.AsNullable()))
|
||||
_chat.TrySendInGameICMessage(entity, Loc.GetString("id-card-console-damaged"), InGameICChatType.Speak, true);
|
||||
}
|
||||
|
||||
#region PublicAPI
|
||||
|
||||
/// <summary>
|
||||
/// Tries to drop any IDs stored in the console, and then tries to throw them away.
|
||||
/// Returns true if anything was ejected and false otherwise.
|
||||
/// </summary>
|
||||
public bool TryDropAndThrowIds(Entity<IdCardConsoleComponent?, ItemSlotsComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp1, ref ent.Comp2))
|
||||
return false;
|
||||
|
||||
var didEject = false;
|
||||
|
||||
foreach (var slot in ent.Comp2.Slots.Values)
|
||||
{
|
||||
if (slot.Item == null || slot.ContainerSlot == null)
|
||||
continue;
|
||||
|
||||
var item = slot.Item.Value;
|
||||
if (_container.Remove(item, slot.ContainerSlot))
|
||||
{
|
||||
_throwing.TryThrow(item, _random.NextVector2(), baseThrowSpeed: 5f);
|
||||
didEject = true;
|
||||
}
|
||||
}
|
||||
|
||||
return didEject;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Kitchen.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Access;
|
||||
@@ -19,6 +20,7 @@ public sealed class IdCardSystem : SharedIdCardSystem
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly MicrowaveSystem _microwave = default!;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -93,4 +95,22 @@ public sealed class IdCardSystem : SharedIdCardSystem
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExpireId(Entity<ExpireIdCardComponent> ent)
|
||||
{
|
||||
if (ent.Comp.Expired)
|
||||
return;
|
||||
|
||||
base.ExpireId(ent);
|
||||
|
||||
if (ent.Comp.ExpireMessage != null)
|
||||
{
|
||||
_chat.TrySendInGameICMessage(
|
||||
ent,
|
||||
Loc.GetString(ent.Comp.ExpireMessage),
|
||||
InGameICChatType.Speak,
|
||||
ChatTransmitRange.Normal,
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Server.Actions;
|
||||
|
||||
@@ -20,8 +19,10 @@ namespace Content.Server.Actions;
|
||||
[RegisterComponent]
|
||||
public sealed partial class ActionOnInteractComponent : Component
|
||||
{
|
||||
[DataField(required:true)]
|
||||
[DataField(required: true)]
|
||||
public List<EntProtoId>? Actions;
|
||||
|
||||
[DataField] public List<EntityUid>? ActionEntities;
|
||||
|
||||
[DataField] public bool RequiresCharge;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Charges.Components;
|
||||
using Content.Shared.Charges.Systems;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -15,6 +17,7 @@ public sealed class ActionOnInteractSystem : EntitySystem
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
|
||||
[Dependency] private readonly SharedChargesSystem _charges = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -54,6 +57,9 @@ public sealed class ActionOnInteractSystem : EntitySystem
|
||||
if (options.Count == 0)
|
||||
return;
|
||||
|
||||
if (!TryUseCharge((uid, component)))
|
||||
return;
|
||||
|
||||
var (actId, act) = _random.Pick(options);
|
||||
_actions.PerformAction(args.User, null, actId, act, act.Event, _timing.CurTime, false);
|
||||
args.Handled = true;
|
||||
@@ -85,6 +91,9 @@ public sealed class ActionOnInteractSystem : EntitySystem
|
||||
|
||||
if (entOptions.Count > 0)
|
||||
{
|
||||
if (!TryUseCharge((uid, component)))
|
||||
return;
|
||||
|
||||
var (entActId, entAct) = _random.Pick(entOptions);
|
||||
if (entAct.Event != null)
|
||||
{
|
||||
@@ -108,6 +117,9 @@ public sealed class ActionOnInteractSystem : EntitySystem
|
||||
|
||||
if (entWorldOptions.Count > 0)
|
||||
{
|
||||
if (!TryUseCharge((uid, component)))
|
||||
return;
|
||||
|
||||
var (entActId, entAct) = _random.Pick(entWorldOptions);
|
||||
if (entAct.Event != null)
|
||||
{
|
||||
@@ -132,6 +144,9 @@ public sealed class ActionOnInteractSystem : EntitySystem
|
||||
if (options.Count == 0)
|
||||
return;
|
||||
|
||||
if (!TryUseCharge((uid, component)))
|
||||
return;
|
||||
|
||||
var (actId, act) = _random.Pick(options);
|
||||
if (act.Event != null)
|
||||
{
|
||||
@@ -163,4 +178,17 @@ public sealed class ActionOnInteractSystem : EntitySystem
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
private bool TryUseCharge(Entity<ActionOnInteractComponent> ent)
|
||||
{
|
||||
if (!ent.Comp.RequiresCharge)
|
||||
return true;
|
||||
|
||||
Entity<LimitedChargesComponent?> charges = ent.Owner;
|
||||
if (_charges.IsEmpty(charges))
|
||||
return false;
|
||||
|
||||
_charges.TryUseCharge(charges);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Mind;
|
||||
@@ -8,6 +8,7 @@ using Content.Shared.Mind;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
@@ -15,7 +16,7 @@ namespace Content.Server.Administration.Commands;
|
||||
public sealed class AGhostCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entities = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
|
||||
|
||||
public override string Command => "aghost";
|
||||
public override string Help => "aghost";
|
||||
@@ -104,8 +105,8 @@ public sealed class AGhostCommand : LocalizedCommands
|
||||
// TODO: Remove duplication between all this and "GamePreset.OnGhostAttempt()"...
|
||||
if (!string.IsNullOrWhiteSpace(mind.CharacterName))
|
||||
metaDataSystem.SetEntityName(ghost, mind.CharacterName);
|
||||
else if (!string.IsNullOrWhiteSpace(mind.Session?.Name))
|
||||
metaDataSystem.SetEntityName(ghost, mind.Session.Name);
|
||||
else if (!string.IsNullOrWhiteSpace(player.Name))
|
||||
metaDataSystem.SetEntityName(ghost, player.Name);
|
||||
|
||||
mindSystem.Visit(mindId, ghost, mind);
|
||||
}
|
||||
@@ -116,6 +117,6 @@ public sealed class AGhostCommand : LocalizedCommands
|
||||
}
|
||||
|
||||
var comp = _entities.GetComponent<GhostComponent>(ghost);
|
||||
ghostSystem.SetCanReturnToBody(comp, canReturn);
|
||||
ghostSystem.SetCanReturnToBody((ghost, comp), canReturn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace Content.Server.Administration.Commands;
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
public sealed class ForceGhostCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Server.Warps;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Follower;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Warps;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
@@ -1,37 +1,31 @@
|
||||
using Content.Server.Administration.Systems;
|
||||
using Content.Shared.Climbing.Components;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server.Administration.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component to track the timer for the SuperBonk smite.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(SuperBonkSystem))]
|
||||
public sealed partial class SuperBonkComponent: Component
|
||||
[RegisterComponent, AutoGenerateComponentPause, Access(typeof(SuperBonkSystem))]
|
||||
public sealed partial class SuperBonkComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity being Super Bonked.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid Target;
|
||||
|
||||
/// <summary>
|
||||
/// All of the tables the target will be bonked on.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Dictionary<EntityUid, BonkableComponent>.Enumerator Tables;
|
||||
public List<EntityUid>.Enumerator Tables;
|
||||
|
||||
/// <summary>
|
||||
/// Value used to reset the timer once it expires.
|
||||
/// How often should we bonk.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float InitialTime = 0.10f;
|
||||
public TimeSpan BonkCooldown = TimeSpan.FromMilliseconds(100);
|
||||
|
||||
/// <summary>
|
||||
/// Timer till the next bonk.
|
||||
/// Next time when we will bonk.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float TimeRemaining = 0.10f;
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan NextBonk = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to remove the clumsy component from the target after SuperBonk is done.
|
||||
|
||||
@@ -330,7 +330,12 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa
|
||||
var cachedInfo = adminSys.GetCachedPlayerInfo(new NetUserId(id));
|
||||
if (cachedInfo != null && cachedInfo.Antag)
|
||||
{
|
||||
logMessage += " [ANTAG: " + cachedInfo.CharacterName + "]";
|
||||
var subtype = Loc.GetString(cachedInfo.Subtype ?? cachedInfo.RoleProto.Name);
|
||||
logMessage = Loc.GetString(
|
||||
"admin-alert-antag-label",
|
||||
("message", logMessage),
|
||||
("name", cachedInfo.CharacterName),
|
||||
("subtype", subtype));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json;
|
||||
using Content.Shared.Station.Components;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
@@ -72,6 +72,6 @@ public readonly struct SerializableEntityCoordinates
|
||||
EntityUid = coordinates.EntityId;
|
||||
X = coordinates.X;
|
||||
Y = coordinates.Y;
|
||||
MapUid = coordinates.GetMapUid(entityManager);
|
||||
MapUid = entityManager.System<SharedTransformSystem>().GetMap(coordinates);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,8 @@ using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Database;
|
||||
using Content.Server.Players;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Info;
|
||||
using Content.Shared.Players;
|
||||
using Robust.Server.Console;
|
||||
using Robust.Server.Player;
|
||||
@@ -108,7 +106,7 @@ namespace Content.Server.Administration.Managers
|
||||
// The DB function handles this scenario fine, but it's worth noting.
|
||||
await _dbManager.UpdateAdminDeadminnedAsync(player.UserId, newState);
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception)
|
||||
{
|
||||
_sawmill.Error("Failed to save deadmin state to database for {Admin}", player.UserId);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using Content.Server.EUI;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Eui;
|
||||
using Content.Shared.Follower;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
@@ -33,11 +34,13 @@ public sealed class PlayerPanelEui : BaseEui
|
||||
private bool _frozen;
|
||||
private bool _canFreeze;
|
||||
private bool _canAhelp;
|
||||
private FollowerSystem _follower;
|
||||
|
||||
public PlayerPanelEui(LocatedPlayerData player)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_targetPlayer = player;
|
||||
_follower = _entity.System<FollowerSystem>();
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
@@ -141,6 +144,16 @@ public sealed class PlayerPanelEui : BaseEui
|
||||
_entity.DeleteEntity(session.AttachedEntity);
|
||||
}
|
||||
break;
|
||||
case PlayerPanelFollowMessage:
|
||||
if (!_admins.HasAdminFlag(Player, AdminFlags.Admin) ||
|
||||
!_player.TryGetSessionById(_targetPlayer.UserId, out session) ||
|
||||
session.AttachedEntity == null ||
|
||||
Player.AttachedEntity is null ||
|
||||
session.AttachedEntity == Player.AttachedEntity)
|
||||
return;
|
||||
|
||||
_follower.StartFollowingEntity(Player.AttachedEntity.Value, session.AttachedEntity.Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -153,9 +153,7 @@ public sealed class AdminSystem : EntitySystem
|
||||
|
||||
private void OnRoleEvent(RoleEvent ev)
|
||||
{
|
||||
var session = _minds.GetSession(ev.Mind);
|
||||
|
||||
if (!ev.RoleTypeUpdate || session == null)
|
||||
if (!ev.RoleTypeUpdate || !_playerManager.TryGetSessionById(ev.Mind.UserId, out var session))
|
||||
return;
|
||||
|
||||
UpdatePlayerList(session);
|
||||
@@ -235,12 +233,16 @@ public sealed class AdminSystem : EntitySystem
|
||||
// Starting role, antagonist status and role type
|
||||
RoleTypePrototype roleType = new();
|
||||
var startingRole = string.Empty;
|
||||
LocId? subtype = null;
|
||||
if (_minds.TryGetMind(session, out var mindId, out var mindComp) && mindComp is not null)
|
||||
{
|
||||
sortWeight = _role.GetRoleCompByTime(mindComp)?.Comp.SortWeight ?? 0;
|
||||
|
||||
if (_proto.TryIndex(mindComp.RoleType, out var role))
|
||||
{
|
||||
roleType = role;
|
||||
subtype = mindComp.Subtype;
|
||||
}
|
||||
else
|
||||
Log.Error($"{ToPrettyString(mindId)} has invalid Role Type '{mindComp.RoleType}'. Displaying '{Loc.GetString(roleType.Name)}' instead");
|
||||
|
||||
@@ -269,6 +271,7 @@ public sealed class AdminSystem : EntitySystem
|
||||
startingRole,
|
||||
antag,
|
||||
roleType,
|
||||
subtype,
|
||||
sortWeight,
|
||||
GetNetEntity(session?.AttachedEntity),
|
||||
data.UserId,
|
||||
|
||||
@@ -421,7 +421,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
var xform = Transform(args.Target);
|
||||
var fixtures = Comp<FixturesComponent>(args.Target);
|
||||
_transformSystem.Unanchor(args.Target); // Just in case.
|
||||
_transformSystem.Unanchor(args.Target, xform); // Just in case.
|
||||
_physics.SetBodyType(args.Target, BodyType.Dynamic, manager: fixtures, body: physics);
|
||||
_physics.SetBodyStatus(args.Target, physics, BodyStatus.InAir);
|
||||
_physics.WakeBody(args.Target, manager: fixtures, body: physics);
|
||||
@@ -877,9 +877,9 @@ public sealed partial class AdminVerbSystem
|
||||
var hadSlipComponent = EnsureComp(args.Target, out SlipperyComponent slipComponent);
|
||||
if (!hadSlipComponent)
|
||||
{
|
||||
slipComponent.SuperSlippery = true;
|
||||
slipComponent.ParalyzeTime = 5;
|
||||
slipComponent.LaunchForwardsMultiplier = 20;
|
||||
slipComponent.SlipData.SuperSlippery = true;
|
||||
slipComponent.SlipData.ParalyzeTime = TimeSpan.FromSeconds(5);
|
||||
slipComponent.SlipData.LaunchForwardsMultiplier = 20;
|
||||
}
|
||||
|
||||
_slipperySystem.TrySlip(args.Target, slipComponent, args.Target, requiresContact: false);
|
||||
|
||||
@@ -65,343 +65,340 @@ public sealed partial class AdminVerbSystem
|
||||
if (!_adminManager.HasAdminFlag(player, AdminFlags.Admin))
|
||||
return;
|
||||
|
||||
if (_adminManager.HasAdminFlag(player, AdminFlags.Admin))
|
||||
if (TryComp<DoorBoltComponent>(args.Target, out var bolts))
|
||||
{
|
||||
if (TryComp<DoorBoltComponent>(args.Target, out var bolts))
|
||||
Verb bolt = new()
|
||||
{
|
||||
Verb bolt = new()
|
||||
{
|
||||
Text = bolts.BoltsDown ? "Unbolt" : "Bolt",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = bolts.BoltsDown
|
||||
? new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/unbolt.png"))
|
||||
: new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/bolt.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_door.SetBoltsDown((args.Target, bolts), !bolts.BoltsDown);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
Message = Loc.GetString(bolts.BoltsDown
|
||||
? "admin-trick-unbolt-description"
|
||||
: "admin-trick-bolt-description"),
|
||||
Priority = (int) (bolts.BoltsDown ? TricksVerbPriorities.Unbolt : TricksVerbPriorities.Bolt),
|
||||
};
|
||||
args.Verbs.Add(bolt);
|
||||
}
|
||||
|
||||
if (TryComp<AirlockComponent>(args.Target, out var airlockComp))
|
||||
{
|
||||
Verb emergencyAccess = new()
|
||||
{
|
||||
Text = airlockComp.EmergencyAccess ? "Emergency Access Off" : "Emergency Access On",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/emergency_access.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_airlockSystem.SetEmergencyAccess((args.Target, airlockComp), !airlockComp.EmergencyAccess);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
Message = Loc.GetString(airlockComp.EmergencyAccess
|
||||
? "admin-trick-emergency-access-off-description"
|
||||
: "admin-trick-emergency-access-on-description"),
|
||||
Priority = (int) (airlockComp.EmergencyAccess ? TricksVerbPriorities.EmergencyAccessOff : TricksVerbPriorities.EmergencyAccessOn),
|
||||
};
|
||||
args.Verbs.Add(emergencyAccess);
|
||||
}
|
||||
|
||||
if (HasComp<DamageableComponent>(args.Target))
|
||||
{
|
||||
Verb rejuvenate = new()
|
||||
{
|
||||
Text = "Rejuvenate",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/rejuvenate.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_rejuvenate.PerformRejuvenate(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-rejuvenate-description"),
|
||||
Priority = (int) TricksVerbPriorities.Rejuvenate,
|
||||
};
|
||||
args.Verbs.Add(rejuvenate);
|
||||
}
|
||||
|
||||
if (!HasComp<GodmodeComponent>(args.Target))
|
||||
{
|
||||
Verb makeIndestructible = new()
|
||||
{
|
||||
Text = "Make Indestructible",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/plus.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_sharedGodmodeSystem.EnableGodmode(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-make-indestructible-description"),
|
||||
Priority = (int) TricksVerbPriorities.MakeIndestructible,
|
||||
};
|
||||
args.Verbs.Add(makeIndestructible);
|
||||
}
|
||||
else
|
||||
{
|
||||
Verb makeVulnerable = new()
|
||||
{
|
||||
Text = "Make Vulnerable",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/plus.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_sharedGodmodeSystem.DisableGodmode(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-make-vulnerable-description"),
|
||||
Priority = (int) TricksVerbPriorities.MakeVulnerable,
|
||||
};
|
||||
args.Verbs.Add(makeVulnerable);
|
||||
}
|
||||
|
||||
if (TryComp<BatteryComponent>(args.Target, out var battery))
|
||||
{
|
||||
Verb refillBattery = new()
|
||||
{
|
||||
Text = "Refill Battery",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/fill_battery.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_batterySystem.SetCharge(args.Target, battery.MaxCharge, battery);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
Message = Loc.GetString("admin-trick-refill-battery-description"),
|
||||
Priority = (int) TricksVerbPriorities.RefillBattery,
|
||||
};
|
||||
args.Verbs.Add(refillBattery);
|
||||
|
||||
Verb drainBattery = new()
|
||||
{
|
||||
Text = "Drain Battery",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/drain_battery.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_batterySystem.SetCharge(args.Target, 0, battery);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
Message = Loc.GetString("admin-trick-drain-battery-description"),
|
||||
Priority = (int) TricksVerbPriorities.DrainBattery,
|
||||
};
|
||||
args.Verbs.Add(drainBattery);
|
||||
|
||||
Verb infiniteBattery = new()
|
||||
{
|
||||
Text = "Infinite Battery",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/infinite_battery.png")),
|
||||
Act = () =>
|
||||
{
|
||||
var recharger = EnsureComp<BatterySelfRechargerComponent>(args.Target);
|
||||
recharger.AutoRecharge = true;
|
||||
recharger.AutoRechargeRate = battery.MaxCharge; // Instant refill.
|
||||
recharger.AutoRechargePause = false; // No delay.
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
Message = Loc.GetString("admin-trick-infinite-battery-object-description"),
|
||||
Priority = (int) TricksVerbPriorities.InfiniteBattery,
|
||||
};
|
||||
args.Verbs.Add(infiniteBattery);
|
||||
}
|
||||
|
||||
if (TryComp<AnchorableComponent>(args.Target, out var anchor))
|
||||
{
|
||||
Verb blockUnanchor = new()
|
||||
{
|
||||
Text = "Block Unanchoring",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/anchor.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
RemComp(args.Target, anchor);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
Message = Loc.GetString("admin-trick-block-unanchoring-description"),
|
||||
Priority = (int) TricksVerbPriorities.BlockUnanchoring,
|
||||
};
|
||||
args.Verbs.Add(blockUnanchor);
|
||||
}
|
||||
|
||||
if (TryComp<GasTankComponent>(args.Target, out var tank))
|
||||
{
|
||||
Verb refillInternalsO2 = new()
|
||||
{
|
||||
Text = "Refill Internals Oxygen",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/oxygen.rsi"), "icon"),
|
||||
Act = () =>
|
||||
{
|
||||
RefillGasTank(args.Target, Gas.Oxygen, tank);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-internals-refill-oxygen-description"),
|
||||
Priority = (int) TricksVerbPriorities.RefillOxygen,
|
||||
};
|
||||
args.Verbs.Add(refillInternalsO2);
|
||||
|
||||
Verb refillInternalsN2 = new()
|
||||
{
|
||||
Text = "Refill Internals Nitrogen",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/red.rsi"), "icon"),
|
||||
Act = () =>
|
||||
{
|
||||
RefillGasTank(args.Target, Gas.Nitrogen, tank);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-internals-refill-nitrogen-description"),
|
||||
Priority = (int) TricksVerbPriorities.RefillNitrogen,
|
||||
};
|
||||
args.Verbs.Add(refillInternalsN2);
|
||||
|
||||
Verb refillInternalsPlasma = new()
|
||||
{
|
||||
Text = "Refill Internals Plasma",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/plasma.rsi"), "icon"),
|
||||
Act = () =>
|
||||
{
|
||||
RefillGasTank(args.Target, Gas.Plasma, tank);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-internals-refill-plasma-description"),
|
||||
Priority = (int) TricksVerbPriorities.RefillPlasma,
|
||||
};
|
||||
args.Verbs.Add(refillInternalsPlasma);
|
||||
}
|
||||
|
||||
if (HasComp<InventoryComponent>(args.Target))
|
||||
{
|
||||
Verb refillInternalsO2 = new()
|
||||
{
|
||||
Text = "Refill Internals Oxygen",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/oxygen.rsi"), "icon"),
|
||||
Act = () => RefillEquippedTanks(args.User, Gas.Oxygen),
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-internals-refill-oxygen-description"),
|
||||
Priority = (int) TricksVerbPriorities.RefillOxygen,
|
||||
};
|
||||
args.Verbs.Add(refillInternalsO2);
|
||||
|
||||
Verb refillInternalsN2 = new()
|
||||
{
|
||||
Text = "Refill Internals Nitrogen",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/red.rsi"), "icon"),
|
||||
Act = () =>RefillEquippedTanks(args.User, Gas.Nitrogen),
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-internals-refill-nitrogen-description"),
|
||||
Priority = (int) TricksVerbPriorities.RefillNitrogen,
|
||||
};
|
||||
args.Verbs.Add(refillInternalsN2);
|
||||
|
||||
Verb refillInternalsPlasma = new()
|
||||
{
|
||||
Text = "Refill Internals Plasma",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/plasma.rsi"), "icon"),
|
||||
Act = () => RefillEquippedTanks(args.User, Gas.Plasma),
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-internals-refill-plasma-description"),
|
||||
Priority = (int) TricksVerbPriorities.RefillPlasma,
|
||||
};
|
||||
args.Verbs.Add(refillInternalsPlasma);
|
||||
}
|
||||
|
||||
Verb sendToTestArena = new()
|
||||
{
|
||||
Text = "Send to test arena",
|
||||
Text = bolts.BoltsDown ? "Unbolt" : "Bolt",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/eject.svg.192dpi.png")),
|
||||
|
||||
Icon = bolts.BoltsDown
|
||||
? new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/unbolt.png"))
|
||||
: new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/bolt.png")),
|
||||
Act = () =>
|
||||
{
|
||||
var (mapUid, gridUid) = _adminTestArenaSystem.AssertArenaLoaded(player);
|
||||
_transformSystem.SetCoordinates(args.Target, new EntityCoordinates(gridUid ?? mapUid, Vector2.One));
|
||||
_door.SetBoltsDown((args.Target, bolts), !bolts.BoltsDown);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
Message = Loc.GetString("admin-trick-send-to-test-arena-description"),
|
||||
Priority = (int) TricksVerbPriorities.SendToTestArena,
|
||||
Message = Loc.GetString(bolts.BoltsDown
|
||||
? "admin-trick-unbolt-description"
|
||||
: "admin-trick-bolt-description"),
|
||||
Priority = (int)(bolts.BoltsDown ? TricksVerbPriorities.Unbolt : TricksVerbPriorities.Bolt),
|
||||
};
|
||||
args.Verbs.Add(sendToTestArena);
|
||||
args.Verbs.Add(bolt);
|
||||
}
|
||||
|
||||
var activeId = FindActiveId(args.Target);
|
||||
|
||||
if (activeId is not null)
|
||||
if (TryComp<AirlockComponent>(args.Target, out var airlockComp))
|
||||
{
|
||||
Verb emergencyAccess = new()
|
||||
{
|
||||
Verb grantAllAccess = new()
|
||||
Text = airlockComp.EmergencyAccess ? "Emergency Access Off" : "Emergency Access On",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/emergency_access.png")),
|
||||
Act = () =>
|
||||
{
|
||||
Text = "Grant All Access",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Misc/id_cards.rsi"), "centcom"),
|
||||
Act = () =>
|
||||
{
|
||||
GiveAllAccess(activeId.Value);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-grant-all-access-description"),
|
||||
Priority = (int) TricksVerbPriorities.GrantAllAccess,
|
||||
};
|
||||
args.Verbs.Add(grantAllAccess);
|
||||
_airlockSystem.SetEmergencyAccess((args.Target, airlockComp), !airlockComp.EmergencyAccess);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
Message = Loc.GetString(airlockComp.EmergencyAccess
|
||||
? "admin-trick-emergency-access-off-description"
|
||||
: "admin-trick-emergency-access-on-description"),
|
||||
Priority = (int)(airlockComp.EmergencyAccess ? TricksVerbPriorities.EmergencyAccessOff : TricksVerbPriorities.EmergencyAccessOn),
|
||||
};
|
||||
args.Verbs.Add(emergencyAccess);
|
||||
}
|
||||
|
||||
Verb revokeAllAccess = new()
|
||||
{
|
||||
Text = "Revoke All Access",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Misc/id_cards.rsi"), "default"),
|
||||
Act = () =>
|
||||
{
|
||||
RevokeAllAccess(activeId.Value);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-revoke-all-access-description"),
|
||||
Priority = (int) TricksVerbPriorities.RevokeAllAccess,
|
||||
};
|
||||
args.Verbs.Add(revokeAllAccess);
|
||||
}
|
||||
|
||||
if (HasComp<AccessComponent>(args.Target))
|
||||
if (HasComp<DamageableComponent>(args.Target))
|
||||
{
|
||||
Verb rejuvenate = new()
|
||||
{
|
||||
Verb grantAllAccess = new()
|
||||
Text = "Rejuvenate",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/rejuvenate.png")),
|
||||
Act = () =>
|
||||
{
|
||||
Text = "Grant All Access",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Misc/id_cards.rsi"), "centcom"),
|
||||
Act = () =>
|
||||
{
|
||||
GiveAllAccess(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-grant-all-access-description"),
|
||||
Priority = (int) TricksVerbPriorities.GrantAllAccess,
|
||||
};
|
||||
args.Verbs.Add(grantAllAccess);
|
||||
_rejuvenate.PerformRejuvenate(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-rejuvenate-description"),
|
||||
Priority = (int)TricksVerbPriorities.Rejuvenate,
|
||||
};
|
||||
args.Verbs.Add(rejuvenate);
|
||||
}
|
||||
|
||||
Verb revokeAllAccess = new()
|
||||
if (!HasComp<GodmodeComponent>(args.Target))
|
||||
{
|
||||
Verb makeIndestructible = new()
|
||||
{
|
||||
Text = "Make Indestructible",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/plus.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
Text = "Revoke All Access",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Misc/id_cards.rsi"), "default"),
|
||||
Act = () =>
|
||||
{
|
||||
RevokeAllAccess(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-revoke-all-access-description"),
|
||||
Priority = (int) TricksVerbPriorities.RevokeAllAccess,
|
||||
};
|
||||
args.Verbs.Add(revokeAllAccess);
|
||||
}
|
||||
_sharedGodmodeSystem.EnableGodmode(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-make-indestructible-description"),
|
||||
Priority = (int)TricksVerbPriorities.MakeIndestructible,
|
||||
};
|
||||
args.Verbs.Add(makeIndestructible);
|
||||
}
|
||||
else
|
||||
{
|
||||
Verb makeVulnerable = new()
|
||||
{
|
||||
Text = "Make Vulnerable",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/plus.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_sharedGodmodeSystem.DisableGodmode(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-make-vulnerable-description"),
|
||||
Priority = (int)TricksVerbPriorities.MakeVulnerable,
|
||||
};
|
||||
args.Verbs.Add(makeVulnerable);
|
||||
}
|
||||
|
||||
if (TryComp<BatteryComponent>(args.Target, out var battery))
|
||||
{
|
||||
Verb refillBattery = new()
|
||||
{
|
||||
Text = "Refill Battery",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/fill_battery.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_batterySystem.SetCharge(args.Target, battery.MaxCharge, battery);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
Message = Loc.GetString("admin-trick-refill-battery-description"),
|
||||
Priority = (int)TricksVerbPriorities.RefillBattery,
|
||||
};
|
||||
args.Verbs.Add(refillBattery);
|
||||
|
||||
Verb drainBattery = new()
|
||||
{
|
||||
Text = "Drain Battery",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/drain_battery.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_batterySystem.SetCharge(args.Target, 0, battery);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
Message = Loc.GetString("admin-trick-drain-battery-description"),
|
||||
Priority = (int)TricksVerbPriorities.DrainBattery,
|
||||
};
|
||||
args.Verbs.Add(drainBattery);
|
||||
|
||||
Verb infiniteBattery = new()
|
||||
{
|
||||
Text = "Infinite Battery",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/infinite_battery.png")),
|
||||
Act = () =>
|
||||
{
|
||||
var recharger = EnsureComp<BatterySelfRechargerComponent>(args.Target);
|
||||
recharger.AutoRecharge = true;
|
||||
recharger.AutoRechargeRate = battery.MaxCharge; // Instant refill.
|
||||
recharger.AutoRechargePause = false; // No delay.
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
Message = Loc.GetString("admin-trick-infinite-battery-object-description"),
|
||||
Priority = (int)TricksVerbPriorities.InfiniteBattery,
|
||||
};
|
||||
args.Verbs.Add(infiniteBattery);
|
||||
}
|
||||
|
||||
if (TryComp<AnchorableComponent>(args.Target, out var anchor))
|
||||
{
|
||||
Verb blockUnanchor = new()
|
||||
{
|
||||
Text = "Block Unanchoring",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/anchor.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
RemComp(args.Target, anchor);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
Message = Loc.GetString("admin-trick-block-unanchoring-description"),
|
||||
Priority = (int)TricksVerbPriorities.BlockUnanchoring,
|
||||
};
|
||||
args.Verbs.Add(blockUnanchor);
|
||||
}
|
||||
|
||||
if (TryComp<GasTankComponent>(args.Target, out var tank))
|
||||
{
|
||||
Verb refillInternalsO2 = new()
|
||||
{
|
||||
Text = "Refill Internals Oxygen",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/oxygen.rsi"), "icon"),
|
||||
Act = () =>
|
||||
{
|
||||
RefillGasTank(args.Target, Gas.Oxygen, tank);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-internals-refill-oxygen-description"),
|
||||
Priority = (int)TricksVerbPriorities.RefillOxygen,
|
||||
};
|
||||
args.Verbs.Add(refillInternalsO2);
|
||||
|
||||
Verb refillInternalsN2 = new()
|
||||
{
|
||||
Text = "Refill Internals Nitrogen",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/red.rsi"), "icon"),
|
||||
Act = () =>
|
||||
{
|
||||
RefillGasTank(args.Target, Gas.Nitrogen, tank);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-internals-refill-nitrogen-description"),
|
||||
Priority = (int)TricksVerbPriorities.RefillNitrogen,
|
||||
};
|
||||
args.Verbs.Add(refillInternalsN2);
|
||||
|
||||
Verb refillInternalsPlasma = new()
|
||||
{
|
||||
Text = "Refill Internals Plasma",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/plasma.rsi"), "icon"),
|
||||
Act = () =>
|
||||
{
|
||||
RefillGasTank(args.Target, Gas.Plasma, tank);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-internals-refill-plasma-description"),
|
||||
Priority = (int)TricksVerbPriorities.RefillPlasma,
|
||||
};
|
||||
args.Verbs.Add(refillInternalsPlasma);
|
||||
}
|
||||
|
||||
if (HasComp<InventoryComponent>(args.Target))
|
||||
{
|
||||
Verb refillInternalsO2 = new()
|
||||
{
|
||||
Text = "Refill Internals Oxygen",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/oxygen.rsi"), "icon"),
|
||||
Act = () => RefillEquippedTanks(args.User, Gas.Oxygen),
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-internals-refill-oxygen-description"),
|
||||
Priority = (int)TricksVerbPriorities.RefillOxygen,
|
||||
};
|
||||
args.Verbs.Add(refillInternalsO2);
|
||||
|
||||
Verb refillInternalsN2 = new()
|
||||
{
|
||||
Text = "Refill Internals Nitrogen",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/red.rsi"), "icon"),
|
||||
Act = () => RefillEquippedTanks(args.User, Gas.Nitrogen),
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-internals-refill-nitrogen-description"),
|
||||
Priority = (int)TricksVerbPriorities.RefillNitrogen,
|
||||
};
|
||||
args.Verbs.Add(refillInternalsN2);
|
||||
|
||||
Verb refillInternalsPlasma = new()
|
||||
{
|
||||
Text = "Refill Internals Plasma",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/plasma.rsi"), "icon"),
|
||||
Act = () => RefillEquippedTanks(args.User, Gas.Plasma),
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-internals-refill-plasma-description"),
|
||||
Priority = (int)TricksVerbPriorities.RefillPlasma,
|
||||
};
|
||||
args.Verbs.Add(refillInternalsPlasma);
|
||||
}
|
||||
|
||||
Verb sendToTestArena = new()
|
||||
{
|
||||
Text = "Send to test arena",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/eject.svg.192dpi.png")),
|
||||
|
||||
Act = () =>
|
||||
{
|
||||
var (mapUid, gridUid) = _adminTestArenaSystem.AssertArenaLoaded(player);
|
||||
_transformSystem.SetCoordinates(args.Target, new EntityCoordinates(gridUid ?? mapUid, Vector2.One));
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
Message = Loc.GetString("admin-trick-send-to-test-arena-description"),
|
||||
Priority = (int)TricksVerbPriorities.SendToTestArena,
|
||||
};
|
||||
args.Verbs.Add(sendToTestArena);
|
||||
|
||||
var activeId = FindActiveId(args.Target);
|
||||
|
||||
if (activeId is not null)
|
||||
{
|
||||
Verb grantAllAccess = new()
|
||||
{
|
||||
Text = "Grant All Access",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Misc/id_cards.rsi"), "centcom"),
|
||||
Act = () =>
|
||||
{
|
||||
GiveAllAccess(activeId.Value);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-grant-all-access-description"),
|
||||
Priority = (int)TricksVerbPriorities.GrantAllAccess,
|
||||
};
|
||||
args.Verbs.Add(grantAllAccess);
|
||||
|
||||
Verb revokeAllAccess = new()
|
||||
{
|
||||
Text = "Revoke All Access",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Misc/id_cards.rsi"), "default"),
|
||||
Act = () =>
|
||||
{
|
||||
RevokeAllAccess(activeId.Value);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-revoke-all-access-description"),
|
||||
Priority = (int)TricksVerbPriorities.RevokeAllAccess,
|
||||
};
|
||||
args.Verbs.Add(revokeAllAccess);
|
||||
}
|
||||
|
||||
if (HasComp<AccessComponent>(args.Target))
|
||||
{
|
||||
Verb grantAllAccess = new()
|
||||
{
|
||||
Text = "Grant All Access",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Misc/id_cards.rsi"), "centcom"),
|
||||
Act = () =>
|
||||
{
|
||||
GiveAllAccess(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-grant-all-access-description"),
|
||||
Priority = (int)TricksVerbPriorities.GrantAllAccess,
|
||||
};
|
||||
args.Verbs.Add(grantAllAccess);
|
||||
|
||||
Verb revokeAllAccess = new()
|
||||
{
|
||||
Text = "Revoke All Access",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Misc/id_cards.rsi"), "default"),
|
||||
Act = () =>
|
||||
{
|
||||
RevokeAllAccess(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-revoke-all-access-description"),
|
||||
Priority = (int)TricksVerbPriorities.RevokeAllAccess,
|
||||
};
|
||||
args.Verbs.Add(revokeAllAccess);
|
||||
}
|
||||
|
||||
if (TryComp<StackComponent>(args.Target, out var stack))
|
||||
|
||||
@@ -2,16 +2,12 @@ using Content.Server.Administration.Logs;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Administration.UI;
|
||||
using Content.Server.Disposal.Tube;
|
||||
using Content.Server.Disposal.Tube.Components;
|
||||
using Content.Server.EUI;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Ghost.Roles;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Mind.Commands;
|
||||
using Content.Server.Prayer;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.Xenoarchaeology.XenoArtifacts;
|
||||
using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
@@ -26,7 +22,6 @@ using Content.Shared.Verbs;
|
||||
using Robust.Server.Console;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -58,9 +53,7 @@ namespace Content.Server.Administration.Systems
|
||||
[Dependency] private readonly AdminSystem _adminSystem = default!;
|
||||
[Dependency] private readonly DisposalTubeSystem _disposalTubes = default!;
|
||||
[Dependency] private readonly EuiManager _euiManager = default!;
|
||||
[Dependency] private readonly GameTicker _ticker = default!;
|
||||
[Dependency] private readonly GhostRoleSystem _ghostRoleSystem = default!;
|
||||
[Dependency] private readonly ArtifactSystem _artifactSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly PrayerSystem _prayerSystem = default!;
|
||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||
@@ -151,7 +144,7 @@ namespace Content.Server.Administration.Systems
|
||||
|
||||
var stationUid = _stations.GetOwningStation(args.Target);
|
||||
|
||||
var profile = _ticker.GetPlayerProfile(targetActor.PlayerSession);
|
||||
var profile = _gameTicker.GetPlayerProfile(targetActor.PlayerSession);
|
||||
var mobUid = _spawning.SpawnPlayerMob(coords.Value, null, profile, stationUid);
|
||||
|
||||
if (_mindSystem.TryGetMind(args.Target, out var mindId, out var mindComp))
|
||||
@@ -177,7 +170,7 @@ namespace Content.Server.Administration.Systems
|
||||
|
||||
var stationUid = _stations.GetOwningStation(args.Target);
|
||||
|
||||
var profile = _ticker.GetPlayerProfile(targetActor.PlayerSession);
|
||||
var profile = _gameTicker.GetPlayerProfile(targetActor.PlayerSession);
|
||||
_spawning.SpawnPlayerMob(coords.Value, null, profile, stationUid);
|
||||
},
|
||||
ConfirmationPopup = true,
|
||||
@@ -194,7 +187,7 @@ namespace Content.Server.Administration.Systems
|
||||
});
|
||||
}
|
||||
|
||||
if (_mindSystem.TryGetMind(args.Target, out _, out var mind) && mind.UserId != null)
|
||||
if (_mindSystem.TryGetMind(args.Target, out var mindId, out var mindComp) && mindComp.UserId != null)
|
||||
{
|
||||
// Erase
|
||||
args.Verbs.Add(new Verb
|
||||
@@ -206,7 +199,7 @@ namespace Content.Server.Administration.Systems
|
||||
new("/Textures/Interface/VerbIcons/delete_transparent.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_adminSystem.Erase(mind.UserId.Value);
|
||||
_adminSystem.Erase(mindComp.UserId.Value);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
ConfirmationPopup = true
|
||||
@@ -219,11 +212,20 @@ namespace Content.Server.Administration.Systems
|
||||
Category = VerbCategory.Admin,
|
||||
Act = () =>
|
||||
{
|
||||
_console.ExecuteCommand(player, $"respawn \"{mind.UserId}\"");
|
||||
_console.ExecuteCommand(player, $"respawn \"{mindComp.UserId}\"");
|
||||
},
|
||||
ConfirmationPopup = true,
|
||||
// No logimpact as the command does it internally.
|
||||
});
|
||||
|
||||
// Inspect mind
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Text = Loc.GetString("inspect-mind-verb-get-data-text"),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/sentient.svg.192dpi.png")),
|
||||
Category = VerbCategory.Debug,
|
||||
Act = () => _console.RemoteExecuteCommand(player, $"vv {GetNetEntity(mindId)}"),
|
||||
});
|
||||
}
|
||||
|
||||
// Freeze
|
||||
@@ -445,29 +447,6 @@ namespace Content.Server.Administration.Systems
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
// XenoArcheology
|
||||
if (_adminManager.IsAdmin(player) && TryComp<ArtifactComponent>(args.Target, out var artifact))
|
||||
{
|
||||
// make artifact always active (by adding timer trigger)
|
||||
args.Verbs.Add(new Verb()
|
||||
{
|
||||
Text = Loc.GetString("artifact-verb-make-always-active"),
|
||||
Category = VerbCategory.Debug,
|
||||
Act = () => EntityManager.AddComponent<ArtifactTimerTriggerComponent>(args.Target),
|
||||
Disabled = EntityManager.HasComponent<ArtifactTimerTriggerComponent>(args.Target),
|
||||
Impact = LogImpact.High
|
||||
});
|
||||
|
||||
// force to activate artifact ignoring timeout
|
||||
args.Verbs.Add(new Verb()
|
||||
{
|
||||
Text = Loc.GetString("artifact-verb-activate"),
|
||||
Category = VerbCategory.Debug,
|
||||
Act = () => _artifactSystem.ForceActivateArtifact(args.Target, component: artifact),
|
||||
Impact = LogImpact.High
|
||||
});
|
||||
}
|
||||
|
||||
// Make Sentient verb
|
||||
if (_groupController.CanCommand(player, "makesentient") &&
|
||||
args.User != args.Target &&
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Shared.Clumsy;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Administration.Systems;
|
||||
|
||||
@@ -12,44 +13,38 @@ public sealed class SuperBonkSystem : EntitySystem
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly ClumsySystem _clumsySystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SuperBonkComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<SuperBonkComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
SubscribeLocalEvent<SuperBonkComponent, ComponentShutdown>(OnBonkShutdown);
|
||||
SubscribeLocalEvent<SuperBonkComponent, ComponentShutdown>(OnShutdown);
|
||||
}
|
||||
|
||||
public void StartSuperBonk(EntityUid target, float delay = 0.1f, bool stopWhenDead = false)
|
||||
private void OnInit(Entity<SuperBonkComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
var (_, component) = ent;
|
||||
|
||||
//The other check in the code to stop when the target dies does not work if the target is already dead.
|
||||
if (stopWhenDead && TryComp<MobStateComponent>(target, out var mState))
|
||||
{
|
||||
if (mState.CurrentState == MobState.Dead)
|
||||
return;
|
||||
}
|
||||
component.NextBonk = _timing.CurTime + component.BonkCooldown;
|
||||
}
|
||||
|
||||
var hadClumsy = EnsureComp<ClumsyComponent>(target, out _);
|
||||
private void OnMobStateChanged(Entity<SuperBonkComponent> ent, ref MobStateChangedEvent args)
|
||||
{
|
||||
var (uid, component) = ent;
|
||||
|
||||
var tables = EntityQueryEnumerator<BonkableComponent>();
|
||||
var bonks = new Dictionary<EntityUid, BonkableComponent>();
|
||||
// This is done so we don't crash if something like a new table is spawned.
|
||||
while (tables.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
bonks.Add(uid, comp);
|
||||
}
|
||||
if (component.StopWhenDead && args.NewMobState == MobState.Dead)
|
||||
RemCompDeferred<SuperBonkComponent>(uid);
|
||||
}
|
||||
|
||||
var sComp = new SuperBonkComponent
|
||||
{
|
||||
Target = target,
|
||||
Tables = bonks.GetEnumerator(),
|
||||
RemoveClumsy = !hadClumsy,
|
||||
StopWhenDead = stopWhenDead,
|
||||
};
|
||||
private void OnShutdown(Entity<SuperBonkComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
var (uid, component) = ent;
|
||||
|
||||
AddComp(target, sComp);
|
||||
if (component.RemoveClumsy)
|
||||
RemComp<ClumsyComponent>(uid);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
@@ -59,49 +54,58 @@ public sealed class SuperBonkSystem : EntitySystem
|
||||
|
||||
while (comps.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
comp.TimeRemaining -= frameTime;
|
||||
if (!(comp.TimeRemaining <= 0))
|
||||
if (comp.NextBonk > _timing.CurTime)
|
||||
continue;
|
||||
|
||||
Bonk(comp);
|
||||
|
||||
if (!(comp.Tables.MoveNext()))
|
||||
if (!TryBonk(uid, comp.Tables.Current) || !comp.Tables.MoveNext())
|
||||
{
|
||||
RemComp<SuperBonkComponent>(comp.Target);
|
||||
RemComp<SuperBonkComponent>(uid);
|
||||
continue;
|
||||
}
|
||||
|
||||
comp.TimeRemaining = comp.InitialTime;
|
||||
comp.NextBonk += comp.BonkCooldown;
|
||||
}
|
||||
}
|
||||
|
||||
private void Bonk(SuperBonkComponent comp)
|
||||
public void StartSuperBonk(EntityUid target, bool stopWhenDead = false)
|
||||
{
|
||||
var uid = comp.Tables.Current.Key;
|
||||
//The other check in the code to stop when the target dies does not work if the target is already dead.
|
||||
if (stopWhenDead && TryComp<MobStateComponent>(target, out var mobState) && mobState.CurrentState == MobState.Dead)
|
||||
return;
|
||||
|
||||
|
||||
if (EnsureComp<SuperBonkComponent>(target, out var component))
|
||||
return;
|
||||
|
||||
var tables = EntityQueryEnumerator<BonkableComponent>();
|
||||
var bonks = new List<EntityUid>();
|
||||
// This is done so we don't crash if something like a new table is spawned.
|
||||
while (tables.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
bonks.Add(uid);
|
||||
}
|
||||
|
||||
component.Tables = bonks.GetEnumerator();
|
||||
component.RemoveClumsy = !EnsureComp<ClumsyComponent>(target, out _);
|
||||
component.StopWhenDead = stopWhenDead;
|
||||
}
|
||||
|
||||
private bool TryBonk(EntityUid uid, EntityUid tableUid)
|
||||
{
|
||||
if (!TryComp<ClumsyComponent>(uid, out var clumsyComp))
|
||||
return false;
|
||||
|
||||
// It would be very weird for something without a transform component to have a bonk component
|
||||
// but just in case because I don't want to crash the server.
|
||||
if (!HasComp<TransformComponent>(uid) || !TryComp<ClumsyComponent>(comp.Target, out var clumsyComp))
|
||||
return;
|
||||
|
||||
_transformSystem.SetCoordinates(comp.Target, Transform(uid).Coordinates);
|
||||
|
||||
_clumsySystem.HitHeadClumsy((comp.Target, clumsyComp), uid);
|
||||
|
||||
_audioSystem.PlayPvs(clumsyComp.TableBonkSound, comp.Target);
|
||||
}
|
||||
|
||||
private void OnMobStateChanged(EntityUid uid, SuperBonkComponent comp, MobStateChangedEvent args)
|
||||
{
|
||||
if (comp.StopWhenDead && args.NewMobState == MobState.Dead)
|
||||
if (HasComp<TransformComponent>(tableUid))
|
||||
{
|
||||
RemComp<SuperBonkComponent>(uid);
|
||||
}
|
||||
}
|
||||
_transformSystem.SetCoordinates(uid, Transform(tableUid).Coordinates);
|
||||
|
||||
private void OnBonkShutdown(EntityUid uid, SuperBonkComponent comp, ComponentShutdown ev)
|
||||
{
|
||||
if (comp.RemoveClumsy)
|
||||
RemComp<ClumsyComponent>(comp.Target);
|
||||
_clumsySystem.HitHeadClumsy((uid, clumsyComp), tableUid);
|
||||
|
||||
_audioSystem.PlayPvs(clumsyComp.TableBonkSound, tableUid);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -29,10 +29,10 @@ public sealed class TagCommand : ToolshedCommand
|
||||
public IEnumerable<EntityUid> With(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] IEnumerable<EntityUid> entities,
|
||||
[CommandArgument] ValueRef<string, Prototype<TagPrototype>> tag)
|
||||
[CommandArgument] ProtoId<TagPrototype> tag)
|
||||
{
|
||||
_tag ??= GetSys<TagSystem>();
|
||||
return entities.Where(e => _tag.HasTag(e, tag.Evaluate(ctx)!));
|
||||
return entities.Where(e => _tag.HasTag(e, tag!));
|
||||
}
|
||||
|
||||
[CommandImplementation("add")]
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using Content.Server.AlertLevel.Systems;
|
||||
|
||||
namespace Content.Server.AlertLevel;
|
||||
/// <summary>
|
||||
/// This component is for changing the alert level of the station when triggered.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(AlertLevelChangeOnTriggerSystem))]
|
||||
public sealed partial class AlertLevelChangeOnTriggerComponent : Component
|
||||
{
|
||||
///<summary>
|
||||
///The alert level to change to when triggered.
|
||||
///</summary>
|
||||
[DataField]
|
||||
public string Level = "blue";
|
||||
|
||||
/// <summary>
|
||||
///Whether to play the sound when the alert level changes.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool PlaySound = true;
|
||||
|
||||
/// <summary>
|
||||
///Whether to say the announcement when the alert level changes.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Announce = true;
|
||||
|
||||
/// <summary>
|
||||
///Force the alert change. This applies if the alert level is not selectable or not.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Force = false;
|
||||
}
|
||||
@@ -116,6 +116,20 @@ public sealed class AlertLevelSystem : EntitySystem
|
||||
return alert.CurrentDelay;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the default alert level for a station entity.
|
||||
/// Returns an empty string if the station has no alert levels defined.
|
||||
/// </summary>
|
||||
/// <param name="station">The station entity.</param>
|
||||
public string GetDefaultLevel(Entity<AlertLevelComponent?> station)
|
||||
{
|
||||
if (!Resolve(station.Owner, ref station.Comp) || station.Comp.AlertLevels == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
return station.Comp.AlertLevels.DefaultLevel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the alert level based on the station's entity ID.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using Content.Server.AlertLevel;
|
||||
using Content.Server.Explosion.EntitySystems;
|
||||
using Content.Server.Station.Systems;
|
||||
|
||||
namespace Content.Server.AlertLevel.Systems;
|
||||
|
||||
public sealed class AlertLevelChangeOnTriggerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AlertLevelSystem _alertLevelSystem = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<AlertLevelChangeOnTriggerComponent, TriggerEvent>(OnTrigger);
|
||||
}
|
||||
|
||||
private void OnTrigger(Entity<AlertLevelChangeOnTriggerComponent> ent, ref TriggerEvent args)
|
||||
{
|
||||
var stationUid = _station.GetOwningStation(ent.Owner);
|
||||
if (!stationUid.HasValue)
|
||||
return;
|
||||
|
||||
_alertLevelSystem.SetLevel(stationUid.Value, ent.Comp.Level, ent.Comp.PlaySound, ent.Comp.Announce, ent.Comp.Force);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ using Content.Shared.Popups;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Anomaly.Effects;
|
||||
@@ -26,6 +27,7 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly BodySystem _body = default!;
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _player = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
|
||||
[Dependency] private readonly JitteringSystem _jitter = default!;
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
@@ -102,7 +104,7 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
|
||||
|
||||
if (ent.Comp.StartMessage is not null &&
|
||||
_mind.TryGetMind(ent, out _, out var mindComponent) &&
|
||||
mindComponent.Session != null)
|
||||
_player.TryGetSessionById(mindComponent.UserId, out var session))
|
||||
{
|
||||
var message = Loc.GetString(ent.Comp.StartMessage);
|
||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
|
||||
@@ -111,7 +113,7 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
|
||||
wrappedMessage,
|
||||
default,
|
||||
false,
|
||||
mindComponent.Session.Channel,
|
||||
session.Channel,
|
||||
_messageColor);
|
||||
|
||||
_popup.PopupEntity(message, ent, ent, PopupType.MediumCaution);
|
||||
@@ -137,7 +139,8 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
|
||||
|
||||
private void OnSeverityChanged(Entity<InnerBodyAnomalyComponent> ent, ref AnomalySeverityChangedEvent args)
|
||||
{
|
||||
if (!_mind.TryGetMind(ent, out _, out var mindComponent) || mindComponent.Session == null)
|
||||
if (!_mind.TryGetMind(ent, out _, out var mindComponent) ||
|
||||
!_player.TryGetSessionById(mindComponent.UserId, out var session))
|
||||
return;
|
||||
|
||||
var message = string.Empty;
|
||||
@@ -172,7 +175,7 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
|
||||
wrappedMessage,
|
||||
default,
|
||||
false,
|
||||
mindComponent.Session.Channel,
|
||||
session.Channel,
|
||||
_messageColor);
|
||||
|
||||
_popup.PopupEntity(message, ent, ent, PopupType.MediumCaution);
|
||||
@@ -214,7 +217,7 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
|
||||
|
||||
if (ent.Comp.EndMessage is not null &&
|
||||
_mind.TryGetMind(ent, out _, out var mindComponent) &&
|
||||
mindComponent.Session != null)
|
||||
_player.TryGetSessionById(mindComponent.UserId, out var session))
|
||||
{
|
||||
var message = Loc.GetString(ent.Comp.EndMessage);
|
||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
|
||||
@@ -223,7 +226,7 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
|
||||
wrappedMessage,
|
||||
default,
|
||||
false,
|
||||
mindComponent.Session.Channel,
|
||||
session.Channel,
|
||||
_messageColor);
|
||||
|
||||
|
||||
|
||||
@@ -264,10 +264,10 @@ public sealed partial class AntagSelectionSystem
|
||||
if (!_mind.TryGetMind(entity, out _, out var mindComponent))
|
||||
return;
|
||||
|
||||
if (mindComponent.Session == null)
|
||||
if (!_playerManager.TryGetSessionById(mindComponent.UserId, out var session))
|
||||
return;
|
||||
|
||||
SendBriefing(mindComponent.Session, briefing, briefingColor, briefingSound);
|
||||
SendBriefing(session, briefing, briefingColor, briefingSound);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Server.Atmos.Monitor.Components;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Pinpointer;
|
||||
using Content.Server.Power.Components;
|
||||
|
||||
@@ -17,6 +17,7 @@ using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
|
||||
namespace Content.Server.Atmos.Consoles;
|
||||
|
||||
@@ -360,7 +361,7 @@ public sealed class AtmosMonitoringConsoleSystem : SharedAtmosMonitoringConsoleS
|
||||
chunk.AtmosPipeData[index] = atmosPipeData & ~mask;
|
||||
}
|
||||
|
||||
// Rebuild the tile's pipe data
|
||||
// Rebuild the tile's pipe data
|
||||
foreach (var ent in _sharedMapSystem.GetAnchoredEntities(gridUid, grid, coords))
|
||||
{
|
||||
if (!TryComp<AtmosPipeColorComponent>(ent, out var entAtmosPipeColor))
|
||||
|
||||
@@ -56,11 +56,15 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
_physics.SetBodyStatus(uid, body, BodyStatus.OnGround);
|
||||
}
|
||||
|
||||
if (TryComp<FixturesComponent>(uid, out var fixtures))
|
||||
if (TryComp<FixturesComponent>(uid, out var fixtures)
|
||||
&& TryComp<MovedByPressureComponent>(uid, out var component))
|
||||
{
|
||||
foreach (var (id, fixture) in fixtures.Fixtures)
|
||||
{
|
||||
_physics.AddCollisionMask(uid, id, fixture, (int) CollisionGroup.TableLayer, manager: fixtures);
|
||||
if (component.TableLayerRemoved.Contains(id))
|
||||
{
|
||||
_physics.AddCollisionMask(uid, id, fixture, (int)CollisionGroup.TableLayer, manager: fixtures);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,9 +84,13 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
foreach (var (id, fixture) in fixtures.Fixtures)
|
||||
{
|
||||
_physics.RemoveCollisionMask(uid, id, fixture, (int) CollisionGroup.TableLayer, manager: fixtures);
|
||||
// Mark fixtures that have TableLayer removed
|
||||
if ((fixture.CollisionMask & (int)CollisionGroup.TableLayer) != 0)
|
||||
{
|
||||
component.TableLayerRemoved.Add(id);
|
||||
_physics.RemoveCollisionMask(uid, id, fixture, (int)CollisionGroup.TableLayer, manager: fixtures);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make them dynamic type? Ehh but they still want movement so uhh make it non-predicted like weightless?
|
||||
// idk it's hard.
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ using Content.Shared.Timing;
|
||||
using Content.Shared.Toggleable;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Hands;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Events;
|
||||
@@ -74,6 +75,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
SubscribeLocalEvent<FlammableComponent, TileFireEvent>(OnTileFire);
|
||||
SubscribeLocalEvent<FlammableComponent, RejuvenateEvent>(OnRejuvenate);
|
||||
SubscribeLocalEvent<FlammableComponent, ResistFireAlertEvent>(OnResistFireAlert);
|
||||
Subs.SubscribeWithRelay<FlammableComponent, ExtinguishEvent>(OnExtinguishEvent);
|
||||
|
||||
SubscribeLocalEvent<IgniteOnCollideComponent, StartCollideEvent>(IgniteOnCollide);
|
||||
SubscribeLocalEvent<IgniteOnCollideComponent, LandEvent>(OnIgniteLand);
|
||||
@@ -85,6 +87,14 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
SubscribeLocalEvent<IgniteOnHeatDamageComponent, DamageChangedEvent>(OnDamageChanged);
|
||||
}
|
||||
|
||||
private void OnExtinguishEvent(Entity<FlammableComponent> ent, ref ExtinguishEvent args)
|
||||
{
|
||||
// You know I'm really not sure if having AdjustFireStacks *after* Extinguish,
|
||||
// but I'm just moving this code, not questioning it.
|
||||
Extinguish(ent, ent.Comp);
|
||||
AdjustFireStacks(ent, args.FireStacksAdjustment, ent.Comp);
|
||||
}
|
||||
|
||||
private void OnMeleeHit(EntityUid uid, IgniteOnMeleeHitComponent component, MeleeHitEvent args)
|
||||
{
|
||||
foreach (var entity in args.HitEntities)
|
||||
@@ -330,6 +340,9 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
_ignitionSourceSystem.SetIgnited(uid, false);
|
||||
|
||||
var extinguished = new ExtinguishedEvent();
|
||||
RaiseLocalEvent(uid, ref extinguished);
|
||||
|
||||
UpdateAppearance(uid, flammable);
|
||||
}
|
||||
|
||||
@@ -351,6 +364,9 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
else
|
||||
_adminLogger.Add(LogType.Flammable, $"{ToPrettyString(uid):target} set on fire by {ToPrettyString(ignitionSource):actor}");
|
||||
flammable.OnFire = true;
|
||||
|
||||
var extinguished = new IgnitedEvent();
|
||||
RaiseLocalEvent(uid, ref extinguished);
|
||||
}
|
||||
|
||||
UpdateAppearance(uid, flammable);
|
||||
|
||||
@@ -16,6 +16,7 @@ using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Threading;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -60,12 +61,16 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
private float _updateInterval;
|
||||
|
||||
private int _thresholds;
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<GasTileOverlayComponent> _query;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_query = GetEntityQuery<GasTileOverlayComponent>();
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
|
||||
_updateJob = new UpdatePlayerJob()
|
||||
{
|
||||
EntManager = EntityManager,
|
||||
@@ -76,6 +81,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
MapManager = _mapManager,
|
||||
ChunkViewerPool = _chunkViewerPool,
|
||||
LastSentChunks = _lastSentChunks,
|
||||
GridQuery = _gridQuery,
|
||||
};
|
||||
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
@@ -85,7 +91,6 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
|
||||
SubscribeLocalEvent<GasTileOverlayComponent, ComponentStartup>(OnStartup);
|
||||
_query = GetEntityQuery<GasTileOverlayComponent>();
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, GasTileOverlayComponent component, ComponentStartup args)
|
||||
@@ -375,6 +380,8 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
public Dictionary<ICommonSession, Dictionary<NetEntity, HashSet<Vector2i>>> LastSentChunks;
|
||||
public List<ICommonSession> Sessions;
|
||||
|
||||
public EntityQuery<MapGridComponent> GridQuery;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
var playerSession = Sessions[index];
|
||||
@@ -391,7 +398,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
previouslySent.Remove(netGrid);
|
||||
|
||||
// If grid was deleted then don't worry about sending it to the client.
|
||||
if (!EntManager.TryGetEntity(netGrid, out var gridId) || !MapManager.IsGrid(gridId.Value))
|
||||
if (!EntManager.TryGetEntity(netGrid, out var gridId) || GridQuery.HasComp(gridId.Value))
|
||||
ev.RemovedChunks[netGrid] = oldIndices;
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
using Content.Server.Atmos.Monitor.Components;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.DeviceLinking.Systems;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
@@ -22,8 +19,9 @@ using Content.Shared.Interaction;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using System.Linq;
|
||||
using Content.Shared.DeviceNetwork.Events;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Systems;
|
||||
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos.Monitor.Components;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Events;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Systems;
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Piping.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Events;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
using Content.Server.AlertLevel;
|
||||
using Content.Server.Atmos.Monitor.Components;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.AlertLevel;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
using Content.Shared.DeviceNetwork.Systems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Systems;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Content.Server.Atmos.Monitor.Components;
|
||||
using Content.Server.Atmos.Monitor.Systems;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
using Content.Shared.Wires;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor;
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Binary.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class GasVolumePumpComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("enabled")]
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
[DataField("blocked")]
|
||||
public bool Blocked { get; set; } = false;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Overclocked { get; set; } = false;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("inlet")]
|
||||
public string InletName { get; set; } = "inlet";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("outlet")]
|
||||
public string OutletName { get; set; } = "outlet";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("transferRate")]
|
||||
public float TransferRate { get; set; } = Atmospherics.MaxTransferRate;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("maxTransferRate")]
|
||||
public float MaxTransferRate { get; set; } = Atmospherics.MaxTransferRate;
|
||||
|
||||
[DataField("leakRatio")]
|
||||
public float LeakRatio { get; set; } = 0.1f;
|
||||
|
||||
[DataField("lowerThreshold")]
|
||||
public float LowerThreshold { get; set; } = 0.01f;
|
||||
|
||||
[DataField("higherThreshold")]
|
||||
[GuidebookData]
|
||||
public float HigherThreshold { get; set; } = DefaultHigherThreshold;
|
||||
public static readonly float DefaultHigherThreshold = 2 * Atmospherics.MaxOutputPressure;
|
||||
|
||||
[DataField("overclockThreshold")]
|
||||
public float OverclockThreshold { get; set; } = 1000;
|
||||
|
||||
[DataField("lastMolesTransferred")]
|
||||
public float LastMolesTransferred;
|
||||
}
|
||||
}
|
||||
@@ -1,82 +1,41 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Atmos.Monitor.Systems;
|
||||
using Content.Server.Atmos.Piping.Binary.Components;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using Content.Shared.Atmos.Piping.Binary.Systems;
|
||||
using Content.Shared.Atmos.Piping.Components;
|
||||
using Content.Shared.Atmos.Visuals;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.DeviceNetwork.Events;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class GasVolumePumpSystem : EntitySystem
|
||||
public sealed class GasVolumePumpSystem : SharedGasVolumePumpSystem
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
|
||||
[Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GasVolumePumpComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<GasVolumePumpComponent, AtmosDeviceUpdateEvent>(OnVolumePumpUpdated);
|
||||
SubscribeLocalEvent<GasVolumePumpComponent, AtmosDeviceDisabledEvent>(OnVolumePumpLeaveAtmosphere);
|
||||
SubscribeLocalEvent<GasVolumePumpComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<GasVolumePumpComponent, ActivateInWorldEvent>(OnPumpActivate);
|
||||
SubscribeLocalEvent<GasVolumePumpComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
// Bound UI subscriptions
|
||||
SubscribeLocalEvent<GasVolumePumpComponent, GasVolumePumpChangeTransferRateMessage>(OnTransferRateChangeMessage);
|
||||
SubscribeLocalEvent<GasVolumePumpComponent, GasVolumePumpToggleStatusMessage>(OnToggleStatusMessage);
|
||||
|
||||
SubscribeLocalEvent<GasVolumePumpComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, GasVolumePumpComponent pump, ComponentInit args)
|
||||
{
|
||||
UpdateAppearance(uid, pump);
|
||||
}
|
||||
|
||||
private void OnExamined(EntityUid uid, GasVolumePumpComponent pump, ExaminedEvent args)
|
||||
{
|
||||
if (!EntityManager.GetComponent<TransformComponent>(uid).Anchored || !args.IsInDetailsRange) // Not anchored? Out of range? No status.
|
||||
return;
|
||||
|
||||
if (Loc.TryGetString("gas-volume-pump-system-examined", out var str,
|
||||
("statusColor", "lightblue"), // TODO: change with volume?
|
||||
("rate", pump.TransferRate)
|
||||
))
|
||||
args.PushMarkup(str);
|
||||
}
|
||||
|
||||
private void OnPowerChanged(EntityUid uid, GasVolumePumpComponent component, ref PowerChangedEvent args)
|
||||
{
|
||||
UpdateAppearance(uid, component);
|
||||
}
|
||||
|
||||
private void OnVolumePumpUpdated(EntityUid uid, GasVolumePumpComponent pump, ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!pump.Enabled ||
|
||||
@@ -134,78 +93,18 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
||||
private void OnVolumePumpLeaveAtmosphere(EntityUid uid, GasVolumePumpComponent pump, ref AtmosDeviceDisabledEvent args)
|
||||
{
|
||||
pump.Enabled = false;
|
||||
Dirty(uid, pump);
|
||||
UpdateAppearance(uid, pump);
|
||||
|
||||
DirtyUI(uid, pump);
|
||||
_userInterfaceSystem.CloseUi(uid, GasVolumePumpUiKey.Key);
|
||||
}
|
||||
|
||||
private void OnPumpActivate(EntityUid uid, GasVolumePumpComponent pump, ActivateInWorldEvent args)
|
||||
{
|
||||
if (args.Handled || !args.Complex)
|
||||
return;
|
||||
|
||||
if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
|
||||
return;
|
||||
|
||||
if (Transform(uid).Anchored)
|
||||
{
|
||||
_userInterfaceSystem.OpenUi(uid, GasVolumePumpUiKey.Key, actor.PlayerSession);
|
||||
DirtyUI(uid, pump);
|
||||
}
|
||||
else
|
||||
{
|
||||
_popup.PopupCursor(Loc.GetString("comp-gas-pump-ui-needs-anchor"), args.User);
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnToggleStatusMessage(EntityUid uid, GasVolumePumpComponent pump, GasVolumePumpToggleStatusMessage args)
|
||||
{
|
||||
pump.Enabled = args.Enabled;
|
||||
_adminLogger.Add(LogType.AtmosPowerChanged, LogImpact.Medium,
|
||||
$"{ToPrettyString(args.Actor):player} set the power on {ToPrettyString(uid):device} to {args.Enabled}");
|
||||
DirtyUI(uid, pump);
|
||||
UpdateAppearance(uid, pump);
|
||||
}
|
||||
|
||||
private void OnTransferRateChangeMessage(EntityUid uid, GasVolumePumpComponent pump, GasVolumePumpChangeTransferRateMessage args)
|
||||
{
|
||||
pump.TransferRate = Math.Clamp(args.TransferRate, 0f, pump.MaxTransferRate);
|
||||
_adminLogger.Add(LogType.AtmosVolumeChanged, LogImpact.Medium,
|
||||
$"{ToPrettyString(args.Actor):player} set the transfer rate on {ToPrettyString(uid):device} to {args.TransferRate}");
|
||||
DirtyUI(uid, pump);
|
||||
}
|
||||
|
||||
private void DirtyUI(EntityUid uid, GasVolumePumpComponent? pump)
|
||||
{
|
||||
if (!Resolve(uid, ref pump))
|
||||
return;
|
||||
|
||||
_userInterfaceSystem.SetUiState(uid, GasVolumePumpUiKey.Key,
|
||||
new GasVolumePumpBoundUserInterfaceState(Name(uid), pump.TransferRate, pump.Enabled));
|
||||
}
|
||||
|
||||
private void UpdateAppearance(EntityUid uid, GasVolumePumpComponent? pump = null, AppearanceComponent? appearance = null)
|
||||
{
|
||||
if (!Resolve(uid, ref pump, ref appearance, false))
|
||||
return;
|
||||
|
||||
bool pumpOn = pump.Enabled && (TryComp<ApcPowerReceiverComponent>(uid, out var power) && power.Powered);
|
||||
if (!pumpOn)
|
||||
_appearance.SetData(uid, GasVolumePumpVisuals.State, GasVolumePumpState.Off, appearance);
|
||||
else if (pump.Blocked)
|
||||
_appearance.SetData(uid, GasVolumePumpVisuals.State, GasVolumePumpState.Blocked, appearance);
|
||||
else
|
||||
_appearance.SetData(uid, GasVolumePumpVisuals.State, GasVolumePumpState.On, appearance);
|
||||
}
|
||||
|
||||
private void OnPacketRecv(EntityUid uid, GasVolumePumpComponent component, DeviceNetworkPacketEvent args)
|
||||
{
|
||||
if (!TryComp(uid, out DeviceNetworkComponent? netConn)
|
||||
|| !args.Data.TryGetValue(DeviceNetworkConstants.Command, out var cmd))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var payload = new NetworkPayload();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Server.Atmos.Piping.Binary.Components;
|
||||
using Content.Server.Atmos.Piping.Unary.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
|
||||
@@ -2,10 +2,7 @@ using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Atmos.Monitor.Systems;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.Atmos.Piping.Unary.Components;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Server.Power.Components;
|
||||
@@ -18,7 +15,9 @@ using Content.Shared.UserInterface;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Events;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
{
|
||||
|
||||
@@ -3,8 +3,6 @@ using Content.Server.Atmos.Monitor.Systems;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.Atmos.Piping.Unary.Components;
|
||||
using Content.Server.DeviceLinking.Systems;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
@@ -20,7 +18,9 @@ using Content.Shared.Audio;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DeviceLinking.Events;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.DeviceNetwork.Events;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Power;
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Atmos.Monitor.Components;
|
||||
using Content.Server.Atmos.Monitor.Systems;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.Atmos.Piping.Unary.Components;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Piping.Unary.Visuals;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Piping.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Visuals;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
using Content.Shared.DeviceNetwork.Events;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Tools.Systems;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
@@ -25,7 +25,7 @@ public sealed partial class TileAtmosCollectionSerializer : ITypeSerializer<Dict
|
||||
SerializationHookContext hookCtx, ISerializationContext? context = null,
|
||||
ISerializationManager.InstantiationDelegate<Dictionary<Vector2i, TileAtmosphere>>? instanceProvider = null)
|
||||
{
|
||||
node.TryGetValue(new ValueDataNode("version"), out var versionNode);
|
||||
node.TryGetValue("version", out var versionNode);
|
||||
var version = ((ValueDataNode?) versionNode)?.AsInt() ?? 1;
|
||||
Dictionary<Vector2i, TileAtmosphere> tiles = new();
|
||||
|
||||
@@ -59,7 +59,7 @@ public sealed partial class TileAtmosCollectionSerializer : ITypeSerializer<Dict
|
||||
var dataNode = (MappingDataNode) node["data"];
|
||||
var chunkSize = serializationManager.Read<int>(dataNode["chunkSize"], hookCtx, context);
|
||||
|
||||
dataNode.TryGetValue(new ValueDataNode("uniqueMixes"), out var mixNode);
|
||||
dataNode.TryGet("uniqueMixes", out var mixNode);
|
||||
var unique = mixNode == null ? null : serializationManager.Read<List<GasMixture>?>(mixNode, hookCtx, context);
|
||||
|
||||
if (unique != null)
|
||||
@@ -67,7 +67,7 @@ public sealed partial class TileAtmosCollectionSerializer : ITypeSerializer<Dict
|
||||
var tileNode = (MappingDataNode) dataNode["tiles"];
|
||||
foreach (var (chunkNode, valueNode) in tileNode)
|
||||
{
|
||||
var chunkOrigin = serializationManager.Read<Vector2i>(chunkNode, hookCtx, context);
|
||||
var chunkOrigin = serializationManager.Read<Vector2i>(tileNode.GetKeyNode(chunkNode), hookCtx, context);
|
||||
var chunk = serializationManager.Read<TileAtmosChunk>(valueNode, hookCtx, context);
|
||||
|
||||
foreach (var (mix, data) in chunk.Data)
|
||||
|
||||
@@ -100,21 +100,23 @@ namespace Content.Server.Body.Commands
|
||||
|
||||
var slotId = $"AttachBodyPartVerb-{partUid}";
|
||||
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (body.RootContainer.ContainedEntity != null)
|
||||
if (body.RootContainer.ContainedEntity is null && !bodySystem.AttachPartToRoot(bodyId, partUid.Value, body, part))
|
||||
{
|
||||
bodySystem.AttachPartToRoot(bodyId, partUid.Value, body, part);
|
||||
}
|
||||
else
|
||||
{
|
||||
var (rootPartId, rootPart) = bodySystem.GetRootPartOrNull(bodyId, body)!.Value;
|
||||
if (!bodySystem.TryCreatePartSlotAndAttach(rootPartId, slotId, partUid.Value, part.PartType, rootPart, part))
|
||||
{
|
||||
shell.WriteError($"Could not create slot {slotId} on entity {_entManager.ToPrettyString(bodyId)}");
|
||||
return;
|
||||
}
|
||||
shell.WriteError("Body container does not have a root entity to attach to the body part!");
|
||||
return;
|
||||
}
|
||||
|
||||
var (rootPartId, rootPart) = bodySystem.GetRootPartOrNull(bodyId, body)!.Value;
|
||||
if (!bodySystem.TryCreatePartSlotAndAttach(rootPartId,
|
||||
slotId,
|
||||
partUid.Value,
|
||||
part.PartType,
|
||||
rootPart,
|
||||
part))
|
||||
{
|
||||
shell.WriteError($"Could not create slot {slotId} on entity {_entManager.ToPrettyString(bodyId)}");
|
||||
return;
|
||||
}
|
||||
shell.WriteLine($"Attached part {_entManager.ToPrettyString(partUid.Value)} to {_entManager.ToPrettyString(bodyId)}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,10 @@ using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Server.Labels.Components;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Labels.Components;
|
||||
|
||||
namespace Content.Server.Botany.Systems;
|
||||
|
||||
@@ -45,8 +45,7 @@ public sealed class PlantHolderSystem : EntitySystem
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
|
||||
|
||||
|
||||
public const float HydroponicsSpeedMultiplier = 1f;
|
||||
public const float HydroponicsConsumptionMultiplier = 2f;
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
namespace Content.Server.Cargo.Components;
|
||||
using Content.Shared.Actions;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||
|
||||
/// <summary>
|
||||
/// Any entities intersecting when a shuttle is recalled will be sold.
|
||||
|
||||
@@ -6,8 +6,4 @@ namespace Content.Server.Cargo.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
[Access(typeof(CargoSystem))]
|
||||
public sealed partial class CargoPalletConsoleComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("cashType", customTypeSerializer:typeof(PrototypeIdSerializer<StackPrototype>))]
|
||||
public string CashType = "Credit";
|
||||
}
|
||||
public sealed partial class CargoPalletConsoleComponent : Component;
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
using Content.Shared.Cargo;
|
||||
|
||||
namespace Content.Server.Cargo.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Added to the abstract representation of a station to track its money.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(SharedCargoSystem))]
|
||||
public sealed partial class StationBankAccountComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("balance")]
|
||||
public int Balance = 2000;
|
||||
|
||||
/// <summary>
|
||||
/// How much the bank balance goes up per second, every Delay period. Rounded down when multiplied.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("increasePerSecond")]
|
||||
public int IncreasePerSecond = 1;
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Cargo.Components;
|
||||
|
||||
@@ -16,15 +16,19 @@ public sealed partial class StationCargoOrderDatabaseComponent : Component
|
||||
/// <summary>
|
||||
/// Maximum amount of orders a station is allowed, approved or not.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("capacity")]
|
||||
[DataField]
|
||||
public int Capacity = 20;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("orders")]
|
||||
public List<CargoOrderData> Orders = new();
|
||||
[ViewVariables]
|
||||
public IEnumerable<CargoOrderData> AllOrders => Orders.SelectMany(p => p.Value);
|
||||
|
||||
[DataField]
|
||||
public Dictionary<ProtoId<CargoAccountPrototype>, List<CargoOrderData>> Orders = new();
|
||||
|
||||
/// <summary>
|
||||
/// Used to determine unique order IDs
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int NumOrdersCreated;
|
||||
|
||||
// TODO: Can probably dump this
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Cargo.Components;
|
||||
using Content.Server.Labels;
|
||||
using Content.Server.NameIdentifier;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Cargo;
|
||||
@@ -9,6 +8,7 @@ using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Labels.EntitySystems;
|
||||
using Content.Shared.NameIdentifier;
|
||||
using Content.Shared.Paper;
|
||||
using Content.Shared.Stacks;
|
||||
@@ -27,7 +27,6 @@ public sealed partial class CargoSystem
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
[Dependency] private readonly NameIdentifierSystem _nameIdentifier = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSys = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
[ValidatePrototypeId<NameIdentifierGroupPrototype>]
|
||||
private const string BountyNameIdentifierGroup = "Bounty";
|
||||
@@ -56,13 +55,13 @@ public sealed partial class CargoSystem
|
||||
!TryComp<StationCargoBountyDatabaseComponent>(station, out var bountyDb))
|
||||
return;
|
||||
|
||||
var untilNextSkip = bountyDb.NextSkipTime - _timing.CurTime;
|
||||
var untilNextSkip = bountyDb.NextSkipTime - Timing.CurTime;
|
||||
_uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties, bountyDb.History, untilNextSkip));
|
||||
}
|
||||
|
||||
private void OnPrintLabelMessage(EntityUid uid, CargoBountyConsoleComponent component, BountyPrintLabelMessage args)
|
||||
{
|
||||
if (_timing.CurTime < component.NextPrintTime)
|
||||
if (Timing.CurTime < component.NextPrintTime)
|
||||
return;
|
||||
|
||||
if (_station.GetOwningStation(uid) is not { } station)
|
||||
@@ -72,7 +71,7 @@ public sealed partial class CargoSystem
|
||||
return;
|
||||
|
||||
var label = Spawn(component.BountyLabelId, Transform(uid).Coordinates);
|
||||
component.NextPrintTime = _timing.CurTime + component.PrintDelay;
|
||||
component.NextPrintTime = Timing.CurTime + component.PrintDelay;
|
||||
SetupBountyLabel(label, station, bounty.Value);
|
||||
_audio.PlayPvs(component.PrintSound, uid);
|
||||
}
|
||||
@@ -82,7 +81,7 @@ public sealed partial class CargoSystem
|
||||
if (_station.GetOwningStation(uid) is not { } station || !TryComp<StationCargoBountyDatabaseComponent>(station, out var db))
|
||||
return;
|
||||
|
||||
if (_timing.CurTime < db.NextSkipTime)
|
||||
if (Timing.CurTime < db.NextSkipTime)
|
||||
return;
|
||||
|
||||
if (!TryGetBountyFromId(station, args.BountyId, out var bounty))
|
||||
@@ -102,8 +101,8 @@ public sealed partial class CargoSystem
|
||||
return;
|
||||
|
||||
FillBountyDatabase(station);
|
||||
db.NextSkipTime = _timing.CurTime + db.SkipDelay;
|
||||
var untilNextSkip = db.NextSkipTime - _timing.CurTime;
|
||||
db.NextSkipTime = Timing.CurTime + db.SkipDelay;
|
||||
var untilNextSkip = db.NextSkipTime - Timing.CurTime;
|
||||
_uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, db.History, untilNextSkip));
|
||||
_audio.PlayPvs(component.SkipSound, uid);
|
||||
}
|
||||
@@ -472,7 +471,7 @@ public sealed partial class CargoSystem
|
||||
skipped
|
||||
? CargoBountyHistoryData.BountyResult.Skipped
|
||||
: CargoBountyHistoryData.BountyResult.Completed,
|
||||
_gameTiming.CurTime,
|
||||
Timing.CurTime,
|
||||
actorName));
|
||||
ent.Comp.Bounties.RemoveAt(i);
|
||||
return true;
|
||||
@@ -514,7 +513,7 @@ public sealed partial class CargoSystem
|
||||
continue;
|
||||
}
|
||||
|
||||
var untilNextSkip = db.NextSkipTime - _timing.CurTime;
|
||||
var untilNextSkip = db.NextSkipTime - Timing.CurTime;
|
||||
_uiSystem.SetUiState((uid, ui), CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, db.History, untilNextSkip));
|
||||
}
|
||||
}
|
||||
|
||||
167
Content.Server/Cargo/Systems/CargoSystem.Funds.cs
Normal file
167
Content.Server/Cargo/Systems/CargoSystem.Funds.cs
Normal file
@@ -0,0 +1,167 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.UserInterface;
|
||||
|
||||
namespace Content.Server.Cargo.Systems;
|
||||
|
||||
public sealed partial class CargoSystem
|
||||
{
|
||||
private bool _allowPrimaryAccountAllocation;
|
||||
private bool _allowPrimaryCutAdjustment;
|
||||
|
||||
public void InitializeFunds()
|
||||
{
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, CargoConsoleWithdrawFundsMessage>(OnWithdrawFunds);
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, CargoConsoleToggleLimitMessage>(OnToggleLimit);
|
||||
SubscribeLocalEvent<FundingAllocationConsoleComponent, SetFundingAllocationBuiMessage>(OnSetFundingAllocation);
|
||||
SubscribeLocalEvent<FundingAllocationConsoleComponent, BeforeActivatableUIOpenEvent>(OnFundAllocationBuiOpen);
|
||||
|
||||
_cfg.OnValueChanged(CCVars.AllowPrimaryAccountAllocation, enabled => { _allowPrimaryAccountAllocation = enabled; }, true);
|
||||
_cfg.OnValueChanged(CCVars.AllowPrimaryCutAdjustment, enabled => { _allowPrimaryCutAdjustment = enabled; }, true);
|
||||
}
|
||||
|
||||
private void OnWithdrawFunds(Entity<CargoOrderConsoleComponent> ent, ref CargoConsoleWithdrawFundsMessage args)
|
||||
{
|
||||
if (_station.GetOwningStation(ent) is not { } station ||
|
||||
!TryComp<StationBankAccountComponent>(station, out var bank))
|
||||
return;
|
||||
|
||||
if (args.Account == ent.Comp.Account ||
|
||||
args.Amount <= 0 ||
|
||||
args.Amount > GetBalanceFromAccount((station, bank), ent.Comp.Account) * ent.Comp.TransferLimit)
|
||||
return;
|
||||
|
||||
if (Timing.CurTime < ent.Comp.NextAccountActionTime)
|
||||
return;
|
||||
|
||||
if (!_accessReaderSystem.IsAllowed(args.Actor, ent))
|
||||
{
|
||||
ConsolePopup(args.Actor, Loc.GetString("cargo-console-order-not-allowed"));
|
||||
PlayDenySound(ent, ent.Comp);
|
||||
return;
|
||||
}
|
||||
|
||||
ent.Comp.NextAccountActionTime = Timing.CurTime + ent.Comp.AccountActionDelay;
|
||||
UpdateBankAccount((station, bank), -args.Amount, ent.Comp.Account, dirty: false);
|
||||
_audio.PlayPvs(ApproveSound, ent);
|
||||
|
||||
var tryGetIdentityShortInfoEvent = new TryGetIdentityShortInfoEvent(ent, args.Actor);
|
||||
RaiseLocalEvent(tryGetIdentityShortInfoEvent);
|
||||
|
||||
var ourAccount = _protoMan.Index(ent.Comp.Account);
|
||||
if (args.Account == null)
|
||||
{
|
||||
var stackPrototype = _protoMan.Index(ent.Comp.CashType);
|
||||
_stack.Spawn(args.Amount, stackPrototype, Transform(ent).Coordinates);
|
||||
|
||||
if (!_emag.CheckFlag(ent, EmagType.Interaction))
|
||||
{
|
||||
var msg = Loc.GetString("cargo-console-fund-withdraw-broadcast",
|
||||
("name", tryGetIdentityShortInfoEvent.Title ?? Loc.GetString("cargo-console-fund-transfer-user-unknown")),
|
||||
("amount", args.Amount),
|
||||
("name1", Loc.GetString(ourAccount.Name)),
|
||||
("code1", Loc.GetString(ourAccount.Code)));
|
||||
_radio.SendRadioMessage(ent, msg, ourAccount.RadioChannel, ent, escapeMarkup: false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var otherAccount = _protoMan.Index(args.Account.Value);
|
||||
UpdateBankAccount((station, bank), args.Amount, args.Account.Value);
|
||||
|
||||
if (!_emag.CheckFlag(ent, EmagType.Interaction))
|
||||
{
|
||||
var msg = Loc.GetString("cargo-console-fund-transfer-broadcast",
|
||||
("name", tryGetIdentityShortInfoEvent.Title ?? Loc.GetString("cargo-console-fund-transfer-user-unknown")),
|
||||
("amount", args.Amount),
|
||||
("name1", Loc.GetString(ourAccount.Name)),
|
||||
("code1", Loc.GetString(ourAccount.Code)),
|
||||
("name2", Loc.GetString(otherAccount.Name)),
|
||||
("code2", Loc.GetString(otherAccount.Code)));
|
||||
_radio.SendRadioMessage(ent, msg, ourAccount.RadioChannel, ent, escapeMarkup: false);
|
||||
_radio.SendRadioMessage(ent, msg, otherAccount.RadioChannel, ent, escapeMarkup: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnToggleLimit(Entity<CargoOrderConsoleComponent> ent, ref CargoConsoleToggleLimitMessage args)
|
||||
{
|
||||
if (!_accessReaderSystem.FindAccessTags(args.Actor).Intersect(ent.Comp.RemoveLimitAccess).Any())
|
||||
{
|
||||
ConsolePopup(args.Actor, Loc.GetString("cargo-console-order-not-allowed"));
|
||||
PlayDenySound(ent, ent.Comp);
|
||||
return;
|
||||
}
|
||||
|
||||
_audio.PlayPvs(ent.Comp.ToggleLimitSound, ent);
|
||||
ent.Comp.TransferUnbounded = !ent.Comp.TransferUnbounded;
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
|
||||
private void OnSetFundingAllocation(Entity<FundingAllocationConsoleComponent> ent, ref SetFundingAllocationBuiMessage args)
|
||||
{
|
||||
if (_station.GetOwningStation(ent) is not { } station ||
|
||||
!TryComp<StationBankAccountComponent>(station, out var bank))
|
||||
return;
|
||||
|
||||
var expectedCount = _allowPrimaryAccountAllocation ? bank.RevenueDistribution.Count : bank.RevenueDistribution.Count - 1;
|
||||
if (args.Percents.Count != expectedCount)
|
||||
return;
|
||||
|
||||
var differs = false;
|
||||
foreach (var (account, percent) in args.Percents)
|
||||
{
|
||||
if (percent != (int) Math.Round(bank.RevenueDistribution[account] * 100))
|
||||
{
|
||||
differs = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
differs = differs || args.PrimaryCut != bank.PrimaryCut || args.LockboxCut != bank.LockboxCut;
|
||||
|
||||
if (!differs)
|
||||
return;
|
||||
|
||||
if (args.Percents.Values.Sum() != 100)
|
||||
return;
|
||||
|
||||
var primaryCut = bank.RevenueDistribution[bank.PrimaryAccount];
|
||||
bank.RevenueDistribution.Clear();
|
||||
foreach (var (account, percent )in args.Percents)
|
||||
{
|
||||
bank.RevenueDistribution.Add(account, percent / 100.0);
|
||||
}
|
||||
if (!_allowPrimaryAccountAllocation)
|
||||
{
|
||||
bank.RevenueDistribution.Add(bank.PrimaryAccount, 0);
|
||||
}
|
||||
|
||||
if (_allowPrimaryCutAdjustment && args.PrimaryCut is >= 0.0 and <= 1.0)
|
||||
{
|
||||
bank.PrimaryCut = args.PrimaryCut;
|
||||
}
|
||||
if (_lockboxCutEnabled && args.LockboxCut is >= 0.0 and <= 1.0)
|
||||
{
|
||||
bank.LockboxCut = args.LockboxCut;
|
||||
}
|
||||
|
||||
Dirty(station, bank);
|
||||
|
||||
_audio.PlayPvs(ent.Comp.SetDistributionSound, ent);
|
||||
_adminLogger.Add(
|
||||
LogType.Action,
|
||||
LogImpact.Medium,
|
||||
$"{ToPrettyString(args.Actor):player} set station {ToPrettyString(station)} fund distribution: {string.Join(',', bank.RevenueDistribution.Select(p => $"{p.Key}: {p.Value}").ToList())}, primary cut: {bank.PrimaryCut}, lockbox cut: {bank.LockboxCut}");
|
||||
}
|
||||
|
||||
private void OnFundAllocationBuiOpen(Entity<FundingAllocationConsoleComponent> ent, ref BeforeActivatableUIOpenEvent args)
|
||||
{
|
||||
if (_station.GetOwningStation(ent) is { } station)
|
||||
_uiSystem.SetUiState(ent.Owner, FundingAllocationConsoleUiKey.Key, new FundingAllocationConsoleBuiState(GetNetEntity(station)));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Cargo.Components;
|
||||
using Content.Server.Labels.Components;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.BUI;
|
||||
@@ -11,7 +10,9 @@ using Content.Shared.Database;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Labels.Components;
|
||||
using Content.Shared.Paper;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -23,16 +24,6 @@ namespace Content.Server.Cargo.Systems
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
/// <summary>
|
||||
/// How much time to wait (in seconds) before increasing bank accounts balance.
|
||||
/// </summary>
|
||||
private const int Delay = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Keeps track of how much time has elapsed since last balance increase.
|
||||
/// </summary>
|
||||
private float _timer;
|
||||
|
||||
private void InitializeConsole()
|
||||
{
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, CargoConsoleAddOrderMessage>(OnAddOrderMessage);
|
||||
@@ -41,9 +32,7 @@ namespace Content.Server.Cargo.Systems
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, BoundUIOpenedEvent>(OnOrderUIOpened);
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, BankBalanceUpdatedEvent>(OnOrderBalanceUpdated);
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, GotEmaggedEvent>(OnEmagged);
|
||||
Reset();
|
||||
}
|
||||
|
||||
private void OnInteractUsing(EntityUid uid, CargoOrderConsoleComponent component, ref InteractUsingEvent args)
|
||||
@@ -61,8 +50,8 @@ namespace Content.Server.Cargo.Systems
|
||||
if (!TryComp(stationUid, out StationBankAccountComponent? bank))
|
||||
return;
|
||||
|
||||
_audio.PlayPvs(component.ConfirmSound, uid);
|
||||
UpdateBankAccount((stationUid.Value, bank), (int) price);
|
||||
_audio.PlayPvs(ApproveSound, uid);
|
||||
UpdateBankAccount((stationUid.Value, bank), (int) price, component.Account);
|
||||
QueueDel(args.Used);
|
||||
args.Handled = true;
|
||||
}
|
||||
@@ -73,11 +62,6 @@ namespace Content.Server.Cargo.Systems
|
||||
UpdateOrderState(uid, station);
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
_timer = 0;
|
||||
}
|
||||
|
||||
private void OnEmagged(Entity<CargoOrderConsoleComponent> ent, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
@@ -89,31 +73,17 @@ namespace Content.Server.Cargo.Systems
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void UpdateConsole(float frameTime)
|
||||
private void UpdateConsole()
|
||||
{
|
||||
_timer += frameTime;
|
||||
|
||||
// TODO: Doesn't work with serialization and shouldn't just be updating every delay
|
||||
// client can just interp this just fine on its own.
|
||||
while (_timer > Delay)
|
||||
var stationQuery = EntityQueryEnumerator<StationBankAccountComponent>();
|
||||
while (stationQuery.MoveNext(out var uid, out var bank))
|
||||
{
|
||||
_timer -= Delay;
|
||||
if (Timing.CurTime < bank.NextIncomeTime)
|
||||
continue;
|
||||
bank.NextIncomeTime += bank.IncomeDelay;
|
||||
|
||||
var stationQuery = EntityQueryEnumerator<StationBankAccountComponent>();
|
||||
while (stationQuery.MoveNext(out var uid, out var bank))
|
||||
{
|
||||
var balanceToAdd = bank.IncreasePerSecond * Delay;
|
||||
UpdateBankAccount((uid, bank), balanceToAdd);
|
||||
}
|
||||
|
||||
var query = EntityQueryEnumerator<CargoOrderConsoleComponent>();
|
||||
while (query.MoveNext(out var uid, out var _))
|
||||
{
|
||||
if (!_uiSystem.IsUiOpen(uid, CargoConsoleUiKey.Orders)) continue;
|
||||
|
||||
var station = _station.GetOwningStation(uid);
|
||||
UpdateOrderState(uid, station);
|
||||
}
|
||||
var balanceToAdd = (int) Math.Round(bank.IncreasePerSecond * bank.IncomeDelay.TotalSeconds);
|
||||
UpdateBankAccount((uid, bank), balanceToAdd, bank.RevenueDistribution);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +114,7 @@ namespace Content.Server.Cargo.Systems
|
||||
}
|
||||
|
||||
// Find our order again. It might have been dispatched or approved already
|
||||
var order = orderDatabase.Orders.Find(order => args.OrderId == order.OrderId && !order.Approved);
|
||||
var order = orderDatabase.Orders[component.Account].Find(order => args.OrderId == order.OrderId && !order.Approved);
|
||||
if (order == null)
|
||||
{
|
||||
return;
|
||||
@@ -158,7 +128,7 @@ namespace Content.Server.Cargo.Systems
|
||||
return;
|
||||
}
|
||||
|
||||
var amount = GetOutstandingOrderCount(orderDatabase);
|
||||
var amount = GetOutstandingOrderCount(orderDatabase, component.Account);
|
||||
var capacity = orderDatabase.Capacity;
|
||||
|
||||
// Too many orders, avoid them getting spammed in the UI.
|
||||
@@ -180,9 +150,10 @@ namespace Content.Server.Cargo.Systems
|
||||
}
|
||||
|
||||
var cost = order.Price * order.OrderQuantity;
|
||||
var accountBalance = GetBalanceFromAccount((station.Value, bank), component.Account);
|
||||
|
||||
// Not enough balance
|
||||
if (cost > bank.Balance)
|
||||
if (cost > accountBalance)
|
||||
{
|
||||
ConsolePopup(args.Actor, Loc.GetString("cargo-console-insufficient-funds", ("cost", cost)));
|
||||
PlayDenySound(uid, component);
|
||||
@@ -195,7 +166,7 @@ namespace Content.Server.Cargo.Systems
|
||||
|
||||
if (!ev.Handled)
|
||||
{
|
||||
ev.FulfillmentEntity = TryFulfillOrder((station.Value, stationData), order, orderDatabase);
|
||||
ev.FulfillmentEntity = TryFulfillOrder((station.Value, stationData), component.Account, order, orderDatabase);
|
||||
|
||||
if (ev.FulfillmentEntity == null)
|
||||
{
|
||||
@@ -206,7 +177,7 @@ namespace Content.Server.Cargo.Systems
|
||||
}
|
||||
|
||||
order.Approved = true;
|
||||
_audio.PlayPvs(component.ConfirmSound, uid);
|
||||
_audio.PlayPvs(ApproveSound, uid);
|
||||
|
||||
if (!_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
{
|
||||
@@ -220,20 +191,23 @@ namespace Content.Server.Cargo.Systems
|
||||
("approver", order.Approver ?? string.Empty),
|
||||
("cost", cost));
|
||||
_radio.SendRadioMessage(uid, message, component.AnnouncementChannel, uid, escapeMarkup: false);
|
||||
if (CargoOrderConsoleComponent.BaseAnnouncementChannel != component.AnnouncementChannel)
|
||||
_radio.SendRadioMessage(uid, message, CargoOrderConsoleComponent.BaseAnnouncementChannel, uid, escapeMarkup: false);
|
||||
}
|
||||
|
||||
ConsolePopup(args.Actor, Loc.GetString("cargo-console-trade-station", ("destination", MetaData(ev.FulfillmentEntity.Value).EntityName)));
|
||||
|
||||
// Log order approval
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low,
|
||||
$"{ToPrettyString(player):user} approved order [orderId:{order.OrderId}, quantity:{order.OrderQuantity}, product:{order.ProductId}, requester:{order.Requester}, reason:{order.Reason}] with balance at {bank.Balance}");
|
||||
_adminLogger.Add(LogType.Action,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(player):user} approved order [orderId:{order.OrderId}, quantity:{order.OrderQuantity}, product:{order.ProductId}, requester:{order.Requester}, reason:{order.Reason}] on account {component.Account} with balance at {accountBalance}");
|
||||
|
||||
orderDatabase.Orders.Remove(order);
|
||||
UpdateBankAccount((station.Value, bank), -cost);
|
||||
orderDatabase.Orders[component.Account].Remove(order);
|
||||
UpdateBankAccount((station.Value, bank), -cost, component.Account);
|
||||
UpdateOrders(station.Value);
|
||||
}
|
||||
|
||||
private EntityUid? TryFulfillOrder(Entity<StationDataComponent> stationData, CargoOrderData order, StationCargoOrderDatabaseComponent orderDatabase)
|
||||
private EntityUid? TryFulfillOrder(Entity<StationDataComponent> stationData, ProtoId<CargoAccountPrototype> account, CargoOrderData order, StationCargoOrderDatabaseComponent orderDatabase)
|
||||
{
|
||||
// No slots at the trade station
|
||||
_listEnts.Clear();
|
||||
@@ -253,7 +227,7 @@ namespace Content.Server.Cargo.Systems
|
||||
{
|
||||
var coordinates = new EntityCoordinates(trade, pad.Transform.LocalPosition);
|
||||
|
||||
if (FulfillOrder(order, coordinates, orderDatabase.PrinterOutput))
|
||||
if (FulfillOrder(order, account, coordinates, orderDatabase.PrinterOutput))
|
||||
{
|
||||
tradeDestination = trade;
|
||||
order.NumDispatched++;
|
||||
@@ -288,7 +262,7 @@ namespace Content.Server.Cargo.Systems
|
||||
if (!TryGetOrderDatabase(station, out var orderDatabase))
|
||||
return;
|
||||
|
||||
RemoveOrder(station.Value, args.OrderId, orderDatabase);
|
||||
RemoveOrder(station.Value, component.Account, args.OrderId, orderDatabase);
|
||||
}
|
||||
|
||||
private void OnAddOrderMessage(EntityUid uid, CargoOrderConsoleComponent component, CargoConsoleAddOrderMessage args)
|
||||
@@ -315,14 +289,15 @@ namespace Content.Server.Cargo.Systems
|
||||
|
||||
var data = GetOrderData(args, product, GenerateOrderId(orderDatabase));
|
||||
|
||||
if (!TryAddOrder(stationUid.Value, data, orderDatabase))
|
||||
if (!TryAddOrder(stationUid.Value, component.Account, data, orderDatabase))
|
||||
{
|
||||
PlayDenySound(uid, component);
|
||||
return;
|
||||
}
|
||||
|
||||
// Log order addition
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low,
|
||||
_adminLogger.Add(LogType.Action,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(player):user} added order [orderId:{data.OrderId}, quantity:{data.OrderQuantity}, product:{data.ProductId}, requester:{data.Requester}, reason:{data.Reason}]");
|
||||
|
||||
}
|
||||
@@ -335,29 +310,24 @@ namespace Content.Server.Cargo.Systems
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
private void OnOrderBalanceUpdated(Entity<CargoOrderConsoleComponent> ent, ref BankBalanceUpdatedEvent args)
|
||||
{
|
||||
if (!_uiSystem.IsUiOpen(ent.Owner, CargoConsoleUiKey.Orders))
|
||||
return;
|
||||
|
||||
UpdateOrderState(ent, args.Station);
|
||||
}
|
||||
|
||||
private void UpdateOrderState(EntityUid consoleUid, EntityUid? station)
|
||||
{
|
||||
if (station == null ||
|
||||
!TryComp<StationCargoOrderDatabaseComponent>(station, out var orderDatabase) ||
|
||||
!TryComp<StationBankAccountComponent>(station, out var bankAccount)) return;
|
||||
if (!TryComp<CargoOrderConsoleComponent>(consoleUid, out var console))
|
||||
return;
|
||||
|
||||
if (!TryComp<StationCargoOrderDatabaseComponent>(station, out var orderDatabase))
|
||||
return;
|
||||
|
||||
if (_uiSystem.HasUi(consoleUid, CargoConsoleUiKey.Orders))
|
||||
{
|
||||
_uiSystem.SetUiState(consoleUid, CargoConsoleUiKey.Orders, new CargoConsoleInterfaceState(
|
||||
_uiSystem.SetUiState(consoleUid,
|
||||
CargoConsoleUiKey.Orders,
|
||||
new CargoConsoleInterfaceState(
|
||||
MetaData(station.Value).EntityName,
|
||||
GetOutstandingOrderCount(orderDatabase),
|
||||
GetOutstandingOrderCount(orderDatabase, console.Account),
|
||||
orderDatabase.Capacity,
|
||||
bankAccount.Balance,
|
||||
orderDatabase.Orders
|
||||
GetNetEntity(station.Value),
|
||||
orderDatabase.Orders[console.Account]
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -377,11 +347,11 @@ namespace Content.Server.Cargo.Systems
|
||||
return new CargoOrderData(id, cargoProduct.Product, cargoProduct.Name, cargoProduct.Cost, args.Amount, args.Requester, args.Reason);
|
||||
}
|
||||
|
||||
public static int GetOutstandingOrderCount(StationCargoOrderDatabaseComponent component)
|
||||
public static int GetOutstandingOrderCount(StationCargoOrderDatabaseComponent component, ProtoId<CargoAccountPrototype> account)
|
||||
{
|
||||
var amount = 0;
|
||||
|
||||
foreach (var order in component.Orders)
|
||||
foreach (var order in component.Orders[account])
|
||||
{
|
||||
if (!order.Approved)
|
||||
continue;
|
||||
@@ -430,6 +400,7 @@ namespace Content.Server.Cargo.Systems
|
||||
string description,
|
||||
string dest,
|
||||
StationCargoOrderDatabaseComponent component,
|
||||
ProtoId<CargoAccountPrototype> account,
|
||||
Entity<StationDataComponent> stationData
|
||||
)
|
||||
{
|
||||
@@ -443,16 +414,17 @@ namespace Content.Server.Cargo.Systems
|
||||
order.Approved = true;
|
||||
|
||||
// Log order addition
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low,
|
||||
_adminLogger.Add(LogType.Action,
|
||||
LogImpact.Low,
|
||||
$"AddAndApproveOrder {description} added order [orderId:{order.OrderId}, quantity:{order.OrderQuantity}, product:{order.ProductId}, requester:{order.Requester}, reason:{order.Reason}]");
|
||||
|
||||
// Add it to the list
|
||||
return TryAddOrder(dbUid, order, component) && TryFulfillOrder(stationData, order, component).HasValue;
|
||||
return TryAddOrder(dbUid, account, order, component) && TryFulfillOrder(stationData, account, order, component).HasValue;
|
||||
}
|
||||
|
||||
private bool TryAddOrder(EntityUid dbUid, CargoOrderData data, StationCargoOrderDatabaseComponent component)
|
||||
private bool TryAddOrder(EntityUid dbUid, ProtoId<CargoAccountPrototype> account, CargoOrderData data, StationCargoOrderDatabaseComponent component)
|
||||
{
|
||||
component.Orders.Add(data);
|
||||
component.Orders[account].Add(data);
|
||||
UpdateOrders(dbUid);
|
||||
return true;
|
||||
}
|
||||
@@ -464,12 +436,12 @@ namespace Content.Server.Cargo.Systems
|
||||
return ++orderDB.NumOrdersCreated;
|
||||
}
|
||||
|
||||
public void RemoveOrder(EntityUid dbUid, int index, StationCargoOrderDatabaseComponent orderDB)
|
||||
public void RemoveOrder(EntityUid dbUid, ProtoId<CargoAccountPrototype> account, int index, StationCargoOrderDatabaseComponent orderDB)
|
||||
{
|
||||
var sequenceIdx = orderDB.Orders.FindIndex(order => order.OrderId == index);
|
||||
var sequenceIdx = orderDB.Orders[account].FindIndex(order => order.OrderId == index);
|
||||
if (sequenceIdx != -1)
|
||||
{
|
||||
orderDB.Orders.RemoveAt(sequenceIdx);
|
||||
orderDB.Orders[account].RemoveAt(sequenceIdx);
|
||||
}
|
||||
UpdateOrders(dbUid);
|
||||
}
|
||||
@@ -482,22 +454,22 @@ namespace Content.Server.Cargo.Systems
|
||||
component.Orders.Clear();
|
||||
}
|
||||
|
||||
private static bool PopFrontOrder(StationCargoOrderDatabaseComponent orderDB, [NotNullWhen(true)] out CargoOrderData? orderOut)
|
||||
private static bool PopFrontOrder(StationCargoOrderDatabaseComponent orderDB, ProtoId<CargoAccountPrototype> account, [NotNullWhen(true)] out CargoOrderData? orderOut)
|
||||
{
|
||||
var orderIdx = orderDB.Orders.FindIndex(order => order.Approved);
|
||||
var orderIdx = orderDB.Orders[account].FindIndex(order => order.Approved);
|
||||
if (orderIdx == -1)
|
||||
{
|
||||
orderOut = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
orderOut = orderDB.Orders[orderIdx];
|
||||
orderOut = orderDB.Orders[account][orderIdx];
|
||||
orderOut.NumDispatched++;
|
||||
|
||||
if (orderOut.NumDispatched >= orderOut.OrderQuantity)
|
||||
{
|
||||
// Order is complete. Remove from the queue.
|
||||
orderDB.Orders.RemoveAt(orderIdx);
|
||||
orderDB.Orders[account].RemoveAt(orderIdx);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -505,18 +477,19 @@ namespace Content.Server.Cargo.Systems
|
||||
/// <summary>
|
||||
/// Tries to fulfill the next outstanding order.
|
||||
/// </summary>
|
||||
private bool FulfillNextOrder(StationCargoOrderDatabaseComponent orderDB, EntityCoordinates spawn, string? paperProto)
|
||||
[PublicAPI]
|
||||
private bool FulfillNextOrder(StationCargoOrderDatabaseComponent orderDB, ProtoId<CargoAccountPrototype> account, EntityCoordinates spawn, string? paperProto)
|
||||
{
|
||||
if (!PopFrontOrder(orderDB, out var order))
|
||||
if (!PopFrontOrder(orderDB, account, out var order))
|
||||
return false;
|
||||
|
||||
return FulfillOrder(order, spawn, paperProto);
|
||||
return FulfillOrder(order, account, spawn, paperProto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fulfills the specified cargo order and spawns paper attached to it.
|
||||
/// </summary>
|
||||
private bool FulfillOrder(CargoOrderData order, EntityCoordinates spawn, string? paperProto)
|
||||
private bool FulfillOrder(CargoOrderData order, ProtoId<CargoAccountPrototype> account, EntityCoordinates spawn, string? paperProto)
|
||||
{
|
||||
// Create the item itself
|
||||
var item = Spawn(order.ProductId, spawn);
|
||||
@@ -532,14 +505,18 @@ namespace Content.Server.Cargo.Systems
|
||||
var val = Loc.GetString("cargo-console-paper-print-name", ("orderNumber", order.OrderId));
|
||||
_metaSystem.SetEntityName(printed, val);
|
||||
|
||||
_paperSystem.SetContent((printed, paper), Loc.GetString(
|
||||
var accountProto = _protoMan.Index(account);
|
||||
_paperSystem.SetContent((printed, paper),
|
||||
Loc.GetString(
|
||||
"cargo-console-paper-print-text",
|
||||
("orderNumber", order.OrderId),
|
||||
("itemName", MetaData(item).EntityName),
|
||||
("orderQuantity", order.OrderQuantity),
|
||||
("requester", order.Requester),
|
||||
("reason", order.Reason),
|
||||
("approver", order.Approver ?? string.Empty)));
|
||||
("reason", string.IsNullOrWhiteSpace(order.Reason) ? Loc.GetString("cargo-console-paper-reason-default") : order.Reason),
|
||||
("account", Loc.GetString(accountProto.Name)),
|
||||
("accountcode", Loc.GetString(accountProto.Code)),
|
||||
("approver", string.IsNullOrWhiteSpace(order.Approver) ? Loc.GetString("cargo-console-paper-approver-default") : order.Approver)));
|
||||
|
||||
// attempt to attach the label to the item
|
||||
if (TryComp<PaperLabelComponent>(item, out var label))
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Cargo.Components;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.BUI;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Cargo.Events;
|
||||
using Content.Shared.GameTicking;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.CCVar;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Cargo.Systems;
|
||||
|
||||
@@ -18,6 +19,7 @@ public sealed partial class CargoSystem
|
||||
*/
|
||||
|
||||
private static readonly SoundPathSpecifier ApproveSound = new("/Audio/Effects/Cargo/ping.ogg");
|
||||
private bool _lockboxCutEnabled;
|
||||
|
||||
private void InitializeShuttle()
|
||||
{
|
||||
@@ -29,11 +31,12 @@ public sealed partial class CargoSystem
|
||||
SubscribeLocalEvent<CargoPalletConsoleComponent, CargoPalletAppraiseMessage>(OnPalletAppraise);
|
||||
SubscribeLocalEvent<CargoPalletConsoleComponent, BoundUIOpenedEvent>(OnPalletUIOpen);
|
||||
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
|
||||
_cfg.OnValueChanged(CCVars.LockboxCutEnabled, (enabled) => { _lockboxCutEnabled = enabled; }, true);
|
||||
}
|
||||
|
||||
#region Console
|
||||
|
||||
[PublicAPI]
|
||||
private void UpdateCargoShuttleConsoles(EntityUid shuttleUid, CargoShuttleComponent _)
|
||||
{
|
||||
// Update pilot consoles that are already open.
|
||||
@@ -54,15 +57,18 @@ public sealed partial class CargoSystem
|
||||
|
||||
private void UpdatePalletConsoleInterface(EntityUid uid)
|
||||
{
|
||||
if (Transform(uid).GridUid is not EntityUid gridUid)
|
||||
if (Transform(uid).GridUid is not { } gridUid)
|
||||
{
|
||||
_uiSystem.SetUiState(uid, CargoPalletConsoleUiKey.Sale,
|
||||
new CargoPalletConsoleInterfaceState(0, 0, false));
|
||||
_uiSystem.SetUiState(uid,
|
||||
CargoPalletConsoleUiKey.Sale,
|
||||
new CargoPalletConsoleInterfaceState(0, 0, false));
|
||||
return;
|
||||
}
|
||||
GetPalletGoods(gridUid, out var toSell, out var amount);
|
||||
_uiSystem.SetUiState(uid, CargoPalletConsoleUiKey.Sale,
|
||||
new CargoPalletConsoleInterfaceState((int) amount, toSell.Count, true));
|
||||
GetPalletGoods(gridUid, out var toSell, out var goods);
|
||||
var totalAmount = goods.Sum(t => t.Item3);
|
||||
_uiSystem.SetUiState(uid,
|
||||
CargoPalletConsoleUiKey.Sale,
|
||||
new CargoPalletConsoleInterfaceState((int) totalAmount, toSell.Count, true));
|
||||
}
|
||||
|
||||
private void OnPalletUIOpen(EntityUid uid, CargoPalletConsoleComponent component, BoundUIOpenedEvent args)
|
||||
@@ -98,11 +104,15 @@ public sealed partial class CargoSystem
|
||||
var shuttleName = orderDatabase?.Shuttle != null ? MetaData(orderDatabase.Shuttle.Value).EntityName : string.Empty;
|
||||
|
||||
if (_uiSystem.HasUi(uid, CargoConsoleUiKey.Shuttle))
|
||||
_uiSystem.SetUiState(uid, CargoConsoleUiKey.Shuttle, new CargoShuttleConsoleBoundUserInterfaceState(
|
||||
{
|
||||
_uiSystem.SetUiState(uid,
|
||||
CargoConsoleUiKey.Shuttle,
|
||||
new CargoShuttleConsoleBoundUserInterfaceState(
|
||||
station != null ? MetaData(station.Value).EntityName : Loc.GetString("cargo-shuttle-console-station-unknown"),
|
||||
string.IsNullOrEmpty(shuttleName) ? Loc.GetString("cargo-shuttle-console-shuttle-not-found") : shuttleName,
|
||||
orders
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -132,9 +142,10 @@ public sealed partial class CargoSystem
|
||||
return orders;
|
||||
|
||||
var spaceRemaining = GetCargoSpace(shuttleUid);
|
||||
for (var i = 0; i < component.Orders.Count && spaceRemaining > 0; i++)
|
||||
var allOrders = component.AllOrders.ToList();
|
||||
for (var i = 0; i < allOrders.Count && spaceRemaining > 0; i++)
|
||||
{
|
||||
var order = component.Orders[i];
|
||||
var order = allOrders[i];
|
||||
if (order.Approved)
|
||||
{
|
||||
var numToShip = order.OrderQuantity - order.NumDispatched;
|
||||
@@ -142,8 +153,14 @@ public sealed partial class CargoSystem
|
||||
{
|
||||
// We won't be able to fit the whole order on, so make one
|
||||
// which represents the space we do have left:
|
||||
var reducedOrder = new CargoOrderData(order.OrderId,
|
||||
order.ProductId, order.ProductName, order.Price, spaceRemaining, order.Requester, order.Reason);
|
||||
var reducedOrder = new CargoOrderData(
|
||||
order.OrderId,
|
||||
order.ProductId,
|
||||
order.ProductName,
|
||||
order.Price,
|
||||
spaceRemaining,
|
||||
order.Requester,
|
||||
order.Reason);
|
||||
orders.Add(reducedOrder);
|
||||
}
|
||||
else
|
||||
@@ -219,16 +236,13 @@ public sealed partial class CargoSystem
|
||||
|
||||
#region Station
|
||||
|
||||
private bool SellPallets(EntityUid gridUid, out double amount)
|
||||
private bool SellPallets(EntityUid gridUid, out HashSet<(EntityUid, OverrideSellComponent?, double)> goods)
|
||||
{
|
||||
GetPalletGoods(gridUid, out var toSell, out amount);
|
||||
|
||||
Log.Debug($"Cargo sold {toSell.Count} entities for {amount}");
|
||||
GetPalletGoods(gridUid, out var toSell, out goods);
|
||||
|
||||
if (toSell.Count == 0)
|
||||
return false;
|
||||
|
||||
|
||||
var ev = new EntitySoldEvent(toSell);
|
||||
RaiseLocalEvent(ref ev);
|
||||
|
||||
@@ -240,9 +254,9 @@ public sealed partial class CargoSystem
|
||||
return true;
|
||||
}
|
||||
|
||||
private void GetPalletGoods(EntityUid gridUid, out HashSet<EntityUid> toSell, out double amount)
|
||||
private void GetPalletGoods(EntityUid gridUid, out HashSet<EntityUid> toSell, out HashSet<(EntityUid, OverrideSellComponent?, double)> goods)
|
||||
{
|
||||
amount = 0;
|
||||
goods = new HashSet<(EntityUid, OverrideSellComponent?, double)>();
|
||||
toSell = new HashSet<EntityUid>();
|
||||
|
||||
foreach (var (palletUid, _, _) in GetCargoPallets(gridUid, BuySellType.Sell))
|
||||
@@ -250,7 +264,9 @@ public sealed partial class CargoSystem
|
||||
// Containers should already get the sell price of their children so can skip those.
|
||||
_setEnts.Clear();
|
||||
|
||||
_lookup.GetEntitiesIntersecting(palletUid, _setEnts,
|
||||
_lookup.GetEntitiesIntersecting(
|
||||
palletUid,
|
||||
_setEnts,
|
||||
LookupFlags.Dynamic | LookupFlags.Sundries);
|
||||
|
||||
foreach (var ent in _setEnts)
|
||||
@@ -273,7 +289,7 @@ public sealed partial class CargoSystem
|
||||
if (price == 0)
|
||||
continue;
|
||||
toSell.Add(ent);
|
||||
amount += price;
|
||||
goods.Add((ent, CompOrNull<OverrideSellComponent>(ent), price));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -305,28 +321,50 @@ public sealed partial class CargoSystem
|
||||
{
|
||||
var xform = Transform(uid);
|
||||
|
||||
if (xform.GridUid is not EntityUid gridUid)
|
||||
if (_station.GetOwningStation(uid) is not { } station ||
|
||||
!TryComp<StationBankAccountComponent>(station, out var bankAccount))
|
||||
{
|
||||
_uiSystem.SetUiState(uid, CargoPalletConsoleUiKey.Sale,
|
||||
new CargoPalletConsoleInterfaceState(0, 0, false));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SellPallets(gridUid, out var price))
|
||||
if (xform.GridUid is not { } gridUid)
|
||||
{
|
||||
_uiSystem.SetUiState(uid,
|
||||
CargoPalletConsoleUiKey.Sale,
|
||||
new CargoPalletConsoleInterfaceState(0, 0, false));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SellPallets(gridUid, out var goods))
|
||||
return;
|
||||
|
||||
var stackPrototype = _protoMan.Index<StackPrototype>(component.CashType);
|
||||
_stack.Spawn((int) price, stackPrototype, xform.Coordinates);
|
||||
var baseDistribution = CreateAccountDistribution((station, bankAccount));
|
||||
foreach (var (_, sellComponent, value) in goods)
|
||||
{
|
||||
Dictionary<ProtoId<CargoAccountPrototype>, double> distribution;
|
||||
if (sellComponent != null)
|
||||
{
|
||||
var cut = _lockboxCutEnabled ? bankAccount.LockboxCut : bankAccount.PrimaryCut;
|
||||
distribution = new Dictionary<ProtoId<CargoAccountPrototype>, double>
|
||||
{
|
||||
{ sellComponent.OverrideAccount, cut },
|
||||
{ bankAccount.PrimaryAccount, 1.0 - cut },
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
distribution = baseDistribution;
|
||||
}
|
||||
|
||||
UpdateBankAccount((station, bankAccount), (int) Math.Round(value), distribution, false);
|
||||
}
|
||||
|
||||
Dirty(station, bankAccount);
|
||||
_audio.PlayPvs(ApproveSound, uid);
|
||||
UpdatePalletConsoleInterface(uid);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void OnRoundRestart(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Cargo.Components;
|
||||
using Content.Server.Power.Components;
|
||||
@@ -40,9 +41,8 @@ public sealed partial class CargoSystem
|
||||
continue;
|
||||
|
||||
// todo cannot be fucking asked to figure out device linking rn but this shouldn't just default to the first port.
|
||||
if (!TryComp<DeviceLinkSinkComponent>(uid, out var sinkComponent) ||
|
||||
sinkComponent.LinkedSources.FirstOrNull() is not { } console ||
|
||||
console != args.OrderConsole.Owner)
|
||||
if (!TryGetLinkedConsole((uid, tele), out var console) ||
|
||||
console.Value.Owner != args.OrderConsole.Owner)
|
||||
continue;
|
||||
|
||||
for (var i = 0; i < args.Order.OrderQuantity; i++)
|
||||
@@ -56,10 +56,26 @@ public sealed partial class CargoSystem
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetLinkedConsole(Entity<CargoTelepadComponent> ent,
|
||||
[NotNullWhen(true)] out Entity<CargoOrderConsoleComponent>? console)
|
||||
{
|
||||
console = null;
|
||||
if (!TryComp<DeviceLinkSinkComponent>(ent, out var sinkComponent) ||
|
||||
sinkComponent.LinkedSources.FirstOrNull() is not { } linked)
|
||||
return false;
|
||||
|
||||
if (!TryComp<CargoOrderConsoleComponent>(linked, out var consoleComp))
|
||||
return false;
|
||||
|
||||
console = (linked, consoleComp);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void UpdateTelepad(float frameTime)
|
||||
{
|
||||
var query = EntityQueryEnumerator<CargoTelepadComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
var query = EntityQueryEnumerator<CargoTelepadComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp, out var xform))
|
||||
{
|
||||
// Don't EntityQuery for it as it's not required.
|
||||
TryComp<AppearanceComponent>(uid, out var appearance);
|
||||
@@ -82,15 +98,14 @@ public sealed partial class CargoSystem
|
||||
continue;
|
||||
}
|
||||
|
||||
if (comp.CurrentOrders.Count == 0)
|
||||
if (comp.CurrentOrders.Count == 0 || !TryGetLinkedConsole((uid, comp), out var console))
|
||||
{
|
||||
comp.Accumulator += comp.Delay;
|
||||
continue;
|
||||
}
|
||||
|
||||
var xform = Transform(uid);
|
||||
var currentOrder = comp.CurrentOrders.First();
|
||||
if (FulfillOrder(currentOrder, xform.Coordinates, comp.PrinterOutput))
|
||||
if (FulfillOrder(currentOrder, console.Value.Comp.Account, xform.Coordinates, comp.PrinterOutput))
|
||||
{
|
||||
_audio.PlayPvs(_audio.ResolveSound(comp.TeleportSound), uid, AudioParams.Default.WithVolume(-8f));
|
||||
|
||||
@@ -128,9 +143,12 @@ public sealed partial class CargoSystem
|
||||
!TryComp<StationDataComponent>(station, out var data))
|
||||
return;
|
||||
|
||||
if (!TryGetLinkedConsole(ent, out var console))
|
||||
return;
|
||||
|
||||
foreach (var order in ent.Comp.CurrentOrders)
|
||||
{
|
||||
TryFulfillOrder((station, data), order, db);
|
||||
TryFulfillOrder((station, data), console.Value.Comp.Account, order, db);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,21 +9,23 @@ using Content.Shared.Administration.Logs;
|
||||
using Content.Server.Radio.EntitySystems;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Paper;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Cargo.Systems;
|
||||
|
||||
public sealed partial class CargoSystem : SharedCargoSystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
@@ -65,36 +67,59 @@ public sealed partial class CargoSystem : SharedCargoSystem
|
||||
InitializeShuttle();
|
||||
InitializeTelepad();
|
||||
InitializeBounty();
|
||||
InitializeFunds();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
UpdateConsole(frameTime);
|
||||
UpdateConsole();
|
||||
UpdateTelepad(frameTime);
|
||||
UpdateBounty();
|
||||
}
|
||||
|
||||
public void UpdateBankAccount(
|
||||
Entity<StationBankAccountComponent?> ent,
|
||||
int balanceAdded,
|
||||
ProtoId<CargoAccountPrototype> account,
|
||||
bool dirty = true)
|
||||
{
|
||||
UpdateBankAccount(
|
||||
ent,
|
||||
balanceAdded,
|
||||
new Dictionary<ProtoId<CargoAccountPrototype>, double> { {account, 1} },
|
||||
dirty: dirty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds or removes funds from the <see cref="StationBankAccountComponent"/>.
|
||||
/// </summary>
|
||||
/// <param name="ent">The station.</param>
|
||||
/// <param name="balanceAdded">The amount of funds to add or remove.</param>
|
||||
/// <param name="accountDistribution">The distribution between individual <see cref="CargoAccountPrototype"/>.</param>
|
||||
/// <param name="dirty">Whether to mark the bank account component as dirty.</param>
|
||||
[PublicAPI]
|
||||
public void UpdateBankAccount(Entity<StationBankAccountComponent?> ent, int balanceAdded)
|
||||
public void UpdateBankAccount(
|
||||
Entity<StationBankAccountComponent?> ent,
|
||||
int balanceAdded,
|
||||
Dictionary<ProtoId<CargoAccountPrototype>, double> accountDistribution,
|
||||
bool dirty = true)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return;
|
||||
|
||||
ent.Comp.Balance += balanceAdded;
|
||||
|
||||
var ev = new BankBalanceUpdatedEvent(ent, ent.Comp.Balance);
|
||||
|
||||
var query = EntityQueryEnumerator<BankClientComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var client, out var comp, out var xform))
|
||||
foreach (var (account, percent) in accountDistribution)
|
||||
{
|
||||
var station = _station.GetOwningStation(client, xform);
|
||||
if (station != ent)
|
||||
continue;
|
||||
|
||||
comp.Balance = ent.Comp.Balance;
|
||||
Dirty(client, comp);
|
||||
RaiseLocalEvent(client, ref ev);
|
||||
var accountBalancedAdded = (int) Math.Round(percent * balanceAdded);
|
||||
ent.Comp.Accounts[account] += accountBalancedAdded;
|
||||
}
|
||||
|
||||
var ev = new BankBalanceUpdatedEvent(ent, ent.Comp.Accounts);
|
||||
RaiseLocalEvent(ent, ref ev, true);
|
||||
|
||||
if (!dirty)
|
||||
return;
|
||||
|
||||
Dirty(ent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Linq;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.PDA;
|
||||
using Content.Shared.CartridgeLoader;
|
||||
using Content.Shared.DeviceNetwork.Events;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
using Content.Shared.Paper;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -26,7 +26,7 @@ public sealed partial class LogProbeCartridgeComponent : Component
|
||||
/// The sound to make when we scan something with access
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public SoundSpecifier SoundScan = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg");
|
||||
public SoundSpecifier SoundScan = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg", AudioParams.Default.WithVariation(0.25f));
|
||||
|
||||
/// <summary>
|
||||
/// Paper to spawn when printing logs.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.CartridgeLoader;
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
using Content.Shared.Database;
|
||||
@@ -9,7 +8,6 @@ using Content.Shared.Labels.EntitySystems;
|
||||
using Content.Shared.Paper;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Text;
|
||||
|
||||
@@ -19,11 +17,10 @@ public sealed class LogProbeCartridgeSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly CartridgeLoaderSystem _cartridge = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly SharedLabelSystem _label = default!;
|
||||
[Dependency] private readonly LabelSystem _label = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly PaperSystem _paper = default!;
|
||||
@@ -52,7 +49,7 @@ public sealed class LogProbeCartridgeSystem : EntitySystem
|
||||
return;
|
||||
|
||||
//Play scanning sound with slightly randomized pitch
|
||||
_audio.PlayEntity(ent.Comp.SoundScan, args.InteractEvent.User, target, AudioHelpers.WithVariation(0.25f, _random));
|
||||
_audio.PlayEntity(ent.Comp.SoundScan, args.InteractEvent.User, target);
|
||||
_popup.PopupCursor(Loc.GetString("log-probe-scan", ("device", target)), args.InteractEvent.User);
|
||||
|
||||
ent.Comp.EntityName = Name(target);
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Shared.CartridgeLoader;
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.CartridgeLoader.Cartridges;
|
||||
|
||||
8
Content.Server/Charges/ChargesSystem.cs
Normal file
8
Content.Server/Charges/ChargesSystem.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.Charges.Systems;
|
||||
|
||||
namespace Content.Server.Charges;
|
||||
|
||||
public sealed class ChargesSystem : SharedChargesSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using Content.Server.Charges.Systems;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server.Charges.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Something with limited charges that can be recharged automatically.
|
||||
/// Requires LimitedChargesComponent to function.
|
||||
/// </summary>
|
||||
// TODO: no reason this cant be predicted and server system deleted
|
||||
[RegisterComponent, AutoGenerateComponentPause]
|
||||
[Access(typeof(ChargesSystem))]
|
||||
public sealed partial class AutoRechargeComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The time it takes to regain a single charge
|
||||
/// </summary>
|
||||
[DataField("rechargeDuration"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan RechargeDuration = TimeSpan.FromSeconds(90);
|
||||
|
||||
/// <summary>
|
||||
/// The time when the next charge will be added
|
||||
/// </summary>
|
||||
[DataField("nextChargeTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField]
|
||||
public TimeSpan NextChargeTime;
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using Content.Server.Charges.Components;
|
||||
using Content.Shared.Charges.Components;
|
||||
using Content.Shared.Charges.Systems;
|
||||
using Content.Shared.Examine;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Charges.Systems;
|
||||
|
||||
public sealed class ChargesSystem : SharedChargesSystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<LimitedChargesComponent, AutoRechargeComponent>();
|
||||
while (query.MoveNext(out var uid, out var charges, out var recharge))
|
||||
{
|
||||
if (charges.Charges == charges.MaxCharges || _timing.CurTime < recharge.NextChargeTime)
|
||||
continue;
|
||||
|
||||
AddCharges(uid, 1, charges);
|
||||
recharge.NextChargeTime = _timing.CurTime + recharge.RechargeDuration;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnExamine(EntityUid uid, LimitedChargesComponent comp, ExaminedEvent args)
|
||||
{
|
||||
base.OnExamine(uid, comp, args);
|
||||
|
||||
// only show the recharging info if it's not full
|
||||
if (!args.IsInDetailsRange || comp.Charges == comp.MaxCharges || !TryComp<AutoRechargeComponent>(uid, out var recharge))
|
||||
return;
|
||||
|
||||
var timeRemaining = Math.Round((recharge.NextChargeTime - _timing.CurTime).TotalSeconds);
|
||||
args.PushMarkup(Loc.GetString("limited-charges-recharging", ("seconds", timeRemaining)));
|
||||
}
|
||||
|
||||
public override void AddCharges(EntityUid uid, int change, LimitedChargesComponent? comp = null)
|
||||
{
|
||||
if (!Query.Resolve(uid, ref comp, false))
|
||||
return;
|
||||
|
||||
var startRecharge = comp.Charges == comp.MaxCharges;
|
||||
base.AddCharges(uid, change, comp);
|
||||
|
||||
// if a charge was just used from full, start the recharge timer
|
||||
// TODO: probably make this an event instead of having le server system that just does this
|
||||
if (change < 0 && startRecharge && TryComp<AutoRechargeComponent>(uid, out var recharge))
|
||||
recharge.NextChargeTime = _timing.CurTime + recharge.RechargeDuration;
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,7 @@ internal sealed partial class ChatManager : IChatManager
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly PlayerRateLimitManager _rateLimitManager = default!;
|
||||
[Dependency] private readonly ICP14SponsorManager _sponsor = default!; //CP14 OCC color
|
||||
[Dependency] private readonly ISharedPlayerManager _player = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum length a player-sent message can be sent
|
||||
@@ -182,7 +183,12 @@ internal sealed partial class ChatManager : IChatManager
|
||||
var adminSystem = _entityManager.System<AdminSystem>();
|
||||
var antag = mind.UserId != null && (adminSystem.GetCachedPlayerInfo(mind.UserId.Value)?.Antag ?? false);
|
||||
|
||||
SendAdminAlert($"{mind.Session?.Name}{(antag ? " (ANTAG)" : "")} {message}");
|
||||
// We shouldn't be repeating this but I don't want to touch any more chat code than necessary
|
||||
var playerName = mind.UserId is { } userId && _player.TryGetSessionById(userId, out var session)
|
||||
? session.Name
|
||||
: "Unknown";
|
||||
|
||||
SendAdminAlert($"{playerName}{(antag ? " (ANTAG)" : "")} {message}");
|
||||
}
|
||||
|
||||
public void SendHookOOC(string sender, string message)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Chemistry.Components
|
||||
{
|
||||
@@ -7,11 +7,31 @@ namespace Content.Server.Chemistry.Components
|
||||
{
|
||||
public const string SolutionName = "vapor";
|
||||
|
||||
[DataField("transferAmount")]
|
||||
public FixedPoint2 TransferAmount = FixedPoint2.New(0.5);
|
||||
/// <summary>
|
||||
/// Stores data on the previously reacted tile. We only want to do reaction checks once per tile.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TileRef? PreviousTileRef;
|
||||
|
||||
public float ReactTimer;
|
||||
[DataField("active")]
|
||||
/// <summary>
|
||||
/// Percentage of the reagent that is reacted with the TileReaction.
|
||||
/// <example>
|
||||
/// 0.5 = 50% of the reagent is reacted.
|
||||
/// </example>
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float TransferAmountPercentage;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum amount of the reagent that will be reacted with the TileReaction.
|
||||
/// We do this to prevent floating point issues. A reagent with a low percentage transfer amount will
|
||||
/// transfer 0.01~ forever and never get deleted.
|
||||
/// <remarks>Defaults to 0.05 if not defined, a good general value.</remarks>
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float MinimumTransferAmount = 0.05f;
|
||||
|
||||
[DataField]
|
||||
public bool Active;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Server.Chemistry.Components;
|
||||
using Content.Server.Labels;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Storage.EntitySystems;
|
||||
using Content.Shared.Administration.Logs;
|
||||
@@ -10,6 +9,7 @@ using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Labels.EntitySystems;
|
||||
using Content.Shared.Storage;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Audio;
|
||||
|
||||
@@ -30,8 +30,6 @@ namespace Content.Server.Chemistry.EntitySystems
|
||||
[Dependency] private readonly ReactiveSystem _reactive = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
|
||||
private const float ReactTime = 0.125f;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -50,13 +48,19 @@ namespace Content.Server.Chemistry.EntitySystems
|
||||
}
|
||||
|
||||
// Check for collision with a impassable object (e.g. wall) and stop
|
||||
if ((args.OtherFixture.CollisionLayer & (int) CollisionGroup.Impassable) != 0 && args.OtherFixture.Hard)
|
||||
if ((args.OtherFixture.CollisionLayer & (int)CollisionGroup.Impassable) != 0 && args.OtherFixture.Hard)
|
||||
{
|
||||
EntityManager.QueueDeleteEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
public void Start(Entity<VaporComponent> vapor, TransformComponent vaporXform, Vector2 dir, float speed, MapCoordinates target, float aliveTime, EntityUid? user = null)
|
||||
public void Start(Entity<VaporComponent> vapor,
|
||||
TransformComponent vaporXform,
|
||||
Vector2 dir,
|
||||
float speed,
|
||||
MapCoordinates target,
|
||||
float aliveTime,
|
||||
EntityUid? user = null)
|
||||
{
|
||||
vapor.Comp.Active = true;
|
||||
var despawn = EnsureComp<TimedDespawnComponent>(vapor);
|
||||
@@ -83,7 +87,9 @@ namespace Content.Server.Chemistry.EntitySystems
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_solutionContainerSystem.TryGetSolution(vapor.Owner, VaporComponent.SolutionName, out var vaporSolution))
|
||||
if (!_solutionContainerSystem.TryGetSolution(vapor.Owner,
|
||||
VaporComponent.SolutionName,
|
||||
out var vaporSolution))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -93,53 +99,71 @@ namespace Content.Server.Chemistry.EntitySystems
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
// Enumerate over all VaporComponents
|
||||
var query = EntityQueryEnumerator<VaporComponent, SolutionContainerManagerComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var uid, out var vaporComp, out var container, out var xform))
|
||||
{
|
||||
foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions((uid, container)))
|
||||
// Return early if we're not active
|
||||
if (!vaporComp.Active)
|
||||
continue;
|
||||
|
||||
// Get the current location of the vapor entity first
|
||||
if (TryComp(xform.GridUid, out MapGridComponent? gridComp))
|
||||
{
|
||||
Update(frameTime, (uid, vaporComp), soln, xform);
|
||||
}
|
||||
}
|
||||
}
|
||||
var tile = _map.GetTileRef(xform.GridUid.Value, gridComp, xform.Coordinates);
|
||||
|
||||
private void Update(float frameTime, Entity<VaporComponent> ent, Entity<SolutionComponent> soln, TransformComponent xform)
|
||||
{
|
||||
var (entity, vapor) = ent;
|
||||
if (!vapor.Active)
|
||||
return;
|
||||
// Check if the tile is a tile we've reacted with previously. If so, skip it.
|
||||
// If we have no previous tile reference, we don't return so we can save one.
|
||||
if (vaporComp.PreviousTileRef != null && tile == vaporComp.PreviousTileRef)
|
||||
continue;
|
||||
|
||||
vapor.ReactTimer += frameTime;
|
||||
|
||||
var contents = soln.Comp.Solution;
|
||||
if (vapor.ReactTimer >= ReactTime && TryComp(xform.GridUid, out MapGridComponent? gridComp))
|
||||
{
|
||||
vapor.ReactTimer = 0;
|
||||
|
||||
var tile = _map.GetTileRef(xform.GridUid.Value, gridComp, xform.Coordinates);
|
||||
foreach (var reagentQuantity in contents.Contents.ToArray())
|
||||
{
|
||||
if (reagentQuantity.Quantity == FixedPoint2.Zero) continue;
|
||||
var reagent = _protoManager.Index<ReagentPrototype>(reagentQuantity.Reagent.Prototype);
|
||||
|
||||
var reaction =
|
||||
reagent.ReactionTile(tile, (reagentQuantity.Quantity / vapor.TransferAmount) * 0.25f, EntityManager, reagentQuantity.Reagent.Data);
|
||||
|
||||
if (reaction > reagentQuantity.Quantity)
|
||||
// Enumerate over all the reagents in the vapor entity solution
|
||||
foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions((uid, container)))
|
||||
{
|
||||
Log.Error($"Tried to tile react more than we have for reagent {reagentQuantity}. Found {reaction} and we only have {reagentQuantity.Quantity}");
|
||||
reaction = reagentQuantity.Quantity;
|
||||
// Iterate over the reagents in the solution
|
||||
// Reason: Each reagent in our solution may have a unique TileReaction
|
||||
// In this instance, we check individually for each reagent's TileReaction
|
||||
// This is not doing chemical reactions!
|
||||
var contents = soln.Comp.Solution;
|
||||
foreach (var reagentQuantity in contents.Contents.ToArray())
|
||||
{
|
||||
// Check if the reagent is empty
|
||||
if (reagentQuantity.Quantity == FixedPoint2.Zero)
|
||||
continue;
|
||||
|
||||
var reagent = _protoManager.Index<ReagentPrototype>(reagentQuantity.Reagent.Prototype);
|
||||
|
||||
// Limit the reaction amount to a minimum value to ensure no floating point funnies.
|
||||
// Ex: A solution with a low percentage transfer amount will slowly approach 0.01... and never get deleted
|
||||
var clampedAmount = Math.Max(
|
||||
(float)reagentQuantity.Quantity * vaporComp.TransferAmountPercentage,
|
||||
vaporComp.MinimumTransferAmount);
|
||||
|
||||
// Preform the reagent's TileReaction
|
||||
var reaction =
|
||||
reagent.ReactionTile(tile,
|
||||
clampedAmount,
|
||||
EntityManager,
|
||||
reagentQuantity.Reagent.Data);
|
||||
|
||||
if (reaction > reagentQuantity.Quantity)
|
||||
reaction = reagentQuantity.Quantity;
|
||||
|
||||
_solutionContainerSystem.RemoveReagent(soln, reagentQuantity.Reagent, reaction);
|
||||
}
|
||||
|
||||
// Delete the vapor entity if it has no contents
|
||||
if (contents.Volume == 0)
|
||||
EntityManager.QueueDeleteEntity(uid);
|
||||
|
||||
}
|
||||
|
||||
_solutionContainerSystem.RemoveReagent(soln, reagentQuantity.Reagent, reaction);
|
||||
// Set the previous tile reference to the current tile
|
||||
vaporComp.PreviousTileRef = tile;
|
||||
}
|
||||
}
|
||||
|
||||
if (contents.Volume == 0)
|
||||
{
|
||||
// Delete this
|
||||
EntityManager.QueueDeleteEntity(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Content.Server.Chemistry.TileReactions
|
||||
List<ReagentData>? data)
|
||||
{
|
||||
var spillSystem = entityManager.System<PuddleSystem>();
|
||||
if (reactVolume < 5 || !spillSystem.TryGetPuddle(tile, out _))
|
||||
if (!spillSystem.TryGetPuddle(tile, out _))
|
||||
return FixedPoint2.Zero;
|
||||
|
||||
return spillSystem.TrySpillAt(tile, new Solution(reagent.ID, reactVolume, data), out _, sound: false, tileReact: false)
|
||||
|
||||
@@ -3,11 +3,6 @@ using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Reaction;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Slippery;
|
||||
using Content.Shared.StepTrigger.Components;
|
||||
using Content.Shared.StepTrigger.Systems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
@@ -17,44 +12,17 @@ namespace Content.Server.Chemistry.TileReactions
|
||||
[DataDefinition]
|
||||
public sealed partial class SpillTileReaction : ITileReaction
|
||||
{
|
||||
[DataField("launchForwardsMultiplier")] public float LaunchForwardsMultiplier = 1;
|
||||
[DataField("requiredSlipSpeed")] public float RequiredSlipSpeed = 6;
|
||||
[DataField("paralyzeTime")] public float ParalyzeTime = 1;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="SlipperyComponent.SuperSlippery"/>
|
||||
/// </summary>
|
||||
[DataField("superSlippery")] public bool SuperSlippery;
|
||||
|
||||
public FixedPoint2 TileReact(TileRef tile,
|
||||
ReagentPrototype reagent,
|
||||
FixedPoint2 reactVolume,
|
||||
IEntityManager entityManager,
|
||||
List<ReagentData>? data)
|
||||
{
|
||||
if (reactVolume < 5)
|
||||
return FixedPoint2.Zero;
|
||||
var spillSystem = entityManager.System<PuddleSystem>();
|
||||
|
||||
if (entityManager.EntitySysManager.GetEntitySystem<PuddleSystem>()
|
||||
.TrySpillAt(tile, new Solution(reagent.ID, reactVolume, data), out var puddleUid, false, false))
|
||||
{
|
||||
var slippery = entityManager.EnsureComponent<SlipperyComponent>(puddleUid);
|
||||
slippery.LaunchForwardsMultiplier = LaunchForwardsMultiplier;
|
||||
slippery.ParalyzeTime = ParalyzeTime;
|
||||
slippery.SuperSlippery = SuperSlippery;
|
||||
entityManager.Dirty(puddleUid, slippery);
|
||||
|
||||
var step = entityManager.EnsureComponent<StepTriggerComponent>(puddleUid);
|
||||
entityManager.EntitySysManager.GetEntitySystem<StepTriggerSystem>().SetRequiredTriggerSpeed(puddleUid, RequiredSlipSpeed, step);
|
||||
|
||||
var slow = entityManager.EnsureComponent<SpeedModifierContactsComponent>(puddleUid);
|
||||
var speedModifier = 1 - reagent.Viscosity;
|
||||
entityManager.EntitySysManager.GetEntitySystem<SpeedModifierContactsSystem>().ChangeModifiers(puddleUid, speedModifier, slow);
|
||||
|
||||
return reactVolume;
|
||||
}
|
||||
|
||||
return FixedPoint2.Zero;
|
||||
return spillSystem.TrySpillAt(tile, new Solution(reagent.ID, reactVolume, data), out _, sound: false, tileReact: false)
|
||||
? reactVolume
|
||||
: FixedPoint2.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ namespace Content.Server.Cloning
|
||||
if (!_mindSystem.TryGetMind(body.Value, out var mindId, out var mind))
|
||||
return;
|
||||
|
||||
if (mind.UserId.HasValue == false || mind.Session == null)
|
||||
if (mind.UserId.HasValue == false || !_playerManager.ValidSessionId(mind.UserId.Value))
|
||||
return;
|
||||
|
||||
if (_cloningPodSystem.TryCloning(cloningPodUid, body.Value, (mindId, mind), cloningPod, scannerComp.CloningFailChanceMultiplier))
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Content.Server.Cloning;
|
||||
public sealed partial class CloningSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedStackSystem _stack = default!;
|
||||
[Dependency] private readonly SharedLabelSystem _label = default!;
|
||||
[Dependency] private readonly LabelSystem _label = default!;
|
||||
[Dependency] private readonly ForensicsSystem _forensics = default!;
|
||||
[Dependency] private readonly PaperSystem _paper = default!;
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
namespace Content.Server.CombatMode.Disarm
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies a malus to disarm attempts against this item.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class DisarmMalusComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// So, disarm chances are a % chance represented as a value between 0 and 1.
|
||||
/// This default would be a 30% penalty to that.
|
||||
/// </summary>
|
||||
[DataField("malus")]
|
||||
public float Malus = 0.3f;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.AlertLevel;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.RoundEnd;
|
||||
using Content.Server.Screens.Components;
|
||||
@@ -16,6 +14,7 @@ using Content.Shared.Chat;
|
||||
using Content.Shared.Communications;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
@@ -1,91 +1,8 @@
|
||||
using Content.Shared.Configurable;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Tools.Components;
|
||||
using Content.Shared.Tools.Systems;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
using static Content.Shared.Configurable.ConfigurationComponent;
|
||||
|
||||
namespace Content.Server.Configurable;
|
||||
|
||||
public sealed class ConfigurationSystem : EntitySystem
|
||||
public sealed class ConfigurationSystem : SharedConfigurationSystem
|
||||
{
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ConfigurationComponent, ConfigurationUpdatedMessage>(OnUpdate);
|
||||
SubscribeLocalEvent<ConfigurationComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<ConfigurationComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
SubscribeLocalEvent<ConfigurationComponent, ContainerIsInsertingAttemptEvent>(OnInsert);
|
||||
}
|
||||
|
||||
private void OnInteractUsing(EntityUid uid, ConfigurationComponent component, InteractUsingEvent args)
|
||||
{
|
||||
// TODO use activatable ui system
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (!_toolSystem.HasQuality(args.Used, component.QualityNeeded))
|
||||
return;
|
||||
|
||||
args.Handled = _uiSystem.TryOpenUi(uid, ConfigurationUiKey.Key, args.User);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, ConfigurationComponent component, ComponentStartup args)
|
||||
{
|
||||
UpdateUi(uid, component);
|
||||
}
|
||||
|
||||
private void UpdateUi(EntityUid uid, ConfigurationComponent component)
|
||||
{
|
||||
if (_uiSystem.HasUi(uid, ConfigurationUiKey.Key))
|
||||
_uiSystem.SetUiState(uid, ConfigurationUiKey.Key, new ConfigurationBoundUserInterfaceState(component.Config));
|
||||
}
|
||||
|
||||
private void OnUpdate(EntityUid uid, ConfigurationComponent component, ConfigurationUpdatedMessage args)
|
||||
{
|
||||
foreach (var key in component.Config.Keys)
|
||||
{
|
||||
var value = args.Config.GetValueOrDefault(key);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(value) || component.Validation != null && !component.Validation.IsMatch(value))
|
||||
continue;
|
||||
|
||||
component.Config[key] = value;
|
||||
}
|
||||
|
||||
UpdateUi(uid, component);
|
||||
|
||||
var updatedEvent = new ConfigurationUpdatedEvent(component);
|
||||
RaiseLocalEvent(uid, updatedEvent, false);
|
||||
|
||||
// TODO support float (spinbox) and enum (drop-down) configurations
|
||||
// TODO support verbs.
|
||||
}
|
||||
|
||||
private void OnInsert(EntityUid uid, ConfigurationComponent component, ContainerIsInsertingAttemptEvent args)
|
||||
{
|
||||
if (!_toolSystem.HasQuality(args.EntityUid, component.QualityNeeded))
|
||||
return;
|
||||
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sent when configuration values got changes
|
||||
/// </summary>
|
||||
public sealed class ConfigurationUpdatedEvent : EntityEventArgs
|
||||
{
|
||||
public ConfigurationComponent Configuration;
|
||||
|
||||
public ConfigurationUpdatedEvent(ConfigurationComponent configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,8 @@ using Content.Server.Hands.Systems;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Hands.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Construction.Completions
|
||||
{
|
||||
@@ -31,14 +28,14 @@ namespace Content.Server.Construction.Completions
|
||||
if (!entityManager.TryGetComponent(uid, out ContainerManagerComponent? containerManager))
|
||||
return;
|
||||
|
||||
var containerSys = entityManager.EntitySysManager.GetEntitySystem<ContainerSystem>();
|
||||
var containerSys = entityManager.EntitySysManager.GetEntitySystem<SharedContainerSystem>();
|
||||
var handSys = entityManager.EntitySysManager.GetEntitySystem<HandsSystem>();
|
||||
var transformSys = entityManager.EntitySysManager.GetEntitySystem<TransformSystem>();
|
||||
|
||||
HandsComponent? hands = null;
|
||||
var pickup = Pickup && entityManager.TryGetComponent(userUid, out hands);
|
||||
|
||||
foreach (var container in containerManager.GetAllContainers())
|
||||
foreach (var container in containerSys.GetAllContainers(uid))
|
||||
{
|
||||
foreach (var ent in containerSys.EmptyContainer(container, true, reparent: !pickup))
|
||||
{
|
||||
|
||||
@@ -21,10 +21,11 @@ namespace Content.Server.Construction.Completions
|
||||
|
||||
public void PerformAction(EntityUid uid, EntityUid? userUid, IEntityManager entityManager)
|
||||
{
|
||||
if (!entityManager.TryGetComponent(uid, out ContainerManagerComponent? containerManager) ||
|
||||
!containerManager.TryGetContainer(Container, out var container)) return;
|
||||
var containerSys = entityManager.EntitySysManager.GetEntitySystem<SharedContainerSystem>();
|
||||
|
||||
if (!entityManager.TryGetComponent(uid, out ContainerManagerComponent? containerManager) ||
|
||||
!containerSys.TryGetContainer(uid, Container, out var container, containerManager)) return;
|
||||
|
||||
var containerSys = entityManager.EntitySysManager.GetEntitySystem<ContainerSystem>();
|
||||
var handSys = entityManager.EntitySysManager.GetEntitySystem<HandsSystem>();
|
||||
|
||||
HandsComponent? hands = null;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Construction;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.Construction.Completions
|
||||
@@ -12,7 +12,17 @@ namespace Content.Server.Construction.Completions
|
||||
public void PerformAction(EntityUid uid, EntityUid? userUid, IEntityManager entityManager)
|
||||
{
|
||||
var transform = entityManager.GetComponent<TransformComponent>(uid);
|
||||
transform.Anchored = Value;
|
||||
|
||||
if (transform.Anchored == Value)
|
||||
return;
|
||||
|
||||
var sys = entityManager.System<SharedTransformSystem>();
|
||||
|
||||
if (Value)
|
||||
sys.AnchorEntity(uid, transform);
|
||||
else
|
||||
sys.Unanchor(uid, transform);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Content.Server.Construction.Completions
|
||||
var transform = entityManager.GetComponent<TransformComponent>(uid);
|
||||
|
||||
if (!transform.Anchored)
|
||||
transform.Coordinates = transform.Coordinates.SnapToGrid(entityManager);
|
||||
entityManager.System<SharedTransformSystem>().SetCoordinates(uid, transform.Coordinates.SnapToGrid(entityManager));
|
||||
|
||||
if (SouthRotation)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Examine;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Containers;
|
||||
@@ -39,8 +39,9 @@ namespace Content.Server.Construction.Conditions
|
||||
|
||||
var entity = args.Examined;
|
||||
|
||||
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent(entity, out ContainerManagerComponent? containerManager) ||
|
||||
!containerManager.TryGetContainer(Container, out var container)) return false;
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
if (!entityManager.TryGetComponent(entity, out ContainerManagerComponent? containerManager) ||
|
||||
!entityManager.System<SharedContainerSystem>().TryGetContainer(entity, Container, out var container, containerManager)) return false;
|
||||
|
||||
if (container.ContainedEntities.Count == 0)
|
||||
return false;
|
||||
|
||||
@@ -32,8 +32,9 @@ namespace Content.Server.Construction.Conditions
|
||||
|
||||
var entity = args.Examined;
|
||||
|
||||
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent(entity, out ContainerManagerComponent? containerManager) ||
|
||||
!containerManager.TryGetContainer(Container, out var container)) return false;
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
if (!entityManager.TryGetComponent(entity, out ContainerManagerComponent? containerManager) ||
|
||||
!entityManager.System<SharedContainerSystem>().TryGetContainer(entity, Container, out var container, containerManager)) return false;
|
||||
|
||||
if (container.ContainedEntities.Count != 0)
|
||||
return false;
|
||||
|
||||
@@ -66,10 +66,10 @@ namespace Content.Server.Construction
|
||||
if (!Resolve(uid, ref construction, false))
|
||||
return null;
|
||||
|
||||
if (construction.Node is not {} nodeIdentifier)
|
||||
if (construction.Node is not { } nodeIdentifier)
|
||||
return null;
|
||||
|
||||
return GetCurrentGraph(uid, construction) is not {} graph ? null : GetNodeFromGraph(graph, nodeIdentifier);
|
||||
return GetCurrentGraph(uid, construction) is not { } graph ? null : GetNodeFromGraph(graph, nodeIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -85,10 +85,10 @@ namespace Content.Server.Construction
|
||||
if (!Resolve(uid, ref construction, false))
|
||||
return null;
|
||||
|
||||
if (construction.EdgeIndex is not {} edgeIndex)
|
||||
if (construction.EdgeIndex is not { } edgeIndex)
|
||||
return null;
|
||||
|
||||
return GetCurrentNode(uid, construction) is not {} node ? null : GetEdgeFromNode(node, edgeIndex);
|
||||
return GetCurrentNode(uid, construction) is not { } node ? null : GetEdgeFromNode(node, edgeIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -102,7 +102,7 @@ namespace Content.Server.Construction
|
||||
if (GetCurrentNode(uid, construction) is not { } node)
|
||||
return (null, null);
|
||||
|
||||
if (construction.EdgeIndex is not {} edgeIndex)
|
||||
if (construction.EdgeIndex is not { } edgeIndex)
|
||||
return (node, null);
|
||||
|
||||
return (node, GetEdgeFromNode(node, edgeIndex));
|
||||
@@ -121,7 +121,7 @@ namespace Content.Server.Construction
|
||||
if (!Resolve(uid, ref construction, false))
|
||||
return null;
|
||||
|
||||
if (GetCurrentEdge(uid, construction) is not {} edge)
|
||||
if (GetCurrentEdge(uid, construction) is not { } edge)
|
||||
return null;
|
||||
|
||||
return GetStepFromEdge(edge, construction.StepIndex);
|
||||
@@ -141,10 +141,10 @@ namespace Content.Server.Construction
|
||||
if (!Resolve(uid, ref construction))
|
||||
return null;
|
||||
|
||||
if (construction.TargetNode is not {} targetNodeId)
|
||||
if (construction.TargetNode is not { } targetNodeId)
|
||||
return null;
|
||||
|
||||
if (GetCurrentGraph(uid, construction) is not {} graph)
|
||||
if (GetCurrentGraph(uid, construction) is not { } graph)
|
||||
return null;
|
||||
|
||||
return GetNodeFromGraph(graph, targetNodeId);
|
||||
@@ -165,10 +165,10 @@ namespace Content.Server.Construction
|
||||
if (!Resolve(uid, ref construction))
|
||||
return null;
|
||||
|
||||
if (construction.TargetEdgeIndex is not {} targetEdgeIndex)
|
||||
if (construction.TargetEdgeIndex is not { } targetEdgeIndex)
|
||||
return null;
|
||||
|
||||
if (GetCurrentNode(uid, construction) is not {} node)
|
||||
if (GetCurrentNode(uid, construction) is not { } node)
|
||||
return null;
|
||||
|
||||
return GetEdgeFromNode(node, targetEdgeIndex);
|
||||
@@ -245,8 +245,8 @@ namespace Content.Server.Construction
|
||||
if (!Resolve(uid, ref construction))
|
||||
return false;
|
||||
|
||||
if (GetCurrentGraph(uid, construction) is not {} graph
|
||||
|| GetNodeFromGraph(graph, id) is not {} node)
|
||||
if (GetCurrentGraph(uid, construction) is not { } graph
|
||||
|| GetNodeFromGraph(graph, id) is not { } node)
|
||||
return false;
|
||||
|
||||
var oldNode = construction.Node;
|
||||
@@ -257,11 +257,11 @@ namespace Content.Server.Construction
|
||||
$"{ToPrettyString(userUid.Value):player} changed {ToPrettyString(uid):entity}'s node from \"{oldNode}\" to \"{id}\"");
|
||||
|
||||
// ChangeEntity will handle the pathfinding update.
|
||||
if (node.Entity.GetId(uid, userUid, new(EntityManager)) is {} newEntity
|
||||
if (node.Entity.GetId(uid, userUid, new(EntityManager)) is { } newEntity
|
||||
&& ChangeEntity(uid, userUid, newEntity, construction) != null)
|
||||
return true;
|
||||
|
||||
if(performActions)
|
||||
if (performActions)
|
||||
PerformActions(uid, userUid, node.Actions);
|
||||
|
||||
// An action might have deleted the entity... Account for this.
|
||||
@@ -347,7 +347,7 @@ namespace Content.Server.Construction
|
||||
|
||||
// Retain the target node if an entity change happens in response to deconstruction;
|
||||
// in that case, we must continue to move towards the start node.
|
||||
if (construction.TargetNode is {} targetNode)
|
||||
if (construction.TargetNode is { } targetNode)
|
||||
SetPathfindingTarget(newUid, targetNode, newConstruction);
|
||||
}
|
||||
|
||||
@@ -358,7 +358,7 @@ namespace Content.Server.Construction
|
||||
}
|
||||
|
||||
if (newConstruction.InteractionQueue.Count > 0 && _queuedUpdates.Add(newUid))
|
||||
_constructionUpdateQueue.Enqueue(newUid);
|
||||
_constructionUpdateQueue.Enqueue(newUid);
|
||||
|
||||
// Transform transferring.
|
||||
var newTransform = Transform(newUid);
|
||||
@@ -430,7 +430,7 @@ namespace Content.Server.Construction
|
||||
if (!PrototypeManager.TryIndex<ConstructionGraphPrototype>(graphId, out var graph))
|
||||
return false;
|
||||
|
||||
if(GetNodeFromGraph(graph, nodeId) is not {})
|
||||
if (GetNodeFromGraph(graph, nodeId) is not { })
|
||||
return false;
|
||||
|
||||
construction.Graph = graphId;
|
||||
|
||||
@@ -509,7 +509,7 @@ namespace Content.Server.Construction
|
||||
return;
|
||||
}
|
||||
|
||||
var mapPos = location.ToMap(EntityManager, _transformSystem);
|
||||
var mapPos = _transformSystem.ToMapCoordinates(location);
|
||||
var predicate = GetPredicate(constructionPrototype.CanBuildInImpassable, mapPos);
|
||||
|
||||
if (!_interactionSystem.InRangeUnobstructed(user, mapPos, predicate: predicate))
|
||||
|
||||
@@ -33,12 +33,14 @@ namespace Content.Server.Containers
|
||||
|
||||
private void OnDeconstruct(EntityUid uid, EmptyOnMachineDeconstructComponent component, MachineDeconstructedEvent ev)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent<ContainerManagerComponent>(uid, out var mComp))
|
||||
if (!TryComp<ContainerManagerComponent>(uid, out var mComp))
|
||||
return;
|
||||
var baseCoords = EntityManager.GetComponent<TransformComponent>(uid).Coordinates;
|
||||
|
||||
var baseCoords = Transform(uid).Coordinates;
|
||||
|
||||
foreach (var v in component.Containers)
|
||||
{
|
||||
if (mComp.TryGetContainer(v, out var container))
|
||||
if (_container.TryGetContainer(uid, v, out var container, mComp))
|
||||
{
|
||||
_container.EmptyContainer(container, true, baseCoords);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Shared.Containers;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Throwing;
|
||||
@@ -52,10 +53,3 @@ public sealed class ThrowInsertContainerSystem : EntitySystem
|
||||
_adminLogger.Add(LogType.Landed, LogImpact.Low, $"{ToPrettyString(args.Thrown)} thrown by {ToPrettyString(args.Component.Thrower.Value):player} landed in {ToPrettyString(ent)}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sent before the insertion is made.
|
||||
/// Allows preventing the insertion if any system on the entity should need to.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct BeforeThrowInsertEvent(EntityUid ThrownEntity, bool Cancelled = false);
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
using Content.Server.Damage.Systems;
|
||||
|
||||
namespace Content.Server.Damage.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(DamagePopupSystem))]
|
||||
public sealed partial class DamagePopupComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Bool that will be used to determine if the popup type can be changed with a left click.
|
||||
/// </summary>
|
||||
[DataField("allowTypeChange")] [ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool AllowTypeChange = false;
|
||||
/// <summary>
|
||||
/// Enum that will be used to determine the type of damage popup displayed.
|
||||
/// </summary>
|
||||
[DataField("damagePopupType")] [ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamagePopupType Type = DamagePopupType.Combined;
|
||||
}
|
||||
public enum DamagePopupType
|
||||
{
|
||||
Combined,
|
||||
Total,
|
||||
Delta,
|
||||
Hit,
|
||||
};
|
||||
@@ -1,54 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Damage.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Interaction;
|
||||
|
||||
namespace Content.Server.Damage.Systems;
|
||||
|
||||
public sealed class DamagePopupSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<DamagePopupComponent, DamageChangedEvent>(OnDamageChange);
|
||||
SubscribeLocalEvent<DamagePopupComponent, InteractHandEvent>(OnInteractHand);
|
||||
}
|
||||
|
||||
private void OnDamageChange(EntityUid uid, DamagePopupComponent component, DamageChangedEvent args)
|
||||
{
|
||||
if (args.DamageDelta != null)
|
||||
{
|
||||
var damageTotal = args.Damageable.TotalDamage;
|
||||
var damageDelta = args.DamageDelta.GetTotal();
|
||||
|
||||
var msg = component.Type switch
|
||||
{
|
||||
DamagePopupType.Delta => damageDelta.ToString(),
|
||||
DamagePopupType.Total => damageTotal.ToString(),
|
||||
DamagePopupType.Combined => damageDelta + " | " + damageTotal,
|
||||
DamagePopupType.Hit => "!",
|
||||
_ => "Invalid type",
|
||||
};
|
||||
_popupSystem.PopupEntity(msg, uid);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInteractHand(EntityUid uid, DamagePopupComponent component, InteractHandEvent args)
|
||||
{
|
||||
if (component.AllowTypeChange)
|
||||
{
|
||||
if (component.Type == Enum.GetValues(typeof(DamagePopupType)).Cast<DamagePopupType>().Last())
|
||||
{
|
||||
component.Type = Enum.GetValues(typeof(DamagePopupType)).Cast<DamagePopupType>().First();
|
||||
}
|
||||
else
|
||||
{
|
||||
component.Type = (DamagePopupType) (int) component.Type + 1;
|
||||
}
|
||||
_popupSystem.PopupEntity("Target set to type: " + component.Type.ToString(), uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -408,6 +408,7 @@ namespace Content.Server.Database
|
||||
private ServerDbBase _db = default!;
|
||||
private LoggingProvider _msLogProvider = default!;
|
||||
private ILoggerFactory _msLoggerFactory = default!;
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
private bool _synchronous;
|
||||
// When running in integration tests, we'll use a single in-memory SQLite database connection.
|
||||
@@ -423,6 +424,7 @@ namespace Content.Server.Database
|
||||
{
|
||||
builder.AddProvider(_msLogProvider);
|
||||
});
|
||||
_sawmill = _logMgr.GetSawmill("db.manager");
|
||||
|
||||
_synchronous = _cfg.GetCVar(CCVars.DatabaseSynchronous);
|
||||
|
||||
@@ -1144,7 +1146,7 @@ namespace Content.Server.Database
|
||||
Password = pass
|
||||
}.ConnectionString;
|
||||
|
||||
Logger.DebugS("db.manager", $"Using Postgres \"{host}:{port}/{db}\"");
|
||||
_sawmill.Debug($"Using Postgres \"{host}:{port}/{db}\"");
|
||||
|
||||
builder.UseNpgsql(connectionString);
|
||||
SetupLogging(builder);
|
||||
@@ -1167,12 +1169,12 @@ namespace Content.Server.Database
|
||||
if (!inMemory)
|
||||
{
|
||||
var finalPreferencesDbPath = Path.Combine(_res.UserData.RootDir!, configPreferencesDbPath);
|
||||
Logger.DebugS("db.manager", $"Using SQLite DB \"{finalPreferencesDbPath}\"");
|
||||
_sawmill.Debug($"Using SQLite DB \"{finalPreferencesDbPath}\"");
|
||||
getConnection = () => new SqliteConnection($"Data Source={finalPreferencesDbPath}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.DebugS("db.manager", "Using in-memory SQLite DB");
|
||||
_sawmill.Debug("Using in-memory SQLite DB");
|
||||
_sqliteInMemoryConnection = new SqliteConnection("Data Source=:memory:");
|
||||
// When using an in-memory DB we have to open it manually
|
||||
// so EFCore doesn't open, close and wipe it every operation.
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace Content.Server.Decals
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private readonly Dictionary<NetEntity, HashSet<Vector2i>> _dirtyChunks = new();
|
||||
private readonly Dictionary<ICommonSession, Dictionary<NetEntity, HashSet<Vector2i>>> _previousSentChunks = new();
|
||||
@@ -249,7 +250,7 @@ namespace Content.Server.Decals
|
||||
if (!coordinates.IsValid(EntityManager))
|
||||
return;
|
||||
|
||||
var gridId = coordinates.GetGridUid(EntityManager);
|
||||
var gridId = _transform.GetGrid(coordinates);
|
||||
|
||||
if (gridId == null)
|
||||
return;
|
||||
@@ -296,7 +297,7 @@ namespace Content.Server.Decals
|
||||
if (!PrototypeManager.HasIndex<DecalPrototype>(decal.Id))
|
||||
return false;
|
||||
|
||||
var gridId = coordinates.GetGridUid(EntityManager);
|
||||
var gridId = _transform.GetGrid(coordinates);
|
||||
if (!TryComp(gridId, out MapGridComponent? grid))
|
||||
return false;
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ public sealed partial class CargoDeliveryDataComponent : Component
|
||||
/// 1 delivery per X players.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float PlayerToDeliveryRatio = 7f;
|
||||
public float PlayerToDeliveryRatio = 8f;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum amount of deliveries that will spawn.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.StationRecords;
|
||||
using Content.Shared.Delivery;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
using Content.Server.StationRecords;
|
||||
using Content.Shared.EntityTable;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -16,7 +16,7 @@ public sealed partial class DeliverySystem
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly EntityTableSystem _entityTable = default!;
|
||||
[Dependency] private readonly PowerReceiverSystem _power = default!;
|
||||
[Dependency] private readonly SharedPowerReceiverSystem _power = default!;
|
||||
|
||||
private void InitializeSpawning()
|
||||
{
|
||||
@@ -28,16 +28,14 @@ public sealed partial class DeliverySystem
|
||||
ent.Comp.NextDelivery = _timing.CurTime + ent.Comp.MinDeliveryCooldown; // We want an early wave of mail so cargo doesn't have to wait
|
||||
}
|
||||
|
||||
private void SpawnDelivery(Entity<DeliverySpawnerComponent?> ent, int amount)
|
||||
protected override void SpawnDeliveries(Entity<DeliverySpawnerComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent.Owner, ref ent.Comp))
|
||||
return;
|
||||
|
||||
var coords = Transform(ent).Coordinates;
|
||||
|
||||
_audio.PlayPvs(ent.Comp.SpawnSound, ent.Owner);
|
||||
|
||||
for (int i = 0; i < amount; i++)
|
||||
for (int i = 0; i < ent.Comp.ContainedDeliveryAmount; i++)
|
||||
{
|
||||
var spawns = _entityTable.GetSpawns(ent.Comp.Table);
|
||||
|
||||
@@ -46,9 +44,12 @@ public sealed partial class DeliverySystem
|
||||
Spawn(id, coords);
|
||||
}
|
||||
}
|
||||
|
||||
ent.Comp.ContainedDeliveryAmount = 0;
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
private void SpawnStationDeliveries(Entity<CargoDeliveryDataComponent> ent)
|
||||
private void AdjustStationDeliveries(Entity<CargoDeliveryDataComponent> ent)
|
||||
{
|
||||
if (!TryComp<StationRecordsComponent>(ent, out var records))
|
||||
return;
|
||||
@@ -72,7 +73,7 @@ public sealed partial class DeliverySystem
|
||||
{
|
||||
foreach (var spawner in spawners)
|
||||
{
|
||||
SpawnDelivery(spawner, deliveryCount);
|
||||
AddDeliveriesToSpawner(spawner, deliveryCount);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -87,18 +88,18 @@ public sealed partial class DeliverySystem
|
||||
}
|
||||
for (int j = 0; j < spawners.Count; j++)
|
||||
{
|
||||
SpawnDelivery(spawners[j], amounts[j]);
|
||||
AddDeliveriesToSpawner(spawners[j], amounts[j]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private List<EntityUid> GetValidSpawners(Entity<CargoDeliveryDataComponent> ent)
|
||||
private List<Entity<DeliverySpawnerComponent>> GetValidSpawners(Entity<CargoDeliveryDataComponent> ent)
|
||||
{
|
||||
var validSpawners = new List<EntityUid>();
|
||||
var validSpawners = new List<Entity<DeliverySpawnerComponent>>();
|
||||
|
||||
var spawners = EntityQueryEnumerator<DeliverySpawnerComponent>();
|
||||
while (spawners.MoveNext(out var spawnerUid, out _))
|
||||
while (spawners.MoveNext(out var spawnerUid, out var spawnerComp))
|
||||
{
|
||||
var spawnerStation = _station.GetOwningStation(spawnerUid);
|
||||
|
||||
@@ -108,12 +109,23 @@ public sealed partial class DeliverySystem
|
||||
if (!_power.IsPowered(spawnerUid))
|
||||
continue;
|
||||
|
||||
validSpawners.Add(spawnerUid);
|
||||
if (spawnerComp.ContainedDeliveryAmount >= spawnerComp.MaxContainedDeliveryAmount)
|
||||
continue;
|
||||
|
||||
validSpawners.Add((spawnerUid, spawnerComp));
|
||||
}
|
||||
|
||||
return validSpawners;
|
||||
}
|
||||
|
||||
private void AddDeliveriesToSpawner(Entity<DeliverySpawnerComponent> ent, int amount)
|
||||
{
|
||||
ent.Comp.ContainedDeliveryAmount += Math.Clamp(amount, 0, ent.Comp.MaxContainedDeliveryAmount - ent.Comp.ContainedDeliveryAmount);
|
||||
_audio.PlayPvs(ent.Comp.SpawnSound, ent.Owner);
|
||||
UpdateDeliverySpawnerVisuals(ent, ent.Comp.ContainedDeliveryAmount);
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
private void UpdateSpawner(float frameTime)
|
||||
{
|
||||
var dataQuery = EntityQueryEnumerator<CargoDeliveryDataComponent>();
|
||||
@@ -125,7 +137,7 @@ public sealed partial class DeliverySystem
|
||||
continue;
|
||||
|
||||
deliveryData.NextDelivery += _random.Next(deliveryData.MinDeliveryCooldown, deliveryData.MaxDeliveryCooldown); // Random cooldown between min and max
|
||||
SpawnStationDeliveries((uid, deliveryData));
|
||||
AdjustStationDeliveries((uid, deliveryData));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
using Content.Server.Cargo.Components;
|
||||
using Content.Server.Cargo.Systems;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.StationRecords.Systems;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.Delivery;
|
||||
using Content.Shared.FingerprintReader;
|
||||
using Content.Shared.Labels.EntitySystems;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Delivery;
|
||||
|
||||
@@ -23,8 +26,15 @@ public sealed partial class DeliverySystem : SharedDeliverySystem
|
||||
[Dependency] private readonly StationRecordsSystem _records = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly FingerprintReaderSystem _fingerprintReader = default!;
|
||||
[Dependency] private readonly SharedLabelSystem _label = default!;
|
||||
[Dependency] private readonly LabelSystem _label = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Default reason to use if the penalization is triggered
|
||||
/// </summary>
|
||||
private static readonly LocId DefaultMessage = "delivery-penalty-default-reason";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -39,19 +49,15 @@ public sealed partial class DeliverySystem : SharedDeliverySystem
|
||||
{
|
||||
_container.EnsureContainer<Container>(ent, ent.Comp.Container);
|
||||
|
||||
var stationId = _station.GetStationInMap(Transform(ent).MapID);
|
||||
|
||||
if (stationId == null)
|
||||
if (_station.GetStationInMap(Transform(ent).MapID) is not { } stationId)
|
||||
return;
|
||||
|
||||
_records.TryGetRandomRecord<GeneralStationRecord>(stationId.Value, out var entry);
|
||||
|
||||
if (entry == null)
|
||||
if (!_records.TryGetRandomRecord<GeneralStationRecord>(stationId, out var entry))
|
||||
return;
|
||||
|
||||
ent.Comp.RecipientName = entry.Name;
|
||||
ent.Comp.RecipientJobTitle = entry.JobTitle;
|
||||
ent.Comp.RecipientStation = stationId.Value;
|
||||
ent.Comp.RecipientStation = stationId;
|
||||
|
||||
_appearance.SetData(ent, DeliveryVisuals.JobIcon, entry.JobIcon);
|
||||
|
||||
@@ -73,7 +79,74 @@ public sealed partial class DeliverySystem : SharedDeliverySystem
|
||||
if (!TryComp<StationBankAccountComponent>(ent.Comp.RecipientStation, out var account))
|
||||
return;
|
||||
|
||||
_cargo.UpdateBankAccount((ent.Comp.RecipientStation.Value, account), ent.Comp.SpesoReward);
|
||||
var stationAccountEnt = (ent.Comp.RecipientStation.Value, account);
|
||||
|
||||
var multiplier = GetDeliveryMultiplier(ent!); // Resolve so we know it's got the component
|
||||
|
||||
_cargo.UpdateBankAccount(
|
||||
stationAccountEnt,
|
||||
(int)(ent.Comp.BaseSpesoReward * multiplier),
|
||||
_cargo.CreateAccountDistribution((ent.Comp.RecipientStation.Value, account)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the penalty logic: Announcing the penalty and calculating how much to charge the designated account
|
||||
/// </summary>
|
||||
/// <param name="ent">The delivery for which to run the penalty.</param>
|
||||
/// <param name="reason">The penalty reason, displayed in front of the message.</param>
|
||||
protected override void HandlePenalty(Entity<DeliveryComponent> ent, string? reason = null)
|
||||
{
|
||||
if (!TryComp<StationBankAccountComponent>(ent.Comp.RecipientStation, out var stationAccount))
|
||||
return;
|
||||
|
||||
if (ent.Comp.WasPenalized)
|
||||
return;
|
||||
|
||||
if (!_protoMan.TryIndex(ent.Comp.PenaltyBankAccount, out var accountInfo))
|
||||
return;
|
||||
|
||||
var multiplier = GetDeliveryMultiplier(ent);
|
||||
|
||||
var localizedAccountName = Loc.GetString(accountInfo.Name);
|
||||
|
||||
reason ??= Loc.GetString(DefaultMessage);
|
||||
|
||||
var dist = new Dictionary<ProtoId<CargoAccountPrototype>, double>()
|
||||
{
|
||||
{ ent.Comp.PenaltyBankAccount, 1.0 }
|
||||
};
|
||||
|
||||
var penaltyAccountBalance = stationAccount.Accounts[ent.Comp.PenaltyBankAccount];
|
||||
var calculatedPenalty = (int)(ent.Comp.BaseSpesoPenalty * multiplier);
|
||||
|
||||
// Prevents cargo from going into negatives
|
||||
if (calculatedPenalty > penaltyAccountBalance )
|
||||
calculatedPenalty = Math.Max(0, penaltyAccountBalance);
|
||||
|
||||
_cargo.UpdateBankAccount(
|
||||
(ent.Comp.RecipientStation.Value, stationAccount),
|
||||
-calculatedPenalty,
|
||||
dist);
|
||||
|
||||
var message = Loc.GetString("delivery-penalty-message", ("reason", reason), ("spesos", calculatedPenalty), ("account", localizedAccountName.ToUpper()));
|
||||
_chat.TrySendInGameICMessage(ent, message, InGameICChatType.Speak, hideChat: true);
|
||||
|
||||
ent.Comp.WasPenalized = true;
|
||||
DirtyField(ent.Owner, ent.Comp, nameof(DeliveryComponent.WasPenalized));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gathers the total multiplier for a delivery.
|
||||
/// This is done by components having subscribed to GetDeliveryMultiplierEvent and having added onto it.
|
||||
/// </summary>
|
||||
/// <param name="ent">The delivery for which to get the multiplier.</param>
|
||||
/// <returns>Total multiplier.</returns>
|
||||
private float GetDeliveryMultiplier(Entity<DeliveryComponent> ent)
|
||||
{
|
||||
var ev = new GetDeliveryMultiplierEvent();
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
|
||||
return ev.Multiplier;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
|
||||
if (!system.EntityManager.TryGetComponent<ContainerManagerComponent>(owner, out var containerManager))
|
||||
return;
|
||||
|
||||
foreach (var container in containerManager.GetAllContainers())
|
||||
foreach (var container in system.EntityManager.System<SharedContainerSystem>().GetAllContainers(owner, containerManager))
|
||||
{
|
||||
system.ContainerSystem.EmptyContainer(container, true, system.EntityManager.GetComponent<TransformComponent>(owner).Coordinates);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using Content.Server.DeviceLinking.Components;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Shared.DeviceLinking;
|
||||
using Content.Shared.DeviceLinking.Events;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Events;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
|
||||
namespace Content.Server.DeviceLinking.Systems;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using Content.Server.DeviceLinking.Components;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.Doors.Systems;
|
||||
using Content.Shared.DeviceLinking.Events;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Doors;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Server.DeviceLinking.Components;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Shared.DeviceLinking.Events;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
|
||||
namespace Content.Server.DeviceLinking.Systems;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using Content.Server.DeviceLinking.Components;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Shared.DeviceLinking;
|
||||
using Content.Shared.DeviceLinking.Events;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
|
||||
@@ -2,6 +2,7 @@ using Content.Server.DeviceLinking.Components;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Shared.DeviceLinking;
|
||||
using Content.Shared.DeviceLinking.Events;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
|
||||
namespace Content.Server.DeviceLinking.Systems;
|
||||
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.DeviceNetwork.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
[Access(typeof(DeviceNetworkSystem), typeof(DeviceNet))]
|
||||
public sealed partial class DeviceNetworkComponent : Component
|
||||
{
|
||||
public enum DeviceNetIdDefaults
|
||||
{
|
||||
Private,
|
||||
Wired,
|
||||
Wireless,
|
||||
Apc,
|
||||
AtmosDevices,
|
||||
Reserved = 100,
|
||||
// Ids outside this enum may exist
|
||||
// This exists to let yml use nice names instead of numbers
|
||||
}
|
||||
|
||||
[DataField("deviceNetId")]
|
||||
public DeviceNetIdDefaults NetIdEnum { get; set; }
|
||||
|
||||
public int DeviceNetId => (int) NetIdEnum;
|
||||
|
||||
/// <summary>
|
||||
/// The frequency that this device is listening on.
|
||||
/// </summary>
|
||||
[DataField("receiveFrequency")]
|
||||
public uint? ReceiveFrequency;
|
||||
|
||||
/// <summary>
|
||||
/// frequency prototype. Used to select a default frequency to listen to on. Used when the map is
|
||||
/// initialized.
|
||||
/// </summary>
|
||||
[DataField("receiveFrequencyId", customTypeSerializer: typeof(PrototypeIdSerializer<DeviceFrequencyPrototype>))]
|
||||
public string? ReceiveFrequencyId;
|
||||
|
||||
/// <summary>
|
||||
/// The frequency that this device going to try transmit on.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("transmitFrequency")]
|
||||
public uint? TransmitFrequency;
|
||||
|
||||
/// <summary>
|
||||
/// frequency prototype. Used to select a default frequency to transmit on. Used when the map is
|
||||
/// initialized.
|
||||
/// </summary>
|
||||
[DataField("transmitFrequencyId", customTypeSerializer: typeof(PrototypeIdSerializer<DeviceFrequencyPrototype>))]
|
||||
public string? TransmitFrequencyId;
|
||||
|
||||
/// <summary>
|
||||
/// The address of the device, either on the network it is currently connected to or whatever address it
|
||||
/// most recently used.
|
||||
/// </summary>
|
||||
[DataField("address")]
|
||||
public string Address = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the address was customized and should be preserved across networks. If false, a randomly
|
||||
/// generated address will be created whenever this device connects to a network.
|
||||
/// </summary>
|
||||
[DataField("customAddress")]
|
||||
public bool CustomAddress = false;
|
||||
|
||||
/// <summary>
|
||||
/// Prefix to prepend to any automatically generated addresses. Helps players to identify devices. This gets
|
||||
/// localized.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("prefix")]
|
||||
public string? Prefix;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the device should listen for all device messages, regardless of the intended recipient.
|
||||
/// </summary>
|
||||
[DataField("receiveAll")]
|
||||
public bool ReceiveAll;
|
||||
|
||||
/// <summary>
|
||||
/// If the device should show its address upon an examine. Useful for devices
|
||||
/// that do not have a visible UI.
|
||||
/// </summary>
|
||||
[DataField("examinableAddress")]
|
||||
public bool ExaminableAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the device should attempt to join the network on map init.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("autoConnect")]
|
||||
public bool AutoConnect = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to send the broadcast recipients list to the sender so it can be filtered.
|
||||
/// <see cref="DeviceListSystem"/>
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("sendBroadcastAttemptEvent")]
|
||||
public bool SendBroadcastAttemptEvent = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this device's address can be saved to device-lists
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("savableAddress")]
|
||||
public bool SavableAddress = true;
|
||||
|
||||
/// <summary>
|
||||
/// A list of device-lists that this device is on.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[Access(typeof(DeviceListSystem))]
|
||||
public HashSet<EntityUid> DeviceLists = new();
|
||||
|
||||
/// <summary>
|
||||
/// A list of configurators that this device is on.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[Access(typeof(NetworkConfiguratorSystem))]
|
||||
public HashSet<EntityUid> Configurators = new();
|
||||
}
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Robust.Shared.Random;
|
||||
using static Content.Server.DeviceNetwork.Components.DeviceNetworkComponent;
|
||||
|
||||
namespace Content.Server.DeviceNetwork;
|
||||
|
||||
/// <summary>
|
||||
/// Data class for storing and retrieving information about devices connected to a device network.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This basically just makes <see cref="DeviceNetworkComponent"/> accessible via their addresses and frequencies on
|
||||
/// some network.
|
||||
/// </remarks>
|
||||
public sealed class DeviceNet
|
||||
{
|
||||
/// <summary>
|
||||
/// Devices, mapped by their "Address", which is just an int that gets converted to Hex for displaying to users.
|
||||
/// This dictionary contains all devices connected to this network, though they may not be listening to any
|
||||
/// specific frequency.
|
||||
/// </summary>
|
||||
public readonly Dictionary<string, DeviceNetworkComponent> Devices = new();
|
||||
|
||||
/// <summary>
|
||||
/// Devices listening on a given frequency.
|
||||
/// </summary>
|
||||
public readonly Dictionary<uint, HashSet<DeviceNetworkComponent>> ListeningDevices = new();
|
||||
|
||||
/// <summary>
|
||||
/// Devices listening to all packets on a given frequency, regardless of the intended recipient.
|
||||
/// </summary>
|
||||
public readonly Dictionary<uint, HashSet<DeviceNetworkComponent>> ReceiveAllDevices = new();
|
||||
|
||||
private readonly IRobustRandom _random;
|
||||
public readonly int NetId;
|
||||
|
||||
public DeviceNet(int netId, IRobustRandom random)
|
||||
{
|
||||
_random = random;
|
||||
NetId = netId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a device to the network.
|
||||
/// </summary>
|
||||
public bool Add(DeviceNetworkComponent device)
|
||||
{
|
||||
if (device.CustomAddress)
|
||||
{
|
||||
// Only add if the device's existing address is available.
|
||||
if (!Devices.TryAdd(device.Address, device))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Randomly generate a new address if the existing random one is invalid. Otherwise, keep the existing address
|
||||
if (string.IsNullOrWhiteSpace(device.Address) || Devices.ContainsKey(device.Address))
|
||||
device.Address = GenerateValidAddress(device.Prefix);
|
||||
|
||||
Devices[device.Address] = device;
|
||||
}
|
||||
|
||||
if (device.ReceiveFrequency is not uint freq)
|
||||
return true;
|
||||
|
||||
if (!ListeningDevices.TryGetValue(freq, out var devices))
|
||||
ListeningDevices[freq] = devices = new();
|
||||
|
||||
devices.Add(device);
|
||||
|
||||
if (!device.ReceiveAll)
|
||||
return true;
|
||||
|
||||
if (!ReceiveAllDevices.TryGetValue(freq, out var receiveAlldevices))
|
||||
ReceiveAllDevices[freq] = receiveAlldevices = new();
|
||||
|
||||
receiveAlldevices.Add(device);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a device from the network.
|
||||
/// </summary>
|
||||
public bool Remove(DeviceNetworkComponent device)
|
||||
{
|
||||
if (device.Address == null || !Devices.Remove(device.Address))
|
||||
return false;
|
||||
|
||||
if (device.ReceiveFrequency is not uint freq)
|
||||
return true;
|
||||
|
||||
if (ListeningDevices.TryGetValue(freq, out var listening))
|
||||
{
|
||||
listening.Remove(device);
|
||||
if (listening.Count == 0)
|
||||
ListeningDevices.Remove(freq);
|
||||
}
|
||||
|
||||
if (device.ReceiveAll && ReceiveAllDevices.TryGetValue(freq, out var receiveAll))
|
||||
{
|
||||
receiveAll.Remove(device);
|
||||
if (receiveAll.Count == 0)
|
||||
ListeningDevices.Remove(freq);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Give an existing device a new randomly generated address. Useful if the device's address prefix was updated
|
||||
/// and they want a new address to reflect that, or something like that.
|
||||
/// </summary>
|
||||
public bool RandomizeAddress(string oldAddress, string? prefix = null)
|
||||
{
|
||||
if (!Devices.Remove(oldAddress, out var device))
|
||||
return false;
|
||||
|
||||
device.Address = GenerateValidAddress(prefix ?? device.Prefix);
|
||||
device.CustomAddress = false;
|
||||
Devices[device.Address] = device;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the address of an existing device.
|
||||
/// </summary>
|
||||
public bool UpdateAddress(string oldAddress, string newAddress)
|
||||
{
|
||||
if (Devices.ContainsKey(newAddress))
|
||||
return false;
|
||||
|
||||
if (!Devices.Remove(oldAddress, out var device))
|
||||
return false;
|
||||
|
||||
device.Address = newAddress;
|
||||
device.CustomAddress = true;
|
||||
Devices[newAddress] = device;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make an existing network device listen to a new frequency.
|
||||
/// </summary>
|
||||
public bool UpdateReceiveFrequency(string address, uint? newFrequency)
|
||||
{
|
||||
if (!Devices.TryGetValue(address, out var device))
|
||||
return false;
|
||||
|
||||
if (device.ReceiveFrequency == newFrequency)
|
||||
return true;
|
||||
|
||||
if (device.ReceiveFrequency is uint freq)
|
||||
{
|
||||
if (ListeningDevices.TryGetValue(freq, out var listening))
|
||||
{
|
||||
listening.Remove(device);
|
||||
if (listening.Count == 0)
|
||||
ListeningDevices.Remove(freq);
|
||||
}
|
||||
|
||||
if (device.ReceiveAll && ReceiveAllDevices.TryGetValue(freq, out var receiveAll))
|
||||
{
|
||||
receiveAll.Remove(device);
|
||||
if (receiveAll.Count == 0)
|
||||
ListeningDevices.Remove(freq);
|
||||
}
|
||||
}
|
||||
|
||||
device.ReceiveFrequency = newFrequency;
|
||||
|
||||
if (newFrequency == null)
|
||||
return true;
|
||||
|
||||
if (!ListeningDevices.TryGetValue(newFrequency.Value, out var devices))
|
||||
ListeningDevices[newFrequency.Value] = devices = new();
|
||||
|
||||
devices.Add(device);
|
||||
|
||||
if (!device.ReceiveAll)
|
||||
return true;
|
||||
|
||||
if (!ReceiveAllDevices.TryGetValue(newFrequency.Value, out var receiveAlldevices))
|
||||
ReceiveAllDevices[newFrequency.Value] = receiveAlldevices = new();
|
||||
|
||||
receiveAlldevices.Add(device);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make an existing network device listen to a new frequency.
|
||||
/// </summary>
|
||||
public bool UpdateReceiveAll(string address, bool receiveAll)
|
||||
{
|
||||
if (!Devices.TryGetValue(address, out var device))
|
||||
return false;
|
||||
|
||||
if (device.ReceiveAll == receiveAll)
|
||||
return true;
|
||||
|
||||
device.ReceiveAll = receiveAll;
|
||||
|
||||
if (device.ReceiveFrequency is not uint freq)
|
||||
return true;
|
||||
|
||||
// remove or add to set of listening devices
|
||||
|
||||
HashSet<DeviceNetworkComponent>? devices;
|
||||
if (receiveAll)
|
||||
{
|
||||
if (!ReceiveAllDevices.TryGetValue(freq, out devices))
|
||||
ReceiveAllDevices[freq] = devices = new();
|
||||
devices.Add(device);
|
||||
}
|
||||
else if (ReceiveAllDevices.TryGetValue(freq, out devices))
|
||||
{
|
||||
devices.Remove(device);
|
||||
if (devices.Count == 0)
|
||||
ReceiveAllDevices.Remove(freq);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a valid address by randomly generating one and checking if it already exists on the network.
|
||||
/// </summary>
|
||||
private string GenerateValidAddress(string? prefix)
|
||||
{
|
||||
prefix = string.IsNullOrWhiteSpace(prefix) ? null : Loc.GetString(prefix);
|
||||
string address;
|
||||
do
|
||||
{
|
||||
var num = _random.Next();
|
||||
address = $"{prefix}{num >> 16:X4}-{num & 0xFFFF:X4}";
|
||||
}
|
||||
while (Devices.ContainsKey(address));
|
||||
|
||||
return address;
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.DeviceNetwork
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of constants to help with using device networks
|
||||
/// </summary>
|
||||
public static class DeviceNetworkConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// Used by logic gates to transmit the state of their ports
|
||||
/// </summary>
|
||||
public const string LogicState = "logic_state";
|
||||
|
||||
#region Commands
|
||||
|
||||
/// <summary>
|
||||
/// The key for command names
|
||||
/// E.g. [DeviceNetworkConstants.Command] = "ping"
|
||||
/// </summary>
|
||||
public const string Command = "command";
|
||||
|
||||
/// <summary>
|
||||
/// The command for setting a devices state
|
||||
/// E.g. to turn a light on or off
|
||||
/// </summary>
|
||||
public const string CmdSetState = "set_state";
|
||||
|
||||
/// <summary>
|
||||
/// The command for a device that just updated its state
|
||||
/// E.g. suit sensors broadcasting owners vitals state
|
||||
/// </summary>
|
||||
public const string CmdUpdatedState = "updated_state";
|
||||
|
||||
#endregion
|
||||
|
||||
#region SetState
|
||||
|
||||
/// <summary>
|
||||
/// Used with the <see cref="CmdSetState"/> command to turn a device on or off
|
||||
/// </summary>
|
||||
public const string StateEnabled = "state_enabled";
|
||||
|
||||
#endregion
|
||||
|
||||
#region DisplayHelpers
|
||||
|
||||
/// <summary>
|
||||
/// Converts the unsigned int to string and inserts a number before the last digit
|
||||
/// </summary>
|
||||
public static string FrequencyToString(this uint frequency)
|
||||
{
|
||||
var result = frequency.ToString();
|
||||
if (result.Length <= 2)
|
||||
return result + ".0";
|
||||
|
||||
return result.Insert(result.Length - 1, ".");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Either returns the localized name representation of the corresponding <see cref="DeviceNetworkComponent.DeviceNetIdDefaults"/>
|
||||
/// or converts the id to string
|
||||
/// </summary>
|
||||
public static string DeviceNetIdToLocalizedName(this int id)
|
||||
{
|
||||
|
||||
if (!Enum.IsDefined(typeof(DeviceNetworkComponent.DeviceNetIdDefaults), id))
|
||||
return id.ToString();
|
||||
|
||||
var result = ((DeviceNetworkComponent.DeviceNetIdDefaults) id).ToString();
|
||||
var resultKebab = "device-net-id-" + CaseConversion.PascalToKebab(result);
|
||||
|
||||
return !Loc.TryGetString(resultKebab, out var name) ? result : name;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using Content.Server.NodeContainer.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Power.Nodes;
|
||||
using Content.Shared.DeviceNetwork.Events;
|
||||
|
||||
namespace Content.Server.DeviceNetwork.Systems
|
||||
{
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using System.Linq;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
using Content.Shared.DeviceNetwork.Events;
|
||||
using Content.Shared.DeviceNetwork.Systems;
|
||||
using Content.Shared.Interaction;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map.Events;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user