Implement some field-level deltas (#28242)

* Update GasTileOverlayState

* Update DecalGridState

* Update NavMapState

* poke

* poke2

* poke3

* Implement field deltas for guns

* Content done

* Update

---------

Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
This commit is contained in:
metalgearsloth
2024-12-21 15:54:11 +11:00
committed by GitHub
parent 1f859167fd
commit 9f4aa1ebe0
12 changed files with 97 additions and 46 deletions

View File

@@ -15,6 +15,7 @@ public sealed partial class GunSystem
{ {
var existing = component.Entities[^1]; var existing = component.Entities[^1];
component.Entities.RemoveAt(component.Entities.Count - 1); component.Entities.RemoveAt(component.Entities.Count - 1);
DirtyField(uid, component, nameof(BallisticAmmoProviderComponent.Entities));
Containers.Remove(existing, component.Container); Containers.Remove(existing, component.Container);
EnsureShootable(existing); EnsureShootable(existing);
@@ -22,6 +23,7 @@ public sealed partial class GunSystem
else if (component.UnspawnedCount > 0) else if (component.UnspawnedCount > 0)
{ {
component.UnspawnedCount--; component.UnspawnedCount--;
DirtyField(uid, component, nameof(BallisticAmmoProviderComponent.UnspawnedCount));
ent = Spawn(component.Proto, coordinates); ent = Spawn(component.Proto, coordinates);
EnsureShootable(ent.Value); EnsureShootable(ent.Value);
} }

View File

@@ -39,8 +39,9 @@ public sealed partial class GunSystem
if (solution == null && !_solutionContainer.TryGetSolution(uid, component.SolutionId, out _, out solution)) if (solution == null && !_solutionContainer.TryGetSolution(uid, component.SolutionId, out _, out solution))
{ {
component.Shots = shots; component.Shots = shots;
DirtyField(uid, component, nameof(SolutionAmmoProviderComponent.Shots));
component.MaxShots = maxShots; component.MaxShots = maxShots;
Dirty(uid, component); DirtyField(uid, component, nameof(SolutionAmmoProviderComponent.MaxShots));
return; return;
} }
@@ -48,8 +49,10 @@ public sealed partial class GunSystem
maxShots = (int) (solution.MaxVolume / component.FireCost); maxShots = (int) (solution.MaxVolume / component.FireCost);
component.Shots = shots; component.Shots = shots;
DirtyField(uid, component, nameof(SolutionAmmoProviderComponent.Shots));
component.MaxShots = maxShots; component.MaxShots = maxShots;
Dirty(uid, component); DirtyField(uid, component, nameof(SolutionAmmoProviderComponent.MaxShots));
UpdateSolutionAppearance(uid, component); UpdateSolutionAppearance(uid, component);
} }

View File

@@ -10,7 +10,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic;
namespace Content.Shared.Nutrition.Components; namespace Content.Shared.Nutrition.Components;
[RegisterComponent, NetworkedComponent, Access(typeof(HungerSystem))] [RegisterComponent, NetworkedComponent, Access(typeof(HungerSystem))]
[AutoGenerateComponentState, AutoGenerateComponentPause] [AutoGenerateComponentState(fieldDeltas: true), AutoGenerateComponentPause]
public sealed partial class HungerComponent : Component public sealed partial class HungerComponent : Component
{ {
/// <summary> /// <summary>

View File

@@ -7,7 +7,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Nutrition.Components; namespace Content.Shared.Nutrition.Components;
[RegisterComponent, NetworkedComponent, Access(typeof(ThirstSystem))] [RegisterComponent, NetworkedComponent, Access(typeof(ThirstSystem))]
[AutoGenerateComponentState, AutoGenerateComponentPause] [AutoGenerateComponentState(fieldDeltas: true), AutoGenerateComponentPause]
public sealed partial class ThirstComponent : Component public sealed partial class ThirstComponent : Component
{ {
// Base stuff // Base stuff

View File

@@ -122,7 +122,7 @@ public sealed class HungerSystem : EntitySystem
{ {
entity.Comp.LastAuthoritativeHungerChangeTime = _timing.CurTime; entity.Comp.LastAuthoritativeHungerChangeTime = _timing.CurTime;
entity.Comp.LastAuthoritativeHungerValue = ClampHungerWithinThresholds(entity.Comp, value); entity.Comp.LastAuthoritativeHungerValue = ClampHungerWithinThresholds(entity.Comp, value);
Dirty(entity); DirtyField(entity.Owner, entity.Comp, nameof(HungerComponent.LastAuthoritativeHungerChangeTime));
} }
private void UpdateCurrentThreshold(EntityUid uid, HungerComponent? component = null) private void UpdateCurrentThreshold(EntityUid uid, HungerComponent? component = null)
@@ -133,6 +133,7 @@ public sealed class HungerSystem : EntitySystem
var calculatedHungerThreshold = GetHungerThreshold(component); var calculatedHungerThreshold = GetHungerThreshold(component);
if (calculatedHungerThreshold == component.CurrentThreshold) if (calculatedHungerThreshold == component.CurrentThreshold)
return; return;
component.CurrentThreshold = calculatedHungerThreshold; component.CurrentThreshold = calculatedHungerThreshold;
DoHungerThresholdEffects(uid, component); DoHungerThresholdEffects(uid, component);
} }

View File

@@ -102,7 +102,8 @@ public sealed class ThirstSystem : EntitySystem
component.ThirstThresholds[ThirstThreshold.Dead], component.ThirstThresholds[ThirstThreshold.Dead],
component.ThirstThresholds[ThirstThreshold.OverHydrated] component.ThirstThresholds[ThirstThreshold.OverHydrated]
); );
Dirty(uid, component);
EntityManager.DirtyField(uid, component, nameof(ThirstComponent.CurrentThirst));
} }
private bool IsMovementThreshold(ThirstThreshold threshold) private bool IsMovementThreshold(ThirstThreshold threshold)

