Merge branch 'master' into 2020-08-19-firelocks

# Conflicts:
#	Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs
#	Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs
#	SpaceStation14.sln.DotSettings
This commit is contained in:
Víctor Aguilera Puerto
2020-08-28 14:35:45 +02:00
494 changed files with 11527 additions and 3829 deletions

View File

@@ -1,4 +1,5 @@
using Content.Server.AI.Utility;
#nullable enable
using Content.Server.AI.Utility;
using Content.Server.AI.WorldState.States.Inventory;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.Utility;
@@ -34,7 +35,7 @@ namespace Content.Server.AI.Operators.Inventory
return Outcome.Failed;
}
if (!container.Owner.TryGetComponent(out EntityStorageComponent storageComponent) ||
if (!container.Owner.TryGetComponent(out EntityStorageComponent? storageComponent) ||
storageComponent.IsWeldedShut)
{
return Outcome.Failed;

View File

@@ -1,3 +1,4 @@
#nullable enable
using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.EntitySystems.Click;
@@ -20,11 +21,9 @@ namespace Content.Server.AI.Operators.Inventory
_target = target;
}
// TODO: When I spawn new entities they seem to duplicate clothing or something?
public override Outcome Execute(float frameTime)
{
if (_target == null ||
_target.Deleted ||
if (_target.Deleted ||
!_target.HasComponent<ItemComponent>() ||
ContainerHelpers.IsInContainer(_target) ||
!InteractionChecks.InRangeUnobstructed(_owner, _target.Transform.MapPosition))
@@ -32,7 +31,7 @@ namespace Content.Server.AI.Operators.Inventory
return Outcome.Failed;
}
if (!_owner.TryGetComponent(out HandsComponent handsComponent))
if (!_owner.TryGetComponent(out HandsComponent? handsComponent))
{
return Outcome.Failed;
}

View File

@@ -1,3 +1,4 @@
#nullable enable
using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Storage;
using Robust.Shared.Interfaces.GameObjects;
@@ -7,12 +8,12 @@ namespace Content.Server.AI.Operators.Inventory
/// <summary>
/// Will find the item in storage, put it in an active hand, then use it
/// </summary>
public class UseItemInHandsOperator : AiOperator
public class UseItemInInventoryOperator : AiOperator
{
private readonly IEntity _owner;
private readonly IEntity _target;
public UseItemInHandsOperator(IEntity owner, IEntity target)
public UseItemInInventoryOperator(IEntity owner, IEntity target)
{
_owner = owner;
_target = target;
@@ -20,18 +21,13 @@ namespace Content.Server.AI.Operators.Inventory
public override Outcome Execute(float frameTime)
{
if (_target == null)
{
return Outcome.Failed;
}
// TODO: Also have this check storage a la backpack etc.
if (!_owner.TryGetComponent(out HandsComponent handsComponent))
if (!_owner.TryGetComponent(out HandsComponent? handsComponent))
{
return Outcome.Failed;
}
if (!_target.TryGetComponent(out ItemComponent itemComponent))
if (!_target.TryGetComponent(out ItemComponent? itemComponent))
{
return Outcome.Failed;
}

View File

@@ -16,12 +16,20 @@ namespace Content.Server.AI.Operators.Movement
public float ArrivalDistance { get; }
public float PathfindingProximity { get; }
public MoveToEntityOperator(IEntity owner, IEntity target, float arrivalDistance = 1.0f, float pathfindingProximity = 1.5f)
private bool _requiresInRangeUnobstructed;
public MoveToEntityOperator(
IEntity owner,
IEntity target,
float arrivalDistance = 1.0f,
float pathfindingProximity = 1.5f,
bool requiresInRangeUnobstructed = false)
{
_owner = owner;
_target = target;
ArrivalDistance = arrivalDistance;
PathfindingProximity = pathfindingProximity;
_requiresInRangeUnobstructed = requiresInRangeUnobstructed;
}
public override bool TryStartup()
@@ -32,7 +40,7 @@ namespace Content.Server.AI.Operators.Movement
}
var steering = EntitySystem.Get<AiSteeringSystem>();
_request = new EntityTargetSteeringRequest(_target, ArrivalDistance, PathfindingProximity);
_request = new EntityTargetSteeringRequest(_target, ArrivalDistance, PathfindingProximity, _requiresInRangeUnobstructed);
steering.Register(_owner, _request);
return true;
}

View File

@@ -0,0 +1,73 @@
#nullable enable
using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.Components.Nutrition;
using Content.Shared.GameObjects.Components.Nutrition;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Random;
namespace Content.Server.AI.Operators.Nutrition
{
public class UseDrinkInInventoryOperator : AiOperator
{
private readonly IEntity _owner;
private readonly IEntity _target;
private float _interactionCooldown;
public UseDrinkInInventoryOperator(IEntity owner, IEntity target)
{
_owner = owner;
_target = target;
}
public override Outcome Execute(float frameTime)
{
if (_interactionCooldown >= 0)
{
_interactionCooldown -= frameTime;
return Outcome.Continuing;
}
// TODO: Also have this check storage a la backpack etc.
if (_target.Deleted ||
!_owner.TryGetComponent(out HandsComponent? handsComponent) ||
!_target.TryGetComponent(out ItemComponent? itemComponent))
{
return Outcome.Failed;
}
DrinkComponent? drinkComponent = null;
foreach (var slot in handsComponent.ActivePriorityEnumerable())
{
if (handsComponent.GetItem(slot) != itemComponent) continue;
handsComponent.ActiveHand = slot;
if (!_target.TryGetComponent(out drinkComponent))
{
return Outcome.Failed;
}
// This should also implicitly open it.
handsComponent.ActivateItem();
_interactionCooldown = IoCManager.Resolve<IRobustRandom>().NextFloat() + 0.5f;
}
if (drinkComponent == null)
{
return Outcome.Failed;
}
if (drinkComponent.Deleted ||
drinkComponent.Empty ||
_owner.TryGetComponent(out ThirstComponent? thirstComponent) &&
thirstComponent.CurrentThirst >= thirstComponent.ThirstThresholds[ThirstThreshold.Okay])
{
return Outcome.Success;
}
return Outcome.Continuing;
}
}
}

View File

@@ -0,0 +1,73 @@
#nullable enable
using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.Components.Nutrition;
using Content.Shared.GameObjects.Components.Nutrition;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Random;
namespace Content.Server.AI.Operators.Nutrition
{
public class UseFoodInInventoryOperator : AiOperator
{
private readonly IEntity _owner;
private readonly IEntity _target;
private float _interactionCooldown;
public UseFoodInInventoryOperator(IEntity owner, IEntity target)
{
_owner = owner;
_target = target;
}
public override Outcome Execute(float frameTime)
{
if (_interactionCooldown >= 0)
{
_interactionCooldown -= frameTime;
return Outcome.Continuing;
}
// TODO: Also have this check storage a la backpack etc.
if (_target.Deleted ||
!_owner.TryGetComponent(out HandsComponent? handsComponent) ||
!_target.TryGetComponent(out ItemComponent? itemComponent))
{
return Outcome.Failed;
}
FoodComponent? foodComponent = null;
foreach (var slot in handsComponent.ActivePriorityEnumerable())
{
if (handsComponent.GetItem(slot) != itemComponent) continue;
handsComponent.ActiveHand = slot;
if (!_target.TryGetComponent(out foodComponent))
{
return Outcome.Failed;
}
// This should also implicitly open it.
handsComponent.ActivateItem();
_interactionCooldown = IoCManager.Resolve<IRobustRandom>().NextFloat() + 0.5f;
}
if (foodComponent == null)
{
return Outcome.Failed;
}
if (_target.Deleted ||
foodComponent.UsesRemaining == 0 ||
_owner.TryGetComponent(out HungerComponent? hungerComponent) &&
hungerComponent.CurrentHunger >= hungerComponent.HungerThresholds[HungerThreshold.Okay])
{
return Outcome.Success;
}
return Outcome.Continuing;
}
}
}

View File

@@ -11,7 +11,7 @@ namespace Content.Server.AI.Operators.Sequences
{
Sequence = new Queue<AiOperator>(new AiOperator[]
{
new MoveToEntityOperator(owner, target),
new MoveToEntityOperator(owner, target, requiresInRangeUnobstructed: true),
new OpenStorageOperator(owner, target),
new PickupEntityOperator(owner, target),
});

View File

@@ -26,7 +26,7 @@ namespace Content.Server.AI.Utility.Actions.Clothing.Gloves
ActionOperators = new Queue<AiOperator>(new AiOperator[]
{
new EquipEntityOperator(Owner, _entity),
new UseItemInHandsOperator(Owner, _entity),
new UseItemInInventoryOperator(Owner, _entity),
});
}

View File

@@ -26,7 +26,7 @@ namespace Content.Server.AI.Utility.Actions.Clothing.Head
ActionOperators = new Queue<AiOperator>(new AiOperator[]
{
new EquipEntityOperator(Owner, _entity),
new UseItemInHandsOperator(Owner, _entity),
new UseItemInInventoryOperator(Owner, _entity),
});
}

View File

@@ -26,7 +26,7 @@ namespace Content.Server.AI.Utility.Actions.Clothing.OuterClothing
ActionOperators = new Queue<AiOperator>(new AiOperator[]
{
new EquipEntityOperator(Owner, _entity),
new UseItemInHandsOperator(Owner, _entity),
new UseItemInInventoryOperator(Owner, _entity),
});
}

View File

@@ -26,7 +26,7 @@ namespace Content.Server.AI.Utility.Actions.Clothing.Shoes
ActionOperators = new Queue<AiOperator>(new AiOperator[]
{
new EquipEntityOperator(Owner, _entity),
new UseItemInHandsOperator(Owner, _entity),
new UseItemInInventoryOperator(Owner, _entity),
});
}

View File

@@ -40,10 +40,6 @@ namespace Content.Server.AI.Utility.Actions.Nutrition.Drink
return new[]
{
considerationsManager.Get<FreeHandCon>()
.BoolCurve(context),
considerationsManager.Get<ThirstCon>()
.PresetCurve(context, PresetCurve.Nutrition),
considerationsManager.Get<TargetDistanceCon>()
.PresetCurve(context, PresetCurve.Distance),
considerationsManager.Get<DrinkValueCon>()

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Content.Server.AI.Operators;
using Content.Server.AI.Operators.Inventory;
using Content.Server.AI.Operators.Nutrition;
using Content.Server.AI.Utility.Considerations;
using Content.Server.AI.Utility.Considerations.Inventory;
using Content.Server.AI.Utility.Considerations.Nutrition.Drink;
@@ -27,7 +28,7 @@ namespace Content.Server.AI.Utility.Actions.Nutrition.Drink
ActionOperators = new Queue<AiOperator>(new AiOperator[]
{
new EquipEntityOperator(Owner, _entity),
new UseItemInHandsOperator(Owner, _entity),
new UseDrinkInInventoryOperator(Owner, _entity),
});
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Content.Server.AI.Operators;
using Content.Server.AI.Operators.Inventory;
using Content.Server.AI.Operators.Nutrition;
using Content.Server.AI.Utility.Considerations;
using Content.Server.AI.Utility.Considerations.Inventory;
using Content.Server.AI.Utility.Considerations.Nutrition.Food;
@@ -27,7 +28,7 @@ namespace Content.Server.AI.Utility.Actions.Nutrition.Food
ActionOperators = new Queue<AiOperator>(new AiOperator[]
{
new EquipEntityOperator(Owner, _entity),
new UseItemInHandsOperator(Owner, _entity),
new UseFoodInInventoryOperator(Owner, _entity),
});
}

View File

@@ -14,7 +14,7 @@ namespace Content.Server.AI.Utility.BehaviorSets
// TODO: Ideally long-term we should just store the weapons in backpack
new EquipMeleeExp(),
new PickUpMeleeWeaponExp(),
new MeleeAttackNearbyPlayerExp(),
new MeleeAttackNearbyExp(),
};
}
}

View File

@@ -11,7 +11,7 @@ namespace Content.Server.AI.Utility.Considerations.Combat
{
var target = context.GetState<TargetEntityState>().GetValue();
if (target == null || !target.TryGetComponent(out IDamageableComponent damageableComponent))
if (target == null || target.Deleted || !target.TryGetComponent(out IDamageableComponent damageableComponent))
{
return 0.0f;
}

View File

@@ -9,7 +9,7 @@ namespace Content.Server.AI.Utility.Considerations.Movement
{
var self = context.GetState<SelfState>().GetValue();
var target = context.GetState<TargetEntityState>().GetValue();
if (target == null || target.Transform.GridID != self.Transform.GridID)
if (target == null || target.Deleted || target.Transform.GridID != self.Transform.GridID)
{
return 0.0f;
}

View File

@@ -4,17 +4,19 @@ using Content.Server.AI.Utility.Actions;
using Content.Server.AI.Utility.Actions.Combat.Melee;
using Content.Server.AI.Utility.Considerations;
using Content.Server.AI.Utility.Considerations.Combat.Melee;
using Content.Server.AI.Utils;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.GameObjects.Components.AI;
using Content.Server.GameObjects.Components.Movement;
using Content.Shared.GameObjects.Components.Damage;
using Robust.Server.GameObjects;
using Content.Server.GameObjects.EntitySystems.AI;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
{
public sealed class MeleeAttackNearbyPlayerExp : ExpandableUtilityAction
public sealed class MeleeAttackNearbyExp : ExpandableUtilityAction
{
public override float Bonus => UtilityAction.CombatBonus;
@@ -37,13 +39,10 @@ namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
throw new InvalidOperationException();
}
foreach (var entity in Visibility.GetEntitiesInRange(owner.Transform.GridPosition, typeof(IDamageableComponent),
controller.VisionRadius))
foreach (var target in EntitySystem.Get<AiFactionTagSystem>()
.GetNearbyHostiles(owner, controller.VisionRadius))
{
if (entity.HasComponent<BasicActorComponent>() && entity != owner)
{
yield return new MeleeWeaponAttackEntity(owner, entity, Bonus);
}
yield return new MeleeWeaponAttackEntity(owner, target, Bonus);
}
}
}

View File

@@ -1,24 +0,0 @@
using System.Collections.Generic;
using Content.Server.AI.Utility.Actions;
using Content.Server.AI.Utility.Actions.Combat.Melee;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.AI.WorldState.States.Mobs;
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
{
public sealed class MeleeAttackNearbySpeciesExp : ExpandableUtilityAction
{
public override float Bonus => UtilityAction.CombatBonus;
public override IEnumerable<UtilityAction> GetActions(Blackboard context)
{
var owner = context.GetState<SelfState>().GetValue();
foreach (var entity in context.GetState<NearbyBodiesState>().GetValue())
{
yield return new MeleeWeaponAttackEntity(owner, entity, Bonus);
}
}
}
}

View File

@@ -8,8 +8,10 @@ using Content.Server.AI.Utils;
using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States;
using Content.Server.GameObjects.Components.Movement;
using Content.Server.GameObjects.EntitySystems.AI;
using Content.Shared.GameObjects.Components.Body;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.IoC;
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
@@ -37,13 +39,10 @@ namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
throw new InvalidOperationException();
}
foreach (var entity in Visibility.GetEntitiesInRange(owner.Transform.GridPosition, typeof(IBodyManagerComponent),
controller.VisionRadius))
foreach (var target in EntitySystem.Get<AiFactionTagSystem>()
.GetNearbyHostiles(owner, controller.VisionRadius))
{
if (entity.HasComponent<BasicActorComponent>() && entity != owner)
{
yield return new UnarmedAttackEntity(owner, entity, Bonus);
}
yield return new UnarmedAttackEntity(owner, target, Bonus);
}
}
}

View File

