341 lines
7.7 KiB
C#
341 lines
7.7 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Godot;
|
|
|
|
public partial class GameManager : Node3D
|
|
{
|
|
[Export] public Control GameMenu { get; private set; }
|
|
[Export] public QueueManager QueueManager { get; private set; }
|
|
[Export] public Node3D SpaceRoot { get; private set; }
|
|
|
|
[Export] public double closeDistance = 50;
|
|
[Export] public double closeTickInterval = 1;
|
|
[Export] public double farTickInterval = 10;
|
|
[Export] public int maxFarThreads = 16;
|
|
|
|
public static bool Loading { get; private set; } = true;
|
|
public static GameManager Instance { get; private set; }
|
|
public static IGenerator Generator { get; private set; }
|
|
public static Universe GameUniverse { get; private set; }
|
|
public Player MainPlayer { get; private set; }
|
|
public Dictionary<long, Player> Players { get; private set; } = [];
|
|
public Dictionary<long, HashSet<GameObject>> PlayerSpawnedObjects { get; private set; } = [];
|
|
|
|
private double closeTickTimer = 0;
|
|
private double farTickTimer = 0;
|
|
|
|
public bool simulatingClose = false;
|
|
public bool simulatingFar = false;
|
|
public bool playerReady = false;
|
|
|
|
private readonly Dictionary<GameObject, Node3D> localSpawnedObjects = [];
|
|
private readonly ConcurrentQueue<GameObject> spawnQueue = [];
|
|
|
|
public override void _EnterTree()
|
|
{
|
|
Instance = this;
|
|
}
|
|
|
|
public override void _Ready()
|
|
{
|
|
Generator = new TestGenerator();
|
|
|
|
if (Global.IsGameHost)
|
|
{
|
|
GameUniverse = Generator.GenerateUniverse();
|
|
Loading = false;
|
|
}
|
|
}
|
|
|
|
private void OnPlayerReady()
|
|
{
|
|
playerReady = true;
|
|
}
|
|
|
|
public override void _ExitTree()
|
|
{
|
|
Loading = true;
|
|
Instance = null;
|
|
}
|
|
|
|
public override void _Process(double delta)
|
|
{
|
|
if (!playerReady || !Global.IsGameHost)
|
|
{
|
|
return;
|
|
}
|
|
|
|
closeTickTimer += delta;
|
|
if (closeTickTimer >= closeTickInterval && !simulatingClose)
|
|
{
|
|
SimulateClose(closeTickTimer);
|
|
SyncClose();
|
|
closeTickTimer = 0;
|
|
}
|
|
|
|
farTickTimer += delta;
|
|
if (farTickTimer >= farTickInterval && !simulatingFar && QueueManager.SectorReassignQueue.IsEmpty)
|
|
{
|
|
double taskFarTickTimer = farTickTimer;
|
|
Task.Run(() =>
|
|
{
|
|
SimulateFar(taskFarTickTimer);
|
|
});
|
|
|
|
farTickTimer = 0;
|
|
}
|
|
}
|
|
|
|
public Sector GetCurrentSector()
|
|
{
|
|
return MainPlayer.PlayerData.CurrentSector;
|
|
}
|
|
|
|
public Player GetPlayer(long id)
|
|
{
|
|
Players.TryGetValue(id, out Player player);
|
|
return player;
|
|
}
|
|
|
|
private void SimulateClose(double delta)
|
|
{
|
|
simulatingClose = true;
|
|
|
|
HashSet<GameObject> objectsToSimulate = [];
|
|
|
|
foreach (KeyValuePair<long, Player> player in Players)
|
|
{
|
|
Character character = player.Value.PlayerData;
|
|
|
|
List<Sector> playerSectors = character.CurrentSector.GetNeighbouringSectors();
|
|
playerSectors.ForEach(sector =>
|
|
{
|
|
sector.GameObjects.ForEach(gameObject =>
|
|
{
|
|
double distance = character.GetDistanceToObject(gameObject);
|
|
if (distance <= closeDistance)
|
|
{
|
|
objectsToSimulate.Add(gameObject);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
foreach (GameObject gameObject in objectsToSimulate)
|
|
{
|
|
gameObject.Simulate(delta);
|
|
}
|
|
|
|
simulatingClose = false;
|
|
}
|
|
|
|
private void SyncClose()
|
|
{
|
|
foreach (KeyValuePair<long, Player> player in Players)
|
|
{
|
|
bool isHostPlayer = player.Key == 1;
|
|
long playerKey = player.Key;
|
|
Character character = player.Value.PlayerData;
|
|
|
|
PlayerSpawnedObjects.TryGetValue(playerKey, out HashSet<GameObject> playerSpawnedObjects);
|
|
playerSpawnedObjects ??= [];
|
|
HashSet<GameObject> playerNewSpawnedObjects = [];
|
|
|
|
List<Sector> playerSectors = character.CurrentSector.GetNeighbouringSectors();
|
|
|
|
// Spawn/Send object
|
|
playerSectors.ForEach(sector =>
|
|
{
|
|
sector.GameObjects.ForEach(gameObject =>
|
|
{
|
|
double distance = character.GetDistanceToObject(gameObject);
|
|
if (distance <= closeDistance)
|
|
{
|
|
if (isHostPlayer)
|
|
Spawn(gameObject);
|
|
else
|
|
gameObject.NetworkWrite(playerKey, !playerSpawnedObjects.Contains(gameObject));
|
|
|
|
playerNewSpawnedObjects.Add(gameObject);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Despawn object
|
|
foreach (GameObject gameObject in playerSpawnedObjects)
|
|
{
|
|
if (!playerNewSpawnedObjects.Contains(gameObject))
|
|
{
|
|
if (isHostPlayer)
|
|
Despawn(gameObject);
|
|
else
|
|
RPCNode.Instance.RpcId(playerKey, nameof(RPCNode.RpcDespawnGameObject), gameObject.UUID.ToString());
|
|
}
|
|
}
|
|
|
|
PlayerSpawnedObjects[playerKey] = playerNewSpawnedObjects;
|
|
}
|
|
}
|
|
|
|
private void SimulateFar(double delta)
|
|
{
|
|
simulatingFar = true;
|
|
|
|
Sector[,,] sectors = GameUniverse.Sectors;
|
|
|
|
HashSet<GameObject> allSpawnedObjects = [];
|
|
foreach (HashSet<GameObject> item in PlayerSpawnedObjects.Values)
|
|
{
|
|
foreach (GameObject gameObject in item)
|
|
{
|
|
allSpawnedObjects.Add(gameObject);
|
|
}
|
|
}
|
|
|
|
int sizeX = sectors.GetLength(0);
|
|
|
|
double taskDelta = delta;
|
|
Parallel.For(0, sizeX, x =>
|
|
{
|
|
SimulateFarClustered(allSpawnedObjects, taskDelta, x);
|
|
});
|
|
|
|
simulatingFar = false;
|
|
}
|
|
|
|
private static void SimulateFarClustered(HashSet<GameObject> ignoreObjects, double delta, int x)
|
|
{
|
|
Sector[,,] sectors = GameUniverse.Sectors;
|
|
|
|
int sizeY = sectors.GetLength(1);
|
|
int sizeZ = sectors.GetLength(2);
|
|
|
|
for (int y = 0; y < sizeY; y++)
|
|
{
|
|
for (int z = 0; z < sizeZ; z++)
|
|
{
|
|
try
|
|
{
|
|
sectors[x, y, z].Simulate(delta, ignoreObjects);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
QueueManager.LogQueue.Enqueue("EXCEPTION: " + e.Message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public Player SpawnPlayer(Character character, long networkId, bool isMainPlayer = false)
|
|
{
|
|
Player player = character.InstantiatePlayer();
|
|
player.GameMenu = GameMenu;
|
|
|
|
if (isMainPlayer)
|
|
{
|
|
MainPlayer = player;
|
|
OnPlayerReady();
|
|
}
|
|
|
|
Players.Add(networkId, player);
|
|
|
|
character.UpdateSector();
|
|
|
|
SpaceRoot.CallDeferred("add_child", player);
|
|
return player;
|
|
}
|
|
|
|
public void DespawnPlayer(Player player, long id)
|
|
{
|
|
Players.Remove(id);
|
|
|
|
SpaceRoot.CallDeferred("remove_child", player);
|
|
player.CallDeferred("queue_free");
|
|
}
|
|
|
|
public void Spawn(GameObject gameObject)
|
|
{
|
|
spawnQueue.Enqueue(gameObject);
|
|
CallDeferred(nameof(ProcessSpawnQueue));
|
|
}
|
|
|
|
private void ProcessSpawnQueue()
|
|
{
|
|
spawnQueue.TryDequeue(out GameObject gameObject);
|
|
if (gameObject == null)
|
|
{
|
|
return;
|
|
}
|
|
if (localSpawnedObjects.ContainsKey(gameObject))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Node3D instance = gameObject.Instantiate(GetCurrentSector());
|
|
localSpawnedObjects.Add(gameObject, instance);
|
|
|
|
SpaceRoot.CallDeferred("add_child", instance);
|
|
}
|
|
|
|
public void Despawn(GameObject gameObject)
|
|
{
|
|
if (!localSpawnedObjects.ContainsKey(gameObject))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Node3D nodeToDespawn = localSpawnedObjects.GetValueOrDefault(gameObject);
|
|
localSpawnedObjects.Remove(gameObject);
|
|
|
|
SpaceRoot.CallDeferred("remove_child", nodeToDespawn);
|
|
nodeToDespawn.CallDeferred("queue_free");
|
|
}
|
|
|
|
public void Despawn(Guid uuid)
|
|
{
|
|
foreach (GameObject gameObject in localSpawnedObjects.Keys)
|
|
{
|
|
if (gameObject.UUID == uuid)
|
|
{
|
|
Despawn(gameObject);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ApplyOrigin()
|
|
{
|
|
Sector current = GetCurrentSector();
|
|
|
|
foreach (KeyValuePair<long, Player> player in Players)
|
|
{
|
|
player.Value.PlayerData.UpdateSectorOffset(current);
|
|
player.Value.PlayerData.UpdateNodePosition();
|
|
}
|
|
|
|
foreach (KeyValuePair<GameObject, Node3D> spawned in localSpawnedObjects)
|
|
{
|
|
GameObject gameObject = spawned.Key;
|
|
Node3D node = spawned.Value;
|
|
|
|
gameObject.UpdateSectorOffset(current);
|
|
node._Process(0);
|
|
}
|
|
}
|
|
|
|
public void SendUniverseToClient(long id)
|
|
{
|
|
RpcId(id, nameof(RpcDownloadUniverse), Generator.GetUniverseSize(), Generator.GetSectorSize());
|
|
}
|
|
|
|
[Rpc(MultiplayerApi.RpcMode.Authority)]
|
|
public void RpcDownloadUniverse(Vector3I universeSize, Vector3 sectorSize)
|
|
{
|
|
GameUniverse = Generator.InitializeEmptyUniverse(universeSize, sectorSize);
|
|
|
|
Loading = false;
|
|
}
|
|
}
|