Refactor map netcode. (#726)

Maps are now synchronized as part of game states.
This instantly fixed all race conditions and the client can now live through a round restart with 0 problems (outside of Godot crashing yay).
This commit is contained in:
Pieter-Jan Briers
2019-01-19 00:39:55 +01:00
committed by GitHub
parent 8d58ad304a
commit 58fb11a989
20 changed files with 261 additions and 416 deletions

View File

@@ -187,10 +187,6 @@ namespace SS14.Client
_stateManager.RequestStateChange<GameScreen>();
OnPlayerJoinedGame(_playMan.LocalPlayer.Session);
// request entire map be sent to us
var mapMsg = _net.CreateNetMessage<MsgMapReq>();
_net.ClientSendMessage(mapMsg);
}
}

View File

@@ -148,7 +148,7 @@ namespace SS14.Client.GameObjects
}
// After the first set of states comes in we do the startup.
if (!EntitiesInitialized && MapsInitialized)
if (!EntitiesInitialized)
{
InitializeEntities();
}
@@ -163,7 +163,6 @@ namespace SS14.Client.GameObjects
{
base.Shutdown();
MapsInitialized = false;
EntitiesInitialized = false;
}

View File

@@ -12,6 +12,7 @@ using SS14.Shared.Network.Messages;
using System.Collections.Generic;
using System.Linq;
using SS14.Client.Player;
using SS14.Shared.Interfaces.Map;
namespace SS14.Client.GameStates
{
@@ -29,6 +30,8 @@ namespace SS14.Client.GameStates
private readonly IClientNetManager netManager;
[Dependency]
private readonly IBaseClient baseClient;
[Dependency]
private readonly IMapManager _mapManager;
public void Initialize()
{
@@ -73,8 +76,10 @@ namespace SS14.Client.GameStates
private void ApplyGameState(GameState gameState)
{
_mapManager.ApplyGameStatePre(gameState.MapData);
entityManager.ApplyEntityStates(gameState.EntityStates, gameState.EntityDeletions, gameState.GameTime);
playerManager.ApplyPlayerStates(gameState.PlayerStates);
_mapManager.ApplyGameStatePost(gameState.MapData);
}
}
}

View File

@@ -29,9 +29,9 @@ namespace SS14.Client.Map
private void UpdateOnGridModified(object sender, GridChangedEventArgs args)
{
var tilemap = RenderTileMaps[args.Grid.Index];
foreach ((int x, int y, Tile tile) in args.Modified)
foreach (var (index, tile) in args.Modified)
{
tilemap.SetCell(x, -y, tile.TileId);
tilemap.SetCell(index.X, -index.Y, tile.TileId);
}
}

View File

