Initialy, my idea was adding an options/choises for placing the camera on the +X -X +Y -Y +Z -Z axises, and the camera to point perfectly perpendicular at the zeroes/crossing of the other two axises, that make a perpendicular plane to the camera axis. The distance betwen the camera and the other two axises can be automated, so the whole model is viewable, no model parts going out of the 3D view planel, or even better, it can be added an option to select that distance by number, or the user sliding along the axis (which means practicaly: zoom in and out), so a user can keep it constant when rotating around the model, which is more convinient if you ask me.Most of that is already possible. The camera is currently always placed on the +X axis facing back to the model, positioned far enough away to show the entire model, up to a certain maximum size because there are some really large models out there (e.g. Natural Selection has some map-sized models). You can move the camera along the camera forward axis using the scroll wheel or right click+drag. The distance can be set explicitly through the Cameras panel. The Arc Ball camera maintains its distance while rotating around the model.
But then I realized, that it will be way way better if there is an option for switching between the proposed above and this:This is already the case. The current sequence's bounding box is used to get the center of the model, the camera is positioned at the middle point of that box. The Arc Ball camera rotates around this point. The point doesn't update when you change sequences.
Imagine the model have a perfect geometry center (center of mass). Imagine all 3 axises cross at this center instead of 0. Now if place the camera on any of this new "center of mass axises", the user will see the model better, because the "axises that cross at 0" are usually way way under the model compared to "center of mass axises". Everything else stays the same as above.
Asset->Center View
will use the current sequence to re-center the camera so you can use that to get a sequence-specific position.
No. I only suggest, that after placing the camera on an axis as described above, the user can move/rotate/zoom/roam it freely like usually/before from that axes position (go away from that axis). I just mean, that the view/camera shouldn't stay locked on the axis until explicitly other predetermined camera position is selected.This is the current behavior.
This is just a basic predetermined top, front, right side, etc. scene view in most programs. Goldsource model viewers just only have: start from only one option - center view (front view) and then you "free roam" around the model.There are 3 camera modes in HLAM, 2 of which were also in HLMV:
Aside from that, is Windows XP 32 SP3 build for HLAM even possible?No, Qt dropped support for XP after Qt 5.6. HLAM uses 5.15 and depends on certain features that were added after 5.6's release. 5.6 isn't available for download through Qt's maintenance tool anymore, though i did find a download link on their website but i don't know if that's a usable build.
func_train
entities teleport to their first target when their Activate()
method is called.Activate()
, so they won't teleport. You should make a request to the Sven Co-op team to update trigger_createentity
to optionally call Activate()
so the entity gets set up properly. Or alternatively making entities like these work properly when spawned at runtime without requiring a separate Activate()
call.Activate()
yourself.Wow, this is some really good work, even for you! The synthetic entities would allow people to make some really cool stuff per-map, I'd imagine... Kind of makes me want to reinstall JACK or Hammer 3.5!Synthetic entities are just a way to assign a new classname to a set of default keyvalues, really. Scripts with custom entities would be much more powerful than this, but i don't want to add scripting support until the codebase is stable.
{
"Name": "TemplateName",
"Include": [
{
"FileName": "some_other_path_under_cfg.json"
}
],
"KeyValues": [
{
"Key": "Key1",
"Value": "Value1"
},
{
"Key": "Key2",
"Value": "Value2"
},
{
"Key": "Key1",
"Value": "Value3"
}
]
}
Included in a map or server configuration file like this:
{
"EntityTemplates": [
{
"FileName": "path_to_template_file.json",
"Type": "EntityTemplate"
},
{
"FileName": "path_to_other_template_file.json",
"Type": "DefaultEntityTemplate",
"EntityNames": [
"some_entity_name"
]
},
{
"FileName": "ammo_9mmclip.json",
"Type": "SyntheticEntityTemplate",
"BaseEntityName": "ammo_generic",
"EntityNames": [
"ammo_9mmclip",
"ammo_glockclip"
]
}
]
}
A template marked as default will be used to initialize all entities of the types listed in EntityNames
. This occurs after entity creation, before any keyvalues are added.Type
key can be omitted."Include": [
{
"FileName": "some_path_under_cfg.json"
}
]
Map and server configs can both do the same things, but map configs can have different filters applied to cvar setters and stuff (blacklisting certain cvars):
{
"Sections": [
{
"Name": "Pretty name for debugging",
"Condition": "Multiplayer && !Deathmatch",
"InitializationOrder": "PreMapSpawn",
"Actions": {
"Cvars": [
{
"Name": "mp_flashlight",
"Value": true
}
],
"Include": [
{
"FileName": "some_other_path_under_cfg.json"
}
]
}
}
]
}
Sections can be used to conditionally include configurations based on game state, so the same config can be used for singleplayer and multiplayer, or deathmatch and teamplay. The only problem with this is that config files can change the game mode, so this will need to be well defined somehow.InitializationOrder
controls when each section is applied. Server config sections are always applied before map config sections that have the same InitializationOrder
. You can have a server config applying something as a default, then a map config overriding things, then a server config forcing a setting back (e.g. time limit). Servers get a special order value that lets them set things after map configs to force things.void UTIL_ScreenFadeBuild( ScreenFade &fade, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags )
{
fade.duration = FixedUnsigned16( fadeTime, 1<<8 ); // 8.8 fixed
fade.holdTime = FixedUnsigned16( fadeHold, 1<<8 ); // 8.8 fixed
fade.r = (int)color.x;
fade.g = (int)color.y;
fade.b = (int)color.z;
fade.a = alpha;
fade.fadeFlags = flags | FFADE_LONGFADE;
}
Located here: https://github.com/ValveSoftware/halflife/blob/c7240b965743a53a29491dd49320c88eecf6257b/dlls/util.cpp#L726-L735FFADE_LONGFADE
flag indicates that a longer fade time can be used. The parameter to FixUnsigned16
has to be modified for both calls to use 1 << 8
.{
"BaseEntityName": "ammo_generic",
"EntityName": "ammo_9mmclip",
"EntityNameAliases": [
"ammo_glockclip"
],
"KeyValues": [
{
"Key": "model",
"Value": "models/w_9mmclip.mdl"
},
{
"Key": "ammo_amount",
"Value": 17
},
{
"Key": "ammo_name",
"Value": "9mm"
}
]
}
This is part of the proposal i wrote, it hasn't been implemented yet. Ammo entities can be described in terms of ammo_generic
entities, so this moves a bunch of code into user-editable config files.{
"wad" "\quiver\valve\halflife.wad;\quiver\valve\decals.wad;\quiver\valve\xeno.wad;\quiver\valve\sample.wad"
"chaptertitle" "C0A1TITLE"
"message" "Anomalous Materials"
"classname" "worldspawn"
}
{
"origin" "-424 280 -160"
"message" "ambience/crtnoise.wav"
"health" "2"
"spawnflags" "2"
"classname" "ambient_generic"
}
Entity data parsing is normally done in the engine, but there is an edge case that allows you to fool the engine into thinking it's already done parsing.classname
key for worldspawn
, the first entity in any map. Though it is possible for the classname to differ, this is forced back to worldspawn
for security purposes.{
in the entity data string.server_t
instance. This is possible by taking the string_t mapname
member in globalvars_t
and getting the pointer to the name using STRING()
. This is the address of the name
member in server_t
. Subtracting the offset of that member in server_t
gets a pointer to server_t
.server_t
's worldmodel
member is the BSP model, which contains a member entities
. This is the entity data string.{}
will cause the engine to stop parsing the string. It will still try to call the worldspawn
function which is an empty function. If it doesn't exist the engine will remove the world entity, so it has to be there.custom
in DispatchSave.custom
in SaveReadFields
for every ETABLE
read in.custom
for every entity it wants to create. This function is empty just like worldspawn
and does not actually create anything. The name custom
is special because the entity data parsing code in the engine also uses it to spawn entities it can't find a function for. This serves as a fallback to make sure that all works properly, but it should never be needed for that.DispatchRestore
is called the server checks if it has previously seen a particular SAVERESTOREDATA
instance before (identified by the map it was made for, and the landmark name associated with it). If it hasn't, it creates all of the entities that would have been created by the engine for that save game data.CREATE_NAMED_ENTITY
is also handled by the server dll so the engine does not create entities anymore, it only thinks it does.
{
"classname" "worldspawn"
}
{
"Entities": [
{
"ClassName": "worldspawn",
"KeyValues": [
{
"Key": "wad",
"Value": "\quiver\valve\halflife.wad;\quiver\valve\decals.wad;\quiver\valve\xeno.wad;\quiver\valve\sample.wad"
}
]
}
]
}
#load "Bootstrapper.builtin"
public class Diagnostics : ScriptProcessor, IDisposable
{
const string DiagnosticsFile = "Diagnostics.txt";
private readonly EntityCounter _itemsCount = EntityCounter.StartsWith("item_");
private readonly EntityCounter _weaponsCount = EntityCounter.StartsWith("weapon_");
private readonly EntityCounter _monstersCount = EntityCounter.StartsWith("monster_");
private readonly StreamWriter _writer;
public Diagnostics()
{
//Overwrite the old file if it exists
_writer = File.CreateText(DiagnosticsFile);
}
public void Dispose()
{
_writer.Dispose();
}
public override void OnProcessFile()
{
if (IsAnyOfficialGameCampaignMap(SourceFileName.Name))
{
_itemsCount.AddFrom(Entities, out var itemsCount);
_weaponsCount.AddFrom(Entities, out var weaponsCount);
_monstersCount.AddFrom(Entities, out var monstersCount);
_writer.WriteLine($"File {SourceFileName.Name}");
_writer.WriteLine($"\t{itemsCount} items");
_writer.WriteLine($"\t{weaponsCount} weapons");
_writer.WriteLine($"\t{monstersCount} monsters");
}
else
{
Logger.LogInformation("Ignoring map {Name}", SourceFileName.Name);
}
}
public override void OnEndProcessing()
{
_writer.WriteLine($"Total: {_itemsCount.Count} items");
_writer.WriteLine($"Total: {_weaponsCount.Count} weapons");
_writer.WriteLine($"Total: {_monstersCount.Count} monsters");
}
}
The line #load "Bootstrapper.builtin"
is a bit of magic that makes this class stateful across invocations. IsAnyOfficialGameCampaignMap
is a built-in function that matches map names up to official map names through regular expressions. There could be false positives if somebody named their map in a specific way, but that almost never happens.void ReplaceWorldItems(MapEntity entity)
{
//Convert world_items entities to their respective entities
if (entity.ClassName == "world_items")
{
switch (entity.GetInt("type"))
{
case 42:
entity.ClassName = "item_antidote";
entity.Remove("type");
break;
case 43:
//Remove security items (no purpose, and has been removed from the codebase)
Entities.Remove(entity);
break;
case 44:
entity.ClassName = "item_battery";
entity.Remove("type");
break;
case 45:
entity.ClassName = "item_suit";
entity.Remove("type");
break;
}
}
}
void UpdateSuits(MapEntity entity)
{
//Set the logon type to the original default
if (entity.ClassName == "item_suit" && !entity.ContainsKey("logon_type"))
{
entity.SetString("logon_type", "LongLogon");
}
}
foreach (var entity in Entities.ToList())
{
ReplaceWorldItems(entity);
UpdateSuits(entity);
}
The tool is still a work in progress so no release yet.LINK_ENTITY_TO_CLASS
macro is no longer used, the code generator takes care of that part now as well.#pragma once
#include "CBaseEntity.hpp"
#include "CEnvSpark.generated.hpp"
constexpr int SF_SPARK_TOGGLE = 1 << 5;
constexpr int SF_SPARK_START_ON = 1 << 6;
class EHL_CLASS("EntityName": "env_spark", "EntityNameAliases": ["env_debris"]) CEnvSpark : public CBaseEntity
{
EHL_GENERATED_BODY()
public:
void Spawn() override;
void Precache() override;
void EXPORT SparkThink();
void EXPORT SparkStart(const UseInfo& info);
void EXPORT SparkStop(const UseInfo& info);
void KeyValue(KeyValueData* pkvd) override;
EHL_FIELD("Persisted": true)
float m_flMaxDelay = 0;
};
The code generator will now also raise an error if you use unknown attributes or incorrect attribute values (e.g. a string value for Persisted, which is a boolean).cpp.hint
file to tell Intellisense how to deal with the codegen macros. Without this file it can't properly parse class declarations and lists functions as having no definition, which is pretty annoying.
.natvis
file to tell the debugger how to display entity classes in the debugger. This lets you easily see the classname, targetname and target of any entity you have a pointer of: You'll have to click the refresh icon since these are functions. This makes debugging a lot easier, especially when an entity has an unknown class (e.g. AI code where you want to know the target entity's class).current time + delay
, but was current time + current time + delay
.LINK_ENTITY_TO_CLASS
now also registers entities in a global dictionary to allow name based lookup. I've also added support for "alias" names that ensure aliased entity names such as weapon_glock
not only map to weapon_9mmhandgun
but also automatically change the classname to the intended name.GetClassPtr
function has been removed since it is badly designed and doesn't handle creation properly (classname is never set). The CREATE_NAMED_ENTITY engine function is now obsolete and has also been replaced by the new entity list API.common/null.wav
effectively disables it.RGB_HUD_COLOR
to the color you want to use.void DLLEXPORT HUD_Frame( double time )
{
// RecClHudFrame(time);
UnpackRGB(giR, giG, giB, RGB_HUD_COLOR);
GetClientVoiceMgr()->Frame(time);
}
And change it so the RGB value is set to whatever you want it to be. If you're using separate cvars for the color then it should be like this:
giR = CVAR_GET_FLOAT( "hud_red" );
giG = CVAR_GET_FLOAT( "hud_green" );
giB = CVAR_GET_FLOAT( "hud_blue" );
Note that if you do this, CTF's HUD colors will no longer work since you're overriding the color every frame.EA
group.EA
prefix and that has a number following that prefix.sentences.txt
. Note that you will need to make a custom mod to do this since modifying the original file will cause problems (will be reverted if the game is validated through Steam, multiple maps with their own copy will overwrite each-others changes, etc).Persisted
attribute will be added to the save game data. The Type
attribute can be used to signal that a variable needs specific support like saving it as a time or position variable.cbase.h
into separate files for each class, each header now includes their dependencies as well. Logic that used to be in Restore
is now in a separate method called PostRestore
, called if Restore
returned true.UTIL_FindEntityByString
since that does the work of testing for valid entities for you. FIND_ENTITY_BY_STRING can return the world which without the FNullEnt
checks can result in an infinite loop.pEntity->TraceAttack({attacker, SkillValue("sk_9mmAR_bullet"), vecDir, tr, DMG_BULLET});
void TraceAttack(const TraceAttackInfo& info) override;
UTIL_PlaybackEvent(flags, m_hPlayer, m_usMP5, {.fparam1 = vecDir.x, .fparam2 = vecDir.y});
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.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.
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).
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);
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)
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"
CBaseAnimating::GetAttachment
should work. On the client side it's complicated because attachment positions are cached in cl_entity_t::attachments
and get updated once per frame at a specific time. Depending on when you access that the values will either be outdated, set to the viewmodel origin or to their correct values.