Forum posts

Posted 8 hours ago2019-01-18 16:31:42 UTC
in Opposing Force SDK Post #341712
Does anyone have an up-to-date fgd for Opposing Force? I think i have this one:

It's missing some stuff, so if i could get a newer one i can add anything that's still missing.

Edit: found it, thanks Shepard:
Posted 2 days ago2019-01-16 19:25:01 UTC
in Opposing Force SDK Post #341703
I've documented how the relationship table can be extracted from the game:

Requires a Linux VM or OS.
Posted 2 days ago2019-01-16 17:18:32 UTC
in SharpLife - Dot Net Core based modding p Post #341701
and i also trying to say, trigger changelevels arent working in singleplayer maps when playing MP, are they?
They're disabled for multiplayer, but it's easy to re-enable them. The hardest part about seamless transitions in the vanilla engine is that it wasn't designed for multiplayer use, so save games don't handle it. Also a fair amount of code in the SDK assumes the first player is in charge of gameplay, so things can break in weird ways. Easy to fix if you know what you're doing though.
i was thinking hl's coop mode is broken. i didnt know it wasnt
Half-Life itself doesn't have co-op at all, beyond an IsCoOp member in gamerules that's only used to find info_player_coop spawn points. Valve didn't finish it in time for Half-Life, but Op4 does seem to have it. Which brings me to:
Posted 2 days ago2019-01-16 17:14:23 UTC
in Opposing Force SDK Post #341700
I'm currently working to implement Opposing Force in the Half-Life updated SDK:

I decided to do this now because i know that it will be a while before i can get to this if i make it part of SharpLife, and since i have to reconstruct the original C++ code anyway before i can convert it i figured i'd get it done now, so people can use it and find any mistakes/bugs before it gets converted.

So far i've implemented CTF gamerules, which is about 2000 lines of code. Other gamerules need to be updated, and the co-op gamerules need to be implemented as well.

I'm going to start with the NPCs now, since that's the interesting part and because that's what the largest part is.

Implementation is based on what's needed to make single maps work, starting with the training maps, then the campaign, then multiplayer. This way it can be tested more easily.

The ultimate goal is to make an SDK that completely implements Opposing Force. You won't be able to copy paste it into your own mod unfortunately since it changes existing SDK code (some network messages are different, gamerules are different too). When the time comes i'll port it to SharpLife, integrating all of the features so that you can play Opposing Force as well as Half-Life using the same base game, and use any features present in either in your maps.

I'm not doing a 1:1 reconstruction, there are places where i'm making use of newer language features and simpler code to make things easier. For example, iterating over players is done like this:
int teamCount[ MaxTeams ] = {};

for( auto pPlayer : UTIL_FindPlayers() )
	if( pPlayer->m_iTeamNum != CTFTeam::None )
		++teamCount[ ( ( int ) pPlayer->m_iTeamNum ) - 1 ];
I'm also using enum class, as you can see here. It keeps things cleaner and adds type checking, though it does have some drawbacks.

Iterating over entities is similar:
auto useDefault = true;

for( auto pEquip : UTIL_FindEntitiesByClassname( "game_player_equip" ) )
	useDefault = false;

	pEquip->Touch( pPlayer );
It's basically the same as before, but it's easier to use and you can't screw up the logic, like off by one errors in player iteration or a forgotten null check. It's also type safe if you need a specific class:
for( auto pFlag : UTIL_FindEntitiesByClassname<CTFGoalFlag>( "item_ctfflag" ) )
	if( pFlag->m_iGoalState == 2 )
		pFlag->TurnOnLight( pPlayer );
(Not all of the constants have been defined yet)

I've also added helpers to make it easier to do type conversions:
auto pPlayer = GET_PRIVATE<CBasePlayer>( pItem->pev->owner );
auto pOtherPlayer = CBaseEntity::Instance<CBasePlayer>( tr.pHit );
auto pFlag = pPlayer->m_pFlag.Entity<CTFGoalFlag>();
This eliminates the casts used otherwise, and also allows you to swap out the static_cast with a dynamic_cast to help find incorrect casts.
Posted 2 days ago2019-01-16 16:48:17 UTC
in SharpLife - Dot Net Core based modding p Post #341698
What are the entities did you tested successfully among, info_player entities, env entities, lights (I saw something about those), scripst,etc.? apart from what´s descibed in Post #339709.
That's the prototype for SharpLife, which ran under the original engine and only implemented game logic. It was essentially a converted SDK, but i scrapped it in favor of a complete rebuild, so just ignore all that.
Also, what programs are you using to edit-compile the source?
Visual Studio 2017 with C#7.3. The native wrapper uses C++17, but you'll probably never touch it since it's very simple and only does one thing.
Sorry for saying the "translation" thing, but, in your experience, what will be the major problems a non expert code will find when trying to "port" his/her code to SL?, many of us use tutorials all written in C++... ;)
No problems really, you'll just have to know which pev members map to which members in the new version. That's not really hard to figure out.
Sorry for my too basic questions, I know that maybe this is not the place to ask them because the info here reached a high level, but I am very interested in SL as I was in EHL and PS before, and I want to gather as much info as possible before making the decission to jump from C++ to C#, because if C++ was sometimes like chinese to me, I think I will not be able to learn korean. :glad: :walter:
You shouldn't make any decisions yet, this project is far from complete. I'd suggest checking out tutorials for basic Hello World stuff first, then maybe WPF since it requires the use of many language features.

If C++ is chinese then C# is dutch. It's way easier to learn and use. C++ has a lot of complexity and gotcha's that don't exist in C# and IntelliSense and the compiler will tell you how to fix most problems in C#. I'm using extensions that find code that can be improved which also helps out a lot.
btw can we make another thread for SL v2 for not to forget cool ideas. and not to flood this thread with weird ideas. :D
That's probably years away at this point, so i wouldn't bother just yet.
cause i have one more idea to implement: unbroken coop mode and seamless level transition in multiplayer server. :D
I was already going to implement co-op (Opposing Force has a co-op gamemode in it), what do you mean by unbroken?

