mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 10:21:18 +03:00
Prediction + small cleanup: GatherableSystem (#44840)
* Predict * Reuse pos * Update Content.Shared/Gatherable/GatherableSystem.cs Co-authored-by: Pok <113675512+Pok27@users.noreply.github.com> * Update Content.Shared/Gatherable/GatherableSystem.cs Co-authored-by: Connor Huffine <chuffine@gmail.com> * Review * refactor: GatherableSystem xml-doc --------- Co-authored-by: Pok <113675512+Pok27@users.noreply.github.com> Co-authored-by: Connor Huffine <chuffine@gmail.com> Co-authored-by: Fildrance <fildrance@gmail.com>
This commit is contained in:
parent
b28ca9f741
commit
54986e0347
8 changed files with 186 additions and 179 deletions
|
|
@ -1,40 +0,0 @@
|
|||
using Content.Shared.EntityTable.EntitySelectors;
|
||||
using Content.Shared.Whitelist;
|
||||
|
||||
namespace Content.Server.Gatherable.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
[Access(typeof(GatherableSystem))]
|
||||
public sealed partial class GatherableComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Whitelist for specifying the kind of tools can be used on a resource
|
||||
/// Supports multiple tags.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public EntityWhitelist? ToolWhitelist;
|
||||
|
||||
/// <summary>
|
||||
/// YAML example below
|
||||
/// (Tag1, Tag2, LootTableID1, LootTableID2 are placeholders for example)
|
||||
/// --------------------
|
||||
/// useMappedLoot: true
|
||||
/// toolWhitelist:
|
||||
/// tags:
|
||||
/// - Tag1
|
||||
/// - Tag2
|
||||
/// loot:
|
||||
/// Tag1: !type:NestedSelector
|
||||
/// tableId: LootTableID1
|
||||
/// Tag2: !type:NestedSelector
|
||||
/// tableId: LootTableID2
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Dictionary<string, EntityTableSelector>? Loot = new();
|
||||
|
||||
/// <summary>
|
||||
/// Random shift of the appearing entity during gathering
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float GatherOffset = 0.3f;
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
using Content.Shared.Audio;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Gatherable.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Plays the specified sound when this entity is gathered.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(GatherableSystem))]
|
||||
public sealed partial class SoundOnGatherComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("sound")]
|
||||
public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Effects/break_stone.ogg")
|
||||
{
|
||||
Params = AudioParams.Default
|
||||
.WithVariation(SharedContentAudioSystem.DefaultVariation)
|
||||
.WithVolume(-3f),
|
||||
};
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
using Content.Server.Gatherable.Components;
|
||||
using Content.Shared.Projectiles;
|
||||
using Robust.Shared.Physics.Events;
|
||||
|
||||
namespace Content.Server.Gatherable;
|
||||
|
||||
public sealed partial class GatherableSystem
|
||||
{
|
||||
private void InitializeProjectile()
|
||||
{
|
||||
SubscribeLocalEvent<GatheringProjectileComponent, StartCollideEvent>(OnProjectileCollide);
|
||||
}
|
||||
|
||||
private void OnProjectileCollide(Entity<GatheringProjectileComponent> gathering, ref StartCollideEvent args)
|
||||
{
|
||||
if (!args.OtherFixture.Hard ||
|
||||
args.OurFixtureId != SharedProjectileSystem.ProjectileFixture ||
|
||||
gathering.Comp.Amount <= 0 ||
|
||||
!TryComp<GatherableComponent>(args.OtherEntity, out var gatherable))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Gather(args.OtherEntity, gathering, gatherable);
|
||||
gathering.Comp.Amount--;
|
||||
|
||||
if (gathering.Comp.Amount <= 0)
|
||||
QueueDel(gathering);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
using Content.Server.Destructible;
|
||||
using Content.Server.Gatherable.Components;
|
||||
using Content.Shared.EntityTable;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Gatherable;
|
||||
|
||||
public sealed partial class GatherableSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private IRobustRandom _random = default!;
|
||||
[Dependency] private DestructibleSystem _destructible = default!;
|
||||
[Dependency] private SharedAudioSystem _audio = default!;
|
||||
[Dependency] private TagSystem _tagSystem = default!;
|
||||
[Dependency] private TransformSystem _transform = default!;
|
||||
[Dependency] private EntityWhitelistSystem _whitelistSystem = default!;
|
||||
[Dependency] private EntityTableSystem _entityTable = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GatherableComponent, ActivateInWorldEvent>(OnActivate);
|
||||
SubscribeLocalEvent<GatherableComponent, AttackedEvent>(OnAttacked);
|
||||
InitializeProjectile();
|
||||
}
|
||||
|
||||
private void OnAttacked(Entity<GatherableComponent> gatherable, ref AttackedEvent args)
|
||||
{
|
||||
if (_whitelistSystem.IsWhitelistFailOrNull(gatherable.Comp.ToolWhitelist, args.Used))
|
||||
return;
|
||||
|
||||
Gather(gatherable, args.User);
|
||||
}
|
||||
|
||||
private void OnActivate(Entity<GatherableComponent> gatherable, ref ActivateInWorldEvent args)
|
||||
{
|
||||
if (args.Handled || !args.Complex)
|
||||
return;
|
||||
|
||||
if (_whitelistSystem.IsWhitelistFailOrNull(gatherable.Comp.ToolWhitelist, args.User))
|
||||
return;
|
||||
|
||||
Gather(gatherable, args.User);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public void Gather(EntityUid gatheredUid, EntityUid? gatherer = null, GatherableComponent? component = null)
|
||||
{
|
||||
if (!Resolve(gatheredUid, ref component))
|
||||
return;
|
||||
|
||||
if (TryComp<SoundOnGatherComponent>(gatheredUid, out var soundComp))
|
||||
{
|
||||
_audio.PlayPvs(soundComp.Sound, Transform(gatheredUid).Coordinates);
|
||||
}
|
||||
|
||||
// Complete the gathering process
|
||||
_destructible.DestroyEntity(gatheredUid);
|
||||
|
||||
// Spawn the loot!
|
||||
if (component.Loot == null)
|
||||
return;
|
||||
|
||||
var pos = _transform.GetMapCoordinates(gatheredUid);
|
||||
|
||||
foreach (var (tag, table) in component.Loot)
|
||||
{
|
||||
if (tag != "All")
|
||||
{
|
||||
if (gatherer != null && !_tagSystem.HasTag(gatherer.Value, tag))
|
||||
continue;
|
||||
}
|
||||
var spawnLoot = _entityTable.GetSpawns(table);
|
||||
foreach (var loot in spawnLoot)
|
||||
{
|
||||
var spawnPos = pos.Offset(_random.NextVector2(component.GatherOffset));
|
||||
Spawn(loot, spawnPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Content.Shared/Gatherable/Components/GatherableComponent.cs
Normal file
44
Content.Shared/Gatherable/Components/GatherableComponent.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using Content.Shared.EntityTable.EntitySelectors;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Gatherable.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Makes the entity possible to gather.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(GatherableSystem))]
|
||||
public sealed partial class GatherableComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Whitelist for specifying the kind of tools can be used on a resource.
|
||||
/// Supports multiple tags.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public EntityWhitelist? ToolWhitelist;
|
||||
|
||||
/// <summary>
|
||||
/// YAML example below
|
||||
/// (Tag1, Tag2, LootTableID1, LootTableID2 are placeholders for example)
|
||||
/// --------------------
|
||||
/// useMappedLoot: true
|
||||
/// toolWhitelist:
|
||||
/// tags:
|
||||
/// - Tag1
|
||||
/// - Tag2
|
||||
/// loot:
|
||||
/// Tag1: !type:NestedSelector
|
||||
/// tableId: LootTableID1
|
||||
/// Tag2: !type:NestedSelector
|
||||
/// tableId: LootTableID2
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Dictionary<string, EntityTableSelector>? Loot = [];
|
||||
|
||||
/// <summary>
|
||||
/// Random shift of the appearing entity during gathering.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float GatherOffset = 0.3f;
|
||||
}
|
||||
|
|
@ -1,14 +1,17 @@
|
|||
namespace Content.Server.Gatherable.Components;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Gatherable.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Destroys a gatherable entity when colliding with it.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(GatherableSystem))]
|
||||
public sealed partial class GatheringProjectileComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How many more times we can gather.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("amount")]
|
||||
[DataField, AutoNetworkedField]
|
||||
public int Amount = 1;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using Content.Shared.Audio;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Gatherable.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Plays the specified sound when this entity is gathered.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(GatherableSystem))]
|
||||
public sealed partial class SoundOnGatherComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Sound to play when this entity is gathered.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Effects/break_stone.ogg", AudioParams.Default
|
||||
.WithVariation(SharedContentAudioSystem.DefaultVariation)
|
||||
.WithVolume(-3f));
|
||||
}
|
||||
115
Content.Shared/Gatherable/GatherableSystem.cs
Normal file
115
Content.Shared/Gatherable/GatherableSystem.cs
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
using Content.Shared.Destructible;
|
||||
using Content.Shared.EntityTable;
|
||||
using Content.Shared.Gatherable.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Projectiles;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Gatherable;
|
||||
|
||||
/// <summary>
|
||||
/// System for handling collecting items from in-the-world structures,
|
||||
/// like gathering mushrooms or some rare herbs.
|
||||
/// </summary>
|
||||
public sealed partial class GatherableSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private SharedAudioSystem _audio = default!;
|
||||
[Dependency] private SharedDestructibleSystem _destructible = default!;
|
||||
[Dependency] private EntityTableSystem _entityTable = default!;
|
||||
[Dependency] private TagSystem _tagSystem = default!;
|
||||
[Dependency] private IGameTiming _timing = default!;
|
||||
[Dependency] private EntityWhitelistSystem _whitelistSystem = default!;
|
||||
|
||||
/// <summary>
|
||||
/// If a gatherable entity is attacked with a tool that passes the tool entity whitelist (or if that whitelist doesn't exist), then it will be gathered.
|
||||
/// </summary>
|
||||
[SubscribeLocalEvent]
|
||||
private void OnAttacked(Entity<GatherableComponent> gatherable, ref AttackedEvent args)
|
||||
{
|
||||
if (_whitelistSystem.IsWhitelistFailOrNull(gatherable.Comp.ToolWhitelist, args.Used))
|
||||
return;
|
||||
|
||||
Gather(gatherable.AsNullable(), args.User);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If a gatherable entity is activated by a user that passes the tool entity whitelist (or if that whitelist doesn't exist), then it will be gathered.
|
||||
/// </summary>
|
||||
[SubscribeLocalEvent]
|
||||
private void OnActivate(Entity<GatherableComponent> gatherable, ref ActivateInWorldEvent args)
|
||||
{
|
||||
if (args.Handled || !args.Complex)
|
||||
return;
|
||||
|
||||
if (_whitelistSystem.IsWhitelistFailOrNull(gatherable.Comp.ToolWhitelist, args.User))
|
||||
return;
|
||||
|
||||
Gather(gatherable.AsNullable(), args.User);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If a projectile hits a gatherable entity, it will gather that entity and decrease its own gathering amount.
|
||||
/// If the projectile’s gathering amount is zero or less, it will be deleted.
|
||||
/// </summary>
|
||||
[SubscribeLocalEvent]
|
||||
private void OnProjectileCollide(Entity<GatheringProjectileComponent> gathering, ref StartCollideEvent args)
|
||||
{
|
||||
if (!args.OtherFixture.Hard)
|
||||
return;
|
||||
|
||||
if (args.OurFixtureId != SharedProjectileSystem.ProjectileFixture)
|
||||
return;
|
||||
|
||||
if (gathering.Comp.Amount <= 0)
|
||||
return;
|
||||
|
||||
if (!TryComp<GatherableComponent>(args.OtherEntity, out var gatherable))
|
||||
return;
|
||||
|
||||
Gather((args.OtherEntity, gatherable), gathering);
|
||||
gathering.Comp.Amount--;
|
||||
Dirty(gathering);
|
||||
|
||||
if (gathering.Comp.Amount <= 0)
|
||||
PredictedQueueDel(gathering);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the gathered entity, plays a sound if it has <see cref="SoundOnGatherComponent"/>, and spawns loot if possible.
|
||||
/// </summary>
|
||||
/// <param name="gathered">The entity that was gathered.</param>
|
||||
/// <param name="gatherer">The entity that gathered it.</param>
|
||||
public void Gather(Entity<GatherableComponent?> gathered, EntityUid? gatherer = null)
|
||||
{
|
||||
if (!Resolve(gathered, ref gathered.Comp))
|
||||
return;
|
||||
|
||||
var pos = Transform(gathered).Coordinates;
|
||||
if (TryComp<SoundOnGatherComponent>(gathered, out var soundComp))
|
||||
_audio.PlayPredicted(soundComp.Sound, pos, gatherer);
|
||||
|
||||
_destructible.DestroyEntity(gathered);
|
||||
|
||||
if (gathered.Comp.Loot is null)
|
||||
return;
|
||||
|
||||
foreach (var (tag, table) in gathered.Comp.Loot)
|
||||
{
|
||||
if (tag != "All" && gatherer != null && !_tagSystem.HasTag(gatherer.Value, tag))
|
||||
continue;
|
||||
|
||||
var random = SharedRandomExtensions.PredictedRandom(_timing, GetNetEntity(gathered));
|
||||
foreach (var loot in _entityTable.GetSpawns(table, random))
|
||||
{
|
||||
PredictedSpawnAtPosition(loot, pos.Offset(random.NextVector2(gathered.Comp.GatherOffset)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue