Skip to content

Commit

Permalink
Merge pull request StarCoreSE#16 from Jnick-24/projectile-testing
Browse files Browse the repository at this point in the history
Smarter Projectile Syncing & Particles
  • Loading branch information
ari-steas authored Jan 6, 2024
2 parents 5b34577 + 4d51f84 commit c0b1714
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 39 deletions.
4 changes: 4 additions & 0 deletions Heart Module/Data/Scripts/HeartModule/HeartData.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using Heart_Module.Data.Scripts.HeartModule.ExceptionHandler;
using Heart_Module.Data.Scripts.HeartModule.Network;
using Sandbox.ModAPI;
using VRage.Game;

namespace Heart_Module.Data.Scripts.HeartModule
{
Expand All @@ -11,5 +13,7 @@ internal class HeartData
public bool IsSuspended = false;
public HeartNetwork Net = new HeartNetwork();
public HeartLog Log = new HeartLog();
public int SyncRange = MyAPIGateway.Session.SessionSettings.SyncDistance;
public int SyncRangeSq = MyAPIGateway.Session.SessionSettings.SyncDistance * MyAPIGateway.Session.SessionSettings.SyncDistance;
}
}
27 changes: 27 additions & 0 deletions Heart Module/Data/Scripts/HeartModule/Network/HeartNetwork.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ void ReceivedPacket(ushort channelId, byte[] serialized, ulong senderSteamId, bo
}
}

public void SendToPlayer(PacketBase packet, ulong playerSteamId, byte[] serialized = null)
{
RelayToClient(packet, playerSteamId, 0, serialized);
}

