Merge branch 'master' into ed-17-07-2024-dungenagain
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
14
Content.Shared/DisplacementMap/DisplacementData.cs
Normal file
14
Content.Shared/DisplacementMap/DisplacementData.cs
Normal 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";
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Content.Shared.Eye.Blinding.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class ActivatableUIRequiresVisionComponent : Component;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
63
Content.Shared/Eye/Blinding/Systems/EyeProtectionSystem.cs
Normal file
63
Content.Shared/Eye/Blinding/Systems/EyeProtectionSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
339
Content.Shared/FixedPoint/FixedPoint4.cs
Normal file
339
Content.Shared/FixedPoint/FixedPoint4.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
21
Content.Shared/Ghost/GhostRoleRadioEvents.cs
Normal file
21
Content.Shared/Ghost/GhostRoleRadioEvents.cs
Normal 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
|
||||
}
|
||||
@@ -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 = [];
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
18
Content.Shared/Lock/LockingWhitelistComponent.cs
Normal file
18
Content.Shared/Lock/LockingWhitelistComponent.cs
Normal 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;
|
||||
}
|
||||
28
Content.Shared/Lock/LockingWhitelistSystem.cs
Normal file
28
Content.Shared/Lock/LockingWhitelistSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
16
Content.Shared/Station/Components/StationMemberComponent.cs
Normal file
16
Content.Shared/Station/Components/StationMemberComponent.cs
Normal 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;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Storage.EntitySystems;
|
||||
using Content.Shared.Storage.EntitySystems;
|
||||
using Content.Shared.Whitelist;
|
||||
|
||||
namespace Content.Shared.Storage.Components
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
18
Content.Shared/_CP14/Currency/CP14CurrencyComponent.cs
Normal file
18
Content.Shared/_CP14/Currency/CP14CurrencyComponent.cs
Normal 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;
|
||||
}
|
||||
62
Content.Shared/_CP14/Currency/CP14CurrencySystem.cs
Normal file
62
Content.Shared/_CP14/Currency/CP14CurrencySystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
317
Content.Shared/_CP14/MagicSpell/CP14SharedMagicSystem.cs
Normal file
317
Content.Shared/_CP14/MagicSpell/CP14SharedMagicSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
25
Content.Shared/_CP14/MagicSpell/Spells/CP14SpellEffect.cs
Normal file
25
Content.Shared/_CP14/MagicSpell/Spells/CP14SpellEffect.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Content.Shared/_CP14/MagicSpell/Spells/CP14ThrowToUser.cs
Normal file
23
Content.Shared/_CP14/MagicSpell/Spells/CP14ThrowToUser.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
109
Content.Shared/_CP14/MagicSpellStorage/CP14SpellStorageSystem.cs
Normal file
109
Content.Shared/_CP14/MagicSpellStorage/CP14SpellStorageSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 },
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 },
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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!;
|
||||
}
|
||||
19
Content.Shared/_CP14/Workbench/SharedCP14WorkbenchSystem.cs
Normal file
19
Content.Shared/_CP14/Workbench/SharedCP14WorkbenchSystem.cs
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user