Merge remote-tracking branch 'upstream/stable' into ed-29-10-2024-upstream
# Conflicts: # Content.Server/Chat/Managers/ChatSanitizationManager.cs # Content.Server/Temperature/Systems/TemperatureSystem.cs # Content.Shared/Localizations/ContentLocalizationManager.cs
This commit is contained in:
@@ -178,15 +178,13 @@ namespace Content.Shared.APC
|
||||
public sealed class ApcBoundInterfaceState : BoundUserInterfaceState, IEquatable<ApcBoundInterfaceState>
|
||||
{
|
||||
public readonly bool MainBreaker;
|
||||
public readonly bool HasAccess;
|
||||
public readonly int Power;
|
||||
public readonly ApcExternalPowerState ApcExternalPower;
|
||||
public readonly float Charge;
|
||||
|
||||
public ApcBoundInterfaceState(bool mainBreaker, bool hasAccess, int power, ApcExternalPowerState apcExternalPower, float charge)
|
||||
public ApcBoundInterfaceState(bool mainBreaker, int power, ApcExternalPowerState apcExternalPower, float charge)
|
||||
{
|
||||
MainBreaker = mainBreaker;
|
||||
HasAccess = hasAccess;
|
||||
Power = power;
|
||||
ApcExternalPower = apcExternalPower;
|
||||
Charge = charge;
|
||||
@@ -197,7 +195,6 @@ namespace Content.Shared.APC
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return MainBreaker == other.MainBreaker &&
|
||||
HasAccess == other.HasAccess &&
|
||||
Power == other.Power &&
|
||||
ApcExternalPower == other.ApcExternalPower &&
|
||||
MathHelper.CloseTo(Charge, other.Charge);
|
||||
@@ -210,7 +207,7 @@ namespace Content.Shared.APC
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(MainBreaker, HasAccess, Power, (int) ApcExternalPower, Charge);
|
||||
return HashCode.Combine(MainBreaker, Power, (int) ApcExternalPower, Charge);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using Content.Shared.Actions;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.ForceSay;
|
||||
using Content.Shared.Emoting;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Eye.Blinding.Systems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
@@ -61,6 +62,7 @@ public sealed partial class SleepingSystem : EntitySystem
|
||||
|
||||
SubscribeLocalEvent<ForcedSleepingComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<SleepingComponent, UnbuckleAttemptEvent>(OnUnbuckleAttempt);
|
||||
SubscribeLocalEvent<SleepingComponent, EmoteAttemptEvent>(OnEmoteAttempt);
|
||||
}
|
||||
|
||||
private void OnUnbuckleAttempt(Entity<SleepingComponent> ent, ref UnbuckleAttemptEvent args)
|
||||
@@ -310,6 +312,14 @@ public sealed partial class SleepingSystem : EntitySystem
|
||||
Wake((ent, ent.Comp));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prevents the use of emote actions while sleeping
|
||||
/// </summary>
|
||||
public void OnEmoteAttempt(Entity<SleepingComponent> ent, ref EmoteAttemptEvent args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -45,6 +45,21 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<string> DefaultGuide =
|
||||
CVarDef.Create("server.default_guide", "NewPlayer", CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
/// <summary>
|
||||
/// If greater than 0, automatically restart the server after this many minutes of uptime.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is intended to work around various bugs and performance issues caused by long continuous server uptime.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This uses the same non-disruptive logic as update restarts,
|
||||
/// i.e. the game will only restart at round end or when there is nobody connected.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<int> ServerUptimeRestartMinutes =
|
||||
CVarDef.Create("server.uptime_restart_minutes", 0, CVar.SERVERONLY);
|
||||
|
||||
/*
|
||||
* Ambience
|
||||
*/
|
||||
@@ -449,6 +464,12 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<float> GameEntityMenuLookup =
|
||||
CVarDef.Create("game.entity_menu_lookup", 0.25f, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// Should the clients window show the server hostname in the title?
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> GameHostnameInTitlebar =
|
||||
CVarDef.Create("game.hostname_in_titlebar", true, CVar.SERVER | CVar.REPLICATED);
|
||||
|
||||
/*
|
||||
* Discord
|
||||
*/
|
||||
|
||||
@@ -84,6 +84,35 @@ public abstract class SharedChatSystem : EntitySystem
|
||||
return current ?? _prototypeManager.Index<SpeechVerbPrototype>(speech.SpeechVerb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits the input message into a radio prefix part and the rest to preserve it during sanitization.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is primarily for the chat emote sanitizer, which can match against ":b" as an emote, which is a valid radio keycode.
|
||||
/// </remarks>
|
||||
public void GetRadioKeycodePrefix(EntityUid source,
|
||||
string input,
|
||||
out string output,
|
||||
out string prefix)
|
||||
{
|
||||
prefix = string.Empty;
|
||||
output = input;
|
||||
|
||||
// If the string is less than 2, then it's probably supposed to be an emote.
|
||||
// No one is sending empty radio messages!
|
||||
if (input.Length <= 2)
|
||||
return;
|
||||
|
||||
if (!(input.StartsWith(RadioChannelPrefix) || input.StartsWith(RadioChannelAltPrefix)))
|
||||
return;
|
||||
|
||||
if (!_keyCodes.TryGetValue(char.ToLower(input[1]), out _))
|
||||
return;
|
||||
|
||||
prefix = input[..2];
|
||||
output = input[2..];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to resolve radio prefixes in chat messages (e.g., remove a leading ":e" and resolve the requested
|
||||
/// channel. Returns true if a radio message was attempted, even if the channel is invalid.
|
||||
|
||||
13
Content.Shared/Chemistry/InjectOverTimeEvent.cs
Normal file
13
Content.Shared/Chemistry/InjectOverTimeEvent.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Content.Shared.Chemistry.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Raised directed on an entity when it embeds in another entity.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct InjectOverTimeEvent(EntityUid embeddedIntoUid)
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity that is embedded in.
|
||||
/// </summary>
|
||||
public readonly EntityUid EmbeddedIntoUid = embeddedIntoUid;
|
||||
}
|
||||
9
Content.Shared/Ghost/SpectralComponent.cs
Normal file
9
Content.Shared/Ghost/SpectralComponent.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Ghost;
|
||||
|
||||
/// <summary>
|
||||
/// Marker component to identify "ghostly" entities.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class SpectralComponent : Component { }
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory.VirtualItem;
|
||||
@@ -130,7 +131,7 @@ public abstract partial class SharedHandsSystem
|
||||
TransformSystem.DropNextTo((entity, itemXform), (uid, userXform));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// drop the item with heavy calculations from their hands and place it at the calculated interaction range position
|
||||
// The DoDrop is handle if there's no drop target
|
||||
DoDrop(uid, hand, doDropInteraction: doDropInteraction, handsComp);
|
||||
@@ -138,7 +139,7 @@ public abstract partial class SharedHandsSystem
|
||||
// if there's no drop location stop here
|
||||
if (targetDropLocation == null)
|
||||
return true;
|
||||
|
||||
|
||||
// otherwise, also move dropped item and rotate it properly according to grid/map
|
||||
var (itemPos, itemRot) = TransformSystem.GetWorldPositionRotation(entity);
|
||||
var origin = new MapCoordinates(itemPos, itemXform.MapID);
|
||||
@@ -197,7 +198,7 @@ public abstract partial class SharedHandsSystem
|
||||
/// <summary>
|
||||
/// Removes the contents of a hand from its container. Assumes that the removal is allowed. In general, you should not be calling this directly.
|
||||
/// </summary>
|
||||
public virtual void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, HandsComponent? handsComp = null)
|
||||
public virtual void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, HandsComponent? handsComp = null, bool log = true)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp))
|
||||
return;
|
||||
@@ -221,6 +222,9 @@ public abstract partial class SharedHandsSystem
|
||||
if (doDropInteraction)
|
||||
_interactionSystem.DroppedInteraction(uid, entity);
|
||||
|
||||
if (log)
|
||||
_adminLogger.Add(LogType.Drop, LogImpact.Low, $"{ToPrettyString(uid):user} dropped {ToPrettyString(entity):entity}");
|
||||
|
||||
if (hand == handsComp.ActiveHand)
|
||||
RaiseLocalEvent(entity, new HandDeselectedEvent(uid));
|
||||
}
|
||||
|
||||
@@ -178,8 +178,8 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
if (!CanPickupToHand(uid, entity, handsComp.ActiveHand, checkActionBlocker, handsComp))
|
||||
return false;
|
||||
|
||||
DoDrop(uid, hand, false, handsComp);
|
||||
DoPickup(uid, handsComp.ActiveHand, entity, handsComp);
|
||||
DoDrop(uid, hand, false, handsComp, log:false);
|
||||
DoPickup(uid, handsComp.ActiveHand, entity, handsComp, log: false);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Puts an entity into the player's hand, assumes that the insertion is allowed. In general, you should not be calling this function directly.
|
||||
/// </summary>
|
||||
public virtual void DoPickup(EntityUid uid, Hand hand, EntityUid entity, HandsComponent? hands = null)
|
||||
public virtual void DoPickup(EntityUid uid, Hand hand, EntityUid entity, HandsComponent? hands = null, bool log = true)
|
||||
{
|
||||
if (!Resolve(uid, ref hands))
|
||||
return;
|
||||
@@ -235,7 +235,8 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
_adminLogger.Add(LogType.Pickup, LogImpact.Low, $"{ToPrettyString(uid):user} picked up {ToPrettyString(entity):entity}");
|
||||
if (log)
|
||||
_adminLogger.Add(LogType.Pickup, LogImpact.Low, $"{ToPrettyString(uid):user} picked up {ToPrettyString(entity):entity}");
|
||||
|
||||
Dirty(uid, hands);
|
||||
|
||||
|
||||
@@ -94,22 +94,38 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem
|
||||
/// </summary>
|
||||
public void AddImplants(EntityUid uid, IEnumerable<String> implants)
|
||||
{
|
||||
var coords = Transform(uid).Coordinates;
|
||||
foreach (var id in implants)
|
||||
{
|
||||
var ent = Spawn(id, coords);
|
||||
if (TryComp<SubdermalImplantComponent>(ent, out var implant))
|
||||
{
|
||||
ForceImplant(uid, ent, implant);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning($"Found invalid starting implant '{id}' on {uid} {ToPrettyString(uid):implanted}");
|
||||
Del(ent);
|
||||
}
|
||||
AddImplant(uid, id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a single implant to a person, and returns the implant.
|
||||
/// Logs any implant ids that don't have <see cref="SubdermalImplantComponent"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The implant, if it was successfully created. Otherwise, null.
|
||||
/// </returns>>
|
||||
public EntityUid? AddImplant(EntityUid uid, String implantId)
|
||||
{
|
||||
var coords = Transform(uid).Coordinates;
|
||||
var ent = Spawn(implantId, coords);
|
||||
|
||||
if (TryComp<SubdermalImplantComponent>(ent, out var implant))
|
||||
{
|
||||
ForceImplant(uid, ent, implant);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning($"Found invalid starting implant '{implantId}' on {uid} {ToPrettyString(uid):implanted}");
|
||||
Del(ent);
|
||||
return null;
|
||||
}
|
||||
|
||||
return ent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces an implant into a person
|
||||
/// Good for on spawn related code or admin additions
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Content.Shared.Interaction.Events;
|
||||
/// </remarks>
|
||||
public sealed class ContactInteractionEvent : HandledEntityEventArgs
|
||||
{
|
||||
public readonly EntityUid Other;
|
||||
public EntityUid Other;
|
||||
|
||||
public ContactInteractionEvent(EntityUid other)
|
||||
{
|
||||
|
||||
@@ -3,5 +3,7 @@ namespace Content.Shared.Interaction.Events;
|
||||
/// <summary>
|
||||
/// Raised on the target when failing to pet/hug something.
|
||||
/// </summary>
|
||||
// TODO INTERACTION
|
||||
// Rename this, or move it to another namespace to make it clearer that this is specific to "petting/hugging" (InteractionPopupSystem)
|
||||
[ByRefEvent]
|
||||
public readonly record struct InteractionFailureEvent(EntityUid User);
|
||||
|
||||
@@ -3,5 +3,7 @@ namespace Content.Shared.Interaction.Events;
|
||||
/// <summary>
|
||||
/// Raised on the target when successfully petting/hugging something.
|
||||
/// </summary>
|
||||
// TODO INTERACTION
|
||||
// Rename this, or move it to another namespace to make it clearer that this is specific to "petting/hugging" (InteractionPopupSystem)
|
||||
[ByRefEvent]
|
||||
public readonly record struct InteractionSuccessEvent(EntityUid User);
|
||||
|
||||
@@ -456,8 +456,22 @@ namespace Content.Shared.Interaction
|
||||
inRangeUnobstructed);
|
||||
}
|
||||
|
||||
private bool IsDeleted(EntityUid uid)
|
||||
{
|
||||
return TerminatingOrDeleted(uid) || EntityManager.IsQueuedForDeletion(uid);
|
||||
}
|
||||
|
||||
private bool IsDeleted(EntityUid? uid)
|
||||
{
|
||||
//optional / null entities can pass this validation check. I.e., is-deleted returns false for null uids
|
||||
return uid != null && IsDeleted(uid.Value);
|
||||
}
|
||||
|
||||
public void InteractHand(EntityUid user, EntityUid target)
|
||||
{
|
||||
if (IsDeleted(user) || IsDeleted(target))
|
||||
return;
|
||||
|
||||
var complexInteractions = _actionBlockerSystem.CanComplexInteract(user);
|
||||
if (!complexInteractions)
|
||||
{
|
||||
@@ -466,7 +480,8 @@ namespace Content.Shared.Interaction
|
||||
checkCanInteract: false,
|
||||
checkUseDelay: true,
|
||||
checkAccess: false,
|
||||
complexInteractions: complexInteractions);
|
||||
complexInteractions: complexInteractions,
|
||||
checkDeletion: false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -479,6 +494,7 @@ namespace Content.Shared.Interaction
|
||||
return;
|
||||
}
|
||||
|
||||
DebugTools.Assert(!IsDeleted(user) && !IsDeleted(target));
|
||||
// all interactions should only happen when in range / unobstructed, so no range check is needed
|
||||
var message = new InteractHandEvent(user, target);
|
||||
RaiseLocalEvent(target, message, true);
|
||||
@@ -487,18 +503,23 @@ namespace Content.Shared.Interaction
|
||||
if (message.Handled)
|
||||
return;
|
||||
|
||||
DebugTools.Assert(!IsDeleted(user) && !IsDeleted(target));
|
||||
// Else we run Activate.
|
||||
InteractionActivate(user,
|
||||
target,
|
||||
checkCanInteract: false,
|
||||
checkUseDelay: true,
|
||||
checkAccess: false,
|
||||
complexInteractions: complexInteractions);
|
||||
complexInteractions: complexInteractions,
|
||||
checkDeletion: false);
|
||||
}
|
||||
|
||||
public void InteractUsingRanged(EntityUid user, EntityUid used, EntityUid? target,
|
||||
EntityCoordinates clickLocation, bool inRangeUnobstructed)
|
||||
{
|
||||
if (IsDeleted(user) || IsDeleted(used) || IsDeleted(target))
|
||||
return;
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
_adminLogger.Add(
|
||||
@@ -514,9 +535,10 @@ namespace Content.Shared.Interaction
|
||||
$"{ToPrettyString(user):user} interacted with *nothing* using {ToPrettyString(used):used}");
|
||||
}
|
||||
|
||||
if (RangedInteractDoBefore(user, used, target, clickLocation, inRangeUnobstructed))
|
||||
if (RangedInteractDoBefore(user, used, target, clickLocation, inRangeUnobstructed, checkDeletion: false))
|
||||
return;
|
||||
|
||||
DebugTools.Assert(!IsDeleted(user) && !IsDeleted(used) && !IsDeleted(target));
|
||||
if (target != null)
|
||||
{
|
||||
var rangedMsg = new RangedInteractEvent(user, used, target.Value, clickLocation);
|
||||
@@ -524,12 +546,12 @@ namespace Content.Shared.Interaction
|
||||
|
||||
// We contact the USED entity, but not the target.
|
||||
DoContactInteraction(user, used, rangedMsg);
|
||||
|
||||
if (rangedMsg.Handled)
|
||||
return;
|
||||
}
|
||||
|
||||
InteractDoAfter(user, used, target, clickLocation, inRangeUnobstructed);
|
||||
DebugTools.Assert(!IsDeleted(user) && !IsDeleted(used) && !IsDeleted(target));
|
||||
InteractDoAfter(user, used, target, clickLocation, inRangeUnobstructed, checkDeletion: false);
|
||||
}
|
||||
|
||||
protected bool ValidateInteractAndFace(EntityUid user, EntityCoordinates coordinates)
|
||||
@@ -933,11 +955,18 @@ namespace Content.Shared.Interaction
|
||||
EntityUid used,
|
||||
EntityUid? target,
|
||||
EntityCoordinates clickLocation,
|
||||
bool canReach)
|
||||
bool canReach,
|
||||
bool checkDeletion = true)
|
||||
{
|
||||
if (checkDeletion && (IsDeleted(user) || IsDeleted(used) || IsDeleted(target)))
|
||||
return false;
|
||||
|
||||
var ev = new BeforeRangedInteractEvent(user, used, target, clickLocation, canReach);
|
||||
RaiseLocalEvent(used, ev);
|
||||
|
||||
if (!ev.Handled)
|
||||
return false;
|
||||
|
||||
// We contact the USED entity, but not the target.
|
||||
DoContactInteraction(user, used, ev);
|
||||
return ev.Handled;
|
||||
@@ -966,6 +995,9 @@ namespace Content.Shared.Interaction
|
||||
bool checkCanInteract = true,
|
||||
bool checkCanUse = true)
|
||||
{
|
||||
if (IsDeleted(user) || IsDeleted(used) || IsDeleted(target))
|
||||
return false;
|
||||
|
||||
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, target))
|
||||
return false;
|
||||
|
||||
@@ -977,9 +1009,10 @@ namespace Content.Shared.Interaction
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(user):user} interacted with {ToPrettyString(target):target} using {ToPrettyString(used):used}");
|
||||
|
||||
if (RangedInteractDoBefore(user, used, target, clickLocation, true))
|
||||
if (RangedInteractDoBefore(user, used, target, clickLocation, canReach: true, checkDeletion: false))
|
||||
return true;
|
||||
|
||||
DebugTools.Assert(!IsDeleted(user) && !IsDeleted(used) && !IsDeleted(target));
|
||||
// all interactions should only happen when in range / unobstructed, so no range check is needed
|
||||
var interactUsingEvent = new InteractUsingEvent(user, used, target, clickLocation);
|
||||
RaiseLocalEvent(target, interactUsingEvent, true);
|
||||
@@ -989,8 +1022,10 @@ namespace Content.Shared.Interaction
|
||||
if (interactUsingEvent.Handled)
|
||||
return true;
|
||||
|
||||
if (InteractDoAfter(user, used, target, clickLocation, canReach: true))
|
||||
if (InteractDoAfter(user, used, target, clickLocation, canReach: true, checkDeletion: false))
|
||||
return true;
|
||||
|
||||
DebugTools.Assert(!IsDeleted(user) && !IsDeleted(used) && !IsDeleted(target));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1004,11 +1039,14 @@ namespace Content.Shared.Interaction
|
||||
/// <param name="canReach">Whether the <paramref name="user"/> is in range of the <paramref name="target"/>.
|
||||
/// </param>
|
||||
/// <returns>True if the interaction was handled. Otherwise, false.</returns>
|
||||
public bool InteractDoAfter(EntityUid user, EntityUid used, EntityUid? target, EntityCoordinates clickLocation, bool canReach)
|
||||
public bool InteractDoAfter(EntityUid user, EntityUid used, EntityUid? target, EntityCoordinates clickLocation, bool canReach, bool checkDeletion = true)
|
||||
{
|
||||
if (target is { Valid: false })
|
||||
target = null;
|
||||
|
||||
if (checkDeletion && (IsDeleted(user) || IsDeleted(used) || IsDeleted(target)))
|
||||
return false;
|
||||
|
||||
var afterInteractEvent = new AfterInteractEvent(user, used, target, clickLocation, canReach);
|
||||
RaiseLocalEvent(used, afterInteractEvent);
|
||||
DoContactInteraction(user, used, afterInteractEvent);
|
||||
@@ -1024,6 +1062,7 @@ namespace Content.Shared.Interaction
|
||||
if (target == null)
|
||||
return false;
|
||||
|
||||
DebugTools.Assert(!IsDeleted(user) && !IsDeleted(used) && !IsDeleted(target));
|
||||
var afterInteractUsingEvent = new AfterInteractUsingEvent(user, used, target, clickLocation, canReach);
|
||||
RaiseLocalEvent(target.Value, afterInteractUsingEvent);
|
||||
|
||||
@@ -1034,9 +1073,7 @@ namespace Content.Shared.Interaction
|
||||
// Contact interactions are currently only used for forensics, so we don't raise used -> target
|
||||
}
|
||||
|
||||
if (afterInteractUsingEvent.Handled)
|
||||
return true;
|
||||
return false;
|
||||
return afterInteractUsingEvent.Handled;
|
||||
}
|
||||
|
||||
#region ActivateItemInWorld
|
||||
@@ -1068,8 +1105,13 @@ namespace Content.Shared.Interaction
|
||||
bool checkCanInteract = true,
|
||||
bool checkUseDelay = true,
|
||||
bool checkAccess = true,
|
||||
bool? complexInteractions = null)
|
||||
bool? complexInteractions = null,
|
||||
bool checkDeletion = true)
|
||||
{
|
||||
if (checkDeletion && (IsDeleted(user) || IsDeleted(used)))
|
||||
return false;
|
||||
|
||||
DebugTools.Assert(!IsDeleted(user) && !IsDeleted(used));
|
||||
_delayQuery.TryComp(used, out var delayComponent);
|
||||
if (checkUseDelay && delayComponent != null && _useDelay.IsDelayed((used, delayComponent)))
|
||||
return false;
|
||||
@@ -1085,21 +1127,32 @@ namespace Content.Shared.Interaction
|
||||
if (checkAccess && !IsAccessible(user, used))
|
||||
return false;
|
||||
|
||||
complexInteractions ??= SupportsComplexInteractions(user);
|
||||
complexInteractions ??= _actionBlockerSystem.CanComplexInteract(user);
|
||||
var activateMsg = new ActivateInWorldEvent(user, used, complexInteractions.Value);
|
||||
RaiseLocalEvent(used, activateMsg, true);
|
||||
if (activateMsg.Handled)
|
||||
{
|
||||
DoContactInteraction(user, used);
|
||||
if (!activateMsg.WasLogged)
|
||||
_adminLogger.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}");
|
||||
|
||||
if (delayComponent != null)
|
||||
_useDelay.TryResetDelay(used, component: delayComponent);
|
||||
return true;
|
||||
}
|
||||
|
||||
DebugTools.Assert(!IsDeleted(user) && !IsDeleted(used));
|
||||
var userEv = new UserActivateInWorldEvent(user, used, complexInteractions.Value);
|
||||
RaiseLocalEvent(user, userEv, true);
|
||||
if (!activateMsg.Handled && !userEv.Handled)
|
||||
if (!userEv.Handled)
|
||||
return false;
|
||||
|
||||
DoContactInteraction(user, used, activateMsg);
|
||||
DoContactInteraction(user, used);
|
||||
// Still need to call this even without checkUseDelay in case this gets relayed from Activate.
|
||||
if (delayComponent != null)
|
||||
_useDelay.TryResetDelay(used, component: delayComponent);
|
||||
|
||||
if (!activateMsg.WasLogged)
|
||||
_adminLogger.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}");
|
||||
_adminLogger.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}");
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
@@ -1118,6 +1171,9 @@ namespace Content.Shared.Interaction
|
||||
bool checkCanInteract = true,
|
||||
bool checkUseDelay = true)
|
||||
{
|
||||
if (IsDeleted(user) || IsDeleted(used))
|
||||
return false;
|
||||
|
||||
_delayQuery.TryComp(used, out var delayComponent);
|
||||
if (checkUseDelay && delayComponent != null && _useDelay.IsDelayed((used, delayComponent)))
|
||||
return true; // if the item is on cooldown, we consider this handled.
|
||||
@@ -1138,8 +1194,9 @@ namespace Content.Shared.Interaction
|
||||
return true;
|
||||
}
|
||||
|
||||
DebugTools.Assert(!IsDeleted(user) && !IsDeleted(used));
|
||||
// else, default to activating the item
|
||||
return InteractionActivate(user, used, false, false, false);
|
||||
return InteractionActivate(user, used, false, false, false, checkDeletion: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1164,10 +1221,11 @@ namespace Content.Shared.Interaction
|
||||
|
||||
public void DroppedInteraction(EntityUid user, EntityUid item)
|
||||
{
|
||||
if (IsDeleted(user) || IsDeleted(item))
|
||||
return;
|
||||
|
||||
var dropMsg = new DroppedEvent(user);
|
||||
RaiseLocalEvent(item, dropMsg, true);
|
||||
if (dropMsg.Handled)
|
||||
_adminLogger.Add(LogType.Drop, LogImpact.Low, $"{ToPrettyString(user):user} dropped {ToPrettyString(item):entity}");
|
||||
|
||||
// If the dropper is rotated then use their targetrelativerotation as the drop rotation
|
||||
var rotation = Angle.Zero;
|
||||
@@ -1314,15 +1372,21 @@ namespace Content.Shared.Interaction
|
||||
if (uidB == null || args?.Handled == false)
|
||||
return;
|
||||
|
||||
// Entities may no longer exist (banana was eaten, or human was exploded)?
|
||||
if (!Exists(uidA) || !Exists(uidB))
|
||||
if (uidA == uidB.Value)
|
||||
return;
|
||||
|
||||
if (Paused(uidA) || Paused(uidB.Value))
|
||||
if (!TryComp(uidA, out MetaDataComponent? metaA) || metaA.EntityPaused)
|
||||
return;
|
||||
|
||||
RaiseLocalEvent(uidA, new ContactInteractionEvent(uidB.Value));
|
||||
RaiseLocalEvent(uidB.Value, new ContactInteractionEvent(uidA));
|
||||
if (!TryComp(uidB, out MetaDataComponent? metaB) || metaB.EntityPaused)
|
||||
return ;
|
||||
|
||||
// TODO Struct event
|
||||
var ev = new ContactInteractionEvent(uidB.Value);
|
||||
RaiseLocalEvent(uidA, ev);
|
||||
|
||||
ev.Other = uidA;
|
||||
RaiseLocalEvent(uidB.Value, ev);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ namespace Content.Shared.Localizations
|
||||
_loc.AddFunction(culture, "LOC", FormatLoc);
|
||||
_loc.AddFunction(culture, "NATURALFIXED", FormatNaturalFixed);
|
||||
_loc.AddFunction(culture, "NATURALPERCENT", FormatNaturalPercent);
|
||||
_loc.AddFunction(culture, "PLAYTIME", FormatPlaytime);
|
||||
|
||||
_loc.AddFunction(culture, "MANY", FormatMany); // TODO: Temporary fix for MANY() fluent errors. Remove after resolve errors.
|
||||
|
||||
/*
|
||||
@@ -151,6 +153,16 @@ namespace Content.Shared.Localizations
|
||||
return Loc.GetString($"zzzz-fmt-direction-{dir.ToString()}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats playtime as hours and minutes.
|
||||
/// </summary>
|
||||
public static string FormatPlaytime(TimeSpan time)
|
||||
{
|
||||
var hours = (int)time.TotalHours;
|
||||
var minutes = time.Minutes;
|
||||
return Loc.GetString($"zzzz-fmt-playtime", ("hours", hours), ("minutes", minutes));
|
||||
}
|
||||
|
||||
private static ILocValue FormatLoc(LocArgs args)
|
||||
{
|
||||
var id = ((LocValueString) args.Args[0]).Value;
|
||||
@@ -239,5 +251,15 @@ namespace Content.Shared.Localizations
|
||||
|
||||
return new LocValueString(res);
|
||||
}
|
||||
|
||||
private static ILocValue FormatPlaytime(LocArgs args)
|
||||
{
|
||||
var time = TimeSpan.Zero;
|
||||
if (args.Args is { Count: > 0 } && args.Args[0].Value is TimeSpan timeArg)
|
||||
{
|
||||
time = timeArg;
|
||||
}
|
||||
return new LocValueString(FormatPlaytime(time));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,19 +483,6 @@ public abstract class SharedMindSystem : EntitySystem
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a role component from a player's mind.
|
||||
/// </summary>
|
||||
/// <returns>Whether a role was found</returns>
|
||||
public bool TryGetRole<T>(EntityUid user, [NotNullWhen(true)] out T? role) where T : IComponent
|
||||
{
|
||||
role = default;
|
||||
if (!TryComp<MindContainerComponent>(user, out var mindContainer) || mindContainer.Mind == null)
|
||||
return false;
|
||||
|
||||
return TryComp(mindContainer.Mind, out role);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the Mind's UserId, Session, and updates the player's PlayerData. This should have no direct effect on the
|
||||
/// entity that any mind is connected to, except as a side effect of the fact that it may change a player's
|
||||
|
||||
@@ -27,6 +27,50 @@ public sealed partial class NavMapComponent : Component
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Dictionary<NetEntity, SharedNavMapSystem.NavMapBeacon> Beacons = new();
|
||||
|
||||
/// <summary>
|
||||
/// Describes the properties of a region on the station.
|
||||
/// It is indexed by the entity assigned as the region owner.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public Dictionary<NetEntity, SharedNavMapSystem.NavMapRegionProperties> RegionProperties = new();
|
||||
|
||||
/// <summary>
|
||||
/// All flood filled regions, ready for display on a NavMapControl.
|
||||
/// It is indexed by the entity assigned as the region owner.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For client use only
|
||||
/// </remarks>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public Dictionary<NetEntity, NavMapRegionOverlay> RegionOverlays = new();
|
||||
|
||||
/// <summary>
|
||||
/// A queue of all region owners that are waiting their associated regions to be floodfilled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For client use only
|
||||
/// </remarks>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public Queue<NetEntity> QueuedRegionsToFlood = new();
|
||||
|
||||
/// <summary>
|
||||
/// A look up table to get a list of region owners associated with a flood filled chunk.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For client use only
|
||||
/// </remarks>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public Dictionary<Vector2i, HashSet<NetEntity>> ChunkToRegionOwnerTable = new();
|
||||
|
||||
/// <summary>
|
||||
/// A look up table to find flood filled chunks associated with a given region owner.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For client use only
|
||||
/// </remarks>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public Dictionary<NetEntity, HashSet<Vector2i>> RegionOwnerToChunkTable = new();
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
@@ -51,10 +95,30 @@ public sealed class NavMapChunk(Vector2i origin)
|
||||
public GameTick LastUpdate;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NavMapRegionOverlay(Enum uiKey, List<(Vector2i, Vector2i)> gridCoords)
|
||||
{
|
||||
/// <summary>
|
||||
/// The key to the UI that will be displaying this region on its navmap
|
||||
/// </summary>
|
||||
public Enum UiKey = uiKey;
|
||||
|
||||
/// <summary>
|
||||
/// The local grid coordinates of the rectangles that make up the region
|
||||
/// Item1 is the top left corner, Item2 is the bottom right corner
|
||||
/// </summary>
|
||||
public List<(Vector2i, Vector2i)> GridCoords = gridCoords;
|
||||
|
||||
/// <summary>
|
||||
/// Color of the region
|
||||
/// </summary>
|
||||
public Color Color = Color.White;
|
||||
}
|
||||
|
||||
public enum NavMapChunkType : byte
|
||||
{
|
||||
// Values represent bit shift offsets when retrieving data in the tile array.
|
||||
Invalid = byte.MaxValue,
|
||||
Invalid = byte.MaxValue,
|
||||
Floor = 0, // I believe floors have directional information for diagonal tiles?
|
||||
Wall = SharedNavMapSystem.Directions,
|
||||
Airlock = 2 * SharedNavMapSystem.Directions,
|
||||
|
||||
@@ -3,10 +3,9 @@ using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Pinpointer;
|
||||
|
||||
@@ -16,7 +15,7 @@ public abstract class SharedNavMapSystem : EntitySystem
|
||||
public const int Directions = 4; // Not directly tied to number of atmos directions
|
||||
|
||||
public const int ChunkSize = 8;
|
||||
public const int ArraySize = ChunkSize* ChunkSize;
|
||||
public const int ArraySize = ChunkSize * ChunkSize;
|
||||
|
||||
public const int AllDirMask = (1 << Directions) - 1;
|
||||
public const int AirlockMask = AllDirMask << (int) NavMapChunkType.Airlock;
|
||||
@@ -24,6 +23,7 @@ public abstract class SharedNavMapSystem : EntitySystem
|
||||
public const int FloorMask = AllDirMask << (int) NavMapChunkType.Floor;
|
||||
|
||||
[Robust.Shared.IoC.Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
[Robust.Shared.IoC.Dependency] private readonly INetManager _net = default!;
|
||||
|
||||
private static readonly ProtoId<TagPrototype>[] WallTags = {"Wall", "Window"};
|
||||
private EntityQuery<NavMapDoorComponent> _doorQuery;
|
||||
@@ -57,7 +57,7 @@ public abstract class SharedNavMapSystem : EntitySystem
|
||||
public NavMapChunkType GetEntityType(EntityUid uid)
|
||||
{
|
||||
if (_doorQuery.HasComp(uid))
|
||||
return NavMapChunkType.Airlock;
|
||||
return NavMapChunkType.Airlock;
|
||||
|
||||
if (_tagSystem.HasAnyTag(uid, WallTags))
|
||||
return NavMapChunkType.Wall;
|
||||
@@ -81,6 +81,57 @@ public abstract class SharedNavMapSystem : EntitySystem
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AddOrUpdateNavMapRegion(EntityUid uid, NavMapComponent component, NetEntity regionOwner, NavMapRegionProperties regionProperties)
|
||||
{
|
||||
// Check if a new region has been added or an existing one has been altered
|
||||
var isDirty = !component.RegionProperties.TryGetValue(regionOwner, out var oldProperties) || oldProperties != regionProperties;
|
||||
|
||||
if (isDirty)
|
||||
{
|
||||
component.RegionProperties[regionOwner] = regionProperties;
|
||||
|
||||
if (_net.IsServer)
|
||||
Dirty(uid, component);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveNavMapRegion(EntityUid uid, NavMapComponent component, NetEntity regionOwner)
|
||||
{
|
||||
bool regionOwnerRemoved = component.RegionProperties.Remove(regionOwner) | component.RegionOverlays.Remove(regionOwner);
|
||||
|
||||
if (regionOwnerRemoved)
|
||||
{
|
||||
if (component.RegionOwnerToChunkTable.TryGetValue(regionOwner, out var affectedChunks))
|
||||
{
|
||||
foreach (var affectedChunk in affectedChunks)
|
||||
{
|
||||
if (component.ChunkToRegionOwnerTable.TryGetValue(affectedChunk, out var regionOwners))
|
||||
regionOwners.Remove(regionOwner);
|
||||
}
|
||||
|
||||
component.RegionOwnerToChunkTable.Remove(regionOwner);
|
||||
}
|
||||
|
||||
if (_net.IsServer)
|
||||
Dirty(uid, component);
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<NetEntity, NavMapRegionOverlay> GetNavMapRegionOverlays(EntityUid uid, NavMapComponent component, Enum uiKey)
|
||||
{
|
||||
var regionOverlays = new Dictionary<NetEntity, NavMapRegionOverlay>();
|
||||
|
||||
foreach (var (regionOwner, regionOverlay) in component.RegionOverlays)
|
||||
{
|
||||
if (!regionOverlay.UiKey.Equals(uiKey))
|
||||
continue;
|
||||
|
||||
regionOverlays.Add(regionOwner, regionOverlay);
|
||||
}
|
||||
|
||||
return regionOverlays;
|
||||
}
|
||||
|
||||
#region: Event handling
|
||||
|
||||
private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentGetState args)
|
||||
@@ -97,7 +148,7 @@ public abstract class SharedNavMapSystem : EntitySystem
|
||||
chunks.Add(origin, chunk.TileData);
|
||||
}
|
||||
|
||||
args.State = new NavMapState(chunks, component.Beacons);
|
||||
args.State = new NavMapState(chunks, component.Beacons, component.RegionProperties);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -110,7 +161,7 @@ public abstract class SharedNavMapSystem : EntitySystem
|
||||
chunks.Add(origin, chunk.TileData);
|
||||
}
|
||||
|
||||
args.State = new NavMapDeltaState(chunks, component.Beacons, new(component.Chunks.Keys));
|
||||
args.State = new NavMapDeltaState(chunks, component.Beacons, component.RegionProperties, new(component.Chunks.Keys));
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -120,22 +171,26 @@ public abstract class SharedNavMapSystem : EntitySystem
|
||||
[Serializable, NetSerializable]
|
||||
protected sealed class NavMapState(
|
||||
Dictionary<Vector2i, int[]> chunks,
|
||||
Dictionary<NetEntity, NavMapBeacon> beacons)
|
||||
Dictionary<NetEntity, NavMapBeacon> beacons,
|
||||
Dictionary<NetEntity, NavMapRegionProperties> regions)
|
||||
: ComponentState
|
||||
{
|
||||
public Dictionary<Vector2i, int[]> Chunks = chunks;
|
||||
public Dictionary<NetEntity, NavMapBeacon> Beacons = beacons;
|
||||
public Dictionary<NetEntity, NavMapRegionProperties> Regions = regions;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
protected sealed class NavMapDeltaState(
|
||||
Dictionary<Vector2i, int[]> modifiedChunks,
|
||||
Dictionary<NetEntity, NavMapBeacon> beacons,
|
||||
Dictionary<NetEntity, NavMapRegionProperties> regions,
|
||||
HashSet<Vector2i> allChunks)
|
||||
: ComponentState, IComponentDeltaState<NavMapState>
|
||||
{
|
||||
public Dictionary<Vector2i, int[]> ModifiedChunks = modifiedChunks;
|
||||
public Dictionary<NetEntity, NavMapBeacon> Beacons = beacons;
|
||||
public Dictionary<NetEntity, NavMapRegionProperties> Regions = regions;
|
||||
public HashSet<Vector2i> AllChunks = allChunks;
|
||||
|
||||
public void ApplyToFullState(NavMapState state)
|
||||
@@ -159,11 +214,18 @@ public abstract class SharedNavMapSystem : EntitySystem
|
||||
{
|
||||
state.Beacons.Add(nuid, beacon);
|
||||
}
|
||||
|
||||
state.Regions.Clear();
|
||||
foreach (var (nuid, region) in Regions)
|
||||
{
|
||||
state.Regions.Add(nuid, region);
|
||||
}
|
||||
}
|
||||
|
||||
public NavMapState CreateNewFullState(NavMapState state)
|
||||
{
|
||||
var chunks = new Dictionary<Vector2i, int[]>(state.Chunks.Count);
|
||||
|
||||
foreach (var (index, data) in state.Chunks)
|
||||
{
|
||||
if (!AllChunks!.Contains(index))
|
||||
@@ -177,12 +239,25 @@ public abstract class SharedNavMapSystem : EntitySystem
|
||||
Array.Copy(newData, data, ArraySize);
|
||||
}
|
||||
|
||||
return new NavMapState(chunks, new(Beacons));
|
||||
return new NavMapState(chunks, new(Beacons), new(Regions));
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public record struct NavMapBeacon(NetEntity NetEnt, Color Color, string Text, Vector2 Position);
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public record struct NavMapRegionProperties(NetEntity Owner, Enum UiKey, HashSet<Vector2i> Seeds)
|
||||
{
|
||||
// Server defined color for the region
|
||||
public Color Color = Color.White;
|
||||
|
||||
// The maximum number of tiles that can be assigned to this region
|
||||
public int MaxArea = 625;
|
||||
|
||||
// The maximum distance this region can propagate from its seeds
|
||||
public int MaxRadius = 25;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ using Robust.Shared.GameStates;
|
||||
namespace Content.Shared.Power.Generator;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class ActiveGeneratorRevvingComponent: Component
|
||||
public sealed partial class ActiveGeneratorRevvingComponent : Component
|
||||
{
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly), AutoNetworkedField]
|
||||
public TimeSpan CurrentTime = TimeSpan.Zero;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Content.Shared.Power.Generator;
|
||||
|
||||
public sealed class ActiveGeneratorRevvingSystem: EntitySystem
|
||||
public sealed class ActiveGeneratorRevvingSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -25,7 +25,7 @@ public sealed class ActiveGeneratorRevvingSystem: EntitySystem
|
||||
/// <param name="component">ActiveGeneratorRevvingComponent of the generator entity.</param>
|
||||
public void StartAutoRevving(EntityUid uid, ActiveGeneratorRevvingComponent? component = null)
|
||||
{
|
||||
if (Resolve(uid, ref component))
|
||||
if (Resolve(uid, ref component, false))
|
||||
{
|
||||
// reset the revving
|
||||
component.CurrentTime = TimeSpan.FromSeconds(0);
|
||||
|
||||
@@ -13,37 +13,43 @@ public sealed partial class EmbeddableProjectileComponent : Component
|
||||
/// <summary>
|
||||
/// Minimum speed of the projectile to embed.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public float MinimumSpeed = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// Delete the entity on embedded removal?
|
||||
/// Does nothing if there's no RemovalTime.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool DeleteOnRemove;
|
||||
|
||||
/// <summary>
|
||||
/// How long it takes to remove the embedded object.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public float? RemovalTime = 3f;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this entity will embed when thrown, or only when shot as a projectile.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool EmbedOnThrow = true;
|
||||
|
||||
/// <summary>
|
||||
/// How far into the entity should we offset (0 is wherever we collided).
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public Vector2 Offset = Vector2.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play after embedding into a hit target.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public SoundSpecifier? Sound;
|
||||
|
||||
/// <summary>
|
||||
/// Uid of the entity the projectile is embed into.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? EmbeddedIntoUid;
|
||||
}
|
||||
|
||||
@@ -71,6 +71,8 @@ public abstract partial class SharedProjectileSystem : EntitySystem
|
||||
TryComp<PhysicsComponent>(uid, out var physics);
|
||||
_physics.SetBodyType(uid, BodyType.Dynamic, body: physics, xform: xform);
|
||||
_transform.AttachToGridOrMap(uid, xform);
|
||||
component.EmbeddedIntoUid = null;
|
||||
Dirty(uid, component);
|
||||
|
||||
// Reset whether the projectile has damaged anything if it successfully was removed
|
||||
if (TryComp<ProjectileComponent>(uid, out var projectile))
|
||||
@@ -127,8 +129,10 @@ public abstract partial class SharedProjectileSystem : EntitySystem
|
||||
}
|
||||
|
||||
_audio.PlayPredicted(component.Sound, uid, null);
|
||||
component.EmbeddedIntoUid = target;
|
||||
var ev = new EmbedEvent(user, target);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
private void PreventCollision(EntityUid uid, ProjectileComponent component, ref PreventCollideEvent args)
|
||||
|
||||
@@ -42,10 +42,12 @@ public abstract class SharedJammerSystem : EntitySystem
|
||||
{
|
||||
entity.Comp.SelectedPowerLevel = currIndex;
|
||||
Dirty(entity);
|
||||
if (_jammer.TrySetRange(entity.Owner, GetCurrentRange(entity)))
|
||||
{
|
||||
Popup.PopupPredicted(Loc.GetString(setting.Message), user, user);
|
||||
}
|
||||
|
||||
// If the jammer is off, this won't do anything which is fine.
|
||||
// The range should be updated when it turns on again!
|
||||
_jammer.TrySetRange(entity.Owner, GetCurrentRange(entity));
|
||||
|
||||
Popup.PopupClient(Loc.GetString(setting.Message), user, user);
|
||||
},
|
||||
Text = Loc.GetString(setting.Name),
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@ public sealed partial class AgeRequirement : JobRequirement
|
||||
|
||||
if (!Inverted)
|
||||
{
|
||||
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-age-to-young",
|
||||
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-age-too-young",
|
||||
("age", RequiredAge)));
|
||||
|
||||
if (profile.Age < RequiredAge)
|
||||
@@ -38,7 +38,7 @@ public sealed partial class AgeRequirement : JobRequirement
|
||||
}
|
||||
else
|
||||
{
|
||||
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-age-to-old",
|
||||
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-age-too-old",
|
||||
("age", RequiredAge)));
|
||||
|
||||
if (profile.Age > RequiredAge)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Localizations;
|
||||
using Content.Shared.Preferences;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -15,7 +16,7 @@ public sealed partial class DepartmentTimeRequirement : JobRequirement
|
||||
/// Which department needs the required amount of time.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public ProtoId<DepartmentPrototype> Department = default!;
|
||||
public ProtoId<DepartmentPrototype> Department;
|
||||
|
||||
/// <summary>
|
||||
/// How long (in seconds) this requirement is.
|
||||
@@ -47,7 +48,9 @@ public sealed partial class DepartmentTimeRequirement : JobRequirement
|
||||
playtime += otherTime;
|
||||
}
|
||||
|
||||
var deptDiff = Time.TotalMinutes - playtime.TotalMinutes;
|
||||
var deptDiffSpan = Time - playtime;
|
||||
var deptDiff = deptDiffSpan.TotalMinutes;
|
||||
var formattedDeptDiff = ContentLocalizationManager.FormatPlaytime(deptDiffSpan);
|
||||
var nameDepartment = "role-timer-department-unknown";
|
||||
|
||||
if (protoManager.TryIndex(Department, out var departmentIndexed))
|
||||
@@ -62,7 +65,7 @@ public sealed partial class DepartmentTimeRequirement : JobRequirement
|
||||
|
||||
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
|
||||
"role-timer-department-insufficient",
|
||||
("time", Math.Ceiling(deptDiff)),
|
||||
("time", formattedDeptDiff),
|
||||
("department", Loc.GetString(nameDepartment)),
|
||||
("departmentColor", department.Color.ToHex())));
|
||||
return false;
|
||||
@@ -72,7 +75,7 @@ public sealed partial class DepartmentTimeRequirement : JobRequirement
|
||||
{
|
||||
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
|
||||
"role-timer-department-too-high",
|
||||
("time", -deptDiff),
|
||||
("time", formattedDeptDiff),
|
||||
("department", Loc.GetString(nameDepartment)),
|
||||
("departmentColor", department.Color.ToHex())));
|
||||
return false;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Localizations;
|
||||
using Content.Shared.Players.PlayTimeTracking;
|
||||
using Content.Shared.Preferences;
|
||||
using JetBrains.Annotations;
|
||||
@@ -25,7 +26,9 @@ public sealed partial class OverallPlaytimeRequirement : JobRequirement
|
||||
reason = new FormattedMessage();
|
||||
|
||||
var overallTime = playTimes.GetValueOrDefault(PlayTimeTrackingShared.TrackerOverall);
|
||||
var overallDiff = Time.TotalMinutes - overallTime.TotalMinutes;
|
||||
var overallDiffSpan = Time - overallTime;
|
||||
var overallDiff = overallDiffSpan.TotalMinutes;
|
||||
var formattedOverallDiff = ContentLocalizationManager.FormatPlaytime(overallDiffSpan);
|
||||
|
||||
if (!Inverted)
|
||||
{
|
||||
@@ -34,14 +37,14 @@ public sealed partial class OverallPlaytimeRequirement : JobRequirement
|
||||
|
||||
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
|
||||
"role-timer-overall-insufficient",
|
||||
("time", Math.Ceiling(overallDiff))));
|
||||
("time", formattedOverallDiff)));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (overallDiff <= 0 || overallTime >= Time)
|
||||
{
|
||||
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-overall-too-high",
|
||||
("time", -overallDiff)));
|
||||
("time", formattedOverallDiff)));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Localizations;
|
||||
using Content.Shared.Players.PlayTimeTracking;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
@@ -17,7 +18,7 @@ public sealed partial class RoleTimeRequirement : JobRequirement
|
||||
/// What particular role they need the time requirement with.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public ProtoId<PlayTimeTrackerPrototype> Role = default!;
|
||||
public ProtoId<PlayTimeTrackerPrototype> Role;
|
||||
|
||||
/// <inheritdoc cref="DepartmentTimeRequirement.Time"/>
|
||||
[DataField(required: true)]
|
||||
@@ -34,7 +35,9 @@ public sealed partial class RoleTimeRequirement : JobRequirement
|
||||
string proto = Role;
|
||||
|
||||
playTimes.TryGetValue(proto, out var roleTime);
|
||||
var roleDiff = Time.TotalMinutes - roleTime.TotalMinutes;
|
||||
var roleDiffSpan = Time - roleTime;
|
||||
var roleDiff = roleDiffSpan.TotalMinutes;
|
||||
var formattedRoleDiff = ContentLocalizationManager.FormatPlaytime(roleDiffSpan);
|
||||
var departmentColor = Color.Yellow;
|
||||
|
||||
if (entManager.EntitySysManager.TryGetEntitySystem(out SharedJobSystem? jobSystem))
|
||||
@@ -52,7 +55,7 @@ public sealed partial class RoleTimeRequirement : JobRequirement
|
||||
|
||||
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
|
||||
"role-timer-role-insufficient",
|
||||
("time", Math.Ceiling(roleDiff)),
|
||||
("time", formattedRoleDiff),
|
||||
("job", Loc.GetString(proto)),
|
||||
("departmentColor", departmentColor.ToHex())));
|
||||
return false;
|
||||
@@ -62,7 +65,7 @@ public sealed partial class RoleTimeRequirement : JobRequirement
|
||||
{
|
||||
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
|
||||
"role-timer-role-too-high",
|
||||
("time", -roleDiff),
|
||||
("time", formattedRoleDiff),
|
||||
("job", Loc.GetString(proto)),
|
||||
("departmentColor", departmentColor.ToHex())));
|
||||
return false;
|
||||
|
||||
@@ -103,7 +103,6 @@ public abstract class SharedJobSystem : EntitySystem
|
||||
public bool MindHasJobWithId(EntityUid? mindId, string prototypeId)
|
||||
{
|
||||
|
||||
MindRoleComponent? comp = null;
|
||||
if (mindId is null)
|
||||
return false;
|
||||
|
||||
@@ -112,9 +111,7 @@ public abstract class SharedJobSystem : EntitySystem
|
||||
if (role is null)
|
||||
return false;
|
||||
|
||||
comp = role.Value.Comp;
|
||||
|
||||
return (comp.JobPrototype == prototypeId);
|
||||
return role.Value.Comp1.JobPrototype == prototypeId;
|
||||
}
|
||||
|
||||
public bool MindTryGetJob(
|
||||
@@ -124,7 +121,7 @@ public abstract class SharedJobSystem : EntitySystem
|
||||
prototype = null;
|
||||
MindTryGetJobId(mindId, out var protoId);
|
||||
|
||||
return (_prototypes.TryIndex<JobPrototype>(protoId, out prototype) || prototype is not null);
|
||||
return _prototypes.TryIndex(protoId, out prototype) || prototype is not null;
|
||||
}
|
||||
|
||||
public bool MindTryGetJobId(
|
||||
@@ -137,9 +134,9 @@ public abstract class SharedJobSystem : EntitySystem
|
||||
return false;
|
||||
|
||||
if (_roles.MindHasRole<JobRoleComponent>(mindId.Value, out var role))
|
||||
job = role.Value.Comp.JobPrototype;
|
||||
job = role.Value.Comp1.JobPrototype;
|
||||
|
||||
return (job is not null);
|
||||
return job is not null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -42,6 +42,8 @@ public sealed partial class MindRoleComponent : BaseMindRoleComponent
|
||||
public ProtoId<JobPrototype>? JobPrototype { get; set; }
|
||||
}
|
||||
|
||||
// Why does this base component actually exist? It does make auto-categorization easy, but before that it was useless?
|
||||
[EntityCategory("Roles")]
|
||||
public abstract partial class BaseMindRoleComponent : Component
|
||||
{
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Roles;
|
||||
|
||||
@@ -92,19 +93,18 @@ public abstract class SharedRoleSystem : EntitySystem
|
||||
bool silent = false,
|
||||
string? jobPrototype = null)
|
||||
{
|
||||
// Can't have someone get paid for two jobs now, can we
|
||||
if (MindHasRole<JobRoleComponent>(mindId, out var jobRole)
|
||||
&& jobRole.Value.Comp.JobPrototype != jobPrototype)
|
||||
{
|
||||
Resolve(mindId, ref mind);
|
||||
if (mind is not null)
|
||||
{
|
||||
_adminLogger.Add(LogType.Mind,
|
||||
LogImpact.Low,
|
||||
$"Job Role of {ToPrettyString(mind.OwnedEntity)} changed from '{jobRole.Value.Comp.JobPrototype}' to '{jobPrototype}'");
|
||||
}
|
||||
if (!Resolve(mindId, ref mind))
|
||||
return;
|
||||
|
||||
jobRole.Value.Comp.JobPrototype = jobPrototype;
|
||||
// Can't have someone get paid for two jobs now, can we
|
||||
if (MindHasRole<JobRoleComponent>((mindId, mind), out var jobRole)
|
||||
&& jobRole.Value.Comp1.JobPrototype != jobPrototype)
|
||||
{
|
||||
_adminLogger.Add(LogType.Mind,
|
||||
LogImpact.Low,
|
||||
$"Job Role of {ToPrettyString(mind.OwnedEntity)} changed from '{jobRole.Value.Comp1.JobPrototype}' to '{jobPrototype}'");
|
||||
|
||||
jobRole.Value.Comp1.JobPrototype = jobPrototype;
|
||||
}
|
||||
else
|
||||
MindAddRoleDo(mindId, "MindRoleJob", mind, silent, jobPrototype);
|
||||
@@ -146,11 +146,12 @@ public abstract class SharedRoleSystem : EntitySystem
|
||||
{
|
||||
mindRoleComp.JobPrototype = jobPrototype;
|
||||
EnsureComp<JobRoleComponent>(mindRoleId);
|
||||
DebugTools.AssertNull(mindRoleComp.AntagPrototype);
|
||||
DebugTools.Assert(!mindRoleComp.Antag);
|
||||
DebugTools.Assert(!mindRoleComp.ExclusiveAntag);
|
||||
}
|
||||
|
||||
if (mindRoleComp.Antag || mindRoleComp.ExclusiveAntag)
|
||||
antagonist = true;
|
||||
|
||||
antagonist |= mindRoleComp.Antag;
|
||||
mind.MindRoles.Add(mindRoleId);
|
||||
|
||||
var mindEv = new MindRoleAddedEvent(silent);
|
||||
@@ -182,51 +183,55 @@ public abstract class SharedRoleSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Removes all instances of a specific role from this mind.
|
||||
/// </summary>
|
||||
/// <param name="mindId">The mind to remove the role from.</param>
|
||||
/// <param name="mind">The mind to remove the role from.</param>
|
||||
/// <typeparam name="T">The type of the role to remove.</typeparam>
|
||||
/// <exception cref="ArgumentException">Thrown if the mind does not exist or does not have this role.</exception>
|
||||
/// <returns>Returns False if there was something wrong with the mind or the removal. True if successful</returns>>
|
||||
public bool MindRemoveRole<T>(EntityUid mindId) where T : IComponent
|
||||
/// <returns>Returns false if the role did not exist. True if successful</returns>>
|
||||
public bool MindRemoveRole<T>(Entity<MindComponent?> mind) where T : IComponent
|
||||
{
|
||||
if (!TryComp<MindComponent>(mindId, out var mind) )
|
||||
throw new ArgumentException($"{mindId} does not exist or does not have mind component");
|
||||
if (typeof(T) == typeof(MindRoleComponent))
|
||||
throw new InvalidOperationException();
|
||||
|
||||
if (!Resolve(mind.Owner, ref mind.Comp))
|
||||
return false;
|
||||
|
||||
var found = false;
|
||||
var antagonist = false;
|
||||
var delete = new List<EntityUid>();
|
||||
foreach (var role in mind.MindRoles)
|
||||
foreach (var role in mind.Comp.MindRoles)
|
||||
{
|
||||
if (!HasComp<T>(role))
|
||||
continue;
|
||||
|
||||
var roleComp = Comp<MindRoleComponent>(role);
|
||||
antagonist = roleComp.Antag;
|
||||
_entityManager.DeleteEntity(role);
|
||||
if (!TryComp(role, out MindRoleComponent? roleComp))
|
||||
{
|
||||
Log.Error($"Encountered mind role entity {ToPrettyString(role)} without a {nameof(MindRoleComponent)}");
|
||||
continue;
|
||||
}
|
||||
|
||||
antagonist |= roleComp.Antag | roleComp.ExclusiveAntag;
|
||||
_entityManager.DeleteEntity(role);
|
||||
delete.Add(role);
|
||||
found = true;
|
||||
|
||||
}
|
||||
|
||||
foreach (var role in delete)
|
||||
{
|
||||
mind.MindRoles.Remove(role);
|
||||
}
|
||||
|
||||
if (!found)
|
||||
return false;
|
||||
|
||||
foreach (var role in delete)
|
||||
{
|
||||
throw new ArgumentException($"{mindId} does not have this role: {typeof(T)}");
|
||||
mind.Comp.MindRoles.Remove(role);
|
||||
}
|
||||
|
||||
var message = new RoleRemovedEvent(mindId, mind, antagonist);
|
||||
|
||||
if (mind.OwnedEntity != null)
|
||||
if (mind.Comp.OwnedEntity != null)
|
||||
{
|
||||
RaiseLocalEvent(mind.OwnedEntity.Value, message, true);
|
||||
var message = new RoleRemovedEvent(mind.Owner, mind.Comp, antagonist);
|
||||
RaiseLocalEvent(mind.Comp.OwnedEntity.Value, message, true);
|
||||
}
|
||||
|
||||
_adminLogger.Add(LogType.Mind,
|
||||
LogImpact.Low,
|
||||
$"'Role {typeof(T).Name}' removed from mind of {ToPrettyString(mind.OwnedEntity)}");
|
||||
$"All roles of type '{typeof(T).Name}' removed from mind of {ToPrettyString(mind.Comp.OwnedEntity)}");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -238,16 +243,14 @@ public abstract class SharedRoleSystem : EntitySystem
|
||||
/// <returns>True if the role existed and was removed</returns>
|
||||
public bool MindTryRemoveRole<T>(EntityUid mindId) where T : IComponent
|
||||
{
|
||||
if (!MindHasRole<T>(mindId))
|
||||
{
|
||||
Log.Warning($"Failed to remove role {typeof(T)} from {mindId} : mind does not have role ");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(MindRoleComponent))
|
||||
return false;
|
||||
|
||||
return MindRemoveRole<T>(mindId);
|
||||
if (MindRemoveRole<T>(mindId))
|
||||
return true;
|
||||
|
||||
Log.Warning($"Failed to remove role {typeof(T)} from {ToPrettyString(mindId)} : mind does not have role ");
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -259,30 +262,29 @@ public abstract class SharedRoleSystem : EntitySystem
|
||||
/// <param name="role">The Mind Role entity component</param>
|
||||
/// <param name="roleT">The Mind Role's entity component for T</param>
|
||||
/// <returns>True if the role is found</returns>
|
||||
public bool MindHasRole<T>(EntityUid mindId,
|
||||
[NotNullWhen(true)] out Entity<MindRoleComponent>? role,
|
||||
[NotNullWhen(true)] out Entity<T>? roleT) where T : IComponent
|
||||
public bool MindHasRole<T>(Entity<MindComponent?> mind,
|
||||
[NotNullWhen(true)] out Entity<MindRoleComponent, T>? role) where T : IComponent
|
||||
{
|
||||
role = null;
|
||||
roleT = null;
|
||||
|
||||
if (!TryComp<MindComponent>(mindId, out var mind))
|
||||
if (!Resolve(mind.Owner, ref mind.Comp))
|
||||
return false;
|
||||
|
||||
var found = false;
|
||||
|
||||
foreach (var roleEnt in mind.MindRoles)
|
||||
foreach (var roleEnt in mind.Comp.MindRoles)
|
||||
{
|
||||
if (!HasComp<T>(roleEnt))
|
||||
if (!TryComp(roleEnt, out T? tcomp))
|
||||
continue;
|
||||
|
||||
role = (roleEnt,Comp<MindRoleComponent>(roleEnt));
|
||||
roleT = (roleEnt,Comp<T>(roleEnt));
|
||||
found = true;
|
||||
break;
|
||||
if (!TryComp(roleEnt, out MindRoleComponent? roleComp))
|
||||
{
|
||||
Log.Error($"Encountered mind role entity {ToPrettyString(roleEnt)} without a {nameof(MindRoleComponent)}");
|
||||
continue;
|
||||
}
|
||||
|
||||
role = (roleEnt, roleComp, tcomp);
|
||||
return true;
|
||||
}
|
||||
|
||||
return found;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -317,7 +319,13 @@ public abstract class SharedRoleSystem : EntitySystem
|
||||
if (!HasComp(roleEnt, type))
|
||||
continue;
|
||||
|
||||
role = (roleEnt,Comp<MindRoleComponent>(roleEnt));
|
||||
if (!TryComp(roleEnt, out MindRoleComponent? roleComp))
|
||||
{
|
||||
Log.Error($"Encountered mind role entity {ToPrettyString(roleEnt)} without a {nameof(MindRoleComponent)}");
|
||||
continue;
|
||||
}
|
||||
|
||||
role = (roleEnt, roleComp);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@@ -325,20 +333,6 @@ public abstract class SharedRoleSystem : EntitySystem
|
||||
return found;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the first mind role of a specific type on a mind entity.
|
||||
/// Outputs an entity component for the mind role's MindRoleComponent
|
||||
/// </summary>
|
||||
/// <param name="mindId">The mind entity</param>
|
||||
/// <param name="role">The Mind Role entity component</param>
|
||||
/// <typeparam name="T">The type of the role to find.</typeparam>
|
||||
/// <returns>True if the role is found</returns>
|
||||
public bool MindHasRole<T>(EntityUid mindId,
|
||||
[NotNullWhen(true)] out Entity<MindRoleComponent>? role) where T : IComponent
|
||||
{
|
||||
return MindHasRole<T>(mindId, out role, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the first mind role of a specific type on a mind entity.
|
||||
/// </summary>
|
||||
@@ -347,7 +341,7 @@ public abstract class SharedRoleSystem : EntitySystem
|
||||
/// <returns>True if the role is found</returns>
|
||||
public bool MindHasRole<T>(EntityUid mindId) where T : IComponent
|
||||
{
|
||||
return MindHasRole<T>(mindId, out _, out _);
|
||||
return MindHasRole<T>(mindId, out _);
|
||||
}
|
||||
|
||||
//TODO: Delete this later
|
||||
@@ -374,28 +368,31 @@ public abstract class SharedRoleSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Reads all Roles of a mind Entity and returns their data as RoleInfo
|
||||
/// </summary>
|
||||
/// <param name="mindId">The mind entity</param>
|
||||
/// <param name="mind">The mind entity</param>
|
||||
/// <returns>RoleInfo list</returns>
|
||||
public List<RoleInfo> MindGetAllRoleInfo(EntityUid mindId)
|
||||
public List<RoleInfo> MindGetAllRoleInfo(Entity<MindComponent?> mind)
|
||||
{
|
||||
var roleInfo = new List<RoleInfo>();
|
||||
|
||||
if (!TryComp<MindComponent>(mindId, out var mind))
|
||||
if (!Resolve(mind.Owner, ref mind.Comp))
|
||||
return roleInfo;
|
||||
|
||||
foreach (var role in mind.MindRoles)
|
||||
foreach (var role in mind.Comp.MindRoles)
|
||||
{
|
||||
var valid = false;
|
||||
var name = "game-ticker-unknown-role";
|
||||
var prototype = "";
|
||||
string? playTimeTracker = null;
|
||||
string? playTimeTracker = null;
|
||||
|
||||
var comp = Comp<MindRoleComponent>(role);
|
||||
if (comp.AntagPrototype is not null)
|
||||
if (!TryComp(role, out MindRoleComponent? comp))
|
||||
{
|
||||
prototype = comp.AntagPrototype;
|
||||
Log.Error($"Encountered mind role entity {ToPrettyString(role)} without a {nameof(MindRoleComponent)}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (comp.AntagPrototype is not null)
|
||||
prototype = comp.AntagPrototype;
|
||||
|
||||
if (comp.JobPrototype is not null && comp.AntagPrototype is null)
|
||||
{
|
||||
prototype = comp.JobPrototype;
|
||||
@@ -429,7 +426,7 @@ public abstract class SharedRoleSystem : EntitySystem
|
||||
}
|
||||
|
||||
if (valid)
|
||||
roleInfo.Add(new RoleInfo(name, comp.Antag || comp.ExclusiveAntag , playTimeTracker, prototype));
|
||||
roleInfo.Add(new RoleInfo(name, comp.Antag, playTimeTracker, prototype));
|
||||
}
|
||||
return roleInfo;
|
||||
}
|
||||
@@ -442,12 +439,9 @@ public abstract class SharedRoleSystem : EntitySystem
|
||||
public bool MindIsAntagonist(EntityUid? mindId)
|
||||
{
|
||||
if (mindId is null)
|
||||
{
|
||||
Log.Warning($"Antagonist status of mind entity {mindId} could not be determined - mind entity not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
return CheckAntagonistStatus(mindId.Value).Item1;
|
||||
return CheckAntagonistStatus(mindId.Value).Antag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -458,37 +452,28 @@ public abstract class SharedRoleSystem : EntitySystem
|
||||
public bool MindIsExclusiveAntagonist(EntityUid? mindId)
|
||||
{
|
||||
if (mindId is null)
|
||||
{
|
||||
Log.Warning($"Antagonist status of mind entity {mindId} could not be determined - mind entity not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
return CheckAntagonistStatus(mindId.Value).Item2;
|
||||
return CheckAntagonistStatus(mindId.Value).ExclusiveAntag;
|
||||
}
|
||||
|
||||
private (bool, bool) CheckAntagonistStatus(EntityUid mindId)
|
||||
public (bool Antag, bool ExclusiveAntag) CheckAntagonistStatus(Entity<MindComponent?> mind)
|
||||
{
|
||||
if (!TryComp<MindComponent>(mindId, out var mind))
|
||||
{
|
||||
Log.Warning($"Antagonist status of mind entity {mindId} could not be determined - mind component not found");
|
||||
if (!Resolve(mind.Owner, ref mind.Comp))
|
||||
return (false, false);
|
||||
}
|
||||
|
||||
var antagonist = false;
|
||||
var exclusiveAntag = false;
|
||||
foreach (var role in mind.MindRoles)
|
||||
foreach (var role in mind.Comp.MindRoles)
|
||||
{
|
||||
if (!TryComp<MindRoleComponent>(role, out var roleComp))
|
||||
{
|
||||
//If this ever shows up outside of an integration test, then we need to look into this further.
|
||||
Log.Warning($"Mind Role Entity {role} does not have MindRoleComponent!");
|
||||
Log.Error($"Mind Role Entity {ToPrettyString(role)} does not have a MindRoleComponent, despite being listed as a role belonging to {ToPrettyString(mind)}|");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (roleComp.Antag || exclusiveAntag)
|
||||
antagonist = true;
|
||||
if (roleComp.ExclusiveAntag)
|
||||
exclusiveAntag = true;
|
||||
antagonist |= roleComp.Antag;
|
||||
exclusiveAntag |= roleComp.ExclusiveAntag;
|
||||
}
|
||||
|
||||
return (antagonist, exclusiveAntag);
|
||||
@@ -504,6 +489,9 @@ public abstract class SharedRoleSystem : EntitySystem
|
||||
_audio.PlayGlobal(sound, mind.Session);
|
||||
}
|
||||
|
||||
// TODO ROLES Change to readonly.
|
||||
// Passing around a reference to a prototype's hashset makes me uncomfortable because it might be accidentally
|
||||
// mutated.
|
||||
public HashSet<JobRequirement>? GetJobRequirement(JobPrototype job)
|
||||
{
|
||||
if (_requirementOverride != null && _requirementOverride.Jobs.TryGetValue(job.ID, out var req))
|
||||
@@ -512,6 +500,7 @@ public abstract class SharedRoleSystem : EntitySystem
|
||||
return job.Requirements;
|
||||
}
|
||||
|
||||
// TODO ROLES Change to readonly.
|
||||
public HashSet<JobRequirement>? GetJobRequirement(ProtoId<JobPrototype> job)
|
||||
{
|
||||
if (_requirementOverride != null && _requirementOverride.Jobs.TryGetValue(job, out var req))
|
||||
@@ -520,6 +509,7 @@ public abstract class SharedRoleSystem : EntitySystem
|
||||
return _prototypes.Index(job).Requirements;
|
||||
}
|
||||
|
||||
// TODO ROLES Change to readonly.
|
||||
public HashSet<JobRequirement>? GetAntagRequirement(ProtoId<AntagPrototype> antag)
|
||||
{
|
||||
if (_requirementOverride != null && _requirementOverride.Antags.TryGetValue(antag, out var req))
|
||||
@@ -528,6 +518,7 @@ public abstract class SharedRoleSystem : EntitySystem
|
||||
return _prototypes.Index(antag).Requirements;
|
||||
}
|
||||
|
||||
// TODO ROLES Change to readonly.
|
||||
public HashSet<JobRequirement>? GetAntagRequirement(AntagPrototype antag)
|
||||
{
|
||||
if (_requirementOverride != null && _requirementOverride.Antags.TryGetValue(antag.ID, out var req))
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
//using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Silicons.Borgs.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used to override the action icon for cyborg actions.
|
||||
/// Without this component the no-action state will be used.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class BorgModuleIconComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The action icon for this module
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SpriteSpecifier.Rsi Icon = default!;
|
||||
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Shared.Silicons.Laws.Components;
|
||||
|
||||
@@ -20,4 +21,12 @@ public sealed partial class SiliconLawProviderComponent : Component
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public SiliconLawset? Lawset;
|
||||
|
||||
/// <summary>
|
||||
/// The sound that plays for the Silicon player
|
||||
/// when the particular lawboard has been inserted.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier? LawUploadSound = new SoundPathSpecifier("/Audio/Misc/cryo_warning.ogg");
|
||||
|
||||
}
|
||||
|
||||
@@ -285,6 +285,8 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
|
||||
private bool SetupEye(Entity<StationAiCoreComponent> ent)
|
||||
{
|
||||
if (_net.IsClient)
|
||||
return false;
|
||||
if (ent.Comp.RemoteEntity != null)
|
||||
return false;
|
||||
|
||||
@@ -299,8 +301,11 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
|
||||
private void ClearEye(Entity<StationAiCoreComponent> ent)
|
||||
{
|
||||
if (_net.IsClient)
|
||||
return;
|
||||
QueueDel(ent.Comp.RemoteEntity);
|
||||
ent.Comp.RemoteEntity = null;
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
private void AttachEye(Entity<StationAiCoreComponent> ent)
|
||||
@@ -330,6 +335,8 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
if (_timing.ApplyingState)
|
||||
return;
|
||||
|
||||
SetupEye(ent);
|
||||
|
||||
// Just so text and the likes works properly
|
||||
_metadata.SetEntityName(ent.Owner, MetaData(args.Entity).EntityName);
|
||||
|
||||
@@ -351,6 +358,7 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
{
|
||||
_eye.SetTarget(args.Entity, null, eyeComp);
|
||||
}
|
||||
ClearEye(ent);
|
||||
}
|
||||
|
||||
private void UpdateAppearance(Entity<StationAiHolderComponent?> entity)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Sound.Components;
|
||||
@@ -8,4 +9,9 @@ namespace Content.Shared.Sound.Components;
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class EmitSoundOnUIOpenComponent : BaseEmitSoundComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Blacklist for making the sound not play if certain entities open the UI
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityWhitelist Blacklist = new();
|
||||
}
|
||||
|
||||
@@ -58,7 +58,10 @@ public abstract class SharedEmitSoundSystem : EntitySystem
|
||||
|
||||
private void HandleEmitSoundOnUIOpen(EntityUid uid, EmitSoundOnUIOpenComponent component, AfterActivatableUIOpenEvent args)
|
||||
{
|
||||
TryEmitSound(uid, component, args.User);
|
||||
if (_whitelistSystem.IsBlacklistFail(component.Blacklist, args.User))
|
||||
{
|
||||
TryEmitSound(uid, component, args.User);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMobState(Entity<SoundWhileAliveComponent> entity, ref MobStateChangedEvent args)
|
||||
|
||||
@@ -150,6 +150,7 @@ public abstract class SharedStationSpawningSystem : EntitySystem
|
||||
|
||||
foreach (var (slot, entProtos) in startingGear.Storage)
|
||||
{
|
||||
ents.Clear();
|
||||
if (entProtos.Count == 0)
|
||||
continue;
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ using Content.Shared.Verbs;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Tools.EntitySystems;
|
||||
using Content.Shared.Whitelist;
|
||||
using Content.Shared.Materials;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Shared.Storage.EntitySystems;
|
||||
|
||||
@@ -35,6 +37,7 @@ public sealed class SecretStashSystem : EntitySystem
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SecretStashComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<SecretStashComponent, DestructionEventArgs>(OnDestroyed);
|
||||
SubscribeLocalEvent<SecretStashComponent, GotReclaimedEvent>(OnReclaimed);
|
||||
SubscribeLocalEvent<SecretStashComponent, InteractUsingEvent>(OnInteractUsing, after: new[] { typeof(ToolOpenableSystem) });
|
||||
SubscribeLocalEvent<SecretStashComponent, InteractHandEvent>(OnInteractHand);
|
||||
SubscribeLocalEvent<SecretStashComponent, GetVerbsEvent<InteractionVerb>>(OnGetVerb);
|
||||
@@ -47,12 +50,12 @@ public sealed class SecretStashSystem : EntitySystem
|
||||
|
||||
private void OnDestroyed(Entity<SecretStashComponent> entity, ref DestructionEventArgs args)
|
||||
{
|
||||
var storedInside = _containerSystem.EmptyContainer(entity.Comp.ItemContainer);
|
||||
if (storedInside != null && storedInside.Count >= 1)
|
||||
{
|
||||
var popup = Loc.GetString("comp-secret-stash-on-destroyed-popup", ("stashname", GetStashName(entity)));
|
||||
_popupSystem.PopupEntity(popup, storedInside[0], PopupType.MediumCaution);
|
||||
}
|
||||
DropContentsAndAlert(entity);
|
||||
}
|
||||
|
||||
private void OnReclaimed(Entity<SecretStashComponent> entity, ref GotReclaimedEvent args)
|
||||
{
|
||||
DropContentsAndAlert(entity, args.ReclaimerCoordinates);
|
||||
}
|
||||
|
||||
private void OnInteractUsing(Entity<SecretStashComponent> entity, ref InteractUsingEvent args)
|
||||
@@ -211,5 +214,18 @@ public sealed class SecretStashSystem : EntitySystem
|
||||
return entity.Comp.ItemContainer.ContainedEntity != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drop the item stored in the stash and alert all nearby players with a popup.
|
||||
/// </summary>
|
||||
private void DropContentsAndAlert(Entity<SecretStashComponent> entity, EntityCoordinates? cords = null)
|
||||
{
|
||||
var storedInside = _containerSystem.EmptyContainer(entity.Comp.ItemContainer, true, cords);
|
||||
if (storedInside != null && storedInside.Count >= 1)
|
||||
{
|
||||
var popup = Loc.GetString("comp-secret-stash-on-destroyed-popup", ("stashname", GetStashName(entity)));
|
||||
_popupSystem.PopupPredicted(popup, storedInside[0], null, PopupType.MediumCaution);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -673,7 +673,7 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
|
||||
private void OnSaveItemLocation(StorageSaveItemLocationEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
if (!ValidateInput(args, msg.Storage, msg.Item, out var player, out var storage, out var item, held: true))
|
||||
if (!ValidateInput(args, msg.Storage, msg.Item, out var player, out var storage, out var item))
|
||||
return;
|
||||
|
||||
SaveItemLocation(storage!, item.Owner);
|
||||
|
||||
@@ -103,7 +103,7 @@ public abstract class SharedStrippableSystem : EntitySystem
|
||||
|
||||
if (userHands.ActiveHandEntity != null && !hasEnt)
|
||||
StartStripInsertInventory((user, userHands), strippable.Owner, userHands.ActiveHandEntity.Value, args.Slot);
|
||||
else if (userHands.ActiveHandEntity == null && hasEnt)
|
||||
else if (hasEnt)
|
||||
StartStripRemoveInventory(user, strippable.Owner, held!.Value, args.Slot);
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ public abstract class SharedStrippableSystem : EntitySystem
|
||||
|
||||
if (user.Comp.ActiveHandEntity != null && handSlot.HeldEntity == null)
|
||||
StartStripInsertHand(user, target, user.Comp.ActiveHandEntity.Value, handId, targetStrippable);
|
||||
else if (user.Comp.ActiveHandEntity == null && handSlot.HeldEntity != null)
|
||||
else if (handSlot.HeldEntity != null)
|
||||
StartStripRemoveHand(user, target, handSlot.HeldEntity.Value, handId, targetStrippable);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ public sealed class IntrinsicUISystem : EntitySystem
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<IntrinsicUIComponent, MapInitEvent>(InitActions);
|
||||
SubscribeLocalEvent<IntrinsicUIComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<IntrinsicUIComponent, ToggleIntrinsicUIEvent>(OnActionToggle);
|
||||
}
|
||||
|
||||
@@ -21,6 +22,15 @@ public sealed class IntrinsicUISystem : EntitySystem
|
||||
args.Handled = InteractUI(uid, args.Key, component);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, IntrinsicUIComponent component, ref ComponentShutdown args)
|
||||
{
|
||||
foreach (var actionEntry in component.UIs.Values)
|
||||
{
|
||||
var actionId = actionEntry.ToggleActionEntity;
|
||||
_actionsSystem.RemoveAction(uid, actionId);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitActions(EntityUid uid, IntrinsicUIComponent component, MapInitEvent args)
|
||||
{
|
||||
foreach (var entry in component.UIs.Values)
|
||||
|
||||
@@ -87,12 +87,13 @@ namespace Content.Shared.VendingMachines
|
||||
/// Sound that plays when ejecting an item
|
||||
/// </summary>
|
||||
[DataField("soundVend")]
|
||||
// Grabbed from: https://github.com/discordia-space/CEV-Eris/blob/f702afa271136d093ddeb415423240a2ceb212f0/sound/machines/vending_drop.ogg
|
||||
// Grabbed from: https://github.com/tgstation/tgstation/blob/d34047a5ae911735e35cd44a210953c9563caa22/sound/machines/machine_vend.ogg
|
||||
public SoundSpecifier SoundVend = new SoundPathSpecifier("/Audio/Machines/machine_vend.ogg")
|
||||
{
|
||||
Params = new AudioParams
|
||||
{
|
||||
Volume = -2f
|
||||
Volume = -4f,
|
||||
Variation = 0.15f
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -440,7 +440,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
DoLungeAnimation(user, weaponUid, weapon.Angle, GetCoordinates(attack.Coordinates).ToMap(EntityManager, TransformSystem), weapon.Range, animation);
|
||||
DoLungeAnimation(user, weaponUid, weapon.Angle, TransformSystem.ToMapCoordinates(GetCoordinates(attack.Coordinates)), weapon.Range, animation);
|
||||
}
|
||||
|
||||
var attackEv = new MeleeAttackEvent(weaponUid);
|
||||
@@ -472,12 +472,14 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
// TODO: This needs fixing
|
||||
if (meleeUid == user)
|
||||
{
|
||||
AdminLogger.Add(LogType.MeleeHit, LogImpact.Low,
|
||||
AdminLogger.Add(LogType.MeleeHit,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(user):actor} melee attacked (light) using their hands and missed");
|
||||
}
|
||||
else
|
||||
{
|
||||
AdminLogger.Add(LogType.MeleeHit, LogImpact.Low,
|
||||
AdminLogger.Add(LogType.MeleeHit,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(user):actor} melee attacked (light) using {ToPrettyString(meleeUid):tool} and missed");
|
||||
}
|
||||
var missEvent = new MeleeHitEvent(new List<EntityUid>(), user, meleeUid, damage, null);
|
||||
@@ -526,12 +528,14 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
|
||||
if (meleeUid == user)
|
||||
{
|
||||
AdminLogger.Add(LogType.MeleeHit, LogImpact.Medium,
|
||||
AdminLogger.Add(LogType.MeleeHit,
|
||||
LogImpact.Medium,
|
||||
$"{ToPrettyString(user):actor} melee attacked (light) {ToPrettyString(target.Value):subject} using their hands and dealt {damageResult.GetTotal():damage} damage");
|
||||
}
|
||||
else
|
||||
{
|
||||
AdminLogger.Add(LogType.MeleeHit, LogImpact.Medium,
|
||||
AdminLogger.Add(LogType.MeleeHit,
|
||||
LogImpact.Medium,
|
||||
$"{ToPrettyString(user):actor} melee attacked (light) {ToPrettyString(target.Value):subject} using {ToPrettyString(meleeUid):tool} and dealt {damageResult.GetTotal():damage} damage");
|
||||
}
|
||||
|
||||
@@ -553,7 +557,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
if (!TryComp(user, out TransformComponent? userXform))
|
||||
return false;
|
||||
|
||||
var targetMap = GetCoordinates(ev.Coordinates).ToMap(EntityManager, TransformSystem);
|
||||
var targetMap = TransformSystem.ToMapCoordinates(GetCoordinates(ev.Coordinates));
|
||||
|
||||
if (targetMap.MapId != userXform.MapID)
|
||||
return false;
|
||||
@@ -569,12 +573,14 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
{
|
||||
if (meleeUid == user)
|
||||
{
|
||||
AdminLogger.Add(LogType.MeleeHit, LogImpact.Low,
|
||||
AdminLogger.Add(LogType.MeleeHit,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(user):actor} melee attacked (heavy) using their hands and missed");
|
||||
}
|
||||
else
|
||||
{
|
||||
AdminLogger.Add(LogType.MeleeHit, LogImpact.Low,
|
||||
AdminLogger.Add(LogType.MeleeHit,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(user):actor} melee attacked (heavy) using {ToPrettyString(meleeUid):tool} and missed");
|
||||
}
|
||||
var missEvent = new MeleeHitEvent(new List<EntityUid>(), user, meleeUid, damage, direction);
|
||||
@@ -595,8 +601,14 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
// Validate client
|
||||
for (var i = entities.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (ArcRaySuccessful(entities[i], userPos, direction.ToWorldAngle(), component.Angle, distance,
|
||||
userXform.MapID, user, session))
|
||||
if (ArcRaySuccessful(entities[i],
|
||||
userPos,
|
||||
direction.ToWorldAngle(),
|
||||
component.Angle,
|
||||
distance,
|
||||
userXform.MapID,
|
||||
user,
|
||||
session))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -663,16 +675,24 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
|
||||
if (damageResult != null && damageResult.GetTotal() > FixedPoint2.Zero)
|
||||
{
|
||||
// If the target has stamina and is taking blunt damage, they should also take stamina damage based on their blunt to stamina factor
|
||||
if (damageResult.DamageDict.TryGetValue("Blunt", out var bluntDamage))
|
||||
{
|
||||
_stamina.TakeStaminaDamage(entity, (bluntDamage * component.BluntStaminaDamageFactor).Float(), visual: false, source: user, with: meleeUid == user ? null : meleeUid);
|
||||
}
|
||||
|
||||
appliedDamage += damageResult;
|
||||
|
||||
if (meleeUid == user)
|
||||
{
|
||||
AdminLogger.Add(LogType.MeleeHit, LogImpact.Medium,
|
||||
AdminLogger.Add(LogType.MeleeHit,
|
||||
LogImpact.Medium,
|
||||
$"{ToPrettyString(user):actor} melee attacked (heavy) {ToPrettyString(entity):subject} using their hands and dealt {damageResult.GetTotal():damage} damage");
|
||||
}
|
||||
else
|
||||
{
|
||||
AdminLogger.Add(LogType.MeleeHit, LogImpact.Medium,
|
||||
AdminLogger.Add(LogType.MeleeHit,
|
||||
LogImpact.Medium,
|
||||
$"{ToPrettyString(user):actor} melee attacked (heavy) {ToPrettyString(entity):subject} using {ToPrettyString(meleeUid):tool} and dealt {damageResult.GetTotal():damage} damage");
|
||||
}
|
||||
}
|
||||
@@ -706,8 +726,13 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
{
|
||||
var castAngle = new Angle(baseAngle + increment * i);
|
||||
var res = _physics.IntersectRay(mapId,
|
||||
new CollisionRay(position, castAngle.ToWorldVec(),
|
||||
AttackMask), range, ignore, false).ToList();
|
||||
new CollisionRay(position,
|
||||
castAngle.ToWorldVec(),
|
||||
AttackMask),
|
||||
range,
|
||||
ignore,
|
||||
false)
|
||||
.ToList();
|
||||
|
||||
if (res.Count != 0)
|
||||
{
|
||||
@@ -718,8 +743,14 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
return resSet;
|
||||
}
|
||||
|
||||
protected virtual bool ArcRaySuccessful(EntityUid targetUid, Vector2 position, Angle angle, Angle arcWidth, float range,
|
||||
MapId mapId, EntityUid ignore, ICommonSession? session)
|
||||
protected virtual bool ArcRaySuccessful(EntityUid targetUid,
|
||||
Vector2 position,
|
||||
Angle angle,
|
||||
Angle arcWidth,
|
||||
float range,
|
||||
MapId mapId,
|
||||
EntityUid ignore,
|
||||
ICommonSession? session)
|
||||
{
|
||||
// Only matters for server.
|
||||
return true;
|
||||
|
||||
@@ -114,19 +114,14 @@ public abstract class SharedGrapplingGunSystem : EntitySystem
|
||||
|
||||
private void OnGunActivate(EntityUid uid, GrapplingGunComponent component, ActivateInWorldEvent args)
|
||||
{
|
||||
if (!Timing.IsFirstTimePredicted || args.Handled || !args.Complex)
|
||||
return;
|
||||
|
||||
if (Deleted(component.Projectile))
|
||||
if (!Timing.IsFirstTimePredicted || args.Handled || !args.Complex || component.Projectile is not {} projectile)
|
||||
return;
|
||||
|
||||
_audio.PlayPredicted(component.CycleSound, uid, args.User);
|
||||
_appearance.SetData(uid, SharedTetherGunSystem.TetherVisualsStatus.Key, true);
|
||||
|
||||
if (_netManager.IsServer)
|
||||
{
|
||||
QueueDel(component.Projectile.Value);
|
||||
}
|
||||
QueueDel(projectile);
|
||||
|
||||
component.Projectile = null;
|
||||
SetReeling(uid, component, false, args.User);
|
||||
|
||||
Reference in New Issue
Block a user