@@ -5,10 +5,12 @@ using SS14.Server.Interfaces.GameState;
using SS14.Server.Interfaces.Player;
using SS14.Shared.Enums;
using SS14.Shared.GameStates;
using SS14.Shared.Interfaces.Map;
using SS14.Shared.Interfaces.Network;
using SS14.Shared.Interfaces.Timing;
using SS14.Shared.IoC;
using SS14.Shared.Network.Messages;
using SS14.Shared.Utility;
namespace SS14.Server.GameStates
{
@@ -31,6 +33,8 @@ namespace SS14.Server.GameStates
[Dependency]
private IPlayerManager _playerManager;
[Dependency] private IMapManager _mapManager;
public void Initialize()
{
_networkManager.RegisterNetMessage<MsgState>(MsgState.NAME);
@@ -44,16 +48,18 @@ namespace SS14.Server.GameStates
public void SendGameStateUpdate()
{
DebugTools.Assert(_networkManager.IsServer);
if (!_networkManager.IsConnected)
{
// Prevent deletions piling up if we have no clients.
_entityManager.CullDeletionHistory(uint.MaxValue);
_mapManager.CullDeletionHistory(uint.MaxValue);
return;
}
var connections = _networkManager.Channels;
uint oldestAck = uint.MaxValue;
foreach (var connection in connections)
var oldestAck = uint.MaxValue;
foreach (var connection in _networkManager.Channels)
{
if (!ackedStates.TryGetValue(connection.ConnectionId, out var ack))
{
@@ -74,10 +80,11 @@ namespace SS14.Server.GameStates
var entities = _entityManager.GetEntityStates(oldestAck);
var players = _playerManager.GetPlayerStates();
var deletions = _entityManager.GetDeletedEntities(oldestAck);
var mapData = _mapManager.GetStateData(oldestAck);
var state = new GameState(oldestAck, _gameTiming.CurTick, entities, players, deletions);
var state = new GameState(oldestAck, _gameTiming.CurTick, entities, players, deletions, mapData);
foreach (var c in connections)
foreach (var c in _networkManager.Channels)
{
var session = _playerManager.GetSessionByChannel(c);

View File

@@ -9,17 +9,6 @@
RequestEntRemove,
}
public enum MapMessage
{
TurfUpdate = 0,
TurfClick,
SendTileMap,
SendMapInfo,
CreateMap,
DeleteMap
}
public enum SessionStatus : byte
{
Zombie = 0,

View File

@@ -69,7 +69,6 @@ namespace SS14.Shared.GameObjects
protected bool EntitiesInitialized;
public bool Started { get; protected set; }
public bool MapsInitialized { get; set; } = false;
#region IEntityManager Members
@@ -93,25 +92,14 @@ namespace SS14.Shared.GameObjects
public virtual void Update(float frameTime)
{
ProcessMsgBuffer();
// for some 'special' reason entities require the map they are on to exist before they can
// be initialized. This should stop the EntitySystems from updating over the uninitialized entities.
if(MapsInitialized || _network.IsServer)
{
EntitySystemManager.Update(frameTime);
// dispatching events to systems will usually cause them to interact
// with entities, we don't want that to happen if the maps are not initialized.
ProcessEventQueue();
}
EntitySystemManager.Update(frameTime);
ProcessEventQueue();
CullDeletedEntities();
}
public virtual void FrameUpdate(float frameTime)
{
if (MapsInitialized || _network.IsServer)
EntitySystemManager.FrameUpdate(frameTime);
EntitySystemManager.FrameUpdate(frameTime);
}
/// <summary>

View File

@@ -19,14 +19,14 @@ namespace SS14.Shared.GameStates
/// <summary>
/// Constructor!
/// </summary>
/// <param name="sequence"></param>
public GameState(uint fromSequence, uint toSequence, List<EntityState> entities, List<PlayerState> players, List<EntityUid> deletions)
public GameState(uint fromSequence, uint toSequence, List<EntityState> entities, List<PlayerState> players, List<EntityUid> deletions, GameStateMapData mapData)
{
FromSequence = fromSequence;
ToSequence = toSequence;
EntityStates = entities;
PlayerStates = players;
EntityDeletions = deletions;
MapData = mapData;
}
public readonly uint FromSequence;
@@ -35,5 +35,6 @@ namespace SS14.Shared.GameStates
public readonly List<EntityState> EntityStates;
public readonly List<PlayerState> PlayerStates;
public readonly List<EntityUid> EntityDeletions;
public readonly GameStateMapData MapData;
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using SS14.Shared.Map;
using SS14.Shared.Maths;
using SS14.Shared.Serialization;
namespace SS14.Shared.GameStates
{
[Serializable, NetSerializable]
public class GameStateMapData
{
// Dict of the new maps along with which grids are their defaults.
public readonly Dictionary<MapId, GridId> CreatedMaps;
public readonly Dictionary<GridId, GridCreationDatum> CreatedGrids;
public readonly Dictionary<GridId, GridDatum> GridData;
public readonly List<GridId> DeletedGrids;
public readonly List<MapId> DeletedMaps;
public GameStateMapData(Dictionary<GridId, GridDatum> gridData, List<GridId> deletedGrids, List<MapId> deletedMaps, Dictionary<MapId, GridId> createdMaps, Dictionary<GridId, GridCreationDatum> createdGrids)
{
GridData = gridData;
DeletedGrids = deletedGrids;
DeletedMaps = deletedMaps;
CreatedMaps = createdMaps;
CreatedGrids = createdGrids;
}
[Serializable, NetSerializable]
public struct GridCreationDatum
{
public readonly ushort ChunkSize;
public readonly float SnapSize;
public readonly bool IsTheDefault;
public GridCreationDatum(ushort chunkSize, float snapSize, bool isTheDefault)
{
ChunkSize = chunkSize;
SnapSize = snapSize;
IsTheDefault = isTheDefault;
}
}
[Serializable, NetSerializable]
public struct GridDatum
{
public readonly MapCoordinates Coordinates;
public readonly List<ChunkDatum> ChunkData;
public GridDatum(List<ChunkDatum> chunkData, MapCoordinates coordinates)
{
ChunkData = chunkData;
Coordinates = coordinates;
}
}
[Serializable, NetSerializable]
public struct ChunkDatum
{
public readonly MapIndices Index;
// Definitely wasteful to send EVERY tile.
// Optimize away future coder.
// Also it's stored row-major.
public readonly Tile[] TileData;
public ChunkDatum(MapIndices index, Tile[] tileData)
{
Index = index;
TileData = tileData;
}
}
}
}

View File

@@ -20,8 +20,6 @@ namespace SS14.Shared.Interfaces.GameObjects
/// </summary>
void FrameUpdate(float frameTime);
bool MapsInitialized { get; set; }
IComponentManager ComponentManager { get; }
IEntityNetworkManager EntityNetManager { get; }

View File

@@ -14,11 +14,6 @@ namespace SS14.Shared.Interfaces.Map
/// </summary>
ushort ChunkSize { get; }
/// <summary>
/// The supported version of the chunk format.
/// </summary>
uint Version { get; }
/// <summary>
/// The X index of this chunk.
/// </summary>

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using SS14.Shared.GameStates;
using SS14.Shared.Interfaces.Network;
using SS14.Shared.Map;
@@ -67,8 +68,6 @@ namespace SS14.Shared.Interfaces.Map
bool TryGetMap(MapId mapID, out IMap map);
void SendMap(INetChannel channel);
void DeleteMap(MapId mapID);
IMapGrid CreateGrid(MapId currentMapID, GridId? gridID = null, ushort chunkSize = 16, float snapSize = 1);
@@ -100,5 +99,13 @@ namespace SS14.Shared.Interfaces.Map
/// An existing map has been destroyed.
/// </summary>
event EventHandler<MapEventArgs> MapDestroyed;
GameStateMapData GetStateData(uint fromTick);
void CullDeletionHistory(uint uptoTick);
// Two methods here, so that new grids etc can be made BEFORE entities get states applied,
// but old ones can be deleted after.
void ApplyGameStatePre(GameStateMapData data);
void ApplyGameStatePost(GameStateMapData data);
}
}

View File

@@ -15,12 +15,12 @@ namespace SS14.Shared.Map
/// </summary>
internal class Chunk : IMapChunk
{
private const int ChunkVersion = 1;
internal uint LastModifiedTick { get; private set; }
private readonly MapGrid _grid;
private readonly MapIndices _gridIndices;
private readonly MapManager _mapManager;
private readonly Tile[,] _tiles;
internal readonly Tile[,] _tiles;
private readonly SnapGridCell[,] _snapGrid;
/// <summary>
@@ -34,6 +34,7 @@ namespace SS14.Shared.Map
public Chunk(MapManager manager, MapGrid grid, int x, int y, ushort chunkSize)
{
_mapManager = manager;
LastModifiedTick = _mapManager._gameTiming.CurTick;
ChunkSize = chunkSize;
_grid = grid;
_gridIndices = new MapIndices(x, y);
@@ -45,9 +46,6 @@ namespace SS14.Shared.Map
/// <inheritdoc />
public ushort ChunkSize { get; }
/// <inheritdoc />
public uint Version => ChunkVersion;
/// <inheritdoc />
public int X => _gridIndices.X;
@@ -107,6 +105,7 @@ namespace SS14.Shared.Map
var gridTile = ChunkTileToGridTile(new MapIndices(xChunkTile, yChunkTile));
var newTileRef = new TileRef(_grid.MapID, _grid.Index, gridTile.X, gridTile.Y, tile);
var oldTile = _tiles[xChunkTile, yChunkTile];
_grid.LastModifiedTick = LastModifiedTick = _mapManager._gameTiming.CurTick;
_mapManager.RaiseOnTileChanged(newTileRef, oldTile);
_grid.UpdateAABB(gridTile);

View File

@@ -8,6 +8,7 @@ namespace SS14.Shared.Map
{
class Map : IMap
{
public uint CreatedTick { get; }
public IMapGrid DefaultGrid { get; set; }
public MapId Index { get; }
private readonly MapManager _mapManager;
@@ -17,6 +18,7 @@ namespace SS14.Shared.Map
{
Index = mapID;
_mapManager = mapManager;
CreatedTick = _mapManager._gameTiming.CurTick;
}
/// <inheritdoc />

View File

@@ -10,11 +10,14 @@ namespace SS14.Shared.Map
{
public class MapGrid : IMapGrid
{
public uint CreatedTick { get; }
public uint LastModifiedTick { get; internal set; }
public bool IsDefaultGrid => Map.DefaultGrid == this;
public IMap Map => _mapManager.GetMap(MapID);
public MapId MapID { get; private set; }
private readonly MapManager _mapManager;
private readonly Dictionary<MapIndices, Chunk> _chunks = new Dictionary<MapIndices, Chunk>();
internal readonly Dictionary<MapIndices, Chunk> _chunks = new Dictionary<MapIndices, Chunk>();
private Vector2 _worldPosition;
internal MapGrid(MapManager mapManager, GridId gridIndex, ushort chunkSize, float snapsize, MapId mapID)
{
@@ -23,6 +26,7 @@ namespace SS14.Shared.Map
ChunkSize = chunkSize;
SnapSize = snapsize;
MapID = mapID;
LastModifiedTick = CreatedTick = _mapManager._gameTiming.CurTick;
}
/// <summary>
@@ -47,10 +51,18 @@ namespace SS14.Shared.Map
/// <summary>
/// The length of the side of a square tile in world units.
/// </summary>
public ushort TileSize { get; set; } = 1;
public ushort TileSize { get; } = 1;
/// <inheritdoc />
public Vector2 WorldPosition { get; set; }
public Vector2 WorldPosition
{
get => _worldPosition;
set
{
_worldPosition = value;
LastModifiedTick = _mapManager._gameTiming.CurTick;
}
}
/// <summary>
/// Expands the AABB for this grid when a new tile is added. If the tile is already inside the existing AABB,

View File

@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using SS14.Shared.Enums;
using SS14.Shared.GameStates;
using SS14.Shared.Interfaces.GameObjects;
using SS14.Shared.Interfaces.Map;
using SS14.Shared.Interfaces.Network;
@@ -14,205 +16,155 @@ namespace SS14.Shared.Map
{
public partial class MapManager
{
[Dependency]
private readonly INetManager _netManager;
[Dependency] private readonly INetManager _netManager;
private int _gridsToReceive;
private int _gridsReceived;
public void SendMap(INetChannel channel)
public GameStateMapData GetStateData(uint fromTick)
{
if (_netManager.IsClient)
return;
DebugTools.Assert(_netManager.IsServer, "Why is the client calling this?");
Logger.Info($"{channel.RemoteEndPoint}: Sending map");
var quantityGridsSent = 0;
foreach (var map in GetAllMaps())
var gridDatums = new Dictionary<GridId, GameStateMapData.GridDatum>();
foreach (var grid in _grids.Values)
{
foreach (var grid in map.GetAllGrids())
if (grid.LastModifiedTick < fromTick)
{
quantityGridsSent++;
var message = _netManager.CreateNetMessage<MsgMap>();
message.MessageType = MapMessage.SendTileMap;
message.MapIndex = map.Index;
message.GridIndex = grid.Index;
continue;
}
// Map chunks
var gridSize = grid.ChunkSize;
message.ChunkSize = gridSize;
message.ChunkDefs = new MsgMap.ChunkDef[grid.ChunkCount];
var defCounter = 0;
foreach (var chunk in grid.GetMapChunks())
var chunkData = new List<GameStateMapData.ChunkDatum>();
foreach (var (index, chunk) in grid._chunks)
{
if (chunk.LastModifiedTick < fromTick)
{
var newChunk = new MsgMap.ChunkDef
{
X = chunk.X,
Y = chunk.Y
};
newChunk.Tiles = new uint[gridSize * gridSize];
var counter = 0;
foreach (var tile in chunk)
{
newChunk.Tiles[counter] = (uint)tile.Tile;
counter++;
}
message.ChunkDefs[defCounter++] = newChunk;
continue;
}
_netManager.ServerSendMessage(message, channel);
}
}
var msg = _netManager.CreateNetMessage<MsgMap>();
msg.MessageType = MapMessage.SendMapInfo;
msg.MapGridsToSend = quantityGridsSent;
_netManager.ServerSendMessage(msg, channel);
}
var tileBuffer = new Tile[grid.ChunkSize * (uint) grid.ChunkSize];
private void HandleNetworkMessage(MsgMap message)
{
switch (message.MessageType)
{
case MapMessage.TurfClick:
HandleTurfClick(message);
break;
case MapMessage.TurfUpdate:
HandleTileUpdate(message);
break;
case MapMessage.SendTileMap:
HandleTileMap(message);
break;
case MapMessage.SendMapInfo:
CollectMapInfo(message);
break;
case MapMessage.CreateMap:
CreateMap(message.MapIndex);
break;
case MapMessage.DeleteMap:
DeleteMap(message.MapIndex);
break;
default:
throw new ArgumentOutOfRangeException(nameof(message));
}
}
private void HandleTurfClick(MsgMap message)
{
/*
// Who clicked and on what tile.
Atom.Atom clicker = SS13Server.Singleton.playerManager.GetSessionByConnection(message.SenderConnection).attachedAtom;
short x = message.ReadInt16();
short y = message.ReadInt16();
if (Vector2.Distance(clicker.position, new Vector2(x * tileSpacing + (tileSpacing / 2), y * tileSpacing + (tileSpacing / 2))) > 96)
{
return; // They were too far away to click us!
}
bool Update = false;
if (IsSaneArrayPosition(x, y))
{
Update = tileArray[x, y].ClickedBy(clicker);
if (Update)
{
if (tileArray[x, y].tileState == TileState.Dead)
// Flatten the tile array.
// NetSerializer doesn't do multi-dimensional arrays.
// This is probably really expensive.
for (var x = 0; x < grid.ChunkSize; x++)
for (var y = 0; y < grid.ChunkSize; y++)
{
Tiles.Atmos.GasCell g = tileArray[x, y].gasCell;
Tiles.Tile t = GenerateNewTile(x, y, tileArray[x, y].tileType);
tileArray[x, y] = t;
tileArray[x, y].gasCell = g;
tileBuffer[x * grid.ChunkSize + y] = chunk._tiles[x, y];
}
NetworkUpdateTile(x, y);
chunkData.Add(new GameStateMapData.ChunkDatum(index, tileBuffer));
}
var gridDatum =
new GameStateMapData.GridDatum(chunkData, new MapCoordinates(grid.WorldPosition, grid.MapID));
gridDatums.Add(grid.Index, gridDatum);
}
*/
var mapDeletionsData = _mapDeletionHistory.Where(d => d.tick >= fromTick).Select(d => d.mapId).ToList();
var gridDeletionsData = _gridDeletionHistory.Where(d => d.tick >= fromTick).Select(d => d.gridId).ToList();
var mapCreations = _maps.Values.Where(m => m.CreatedTick >= fromTick)
.ToDictionary(m => m.Index, m => m.DefaultGrid.Index);
var gridCreations = _grids.Values.Where(g => g.CreatedTick >= fromTick).ToDictionary(g => g.Index,
grid => new GameStateMapData.GridCreationDatum(grid.ChunkSize, grid.SnapSize,
grid.IsDefaultGrid));
return new GameStateMapData(gridDatums, gridDeletionsData, mapDeletionsData, mapCreations, gridCreations);
}
/// <summary>
/// Deserializes an IMapManager and ITileDefinitionManager from a properly formatted NetMessage.
/// </summary>
/// <param name="message">The message containing a serialized map and tileDefines.</param>
private void HandleTileMap(MsgMap message)
public void CullDeletionHistory(uint uptoTick)
{
DebugTools.Assert(_netManager.IsClient, "Why is the server calling this?");
_mapDeletionHistory.RemoveAll(t => t.tick < uptoTick);
_gridDeletionHistory.RemoveAll(t => t.tick < uptoTick);
}
_gridsReceived++;
public void ApplyGameStatePre(GameStateMapData data)
{
DebugTools.Assert(_netManager.IsClient, "Only the client should call this.");
var mapIndex = message.MapIndex;
var gridIndex = message.GridIndex;
// First we need to figure out all the NEW MAPS.
// And make their default grids too.
foreach (var (mapId, gridId) in data.CreatedMaps)
{
if (_maps.ContainsKey(mapId))
{
continue;
}
var gridCreation = data.CreatedGrids[gridId];
DebugTools.Assert(gridCreation.IsTheDefault);
var chunkSize = message.ChunkSize;
var chunkCount = message.ChunkDefs.Length;
var newMap = new Map(this, mapId);
_maps.Add(mapId, newMap);
MapCreated?.Invoke(this, new MapEventArgs(newMap));
newMap.DefaultGrid = CreateGrid(newMap.Index, gridId, gridCreation.ChunkSize, gridCreation.SnapSize);
}
if (!TryGetMap(mapIndex, out var map))
map = CreateMap(mapIndex);
// Then make all the other grids.
foreach (var (gridId, creationDatum) in data.CreatedGrids)
{
if (creationDatum.IsTheDefault || _grids.ContainsKey(gridId))
{
continue;
}
if (!map.GridExists(gridIndex))
GetMap(mapIndex).CreateGrid(gridIndex, chunkSize);
var grid = GetMap(mapIndex).GetGrid(gridIndex);
CreateGrid(data.GridData[gridId].Coordinates.MapId, gridId, creationDatum.ChunkSize,
creationDatum.SnapSize);
}
SuppressOnTileChanged = true;
var modified = new List<(int x, int y, Tile tile)>();
for (var i = 0; i < chunkCount; ++i)
// Ok good all the grids and maps exist now.
foreach (var (gridId, gridDatum) in data.GridData)
{
var chunkPos = new MapIndices(message.ChunkDefs[i].X, message.ChunkDefs[i].Y);
var chunk = grid.GetChunk(chunkPos);
var counter = 0;
for (ushort x = 0; x < chunk.ChunkSize; x++)
var grid = _grids[gridId];
if (grid.MapID != gridDatum.Coordinates.MapId)
{
for (ushort y = 0; y < chunk.ChunkSize; y++)
throw new NotImplementedException("Moving grids between maps is not yet implemented");
}
grid.WorldPosition = gridDatum.Coordinates.Position;
var modified = new List<(MapIndices position, Tile tile)>();
foreach (var chunkData in gridDatum.ChunkData)
{
var chunk = grid.GetChunk(chunkData.Index);
DebugTools.Assert(chunkData.TileData.Length == grid.ChunkSize * grid.ChunkSize);
var counter = 0;
for (ushort x = 0; x < grid.ChunkSize; x++)
for (ushort y = 0; y < grid.ChunkSize; y++)
{
var tile = (Tile)message.ChunkDefs[i].Tiles[counter];
var tile = chunkData.TileData[counter++];
if (chunk.GetTile(x, y).Tile != tile)
{
chunk.SetTile(x, y, tile);
modified.Add((x + chunk.X * chunk.ChunkSize, y + chunk.Y * chunk.ChunkSize, tile));
modified.Add((new MapIndices(chunk.X * grid.ChunkSize + x, chunk.Y * grid.ChunkSize + y), tile));
}
counter++;
}
}
if (modified.Count != 0)
{
GridChanged?.Invoke(this, new GridChangedEventArgs(grid, modified));
}
}
SuppressOnTileChanged = false;
if (modified.Count != 0)
}
public void ApplyGameStatePost(GameStateMapData data)
{
DebugTools.Assert(_netManager.IsClient, "Only the client should call this.");
foreach (var grid in data.DeletedGrids)
{
GridChanged?.Invoke(this, new GridChangedEventArgs(grid, modified));
if (_grids.ContainsKey(grid))
{
DeleteGrid(grid);
}
}
if (_gridsReceived == _gridsToReceive)
IoCManager.Resolve<IEntityManager>().MapsInitialized = true;
}
/// <summary>
/// Updates a single tile from the network message.
/// </summary>
/// <param name="message">The message containing the info.</param>
private void HandleTileUpdate(MsgMap message)
{
DebugTools.Assert(_netManager.IsClient, "Why is the server calling this?");
var x = message.SingleTurf.X;
var y = message.SingleTurf.Y;
var tile = (Tile)message.SingleTurf.Tile;
var pos = new GridCoordinates(x, y, message.GridIndex);
pos.Grid.SetTile(pos, tile);
}
private void CollectMapInfo(MsgMap message)
{
DebugTools.Assert(_netManager.IsClient, "Why is the server calling this?");
_gridsToReceive = message.MapGridsToSend;
if (_gridsReceived == _gridsToReceive)
IoCManager.Resolve<IEntityManager>().MapsInitialized = true;
foreach (var map in data.DeletedMaps)
{
if (_maps.ContainsKey(map))
{
DeleteMap(map);
}
}
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using SS14.Shared.Enums;
using SS14.Shared.Interfaces.Map;
using SS14.Shared.Interfaces.Timing;
using SS14.Shared.IoC;
using SS14.Shared.Log;
using SS14.Shared.Maths;
@@ -14,6 +15,8 @@ namespace SS14.Shared.Map
/// <inheritdoc />
public partial class MapManager : IMapManager, IPostInjectInit
{
[Dependency] private protected readonly IGameTiming _gameTiming;
/// <inheritdoc />
public IMap DefaultMap => GetMap(MapId.Nullspace);
@@ -50,6 +53,9 @@ namespace SS14.Shared.Map
private readonly Dictionary<GridId, MapGrid> _grids = new Dictionary<GridId, MapGrid>();
private readonly List<(uint tick, GridId gridId)> _gridDeletionHistory = new List<(uint, GridId)>();
private readonly List<(uint tick, MapId mapId)> _mapDeletionHistory = new List<(uint, MapId)>();
public void PostInject()
{
CreateMap(MapId.Nullspace, GridId.Nullspace);
@@ -58,14 +64,13 @@ namespace SS14.Shared.Map
/// <inheritdoc />
public void Initialize()
{
_netManager.RegisterNetMessage<MsgMap>(MsgMap.NAME, HandleNetworkMessage);
_netManager.RegisterNetMessage<MsgMapReq>(MsgMapReq.NAME, message => SendMap(message.MsgChannel));
// So uh I removed the contents from this but I'm too lazy to remove the Initialize method.
// Deal with it.
}
public void Startup()
{
_gridsToReceive = -1;
_gridsReceived = 0;
// Ditto, removed contents but too lazy to remove method.
}
public void Shutdown()
@@ -92,22 +97,6 @@ namespace SS14.Shared.Map
return;
TileChanged?.Invoke(this, new TileChangedEventArgs(tileRef, oldTile));
if (_netManager.IsClient)
return;
var message = _netManager.CreateNetMessage<MsgMap>();
message.MessageType = MapMessage.TurfUpdate;
message.SingleTurf = new MsgMap.Turf
{
X = tileRef.X,
Y = tileRef.Y,
Tile = (uint)tileRef.Tile
};
message.GridIndex = tileRef.LocalPos.GridID;
_netManager.ServerSendToAll(message);
}
@@ -131,12 +120,7 @@ namespace SS14.Shared.Map
if (_netManager.IsClient)
return;
var msg = _netManager.CreateNetMessage<MsgMap>();
msg.MessageType = MapMessage.DeleteMap;
msg.MapIndex = mapID;
_netManager.ServerSendToAll(msg);
_mapDeletionHistory.Add((_gameTiming.CurTick, mapID));
}
/// <inheritdoc />
@@ -171,16 +155,6 @@ namespace SS14.Shared.Map
MapCreated?.Invoke(this, new MapEventArgs(newMap));
newMap.DefaultGrid = CreateGrid(newMap.Index, defaultGridID);
if (_netManager.IsClient)
return newMap;
var msg = _netManager.CreateNetMessage<MsgMap>();
msg.MessageType = MapMessage.CreateMap;
msg.MapIndex = newMap.Index;
_netManager.ServerSendToAll(msg);
return newMap;
}
@@ -275,6 +249,12 @@ namespace SS14.Shared.Map
_grids.Remove(grid.Index);
OnGridRemoved?.Invoke(gridID);
if (_netManager.IsClient)
{
return;
}
_gridDeletionHistory.Add((_gameTiming.CurTick, gridID));
}
}
@@ -332,12 +312,12 @@ namespace SS14.Shared.Map
/// </summary>
public IMapGrid Grid { get; }
public IReadOnlyCollection<(int x, int y, Tile tile)> Modified { get; }
public IReadOnlyCollection<(MapIndices position, Tile tile)> Modified { get; }
/// <summary>
/// Creates a new instance of this class.
/// </summary>
public GridChangedEventArgs(IMapGrid grid, IReadOnlyCollection<(int x, int y, Tile tile)> modified)
public GridChangedEventArgs(IMapGrid grid, IReadOnlyCollection<(MapIndices position, Tile tile)> modified)
{
Grid = grid;
Modified = modified;

View File

@@ -1,135 +0,0 @@
using Lidgren.Network;
using SS14.Shared.Enums;
using SS14.Shared.Interfaces.Network;
using SS14.Shared.Map;
namespace SS14.Shared.Network.Messages
{
public class MsgMap : NetMessage
{
#region REQUIRED
public const MsgGroups Group = MsgGroups.Entity;
public static readonly string NAME = nameof(MsgMap);
public MsgMap(INetChannel channel) : base(NAME, Group) { }
#endregion
public MapMessage MessageType { get; set; }
public MapId MapIndex { get; set; }
public GridId GridIndex { get; set; }
public Turf SingleTurf { get; set; }
public ChunkDef[] ChunkDefs { get; set; }
public ushort ChunkSize { get; set; }
public int MapGridsToSend { get; set; }
public class Turf
{
public int X { get; set; }
public int Y { get; set; }
public uint Tile { get; set; }
}
public class ChunkDef
{
public int X { get; set; }
public int Y { get; set; }
public uint[] Tiles { get; set; }
}
public override void ReadFromBuffer(NetIncomingMessage buffer)
{
MessageType = (MapMessage) buffer.ReadByte();
switch (MessageType)
{
case MapMessage.TurfUpdate:
MapIndex = new MapId(buffer.ReadInt32());
GridIndex = new GridId(buffer.ReadInt32());
SingleTurf = new Turf()
{
X = buffer.ReadInt32(),
Y = buffer.ReadInt32(),
Tile = buffer.ReadUInt32()
};
break;
case MapMessage.SendTileMap:
GridIndex = new GridId(buffer.ReadInt32());
MapIndex = new MapId(buffer.ReadInt32());
// map chunks
ChunkSize = buffer.ReadUInt16();
var numChunks = buffer.ReadInt32();
ChunkDefs = new ChunkDef[numChunks];
for (var i = 0; i < numChunks; i++)
{
var newChunk = new ChunkDef()
{
X = buffer.ReadInt32(),
Y = buffer.ReadInt32()
};
var chunkCount = ChunkSize * ChunkSize;
var tiles = new uint[chunkCount];
for (var j = 0; j < chunkCount; j++)
{
tiles[j] = buffer.ReadUInt32();
}
newChunk.Tiles = tiles;
ChunkDefs[i] = newChunk;
}
break;
case MapMessage.SendMapInfo:
MapGridsToSend = buffer.ReadInt32();
break;
case MapMessage.CreateMap:
case MapMessage.DeleteMap:
MapIndex = new MapId(buffer.ReadInt32());
break;
}
}
public override void WriteToBuffer(NetOutgoingMessage buffer)
{
buffer.Write((byte)MessageType);
switch (MessageType)
{
case MapMessage.TurfUpdate:
buffer.Write((int)MapIndex);
buffer.Write((int)GridIndex);
buffer.Write(SingleTurf.X);
buffer.Write(SingleTurf.Y);
buffer.Write(SingleTurf.Tile);
break;
case MapMessage.SendTileMap:
buffer.Write((int)GridIndex);
buffer.Write((int)MapIndex);
// Map chunks
buffer.Write(ChunkSize);
buffer.Write(ChunkDefs.Length);
foreach (var chunk in ChunkDefs)
{
buffer.Write(chunk.X);
buffer.Write(chunk.Y);
// ordered list
foreach (var tile in chunk.Tiles)
buffer.Write(tile);
}
break;
case MapMessage.SendMapInfo:
buffer.Write(MapGridsToSend);
break;
case MapMessage.CreateMap:
case MapMessage.DeleteMap:
buffer.Write((int)MapIndex);
break;
}
}
}
}

View File

@@ -1,22 +0,0 @@
using Lidgren.Network;
using SS14.Shared.Interfaces.Network;
namespace SS14.Shared.Network.Messages
{
public class MsgMapReq : NetMessage
{
#region REQUIRED
public static readonly MsgGroups GROUP = MsgGroups.Entity;
public static readonly string NAME = nameof(MsgMapReq);
public MsgMapReq(INetChannel channel) : base(NAME, GROUP) { }
#endregion
public override void ReadFromBuffer(NetIncomingMessage buffer)
{
}
public override void WriteToBuffer(NetOutgoingMessage buffer)
{
}
}
}

View File

@@ -129,6 +129,7 @@
<Compile Include="GameObjects\EntityUid.cs" />
<Compile Include="GameObjects\IncomingEntityMessage.cs" />
<Compile Include="GameObjects\NetComponent.cs" />
<Compile Include="GameStates\GameStateMapData.cs" />
<Compile Include="Input\InputCmdContext.cs" />
<Compile Include="Input\CommandBindMapping.cs" />
<Compile Include="Input\InputCmdMessage.cs" />
@@ -210,8 +211,6 @@
<Compile Include="Network\Messages\MsgConCmdAck.cs" />
<Compile Include="Network\Messages\MsgConCmdReg.cs" />
<Compile Include="Network\Messages\MsgEntity.cs" />
<Compile Include="Network\Messages\MsgMap.cs" />
<Compile Include="Network\Messages\MsgMapReq.cs" />
<Compile Include="Network\Messages\MsgPlacement.cs" />
<Compile Include="Network\Messages\MsgPlayerList.cs" />
<Compile Include="Network\Messages\MsgPlayerListReq.cs" />