Merge remote-tracking branch 'upstream/stable' into ed-11-12-2024-upstream

# Conflicts:
#	Content.IntegrationTests/Tests/PostMapInitTest.cs
#	Content.Shared/Lock/LockSystem.cs
#	Resources/Maps/oasis.yml
#	Resources/Prototypes/Maps/Pools/default.yml
#	Resources/Prototypes/audio.yml
This commit is contained in:
Ed
2024-12-11 14:49:02 +03:00
298 changed files with 153553 additions and 9112 deletions

View File

@@ -16,6 +16,7 @@ public sealed class GasProducerAnomalySystem : EntitySystem
{
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
public override void Initialize()
{
@@ -56,8 +57,11 @@ public sealed class GasProducerAnomalySystem : EntitySystem
return;
var localpos = xform.Coordinates.Position;
var tilerefs = grid.GetLocalTilesIntersecting(
new Box2(localpos + new Vector2(-radius, -radius), localpos + new Vector2(radius, radius))).ToArray();
var tilerefs = _map.GetLocalTilesIntersecting(
xform.GridUid.Value,
grid,
new Box2(localpos + new Vector2(-radius, -radius), localpos + new Vector2(radius, radius)))
.ToArray();
if (tilerefs.Length == 0)
return;

View File

@@ -102,10 +102,12 @@ public sealed class LogicGateSystem : EntitySystem
if (args.Port == comp.InputPortA)
{
comp.StateA = state;
_appearance.SetData(uid, LogicGateVisuals.InputA, state == SignalState.High); //If A == High => Sets input A sprite to True
}
else if (args.Port == comp.InputPortB)
{
comp.StateB = state;
_appearance.SetData(uid, LogicGateVisuals.InputB, state == SignalState.High); //If B == High => Sets input B sprite to True
}
UpdateOutput(uid, comp);
@@ -143,6 +145,8 @@ public sealed class LogicGateSystem : EntitySystem
break;
}
_appearance.SetData(uid, LogicGateVisuals.Output, output);
// only send a payload if it actually changed
if (output != comp.LastOutput)
{

View File

@@ -18,7 +18,7 @@ using JetBrains.Annotations;
using Robust.Server.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Map.Events;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -66,6 +66,42 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
SubscribeLocalEvent<NetworkConfiguratorComponent, BoundUserInterfaceCheckRangeEvent>(OnUiRangeCheck);
SubscribeLocalEvent<DeviceListComponent, ComponentRemove>(OnComponentRemoved);
SubscribeLocalEvent<BeforeSaveEvent>(OnMapSave);
}
private void OnMapSave(BeforeSaveEvent ev)
{
var enumerator = AllEntityQuery<NetworkConfiguratorComponent>();
while (enumerator.MoveNext(out var uid, out var conf))
{
if (CompOrNull<TransformComponent>(conf.ActiveDeviceList)?.MapUid != ev.Map)
continue;
// The linked device list is (probably) being saved. Make sure that the configurator is also being saved
// (i.e., not in the hands of a mapper/ghost). In the future, map saving should raise a separate event
// containing a set of all entities that are about to be saved, which would make checking this much easier.
// This is a shitty bandaid, and will force close the UI during auto-saves.
// TODO Map serialization refactor
var xform = Transform(uid);
if (xform.MapUid == ev.Map && IsSaveable(uid))
continue;
_uiSystem.CloseUi(uid, NetworkConfiguratorUiKey.Configure);
DebugTools.AssertNull(conf.ActiveDeviceList);
}
bool IsSaveable(EntityUid uid)
{
while (uid.IsValid())
{
if (Prototype(uid)?.MapSavable == false)
return false;
uid = Transform(uid).ParentUid;
}
return true;
}
}
private void OnUiRangeCheck(Entity<NetworkConfiguratorComponent> ent, ref BoundUserInterfaceCheckRangeEvent args)
@@ -485,6 +521,9 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
if (!TryComp(targetUid, out DeviceListComponent? list))
return;
if (TryComp(configurator.ActiveDeviceList, out DeviceListComponent? oldList))
oldList.Configurators.Remove(configuratorUid);
list.Configurators.Add(configuratorUid);
configurator.ActiveDeviceList = targetUid;
Dirty(configuratorUid, configurator);
@@ -758,7 +797,7 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
{
if (query.TryGetComponent(device, out var comp))
{
component.Devices[addr] = device;
component.Devices.Add(addr, device);
comp.Configurators.Add(uid);
}
}

View File

@@ -28,6 +28,7 @@ public sealed partial class DragonSystem : EntitySystem
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
private EntityQuery<CarpRiftsConditionComponent> _objQuery;
@@ -156,7 +157,7 @@ public sealed partial class DragonSystem : EntitySystem
}
// cant put a rift on solars
foreach (var tile in grid.GetTilesIntersecting(new Circle(_transform.GetWorldPosition(xform), RiftTileRadius), false))
foreach (var tile in _map.GetTilesIntersecting(xform.GridUid.Value, grid, new Circle(_transform.GetWorldPosition(xform), RiftTileRadius), false))
{
if (!tile.IsSpace(_tileDef))
continue;

View File

@@ -121,7 +121,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
activated.TimeLeft -= frameTime;
if (activated.TimeLeft <= 0 || !IsPowered(uid, electrified, transform))
{
_appearance.SetData(uid, ElectrifiedVisuals.IsPowered, false);
_appearance.SetData(uid, ElectrifiedVisuals.ShowSparks, false);
RemComp<ActivatedElectrifiedComponent>(uid);
}
}
@@ -217,7 +217,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
return false;
EnsureComp<ActivatedElectrifiedComponent>(uid);
_appearance.SetData(uid, ElectrifiedVisuals.IsPowered, true);
_appearance.SetData(uid, ElectrifiedVisuals.ShowSparks, true);
siemens *= electrified.SiemensCoefficient;
if (!DoCommonElectrocutionAttempt(targetUid, uid, ref siemens) || siemens <= 0)
@@ -488,15 +488,4 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
}
_audio.PlayPvs(electrified.ShockNoises, targetUid, AudioParams.Default.WithVolume(electrified.ShockVolume));
}
public void SetElectrifiedWireCut(Entity<ElectrifiedComponent> ent, bool value)
{
if (ent.Comp.IsWireCut == value)
{
return;
}
ent.Comp.IsWireCut = value;
Dirty(ent);
}
}