@@ -22,6 +22,12 @@ namespace Content.Server.Administration
}
var mind = player.ContentData().Mind;
if (mind == null)
{
shell.SendText(player, "You can't ghost here!");
return;
}
if (mind.VisitingEntity != null && mind.VisitingEntity.Prototype.ID == "AdminObserver")
{
var visiting = mind.VisitingEntity;

View File

@@ -1,9 +1,11 @@
#nullable enable
using System;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Atmos;
using Robust.Server.Interfaces.Console;
using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
@@ -58,7 +60,9 @@ namespace Content.Server.Atmos
public string Help => "listgases";
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
{
foreach (var gasPrototype in Atmospherics.Gases)
var atmosSystem = EntitySystem.Get<AtmosphereSystem>();
foreach (var gasPrototype in atmosSystem.Gases)
{
shell.SendText(player, $"{gasPrototype.Name} ID: {gasPrototype.ID}");
}

View File

@@ -0,0 +1,16 @@
using Robust.Shared.GameObjects;
namespace Content.Server.Atmos
{
public class FireActEvent : EntitySystemMessage
{
public float Temperature { get; }
public float Volume { get; }
public FireActEvent(float temperature, float volume)
{
Temperature = temperature;
Volume = volume;
}
}
}

View File

@@ -4,8 +4,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Content.Server.Atmos.Reactions;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Shared.Atmos;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
@@ -20,12 +22,17 @@ namespace Content.Server.Atmos
[Serializable]
public class GasMixture : IExposeData, IEquatable<GasMixture>, ICloneable
{
private readonly AtmosphereSystem _atmosphereSystem;
[ViewVariables]
private float[] _moles = new float[Atmospherics.TotalNumberOfGases];
[ViewVariables]
private float[] _molesArchived = new float[Atmospherics.TotalNumberOfGases];
[ViewVariables]
private float _temperature = Atmospherics.TCMB;
public IReadOnlyList<float> Gases => _moles;
[ViewVariables]
@@ -51,7 +58,7 @@ namespace Content.Server.Atmos
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
{
capacity += Atmospherics.GetGas(i).SpecificHeat * _moles[i];
capacity += _atmosphereSystem.GetGas(i).SpecificHeat * _moles[i];
}
return MathF.Max(capacity, Atmospherics.MinimumHeatCapacity);
@@ -68,7 +75,7 @@ namespace Content.Server.Atmos
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
{
capacity += Atmospherics.GetGas(i).SpecificHeat * _molesArchived[i];
capacity += _atmosphereSystem.GetGas(i).SpecificHeat * _molesArchived[i];
}
return MathF.Max(capacity, Atmospherics.MinimumHeatCapacity);
@@ -122,15 +129,21 @@ namespace Content.Server.Atmos
[ViewVariables]
public float Volume { get; set; }
public GasMixture()
public GasMixture() : this(null)
{
}
public GasMixture(float volume)
public GasMixture(AtmosphereSystem? atmosphereSystem)
{
_atmosphereSystem = atmosphereSystem ?? EntitySystem.Get<AtmosphereSystem>();
}
public GasMixture(float volume, AtmosphereSystem? atmosphereSystem = null)
{
if (volume < 0)
volume = 0;
Volume = volume;
_atmosphereSystem = atmosphereSystem ?? EntitySystem.Get<AtmosphereSystem>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -215,12 +228,12 @@ namespace Content.Server.Atmos
public GasMixture RemoveRatio(float ratio)
{
if(ratio <= 0)
return new GasMixture(Volume);
return new GasMixture(Volume, _atmosphereSystem);
if (ratio > 1)
ratio = 1;
var removed = new GasMixture {Volume = Volume, Temperature = Temperature};
var removed = new GasMixture(_atmosphereSystem) {Volume = Volume, Temperature = Temperature};
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
{
@@ -243,7 +256,7 @@ namespace Content.Server.Atmos
public void CopyFromMutable(GasMixture sample)
{
if (Immutable) return;
sample._moles.AsSpan().CopyTo(_moles.AsSpan());
sample._moles.CopyTo(_moles, 0);
Temperature = sample.Temperature;
}
@@ -274,7 +287,7 @@ namespace Content.Server.Atmos
if (!(MathF.Abs(delta) >= Atmospherics.GasMinMoles)) continue;
if (absTemperatureDelta > Atmospherics.MinimumTemperatureDeltaToConsider)
{
var gasHeatCapacity = delta * Atmospherics.GetGas(i).SpecificHeat;
var gasHeatCapacity = delta * _atmosphereSystem.GetGas(i).SpecificHeat;
if (delta > 0)
{
heatCapacityToSharer += gasHeatCapacity;
@@ -476,8 +489,7 @@ namespace Content.Server.Atmos
var temperature = Temperature;
var energy = ThermalEnergy;
// TODO ATMOS Take reaction priority into account!
foreach (var prototype in IoCManager.Resolve<IPrototypeManager>().EnumeratePrototypes<GasReactionPrototype>())
foreach (var prototype in _atmosphereSystem.GasReactions)
{
if (energy < prototype.MinimumEnergyRequirement ||
temperature < prototype.MinimumTemperatureRequirement)
@@ -499,7 +511,7 @@ namespace Content.Server.Atmos
if (!doReaction)
continue;
reaction = prototype.React(this, holder);
reaction = prototype.React(this, holder, _atmosphereSystem.EventBus);
if(reaction.HasFlag(ReactionResult.StopReactions))
break;
}
@@ -579,7 +591,7 @@ namespace Content.Server.Atmos
public object Clone()
{
var newMixture = new GasMixture()
var newMixture = new GasMixture(_atmosphereSystem)
{
_moles = (float[])_moles.Clone(),
_molesArchived = (float[])_molesArchived.Clone(),

View File

@@ -2,7 +2,6 @@
using Content.Server.Interfaces;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components;
using Content.Shared.GameObjects.Components.Pointing;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
@@ -20,10 +19,8 @@ namespace Content.Server.Atmos
[RegisterComponent]
public class GasSprayerComponent : Component, IAfterInteract
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[Dependency] private readonly IServerEntityManager _serverEntityManager = default!;
#pragma warning restore 649
//TODO: create a function that can create a gas based on a solution mix
public override string Name => "GasSprayer";

View File

@@ -1,6 +1,7 @@
#nullable enable
using System;
using Content.Server.GameObjects.Components.Atmos;
using Content.Shared.Atmos;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.Interfaces.Physics;
using Robust.Shared.Interfaces.Random;
@@ -24,7 +25,7 @@ namespace Content.Server.Atmos
private const float ProbabilityBasePercent = 10f;
private const float ThrowForce = 100f;
public void ExperiencePressureDifference(int cycle, float pressureDifference, Direction direction,
public void ExperiencePressureDifference(int cycle, float pressureDifference, AtmosDirection direction,
float pressureResistanceProbDelta, GridCoordinates throwTarget)
{
if (ControlledComponent == null)
@@ -54,14 +55,14 @@ namespace Content.Server.Atmos
if (throwTarget != GridCoordinates.InvalidGrid)
{
var moveForce = maxForce * MathHelper.Clamp(moveProb, 0, 100) / 150f;
var pos = ((throwTarget.Position - transform.GridPosition.Position).Normalized + direction.ToVec()).Normalized;
var pos = ((throwTarget.Position - transform.GridPosition.Position).Normalized + direction.ToDirection().ToVec()).Normalized;
LinearVelocity = pos * moveForce;
}
else
{
var moveForce = MathF.Min(maxForce * MathHelper.Clamp(moveProb, 0, 100) / 2500f, 20f);
LinearVelocity = direction.ToVec() * moveForce;
LinearVelocity = direction.ToDirection().ToVec() * moveForce;
}
pressureComponent.LastHighPressureMovementAirCycle = cycle;

View File

@@ -1,4 +1,6 @@
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Atmos.Piping;
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -12,11 +14,6 @@ namespace Content.Server.Atmos
/// </summary>
int UpdateCounter { get; }
/// <summary>
/// How many tiles have high pressure delta.
/// </summary>
int HighPressureDeltaCount { get; }
/// <summary>
/// Control variable for equalization.
/// </summary>
@@ -120,14 +117,14 @@ namespace Content.Server.Atmos
/// </summary>
/// <param name="indices"></param>
/// <returns></returns>
TileAtmosphere GetTile(MapIndices indices);
TileAtmosphere GetTile(MapIndices indices, bool createSpace = true);
/// <summary>
/// Returns a tile.
/// </summary>
/// <param name="coordinates"></param>
/// <returns></returns>
TileAtmosphere GetTile(GridCoordinates coordinates);
TileAtmosphere GetTile(GridCoordinates coordinates, bool createSpace = true);
/// <summary>
/// Returns if the tile in question is air-blocked.
@@ -158,5 +155,13 @@ namespace Content.Server.Atmos
Dictionary<Direction, TileAtmosphere> GetAdjacentTiles(MapIndices indices, bool includeAirBlocked = false);
void Update(float frameTime);
void AddPipeNet(IPipeNet pipeNet);
void RemovePipeNet(IPipeNet pipeNet);
void AddPipeNetDevice(PipeNetDeviceComponent pipeNetDevice);
void RemovePipeNetDevice(PipeNetDeviceComponent pipeNetDevice);
}
}

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using Content.Server.Interfaces;
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using YamlDotNet.RepresentationModel;
@@ -64,13 +65,13 @@ namespace Content.Server.Atmos.Reactions
serializer.DataField(ref _effects, "effects", new List<IGasReactionEffect>());
}
public ReactionResult React(GasMixture mixture, IGasMixtureHolder holder)
public ReactionResult React(GasMixture mixture, IGasMixtureHolder holder, IEventBus eventBus)
{
var result = ReactionResult.NoReaction;
foreach (var effect in _effects)
{
result |= effect.React(mixture, holder);
result |= effect.React(mixture, holder, eventBus);
}
return result;

View File

@@ -1,8 +1,12 @@
#nullable enable
using System;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Interaction;
using Content.Shared.Atmos;
using Content.Shared.Maps;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Serialization;
namespace Content.Server.Atmos.Reactions
@@ -10,7 +14,7 @@ namespace Content.Server.Atmos.Reactions
[UsedImplicitly]
public class PhoronFireReaction : IGasReactionEffect
{
public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder)
public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, IEventBus eventBus)
{
var energyReleased = 0f;
var oldHeatCapacity = mixture.HeatCapacity;
@@ -71,9 +75,7 @@ namespace Content.Server.Atmos.Reactions
{
location.HotspotExpose(temperature, mixture.Volume);
// TODO ATMOS Expose temperature all items on cell
location.TemperatureExpose(mixture, temperature, mixture.Volume);
eventBus.QueueEvent(EventSource.Local, new TemperatureExposeEvent(location.GridIndices, location.GridIndex, mixture, temperature, mixture.Volume));
}
}

View File

@@ -1,7 +1,10 @@
#nullable enable
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Interaction;
using Content.Shared.Atmos;
using Content.Shared.Maps;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Server.Atmos.Reactions
@@ -13,7 +16,7 @@ namespace Content.Server.Atmos.Reactions
{
}
public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder)
public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, IEventBus eventBus)
{
var energyReleased = 0f;
var oldHeatCapacity = mixture.HeatCapacity;
@@ -66,9 +69,7 @@ namespace Content.Server.Atmos.Reactions
{
location.HotspotExpose(temperature, mixture.Volume);
// TODO ATMOS Expose temperature all items on cell
location.TemperatureExpose(mixture, temperature, mixture.Volume);
eventBus.QueueEvent(EventSource.Local, new TemperatureExposeEvent(location.GridIndices, location.GridIndex, mixture, temperature, mixture.Volume));
}
}

View File

@@ -0,0 +1,23 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
namespace Content.Server.Atmos
{
public class TemperatureExposeEvent : EntitySystemMessage
{
public MapIndices Indices { get; }
public GridId Grid { get; }
public GasMixture Air { get; }
public float Temperature { get; }
public float Volume { get; }
public TemperatureExposeEvent(MapIndices indices, GridId gridId, GasMixture air, float temperature, float volume)
{
Indices = indices;
Grid = gridId;
Air = air;
Temperature = temperature;
Volume = volume;
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using Content.Shared.Atmos;
using Robust.Shared.Maths;
using Robust.Shared.ViewVariables;
@@ -30,15 +31,15 @@ namespace Content.Server.Atmos
[ViewVariables]
public float TransferDirectionSouth;
public float this[Direction direction]
public float this[AtmosDirection direction]
{
get =>
direction switch
{
Direction.East => TransferDirectionEast,
Direction.West => TransferDirectionWest,
Direction.North => TransferDirectionNorth,
Direction.South => TransferDirectionSouth,
AtmosDirection.East => TransferDirectionEast,
AtmosDirection.West => TransferDirectionWest,
AtmosDirection.North => TransferDirectionNorth,
AtmosDirection.South => TransferDirectionSouth,
_ => throw new ArgumentOutOfRangeException(nameof(direction))
};
@@ -46,16 +47,16 @@ namespace Content.Server.Atmos
{
switch (direction)
{
case Direction.East:
case AtmosDirection.East:
TransferDirectionEast = value;
break;
case Direction.West:
case AtmosDirection.West:
TransferDirectionWest = value;
break;
case Direction.North:
case AtmosDirection.North:
TransferDirectionNorth = value;
break;
case Direction.South:
case AtmosDirection.South:
TransferDirectionSouth = value;
break;
default:
@@ -64,10 +65,16 @@ namespace Content.Server.Atmos
}
}
public float this[int index]
{
get => this[(AtmosDirection) (1 << index)];
set => this[(AtmosDirection) (1 << index)] = value;
}
[ViewVariables]
public float CurrentTransferAmount;
public Direction CurrentTransferDirection;
public AtmosDirection CurrentTransferDirection;
[ViewVariables]
public bool FastDone;

View File

@@ -4,7 +4,6 @@ using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Content.Server.Atmos.Reactions;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.GameObjects.EntitySystems.Atmos;
using Content.Server.Interfaces;
using Content.Shared.Atmos;
@@ -12,13 +11,13 @@ using Content.Shared.Audio;
using Content.Shared.Maps;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Random;
@@ -28,18 +27,15 @@ namespace Content.Server.Atmos
{
public class TileAtmosphere : IGasMixtureHolder
{
[Robust.Shared.IoC.Dependency] private IRobustRandom _robustRandom = default!;
[Robust.Shared.IoC.Dependency] private IEntityManager _entityManager = default!;
[Robust.Shared.IoC.Dependency] private IMapManager _mapManager = default!;
[Robust.Shared.IoC.Dependency] private readonly IRobustRandom _robustRandom = default!;
[Robust.Shared.IoC.Dependency] private readonly IEntityManager _entityManager = default!;
[Robust.Shared.IoC.Dependency] private readonly IMapManager _mapManager = default!;
private static readonly TileAtmosphereComparer _comparer = new TileAtmosphereComparer();
private static readonly TileAtmosphereComparer Comparer = new TileAtmosphereComparer();
[ViewVariables]
private int _archivedCycle = 0;
[ViewVariables]
private int _currentCycle = 0;
[ViewVariables] private int _archivedCycle;
[ViewVariables] private int _currentCycle;
[ViewVariables]
private static GasTileOverlaySystem _gasTileOverlaySystem;
@@ -51,13 +47,13 @@ namespace Content.Server.Atmos
private float _temperatureArchived = Atmospherics.T20C;
// I know this being static is evil, but I seriously can't come up with a better solution to sound spam.
private static int _soundCooldown = 0;
private static int _soundCooldown;
[ViewVariables]
public TileAtmosphere PressureSpecificTarget { get; set; } = null;
public TileAtmosphere PressureSpecificTarget { get; set; }
[ViewVariables]
public float PressureDifference { get; set; } = 0;
public float PressureDifference { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
public float HeatCapacity { get; set; } = 1f;
@@ -66,13 +62,19 @@ namespace Content.Server.Atmos
public float ThermalConductivity => Tile?.Tile.GetContentTileDefinition().ThermalConductivity ?? 0.05f;
[ViewVariables]
public bool Excited { get; set; } = false;
public bool Excited { get; set; }
[ViewVariables]
private GridAtmosphereComponent _gridAtmosphereComponent;
private readonly GridAtmosphereComponent _gridAtmosphereComponent;
/// <summary>
/// Adjacent tiles in the same order as <see cref="AtmosDirection"/>. (NSEW)
/// </summary>
[ViewVariables]
private readonly TileAtmosphere[] _adjacentTiles = new TileAtmosphere[Atmospherics.Directions];
[ViewVariables]
private readonly Dictionary<Direction, TileAtmosphere> _adjacentTiles = new Dictionary<Direction, TileAtmosphere>();
private AtmosDirection _adjacentBits = AtmosDirection.Invalid;
[ViewVariables]
private TileAtmosInfo _tileAtmosInfo;
@@ -80,7 +82,7 @@ namespace Content.Server.Atmos
[ViewVariables]
public Hotspot Hotspot;
private Direction _pressureDirection;
private AtmosDirection _pressureDirection;
[ViewVariables]
public GridId GridIndex { get; }
@@ -100,14 +102,16 @@ namespace Content.Server.Atmos
[ViewVariables]
public bool BlocksAir => _gridAtmosphereComponent.IsAirBlocked(GridIndices);
public TileAtmosphere(GridAtmosphereComponent atmosphereComponent, GridId gridIndex, MapIndices gridIndices, GasMixture mixture = null)
public TileAtmosphere(GridAtmosphereComponent atmosphereComponent, GridId gridIndex, MapIndices gridIndices, GasMixture mixture = null, bool immutable = false)
{
IoCManager.InjectDependencies(this);
_gridAtmosphereComponent = atmosphereComponent;
GridIndex = gridIndex;
GridIndices = gridIndices;
Air = mixture;
ResetTileAtmosInfo();
if(immutable)
Air?.MarkImmutable();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -163,7 +167,7 @@ namespace Content.Server.Atmos
}
}
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void HighPressureMovements()
{
// TODO ATMOS finish this
@@ -175,7 +179,6 @@ namespace Content.Server.Atmos
GridIndices.ToGridCoordinates(_mapManager, GridIndex), AudioHelpers.WithVariation(0.125f).WithVolume(MathHelper.Clamp(PressureDifference / 10, 10, 100)));
}
foreach (var entity in _entityManager.GetEntitiesIntersecting(_mapManager.GetGrid(GridIndex).ParentMapId, Box2.UnitCentered.Translated(GridIndices)))
{
if (!entity.TryGetComponent(out ICollidableComponent physics)
@@ -195,7 +198,7 @@ namespace Content.Server.Atmos
if (PressureDifference > 100)
{
// Do space wind graphics here!
// TODO ATMOS Do space wind graphics here!
}
_soundCooldown++;
@@ -220,20 +223,22 @@ namespace Content.Server.Atmos
}
}
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void EqualizePressureInZone(int cycleNum)
{
if (Air == null || (_tileAtmosInfo.LastCycle >= cycleNum)) return; // Already done.
ResetTileAtmosInfo();
_tileAtmosInfo = new TileAtmosInfo();
var startingMoles = Air.TotalMoles;
var runAtmos = false;
// We need to figure if this is necessary
foreach (var (direction, other) in _adjacentTiles)
for (var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
if (!_adjacentBits.HasFlag(direction)) continue;
var other = _adjacentTiles[i];
if (other?.Air == null) continue;
var comparisonMoles = other.Air.TotalMoles;
if (!(MathF.Abs(comparisonMoles - startingMoles) > Atmospherics.MinimumMolesDeltaToMove)) continue;
@@ -265,13 +270,15 @@ namespace Content.Server.Atmos
totalMoles += tileMoles;
}
foreach (var (_, adj) in exploring._adjacentTiles)
for (var j = 0; j < Atmospherics.Directions; j++)
{
var direction = (AtmosDirection) (1 << j);
if (!exploring._adjacentBits.HasFlag(direction)) continue;
var adj = exploring._adjacentTiles[j];
if (adj?.Air == null) continue;
if(adj._tileAtmosInfo.LastQueueCycle == queueCycle) continue;
adj.ResetTileAtmosInfo();
adj._tileAtmosInfo = new TileAtmosInfo {LastQueueCycle = queueCycle};
adj._tileAtmosInfo.LastQueueCycle = queueCycle;
if(tileCount < Atmospherics.ZumosHardTileLimit)
tiles[tileCount++] = adj;
if (adj.Air.Immutable)
@@ -326,47 +333,42 @@ namespace Content.Server.Atmos
if (giverTilesLength > logN && takerTilesLength > logN)
{
// Even if it fails, it will speed up the next part.
Array.Sort(tiles, 0, tileCount, _comparer);
Array.Sort(tiles, 0, tileCount, Comparer);
for (var i = 0; i < tileCount; i++)
{
var tile = tiles[i];
tile._tileAtmosInfo.FastDone = true;
if (!(tile._tileAtmosInfo.MoleDelta > 0)) continue;
var eligibleDirections = ArrayPool<Direction>.Shared.Rent(4);
var eligibleDirections = AtmosDirection.Invalid;
var eligibleDirectionCount = 0;
foreach (var direction in Cardinal)
for (var j = 0; j < Atmospherics.Directions; j++)
{
if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue;
var direction = (AtmosDirection) (1 << j);
if (!tile._adjacentBits.HasFlag(direction)) continue;
var tile2 = tile._adjacentTiles[j];
// skip anything that isn't part of our current processing block.
if (tile2._tileAtmosInfo.FastDone || tile2._tileAtmosInfo.LastQueueCycle != queueCycle)
continue;
eligibleDirections[eligibleDirectionCount++] = direction;
eligibleDirections |= direction;
eligibleDirectionCount++;
}
if (eligibleDirectionCount <= 0)
continue; // Oof we've painted ourselves into a corner. Bad luck. Next part will handle this.
var molesToMove = tile._tileAtmosInfo.MoleDelta / eligibleDirectionCount;
foreach (var direction in Cardinal)
for (var j = 0; j < Atmospherics.Directions; j++)
{
var hasDirection = false;
for (var j = 0; j < eligibleDirectionCount; j++)
{
if (eligibleDirections[j] != direction) continue;
hasDirection = true;
break;
}
var direction = (AtmosDirection) (1 << j);
if (!eligibleDirections.HasFlag(direction)) continue;
if (hasDirection || !tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue;
tile.AdjustEqMovement(direction, molesToMove);
tile._tileAtmosInfo.MoleDelta -= molesToMove;
tile2._tileAtmosInfo.MoleDelta += molesToMove;
tile._adjacentTiles[j]._tileAtmosInfo.MoleDelta += molesToMove;
}
ArrayPool<Direction>.Shared.Return(eligibleDirections);
}
giverTilesLength = 0;
@@ -393,7 +395,7 @@ namespace Content.Server.Atmos
for (var j = 0; j < giverTilesLength; j++)
{
var giver = giverTiles[j];
giver._tileAtmosInfo.CurrentTransferDirection = (Direction) (-1);
giver._tileAtmosInfo.CurrentTransferDirection = AtmosDirection.Invalid;
giver._tileAtmosInfo.CurrentTransferAmount = 0;
var queueCycleSlow = ++_gridAtmosphereComponent.EqualizationQueueCycleControl;
var queueLength = 0;
@@ -405,9 +407,11 @@ namespace Content.Server.Atmos
break; // We're done here now. Let's not do more work than needed.
var tile = queue[i];
foreach (var direction in Cardinal)
for (var k = 0; k < Atmospherics.Directions; k++)
{
if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue;
var direction = (AtmosDirection) (1 << k);
if (!tile._adjacentBits.HasFlag(direction)) continue;
var tile2 = tile._adjacentTiles[k];
if (giver._tileAtmosInfo.MoleDelta <= 0) break; // We're done here now. Let's not do more work than needed.
if (tile2._tileAtmosInfo.LastQueueCycle != queueCycle) continue;
if (tile2._tileAtmosInfo.LastSlowQueueCycle == queueCycleSlow) continue;
@@ -441,15 +445,11 @@ namespace Content.Server.Atmos
for (var i = queueLength - 1; i >= 0; i--)
{
var tile = queue[i];
if (tile._tileAtmosInfo.CurrentTransferAmount != 0 &&
tile._tileAtmosInfo.CurrentTransferDirection != (Direction) (-1))
if (tile._tileAtmosInfo.CurrentTransferAmount != 0 && tile._tileAtmosInfo.CurrentTransferDirection != AtmosDirection.Invalid)
{
tile.AdjustEqMovement(tile._tileAtmosInfo.CurrentTransferDirection,
tile._tileAtmosInfo.CurrentTransferAmount);
if (tile._adjacentTiles.TryGetValue(tile._tileAtmosInfo.CurrentTransferDirection,
out var adjacent))
adjacent._tileAtmosInfo.CurrentTransferAmount +=
tile._tileAtmosInfo.CurrentTransferAmount;
tile.AdjustEqMovement(tile._tileAtmosInfo.CurrentTransferDirection, tile._tileAtmosInfo.CurrentTransferAmount);
tile._adjacentTiles[tile._tileAtmosInfo.CurrentTransferDirection.ToIndex()]
._tileAtmosInfo.CurrentTransferAmount += tile._tileAtmosInfo.CurrentTransferAmount;
tile._tileAtmosInfo.CurrentTransferAmount = 0;
}
}
@@ -463,7 +463,7 @@ namespace Content.Server.Atmos
for (var j = 0; j < takerTilesLength; j++)
{
var taker = takerTiles[j];
taker._tileAtmosInfo.CurrentTransferDirection = Direction.Invalid;
taker._tileAtmosInfo.CurrentTransferDirection = AtmosDirection.Invalid;
taker._tileAtmosInfo.CurrentTransferAmount = 0;
var queueCycleSlow = ++_gridAtmosphereComponent.EqualizationQueueCycleControl;
var queueLength = 0;
@@ -475,10 +475,11 @@ namespace Content.Server.Atmos
break; // We're done here now. Let's not do more work than needed.
var tile = queue[i];
foreach (var direction in Cardinal)
for (var k = 0; k < Atmospherics.Directions; k++)
{
if (!tile._adjacentTiles.ContainsKey(direction)) continue;
var tile2 = tile._adjacentTiles[direction];
var direction = (AtmosDirection) (1 << k);
if (!tile._adjacentBits.HasFlag(direction)) continue;
var tile2 = tile._adjacentTiles[k];
if (taker._tileAtmosInfo.MoleDelta >= 0) break; // We're done here now. Let's not do more work than needed.
if (tile2._tileAtmosInfo.LastQueueCycle != queueCycle) continue;
@@ -512,16 +513,14 @@ namespace Content.Server.Atmos
for (var i = queueLength - 1; i >= 0; i--)
{
var tile = queue[i];
if (tile._tileAtmosInfo.CurrentTransferAmount == 0 || tile._tileAtmosInfo.CurrentTransferDirection == Direction.Invalid)
if (tile._tileAtmosInfo.CurrentTransferAmount == 0 || tile._tileAtmosInfo.CurrentTransferDirection == AtmosDirection.Invalid)
continue;
tile.AdjustEqMovement(tile._tileAtmosInfo.CurrentTransferDirection, tile._tileAtmosInfo.CurrentTransferAmount);
if (tile._adjacentTiles.TryGetValue(tile._tileAtmosInfo.CurrentTransferDirection, out var adjacent))
{
adjacent._tileAtmosInfo.CurrentTransferAmount += tile._tileAtmosInfo.CurrentTransferAmount;
tile._tileAtmosInfo.CurrentTransferAmount = 0;
}
tile._adjacentTiles[tile._tileAtmosInfo.CurrentTransferDirection.ToIndex()]
._tileAtmosInfo.CurrentTransferAmount += tile._tileAtmosInfo.CurrentTransferAmount;
tile._tileAtmosInfo.CurrentTransferAmount = 0;
}
}
@@ -537,9 +536,11 @@ namespace Content.Server.Atmos
for (var i = 0; i < tileCount; i++)
{
var tile = tiles[i];
foreach (var direction in Cardinal)
for (var j = 0; j < Atmospherics.Directions; j++)
{
if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue;
var direction = (AtmosDirection) (1 << j);
if (!tile._adjacentBits.HasFlag(direction)) continue;
var tile2 = tile._adjacentTiles[j];
if (tile2?.Air?.Compare(Air) == GasMixture.GasCompareResult.NoExchange) continue;
_gridAtmosphereComponent.AddActiveTile(tile2);
break;
@@ -555,69 +556,68 @@ namespace Content.Server.Atmos
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void FinalizeEq()
{
var transferDirections = new Dictionary<Direction, float>();
var transferDirections = new float[Atmospherics.Directions];
var hasTransferDirs = false;
foreach (var direction in Cardinal)
for (var i = 0; i < Atmospherics.Directions; i++)
{
var amount = _tileAtmosInfo[direction];
var amount = _tileAtmosInfo[i];
if (amount == 0) continue;
transferDirections[direction] = amount;
_tileAtmosInfo[direction] = 0;
transferDirections[i] = amount;
_tileAtmosInfo[i] = 0; // Set them to 0 to prevent infinite recursion.
hasTransferDirs = true;
}
if (!hasTransferDirs) return;
foreach (var (direction, amount) in transferDirections)
for(var i = 0; i < Atmospherics.Directions; i++)
{
if (!_adjacentTiles.TryGetValue(direction, out var tile) || tile.Air == null) continue;
var direction = (AtmosDirection) (1 << i);
if (!_adjacentBits.HasFlag(direction)) continue;
var amount = transferDirections[i];
var tile = _adjacentTiles[i];
if (tile?.Air == null) continue;
if (amount > 0)
{
if (Air.TotalMoles < amount)
FinalizeEqNeighbors(transferDirections.Keys);
FinalizeEqNeighbors(transferDirections);
tile._tileAtmosInfo[direction.GetOpposite()] = 0;
tile.Air.Merge(Air.Remove(amount));
UpdateVisuals();
tile.UpdateVisuals();
ConsiderPressureDifference(direction, amount);
ConsiderPressureDifference(tile, amount);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void FinalizeEqNeighbors(IEnumerable<Direction> directions)
private void FinalizeEqNeighbors(in float[] transferDirs)
{
foreach (var direction in directions)
for (var i = 0; i < Atmospherics.Directions; i++)
{
var amount = _tileAtmosInfo[direction];
if(amount < 0 && _adjacentTiles.TryGetValue(direction, out var adjacent))
adjacent.FinalizeEq();
var direction = (AtmosDirection) (1 << i);
var amount = transferDirs[i];
if(amount < 0 && _adjacentBits.HasFlag(direction))
_adjacentTiles[i].FinalizeEq(); // A bit of recursion if needed.
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ConsiderPressureDifference(Direction direction, float difference)
private void ConsiderPressureDifference(TileAtmosphere other, float difference)
{
_gridAtmosphereComponent.AddHighPressureDelta(this);
if (difference > PressureDifference)
{
PressureDifference = difference;
_pressureDirection = difference < 0 ? direction.GetOpposite() : direction;
_pressureDirection = ((Vector2i)(GridIndices - other.GridIndices)).GetDir().ToAtmosDirection();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void AdjustEqMovement(Direction direction, float molesToMove)
private void AdjustEqMovement(AtmosDirection direction, float amount)
{
_tileAtmosInfo[direction] += molesToMove;
if(direction != Direction.Invalid && _adjacentTiles.TryGetValue(direction, out var adj))
adj._tileAtmosInfo[direction.GetOpposite()] -= molesToMove;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ResetTileAtmosInfo()
{
_tileAtmosInfo = new TileAtmosInfo {CurrentTransferDirection = Direction.Invalid};
_tileAtmosInfo[direction] += amount;
_adjacentTiles[direction.ToIndex()]._tileAtmosInfo[direction.GetOpposite()] -= amount;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -635,9 +635,13 @@ namespace Content.Server.Atmos
_currentCycle = fireCount;
var adjacentTileLength = 0;
foreach (var (direction, enemyTile) in _adjacentTiles)
for(var i = 0; i < Atmospherics.Directions; i++)
{
// If the tile is null or has no air, we don't do anything
var direction = (AtmosDirection) (1 << i);
if (!_adjacentBits.HasFlag(direction)) continue;
var enemyTile = _adjacentTiles[i];
// If the tile is null or has no air, we don't do anything for it.
if(enemyTile?.Air == null) continue;
adjacentTileLength++;
if (fireCount <= enemyTile._currentCycle) continue;
@@ -685,11 +689,11 @@ namespace Content.Server.Atmos
// Space wind!
if (difference > 0)
{
ConsiderPressureDifference(direction, difference);
ConsiderPressureDifference(enemyTile, difference);
}
else
{
enemyTile.ConsiderPressureDifference(direction.GetOpposite(), -difference);
enemyTile.ConsiderPressureDifference(this, -difference);
}
LastShareCheck();
@@ -737,7 +741,7 @@ namespace Content.Server.Atmos
if (Air.Temperature > Atmospherics.FireMinimumTemperatureToSpread)
{
var radiatedTemperature = Air.Temperature * Atmospherics.FireSpreadRadiosityScale;
foreach (var (_, tile) in _adjacentTiles)
foreach (var tile in _adjacentTiles)
{
if(!tile.Hotspot.Valid)
tile.HotspotExpose(radiatedTemperature, Atmospherics.CellVolume/4);
@@ -771,17 +775,19 @@ namespace Content.Server.Atmos
else
{
var affected = Air.RemoveRatio(Hotspot.Volume / Air.Volume);
if (affected != null)
{
affected.Temperature = Hotspot.Temperature;
affected.React(this);
Hotspot.Temperature = affected.Temperature;
Hotspot.Volume = affected.ReactionResults[GasReaction.Fire] * Atmospherics.FireGrowthRate;
AssumeAir(affected);
}
affected.Temperature = Hotspot.Temperature;
affected.React(this);
Hotspot.Temperature = affected.Temperature;
Hotspot.Volume = affected.ReactionResults[GasReaction.Fire] * Atmospherics.FireGrowthRate;
AssumeAir(affected);
}
// TODO ATMOS Let all entities in this tile know about the fire?
var tileRef = GridIndices.GetTileRef(GridIndex);
if (tileRef == null) return;
_gridAtmosphereComponent.Owner.EntityManager.
EventBus.QueueEvent(EventSource.Local, new FireActEvent(Hotspot.Temperature, Hotspot.Volume));
}
private bool ConsiderSuperconductivity()
@@ -806,24 +812,24 @@ namespace Content.Server.Atmos
public void Superconduct()
{
var directions = ConductivityDirections();
var adjacentTiles = _gridAtmosphereComponent.GetAdjacentTiles(GridIndices, true);
if (directions.Length > 0)
for(var i = 0; i < Atmospherics.Directions; i++)
{
foreach (var direction in directions)
{
if (!adjacentTiles.TryGetValue(direction, out var adjacent)) continue;
var direction = (AtmosDirection) (1 << i);
if (!directions.HasFlag(direction)) continue;
if (adjacent.ThermalConductivity == 0f)
continue;
var adjacent = _adjacentTiles[direction.ToIndex()];
if(adjacent._archivedCycle < _gridAtmosphereComponent.UpdateCounter)
adjacent.Archive(_gridAtmosphereComponent.UpdateCounter);
// TODO ATMOS handle adjacent being null.
if (adjacent == null || adjacent.ThermalConductivity == 0f)
continue;
adjacent.NeighborConductWithSource(this);
if(adjacent._archivedCycle < _gridAtmosphereComponent.UpdateCounter)
adjacent.Archive(_gridAtmosphereComponent.UpdateCounter);
adjacent.ConsiderSuperconductivity();
}
adjacent.NeighborConductWithSource(this);
adjacent.ConsiderSuperconductivity();
}
RadiateToSpace();
@@ -917,20 +923,20 @@ namespace Content.Server.Atmos
}
}
public Direction[] ConductivityDirections()
public AtmosDirection ConductivityDirections()
{
if(BlocksAir)
{
if(_archivedCycle < _gridAtmosphereComponent.UpdateCounter)
Archive(_gridAtmosphereComponent.UpdateCounter);
return Cardinal;
return AtmosDirection.All;
}
// TODO ATMOS check if this is correct
return Cardinal;
return AtmosDirection.All;
}
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ExplosivelyDepressurize(int cycleNum)
{
if (Air == null) return;
@@ -947,14 +953,13 @@ namespace Content.Server.Atmos
tiles[tileCount++] = this;
ResetTileAtmosInfo();
_tileAtmosInfo.LastQueueCycle = queueCycle;
_tileAtmosInfo = new TileAtmosInfo {LastQueueCycle = queueCycle};
for (var i = 0; i < tileCount; i++)
{
var tile = tiles[i];
tile._tileAtmosInfo.LastCycle = cycleNum;
tile._tileAtmosInfo.CurrentTransferDirection = Direction.Invalid;
tile._tileAtmosInfo.CurrentTransferDirection = AtmosDirection.Invalid;
if (tile.Air.Immutable)
{
spaceTiles[spaceTileCount++] = tile;
@@ -962,18 +967,19 @@ namespace Content.Server.Atmos
}
else
{
foreach (var direction in Cardinal)
for (var j = 0; j < Atmospherics.Directions; j++)
{
if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue;
var direction = (AtmosDirection) (1 << j);
if (!tile._adjacentBits.HasFlag(direction)) continue;
var tile2 = tile._adjacentTiles[j];
if (tile2.Air == null) continue;
if (tile2._tileAtmosInfo.LastQueueCycle == queueCycle) continue;
tile.ConsiderFirelocks(tile2);
// The firelocks might have closed on us.
if (tile._adjacentTiles[direction]?.Air == null) continue;
tile2.ResetTileAtmosInfo();
tile2._tileAtmosInfo.LastQueueCycle = queueCycle;
if (!tile._adjacentBits.HasFlag(direction)) continue;
tile2._tileAtmosInfo = new TileAtmosInfo {LastQueueCycle = queueCycle};
tiles[tileCount++] = tile2;
}
}
@@ -991,18 +997,21 @@ namespace Content.Server.Atmos
var tile = spaceTiles[i];
progressionOrder[progressionCount++] = tile;
tile._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow;
tile._tileAtmosInfo.CurrentTransferDirection = Direction.Invalid;
tile._tileAtmosInfo.CurrentTransferDirection = AtmosDirection.Invalid;
}
for (var i = 0; i < progressionCount; i++)
{
var tile = progressionOrder[i];
foreach (var direction in Cardinal)
for (var j = 0; j < Atmospherics.Directions; j++)
{
if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue;
var direction = (AtmosDirection) (1 << j);
// TODO ATMOS This is a terrible hack that accounts for the mess that are space TileAtmospheres.
if (!tile._adjacentBits.HasFlag(direction) && !tile.Air.Immutable) continue;
var tile2 = tile._adjacentTiles[j];
if (tile2?._tileAtmosInfo.LastQueueCycle != queueCycle) continue;
if (tile2._tileAtmosInfo.LastSlowQueueCycle == queueCycleSlow) continue;
if(tile2.Air.Immutable) continue;
if(tile2.Air?.Immutable ?? false) continue;
tile2._tileAtmosInfo.CurrentTransferDirection = direction.GetOpposite();
tile2._tileAtmosInfo.CurrentTransferAmount = 0;
tile2.PressureSpecificTarget = tile.PressureSpecificTarget;
@@ -1014,10 +1023,11 @@ namespace Content.Server.Atmos
for (var i = progressionCount - 1; i >= 0; i--)
{
var tile = progressionOrder[i];
if (tile._tileAtmosInfo.CurrentTransferDirection == Direction.Invalid) continue;
if (tile._tileAtmosInfo.CurrentTransferDirection == AtmosDirection.Invalid) continue;
_gridAtmosphereComponent.AddHighPressureDelta(tile);
_gridAtmosphereComponent.AddActiveTile(tile);
if (!tile._adjacentTiles.TryGetValue(tile._tileAtmosInfo.CurrentTransferDirection, out var tile2) || tile2.Air == null) continue;
var tile2 = tile._adjacentTiles[tile._tileAtmosInfo.CurrentTransferDirection.ToIndex()];
if (tile2?.Air == null) continue;
var sum = tile2.Air.TotalMoles;
totalGasesRemoved += sum;
tile._tileAtmosInfo.CurrentTransferAmount += sum;
@@ -1025,7 +1035,7 @@ namespace Content.Server.Atmos
tile.PressureDifference = tile._tileAtmosInfo.CurrentTransferAmount;
tile._pressureDirection = tile._tileAtmosInfo.CurrentTransferDirection;
if (tile2._tileAtmosInfo.CurrentTransferDirection == Direction.Invalid)
if (tile2._tileAtmosInfo.CurrentTransferDirection == AtmosDirection.Invalid)
{
tile2.PressureDifference = tile2._tileAtmosInfo.CurrentTransferAmount;
tile2._pressureDirection = tile._tileAtmosInfo.CurrentTransferDirection;
@@ -1101,21 +1111,30 @@ namespace Content.Server.Atmos
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UpdateAdjacent()
{
foreach (var direction in Cardinal)
for (var i = 0; i < Atmospherics.Directions; i++)
{
if (!_gridAtmosphereComponent.IsAirBlocked(GridIndices.Offset(direction)))
var direction = (AtmosDirection) (1 << i);
var otherIndices = GridIndices.Offset(direction.ToDirection());
var isSpace = _gridAtmosphereComponent.IsSpace(GridIndices);
var adjacent = _gridAtmosphereComponent.GetTile(otherIndices, !isSpace);
_adjacentTiles[direction.ToIndex()] = adjacent;
adjacent?.UpdateAdjacent(direction.GetOpposite());
if (adjacent != null && !_gridAtmosphereComponent.IsAirBlocked(adjacent.GridIndices))
{
var adjacent = _gridAtmosphereComponent.GetTile(GridIndices.Offset(direction));
_adjacentTiles[direction] = adjacent;
adjacent.UpdateAdjacent(direction.GetOpposite());
_adjacentBits |= direction;
}
}
}
public void UpdateAdjacent(Direction direction)
public void UpdateAdjacent(AtmosDirection direction)
{
if (!_gridAtmosphereComponent.IsAirBlocked(GridIndices.Offset(direction)))
_adjacentTiles[direction] = _gridAtmosphereComponent.GetTile(GridIndices.Offset(direction));
if (!_gridAtmosphereComponent.IsAirBlocked(GridIndices.Offset(direction.ToDirection())))
{
_adjacentTiles[direction.ToIndex()] = _gridAtmosphereComponent.GetTile(GridIndices.Offset(direction.ToDirection()));
}
}
private void LastShareCheck()
@@ -1130,13 +1149,7 @@ namespace Content.Server.Atmos
}
}
private static readonly Direction[] Cardinal =
new Direction[]
{
Direction.North, Direction.East, Direction.South, Direction.West
};
public void TemperatureExpose(GasMixture mixture, float temperature, float cellVolume)
public void TemperatureExpose(GasMixture air, float temperature, float volume)
{
// TODO ATMOS do this
}

View File

@@ -1,4 +1,4 @@
#nullable enable
#nullable enable
using System.Linq;
using Content.Server.GameObjects.Components.Body;
using Content.Shared.Body.Part;
@@ -42,7 +42,7 @@ namespace Content.Server.Body
}
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
prototypeManager.TryIndex("bodyPart.Hand.BasicHuman", out BodyPartPrototype prototype);
prototypeManager.TryIndex("bodyPart.LHand.BasicHuman", out BodyPartPrototype prototype);
var part = new BodyPart(prototype);
var slot = part.GetHashCode().ToString();

View File

@@ -1,11 +1,9 @@
using System.Linq;
using Content.Server.GameObjects.Components.Observer;
using Content.Server.GameObjects.Components.Observer;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Server.Interfaces.Chat;
using Content.Shared.Chat;
using Content.Shared.GameObjects.EntitySystems;
using NFluidsynth;
using Robust.Server.Console;
using Robust.Server.Interfaces.GameObjects;
using Robust.Server.Interfaces.Player;
@@ -13,6 +11,10 @@ using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using System;
using System.Collections.Generic;
using System.Linq;
using static Content.Server.Interfaces.Chat.IChatManager;
namespace Content.Server.Chat
{
@@ -33,14 +35,15 @@ namespace Content.Server.Chat
/// </summary>
private const string MaxLengthExceededMessage = "Your message exceeded {0} character limit";
#pragma warning disable 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
[Dependency] private readonly IServerNetManager _netManager;
[Dependency] private readonly IPlayerManager _playerManager;
[Dependency] private readonly ILocalizationManager _localizationManager;
[Dependency] private readonly IMoMMILink _mommiLink;
[Dependency] private readonly IConGroupController _conGroupController;
#pragma warning restore 649
//TODO: make prio based?
private List<TransformChat> _chatTransformHandlers;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly IServerNetManager _netManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly ILocalizationManager _localizationManager = default!;
[Dependency] private readonly IMoMMILink _mommiLink = default!;
[Dependency] private readonly IConGroupController _conGroupController = default!;
public void Initialize()
{
@@ -51,6 +54,8 @@ namespace Content.Server.Chat
var msg = _netManager.CreateNetMessage<ChatMaxMsgLengthMessage>();
msg.MaxMessageLength = MaxMessageLength;
_netManager.ServerSendToAll(msg);
_chatTransformHandlers = new List<TransformChat>();
}
public void DispatchServerAnnouncement(string message)
@@ -98,6 +103,12 @@ namespace Content.Server.Chat
return;
}
foreach (var handler in _chatTransformHandlers)
{
//TODO: rather return a bool and use a out var?
message = handler(source, message);
}
var pos = source.Transform.GridPosition;
var clients = _playerManager.GetPlayersInRange(pos, VoiceRange).Select(p => p.ConnectedClient);
@@ -217,5 +228,10 @@ namespace Content.Server.Chat
response.MaxMessageLength = MaxMessageLength;
_netManager.ServerSendMessage(response, msg.MsgChannel);
}
public void RegisterChatTransform(TransformChat handler)
{
_chatTransformHandlers.Add(handler);
}
}
}

View File

@@ -15,6 +15,7 @@ using Robust.Shared.Interfaces.Log;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Timing;
using Content.Server.GameObjects.Components.Mobs.Speech;
namespace Content.Server
{
@@ -64,6 +65,7 @@ namespace Content.Server
IoCManager.Resolve<IServerPreferencesManager>().StartInit();
IoCManager.Resolve<INodeGroupFactory>().Initialize();
IoCManager.Resolve<ISandboxManager>().Initialize();
IoCManager.Resolve<IAccentManager>().Initialize();
}
public override void PostInit()

View File

@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.AI
{
[RegisterComponent]
public sealed class AiFactionTagComponent : Component
{
public override string Name => "AiFactionTag";
public Faction Factions { get; private set; } = Faction.None;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataReadWriteFunction(
"factions",
new List<Faction>(),
factions => factions.ForEach(faction => Factions |= faction),
() =>
{
var writeFactions = new List<Faction>();
foreach (Faction fac in Enum.GetValues(typeof(Faction)))
{
if ((Factions & fac) != 0)
{
writeFactions.Add(fac);
}
}
return writeFactions;
});
}
}
[Flags]
public enum Faction
{
None = 0,
NanoTransen = 1 << 0,
SimpleHostile = 1 << 1,
SimpleNeutral = 1 << 2,
Syndicate = 1 << 3,
Xeno = 1 << 4,
}
}

View File

@@ -1,8 +1,10 @@
using System.Collections.Generic;
#nullable enable
using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility;
using Content.Shared.Access;
using Content.Shared.GameObjects.Components.Access;
using Content.Shared.Interfaces.GameObjects.Components;
@@ -15,6 +17,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Access
{
@@ -22,16 +25,13 @@ namespace Content.Server.GameObjects.Components.Access
[ComponentReference(typeof(IActivate))]
public class IdCardConsoleComponent : SharedIdCardConsoleComponent, IActivate
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager;
[Dependency] private readonly ILocalizationManager _localizationManager;
[Dependency] private readonly IPrototypeManager _prototypeManager;
#pragma warning restore 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private BoundUserInterface _userInterface;
private ContainerSlot _privilegedIdContainer;
private ContainerSlot _targetIdContainer;
private AccessReader _accessReader;
private ContainerSlot _privilegedIdContainer = default!;
private ContainerSlot _targetIdContainer = default!;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(IdCardConsoleUiKey.Key);
public override void Initialize()
{
@@ -40,16 +40,30 @@ namespace Content.Server.GameObjects.Components.Access
_privilegedIdContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-privilegedId", Owner);
_targetIdContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-targetId", Owner);
_accessReader = Owner.GetComponent<AccessReader>();
if (!Owner.EnsureComponent(out AccessReader _))
{
Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} didn't have a {nameof(AccessReader)}");
}
if (UserInterface == null)
{
Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} doesn't have a {nameof(ServerUserInterfaceComponent)}");
}
else
{
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
}
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(IdCardConsoleUiKey.Key);
_userInterface.OnReceiveMessage += OnUiReceiveMessage;
UpdateUserInterface();
}
private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
{
if (obj.Session.AttachedEntity == null)
{
return;
}
switch (obj.Message)
{
case IdButtonPressedMessage msg:
@@ -72,13 +86,19 @@ namespace Content.Server.GameObjects.Components.Access
}
/// <summary>
/// Returns true if there is an ID in <see cref="_privilegedIdContainer"/> and said ID satisfies the requirements of <see cref="_accessReader"/>.
/// Returns true if there is an ID in <see cref="_privilegedIdContainer"/> and said ID satisfies the requirements of <see cref="AccessReader"/>.
/// </summary>
private bool PrivilegedIdIsAuthorized()
{
if (!Owner.TryGetComponent(out AccessReader? reader))
{
return true;
}
var privilegedIdEntity = _privilegedIdContainer.ContainedEntity;
return privilegedIdEntity != null && _accessReader.IsAllowed(privilegedIdEntity);
return privilegedIdEntity != null && reader.IsAllowed(privilegedIdEntity);
}
/// <summary>
/// Called when the "Submit" button in the UI gets pressed.
/// Writes data passed from the UI into the ID stored in <see cref="_targetIdContainer"/>, if present.
@@ -110,9 +130,9 @@ namespace Content.Server.GameObjects.Components.Access
/// </summary>
private void HandleId(IEntity user, ContainerSlot container)
{
if (!user.TryGetComponent(out IHandsComponent hands))
if (!user.TryGetComponent(out IHandsComponent? hands))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user, _localizationManager.GetString("You have no hands."));
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Loc.GetString("You have no hands."));
return;
}
@@ -133,9 +153,15 @@ namespace Content.Server.GameObjects.Components.Access
{
return;
}
if(!hands.Drop(hands.ActiveHand, container))
if (hands.ActiveHand == null)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user, _localizationManager.GetString("You can't let go of the ID card!"));
return;
}
if (!hands.Drop(hands.ActiveHand, container))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Loc.GetString("You can't let go of the ID card!"));
return;
}
UpdateUserInterface();
@@ -185,17 +211,17 @@ namespace Content.Server.GameObjects.Components.Access
_privilegedIdContainer.ContainedEntity?.Name ?? "",
_targetIdContainer.ContainedEntity?.Name ?? "");
}
_userInterface.SetState(newState);
UserInterface?.SetState(newState);
}
public void Activate(ActivateEventArgs eventArgs)
{
if(!eventArgs.User.TryGetComponent(out IActorComponent actor))
if(!eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
return;
}
_userInterface.Open(actor.playerSession);
UserInterface?.Open(actor.playerSession);
}
}
}

View File

@@ -0,0 +1,355 @@

using Robust.Server.GameObjects;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Content.Server.GameObjects.EntitySystems.DoAfter;
using Robust.Shared.ViewVariables;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Shared.GameObjects.Components.ActionBlocking;
using Content.Shared.GameObjects.Verbs;
using Content.Server.GameObjects.Components.Items.Storage;
using Robust.Shared.Log;
using System.Linq;
using Robust.Server.GameObjects.Components.Container;
using Robust.Server.GameObjects.EntitySystems;
using Content.Server.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.Components.Mobs;
using Robust.Shared.Maths;
using System;
using System.Collections.Generic;
using Content.Server.GameObjects.Components.GUI;
namespace Content.Server.GameObjects.Components.ActionBlocking
{
[RegisterComponent]
public class CuffableComponent : SharedCuffableComponent
{
[Dependency]
private readonly ISharedNotifyManager _notifyManager;
/// <summary>
/// How many of this entity's hands are currently cuffed.
/// </summary>
[ViewVariables]
public int CuffedHandCount => _container.ContainedEntities.Count * 2;
protected IEntity LastAddedCuffs => _container.ContainedEntities[_container.ContainedEntities.Count - 1];
public IReadOnlyList<IEntity> StoredEntities => _container.ContainedEntities;
/// <summary>
/// Container of various handcuffs currently applied to the entity.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
private Container _container = default!;
private float _interactRange;
private IHandsComponent _hands;
public event Action OnCuffedStateChanged;
public override void Initialize()
{
base.Initialize();
_container = ContainerManagerComponent.Ensure<Container>(Name, Owner);
_interactRange = SharedInteractionSystem.InteractionRange / 2;
Owner.EntityManager.EventBus.SubscribeEvent<HandCountChangedEvent>(EventSource.Local, this, HandleHandCountChange);
if (!Owner.TryGetComponent(out _hands))
{
Logger.Warning("Player does not have an IHandsComponent!");
}
}
public override ComponentState GetComponentState()
{
// there are 2 approaches i can think of to handle the handcuff overlay on players
// 1 - make the current RSI the handcuff type that's currently active. all handcuffs on the player will appear the same.
// 2 - allow for several different player overlays for each different cuff type.
// approach #2 would be more difficult/time consuming to do and the payoff doesn't make it worth it.
// right now we're doing approach #1.
if (CuffedHandCount > 0)
{
if (LastAddedCuffs.TryGetComponent<HandcuffComponent>(out var cuffs))
{
return new CuffableComponentState(CuffedHandCount,
CanStillInteract,
cuffs.CuffedRSI,
$"{cuffs.OverlayIconState}-{CuffedHandCount}",
cuffs.Color);
// the iconstate is formatted as blah-2, blah-4, blah-6, etc.
// the number corresponds to how many hands are cuffed.
}
}
return new CuffableComponentState(CuffedHandCount,
CanStillInteract,
"/Objects/Misc/handcuffs.rsi",
"body-overlay-2",
Color.White);
}
/// <summary>
/// Add a set of cuffs to an existing CuffedComponent.
/// </summary>
/// <param name="prototype"></param>
public void AddNewCuffs(IEntity handcuff)
{
if (!handcuff.HasComponent<HandcuffComponent>())
{
Logger.Warning($"Handcuffs being applied to player are missing a {nameof(HandcuffComponent)}!");
return;
}
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(
handcuff.Transform.MapPosition,
Owner.Transform.MapPosition,
_interactRange,
ignoredEnt: Owner))
{
Logger.Warning("Handcuffs being applied to player are obstructed or too far away! This should not happen!");
return;
}
_container.Insert(handcuff);
CanStillInteract = _hands.Hands.Count() > CuffedHandCount;
OnCuffedStateChanged.Invoke();
UpdateStatusEffect();
UpdateHeldItems();
Dirty();
}
/// <summary>
/// Check the current amount of hands the owner has, and if there's less hands than active cuffs we remove some cuffs.
/// </summary>
private void UpdateHandCount()
{
var dirty = false;
var handCount = _hands.Hands.Count();
while (CuffedHandCount > handCount && CuffedHandCount > 0)
{
dirty = true;
var entity = _container.ContainedEntities[_container.ContainedEntities.Count - 1];
_container.Remove(entity);
entity.Transform.WorldPosition = Owner.Transform.GridPosition.Position;
}
if (dirty)
{
CanStillInteract = handCount > CuffedHandCount;
OnCuffedStateChanged.Invoke();
Dirty();
}
}
private void HandleHandCountChange(HandCountChangedEvent message)
{
if (message.Sender == Owner)
{
UpdateHandCount();
}
}
/// <summary>
/// Check how many items the user is holding and if it's more than the number of cuffed hands, drop some items.
/// </summary>
public void UpdateHeldItems()
{
var itemCount = _hands.GetAllHeldItems().Count();
var freeHandCount = _hands.Hands.Count() - CuffedHandCount;
if (freeHandCount < itemCount)
{
foreach (ItemComponent item in _hands.GetAllHeldItems())
{
if (freeHandCount < itemCount)
{
freeHandCount++;
_hands.Drop(item.Owner);
}
else
{
break;
}
}
}
}
/// <summary>
/// Updates the status effect indicator on the HUD.
/// </summary>
private void UpdateStatusEffect()
{
if (Owner.TryGetComponent(out ServerStatusEffectsComponent status))
{
status.ChangeStatusEffectIcon(StatusEffect.Cuffed,
CanStillInteract ? "/Textures/Interface/StatusEffects/Handcuffed/Uncuffed.png" : "/Textures/Interface/StatusEffects/Handcuffed/Handcuffed.png");
}
}
/// <summary>
/// Attempt to uncuff a cuffed entity. Can be called by the cuffed entity, or another entity trying to help uncuff them.
/// If the uncuffing succeeds, the cuffs will drop on the floor.
/// </summary>
/// <param name="user">The cuffed entity</param>
/// <param name="cuffsToRemove">Optional param for the handcuff entity to remove from the cuffed entity. If null, uses the most recently added handcuff entity.</param>
public async void TryUncuff(IEntity user, IEntity cuffsToRemove = null)
{
var isOwner = user == Owner;
if (cuffsToRemove == null)
{
cuffsToRemove = LastAddedCuffs;
}
else
{
if (!_container.ContainedEntities.Contains(cuffsToRemove))
{
Logger.Warning("A user is trying to remove handcuffs that aren't in the owner's container. This should never happen!");
}
}
if (!cuffsToRemove.TryGetComponent<HandcuffComponent>(out var cuff))
{
Logger.Warning($"A user is trying to remove handcuffs without a {nameof(HandcuffComponent)}. This should never happen!");
return;
}
if (!isOwner && !ActionBlockerSystem.CanInteract(user))
{
user.PopupMessage(user, Loc.GetString("You can't do that!"));
return;
}
if (!isOwner &&
!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(
user.Transform.MapPosition,
Owner.Transform.MapPosition,
_interactRange,
ignoredEnt: Owner))
{
user.PopupMessage(user, Loc.GetString("You are too far away to remove the cuffs."));
return;
}
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(
cuffsToRemove.Transform.MapPosition,
Owner.Transform.MapPosition,
_interactRange,
ignoredEnt: Owner))
{
Logger.Warning("Handcuffs being removed from player are obstructed or too far away! This should not happen!");
return;
}
user.PopupMessage(user, Loc.GetString("You start removing the cuffs."));
var audio = EntitySystem.Get<AudioSystem>();
audio.PlayFromEntity(isOwner ? cuff.StartBreakoutSound : cuff.StartUncuffSound, Owner);
var uncuffTime = isOwner ? cuff.BreakoutTime : cuff.UncuffTime;
var doAfterEventArgs = new DoAfterEventArgs(user, uncuffTime)
{
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
NeedHand = true
};
var doAfterSystem = EntitySystem.Get<DoAfterSystem>();
var result = await doAfterSystem.DoAfter(doAfterEventArgs);
if (result != DoAfterStatus.Cancelled)
{
audio.PlayFromEntity(cuff.EndUncuffSound, Owner);
_container.ForceRemove(cuffsToRemove);
cuffsToRemove.Transform.AttachToGridOrMap();
cuffsToRemove.Transform.WorldPosition = Owner.Transform.WorldPosition;
if (cuff.BreakOnRemove)
{
cuff.Broken = true;
cuffsToRemove.Name = cuff.BrokenName;
cuffsToRemove.Description = cuff.BrokenDesc;
if (cuffsToRemove.TryGetComponent<SpriteComponent>(out var sprite))
{
sprite.LayerSetState(0, cuff.BrokenState); // TODO: safety check to see if RSI contains the state?
}
}
CanStillInteract = _hands.Hands.Count() > CuffedHandCount;
OnCuffedStateChanged.Invoke();
UpdateStatusEffect();
Dirty();
if (CuffedHandCount == 0)
{
_notifyManager.PopupMessage(user, user, Loc.GetString("You successfully remove the cuffs."));
if (!isOwner)
{
_notifyManager.PopupMessage(user, Owner, Loc.GetString("{0:theName} uncuffs your hands.", user));
}
}
else
{
if (!isOwner)
{
_notifyManager.PopupMessage(user, user, Loc.GetString("You successfully remove the cuffs. {0} of {1:theName}'s hands remain cuffed.", CuffedHandCount, user));
_notifyManager.PopupMessage(user, Owner, Loc.GetString("{0:theName} removes your cuffs. {1} of your hands remain cuffed.", user, CuffedHandCount));
}
else
{
_notifyManager.PopupMessage(user, user, Loc.GetString("You successfully remove the cuffs. {0} of your hands remain cuffed.", CuffedHandCount));
}
}
}
else
{
_notifyManager.PopupMessage(user, user, Loc.GetString("You fail to remove the cuffs."));
}
return;
}
/// <summary>
/// Allows the uncuffing of a cuffed person. Used by other people and by the component owner to break out of cuffs.
/// </summary>
[Verb]
private sealed class UncuffVerb : Verb<CuffableComponent>
{
protected override void GetData(IEntity user, CuffableComponent component, VerbData data)
{
if ((user != component.Owner && !ActionBlockerSystem.CanInteract(user)) || component.CuffedHandCount == 0)
{
data.Visibility = VerbVisibility.Invisible;
return;
}
data.Text = Loc.GetString("Uncuff");
}
protected override void Activate(IEntity user, CuffableComponent component)
{
if (component.CuffedHandCount > 0)
{
component.TryUncuff(user);
}
}
}
}
}

View File

@@ -0,0 +1,248 @@
using Content.Server.GameObjects.EntitySystems.DoAfter;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Content.Server.GameObjects.Components.GUI;
using Robust.Shared.Serialization;
using Robust.Shared.Log;
using Robust.Shared.Localization;
using Robust.Shared.ViewVariables;
using Robust.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.ActionBlocking;
using Content.Server.GameObjects.Components.Mobs;
using Robust.Shared.Maths;
using System;
namespace Content.Server.GameObjects.Components.ActionBlocking
{
[RegisterComponent]
public class HandcuffComponent : SharedHandcuffComponent, IAfterInteract
{
[Dependency]
private readonly ISharedNotifyManager _notifyManager;
/// <summary>
/// The time it takes to apply a <see cref="CuffedComponent"/> to an entity.
/// </summary>
[ViewVariables]
public float CuffTime { get; set; }
/// <summary>
/// The time it takes to remove a <see cref="CuffedComponent"/> from an entity.
/// </summary>
[ViewVariables]
public float UncuffTime { get; set; }
/// <summary>
/// The time it takes for a cuffed entity to remove <see cref="CuffedComponent"/> from itself.
/// </summary>
[ViewVariables]
public float BreakoutTime { get; set; }
/// <summary>
/// If an entity being cuffed is stunned, this amount of time is subtracted from the time it takes to add/remove their cuffs.
/// </summary>
[ViewVariables]
public float StunBonus { get; set; }
/// <summary>
/// Will the cuffs break when removed?
/// </summary>
[ViewVariables]
public bool BreakOnRemove { get; set; }
/// <summary>
/// The path of the RSI file used for the player cuffed overlay.
/// </summary>
[ViewVariables]
public string CuffedRSI { get; set; }
/// <summary>
/// The iconstate used with the RSI file for the player cuffed overlay.
/// </summary>
[ViewVariables]
public string OverlayIconState { get; set; }
/// <summary>
/// The iconstate used for broken handcuffs
/// </summary>
[ViewVariables]
public string BrokenState { get; set; }
/// <summary>
/// The iconstate used for broken handcuffs
/// </summary>
[ViewVariables]
public string BrokenName { get; set; }
/// <summary>
/// The iconstate used for broken handcuffs
/// </summary>
[ViewVariables]
public string BrokenDesc { get; set; }
[ViewVariables]
public bool Broken
{
get
{
return _isBroken;
}
set
{
if (_isBroken != value)
{
_isBroken = value;
Dirty();
}
}
}
public string StartCuffSound { get; set; }
public string EndCuffSound { get; set; }
public string StartBreakoutSound { get; set; }
public string StartUncuffSound { get; set; }
public string EndUncuffSound { get; set; }
public Color Color { get; set; }
// Non-exposed data fields
private bool _isBroken = false;
private float _interactRange;
private DoAfterSystem _doAfterSystem;
private AudioSystem _audioSystem;
public override void Initialize()
{
base.Initialize();
_audioSystem = EntitySystem.Get<AudioSystem>();
_doAfterSystem = EntitySystem.Get<DoAfterSystem>();
_interactRange = SharedInteractionSystem.InteractionRange / 2;
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, x => x.CuffTime, "cuffTime", 5.0f);
serializer.DataField(this, x => x.BreakoutTime, "breakoutTime", 30.0f);
serializer.DataField(this, x => x.UncuffTime, "uncuffTime", 5.0f);
serializer.DataField(this, x => x.StunBonus, "stunBonus", 2.0f);
serializer.DataField(this, x => x.StartCuffSound, "startCuffSound", "/Audio/Items/Handcuffs/cuff_start.ogg");
serializer.DataField(this, x => x.EndCuffSound, "endCuffSound", "/Audio/Items/Handcuffs/cuff_end.ogg");
serializer.DataField(this, x => x.StartUncuffSound, "startUncuffSound", "/Audio/Items/Handcuffs/cuff_takeoff_start.ogg");
serializer.DataField(this, x => x.EndUncuffSound, "endUncuffSound", "/Audio/Items/Handcuffs/cuff_takeoff_end.ogg");
serializer.DataField(this, x => x.StartBreakoutSound, "startBreakoutSound", "/Audio/Items/Handcuffs/cuff_breakout_start.ogg");
serializer.DataField(this, x => x.CuffedRSI, "cuffedRSI", "Objects/Misc/handcuffs.rsi");
serializer.DataField(this, x => x.OverlayIconState, "bodyIconState", "body-overlay");
serializer.DataField(this, x => x.Color, "color", Color.White);
serializer.DataField(this, x => x.BreakOnRemove, "breakOnRemove", false);
serializer.DataField(this, x => x.BrokenState, "brokenIconState", string.Empty);
serializer.DataField(this, x => x.BrokenName, "brokenName", string.Empty);
serializer.DataField(this, x => x.BrokenDesc, "brokenDesc", string.Empty);
}
public override ComponentState GetComponentState()
{
return new HandcuffedComponentState(Broken ? BrokenState : string.Empty);
}
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
if (eventArgs.Target == null || !ActionBlockerSystem.CanUse(eventArgs.User) || !eventArgs.Target.TryGetComponent<CuffableComponent>(out var cuffed))
{
return;
}
if (eventArgs.Target == eventArgs.User)
{
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't cuff yourself!"));
return;
}
if (Broken)
{
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("The cuffs are broken!"));
return;
}
if (!eventArgs.Target.TryGetComponent<HandsComponent>(out var hands))
{
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("{0:theName} has no hands!", eventArgs.Target));
return;
}
if (cuffed.CuffedHandCount == hands.Count)
{
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("{0:theName} has no free hands to handcuff!", eventArgs.Target));
return;
}
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(
eventArgs.User.Transform.MapPosition,
eventArgs.Target.Transform.MapPosition,
_interactRange,
ignoredEnt: Owner))
{
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You are too far away to use the cuffs!"));
return;
}
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You start cuffing {0:theName}.", eventArgs.Target));
_notifyManager.PopupMessage(eventArgs.User, eventArgs.Target, Loc.GetString("{0:theName} starts cuffing you!", eventArgs.User));
_audioSystem.PlayFromEntity(StartCuffSound, Owner);
TryUpdateCuff(eventArgs.User, eventArgs.Target, cuffed);
}
/// <summary>
/// Update the cuffed state of an entity
/// </summary>
private async void TryUpdateCuff(IEntity user, IEntity target, CuffableComponent cuffs)
{
var cuffTime = CuffTime;
if (target.TryGetComponent<StunnableComponent>(out var stun) && stun.Stunned)
{
cuffTime = MathF.Max(0.1f, cuffTime - StunBonus);
}
var doAfterEventArgs = new DoAfterEventArgs(user, cuffTime, default, target)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
NeedHand = true
};
var result = await _doAfterSystem.DoAfter(doAfterEventArgs);
if (result != DoAfterStatus.Cancelled)
{
_audioSystem.PlayFromEntity(EndCuffSound, Owner);
_notifyManager.PopupMessage(user, user, Loc.GetString("You successfully cuff {0:theName}.", target));
_notifyManager.PopupMessage(target, target, Loc.GetString("You have been cuffed by {0:theName}!", user));
if (user.TryGetComponent<HandsComponent>(out var hands))
{
hands.Drop(Owner);
cuffs.AddNewCuffs(Owner);
}
else
{
Logger.Warning("Unable to remove handcuffs from player's hands! This should not happen!");
}
}
else
{
user.PopupMessage(user, Loc.GetString("You were interrupted while cuffing {0:theName}!", target));
target.PopupMessage(target, Loc.GetString("You interrupt {0:theName} while they are cuffing you!", user));
}
}
}
}

View File

@@ -1,9 +1,12 @@
using System;
#nullable enable
using Content.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -13,7 +16,8 @@ namespace Content.Server.GameObjects.Components.Atmos
[RegisterComponent]
public class AirtightComponent : Component, IMapInit
{
private SnapGridComponent _snapGrid;
[Dependency] private readonly IMapManager _mapManager = default!;
private (GridId, MapIndices) _lastPosition;
public override string Name => "Airtight";
@@ -28,7 +32,11 @@ namespace Content.Server.GameObjects.Components.Atmos
set
{
_airBlocked = value;
EntitySystem.Get<AtmosphereSystem>().GetGridAtmosphere(Owner.Transform.GridID)?.Revalidate(_snapGrid.Position);
if (Owner.TryGetComponent(out SnapGridComponent? snapGrid))
{
EntitySystem.Get<AtmosphereSystem>().GetGridAtmosphere(Owner.Transform.GridID)?.Invalidate(snapGrid.Position);
}
}
}
@@ -48,19 +56,23 @@ namespace Content.Server.GameObjects.Components.Atmos
base.Initialize();
// Using the SnapGrid is critical for the performance of the room builder, and thus if
// it is absent the component will not be airtight. An exception is much easier to track
// down than the object magically not being airtight, so throw one if the SnapGrid component
// it is absent the component will not be airtight. A warning is much easier to track
// down than the object magically not being airtight, so log one if the SnapGrid component
// is missing.
if (!Owner.TryGetComponent(out _snapGrid))
throw new Exception("Airtight entities must have a SnapGrid component");
if (!Owner.EnsureComponent(out SnapGridComponent _))
Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition.ToString()} didn't have a {nameof(SnapGridComponent)}");
UpdatePosition();
}
public void MapInit()
{
_snapGrid.OnPositionChanged += OnTransformMove;
_lastPosition = (Owner.Transform.GridID, _snapGrid.Position);
if (Owner.TryGetComponent(out SnapGridComponent? snapGrid))
{
snapGrid.OnPositionChanged += OnTransformMove;
_lastPosition = (Owner.Transform.GridID, snapGrid.Position);
}
UpdatePosition();
}
@@ -70,11 +82,16 @@ namespace Content.Server.GameObjects.Components.Atmos
_airBlocked = false;
_snapGrid.OnPositionChanged -= OnTransformMove;
if (Owner.TryGetComponent(out SnapGridComponent? snapGrid))
{
snapGrid.OnPositionChanged -= OnTransformMove;
}
if(_fixVacuum)
EntitySystem.Get<AtmosphereSystem>().GetGridAtmosphere(Owner.Transform.GridID)?
.FixVacuum(_snapGrid.Position);
if (_fixVacuum)
{
var mapIndices = Owner.Transform.GridPosition.ToMapIndices(_mapManager);
EntitySystem.Get<AtmosphereSystem>().GetGridAtmosphere(Owner.Transform.GridID)?.FixVacuum(mapIndices);
}
UpdatePosition();
}
@@ -83,15 +100,22 @@ namespace Content.Server.GameObjects.Components.Atmos
{
UpdatePosition(_lastPosition.Item1, _lastPosition.Item2);
UpdatePosition();
_lastPosition = (Owner.Transform.GridID, _snapGrid.Position);
if (Owner.TryGetComponent(out SnapGridComponent? snapGrid))
{
_lastPosition = (Owner.Transform.GridID, snapGrid.Position);
}
}
private void UpdatePosition() => UpdatePosition(Owner.Transform.GridID, _snapGrid.Position);
private void UpdatePosition()
{
var mapIndices = Owner.Transform.GridPosition.ToMapIndices(_mapManager);
UpdatePosition(Owner.Transform.GridID, mapIndices);
}
private void UpdatePosition(GridId gridId, MapIndices pos)
{
EntitySystem.Get<AtmosphereSystem>().GetGridAtmosphere(gridId)?.Invalidate(pos);
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility;
using Content.Shared.Atmos;
using Content.Shared.GameObjects.Components;
using Content.Shared.GameObjects.EntitySystems;
@@ -16,30 +17,32 @@ using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Atmos
{
[RegisterComponent]
public class GasAnalyzerComponent : SharedGasAnalyzerComponent, IAfterInteract, IDropped, IUse
{
#pragma warning disable 649
[Dependency] private IServerNotifyManager _notifyManager = default!;
[Dependency] private IMapManager _mapManager = default!;
#pragma warning restore 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
private BoundUserInterface _userInterface = default!;
private GasAnalyzerDanger _pressureDanger;
private float _timeSinceSync;
private const float TimeBetweenSyncs = 2f;
private bool _checkPlayer = false; // Check at the player pos or at some other tile?
private GridCoordinates? _position; // The tile that we scanned
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GasAnalyzerUiKey.Key);
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(GasAnalyzerUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage;
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage;
}
}
public override ComponentState GetComponentState()
@@ -56,7 +59,7 @@ namespace Content.Server.GameObjects.Components.Atmos
{
_checkPlayer = true;
_position = null;
_userInterface.Open(session);
UserInterface?.Open(session);
UpdateUserInterface();
Resync();
}
@@ -71,7 +74,7 @@ namespace Content.Server.GameObjects.Components.Atmos
{
_checkPlayer = false;
_position = pos;
_userInterface.Open(session);
UserInterface?.Open(session);
UpdateUserInterface();
Resync();
}
@@ -79,7 +82,7 @@ namespace Content.Server.GameObjects.Components.Atmos
public void CloseInterface(IPlayerSession session)
{
_position = null;
_userInterface.Close(session);
UserInterface?.Close(session);
Resync();
}
@@ -123,10 +126,15 @@ namespace Content.Server.GameObjects.Components.Atmos
private void UpdateUserInterface()
{
if (UserInterface == null)
{
return;
}
string? error = null;
// Check if the player is still holding the gas analyzer => if not, don't update
foreach (var session in _userInterface.SubscribedSessions)
foreach (var session in UserInterface.SubscribedSessions)
{
if (session.AttachedEntity == null)
return;
@@ -151,12 +159,13 @@ namespace Content.Server.GameObjects.Components.Atmos
pos = _position.Value;
}
var gam = EntitySystem.Get<AtmosphereSystem>().GetGridAtmosphere(pos.GridID);
var atmosSystem = EntitySystem.Get<AtmosphereSystem>();
var gam = atmosSystem.GetGridAtmosphere(pos.GridID);
var tile = gam?.GetTile(pos).Air;
if (tile == null)
{
error = "No Atmosphere!";
_userInterface.SetState(
UserInterface.SetState(
new GasAnalyzerBoundUserInterfaceState(
0,
0,
@@ -166,16 +175,17 @@ namespace Content.Server.GameObjects.Components.Atmos
}
var gases = new List<GasEntry>();
for (int i = 0; i < Atmospherics.TotalNumberOfGases; i++)
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
{
var gas = Atmospherics.GetGas(i);
var gas = atmosSystem.GetGas(i);
if (tile.Gases[i] <= Atmospherics.GasMinMoles) continue;
gases.Add(new GasEntry(gas.Name, tile.Gases[i], gas.Color));
}
_userInterface.SetState(
UserInterface.SetState(
new GasAnalyzerBoundUserInterfaceState(
tile.Pressure,
tile.Temperature,

View File

@@ -6,16 +6,23 @@ using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Atmos
{
[RegisterComponent]
public class GasMixtureComponent : Component
public class GasMixtureHolderComponent : Component
{
public override string Name => "GasMixture";
public override string Name => "GasMixtureHolder";
[ViewVariables] public GasMixture GasMixture { get; set; } = new GasMixture();
[ViewVariables] public GasMixture GasMixture { get; set; }
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, x => GasMixture.Volume, "volume", 0f);
GasMixture = new GasMixture();
serializer.DataReadWriteFunction(
"volume",
0f,
vol => GasMixture.Volume = vol,
() => GasMixture.Volume);
}
}
}

View File

@@ -1,9 +1,12 @@
using System;
#nullable enable
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Content.Server.Atmos;
using Content.Server.GameObjects.Components.Atmos.Piping;
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Content.Shared.Atmos;
using Content.Shared.Maps;
using Robust.Server.Interfaces.GameObjects;
@@ -33,7 +36,7 @@ namespace Content.Server.GameObjects.Components.Atmos
/// <summary>
/// Check current execution time every n instances processed.
/// </summary>
private const int LagCheckIterations = 15;
private const int LagCheckIterations = 30;
/// <summary>
/// Max milliseconds allowed for atmos updates.
@@ -47,32 +50,94 @@ namespace Content.Server.GameObjects.Components.Atmos
public override string Name => "GridAtmosphere";
private bool _paused = false;
private float _timer = 0f;
private Stopwatch _stopwatch = new Stopwatch();
[ViewVariables]
public int UpdateCounter { get; private set; } = 0;
private IMapGrid _grid;
[ViewVariables]
private double _tileEqualizeLastProcess;
[ViewVariables]
private readonly HashSet<ExcitedGroup> _excitedGroups = new HashSet<ExcitedGroup>(1000);
[ViewVariables]
private int ExcitedGroupCount => _excitedGroups.Count;
[ViewVariables]
private double _excitedGroupLastProcess;
[ViewVariables]
private readonly Dictionary<MapIndices, TileAtmosphere> _tiles = new Dictionary<MapIndices, TileAtmosphere>(1000);
[ViewVariables]
private readonly HashSet<TileAtmosphere> _activeTiles = new HashSet<TileAtmosphere>(1000);
[ViewVariables]
private int ActiveTilesCount => _activeTiles.Count;
[ViewVariables]
private double _activeTilesLastProcess;
[ViewVariables]
private readonly HashSet<TileAtmosphere> _hotspotTiles = new HashSet<TileAtmosphere>(1000);
[ViewVariables]
private int HotspotTilesCount => _hotspotTiles.Count;
[ViewVariables]
private double _hotspotsLastProcess;
[ViewVariables]
private readonly HashSet<TileAtmosphere> _superconductivityTiles = new HashSet<TileAtmosphere>(1000);
[ViewVariables]
private int SuperconductivityTilesCount => _superconductivityTiles.Count;
[ViewVariables]
private double _superconductivityLastProcess;
[ViewVariables]
private readonly HashSet<MapIndices> _invalidatedCoords = new HashSet<MapIndices>(1000);
[ViewVariables]
private int InvalidatedCoordsCount => _invalidatedCoords.Count;
[ViewVariables]
private HashSet<TileAtmosphere> _highPressureDelta = new HashSet<TileAtmosphere>(1000);
[ViewVariables]
private int HighPressureDeltaCount => _highPressureDelta.Count;
[ViewVariables]
private double _highPressureDeltaLastProcess;
[ViewVariables]
private readonly HashSet<IPipeNet> _pipeNets = new HashSet<IPipeNet>();
[ViewVariables]
private double _pipeNetLastProcess;
[ViewVariables]
private readonly HashSet<PipeNetDeviceComponent> _pipeNetDevices = new HashSet<PipeNetDeviceComponent>();
[ViewVariables]
private double _pipeNetDevicesLastProcess;
[ViewVariables]
private Queue<TileAtmosphere> _currentRunTiles = new Queue<TileAtmosphere>();
[ViewVariables]
private Queue<ExcitedGroup> _currentRunExcitedGroups = new Queue<ExcitedGroup>();
[ViewVariables]
private Queue<IPipeNet> _currentRunPipeNet = new Queue<IPipeNet>();
[ViewVariables]
private Queue<PipeNetDeviceComponent> _currentRunPipeNetDevice = new Queue<PipeNetDeviceComponent>();
[ViewVariables]
private ProcessState _state = ProcessState.TileEqualize;
@@ -84,47 +149,47 @@ namespace Content.Server.GameObjects.Components.Atmos
HighPressureDelta,
Hotspots,
Superconductivity,
PipeNet,
PipeNetDevices,
}
/// <inheritdoc />
public void PryTile(MapIndices indices)
{
if (!Owner.TryGetComponent(out IMapGridComponent? mapGridComponent)) return;
if (IsSpace(indices) || IsAirBlocked(indices)) return;
var tile = _grid.GetTileRef(indices).Tile;
var mapGrid = mapGridComponent.Grid;
var tile = mapGrid.GetTileRef(indices).Tile;
var tileDefinitionManager = IoCManager.Resolve<ITileDefinitionManager>();
var tileDef = (ContentTileDefinition)tileDefinitionManager[tile.TypeId];
var underplating = tileDefinitionManager["underplating"];
_grid.SetTile(indices, new Tile(underplating.TileId));
mapGrid.SetTile(indices, new Tile(underplating.TileId));
//Actually spawn the relevant tile item at the right position and give it some offset to the corner.
var tileItem = IoCManager.Resolve<IServerEntityManager>().SpawnEntity(tileDef.ItemDropPrototypeName, new GridCoordinates(indices.X, indices.Y, _grid));
var tileItem = IoCManager.Resolve<IServerEntityManager>().SpawnEntity(tileDef.ItemDropPrototypeName, new GridCoordinates(indices.X, indices.Y, mapGrid));
tileItem.Transform.WorldPosition += (0.2f, 0.2f);
}
public override void Initialize()
{
base.Initialize();
_grid = Owner.GetComponent<IMapGridComponent>().Grid;
RepopulateTiles();
}
public override void OnAdd()
{
base.OnAdd();
_grid = Owner.GetComponent<IMapGridComponent>().Grid;
RepopulateTiles();
}
public void RepopulateTiles()
{
foreach (var tile in _grid.GetAllTiles())
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
foreach (var tile in mapGrid.Grid.GetAllTiles())
{
if(!_tiles.ContainsKey(tile.GridIndices))
_tiles.Add(tile.GridIndices, new TileAtmosphere(this, tile.GridIndex, tile.GridIndices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C}));
@@ -145,69 +210,66 @@ namespace Content.Server.GameObjects.Components.Atmos
private void Revalidate()
{
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
foreach (var indices in _invalidatedCoords.ToArray())
{
Revalidate(indices);
var tile = GetTile(indices);
AddActiveTile(tile);
if (tile == null)
{
tile = new TileAtmosphere(this, mapGrid.Grid.Index, indices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C});
_tiles[indices] = tile;
}
if (IsSpace(indices))
{
tile.Air = new GasMixture(GetVolumeForCells(1));
tile.Air.MarkImmutable();
_tiles[indices] = tile;
} else if (IsAirBlocked(indices))
{
tile.Air = null;
}
else
{
var obs = GetObstructingComponent(indices);
if (obs != null)
{
if (tile.Air == null && obs.FixVacuum)
{
FixVacuum(tile.GridIndices);
}
}
tile.Air ??= new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C};
}
tile.UpdateAdjacent();
tile.UpdateVisuals();
for (var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
var otherIndices = indices.Offset(direction.ToDirection());
var otherTile = GetTile(otherIndices);
AddActiveTile(otherTile);
otherTile?.UpdateAdjacent(direction.GetOpposite());
}
}
_invalidatedCoords.Clear();
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Revalidate(MapIndices indices)
{
var tile = GetTile(indices);
AddActiveTile(tile);
if (tile == null)
{
tile = new TileAtmosphere(this, _grid.Index, indices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C});
_tiles[indices] = tile;
}
if (IsSpace(indices))
{
tile.Air = new GasMixture(GetVolumeForCells(1));
tile.Air.MarkImmutable();
_tiles[indices] = tile;
} else if (IsAirBlocked(indices))
{
tile.Air = null;
}
else
{
var obs = GetObstructingComponent(indices);
if (obs != null)
{
if (tile.Air == null && obs.FixVacuum)
{
FixVacuum(tile.GridIndices);
}
}
tile.Air ??= new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C};
}
tile.UpdateAdjacent();
tile.UpdateVisuals();
foreach (var direction in Cardinal)
{
var otherIndices = indices.Offset(direction);
var otherTile = GetTile(otherIndices);
AddActiveTile(otherTile);
otherTile?.UpdateAdjacent(direction.GetOpposite());
}
}
/// <inheritdoc />
public void FixVacuum(MapIndices indices)
{
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
var tile = GetTile(indices);
if (tile?.GridIndex != _grid.Index) return;
if (tile?.GridIndex != mapGrid.Grid.Index) return;
var adjacent = GetAdjacentTiles(indices);
tile.Air = new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C};
_tiles[indices] = tile;
@@ -224,16 +286,17 @@ namespace Content.Server.GameObjects.Components.Atmos
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddActiveTile(TileAtmosphere tile)
public void AddActiveTile(TileAtmosphere? tile)
{
if (tile?.GridIndex != _grid.Index || tile?.Air == null) return;
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
if (tile?.GridIndex != mapGrid.Grid.Index || tile?.Air == null) return;
tile.Excited = true;
_activeTiles.Add(tile);
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveActiveTile(TileAtmosphere tile)
public void RemoveActiveTile(TileAtmosphere? tile)
{
if (tile == null) return;
_activeTiles.Remove(tile);
@@ -243,27 +306,29 @@ namespace Content.Server.GameObjects.Components.Atmos
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHotspotTile(TileAtmosphere tile)
public void AddHotspotTile(TileAtmosphere? tile)
{
if (tile?.GridIndex != _grid.Index || tile?.Air == null) return;
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
if (tile?.GridIndex != mapGrid.Grid.Index || tile?.Air == null) return;
_hotspotTiles.Add(tile);
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveHotspotTile(TileAtmosphere tile)
public void RemoveHotspotTile(TileAtmosphere? tile)
{
if (tile == null) return;
_hotspotTiles.Remove(tile);
}
public void AddSuperconductivityTile(TileAtmosphere tile)
public void AddSuperconductivityTile(TileAtmosphere? tile)
{
if (tile?.GridIndex != _grid.Index) return;
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
if (tile?.GridIndex != mapGrid.Grid.Index) return;
_superconductivityTiles.Add(tile);
}
public void RemoveSuperconductivityTile(TileAtmosphere tile)
public void RemoveSuperconductivityTile(TileAtmosphere? tile)
{
if (tile == null) return;
_superconductivityTiles.Remove(tile);
@@ -271,9 +336,10 @@ namespace Content.Server.GameObjects.Components.Atmos
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHighPressureDelta(TileAtmosphere tile)
public void AddHighPressureDelta(TileAtmosphere? tile)
{
if (tile?.GridIndex != _grid.Index) return;
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
if (tile?.GridIndex != mapGrid.Grid.Index) return;
_highPressureDelta.Add(tile);
}
@@ -298,23 +364,43 @@ namespace Content.Server.GameObjects.Components.Atmos
_excitedGroups.Remove(excitedGroup);
}
/// <inheritdoc />
public TileAtmosphere GetTile(GridCoordinates coordinates)
public void AddPipeNet(IPipeNet pipeNet)
{
return GetTile(coordinates.ToMapIndices(_mapManager));
_pipeNets.Add(pipeNet);
}
public void RemovePipeNet(IPipeNet pipeNet)
{
_pipeNets.Remove(pipeNet);
}
public void AddPipeNetDevice(PipeNetDeviceComponent pipeNetDevice)
{
_pipeNetDevices.Add(pipeNetDevice);
}
public void RemovePipeNetDevice(PipeNetDeviceComponent pipeNetDevice)
{
_pipeNetDevices.Remove(pipeNetDevice);
}
/// <inheritdoc />
public TileAtmosphere GetTile(MapIndices indices)
public TileAtmosphere? GetTile(GridCoordinates coordinates, bool createSpace = true)
{
return GetTile(coordinates.ToMapIndices(_mapManager), createSpace);
}
/// <inheritdoc />
public TileAtmosphere? GetTile(MapIndices indices, bool createSpace = true)
{
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return null;
if (_tiles.TryGetValue(indices, out var tile)) return tile;
// We don't have that tile!
if (IsSpace(indices))
if (IsSpace(indices) && createSpace)
{
var space = new TileAtmosphere(this, _grid.Index, indices, new GasMixture(int.MaxValue){Temperature = Atmospherics.TCMB});
space.Air.MarkImmutable();
return space;
return new TileAtmosphere(this, mapGrid.Grid.Index, indices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.TCMB}, true);
}
return null;
@@ -331,32 +417,34 @@ namespace Content.Server.GameObjects.Components.Atmos
public bool IsSpace(MapIndices indices)
{
// TODO ATMOS use ContentTileDefinition to define in YAML whether or not a tile is considered space
return _grid.GetTileRef(indices).Tile.IsEmpty;
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return default;
return mapGrid.Grid.GetTileRef(indices).Tile.IsEmpty;
}
public Dictionary<Direction, TileAtmosphere> GetAdjacentTiles(MapIndices indices, bool includeAirBlocked = false)
public Dictionary<AtmosDirection, TileAtmosphere> GetAdjacentTiles(MapIndices indices, bool includeAirBlocked = false)
{
var sides = new Dictionary<Direction, TileAtmosphere>();
foreach (var dir in Cardinal)
var sides = new Dictionary<AtmosDirection, TileAtmosphere>();
for (var i = 0; i < Atmospherics.Directions; i++)
{
var side = indices.Offset(dir);
var direction = (AtmosDirection) (1 << i);
var side = indices.Offset(direction.ToDirection());
var tile = GetTile(side);
if(tile?.Air != null || includeAirBlocked)
sides[dir] = tile;
if (tile != null && (tile.Air != null || includeAirBlocked))
sides[direction] = tile;
}
return sides;
}
/// <inheritdoc />
public int HighPressureDeltaCount => _highPressureDelta.Count;
public long EqualizationQueueCycleControl { get; set; }
/// <inheritdoc />
public float GetVolumeForCells(int cellCount)
{
return _grid.TileSize * cellCount * Atmospherics.CellVolume;
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return default;
return mapGrid.Grid.TileSize * cellCount * Atmospherics.CellVolume;
}
/// <inheritdoc />
@@ -376,27 +464,83 @@ namespace Content.Server.GameObjects.Components.Atmos
switch (_state)
{
case ProcessState.TileEqualize:
ProcessTileEqualize();
if (!ProcessTileEqualize(_paused))
{
_paused = true;
return;
}
_paused = false;
_state = ProcessState.ActiveTiles;
return;
case ProcessState.ActiveTiles:
ProcessActiveTiles();
if (!ProcessActiveTiles(_paused))
{
_paused = true;
return;
}
_paused = false;
_state = ProcessState.ExcitedGroups;
return;
case ProcessState.ExcitedGroups:
ProcessExcitedGroups();
if (!ProcessExcitedGroups(_paused))
{
_paused = true;
return;
}
_paused = false;
_state = ProcessState.HighPressureDelta;
return;
case ProcessState.HighPressureDelta:
ProcessHighPressureDelta();
if (!ProcessHighPressureDelta(_paused))
{
_paused = true;
return;
}
_paused = false;
_state = ProcessState.Hotspots;
break;
case ProcessState.Hotspots:
ProcessHotspots();
if (!ProcessHotspots(_paused))
{
_paused = true;
return;
}
_paused = false;
_state = ProcessState.Superconductivity;
break;
case ProcessState.Superconductivity:
ProcessSuperconductivity();
if (!ProcessSuperconductivity(_paused))
{
_paused = true;
return;
}
_paused = false;
_state = ProcessState.PipeNet;
break;
case ProcessState.PipeNet:
if (!ProcessPipeNets(_paused))
{
_paused = true;
return;
}
_paused = false;
_state = ProcessState.PipeNetDevices;
break;
case ProcessState.PipeNetDevices:
if (!ProcessPipeNetDevices(_paused))
{
_paused = true;
return;
}
_paused = false;
_state = ProcessState.TileEqualize;
break;
}
@@ -404,47 +548,71 @@ namespace Content.Server.GameObjects.Components.Atmos
UpdateCounter++;
}
public void ProcessTileEqualize()
public bool ProcessTileEqualize(bool resumed = false)
{
_stopwatch.Restart();
if(!resumed)
_currentRunTiles = new Queue<TileAtmosphere>(_activeTiles);
var number = 0;
foreach (var tile in _activeTiles.ToArray())
while (_currentRunTiles.Count > 0)
{
var tile = _currentRunTiles.Dequeue();
tile.EqualizePressureInZone(UpdateCounter);
if (number++ < LagCheckIterations) continue;
number = 0;
// Process the rest next time.
if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds)
return;
{
_tileEqualizeLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return false;
}
}
_tileEqualizeLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return true;
}
public void ProcessActiveTiles()
public bool ProcessActiveTiles(bool resumed = false)
{
_stopwatch.Restart();
if(!resumed)
_currentRunTiles = new Queue<TileAtmosphere>(_activeTiles);
var number = 0;
foreach (var tile in _activeTiles.ToArray())
while (_currentRunTiles.Count > 0)
{
var tile = _currentRunTiles.Dequeue();
tile.ProcessCell(UpdateCounter);
if (number++ < LagCheckIterations) continue;
number = 0;
// Process the rest next time.
if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds)
return;
{
_activeTilesLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return false;
}
}
_activeTilesLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return true;
}
public void ProcessExcitedGroups()
public bool ProcessExcitedGroups(bool resumed = false)
{
_stopwatch.Restart();
if(!resumed)
_currentRunExcitedGroups = new Queue<ExcitedGroup>(_excitedGroups);
var number = 0;
foreach (var excitedGroup in _excitedGroups.ToArray())
while (_currentRunExcitedGroups.Count > 0)
{
var excitedGroup = _currentRunExcitedGroups.Dequeue();
excitedGroup.BreakdownCooldown++;
excitedGroup.DismantleCooldown++;
@@ -458,17 +626,27 @@ namespace Content.Server.GameObjects.Components.Atmos
number = 0;
// Process the rest next time.
if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds)
return;
{
_excitedGroupLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return false;
}
}
_excitedGroupLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return true;
}
public void ProcessHighPressureDelta()
public bool ProcessHighPressureDelta(bool resumed = false)
{
_stopwatch.Restart();
if(!resumed)
_currentRunTiles = new Queue<TileAtmosphere>(_highPressureDelta);
var number = 0;
foreach (var tile in _highPressureDelta.ToArray())
while (_currentRunTiles.Count > 0)
{
var tile = _currentRunTiles.Dequeue();
tile.HighPressureMovements();
tile.PressureDifference = 0f;
tile.PressureSpecificTarget = null;
@@ -478,47 +656,129 @@ namespace Content.Server.GameObjects.Components.Atmos
number = 0;
// Process the rest next time.
if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds)
return;
{
_highPressureDeltaLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return false;
}
}
_highPressureDeltaLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return true;
}
private void ProcessHotspots()
private bool ProcessHotspots(bool resumed = false)
{
_stopwatch.Restart();
if(!resumed)
_currentRunTiles = new Queue<TileAtmosphere>(_hotspotTiles);
var number = 0;
foreach (var hotspot in _hotspotTiles.ToArray())
while (_currentRunTiles.Count > 0)
{
var hotspot = _currentRunTiles.Dequeue();
hotspot.ProcessHotspot();
if (number++ < LagCheckIterations) continue;
number = 0;
// Process the rest next time.
if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds)
return;
{
_hotspotsLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return false;
}
}
_hotspotsLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return true;
}
private void ProcessSuperconductivity()
private bool ProcessSuperconductivity(bool resumed = false)
{
_stopwatch.Restart();
if(!resumed)
_currentRunTiles = new Queue<TileAtmosphere>(_superconductivityTiles);
var number = 0;
foreach (var superconductivity in _superconductivityTiles.ToArray())
while (_currentRunTiles.Count > 0)
{
var superconductivity = _currentRunTiles.Dequeue();
superconductivity.Superconduct();
if (number++ < LagCheckIterations) continue;
number = 0;
// Process the rest next time.
if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds)
return;
{
_superconductivityLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return false;
}
}
_superconductivityLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return true;
}
private AirtightComponent GetObstructingComponent(MapIndices indices)
private bool ProcessPipeNets(bool resumed = false)
{
foreach (var v in _grid.GetSnapGridCell(indices, SnapGridOffset.Center))
_stopwatch.Restart();
if(!resumed)
_currentRunPipeNet = new Queue<IPipeNet>(_pipeNets);
var number = 0;
while (_currentRunPipeNet.Count > 0)
{
var pipenet = _currentRunPipeNet.Dequeue();
pipenet.Update();
if (number++ < LagCheckIterations) continue;
number = 0;
// Process the rest next time.
if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds)
{
_pipeNetLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return false;
}
}
_pipeNetLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return true;
}
private bool ProcessPipeNetDevices(bool resumed = false)
{
_stopwatch.Restart();
if(!resumed)
_currentRunPipeNetDevice = new Queue<PipeNetDeviceComponent>(_pipeNetDevices);
var number = 0;
while (_currentRunPipeNet.Count > 0)
{
var device = _currentRunPipeNetDevice.Dequeue();
device.Update();
if (number++ < LagCheckIterations) continue;
number = 0;
// Process the rest next time.
if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds)
{
_pipeNetDevicesLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return false;
}
}
_pipeNetDevicesLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return true;
}
private AirtightComponent? GetObstructingComponent(MapIndices indices)
{
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return default;
foreach (var v in mapGrid.Grid.GetSnapGridCell(indices, SnapGridOffset.Center))
{
if (v.Owner.TryGetComponent<AirtightComponent>(out var ac))
return ac;
@@ -527,12 +787,6 @@ namespace Content.Server.GameObjects.Components.Atmos
return null;
}
private static readonly Direction[] Cardinal =
new []
{
Direction.North, Direction.East, Direction.South, Direction.West
};
public void Dispose()
{
@@ -541,22 +795,24 @@ namespace Content.Server.GameObjects.Components.Atmos
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
if (serializer.Reading)
if (serializer.Reading &&
Owner.TryGetComponent(out IMapGridComponent? mapGrid))
{
var gridId = Owner.GetComponent<IMapGridComponent>().Grid.Index;
var gridId = mapGrid.Grid.Index;
if (!serializer.TryReadDataField("uniqueMixes", out List<GasMixture> uniqueMixes) ||
!serializer.TryReadDataField("tiles", out Dictionary<MapIndices, int> tiles))
if (!serializer.TryReadDataField("uniqueMixes", out List<GasMixture>? uniqueMixes) ||
!serializer.TryReadDataField("tiles", out Dictionary<MapIndices, int>? tiles))
return;
_tiles.Clear();
foreach (var (indices, mix) in tiles)
foreach (var (indices, mix) in tiles!)
{
_tiles.Add(indices, new TileAtmosphere(this, gridId, indices, (GasMixture)uniqueMixes[mix].Clone()));
_tiles.Add(indices, new TileAtmosphere(this, gridId, indices, (GasMixture)uniqueMixes![mix].Clone()));
Invalidate(indices);
}
} else if (serializer.Writing)
}
else if (serializer.Writing)
{
var uniqueMixes = new List<GasMixture>();
var uniqueMixHash = new Dictionary<GasMixture, int>();

View File

@@ -0,0 +1,50 @@
using Content.Server.Atmos;
using Content.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Log;
namespace Content.Server.GameObjects.Components.Atmos.Piping
{
/// <summary>
/// Adds itself to a <see cref="IGridAtmosphereComponent"/> to be updated by.
/// TODO: Make compatible with unanchoring/anchoring. Currently assumes that the Owner does not move.
/// </summary>
public abstract class PipeNetDeviceComponent : Component
{
public abstract void Update();
protected IGridAtmosphereComponent JoinedGridAtmos { get; private set; }
public override void Initialize()
{
base.Initialize();
JoinGridAtmos();
}
public override void OnRemove()
{
base.OnRemove();
LeaveGridAtmos();
}
private void JoinGridAtmos()
{
var gridAtmos = EntitySystem.Get<AtmosphereSystem>()
.GetGridAtmosphere(Owner.Transform.GridID);
if (gridAtmos == null)
{
Logger.Error($"{nameof(PipeNetDeviceComponent)} on entity {Owner.Uid} could not find an {nameof(IGridAtmosphereComponent)}.");
return;
}
JoinedGridAtmos = gridAtmos;
JoinedGridAtmos.AddPipeNetDevice(this);
}
private void LeaveGridAtmos()
{
JoinedGridAtmos?.RemovePipeNetDevice(this);
JoinedGridAtmos = null;
}
}
}

View File

@@ -0,0 +1,68 @@
using Content.Server.Atmos;
using Content.Server.GameObjects.Components.NodeContainer;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using System.Linq;
namespace Content.Server.GameObjects.Components.Atmos.Piping
{
/// <summary>
/// Transfer gas from one <see cref="PipeNode"/> to another.
/// </summary>
public abstract class BasePumpComponent : PipeNetDeviceComponent
{
/// <summary>
/// Needs to be same <see cref="PipeDirection"/> as that of a <see cref="Pipe"/> on this entity.
/// </summary>
[ViewVariables]
private PipeDirection _inletDirection;
/// <summary>
/// Needs to be same <see cref="PipeDirection"/> as that of a <see cref="Pipe"/> on this entity.
/// </summary>
[ViewVariables]
private PipeDirection _outletDirection;
[ViewVariables]
private PipeNode _inletPipe;
[ViewVariables]
private PipeNode _outletPipe;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _inletDirection, "inletDirection", PipeDirection.None);
serializer.DataField(ref _outletDirection, "outletDirection", PipeDirection.None);
}
public override void Initialize()
{
base.Initialize();
if (!Owner.TryGetComponent<NodeContainerComponent>(out var container))
{
JoinedGridAtmos?.RemovePipeNetDevice(this);
Logger.Error($"{typeof(BasePumpComponent)} on entity {Owner.Uid} did not have a {nameof(NodeContainerComponent)}.");
return;
}
var pipeNodes = container.Nodes.OfType<PipeNode>();
_inletPipe = pipeNodes.Where(pipe => pipe.PipeDirection == _inletDirection).FirstOrDefault();
_outletPipe = pipeNodes.Where(pipe => pipe.PipeDirection == _outletDirection).FirstOrDefault();
if (_inletPipe == null | _outletPipe == null)
{
JoinedGridAtmos?.RemovePipeNetDevice(this);
Logger.Error($"{typeof(BasePumpComponent)} on entity {Owner.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}.");
return;
}
}
public override void Update()
{
PumpGas(_inletPipe.Air, _outletPipe.Air);
}
protected abstract void PumpGas(GasMixture inletGas, GasMixture outletGas);
}
}

View File

@@ -0,0 +1,21 @@
using Content.Server.Atmos;
using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Atmos.Piping
{
/// <summary>
/// Placeholder example of pump functionality.
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(BasePumpComponent))]
public class DebugPumpComponent : BasePumpComponent
{
public override string Name => "DebugPump";
protected override void PumpGas(GasMixture inletGas, GasMixture outletGas)
{
outletGas.Merge(inletGas);
inletGas.Clear();
}
}
}

View File

@@ -0,0 +1,52 @@
using Content.Server.Atmos;
using Content.Server.GameObjects.Components.NodeContainer;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Log;
using Robust.Shared.ViewVariables;
using System.Linq;
namespace Content.Server.GameObjects.Components.Atmos.Piping
{
/// <summary>
/// Transfers gas from the tile it is on to a <see cref="PipeNode"/>.
/// </summary>
public abstract class BaseSiphonComponent : PipeNetDeviceComponent
{
[ViewVariables]
private PipeNode _scrubberOutlet;
private AtmosphereSystem _atmosSystem;
public override void Initialize()
{
base.Initialize();
_atmosSystem = EntitySystem.Get<AtmosphereSystem>();
if (!Owner.TryGetComponent<NodeContainerComponent>(out var container))
{
JoinedGridAtmos?.RemovePipeNetDevice(this);
Logger.Error($"{typeof(BaseSiphonComponent)} on entity {Owner.Uid} did not have a {nameof(NodeContainerComponent)}.");
return;
}
_scrubberOutlet = container.Nodes.OfType<PipeNode>().FirstOrDefault();
if (_scrubberOutlet == null)
{
JoinedGridAtmos?.RemovePipeNetDevice(this);
Logger.Error($"{typeof(BaseSiphonComponent)} on entity {Owner.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}.");
return;
}
}
public override void Update()
{
var tileAtmos = AtmosHelpers.GetTileAtmosphere(Owner.Transform.GridPosition);
if (tileAtmos == null)
return;
ScrubGas(tileAtmos.Air, _scrubberOutlet.Air);
_atmosSystem.GetGridAtmosphere(Owner.Transform.GridID).Invalidate(tileAtmos.GridIndices);
}
protected abstract void ScrubGas(GasMixture inletGas, GasMixture outletGas);
}
}

View File

@@ -0,0 +1,21 @@
using Content.Server.Atmos;
using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Atmos.Piping
{
/// <summary>
/// Placeholder example of scrubber functionality.
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(BaseSiphonComponent))]
public class DebugSiphonComponent : BaseSiphonComponent
{
public override string Name => "DebugSiphon";
protected override void ScrubGas(GasMixture inletGas, GasMixture outletGas)
{
outletGas.Merge(inletGas);
inletGas.Clear();
}
}
}

