building profiling tools and refactoring networking

This commit is contained in:
j0sh.oleary11
2020-12-21 22:04:30 -08:00
parent 5f75be5a72
commit a15a6a5402
71 changed files with 2336 additions and 1380 deletions

View File

@@ -49,7 +49,7 @@
<TrimmerRootAssembly Include="Microsoft.Xna.Framework.Content.ContentTypeReader" Visible="false" />
</ItemGroup>
<ItemGroup>
<None Update="Assets\Scripts\MainMenu.lua">
<!--None Update="Assets\Scripts\MainMenu.lua">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\Scripts\menu.lua">
@@ -57,8 +57,8 @@
</None>
<None Update="Assets\Scripts\SingleplayerMenu.lua">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Assets\Textures\icon.png">
</None-->
<None Update="assets\Textures\icon.png">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
@@ -89,9 +89,9 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Folder Include="Assets\Sound\mu\" />
<Folder Include="assets\sound\mu\" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="xcopy &quot;$(ProjectDir)Assets\*.*&quot; &quot;$(TargetDir)Assets\\&quot; /E /I /F /Y" />
<Exec Command="xcopy &quot;$(ProjectDir)assets\*.*&quot; &quot;$(TargetDir)assets\\&quot; /E /I /F /Y" />
</Target>
</Project>

View File

@@ -15,35 +15,28 @@ using CaveGame.Server;
using System.Threading.Tasks;
using CaveGame.Core.Game.Items;
using CaveGame.Core.Inventory;
using DataManagement;
namespace CaveGame.Client
{
public class CaveGameGL : Microsoft.Xna.Framework.Game
{
// TODO: If running local server, shutdown server when player leaves the world
public GameSettings GameSettings { get; set; }
public IGameContext CurrentGameContext { get; set; }
private IGameContext PreviousGameContext { get; set; }
#region Game States
public GameClient GameClientContext;
public Menu.MenuManager MenuContext;
public Menu.Settings SettingsContext;
#endregion
public GameClient GameClientContext { get; private set; }
public Menu.MenuManager MenuContext { get; private set; }
public Menu.Settings SettingsContext { get; private set; }
public GraphicsEngine GraphicsEngine { get; private set; }
public GraphicsDeviceManager GraphicsDeviceManager { get; private set; }
public SpriteBatch SpriteBatch { get; private set; }
#region GameComponents
public CommandBar Console { get; private set; }
public CommandBar Console { get; private set; } // TOOD: Change Name of CommandBar class to Console
public FrameCounter FPSCounter { get; private set; }
public Splash Splash { get; private set; }
#endregion
public static float ClickTimer;
public SteamManager SteamManager { get; private set; }
public static float ClickTimer { get; set; }
public void OnSetFPSLimit(int limit)
{
@@ -57,7 +50,7 @@ namespace CaveGame.Client
}
}
public void OnSetChatSize(GameChatSize size) {}
public void OnSetChatSize(GameChatSize size) { }
public void OnSetFullscreen(bool full)
{
@@ -78,32 +71,45 @@ namespace CaveGame.Client
// join local (singleplayer server
public void EnterLocalGame(WorldMetadata meta)
{
{
var serverCFG = new ServerConfig
{
Port = 40270, // singleplayer server uses slightly different port
World = meta.Name,
ServerName = $"LocalServer [{meta.Name}] ",
ServerName = $"LocalServer [{meta.Name}] ",
ServerMOTD = "Singleplayer game world.",
};
var worldMDT = meta;
LocalServer server = new LocalServer(serverCFG, worldMDT);
server.Output = Console;
Task.Run(() => {
server.Start();
server.Run();
});
this.CurrentGameContext = this.GameClientContext;
this.GameClientContext.NetworkUsername = "Player";
this.GameClientContext.ConnectAddress = "127.0.0.1:40270";
}
Task.Run(server.Start);
StartClient(SteamManager.SteamUsername, "127.0.0.1:40270");
CurrentGameContext = GameClientContext;
GameClientContext.OnShutdown += server.Shutdown;
}
public void StartClient(string userName, string address)
{
GameClientContext?.Dispose();
GameClientContext = new GameClient(this);
GameClientContext.NetworkUsername = userName;
GameClientContext.ConnectAddress = address;
CurrentGameContext = GameClientContext;
}
public CaveGameGL()
{
IsMouseVisible = true;
Content.RootDirectory = "Assets";
Window.AllowUserResizing = true;
Window.AllowAltF4 = true;
GameSettings = Configuration.Load<GameSettings>("settings.xml", true);
SteamManager = new SteamManager(this);
GraphicsDeviceManager = new GraphicsDeviceManager(this)
{
PreferredBackBufferWidth = 1280,
@@ -113,41 +119,29 @@ namespace CaveGame.Client
PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8
};
IsMouseVisible = true;
Content.RootDirectory = "Assets";
Window.AllowUserResizing = true;
Window.AllowAltF4 = true;
GraphicsEngine = new GraphicsEngine();
Splash = new Splash();
FPSCounter = new FrameCounter(this);
Components.Add(FPSCounter);
#if DEBUG
GraphicsEngine.LoadingDelay = 0.05f;
Splash.SplashTimer = 5f;
Splash.SplashTimer = 3f;
#endif
OnSetFPSLimit(GameSettings.FPSLimit);
}
void Window_ClientSizeChanged(object sender, EventArgs e)
{
GraphicsEngine.WindowSize = Window.ClientBounds.Size.ToVector2();
if (GameClientContext != null)
{
GameClientContext.Camera.Bounds = Window.ClientBounds;
GameClientContext.Camera._screenSize = new Vector2(Window.ClientBounds.Width, Window.ClientBounds.Height);
}
}
void Window_ClientSizeChanged(object sender, EventArgs e) => GraphicsEngine.WindowSize = Window.ClientBounds.Size.ToVector2();
#region GameConsole Commands
private void OnTeleportCommand(CommandBar sender, Command command, params string[] args)
{
if (args.Length < 2)
{
sender.Out("Please provide a valid coordinate!", Color.Red);
return;
}
@@ -182,7 +176,7 @@ namespace CaveGame.Client
{
if (CurrentGameContext == GameClientContext)
{
GameClientContext.OverrideDisconnect();
GameClientContext.Disconnect();
} else
{
@@ -201,7 +195,6 @@ namespace CaveGame.Client
TakeScreenshot();
sender.Out("Screenshot taken!");
}
}
private void SendAdminCommand(string command, params string[] args) => GameClientContext.Send(new AdminCommandPacket(command, args, GameClientContext.MyPlayer.EntityNetworkID));
private void CmdTimeCommand(CommandBar sender, Command command, params string[] args)
@@ -245,16 +238,22 @@ namespace CaveGame.Client
}
sender.Out("No item found with matching name!");
}
#endregion
protected override void Initialize()
public void GoToMainMenu()
{
Window.TextInput += TextInputManager.OnTextInput;
Window.ClientSizeChanged += new EventHandler<EventArgs>(Window_ClientSizeChanged);
OnSetFullscreen(GameSettings.Fullscreen);
Console = new CommandBar(this);
CurrentGameContext = MenuContext;
MenuContext.CurrentPage = MenuContext.Pages["mainmenu"];
}
public void GoToTimeoutPage(string timeout)
{
CurrentGameContext = MenuContext;
MenuContext.CurrentPage = MenuContext.Pages["timeoutmenu"];
MenuContext.TimeoutMessage = timeout;
}
private void InitCommands()
{
// epic new .NET 5 feature
Command[] commands =
{
@@ -266,21 +265,26 @@ namespace CaveGame.Client
new ("time", "Set/Get time of day", new List<string> { "time" }, CmdTimeCommand),
new ("sv_summon", "Summon an entity", new List<string>{"entityid, xpos, ypos, metadatastring" }, CmdRequestSummonEntity),
new ("gimme", "Gives you an item", new List<string>{"itemid", "amount"}, CmdRequestItemstack),
};
foreach (var command in commands)
Console.BindCommandInformation(command);
commands.ForEach(c => Console.BindCommandInformation(c));
//commands.ForEach(Console.BindCommandInformation);
}
protected override void Initialize()
{
Window.TextInput += TextInputManager.OnTextInput;
Window.ClientSizeChanged += new EventHandler<EventArgs>(Window_ClientSizeChanged);
OnSetFullscreen(GameSettings.Fullscreen);
Console = new CommandBar(this);
InitCommands();
GameConsole.SetInstance(Console);
Components.Add(Console);
FPSCounter = new FrameCounter(this);
Components.Add(FPSCounter);
SteamManager.Initialize();
Components.Add(SteamManager);
base.Initialize();
}
@@ -300,6 +304,7 @@ namespace CaveGame.Client
MenuContext = new MenuManager(this);
GameClientContext = new GameClient(this);
SettingsContext = new Settings(this);
Window.TextInput += SettingsContext.OnTextInput;
CurrentGameContext = MenuContext;
}

View File

@@ -47,6 +47,8 @@ namespace CaveGame.Client
public class SteamManager: GameComponent, ISteamManager
{
// api members
public bool HasAchievement(GameSteamAchievement achievement)
{
@@ -63,6 +65,7 @@ namespace CaveGame.Client
//
public SteamManager Instance { get; set; }
public string SteamUsername => SteamEnabled ? SteamFriends.GetPersonaName() : "Player1";
public bool SteamEnabled { get; set; }
public bool SteamInitialized { get; private set; }
@@ -71,7 +74,7 @@ namespace CaveGame.Client
Microsoft.Xna.Framework.Game game;
bool receivedUserStats;
DelayedTask callbackRun;
RepeatingIntervalTask callbackRun;
public SteamManager(Microsoft.Xna.Framework.Game _game) : base(_game)
{
@@ -90,7 +93,7 @@ namespace CaveGame.Client
if (SteamInitialized)
{
callbackRun = new DelayedTask(() => Steamworks.SteamAPI.RunCallbacks(), 1 / 20.0f);
callbackRun = new RepeatingIntervalTask(() => Steamworks.SteamAPI.RunCallbacks(), 1 / 20.0f);
m_OverlayActivated = Steamworks.Callback<Steamworks.GameOverlayActivated_t>.Create(Steam_OnOverlayActivated);
Steamworks.Callback<Steamworks.SteamShutdown_t>.Create(Steam_OnShutdown);
Steamworks.Callback<Steamworks.ScreenshotRequested_t>.Create(Steam_OnScreenshotRequested);

View File

@@ -9,12 +9,13 @@
<Import_RootNamespace>CaveGame.Client</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)DebugTools\DebugInformation.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DebugTools\GraphRenderer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DebugTools\Profiler.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GameSounds.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DebugTools\ChunkGridLineRenderer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GameSettings.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Game\Entities\LocalPlayer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Game\Entities\PeerPlayer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Game\Entities\ClientPlayer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)InputManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Input\KeyPressDetector.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Menu\Multiplayer.cs" />
@@ -28,7 +29,7 @@
<Compile Include="$(MSBuildThisFileDirectory)LocalWorld.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Menu\Settings.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Menu\UITheme.cs" />
<Compile Include="$(MSBuildThisFileDirectory)NetworkClient.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Network\NetworkClient.cs" />
<Compile Include="$(MSBuildThisFileDirectory)IGameContext.cs" />
<Compile Include="$(MSBuildThisFileDirectory)PauseMenu.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SavedWorldManager.cs" />
@@ -37,6 +38,8 @@
<Compile Include="$(MSBuildThisFileDirectory)UI\ScrollRect.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UI\UINode.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UI\UIRect.cs" />
<Compile Include="$(MSBuildThisFileDirectory)World\ChunkRenderer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)World\Sky.cs" />
<Compile Include="..\Client\Menu\MenuManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)PlayerContainerFrontend.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UI\Label.cs" />
@@ -52,4 +55,7 @@
<ItemGroup>
<MonoGameContentReference Include="$(MSBuildThisFileDirectory)Content\Content.mgcb" />
</ItemGroup>
<ItemGroup>
<Folder Include="$(MSBuildThisFileDirectory)Network\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CaveGame.Client.DebugTools
{
class DebugInformation
{
}
}

View File

@@ -0,0 +1,111 @@
using CaveGame.Core;
using DataManagement;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Text;
namespace CaveGame.Client.DebugTools
{
public interface GraphSample {
double Value { get; set; }
}
public interface GraphDataset<TSample> where TSample: GraphSample {
Color Color { get; set; }
int SampleCount { get; set; }
List<TSample> Data { get; set; }
}
public class GraphRecorder<TSample>: GraphDataset<TSample> where TSample : GraphSample
{
public Color Color { get; set; }
public int SampleCount { get; set; }
public List<TSample> Data { get; set; }
public TSample Average;
public GraphRecorder()
{
Data = new List<TSample>();
}
public void Push(TSample sample)
{
Data.Add(sample);
if (Data.Count > SampleCount)
{
Data.RemoveAt(0);
}
}
}
public class GraphRenderer<TSample> where TSample : GraphSample
{
public Vector2 ScreenPosition { get; set; }
public Vector2 GraphSize { get; set; }
public float XAxisMin { get; set; }
public float XAxisMax { get; set; }
public float YAxisMin { get; set; }
public float YAxisMax { get; set; }
public string GraphName { get; set; }
public float Scale { get; set; }
public Color BackgroundColor { get; set; }
public GraphRecorder<TSample> DataSet { get; set; }
private void DrawBackground(GraphicsEngine GFX)
{
GFX.Rect(BackgroundColor, ScreenPosition, GraphSize);
GFX.Text(GraphName, ScreenPosition - GFX.Fonts.Arial10.MeasureString(GraphName).GetY());
}
private double ScaleDatapoint(TSample datapoint)
{
return datapoint.Value * Scale;
}
private void DrawReferenceLines(GraphicsEngine GFX)
{
Vector2 datapoint = new Vector2(0, GraphSize.Y - (float)Scale * (1 / 60.0f));
Vector2 linePos = ScreenPosition + datapoint;
GFX.Line(new Color(0.7f, 0.7f, 0.7f), linePos, linePos+GraphSize.GetX());
GFX.Text(GFX.Fonts.Arial10, "60fps", ScreenPosition + datapoint.GetY(), Color.White, TextXAlignment.Right, TextYAlignment.Center );
}
private void DrawLineGraph(GraphicsEngine GFX, GraphDataset<TSample> data)
{
Vector2 lastDatapoint = GraphSize.GetY();
float spacing = GraphSize.X / data.SampleCount;
int start = Math.Max(data.Data.Count - data.SampleCount, 0);
for (int idx = start; idx < data.Data.Count; idx++)
{
Vector2 datapoint = new Vector2(spacing * idx, GraphSize.Y - (float)ScaleDatapoint(data.Data[idx]));
GFX.Line(data.Color, ScreenPosition + lastDatapoint, ScreenPosition + datapoint);
lastDatapoint = datapoint;
}
}
public void Draw(GraphicsEngine GFX)
{
DrawBackground(GFX);
DrawReferenceLines(GFX);
DrawLineGraph(GFX, DataSet);
}
}
}

View File

@@ -0,0 +1,133 @@
using CaveGame.Core;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace CaveGame.Client.DebugTools
{
public class ProfilerEntry
{
public string Name { get; set; }
public Stopwatch Timer { get; set; }
public double RecordedTime { get; set; }
public ProfilerEntry()
{
Timer = new Stopwatch();
}
}
public class ProfilerRegion
{
public string Name { get; set; }
public Stopwatch Timer { get; set; }
public double RecordedTime { get; set; }
public Dictionary<string, ProfilerEntry> Functions;
public ProfilerRegion()
{
Timer = new Stopwatch();
Functions = new Dictionary<string, ProfilerEntry>();
}
}
public static class Profiler
{
static ProfilerRegion currentRegion;
static ProfilerEntry currentFunction;
public static Dictionary<string, ProfilerRegion> Regions = new Dictionary<string, ProfilerRegion>();
public static void StartRegion(string regionName)
{
if (!Regions.ContainsKey(regionName))
Regions.Add(regionName, new ProfilerRegion { Name = regionName });
currentRegion = Regions[regionName];
//currentRegion.Timer.Reset();
currentRegion.Timer.Start();
}
public static void EndRegion(string regionName)
{
currentRegion = Regions[regionName];
currentRegion.Timer.Stop();
currentRegion.RecordedTime = currentRegion.Timer.Elapsed.TotalMilliseconds;
currentRegion.Timer.Reset();
}
public static void EndRegion()
{
if (currentRegion == null)
return;
currentRegion.Timer.Stop();
currentRegion.RecordedTime = currentRegion.Timer.Elapsed.TotalMilliseconds;
currentRegion.Timer.Reset();
}
public static void Start(string functionName)
{
if (currentRegion == null)
return;
if (!currentRegion.Functions.ContainsKey(functionName))
currentRegion.Functions.Add(functionName, new ProfilerEntry { Name = functionName });
currentFunction = currentRegion.Functions[functionName];
//currentFunction.Timer.Reset();
currentFunction.Timer.Start();
}
public static void Track(string functionName, Action action)
{
Start(functionName);
action.Invoke();
End(functionName);
}
public static void End()
{
if (currentFunction == null)
return;
currentFunction.Timer.Stop();
currentFunction.RecordedTime = currentFunction.Timer.Elapsed.TotalMilliseconds;
currentFunction.Timer.Reset();
}
public static void End(string funcname)
{
currentFunction = currentRegion.Functions[funcname];
currentFunction.Timer.Stop();
currentFunction.RecordedTime = currentFunction.Timer.Elapsed.TotalMilliseconds;
currentFunction.Timer.Reset();
}
const int percent_accuracy = 0;
public static void Draw(GraphicsEngine GFX)
{
float renderHeight = 0;
foreach (var dataset in Regions)
{
var totalMS = dataset.Value.RecordedTime;
double timeAccountedFor = totalMS;
GFX.Text($"{dataset.Key} : {Math.Round(totalMS, 2)}ms", new Vector2(50, 100 + renderHeight));
renderHeight += 12;
foreach (var subset in dataset.Value.Functions)
{
var frac = subset.Value.RecordedTime / totalMS;
timeAccountedFor -= subset.Value.RecordedTime;
GFX.Text($"{subset.Key} : {Math.Round(frac * 100)}% {Math.Round(subset.Value.RecordedTime, 2)}ms", new Vector2(70, 100 + renderHeight));
renderHeight += 12;
}
var unaccountedFrac = timeAccountedFor / totalMS;
if (unaccountedFrac > 0) {
GFX.Text($"Other : {Math.Round(unaccountedFrac*100)}% {Math.Round(timeAccountedFor, 2)}ms", new Vector2(70, 100 + renderHeight));
renderHeight += 12;
}
}
}
}
}

View File

@@ -1,41 +0,0 @@
using CaveGame.Core;
using CaveGame.Core.Game.Entities;
using CaveGame.Core.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Text;
namespace CaveGame.Client.Game.Entities
{
public abstract class ClientPlayer : Core.Game.Entities.Player
{
public override void Draw(GraphicsEngine gfx)
{
Rectangle spriteFrame = new Rectangle(0, 0, 16, 24);
int flipSprite = 0;
if (Facing == Direction.Left)
flipSprite = 0;
if (Facing == Direction.Right)
flipSprite = 1;
if (Walking)
{
spriteFrame = new Rectangle(16, 0, 16, 24);
if (walkingAnimationTimer % 2 >= 1)
spriteFrame = new Rectangle(32, 0, 16, 24);
}
if (!OnGround)
spriteFrame = new Rectangle(48, 0, 16, 24);
DrawHealth(gfx);
gfx.Sprite(gfx.Player, TopLeft, spriteFrame, Color, Rotation.Zero, new Vector2(0, 0), 1, (SpriteEffects)flipSprite, 0);
}
}
}

View File

@@ -14,7 +14,7 @@ namespace CaveGame.Client.Game.Entities
public class LocalPlayer : ClientPlayer, IClientPhysicsObserver
public class LocalPlayer : Player, IClientPhysicsObserver
{
float jumpEnergy;
@@ -169,7 +169,7 @@ namespace CaveGame.Client.Game.Entities
base.PhysicsStep(world, step);
}
public virtual void ClientPhysicsTick(IClientWorld world, float step) => PhysicsStep(world, step);
public override void ClientPhysicsTick(IClientWorld world, float step) => PhysicsStep(world, step);
}
}

View File

@@ -1,25 +0,0 @@
using CaveGame.Core;
using CaveGame.Core.Game.Entities;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Text;
namespace CaveGame.Client.Game.Entities
{
// Player entities that are controlled by other clients
public class PeerPlayer : ClientPlayer, IClientPhysicsObserver
{
//public void ClientPhysicsTick(IClientWorld world, float step) => PhysicsStep(world, step);
public override void Draw(GraphicsEngine GFX)
{
Vector2 namebounds = GFX.Fonts.Arial8.MeasureString(DisplayName);
GFX.Text(GFX.Fonts.Arial8, DisplayName, Position - new Vector2(namebounds.X/2, namebounds.Y), Color.White);
base.Draw(GFX);
}
public void ClientPhysicsTick(IClientWorld world, float step) => Position = Position.Lerp(NextPosition, 0.5f);
}
}

