Merge branch 'master' into 2021-12-03-remove-IEntity-komm-süsser-todd

# Conflicts:
#	Content.Client/Crayon/CrayonDecalVisualizer.cs
#	Content.Client/Tabletop/TabletopSystem.cs
#	Content.IntegrationTests/Tests/InventoryHelpersTest.cs
#	Content.Server/AI/EntitySystems/AiSystem.cs
#	Content.Server/AI/Utility/AiLogic/UtilityAI.cs
#	Content.Server/AME/AMENodeGroup.cs
#	Content.Server/Administration/AdminVerbSystem.cs
#	Content.Server/Body/Systems/RespiratorSystem.cs
#	Content.Server/Chemistry/Components/InjectorComponent.cs
#	Content.Server/Chemistry/TileReactions/CleanTileReaction.cs
#	Content.Server/Chemistry/TileReactions/SpillTileReaction.cs
#	Content.Server/Crayon/CrayonComponent.cs
#	Content.Server/Doors/Components/ServerDoorComponent.cs
#	Content.Server/Explosion/EntitySystems/TriggerSystem.cs
#	Content.Server/Fluids/Components/MopComponent.cs
#	Content.Server/Fluids/Components/SpillExtensions.cs
#	Content.Server/Fluids/EntitySystems/PuddleSystem.cs
#	Content.Server/Instruments/InstrumentSystem.cs
#	Content.Server/Nutrition/EntitySystems/DrinkSystem.cs
#	Content.Server/Nutrition/EntitySystems/FoodSystem.cs
#	Content.Server/PneumaticCannon/PneumaticCannonSystem.cs
#	Content.Server/Storage/Components/EntityStorageComponent.cs
#	Content.Server/Storage/Components/StorageFillComponent.cs
#	Content.Server/Stunnable/StunbatonSystem.cs
#	Content.Server/Throwing/ThrowHelper.cs
#	Content.Server/Weapon/Ranged/Barrels/BarrelSystem.cs
#	Content.Server/Weapon/Ranged/Barrels/Components/ServerBatteryBarrelComponent.cs
#	Content.Server/Weapon/Ranged/ServerRangedWeaponComponent.cs
#	Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs
#	Content.Shared/Damage/Components/DamageableComponent.cs
#	Content.Shared/Damage/Systems/DamageableSystem.cs
#	Content.Shared/MobState/Components/MobStateComponent.cs
#	Content.Shared/Slippery/SharedSlipperySystem.cs
This commit is contained in:
Vera Aguilera Puerto
2021-12-07 17:48:49 +01:00
171 changed files with 8579 additions and 6088 deletions

View File

