mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-06-09 13:26:34 +02:00
Stable -> Master (#43587)
* shared...
* comment and delete empty system
* fix double purchase bug
* fix a couple radiation system bugs as a treat
* logmissing false
* [STAGING] Fix blood regeneration (#43576)
fsafsaasffas
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
* [STAGING] Revert "Force Vent Critters to Attack (#42399)" (#43577)
Revert "Force Vent Critters to Attack (#42399)"
This reverts commit 81be6f2571.
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
* asfafasasffas
---------
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
This commit is contained in:
@@ -5,7 +5,7 @@ using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client.Radiation.Systems;
|
||||
|
||||
public sealed class RadiationSystem : EntitySystem
|
||||
public sealed class RadiationSystem : SharedRadiationSystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Content.Server.Physics.Components;
|
||||
using Content.Server.Radiation.Systems;
|
||||
using Content.Server.Singularity.Components;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Anomaly.Effects;
|
||||
using Content.Shared.Anomaly.Effects.Components;
|
||||
using Content.Shared.Radiation.Components;
|
||||
|
||||
namespace Content.Server.Anomaly.Effects;
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace Content.Server.Anomaly.Effects;
|
||||
/// </summary>
|
||||
public sealed class GravityAnomalySystem : SharedGravityAnomalySystem
|
||||
{
|
||||
[Dependency] private readonly RadiationSystem _radiation = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -22,8 +24,7 @@ public sealed class GravityAnomalySystem : SharedGravityAnomalySystem
|
||||
|
||||
private void OnSeverityChanged(Entity<GravityAnomalyComponent> anomaly, ref AnomalySeverityChangedEvent args)
|
||||
{
|
||||
if (TryComp<RadiationSourceComponent>(anomaly, out var radSource))
|
||||
radSource.Intensity = anomaly.Comp.MaxRadiationIntensity * args.Severity;
|
||||
_radiation.SetIntensity(anomaly.Owner, anomaly.Comp.MaxRadiationIntensity * args.Severity);
|
||||
|
||||
if (TryComp<GravityWellComponent>(anomaly, out var gravityWell))
|
||||
{
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server.ForceAttack;
|
||||
|
||||
/// <summary>
|
||||
/// This is used to force a player-controlled mob to attack nearby enemies, preventing "friendly antag"ing.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class ForceAttackComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The next time this component will attempt to force an attack.
|
||||
/// </summary>
|
||||
[AutoPausedField]
|
||||
public TimeSpan NextAttack = TimeSpan.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// Whether an enemy is in range.
|
||||
/// </summary>
|
||||
public bool InRange = false;
|
||||
|
||||
/// <summary>
|
||||
/// The time this component will wait before forcing an attack when an enemy is in range.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan PassiveTime = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// The message displayed on forced attack
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId Message = "force-attack-component-message";
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.CombatMode;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.NPC.Components;
|
||||
using Content.Shared.NPC.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Weapons.Melee;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.ForceAttack;
|
||||
|
||||
/// <summary>
|
||||
/// This handles forcing a player-controlled mob to attack nearby enemies.
|
||||
/// </summary>
|
||||
public sealed class ForceAttackSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly SharedMeleeWeaponSystem _melee = default!;
|
||||
[Dependency] private readonly NpcFactionSystem _faction = default!;
|
||||
[Dependency] private readonly SharedCombatModeSystem _mode = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly MobStateSystem _mob = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ForceAttackComponent, MeleeAttackEvent>(OnMeleeAttack);
|
||||
}
|
||||
|
||||
private void OnMeleeAttack(Entity<ForceAttackComponent> ent, ref MeleeAttackEvent args)
|
||||
{
|
||||
ent.Comp.NextAttack = _timing.CurTime + ent.Comp.PassiveTime;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var curTime = _timing.CurTime;
|
||||
|
||||
// Query includes ActorComponent to only get mobs currently controlled by players
|
||||
var query = EntityQueryEnumerator<ForceAttackComponent, NpcFactionMemberComponent, CombatModeComponent, ActorComponent>();
|
||||
while (query.MoveNext(out var uid, out var forceComp, out var factionComp, out var modeComp, out _))
|
||||
{
|
||||
// Check if we have a weapon
|
||||
if (!_melee.TryGetWeapon(uid, out var weaponUid, out var weapon) || weapon.NextAttack > curTime)
|
||||
continue;
|
||||
|
||||
// Find a target in range that isn't critical or dead
|
||||
if (!_faction.GetNearbyHostiles((uid, factionComp), weapon.Range)
|
||||
.Where((potTarget) => !_mob.IsIncapacitated(potTarget))
|
||||
.TryFirstOrNull(out var target))
|
||||
{
|
||||
forceComp.InRange = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!forceComp.InRange) // Just entered range
|
||||
{
|
||||
forceComp.InRange = true;
|
||||
forceComp.NextAttack = curTime + forceComp.PassiveTime;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (forceComp.NextAttack > curTime)
|
||||
continue;
|
||||
|
||||
// Force mob to enter combat mode (necessary for AttemptAttack to succeed).
|
||||
_mode.SetInCombatMode(uid, true, modeComp);
|
||||
|
||||
var popupMessage = Loc.GetString(forceComp.Message);
|
||||
if (popupMessage.Length != 0)
|
||||
_popup.PopupEntity(popupMessage, uid, uid);
|
||||
|
||||
_melee.AttemptLightAttack(uid, weaponUid, weapon, target.Value, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,5 @@
|
||||
using Content.Server.Store.Components;
|
||||
using Content.Server.Store.Systems;
|
||||
using Content.Shared.Implants;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Store.Components;
|
||||
|
||||
namespace Content.Server.Implants;
|
||||
|
||||
public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem
|
||||
{
|
||||
[Dependency] private readonly StoreSystem _store = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<StoreComponent, ImplantRelayEvent<AfterInteractUsingEvent>>(OnStoreRelay);
|
||||
}
|
||||
|
||||
// TODO: This shouldn't be in the SubdermalImplantSystem
|
||||
private void OnStoreRelay(EntityUid uid, StoreComponent store, ImplantRelayEvent<AfterInteractUsingEvent> implantRelay)
|
||||
{
|
||||
var args = implantRelay.Event;
|
||||
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
// can only insert into yourself to prevent uplink checking with renault
|
||||
if (args.Target != args.User)
|
||||
return;
|
||||
|
||||
if (!TryComp<CurrencyComponent>(args.Used, out var currency))
|
||||
return;
|
||||
|
||||
// same as store code, but message is only shown to yourself
|
||||
if (!_store.TryAddCurrency((args.Used, currency), (uid, store)))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
var msg = Loc.GetString("store-currency-inserted-implant", ("used", args.Used));
|
||||
_popup.PopupEntity(msg, args.User, args.User);
|
||||
}
|
||||
}
|
||||
public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Store.Systems;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.PDA.Ringer;
|
||||
using Content.Shared.Store.Components;
|
||||
using Content.Shared.Store;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
|
||||
@@ -7,10 +7,11 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Threading;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Radiation.Systems;
|
||||
|
||||
namespace Content.Server.Radiation.Systems;
|
||||
|
||||
public sealed partial class RadiationSystem : EntitySystem
|
||||
public sealed partial class RadiationSystem : SharedRadiationSystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
@@ -20,9 +21,8 @@ public sealed partial class RadiationSystem : EntitySystem
|
||||
[Dependency] private readonly IParallelManager _parallel = default!;
|
||||
|
||||
[Dependency] private readonly EntityQuery<RadiationReceiverComponent> _receiverQuery = default!;
|
||||
private EntityQuery<RadiationBlockingContainerComponent> _blockerQuery;
|
||||
private EntityQuery<RadiationGridResistanceComponent> _resistanceQuery;
|
||||
private EntityQuery<StackComponent> _stackQuery;
|
||||
[Dependency] private EntityQuery<RadiationBlockingContainerComponent> _blockerQuery = default;
|
||||
[Dependency] private EntityQuery<RadiationGridResistanceComponent> _resistanceQuery = default;
|
||||
|
||||
private readonly B2DynamicTree<EntityUid> _sourceTree = new();
|
||||
private readonly Dictionary<EntityUid, SourceData> _sourceDataMap = new();
|
||||
@@ -36,20 +36,16 @@ public sealed partial class RadiationSystem : EntitySystem
|
||||
SubscribeCvars();
|
||||
InitRadBlocking();
|
||||
|
||||
_blockerQuery = GetEntityQuery<RadiationBlockingContainerComponent>();
|
||||
_resistanceQuery = GetEntityQuery<RadiationGridResistanceComponent>();
|
||||
_stackQuery = GetEntityQuery<StackComponent>();
|
||||
|
||||
SubscribeLocalEvent<RadiationSourceComponent, ComponentInit>(OnSourceInit);
|
||||
SubscribeLocalEvent<RadiationSourceComponent, ComponentStartup>(OnSourceStartup);
|
||||
SubscribeLocalEvent<RadiationSourceComponent, ComponentShutdown>(OnSourceShutdown);
|
||||
SubscribeLocalEvent<RadiationSourceComponent, MoveEvent>(OnSourceMove);
|
||||
SubscribeLocalEvent<RadiationSourceComponent, StackCountChangedEvent>(OnSourceStackChanged);
|
||||
|
||||
SubscribeLocalEvent<RadiationReceiverComponent, ComponentInit>(OnReceiverInit);
|
||||
SubscribeLocalEvent<RadiationReceiverComponent, ComponentStartup>(OnReceiverStartup);
|
||||
SubscribeLocalEvent<RadiationReceiverComponent, ComponentShutdown>(OnReceiverShutdown);
|
||||
}
|
||||
|
||||
private void OnSourceInit(Entity<RadiationSourceComponent> entity, ref ComponentInit args)
|
||||
private void OnSourceStartup(Entity<RadiationSourceComponent> entity, ref ComponentStartup args)
|
||||
{
|
||||
UpdateSource(entity);
|
||||
}
|
||||
@@ -78,7 +74,7 @@ public sealed partial class RadiationSystem : EntitySystem
|
||||
UpdateSource(entity);
|
||||
}
|
||||
|
||||
private void OnReceiverInit(EntityUid uid, RadiationReceiverComponent component, ComponentInit args)
|
||||
private void OnReceiverStartup(EntityUid uid, RadiationReceiverComponent component, ComponentStartup args)
|
||||
{
|
||||
_activeReceivers.Add(uid);
|
||||
}
|
||||
@@ -88,7 +84,7 @@ public sealed partial class RadiationSystem : EntitySystem
|
||||
_activeReceivers.Remove(uid);
|
||||
}
|
||||
|
||||
private void UpdateSource(Entity<RadiationSourceComponent> entity)
|
||||
protected override void UpdateSource(Entity<RadiationSourceComponent> entity)
|
||||
{
|
||||
var (uid, component) = entity;
|
||||
var xform = Transform(uid);
|
||||
|
||||
@@ -4,11 +4,14 @@ using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Store.Components;
|
||||
using Content.Shared.Weapons.Ranged.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Store.Systems;
|
||||
|
||||
public sealed partial class StoreSystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private void InitializeRefund()
|
||||
{
|
||||
SubscribeLocalEvent<StoreComponent, EntityTerminatingEvent>(OnStoreTerminating);
|
||||
|
||||
@@ -7,32 +7,25 @@ using Content.Shared.Actions;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mindshield.Components;
|
||||
using Content.Shared.NPC.Systems;
|
||||
using Content.Shared.PDA.Ringer;
|
||||
using Content.Shared.Store;
|
||||
using Content.Shared.Store.Components;
|
||||
using Content.Shared.UserInterface;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Store.Systems;
|
||||
|
||||
public sealed partial class StoreSystem
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _admin = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
[Dependency] private readonly ActionUpgradeSystem _actionUpgrade = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly StackSystem _stack = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly StackSystem _stack = default!;
|
||||
|
||||
private void InitializeUi()
|
||||
{
|
||||
@@ -66,98 +59,6 @@ public sealed partial class StoreSystem
|
||||
RaiseLocalEvent(entity.Comp.Store.Value, ev);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the store Ui open and closed
|
||||
/// </summary>
|
||||
/// <param name="user">the person doing the toggling</param>
|
||||
/// <param name="storeEnt">the store being toggled</param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="remoteAccess">The entity remotely accessing the store, if any.</param>
|
||||
/// <param name="remoteComponent">The remote access component, if any.</param>
|
||||
public void ToggleUi(EntityUid user, EntityUid storeEnt, StoreComponent? component = null, EntityUid? remoteAccess = null, RemoteStoreComponent? remoteComponent = null)
|
||||
{
|
||||
if (!Resolve(storeEnt, ref component))
|
||||
return;
|
||||
|
||||
if (remoteAccess != null && !Resolve(remoteAccess.Value, ref remoteComponent) && remoteComponent!.Store != storeEnt)
|
||||
return;
|
||||
|
||||
if (!TryComp<ActorComponent>(user, out var actor))
|
||||
return;
|
||||
|
||||
if (!_ui.TryToggleUi(remoteAccess != null ? remoteAccess.Value : storeEnt, StoreUiKey.Key, actor.PlayerSession))
|
||||
return;
|
||||
|
||||
UpdateUserInterface(user, storeEnt, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the store UI for everyone, if it's open
|
||||
/// </summary>
|
||||
public void CloseUi(EntityUid uid, StoreComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
_ui.CloseUi(uid, StoreUiKey.Key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the user interface for a store and refreshes the listings
|
||||
/// </summary>
|
||||
/// <param name="user">The person who if opening the store ui. Listings are filtered based on this.</param>
|
||||
/// <param name="store">The store entity itself</param>
|
||||
/// <param name="component">The store component being refreshed.</param>
|
||||
public void UpdateUserInterface(EntityUid? user, EntityUid store, StoreComponent? component = null)
|
||||
{
|
||||
if (!Resolve(store, ref component))
|
||||
return;
|
||||
|
||||
//this is the person who will be passed into logic for all listing filtering.
|
||||
if (user != null) //if we have no "buyer" for this update, then don't update the listings
|
||||
{
|
||||
component.LastAvailableListings = GetAvailableListings(component.AccountOwner ?? user.Value, store, component)
|
||||
.ToHashSet();
|
||||
}
|
||||
|
||||
//dictionary for all currencies, including 0 values for currencies on the whitelist
|
||||
Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> allCurrency = new();
|
||||
foreach (var supported in component.CurrencyWhitelist)
|
||||
{
|
||||
allCurrency.Add(supported, FixedPoint2.Zero);
|
||||
|
||||
if (component.Balance.TryGetValue(supported, out var value))
|
||||
allCurrency[supported] = value;
|
||||
}
|
||||
|
||||
// TODO: if multiple users are supposed to be able to interact with a single BUI & see different
|
||||
// stores/listings, this needs to use session specific BUI states.
|
||||
|
||||
// only tell operatives to lock their uplink if it can be locked
|
||||
var showFooter = HasComp<RingerUplinkComponent>(store);
|
||||
|
||||
var state = new StoreUpdateState(component.LastAvailableListings, allCurrency, showFooter, component.RefundAllowed);
|
||||
UpdateRemoteStores(store, state);
|
||||
_ui.SetUiState(store, StoreUiKey.Key, state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates any remote store connections to a specific store.
|
||||
/// </summary>
|
||||
/// <param name="store">The store being updated.</param>
|
||||
/// <param name="state">The state being applied.</param>
|
||||
public void UpdateRemoteStores(EntityUid store, StoreUpdateState state)
|
||||
{
|
||||
var query = EntityQueryEnumerator<RemoteStoreComponent, UserInterfaceComponent>();
|
||||
while (query.MoveNext(out var uid, out var remote, out var ui))
|
||||
{
|
||||
if (remote.Store != store)
|
||||
continue;
|
||||
|
||||
_ui.SetUiState((uid, ui), StoreUiKey.Key, state);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRequestUpdate(EntityUid uid, StoreComponent component, StoreRequestUpdateInterfaceMessage args)
|
||||
{
|
||||
UpdateUserInterface(args.Actor, GetEntity(args.Entity), component);
|
||||
@@ -246,7 +147,7 @@ public sealed partial class StoreSystem
|
||||
EntityUid? actionId;
|
||||
// I guess we just allow duplicate actions?
|
||||
// Allow duplicate actions and just have a single list buy for the buy-once ones.
|
||||
if (listing.ApplyToMob || !_mind.TryGetMind(buyer, out var mind, out _))
|
||||
if (listing.ApplyToMob || !Mind.TryGetMind(buyer, out var mind, out _))
|
||||
actionId = _actions.AddAction(buyer, listing.ProductAction);
|
||||
else
|
||||
actionId = _actionContainer.AddAction(mind, listing.ProductAction);
|
||||
@@ -322,7 +223,7 @@ public sealed partial class StoreSystem
|
||||
|
||||
_admin.Add(LogType.StorePurchase,
|
||||
logImpact,
|
||||
$"{ToPrettyString(buyer):player} purchased listing \"{ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listing, _proto)}\" from {ToPrettyString(uid)}{logExtraInfo}.");
|
||||
$"{ToPrettyString(buyer):player} purchased listing \"{ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listing, Proto)}\" from {ToPrettyString(uid)}{logExtraInfo}.");
|
||||
|
||||
listing.PurchaseAmount++; //track how many times something has been purchased
|
||||
if (msg.SoundSource != null && GetEntity(msg.SoundSource) != null)
|
||||
@@ -355,7 +256,7 @@ public sealed partial class StoreSystem
|
||||
return;
|
||||
|
||||
//make sure a malicious client didn't send us random shit
|
||||
if (!_proto.TryIndex<CurrencyPrototype>(msg.Currency, out var proto))
|
||||
if (!Proto.TryIndex<CurrencyPrototype>(msg.Currency, out var proto))
|
||||
return;
|
||||
|
||||
//we need an actually valid entity to spawn. This check has been done earlier, but just in case.
|
||||
|
||||
@@ -1,39 +1,23 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Store.Components;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Shared.Store;
|
||||
using Content.Shared.Store.Components;
|
||||
using Content.Shared.Store.Events;
|
||||
using Content.Shared.UserInterface;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Store.Systems;
|
||||
|
||||
public sealed partial class StoreSystem : SharedStoreSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<StoreComponent, ActivatableUIOpenAttemptEvent>(OnStoreOpenAttempt);
|
||||
SubscribeLocalEvent<CurrencyComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<StoreComponent, BeforeActivatableUIOpenEvent>(BeforeActivatableUiOpen);
|
||||
|
||||
SubscribeLocalEvent<StoreComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<StoreComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<StoreComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<StoreComponent, IntrinsicStoreActionEvent>(OnIntrinsicStoreAction);
|
||||
|
||||
SubscribeLocalEvent<RemoteStoreComponent, OpenUplinkImplantEvent>(OnImplantActivate);
|
||||
|
||||
@@ -48,8 +32,8 @@ public sealed partial class StoreSystem : SharedStoreSystem
|
||||
component.StartingMap = Transform(uid).MapUid;
|
||||
|
||||
// Add the bui key if it does not exist already (the check is needed to make sure that we don't overwrite existing InterfaceData).
|
||||
if (!_uiSystem.HasUi(uid, StoreUiKey.Key))
|
||||
_uiSystem.SetUi(uid, StoreUiKey.Key, new InterfaceData("StoreBoundUserInterface"));
|
||||
if (!UI.HasUi(uid, StoreUiKey.Key))
|
||||
UI.SetUi(uid, StoreUiKey.Key, new InterfaceData("StoreBoundUserInterface"));
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, StoreComponent component, ComponentStartup args)
|
||||
@@ -75,7 +59,7 @@ public sealed partial class StoreSystem : SharedStoreSystem
|
||||
if (!component.OwnerOnly)
|
||||
return;
|
||||
|
||||
if (!_mind.TryGetMind(args.User, out var mind, out _))
|
||||
if (!Mind.TryGetMind(args.User, out var mind, out _))
|
||||
return;
|
||||
|
||||
component.AccountOwner ??= mind;
|
||||
@@ -85,32 +69,11 @@ public sealed partial class StoreSystem : SharedStoreSystem
|
||||
return;
|
||||
|
||||
if (!args.Silent)
|
||||
_popup.PopupEntity(Loc.GetString("store-not-account-owner", ("store", uid)), uid, args.User);
|
||||
Popup.PopupEntity(Loc.GetString("store-not-account-owner", ("store", uid)), uid, args.User);
|
||||
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, CurrencyComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (args.Handled || !args.CanReach || args.Target is not { } target)
|
||||
return;
|
||||
|
||||
if (!TryGetStore(target, out var store))
|
||||
return;
|
||||
|
||||
var ev = new CurrencyInsertAttemptEvent(args.User, target, args.Used, store.Value.Comp);
|
||||
RaiseLocalEvent(target, ev);
|
||||
if (ev.Cancelled)
|
||||
return;
|
||||
|
||||
if (!TryAddCurrency((uid, component), (store.Value, store.Value.Comp)))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
var msg = Loc.GetString("store-currency-inserted", ("used", args.Used), ("target", target));
|
||||
_popup.PopupEntity(msg, target, args.User);
|
||||
}
|
||||
|
||||
private void OnImplantActivate(Entity<RemoteStoreComponent> entity, ref OpenUplinkImplantEvent args)
|
||||
{
|
||||
if (GetRemoteStore(entity.AsNullable()) is not { } store)
|
||||
@@ -118,103 +81,4 @@ public sealed partial class StoreSystem : SharedStoreSystem
|
||||
|
||||
ToggleUi(args.Performer, store, store.Comp, entity, entity.Comp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value from an entity's currency component.
|
||||
/// Scales with stacks.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this result is intended to be used with <see cref="TryAddCurrency(Robust.Shared.GameObjects.Entity{Content.Server.Store.Components.CurrencyComponent?},Robust.Shared.GameObjects.Entity{Content.Shared.Store.Components.StoreComponent?})"/>,
|
||||
/// consider using <see cref="TryAddCurrency(Robust.Shared.GameObjects.Entity{Content.Server.Store.Components.CurrencyComponent?},Robust.Shared.GameObjects.Entity{Content.Shared.Store.Components.StoreComponent?})"/> instead to ensure that the currency is consumed in the process.
|
||||
/// </remarks>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <returns>The value of the currency</returns>
|
||||
public Dictionary<string, FixedPoint2> GetCurrencyValue(EntityUid uid, CurrencyComponent component)
|
||||
{
|
||||
var amount = EntityManager.GetComponentOrNull<StackComponent>(uid)?.Count ?? 1;
|
||||
return component.Price.ToDictionary(v => v.Key, p => p.Value * amount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add a currency to a store's balance. Note that if successful, this will consume the currency in the process.
|
||||
/// </summary>
|
||||
public bool TryAddCurrency(Entity<CurrencyComponent?> currency, Entity<StoreComponent?> store)
|
||||
{
|
||||
if (!Resolve(currency.Owner, ref currency.Comp))
|
||||
return false;
|
||||
|
||||
if (!Resolve(store.Owner, ref store.Comp))
|
||||
return false;
|
||||
|
||||
var value = currency.Comp.Price;
|
||||
if (TryComp(currency.Owner, out StackComponent? stack) && stack.Count != 1)
|
||||
{
|
||||
value = currency.Comp.Price
|
||||
.ToDictionary(v => v.Key, p => p.Value * stack.Count);
|
||||
}
|
||||
|
||||
if (!TryAddCurrency(value, store, store.Comp))
|
||||
return false;
|
||||
|
||||
// Avoid having the currency accidentally be re-used. E.g., if multiple clients try to use the currency in the
|
||||
// same tick
|
||||
currency.Comp.Price.Clear();
|
||||
if (stack != null)
|
||||
_stack.SetCount((currency.Owner, stack), 0);
|
||||
|
||||
QueueDel(currency);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add a currency to a store's balance
|
||||
/// </summary>
|
||||
/// <param name="currency">The value to add to the store</param>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="store">The store to add it to</param>
|
||||
/// <returns>Whether or not the currency was succesfully added</returns>
|
||||
public bool TryAddCurrency(Dictionary<string, FixedPoint2> currency, EntityUid uid, StoreComponent? store = null)
|
||||
{
|
||||
if (!Resolve(uid, ref store))
|
||||
return false;
|
||||
|
||||
//verify these before values are modified
|
||||
foreach (var type in currency)
|
||||
{
|
||||
if (!store.CurrencyWhitelist.Contains(type.Key))
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var type in currency)
|
||||
{
|
||||
if (!store.Balance.TryAdd(type.Key, type.Value))
|
||||
store.Balance[type.Key] += type.Value;
|
||||
}
|
||||
|
||||
UpdateUserInterface(null, uid, store);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnIntrinsicStoreAction(Entity<StoreComponent> ent, ref IntrinsicStoreActionEvent args)
|
||||
{
|
||||
ToggleUi(args.Performer, ent.Owner, ent.Comp);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public sealed class CurrencyInsertAttemptEvent : CancellableEntityEventArgs
|
||||
{
|
||||
public readonly EntityUid User;
|
||||
public readonly EntityUid Target;
|
||||
public readonly EntityUid Used;
|
||||
public readonly StoreComponent Store;
|
||||
|
||||
public CurrencyInsertAttemptEvent(EntityUid user, EntityUid target, EntityUid used, StoreComponent store)
|
||||
{
|
||||
User = user;
|
||||
Target = target;
|
||||
Used = used;
|
||||
Store = store;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,8 @@ public sealed class UplinkSystem : EntitySystem
|
||||
}
|
||||
|
||||
// If we didn't have an uplink, make an empty one.
|
||||
SetUplink(args.Implanted, Spawn(TraitorUplinkStore, MapCoordinates.Nullspace), 0, false);
|
||||
entity.Comp.Store = Spawn(TraitorUplinkStore, MapCoordinates.Nullspace);
|
||||
SetUplink(args.Implanted, entity.Comp.Store.Value, 0, false);
|
||||
Log.Error($"{ToPrettyString(args.Implanted)} did not have an uplink when they were implanted.");
|
||||
}
|
||||
|
||||
|
||||
@@ -404,8 +404,10 @@ public abstract class SharedBloodstreamSystem : EntitySystem
|
||||
|| amount == 0)
|
||||
return false;
|
||||
|
||||
// TODO: Either make this percentage based regeneration and pre-pass the percentage.
|
||||
// TODO: Solution regulation API that doesn't result in very minor FixedPoint2 errors (Currently gingerbreadman only regenerates 0.99u instead of 1.00u)
|
||||
referenceFactor = Math.Clamp(referenceFactor, 0f, ent.Comp.MaxVolumeModifier);
|
||||
var ratio = amount / ent.Comp.BloodReferenceSolution.Volume;
|
||||
var ratio = (float)amount / (float)ent.Comp.BloodReferenceSolution.Volume;
|
||||
|
||||
foreach (var (referenceReagent, referenceQuantity) in ent.Comp.BloodReferenceSolution)
|
||||
{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.IdentityManagement.Components;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Store;
|
||||
|
||||
namespace Content.Shared.Implants;
|
||||
|
||||
@@ -12,11 +12,14 @@ public abstract partial class SharedSubdermalImplantSystem
|
||||
public void InitializeRelay()
|
||||
{
|
||||
SubscribeLocalEvent<ImplantedComponent, MobStateChangedEvent>(RelayToImplantEvent);
|
||||
SubscribeLocalEvent<ImplantedComponent, AfterInteractUsingEvent>(RelayToImplantEvent);
|
||||
SubscribeLocalEvent<ImplantedComponent, SuicideEvent>(RelayToImplantEvent);
|
||||
SubscribeLocalEvent<ImplantedComponent, TransformSpeakerNameEvent>(RelayToImplantEvent);
|
||||
SubscribeLocalEvent<ImplantedComponent, TransformSpeechEvent>(RelayToImplantEvent);
|
||||
SubscribeLocalEvent<ImplantedComponent, SeeIdentityAttemptEvent>(RelayToImplantEvent);
|
||||
|
||||
// Ref relays, for when you need to write to the event!
|
||||
SubscribeLocalEvent<ImplantedComponent, CurrencyInsertAttemptEvent>(RefRelayToImplantEvent);
|
||||
SubscribeLocalEvent<ImplantedComponent, GetStoreEvent>(RefRelayToImplantEvent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -36,6 +39,26 @@ public abstract partial class SharedSubdermalImplantSystem
|
||||
RaiseLocalEvent(implant, relayEv);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Relays events from the implanted to the implant.
|
||||
/// </summary>
|
||||
private void RefRelayToImplantEvent<T>(Entity<ImplantedComponent> entity, ref T args) where T : notnull
|
||||
{
|
||||
if (!_container.TryGetContainer(entity, ImplanterComponent.ImplantSlotId, out var implantContainer))
|
||||
return;
|
||||
|
||||
var relayEv = new ImplantRelayEvent<T>(args, entity);
|
||||
foreach (var implant in implantContainer.ContainedEntities)
|
||||
{
|
||||
if (args is HandledEntityEventArgs { Handled: true })
|
||||
return;
|
||||
|
||||
RaiseLocalEvent(implant, relayEv);
|
||||
}
|
||||
|
||||
args = relayEv.Event;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -43,7 +66,7 @@ public abstract partial class SharedSubdermalImplantSystem
|
||||
/// </summary>
|
||||
public sealed class ImplantRelayEvent<T> where T : notnull
|
||||
{
|
||||
public readonly T Event;
|
||||
public T Event;
|
||||
|
||||
public readonly EntityUid ImplantedEntity;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Radiation.Systems;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Content.Shared.Radiation.Components;
|
||||
@@ -6,6 +7,7 @@ namespace Content.Shared.Radiation.Components;
|
||||
/// Irradiate all objects in range.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[Access(typeof(SharedRadiationSystem))]
|
||||
public sealed partial class RadiationSourceComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using Content.Shared.Radiation.Components;
|
||||
|
||||
namespace Content.Shared.Radiation.Systems;
|
||||
|
||||
public abstract partial class SharedRadiationSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly EntityQuery<RadiationSourceComponent> SourceQuery = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the intensity of a <see cref="RadiationSourceComponent"/> to the passed intensity.
|
||||
/// </summary>
|
||||
/// <param name="entity">Radiation source we're attempting to update</param>
|
||||
/// <param name="intensity">Intensity we're setting the source to.</param>
|
||||
public void SetIntensity(Entity<RadiationSourceComponent?> entity, float intensity)
|
||||
{
|
||||
if (!SourceQuery.Resolve(entity, ref entity.Comp, false))
|
||||
return;
|
||||
|
||||
entity.Comp.Intensity = intensity;
|
||||
UpdateSource((entity, entity.Comp));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the radiation source cache. Does nothing on client, see server!
|
||||
/// </summary>
|
||||
protected virtual void UpdateSource(Entity<RadiationSourceComponent> entity) { }
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Radiation.Components;
|
||||
using Content.Shared.Radiation.Systems;
|
||||
using Content.Shared.Singularity.Components;
|
||||
using Content.Shared.Singularity.Events;
|
||||
using Robust.Shared.Containers;
|
||||
@@ -19,6 +20,7 @@ public abstract class SharedSingularitySystem : EntitySystem
|
||||
[Dependency] private readonly SharedContainerSystem _containers = default!;
|
||||
[Dependency] private readonly SharedEventHorizonSystem _horizons = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedRadiationSystem _radiation = default!;
|
||||
[Dependency] protected readonly IViewVariablesManager Vvm = default!;
|
||||
#endregion Dependencies
|
||||
|
||||
@@ -138,10 +140,7 @@ public abstract class SharedSingularitySystem : EntitySystem
|
||||
_visualizer.SetData(uid, SingularityAppearanceKeys.Singularity, singularity.Level, appearance);
|
||||
}
|
||||
|
||||
if (TryComp<RadiationSourceComponent>(uid, out var radiationSource))
|
||||
{
|
||||
UpdateRadiation(uid, singularity, radiationSource);
|
||||
}
|
||||
UpdateRadiation(uid, singularity);
|
||||
|
||||
RaiseLocalEvent(uid, new SingularityLevelChangedEvent(singularity.Level, oldValue, singularity));
|
||||
if (singularity.Level <= 0)
|
||||
@@ -165,12 +164,12 @@ public abstract class SharedSingularitySystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="uid">The uid of the singularity to update the radiation of.</param>
|
||||
/// <param name="singularity">The state of the singularity to update the radiation of.</param>
|
||||
/// <param name="rads">The state of the radioactivity of the singularity to update.</param>
|
||||
private void UpdateRadiation(EntityUid uid, SingularityComponent? singularity = null, RadiationSourceComponent? rads = null)
|
||||
private void UpdateRadiation(EntityUid uid, SingularityComponent? singularity = null)
|
||||
{
|
||||
if(!Resolve(uid, ref singularity, ref rads, logMissing: false))
|
||||
if(!Resolve(uid, ref singularity, logMissing: false))
|
||||
return;
|
||||
rads.Intensity = singularity.Level * singularity.RadsPerLevel;
|
||||
|
||||
_radiation.SetIntensity(uid, singularity.Level * singularity.RadsPerLevel);
|
||||
}
|
||||
|
||||
#endregion Getters/Setters
|
||||
|
||||
+1
-2
@@ -1,8 +1,7 @@
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Store;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
|
||||
namespace Content.Server.Store.Components;
|
||||
namespace Content.Shared.Store.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Identifies a component that can be inserted into a store
|
||||
+7
-7
@@ -1,12 +1,12 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Store;
|
||||
using Content.Shared.Store.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Store.Systems;
|
||||
namespace Content.Shared.Store;
|
||||
|
||||
public sealed partial class StoreSystem
|
||||
|
||||
public abstract partial class SharedStoreSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Refreshes all listings on a store.
|
||||
@@ -46,7 +46,7 @@ public sealed partial class StoreSystem
|
||||
public HashSet<ListingDataWithCostModifiers> GetAllListings()
|
||||
{
|
||||
var clones = new HashSet<ListingDataWithCostModifiers>();
|
||||
foreach (var prototype in _proto.EnumeratePrototypes<ListingPrototype>())
|
||||
foreach (var prototype in Proto.EnumeratePrototypes<ListingPrototype>())
|
||||
{
|
||||
clones.Add(new ListingDataWithCostModifiers(prototype));
|
||||
}
|
||||
@@ -62,7 +62,7 @@ public sealed partial class StoreSystem
|
||||
/// <returns>Whether or not the listing was added successfully</returns>
|
||||
public bool TryAddListing(StoreComponent component, string listingId)
|
||||
{
|
||||
if (!_proto.TryIndex<ListingPrototype>(listingId, out var proto))
|
||||
if (!Proto.TryIndex<ListingPrototype>(listingId, out var proto))
|
||||
{
|
||||
Log.Error("Attempted to add invalid listing.");
|
||||
return false;
|
||||
@@ -145,7 +145,7 @@ public sealed partial class StoreSystem
|
||||
/// <param name="buyer">The buying entity.</param>
|
||||
public EntityUid GetBuyerMind(EntityUid buyer)
|
||||
{
|
||||
if (!HasComp<MindComponent>(buyer) && _mind.TryGetMind(buyer, out var buyerMind, out var _))
|
||||
if (!HasComp<MindComponent>(buyer) && Mind.TryGetMind(buyer, out var buyerMind, out _))
|
||||
return buyerMind;
|
||||
|
||||
return buyer;
|
||||
@@ -0,0 +1,105 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.PDA.Ringer;
|
||||
using Content.Shared.Store.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Store;
|
||||
|
||||
/// <summary>
|
||||
/// This handles...
|
||||
/// </summary>
|
||||
public abstract partial class SharedStoreSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Toggles the store Ui open and closed
|
||||
/// </summary>
|
||||
/// <param name="user">the person doing the toggling</param>
|
||||
/// <param name="storeEnt">the store being toggled</param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="remoteAccess">The entity remotely accessing the store, if any.</param>
|
||||
/// <param name="remoteComponent">The remote access component, if any.</param>
|
||||
public void ToggleUi(EntityUid user, EntityUid storeEnt, StoreComponent? component = null, EntityUid? remoteAccess = null, RemoteStoreComponent? remoteComponent = null)
|
||||
{
|
||||
if (!Resolve(storeEnt, ref component))
|
||||
return;
|
||||
|
||||
if (remoteAccess != null && !Resolve(remoteAccess.Value, ref remoteComponent) && remoteComponent!.Store != storeEnt)
|
||||
return;
|
||||
|
||||
if (!TryComp<ActorComponent>(user, out var actor))
|
||||
return;
|
||||
|
||||
if (!UI.TryToggleUi(remoteAccess != null ? remoteAccess.Value : storeEnt, StoreUiKey.Key, actor.PlayerSession))
|
||||
return;
|
||||
|
||||
UpdateUserInterface(user, storeEnt, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the store UI for everyone, if it's open
|
||||
/// </summary>
|
||||
public void CloseUi(EntityUid uid, StoreComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
UI.CloseUi(uid, StoreUiKey.Key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the user interface for a store and refreshes the listings
|
||||
/// </summary>
|
||||
/// <param name="user">The person who if opening the store ui. Listings are filtered based on this.</param>
|
||||
/// <param name="store">The store entity itself</param>
|
||||
/// <param name="component">The store component being refreshed.</param>
|
||||
public void UpdateUserInterface(EntityUid? user, EntityUid store, StoreComponent? component = null)
|
||||
{
|
||||
if (!Resolve(store, ref component))
|
||||
return;
|
||||
|
||||
//this is the person who will be passed into logic for all listing filtering.
|
||||
if (user != null) //if we have no "buyer" for this update, then don't update the listings
|
||||
{
|
||||
component.LastAvailableListings = GetAvailableListings(component.AccountOwner ?? user.Value, store, component).ToHashSet();
|
||||
}
|
||||
|
||||
//dictionary for all currencies, including 0 values for currencies on the whitelist
|
||||
Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> allCurrency = new();
|
||||
foreach (var supported in component.CurrencyWhitelist)
|
||||
{
|
||||
allCurrency.Add(supported, FixedPoint2.Zero);
|
||||
|
||||
if (component.Balance.TryGetValue(supported, out var value))
|
||||
allCurrency[supported] = value;
|
||||
}
|
||||
|
||||
// TODO: if multiple users are supposed to be able to interact with a single BUI & see different
|
||||
// stores/listings, this needs to use session specific BUI states.
|
||||
|
||||
// only tell operatives to lock their uplink if it can be locked
|
||||
var showFooter = HasComp<RingerUplinkComponent>(store);
|
||||
|
||||
var state = new StoreUpdateState(component.LastAvailableListings, allCurrency, showFooter, component.RefundAllowed);
|
||||
UpdateRemoteStores(store, state);
|
||||
UI.SetUiState(store, StoreUiKey.Key, state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates any remote store connections to a specific store.
|
||||
/// </summary>
|
||||
/// <param name="store">The store being updated.</param>
|
||||
/// <param name="state">The state being applied.</param>
|
||||
public void UpdateRemoteStores(EntityUid store, StoreUpdateState state)
|
||||
{
|
||||
var query = EntityQueryEnumerator<RemoteStoreComponent, UserInterfaceComponent>();
|
||||
while (query.MoveNext(out var uid, out var remote, out var ui))
|
||||
{
|
||||
if (remote.Store != store)
|
||||
continue;
|
||||
|
||||
UI.SetUiState((uid, ui), StoreUiKey.Key, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Implants;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Shared.Store.Components;
|
||||
using Content.Shared.Store.Events;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Store;
|
||||
|
||||
@@ -10,9 +18,76 @@ namespace Content.Shared.Store;
|
||||
/// </summary>
|
||||
public abstract partial class SharedStoreSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly IPrototypeManager Proto = default!;
|
||||
[Dependency] protected readonly SharedMindSystem Mind = default!;
|
||||
[Dependency] protected readonly SharedPopupSystem Popup = default!;
|
||||
[Dependency] protected readonly SharedStackSystem Stack = default!;
|
||||
[Dependency] protected readonly SharedUserInterfaceSystem UI = default!;
|
||||
|
||||
[Dependency] protected readonly EntityQuery<StoreComponent> StoreQuery = default!;
|
||||
[Dependency] protected readonly EntityQuery<RemoteStoreComponent> RemoteStoreQuery = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CurrencyComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<RemoteStoreComponent, GetStoreEvent>(OnGetStore);
|
||||
SubscribeLocalEvent<RemoteStoreComponent, ImplantRelayEvent<GetStoreEvent>>((x, ref y) =>
|
||||
{
|
||||
var ev = y.Event;
|
||||
OnGetStore(x, ref ev);
|
||||
y.Event = ev;
|
||||
});
|
||||
SubscribeLocalEvent<RemoteStoreComponent, ImplantRelayEvent<CurrencyInsertAttemptEvent>>(OnImplantInsertAttempt);
|
||||
SubscribeLocalEvent<StoreComponent, IntrinsicStoreActionEvent>(OnIntrinsicStoreAction);
|
||||
}
|
||||
|
||||
private void OnGetStore(Entity<RemoteStoreComponent> entity, ref GetStoreEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (!StoreQuery.TryComp(entity.Comp.Store, out var store))
|
||||
return;
|
||||
|
||||
args.Store = (entity.Comp.Store.Value, store);
|
||||
}
|
||||
|
||||
private void OnImplantInsertAttempt(Entity<RemoteStoreComponent> implant, ref ImplantRelayEvent<CurrencyInsertAttemptEvent> args)
|
||||
{
|
||||
var ev = args.Event;
|
||||
|
||||
// Only allow insertion if the person implanted is doing the action.
|
||||
if (ev.User == ev.Target)
|
||||
ev.TargetOverride = implant;
|
||||
else
|
||||
ev.Cancel();
|
||||
|
||||
args.Event = ev;
|
||||
}
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, CurrencyComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (args.Handled || !args.CanReach || args.Target is not { } target)
|
||||
return;
|
||||
|
||||
if (!TryGetStore(target, out var store))
|
||||
return;
|
||||
|
||||
var ev = new CurrencyInsertAttemptEvent(args.User, target, args.Used, store.Value.Comp);
|
||||
RaiseLocalEvent(target, ev);
|
||||
if (ev.Cancelled)
|
||||
return;
|
||||
|
||||
if (!TryAddCurrency((uid, component), (store.Value, store.Value.Comp)))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
var msg = Loc.GetString("store-currency-inserted", ("used", args.Used), ("target", ev.TargetOverride ?? target));
|
||||
Popup.PopupEntity(msg, target, args.User);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to find a store connected to this entity.
|
||||
/// First checking for a <see cref="StoreComponent"/> on this entity,
|
||||
@@ -21,7 +96,7 @@ public abstract partial class SharedStoreSystem : EntitySystem
|
||||
/// <param name="entity">Entity we're checking for an attached store on</param>
|
||||
/// <param name="store">Store entity we're returning.</param>
|
||||
/// <returns>True if a store was found.</returns>
|
||||
public bool TryGetStore(Entity<RemoteStoreComponent?> entity, [NotNullWhen(true)] out Entity<StoreComponent>? store)
|
||||
public bool TryGetStore(EntityUid entity, [NotNullWhen(true)] out Entity<StoreComponent>? store)
|
||||
{
|
||||
store = GetStore(entity);
|
||||
return store != null;
|
||||
@@ -34,12 +109,14 @@ public abstract partial class SharedStoreSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="entity">Entity we're checking for an attached store on</param>
|
||||
/// <returns>The store entity and component if found.</returns>
|
||||
public Entity<StoreComponent>? GetStore(Entity<RemoteStoreComponent?> entity)
|
||||
public Entity<StoreComponent>? GetStore(EntityUid entity)
|
||||
{
|
||||
if (StoreQuery.TryComp(entity, out var storeComp))
|
||||
return (entity, storeComp);
|
||||
|
||||
return GetRemoteStore(entity);
|
||||
var ev = new GetStoreEvent();
|
||||
RaiseLocalEvent(entity, ref ev);
|
||||
return ev.Store;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -65,4 +142,113 @@ public abstract partial class SharedStoreSystem : EntitySystem
|
||||
|
||||
entity.Comp.Store = store;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value from an entity's currency component.
|
||||
/// Scales with stacks.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this result is intended to be used with <see cref="TryAddCurrency(Robust.Shared.GameObjects.Entity{Shared.Store.Components.CurrencyComponent?},Robust.Shared.GameObjects.Entity{Content.Shared.Store.Components.StoreComponent?})"/>,
|
||||
/// consider using <see cref="TryAddCurrency(Robust.Shared.GameObjects.Entity{Shared.Store.Components.CurrencyComponent?},Robust.Shared.GameObjects.Entity{Content.Shared.Store.Components.StoreComponent?})"/> instead to ensure that the currency is consumed in the process.
|
||||
/// </remarks>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <returns>The value of the currency</returns>
|
||||
public Dictionary<string, FixedPoint2> GetCurrencyValue(EntityUid uid, CurrencyComponent component)
|
||||
{
|
||||
var amount = EntityManager.GetComponentOrNull<StackComponent>(uid)?.Count ?? 1;
|
||||
return component.Price.ToDictionary(v => v.Key, p => p.Value * amount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add a currency to a store's balance. Note that if successful, this will consume the currency in the process.
|
||||
/// </summary>
|
||||
public bool TryAddCurrency(Entity<CurrencyComponent?> currency, Entity<StoreComponent?> store)
|
||||
{
|
||||
if (!Resolve(currency.Owner, ref currency.Comp))
|
||||
return false;
|
||||
|
||||
if (!Resolve(store.Owner, ref store.Comp))
|
||||
return false;
|
||||
|
||||
var value = currency.Comp.Price;
|
||||
if (TryComp(currency.Owner, out StackComponent? stack) && stack.Count != 1)
|
||||
{
|
||||
value = currency.Comp.Price
|
||||
.ToDictionary(v => v.Key, p => p.Value * stack.Count);
|
||||
}
|
||||
|
||||
if (!TryAddCurrency(value, store, store.Comp))
|
||||
return false;
|
||||
|
||||
// Avoid having the currency accidentally be re-used. E.g., if multiple clients try to use the currency in the
|
||||
// same tick
|
||||
currency.Comp.Price.Clear();
|
||||
if (stack != null)
|
||||
Stack.SetCount((currency.Owner, stack), 0);
|
||||
|
||||
QueueDel(currency);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add a currency to a store's balance
|
||||
/// </summary>
|
||||
/// <param name="currency">The value to add to the store</param>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="store">The store to add it to</param>
|
||||
/// <returns>Whether or not the currency was succesfully added</returns>
|
||||
public bool TryAddCurrency(Dictionary<string, FixedPoint2> currency, EntityUid uid, StoreComponent? store = null)
|
||||
{
|
||||
if (!Resolve(uid, ref store))
|
||||
return false;
|
||||
|
||||
//verify these before values are modified
|
||||
foreach (var type in currency)
|
||||
{
|
||||
if (!store.CurrencyWhitelist.Contains(type.Key))
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var type in currency)
|
||||
{
|
||||
if (!store.Balance.TryAdd(type.Key, type.Value))
|
||||
store.Balance[type.Key] += type.Value;
|
||||
}
|
||||
|
||||
UpdateUserInterface(null, uid, store);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnIntrinsicStoreAction(Entity<StoreComponent> ent, ref IntrinsicStoreActionEvent args)
|
||||
{
|
||||
ToggleUi(args.Performer, ent.Owner, ent.Comp);
|
||||
}
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct GetStoreEvent
|
||||
{
|
||||
public readonly bool Handled => Store != null;
|
||||
public Entity<StoreComponent>? Store;
|
||||
}
|
||||
|
||||
public sealed class CurrencyInsertAttemptEvent : CancellableEntityEventArgs
|
||||
{
|
||||
public readonly EntityUid User;
|
||||
public readonly EntityUid Target;
|
||||
public readonly EntityUid Used;
|
||||
public readonly StoreComponent Store;
|
||||
|
||||
// An optional override for the "Target" of this interaction, used to change the name that pops up!
|
||||
public EntityUid? TargetOverride;
|
||||
|
||||
public CurrencyInsertAttemptEvent(EntityUid user, EntityUid target, EntityUid used, StoreComponent store)
|
||||
{
|
||||
User = user;
|
||||
Target = target;
|
||||
Used = used;
|
||||
Store = store;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -346,12 +346,12 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
AttemptAttack(user, weaponUid, weapon, new LightAttackEvent(null, GetNetEntity(weaponUid), GetNetCoordinates(coordinates)), null);
|
||||
}
|
||||
|
||||
public bool AttemptLightAttack(EntityUid user, EntityUid weaponUid, MeleeWeaponComponent weapon, EntityUid target, bool predicted = true)
|
||||
public bool AttemptLightAttack(EntityUid user, EntityUid weaponUid, MeleeWeaponComponent weapon, EntityUid target)
|
||||
{
|
||||
if (!TryComp(target, out TransformComponent? targetXform))
|
||||
return false;
|
||||
|
||||
return AttemptAttack(user, weaponUid, weapon, new LightAttackEvent(GetNetEntity(target), GetNetEntity(weaponUid), GetNetCoordinates(targetXform.Coordinates)), null, predicted);
|
||||
return AttemptAttack(user, weaponUid, weapon, new LightAttackEvent(GetNetEntity(target), GetNetEntity(weaponUid), GetNetCoordinates(targetXform.Coordinates)), null);
|
||||
}
|
||||
|
||||
public bool AttemptDisarmAttack(EntityUid user, EntityUid weaponUid, MeleeWeaponComponent weapon, EntityUid target)
|
||||
@@ -366,7 +366,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
/// Called when a windup is finished and an attack is tried.
|
||||
/// </summary>
|
||||
/// <returns>True if attack successful</returns>
|
||||
private bool AttemptAttack(EntityUid user, EntityUid weaponUid, MeleeWeaponComponent weapon, AttackEvent attack, ICommonSession? session, bool predicted = true)
|
||||
private bool AttemptAttack(EntityUid user, EntityUid weaponUid, MeleeWeaponComponent weapon, AttackEvent attack, ICommonSession? session)
|
||||
{
|
||||
var curTime = Timing.CurTime;
|
||||
|
||||
@@ -473,7 +473,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
DoLungeAnimation(user, weaponUid, weapon.Angle, TransformSystem.ToMapCoordinates(GetCoordinates(attack.Coordinates)), weapon.Range, animation, predicted);
|
||||
DoLungeAnimation(user, weaponUid, weapon.Angle, TransformSystem.ToMapCoordinates(GetCoordinates(attack.Coordinates)), weapon.Range, animation);
|
||||
}
|
||||
|
||||
var attackEv = new MeleeAttackEvent(weaponUid);
|
||||
@@ -963,7 +963,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
return true;
|
||||
}
|
||||
|
||||
private void DoLungeAnimation(EntityUid user, EntityUid weapon, Angle angle, MapCoordinates coordinates, float length, string? animation, bool predicted = true)
|
||||
private void DoLungeAnimation(EntityUid user, EntityUid weapon, Angle angle, MapCoordinates coordinates, float length, string? animation)
|
||||
{
|
||||
// TODO: Assert that offset eyes are still okay.
|
||||
if (!TryComp(user, out TransformComponent? userXform))
|
||||
@@ -984,7 +984,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
if (localPos.Length() > visualLength)
|
||||
localPos = localPos.Normalized() * visualLength;
|
||||
|
||||
DoLunge(user, weapon, angle, localPos, animation, predicted);
|
||||
DoLunge(user, weapon, angle, localPos, animation);
|
||||
}
|
||||
|
||||
public abstract void DoLunge(EntityUid user, EntityUid weapon, Angle angle, Vector2 localPos, string? animation, bool predicted = true);
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
force-attack-component-message = Your anger overwhelms you!
|
||||
@@ -2608,7 +2608,6 @@
|
||||
raffle:
|
||||
settings: short
|
||||
- type: GhostTakeoverAvailable
|
||||
- type: ForceAttack
|
||||
|
||||
- type: entity
|
||||
name: tarantula
|
||||
|
||||
@@ -386,7 +386,6 @@
|
||||
solution: bloodstream
|
||||
- type: DrainableSolution
|
||||
solution: bloodstream
|
||||
- type: ForceAttack
|
||||
|
||||
- type: entity
|
||||
parent: MarkerBase
|
||||
|
||||
@@ -168,7 +168,6 @@
|
||||
- MindRoleGhostRoleTeamAntagonist
|
||||
raffle:
|
||||
settings: short
|
||||
- type: ForceAttack
|
||||
|
||||
- type: entity
|
||||
name: green slime
|
||||
@@ -209,7 +208,6 @@
|
||||
- MindRoleGhostRoleTeamAntagonist
|
||||
raffle:
|
||||
settings: short
|
||||
- type: ForceAttack
|
||||
|
||||
- type: entity
|
||||
name: yellow slime
|
||||
@@ -249,4 +247,3 @@
|
||||
- MindRoleGhostRoleTeamAntagonist
|
||||
raffle:
|
||||
settings: short
|
||||
- type: ForceAttack
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
- Syndicate
|
||||
|
||||
- type: entity
|
||||
parent: [ BaseSubdermalImplant, StorePresetUplink ]
|
||||
parent: BaseSubdermalImplant
|
||||
id: UplinkImplant
|
||||
name: uplink implant
|
||||
description: This implant lets the user access a hidden Syndicate uplink at will.
|
||||
|
||||
@@ -17,16 +17,3 @@
|
||||
responseType: "General Feedback"
|
||||
responseLink: "https://forum.spacestation14.com/c/development/feedback/51"
|
||||
showRoundEnd: false
|
||||
|
||||
- type: feedbackPopup
|
||||
id: ForceVentCrittersToAttackFeedback
|
||||
popupOrigin: wizden_master
|
||||
title: "[bold]Played an antagonist vent critter role?[/bold]"
|
||||
description: >-
|
||||
If you've played or interacted with a player-controlled antagonist vent critter (spiders, slimes, clown spiders), please leave your feedback on how the new auto-attack feature went.
|
||||
responseType: "Feedback Thread"
|
||||
responseLink: "https://forum.spacestation14.com/t/force-vent-critters-to-attack-feedback/26861"
|
||||
showRoundEnd: true
|
||||
ruleWhitelist:
|
||||
components:
|
||||
- VentHordeRule
|
||||
|
||||
Reference in New Issue
Block a user