View File

@@ -134,13 +134,6 @@ public sealed class DrainSystem : SharedDrainSystem
}
drain.Accumulator -= drain.DrainFrequency;
// Disable ambient sound from emptying manually
if (!drain.AutoDrain)
{
_ambientSoundSystem.SetAmbience(uid, false);
continue;
}
if (!managerQuery.TryGetComponent(uid, out var manager))
continue;
@@ -160,41 +153,44 @@ public sealed class DrainSystem : SharedDrainSystem
// This will ensure that UnitsPerSecond is per second...
var amount = drain.UnitsPerSecond * drain.DrainFrequency;
_puddles.Clear();
_lookup.GetEntitiesInRange(Transform(uid).Coordinates, drain.Range, _puddles);
if (_puddles.Count == 0)
if (drain.AutoDrain)
{
_ambientSoundSystem.SetAmbience(uid, false);
continue;
}
_puddles.Clear();
_lookup.GetEntitiesInRange(Transform(uid).Coordinates, drain.Range, _puddles);
_ambientSoundSystem.SetAmbience(uid, true);
amount /= _puddles.Count;
foreach (var puddle in _puddles)
{
// Queue the solution deletion if it's empty. EvaporationSystem might also do this
// but queuedelete should be pretty safe.
if (!_solutionContainerSystem.ResolveSolution(puddle.Owner, puddle.Comp.SolutionName, ref puddle.Comp.Solution, out var puddleSolution))
if (_puddles.Count == 0)
{
EntityManager.QueueDeleteEntity(puddle);
_ambientSoundSystem.SetAmbience(uid, false);
continue;
}
// Removes the lowest of:
// the drain component's units per second adjusted for # of puddles
// the puddle's remaining volume (making it cleanly zero)
// the drain's remaining volume in its buffer.
var transferSolution = _solutionContainerSystem.SplitSolution(puddle.Comp.Solution.Value,
FixedPoint2.Min(FixedPoint2.New(amount), puddleSolution.Volume, drainSolution.AvailableVolume));
_ambientSoundSystem.SetAmbience(uid, true);
drainSolution.AddSolution(transferSolution, _prototypeManager);
amount /= _puddles.Count;
if (puddleSolution.Volume <= 0)
foreach (var puddle in _puddles)
{
QueueDel(puddle);
// Queue the solution deletion if it's empty. EvaporationSystem might also do this
// but queuedelete should be pretty safe.
if (!_solutionContainerSystem.ResolveSolution(puddle.Owner, puddle.Comp.SolutionName, ref puddle.Comp.Solution, out var puddleSolution))
{
EntityManager.QueueDeleteEntity(puddle);
continue;
}
// Removes the lowest of:
// the drain component's units per second adjusted for # of puddles
// the puddle's remaining volume (making it cleanly zero)
// the drain's remaining volume in its buffer.
var transferSolution = _solutionContainerSystem.SplitSolution(puddle.Comp.Solution.Value,
FixedPoint2.Min(FixedPoint2.New(amount), puddleSolution.Volume, drainSolution.AvailableVolume));
drainSolution.AddSolution(transferSolution, _prototypeManager);
if (puddleSolution.Volume <= 0)
{
QueueDel(puddle);
}
}
}