View File

@@ -13,27 +13,27 @@ public partial class AmmoComponent : Component, IShootable
{ {
// Muzzle flash stored on ammo because if we swap a gun to whatever we may want to override it. // Muzzle flash stored on ammo because if we swap a gun to whatever we may want to override it.
[ViewVariables(VVAccess.ReadWrite), DataField("muzzleFlash", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))] [DataField]
public string? MuzzleFlash = "MuzzleFlashEffect"; public EntProtoId? MuzzleFlash = "MuzzleFlashEffect";
} }
/// <summary> /// <summary>
/// Spawns another prototype to be shot instead of itself. /// Spawns another prototype to be shot instead of itself.
/// </summary> /// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] [RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true)]
public sealed partial class CartridgeAmmoComponent : AmmoComponent public sealed partial class CartridgeAmmoComponent : AmmoComponent
{ {
[ViewVariables(VVAccess.ReadWrite), DataField("proto", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))] [ViewVariables(VVAccess.ReadWrite), DataField("proto", required: true)]
public string Prototype = default!; public EntProtoId Prototype;
[ViewVariables(VVAccess.ReadWrite), DataField("spent")] [ViewVariables(VVAccess.ReadWrite), DataField]
[AutoNetworkedField] [AutoNetworkedField]
public bool Spent = false; public bool Spent;
/// <summary> /// <summary>
/// Caseless ammunition. /// Caseless ammunition.
/// </summary> /// </summary>
[DataField("deleteOnSpawn")] [DataField]
public bool DeleteOnSpawn; public bool DeleteOnSpawn;
[DataField("soundEject")] [DataField("soundEject")]

View File

@@ -7,13 +7,13 @@ using Robust.Shared.Prototypes;
namespace Content.Shared.Weapons.Ranged.Components; namespace Content.Shared.Weapons.Ranged.Components;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedGunSystem))] [RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true), Access(typeof(SharedGunSystem))]
public sealed partial class BallisticAmmoProviderComponent : Component public sealed partial class BallisticAmmoProviderComponent : Component
{ {
[ViewVariables(VVAccess.ReadWrite), DataField] [DataField]
public SoundSpecifier? SoundRack = new SoundPathSpecifier("/Audio/Weapons/Guns/Cock/smg_cock.ogg"); public SoundSpecifier? SoundRack = new SoundPathSpecifier("/Audio/Weapons/Guns/Cock/smg_cock.ogg");
[ViewVariables(VVAccess.ReadWrite), DataField] [DataField]
public SoundSpecifier? SoundInsert = new SoundPathSpecifier("/Audio/Weapons/Guns/MagIn/bullet_insert.ogg"); public SoundSpecifier? SoundInsert = new SoundPathSpecifier("/Audio/Weapons/Guns/MagIn/bullet_insert.ogg");
[ViewVariables(VVAccess.ReadWrite), DataField] [ViewVariables(VVAccess.ReadWrite), DataField]

View File

@@ -8,7 +8,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Weapons.Ranged.Components; namespace Content.Shared.Weapons.Ranged.Components;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] [RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true), AutoGenerateComponentPause]
[Access(typeof(SharedGunSystem))] [Access(typeof(SharedGunSystem))]
public sealed partial class GunComponent : Component public sealed partial class GunComponent : Component
{ {

View File

@@ -5,38 +5,38 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
namespace Content.Shared.Weapons.Ranged.Components; namespace Content.Shared.Weapons.Ranged.Components;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedGunSystem))] [RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true), Access(typeof(SharedGunSystem))]
public sealed partial class SolutionAmmoProviderComponent : Component public sealed partial class SolutionAmmoProviderComponent : Component
{ {
/// <summary> /// <summary>
/// The solution where reagents are extracted from for the projectile. /// The solution where reagents are extracted from for the projectile.
/// </summary> /// </summary>
[DataField("solutionId", required: true), AutoNetworkedField] [DataField(required: true), AutoNetworkedField]
public string SolutionId = default!; public string SolutionId = default!;
/// <summary> /// <summary>
/// How much reagent it costs to fire once. /// How much reagent it costs to fire once.
/// </summary> /// </summary>
[DataField("fireCost"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] [DataField, AutoNetworkedField]
public float FireCost = 5; public float FireCost = 5;
/// <summary> /// <summary>
/// The amount of shots currently available. /// The amount of shots currently available.
/// used for network predictions. /// used for network predictions.
/// </summary> /// </summary>
[DataField("shots"), ViewVariables, AutoNetworkedField] [DataField, AutoNetworkedField]
public int Shots; public int Shots;
/// <summary> /// <summary>
/// The max amount of shots the gun can fire. /// The max amount of shots the gun can fire.
/// used for network prediction /// used for network prediction
/// </summary> /// </summary>
[DataField("maxShots"), ViewVariables, AutoNetworkedField] [DataField, AutoNetworkedField]
public int MaxShots; public int MaxShots;
/// <summary> /// <summary>
/// The prototype that's fired by the gun. /// The prototype that's fired by the gun.
/// </summary> /// </summary>
[DataField("proto", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>)), ViewVariables(VVAccess.ReadWrite)] [DataField("proto")]
public string Prototype = default!; public EntProtoId Prototype;
} }

