diff --git a/engine/openbor.c b/engine/openbor.c index 7cfeca536..b278d0df8 100644 --- a/engine/openbor.c +++ b/engine/openbor.c @@ -852,6 +852,10 @@ Script level_script; //execute when level start Script endlevel_script; //execute when level finished Script update_script; //execute when ingame update Script updated_script; //execute when ingame update finished +Script update_logic_script; //execute before each logical tick +Script updated_logic_script; //execute after each logical tick +Script model_load_script; //execute after any model finishes loading +Script model_unload_script; //execute before any model is unloaded Script loading_script; // in loading screen Script input_script_all; //keyscript for all players Script key_script_all; //keyscript for all players @@ -1182,6 +1186,10 @@ void init_scripts() Script_Global_Init(); Script_Init(&update_script, "update", NULL, 1); Script_Init(&updated_script, "updated", NULL, 1); + Script_Init(&update_logic_script, "updatelogic", NULL, 1); + Script_Init(&updated_logic_script, "updatedlogic", NULL, 1); + Script_Init(&model_load_script, "modelload", NULL, 1); + Script_Init(&model_unload_script, "modelunload", NULL, 1); Script_Init(&level_script, "level", NULL, 1); Script_Init(&endlevel_script, "endlevel", NULL, 1); Script_Init(&input_script_all, "inputall", NULL, 1); @@ -1230,6 +1238,22 @@ void load_scripts() { Script_Clear(&updated_script, 2); } + if(!load_script(&update_logic_script, "data/scripts/updatelogic.c")) + { + Script_Clear(&update_logic_script, 2); + } + if(!load_script(&updated_logic_script, "data/scripts/updatedlogic.c")) + { + Script_Clear(&updated_logic_script, 2); + } + if(!load_script(&model_load_script, "data/scripts/modelload.c")) + { + Script_Clear(&model_load_script, 2); + } + if(!load_script(&model_unload_script, "data/scripts/modelunload.c")) + { + Script_Clear(&model_unload_script, 2); + } if(!load_script(&level_script, "data/scripts/level.c")) { Script_Clear(&level_script, 2); @@ -1352,6 +1376,10 @@ void load_scripts() } Script_Compile(&update_script); Script_Compile(&updated_script); + Script_Compile(&update_logic_script); + Script_Compile(&updated_logic_script); + Script_Compile(&model_load_script); + Script_Compile(&model_unload_script); Script_Compile(&level_script); Script_Compile(&endlevel_script); Script_Compile(&input_script_all); @@ -1405,6 +1433,10 @@ void clear_scripts() //and will never have another chance to be loaded, so just clear the variable list in it Script_Clear(&update_script, 2); Script_Clear(&updated_script, 2); + Script_Clear(&update_logic_script, 2); + Script_Clear(&updated_logic_script, 2); + Script_Clear(&model_load_script, 2); + Script_Clear(&model_unload_script, 2); Script_Clear(&level_script, 2); Script_Clear(&endlevel_script, 2); Script_Clear(&input_script_all, 2); @@ -2455,7 +2487,7 @@ void execute_spawn_script(s_spawn_entry *p, entity *e) Script_Set_Local_Variant(cs, "spawnz", &tempvar); tempvar.dblVal = (DOUBLE)p->position.y; Script_Set_Local_Variant(cs, "spawny", &tempvar); - Script_Set_Local_Variant(cs, "spawna", &tempvar); // Legacy alias. + Script_Set_Local_Variant(cs, "spawna", &tempvar); ScriptVariant_ChangeType(&tempvar, VT_INTEGER); tempvar.lVal = (LONG)p->at; Script_Set_Local_Variant(cs, "spawnat", &tempvar); @@ -2467,8 +2499,8 @@ void execute_spawn_script(s_spawn_entry *p, entity *e) Script_Set_Local_Variant(cs, "self", &tempvar); Script_Set_Local_Variant(cs, "spawnx", &tempvar); Script_Set_Local_Variant(cs, "spawnz", &tempvar); - Script_Set_Local_Variant(cs, "spawna", &tempvar); Script_Set_Local_Variant(cs, "spawny", &tempvar); + Script_Set_Local_Variant(cs, "spawna", &tempvar); Script_Set_Local_Variant(cs, "spawnat", &tempvar); } } @@ -7777,15 +7809,101 @@ void cache_model_sprites(s_model *m, int ld) } } +/* +* Caskey, Damon V. +* 2026-08-21 +* +* Execute a model lifecycle script with the model template, +* stable cache index, and model name exposed as local values. +*/ +static void execute_model_lifecycle_script(Script *cs, s_model *model) +{ + ScriptVariant tempvar; + + if(!Script_IsInitialized(cs)) + { + return; + } + + ScriptVariant_Init(&tempvar); + + ScriptVariant_ChangeType(&tempvar, VT_PTR); + tempvar.ptrVal = (VOID *)model; + Script_Set_Local_Variant(cs, "model", &tempvar); + + ScriptVariant_ChangeType(&tempvar, VT_INTEGER); + tempvar.lVal = (LONG)model->index; + Script_Set_Local_Variant(cs, "modelindex", &tempvar); + + ScriptVariant_ChangeType(&tempvar, VT_STR); + tempvar.strVal = StrCache_CreateNewFrom(model_cache[model->index].name); + Script_Set_Local_Variant(cs, "modelname", &tempvar); + + Script_Execute(cs); + + ScriptVariant_Clear(&tempvar); + Script_Set_Local_Variant(cs, "model", &tempvar); + Script_Set_Local_Variant(cs, "modelindex", &tempvar); + Script_Set_Local_Variant(cs, "modelname", &tempvar); +} + +/* +* Caskey, Damon V. +* 2026-08-21 +* +* Execute model-owned setup before notifying the global +* model-load observer of a completed model parse. +*/ +static void execute_model_load_scripts(s_model *model) +{ + s_modelcache *cache = &model_cache[model->index]; + + execute_model_lifecycle_script(cache->load_script, model); + execute_model_lifecycle_script(&model_load_script, model); +} + +/* +* Caskey, Damon V. +* 2026-08-21 +* +* Execute model-owned teardown before notifying the global +* model-unload observer and beginning native destruction. +*/ +static void execute_model_unload_scripts(s_model *model) +{ + s_modelcache *cache = &model_cache[model->index]; + + execute_model_lifecycle_script(cache->unload_script, model); + execute_model_lifecycle_script(&model_unload_script, model); +} + // Unload single model from memory int free_model(s_model *model) { int i; + s_modelcache *cache; + if(!model) { return 0; } + + cache = &model_cache[model->index]; + + /* + * Ignore recursive removal from lifecycle callbacks and + * attempts to remove a model that has not finished loading. + */ + if(cache->lifecycle != MODEL_LIFECYCLE_LOADED) + { + return 0; + } + + cache->lifecycle = MODEL_LIFECYCLE_UNLOAD_EVENT; + execute_model_unload_scripts(model); + cache->lifecycle = MODEL_LIFECYCLE_UNLOADING; + printf("Unload '%s' ", model->name); if(hasFreetype(model, MF_ANIMLIST)) @@ -7878,10 +7996,14 @@ int free_model(s_model *model) } printf("."); - model_cache[model->index].model = NULL; + cache->model = NULL; deleteModel(model->name); printf("."); + Script_Clear(cache->load_script, 1); + Script_Clear(cache->unload_script, 1); + cache->lifecycle = MODEL_LIFECYCLE_UNLOADED; + printf("Done.\n"); return models_loaded--; @@ -12173,6 +12295,8 @@ void cache_model(char *name, char *path, int flag) model_cache[models_cached].path[len] = 0; model_cache[models_cached].loadflag = flag; + model_cache[models_cached].load_script = alloc_script(); + model_cache[models_cached].unload_script = alloc_script(); _peek_model_name(models_cached); ++models_cached; @@ -12186,6 +12310,12 @@ void free_modelcache() while(models_cached) { --models_cached; + Script_Clear(model_cache[models_cached].load_script, 2); + free(model_cache[models_cached].load_script); + model_cache[models_cached].load_script = NULL; + Script_Clear(model_cache[models_cached].unload_script, 2); + free(model_cache[models_cached].unload_script); + model_cache[models_cached].unload_script = NULL; free(model_cache[models_cached].name); model_cache[models_cached].name = NULL; free(model_cache[models_cached].path); @@ -15738,6 +15868,8 @@ s_model *load_cached_model(char *name, char *owner, char unload) borShutdown(1, "Fatal: No cache entry for '%s' within '%s'\n\n", name, owner); } + model_cache[cacheindex].lifecycle = MODEL_LIFECYCLE_LOADING; + // Get the text file name of model. filename = model_cache[cacheindex].path; printf(" from %s \n", filename); @@ -17407,6 +17539,36 @@ s_model *load_cached_model(char *name, char *owner, char unload) case CMD_MODEL_ONKILLSCRIPT: pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->onkill_script, "onkillscript", filename, 1, 0); break; + case CMD_MODEL_MODELLOADSCRIPT: + if(Script_IsInitialized(model_cache[cacheindex].load_script)) + { + Script_Clear(model_cache[cacheindex].load_script, 1); + } + pos += lcmHandleCommandScripts( + &arglist, + buf + pos, + model_cache[cacheindex].load_script, + "modelloadscript", + filename, + 1, + 0 + ); + break; + case CMD_MODEL_MODELUNLOADSCRIPT: + if(Script_IsInitialized(model_cache[cacheindex].unload_script)) + { + Script_Clear(model_cache[cacheindex].unload_script, 1); + } + pos += lcmHandleCommandScripts( + &arglist, + buf + pos, + model_cache[cacheindex].unload_script, + "modelunloadscript", + filename, + 1, + 0 + ); + break; case CMD_MODEL_DIDBLOCKSCRIPT: pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->didblock_script, "didblockscript", filename, 1, 0); break; @@ -21027,6 +21189,9 @@ s_model *load_cached_model(char *name, char *owner, char unload) if(!shutdownmessage) { + model_cache[cacheindex].lifecycle = MODEL_LIFECYCLE_LOAD_EVENT; + execute_model_load_scripts(newchar); + model_cache[cacheindex].lifecycle = MODEL_LIFECYCLE_LOADED; printf(" ...done!\n"); return newchar; } @@ -23481,6 +23646,8 @@ void free_level(s_level *lv) //offload scripts Script_Clear(&(lv->update_script), 2); Script_Clear(&(lv->updated_script), 2); + Script_Clear(&(lv->update_logic_script), 2); + Script_Clear(&(lv->updated_logic_script), 2); Script_Clear(&(lv->key_script), 2); Script_Clear(&(lv->level_script), 2); Script_Clear(&(lv->endlevel_script), 2); @@ -24481,6 +24648,8 @@ void load_level(char *filename) break; case CMD_LEVEL_UPDATESCRIPT: case CMD_LEVEL_UPDATEDSCRIPT: + case CMD_LEVEL_UPDATELOGICSCRIPT: + case CMD_LEVEL_UPDATEDLOGICSCRIPT: case CMD_LEVEL_KEYSCRIPT: case CMD_LEVEL_LEVELSCRIPT: case CMD_LEVEL_ENDLEVELSCRIPT: @@ -24494,6 +24663,14 @@ void load_level(char *filename) tempscript = &(level->updated_script); scriptname = "levelupdatedscript"; break; + case CMD_LEVEL_UPDATELOGICSCRIPT: + tempscript = &(level->update_logic_script); + scriptname = "levelupdatelogicscript"; + break; + case CMD_LEVEL_UPDATEDLOGICSCRIPT: + tempscript = &(level->updated_logic_script); + scriptname = "levelupdatedlogicscript"; + break; case CMD_LEVEL_KEYSCRIPT: tempscript = &(level->key_script); scriptname = "levelkeyscript"; @@ -50715,6 +50892,44 @@ void execute_updatedscripts() } } +/* +* Caskey, Damon V. +* 2026-08-21 +* +* Execute global and level scripts immediately before an +* engine logical clock tick is processed. +*/ +void execute_updatelogicscripts() +{ + if(Script_IsInitialized(&update_logic_script)) + { + Script_Execute(&update_logic_script); + } + if(level && Script_IsInitialized(&level->update_logic_script)) + { + Script_Execute(&level->update_logic_script); + } +} + +/* +* Caskey, Damon V. +* 2026-08-21 +* +* Execute global and level scripts after an engine logical +* clock tick is processed, before its clock value advances. +*/ +void execute_updatedlogicscripts() +{ + if(Script_IsInitialized(&updated_logic_script)) + { + Script_Execute(&updated_logic_script); + } + if(level && Script_IsInitialized(&level->updated_logic_script)) + { + Script_Execute(&level->updated_logic_script); + } +} + void draw_textobjs() { int i; @@ -51140,6 +51355,11 @@ void update(int ingame, int usevwait) while(_time < newtime) { + if(ingame == 1 || alwaysupdate) + { + execute_updatelogicscripts(); + } + if(ingame == 1) { update_scroller(); @@ -51193,6 +51413,12 @@ void update(int ingame, int usevwait) { update_ents(); } + + if(ingame == 1 || alwaysupdate) + { + execute_updatedlogicscripts(); + } + ++_time; } diff --git a/engine/openbor.h b/engine/openbor.h index cf46ee4ed..06159b03d 100644 --- a/engine/openbor.h +++ b/engine/openbor.h @@ -1072,7 +1072,9 @@ typedef enum LEVEL_PROPERTY_SCRIPT_LEVEL_END, // Script endlevel_script; LEVEL_PROPERTY_SCRIPT_LEVEL_START, // Script level_script; LEVEL_PROPERTY_SCRIPT_KEY, // Script key_script; + LEVEL_PROPERTY_SCRIPT_UPDATE_LOGIC, // Script update_logic_script; LEVEL_PROPERTY_SCRIPT_UPDATE, // Script update_script; + LEVEL_PROPERTY_SCRIPT_UPDATED_LOGIC, // Script updated_logic_script; LEVEL_PROPERTY_SCRIPT_UPDATED, // Script updated_script; LEVEL_PROPERTY_SCROLL_DIRECTION, // int scrolldir; LEVEL_PROPERTY_SCROLL_VELOCITY, // float scrollspeed; @@ -3809,6 +3811,17 @@ typedef struct char *name; char *path; s_model *model; + Script *load_script; + Script *unload_script; + enum + { + MODEL_LIFECYCLE_UNLOADED, + MODEL_LIFECYCLE_LOADING, + MODEL_LIFECYCLE_LOAD_EVENT, + MODEL_LIFECYCLE_LOADED, + MODEL_LIFECYCLE_UNLOAD_EVENT, + MODEL_LIFECYCLE_UNLOADING + } lifecycle; int loadflag; int selectable; } s_modelcache; @@ -4303,6 +4316,8 @@ typedef struct //---------------------scripts------------------------------- Script update_script; Script updated_script; + Script update_logic_script; + Script updated_logic_script; Script key_script; Script level_script; Script endlevel_script; diff --git a/engine/openborscript.c b/engine/openborscript.c index 655dd6ce7..112adb435 100644 --- a/engine/openborscript.c +++ b/engine/openborscript.c @@ -15778,7 +15778,6 @@ HRESULT openbor_loadmodel(ScriptVariant **varlist , ScriptVariant **pretvar, int //unload_model("name"); HRESULT openbor_unload_model(ScriptVariant **varlist , ScriptVariant **pretvar, int paramCount) { - LONG unload = 0; s_model *model; if(paramCount < 1) { @@ -15789,7 +15788,7 @@ HRESULT openbor_unload_model(ScriptVariant **varlist , ScriptVariant **pretvar, goto unload_model_error; } - model = load_cached_model(StrCache_Get(varlist[0]->strVal), "openbor_loadmodel", (char)unload); + model = findmodel(StrCache_Get(varlist[0]->strVal)); if(paramCount >= 1 && model) { diff --git a/engine/source/openborscript/commands.c b/engine/source/openborscript/commands.c index 88dd1d77a..9684ef3e1 100644 --- a/engine/source/openborscript/commands.c +++ b/engine/source/openborscript/commands.c @@ -513,6 +513,8 @@ List *createModelCommandList(void) LIST_ADD(CMD_MODEL_ONENTITYCOLLISIONSCRIPT, "onentitycollisionscript"); LIST_ADD(CMD_MODEL_ONFALLSCRIPT, "onfallscript"); LIST_ADD(CMD_MODEL_ONKILLSCRIPT, "onkillscript"); + LIST_ADD(CMD_MODEL_MODELLOADSCRIPT, "modelloadscript"); + LIST_ADD(CMD_MODEL_MODELUNLOADSCRIPT, "modelunloadscript"); LIST_ADD(CMD_MODEL_ONMODELCOPYSCRIPT, "onmodelcopyscript"); LIST_ADD(CMD_MODEL_ONMOVEYSCRIPT, "onmoveyscript"); LIST_ADD(CMD_MODEL_ONMOVEXSCRIPT, "onmovexscript"); @@ -758,7 +760,9 @@ List *createLevelCommandList(void) LIST_ADD(CMD_LEVEL_SPAWNSCRIPT, "spawnscript"); LIST_ADD(CMD_LEVEL_STAGENUMBER, "stagenumber"); LIST_ADD(CMD_LEVEL_TYPE, "type"); + LIST_ADD(CMD_LEVEL_UPDATEDLOGICSCRIPT, "updatedlogicscript"); LIST_ADD(CMD_LEVEL_UPDATEDSCRIPT, "updatedscript"); + LIST_ADD(CMD_LEVEL_UPDATELOGICSCRIPT, "updatelogicscript"); LIST_ADD(CMD_LEVEL_UPDATESCRIPT, "updatescript"); LIST_ADD(CMD_LEVEL_VBGSPEED, "vbgspeed"); LIST_ADD(CMD_LEVEL_WAIT, "wait"); diff --git a/engine/source/openborscript/commands.h b/engine/source/openborscript/commands.h index 297025ed5..a52c0737a 100644 --- a/engine/source/openborscript/commands.h +++ b/engine/source/openborscript/commands.h @@ -234,7 +234,9 @@ typedef enum levelCommand CMD_LEVEL_SPAWNSCRIPT, CMD_LEVEL_STAGENUMBER, CMD_LEVEL_TYPE, + CMD_LEVEL_UPDATEDLOGICSCRIPT, CMD_LEVEL_UPDATEDSCRIPT, + CMD_LEVEL_UPDATELOGICSCRIPT, CMD_LEVEL_UPDATESCRIPT, CMD_LEVEL_VBGSPEED, CMD_LEVEL_WAIT, @@ -620,6 +622,8 @@ typedef enum modelCommand CMD_MODEL_ONENTITYCOLLISIONSCRIPT, CMD_MODEL_ONFALLSCRIPT, CMD_MODEL_ONKILLSCRIPT, + CMD_MODEL_MODELLOADSCRIPT, + CMD_MODEL_MODELUNLOADSCRIPT, CMD_MODEL_ONMODELCOPYSCRIPT, CMD_MODEL_ONMOVEYSCRIPT, CMD_MODEL_ONMOVEXSCRIPT, diff --git a/engine/source/openborscript/constants.c b/engine/source/openborscript/constants.c index db610f02c..cd30d042c 100644 --- a/engine/source/openborscript/constants.c +++ b/engine/source/openborscript/constants.c @@ -1381,7 +1381,9 @@ bool mapstrings_transconst(ScriptVariant **varlist, int paramCount) ICMPCONST(LEVEL_PROPERTY_SCRIPT_LEVEL_END) ICMPCONST(LEVEL_PROPERTY_SCRIPT_LEVEL_START) ICMPCONST(LEVEL_PROPERTY_SCRIPT_KEY) + ICMPCONST(LEVEL_PROPERTY_SCRIPT_UPDATE_LOGIC) ICMPCONST(LEVEL_PROPERTY_SCRIPT_UPDATE) + ICMPCONST(LEVEL_PROPERTY_SCRIPT_UPDATED_LOGIC) ICMPCONST(LEVEL_PROPERTY_SCRIPT_UPDATED) ICMPCONST(LEVEL_PROPERTY_SCROLL_DIRECTION) ICMPCONST(LEVEL_PROPERTY_SCROLL_VELOCITY) diff --git a/engine/source/openborscript/level.c b/engine/source/openborscript/level.c index c4eb29089..2d1b2ed9b 100644 --- a/engine/source/openborscript/level.c +++ b/engine/source/openborscript/level.c @@ -729,6 +729,7 @@ HRESULT openbor_get_level_property(ScriptVariant **varlist, ScriptVariant **pret ScriptVariant_ChangeType(*pretvar, VT_PTR); (*pretvar)->ptrVal = &handle->endlevel_script; //} + break; case LEVEL_PROPERTY_SCRIPT_LEVEL_START: // Script level_script; @@ -737,6 +738,7 @@ HRESULT openbor_get_level_property(ScriptVariant **varlist, ScriptVariant **pret ScriptVariant_ChangeType(*pretvar, VT_PTR); (*pretvar)->ptrVal = &handle->level_script; //} + break; case LEVEL_PROPERTY_SCRIPT_KEY: @@ -745,6 +747,13 @@ HRESULT openbor_get_level_property(ScriptVariant **varlist, ScriptVariant **pret ScriptVariant_ChangeType(*pretvar, VT_PTR); (*pretvar)->ptrVal = &handle->key_script; //} + break; + + case LEVEL_PROPERTY_SCRIPT_UPDATE_LOGIC: + + ScriptVariant_ChangeType(*pretvar, VT_PTR); + (*pretvar)->ptrVal = &handle->update_logic_script; + break; case LEVEL_PROPERTY_SCRIPT_UPDATE: @@ -753,6 +762,13 @@ HRESULT openbor_get_level_property(ScriptVariant **varlist, ScriptVariant **pret ScriptVariant_ChangeType(*pretvar, VT_PTR); (*pretvar)->ptrVal = &handle->update_script; //} + break; + + case LEVEL_PROPERTY_SCRIPT_UPDATED_LOGIC: + + ScriptVariant_ChangeType(*pretvar, VT_PTR); + (*pretvar)->ptrVal = &handle->updated_logic_script; + break; case LEVEL_PROPERTY_SCRIPT_UPDATED: @@ -761,6 +777,7 @@ HRESULT openbor_get_level_property(ScriptVariant **varlist, ScriptVariant **pret ScriptVariant_ChangeType(*pretvar, VT_PTR); (*pretvar)->ptrVal = &handle->updated_script; //} + break; case LEVEL_PROPERTY_SCROLL_DIRECTION: @@ -1455,6 +1472,16 @@ HRESULT openbor_set_level_property(ScriptVariant **varlist, ScriptVariant **pret handle->key_script = *(Script *)arg_value->ptrVal; break; + case LEVEL_PROPERTY_SCRIPT_UPDATE_LOGIC: + + if(invalid_pointer_input) + { + goto error_local; + } + + handle->update_logic_script = *(Script *)arg_value->ptrVal; + break; + case LEVEL_PROPERTY_SCRIPT_UPDATE: if(invalid_pointer_input) @@ -1465,6 +1492,16 @@ HRESULT openbor_set_level_property(ScriptVariant **varlist, ScriptVariant **pret handle->update_script = *(Script *)arg_value->ptrVal; break; + case LEVEL_PROPERTY_SCRIPT_UPDATED_LOGIC: + + if(invalid_pointer_input) + { + goto error_local; + } + + handle->updated_logic_script = *(Script *)arg_value->ptrVal; + break; + case LEVEL_PROPERTY_SCRIPT_UPDATED: if(invalid_pointer_input) @@ -2504,4 +2541,3 @@ HRESULT openbor_changelevelproperty(ScriptVariant **varlist , ScriptVariant **pr printf("\n"); return E_FAIL; } -