Seamless level transitions basically revolve around 2 things: not showing the changelevel dialog and transitioning entities, which singleplayer code already does. For multiplayer all that's needed is to track which player is which since they don't remain connected during transitions. That's not very hard to do.
Posted 2 days ago2019-01-16 13:24:48 UTC
in SharpLife - Dot Net Core based modding p Post #341693
You're getting way ahead of yourself here. SharpLife doesn't even support all of the original engine's features yet, talking about adding a bunch of new stuff now is far too soon. At this rate it'll end up being some mythical super engine that can do anything but doesn't actually work yet.

We can talk about this stuff later on, when the initial version is finished. For now i'd like to focus on getting it all to work as it should.
Posted 2 days ago2019-01-16 10:01:38 UTC
in SharpLife - Dot Net Core based modding p Post #341690
You caught me!! ;) But, If it can load original content like the original HL does I will wait for sure. Also It appears to work without vgui.dll, which´s the major limitation of Xash when it comes to go greenlightened on STEAM, right? , I´m with ZWC for the last 14 years, one more will not harm me. :crowbar: :D
The original engine references vgui.dll, but my code doesn't use it.
In what a media playing is about, can we use other formats than AVI for the intro and sierra videos? also, I remember that in EHL you wanted to add background maps for the game menus, is this still present in SL?
The original engine can't play any videos, and SharpLife can't do it either. I'm not going to look into that any time soon either.
Background maps will be a thing for later, when the engine is stable.
Posted 3 days ago2019-01-15 20:36:26 UTC
in SharpLife - Dot Net Core based modding p Post #341682
I'm not translating it, i'm making a new engine that's also a mod that lets you make Half-Life mods and load original content.

If you want a better engine other than Xash then you'll have to wait.
Posted 3 days ago2019-01-15 20:19:19 UTC
in SharpLife - Dot Net Core based modding p Post #341680
Yeah your example is pretty much how save/restore should work.

C# is much easier to use than C++. I'll show a couple SDK examples to show this.

This is some code that shows up a lot in gamerules classes:
	"\"%s<%i><%u><spectator>\" joined team \"spectator\"\n",
	STRING( pPlayer->pev->netname ),
	g_engfuncs.pfnGetPlayerUserId( pPlayer->edict() ),
	g_engfuncs.pfnGetPlayerWONId( pPlayer->edict() ) );
The equivalent in SharpLife would be:
_logger.Information( "\"{PlayerName}<{UserId}><{SteamId}><spectator>\" joined team \"spectator\"", player.PlayerName, player.UserId, player.SteamId );
Changing WONId to SteamId since WONId is always -1 now.

This is for logging, for normal string formatting you can just use string interpolation:
sprintf( text, "* you are on team '%s'\n", team_names[ ( int ) pPlayer->m_iTeamNum - 1 ] );
var text = $"* you are on team '{TeamNames[(int) player.TeamNumber]}'\n";
No possibility of buffer overflows here.

EHandles can also be made safer:
CTFGoalFlag pFlag = ( CTFGoalFlag* ) ( CBaseEntity* ) pPlayer->m_pFlag;

if( pFlag )
You don't have to do the double cast anymore (needed since EHANDLE can only be cast to CBaseEntity through its conversion operator), and the cast is safe instead of potentially creating an invalid object that can crash the game when used. Use of ? here is the same as the if check, if the object is not null call this method.

I've also had to write this code recently:
edict_t* pKiller = INDEXENT( 0 );

entvars_t* pEntKiller = pKiller ? &pKiller->v : nullptr;
None of that is needed in SharpLife, you can just use BaseEntity directly:
var killer = EntityList.GetEntityByIndex( 0 );
Since this just gets the world you can also get it through Context.Entities.World.

This is all pretty basic stuff, it gets easier once you deal with entities more:
pPlayer->pev->effects |= EF_NODRAW;
pPlayer->pev->flags |= FL_SPECTATOR;
pPlayer->pev->solid = SOLID_NOT;
pPlayer->pev->movetype = MOVETYPE_NOCLIP;
pPlayer->pev->takedamage = DAMAGE_NO;
pPlayer->m_afPhysicsFlags |= PFLAG_OBSERVER;
pPlayer->m_iNewTeamNum = CTFTeam::None;
pPlayer->m_iCurrentMenu = 0;
pPlayer->m_iTeamNum = CTFTeam::None;
player.Effects |= Effect.Nodraw;
player.Flags |= EntityFlag.Spectator;
player.Solid = Solid.Not;
player.Movetype = Movetype.Noclip;
player.Takedamage = TakeDamageMode.No;
player.HideHUD |= HideHud.Health | HideHud.Weapons;
player.PhysicsFlags |= PhysicsFlag.Observer;
player.NewTeamNumber = CTFTeam.None;
player.CurrentMenu = Menu.None;
player.TeamNum = CTFTeam.None;
Though i expect some of these variables will become obsolete since they're used for networking physics specific stuff. There's overlap here and there that can be eliminated.

Network messages will also be easier to use, right now they're just Protobuf messages so you can easily create and send them without having to worry about getting it wrong.