View File

@@ -0,0 +1,54 @@
using Content.Server.Atmos;
using Content.Server.GameObjects.Components.NodeContainer;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Log;
using Robust.Shared.ViewVariables;
using System.Linq;
namespace Content.Server.GameObjects.Components.Atmos.Piping
{
/// <summary>
/// Transfers gas from a <see cref="PipeNode"/> to the tile it is on.
/// </summary>
public abstract class BaseVentComponent : PipeNetDeviceComponent
{
[ViewVariables]
private PipeNode _ventInlet;
private AtmosphereSystem _atmosSystem;
public override void Initialize()
{
base.Initialize();
_atmosSystem = EntitySystem.Get<AtmosphereSystem>();
if (!Owner.TryGetComponent<NodeContainerComponent>(out var container))
{
JoinedGridAtmos?.RemovePipeNetDevice(this);
Logger.Error($"{typeof(BaseVentComponent)} on entity {Owner.Uid} did not have a {nameof(NodeContainerComponent)}.");
return;
}
_ventInlet = container.Nodes.OfType<PipeNode>().FirstOrDefault();
if (_ventInlet == null)
{
JoinedGridAtmos?.RemovePipeNetDevice(this);
Logger.Error($"{typeof(BaseVentComponent)} on entity {Owner.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}.");
return;
}
}
public override void Update()
{
var tileAtmos = AtmosHelpers.GetTileAtmosphere(Owner.Transform.GridPosition);
if (tileAtmos == null)
return;
VentGas(_ventInlet.Air, tileAtmos.Air);
_atmosSystem.GetGridAtmosphere(Owner.Transform.GridID).Invalidate(tileAtmos.GridIndices);
}
protected abstract void VentGas(GasMixture inletGas, GasMixture outletGas);
}
}

