#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; /** Not for production use, provides access to a dump of the entity's fields when it is parsed from map data */ noref string __fullspawndata; .string fullspawndata; // 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_qc(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); } // this function simply avoids expanding IL_NEW during compilation // for each spawning entity void g_spawn_queue_spawn() { g_spawn_queue = IL_NEW(); } 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_spawn(); \ __spawnfunc_expecting = 0; \ this = __spawnfunc_expect; \ __spawnfunc_expect = NULL; \ dospawn = false; \ } else { \ /* userland call */ \ assert(this); \ } \ if (!this.sourceLoc) { \ this.sourceLoc = __FILE__":"STR(__LINE__); \ } \ this.classname = #id; \ if (!this.spawnfunc_checked) { \ if (__fullspawndata) { \ /* not supported in old DP */ \ /* must be read inside the real spawnfunc */ \ this.fullspawndata = __fullspawndata; \ } \ this.spawnfunc_checked = true; \ if (this) { \ /* not worldspawn, delay spawn */ \ /* clear some dangerous fields (TODO: properly support these in the map!) */ \ this.think = func_null; \ this.nextthink = 0; \ __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