Debugging is also much easier since it'll tell you where a problem occurred, for example here's the log entry for a fatal error where it failed to load a sprite:
2019-01-10 00:34:47.640 +01:00 [ERR] A fatal error occurred
System.IO.FileNotFoundException: Could not resolve absolute path
File name: 'sprites\sewerfog.spr'
   at SharpLife.FileSystem.DiskFileSystem.GetAbsolutePath(String relativePath, String pathID) in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.FileSystem\DiskFileSystem.cs:line 134
   at SharpLife.FileSystem.DiskFileSystem.Open(String relativePath, FileMode mode, FileAccess access, FileShare share, String pathID) in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.FileSystem\DiskFileSystem.cs:line 222
   at SharpLife.FileSystem.FileSystemExtensions.OpenRead(IFileSystem self, String relativePath, String pathID) in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.FileSystem\FileSystemExtensions.cs:line 254
   at SharpLife.Models.ModelManager.LoadModel(String modelName) in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.Models\ModelManager.cs:line 65
   at SharpLife.Models.ModelManager.InternalLoad(String modelName, Boolean throwOnFailure) in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.Models\ModelManager.cs:line 87
   at SharpLife.Models.ModelManager.Load(String modelName) in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.Models\ModelManager.cs:line 119
   at SharpLife.Engine.Server.Resources.ServerModels.LoadModel(String modelName) in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.Engine.Server\Resources\ServerModels.cs:line 75
   at SharpLife.Game.Server.Entities.BaseEntity.KeyValue(String key, String value) in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.Game.Server\Entities\BaseEntity.cs:line 228
   at SharpLife.Game.Server.Entities.Effects.EnvSprite.KeyValue(String key, String value) in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.Game.Server\Entities\Effects\EnvSprite.cs:line 45
   at SharpLife.Game.Server.Entities.ServerEntities.LoadEntity(List`1 block, Int32 index) in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.Game.Server\Entities\ServerEntities.cs:line 267
   at SharpLife.Game.Server.Entities.ServerEntities.LoadEntities(String entityData) in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.Game.Server\Entities\ServerEntities.cs:line 197
   at SharpLife.Game.Server.Entities.ServerEntities.MapLoadBegin(ITime gameTime, IMapInfo mapInfo, GamePhysics gamePhysics, String entityData, Boolean loadGame) in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.Game.Server\Entities\ServerEntities.cs:line 170
   at SharpLife.Game.Server.API.GameServer.MapLoadContinue(Boolean loadGame) in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.Game.Server\API\GameServer.cs:line 207
   at SharpLife.Engine.Server.Host.EngineServerHost.InitializeMap(ServerStartFlags flags) in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.Engine.Server\Host\EngineServerHost.cs:line 240
   at SharpLife.Engine.Engines.ClientServerEngine.FinishLoadMap(String startSpot, ServerStartFlags flags) in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.Engine\Engines\ClientServerEngine.cs:line 442
   at SharpLife.Engine.Engines.ClientServerEngine.StartNewMap(ICommandArgs command) in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.Engine\Engines\ClientServerEngine.cs:line 426
   at SharpLife.CommandSystem.Commands.Command.OnCommand(ICommandArgs command) in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.CommandSystem\Commands\Command.cs:line 43
   at SharpLife.CommandSystem.CommandQueue.Execute() in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.CommandSystem\CommandQueue.cs:line 95
   at SharpLife.CommandSystem.CommandSystem.Execute() in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.CommandSystem\CommandSystem.cs:line 103
   at SharpLife.Engine.Engines.ClientServerEngine.Update(Single deltaSeconds) in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.Engine\Engines\ClientServerEngine.cs:line 196
   at SharpLife.Engine.Engines.ClientServerEngine.Run(String[] args, HostType hostType) in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.Engine\Engines\ClientServerEngine.cs:line 181
   at SharpLife.Engine.Host.EngineHost.Start(String[] args, HostType type) in C:\Users\Sam\Documents\GitHub\SharpLife_full_dev\SharpLife-Engine\src\SharpLife.Engine\Host\EngineHost.cs:line 36
It's a bit much but you get everything you need to know.

Compare this with GoldSource or even just C++ debugging where something like this is little more than a message box telling you there was a fatal error, without actually pointing you at the code that triggered it.

Once you get used to the language you'll find it's much easier, you won't want to go back to C++ after this.
Posted 3 days ago2019-01-15 16:53:22 UTC
in SharpLife - Dot Net Core based modding p Post #341673
Also it's written in a different language and has a different API so existing mods can't run under it anyway.
Posted 3 days ago2019-01-15 15:51:37 UTC
in SharpLife - Dot Net Core based modding p Post #341670
some corpses can gibbed with one hit. others are too much hits and they broke the hitting animation so without swinging the crowbar gibbing the corpses. (so politicians or media or something dont want to see a game where player constantly whacking a corpse. it seems so they removed animations.)
Depending on the NPC the health for corpses is set to half the original health value, so it could be quite high sometimes. Corpses are still subject to hitgroups so that might have something to do with it if you're essentially hitting a monster's feet.

The animation not playing is because the weapon prediction code doesn't run think functions on the client, i already fixed that in HLEnhanced. If you turn it off using cl_lw it should work as before, but in multiplayer it'll be laggier.
and you cant hit the corpse if that part of corpse is out of the otiginal monsters hull (bounding box)
That can probably be fixed by changing the bounding box upon death to match the shape when the NPC has come to rest. That depends on how sequences store hull sizes and stuff.
if hl2 smd to hl1 mdl compiler isnt posible, at least hl2 smd file to hl1 smd file can be made. the hl2 smd file format is easy to find, i guess. and there is info about hl2 smd at valve developer site.
It's possible to convert them, but information will be lost. That's why it may be better to use a new format that supports what you need before writing any converter tools.
Posted 3 days ago2019-01-15 15:30:03 UTC
in SharpLife - Dot Net Core based modding p Post #341666
what about 24 bit textures? ok if only v2 supports this. i cant wait to see v2 :)
V2 can support 32 bit just fine. I'm using a library made for image loading so eventually it could support any kind of image that that library supports.
and there is one more probleb about goldsrc. i dont want to use milkshape. its expensive and limited. i can use blender but its only supports hl2 smd files. so maybe you can make a compiler for hl2 smds to hl1 mdls.
I've never dabbled in Source programming and from what i know about it the studiomdl compiler for it is closed source. Maybe an easier to use format could be made for it one day though.
Better use hornetgun... But the entity limit is a problem (BIG PROBLEM) as you say. How much entities will support SL at a time before struggling?, also, and remember that I am quite an ignorant in coding, will it depend of the PC specs or this number will adapt itself independently of the system on which SL will run on?
SharpLife supports server-only entities which avoids the whole problem of networking entities to the client and having bandwidth issues. Projectiles don't need to be visible all the time, but networking is also much better so the client can handle more of it on the client, whereas GoldSource just does everything server side and then sends it to the client.

The system requirements are pretty high compared to GoldSource, but they haven't been set in stone. What i know right now is you'll need Windows 7 or newer, and a GPU capable or supporting fairly new versions of OpenGL (4.2 or newer). It may be high but do remember that hardware will catch up so the engine should have a long shelf life, longer than GoldSource considering how poorly the old renderer is supported now.

Pretty much all of the requirements can change depending on future development, and if Valve get their heads out of their collective asses and open source the engine i could do 64 bit development which would drastically increase the options available for bigger features.
Posted 3 days ago2019-01-15 11:09:48 UTC
in SharpLife - Dot Net Core based modding p Post #341662
Wad files don't support 32 bit textures so no.

You can already make projectile bullets in vanilla GoldSource, it just takes some effort. Modifying the RPG/AR grenade code should get you started.

What i talked about before about a V2 of SharpLife is what all the newer stuff should go into. V1 reproduces the original engine, using original file formats and stuff. Then backwards compatibility is broken by using new formats optimized for performance and customization and a better toolchain. If i can do that then i can solve all of the problems related to limitations caused by the file formats, texture name length being one of them.

That'll require a lot of support in the tools themselves. That's why i've been working on new compile tools, the map editor will also need to support the material system so i hope Sledge will support plugins that can provide a material system.
Posted 4 days ago2019-01-14 18:15:47 UTC
in SharpLife - Dot Net Core based modding p Post #341655
you told me 3d skybox and HD sky is posible.
are bigger maps and bigger textures posible too?
Both are possible.
And wad files are pain in the ass too xD could you make it so we dont need wadded textures.
A image file in the mod folder works very well, at least for developing a mod.
Due to how maps reference textures it's not currently possible to ditch wad files since you can only have 15 characters in a texture name, not nearly enough for directory names.
One question, what will be the structure of SL?, I mean, will it load a mod directly or it´ll depend of a Half-Life folder with the valve folder and all the rest of junk?, I like the way (don´t flame me please) Xash works. With it you must only create a folder with whatever name you want, you put the Xash exe, three dlls and the very folder of the mod in it and that´s all. Less than 10 Mb in size. And you can launch a full mod as if it was a standalone game (at least if you have all the needed assets in the MOD folder, of course).
SharpLife is an all-in-one package, so you'll make a mod that're the game libraries in SharpLife, and deploy the entire engine as a Half-Life mod. This allows each mod to change the engine as needed. Currently the assemblies directory is 11.2 Mb, but that includes debug builds and debug info, so it probably adds up to a similar size when it's all release builds.

SharpLife still needs a file, used so the original engine can load the client.dll library which bootstraps SharpLife. This also allows Steam to find the mod and list it automatically.

It does depend on the valve folder, since it loads assets from there. The current design allows it to load assets from other game directories as well, so you won't have to copy over Opposing Force files for instance, which makes it easier to use game-specific files without having to redistribute anything. Ideally maps can specify which games they use assets from to help with error handling.

All of this is configurable in the code, so you can remove the dependency on original content if you want.
Posted 4 days ago2019-01-14 16:19:45 UTC
in SharpLife - Dot Net Core based modding p Post #341653
SharpLife currently has a limit set to 256 but it's a cvar that you can increase to any value, or you could remove the limit from the code altogether.

The only limit i've found is that really large images fail to load in some graphics backends, i'll need to reproduce the issue and report it to see if it's caused by Veldrid or something else.

SharpLife uses Veldrid to handle the graphics backend, and that supports OpenGL, Vulkan, DirectX, and Metal on Mac. All i need to do is add the code to let you choose and set up the correct backend (just a few lines of code for each backend), and it will work. Shaders can be written in GLSL and used by all backends, so it's pretty much ready to go already, though when i last tested it Vulkan had issues. They've probably been fixed by now though.
Posted 5 days ago2019-01-13 20:04:20 UTC
in SharpLife - Dot Net Core based modding p Post #341639
Both of those things should be possible.

I'm currently looking into how to make a material system to let you choose which shader is used for textures, but something like this will require new file formats and breaking changes in some places (e.g. no more render modes) so i'll have to choose between greater flexibility or supporting the original engine's way of doing things.

I'm thinking it may be better to go for a first version that supports the original formats, then making breaking changes in a V2 branch that focuses on maximizing performance at the cost of dropping features like render modes. There would probably be other ways to accomplish it though, like color can be done by using a proxy to fetch the color from an entity while rendering. I'll probably leave further research for later since it's quite complicated.
Posted 5 days ago2019-01-13 18:09:39 UTC
in SharpLife - Dot Net Core based modding p Post #341637
No, it'll take a while before everything gets to a functional level. I can't say how long it'll take.
Posted 5 days ago2019-01-13 17:44:14 UTC
in SharpLife - Dot Net Core based modding p Post #341635
SharpLife basically ignores the original engine and does everything itself. The original engine is only involved so SharpLife can be called a mod and used legally by modders.

As such i can implement the renderer using modern techniques which dramatically increases performance.
Posted 1 week ago2019-01-11 19:41:12 UTC
in SharpLife - Dot Net Core based modding p Post #341619
Could you please stop making off topic posts in this thread? I'm also not interested in you trying to get my attention for whatever you're working on.
It might, i'm not sure.
Posted 1 week ago2019-01-09 18:00:31 UTC
in SharpLife - Dot Net Core based modding p Post #341604
I've committed the latest assemblies for the master branch. The previous ones were a couple months out of date, and i made some changes to debug logging to make it easier to figure out why startup could fail.

The native wrapper now logs to <gamedir>/logs/SharpLifeWrapper-Native.log.

If you enable debug logging (DebugLoggingEnabled=true in cfg/SharpLife-Wrapper-Native.ini), you'll now get this in the log:
[09/01/2019 18:56:14 +0100]: Managed host initialized with game directory sharplife_full (client)
[09/01/2019 18:56:14 +0100]: Configuration loaded
[09/01/2019 18:56:14 +0100]: CoreCLR loaded from C:\Program Files (x86)\dotnet\shared\Microsoft.NETCore.App\2.1.2
[09/01/2019 18:56:14 +0100]: Runtime started
[09/01/2019 18:56:14 +0100]: AppDomain 1 created
[09/01/2019 18:56:14 +0100]: Managed host started
[09/01/2019 18:56:14 +0100]: Attempting to load assembly and acquire entry point
[09/01/2019 18:56:14 +0100]: Created delegate to entry point SharpLife.Engine.Host.NativeLauncher.Start
[09/01/2019 18:56:14 +0100]: Attempting to execute entry point
[09/01/2019 18:56:16 +0100]: Entry point executed with exit code 0
[09/01/2019 18:56:16 +0100]: Shutting down managed host
[09/01/2019 18:56:16 +0100]: Exiting with code Success (0)
The engine now returns an exit code indicating if anything went wrong. Since the engine can't log anything until the game directory is known, a missing game directory is logged as UnhandledException since it throws an exception. If there are no command line arguments at all the error is NoCommandLineArguments.

This should cover the different problems during startup.
I made a small tool to find the error code returned by LoadLibrary calls. This is needed because the engine doesn't report the error code for failed client.dll library loading.

Repository here:

See the README for usage.

The issue that made this tool necessary:

I also included additional information for finding missing dependencies using other tools, since the error code won't tell you which dependencies are missing.
I used Process Monitor to find it by looking for library load attempts after the client dll is loaded, this is what the relevant information looks like:
User posted image
The missing library is a debug version of a VS2017 redistributable library that contains the C runtime. The redistributable only contains the release version, and the client.dll was a debug build. Recompiling it as a release build solved the problem. If you have VS2017 with C++ support installed it will have installed this debug version so you would be able to run it regardless.

This should make debugging could not load library <name> errors much easier.
Posted 1 week ago2019-01-07 22:17:27 UTC
in playing with the console command list Post #341579
You can get the command arguments using cl_enginefunc_t::Cmd_Argc and cl_enginefunc_t::Cmd_Argv, just like you'd do it on the server side.

All the function member does is store the callback invoked for a named command, you can't control which one is executed based on inputs, you'd have to do that in the callback itself.

If your goal is to override an engine command conditionally based on what's passed into it you can create your own callback that stores off the original function and that forwards calls unless your condition is matched.

Unfortunately you can't add state to the callback since it's all basic C style code, so you'll have to either use global variables or create a lookup table to get a stateful object that will handle the callback. If you're going to do that registering the same callback for all your commands and then using the first argument to find the handler should do.

The complete definition for the engine's data structure is this:
typedef struct cmd_function_s
  cmd_function_s *next;
  char *name;
  xcommand_t function;
  int flags;
} cmd_function_t;
The flags member is used to keep track of where commands came from so they can be removed (client, server, GameUI). Otherwise, if a library gets unloaded the command can still be executed, but the callback will point to freed memory.

As for multi-line formatting, see this:
Referencing this here so everybody can see it:

If anybody knows of tutorials for learning to work with the SDK, can you post them here?
It's crashing because an env_beam between two entities is trying to use an entity that's been killtargeted. It passes what is supposed to be the entity origin by reference to the engine, which then tries to dereference a garbage address. That's causing the crash.

I'm going to decompile the map and see how this entity setup works, i'm thinking a killtarget isn't working properly here.

Edit: i've found the problem. env_spritetrain isn't fully implemented so it never triggers the fire on pass targets for its path_corner entities, which are supposed to killtarget the alien beam effects. As a result the beam tries to use one of the alien ship entities, which is killtargeted when the ship hits a path_track of its own so it tries to pass a reference to the origin of that ship to the engine, which then causes a crash due to dereferencing freed memory.

To fix this env_spritetrain needs to be implemented. It should be relatively easy to do, inherit from func_train and internally create an env_sprite using MOVETYPE_FOLLOW to attach it to the train.
He forgot to add an import library. You can get it from the original Half-Life Github repository. Clone it and copy the file utils/vgui/lib/win32_vc6/vgui.lib to SourceCode/utils/vgui/lib/win32_vc6.

Edit: the developer just committed all of the missing library files, so you can just pull the latest commit.
I've never used it before, but after checking the source code i've found 2 problems:
  • The projects were never updated for V2017 so it has the "'abs': ambiguous call to overloaded function" errors
  • The last commit in the op4 branch restructured the folders without updating the project files
To fix this you can just open the vcxproj and filters files and manually change the paths so that each one points to the "gearbox" directory:
<ClCompile Include="..\..\dlls\aflock.cpp" />


<ClCompile Include="..\..\gearbox\dlls\aflock.cpp" />

The same goes for ClInclude elements.

Both the gearbox_dll and gearbox_cldll projects need this done, and the filters files also need to be updated to reference the correct files. It's a bit of work, but once it's done it should work fine.

I've reported the issue, hopefully he'll fix it pretty soon:

Otherwise you can try reverting the last commit so the folder structure matches the project files.

I also found this, it might be a better option for you:
Posted 1 week ago2019-01-05 18:13:33 UTC
in SharpLife - Dot Net Core based modding p Post #341555
I've been working on the scripting system some more. I was going to rework CSharpScript's script class loading behavior to make it work more like the assembly based provider does things, but i discovered that there is no way to directly access the generated assembly's Reflection instance. I've requested that this be added:

Once this is possible the assembly and CSharpScript providers should behave largely the same way. There won't be any need to return the type of the script class, and a callback in the script info instance will allow the user to resolve the correct class. This will make it much easier to implement per-entity custom classes by simply finding the class defined in a script that inherits from the right base class as shown in bonbon's image.

That callback should just do this:
private static Type FindEntityClass(CSharpScriptInfo info, Assembly assembly)
    var scriptClasses = assembly.GetTypes().Where(t => t.IsClass && typeof(TScript).IsAssignableFrom(t)).ToList();

    if (scriptClasses.Count == 0)
        //No classes found that extend from the required type
        throw some exception;

    if (scriptClasses.Count > 1)
        //More than one candidate class found
        throw some exception;

    return scriptClasses[0];
Where TScript is the type of the entity that it needs to inherit from.

The same thing will be possible using the assembly provider, which makes it easier to use both.

With this in place the game should have a simple scripting system that lets you load scripts once and then create instances of types when you need them like this:
//The script system should cache the loaded script so repeated calls return the same script instance
var script = Context.ScriptSystem.Load("my_script_file.csx");

//The script itself doesn't contain any object instances, these are created on demand only
var entity = script?.CreateEntity(Context.EntityList, typeof(Item));
It may be easier to rework the scripting system to only provide an abstraction for the provider and letting the user get the type information using the resulting Assembly, but this would restrict the supported languages to whatever can produce an Assembly instance.

In that case i can remove the type parameter from the scripting system itself, since the only purpose is to make it easier to access individual script object instances and it's better to let users keep track of them. Perhaps splitting the system up into providers and type instance factories would allow for both, i'll need to look into it further.

I'm also going to make sure that scripts can load other scripts using #load, which CSharpScript already supports but which requires me to provide a SourceReferenceResolver. This should let you split scripts up just like you can with Angelscript. That in turn means that this resolver will need to understand how the SteamPipe filesystem works. That shouldn't be a problem since the resolver class API is pretty easy to use, and should just pass through to the filesystem class i've already created for SharpLife.

Edit: i just discovered that there is an existing framework that does pretty much the same thing called Managed Extensibility Framework:

It looks like it can do pretty much everything that this system can currently do, albeit without CSharpScript support. I'm going to look into it some more to see if it can do what i'm doing with my own system.

Edit: MEF can do everything that my system can do, and for the use case i have in mind for it each script assembly would need its own CompositionContainer which eliminates the problem with adding assemblies later on.

That leaves only one problem which is memory usage. I'll have to look into how much it uses for small assemblies like scripts. I read some reports about high memory usage due to dependent assemblies being loaded, but CSharpScript requires explicit references to all dependent assemblies so they have to be loaded already. Indirect dependencies might be a bigger issue but that remains to be seen. The biggest issue i need to look into is how much memory a CompositionContainer needs on its own.

Edit: Looks like an empty container is just 320 bytes in total, though that may not be 100% accurate. Looks like it's worth using like this then:

Assembly and CSharpScript based plugins are all part of one container, exporting their main script implementation using MEF. Required interfaces can be exported from the game and imported by plugins using MEF, allowing for dependency injection on both sides.

CSharpScript based map-specific scripts get their own container, with scripts being cached along with their container based on file modification time to allow easy reloading. This reduces memory usage by loading scripts only once per server lifetime. Since a compiled script is just an assembly it can't be unloaded, just like a regular assembly. Otherwise it'd leak tons of memory by way of redundant compilation and assembly generation.

Scripts that are reloaded, either due to a newer version existing on disk or by forcing a reload (cheat command) would throw out the cached version and reload it entirely. The old version would continue to be in memory and objects created from it will continue to exist and be referenced as before, so a complete refresh is needed to oust old versions (reloading the map should do it since it's a map specific script).

I'm going to look into reworking the scripting system to support this, so most of my current provider code will probably end up being thrown out. The script validation code and API configuration types are still needed so most of it will still see use. This will make it impossible to use non-CLR based languages for plugins or scripts, but i doubt that's a big deal since C# and VB are more than enough to support the required use cases.
Posted 2 weeks ago2019-01-04 13:18:24 UTC
in custom blood colours Post #341542
It's used for particles as well if i'm not mistaken.
I found this article while cleaning up my bookmarks:

It goes into detail on how Quake's visibility calculation was developed and how it works. It's an interesting read.

There are more articles on this website, you can find them here:
Posted 2 weeks ago2018-12-29 09:36:38 UTC
in getting number of sprite frames on server Post #341521
Are you sure it's actually a sprite? If it's a studio model it will return what looks like the largest valid value for pev->body for that model.
Posted 3 weeks ago2018-12-28 22:34:49 UTC
in Sven Co-op forums shutting down Post #341509
Or are there other reasons? I remember the sour relationship you had with the members of the Sven-Coop development team. Anyway, you will be a little tempted to feel a kind of cruel satisfaction ... :crowbar:
I am in no way involved with this decision, and my only concern is that valuable information could be lost.
Posted 3 weeks ago2018-12-28 20:33:47 UTC
in Sven Co-op forums shutting down Post #341505
This doesn't seem a valid reason to shutdown when you can switch to a different more secure bulletin board platform...
That may have to do with the planned new forum that was to be written from scratch, but i don't know if that's still the plan.
Posted 3 weeks ago2018-12-28 18:23:29 UTC
in Sven Co-op forums shutting down Post #341503

The forums are getting shut down soon, so i'd advise anyone who needs information hosted there to back it up, maybe rewrite it as a wiki article here.
Posted 3 weeks ago2018-12-26 16:47:36 UTC
in SharpLife - Dot Net Core based modding p Post #341490
I'm currently working to make SharpLife.Scripting more flexible when using CSharpScript to make it usable for client side scripting. Currently scripts can access whatever they want so they can use things like reflection, filesystem APIs, etc, which can cause security issues. To prevent this from happening, i've been working on a way to explicitly register an API that scripts can access.

It currently looks like this:
private static void ConfigureAPI(APIConfigurationBuilder builder)

        var type = builder.AddType<App>();
        type.AddMethod(a => a.Test(default, default));

        var type = builder.AddType<string>();
        type.AddMember(() => string.Empty);
        type.AddMethod(s => s.Split(default));
First you add assemblies that you want scripts to be able to access. Then you can add namespaces and types to grant or deny access to.
Types can have properties, fields and methods added where you use lambdas to specify the member being added.

It's possible to specify whether you're adding something to allow or deny access, or to inherit the default access settings for that particular type of API functionality (namespace, type or member). These settings can be configured, whichever access rule is set when the configuration is built will be used for all types that don't specify a rule to use.

As you can see in the example you can select method overloads. Since you only need type information and not actual values use of default and default(type) is pretty common.

Once a configuration has been finished it becomes immutable, preventing changes from being made to the API.

By default, an empty APIConfiguration instance denies access to everything except built-in types (int, float, bool, string, etc).
If a script accesses an API that it isn't permitted to access the script is never compiled, so no code is generated and no static initializers will run, preventing any potentially malicious code from executing.

Malicious code includes but is not limited to using filesystem APIs to create files that interfere with client side game functionality, using reflection to access engine functionality to manipulate game behavior (e.g. slowhacking), and using web APIs to download other files that could be used to execute more malicious code.

A properly configured API configuration can prevent abuse while still allowing access to any APIs that are needed. It is possible to block access to parts of types that can be abused, for instance you could allow querying of types and their members, but not allow any invocation of members through reflection.

API configurations are separate from script providers, so one configuration can be used by multiple providers at the same time (e.g. CSharpScript and Python).

I'm still working on it, but most of the functionality is already working. I'm using the Roslyn API for code analysis to check parsed scripts, this API allows for much more than that so it's pretty interesting.

Does this look like it makes sense? Is it easy to understand the purpose and use of this functionality, the reason why it's necessary? Does anyone have any suggestions to improve it if possible?
Posted 4 weeks ago2018-12-21 11:06:50 UTC
in VHLT source code cleaned up Post #341472
It's easier to write everything in the same language so the code can be shared. The engine's data structures for BSP data can also be used by the compiler, so i can increase limits very easily.
It's also much easier to provide debug info when the compiler breaks, and it's easier to maintain this codebase since it's designed and written at by one person in one go, without a bunch of #ifdefs and varying code styles.

Also, C is very poorly suited to writing tools and games these days. A fair amount of code in these tools does nothing more than free memory, something that even C++ could do automatically through deterministic destructors.
Posted 1 month ago2018-12-14 08:42:21 UTC
in SharpLife - Dot Net Core based modding p Post #341450
I've never used Unity before, but i already planned to do something similar. If you look at Sven Co-op's scripting API you'll see that the design is fairly similar, my design for SharpLife is a refined version of that. I'll need to look into making it easier to make single class scripts that are automatically associated with an entity since it's currently designed to use single scripts for entire maps.
Posted 1 month ago2018-12-13 20:49:23 UTC
in VHLT source code cleaned up Post #341446
I'm posting this separately for visibility: does anyone have a large map that takes a bit (~5 or more minutes) to fully compile using a full run of all tools? I'm going to run a performance analysis tool to find the code that's taking the longest so i can see if anything can be optimized. Ideally with default textures only so i don't need to reference other wad files.
Posted 1 month ago2018-12-13 16:17:43 UTC
in VHLT source code cleaned up Post #341444
I've been keeping track of code i've found that could be optimized in the original version, here's what i've got so far:
  • The aforementioned use of a reader-writer lock to ensure thread-safe access to the planes array eliminates potential garbage reads
  • A fair amount of code leaks memory by not freeing the faces list created by MakeBrushPlanes. Using a std::unique_ptr that frees the memory eliminates the leak, using the function FreeFaceList as the custom deleter function. Though i've never heard of CSG running out of memory, in a shared process this does matter a lot more, especially once RAD has started. This isn't a problem in my version of the tools because it's garbage collected
  • The faces list could be converted to a std::vector<bface_t> to automatically manage the memory and to allow faster iteration by storing the faces by value. Note that this will increase the cost of sorting the sides (the entire face needs to be copied, instead of just changing its position in the list), but that cost may be offset by the speed increase resulting from all faces being located in the same memory range, and thus more likely to be in the CPU cache. Sorting is only done once per brush per hull (4 hulls) at the most (disabling clipping can skip the whole process, some contents types don't create faces at all either), but iteration is done many times, and additional faces are added during the brush expansion process
  • Origin brush processing and zhlt_minsmaxs handling both use the CreateBrush function to get the bounding box from a brush. However CreateBrush does a lot more work than just calculating the bounding box, work that can be avoided entirely thus speeding up the parsing of the .map file. If properly reworked the number of planes generated can also be reduced by not letting these two features add planes to the array used by the actual brush creation operation. This can reduce the chances of hitting the internal and/or engine limit on the number of planes, since the planes that contain origin and zhlt_minsmaxs brushes are not often going to be used by other brushes
  • Merging CSG and BSP will cut down on work done to save and load the data generated by CSG, speeding up both tools and eliminating a possible data corruption issue if the plane data is modified between CSG and BSP execution
  • As is the case with the engine, there's code that uses a varying number of (-)99999 to denote maximum/minimum values, which can cause problems when the engine limit is increased and larger maps are possible. For instance the function isInvalidHull uses 99999 as its limit, so brushes that are partially or completely beyond this limit can end up marked as invalid. Using <cstdint>'s limits constants INT_MIN and INT_MAX appropriately should solve the problem
That's right, those entities aren't removed from the entity data after RAD's done.
If you look at maps like the Infested series for Sven Co-op you'll see that it uses the "second train" approach. This does cause issues when one of them gets blocked, for instance the elevator in the first map can end up out of sync with the doors stuck closed or in the wrong place.
Texlights are only possible if you have the original lights.rad, since lightmaps contain lighting data from multiple sources. You might be able to determine which textures were given a texlight value based on lighting values emitted near instances, and approximate the values but it's never going to be as accurate as you want it to be.
Posted 1 month ago2018-12-12 12:57:10 UTC
in VHLT source code cleaned up Post #341435
Yeah we'll just have to wait and see for performance.

I've been converting CSG's code and i've noticed some areas where performance can improve by not having to write stuff to files. CSG normally writes plane data to 2 files; the BSP file and a separate .pln file. BSP loads the .pln file or if it doesn't exist, converts the data from the BSP file.

Since this data is now shared directly it eliminates the file I/O overhead and the need to support conversion from the BSP file.

This is probably why CSG was merged into BSP for Source.

I've also found a comment indicating a possible race condition in in the plane generation code:

There is a possibility that one thread is modifying the plane list while another is reading from it, which could cause it to read garbage.

To fix this the code needs to use a reader-writer lock:

That's the best solution for this type of problem, it eliminates any race conditions and garbage reads but it'll slow it down a bit to acquire and release the lock.
Wait, so he's asking to be paid to develop this?

That aside i don't expect any decompiler to ever produce a 1:1 copy of the original, and there will probably be cases where brushwork is too complex to produce a compileable file.
Posted 1 month ago2018-12-10 09:30:19 UTC
in VHLT source code cleaned up Post #341414
The tools are already running as much as possible on worker threads. The advantage of using GPU calculations is that the GPU has cores made for mathematical calculations and can handle the spreading of work by itself since it already does that to optimize graphics rendering.

I don't know how much difference there could be in terms of performance since i can't find anything on that, so we'll have to wait and see how it works out. I should be able to get the CSG threading part done soon, then i can do some tests there to see how it compares. unfortunately since CSG is usually done so fast it probably won't have much difference in performance, it may even be slower due to overhead in creating GPU resources. RAD is really where it takes the longest, so that's where it can come in handy.

I'm not sure why having more cores wouldn't have an effect, it could be that each core's frequency is lower so it doesn't have as much effect. Perhaps the OS is under-reporting the processor count so it might not be taking advantage of all cores, but you should see that in the compiler settings output.

It's also possible that the worker thread code isn't optimized for a large number of threads and is locking too much to take full advantage of what it gets, i'm not sure.
Posted 1 month ago2018-12-10 07:45:40 UTC
in VHLT source code cleaned up Post #341412
There are a lot of things in this design that makes it faster. It only has to parse in the command line once, data is passed between tools directly instead of having to write it all out to files and then parsing it back in, and there are no fixed size buffers so it's easier to remove stuff without having to touch and move a bunch of lists.

Also, memory allocation in C# is much faster than in C++: http:/

The articles and discussions i've found on this are pretty old and Core is known to be much faster and more efficient so it may actually be the best tool for the job.

If i can use hardware acceleration for maths-intensive stuff like VIS and RAD it'll go even faster. You can then compile maps on your GPU.

I'd also consider any slowdowns to be worth the cost is it means having tools that you can actually understand the inner workings of and modify as needed. Very few people know how to make adjustments to these tools.
Posted 1 month ago2018-12-09 14:47:54 UTC
in VHLT source code cleaned up Post #341408
I think i've nailed down the design of the compiler and tools to make it easy to use as a library as well as a command line program:
ILogger logger = ...;
var providers = new ICompileToolProvider[]

using (var compiler = new MapCompiler(logger, providers))
        compiler.Compile(mapData, sharedOptions);
    catch (CompilationFailureException e)
        logger.Error(e, "An exception occurred while compiling");
The compiler takes a logger and a list of providers of tools. A tool is provided by a provider that specifies the tool name, its type and can create instances of the tool.

Tools can be configured using an initializer:
var provider = CSGTool.Provider.WithInitializer(csg => csg.HullData = myHullData);
This creates a provider that invokes an initializer function on the tool. In this case the hull data used by CSG is overridden to use your own settings, but more options will be available for each tool.

This approach also allows you to create custom tools that can be inserted between other tools. As long as your tool returns the correct data type for the next tool this will work just fine. You could use this to add tools like analyzers or something that can output the data returned by a tool. For instance, if RAD were to return lighting data that isn't in the BSP file as its result you could output it to a file.

As far as command line usage goes the library i'm using doesn't support the older -option value syntax, only -ovalue or --option=value. Since you'd need a new interface to use this compiler properly anyway i don't see this as a problem. With this compiler you only need to invoke the frontend once to run all the tools you want, so having different syntax can actually prevent misuse by erroring out on the old syntax.

This also lets me use better names for options. Whereas the old tools would often have -no<whatever> arguments, this one just has --<whatever>, for instance -noresetlog becomes --resetlog.

I'm trying to avoid doing any file I/O in the compiler itself, so any files that need loading will be loaded either before the compiler is created or in the initializer for the tool that needs the data. If it's output data then it will need to be handled as a post-tool operation, possibly a custom tool.

This also means that any data that is being output to a file should be available to you programmatically when invoking the compiler as a library. This can make it much easier to access some data, like point file output.

As far as log output goes, i'm currently using the standard Serilog message template, modified to include the name of the tool that's logging the data:
[14:54:40 INF] [MapCompilerFrontEnd] test
The format is:
[time log_level] [tool_name] message newline exception
Since this drastically alters the output in log files i'll probably add an option to remove the extra information and just log what the old one does.

I've looked into how work can be spread over multiple threads, and it looks like C# has a class for that:

It's possible to configure this to match the original's settings, then it should be a pretty simple task to dispatch work and await completion. However, thread priority is something that should not be changed according to what i've found since this class has some monitoring system to help manage how threads are used. It may not even be necessary to change the priority if it's able to see other processes so that will require some closer inspection once there's some work being done.
Posted 1 month ago2018-12-08 16:40:40 UTC
in VHLT source code cleaned up Post #341406
I'm looking at the code that handles .map file loading and there's special code for QuArK style texture coordinates. However, according to its official documentation this is only used when you use the "Quark etp" format. The Valve 220 format uses the correct values.

I'm bringing this up because the code used to handle this has a pretty substantial presence in the map loading and CSG stages. The script tokenizer has to handle it specially and CSG has to convert texture coordinates differently for this format, which means the map data structures need to store off 2 different formats.

I found that Quark saves .map files correctly when used in "Half-Life" mode, which is when you select it as the active game:

So i will not be porting the Quark specific //TX# way of specifying texture data in those files. If these tools ever get finished you'll need to properly configure Quark for Half-Life to use it.
Posted 1 month ago2018-12-04 17:12:11 UTC
in SharpLife - Dot Net Core based modding p Post #341386
NET Core 3's preview has been released, so i can start doing some experiments with model viewer's design in Core:

I'm also working to get the Tokenizer class ready for use with .map file reading, it's now flexible enough to do all that. I've also eliminated memory allocation overhead involved with creating lists to configure it, now you can just cache the tokenizer configuration instance and reuse it.
Posted 1 month ago2018-12-03 13:19:36 UTC
in VHLT source code cleaned up Post #341383
Yeah i'm giving it a shot:
User posted image
Each tool is a library, the MapCompiler library provides a compiler, the MapCompilerFrontEnd is the command line interface to it.
You could invoke the compile programmatically with this. You could potentially generate BSP files dynamically this way, though it would still take time to compile everything.

I need to rework the Tokenizer class first so it can read .map files properly since apparently QuArK embeds texture info in comments and stuff.