public void SendToEveryone(PacketBase packet, byte[] serialized = null)
{
RelayToClients(packet, 0, serialized);
Expand Down Expand Up @@ -60,6 +65,28 @@ void RelayToClients(PacketBase packet, ulong senderSteamId = 0, byte[] serialize
TempPlayers.Clear();
}

void RelayToClient(PacketBase packet, ulong playerSteamId, ulong senderSteamId, byte[] serialized = null)
{
if (playerSteamId == MyAPIGateway.Multiplayer.ServerId || playerSteamId == senderSteamId)
return;

if (serialized == null) // only serialize if necessary, and only once.
serialized = MyAPIGateway.Utilities.SerializeToBinary(packet);

MyAPIGateway.Multiplayer.SendMessageTo(HeartData.HeartNetworkId, serialized, playerSteamId);
}

void RelayToServer(PacketBase packet, ulong senderSteamId = 0, byte[] serialized = null)
{
if (senderSteamId == MyAPIGateway.Multiplayer.ServerId)
return;

if (serialized == null) // only serialize if necessary, and only once.
serialized = MyAPIGateway.Utilities.SerializeToBinary(packet);

MyAPIGateway.Multiplayer.SendMessageToServer(HeartData.HeartNetworkId, serialized);
}

void HandlePacket(PacketBase packet, ulong senderSteamId)
{
packet.Received(senderSteamId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@ private void m_Update()

private void m_QueueEvent(DamageEvent damageEvent)
{
if (MyAPIGateway.Session.IsServer)
CriticalHandle.ThrowCriticalException(new Exception("Testing"), typeof(DamageHandler));
DamageEvents.Add(damageEvent);
}

Expand Down
15 changes: 7 additions & 8 deletions Heart Module/Data/Scripts/HeartModule/Projectiles/Projectile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public partial class Projectile
public float Velocity = 0;
public int RemainingImpacts = 0;

public Action<Projectile> Close = (p) => { };
public Action<Projectile> OnClose = (p) => { };
public long LastUpdate { get; private set; }

public float DistanceTravelled { get; private set; } = 0;
Expand Down Expand Up @@ -97,11 +97,6 @@ public void TickUpdate(float delta)
NextMoveStep = Position + (InheritedVelocity + Direction * (Velocity + Definition.PhysicalProjectile.Acceleration * delta)) * delta;
}

public void DrawUpdate(float delta)
{
DebugDraw.AddPoint(Position + (InheritedVelocity + Direction * (Velocity + Definition.PhysicalProjectile.Acceleration * delta)) * delta, Color.Green, 0.000001f);
}

public void CheckHits(float delta)
{
List<IHitInfo> intersects = new List<IHitInfo>();
Expand All @@ -112,12 +107,14 @@ public void CheckHits(float delta)

foreach (var hitInfo in intersects)
{
if (QueuedDispose)
break;
double dist = len * hitInfo.Fraction;
ProjectileHit(hitInfo.HitEntity);
ProjectileHit(hitInfo.HitEntity, hitInfo.Position);
}
}

public void ProjectileHit(IMyEntity impact)
public void ProjectileHit(IMyEntity impact, Vector3D impactPosition)
{
if (impact.EntityId == Firer)
return;
Expand All @@ -127,6 +124,8 @@ public void ProjectileHit(IMyEntity impact)
else if (impact is IMyCharacter)
DamageHandler.QueueEvent(new DamageEvent(impact, DamageEvent.DamageEntType.Character, this));

DrawImpactParticle(impactPosition);

RemainingImpacts -= 1;
if (RemainingImpacts <= 0)
QueueDispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ internal class ProjectileDefinitionManager
SlimBlockDamageMod = 25,
FatBlockDamageMod = 1,
BaseDamage = 100,
AreaDamage = 50,
AreaDamage = 100,
AreaRadius = 15,
MaxImpacts = 1,
},
Expand All @@ -37,8 +37,8 @@ internal class ProjectileDefinitionManager
Model = "",
TrailTexture = "",
TrailFadeTime = 0,
AttachedParticle = "",
ImpactParticle = "",
AttachedParticle = "Smoke_Missile",
ImpactParticle = "Explosion_LargeCaliberShell_Backup",
VisibleChance = 1,
},
Audio = new Audio()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using Heart_Module.Data.Scripts.HeartModule.Debug;
using Sandbox.ModAPI;
using VRage.Game;
using VRage.Game.Entity;
using VRageMath;
using VRageRender;

namespace Heart_Module.Data.Scripts.HeartModule.Projectiles
{
partial class Projectile
{
MyBillboard ProjectileBillboard;
MyEntity ProjectileEntity;
MyParticleEffect ProjectileEffect;
uint RenderId = 0;

internal void InitDrawing()
{
ProjectileBillboard = new MyBillboard();
ProjectileEntity = new MyEntity();
RenderId = ProjectileEntity.Render.GetRenderObjectID();
}

public void DrawUpdate(float delta)
{
Vector3D visualPosition = Position + (InheritedVelocity + Direction * (Velocity + Definition.PhysicalProjectile.Acceleration * delta)) * delta;
// Temporary debug draw
//DebugDraw.AddPoint(visualPosition, Color.Green, 0.000001f);

if (Definition.Visual.AttachedParticle != "")
{
MatrixD matrix = MatrixD.CreateWorld(visualPosition, Direction, Vector3D.Cross(Direction, Vector3D.Up));

if (ProjectileEffect == null)
{
MyParticlesManager.TryCreateParticleEffect(Definition.Visual.AttachedParticle, ref matrix, ref visualPosition, RenderId, out ProjectileEffect);
}
else
{
ProjectileEffect.WorldMatrix = matrix;
}
}
}

private void DrawImpactParticle(Vector3D ImpactPosition)
{
if (Definition.Visual.ImpactParticle == "")
return;

MatrixD matrix = MatrixD.CreateTranslation(ImpactPosition);
MyParticleEffect hitEffect;
if (MyParticlesManager.TryCreateParticleEffect(Definition.Visual.ImpactParticle, ref matrix, ref ImpactPosition, uint.MaxValue, out hitEffect))
{
MyAPIGateway.Utilities.ShowNotification("Spawned particle at " + hitEffect.WorldMatrix.Translation);
//hitEffect.UserScale = av.AmmoDef.AmmoGraphics.Particles.Hit.Extras.Scale;
//hitEffect.Velocity = av.Hit.HitVelocity;

if (hitEffect.Loop)
hitEffect.Stop();
}
}

internal void CloseDrawing()
{
ProjectileEffect?.Close();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Diagnostics;
using System.Linq;
using VRage.Game.Components;
using VRage.Game.ModAPI;
using VRageMath;

namespace Heart_Module.Data.Scripts.HeartModule.Projectiles
Expand All @@ -14,10 +15,10 @@ namespace Heart_Module.Data.Scripts.HeartModule.Projectiles
public class ProjectileManager : MySessionComponentBase
{
public static ProjectileManager I = new ProjectileManager();
const int MaxProjectilesSynced = 25; // TODO: Sync within range of client. This value should be ~100kB/s per player
const int MaxProjectilesSynced = 25; // This value should result in ~100kB/s per player.

private Dictionary<uint, Projectile> ActiveProjectiles = new Dictionary<uint, Projectile>();
private List<uint> ProjectileSyncStream = new List<uint>();
private Dictionary<ulong, List<uint>> ProjectileSyncStream = new Dictionary<ulong, List<uint>>();
public uint NextId { get; private set; } = 0;
private List<Projectile> QueuedCloseProjectiles = new List<Projectile>();
private float delta = 0;
Expand Down Expand Up @@ -57,18 +58,21 @@ public override void UpdateAfterSimulation()
{
if (HeartData.I.IsSuspended) return;

if (j >= 1 && MyAPIGateway.Session.IsServer)
// spawn projectiles at world origin for debugging. Don't actually do this to spawn projectiles, please.
if (j >= 25 && MyAPIGateway.Session.IsServer)
{
j = 0;
try
{
Random r = new Random();
Vector3D randVec = new Vector3D(r.NextDouble(), r.NextDouble(), r.NextDouble()).Normalized();
Projectile p = new Projectile(new SerializableProjectile()
{
IsActive = true,
Id = 0,
DefinitionId = 0,
Position = MyAPIGateway.Session.Player?.GetPosition() ?? Vector3D.Zero,
Direction = MyAPIGateway.Session.Player?.Controller.ControlledEntity.Entity.WorldMatrix.Forward ?? Vector3D.Forward,
Position = MyAPIGateway.Session.Player?.GetPosition() ?? Vector3D.Zero, // CHECK OUT HOW HARD I CAN PISS
Direction = MyAPIGateway.Session.Player?.Controller.ControlledEntity.Entity.WorldMatrix.Forward.Rotate(randVec, r.NextDouble() * 0.0873 - 0.04365) ?? Vector3D.Forward,
Velocity = 100,
Timestamp = DateTime.Now.Ticks,
InheritedVelocity = Vector3D.Zero,
Expand All @@ -84,6 +88,7 @@ public override void UpdateAfterSimulation()
}
j++;

// Delta time for tickrate-independent projectile movement
delta = clock.ElapsedTicks / (float)TimeSpan.TicksPerSecond;

// Tick projectiles
Expand All @@ -100,32 +105,39 @@ public override void UpdateAfterSimulation()
//MyAPIGateway.Utilities.ShowMessage("Heart", $"Closing projectile {projectile.Id}. Age: {projectile.Age} ");
if (MyAPIGateway.Session.IsServer)
SyncProjectile(projectile, 2);
projectile.Close.Invoke(projectile);

if (!MyAPIGateway.Utilities.IsDedicated)
projectile.CloseDrawing();

ActiveProjectiles.Remove(projectile.Id);
projectile.OnClose.Invoke(projectile);
}
QueuedCloseProjectiles.Clear();

// Sync stuff
int numSyncs = 0;
if (MyAPIGateway.Session.IsServer && MyAPIGateway.Multiplayer.MultiplayerActive)
{
for (int i = 0; i < MaxProjectilesSynced && i < ProjectileSyncStream.Count; i++)
List<IMyPlayer> players = new List<IMyPlayer>();
MyAPIGateway.Multiplayer.Players.GetPlayers(players);

foreach (var player in players) // Ensure that all players are being synced
if (!ProjectileSyncStream.ContainsKey(player.SteamUserId))
ProjectileSyncStream.Add(player.SteamUserId, new List<uint>());

foreach (ulong syncedPlayerSteamId in ProjectileSyncStream.Keys.ToList())
{
uint id = ProjectileSyncStream[i];

if (ActiveProjectiles.ContainsKey(id))
bool remove = true;
foreach (var player in players)
{
SyncProjectile(ActiveProjectiles[id], 1);
numSyncs++;
if (syncedPlayerSteamId == player.SteamUserId)
{
SyncPlayerProjectiles(player); // Sync individual players to lower network load
remove = false;
}
}
if (remove) // Remove disconnected players from sync list
ProjectileSyncStream.Remove(syncedPlayerSteamId);
}

if (ProjectileSyncStream.Count < MaxProjectilesSynced)
{
ProjectileSyncStream.Clear();
ProjectileSyncStream = ActiveProjectiles.Keys.ToList();
}
else
ProjectileSyncStream.RemoveRange(0, MaxProjectilesSynced);
}
else
{
Expand All @@ -137,6 +149,34 @@ public override void UpdateAfterSimulation()
clock.Restart();
}

private void SyncPlayerProjectiles(IMyPlayer player)
{
int numSyncs = 0;

for (int i = 0; i < MaxProjectilesSynced && i < ProjectileSyncStream[player.SteamUserId].Count; i++)
{
uint id = ProjectileSyncStream[player.SteamUserId][i];

if (ActiveProjectiles.ContainsKey(id))
{
SyncProjectile(ActiveProjectiles[id], 1, player.SteamUserId);
numSyncs++;
}
}

if (ProjectileSyncStream[player.SteamUserId].Count < MaxProjectilesSynced)
{
// Limits projectile syncing to within sync range.
// Syncing is based off of character position for now, camera position may be wise in the future
ProjectileSyncStream[player.SteamUserId].Clear();
foreach (var projectile in ActiveProjectiles.Values)
if (Vector3D.DistanceSquared(projectile.Position, player.GetPosition()) < HeartData.I.SyncRangeSq)
ProjectileSyncStream[player.SteamUserId].Add(projectile.Id);
}
else
ProjectileSyncStream[player.SteamUserId].RemoveRange(0, MaxProjectilesSynced);
}

public override void UpdatingStopped()
{
clock.Stop();
Expand All @@ -146,15 +186,13 @@ public override void Draw()
{
if (HeartData.I.IsSuspended) return;

if (MyAPIGateway.Utilities.IsDedicated)
if (MyAPIGateway.Utilities.IsDedicated) // We don't want to needlessly use server CPU time
return;

delta = clock.ElapsedTicks / (float)TimeSpan.TicksPerSecond;
// Triggered every frame, avoids jitter in projectiles
foreach (var projectile in ActiveProjectiles.Values)
{
projectile.DrawUpdate(delta);
}
}

public void UpdateProjectile(SerializableProjectile projectile)
Expand All @@ -176,12 +214,19 @@ public void AddProjectile(Projectile projectile)
while (!IsIdAvailable(NextId))
NextId++;
projectile.SetId(NextId);
projectile.Close += (p) => ActiveProjectiles.Remove(p.Id);
ActiveProjectiles.Add(projectile.Id, projectile);
SyncProjectile(projectile, 0);
if (!MyAPIGateway.Utilities.IsDedicated)
projectile.InitDrawing();
}

public void SyncProjectile(Projectile projectile, int DetailLevel = 1) => HeartData.I.Net.SendToEveryone(projectile.AsSerializable(DetailLevel));
public void SyncProjectile(Projectile projectile, int DetailLevel = 1, ulong PlayerSteamId = 0)
{
if (PlayerSteamId == 0)
HeartData.I.Net.SendToEveryone(projectile.AsSerializable(DetailLevel));
else
HeartData.I.Net.SendToPlayer(projectile.AsSerializable(DetailLevel), PlayerSteamId);
}

public Projectile GetProjectile(uint id) => ActiveProjectiles.GetValueOrDefault(id, null);
public bool IsIdAvailable(uint id) => !ActiveProjectiles.ContainsKey(id);
Expand Down

0 comments on commit c0b1714

Please sign in to comment.