Enhanced Half-Life (custom SDK/mod) Created 1 week ago2021-04-28 17:36:55 UTC by Solokiller Solokiller

Created 1 week ago2021-04-28 17:36:55 UTC by Solokiller Solokiller

Posted 1 week ago2021-04-28 17:36:55 UTC Post #345569
Enhanced Half-Life is a custom SDK/mod designed to replace my old Half-Life Enhanced SDK/mod.

It's not ready for a public release and the code is still in flux (400+ commits of refactoring and cleanup on top of Half-Life Updated) so i'm keeping that private for now.

The purpose of EHL is to do the following:
  • Provide an updated SDK that uses modern source control principles, including a proper directory structure and use of CMake to generate the project files
  • Third party dependencies are included instead of requiring manual setup by programmers (i'm looking at you, HLE's XercesC dependency)
  • Ease of use: can make a new mod in ~5 minutes by cloning the repository, configuring the CMake project to install to a mod directory, generating the files and building the INSTALL target. Also installs liblist.gam and delta.lst (part of the source code repository) to ensure smooth setup
  • Uses C++20, requires Visual Studio 2019 or newer. No Windows XP support.
  • Cleaned up codebase to ensure source code is the highest quality:
    • Obsolete files and APIs have been removed
    • Documentation has been upgraded to use Doxygen style annotations
    • Include guards have been converted to #pragma once and headers now always use #pragma once unless they need to be included multiple times
    • #define has been mostly replaced with constexpr constants
    • Global variables removed whenever possible
    • Duplicate code merged
    • Cleaned up C-style code to use C++ style, use const correctness, Vector instead of float[3]
    • Fixed old hacks needed because of backwards compatibility
    • Routed all filesystem access through IFileSystem, all references to the game directory name removed
    • Safer strings functions used to avoid buffer overflows
    • string_view used to make for better code (works well with non-terminated strings and substrings)
    • Defined constants for many magic numbers
    • Converted many constants to enum class which helps to catch invalid conversions
    • Lots of other stuff that's still in progress
  • The overall goal is to make the smallest SDK possible while maintaining full functionality, and even expanding on features by using newer language/library features, and using code generation to help eliminate redundancy in things like save game code
  • All of HLEnhanced features should eventually be re-implemented in this project, along with merging the unique features from Opposing Force Updated (weapons, NPCs, game modes) and Blue Shift Updated (handful of entities). This should make EHL the one-stop-shop for making a mod that mimics any of these 3 games, aside from the issue of Op4 weapons having grunt hands only
  • The UI should be replaced entirely with a VGUI2 version. The code for that exists in HLEnhanced and will be ported over later on when i tackle that task. Eliminating the use of VGUI1 entirely will help to simplify things.
  • All files loaded by the game codebase are to be converted to XML for consistency and to eliminate issues with invalid parsing, error handling, memory leaks, etc. I considered a few different formats (INI, TOML, JSON, and others) but XML is the format that provides everything needed, though i haven't yet started upgrading the codebase to use it yet. I'm planning to use RapidXML for parsing so the heavyweight XercesC dependency won't be a problem this time
  • Backwards compatibility with existing maps/models will likely be broken to improve entity keyvalue APIs and internal logic. For example some models made for barney and scientist NPCs will likely break due to changes in how submodels are changed (now done correctly). This will help to remove some of the cruft from the codebase that was needed due to Half-Life's maps having older keyvalues still in use. Breaking compatibility now in a big way can help avoid breaking changes later on
So far 53 files have been removed (not counting Ricochet and DMC files which have also been removed) and every file has been moved at least once. Engine headers have had internal engine stuff removed, lots of legacy stuff has been tossed out like the old security module stuff that the client used to have.
Compare this: https://github.com/ValveSoftware/halflife/blob/c7240b965743a53a29491dd49320c88eecf6257b/engine/APIProxy.h
To this: https://i.imgur.com/FVeCt0n.png

(that header was split in 2 so the client API part is only included by files that need to know about it)