View File

@@ -57,7 +57,7 @@ public abstract partial class SharedGunSystem
Audio.PlayPredicted(component.SoundInsert, uid, args.User); Audio.PlayPredicted(component.SoundInsert, uid, args.User);
args.Handled = true; args.Handled = true;
UpdateBallisticAppearance(uid, component); UpdateBallisticAppearance(uid, component);
Dirty(uid, component); DirtyField(uid, component, nameof(BallisticAmmoProviderComponent.Entities));
} }
private void OnBallisticAfterInteract(EntityUid uid, BallisticAmmoProviderComponent component, AfterInteractEvent args) private void OnBallisticAfterInteract(EntityUid uid, BallisticAmmoProviderComponent component, AfterInteractEvent args)
@@ -194,10 +194,9 @@ public abstract partial class SharedGunSystem
!Paused(uid)) !Paused(uid))
{ {
gunComp.NextFire = Timing.CurTime + TimeSpan.FromSeconds(1 / gunComp.FireRateModified); gunComp.NextFire = Timing.CurTime + TimeSpan.FromSeconds(1 / gunComp.FireRateModified);
Dirty(uid, gunComp); DirtyField(uid, gunComp, nameof(GunComponent.NextFire));
} }
Dirty(uid, component);
Audio.PlayPredicted(component.SoundRack, uid, user); Audio.PlayPredicted(component.SoundRack, uid, user);
var shots = GetBallisticShots(component); var shots = GetBallisticShots(component);
@@ -228,7 +227,7 @@ public abstract partial class SharedGunSystem
{ {
component.UnspawnedCount = Math.Max(0, component.Capacity - component.Container.ContainedEntities.Count); component.UnspawnedCount = Math.Max(0, component.Capacity - component.Container.ContainedEntities.Count);
UpdateBallisticAppearance(uid, component); UpdateBallisticAppearance(uid, component);
Dirty(uid, component); DirtyField(uid, component, nameof(BallisticAmmoProviderComponent.UnspawnedCount));
} }
} }
@@ -249,18 +248,19 @@ public abstract partial class SharedGunSystem
args.Ammo.Add((entity, EnsureShootable(entity))); args.Ammo.Add((entity, EnsureShootable(entity)));
component.Entities.RemoveAt(component.Entities.Count - 1); component.Entities.RemoveAt(component.Entities.Count - 1);
DirtyField(uid, component, nameof(BallisticAmmoProviderComponent.Entities));
Containers.Remove(entity, component.Container); Containers.Remove(entity, component.Container);
} }
else if (component.UnspawnedCount > 0) else if (component.UnspawnedCount > 0)
{ {
component.UnspawnedCount--; component.UnspawnedCount--;
DirtyField(uid, component, nameof(BallisticAmmoProviderComponent.UnspawnedCount));
entity = Spawn(component.Proto, args.Coordinates); entity = Spawn(component.Proto, args.Coordinates);
args.Ammo.Add((entity, EnsureShootable(entity))); args.Ammo.Add((entity, EnsureShootable(entity)));
} }
} }
UpdateBallisticAppearance(uid, component); UpdateBallisticAppearance(uid, component);
Dirty(uid, component);
} }
private void OnBallisticAmmoCount(EntityUid uid, BallisticAmmoProviderComponent component, ref GetAmmoCountEvent args) private void OnBallisticAmmoCount(EntityUid uid, BallisticAmmoProviderComponent component, ref GetAmmoCountEvent args)