View File

@@ -1,7 +1,6 @@
//#define SERVER
using CaveGame.Client.UI;
using CaveGame.Client.UI;
using CaveGame.Core;
using CaveGame.Core.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
@@ -31,7 +30,7 @@ namespace CaveGame.Client
inputBox = new TextInput();
inputBox.ClearOnReturn = true;
inputBox.Handler += client.SendChatMessage;
inputBox.Handler += SendChatMessage;
MessageHistory = new List<Message>();
Open = false;
@@ -39,6 +38,13 @@ namespace CaveGame.Client
Client = client;
}
public void SendChatMessage(object sender, string message)
{
Open = false;
Client.Send(new ClientChatMessagePacket(message));
}
public void OnTextInput(object sender, TextInputEventArgs args)
{
if (Client.Game.Console.Open)

View File

@@ -1,82 +1,89 @@
//#define SERVER
using CaveGame;
using CaveGame.Client;
using CaveGame.Client.DebugTools;
using CaveGame.Client.Game.Entities;
using CaveGame.Client.UI;
using CaveGame.Core;
using CaveGame.Core.Game.Entities;
using CaveGame.Core.Furniture;
using CaveGame.Core.Game.Entities;
using CaveGame.Core.Game.Items;
using CaveGame.Core.Game.Tiles;
using CaveGame.Core.Game.Walls;
using CaveGame.Core.Generic;
using CaveGame.Core.Inventory;
using CaveGame.Core.Network;
using CaveGame.Core.Game.Tiles;
using CaveGame.Core.Game.Walls;
using CaveGame.Core.Network.Packets;
using DataManagement;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Diagnostics;
using System.Threading;
using CaveGame.Client.Game.Entities;
using CaveGame.Core.Game.Inventory;
using CaveGame.Core.Game.Items;
using CaveGame.Client.DebugTools;
using CaveGame.Core.Network.Packets;
using System.Linq;
using System.Collections.Generic;
using CaveGame.Core.Game;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace CaveGame.Client
{
using BombEntity = Core.Game.Entities.Bomb;
using ArrowEntity = Core.Game.Entities.Arrow;
using WurmholeEntity = Core.Game.Entities.Wurmhole;
using DynamiteEntity = Core.Game.Entities.Dynamite;
using ArrowEntity = Core.Game.Entities.Arrow;
using BombEntity = Core.Game.Entities.Bomb;
using DynamiteEntity = Core.Game.Entities.Dynamite;
using WurmholeEntity = Core.Game.Entities.Wurmhole;
public delegate void ClientShutdown();
public class GameClient : IGameContext, IGameClient
{
public static float CameraZoom = 2.0f;
public bool ChunkLock { get; set; }
protected ChunkGridLineRenderer chunkGridTool;// = new ChunkGridLineRenderer();
public bool ShowChunkBoundaries { get; set; }
public CaveGameGL Game { get; private set; }
public GameChat Chat { get; private set; }
public event ClientShutdown OnShutdown;
public string NetworkUsername { get; set; }
public string ConnectAddress { get; set; }
public bool Active { get; set; }
public static float CameraZoom = 2.0f;
public bool ShowChunkBoundaries { get; set; }
public CaveGameGL Game { get; private set; }
public GameChat Chat { get; private set; }
public LocalWorld World { get; private set; }
public PlayerContainerFrontend Inventory { get; set; }
public Camera2D Camera { get; }
protected NetworkClient NetClient { get; set; }
protected struct FpsSample : GraphSample
{
public double Value { get; set; }
}
protected GraphRenderer<FpsSample> FPSGraph { get; private set; }
Microsoft.Xna.Framework.Game IGameContext.Game => Game;
IClientWorld IGameClient.World => World;
//public Hotbar Hotbar { get; set; }
public PlayerContainerFrontend Inventory { get; set; }
public LocalWorld World { get; private set; }
private NetworkClient gameClient;
public Camera2D Camera { get; }
public int ChunkingRadius { get; set; }
int MyUserID;
int MyPlayerID;
public Game.Entities.LocalPlayer MyPlayer { get; private set; }
private DelayedTask chunkUnloadingTask;
DelayedTask playerStateReplicationTask;
DelayedTask chunkLoadingTask;
private int IntitalScrollValue;
PauseMenu PauseMenu { get; set; }
private void onClientExit(TextButton sender, MouseState state)=>OverrideDisconnect();
public void OverrideDisconnect()
{
PauseMenu.Open = false;
Disconnect();
Game.CurrentGameContext = Game.MenuContext;
}
protected List<RepeatingIntervalTask> ClientTasks { get; set; }
private Dictionary<PacketType, NetworkListener> NetworkEvents;
private void InitNetworkEvents() => NetworkEvents = new Dictionary<PacketType, NetworkListener>()
@@ -87,6 +94,10 @@ namespace CaveGame.Client
[PacketType.sDownloadChunk] = DownloadChunk,
[PacketType.sUpdateTile] = UpdateTile,
[PacketType.sUpdateWall] = UpdateWall,
[PacketType.netPlaceTile] = UpdateTile,
[PacketType.netPlaceWall] = UpdateWall,
[PacketType.sRejectLogin] = OnServerRejectLogin,
[PacketType.sAcceptLogin] = OnServerAcceptLogin,
[PacketType.sPlayerPeerJoined] = OnPeerJoined,
@@ -107,51 +118,82 @@ namespace CaveGame.Client
[PacketType.netPlayerState] = OnPlayerAnimationStateUpdate,
};
internal void Dispose()
{
//throw new NotImplementedException();
}
public float ServerKeepAlive { get; set; }
public GameClient(CaveGameGL _game)
{
Game = _game;
InitNetworkEvents();
Game = _game;
MouseState mouse = Mouse.GetState();
World = new LocalWorld(this);
Camera = new Camera2D(Game.GraphicsDevice.Viewport) { Zoom = CameraZoom };
Camera = new Camera2D{ Zoom = CameraZoom };
Chat = new GameChat(this);
PauseMenu = new PauseMenu(this);
Inventory = new PlayerContainerFrontend();
IntitalScrollValue = mouse.ScrollWheelValue;
ClientTasks = new List<RepeatingIntervalTask>
{
new RepeatingIntervalTask(ReplicatePlayerState, 1 / 10.0f),
new RepeatingIntervalTask(ChunkUnloadingCheck, 1/2.0f),
new RepeatingIntervalTask(ChunkLoadingCheckUpdate, 1 / 2.0f),
};
FPSGraph = new GraphRenderer<FpsSample>
{
BackgroundColor = new Color(0.1f, 0.1f, 0.1f)*0.5f,
ScreenPosition = new Vector2(50, 400),
GraphSize = new Vector2(500, 200),
GraphName = "FPS",
YAxisMin = 0,
Scale = 1000,
YAxisMax = 120,
DataSet = new GraphRecorder<FpsSample>
{
SampleCount = 500,
Color = Color.Yellow,
},
};
playerStateReplicationTask = new DelayedTask(ReplicatePlayerState, 1 / 10.0f);
chunkUnloadingTask = new DelayedTask(ChunkUnloadingCheck, 1/2.0f);
chunkLoadingTask = new DelayedTask(ChunkLoadingCheckUpdate, 1 / 2.0f);
ChunkingRadius = 1;
}
private void Uncouple()
{
PauseMenu.Open = false;
World?.ClientDisconnect(); // start cleaning up server
NetClient?.Logout(MyUserID, UserDisconnectReason.LogOff);
World?.Dispose(); // destroy local world
OnShutdown?.Invoke(); // bound to localserver (if it exists)
//Dispose();
}
public void Send(Packet p)=>gameClient.SendPacket(p);
public void SendChatMessage(object sender, string message)
{
Chat.Open = false;
gameClient.SendPacket(new ClientChatMessagePacket(message));
}
public void Disconnect()
{
if (MyPlayer != null) {
World.ClientDisconnect();
gameClient.SendPacket(new DisconnectPacket(MyPlayer.EntityNetworkID, UserDisconnectReason.LogOff));
gameClient.Stop();
}
Uncouple();
Game.GoToMainMenu();
}
public void ForcedDisconnect(string kickReason)
{
Uncouple();
Game.GoToTimeoutPage(kickReason);
}
private void ChunkUnloadingCheck()
{
foreach (var chunkpair in World.Chunks)
{
if (!World.LoadedChunks.Contains(chunkpair.Key))
@@ -160,10 +202,10 @@ namespace CaveGame.Client
{
World.Chunks.TryRemove(chunkpair.Key, out _);
World.Lighting.UnregisterChunk(chunkpair.Key);
chunkpair.Value.Dispose();
}
}
}
// }
}
private void ChunkLoadingCheckUpdate()
{
@@ -187,7 +229,7 @@ namespace CaveGame.Client
if ((!World.Chunks.ContainsKey(chunkCoords)) && (!World.RequestedChunks.Contains(chunkCoords)))
{
gameClient.SendPacket(new RequestChunkPacket(chunkCoords));
NetClient.SendPacket(new RequestChunkPacket(chunkCoords));
World.RequestedChunks.Add(chunkCoords);
}
}
@@ -195,24 +237,36 @@ namespace CaveGame.Client
}
}
public void Send(Packet p) => NetClient.SendPacket(p);
#region NetworkListenerMethods
public delegate void NetworkListener(NetworkMessage message);
private void OnPing(NetworkMessage message) { } // dont do jack
private void DownloadChunk(NetworkMessage message)
{
ChunkDownloadPacket chunkdata = new ChunkDownloadPacket(message.Packet.GetBytes());
Chunk chunk = chunkdata.StoredChunk;
// Did we ask for this chunk?
if (World.RequestedChunks.Contains(chunk.Coordinates))
Task.Run(() =>
{
//World.Chunks. Add(chunk.Coordinates, chunk);
World.Chunks.TryAdd(chunk.Coordinates, chunk);
World.RequestedChunks.Remove(chunk.Coordinates);
World.Lighting.RegisterChunk(chunk);
}
//Stopwatch stopWatch = new Stopwatch();
//stopWatch.Start();
Chunk chunk = chunkdata.StoredChunk;
//stopWatch.Stop();
// GameConsole.Log($"ChunkDecode: {stopWatch.ElapsedMilliseconds}(ms)");
// Did we ask for this chunk?
if (World.RequestedChunks.Contains(chunk.Coordinates))
{
//World.Chunks. Add(chunk.Coordinates, chunk);
World.Chunks.TryAdd(chunk.Coordinates, chunk);
World.RequestedChunks.Remove(chunk.Coordinates);
World.Lighting.RegisterChunk(chunk);
}
});
}
private void UpdateTile(NetworkMessage message)
@@ -241,7 +295,7 @@ namespace CaveGame.Client
{
PlayerJoinedPacket packet = new PlayerJoinedPacket(message.Packet.GetBytes());
var player = new Game.Entities.PeerPlayer()
var player = new Core.Game.Entities.Player()
{
EntityNetworkID = packet.EntityID,
Color = packet.PlayerColor,
@@ -260,7 +314,7 @@ namespace CaveGame.Client
}
private void OnEntityPhysUpdate(NetworkMessage message)
{
EntityPositionPacket packet = new EntityPositionPacket(message.Packet.GetBytes());
EntityPhysicsStatePacket packet = new EntityPhysicsStatePacket(message.Packet.GetBytes());
var entity = World.FindEntityOfID(packet.EntityID);
@@ -463,11 +517,19 @@ namespace CaveGame.Client
}
private void OnDamageTile(NetworkMessage msg)
{
// TODO: Implement damage texture onto tile
// TODO: add audio for tile being damaged
{
//GameConsole.Log("WTF BRYH");
DamageTilePacket packet = new DamageTilePacket(msg.Packet.GetBytes());
}
var tile = World.GetTile(packet.Position.X, packet.Position.Y);
tile.Damage += (byte)packet.Damage;
if (tile.Damage >= tile.Hardness)
{
World.SetTile(packet.Position.X, packet.Position.Y, new Core.Game.Tiles.Air());
}
}
private void GiveItToMeDaddy(NetworkMessage msg)
{
@@ -512,31 +574,34 @@ namespace CaveGame.Client
foreach (var name in packet.PlayerList.Where(str => str.Length > 0))
GameConsole.Log($" {name}", Color.LightBlue);
GameConsole.Log($"Requesting game session...");
gameClient.SendPacket(new RequestJoinPacket(NetworkUsername));
NetClient.SendPacket(new RequestJoinPacket(NetworkUsername));
}
#endregion
private void ReadIncomingPackets()
{
while (gameClient.HaveIncomingMessage())
while (NetClient.HaveIncomingMessage())
{
NetworkMessage msg = gameClient.GetLatestMessage();
NetworkMessage msg = NetClient.GetLatestMessage();
foreach(var ev in NetworkEvents)
if (ev.Key == msg.Packet.Type)
{
ServerKeepAlive = 0;
ev.Value.Invoke(msg);
}
}
}
private void DrawChunks(GraphicsEngine GFX)
float redrawTimer { get; set; }
private void RedrawChunkBuffers(GraphicsEngine GFX)
{
if (drawTimer > (1/5.0f))
foreach (var chunkpair in World.Chunks)
{
drawTimer = 0;
Chunk.RefreshedThisFrame = false;
foreach (var chunkpair in World.Chunks)
{
chunkpair.Value.Draw(GFX);
}
var chunk = chunkpair.Value;
chunk.RedrawWallBuffer(GFX);
chunk.RedrawTileBuffer(GFX);
}
}
@@ -549,19 +614,19 @@ namespace CaveGame.Client
}
private void DrawChunkFGTextures(GraphicsEngine gfx)
private void DrawChunkTileTextures(GraphicsEngine gfx)
{
foreach (var chunkpair in World.Chunks)
{
Chunk chunk = chunkpair.Value;
Vector2 pos = new Vector2(chunk.Coordinates.X * Globals.ChunkSize * Globals.TileSize, chunk.Coordinates.Y * Globals.ChunkSize * Globals.TileSize);
if (chunk.ForegroundRenderBuffer != null)
gfx.Sprite(chunk.ForegroundRenderBuffer, pos, Color.White);
if (chunk.TileRenderBuffer != null)
gfx.Sprite(chunk.TileRenderBuffer, pos, Color.White);
}
}
private void DrawChunkBGTextures(GraphicsEngine gfx)
private void DrawChunkWallTextures(GraphicsEngine gfx)
{
foreach (var chunkpair in World.Chunks)
{
@@ -569,8 +634,8 @@ namespace CaveGame.Client
Chunk chunk = chunkpair.Value;
Vector2 pos = new Vector2(chunk.Coordinates.X * Globals.ChunkSize * Globals.TileSize, chunk.Coordinates.Y * Globals.ChunkSize * Globals.TileSize);
if (chunk.BackgroundRenderBuffer != null)
gfx.Sprite(chunk.BackgroundRenderBuffer, pos, Color.White);
if (chunk.WallRenderBuffer != null)
gfx.Sprite(chunk.WallRenderBuffer, pos, Color.White);
}
}
private void DrawDebugInfo(GraphicsEngine gfx)
@@ -592,148 +657,73 @@ namespace CaveGame.Client
if (MyPlayer != null)
{
string positionData = String.Format("pos {0} {1} vel {2} {3}",
string mObjData = String.Format("entities {0} furn {1}", World.Entities.Count, World.Furniture.Count);
string[] debugStats = {
String.Format("pos {0} {1} vel {2} {3}, rv {4} {5}",
Math.Floor(MyPlayer.Position.X / Globals.TileSize),
Math.Floor(MyPlayer.Position.Y / Globals.TileSize),
Math.Round(MyPlayer.Velocity.X, 2),
Math.Round(MyPlayer.Velocity.Y, 2)
);
string networkData = String.Format("pin {0}, pout {1} myid {2} ipaddr {3}",
gameClient.ReceivedCount,
gameClient.SentCount,
MyPlayer?.EntityNetworkID,
ConnectAddress
);
string worldData = String.Format("tid {0}, state {1} tdmg {2} wid {3} wdmg {4} light {5}",
Math.Round(MyPlayer.Velocity.Y, 2),
Math.Round(MyPlayer.RecentVelocity.X, 2),
Math.Round(MyPlayer.RecentVelocity.Y, 2)
),
$"userid {MyUserID} netaddr {ConnectAddress}",
$"in {Math.Round(NetClient.BytesReceivedPerSecond/1000.0f, 2)}kb/s || {Math.Round(NetClient.TotalBytesReceived/1000.0f, 2)}kb total || {NetClient.PacketsReceived}ct",
$"out {Math.Round(NetClient.BytesSentPerSecond/1000.0f, 2)}kb/s || {Math.Round(NetClient.TotalBytesSent/1000.0f, 2)}kb total || {NetClient.PacketsSent}ct",
String.Format("tid {0}, state {1} tdmg {2} wid {3} wdmg {4} light {5}",
tileat.ID,
tileat.TileState,
tileat.Damage,
wallat.ID,
wallat.Damage,
World.GetLight((int)tileCoords.X, (int)tileCoords.Y).ToString()
);
),
mObjData,
};
string mObjectData = String.Format("entities {0} furn {1}", World.Entities.Count, World.Furniture.Count);
gfx.Text(positionData, new Vector2(2, 12));
gfx.Text(networkData, new Vector2(2, 24));
gfx.Text(worldData, new Vector2(2, 36));
gfx.Text(mObjectData, new Vector2(2, 48));
int incr = 1;
foreach(var line in debugStats)
{
gfx.Text(line, new Vector2(2, incr * 14));
incr++;
}
}
gfx.End();
}
private void DrawSkyColor(GraphicsEngine GFX)
{
for (int y = 0; y<10; y++)
{
int hourTime = (int)Math.Floor( ( (World.TimeOfDay+1)%24) / 2);
int bottom = hourTime*2;
int top = (hourTime*2) + 1;
//float diff = World.TimeOfDay % 1;
var thisSection = Color.Lerp(World.SkyColors[bottom], World.SkyColors[top], y/10.0f);
int prevhourTime = (int)Math.Floor((World.TimeOfDay % 24) / 2);
int prevbottom = prevhourTime * 2;
int prevtop = (prevhourTime * 2) + 1;
//float diff = World.TimeOfDay % 1;
var prevSection = Color.Lerp(World.SkyColors[prevbottom], World.SkyColors[prevtop], y / 10.0f);
var finalColor = Color.Lerp(prevSection, thisSection, (World.TimeOfDay % 2.0f) / 2.0f);
float sliceHeight = Camera._screenSize.Y / 10.0f;
GFX.Rect(finalColor, new Vector2(0,(sliceHeight*y)), new Vector2(Camera._screenSize.X, sliceHeight + 1));
}
}
public void Draw(GraphicsEngine GFX)
{
DrawChunks(GFX);
Game.GraphicsDevice.Clear(World.SkyColor);
GFX.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp);
DrawSkyColor(GFX);
GFX.End();
GFX.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp, null, null, null, Camera.View);
if (PauseMenu.Open)
PauseMenu.DrawWaterPixelsFilter(GFX);
DrawChunkBGTextures(GFX);
DrawChunkFGTextures(GFX);
foreach (var furn in World.Furniture)
{
furn.Draw(GFX);
}
EntityRendering(GFX);
World.ParticleSystem.Draw(GFX);
if (!Inventory.EquippedItem.Equals(ItemStack.Empty))
{
Inventory.EquippedItem.Item?.OnClientDraw(GFX);
}
MouseState mouse = Mouse.GetState();
var mp = Camera.ScreenToWorldCoordinates(mouse.Position.ToVector2());
mp /= 8;
mp.Floor();
var tileCoords = mp;
mp *= 8;
if (mouse.LeftButton == ButtonState.Pressed)
{
GFX.Rect(Color.Green, mp, new Vector2(8, 8));
} else
{
GFX.Rect(new Color(1,1,1,0.5f), mp, new Vector2(8, 8));
}
GFX.End();
if (ShowChunkBoundaries)
{
GFX.Begin(SpriteSortMode.Immediate, null, SamplerState.PointClamp, null, null, null, Camera.View);
chunkGridTool?.Draw(GFX, Camera);
GFX.End();
}
// TODO: Consolidate draw calls
GFX.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp);
Inventory.Draw(GFX);
PauseMenu.Draw(GFX);
GFX.End();
DrawDebugInfo(GFX);
Chat.Draw(GFX);
}
public void Load()
{
PauseMenu.LoadShader(Game.Content);
if (NetworkClient.IsAddressValidIPv4(ConnectAddress))
{
FailConnect("Server address is not a valid IPv4 address!");
return;
}
if (NetworkClient.IsServerOnline(ConnectAddress))
{
FailConnect("Failed to connect, server may be offline!");
return;
}
gameClient = new NetworkClient(ConnectAddress);
NetClient = new NetworkClient(ConnectAddress);
//gameClient = new NetworkClient("127.0.0.1:40269");
//gameClient.Output = Game.Console;
gameClient.Start();
gameClient.SendPacket(new InitServerHandshakePacket(Globals.ProtocolVersion));
NetClient.Start();
NetClient.SendPacket(new InitServerHandshakePacket(Globals.ProtocolVersion));
//gameClient.SendPacket(new RequestJoinPacket("jooj"));
}
private void FailConnect(string reason)
{
}
public void Unload() {}
MouseState previous = Mouse.GetState();
private void HotbarUpdate(GameTime gt)
@@ -770,17 +760,13 @@ namespace CaveGame.Client
}
private void ReplicatePlayerState()
{
//Debug.WriteLine("Replicating");
if (MyPlayer != null)
{
gameClient?.SendPacket(
new EntityPositionPacket(MyPlayer)
);
gameClient?.SendPacket(new PlayerStatePacket(MyPlayer.Facing, MyPlayer.OnGround, MyPlayer.Walking));
NetClient?.SendPacket(new EntityPhysicsStatePacket(MyPlayer));
NetClient?.SendPacket(new PlayerStatePacket(MyPlayer.Facing, MyPlayer.OnGround, MyPlayer.Walking));
}
}
@@ -801,7 +787,7 @@ namespace CaveGame.Client
//float ZoomFactor = ((mouse.ScrollWheelValue - IntitalScrollValue) * (Senitivity / 120)) + 2;
Vector2 MouseCameraMovement = ((mouse.Position.ToVector2() / Camera._screenSize) - new Vector2(0.5f, 0.5f)) * 5.5f;
Vector2 MouseCameraMovement = ((mouse.Position.ToVector2() / Camera.WindowSize) - new Vector2(0.5f, 0.5f)) * 5.5f;
Camera.Zoom = Math.Clamp(scroll, 0.05f, 10f);
@@ -857,22 +843,22 @@ namespace CaveGame.Client
previousKB = currentKB;
}
float drawTimer = 0;
public void Update(GameTime gt)
{
FPSGraph.DataSet.Push(new FpsSample { Value = gt.GetDelta()});
NetClient.Update(gt);
redrawTimer += gt.GetDelta();
Profiler.StartRegion("Update");
ServerKeepAlive += gt.GetDelta();
UpdateInputs();
Camera.Update(gt);
drawTimer += (float)gt.ElapsedGameTime.TotalSeconds;
playerStateReplicationTask.Update(gt);
chunkUnloadingTask.Update(gt);
chunkLoadingTask.Update(gt);
ClientTasks.ForEach(ct => ct.Update(gt));
if (MyPlayer != null)
{
@@ -882,14 +868,130 @@ namespace CaveGame.Client
MyPlayer.IgnoreInput = false;
}
Profiler.Start("UIUpdate");
Inventory.Update(gt);
PauseMenu.Update(gt);
Chat.Update(gt);
Chat.Update(gt);
Profiler.End();
//Profiler.Start("WorldUpdate");
World.Update(gt);
//Profiler.End();
Profiler.Start("Camera");
HotbarUpdate(gt);
UpdateCamera(gt);
Profiler.End();
Profiler.Start("ReadPackets");
ReadIncomingPackets();
Profiler.End();
Profiler.EndRegion();
}
protected void DrawBackgroundLayer(GraphicsEngine GFX)
{
Game.GraphicsDevice.Clear(World.SkyColor);
GFX.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp);
World.Sky.DrawSkyColors(GFX);
GFX.End();
}
protected void DrawGameLayer(GraphicsEngine GFX)
{
// Game Layer
GFX.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp, null, null, null, Camera.View);
if (PauseMenu.Open)
PauseMenu.DrawWaterPixelsFilter(GFX);
World.Sky.DrawBackground(GFX);
Profiler.Start("DrawChunkCanvases");
DrawChunkWallTextures(GFX);
DrawChunkTileTextures(GFX);
Profiler.End();
Profiler.Start("DrawFurniture");
foreach (var furn in World.Furniture)
{
furn.Draw(GFX);
}
Profiler.End();
Profiler.Start("DrawEntities");
EntityRendering(GFX);
Profiler.End();
Profiler.Start("DrawParticles");
World.ParticleSystem.Draw(GFX);
Profiler.End();
if (!Inventory.EquippedItem.Equals(ItemStack.Empty))
{
Inventory.EquippedItem.Item?.OnClientDraw(GFX);
}
MouseState mouse = Mouse.GetState();
var mp = Camera.ScreenToWorldCoordinates(mouse.Position.ToVector2());
mp /= 8;
mp.Floor();
var tileCoords = mp;
mp *= 8;
if (mouse.LeftButton == ButtonState.Pressed)
{
GFX.Rect(Color.Green, mp, new Vector2(8, 8));
}
else
{
GFX.Rect(new Color(1, 1, 1, 0.5f), mp, new Vector2(8, 8));
}
GFX.End();
}
protected void DrawUILayer(GraphicsEngine GFX)
{
Profiler.Start("DrawUI");
GFX.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp);
Inventory.Draw(GFX);
PauseMenu.Draw(GFX);
GFX.End();
Chat.Draw(GFX);
Profiler.End();
Profiler.Start("DrawDebug");
DrawDebugInfo(GFX);
Profiler.End();
}
public void Draw(GraphicsEngine GFX)
{
Profiler.StartRegion("Draw");
if (redrawTimer > (1.0f / 8.0f))
{
redrawTimer = 0;
Profiler.Start("DrawChunkBuffers");
RedrawChunkBuffers(GFX);
//Profiler.Track("DrawChunkBuffers", ()=>RedrawChunkBuffers(GFX));
Profiler.End("DrawChunkBuffers");
}
DrawBackgroundLayer(GFX);
DrawGameLayer(GFX);
DrawUILayer(GFX);
Profiler.EndRegion("Draw");
GFX.Begin(SpriteSortMode.Immediate);
Profiler.Draw(GFX);
FPSGraph.Draw(GFX);
GFX.End();
}
}
}