View File

@@ -0,0 +1,21 @@
using Content.Server.Atmos;
using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Atmos.Piping
{
/// <summary>
/// Placeholder example of vent functionality.
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(BaseVentComponent))]
public class DebugVentComponent : BaseVentComponent
{
public override string Name => "DebugVent";
protected override void VentGas(GasMixture inletGas, GasMixture outletGas)
{
outletGas.Merge(inletGas);
inletGas.Clear();
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Linq;
#nullable enable
using System.Linq;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Robust.Server.GameObjects;
using Robust.Server.Interfaces.GameObjects;
@@ -19,18 +20,13 @@ namespace Content.Server.GameObjects.Components.BarSign
{
public override string Name => "BarSign";
#pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager;
[Dependency] private readonly IRobustRandom _robustRandom;
#pragma warning restore 649
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
private string _currentSign;
private PowerReceiverComponent _power;
private SpriteComponent _sprite;
private string? _currentSign;
[ViewVariables(VVAccess.ReadWrite)]
public string CurrentSign
public string? CurrentSign
{
get => _currentSign;
set
@@ -40,6 +36,8 @@ namespace Content.Server.GameObjects.Components.BarSign
}
}
private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
private void UpdateSignInfo()
{
if (_currentSign == null)
@@ -53,15 +51,18 @@ namespace Content.Server.GameObjects.Components.BarSign
return;
}
if (!_power.Powered)
if (Owner.TryGetComponent(out SpriteComponent? sprite))
{
_sprite.LayerSetState(0, "empty");
_sprite.LayerSetShader(0, "shaded");
}
else
{
_sprite.LayerSetState(0, prototype.Icon);
_sprite.LayerSetShader(0, "unshaded");
if (!Powered)
{
sprite.LayerSetState(0, "empty");
sprite.LayerSetShader(0, "shaded");
}
else
{
sprite.LayerSetState(0, prototype.Icon);
sprite.LayerSetShader(0, "unshaded");
}
}
if (!string.IsNullOrEmpty(prototype.Name))
@@ -80,21 +81,25 @@ namespace Content.Server.GameObjects.Components.BarSign
{
base.Initialize();
_power = Owner.GetComponent<PowerReceiverComponent>();
_sprite = Owner.GetComponent<SpriteComponent>();
_power.OnPowerStateChanged += PowerOnOnPowerStateChanged;
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
receiver.OnPowerStateChanged += PowerOnOnPowerStateChanged;
}
UpdateSignInfo();
}
public override void OnRemove()
{
_power.OnPowerStateChanged -= PowerOnOnPowerStateChanged;
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
receiver.OnPowerStateChanged -= PowerOnOnPowerStateChanged;
}
base.OnRemove();
}
private void PowerOnOnPowerStateChanged(object sender, PowerStateEventArgs e)
private void PowerOnOnPowerStateChanged(object? sender, PowerStateEventArgs e)
{
UpdateSignInfo();
}

View File

@@ -18,14 +18,12 @@ using Content.Shared.Body.Template;
using Content.Shared.GameObjects.Components.Body;
using Content.Shared.GameObjects.Components.Damage;
using Content.Shared.GameObjects.Components.Movement;
using Robust.Server.GameObjects;
using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
@@ -43,11 +41,9 @@ namespace Content.Server.GameObjects.Components.Body
[ComponentReference(typeof(IBodyManagerComponent))]
public class BodyManagerComponent : SharedBodyManagerComponent, IBodyPartContainer, IRelayMoveInput
{
#pragma warning disable CS0649
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IBodyNetworkFactory _bodyNetworkFactory = default!;
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
#pragma warning restore
[ViewVariables] private string _presetName = default!;
@@ -138,11 +134,6 @@ namespace Content.Server.GameObjects.Components.Body
base.Initialize();
LoadBodyPreset(Preset);
foreach (var behavior in Owner.GetAllComponents<IOnHealthChangedBehavior>())
{
HealthChangedEvent += behavior.OnHealthChanged;
}
}
protected override void Startup()

View File

