Merge branch 'master' into air-alarm-fixup
This commit is contained in:
@@ -14,7 +14,10 @@ namespace Content.Shared.Access.Components
|
||||
[Access(typeof(AccessSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends
|
||||
public HashSet<string> Tags = new();
|
||||
|
||||
[DataField("groups", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AccessGroupPrototype>))]
|
||||
public HashSet<string> Groups = new();
|
||||
/// <summary>
|
||||
/// Access Groups. These are added to the tags during map init. After map init this will have no effect.
|
||||
/// </summary>
|
||||
[DataField("groups", readOnly: true, customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AccessGroupPrototype>))]
|
||||
public readonly HashSet<string> Groups = new();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ namespace Content.Shared.Access.Components
|
||||
[Access(typeof(SharedIdCardSystem), typeof(SharedPDASystem), typeof(SharedAgentIdCardSystem))]
|
||||
public sealed class IdCardComponent : Component
|
||||
{
|
||||
[DataField("originalOwnerName")]
|
||||
public string OriginalOwnerName = default!;
|
||||
[DataField("originalEntityName")]
|
||||
public string OriginalEntityName = string.Empty;
|
||||
|
||||
[DataField("fullName")]
|
||||
[Access(typeof(SharedIdCardSystem), typeof(SharedPDASystem), typeof(SharedAgentIdCardSystem),
|
||||
|
||||
@@ -26,12 +26,14 @@ namespace Content.Shared.Access.Components
|
||||
public readonly string FullName;
|
||||
public readonly string JobTitle;
|
||||
public readonly List<string> AccessList;
|
||||
public readonly string JobPrototype;
|
||||
|
||||
public WriteToTargetIdMessage(string fullName, string jobTitle, List<string> accessList)
|
||||
public WriteToTargetIdMessage(string fullName, string jobTitle, List<string> accessList, string jobPrototype)
|
||||
{
|
||||
FullName = fullName;
|
||||
JobTitle = jobTitle;
|
||||
AccessList = accessList;
|
||||
JobPrototype = jobPrototype;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +84,7 @@ namespace Content.Shared.Access.Components
|
||||
public readonly string? TargetIdFullName;
|
||||
public readonly string? TargetIdJobTitle;
|
||||
public readonly string[]? TargetIdAccessList;
|
||||
public readonly string TargetIdJobPrototype;
|
||||
|
||||
public IdCardConsoleBoundUserInterfaceState(bool isPrivilegedIdPresent,
|
||||
bool isPrivilegedIdAuthorized,
|
||||
@@ -89,6 +92,7 @@ namespace Content.Shared.Access.Components
|
||||
string? targetIdFullName,
|
||||
string? targetIdJobTitle,
|
||||
string[]? targetIdAccessList,
|
||||
string targetIdJobPrototype,
|
||||
string privilegedIdName,
|
||||
string targetIdName)
|
||||
{
|
||||
@@ -98,6 +102,7 @@ namespace Content.Shared.Access.Components
|
||||
TargetIdFullName = targetIdFullName;
|
||||
TargetIdJobTitle = targetIdJobTitle;
|
||||
TargetIdAccessList = targetIdAccessList;
|
||||
TargetIdJobPrototype = targetIdJobPrototype;
|
||||
PrivilegedIdName = privilegedIdName;
|
||||
TargetIdName = targetIdName;
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ namespace Content.Shared.Access.Systems
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<AccessComponent, ComponentInit>(OnAccessInit);
|
||||
SubscribeLocalEvent<AccessComponent, MapInitEvent>(OnAccessInit);
|
||||
}
|
||||
|
||||
private void OnAccessInit(EntityUid uid, AccessComponent component, ComponentInit args)
|
||||
private void OnAccessInit(EntityUid uid, AccessComponent component, MapInitEvent args)
|
||||
{
|
||||
// Add all tags in groups to the list of tags.
|
||||
foreach (var group in component.Groups)
|
||||
|
||||
@@ -12,6 +12,7 @@ public sealed class BlockingUserSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly BlockingSystem _blockingSystem = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -54,9 +55,9 @@ public sealed class BlockingUserSystem : EntitySystem
|
||||
|
||||
private void OnDamageChanged(EntityUid uid, BlockingUserComponent component, DamageChangedEvent args)
|
||||
{
|
||||
if (component.BlockingItem != null)
|
||||
if (args.DamageDelta != null && args.DamageIncreased)
|
||||
{
|
||||
RaiseLocalEvent(component.BlockingItem.Value, args);
|
||||
_damageable.TryChangeDamage(component.BlockingItem, args.DamageDelta);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace Content.Shared.Body.Components
|
||||
[NetworkedComponent()]
|
||||
public abstract class SharedBodyPartComponent : Component
|
||||
{
|
||||
public const string ContainerId = "bodypart";
|
||||
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
private SharedBodyComponent? _body;
|
||||
|
||||
@@ -255,9 +257,6 @@ namespace Content.Shared.Body.Components
|
||||
|
||||
private void AddedToBody(SharedBodyComponent body)
|
||||
{
|
||||
var transformComponent = _entMan.GetComponent<TransformComponent>(Owner);
|
||||
transformComponent.LocalRotation = 0;
|
||||
transformComponent.AttachParent(body.Owner);
|
||||
OnAddedToBody(body);
|
||||
|
||||
foreach (var mechanism in _mechanisms)
|
||||
|
||||
@@ -5,11 +5,14 @@ using Content.Shared.Movement.Events;
|
||||
using Content.Shared.Standing;
|
||||
using Content.Shared.Throwing;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Buckle
|
||||
{
|
||||
public abstract class SharedBuckleSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly IGameTiming GameTiming = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -28,6 +31,20 @@ namespace Content.Shared.Buckle
|
||||
// TODO: This looks dirty af.
|
||||
// On rotation of a strap, reattach all buckled entities.
|
||||
// This fixes buckle offsets and draw depths.
|
||||
// This is mega cursed. Please somebody save me from Mr Buckle's wild ride.
|
||||
|
||||
// Consider a chair that has a player strapped to it. Then the client receives a new server state, showing
|
||||
// that the player entity has moved elsewhere, and the chair has rotated. If the client applies the player
|
||||
// state, then the chairs transform comp state, and then the buckle state. The transform state will
|
||||
// forcefully teleport the player back to the chair (client-side only). This causes even more issues if the
|
||||
// chair was teleporting in from nullspace after having left PVS.
|
||||
//
|
||||
// One option is to just never trigger re-buckles during state application.
|
||||
// another is to.. just not do this? Like wtf is this code. But I CBF with buckle atm.
|
||||
|
||||
if (GameTiming.ApplyingState)
|
||||
return;
|
||||
|
||||
foreach (var buckledEntity in component.BuckledEntities)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(buckledEntity, out SharedBuckleComponent? buckled))
|
||||
@@ -35,6 +52,12 @@ namespace Content.Shared.Buckle
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!buckled.Buckled || buckled.LastEntityBuckledTo != uid)
|
||||
{
|
||||
Logger.Error($"A moving strap entity {ToPrettyString(uid)} attempted to re-parent an entity that does not 'belong' to it {ToPrettyString(buckledEntity)}");
|
||||
continue;
|
||||
}
|
||||
|
||||
buckled.ReAttach(component);
|
||||
Dirty(buckled);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace Content.Shared.Construction.Prototypes
|
||||
[Prototype("construction")]
|
||||
public sealed class ConstructionPrototype : IPrototype
|
||||
{
|
||||
private string _category = string.Empty;
|
||||
|
||||
[DataField("conditions")] private List<IConstructionCondition> _conditions = new();
|
||||
|
||||
/// <summary>
|
||||
@@ -52,7 +54,12 @@ namespace Content.Shared.Construction.Prototypes
|
||||
[DataField("canBuildInImpassable")]
|
||||
public bool CanBuildInImpassable { get; private set; }
|
||||
|
||||
[DataField("category")] public string Category { get; private set; } = string.Empty;
|
||||
[DataField("category")]
|
||||
public string Category
|
||||
{
|
||||
get => _category;
|
||||
private set => _category = Loc.GetString(value);
|
||||
}
|
||||
|
||||
[DataField("objectType")] public ConstructionType Type { get; private set; } = ConstructionType.Structure;
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Examine;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Construction.Steps
|
||||
{
|
||||
[DataDefinition]
|
||||
public sealed class PrototypeConstructionGraphStep : ArbitraryInsertConstructionGraphStep
|
||||
{
|
||||
[DataField("prototype")] public string Prototype { get; } = string.Empty;
|
||||
[DataField("prototype", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>), required:true)]
|
||||
public string Prototype { get; } = string.Empty;
|
||||
|
||||
public override bool EntityValid(EntityUid uid, IEntityManager entityManager)
|
||||
{
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace Content.Shared.Disposal.Components
|
||||
[NetworkedComponent]
|
||||
public abstract class SharedDisposalUnitComponent : Component
|
||||
{
|
||||
public const string ContainerId = "DisposalUnit";
|
||||
|
||||
// TODO: Could maybe turn the contact off instead far more cheaply as farseer (though not box2d) had support for it?
|
||||
// Need to suss it out.
|
||||
/// <summary>
|
||||
|
||||
@@ -23,6 +23,9 @@ public abstract class SharedDoorSystem : EntitySystem
|
||||
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
|
||||
[Dependency] protected readonly TagSystem Tags = default!;
|
||||
[Dependency] protected readonly IGameTiming GameTiming = default!;
|
||||
[Dependency] protected readonly SharedAudioSystem Audio = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
/// <summary>
|
||||
/// A body must have an intersection percentage larger than this in order to be considered as colliding with a
|
||||
@@ -167,7 +170,7 @@ public abstract class SharedDoorSystem : EntitySystem
|
||||
if (!TryComp(uid, out AppearanceComponent? appearance))
|
||||
return;
|
||||
|
||||
appearance.SetData(DoorVisuals.State, door.State);
|
||||
_appearance.SetData(uid, DoorVisuals.State, door.State);
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -199,7 +202,7 @@ public abstract class SharedDoorSystem : EntitySystem
|
||||
SetState(uid, DoorState.Denying, door);
|
||||
|
||||
if (door.DenySound != null)
|
||||
PlaySound(uid, door.DenySound.GetSound(), AudioParams.Default.WithVolume(-3), user, predicted);
|
||||
PlaySound(uid, door.DenySound, AudioParams.Default.WithVolume(-3), user, predicted);
|
||||
}
|
||||
|
||||
public bool TryToggleDoor(EntityUid uid, DoorComponent? door = null, EntityUid? user = null, bool predicted = false)
|
||||
@@ -265,14 +268,14 @@ public abstract class SharedDoorSystem : EntitySystem
|
||||
SetState(uid, DoorState.Opening, door);
|
||||
|
||||
if (door.OpenSound != null)
|
||||
PlaySound(uid, door.OpenSound.GetSound(), AudioParams.Default.WithVolume(-5), user, predicted);
|
||||
PlaySound(uid, door.OpenSound, AudioParams.Default.WithVolume(-5), user, predicted);
|
||||
|
||||
// I'm not sure what the intent here is/was? It plays a sound if the user is opening a door with a hands
|
||||
// component, but no actual hands!? What!? Is this the sound of them head-butting the door to get it to open??
|
||||
// I'm 99% sure something is wrong here, but I kind of want to keep it this way.
|
||||
|
||||
if (user != null && TryComp(user.Value, out SharedHandsComponent? hands) && hands.Hands.Count == 0)
|
||||
PlaySound(uid, door.TryOpenDoorSound.GetSound(), AudioParams.Default.WithVolume(-2), user, predicted);
|
||||
PlaySound(uid, door.TryOpenDoorSound, AudioParams.Default.WithVolume(-2), user, predicted);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -329,7 +332,7 @@ public abstract class SharedDoorSystem : EntitySystem
|
||||
SetState(uid, DoorState.Closing, door);
|
||||
|
||||
if (door.CloseSound != null)
|
||||
PlaySound(uid, door.CloseSound.GetSound(), AudioParams.Default.WithVolume(-5), user, predicted);
|
||||
PlaySound(uid, door.CloseSound, AudioParams.Default.WithVolume(-5), user, predicted);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -422,7 +425,8 @@ public abstract class SharedDoorSystem : EntitySystem
|
||||
yield break;
|
||||
|
||||
// TODO SLOTH fix electro's code.
|
||||
var doorAABB = physics.GetWorldAABB();
|
||||
// ReSharper disable once InconsistentNaming
|
||||
var doorAABB = _entityLookup.GetWorldAABB(uid);
|
||||
|
||||
foreach (var otherPhysics in PhysicsSystem.GetCollidingEntities(Transform(uid).MapID, doorAABB))
|
||||
{
|
||||
@@ -436,7 +440,7 @@ public abstract class SharedDoorSystem : EntitySystem
|
||||
&& (otherPhysics.CollisionMask & physics.CollisionLayer) == 0)
|
||||
continue;
|
||||
|
||||
if (otherPhysics.GetWorldAABB().IntersectPercentage(doorAABB) < IntersectPercentage)
|
||||
if (_entityLookup.GetWorldAABB(otherPhysics.Owner).IntersectPercentage(doorAABB) < IntersectPercentage)
|
||||
continue;
|
||||
|
||||
yield return otherPhysics.Owner;
|
||||
@@ -614,5 +618,5 @@ public abstract class SharedDoorSystem : EntitySystem
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected abstract void PlaySound(EntityUid uid, string sound, AudioParams audioParams, EntityUid? predictingPlayer, bool predicted);
|
||||
protected abstract void PlaySound(EntityUid uid, SoundSpecifier soundSpecifier, AudioParams audioParams, EntityUid? predictingPlayer, bool predicted);
|
||||
}
|
||||
|
||||
@@ -34,14 +34,12 @@ namespace Content.Shared.Entry
|
||||
{
|
||||
base.PostInit();
|
||||
|
||||
_initTileDefinitions();
|
||||
InitTileDefinitions();
|
||||
IoCManager.Resolve<SpriteAccessoryManager>().Initialize();
|
||||
IoCManager.Resolve<MarkingManager>().Initialize();
|
||||
|
||||
var configMan = IoCManager.Resolve<IConfigurationManager>();
|
||||
#if FULL_RELEASE
|
||||
configMan.OverrideDefault(CVars.NetInterpRatio, 2);
|
||||
#else
|
||||
#if DEBUG
|
||||
configMan.OverrideDefault(CVars.NetFakeLagMin, 0.075f);
|
||||
configMan.OverrideDefault(CVars.NetFakeLoss, 0.005f);
|
||||
configMan.OverrideDefault(CVars.NetFakeDuplicates, 0.005f);
|
||||
@@ -50,10 +48,9 @@ namespace Content.Shared.Entry
|
||||
// just leaving this disabled.
|
||||
// configMan.OverrideDefault(CVars.NetFakeLagRand, 0.01f);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
private void _initTileDefinitions()
|
||||
private void InitTileDefinitions()
|
||||
{
|
||||
// Register space first because I'm a hard coding hack.
|
||||
var spaceDef = _prototypeManager.Index<ContentTileDefinition>(ContentTileDefinition.SpaceID);
|
||||
|
||||
@@ -51,6 +51,17 @@ namespace Content.Shared.Movement.Systems
|
||||
Dirty(move);
|
||||
}
|
||||
|
||||
public void ChangeBaseSpeed(EntityUid uid, float baseWalkSpeed, float baseSprintSpeed, float acceleration, MovementSpeedModifierComponent? move = null)
|
||||
{
|
||||
if (!Resolve(uid, ref move, false))
|
||||
return;
|
||||
|
||||
move.BaseWalkSpeed = baseWalkSpeed;
|
||||
move.BaseSprintSpeed = baseSprintSpeed;
|
||||
move.Acceleration = acceleration;
|
||||
Dirty(move);
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
private sealed class MovementSpeedModifierComponentState : ComponentState
|
||||
{
|
||||
|
||||
@@ -233,6 +233,9 @@ namespace Content.Shared.Movement.Systems
|
||||
Accelerate(ref velocity, in worldTotal, accel, frameTime);
|
||||
|
||||
PhysicsSystem.SetLinearVelocity(physicsComponent, velocity);
|
||||
|
||||
// Ensures that players do not spiiiiiiin
|
||||
PhysicsSystem.SetAngularVelocity(physicsComponent, 0);
|
||||
}
|
||||
|
||||
private void Friction(float minimumFrictionSpeed, float frameTime, float friction, ref Vector2 velocity)
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
|
||||
namespace Content.Shared.PDA
|
||||
{
|
||||
public enum UplinkCategory
|
||||
{
|
||||
Weapons,
|
||||
Ammo,
|
||||
Explosives,
|
||||
Misc,
|
||||
Bundles,
|
||||
Tools,
|
||||
Utility,
|
||||
Job,
|
||||
Armor,
|
||||
Pointless,
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.PDA
|
||||
{
|
||||
[Prototype("uplinkListing")]
|
||||
public sealed class UplinkStoreListingPrototype : IPrototype
|
||||
{
|
||||
[ViewVariables]
|
||||
[IdDataFieldAttribute]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
[DataField("itemId", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string ItemId { get; } = string.Empty;
|
||||
|
||||
[DataField("price")]
|
||||
public int Price { get; } = 5;
|
||||
|
||||
[DataField("category")]
|
||||
public UplinkCategory Category { get; } = UplinkCategory.Utility;
|
||||
|
||||
[DataField("description")]
|
||||
public string Description { get; } = string.Empty;
|
||||
|
||||
[DataField("listingName")]
|
||||
public string ListingName { get; } = string.Empty;
|
||||
|
||||
[DataField("icon")]
|
||||
public SpriteSpecifier? Icon { get; } = null;
|
||||
|
||||
[DataField("jobWhitelist", customTypeSerializer:typeof(PrototypeIdHashSetSerializer<JobPrototype>))]
|
||||
public HashSet<string>? JobWhitelist;
|
||||
|
||||
[DataField("surplus")]
|
||||
public bool CanSurplus = true;
|
||||
}
|
||||
}
|
||||
@@ -40,49 +40,6 @@ namespace Content.Shared.Pulling.Components
|
||||
[ViewVariables]
|
||||
public bool PrevFixedRotation;
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new PullableComponentState(Puller);
|
||||
}
|
||||
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
|
||||
if (curState is not PullableComponentState state)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state.Puller.HasValue)
|
||||
{
|
||||
EntitySystem.Get<SharedPullingStateManagementSystem>().ForceDisconnectPullable(this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state.Puller.Value.IsValid())
|
||||
{
|
||||
Logger.Error($"Invalid entity {state.Puller.Value} for pulling");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Puller == state.Puller)
|
||||
{
|
||||
// don't disconnect and reconnect a puller for no reason
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent<SharedPullerComponent?>(state.Puller.Value, out var comp))
|
||||
{
|
||||
Logger.Error($"Entity {state.Puller.Value} for pulling had no Puller component");
|
||||
// ensure it disconnects from any different puller, still
|
||||
EntitySystem.Get<SharedPullingStateManagementSystem>().ForceDisconnectPullable(this);
|
||||
return;
|
||||
}
|
||||
|
||||
EntitySystem.Get<SharedPullingStateManagementSystem>().ForceRelationship(comp, this);
|
||||
}
|
||||
|
||||
protected override void OnRemove()
|
||||
{
|
||||
if (Puller != null)
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Linq;
|
||||
using Content.Shared.Physics.Pull;
|
||||
using Content.Shared.Pulling.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -24,6 +25,41 @@ namespace Content.Shared.Pulling
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SharedPullableComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<SharedPullableComponent, ComponentGetState>(OnGetState);
|
||||
SubscribeLocalEvent<SharedPullableComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
private void OnGetState(EntityUid uid, SharedPullableComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new PullableComponentState(component.Puller);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, SharedPullableComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not PullableComponentState state)
|
||||
return;
|
||||
|
||||
if (!state.Puller.HasValue)
|
||||
{
|
||||
ForceDisconnectPullable(component);
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.Puller == state.Puller)
|
||||
{
|
||||
// don't disconnect and reconnect a puller for no reason
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp<SharedPullerComponent?>(state.Puller.Value, out var comp))
|
||||
{
|
||||
Logger.Error($"Pullable state for entity {ToPrettyString(uid)} had invalid puller entity {ToPrettyString(state.Puller.Value)}");
|
||||
// ensure it disconnects from any different puller, still
|
||||
ForceDisconnectPullable(component);
|
||||
return;
|
||||
}
|
||||
|
||||
ForceRelationship(comp, component);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, SharedPullableComponent component, ComponentShutdown args)
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Revenant;
|
||||
|
||||
[Serializable]
|
||||
[Prototype("revenantListing")]
|
||||
public sealed class RevenantStoreListingPrototype : IPrototype
|
||||
{
|
||||
[ViewVariables]
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
[DataField("actionId", customTypeSerializer:typeof(PrototypeIdSerializer<InstantActionPrototype>))]
|
||||
public string ActionId { get; } = string.Empty;
|
||||
|
||||
[DataField("price")]
|
||||
public int Price { get; } = 5;
|
||||
|
||||
[DataField("description")]
|
||||
public string Description { get; } = string.Empty;
|
||||
|
||||
[DataField("listingName")]
|
||||
public string ListingName { get; } = string.Empty;
|
||||
|
||||
[DataField("icon")]
|
||||
public SpriteSpecifier? Icon { get; } = null;
|
||||
}
|
||||
@@ -16,34 +16,3 @@ public enum RevenantVisuals : byte
|
||||
Stunned,
|
||||
Harvesting,
|
||||
}
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
public enum RevenantUiKey : byte
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class RevenantUpdateState : BoundUserInterfaceState
|
||||
{
|
||||
public float Essence;
|
||||
|
||||
public readonly List<RevenantStoreListingPrototype> Listings;
|
||||
|
||||
public RevenantUpdateState(float essence, List<RevenantStoreListingPrototype> listings)
|
||||
{
|
||||
Essence = essence;
|
||||
Listings = listings;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class RevenantBuyListingMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public RevenantStoreListingPrototype Listing;
|
||||
|
||||
public RevenantBuyListingMessage (RevenantStoreListingPrototype listing)
|
||||
{
|
||||
Listing = listing;
|
||||
}
|
||||
}
|
||||
|
||||
43
Content.Shared/Store/CurrencyPrototype.cs
Normal file
43
Content.Shared/Store/CurrencyPrototype.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Store;
|
||||
|
||||
/// <summary>
|
||||
/// Prototype used to define different types of currency for generic stores.
|
||||
/// Mainly used for antags, such as traitors, nukies, and revenants
|
||||
/// This is separate to the cargo ordering system.
|
||||
/// </summary>
|
||||
[Prototype("currency")]
|
||||
[DataDefinition, Serializable, NetSerializable]
|
||||
public sealed class CurrencyPrototype : IPrototype
|
||||
{
|
||||
[ViewVariables]
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The Loc string used for displaying the balance of a certain currency at the top of the store ui
|
||||
/// </summary>
|
||||
[DataField("balanceDisplay")]
|
||||
public string BalanceDisplay { get; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The Loc string used for displaying the price of listings in store UI
|
||||
/// </summary>
|
||||
[DataField("priceDisplay")]
|
||||
public string PriceDisplay { get; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The physical entity of the currency
|
||||
/// </summary>
|
||||
[DataField("entityId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? EntityId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not this currency can be withdrawn from a shop by a player. Requires a valid entityId.
|
||||
/// </summary>
|
||||
[DataField("canWithdraw")]
|
||||
public bool CanWithdraw { get; } = true;
|
||||
}
|
||||
23
Content.Shared/Store/ListingCondition.cs
Normal file
23
Content.Shared/Store/ListingCondition.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Store;
|
||||
|
||||
/// <summary>
|
||||
/// Used to define a complicated condition that requires C#
|
||||
/// </summary>
|
||||
[ImplicitDataDefinitionForInheritors]
|
||||
[MeansImplicitUse]
|
||||
public abstract class ListingCondition
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether or not a certain entity can purchase a listing.
|
||||
/// </summary>
|
||||
/// <returns>Whether or not the listing can be purchased</returns>
|
||||
public abstract bool Condition(ListingConditionArgs args);
|
||||
}
|
||||
|
||||
/// <param name="Buyer">The person purchasing the listing</param>
|
||||
/// <param name="Listing">The liting itself</param>
|
||||
/// <param name="EntityManager">An entitymanager for sane coding</param>
|
||||
public readonly record struct ListingConditionArgs(EntityUid Buyer, EntityUid? StoreEntity, ListingData Listing, IEntityManager EntityManager);
|
||||
133
Content.Shared/Store/ListingPrototype.cs
Normal file
133
Content.Shared/Store/ListingPrototype.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.FixedPoint;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Shared.Store;
|
||||
|
||||
/// <summary>
|
||||
/// This is the data object for a store listing which is passed around in code.
|
||||
/// this allows for prices and features of listings to be dynamically changed in code
|
||||
/// without having to modify the prototypes.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
[Virtual, DataDefinition]
|
||||
public class ListingData : IEquatable<ListingData>
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the listing. If empty, uses the entity's name (if present)
|
||||
/// </summary>
|
||||
[DataField("name")]
|
||||
public string Name = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The description of the listing. If empty, uses the entity's description (if present)
|
||||
/// </summary>
|
||||
[DataField("description")]
|
||||
public string Description = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The categories that this listing applies to. Used for filtering a listing for a store.
|
||||
/// </summary>
|
||||
[DataField("categories", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<StoreCategoryPrototype>))]
|
||||
public List<string> Categories = new();
|
||||
|
||||
/// <summary>
|
||||
/// The cost of the listing. String represents the currency type while the FixedPoint2 represents the amount of that currency.
|
||||
/// </summary>
|
||||
[DataField("cost", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<FixedPoint2, CurrencyPrototype>))]
|
||||
public Dictionary<string, FixedPoint2> Cost = new();
|
||||
|
||||
/// <summary>
|
||||
/// Specific customizeable conditions that determine whether or not the listing can be purchased.
|
||||
/// </summary>
|
||||
[NonSerialized]
|
||||
[DataField("conditions", serverOnly: true)]
|
||||
public List<ListingCondition>? Conditions;
|
||||
|
||||
/// <summary>
|
||||
/// The icon for the listing. If null, uses the icon for the entity or action.
|
||||
/// </summary>
|
||||
[DataField("icon")]
|
||||
public SpriteSpecifier? Icon;
|
||||
|
||||
/// <summary>
|
||||
/// The priority for what order the listings will show up in on the menu.
|
||||
/// </summary>
|
||||
[DataField("priority")]
|
||||
public int Priority = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The entity that is given when the listing is purchased.
|
||||
/// </summary>
|
||||
[DataField("productEntity", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? ProductEntity;
|
||||
|
||||
/// <summary>
|
||||
/// The action that is given when the listing is purchased.
|
||||
/// </summary>
|
||||
[DataField("productAction", customTypeSerializer: typeof(PrototypeIdSerializer<InstantActionPrototype>))]
|
||||
public string? ProductAction;
|
||||
|
||||
/// <summary>
|
||||
/// The event that is broadcast when the listing is purchased.
|
||||
/// </summary>
|
||||
[DataField("productEvent")]
|
||||
public object? ProductEvent;
|
||||
|
||||
/// <summary>
|
||||
/// used internally for tracking how many times an item was purchased.
|
||||
/// </summary>
|
||||
public int PurchaseAmount = 0;
|
||||
|
||||
public bool Equals(ListingData? listing)
|
||||
{
|
||||
if (listing == null)
|
||||
return false;
|
||||
|
||||
//simple conditions
|
||||
if (Priority != listing.Priority ||
|
||||
Name != listing.Name ||
|
||||
Description != listing.Description ||
|
||||
ProductEntity != listing.ProductEntity ||
|
||||
ProductAction != listing.ProductAction ||
|
||||
ProductEvent != listing.ProductEvent)
|
||||
return false;
|
||||
|
||||
if (Icon != null && !Icon.Equals(listing.Icon))
|
||||
return false;
|
||||
|
||||
///more complicated conditions that eat perf. these don't really matter
|
||||
///as much because you will very rarely have to check these.
|
||||
if (!Categories.OrderBy(x => x).SequenceEqual(listing.Categories.OrderBy(x => x)))
|
||||
return false;
|
||||
|
||||
if (!Cost.OrderBy(x => x).SequenceEqual(listing.Cost.OrderBy(x => x)))
|
||||
return false;
|
||||
|
||||
if ((Conditions != null && listing.Conditions != null) &&
|
||||
!Conditions.OrderBy(x => x).SequenceEqual(listing.Conditions.OrderBy(x => x)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//<inheritdoc>
|
||||
/// <summary>
|
||||
/// Defines a set item listing that is available in a store
|
||||
/// </summary>
|
||||
[Prototype("listing")]
|
||||
[Serializable, NetSerializable]
|
||||
[DataDefinition]
|
||||
public sealed class ListingPrototype : ListingData, IPrototype
|
||||
{
|
||||
[ViewVariables]
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
}
|
||||
22
Content.Shared/Store/StoreCategoryPrototype.cs
Normal file
22
Content.Shared/Store/StoreCategoryPrototype.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Store;
|
||||
|
||||
/// <summary>
|
||||
/// Used to define different categories for a store.
|
||||
/// </summary>
|
||||
[Prototype("storeCategory")]
|
||||
[Serializable, NetSerializable, DataDefinition]
|
||||
public sealed class StoreCategoryPrototype : IPrototype
|
||||
{
|
||||
[ViewVariables]
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
[DataField("name")]
|
||||
public string Name { get; } = string.Empty;
|
||||
|
||||
[DataField("priority")]
|
||||
public int Priority { get; } = 0;
|
||||
}
|
||||
41
Content.Shared/Store/StorePresetPrototype.cs
Normal file
41
Content.Shared/Store/StorePresetPrototype.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
using Content.Shared.FixedPoint;
|
||||
|
||||
namespace Content.Shared.Store;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies generic info for initializing a store.
|
||||
/// </summary>
|
||||
[Prototype("storePreset")]
|
||||
[DataDefinition]
|
||||
public sealed class StorePresetPrototype : IPrototype
|
||||
{
|
||||
[ViewVariables] [IdDataField] public string ID { get; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The name displayed at the top of the store window
|
||||
/// </summary>
|
||||
[DataField("storeName", required: true)]
|
||||
public string StoreName { get; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The categories that this store can access
|
||||
/// </summary>
|
||||
[DataField("categories", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<StoreCategoryPrototype>))]
|
||||
public HashSet<string> Categories { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The inital balance that the store initializes with.
|
||||
/// </summary>
|
||||
[DataField("initialBalance",
|
||||
customTypeSerializer: typeof(PrototypeIdDictionarySerializer<FixedPoint2, CurrencyPrototype>))]
|
||||
public Dictionary<string, FixedPoint2>? InitialBalance { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The currencies that are accepted in the store
|
||||
/// </summary>
|
||||
[DataField("currencyWhitelist", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<CurrencyPrototype>))]
|
||||
public HashSet<string> CurrencyWhitelist { get; } = new();
|
||||
}
|
||||
84
Content.Shared/Store/StoreUi.cs
Normal file
84
Content.Shared/Store/StoreUi.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.MobState;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Store;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum StoreUiKey : byte
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class StoreUpdateState : BoundUserInterfaceState
|
||||
{
|
||||
public readonly EntityUid? Buyer;
|
||||
|
||||
public readonly HashSet<ListingData> Listings;
|
||||
|
||||
public readonly Dictionary<string, FixedPoint2> Balance;
|
||||
|
||||
public StoreUpdateState(EntityUid? buyer, HashSet<ListingData> listings, Dictionary<string, FixedPoint2> balance)
|
||||
{
|
||||
Buyer = buyer;
|
||||
Listings = listings;
|
||||
Balance = balance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// initializes miscellaneous data about the store.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class StoreInitializeState : BoundUserInterfaceState
|
||||
{
|
||||
public readonly string Name;
|
||||
|
||||
public StoreInitializeState(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class StoreRequestUpdateInterfaceMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public EntityUid CurrentBuyer;
|
||||
|
||||
public StoreRequestUpdateInterfaceMessage(EntityUid currentBuyer)
|
||||
{
|
||||
CurrentBuyer = currentBuyer;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class StoreBuyListingMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public EntityUid Buyer;
|
||||
|
||||
public ListingData Listing;
|
||||
|
||||
public StoreBuyListingMessage(EntityUid buyer, ListingData listing)
|
||||
{
|
||||
Buyer = buyer;
|
||||
Listing = listing;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class StoreRequestWithdrawMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public EntityUid Buyer;
|
||||
|
||||
public string Currency;
|
||||
|
||||
public int Amount;
|
||||
|
||||
public StoreRequestWithdrawMessage(EntityUid buyer, string currency, int amount)
|
||||
{
|
||||
Buyer = buyer;
|
||||
Currency = currency;
|
||||
Amount = amount;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,47 @@
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Tools.Components
|
||||
{
|
||||
[NetworkedComponent]
|
||||
public abstract class SharedMultipleToolComponent : Component
|
||||
{
|
||||
[DataDefinition]
|
||||
public sealed class ToolEntry
|
||||
{
|
||||
[DataField("behavior", required: true)]
|
||||
public PrototypeFlags<ToolQualityPrototype> Behavior { get; } = new();
|
||||
|
||||
[DataField("useSound")]
|
||||
public SoundSpecifier? Sound { get; } = null;
|
||||
|
||||
[DataField("changeSound")]
|
||||
public SoundSpecifier? ChangeSound { get; } = null;
|
||||
|
||||
[DataField("sprite")]
|
||||
public SpriteSpecifier? Sprite { get; } = null;
|
||||
}
|
||||
|
||||
[DataField("entries", required: true)]
|
||||
public ToolEntry[] Entries { get; } = Array.Empty<ToolEntry>();
|
||||
|
||||
[ViewVariables]
|
||||
public uint CurrentEntry = 0;
|
||||
|
||||
[ViewVariables]
|
||||
public string CurrentQualityName = String.Empty;
|
||||
}
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
public sealed class MultipleToolComponentState : ComponentState
|
||||
{
|
||||
public string QualityName { get; }
|
||||
public readonly uint Selected;
|
||||
|
||||
public MultipleToolComponentState(string qualityName)
|
||||
public MultipleToolComponentState(uint selected)
|
||||
{
|
||||
QualityName = qualityName;
|
||||
Selected = selected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Tools.Components;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Tools;
|
||||
|
||||
public abstract class SharedToolSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<SharedMultipleToolComponent, ComponentStartup>(OnMultipleToolStartup);
|
||||
SubscribeLocalEvent<SharedMultipleToolComponent, ActivateInWorldEvent>(OnMultipleToolActivated);
|
||||
SubscribeLocalEvent<SharedMultipleToolComponent, ComponentGetState>(OnMultipleToolGetState);
|
||||
SubscribeLocalEvent<SharedMultipleToolComponent, ComponentHandleState>(OnMultipleToolHandleState);
|
||||
}
|
||||
|
||||
private void OnMultipleToolHandleState(EntityUid uid, SharedMultipleToolComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not MultipleToolComponentState state)
|
||||
return;
|
||||
|
||||
component.CurrentEntry = state.Selected;
|
||||
SetMultipleTool(uid, component);
|
||||
}
|
||||
|
||||
private void OnMultipleToolStartup(EntityUid uid, SharedMultipleToolComponent multiple, ComponentStartup args)
|
||||
{
|
||||
// Only set the multiple tool if we have a tool component.
|
||||
if(EntityManager.TryGetComponent(uid, out ToolComponent? tool))
|
||||
SetMultipleTool(uid, multiple, tool);
|
||||
}
|
||||
|
||||
private void OnMultipleToolActivated(EntityUid uid, SharedMultipleToolComponent multiple, ActivateInWorldEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = CycleMultipleTool(uid, multiple, args.User);
|
||||
}
|
||||
|
||||
private void OnMultipleToolGetState(EntityUid uid, SharedMultipleToolComponent multiple, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new MultipleToolComponentState(multiple.CurrentEntry);
|
||||
}
|
||||
|
||||
public bool CycleMultipleTool(EntityUid uid, SharedMultipleToolComponent? multiple = null, EntityUid? user = null)
|
||||
{
|
||||
if (!Resolve(uid, ref multiple))
|
||||
return false;
|
||||
|
||||
if (multiple.Entries.Length == 0)
|
||||
return false;
|
||||
|
||||
multiple.CurrentEntry = (uint) ((multiple.CurrentEntry + 1) % multiple.Entries.Length);
|
||||
SetMultipleTool(uid, multiple, playSound: true, user: user);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual void SetMultipleTool(EntityUid uid,
|
||||
SharedMultipleToolComponent? multiple = null,
|
||||
ToolComponent? tool = null,
|
||||
bool playSound = false,
|
||||
EntityUid? user = null)
|
||||
{
|
||||
if (!Resolve(uid, ref multiple, ref tool))
|
||||
return;
|
||||
|
||||
Dirty(multiple);
|
||||
|
||||
if (multiple.Entries.Length <= multiple.CurrentEntry)
|
||||
{
|
||||
multiple.CurrentQualityName = Loc.GetString("multiple-tool-component-no-behavior");
|
||||
return;
|
||||
}
|
||||
|
||||
var current = multiple.Entries[multiple.CurrentEntry];
|
||||
tool.UseSound = current.Sound;
|
||||
tool.Qualities = current.Behavior;
|
||||
|
||||
if (playSound && current.ChangeSound != null)
|
||||
_audioSystem.PlayPredicted(current.ChangeSound, uid, user);
|
||||
|
||||
if (_protoMan.TryIndex(current.Behavior.First(), out ToolQualityPrototype? quality))
|
||||
multiple.CurrentQualityName = Loc.GetString(quality.Name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
namespace Content.Shared.Traitor.Uplink
|
||||
{
|
||||
public sealed class UplinkAccount
|
||||
{
|
||||
public readonly EntityUid? AccountHolder;
|
||||
public int Balance;
|
||||
|
||||
public UplinkAccount(int startingBalance, EntityUid? accountHolder = null)
|
||||
{
|
||||
AccountHolder = accountHolder;
|
||||
Balance = startingBalance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Traitor.Uplink
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class UplinkAccountData
|
||||
{
|
||||
public EntityUid? DataAccountHolder;
|
||||
public int DataBalance;
|
||||
public UplinkAccountData(EntityUid? dataAccountHolder, int dataBalance)
|
||||
{
|
||||
DataAccountHolder = dataAccountHolder;
|
||||
DataBalance = dataBalance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using Content.Shared.PDA;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Traitor.Uplink
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class UplinkListingData : ComponentState, IEquatable<UplinkListingData>
|
||||
{
|
||||
public readonly string ItemId;
|
||||
public readonly int Price;
|
||||
public readonly UplinkCategory Category;
|
||||
public readonly string Description;
|
||||
public readonly string ListingName;
|
||||
public readonly SpriteSpecifier? Icon;
|
||||
public readonly HashSet<string>? JobWhitelist;
|
||||
|
||||
public UplinkListingData(string listingName, string itemId,
|
||||
int price, UplinkCategory category,
|
||||
string description, SpriteSpecifier? icon, HashSet<string>? jobWhitelist)
|
||||
{
|
||||
ListingName = listingName;
|
||||
Price = price;
|
||||
Category = category;
|
||||
Description = description;
|
||||
ItemId = itemId;
|
||||
Icon = icon;
|
||||
JobWhitelist = jobWhitelist;
|
||||
}
|
||||
|
||||
public bool Equals(UplinkListingData? other)
|
||||
{
|
||||
if (other == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ItemId == other.ItemId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Traitor.Uplink
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class UplinkBuyListingMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public string ItemId;
|
||||
|
||||
public UplinkBuyListingMessage(string itemId)
|
||||
{
|
||||
ItemId = itemId;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class UplinkRequestUpdateInterfaceMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public UplinkRequestUpdateInterfaceMessage()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class UplinkTryWithdrawTC : BoundUserInterfaceMessage
|
||||
{
|
||||
public int TC;
|
||||
|
||||
public UplinkTryWithdrawTC(int tc)
|
||||
{
|
||||
TC = tc;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Traitor.Uplink
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class UplinkBuySuccessMessage : EntityEventArgs
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class UplinkInsufficientFundsMessage : EntityEventArgs
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Traitor.Uplink
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class UplinkUpdateState : BoundUserInterfaceState
|
||||
{
|
||||
public UplinkAccountData Account;
|
||||
public UplinkListingData[] Listings;
|
||||
|
||||
public UplinkUpdateState(UplinkAccountData account, UplinkListingData[] listings)
|
||||
{
|
||||
Account = account;
|
||||
Listings = listings;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Traitor.Uplink
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public enum UplinkUiKey : byte
|
||||
{
|
||||
Key
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,12 @@ public sealed class FlyBySoundComponent : Component
|
||||
/// Probability that the sound plays
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("prob")]
|
||||
public float Prob = 0.75f;
|
||||
public float Prob = 0.10f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("sound")]
|
||||
public SoundSpecifier Sound = new SoundCollectionSpecifier("BulletMiss")
|
||||
{
|
||||
Params = AudioParams.Default.WithVolume(5f),
|
||||
Params = AudioParams.Default,
|
||||
};
|
||||
|
||||
[ViewVariables, DataField("range")] public float Range = 1.5f;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
@@ -16,6 +17,7 @@ public abstract partial class SharedGunSystem
|
||||
protected virtual void InitializeBallistic()
|
||||
{
|
||||
SubscribeLocalEvent<BallisticAmmoProviderComponent, ComponentInit>(OnBallisticInit);
|
||||
SubscribeLocalEvent<BallisticAmmoProviderComponent, MapInitEvent>(OnBallisticMapInit);
|
||||
SubscribeLocalEvent<BallisticAmmoProviderComponent, TakeAmmoEvent>(OnBallisticTakeAmmo);
|
||||
SubscribeLocalEvent<BallisticAmmoProviderComponent, GetAmmoCountEvent>(OnBallisticAmmoCount);
|
||||
SubscribeLocalEvent<BallisticAmmoProviderComponent, ComponentGetState>(OnBallisticGetState);
|
||||
@@ -24,10 +26,10 @@ public abstract partial class SharedGunSystem
|
||||
SubscribeLocalEvent<BallisticAmmoProviderComponent, ExaminedEvent>(OnBallisticExamine);
|
||||
SubscribeLocalEvent<BallisticAmmoProviderComponent, GetVerbsEvent<Verb>>(OnBallisticVerb);
|
||||
SubscribeLocalEvent<BallisticAmmoProviderComponent, InteractUsingEvent>(OnBallisticInteractUsing);
|
||||
SubscribeLocalEvent<BallisticAmmoProviderComponent, ActivateInWorldEvent>(OnBallisticActivate);
|
||||
SubscribeLocalEvent<BallisticAmmoProviderComponent, UseInHandEvent>(OnBallisticUse);
|
||||
}
|
||||
|
||||
private void OnBallisticActivate(EntityUid uid, BallisticAmmoProviderComponent component, ActivateInWorldEvent args)
|
||||
private void OnBallisticUse(EntityUid uid, BallisticAmmoProviderComponent component, UseInHandEvent args)
|
||||
{
|
||||
ManualCycle(component, Transform(uid).MapPosition, args.User);
|
||||
args.Handled = true;
|
||||
@@ -62,6 +64,9 @@ public abstract partial class SharedGunSystem
|
||||
|
||||
private void OnBallisticExamine(EntityUid uid, BallisticAmmoProviderComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange)
|
||||
return;
|
||||
|
||||
args.PushMarkup(Loc.GetString("gun-magazine-examine", ("color", AmmoExamineColor), ("count", GetBallisticShots(component))));
|
||||
}
|
||||
|
||||
@@ -122,15 +127,16 @@ public abstract partial class SharedGunSystem
|
||||
private void OnBallisticInit(EntityUid uid, BallisticAmmoProviderComponent component, ComponentInit args)
|
||||
{
|
||||
component.Container = Containers.EnsureContainer<Container>(uid, "ballistic-ammo");
|
||||
component.UnspawnedCount = component.Capacity;
|
||||
}
|
||||
|
||||
private void OnBallisticMapInit(EntityUid uid, BallisticAmmoProviderComponent component, MapInitEvent args)
|
||||
{
|
||||
// TODO this should be part of the prototype, not set on map init.
|
||||
// Alternatively, just track spawned count, instead of unspawned count.
|
||||
if (component.FillProto != null)
|
||||
{
|
||||
component.UnspawnedCount -= Math.Min(component.UnspawnedCount, component.Container.ContainedEntities.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
component.UnspawnedCount = 0;
|
||||
component.UnspawnedCount = Math.Max(0, component.Capacity - component.Container.ContainedEntities.Count);
|
||||
Dirty(component);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,15 +157,17 @@ public abstract partial class SharedGunSystem
|
||||
{
|
||||
entity = component.Entities[^1];
|
||||
|
||||
args.Ammo.Add(EnsureComp<AmmoComponent>(entity));
|
||||
|
||||
// Leave the entity as is if it doesn't auto cycle
|
||||
// TODO: Suss this out with NewAmmoComponent as I don't think it gets removed from container properly
|
||||
if (HasComp<CartridgeAmmoComponent>(entity) && component.AutoCycle)
|
||||
if (!component.AutoCycle)
|
||||
{
|
||||
component.Entities.RemoveAt(component.Entities.Count - 1);
|
||||
component.Container.Remove(entity);
|
||||
return;
|
||||
}
|
||||
|
||||
args.Ammo.Add(EnsureComp<AmmoComponent>(entity));
|
||||
component.Entities.RemoveAt(component.Entities.Count - 1);
|
||||
component.Container.Remove(entity);
|
||||
}
|
||||
else if (component.UnspawnedCount > 0)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
@@ -18,12 +19,15 @@ public abstract partial class SharedGunSystem
|
||||
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, TakeAmmoEvent>(OnChamberMagazineTakeAmmo);
|
||||
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, GetVerbsEvent<Verb>>(OnMagazineVerb);
|
||||
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, ItemSlotChangedEvent>(OnMagazineSlotChange);
|
||||
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, ActivateInWorldEvent>(OnMagazineActivate);
|
||||
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, UseInHandEvent>(OnMagazineUse);
|
||||
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, ExaminedEvent>(OnChamberMagazineExamine);
|
||||
}
|
||||
|
||||
private void OnChamberMagazineExamine(EntityUid uid, ChamberMagazineAmmoProviderComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange)
|
||||
return;
|
||||
|
||||
var (count, _) = GetChamberMagazineCountCapacity(component);
|
||||
args.PushMarkup(Loc.GetString("gun-magazine-examine", ("color", AmmoExamineColor), ("count", count)));
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ public abstract partial class SharedGunSystem
|
||||
{
|
||||
private void OnExamine(EntityUid uid, GunComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange)
|
||||
return;
|
||||
|
||||
args.PushMarkup(Loc.GetString("gun-selected-mode-examine", ("color", ModeExamineColor), ("mode", GetLocSelector(component.SelectedMode))));
|
||||
args.PushMarkup(Loc.GetString("gun-fire-rate-examine", ("color", FireRateExamineColor), ("fireRate", component.FireRate)));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
@@ -17,17 +18,20 @@ public abstract partial class SharedGunSystem
|
||||
SubscribeLocalEvent<MagazineAmmoProviderComponent, TakeAmmoEvent>(OnMagazineTakeAmmo);
|
||||
SubscribeLocalEvent<MagazineAmmoProviderComponent, GetVerbsEvent<Verb>>(OnMagazineVerb);
|
||||
SubscribeLocalEvent<MagazineAmmoProviderComponent, ItemSlotChangedEvent>(OnMagazineSlotChange);
|
||||
SubscribeLocalEvent<MagazineAmmoProviderComponent, ActivateInWorldEvent>(OnMagazineActivate);
|
||||
SubscribeLocalEvent<MagazineAmmoProviderComponent, UseInHandEvent>(OnMagazineUse);
|
||||
SubscribeLocalEvent<MagazineAmmoProviderComponent, ExaminedEvent>(OnMagazineExamine);
|
||||
}
|
||||
|
||||
private void OnMagazineExamine(EntityUid uid, MagazineAmmoProviderComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange)
|
||||
return;
|
||||
|
||||
var (count, _) = GetMagazineCountCapacity(component);
|
||||
args.PushMarkup(Loc.GetString("gun-magazine-examine", ("color", AmmoExamineColor), ("count", count)));
|
||||
}
|
||||
|
||||
private void OnMagazineActivate(EntityUid uid, MagazineAmmoProviderComponent component, ActivateInWorldEvent args)
|
||||
private void OnMagazineUse(EntityUid uid, MagazineAmmoProviderComponent component, UseInHandEvent args)
|
||||
{
|
||||
var magEnt = GetMagazineEntity(uid);
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ public abstract partial class SharedGunSystem : EntitySystem
|
||||
[Dependency] protected readonly SharedPopupSystem PopupSystem = default!;
|
||||
[Dependency] protected readonly ThrowingSystem ThrowingSystem = default!;
|
||||
[Dependency] protected readonly TagSystem TagSystem = default!;
|
||||
[Dependency] protected readonly SharedAudioSystem Audio = default!;
|
||||
[Dependency] protected readonly SharedProjectileSystem Projectiles = default!;
|
||||
|
||||
protected ISawmill Sawmill = default!;
|
||||
@@ -154,19 +155,18 @@ public abstract partial class SharedGunSystem : EntitySystem
|
||||
|
||||
public GunComponent? GetGun(EntityUid entity)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(entity, out SharedHandsComponent? hands) ||
|
||||
hands.ActiveHandEntity is not { } held)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!EntityManager.TryGetComponent(held, out GunComponent? gun))
|
||||
return null;
|
||||
|
||||
if (!_combatMode.IsInCombatMode(entity))
|
||||
return null;
|
||||
|
||||
return gun;
|
||||
if (EntityManager.TryGetComponent(entity, out SharedHandsComponent? hands) &&
|
||||
hands.ActiveHandEntity is { } held &&
|
||||
TryComp(held, out GunComponent? gun))
|
||||
{
|
||||
return gun;
|
||||
}
|
||||
|
||||
// Last resort is check if the entity itself is a gun.
|
||||
return !TryComp(entity, out gun) ? null : gun;
|
||||
}
|
||||
|
||||
private void StopShooting(GunComponent gun)
|
||||
|
||||
Reference in New Issue
Block a user