mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-09-06 19:44:15 -04:00
442 lines
17 KiB
C#
442 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using OpenDiablo2.Common;
|
|
using OpenDiablo2.Common.Enums;
|
|
using OpenDiablo2.Common.Exceptions;
|
|
using OpenDiablo2.Common.Interfaces;
|
|
using OpenDiablo2.Common.Models;
|
|
using OpenDiablo2.Core.Map_Engine;
|
|
|
|
namespace OpenDiablo2.Core.GameState_
|
|
{
|
|
public sealed class GameState : IGameState
|
|
{
|
|
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
|
|
|
|
private readonly ISceneManager sceneManager;
|
|
private readonly IResourceManager resourceManager;
|
|
private readonly IPaletteProvider paletteProvider;
|
|
private readonly IEngineDataManager engineDataManager;
|
|
private readonly IRenderWindow renderWindow;
|
|
private readonly Func<IMapEngine> getMapEngine;
|
|
private readonly Func<eSessionType, ISessionManager> getSessionManager;
|
|
|
|
private float animationTime = 0f;
|
|
private List<MapInfo> mapInfo;
|
|
private readonly List<MapCellInfo> mapDataLookup = new List<MapCellInfo>();
|
|
private ISessionManager sessionManager;
|
|
|
|
public int Act { get; private set; }
|
|
public string MapName { get; private set; }
|
|
public Palette CurrentPalette => paletteProvider.PaletteTable[$"ACT{Act}"];
|
|
public List<PlayerInfo> PlayerInfos { get; private set; } = new List<PlayerInfo>();
|
|
|
|
readonly private IMouseCursor originalMouseCursor;
|
|
|
|
public int Seed { get; internal set; }
|
|
|
|
public Item SelectedItem { get; internal set; }
|
|
public object ThreadLocker { get; } = new object();
|
|
|
|
public int CameraOffset { get; set; } = 0;
|
|
|
|
IEnumerable<PlayerInfo> IGameState.PlayerInfos => PlayerInfos;
|
|
|
|
const double Deg2Rad = Math.PI / 180.0;
|
|
|
|
public GameState(
|
|
ISceneManager sceneManager,
|
|
IResourceManager resourceManager,
|
|
IPaletteProvider paletteProvider,
|
|
IEngineDataManager engineDataManager,
|
|
IRenderWindow renderWindow,
|
|
Func<IMapEngine> getMapEngine,
|
|
Func<eSessionType, ISessionManager> getSessionManager
|
|
)
|
|
{
|
|
this.sceneManager = sceneManager;
|
|
this.resourceManager = resourceManager;
|
|
this.paletteProvider = paletteProvider;
|
|
this.getMapEngine = getMapEngine;
|
|
this.getSessionManager = getSessionManager;
|
|
this.engineDataManager = engineDataManager;
|
|
this.renderWindow = renderWindow;
|
|
|
|
this.originalMouseCursor = renderWindow.MouseCursor;
|
|
|
|
}
|
|
|
|
public void Initialize(string characterName, eHero hero, eSessionType sessionType)
|
|
{
|
|
sessionManager = getSessionManager(sessionType);
|
|
sessionManager.Initialize();
|
|
|
|
sessionManager.OnSetSeed += OnSetSeedEvent;
|
|
sessionManager.OnLocatePlayers += OnLocatePlayers;
|
|
sessionManager.OnPlayerInfo += OnPlayerInfo;
|
|
sessionManager.OnFocusOnPlayer += OnFocusOnPlayer;
|
|
|
|
mapInfo = new List<MapInfo>();
|
|
sceneManager.ChangeScene(eSceneType.Game);
|
|
|
|
sessionManager.JoinGame(characterName, hero);
|
|
}
|
|
|
|
private void OnFocusOnPlayer(int clientHash, int playerId)
|
|
=> getMapEngine().FocusedPlayerId = playerId;
|
|
|
|
private void OnPlayerInfo(int clientHash, IEnumerable<PlayerInfo> playerInfo)
|
|
=> this.PlayerInfos = playerInfo.ToList();
|
|
|
|
private void OnLocatePlayers(int clientHash, IEnumerable<PlayerLocationDetails> playerLocationDetails)
|
|
{
|
|
foreach (var player in PlayerInfos)
|
|
{
|
|
var details = playerLocationDetails.FirstOrDefault(x => x.PlayerId == player.LocationDetails.PlayerId);
|
|
if (details == null)
|
|
continue;
|
|
|
|
player.LocationDetails = details;
|
|
}
|
|
}
|
|
|
|
private void OnSetSeedEvent(int clientHash, int seed)
|
|
{
|
|
log.Info($"Setting seed to {seed}");
|
|
this.Seed = seed;
|
|
new MapGenerator(this).Generate();
|
|
}
|
|
|
|
public MapInfo LoadSubMap(int levelDefId, Point origin)
|
|
{
|
|
var level = engineDataManager.LevelPresets.First(x => x.Def == levelDefId);
|
|
var levelDetails = engineDataManager.LevelDetails.First(x => x.Id == level.LevelId);
|
|
var levelType = engineDataManager.LevelTypes.First(x => x.Id == levelDetails.LevelType);
|
|
|
|
// Some maps have variations, so lets pick a random one
|
|
var mapNames = new List<string>();
|
|
if (level.File1 != "0") mapNames.Add(level.File1);
|
|
if (level.File2 != "0") mapNames.Add(level.File2);
|
|
if (level.File3 != "0") mapNames.Add(level.File3);
|
|
if (level.File4 != "0") mapNames.Add(level.File4);
|
|
if (level.File5 != "0") mapNames.Add(level.File5);
|
|
if (level.File6 != "0") mapNames.Add(level.File6);
|
|
|
|
|
|
var random = new Random(Seed);
|
|
var mapName = "data\\global\\tiles\\" + mapNames[random.Next(mapNames.Count)].Replace("/", "\\");
|
|
var fileData = resourceManager.GetMPQDS1(mapName, level, levelDetails, levelType);
|
|
|
|
var result = new MapInfo
|
|
{
|
|
LevelId = eLevelId.None,
|
|
LevelPreset = level,
|
|
LevelDetail = levelDetails,
|
|
LevelType = levelType,
|
|
FileData = fileData,
|
|
CellInfo = new Dictionary<eRenderCellType, MapCellInfo[]>(),
|
|
TileLocation = new Rectangle(origin, new Size(fileData.Width - 1, fileData.Height - 1))
|
|
};
|
|
|
|
mapInfo.Add(result);
|
|
|
|
return result;
|
|
}
|
|
|
|
public MapInfo LoadMap(eLevelId levelId, Point origin)
|
|
{
|
|
var level = engineDataManager.LevelPresets.First(x => x.LevelId == (int)levelId);
|
|
var levelDetails = engineDataManager.LevelDetails.First(x => x.Id == level.LevelId);
|
|
var levelType = engineDataManager.LevelTypes.First(x => x.Id == levelDetails.LevelType);
|
|
|
|
// Some maps have variations, so lets pick a random one
|
|
var mapNames = new List<string>();
|
|
if (level.File1 != "0") mapNames.Add(level.File1);
|
|
if (level.File2 != "0") mapNames.Add(level.File2);
|
|
if (level.File3 != "0") mapNames.Add(level.File3);
|
|
if (level.File4 != "0") mapNames.Add(level.File4);
|
|
if (level.File5 != "0") mapNames.Add(level.File5);
|
|
if (level.File6 != "0") mapNames.Add(level.File6);
|
|
|
|
|
|
var random = new Random(Seed);
|
|
var mapName = "data\\global\\tiles\\" + mapNames[random.Next(mapNames.Count)].Replace("/", "\\");
|
|
MapName = level.Name;
|
|
Act = levelType.Act;
|
|
|
|
var fileData = resourceManager.GetMPQDS1(mapName, level, levelDetails, levelType);
|
|
|
|
var result = new MapInfo
|
|
{
|
|
LevelId = levelId,
|
|
LevelPreset = level,
|
|
LevelDetail = levelDetails,
|
|
LevelType = levelType,
|
|
FileData = fileData,
|
|
CellInfo = new Dictionary<eRenderCellType, MapCellInfo[]>(),
|
|
TileLocation = new Rectangle(origin, new Size(fileData.Width - 1, fileData.Height - 1))
|
|
};
|
|
|
|
mapInfo.Add(result);
|
|
|
|
return result;
|
|
}
|
|
|
|
public IEnumerable<MapCellInfo> GetMapCellInfo(int cellX, int cellY, eRenderCellType renderCellType)
|
|
{
|
|
var map = GetMap(ref cellX, ref cellY);
|
|
|
|
if (map == null)
|
|
return Enumerable.Empty<MapCellInfo>();
|
|
|
|
if (cellY >= map.FileData.Height || cellX >= map.FileData.Width)
|
|
return new List<MapCellInfo>(); // Temporary code
|
|
|
|
var idx = cellX + (cellY * map.FileData.Width);
|
|
|
|
switch (renderCellType)
|
|
{
|
|
case eRenderCellType.Floor:
|
|
return map.FileData.FloorLayers
|
|
.Select(floorLayer => GetMapCellInfo(map, cellX, cellY, floorLayer.Props[idx], eRenderCellType.Floor))
|
|
.Where(x => x != null);
|
|
|
|
case eRenderCellType.WallUpper:
|
|
case eRenderCellType.WallLower:
|
|
case eRenderCellType.Roof:
|
|
return map.FileData.WallLayers
|
|
.Select(wallLayer => GetMapCellInfo(map, cellX, cellY, wallLayer.Props[idx], renderCellType, wallLayer.Orientations[idx]))
|
|
.Where(x => x != null);
|
|
|
|
default:
|
|
throw new OpenDiablo2Exception("Unknown render cell type!");
|
|
}
|
|
}
|
|
|
|
private MapInfo GetMap(ref int cellX, ref int cellY)
|
|
{
|
|
var x = cellX;
|
|
var y = cellY;
|
|
var map = mapInfo.FirstOrDefault(z => (x >= z.TileLocation.X) && (y >= z.TileLocation.Y)
|
|
&& (x < z.TileLocation.Right) && (y < z.TileLocation.Bottom));
|
|
if (map == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
cellX -= map.TileLocation.X;
|
|
cellY -= map.TileLocation.Y;
|
|
return map;
|
|
}
|
|
|
|
public void UpdateMapCellInfo(int cellX, int cellY, eRenderCellType renderCellType, IEnumerable<MapCellInfo> mapCellInfo)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void SelectItem(Item item)
|
|
{
|
|
if (item == null)
|
|
{
|
|
renderWindow.MouseCursor = this.originalMouseCursor;
|
|
}
|
|
else
|
|
{
|
|
var cursorsprite = renderWindow.LoadSprite(ResourcePaths.GeneratePathForItem(item.InvFile), Palettes.Units);
|
|
|
|
renderWindow.MouseCursor = renderWindow.LoadCursor(cursorsprite, 0, new Point(cursorsprite.FrameSize.Width / 2, cursorsprite.FrameSize.Height / 2));
|
|
}
|
|
|
|
this.SelectedItem = item;
|
|
}
|
|
|
|
private MapCellInfo GetMapCellInfo(MapInfo map, int cellX, int cellY, MPQDS1TileProps props, eRenderCellType cellType, MPQDS1WallOrientationTileProps wallOrientations = null)
|
|
{
|
|
if (!map.CellInfo.ContainsKey(cellType))
|
|
{
|
|
map.CellInfo[cellType] = new MapCellInfo[map.FileData.Width * map.FileData.Height];
|
|
}
|
|
|
|
var cellInfo = map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)];
|
|
if (cellInfo != null)
|
|
return cellInfo.Ignore ? null : cellInfo;
|
|
|
|
|
|
var sub_index = props.Prop2;
|
|
var main_index = (props.Prop3 >> 4) + ((props.Prop4 & 0x03) << 4);
|
|
var orientation = 0;
|
|
|
|
// Floors can't have rotations, should we blow up here?
|
|
if (cellType == eRenderCellType.Floor && props.Prop1 == 0)
|
|
{
|
|
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
|
|
return null;
|
|
}
|
|
|
|
if (cellType == eRenderCellType.Roof)
|
|
{
|
|
if (orientation != 15) // Only 15 (roof)
|
|
{
|
|
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
|
|
return null;
|
|
}
|
|
|
|
if (props.Prop1 == 0)
|
|
{
|
|
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
|
|
return null;
|
|
}
|
|
|
|
if (((props.Prop4 & 0x80) > 0) && (orientation != 10 && orientation != 11))
|
|
{
|
|
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
|
|
return null;
|
|
}
|
|
|
|
}
|
|
if (cellType == eRenderCellType.WallUpper || cellType == eRenderCellType.WallLower)
|
|
{
|
|
orientation = wallOrientations.Orientation1;
|
|
|
|
|
|
if (props.Prop1 == 0)
|
|
{
|
|
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
|
|
return null;
|
|
}
|
|
|
|
// < 15 shouldn't happen for upper wall types, should we even check for this?
|
|
if (cellType == eRenderCellType.WallUpper && orientation <= 15)
|
|
{
|
|
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
|
|
return null;
|
|
}
|
|
|
|
// TODO: Support special walls
|
|
if (orientation == 10 || orientation == 11)
|
|
{
|
|
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
|
|
return null;
|
|
}
|
|
|
|
// This is also a thing apparently
|
|
if (((props.Prop4 & 0x80) > 0) && (orientation != 10 && orientation != 11))
|
|
{
|
|
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
|
|
return null;
|
|
}
|
|
}
|
|
|
|
int frame = 0;
|
|
var tiles = (map.PrimaryMap ?? map).FileData.LookupTable
|
|
.Where(x => x.MainIndex == main_index && x.SubIndex == sub_index && x.Orientation == orientation)
|
|
.Select(x => x.TileRef);
|
|
|
|
if (!tiles.Any())
|
|
{
|
|
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
|
|
return null;
|
|
}
|
|
|
|
MPQDT1Tile tile = null;
|
|
if (tiles.First().Animated)
|
|
{
|
|
#if DEBUG
|
|
if (!tiles.All(x => x.Animated))
|
|
throw new OpenDiablo2Exception("Some tiles are animated and some aren't...");
|
|
#endif
|
|
var frameIndex = (int)Math.Floor(tiles.Count() * animationTime);
|
|
tile = tiles.ElementAt(frameIndex);
|
|
}
|
|
else
|
|
{
|
|
if (tiles.Any())
|
|
{
|
|
var totalRarity = tiles.Sum(q => q.RarityOrFrameIndex);
|
|
var random = new Random(Seed + cellX + (map.FileData.Width * cellY));
|
|
var x = random.Next(totalRarity);
|
|
var z = 0;
|
|
foreach (var t in tiles)
|
|
{
|
|
z += t.RarityOrFrameIndex;
|
|
if (x <= z)
|
|
{
|
|
tile = t;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (tile.Animated)
|
|
throw new OpenDiablo2Exception("Why are we randomly finding an animated tile? Something's wrong here.");
|
|
}
|
|
else tile = tiles.First();
|
|
}
|
|
|
|
// This WILL happen to you
|
|
if (tile.Width == 0 || tile.Height == 0)
|
|
{
|
|
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
|
|
return null;
|
|
}
|
|
|
|
|
|
if (tile.BlockDataLength == 0)
|
|
{
|
|
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
|
|
return null; // Why is this a thing?
|
|
}
|
|
|
|
|
|
var mapCellInfo = mapDataLookup.FirstOrDefault(x => x.Tile.Id == tile.Id && x.AnimationId == frame);
|
|
if (mapCellInfo == null)
|
|
{
|
|
mapCellInfo = renderWindow.CacheMapCell(tile);
|
|
mapDataLookup.Add(mapCellInfo);
|
|
|
|
switch (cellType)
|
|
{
|
|
case eRenderCellType.WallUpper:
|
|
case eRenderCellType.WallLower:
|
|
mapCellInfo.OffY -= 80;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = mapCellInfo;
|
|
return mapCellInfo;
|
|
}
|
|
|
|
public void Update(long ms)
|
|
{
|
|
animationTime += ms / 1000f;
|
|
animationTime -= (float)Math.Truncate(animationTime);
|
|
var seconds = ms / 1000f;
|
|
|
|
foreach (var player in PlayerInfos)
|
|
UpdatePlayer(player, seconds);
|
|
}
|
|
|
|
private void UpdatePlayer(PlayerInfo player, float seconds)
|
|
{
|
|
if (player.LocationDetails.MovementType == eMovementType.Stopped)
|
|
return;
|
|
|
|
var rads = (float)player.LocationDetails.MovementDirection * 22 * (float)Deg2Rad;
|
|
|
|
var moveX = (float)Math.Cos(rads) * seconds * player.LocationDetails.MovementSpeed;
|
|
var moveY = (float)Math.Sin(rads) * seconds * player.LocationDetails.MovementSpeed;
|
|
|
|
player.LocationDetails.PlayerX += moveX;
|
|
player.LocationDetails.PlayerY += moveY;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
sessionManager?.Dispose();
|
|
}
|
|
}
|
|
}
|