// Copyright (c) ZeniMax Media Inc. // Licensed under the GNU General Public License 2.0. // g_local.h -- local definitions for game module #pragma once #include "bg_local.h" // the "gameversion" client command will print this plus compile date constexpr const char *GAMEVERSION = "baseq2"; //================================================================== constexpr vec3_t PLAYER_MINS = { -16, -16, -24 }; constexpr vec3_t PLAYER_MAXS = { 16, 16, 32 }; #include template constexpr bool is_char_ptr_v = std::is_convertible_v; template constexpr bool is_valid_loc_embed_v = !std::is_null_pointer_v && (std::is_floating_point_v> || std::is_integral_v> || is_char_ptr_v); struct local_game_import_t : game_import_t { inline local_game_import_t() = default; inline local_game_import_t(const game_import_t &imports) : game_import_t(imports) { } private: // shared buffer for wrappers below static char print_buffer[0x10000]; public: #ifdef USE_CPP20_FORMAT template inline void Com_PrintFmt(std::format_string format_str, Args &&... args) #else #define Com_PrintFmt(str, ...) \ Com_PrintFmt_(FMT_STRING(str), __VA_ARGS__) template inline void Com_PrintFmt_(const S &format_str, Args &&... args) #endif { G_FmtTo_(print_buffer, format_str, std::forward(args)...); Com_Print(print_buffer); } #ifdef USE_CPP20_FORMAT template inline void Com_ErrorFmt(std::format_string format_str, Args &&... args) #else #define Com_ErrorFmt(str, ...) \ Com_ErrorFmt_(FMT_STRING(str), __VA_ARGS__) template inline void Com_ErrorFmt_(const S &format_str, Args &&... args) #endif { G_FmtTo_(print_buffer, format_str, std::forward(args)...); Com_Error(print_buffer); } private: // localized print functions template inline void loc_embed(T input, char* buffer, const char*& output) { if constexpr (std::is_floating_point_v || std::is_integral_v) { auto result = std::to_chars(buffer, buffer + MAX_INFO_STRING - 1, input); *result.ptr = '\0'; output = buffer; } else if constexpr (is_char_ptr_v) { if (!input) Com_Error("null const char ptr passed to loc"); output = input; } else Com_Error("invalid loc argument"); } static std::array buffers; static std::array buffer_ptrs; public: template inline void LocClient_Print(edict_t* e, print_type_t level, const char* base, Args&& ...args) { static_assert(sizeof...(args) < MAX_LOCALIZATION_ARGS, "too many arguments to gi.LocClient_Print"); static_assert(((is_valid_loc_embed_v) && ...), "invalid argument passed to gi.LocClient_Print"); size_t n = 0; ((loc_embed(args, buffers[n], buffer_ptrs[n]), ++n), ...); Loc_Print(e, level, base, &buffer_ptrs.front(), sizeof...(args)); } template inline void LocBroadcast_Print(print_type_t level, const char* base, Args&& ...args) { static_assert(sizeof...(args) < MAX_LOCALIZATION_ARGS, "too many arguments to gi.LocBroadcast_Print"); static_assert(((is_valid_loc_embed_v) && ...), "invalid argument passed to gi.LocBroadcast_Print"); size_t n = 0; ((loc_embed(args, buffers[n], buffer_ptrs[n]), ++n), ...); Loc_Print(nullptr, (print_type_t)(level | print_type_t::PRINT_BROADCAST), base, &buffer_ptrs.front(), sizeof...(args)); } template inline void LocCenter_Print(edict_t* e, const char* base, Args&& ...args) { static_assert(sizeof...(args) < MAX_LOCALIZATION_ARGS, "too many arguments to gi.LocCenter_Print"); static_assert(((is_valid_loc_embed_v) && ...), "invalid argument passed to gi.LocCenter_Print"); size_t n = 0; ((loc_embed(args, buffers[n], buffer_ptrs[n]), ++n), ...); Loc_Print(e, PRINT_CENTER, base, &buffer_ptrs.front(), sizeof...(args)); } // collision detection [[nodiscard]] inline trace_t trace(const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end, const edict_t *passent, contents_t contentmask) { return game_import_t::trace(start, &mins, &maxs, end, passent, contentmask); } [[nodiscard]] inline trace_t traceline(const vec3_t &start, const vec3_t &end, const edict_t *passent, contents_t contentmask) { return game_import_t::trace(start, nullptr, nullptr, end, passent, contentmask); } // [Paril-KEX] clip the box against the specified entity [[nodiscard]] inline trace_t clip(edict_t *entity, const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end, contents_t contentmask) { return game_import_t::clip(entity, start, &mins, &maxs, end, contentmask); } [[nodiscard]] inline trace_t clip(edict_t *entity, const vec3_t &start, const vec3_t &end, contents_t contentmask) { return game_import_t::clip(entity, start, nullptr, nullptr, end, contentmask); } void unicast(edict_t *ent, bool reliable, uint32_t dupe_key = 0) { game_import_t::unicast(ent, reliable, dupe_key); } void local_sound(edict_t *target, const vec3_t &origin, edict_t *ent, soundchan_t channel, int soundindex, float volume, float attenuation, float timeofs, uint32_t dupe_key = 0) { game_import_t::local_sound(target, &origin, ent, channel, soundindex, volume, attenuation, timeofs, dupe_key); } void local_sound(edict_t *target, edict_t *ent, soundchan_t channel, int soundindex, float volume, float attenuation, float timeofs, uint32_t dupe_key = 0) { game_import_t::local_sound(target, nullptr, ent, channel, soundindex, volume, attenuation, timeofs, dupe_key); } void local_sound(const vec3_t &origin, edict_t *ent, soundchan_t channel, int soundindex, float volume, float attenuation, float timeofs, uint32_t dupe_key = 0) { game_import_t::local_sound(ent, &origin, ent, channel, soundindex, volume, attenuation, timeofs, dupe_key); } void local_sound(edict_t *ent, soundchan_t channel, int soundindex, float volume, float attenuation, float timeofs, uint32_t dupe_key = 0) { game_import_t::local_sound(ent, nullptr, ent, channel, soundindex, volume, attenuation, timeofs, dupe_key); } }; extern local_game_import_t gi; // edict->spawnflags // these are set with checkboxes on each entity in the map editor. // the following 8 are reserved and should never be used by any entity. // (power cubes in coop use these after spawning as well) struct spawnflags_t { uint32_t value; explicit constexpr spawnflags_t(uint32_t v) : value(v) { } explicit operator uint32_t() const { return value; } // has any flags at all (!!a) constexpr bool any() const { return !!value; } // has any of the given flags (!!(a & b)) constexpr bool has(const spawnflags_t &flags) const { return !!(value & flags.value); } // has all of the given flags ((a & b) == b) constexpr bool has_all(const spawnflags_t &flags) const { return (value & flags.value) == flags.value; } constexpr bool operator!() const { return !value; } constexpr bool operator==(const spawnflags_t &flags) const { return value == flags.value; } constexpr bool operator!=(const spawnflags_t &flags) const { return value != flags.value; } constexpr spawnflags_t operator~() const { return spawnflags_t(~value); } constexpr spawnflags_t operator|(const spawnflags_t &v2) const { return spawnflags_t(value | v2.value); } constexpr spawnflags_t operator&(const spawnflags_t &v2) const { return spawnflags_t(value & v2.value); } constexpr spawnflags_t operator^(const spawnflags_t &v2) const { return spawnflags_t(value ^ v2.value); } constexpr spawnflags_t &operator|=(const spawnflags_t &v2) { value |= v2.value; return *this; } constexpr spawnflags_t &operator&=(const spawnflags_t &v2) { value &= v2.value; return *this; } constexpr spawnflags_t &operator^=(const spawnflags_t &v2) { value ^= v2.value; return *this; } }; // these spawnflags affect every entity. note that items are a bit special // because these 8 bits are instead used for power cube bits. constexpr spawnflags_t SPAWNFLAG_NONE = spawnflags_t(0); constexpr spawnflags_t SPAWNFLAG_NOT_EASY = spawnflags_t(0x00000100), SPAWNFLAG_NOT_MEDIUM = spawnflags_t(0x00000200), SPAWNFLAG_NOT_HARD = spawnflags_t(0x00000400), SPAWNFLAG_NOT_DEATHMATCH = spawnflags_t(0x00000800), SPAWNFLAG_NOT_COOP = spawnflags_t(0x00001000), SPAWNFLAG_RESERVED1 = spawnflags_t(0x00002000), SPAWNFLAG_COOP_ONLY = spawnflags_t(0x00004000), SPAWNFLAG_RESERVED2 = spawnflags_t(0x00008000); constexpr spawnflags_t SPAWNFLAG_EDITOR_MASK = (SPAWNFLAG_NOT_EASY | SPAWNFLAG_NOT_MEDIUM | SPAWNFLAG_NOT_HARD | SPAWNFLAG_NOT_DEATHMATCH | SPAWNFLAG_NOT_COOP | SPAWNFLAG_RESERVED1 | SPAWNFLAG_COOP_ONLY | SPAWNFLAG_RESERVED2); // use this for global spawnflags constexpr spawnflags_t operator "" _spawnflag(unsigned long long int v) { if (v & SPAWNFLAG_EDITOR_MASK.value) throw std::invalid_argument("attempting to use reserved spawnflag"); return static_cast(static_cast(v)); } // use this for global spawnflags constexpr spawnflags_t operator "" _spawnflag_bit(unsigned long long int v) { v = 1ull << v; if (v & SPAWNFLAG_EDITOR_MASK.value) throw std::invalid_argument("attempting to use reserved spawnflag"); return static_cast(static_cast(v)); } // stores a level time; most newer engines use int64_t for // time storage, but seconds are handy for compatibility // with Quake and older mods. struct gtime_t { private: // times always start at zero, just to prevent memory issues int64_t _ms = 0; // internal; use _sec/_ms/_min or gtime_t::from_sec(n)/gtime_t::from_ms(n)/gtime_t::from_min(n) constexpr explicit gtime_t(const int64_t &ms) : _ms(ms) { } public: constexpr gtime_t() = default; constexpr gtime_t(const gtime_t &) = default; constexpr gtime_t &operator=(const gtime_t &) = default; // constructors are here, explicitly named, so you always // know what you're getting. // new time from ms static constexpr gtime_t from_ms(const int64_t &ms) { return gtime_t(ms); } // new time from seconds template || std::is_integral_v>> static constexpr gtime_t from_sec(const T &s) { return gtime_t(static_cast(s * 1000)); } // new time from minutes template || std::is_integral_v>> static constexpr gtime_t from_min(const T &s) { return gtime_t(static_cast(s * 60000)); } // new time from hz static constexpr gtime_t from_hz(const uint64_t &hz) { return from_ms(static_cast((1.0 / hz) * 1000)); } // get value in minutes (truncated for integers) template constexpr T minutes() const { return static_cast(_ms / static_cast, T, float>>(60000)); } // get value in seconds (truncated for integers) template constexpr T seconds() const { return static_cast(_ms / static_cast, T, float>>(1000)); } // get value in milliseconds constexpr const int64_t &milliseconds() const { return _ms; } int64_t frames() const { return _ms / gi.frame_time_ms; } // check if non-zero constexpr explicit operator bool() const { return !!_ms; } // invert time constexpr gtime_t operator-() const { return gtime_t(-_ms); } // operations with other times as input constexpr gtime_t operator+(const gtime_t &r) const { return gtime_t(_ms + r._ms); } constexpr gtime_t operator-(const gtime_t &r) const { return gtime_t(_ms - r._ms); } constexpr gtime_t &operator+=(const gtime_t &r) { return *this = *this + r; } constexpr gtime_t &operator-=(const gtime_t &r) { return *this = *this - r; } // operations with scalars as input template || std::is_integral_v>> constexpr gtime_t operator*(const T &r) const { return gtime_t::from_ms(static_cast(_ms * r)); } template || std::is_integral_v>> constexpr gtime_t operator/(const T &r) const { return gtime_t::from_ms(static_cast(_ms / r)); } template || std::is_integral_v>> constexpr gtime_t &operator*=(const T &r) { return *this = *this * r; } template || std::is_integral_v>> constexpr gtime_t &operator/=(const T &r) { return *this = *this / r; } // comparisons with gtime_ts constexpr bool operator==(const gtime_t &time) const { return _ms == time._ms; } constexpr bool operator!=(const gtime_t &time) const { return _ms != time._ms; } constexpr bool operator<(const gtime_t &time) const { return _ms < time._ms; } constexpr bool operator>(const gtime_t &time) const { return _ms > time._ms; } constexpr bool operator<=(const gtime_t &time) const { return _ms <= time._ms; } constexpr bool operator>=(const gtime_t &time) const { return _ms >= time._ms; } }; // user literals, allowing you to specify times // as 128_sec and 128_ms constexpr gtime_t operator"" _min(long double s) { return gtime_t::from_min(s); } constexpr gtime_t operator"" _min(unsigned long long int s) { return gtime_t::from_min(s); } constexpr gtime_t operator"" _sec(long double s) { return gtime_t::from_sec(s); } constexpr gtime_t operator"" _sec(unsigned long long int s) { return gtime_t::from_sec(s); } constexpr gtime_t operator"" _ms(long double s) { return gtime_t::from_ms(static_cast(s)); } constexpr gtime_t operator"" _ms(unsigned long long int s) { return gtime_t::from_ms(static_cast(s)); } constexpr gtime_t operator"" _hz(unsigned long long int s) { return gtime_t::from_ms(static_cast((1.0 / s) * 1000)); } #define SERVER_TICK_RATE gi.tick_rate // in hz extern gtime_t FRAME_TIME_S; extern gtime_t FRAME_TIME_MS; // view pitching times inline gtime_t DAMAGE_TIME_SLACK() { return (100_ms - FRAME_TIME_MS); } inline gtime_t DAMAGE_TIME() { return 500_ms + DAMAGE_TIME_SLACK(); } inline gtime_t FALL_TIME() { return 300_ms + DAMAGE_TIME_SLACK(); } // every save_data_list_t has a tag // which is used for integrity checks. enum save_data_tag_t { SAVE_DATA_MMOVE, SAVE_FUNC_MONSTERINFO_STAND, SAVE_FUNC_MONSTERINFO_IDLE, SAVE_FUNC_MONSTERINFO_SEARCH, SAVE_FUNC_MONSTERINFO_WALK, SAVE_FUNC_MONSTERINFO_RUN, SAVE_FUNC_MONSTERINFO_DODGE, SAVE_FUNC_MONSTERINFO_ATTACK, SAVE_FUNC_MONSTERINFO_MELEE, SAVE_FUNC_MONSTERINFO_SIGHT, SAVE_FUNC_MONSTERINFO_CHECKATTACK, SAVE_FUNC_MONSTERINFO_SETSKIN, SAVE_FUNC_MONSTERINFO_BLOCKED, SAVE_FUNC_MONSTERINFO_DUCK, SAVE_FUNC_MONSTERINFO_UNDUCK, SAVE_FUNC_MONSTERINFO_SIDESTEP, SAVE_FUNC_MONSTERINFO_PHYSCHANGED, SAVE_FUNC_MOVEINFO_ENDFUNC, SAVE_FUNC_MOVEINFO_BLOCKED, SAVE_FUNC_PRETHINK, SAVE_FUNC_THINK, SAVE_FUNC_TOUCH, SAVE_FUNC_USE, SAVE_FUNC_PAIN, SAVE_FUNC_DIE }; // forward-linked list, storing data for // saving pointers. every save_data_ptr has an // instance of this; there's one head instance of this // in g_save.cpp. struct save_data_list_t { const char *name; // name of savable object; persisted in the JSON file save_data_tag_t tag; const void *ptr; // pointer to raw data const save_data_list_t *next; // next in list save_data_list_t(const char *name, save_data_tag_t tag, const void *ptr); static const save_data_list_t *fetch(const void *link_ptr, save_data_tag_t tag); }; #include // save data wrapper, which holds a pointer to a T // and the tag value for integrity. this is how you // store a savable pointer of data safely. template struct save_data_t { using value_type = typename std::conditional::value && std::is_function::type>::value, T, const T *>::type; private: value_type value; const save_data_list_t *list; public: constexpr save_data_t() : value(nullptr), list(nullptr) { } constexpr save_data_t(nullptr_t) : save_data_t() { } constexpr save_data_t(const save_data_list_t *list_in) : value(list_in->ptr), list(list_in) { } inline save_data_t(value_type ptr_in) : value(ptr_in), list(ptr_in ? save_data_list_t::fetch(reinterpret_cast(ptr_in), static_cast(Tag)) : nullptr) { } inline save_data_t(const save_data_t &ref_in) : save_data_t(ref_in.value) { } inline save_data_t &operator=(value_type ptr_in) { if (value != ptr_in) { value = ptr_in; list = value ? save_data_list_t::fetch(reinterpret_cast(ptr_in), static_cast(Tag)) : nullptr; } return *this; } constexpr const value_type pointer() const { return value; } constexpr const save_data_list_t *save_list() const { return list; } constexpr const char *name() const { return value ? list->name : "null"; } constexpr const value_type operator->() const { return value; } constexpr explicit operator bool() const { return value; } constexpr bool operator==(value_type ptr_in) const { return value == ptr_in; } constexpr bool operator!=(value_type ptr_in) const { return value != ptr_in; } constexpr bool operator==(const save_data_t *ptr_in) const { return value == ptr_in->value; } constexpr bool operator==(const save_data_t &ref_in) const { return value == ref_in.value; } constexpr bool operator!=(const save_data_t *ptr_in) const { return value != ptr_in->value; } constexpr bool operator!=(const save_data_t &ref_in) const { return value != ref_in.value; } // invoke wrapper, for function-likes template inline auto operator()(Args&& ...args) const { static_assert(std::is_invocable_v, Args...>, "bad invoke args"); return std::invoke(value, std::forward(args)...); } }; // memory tags to allow dynamic memory to be cleaned up enum { TAG_GAME = 765, // clear when unloading the dll TAG_LEVEL = 766 // clear when loading a new level }; constexpr float MELEE_DISTANCE = 50; constexpr size_t BODY_QUEUE_SIZE = 8; // null trace used when touches don't need a trace constexpr trace_t null_trace {}; enum weaponstate_t { WEAPON_READY, WEAPON_ACTIVATING, WEAPON_DROPPING, WEAPON_FIRING }; // gib flags enum gib_type_t { GIB_NONE = 0, // no flags (organic) GIB_METALLIC = 1, // bouncier GIB_ACID = 2, // acidic (gekk) GIB_HEAD = 4, // head gib; the input entity will transform into this GIB_DEBRIS = 8, // explode outwards rather than in velocity, no blood GIB_SKINNED = 16, // use skinnum GIB_UPRIGHT = 32, // stay upright on ground }; MAKE_ENUM_BITFLAGS(gib_type_t); // monster ai flags enum monster_ai_flags_t : uint64_t { AI_NONE = 0, AI_STAND_GROUND = bit_v<0>, AI_TEMP_STAND_GROUND = bit_v<1>, AI_SOUND_TARGET = bit_v<2>, AI_LOST_SIGHT = bit_v<3>, AI_PURSUIT_LAST_SEEN = bit_v<4>, AI_PURSUE_NEXT = bit_v<5>, AI_PURSUE_TEMP = bit_v<6>, AI_HOLD_FRAME = bit_v<7>, AI_GOOD_GUY = bit_v<8>, AI_BRUTAL = bit_v<9>, AI_NOSTEP = bit_v<10>, AI_DUCKED = bit_v<11>, AI_COMBAT_POINT = bit_v<12>, AI_MEDIC = bit_v<13>, AI_RESURRECTING = bit_v<14>, // ROGUE AI_MANUAL_STEERING = bit_v<15>, AI_TARGET_ANGER = bit_v<16>, AI_DODGING = bit_v<17>, AI_CHARGING = bit_v<18>, AI_HINT_PATH = bit_v<19>, AI_IGNORE_SHOTS = bit_v<20>, // PMM - FIXME - last second added for E3 .. there's probably a better way to do this, but // this works AI_DO_NOT_COUNT = bit_v<21>, // set for healed monsters AI_SPAWNED_CARRIER = bit_v<22>, // both do_not_count and spawned are set for spawned monsters AI_SPAWNED_MEDIC_C = bit_v<23>, // both do_not_count and spawned are set for spawned monsters AI_SPAWNED_WIDOW = bit_v<24>, // both do_not_count and spawned are set for spawned monsters AI_BLOCKED = bit_v<25>, // used by blocked_checkattack: set to say I'm attacking while blocked // (prevents run-attacks) // ROGUE AI_SPAWNED_ALIVE = bit_v<26>, // [Paril-KEX] for spawning dead AI_SPAWNED_DEAD = bit_v<27>, AI_HIGH_TICK_RATE = bit_v<28>, // not limited by 10hz actions AI_NO_PATH_FINDING = bit_v<29>, // don't try nav nodes for path finding AI_PATHING = bit_v<30>, // using nav nodes currently AI_STINKY = bit_v<31>, // spawn flies AI_STUNK = bit_v<32>, // already spawned files AI_ALTERNATE_FLY = bit_v<33>, // use alternate flying mechanics; see monsterinfo.fly_xxx AI_TEMP_MELEE_COMBAT = bit_v<34>, // temporarily switch to the melee combat style AI_FORGET_ENEMY = bit_v<35>, // forget the current enemy AI_DOUBLE_TROUBLE = bit_v<36>, // JORG only AI_REACHED_HOLD_COMBAT = bit_v<37>, AI_THIRD_EYE = bit_v<38> }; MAKE_ENUM_BITFLAGS(monster_ai_flags_t); constexpr monster_ai_flags_t AI_SPAWNED_MASK = AI_SPAWNED_CARRIER | AI_SPAWNED_MEDIC_C | AI_SPAWNED_WIDOW; // mask to catch all three flavors of spawned // monster attack state enum monster_attack_state_t { AS_NONE, AS_STRAIGHT, AS_SLIDING, AS_MELEE, AS_MISSILE, AS_BLIND // PMM - used by boss code to do nasty things even if it can't see you }; // handedness values enum handedness_t { RIGHT_HANDED, LEFT_HANDED, CENTER_HANDED }; enum class auto_switch_t { SMART, ALWAYS, ALWAYS_NO_AMMO, NEVER }; constexpr uint32_t SFL_CROSS_TRIGGER_MASK = (0xffffffffu & ~SPAWNFLAG_EDITOR_MASK.value); // noise types for PlayerNoise enum player_noise_t { PNOISE_SELF, PNOISE_WEAPON, PNOISE_IMPACT }; struct gitem_armor_t { int32_t base_count; int32_t max_count; float normal_protection; float energy_protection; }; static constexpr gitem_armor_t jacketarmor_info = { 25, 50, .30f, .00f }; static constexpr gitem_armor_t combatarmor_info = { 50, 100, .60f, .30f }; static constexpr gitem_armor_t bodyarmor_info = { 100, 200, .80f, .60f }; // edict->movetype values enum movetype_t { MOVETYPE_NONE, // never moves MOVETYPE_NOCLIP, // origin and angles change with no interaction MOVETYPE_PUSH, // no clip to world, push on box contact MOVETYPE_STOP, // no clip to world, stops on box contact MOVETYPE_WALK, // gravity MOVETYPE_STEP, // gravity, special edge handling MOVETYPE_FLY, MOVETYPE_TOSS, // gravity MOVETYPE_FLYMISSILE, // extra size to monsters MOVETYPE_BOUNCE, // RAFAEL MOVETYPE_WALLBOUNCE, // RAFAEL // ROGUE MOVETYPE_NEWTOSS // PGM - for deathball // ROGUE }; // edict->flags enum ent_flags_t : uint64_t { FL_NONE = 0, // no flags FL_FLY = bit_v<0>, FL_SWIM = bit_v<1>, // implied immunity to drowning FL_IMMUNE_LASER = bit_v<2>, FL_INWATER = bit_v<3>, FL_GODMODE = bit_v<4>, FL_NOTARGET = bit_v<5>, FL_IMMUNE_SLIME = bit_v<6>, FL_IMMUNE_LAVA = bit_v<7>, FL_PARTIALGROUND = bit_v<8>, // not all corners are valid FL_WATERJUMP = bit_v<9>, // player jumping out of water FL_TEAMSLAVE = bit_v<10>, // not the first on the team FL_NO_KNOCKBACK = bit_v<11>, FL_POWER_ARMOR = bit_v<12>, // power armor (if any) is active // ROGUE FL_MECHANICAL = bit_v<13>, // entity is mechanical, use sparks not blood FL_SAM_RAIMI = bit_v<14>, // entity is in sam raimi cam mode FL_DISGUISED = bit_v<15>, // entity is in disguise, monsters will not recognize. FL_NOGIB = bit_v<16>, // player has been vaporized by a nuke, drop no gibs FL_DAMAGEABLE = bit_v<17>, FL_STATIONARY = bit_v<18>, // ROGUE FL_ALIVE_KNOCKBACK_ONLY = bit_v<19>, // only apply knockback if alive or on same frame as death FL_NO_DAMAGE_EFFECTS = bit_v<20>, // [Paril-KEX] gets scaled by coop health scaling FL_COOP_HEALTH_SCALE = bit_v<21>, FL_FLASHLIGHT = bit_v<22>, // enable flashlight FL_KILL_VELOCITY = bit_v<23>, // for berserker slam FL_NOVISIBLE = bit_v<24>, // super invisibility FL_DODGE = bit_v<25>, // monster should try to dodge this FL_TEAMMASTER = bit_v<26>, // is a team master (only here so that entities abusing teammaster/teamchain for stuff don't break) FL_LOCKED = bit_v<27>, // entity is locked for the purposes of navigation FL_ALWAYS_TOUCH = bit_v<28>, // always touch, even if we normally wouldn't FL_NO_STANDING = bit_v<29>, // don't allow "standing" on non-brush entities FL_WANTS_POWER_ARMOR = bit_v<30>, // for players, auto-shield FL_RESPAWN = bit_v<31>, // used for item respawning FL_TRAP = bit_v<32>, // entity is a trap of some kind FL_TRAP_LASER_FIELD = bit_v<33>, // enough of a special case to get it's own flag... FL_IMMORTAL = bit_v<34> // never go below 1hp }; MAKE_ENUM_BITFLAGS( ent_flags_t ); // gitem_t->flags enum item_flags_t : uint32_t { IF_NONE = 0, IF_WEAPON = bit_v<0>, // use makes active weapon IF_AMMO = bit_v<1>, IF_ARMOR = bit_v<2>, IF_STAY_COOP = bit_v<3>, IF_KEY = bit_v<4>, IF_POWERUP = bit_v<5>, // ROGUE IF_NOT_GIVEABLE = bit_v<6>, // item can not be given // ROGUE IF_HEALTH = bit_v<7>, // ZOID IF_TECH = bit_v<8>, IF_NO_HASTE = bit_v<9>, // ZOID IF_NO_INFINITE_AMMO = bit_v<10>, // [Paril-KEX] don't allow infinite ammo to affect IF_POWERUP_WHEEL = bit_v<11>, // [Paril-KEX] item should be in powerup wheel IF_POWERUP_ONOFF = bit_v<12>, // [Paril-KEX] for wheel; can't store more than one, show on/off state IF_NOT_RANDOM = bit_v<13>, // [Paril-KEX] item never shows up in randomizations IF_ANY = 0xFFFFFFFF }; MAKE_ENUM_BITFLAGS(item_flags_t); // health edict_t->style enum { HEALTH_IGNORE_MAX = 1, HEALTH_TIMED = 2 }; // item IDs; must match itemlist order enum item_id_t : int32_t { IT_NULL, // must always be zero IT_ARMOR_BODY, IT_ARMOR_COMBAT, IT_ARMOR_JACKET, IT_ARMOR_SHARD, IT_ITEM_POWER_SCREEN, IT_ITEM_POWER_SHIELD, IT_WEAPON_GRAPPLE, IT_WEAPON_BLASTER, IT_WEAPON_CHAINFIST, IT_WEAPON_SHOTGUN, IT_WEAPON_SSHOTGUN, IT_WEAPON_MACHINEGUN, IT_WEAPON_ETF_RIFLE, IT_WEAPON_CHAINGUN, IT_AMMO_GRENADES, IT_AMMO_TRAP, IT_AMMO_TESLA, IT_WEAPON_GLAUNCHER, IT_WEAPON_PROXLAUNCHER, IT_WEAPON_RLAUNCHER, IT_WEAPON_HYPERBLASTER, IT_WEAPON_IONRIPPER, IT_WEAPON_PLASMABEAM, IT_WEAPON_RAILGUN, IT_WEAPON_PHALANX, IT_WEAPON_BFG, IT_WEAPON_DISRUPTOR, #if 0 IT_WEAPON_DISINTEGRATOR, #endif IT_AMMO_SHELLS, IT_AMMO_BULLETS, IT_AMMO_CELLS, IT_AMMO_ROCKETS, IT_AMMO_SLUGS, IT_AMMO_MAGSLUG, IT_AMMO_FLECHETTES, IT_AMMO_PROX, IT_AMMO_NUKE, IT_AMMO_ROUNDS, IT_ITEM_QUAD, IT_ITEM_QUADFIRE, IT_ITEM_INVULNERABILITY, IT_ITEM_INVISIBILITY, IT_ITEM_SILENCER, IT_ITEM_REBREATHER, IT_ITEM_ENVIROSUIT, IT_ITEM_ANCIENT_HEAD, IT_ITEM_LEGACY_HEAD, IT_ITEM_ADRENALINE, IT_ITEM_BANDOLIER, IT_ITEM_PACK, IT_ITEM_IR_GOGGLES, IT_ITEM_DOUBLE, IT_ITEM_SPHERE_VENGEANCE, IT_ITEM_SPHERE_HUNTER, IT_ITEM_SPHERE_DEFENDER, IT_ITEM_DOPPELGANGER, IT_ITEM_TAG_TOKEN, IT_KEY_DATA_CD, IT_KEY_POWER_CUBE, IT_KEY_EXPLOSIVE_CHARGES, IT_KEY_YELLOW, IT_KEY_POWER_CORE, IT_KEY_PYRAMID, IT_KEY_DATA_SPINNER, IT_KEY_PASS, IT_KEY_BLUE_KEY, IT_KEY_RED_KEY, IT_KEY_GREEN_KEY, IT_KEY_COMMANDER_HEAD, IT_KEY_AIRSTRIKE, IT_KEY_NUKE_CONTAINER, IT_KEY_NUKE, IT_HEALTH_SMALL, IT_HEALTH_MEDIUM, IT_HEALTH_LARGE, IT_HEALTH_MEGA, IT_FLAG1, IT_FLAG2, IT_TECH_RESISTANCE, IT_TECH_STRENGTH, IT_TECH_HASTE, IT_TECH_REGENERATION, IT_ITEM_FLASHLIGHT, IT_ITEM_COMPASS, IT_TOTAL }; struct gitem_t { item_id_t id; // matches item list index const char *classname; // spawning name bool (*pickup)(edict_t *ent, edict_t *other); void (*use)(edict_t *ent, gitem_t *item); void (*drop)(edict_t *ent, gitem_t *item); void (*weaponthink)(edict_t *ent); const char *pickup_sound; const char *world_model; effects_t world_model_flags; const char *view_model; // client side info const char *icon; const char *use_name; // for use command, english only const char *pickup_name; // for printing on pickup const char *pickup_name_definite; // definite article version for languages that need it int quantity = 0; // for ammo how much, for weapons how much is used per shot item_id_t ammo = IT_NULL; // for weapons item_id_t chain = IT_NULL; // weapon chain root item_flags_t flags = IF_NONE; // IT_* flags const char *vwep_model = nullptr; // vwep model string (for weapons) const gitem_armor_t *armor_info = nullptr; int tag = 0; const char *precaches = nullptr; // string of all models, sounds, and images this item will use int32_t sort_id = 0; // used by some items to control their sorting int32_t quantity_warn = 5; // when to warn on low ammo // set in InitItems, don't set by hand // circular list of chained weapons gitem_t *chain_next = nullptr; // set in SP_worldspawn, don't set by hand // model index for vwep int32_t vwep_index = 0; // set in SetItemNames, don't set by hand // offset into CS_WHEEL_AMMO/CS_WHEEL_WEAPONS/CS_WHEEL_POWERUPS int32_t ammo_wheel_index = -1; int32_t weapon_wheel_index = -1; int32_t powerup_wheel_index = -1; }; // means of death enum mod_id_t : uint8_t { MOD_UNKNOWN, MOD_BLASTER, MOD_SHOTGUN, MOD_SSHOTGUN, MOD_MACHINEGUN, MOD_CHAINGUN, MOD_GRENADE, MOD_G_SPLASH, MOD_ROCKET, MOD_R_SPLASH, MOD_HYPERBLASTER, MOD_RAILGUN, MOD_BFG_LASER, MOD_BFG_BLAST, MOD_BFG_EFFECT, MOD_HANDGRENADE, MOD_HG_SPLASH, MOD_WATER, MOD_SLIME, MOD_LAVA, MOD_CRUSH, MOD_TELEFRAG, MOD_TELEFRAG_SPAWN, MOD_FALLING, MOD_SUICIDE, MOD_HELD_GRENADE, MOD_EXPLOSIVE, MOD_BARREL, MOD_BOMB, MOD_EXIT, MOD_SPLASH, MOD_TARGET_LASER, MOD_TRIGGER_HURT, MOD_HIT, MOD_TARGET_BLASTER, // RAFAEL 14-APR-98 MOD_RIPPER, MOD_PHALANX, MOD_BRAINTENTACLE, MOD_BLASTOFF, MOD_GEKK, MOD_TRAP, // END 14-APR-98 //======== // ROGUE MOD_CHAINFIST, MOD_DISINTEGRATOR, MOD_ETF_RIFLE, MOD_BLASTER2, MOD_HEATBEAM, MOD_TESLA, MOD_PROX, MOD_NUKE, MOD_VENGEANCE_SPHERE, MOD_HUNTER_SPHERE, MOD_DEFENDER_SPHERE, MOD_TRACKER, MOD_DBALL_CRUSH, MOD_DOPPLE_EXPLODE, MOD_DOPPLE_VENGEANCE, MOD_DOPPLE_HUNTER, // ROGUE //======== MOD_GRAPPLE, MOD_BLUEBLASTER }; struct mod_t { mod_id_t id; bool friendly_fire = false; bool no_point_loss = false; mod_t() = default; constexpr mod_t(mod_id_t id, bool no_point_loss = false) : id(id), no_point_loss(no_point_loss) { } }; // the total number of levels we'll track for the // end of unit screen. constexpr size_t MAX_LEVELS_PER_UNIT = 8; struct level_entry_t { // bsp name char map_name[MAX_QPATH]; // map name char pretty_name[MAX_QPATH]; // these are set when we leave the level int32_t total_secrets; int32_t found_secrets; int32_t total_monsters; int32_t killed_monsters; // total time spent in the level, for end screen gtime_t time; // the order we visited levels in int32_t visit_order; }; // // this structure is left intact through an entire game // it should be initialized at dll load time, and read/written to // the server.ssv file for savegames // struct game_locals_t { char helpmessage1[MAX_TOKEN_CHARS]; char helpmessage2[MAX_TOKEN_CHARS]; int32_t help1changed, help2changed; gclient_t *clients; // [maxclients] // can't store spawnpoint in level, because // it would get overwritten by the savegame restore char spawnpoint[MAX_TOKEN_CHARS]; // needed for coop respawns // store latched cvars here that we want to get at often uint32_t maxclients; uint32_t maxentities; // cross level triggers uint32_t cross_level_flags, cross_unit_flags; bool autosaved; // [Paril-KEX] int32_t airacceleration_modified, gravity_modified; std::array level_entries; int32_t max_lag_origins; vec3_t *lag_origins; // maxclients * max_lag_origins }; constexpr size_t MAX_HEALTH_BARS = 2; // // this structure is cleared as each map is entered // it is read/written to the level.sav file for savegames // struct level_locals_t { bool in_frame; gtime_t time; char level_name[MAX_QPATH]; // the descriptive name (Outer Base, etc) char mapname[MAX_QPATH]; // the server name (base1, etc) char nextmap[MAX_QPATH]; // go here when fraglimit is hit char forcemap[MAX_QPATH]; // go here // intermission state gtime_t intermissiontime; // time the intermission was started const char *changemap; const char *achievement; bool exitintermission; bool intermission_eou; bool intermission_clear; // [Paril-KEX] clear inventory on switch bool level_intermission_set; // [Paril-KEX] for target_camera switches; don't find intermission point bool intermission_fade, intermission_fading; // [Paril-KEX] fade on exit instead of immediately leaving gtime_t intermission_fade_time; vec3_t intermission_origin; vec3_t intermission_angle; bool respawn_intermission; // only set once for respawning players int32_t pic_health; int32_t total_secrets; int32_t found_secrets; int32_t total_goals; int32_t found_goals; int32_t total_monsters; std::array monsters_registered; // only for debug int32_t killed_monsters; edict_t *current_entity; // entity running from G_RunFrame int32_t body_que; // dead bodies int32_t power_cubes; // ugly necessity for coop // ROGUE edict_t *disguise_violator; gtime_t disguise_violation_time; int32_t disguise_icon; // [Paril-KEX] // ROGUE int32_t shadow_light_count; // [Sam-KEX] bool is_n64; gtime_t coop_level_restart_time; // restart the level after this time bool instantitems; // instantitems 1 set in worldspawn // N64 goal stuff const char *goals; // nullptr if no goals in world int32_t goal_num; // current relative goal number, increased with each target_goal // offset for the first vwep model, for // skinnum encoding int32_t vwep_offset; // coop health scaling factor; // this percentage of health is added // to the monster's health per player. float coop_health_scaling; // this isn't saved in the save file, but stores // the amount of players currently active in the // level, compared against monsters' individual // scale # int32_t coop_scale_players; // [Paril-KEX] current level entry level_entry_t *entry; // [Paril-KEX] current poi bool valid_poi; vec3_t current_poi; int32_t current_poi_image; int32_t current_poi_stage; edict_t *current_dynamic_poi; vec3_t *poi_points[MAX_SPLIT_PLAYERS]; // temporary storage for POIs in coop // start items const char *start_items; // disable grappling hook bool no_grapple; // saved gravity float gravity; // level is a hub map, and shouldn't be included in EOU stuff bool hub_map; // active health bar entities std::array health_bar_entities; int32_t intermission_server_frame; bool deadly_kill_box; bool story_active; gtime_t next_auto_save; gtime_t next_match_report; }; struct shadow_light_temp_t { shadow_light_data_t data; const char *lightstyletarget = nullptr; }; #include // spawn_temp_t is only used to hold entity field values that // can be set from the editor, but aren't actualy present // in edict_t during gameplay. // defaults can/should be set in the struct. struct spawn_temp_t { // world vars const char *sky; float skyrotate; vec3_t skyaxis; int32_t skyautorotate = 1; const char *nextmap; int32_t lip; int32_t distance; int32_t height; const char *noise; float pausetime; const char *item; const char *gravity; float minyaw; float maxyaw; float minpitch; float maxpitch; shadow_light_temp_t sl; // [Sam-KEX] const char* music; // [Edward-KEX] int instantitems; float radius; // [Paril-KEX] bool hub_map; // [Paril-KEX] const char *achievement; // [Paril-KEX] // [Paril-KEX] const char *goals; // [Paril-KEX] const char *image; int fade_start_dist = 96; int fade_end_dist = 384; const char *start_items; int no_grapple = 0; float health_multiplier = 1.0f; const char *reinforcements; // [Paril-KEX] const char *noise_start, *noise_middle, *noise_end; // [Paril-KEX] int32_t loop_count; // [Paril-KEX] std::unordered_set keys_specified; inline bool was_key_specified(const char *key) const { return keys_specified.find(key) != keys_specified.end(); } }; enum move_state_t { STATE_TOP, STATE_BOTTOM, STATE_UP, STATE_DOWN }; #define DEFINE_DATA_FUNC(ns_lower, ns_upper, returnType, ...) \ using save_##ns_lower##_t = save_data_t #define SAVE_DATA_FUNC(n, ns, returnType, ...) \ using save_##n##_t = save_data_t; \ extern returnType n(__VA_ARGS__); \ static const save_data_list_t save__##n(#n, SAVE_FUNC_##ns, reinterpret_cast(n)); \ auto n DEFINE_DATA_FUNC(moveinfo_endfunc, MOVEINFO_ENDFUNC, void, edict_t *self); #define MOVEINFO_ENDFUNC(n) \ SAVE_DATA_FUNC(n, MOVEINFO_ENDFUNC, void, edict_t *self) DEFINE_DATA_FUNC(moveinfo_blocked, MOVEINFO_BLOCKED, void, edict_t *self, edict_t *other); #define MOVEINFO_BLOCKED(n) \ SAVE_DATA_FUNC(n, MOVEINFO_BLOCKED, void, edict_t *self, edict_t *other) struct moveinfo_t { // fixed data vec3_t start_origin; vec3_t start_angles; vec3_t end_origin; vec3_t end_angles, end_angles_reversed; int32_t sound_start; int32_t sound_middle; int32_t sound_end; float accel; float speed; float decel; float distance; float wait; // state data move_state_t state; bool reversing; vec3_t dir; vec3_t dest; float current_speed; float move_speed; float next_speed; float remaining_distance; float decel_distance; save_moveinfo_endfunc_t endfunc; save_moveinfo_blocked_t blocked; }; struct mframe_t { void (*aifunc)(edict_t *self, float dist) = nullptr; float dist = 0; void (*thinkfunc)(edict_t *self) = nullptr; int32_t lerp_frame = -1; }; // this check only works on windows, and is only // of importance to developers anyways #if defined(_WIN32) && defined(_MSC_VER) #if _MSC_VER >= 1934 #define COMPILE_TIME_MOVE_CHECK #endif #endif struct mmove_t { int32_t firstframe; int32_t lastframe; const mframe_t *frame; void (*endfunc)(edict_t *self); float sidestep_scale; #ifdef COMPILE_TIME_MOVE_CHECK template constexpr mmove_t(int32_t firstframe, int32_t lastframe, const mframe_t (&frames)[N], void (*endfunc)(edict_t *self) = nullptr, float sidestep_scale = 0.0f) : firstframe(firstframe), lastframe(lastframe), frame(frames), endfunc(endfunc), sidestep_scale(sidestep_scale) { if ((lastframe - firstframe + 1) != N) throw std::exception("bad animation frames; check your numbers!"); } #endif }; using save_mmove_t = save_data_t; #ifdef COMPILE_TIME_MOVE_CHECK #define MMOVE_T(n) \ extern const mmove_t n; \ static const save_data_list_t save__##n(#n, SAVE_DATA_MMOVE, &n); \ constexpr mmove_t n #else #define MMOVE_T(n) \ extern const mmove_t n; \ static const save_data_list_t save__##n(#n, SAVE_DATA_MMOVE, &n); \ const mmove_t n #endif DEFINE_DATA_FUNC(monsterinfo_stand, MONSTERINFO_STAND, void, edict_t *self); #define MONSTERINFO_STAND(n) \ SAVE_DATA_FUNC(n, MONSTERINFO_STAND, void, edict_t *self) DEFINE_DATA_FUNC(monsterinfo_idle, MONSTERINFO_IDLE, void, edict_t *self); #define MONSTERINFO_IDLE(n) \ SAVE_DATA_FUNC(n, MONSTERINFO_IDLE, void, edict_t *self) DEFINE_DATA_FUNC(monsterinfo_search, MONSTERINFO_SEARCH, void, edict_t *self); #define MONSTERINFO_SEARCH(n) \ SAVE_DATA_FUNC(n, MONSTERINFO_SEARCH, void, edict_t *self) DEFINE_DATA_FUNC(monsterinfo_walk, MONSTERINFO_WALK, void, edict_t *self); #define MONSTERINFO_WALK(n) \ SAVE_DATA_FUNC(n, MONSTERINFO_WALK, void, edict_t *self) DEFINE_DATA_FUNC(monsterinfo_run, MONSTERINFO_RUN, void, edict_t *self); #define MONSTERINFO_RUN(n) \ SAVE_DATA_FUNC(n, MONSTERINFO_RUN, void, edict_t *self) DEFINE_DATA_FUNC(monsterinfo_dodge, MONSTERINFO_DODGE, void, edict_t *self, edict_t *attacker, gtime_t eta, trace_t *tr, bool gravity); #define MONSTERINFO_DODGE(n) \ SAVE_DATA_FUNC(n, MONSTERINFO_DODGE, void, edict_t *self, edict_t *attacker, gtime_t eta, trace_t *tr, bool gravity) DEFINE_DATA_FUNC(monsterinfo_attack, MONSTERINFO_ATTACK, void, edict_t *self); #define MONSTERINFO_ATTACK(n) \ SAVE_DATA_FUNC(n, MONSTERINFO_ATTACK, void, edict_t *self) DEFINE_DATA_FUNC(monsterinfo_melee, MONSTERINFO_MELEE, void, edict_t *self); #define MONSTERINFO_MELEE(n) \ SAVE_DATA_FUNC(n, MONSTERINFO_MELEE, void, edict_t *self) DEFINE_DATA_FUNC(monsterinfo_sight, MONSTERINFO_SIGHT, void, edict_t *self, edict_t *other); #define MONSTERINFO_SIGHT(n) \ SAVE_DATA_FUNC(n, MONSTERINFO_SIGHT, void, edict_t *self, edict_t *other) DEFINE_DATA_FUNC(monsterinfo_checkattack, MONSTERINFO_CHECKATTACK, bool, edict_t *self); #define MONSTERINFO_CHECKATTACK(n) \ SAVE_DATA_FUNC(n, MONSTERINFO_CHECKATTACK, bool, edict_t *self) DEFINE_DATA_FUNC(monsterinfo_setskin, MONSTERINFO_SETSKIN, void, edict_t *self); #define MONSTERINFO_SETSKIN(n) \ SAVE_DATA_FUNC(n, MONSTERINFO_SETSKIN, void, edict_t *self) DEFINE_DATA_FUNC(monsterinfo_blocked, MONSTERINFO_BLOCKED, bool, edict_t *self, float dist); #define MONSTERINFO_BLOCKED(n) \ SAVE_DATA_FUNC(n, MONSTERINFO_BLOCKED, bool, edict_t *self, float dist) DEFINE_DATA_FUNC(monsterinfo_physicschange, MONSTERINFO_PHYSCHANGED, void, edict_t *self); #define MONSTERINFO_PHYSCHANGED(n) \ SAVE_DATA_FUNC(n, MONSTERINFO_PHYSCHANGED, void, edict_t *self) DEFINE_DATA_FUNC(monsterinfo_duck, MONSTERINFO_DUCK, bool, edict_t *self, gtime_t eta); #define MONSTERINFO_DUCK(n) \ SAVE_DATA_FUNC(n, MONSTERINFO_DUCK, bool, edict_t *self, gtime_t eta) DEFINE_DATA_FUNC(monsterinfo_unduck, MONSTERINFO_UNDUCK, void, edict_t *self); #define MONSTERINFO_UNDUCK(n) \ SAVE_DATA_FUNC(n, MONSTERINFO_UNDUCK, void, edict_t *self) DEFINE_DATA_FUNC(monsterinfo_sidestep, MONSTERINFO_SIDESTEP, bool, edict_t *self); #define MONSTERINFO_SIDESTEP(n) \ SAVE_DATA_FUNC(n, MONSTERINFO_SIDESTEP, bool, edict_t *self) // combat styles, for navigation enum combat_style_t { COMBAT_UNKNOWN, // automatically choose based on attack functions COMBAT_MELEE, // should attempt to get up close for melee COMBAT_MIXED, // has mixed melee/ranged; runs to get up close if far enough away COMBAT_RANGED // don't bother pathing if we can see the player }; struct reinforcement_t { const char *classname; int32_t strength; vec3_t mins, maxs; }; struct reinforcement_list_t { reinforcement_t *reinforcements; uint32_t num_reinforcements; }; constexpr size_t MAX_REINFORCEMENTS = 5; // max number of spawns we can do at once. constexpr gtime_t HOLD_FOREVER = gtime_t::from_ms(std::numeric_limits::max()); struct monsterinfo_t { // [Paril-KEX] allow some moves to be done instantaneously, but // others can wait the full frame. // NB: always use `M_SetAnimation` as it handles edge cases. save_mmove_t active_move, next_move; monster_ai_flags_t aiflags; // PGM - unsigned, since we're close to the max int32_t nextframe; // if next_move is set, this is ignored until a frame is ran float scale; save_monsterinfo_stand_t stand; save_monsterinfo_idle_t idle; save_monsterinfo_search_t search; save_monsterinfo_walk_t walk; save_monsterinfo_run_t run; save_monsterinfo_dodge_t dodge; save_monsterinfo_attack_t attack; save_monsterinfo_melee_t melee; save_monsterinfo_sight_t sight; save_monsterinfo_checkattack_t checkattack; save_monsterinfo_setskin_t setskin; save_monsterinfo_physicschange_t physics_change; gtime_t pausetime; gtime_t attack_finished; gtime_t fire_wait; vec3_t saved_goal; gtime_t search_time; gtime_t trail_time; vec3_t last_sighting; monster_attack_state_t attack_state; bool lefty; gtime_t idle_time; int32_t linkcount; item_id_t power_armor_type; int32_t power_armor_power; // for monster revive item_id_t initial_power_armor_type; int32_t max_power_armor_power; int32_t weapon_sound, engine_sound; // ROGUE save_monsterinfo_blocked_t blocked; gtime_t last_hint_time; // last time the monster checked for hintpaths. edict_t *goal_hint; // which hint_path we're trying to get to int32_t medicTries; edict_t *badMedic1, *badMedic2; // these medics have declared this monster "unhealable" edict_t *healer; // this is who is healing this monster save_monsterinfo_duck_t duck; save_monsterinfo_unduck_t unduck; save_monsterinfo_sidestep_t sidestep; float base_height; gtime_t next_duck_time; gtime_t duck_wait_time; edict_t *last_player_enemy; // blindfire stuff .. the boolean says whether the monster will do it, and blind_fire_time is the timing // (set in the monster) of the next shot bool blindfire; // will the monster blindfire? bool can_jump; // will the monster jump? bool had_visibility; // Paril: used for blindfire float drop_height, jump_height; gtime_t blind_fire_delay; vec3_t blind_fire_target; // used by the spawners to not spawn too much and keep track of #s of monsters spawned int32_t monster_slots; // nb: for spawned monsters, this is how many slots we took from our commander int32_t monster_used; edict_t *commander; // powerup timers, used by widow, our friend gtime_t quad_time; gtime_t invincible_time; gtime_t double_time; // ROGUE // Paril gtime_t surprise_time; item_id_t armor_type; int32_t armor_power; bool close_sight_tripped; gtime_t melee_debounce_time; // don't melee until this time has passed gtime_t strafe_check_time; // time until we should reconsider strafing int32_t base_health; // health that we had on spawn, before any co-op adjustments int32_t health_scaling; // number of players we've been scaled up to gtime_t next_move_time; // high tick rate gtime_t bad_move_time; // don't try straight moves until this is over gtime_t bump_time; // don't slide against walls for a bit gtime_t random_change_time; // high tickrate gtime_t path_blocked_counter; // break out of paths when > a certain time gtime_t path_wait_time; // don't try nav nodes until this is over PathInfo nav_path; // if AI_PATHING, this is where we are trying to reach gtime_t nav_path_cache_time; // cache nav_path result for this much time combat_style_t combat_style; // pathing style edict_t *damage_attacker; edict_t *damage_inflictor; int32_t damage_blood, damage_knockback; vec3_t damage_from; mod_t damage_mod; // alternate flying mechanics float fly_max_distance, fly_min_distance; // how far we should try to stay float fly_acceleration; // accel/decel speed float fly_speed; // max speed from flying vec3_t fly_ideal_position; // ideally where we want to end up to hover, relative to our target if not pinned gtime_t fly_position_time; // if <= level.time, we can try changing positions bool fly_buzzard, fly_above; // orbit around all sides of their enemy, not just the sides bool fly_pinned; // whether we're currently pinned to ideal position (made absolute) bool fly_thrusters; // slightly different flight mechanics, for melee attacks gtime_t fly_recovery_time; // time to try a new dir to get away from hazards vec3_t fly_recovery_dir; gtime_t checkattack_time; int32_t start_frame; gtime_t dodge_time; int32_t move_block_counter; gtime_t move_block_change_time; gtime_t react_to_damage_time; reinforcement_list_t reinforcements; std::array chosen_reinforcements; // readied for spawn; 255 is value for none gtime_t jump_time; // NOTE: if adding new elements, make sure to add them // in g_save.cpp too! }; // non-monsterinfo save stuff using save_prethink_t = save_data_t; #define PRETHINK(n) \ void n(edict_t *self); \ static const save_data_list_t save__##n(#n, SAVE_FUNC_PRETHINK, reinterpret_cast(n)); \ auto n using save_think_t = save_data_t; #define THINK(n) \ void n(edict_t *self); \ static const save_data_list_t save__##n(#n, SAVE_FUNC_THINK, reinterpret_cast(n)); \ auto n using save_touch_t = save_data_t; #define TOUCH(n) \ void n(edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self); \ static const save_data_list_t save__##n(#n, SAVE_FUNC_TOUCH, reinterpret_cast(n)); \ auto n using save_use_t = save_data_t; #define USE(n) \ void n(edict_t *self, edict_t *other, edict_t *activator); \ static const save_data_list_t save__##n(#n, SAVE_FUNC_USE, reinterpret_cast(n)); \ auto n using save_pain_t = save_data_t; #define PAIN(n) \ void n(edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod); \ static const save_data_list_t save__##n(#n, SAVE_FUNC_PAIN, reinterpret_cast(n)); \ auto n using save_die_t = save_data_t; #define DIE(n) \ void n(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod); \ static const save_data_list_t save__##n(#n, SAVE_FUNC_DIE, reinterpret_cast(n)); \ auto n // ROGUE // this determines how long to wait after a duck to duck again. // if we finish a duck-up, this gets cut in half. constexpr gtime_t DUCK_INTERVAL = 5000_ms; // ROGUE extern game_locals_t game; extern level_locals_t level; extern game_export_t globals; extern spawn_temp_t st; extern int sm_meat_index; extern int snd_fry; extern edict_t *g_edicts; #include extern std::mt19937 mt_rand; // uniform float [0, 1) [[nodiscard]] inline float frandom() { return std::uniform_real_distribution()(mt_rand); } // uniform float [min_inclusive, max_exclusive) [[nodiscard]] inline float frandom(float min_inclusive, float max_exclusive) { return std::uniform_real_distribution(min_inclusive, max_exclusive)(mt_rand); } // uniform float [0, max_exclusive) [[nodiscard]] inline float frandom(float max_exclusive) { return std::uniform_real_distribution(0, max_exclusive)(mt_rand); } // uniform time [min_inclusive, max_exclusive) [[nodiscard]] inline gtime_t random_time(gtime_t min_inclusive, gtime_t max_exclusive) { return gtime_t::from_ms(std::uniform_int_distribution(min_inclusive.milliseconds(), max_exclusive.milliseconds())(mt_rand)); } // uniform time [0, max_exclusive) [[nodiscard]] inline gtime_t random_time(gtime_t max_exclusive) { return gtime_t::from_ms(std::uniform_int_distribution(0, max_exclusive.milliseconds())(mt_rand)); } // uniform float [-1, 1) // note: closed on min but not max // to match vanilla behavior [[nodiscard]] inline float crandom() { return std::uniform_real_distribution(-1.f, 1.f)(mt_rand); } // uniform float (-1, 1) [[nodiscard]] inline float crandom_open() { return std::uniform_real_distribution(std::nextafterf(-1.f, 0.f), 1.f)(mt_rand); } // raw unsigned int32 value from random [[nodiscard]] inline uint32_t irandom() { return mt_rand(); } // uniform int [min, max) // always returns min if min == (max - 1) // undefined behavior if min > (max - 1) [[nodiscard]] inline int32_t irandom(int32_t min_inclusive, int32_t max_exclusive) { if (min_inclusive == max_exclusive - 1) return min_inclusive; return std::uniform_int_distribution(min_inclusive, max_exclusive - 1)(mt_rand); } // uniform int [0, max) // always returns 0 if max <= 0 // note for Q2 code: // - to fix rand()%x, do irandom(x) // - to fix rand()&x, do irandom(x + 1) [[nodiscard]] inline int32_t irandom(int32_t max_exclusive) { if (max_exclusive <= 0) return 0; return irandom(0, max_exclusive); } // uniform random index from given container template [[nodiscard]] inline int32_t random_index(const T &container) { return irandom(std::size(container)); } // uniform random element from given container template [[nodiscard]] inline auto random_element(T &container) -> decltype(*std::begin(container)) { return *(std::begin(container) + random_index(container)); } // flip a coin [[nodiscard]]inline bool brandom() { return irandom(2) == 0; } extern cvar_t *deathmatch; extern cvar_t *coop; extern cvar_t *skill; extern cvar_t *fraglimit; extern cvar_t *timelimit; // ZOID extern cvar_t *capturelimit; extern cvar_t *g_quick_weapon_switch; extern cvar_t *g_instant_weapon_switch; // ZOID extern cvar_t *password; extern cvar_t *spectator_password; extern cvar_t *needpass; extern cvar_t *g_select_empty; extern cvar_t *sv_dedicated; extern cvar_t *filterban; extern cvar_t *sv_gravity; extern cvar_t *sv_maxvelocity; extern cvar_t *gun_x, *gun_y, *gun_z; extern cvar_t *sv_rollspeed; extern cvar_t *sv_rollangle; extern cvar_t *run_pitch; extern cvar_t *run_roll; extern cvar_t *bob_up; extern cvar_t *bob_pitch; extern cvar_t *bob_roll; extern cvar_t *sv_cheats; extern cvar_t *g_debug_monster_paths; extern cvar_t *g_debug_monster_kills; extern cvar_t *maxspectators; extern cvar_t *bot_debug_follow_actor; extern cvar_t *bot_debug_move_to_point; extern cvar_t *flood_msgs; extern cvar_t *flood_persecond; extern cvar_t *flood_waitdelay; extern cvar_t *sv_maplist; extern cvar_t *g_skipViewModifiers; extern cvar_t *sv_stopspeed; // PGM - this was a define in g_phys.c extern cvar_t *g_strict_saves; extern cvar_t *g_coop_health_scaling; extern cvar_t *g_weapon_respawn_time; extern cvar_t* g_no_health; extern cvar_t* g_no_items; extern cvar_t* g_dm_weapons_stay; extern cvar_t* g_dm_no_fall_damage; extern cvar_t* g_dm_instant_items; extern cvar_t* g_dm_same_level; extern cvar_t* g_friendly_fire; extern cvar_t* g_dm_force_respawn; extern cvar_t* g_dm_force_respawn_time; extern cvar_t* g_dm_spawn_farthest; extern cvar_t* g_no_armor; extern cvar_t* g_dm_allow_exit; extern cvar_t* g_infinite_ammo; extern cvar_t* g_dm_no_quad_drop; extern cvar_t* g_dm_no_quadfire_drop; extern cvar_t* g_no_mines; extern cvar_t* g_dm_no_stack_double; extern cvar_t* g_no_nukes; extern cvar_t* g_no_spheres; extern cvar_t* g_teamplay_armor_protect; extern cvar_t* g_allow_techs; extern cvar_t* g_start_items; extern cvar_t* g_map_list; extern cvar_t* g_map_list_shuffle; extern cvar_t *g_lag_compensation; // ROGUE extern cvar_t *gamerules; extern cvar_t *huntercam; extern cvar_t *g_dm_strong_mines; extern cvar_t *g_dm_random_items; // ROGUE // [Kex] extern cvar_t* g_instagib; extern cvar_t* g_coop_player_collision; extern cvar_t* g_coop_squad_respawn; extern cvar_t* g_coop_enable_lives; extern cvar_t* g_coop_num_lives; extern cvar_t* g_coop_instanced_items; extern cvar_t* g_allow_grapple; extern cvar_t* g_grapple_fly_speed; extern cvar_t* g_grapple_pull_speed; extern cvar_t* g_grapple_damage; extern cvar_t *sv_airaccelerate; extern cvar_t *g_damage_scale; extern cvar_t *g_disable_player_collision; extern cvar_t *ai_damage_scale; extern cvar_t *ai_model_scale; extern cvar_t *ai_allow_dm_spawn; extern cvar_t *ai_movement_disabled; #define world (&g_edicts[0]) uint32_t GetUnicastKey(); // item spawnflags constexpr spawnflags_t SPAWNFLAG_ITEM_TRIGGER_SPAWN = 0x00000001_spawnflag; constexpr spawnflags_t SPAWNFLAG_ITEM_NO_TOUCH = 0x00000002_spawnflag; constexpr spawnflags_t SPAWNFLAG_ITEM_TOSS_SPAWN = 0x00000004_spawnflag; constexpr spawnflags_t SPAWNFLAG_ITEM_MAX = 0x00000008_spawnflag; // 8 bits reserved for editor flags & power cube bits // (see SPAWNFLAG_NOT_EASY above) constexpr spawnflags_t SPAWNFLAG_ITEM_DROPPED = 0x00010000_spawnflag; constexpr spawnflags_t SPAWNFLAG_ITEM_DROPPED_PLAYER = 0x00020000_spawnflag; constexpr spawnflags_t SPAWNFLAG_ITEM_TARGETS_USED = 0x00040000_spawnflag; extern gitem_t itemlist[IT_TOTAL]; // // g_cmds.c // bool CheckFlood(edict_t *ent); void Cmd_Help_f(edict_t *ent); void Cmd_Score_f(edict_t *ent); // // g_items.c // void PrecacheItem(gitem_t *it); void InitItems(); void SetItemNames(); gitem_t *FindItem(const char *pickup_name); gitem_t *FindItemByClassname(const char *classname); edict_t *Drop_Item(edict_t *ent, gitem_t *item); void SetRespawn(edict_t *ent, gtime_t delay, bool hide_self = true); void ChangeWeapon(edict_t *ent); void SpawnItem(edict_t *ent, gitem_t *item); void Think_Weapon(edict_t *ent); item_id_t ArmorIndex(edict_t *ent); item_id_t PowerArmorType(edict_t *ent); gitem_t *GetItemByIndex(item_id_t index); gitem_t *GetItemByAmmo(ammo_t ammo); gitem_t *GetItemByPowerup(powerup_t powerup); bool Add_Ammo(edict_t *ent, gitem_t *item, int count); void G_CheckPowerArmor(edict_t *ent); void Touch_Item(edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self); void droptofloor(edict_t *ent); void P_ToggleFlashlight(edict_t *ent, bool state); bool Entity_IsVisibleToPlayer(edict_t* ent, edict_t* player); void Compass_Update(edict_t *ent, bool first); // // g_utils.c // bool KillBox(edict_t *ent, bool from_spawning, mod_id_t mod = MOD_TELEFRAG, bool bsp_clipping = true); edict_t *G_Find(edict_t *from, std::function matcher); // utility template for getting the type of a field template struct member_object_type { }; template struct member_object_type { using type = T1; }; template using member_object_type_t = typename member_object_type>::type; template edict_t *G_FindByString(edict_t *from, const std::string_view &value) { static_assert(std::is_same_v, const char *>, "can only use string member functions"); return G_Find(from, [&](edict_t *e) { return e->*M && strlen(e->*M) == value.length() && !Q_strncasecmp(e->*M, value.data(), value.length()); }); } edict_t *findradius(edict_t *from, const vec3_t &org, float rad); edict_t *G_PickTarget(const char *targetname); void G_UseTargets(edict_t *ent, edict_t *activator); void G_PrintActivationMessage(edict_t *ent, edict_t *activator, bool coop_global); void G_SetMovedir(vec3_t &angles, vec3_t &movedir); void G_InitEdict(edict_t *e); edict_t *G_Spawn(); void G_FreeEdict(edict_t *e); void G_TouchTriggers(edict_t *ent); void G_TouchProjectiles(edict_t *ent, vec3_t previous_origin); char *G_CopyString(const char *in, int32_t tag); char *G_CopyString(const char *in, size_t len, int32_t tag); // ROGUE edict_t *findradius2(edict_t *from, const vec3_t &org, float rad); // ROGUE void G_PlayerNotifyGoal(edict_t *player); // // g_spawn.c // void ED_CallSpawn(edict_t *ent); char *ED_NewString(char *string); // // g_target.c // void target_laser_think(edict_t *self); void target_laser_off(edict_t *self); constexpr spawnflags_t SPAWNFLAG_LASER_ON = 0x0001_spawnflag; constexpr spawnflags_t SPAWNFLAG_LASER_RED = 0x0002_spawnflag; constexpr spawnflags_t SPAWNFLAG_LASER_GREEN = 0x0004_spawnflag; constexpr spawnflags_t SPAWNFLAG_LASER_BLUE = 0x0008_spawnflag; constexpr spawnflags_t SPAWNFLAG_LASER_YELLOW = 0x0010_spawnflag; constexpr spawnflags_t SPAWNFLAG_LASER_ORANGE = 0x0020_spawnflag; constexpr spawnflags_t SPAWNFLAG_LASER_FAT = 0x0040_spawnflag; constexpr spawnflags_t SPAWNFLAG_LASER_ZAP = 0x80000000_spawnflag; constexpr spawnflags_t SPAWNFLAG_LASER_LIGHTNING = 0x10000_spawnflag; constexpr spawnflags_t SPAWNFLAG_HEALTHBAR_PVS_ONLY = 1_spawnflag; // damage flags enum damageflags_t { DAMAGE_NONE = 0, // no damage flags DAMAGE_RADIUS = 0x00000001, // damage was indirect DAMAGE_NO_ARMOR = 0x00000002, // armour does not protect from this damage DAMAGE_ENERGY = 0x00000004, // damage is from an energy based weapon DAMAGE_NO_KNOCKBACK = 0x00000008, // do not affect velocity, just view angles DAMAGE_BULLET = 0x00000010, // damage is from a bullet (used for ricochets) DAMAGE_NO_PROTECTION = 0x00000020, // armor, shields, invulnerability, and godmode have no effect // ROGUE DAMAGE_DESTROY_ARMOR = 0x00000040, // damage is done to armor and health. DAMAGE_NO_REG_ARMOR = 0x00000080, // damage skips regular armor DAMAGE_NO_POWER_ARMOR = 0x00000100,// damage skips power armor // ROGUE DAMAGE_NO_INDICATOR = 0x00000200 // for clients: no damage indicators }; MAKE_ENUM_BITFLAGS(damageflags_t); // // g_combat.c // bool OnSameTeam(edict_t *ent1, edict_t *ent2); bool CanDamage(edict_t *targ, edict_t *inflictor); bool CheckTeamDamage(edict_t *targ, edict_t *attacker); void T_Damage(edict_t *targ, edict_t *inflictor, edict_t *attacker, const vec3_t &dir, const vec3_t &point, const vec3_t &normal, int damage, int knockback, damageflags_t dflags, mod_t mod); void T_RadiusDamage(edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, damageflags_t dflags, mod_t mod); void Killed(edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, mod_t mod); // ROGUE void T_RadiusNukeDamage(edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, mod_t mod); void T_RadiusClassDamage(edict_t *inflictor, edict_t *attacker, float damage, char *ignoreClass, float radius, mod_t mod); void cleanupHealTarget(edict_t *ent); // ROGUE constexpr int32_t DEFAULT_BULLET_HSPREAD = 300; constexpr int32_t DEFAULT_BULLET_VSPREAD = 500; constexpr int32_t DEFAULT_SHOTGUN_HSPREAD = 1000; constexpr int32_t DEFAULT_SHOTGUN_VSPREAD = 500; constexpr int32_t DEFAULT_DEATHMATCH_SHOTGUN_COUNT = 12; constexpr int32_t DEFAULT_SHOTGUN_COUNT = 12; constexpr int32_t DEFAULT_SSHOTGUN_COUNT = 20; // // g_func.c // void train_use(edict_t *self, edict_t *other, edict_t *activator); void func_train_find(edict_t *self); edict_t *plat_spawn_inside_trigger(edict_t *ent); void Move_Calc(edict_t *ent, const vec3_t &dest, void(*endfunc)(edict_t *self)); void G_SetMoveinfoSounds(edict_t *self, const char *default_start, const char *default_mid, const char *default_end); constexpr spawnflags_t SPAWNFLAG_TRAIN_START_ON = 1_spawnflag; constexpr spawnflags_t SPAWNFLAG_WATER_SMART = 2_spawnflag; constexpr spawnflags_t SPAWNFLAG_TRAIN_MOVE_TEAMCHAIN = 8_spawnflag; constexpr spawnflags_t SPAWNFLAG_DOOR_REVERSE = 2_spawnflag; // // g_monster.c // void monster_muzzleflash(edict_t *self, const vec3_t &start, monster_muzzleflash_id_t id); void monster_fire_bullet(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int kick, int hspread, int vspread, monster_muzzleflash_id_t flashtype); void monster_fire_shotgun(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int kick, int hspread, int vspread, int count, monster_muzzleflash_id_t flashtype); void monster_fire_blaster(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, monster_muzzleflash_id_t flashtype, effects_t effect); void monster_fire_flechette(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, monster_muzzleflash_id_t flashtype); void monster_fire_grenade(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed, monster_muzzleflash_id_t flashtype, float right_adjust, float up_adjust); void monster_fire_rocket(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, monster_muzzleflash_id_t flashtype); void monster_fire_railgun(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int kick, monster_muzzleflash_id_t flashtype); void monster_fire_bfg(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed, int kick, float damage_radius, monster_muzzleflash_id_t flashtype); bool M_CheckClearShot(edict_t *self, const vec3_t &offset); bool M_CheckClearShot(edict_t *self, const vec3_t &offset, vec3_t &start); vec3_t M_ProjectFlashSource(edict_t *self, const vec3_t &offset, const vec3_t &forward, const vec3_t &right); bool M_droptofloor_generic(vec3_t &origin, const vec3_t &mins, const vec3_t &maxs, bool ceiling, edict_t *ignore, contents_t mask, bool allow_partial); bool M_droptofloor(edict_t *ent); void monster_think(edict_t *self); void monster_dead_think(edict_t *self); void monster_dead(edict_t *self); void walkmonster_start(edict_t *self); void swimmonster_start(edict_t *self); void flymonster_start(edict_t *self); void monster_death_use(edict_t *self); void M_CatagorizePosition(edict_t *self, const vec3_t &in_point, water_level_t &waterlevel, contents_t &watertype); void M_WorldEffects(edict_t *ent); bool M_CheckAttack(edict_t *self); void M_CheckGround(edict_t *ent, contents_t mask); void monster_use(edict_t *self, edict_t *other, edict_t *activator); void M_ProcessPain(edict_t *e); bool M_ShouldReactToPain(edict_t *self, const mod_t &mod); void M_SetAnimation(edict_t *self, const save_mmove_t &move, bool instant = true); bool M_AllowSpawn( edict_t * self ); // Paril: used in N64. causes them to be mad at the player // regardless of circumstance. constexpr size_t HACKFLAG_ATTACK_PLAYER = 1; // used in N64, appears to change their behavior for the end scene. constexpr size_t HACKFLAG_END_CUTSCENE = 4; bool monster_start(edict_t *self); void monster_start_go(edict_t *self); // RAFAEL void monster_fire_ionripper(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, monster_muzzleflash_id_t flashtype, effects_t effect); void monster_fire_heat(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, monster_muzzleflash_id_t flashtype, float lerp_factor); void monster_fire_dabeam(edict_t *self, int damage, bool secondary, void(*update_func)(edict_t *self)); void dabeam_update(edict_t *self, bool damage); void monster_fire_blueblaster(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, monster_muzzleflash_id_t flashtype, effects_t effect); void G_Monster_CheckCoopHealthScaling(); // RAFAEL // ROGUE void monster_fire_blaster2(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, monster_muzzleflash_id_t flashtype, effects_t effect); void monster_fire_tracker(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, edict_t *enemy, monster_muzzleflash_id_t flashtype); void monster_fire_heatbeam(edict_t *self, const vec3_t &start, const vec3_t &dir, const vec3_t &offset, int damage, int kick, monster_muzzleflash_id_t flashtype); void stationarymonster_start(edict_t *self); void monster_done_dodge(edict_t *self); // ROGUE stuck_result_t G_FixStuckObject(edict_t *self, vec3_t check); // this is for the count of monsters int32_t M_SlotsLeft(edict_t *self); // shared with monsters constexpr spawnflags_t SPAWNFLAG_MONSTER_AMBUSH = 1_spawnflag; constexpr spawnflags_t SPAWNFLAG_MONSTER_TRIGGER_SPAWN = 2_spawnflag; constexpr spawnflags_t SPAWNFLAG_MONSTER_DEAD = 16_spawnflag_bit; constexpr spawnflags_t SPAWNFLAG_MONSTER_SUPER_STEP = 17_spawnflag_bit; constexpr spawnflags_t SPAWNFLAG_MONSTER_NO_DROP = 18_spawnflag_bit; constexpr spawnflags_t SPAWNFLAG_MONSTER_SCENIC = 19_spawnflag_bit; // fixbot spawnflags constexpr spawnflags_t SPAWNFLAG_FIXBOT_FIXIT = 4_spawnflag; constexpr spawnflags_t SPAWNFLAG_FIXBOT_TAKEOFF = 8_spawnflag; constexpr spawnflags_t SPAWNFLAG_FIXBOT_LANDING = 16_spawnflag; constexpr spawnflags_t SPAWNFLAG_FIXBOT_WORKING = 32_spawnflag; // // g_misc.c // void ThrowClientHead(edict_t *self, int damage); void gib_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod); edict_t *ThrowGib(edict_t *self, const char *gibname, int damage, gib_type_t type, float scale); void BecomeExplosion1(edict_t *self); void misc_viper_use(edict_t *self, edict_t *other, edict_t *activator); void misc_strogg_ship_use(edict_t *self, edict_t *other, edict_t *activator); void VelocityForDamage(int damage, vec3_t &v); void ClipGibVelocity(edict_t *ent); constexpr spawnflags_t SPAWNFLAG_PATH_CORNER_TELEPORT = 1_spawnflag; constexpr spawnflags_t SPAWNFLAG_POINT_COMBAT_HOLD = 1_spawnflag; // max chars for a clock string; // " 0:00:00" is the longest string possible // plus null terminator. constexpr size_t CLOCK_MESSAGE_SIZE = 9; // // g_ai.c // edict_t *AI_GetSightClient(edict_t *self); void ai_stand(edict_t *self, float dist); void ai_move(edict_t *self, float dist); void ai_walk(edict_t *self, float dist); void ai_turn(edict_t *self, float dist); void ai_run(edict_t *self, float dist); void ai_charge(edict_t *self, float dist); constexpr float RANGE_MELEE = 20; // bboxes basically touching constexpr float RANGE_NEAR = 440; constexpr float RANGE_MID = 940; // [Paril-KEX] adjusted to return an actual distance, measured // in a way that is consistent regardless of what is fighting what float range_to(edict_t *self, edict_t *other); void FoundTarget(edict_t *self); void HuntTarget(edict_t *self, bool animate_state = true); bool infront(edict_t *self, edict_t *other); bool visible(edict_t *self, edict_t *other, bool through_glass = true); bool FacingIdeal(edict_t *self); // // g_weapon.c // bool fire_hit(edict_t *self, vec3_t aim, int damage, int kick); void fire_bullet(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int kick, int hspread, int vspread, mod_t mod); void fire_shotgun(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int kick, int hspread, int vspread, int count, mod_t mod); void blaster_touch(edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self); void fire_blaster(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed, effects_t effect, mod_t mod); void Grenade_Explode(edict_t *ent); void fire_grenade(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed, gtime_t timer, float damage_radius, float right_adjust, float up_adjust, bool monster); void fire_grenade2(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed, gtime_t timer, float damage_radius, bool held); void rocket_touch(edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self); edict_t *fire_rocket(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, float damage_radius, int radius_damage); void fire_rail(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int kick); void fire_bfg(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, float damage_radius); // RAFAEL void fire_ionripper(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed, effects_t effect); void fire_heat(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, float damage_radius, int radius_damage, float turn_fraction); void fire_blueblaster(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed, effects_t effect); void fire_plasma(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, float damage_radius, int radius_damage); void fire_trap(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int speed); // RAFAEL void fire_disintegrator(edict_t *self, const vec3_t &start, const vec3_t &dir, int speed); vec3_t P_CurrentKickAngles(edict_t *ent); vec3_t P_CurrentKickOrigin(edict_t *ent); void P_AddWeaponKick(edict_t *ent, const vec3_t &origin, const vec3_t &angles); // we won't ever pierce more than this many entities for a single trace. constexpr size_t MAX_PIERCE = 16; // base class for pierce args; this stores // the stuff we are piercing. struct pierce_args_t { // stuff we pierced std::array pierced; std::array pierce_solidities; size_t num_pierced = 0; // the last trace that was done, when piercing stopped trace_t tr; // mark entity as pierced inline bool mark(edict_t *ent); // restore entities' previous solidities inline void restore(); // we hit an entity; return false to stop the piercing. // you can adjust the mask for the re-trace (for water, etc). virtual bool hit(contents_t &mask, vec3_t &end) = 0; virtual ~pierce_args_t() { restore(); } }; void pierce_trace(const vec3_t &start, const vec3_t &end, edict_t *ignore, pierce_args_t &pierce, contents_t mask); // // g_ptrail.c // void PlayerTrail_Add(edict_t *player); void PlayerTrail_Destroy(edict_t *player); edict_t *PlayerTrail_Pick(edict_t *self, bool next); // // g_client.c // constexpr spawnflags_t SPAWNFLAG_CHANGELEVEL_CLEAR_INVENTORY = 8_spawnflag; constexpr spawnflags_t SPAWNFLAG_CHANGELEVEL_NO_END_OF_UNIT = 16_spawnflag; constexpr spawnflags_t SPAWNFLAG_CHANGELEVEL_FADE_OUT = 32_spawnflag; constexpr spawnflags_t SPAWNFLAG_CHANGELEVEL_IMMEDIATE_LEAVE = 64_spawnflag; void respawn(edict_t *ent); void BeginIntermission(edict_t *targ); void PutClientInServer(edict_t *ent); void InitClientPersistant(edict_t *ent, gclient_t *client); void InitClientResp(gclient_t *client); void InitBodyQue(); void ClientBeginServerFrame(edict_t *ent); void ClientUserinfoChanged(edict_t *ent, const char *userinfo); void P_AssignClientSkinnum(edict_t *ent); void P_ForceFogTransition(edict_t *ent, bool instant); void P_SendLevelPOI(edict_t *ent); unsigned int P_GetLobbyUserNum( const edict_t * player ); void G_UpdateLevelEntry(); void G_EndOfUnitMessage(); bool SelectSpawnPoint(edict_t *ent, vec3_t &origin, vec3_t &angles, bool force_spawn, bool &landmark); struct select_spawn_result_t { edict_t *spot; bool any_valid = false; // set if a spawn point was found, even if it was taken }; select_spawn_result_t SelectDeathmatchSpawnPoint(bool farthest, bool force_spawn, bool fallback_to_ctf_or_start); void G_PostRespawn(edict_t *self); // // g_player.c // void player_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod); // // g_svcmds.c // void ServerCommand(); bool SV_FilterPacket(const char *from); // // p_view.c // void ClientEndServerFrame(edict_t *ent); void G_LagCompensate(edict_t *from_player, const vec3_t &start, const vec3_t &dir); void G_UnLagCompensate(); // // p_hud.c // void MoveClientToIntermission(edict_t *client); void G_SetStats(edict_t *ent); void G_SetCoopStats(edict_t *ent); void G_SetSpectatorStats(edict_t *ent); void G_CheckChaseStats(edict_t *ent); void ValidateSelectedItem(edict_t *ent); void DeathmatchScoreboardMessage(edict_t *client, edict_t *killer); void G_ReportMatchDetails(bool is_end); // // p_weapon.c // void PlayerNoise(edict_t *who, const vec3_t &where, player_noise_t type); void P_ProjectSource(edict_t *ent, const vec3_t &angles, vec3_t distance, vec3_t &result_start, vec3_t &result_dir); void NoAmmoWeaponChange(edict_t *ent, bool sound); void G_RemoveAmmo(edict_t *ent); void G_RemoveAmmo(edict_t *ent, int32_t quantity); void Weapon_Generic(edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, const int *pause_frames, const int *fire_frames, void (*fire)(edict_t *ent)); void Weapon_Repeating(edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, const int *pause_frames, void (*fire)(edict_t *ent)); void Throw_Generic(edict_t *ent, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_PRIME_SOUND, const char *prime_sound, int FRAME_THROW_HOLD, int FRAME_THROW_FIRE, const int *pause_frames, int EXPLODE, const char *primed_sound, void (*fire)(edict_t *ent, bool held), bool extra_idle_frame); byte P_DamageModifier(edict_t *ent); bool G_CheckInfiniteAmmo(gitem_t *item); void Weapon_PowerupSound(edict_t *ent); constexpr gtime_t GRENADE_TIMER = 3_sec; constexpr float GRENADE_MINSPEED = 400.f; constexpr float GRENADE_MAXSPEED = 800.f; extern bool is_quad; // RAFAEL extern bool is_quadfire; // RAFAEL extern player_muzzle_t is_silenced; // ROGUE extern byte damage_multiplier; // ROGUE // // m_move.c // bool M_CheckBottom_Fast_Generic(const vec3_t &absmins, const vec3_t &absmaxs, bool ceiling); bool M_CheckBottom_Slow_Generic(const vec3_t &origin, const vec3_t &absmins, const vec3_t &absmaxs, edict_t *ignore, contents_t mask, bool ceiling, bool allow_any_step_height); bool M_CheckBottom(edict_t *ent); bool SV_CloseEnough(edict_t *ent, edict_t *goal, float dist); bool M_walkmove(edict_t *ent, float yaw, float dist); void M_MoveToGoal(edict_t *ent, float dist); void M_ChangeYaw(edict_t *ent); bool ai_check_move(edict_t *self, float dist); // // g_phys.c // constexpr float sv_friction = 6; constexpr float sv_waterfriction = 1; void G_RunEntity(edict_t *ent); bool SV_RunThink(edict_t *ent); void SV_AddRotationalFriction(edict_t *ent); void SV_AddGravity(edict_t *ent); void SV_CheckVelocity(edict_t *ent); void SV_FlyMove(edict_t *ent, float time, contents_t mask); contents_t G_GetClipMask(edict_t *ent); void G_Impact(edict_t *e1, const trace_t &trace); // // g_main.c // void SaveClientData(); void FetchClientEntData(edict_t *ent); void EndDMLevel(); // // g_chase.c // void UpdateChaseCam(edict_t *ent); void ChaseNext(edict_t *ent); void ChasePrev(edict_t *ent); void GetChaseTarget(edict_t *ent); //==================== // ROGUE PROTOTYPES // // g_newweap.c // void fire_flechette(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, int kick); void fire_prox(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed); void fire_nuke(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int speed); bool fire_player_melee(edict_t *self, const vec3_t &start, const vec3_t &aim, int reach, int damage, int kick, mod_t mod); void fire_tesla(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed); void fire_blaster2(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int damage, int speed, effects_t effect, bool hyper); void fire_heatbeam(edict_t *self, const vec3_t &start, const vec3_t &aimdir, const vec3_t &offset, int damage, int kick, bool monster); void fire_tracker(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, edict_t *enemy); // // g_newai.c // bool blocked_checkplat(edict_t *self, float dist); enum class blocked_jump_result_t { NO_JUMP, JUMP_TURN, JUMP_JUMP_UP, JUMP_JUMP_DOWN }; blocked_jump_result_t blocked_checkjump(edict_t *self, float dist); bool monsterlost_checkhint(edict_t *self); bool inback(edict_t *self, edict_t *other); float realrange(edict_t *self, edict_t *other); edict_t *SpawnBadArea(const vec3_t &mins, const vec3_t &maxs, gtime_t lifespan, edict_t *owner); edict_t *CheckForBadArea(edict_t *ent); bool MarkTeslaArea(edict_t *self, edict_t *tesla); void InitHintPaths(); void PredictAim(edict_t *self, edict_t *target, const vec3_t &start, float bolt_speed, bool eye_height, float offset, vec3_t *aimdir, vec3_t *aimpoint); bool M_CalculatePitchToFire(edict_t *self, const vec3_t &target, const vec3_t &start, vec3_t &aim, float speed, float time_remaining, bool mortar, bool destroy_on_touch = false); bool below(edict_t *self, edict_t *other); void drawbbox(edict_t *self); void M_MonsterDodge(edict_t *self, edict_t *attacker, gtime_t eta, trace_t *tr, bool gravity); void monster_duck_down(edict_t *self); void monster_duck_hold(edict_t *self); void monster_duck_up(edict_t *self); bool has_valid_enemy(edict_t *self); void TargetTesla(edict_t *self, edict_t *tesla); void hintpath_stop(edict_t *self); edict_t *PickCoopTarget(edict_t *self); int CountPlayers(); bool monster_jump_finished(edict_t *self); void BossExplode(edict_t *self); // g_rogue_func void plat2_spawn_danger_area(edict_t *ent); void plat2_kill_danger_area(edict_t *ent); // g_rogue_spawn edict_t *CreateMonster(const vec3_t &origin, const vec3_t &angles, const char *classname); edict_t *CreateFlyMonster(const vec3_t &origin, const vec3_t &angles, const vec3_t &mins, const vec3_t &maxs, const char *classname); edict_t *CreateGroundMonster(const vec3_t &origin, const vec3_t &angles, const vec3_t &mins, const vec3_t &maxs, const char *classname, float height); bool FindSpawnPoint(const vec3_t &startpoint, const vec3_t &mins, const vec3_t &maxs, vec3_t &spawnpoint, float maxMoveUp, bool drop = true); bool CheckSpawnPoint(const vec3_t &origin, const vec3_t &mins, const vec3_t &maxs); bool CheckGroundSpawnPoint(const vec3_t &origin, const vec3_t &entMins, const vec3_t &entMaxs, float height, float gravity); void SpawnGrow_Spawn(const vec3_t &startpos, float start_size, float end_size); void Widowlegs_Spawn(const vec3_t &startpos, const vec3_t &angles); // g_rogue_items bool Pickup_Nuke(edict_t *ent, edict_t *other); void Use_IR(edict_t *ent, gitem_t *item); void Use_Double(edict_t *ent, gitem_t *item); void Use_Nuke(edict_t *ent, gitem_t *item); void Use_Doppleganger(edict_t *ent, gitem_t *item); bool Pickup_Doppleganger(edict_t *ent, edict_t *other); bool Pickup_Sphere(edict_t *ent, edict_t *other); void Use_Defender(edict_t *ent, gitem_t *item); void Use_Hunter(edict_t *ent, gitem_t *item); void Use_Vengeance(edict_t *ent, gitem_t *item); void Item_TriggeredSpawn(edict_t *self, edict_t *other, edict_t *activator); void SetTriggeredSpawn(edict_t *ent); // // g_sphere.c // void Defender_Launch(edict_t *self); void Vengeance_Launch(edict_t *self); void Hunter_Launch(edict_t *self); // // g_newdm.c // void InitGameRules(); item_id_t DoRandomRespawn(edict_t *ent); void PrecacheForRandomRespawn(); bool Tag_PickupToken(edict_t *ent, edict_t *other); void Tag_DropToken(edict_t *ent, gitem_t *item); void fire_doppleganger(edict_t *ent, const vec3_t &start, const vec3_t &aimdir); // // p_client.c // void RemoveAttackingPainDaemons(edict_t *self); bool G_ShouldPlayersCollide(bool weaponry); bool P_UseCoopInstancedItems(); constexpr spawnflags_t SPAWNFLAG_LANDMARK_KEEP_Z = 1_spawnflag; // [Paril-KEX] convenience functions that returns true // if the powerup should be 'active' (false to disable, // will flash at 500ms intervals after 3 sec) [[nodiscard]] constexpr bool G_PowerUpExpiringRelative(gtime_t left) { return left.milliseconds() > 3000 || (left.milliseconds() % 1000) < 500; } [[nodiscard]] constexpr bool G_PowerUpExpiring(gtime_t time) { return G_PowerUpExpiringRelative(time - level.time); } // ZOID #include "ctf/g_ctf.h" #include "ctf/p_ctf_menu.h" // ZOID //============================================================================ // client_t->anim_priority enum anim_priority_t { ANIM_BASIC, // stand / run ANIM_WAVE, ANIM_JUMP, ANIM_PAIN, ANIM_ATTACK, ANIM_DEATH, // flags ANIM_REVERSED = bit_v<8> }; MAKE_ENUM_BITFLAGS(anim_priority_t); // height fog data values struct height_fog_t { // r g b dist std::array start; std::array end; float falloff; float density; inline bool operator==(const height_fog_t &o) const { return start == o.start && end == o.end && falloff == o.falloff && density == o.density; } }; constexpr gtime_t SELECTED_ITEM_TIME = 3_sec; enum bmodel_animstyle_t : int32_t { BMODEL_ANIM_FORWARDS, BMODEL_ANIM_BACKWARDS, BMODEL_ANIM_RANDOM }; struct bmodel_anim_t { // range, inclusive int32_t start, end; bmodel_animstyle_t style; int32_t speed; // in milliseconds bool nowrap; int32_t alt_start, alt_end; bmodel_animstyle_t alt_style; int32_t alt_speed; // in milliseconds bool alt_nowrap; // game-only bool enabled; bool alternate, currently_alternate; gtime_t next_tick; }; // never turn back shield on automatically; this is // the legacy behavior. constexpr int32_t AUTO_SHIELD_MANUAL = -1; // when it is >= 0, the shield will turn back on // when we have that many cells in our inventory // if possible. constexpr int32_t AUTO_SHIELD_AUTO = 0; // client data that stays across multiple level loads struct client_persistant_t { char userinfo[MAX_INFO_STRING]; char social_id[MAX_INFO_VALUE]; char netname[MAX_NETNAME]; handedness_t hand; auto_switch_t autoswitch; int32_t autoshield; // see AUTO_SHIELD_* bool connected, spawned; // a loadgame will leave valid entities that // just don't have a connection yet // values saved and restored from edicts when changing levels int32_t health; int32_t max_health; ent_flags_t savedFlags; item_id_t selected_item; gtime_t selected_item_time; std::array inventory; // ammo capacities std::array max_ammo; gitem_t *weapon; gitem_t *lastweapon; int32_t power_cubes; // used for tracking the cubes in coop games int32_t score; // for calculating total unit score in coop games int32_t game_help1changed, game_help2changed; int32_t helpchanged; // flash F1 icon if non 0, play sound // and increment only if 1, 2, or 3 gtime_t help_time; bool spectator; // client wants to be a spectator bool bob_skip; // [Paril-KEX] client wants no movement bob // [Paril-KEX] fog that we want to achieve; density rgb skyfogfactor std::array wanted_fog; height_fog_t wanted_heightfog; // relative time value, copied from last touched trigger gtime_t fog_transition_time; gtime_t megahealth_time; // relative megahealth time value int32_t lives; // player lives left (1 = no respawns remaining) uint8_t n64_crouch_warn_times; gtime_t n64_crouch_warning; }; // client data that stays across deathmatch respawns struct client_respawn_t { client_persistant_t coop_respawn; // what to set client->pers to on a respawn gtime_t entertime; // level.time the client entered the game int32_t score; // frags, etc vec3_t cmd_angles; // angles sent over in the last command bool spectator; // client is a spectator // ZOID ctfteam_t ctf_team; // CTF team int32_t ctf_state; gtime_t ctf_lasthurtcarrier; gtime_t ctf_lastreturnedflag; gtime_t ctf_flagsince; gtime_t ctf_lastfraggedcarrier; bool id_state; gtime_t lastidtime; bool voted; // for elections bool ready; bool admin; ghost_t *ghost; // for ghost codes // ZOID }; // [Paril-KEX] seconds until we are fully invisible after // making a racket constexpr gtime_t INVISIBILITY_TIME = 2_sec; // max number of individual damage indicators we'll track constexpr size_t MAX_DAMAGE_INDICATORS = 4; struct damage_indicator_t { vec3_t from; int32_t health, armor, power; }; // time between ladder sounds constexpr gtime_t LADDER_SOUND_TIME = 300_ms; // time after damage that we can't respawn on a player for constexpr gtime_t COOP_DAMAGE_RESPAWN_TIME = 2000_ms; // this structure is cleared on each PutClientInServer(), // except for 'client->pers' struct gclient_t { // shared with server; do not touch members until the "private" section player_state_t ps; // communicated by server to clients int32_t ping; // private to game client_persistant_t pers; client_respawn_t resp; pmove_state_t old_pmove; // for detecting out-of-pmove changes bool showscores; // set layout stat bool showeou; // end of unit screen bool showinventory; // set layout stat bool showhelp; button_t buttons; button_t oldbuttons; button_t latched_buttons; usercmd_t cmd; // last CMD send // weapon cannot fire until this time is up gtime_t weapon_fire_finished; // time between processing individual animation frames gtime_t weapon_think_time; // if we latched fire between server frames but before // the weapon fire finish has elapsed, we'll "press" it // automatically when we have a chance bool weapon_fire_buffered; bool weapon_thunk; gitem_t *newweapon; // sum up damage over an entire frame, so // shotgun blasts give a single big kick int32_t damage_armor; // damage absorbed by armor int32_t damage_parmor; // damage absorbed by power armor int32_t damage_blood; // damage taken out of health int32_t damage_knockback; // impact damage vec3_t damage_from; // origin for vector calculation damage_indicator_t damage_indicators[MAX_DAMAGE_INDICATORS]; uint8_t num_damage_indicators; float killer_yaw; // when dead, look at killer weaponstate_t weaponstate; struct { vec3_t angles, origin; gtime_t time, total; } kick; gtime_t quake_time; vec3_t kick_origin; float v_dmg_roll, v_dmg_pitch; gtime_t v_dmg_time; // damage kicks gtime_t fall_time; float fall_value; // for view drop on fall float damage_alpha; float bonus_alpha; vec3_t damage_blend; vec3_t v_angle, v_forward; // aiming direction float bobtime; // so off-ground doesn't change it vec3_t oldviewangles; vec3_t oldvelocity; edict_t *oldgroundentity; // [Paril-KEX] gtime_t flash_time; // [Paril-KEX] for high tickrate gtime_t next_drown_time; water_level_t old_waterlevel; int32_t breather_sound; int32_t machinegun_shots; // for weapon raising // animation vars int32_t anim_end; anim_priority_t anim_priority; bool anim_duck; bool anim_run; gtime_t anim_time; // powerup timers gtime_t quad_time; gtime_t invincible_time; gtime_t breather_time; gtime_t enviro_time; gtime_t invisible_time; bool grenade_blew_up; gtime_t grenade_time, grenade_finished_time; // RAFAEL gtime_t quadfire_time; // RAFAEL int32_t silencer_shots; int32_t weapon_sound; gtime_t pickup_msg_time; gtime_t flood_locktill; // locked from talking gtime_t flood_when[10]; // when messages were said int32_t flood_whenhead; // head pointer for when said gtime_t respawn_time; // can respawn when time > this edict_t *chase_target; // player we are chasing bool update_chase; // need to update chase info? //======= // ROGUE gtime_t double_time; gtime_t ir_time; gtime_t nuke_time; gtime_t tracker_pain_time; edict_t *owned_sphere; // this points to the player's sphere // ROGUE //======= gtime_t empty_click_sound; // ZOID bool inmenu; // in menu pmenuhnd_t *menu; // current menu gtime_t menutime; // time to update menu bool menudirty; edict_t *ctf_grapple; // entity of grapple int32_t ctf_grapplestate; // true if pulling gtime_t ctf_grapplereleasetime; // time of grapple release gtime_t ctf_regentime; // regen tech gtime_t ctf_techsndtime; gtime_t ctf_lasttechmsg; // ZOID // used for player trails. edict_t *trail_head, *trail_tail; // whether to use weapon chains bool no_weapon_chains; // seamless level transitions bool landmark_free_fall; const char* landmark_name; vec3_t landmark_rel_pos; // position relative to landmark, un-rotated from landmark angle gtime_t landmark_noise_time; gtime_t invisibility_fade_time; // [Paril-KEX] at this time, the player will be mostly fully cloaked gtime_t chase_msg_time; // to prevent CTF message spamming int32_t menu_sign; // menu sign vec3_t last_ladder_pos; // for ladder step sounds gtime_t last_ladder_sound; coop_respawn_t coop_respawn_state; gtime_t last_damage_time; // [Paril-KEX] these are now per-player, to work better in coop edict_t *sight_entity; gtime_t sight_entity_time; edict_t *sound_entity; gtime_t sound_entity_time; edict_t *sound2_entity; gtime_t sound2_entity_time; // saved positions for lag compensation uint8_t num_lag_origins; // 0 to MAX_LAG_ORIGINS, how many we can go back uint8_t next_lag_origin; // the next one to write to bool is_lag_compensated; vec3_t lag_restore_origin; // for high tickrate weapon angles vec3_t slow_view_angles; gtime_t slow_view_angle_time; // not saved bool help_draw_points; size_t help_draw_index, help_draw_count; gtime_t help_draw_time; uint32_t step_frame; int32_t help_poi_image; vec3_t help_poi_location; // only set temporarily bool awaiting_respawn; gtime_t respawn_timeout; // after this time, force a respawn // [Paril-KEX] current active fog values; density rgb skyfogfactor std::array fog; height_fog_t heightfog; gtime_t last_attacker_time; }; // ========================================== // PLAT 2 // ========================================== enum plat2flags_t { PLAT2_NONE = 0, PLAT2_CALLED = 1, PLAT2_MOVING = 2, PLAT2_WAITING = 4 }; MAKE_ENUM_BITFLAGS(plat2flags_t); #include struct edict_t { edict_t() = delete; edict_t(const edict_t &) = delete; edict_t(edict_t &&) = delete; // shared with server; do not touch members until the "private" section entity_state_t s; gclient_t *client; // nullptr if not a player // the server expects the first part // of gclient_t to be a player_state_t // but the rest of it is opaque sv_entity_t sv; // read only info about this entity for the server bool inuse; // world linkage data bool linked; int32_t linkcount; int32_t areanum, areanum2; svflags_t svflags; vec3_t mins, maxs; vec3_t absmin, absmax, size; solid_t solid; contents_t clipmask; edict_t *owner; //================================ // private to game int32_t spawn_count; // [Paril-KEX] used to differentiate different entities that may be in the same slot movetype_t movetype; ent_flags_t flags; const char *model; gtime_t freetime; // sv.time when the object was freed // // only used locally in game, not by server // const char *message; const char *classname; spawnflags_t spawnflags; gtime_t timestamp; float angle; // set in qe3, -1 = up, -2 = down const char *target; const char *targetname; const char *killtarget; const char *team; const char *pathtarget; const char *deathtarget; const char *healthtarget; const char *itemtarget; // [Paril-KEX] const char *combattarget; edict_t *target_ent; float speed, accel, decel; vec3_t movedir; vec3_t pos1, pos2, pos3; vec3_t velocity; vec3_t avelocity; int32_t mass; gtime_t air_finished; float gravity; // per entity gravity multiplier (1.0 is normal) // use for lowgrav artifact, flares edict_t *goalentity; edict_t *movetarget; float yaw_speed; float ideal_yaw; gtime_t nextthink; save_prethink_t prethink; save_prethink_t postthink; save_think_t think; save_touch_t touch; save_use_t use; save_pain_t pain; save_die_t die; gtime_t touch_debounce_time; // are all these legit? do we need more/less of them? gtime_t pain_debounce_time; gtime_t damage_debounce_time; gtime_t fly_sound_debounce_time; // move to clientinfo gtime_t last_move_time; int32_t health; int32_t max_health; int32_t gib_health; gtime_t show_hostile; gtime_t powerarmor_time; const char *map; // target_changelevel int32_t viewheight; // height above origin where eyesight is determined bool deadflag; bool takedamage; int32_t dmg; int32_t radius_dmg; float dmg_radius; int32_t sounds; // make this a spawntemp var? int32_t count; edict_t *chain; edict_t *enemy; edict_t *oldenemy; edict_t *activator; edict_t *groundentity; int32_t groundentity_linkcount; edict_t *teamchain; edict_t *teammaster; edict_t *mynoise; // can go in client only edict_t *mynoise2; int32_t noise_index; int32_t noise_index2; float volume; float attenuation; // timing variables float wait; float delay; // before firing targets float random; gtime_t teleport_time; contents_t watertype; water_level_t waterlevel; vec3_t move_origin; vec3_t move_angles; int32_t style; // also used as areaportal number gitem_t *item; // for bonus items // common data blocks moveinfo_t moveinfo; monsterinfo_t monsterinfo; //========= // ROGUE plat2flags_t plat2flags; vec3_t offset; vec3_t gravityVector; edict_t *bad_area; edict_t *hint_chain; edict_t *monster_hint_chain; edict_t *target_hint_chain; int32_t hint_chain_id; // ROGUE //========= char clock_message[CLOCK_MESSAGE_SIZE]; // Paril: we died on this frame, apply knockback even if we're dead gtime_t dead_time; // used for dabeam monsters edict_t *beam, *beam2; // proboscus for Parasite edict_t *proboscus; // for vooping things edict_t *disintegrator; gtime_t disintegrator_time; int32_t hackflags; // n64 // fog stuff struct { vec3_t color; float density; float sky_factor; vec3_t color_off; float density_off; float sky_factor_off; } fog; struct { float falloff; float density; vec3_t start_color; float start_dist; vec3_t end_color; float end_dist; float falloff_off; float density_off; vec3_t start_color_off; float start_dist_off; vec3_t end_color_off; float end_dist_off; } heightfog; // instanced coop items std::bitset item_picked_up_by; gtime_t slime_debounce_time; // [Paril-KEX] bmodel_anim_t bmodel_anim; mod_t lastMOD; const char *style_on, *style_off; uint32_t crosslevel_flags; // NOTE: if adding new elements, make sure to add them // in g_save.cpp too! }; //============= // ROGUE constexpr spawnflags_t SPHERE_DEFENDER = 0x0001_spawnflag; constexpr spawnflags_t SPHERE_HUNTER = 0x0002_spawnflag; constexpr spawnflags_t SPHERE_VENGEANCE = 0x0004_spawnflag; constexpr spawnflags_t SPHERE_DOPPLEGANGER = 0x10000_spawnflag; constexpr spawnflags_t SPHERE_TYPE = SPHERE_DEFENDER | SPHERE_HUNTER | SPHERE_VENGEANCE; constexpr spawnflags_t SPHERE_FLAGS = SPHERE_DOPPLEGANGER; // // deathmatch games // enum { RDM_TAG = 2, RDM_DEATHBALL = 3 }; struct dm_game_rt { void (*GameInit)(); void (*PostInitSetup)(); void (*ClientBegin)(edict_t *ent); bool (*SelectSpawnPoint)(edict_t *ent, vec3_t &origin, vec3_t &angles, bool force_spawn); void (*PlayerDeath)(edict_t *targ, edict_t *inflictor, edict_t *attacker); void (*Score)(edict_t *attacker, edict_t *victim, int scoreChange, const mod_t &mod); void (*PlayerEffects)(edict_t *ent); void (*DogTag)(edict_t *ent, edict_t *killer, const char **pic); void (*PlayerDisconnect)(edict_t *ent); int (*ChangeDamage)(edict_t *targ, edict_t *attacker, int damage, mod_t mod); int (*ChangeKnockback)(edict_t *targ, edict_t *attacker, int knockback, mod_t mod); int (*CheckDMRules)(); }; extern dm_game_rt DMGame; // ROGUE //============ // [Paril-KEX] inline void monster_footstep(edict_t *self) { if (self->groundentity) self->s.event = EV_OTHER_FOOTSTEP; } // [Kex] helpers // TFilter must be a type that is invokable with the // signature bool(edict_t *); it must return true if // the entity given is valid for the given filter template struct entity_iterator_t { using iterator_category = std::random_access_iterator_tag; using value_type = edict_t *; using reference = edict_t *; using pointer = edict_t *; using difference_type = ptrdiff_t; private: uint32_t index; uint32_t end_index; // where the end index is located for this iterator // index < globals.num_edicts are valid TFilter filter; // this doubles as the "end" iterator inline bool is_out_of_range(uint32_t i) const { return i >= end_index; } inline bool is_out_of_range() const { return is_out_of_range(index); } inline void throw_if_out_of_range() const { if (is_out_of_range()) throw std::out_of_range("index"); } inline difference_type clamped_index() const { if (is_out_of_range()) return end_index; return index; } public: // note: index is not affected by filter. it is up to // the caller to ensure this index is filtered. constexpr entity_iterator_t(uint32_t i, uint32_t end_index = -1) : index(i), end_index((end_index >= globals.num_edicts) ? globals.num_edicts : end_index) { } inline reference operator*() { throw_if_out_of_range(); return &g_edicts[index]; } inline pointer operator->() { throw_if_out_of_range(); return &g_edicts[index]; } inline entity_iterator_t &operator++() { throw_if_out_of_range(); return *this = *this + 1; } inline entity_iterator_t &operator--() { throw_if_out_of_range(); return *this = *this - 1; } inline difference_type operator-(const entity_iterator_t &it) const { return clamped_index() - it.clamped_index(); } inline entity_iterator_t operator+(const difference_type &offset) const { entity_iterator_t it(index + offset, end_index); // move in the specified direction, only stopping if we // run out of range or find a filtered entity while (!is_out_of_range(it.index) && !filter(*it)) it.index += offset > 0 ? 1 : -1; return it; } // + -1 and - 1 are the same (and - -1 & + 1) inline entity_iterator_t operator-(const difference_type &offset) const { return *this + (-offset); } // comparison. hopefully this won't break anything, but == and != use the // clamped index (so -1 and num_edicts will be equal technically since they // are the same "invalid" entity) but <= and >= will affect them properly. inline bool operator==(const entity_iterator_t &it) const { return clamped_index() == it.clamped_index(); } inline bool operator!=(const entity_iterator_t &it) const { return clamped_index() != it.clamped_index(); } inline bool operator<(const entity_iterator_t &it) const { return index < it.index; } inline bool operator>(const entity_iterator_t &it) const { return index > it.index; } inline bool operator<=(const entity_iterator_t &it) const { return index <= it.index; } inline bool operator>=(const entity_iterator_t &it) const { return index >= it.index; } inline edict_t *operator[](const difference_type &offset) const { return *(*this + offset); } }; // iterate over range of entities, with the specified filter. // can be "open-ended" (automatically expand with num_edicts) // by leaving the max unset. template struct entity_iterable_t { private: uint32_t begin_index, end_index; TFilter filter; // find the first entity that matches the filter, from the specified index, // in the specified direction inline uint32_t find_matched_index(uint32_t index, int32_t direction) { while (index < globals.num_edicts && !filter(&g_edicts[index])) index += direction; return index; } public: // iterate all allocated entities that match the filter, // including ones allocated after this iterator is constructed inline entity_iterable_t() : begin_index(find_matched_index(0, 1)), end_index(game.maxentities) { } // iterate all allocated entities that match the filter from the specified begin offset // including ones allocated after this iterator is constructed inline entity_iterable_t(uint32_t start) : begin_index(find_matched_index(start, 1)), end_index(game.maxentities) { } // iterate all allocated entities that match the filter from the specified begin offset // to the specified INCLUSIVE end offset (or the first entity that matches before it), // including end itself but not ones that may appear after this iterator is done inline entity_iterable_t(uint32_t start, uint32_t end) : begin_index(find_matched_index(start, 1)), end_index(find_matched_index(end, -1) + 1) { } inline entity_iterator_t begin() const { return entity_iterator_t(begin_index, end_index); } inline entity_iterator_t end() const { return end_index; } }; // inuse players that are connected; may not be spawned yet, however struct active_players_filter_t { inline bool operator()(edict_t *ent) const { return (ent->inuse && ent->client && ent->client->pers.connected); } }; inline entity_iterable_t active_players() { return entity_iterable_t { 1u, game.maxclients }; } struct gib_def_t { size_t count; const char *gibname; float scale; gib_type_t type; constexpr gib_def_t(size_t count, const char *gibname) : count(count), gibname(gibname), scale(1.0f), type(GIB_NONE) { } constexpr gib_def_t(size_t count, const char *gibname, gib_type_t type) : count(count), gibname(gibname), scale(1.0f), type(type) { } constexpr gib_def_t(size_t count, const char *gibname, float scale) : count(count), gibname(gibname), scale(scale), type(GIB_NONE) { } constexpr gib_def_t(size_t count, const char *gibname, float scale, gib_type_t type) : count(count), gibname(gibname), scale(scale), type(type) { } constexpr gib_def_t(const char *gibname, float scale, gib_type_t type) : count(1), gibname(gibname), scale(scale), type(type) { } constexpr gib_def_t(const char *gibname, float scale) : count(1), gibname(gibname), scale(scale), type(GIB_NONE) { } constexpr gib_def_t(const char *gibname, gib_type_t type) : count(1), gibname(gibname), scale(1.0f), type(type) { } constexpr gib_def_t(const char *gibname) : count(1), gibname(gibname), scale(1.0f), type(GIB_NONE) { } }; // convenience function to throw different gib types // NOTE: always throw the head gib *last* since self's size is used // to position the gibs! inline void ThrowGibs(edict_t *self, int32_t damage, std::initializer_list gibs) { for (auto &gib : gibs) for (size_t i = 0; i < gib.count; i++) ThrowGib(self, gib.gibname, damage, gib.type, gib.scale * (self->s.scale ? self->s.scale : 1)); } inline bool M_CheckGib(edict_t *self, const mod_t &mod) { if (self->deadflag) { if (mod.id == MOD_CRUSH) return true; } return self->health <= self->gib_health; } // Fmt support for entities template<> struct fmt::formatter { template constexpr auto parse(ParseContext& ctx) { return ctx.begin(); } template auto format(const edict_t &p, FormatContext &ctx) -> decltype(ctx.out()) { if (p.linked) return fmt::format_to(ctx.out(), FMT_STRING("{} @ {}"), p.classname, (p.absmax + p.absmin) * 0.5f); return fmt::format_to(ctx.out(), FMT_STRING("{} @ {}"), p.classname, p.s.origin); } }; // POI tags used by this mod enum pois_t : uint16_t { POI_OBJECTIVE = MAX_EDICTS, // current objective POI_RED_FLAG, // red flag/carrier POI_BLUE_FLAG, // blue flag/carrier POI_PING, POI_PING_END = POI_PING + MAX_CLIENTS - 1, }; // implementation of pierce stuff inline bool pierce_args_t::mark(edict_t *ent) { // ran out of pierces if (num_pierced == MAX_PIERCE) return false; pierced[num_pierced] = ent; pierce_solidities[num_pierced] = ent->solid; num_pierced++; ent->solid = SOLID_NOT; gi.linkentity(ent); return true; } // implementation of pierce stuff inline void pierce_args_t::restore() { for (size_t i = 0; i < num_pierced; i++) { auto &ent = pierced[i]; ent->solid = pierce_solidities[i]; gi.linkentity(ent); } num_pierced = 0; }