@@ -300,12 +300,13 @@ namespace Content.Shared.CCVar
CVarDef.Create("hud.fps_counter_visible", false, CVar.CLIENTONLY | CVar.ARCHIVE);
/*
* AI
* NPCs
*/
public static readonly CVarDef<int> AIMaxUpdates =
CVarDef.Create("ai.maxupdates", 64);
public static readonly CVarDef<int> NPCMaxUpdates =
CVarDef.Create("npc.max_updates", 64);
public static readonly CVarDef<bool> NPCEnabled = CVarDef.Create("npc.enabled", true);
/*
* Net

View File

@@ -1,6 +1,7 @@
using Content.Shared.Sound;
using Content.Shared.Whitelist;
using Robust.Shared.Analyzers;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
@@ -81,6 +82,12 @@ namespace Content.Shared.Containers.ItemSlots
public SoundSpecifier? EjectSound;
// maybe default to /Audio/Machines/id_swipe.ogg?
/// <summary>
/// Options used for playing the insert/eject sounds.
/// </summary>
[DataField("soundOptions")]
public AudioParams SoundOptions = AudioParams.Default;
/// <summary>
/// The name of this item slot. This will be shown to the user in the verb menu.
/// </summary>
@@ -116,6 +123,18 @@ namespace Content.Shared.Containers.ItemSlots
[DataField("ejectOnInteract")]
public bool EjectOnInteract = false;
/// <summary>
/// If true, and if this slot is attached to an item, then it will attempt to eject slot when to the slot is
/// used in the user's hands.
/// </summary>
/// <remarks>
/// Desirable for things like ranged weapons ('Z' to eject), but not desirable for others (e.g., PDA uses
/// 'Z' to open UI). Unlike <see cref="EjectOnInteract"/>, this will not make any changes to the context
/// menu, nor will it disable alt-click interactions.
/// </remarks>
[DataField("ejectOnUse")]
public bool EjectOnUse = false;
/// <summary>
/// Override the insert verb text. Defaults to [insert category] -> [item-name]. If not null, the verb will
/// not be given a category.

View File

@@ -34,6 +34,7 @@ namespace Content.Shared.Containers.ItemSlots
SubscribeLocalEvent<ItemSlotsComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<ItemSlotsComponent, InteractHandEvent>(OnInteractHand);
SubscribeLocalEvent<ItemSlotsComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<ItemSlotsComponent, GetAlternativeVerbsEvent>(AddEjectVerbs);
SubscribeLocalEvent<ItemSlotsComponent, GetInteractionVerbsEvent>(AddInteractionVerbsVerbs);
@@ -125,6 +126,25 @@ namespace Content.Shared.Containers.ItemSlots
}
}
/// <summary>
/// Attempt to eject an item from the first valid item slot.
/// </summary>
private void OnUseInHand(EntityUid uid, ItemSlotsComponent itemSlots, UseInHandEvent args)
{
if (args.Handled)
return;
foreach (var slot in itemSlots.Slots.Values)
{
if (slot.Locked || !slot.EjectOnUse || slot.Item == null)
continue;
args.Handled = true;
TryEjectToHands(uid, slot, args.User);
break;
}
}
/// <summary>
/// Tries to insert a held item in any fitting item slot. If a valid slot already contains an item, it will
/// swap it out and place the old one in the user's hand.
@@ -172,7 +192,7 @@ namespace Content.Shared.Containers.ItemSlots
// ContainerSlot automatically raises a directed EntInsertedIntoContainerMessage
if (slot.InsertSound != null)
SoundSystem.Play(Filter.Pvs(uid), slot.InsertSound.GetSound(), uid);
SoundSystem.Play(Filter.Pvs(uid), slot.InsertSound.GetSound(), uid, slot.SoundOptions);
}
/// <summary>
@@ -267,7 +287,7 @@ namespace Content.Shared.Containers.ItemSlots
// ContainerSlot automatically raises a directed EntRemovedFromContainerMessage
if (slot.EjectSound != null)
SoundSystem.Play(Filter.Pvs(uid), slot.EjectSound.GetSound(), uid);
SoundSystem.Play(Filter.Pvs(uid), slot.EjectSound.GetSound(), uid, slot.SoundOptions);
}
/// <summary>
@@ -317,7 +337,7 @@ namespace Content.Shared.Containers.ItemSlots
return false;
if (user != null && EntityManager.TryGetComponent(user.Value, out SharedHandsComponent? hands))
hands.TryPutInAnyHand(item.Value);
hands.TryPutInActiveHandOrAny(item.Value);
return true;
}

View File

@@ -72,16 +72,4 @@ namespace Content.Shared.Crayon
Color = color;
}
}
[Serializable, NetSerializable, Prototype("crayonDecal")]
public class CrayonDecalPrototype : IPrototype
{
[ViewVariables]
[DataField("id", required: true)]
public string ID { get; } = default!;
[DataField("spritePath")] public string SpritePath { get; } = string.Empty;
[DataField("decals")] public List<string> Decals { get; } = new();
}
}

View File

@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using Content.Shared.Acts;
using Content.Shared.Damage.Prototypes;
using Content.Shared.Damage;
using Content.Shared.FixedPoint;
using Content.Shared.Radiation;
using Robust.Shared.Analyzers;
@@ -13,8 +12,6 @@ using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
using Robust.Shared.ViewVariables;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
namespace Content.Shared.Damage
{
@@ -95,11 +92,7 @@ namespace Content.Shared.Damage
damage.DamageDict.Add(typeID, damageValue);
}
var actual = EntitySystem.Get<DamageableSystem>().TryChangeDamage(Owner, damage);
// should logging be disabled during rad storms? a lot of entities are going to be damaged.
if (actual != null && !actual.Empty)
EntitySystem.Get<SharedAdminLogSystem>().Add(LogType.Radiation, $"{Owner} took {actual.Total} radiation damage");
EntitySystem.Get<DamageableSystem>().TryChangeDamage(Owner, damage);
}
// TODO EXPLOSION Remove this.
@@ -120,11 +113,7 @@ namespace Content.Shared.Damage
damage.DamageDict.Add(typeID, damageValue);
}
var actual = EntitySystem.Get<DamageableSystem>().TryChangeDamage(Owner, damage);
// will logging handle nukes?
if (actual != null && !actual.Empty)
EntitySystem.Get<SharedAdminLogSystem>().Add(LogType.Explosion, $"{Owner} took {actual.Total} explosion damage");
EntitySystem.Get<DamageableSystem>().TryChangeDamage(Owner, damage);
}
}

View File

@@ -1,7 +1,5 @@
using System.Linq;
using Content.Shared.Administration.Logs;
using Content.Shared.Damage.Prototypes;
using Content.Shared.Database;
using Content.Shared.FixedPoint;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
@@ -15,8 +13,6 @@ namespace Content.Shared.Damage
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedAdminLogSystem _logs = default!;
public override void Initialize()
{
SubscribeLocalEvent<DamageableComponent, ComponentInit>(DamageableInit);
@@ -24,45 +20,6 @@ namespace Content.Shared.Damage
SubscribeLocalEvent<DamageableComponent, ComponentGetState>(DamageableGetState);
}
/// <summary>
/// Update the total damage value and optionally add to admin logs
/// </summary>
protected virtual void SetTotalDamage(DamageableComponent damageable, FixedPoint2 @new, bool logChange)
{
var owner = damageable.Owner;
var old = damageable.TotalDamage;
if (@new == old)
{
return;
}
damageable.TotalDamage = @new;
if (!logChange)
return;
LogType logType;
string type;
FixedPoint2 change;
if (@new > old)
{
logType = LogType.Damaged;
type = "received";
change = @new - old;
}
else
{
logType = LogType.Healed;
type = "healed";
change = old - @new;
}
_logs.Add(logType, $"{owner} {type} {change} damage. Old: {old} | New: {@new}");
}
/// <summary>
/// Initialize a damageable component
/// </summary>
@@ -111,7 +68,7 @@ namespace Content.Shared.Damage
public void SetDamage(DamageableComponent damageable, DamageSpecifier damage)
{
damageable.Damage = damage;
DamageChanged(damageable, false);
DamageChanged(damageable);
}
/// <summary>
@@ -121,11 +78,11 @@ namespace Content.Shared.Damage
/// This updates cached damage information, flags the component as dirty, and raises a damage changed event.
/// The damage changed event is used by other systems, such as damage thresholds.
/// </remarks>
public void DamageChanged(DamageableComponent component, bool logChange, DamageSpecifier? damageDelta = null,
public void DamageChanged(DamageableComponent component, DamageSpecifier? damageDelta = null,
bool interruptsDoAfters = true)
{
component.DamagePerGroup = component.Damage.GetDamagePerGroup();
SetTotalDamage(component, component.Damage.Total, logChange);
component.TotalDamage = component.Damage.Total;
component.Dirty();
if (EntityManager.TryGetComponent<AppearanceComponent>(component.Owner, out var appearance) && damageDelta != null)
@@ -146,7 +103,7 @@ namespace Content.Shared.Damage
/// null if the user had no applicable components that can take damage.
/// </returns>
public DamageSpecifier? TryChangeDamage(EntityUid? uid, DamageSpecifier damage, bool ignoreResistances = false,
bool interruptsDoAfters = true, bool logChange = false)
bool interruptsDoAfters = true)
{
if (!EntityManager.TryGetComponent<DamageableComponent>(uid, out var damageable))
{
@@ -195,7 +152,7 @@ namespace Content.Shared.Damage
if (!delta.Empty)
{
DamageChanged(damageable, logChange, delta, interruptsDoAfters);
DamageChanged(damageable, delta, interruptsDoAfters);
}
return delta;
@@ -222,7 +179,7 @@ namespace Content.Shared.Damage
// Setting damage does not count as 'dealing' damage, even if it is set to a larger value, so we pass an
// empty damage delta.
DamageChanged(component, false, new DamageSpecifier());
DamageChanged(component, new DamageSpecifier());
}
private void DamageableGetState(EntityUid uid, DamageableComponent component, ref ComponentGetState args)
@@ -247,7 +204,7 @@ namespace Content.Shared.Damage
if (!delta.Empty)
{
component.Damage = newDamage;
DamageChanged(component, false, delta);
DamageChanged(component, delta);
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Shared.Decals
{
[Serializable, NetSerializable]
[DataDefinition]
public class Decal
{
[DataField("coordinates")] public readonly Vector2 Coordinates = Vector2.Zero;
[DataField("id")] public readonly string Id = string.Empty;
[DataField("color")] public readonly Color? Color;
[DataField("angle")] public readonly Angle Angle = Angle.Zero;
[DataField("zIndex")] public readonly int ZIndex;
[DataField("cleanable")] public bool Cleanable;
public Decal() {}
public Decal(Vector2 coordinates, string id, Color? color, Angle angle, int zIndex, bool cleanable)
{
Coordinates = coordinates;
Id = id;
Color = color;
Angle = angle;
ZIndex = zIndex;
Cleanable = cleanable;
}
public Decal WithCoordinates(Vector2 coordinates) => new(coordinates, Id, Color, Angle, ZIndex, Cleanable);
public Decal WithId(string id) => new(Coordinates, id, Color, Angle, ZIndex, Cleanable);
public Decal WithColor(Color? color) => new(Coordinates, Id, color, Angle, ZIndex, Cleanable);
public Decal WithRotation(Angle angle) => new(Coordinates, Id, Color, angle, ZIndex, Cleanable);
public Decal WithZIndex(int zIndex) => new(Coordinates, Id, Color, Angle, zIndex, Cleanable);
public Decal WithCleanable(bool cleanable) => new(Coordinates, Id, Color, Angle, ZIndex, cleanable);
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
namespace Content.Shared.Decals
{
[Serializable, NetSerializable]
public class DecalChunkUpdateEvent : EntityEventArgs
{
public Dictionary<GridId, Dictionary<Vector2i, Dictionary<uint, Decal>>> Data = new();
}
}

View File

@@ -0,0 +1,74 @@
using System.Collections.Generic;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
namespace Content.Shared.Decals
{
[TypeSerializer]
public class DecalGridChunkCollectionTypeSerializer : ITypeSerializer<DecalGridComponent.DecalGridChunkCollection, MappingDataNode>
{
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies, ISerializationContext? context = null)
{
return serializationManager.ValidateNode<Dictionary<Vector2i, Dictionary<uint, Decal>>>(node, context);
}
public DeserializationResult Read(ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null)
{
//todo this read method does not support pushing inheritance
var dictionary =
serializationManager.ReadValueOrThrow<Dictionary<Vector2i, Dictionary<uint, Decal>>>(node, context, skipHook);
var uids = new SortedSet<uint>();
var uidChunkMap = new Dictionary<uint, Vector2i>();
foreach (var (indices, decals) in dictionary)
{
foreach (var (uid, _) in decals)
{
uids.Add(uid);
uidChunkMap[uid] = indices;
}
}
var uidMap = new Dictionary<uint, uint>();
uint nextIndex = 0;
foreach (var uid in uids)
{
uidMap[uid] = nextIndex++;
}
var newDict = new Dictionary<Vector2i, Dictionary<uint, Decal>>();
foreach (var (oldUid, newUid) in uidMap)
{
var indices = uidChunkMap[oldUid];
if(!newDict.ContainsKey(indices))
newDict[indices] = new();
newDict[indices][newUid] = dictionary[indices][oldUid];
}
return new DeserializedValue<DecalGridComponent.DecalGridChunkCollection>(
new DecalGridComponent.DecalGridChunkCollection(newDict){NextUid = nextIndex});
}
public DataNode Write(ISerializationManager serializationManager, DecalGridComponent.DecalGridChunkCollection value, bool alwaysWrite = false,
ISerializationContext? context = null)
{
return serializationManager.WriteValue(value.ChunkCollection, alwaysWrite, context);
}
public DecalGridComponent.DecalGridChunkCollection Copy(ISerializationManager serializationManager, DecalGridComponent.DecalGridChunkCollection source,
DecalGridComponent.DecalGridChunkCollection target, bool skipHook, ISerializationContext? context = null)
{
var dict = serializationManager.Copy(source.ChunkCollection, target.ChunkCollection, context, skipHook)!;
return new DecalGridComponent.DecalGridChunkCollection(dict) {NextUid = source.NextUid};
}
}
}

View File

@@ -0,0 +1,23 @@
using System.Collections.Generic;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Shared.Decals
{
[RegisterComponent]
[Friend(typeof(SharedDecalSystem))]
public class DecalGridComponent : Component
{
public override string Name => "DecalGrid";
[DataField("chunkCollection", serverOnly: true)]
public DecalGridChunkCollection ChunkCollection = new(new ());
public record DecalGridChunkCollection(Dictionary<Vector2i, Dictionary<uint, Decal>> ChunkCollection)
{
public uint NextUid;
}
}
}

View File

@@ -0,0 +1,15 @@
using System.Collections.Generic;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
namespace Content.Shared.Decals
{
[Prototype("decal")]
public class DecalPrototype : IPrototype
{
[DataField("id")] public string ID { get; } = null!;
[DataField("sprite")] public SpriteSpecifier Sprite { get; } = SpriteSpecifier.Invalid;
[DataField("tags")] public List<string> Tags = new();
}
}

View File

@@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
namespace Content.Shared.Decals
{
public abstract class SharedDecalSystem : EntitySystem
{
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] protected readonly IMapManager MapManager = default!;
protected readonly Dictionary<GridId, Dictionary<uint, Vector2i>> ChunkIndex = new();
public const int ChunkSize = 32;
public static Vector2i GetChunkIndices(Vector2 coordinates) => new ((int) Math.Floor(coordinates.X / ChunkSize), (int) Math.Floor(coordinates.Y / ChunkSize));
private float _viewSize;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GridInitializeEvent>(OnGridInitialize);
_configurationManager.OnValueChanged(CVars.NetMaxUpdateRange, OnPvsRangeChanged, true);
}
public override void Shutdown()
{
base.Shutdown();
_configurationManager.UnsubValueChanged(CVars.NetMaxUpdateRange, OnPvsRangeChanged);
}
private void OnPvsRangeChanged(float obj)
{
_viewSize = obj * 2f;
}
private void OnGridInitialize(GridInitializeEvent msg)
{
var comp = EntityManager.EnsureComponent<DecalGridComponent>(MapManager.GetGrid(msg.GridId).GridEntityId);
ChunkIndex[msg.GridId] = new();
foreach (var (indices, decals) in comp.ChunkCollection.ChunkCollection)
{
foreach (var uid in decals.Keys)
{
ChunkIndex[msg.GridId][uid] = indices;
}
}
}
protected DecalGridComponent.DecalGridChunkCollection DecalGridChunkCollection(GridId gridId) => EntityManager
.GetComponent<DecalGridComponent>(MapManager.GetGrid(gridId).GridEntityId).ChunkCollection;
protected Dictionary<Vector2i, Dictionary<uint, Decal>> ChunkCollection(GridId gridId) => DecalGridChunkCollection(gridId).ChunkCollection;
protected virtual void DirtyChunk(GridId id, Vector2i chunkIndices) {}
protected bool RemoveDecalInternal(GridId gridId, uint uid)
{
if (!RemoveDecalHook(gridId, uid)) return false;
if (!ChunkIndex.TryGetValue(gridId, out var values) || !values.TryGetValue(uid, out var indices))
{
return false;
}
var chunkCollection = ChunkCollection(gridId);
if (!chunkCollection.TryGetValue(indices, out var chunk) || !chunk.Remove(uid))
{
return false;
}
if (chunkCollection[indices].Count == 0)
chunkCollection.Remove(indices);
ChunkIndex[gridId]?.Remove(uid);
DirtyChunk(gridId, indices);
return true;
}
protected virtual bool RemoveDecalHook(GridId gridId, uint uid) => true;
private (Box2 view, MapId mapId) CalcViewBounds(in EntityUid euid)
{
var xform = EntityManager.GetComponent<TransformComponent>(euid);
var view = Box2.UnitCentered.Scale(_viewSize).Translated(xform.WorldPosition);
var map = xform.MapID;
return (view, map);
}
protected Dictionary<GridId, HashSet<Vector2i>> GetChunksForViewers(HashSet<EntityUid> viewers)
{
var chunks = new Dictionary<GridId, HashSet<Vector2i>>();
foreach (var viewerUid in viewers)
{
var (bounds, mapId) = CalcViewBounds(viewerUid);
MapManager.FindGridsIntersectingEnumerator(mapId, bounds, out var gridsEnumerator, true);
while(gridsEnumerator.MoveNext(out var grid))
{
if(!chunks.ContainsKey(grid.Index))
chunks[grid.Index] = new();
var enumerator = new ChunkIndicesEnumerator(grid.InvWorldMatrix.TransformBox(bounds), ChunkSize);
while (enumerator.MoveNext(out var indices))
{
chunks[grid.Index].Add(indices.Value);
}
}
}
return chunks;
}
}
internal struct ChunkIndicesEnumerator
{
private Vector2i _chunkLB;
private Vector2i _chunkRT;
private int _xIndex;
private int _yIndex;
internal ChunkIndicesEnumerator(Box2 localAABB, int chunkSize)
{
_chunkLB = new Vector2i((int)Math.Floor(localAABB.Left / chunkSize), (int)Math.Floor(localAABB.Bottom / chunkSize));
_chunkRT = new Vector2i((int)Math.Floor(localAABB.Right / chunkSize), (int)Math.Floor(localAABB.Top / chunkSize));
_xIndex = _chunkLB.X;
_yIndex = _chunkLB.Y;
}
public bool MoveNext([NotNullWhen(true)] out Vector2i? indices)
{
if (_yIndex > _chunkRT.Y)
{
_yIndex = _chunkLB.Y;
_xIndex += 1;
}
indices = new Vector2i(_xIndex, _yIndex);
_yIndex += 1;
return _xIndex <= _chunkRT.X;
}
}
}

View File

@@ -53,12 +53,13 @@ namespace Content.Shared.Jittering
/// </remarks>
/// <param name="uid">Entity in question.</param>
/// <param name="time">For how much time to apply the effect.</param>
/// <param name="refresh">The status effect cooldown should be refreshed (true) or accumulated (false).</param>
/// <param name="amplitude">Jitteriness of the animation. See <see cref="MaxAmplitude"/> and <see cref="MinAmplitude"/>.</param>
/// <param name="frequency">Frequency for jittering. See <see cref="MaxFrequency"/> and <see cref="MinFrequency"/>.</param>
/// <param name="forceValueChange">Whether to change any existing jitter value even if they're greater than the ones we're setting.</param>
/// <param name="status">The status effects component to modify.</param>
/// <param name="alerts">The alerts component.</param>
public void DoJitter(EntityUid uid, TimeSpan time, float amplitude = 10f, float frequency = 4f, bool forceValueChange = false,
public void DoJitter(EntityUid uid, TimeSpan time, bool refresh, float amplitude = 10f, float frequency = 4f, bool forceValueChange = false,
StatusEffectsComponent? status = null,
SharedAlertsComponent? alerts = null)
{
@@ -68,7 +69,7 @@ namespace Content.Shared.Jittering
amplitude = Math.Clamp(amplitude, MinAmplitude, MaxAmplitude);
frequency = Math.Clamp(frequency, MinFrequency, MaxFrequency);
if (StatusEffects.TryAddStatusEffect<JitteringComponent>(uid, "Jitter", time, status, alerts))
if (StatusEffects.TryAddStatusEffect<JitteringComponent>(uid, "Jitter", time, refresh, status, alerts))
{
var jittering = EntityManager.GetComponent<JitteringComponent>(uid);

View File

@@ -289,9 +289,11 @@ namespace Content.Shared.MobState.Components
/// </summary>
private void SetMobState(IMobState? old, (IMobState state, FixedPoint2 threshold)? current)
{
var entMan = IoCManager.Resolve<IEntityManager>();
if (!current.HasValue)
{
old?.ExitState(Owner, IoCManager.Resolve<IEntityManager>());
old?.ExitState(Owner, entMan);
return;
}
@@ -301,22 +303,19 @@ namespace Content.Shared.MobState.Components
if (state == old)
{
state.UpdateState(Owner, threshold, IoCManager.Resolve<IEntityManager>());
state.UpdateState(Owner, threshold, entMan);
return;
}
old?.ExitState(Owner, IoCManager.Resolve<IEntityManager>());
old?.ExitState(Owner, entMan);
CurrentState = state;
state.EnterState(Owner, IoCManager.Resolve<IEntityManager>());
state.UpdateState(Owner, threshold, IoCManager.Resolve<IEntityManager>());
state.EnterState(Owner, entMan);
state.UpdateState(Owner, threshold, entMan);
var message = new MobStateChangedMessage(this, old, state);
#pragma warning disable 618
SendMessage(message);
#pragma warning restore 618
IoCManager.Resolve<IEntityManager>().EventBus.RaiseEvent(EventSource.Local, message);
var message = new MobStateChangedEvent(this, old, state);
entMan.EventBus.RaiseLocalEvent(Owner, message);
Dirty();
}

View File

@@ -4,11 +4,9 @@ using Robust.Shared.GameObjects;
namespace Content.Shared.MobState
{
#pragma warning disable 618
public class MobStateChangedMessage : ComponentMessage
#pragma warning restore 618
public class MobStateChangedEvent : EntityEventArgs
{
public MobStateChangedMessage(
public MobStateChangedEvent(
MobStateComponent component,
IMobState? oldMobState,
IMobState currentMobState)

View File

@@ -68,7 +68,7 @@ namespace Content.Shared.Nutrition.EntitySystems
CreamedEntity(uid, creamPied, args);
_stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(creamPie.ParalyzeTime));
_stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(creamPie.ParalyzeTime), true);
}
protected virtual void CreamedEntity(EntityUid uid, CreamPiedComponent creamPied, ThrowHitByEvent args) {}

View File

@@ -63,7 +63,7 @@ namespace Content.Shared.Slippery
if (!component.Slippery
|| component.Owner.IsInContainer()
|| component.Slipped.Contains(uid)
|| !_statusEffectsSystem.CanApplyEffect(uid, "Stun"))
|| !_statusEffectsSystem.CanApplyEffect(uid, "Stun")) //Should be KnockedDown instead?
{
return false;
}
@@ -94,11 +94,17 @@ namespace Content.Shared.Slippery
otherBody.LinearVelocity *= component.LaunchForwardsMultiplier;
_stunSystem.TryParalyze(otherBody.Owner, TimeSpan.FromSeconds(5));
bool playSound = !_statusEffectsSystem.HasStatusEffect(otherBody.Owner, "KnockedDown");
_stunSystem.TryParalyze(otherBody.Owner, TimeSpan.FromSeconds(component.ParalyzeTime), true);
component.Slipped.Add(otherBody.Owner);
component.Dirty();
PlaySound(component);
//Preventing from playing the slip sound when you are already knocked down.
if(playSound)
{
PlaySound(component);
}
_adminLog.Add(LogType.Slip, LogImpact.Low, $"{component.Owner} slipped on collision with {otherBody.Owner}");

View File

@@ -18,7 +18,7 @@ namespace Content.Shared.Slippery
{
public override string Name => "Slippery";
private float _paralyzeTime = 3f;
private float _paralyzeTime = 5f;
private float _intersectPercentage = 0.3f;
private float _requiredSlipSpeed = 5f;
private float _launchForwardsMultiplier = 1f;

View File

@@ -8,7 +8,7 @@ namespace Content.Shared.Speech.EntitySystems
public abstract class SharedStutteringSystem : EntitySystem
{
// For code in shared... I imagine we ain't getting accent prediction anytime soon so let's not bother.
public virtual void DoStutter(EntityUid uid, TimeSpan time, StatusEffectsComponent? status = null, SharedAlertsComponent? alerts = null)
public virtual void DoStutter(EntityUid uid, TimeSpan time, bool refresh, StatusEffectsComponent? status = null, SharedAlertsComponent? alerts = null)
{
}
}

View File

@@ -38,6 +38,13 @@ namespace Content.Shared.StatusEffect
[ViewVariables]
public (TimeSpan, TimeSpan) Cooldown;
/// <summary>
/// Specifies whether to refresh or accumulate the cooldown of the status effect.
/// true - refresh time, false - accumulate time.
/// </summary>
[ViewVariables]
public bool CooldownRefresh = true;
/// <summary>
/// The name of the relevant component that
/// was added alongside the effect, if any.
@@ -45,9 +52,10 @@ namespace Content.Shared.StatusEffect
[ViewVariables]
public string? RelevantComponent;
public StatusEffectState((TimeSpan, TimeSpan) cooldown, string? relevantComponent=null)
public StatusEffectState((TimeSpan, TimeSpan) cooldown, bool refresh, string? relevantComponent=null)
{
Cooldown = cooldown;
CooldownRefresh = refresh;
RelevantComponent = relevantComponent;
}
}

View File

@@ -64,7 +64,8 @@ namespace Content.Shared.StatusEffect
}
var time = effect.Value.Cooldown.Item2 - effect.Value.Cooldown.Item1;
TryAddStatusEffect(uid, effect.Key, time);
//TODO: Not sure how to handle refresh here.
TryAddStatusEffect(uid, effect.Key, time, true);
}
}
}
@@ -75,11 +76,12 @@ namespace Content.Shared.StatusEffect
/// <param name="uid">The entity to add the effect to.</param>
/// <param name="key">The status effect ID to add.</param>
/// <param name="time">How long the effect should last for.</param>
/// <param name="refresh">The status effect cooldown should be refreshed (true) or accumulated (false).</param>
/// <param name="status">The status effects component to change, if you already have it.</param>
/// <param name="alerts">The alerts component to modify, if the status effect has an alert.</param>
/// <returns>False if the effect could not be added or the component already exists, true otherwise.</returns>
/// <typeparam name="T">The component type to add and remove from the entity.</typeparam>
public bool TryAddStatusEffect<T>(EntityUid uid, string key, TimeSpan time,
public bool TryAddStatusEffect<T>(EntityUid uid, string key, TimeSpan time, bool refresh,
StatusEffectsComponent? status=null,
SharedAlertsComponent? alerts=null)
where T: Component, new()
@@ -89,7 +91,7 @@ namespace Content.Shared.StatusEffect
Resolve(uid, ref alerts, false);
if (TryAddStatusEffect(uid, key, time, status, alerts))
if (TryAddStatusEffect(uid, key, time, refresh, status, alerts))
{
// If they already have the comp, we just won't bother updating anything.
if (!EntityManager.HasComponent<T>(uid))
@@ -103,7 +105,7 @@ namespace Content.Shared.StatusEffect
return false;
}
public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time, string component,
public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time, bool refresh, string component,
StatusEffectsComponent? status = null,
SharedAlertsComponent? alerts = null)
{
@@ -112,7 +114,7 @@ namespace Content.Shared.StatusEffect
Resolve(uid, ref alerts, false);
if (TryAddStatusEffect(uid, key, time, status, alerts))
if (TryAddStatusEffect(uid, key, time, refresh, status, alerts))
{
// If they already have the comp, we just won't bother updating anything.
if (!EntityManager.HasComponent(uid, _componentFactory.GetRegistration(component).Type))
@@ -136,6 +138,7 @@ namespace Content.Shared.StatusEffect
/// <param name="uid">The entity to add the effect to.</param>
/// <param name="key">The status effect ID to add.</param>
/// <param name="time">How long the effect should last for.</param>
/// <param name="refresh">The status effect cooldown should be refreshed (true) or accumulated (false).</param>
/// <param name="status">The status effects component to change, if you already have it.</param>
/// <param name="alerts">The alerts component to modify, if the status effect has an alert.</param>
/// <returns>False if the effect could not be added, or if the effect already existed.</returns>
@@ -146,7 +149,7 @@ namespace Content.Shared.StatusEffect
/// If the effect already exists, it will simply replace the cooldown with the new one given.
/// If you want special 'effect merging' behavior, do it your own damn self!
/// </remarks>
public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time,
public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time, bool refresh,
StatusEffectsComponent? status=null,
SharedAlertsComponent? alerts=null)
{
@@ -163,14 +166,27 @@ namespace Content.Shared.StatusEffect
(TimeSpan, TimeSpan) cooldown = (_gameTiming.CurTime, _gameTiming.CurTime + time);
// If they already have this status effect, add the time onto it's cooldown rather than anything else.
if (HasStatusEffect(uid, key, status))
{
status.ActiveEffects[key].Cooldown.Item2 += time;
status.ActiveEffects[key].CooldownRefresh = refresh;
if(refresh)
{
//Making sure we don't reset a longer cooldown by applying a shorter one.
if((status.ActiveEffects[key].Cooldown.Item2 - _gameTiming.CurTime) < time)
{
//Refresh cooldown time.
status.ActiveEffects[key].Cooldown = cooldown;
}
}
else
{
//Accumulate cooldown time.
status.ActiveEffects[key].Cooldown.Item2 += time;
}
}
else
{
status.ActiveEffects.Add(key, new StatusEffectState(cooldown, null));
status.ActiveEffects.Add(key, new StatusEffectState(cooldown, refresh, null));
}
if (proto.Alert != null && alerts != null)

View File

@@ -118,7 +118,7 @@ namespace Content.Shared.Stunnable
/// <summary>
/// Stuns the entity, disallowing it from doing many interactions temporarily.
/// </summary>
public bool TryStun(EntityUid uid, TimeSpan time,
public bool TryStun(EntityUid uid, TimeSpan time, bool refresh,
StatusEffectsComponent? status = null,
SharedAlertsComponent? alerts = null)
{
@@ -130,13 +130,13 @@ namespace Content.Shared.Stunnable
Resolve(uid, ref alerts, false);
return _statusEffectSystem.TryAddStatusEffect<StunnedComponent>(uid, "Stun", time, alerts: alerts);
return _statusEffectSystem.TryAddStatusEffect<StunnedComponent>(uid, "Stun", time, refresh, alerts: alerts);
}
/// <summary>
/// Knocks down the entity, making it fall to the ground.
/// </summary>
public bool TryKnockdown(EntityUid uid, TimeSpan time,
public bool TryKnockdown(EntityUid uid, TimeSpan time, bool refresh,
StatusEffectsComponent? status = null,
SharedAlertsComponent? alerts = null)
{
@@ -148,13 +148,13 @@ namespace Content.Shared.Stunnable
Resolve(uid, ref alerts, false);
return _statusEffectSystem.TryAddStatusEffect<KnockedDownComponent>(uid, "KnockedDown", time, alerts: alerts);
return _statusEffectSystem.TryAddStatusEffect<KnockedDownComponent>(uid, "KnockedDown", time, refresh, alerts: alerts);
}
/// <summary>
/// Applies knockdown and stun to the entity temporarily.
/// </summary>
public bool TryParalyze(EntityUid uid, TimeSpan time,
public bool TryParalyze(EntityUid uid, TimeSpan time, bool refresh,
StatusEffectsComponent? status = null,
SharedAlertsComponent? alerts = null)
{
@@ -164,13 +164,13 @@ namespace Content.Shared.Stunnable
// Optional component.
Resolve(uid, ref alerts, false);
return TryKnockdown(uid, time, status, alerts) && TryStun(uid, time, status, alerts);
return TryKnockdown(uid, time, refresh, status, alerts) && TryStun(uid, time, refresh, status, alerts);
}
/// <summary>
/// Slows down the mob's walking/running speed temporarily
/// </summary>
public bool TrySlowdown(EntityUid uid, TimeSpan time,
public bool TrySlowdown(EntityUid uid, TimeSpan time, bool refresh,
float walkSpeedMultiplier = 1f, float runSpeedMultiplier = 1f,
StatusEffectsComponent? status = null,
SharedAlertsComponent? alerts = null)
@@ -184,7 +184,7 @@ namespace Content.Shared.Stunnable
if (time <= TimeSpan.Zero)
return false;
if (_statusEffectSystem.TryAddStatusEffect<SlowedDownComponent>(uid, "SlowedDown", time, status, alerts))
if (_statusEffectSystem.TryAddStatusEffect<SlowedDownComponent>(uid, "SlowedDown", time, refresh, status, alerts))
{
var slowed = EntityManager.GetComponent<SlowedDownComponent>(uid);
// Doesn't make much sense to have the "TrySlowdown" method speed up entities now does it?