Merge branch 'master' into ed-17-07-2024-dungenagain

This commit is contained in:
Ed
2024-08-05 23:08:01 +03:00
committed by GitHub
1423 changed files with 219353 additions and 54715 deletions

View File

@@ -120,7 +120,13 @@ namespace Content.Shared.ActionBlocker
var ev = new ThrowAttemptEvent(user, itemUid);
RaiseLocalEvent(user, ev);
return !ev.Cancelled;
if (ev.Cancelled)
return false;
var itemEv = new ThrowItemAttemptEvent(user);
RaiseLocalEvent(itemUid, ref itemEv);
return !itemEv.Cancelled;
}
public bool CanSpeak(EntityUid uid)

View File

@@ -237,7 +237,7 @@ public abstract class SharedActionsSystem : EntitySystem
}
#region ComponentStateManagement
protected virtual void UpdateAction(EntityUid? actionId, BaseActionComponent? action = null)
public virtual void UpdateAction(EntityUid? actionId, BaseActionComponent? action = null)
{
// See client-side code.
}

View File

@@ -183,6 +183,30 @@ public partial class SharedBodySystem
return list;
}
/// <summary>
/// Returns a list of Entity<<see cref="T"/>, <see cref="OrganComponent"/>>
/// for each organ of the body
/// </summary>
/// <typeparam name="T">The component that we want to return</typeparam>
/// <param name="entity">The body to check the organs of</param>
public List<Entity<T, OrganComponent>> GetBodyOrganEntityComps<T>(
Entity<BodyComponent?> entity)
where T : IComponent
{
if (!Resolve(entity, ref entity.Comp))
return new List<Entity<T, OrganComponent>>();
var query = GetEntityQuery<T>();
var list = new List<Entity<T, OrganComponent>>(3);
foreach (var organ in GetBodyOrgans(entity.Owner, entity.Comp))
{
if (query.TryGetComponent(organ.Id, out var comp))
list.Add((organ.Id, comp, organ.Component));
}
return list;
}
/// <summary>
/// Tries to get a list of ValueTuples of <see cref="T"/> and OrganComponent on each organs
/// in the given body.

View File