I'd like to show something i finished up today. I improved the health and hev chargers:
video
Features:
  • Fixed dmdelay keyvalue not working and renamed it to recharge_delay, and allowed the use of floating point values
  • Allows instant recharging (warning: sound spam)
  • Allows mappers to control initial capacity, total capacity, charge per use and sounds to play
  • Allows chargers to start out of charge (initial capacity of 0)
  • Allows infinite capacity (initial/total capacity of -1)
  • Allows changing charge interval(time between charge uses, basically how quickly it gives you health or armor)
  • Prevents chargers from jamming up if they are constantly +used while recharging
  • Custom sounds
  • Sound to play on recharge (similar to sound played when items respawn in multiplayer)
  • Custom sound pitch setting (100 == default, 0 == minimum, 255 == maximum)
Keyvalues:

recharge_delay(string) : "Recharge delay"
charge_per_use(integer) : "Charge per use"
charge_interval(string) : "Interval between charges"
initial_capacity(integer) : "Initial charger capacity (-1 == unlimited)"
total_capacity(integer) : "Total charger capacity (-1 == unlimited)"
sound_pitch(integer) : "Sound pitch"
charge_on_sound(sound) : "Charge start sound"
charge_loop_sound(sound) : "Charge loop sound"
refuse_charge_sound(sound) : "Charge refuse sound"
recharge_sound(sound) : "Recharge sound"


Potential uses:
Making chargers that never run out and/or give tons of health/armor in an instant. Useful for co-op spawn areas, bunkers, etc.
Making chargers that start off empty but recharge after a long time to have some charge (or unlimited).
Making chargers that have little charge, but recharge quickly.
Chargers that look and sound alien.
Silent chargers (use common/null.wav).
Making chargers that give players their entire charge at once, or however much the player can take (health always expends charge_per_use, armor is conservative and only gives what the player can take). Like Condition Zero Deleted Scenes health stations.
Making chargers that charge really slowly, but charge a lot (forces player to expose themselves).

I'm still working on code cleanup so new features are few and far between. I decided to update the chargers because i was merging the code for both and i wound up adding a bunch of stuff.

Feedback and ideas are always welcome.
Posted 1 week ago2021-04-28 19:22:41 UTC Post #345570
This is very interesting, looking forward to it! Keep up the good work :)

I have a few questions:
  • The overall goal is to make the smallest SDK possible while maintaining full functionality, and even expanding on features by using newer language/library features, and using code generation to help eliminate redundancy in things like save game code
  • All of HLEnhanced features should eventually be re-implemented in this project, along with merging the unique features from Opposing Force Updated (weapons, NPCs, game modes) and Blue Shift Updated (handful of entities). This should make EHL the one-stop-shop for making a mod that mimics any of these 3 games, aside from the issue of Op4 weapons having grunt hands only
Are you going to provide the ability to have these extra features toggle-able through preprocessor defines (like HLEnhanced's USE_OPFOR) or separate repositories (like Half-Life Updated) to get a "HL SDK but just cleaned up"? I know this would require a lot of work but that would be nice for total conversions which do not rely or very little on existing entities.
The UI should be replaced entirely with a VGUI2 version. The code for that exists in HLEnhanced and will be ported over later on when i tackle that task. Eliminating the use of VGUI1 entirely will help to simplify things.
By "the UI should be replaced entirely with a VGUI2 version", I'm guessing you are talking about all the existing VGUI elements (class menu, team menu, scoreboard, observer's control panel...) or are you extending this to other UI stuff (HUD based menu, ammo, crosshair, health...)?

I have an idea: a few days or weeks ago, I was talking with some other GoldSrc programmers about skill CVARs and how it's tedious to add them. As you know already, when you add a skill CVAR in the game code (not talking about updating skill.cfg), you need to add a float variable to the skilldata_t structure, create the 3 CVARs themselves (cvar_t), register them (CVAR_REGISTER), "link" the variable and the CVARs using GetSkillCvar and optionally provide a multiplayer override. This is a pain in the ass because:
  • If you want to add a 4th difficulty, you need to manually add a 4th CVAR and register it for every set of skill CVARs and don't forget to update the "hardcoded clamp".
  • Prone to human mistakes in my opinion, especially for beginners, also known as the "did I updated the number correctly in that cvar_t->name syndrome?"