View File

@@ -39,23 +39,23 @@ namespace CaveGame.Client
public static void LoadAssets(ContentManager Content)
{
Content.RootDirectory = "Assets";
MenuBlip = Content.Load<SoundEffect>("Sound/click1");
Content.RootDirectory = Path.Combine("assets", "sound");
MenuBlip = Content.Load<SoundEffect>("click1");
Mu_Big_Brother = Content.Load<Song>("Sound/mu/big_brother");
Mu_Cliff = Content.Load<Song>("Sound/mu/cliff");
Mu_Hey_Bella = Content.Load<Song>("Sound/mu/hey_bella");
Mu_Mithril_Ocean = Content.Load<Song>("Sound/mu/mithril_ocean");
AmbientLava = Content.Load<Song>("Sound/ambient/lava");
AmbientBirds1 = Content.Load<Song>("Sound/ambient/birds1");
AmbientBirds2 = Content.Load<Song>("Sound/ambient/birds2");
AmbientBirds3 = Content.Load<Song>("Sound/ambient/birds3");
AmbientBirds4 = Content.Load<Song>("Sound/ambient/birds4");
AmbientBirds5 = Content.Load<Song>("Sound/ambient/birds5");
AmbientBirds6 = Content.Load<Song>("Sound/ambient/birds6");
AmbientBirds7 = Content.Load<Song>("Sound/ambient/birds7");
AmbientCreepy1 = Content.Load<Song>("Sound/ambient/birds1");
AmbientBirds7 = Content.Load<Song>("Sound/ambient/birds1");
Mu_Big_Brother = Content.Load<Song>("mu/big_brother");
Mu_Cliff = Content.Load<Song>("mu/cliff");
Mu_Hey_Bella = Content.Load<Song>("mu/hey_bella");
Mu_Mithril_Ocean = Content.Load<Song>("mu/mithril_ocean");
AmbientLava = Content.Load<Song>("ambient/lava");
AmbientBirds1 = Content.Load<Song>("ambient/birds1");
AmbientBirds2 = Content.Load<Song>("ambient/birds2");
AmbientBirds3 = Content.Load<Song>("ambient/birds3");
AmbientBirds4 = Content.Load<Song>("ambient/birds4");
AmbientBirds5 = Content.Load<Song>("ambient/birds5");
AmbientBirds6 = Content.Load<Song>("ambient/birds6");
AmbientBirds7 = Content.Load<Song>("ambient/birds7");
AmbientCreepy1 = Content.Load<Song>("ambient/birds1");
AmbientBirds7 = Content.Load<Song>("ambient/birds1");
}
}
}

View File

@@ -13,6 +13,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
// TODO: Refactor and clean
namespace CaveGame.Client
{
[StructLayout(LayoutKind.Explicit)]
@@ -29,6 +30,13 @@ namespace CaveGame.Client
Light = l;
}
public Cell(Point coords, Light3 l)
{
X = coords.X;
Y = coords.Y;
Light = l;
}
public void Dispose()
{
System.GC.SuppressFinalize(this);
@@ -71,6 +79,14 @@ namespace CaveGame.Client
}
}
internal struct InvokedLightCell
{
public Point Coords { get; set; }
public Light3 Value { get; set; }
}
public class LightingEngine : ILightingEngine
{
public Thread LightThread;
@@ -126,6 +142,7 @@ namespace CaveGame.Client
OutputLights = new Dictionary<ChunkCoordinates, Light3[,]>();
LocalChunks = new Dictionary<ChunkCoordinates, Chunk>();
LightCells = new Dictionary<ChunkCoordinates, Light3[,]>();
InvokedCells = new ConcurrentQueue<InvokedLightCell>();
UpdatedCells = new Queue<Cell>();
}
@@ -133,6 +150,7 @@ namespace CaveGame.Client
private ConcurrentQueue<ChunkCoordinates> RemovedChunkQueue;
private ConcurrentQueue<TileChangedCell> ChangedTiles;
private ConcurrentQueue<WallChangedCell> ChangedWalls;
private ConcurrentQueue<InvokedLightCell> InvokedCells;
private Dictionary<ChunkCoordinates, Light3[,]> OutputLights;
private Dictionary<ChunkCoordinates, Chunk> LocalChunks;
@@ -152,7 +170,7 @@ namespace CaveGame.Client
public Light3 GetLight(int x, int y)
{
int chunkX = (int)Math.Floor((float)x / Globals.ChunkSize);
int chunkY = (int)Math.Floor((double)y / Globals.ChunkSize);
int chunkY = (int)Math.Floor((float)y / Globals.ChunkSize);
var tileX = x.Mod(Globals.ChunkSize);
var tileY = y.Mod(Globals.ChunkSize);
@@ -166,7 +184,9 @@ namespace CaveGame.Client
}
return new Light3(0, 0, 0);
}
public void SetLight(int x, int y, Light3 val)
public Light3 GetLight(Point coords) => GetLight(coords.X, coords.Y);
private void SetLight(int x, int y, Light3 val)
{
int chunkX = (int)Math.Floor((double)x / Globals.ChunkSize);
int chunkY = (int)Math.Floor((double)y / Globals.ChunkSize);
@@ -182,6 +202,14 @@ namespace CaveGame.Client
chunk[tileX, tileY] = val;
}
}
public void InvokeLight(Point coords, Light3 value)
{
UpdatedCells.Enqueue(new Cell(coords, value));
SetLight(coords, value);
}
private void SetLight(Point coords, Light3 val) => SetLight(coords.X, coords.Y, val);
private void SetTile(int x, int y, Tile t)
{
int chunkX = (int)Math.Floor((double)x / Globals.ChunkSize);
@@ -278,7 +306,7 @@ namespace CaveGame.Client
byte opacity = Math.Max(tile.Opacity, wall.Opacity);
var tileLight = Light3.Dark;
if (tile.ID == 0 && wall.ID == 0)
tileLight = Light3.Ambience;
tileLight = World.Ambience;
else if (tile is ILightEmitter emitter)
tileLight = emitter.Light;

View File

@@ -12,6 +12,7 @@ using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using CaveGame.Core.Game.Walls;
using CaveGame.Client.DebugTools;
namespace CaveGame.Client
{
@@ -28,26 +29,30 @@ namespace CaveGame.Client
public LightingEngine Lighting { get; set; }
ILightingEngine IClientWorld.Lighting => Lighting;
protected DelayedTask localTileUpdateTask;
protected DelayedTask localLightingUpdateTask;
public override int GetHashCode()
{
return base.GetHashCode();
}
public GameClient Client { get; set; }
public Sky Sky { get; private set; }
public LocalWorld(GameClient client) : base()
{
Context = NetworkContext.Client;
Client = client;
localTileUpdateTask = new DelayedTask(ApplyVisualTileUpdates, 1 / 10.0f);
localLightingUpdateTask = new DelayedTask(GetLatestDataFromLightingThread, 1 / 20.0f);
Sky = new Sky(this);
ParticleSystem = new ParticleEmitter(this);
Lighting = new LightingEngine(this);
Lighting.On();
WorldTimedTasks.Add(new RepeatingIntervalTask(ApplyVisualTileUpdates, 1 / 10.0f));
WorldTimedTasks.Add(new RepeatingIntervalTask(GetLatestDataFromLightingThread, 1 / 20.0f));
RequestedChunks = new List<ChunkCoordinates>();
LoadedChunks = new List<ChunkCoordinates>();
Lighting.On();
Tile.InitializeManager(420);
}
@@ -57,6 +62,11 @@ namespace CaveGame.Client
Lighting.Off();
}
public void Dispose()
{
}
public void UnloadChunk(ChunkCoordinates coords) {}
Random r = new Random();
@@ -68,15 +78,8 @@ namespace CaveGame.Client
return AmbientLights[wrapped];
}
}
public Color SkyColor
{
get {
int wrapped = ((int)Math.Floor(TimeOfDay)).Mod(24);
int last = ((int)Math.Floor(TimeOfDay) - 1).Mod(24);
float diff = TimeOfDay % 1;
return Color.Lerp(SkyColors[last], SkyColors[wrapped], diff);
}
}
public Color SkyColor => Sky.SkyColor;
public override void SetTileNoLight(int x, int y, Tile t)
{
@@ -146,53 +149,49 @@ namespace CaveGame.Client
public override void Update(GameTime gt)
{
localTileUpdateTask.Update(gt);
localLightingUpdateTask.Update(gt);
//Profiler.Start("WorldTasks");
//base.Update(gt);
//localTileUpdateTask.Update(gt);
//localLightingUpdateTask.Update(gt);
//Profiler.End();
Profiler.Start("Particles");
ParticleSystem.Update(gt);
Profiler.End();
Profiler.Start("Lighting");
Lighting.Update(gt);
Profiler.End();
Profiler.Start("EntityUpdate");
foreach (IEntity entity in Entities)
entity.ClientUpdate(Client, gt);
Profiler.End();
TimeOfDay += (float)gt.ElapsedGameTime.TotalSeconds / 30.0f;
//Entities.RemoveAll(e => e.Dead);
//Profiler.Start("PhysicsTask");
//physicsTask.Update(gt);
//Profiler.End();
base.Update(gt);
}
private void LocalTileUpdates(Chunk chunk)
{
for (int x = 0; x < Globals.ChunkSize; x++)
{
for (int y = 0; y < Globals.ChunkSize; y++)
{
if (chunk.TileUpdate[x, y] == true)
{
chunk.TileUpdate[x, y] = false;
if (chunk.GetTile(x, y) is ILocalTileUpdate tile)
{
int worldX = (chunk.Coordinates.X * Globals.ChunkSize) + x;
int worldY = (chunk.Coordinates.Y * Globals.ChunkSize) + y;
tile.LocalTileUpdate(this, worldX, worldY);
}
}
}
}
}
private void SpawnExplosionParticles(Explosion blast)
{
for (int i = 0; i < 360; i += 5)
for (int i = 0; i < 360; i += 20)
{
float randy = r.Next(0, 10) - 5;
Rotation rotation = Rotation.FromDeg(i + randy);
Vector2 direction = new Vector2((float)Math.Sin(rotation.Radians), (float)Math.Cos(rotation.Radians));
float size = ((float)r.NextDouble() * 0.4f) + (blast.BlastPressure * 0.2f);
ParticleSystem.EmitSmokeParticle(blast.Position, Color.White, Rotation.FromDeg(0), size, direction * (blast.BlastRadius * 1.0f + ((float)r.NextDouble() * 5)));
float size = ((float)r.NextDouble() * 0.2f) + (blast.BlastPressure * 0.1f);
ParticleSystem.EmitSmokeParticle(blast.Position, Color.Gray, Rotation.FromDeg(0), new Vector2(size, size), direction * (blast.BlastRadius * ((float)r.NextDouble())));
}
ParticleSystem.EmitExplosionParticle(blast.Position);
}
public override void Explosion(Explosion Blast, bool DamageTiles, bool DamageEntities)
@@ -216,17 +215,21 @@ namespace CaveGame.Client
ParticleSystem.Add(new TileBloodSplatterParticle { AttachedTo = result.TileCoordinates, Position = rounded, Face = result.Face });
}
}
}
}
}
private void ApplyVisualTileUpdates()
{
foreach (var kvp in Chunks)
int count = TileUpdateQueue.Count;
for (int i = 0; i < count; i++)
{
LocalTileUpdates(kvp.Value);
Point coords = TileUpdateQueue.Dequeue();
if (GetTile(coords) is ILocalTileUpdate tile)
tile.LocalTileUpdate(this, coords.X, coords.Y);
}
}
}
}

View File

@@ -70,7 +70,7 @@ namespace CaveGame.Client.Menu
luastate["_G.script"] = luastate;
luastate.DoString(@"_G.oldprint = print; _G.print = function(str) game.Console:LuaPrint(str) end");
luastate.DoString(LuaSnippets.UtilityFunctions);
luastate.DoFile(Path.Combine("Assets", "Scripts", "menu.lua"));
luastate.DoFile(Path.Combine("assets", "scripts", "menu.lua"));
}
public void Load()

View File

@@ -170,7 +170,7 @@ namespace CaveGame.Client.Menu
GameSounds.MenuBlip?.Play(0.8f, 1, 0.0f);
}
fpsCapSlider.OnValueChanged += onFpsCapSliderChanged;
fpsCapSlider.SetIndex(GameSettings.CurrentSettings.FPSLimitIndex);
//fpsCapSlider.SetIndex(GameSettings.CurrentSettings.FPSLimitIndex);
Label chatSizeText = new Label
{
@@ -199,7 +199,7 @@ namespace CaveGame.Client.Menu
GameSounds.MenuBlip?.Play(0.8f, 1, 0.0f);
}
chatSizeSlider.OnValueChanged += onChatSliderChanged;
chatSizeSlider.SetIndex((int)GameSettings.CurrentSettings.ChatSize);
//chatSizeSlider.SetIndex((int)GameSettings.CurrentSettings.ChatSize);
void bindButtonClick(TextButton b, MouseState m)
{

View File

@@ -0,0 +1,194 @@
using CaveGame.Core;
using CaveGame.Core.Generic;
using CaveGame.Core.Network;
using CaveGame.Core.Network.Packets;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace CaveGame.Client
{
public class NetworkClient : SharedNetworkSubsystem
{
public IPEndPoint EndPoint
{
get {
IPEndPoint output = default;
bool success = IPEndPoint.TryParse(ServerHostname + ":"+ServerPort, out output);
return output;
}
}
private ThreadSafeValue<bool> running = new ThreadSafeValue<bool>(false);
public readonly string ServerHostname;
public readonly int ServerPort;
private ConcurrentQueue<NetworkMessage> incomingMessages;
private ConcurrentQueue<Packet> outgoingMessages;
public static bool IsServerOnline(string hostname)
{
try
{
Ping pingSender = new Ping();
PingOptions options = new PingOptions();
options.DontFragment = true;
string data = "CAVEGAME CAVEGAME CAVEGAME";
byte[] buffer = Encoding.ASCII.GetBytes(data);
int timeout = 120;
PingReply reply = pingSender.Send(hostname, timeout, buffer, options);
if (reply.Status == IPStatus.Success)
return true;
}
catch (Exception ex)
{
}
return false;
}
public static bool IsAddressValidIPv4(string hostname)=>IPEndPoint.TryParse(hostname, out _);
public static HandshakeResponsePacket GetServerInformation(string hostname)
{
var tempClient = new NetworkClient(hostname);
return null;
// TODO: Finish
}
public NetworkClient(string ipaddress)
{
IPEndPoint addr;
bool success = IPEndPoint.TryParse(ipaddress, out addr);
ServerHostname = addr.Address.ToString();
ServerPort = addr.Port;
UdpSocket = new UdpClient(ServerHostname, ServerPort);
IOControlFixICMPBullshit();
}
private void FlushOutgoingPackets()
{
int outQCount = outgoingMessages.Count;
// write out queued messages
for (int i = 0; i < outQCount; i++)
{
Packet packet;
bool have = outgoingMessages.TryDequeue(out packet);
if (have)
{
packet.Send(UdpSocket);
PacketsSent++;
TotalBytesSent += packet.Payload.Length;
InternalSendCount += packet.Payload.Length;
}
}
}
private void ReadIncomingPackets()
{
IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);
byte[] data = UdpSocket.Receive(ref ep);
NetworkMessage nm = new NetworkMessage();
nm.Sender = ep;
nm.Packet = new Packet(data);
nm.ReceiveTime = DateTime.Now;
incomingMessages.Enqueue(nm);
PacketsReceived++;
TotalBytesReceived += nm.Packet.Payload.Length;
InternalReceiveCount += nm.Packet.Payload.Length;
LatestReceiveTimestamp = DateTime.Now;
}
private void NetworkThreadLoop()
{
GameConsole.Log("NetworkClientSubsystem thread started.");
while (running.Value) {
bool canRead = UdpSocket.Available > 0;
int outgoingMessageQueueCount = outgoingMessages.Count;
// get data if there's any
if (canRead)
ReadIncomingPackets();
FlushOutgoingPackets();
// if nothing happened, take a nap
if (!canRead && (outgoingMessageQueueCount == 0))
Thread.Sleep(NAP_TIME_MILLISECONDS);
}
UdpSocket.Close();
UdpSocket.Dispose();
GameConsole.Log("NetworkClientSubsystem thread stopped.");
}
public void SendPacket(Packet packet)
{
//Output?.Out("client: Sending packet " + packet.Type.ToString(), Color.Cyan);
outgoingMessages.Enqueue(packet);
LatestSendTimestamp = DateTime.Now;
}
public void Logout(int userID, UserDisconnectReason reason)
{
SendPacket(new DisconnectPacket(userID, reason));
Stop();
}
public void Start()
{
running.Value = true;
Task.Factory.StartNew(NetworkThreadLoop);
}
public void Stop()
{
running.Value = false;
}
public override void Update(GameTime gt)
{
base.Update(gt);
}
public bool HaveIncomingMessage() => incomingMessages.Count > 0;
public NetworkMessage GetLatestMessage()
{
NetworkMessage msg;
bool success = incomingMessages.TryDequeue(out msg);
if (success)
{
return msg;
}
throw new Exception("No Message Queued! Used HaveIncomingMessage() to check!");
}
}
}