View File

@@ -119,7 +119,7 @@ public abstract partial class SharedGunSystem : EntitySystem
if (melee.NextAttack > component.NextFire) if (melee.NextAttack > component.NextFire)
{ {
component.NextFire = melee.NextAttack; component.NextFire = melee.NextAttack;
Dirty(uid, component); EntityManager.DirtyField(uid, component, nameof(MeleeWeaponComponent.NextAttack));
} }
} }
@@ -200,7 +200,7 @@ public abstract partial class SharedGunSystem : EntitySystem
gun.ShotCounter = 0; gun.ShotCounter = 0;
gun.ShootCoordinates = null; gun.ShootCoordinates = null;
gun.Target = null; gun.Target = null;
Dirty(uid, gun); EntityManager.DirtyField(uid, gun, nameof(GunComponent.ShotCounter));
} }
/// <summary> /// <summary>
@@ -211,6 +211,7 @@ public abstract partial class SharedGunSystem : EntitySystem
gun.ShootCoordinates = toCoordinates; gun.ShootCoordinates = toCoordinates;
AttemptShoot(user, gunUid, gun); AttemptShoot(user, gunUid, gun);
gun.ShotCounter = 0; gun.ShotCounter = 0;
EntityManager.DirtyField(gunUid, gun, nameof(GunComponent.ShotCounter));
} }
/// <summary> /// <summary>
@@ -228,7 +229,9 @@ public abstract partial class SharedGunSystem : EntitySystem
{ {
if (gun.FireRateModified <= 0f || if (gun.FireRateModified <= 0f ||
!_actionBlockerSystem.CanAttack(user)) !_actionBlockerSystem.CanAttack(user))
{
return; return;
}
var toCoordinates = gun.ShootCoordinates; var toCoordinates = gun.ShootCoordinates;
@@ -277,7 +280,7 @@ public abstract partial class SharedGunSystem : EntitySystem
} }
// NextFire has been touched regardless so need to dirty the gun. // NextFire has been touched regardless so need to dirty the gun.
Dirty(gunUid, gun); EntityManager.DirtyField(gunUid, gun, nameof(GunComponent.NextFire));
// Get how many shots we're actually allowed to make, due to clip size or otherwise. // Get how many shots we're actually allowed to make, due to clip size or otherwise.
// Don't do this in the loop so we still reset NextFire. // Don't do this in the loop so we still reset NextFire.
@@ -331,6 +334,7 @@ public abstract partial class SharedGunSystem : EntitySystem
// Even if we don't actually shoot update the ShotCounter. This is to avoid spamming empty sounds // Even if we don't actually shoot update the ShotCounter. This is to avoid spamming empty sounds
// where the gun may be SemiAuto or Burst. // where the gun may be SemiAuto or Burst.
gun.ShotCounter += shots; gun.ShotCounter += shots;
EntityManager.DirtyField(gunUid, gun, nameof(GunComponent.ShotCounter));
if (ev.Ammo.Count <= 0) if (ev.Ammo.Count <= 0)
{ {
@@ -387,8 +391,6 @@ public abstract partial class SharedGunSystem : EntitySystem
if (_gravity.IsWeightless(user, userPhysics)) if (_gravity.IsWeightless(user, userPhysics))
CauseImpulse(fromCoordinates, toCoordinates.Value, user, userPhysics); CauseImpulse(fromCoordinates, toCoordinates.Value, user, userPhysics);
} }
Dirty(gunUid, gun);
} }
public void Shoot( public void Shoot(
@@ -442,7 +444,7 @@ public abstract partial class SharedGunSystem : EntitySystem
protected void SetCartridgeSpent(EntityUid uid, CartridgeAmmoComponent cartridge, bool spent) protected void SetCartridgeSpent(EntityUid uid, CartridgeAmmoComponent cartridge, bool spent)
{ {
if (cartridge.Spent != spent) if (cartridge.Spent != spent)
Dirty(uid, cartridge); DirtyField(uid, cartridge, nameof(CartridgeAmmoComponent.Spent));
cartridge.Spent = spent; cartridge.Spent = spent;
Appearance.SetData(uid, AmmoVisuals.Spent, spent); Appearance.SetData(uid, AmmoVisuals.Spent, spent);
@@ -541,17 +543,59 @@ public abstract partial class SharedGunSystem : EntitySystem
RaiseLocalEvent(gun, ref ev); RaiseLocalEvent(gun, ref ev);
if (comp.SoundGunshotModified != ev.SoundGunshot)
{
comp.SoundGunshotModified = ev.SoundGunshot; comp.SoundGunshotModified = ev.SoundGunshot;
comp.CameraRecoilScalarModified = ev.CameraRecoilScalar; DirtyField(gun, nameof(GunComponent.SoundGunshotModified));
comp.AngleIncreaseModified = ev.AngleIncrease; }
comp.AngleDecayModified = ev.AngleDecay;
comp.MaxAngleModified = ev.MaxAngle;
comp.MinAngleModified = ev.MinAngle;
comp.ShotsPerBurstModified = ev.ShotsPerBurst;
comp.FireRateModified = ev.FireRate;
comp.ProjectileSpeedModified = ev.ProjectileSpeed;
Dirty(gun); if (!MathHelper.CloseTo(comp.CameraRecoilScalarModified, ev.CameraRecoilScalar))
{
comp.CameraRecoilScalarModified = ev.CameraRecoilScalar;
DirtyField(gun, nameof(GunComponent.CameraRecoilScalarModified));
}
if (!comp.AngleIncreaseModified.EqualsApprox(ev.AngleIncrease))
{
comp.AngleIncreaseModified = ev.AngleIncrease;
DirtyField(gun, nameof(GunComponent.AngleIncreaseModified));
}
if (!comp.AngleDecayModified.EqualsApprox(ev.AngleDecay))
{
comp.AngleDecayModified = ev.AngleDecay;
DirtyField(gun, nameof(GunComponent.AngleDecayModified));
}
if (!comp.MaxAngleModified.EqualsApprox(ev.MinAngle))
{
comp.MaxAngleModified = ev.MaxAngle;
DirtyField(gun, nameof(GunComponent.MaxAngleModified));
}
if (!comp.MinAngleModified.EqualsApprox(ev.MinAngle))
{
comp.MinAngleModified = ev.MinAngle;
DirtyField(gun, nameof(GunComponent.MinAngleModified));
}
if (comp.ShotsPerBurstModified != ev.ShotsPerBurst)
{
comp.ShotsPerBurstModified = ev.ShotsPerBurst;
DirtyField(gun, nameof(GunComponent.ShotsPerBurstModified));
}
if (!MathHelper.CloseTo(comp.FireRateModified, ev.FireRate))
{
comp.FireRateModified = ev.FireRate;
DirtyField(gun, nameof(GunComponent.FireRateModified));
}
if (!MathHelper.CloseTo(comp.ProjectileSpeedModified, ev.ProjectileSpeed))
{
comp.ProjectileSpeedModified = ev.ProjectileSpeed;
DirtyField(gun, nameof(GunComponent.ProjectileSpeedModified));
}
} }
protected abstract void CreateEffect(EntityUid gunUid, MuzzleFlashEvent message, EntityUid? user = null); protected abstract void CreateEffect(EntityUid gunUid, MuzzleFlashEvent message, EntityUid? user = null);