View File

@@ -1,7 +1,9 @@
using Content.Server.Popups;
using Content.Shared.Administration;
using Content.Shared.GameTicking;
using Content.Shared.Mind;
using Robust.Shared.Console;
using Content.Server.GameTicking;
namespace Content.Server.Ghost
{
@@ -23,6 +25,14 @@ namespace Content.Server.Ghost
return;
}
var gameTicker = _entities.System<GameTicker>();
if (!gameTicker.PlayerGameStatuses.TryGetValue(player.UserId, out var playerStatus) ||
playerStatus is not PlayerGameStatus.JoinedGame)
{
shell.WriteLine("ghost-command-error-lobby");
return;
}
if (player.AttachedEntity is { Valid: true } frozen &&
_entities.HasComponent<AdminFrozenComponent>(frozen))
{

View File

@@ -1,3 +1,5 @@
using System.Linq;
using System.Numerics;
using Content.Server.Administration.Logs;
using Content.Server.Chat.Managers;
using Content.Server.GameTicking;
@@ -32,14 +34,13 @@ using Robust.Shared.Physics.Systems;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using System.Linq;
using System.Numerics;
namespace Content.Server.Ghost
{
public sealed class GhostSystem : SharedGhostSystem
{
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly IAdminLogManager _adminLog = default!;
[Dependency] private readonly SharedEyeSystem _eye = default!;
[Dependency] private readonly FollowerSystem _followerSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
@@ -334,6 +335,8 @@ namespace Content.Server.Ghost
private void WarpTo(EntityUid uid, EntityUid target)
{
_adminLog.Add(LogType.GhostWarp, $"{ToPrettyString(uid)} ghost warped to {ToPrettyString(target)}");
if ((TryComp(target, out WarpPointComponent? warp) && warp.Follow) || HasComp<MobStateComponent>(target))
{
_followerSystem.StartFollowingEntity(uid, target);

View File

@@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using Content.Server.Administration;
using Content.Server.Ghost.Roles.Components;
using Content.Server.Ghost.Roles.Raffles;
@@ -77,13 +77,13 @@ namespace Content.Server.Ghost.Roles
if (isProto)
{
if (!_protoManager.TryIndex<GhostRoleRaffleSettingsPrototype>(args[4], out var proto))
if (!_protoManager.TryIndex<GhostRoleRaffleSettingsPrototype>(args[3], out var proto))
{
var validProtos = string.Join(", ",
_protoManager.EnumeratePrototypes<GhostRoleRaffleSettingsPrototype>().Select(p => p.ID)
);
shell.WriteLine($"{args[4]} is not a valid raffle settings prototype. Valid options: {validProtos}");
shell.WriteLine($"{args[3]} is not a valid raffle settings prototype. Valid options: {validProtos}");
return;
}

View File

@@ -9,7 +9,6 @@ using Content.Shared.Light;
using Content.Shared.Light.Components;
using Content.Shared.Rounding;
using Content.Shared.Toggleable;
using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
@@ -46,7 +45,6 @@ namespace Content.Server.Light.EntitySystems
SubscribeLocalEvent<HandheldLightComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<HandheldLightComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<HandheldLightComponent, GetVerbsEvent<ActivationVerb>>(AddToggleLightVerb);
SubscribeLocalEvent<HandheldLightComponent, ActivateInWorldEvent>(OnActivate);
@@ -179,25 +177,7 @@ namespace Content.Server.Light.EntitySystems
}
}
private void AddToggleLightVerb(Entity<HandheldLightComponent> ent, ref GetVerbsEvent<ActivationVerb> args)
{
if (!args.CanAccess || !args.CanInteract || !ent.Comp.ToggleOnInteract)
return;
var @event = args;
ActivationVerb verb = new()
{
Text = Loc.GetString("verb-common-toggle-light"),
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/light.svg.192dpi.png")),
Act = ent.Comp.Activated
? () => TurnOff(ent)
: () => TurnOn(@event.User, ent)
};
args.Verbs.Add(verb);
}
public bool TurnOff(Entity<HandheldLightComponent> ent, bool makeNoise = true)
public override bool TurnOff(Entity<HandheldLightComponent> ent, bool makeNoise = true)
{
if (!ent.Comp.Activated || !_lights.TryGetLight(ent, out var pointLightComponent))
{
@@ -211,7 +191,7 @@ namespace Content.Server.Light.EntitySystems
return true;
}
public bool TurnOn(EntityUid user, Entity<HandheldLightComponent> uid)
public override bool TurnOn(EntityUid user, Entity<HandheldLightComponent> uid)
{
var component = uid.Comp;
if (component.Activated || !_lights.TryGetLight(uid, out var pointLightComponent))

View File

@@ -40,6 +40,7 @@ public sealed class NukeSystem : EntitySystem
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
[Dependency] private readonly AppearanceSystem _appearance = default!;
@@ -190,7 +191,7 @@ public sealed class NukeSystem : EntitySystem
var worldPos = _transform.GetWorldPosition(xform);
foreach (var tile in grid.GetTilesIntersecting(new Circle(worldPos, component.RequiredFloorRadius), false))
foreach (var tile in _map.GetTilesIntersecting(xform.GridUid.Value, grid, new Circle(worldPos, component.RequiredFloorRadius), false))
{
if (!tile.IsSpace(_tileDefManager))
continue;

View File

@@ -18,8 +18,11 @@ public sealed partial class CableComponent : Component
[DataField]
public EntProtoId CableDroppedOnCutPrototype = "CableHVStack1";
/// <summary>
/// The tool quality needed to cut the cable. Setting to null prevents cutting.
/// </summary>
[DataField]
public ProtoId<ToolQualityPrototype> CuttingQuality = SharedToolSystem.CutQuality;
public ProtoId<ToolQualityPrototype>? CuttingQuality = SharedToolSystem.CutQuality;
/// <summary>
/// Checked by <see cref="CablePlacerComponent"/> to determine if there is

View File

@@ -35,7 +35,10 @@ public sealed partial class CableSystem : EntitySystem
if (args.Handled)
return;
args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, cable.CuttingDelay, cable.CuttingQuality, new CableCuttingFinishedEvent());
if (cable.CuttingQuality != null)
{
args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, cable.CuttingDelay, cable.CuttingQuality, new CableCuttingFinishedEvent());
}
}
private void OnCableCut(EntityUid uid, CableComponent cable, DoAfterEvent args)

View File

@@ -17,7 +17,7 @@ public sealed partial class PowerWireAction : BaseWireAction
[DataField("pulseTimeout")]
private int _pulseTimeout = 30;
private ElectrocutionSystem _electrocutionSystem = default!;
private ElectrocutionSystem _electrocution = default!;
public override object StatusKey { get; } = PowerWireActionKey.Status;
@@ -105,8 +105,8 @@ public sealed partial class PowerWireAction : BaseWireAction
&& !EntityManager.TryGetComponent(used, out electrified))
return;
_electrocutionSystem.SetElectrifiedWireCut((used, electrified), setting);
electrified.Enabled = setting;
_electrocution.SetElectrifiedWireCut((used, electrified), setting);
_electrocution.SetElectrified((used, electrified), setting);
}
/// <returns>false if failed, true otherwise, or if the entity cannot be electrified</returns>
@@ -120,7 +120,7 @@ public sealed partial class PowerWireAction : BaseWireAction
// always set this to true
SetElectrified(wire.Owner, true, electrified);
var electrifiedAttempt = _electrocutionSystem.TryDoElectrifiedAct(wire.Owner, user);
var electrifiedAttempt = _electrocution.TryDoElectrifiedAct(wire.Owner, user);
// if we were electrified, then return false
return !electrifiedAttempt;
@@ -161,7 +161,7 @@ public sealed partial class PowerWireAction : BaseWireAction
{
base.Initialize();
_electrocutionSystem = EntityManager.System<ElectrocutionSystem>();
_electrocution = EntityManager.System<ElectrocutionSystem>();
}
// This should add a wire into the entity's state, whether it be

View File

@@ -58,7 +58,7 @@ public sealed partial class DungeonJob
}
}
if (tile is not null && biomeSystem.TryGetEntity(node, indexedBiome.Layers, tile.Value, seed, _grid, out var entityProto))
if (biomeSystem.TryGetEntity(node, indexedBiome.Layers, tile ?? tileRef.Value.Tile, seed, _grid, out var entityProto))
{
var ent = _entManager.SpawnEntity(entityProto, new EntityCoordinates(_gridUid, node + _grid.TileSizeHalfVector));
var xform = xformQuery.Get(ent);

View File

@@ -20,7 +20,11 @@ public sealed partial class DungeonJob
}
var tileDef = _prototype.Index(tileProto);
data.SpawnGroups.TryGetValue(DungeonDataKey.WallMounts, out var spawnProto);
if (!data.SpawnGroups.TryGetValue(DungeonDataKey.WallMounts, out var spawnProto))
{
// caves can have no walls
return;
}
var checkedTiles = new HashSet<Vector2i>();
var allExterior = new HashSet<Vector2i>(dungeon.CorridorExteriorTiles);