@@ -1,10 +1,14 @@
using System.Collections.Generic;
#nullable enable
using System.Collections.Generic;
using Content.Server.Body;
using Content.Server.Utility;
using Content.Shared.Body.Scanner;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Body
{
@@ -12,32 +16,39 @@ namespace Content.Server.GameObjects.Components.Body
[ComponentReference(typeof(IActivate))]
public class BodyScannerComponent : Component, IActivate
{
private BoundUserInterface _userInterface;
public sealed override string Name => "BodyScanner";
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(BodyScannerUiKey.Key);
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent actor) ||
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor) ||
actor.playerSession.AttachedEntity == null)
{
return;
}
if (actor.playerSession.AttachedEntity.TryGetComponent(out BodyManagerComponent attempt))
if (actor.playerSession.AttachedEntity.TryGetComponent(out BodyManagerComponent? attempt))
{
var state = InterfaceState(attempt.Template, attempt.Parts);
_userInterface.SetState(state);
UserInterface?.SetState(state);
}
_userInterface.Open(actor.playerSession);
UserInterface?.Open(actor.playerSession);
}
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(BodyScannerUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
if (UserInterface == null)
{
Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} doesn't have a {nameof(ServerUserInterfaceComponent)}");
}
else
{
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg) { }

View File

@@ -29,7 +29,7 @@ namespace Content.Server.GameObjects.Components.Body.Circulatory
/// </summary>
[ViewVariables] public ReagentUnit EmptyVolume => _internalSolution.EmptyVolume;
[ViewVariables] public GasMixture Air { get; set; } = new GasMixture(6);
[ViewVariables] public GasMixture Air { get; set; }
[ViewVariables] public SolutionComponent Solution => _internalSolution;
@@ -45,6 +45,8 @@ namespace Content.Server.GameObjects.Components.Body.Circulatory
{
base.ExposeData(serializer);
Air = new GasMixture(6);
serializer.DataField(ref _initialMaxVolume, "maxVolume", ReagentUnit.New(250));
}

View File

@@ -1,12 +1,11 @@
using System.Collections.Generic;
#nullable enable
using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.Components.Body.Circulatory;
using Content.Server.GameObjects.Components.Chemistry;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Nutrition;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -20,25 +19,21 @@ namespace Content.Server.GameObjects.Components.Body.Digestive
[RegisterComponent]
public class StomachComponent : SharedStomachComponent
{
#pragma warning disable 649
[Dependency] private readonly ILocalizationManager _localizationManager;
#pragma warning restore 649
/// <summary>
/// Max volume of internal solution storage
/// </summary>
public ReagentUnit MaxVolume
{
get => _stomachContents.MaxVolume;
set => _stomachContents.MaxVolume = value;
get => Owner.TryGetComponent(out SolutionComponent? solution) ? solution.MaxVolume : ReagentUnit.Zero;
set
{
if (Owner.TryGetComponent(out SolutionComponent? solution))
{
solution.MaxVolume = value;
}
}
}
/// <summary>
/// Internal solution storage
/// </summary>
[ViewVariables]
private SolutionComponent _stomachContents;
/// <summary>
/// Initial internal solution storage volume
/// </summary>
@@ -68,20 +63,29 @@ namespace Content.Server.GameObjects.Components.Body.Digestive
{
base.Startup();
_stomachContents = Owner.GetComponent<SolutionComponent>();
_stomachContents.MaxVolume = _initialMaxVolume;
if (!Owner.EnsureComponent(out SolutionComponent solution))
{
Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionComponent)}");
}
solution.MaxVolume = _initialMaxVolume;
}
public bool TryTransferSolution(Solution solution)
{
if (!Owner.TryGetComponent(out SolutionComponent? solutionComponent))
{
return false;
}
// TODO: For now no partial transfers. Potentially change by design
if (solution.TotalVolume + _stomachContents.CurrentVolume > _stomachContents.MaxVolume)
if (solution.TotalVolume + solutionComponent.CurrentVolume > solutionComponent.MaxVolume)
{
return false;
}
// Add solution to _stomachContents
_stomachContents.TryAddSolution(solution, false, true);
solutionComponent.TryAddSolution(solution, false, true);
// Add each reagent to _reagentDeltas. Used to track how long each reagent has been in the stomach
foreach (var reagent in solution.Contents)
{
@@ -99,7 +103,8 @@ namespace Content.Server.GameObjects.Components.Body.Digestive
/// <param name="frameTime">The time since the last update in seconds.</param>
public void Update(float frameTime)
{
if (!Owner.TryGetComponent(out BloodstreamComponent bloodstream))
if (!Owner.TryGetComponent(out SolutionComponent? solutionComponent) ||
!Owner.TryGetComponent(out BloodstreamComponent? bloodstream))
{
return;
}
@@ -114,7 +119,7 @@ namespace Content.Server.GameObjects.Components.Body.Digestive
delta.Increment(frameTime);
if (delta.Lifetime > _digestionDelay)
{
_stomachContents.TryRemoveReagent(delta.ReagentId, delta.Quantity);
solutionComponent.TryRemoveReagent(delta.ReagentId, delta.Quantity);
transferSolution.AddReagent(delta.ReagentId, delta.Quantity);
_reagentDeltas.Remove(delta);
}

View File

@@ -1,11 +1,14 @@
using System.Collections.Generic;
#nullable enable
using System.Collections.Generic;
using System.Linq;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Server.Body;
using Content.Server.Utility;
using Content.Shared.Body.Surgery;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.Interfaces.GameObjects;
using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
@@ -21,20 +24,18 @@ namespace Content.Server.GameObjects.Components.Body
[RegisterComponent]
public class DroppedBodyPartComponent : Component, IAfterInteract, IBodyPartContainer
{
#pragma warning disable 649
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager;
#pragma warning restore 649
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager = default!;
private readonly Dictionary<int, object> _optionsCache = new Dictionary<int, object>();
private BodyManagerComponent _bodyManagerComponentCache;
private BodyManagerComponent? _bodyManagerComponentCache;
private int _idHash;
private IEntity _performerCache;
private BoundUserInterface _userInterface;
private IEntity? _performerCache;
public sealed override string Name => "DroppedBodyPart";
[ViewVariables] public BodyPart ContainedBodyPart { get; private set; }
[ViewVariables] public BodyPart ContainedBodyPart { get; private set; } = default!;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GenericSurgeryUiKey.Key);
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
@@ -48,7 +49,7 @@ namespace Content.Server.GameObjects.Components.Body
_performerCache = null;
_bodyManagerComponentCache = null;
if (eventArgs.Target.TryGetComponent(out BodyManagerComponent bodyManager))
if (eventArgs.Target.TryGetComponent(out BodyManagerComponent? bodyManager))
{
SendBodySlotListToUser(eventArgs, bodyManager);
}
@@ -58,9 +59,10 @@ namespace Content.Server.GameObjects.Components.Body
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(GenericSurgeryUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
}
public void TransferBodyPartData(BodyPart data)
@@ -68,7 +70,7 @@ namespace Content.Server.GameObjects.Components.Body
ContainedBodyPart = data;
Owner.Name = Loc.GetString(ContainedBodyPart.Name);
if (Owner.TryGetComponent(out SpriteComponent component))
if (Owner.TryGetComponent(out SpriteComponent? component))
{
component.LayerSetRSI(0, data.RSIPath);
component.LayerSetState(0, data.RSIState);
@@ -91,7 +93,7 @@ namespace Content.Server.GameObjects.Components.Body
foreach (var slot in unoccupiedSlots)
{
if (!bodyManager.TryGetSlotType(slot, out var typeResult) ||
typeResult != ContainedBodyPart.PartType ||
typeResult != ContainedBodyPart?.PartType ||
!bodyManager.TryGetBodyPartConnections(slot, out var parts))
{
continue;
@@ -129,7 +131,18 @@ namespace Content.Server.GameObjects.Components.Body
/// </summary>
private void HandleReceiveBodyPartSlot(int key)
{
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
if (_performerCache == null ||
!_performerCache.TryGetComponent(out IActorComponent? actor))
{
return;
}
CloseSurgeryUI(actor.playerSession);
if (_bodyManagerComponentCache == null)
{
return;
}
// TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc
if (!_optionsCache.TryGetValue(key, out var targetObject))
@@ -138,34 +151,42 @@ namespace Content.Server.GameObjects.Components.Body
Loc.GetString("You see no useful way to attach {0:theName} anymore.", Owner));
}
var target = targetObject as string;
var target = (string) targetObject!;
string message;
if (_bodyManagerComponentCache.InstallDroppedBodyPart(this, target))
{
message = Loc.GetString("You attach {0:theName}.", ContainedBodyPart);
}
else
{
message = Loc.GetString("You can't attach it!");
}
_sharedNotifyManager.PopupMessage(
_bodyManagerComponentCache.Owner,
_performerCache,
!_bodyManagerComponentCache.InstallDroppedBodyPart(this, target)
? Loc.GetString("You can't attach it!")
: Loc.GetString("You attach {0:theName}.", ContainedBodyPart));
message);
}
private void OpenSurgeryUI(IPlayerSession session)
{
_userInterface.Open(session);
UserInterface?.Open(session);
}
private void UpdateSurgeryUIBodyPartSlotRequest(IPlayerSession session, Dictionary<string, int> options)
{
_userInterface.SendMessage(new RequestBodyPartSlotSurgeryUIMessage(options), session);
UserInterface?.SendMessage(new RequestBodyPartSlotSurgeryUIMessage(options), session);
}
private void CloseSurgeryUI(IPlayerSession session)
{
_userInterface.Close(session);
UserInterface?.Close(session);
}
private void CloseAllSurgeryUIs()
{
_userInterface.CloseAll();
UserInterface?.CloseAll();
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)

View File

@@ -1,21 +1,23 @@
using System;
#nullable enable
using System.Collections.Generic;
using Content.Server.Body;
using Content.Server.Body.Mechanisms;
using Content.Server.Utility;
using Content.Shared.Body.Mechanism;
using Content.Shared.Body.Surgery;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.Interfaces.GameObjects;
using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Body
@@ -26,24 +28,22 @@ namespace Content.Server.GameObjects.Components.Body
[RegisterComponent]
public class DroppedMechanismComponent : Component, IAfterInteract
{
#pragma warning disable 649
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager;
[Dependency] private IPrototypeManager _prototypeManager;
#pragma warning restore 649
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public sealed override string Name => "DroppedMechanism";
private readonly Dictionary<int, object> _optionsCache = new Dictionary<int, object>();
private BodyManagerComponent _bodyManagerComponentCache;
private BodyManagerComponent? _bodyManagerComponentCache;
private int _idHash;
private IEntity _performerCache;
private IEntity? _performerCache;
private BoundUserInterface _userInterface;
[ViewVariables] public Mechanism ContainedMechanism { get; private set; } = default!;
[ViewVariables] public Mechanism ContainedMechanism { get; private set; }
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GenericSurgeryUiKey.Key);
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
@@ -63,12 +63,7 @@ namespace Content.Server.GameObjects.Components.Body
}
else if (eventArgs.Target.TryGetComponent<DroppedBodyPartComponent>(out var droppedBodyPart))
{
if (droppedBodyPart.ContainedBodyPart == null)
{
Logger.Debug(
"Installing a mechanism was attempted on an IEntity with a DroppedBodyPartComponent that doesn't have a BodyPart in it!");
throw new InvalidOperationException("A DroppedBodyPartComponent exists without a BodyPart in it!");
}
DebugTools.AssertNotNull(droppedBodyPart.ContainedBodyPart);
if (!droppedBodyPart.ContainedBodyPart.TryInstallDroppedMechanism(this))
{
@@ -82,9 +77,10 @@ namespace Content.Server.GameObjects.Components.Body
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(GenericSurgeryUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
}
public void InitializeDroppedMechanism(Mechanism data)
@@ -92,7 +88,7 @@ namespace Content.Server.GameObjects.Components.Body
ContainedMechanism = data;
Owner.Name = Loc.GetString(ContainedMechanism.Name);
if (Owner.TryGetComponent(out SpriteComponent component))
if (Owner.TryGetComponent(out SpriteComponent? component))
{
component.LayerSetRSI(0, data.RSIPath);
component.LayerSetState(0, data.RSIState);
@@ -111,7 +107,7 @@ namespace Content.Server.GameObjects.Components.Body
if (serializer.Reading && debugLoadMechanismData != "")
{
_prototypeManager.TryIndex(debugLoadMechanismData, out MechanismPrototype data);
_prototypeManager.TryIndex(debugLoadMechanismData!, out MechanismPrototype data);
var mechanism = new Mechanism(data);
mechanism.EnsureInitialize();
@@ -155,7 +151,18 @@ namespace Content.Server.GameObjects.Components.Body
/// </summary>
private void HandleReceiveBodyPart(int key)
{
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
if (_performerCache == null ||
!_performerCache.TryGetComponent(out IActorComponent? actor))
{
return;
}
CloseSurgeryUI(actor.playerSession);
if (_bodyManagerComponentCache == null)
{
return;
}
// TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc
if (!_optionsCache.TryGetValue(key, out var targetObject))
@@ -165,36 +172,37 @@ namespace Content.Server.GameObjects.Components.Body
return;
}
var target = targetObject as BodyPart;
var target = (BodyPart) targetObject;
var message = target.TryInstallDroppedMechanism(this)
? Loc.GetString("You jam the {0} inside {1:them}.", ContainedMechanism.Name, _performerCache)
: Loc.GetString("You can't fit it in!");
_sharedNotifyManager.PopupMessage(
_bodyManagerComponentCache.Owner,
_performerCache,
!target.TryInstallDroppedMechanism(this)
? Loc.GetString("You can't fit it in!")
: Loc.GetString("You jam the {1} inside {0:them}.", _performerCache, ContainedMechanism.Name));
message);
// TODO: {1:theName}
}
private void OpenSurgeryUI(IPlayerSession session)
{
_userInterface.Open(session);
UserInterface?.Open(session);
}
private void UpdateSurgeryUIBodyPartRequest(IPlayerSession session, Dictionary<string, int> options)
{
_userInterface.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session);
UserInterface?.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session);
}
private void CloseSurgeryUI(IPlayerSession session)
{
_userInterface.Close(session);
UserInterface?.Close(session);
}
private void CloseAllSurgeryUIs()
{
_userInterface.CloseAll();
UserInterface?.CloseAll();
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)

View File

@@ -21,7 +21,7 @@ namespace Content.Server.GameObjects.Components.Body.Respiratory
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] private float Pressure { get; set; }
[ViewVariables] public GasMixture Air { get; set; } = new GasMixture();
[ViewVariables] public GasMixture Air { get; set; }
[ViewVariables] public LungStatus Status { get; set; }
@@ -29,6 +29,8 @@ namespace Content.Server.GameObjects.Components.Body.Respiratory
{
base.ExposeData(serializer);
Air = new GasMixture();
serializer.DataReadWriteFunction(
"volume",
6,

View File

@@ -1,8 +1,10 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using Content.Server.Body;
using Content.Server.Body.Mechanisms;
using Content.Server.Body.Surgery;
using Content.Server.Utility;
using Content.Shared.Body.Surgery;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Body;
@@ -18,6 +20,8 @@ using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Body
{
@@ -30,9 +34,7 @@ namespace Content.Server.GameObjects.Components.Body
[RegisterComponent]
public class SurgeryToolComponent : Component, ISurgeon, IAfterInteract
{
#pragma warning disable 649
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager;
#pragma warning restore 649
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager = default!;
public override string Name => "SurgeryTool";
public override uint? NetID => ContentNetIDs.SURGERY;
@@ -41,17 +43,17 @@ namespace Content.Server.GameObjects.Components.Body
private float _baseOperateTime;
private BodyManagerComponent _bodyManagerComponentCache;
private BodyManagerComponent? _bodyManagerComponentCache;
private ISurgeon.MechanismRequestCallback _callbackCache;
private ISurgeon.MechanismRequestCallback? _callbackCache;
private int _idHash;
private IEntity _performerCache;
private IEntity? _performerCache;
private SurgeryType _surgeryType;
private BoundUserInterface _userInterface;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GenericSurgeryUiKey.Key);
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
@@ -60,7 +62,7 @@ namespace Content.Server.GameObjects.Components.Body
return;
}
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
return;
}
@@ -73,7 +75,7 @@ namespace Content.Server.GameObjects.Components.Body
_callbackCache = null;
// Attempt surgery on a BodyManagerComponent by sending a list of operable BodyParts to the client to choose from
if (eventArgs.Target.TryGetComponent(out BodyManagerComponent body))
if (eventArgs.Target.TryGetComponent(out BodyManagerComponent? body))
{
// Create dictionary to send to client (text to be shown : data sent back if selected)
var toSend = new Dictionary<string, int>();
@@ -105,13 +107,7 @@ namespace Content.Server.GameObjects.Components.Body
// Attempt surgery on a DroppedBodyPart - there's only one possible target so no need for selection UI
_performerCache = eventArgs.User;
if (droppedBodyPart.ContainedBodyPart == null)
{
// Throw error if the DroppedBodyPart has no data in it.
Logger.Debug(
"Surgery was attempted on an IEntity with a DroppedBodyPartComponent that doesn't have a BodyPart in it!");
throw new InvalidOperationException("A DroppedBodyPartComponent exists without a BodyPart in it!");
}
DebugTools.AssertNotNull(droppedBodyPart.ContainedBodyPart);
// If surgery can be performed...
if (!droppedBodyPart.ContainedBodyPart.SurgeryCheck(_surgeryType))
@@ -144,7 +140,7 @@ namespace Content.Server.GameObjects.Components.Body
toSend.Add(mechanism.Name, _idHash++);
}
if (_optionsCache.Count > 0)
if (_optionsCache.Count > 0 && _performerCache != null)
{
OpenSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
UpdateSurgeryUIMechanismRequest(_performerCache.GetComponent<BasicActorComponent>().playerSession,
@@ -162,34 +158,35 @@ namespace Content.Server.GameObjects.Components.Body
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(GenericSurgeryUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
}
private void OpenSurgeryUI(IPlayerSession session)
{
_userInterface.Open(session);
UserInterface?.Open(session);
}
private void UpdateSurgeryUIBodyPartRequest(IPlayerSession session, Dictionary<string, int> options)
{
_userInterface.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session);
UserInterface?.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session);
}
private void UpdateSurgeryUIMechanismRequest(IPlayerSession session, Dictionary<string, int> options)
{
_userInterface.SendMessage(new RequestMechanismSurgeryUIMessage(options), session);
UserInterface?.SendMessage(new RequestMechanismSurgeryUIMessage(options), session);
}
private void CloseSurgeryUI(IPlayerSession session)
{
_userInterface.Close(session);
UserInterface?.Close(session);
}
private void CloseAllSurgeryUIs()
{
_userInterface.CloseAll();
UserInterface?.CloseAll();
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
@@ -211,14 +208,22 @@ namespace Content.Server.GameObjects.Components.Body
/// </summary>
private void HandleReceiveBodyPart(int key)
{
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
// TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc
if (!_optionsCache.TryGetValue(key, out var targetObject))
if (_performerCache == null ||
!_performerCache.TryGetComponent(out IActorComponent? actor))
{
SendNoUsefulWayToUseAnymorePopup();
return;
}
var target = targetObject as BodyPart;
CloseSurgeryUI(actor.playerSession);
// TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc
if (!_optionsCache.TryGetValue(key, out var targetObject) ||
_bodyManagerComponentCache == null)
{
SendNoUsefulWayToUseAnymorePopup();
return;
}
var target = (BodyPart) targetObject!;
if (!target.AttemptSurgery(_surgeryType, _bodyManagerComponentCache, this, _performerCache))
{
@@ -233,19 +238,27 @@ namespace Content.Server.GameObjects.Components.Body
private void HandleReceiveMechanism(int key)
{
// TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc
if (!_optionsCache.TryGetValue(key, out var targetObject))
if (!_optionsCache.TryGetValue(key, out var targetObject) ||
_performerCache == null ||
!_performerCache.TryGetComponent(out IActorComponent? actor))
{
SendNoUsefulWayToUseAnymorePopup();
return;
}
var target = targetObject as Mechanism;
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
_callbackCache(target, _bodyManagerComponentCache, this, _performerCache);
CloseSurgeryUI(actor.playerSession);
_callbackCache?.Invoke(target, _bodyManagerComponentCache, this, _performerCache);
}
private void SendNoUsefulWayToUsePopup()
{
if (_bodyManagerComponentCache == null)
{
return;
}
_sharedNotifyManager.PopupMessage(
_bodyManagerComponentCache.Owner,
_performerCache,
@@ -254,6 +267,11 @@ namespace Content.Server.GameObjects.Components.Body
private void SendNoUsefulWayToUseAnymorePopup()
{
if (_bodyManagerComponentCache == null)
{
return;
}
_sharedNotifyManager.PopupMessage(
_bodyManagerComponentCache.Owner,
_performerCache,

View File

@@ -34,13 +34,11 @@ namespace Content.Server.GameObjects.Components.Buckle
[RegisterComponent]
public class BuckleComponent : SharedBuckleComponent, IInteractHand, IDragDrop
{
#pragma warning disable 649
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
#pragma warning restore 649
private int _size;

View File

@@ -2,6 +2,7 @@
using Content.Server.Cargo;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components.Cargo;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Prototypes.Cargo;
@@ -10,6 +11,7 @@ using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -19,21 +21,11 @@ namespace Content.Server.GameObjects.Components.Cargo
[ComponentReference(typeof(IActivate))]
public class CargoConsoleComponent : SharedCargoConsoleComponent, IActivate
{
#pragma warning disable 649
[Dependency] private readonly ICargoOrderDataManager _cargoOrderDataManager = default!;
#pragma warning restore 649
[ViewVariables]
public int Points = 1000;
private BoundUserInterface _userInterface = default!;
[ViewVariables]
public GalacticMarketComponent Market { get; private set; } = default!;
[ViewVariables]
public CargoOrderDatabaseComponent Orders { get; private set; } = default!;
private CargoBankAccount? _bankAccount;
[ViewVariables]
@@ -65,22 +57,44 @@ namespace Content.Server.GameObjects.Components.Cargo
private bool _requestOnly = false;
private PowerReceiverComponent _powerReceiver = default!;
private bool Powered => _powerReceiver.Powered;
private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
private CargoConsoleSystem _cargoConsoleSystem = default!;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(CargoConsoleUiKey.Key);
public override void Initialize()
{
base.Initialize();
Market = Owner.GetComponent<GalacticMarketComponent>();
Orders = Owner.GetComponent<CargoOrderDatabaseComponent>();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(CargoConsoleUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
if (!Owner.EnsureComponent(out GalacticMarketComponent _))
{
Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} had no {nameof(GalacticMarketComponent)}");
}
if (!Owner.EnsureComponent(out CargoOrderDatabaseComponent _))
{
Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} had no {nameof(GalacticMarketComponent)}");
}
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
_cargoConsoleSystem = EntitySystem.Get<CargoConsoleSystem>();
BankAccount = _cargoConsoleSystem.StationAccount;
}
public override void OnRemove()
{
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
base.OnRemove();
}
/// <summary>
/// Reads data from YAML
/// </summary>
@@ -93,8 +107,13 @@ namespace Content.Server.GameObjects.Components.Cargo
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg)
{
if (!Owner.TryGetComponent(out CargoOrderDatabaseComponent? orders))
{
return;
}
var message = serverMsg.Message;
if (!Orders.ConnectedToDatabase)
if (!orders.ConnectedToDatabase)
return;
if (!Powered)
return;
@@ -107,45 +126,45 @@ namespace Content.Server.GameObjects.Components.Cargo
break;
}
_cargoOrderDataManager.AddOrder(Orders.Database.Id, msg.Requester, msg.Reason, msg.ProductId, msg.Amount, _bankAccount.Id);
_cargoOrderDataManager.AddOrder(orders.Database.Id, msg.Requester, msg.Reason, msg.ProductId, msg.Amount, _bankAccount.Id);
break;
}
case CargoConsoleRemoveOrderMessage msg:
{
_cargoOrderDataManager.RemoveOrder(Orders.Database.Id, msg.OrderNumber);
_cargoOrderDataManager.RemoveOrder(orders.Database.Id, msg.OrderNumber);
break;
}
case CargoConsoleApproveOrderMessage msg:
{
if (_requestOnly ||
!Orders.Database.TryGetOrder(msg.OrderNumber, out var order) ||
!orders.Database.TryGetOrder(msg.OrderNumber, out var order) ||
_bankAccount == null)
{
break;
}
_prototypeManager.TryIndex(order.ProductId, out CargoProductPrototype product);
if (product == null)
PrototypeManager.TryIndex(order.ProductId, out CargoProductPrototype product);
if (product == null!)
break;
var capacity = _cargoOrderDataManager.GetCapacity(Orders.Database.Id);
var capacity = _cargoOrderDataManager.GetCapacity(orders.Database.Id);
if (capacity.CurrentCapacity == capacity.MaxCapacity)
break;
if (!_cargoConsoleSystem.ChangeBalance(_bankAccount.Id, (-product.PointCost) * order.Amount))
break;
_cargoOrderDataManager.ApproveOrder(Orders.Database.Id, msg.OrderNumber);
_cargoOrderDataManager.ApproveOrder(orders.Database.Id, msg.OrderNumber);
UpdateUIState();
break;
}
case CargoConsoleShuttleMessage _:
{
var approvedOrders = _cargoOrderDataManager.RemoveAndGetApprovedFrom(Orders.Database);
Orders.Database.ClearOrderCapacity();
var approvedOrders = _cargoOrderDataManager.RemoveAndGetApprovedFrom(orders.Database);
orders.Database.ClearOrderCapacity();
// TODO replace with shuttle code
// TEMPORARY loop for spawning stuff on top of console
foreach (var order in approvedOrders)
{
if (!_prototypeManager.TryIndex(order.ProductId, out CargoProductPrototype product))
if (!PrototypeManager.TryIndex(order.ProductId, out CargoProductPrototype product))
continue;
for (var i = 0; i < order.Amount; i++)
{
@@ -166,12 +185,12 @@ namespace Content.Server.GameObjects.Components.Cargo
if (!Powered)
return;
_userInterface.Open(actor.playerSession);
UserInterface?.Open(actor.playerSession);
}
private void UpdateUIState()
{
if (_bankAccount == null)
if (_bankAccount == null || !Owner.IsValid())
{
return;
}
@@ -180,7 +199,7 @@ namespace Content.Server.GameObjects.Components.Cargo
var name = _bankAccount.Name;
var balance = _bankAccount.Balance;
var capacity = _cargoOrderDataManager.GetCapacity(id);
_userInterface.SetState(new CargoConsoleInterfaceState(_requestOnly, id, name, balance, capacity));
UserInterface?.SetState(new CargoConsoleInterfaceState(_requestOnly, id, name, balance, capacity));
}
}
}

View File

@@ -8,9 +8,7 @@ namespace Content.Server.GameObjects.Components.Cargo
[RegisterComponent]
public class CargoOrderDatabaseComponent : SharedCargoOrderDatabaseComponent
{
#pragma warning disable 649
[Dependency] private readonly ICargoOrderDataManager _cargoOrderDataManager;
#pragma warning restore 649
[Dependency] private readonly ICargoOrderDataManager _cargoOrderDataManager = default!;
public CargoOrderDatabase Database { get; set; }
public bool ConnectedToDatabase => Database != null;

View File

@@ -1,4 +1,6 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.GUI;
@@ -7,6 +9,7 @@ using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Chemistry.ChemMaster;
using Content.Shared.GameObjects.EntitySystems;
@@ -40,24 +43,20 @@ namespace Content.Server.GameObjects.Components.Chemistry
[ComponentReference(typeof(IInteractUsing))]
public class ChemMasterComponent : SharedChemMasterComponent, IActivate, IInteractUsing, ISolutionChange
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager;
[Dependency] private readonly ILocalizationManager _localizationManager;
#pragma warning restore 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[ViewVariables] private BoundUserInterface _userInterface;
[ViewVariables] private ContainerSlot _beakerContainer;
[ViewVariables] private string _packPrototypeId;
[ViewVariables] private ContainerSlot _beakerContainer = default!;
[ViewVariables] private string _packPrototypeId = "";
[ViewVariables] private bool HasBeaker => _beakerContainer.ContainedEntity != null;
[ViewVariables] private bool BufferModeTransfer = true;
[ViewVariables] private bool _bufferModeTransfer = true;
private PowerReceiverComponent _powerReceiver;
private bool Powered => _powerReceiver.Powered;
private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
private readonly SolutionComponent BufferSolution = new SolutionComponent();
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ChemMasterUiKey.Key);
/// <summary>
/// Shows the serializer how to save/load this components yaml prototype.
@@ -77,14 +76,19 @@ namespace Content.Server.GameObjects.Components.Chemistry
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(ChemMasterUiKey.Key);
_userInterface.OnReceiveMessage += OnUiReceiveMessage;
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
}
_beakerContainer =
ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-reagentContainerContainer", Owner);
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
_powerReceiver.OnPowerStateChanged += OnPowerChanged;
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
receiver.OnPowerStateChanged += OnPowerChanged;
}
//BufferSolution = Owner.BufferSolution
BufferSolution.Solution = new Solution();
@@ -93,7 +97,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
UpdateUserInterface();
}
private void OnPowerChanged(object sender, PowerStateEventArgs e)
private void OnPowerChanged(object? sender, PowerStateEventArgs e)
{
UpdateUserInterface();
}
@@ -105,6 +109,11 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// <param name="obj">A user interface message from the client.</param>
private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
{
if (obj.Session.AttachedEntity == null)
{
return;
}
var msg = (UiActionMessage) obj.Message;
var needsPower = msg.action switch
{
@@ -124,11 +133,11 @@ namespace Content.Server.GameObjects.Components.Chemistry
TransferReagent(msg.id, msg.amount, msg.isBuffer);
break;
case UiAction.Transfer:
BufferModeTransfer = true;
_bufferModeTransfer = true;
UpdateUserInterface();
break;
case UiAction.Discard:
BufferModeTransfer = false;
_bufferModeTransfer = false;
UpdateUserInterface();
break;
case UiAction.CreatePills:
@@ -147,7 +156,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// </summary>
/// <param name="playerEntity">The player entity.</param>
/// <returns>Returns true if the entity can use the chem master, and false if it cannot.</returns>
private bool PlayerCanUseChemMaster(IEntity playerEntity, bool needsPower = true)
private bool PlayerCanUseChemMaster(IEntity? playerEntity, bool needsPower = true)
{
//Need player entity to check if they are still able to use the chem master
if (playerEntity == null)
@@ -172,18 +181,18 @@ namespace Content.Server.GameObjects.Components.Chemistry
if (beaker == null)
{
return new ChemMasterBoundUserInterfaceState(Powered, false, ReagentUnit.New(0), ReagentUnit.New(0),
"", Owner.Name, null, BufferSolution.ReagentList.ToList(), BufferModeTransfer, BufferSolution.CurrentVolume, BufferSolution.MaxVolume);
"", Owner.Name, new List<Solution.ReagentQuantity>(), BufferSolution.ReagentList.ToList(), _bufferModeTransfer, BufferSolution.CurrentVolume, BufferSolution.MaxVolume);
}
var solution = beaker.GetComponent<SolutionComponent>();
return new ChemMasterBoundUserInterfaceState(Powered, true, solution.CurrentVolume, solution.MaxVolume,
beaker.Name, Owner.Name, solution.ReagentList.ToList(), BufferSolution.ReagentList.ToList(), BufferModeTransfer, BufferSolution.CurrentVolume, BufferSolution.MaxVolume);
beaker.Name, Owner.Name, solution.ReagentList.ToList(), BufferSolution.ReagentList.ToList(), _bufferModeTransfer, BufferSolution.CurrentVolume, BufferSolution.MaxVolume);
}
private void UpdateUserInterface()
{
var state = GetUserInterfaceState();
_userInterface.SetState(state);
UserInterface?.SetState(state);
}
/// <summary>
@@ -207,7 +216,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
private void TransferReagent(string id, ReagentUnit amount, bool isBuffer)
{
if (!HasBeaker && BufferModeTransfer) return;
if (!HasBeaker && _bufferModeTransfer) return;
var beaker = _beakerContainer.ContainedEntity;
var beakerSolution = beaker.GetComponent<SolutionComponent>();
if (isBuffer)
@@ -227,7 +236,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
BufferSolution.Solution.RemoveReagent(id, actualAmount);
if (BufferModeTransfer)
if (_bufferModeTransfer)
{
beakerSolution.Solution.AddReagent(id, actualAmount);
}
@@ -351,22 +360,22 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// <param name="args">Data relevant to the event such as the actor which triggered it.</param>
void IActivate.Activate(ActivateEventArgs args)
{
if (!args.User.TryGetComponent(out IActorComponent actor))
if (!args.User.TryGetComponent(out IActorComponent? actor))
{
return;
}
if (!args.User.TryGetComponent(out IHandsComponent hands))
if (!args.User.TryGetComponent(out IHandsComponent? hands))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
_localizationManager.GetString("You have no hands."));
Loc.GetString("You have no hands."));
return;
}
var activeHandEntity = hands.GetActiveHand?.Owner;
if (activeHandEntity == null)
{
_userInterface.Open(actor.playerSession);
UserInterface?.Open(actor.playerSession);
}
}
@@ -379,26 +388,33 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// <returns></returns>
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs args)
{
if (!args.User.TryGetComponent(out IHandsComponent hands))
if (!args.User.TryGetComponent(out IHandsComponent? hands))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
_localizationManager.GetString("You have no hands."));
Loc.GetString("You have no hands."));
return true;
}
if (hands.GetActiveHand == null)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
Loc.GetString("You have nothing on your hand."));
return false;
}
var activeHandEntity = hands.GetActiveHand.Owner;
if (activeHandEntity.TryGetComponent<SolutionComponent>(out var solution))
{
if (HasBeaker)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
_localizationManager.GetString("This ChemMaster already has a container in it."));
Loc.GetString("This ChemMaster already has a container in it."));
}
else if ((solution.Capabilities & SolutionCaps.FitsInDispenser) == 0) //Close enough to a chem master...
{
//If it can't fit in the chem master, don't put it in. For example, buckets and mop buckets can't fit.
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
_localizationManager.GetString("That can't fit in the ChemMaster."));
Loc.GetString("That can't fit in the ChemMaster."));
}
else
{
@@ -409,7 +425,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
else
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
_localizationManager.GetString("You can't put this in the ChemMaster."));
Loc.GetString("You can't put this in the ChemMaster."));
}
return true;