View File

@@ -1,156 +0,0 @@
using CaveGame.Core;
using CaveGame.Core.Generic;
using CaveGame.Core.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace CaveGame.Client
{
public class NetworkClient
{
public IPEndPoint EndPoint
{
get {
IPEndPoint output = default;
bool success = IPEndPoint.TryParse(ServerHostname + ":"+ServerPort, out output);
return output;
}
}
public IMessageOutlet Output { get; set; }
public int ReceivedCount { get; private set; }
public int SentCount { get; private set; }
private DateTime lastPacketReceivedTime = DateTime.MinValue;
private DateTime lastPacketSentTime = DateTime.MinValue;
private long lastPacketReceivedTimestamp = 0; // from server
private TimeSpan heartbeatTimeout = TimeSpan.FromSeconds(20);
private ThreadSafeValue<bool> running = new ThreadSafeValue<bool>(false);
public readonly string ServerHostname;
public readonly int ServerPort;
private ConcurrentQueue<NetworkMessage> incomingMessages;
private ConcurrentQueue<Packet> outgoingMessages;
private UdpClient udpClient;
public NetworkClient(string ipaddress)
{
IPEndPoint addr;
bool success = IPEndPoint.TryParse(ipaddress, out addr);
ServerHostname = addr.Address.ToString();
ServerPort = addr.Port;
System.Diagnostics.Debug.WriteLine(addr.ToString());
incomingMessages = new ConcurrentQueue<NetworkMessage>();
outgoingMessages = new ConcurrentQueue<Packet>();
udpClient = new UdpClient(ServerHostname, ServerPort);
}
public NetworkClient(string hostname, int port)
{
incomingMessages = new ConcurrentQueue<NetworkMessage>();
outgoingMessages = new ConcurrentQueue<Packet>();
ServerHostname = hostname;
ServerPort = port;
udpClient = new UdpClient(ServerHostname, ServerPort);
}
private void NetworkRun()
{
while (running.Value) {
bool canRead = udpClient.Available > 0;
int outgoingMessageQueueCount = outgoingMessages.Count;
// get data if there's any
if (canRead)
{
IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);
byte[] data = udpClient.Receive(ref ep);
NetworkMessage nm = new NetworkMessage();
nm.Sender = ep;
nm.Packet = new Packet(data);
nm.ReceiveTime = DateTime.Now;
incomingMessages.Enqueue(nm);
ReceivedCount++;
lastPacketReceivedTime = DateTime.Now;
}
// write out queued messages
for (int i = 0; i < outgoingMessageQueueCount; i++)
{
Packet packet;
bool have = outgoingMessages.TryDequeue(out packet);
if (have)
{
packet.Send(udpClient);
SentCount++;
//Output?.Out(string.Format("client: sending {0} at {1}", packet.Payload, packet.Timestamp), Color.Cyan);
}
}
// if nothing happened, take a nap
if (!canRead && (outgoingMessageQueueCount == 0))
Thread.Sleep(1);
}
udpClient.Close();
udpClient.Dispose();
}
public void SendPacket(Packet packet)
{
Output?.Out("client: Sending packet " + packet.Type.ToString(), Color.Cyan);
outgoingMessages.Enqueue(packet);
lastPacketSentTime = DateTime.Now;
}
public void Start()
{
running.Value = true;
Task.Factory.StartNew(NetworkRun);
}
public void Stop()
{
running.Value = false;
}
public bool HaveIncomingMessage()
{
return incomingMessages.Count > 0;
}
public NetworkMessage GetLatestMessage()
{
NetworkMessage msg;
bool success = incomingMessages.TryDequeue(out msg);
if (success)
{
return msg;
}
throw new Exception("No Message Queued! Used HaveIncomingMessage() to check!");
}
}
}

View File

@@ -6,6 +6,7 @@ using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace CaveGame.Client
@@ -36,7 +37,8 @@ namespace CaveGame.Client
public void LoadShader(ContentManager GameContent)
{
WaterpixelsShader = GameContent.Load<Effect>("Shaders/Waterpixels");
GameContent.RootDirectory = Path.Combine("assets", "shaders");
WaterpixelsShader = GameContent.Load<Effect>("Waterpixels");
}
public void Update(GameTime gt) {
@@ -101,7 +103,7 @@ namespace CaveGame.Client
public void TryClientExit(TextButton tbtn, MouseState ms)
{
Client.OverrideDisconnect();
Client.Disconnect();
}
}
}

View File

@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CaveGame.Client
{
public class ChunkRenderer
{
}
}

113
Client/World/Sky.cs Normal file
View File

@@ -0,0 +1,113 @@
using CaveGame.Core;
using DataManagement;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Text;
namespace CaveGame.Client
{
public class Sky
{
public LocalWorld World { get; private set; }
public Color SkyColor
{
get
{
int wrapped = ((int)Math.Floor(World.TimeOfDay)).Mod(24);
int last = ((int)Math.Floor(World.TimeOfDay) - 1).Mod(24);
float diff = World.TimeOfDay % 1;
return Color.Lerp(SkyColors[last], SkyColors[wrapped], diff);
}
}
public static Color[] SkyColors =
{
new Color(0, 2, 6), new Color(5, 5, 30), //0 or 24
new Color(2, 2, 10), new Color(16, 16, 40), //2
new Color(2, 2, 10), new Color(20, 20, 45), //4
new Color(8, 9, 50), new Color(85, 85, 40), //6
new Color(40, 60, 90), new Color(90, 90, 190), //8
new Color(70, 90, 130), new Color(110, 110, 230), //10
new Color(70, 80, 170), new Color(170, 170, 255), //12
new Color(80, 100, 140), new Color(140, 140, 250), //14
new Color(35, 41, 60), new Color(60, 80, 140), //14
new Color(50, 32, 50), new Color(170, 100, 70), // 18
new Color(25, 25, 55), new Color(92, 52, 23), //20
new Color(5, 7, 14), new Color(9, 23, 45), //22
};
public Sky(LocalWorld world)
{
World = world;
}
private void DrawSkyColorGradient(GraphicsEngine GFX)
{
for (int y = 0; y < 10; y++)
{
int hourTime = (int)Math.Floor(((World.TimeOfDay + 1) % 24) / 2);
int bottom = hourTime * 2;
int top = (hourTime * 2) + 1;
//float diff = World.TimeOfDay % 1;
var thisSection = Color.Lerp(SkyColors[bottom], SkyColors[top], y / 10.0f);
int prevhourTime = (int)Math.Floor((World.TimeOfDay % 24) / 2);
int prevbottom = prevhourTime * 2;
int prevtop = (prevhourTime * 2) + 1;
//float diff = World.TimeOfDay % 1;
var prevSection = Color.Lerp(SkyColors[prevbottom], SkyColors[prevtop], y / 10.0f);
var finalColor = Color.Lerp(prevSection, thisSection, (World.TimeOfDay % 2.0f) / 2.0f);
float sliceHeight = World.Client.Camera.WindowSize.Y / 10.0f;
GFX.Rect(finalColor, new Vector2(0, (sliceHeight * y)), new Vector2(World.Client.Camera.WindowSize.X, sliceHeight + 1));
}
}
private void DrawBackgroundParallax(GraphicsEngine GFX)
{
float starfieldPar = 0.85f;
float scale = 1.5f;
float textureWidth = GFX.Starfield.Width * scale;
float textureHeight = GFX.Starfield.Height * scale;
var pos = World.Client.Camera.Position;
var gridPos = new Vector2(
(float)pos.X / textureWidth,
(float)pos.Y / textureHeight
) * starfieldPar;
for (int tx = -2; tx < 2; tx++)
{
for (int ty = -2; ty < 2; ty++)
{
float xPos = tx + gridPos.X;
float yPos = ty + gridPos.Y;
GFX.Sprite(GFX.Starfield, new Vector2(xPos * textureWidth, yPos * textureHeight), null, Color.White, Rotation.Zero, Vector2.Zero, scale, Microsoft.Xna.Framework.Graphics.SpriteEffects.None, 0);
}
}
}
public void DrawSkyColors(GraphicsEngine GFX) => DrawSkyColorGradient(GFX);
public void DrawBackground(GraphicsEngine GFX) => DrawBackgroundParallax(GFX);
}
}

View File

@@ -9,10 +9,20 @@
<Import_RootNamespace>CaveGame.Core</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)AssetLoader.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Camera.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Enums.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GameConsole.cs" />
<Compile Include="$(MSBuildThisFileDirectory)AssetManagement\AssetLoader.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Enums\DamageType.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Enums\Direction.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Enums\GameSteamAchievement.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Enums\TextAlignment.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Game\Camera.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interfaces\ICaveGameImplementation.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interfaces\IDamageSource.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interfaces\IEntityManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interfaces\IGameClient.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interfaces\IGameServer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interfaces\INetworkingSubsystem.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interfaces\ISteamManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Logging\GameConsole.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Game\CraftingRecipes\Recipe.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Game\Entities\Arrow.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Game\Entities\Bee.cs" />
@@ -41,39 +51,40 @@
<Compile Include="$(MSBuildThisFileDirectory)Game\Tiles\Torches.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Game\Tiles\VegetationTiles.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Game\Tiles\WoodTiles.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Generic\DelayedTask.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FileUtil\StructureFile.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Game\Furniture\Furniture.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Generic\CircularArray.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Generic\RepeatingIntervalTask.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Generic\ThreadSafeValue.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Generic\UniqueQueue.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GraphicsEngine.cs" />
<Compile Include="$(MSBuildThisFileDirectory)IGameMainControllers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ILightingEngine.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Logger.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interfaces\ILightingEngine.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Logging\Logger.cs" />
<Compile Include="$(MSBuildThisFileDirectory)LuaInterop\LuaEvent.cs" />
<Compile Include="$(MSBuildThisFileDirectory)LuaInterop\LuaInterop.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Network\SharedNetworkSubsystem.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Network\Packets\SessonControlPackets.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Network\User.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DataTypes\Rotation.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ParticleEmitter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Shell.cs" />
<Compile Include="$(MSBuildThisFileDirectory)WorldMetadata.cs" />
<Compile Include="$(MSBuildThisFileDirectory)OperatingSystem.cs" />
<Compile Include="$(MSBuildThisFileDirectory)World\WorldMetadata.cs" />
<Compile Include="$(MSBuildThisFileDirectory)World\Chunk.cs" />
<Compile Include="$(MSBuildThisFileDirectory)World\ChunkCoordinates.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Physics\CollisionSolver.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CollisionSolver.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Game\Entities\Entity.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Game\Entities\ItemstackEntity.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Game\Entities\Player.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FileUtil\Configuration.cs" />
<Compile Include="$(MSBuildThisFileDirectory)World\GameWorld.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Globals.cs" />
<Compile Include="$(MSBuildThisFileDirectory)IMessageOutlet.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interfaces\IMessageOutlet.cs" />
<Compile Include="$(MSBuildThisFileDirectory)MonoGameExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Game\Items\Item.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Game\Inventory\ItemStack.cs" />
<Compile Include="$(MSBuildThisFileDirectory)World\Generator.cs" />
<Compile Include="$(MSBuildThisFileDirectory)World\Light3.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Message.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Chat\ChatMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Network\NetworkMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Network\Packet.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Network\TypeSerializer.cs" />
@@ -91,5 +102,9 @@
<ItemGroup>
<Folder Include="$(MSBuildThisFileDirectory)Game\Items\" />
<Folder Include="$(MSBuildThisFileDirectory)DataTypes\" />
<Folder Include="$(MSBuildThisFileDirectory)Game\Structures\" />
<Folder Include="$(MSBuildThisFileDirectory)Logging\" />
<Folder Include="$(MSBuildThisFileDirectory)Chat\" />
<Folder Include="$(MSBuildThisFileDirectory)AssetManagement\" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,6 @@
using Microsoft.Xna.Framework;
using CaveGame.Core.Game.Entities;
using CaveGame.Core.Game.Tiles;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -7,14 +9,26 @@ using System.Text;
namespace CaveGame.Core
{
public class CastResult
public struct TileRaycastResult
{
public bool Hit { get; set; }
public bool Hit { get; set; }
public Tile Target { get; set; }
public Point TileCoordinates { get; set; }
public Vector2 Intersection { get; set; }
public Vector2 SurfaceNormal { get; set; }
public Face Face { get; set; }
}
}
public struct EntityRaycastResult
{
public bool Hit { get; set; }
public IEntity Target { get; set; }
public Vector2 Intersection { get; set; }
public Vector2 SurfaceNormal { get; set; }
public Face Face { get; set; }
}
public struct LineSegment
{
@@ -32,10 +46,6 @@ namespace CaveGame.Core
}
}
public static class CollisionSolver
{
@@ -62,8 +72,6 @@ namespace CaveGame.Core
if (top_hits || bottom_hits || left_hits || right_hits)
{
intersection = seg.B;
if (top_hits && seg.A.Distance(top_intersect) < seg.A.Distance(intersection))
@@ -89,14 +97,8 @@ namespace CaveGame.Core
intersection = right_intersect;
collidingface = Face.Right;
}
return true;
}
return false;
}

23
Core/Enums/DamageType.cs Normal file
View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CaveGame.Core
{
public enum DamageType
{
Frostbite,
Fire,
Lava,
BluntForceTrauma,
PunctureTrauma,
Electrocution,
LacerationTrauma,
Neurotoxin,
Poison,
Explosion,
Psionic,
Asphyxiation,
ActOfGod,
}
}

View File

