added multiple items to custom.cfg, so game delay timer cvar is now configurable, the infected team is configurable as well. creature (well, just zombies) now correctly detects their "creature" status even with custom model names (assumes that bot is on an infected team = zombie) Co-Authored-By: Max <161382234+dyspose@users.noreply.github.com>
658 lines
17 KiB
C++
658 lines
17 KiB
C++
//
|
|
// YaPB, based on PODBot by Markus Klinge ("CountFloyd").
|
|
// Copyright © YaPB Project Developers <yapb@jeefo.net>.
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
//
|
|
|
|
#pragma once
|
|
|
|
// line draw
|
|
CR_DECLARE_SCOPED_ENUM (DrawLine,
|
|
Simple,
|
|
Arrow,
|
|
Count
|
|
)
|
|
|
|
// trace ignore
|
|
CR_DECLARE_SCOPED_ENUM (TraceIgnore,
|
|
None = 0,
|
|
Glass = cr::bit (0),
|
|
Monsters = cr::bit (1),
|
|
Everything = Glass | Monsters
|
|
)
|
|
|
|
// variable type
|
|
CR_DECLARE_SCOPED_ENUM (Var,
|
|
Normal = 0,
|
|
ReadOnly,
|
|
Password,
|
|
NoServer,
|
|
GameRef,
|
|
Xash3D // registrable only on xash3d engine
|
|
)
|
|
|
|
// supported cs's
|
|
CR_DECLARE_SCOPED_ENUM (GameFlags,
|
|
Modern = cr::bit (0), // counter-strike 1.6 and above
|
|
Xash3D = cr::bit (1), // counter-strike 1.6 under the xash engine (additional flag)
|
|
ConditionZero = cr::bit (2), // counter-strike: condition zero
|
|
Legacy = cr::bit (3), // counter-strike 1.3-1.5 with/without steam
|
|
Mobility = cr::bit (4), // additional flag that bot is running on android (additional flag)
|
|
Unused = cr::bit (5), // not used currently
|
|
Metamod = cr::bit (6), // game running under meta\mod
|
|
CSDM = cr::bit (7), // csdm mod currently in use
|
|
FreeForAll = cr::bit (8), // csdm mod with ffa mode
|
|
ReGameDLL = cr::bit (9), // server dll is a regamedll
|
|
HasFakePings = cr::bit (10), // on that game version we can fake bots pings
|
|
HasBotVoice = cr::bit (11), // on that game version we can use chatter
|
|
AnniversaryHL25 = cr::bit (12), // half-life 25th anniversary engine
|
|
Xash3DLegacy = cr::bit (13), // old xash3d-branch
|
|
ZombieMod = cr::bit (14) // zombie mod is active
|
|
)
|
|
|
|
// defines map type
|
|
CR_DECLARE_SCOPED_ENUM (MapFlags,
|
|
Assassination = cr::bit (0),
|
|
HostageRescue = cr::bit (1),
|
|
Demolition = cr::bit (2),
|
|
Escape = cr::bit (3),
|
|
KnifeArena = cr::bit (4),
|
|
FightYard = cr::bit (5),
|
|
GrenadeWar = cr::bit(6),
|
|
HasDoors = cr::bit (10), // additional flags
|
|
HasButtons = cr::bit (11) // map has buttons
|
|
)
|
|
|
|
// recursive entity search
|
|
CR_DECLARE_SCOPED_ENUM (EntitySearchResult,
|
|
Continue,
|
|
Break
|
|
)
|
|
|
|
// variable reg pair
|
|
struct ConVarReg {
|
|
cvar_t reg;
|
|
String info;
|
|
String init;
|
|
String regval;
|
|
String name;
|
|
class ConVar *self;
|
|
float initial, min, max;
|
|
bool missing;
|
|
bool bounded;
|
|
int32_t type;
|
|
};
|
|
|
|
// entity prototype
|
|
using EntityProto = void (*) (entvars_t *);
|
|
|
|
// rehlds has this fixed, but original hlds doesn't allocate string space passed to precache* argument, so game will crash when unloading module using metamod
|
|
class EngineWrap final {
|
|
public:
|
|
EngineWrap () = default;
|
|
~EngineWrap () = default;
|
|
|
|
private:
|
|
const char *allocStr (const char *str) const {
|
|
return string_t::from (engfuncs.pfnAllocString (str));
|
|
}
|
|
|
|
public:
|
|
int32_t precacheModel (const char *model) const {
|
|
return engfuncs.pfnPrecacheModel (allocStr (model));
|
|
}
|
|
|
|
int32_t precacheSound (const char *sound) const {
|
|
return engfuncs.pfnPrecacheSound (allocStr (sound));
|
|
}
|
|
|
|
void setModel (edict_t *ent, const char *model) {
|
|
engfuncs.pfnSetModel (ent, allocStr (model));
|
|
}
|
|
};
|
|
|
|
// provides utility functions to not call original engine (less call-cost)
|
|
class Game final : public Singleton <Game> {
|
|
public:
|
|
using EntitySearch = const Lambda <EntitySearchResult (edict_t *)> &;
|
|
|
|
private:
|
|
int m_drawModels[DrawLine::Count] {};
|
|
int m_spawnCount[Team::Unassigned] {};
|
|
|
|
// bot client command
|
|
StringArray m_botArgs {};
|
|
|
|
edict_t *m_startEntity {};
|
|
edict_t *m_localEntity {};
|
|
|
|
Array <edict_t *> m_breakables {};
|
|
SmallArray <ConVarReg> m_cvars {};
|
|
SharedLibrary m_gameLib {};
|
|
SharedLibrary m_engineLib {};
|
|
EngineWrap m_engineWrap {};
|
|
|
|
bool m_precached {};
|
|
|
|
int m_gameFlags {};
|
|
int m_mapFlags {};
|
|
|
|
float m_oneSecondFrame {}; // per second updated
|
|
float m_halfSecondFrame {}; // per half second update
|
|
|
|
public:
|
|
Game ();
|
|
~Game () = default;
|
|
|
|
public:
|
|
// preaches internal stuff
|
|
void precache ();
|
|
|
|
// initialize levels
|
|
void levelInitialize (edict_t *entities, int max);
|
|
|
|
// shutdown levels
|
|
void levelShutdown ();
|
|
|
|
// display world line
|
|
void drawLine (edict_t *ent, const Vector &start, const Vector &end, int width, int noise, const Color &color, int brightness, int speed, int life, DrawLine type = DrawLine::Simple);
|
|
|
|
// test line
|
|
void testLine (const Vector &start, const Vector &end, int ignoreFlags, edict_t *ignoreEntity, TraceResult *ptr);
|
|
|
|
// test model
|
|
void testModel (const Vector &start, const Vector &end, int hullNumber, edict_t *entToHit, TraceResult *ptr);
|
|
|
|
// test line
|
|
void testHull (const Vector &start, const Vector &end, int ignoreFlags, int hullNumber, edict_t *ignoreEntity, TraceResult *ptr);
|
|
|
|
// we are on dedicated server ?
|
|
bool isDedicated ();
|
|
|
|
// get stripped down mod name
|
|
const char *getRunningModName ();
|
|
|
|
// get the valid mapname
|
|
const char *getMapName ();
|
|
|
|
// get the "any" entity origin
|
|
Vector getEntityOrigin (edict_t *ent);
|
|
|
|
// registers a server command
|
|
void registerEngineCommand (const char *command, void func ());
|
|
|
|
// play's sound to client
|
|
void playSound (edict_t *ent, const char *sound);
|
|
|
|
// sends bot command
|
|
void prepareBotArgs (edict_t *ent, String str);
|
|
|
|
// adds cvar to registration stack
|
|
void pushConVar (StringRef name, StringRef value, StringRef info, bool bounded, float min, float max, int32_t varType, bool missingAction, StringRef regval, class ConVar *self);
|
|
|
|
// check the cvar bounds
|
|
void checkCvarsBounds ();
|
|
|
|
// sends local registration stack for engine registration
|
|
void registerCvars (bool gameVars = false);
|
|
|
|
// checks whether software rendering is enabled
|
|
bool isSoftwareRenderer ();
|
|
|
|
// checks if this is 25th anniversary half-life update
|
|
bool is25thAnniversaryUpdate ();
|
|
|
|
// load the cs binary in non metamod mode
|
|
bool loadCSBinary ();
|
|
|
|
// do post-load stuff
|
|
bool postload ();
|
|
|
|
// detects if csdm mod is in use
|
|
void applyGameModes ();
|
|
|
|
// executes stuff every 1 second
|
|
void slowFrame ();
|
|
|
|
// search entities by variable field
|
|
void searchEntities (StringRef field, StringRef value, EntitySearch functor);
|
|
|
|
// search entities in sphere
|
|
void searchEntities (const Vector &position, float radius, EntitySearch functor);
|
|
|
|
// check if map has entity
|
|
bool hasEntityInGame (StringRef classname);
|
|
|
|
// print the version to server console on startup
|
|
void printBotVersion ();
|
|
|
|
// public inlines
|
|
public:
|
|
// get the current time on server
|
|
float time () const {
|
|
return globals->time;
|
|
}
|
|
|
|
// get "maxplayers" limit on server
|
|
int maxClients () const {
|
|
return globals->maxClients;
|
|
}
|
|
|
|
// get the fakeclient command interface
|
|
bool isBotCmd () const {
|
|
return !m_botArgs.empty ();
|
|
}
|
|
|
|
// gets custom engine args for client command
|
|
const char *botArgs () const {
|
|
return strings.format (String::join (m_botArgs, " ", m_botArgs[0].startsWith ("say") ? 1 : 0).chars ());
|
|
}
|
|
|
|
// gets custom engine argv for client command
|
|
const char *botArgv (int32_t index) const {
|
|
if (static_cast <size_t> (index) >= m_botArgs.length ()) {
|
|
return "";
|
|
}
|
|
return m_botArgs[index].chars ();
|
|
}
|
|
|
|
// gets custom engine argc for client command
|
|
int32_t botArgc () const {
|
|
return m_botArgs.length <int32_t> ();
|
|
}
|
|
|
|
// gets edict pointer out of entity index
|
|
edict_t *entityOfIndex (const int index) {
|
|
return static_cast <edict_t *> (m_startEntity + index);
|
|
};
|
|
|
|
// gets edict pointer out of entity index (player)
|
|
edict_t *playerOfIndex (const int index) {
|
|
return entityOfIndex (index) + 1;
|
|
};
|
|
|
|
// gets edict index out of it's pointer
|
|
int indexOfEntity (const edict_t *ent) {
|
|
return static_cast <int> (ent - m_startEntity);
|
|
};
|
|
|
|
// gets edict index of it's pointer (player)
|
|
int indexOfPlayer (const edict_t *ent) {
|
|
return indexOfEntity (ent) - 1;
|
|
}
|
|
|
|
// verify entity isn't null
|
|
bool isNullEntity (const edict_t *ent) {
|
|
return !ent || !indexOfEntity (ent) || ent->free;
|
|
}
|
|
|
|
// get the wroldspawn entity
|
|
edict_t *getStartEntity () {
|
|
return m_startEntity;
|
|
}
|
|
|
|
// get spawn count for team
|
|
int getSpawnCount (int team) const {
|
|
return m_spawnCount[team];
|
|
}
|
|
|
|
// gets the player team
|
|
int getTeam (edict_t *ent) {
|
|
if (isNullEntity (ent)) {
|
|
return Team::Unassigned;
|
|
}
|
|
return util.getClient (indexOfPlayer (ent)).team;
|
|
}
|
|
|
|
// gets the player team (real in ffa)
|
|
int getRealTeam (edict_t *ent) {
|
|
if (isNullEntity (ent)) {
|
|
return Team::Unassigned;
|
|
}
|
|
return util.getClient (indexOfPlayer (ent)).team2;
|
|
}
|
|
|
|
// sets the precache to uninitialize
|
|
void setUnprecached () {
|
|
m_precached = false;
|
|
}
|
|
|
|
// gets the local entity (host edict)
|
|
edict_t *getLocalEntity () {
|
|
return m_localEntity;
|
|
}
|
|
|
|
// sets the local entity (host edict)
|
|
void setLocalEntity (edict_t *ent) {
|
|
m_localEntity = ent;
|
|
}
|
|
|
|
// sets player start entity draw models
|
|
void setPlayerStartDrawModels ();
|
|
|
|
// check the engine visibility wrapper
|
|
bool checkVisibility (edict_t *ent, uint8_t *set);
|
|
|
|
// get pvs/pas visibility set
|
|
uint8_t *getVisibilitySet (Bot *bot, bool pvs);
|
|
|
|
// what kind of game engine / game dll / mod / tool we're running ?
|
|
bool is (const int type) const {
|
|
return !!(m_gameFlags & type);
|
|
}
|
|
|
|
// adds game flag
|
|
void addGameFlag (const int type) {
|
|
m_gameFlags |= type;
|
|
}
|
|
|
|
// gets the map type
|
|
bool mapIs (const int type) const {
|
|
return !!(m_mapFlags & type);
|
|
}
|
|
|
|
// get loaded gamelib
|
|
const SharedLibrary &lib () {
|
|
return m_gameLib;
|
|
}
|
|
|
|
// get loaded engine lib
|
|
const SharedLibrary &elib () {
|
|
return m_engineLib;
|
|
}
|
|
|
|
// get registered cvars list
|
|
const SmallArray <ConVarReg> &getCvars () {
|
|
return m_cvars;
|
|
}
|
|
|
|
// check if map has breakables
|
|
const Array <edict_t *> &getBreakables () {
|
|
return m_breakables;
|
|
}
|
|
|
|
// map has breakables ?
|
|
bool hasBreakables () const {
|
|
return !m_breakables.empty ();
|
|
}
|
|
|
|
// find variable value by variable name
|
|
StringRef findCvar (StringRef name) {
|
|
return engfuncs.pfnCVarGetString (name.chars ());
|
|
}
|
|
|
|
// helper to sending the client message
|
|
void sendClientMessage (bool console, edict_t *ent, StringRef message);
|
|
|
|
// helper to sending the server message
|
|
void sendServerMessage (StringRef message);
|
|
|
|
// helper for sending hud messages to client
|
|
void sendHudMessage (edict_t *ent, const hudtextparms_t &htp, StringRef message);
|
|
|
|
// send server command
|
|
template <typename ...Args> void serverCommand (const char *fmt, Args &&...args) {
|
|
engfuncs.pfnServerCommand (strings.concat (strings.format (fmt, cr::forward <Args> (args)...), "\n", StringBuffer::StaticBufferSize));
|
|
}
|
|
|
|
// send a bot command
|
|
template <typename ...Args> void botCommand (edict_t *ent, const char *fmt, Args &&...args) {
|
|
prepareBotArgs (ent, strings.format (fmt, cr::forward <Args> (args)...));
|
|
}
|
|
|
|
// prints data to servers console
|
|
template <typename ...Args> void print (const char *fmt, Args &&...args) {
|
|
sendServerMessage (strings.concat (strings.format (conf.translate (fmt), cr::forward <Args> (args)...), "\n", StringBuffer::StaticBufferSize));
|
|
}
|
|
|
|
// prints center message to specified player
|
|
template <typename ...Args> void clientPrint (edict_t *ent, const char *fmt, Args &&...args) {
|
|
if (isNullEntity (ent)) {
|
|
print (fmt, cr::forward <Args> (args)...);
|
|
return;
|
|
}
|
|
sendClientMessage (true, ent, strings.concat (strings.format (conf.translate (fmt), cr::forward <Args> (args)...), "\n", StringBuffer::StaticBufferSize));
|
|
}
|
|
|
|
// prints message to client console
|
|
template <typename ...Args> void centerPrint (edict_t *ent, const char *fmt, Args &&...args) {
|
|
if (isNullEntity (ent)) {
|
|
print (fmt, cr::forward <Args> (args)...);
|
|
return;
|
|
}
|
|
sendClientMessage (false, ent, strings.concat (strings.format (conf.translate (fmt), cr::forward <Args> (args)...), "\n", StringBuffer::StaticBufferSize));
|
|
}
|
|
};
|
|
|
|
// reference some game/mod cvars for access
|
|
class ConVarRef final : public NonCopyable {
|
|
private:
|
|
cvar_t *ptr_ {};
|
|
String name_ {};
|
|
bool checked_ {};
|
|
|
|
public:
|
|
ConVarRef (StringRef name) : name_ (name) {}
|
|
~ConVarRef () = default;
|
|
|
|
public:
|
|
bool exists () {
|
|
if (checked_ && !ptr_) {
|
|
return false;
|
|
}
|
|
checked_ = true;
|
|
ptr_ = engfuncs.pfnCVarGetPointer (name_.chars ());
|
|
|
|
return ptr_ != nullptr;
|
|
}
|
|
|
|
template <typename U = float> U value () {
|
|
return exists () ? static_cast <U> (ptr_->value) : static_cast <U> (0);
|
|
}
|
|
|
|
void set (StringRef value) {
|
|
if (exists ()) {
|
|
engfuncs.pfnCvar_DirectSet (ptr_, value.chars ());
|
|
}
|
|
}
|
|
};
|
|
|
|
// simplify access for console variables
|
|
class ConVar final : public NonCopyable {
|
|
public:
|
|
cvar_t *ptr;
|
|
|
|
private:
|
|
String name_ {};
|
|
|
|
public:
|
|
ConVar () = delete;
|
|
~ConVar () = default;
|
|
|
|
public:
|
|
ConVar (StringRef name, StringRef initval, int32_t type = Var::NoServer, bool regMissing = false, StringRef regVal = nullptr) : ptr (nullptr) {
|
|
setPrefix (name, type);
|
|
Game::instance ().pushConVar (name_.chars (), initval, "", false, 0.0f, 0.0f, type, regMissing, regVal, this);
|
|
}
|
|
|
|
ConVar (StringRef name, StringRef initval, StringRef info, bool bounded = true, float min = 0.0f, float max = 1.0f, int32_t type = Var::NoServer, bool regMissing = false, const char *regVal = nullptr) : ptr (nullptr) {
|
|
setPrefix (name, type);
|
|
Game::instance ().pushConVar (name_.chars (), initval, info, bounded, min, max, type, regMissing, regVal, this);
|
|
}
|
|
|
|
public:
|
|
template <typename U> constexpr U as () const {
|
|
if constexpr (cr::is_same <U, float>::value) {
|
|
return ptr->value;
|
|
}
|
|
else if constexpr (cr::is_same <U, bool>::value) {
|
|
return ptr->value > 0.0f;
|
|
}
|
|
else if constexpr (cr::is_same <U, int>::value) {
|
|
return static_cast <U> (ptr->value);
|
|
}
|
|
else if constexpr (cr::is_same <U, StringRef>::value) {
|
|
return ptr->string;
|
|
}
|
|
}
|
|
|
|
public:
|
|
operator bool () const {
|
|
return as <bool> ();
|
|
}
|
|
|
|
operator float () const {
|
|
return as <float> ();
|
|
}
|
|
|
|
operator int () const {
|
|
return as <int> ();
|
|
}
|
|
|
|
operator StringRef () {
|
|
return as <StringRef> ();
|
|
}
|
|
|
|
public:
|
|
StringRef name () const {
|
|
return ptr->name;
|
|
}
|
|
|
|
void set (float val) {
|
|
engfuncs.pfnCVarSetFloat (ptr->name, val);
|
|
}
|
|
|
|
void set (int val) {
|
|
set (static_cast <float> (val));
|
|
}
|
|
|
|
void set (const char *val) {
|
|
engfuncs.pfnCvar_DirectSet (ptr, val);
|
|
}
|
|
|
|
// revet cvar to default value
|
|
void revert ();
|
|
|
|
// set the cvar prefix if needed
|
|
void setPrefix (StringRef name, int32_t type);
|
|
};
|
|
|
|
class MessageWriter final {
|
|
private:
|
|
bool m_autoDestruct { false };
|
|
|
|
public:
|
|
MessageWriter () = default;
|
|
|
|
MessageWriter (int dest, int type, const Vector &pos = nullptr, edict_t *to = nullptr) {
|
|
start (dest, type, pos, to);
|
|
m_autoDestruct = true;
|
|
}
|
|
|
|
~MessageWriter () {
|
|
if (m_autoDestruct) {
|
|
end ();
|
|
}
|
|
}
|
|
|
|
public:
|
|
MessageWriter &start (int dest, int type, const Vector &pos = nullptr, edict_t *to = nullptr) {
|
|
engfuncs.pfnMessageBegin (dest, type, pos, to);
|
|
return *this;
|
|
}
|
|
|
|
void end () {
|
|
engfuncs.pfnMessageEnd ();
|
|
}
|
|
|
|
MessageWriter &writeByte (int val) {
|
|
engfuncs.pfnWriteByte (val);
|
|
return *this;
|
|
}
|
|
|
|
MessageWriter &writeLong (int val) {
|
|
engfuncs.pfnWriteLong (val);
|
|
return *this;
|
|
}
|
|
|
|
MessageWriter &writeChar (int val) {
|
|
engfuncs.pfnWriteChar (val);
|
|
return *this;
|
|
}
|
|
|
|
MessageWriter &writeShort (int val) {
|
|
engfuncs.pfnWriteShort (val);
|
|
return *this;
|
|
}
|
|
|
|
MessageWriter &writeCoord (float val) {
|
|
engfuncs.pfnWriteCoord (val);
|
|
return *this;
|
|
}
|
|
|
|
MessageWriter &writeString (const char *val) {
|
|
engfuncs.pfnWriteString (val);
|
|
return *this;
|
|
}
|
|
|
|
public:
|
|
static constexpr uint16_t fu16 (float value, float scale) {
|
|
return cr::clamp <uint16_t> (static_cast <uint16_t> (value * cr::bit (static_cast <short> (scale))), 0, USHRT_MAX);
|
|
}
|
|
|
|
static constexpr short fs16 (float value, float scale) {
|
|
return cr::clamp <short> (static_cast <short> (value * cr::bit (static_cast <short> (scale))), -SHRT_MAX, SHRT_MAX);
|
|
}
|
|
};
|
|
|
|
class LightMeasure final : public Singleton <LightMeasure> {
|
|
private:
|
|
lightstyle_t m_lightstyle[MAX_LIGHTSTYLES] {};
|
|
uint32_t m_lightstyleValue[MAX_LIGHTSTYLEVALUE] {};
|
|
bool m_doAnimation = false;
|
|
|
|
Color m_point;
|
|
model_t *m_worldModel = nullptr;
|
|
|
|
public:
|
|
LightMeasure () {
|
|
initializeLightstyles ();
|
|
m_point.reset ();
|
|
}
|
|
|
|
public:
|
|
void initializeLightstyles ();
|
|
void animateLight ();
|
|
void updateLight (int style, char *value);
|
|
|
|
float getLightLevel (const Vector &point);
|
|
float getSkyColor ();
|
|
|
|
private:
|
|
template <typename S, typename M> bool recursiveLightPoint (const M *node, const Vector &start, const Vector &end);
|
|
|
|
public:
|
|
void resetWorldModel () {
|
|
m_worldModel = nullptr;
|
|
}
|
|
|
|
void setWorldModel (model_t *model) {
|
|
if (m_worldModel) {
|
|
return;
|
|
}
|
|
m_worldModel = model;
|
|
}
|
|
|
|
model_t *getWorldModel () const {
|
|
return m_worldModel;
|
|
}
|
|
|
|
void enableAnimation (bool enable) {
|
|
m_doAnimation = enable;
|
|
}
|
|
};
|
|
|
|
// expose globals
|
|
CR_EXPOSE_GLOBAL_SINGLETON (Game, game);
|
|
CR_EXPOSE_GLOBAL_SINGLETON (LightMeasure, illum);
|