Merge remote-tracking branch 'space-station-14/master' into 29-04-2024-upstream
# Conflicts: # Resources/Prototypes/Entities/Mobs/Customization/Markings/human_hair.yml
This commit is contained in:
@@ -18,7 +18,6 @@ using Content.Server.Storage.Components;
|
||||
using Content.Server.Storage.EntitySystems;
|
||||
using Content.Server.Tabletop;
|
||||
using Content.Server.Tabletop.Components;
|
||||
using Content.Server.Terminator.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Administration.Components;
|
||||
using Content.Shared.Body.Components;
|
||||
@@ -31,7 +30,6 @@ using Content.Shared.Database;
|
||||
using Content.Shared.Electrocution;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
@@ -74,7 +72,6 @@ public sealed partial class AdminVerbSystem
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly TabletopSystem _tabletopSystem = default!;
|
||||
[Dependency] private readonly TerminatorSystem _terminator = default!;
|
||||
[Dependency] private readonly VomitSystem _vomitSystem = default!;
|
||||
[Dependency] private readonly WeldableSystem _weldableSystem = default!;
|
||||
[Dependency] private readonly SharedContentEyeSystem _eyeSystem = default!;
|
||||
@@ -824,27 +821,5 @@ public sealed partial class AdminVerbSystem
|
||||
Impact = LogImpact.Extreme,
|
||||
};
|
||||
args.Verbs.Add(superBonk);
|
||||
|
||||
Verb terminate = new()
|
||||
{
|
||||
Text = "Terminate",
|
||||
Category = VerbCategory.Smite,
|
||||
Icon = new SpriteSpecifier.Rsi(new ("Mobs/Species/Terminator/parts.rsi"), "skull_icon"),
|
||||
Act = () =>
|
||||
{
|
||||
if (!TryComp<MindContainerComponent>(args.Target, out var mindContainer) || mindContainer.Mind == null)
|
||||
return;
|
||||
|
||||
var coords = Transform(args.Target).Coordinates;
|
||||
var mindId = mindContainer.Mind.Value;
|
||||
_terminator.CreateSpawner(coords, mindId);
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("admin-smite-terminate-prompt"), args.Target,
|
||||
args.Target, PopupType.LargeCaution);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-smite-terminate-description")
|
||||
};
|
||||
args.Verbs.Add(terminate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,16 +25,19 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
private void OnAirtightInit(Entity<AirtightComponent> airtight, ref ComponentInit args)
|
||||
{
|
||||
var xform = EntityManager.GetComponent<TransformComponent>(airtight);
|
||||
|
||||
if (airtight.Comp.FixAirBlockedDirectionInitialize)
|
||||
// TODO AIRTIGHT what FixAirBlockedDirectionInitialize even for?
|
||||
if (!airtight.Comp.FixAirBlockedDirectionInitialize)
|
||||
{
|
||||
var moveEvent = new MoveEvent(airtight, default, default, Angle.Zero, xform.LocalRotation, xform, false);
|
||||
if (AirtightMove(airtight, ref moveEvent))
|
||||
return;
|
||||
UpdatePosition(airtight);
|
||||
return;
|
||||
}
|
||||
|
||||
UpdatePosition(airtight);
|
||||
var xform = Transform(airtight);
|
||||
airtight.Comp.CurrentAirBlockedDirection =
|
||||
(int) Rotate((AtmosDirection) airtight.Comp.InitialAirBlockedDirection, xform.LocalRotation);
|
||||
UpdatePosition(airtight, xform);
|
||||
var airtightEv = new AirtightChanged(airtight, airtight, default);
|
||||
RaiseLocalEvent(airtight, ref airtightEv, true);
|
||||
}
|
||||
|
||||
private void OnAirtightShutdown(Entity<AirtightComponent> airtight, ref ComponentShutdown args)
|
||||
@@ -42,13 +45,8 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
var xform = Transform(airtight);
|
||||
|
||||
// If the grid is deleting no point updating atmos.
|
||||
if (HasComp<MapGridComponent>(xform.GridUid) &&
|
||||
MetaData(xform.GridUid.Value).EntityLifeStage > EntityLifeStage.MapInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetAirblocked(airtight, false, xform);
|
||||
if (xform.GridUid != null && LifeStage(xform.GridUid.Value) <= EntityLifeStage.MapInitialized)
|
||||
SetAirblocked(airtight, false, xform);
|
||||
}
|
||||
|
||||
private void OnAirtightPositionChanged(EntityUid uid, AirtightComponent airtight, ref AnchorStateChangedEvent args)
|
||||
@@ -83,21 +81,14 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAirtightMoved(Entity<AirtightComponent> airtight, ref MoveEvent ev)
|
||||
{
|
||||
AirtightMove(airtight, ref ev);
|
||||
}
|
||||
|
||||
private bool AirtightMove(Entity<AirtightComponent> ent, ref MoveEvent ev)
|
||||
private void OnAirtightMoved(Entity<AirtightComponent> ent, ref MoveEvent ev)
|
||||
{
|
||||
var (owner, airtight) = ent;
|
||||
|
||||
airtight.CurrentAirBlockedDirection = (int) Rotate((AtmosDirection)airtight.InitialAirBlockedDirection, ev.NewRotation);
|
||||
var pos = airtight.LastPosition;
|
||||
UpdatePosition(ent, ev.Component);
|
||||
var airtightEv = new AirtightChanged(owner, airtight, pos);
|
||||
RaiseLocalEvent(owner, ref airtightEv, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetAirblocked(Entity<AirtightComponent> airtight, bool airblocked, TransformComponent? xform = null)
|
||||
|
||||
@@ -149,6 +149,29 @@ public sealed class RottingSystem : SharedRottingSystem
|
||||
args.Handled = component.CurrentTemperature < Atmospherics.T0C + 0.85f;
|
||||
}
|
||||
|
||||
|
||||
public void ReduceAccumulator(EntityUid uid, TimeSpan time)
|
||||
{
|
||||
if (!TryComp<PerishableComponent>(uid, out var perishable))
|
||||
return;
|
||||
|
||||
if (!TryComp<RottingComponent>(uid, out var rotting))
|
||||
{
|
||||
perishable.RotAccumulator -= time;
|
||||
return;
|
||||
}
|
||||
var total = (rotting.TotalRotTime + perishable.RotAccumulator) - time;
|
||||
|
||||
if (total < perishable.RotAfter)
|
||||
{
|
||||
RemCompDeferred(uid, rotting);
|
||||
perishable.RotAccumulator = total;
|
||||
}
|
||||
|
||||
else
|
||||
rotting.TotalRotTime = total - perishable.RotAfter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is anything speeding up the decay?
|
||||
/// e.g. buried in a grave
|
||||
|
||||
@@ -5,7 +5,7 @@ using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Chat.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
[AdminCommand(AdminFlags.Adminchat)]
|
||||
internal sealed class AdminChatCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "asay";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Frozen;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Speech;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
@@ -80,6 +81,16 @@ public partial class ChatSystem
|
||||
bool ignoreActionBlocker = false
|
||||
)
|
||||
{
|
||||
if (!(emote.Whitelist?.IsValid(source, EntityManager) ?? true))
|
||||
return;
|
||||
if (emote.Blacklist?.IsValid(source, EntityManager) ?? false)
|
||||
return;
|
||||
|
||||
if (!emote.Available &&
|
||||
TryComp<SpeechComponent>(source, out var speech) &&
|
||||
!speech.AllowedEmotes.Contains(emote.ID))
|
||||
return;
|
||||
|
||||
// check if proto has valid message for chat
|
||||
if (emote.ChatMessages.Count != 0)
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ public sealed class TransformableContainerSystem : EntitySystem
|
||||
SubscribeLocalEvent<TransformableContainerComponent, SolutionContainerChangedEvent>(OnSolutionChange);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<TransformableContainerComponent> entity, ref MapInitEvent args)
|
||||
private void OnMapInit(Entity<TransformableContainerComponent> entity, ref MapInitEvent args)
|
||||
{
|
||||
var meta = MetaData(entity.Owner);
|
||||
if (string.IsNullOrEmpty(entity.Comp.InitialName))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Shared.Body.Prototypes;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -35,7 +35,7 @@ namespace Content.Server.Chemistry.ReagentEffectConditions
|
||||
public override string GuidebookExplanation(IPrototypeManager prototype)
|
||||
{
|
||||
return Loc.GetString("reagent-effect-condition-guidebook-organ-type",
|
||||
("name", prototype.Index<MetabolizerTypePrototype>(Type).Name),
|
||||
("name", prototype.Index<MetabolizerTypePrototype>(Type).LocalizedName),
|
||||
("shouldhave", ShouldHave));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Content.Server.Chemistry.ReagentEffectConditions
|
||||
prototype.TryIndex(Reagent, out reagentProto);
|
||||
|
||||
return Loc.GetString("reagent-effect-condition-guidebook-reagent-threshold",
|
||||
("reagent", reagentProto?.LocalizedName ?? "this reagent"),
|
||||
("reagent", reagentProto?.LocalizedName ?? Loc.GetString("reagent-effect-condition-guidebook-this-reagent")),
|
||||
("max", Max == FixedPoint2.MaxValue ? (float) int.MaxValue : Max.Float()),
|
||||
("min", Min.Float()));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Body.Prototypes;
|
||||
using Content.Shared.Body.Prototypes;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.FixedPoint;
|
||||
using JetBrains.Annotations;
|
||||
@@ -74,7 +74,7 @@ namespace Content.Server.Chemistry.ReagentEffects
|
||||
return Loc.GetString("reagent-effect-guidebook-adjust-reagent-group",
|
||||
("chance", Probability),
|
||||
("deltasign", MathF.Sign(Amount.Float())),
|
||||
("group", groupProto.ID),
|
||||
("group", groupProto.LocalizedName),
|
||||
("amount", MathF.Abs(Amount.Float())));
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Content.Server.Chemistry.ReagentEffects
|
||||
|
||||
damages.Add(
|
||||
Loc.GetString("health-change-display",
|
||||
("kind", group.ID),
|
||||
("kind", group.LocalizedName),
|
||||
("amount", MathF.Abs(amount.Float())),
|
||||
("deltasign", sign)
|
||||
));
|
||||
@@ -96,7 +96,7 @@ namespace Content.Server.Chemistry.ReagentEffects
|
||||
|
||||
damages.Add(
|
||||
Loc.GetString("health-change-display",
|
||||
("kind", kind),
|
||||
("kind", prototype.Index<DamageTypePrototype>(kind).LocalizedName),
|
||||
("amount", MathF.Abs(amount.Float())),
|
||||
("deltasign", sign)
|
||||
));
|
||||
|
||||
31
Content.Server/Chemistry/ReagentEffects/ReduceRotting.cs
Normal file
31
Content.Server/Chemistry/ReagentEffects/ReduceRotting.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Server.Atmos.Rotting;
|
||||
|
||||
namespace Content.Server.Chemistry.ReagentEffects
|
||||
{
|
||||
/// <summary>
|
||||
/// Reduces the rotting accumulator on the patient, making them revivable.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed partial class ReduceRotting : ReagentEffect
|
||||
{
|
||||
[DataField("seconds")]
|
||||
public double RottingAmount = 10;
|
||||
|
||||
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
=> Loc.GetString("reagent-effect-guidebook-reduce-rotting",
|
||||
("chance", Probability),
|
||||
("time", RottingAmount));
|
||||
public override void Effect(ReagentEffectArgs args)
|
||||
{
|
||||
if (args.Scale != 1f)
|
||||
return;
|
||||
|
||||
var rottingSys = args.EntityManager.EntitySysManager.GetEntitySystem<RottingSystem>();
|
||||
|
||||
rottingSys.ReduceAccumulator(args.SolutionEntity, TimeSpan.FromSeconds(RottingAmount));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace Content.Server.Construction.Conditions
|
||||
public bool Condition(EntityUid uid, IEntityManager entityManager)
|
||||
{
|
||||
if (!entityManager.TryGetComponent(uid, out LockComponent? lockcomp))
|
||||
return false;
|
||||
return true;
|
||||
|
||||
return lockcomp.Locked == IsLocked;
|
||||
}
|
||||
@@ -25,7 +25,8 @@ namespace Content.Server.Construction.Conditions
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
var entity = args.Examined;
|
||||
|
||||
if (!entMan.TryGetComponent(entity, out LockComponent? lockcomp)) return false;
|
||||
if (!entMan.TryGetComponent(entity, out LockComponent? lockcomp))
|
||||
return true;
|
||||
|
||||
switch (IsLocked)
|
||||
{
|
||||
|
||||
35
Content.Server/Containers/ThrowInsertContainerComponent.cs
Normal file
35
Content.Server/Containers/ThrowInsertContainerComponent.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Containers;
|
||||
|
||||
/// <summary>
|
||||
/// Allows objects to fall inside the Container when thrown
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[Access(typeof(ThrowInsertContainerSystem))]
|
||||
public sealed partial class ThrowInsertContainerComponent : Component
|
||||
{
|
||||
[DataField(required: true)]
|
||||
public string ContainerId = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Throw chance of hitting into the container
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Probability = 0.75f;
|
||||
|
||||
/// <summary>
|
||||
/// Sound played when an object is throw into the container.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier? InsertSound = new SoundPathSpecifier("/Audio/Effects/trashbag1.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Sound played when an item is thrown and misses the container.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier? MissSound = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg");
|
||||
|
||||
[DataField]
|
||||
public LocId MissLocString = "container-thrown-missed";
|
||||
}
|
||||
50
Content.Server/Containers/ThrowInsertContainerSystem.cs
Normal file
50
Content.Server/Containers/ThrowInsertContainerSystem.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Throwing;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Containers;
|
||||
|
||||
public sealed class ThrowInsertContainerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ThrowInsertContainerComponent, ThrowHitByEvent>(OnThrowCollide);
|
||||
}
|
||||
|
||||
private void OnThrowCollide(Entity<ThrowInsertContainerComponent> ent, ref ThrowHitByEvent args)
|
||||
{
|
||||
var container = _containerSystem.GetContainer(ent, ent.Comp.ContainerId);
|
||||
|
||||
if (!_containerSystem.CanInsert(args.Thrown, container))
|
||||
return;
|
||||
|
||||
|
||||
var rand = _random.NextFloat();
|
||||
if (_random.Prob(ent.Comp.Probability))
|
||||
{
|
||||
_audio.PlayPvs(ent.Comp.MissSound, ent);
|
||||
_popup.PopupEntity(Loc.GetString(ent.Comp.MissLocString), ent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_containerSystem.Insert(args.Thrown, container))
|
||||
throw new InvalidOperationException("Container insertion failed but CanInsert returned true");
|
||||
|
||||
_audio.PlayPvs(ent.Comp.InsertSound, ent);
|
||||
|
||||
if (args.Component.Thrower != null)
|
||||
_adminLogger.Add(LogType.Landed, LogImpact.Low, $"{ToPrettyString(args.Thrown)} thrown by {ToPrettyString(args.Component.Thrower.Value):player} landed in {ToPrettyString(ent)}");
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Shared.Popups;
|
||||
|
||||
namespace Content.Server.Destructible.Thresholds.Behaviors;
|
||||
namespace Content.Server.Destructible.Thresholds.Behaviors;
|
||||
|
||||
/// <summary>
|
||||
/// Shows a popup for everyone.
|
||||
|
||||
@@ -73,8 +73,6 @@ public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
|
||||
SubscribeLocalEvent<DisposalUnitComponent, PowerChangedEvent>(OnPowerChange);
|
||||
SubscribeLocalEvent<DisposalUnitComponent, ComponentInit>(OnDisposalInit);
|
||||
|
||||
SubscribeLocalEvent<DisposalUnitComponent, ThrowHitByEvent>(OnThrowCollide);
|
||||
|
||||
SubscribeLocalEvent<DisposalUnitComponent, ActivateInWorldEvent>(OnActivate);
|
||||
SubscribeLocalEvent<DisposalUnitComponent, AfterInteractUsingEvent>(OnAfterInteractUsing);
|
||||
SubscribeLocalEvent<DisposalUnitComponent, DragDropTargetEvent>(OnDragDropOn);
|
||||
@@ -294,40 +292,6 @@ public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Thrown items have a chance of bouncing off the unit and not going in.
|
||||
/// </summary>
|
||||
private void OnThrowCollide(EntityUid uid, SharedDisposalUnitComponent component, ThrowHitByEvent args)
|
||||
{
|
||||
var canInsert = CanInsert(uid, component, args.Thrown);
|
||||
var randDouble = _robustRandom.NextDouble();
|
||||
|
||||
if (!canInsert)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (randDouble > 0.75)
|
||||
{
|
||||
_audioSystem.PlayPvs(component.MissSound, uid);
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("disposal-unit-thrown-missed"), uid);
|
||||
return;
|
||||
}
|
||||
|
||||
var inserted = _containerSystem.Insert(args.Thrown, component.Container);
|
||||
|
||||
if (!inserted)
|
||||
{
|
||||
throw new InvalidOperationException("Container insertion failed but CanInsert returned true");
|
||||
}
|
||||
|
||||
if (args.Component.Thrower != null)
|
||||
_adminLogger.Add(LogType.Landed, LogImpact.Low, $"{ToPrettyString(args.Thrown)} thrown by {ToPrettyString(args.Component.Thrower.Value):player} landed in {ToPrettyString(uid)}");
|
||||
|
||||
AfterInsert(uid, component, args.Thrown);
|
||||
}
|
||||
|
||||
private void OnDisposalInit(EntityUid uid, SharedDisposalUnitComponent component, ComponentInit args)
|
||||
{
|
||||
component.Container = _containerSystem.EnsureContainer<Container>(uid, SharedDisposalUnitComponent.ContainerId);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.EUI;
|
||||
using Content.Shared.Eui;
|
||||
using Content.Shared.Fax.Components;
|
||||
using Content.Shared.Fax;
|
||||
using Content.Shared.Follower;
|
||||
using Content.Shared.Ghost;
|
||||
@@ -54,7 +55,7 @@ public sealed class AdminFaxEui : BaseEui
|
||||
}
|
||||
case AdminFaxEuiMsg.Send sendData:
|
||||
{
|
||||
var printout = new FaxPrintout(sendData.Content, sendData.Title, null, sendData.StampState,
|
||||
var printout = new FaxPrintout(sendData.Content, sendData.Title, null, null, sendData.StampState,
|
||||
new() { new StampDisplayInfo { StampedName = sendData.From, StampedColor = sendData.StampColor } });
|
||||
_faxSystem.Receive(_entityManager.GetEntity(sendData.Target), printout);
|
||||
break;
|
||||
|
||||
@@ -23,6 +23,7 @@ public static class FaxConstants
|
||||
|
||||
public const string FaxNameData = "fax_data_name";
|
||||
public const string FaxPaperNameData = "fax_data_title";
|
||||
public const string FaxPaperLabelData = "fax_data_label";
|
||||
public const string FaxPaperPrototypeData = "fax_data_prototype";
|
||||
public const string FaxPaperContentData = "fax_data_content";
|
||||
public const string FaxPaperStampStateData = "fax_data_stamp_state";
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Paper;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Fax;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class FaxMachineComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Name with which the fax will be visible to others on the network
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("name")]
|
||||
public string FaxName { get; set; } = "Unknown";
|
||||
|
||||
/// <summary>
|
||||
/// Device address of fax in network to which data will be send
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("destinationAddress")]
|
||||
public string? DestinationFaxAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Contains the item to be sent, assumes it's paper...
|
||||
/// </summary>
|
||||
[DataField("paperSlot", required: true)]
|
||||
public ItemSlot PaperSlot = new();
|
||||
|
||||
/// <summary>
|
||||
/// Is fax machine should respond to pings in network
|
||||
/// This will make it visible to others on the network
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("responsePings")]
|
||||
public bool ResponsePings { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Should admins be notified on message receive
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("notifyAdmins")]
|
||||
public bool NotifyAdmins { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Should that fax receive nuke codes send by admins. Probably should be captain fax only
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("receiveNukeCodes")]
|
||||
public bool ReceiveNukeCodes { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when fax has been emagged
|
||||
/// </summary>
|
||||
[DataField("emagSound")]
|
||||
public SoundSpecifier EmagSound = new SoundCollectionSpecifier("sparks");
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when fax printing new message
|
||||
/// </summary>
|
||||
[DataField("printSound")]
|
||||
public SoundSpecifier PrintSound = new SoundPathSpecifier("/Audio/Machines/printer.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when fax successfully send message
|
||||
/// </summary>
|
||||
[DataField("sendSound")]
|
||||
public SoundSpecifier SendSound = new SoundPathSpecifier("/Audio/Machines/high_tech_confirm.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Known faxes in network by address with fax names
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Dictionary<string, string> KnownFaxes { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Print queue of the incoming message
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("printingQueue")]
|
||||
public Queue<FaxPrintout> PrintingQueue { get; private set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Message sending timeout
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("sendTimeoutRemaining")]
|
||||
public float SendTimeoutRemaining;
|
||||
|
||||
/// <summary>
|
||||
/// Message sending timeout
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("sendTimeout")]
|
||||
public float SendTimeout = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// Remaining time of inserting animation
|
||||
/// </summary>
|
||||
[DataField("insertingTimeRemaining")]
|
||||
public float InsertingTimeRemaining;
|
||||
|
||||
/// <summary>
|
||||
/// How long the inserting animation will play
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public float InsertionTime = 1.88f; // 0.02 off for correct animation
|
||||
|
||||
/// <summary>
|
||||
/// Remaining time of printing animation
|
||||
/// </summary>
|
||||
[DataField("printingTimeRemaining")]
|
||||
public float PrintingTimeRemaining;
|
||||
|
||||
/// <summary>
|
||||
/// How long the printing animation will play
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public float PrintingTime = 2.3f;
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public sealed partial class FaxPrintout
|
||||
{
|
||||
[DataField("name", required: true)]
|
||||
public string Name { get; private set; } = default!;
|
||||
|
||||
[DataField("content", required: true)]
|
||||
public string Content { get; private set; } = default!;
|
||||
|
||||
[DataField("prototypeId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>), required: true)]
|
||||
public string PrototypeId { get; private set; } = default!;
|
||||
|
||||
[DataField("stampState")]
|
||||
public string? StampState { get; private set; }
|
||||
|
||||
[DataField("stampedBy")]
|
||||
public List<StampDisplayInfo> StampedBy { get; private set; } = new();
|
||||
|
||||
private FaxPrintout()
|
||||
{
|
||||
}
|
||||
|
||||
public FaxPrintout(string content, string name, string? prototypeId = null, string? stampState = null, List<StampDisplayInfo>? stampedBy = null)
|
||||
{
|
||||
Content = content;
|
||||
Name = name;
|
||||
PrototypeId = prototypeId ?? "";
|
||||
StampState = stampState;
|
||||
StampedBy = stampedBy ?? new List<StampDisplayInfo>();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using Content.Server.Chat.Managers;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Labels;
|
||||
using Content.Server.Paper;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Power.Components;
|
||||
@@ -16,7 +17,11 @@ using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Fax;
|
||||
using Content.Shared.Fax.Systems;
|
||||
using Content.Shared.Fax.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Labels.Components;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Paper;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
@@ -36,12 +41,14 @@ public sealed class FaxSystem : EntitySystem
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
|
||||
[Dependency] private readonly PaperSystem _paperSystem = default!;
|
||||
[Dependency] private readonly LabelSystem _labelSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly ToolSystem _toolSystem = default!;
|
||||
[Dependency] private readonly QuickDialogSystem _quickDialog = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly FaxecuteSystem _faxecute = default!;
|
||||
|
||||
private const string PaperSlotId = "Paper";
|
||||
|
||||
@@ -236,7 +243,7 @@ public sealed class FaxSystem : EntitySystem
|
||||
}
|
||||
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low,
|
||||
$"{ToPrettyString(args.User):user} renamed {ToPrettyString(uid)} from \"{component.FaxName}\" to \"{newName}\"");
|
||||
$"{ToPrettyString(args.User):user} renamed {ToPrettyString(uid):tool} from \"{component.FaxName}\" to \"{newName}\"");
|
||||
component.FaxName = newName;
|
||||
_popupSystem.PopupEntity(Loc.GetString("fax-machine-popup-name-set"), uid);
|
||||
UpdateUserInterface(uid, component);
|
||||
@@ -288,11 +295,12 @@ public sealed class FaxSystem : EntitySystem
|
||||
!args.Data.TryGetValue(FaxConstants.FaxPaperContentData, out string? content))
|
||||
return;
|
||||
|
||||
args.Data.TryGetValue(FaxConstants.FaxPaperLabelData, out string? label);
|
||||
args.Data.TryGetValue(FaxConstants.FaxPaperStampStateData, out string? stampState);
|
||||
args.Data.TryGetValue(FaxConstants.FaxPaperStampedByData, out List<StampDisplayInfo>? stampedBy);
|
||||
args.Data.TryGetValue(FaxConstants.FaxPaperPrototypeData, out string? prototypeId);
|
||||
|
||||
var printout = new FaxPrintout(content, name, prototypeId, stampState, stampedBy);
|
||||
var printout = new FaxPrintout(content, name, label, prototypeId, stampState, stampedBy);
|
||||
Receive(uid, printout, args.SenderAddress);
|
||||
|
||||
break;
|
||||
@@ -307,18 +315,25 @@ public sealed class FaxSystem : EntitySystem
|
||||
|
||||
private void OnFileButtonPressed(EntityUid uid, FaxMachineComponent component, FaxFileMessage args)
|
||||
{
|
||||
args.Label = args.Label?[..Math.Min(args.Label.Length, FaxFileMessageValidation.MaxLabelSize)];
|
||||
args.Content = args.Content[..Math.Min(args.Content.Length, FaxFileMessageValidation.MaxContentSize)];
|
||||
PrintFile(uid, component, args);
|
||||
}
|
||||
|
||||
private void OnCopyButtonPressed(EntityUid uid, FaxMachineComponent component, FaxCopyMessage args)
|
||||
{
|
||||
Copy(uid, component, args);
|
||||
if (HasComp<MobStateComponent>(component.PaperSlot.Item))
|
||||
_faxecute.Faxecute(uid, component); /// when button pressed it will hurt the mob.
|
||||
else
|
||||
Copy(uid, component, args);
|
||||
}
|
||||
|
||||
private void OnSendButtonPressed(EntityUid uid, FaxMachineComponent component, FaxSendMessage args)
|
||||
{
|
||||
Send(uid, component, args.Actor);
|
||||
if (HasComp<MobStateComponent>(component.PaperSlot.Item))
|
||||
_faxecute.Faxecute(uid, component); /// when button pressed it will hurt the mob.
|
||||
else
|
||||
Send(uid, component, args);
|
||||
}
|
||||
|
||||
private void OnRefreshButtonPressed(EntityUid uid, FaxMachineComponent component, FaxRefreshMessage args)
|
||||
@@ -336,14 +351,20 @@ public sealed class FaxSystem : EntitySystem
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (TryComp<FaxableObjectComponent>(component.PaperSlot.Item, out var faxable))
|
||||
component.InsertingState = faxable.InsertingState;
|
||||
|
||||
|
||||
if (component.InsertingTimeRemaining > 0)
|
||||
{
|
||||
_appearanceSystem.SetData(uid, FaxMachineVisuals.VisualState, FaxMachineVisualState.Inserting);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
else if (component.PrintingTimeRemaining > 0)
|
||||
_appearanceSystem.SetData(uid, FaxMachineVisuals.VisualState, FaxMachineVisualState.Printing);
|
||||
else
|
||||
_appearanceSystem.SetData(uid, FaxMachineVisuals.VisualState, FaxMachineVisualState.Normal);
|
||||
}
|
||||
|
||||
private void UpdateUserInterface(EntityUid uid, FaxMachineComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
@@ -409,16 +430,20 @@ public sealed class FaxSystem : EntitySystem
|
||||
else
|
||||
prototype = DefaultPaperPrototypeId;
|
||||
|
||||
var name = Loc.GetString("fax-machine-printed-paper-name");
|
||||
var name = Loc.GetString("fax-machine-printed-paper-name");
|
||||
|
||||
var printout = new FaxPrintout(args.Content, name, prototype);
|
||||
var printout = new FaxPrintout(args.Content, name, args.Label, prototype);
|
||||
component.PrintingQueue.Enqueue(printout);
|
||||
component.SendTimeoutRemaining += component.SendTimeout;
|
||||
|
||||
UpdateUserInterface(uid, component);
|
||||
|
||||
// Unfortunately, since a paper entity does not yet exist, we have to emulate what LabelSystem will do.
|
||||
var nameWithLabel = (args.Label is { } label) ? $"{name} ({label})" : name;
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low,
|
||||
$"{ToPrettyString(args.Actor):actor} added print job to {ToPrettyString(uid):tool} with text: {args.Content}");
|
||||
$"{ToPrettyString(args.Actor):actor} " +
|
||||
$"added print job to \"{component.FaxName}\" {ToPrettyString(uid):tool} " +
|
||||
$"of {nameWithLabel}: {args.Content}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -438,9 +463,12 @@ public sealed class FaxSystem : EntitySystem
|
||||
!TryComp<PaperComponent>(sendEntity, out var paper))
|
||||
return;
|
||||
|
||||
TryComp<LabelComponent>(sendEntity, out var labelComponent);
|
||||
|
||||
// TODO: See comment in 'Send()' about not being able to copy whole entities
|
||||
var printout = new FaxPrintout(paper.Content,
|
||||
metadata.EntityName,
|
||||
labelComponent?.OriginalName ?? metadata.EntityName,
|
||||
labelComponent?.CurrentLabel,
|
||||
metadata.EntityPrototype?.ID ?? DefaultPaperPrototypeId,
|
||||
paper.StampState,
|
||||
paper.StampedBy);
|
||||
@@ -454,14 +482,16 @@ public sealed class FaxSystem : EntitySystem
|
||||
UpdateUserInterface(uid, component);
|
||||
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low,
|
||||
$"{ToPrettyString(args.Actor):actor} added copy job to {ToPrettyString(uid):tool} with text: {ToPrettyString(component.PaperSlot.Item):subject}");
|
||||
$"{ToPrettyString(args.Actor):actor} " +
|
||||
$"added copy job to \"{component.FaxName}\" {ToPrettyString(uid):tool} " +
|
||||
$"of {ToPrettyString(sendEntity):subject}: {printout.Content}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends message to addressee if paper is set and a known fax is selected
|
||||
/// A timeout is set after sending, which is shared by the copy button.
|
||||
/// </summary>
|
||||
public void Send(EntityUid uid, FaxMachineComponent? component = null, EntityUid? sender = null)
|
||||
public void Send(EntityUid uid, FaxMachineComponent? component, FaxSendMessage args)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
@@ -477,13 +507,16 @@ public sealed class FaxSystem : EntitySystem
|
||||
return;
|
||||
|
||||
if (!TryComp<MetaDataComponent>(sendEntity, out var metadata) ||
|
||||
!TryComp<PaperComponent>(sendEntity, out var paper))
|
||||
!TryComp<PaperComponent>(sendEntity, out var paper))
|
||||
return;
|
||||
|
||||
TryComp<LabelComponent>(sendEntity, out var labelComponent);
|
||||
|
||||
var payload = new NetworkPayload()
|
||||
{
|
||||
{ DeviceNetworkConstants.Command, FaxConstants.FaxPrintCommand },
|
||||
{ FaxConstants.FaxPaperNameData, metadata.EntityName },
|
||||
{ FaxConstants.FaxPaperNameData, labelComponent?.OriginalName ?? metadata.EntityName },
|
||||
{ FaxConstants.FaxPaperLabelData, labelComponent?.CurrentLabel },
|
||||
{ FaxConstants.FaxPaperContentData, paper.Content },
|
||||
};
|
||||
|
||||
@@ -504,7 +537,11 @@ public sealed class FaxSystem : EntitySystem
|
||||
|
||||
_deviceNetworkSystem.QueuePacket(uid, component.DestinationFaxAddress, payload);
|
||||
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{(sender != null ? ToPrettyString(sender.Value) : "Unknown"):user} sent fax from \"{component.FaxName}\" {ToPrettyString(uid)} to {faxName} ({component.DestinationFaxAddress}): {paper.Content}");
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low,
|
||||
$"{ToPrettyString(args.Actor):actor} " +
|
||||
$"sent fax from \"{component.FaxName}\" {ToPrettyString(uid):tool} " +
|
||||
$"to \"{faxName}\" ({component.DestinationFaxAddress}) " +
|
||||
$"of {ToPrettyString(sendEntity):subject}: {paper.Content}");
|
||||
|
||||
component.SendTimeoutRemaining += component.SendTimeout;
|
||||
|
||||
@@ -560,7 +597,13 @@ public sealed class FaxSystem : EntitySystem
|
||||
}
|
||||
|
||||
_metaData.SetEntityName(printed, printout.Name);
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"\"{component.FaxName}\" {ToPrettyString(uid)} printed {ToPrettyString(printed)}: {printout.Content}");
|
||||
|
||||
if (printout.Label is { } label)
|
||||
{
|
||||
_labelSystem.Label(printed, label);
|
||||
}
|
||||
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"\"{component.FaxName}\" {ToPrettyString(uid):tool} printed {ToPrettyString(printed):subject}: {printout.Content}");
|
||||
}
|
||||
|
||||
private void NotifyAdmins(string faxName)
|
||||
|
||||
@@ -87,19 +87,25 @@ public sealed class DrainSystem : SharedDrainSystem
|
||||
|
||||
// Try to transfer as much solution as possible to the drain
|
||||
|
||||
var transferSolution = _solutionContainerSystem.SplitSolution(containerSoln.Value,
|
||||
FixedPoint2.Min(containerSolution.Volume, drainSolution.AvailableVolume));
|
||||
var amountToPutInDrain = drainSolution.AvailableVolume;
|
||||
var amountToSpillOnGround = containerSolution.Volume - drainSolution.AvailableVolume;
|
||||
|
||||
_solutionContainerSystem.TryAddSolution(drain.Solution.Value, transferSolution);
|
||||
|
||||
_audioSystem.PlayPvs(drain.ManualDrainSound, target);
|
||||
_ambientSoundSystem.SetAmbience(target, true);
|
||||
|
||||
// If drain is full, spill
|
||||
|
||||
if (drainSolution.MaxVolume == drainSolution.Volume)
|
||||
if (amountToPutInDrain > 0)
|
||||
{
|
||||
_puddleSystem.TrySpillAt(Transform(target).Coordinates, containerSolution, out _);
|
||||
var solutionToPutInDrain = _solutionContainerSystem.SplitSolution(containerSoln.Value, amountToPutInDrain);
|
||||
_solutionContainerSystem.TryAddSolution(drain.Solution.Value, solutionToPutInDrain);
|
||||
|
||||
_audioSystem.PlayPvs(drain.ManualDrainSound, target);
|
||||
_ambientSoundSystem.SetAmbience(target, true);
|
||||
}
|
||||
|
||||
|
||||
// Spill the remainder.
|
||||
|
||||
if (amountToSpillOnGround > 0)
|
||||
{
|
||||
var solutionToSpill = _solutionContainerSystem.SplitSolution(containerSoln.Value, amountToSpillOnGround);
|
||||
_puddleSystem.TrySpillAt(Transform(target).Coordinates, solutionToSpill, out _);
|
||||
_popupSystem.PopupEntity(
|
||||
Loc.GetString("drain-component-empty-verb-target-is-full-message", ("object", target)),
|
||||
container);
|
||||
|
||||
@@ -173,6 +173,26 @@ namespace Content.Server.GameTicking
|
||||
return gridUids;
|
||||
}
|
||||
|
||||
public int ReadyPlayerCount()
|
||||
{
|
||||
var total = 0;
|
||||
foreach (var (userId, status) in _playerGameStatuses)
|
||||
{
|
||||
if (LobbyEnabled && status == PlayerGameStatus.NotReadyToPlay)
|
||||
continue;
|
||||
|
||||
if (!_playerManager.TryGetSessionById(userId, out _))
|
||||
continue;
|
||||
|
||||
if (_banManager.GetRoleBans(userId) == null)
|
||||
continue;
|
||||
|
||||
total++;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
public void StartRound(bool force = false)
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE
|
||||
@@ -228,6 +248,8 @@ namespace Content.Server.GameTicking
|
||||
readyPlayerProfiles.Add(userId, profile);
|
||||
}
|
||||
|
||||
DebugTools.AssertEqual(readyPlayers.Count, ReadyPlayerCount());
|
||||
|
||||
// Just in case it hasn't been loaded previously we'll try loading it.
|
||||
LoadMaps();
|
||||
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.GameTicking.Components;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.GameTicking.Presets;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Shared.Random;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules;
|
||||
|
||||
@@ -20,11 +22,46 @@ public sealed class SecretRuleSystem : GameRuleSystem<SecretRuleComponent>
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IComponentFactory _compFact = default!;
|
||||
|
||||
private string _ruleCompName = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_ruleCompName = _compFact.GetComponentName(typeof(GameRuleComponent));
|
||||
}
|
||||
|
||||
protected override void Added(EntityUid uid, SecretRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
|
||||
{
|
||||
base.Added(uid, component, gameRule, args);
|
||||
PickRule(component);
|
||||
var weights = _configurationManager.GetCVar(CCVars.SecretWeightPrototype);
|
||||
|
||||
if (!TryPickPreset(weights, out var preset))
|
||||
{
|
||||
Log.Error($"{ToPrettyString(uid)} failed to pick any preset. Removing rule.");
|
||||
Del(uid);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Info($"Selected {preset.ID} as the secret preset.");
|
||||
_adminLogger.Add(LogType.EventStarted, $"Selected {preset.ID} as the secret preset.");
|
||||
_chatManager.SendAdminAnnouncement(Loc.GetString("rule-secret-selected-preset", ("preset", preset.ID)));
|
||||
|
||||
foreach (var rule in preset.Rules)
|
||||
{
|
||||
EntityUid ruleEnt;
|
||||
|
||||
// if we're pre-round (i.e. will only be added)
|
||||
// then just add rules. if we're added in the middle of the round (or at any other point really)
|
||||
// then we want to start them as well
|
||||
if (GameTicker.RunLevel <= GameRunLevel.InRound)
|
||||
ruleEnt = GameTicker.AddGameRule(rule);
|
||||
else
|
||||
GameTicker.StartGameRule(rule, out ruleEnt);
|
||||
|
||||
component.AdditionalGameRules.Add(ruleEnt);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Ended(EntityUid uid, SecretRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
|
||||
@@ -37,32 +74,101 @@ public sealed class SecretRuleSystem : GameRuleSystem<SecretRuleComponent>
|
||||
}
|
||||
}
|
||||
|
||||
private void PickRule(SecretRuleComponent component)
|
||||
private bool TryPickPreset(ProtoId<WeightedRandomPrototype> weights, [NotNullWhen(true)] out GamePresetPrototype? preset)
|
||||
{
|
||||
// TODO: This doesn't consider what can't start due to minimum player count,
|
||||
// but currently there's no way to know anyway as they use cvars.
|
||||
var presetString = _configurationManager.GetCVar(CCVars.SecretWeightPrototype);
|
||||
var preset = _prototypeManager.Index<WeightedRandomPrototype>(presetString).Pick(_random);
|
||||
Log.Info($"Selected {preset} for secret.");
|
||||
_adminLogger.Add(LogType.EventStarted, $"Selected {preset} for secret.");
|
||||
_chatManager.SendAdminAnnouncement(Loc.GetString("rule-secret-selected-preset", ("preset", preset)));
|
||||
var options = _prototypeManager.Index(weights).Weights.ShallowClone();
|
||||
var players = GameTicker.ReadyPlayerCount();
|
||||
|
||||
var rules = _prototypeManager.Index<GamePresetPrototype>(preset).Rules;
|
||||
foreach (var rule in rules)
|
||||
GamePresetPrototype? selectedPreset = null;
|
||||
var sum = options.Values.Sum();
|
||||
while (options.Count > 0)
|
||||
{
|
||||
EntityUid ruleEnt;
|
||||
|
||||
// if we're pre-round (i.e. will only be added)
|
||||
// then just add rules. if we're added in the middle of the round (or at any other point really)
|
||||
// then we want to start them as well
|
||||
if (GameTicker.RunLevel <= GameRunLevel.InRound)
|
||||
ruleEnt = GameTicker.AddGameRule(rule);
|
||||
else
|
||||
var accumulated = 0f;
|
||||
var rand = _random.NextFloat(sum);
|
||||
foreach (var (key, weight) in options)
|
||||
{
|
||||
GameTicker.StartGameRule(rule, out ruleEnt);
|
||||
accumulated += weight;
|
||||
if (accumulated < rand)
|
||||
continue;
|
||||
|
||||
if (!_prototypeManager.TryIndex(key, out selectedPreset))
|
||||
Log.Error($"Invalid preset {selectedPreset} in secret rule weights: {weights}");
|
||||
|
||||
options.Remove(key);
|
||||
sum -= weight;
|
||||
break;
|
||||
}
|
||||
|
||||
component.AdditionalGameRules.Add(ruleEnt);
|
||||
if (CanPick(selectedPreset, players))
|
||||
{
|
||||
preset = selectedPreset;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (selectedPreset != null)
|
||||
Log.Info($"Excluding {selectedPreset.ID} from secret preset selection.");
|
||||
}
|
||||
|
||||
preset = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanPickAny()
|
||||
{
|
||||
var secretPresetId = _configurationManager.GetCVar(CCVars.SecretWeightPrototype);
|
||||
return CanPickAny(secretPresetId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can any of the given presets be picked, taking into account the currently available player count?
|
||||
/// </summary>
|
||||
public bool CanPickAny(ProtoId<WeightedRandomPrototype> weightedPresets)
|
||||
{
|
||||
var ids = _prototypeManager.Index(weightedPresets).Weights.Keys
|
||||
.Select(x => new ProtoId<GamePresetPrototype>(x));
|
||||
|
||||
return CanPickAny(ids);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can any of the given presets be picked, taking into account the currently available player count?
|
||||
/// </summary>
|
||||
public bool CanPickAny(IEnumerable<ProtoId<GamePresetPrototype>> protos)
|
||||
{
|
||||
var players = GameTicker.ReadyPlayerCount();
|
||||
foreach (var id in protos)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(id, out var selectedPreset))
|
||||
Log.Error($"Invalid preset {selectedPreset} in secret rule weights: {id}");
|
||||
|
||||
if (CanPick(selectedPreset, players))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the given preset be picked, taking into account the currently available player count?
|
||||
/// </summary>
|
||||
private bool CanPick([NotNullWhen(true)] GamePresetPrototype? selected, int players)
|
||||
{
|
||||
if (selected == null)
|
||||
return false;
|
||||
|
||||
foreach (var ruleId in selected.Rules)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(ruleId, out EntityPrototype? rule)
|
||||
|| !rule.TryGetComponent(_ruleCompName, out GameRuleComponent? ruleComp))
|
||||
{
|
||||
Log.Error($"Encountered invalid rule {ruleId} in preset {selected.ID}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ruleComp.MinPlayers > players)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
using Content.Shared.Whitelist;
|
||||
|
||||
namespace Content.Server.Labels.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class HandLabelerComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("assignedLabel")]
|
||||
public string AssignedLabel { get; set; } = string.Empty;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("maxLabelChars")]
|
||||
public int MaxLabelChars { get; set; } = 50;
|
||||
|
||||
[DataField("whitelist")]
|
||||
public EntityWhitelist Whitelist = new();
|
||||
}
|
||||
}
|
||||
@@ -1,116 +1,8 @@
|
||||
using Content.Server.Labels.Components;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Labels;
|
||||
using Content.Shared.Verbs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using Content.Shared.Labels.EntitySystems;
|
||||
|
||||
namespace Content.Server.Labels
|
||||
namespace Content.Server.Labels.Label;
|
||||
|
||||
public sealed class HandLabelerSystem : SharedHandLabelerSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// A hand labeler system that lets an object apply labels to objects with the <see cref="LabelComponent"/> .
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class HandLabelerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly LabelSystem _labelSystem = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<HandLabelerComponent, AfterInteractEvent>(AfterInteractOn);
|
||||
SubscribeLocalEvent<HandLabelerComponent, GetVerbsEvent<UtilityVerb>>(OnUtilityVerb);
|
||||
// Bound UI subscriptions
|
||||
SubscribeLocalEvent<HandLabelerComponent, HandLabelerLabelChangedMessage>(OnHandLabelerLabelChanged);
|
||||
}
|
||||
|
||||
private void OnUtilityVerb(EntityUid uid, HandLabelerComponent handLabeler, GetVerbsEvent<UtilityVerb> args)
|
||||
{
|
||||
if (args.Target is not { Valid: true } target || !handLabeler.Whitelist.IsValid(target) || !args.CanAccess)
|
||||
return;
|
||||
|
||||
string labelerText = handLabeler.AssignedLabel == string.Empty ? Loc.GetString("hand-labeler-remove-label-text") : Loc.GetString("hand-labeler-add-label-text");
|
||||
|
||||
var verb = new UtilityVerb()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
AddLabelTo(uid, handLabeler, target, out var result);
|
||||
if (result != null)
|
||||
_popupSystem.PopupEntity(result, args.User, args.User);
|
||||
},
|
||||
Text = labelerText
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void AfterInteractOn(EntityUid uid, HandLabelerComponent handLabeler, AfterInteractEvent args)
|
||||
{
|
||||
if (args.Target is not {Valid: true} target || !handLabeler.Whitelist.IsValid(target) || !args.CanReach)
|
||||
return;
|
||||
|
||||
AddLabelTo(uid, handLabeler, target, out string? result);
|
||||
if (result == null)
|
||||
return;
|
||||
_popupSystem.PopupEntity(result, args.User, args.User);
|
||||
|
||||
// Log labeling
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low,
|
||||
$"{ToPrettyString(args.User):user} labeled {ToPrettyString(target):target} with {ToPrettyString(uid):labeler}");
|
||||
}
|
||||
|
||||
private void AddLabelTo(EntityUid uid, HandLabelerComponent? handLabeler, EntityUid target, out string? result)
|
||||
{
|
||||
if (!Resolve(uid, ref handLabeler))
|
||||
{
|
||||
result = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (handLabeler.AssignedLabel == string.Empty)
|
||||
{
|
||||
_labelSystem.Label(target, null);
|
||||
result = Loc.GetString("hand-labeler-successfully-removed");
|
||||
return;
|
||||
}
|
||||
|
||||
_labelSystem.Label(target, handLabeler.AssignedLabel);
|
||||
result = Loc.GetString("hand-labeler-successfully-applied");
|
||||
}
|
||||
|
||||
private void OnHandLabelerLabelChanged(EntityUid uid, HandLabelerComponent handLabeler, HandLabelerLabelChangedMessage args)
|
||||
{
|
||||
if (args.Actor is not {Valid: true} player)
|
||||
return;
|
||||
|
||||
var label = args.Label.Trim();
|
||||
handLabeler.AssignedLabel = label.Substring(0, Math.Min(handLabeler.MaxLabelChars, label.Length));
|
||||
DirtyUI(uid, handLabeler);
|
||||
|
||||
// Log label change
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low,
|
||||
$"{ToPrettyString(player):user} set {ToPrettyString(uid):labeler} to apply label \"{handLabeler.AssignedLabel}\"");
|
||||
|
||||
}
|
||||
|
||||
private void DirtyUI(EntityUid uid,
|
||||
HandLabelerComponent? handLabeler = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handLabeler))
|
||||
return;
|
||||
|
||||
_userInterfaceSystem.SetUiState(uid, HandLabelerUiKey.Key,
|
||||
new HandLabelerBoundUserInterfaceState(handLabeler.AssignedLabel));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Content.Server.Labels
|
||||
/// <param name="text">intended label text (null to remove)</param>
|
||||
/// <param name="label">label component for resolve</param>
|
||||
/// <param name="metadata">metadata component for resolve</param>
|
||||
public void Label(EntityUid uid, string? text, MetaDataComponent? metadata = null, LabelComponent? label = null)
|
||||
public override void Label(EntityUid uid, string? text, MetaDataComponent? metadata = null, LabelComponent? label = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metadata))
|
||||
return;
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
using Content.Server.Light.Events;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Light;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Toggleable;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Light.EntitySystems
|
||||
{
|
||||
public sealed class UnpoweredFlashlightSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly SharedPointLightSystem _light = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<UnpoweredFlashlightComponent, GetVerbsEvent<ActivationVerb>>(AddToggleLightVerbs);
|
||||
SubscribeLocalEvent<UnpoweredFlashlightComponent, GetItemActionsEvent>(OnGetActions);
|
||||
SubscribeLocalEvent<UnpoweredFlashlightComponent, ToggleActionEvent>(OnToggleAction);
|
||||
SubscribeLocalEvent<UnpoweredFlashlightComponent, MindAddedMessage>(OnMindAdded);
|
||||
SubscribeLocalEvent<UnpoweredFlashlightComponent, GotEmaggedEvent>(OnGotEmagged);
|
||||
SubscribeLocalEvent<UnpoweredFlashlightComponent, MapInitEvent>(OnMapInit);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, UnpoweredFlashlightComponent component, MapInitEvent args)
|
||||
{
|
||||
_actionContainer.EnsureAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
private void OnToggleAction(EntityUid uid, UnpoweredFlashlightComponent component, ToggleActionEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
ToggleLight(uid, component);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnGetActions(EntityUid uid, UnpoweredFlashlightComponent component, GetItemActionsEvent args)
|
||||
{
|
||||
args.AddAction(ref component.ToggleActionEntity, component.ToggleAction);
|
||||
}
|
||||
|
||||
private void AddToggleLightVerbs(EntityUid uid, UnpoweredFlashlightComponent component, GetVerbsEvent<ActivationVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
|
||||
ActivationVerb verb = new()
|
||||
{
|
||||
Text = Loc.GetString("toggle-flashlight-verb-get-data-text"),
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/light.svg.192dpi.png")),
|
||||
Act = () => ToggleLight(uid, component),
|
||||
Priority = -1 // For things like PDA's, Open-UI and other verbs that should be higher priority.
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void OnMindAdded(EntityUid uid, UnpoweredFlashlightComponent component, MindAddedMessage args)
|
||||
{
|
||||
_actionsSystem.AddAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
|
||||
}
|
||||
|
||||
private void OnGotEmagged(EntityUid uid, UnpoweredFlashlightComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_light.TryGetLight(uid, out var light))
|
||||
return;
|
||||
|
||||
if (_prototypeManager.TryIndex<ColorPalettePrototype>(component.EmaggedColorsPrototype, out var possibleColors))
|
||||
{
|
||||
var pick = _random.Pick(possibleColors.Colors.Values);
|
||||
_light.SetColor(uid, pick, light);
|
||||
}
|
||||
|
||||
args.Repeatable = true;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public void ToggleLight(EntityUid uid, UnpoweredFlashlightComponent flashlight)
|
||||
{
|
||||
if (!_light.TryGetLight(uid, out var light))
|
||||
return;
|
||||
|
||||
flashlight.LightOn = !flashlight.LightOn;
|
||||
_light.SetEnabled(uid, flashlight.LightOn, light);
|
||||
|
||||
_appearance.SetData(uid, UnpoweredFlashlightVisuals.LightOn, flashlight.LightOn);
|
||||
|
||||
_audioSystem.PlayPvs(flashlight.ToggleSound, uid);
|
||||
|
||||
RaiseLocalEvent(uid, new LightToggleEvent(flashlight.LightOn), true);
|
||||
_actionsSystem.SetToggled(flashlight.ToggleActionEntity, flashlight.LightOn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace Content.Server.Light.Events
|
||||
{
|
||||
public sealed class LightToggleEvent : EntityEventArgs
|
||||
{
|
||||
public bool IsOn;
|
||||
|
||||
public LightToggleEvent(bool isOn)
|
||||
{
|
||||
IsOn = isOn;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Fax;
|
||||
using Content.Shared.Fax.Components;
|
||||
using Content.Server.Paper;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
@@ -65,6 +66,7 @@ namespace Content.Server.Nuke
|
||||
paperContent,
|
||||
Loc.GetString("nuke-codes-fax-paper-name"),
|
||||
null,
|
||||
null,
|
||||
"paper_stamp-centcom",
|
||||
new List<StampDisplayInfo>
|
||||
{
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
using Content.Server.Objectives.Systems;
|
||||
|
||||
namespace Content.Server.Objectives.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Sets this objective's target to the exterminator's target override, if it has one.
|
||||
/// If not it will be random.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(TerminatorTargetOverrideSystem))]
|
||||
public sealed partial class TerminatorTargetOverrideComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using Content.Server.Objectives.Components;
|
||||
using Content.Server.Terminator.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Objectives.Components;
|
||||
|
||||
namespace Content.Server.Objectives.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Handles copying the exterminator's target override to this objective.
|
||||
/// </summary>
|
||||
public sealed class TerminatorTargetOverrideSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly TargetObjectiveSystem _target = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<TerminatorTargetOverrideComponent, ObjectiveAssignedEvent>(OnAssigned);
|
||||
}
|
||||
|
||||
private void OnAssigned(EntityUid uid, TerminatorTargetOverrideComponent comp, ref ObjectiveAssignedEvent args)
|
||||
{
|
||||
if (args.Mind.OwnedEntity == null)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var user = args.Mind.OwnedEntity.Value;
|
||||
if (!TryComp<TerminatorComponent>(user, out var terminator))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// this exterminator has a target override so set its objective target accordingly
|
||||
if (terminator.Target != null)
|
||||
_target.SetTarget(uid, terminator.Target.Value);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ using Content.Server.Chat.Managers;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.Instruments;
|
||||
using Content.Server.Light.EntitySystems;
|
||||
using Content.Server.Light.Events;
|
||||
using Content.Server.PDA.Ringer;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.Store.Components;
|
||||
@@ -12,7 +11,9 @@ using Content.Server.Store.Systems;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.CartridgeLoader;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Light;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Light.EntitySystems;
|
||||
using Content.Shared.PDA;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -207,8 +208,9 @@ namespace Content.Server.PDA
|
||||
if (!PdaUiKey.Key.Equals(msg.UiKey))
|
||||
return;
|
||||
|
||||
if (TryComp<UnpoweredFlashlightComponent>(uid, out var flashlight))
|
||||
_unpoweredFlashlight.ToggleLight(uid, flashlight);
|
||||
// TODO PREDICTION
|
||||
// When moving this to shared, fill in the user field
|
||||
_unpoweredFlashlight.TryToggleLight(uid, user: null);
|
||||
}
|
||||
|
||||
private void OnUiMessage(EntityUid uid, PdaComponent pda, PdaShowRingtoneMessage msg)
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Whitelist;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Power.Components
|
||||
{
|
||||
@@ -26,5 +31,12 @@ namespace Content.Server.Power.Components
|
||||
/// </summary>
|
||||
[DataField("whitelist")]
|
||||
public EntityWhitelist? Whitelist;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the charger is portable and thus subject to EMP effects
|
||||
/// and bypasses checks for transform, anchored, and ApcPowerReceiverComponent.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Portable = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Emp;
|
||||
using Content.Server.PowerCell;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.PowerCell.Components;
|
||||
using Content.Shared.Emp;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
@@ -28,6 +30,8 @@ internal sealed class ChargerSystem : EntitySystem
|
||||
SubscribeLocalEvent<ChargerComponent, ContainerIsInsertingAttemptEvent>(OnInsertAttempt);
|
||||
SubscribeLocalEvent<ChargerComponent, InsertIntoEntityStorageAttemptEvent>(OnEntityStorageInsertAttempt);
|
||||
SubscribeLocalEvent<ChargerComponent, ExaminedEvent>(OnChargerExamine);
|
||||
|
||||
SubscribeLocalEvent<ChargerComponent, EmpPulseEvent>(OnEmpPulse);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, ChargerComponent component, ComponentStartup args)
|
||||
@@ -158,18 +162,27 @@ internal sealed class ChargerSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEmpPulse(EntityUid uid, ChargerComponent component, ref EmpPulseEvent args)
|
||||
{
|
||||
args.Affected = true;
|
||||
args.Disabled = true;
|
||||
}
|
||||
|
||||
private CellChargerStatus GetStatus(EntityUid uid, ChargerComponent component)
|
||||
{
|
||||
if (!TryComp(uid, out TransformComponent? transformComponent))
|
||||
return CellChargerStatus.Off;
|
||||
|
||||
if (!transformComponent.Anchored)
|
||||
return CellChargerStatus.Off;
|
||||
if (!component.Portable)
|
||||
{
|
||||
if (!TryComp(uid, out TransformComponent? transformComponent) || !transformComponent.Anchored)
|
||||
return CellChargerStatus.Off;
|
||||
}
|
||||
|
||||
if (!TryComp(uid, out ApcPowerReceiverComponent? apcPowerReceiverComponent))
|
||||
return CellChargerStatus.Off;
|
||||
|
||||
if (!apcPowerReceiverComponent.Powered)
|
||||
if (!component.Portable && !apcPowerReceiverComponent.Powered)
|
||||
return CellChargerStatus.Off;
|
||||
|
||||
if (HasComp<EmpDisabledComponent>(uid))
|
||||
return CellChargerStatus.Off;
|
||||
|
||||
if (!_container.TryGetContainer(uid, component.SlotId, out var container))
|
||||
@@ -186,7 +199,7 @@ internal sealed class ChargerSystem : EntitySystem
|
||||
|
||||
return CellChargerStatus.Charging;
|
||||
}
|
||||
|
||||
|
||||
private void TransferPower(EntityUid uid, EntityUid targetEntity, ChargerComponent component, float frameTime)
|
||||
{
|
||||
if (!TryComp(uid, out ApcPowerReceiverComponent? receiverComponent))
|
||||
|
||||
@@ -47,9 +47,13 @@ public sealed class RandomMetadataSystem : EntitySystem
|
||||
var outputSegments = new List<string>();
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
if (_prototype.TryIndex<DatasetPrototype>(segment, out var proto))
|
||||
outputSegments.Add(_random.Pick(proto.Values));
|
||||
else if (Loc.TryGetString(segment, out var localizedSegment))
|
||||
if (_prototype.TryIndex<DatasetPrototype>(segment, out var proto)) {
|
||||
var random = _random.Pick(proto.Values);
|
||||
if (Loc.TryGetString(random, out var localizedSegment))
|
||||
outputSegments.Add(localizedSegment);
|
||||
else
|
||||
outputSegments.Add(random);
|
||||
} else if (Loc.TryGetString(segment, out var localizedSegment))
|
||||
outputSegments.Add(localizedSegment);
|
||||
else
|
||||
outputSegments.Add(segment);
|
||||
|
||||
@@ -15,7 +15,6 @@ public sealed class RoleSystem : SharedRoleSystem
|
||||
SubscribeAntagEvents<NukeopsRoleComponent>();
|
||||
SubscribeAntagEvents<RevolutionaryRoleComponent>();
|
||||
SubscribeAntagEvents<SubvertedSiliconRoleComponent>();
|
||||
SubscribeAntagEvents<TerminatorRoleComponent>();
|
||||
SubscribeAntagEvents<TraitorRoleComponent>();
|
||||
SubscribeAntagEvents<ZombieRoleComponent>();
|
||||
SubscribeAntagEvents<ThiefRoleComponent>();
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
using Content.Server.Speech.EntitySystems;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Humanoid;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
|
||||
namespace Content.Server.Speech.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component required for entities to be able to do vocal emotions.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[Access(typeof(VocalSystem))]
|
||||
public sealed partial class VocalComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Emote sounds prototype id for each sex (not gender).
|
||||
/// Entities without <see cref="HumanoidComponent"/> considered to be <see cref="Sex.Unsexed"/>.
|
||||
/// </summary>
|
||||
[DataField("sounds", customTypeSerializer: typeof(PrototypeIdValueDictionarySerializer<Sex, EmoteSoundsPrototype>))]
|
||||
public Dictionary<Sex, string>? Sounds;
|
||||
|
||||
[DataField("screamId", customTypeSerializer: typeof(PrototypeIdSerializer<EmotePrototype>))]
|
||||
public string ScreamId = "Scream";
|
||||
|
||||
[DataField("wilhelm")]
|
||||
public SoundSpecifier Wilhelm = new SoundPathSpecifier("/Audio/Voice/Human/wilhelm_scream.ogg");
|
||||
|
||||
[DataField("wilhelmProbability")]
|
||||
public float WilhelmProbability = 0.0002f;
|
||||
|
||||
[DataField("screamAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string ScreamAction = "ActionScream";
|
||||
|
||||
[DataField("screamActionEntity")]
|
||||
public EntityUid? ScreamActionEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Currently loaded emote sounds prototype, based on entity sex.
|
||||
/// Null if no valid prototype for entity sex was found.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EmoteSoundsPrototype? EmoteSounds = null;
|
||||
}
|
||||
30
Content.Server/Speech/EmotesMenuSystem.cs
Normal file
30
Content.Server/Speech/EmotesMenuSystem.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Shared.Chat;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Speech;
|
||||
|
||||
public sealed partial class EmotesMenuSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeAllEvent<PlayEmoteMessage>(OnPlayEmote);
|
||||
}
|
||||
|
||||
private void OnPlayEmote(PlayEmoteMessage msg, EntitySessionEventArgs args)
|
||||
{
|
||||
var player = args.SenderSession.AttachedEntity;
|
||||
if (!player.HasValue)
|
||||
return;
|
||||
|
||||
if (!_prototypeManager.TryIndex(msg.ProtoId, out var proto) || proto.ChatTriggers.Count == 0)
|
||||
return;
|
||||
|
||||
_chat.TryEmoteWithChat(player.Value, msg.ProtoId);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using Content.Server.Speech.Components;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Speech;
|
||||
using Content.Shared.Speech.Components;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -58,8 +58,7 @@ namespace Content.Server.StationEvents
|
||||
/// </summary>
|
||||
private void ResetTimer(BasicStationEventSchedulerComponent component)
|
||||
{
|
||||
// 5 - 25 minutes. TG does 3-10 but that's pretty frequent
|
||||
component.TimeUntilNextEvent = _random.Next(300, 1500);
|
||||
component.TimeUntilNextEvent = _random.Next(3 * 60, 10 * 60);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
using Content.Server.Terminator.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Server.Terminator.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Main terminator component, handles the target, if any, and objectives.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(TerminatorSystem))]
|
||||
public sealed partial class TerminatorComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to force the terminate objective's target.
|
||||
/// If null it will be a random person.
|
||||
/// </summary>
|
||||
[DataField("target")]
|
||||
public EntityUid? Target;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using Content.Server.Terminator.Systems;
|
||||
|
||||
namespace Content.Server.Terminator.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="TerminatorComponent.Target"/> after the ghost role spawns.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(TerminatorSystem))]
|
||||
public sealed partial class TerminatorTargetComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The target to set after the ghost role spawns.
|
||||
/// </summary>
|
||||
[DataField("target")]
|
||||
public EntityUid? Target;
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.GenericAntag;
|
||||
using Content.Server.Ghost.Roles.Events;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.Terminator.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Terminator.Systems;
|
||||
|
||||
public sealed class TerminatorSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedRoleSystem _role = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<TerminatorComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<TerminatorComponent, GhostRoleSpawnerUsedEvent>(OnSpawned);
|
||||
SubscribeLocalEvent<TerminatorComponent, GenericAntagCreatedEvent>(OnCreated);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, TerminatorComponent comp, MapInitEvent args)
|
||||
{
|
||||
// cyborg doesn't need to breathe
|
||||
RemComp<RespiratorComponent>(uid);
|
||||
}
|
||||
|
||||
private void OnSpawned(EntityUid uid, TerminatorComponent comp, GhostRoleSpawnerUsedEvent args)
|
||||
{
|
||||
if (!TryComp<TerminatorTargetComponent>(args.Spawner, out var target))
|
||||
return;
|
||||
|
||||
comp.Target = target.Target;
|
||||
}
|
||||
|
||||
private void OnCreated(EntityUid uid, TerminatorComponent comp, ref GenericAntagCreatedEvent args)
|
||||
{
|
||||
var mindId = args.MindId;
|
||||
var mind = args.Mind;
|
||||
|
||||
_role.MindAddRole(mindId, new RoleBriefingComponent
|
||||
{
|
||||
Briefing = Loc.GetString("terminator-role-briefing")
|
||||
}, mind);
|
||||
_role.MindAddRole(mindId, new TerminatorRoleComponent(), mind);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a spawner at a position and return it.
|
||||
/// </summary>
|
||||
/// <param name="coords">Coordinates to create the spawner at</param>
|
||||
/// <param name="target">Optional target mind to force the terminator to target</param>
|
||||
public EntityUid CreateSpawner(EntityCoordinates coords, EntityUid? target)
|
||||
{
|
||||
var uid = Spawn("SpawnPointGhostTerminator", coords);
|
||||
if (target != null)
|
||||
{
|
||||
var comp = EnsureComp<TerminatorTargetComponent>(uid);
|
||||
comp.Target = target;
|
||||
}
|
||||
|
||||
return uid;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Tips;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
@@ -22,11 +26,14 @@ public sealed class TipsSystem : EntitySystem
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly GameTicker _ticker = default!;
|
||||
[Dependency] private readonly IConsoleHost _conHost = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
private bool _tipsEnabled;
|
||||
private float _tipTimeOutOfRound;
|
||||
private float _tipTimeInRound;
|
||||
private string _tipsDataset = "";
|
||||
private float _tipTippyChance;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private TimeSpan _nextTipTime = TimeSpan.Zero;
|
||||
@@ -40,10 +47,100 @@ public sealed class TipsSystem : EntitySystem
|
||||
Subs.CVar(_cfg, CCVars.TipFrequencyInRound, SetInRound, true);
|
||||
Subs.CVar(_cfg, CCVars.TipsEnabled, SetEnabled, true);
|
||||
Subs.CVar(_cfg, CCVars.TipsDataset, SetDataset, true);
|
||||
Subs.CVar(_cfg, CCVars.TipsTippyChance, SetTippyChance, true);
|
||||
|
||||
RecalculateNextTipTime();
|
||||
_conHost.RegisterCommand("tippy", Loc.GetString("cmd-tippy-desc"), Loc.GetString("cmd-tippy-help"), SendTippy, SendTippyHelper);
|
||||
_conHost.RegisterCommand("tip", Loc.GetString("cmd-tip-desc"), "tip", SendTip);
|
||||
}
|
||||
|
||||
private CompletionResult SendTippyHelper(IConsoleShell shell, string[] args)
|
||||
{
|
||||
return args.Length switch
|
||||
{
|
||||
1 => CompletionResult.FromHintOptions(CompletionHelper.SessionNames(), Loc.GetString("cmd-tippy-auto-1")),
|
||||
2 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-2")),
|
||||
3 => CompletionResult.FromHintOptions(CompletionHelper.PrototypeIDs<EntityPrototype>(), Loc.GetString("cmd-tippy-auto-3")),
|
||||
4 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-4")),
|
||||
5 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-5")),
|
||||
6 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-6")),
|
||||
_ => CompletionResult.Empty
|
||||
};
|
||||
}
|
||||
|
||||
private void SendTip(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
AnnounceRandomTip();
|
||||
RecalculateNextTipTime();
|
||||
}
|
||||
|
||||
private void SendTippy(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-tippy-help"));
|
||||
return;
|
||||
}
|
||||
|
||||
ActorComponent? actor = null;
|
||||
if (args[0] != "all")
|
||||
{
|
||||
ICommonSession? session;
|
||||
if (args.Length > 0)
|
||||
{
|
||||
// Get player entity
|
||||
if (!_playerManager.TryGetSessionByUsername(args[0], out session))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-tippy-error-no-user"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
session = shell.Player;
|
||||
}
|
||||
|
||||
if (session?.AttachedEntity is not { } user)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-tippy-error-no-user"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp(user, out actor))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-tippy-error-no-user"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var ev = new TippyEvent(args[1]);
|
||||
|
||||
if (args.Length > 2)
|
||||
{
|
||||
ev.Proto = args[2];
|
||||
if (!_prototype.HasIndex<EntityPrototype>(args[2]))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-tippy-error-no-prototype", ("proto", args[2])));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (args.Length > 3)
|
||||
ev.SpeakTime = float.Parse(args[3]);
|
||||
|
||||
if (args.Length > 4)
|
||||
ev.SlideTime = float.Parse(args[4]);
|
||||
|
||||
if (args.Length > 5)
|
||||
ev.WaddleInterval = float.Parse(args[5]);
|
||||
|
||||
if (actor != null)
|
||||
RaiseNetworkEvent(ev, actor.PlayerSession);
|
||||
else
|
||||
RaiseNetworkEvent(ev);
|
||||
}
|
||||
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
@@ -81,6 +178,11 @@ public sealed class TipsSystem : EntitySystem
|
||||
_tipsDataset = value;
|
||||
}
|
||||
|
||||
private void SetTippyChance(float value)
|
||||
{
|
||||
_tipTippyChance = value;
|
||||
}
|
||||
|
||||
private void AnnounceRandomTip()
|
||||
{
|
||||
if (!_prototype.TryIndex<DatasetPrototype>(_tipsDataset, out var tips))
|
||||
@@ -89,8 +191,16 @@ public sealed class TipsSystem : EntitySystem
|
||||
var tip = _random.Pick(tips.Values);
|
||||
var msg = Loc.GetString("tips-system-chat-message-wrap", ("tip", tip));
|
||||
|
||||
_chat.ChatMessageToManyFiltered(Filter.Broadcast(), ChatChannel.OOC, tip, msg,
|
||||
if (_random.Prob(_tipTippyChance))
|
||||
{
|
||||
var ev = new TippyEvent(msg);
|
||||
ev.SpeakTime = 1 + tip.Length * 0.05f;
|
||||
RaiseNetworkEvent(ev);
|
||||
} else
|
||||
{
|
||||
_chat.ChatMessageToManyFiltered(Filter.Broadcast(), ChatChannel.OOC, tip, msg,
|
||||
EntityUid.Invalid, false, false, Color.MediumPurple);
|
||||
}
|
||||
}
|
||||
|
||||
private void RecalculateNextTipTime()
|
||||
|
||||
@@ -105,7 +105,7 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
// Update shot based on the recoil
|
||||
toMap = fromMap.Position + angle.ToVec() * mapDirection.Length();
|
||||
mapDirection = toMap - fromMap.Position;
|
||||
var gunVelocity = Physics.GetMapLinearVelocity(gunUid);
|
||||
var gunVelocity = Physics.GetMapLinearVelocity(fromEnt);
|
||||
|
||||
// I must be high because this was getting tripped even when true.
|
||||
// DebugTools.Assert(direction != Vector2.Zero);
|
||||
|
||||
@@ -35,8 +35,7 @@ public sealed class PortalArtifactSystem : EntitySystem
|
||||
var secondPortal = Spawn(artifact.Comp.PortalProto, _transform.GetMapCoordinates(target));
|
||||
|
||||
//Manual position swapping, because the portal that opens doesn't trigger a collision, and doesn't teleport targets the first time.
|
||||
_transform.SetCoordinates(artifact, Transform(secondPortal).Coordinates);
|
||||
_transform.SetCoordinates(target, Transform(firstPortal).Coordinates);
|
||||
_transform.SwapPositions(target, secondPortal);
|
||||
|
||||
_link.TryLink(firstPortal, secondPortal, true);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ public sealed class ShuffleArtifactSystem : EntitySystem
|
||||
{
|
||||
var mobState = GetEntityQuery<MobStateComponent>();
|
||||
|
||||
List<EntityCoordinates> allCoords = new();
|
||||
List<Entity<TransformComponent>> toShuffle = new();
|
||||
|
||||
foreach (var ent in _lookup.GetEntitiesInRange(uid, component.Radius, LookupFlags.Dynamic | LookupFlags.Sundries))
|
||||
@@ -33,13 +32,15 @@ public sealed class ShuffleArtifactSystem : EntitySystem
|
||||
var xform = Transform(ent);
|
||||
|
||||
toShuffle.Add((ent, xform));
|
||||
allCoords.Add(xform.Coordinates);
|
||||
}
|
||||
|
||||
foreach (var xform in toShuffle)
|
||||
_random.Shuffle(toShuffle);
|
||||
|
||||
while (toShuffle.Count > 1)
|
||||
{
|
||||
var xformUid = xform.Owner;
|
||||
_xform.SetCoordinates(xformUid, xform, _random.PickAndTake(allCoords));
|
||||
var ent1 = _random.PickAndTake(toShuffle);
|
||||
var ent2 = _random.PickAndTake(toShuffle);
|
||||
_xform.SwapPositions((ent1, ent1), (ent2, ent2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user