- wget -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints
- wget -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache
- make
- - EXPECT=ed9be8d1b1a544f89bcdd7d36876fede
+ - EXPECT=0a08daa9132d147f533776deda07643e
- HASH=$(${ENGINE} -noconfig -nohome +exec serverbench.cfg
| tee /dev/stderr
| grep '^:'
set g_dodging 1
set sv_dodging_wall_dodging 1
-set g_spawn_near_teammate 1
+set g_spawn_near_teammate "!g_assault !g_freezetag"
set g_spawn_near_teammate_ignore_spawnpoint 1
set g_spawnshieldtime 0.5
set g_respawn_delay_forced 2
// spawn near teammate
// =====================
seta cl_spawn_near_teammate 1 "toggle for spawning near teammates (only effective if g_spawn_near_teammate_ignore_spawnpoint is 2)"
-set g_spawn_near_teammate 0 "if set, players prefer spawns near a team mate"
+set g_spawn_near_teammate 0 "players prefer spawns near a team mate"
set g_spawn_near_teammate_distance 640 "max distance to consider a spawn to be near a team mate"
set g_spawn_near_teammate_ignore_spawnpoint 0 "ignore spawnpoints and spawn right at team mates, if 2, clients can ignore this option"
set g_spawn_near_teammate_ignore_spawnpoint_max 10 "if set, test at most this many of the available teammates"
#include "sv_bloodloss.qh"
-REGISTER_MUTATOR(bloodloss, cvar("g_bloodloss"));
+float autocvar_g_bloodloss;
+REGISTER_MUTATOR(bloodloss, autocvar_g_bloodloss);
.float bloodloss_timer;
#include "sv_campcheck.qh"
+string autocvar_g_campcheck;
float autocvar_g_campcheck_damage;
float autocvar_g_campcheck_distance;
float autocvar_g_campcheck_interval;
-REGISTER_MUTATOR(campcheck, cvar("g_campcheck"));
+REGISTER_MUTATOR(campcheck, expr_evaluate(autocvar_g_campcheck));
.float campcheck_nextcheck;
.float campcheck_traveled_distance;
#include "sv_cloaked.qh"
-REGISTER_MUTATOR(cloaked, cvar("g_cloaked"));
+string autocvar_g_cloaked;
+REGISTER_MUTATOR(cloaked, expr_evaluate(autocvar_g_cloaked));
float autocvar_g_balance_cloaked_alpha;
#ifdef SVQC
AUTOCVAR(g_grappling_hook_useammo, bool, false, "Use ammunition with the off-hand grappling hook");
-REGISTER_MUTATOR(hook, cvar("g_grappling_hook")) {
+REGISTER_MUTATOR(hook, expr_evaluate(cvar_string("g_grappling_hook"))) {
MUTATOR_ONADD {
g_grappling_hook = true;
if(!autocvar_g_grappling_hook_useammo)
#include "sv_instagib.qh"
+bool autocvar_g_instagib_damagedbycontents = true;
+bool autocvar_g_instagib_blaster_keepdamage = false;
+bool autocvar_g_instagib_blaster_keepforce = false;
+bool autocvar_g_instagib_mirrordamage;
+bool autocvar_g_instagib_friendlypush = true;
//int autocvar_g_instagib_ammo_drop;
bool autocvar_g_instagib_ammo_convert_cells;
bool autocvar_g_instagib_ammo_convert_rockets;
#include <common/items/_mod.qh>
-REGISTER_MUTATOR(mutator_instagib, cvar("g_instagib") && !g_nexball);
+REGISTER_MUTATOR(mutator_instagib, autocvar_g_instagib && !g_nexball);
spawnfunc(item_minst_cells)
{
#include "sv_invincibleproj.qh"
-REGISTER_MUTATOR(invincibleprojectiles, cvar("g_invincible_projectiles"));
+string autocvar_g_invincible_projectiles;
+REGISTER_MUTATOR(invincibleprojectiles, expr_evaluate(autocvar_g_invincible_projectiles));
MUTATOR_HOOKFUNCTION(invincibleprojectiles, EditProjectile)
{
#include "sv_melee_only.qh"
-REGISTER_MUTATOR(melee_only, cvar("g_melee_only") && !cvar("g_instagib") && !cvar("g_overkill") && !g_nexball);
+string autocvar_g_melee_only;
+REGISTER_MUTATOR(melee_only, expr_evaluate(autocvar_g_melee_only) && !cvar("g_instagib") && !cvar("g_overkill") && !g_nexball);
MUTATOR_HOOKFUNCTION(melee_only, SetStartItems, CBC_ORDER_LAST)
{
#include "sv_midair.qh"
+string autocvar_g_midair;
float autocvar_g_midair_shieldtime;
-REGISTER_MUTATOR(midair, cvar("g_midair"));
+REGISTER_MUTATOR(midair, expr_evaluate(autocvar_g_midair));
.float midair_shieldtime;
#if defined(SVQC)
-REGISTER_MUTATOR(multijump, cvar("g_multijump"));
+REGISTER_MUTATOR(multijump, autocvar_g_multijump);
#elif defined(CSQC)
REGISTER_MUTATOR(multijump, true);
#endif
entity nade_type = Nade_FromProjectile(proj.cnt);
if (nade_type == NADE_TYPE_Null) return;
- if(STAT(NADES_SMALL, NULL))
+ if(STAT(NADES_SMALL))
{
proj.mins = '-8 -8 -8';
proj.maxs = '8 8 8';
#include <common/monsters/sv_monsters.qh>
#include <server/g_subs.qh>
-REGISTER_MUTATOR(nades, cvar("g_nades"));
+REGISTER_MUTATOR(nades, autocvar_g_nades);
.float nade_time_primed;
.float nade_lifetime;
hp -= damage;
SetResourceAmount(this, RESOURCE_HEALTH, hp);
-
+
if ( this.nade_type != NADE_TYPE_HEAL.m_id || IS_PLAYER(attacker) )
this.realowner = attacker;
*/
+string autocvar_g_new_toys;
+
bool nt_IsNewToy(int w);
-REGISTER_MUTATOR(nt, cvar("g_new_toys") && !cvar("g_instagib") && !cvar("g_overkill"))
+REGISTER_MUTATOR(nt, expr_evaluate(autocvar_g_new_toys) && !cvar("g_instagib") && !cvar("g_overkill"))
{
MUTATOR_ONADD
{
#include "sv_nix.qh"
+string autocvar_g_nix;
int autocvar_g_balance_nix_ammo_cells;
int autocvar_g_balance_nix_ammo_plasma;
int autocvar_g_balance_nix_ammo_fuel;
bool NIX_CanChooseWeapon(int wpn);
-REGISTER_MUTATOR(nix, cvar("g_nix") && !cvar("g_instagib") && !cvar("g_overkill"))
+REGISTER_MUTATOR(nix, expr_evaluate(autocvar_g_nix) && !cvar("g_instagib") && !cvar("g_overkill"))
{
MUTATOR_ONADD
{
#include "hmg.qh"
#include "rpc.qh"
+string autocvar_g_overkill;
+
bool autocvar_g_overkill_powerups_replace;
bool autocvar_g_overkill_itemwaypoints = true;
void ok_Initialize();
-REGISTER_MUTATOR(ok, cvar("g_overkill") && !cvar("g_instagib") && !g_nexball && cvar_string("g_mod_balance") == "Overkill")
+REGISTER_MUTATOR(ok, expr_evaluate(autocvar_g_overkill) && !cvar("g_instagib") && !g_nexball && cvar_string("g_mod_balance") == "Overkill")
{
MUTATOR_ONADD
{
float autocvar_g_physical_items_damageforcescale;
float autocvar_g_physical_items_reset;
-REGISTER_MUTATOR(physical_items, cvar("g_physical_items"))
+REGISTER_MUTATOR(physical_items, autocvar_g_physical_items)
{
// check if we have a physics engine
MUTATOR_ONADD
#include "sv_pinata.qh"
-REGISTER_MUTATOR(pinata, cvar("g_pinata") && !cvar("g_instagib") && !cvar("g_overkill"));
+string autocvar_g_pinata;
+REGISTER_MUTATOR(pinata, expr_evaluate(autocvar_g_pinata) && !cvar("g_instagib") && !cvar("g_overkill"));
MUTATOR_HOOKFUNCTION(pinata, PlayerDies)
{
#include "sv_rocketflying.qh"
-REGISTER_MUTATOR(rocketflying, cvar("g_rocket_flying"));
+string autocvar_g_rocket_flying;
+REGISTER_MUTATOR(rocketflying, expr_evaluate(autocvar_g_rocket_flying));
MUTATOR_HOOKFUNCTION(rocketflying, EditProjectile)
{
#include "sv_sandbox.qh"
+string autocvar_g_sandbox;
int autocvar_g_sandbox_info;
bool autocvar_g_sandbox_readonly;
string autocvar_g_sandbox_storage_name;
float autosave_time;
void sandbox_Database_Load();
-REGISTER_MUTATOR(sandbox, cvar("g_sandbox"))
+REGISTER_MUTATOR(sandbox, expr_evaluate(autocvar_g_sandbox))
{
MUTATOR_ONADD
{
#include <lib/float.qh>
+string autocvar_g_spawn_near_teammate;
float autocvar_g_spawn_near_teammate_distance;
int autocvar_g_spawn_near_teammate_ignore_spawnpoint;
int autocvar_g_spawn_near_teammate_ignore_spawnpoint_max;
bool autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health;
bool autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath;
-REGISTER_MUTATOR(spawn_near_teammate, cvar("g_spawn_near_teammate"));
+REGISTER_MUTATOR(spawn_near_teammate, expr_evaluate(autocvar_g_spawn_near_teammate));
.entity msnt_lookat;
MUTATOR_HOOKFUNCTION(spawn_near_teammate, Spawn_Score)
{
+ if (!teamplay) return;
+
entity player = M_ARGV(0, entity);
entity spawn_spot = M_ARGV(1, entity);
vector spawn_score = M_ARGV(2, vector);
spawn_spot.msnt_lookat = NULL;
- if(!teamplay)
- return;
-
RandomSelection_Init();
FOREACH_CLIENT(IS_PLAYER(it) && it != player && SAME_TEAM(it, player) && !IS_DEAD(it), {
if(vdist(spawn_spot.origin - it.origin, >, autocvar_g_spawn_near_teammate_distance))
MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerSpawn)
{
- if(!teamplay) { return; }
+ if (!teamplay) return;
+
entity player = M_ARGV(0, entity);
entity spawn_spot = M_ARGV(1, entity);
#include "sv_superspec.qh"
-REGISTER_MUTATOR(superspec, cvar("g_superspectate"));
+string autocvar_g_superspectate;
+REGISTER_MUTATOR(superspec, expr_evaluate(autocvar_g_superspectate));
#define _SSMAGIX "SUPERSPEC_OPTIONSFILE_V1"
#define _ISLOCAL(ent) ((edict_num(1) == (ent)) ? true : false)
#include "sv_touchexplode.qh"
+string autocvar_g_touchexplode;
float autocvar_g_touchexplode_radius;
float autocvar_g_touchexplode_damage;
float autocvar_g_touchexplode_edgedamage;
float autocvar_g_touchexplode_force;
-REGISTER_MUTATOR(touchexplode, cvar("g_touchexplode"));
+REGISTER_MUTATOR(touchexplode, expr_evaluate(autocvar_g_touchexplode));
.float touchexplode_time;
#include "sv_vampire.qh"
-REGISTER_MUTATOR(vampire, cvar("g_vampire") && !cvar("g_instagib"));
+string autocvar_g_vampire;
+REGISTER_MUTATOR(vampire, expr_evaluate(autocvar_g_vampire) && !cvar("g_instagib"));
MUTATOR_HOOKFUNCTION(vampire, PlayerDamage_SplitHealthArmor)
{
#include "sv_vampirehook.qh"
-REGISTER_MUTATOR(vh, cvar("g_vampirehook"));
+string autocvar_g_vampirehook;
+REGISTER_MUTATOR(vh, expr_evaluate(autocvar_g_vampirehook));
bool autocvar_g_vampirehook_teamheal;
float autocvar_g_vampirehook_damage;
#ifdef CSQC
REGISTER_MUTATOR(walljump, true);
#elif defined(SVQC)
-REGISTER_MUTATOR(walljump, cvar("g_walljump"));
+REGISTER_MUTATOR(walljump, autocvar_g_walljump);
#endif
#define PHYS_WALLJUMP(s) STAT(WALLJUMP, s)
#define SET_ONSLICK(s) ((s).flags |= FL_ONSLICK)
#define UNSET_ONSLICK(s) ((s).flags &= ~FL_ONSLICK)
-#define GAMEPLAYFIX_DOWNTRACEONGROUND(s) STAT(GAMEPLAYFIX_DOWNTRACEONGROUND, NULL)
-#define GAMEPLAYFIX_EASIERWATERJUMP(s) STAT(GAMEPLAYFIX_EASIERWATERJUMP, NULL)
-#define GAMEPLAYFIX_STEPDOWN(s) STAT(GAMEPLAYFIX_STEPDOWN, NULL)
-#define GAMEPLAYFIX_STEPMULTIPLETIMES(s) STAT(GAMEPLAYFIX_STEPMULTIPLETIMES, NULL)
-#define GAMEPLAYFIX_UNSTICKPLAYERS(s) STAT(GAMEPLAYFIX_UNSTICKPLAYERS, NULL)
-#define GAMEPLAYFIX_WATERTRANSITION(s) STAT(GAMEPLAYFIX_WATERTRANSITION, NULL)
-#define UPWARD_VELOCITY_CLEARS_ONGROUND(s) STAT(GAMEPLAYFIX_UPVELOCITYCLEARSONGROUND, NULL)
-
-#define PHYS_STEPHEIGHT(s) STAT(MOVEVARS_STEPHEIGHT, NULL)
-#define PHYS_NOSTEP(s) STAT(NOSTEP, NULL)
-#define PHYS_JUMPSTEP(s) STAT(MOVEVARS_JUMPSTEP, NULL)
-#define PHYS_WALLFRICTION(s) STAT(MOVEVARS_WALLFRICTION, NULL)
+#define GAMEPLAYFIX_DOWNTRACEONGROUND(s) STAT(GAMEPLAYFIX_DOWNTRACEONGROUND)
+#define GAMEPLAYFIX_EASIERWATERJUMP(s) STAT(GAMEPLAYFIX_EASIERWATERJUMP)
+#define GAMEPLAYFIX_STEPDOWN(s) STAT(GAMEPLAYFIX_STEPDOWN)
+#define GAMEPLAYFIX_STEPMULTIPLETIMES(s) STAT(GAMEPLAYFIX_STEPMULTIPLETIMES)
+#define GAMEPLAYFIX_UNSTICKPLAYERS(s) STAT(GAMEPLAYFIX_UNSTICKPLAYERS)
+#define GAMEPLAYFIX_WATERTRANSITION(s) STAT(GAMEPLAYFIX_WATERTRANSITION)
+#define UPWARD_VELOCITY_CLEARS_ONGROUND(s) STAT(GAMEPLAYFIX_UPVELOCITYCLEARSONGROUND)
+
+#define PHYS_STEPHEIGHT(s) STAT(MOVEVARS_STEPHEIGHT)
+#define PHYS_NOSTEP(s) STAT(NOSTEP)
+#define PHYS_JUMPSTEP(s) STAT(MOVEVARS_JUMPSTEP)
+#define PHYS_WALLFRICTION(s) STAT(MOVEVARS_WALLFRICTION)
#ifdef CSQC
.float bouncestop;
#define PHYS_JETPACK_MAXSPEED_UP(s) STAT(JETPACK_MAXSPEED_UP, s)
#define PHYS_JETPACK_REVERSE_THRUST(s) STAT(JETPACK_REVERSE_THRUST, s)
-#define PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS(s) STAT(MOVEVARS_JUMPSPEEDCAP_DISABLE_ONRAMPS, NULL)
+#define PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS(s) STAT(MOVEVARS_JUMPSPEEDCAP_DISABLE_ONRAMPS)
#define PHYS_JUMPVELOCITY(s) STAT(MOVEVARS_JUMPVELOCITY, s)
#define PHYS_MAXAIRSPEED(s) STAT(MOVEVARS_MAXAIRSPEED, s)
#define PHYS_WARSOWBUNNY_TOPSPEED(s) STAT(MOVEVARS_WARSOWBUNNY_TOPSPEED, s)
#define PHYS_WARSOWBUNNY_TURNACCEL(s) STAT(MOVEVARS_WARSOWBUNNY_TURNACCEL, s)
-#define PHYS_SLICK_APPLYGRAVITY(s) STAT(SLICK_APPLYGRAVITY, NULL)
+#define PHYS_SLICK_APPLYGRAVITY(s) STAT(SLICK_APPLYGRAVITY)
#define PHYS_INPUT_BUTTON_ATCK(s) PHYS_INPUT_BUTTON_BUTTON1(s)
#define PHYS_INPUT_BUTTON_JUMP(s) PHYS_INPUT_BUTTON_BUTTON2(s)
int compressed_shotorg = compressShotOrigin(this.movedir);
// make them match perfectly
#ifdef SVQC
- this.movedir = decompressShotOrigin(this.owner.stat_shotorg = compressed_shotorg);
+ // null during init
+ if (this.owner) this.owner.stat_shotorg = compressed_shotorg;
+ this.movedir = decompressShotOrigin(compressed_shotorg);
#else
this.movedir = decompressShotOrigin(compressed_shotorg);
#endif
// projectile IDs 40-50 reserved
const int PROJECTILE_RPC = 60;
+
+// projectile IDs 70-100 reserved
#undef setcolor
#ifdef MENUQC
- #define NULL (0, null_entity)
+ #define NULL (RVALUE, null_entity)
#define world NULL
#else
- #define NULL (0, world)
+ #define NULL (RVALUE, world)
#endif
#define SV_Shutdown _SV_Shutdown
void _StartFrame();
- void StartFrame() { if (_StartFrame) _StartFrame(); }
+ bool _StartFrame_init;
+ void spawnfunc_worldspawn(entity);
+ void StartFrame() {
+ if (!_StartFrame_init) {
+ _StartFrame_init = true;
+ float oldtime = time; time = 1;
+ __spawnfunc_expecting = 2; NULL.__spawnfunc_constructor(NULL);
+ time = oldtime;
+ }
+ if (_StartFrame) _StartFrame();
+ }
#define StartFrame _StartFrame
void _SetNewParms();
#define MACRO_END } while (0)
#endif
+/** Marker for use in (RVALUE, (expr)) */
+#define RVALUE 0
+
#define _CAT(a, b) a ## b
#define CAT(a, b) _CAT(a, b)
#include "p99.qh"
#define OVERLOAD(F, ...) P99_IF_EMPTY(__VA_ARGS__)(P99_PASTE2(F, _00)())(P99_PASTE3(F, _, P00_NARG(__VA_ARGS__))(__VA_ARGS__))
+ /** for use within a macro */
#define OVERLOAD_(F, ...) P99_IF_EMPTY(__VA_ARGS__)(P99_PASTE2(F, _00)())(P99_PASTE3(F, _, P00_NARG(__VA_ARGS__))(__VA_ARGS__))
#else
#define EVAL(...) __VA_ARGS__
#pragma once
+#include "macro.qh"
+
// Transition from global 'self' to local 'this'
// Step 1: auto oldself
// Step 2: const self
#if 1
- #define self (0, self)
+ #define self (RVALUE, self)
[[alias("self")]] entity __self;
#define setself(s) (__self = s)
- #define WITHSELF(value, block) WITH(entity, __self, value, (0, block))
+ #define WITHSELF(value, block) WITH(entity, __self, value, (RVALUE, block))
#endif
// Step 3: propagate SELFPARAM()
// Step 5: this should work
#if 1
#undef self
- #define self (0, this)
+ #define self (RVALUE, this)
#endif
// Step 6: remove SELFPARAM, add parameters
#define SELFWRAP_SET(T, e, f) \
(_selftemp = (e), _selftemp.__##T = ((f) ? T##_self : func_null), _selftemp.self##T = (f))
#define SELFWRAP_GET(T, e) \
- (0, (e).self##T)
+ (RVALUE, (e).self##T)
#define _SELFWRAP_SET(T, e, f) \
((e).__##T = (f))
#define _SELFWRAP_GET(T, e) \
- (0, (e).__##T)
+ (RVALUE, (e).__##T)
SELFWRAP(think, void, (), (entity this), (this))
#define setthink(e, f) SELFWRAP_SET(think, e, f)
#define _spawnfunc_check(fld) \
if (fieldname == #fld) continue;
- noref bool __spawnfunc_expecting;
+ noref int __spawnfunc_expecting;
noref entity __spawnfunc_expect;
noref bool __spawnfunc_unreachable_workaround = true;
+ .void(entity) __spawnfunc_constructor;
+ noref IntrusiveList g_spawn_queue;
+
+ #define SPAWNFUNC_INTERNAL_FIELDS(X) \
+ X(string, classname, "spawnfunc") \
+ X(string, targetname, string_null) \
+ /**/
+
+ #define X(T, fld, def) .T fld, __spawnfunc_##fld;
+ SPAWNFUNC_INTERNAL_FIELDS(X)
+ #undef X
+
+ void __spawnfunc_defer(entity prototype, void(entity) constructor)
+ {
+ IL_PUSH(g_spawn_queue, prototype);
+ #define X(T, fld, def) { prototype.__spawnfunc_##fld = prototype.fld; prototype.fld = def; }
+ SPAWNFUNC_INTERNAL_FIELDS(X);
+ #undef X
+ prototype.__spawnfunc_constructor = constructor;
+ }
+
+ noref IntrusiveList g_map_entities;
+ #define __spawnfunc_spawn_all() MACRO_BEGIN \
+ g_map_entities = IL_NEW(); \
+ IL_EACH(g_spawn_queue, true, __spawnfunc_spawn(it)); \
+ MACRO_END
+
+ void __spawnfunc_spawn(entity prototype)
+ {
+ entity e = new(clone);
+ copyentity(prototype, e);
+ IL_PUSH(g_map_entities, e);
+ #define X(T, fld, def) { e.fld = e.__spawnfunc_##fld; e.__spawnfunc_##fld = def; }
+ SPAWNFUNC_INTERNAL_FIELDS(X);
+ #undef X
+ e.__spawnfunc_constructor(e);
+ }
+
#define spawnfunc_1(id) spawnfunc_2(id, FIELDS_UNION)
#define spawnfunc_2(id, whitelist) \
void __spawnfunc_##id(entity this); \
[[accumulate]] void spawnfunc_##id(entity this) \
{ \
- if (__spawnfunc_expecting) \
- { \
+ bool dospawn = true; \
+ if (__spawnfunc_expecting > 1) { __spawnfunc_expecting = false; } \
+ else if (__spawnfunc_expecting) { \
/* engine call */ \
+ if (!g_spawn_queue) { g_spawn_queue = IL_NEW(); } \
__spawnfunc_expecting = false; \
this = __spawnfunc_expect; \
__spawnfunc_expect = NULL; \
- } \
- else \
- { \
+ dospawn = false; \
+ } else { \
+ /* userland call */ \
assert(this); \
} \
- if (!this.sourceLoc) \
- { \
+ if (!this.sourceLoc) { \
this.sourceLoc = __FILE__ ":" STR(__LINE__); \
} \
- if (!this.spawnfunc_checked) \
- { \
- for (int i = 0, n = numentityfields(); i < n; ++i) \
- { \
+ if (!this.spawnfunc_checked) { \
+ for (int i = 0, n = numentityfields(); i < n; ++i) { \
string value = getentityfieldstring(i, this); \
string fieldname = entityfieldname(i); \
whitelist(_spawnfunc_checktypes) \
LOG_WARNF(_("Entity field %s.%s (%s) is not whitelisted. If you believe this is an error, please file an issue."), #id, fieldname, value); \
} \
this.spawnfunc_checked = true; \
+ if (this) { \
+ /* not worldspawn, delay spawn */ \
+ __spawnfunc_defer(this, __spawnfunc_##id); \
+ } else { \
+ /* world might not be "worldspawn" */ \
+ this.__spawnfunc_constructor = __spawnfunc_##id; \
+ } \
} \
- __spawnfunc_##id(this); \
+ if (dospawn) { __spawnfunc_##id(this); } \
if (__spawnfunc_unreachable_workaround) return; \
} \
void __spawnfunc_##id(entity this)
void stats_get() {}
#define STAT(...) EVAL_STAT(OVERLOAD(STAT, __VA_ARGS__))
#define EVAL_STAT(...) __VA_ARGS__
- #define STAT_1(id) STAT_2(id, NULL)
- #define STAT_2(id, cl) (0, _STAT(id))
+ #define STAT_1(id) (RVALUE, _STAT(id))
+ #define STAT_2(id, cl) STAT_1(id)
#define getstat_int(id) getstati(id, 0, 24)
#define getstat_bool(id) boolean(getstati(id))
}
#define REGISTER_STAT_3(x, T, expr) REGISTER_STAT_2(x, T)
#elif defined(SVQC)
+ /** Internal use only */
+ entity STATS;
/** Add all registered stats, access with `STAT(ID, player)` or `.type stat = _STAT(ID); player.stat` */
void stats_add() {}
- #define STAT(id, cl) (cl)._STAT(id)
+ #define STAT(...) EVAL_STAT(OVERLOAD_(STAT, __VA_ARGS__))
+ #define EVAL_STAT(...) __VA_ARGS__
+ #define STAT_1(id) (RVALUE, STAT_2(id, STATS))
+ #define STAT_2(id, cl) (cl)._STAT(id)
#define addstat_int(id, fld) addstat(id, AS_INT, fld)
#define addstat_bool(id, fld) addstat(id, AS_INT, fld)
const int AS_FLOAT = 8;
.int __stat_null;
- /** Prevent engine stats being sent */
- STATIC_INIT(stats_clear)
+ STATIC_INIT(stats)
{
+ STATS = new(stats);
+ // Prevent engine stats being sent
int r = STATS_ENGINE_RESERVE;
for (int i = 0, n = 256 - r; i < n; ++i) {
#define X(_, name, id) if (i == id) continue;
addstat_##T(STAT_##id.m_id, fld); \
}
void GlobalStats_update(entity this) {}
+ /** TODO: do we want the global copy to update? */
#define REGISTER_STAT_3(id, T, expr) \
REGISTER_STAT_2(id, T); \
[[accumulate]] void GlobalStats_update(entity this) { STAT(id, this) = (expr); } \
- STATIC_INIT(worldstat_##id) { entity this = NULL; STAT(id, this) = (expr); }
+ STATIC_INIT(worldstat_##id) { entity this = STATS; STAT(id, this) = (expr); }
#else
#define REGISTER_STAT_2(id, type)
#define REGISTER_STAT_3(id, T, expr)
float autocvar_g_maxplayers_spectator_blocktime;
float autocvar_g_maxpushtime;
float autocvar_g_maxspeed;
-#define autocvar_g_instagib cvar("g_instagib")
-bool autocvar_g_instagib_damagedbycontents = true;
-bool autocvar_g_instagib_blaster_keepdamage = false;
-bool autocvar_g_instagib_blaster_keepforce = false;
-bool autocvar_g_instagib_mirrordamage;
-bool autocvar_g_instagib_friendlypush = true;
+bool autocvar_g_instagib;
#define autocvar_g_mirrordamage cvar("g_mirrordamage")
#define autocvar_g_mirrordamage_virtual cvar("g_mirrordamage_virtual")
bool autocvar_g_mirrordamage_onlyweapons;
float autocvar_g_monsters_healthbars;
bool autocvar_g_monsters_lineofsight = true;
//bool autocvar_g_monsters_ignoretraces = true;
-#define autocvar_g_bloodloss cvar("g_bloodloss")
bool autocvar_g_nades;
bool autocvar_g_nades_override_dropweapon = true;
vector autocvar_g_nades_throw_offset;
o.y += random() * (world.maxs.y - world.mins.y);
o.z += random() * (world.maxs.z - world.mins.z);
- tracebox(o, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), o - '0 0 32768', MOVE_WORLDONLY, NULL);
+ tracebox(o, STAT(PL_MIN), STAT(PL_MAX), o - '0 0 32768', MOVE_WORLDONLY, NULL);
if(trace_fraction == 1)
continue;
WinningConditionHelper(this); // set worldstatus
world_initialized = 1;
+ __spawnfunc_spawn_all();
}
spawnfunc(light)
+#include "resources.qh"
/// \file
/// \brief Source file that contains implementation of the resource system.
/// \author Lyberta
-/// \copyright GNU GPLv3 or any later version.
-
-#include "resources.qh"
+/// \copyright GNU GPLv2 or any later version.
#include "autocvars.qh"
#include "miscfunctions.qh"
+#pragma once
/// \file
/// \brief Header file that describes the resource system.
/// \author Lyberta
-/// \copyright GNU GPLv3 or any later version.
-
-#pragma once
+/// \copyright GNU GPLv2 or any later version.
/// \brief Unconditional maximum amount of resources the entity can have.
const int RESOURCE_AMOUNT_HARD_LIMIT = 999;
.string gametypefilter;
.string cvarfilter;
bool DoesQ3ARemoveThisEntity(entity this);
+
+/**
+ * Evaluate an expression of the form: [+ | -]? [var[op]val | [op]var | val | var] ...
+ * +: all must match. this is the default
+ * -: one must NOT match
+ *
+ * var>x
+ * var<x
+ * var>=x
+ * var<=x
+ * var==x
+ * var!=x
+ * var===x
+ * var!==x
+ */
+bool expr_evaluate(string s)
+{
+ bool ret = false;
+ if (str2chr(s, 0) == '+') {
+ s = substring(s, 1, -1);
+ } else if (str2chr(s, 0) == '-') {
+ ret = true;
+ s = substring(s, 1, -1);
+ }
+ bool expr_fail = false;
+ for (int i = 0, n = tokenize_console(s); i < n; ++i) {
+ int o;
+ string k, v;
+ s = argv(i);
+ #define X(expr) \
+ if (expr) { \
+ continue; \
+ } else { \
+ expr_fail = true; \
+ break; \
+ }
+ #define BINOP(op, len, expr) \
+ if ((o = strstrofs(s, op, 0)) >= 0) { \
+ k = substring(s, 0, o); \
+ v = substring(s, o + len, -1); \
+ X(expr); \
+ }
+ BINOP(">=", 2, cvar(k) >= stof(v));
+ BINOP("<=", 2, cvar(k) <= stof(v));
+ BINOP(">", 1, cvar(k) > stof(v));
+ BINOP("<", 1, cvar(k) < stof(v));
+ BINOP("==", 2, cvar(k) == stof(v));
+ BINOP("!=", 2, cvar(k) != stof(v));
+ BINOP("===", 3, cvar_string(k) == v);
+ BINOP("!==", 3, cvar_string(k) != v);
+ {
+ k = s;
+ bool b = true;
+ if (str2chr(k, 0) == '!') {
+ k = substring(s, 1, -1);
+ b = false;
+ }
+ float f = stof(k);
+ bool isnum = ftos(f) == k;
+ X(boolean(isnum ? f : cvar(k)) == b);
+ }
+ #undef BINOP
+ #undef X
+ }
+ if (!expr_fail) {
+ ret = !ret;
+ }
+ // now ret is true if we want to keep the item, and false if we want to get rid of it
+ return ret;
+}
+
void SV_OnEntityPreSpawnFunction(entity this)
{
__spawnfunc_expecting = true;
if (this.gametypefilter != "")
if (!isGametypeInFilter(MapInfo_LoadedGametype, teamplay, have_team_spawns, this.gametypefilter))
{
- delete(this);
- __spawnfunc_expecting = false;
- return;
+ goto cleanup;
}
- if(this.cvarfilter != "")
- {
- float n, i, o, inv;
- string s, k, v;
- inv = 0;
-
- s = this.cvarfilter;
- if(substring(s, 0, 1) == "+")
- {
- s = substring(s, 1, -1);
- }
- else if(substring(s, 0, 1) == "-")
- {
- inv = 1;
- s = substring(s, 1, -1);
- }
-
- n = tokenize_console(s);
- for(i = 0; i < n; ++i)
- {
- s = argv(i);
- // syntax:
- // var>x
- // var<x
- // var>=x
- // var<=x
- // var==x
- // var!=x
- // var===x
- // var!==x
- if((o = strstrofs(s, ">=", 0)) >= 0)
- {
- k = substring(s, 0, o);
- v = substring(s, o+2, -1);
- if(cvar(k) < stof(v))
- goto cvar_fail;
- }
- else if((o = strstrofs(s, "<=", 0)) >= 0)
- {
- k = substring(s, 0, o);
- v = substring(s, o+2, -1);
- if(cvar(k) > stof(v))
- goto cvar_fail;
- }
- else if((o = strstrofs(s, ">", 0)) >= 0)
- {
- k = substring(s, 0, o);
- v = substring(s, o+1, -1);
- if(cvar(k) <= stof(v))
- goto cvar_fail;
- }
- else if((o = strstrofs(s, "<", 0)) >= 0)
- {
- k = substring(s, 0, o);
- v = substring(s, o+1, -1);
- if(cvar(k) >= stof(v))
- goto cvar_fail;
- }
- else if((o = strstrofs(s, "==", 0)) >= 0)
- {
- k = substring(s, 0, o);
- v = substring(s, o+2, -1);
- if(cvar(k) != stof(v))
- goto cvar_fail;
- }
- else if((o = strstrofs(s, "!=", 0)) >= 0)
- {
- k = substring(s, 0, o);
- v = substring(s, o+2, -1);
- if(cvar(k) == stof(v))
- goto cvar_fail;
- }
- else if((o = strstrofs(s, "===", 0)) >= 0)
- {
- k = substring(s, 0, o);
- v = substring(s, o+2, -1);
- if(cvar_string(k) != v)
- goto cvar_fail;
- }
- else if((o = strstrofs(s, "!==", 0)) >= 0)
- {
- k = substring(s, 0, o);
- v = substring(s, o+2, -1);
- if(cvar_string(k) == v)
- goto cvar_fail;
- }
- else if(substring(s, 0, 1) == "!")
- {
- k = substring(s, 1, -1);
- if(cvar(k))
- goto cvar_fail;
- }
- else
- {
- k = s;
- if (!cvar(k))
- goto cvar_fail;
- }
- }
- inv = !inv;
-LABEL(cvar_fail)
- // now inv is 1 if we want to keep the item, and 0 if we want to get rid of it
- if (!inv)
- {
- //print("cvarfilter fail\n");
- delete(this);
- __spawnfunc_expecting = false;
- return;
- }
+ if (this.cvarfilter != "" && !expr_evaluate(this.cvarfilter)) {
+ goto cleanup;
}
- if(DoesQ3ARemoveThisEntity(this))
- {
- delete(this);
- __spawnfunc_expecting = false;
- return;
+ if (DoesQ3ARemoveThisEntity(this)) {
+ goto cleanup;
}
set_movetype(this, this.movetype);
- if(this.monster_attack)
+ if (this.monster_attack) {
IL_PUSH(g_monster_targets, this);
+ }
// support special -1 and -2 angle from radiant
- if (this.angles == '0 -1 0')
+ if (this.angles == '0 -1 0') {
this.angles = '-90 0 0';
- else if (this.angles == '0 -2 0')
+ } else if (this.angles == '0 -2 0') {
this.angles = '+90 0 0';
-
- if(this.originjitter.x != 0)
- this.origin_x = this.origin.x + (random() * 2 - 1) * this.originjitter.x;
- if(this.originjitter.y != 0)
- this.origin_y = this.origin.y + (random() * 2 - 1) * this.originjitter.y;
- if(this.originjitter.z != 0)
- this.origin_z = this.origin.z + (random() * 2 - 1) * this.originjitter.z;
- if(this.anglesjitter.x != 0)
- this.angles_x = this.angles.x + (random() * 2 - 1) * this.anglesjitter.x;
- if(this.anglesjitter.y != 0)
- this.angles_y = this.angles.y + (random() * 2 - 1) * this.anglesjitter.y;
- if(this.anglesjitter.z != 0)
- this.angles_z = this.angles.z + (random() * 2 - 1) * this.anglesjitter.z;
- if(this.anglejitter != 0)
- this.angles_y = this.angles.y + (random() * 2 - 1) * this.anglejitter;
-
- if(MUTATOR_CALLHOOK(OnEntityPreSpawn, this))
- {
- delete(this);
- __spawnfunc_expecting = false;
- return;
+ }
+
+ #define X(out, in) MACRO_BEGIN \
+ if (in != 0) { out = out + (random() * 2 - 1) * in; } \
+ MACRO_END
+ X(this.origin.x, this.originjitter.x); X(this.origin.y, this.originjitter.y); X(this.origin.z, this.originjitter.z);
+ X(this.angles.x, this.anglesjitter.x); X(this.angles.y, this.anglesjitter.y); X(this.angles.z, this.anglesjitter.z);
+ X(this.angles.y, this.anglejitter);
+ #undef X
+
+ if (MUTATOR_CALLHOOK(OnEntityPreSpawn, this)) {
+ goto cleanup;
}
+ return;
+LABEL(cleanup)
+ builtin_remove(this);
+ __spawnfunc_expecting = false;
}
void WarpZone_PostInitialize_Callback()
#pragma once
+
+bool expr_evaluate(string s);
// find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds
get_mi_min_max(1);
- world.mins = mi_min;
- world.maxs = mi_max;
+ // assign reflectively to avoid "assignment to world" warning
+ int done = 0; for (int i = 0, n = numentityfields(); i < n; ++i) {
+ string k = entityfieldname(i); vector v = (k == "mins") ? mi_min : (k == "maxs") ? mi_max : '0 0 0';
+ if (v) {
+ putentityfieldstring(i, world, sprintf("%d %d %d", v));
+ if (++done == 2) break;
+ }
+ }
// currently, NetRadiant's limit is 131072 qu for each side
// distance from one corner of a 131072qu cube to the opposite corner is approx. 227023 qu
// set the distance according to map size but don't go over the limit to avoid issues with float precision