@@ -1,4 +1,5 @@
using System;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Text;
@@ -12,7 +13,6 @@ namespace CaveGame.Core
Down,
}
/// <summary>
/// Cardinal Directions. Generally used for surface face of a tile.
/// </summary>
@@ -23,9 +23,8 @@ namespace CaveGame.Core
Left,
Right
}
public enum Compass
{
{
North = 0,
Northeast = 45,
East = 90,
@@ -34,37 +33,23 @@ namespace CaveGame.Core
Southwest = 225,
West = 270,
Northwest = 315
}
public static class CardinalDirectionExtension
{
public static Rotation ToRotation(this Compass dir) => Rotation.FromDeg((int)dir);
}
/// <summary>
/// Applied to the following:
/// IEntity, Explosion, and certain tile classes
/// </summary>
public interface IDamageSource { }
public enum DamageType
{
Frostbite,
Fire,
Lava,
BluntForceTrauma,
PunctureTrauma,
Electrocution,
LacerationTrauma,
Neurotoxin,
Poison,
Explosion,
Psionic,
Asphyxiation,
ActOfGod,
}
public static class CardinalDirectionExtension
{
public static Rotation ToRotation(this Compass dir) => Rotation.FromDeg((int)dir);
public static Vector2 ToSurfaceNormal(this Face face)
{
Vector2 normal = Vector2.Zero;
if (face == Face.Top)
normal = new Vector2(0, -1);
if (face == Face.Bottom)
normal = new Vector2(0, 1);
if (face == Face.Left)
normal = new Vector2(-1, 0);
if (face == Face.Right)
normal = new Vector2(1, 0);
return normal;
}
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CaveGame.Core
{
public enum GameSteamAchievement : byte
{
HELLO_WORLD = 0,
MORNING_WOOD = 1,
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CaveGame.Core
{
public enum TextXAlignment
{
Left,
Center,
Right
}
public enum TextYAlignment
{
Top,
Center,
Bottom,
}
}

View File

@@ -12,9 +12,9 @@ namespace CaveGame.Core
public Vector2 Position { get; set; }
public float Rotation { get; set; }
public Rectangle Bounds { get; set; }
public Vector2 WindowSize => GraphicsEngine.Instance.WindowSize;
public Vector2 _screenSize;
public Vector2 _screenSize { get; set; }
public Vector2 TopLeft
{
@@ -50,7 +50,7 @@ namespace CaveGame.Core
Matrix.CreateTranslation(new Vector3(-OutputPosition.X, -OutputPosition.Y, 0)) *
Matrix.CreateRotationZ(Rotation) *
Matrix.CreateScale(Zoom) *
Matrix.CreateTranslation(new Vector3(Bounds.Width * 0.5f, Bounds.Height * 0.5f, 0));
Matrix.CreateTranslation(new Vector3(WindowSize.X * 0.5f, WindowSize.Y * 0.5f, 0));
}
}
@@ -60,8 +60,8 @@ namespace CaveGame.Core
{
var inverseViewMatrix = Matrix.Invert(View);
var tl = Vector2.Transform(Vector2.Zero, inverseViewMatrix);
var tr = Vector2.Transform(new Vector2(_screenSize.X, 0), inverseViewMatrix);
var bl = Vector2.Transform(new Vector2(0, _screenSize.Y), inverseViewMatrix);
var tr = Vector2.Transform(new Vector2(WindowSize.X, 0), inverseViewMatrix);
var bl = Vector2.Transform(new Vector2(0, WindowSize.Y), inverseViewMatrix);
var br = Vector2.Transform(_screenSize, inverseViewMatrix);
var min = new Vector2(
MathHelper.Min(tl.X, MathHelper.Min(tr.X, MathHelper.Min(bl.X, br.X))),
@@ -73,10 +73,8 @@ namespace CaveGame.Core
}
}
public Camera2D(Viewport viewport)
public Camera2D()
{
Bounds = viewport.Bounds;
_screenSize = new Vector2(Bounds.Width, Bounds.Height);
Rotation = 0;
Zoom = 1;
Position = new Vector2(0, 0);

View File

@@ -59,13 +59,15 @@ namespace CaveGame.Core.Game.Items
public virtual string Name => this.GetType().Name;
public virtual string DisplayName => Name;
public virtual Texture2D Texture { get; }
public bool MouseDown { get; set; }
public void Draw(GraphicsEngine GFX, Texture2D texture, Vector2 position, float scale)
{
GFX.Sprite(texture, position, null, Color.Gray, Rotation.Zero, Vector2.Zero, scale, SpriteEffects.None, 0);
} // pseudo- default draw
public virtual void Draw(GraphicsEngine GFX, Vector2 position, float scale) {}
public virtual void Draw(GraphicsEngine GFX, Vector2 position, float scale) => Draw(GFX, Texture, position, scale);
public virtual void OnClientLMBDown(Player player, IGameClient client, ItemStack stack)
{
MouseDown = true;
@@ -201,7 +203,7 @@ namespace CaveGame.Core.Game.Items
if (client.World.GetTile(x, y).ID != 0)
{
client.Send(new DamageTilePacket(new Point(x, y), 2));
client.Send(new DamageTilePacket(new Point(x, y), Strength));
//client.Send(new PlaceTilePacket(0, 0, 0, x, y));
//client.World.SetTile(x, y, new Tiles.Air());
}
@@ -372,12 +374,33 @@ namespace CaveGame.Core.Game.Items
}
}
public class BowItem : Item {
public override int MaxStack => 1;
public override void OnClientLMBDown(Player player, IGameClient client, ItemStack stack)
{
// TODO: Find and comsume arrows in inventory
bool hasArrows = true;
if (!hasArrows)
return;
MouseState mouse = Mouse.GetState();
var mp = client.Camera.ScreenToWorldCoordinates(mouse.Position.ToVector2());
var unitLookVector = player.Position.LookAt(mp);
client.Send(new PlayerThrowItemPacket(ThrownItem.Arrow, Rotation.FromUnitVector(unitLookVector)));
base.OnClientLMBDown(player, client, stack);
}
public override void Draw(GraphicsEngine GFX, Vector2 position, float scale) => Draw(GFX, GFX.BowSprite, position, scale);
}
public class BombItem : Item
{
public override int MaxStack => 99;
public override void OnClientLMBDown(Player player, IGameClient client, ItemStack stack)
{
stack.Quantity--;

View File

@@ -18,7 +18,6 @@ namespace CaveGame.Core.Game.Tiles
public class Diode { }
public class NORGate { }
public class XANDGate { }
public class Delay { }
public class Pump { }
public class Pipe { }
public class Trapdoor { }

View File

@@ -69,7 +69,7 @@ namespace CaveGame.Core.Game.Tiles
// Tile Properties
public virtual float Friction => 1;
public virtual byte Opacity => 3;
public virtual byte Opacity => 48;
public virtual Color Color => Color.White;
public virtual Rectangle Quad => TileMap.Default;
public virtual byte Hardness => 2;

View File

@@ -25,6 +25,7 @@ namespace CaveGame.Core.Game.Tiles
{
public override void Drop(IGameServer server, IGameWorld world, Point tilePosition) { }
public override void Draw(GraphicsEngine GFX, int x, int y, Light3 light) { } // leave empty
}
public class Vacuum { }
public class Fog { }

View File

@@ -89,27 +89,27 @@ namespace CaveGame.Core.Game.Tiles
}
public class Torch : BaseTorch, ILightEmitter
{
public Light3 Light => new Light3(20, 20, 12);
public Light3 Light => new Light3(1f, 0.9f, 0.7f);
public override Color FlameColor => new Color(1, 1, 0.8f);
}
public class WhiteTorch : BaseTorch, ILightEmitter
{
public Light3 Light => new Light3(20, 20, 20);
public Light3 Light => new Light3(1f, 1f, 1f);
public override Color FlameColor => new Color(1, 1, 1f);
}
public class GreenTorch : BaseTorch, ILightEmitter
{
public Light3 Light => new Light3(0, 20, 0);
public Light3 Light => new Light3(0f, 1.2f, 0f);
public override Color FlameColor => new Color(0.1f, 1, 0.1f);
}
public class RedTorch : BaseTorch, ILightEmitter
{
public Light3 Light => new Light3(20, 0, 0);
public Light3 Light => new Light3(1.2f, 0, 0);
public override Color FlameColor => new Color(1, 0.1f, 0.1f);
}
public class BlueTorch : BaseTorch, ILightEmitter
{
public Light3 Light => new Light3(0, 0, 20);
public Light3 Light => new Light3(0, 0, 1.2f);
public override Color FlameColor => new Color(0.2f, 0.2f, 1.0f);
}
}

View File

@@ -36,7 +36,7 @@ namespace CaveGame.Core.Game.Walls
public class Wall
{
public virtual byte Opacity => 1;
public virtual byte Opacity => 4;
public virtual Color Color => Color.Gray;
public virtual Rectangle Quad => TileMap.Default;
public virtual byte Hardness => 2;

View File

@@ -11,7 +11,7 @@ namespace CaveGame.Core.Generic
SubtractIncrement
}
public class DelayedTask
public class RepeatingIntervalTask
{
public TimeStepProcedure TimeStepProcedure { get; set; }
public bool Active { get; set; }
@@ -23,7 +23,7 @@ namespace CaveGame.Core.Generic
private Action job;
public DelayedTask(Action action, float timeIncrement, TimeStepProcedure procedure = TimeStepProcedure.SetToZero)
public RepeatingIntervalTask(Action action, float timeIncrement, TimeStepProcedure procedure = TimeStepProcedure.SetToZero)
{
Active = true;
job = action;

View File

@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CaveGame.Core.Generic
{
public class UniqueQueue<T> : IEnumerable<T>
{
private HashSet<T> hashSet;
private Queue<T> queue;
public UniqueQueue()
{
hashSet = new HashSet<T>();
queue = new Queue<T>();
}
public int Count
{
get
{
return hashSet.Count;
}
}
public void Clear()
{
hashSet.Clear();
queue.Clear();
}
public bool Contains(T item)
{
return hashSet.Contains(item);
}
public void Enqueue(T item)
{
if (hashSet.Add(item))
{
queue.Enqueue(item);
}
}
public T Dequeue()
{
T item = queue.Dequeue();
hashSet.Remove(item);
return item;
}
public T Peek()
{
return queue.Peek();
}
public IEnumerator<T> GetEnumerator()
{
return queue.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return queue.GetEnumerator();
}
}
}

View File

@@ -18,17 +18,17 @@ namespace CaveGame.Core
{
public const int TileSize = 8;
public const int ChunkSize = 32;
public const string CurrentVersionString = "2.2.0";
public const string CurrentVersionFullString = "2.2.0 Beta";
public const int ProtocolVersion = 2;
public const string CurrentVersionString = "2.3.0";
public const string CurrentVersionFullString = "2.3.0 Beta";
public const int ProtocolVersion = 3;
public static UpdateDescription[] UpdateLog =
{
new UpdateDescription
{
VersionString = "2.2.0",
Date = "2020 November 27",
UpdateName = "Multiplayer Update 2",
VersionString = "2.3.0",
Date = "2020 XX XX",
UpdateName = "Multiplayer Update 2.3",
ChangeLog = new string[]{
"+ Added Biomes",
@@ -48,7 +48,7 @@ namespace CaveGame.Core
},
Notes = new string[]{
"This version will be distributed to playtesters only.",
"2.2.1 will be released on Steam in one week."
"2.3 will be released on Steam in one week."
},
},
new UpdateDescription

View File

@@ -12,11 +12,28 @@ using System.Threading.Tasks;
namespace CaveGame.Core
{
using Circle = List<Vector2>;
using Arc = List<Vector2>;
public interface IGraphicsEngine
{
Vector2 WindowSize { get; set; }
SpriteBatch SpriteBatch { get; set; }
SpriteSortMode SpriteSortMode { get; set; }
BlendState BlendState { get; set; }
SamplerState SamplerState { get; set; }
DepthStencilState DepthStencilState { get; set; }
RasterizerState RasterizerState { get; set; }
Effect Shader { get; set; }
Matrix Matrix { get; set; }
void Begin(SpriteSortMode sorting = SpriteSortMode.Deferred, BlendState blending = null, SamplerState sampling = null,
DepthStencilState depthStencil = null, RasterizerState rasterizing = null, Effect effect = null, Matrix? transform = null);
void End();
}
public class MissingGameDataException : ApplicationException {
public string MissingFilename { get; set; }
@@ -101,17 +118,19 @@ namespace CaveGame.Core
public SpriteFont ComicSans10 { get; private set; }
public void LoadAssets(ContentManager Content)
{
Arial8 = Content.Load<SpriteFont>("Fonts/Arial8");
Arial10 = Content.Load<SpriteFont>("Fonts/Arial10");
Arial12 = Content.Load<SpriteFont>("Fonts/Arial12");
Arial14 = Content.Load<SpriteFont>("Fonts/Arial14");
Arial16 = Content.Load<SpriteFont>("Fonts/Arial16");
Arial20 = Content.Load<SpriteFont>("Fonts/Arial20");
Arial30 = Content.Load<SpriteFont>("Fonts/Arial30");
Arial10Italic = Content.Load<SpriteFont>("Fonts/Arial10Italic");
Consolas10 = Content.Load<SpriteFont>("Fonts/Consolas10");
Consolas12 = Content.Load<SpriteFont>("Fonts/Consolas12");
ComicSans10 = Content.Load<SpriteFont>("Fonts/ComicSans10");
Content.RootDirectory = Path.Combine("assets", "fonts");
Arial8 = Content.Load<SpriteFont>("Arial8");
Arial10 = Content.Load<SpriteFont>("Arial10");
Arial12 = Content.Load<SpriteFont>("Arial12");
Arial14 = Content.Load<SpriteFont>("Arial14");
Arial16 = Content.Load<SpriteFont>("Arial16");
Arial20 = Content.Load<SpriteFont>("Arial20");
Arial30 = Content.Load<SpriteFont>("Arial30");
Arial10Italic = Content.Load<SpriteFont>("Arial10Italic");
Consolas10 = Content.Load<SpriteFont>("Consolas10");
Consolas12 = Content.Load<SpriteFont>("Consolas12");
ComicSans10 = Content.Load<SpriteFont>("ComicSans10");
}
}
@@ -123,8 +142,10 @@ namespace CaveGame.Core
public static GraphicsEngine Instance { get; private set; }
#region Texture Shortcuts
public Texture2D Player => Textures["Entities/player.png"];
public Texture2D TitleScreen => Textures["TitleScreen.png"];
public Texture2D EyeOfHorus => Textures["csoft.png"];
public Texture2D ParticleSet => Textures["particles.png"];
@@ -132,29 +153,38 @@ namespace CaveGame.Core
public Texture2D BG => Textures["menu_bg.png"];
public Texture2D Border => Textures["border.png"];
public Texture2D Slot => Textures["slot.png"];
public Texture2D CloudBackground => Textures["clouds.png"];
public Texture2D Starfield => Textures["stars.png"];
public Texture2D BombSprite => Textures["bomb.png"];
public Texture2D Bong => Textures["bong.png"];
public Texture2D Arrow => Textures["arrow.png"];
public Texture2D Bucket => Textures["bucket.png"];
public Texture2D BigPickaxe => Textures["bigpickaxe.png"];
public Texture2D Helmet => Textures["helmet.png"];
public Texture2D Chestplate => Textures["chestplate.png"];
public Texture2D Sword => Textures["sword.png"];
public Texture2D WallScraper => Textures["wallscraper.png"];
public Texture2D PickaxeNew => Textures["pickaxenew.png"];
public Texture2D Scroll => Textures["scroll.png"];
public Texture2D Dynamite => Textures["dynamite.png"];
public Texture2D Workbench => Textures["workbench.png"];
public Texture2D Potion => Textures["potion.png"];
public Texture2D Jetpack => Textures["jetpack.png"];
public Texture2D Door => Textures["door.png"];
public Texture2D ForestPainting => Textures["forestpainting.png"];
public Texture2D Ingot => Textures["ingot.png"];
public Texture2D Leggings => Textures["leggings.png"];
public Texture2D Furnace => Textures["furnace.png"];
public Texture2D Campfire => Textures["campfire.png"];
public Texture2D VoidMonster => Textures["Entities/tortured.png"];
public Texture2D Explosion => Textures["michaelbay.png"];
public Texture2D BowSprite => Textures["items:bow.png"];
public Texture2D BombSprite => Textures["items:bomb.png"];
public Texture2D Bong => Textures["items:bong.png"];
public Texture2D Arrow => Textures["items:arrow.png"];
public Texture2D Bucket => Textures["items:bucket.png"];
public Texture2D BigPickaxe => Textures["items:bigpickaxe.png"];
public Texture2D Helmet => Textures["armor:helmet.png"];
public Texture2D Chestplate => Textures["armor:chestplate.png"];
public Texture2D Sword => Textures["items:sword.png"];
public Texture2D WallScraper => Textures["items:wallscraper.png"];
public Texture2D PickaxeNew => Textures["items:pickaxenew.png"];
public Texture2D Scroll => Textures["items:scroll.png"];
public Texture2D Dynamite => Textures["items:dynamite.png"];
public Texture2D Workbench => Textures["items:workbench.png"];
public Texture2D Potion => Textures["items:potion.png"];
public Texture2D Jetpack => Textures["items:jetpack.png"];
public Texture2D Door => Textures["items:door.png"];
public Texture2D ForestPainting => Textures["items:forestpainting.png"];
public Texture2D Ingot => Textures["items:ingot.png"];
public Texture2D Leggings => Textures["armor:leggings.png"];
public Texture2D Furnace => Textures["items:furnace.png"];
public Texture2D Campfire => Textures["items:campfire.png"];
public Texture2D Player => Textures["entities:player.png"];
public Texture2D ArrowEntity => Textures["entities:arrow.png"];
public Texture2D VoidMonster => Textures["entities:wurmhole.png"];
public Texture2D Bee => Textures["entities:bee.png"];
public Texture2D Goldfish => Textures["entities:gregothy.png"];
//public static Texture2D Campfire => Textures["campfire.png"];
#endregion
@@ -193,26 +223,29 @@ namespace CaveGame.Core
public void LoadAssets(GraphicsDevice graphicsDevice)
{
var texturesPath = Path.Combine("Assets", "Textures");
var texturesPath = Path.Combine("assets", "textures");
if (!Directory.Exists(texturesPath))
throw new MissingContentFolderException { MissingFilename = "texturesPath" };
foreach (var tex in Directory.GetFiles("Assets/Textures/", "*.png"))
foreach (var tex in Directory.GetFiles(texturesPath, "*.png", SearchOption.AllDirectories))
{
LoadingQueue.Enqueue(new TextureDef(
tex.Replace("Assets/Textures/", ""),
tex
));
var trimmedPath = tex.Replace(texturesPath + @"\", "");
var cleanedPath = trimmedPath.Replace(@"\", ":");
// GameConsole.Log($"QTexture {tex} => {cleanedPath} ");
LoadingQueue.Enqueue(new TextureDef(cleanedPath, tex));
TotalTextures++;
}
var entityTexturesPath = Path.Combine("Assets", "Textures", "Entities");
foreach (var tex in Directory.GetFiles("Assets/Textures/Entities/", "*.png"))
#if !EDITOR
/*var entityTexturesPath = Path.Combine("Assets", "Textures", "Entities");
foreach (var tex in Directory.GetFiles(entityTexturesPath, "*.png", SearchOption.AllDirectories))
{
// Texture2D loaded = AssetLoader.LoadTexture(graphicsDevice, tex);
GameConsole.Log($"QTexture {tex} => {tex.Replace(entityTexturesPath + @"\", "")} ");
LoadingQueue.Enqueue(new TextureDef(
tex.Replace("Assets/Textures/", ""),
tex.Replace(entityTexturesPath + @"\", ""),
tex
));
TotalTextures++;
@@ -226,7 +259,8 @@ namespace CaveGame.Core
tex
));
TotalTextures++;
}
}*/
#endif
}
public void Initialize()

View File

@@ -1,108 +0,0 @@
using CaveGame.Core.Game.Entities;
using CaveGame.Core.Generic;
using CaveGame.Core.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
namespace CaveGame.Core
{
public enum TextXAlignment
{
Left,
Center,
Right
}
public enum TextYAlignment
{
Top,
Center,
Bottom,
}
public enum GameSteamAchievement : byte
{
HELLO_WORLD = 0,
MORNING_WOOD = 1,
}
public interface IEntityManager
{
int GetNextEntityNetworkID();
}
public interface ISteamManager
{
bool HasAchievement(GameSteamAchievement ach);
void AwardAchievement(GameSteamAchievement ach);
}
public interface ICaveGame
{
}
public interface ICommonGameDriver
{
IMessageOutlet Output { get; }
}
public interface IGameClient
{
Camera2D Camera { get; }
void Send(Packet p);
IClientWorld World { get; }
}
public interface IGameServer
{
void SendTo(Packet p, User user);
void SendToAll(Packet p);
void SendToAllExcept(Packet p, User exclusion);
User GetConnectedUser(IPEndPoint ep);
void OutputAndChat(string text);
void Chat(string text);
void Chat(string text, Color color);
IServerWorld World { get; }
void SpawnEntity(IEntity entity);
void Update(GameTime gt);
int TickRate { get; }
int MaxPlayers { get; }
IEntityManager EntityManager { get; }
}
// TODO: Make comprehensive render contract
public interface IGraphicsEngine
{
Vector2 WindowSize { get; set; }
SpriteBatch SpriteBatch { get; set; }
SpriteSortMode SpriteSortMode { get; set; }
BlendState BlendState { get; set; }
SamplerState SamplerState { get; set; }
DepthStencilState DepthStencilState { get; set; }
RasterizerState RasterizerState { get; set; }
Effect Shader { get; set; }
Matrix Matrix { get; set; }
void Begin(SpriteSortMode sorting = SpriteSortMode.Deferred, BlendState blending = null, SamplerState sampling = null,
DepthStencilState depthStencil = null, RasterizerState rasterizing = null, Effect effect = null, Matrix? transform = null);
void End();
}
// todo: sound
public interface ISoundEngine
{
}
}

View File

@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CaveGame.Core
{
public interface ICaveGame
{
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CaveGame.Core
{
/// <summary>
/// Applied to the following:
/// IEntity, Explosion, and certain tile classes
/// </summary>
public interface IDamageSource { }
}

View File

@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CaveGame.Core
{
public interface IEntityManager
{
int GetNextEntityNetworkID();
}
}

View File

@@ -0,0 +1,15 @@
using CaveGame.Core.Network;
using System;
using System.Collections.Generic;
using System.Text;
namespace CaveGame.Core
{
public interface IGameClient
{
Camera2D Camera { get; }
void Send(Packet p);
IClientWorld World { get; }
}
}

View File

@@ -0,0 +1,29 @@
using CaveGame.Core.Game.Entities;
using CaveGame.Core.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
namespace CaveGame.Core
{
public interface IGameServer
{
Entity GetEntity(int networkID);
void SendTo(Packet p, User user);
void SendToAll(Packet p);
void SendToAllExcept(Packet p, User exclusion);
User GetConnectedUser(IPEndPoint ep);
void OutputAndChat(string text);
void Chat(string text);
void Chat(string text, Color color);
IServerWorld World { get; }
void SpawnEntity(IEntity entity);
void Update(GameTime gt);
int TickRate { get; }
int MaxPlayers { get; }
IEntityManager EntityManager { get; }
}
}

View File

@@ -8,6 +8,9 @@ namespace CaveGame.Core
public interface ILightingEngine
{
Light3 GetLight(int x, int y);
void SetLight(int x, int y, Light3 val);
Light3 GetLight(Point coords);
//void SetLight(int x, int y, Light3 val);
// void SetLight(Point coords, Light3 val);
void InvokeLight(Point coords, Light3 val);
}
}

View File

@@ -0,0 +1,24 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Text;
namespace CaveGame.Core
{
public interface INetworkingSubsystem
{
DateTime LatestReceiveTimestamp { get; }
DateTime LatestSendTimestamp { get; }
int PacketsReceived { get; }
int PacketsSent { get; }
int TotalBytesSent { get; }
int TotalBytesReceived { get; }
int BytesSentPerSecond { get; }
int BytesReceivedPerSecond { get; }
void Update(GameTime gt);
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CaveGame.Core
{
public interface ISteamManager
{
bool HasAchievement(GameSteamAchievement ach);
void AwardAchievement(GameSteamAchievement ach);
}
}

View File

@@ -12,5 +12,10 @@ namespace CaveGame.Core
public static void Log(string message) => messenger?.Out(message);
public static void Log(string message, Color color) => messenger?.Out(message, color);
public static void LogWTF(string message) => messenger?.Out($"[WTF]: {message}", new Color(0, 0.8f, 0.8f));
public static void LogWarning(string message) => messenger?.Out($"[WARN]: {message}", new Color(1, 0.7f, 0));
public static void LogProblem(string message) => messenger?.Out($"[PROBLEM]: {message}", new Color(1, 0f, 0.5f));
public static void LogError(string message) => messenger?.Out($"[ERROR]: {message}", new Color(1, 0f, 0));
}
}

View File

@@ -11,7 +11,10 @@ namespace CaveGame.Core
string date = DateTime.Now.ToString("yy-MM-dd");
if (!Directory.Exists("logs"))
Directory.CreateDirectory("logs");
File.AppendAllText($"logs/log_{date}.txt", DateTime.Now.ToString("HH-mm-ss") + ": " + data + "\n");
File.AppendAllText(
Path.Combine("logs", $"log_{date}.txt"),
DateTime.Now.ToString("HH-mm-ss") + ": " + data + "\n"
);
}
}
}

View File

@@ -12,13 +12,35 @@ namespace CaveGame.Core
public static class MonoGameExtensions
{
public static Vector2 LookAt(this Vector2 origin, Vector2 goal)
public static Rectangle GetSpriteFrame(this Rectangle[] animation, float animationTime)
{
Vector2 UnitVector = (goal - origin);
UnitVector.Normalize();
return UnitVector;
int anim_length = animation.Length;
return animation[(int)(animationTime % anim_length)];
}
public static Point ToTileCoords(this Vector2 grug) => new Point(
(int)Math.Floor(grug.X / Globals.TileSize),
(int)Math.Floor(grug.Y / Globals.TileSize)
);
public static Vector2 RoundTo(this Vector2 og, int decimalplaces)
{
return new Vector2((float)Math.Round(og.X, decimalplaces), (float)Math.Round(og.Y, decimalplaces));
}
public static Vector2 GetY(this Vector2 vec) => new Vector2(0, vec.Y);
public static Vector2 GetX(this Vector2 vec) => new Vector2(vec.X, 0);
public static Vector2 LookAt(this Vector2 origin, Vector2 goal) => (goal - origin).Unit();
public static Vector2 Unit(this Vector2 vector)
{
Vector2 copy = vector;
copy.Normalize();
return copy;
}
public static float Distance(this Vector2 a, Vector2 b) => (a - b).Length();
public static Color Inverse(this Color color)

View File

@@ -82,7 +82,10 @@ namespace CaveGame.Core.Network
cAdminCommand,
cThrowItem,
cDamageFurniture,
cDamageTile,
cPlaceTile,
cPlaceWall,
cDamageWall,
#endregion
#region Server-Sender Packets
@@ -254,7 +257,6 @@ namespace CaveGame.Core.Network
BlastRadius = Payload.ReadFloat(8), // 4 bytes (12)
BlastPressure = Payload.ReadFloat(12)// 4 bytes (16)
};
set
{
Payload.WriteVector2(0, value.Position);
@@ -439,7 +441,7 @@ namespace CaveGame.Core.Network
set => Payload = value.ToMetabinary().Serialize();
}
}
public class EntityPositionPacket : Packet
public class EntityPhysicsStatePacket : Packet
{
public int EntityID
{
@@ -472,7 +474,7 @@ namespace CaveGame.Core.Network
}
public EntityPositionPacket(int id, int health, Vector2 position, Vector2 velocity, Vector2 nextPosition) : base(PacketType.netEntityPhysicsUpdate) {
public EntityPhysicsStatePacket(int id, int health, Vector2 position, Vector2 velocity, Vector2 nextPosition) : base(PacketType.netEntityPhysicsUpdate) {
Payload = new byte[32];
EntityID = id;
Health = health;
@@ -481,7 +483,7 @@ namespace CaveGame.Core.Network
NextPosition = nextPosition;
}
public EntityPositionPacket(IEntity entity) : base(PacketType.netEntityPhysicsUpdate)
public EntityPhysicsStatePacket(IEntity entity) : base(PacketType.netEntityPhysicsUpdate)
{
Payload = new byte[32];
EntityID = entity.EntityNetworkID;
@@ -494,7 +496,7 @@ namespace CaveGame.Core.Network
}
}
public EntityPositionPacket(byte[] bytes) : base(bytes) { }
public EntityPhysicsStatePacket(byte[] bytes) : base(bytes) { }
}
public class PlaceFurniturePacket : Packet
{
@@ -743,6 +745,7 @@ namespace CaveGame.Core.Network
public enum ThrownItem : byte
{
Bomb,
Arrow,
}
public class PlayerThrowItemPacket : Packet
@@ -881,6 +884,7 @@ namespace CaveGame.Core.Network
WorldX = worldX;
WorldY = worldY;
}
public PlaceWallPacket(Point coords, Wall w) : this(w.ID, 0, w.Damage, coords.X, coords.Y) { }
}
public class PlaceTilePacket : Packet
{
@@ -918,6 +922,15 @@ namespace CaveGame.Core.Network
WorldX = worldX;
WorldY = worldY;
}
public PlaceTilePacket(Point coords, Tile t) : this(t.ID, t.TileState, t.Damage, coords.X, coords.Y) { }
}
public class ServerUpdateTilePacket : Packet
{
public ServerUpdateTilePacket() : base(PacketType.sUpdateTile) { }
}
public class ServerUpdateWallPacket : Packet
{
public ServerUpdateWallPacket() : base(PacketType.sUpdateWall) { }
}
// temporary?
public class RequestChunkPacket : Packet

View File

@@ -6,6 +6,13 @@ using System.Text;
namespace CaveGame.Core.Network.Packets
{
public class PingPacket : Packet
{
public PingPacket() : base(PacketType.netPing) { Payload = new byte[4]; } // pretty much empty lol
public PingPacket(byte[] data) : base(data) { }
}
/// <summary>
///
/// </summary>

View File

@@ -0,0 +1,92 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace CaveGame.Core.Network
{
public struct OutgoingPayload
{
public Packet Payload { get; set; }
public IPEndPoint TargetAddress { get; set; }
}
public class SharedNetworkSubsystem
{
public const int NAP_TIME_MILLISECONDS = 1;
public const int SIO_UDP_CONNRESET = -1744830452;
public const int PROTOCOL_VERSION = Globals.ProtocolVersion;
protected ConcurrentQueue<NetworkMessage> IncomingMessages { get; private set; }
protected ConcurrentQueue<OutgoingPayload> OutgoingMessages { get; private set; }
public virtual DateTime LatestReceiveTimestamp { get; protected set; }
public virtual DateTime LatestSendTimestamp { get; protected set; }
public virtual int PacketsReceived { get; protected set; }
public virtual int PacketsSent { get; protected set; }
public virtual int TotalBytesSent { get; protected set; }
public virtual int TotalBytesReceived { get; protected set; }
public virtual int BytesSentPerSecond { get; protected set; }
public virtual int BytesReceivedPerSecond { get; protected set; }
public virtual IMessageOutlet Output { get; set; }
public virtual int Port { get; protected set; }
protected int InternalReceiveCount { get; set; }
protected int InternalSendCount { get; set; }
private float counter = 0;
public UdpClient UdpSocket { get; protected set; }
protected void IOControlFixICMPBullshit()
{
UdpSocket.Client.IOControl(
(IOControlCode)SIO_UDP_CONNRESET,
new byte[] { 0, 0, 0, 0 },
null
);
}
public SharedNetworkSubsystem()
{
IncomingMessages = new ConcurrentQueue<NetworkMessage>();
OutgoingMessages = new ConcurrentQueue<OutgoingPayload>();
IOControlFixICMPBullshit();
}
private void ResetByteCounters()
{
BytesSentPerSecond = InternalSendCount;
BytesReceivedPerSecond = InternalReceiveCount;
InternalSendCount = 0;
InternalReceiveCount = 0;
counter = 0;
}
public virtual void Update(GameTime gt)
{
counter += gt.GetDelta();
if (counter > (1.0f))
ResetByteCounters();
}
public virtual void Start()
{
}
public virtual void Close()
{
}
}
}

View File

@@ -32,6 +32,11 @@ namespace CaveGame.Core.Network
{
return dispatchedPackets.Count > 0;
}
public void SendDispatchMessages(IGameServer server)
{
if (DispatcherHasMessage())
server.SendTo(PopDispatcherQueue(), this);
}
public Packet PopDispatcherQueue()
{

View File

@@ -26,23 +26,17 @@ namespace CaveGame.Core
catch
{
// hack because of this: https://github.com/dotnet/corefx/issues/10361
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (IsWindows())
{
url = url.Replace("&", "^&");
Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true });
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
else if (IsLinux())
Process.Start("xdg-open", url);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
else if (IsMacOS())
Process.Start("open", url);
}
else
{
throw;
}
}
}
}

View File

@@ -46,13 +46,14 @@ namespace CaveGame.Core
public static Vector2 Origin = new Vector2(2, 2);
public static Vector2 Friction = new Vector2(0.8f, 0.8f);
public static float Mass = 0.1f;
public static Vector2 BoundingBox = new Vector2(2, 2);
public override float MaxParticleAge => 2.0f;
public Rotation Rotation { get; set; }
public Vector2 Position { get; set; }
public Color Color { get; set; }
public float Scale { get; set; }
public Vector2 Scale { get; set; }
public Vector2 NextPosition;
public Vector2 Velocity;
public Vector2 Accelleration;
@@ -61,7 +62,7 @@ namespace CaveGame.Core
public void Initialize(Vector2 _position, Color _color, Rotation _rotation, float _scale, Vector2 _accel)
public void Initialize(Vector2 _position, Color _color, Rotation _rotation, Vector2 _scale, Vector2 _accel)
{
ParticleAge = 0;
Position = _position;
@@ -83,6 +84,38 @@ namespace CaveGame.Core
public override void PhysicsStep(IGameWorld world, float step)
{
var tilePosition = new Point(
(int)Math.Floor(Position.X / Globals.TileSize),
(int)Math.Floor(Position.Y / Globals.TileSize)
);
int bb = 2;
for (int x = -bb; x < bb; x++)
{
for (int y = -bb; y < bb; y++)
{
Point tileBoxPos = new Point(tilePosition.X + x, tilePosition.Y + y);
var tile = world.GetTile(tileBoxPos.X, tileBoxPos.Y);
if (tile.ID != 0 && (!(tile is INonSolid) || tile is ILiquid))
{
var tileChec = (tileBoxPos.ToVector2() * Globals.TileSize) + new Vector2(4, 4);
var tileBoxSize = new Vector2(4, 4);
if (CollisionSolver.CheckAABB(NextPosition, BoundingBox * Scale, tileChec, tileBoxSize))
{
var separation = CollisionSolver.GetSeparationAABB(NextPosition, BoundingBox, tileChec, tileBoxSize);
var normal = CollisionSolver.GetNormalAABB(separation, Velocity);
if (tile.ID > 0 && !(tile is INonSolid))
{
NextPosition += separation;
}
}
}
}
}
Velocity += (Accelleration * step*3);
Accelleration -= (Accelleration * step*3);
@@ -173,6 +206,70 @@ namespace CaveGame.Core
}
}
public class ExplosionParticle : Particle
{
public static Rectangle SP_EXPLOSION0 = new Rectangle(0, 0, 32, 32);
public static Rectangle SP_EXPLOSION1 = new Rectangle(32, 0, 32, 32);
public static Rectangle SP_EXPLOSION2 = new Rectangle(64, 0, 32, 32);
public static Rectangle SP_EXPLOSION3 = new Rectangle(96, 0, 32, 32);
public static Rectangle SP_EXPLOSION4 = new Rectangle(128, 0, 32, 32);
public static Rectangle[] ANIM =
{
SP_EXPLOSION0,
SP_EXPLOSION1,
SP_EXPLOSION2,
SP_EXPLOSION3,
SP_EXPLOSION4,
};
public static Vector2 Origin => new Vector2(16, 16);
public override float MaxParticleAge => 0.5f;
public Vector2 Position { get; set; }
public Vector2 Scale { get; set; }
public Color Color { get; set; }
Rotation Rotation { get; set; }
Random RNG = new Random();
public ExplosionParticle()
{
Rotation = Rotation.FromDeg(RNG.Next(0, 360));
}
public override void Update(GameTime gt)
{
ParticleAge += (float)gt.ElapsedGameTime.TotalSeconds;
}
public override void PhysicsStep(IGameWorld world, float step)
{
// base.PhysicsStep(world, step);
}
public override void Draw(GraphicsEngine gfx)
{
var quad = ANIM.GetSpriteFrame( (ParticleAge/MaxParticleAge) * (ANIM.Length-1));
gfx.Sprite(gfx.Explosion, Position, quad, Color.White, Rotation, Origin, Scale, SpriteEffects.None, 0);
}
public void Initialize(Vector2 _position, Color _color, Rotation _rotation, Vector2 _scale)
{
ParticleAge = 0;
Position = _position;
Color = _color;
Scale = _scale;
Dead = false;
}
}
public class FireParticle
{
@@ -197,7 +294,8 @@ namespace CaveGame.Core
ObjectPool<SmokeParticle> SmokeParticlePool = new ObjectPool<SmokeParticle>(() => new SmokeParticle());
//ObjectPool<SmokeParticle> SmokeParticlePool = new ObjectPool<SmokeParticle>(() => new SmokeParticle());
ObjectPool<ExplosionParticle> ExplosionParticlePool = new ObjectPool<ExplosionParticle>(() => new ExplosionParticle());
private List<Particle> Particles;
public IGameWorld World { get; set; }
@@ -212,13 +310,19 @@ namespace CaveGame.Core
public void Add(Particle p) => Particles.Add(p);
public void EmitSmokeParticle(Vector2 position, Color color, Rotation rotation, float scale, Vector2 accel)
public void EmitSmokeParticle(Vector2 position, Color color, Rotation rotation, Vector2 scale, Vector2 accel)
{
var myParticle = SmokeParticlePool.Get();
myParticle.Initialize(position, color, rotation, scale, accel);
Add(myParticle);
}
public void EmitExplosionParticle(Vector2 position)
{
var myParticle = ExplosionParticlePool.Get();
myParticle.Initialize(position, Color.White, Rotation.Zero, new Vector2(2.0f));
Add(myParticle);
}
public void Update(GameTime gt)
{
@@ -230,13 +334,25 @@ namespace CaveGame.Core
if (particle.ParticleAge > particle.MaxParticleAge)
particle.Dead = true;
if (particle.Dead && particle is SmokeParticle smokey)
if (particle.Dead)
{
SmokeParticlePool.Return(smokey);
if (particle is SmokeParticle smokey)
SmokeParticlePool.Return(smokey);
Particles.Remove(particle);
continue;
}
if (particle.Dead)
{
if (particle is ExplosionParticle bomb)
ExplosionParticlePool.Return(bomb);
Particles.Remove(particle);
continue;
}
particle.Update(gt);
}
}

View File

@@ -8,12 +8,10 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using CaveGame.Core.Game.Walls;
using System.Collections.Concurrent;
namespace CaveGame.Core
{
public class Chunk
{
public static bool RefreshedThisFrame = false;
@@ -26,21 +24,20 @@ namespace CaveGame.Core
public Light3[,] Lights;
public Tile[,] Tiles;
public bool[,] NetworkUpdated;
public bool[,] TileUpdate;
public Wall[,] Walls;
public ChunkCoordinates Coordinates;
public bool UpdateRenderBuffer = true;
public bool WallBufferNeedsRedrawn = true;
public bool TileBufferNeedsRedrawn = false;
public RenderTarget2D TileRenderBuffer { get; set; }
public RenderTarget2D WallRenderBuffer { get; set; }
public RenderTarget2D ForegroundRenderBuffer;
public RenderTarget2D BackgroundRenderBuffer;
public Chunk(int X, int Y)
{
Coordinates = new ChunkCoordinates(X, Y);
NetworkUpdated = new bool[ChunkSize, ChunkSize];
Tiles = new Tile[ChunkSize, ChunkSize];
TileUpdate = new bool[ChunkSize, ChunkSize];
Walls = new Wall[ChunkSize, ChunkSize];
Lights = new Light3[ChunkSize, ChunkSize];
@@ -53,15 +50,22 @@ namespace CaveGame.Core
SetWall(x, y, new Game.Walls.Air());
}
}
}
bool disposed;
~Chunk() => Dispose(false);
public void ClearUpdateQueue()
{
NetworkUpdated = new bool[ChunkSize, ChunkSize];
TileUpdate = new bool[ChunkSize, ChunkSize];
}
public void Dispose() => Dispose(true);
protected void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing)
{
TileRenderBuffer?.Dispose();
WallRenderBuffer?.Dispose();
}
}
public void FromData(byte[] data)
{
@@ -126,48 +130,25 @@ namespace CaveGame.Core
return data;
}
public void SetTileUpdated(int x, int y)
{
TileUpdate[x, y] = true;
UpdateRenderBuffer = true;
}
public Tile GetTile(int x, int y)=> Tiles[x, y];
public void SetTile(int x, int y, Tile t)
{
//Debug.WriteLine("TT " + t.TileState);
if (Tiles[x,y] == null || Tiles[x, y].Equals(t) == false)
{
Tiles[x, y] = t;
NetworkUpdated[x, y] = true; // TODO: Create WallReplicate and TileReplicate queues
UpdateRenderBuffer = true;
}
Tiles[x, y] = t;
TileBufferNeedsRedrawn = true;
}
public Tile GetTile(int x, int y)
public Wall GetWall(int x, int y) => Walls[x, y];
public void SetWall(int x, int y, Wall w)
{
return Tiles[x, y];
Walls[x, y] = w;
WallBufferNeedsRedrawn = true;
}
public void RedrawTileBuffer(GraphicsEngine GFX)
{
if (TileRenderBuffer == null)
TileRenderBuffer = new RenderTarget2D(GFX.GraphicsDevice, ChunkSize * Globals.TileSize, ChunkSize * Globals.TileSize);
public Wall GetWall(int x, int y) {
return Walls[x, y];
}
public void SetWall(int x, int y, Wall w) {
// if (Walls[x, y] == null)
// {
Walls[x, y] = w;
NetworkUpdated[x, y] = true; // TODO: Create WallReplicate and TileReplicate queues
UpdateRenderBuffer = true;
// }
}
private void DrawForegroundBuffer(GraphicsEngine GFX)
{
if (ForegroundRenderBuffer == null)
ForegroundRenderBuffer = new RenderTarget2D(GFX.GraphicsDevice, ChunkSize * Globals.TileSize, ChunkSize * Globals.TileSize);
GFX.GraphicsDevice.SetRenderTarget(ForegroundRenderBuffer);
GFX.GraphicsDevice.SetRenderTarget(TileRenderBuffer);
GFX.Clear(Color.Black * 0f);
GFX.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
@@ -175,7 +156,7 @@ namespace CaveGame.Core
for (int x = 0; x < ChunkSize; x++)
{
for(int y = 0; y<ChunkSize; y++)
for (int y = 0; y < ChunkSize; y++)
{
tile = GetTile(x, y);
if (tile.ID > 0)
@@ -186,12 +167,12 @@ namespace CaveGame.Core
GFX.GraphicsDevice.SetRenderTarget(null);
}
private void DrawBackgroundBuffer(GraphicsEngine GFX)
{
if (BackgroundRenderBuffer == null)
BackgroundRenderBuffer = new RenderTarget2D(GFX.GraphicsDevice, ChunkSize * Globals.TileSize, ChunkSize * Globals.TileSize);
public void RedrawWallBuffer(GraphicsEngine GFX)
{
if (WallRenderBuffer == null)
WallRenderBuffer = new RenderTarget2D(GFX.GraphicsDevice, ChunkSize * Globals.TileSize, ChunkSize * Globals.TileSize);
GFX.GraphicsDevice.SetRenderTarget(BackgroundRenderBuffer);
GFX.GraphicsDevice.SetRenderTarget(WallRenderBuffer);
GFX.Clear(Color.Black * 0f);
GFX.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
@@ -203,23 +184,13 @@ namespace CaveGame.Core
{
wall = GetWall(x, y);
if (wall.ID > 0)
{
wall.Draw(GFX, x, y, Lights[x, y]);
}
}
}
GFX.End();
GFX.GraphicsDevice.SetRenderTarget(null);
}
public void Draw(GraphicsEngine GFX)
{
Chunk.RefreshedThisFrame = true;
// cock and ball torture
UpdateRenderBuffer = false;
DrawBackgroundBuffer(GFX);
DrawForegroundBuffer(GFX);
}
}
}

View File

@@ -19,10 +19,20 @@ namespace CaveGame.Core
public bool Thermal { get; set; }
}
public enum NetworkContext
{
Server,
Client
}
public interface IGameWorld
{
bool IsServer();
bool IsClient();
NetworkContext Context { get; }
float TimeOfDay { get; set; }
void Explosion(Explosion Blast, bool DamageTiles, bool DamageEntities);
List<IEntity> Entities { get; }
@@ -32,7 +42,7 @@ namespace CaveGame.Core
void GetTile(int x, int y, out Tile t);
Wall GetWall(int x, int y);
void SetWall(int x, int y, Wall w);
void SetTileNetworkUpdated(int x, int y);
void DoUpdatePropogation(int x, int y);
void BreakTile(int x, int y);
void SetTileUpdated(int x, int y);
@@ -42,10 +52,16 @@ namespace CaveGame.Core
void RemoveFurniture(FurnitureTile furn);
FurnitureTile GetFurniture(int networkID);
void Update(GameTime gt);
CastResult TileRaycast(Vector2 origin, Rotation direction, float maxDistance = 1000f, bool detectLiquids = false, bool detectNonSolids = false);
}
TileRaycastResult TileRaycast(Vector2 origin, Rotation direction, float maxDistance = 100f, bool detectLiquids = false, bool detectNonSolids = false);
EntityRaycastResult EntityRaycast(Vector2 origin, Rotation direction, float maxDistance = 100f);
}
public interface IServerWorld : IGameWorld { }
public interface IServerWorld : IGameWorld
{
//void SetTileNetworkUpdated(int x, int y);
void RequestTileNetworkUpdate(Point p);
void RequestWallNetworkUpdate(Point p);
}
public interface IClientWorld: IGameWorld
{
@@ -60,97 +76,117 @@ namespace CaveGame.Core
}
public abstract class World : IGameWorld
{
public bool IsServer() => (Context == NetworkContext.Server);
public bool IsClient() => (Context == NetworkContext.Client);
public float TimeOfDay { get; set; }
public Light3[] AmbientLights = // depends on time of day
{
new Light3(0,0,0), //0 or 24
new Light3(0,0,0), //1
new Light3(0,0,0), //2
new Light3(0,0,0), //3
new Light3(0,0,0), //4
new Light3(0,0,0), //5
new Light3(0,0,0), //6
new Light3(0,0,0), //7
new Light3(0,0,0), //8
new Light3(0,0,0), //9
new Light3(0,0,0), //10
new Light3(0,0,0), //11
new Light3(0,0,0), //12
new Light3(0,0,0), //13
new Light3(0,0,0), //14
new Light3(0,0,0), //15
new Light3(0,0,0), //16
new Light3(0,0,0), //17
new Light3(0,0,0), //18
new Light3(0,0,0), //19
new Light3(0,0,0), //20
new Light3(0,0,0), //21
new Light3(0,0,0), //22
new Light3(0,0,0), //23
Light3.Moonlight, //0 or 24
Light3.Moonlight, //1
Light3.Moonlight, //2
Light3.Moonlight, //3
new Light3(60, 40, 40), //4
new Light3(70, 70, 40), //5
new Light3(90, 90, 60), //6
new Light3(128, 128, 90), //7
Light3.Daylight, //8
Light3.Daylight, //9
Light3.Daylight, //10
Light3.Daylight, //11
Light3.Daylight, //12
Light3.Daylight, //13
Light3.Daylight, //14
Light3.Daylight, //15
Light3.Daylight, //16
Light3.Daylight, //17
Light3.Moonlight, //18
Light3.Moonlight, //19
Light3.Moonlight, //20
Light3.Moonlight, //21
Light3.Moonlight, //22
Light3.Moonlight, //23
};
public Color[] SkyColors =
{
new Color(0, 2, 6), new Color(5, 5, 30), //0 or 24
new Color(2, 2, 10), new Color(16, 16, 40), //2
new Color(2, 2, 10), new Color(20, 20, 45), //4
new Color(8, 9, 50), new Color(85, 85, 40), //6
new Color(40, 60, 90), new Color(90, 90, 190), //8
new Color(70, 90, 130), new Color(110, 110, 230), //10
new Color(70, 80, 170), new Color(170, 170, 255), //12
new Color(80, 100, 140), new Color(140, 140, 250), //14
new Color(35, 41, 60), new Color(60, 80, 140), //14
new Color(50, 32, 50), new Color(170, 100, 70), // 18
new Color(25, 25, 55), new Color(92, 52, 23), //20
new Color(5, 7, 14), new Color(9, 23, 45), //22
};
#region PhysicsConstants
public const float PhysicsStepIncrement = 1 / 100.0f;
public const float Gravity = 6.0f;
public const float AirResistance = 1.5f;
public const float TerminalVelocity = 180.0f;
#endregion
protected List<RepeatingIntervalTask> WorldTimedTasks { get; set; }
public World()
{
TileUpdateQueue = new UniqueQueue<Point>();
WallUpdateQueue = new UniqueQueue<Point>();
Entities = new List<IEntity>();
Chunks = new ConcurrentDictionary<ChunkCoordinates, Chunk>();
Furniture = new List<Furniture.FurnitureTile>();
WorldTimedTasks = new List<RepeatingIntervalTask>();
WorldTimedTasks.Add(new RepeatingIntervalTask(PhysicsStep, PhysicsStepIncrement, TimeStepProcedure.SubtractIncrement));
}
protected UniqueQueue<Point> TileUpdateQueue { get; set; }
protected UniqueQueue<Point> WallUpdateQueue { get; set; }
public void RequestTileUpdate(Point position)=>TileUpdateQueue.Enqueue(position);
public void RequestWallUpdate(Point position) => WallUpdateQueue.Enqueue(position);
public ConcurrentDictionary<ChunkCoordinates, Chunk> Chunks { get; set; }
public List<IEntity> Entities { get; protected set; }
public virtual List<Furniture.FurnitureTile> Furniture { get; protected set; }
public GameSessionType SessionType { get; set; }
public GameSessionType SessionType { get; protected set; }
public NetworkContext Context { get; protected set; }
public void SetTileNetworkUpdated(int x, int y)
{
int chunkX = (int)Math.Floor((double)x / Globals.ChunkSize);
int chunkY = (int)Math.Floor((double)y / Globals.ChunkSize);
var tileX = x.Mod(Globals.ChunkSize);
var tileY = y.Mod(Globals.ChunkSize);
var coords = new ChunkCoordinates(chunkX, chunkY);
if (Chunks.ContainsKey(coords))
{
var chunk = Chunks[coords];
chunk.NetworkUpdated[tileX, tileY] = true;
}
}
public virtual void SetTileNoLight(int x, int y, Tile t)
{
throw new NotImplementedException();
}
public virtual void BreakTile(int x, int y)
{
}
public void SetTile(Point p, Tile t)=>SetTile(p.X, p.Y, t);
public Tile GetTile (Point p) => GetTile(p.X, p.Y);
public Tile GetTile(int x, int y)
{
Coordinates6D coordinates = Coordinates6D.FromWorld(x, y);
var cc = new ChunkCoordinates(coordinates.ChunkX, coordinates.ChunkY);
if (Chunks.ContainsKey(cc))
return Chunks[cc].GetTile(coordinates.TileX, coordinates.TileY);
return new Game.Tiles.Void();
}
public Tile GetTile(Point coords) => GetTile(coords.X, coords.Y);
public void GetTile(int x, int y, out Tile t)
{
Coordinates6D coordinates = Coordinates6D.FromWorld(x, y);
var cc = new ChunkCoordinates(coordinates.ChunkX, coordinates.ChunkY);
t = new Game.Tiles.Void();
if (Chunks.ContainsKey(cc))
t = Chunks[cc].GetTile(coordinates.TileX, coordinates.TileY);
}
public virtual void SetTile(int x, int y, Tile t)
{
@@ -169,6 +205,46 @@ namespace CaveGame.Core
}
DoUpdatePropogation(x, y);
}
public void SetTile(Point p, Tile t) => SetTile(p.X, p.Y, t);
public Wall GetWall(int x, int y)
{
Coordinates6D coordinates = Coordinates6D.FromWorld(x, y);
var cc = new ChunkCoordinates(coordinates.ChunkX, coordinates.ChunkY);
if (Chunks.ContainsKey(cc))
return Chunks[cc].GetWall(coordinates.TileX, coordinates.TileY);
return new Game.Walls.Void();
}
public Wall GetWall(Point coords) => GetWall(coords.X, coords.Y);
public void GetWall(int x, int y, out Wall w)
{
Coordinates6D coordinates = Coordinates6D.FromWorld(x, y);
var cc = new ChunkCoordinates(coordinates.ChunkX, coordinates.ChunkY);
w = new Game.Walls.Void();
if (Chunks.ContainsKey(cc))
w = Chunks[cc].GetWall(coordinates.TileX, coordinates.TileY);
}
public virtual void SetWall(int x, int y, Wall w)
{
Coordinates6D coordinates = Coordinates6D.FromWorld(x, y);
var cc = new ChunkCoordinates(coordinates.ChunkX, coordinates.ChunkY);
if (Chunks.ContainsKey(cc))
Chunks[cc].SetWall(coordinates.TileX, coordinates.TileY, w);
}
public void SetWall(Point p, Wall w) => SetWall(p.X, p.Y, w);
public void SetTileUpdated(int x, int y)
{
int chunkX = (int)Math.Floor((double)x / Globals.ChunkSize);
@@ -182,66 +258,17 @@ namespace CaveGame.Core
if (Chunks.ContainsKey(coords))
{
var chunk = Chunks[coords];
chunk.SetTileUpdated(tileX, tileY);
//chunk.SetTileUpdated(tileX, tileY);
}
}
public void DoUpdatePropogation(int x, int y)
{
SetTileUpdated(x, y);
SetTileUpdated(x, y + 1);
SetTileUpdated(x, y - 1);
SetTileUpdated(x + 1, y);
SetTileUpdated(x - 1, y);
}
public Tile GetTile(int x, int y)
{
Coordinates6D coordinates = Coordinates6D.FromWorld(x, y);
var cc = new ChunkCoordinates(coordinates.ChunkX, coordinates.ChunkY);
if (Chunks.ContainsKey(cc))
return Chunks[cc].GetTile(coordinates.TileX, coordinates.TileY);
return new Game.Tiles.Void();
}
public void GetTile(int x, int y, out Tile t)
{
Coordinates6D coordinates = Coordinates6D.FromWorld(x, y);
var cc = new ChunkCoordinates(coordinates.ChunkX, coordinates.ChunkY);
t = new Game.Tiles.Void();
if (Chunks.ContainsKey(cc))
t = Chunks[cc].GetTile(coordinates.TileX, coordinates.TileY);
}
public Wall GetWall(int x, int y)
{
Coordinates6D coordinates = Coordinates6D.FromWorld(x, y);
var cc = new ChunkCoordinates(coordinates.ChunkX, coordinates.ChunkY);
if (Chunks.ContainsKey(cc))
return Chunks[cc].GetWall(coordinates.TileX, coordinates.TileY);
return new Game.Walls.Void();
}
public void GetWall(int x, int y, out Wall w)
{
Coordinates6D coordinates = Coordinates6D.FromWorld(x, y);
var cc = new ChunkCoordinates(coordinates.ChunkX, coordinates.ChunkY);
w = new Game.Walls.Void();
if (Chunks.ContainsKey(cc))
w = Chunks[cc].GetWall(coordinates.TileX, coordinates.TileY);
}
public virtual void SetWall(int x, int y, Wall w)
{
Coordinates6D coordinates = Coordinates6D.FromWorld(x, y);
var cc = new ChunkCoordinates(coordinates.ChunkX, coordinates.ChunkY);
if (Chunks.ContainsKey(cc))
Chunks[cc].SetWall(coordinates.TileX, coordinates.TileY, w);
RequestTileUpdate(new Point(x, y));
RequestTileUpdate(new Point(x, y + 1));
RequestTileUpdate(new Point(x, y - 1));
RequestTileUpdate(new Point(x + 1, y));
RequestTileUpdate(new Point(x - 1, y));
}
public virtual void OnCollectDeadEntity(IEntity ent)
@@ -294,13 +321,14 @@ namespace CaveGame.Core
TimeOfDay += (float)gt.ElapsedGameTime.TotalSeconds/30.0f;
//Profiler.Start("EntityClear");
foreach (var ent in Entities.ToArray())
if (ent.Dead)
OnCollectDeadEntity(ent);
//Entities.RemoveAll(e => e.Dead);
physicsTask.Update(gt);
foreach (var task in WorldTimedTasks)
task.Update(gt);
}
protected virtual void PhysicsStep() { }
@@ -443,82 +471,62 @@ namespace CaveGame.Core
return false;
}
public CastResult TileRaycast(Vector2 origin, Rotation direction, float maxDistance = 120, bool detectLiquids = false, bool detectNonSolids = false)
{
const float ray_accuracy = 0.15f;
Vector2 last_pt = origin;
const float ray_accuracy = 0.15f;
public TileRaycastResult TileRaycast(Vector2 origin, Rotation direction, float maxDistance = 120, bool detectLiquids = false, bool detectNonSolids = false)
{
for (float i = 0; i < maxDistance; i += ray_accuracy)
{
Vector2 current_pt = origin + (direction.ToUnitVector() * i);
Point tile_coords = new Point(
(int) Math.Floor(current_pt.X / 8),
(int) Math.Floor(current_pt.Y / 8)
(int)Math.Floor(current_pt.X / 8),
(int)Math.Floor(current_pt.Y / 8)
);
Tile tileAt = GetTile(tile_coords.X, tile_coords.Y);
Tile tileAt = GetTile(tile_coords.X, tile_coords.Y);
if (tileAt.ID > 0 && !(tileAt is INonSolid))
{
Vector2 tile_corner = (tile_coords.ToVector2() * 8);
Vector2 tile_size = new Vector2(8, 8);
if (tileAt.ID == 0 || (tileAt is INonSolid))
continue;
LineSegment ray_travel_segment = new LineSegment(origin, current_pt);
Rectangle tile_rect = new Rectangle(tile_corner.ToPoint(), tile_size.ToPoint());
Vector2 tile_corner = (tile_coords.ToVector2() * 8);
Vector2 tile_size = new Vector2(8, 8);
LineSegment ray_travel_segment = new LineSegment(origin, current_pt);
Rectangle tile_rect = new Rectangle(tile_corner.ToPoint(), tile_size.ToPoint());
if (CollisionSolver.Intersects(ray_travel_segment, tile_rect, out Vector2 intersection, out Face face))
{
Vector2 normal = Vector2.Zero;
if (!CollisionSolver.Intersects(ray_travel_segment, tile_rect, out Vector2 intersection, out Face face))
continue;
if (face == Face.Top)
normal = new Vector2(0, -1);
if (face == Face.Bottom)
normal = new Vector2(0, 1);
if (face == Face.Left)
normal = new Vector2(-1, 0);
if (face == Face.Right)
normal = new Vector2(1, 0);
return new CastResult
{
Hit = true,
Intersection = intersection,
SurfaceNormal = normal,
TileCoordinates = tile_coords,
Face = face
};
}
Vector2 normal = face.ToSurfaceNormal();
/*if (CollisionSolver.CheckAABB(ray_check_center, new Vector2(4, 4), tile_center, tile_half))
{
var separation = CollisionSolver.GetSeparationAABB(ray_check_center, new Vector2(4, 4), tile_center, tile_half);
//if (separation.X != 0 && separation.Y != 0)
//{
var normal = CollisionSolver.GetNormalAABB(separation, direction.ToUnitVector());
return new CastResult {
Hit = true,
SurfaceNormal = normal,
Intersection = current_pt-separation,
};
// }
}*/
}
return new TileRaycastResult { Hit = true, Intersection = intersection, SurfaceNormal = normal, TileCoordinates = tile_coords, Face = face };
}
return new CastResult { Hit = false };
return new TileRaycastResult { Hit = false };
}
protected DelayedTask physicsTask;
public World()
{
physicsTask = new DelayedTask(PhysicsStep, PhysicsStepIncrement, TimeStepProcedure.SubtractIncrement);
Entities = new List<IEntity>();
Chunks = new ConcurrentDictionary<ChunkCoordinates, Chunk>();
Furniture = new List<Furniture.FurnitureTile>();
public EntityRaycastResult EntityRaycast(Vector2 origin, Rotation direction, float maxDistance = 100)
{
for (float i = 0; i < maxDistance; i += ray_accuracy)
{
Vector2 intersection;
Face side;
foreach (IEntity candidate in Entities)
{
Vector2 current_pt = origin + (direction.ToUnitVector() * i);
if (!CollisionSolver.Intersects(new LineSegment(origin, current_pt), candidate.GetCollisionRect(), out intersection, out side))
continue;
return new EntityRaycastResult{Hit = true,Face = side, Intersection = intersection,Target = candidate,SurfaceNormal = side.ToSurfaceNormal()};
}
}
return new EntityRaycastResult { Hit = false };
}
}
}

