It's recommended by most and provides everything that's needed to replicate the original system.
It also supports DotNet Core, albeit requiring a manual build. It should be possible to distribute the NuGet package itself and load it up locally, as i've done. That's relatively simple to do compared to the C++ dependency management approach.
Unlike GoldSource Lidgren doesn't impose any packet size limitations, but hardware and software limitations in the network infrastructure still apply here. Once a packet grows beyond a certain size it will be fragmented and reassembled on the other side, which means high amounts of packet loss can still cause problems. I may have to look into creating something that can send data in discrete packets to limit the effects of packet loss, but that will require further investigation to see if it's a real problem or not. I'm not too familiar with that stuff so it may not be a problem.
In any case it means the "reliable channel overflow" errors should be a non-issue. Timeouts, high ping and excessive packet loss should be the only reason to disconnect clients.
As far as GUI libraries go i've found that Imgui has a .NET Core wrapper: https://github.com/mellinoe/ImGui.NET
This should make implementing debug overlays simple, i know suXin from Facepunch has done this before so it should be simple to implement.
There's also a library for graphics programming: https://github.com/mellinoe/veldrid
It's low-level, cross platform and supports multiple backends, so i think this is a good way to support Vulkan and D3D.
It uses SDL2 for window management, and the library names are the same as Half-Life's so there is a need to modify the source. This is because Half-Life uses an older SDL2 library that may be missing functions. I may need to make a custom build for this to work out. I see that Veldrid uses a library to load native libraries, i may be able to provide a custom SDL2 path to load it.
For keyboard and mouse input i can also use SDL2, but i'll need to use a .NET wrapper here that also works together with Veldrid.
The wrapper i found uses DllImport which may not cooperate with manual library loading: https://github.com/flibitijibibo/SDL2-CS
Normally any given library is loaded once, so the goal will be to make sure the right SDL2 library gets loaded. If i can control where it looks for native libraries i can use a custom one and bypass the entire issue, otherwise i'll need to modify the source code of all involved libraries.
One option is to use this: https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.defaultdllimportsearchpathsattribute(v=vs.110).aspx
But that only affects the assemblies that it's applied to. What i'm thinking is i can do this: free the original SDL2 library, load the correct one in the engine assembly, marked with that attribute, and then rely on the other load calls to use that library. This depends on the SDL2 library used by the original engine being freed, which may not be possible since it's a load time linked library. I'll have to verify this, otherwise using renamed libraries is the way to go.
That should cover networking, (debug) graphics and input. Sound can be handled through FMOD, that has a C# wrapper. The engine doesn't use it so there's no problem there.
I already have the filesystem code written up, though some changes are needed here and there. I'll be ditching the registry part of its initialization since that forces it to have Windows only dependencies, it'll be stored in a general purpose engine config file, along with things like the default game directory ("valve").
I've also built a command system, i've taken into account the problems that Valve had with engine upgrades to the classes. The problem there was that the class layout couldn't be altered without breaking mods, i've solved this by not making modders create instances of the classes. Instead, you provide info objects that describe commands, which are then used to construct the classes. Combined with C#'s delegates allowing class member functions and the compatibility between libraries, it should solve all of the problems that the command system has had since Quake.
As an example, here's the alias command:
RegisterConCommand(new ConCommandInfo("alias", arguments =>
{
if (arguments.Count == 0)
{
Console.WriteLine("Current alias commands:");
foreach (var entry in _aliases)
{
Console.WriteLine($"{entry.Key}: {entry.Value}\n");
}
return;
}
// if the alias already exists, reuse it
_aliases[arguments[0]] = arguments.ArgumentsAsString(1);
})
.WithHelpInfo("Aliases a command to a name"));
Compare with the original: https://github.com/id-Software/Quake/blob/master/WinQuake/cmd.c#L341The registration methods return interfaces to the commands to allow you to reference them, add more handlers, directly query variables, etc.
Commands and variables both allow you to add multiple handlers, so for instance if you have your cheat cvar "sv_cheats" defined somewhere, you can do this anywhere:
var svCheats = commandSystem.FindCommand<ConVar>("sv_cheats");
svCheats.OnChange += (ref ConVarChangeEvent conVarChangeEvent) =>
{
if (!conVarChangeEvent.Boolean)
{
players.ForEach(p => p.DisableCheats());
}
};
This adds a handler that checks if cheats have been disabled, and if so disables cheat properties on all players.Variables also have filters that are checked before their values are changed. It's a pretty simple thing, if the filter returns false the change is not allowed. Filters can also modify inputs to for instance clamp values to a range, similar to Source's cvars. I've added filters that can do a few basic things, like testing if inputs are in a list, allowing only numbers, inverting filters, etc. For example, sv_cheats can have a boolean filter applied that restricts all inputs to 0 or 1.
Filters can be added and removed as well. Since both filters and change handlers are events, removing handlers you don't have a reference to is impossible, at least unless you use reflection to get the underlying event handler. It is thus difficult to accidentally break existing handlers.
Once all of this stuff is set up, the essentials should all be there. When i can draw stuff onscreen debugging will be easier, and i can start writing libraries that can load the various file formats used by the engine (bsp, mdl, spr). These will be standalone to allow for re-usability, hopefully useful to make new tools with.
If i can find the time i should be able to set this stuff up soonish, but i can't promise anything. I'm hoping there won't be any more unexpected problems, but you never know with these sorts of workarounds.