fix: crash with hl25 structs on xash3d again

fix: %t placeholder should return damage inflictor when used in team attack section of chat
fix: all arguments in bot commands are lowercased (resolves #680)
bot: disable threads when engine's timescale is active
bot: a little refactor for the code all over the places
linkage: resolved crash due to sse alignment on ancient engines (resolves #614)
utils move wave parse into crlib
practice: move loading practice to thread pool
build: allow to build with static linkents instead of hooking dlsym

Co-Authored-By: Max <161382234+dyspose@users.noreply.github.com>
This commit is contained in:
jeefo 2025-03-16 18:25:15 +03:00
commit 38c45aff9a
No known key found for this signature in database
GPG key ID: D696786B81B667C8
37 changed files with 348 additions and 304 deletions

@ -1 +1 @@
Subproject commit fdeb120576a13457867e720d49bfd2969e10d195 Subproject commit 11e1a6d4c09b16ceda1f5a9d362fe35bec3f0b85

View file

@ -231,8 +231,14 @@ public:
void collectArgs () { void collectArgs () {
m_args.clear (); m_args.clear ();
for (int i = 0; i < engfuncs.pfnCmd_Argc (); ++i) { for (auto i = 0; i < engfuncs.pfnCmd_Argc (); ++i) {
m_args.emplace (String (engfuncs.pfnCmd_Argv (i)).lowercase ()); String arg = engfuncs.pfnCmd_Argv (i);
// only make case-insensetive command itself and first argument
if (i < 2) {
arg = arg.lowercase ();
}
m_args.emplace (arg);
} }
} }

View file

@ -316,27 +316,27 @@ public:
} }
// gets edict pointer out of entity index // gets edict pointer out of entity index
edict_t *entityOfIndex (const int index) { CR_FORCE_INLINE edict_t *entityOfIndex (const int index) {
return static_cast <edict_t *> (m_startEntity + index); return static_cast <edict_t *> (m_startEntity + index);
}; };
// gets edict pointer out of entity index (player) // gets edict pointer out of entity index (player)
edict_t *playerOfIndex (const int index) { CR_FORCE_INLINE edict_t *playerOfIndex (const int index) {
return entityOfIndex (index) + 1; return entityOfIndex (index) + 1;
}; };
// gets edict index out of it's pointer // gets edict index out of it's pointer
int indexOfEntity (const edict_t *ent) { CR_FORCE_INLINE int indexOfEntity (const edict_t *ent) {
return static_cast <int> (ent - m_startEntity); return static_cast <int> (ent - m_startEntity);
}; };
// gets edict index of it's pointer (player) // gets edict index of it's pointer (player)
int indexOfPlayer (const edict_t *ent) { CR_FORCE_INLINE int indexOfPlayer (const edict_t *ent) {
return indexOfEntity (ent) - 1; return indexOfEntity (ent) - 1;
} }
// verify entity isn't null // verify entity isn't null
bool isNullEntity (const edict_t *ent) { CR_FORCE_INLINE bool isNullEntity (const edict_t *ent) {
return !ent || !indexOfEntity (ent) || ent->free; return !ent || !indexOfEntity (ent) || ent->free;
} }

View file

@ -82,6 +82,7 @@ private:
private: private:
CountdownTimer m_recalcTime {}; CountdownTimer m_recalcTime {};
PingBitMsg m_pbm {};
public: public:
explicit BotFakePingManager () = default; explicit BotFakePingManager () = default;

View file

@ -189,11 +189,12 @@ public:
SmallArray <Path> m_paths {}; SmallArray <Path> m_paths {};
HashMap <int32_t, Array <int32_t>, EmptyHash <int32_t>> m_hashTable {}; HashMap <int32_t, Array <int32_t>, EmptyHash <int32_t>> m_hashTable {};
String m_graphAuthor {}; struct GraphInfo {
String m_graphModified {}; String author {};
String modified {};
ExtenHeader m_extenHeader {}; ExtenHeader exten {};
StorageHeader m_graphHeader {}; StorageHeader header {};
} m_info {};
edict_t *m_editor {}; edict_t *m_editor {};
@ -270,11 +271,11 @@ public:
public: public:
StringRef getAuthor () const { StringRef getAuthor () const {
return m_graphAuthor; return m_info.author;
} }
StringRef getModifiedBy () const { StringRef getModifiedBy () const {
return m_graphModified; return m_info.modified;
} }
bool hasChanged () const { bool hasChanged () const {
@ -339,12 +340,12 @@ public:
// set exten header from binary storage // set exten header from binary storage
void setExtenHeader (ExtenHeader *hdr) { void setExtenHeader (ExtenHeader *hdr) {
memcpy (&m_extenHeader, hdr, sizeof (ExtenHeader)); memcpy (&m_info.exten, hdr, sizeof (ExtenHeader));
} }
// set graph header from binary storage // set graph header from binary storage
void setGraphHeader (StorageHeader *hdr) { void setGraphHeader (StorageHeader *hdr) {
memcpy (&m_graphHeader, hdr, sizeof (StorageHeader)); memcpy (&m_info.header, hdr, sizeof (StorageHeader));
} }
// gets the node numbers // gets the node numbers

View file

@ -105,7 +105,7 @@ public:
} }
public: public:
static int32_t CR_STDCALL sendTo (int socket, const void *message, size_t length, int flags, const struct sockaddr *dest, int destLength); CR_FORCE_STACK_ALIGN static int32_t CR_STDCALL sendTo (int socket, const void *message, size_t length, int flags, const struct sockaddr *dest, int destLength);
}; };
// used for transit calls between game dll and engine without all needed functions on bot side // used for transit calls between game dll and engine without all needed functions on bot side
@ -177,11 +177,11 @@ public:
} }
public: public:
static SharedLibrary::Func CR_STDCALL lookupHandler (SharedLibrary::Handle module, const char *function) { CR_FORCE_STACK_ALIGN static SharedLibrary::Func CR_STDCALL lookupHandler (SharedLibrary::Handle module, const char *function) {
return instance ().lookup (module, function); return instance ().lookup (module, function);
} }
static int CR_STDCALL closeHandler (SharedLibrary::Handle module) { CR_FORCE_STACK_ALIGN static int CR_STDCALL closeHandler (SharedLibrary::Handle module) {
return instance ().close (module); return instance ().close (module);
} }
}; };

View file

@ -117,7 +117,7 @@ public:
void notifyBombDefuse (); void notifyBombDefuse ();
void execGameEntity (edict_t *ent); void execGameEntity (edict_t *ent);
void forEach (ForEachBot handler); void forEach (ForEachBot handler);
void erase (Bot *bot); void disconnectBot (Bot *bot);
void handleDeath (edict_t *killer, edict_t *victim); void handleDeath (edict_t *killer, edict_t *victim);
void setLastWinner (int winner); void setLastWinner (int winner);
void checkBotModel (edict_t *ent, char *infobuffer); void checkBotModel (edict_t *ent, char *infobuffer);

View file