View File

@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using Content.Server.GameObjects.Components.Body.Circulatory;
using Content.Server.Interfaces;
using Content.Server.Utility;
@@ -22,9 +23,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
[RegisterComponent]
public class InjectorComponent : SharedInjectorComponent, IAfterInteract, IUse
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager;
#pragma warning restore 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
/// <summary>
/// Whether or not the injector is able to draw from containers or if it's a single use
@@ -53,11 +52,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
private InjectorToggleMode _toggleState;
/// <summary>
/// Internal solution container
/// </summary>
[ViewVariables]
private SolutionComponent _internalContents;
public override void ExposeData(ObjectSerializer serializer)
{
@@ -69,9 +63,15 @@ namespace Content.Server.GameObjects.Components.Chemistry
protected override void Startup()
{
base.Startup();
_internalContents = Owner.GetComponent<SolutionComponent>();
_internalContents.Capabilities |= SolutionCaps.Injector;
//Set _toggleState based on prototype
Owner.EnsureComponent<SolutionComponent>();
if (Owner.TryGetComponent(out SolutionComponent? solution))
{
solution.Capabilities |= SolutionCaps.Injector;
}
// Set _toggleState based on prototype
_toggleState = _injectOnly ? InjectorToggleMode.Inject : InjectorToggleMode.Draw;
}
@@ -114,7 +114,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
if (!InteractionChecks.InRangeUnobstructed(eventArgs)) return;
//Make sure we have the attacking entity
if (eventArgs.Target == null || !_internalContents.Injector)
if (eventArgs.Target == null || !Owner.TryGetComponent(out SolutionComponent? solution) || !solution.Injector)
{
return;
}
@@ -134,7 +134,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
else //Handle injecting into bloodstream
{
if (targetEntity.TryGetComponent(out BloodstreamComponent bloodstream) &&
if (targetEntity.TryGetComponent(out BloodstreamComponent? bloodstream) &&
_toggleState == InjectorToggleMode.Inject)
{
TryInjectIntoBloodstream(bloodstream, eventArgs.User);
@@ -155,7 +155,8 @@ namespace Content.Server.GameObjects.Components.Chemistry
private void TryInjectIntoBloodstream(BloodstreamComponent targetBloodstream, IEntity user)
{
if (_internalContents.CurrentVolume == 0)
if (!Owner.TryGetComponent(out SolutionComponent? solution) ||
solution.CurrentVolume == 0)
{
return;
}
@@ -170,7 +171,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
//Move units from attackSolution to targetSolution
var removedSolution = _internalContents.SplitSolution(realTransferAmount);
var removedSolution = solution.SplitSolution(realTransferAmount);
if (!targetBloodstream.TryTransferSolution(removedSolution))
{
return;
@@ -183,7 +184,8 @@ namespace Content.Server.GameObjects.Components.Chemistry
private void TryInject(SolutionComponent targetSolution, IEntity user)
{
if (_internalContents.CurrentVolume == 0)
if (!Owner.TryGetComponent(out SolutionComponent? solution) ||
solution.CurrentVolume == 0)
{
return;
}
@@ -198,7 +200,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
//Move units from attackSolution to targetSolution
var removedSolution = _internalContents.SplitSolution(realTransferAmount);
var removedSolution = solution.SplitSolution(realTransferAmount);
if (!targetSolution.TryAddSolution(removedSolution))
{
return;
@@ -211,7 +213,8 @@ namespace Content.Server.GameObjects.Components.Chemistry
private void TryDraw(SolutionComponent targetSolution, IEntity user)
{
if (_internalContents.EmptyVolume == 0)
if (!Owner.TryGetComponent(out SolutionComponent? solution) ||
solution.EmptyVolume == 0)
{
return;
}
@@ -227,7 +230,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
//Move units from attackSolution to targetSolution
var removedSolution = targetSolution.SplitSolution(realTransferAmount);
if (!_internalContents.TryAddSolution(removedSolution))
if (!solution.TryAddSolution(removedSolution))
{
return;
}
@@ -239,7 +242,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
public override ComponentState GetComponentState()
{
return new InjectorComponentState(_internalContents.CurrentVolume, _internalContents.MaxVolume, _toggleState);
Owner.TryGetComponent(out SolutionComponent? solution);
var currentVolume = solution?.CurrentVolume ?? ReagentUnit.Zero;
var maxVolume = solution?.MaxVolume ?? ReagentUnit.Zero;
return new InjectorComponentState(currentVolume, maxVolume, _toggleState);
}
}
}

View File

@@ -20,9 +20,8 @@ namespace Content.Server.GameObjects.Components.Chemistry
[ComponentReference(typeof(IAfterInteract))]
public class PillComponent : FoodComponent, IUse, IAfterInteract
{
#pragma warning disable 649
[Dependency] private readonly IEntitySystemManager _entitySystem;
#pragma warning restore 649
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
public override string Name => "Pill";
[ViewVariables]

View File

@@ -19,10 +19,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
[RegisterComponent]
class PourableComponent : Component, IInteractUsing
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager;
[Dependency] private readonly ILocalizationManager _localizationManager;
#pragma warning restore 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
public override string Name => "Pourable";
@@ -91,7 +88,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
if (realTransferAmount <= 0) //Special message if container is full
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, eventArgs.User,
_localizationManager.GetString("Container is full"));
Loc.GetString("Container is full"));
return false;
}
@@ -101,7 +98,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
return false;
_notifyManager.PopupMessage(Owner.Transform.GridPosition, eventArgs.User,
_localizationManager.GetString("Transferred {0}u", removedSolution.TotalVolume));
Loc.GetString("Transferred {0}u", removedSolution.TotalVolume));
return true;
}

View File

@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Linq;
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.GUI;
@@ -7,6 +8,7 @@ using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Chemistry.ReagentDispenser;
using Content.Shared.GameObjects.EntitySystems;
@@ -38,14 +40,10 @@ namespace Content.Server.GameObjects.Components.Chemistry
[ComponentReference(typeof(IInteractUsing))]
public class ReagentDispenserComponent : SharedReagentDispenserComponent, IActivate, IInteractUsing, ISolutionChange
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager;
[Dependency] private readonly ILocalizationManager _localizationManager;
#pragma warning restore 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[ViewVariables] private BoundUserInterface _userInterface;
[ViewVariables] private ContainerSlot _beakerContainer;
[ViewVariables] private string _packPrototypeId;
[ViewVariables] private ContainerSlot _beakerContainer = default!;
[ViewVariables] private string _packPrototypeId = "";
[ViewVariables] private bool HasBeaker => _beakerContainer.ContainedEntity != null;
[ViewVariables] private ReagentUnit _dispenseAmount = ReagentUnit.New(10);
@@ -53,9 +51,9 @@ namespace Content.Server.GameObjects.Components.Chemistry
[ViewVariables]
private SolutionComponent Solution => _beakerContainer.ContainedEntity.GetComponent<SolutionComponent>();
private PowerReceiverComponent _powerReceiver;
private bool Powered => _powerReceiver.Powered;
private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ReagentDispenserUiKey.Key);
/// <summary>
/// Shows the serializer how to save/load this components yaml prototype.
@@ -75,14 +73,19 @@ namespace Content.Server.GameObjects.Components.Chemistry
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(ReagentDispenserUiKey.Key);
_userInterface.OnReceiveMessage += OnUiReceiveMessage;
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
}
_beakerContainer =
ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-reagentContainerContainer", Owner);
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
_powerReceiver.OnPowerStateChanged += OnPowerChanged;
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
receiver.OnPowerStateChanged += OnPowerChanged;
}
InitializeFromPrototype();
UpdateUserInterface();
@@ -108,7 +111,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
}
private void OnPowerChanged(object sender, PowerStateEventArgs e)
private void OnPowerChanged(object? sender, PowerStateEventArgs e)
{
UpdateUserInterface();
}
@@ -120,6 +123,11 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// <param name="obj">A user interface message from the client.</param>
private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
{
if (obj.Session.AttachedEntity == null)
{
return;
}
var msg = (UiButtonPressedMessage) obj.Message;
var needsPower = msg.Button switch
{
@@ -175,7 +183,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// </summary>
/// <param name="playerEntity">The player entity.</param>
/// <returns>Returns true if the entity can use the dispenser, and false if it cannot.</returns>
private bool PlayerCanUseDispenser(IEntity playerEntity, bool needsPower = true)
private bool PlayerCanUseDispenser(IEntity? playerEntity, bool needsPower = true)
{
//Need player entity to check if they are still able to use the dispenser
if (playerEntity == null)
@@ -211,7 +219,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
private void UpdateUserInterface()
{
var state = GetUserInterfaceState();
_userInterface.SetState(state);
UserInterface?.SetState(state);
}
/// <summary>
@@ -265,22 +273,22 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// <param name="args">Data relevant to the event such as the actor which triggered it.</param>
void IActivate.Activate(ActivateEventArgs args)
{
if (!args.User.TryGetComponent(out IActorComponent actor))
if (!args.User.TryGetComponent(out IActorComponent? actor))
{
return;
}
if (!args.User.TryGetComponent(out IHandsComponent hands))
if (!args.User.TryGetComponent(out IHandsComponent? hands))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
_localizationManager.GetString("You have no hands."));
Loc.GetString("You have no hands."));
return;
}
var activeHandEntity = hands.GetActiveHand?.Owner;
if (activeHandEntity == null)
{
_userInterface.Open(actor.playerSession);
UserInterface?.Open(actor.playerSession);
}
}
@@ -293,26 +301,33 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// <returns></returns>
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs args)
{
if (!args.User.TryGetComponent(out IHandsComponent hands))
if (!args.User.TryGetComponent(out IHandsComponent? hands))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
_localizationManager.GetString("You have no hands."));
Loc.GetString("You have no hands."));
return true;
}
if (hands.GetActiveHand == null)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
Loc.GetString("You have nothing on your hand."));
return false;
}
var activeHandEntity = hands.GetActiveHand.Owner;
if (activeHandEntity.TryGetComponent<SolutionComponent>(out var solution))
{
if (HasBeaker)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
_localizationManager.GetString("This dispenser already has a container in it."));
Loc.GetString("This dispenser already has a container in it."));
}
else if ((solution.Capabilities & SolutionCaps.FitsInDispenser) == 0)
{
//If it can't fit in the dispenser, don't put it in. For example, buckets and mop buckets can't fit.
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
_localizationManager.GetString("That can't fit in the dispenser."));
Loc.GetString("That can't fit in the dispenser."));
}
else
{
@@ -323,7 +338,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
else
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
_localizationManager.GetString("You can't put this in the dispenser."));
Loc.GetString("You can't put this in the dispenser."));
}
return true;

View File

@@ -29,11 +29,8 @@ namespace Content.Server.GameObjects.Components.Chemistry
[RegisterComponent]
public class SolutionComponent : SharedSolutionComponent, IExamine
{
#pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager;
[Dependency] private readonly ILocalizationManager _loc;
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
#pragma warning restore 649
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
private IEnumerable<ReactionPrototype> _reactions;
private AudioSystem _audioSystem;
@@ -276,7 +273,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
return;
}
message.AddText(_loc.GetString("Contains:\n"));
message.AddText(Loc.GetString("Contains:\n"));
if (ReagentList.Count == 0)
{
message.AddText("Nothing.\n");
@@ -303,12 +300,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
colorIsh = "Blue";
}
message.AddText(_loc.GetString("A {0} liquid\n", colorIsh));
message.AddText(Loc.GetString("A {0} liquid\n", colorIsh));
}
}
else
{
message.AddText(_loc.GetString("Unknown reagent: {0}u\n", reagent.Quantity));
message.AddText(Loc.GetString("Unknown reagent: {0}u\n", reagent.Quantity));
}
}
}

View File

@@ -1,8 +1,10 @@
using Content.Server.GameObjects.EntitySystems;
#nullable enable
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Chemistry;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@@ -11,28 +13,27 @@ namespace Content.Server.GameObjects.Components.Chemistry
[RegisterComponent]
public class TransformableContainerComponent : Component, ISolutionChange
{
#pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager;
#pragma warning restore 649
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public override string Name => "TransformableContainer";
private bool _transformed = false;
public bool Transformed { get => _transformed; }
private SpriteSpecifier? _initialSprite;
private string _initialName = default!;
private string _initialDescription = default!;
private ReagentPrototype? _currentReagent;
private SpriteSpecifier _initialSprite;
private string _initialName;
private string _initialDescription;
private SpriteComponent _sprite;
private ReagentPrototype _currentReagent;
public bool Transformed { get; private set; }
public override void Initialize()
{
base.Initialize();
_sprite = Owner.GetComponent<SpriteComponent>();
_initialSprite = new SpriteSpecifier.Rsi(new ResourcePath(_sprite.BaseRSIPath), "icon");
if (Owner.TryGetComponent(out SpriteComponent? sprite) &&
sprite.BaseRSIPath != null)
{
_initialSprite = new SpriteSpecifier.Rsi(new ResourcePath(sprite.BaseRSIPath), "icon");
}
_initialName = Owner.Name;
_initialDescription = Owner.Description;
}
@@ -40,14 +41,27 @@ namespace Content.Server.GameObjects.Components.Chemistry
protected override void Startup()
{
base.Startup();
Owner.GetComponent<SolutionComponent>().Capabilities |= SolutionCaps.FitsInDispenser;;
if (!Owner.EnsureComponent(out SolutionComponent solution))
{
Logger.Warning(
$"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionComponent)}");
}
solution.Capabilities |= SolutionCaps.FitsInDispenser;
}
public void CancelTransformation()
{
_currentReagent = null;
_transformed = false;
_sprite.LayerSetSprite(0, _initialSprite);
Transformed = false;
if (Owner.TryGetComponent(out SpriteComponent? sprite) &&
_initialSprite != null)
{
sprite.LayerSetSprite(0, _initialSprite);
}
Owner.Name = _initialName;
Owner.Description = _initialDescription;
}
@@ -76,11 +90,16 @@ namespace Content.Server.GameObjects.Components.Chemistry
!string.IsNullOrWhiteSpace(proto.SpriteReplacementPath))
{
var spriteSpec = new SpriteSpecifier.Rsi(new ResourcePath("Objects/Drinks/" + proto.SpriteReplacementPath),"icon");
_sprite.LayerSetSprite(0, spriteSpec);
if (Owner.TryGetComponent(out SpriteComponent? sprite))
{
sprite?.LayerSetSprite(0, spriteSpec);
}
Owner.Name = proto.Name + " glass";
Owner.Description = proto.Description;
_currentReagent = proto;
_transformed = true;
Transformed = true;
}
}
}

View File