View File

@@ -45,7 +45,6 @@ public sealed class ProjectileSystem : SharedProjectileSystem
RaiseLocalEvent(uid, ref ev);
var otherName = ToPrettyString(target);
var direction = args.OurBody.LinearVelocity.Normalized();
var modifiedDamage = _damageableSystem.TryChangeDamage(target, ev.Damage, component.IgnoreResistances, origin: component.Shooter);
var deleted = Deleted(target);
@@ -64,7 +63,9 @@ public sealed class ProjectileSystem : SharedProjectileSystem
if (!deleted)
{
_guns.PlayImpactSound(target, modifiedDamage, component.SoundHit, component.ForceSound);
_sharedCameraRecoil.KickCamera(target, direction);
if (!args.OurBody.LinearVelocity.IsLengthZero())
_sharedCameraRecoil.KickCamera(target, args.OurBody.LinearVelocity.Normalized());
}
component.DamagedEntity = true;

View File

@@ -21,6 +21,7 @@ public sealed class SpecialRespawnSystem : SharedSpecialRespawnSystem
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly TurfSystem _turf = default!;
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
@@ -105,7 +106,7 @@ public sealed class SpecialRespawnSystem : SharedSpecialRespawnSystem
var found = false;
foreach (var tile in grid.GetTilesIntersecting(circle))
foreach (var tile in _map.GetTilesIntersecting(entityGridUid.Value, grid, circle))
{
if (tile.IsSpace(_tileDefinitionManager)
|| _turf.IsTileBlocked(tile, CollisionGroup.MobMask)
@@ -176,7 +177,7 @@ public sealed class SpecialRespawnSystem : SharedSpecialRespawnSystem
var mapTarget = grid.WorldToTile(mapPos);
var circle = new Circle(mapPos, 2);
foreach (var newTileRef in grid.GetTilesIntersecting(circle))
foreach (var newTileRef in _map.GetTilesIntersecting(targetGrid, grid, circle))
{
if (newTileRef.IsSpace(_tileDefinitionManager) || _turf.IsTileBlocked(newTileRef, CollisionGroup.MobMask) || !_atmosphere.IsTileMixtureProbablySafe(targetGrid, targetMap, mapTarget))
continue;

View File

@@ -43,6 +43,7 @@ public sealed partial class RevenantSystem
[Dependency] private readonly TileSystem _tile = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
private void InitializeAbilities()
{
@@ -228,8 +229,12 @@ public sealed partial class RevenantSystem
var xform = Transform(uid);
if (!TryComp<MapGridComponent>(xform.GridUid, out var map))
return;
var tiles = map.GetTilesIntersecting(Box2.CenteredAround(_transformSystem.GetWorldPosition(xform),
new Vector2(component.DefileRadius * 2, component.DefileRadius))).ToArray();
var tiles = _mapSystem.GetTilesIntersecting(
xform.GridUid.Value,
map,
Box2.CenteredAround(_transformSystem.GetWorldPosition(xform),
new Vector2(component.DefileRadius * 2, component.DefileRadius)))
.ToArray();
_random.Shuffle(tiles);

View File

@@ -193,7 +193,7 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
List<Vector2i> reservedTiles = new();
foreach (var tile in grid.GetTilesIntersecting(new Circle(Vector2.Zero, landingPadRadius), false))
foreach (var tile in _map.GetTilesIntersecting(mapUid, grid, new Circle(Vector2.Zero, landingPadRadius), false))
{
if (!_biome.TryGetBiomeTile(mapUid, grid, tile.GridIndices, out _))
continue;

View File

@@ -253,12 +253,6 @@ public sealed partial class ShuttleSystem
if (TryComp<PhysicsComponent>(shuttleUid, out var shuttlePhysics))
{
// Static physics type is set when station anchor is enabled
if (shuttlePhysics.BodyType == BodyType.Static)
{
reason = Loc.GetString("shuttle-console-static");
return false;
}
// Too large to FTL
if (FTLMassLimit > 0 && shuttlePhysics.Mass > FTLMassLimit)
@@ -1027,6 +1021,7 @@ public sealed partial class ShuttleSystem
continue;
}
// If it has the FTLSmashImmuneComponent ignore it.
if (_immuneQuery.HasComponent(ent))
{
continue;

View File

@@ -0,0 +1,37 @@
using Content.Shared.Silicons.Laws.Components;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
namespace Content.Server.Silicons.Laws;
/// <summary>
/// This handles running the ion storm event a on specific entity when that entity is spawned in.
/// </summary>
public sealed class StartIonStormedSystem : EntitySystem
{
[Dependency] private readonly IonStormSystem _ionStorm = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly SiliconLawSystem _siliconLaw = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StartIonStormedComponent, MapInitEvent>(OnMapInit);
}
private void OnMapInit(Entity<StartIonStormedComponent> ent, ref MapInitEvent args)
{
if (!TryComp<SiliconLawBoundComponent>(ent.Owner, out var lawBound))
return;
if (!TryComp<IonStormTargetComponent>(ent.Owner, out var target))
return;
for (int currentIonStorm = 0; currentIonStorm < ent.Comp.IonStormAmount; currentIonStorm++)
{
_ionStorm.IonStormTarget((ent.Owner, lawBound, target), false);
}
var laws = _siliconLaw.GetLaws(ent.Owner, lawBound);
_adminLogger.Add(LogType.Mind, LogImpact.High, $"{ToPrettyString(ent.Owner):silicon} spawned with ion stormed laws: {laws.LoggingString()}");
}
}

View File

@@ -308,7 +308,7 @@ public sealed class EventHorizonSystem : SharedEventHorizonSystem
foreach (var grid in grids)
{
// TODO: Remover grid.Owner when this iterator returns entityuids as well.
AttemptConsumeTiles(uid, grid.Comp.GetTilesIntersecting(circle), grid, grid, eventHorizon);
AttemptConsumeTiles(uid, _mapSystem.GetTilesIntersecting(grid.Owner, grid.Comp, circle), grid, grid, eventHorizon);
}
}

View File

@@ -0,0 +1,38 @@
using Content.Server.StationEvents.Events;
using Content.Shared.Access;
using Content.Shared.Destructible.Thresholds;
using Robust.Shared.Prototypes;
namespace Content.Server.StationEvents.Components;
/// <summary>
/// Greytide Virus event specific configuration
/// </summary>
[RegisterComponent, Access(typeof(GreytideVirusRule))]
public sealed partial class GreytideVirusRuleComponent : Component
{
/// <summary>
/// Range from which the severity is randomly picked from.
/// </summary>
[DataField]
public MinMax SeverityRange = new(1, 3);
/// <summary>
/// Severity corresponding to the number of access groups affected.
/// Will pick randomly from the SeverityRange if not specified.
/// </summary>
[DataField]
public int? Severity;
/// <summary>
/// Access groups to pick from.
/// </summary>
[DataField]
public List<ProtoId<AccessGroupPrototype>> AccessGroups = new();
/// <summary>
/// Entities with this access level will be ignored.
/// </summary>
[DataField]
public List<ProtoId<AccessLevelPrototype>> Blacklist = new();
}

View File

@@ -148,20 +148,20 @@ public sealed class EventManagerSystem : EntitySystem
return null;
}
var sumOfWeights = 0;
var sumOfWeights = 0.0f;
foreach (var stationEvent in availableEvents.Values)
{
sumOfWeights += (int) stationEvent.Weight;
sumOfWeights += stationEvent.Weight;
}
sumOfWeights = _random.Next(sumOfWeights);
sumOfWeights = _random.NextFloat(sumOfWeights);
foreach (var (proto, stationEvent) in availableEvents)
{
sumOfWeights -= (int) stationEvent.Weight;
sumOfWeights -= stationEvent.Weight;
if (sumOfWeights <= 0)
if (sumOfWeights <= 0.0f)
{
return proto.ID;
}

View File

@@ -0,0 +1,96 @@
using Content.Server.StationEvents.Components;
using Content.Shared.Access;
using Content.Shared.Access.Systems;
using Content.Shared.Access.Components;
using Content.Shared.Doors.Components;
using Content.Shared.Doors.Systems;
using Content.Shared.Lock;
using Content.Shared.GameTicking.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.StationEvents.Events;
/// <summary>
/// Greytide Virus event
/// This will open and bolt airlocks and unlock lockers from randomly selected access groups.
/// </summary>
public sealed class GreytideVirusRule : StationEventSystem<GreytideVirusRuleComponent>
{
[Dependency] private readonly AccessReaderSystem _access = default!;
[Dependency] private readonly SharedDoorSystem _door = default!;
[Dependency] private readonly LockSystem _lock = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IRobustRandom _random = default!;
protected override void Added(EntityUid uid, GreytideVirusRuleComponent virusComp, GameRuleComponent gameRule, GameRuleAddedEvent args)
{
if (!TryComp<StationEventComponent>(uid, out var stationEvent))
return;
// pick severity randomly from range if not specified otherwise
virusComp.Severity ??= virusComp.SeverityRange.Next(_random);
virusComp.Severity = Math.Min(virusComp.Severity.Value, virusComp.AccessGroups.Count);
stationEvent.StartAnnouncement = Loc.GetString("station-event-greytide-virus-start-announcement", ("severity", virusComp.Severity.Value));
base.Added(uid, virusComp, gameRule, args);
}
protected override void Started(EntityUid uid, GreytideVirusRuleComponent virusComp, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, virusComp, gameRule, args);
if (virusComp.Severity == null)
return;
// pick random access groups
var chosen = _random.GetItems(virusComp.AccessGroups, virusComp.Severity.Value, allowDuplicates: false);
// combine all the selected access groups
var accessIds = new HashSet<ProtoId<AccessLevelPrototype>>();
foreach (var group in chosen)
{
if (_prototype.TryIndex(group, out var proto))
accessIds.UnionWith(proto.Tags);
}
var firelockQuery = GetEntityQuery<FirelockComponent>();
var accessQuery = GetEntityQuery<AccessReaderComponent>();
var lockQuery = AllEntityQuery<LockComponent>();
while (lockQuery.MoveNext(out var lockUid, out var lockComp))
{
if (!accessQuery.TryComp(lockUid, out var accessComp))
continue;
// check access
// the AreAccessTagsAllowed function is a little weird because it technically has support for certain tags to be locked out of opening something
// which might have unintened side effects (see the comments in the function itself)
// but no one uses that yet, so it is fine for now
if (!_access.AreAccessTagsAllowed(accessIds, accessComp) || _access.AreAccessTagsAllowed(virusComp.Blacklist, accessComp))
continue;
// open lockers
_lock.Unlock(lockUid, null, lockComp);
}
var airlockQuery = AllEntityQuery<AirlockComponent, DoorComponent>();
while (airlockQuery.MoveNext(out var airlockUid, out var airlockComp, out var doorComp))
{
// don't space everything
if (firelockQuery.HasComp(airlockUid))
continue;
// use the access reader from the door electronics if they exist
if (!_access.GetMainAccessReader(airlockUid, out var accessComp))
continue;
// check access
if (!_access.AreAccessTagsAllowed(accessIds, accessComp) || _access.AreAccessTagsAllowed(virusComp.Blacklist, accessComp))
continue;
// open and bolt airlocks
_door.TryOpenAndBolt(airlockUid, doorComp, airlockComp);
}
}
}

View File

@@ -38,6 +38,7 @@ namespace Content.Server.VendingMachines
[Dependency] private readonly ThrowingSystem _throwingSystem = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SpeakOnUIClosedSystem _speakOnUIClosed = default!;
[Dependency] private readonly SharedPointLightSystem _light = default!;
private const float WallVendEjectDistanceFromWall = 1f;
@@ -334,6 +335,12 @@ namespace Content.Server.VendingMachines
finalState = VendingMachineVisualState.Off;
}
if (_light.TryGetLight(uid, out var pointlight))
{
var lightState = finalState != VendingMachineVisualState.Broken && finalState != VendingMachineVisualState.Off;
_light.SetEnabled(uid, lightState, pointlight);
}
_appearanceSystem.SetData(uid, VendingMachineVisuals.VisualState, finalState);
}

View File

@@ -22,13 +22,14 @@ public abstract partial class ComponentWireAction<TComponent> : BaseWireAction w
public override bool Cut(EntityUid user, Wire wire)
{
base.Cut(user, wire);
return EntityManager.TryGetComponent(wire.Owner, out TComponent? component) && Cut(user, wire, component);
// if the entity doesn't exist, we need to return true otherwise the wire sprite is never updated
return EntityManager.TryGetComponent(wire.Owner, out TComponent? component) ? Cut(user, wire, component) : true;
}
public override bool Mend(EntityUid user, Wire wire)
{
base.Mend(user, wire);
return EntityManager.TryGetComponent(wire.Owner, out TComponent? component) && Mend(user, wire, component);
return EntityManager.TryGetComponent(wire.Owner, out TComponent? component) ? Mend(user, wire, component) : true;
}
public override void Pulse(EntityUid user, Wire wire)

View File

@@ -4,7 +4,6 @@ using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
using Content.Shared.Maps;
using Content.Shared.Physics;
using Content.Shared.Throwing;
using Robust.Server.GameObjects;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Random;
@@ -17,7 +16,8 @@ public sealed class ThrowArtifactSystem : EntitySystem
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly TileSystem _tile = default!;
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
/// <inheritdoc/>
public override void Initialize()
@@ -30,7 +30,9 @@ public sealed class ThrowArtifactSystem : EntitySystem
var xform = Transform(uid);
if (TryComp<MapGridComponent>(xform.GridUid, out var grid))
{
var tiles = grid.GetTilesIntersecting(
var tiles = _mapSystem.GetTilesIntersecting(
xform.GridUid.Value,
grid,
Box2.CenteredAround(_transform.GetWorldPosition(xform), new Vector2(component.Range * 2, component.Range)));
foreach (var tile in tiles)