@ -115,6 +115,9 @@ public:
void load (); void load ();
void save (); void save ();
private:
void syncLoad ();
public: public:
template <typename U = int32_t> U getHighestDamageForTeam (int32_t team) const { template <typename U = int32_t> U getHighestDamageForTeam (int32_t team) const {
return static_cast <U> (cr::max (1, m_teamHighestDamage[team])); return static_cast <U> (cr::max (1, m_teamHighestDamage[team]));

View file

@ -15,58 +15,59 @@
#include VERSION_HEADER #include VERSION_HEADER
// compile time build string
#define CTS_BUILD_STR static inline constexpr StringRef
// simple class for bot internal information // simple class for bot internal information
class Product final : public Singleton <Product> { static constexpr class Product final {
public: public:
explicit constexpr Product () = default; explicit constexpr Product () = default;
~Product () = default; ~Product () = default;
public: public:
struct Build { static constexpr struct BuildInfo {
static constexpr StringRef hash { MODULE_COMMIT_COUNT }; CTS_BUILD_STR hash { MODULE_COMMIT_HASH };
static constexpr StringRef count { MODULE_COMMIT_HASH }; CTS_BUILD_STR count { MODULE_COMMIT_COUNT };
static constexpr StringRef author { MODULE_AUTHOR }; CTS_BUILD_STR author { MODULE_AUTHOR };
static constexpr StringRef machine { MODULE_MACHINE }; CTS_BUILD_STR machine { MODULE_MACHINE };
static constexpr StringRef compiler { MODULE_COMPILER }; CTS_BUILD_STR compiler { MODULE_COMPILER };
static constexpr StringRef id { MODULE_BUILD_ID }; CTS_BUILD_STR id { MODULE_BUILD_ID };
} build {}; } bi {};
public: public:
static constexpr StringRef name { "YaPB" }; CTS_BUILD_STR name { "YaPB" };
static constexpr StringRef nameLower { "yapb" }; CTS_BUILD_STR nameLower { "yapb" };
static constexpr StringRef year { &__DATE__[7] }; CTS_BUILD_STR year { &__DATE__[7] };
static constexpr StringRef author { "YaPB Project" }; CTS_BUILD_STR author { "YaPB Project" };
static constexpr StringRef email { "yapb@jeefo.net" }; CTS_BUILD_STR email { "yapb@jeefo.net" };
static constexpr StringRef url { "https://yapb.jeefo.net/" }; CTS_BUILD_STR url { "https://yapb.jeefo.net/" };
static constexpr StringRef download { "yapb.jeefo.net" }; CTS_BUILD_STR download { "yapb.jeefo.net" };
static constexpr StringRef upload { "yapb.jeefo.net/upload" }; CTS_BUILD_STR upload { "yapb.jeefo.net/upload" };
static constexpr StringRef httpScheme { "http" }; CTS_BUILD_STR httpScheme { "http" };
static constexpr StringRef logtag { "YB" }; CTS_BUILD_STR logtag { "YB" };
static constexpr StringRef dtime { __DATE__ " " __TIME__ }; CTS_BUILD_STR dtime { __DATE__ " " __TIME__ };
static constexpr StringRef date { __DATE__ }; CTS_BUILD_STR date { __DATE__ };
static constexpr StringRef version { MODULE_VERSION "." MODULE_COMMIT_COUNT }; CTS_BUILD_STR version { MODULE_VERSION "." MODULE_COMMIT_COUNT };
static constexpr StringRef cmdPri { "yb" }; CTS_BUILD_STR cmdPri { "yb" };
static constexpr StringRef cmdSec { "yapb" }; CTS_BUILD_STR cmdSec { "yapb" };
}; } product {};
class Folders final : public Singleton <Folders> { static constexpr class Folders final {
public: public:
explicit constexpr Folders () = default; explicit constexpr Folders () = default;
~Folders () = default; ~Folders () = default;
public: public:
static constexpr StringRef bot { "yapb" }; CTS_BUILD_STR bot { "yapb" };
static constexpr StringRef addons { "addons" }; CTS_BUILD_STR addons { "addons" };
static constexpr StringRef config { "conf" }; CTS_BUILD_STR config { "conf" };
static constexpr StringRef data { "data" }; CTS_BUILD_STR data { "data" };
static constexpr StringRef lang { "lang" }; CTS_BUILD_STR lang { "lang" };
static constexpr StringRef logs { "logs" }; CTS_BUILD_STR logs { "logs" };
static constexpr StringRef train { "train" }; CTS_BUILD_STR train { "train" };
static constexpr StringRef graph { "graph" }; CTS_BUILD_STR graph { "graph" };
static constexpr StringRef podbot { "pwf" }; CTS_BUILD_STR podbot { "pwf" };
static constexpr StringRef ebot { "ewp" }; CTS_BUILD_STR ebot { "ewp" };
}; } folders {};
// expose product info #undef CTS_BUILD_STR
CR_EXPOSE_GLOBAL_SINGLETON (Product, product);
CR_EXPOSE_GLOBAL_SINGLETON (Folders, folders);

View file

@ -80,7 +80,7 @@ public:
StringRef getFakeSteamId (edict_t *ent); StringRef getFakeSteamId (edict_t *ent);
// get's the wave length // get's the wave length
float getWaveLength (StringRef filename); float getWaveFileDuration (StringRef filename);
// set custom cvar descriptions // set custom cvar descriptions
void setCustomCvarDescriptions (); void setCustomCvarDescriptions ();

View file

@ -10,7 +10,7 @@
// fallback if no git or custom build // fallback if no git or custom build
#define MODULE_COMMIT_COUNT "0" #define MODULE_COMMIT_COUNT "0"
#define MODULE_COMMIT_HASH "0" #define MODULE_COMMIT_HASH "0"
#define MODULE_AUTHOR "default@mail.net" #define MODULE_AUTHOR "yapb-local@jeefo.net"
#define MODULE_MACHINE "localhost" #define MODULE_MACHINE "localhost"
#define MODULE_COMPILER "default" #define MODULE_COMPILER "default"
#define MODULE_VERSION "4.5" #define MODULE_VERSION "4.5"

View file

@ -617,7 +617,7 @@ public:
float m_preventFlashing {}; // bot turned away from flashbang float m_preventFlashing {}; // bot turned away from flashbang
float m_blindTime {}; // time when bot is blinded float m_blindTime {}; // time when bot is blinded
float m_blindMoveSpeed {}; // mad speeds when bot is blind float m_blindMoveSpeed {}; // mad speeds when bot is blind
float m_blindSidemoveSpeed {}; // mad side move speeds when bot is blind float m_blindSideMoveSpeed {}; // mad side move speeds when bot is blind
float m_fallDownTime {}; // time bot started to fall float m_fallDownTime {}; // time bot started to fall
float m_duckForJump {}; // is bot needed to duck for double jump float m_duckForJump {}; // is bot needed to duck for double jump
float m_baseAgressionLevel {}; // base aggression level (on initializing) float m_baseAgressionLevel {}; // base aggression level (on initializing)
@ -720,11 +720,7 @@ public:
public: public:
Bot (edict_t *bot, int difficulty, int personality, int team, int skin); Bot (edict_t *bot, int difficulty, int personality, int team, int skin);
~Bot () = default;
// need to wait until all threads will finish it's work before terminating bot object
~Bot () {
MutexScopedLock lock1 (m_pathFindLock);
}
public: public:
void logic (); /// the things that can be executed while skipping frames void logic (); /// the things that can be executed while skipping frames

View file

@ -42,6 +42,7 @@ opt_64bit = get_option('64bit')
opt_native = get_option('native') opt_native = get_option('native')
opt_winxp = get_option('winxp') opt_winxp = get_option('winxp')
opt_nosimd = get_option('nosimd') opt_nosimd = get_option('nosimd')
opt_static_linkent = get_option('static_linkent')
# cpp and ldflags from scratch # cpp and ldflags from scratch
cxxflags = [] cxxflags = []
@ -143,6 +144,7 @@ if cxx == 'clang' or cxx == 'gcc'
] ]
if os != 'darwin' and os != 'windows' and cpu != 'aarch64' and cpu != 'arm' and not cpu.startswith('ppc') if os != 'darwin' and os != 'windows' and cpu != 'aarch64' and cpu != 'arm' and not cpu.startswith('ppc')
if not opt_static_linkent
cxxflags += [ cxxflags += [
'-fdata-sections', '-fdata-sections',
'-ffunction-sections', '-ffunction-sections',
@ -154,6 +156,9 @@ if cxx == 'clang' or cxx == 'gcc'
'-Wl,--version-script=' + meson.project_source_root() + '/ext/ldscripts/version.lds', '-Wl,--version-script=' + meson.project_source_root() + '/ext/ldscripts/version.lds',
'-Wl,--gc-sections' '-Wl,--gc-sections'
] ]
else
cxxflags += ['-DLINKENT_STATIC']
endif
if cxx == 'gcc' if cxx == 'gcc'
cxxflags += [ cxxflags += [

View file

@ -17,3 +17,6 @@ option('winxp', type : 'boolean', value : false,
option('nosimd', type : 'boolean', value : false, option('nosimd', type : 'boolean', value : false,
description: 'Disables all SIMD or NEON optimizations.') description: 'Disables all SIMD or NEON optimizations.')
option('static_linkent', type : 'boolean', value : false,
description: 'Use predefined entity link list, instead of hooking dlsym.')

View file

@ -3712,20 +3712,20 @@ void Bot::takeBlind (int alpha) {
if (m_difficulty <= Difficulty::Normal) { if (m_difficulty <= Difficulty::Normal) {
m_blindMoveSpeed = 0.0f; m_blindMoveSpeed = 0.0f;
m_blindSidemoveSpeed = 0.0f; m_blindSideMoveSpeed = 0.0f;
m_blindButton = IN_DUCK; m_blindButton = IN_DUCK;
return; return;
} }
m_blindNodeIndex = findCoverNode (512.0f); m_blindNodeIndex = findCoverNode (512.0f);
m_blindMoveSpeed = -pev->maxspeed; m_blindMoveSpeed = -pev->maxspeed;
m_blindSidemoveSpeed = 0.0f; m_blindSideMoveSpeed = 0.0f;
if (rg.chance (50)) { if (rg.chance (50)) {
m_blindSidemoveSpeed = pev->maxspeed; m_blindSideMoveSpeed = pev->maxspeed;
} }
else { else {
m_blindSidemoveSpeed = -pev->maxspeed; m_blindSideMoveSpeed = -pev->maxspeed;
} }
if (m_healthValue < 85.0f) { if (m_healthValue < 85.0f) {
@ -3994,7 +3994,7 @@ void Bot::runMovement () {
// translate bot buttons // translate bot buttons
translateInput (); translateInput ();
engfuncs.pfnRunPlayerMove (pev->pContainingEntity, engfuncs.pfnRunPlayerMove (ent (),
getRpmAngles (), m_moveSpeed, m_strafeSpeed, getRpmAngles (), m_moveSpeed, m_strafeSpeed,
0.0f, static_cast <uint16_t> (pev->button), static_cast <uint8_t> (pev->impulse), msecVal); 0.0f, static_cast <uint16_t> (pev->button), static_cast <uint8_t> (pev->impulse), msecVal);

View file

@ -142,7 +142,7 @@ void Bot::prepareChatMessage (StringRef message) {
m_chatBuffer = message; m_chatBuffer = message;
// must be called before return or on the end // must be called before return or on the end
auto finishPreparation = [&] () { auto addChatErrors = [&] () {
if (!m_chatBuffer.empty ()) { if (!m_chatBuffer.empty ()) {
chatlib.addChatErrors (m_chatBuffer); chatlib.addChatErrors (m_chatBuffer);
} }
@ -153,7 +153,7 @@ void Bot::prepareChatMessage (StringRef message) {
// nothing found, bail out // nothing found, bail out
if (pos == String::InvalidIndex || pos >= message.length ()) { if (pos == String::InvalidIndex || pos >= message.length ()) {
finishPreparation (); addChatErrors ();
return; return;
} }
@ -241,6 +241,11 @@ void Bot::prepareChatMessage (StringRef message) {
return humanizedName (playerIndex); return humanizedName (playerIndex);
} }
else if (!needsEnemy && m_team == client.team) { else if (!needsEnemy && m_team == client.team) {
if (util.isPlayer (pev->dmg_inflictor)
&& game.getRealTeam (pev->dmg_inflictor) == m_team) {
return humanizedName (game.indexOfPlayer (pev->dmg_inflictor));
}
return humanizedName (playerIndex); return humanizedName (playerIndex);
} }
} }
@ -304,7 +309,7 @@ void Bot::prepareChatMessage (StringRef message) {
}; };
++replaceCounter; ++replaceCounter;
} }
finishPreparation (); addChatErrors ();
} }
bool Bot::checkChatKeywords (String &reply) { bool Bot::checkChatKeywords (String &reply) {

View file

@ -191,7 +191,7 @@ bool Bot::checkBodyPartsWithOffsets (edict_t *target) {
const auto &eyes = getEyesPos (); const auto &eyes = getEyesPos ();
auto spot = target->v.origin; auto spot = target->v.origin;
auto self = pev->pContainingEntity; auto self = ent ();
// creatures can't hurt behind anything // creatures can't hurt behind anything
const auto ignoreFlags = m_isCreature ? TraceIgnore::None : (cv_aim_trace_consider_glass ? TraceIgnore::Monsters : TraceIgnore::Everything); const auto ignoreFlags = m_isCreature ? TraceIgnore::None : (cv_aim_trace_consider_glass ? TraceIgnore::Monsters : TraceIgnore::Everything);
@ -266,7 +266,7 @@ bool Bot::checkBodyPartsWithOffsets (edict_t *target) {
} }
bool Bot::checkBodyPartsWithHitboxes (edict_t *target) { bool Bot::checkBodyPartsWithHitboxes (edict_t *target) {
const auto self = pev->pContainingEntity; const auto self = ent ();
const auto refresh = m_frameInterval * 1.5f; const auto refresh = m_frameInterval * 1.5f;
TraceResult result {}; TraceResult result {};

View file

@ -248,10 +248,10 @@ void BotConfig::loadChatterConfig () {
m_chatter.clear (); m_chatter.clear ();
struct EventMap { static constexpr struct EventMap {
String str; StringRef name {};
int code; int code {};
float repeat; float repeat {};
} chatterEventMap[] = { } chatterEventMap[] = {
{ "Radio_CoverMe", Radio::CoverMe, kMaxChatterRepeatInterval }, { "Radio_CoverMe", Radio::CoverMe, kMaxChatterRepeatInterval },
{ "Radio_YouTakePoint", Radio::YouTakeThePoint, kMaxChatterRepeatInterval }, { "Radio_YouTakePoint", Radio::YouTakeThePoint, kMaxChatterRepeatInterval },
@ -361,18 +361,21 @@ void BotConfig::loadChatterConfig () {
items[1].trim ("(;)"); items[1].trim ("(;)");
for (const auto &event : chatterEventMap) { for (const auto &event : chatterEventMap) {
if (event.str == items.first ()) { if (event.name == items.first ().chars ()) {
// this does common work of parsing comma-separated chatter line // this does common work of parsing comma-separated chatter line
auto sentences = items[1].split (","); auto sentences = items[1].split (",");
sentences.shuffle (); sentences.shuffle ();
for (auto &sound : sentences) { for (auto &sound : sentences) {
sound.trim ().trim ("\""); sound.trim ().trim ("\"");
const auto duration = util.getWaveLength (sound.chars ()); const auto duration = util.getWaveFileDuration (sound.chars ());
if (duration > 0.0f) { if (duration > 0.0f) {
m_chatter[event.code].emplace (cr::move (sound), event.repeat, duration); m_chatter[event.code].emplace (cr::move (sound), event.repeat, duration);
} }
else {
game.print ("Warning: Couldn't get duration of sound '%s.wav.", sound);
}
} }
sentences.clear (); sentences.clear ();
} }
@ -383,9 +386,14 @@ void BotConfig::loadChatterConfig () {
} }
else { else {
cv_radio_mode.set (1); cv_radio_mode.set (1);
// only notify if has bot voice, but failed to open file
if (game.is (GameFlags::HasBotVoice)) {
game.print ("Bots chatter communication disabled."); game.print ("Bots chatter communication disabled.");
} }
} }
}
void BotConfig::loadChatConfig () { void BotConfig::loadChatConfig () {
setupMemoryFiles (); setupMemoryFiles ();

View file

@ -154,12 +154,12 @@ int BotControl::cmdWeaponMode () {
} }
int BotControl::cmdVersion () { int BotControl::cmdVersion () {
const auto &build = product.build; constexpr auto &bi = product.bi;
msg ("%s v%s (ID %s)", product.name, product.version, build.id); msg ("%s v%s (ID %s)", product.name, product.version, bi.id);
msg (" by %s (%s)", product.author, product.email); msg (" by %s (%s)", product.author, product.email);
msg (" %s", product.url); msg (" %s", product.url);
msg ("compiled: %s on %s with %s", product.dtime, build.machine, build.compiler); msg ("compiled: %s on %s with %s", product.dtime, bi.machine, bi.compiler);
return BotCommandResult::Handled; return BotCommandResult::Handled;
} }

View file

@ -187,17 +187,17 @@ void Game::levelInitialize (edict_t *entities, int max) {
} }
void Game::levelShutdown () { void Game::levelShutdown () {
// stop thread pool
worker.shutdown ();
// save collected practice on shutdown // save collected practice on shutdown
practice.save (); practice.save ();
// stop thread pool
worker.shutdown ();
// destroy global killer entity // destroy global killer entity
bots.destroyKillerEntity (); bots.destroyKillerEntity ();
// ensure players are off on xash3d // ensure players are off on xash3d
if (game.is (GameFlags::Xash3D)) { if (game.is (GameFlags::Xash3DLegacy)) {
bots.kickEveryone (true, false); bots.kickEveryone (true, false);
} }
@ -310,7 +310,7 @@ const char *Game::getRunningModName () {
return name.chars (); return name.chars ();
} }
char engineModName[256] {}; char engineModName[StringBuffer::StaticBufferSize] {};
engfuncs.pfnGetGameDir (engineModName); engfuncs.pfnGetGameDir (engineModName);
name = engineModName; name = engineModName;
@ -545,7 +545,7 @@ void Game::prepareBotArgs (edict_t *ent, String str) {
m_botArgs.emplace (args.substr (quote, args.length () - 1).trim ("\"")); // add string with trimmed quotes m_botArgs.emplace (args.substr (quote, args.length () - 1).trim ("\"")); // add string with trimmed quotes
} }
else { else {
for (auto &&arg : args.split (" ")) { for (auto arg : args.split (" ")) {
m_botArgs.emplace (arg); m_botArgs.emplace (arg);
} }
} }
@ -560,8 +560,8 @@ void Game::prepareBotArgs (edict_t *ent, String str) {
}; };
if (str.find (';', 0) != String::InvalidIndex) { if (str.find (';', 0) != String::InvalidIndex) {
for (auto &&part : str.split (";")) { for (auto part : str.split (";")) {
parsePartArgs (part); parsePartArgs (part.trim ());
} }
} }
else { else {
@ -594,7 +594,7 @@ bool Game::isSoftwareRenderer () {
return false; return false;
} }
// and on only windows version you can use software-render game. Linux, OSX always defaults to OpenGL // and on only windows version you can use software-render game. Linux, macOS always defaults to OpenGL
if (plat.win) { if (plat.win) {
return plat.hasModule ("sw"); return plat.hasModule ("sw");
} }
@ -605,7 +605,7 @@ bool Game::is25thAnniversaryUpdate () {
static ConVarRef sv_use_steam_networking ("sv_use_steam_networking"); static ConVarRef sv_use_steam_networking ("sv_use_steam_networking");
static ConVarRef host_hl25_extended_structs ("host_hl25_extended_structs"); static ConVarRef host_hl25_extended_structs ("host_hl25_extended_structs");
return sv_use_steam_networking.exists () || host_hl25_extended_structs.exists (); return sv_use_steam_networking.exists () || host_hl25_extended_structs.value () > 0.0f;
} }
void Game::pushConVar (StringRef name, StringRef value, StringRef info, bool bounded, float min, float max, int32_t varType, bool missingAction, StringRef regval, ConVar *self) { void Game::pushConVar (StringRef name, StringRef value, StringRef info, bool bounded, float min, float max, int32_t varType, bool missingAction, StringRef regval, ConVar *self) {
@ -1161,7 +1161,7 @@ void Game::printBotVersion () {
if (is (GameFlags::Xash3D)) { if (is (GameFlags::Xash3D)) {
if (is (GameFlags::Xash3DLegacy)) { if (is (GameFlags::Xash3DLegacy)) {
gameVersionStr.append (" @ Xash3D (Old)"); gameVersionStr.append (" @ Xash3D-NG");
} }
else { else {
gameVersionStr.append (" @ Xash3D FWGS"); gameVersionStr.append (" @ Xash3D FWGS");
@ -1315,7 +1315,7 @@ void LightMeasure::animateLight () {
for (auto j = 0; j < MAX_LIGHTSTYLES; ++j) { for (auto j = 0; j < MAX_LIGHTSTYLES; ++j) {
if (!m_lightstyle[j].length) { if (!m_lightstyle[j].length) {
m_lightstyleValue[j] = 256; m_lightstyleValue[j] = MAX_LIGHTSTYLEVALUE;
continue; continue;
} }
m_lightstyleValue[j] = static_cast <uint32_t> (m_lightstyle[j].map[index % m_lightstyle[j].length] - 'a') * 22u; m_lightstyleValue[j] = static_cast <uint32_t> (m_lightstyle[j].map[index % m_lightstyle[j].length] - 'a') * 22u;

View file

@ -11,7 +11,7 @@
// nice interface to handle with linkents. if ever rehlds or hlds engine will ever run on ARM or // nice interface to handle with linkents. if ever rehlds or hlds engine will ever run on ARM or
// other platforms, and you want to run bot on it without metamod, consider enabling LINKENT_STATIC_THUNKS // other platforms, and you want to run bot on it without metamod, consider enabling LINKENT_STATIC_THUNKS
// when compiling the bot, to get it supported. // when compiling the bot, to get it supported.
#if defined(LINKENT_STATIC_THUNKS) #if defined(LINKENT_STATIC)
void forwardEntity_helper (EntityProto &addr, const char *name, entvars_t *pev) { void forwardEntity_helper (EntityProto &addr, const char *name, entvars_t *pev) {
if (!addr) { if (!addr) {
addr = game.lib ().resolve <EntityProto> (name); addr = game.lib ().resolve <EntityProto> (name);

View file

@ -22,22 +22,21 @@ void BotFakePingManager::reset (edict_t *to) {
if (!hasFeature ()) { if (!hasFeature ()) {
return; return;
} }
static PingBitMsg pbm {};
for (const auto &client : util.getClients ()) { for (const auto &client : util.getClients ()) {
if (!(client.flags & ClientFlags::Used) || util.isFakeClient (client.ent)) { if (!(client.flags & ClientFlags::Used) || util.isFakeClient (client.ent)) {
continue; continue;
} }
pbm.start (client.ent); m_pbm.start (client.ent);
pbm.write (1, PingBitMsg::Single); m_pbm.write (1, PingBitMsg::Single);
pbm.write (game.indexOfPlayer (to), PingBitMsg::PlayerID); m_pbm.write (game.indexOfPlayer (to), PingBitMsg::PlayerID);
pbm.write (0, PingBitMsg::Ping); m_pbm.write (0, PingBitMsg::Ping);
pbm.write (0, PingBitMsg::Loss); m_pbm.write (0, PingBitMsg::Loss);
pbm.send (); m_pbm.send ();
} }
pbm.flush (); m_pbm.flush ();
} }
void BotFakePingManager::syncCalculate () { void BotFakePingManager::syncCalculate () {
@ -107,19 +106,18 @@ void BotFakePingManager::emit (edict_t *ent) {
if (!util.isPlayer (ent)) { if (!util.isPlayer (ent)) {
return; return;
} }
static PingBitMsg pbm {};
for (const auto &bot : bots) { for (const auto &bot : bots) {
pbm.start (ent); m_pbm.start (ent);
pbm.write (1, PingBitMsg::Single); m_pbm.write (1, PingBitMsg::Single);
pbm.write (bot->entindex () - 1, PingBitMsg::PlayerID); m_pbm.write (bot->entindex () - 1, PingBitMsg::PlayerID);
pbm.write (bot->m_ping, PingBitMsg::Ping); m_pbm.write (bot->m_ping, PingBitMsg::Ping);
pbm.write (0, PingBitMsg::Loss); m_pbm.write (0, PingBitMsg::Loss);
pbm.send (); m_pbm.send ();
} }
pbm.flush (); m_pbm.flush ();
} }
void BotFakePingManager::restartTimer () { void BotFakePingManager::restartTimer () {

View file

@ -31,8 +31,8 @@ void BotGraph::reset () {
m_narrowChecked = false; m_narrowChecked = false;
m_lightChecked = false; m_lightChecked = false;
m_graphAuthor.clear (); m_info.author.clear ();
m_graphModified.clear (); m_info.modified.clear ();
m_paths.clear (); m_paths.clear ();
} }
@ -600,7 +600,7 @@ IntArray BotGraph::getNearestInRadius (float radius, const Vector &origin, int m
} }
void BotGraph::add (int type, const Vector &pos) { void BotGraph::add (int type, const Vector &pos) {
if (game.isNullEntity (m_editor) && !analyzer.isAnalyzing ()) { if (!hasEditor () && !analyzer.isAnalyzing ()) {
return; return;
} }
int index = kInvalidNodeIndex; int index = kInvalidNodeIndex;
@ -610,7 +610,7 @@ void BotGraph::add (int type, const Vector &pos) {
Vector newOrigin = pos; Vector newOrigin = pos;
if (newOrigin.empty ()) { if (newOrigin.empty ()) {
if (game.isNullEntity (m_editor)) { if (!hasEditor ()) {
return; return;
} }
newOrigin = m_editor->v.origin; newOrigin = m_editor->v.origin;
@ -1277,21 +1277,24 @@ void BotGraph::showStats () {
} }
void BotGraph::showFileInfo () { void BotGraph::showFileInfo () {
const auto &info = m_info.header;
const auto &exten = m_info.exten;
msg ("header:"); msg ("header:");
msg (" magic: %d", m_graphHeader.magic); msg (" magic: %d", info.magic);
msg (" version: %d", m_graphHeader.version); msg (" version: %d", info.version);
msg (" node_count: %d", m_graphHeader.length); msg (" node_count: %d", info.length);
msg (" compressed_size: %dkB", m_graphHeader.compressed / 1024); msg (" compressed_size: %dkB", info.compressed / 1024);
msg (" uncompressed_size: %dkB", m_graphHeader.uncompressed / 1024); msg (" uncompressed_size: %dkB", info.uncompressed / 1024);
msg (" options: %d", m_graphHeader.options); // display as string ? msg (" options: %d", info.options); // display as string ?
msg (" analyzed: %s", isAnalyzed () ? conf.translate ("yes") : conf.translate ("no")); // display as string ? msg (" analyzed: %s", isAnalyzed () ? conf.translate ("yes") : conf.translate ("no")); // display as string ?
msg (""); msg ("");
msg ("extensions:"); msg ("extensions:");
msg (" author: %s", m_extenHeader.author); msg (" author: %s", exten.author);
msg (" modified_by: %s", m_extenHeader.modified); msg (" modified_by: %s", exten.modified);
msg (" bsp_size: %d", m_extenHeader.mapSize); msg (" bsp_size: %d", exten.mapSize);
} }
void BotGraph::emitNotify (int32_t sound) { void BotGraph::emitNotify (int32_t sound) {
@ -1576,7 +1579,7 @@ void BotGraph::initNarrowPlaces () {
constexpr int32_t kNarrowPlacesMinGraphVersion = 2; constexpr int32_t kNarrowPlacesMinGraphVersion = 2;
// if version 2 or higher, narrow places already initialized and saved into file // if version 2 or higher, narrow places already initialized and saved into file
if (m_graphHeader.version >= kNarrowPlacesMinGraphVersion && !hasEditFlag (GraphEdit::On)) { if (m_info.header.version >= kNarrowPlacesMinGraphVersion && !hasEditFlag (GraphEdit::On)) {
m_narrowChecked = true; m_narrowChecked = true;
return; return;
} }
@ -1747,7 +1750,7 @@ bool BotGraph::convertOldFormat () {
if (!m_paths.empty ()) { if (!m_paths.empty ()) {
msg ("Converting old PWF to new format Graph."); msg ("Converting old PWF to new format Graph.");
m_graphAuthor = header.author; m_info.author = header.author;
// clean editor so graph will be saved with header's author // clean editor so graph will be saved with header's author
auto editor = m_editor; auto editor = m_editor;
@ -1774,8 +1777,8 @@ bool BotGraph::loadGraphData () {
ExtenHeader exten {}; ExtenHeader exten {};
int32_t outOptions = 0; int32_t outOptions = 0;
m_graphHeader = {}; m_info.header = {};
m_extenHeader = {}; m_info.exten = {};
// re-initialize paths // re-initialize paths
reset (); reset ();
@ -1797,15 +1800,15 @@ bool BotGraph::loadGraphData () {
StringRef author = exten.author; StringRef author = exten.author;
if ((outOptions & StorageOption::Official) || author.startsWith ("official") || author.length () < 2) { if ((outOptions & StorageOption::Official) || author.startsWith ("official") || author.length () < 2) {
m_graphAuthor.assign (product.name); m_info.author.assign (product.name);
} }
else { else {
m_graphAuthor.assign (author); m_info.author.assign (author);
} }
StringRef modified = exten.modified; StringRef modified = exten.modified;
if (!modified.empty () && !modified.contains ("(none)")) { if (!modified.empty () && !modified.contains ("(none)")) {
m_graphModified.assign (exten.modified); m_info.modified.assign (exten.modified);
} }
planner.init (); // initialize our little path planner planner.init (); // initialize our little path planner
practice.load (); // load bots practice practice.load (); // load bots practice
@ -1848,8 +1851,8 @@ bool BotGraph::saveGraphData () {
auto options = StorageOption::Graph | StorageOption::Exten; auto options = StorageOption::Graph | StorageOption::Exten;
String editorName {}; String editorName {};
if (game.isNullEntity (m_editor) && !m_graphAuthor.empty ()) { if (!hasEditor () && !m_info.author.empty ()) {
editorName = m_graphAuthor; editorName = m_info.author;
if (!game.isDedicated ()) { if (!game.isDedicated ()) {
options |= StorageOption::Recovered; options |= StorageOption::Recovered;
@ -1874,15 +1877,15 @@ bool BotGraph::saveGraphData () {
ExtenHeader exten {}; ExtenHeader exten {};
// only modify the author if no author currently assigned to graph file // only modify the author if no author currently assigned to graph file
if (m_graphAuthor.empty () || strings.isEmpty (m_extenHeader.author)) { if (m_info.author.empty () || strings.isEmpty (m_info.exten.author)) {
strings.copy (exten.author, editorName.chars (), cr::bufsize (exten.author)); strings.copy (exten.author, editorName.chars (), cr::bufsize (exten.author));
} }
else { else {
strings.copy (exten.author, m_extenHeader.author, cr::bufsize (exten.author)); strings.copy (exten.author, m_info.exten.author, cr::bufsize (exten.author));
} }
// only update modified by, if name differs // only update modified by, if name differs
if (m_graphAuthor != editorName && !strings.isEmpty (m_extenHeader.author)) { if (m_info.author != editorName && !strings.isEmpty (m_info.exten.author)) {
strings.copy (exten.modified, editorName.chars (), cr::bufsize (exten.author)); strings.copy (exten.modified, editorName.chars (), cr::bufsize (exten.author));
} }
exten.mapSize = getBspSize (); exten.mapSize = getBspSize ();
@ -1901,8 +1904,8 @@ void BotGraph::saveOldFormat () {
String editorName {}; String editorName {};
if (game.isNullEntity (m_editor) && !m_graphAuthor.empty ()) { if (!hasEditor () && !m_info.author.empty ()) {
editorName = m_graphAuthor; editorName = m_info.author;
} }
else if (!game.isNullEntity (m_editor)) { else if (!game.isNullEntity (m_editor)) {
editorName = m_editor->v.netname.chars (); editorName = m_editor->v.netname.chars ();
@ -2284,7 +2287,7 @@ void BotGraph::frame () {
// draw the radius circle // draw the radius circle
Vector origin = (path.flags & NodeFlag::Crouch) ? path.origin : path.origin - Vector (0.0f, 0.0f, 18.0f); Vector origin = (path.flags & NodeFlag::Crouch) ? path.origin : path.origin - Vector (0.0f, 0.0f, 18.0f);
static Color radiusColor { 36, 36, 255 }; constexpr Color radiusColor { 36, 36, 255 };
// if radius is nonzero, draw a full circle // if radius is nonzero, draw a full circle
if (path.radius > 0.0f) { if (path.radius > 0.0f) {
@ -2846,7 +2849,7 @@ const Array <int32_t> &BotGraph::getNodesInBucket (const Vector &pos) {
} }
bool BotGraph::isAnalyzed () const { bool BotGraph::isAnalyzed () const {
return (m_graphHeader.options & StorageOption::Analyzed); return (m_info.header.options & StorageOption::Analyzed);
} }
void BotGraph::eraseFromBucket (const Vector &pos, int index) { void BotGraph::eraseFromBucket (const Vector &pos, int index) {

View file

@ -174,15 +174,20 @@ bool DynamicLinkerHook::needsBypass () const {
} }
void DynamicLinkerHook::initialize () { void DynamicLinkerHook::initialize () {
#if defined(LINKENT_STATIC)
return;
#endif
if (plat.isNonX86 () || game.is (GameFlags::Metamod)) { if (plat.isNonX86 () || game.is (GameFlags::Metamod)) {
return; return;
} }
constexpr StringRef kKernel32Module = "kernel32.dll";
m_dlsym.initialize ("kernel32.dll", "GetProcAddress", DLSYM_FUNCTION); m_dlsym.initialize (kKernel32Module, "GetProcAddress", DLSYM_FUNCTION);
m_dlsym.install (reinterpret_cast <void *> (lookupHandler), true); m_dlsym.install (reinterpret_cast <void *> (lookupHandler), true);
if (needsBypass ()) { if (needsBypass ()) {
m_dlclose.initialize ("kernel32.dll", "FreeLibrary", DLCLOSE_FUNCTION); m_dlclose.initialize (kKernel32Module, "FreeLibrary", DLCLOSE_FUNCTION);
m_dlclose.install (reinterpret_cast <void *> (closeHandler), true); m_dlclose.install (reinterpret_cast <void *> (closeHandler), true);
} }
m_self.locate (&engfuncs); m_self.locate (&engfuncs);

View file

@ -34,7 +34,7 @@ plugin_info_t Plugin_info = {
// compilers can't create lambdas with vaargs, so put this one in it's own namespace // compilers can't create lambdas with vaargs, so put this one in it's own namespace
namespace Hooks { namespace Hooks {
void handler_engClientCommand (edict_t *ent, char const *format, ...) { CR_FORCE_STACK_ALIGN void handler_engClientCommand (edict_t *ent, char const *format, ...) {
// this function forces the client whose player entity is ent to issue a client command. // this function forces the client whose player entity is ent to issue a client command.
// How it works is that clients all have a argv global string in their client DLL that // How it works is that clients all have a argv global string in their client DLL that
// stores the command string; if ever that string is filled with characters, the client DLL // stores the command string; if ever that string is filled with characters, the client DLL
@ -99,7 +99,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
memcpy (table, &dllapi, sizeof (gamefuncs_t)); memcpy (table, &dllapi, sizeof (gamefuncs_t));
} }
table->pfnGameInit = [] () { table->pfnGameInit = [] () CR_FORCE_STACK_ALIGN {
// this function is a one-time call, and appears to be the second function called in the // this function is a one-time call, and appears to be the second function called in the
// DLL after GiveFntprsToDll() has been called. Its purpose is to tell the MOD DLL to // DLL after GiveFntprsToDll() has been called. Its purpose is to tell the MOD DLL to
// initialize the game before the engine actually hooks into it with its video frames and // initialize the game before the engine actually hooks into it with its video frames and
@ -121,7 +121,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
dllapi.pfnGameInit (); dllapi.pfnGameInit ();
}; };
table->pfnSpawn = [] (edict_t *ent) { table->pfnSpawn = [] (edict_t *ent) CR_FORCE_STACK_ALIGN {
// this function asks the game DLL to spawn (i.e, give a physical existence in the virtual // this function asks the game DLL to spawn (i.e, give a physical existence in the virtual
// world, in other words to 'display') the entity pointed to by ent in the game. The // world, in other words to 'display') the entity pointed to by ent in the game. The
// Spawn() function is one of the functions any entity is supposed to have in the game DLL, // Spawn() function is one of the functions any entity is supposed to have in the game DLL,
@ -141,7 +141,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
return result; return result;
}; };
table->pfnTouch = [] (edict_t *pentTouched, edict_t *pentOther) { table->pfnTouch = [] (edict_t *pentTouched, edict_t *pentOther) CR_FORCE_STACK_ALIGN {
// this function is called when two entities' bounding boxes enter in collision. For example, // this function is called when two entities' bounding boxes enter in collision. For example,
// when a player walks upon a gun, the player entity bounding box collides to the gun entity // when a player walks upon a gun, the player entity bounding box collides to the gun entity
// bounding box, and the result is that this function is called. It is used by the game for // bounding box, and the result is that this function is called. It is used by the game for
@ -170,7 +170,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
dllapi.pfnTouch (pentTouched, pentOther); dllapi.pfnTouch (pentTouched, pentOther);
}; };
table->pfnClientConnect = [] (edict_t *ent, const char *name, const char *addr, char rejectReason[128]) { table->pfnClientConnect = [] (edict_t *ent, const char *name, const char *addr, char rejectReason[128]) CR_FORCE_STACK_ALIGN {
// this function is called in order to tell the MOD DLL that a client attempts to connect the // this function is called in order to tell the MOD DLL that a client attempts to connect the
// game. The entity pointer of this client is ent, the name under which he connects is // game. The entity pointer of this client is ent, the name under which he connects is
// pointed to by the pszName pointer, and its IP address string is pointed by the pszAddress // pointed to by the pszName pointer, and its IP address string is pointed by the pszAddress
@ -210,7 +210,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
return dllapi.pfnClientConnect (ent, name, addr, rejectReason); return dllapi.pfnClientConnect (ent, name, addr, rejectReason);
}; };
table->pfnClientDisconnect = [] (edict_t *ent) { table->pfnClientDisconnect = [] (edict_t *ent) CR_FORCE_STACK_ALIGN {
// this function is called whenever a client is VOLUNTARILY disconnected from the server, // this function is called whenever a client is VOLUNTARILY disconnected from the server,
// either because the client dropped the connection, or because the server dropped him from // either because the client dropped the connection, or because the server dropped him from
// the game (latency timeout). The effect is the freeing of a client slot on the server. Note // the game (latency timeout). The effect is the freeing of a client slot on the server. Note
@ -224,7 +224,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
for (auto &bot : bots) { for (auto &bot : bots) {
if (bot->pev == &ent->v) { if (bot->pev == &ent->v) {
bots.erase (bot.get ()); // remove the bot from bots array bots.disconnectBot (bot.get ()); // remove the bot from bots array
break; break;
} }
@ -249,8 +249,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
dllapi.pfnClientDisconnect (ent); dllapi.pfnClientDisconnect (ent);
}; };
table->pfnClientPutInServer = [] (edict_t *ent) CR_FORCE_STACK_ALIGN {
table->pfnClientPutInServer = [] (edict_t *ent) {
// this function is called once a just connected client actually enters the game, after // this function is called once a just connected client actually enters the game, after
// having downloaded and synchronized its resources with the of the server's. It's the // having downloaded and synchronized its resources with the of the server's. It's the
// perfect place to hook for client connecting, since a client can always try to connect // perfect place to hook for client connecting, since a client can always try to connect
@ -271,7 +270,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
dllapi.pfnClientPutInServer (ent); dllapi.pfnClientPutInServer (ent);
}; };
table->pfnClientUserInfoChanged = [] (edict_t *ent, char *infobuffer) { table->pfnClientUserInfoChanged = [] (edict_t *ent, char *infobuffer) CR_FORCE_STACK_ALIGN {
// this function is called when a player changes model, or changes team. Occasionally it // this function is called when a player changes model, or changes team. Occasionally it
// enforces rules on these changes (for example, some MODs don't want to allow players to // enforces rules on these changes (for example, some MODs don't want to allow players to
// change their player model). But most commonly, this function is in charge of handling // change their player model). But most commonly, this function is in charge of handling
@ -286,7 +285,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
dllapi.pfnClientUserInfoChanged (ent, infobuffer); dllapi.pfnClientUserInfoChanged (ent, infobuffer);
}; };
table->pfnClientCommand = [] (edict_t *ent) { table->pfnClientCommand = [] (edict_t *ent) CR_FORCE_STACK_ALIGN {
// this function is called whenever the client whose player entity is ent issues a client // this function is called whenever the client whose player entity is ent issues a client
// command. How it works is that clients all have a global string in their client DLL that // command. How it works is that clients all have a global string in their client DLL that
// stores the command string; if ever that string is filled with characters, the client DLL // stores the command string; if ever that string is filled with characters, the client DLL
@ -323,7 +322,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
dllapi.pfnClientCommand (ent); dllapi.pfnClientCommand (ent);
}; };
table->pfnServerActivate = [] (edict_t *edictList, int edictCount, int clientMax) { table->pfnServerActivate = [] (edict_t *edictList, int edictCount, int clientMax) CR_FORCE_STACK_ALIGN {
// this function is called when the server has fully loaded and is about to manifest itself // this function is called when the server has fully loaded and is about to manifest itself
// on the network as such. Since a mapchange is actually a server shutdown followed by a // on the network as such. Since a mapchange is actually a server shutdown followed by a
// restart, this function is also called when a new map is being loaded. Hence it's the // restart, this function is also called when a new map is being loaded. Hence it's the
@ -340,7 +339,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
game.levelInitialize (edictList, edictCount); game.levelInitialize (edictList, edictCount);
}; };
table->pfnServerDeactivate = [] () { table->pfnServerDeactivate = [] () CR_FORCE_STACK_ALIGN {
// this function is called when the server is shutting down. A particular note about map // this function is called when the server is shutting down. A particular note about map
// changes: changing the map means shutting down the server and starting a new one. Of course // changes: changing the map means shutting down the server and starting a new one. Of course
// this process is transparent to the user, but either in single player when the hero reaches // this process is transparent to the user, but either in single player when the hero reaches
@ -360,7 +359,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
dllapi.pfnServerDeactivate (); dllapi.pfnServerDeactivate ();
}; };
table->pfnStartFrame = [] () { table->pfnStartFrame = [] () CR_FORCE_STACK_ALIGN {
// this function starts a video frame. It is called once per video frame by the game. If // this function starts a video frame. It is called once per video frame by the game. If
// you run Half-Life at 90 fps, this function will then be called 90 times per second. By // you run Half-Life at 90 fps, this function will then be called 90 times per second. By
// placing a hook on it, we have a good place to do things that should be done continuously // placing a hook on it, we have a good place to do things that should be done continuously
@ -415,7 +414,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
}; };
if (game.is (GameFlags::HasFakePings) && !game.is (GameFlags::Metamod)) { if (game.is (GameFlags::HasFakePings) && !game.is (GameFlags::Metamod)) {
table->pfnUpdateClientData = [] (const struct edict_s *player, int sendweapons, struct clientdata_s *cd) { table->pfnUpdateClientData = [] (const struct edict_s *player, int sendweapons, struct clientdata_s *cd) CR_FORCE_STACK_ALIGN {
// this function is a synchronization tool that is used periodically by the engine to tell // this function is a synchronization tool that is used periodically by the engine to tell
// the game DLL to send player info over the network to one of its clients when it suspects // the game DLL to send player info over the network to one of its clients when it suspects
// that this client is desynchronizing. Early bots were using it to ask the game DLL for the // that this client is desynchronizing. Early bots were using it to ask the game DLL for the
@ -437,7 +436,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
}; };
} }
table->pfnPM_Move = [] (playermove_t *pm, int server) { table->pfnPM_Move = [] (playermove_t *pm, int server) CR_FORCE_STACK_ALIGN {
// this is the player movement code clients run to predict things when the server can't update // this is the player movement code clients run to predict things when the server can't update
// them often enough (or doesn't want to). The server runs exactly the same function for // them often enough (or doesn't want to). The server runs exactly the same function for
// moving players. There is normally no distinction between them, else client-side prediction // moving players. There is normally no distinction between them, else client-side prediction
@ -451,7 +450,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
dllapi.pfnPM_Move (pm, server); dllapi.pfnPM_Move (pm, server);
}; };
table->pfnKeyValue = [] (edict_t *ent, KeyValueData *kvd) { table->pfnKeyValue = [] (edict_t *ent, KeyValueData *kvd) CR_FORCE_STACK_ALIGN {
// this function is called when the game requests a pointer to some entity's keyvalue data. // this function is called when the game requests a pointer to some entity's keyvalue data.
// The keyvalue data is held in each entity's infobuffer (basically a char buffer where each // The keyvalue data is held in each entity's infobuffer (basically a char buffer where each
// game DLL can put the stuff it wants) under - as it says - the form of a key/value pair. A // game DLL can put the stuff it wants) under - as it says - the form of a key/value pair. A
@ -475,7 +474,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
return HLTrue; return HLTrue;
} }
CR_LINKAGE_C int GetEntityAPI_Post (gamefuncs_t *table, int) { CR_C_LINKAGE int GetEntityAPI_Post (gamefuncs_t *table, int) {
// this function is called right after GiveFnptrsToDll() by the engine in the game DLL (or // this function is called right after GiveFnptrsToDll() by the engine in the game DLL (or
// what it BELIEVES to be the game DLL), in order to copy the list of MOD functions that can // what it BELIEVES to be the game DLL), in order to copy the list of MOD functions that can
// be called by the engine, into a memory block pointed to by the functionTable pointer // be called by the engine, into a memory block pointed to by the functionTable pointer
@ -488,7 +487,7 @@ CR_LINKAGE_C int GetEntityAPI_Post (gamefuncs_t *table, int) {
plat.bzero (table, sizeof (gamefuncs_t)); plat.bzero (table, sizeof (gamefuncs_t));
table->pfnSpawn = [] (edict_t *ent) { table->pfnSpawn = [] (edict_t *ent) CR_FORCE_STACK_ALIGN {
// this function asks the game DLL to spawn (i.e, give a physical existence in the virtual // this function asks the game DLL to spawn (i.e, give a physical existence in the virtual
// world, in other words to 'display') the entity pointed to by ent in the game. The // world, in other words to 'display') the entity pointed to by ent in the game. The
// Spawn() function is one of the functions any entity is supposed to have in the game DLL, // Spawn() function is one of the functions any entity is supposed to have in the game DLL,
@ -502,7 +501,7 @@ CR_LINKAGE_C int GetEntityAPI_Post (gamefuncs_t *table, int) {
RETURN_META_VALUE (MRES_HANDLED, 0); RETURN_META_VALUE (MRES_HANDLED, 0);
}; };
table->pfnStartFrame = [] () { table->pfnStartFrame = [] () CR_FORCE_STACK_ALIGN {
// this function starts a video frame. It is called once per video frame by the game. If // this function starts a video frame. It is called once per video frame by the game. If
// you run Half-Life at 90 fps, this function will then be called 90 times per second. By // you run Half-Life at 90 fps, this function will then be called 90 times per second. By
// placing a hook on it, we have a good place to do things that should be done continuously // placing a hook on it, we have a good place to do things that should be done continuously
@ -515,7 +514,7 @@ CR_LINKAGE_C int GetEntityAPI_Post (gamefuncs_t *table, int) {
RETURN_META (MRES_IGNORED); RETURN_META (MRES_IGNORED);
}; };
table->pfnServerActivate = [] (edict_t *edictList, int edictCount, int) { table->pfnServerActivate = [] (edict_t *edictList, int edictCount, int) CR_FORCE_STACK_ALIGN {
// this function is called when the server has fully loaded and is about to manifest itself // this function is called when the server has fully loaded and is about to manifest itself
// on the network as such. Since a mapchange is actually a server shutdown followed by a // on the network as such. Since a mapchange is actually a server shutdown followed by a
// restart, this function is also called when a new map is being loaded. Hence it's the // restart, this function is also called when a new map is being loaded. Hence it's the
@ -531,7 +530,7 @@ CR_LINKAGE_C int GetEntityAPI_Post (gamefuncs_t *table, int) {
}; };
if (game.is (GameFlags::HasFakePings)) { if (game.is (GameFlags::HasFakePings)) {
table->pfnUpdateClientData = [] (const struct edict_s *player, int, struct clientdata_s *) { table->pfnUpdateClientData = [] (const struct edict_s *player, int, struct clientdata_s *) CR_FORCE_STACK_ALIGN {
// this function is a synchronization tool that is used periodically by the engine to tell // this function is a synchronization tool that is used periodically by the engine to tell
// the game DLL to send player info over the network to one of its clients when it suspects // the game DLL to send player info over the network to one of its clients when it suspects
// that this client is desynchronizing. Early bots were using it to ask the game DLL for the // that this client is desynchronizing. Early bots were using it to ask the game DLL for the
@ -555,13 +554,13 @@ CR_LINKAGE_C int GetEntityAPI_Post (gamefuncs_t *table, int) {
return HLTrue; return HLTrue;
} }
CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) { CR_C_LINKAGE int GetEngineFunctions (enginefuncs_t *table, int *) {
if (game.is (GameFlags::Metamod)) { if (game.is (GameFlags::Metamod)) {
plat.bzero (table, sizeof (enginefuncs_t)); plat.bzero (table, sizeof (enginefuncs_t));
} }
if (entlink.needsBypass () && !game.is (GameFlags::Metamod)) { if (entlink.needsBypass () && !game.is (GameFlags::Metamod)) {
table->pfnCreateNamedEntity = [] (string_t classname) -> edict_t * { table->pfnCreateNamedEntity = [] (string_t classname) CR_FORCE_STACK_ALIGN {
if (entlink.isPaused ()) { if (entlink.isPaused ()) {
entlink.enable (); entlink.enable ();
@ -572,7 +571,7 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
} }
if (game.is (GameFlags::Legacy)) { if (game.is (GameFlags::Legacy)) {
table->pfnFindEntityByString = [] (edict_t *edictStartSearchAfter, const char *field, const char *value) { table->pfnFindEntityByString = [] (edict_t *edictStartSearchAfter, const char *field, const char *value) CR_FORCE_STACK_ALIGN {
// round starts in counter-strike 1.5 // round starts in counter-strike 1.5
if (strcmp (value, "info_map_parameters") == 0) { if (strcmp (value, "info_map_parameters") == 0) {
bots.initRound (); bots.initRound ();
@ -583,10 +582,22 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
} }
return engfuncs.pfnFindEntityByString (edictStartSearchAfter, field, value); return engfuncs.pfnFindEntityByString (edictStartSearchAfter, field, value);
}; };
table->pfnChangeLevel = [] (char *s1, char *s2) CR_FORCE_STACK_ALIGN {
// this function gets called when server is changing a level
// kick off all the bots, needed for legacy engine versions
bots.kickEveryone (true, false);
if (game.is (GameFlags::Metamod)) {
RETURN_META (MRES_IGNORED);
}
engfuncs.pfnChangeLevel (s1, s2);
};
} }
if (!game.is (GameFlags::Legacy)) { if (!game.is (GameFlags::Legacy)) {
table->pfnLightStyle = [] (int style, char *val) { table->pfnLightStyle = [] (int style, char *val) CR_FORCE_STACK_ALIGN {
// this function update lightstyle for the bots // this function update lightstyle for the bots
illum.updateLight (style, val); illum.updateLight (style, val);
@ -597,7 +608,7 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
engfuncs.pfnLightStyle (style, val); engfuncs.pfnLightStyle (style, val);
}; };
table->pfnGetPlayerAuthId = [] (edict_t *e) -> const char * { table->pfnGetPlayerAuthId = [] (edict_t *e) CR_FORCE_STACK_ALIGN {
if (bots[e]) { if (bots[e]) {
auto authid = util.getFakeSteamId (e); auto authid = util.getFakeSteamId (e);
@ -608,13 +619,13 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
} }
if (game.is (GameFlags::Metamod)) { if (game.is (GameFlags::Metamod)) {
RETURN_META_VALUE (MRES_IGNORED, nullptr); RETURN_META_VALUE (MRES_IGNORED, "");
} }
return engfuncs.pfnGetPlayerAuthId (e); return engfuncs.pfnGetPlayerAuthId (e);
}; };
} }
table->pfnEmitSound = [] (edict_t *entity, int channel, const char *sample, float volume, float attenuation, int flags, int pitch) { table->pfnEmitSound = [] (edict_t *entity, int channel, const char *sample, float volume, float attenuation, int flags, int pitch) CR_FORCE_STACK_ALIGN {
// this function tells the engine that the entity pointed to by "entity", is emitting a sound // this function tells the engine that the entity pointed to by "entity", is emitting a sound
// which fileName is "sample", at level "channel" (CHAN_VOICE, etc...), with "volume" as // which fileName is "sample", at level "channel" (CHAN_VOICE, etc...), with "volume" as
// loudness multiplicator (normal volume VOL_NORM is 1.0), with a pitch of "pitch" (normal // loudness multiplicator (normal volume VOL_NORM is 1.0), with a pitch of "pitch" (normal
@ -633,7 +644,7 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
engfuncs.pfnEmitSound (entity, channel, sample, volume, attenuation, flags, pitch); engfuncs.pfnEmitSound (entity, channel, sample, volume, attenuation, flags, pitch);
}; };
table->pfnMessageBegin = [] (int msgDest, int msgType, const float *origin, edict_t *ed) { table->pfnMessageBegin = [] (int msgDest, int msgType, const float *origin, edict_t *ed) CR_FORCE_STACK_ALIGN {
// this function called each time a message is about to sent. // this function called each time a message is about to sent.
msgs.start (ed, msgType); msgs.start (ed, msgType);
@ -644,7 +655,7 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
}; };
if (!game.is (GameFlags::Metamod)) { if (!game.is (GameFlags::Metamod)) {
table->pfnMessageEnd = [] () { table->pfnMessageEnd = [] () CR_FORCE_STACK_ALIGN {
engfuncs.pfnMessageEnd (); engfuncs.pfnMessageEnd ();
// this allows us to send messages right in handler code // this allows us to send messages right in handler code
@ -652,7 +663,7 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
}; };
} }
table->pfnWriteByte = [] (int value) { table->pfnWriteByte = [] (int value) CR_FORCE_STACK_ALIGN {
// if this message is for a bot, call the client message function... // if this message is for a bot, call the client message function...
msgs.collect (value); msgs.collect (value);
@ -662,7 +673,7 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
engfuncs.pfnWriteByte (value); engfuncs.pfnWriteByte (value);
}; };
table->pfnWriteChar = [] (int value) { table->pfnWriteChar = [] (int value) CR_FORCE_STACK_ALIGN {
// if this message is for a bot, call the client message function... // if this message is for a bot, call the client message function...
msgs.collect (value); msgs.collect (value);
@ -672,7 +683,7 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
engfuncs.pfnWriteChar (value); engfuncs.pfnWriteChar (value);
}; };
table->pfnWriteShort = [] (int value) { table->pfnWriteShort = [] (int value) CR_FORCE_STACK_ALIGN {
// if this message is for a bot, call the client message function... // if this message is for a bot, call the client message function...
msgs.collect (value); msgs.collect (value);
@ -682,7 +693,7 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
engfuncs.pfnWriteShort (value); engfuncs.pfnWriteShort (value);
}; };
table->pfnWriteLong = [] (int value) { table->pfnWriteLong = [] (int value) CR_FORCE_STACK_ALIGN {
// if this message is for a bot, call the client message function... // if this message is for a bot, call the client message function...
msgs.collect (value); msgs.collect (value);
@ -692,7 +703,7 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
engfuncs.pfnWriteLong (value); engfuncs.pfnWriteLong (value);
}; };
table->pfnWriteAngle = [] (float value) { table->pfnWriteAngle = [] (float value) CR_FORCE_STACK_ALIGN {
// if this message is for a bot, call the client message function... // if this message is for a bot, call the client message function...
msgs.collect (value); msgs.collect (value);
@ -702,7 +713,7 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
engfuncs.pfnWriteAngle (value); engfuncs.pfnWriteAngle (value);
}; };
table->pfnWriteCoord = [] (float value) { table->pfnWriteCoord = [] (float value) CR_FORCE_STACK_ALIGN {
// if this message is for a bot, call the client message function... // if this message is for a bot, call the client message function...
msgs.collect (value); msgs.collect (value);
@ -712,7 +723,7 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
engfuncs.pfnWriteCoord (value); engfuncs.pfnWriteCoord (value);
}; };
table->pfnWriteString = [] (const char *sz) { table->pfnWriteString = [] (const char *sz) CR_FORCE_STACK_ALIGN {
// if this message is for a bot, call the client message function... // if this message is for a bot, call the client message function...
msgs.collect (sz); msgs.collect (sz);
@ -722,7 +733,7 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
engfuncs.pfnWriteString (sz); engfuncs.pfnWriteString (sz);
}; };
table->pfnWriteEntity = [] (int value) { table->pfnWriteEntity = [] (int value) CR_FORCE_STACK_ALIGN {
// if this message is for a bot, call the client message function... // if this message is for a bot, call the client message function...
msgs.collect (value); msgs.collect (value);
@ -736,7 +747,7 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
table->pfnClientCommand = Hooks::handler_engClientCommand; table->pfnClientCommand = Hooks::handler_engClientCommand;
if (!game.is (GameFlags::Metamod)) { if (!game.is (GameFlags::Metamod)) {
table->pfnRegUserMsg = [] (const char *name, int size) { table->pfnRegUserMsg = [] (const char *name, int size) CR_FORCE_STACK_ALIGN {
// this function registers a "user message" by the engine side. User messages are network // this function registers a "user message" by the engine side. User messages are network
// messages the game DLL asks the engine to send to clients. Since many MODs have completely // messages the game DLL asks the engine to send to clients. Since many MODs have completely
// different client features (Counter-Strike has a radar and a timer, for example), network // different client features (Counter-Strike has a radar and a timer, for example), network
@ -751,7 +762,7 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
}; };
} }
table->pfnClientPrintf = [] (edict_t *ent, PRINT_TYPE printType, const char *message) { table->pfnClientPrintf = [] (edict_t *ent, PRINT_TYPE printType, const char *message) CR_FORCE_STACK_ALIGN {
// this function prints the text message string pointed to by message by the client side of // this function prints the text message string pointed to by message by the client side of
// the client entity pointed to by ent, in a manner depending of printType (print_console, // the client entity pointed to by ent, in a manner depending of printType (print_console,
// print_center or print_chat). Be certain never to try to feed a bot with this function, // print_center or print_chat). Be certain never to try to feed a bot with this function,
@ -771,7 +782,7 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
engfuncs.pfnClientPrintf (ent, printType, message); engfuncs.pfnClientPrintf (ent, printType, message);
}; };
table->pfnCmd_Args = [] () { table->pfnCmd_Args = [] () CR_FORCE_STACK_ALIGN {
// this function returns a pointer to the whole current client command string. Since bots // this function returns a pointer to the whole current client command string. Since bots
// have no client DLL and we may want a bot to execute a client command, we had to implement // have no client DLL and we may want a bot to execute a client command, we had to implement
// a argv string in the bot DLL for holding the bots' commands, and also keep track of the // a argv string in the bot DLL for holding the bots' commands, and also keep track of the
@ -793,7 +804,7 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
return engfuncs.pfnCmd_Args (); // ask the client command string to the engine return engfuncs.pfnCmd_Args (); // ask the client command string to the engine
}; };
table->pfnCmd_Argv = [] (int argc) { table->pfnCmd_Argv = [] (int argc) CR_FORCE_STACK_ALIGN {
// this function returns a pointer to a certain argument of the current client command. Since // this function returns a pointer to a certain argument of the current client command. Since
// bots have no client DLL and we may want a bot to execute a client command, we had to // bots have no client DLL and we may want a bot to execute a client command, we had to
// implement a argv string in the bot DLL for holding the bots' commands, and also keep // implement a argv string in the bot DLL for holding the bots' commands, and also keep
@ -815,7 +826,7 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
return engfuncs.pfnCmd_Argv (argc); // ask the argument number "argc" to the engine return engfuncs.pfnCmd_Argv (argc); // ask the argument number "argc" to the engine
}; };
table->pfnCmd_Argc = [] () { table->pfnCmd_Argc = [] () CR_FORCE_STACK_ALIGN {
// this function returns the number of arguments the current client command string has. Since // this function returns the number of arguments the current client command string has. Since
// bots have no client DLL and we may want a bot to execute a client command, we had to // bots have no client DLL and we may want a bot to execute a client command, we had to
// implement a argv string in the bot DLL for holding the bots' commands, and also keep // implement a argv string in the bot DLL for holding the bots' commands, and also keep
@ -837,7 +848,7 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
return engfuncs.pfnCmd_Argc (); // ask the engine how many arguments there are return engfuncs.pfnCmd_Argc (); // ask the engine how many arguments there are
}; };
table->pfnSetClientMaxspeed = [] (const edict_t *ent, float newMaxspeed) { table->pfnSetClientMaxspeed = [] (const edict_t *ent, float newMaxspeed) CR_FORCE_STACK_ALIGN {
auto bot = bots[const_cast <edict_t *> (ent)]; auto bot = bots[const_cast <edict_t *> (ent)];
// check wether it's not a bot // check wether it's not a bot
@ -875,7 +886,7 @@ CR_EXPORT int GetNewDLLFunctions (newgamefuncs_t *table, int *interfaceVersion)
plat.bzero (table, sizeof (newgamefuncs_t)); plat.bzero (table, sizeof (newgamefuncs_t));
if (!game.is (GameFlags::Legacy)) { if (!game.is (GameFlags::Legacy)) {
table->pfnOnFreeEntPrivateData = [] (edict_t *ent) { table->pfnOnFreeEntPrivateData = [] (edict_t *ent) CR_FORCE_STACK_ALIGN {
for (auto &bot : bots) { for (auto &bot : bots) {
if (bot->m_enemy == ent) { if (bot->m_enemy == ent) {
bot->m_enemy = nullptr; bot->m_enemy = nullptr;
@ -895,16 +906,16 @@ CR_EXPORT int GetNewDLLFunctions (newgamefuncs_t *table, int *interfaceVersion)
return HLTrue; return HLTrue;
} }
CR_LINKAGE_C int GetEngineFunctions_Post (enginefuncs_t *table, int *) { CR_C_LINKAGE int GetEngineFunctions_Post (enginefuncs_t *table, int *) {
plat.bzero (table, sizeof (enginefuncs_t)); plat.bzero (table, sizeof (enginefuncs_t));
table->pfnMessageEnd = [] () { table->pfnMessageEnd = [] () CR_FORCE_STACK_ALIGN {
msgs.stop (); // this allows us to send messages right in handler code msgs.stop (); // this allows us to send messages right in handler code
RETURN_META (MRES_IGNORED); RETURN_META (MRES_IGNORED);
}; };
table->pfnRegUserMsg = [] (const char *name, int) { table->pfnRegUserMsg = [] (const char *name, int) CR_FORCE_STACK_ALIGN {
// this function registers a "user message" by the engine side. User messages are network // this function registers a "user message" by the engine side. User messages are network
// messages the game DLL asks the engine to send to clients. Since many MODs have completely // messages the game DLL asks the engine to send to clients. Since many MODs have completely
// different client features (Counter-Strike has a radar and a timer, for example), network // different client features (Counter-Strike has a radar and a timer, for example), network
@ -995,15 +1006,15 @@ CR_EXPORT int Meta_Detach (PLUG_LOADTIME now, PL_UNLOAD_REASON reason) {
gpMetaUtilFuncs->pfnLogError (PLID, "%s: plugin NOT detaching (can't unload plugin right now)", Plugin_info.name); gpMetaUtilFuncs->pfnLogError (PLID, "%s: plugin NOT detaching (can't unload plugin right now)", Plugin_info.name);
return HLFalse; // returning FALSE prevents metamod from unloading this plugin return HLFalse; // returning FALSE prevents metamod from unloading this plugin
} }
// stop the worker
worker.shutdown ();
// kick all bots off this server // kick all bots off this server
bots.kickEveryone (true); bots.kickEveryone (true);
// save collected practice on shutdown // save collected practice on shutdown
practice.save (); practice.save ();
// stop the worker
worker.shutdown ();
// disable hooks // disable hooks
fakequeries.disable (); fakequeries.disable ();
@ -1023,13 +1034,13 @@ CR_EXPORT void Meta_Init () {
// games GiveFnptrsToDll is a bit tricky // games GiveFnptrsToDll is a bit tricky
#if defined(CR_WINDOWS) #if defined(CR_WINDOWS)
# if defined(CR_CXX_MSVC) || (defined(CR_CXX_CLANG) && !defined(CR_CXX_GCC)) # if defined(CR_CXX_MSVC) || (defined(CR_CXX_CLANG) && !defined(CR_CXX_GCC))
# if defined (CR_ARCH_X86) # if defined(CR_ARCH_X32)
# pragma comment(linker, "/EXPORT:GiveFnptrsToDll=_GiveFnptrsToDll@8,@1") # pragma comment(linker, "/EXPORT:GiveFnptrsToDll=_GiveFnptrsToDll@8,@1")
# endif # endif
# pragma comment(linker, "/SECTION:.data,RW") # pragma comment(linker, "/SECTION:.data,RW")
# endif # endif
# if defined(CR_CXX_MSVC) && !defined(CR_ARCH_X64) # if defined(CR_CXX_MSVC) && !defined(CR_ARCH_X64)
# define DLL_GIVEFNPTRSTODLL CR_LINKAGE_C void CR_STDCALL # define DLL_GIVEFNPTRSTODLL CR_C_LINKAGE void CR_STDCALL
# elif defined(CR_CXX_CLANG) || defined(CR_CXX_GCC) || defined(CR_ARCH_X64) # elif defined(CR_CXX_CLANG) || defined(CR_CXX_GCC) || defined(CR_ARCH_X64)
# define DLL_GIVEFNPTRSTODLL CR_EXPORT void CR_STDCALL # define DLL_GIVEFNPTRSTODLL CR_EXPORT void CR_STDCALL
# endif # endif

View file

@ -1116,14 +1116,14 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int skin) {
if (cv_show_latency.as <int> () == 1) { if (cv_show_latency.as <int> () == 1) {
engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "*bot", "1"); engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "*bot", "1");
} }
auto avatar = conf.getRandomAvatar (); const auto &avatar = conf.getRandomAvatar ();
if (cv_show_avatars && !avatar.empty ()) { if (cv_show_avatars && !avatar.empty ()) {
engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "*sid", avatar.chars ()); engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "*sid", avatar.chars ());
} }
} }
char reject[256] = { 0, }; char reject[StringBuffer::StaticBufferSize] = { 0, };
MDLL_ClientConnect (bot, bot->v.netname.chars (), strings.format ("127.0.0.%d", clientIndex + 100), reject); MDLL_ClientConnect (bot, bot->v.netname.chars (), strings.format ("127.0.0.%d", clientIndex + 100), reject);
if (!strings.isEmpty (reject)) { if (!strings.isEmpty (reject)) {
@ -1228,7 +1228,9 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int skin) {
m_pathWalk.init (m_planner->getMaxLength ()); m_pathWalk.init (m_planner->getMaxLength ());
// init player models parts enumerator // init player models parts enumerator
if (cv_use_hitbox_enemy_targeting) {
m_hitboxEnumerator = cr::makeUnique <PlayerHitboxEnumerator> (); m_hitboxEnumerator = cr::makeUnique <PlayerHitboxEnumerator> ();
}
// bot is not kicked by rotation // bot is not kicked by rotation
m_kickedByRotation = false; m_kickedByRotation = false;
@ -1335,21 +1337,23 @@ bool BotManager::isTeamStacked (int team) {
return teamCount[team] + 1 > teamCount[team == Team::CT ? Team::Terrorist : Team::CT] + limitTeams; return teamCount[team] + 1 > teamCount[team == Team::CT ? Team::Terrorist : Team::CT] + limitTeams;
} }
void BotManager::erase (Bot *bot) { void BotManager::disconnectBot (Bot *bot) {
for (auto &e : m_bots) { for (auto &e : m_bots) {
if (e.get () != bot) { if (e.get () != bot) {
continue; continue;
} }
bot->markStale ();
if (!bot->m_kickedByRotation && cv_save_bots_names) { if (!bot->m_kickedByRotation && cv_save_bots_names) {
m_saveBotNames.emplaceLast (bot->pev->netname.str ()); m_saveBotNames.emplaceLast (bot->pev->netname.str ());
} }
bot->markStale ();
const auto index = m_bots.index (e); const auto index = m_bots.index (e);
e.reset (); e.reset ();
m_bots.erase (index, 1); // remove from bots array m_bots.erase (index, 1); // remove from bots array
bot = nullptr;
break; break;
} }
} }
@ -1556,7 +1560,10 @@ void Bot::newRound () {
m_followWaitTime = 0.0f; m_followWaitTime = 0.0f;
m_hostages.clear (); m_hostages.clear ();
if (cv_use_hitbox_enemy_targeting) {
m_hitboxEnumerator->reset (); m_hitboxEnumerator->reset ();
}
m_approachingLadderTimer.invalidate (); m_approachingLadderTimer.invalidate ();
m_forgetLastVictimTimer.invalidate (); m_forgetLastVictimTimer.invalidate ();
@ -1745,6 +1752,10 @@ void Bot::kick (bool silent) {
} }
void Bot::markStale () { void Bot::markStale () {
// wait till threads tear down
MutexScopedLock lock1 (m_pathFindLock);
MutexScopedLock lock2 (m_predictLock);
// switch chatter icon off // switch chatter icon off
showChatterIcon (false, true); showChatterIcon (false, true);
@ -2180,13 +2191,19 @@ void BotThreadWorker::shutdown () {
} }
void BotThreadWorker::startup (int workers) { void BotThreadWorker::startup (int workers) {
String disableWorkerEnv = plat.env ("YAPB_SINGLE_THREADED"); StringRef disableWorkerEnv = plat.env ("YAPB_SINGLE_THREADED");
// disable on legacy games // disable on legacy games
const bool isLegacyGame = game.is (GameFlags::Legacy); const bool isLegacyGame = game.is (GameFlags::Legacy);
// do not do any threading when timescale enabled
ConVarRef timescale ("sys_timescale");
// disable worker if requested via env variable or workers are disabled // disable worker if requested via env variable or workers are disabled
if (isLegacyGame || workers == 0 || (!disableWorkerEnv.empty () && disableWorkerEnv == "1")) { if (isLegacyGame
|| workers == 0
|| timescale.value () > 0
|| (!disableWorkerEnv.empty () && disableWorkerEnv == "1")) {
return; return;
} }
m_pool = cr::makeUnique <ThreadPool> (); m_pool = cr::makeUnique <ThreadPool> ();

View file

@ -117,7 +117,7 @@ CR_EXPORT IYaPBModule *GetBotAPI (int version) {
if (version != kYaPBModuleVersion) { if (version != kYaPBModuleVersion) {
return nullptr; return nullptr;
} }
static YaPBModule botModule; static YaPBModule botModule {};
return &botModule; return &botModule;
} }

View file

@ -3357,7 +3357,7 @@ edict_t *Bot::lookupButton (StringRef target, bool blindTest) {
const Vector &pos = game.getEntityOrigin (ent); const Vector &pos = game.getEntityOrigin (ent);
if (!blindTest) { if (!blindTest) {
game.testLine (pev->origin, pos, TraceIgnore::Monsters, pev->pContainingEntity, &tr); game.testLine (pev->origin, pos, TraceIgnore::Monsters, this->ent (), &tr);
} }
// check if this place safe // check if this place safe
@ -3449,11 +3449,6 @@ void Bot::findShortestPath (int srcIndex, int destIndex) {
void Bot::syncFindPath (int srcIndex, int destIndex, FindPath pathType) { void Bot::syncFindPath (int srcIndex, int destIndex, FindPath pathType) {
// this function finds a path from srcIndex to destIndex; // this function finds a path from srcIndex to destIndex;
// stale bots shouldn't do pathfinding
if (m_isStale) {
return;
}
if (!m_pathFindLock.tryLock ()) { if (!m_pathFindLock.tryLock ()) {
return; // allow only single instance of syncFindPath per-bot return; // allow only single instance of syncFindPath per-bot
} }
@ -3559,6 +3554,11 @@ void Bot::syncFindPath (int srcIndex, int destIndex, FindPath pathType) {
} }
void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath::Fast */) { void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath::Fast */) {
// stale bots shouldn't do pathfinding
if (m_isStale) {
return;
}
worker.enqueue ([this, srcIndex, destIndex, pathType] () { worker.enqueue ([this, srcIndex, destIndex, pathType] () {
syncFindPath (srcIndex, destIndex, pathType); syncFindPath (srcIndex, destIndex, pathType);
}); });

View file

@ -163,7 +163,7 @@ void BotPractice::save () {
bstor.save <DangerSaveRestore> (data); bstor.save <DangerSaveRestore> (data);
} }
void BotPractice::load () { void BotPractice::syncLoad () {
if (!graph.length ()) { if (!graph.length ()) {
return; // no action return; // no action
} }
@ -182,3 +182,9 @@ void BotPractice::load () {
} }
} }
void BotPractice::load () {
worker.enqueue ([this] () {
syncLoad ();
});
}

View file

@ -436,81 +436,42 @@ StringRef BotSupport::weaponIdToAlias (int32_t id) {
return none; return none;
} }
// helper class for reading wave header float BotSupport::getWaveFileDuration (StringRef filename) {
class WaveEndianessHelper final : public NonCopyable { constexpr auto kZeroLength = 0.0f;
private:
#if defined (CR_ARCH_CPU_BIG_ENDIAN)
bool little { false };
#else
bool little { true };
#endif
public: using WaveHeader = WaveHelper <>::Header;
uint16_t read16 (uint16_t value) {
return little ? value : static_cast <uint16_t> ((value >> 8) | (value << 8));
}
uint32_t read32 (uint32_t value) {
return little ? value : (((value & 0x000000ff) << 24) | ((value & 0x0000ff00) << 8) | ((value & 0x00ff0000) >> 8) | ((value & 0xff000000) >> 24));
}
bool isWave (char *format) const {
if (little && memcmp (format, "WAVE", 4) == 0) {
return true;
}
return *reinterpret_cast <uint32_t *> (format) == 0x57415645;
}
};
float BotSupport::getWaveLength (StringRef filename) {
auto filePath = strings.joinPath (cv_chatter_path.as <StringRef> (), strings.format ("%s.wav", filename)); auto filePath = strings.joinPath (cv_chatter_path.as <StringRef> (), strings.format ("%s.wav", filename));
MemFile fp (filePath); MemFile fp (filePath);
// we're got valid handle? // we're got valid handle?
if (!fp) { if (!fp) {
return 0.0f; return kZeroLength;
} }
// else fuck with manual search WaveHeader hdr {};
struct WavHeader { static WaveHelper wh {};
char riff[4];
uint32_t chunkSize;
char wave[4];
char fmt[4];
uint32_t subchunk1Size;
uint16_t audioFormat;
uint16_t numChannels;
uint32_t sampleRate;
uint32_t byteRate;
uint16_t blockAlign;
uint16_t bitsPerSample;
char dataChunkId[4];
uint32_t dataChunkLength;
} header {};
static WaveEndianessHelper weh {}; if (fp.read (&hdr, sizeof (WaveHeader)) == 0) {
logger.error ("WAVE %s - has wrong or unsupported format.", filePath);
if (fp.read (&header, sizeof (WavHeader)) == 0) { return kZeroLength;
logger.error ("Wave File %s - has wrong or unsupported format", filePath);
return 0.0f;
} }
fp.close (); fp.close ();
if (!weh.isWave (header.wave)) { if (!wh.isWave (hdr.wave)) {
logger.error ("Wave File %s - has wrong wave chunk id", filePath); logger.error ("WAVE %s - has wrong wave chunk id.", filePath);
return 0.0f; return kZeroLength;
} }
if (weh.read32 (header.dataChunkLength) == 0) { if (wh.read32 <uint32_t> (hdr.dataChunkLength) == 0) {
logger.error ("Wave File %s - has zero length!", filePath); logger.error ("WAVE %s - has zero length!.", filePath);
return 0.0f; return kZeroLength;
} }
const auto length = static_cast <float> (weh.read32 (header.dataChunkLength)); const auto length = wh.read32 <float> (hdr.dataChunkLength);
const auto bps = static_cast <float> (weh.read16 (header.bitsPerSample)) / 8; const auto bps = wh.read16 <float> (hdr.bitsPerSample) / 8.0f;
const auto channels = static_cast <float> (weh.read16 (header.numChannels)); const auto channels = wh.read16 <float> (hdr.numChannels);
const auto rate = static_cast <float> (weh.read32 (header.sampleRate)); const auto rate = wh.read32 <float> (hdr.sampleRate);
return length / bps / channels / rate; return length / bps / channels / rate;
} }

View file

@ -331,7 +331,7 @@ void Bot::spraypaint_ () {
if (getTask ()->time - 0.5f < game.time ()) { if (getTask ()->time - 0.5f < game.time ()) {
// emit spray can sound // emit spray can sound
engfuncs.pfnEmitSound (pev->pContainingEntity, CHAN_VOICE, "player/sprayer.wav", 1.0f, ATTN_NORM, 0, 100); engfuncs.pfnEmitSound (ent (), CHAN_VOICE, "player/sprayer.wav", 1.0f, ATTN_NORM, 0, 100);
game.testLine (getEyesPos (), getEyesPos () + forward * 128.0f, TraceIgnore::Monsters, ent (), &tr); game.testLine (getEyesPos (), getEyesPos () + forward * 128.0f, TraceIgnore::Monsters, ent (), &tr);
@ -594,7 +594,7 @@ void Bot::blind_ () {
m_blindNodeIndex = kInvalidNodeIndex; m_blindNodeIndex = kInvalidNodeIndex;
m_blindMoveSpeed = 0.0f; m_blindMoveSpeed = 0.0f;
m_blindSidemoveSpeed = 0.0f; m_blindSideMoveSpeed = 0.0f;
m_blindButton = 0; m_blindButton = 0;
m_states |= Sense::SuspectEnemy; m_states |= Sense::SuspectEnemy;
@ -610,7 +610,7 @@ void Bot::blind_ () {
} }
else { else {
m_moveSpeed = m_blindMoveSpeed; m_moveSpeed = m_blindMoveSpeed;
m_strafeSpeed = m_blindSidemoveSpeed; m_strafeSpeed = m_blindSideMoveSpeed;
pev->button |= m_blindButton; pev->button |= m_blindButton;
m_states |= Sense::SuspectEnemy; m_states |= Sense::SuspectEnemy;

View file

@ -375,14 +375,19 @@ void Frustum::calculate (Planes &planes, const Vector &viewAngle, const Vector &
auto fc = viewOffset + forward * kMaxViewDistance; auto fc = viewOffset + forward * kMaxViewDistance;
auto nc = viewOffset + forward * kMinViewDistance; auto nc = viewOffset + forward * kMinViewDistance;
auto fbl = fc + (up * m_farHeight * 0.5f) - (right * m_farWidth * 0.5f); auto up_half_far = up * m_farHeight * 0.5f;
auto fbr = fc + (up * m_farHeight * 0.5f) + (right * m_farWidth * 0.5f); auto right_half_far = right * m_farWidth * 0.5f;
auto ftl = fc - (up * m_farHeight * 0.5f) - (right * m_farWidth * 0.5f); auto up_half_near = up * m_nearHeight * 0.5f;
auto ftr = fc - (up * m_farHeight * 0.5f) + (right * m_farWidth * 0.5f); auto right_half_near = right * m_nearWidth * 0.5f;
auto nbl = nc + (up * m_nearHeight * 0.5f) - (right * m_nearWidth * 0.5f);
auto nbr = nc + (up * m_nearHeight * 0.5f) + (right * m_nearWidth * 0.5f); auto fbl = fc - right_half_far + up_half_far;
auto ntl = nc - (up * m_nearHeight * 0.5f) - (right * m_nearWidth * 0.5f); auto fbr = fc + right_half_far + up_half_far;
auto ntr = nc - (up * m_nearHeight * 0.5f) + (right * m_nearWidth * 0.5f); auto ftl = fc - right_half_far - up_half_far;
auto ftr = fc + right_half_far - up_half_far;
auto nbl = nc - right_half_near + up_half_near;
auto nbr = nc + right_half_near + up_half_near;
auto ntl = nc - right_half_near - up_half_near;
auto ntr = nc + right_half_near - up_half_near;
auto setPlane = [&] (PlaneSide side, const Vector &v1, const Vector &v2, const Vector &v3) { auto setPlane = [&] (PlaneSide side, const Vector &v1, const Vector &v2, const Vector &v3) {
auto &plane = planes[static_cast <int> (side)]; auto &plane = planes[static_cast <int> (side)];

View file

@ -124,14 +124,19 @@ void GraphVistable::rebuild () {
else { else {
m_sliceIndex += rg (250, 400); m_sliceIndex += rg (250, 400);
} }
auto notifyProgress = [] (int value) {
game.print ("Rebuilding vistable... %d%% done.", value);
};
// notify host about rebuilding // notify host about rebuilding
if (m_notifyMsgTimestamp > 0.0f && m_notifyMsgTimestamp < game.time () && end == m_length) { if (m_notifyMsgTimestamp > 0.0f && m_notifyMsgTimestamp < game.time () && end == m_length) {
game.print ("Rebuilding vistable... %d%% done.", m_curIndex * 100 / m_length); notifyProgress (m_curIndex * 100 / m_length);
m_notifyMsgTimestamp = game.time () + 1.0f; m_notifyMsgTimestamp = game.time () + 1.0f;
} }
if (m_curIndex == m_length && end == m_length) { if (m_curIndex == m_length && end == m_length) {
notifyProgress (100);
m_rebuild = false; m_rebuild = false;
m_notifyMsgTimestamp = 0.0f; m_notifyMsgTimestamp = 0.0f;

View file

@ -51,6 +51,7 @@
<ClInclude Include="..\ext\crlib\crlib\ulz.h" /> <ClInclude Include="..\ext\crlib\crlib\ulz.h" />
<ClInclude Include="..\ext\crlib\crlib\uniqueptr.h" /> <ClInclude Include="..\ext\crlib\crlib\uniqueptr.h" />
<ClInclude Include="..\ext\crlib\crlib\vector.h" /> <ClInclude Include="..\ext\crlib\crlib\vector.h" />
<ClInclude Include="..\ext\crlib\crlib\wavehelper.h" />
<ClInclude Include="..\ext\linkage\linkage\goldsrc.h" /> <ClInclude Include="..\ext\linkage\linkage\goldsrc.h" />
<ClInclude Include="..\ext\linkage\linkage\metamod.h" /> <ClInclude Include="..\ext\linkage\linkage\metamod.h" />
<ClInclude Include="..\ext\linkage\linkage\physint.h" /> <ClInclude Include="..\ext\linkage\linkage\physint.h" />
@ -150,7 +151,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType> <ConfigurationType>DynamicLibrary</ConfigurationType>
<UseOfMfc>false</UseOfMfc> <UseOfMfc>false</UseOfMfc>
<PlatformToolset>ClangCL</PlatformToolset> <PlatformToolset>v143</PlatformToolset>
<UseDebugLibraries>false</UseDebugLibraries> <UseDebugLibraries>false</UseDebugLibraries>
<EnableASAN>false</EnableASAN> <EnableASAN>false</EnableASAN>
</PropertyGroup> </PropertyGroup>

View file

@ -195,6 +195,9 @@
<ClInclude Include="..\inc\fakeping.h"> <ClInclude Include="..\inc\fakeping.h">
<Filter>inc</Filter> <Filter>inc</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\ext\crlib\crlib\wavehelper.h">
<Filter>inc\ext\crlib</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="..\src\botlib.cpp"> <ClCompile Include="..\src\botlib.cpp">