I was wondering if it would be possible to simplify all of these tedious things by having some kind of CSkillCvar class that would create the appropriate number of cvar_t, register them automatically?
Posted 1 week ago2021-04-28 19:57:35 UTC Post #345571
Are you going to provide the ability to have these extra features toggle-able through preprocessor defines (like HLEnhanced's USE_OPFOR) or separate repositories (like Half-Life Updated) to get a "HL SDK but just cleaned up"? I know this would require a lot of work but that would be nice for total conversions which do not rely or very little on existing entities.
Most of the stuff unique to Op4 and Blue Shift is opt-in for level designers and useful regardless so i don't see much point in making them conditionally included. On the programming side of things you can usually exclude these by removing the files from the CMakeLists files. I'm hoping to decouple the code so there aren't any hardcoded references to other entities like Op4's code currently does (e.g. player TakeDamage references allied grunts). Only the weapons have always-precached assets so maybe a way to eliminate that overhead is better than conditional inclusion.
By "the UI should be replaced entirely with a VGUI2 version", I'm guessing you are talking about all the existing VGUI elements (class menu, team menu, scoreboard, observer's control panel...) or are you extending this to other UI stuff (HUD based menu, ammo, crosshair, health...)?
Well i've tossed the class and team menus out of the codebase since they're TFC-specific, but i want to implement VGUI2-based versions of all of them. Ideally even the Hud should be VGUI2, that means converting the hud sprites to TGA which i call an improvement (no blurring when upscaling i hope).

Having only one UI for everything makes it easier to make new content for things. No more "is it a hud sprite, a VGUI1 TGA or a VGUI2 TGA?".
I was wondering if it would be possible to simplify all of these tedious things by having some kind of CSkillCvar class that would create the appropriate number of cvar_t, register them automatically?
Yeah i was thinking about that too. I'm thinking i'll just chuck skill.cfg out the window and use an XML file:
<Skill>
    <SkillValue name="sk_9mmAR_bullet" level="2" value="4"/>
</Skill>
No more cvars, just strings:

pEntity->TraceAttack(pevAttacker, SkillValue("sk_9mmAR_bullet"), vecDir, &tr, DMG_BULLET);

And then all of the variables are handled like so (pseudocode):
struct SkillValues
{
    std::array<std::optional<float>, SkillLevelCount> Values;
};

class SkillData
{
public:
    void LoadFromFile(const char* fileName)
    {
        auto xml = LoadXML(fileName);

        for (auto skillValue : xml.SkillValues)
        {
            auto it = _skillValues.find(skillValue.Name);

            if (it == _skillValues.end())
            {
                it = _skillValues.emplace(skillValue.Name).first;
            }

            it->Values[skillValue.Level] = atof(skillValue.Value);
        }
    }