@@ -11,11 +11,17 @@ namespace Content.Shared.CCVar
public sealed class CCVars : CVars
{
#region CP14
/// <summary>
/// how long does it take to fly an expedition ship to an expedition point?
/// </summary>
public static readonly CVarDef<float> CP14ExpeditionArrivalTime =
CVarDef.Create("cp14.arrival_time", 60f, CVar.SERVERONLY);
CVarDef.Create("cp14.arrival_time", 180f, CVar.SERVERONLY);
/// <summary>
/// is the expedition ship's system enabled?
/// </summary>
public static readonly CVarDef<bool> CP14ExpeditionShip =
CVarDef.Create("cp14.arrivals_ship", true, CVar.SERVERONLY);
#endregion
/*
* Server
@@ -511,7 +517,7 @@ namespace Content.Shared.CCVar
/// The dataset prototype to use when selecting a random tip.
/// </summary>
public static readonly CVarDef<string> TipsDataset =
CVarDef.Create("tips.dataset", "Tips");
CVarDef.Create("tips.dataset", "CP14Tips");
/// <summary>
/// The number of seconds between each tip being displayed when the round is not actively going
@@ -1473,7 +1479,7 @@ namespace Content.Shared.CCVar
/// Whether the arrivals shuttle is enabled.
/// </summary>
public static readonly CVarDef<bool> ArrivalsShuttles =
CVarDef.Create("shuttle.arrivals", true, CVar.SERVERONLY);
CVarDef.Create("shuttle.arrivals", false, CVar.SERVERONLY); //CP14 arrivals disabled
/// <summary>
/// The map to use for the arrivals station.
@@ -1505,6 +1511,13 @@ namespace Content.Shared.CCVar
public static readonly CVarDef<bool> GodmodeArrivals =
CVarDef.Create("shuttle.godmode_arrivals", false, CVar.SERVERONLY);
/// <summary>
/// If a grid is split then hide any smaller ones under this mass (kg) from the map.
/// This is useful to avoid split grids spamming out labels.
/// </summary>
public static readonly CVarDef<int> HideSplitGridsUnder =
CVarDef.Create("shuttle.hide_split_grids_under", 30, CVar.SERVERONLY);
/// <summary>
/// Whether to automatically spawn escape shuttles.
/// </summary>
@@ -1593,7 +1606,7 @@ namespace Content.Shared.CCVar
/// Whether the emergency shuttle is enabled or should the round just end.
/// </summary>
public static readonly CVarDef<bool> EmergencyShuttleEnabled =
CVarDef.Create("shuttle.emergency", true, CVar.SERVERONLY);
CVarDef.Create("shuttle.emergency", false, CVar.SERVERONLY); //CP14 Emergency disabled
/// <summary>
/// The percentage of time passed from the initial call to when the shuttle can no longer be recalled.

View File

@@ -73,6 +73,12 @@ public sealed partial class InjectorComponent : Component
[DataField]
public TimeSpan Delay = TimeSpan.FromSeconds(5);
/// <summary>
/// Each additional 1u after first 5u increases the delay by X seconds.
/// </summary>
[DataField]
public TimeSpan DelayPerVolume = TimeSpan.FromSeconds(0.1);
/// <summary>
/// The state of the injector. Determines it's attack behavior. Containers must have the
/// right SolutionCaps to support injection/drawing. For InjectOnly injectors this should
@@ -81,6 +87,22 @@ public sealed partial class InjectorComponent : Component
[AutoNetworkedField]
[DataField]
public InjectorToggleMode ToggleState = InjectorToggleMode.Draw;
#region Arguments for injection doafter
/// <inheritdoc cref=DoAfterArgs.NeedHand>
[DataField]
public bool NeedHand = true;
/// <inheritdoc cref=DoAfterArgs.BreakOnHandChange>
[DataField]
public bool BreakOnHandChange = true;
/// <inheritdoc cref=DoAfterArgs.MovementThreshold>
[DataField]
public float MovementThreshold = 0.1f;
#endregion
}
/// <summary>

View File

@@ -59,18 +59,6 @@ public sealed partial class ClothingComponent : Component
[DataField("sprite")]
public string? RsiPath;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("maleMask")]
public ClothingMask MaleMask = ClothingMask.UniformFull;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("femaleMask")]
public ClothingMask FemaleMask = ClothingMask.UniformFull;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("unisexMask")]
public ClothingMask UnisexMask = ClothingMask.UniformFull;
/// <summary>
/// Name of the inventory slot the clothing is in.
/// </summary>

View File

@@ -233,7 +233,6 @@ public abstract class ClothingSystem : EntitySystem
clothing.ClothingVisuals = otherClothing.ClothingVisuals;
clothing.EquippedPrefix = otherClothing.EquippedPrefix;
clothing.RsiPath = otherClothing.RsiPath;
clothing.FemaleMask = otherClothing.FemaleMask;
_itemSys.VisualsChanged(uid);
Dirty(uid, clothing);

View File

@@ -58,7 +58,7 @@ namespace Content.Shared.Cuffs
{
base.Initialize();
SubscribeLocalEvent<HandCountChangedEvent>(OnHandCountChanged);
SubscribeLocalEvent<CuffableComponent, HandCountChangedEvent>(OnHandCountChanged);
SubscribeLocalEvent<UncuffAttemptEvent>(OnUncuffAttempt);
SubscribeLocalEvent<CuffableComponent, EntRemovedFromContainerMessage>(OnCuffsRemovedFromContainer);
@@ -380,33 +380,24 @@ namespace Content.Shared.Cuffs
/// <summary>
/// Check the current amount of hands the owner has, and if there's less hands than active cuffs we remove some cuffs.
/// </summary>
private void OnHandCountChanged(HandCountChangedEvent message)
private void OnHandCountChanged(Entity<CuffableComponent> ent, ref HandCountChangedEvent message)
{
var owner = message.Sender;
if (!TryComp(owner, out CuffableComponent? cuffable) ||
!cuffable.Initialized)
{
return;
}
var dirty = false;
var handCount = CompOrNull<HandsComponent>(owner)?.Count ?? 0;
var handCount = CompOrNull<HandsComponent>(ent.Owner)?.Count ?? 0;
while (cuffable.CuffedHandCount > handCount && cuffable.CuffedHandCount > 0)
while (ent.Comp.CuffedHandCount > handCount && ent.Comp.CuffedHandCount > 0)
{
dirty = true;
var container = cuffable.Container;
var entity = container.ContainedEntities[^1];
var handcuffContainer = ent.Comp.Container;
var handcuffEntity = handcuffContainer.ContainedEntities[^1];
_container.Remove(entity, container);
_transform.SetWorldPosition(entity, _transform.GetWorldPosition(owner));
_transform.PlaceNextTo(handcuffEntity, ent.Owner);
}
if (dirty)
{
UpdateCuffState(owner, cuffable);
UpdateCuffState(ent.Owner, ent.Comp);
}
}

View File

@@ -0,0 +1,14 @@
namespace Content.Shared.DisplacementMap;
[DataDefinition]
public sealed partial class DisplacementData
{
/// <summary>
/// allows you to attach different maps for layers of different sizes.
/// </summary>
[DataField(required: true)]
public Dictionary<int, PrototypeLayerData> SizeMaps = new();
[DataField]
public string? ShaderOverride = "DisplacedStencilDraw";
}

View File

@@ -21,6 +21,7 @@ using Robust.Shared.Physics.Systems;
using Robust.Shared.Timing;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Network;
using Robust.Shared.Map.Components;
namespace Content.Shared.Doors.Systems;
@@ -40,6 +41,8 @@ public abstract partial class SharedDoorSystem : EntitySystem
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
[Dependency] private readonly PryingSystem _pryingSystem = default!;
[Dependency] protected readonly SharedPopupSystem Popup = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
[ValidatePrototypeId<TagPrototype>]
public const string DoorBumpTag = "DoorBumpOpener";
@@ -546,29 +549,37 @@ public abstract partial class SharedDoorSystem : EntitySystem
if (!Resolve(uid, ref physics))
yield break;
var xform = Transform(uid);
// Getting the world bounds from the gridUid allows us to use the version of
// GetCollidingEntities that returns Entity<PhysicsComponent>
if (!TryComp<MapGridComponent>(xform.GridUid, out var mapGridComp))
yield break;
var tileRef = _mapSystem.GetTileRef(xform.GridUid.Value, mapGridComp, xform.Coordinates);
var doorWorldBounds = _entityLookup.GetWorldBounds(tileRef);
// TODO SLOTH fix electro's code.
// ReSharper disable once InconsistentNaming
var doorAABB = _entityLookup.GetWorldAABB(uid);
foreach (var otherPhysics in PhysicsSystem.GetCollidingEntities(Transform(uid).MapID, doorAABB))
foreach (var otherPhysics in PhysicsSystem.GetCollidingEntities(Transform(uid).MapID, doorWorldBounds))
{
if (otherPhysics == physics)
if (otherPhysics.Comp == physics)
continue;
//TODO: Make only shutters ignore these objects upon colliding instead of all airlocks
// Excludes Glasslayer for windows, GlassAirlockLayer for windoors, TableLayer for tables
if (!otherPhysics.CanCollide || otherPhysics.CollisionLayer == (int)CollisionGroup.GlassLayer || otherPhysics.CollisionLayer == (int)CollisionGroup.GlassAirlockLayer || otherPhysics.CollisionLayer == (int)CollisionGroup.TableLayer)
if (!otherPhysics.Comp.CanCollide || otherPhysics.Comp.CollisionLayer == (int) CollisionGroup.GlassLayer || otherPhysics.Comp.CollisionLayer == (int) CollisionGroup.GlassAirlockLayer || otherPhysics.Comp.CollisionLayer == (int) CollisionGroup.TableLayer)
continue;
//If the colliding entity is a slippable item ignore it by the airlock
if (otherPhysics.CollisionLayer == (int)CollisionGroup.SlipLayer && otherPhysics.CollisionMask == (int)CollisionGroup.ItemMask)
if (otherPhysics.Comp.CollisionLayer == (int) CollisionGroup.SlipLayer && otherPhysics.Comp.CollisionMask == (int) CollisionGroup.ItemMask)
continue;
//For when doors need to close over conveyor belts
if (otherPhysics.CollisionLayer == (int) CollisionGroup.ConveyorMask)
if (otherPhysics.Comp.CollisionLayer == (int) CollisionGroup.ConveyorMask)
continue;
if ((physics.CollisionMask & otherPhysics.CollisionLayer) == 0 && (otherPhysics.CollisionMask & physics.CollisionLayer) == 0)
if ((physics.CollisionMask & otherPhysics.Comp.CollisionLayer) == 0 && (otherPhysics.Comp.CollisionMask & physics.CollisionLayer) == 0)
continue;
if (_entityLookup.GetWorldAABB(otherPhysics.Owner).IntersectPercentage(doorAABB) < IntersectPercentage)

View File

@@ -18,6 +18,7 @@ namespace Content.Shared.Examine
{
public abstract partial class ExamineSystemShared : EntitySystem
{
[Dependency] private readonly OccluderSystem _occluder = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
@@ -182,12 +183,9 @@ namespace Content.Shared.Examine
length = MaxRaycastRange;
}
var occluderSystem = Get<OccluderSystem>();
IoCManager.Resolve(ref entMan);
var ray = new Ray(origin.Position, dir.Normalized());
var rayResults = occluderSystem
.IntersectRayWithPredicate(origin.MapId, ray, length, state, predicate, false).ToList();
var rayResults = _occluder
.IntersectRayWithPredicate(origin.MapId, ray, length, state, predicate, false);
if (rayResults.Count == 0) return true;
@@ -195,13 +193,13 @@ namespace Content.Shared.Examine
foreach (var result in rayResults)
{
if (!entMan.TryGetComponent(result.HitEntity, out OccluderComponent? o))
if (!TryComp(result.HitEntity, out OccluderComponent? o))
{
continue;
}
var bBox = o.BoundingBox;
bBox = bBox.Translated(entMan.GetComponent<TransformComponent>(result.HitEntity).WorldPosition);
bBox = bBox.Translated(_transform.GetWorldPosition(result.HitEntity));
if (bBox.Contains(origin.Position) || bBox.Contains(other.Position))
{
@@ -216,7 +214,6 @@ namespace Content.Shared.Examine
public bool InRangeUnOccluded(EntityUid origin, EntityUid other, float range = ExamineRange, Ignored? predicate = null, bool ignoreInsideBlocker = true)
{
var entMan = IoCManager.Resolve<IEntityManager>();
var originPos = _transform.GetMapCoordinates(origin);
var otherPos = _transform.GetMapCoordinates(other);
@@ -225,16 +222,14 @@ namespace Content.Shared.Examine
public bool InRangeUnOccluded(EntityUid origin, EntityCoordinates other, float range = ExamineRange, Ignored? predicate = null, bool ignoreInsideBlocker = true)
{
var entMan = IoCManager.Resolve<IEntityManager>();
var originPos = _transform.GetMapCoordinates(origin);
var otherPos = other.ToMap(entMan, _transform);
var otherPos = _transform.ToMapCoordinates(other);
return InRangeUnOccluded(originPos, otherPos, range, predicate, ignoreInsideBlocker);
}
public bool InRangeUnOccluded(EntityUid origin, MapCoordinates other, float range = ExamineRange, Ignored? predicate = null, bool ignoreInsideBlocker = true)
{
var entMan = IoCManager.Resolve<IEntityManager>();
var originPos = _transform.GetMapCoordinates(origin);
return InRangeUnOccluded(originPos, other, range, predicate, ignoreInsideBlocker);
@@ -250,11 +245,12 @@ namespace Content.Shared.Examine
}
var hasDescription = false;
var metadata = MetaData(entity);
//Add an entity description if one is declared
if (!string.IsNullOrEmpty(EntityManager.GetComponent<MetaDataComponent>(entity).EntityDescription))
if (!string.IsNullOrEmpty(metadata.EntityDescription))
{
message.AddText(EntityManager.GetComponent<MetaDataComponent>(entity).EntityDescription);
message.AddText(metadata.EntityDescription);
hasDescription = true;
}

View File

@@ -0,0 +1,6 @@
namespace Content.Shared.Eye.Blinding.Components
{
[RegisterComponent]
public sealed partial class ActivatableUIRequiresVisionComponent : Component;
}

View File

@@ -0,0 +1,52 @@
using Content.Shared.UserInterface;
using Content.Shared.Eye.Blinding.Components;
using Content.Shared.Popups;
using Robust.Shared.Collections;
namespace Content.Shared.Eye.Blinding.Systems;
public sealed class ActivatableUIRequiresVisionSystem : EntitySystem
{
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedUserInterfaceSystem _userInterfaceSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ActivatableUIRequiresVisionComponent, ActivatableUIOpenAttemptEvent>(OnOpenAttempt);
SubscribeLocalEvent<BlindableComponent, BlindnessChangedEvent>(OnBlindnessChanged);
}
private void OnOpenAttempt(EntityUid uid, ActivatableUIRequiresVisionComponent component, ActivatableUIOpenAttemptEvent args)
{
if (args.Cancelled)
return;
if (TryComp<BlindableComponent>(args.User, out var blindable) && blindable.IsBlind)
{
_popupSystem.PopupClient(Loc.GetString("blindness-fail-attempt"), args.User, Shared.Popups.PopupType.MediumCaution);
args.Cancel();
}
}
private void OnBlindnessChanged(EntityUid uid, BlindableComponent component, ref BlindnessChangedEvent args)
{
if (!args.Blind)
return;
var toClose = new ValueList<(EntityUid Entity, Enum Key)>();
foreach (var bui in _userInterfaceSystem.GetActorUis(uid))
{
if (HasComp<ActivatableUIRequiresVisionComponent>(bui.Entity))
{
toClose.Add(bui);
}
}
foreach (var bui in toClose)
{
_userInterfaceSystem.CloseUi(bui.Entity, bui.Key, uid);
}
}
}

View File

@@ -0,0 +1,63 @@
using Content.Shared.StatusEffect;
using Content.Shared.Inventory;
using Content.Shared.Eye.Blinding.Components;
using Content.Shared.Tools.Components;
using Content.Shared.Item.ItemToggle.Components;
namespace Content.Shared.Eye.Blinding.Systems
{
public sealed class EyeProtectionSystem : EntitySystem
{
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
[Dependency] private readonly BlindableSystem _blindingSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RequiresEyeProtectionComponent, ToolUseAttemptEvent>(OnUseAttempt);
SubscribeLocalEvent<RequiresEyeProtectionComponent, ItemToggledEvent>(OnWelderToggled);
SubscribeLocalEvent<EyeProtectionComponent, GetEyeProtectionEvent>(OnGetProtection);
SubscribeLocalEvent<EyeProtectionComponent, InventoryRelayedEvent<GetEyeProtectionEvent>>(OnGetRelayedProtection);
}
private void OnGetRelayedProtection(EntityUid uid, EyeProtectionComponent component,
InventoryRelayedEvent<GetEyeProtectionEvent> args)
{
OnGetProtection(uid, component, args.Args);
}
private void OnGetProtection(EntityUid uid, EyeProtectionComponent component, GetEyeProtectionEvent args)
{
args.Protection += component.ProtectionTime;
}
private void OnUseAttempt(EntityUid uid, RequiresEyeProtectionComponent component, ToolUseAttemptEvent args)
{
if (!component.Toggled)
return;
if (!TryComp<BlindableComponent>(args.User, out var blindable) || blindable.IsBlind)
return;
var ev = new GetEyeProtectionEvent();
RaiseLocalEvent(args.User, ev);
var time = (float) (component.StatusEffectTime - ev.Protection).TotalSeconds;
if (time <= 0)
return;
// Add permanent eye damage if they had zero protection, also somewhat scale their temporary blindness by
// how much damage they already accumulated.
_blindingSystem.AdjustEyeDamage((args.User, blindable), 1);
var statusTimeSpan = TimeSpan.FromSeconds(time * MathF.Sqrt(blindable.EyeDamage));
_statusEffectsSystem.TryAddStatusEffect(args.User, TemporaryBlindnessSystem.BlindingStatusEffect,
statusTimeSpan, false, TemporaryBlindnessSystem.BlindingStatusEffect);
}
private void OnWelderToggled(EntityUid uid, RequiresEyeProtectionComponent component, ItemToggledEvent args)
{
component.Toggled = args.Activated;
Dirty(uid, component);
}
}
}

View File

@@ -48,6 +48,8 @@ namespace Content.Shared.FixedPoint
public static FixedPoint2 FromCents(int value) => new(value);
public static FixedPoint2 FromHundredths(int value) => new(value);
public static FixedPoint2 New(float value)
{
return new((int) ApplyFloatEpsilon(value * ShiftConstant));
@@ -245,14 +247,14 @@ namespace Content.Shared.FixedPoint
return FixedPoint2.Abs(a - b);
}
public static FixedPoint2 Clamp(FixedPoint2 reagent, FixedPoint2 min, FixedPoint2 max)
public static FixedPoint2 Clamp(FixedPoint2 number, FixedPoint2 min, FixedPoint2 max)
{
if (min > max)
{
throw new ArgumentException($"{nameof(min)} {min} cannot be larger than {nameof(max)} {max}");
}
return reagent < min ? min : reagent > max ? max : reagent;
return number < min ? min : number > max ? max : number;
}
public override readonly bool Equals(object? obj)
@@ -274,7 +276,7 @@ namespace Content.Shared.FixedPoint
if (value == "MaxValue")
Value = int.MaxValue;
else
this = New(Parse.Float(value));
this = New(Parse.Double(value));
}
public override readonly string ToString() => $"{ShiftDown().ToString(CultureInfo.InvariantCulture)}";
@@ -314,7 +316,7 @@ namespace Content.Shared.FixedPoint
}
public static class FixedPointEnumerableExt
public static class FixedPoint2EnumerableExt
{
public static FixedPoint2 Sum(this IEnumerable<FixedPoint2> source)
{

View File

@@ -0,0 +1,339 @@
using System.Globalization;
using System.Linq;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.FixedPoint
{
/// <summary>
/// Represents a quantity of something, to a precision of 0.01.
/// To enforce this level of precision, floats are shifted by 2 decimal points, rounded, and converted to an int.
/// </summary>
[Serializable, CopyByRef]
public struct FixedPoint4 : ISelfSerialize, IComparable<FixedPoint4>, IEquatable<FixedPoint4>, IFormattable
{
public long Value { get; private set; }
private const long Shift = 4;
private const long ShiftConstant = 10000; // Must be equal to pow(10, Shift)
public static FixedPoint4 MaxValue { get; } = new(long.MaxValue);
public static FixedPoint4 Epsilon { get; } = new(1);
public static FixedPoint4 Zero { get; } = new(0);
// This value isn't picked by any proper testing, don't @ me.
private const float FloatEpsilon = 0.00001f;
#if DEBUG
static FixedPoint4()
{
// ReSharper disable once CompareOfFloatsByEqualityOperator
DebugTools.Assert(Math.Pow(10, Shift) == ShiftConstant, "ShiftConstant must be equal to pow(10, Shift)");
}
#endif
private readonly double ShiftDown()
{
return Value / (double) ShiftConstant;
}
private FixedPoint4(long value)
{
Value = value;
}
public static FixedPoint4 New(long value)
{
return new(value * ShiftConstant);
}
public static FixedPoint4 FromTenThousandths(long value) => new(value);
public static FixedPoint4 New(float value)
{
return new((long) ApplyFloatEpsilon(value * ShiftConstant));
}
private static float ApplyFloatEpsilon(float value)
{
return value + FloatEpsilon * Math.Sign(value);
}
private static double ApplyFloatEpsilon(double value)
{
return value + FloatEpsilon * Math.Sign(value);
}
/// <summary>
/// Create the closest <see cref="FixedPoint4"/> for a float value, always rounding up.
/// </summary>
public static FixedPoint4 NewCeiling(float value)
{
return new((long) MathF.Ceiling(value * ShiftConstant));
}
public static FixedPoint4 New(double value)
{
return new((long) ApplyFloatEpsilon(value * ShiftConstant));
}
public static FixedPoint4 New(string value)
{
return New(Parse.Float(value));
}
public static FixedPoint4 operator +(FixedPoint4 a) => a;
public static FixedPoint4 operator -(FixedPoint4 a) => new(-a.Value);
public static FixedPoint4 operator +(FixedPoint4 a, FixedPoint4 b)
=> new(a.Value + b.Value);
public static FixedPoint4 operator -(FixedPoint4 a, FixedPoint4 b)
=> new(a.Value - b.Value);
public static FixedPoint4 operator *(FixedPoint4 a, FixedPoint4 b)
{
return new(b.Value * a.Value / ShiftConstant);
}
public static FixedPoint4 operator *(FixedPoint4 a, float b)
{
return new((long) ApplyFloatEpsilon(a.Value * b));
}
public static FixedPoint4 operator *(FixedPoint4 a, double b)
{
return new((long) ApplyFloatEpsilon(a.Value * b));
}
public static FixedPoint4 operator *(FixedPoint4 a, long b)
{
return new(a.Value * b);
}
public static FixedPoint4 operator /(FixedPoint4 a, FixedPoint4 b)
{
return new((long) (ShiftConstant * (long) a.Value / b.Value));
}
public static FixedPoint4 operator /(FixedPoint4 a, float b)
{
return new((long) ApplyFloatEpsilon(a.Value / b));
}
public static bool operator <=(FixedPoint4 a, long b)
{
return a <= New(b);
}
public static bool operator >=(FixedPoint4 a, long b)
{
return a >= New(b);
}
public static bool operator <(FixedPoint4 a, long b)
{
return a < New(b);
}
public static bool operator >(FixedPoint4 a, long b)
{
return a > New(b);
}
public static bool operator ==(FixedPoint4 a, long b)
{
return a == New(b);
}
public static bool operator !=(FixedPoint4 a, long b)
{
return a != New(b);
}
public static bool operator ==(FixedPoint4 a, FixedPoint4 b)
{
return a.Equals(b);
}
public static bool operator !=(FixedPoint4 a, FixedPoint4 b)
{
return !a.Equals(b);
}
public static bool operator <=(FixedPoint4 a, FixedPoint4 b)
{
return a.Value <= b.Value;
}
public static bool operator >=(FixedPoint4 a, FixedPoint4 b)
{
return a.Value >= b.Value;
}
public static bool operator <(FixedPoint4 a, FixedPoint4 b)
{
return a.Value < b.Value;
}
public static bool operator >(FixedPoint4 a, FixedPoint4 b)
{
return a.Value > b.Value;
}
public readonly float Float()
{
return (float) ShiftDown();
}
public readonly double Double()
{
return ShiftDown();
}
public readonly long Long()
{
return Value / ShiftConstant;
}
public readonly int Int()
{
return (int)Long();
}
// Implicit operators ftw
public static implicit operator FixedPoint4(FixedPoint2 n) => New(n.Int());
public static implicit operator FixedPoint4(float n) => New(n);
public static implicit operator FixedPoint4(double n) => New(n);
public static implicit operator FixedPoint4(int n) => New(n);
public static implicit operator FixedPoint4(long n) => New(n);
public static explicit operator FixedPoint2(FixedPoint4 n) => n.Int();
public static explicit operator float(FixedPoint4 n) => n.Float();
public static explicit operator double(FixedPoint4 n) => n.Double();
public static explicit operator int(FixedPoint4 n) => n.Int();
public static explicit operator long(FixedPoint4 n) => n.Long();
public static FixedPoint4 Min(params FixedPoint4[] fixedPoints)
{
return fixedPoints.Min();
}
public static FixedPoint4 Min(FixedPoint4 a, FixedPoint4 b)
{
return a < b ? a : b;
}
public static FixedPoint4 Max(FixedPoint4 a, FixedPoint4 b)
{
return a > b ? a : b;
}
public static long Sign(FixedPoint4 value)
{
if (value < Zero)
{
return -1;
}
if (value > Zero)
{
return 1;
}
return 0;
}
public static FixedPoint4 Abs(FixedPoint4 a)
{
return FromTenThousandths(Math.Abs(a.Value));
}
public static FixedPoint4 Dist(FixedPoint4 a, FixedPoint4 b)
{
return FixedPoint4.Abs(a - b);
}
public static FixedPoint4 Clamp(FixedPoint4 number, FixedPoint4 min, FixedPoint4 max)
{
if (min > max)
{
throw new ArgumentException($"{nameof(min)} {min} cannot be larger than {nameof(max)} {max}");
}
return number < min ? min : number > max ? max : number;
}
public override readonly bool Equals(object? obj)
{
return obj is FixedPoint4 unit &&
Value == unit.Value;
}
public override readonly int GetHashCode()
{
// ReSharper disable once NonReadonlyMemberInGetHashCode
return HashCode.Combine(Value);
}
public void Deserialize(string value)
{
// TODO implement "lossless" serializer.
// I.e., dont use floats.
if (value == "MaxValue")
Value = int.MaxValue;
else
this = New(Parse.Double(value));
}
public override readonly string ToString() => $"{ShiftDown().ToString(CultureInfo.InvariantCulture)}";
public string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public readonly string Serialize()
{
// TODO implement "lossless" serializer.
// I.e., dont use floats.
if (Value == int.MaxValue)
return "MaxValue";
return ToString();
}
public readonly bool Equals(FixedPoint4 other)
{
return Value == other.Value;
}
public readonly int CompareTo(FixedPoint4 other)
{
if (other.Value > Value)
{
return -1;
}
if (other.Value < Value)
{
return 1;
}
return 0;
}
}
public static class FixedPoint4EnumerableExt
{
public static FixedPoint4 Sum(this IEnumerable<FixedPoint4> source)
{
var acc = FixedPoint4.Zero;
foreach (var n in source)
{
acc += n;
}
return acc;
}
}
}

View File

@@ -32,23 +32,23 @@ public sealed class PettableFriendSystem : EntitySystem
{
var (uid, comp) = ent;
var user = args.User;
if (args.Handled || !_exceptionQuery.TryGetComponent(uid, out var exceptionComp))
return;
if (_useDelayQuery.TryGetComponent(uid, out var useDelay) && !_useDelay.TryResetDelay((uid, useDelay), true))
if (args.Handled || !_exceptionQuery.TryComp(uid, out var exceptionComp))
return;
var exception = (uid, exceptionComp);
if (_factionException.IsIgnored(exception, user))
if (!_factionException.IsIgnored(exception, user))
{
_popup.PopupClient(Loc.GetString(comp.FailureString, ("target", uid)), user, user);
// you have made a new friend :)
_popup.PopupClient(Loc.GetString(comp.SuccessString, ("target", uid)), user, user);
_factionException.IgnoreEntity(exception, user);
args.Handled = true;
return;
}
// you have made a new friend :)
_popup.PopupClient(Loc.GetString(comp.SuccessString, ("target", uid)), user, user);
_factionException.IgnoreEntity(exception, user);
args.Handled = true;
if (_useDelayQuery.TryComp(uid, out var useDelay) && !_useDelay.TryResetDelay((uid, useDelay), true))
return;
_popup.PopupClient(Loc.GetString(comp.FailureString, ("target", uid)), user, user);
}
private void OnRehydrated(Entity<PettableFriendComponent> ent, ref GotRehydratedEvent args)

View File

@@ -0,0 +1,21 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared.Ghost.Roles;
[Serializable, NetSerializable]
public sealed class GhostRoleRadioMessage : BoundUserInterfaceMessage
{
public ProtoId<GhostRolePrototype> ProtoId;
public GhostRoleRadioMessage(ProtoId<GhostRolePrototype> protoId)
{
ProtoId = protoId;
}
}
[Serializable, NetSerializable]
public enum GhostRoleRadioUiKey : byte
{
Key
}

View File

@@ -0,0 +1,29 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.Ghost.Roles.Components
{
/// <summary>
/// Allows a ghost to take this role, spawning a new entity.
/// </summary>
[RegisterComponent, EntityCategory("Spawner")]
public sealed partial class GhostRoleMobSpawnerComponent : Component
{
[DataField]
public bool DeleteOnSpawn = true;
[DataField]
public int AvailableTakeovers = 1;
[ViewVariables]
public int CurrentTakeovers = 0;
[DataField]
public EntProtoId? Prototype;
/// <summary>
/// If this ghostrole spawner has multiple selectable ghostrole prototypes.
/// </summary>
[DataField]
public List<string> SelectablePrototypes = [];
}
}

View File

@@ -30,6 +30,13 @@ public sealed partial class GhostRolePrototype : IPrototype
[DataField(required: true)]
public EntProtoId EntityPrototype;
/// <summary>
/// The entity prototype's sprite to use to represent the ghost role
/// Use this if you don't want to use the entity itself
/// </summary>
[DataField]
public EntProtoId? IconPrototype = null;
/// <summary>
/// Rules of the ghostrole
/// </summary>

View File

@@ -1,5 +1,5 @@
using Content.Shared.DisplacementMap;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Inventory;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
@@ -78,11 +78,8 @@ public sealed partial class HandsComponent : Component
[DataField, ViewVariables(VVAccess.ReadWrite)]
public TimeSpan ThrowCooldown = TimeSpan.FromSeconds(0.5f);
/// <summary>
/// CP14 Hands displacements
/// </summary>
[DataField]
public Dictionary<string, InventoryComponent.SlotDisplacementData> Displacements = [];
public DisplacementData? HandDisplacement;
}
[Serializable, NetSerializable]

View File

@@ -3,6 +3,7 @@ using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Content.Shared.Inventory.VirtualItem;
using Content.Shared.Tag;
using Content.Shared.Interaction.Events;
using Robust.Shared.Containers;
using Robust.Shared.Map;
@@ -125,15 +126,13 @@ public abstract partial class SharedHandsSystem
var userXform = Transform(uid);
var isInContainer = ContainerSystem.IsEntityOrParentInContainer(uid, xform: userXform);
DoDrop(uid, hand, doDropInteraction: doDropInteraction, handsComp);
// drop the item inside the container if the user is in a container
if (targetDropLocation == null || isInContainer)
{
TransformSystem.DropNextTo((entity, itemXform), (uid, userXform));
return true;
}
// otherwise, remove the item from their hands and place it at the calculated interaction range position
DoDrop(uid, hand, doDropInteraction: doDropInteraction, handsComp);
var (itemPos, itemRot) = TransformSystem.GetWorldPositionRotation(entity);
var origin = new MapCoordinates(itemPos, itemXform.MapID);
var target = TransformSystem.ToMapCoordinates(targetDropLocation.Value);

View File

@@ -7,12 +7,13 @@ namespace Content.Shared.Humanoid
{
public static bool HasSexMorph(HumanoidVisualLayers layer)
{
return layer switch
{
HumanoidVisualLayers.Chest => true,
HumanoidVisualLayers.Head => true,
_ => false
};
return true; //Support female body
//return layer switch
//{
// HumanoidVisualLayers.Chest => true,
// HumanoidVisualLayers.Head => true,
// _ => false
//};
}
public static string GetSexMorph(HumanoidVisualLayers layer, Sex sex, string id)

View File

@@ -1,4 +1,5 @@
using Robust.Shared.Containers;
using Content.Shared.DisplacementMap;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
@@ -13,21 +14,21 @@ public sealed partial class InventoryComponent : Component
[DataField("speciesId")] public string? SpeciesId { get; set; }
[DataField] public Dictionary<string, SlotDisplacementData> Displacements = [];
public SlotDefinition[] Slots = Array.Empty<SlotDefinition>();
public ContainerSlot[] Containers = Array.Empty<ContainerSlot>();
[DataDefinition]
public sealed partial class SlotDisplacementData
{
[DataField(required: true)]
public PrototypeLayerData Layer = default!;
[DataField]
public Dictionary<string, DisplacementData> Displacements = new();
[DataField]
public PrototypeLayerData? Layer48; //CP14 48*48 displacement support
/// <summary>
/// Alternate displacement maps, which if available, will be selected for the player of the appropriate gender.
/// </summary>
[DataField]
public Dictionary<string, DisplacementData> FemaleDisplacements = new();
[DataField]
public string? ShaderOverride = "DisplacedStencilDraw";
}
/// <summary>
/// Alternate displacement maps, which if available, will be selected for the player of the appropriate gender.
/// </summary>
[DataField]
public Dictionary<string, DisplacementData> MaleDisplacements = new();
}

View File

@@ -26,9 +26,8 @@ namespace Content.Shared.Localizations
public void Initialize()
{
var culture = new CultureInfo(Culture);
// Uncomment for Ru localization
_loc.LoadCulture(culture);
// Uncomment for Ru localization
var fallbackCulture = new CultureInfo("en-US");
_loc.LoadCulture(fallbackCulture);
_loc.SetFallbackCluture(fallbackCulture);

View File

@@ -92,6 +92,13 @@ public sealed partial class LockComponent : Component
[ByRefEvent]
public record struct LockToggleAttemptEvent(EntityUid User, bool Silent = false, bool Cancelled = false);
/// <summary>
/// Event raised on the user when a toggle is attempted.
/// Can be cancelled to prevent it.
/// </summary>
[ByRefEvent]
public record struct UserLockToggleAttemptEvent(EntityUid Target, bool Silent = false, bool Cancelled = false);
/// <summary>
/// Event raised on a lock after it has been toggled.
/// </summary>

View File

@@ -109,17 +109,14 @@ public sealed class LockSystem : EntitySystem
//CrystallPunk Lock System Adapt Start
if (lockComp.LockSlotId != null && _lockCp14.TryGetLockFromSlot(uid, out var lockEnt))
{
args.PushText(Loc.GetString("cp-lock-examine-lock-slot", ("lock", MetaData(lockEnt.Value).EntityName)));
args.PushText(Loc.GetString("cp14-lock-examine-lock-slot", ("lock", MetaData(lockEnt.Value).EntityName)));
args.PushMarkup(Loc.GetString(lockComp.Locked
? "lock-comp-on-examined-is-locked"
: "lock-comp-on-examined-is-unlocked",
("entityName", Identity.Name(uid, EntityManager))));
if (lockEnt.Value.Comp.LockpickeddFailMarkup)
args.PushMarkup(Loc.GetString("cp-lock-examine-lock-lockpicked", ("lock", MetaData(lockEnt.Value).EntityName)));
} else
{
args.PushText(Loc.GetString("cp-lock-examine-lock-null"));
args.PushMarkup(Loc.GetString("cp14-lock-examine-lock-lockpicked", ("lock", MetaData(lockEnt.Value).EntityName)));
}
//CrystallPunk Lock System Adapt End
}
@@ -158,7 +155,7 @@ public sealed class LockSystem : EntitySystem
_sharedPopupSystem.PopupClient(Loc.GetString("lock-comp-do-lock-success",
("entityName", Identity.Name(uid, EntityManager))), uid, user);
_audio.PlayPredicted(lockComp.LockSound, uid, user);
_audio.PlayPvs(lockComp.LockSound, uid);
lockComp.Locked = true;
_appearanceSystem.SetData(uid, LockVisuals.Locked, true);
@@ -189,7 +186,7 @@ public sealed class LockSystem : EntitySystem
("entityName", Identity.Name(uid, EntityManager))), uid, user.Value);
}
_audio.PlayPredicted(lockComp.UnlockSound, uid, user);
_audio.PlayPvs(lockComp.UnlockSound, uid);
lockComp.Locked = false;
_appearanceSystem.SetData(uid, LockVisuals.Locked, false);
@@ -259,7 +256,12 @@ public sealed class LockSystem : EntitySystem
var ev = new LockToggleAttemptEvent(user, quiet);
RaiseLocalEvent(uid, ref ev, true);
return !ev.Cancelled;
if (ev.Cancelled)
return false;
var userEv = new UserLockToggleAttemptEvent(uid, quiet);
RaiseLocalEvent(user, ref userEv, true);
return !userEv.Cancelled;
}
// TODO: this should be a helper on AccessReaderSystem since so many systems copy paste it
@@ -304,7 +306,7 @@ public sealed class LockSystem : EntitySystem
if (!component.Locked || !component.BreakOnEmag)
return;
_audio.PlayPredicted(component.UnlockSound, uid, args.UserUid);
_audio.PlayPvs(component.UnlockSound, uid);
component.Locked = false;
_appearanceSystem.SetData(uid, LockVisuals.Locked, false);
@@ -408,4 +410,3 @@ public sealed class LockSystem : EntitySystem
_activatableUI.CloseAll(uid);
}
}

View File

@@ -0,0 +1,18 @@
using Content.Shared.Whitelist;
using Robust.Shared.GameStates;
namespace Content.Shared.Lock;
/// <summary>
/// Adds whitelist and blacklist for this mob to lock things.
/// The whitelist and blacklist are checked against the object being locked, not the mob.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(LockingWhitelistSystem))]
public sealed partial class LockingWhitelistComponent : Component
{
[DataField]
public EntityWhitelist? Whitelist;
[DataField]
public EntityWhitelist? Blacklist;
}

View File

@@ -0,0 +1,28 @@
using Content.Shared.Popups;
using Content.Shared.Whitelist;
namespace Content.Shared.Lock;
public sealed class LockingWhitelistSystem : EntitySystem
{
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<LockingWhitelistComponent, UserLockToggleAttemptEvent>(OnUserLockToggleAttempt);
}
private void OnUserLockToggleAttempt(Entity<LockingWhitelistComponent> ent, ref UserLockToggleAttemptEvent args)
{
if (_whitelistSystem.CheckBoth(args.Target, ent.Comp.Blacklist, ent.Comp.Whitelist))
return;
if (!args.Silent)
_popupSystem.PopupClient(Loc.GetString("locking-whitelist-component-lock-toggle-deny"), ent.Owner);
args.Cancelled = true;
}
}

View File

@@ -58,7 +58,7 @@ public enum CollisionGroup
// Tabletop machines, windoors, firelocks
TabletopMachineMask = Impassable | HighImpassable,
// Tabletop machines
TabletopMachineLayer = Opaque | HighImpassable | BulletImpassable,
TabletopMachineLayer = Opaque | BulletImpassable,
// Airlocks, windoors, firelocks
GlassAirlockLayer = HighImpassable | MidImpassable | BulletImpassable | InteractImpassable,

View File

@@ -120,7 +120,10 @@ public abstract partial class SharedProjectileSystem : EntitySystem
if (component.Offset != Vector2.Zero)
{
_transform.SetLocalPosition(uid, xform.LocalPosition + xform.LocalRotation.RotateVec(component.Offset),
var rotation = xform.LocalRotation;
if (TryComp<ThrowingAngleComponent>(uid, out var throwingAngleComp))
rotation += throwingAngleComp.Angle;
_transform.SetLocalPosition(uid, xform.LocalPosition + rotation.RotateVec(component.Offset),
xform);
}

View File

@@ -239,14 +239,14 @@ public sealed partial class EncryptionKeySystem : EntitySystem
{
var msg = Loc.GetString("examine-headset-default-channel",
("prefix", SharedChatSystem.DefaultChannelPrefix),
("channel", defaultChannel),
("channel", proto.LocalizedName),
("color", proto.Color));
examineEvent.PushMarkup(msg);
}
if (HasComp<EncryptionKeyComponent>(examineEvent.Examined))
{
var msg = Loc.GetString("examine-encryption-default-channel",
("channel", defaultChannel),
("channel", proto.LocalizedName),
("color", proto.Color));
examineEvent.PushMarkup(msg);
}

View File

@@ -9,7 +9,7 @@ public sealed partial class RadioChannelPrototype : IPrototype
/// Human-readable name for the channel.
/// </summary>
[DataField("name")]
public string Name { get; private set; } = string.Empty;
public LocId Name { get; private set; } = string.Empty;
[ViewVariables(VVAccess.ReadOnly)]
public string LocalizedName => Loc.GetString(Name);

View File

@@ -6,7 +6,7 @@ namespace Content.Shared.Silicons.Laws;
[Virtual, DataDefinition]
[Serializable, NetSerializable]
public partial class SiliconLaw : IComparable<SiliconLaw>
public partial class SiliconLaw : IComparable<SiliconLaw>, IEquatable<SiliconLaw>
{
/// <summary>
/// A locale string which is the actual text of the law.
@@ -39,13 +39,27 @@ public partial class SiliconLaw : IComparable<SiliconLaw>
return Order.CompareTo(other.Order);
}
public bool Equals(SiliconLaw other)
public bool Equals(SiliconLaw? other)
{
if (other == null)
return false;
return LawString == other.LawString
&& Order == other.Order
&& LawIdentifierOverride == other.LawIdentifierOverride;
}
public override bool Equals(object? obj)
{
if (obj == null)
return false;
return Equals(obj as SiliconLaw);
}
public override int GetHashCode()
{
return HashCode.Combine(LawString, Order, LawIdentifierOverride);
}
/// <summary>
/// Return a shallow clone of this law.
/// </summary>

View File

@@ -1,7 +1,9 @@
namespace Content.Shared.Slippery
using Robust.Shared.GameStates;
namespace Content.Shared.Slippery;
[RegisterComponent, NetworkedComponent]
public sealed partial class NoSlipComponent : Component
{
[RegisterComponent]
public sealed partial class NoSlipComponent : Component
{
}
}

View File

@@ -0,0 +1,16 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Station.Components;
/// <summary>
/// Indicates that a grid is a member of the given station.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class StationMemberComponent : Component
{
/// <summary>
/// Station that this grid is a part of.
/// </summary>
[DataField]
public EntityUid Station = EntityUid.Invalid;
}

View File

@@ -1,4 +1,4 @@
using Content.Shared.Storage.EntitySystems;
using Content.Shared.Storage.EntitySystems;
using Content.Shared.Whitelist;
namespace Content.Shared.Storage.Components

View File

@@ -26,6 +26,7 @@ using Content.Shared.Storage.Components;
using Content.Shared.Timing;
using Content.Shared.Verbs;
using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
@@ -69,6 +70,9 @@ public abstract class SharedStorageSystem : EntitySystem
public const string DefaultStorageMaxItemSize = "Normal";
public const float AreaInsertDelayPerItem = 0.075f;
private static AudioParams _audioParams = AudioParams.Default
.WithMaxDistance(7f)
.WithVolume(-2f);
private ItemSizePrototype _defaultStorageMaxItemSize = default!;
@@ -549,7 +553,7 @@ public abstract class SharedStorageSystem : EntitySystem
// If we picked up at least one thing, play a sound and do a cool animation!
if (successfullyInserted.Count > 0)
{
Audio.PlayPredicted(component.StorageInsertSound, uid, args.User);
Audio.PlayPredicted(component.StorageInsertSound, uid, args.User, _audioParams);
EntityManager.RaiseSharedEvent(new AnimateInsertingEntitiesEvent(
GetNetEntity(uid),
GetNetEntityList(successfullyInserted),
@@ -610,7 +614,7 @@ public abstract class SharedStorageSystem : EntitySystem
{
if (_sharedHandsSystem.TryPickupAnyHand(player, entity, handsComp: hands)
&& storageComp.StorageRemoveSound != null)
Audio.PlayPredicted(storageComp.StorageRemoveSound, uid, player);
Audio.PlayPredicted(storageComp.StorageRemoveSound, uid, player, _audioParams);
{
return;
}
@@ -670,7 +674,7 @@ public abstract class SharedStorageSystem : EntitySystem
return;
TransformSystem.DropNextTo(itemEnt, player);
Audio.PlayPredicted(storageComp.StorageRemoveSound, storageEnt, player);
Audio.PlayPredicted(storageComp.StorageRemoveSound, storageEnt, player, _audioParams);
}
private void OnInsertItemIntoLocation(StorageInsertItemIntoLocationEvent msg, EntitySessionEventArgs args)
@@ -813,7 +817,11 @@ public abstract class SharedStorageSystem : EntitySystem
_appearance.SetData(uid, StorageVisuals.Capacity, capacity, appearance);
_appearance.SetData(uid, StorageVisuals.Open, isOpen, appearance);
_appearance.SetData(uid, SharedBagOpenVisuals.BagState, isOpen ? SharedBagState.Open : SharedBagState.Closed, appearance);
_appearance.SetData(uid, StackVisuals.Hide, !isOpen, appearance);
// HideClosedStackVisuals true sets the StackVisuals.Hide to the open state of the storage.
// This is for containers that only show their contents when open. (e.g. donut boxes)
if (storage.HideStackVisualsWhenClosed)
_appearance.SetData(uid, StackVisuals.Hide, !isOpen, appearance);
}
/// <summary>
@@ -839,7 +847,7 @@ public abstract class SharedStorageSystem : EntitySystem
Insert(target, entity, out _, user: user, targetComp, playSound: false);
}
Audio.PlayPredicted(sourceComp.StorageInsertSound, target, user);
Audio.PlayPredicted(sourceComp.StorageInsertSound, target, user, _audioParams);
}
/// <summary>
@@ -1018,7 +1026,7 @@ public abstract class SharedStorageSystem : EntitySystem
return false;
if (playSound)
Audio.PlayPredicted(storageComp.StorageInsertSound, uid, user);
Audio.PlayPredicted(storageComp.StorageInsertSound, uid, user, _audioParams);
return true;
}
@@ -1048,7 +1056,7 @@ public abstract class SharedStorageSystem : EntitySystem
}
if (playSound)
Audio.PlayPredicted(storageComp.StorageInsertSound, uid, user);
Audio.PlayPredicted(storageComp.StorageInsertSound, uid, user, _audioParams);
return true;
}

View File

@@ -123,6 +123,13 @@ namespace Content.Shared.Storage
[DataField, ViewVariables(VVAccess.ReadWrite)]
public StorageDefaultOrientation? DefaultStorageOrientation;
/// <summary>
/// If true, sets StackVisuals.Hide to true when the container is closed
/// Used in cases where there are sprites that are shown when the container is open but not
/// when it is closed
/// </summary>
[DataField]
public bool HideStackVisualsWhenClosed = true;
/// <summary>
/// these items will be interrupted as early as the attempted interaction stage. Allows you to use some tools

View File

@@ -45,7 +45,7 @@ public sealed class SwapTeleporterSystem : EntitySystem
private void OnInteract(Entity<SwapTeleporterComponent> ent, ref AfterInteractEvent args)
{
var (uid, comp) = ent;
if (args.Target == null)
if (args.Target == null || !args.CanReach)
return;
var target = args.Target.Value;
@@ -164,9 +164,9 @@ public sealed class SwapTeleporterSystem : EntitySystem
return;
}
_popup.PopupEntity(Loc.GetString("swap-teleporter-popup-teleport-other",
_popup.PopupClient(Loc.GetString("swap-teleporter-popup-teleport-other",
("entity", Identity.Entity(linkedEnt, EntityManager))),
otherTeleEnt,
teleEnt,
otherTeleEnt,
PopupType.MediumCaution);
_transform.SwapPositions(teleEnt, otherTeleEnt);

View File

@@ -13,6 +13,14 @@
public EntityUid ItemUid { get; }
}
/// <summary>
/// Raised on the item entity that is thrown.
/// </summary>
/// <param name="User">The user that threw this entity.</param>
/// <param name="Cancelled">Whether or not the throw should be cancelled.</param>
[ByRefEvent]
public record struct ThrowItemAttemptEvent(EntityUid User, bool Cancelled = false);
/// <summary>
/// Raised when we try to pushback an entity from throwing
/// </summary>

View File

@@ -88,5 +88,7 @@ namespace Content.Shared.Verbs
public static readonly VerbCategory SelectType = new("verb-categories-select-type", null);
public static readonly VerbCategory PowerLevel = new("verb-categories-power-level", null);
public static readonly VerbCategory CP14Craft = new("cp14-verb-categories-craft", null); //CP14
}
}

View File

@@ -4,4 +4,4 @@ namespace Content.Shared.Weapons.Melee.Events;
/// Raised directed on a weapon when attempt a melee attack.
/// </summary>
[ByRefEvent]
public record struct AttemptMeleeEvent(bool Cancelled, string? Message);
public record struct AttemptMeleeEvent(EntityUid User, bool Cancelled = false, string? Message = null);

View File

@@ -15,4 +15,7 @@ public sealed partial class GunRequiresWieldComponent : Component
[DataField, AutoNetworkedField]
public TimeSpan PopupCooldown = TimeSpan.FromSeconds(1);
[DataField]
public LocId? WieldRequiresExamineMessage = "gunrequireswield-component-examine";
}

View File

@@ -76,6 +76,7 @@ public abstract partial class SharedGunSystem
args.Handled = true;
// Continuous loading
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, component.FillDelay, new AmmoFillDoAfterEvent(), used: uid, target: args.Target, eventTarget: uid)
{
BreakOnMove = true,

View File

@@ -476,7 +476,7 @@ public abstract partial class SharedGunSystem : EntitySystem
return;
var ev = new MuzzleFlashEvent(GetNetEntity(gun), sprite, worldAngle);
CreateEffect(gun, ev, user);
CreateEffect(gun, ev, gun);
}
public void CauseImpulse(EntityCoordinates fromCoordinates, EntityCoordinates toCoordinates, EntityUid user, PhysicsComponent userPhysics)

View File

@@ -23,6 +23,23 @@ public sealed class EntityWhitelistSystem : EntitySystem
return uid != null && IsValid(list, uid.Value);
}
/// <summary>
/// Checks whether a given entity is allowed by a whitelist and not blocked by a blacklist.
/// If a blacklist is provided and it matches then this returns false.
/// If a whitelist is provided and it does not match then this returns false.
/// If either list is null it does not get checked.
/// </summary>
public bool CheckBoth([NotNullWhen(true)] EntityUid? uid, EntityWhitelist? blacklist = null, EntityWhitelist? whitelist = null)
{
if (uid == null)
return false;
if (blacklist != null && IsValid(blacklist, uid))
return false;
return whitelist == null || IsValid(whitelist, uid);
}
/// <summary>
/// Checks whether a given entity satisfies a whitelist.
/// </summary>

View File

@@ -47,6 +47,7 @@ public sealed class WieldableSystem : EntitySystem
SubscribeLocalEvent<WieldableComponent, HandDeselectedEvent>(OnDeselectWieldable);
SubscribeLocalEvent<MeleeRequiresWieldComponent, AttemptMeleeEvent>(OnMeleeAttempt);
SubscribeLocalEvent<GunRequiresWieldComponent, ExaminedEvent>(OnExamineRequires);
SubscribeLocalEvent<GunRequiresWieldComponent, ShotAttemptedEvent>(OnShootAttempt);
SubscribeLocalEvent<GunWieldBonusComponent, ItemWieldedEvent>(OnGunWielded);
SubscribeLocalEvent<GunWieldBonusComponent, ItemUnwieldedEvent>(OnGunUnwielded);
@@ -116,8 +117,17 @@ public sealed class WieldableSystem : EntitySystem
}
}
private void OnExamineRequires(Entity<GunRequiresWieldComponent> entity, ref ExaminedEvent args)
{
if(entity.Comp.WieldRequiresExamineMessage != null)
args.PushText(Loc.GetString(entity.Comp.WieldRequiresExamineMessage));
}
private void OnExamine(EntityUid uid, GunWieldBonusComponent component, ref ExaminedEvent args)
{
if (HasComp<GunRequiresWieldComponent>(uid))
return;
if (component.WieldBonusExamineMessage != null)
args.PushText(Loc.GetString(component.WieldBonusExamineMessage));
}

View File

@@ -0,0 +1,18 @@
namespace Content.Shared._CP14.Currency;
/// <summary>
/// Reflects the market value of an item, to guide players through the economy.
/// </summary>
[RegisterComponent, Access(typeof(CP14CurrencySystem))]
public sealed partial class CP14CurrencyComponent : Component
{
[DataField]
public int Currency = 1;
/// <summary>
/// allows you to categorize different valuable items in order to, for example, give goals for buying weapons, or earning money specifically.
/// </summary>
[DataField]
public string? Category;
}

View File

@@ -0,0 +1,62 @@
using Content.Shared.Examine;
using Content.Shared.Stacks;
namespace Content.Shared._CP14.Currency;
public sealed partial class CP14CurrencySystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14CurrencyComponent, ExaminedEvent>(OnExamine);
}
private void OnExamine(Entity<CP14CurrencyComponent> currency, ref ExaminedEvent args)
{
var total = GetTotalCurrency(currency, currency.Comp);
var push = Loc.GetString("cp14-currency-examine-title");
push += GetPrettyCurrency(total);
args.PushMarkup(push);
}
public string GetPrettyCurrency(int currency)
{
var total = currency;
if (total <= 0)
return string.Empty;
var gp = total / 100;
total %= 100;
var sp = total / 10;
total %= 10;
var cp = total;
var push = string.Empty;
if (gp > 0) push += " " + Loc.GetString("cp14-currency-examine-gp", ("coin", gp));
if (sp > 0) push += " " + Loc.GetString("cp14-currency-examine-sp", ("coin", sp));
if (cp > 0) push += " " + Loc.GetString("cp14-currency-examine-cp", ("coin", cp));
return push;
}
public int GetTotalCurrency(EntityUid uid, CP14CurrencyComponent? currency = null)
{
if (!Resolve(uid, ref currency))
return 0;
var total = currency.Currency;
if (TryComp<StackComponent>(uid, out var stack))
{
total *= stack.Count;
}
return total;
}
}

View File

@@ -28,7 +28,6 @@ public sealed class SharedCP14LockKeySystem : EntitySystem
private const int DepthComplexity = 2; //TODO - fix this constant duplication from KeyholeGenerationSystem.cs
public override void Initialize()
{
base.Initialize();
@@ -54,7 +53,6 @@ public sealed class SharedCP14LockKeySystem : EntitySystem
if (!TryComp<StorageComponent>(keyring, out var storageComp))
return;
if (TryComp<LockComponent>(args.Target, out var lockComp) &&
TryGetLockFromSlot(args.Target.Value, out var lockEnt))
{
@@ -71,7 +69,7 @@ public sealed class SharedCP14LockKeySystem : EntitySystem
args.Handled = true;
return;
}
_popup.PopupEntity(Loc.GetString("cp-lock-keyring-use-nofit"), args.Target.Value, args.User);
_popup.PopupEntity(Loc.GetString("cp14-lock-keyring-use-nofit"), args.Target.Value, args.User);
}
}
@@ -117,10 +115,11 @@ public sealed class SharedCP14LockKeySystem : EntitySystem
{
TryHackDoorElement(user, target, lockpick, lockItemComp, lockComp, height);
},
Text = Loc.GetString("cp-lock-verb-lockpick-use-text") + $" {height}",
Message = Loc.GetString("cp-lock-verb-lockpick-use-message"),
Text = Loc.GetString("cp14-lock-verb-lockpick-use-text") + $" {height}",
Message = Loc.GetString("cp14-lock-verb-lockpick-use-message"),
Category = VerbCategory.Lockpick,
Priority = height,
CloseMenu = false,
};
args.Verbs.Add(verb);
@@ -141,19 +140,19 @@ public sealed class SharedCP14LockKeySystem : EntitySystem
if (lockComp.Locked)
{
_lock.TryUnlock(target, user, lockComp);
_popup.PopupEntity(Loc.GetString("cp-lock-unlock-lock", ("lock", MetaData(lockEnt.Owner).EntityName)), target, user);
_popup.PopupEntity(Loc.GetString("cp14-lock-unlock-lock", ("lock", MetaData(lockEnt.Owner).EntityName)), target, user);
lockEnt.LockpickStatus = 0;
return true;
}
else
{
_lock.TryLock(target, user, lockComp);
_popup.PopupEntity(Loc.GetString("cp-lock-lock-lock", ("lock", MetaData(lockEnt.Owner).EntityName)), target, user);
_popup.PopupEntity(Loc.GetString("cp14-lock-lock-lock", ("lock", MetaData(lockEnt.Owner).EntityName)), target, user);
lockEnt.LockpickStatus = 0;
return true;
}
}
_popup.PopupEntity(Loc.GetString("cp-lock-lockpick-success"), target, user);
_popup.PopupEntity(Loc.GetString("cp14-lock-lockpick-success"), target, user);
return true;
}
else //Fail
@@ -164,16 +163,16 @@ public sealed class SharedCP14LockKeySystem : EntitySystem
lockpick.Comp.Health--;
if (lockpick.Comp.Health > 0)
{
_popup.PopupEntity(Loc.GetString("cp-lock-lockpick-failed-damage", ("lock", MetaData(lockEnt.Owner).EntityName)), target, user);
_popup.PopupEntity(Loc.GetString("cp14-lock-lockpick-failed-damage", ("lock", MetaData(lockEnt.Owner).EntityName)), target, user);
} else
{
_popup.PopupEntity(Loc.GetString("cp-lock-lockpick-failed-break", ("lock", MetaData(lockEnt.Owner).EntityName)), target, user);
_popup.PopupEntity(Loc.GetString("cp14-lock-lockpick-failed-break", ("lock", MetaData(lockEnt.Owner).EntityName)), target, user);
QueueDel(lockpick);
}
}
else
{
_popup.PopupEntity(Loc.GetString("cp-lock-lockpick-failed", ("lock", MetaData(lockEnt.Owner).EntityName)), target, user);
_popup.PopupEntity(Loc.GetString("cp14-lock-lockpick-failed", ("lock", MetaData(lockEnt.Owner).EntityName)), target, user);
}
lockEnt.LockpickeddFailMarkup = true;
lockEnt.LockpickStatus = 0;
@@ -205,8 +204,8 @@ public sealed class SharedCP14LockKeySystem : EntitySystem
TryUseKeyOnLock(user, target, key, new Entity<CP14LockComponent>(target, lockItemComp));
},
IconEntity = GetNetEntity(key),
Text = Loc.GetString(lockComp.Locked ? "cp-lock-verb-use-key-text-open" : "cp-lock-verb-use-key-text-close", ("item", MetaData(args.Target).EntityName)),
Message = Loc.GetString("cp-lock-verb-use-key-message", ("item", MetaData(args.Target).EntityName))
Text = Loc.GetString(lockComp.Locked ? "cp14-lock-verb-use-key-text-open" : "cp14-lock-verb-use-key-text-close", ("item", MetaData(args.Target).EntityName)),
Message = Loc.GetString("cp14-lock-verb-use-key-message", ("item", MetaData(args.Target).EntityName)),
};
args.Verbs.Add(verb);
@@ -220,14 +219,10 @@ public sealed class SharedCP14LockKeySystem : EntitySystem
if (args.Container.ID != lockSlot.Comp.LockSlotId)
return;
if (!TryComp<CP14LockComponent>(args.EntityUid, out var lockComp))
{
args.Cancel();
if (TryComp<CP14LockComponent>(args.EntityUid, out var lockComp))
return;
}
if (lockComp == null)
return;
args.Cancel();
//if (lockComp.Locked)
//{
@@ -309,18 +304,18 @@ public sealed class SharedCP14LockKeySystem : EntitySystem
if (lockComp.Locked)
{
if(_lock.TryUnlock(target, user))
_popup.PopupEntity(Loc.GetString("cp-lock-unlock-lock", ("lock", MetaData(lockEnt).EntityName)), lockEnt, user);
_popup.PopupEntity(Loc.GetString("cp14-lock-unlock-lock", ("lock", MetaData(lockEnt).EntityName)), lockEnt, user);
}
else
{
if (_lock.TryLock(target, user))
_popup.PopupEntity(Loc.GetString("cp-lock-lock-lock", ("lock", MetaData(lockEnt).EntityName)), lockEnt, user);
_popup.PopupEntity(Loc.GetString("cp14-lock-lock-lock", ("lock", MetaData(lockEnt).EntityName)), lockEnt, user);
}
return true;
}
else
{
_popup.PopupEntity(Loc.GetString("cp-lock-key-use-nofit"), lockEnt, user);
_popup.PopupEntity(Loc.GetString("cp14-lock-key-use-nofit"), lockEnt, user);
}
return false;
}

View File

@@ -0,0 +1,16 @@
namespace Content.Shared._CP14.MagicAttuning;
/// <summary>
/// Reflects the fact that this subject can be focused on (Magical attune as a mechanic from DnD.)
/// </summary>
[RegisterComponent, Access(typeof(CP14SharedMagicAttuningSystem))]
public sealed partial class CP14MagicAttuningItemComponent : Component
{
/// <summary>
/// how long it takes to focus on that object
/// </summary>
[DataField]
public TimeSpan FocusTime = TimeSpan.FromSeconds(5f);
public Entity<CP14MagicAttuningMindComponent>? Link = null;
}

View File

@@ -0,0 +1,22 @@
namespace Content.Shared._CP14.MagicAttuning;
/// <summary>
/// A mind that can focus on objects
/// </summary>
[RegisterComponent, Access(typeof(CP14SharedMagicAttuningSystem))]
public sealed partial class CP14MagicAttuningMindComponent : Component
{
[DataField]
public int MaxAttuning = 3;
/// <summary>
/// The entities that this being is focused on
/// </summary>
[DataField]
public List<EntityUid> AttunedTo = new();
/// <summary>
/// cheat: if added to an entity with MindContainer, automatically copied to the mind, removing it from the body. This is to make it easy to add the component to prototype creatures.
/// </summary>
[DataField]
public bool AutoCopyToMind = false;
}

View File

@@ -0,0 +1,238 @@
using Content.Shared.DoAfter;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Popups;
using Content.Shared.Verbs;
using Robust.Shared.Serialization;
namespace Content.Shared._CP14.MagicAttuning;
/// <summary>
/// This system controls the customization to magic items by the players.
/// </summary>
public sealed partial class CP14SharedMagicAttuningSystem : EntitySystem
{
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14MagicAttuningItemComponent, GetVerbsEvent<InteractionVerb>>(OnInteractionVerb);
SubscribeLocalEvent<CP14MagicAttuningMindComponent, CP14MagicAttuneDoAfterEvent>(OnAttuneDoAfter);
SubscribeLocalEvent<CP14MagicAttuningMindComponent, MindAddedMessage>(OnMindAdded);
}
private void OnMindAdded(Entity<CP14MagicAttuningMindComponent> ent, ref MindAddedMessage args)
{
if (!ent.Comp.AutoCopyToMind)
return;
if (HasComp<MindComponent>(ent))
return;
if (!_mind.TryGetMind(ent, out var mindId, out var mind))
return;
if (!HasComp<CP14MagicAttuningMindComponent>(mindId))
{
var attuneMind = AddComp<CP14MagicAttuningMindComponent>(mindId);
attuneMind.MaxAttuning = ent.Comp.MaxAttuning;
}
}
public bool IsAttunedTo(EntityUid mind, EntityUid item)
{
if (!TryComp<CP14MagicAttuningItemComponent>(item, out var attuningItem))
return false;
if (!TryComp<CP14MagicAttuningMindComponent>(mind, out var attuningMind))
return false;
return attuningMind.AttunedTo.Contains(item);
}
private void OnInteractionVerb(Entity<CP14MagicAttuningItemComponent> attuningItem, ref GetVerbsEvent<InteractionVerb> args)
{
if (!args.CanAccess || !args.CanInteract)
return;
if (!_mind.TryGetMind(args.User, out var mindId, out var mind))
return;
if (!TryComp<CP14MagicAttuningMindComponent>(mindId, out var attumingMind))
return;
var user = args.User;
if (attumingMind.AttunedTo.Contains(args.Target))
{
args.Verbs.Add(new()
{
Act = () =>
{
RemoveAttune((mindId, attumingMind), attuningItem);
},
Text = Loc.GetString("cp14-magic-deattuning-verb-text"),
Message = Loc.GetString("cp14-magic-attuning-verb-message"),
});
}
else
{
args.Verbs.Add(new()
{
Act = () =>
{
TryStartAttune(user, attuningItem);
},
Text = Loc.GetString("cp14-magic-attuning-verb-text"),
Message = Loc.GetString("cp14-magic-attuning-verb-message"),
});
}
}
public bool TryStartAttune(EntityUid user, Entity<CP14MagicAttuningItemComponent> item)
{
if (!_mind.TryGetMind(user, out var mindId, out var mind))
return false;
if (!TryComp<CP14MagicAttuningMindComponent>(mindId, out var attuningMind))
return false;
if (attuningMind.MaxAttuning <= 0)
return false;
//if there's an overabundance of ties, we report that the oldest one is torn.
if (attuningMind.AttunedTo.Count >= attuningMind.MaxAttuning)
{
var oldestAttune = attuningMind.AttunedTo[0];
_popup.PopupEntity(Loc.GetString("cp14-magic-attune-oldest-forgot", ("item", MetaData(oldestAttune).EntityName)), user, user);
}
//we notify the current owner of the item that someone is cutting ties.
if (item.Comp.Link is not null &&
item.Comp.Link.Value.Owner != mindId &&
TryComp<MindComponent>(item.Comp.Link.Value.Owner, out var ownerMind) &&
ownerMind.OwnedEntity is not null)
{
_popup.PopupEntity(Loc.GetString("cp14-magic-attune-oldest-forgot", ("item", MetaData(item).EntityName)), ownerMind.OwnedEntity.Value, ownerMind.OwnedEntity.Value);
}
var doAfterArgs = new DoAfterArgs(EntityManager,
user,
item.Comp.FocusTime,
new CP14MagicAttuneDoAfterEvent(),
mindId,
item)
{
BreakOnDamage = true,
BreakOnMove = true,
DistanceThreshold = 2f,
BlockDuplicate = true,
};
_doAfter.TryStartDoAfter(doAfterArgs);
return true;
}
private void OnAttuneDoAfter(Entity<CP14MagicAttuningMindComponent> ent, ref CP14MagicAttuneDoAfterEvent args)
{
if (args.Cancelled || args.Handled || args.Target is null)
return;
if (ent.Comp.AttunedTo.Count >= ent.Comp.MaxAttuning)
{
var oldestAttune = ent.Comp.AttunedTo[0];
RemoveAttune(ent, oldestAttune);
}
AddAttune(ent, args.Target.Value);
}
private void RemoveAttune(Entity<CP14MagicAttuningMindComponent> attuningMind, EntityUid item)
{
if (!attuningMind.Comp.AttunedTo.Contains(item))
return;
attuningMind.Comp.AttunedTo.Remove(item);
if (!TryComp<CP14MagicAttuningItemComponent>(item, out var attuningItem))
return;
if (!TryComp<MindComponent>(attuningMind, out var mind))
return;
attuningItem.Link = null;
var ev = new RemovedAttuneFromMindEvent(attuningMind, mind.OwnedEntity, item);
RaiseLocalEvent(attuningMind, ev);
RaiseLocalEvent(item, ev);
if (mind.OwnedEntity is not null)
{
_popup.PopupEntity(Loc.GetString("cp14-magic-attune-oldest-forgot-end", ("item", MetaData(item).EntityName)), mind.OwnedEntity.Value, mind.OwnedEntity.Value);
}
}
private void AddAttune(Entity<CP14MagicAttuningMindComponent> attuningMind, EntityUid item)
{
if (attuningMind.Comp.AttunedTo.Contains(item))
return;
if (!TryComp<CP14MagicAttuningItemComponent>(item, out var attuningItem))
return;
if (!TryComp<MindComponent>(attuningMind, out var mind))
return;
if (attuningItem.Link is not null)
RemoveAttune(attuningItem.Link.Value, item);
attuningMind.Comp.AttunedTo.Add(item);
attuningItem.Link = attuningMind;
var ev = new AddedAttuneToMindEvent(attuningMind, mind.OwnedEntity, item);
RaiseLocalEvent(attuningMind, ev);
RaiseLocalEvent(item, ev);
}
}
[Serializable, NetSerializable]
public sealed partial class CP14MagicAttuneDoAfterEvent : SimpleDoAfterEvent
{
}
/// <summary>
/// is evoked on both the item and the mind when a new connection between them appears.
/// </summary>
public sealed class AddedAttuneToMindEvent : EntityEventArgs
{
public readonly EntityUid Mind;
public readonly EntityUid? User;
public readonly EntityUid Item;
public AddedAttuneToMindEvent(EntityUid mind, EntityUid? user, EntityUid item)
{
Mind = mind;
User = user;
Item = item;
}
}
/// <summary>
/// is evoked on both the item and the mind when the connection is broken
/// </summary>
public sealed class RemovedAttuneFromMindEvent : EntityEventArgs
{
public readonly EntityUid Mind;
public readonly EntityUid? User;
public readonly EntityUid Item;
public RemovedAttuneFromMindEvent(EntityUid mind, EntityUid? user, EntityUid item)
{
Mind = mind;
User = user;
Item = item;
}
}

View File

@@ -1,4 +1,6 @@
using Content.Shared.Alert;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicEnergy.Components;
@@ -13,4 +15,7 @@ public sealed partial class CP14MagicEnergyContainerComponent : Component
[DataField]
public FixedPoint2 MaxEnergy = 100f;
[DataField]
public ProtoId<AlertPrototype>? MagicAlert = null;
}

View File

@@ -1,16 +0,0 @@
using Content.Shared.Inventory;
namespace Content.Shared._CP14.MagicEnergy.Components;
/// <summary>
/// Controls the strength of the PointLight component, depending on the amount of mana in the object
/// </summary>
[RegisterComponent, Access(typeof(SharedCP14MagicEnergySystem))]
public sealed partial class CP14MagicEnergyPointLightControllerComponent : Component
{
[DataField]
public float MaxEnergy = 1f;
[DataField]
public float MinEnergy = 0f;
}

View File

@@ -1,15 +1,37 @@
using Content.Shared._CP14.MagicEnergy.Components;
using Content.Shared.Examine;
using Content.Shared.Alert;
using Content.Shared.FixedPoint;
using Content.Shared.Inventory;
using Content.Shared.Rounding;
namespace Content.Shared._CP14.MagicEnergy;
public partial class SharedCP14MagicEnergySystem : EntitySystem
{
[Dependency] private readonly AlertsSystem _alerts = default!;
public override void Initialize()
{
SubscribeLocalEvent<CP14MagicEnergyContainerComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<CP14MagicEnergyContainerComponent, ComponentShutdown>(OnComponentShutdown);
}
private void OnComponentStartup(Entity<CP14MagicEnergyContainerComponent> ent, ref ComponentStartup args)
{
UpdateMagicAlert(ent);
}
private void OnComponentShutdown(Entity<CP14MagicEnergyContainerComponent> ent, ref ComponentShutdown args)
{
if (ent.Comp.MagicAlert == null)
return;
_alerts.ClearAlert(ent, ent.Comp.MagicAlert.Value);
}
public string GetEnergyExaminedText(EntityUid uid, CP14MagicEnergyContainerComponent ent)
{
var power = (int)((ent.Energy / ent.MaxEnergy) * 100);
var power = (int)(ent.Energy / ent.MaxEnergy * 100);
var color = "#3fc488";
if (power < 66)
@@ -22,6 +44,92 @@ public partial class SharedCP14MagicEnergySystem : EntitySystem
("power", power),
("color", color));
}
public void ChangeEnergy(EntityUid uid, CP14MagicEnergyContainerComponent component, FixedPoint2 energy, bool safe = false)
{
if (!safe)
{
//Overload
if (component.Energy + energy > component.MaxEnergy)
{
RaiseLocalEvent(uid, new CP14MagicEnergyOverloadEvent()
{
OverloadEnergy = (component.Energy + energy) - component.MaxEnergy,
});
}
//Burn out
if (component.Energy + energy < 0)
{
RaiseLocalEvent(uid, new CP14MagicEnergyBurnOutEvent()
{
BurnOutEnergy = -energy - component.Energy
});
}
}
var oldEnergy = component.Energy;
var newEnergy = Math.Clamp((float)component.Energy + (float)energy, 0, (float)component.MaxEnergy);
component.Energy = newEnergy;
if (oldEnergy != newEnergy)
{
RaiseLocalEvent(uid, new CP14MagicEnergyLevelChangeEvent()
{
OldValue = component.Energy,
NewValue = newEnergy,
MaxValue = component.MaxEnergy,
});
}
UpdateMagicAlert((uid, component));
}
public bool HasEnergy(EntityUid uid, FixedPoint2 energy, CP14MagicEnergyContainerComponent? component = null, bool safe = false)
{
if (!Resolve(uid, ref component))
return false;
if (safe == false)
return true;
return component.Energy > energy;
}
public bool TryConsumeEnergy(EntityUid uid, FixedPoint2 energy, CP14MagicEnergyContainerComponent? component = null, bool safe = false)
{
if (!Resolve(uid, ref component))
return false;
if (energy <= 0)
return true;
// Attempting to absorb more energy than is contained in the container available only in non-safe methods (with container destruction)
if (component.Energy < energy)
{
if (safe)
{
return false;
}
else
{
ChangeEnergy(uid, component, -energy, safe);
return true;
}
}
ChangeEnergy(uid, component, -energy, safe);
return true;
}
private void UpdateMagicAlert(Entity<CP14MagicEnergyContainerComponent> ent)
{
if (ent.Comp.MagicAlert == null)
return;
var level = ContentHelpers.RoundToLevels(MathF.Max(0f, (float) ent.Comp.Energy), (float) ent.Comp.MaxEnergy, 10);
_alerts.ShowAlert(ent, ent.Comp.MagicAlert.Value, (short)level);
}
}
/// <summary>

View File

@@ -0,0 +1,317 @@
using Content.Shared._CP14.MagicEnergy;
using Content.Shared._CP14.MagicEnergy.Components;
using Content.Shared._CP14.MagicSpell.Components;
using Content.Shared._CP14.MagicSpell.Events;
using Content.Shared._CP14.MagicSpell.Spells;
using Content.Shared.DoAfter;
using Content.Shared.Hands.Components;
using Content.Shared.Popups;
using Content.Shared.Speech.Muting;
using Content.Shared.Weapons.Ranged.Systems;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Random;
namespace Content.Shared._CP14.MagicSpell;
/// <summary>
/// This system handles the basic mechanics of spell use, such as doAfter, event invocation, and energy spending.
/// </summary>
public partial class CP14SharedMagicSystem : EntitySystem
{
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly SharedGunSystem _gunSystem = default!;
[Dependency] private readonly SharedCP14MagicEnergySystem _magicEnergy = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly IRobustRandom _random = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14MagicEffectComponent, CP14BeforeCastMagicEffectEvent>(OnBeforeCastMagicEffect);
SubscribeLocalEvent<CP14DelayedInstantActionEvent>(OnInstantAction);
SubscribeLocalEvent<CP14DelayedEntityTargetActionEvent>(OnEntityTargetAction);
SubscribeLocalEvent<CP14DelayedWorldTargetActionEvent>(OnWorldTargetAction);
SubscribeLocalEvent<CP14MagicEffectComponent, CP14DelayedInstantActionDoAfterEvent>(OnDelayedInstantActionDoAfter);
SubscribeLocalEvent<CP14MagicEffectComponent, CP14DelayedEntityTargetActionDoAfterEvent>(OnDelayedEntityTargetDoAfter);
SubscribeLocalEvent<CP14MagicEffectComponent, CP14DelayedWorldTargetActionDoAfterEvent>(OnDelayedWorldTargetDoAfter);
SubscribeLocalEvent<CP14MagicEffectSomaticAspectComponent, CP14BeforeCastMagicEffectEvent>(OnSomaticAspectBeforeCast);
SubscribeLocalEvent<CP14MagicEffectVerbalAspectComponent, CP14BeforeCastMagicEffectEvent>(OnVerbalAspectBeforeCast);
SubscribeLocalEvent<CP14MagicEffectVerbalAspectComponent, CP14AfterCastMagicEffectEvent>(OnVerbalAspectAfterCast);
SubscribeLocalEvent<CP14MagicEffectComponent, CP14AfterCastMagicEffectEvent>(OnAfterCastMagicEffect);
}
private void OnBeforeCastMagicEffect(Entity<CP14MagicEffectComponent> ent, ref CP14BeforeCastMagicEffectEvent args)
{
if (!TryComp<CP14MagicEnergyContainerComponent>(args.Performer, out var magicContainer))
{
args.Cancel();
return;
}
if (!_magicEnergy.HasEnergy(args.Performer, ent.Comp.ManaCost, magicContainer, ent.Comp.Safe))
{
args.PushReason(Loc.GetString("cp14-magic-spell-not-enough-mana"));
args.Cancel();
}
else if(!_magicEnergy.HasEnergy(args.Performer, ent.Comp.ManaCost, magicContainer, true) && _net.IsServer)
{
_popup.PopupEntity(Loc.GetString("cp14-magic-spell-not-enough-mana-cast-warning-"+_random.Next(5)), args.Performer, args.Performer, PopupType.SmallCaution);
}
}
private void OnInstantAction(CP14DelayedInstantActionEvent args)
{
if (args.Handled)
return;
args.Handled = true;
if (args is not ICP14DelayedMagicEffect delayedEffect)
return;
if (!TryCastSpell(args.Action, args.Performer))
return;
var doAfterEventArgs = new DoAfterArgs(EntityManager, args.Performer, delayedEffect.Delay, new CP14DelayedInstantActionDoAfterEvent(), args.Action)
{
BreakOnMove = delayedEffect.BreakOnMove,
BreakOnDamage = delayedEffect.BreakOnDamage,
Hidden = delayedEffect.Hidden,
BlockDuplicate = true,
DistanceThreshold = 100f,
};
_doAfter.TryStartDoAfter(doAfterEventArgs);
//Telegraphy effects
if (_net.IsServer && TryComp<CP14MagicEffectComponent>(args.Action, out var magicEffect))
{
foreach (var effect in magicEffect.TelegraphyEffects)
{
effect.Effect(EntityManager, new CP14SpellEffectBaseArgs(args.Performer, args.Performer, Transform(args.Performer).Coordinates));
}
}
}
private void OnWorldTargetAction(CP14DelayedWorldTargetActionEvent args)
{
if (args.Handled)
return;
args.Handled = true;
if (args is not ICP14DelayedMagicEffect delayedEffect)
return;
if (!TryCastSpell(args.Action, args.Performer))
return;
var doAfter = new CP14DelayedWorldTargetActionDoAfterEvent()
{
Target = EntityManager.GetNetCoordinates(args.Target)
};
var doAfterEventArgs = new DoAfterArgs(EntityManager, args.Performer, delayedEffect.Delay, doAfter, args.Action)
{
BreakOnMove = delayedEffect.BreakOnMove,
BreakOnDamage = delayedEffect.BreakOnDamage,
Hidden = delayedEffect.Hidden,
BlockDuplicate = true,
DistanceThreshold = 100f,
};
_doAfter.TryStartDoAfter(doAfterEventArgs);
//Telegraphy effects
if (_net.IsServer && TryComp<CP14MagicEffectComponent>(args.Action, out var magicEffect))
{
foreach (var effect in magicEffect.TelegraphyEffects)
{
effect.Effect(EntityManager, new CP14SpellEffectBaseArgs(args.Performer, null, args.Target));
}
}
}
private void OnEntityTargetAction(CP14DelayedEntityTargetActionEvent args)
{
if (args.Handled)
return;
args.Handled = true;
if (args is not ICP14DelayedMagicEffect delayedEffect)
return;
if (!TryCastSpell(args.Action, args.Performer))
return;
var doAfterEventArgs = new DoAfterArgs(EntityManager, args.Performer, delayedEffect.Delay, new CP14DelayedEntityTargetActionDoAfterEvent(), args.Action, args.Target)
{
BreakOnMove = delayedEffect.BreakOnMove,
BreakOnDamage = delayedEffect.BreakOnDamage,
Hidden = delayedEffect.Hidden,
BlockDuplicate = true,
DistanceThreshold = 100f,
};
_doAfter.TryStartDoAfter(doAfterEventArgs);
//Telegraphy effects
if (_net.IsServer && TryComp<CP14MagicEffectComponent>(args.Action, out var magicEffect))
{
foreach (var effect in magicEffect.TelegraphyEffects)
{
effect.Effect(EntityManager, new CP14SpellEffectBaseArgs(args.Performer, args.Target, null));
}
}
}
private void OnDelayedWorldTargetDoAfter(Entity<CP14MagicEffectComponent> ent, ref CP14DelayedWorldTargetActionDoAfterEvent args)
{
var endEv = new CP14EndCastMagicEffectEvent();
RaiseLocalEvent(ent, ref endEv);
if (args.Cancelled || !_net.IsServer)
return;
foreach (var effect in ent.Comp.Effects)
{
effect.Effect(EntityManager, new CP14SpellEffectBaseArgs(args.User, null, GetCoordinates(args.Target)));
}
var ev = new CP14AfterCastMagicEffectEvent {Performer = args.User};
RaiseLocalEvent(ent, ref ev);
}
private void OnDelayedEntityTargetDoAfter(Entity<CP14MagicEffectComponent> ent, ref CP14DelayedEntityTargetActionDoAfterEvent args)
{
var endEv = new CP14EndCastMagicEffectEvent();
RaiseLocalEvent(ent, ref endEv);
if (args.Cancelled || !_net.IsServer)
return;
foreach (var effect in ent.Comp.Effects)
{
effect.Effect(EntityManager, new CP14SpellEffectBaseArgs(args.User, args.Target, null));
}
var ev = new CP14AfterCastMagicEffectEvent {Performer = args.User};
RaiseLocalEvent(ent, ref ev);
}
private void OnDelayedInstantActionDoAfter(Entity<CP14MagicEffectComponent> ent, ref CP14DelayedInstantActionDoAfterEvent args)
{
var endEv = new CP14EndCastMagicEffectEvent();
RaiseLocalEvent(ent, ref endEv);
if (args.Cancelled || !_net.IsServer)
return;
foreach (var effect in ent.Comp.Effects)
{
effect.Effect(EntityManager, new CP14SpellEffectBaseArgs(args.User, args.User, Transform(args.User).Coordinates));
}
var ev = new CP14AfterCastMagicEffectEvent {Performer = args.User};
RaiseLocalEvent(ent, ref ev);
}
private void OnSomaticAspectBeforeCast(Entity<CP14MagicEffectSomaticAspectComponent> ent, ref CP14BeforeCastMagicEffectEvent args)
{
if (TryComp<HandsComponent>(args.Performer, out var hands) || hands is not null)
{
var freeHand = 0;
foreach (var hand in hands.Hands)
{
if (hand.Value.IsEmpty)
freeHand++;
}
if (freeHand >= ent.Comp.FreeHandRequired)
return;
}
args.PushReason(Loc.GetString("cp14-magic-spell-need-somatic-component"));
args.Cancel();
}
private void OnVerbalAspectBeforeCast(Entity<CP14MagicEffectVerbalAspectComponent> ent, ref CP14BeforeCastMagicEffectEvent args)
{
if (HasComp<MutedComponent>(args.Performer))
{
args.PushReason(Loc.GetString("cp14-magic-spell-need-verbal-component"));
args.Cancel();
}
else
{
if (!args.Cancelled)
{
var ev = new CP14VerbalAspectSpeechEvent
{
Performer = args.Performer,
Speech = ent.Comp.StartSpeech,
};
RaiseLocalEvent(ent, ref ev);
}
}
}
private void OnVerbalAspectAfterCast(Entity<CP14MagicEffectVerbalAspectComponent> ent, ref CP14AfterCastMagicEffectEvent args)
{
if (_net.IsClient)
return;
var ev = new CP14VerbalAspectSpeechEvent
{
Performer = args.Performer,
Speech = ent.Comp.EndSpeech,
};
RaiseLocalEvent(ent, ref ev);
}
private bool TryCastSpell(EntityUid spell, EntityUid performer)
{
var ev = new CP14BeforeCastMagicEffectEvent
{
Performer = performer,
};
RaiseLocalEvent(spell, ref ev);
if (ev.Reason != string.Empty && _net.IsServer)
{
_popup.PopupEntity(ev.Reason, performer, performer);
}
if (!ev.Cancelled)
{
var evStart = new CP14StartCastMagicEffectEvent()
{
Performer = performer,
};
RaiseLocalEvent(spell, ref evStart);
}
return !ev.Cancelled;
}
private void OnAfterCastMagicEffect(Entity<CP14MagicEffectComponent> ent, ref CP14AfterCastMagicEffectEvent args)
{
if (_net.IsClient)
return;
if (!HasComp<CP14MagicEnergyContainerComponent>(args.Performer))
return;
_magicEnergy.TryConsumeEnergy(args.Performer.Value, ent.Comp.ManaCost, safe: ent.Comp.Safe);
}
}

View File

@@ -0,0 +1,16 @@
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicSpell.Components;
/// <summary>
/// Creates a temporary entity that exists while the spell is cast, and disappears at the end. For visual special effects.
/// </summary>
[RegisterComponent, Access(typeof(CP14SharedMagicSystem))]
public sealed partial class CP14MagicEffectCastingVisualComponent : Component
{
[DataField]
public EntityUid? SpawnedEntity;
[DataField(required: true)]
public EntProtoId Proto = default!;
}

View File

@@ -0,0 +1,26 @@
using Content.Shared._CP14.MagicSpell.Spells;
using Content.Shared.FixedPoint;
namespace Content.Shared._CP14.MagicSpell.Components;
/// <summary>
/// Restricts the use of this action, by spending mana or user requirements.
/// </summary>
[RegisterComponent, Access(typeof(CP14SharedMagicSystem))]
public sealed partial class CP14MagicEffectComponent : Component
{
[DataField]
public FixedPoint2 ManaCost = 0f;
[DataField]
public bool Safe = false;
/// <summary>
/// Effects that will trigger at the beginning of the cast, before mana is spent. Should have no gameplay importance, just special effects, popups and sounds.
/// </summary>
[DataField]
public List<CP14SpellEffect> TelegraphyEffects = new();
[DataField]
public List<CP14SpellEffect> Effects = new();
}

View File

@@ -0,0 +1,11 @@
namespace Content.Shared._CP14.MagicSpell.Components;
/// <summary>
/// Requires the user to have at least one free hand to use this spell
/// </summary>
[RegisterComponent, Access(typeof(CP14SharedMagicSystem))]
public sealed partial class CP14MagicEffectSomaticAspectComponent : Component
{
[DataField]
public int FreeHandRequired = 1;
}

View File

@@ -0,0 +1,25 @@
namespace Content.Shared._CP14.MagicSpell.Components;
/// <summary>
/// Requires the user to be able to speak in order to use this spell. Also forces the user to use certain phrases at the beginning and end of a spell cast
/// </summary>
[RegisterComponent, Access(typeof(CP14SharedMagicSystem))]
public sealed partial class CP14MagicEffectVerbalAspectComponent : Component
{
[DataField]
public string StartSpeech = string.Empty;
[DataField]
public string EndSpeech = string.Empty;
}
/// <summary>
/// patch to send an event to the server for saying a phrase out loud
/// </summary>
[ByRefEvent]
public sealed class CP14VerbalAspectSpeechEvent : EntityEventArgs
{
public EntityUid? Performer { get; init; }
public string? Speech { get; init; }
}

View File

@@ -0,0 +1,41 @@
namespace Content.Shared._CP14.MagicSpell.Events;
[ByRefEvent]
public sealed class CP14BeforeCastMagicEffectEvent : CancellableEntityEventArgs
{
/// <summary>
/// The Performer of the event, to check if they meet the requirements.
/// </summary>
public EntityUid Performer { get; init; }
public string Reason = string.Empty;
public void PushReason(string reason)
{
Reason += $"{reason}\n";
}
}
[ByRefEvent]
public sealed class CP14AfterCastMagicEffectEvent : EntityEventArgs
{
public EntityUid? Performer { get; init; }
}
/// <summary>
/// is invoked if all conditions are met and the spell has begun to be cast
/// </summary>
[ByRefEvent]
public sealed class CP14StartCastMagicEffectEvent : EntityEventArgs
{
public EntityUid Performer { get; init; }
}
/// <summary>
/// is invoked on the spell itself when the spell process has been completed or interrupted
/// </summary>
[ByRefEvent]
public sealed class CP14EndCastMagicEffectEvent : EntityEventArgs
{
}

View File

@@ -0,0 +1,73 @@
using Content.Shared.Actions;
using Content.Shared.DoAfter;
using Robust.Shared.Map;
using Robust.Shared.Serialization;
namespace Content.Shared._CP14.MagicSpell.Events;
//World target
public sealed partial class CP14DelayedWorldTargetActionEvent : WorldTargetActionEvent, ICP14DelayedMagicEffect
{
[DataField]
public float Delay { get; private set; } = 1f;
[DataField]
public bool BreakOnMove { get; private set; } = true;
[DataField]
public bool BreakOnDamage { get; private set; } = true;
[DataField]
public bool Hidden { get; private set; } = false;
}
[Serializable, NetSerializable]
public sealed partial class CP14DelayedWorldTargetActionDoAfterEvent : DoAfterEvent
{
[DataField]
public NetCoordinates Target;
public override DoAfterEvent Clone() => this;
}
//Entity Target
public sealed partial class CP14DelayedEntityTargetActionEvent : EntityTargetActionEvent, ICP14DelayedMagicEffect
{
[DataField]
public float Delay { get; private set; } = 1f;
[DataField]
public bool BreakOnMove { get; private set; } = true;
[DataField]
public bool BreakOnDamage { get; private set; } = true;
[DataField]
public bool Hidden { get; private set; } = false;
}
[Serializable, NetSerializable]
public sealed partial class CP14DelayedEntityTargetActionDoAfterEvent : SimpleDoAfterEvent
{
}
//Instant
public sealed partial class CP14DelayedInstantActionEvent : InstantActionEvent, ICP14DelayedMagicEffect
{
[DataField]
public float Delay { get; private set; } = 1f;
[DataField]
public bool BreakOnMove { get; private set; } = true;
[DataField]
public bool BreakOnDamage { get; private set; } = true;
[DataField]
public bool Hidden { get; private set; } = false;
}
[Serializable, NetSerializable]
public sealed partial class CP14DelayedInstantActionDoAfterEvent : SimpleDoAfterEvent
{
}

View File

@@ -0,0 +1,15 @@
namespace Content.Shared._CP14.MagicSpell.Events;
public interface ICP14DelayedMagicEffect // The speak n spell interface
{
/// <summary>
/// Localized string spoken by the caster when casting this spell.
/// </summary>
public float Delay { get; }
public bool BreakOnMove { get; }
public bool BreakOnDamage { get; }
public bool Hidden{ get; }
}

View File

@@ -0,0 +1,17 @@
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicSpell.Spells;
public sealed partial class CP14SpellAddComponent : CP14SpellEffect
{
[DataField]
public ComponentRegistry Components = new();
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
if (args.Target is null)
return;
entManager.AddComponents(args.Target.Value, Components);
}
}

View File

@@ -0,0 +1,22 @@
using Content.Shared.EntityEffects;
namespace Content.Shared._CP14.MagicSpell.Spells;
public sealed partial class CP14SpellApplyEntityEffect : CP14SpellEffect
{
[DataField(required: true, serverOnly: true)]
public List<EntityEffect> Effects = new();
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
if (args.Target is null)
return;
var targetEntity = args.Target.Value;
foreach (var effect in Effects)
{
effect.Effect(new EntityEffectBaseArgs(targetEntity, entManager));
}
}
}

View File

@@ -0,0 +1,37 @@
using Content.Shared.Examine;
using Content.Shared.Popups;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicSpell.Spells;
public sealed partial class CP14SpellCasterTeleport : CP14SpellEffect
{
[DataField]
public bool NeedVision = true;
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
EntityCoordinates? targetPoint = null;
if (args.Position is not null)
targetPoint = args.Position.Value;
else if (args.Target is not null && entManager.TryGetComponent<TransformComponent>(args.Target.Value, out var transformComponent))
targetPoint = transformComponent.Coordinates;
if (targetPoint is null || args.User is null)
return;
var transform = entManager.System<SharedTransformSystem>();
var examine = entManager.System<ExamineSystemShared>();
var popup = entManager.System<SharedPopupSystem>();
if (NeedVision && !examine.InRangeUnOccluded(args.User.Value, targetPoint.Value))
{
// can only dash if the destination is visible on screen
popup.PopupEntity(Loc.GetString("dash-ability-cant-see"), args.User.Value, args.User.Value);
return;
}
transform.SetCoordinates(args.User.Value, targetPoint.Value);
}
}

View File

@@ -0,0 +1,25 @@
using JetBrains.Annotations;
using Robust.Shared.Map;
namespace Content.Shared._CP14.MagicSpell.Spells;
[ImplicitDataDefinitionForInheritors]
[MeansImplicitUse]
public abstract partial class CP14SpellEffect
{
public abstract void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args);
}
public record class CP14SpellEffectBaseArgs
{
public EntityUid? User;
public EntityUid? Target;
public EntityCoordinates? Position;
public CP14SpellEffectBaseArgs(EntityUid? user, EntityUid? target, EntityCoordinates? position)
{
User = user;
Target = target;
Position = position;
}
}

View File

@@ -0,0 +1,52 @@
using Content.Shared.Weapons.Ranged.Systems;
using Robust.Shared.Map;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicSpell.Spells;
public sealed partial class CP14SpellProjectile : CP14SpellEffect
{
[DataField(required: true)]
public EntProtoId Prototype;
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
EntityCoordinates? targetPoint = null;
if (args.Position is not null)
targetPoint = args.Position.Value;
else if (args.Target is not null && entManager.TryGetComponent<TransformComponent>(args.Target.Value, out var transformComponent))
targetPoint = transformComponent.Coordinates;
if (targetPoint is null)
return;
var transform = entManager.System<SharedTransformSystem>();
var physics = entManager.System<SharedPhysicsSystem>();
var gunSystem = entManager.System<SharedGunSystem>();
var mapManager = IoCManager.Resolve<IMapManager>();
if (!entManager.TryGetComponent<TransformComponent>(args.User, out var xform))
return;
var fromCoords = xform.Coordinates;
if (fromCoords == targetPoint)
return;
var userVelocity = physics.GetMapLinearVelocity(args.User.Value);
// If applicable, this ensures the projectile is parented to grid on spawn, instead of the map.
var fromMap = transform.ToMapCoordinates(fromCoords);
var spawnCoords = mapManager.TryFindGridAt(fromMap, out var gridUid, out _)
? transform.WithEntityId(fromCoords, gridUid)
: new(mapManager.GetMapEntityId(fromMap.MapId), fromMap.Position);
var ent = entManager.SpawnAtPosition(Prototype, spawnCoords);
var direction = targetPoint.Value.ToMapPos(entManager, transform) -
spawnCoords.ToMapPos(entManager, transform);
gunSystem.ShootProjectile(ent, direction, userVelocity, args.User.Value, args.User);
}
}

View File

@@ -0,0 +1,27 @@
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicSpell.Spells;
public sealed partial class CP14SpellSpawnEntityOnTarget : CP14SpellEffect
{
[DataField]
public List<EntProtoId> Spawns = new();
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
EntityCoordinates? targetPoint = null;
if (args.Position is not null)
targetPoint = args.Position.Value;
else if (args.Target is not null && entManager.TryGetComponent<TransformComponent>(args.Target.Value, out var transformComponent))
targetPoint = transformComponent.Coordinates;
if (targetPoint is null)
return;
foreach (var spawn in Spawns)
{
entManager.SpawnAtPosition(spawn, targetPoint.Value);
}
}
}

View File

@@ -0,0 +1,21 @@
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicSpell.Spells;
public sealed partial class CP14SpellSpawnEntityOnUser : CP14SpellEffect
{
[DataField]
public List<EntProtoId> Spawns = new();
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
if (args.User is null || !entManager.TryGetComponent<TransformComponent>(args.User.Value, out var transformComponent))
return;
foreach (var spawn in Spawns)
{
entManager.SpawnAtPosition(spawn, transformComponent.Coordinates);
}
}
}

View File

@@ -0,0 +1,31 @@
using Content.Shared.Hands.EntitySystems;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicSpell.Spells;
public sealed partial class CP14SpellSpawnInHandEntity : CP14SpellEffect
{
[DataField]
public List<EntProtoId> Spawns = new();
[DataField]
public bool DeleteIfCantPickup = false;
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
if (args.Target is null)
return;
if (!entManager.TryGetComponent<TransformComponent>(args.Target.Value, out var transformComponent))
return;
var handSystem = entManager.System<SharedHandsSystem>();
foreach (var spawn in Spawns)
{
var item = entManager.SpawnAtPosition(spawn, transformComponent.Coordinates);
if (!handSystem.TryPickupAnyHand(args.Target.Value, item) && DeleteIfCantPickup)
entManager.QueueDeleteEntity(item);
}
}
}

View File

@@ -0,0 +1,23 @@
using Content.Shared.Throwing;
namespace Content.Shared._CP14.MagicSpell.Spells;
public sealed partial class CP14SpellThrowToUser : CP14SpellEffect
{
[DataField]
public float ThrowPower = 10f;
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
if (args.Target is null)
return;
var targetEntity = args.Target.Value;
var throwing = entManager.System<ThrowingSystem>();
if (!entManager.TryGetComponent<TransformComponent>(args.User, out var xform))
return;
throwing.TryThrow(targetEntity, xform.Coordinates, ThrowPower);
}
}

View File

@@ -0,0 +1,9 @@
namespace Content.Shared._CP14.MagicSpellStorage;
/// <summary>
/// Denotes that this item's spells can be accessed while holding it in your hand
/// </summary>
[RegisterComponent, Access(typeof(CP14SpellStorageSystem))]
public sealed partial class CP14SpellStorageAccessHoldingComponent : Component
{
}

View File

@@ -0,0 +1,9 @@
namespace Content.Shared._CP14.MagicSpellStorage;
/// <summary>
/// Denotes that this item's spells can be accessed while wearing it in your body
/// </summary>
[RegisterComponent, Access(typeof(CP14SpellStorageSystem))]
public sealed partial class CP14SpellStorageAccessWearingComponent : Component
{
}

View File

@@ -0,0 +1,22 @@
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicSpellStorage;
/// <summary>
/// A component that allows you to store spells in items
/// </summary>
[RegisterComponent, Access(typeof(CP14SpellStorageSystem))]
public sealed partial class CP14SpellStorageComponent : Component
{
/// <summary>
/// list of spell prototypes used for initialization.
/// </summary>
[DataField]
public List<EntProtoId> Spells = new();
/// <summary>
/// created after the initialization of spell entities.
/// </summary>
[DataField]
public List<EntityUid> SpellEntities = new();
}

View File

@@ -0,0 +1,9 @@
namespace Content.Shared._CP14.MagicSpellStorage;
/// <summary>
/// The ability to access spellcasting is limited by the attuning requirement
/// </summary>
[RegisterComponent, Access(typeof(CP14SpellStorageSystem))]
public sealed partial class CP14SpellStorageRequireAttuneComponent : Component
{
}

View File

@@ -0,0 +1,109 @@
using Content.Shared._CP14.MagicAttuning;
using Content.Shared.Actions;
using Content.Shared.Clothing;
using Content.Shared.Hands;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Mind;
namespace Content.Shared._CP14.MagicSpellStorage;
/// <summary>
/// this part of the system is responsible for storing spells in items, and the methods players use to obtain them.
/// </summary>
public sealed partial class CP14SpellStorageSystem : EntitySystem
{
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly CP14SharedMagicAttuningSystem _attuning = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
public override void Initialize()
{
SubscribeLocalEvent<CP14SpellStorageComponent, MapInitEvent>(OnMagicStorageInit);
SubscribeLocalEvent<CP14SpellStorageAccessHoldingComponent, GotEquippedHandEvent>(OnEquippedHand);
SubscribeLocalEvent<CP14SpellStorageAccessHoldingComponent, AddedAttuneToMindEvent>(OnHandAddedAttune);
SubscribeLocalEvent<CP14SpellStorageAccessWearingComponent, ClothingGotEquippedEvent>(OnClothingEquipped);
SubscribeLocalEvent<CP14SpellStorageAccessWearingComponent, ClothingGotUnequippedEvent>(OnClothingUnequipped);
SubscribeLocalEvent<CP14SpellStorageRequireAttuneComponent, RemovedAttuneFromMindEvent>(OnRemovedAttune);
}
/// <summary>
/// When we initialize, we create action entities, and add them to this item.
/// </summary>
private void OnMagicStorageInit(Entity<CP14SpellStorageComponent> mStorage, ref MapInitEvent args)
{
foreach (var spell in mStorage.Comp.Spells)
{
var spellEnt = _actionContainer.AddAction(mStorage, spell);
if (spellEnt is null)
continue;
mStorage.Comp.SpellEntities.Add(spellEnt.Value);
}
}
private void OnEquippedHand(Entity<CP14SpellStorageAccessHoldingComponent> ent, ref GotEquippedHandEvent args)
{
if (!TryComp<CP14SpellStorageComponent>(ent, out var spellStorage))
return;
TryGrantAccess((ent, spellStorage), args.User);
}
private void OnHandAddedAttune(Entity<CP14SpellStorageAccessHoldingComponent> ent, ref AddedAttuneToMindEvent args)
{
if (!TryComp<CP14SpellStorageComponent>(ent, out var spellStorage))
return;
if (args.User is null)
return;
if (!_hands.IsHolding(args.User.Value, ent))
return;
TryGrantAccess((ent, spellStorage), args.User.Value);
}
private void OnClothingEquipped(Entity<CP14SpellStorageAccessWearingComponent> ent, ref ClothingGotEquippedEvent args)
{
if (!TryComp<CP14SpellStorageComponent>(ent, out var spellStorage))
return;
TryGrantAccess((ent, spellStorage), args.Wearer);
}
private void OnClothingUnequipped(Entity<CP14SpellStorageAccessWearingComponent> ent, ref ClothingGotUnequippedEvent args)
{
_actions.RemoveProvidedActions(args.Wearer, ent);
}
private bool TryGrantAccess(Entity<CP14SpellStorageComponent> storage, EntityUid user)
{
if (!_mind.TryGetMind(user, out var mindId, out var mind))
return false;
if (mind.OwnedEntity is null)
return false;
if (TryComp<CP14SpellStorageRequireAttuneComponent>(storage, out var reqAttune))
{
if (!_attuning.IsAttunedTo(mindId, storage))
return false;
}
_actions.GrantActions(user, storage.Comp.SpellEntities, storage);
return true;
}
private void OnRemovedAttune(Entity<CP14SpellStorageRequireAttuneComponent> ent, ref RemovedAttuneFromMindEvent args)
{
if (args.User is null)
return;
_actions.RemoveProvidedActions(args.User.Value, ent);
}
}

View File

@@ -0,0 +1,21 @@
using Content.Shared.Damage;
using Content.Shared.FixedPoint;
namespace Content.Shared._CP14.MagicWeakness;
/// <summary>
/// imposes damage on excessive use of magic
/// </summary>
[RegisterComponent, Access(typeof(CP14MagicWeaknessSystem))]
public sealed partial class CP14MagicUnsafeDamageComponent : Component
{
[DataField]
public DamageSpecifier DamagePerEnergy = new()
{
DamageDict = new Dictionary<string, FixedPoint2>
{
{"Blunt", 0.5},
{"Heat", 0.5},
},
};
}

View File

@@ -0,0 +1,20 @@
using Content.Shared.Damage;
using Content.Shared.FixedPoint;
namespace Content.Shared._CP14.MagicWeakness;
/// <summary>
/// imposes debuffs on excessive use of magic
/// </summary>
[RegisterComponent, Access(typeof(CP14MagicWeaknessSystem))]
public sealed partial class CP14MagicUnsafeSleepComponent : Component
{
[DataField]
public float SleepPerEnergy = 0.5f;
/// <summary>
/// At the specified amount of extra mana expenditure, the character falls asleep.
/// </summary>
[DataField]
public FixedPoint2 SleepThreshold = 20f;
}

View File

@@ -0,0 +1,43 @@
using Content.Shared._CP14.MagicEnergy;
using Content.Shared.Bed.Sleep;
using Content.Shared.Damage;
using Content.Shared.Popups;
using Content.Shared.StatusEffect;
namespace Content.Shared._CP14.MagicWeakness;
public partial class CP14MagicWeaknessSystem : EntitySystem
{
[ValidatePrototypeId<StatusEffectPrototype>]
private const string StatusEffectKey = "ForcedSleep";
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14MagicUnsafeDamageComponent, CP14MagicEnergyBurnOutEvent>(OnMagicEnergyBurnOutDamage);
SubscribeLocalEvent<CP14MagicUnsafeSleepComponent, CP14MagicEnergyBurnOutEvent>(OnMagicEnergyBurnOutSleep);
}
private void OnMagicEnergyBurnOutSleep(Entity<CP14MagicUnsafeSleepComponent> ent, ref CP14MagicEnergyBurnOutEvent args)
{
if (args.BurnOutEnergy > ent.Comp.SleepThreshold)
{
_popup.PopupEntity(Loc.GetString("cp14-magic-energy-damage-burn-out-fall"), ent, ent, PopupType.LargeCaution);
_statusEffects.TryAddStatusEffect<ForcedSleepingComponent>(ent,
StatusEffectKey,
TimeSpan.FromSeconds(ent.Comp.SleepPerEnergy * (float)args.BurnOutEnergy),
false);
}
}
private void OnMagicEnergyBurnOutDamage(Entity<CP14MagicUnsafeDamageComponent> ent, ref CP14MagicEnergyBurnOutEvent args)
{
_popup.PopupEntity(Loc.GetString("cp14-magic-energy-damage-burn-out"), ent, ent, PopupType.LargeCaution);
_damageable.TryChangeDamage(ent, ent.Comp.DamagePerEnergy * args.BurnOutEnergy);
}
}

View File

@@ -0,0 +1,16 @@
using Content.Shared.Damage;
namespace Content.Shared._CP14.MeleeWeapon.Components;
[RegisterComponent]
public sealed partial class CP14MeleeSelfDamageComponent : Component
{
[DataField]
public DamageSpecifier DamageToSelf = new()
{
DamageDict = new()
{
{ "Blunt", 1 },
}
};
}

View File

@@ -0,0 +1,16 @@
using Content.Shared._CP14.MeleeWeapon.EntitySystems;
namespace Content.Shared._CP14.MeleeWeapon.Components;
/// <summary>
/// allows the object to become blunt with use
/// </summary>
[RegisterComponent, Access(typeof(CP14SharpeningSystem))]
public sealed partial class CP14SharpenedComponent : Component
{
[DataField]
public float Sharpness = 1f;
[DataField]
public float SharpnessDamageBy1Damage = 0.002f; //500 damage
}

View File

@@ -0,0 +1,52 @@
using Content.Shared._CP14.MeleeWeapon.EntitySystems;
using Content.Shared.Damage;
using Robust.Shared.Audio;
namespace Content.Shared._CP14.MeleeWeapon.Components;
/// <summary>
/// component allows you to sharpen objects by restoring their damage.
/// </summary>
[RegisterComponent, Access(typeof(CP14SharpeningSystem))]
public sealed partial class CP14SharpeningStoneComponent : Component
{
/// <summary>
/// the amount of acuity recoverable per use
/// </summary>
[DataField]
public float SharpnessHeal = 0.05f;
/// <summary>
/// sound when used
/// </summary>
[DataField]
public SoundSpecifier SharpeningSound =
new SoundPathSpecifier("/Audio/_CP14/Items/sharpening_stone.ogg")
{
Params = AudioParams.Default.WithVariation(0.02f),
};
/// <summary>
/// the damage that the sharpening stone does to itself for use
/// </summary>
[DataField]
public DamageSpecifier SelfDamage = new()
{
DamageDict = new()
{
{ "Blunt", 1 },
}
};
/// <summary>
/// the damage the sharpening stone does to the target
/// </summary>
[DataField]
public DamageSpecifier TargetDamage = new()
{
DamageDict = new()
{
{ "Blunt", 1 },
}
};
}

View File

@@ -0,0 +1,24 @@
using Content.Shared._CP14.MeleeWeapon.Components;
using Content.Shared.Damage;
using Content.Shared.Weapons.Melee.Events;
namespace Content.Shared._CP14.MeleeWeapon.EntitySystems;
public sealed class CP14MeleeSelfDamageSystem : EntitySystem
{
[Dependency] private readonly DamageableSystem _damageable = default!;
public override void Initialize()
{
SubscribeLocalEvent<CP14MeleeSelfDamageComponent, MeleeHitEvent>(OnMeleeHit);
}
private void OnMeleeHit(Entity<CP14MeleeSelfDamageComponent> ent, ref MeleeHitEvent args)
{
if (!args.IsHit)
return;
if (args.HitEntities.Count == 0)
return;
_damageable.TryChangeDamage(ent, ent.Comp.DamageToSelf);
}
}

View File

@@ -0,0 +1,143 @@
using System.Linq;
using Content.Shared._CP14.MeleeWeapon.Components;
using Content.Shared.Damage;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Placeable;
using Content.Shared.Popups;
using Content.Shared.Timing;
using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Wieldable;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Network;
namespace Content.Shared._CP14.MeleeWeapon.EntitySystems;
public sealed class CP14SharpeningSystem : EntitySystem
{
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly UseDelaySystem _useDelay = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly INetManager _net = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14SharpenedComponent, GetMeleeDamageEvent>(OnGetMeleeDamage, after: new[] { typeof(WieldableSystem) });
SubscribeLocalEvent<CP14SharpenedComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<CP14SharpenedComponent, MeleeHitEvent>(OnMeleeHit);
SubscribeLocalEvent<CP14SharpeningStoneComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<CP14SharpeningStoneComponent, ActivateInWorldEvent>(OnInteract);
}
private void OnMeleeHit(Entity<CP14SharpenedComponent> sharpened, ref MeleeHitEvent args)
{
if (!args.HitEntities.Any())
return;
sharpened.Comp.Sharpness = MathHelper.Clamp(sharpened.Comp.Sharpness - args.BaseDamage.GetTotal().Float() * sharpened.Comp.SharpnessDamageBy1Damage, 0.1f, 1f);
}
private void OnInteract(Entity<CP14SharpeningStoneComponent> stone, ref ActivateInWorldEvent args)
{
if (args.Handled)
return;
if (!TryComp<ItemPlacerComponent>(stone, out var itemPlacer))
return;
if (itemPlacer.PlacedEntities.Count <= 0)
return;
foreach (var item in itemPlacer.PlacedEntities)
{
if (!TryComp<CP14SharpenedComponent>(item, out var sharpened))
continue;
SharpThing(stone, item, sharpened, args.User);
return;
}
}
private void OnAfterInteract(Entity<CP14SharpeningStoneComponent> stone, ref AfterInteractEvent args)
{
if (!args.CanReach || args.Target == null || !TryComp<CP14SharpenedComponent>(args.Target, out var sharpened))
return;
if (TryComp<UseDelayComponent>(stone, out var useDelay) && _useDelay.IsDelayed( new Entity<UseDelayComponent>(stone, useDelay)))
return;
SharpThing(stone, args.Target.Value, sharpened, args.User);
}
private void SharpThing(Entity<CP14SharpeningStoneComponent> stone, EntityUid target, CP14SharpenedComponent component, EntityUid user)
{
var ev = new SharpingEvent()
{
User = user,
Target = target,
};
RaiseLocalEvent(stone, ev);
if (!ev.Canceled)
{
_audio.PlayPredicted(stone.Comp.SharpeningSound, target, user);
_damageableSystem.TryChangeDamage(stone, stone.Comp.SelfDamage);
_damageableSystem.TryChangeDamage(target, stone.Comp.TargetDamage);
component.Sharpness = MathHelper.Clamp01(component.Sharpness + stone.Comp.SharpnessHeal);
if (_net.IsServer)
{
Spawn("EffectSparks", Transform(target).Coordinates);
if (component.Sharpness >= 0.99)
_popup.PopupEntity(Loc.GetString("sharpening-ready"), target, user);
}
}
_useDelay.TryResetDelay(stone);
}
private void OnExamined(Entity<CP14SharpenedComponent> sharpened, ref ExaminedEvent args)
{
if (sharpened.Comp.Sharpness > 0.95f)
{
args.PushMarkup(Loc.GetString("sharpening-examined-95"));
return;
}
if (sharpened.Comp.Sharpness > 0.75f)
{
args.PushMarkup(Loc.GetString("sharpening-examined-75"));
return;
}
if (sharpened.Comp.Sharpness > 0.5f)
{
args.PushMarkup(Loc.GetString("sharpening-examined-50"));
return;
}
args.PushMarkup(Loc.GetString("sharpening-examined-25"));
}
private void OnGetMeleeDamage(Entity<CP14SharpenedComponent> sharpened, ref GetMeleeDamageEvent args)
{
args.Damage *= sharpened.Comp.Sharpness;
}
}
/// <summary>
/// Caused on a sharpening stone when someone tries to sharpen an object with it
/// </summary>
public sealed class SharpingEvent : EntityEventArgs
{
public bool Canceled = false;
public EntityUid User;
public EntityUid Target;
}

View File

@@ -3,7 +3,7 @@ using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Skills.Prototypes;
/// <summary>
/// A prototype of the lock category. Need a roundstart mapping to ensure that keys and locks will fit together despite randomization.
///
/// </summary>
[Prototype("CP14Skill")]
public sealed partial class CP14SkillPrototype : IPrototype

View File

@@ -0,0 +1,31 @@
using Content.Shared.Stacks;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Workbench.Prototypes;
/// <summary>
///
/// </summary>
[Prototype("CP14Recipe")]
public sealed partial class CP14WorkbenchRecipePrototype : IPrototype
{
[ViewVariables]
[IdDataField]
public string ID { get; private set; } = default!;
[DataField]
public TimeSpan CraftTime = TimeSpan.FromSeconds(1f);
[DataField]
public SoundSpecifier? OverrideCraftSound;
[DataField]
public Dictionary<EntProtoId, int> Entities = new();
[DataField]
public Dictionary<ProtoId<StackPrototype>, int> Stacks = new();
[DataField(required: true)]
public EntProtoId Result = default!;
}

View File

@@ -0,0 +1,19 @@
using Content.Shared._CP14.Workbench.Prototypes;
using Content.Shared.DoAfter;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared._CP14.Workbench;
public class SharedCP14WorkbenchSystem : EntitySystem
{
}
[Serializable, NetSerializable]
public sealed partial class CP14CraftDoAfterEvent : DoAfterEvent
{
[DataField(required: true)]
public ProtoId<CP14WorkbenchRecipePrototype> Recipe = default!;
public override DoAfterEvent Clone() => this;
}