View File

@@ -4,11 +4,21 @@ using System.Runtime.InteropServices;
namespace CaveGame.Core
{
/// <summary>
/// A structure representing lighting value in-game
/// Roughly equatable to a color struct, but with specialized
/// methods for dealing with light calculation
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct Light3 : IEquatable<Light3>
{
public static Light3 Dark = new Light3(0, 0, 0);
public static Light3 Ambience = new Light3(16, 16, 16);
public static Light3 Moonlight = new Light3(128, 128, 128);
public static Light3 Dawn = new Light3(96, 96, 40);
public static Light3 Ambience = new Light3(255, 255, 255);
public static Light3 Daylight = new Light3(255, 255, 255);
public static Light3 Dusk = new Light3(96, 60, 40);
[FieldOffset(0)] public byte Red;
[FieldOffset(1)] public byte Blue;
@@ -21,21 +31,36 @@ namespace CaveGame.Core
Blue = b;
}
public bool Equals(Light3 other)
{
return (other.Red == Red && other.Blue == Blue && other.Green == Green);
}
private static double Squirt = Math.Sqrt(15.0);
public Light3(float r, float g, float b)
{
Red = (byte)(r * 255);
Green = (byte)(g * 255);
Blue = (byte)(b * 255);
}
public bool Equals(Light3 other)=>(other.Red == Red && other.Blue == Blue && other.Green == Green);
public Color ToColor() => new Color(Red, Green, Blue);
public Color MultiplyAgainst(Color col)
{
return new Color(
(col.R / 255.0f) * (float)(Math.Sqrt(Red / 30.0f)),
(col.G / 255.0f) * (float)(Math.Sqrt(Green / 30.0f)),
(col.B / 255.0f) * (float)(Math.Sqrt(Blue / 30.0f)),
(col.R / 255.0f) * (Red / 255.0f),
(col.G / 255.0f) * (Green / 255.0f),
(col.B / 255.0f) * (Blue / 255.0f),
col.A
);
}
public static Color operator *(Light3 l, Color c) => l.MultiplyAgainst(c);
public static Color operator *(Color c, Light3 l) => l.MultiplyAgainst(c);
public static Light3 operator +(Light3 a, Light3 b) => new Light3(a.Red + b.Red, a.Green + b.Green, a.Blue + b.Blue);
public static Light3 operator -(Light3 a, Light3 b) => new Light3(a.Red - b.Red, a.Green -b.Green, a.Blue - b.Blue);
public Light3 Absorb(byte opacity)
{
byte red = (byte)Math.Max(0, Red - opacity);

View File

@@ -6,8 +6,7 @@ using System.Xml;
namespace CaveGame.Core
{
public class WorldMetadata
public class WorldMetadata
{
public int Seed { get; set; }
public string Name { get; set; }

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
@@ -6,6 +7,21 @@ namespace DataManagement
{
public static class ContainerExtensions
{
public static IEnumerable<T> DequeueAll<T>(this Queue<T> queue, int chunkSize)
{
for (int i = 0; i < chunkSize && queue.Count > 0; i++)
{
yield return queue.Dequeue();
}
}
public static IEnumerable<T> DequeueAll<T>(this Queue<T> queue)
{
int max = queue.Count;
for (int i = 0; i < max && queue.Count > 0; i++)
{
yield return queue.Dequeue();
}
}
public static List<T> ToList<T>(this T[,] array)
{
int width = array.GetLength(0);

View File

@@ -1,9 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DataManagement
{
public interface IStatistic
{
decimal Comparator { get; set; }
}
public static class LINQExtensions
{
// LINQ Extensions, borrowed from Jonathan Skeet
@@ -16,5 +23,13 @@ namespace DataManagement
action(element);
}
}
public static TStat GetMean<TStat>(this TStat[] collection) where TStat : IStatistic
{
//return collection. Average();
return default;
}
}
}

View File

@@ -19,5 +19,6 @@
<Compile Include="$(MSBuildThisFileDirectory)Plugin.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ServerConfig.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ServerWorld.cs" />
<Compile Include="$(MSBuildThisFileDirectory)World\ChunkManager.cs" />
</ItemGroup>
</Project>

View File

@@ -25,6 +25,7 @@ using System.Threading.Tasks;
using System.Xml;
using CaveGame.Core.Inventory;
using CaveGame.Core.Network.Packets;
using CaveGame.Core.Generic;
namespace CaveGame.Server
{
@@ -32,12 +33,9 @@ namespace CaveGame.Server
{
DateTime Time { get; }
IMessageOutlet Output { get; }
}
public class PlayerChatMessageEventArgs : LuaEventArgs
{
public Player Player { get; private set; }
@@ -72,10 +70,13 @@ namespace CaveGame.Server
}
}
public class GameServer : IGameServer
{
public delegate void ThrowDelegate(Rotation rotation, Player player);
public delegate void NetworkListener(NetworkMessage message, User user);
public bool Running { get; set; }
public DateTime Time => DateTime.Now;
@@ -86,6 +87,8 @@ namespace CaveGame.Server
public void Message(User user, string msg, Color color) => SendTo(new ServerChatMessagePacket(msg, color), user);
public Entity GetEntity(int networkID) => (Entity)World.FindEntityOfID(networkID);
public IMessageOutlet Output
{
get { return server.Output; }
@@ -94,8 +97,6 @@ namespace CaveGame.Server
private NetworkServer server;
public EntityManager EntityManager { get; private set; }
public int TickRate { get; private set; }
@@ -107,7 +108,6 @@ namespace CaveGame.Server
public int MaxPlayers { get; private set; }
float ticker;
IEntityManager IGameServer.EntityManager => EntityManager;
IServerWorld IGameServer.World => World;
@@ -117,8 +117,8 @@ namespace CaveGame.Server
entity.EntityNetworkID = EntityManager.GetNextEntityNetworkID();
World.SpawnEntity(entity);
}
RepeatingIntervalTask[] ServerTasks;
public GameServer(ServerConfig config, WorldMetadata worldmdt) {
InitNetworkEvents();
@@ -127,16 +127,22 @@ namespace CaveGame.Server
MaxPlayers = config.MaxPlayers;
TickRate = config.TickRate;
ticker = 0;
server = new NetworkServer(config.Port);
ConnectedUsers = new List<User>();
World = new ServerWorld(worldmdt);
World.Server = this;
EntityManager = new EntityManager();
ServerTasks = new RepeatingIntervalTask[]
{
new (ReplicateNetworkEntitiesPhysicalStates, 1.0f/20.0f),
};
}
public delegate void NetworkListener(NetworkMessage message, User user);
private Dictionary<PacketType, NetworkListener> NetworkEvents;
private void InitNetworkEvents() => NetworkEvents = new Dictionary<PacketType, NetworkListener>()
@@ -160,6 +166,8 @@ namespace CaveGame.Server
#region NetworkListenerMethods
protected virtual void OnPing(NetworkMessage msg, User user)=> SendTo(msg.Packet, user); // response
private void OnClientDamageWall(NetworkMessage message, User user)
{
throw new NotImplementedException();
@@ -252,7 +260,7 @@ namespace CaveGame.Server
private void OnPlayerPosition(NetworkMessage msg, User user) // player tells us their state
{
// TODO: make server more authoritative, prevent cheating
EntityPositionPacket packet = new EntityPositionPacket(msg.Packet.GetBytes());
EntityPhysicsStatePacket packet = new EntityPhysicsStatePacket(msg.Packet.GetBytes());
if (World.FindEntityOfID(packet.EntityID, out Player player))
{
@@ -289,7 +297,7 @@ namespace CaveGame.Server
t.TileState = packet.TileState;
t.Damage = packet.Damage;
World.SetTile(packet.WorldX, packet.WorldY, t);
//SendToAll(packet);
SendToAll(packet);
}
private void OnClientRequestChunk(NetworkMessage msg, User user)
{
@@ -314,30 +322,31 @@ namespace CaveGame.Server
}
int bombcount = 0;
float bombVelocity = 3.5f;
float arrowVelocity = 3.0f;
private TEntity SpawnThrowable<TEntity>(Rotation dir, Vector2 startpos, float vel, NetEntityType entityType) where TEntity: IEntity, IPhysicsEntity, new()
{
TEntity entity = new TEntity
{ Velocity = dir.ToUnitVector() * vel, Position = startpos, NextPosition = startpos};
SpawnEntity(entity);
SendToAll(new SpawnEntityGenericPacket(entity.EntityNetworkID, entityType));
return entity;
}
private void OnThrowBomb(Rotation dir, Vector2 pos, float force) => SpawnThrowable<Bomb>(dir, pos, force, NetEntityType.Bomb);
private void OnShootArrow(Rotation dir, Vector2 pos, float force) => SpawnThrowable<Arrow>(dir, pos, force, NetEntityType.Arrow);
private void OnClientThrowItem(NetworkMessage msg, User user)
{
PlayerThrowItemPacket packet = new PlayerThrowItemPacket(msg.Packet.GetBytes());
Player p = user.PlayerEntity;
if (packet.Item == ThrownItem.Bomb)
{
Core.Game.Entities.Bomb bomb = new Core.Game.Entities.Bomb
{
Velocity = packet.ThrownDirection.ToUnitVector() * bombVelocity,
Position = p.Position,
NextPosition = p.NextPosition,
};
SpawnEntity(bomb);
SendToAll(new SpawnEntityGenericPacket(bomb.EntityNetworkID, NetEntityType.Bomb));
bombcount++;
}
OnThrowBomb(packet.ThrownDirection, p.Position, bombVelocity);
if (packet.Item == ThrownItem.Arrow)
OnShootArrow(packet.ThrownDirection, p.Position, arrowVelocity);
}
int furniturecount = 0;
private void OnClientPlaceFurniture(NetworkMessage msg, User user)
{
PlaceFurniturePacket packet = new PlaceFurniturePacket(msg.Packet.GetBytes());
@@ -352,7 +361,6 @@ namespace CaveGame.Server
packet.NetworkID = f.FurnitureNetworkID;
SendToAll(packet);
furniturecount++;
}
private void OnClientDamageFurniture(NetworkMessage msg, User user)
{
@@ -396,14 +404,12 @@ namespace CaveGame.Server
var tile = World.GetTile(packet.Position.X, packet.Position.Y);
tile.Damage += (byte)packet.Damage;
Debug.WriteLine("tile damage:" + tile.Damage);
SendToAllExcept(packet, user);
SendToAll(packet);
if (tile.Damage >= tile.Hardness)
{
Debug.WriteLine("Tile Break");
tile.Drop(this, World, packet.Position);
World.SetTile(packet.Position.X, packet.Position.Y, new Air());
//SendToAll(new PlaceTilePacket(0, 0, 0, packet.Position.X, packet.Position.Y));
SendToAll(new PlaceTilePacket(0, 0, 0, packet.Position.X, packet.Position.Y));
}
}
@@ -476,10 +482,8 @@ namespace CaveGame.Server
if (msg.Packet.Type == PacketType.cHandshake)
HandshakeResponse(msg);
if (msg.Packet.Type == PacketType.cRequestLogin)
OnClientRequestLogin(msg);
if (msg.Packet.Type == PacketType.cConfirmLogin)
OnClientLoginSuccess(msg);
@@ -493,7 +497,7 @@ namespace CaveGame.Server
foreach (var ev in NetworkEvents)
if (ev.Key == msg.Packet.Type)
ev.Value.Invoke(msg, user);
#region old shit
/*if (msg.Packet.Type == PacketType.ClientQuit)
OnClientLogout(msg, user);
if (msg.Packet.Type == PacketType.ServerEntityPhysicsState)
@@ -522,8 +526,9 @@ namespace CaveGame.Server
OnPlayerDamageTile(msg, user);
if (msg.Packet.Type == PacketType.cAdminCommand)
OnAdminCommandInput(msg, user);*/
#endregion
}
}
}
float tileticker = 0;
@@ -536,98 +541,57 @@ namespace CaveGame.Server
ConnectedUsers.Remove(user);
}
protected void DispatchUserMessages(User user)
{
if (user.DispatcherHasMessage())
{
Packet p = user.PopDispatcherQueue();
SendTo(p, user);
}
}
// generic entity physics replication
protected virtual void ReplicateNetworkEntitiesPhysicalStates()
{
foreach (var entity in World.Entities)
{
if (entity.Dead)
continue;
SendToAll(new EntityPhysicsStatePacket(entity));
if (entity is Wurmhole wurmhole && !entity.Dead)
{
// TODO: remove dipshit hack;
if (wurmhole.Provoked && wurmhole.TriggerNetworkHandled == false)
{
wurmhole.TriggerNetworkHandled = true;
SendToAll(new ProvokeEntityGenericPacket(wurmhole.EntityNetworkID));
}
}
}
}
public virtual void HandleUserConnectionStates()
{
}
public virtual void Update(GameTime gt)
{
float delta = (float)gt.ElapsedGameTime.TotalSeconds;
ConnectedUsers.Where(u => u.Kicked == true).ForEach(InternalUserKick);
ConnectedUsers.ForEach(u => DispatchUserMessages(u));
foreach(var user in ConnectedUsers.ToArray())
{
user.KeepAlive += delta;
if (user.KeepAlive > 10.0f)
{
user.Kick("Network Timeout");
}
}
ticker += delta;
if (ticker > 1/ 20.0f)
{
ticker = 0;
foreach(var entity in World.Entities)
{
if (entity.Dead)
continue;
var packet = new EntityPositionPacket(entity);
SendToAll(packet);
if (entity is Wurmhole wurmhole && !entity.Dead)
{
// TODO: remove dipshit hack;
if (wurmhole.Provoked)
{
if (wurmhole.TriggerNetworkHandled == false)
{
wurmhole.TriggerNetworkHandled = true;
SendToAll(new ProvokeEntityGenericPacket(wurmhole.EntityNetworkID));
}
}
}
}
}
ReadIncomingPackets();
HandleUserConnectionStates();
ServerTasks.ForEach(t => t.Update(gt));
ConnectedUsers.ForEach(u => u.SendDispatchMessages(this));
ConnectedUsers.ForEach(u => u.KeepAlive += gt.GetDelta());
ConnectedUsers.Where(u => u.KeepAlive > 10.0f).ForEach(u => u.Kick("Network Timeout"));
ConnectedUsers.ToArray().Where(u => u.Kicked == true).ForEach(InternalUserKick); // InvalidOperationException
World.Update(gt);
tileticker += delta;
if (tileticker > (1/5.0f))
{
tileticker = 0;
foreach (var kvp in World.Chunks)
{
Chunk c = kvp.Value;
for (int x = 0; x < Chunk.ChunkSize; x++)
{
for (int y = 0; y < Chunk.ChunkSize; y++)
{
if (c.NetworkUpdated[x, y] == true)
{
c.NetworkUpdated[x, y] = false;
var t = c.Tiles[x, y];
var w = c.Walls[x, y];
SendToAll(new PlaceTilePacket(t.ID, t.TileState, t.Damage, (c.Coordinates.X * Chunk.ChunkSize) + x, (c.Coordinates.Y * Chunk.ChunkSize) + y));
SendToAll(new PlaceWallPacket(w.ID, 0, w.Damage, (c.Coordinates.X * Chunk.ChunkSize) + x, (c.Coordinates.Y * Chunk.ChunkSize) + y));
}
}
}
}
}
server.Update(gt);
}
public void Start()
{
server.Start();
Running = true;
Task.Run(GameserverThreadLoop);
}
public virtual void Shutdown()
{
@@ -664,10 +628,9 @@ namespace CaveGame.Server
Output?.Out(String.Format("[{0} {1}] {2}", "server", DateTime.Now.ToString("HH:mm:ss.ff"), text));
}
// Should be run on it's own thread
public void Run()
public void GameserverThreadLoop()
{
server.Run();
Stopwatch timing = new Stopwatch();
@@ -684,9 +647,6 @@ namespace CaveGame.Server
Thread.Sleep(TickRate);
}
}
}
}
}
}