    float GetSkillValue(std::string_view name, float defaultValue = 0) const
    {
        if (auto it = _skillValues.find(name); it != _skillValues.end())
        {
            return it->second.Values[_level].value_or(defaultValue);
        }

        return defaultValue;
    }

private:
    std::unordered_map<std::string, SkillValues> _skillValues;
    SkillLevel _level;
};
(That's actually most of the code needed for this)

Just have a CBaseEntity member to forward to the global and you're done.

This scales with any number of skill levels (just update the SkillLevelCount constant), any new skill values are automagically loaded in. There is still the skill level clamping being done but it's trivial to update that. The biggest problem is the hardcoded "new game" dialog, but i'm sure there are ways around that too.
Posted 1 week ago2021-04-28 20:01:07 UTC Post #345572
Looks great. I have some suggestions.
  1. Provide access to UI interfaces: IBaseUI, IGameUI, IGameUIFuncs, IGameConsole, IEngineVGui, etc. (an ability to manipulate the UI without accessing the engine / GameUI).
  2. Add missing interfaces inside server dll: NEW_DLL_FUNCTIONS / SV_SAVEGAMECOMMENT.
  3. Make a new generic mathlib that can suit all solution projects. You mentioned adding vgui2 support to client.dll, which is great. But putting it all in there is a pain in the ass. For example vgui2_controls.lib uses mathlib.h from Source SDK which also includes its own vector.h header file, for client and server we already have vector.h and util_vector.h and they will all hate each other. This could also be related to the tier0's platform.h, which will conflict with Platform.h from vanilla SDK. Creating common header files will not confuse people and take the project one step further.
I have many more suggestions (for example hacking the engine data like server_t / server_static_t etc), but I need to see what will be added next, so I will not suggest anything that could potentially go beyond the scope of the project. Best of luck with it.
Posted 1 week ago2021-04-28 20:29:46 UTC Post #345573
Somebody on Knockout requested this:

New keyvalues:
fire_on_recharge(target_destination) : "Fire on recharge"
fire_on_empty(target_destination) : "Fire on empty"

spawnflags(flags) =
[
    1 : "Fire on spawn" : 0 //If set, fire_on_recharge or fire_on_empty will be triggered on map spawn depending on initial capacity
]
I made it so "fire_on_recharge" sends a USE_ON and "fire_on_empty" sends a USE_OFF so you can directly enable and disable the sprite just by triggering it. Otherwise you can use a relay or multi_manager to handle it. The spawnflag is used to have the appropriate target triggered when the map starts. I made it opt-in so you don't have entities getting triggered by default, and you may want to set things up manually. But for simple cases this automates the work.
Provide access to UI interfaces: IBaseUI, IGameUI, IGameUIFuncs, IGameConsole, IEngineVGui, etc. (an ability to manipulate the UI without accessing the engine / GameUI).
Those are engine interfaces. They're also part of the VGUI2 API.
Add missing interfaces inside server dll: NEW_DLL_FUNCTIONS / SV_SAVEGAMECOMMENT.
Yeah i'll get to those sooner or later.
Make a new generic mathlib that can suit all solution projects. You mentioned adding vgui2 support to client.dll, which is great. But putting it all in there is a pain in the ass. For example vgui2_controls.lib uses mathlib.h from Source SDK which also includes its own vector.h header file, for client and server we already have vector.h and util_vector.h and they will all hate each other. This could also be related to the tier0's platform.h, which will conflict with Platform.h from vanilla SDK. Creating common header files will not confuse people and take the project one step further.
I know there will be conflicts, i'll be merging that stuff in as needed when i get to it. I've already cleaned up most of the duplicate stuff in the Half-Life SDK so the Source SDK won't be too much trouble when i get to it. I've even gotten rid of the Windows.h dependency messing up every file, it's only used in a few files now.
Posted 3 days ago2021-05-06 19:15:57 UTC Post #345589
Minor progress update:
  • I've implemented all of NEW_DLL_FUNCTIONS and provided a default implementation of SV_SaveGameComment with the hardcoded list of title names.
  • I've refactored the codebase so all references to entities use CBaseEntity. Only engine<->game calls deal with edict_t and entvars_t pointers directly now. I hope to eliminate as many of those as i can to streamline things.
  • virtual CBaseEntity methods that take a lot of parameters now use parameter structs. To use the above example for using skill values: pEntity->TraceAttack({attacker, SkillValue("sk_9mmAR_bullet"), vecDir, tr, DMG_BULLET});
    • On the callee's side: void TraceAttack(const TraceAttackInfo& info) override;
    • The purpose of this is to allow for changes to made to the data being passed. I've already adjusted KilledInfo to also include the inflictor so the global used in CBasePlayer::Killed is gone now.
  • Persistent pointers to entities are all EHandles now. Previously entities like the Alien Slave maintained raw pointers to effects entities. If those entities were to be removed for any reason the game would crash if they were accessed.
  • EHandles are now type-safe. You can use EHandle<class deriving from CBaseEntity> to specify a more specific type to use, which eliminates casting. I've also added methods to get the pointer to eliminate the need to cast to CBaseEntity, and a method to call UTIL_Remove on it if it's valid to simplify cleanup.
  • Entities that create effects entities now also remove them when killtargeted or otherwise removed (UTIL_Remove). This does not happen if the entity is destroyed through other means (anything that doesn't call UpdateOnRemove but does call the destructor) to prevent edge cases that could break things (e.g. removing entities when the map is changing, where the engine already does that and doesn't change the serialnumber on edicts).
  • The Alien Slave no longer creates beam entities that can be orphaned if the slave is caught in a transition volume during a level change. See https://github.com/ValveSoftware/halflife/issues/3104 for more information.
  • Fixed ~500 warnings that the V142 toolset reports concerning arithmetic overflow (casting float to double after performing arithmetic on them, nothing buggy in practice), uninitialized class and local variables and null pointer access.
  • Added precompiled headers to the client and server projects. These dramatically speed up compile times, i can compile both in 10 seconds flat. This also seems to reduce memory usage and the size of the Intellisense database files (~5 GB -> ~1.5 GB).
  • Simplified CMake setup even more. The game directory is now extracted from the CMake install prefix just like the mod directory which reduces the amount of required variables to just one, and allows it to work on all platforms instead of requiring manual configuration on non-Windows platforms (which used the registry value for the Half-Life directory). This also means mods targeting other engines will be set up more quickly, but no support is provided for other engines regardless.
  • Simplified multiplayer logging. Code that uses UTIL_LogPrintf in the SDK tends to follow the same pattern (if teamplay log this, else log that). Now gamerules handles this with a special logging function. Only one use requires this check due to logging player kills which requires logging 2 players worth of data.
  • PLAYBACK_EVENT_FULL calls have been simplified by using parameter structs and designated initializers to eliminate the need to specify unused variables: UTIL_PlaybackEvent(flags, m_hPlayer, m_usMP5, {.fparam1 = vecDir.x, .fparam2 = vecDir.y});
  • Added ent_remove developer cheat command: calls UTIL_Remove on the entity the player is looking at. Intended to be used to remove obstructions and unwanted entities, and to test what happens when an entity is removed at a specific point in its lifetime (e.g. while attacking, creating effects, etc).
  • Any math functions that exist in the codebase have been moved to mathlib.h/.cpp to help keep things centralized.
I've also been thinking about configuration file support some more to work out how to handle it. I chose XML because it allows for structured data and comments, but given how often people edit these without knowing how they actually work i want to make a front-end that makes it easier by doing as much work as possible for the user.

However, a front-end would load and save these files which would wipe out the comments anyway, so that advantage is negated. As such JSON is the better option since it's smaller in size, simpler to read, edit and parse and the parsers available support the entire format specification (RapidXML only supports a subset of XML).

I figure any comments that are needed can be embedded using a comment value that gets ignored during parsing. This comment can be shown as an editable field in the front-end which solves the problem of comments being wiped out by loading and saving.

The best C++ library for JSON parsing is nlohmann JSON which like RapidXML is a single header file (or a few headers depending on how you configure it). The advantage that this has over RapidXML is that it uses C++11 syntax.

This makes it easier to use, for example:
for (xml_attribute<> *attr = node->first_attribute();
     attr; attr = attr->next_attribute())
{
//Operate on node attributes
}
Versus:
for (auto& element : j)
{
//Operate on array element
}
So for the programmer this library is easier to use than RapidXML.

I was thinking of making this front-end complete configuration-driven, meaning it lets you specify a list of JSON files that describe each configuration file format (skill.json, listenserver.json, etc) so that it can construct an appropriate UI for each format. That would certainly simplify things for the user.

Beyond that i'm also thinking about how i can bypass the engine for certain things, like entity data string parsing, save/restore and other things. The more engine features i can take control of the easier things will become.

Most of the code cleanup stuff is done now so i can focus on upgrading things some more.
You must be logged in to post a response.