@@ -7,6 +7,7 @@ using Robust.Shared.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -19,8 +20,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
[Dependency] private readonly IMapManager _mapManager = default!;
public override string Name => "Vapor";
[ViewVariables]
private SolutionComponent _contents;
[ViewVariables]
private ReagentUnit _transferAmount;
@@ -28,11 +27,15 @@ namespace Content.Server.GameObjects.Components.Chemistry
private Vector2 _direction;
private float _velocity;
public override void Initialize()
{
base.Initialize();
_contents = Owner.GetComponent<SolutionComponent>();
if (!Owner.EnsureComponent(out SolutionComponent _))
{
Logger.Warning(
$"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionComponent)}");
}
}
public void Start(Vector2 dir, float velocity)
@@ -56,6 +59,9 @@ namespace Content.Server.GameObjects.Components.Chemistry
public void Update()
{
if (!Owner.TryGetComponent(out SolutionComponent contents))
return;
if (!_running)
return;
@@ -70,11 +76,11 @@ namespace Content.Server.GameObjects.Components.Chemistry
foreach (var tile in tiles)
{
var pos = tile.GridIndices.ToGridCoordinates(_mapManager, tile.GridIndex);
SpillHelper.SpillAt(pos, _contents.SplitSolution(amount), "PuddleSmear", false); //make non PuddleSmear?
SpillHelper.SpillAt(pos, contents.SplitSolution(amount), "PuddleSmear", false); //make non PuddleSmear?
}
}
if (_contents.CurrentVolume == 0)
if (contents.CurrentVolume == 0)
{
// Delete this
Owner.Delete();
@@ -87,7 +93,14 @@ namespace Content.Server.GameObjects.Components.Chemistry
{
return false;
}
var result = _contents.TryAddSolution(solution);
if (!Owner.TryGetComponent(out SolutionComponent contents))
{
return false;
}
var result = contents.TryAddSolution(solution);
if (!result)
{
return false;

View File

@@ -1,5 +1,7 @@
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
#nullable enable
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components.Command;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects.Components.UserInterface;
@@ -8,6 +10,7 @@ using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Command
{
@@ -15,22 +18,22 @@ namespace Content.Server.GameObjects.Components.Command
[ComponentReference(typeof(IActivate))]
public class CommunicationsConsoleComponent : SharedCommunicationsConsoleComponent, IActivate
{
#pragma warning disable 649
[Dependency] private IEntitySystemManager _entitySystemManager;
#pragma warning restore 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
private BoundUserInterface _userInterface;
private PowerReceiverComponent _powerReceiver;
private bool Powered => _powerReceiver.Powered;
private RoundEndSystem RoundEndSystem => _entitySystemManager.GetEntitySystem<RoundEndSystem>();
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(CommunicationsConsoleUiKey.Key);
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(CommunicationsConsoleUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
RoundEndSystem.OnRoundEndCountdownStarted += UpdateBoundInterface;
RoundEndSystem.OnRoundEndCountdownCancelled += UpdateBoundInterface;
@@ -39,7 +42,7 @@ namespace Content.Server.GameObjects.Components.Command
private void UpdateBoundInterface()
{
_userInterface.SetState(new CommunicationsConsoleInterfaceState(RoundEndSystem.ExpectedCountdownEnd));
UserInterface?.SetState(new CommunicationsConsoleInterfaceState(RoundEndSystem.ExpectedCountdownEnd));
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage obj)
@@ -58,12 +61,12 @@ namespace Content.Server.GameObjects.Components.Command
public void OpenUserInterface(IPlayerSession session)
{
_userInterface.Open(session);
UserInterface?.Open(session);
}
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
return;
if (!Powered)

View File

@@ -28,10 +28,8 @@ namespace Content.Server.GameObjects.Components.Conveyor
[RegisterComponent]
public class ConveyorComponent : Component, IInteractUsing
{
#pragma warning disable 649
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
#pragma warning restore 649
public override string Name => "Conveyor";

View File

@@ -21,10 +21,8 @@ namespace Content.Server.GameObjects.Components.Damage
[ComponentReference(typeof(IDamageableComponent))]
public class BreakableComponent : RuinableComponent, IExAct
{
#pragma warning disable 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
[Dependency] private readonly IRobustRandom _random;
#pragma warning restore 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
public override string Name => "Breakable";
@@ -41,8 +39,6 @@ namespace Content.Server.GameObjects.Components.Damage
switch (eventArgs.Severity)
{
case ExplosionSeverity.Destruction:
PerformDestruction();
break;
case ExplosionSeverity.Heavy:
PerformDestruction();
break;

View File

@@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Interactable;
using Content.Shared.Damage;
using Content.Shared.GameObjects.Components.Damage;
using Content.Shared.GameObjects.Components.Interactable;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
@@ -27,12 +28,6 @@ namespace Content.Server.GameObjects.Components.Damage
serializer.DataField(ref _tools, "tools", new List<ToolQuality>());
}
public override void Initialize()
{
base.Initialize();
Owner.EnsureComponent<DestructibleComponent>();
}
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
{
if (eventArgs.Using.TryGetComponent<ToolComponent>(out var tool))
@@ -56,7 +51,7 @@ namespace Content.Server.GameObjects.Components.Damage
protected bool CallDamage(InteractUsingEventArgs eventArgs, ToolComponent tool)
{
if (eventArgs.Target.TryGetComponent<DestructibleComponent>(out var damageable))
if (eventArgs.Target.TryGetComponent<IDamageableComponent>(out var damageable))
{
damageable.ChangeDamage(tool.HasQuality(ToolQuality.Welding)
? DamageType.Heat

View File

@@ -16,9 +16,7 @@ namespace Content.Server.GameObjects.Components.Damage
[ComponentReference(typeof(IDamageableComponent))]
public class DestructibleComponent : RuinableComponent, IDestroyAct
{
#pragma warning disable 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
#pragma warning restore 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
protected ActSystem ActSystem;

View File

@@ -5,7 +5,6 @@ using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Damage
{
@@ -18,13 +17,6 @@ namespace Content.Server.GameObjects.Components.Damage
{
private DamageState _currentDamageState;
/// <summary>
/// How much HP this component can sustain before triggering
/// <see cref="PerformDestruction"/>.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public int MaxHp { get; private set; }
/// <summary>
/// Sound played upon destruction.
/// </summary>
@@ -35,29 +27,24 @@ namespace Content.Server.GameObjects.Components.Damage
public override DamageState CurrentDamageState => _currentDamageState;
public override void Initialize()
{
base.Initialize();
HealthChangedEvent += OnHealthChanged;
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, ruinable => ruinable.MaxHp, "maxHP", 100);
serializer.DataReadWriteFunction(
"deadThreshold",
100,
t => DeadThreshold = t ,
() => DeadThreshold ?? -1);
serializer.DataField(this, ruinable => ruinable.DestroySound, "destroySound", string.Empty);
}
public override void OnRemove()
protected override void EnterState(DamageState state)
{
base.OnRemove();
HealthChangedEvent -= OnHealthChanged;
}
base.EnterState(state);
private void OnHealthChanged(HealthChangedEventArgs e)
{
if (CurrentDamageState != DamageState.Dead && TotalDamage >= MaxHp)
if (state == DamageState.Dead)
{
PerformDestruction();
}

View File

@@ -1,7 +1,11 @@
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Random;
namespace Content.Server.GameObjects.Components.Disposal
{
@@ -9,6 +13,8 @@ namespace Content.Server.GameObjects.Components.Disposal
[ComponentReference(typeof(IDisposalTubeComponent))]
public class DisposalEntryComponent : DisposalTubeComponent
{
[Dependency] private readonly IRobustRandom _random = default!;
private const string HolderPrototypeId = "DisposalHolder";
public override string Name => "DisposalEntry";
@@ -43,8 +49,19 @@ namespace Content.Server.GameObjects.Components.Disposal
return new[] {Owner.Transform.LocalRotation.GetDir()};
}
/// <summary>
/// Ejects contents when they come from the same direction the entry is facing.
/// </summary>
public override Direction NextDirection(DisposalHolderComponent holder)
{
if (holder.PreviousTube != null && DirectionTo(holder.PreviousTube) == ConnectableDirections()[0])
{
var invalidDirections = new Direction[] { ConnectableDirections()[0], Direction.Invalid };
var directions = System.Enum.GetValues(typeof(Direction))
.Cast<Direction>().Except(invalidDirections).ToList();
return _random.Pick<Direction>(directions);
}
return ConnectableDirections()[0];
}
}

View File

@@ -14,9 +14,7 @@ namespace Content.Server.GameObjects.Components.Disposal
[ComponentReference(typeof(IDisposalTubeComponent))]
public class DisposalJunctionComponent : DisposalTubeComponent
{
#pragma warning disable 649
[Dependency] private readonly IRobustRandom _random;
#pragma warning restore 649
[Dependency] private readonly IRobustRandom _random = default!;
/// <summary>
/// The angles to connect to.

View File

@@ -1,4 +1,5 @@
using Content.Server.Interfaces;
#nullable enable
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
@@ -16,6 +17,7 @@ using Robust.Shared.Maths;
using Robust.Shared.ViewVariables;
using System;
using System.Collections.Generic;
using Content.Server.Utility;
using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalRouterComponent;
namespace Content.Server.GameObjects.Components.Disposal
@@ -25,22 +27,19 @@ namespace Content.Server.GameObjects.Components.Disposal
[ComponentReference(typeof(IDisposalTubeComponent))]
public class DisposalRouterComponent : DisposalJunctionComponent, IActivate
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager;
#pragma warning restore 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
public override string Name => "DisposalRouter";
[ViewVariables]
private BoundUserInterface _userInterface;
[ViewVariables]
private HashSet<string> _tags;
private readonly HashSet<string> _tags = new HashSet<string>();
[ViewVariables]
public bool Anchored =>
!Owner.TryGetComponent(out CollidableComponent collidable) ||
!Owner.TryGetComponent(out ICollidableComponent? collidable) ||
collidable.Anchored;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(DisposalRouterUiKey.Key);
public override Direction NextDirection(DisposalHolderComponent holder)
{
var directions = ConnectableDirections();
@@ -53,15 +52,14 @@ namespace Content.Server.GameObjects.Components.Disposal
return Owner.Transform.LocalRotation.GetDir();
}
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(DisposalRouterUiKey.Key);
_userInterface.OnReceiveMessage += OnUiReceiveMessage;
_tags = new HashSet<string>();
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
}
UpdateUserInterface();
}
@@ -73,6 +71,11 @@ namespace Content.Server.GameObjects.Components.Disposal
/// <param name="obj">A user interface message from the client.</param>
private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
{
if (obj.Session.AttachedEntity == null)
{
return;
}
var msg = (UiActionMessage) obj.Message;
if (!PlayerCanUseDisposalTagger(obj.Session.AttachedEntity))
@@ -112,10 +115,10 @@ namespace Content.Server.GameObjects.Components.Disposal
/// <summary>
/// Gets component data to be used to update the user interface client-side.
/// </summary>
/// <returns>Returns a <see cref="SharedDisposalRouterComponent.DisposalRouterBoundUserInterfaceState"/></returns>
/// <returns>Returns a <see cref="DisposalRouterUserInterfaceState"/></returns>
private DisposalRouterUserInterfaceState GetUserInterfaceState()
{
if(_tags == null || _tags.Count <= 0)
if(_tags.Count <= 0)
{
return new DisposalRouterUserInterfaceState("");
}
@@ -136,7 +139,7 @@ namespace Content.Server.GameObjects.Components.Disposal
private void UpdateUserInterface()
{
var state = GetUserInterfaceState();
_userInterface.SetState(state);
UserInterface?.SetState(state);
}
private void ClickSound()
@@ -150,12 +153,12 @@ namespace Content.Server.GameObjects.Components.Disposal
/// <param name="args">Data relevant to the event such as the actor which triggered it.</param>
void IActivate.Activate(ActivateEventArgs args)
{
if (!args.User.TryGetComponent(out IActorComponent actor))
if (!args.User.TryGetComponent(out IActorComponent? actor))
{
return;
}
if (!args.User.TryGetComponent(out IHandsComponent hands))
if (!args.User.TryGetComponent(out IHandsComponent? hands))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
Loc.GetString("You have no hands."));
@@ -166,13 +169,13 @@ namespace Content.Server.GameObjects.Components.Disposal
if (activeHandEntity == null)
{
UpdateUserInterface();
_userInterface.Open(actor.playerSession);
UserInterface?.Open(actor.playerSession);
}
}
public override void OnRemove()
{
_userInterface.CloseAll();
UserInterface?.CloseAll();
base.OnRemove();
}
}

View File

@@ -1,5 +1,7 @@
using Content.Server.Interfaces;
#nullable enable
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects.Components.UserInterface;
@@ -23,35 +25,33 @@ namespace Content.Server.GameObjects.Components.Disposal
[ComponentReference(typeof(IDisposalTubeComponent))]
public class DisposalTaggerComponent : DisposalTransitComponent, IActivate
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager;
#pragma warning restore 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
public override string Name => "DisposalTagger";
[ViewVariables]
private BoundUserInterface _userInterface;
[ViewVariables(VVAccess.ReadWrite)]
private string _tag = "";
[ViewVariables]
public bool Anchored =>
!Owner.TryGetComponent(out CollidableComponent collidable) ||
!Owner.TryGetComponent(out CollidableComponent? collidable) ||
collidable.Anchored;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(DisposalTaggerUiKey.Key);
public override Direction NextDirection(DisposalHolderComponent holder)
{
holder.Tags.Add(_tag);
return base.NextDirection(holder);
}
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(DisposalTaggerUiKey.Key);
_userInterface.OnReceiveMessage += OnUiReceiveMessage;
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
}
UpdateUserInterface();
}
@@ -70,7 +70,7 @@ namespace Content.Server.GameObjects.Components.Disposal
//Check for correct message and ignore maleformed strings
if (msg.Action == UiAction.Ok && TagRegex.IsMatch(msg.Tag))
{
{
_tag = msg.Tag;
ClickSound();
}
@@ -81,7 +81,7 @@ namespace Content.Server.GameObjects.Components.Disposal
/// </summary>
/// <param name="playerEntity">The player entity.</param>
/// <returns>Returns true if the entity can use the configuration interface, and false if it cannot.</returns>
private bool PlayerCanUseDisposalTagger(IEntity playerEntity)
private bool PlayerCanUseDisposalTagger(IEntity? playerEntity)
{
//Need player entity to check if they are still able to use the configuration interface
if (playerEntity == null)
@@ -98,7 +98,7 @@ namespace Content.Server.GameObjects.Components.Disposal
/// <summary>
/// Gets component data to be used to update the user interface client-side.
/// </summary>
/// <returns>Returns a <see cref="SharedDisposalTaggerComponent.DisposalTaggerBoundUserInterfaceState"/></returns>
/// <returns>Returns a <see cref="DisposalTaggerUserInterfaceState"/></returns>
private DisposalTaggerUserInterfaceState GetUserInterfaceState()
{
return new DisposalTaggerUserInterfaceState(_tag);
@@ -107,7 +107,7 @@ namespace Content.Server.GameObjects.Components.Disposal
private void UpdateUserInterface()
{
var state = GetUserInterfaceState();
_userInterface.SetState(state);
UserInterface?.SetState(state);
}
private void ClickSound()
@@ -121,12 +121,12 @@ namespace Content.Server.GameObjects.Components.Disposal
/// <param name="args">Data relevant to the event such as the actor which triggered it.</param>
void IActivate.Activate(ActivateEventArgs args)
{
if (!args.User.TryGetComponent(out IActorComponent actor))
if (!args.User.TryGetComponent(out IActorComponent? actor))
{
return;
}
if (!args.User.TryGetComponent(out IHandsComponent hands))
if (!args.User.TryGetComponent(out IHandsComponent? hands))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
Loc.GetString("You have no hands."));
@@ -137,14 +137,14 @@ namespace Content.Server.GameObjects.Components.Disposal
if (activeHandEntity == null)
{
UpdateUserInterface();
_userInterface.Open(actor.playerSession);
UserInterface?.Open(actor.playerSession);
}
}
public override void OnRemove()
{
base.OnRemove();
_userInterface.CloseAll();
UserInterface?.CloseAll();
}
}
}

View File

@@ -24,7 +24,6 @@ using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Disposal
{
// TODO: Make unanchored pipes pullable
public abstract class DisposalTubeComponent : Component, IDisposalTubeComponent, IBreakAct
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
@@ -69,21 +68,11 @@ namespace Content.Server.GameObjects.Components.Disposal
{
var nextDirection = NextDirection(holder);
var snapGrid = Owner.GetComponent<SnapGridComponent>();
var oppositeDirection = new Angle(nextDirection.ToAngle().Theta + Math.PI).GetDir();
var tube = snapGrid
.GetInDir(nextDirection)
.Select(x => x.TryGetComponent(out IDisposalTubeComponent? c) ? c : null)
.FirstOrDefault(x => x != null && x != this);
if (tube == null)
{
return null;
}
var oppositeDirection = new Angle(nextDirection.ToAngle().Theta + Math.PI).GetDir();
if (!tube.CanConnect(oppositeDirection, this))
{
return null;
}
.FirstOrDefault(x => x != null && x != this && x.CanConnect(oppositeDirection, this));
return tube;
}
@@ -192,6 +181,8 @@ namespace Content.Server.GameObjects.Components.Disposal
return;
}
collidable.CanCollide = !collidable.Anchored;
if (collidable.Anchored)
{
OnAnchor();
@@ -230,6 +221,8 @@ namespace Content.Server.GameObjects.Components.Disposal
var collidable = Owner.EnsureComponent<CollidableComponent>();
collidable.AnchoredChanged += AnchoredChanged;
collidable.CanCollide = !collidable.Anchored;
}
protected override void Startup()

View File

@@ -7,8 +7,10 @@ using System.Threading.Tasks;
using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.EntitySystems.DoAfter;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components.Body;
using Content.Shared.GameObjects.Components.Disposal;
using Content.Shared.GameObjects.EntitySystems;
@@ -29,6 +31,7 @@ using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using Timer = Robust.Shared.Timers.Timer;
@@ -36,13 +39,12 @@ using Timer = Robust.Shared.Timers.Timer;
namespace Content.Server.GameObjects.Components.Disposal
{
[RegisterComponent]
[ComponentReference(typeof(SharedDisposalUnitComponent))]
[ComponentReference(typeof(IInteractUsing))]
public class DisposalUnitComponent : SharedDisposalUnitComponent, IInteractHand, IInteractUsing, IDragDropOn
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
#pragma warning restore 649
public override string Name => "DisposalUnit";
@@ -67,6 +69,12 @@ namespace Content.Server.GameObjects.Components.Disposal
[ViewVariables]
private TimeSpan _automaticEngageTime;
[ViewVariables]
private TimeSpan _flushDelay;
[ViewVariables]
private float _entryDelay;
/// <summary>
/// Token used to cancel the automatic engage of a disposal unit
/// after an entity enters it.
@@ -81,9 +89,6 @@ namespace Content.Server.GameObjects.Components.Disposal
[ViewVariables] public IReadOnlyList<IEntity> ContainedEntities => _container.ContainedEntities;
[ViewVariables]
private BoundUserInterface _userInterface = default!;
[ViewVariables]
public bool Powered =>
!Owner.TryGetComponent(out PowerReceiverComponent? receiver) ||
@@ -115,6 +120,15 @@ namespace Content.Server.GameObjects.Components.Disposal
}
}
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(DisposalUnitUiKey.Key);
private DisposalUnitBoundUserInterfaceState? _lastUiState;
/// <summary>
/// Store the translated state.
/// </summary>
private (PressureState State, string Localized) _locState;
public bool CanInsert(IEntity entity)
{
if (!Anchored)
@@ -161,19 +175,40 @@ namespace Content.Server.GameObjects.Components.Disposal
if (entity.TryGetComponent(out IActorComponent? actor))
{
_userInterface.Close(actor.playerSession);
UserInterface?.Close(actor.playerSession);
}
UpdateVisualState();
}
public bool TryInsert(IEntity entity)
public async Task<bool> TryInsert(IEntity entity, IEntity? user = default)
{
if (!CanInsert(entity) || !_container.Insert(entity))
{
if (!CanInsert(entity))
return false;
if (user != null && _entryDelay > 0f)
{
var doAfterSystem = EntitySystem.Get<DoAfterSystem>();
var doAfterArgs = new DoAfterEventArgs(user, _entryDelay, default, Owner)
{
BreakOnDamage = true,
BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
NeedHand = false,
};
var result = await doAfterSystem.DoAfter(doAfterArgs);
if (result == DoAfterStatus.Cancelled)
return false;
}
if (!_container.Insert(entity))
return false;
AfterInsert(entity);
return true;
@@ -218,9 +253,9 @@ namespace Content.Server.GameObjects.Components.Disposal
{
Engaged ^= true;
if (Engaged)
if (Engaged && CanFlush())
{
TryFlush();
Timer.Spawn(_flushDelay, () => TryFlush());
}
}
@@ -284,17 +319,35 @@ namespace Content.Server.GameObjects.Components.Disposal
private DisposalUnitBoundUserInterfaceState GetInterfaceState()
{
var state = Loc.GetString($"{State}");
return new DisposalUnitBoundUserInterfaceState(Owner.Name, state, _pressure, Powered, Engaged);
string stateString;
if (_locState.State != State)
{
stateString = Loc.GetString($"{State}");
_locState = (State, stateString);
}
else
{
stateString = _locState.Localized;
}
return new DisposalUnitBoundUserInterfaceState(Owner.Name, stateString, _pressure, Powered, Engaged);
}
private void UpdateInterface()
{
var state = GetInterfaceState();
_userInterface.SetState(state);
if (_lastUiState != null && _lastUiState.Equals(state))
{
return;
}
_lastUiState = state;
UserInterface?.SetState(state);
}
private bool PlayerCanUse(IEntity player)
private bool PlayerCanUse(IEntity? player)
{
if (player == null)
{
@@ -402,8 +455,9 @@ namespace Content.Server.GameObjects.Components.Disposal
: LightState.Ready);
}
public void Update(float frameTime)
public override void Update(float frameTime)
{
base.Update(frameTime);
if (!Powered)
{
return;
@@ -461,10 +515,16 @@ namespace Content.Server.GameObjects.Components.Disposal
() => (int) _automaticEngageTime.TotalSeconds);
serializer.DataReadWriteFunction(
"automaticEngageTime",
30,
seconds => _automaticEngageTime = TimeSpan.FromSeconds(seconds),
() => (int) _automaticEngageTime.TotalSeconds);
"flushDelay",
3,
seconds => _flushDelay = TimeSpan.FromSeconds(seconds),
() => (int) _flushDelay.TotalSeconds);
serializer.DataReadWriteFunction(
"entryDelay",
0.5f,
seconds => _entryDelay = seconds,
() => (int) _entryDelay);
}
public override void Initialize()
@@ -472,9 +532,11 @@ namespace Content.Server.GameObjects.Components.Disposal
base.Initialize();
_container = ContainerManagerComponent.Ensure<Container>(Name, Owner);
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(DisposalUnitUiKey.Key);
_userInterface.OnReceiveMessage += OnUiReceiveMessage;
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
}
UpdateInterface();
}
@@ -483,10 +545,15 @@ namespace Content.Server.GameObjects.Components.Disposal
{
base.Startup();
Owner.EnsureComponent<AnchorableComponent>();
if(!Owner.HasComponent<AnchorableComponent>())
{
Logger.WarningS("VitalComponentMissing", $"Disposal unit {Owner.Uid} is missing an anchorable component");
}
var collidable = Owner.EnsureComponent<CollidableComponent>();
collidable.AnchoredChanged += UpdateVisualState;
if (Owner.TryGetComponent(out CollidableComponent? collidable))
{
collidable.AnchoredChanged += UpdateVisualState;
}
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
@@ -513,7 +580,7 @@ namespace Content.Server.GameObjects.Components.Disposal
_container.ForceRemove(entity);
}
_userInterface.CloseAll();
UserInterface?.CloseAll();
_automaticEngageToken?.Cancel();
_automaticEngageToken = null;
@@ -571,7 +638,7 @@ namespace Content.Server.GameObjects.Components.Disposal
return false;
}
_userInterface.Open(actor.playerSession);
UserInterface?.Open(actor.playerSession);
return true;
}
@@ -587,7 +654,8 @@ namespace Content.Server.GameObjects.Components.Disposal
bool IDragDropOn.DragDropOn(DragDropEventArgs eventArgs)
{
return TryInsert(eventArgs.Dropped);
_ = TryInsert(eventArgs.Dropped, eventArgs.User);
return true;
}
[Verb]
@@ -609,7 +677,7 @@ namespace Content.Server.GameObjects.Components.Disposal
protected override void Activate(IEntity user, DisposalUnitComponent component)
{
component.TryInsert(user);
_ = component.TryInsert(user, user);
}
}

View File

@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Threading;
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Interactable;
@@ -34,10 +35,7 @@ namespace Content.Server.GameObjects.Components.Doors
/// </summary>
private static readonly TimeSpan PowerWiresTimeout = TimeSpan.FromSeconds(5.0);
private PowerReceiverComponent _powerReceiver;
private WiresComponent _wires;
private CancellationTokenSource _powerWiresPulsedTimerCancel;
private CancellationTokenSource _powerWiresPulsedTimerCancel = new CancellationTokenSource();
private bool _powerWiresPulsed;
@@ -89,13 +87,15 @@ namespace Content.Server.GameObjects.Components.Doors
private void UpdateWiresStatus()
{
WiresComponent? wires;
var powerLight = new StatusLightData(Color.Yellow, StatusLightState.On, "POWR");
if (PowerWiresPulsed)
{
powerLight = new StatusLightData(Color.Yellow, StatusLightState.BlinkingFast, "POWR");
}
else if (_wires.IsWireCut(Wires.MainPower) &&
_wires.IsWireCut(Wires.BackupPower))
else if (Owner.TryGetComponent(out wires) &&
wires.IsWireCut(Wires.MainPower) &&
wires.IsWireCut(Wires.BackupPower))
{
powerLight = new StatusLightData(Color.Red, StatusLightState.On, "POWR");
}
@@ -114,12 +114,17 @@ namespace Content.Server.GameObjects.Components.Doors
var safetyStatus =
new StatusLightData(Color.Red, Safety ? StatusLightState.On : StatusLightState.Off, "SAFE");
_wires.SetStatus(AirlockWireStatus.PowerIndicator, powerLight);
_wires.SetStatus(AirlockWireStatus.BoltIndicator, boltStatus);
_wires.SetStatus(AirlockWireStatus.BoltLightIndicator, boltLightsStatus);
_wires.SetStatus(AirlockWireStatus.AIControlIndicator, new StatusLightData(Color.Purple, StatusLightState.BlinkingSlow, "AICT"));
_wires.SetStatus(AirlockWireStatus.TimingIndicator, timingStatus);
_wires.SetStatus(AirlockWireStatus.SafetyIndicator, safetyStatus);
if (!Owner.TryGetComponent(out wires))
{
return;
}
wires.SetStatus(AirlockWireStatus.PowerIndicator, powerLight);
wires.SetStatus(AirlockWireStatus.BoltIndicator, boltStatus);
wires.SetStatus(AirlockWireStatus.BoltLightIndicator, boltLightsStatus);
wires.SetStatus(AirlockWireStatus.AIControlIndicator, new StatusLightData(Color.Purple, StatusLightState.BlinkingSlow, "AICT"));
wires.SetStatus(AirlockWireStatus.TimingIndicator, timingStatus);
wires.SetStatus(AirlockWireStatus.SafetyIndicator, safetyStatus);
/*
_wires.SetStatus(6, powerLight);
_wires.SetStatus(7, powerLight);
@@ -131,26 +136,45 @@ namespace Content.Server.GameObjects.Components.Doors
private void UpdatePowerCutStatus()
{
_powerReceiver.PowerDisabled = PowerWiresPulsed ||
_wires.IsWireCut(Wires.MainPower) ||
_wires.IsWireCut(Wires.BackupPower);
if (!Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
return;
}
if (PowerWiresPulsed)
{
receiver.PowerDisabled = true;
return;
}
if (!Owner.TryGetComponent(out WiresComponent? wires))
{
return;
}
receiver.PowerDisabled =
wires.IsWireCut(Wires.MainPower) ||
wires.IsWireCut(Wires.BackupPower);
}
private void UpdateBoltLightStatus()
{
if (Owner.TryGetComponent(out AppearanceComponent appearance))
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(DoorVisuals.BoltLights, BoltLightsVisible);
}
}
protected override DoorState State
public override DoorState State
{
set
protected set
{
base.State = value;
// Only show the maintenance panel if the airlock is closed
_wires.IsPanelVisible = value != DoorState.Open;
if (Owner.TryGetComponent(out WiresComponent? wires))
{
wires.IsPanelVisible = value != DoorState.Open;
}
// If the door is closed, we should look if the bolt was locked while closing
UpdateBoltLightStatus();
}
@@ -159,25 +183,32 @@ namespace Content.Server.GameObjects.Components.Doors
public override void Initialize()
{
base.Initialize();
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
_wires = Owner.GetComponent<WiresComponent>();
_powerReceiver.OnPowerStateChanged += PowerDeviceOnOnPowerStateChanged;
if (Owner.TryGetComponent(out AppearanceComponent appearance))
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
appearance.SetData(DoorVisuals.Powered, _powerReceiver.Powered);
receiver.OnPowerStateChanged += PowerDeviceOnOnPowerStateChanged;
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(DoorVisuals.Powered, receiver.Powered);
}
}
}
public override void OnRemove()
{
_powerReceiver.OnPowerStateChanged -= PowerDeviceOnOnPowerStateChanged;
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
receiver.OnPowerStateChanged -= PowerDeviceOnOnPowerStateChanged;
}
base.OnRemove();
}
private void PowerDeviceOnOnPowerStateChanged(object sender, PowerStateEventArgs e)
private void PowerDeviceOnOnPowerStateChanged(object? sender, PowerStateEventArgs e)
{
if (Owner.TryGetComponent(out AppearanceComponent appearance))
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(DoorVisuals.Powered, e.Powered);
}
@@ -188,11 +219,12 @@ namespace Content.Server.GameObjects.Components.Doors
protected override void ActivateImpl(ActivateEventArgs args)
{
if (_wires.IsPanelOpen)
if (Owner.TryGetComponent(out WiresComponent? wires) &&
wires.IsPanelOpen)
{
if (args.User.TryGetComponent(out IActorComponent actor))
if (args.User.TryGetComponent(out IActorComponent? actor))
{
_wires.OpenInterface(actor.playerSession);
wires.OpenInterface(actor.playerSession);
}
}
else
@@ -272,8 +304,7 @@ namespace Content.Server.GameObjects.Components.Doors
case Wires.MainPower:
case Wires.BackupPower:
PowerWiresPulsed = true;
_powerWiresPulsedTimerCancel?.Cancel();
_powerWiresPulsedTimerCancel = new CancellationTokenSource();
_powerWiresPulsedTimerCancel.Cancel();
Timer.Spawn(PowerWiresTimeout,
() => PowerWiresPulsed = false,
_powerWiresPulsedTimerCancel.Token);
@@ -377,7 +408,8 @@ namespace Content.Server.GameObjects.Components.Doors
private bool IsPowered()
{
return _powerReceiver.Powered;
return !Owner.TryGetComponent(out PowerReceiverComponent? receiver)
|| receiver.Powered;
}
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
@@ -388,11 +420,12 @@ namespace Content.Server.GameObjects.Components.Doors
if (tool.HasQuality(ToolQuality.Cutting)
|| tool.HasQuality(ToolQuality.Multitool))
{
if (_wires.IsPanelOpen)
if (Owner.TryGetComponent(out WiresComponent? wires)
&& wires.IsPanelOpen)
{
if (eventArgs.User.TryGetComponent(out IActorComponent actor))
if (eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
_wires.OpenInterface(actor.playerSession);
wires.OpenInterface(actor.playerSession);
return true;
}
}

View File

@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Linq;
using System.Threading;
using Content.Server.Atmos;
@@ -35,10 +36,10 @@ namespace Content.Server.GameObjects.Components.Doors
private DoorState _state = DoorState.Closed;
protected virtual DoorState State
public virtual DoorState State
{
get => _state;
set => _state = value;
protected set => _state = value;
}
protected float OpenTimeCounter;
@@ -46,10 +47,7 @@ namespace Content.Server.GameObjects.Components.Doors
protected const float AutoCloseDelay = 5;
protected float CloseSpeed = AutoCloseDelay;
private AirtightComponent airtightComponent;
private ICollidableComponent _collidableComponent;
private AppearanceComponent _appearance;
private CancellationTokenSource _cancellationTokenSource;
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
protected virtual TimeSpan CloseTimeOne => TimeSpan.FromSeconds(0.3f);
protected virtual TimeSpan CloseTimeTwo => TimeSpan.FromSeconds(0.9f);
@@ -72,21 +70,9 @@ namespace Content.Server.GameObjects.Components.Doors
serializer.DataField(ref _occludes, "occludes", true);
}
public override void Initialize()
{
base.Initialize();
airtightComponent = Owner.GetComponent<AirtightComponent>();
_collidableComponent = Owner.GetComponent<ICollidableComponent>();
_appearance = Owner.GetComponent<AppearanceComponent>();
_cancellationTokenSource = new CancellationTokenSource();
}
public override void OnRemove()
{
_cancellationTokenSource.Cancel();
_collidableComponent = null;
_appearance = null;
_cancellationTokenSource?.Cancel();
base.OnRemove();
}
@@ -108,7 +94,6 @@ namespace Content.Server.GameObjects.Components.Doors
ActivateImpl(eventArgs);
}
void ICollideBehavior.CollideWith(IEntity entity)
{
if (State != DoorState.Closed)
@@ -139,8 +124,10 @@ namespace Content.Server.GameObjects.Components.Doors
protected void SetAppearance(DoorVisualState state)
{
if (_appearance != null || Owner.TryGetComponent(out _appearance))
_appearance.SetData(DoorVisuals.VisualState, state);
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(DoorVisuals.VisualState, state);
}
}
public virtual bool CanOpen()
@@ -151,28 +138,53 @@ namespace Content.Server.GameObjects.Components.Doors
public virtual bool CanOpen(IEntity user)
{
if (!CanOpen()) return false;
if (!Owner.TryGetComponent(out AccessReader accessReader))
if (!Owner.TryGetComponent<AccessReader>(out var accessReader))
{
return true;
}
return accessReader.IsAllowed(user);
var doorSystem = EntitySystem.Get<DoorSystem>();
var isAirlockExternal = HasAccessType("External");
return doorSystem.AccessType switch
{
DoorSystem.AccessTypes.AllowAll => true,
DoorSystem.AccessTypes.AllowAllIdExternal => isAirlockExternal ? accessReader.IsAllowed(user) : true,
DoorSystem.AccessTypes.AllowAllNoExternal => !isAirlockExternal,
_ => accessReader.IsAllowed(user)
};
}
/// <summary>
/// Returns whether a door has a certain access type. For example, maintenance doors will have access type
/// "Maintenance" in their AccessReader.
/// </summary>
private bool HasAccessType(string accesType)
{
if(Owner.TryGetComponent<AccessReader>(out var accessReader))
{
return accessReader.AccessLists.Any(list => list.Contains(accesType));
}
return true;
}
public void TryOpen(IEntity user)
{
if (!CanOpen(user))
if (CanOpen(user))
{
Open();
if (user.TryGetComponent(out HandsComponent? hands) && hands.Count == 0)
{
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Effects/bang.ogg", Owner,
AudioParams.Default.WithVolume(-2));
}
}
else
{
Deny();
return;
}
Open();
if (user.TryGetComponent(out HandsComponent hands) && hands.Count == 0)
{
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Effects/bang.ogg", Owner,
AudioParams.Default.WithVolume(-2));
}
}
@@ -185,15 +197,22 @@ namespace Content.Server.GameObjects.Components.Doors
State = DoorState.Opening;
SetAppearance(DoorVisualState.Opening);
if (_occludes && Owner.TryGetComponent(out OccluderComponent occluder))
if (_occludes && Owner.TryGetComponent(out OccluderComponent? occluder))
{
occluder.Enabled = false;
}
Timer.Spawn(OpenTimeOne, async () =>
{
airtightComponent.AirBlocked = false;
_collidableComponent.Hard = false;
if (Owner.TryGetComponent(out AirtightComponent? airtight))
{
airtight.AirBlocked = false;
}
if (Owner.TryGetComponent(out ICollidableComponent? collidable))
{
collidable.Hard = false;
}
await Timer.Delay(OpenTimeTwo, _cancellationTokenSource.Token);
@@ -212,7 +231,7 @@ namespace Content.Server.GameObjects.Components.Doors
public virtual bool CanClose(IEntity user)
{
if (!CanClose()) return false;
if (!Owner.TryGetComponent(out AccessReader accessReader))
if (!Owner.TryGetComponent(out AccessReader? accessReader))
{
return true;
}
@@ -233,18 +252,22 @@ namespace Content.Server.GameObjects.Components.Doors
private void CheckCrush()
{
if (!Owner.TryGetComponent(out ICollidableComponent? body))
{
return;
}
// Check if collides with something
var collidesWith = _collidableComponent.GetCollidingEntities(Vector2.Zero, false);
var collidesWith = body.GetCollidingEntities(Vector2.Zero, false);
if (collidesWith.Count() != 0)
{
// Crush
bool hitSomeone = false;
foreach (var e in collidesWith)
{
if (!e.TryGetComponent(out StunnableComponent stun)
|| !e.TryGetComponent(out IDamageableComponent damage)
|| !e.TryGetComponent(out ICollidableComponent otherBody)
|| !Owner.TryGetComponent(out ICollidableComponent body))
if (!e.TryGetComponent(out StunnableComponent? stun)
|| !e.TryGetComponent(out IDamageableComponent? damage)
|| !e.TryGetComponent(out ICollidableComponent? otherBody))
continue;
var percentage = otherBody.WorldAABB.IntersectPercentage(body.WorldAABB);
@@ -319,7 +342,8 @@ namespace Content.Server.GameObjects.Components.Doors
public bool Close()
{
bool shouldCheckCrush = false;
if (_collidableComponent.IsColliding(Vector2.Zero, false))
if (Owner.TryGetComponent(out ICollidableComponent? collidable) && collidable.IsColliding(Vector2.Zero, false))
{
if (Safety)
return false;
@@ -331,7 +355,7 @@ namespace Content.Server.GameObjects.Components.Doors
State = DoorState.Closing;
OpenTimeCounter = 0;
SetAppearance(DoorVisualState.Closing);
if (_occludes && Owner.TryGetComponent(out OccluderComponent occluder))
if (_occludes && Owner.TryGetComponent(out OccluderComponent? occluder))
{
occluder.Enabled = true;
}
@@ -343,8 +367,15 @@ namespace Content.Server.GameObjects.Components.Doors
CheckCrush();
}
airtightComponent.AirBlocked = true;
_collidableComponent.Hard = true;
if (Owner.TryGetComponent(out AirtightComponent? airtight))
{
airtight.AirBlocked = true;
}
if (Owner.TryGetComponent(out ICollidableComponent? body))
{
body.Hard = true;
}
await Timer.Delay(CloseTimeTwo, _cancellationTokenSource.Token);
@@ -391,7 +422,7 @@ namespace Content.Server.GameObjects.Components.Doors
}
}
protected enum DoorState
public enum DoorState
{
Closed,
Open,

View File

@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Chemistry;
using Content.Shared.Chemistry;
@@ -19,23 +20,25 @@ namespace Content.Server.GameObjects.Components.Fluids
[RegisterComponent]
public class BucketComponent : Component, IInteractUsing
{
#pragma warning disable 649
[Dependency] private readonly ILocalizationManager _localizationManager;
#pragma warning restore 649
public override string Name => "Bucket";
public ReagentUnit MaxVolume
{
get => _contents.MaxVolume;
set => _contents.MaxVolume = value;
get => Owner.TryGetComponent(out SolutionComponent? solution) ? solution.MaxVolume : ReagentUnit.Zero;
set
{
if (Owner.TryGetComponent(out SolutionComponent? solution))
{
solution.MaxVolume = value;
}
}
}
public ReagentUnit CurrentVolume => _contents.CurrentVolume;
public ReagentUnit CurrentVolume => Owner.TryGetComponent(out SolutionComponent? solution)
? solution.CurrentVolume
: ReagentUnit.Zero;
private SolutionComponent _contents;
private string _sound;
private string? _sound;
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
@@ -47,16 +50,28 @@ namespace Content.Server.GameObjects.Components.Fluids
public override void Initialize()
{
base.Initialize();
_contents = Owner.GetComponent<SolutionComponent>();
Owner.EnsureComponent<SolutionComponent>();
}
private bool TryGiveToMop(MopComponent mopComponent)
{
if (!Owner.TryGetComponent(out SolutionComponent? contents))
{
return false;
}
var mopContents = mopComponent.Contents;
if (mopContents == null)
{
return false;
}
// Let's fill 'er up
// If this is called the mop should be empty but just in case we'll do Max - Current
var transferAmount = ReagentUnit.Min(mopComponent.MaxVolume - mopComponent.CurrentVolume, CurrentVolume);
var solution = _contents.SplitSolution(transferAmount);
if (!mopComponent.Contents.TryAddSolution(solution) || mopComponent.CurrentVolume == 0)
var solution = contents.SplitSolution(transferAmount);
if (!mopContents.TryAddSolution(solution) || mopComponent.CurrentVolume == 0)
{
return false;
}
@@ -73,7 +88,12 @@ namespace Content.Server.GameObjects.Components.Fluids
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
{
if (!eventArgs.Using.TryGetComponent(out MopComponent mopComponent))
if (!Owner.TryGetComponent(out SolutionComponent? contents))
{
return false;
}
if (!eventArgs.Using.TryGetComponent(out MopComponent? mopComponent))
{
return false;
}
@@ -86,7 +106,7 @@ namespace Content.Server.GameObjects.Components.Fluids
return false;
}
Owner.PopupMessage(eventArgs.User, _localizationManager.GetString("Splish"));
Owner.PopupMessage(eventArgs.User, Loc.GetString("Splish"));
return true;
}
@@ -96,15 +116,22 @@ namespace Content.Server.GameObjects.Components.Fluids
return false;
}
var solution = mopComponent.Contents.SplitSolution(transferAmount);
if (!_contents.TryAddSolution(solution))
var mopContents = mopComponent.Contents;
if (mopContents == null)
{
return false;
}
var solution = mopContents.SplitSolution(transferAmount);
if (!contents.TryAddSolution(solution))
{
//This really shouldn't happen
throw new InvalidOperationException();
}
// Give some visual feedback shit's happening (for anyone who can't hear sound)
Owner.PopupMessage(eventArgs.User, _localizationManager.GetString("Sploosh"));
Owner.PopupMessage(eventArgs.User, Loc.GetString("Sploosh"));
if (_sound == null)
{
@@ -114,7 +141,6 @@ namespace Content.Server.GameObjects.Components.Fluids
EntitySystem.Get<AudioSystem>().PlayFromEntity(_sound, Owner);
return true;
}
}
}

View File

@@ -1,4 +1,5 @@
using Content.Server.GameObjects.Components.Chemistry;
#nullable enable
using Content.Server.GameObjects.Components.Chemistry;
using Content.Server.Utility;
using Content.Shared.Chemistry;
using Content.Shared.Interfaces;
@@ -6,8 +7,8 @@ using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Fluids
@@ -18,21 +19,24 @@ namespace Content.Server.GameObjects.Components.Fluids
[RegisterComponent]
public class MopComponent : Component, IAfterInteract
{
#pragma warning disable 649
[Dependency] private readonly ILocalizationManager _localizationManager;
#pragma warning restore 649
public override string Name => "Mop";
internal SolutionComponent Contents => _contents;
private SolutionComponent _contents;
public SolutionComponent? Contents => Owner.GetComponentOrNull<SolutionComponent>();
public ReagentUnit MaxVolume
{
get => _contents.MaxVolume;
set => _contents.MaxVolume = value;
get => Owner.GetComponentOrNull<SolutionComponent>()?.MaxVolume ?? ReagentUnit.Zero;
set
{
if (Owner.TryGetComponent(out SolutionComponent? solution))
{
solution.MaxVolume = value;
}
}
}
public ReagentUnit CurrentVolume => _contents.CurrentVolume;
public ReagentUnit CurrentVolume =>
Owner.GetComponentOrNull<SolutionComponent>()?.CurrentVolume ?? ReagentUnit.Zero;
// Currently there's a separate amount for pickup and dropoff so
// Picking up a puddle requires multiple clicks
@@ -41,7 +45,7 @@ namespace Content.Server.GameObjects.Components.Fluids
public ReagentUnit PickupAmount => _pickupAmount;
private ReagentUnit _pickupAmount;
private string _pickupSound;
private string _pickupSound = "";
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
@@ -54,12 +58,16 @@ namespace Content.Server.GameObjects.Components.Fluids
public override void Initialize()
{
base.Initialize();
_contents = Owner.GetComponent<SolutionComponent>();
if (!Owner.EnsureComponent(out SolutionComponent _))
{
Logger.Warning($"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionComponent)}");
}
}
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
if (!Owner.TryGetComponent(out SolutionComponent? contents)) return;
if (!InteractionChecks.InRangeUnobstructed(eventArgs)) return;
if (CurrentVolume <= 0)
@@ -71,12 +79,12 @@ namespace Content.Server.GameObjects.Components.Fluids
if (eventArgs.Target == null)
{
// Drop the liquid on the mop on to the ground
SpillHelper.SpillAt(eventArgs.ClickLocation, _contents.SplitSolution(CurrentVolume), "PuddleSmear");
SpillHelper.SpillAt(eventArgs.ClickLocation, contents.SplitSolution(CurrentVolume), "PuddleSmear");
return;
}
if (!eventArgs.Target.TryGetComponent(out PuddleComponent puddleComponent))
if (!eventArgs.Target.TryGetComponent(out PuddleComponent? puddleComponent))
{
return;
}
@@ -107,23 +115,22 @@ namespace Content.Server.GameObjects.Components.Fluids
if (puddleCleaned) //After cleaning the puddle, make a new puddle with solution from the mop as a "wet floor". Then evaporate it slowly.
{
SpillHelper.SpillAt(eventArgs.ClickLocation, _contents.SplitSolution(transferAmount), "PuddleSmear");
SpillHelper.SpillAt(eventArgs.ClickLocation, contents.SplitSolution(transferAmount), "PuddleSmear");
}
else
{
_contents.SplitSolution(transferAmount);
contents.SplitSolution(transferAmount);
}
// Give some visual feedback shit's happening (for anyone who can't hear sound)
Owner.PopupMessage(eventArgs.User, _localizationManager.GetString("Swish"));
Owner.PopupMessage(eventArgs.User, Loc.GetString("Swish"));
if (_pickupSound == null)
if (string.IsNullOrWhiteSpace(_pickupSound))
{
return;
}
EntitySystem.Get<AudioSystem>().PlayFromEntity(_pickupSound, Owner);
}
}
}

View File

@@ -46,10 +46,7 @@ namespace Content.Server.GameObjects.Components.Fluids
// based on behaviour (e.g. someone being punched vs slashed with a sword would have different blood sprite)
// to check for low volumes for evaporation or whatever
#pragma warning disable 649
[Dependency] private readonly IMapManager _mapManager;
[Dependency] private readonly ILocalizationManager _loc;
#pragma warning restore 649
[Dependency] private readonly IMapManager _mapManager = default!;
public override string Name => "Puddle";
@@ -116,6 +113,7 @@ namespace Content.Server.GameObjects.Components.Fluids
public override void Initialize()
{
base.Initialize();
if (Owner.TryGetComponent(out SolutionComponent solutionComponent))
{
_contents = solutionComponent;
@@ -123,21 +121,27 @@ namespace Content.Server.GameObjects.Components.Fluids
else
{
_contents = Owner.AddComponent<SolutionComponent>();
_contents.Initialize();
}
_snapGrid = Owner.GetComponent<SnapGridComponent>();
_snapGrid = Owner.EnsureComponent<SnapGridComponent>();
// Smaller than 1m^3 for now but realistically this shouldn't be hit
MaxVolume = ReagentUnit.New(1000);
// Random sprite state set server-side so it's consistent across all clients
_spriteComponent = Owner.GetComponent<SpriteComponent>();
_spriteComponent = Owner.EnsureComponent<SpriteComponent>();
var robustRandom = IoCManager.Resolve<IRobustRandom>();
var randomVariant = robustRandom.Next(0, _spriteVariants - 1);
var baseName = new ResourcePath(_spriteComponent.BaseRSIPath).FilenameWithoutExtension;
_spriteComponent.LayerSetState(0, $"{baseName}-{randomVariant}"); // TODO: Remove hardcode
if (_spriteComponent.BaseRSIPath != null)
{
var baseName = new ResourcePath(_spriteComponent.BaseRSIPath).FilenameWithoutExtension;
_spriteComponent.LayerSetState(0, $"{baseName}-{randomVariant}"); // TODO: Remove hardcode
}
// UpdateAppearance should get called soon after this so shouldn't need to call Dirty() here
UpdateStatus();
@@ -153,7 +157,7 @@ namespace Content.Server.GameObjects.Components.Fluids
{
if(_slippery)
{
message.AddText(_loc.GetString("It looks slippery."));
message.AddText(Loc.GetString("It looks slippery."));
}
}

View File

@@ -8,6 +8,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -16,10 +17,9 @@ namespace Content.Server.GameObjects.Components.Fluids
[RegisterComponent]
class SprayComponent : Component, IAfterInteract
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[Dependency] private readonly IServerEntityManager _serverEntityManager = default!;
#pragma warning restore 649
public override string Name => "Spray";
private ReagentUnit _transferAmount;
@@ -46,13 +46,17 @@ namespace Content.Server.GameObjects.Components.Fluids
set => _sprayVelocity = value;
}
private SolutionComponent _contents;
public ReagentUnit CurrentVolume => _contents.CurrentVolume;
public ReagentUnit CurrentVolume => Owner.GetComponentOrNull<SolutionComponent>()?.CurrentVolume ?? ReagentUnit.Zero;
public override void Initialize()
{
base.Initialize();
_contents = Owner.GetComponent<SolutionComponent>();
if (!Owner.EnsureComponent(out SolutionComponent _))
{
Logger.Warning(
$"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionComponent)}");
}
}
public override void ExposeData(ObjectSerializer serializer)
@@ -75,8 +79,11 @@ namespace Content.Server.GameObjects.Components.Fluids
if (eventArgs.ClickLocation.GridID != playerPos.GridID)
return;
if (!Owner.TryGetComponent(out SolutionComponent contents))
return;
var direction = (eventArgs.ClickLocation.Position - playerPos.Position).Normalized;
var solution = _contents.SplitSolution(_transferAmount);
var solution = contents.SplitSolution(_transferAmount);
playerPos = playerPos.Offset(direction); // Move a bit so we don't hit the player
//TODO: check for wall?

View File

@@ -29,6 +29,7 @@ using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Players;
using Robust.Shared.ViewVariables;
using Content.Server.GameObjects.Components.ActionBlocking;
namespace Content.Server.GameObjects.Components.GUI
{
@@ -37,9 +38,7 @@ namespace Content.Server.GameObjects.Components.GUI
[ComponentReference(typeof(ISharedHandsComponent))]
public class HandsComponent : SharedHandsComponent, IHandsComponent, IBodyPartAdded, IBodyPartRemoved
{
#pragma warning disable 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
#pragma warning restore 649
private string? _activeHand;
private uint _nextHand;
@@ -446,6 +445,7 @@ namespace Content.Server.GameObjects.Components.GUI
ActiveHand ??= name;
OnItemChanged?.Invoke();
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new HandCountChangedEvent(Owner));
Dirty();
}
@@ -468,6 +468,7 @@ namespace Content.Server.GameObjects.Components.GUI
}
OnItemChanged?.Invoke();
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new HandCountChangedEvent(Owner));
Dirty();
}
@@ -793,4 +794,14 @@ namespace Content.Server.GameObjects.Components.GUI
return new SharedHand(index, Name, Entity?.Uid, location);
}
}
public class HandCountChangedEvent : EntitySystemMessage
{
public HandCountChangedEvent(IEntity sender)
{
Sender = sender;
}
public IEntity Sender { get; }
}
}

View File

@@ -21,7 +21,7 @@ namespace Content.Server.GameObjects.Components.GUI
{
base.Initialize();
_inventory = Owner.GetComponent<InventoryComponent>();
_inventory = Owner.EnsureComponent<InventoryComponent>();
}
bool IInventoryController.CanEquip(Slots slot, IEntity entity, bool flagsCheck, out string reason)

View File

@@ -28,10 +28,8 @@ namespace Content.Server.GameObjects.Components.GUI
[RegisterComponent]
public class InventoryComponent : SharedInventoryComponent, IExAct, IEffectBlocker, IPressureProtection
{
#pragma warning disable 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
[Dependency] private readonly IServerNotifyManager _serverNotifyManager;
#pragma warning restore 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly IServerNotifyManager _serverNotifyManager = default!;
[ViewVariables]
private readonly Dictionary<Slots, ContainerSlot> _slotContainers = new Dictionary<Slots, ContainerSlot>();

View File

@@ -1,12 +1,15 @@
using System;
#nullable enable
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Content.Server.GameObjects.Components.ActionBlocking;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.EntitySystems.DoAfter;
using Content.Server.Interfaces;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components.GUI;
using Content.Shared.GameObjects.Components.Inventory;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Verbs;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.Interfaces.GameObjects;
@@ -16,7 +19,6 @@ using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.ViewVariables;
using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines;
@@ -25,27 +27,39 @@ namespace Content.Server.GameObjects.Components.GUI
[RegisterComponent]
public sealed class StrippableComponent : SharedStrippableComponent, IDragDrop
{
[Dependency] private IServerNotifyManager _notifyManager = default!;
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
public const float StripDelay = 2f;
[ViewVariables]
private BoundUserInterface _userInterface;
private InventoryComponent _inventoryComponent;
private HandsComponent _handsComponent;
[ViewVariables]
private BoundUserInterface? UserInterface => Owner.GetUIOrNull(StrippingUiKey.Key);
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(StrippingUiKey.Key);
_userInterface.OnReceiveMessage += HandleUserInterfaceMessage;
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += HandleUserInterfaceMessage;
}
_inventoryComponent = Owner.GetComponent<InventoryComponent>();
_handsComponent = Owner.GetComponent<HandsComponent>();
Owner.EnsureComponent<InventoryComponent>();
Owner.EnsureComponent<HandsComponent>();
Owner.EnsureComponent<CuffableComponent>();
if (Owner.TryGetComponent(out CuffableComponent? cuffed))
{
cuffed.OnCuffedStateChanged += UpdateSubscribed;
}
if (Owner.TryGetComponent(out InventoryComponent? inventory))
{
inventory.OnItemChanged += UpdateSubscribed;
}
_inventoryComponent.OnItemChanged += UpdateSubscribed;
if (Owner.TryGetComponent(out HandsComponent? hands))
{
hands.OnItemChanged += UpdateSubscribed;
}
// Initial update.
UpdateSubscribed();
@@ -53,33 +67,69 @@ namespace Content.Server.GameObjects.Components.GUI
private void UpdateSubscribed()
{
if (UserInterface == null)
{
return;
}
var inventory = GetInventorySlots();
var hands = GetHandSlots();
var cuffs = GetHandcuffs();
_userInterface.SetState(new StrippingBoundUserInterfaceState(inventory, hands));
UserInterface.SetState(new StrippingBoundUserInterfaceState(inventory, hands, cuffs));
}
public bool CanBeStripped(IEntity by)
{
return by != Owner
&& by.HasComponent<HandsComponent>()
&& ActionBlockerSystem.CanInteract(by);
}
public bool CanDragDrop(DragDropEventArgs eventArgs)
{
return eventArgs.User.HasComponent<HandsComponent>()
&& eventArgs.Target != eventArgs.Dropped && eventArgs.Target == eventArgs.User;
return eventArgs.Target != eventArgs.Dropped
&& eventArgs.Target == eventArgs.User
&& CanBeStripped(eventArgs.User);
}
public bool DragDrop(DragDropEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) return false;
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) return false;
OpenUserInterface(actor.playerSession);
return true;
}
private Dictionary<EntityUid, string> GetHandcuffs()
{
var dictionary = new Dictionary<EntityUid, string>();
if (!Owner.TryGetComponent(out CuffableComponent? cuffed))
{
return dictionary;
}
foreach (IEntity entity in cuffed.StoredEntities)
{
dictionary.Add(entity.Uid, entity.Name);
}
return dictionary;
}
private Dictionary<Slots, string> GetInventorySlots()
{
var dictionary = new Dictionary<Slots, string>();
foreach (var slot in _inventoryComponent.Slots)
if (!Owner.TryGetComponent(out InventoryComponent? inventory))
{
dictionary[slot] = _inventoryComponent.GetSlotItem(slot)?.Owner.Name ?? "None";
return dictionary;
}
foreach (var slot in inventory.Slots)
{
dictionary[slot] = inventory.GetSlotItem(slot)?.Owner.Name ?? "None";
}
return dictionary;
@@ -89,9 +139,14 @@ namespace Content.Server.GameObjects.Components.GUI
{
var dictionary = new Dictionary<string, string>();
foreach (var hand in _handsComponent.Hands)
if (!Owner.TryGetComponent(out HandsComponent? hands))
{
dictionary[hand] = _handsComponent.GetItem(hand)?.Owner.Name ?? "None";
return dictionary;
}
foreach (var hand in hands.Hands)
{
dictionary[hand] = hands.GetItem(hand)?.Owner.Name ?? "None";
}
return dictionary;
@@ -99,7 +154,7 @@ namespace Content.Server.GameObjects.Components.GUI
private void OpenUserInterface(IPlayerSession session)
{
_userInterface.Open(session);
UserInterface?.Open(session);
}
/// <summary>
@@ -336,34 +391,80 @@ namespace Content.Server.GameObjects.Components.GUI
private void HandleUserInterfaceMessage(ServerBoundUserInterfaceMessage obj)
{
var user = obj.Session.AttachedEntity;
if (user == null || !(user.TryGetComponent(out HandsComponent userHands))) return;
if (user == null || !(user.TryGetComponent(out HandsComponent? userHands))) return;
var placingItem = userHands.GetActiveHand != null;
switch (obj.Message)
{
case StrippingInventoryButtonPressed inventoryMessage:
var inventory = Owner.GetComponent<InventoryComponent>();
if (inventory.TryGetSlotItem(inventoryMessage.Slot, out ItemComponent _))
placingItem = false;
if (Owner.TryGetComponent<InventoryComponent>(out var inventory))
{
if (inventory.TryGetSlotItem(inventoryMessage.Slot, out ItemComponent _))
placingItem = false;
if(placingItem)
PlaceActiveHandItemInInventory(user, inventoryMessage.Slot);
else
TakeItemFromInventory(user, inventoryMessage.Slot);
if (placingItem)
PlaceActiveHandItemInInventory(user, inventoryMessage.Slot);
else
TakeItemFromInventory(user, inventoryMessage.Slot);
}
break;
case StrippingHandButtonPressed handMessage:
var hands = Owner.GetComponent<HandsComponent>();
if (hands.TryGetItem(handMessage.Hand, out _))
placingItem = false;
if (Owner.TryGetComponent<HandsComponent>(out var hands))
{
if (hands.TryGetItem(handMessage.Hand, out _))
placingItem = false;
if(placingItem)
PlaceActiveHandItemInHands(user, handMessage.Hand);
else
TakeItemFromHands(user, handMessage.Hand);
if (placingItem)
PlaceActiveHandItemInHands(user, handMessage.Hand);
else
TakeItemFromHands(user, handMessage.Hand);
}
break;
case StrippingHandcuffButtonPressed handcuffMessage:
if (Owner.TryGetComponent<CuffableComponent>(out var cuffed))
{
foreach (var entity in cuffed.StoredEntities)
{
if (entity.Uid == handcuffMessage.Handcuff)
{
cuffed.TryUncuff(user, entity);
return;
}
}
}
break;
}
}
[Verb]
private sealed class StripVerb : Verb<StrippableComponent>
{
protected override void GetData(IEntity user, StrippableComponent component, VerbData data)
{
if (!component.CanBeStripped(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
data.Visibility = VerbVisibility.Visible;
data.Text = Loc.GetString("Strip");
}
protected override void Activate(IEntity user, StrippableComponent component)
{
if (!user.TryGetComponent(out IActorComponent? actor))
{
return;
}
component.OpenUserInterface(actor.playerSession);
}
}
}

View File

@@ -1,9 +1,10 @@
using System.Threading.Tasks;
#nullable enable
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Damage;
using Content.Server.GameObjects.Components.Interactable;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components.Gravity;
using Content.Shared.GameObjects.Components.Interactable;
using Content.Shared.GameObjects.EntitySystems;
@@ -16,17 +17,13 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Gravity
{
[RegisterComponent]
public class GravityGeneratorComponent : SharedGravityGeneratorComponent, IInteractUsing, IBreakAct, IInteractHand
{
private BoundUserInterface _userInterface;
private PowerReceiverComponent _powerReceiver;
private SpriteComponent _sprite;
private bool _switchedOn;
@@ -34,7 +31,7 @@ namespace Content.Server.GameObjects.Components.Gravity
private GravityGeneratorStatus _status;
public bool Powered => _powerReceiver.Powered;
public bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
public bool SwitchedOn => _switchedOn;
@@ -64,15 +61,17 @@ namespace Content.Server.GameObjects.Components.Gravity
public override string Name => "GravityGenerator";
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GravityGeneratorUiKey.Key);
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(GravityGeneratorUiKey.Key);
_userInterface.OnReceiveMessage += HandleUIMessage;
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
_sprite = Owner.GetComponent<SpriteComponent>();
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += HandleUIMessage;
}
_switchedOn = true;
_intact = true;
_status = GravityGeneratorStatus.On;
@@ -101,7 +100,7 @@ namespace Content.Server.GameObjects.Components.Gravity
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
{
if (!eventArgs.Using.TryGetComponent(out WelderComponent tool))
if (!eventArgs.Using.TryGetComponent(out WelderComponent? tool))
return false;
if (!await tool.UseTool(eventArgs.User, Owner, 2f, ToolQuality.Welding, 5f))
@@ -150,7 +149,7 @@ namespace Content.Server.GameObjects.Components.Gravity
switch (message.Message)
{
case GeneratorStatusRequestMessage _:
_userInterface.SetState(new GeneratorState(Status == GravityGeneratorStatus.On));
UserInterface?.SetState(new GeneratorState(Status == GravityGeneratorStatus.On));
break;
case SwitchGeneratorMessage msg:
_switchedOn = msg.On;
@@ -163,35 +162,51 @@ namespace Content.Server.GameObjects.Components.Gravity
private void OpenUserInterface(IPlayerSession playerSession)
{
_userInterface.Open(playerSession);
UserInterface?.Open(playerSession);
}
private void MakeBroken()
{
_status = GravityGeneratorStatus.Broken;
_sprite.LayerSetState(0, "broken");
_sprite.LayerSetVisible(1, false);
if (Owner.TryGetComponent(out SpriteComponent? sprite))
{
sprite.LayerSetState(0, "broken");
sprite.LayerSetVisible(1, false);
}
}
private void MakeUnpowered()
{
_status = GravityGeneratorStatus.Unpowered;
_sprite.LayerSetState(0, "off");
_sprite.LayerSetVisible(1, false);
if (Owner.TryGetComponent(out SpriteComponent? sprite))
{
sprite.LayerSetState(0, "off");
sprite.LayerSetVisible(1, false);
}
}
private void MakeOff()
{
_status = GravityGeneratorStatus.Off;
_sprite.LayerSetState(0, "off");
_sprite.LayerSetVisible(1, false);
if (Owner.TryGetComponent(out SpriteComponent? sprite))
{
sprite.LayerSetState(0, "off");
sprite.LayerSetVisible(1, false);
}
}
private void MakeOn()
{
_status = GravityGeneratorStatus.On;
_sprite.LayerSetState(0, "on");
_sprite.LayerSetVisible(1, true);
if (Owner.TryGetComponent(out SpriteComponent? sprite))
{
sprite.LayerSetState(0, "on");
sprite.LayerSetVisible(1, true);
}
}
}

View File

@@ -1,8 +1,10 @@
using System;
#nullable enable
using System;
using System.Linq;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.Interfaces;
using Content.Server.Mobs;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components.Instruments;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
@@ -34,12 +36,8 @@ namespace Content.Server.GameObjects.Components.Instruments
IUse,
IThrown
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager;
[Dependency] private readonly IGameTiming _gameTiming;
#pragma warning restore 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
private static readonly TimeSpan OneSecAgo = TimeSpan.FromSeconds(-1);
@@ -47,7 +45,7 @@ namespace Content.Server.GameObjects.Components.Instruments
/// The client channel currently playing the instrument, or null if there's none.
/// </summary>
[ViewVariables]
private IPlayerSession _instrumentPlayer;
private IPlayerSession? _instrumentPlayer;
private bool _handheld;
@@ -72,9 +70,6 @@ namespace Content.Server.GameObjects.Components.Instruments
[ViewVariables]
private int _midiEventCount = 0;
[ViewVariables]
private BoundUserInterface _userInterface;
/// <summary>
/// Whether the instrument is an item which can be held or not.
/// </summary>
@@ -95,7 +90,7 @@ namespace Content.Server.GameObjects.Components.Instruments
}
}
public IPlayerSession InstrumentPlayer
public IPlayerSession? InstrumentPlayer
{
get => _instrumentPlayer;
private set
@@ -108,11 +103,13 @@ namespace Content.Server.GameObjects.Components.Instruments
_instrumentPlayer = value;
if (value != null)
_instrumentPlayer.PlayerStatusChanged += OnPlayerStatusChanged;
_instrumentPlayer!.PlayerStatusChanged += OnPlayerStatusChanged;
}
}
private void OnPlayerStatusChanged(object sender, SessionStatusEventArgs e)
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(InstrumentUiKey.Key);
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{
if (e.Session != _instrumentPlayer || e.NewStatus != SessionStatus.Disconnected) return;
InstrumentPlayer = null;
@@ -122,8 +119,11 @@ namespace Content.Server.GameObjects.Components.Instruments
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(InstrumentUiKey.Key);
_userInterface.OnClosed += UserInterfaceOnClosed;
if (UserInterface != null)
{
UserInterface.OnClosed += UserInterfaceOnClosed;
}
}
public override void ExposeData(ObjectSerializer serializer)
@@ -137,14 +137,14 @@ namespace Content.Server.GameObjects.Components.Instruments
return new InstrumentState(Playing, _lastSequencerTick);
}
public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession session = null)
public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession? session = null)
{
base.HandleNetworkMessage(message, channel, session);
switch (message)
{
case InstrumentMidiEventMessage midiEventMsg:
if (!Playing || session != _instrumentPlayer) return;
if (!Playing || session != _instrumentPlayer || InstrumentPlayer == null) return;
var send = true;
@@ -231,7 +231,7 @@ namespace Content.Server.GameObjects.Components.Instruments
Clean();
SendNetworkMessage(new InstrumentStopMidiMessage());
InstrumentPlayer = null;
_userInterface.CloseAll();
UserInterface?.CloseAll();
}
public void Thrown(ThrownEventArgs eventArgs)
@@ -239,7 +239,7 @@ namespace Content.Server.GameObjects.Components.Instruments
Clean();
SendNetworkMessage(new InstrumentStopMidiMessage());
InstrumentPlayer = null;
_userInterface.CloseAll();
UserInterface?.CloseAll();
}
public void HandSelected(HandSelectedEventArgs eventArgs)
@@ -255,12 +255,12 @@ namespace Content.Server.GameObjects.Components.Instruments
{
Clean();
SendNetworkMessage(new InstrumentStopMidiMessage());
_userInterface.CloseAll();
UserInterface?.CloseAll();
}
public void Activate(ActivateEventArgs eventArgs)
{
if (Handheld || !eventArgs.User.TryGetComponent(out IActorComponent actor)) return;
if (Handheld || !eventArgs.User.TryGetComponent(out IActorComponent? actor)) return;
if (InstrumentPlayer != null) return;
@@ -270,7 +270,7 @@ namespace Content.Server.GameObjects.Components.Instruments
public bool UseEntity(UseEntityEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) return false;
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) return false;
if (InstrumentPlayer == actor.playerSession)
{
@@ -291,7 +291,7 @@ namespace Content.Server.GameObjects.Components.Instruments
private void OpenUserInterface(IPlayerSession session)
{
_userInterface.Open(session);
UserInterface?.Open(session);
}
public override void Update(float delta)
@@ -302,7 +302,7 @@ namespace Content.Server.GameObjects.Components.Instruments
{
InstrumentPlayer = null;
Clean();
_userInterface.CloseAll();
UserInterface?.CloseAll();
}
if ((_batchesDropped >= MaxMidiBatchDropped
@@ -314,9 +314,9 @@ namespace Content.Server.GameObjects.Components.Instruments
SendNetworkMessage(new InstrumentStopMidiMessage());
Playing = false;
_userInterface.CloseAll();
UserInterface?.CloseAll();
if (mob.TryGetComponent(out StunnableComponent stun))
if (mob != null && mob.TryGetComponent(out StunnableComponent? stun))
{
stun.Stun(1);
Clean();

View File

@@ -1,4 +1,5 @@
using System.Threading.Tasks;
#nullable enable
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Clothing;
using Content.Server.GameObjects.Components.Items.Storage;
@@ -27,28 +28,26 @@ namespace Content.Server.GameObjects.Components.Interactable
/// Component that represents a handheld lightsource which can be toggled on and off.
/// </summary>
[RegisterComponent]
internal sealed class HandheldLightComponent : SharedHandheldLightComponent, IUse, IExamine, IInteractUsing, IMapInit
internal sealed class HandheldLightComponent : SharedHandheldLightComponent, IUse, IExamine, IInteractUsing,
IMapInit
{
#pragma warning disable 649
[Dependency] private readonly ISharedNotifyManager _notifyManager;
[Dependency] private readonly ILocalizationManager _localizationManager;
#pragma warning restore 649
[Dependency] private readonly ISharedNotifyManager _notifyManager = default!;
[ViewVariables(VVAccess.ReadWrite)] public float Wattage { get; set; } = 10;
[ViewVariables] private ContainerSlot _cellContainer;
private PointLightComponent _pointLight;
private SpriteComponent _spriteComponent;
private ClothingComponent _clothingComponent;
[ViewVariables] private ContainerSlot _cellContainer = default!;
[ViewVariables]
private BatteryComponent Cell
private BatteryComponent? Cell
{
get
{
if (_cellContainer.ContainedEntity == null) return null;
if (_cellContainer.ContainedEntity.TryGetComponent(out BatteryComponent? cell))
{
return cell;
}
_cellContainer.ContainedEntity.TryGetComponent(out BatteryComponent cell);
return cell;
return null;
}
}
@@ -58,6 +57,8 @@ namespace Content.Server.GameObjects.Components.Interactable
[ViewVariables]
public bool Activated { get; private set; }
[ViewVariables] protected override bool HasCell => Cell != null;
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
if (!eventArgs.Using.HasComponent<BatteryComponent>()) return false;
@@ -81,11 +82,9 @@ namespace Content.Server.GameObjects.Components.Interactable
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
{
var loc = IoCManager.Resolve<ILocalizationManager>();
if (Activated)
{
message.AddMarkup(loc.GetString("The light is currently [color=darkgreen]on[/color]."));
message.AddMarkup(Loc.GetString("The light is currently [color=darkgreen]on[/color]."));
}
}
@@ -98,11 +97,10 @@ namespace Content.Server.GameObjects.Components.Interactable
{
base.Initialize();
_pointLight = Owner.GetComponent<PointLightComponent>();
_spriteComponent = Owner.GetComponent<SpriteComponent>();
Owner.TryGetComponent(out _clothingComponent);
Owner.EnsureComponent<PointLightComponent>();
_cellContainer =
ContainerManagerComponent.Ensure<ContainerSlot>("flashlight_cell_container", Owner, out _);
Dirty();
}
@@ -140,7 +138,6 @@ namespace Content.Server.GameObjects.Components.Interactable
Activated = false;
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Items/flashlight_toggle.ogg", Owner);
}
private void TurnOn(IEntity user)
@@ -153,10 +150,9 @@ namespace Content.Server.GameObjects.Components.Interactable
var cell = Cell;
if (cell == null)
{
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/button.ogg", Owner);
_notifyManager.PopupMessage(Owner, user, _localizationManager.GetString("Cell missing..."));
_notifyManager.PopupMessage(Owner, user, Loc.GetString("Cell missing..."));
return;
}
@@ -166,7 +162,7 @@ namespace Content.Server.GameObjects.Components.Interactable
if (Wattage > cell.CurrentCharge)
{
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/button.ogg", Owner);
_notifyManager.PopupMessage(Owner, user, _localizationManager.GetString("Dead cell..."));
_notifyManager.PopupMessage(Owner, user, Loc.GetString("Dead cell..."));
return;
}
@@ -174,25 +170,46 @@ namespace Content.Server.GameObjects.Components.Interactable
SetState(true);
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Items/flashlight_toggle.ogg", Owner);
}
private void SetState(bool on)
{
_spriteComponent.LayerSetVisible(1, on);
_pointLight.Enabled = on;
if (_clothingComponent != null)
if (Owner.TryGetComponent(out SpriteComponent? sprite))
{
_clothingComponent.ClothingEquippedPrefix = on ? "On" : "Off";
sprite.LayerSetVisible(1, on);
}
if (Owner.TryGetComponent(out PointLightComponent? light))
{
light.Enabled = on;
}
if (Owner.TryGetComponent(out ClothingComponent? clothing))
{
clothing.ClothingEquippedPrefix = on ? "On" : "Off";
}
}
public void OnUpdate(float frameTime)
{
if (!Activated) return;
if (!Activated || Cell == null) return;
var cell = Cell;
if (cell == null || !cell.TryUseCharge(Wattage * frameTime)) TurnOff();
var appearanceComponent = Owner.GetComponent<AppearanceComponent>();
if (Cell.MaxCharge - Cell.CurrentCharge < Cell.MaxCharge * 0.70)
{
appearanceComponent.SetData(HandheldLightVisuals.Power, HandheldLightPowerStates.FullPower);
}
else if (Cell.MaxCharge - Cell.CurrentCharge < Cell.MaxCharge * 0.90)
{
appearanceComponent.SetData(HandheldLightVisuals.Power, HandheldLightPowerStates.LowPower);
}
else
{
appearanceComponent.SetData(HandheldLightVisuals.Power, HandheldLightPowerStates.Dying);
}
if (Cell == null || !Cell.TryUseCharge(Wattage * frameTime)) TurnOff();
Dirty();
}
@@ -211,7 +228,9 @@ namespace Content.Server.GameObjects.Components.Interactable
return;
}
if (!user.TryGetComponent(out HandsComponent hands))
Dirty();
if (!user.TryGetComponent(out HandsComponent? hands))
{
return;
}
@@ -222,24 +241,23 @@ namespace Content.Server.GameObjects.Components.Interactable
}
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Items/pistol_magout.ogg", Owner);
}
public override ComponentState GetComponentState()
{
if (Cell == null)
{
return new HandheldLightComponentState(null);
return new HandheldLightComponentState(null, false);
}
if (Wattage > Cell.CurrentCharge)
{
// Practically zero.
// This is so the item status works correctly.
return new HandheldLightComponentState(0);
return new HandheldLightComponentState(0, HasCell);
}
return new HandheldLightComponentState(Cell.CurrentCharge / Cell.MaxCharge);
return new HandheldLightComponentState(Cell.CurrentCharge / Cell.MaxCharge, HasCell);
}
[Verb]

View File

@@ -14,11 +14,9 @@ namespace Content.Server.GameObjects.Components.Interactable
[RegisterComponent]
public class TilePryingComponent : Component, IAfterInteract
{
#pragma warning disable 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager;
[Dependency] private readonly IMapManager _mapManager;
#pragma warning restore 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
public override string Name => "TilePrying";
private bool _toolComponentNeeded = true;

View File

@@ -29,10 +29,8 @@ namespace Content.Server.GameObjects.Components.Interactable
[ComponentReference(typeof(IToolComponent))]
public class WelderComponent : ToolComponent, IExamine, IUse, ISuicideAct, ISolutionChange
{
#pragma warning disable 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
#pragma warning restore 649
public override string Name => "Welder";
public override uint? NetID => ContentNetIDs.WELDER;

View File

@@ -20,9 +20,7 @@ namespace Content.Server.GameObjects.Components.Items.Clothing
[ComponentReference(typeof(IItemComponent))]
public class ClothingComponent : ItemComponent, IUse
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _serverNotifyManager;
#pragma warning restore 649
[Dependency] private readonly IServerNotifyManager _serverNotifyManager = default!;
public override string Name => "Clothing";
public override uint? NetID => ContentNetIDs.CLOTHING;
@@ -63,7 +61,6 @@ namespace Content.Server.GameObjects.Components.Items.Clothing
});
serializer.DataField(ref _quickEquipEnabled, "QuickEquip", true);
serializer.DataFieldCached(ref _heatResistance, "HeatResistance", 323);
}

View File

@@ -20,10 +20,8 @@ namespace Content.Server.GameObjects.Components.Items
[RegisterComponent]
public class DiceComponent : Component, IActivate, IUse, ILand, IExamine
{
#pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager;
[Dependency] private readonly IRobustRandom _random;
#pragma warning restore 649
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
public override string Name => "Dice";
@@ -85,9 +83,9 @@ namespace Content.Server.GameObjects.Components.Items
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
{
//No details check, since the sprite updates to show the side.
var loc = IoCManager.Resolve<ILocalizationManager>();
message.AddMarkup(loc.GetString("A dice with [color=lightgray]{0}[/color] sides.\n" +
"It has landed on a [color=white]{1}[/color].", _sides, _currentSide));
message.AddMarkup(Loc.GetString(
"A dice with [color=lightgray]{0}[/color] sides.\n" + "It has landed on a [color=white]{1}[/color].",
_sides, _currentSide));
}
}
}

Some files were not shown because too many files have changed in this diff Show More