View File

@@ -16,19 +16,9 @@ using System.Threading.Tasks;
namespace CaveGame.Server
{
public class NetworkServer
public class NetworkServer : SharedNetworkSubsystem
{
public readonly int Port;
public int ProtocolVersion;
public IMessageOutlet Output { get; set; }
private UdpClient udpClient;
private ConcurrentQueue<NetworkMessage> incomingMessages;
private ConcurrentQueue<Tuple<Packet, IPEndPoint>> outgoingMessages;
@@ -37,7 +27,8 @@ namespace CaveGame.Server
public NetworkServer(int port)
{
udpClient = new UdpClient(port, AddressFamily.InterNetwork);
UdpSocket = new UdpClient(port, AddressFamily.InterNetwork);
IOControlFixICMPBullshit();
Port = port;
incomingMessages = new ConcurrentQueue<NetworkMessage>();
@@ -45,16 +36,6 @@ namespace CaveGame.Server
}
private string DumpHex(byte[] data)
{
StringBuilder bob = new StringBuilder();
for (int i = 0; i < data.Length; i++)
{
bob.Append(String.Format("{0:X2} ", data[i]));
}
return bob.ToString();
}
private void NetworkThread()
{
if (!listening.Value)
@@ -63,7 +44,7 @@ namespace CaveGame.Server
while (listening.Value)
{
bool canRead = udpClient.Available > 0;
bool canRead = UdpSocket.Available > 0;
// Read in message if we have any data
if (canRead)
@@ -72,27 +53,22 @@ namespace CaveGame.Server
IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);
try
{
byte[] data = udpClient.Receive(ref ep);
byte[] data = UdpSocket.Receive(ref ep);
NetworkMessage nm = new NetworkMessage();
nm.Sender = ep;
nm.Packet = new Packet(data);
nm.ReceiveTime = DateTime.Now;
#if PACKETDEBUG
Output?.Out(String.Format("PK_IN: [{0}] at [{1} {2}] from [{3}]", nm.Packet.Type.ToString(), nm.ReceiveTime.ToLongTimeString(), nm.ReceiveTime.Millisecond, nm.Sender.ToString()));
Output?.Out(String.Format("DATUM: [{0}]", DumpHex(nm.Packet.Payload)));
Output?.Out("");
#endif
PacketsReceived++;
InternalReceiveCount += nm.Packet.Payload.Length;
TotalBytesReceived += nm.Packet.Payload.Length;
incomingMessages.Enqueue(nm);
} catch(SocketException ex)
{
Output?.Out(ex.Message);
}
}
// Output?.Out("server: "+nm.ToString());
}
@@ -105,18 +81,16 @@ namespace CaveGame.Server
bool have = outgoingMessages.TryDequeue(out msg);
if (have)
{
#if PACKETDEBUG
Output?.Out(String.Format("PK_OUT: [{0}] at [{1} {2}] to [{3}]", msg.Item1.Type.ToString(), DateTime.Now.ToLongTimeString(), DateTime.Now.Millisecond, msg.Item2.ToString()));
Output?.Out(String.Format("DATUM: [{0}]", DumpHex(msg.Item1.Payload)));
Output?.Out("");
#endif
msg.Item1.Send(udpClient, msg.Item2);
Packet packet = msg.Item1;
IPEndPoint target = msg.Item2;
packet.Send(UdpSocket, target);
PacketsSent++;
TotalBytesSent += packet.Payload.Length;
InternalSendCount += packet.Payload.Length;
}
}
// If Nothing happened, take a nap
if (!canRead && (numToWrite == 0))
Thread.Sleep(1);
@@ -127,10 +101,7 @@ namespace CaveGame.Server
{
listening.Value = true;
Output?.Out(String.Format("server: listening on port {0}", Port), Color.Coral);
}
public void Run() {
Task.Factory.StartNew(NetworkThread);
Output?.Out("server: Network listener thread created", Color.Coral);
}
public bool HaveIncomingMessage()
@@ -147,15 +118,21 @@ namespace CaveGame.Server
{
return msg;
}
throw new Exception("No Message Queued! Used HaveIncomingMessage() to check!");
throw new Exception("No Message Queued! Use HaveIncomingMessage() to check!");
}
public void SendPacket(Packet packet, IPEndPoint target)
{
//Output?.Out("server: Sending packet " + packet.Type.ToString(), Color.Coral);
outgoingMessages.Enqueue(new Tuple<Packet, IPEndPoint>(packet, target));
}
public override void Update(GameTime gt)
{
base.Update(gt);
}
public void Cleanup() { }
public void Close()
{

View File

@@ -13,38 +13,49 @@ using System.Xml;
using System.Xml.Serialization;
using System.Collections.Concurrent;
using CaveGame.Core.Game.Tiles;
using DataManagement;
namespace CaveGame.Server
{
public class ServerWorld : Core.World, IServerWorld
{
public int WorldSeed => Metadata.Seed;
public string WorldName => Metadata.Name;
private ConcurrentQueue<IEntity> entityQueue;
public void SpawnEntity(IEntity entity) => entityQueue.Enqueue(entity);
private ConcurrentQueue<IEntity> EntityQueue { get; set; }
public void SpawnEntity(IEntity entity) => EntityQueue.Enqueue(entity);
private Queue<Point> TileNetworkUpdateQueue { get; set; }
private Queue<Point> WallNetworkUpdateQueue { get; set; }
public void RequestTileNetworkUpdate(Point position) => TileNetworkUpdateQueue.Enqueue(position);
public void RequestWallNetworkUpdate(Point position) => WallNetworkUpdateQueue.Enqueue(position);
public GameServer Server { get; set; }
public Generator Generator { get; set; }
public Dictionary<ChunkCoordinates, bool> LoadedChunks;
protected DelayedTask serverTileUpdateTask;
protected DelayedTask serverRandomTileUpdateTask;
protected RepeatingIntervalTask TileUpdateTask { get; set; }
protected RepeatingIntervalTask RandomTileUpdateTask { get; set; }
//protected RepeatingIntervalTask
public override List<FurnitureTile> Furniture { get; protected set; }
public WorldMetadata Metadata { get; private set; }
public ServerWorld(WorldMetadata metadata)
public ServerWorld(WorldMetadata metadata) : base()
{
TileNetworkUpdateQueue = new Queue<Point>();
WallNetworkUpdateQueue = new Queue<Point>();
Context = NetworkContext.Server;
//SessionType = GameSessionType.
Metadata = metadata;
CreateDirectoryIfNull(Path.Combine("Worlds", WorldName));
@@ -63,13 +74,55 @@ namespace CaveGame.Server
worldXml.WriteEndDocument();
worldXml.Close();
entityQueue = new ConcurrentQueue<IEntity>();
serverTileUpdateTask = new DelayedTask(ApplyTileUpdates, 1 / 10.0f);
serverRandomTileUpdateTask = new DelayedTask(ApplyRandomTileTicksToLoadedChunks, 1 / 5.0f);
WorldTimedTasks.Add(new(ProcessTileUpdateRequests, 1 / 10.0f));
WorldTimedTasks.Add(new(DoRandomTileTicks, 1 / 5.0f));
WorldTimedTasks.Add(new(SendTileNetworkUpdates, 1 / 5.0f));
EntityQueue = new ConcurrentQueue<IEntity>();
//serverTileUpdateTask = new RepeatingIntervalTask(TileUpdates, 1 / 10.0f);
//serverRandomTileUpdateTask = new RepeatingIntervalTask(ApplyRandomTileTicksToLoadedChunks, 1 / 5.0f);
Generator = new Generator(WorldSeed);
Tile.InitializeManager(WorldSeed);
}
public override void SetTile(int x, int y, Tile t)
{
base.SetTile(x, y, t);
RequestTileNetworkUpdate(new Point(x, y));
}
public virtual void SetTileNetworkUpdated(int x, int y)
{
//throw new NotImplementedException();
}
public override void BreakTile(int x, int y)
{
GetTile(x, y).Drop(Server, this, new Point(x, y));
SetTile(x, y, new Air());
base.BreakTile(x, y);
}
private void ProcessTileUpdateRequests()
{
int count = TileUpdateQueue.Count;
for (int i = 0; i < count-1; i++)
{
Point coords = TileUpdateQueue.Dequeue();
//bool success = TileUpdateQueue.TryDequeue(out coords);
if (GetTile(coords) is ITileUpdate tile)
tile.TileUpdate(this, coords.X, coords.Y);
foreach (var furn in Furniture.ToArray())
furn.OnTileUpdate(this, coords.X, coords.Y);
}
}
private static void CreateDirectoryIfNull(string fname)
{
if (!System.IO.Directory.Exists(fname))
@@ -128,7 +181,6 @@ namespace CaveGame.Server
Generator.HeightPass(ref chunk);
//World.Chunks.Add(coords, chunk);
Chunks.TryAdd(coords, chunk);
chunk.ClearUpdateQueue();
}
if (!chunk.DungeonPassCompleted)
@@ -146,7 +198,6 @@ namespace CaveGame.Server
//World.Chunks.Add(newCoords, thischunk);
Chunks.TryAdd(newCoords, thischunk);
Generator.HeightPass(ref thischunk);
thischunk.ClearUpdateQueue();
}
}
}
@@ -162,6 +213,10 @@ namespace CaveGame.Server
return chunk;
}
public Task<Chunk> LoadChunk(ChunkCoordinates coords) { return null; }// TODO: Implement
public Task<bool> UnloadChunk(ChunkCoordinates coords) { return null; }// TODO: Implement
protected override void PhysicsStep()
{
foreach (IEntity entity in Entities.ToArray())
@@ -169,12 +224,7 @@ namespace CaveGame.Server
physicsObserver.ServerPhysicsTick(this, PhysicsStepIncrement);
}
public override void BreakTile(int x, int y)
{
GetTile(x, y).Drop(Server, this, new Point(x, y));
SetTile(x, y, new Air());
base.BreakTile(x, y);
}
public override void RemoveFurniture(Core.Furniture.FurnitureTile furn)
{
@@ -183,37 +233,18 @@ namespace CaveGame.Server
//base.RemoveFurniture(furn);
}
private void SendTileNetworkUpdates()
{
foreach (var tileCoords in TileNetworkUpdateQueue.DequeueAll())
Server.SendToAll(new PlaceTilePacket(tileCoords, GetTile(tileCoords)));
private void TileUpdates(Chunk chunk)
{
for (int x = 0; x < Globals.ChunkSize; x++)
{
for (int y = 0; y < Globals.ChunkSize; y++)
{
if (chunk.TileUpdate[x, y] == true)
{
chunk.TileUpdate[x, y] = false;
int worldX = (chunk.Coordinates.X * Globals.ChunkSize) + x;
int worldY = (chunk.Coordinates.Y * Globals.ChunkSize) + y;
foreach (var furn in Furniture.ToArray())
furn.OnTileUpdate(this, worldX, worldY);
if (chunk.GetTile(x, y) is ITileUpdate tile)
tile.TileUpdate(this, worldX, worldY);
}
}
}
foreach (var wallCoords in WallNetworkUpdateQueue.DequeueAll())
Server.SendToAll(new PlaceWallPacket(wallCoords, GetWall(wallCoords)));
}
private void ApplyTileUpdates()
{
foreach (var kvp in Chunks)
TileUpdates(kvp.Value);
}
private async void ApplyRandomTileTicksToLoadedChunks()
private async void DoRandomTileTicks()
{
await (Task.Run(() =>
{
@@ -267,15 +298,10 @@ namespace CaveGame.Server
public override void Update(GameTime gt)
{
for (int i = 0; i < entityQueue.Count; i++)
{
bool success = entityQueue.TryDequeue(out var newEntity);
if (success)
Entities.Add(newEntity);
}
serverRandomTileUpdateTask.Update(gt);
serverTileUpdateTask.Update(gt);
for (int i = 0; i < EntityQueue.Count; i++)
if (EntityQueue.TryDequeue(out IEntity entity))
Entities.Add(entity);
foreach (var ent in Entities.ToArray())

View File

@@ -0,0 +1 @@