#pragma once // remove this ifdef when client or menu will actually make use of this stuff #ifdef SVQC /** If this global exists, only functions with spawnfunc_ name prefix qualify as spawn functions */ noref bool require_spawnfunc_prefix; .bool spawnfunc_checked; // Optional type checking; increases compile time too much to be enabled by default #if 0 bool entityfieldassignablefromeditor(int i) { switch (entityfieldtype(i)) { case FIELD_STRING: case FIELD_FLOAT: case FIELD_VECTOR: return true; } return false; } #define _spawnfunc_checktypes(fld) \ if (s == #fld) \ if (!entityfieldassignablefromeditor(i)) LOG_FATALF("Entity field '%s' cannot be whitelisted", s); #else #define _spawnfunc_checktypes(fld) #endif #define _spawnfunc_check(fld) \ if (s == #fld) continue; 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, target, string_null) \ X(string, target2, string_null) \ X(string, target3, string_null) \ X(string, target4, string_null) \ 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 #ifdef SVQC void _SV_OnEntityPreSpawnFunction(entity this); #endif 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 #ifdef SVQC _SV_OnEntityPreSpawnFunction(e); if (wasfreed(e)) { return; } #endif e.__spawnfunc_constructor(e); } #define FIELD_SCALAR(fld, n) \ fld(n) #define FIELD_VEC(fld, n) \ fld(n) \ fld(n##_x) \ fld(n##_y) \ fld(n##_z) #define FIELDS_NONE(fld) #define FIELDS_ALL(fld) if (false) #define FIELDS_COMMON(fld) \ FIELD_SCALAR(fld, classname) \ FIELD_SCALAR(fld, sourceLoc) \ FIELD_SCALAR(fld, spawnfunc_checked) \ FIELD_VEC(fld, origin) \ /**/ #define FIELDS_UNION(fld) \ FIELD_SCALAR(fld, Version) \ FIELD_SCALAR(fld, ammo_cells) \ FIELD_SCALAR(fld, ammo_nails) \ FIELD_SCALAR(fld, ammo_rockets) \ FIELD_SCALAR(fld, antiwall_flag) \ FIELD_SCALAR(fld, armorvalue) \ FIELD_SCALAR(fld, atten) \ FIELD_SCALAR(fld, bgmscriptdecay) \ FIELD_SCALAR(fld, bgmscriptsustain) \ FIELD_SCALAR(fld, bgmscript) \ FIELD_SCALAR(fld, button0) \ FIELD_SCALAR(fld, chmap) \ FIELD_SCALAR(fld, cnt) \ FIELD_SCALAR(fld, colormap) \ FIELD_SCALAR(fld, count) \ FIELD_SCALAR(fld, curvetarget) \ FIELD_SCALAR(fld, cvarfilter) \ FIELD_SCALAR(fld, debrisdamageforcescale) \ FIELD_SCALAR(fld, debrisfadetime) \ FIELD_SCALAR(fld, debrismovetype) \ FIELD_SCALAR(fld, debrisskin) \ FIELD_SCALAR(fld, debristimejitter) \ FIELD_SCALAR(fld, debristime) \ FIELD_SCALAR(fld, debris) \ FIELD_SCALAR(fld, delay) \ FIELD_SCALAR(fld, dmgtime) \ FIELD_SCALAR(fld, dmg) \ FIELD_SCALAR(fld, dmg_edge) \ FIELD_SCALAR(fld, dmg_force) \ FIELD_SCALAR(fld, dmg_radius) \ FIELD_SCALAR(fld, effects) \ FIELD_SCALAR(fld, falloff) \ FIELD_SCALAR(fld, flags) \ FIELD_SCALAR(fld, fog) \ FIELD_SCALAR(fld, frags) \ FIELD_SCALAR(fld, frame) \ FIELD_SCALAR(fld, gametype) \ FIELD_SCALAR(fld, gametypefilter) \ FIELD_SCALAR(fld, geomtype) \ FIELD_SCALAR(fld, gravity) \ FIELD_SCALAR(fld, health) \ FIELD_SCALAR(fld, height) \ FIELD_SCALAR(fld, impulse) \ FIELD_SCALAR(fld, killtarget) \ FIELD_SCALAR(fld, lerpfrac) \ FIELD_SCALAR(fld, light_lev) \ FIELD_SCALAR(fld, lip) \ FIELD_SCALAR(fld, loddistance1) \ FIELD_SCALAR(fld, lodmodel1) \ FIELD_SCALAR(fld, ltime) \ FIELD_SCALAR(fld, map) \ FIELD_SCALAR(fld, max_health) \ FIELD_SCALAR(fld, mdl) \ FIELD_SCALAR(fld, message2) \ FIELD_SCALAR(fld, message) \ FIELD_SCALAR(fld, modelindex) \ FIELD_SCALAR(fld, modelscale) \ FIELD_SCALAR(fld, model) \ FIELD_SCALAR(fld, monsterid) \ FIELD_SCALAR(fld, monster_moveflags) \ FIELD_SCALAR(fld, monster_name) \ FIELD_SCALAR(fld, movetype) \ FIELD_SCALAR(fld, move_movetype) \ FIELD_SCALAR(fld, netname) \ FIELD_SCALAR(fld, nextthink) \ FIELD_SCALAR(fld, noalign) \ FIELD_SCALAR(fld, noise1) \ FIELD_SCALAR(fld, noise2) \ FIELD_SCALAR(fld, noise3) \ FIELD_SCALAR(fld, noise) \ FIELD_SCALAR(fld, phase) \ FIELD_SCALAR(fld, platmovetype) \ FIELD_SCALAR(fld, race_place) \ FIELD_SCALAR(fld, radius) \ FIELD_SCALAR(fld, respawntimestart) \ FIELD_SCALAR(fld, respawntimejitter) \ FIELD_SCALAR(fld, respawntime) \ FIELD_SCALAR(fld, restriction) \ FIELD_SCALAR(fld, scale) \ FIELD_SCALAR(fld, skin) \ FIELD_SCALAR(fld, solid) \ FIELD_SCALAR(fld, sound1) \ FIELD_SCALAR(fld, sounds) \ FIELD_SCALAR(fld, spawnflags) \ FIELD_SCALAR(fld, spawnmob) \ FIELD_SCALAR(fld, speed) \ FIELD_SCALAR(fld, strength) \ FIELD_SCALAR(fld, style) \ FIELD_SCALAR(fld, target2) \ FIELD_SCALAR(fld, target3) \ FIELD_SCALAR(fld, target4) \ FIELD_SCALAR(fld, targetname) \ FIELD_SCALAR(fld, target) \ FIELD_SCALAR(fld, target_random) \ FIELD_SCALAR(fld, target_range) \ FIELD_SCALAR(fld, team) \ FIELD_SCALAR(fld, trigger_reverse) \ FIELD_SCALAR(fld, turret_scale_health) \ FIELD_SCALAR(fld, turret_scale_range) \ FIELD_SCALAR(fld, turret_scale_respawn) \ FIELD_SCALAR(fld, volume) \ FIELD_SCALAR(fld, wait) \ FIELD_SCALAR(fld, warpzone_fadeend) \ FIELD_SCALAR(fld, warpzone_fadestart) \ FIELD_SCALAR(fld, weapon) \ FIELD_SCALAR(fld, worldtype) \ FIELD_VEC(fld, absmax) \ FIELD_VEC(fld, absmin) \ FIELD_VEC(fld, angles) \ FIELD_VEC(fld, avelocity) \ FIELD_VEC(fld, beam_color)\ FIELD_VEC(fld, debrisavelocityjitter) \ FIELD_VEC(fld, debrisvelocity) \ FIELD_VEC(fld, debrisvelocityjitter) \ FIELD_VEC(fld, color) \ FIELD_VEC(fld, mangle) \ FIELD_VEC(fld, maxs) \ FIELD_VEC(fld, mins) \ FIELD_VEC(fld, modelscale_vec) \ FIELD_VEC(fld, velocity) \ /**/ ERASEABLE void _checkWhitelisted(entity this, string id) { for (int i = 0, n = numentityfields(); i < n; ++i) { string value = getentityfieldstring(i, this); string s = entityfieldname(i); FIELDS_UNION(_spawnfunc_checktypes) if (value == "") continue; if (s == "") continue; FIELDS_COMMON(_spawnfunc_check) FIELDS_UNION(_spawnfunc_check) LOG_WARNF(_("Entity field %s.%s (%s) is not whitelisted. If you believe this is an error, please file an issue."), id, s, value); } } noref bool __spawnfunc_first; #define spawnfunc(id) \ void __spawnfunc_##id(entity this); \ ACCUMULATE void spawnfunc_##id(entity this) \ { \ if (!__spawnfunc_first) { \ __spawnfunc_first = true; \ static_init_early(); \ } \ bool dospawn = true; \ if (__spawnfunc_expecting > 1) { __spawnfunc_expecting = 0; } \ else if (__spawnfunc_expecting) { \ /* engine call */ \ if (!g_spawn_queue) { g_spawn_queue = IL_NEW(); } \ __spawnfunc_expecting = 0; \ this = __spawnfunc_expect; \ __spawnfunc_expect = NULL; \ dospawn = false; \ } else { \ /* userland call */ \ assert(this); \ } \ if (!this.sourceLoc) { \ this.sourceLoc = __FILE__":"STR(__LINE__); \ } \ if (!this.spawnfunc_checked) { \ _checkWhitelisted(this, #id); \ 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; \ } \ } \ if (dospawn) { __spawnfunc_##id(this); } \ if (__spawnfunc_unreachable_workaround) return; \ } \ void __spawnfunc_##id(entity this) #endif