// DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) // Copyright (C) 2022 by Sally "TehRealSalt" Cochenour // Copyright (C) 2022 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- /// \file k_acs.c /// \brief Action Code Script implementation using ACSVM #include "k_acs.h" #include "doomtype.h" #include "doomdef.h" #include "doomstat.h" #include "z_zone.h" #include "w_wad.h" #include "i_system.h" #include "r_defs.h" #include "r_state.h" #include "p_polyobj.h" #include "taglist.h" #include "CAPI/BinaryIO.h" #include "CAPI/Environment.h" #include "CAPI/Module.h" #include "CAPI/PrintBuf.h" #include "CAPI/Scope.h" #include "CAPI/String.h" #include "CAPI/Thread.h" static ACSVM_Environment *ACSenv = NULL; /*-------------------------------------------------- ACSVM_Environment *ACS_GetEnvironment(void) See header file for description. --------------------------------------------------*/ ACSVM_Environment *ACS_GetEnvironment(void) { return ACSenv; } /*-------------------------------------------------- static void ACS_EnvBadAlloc(ACSVM_Environment *env, char const *what) ACSVM Environment hook. Runs in case of a memory allocation failure occuring. Environment state afterwards is unusable; the only thing safe to do is using ACSVM_FreeEnvironment. Input Arguments:- env - The ACS environment data. what - Error string. Return:- N/A --------------------------------------------------*/ static void ACS_EnvBadAlloc(ACSVM_Environment *env, char const *what) { (void)env; CONS_Alert(CONS_ERROR, "Error allocating memory for ACS (%s)\n", what); if (env == ACSenv) { // Restart the main environment. ACS_Shutdown(); I_RemoveExitFunc(ACS_Shutdown); // Since ACS_Init will add it again. ACS_Init(); } } /*-------------------------------------------------- static void ACS_EnvReadError(ACSVM_Environment *env, char const *what) ACSVM Environment hook. Runs when an ACS module fails to read. Environment state should be safe afterwards. Input Arguments:- env - The ACS environment data. what - Error string. Return:- N/A --------------------------------------------------*/ static void ACS_EnvReadError(ACSVM_Environment *env, char const *what) { (void)env; CONS_Alert(CONS_WARNING, "Error reading ACS module (%s)\n", what); } /*-------------------------------------------------- static void ACS_EnvSerialError(ACSVM_Environment *env, char const *what) ACSVM Environment hook. Runs when the ACS state fails to save or load. Environment state is safe in that it shouldn't be causing crashes, but it is indeterminate. Input Arguments:- env - The ACS environment data. what - Error string. Return:- N/A --------------------------------------------------*/ static void ACS_EnvSerialError(ACSVM_Environment *env, char const *what) { (void)env; CONS_Alert(CONS_WARNING, "Error serializing ACS state (%s)\n", what); } /*-------------------------------------------------- static void ACS_EnvConstruct(ACSVM_Environment *env) ACSVM Environment hook. Runs when the environment is initally created. Input Arguments:- env - The ACS environment data to construct. Return:- N/A --------------------------------------------------*/ static void ACS_EnvConstruct(ACSVM_Environment *env) { ACSVM_GlobalScope *global = ACSVM_Environment_GetGlobalScope(env, 0); // Activate global scope immediately, since we don't want it off. // Not that we're adding any modules to it, though. :p ACSVM_GlobalScope_SetActive(global, true); // Add the data & function pointers // See also: // - https://doomwiki.org/wiki/ACS0_instruction_set // - https://github.com/DavidPH/ACSVM/blob/master/ACSVM/CodeData.hpp // - https://github.com/DavidPH/ACSVM/blob/master/ACSVM/CodeList.hpp // 0 to 56: Implemented by ACSVM ACSVM_Environment_AddCodeDataACS0(env, 57, "", ACSVM_Code_CallFunc, 2, ACSVM_Environment_AddCallFunc(env, ACS_CF_Random)); ACSVM_Environment_AddCodeDataACS0(env, 58, "WW", ACSVM_Code_CallFunc, 0, ACSVM_Environment_AddCallFunc(env, ACS_CF_Random)); ACSVM_Environment_AddCodeDataACS0(env, 61, "", ACSVM_Code_CallFunc, 1, ACSVM_Environment_AddCallFunc(env, ACS_CF_TagWait)); ACSVM_Environment_AddCodeDataACS0(env, 62, "W", ACSVM_Code_CallFunc, 0, ACSVM_Environment_AddCallFunc(env, ACS_CF_TagWait)); ACSVM_Environment_AddCodeDataACS0(env, 63, "", ACSVM_Code_CallFunc, 1, ACSVM_Environment_AddCallFunc(env, ACS_CF_PolyWait)); ACSVM_Environment_AddCodeDataACS0(env, 64, "W", ACSVM_Code_CallFunc, 0, ACSVM_Environment_AddCallFunc(env, ACS_CF_PolyWait)); // 69 to 79: Implemented by ACSVM // 81 to 82: Implemented by ACSVM // 84 to 85: Implemented by ACSVM ACSVM_Environment_AddCodeDataACS0(env, 86, "", ACSVM_Code_CallFunc, 0, ACSVM_Environment_AddCallFunc(env, ACS_CF_EndPrint)); // 87 to 89: Implemented by ACSVM ACSVM_Environment_AddCodeDataACS0(env, 90, "", ACSVM_Code_CallFunc, 0, ACSVM_Environment_AddCallFunc(env, ACS_CF_PlayerCount)); ACSVM_Environment_AddCodeDataACS0(env, 91, "", ACSVM_Code_CallFunc, 0, ACSVM_Environment_AddCallFunc(env, ACS_CF_GameType)); ACSVM_Environment_AddCodeDataACS0(env, 92, "", ACSVM_Code_CallFunc, 0, ACSVM_Environment_AddCallFunc(env, ACS_CF_GameSpeed)); ACSVM_Environment_AddCodeDataACS0(env, 93, "", ACSVM_Code_CallFunc, 0, ACSVM_Environment_AddCallFunc(env, ACS_CF_Timer)); // 136 to 137: Implemented by ACSVM // 157: Implemented by ACSVM // 167 to 173: Implemented by ACSVM ACSVM_Environment_AddCodeDataACS0(env, 174, "BB", ACSVM_Code_CallFunc, 0, ACSVM_Environment_AddCallFunc(env, ACS_CF_Random)); // 175 to 179: Implemented by ACSVM // 181 to 189: Implemented by ACSVM // 203 to 217: Implemented by ACSVM // 225 to 243: Implemented by ACSVM // 253: Implemented by ACSVM // 256 to 257: Implemented by ACSVM // 263: Implemented by ACSVM ACSVM_Environment_AddCodeDataACS0(env, 270, "", ACSVM_Code_CallFunc, 0, ACSVM_Environment_AddCallFunc(env, ACS_CF_EndPrint)); // 273 to 275: Implemented by ACSVM // 291 to 325: Implemented by ACSVM // 330: Implemented by ACSVM // 349 to 361: Implemented by ACSVM // 363 to 380: Implemented by ACSVM } /*-------------------------------------------------- static void ACS_EnvLoadModule(ACSVM_Environment *env, ACSVM_Module *module) ACSVM Environment hook. Runs when a ACS module is being loaded. Input Arguments:- env - The ACS environment data. module - The ACS module being loaded. Return:- true when successful, otherwise false. Returning false will also call the ACS_EnvReadError hook. --------------------------------------------------*/ static bool ACS_EnvLoadModule(ACSVM_Environment *env, ACSVM_Module *module) { ACSVM_ModuleName name = ACSVM_Module_GetName(module); const char *str = ACSVM_String_GetStr(name.s); size_t lumpLen = 0; ACSVM_Byte *data = NULL; size_t size = 0; bool ret = false; (void)env; if (name.i == (size_t)LUMPERROR) { // No lump given for module. CONS_Alert(CONS_ERROR, "Bad lump for ACS module \"%s\"\n", str); return false; } lumpLen = W_LumpLength(name.i); if (W_IsLumpWad(name.i) == true || lumpLen == 0) { // The lump given is a virtual resource. // Try to grab it from there. virtres_t *vRes = vres_GetMap(name.i); virtlump_t *vLump = vres_Find(vRes, "BEHAVIOR"); CONS_Printf("Attempting to load ACS module from map's virtual resource...\n"); if (vLump != NULL) { data = Z_Calloc(vLump->size, PU_STATIC, NULL); memcpy(data, vLump->data, vLump->size); size = vLump->size; CONS_Printf("Successfully found BEHAVIOR lump.\n"); } else { CONS_Printf("No BEHAVIOR lump found.\n"); } } else { // It's a real lump. data = Z_Calloc(lumpLen, PU_STATIC, NULL); W_ReadLump(name.i, data); size = lumpLen; CONS_Printf("Loading ACS module directly from lump.\n"); } if (data != NULL && size > 0) { CONS_Printf("Reading bytecode of ACS module...\n"); ret = ACSVM_Module_ReadBytecode(module, data, size); } else { // Unlike Hexen, BEHAVIOR is not required. // Simply ignore in this instance. CONS_Printf("No data received, ignoring...\n"); ret = true; } Z_Free(data); return ret; } /*-------------------------------------------------- static bool ACS_EnvCheckTag(ACSVM_Environment const *env, ACSVM_Word type, ACSVM_Word tag) ACSVM Environment hook. Ran to determine whenever or not a thread should still be waiting on a tag movement. See: TagWait, PolyWait. Input Arguments:- env - The ACS environment data. type - The kind of level data we're waiting on. See also: acs_tagType_e. tag - The tag of said level data. Return:- true when the tag is done moving and execution can continue, or false to keep the thread paused. --------------------------------------------------*/ static bool ACS_EnvCheckTag(ACSVM_Environment const *env, ACSVM_Word type, ACSVM_Word tag) { (void)env; switch (type) { case ACS_TAGTYPE_SECTOR: { INT32 secnum = -1; TAG_ITER_SECTORS(tag, secnum) { sector_t *sec = §ors[secnum]; if (sec->floordata != NULL || sec->ceilingdata != NULL) { return false; } } return true; } case ACS_TAGTYPE_POLYOBJ: { const polyobj_t *po = Polyobj_GetForNum(tag); return (po == NULL || po->thinker == NULL); } } return true; } /*-------------------------------------------------- void ACS_Init(void) See header file for description. --------------------------------------------------*/ void ACS_Init(void) { // Initialize ACS on engine start-up. ACSVM_EnvironmentFuncs funcs = {0}; funcs.bad_alloc = ACS_EnvBadAlloc; funcs.readError = ACS_EnvReadError; funcs.serialError = ACS_EnvSerialError; funcs.ctor = ACS_EnvConstruct; funcs.loadModule = ACS_EnvLoadModule; funcs.checkTag = ACS_EnvCheckTag; ACSenv = ACSVM_AllocEnvironment(&funcs, NULL); I_AddExitFunc(ACS_Shutdown); } /*-------------------------------------------------- void ACS_Shutdown(void) See header file for description. --------------------------------------------------*/ void ACS_Shutdown(void) { // Delete ACS environment. ACSVM_FreeEnvironment(ACSenv); ACSenv = NULL; } /*-------------------------------------------------- static void ACS_ResetHub(ACSVM_GlobalScope *global) Shortcut function to quickly free the only hub scope Ring Racers uses. Input Arguments:- global - The global scope to free the hub from. Return:- N/A --------------------------------------------------*/ static void ACS_ResetHub(ACSVM_GlobalScope *global) { ACSVM_HubScope *hub = ACSVM_GlobalScope_GetHubScope(global, 0); ACSVM_GlobalScope_FreeHubScope(global, hub); } /*-------------------------------------------------- static void ACS_ResetMap(ACSVM_HubScope *hub) Shortcut function to quickly free the only map scope Ring Racers uses. Input Arguments:- hub - The hub scope to free the map from. Return:- N/A --------------------------------------------------*/ static void ACS_ResetMap(ACSVM_HubScope *hub) { ACSVM_MapScope *map = ACSVM_HubScope_GetMapScope(hub, 0); ACSVM_HubScope_FreeMapScope(hub, map); } /*-------------------------------------------------- void ACS_LoadLevelScripts(size_t mapID) See header file for description. --------------------------------------------------*/ void ACS_LoadLevelScripts(size_t mapID) { ACSVM_Environment *env = ACSenv; ACSVM_StringTable *strTab = ACSVM_Environment_GetStringTable(env); ACSVM_GlobalScope *global = NULL; ACSVM_HubScope *hub = NULL; ACSVM_MapScope *map = NULL; ACSVM_Module **modules = NULL; size_t modules_len = 0; size_t modules_size = 4; global = ACSVM_Environment_GetGlobalScope(ACSenv, 0); // Just some notes on how Hexen's scopes work, if anyone // intends to implement proper hub logic: // The integer is an ID for which hub / map it is, // and instead sets active according to which ones // should run, since you can go between them. // But I didn't intend on implementing these features, // since hubs aren't planned for Ring Racers (although // they might be useful for SRB2), and I intentionally // avoided implementing global ACS (since Lua would be // a better language to do that kind of code). // Since we literally only are using map scope, we can // just free everything between every level. But if // hubs are to be implemented, this logic would need // to be far more sophisticated. // Reset hub scope, even if we are not using it. ACS_ResetHub(global); hub = ACSVM_GlobalScope_GetHubScope(global, 0); ACSVM_HubScope_SetActive(hub, true); // Start up new map scope. ACS_ResetMap(hub); map = ACSVM_HubScope_GetMapScope(hub, 0); ACSVM_MapScope_SetActive(map, true); // Allocate module list. modules = Z_Calloc(modules_size * sizeof(ACSVM_Module *), PU_STATIC, NULL); // Insert BEHAVIOR lump into the list. { char const *str = mapheaderinfo[mapID]->lumpname; size_t len = strlen(str); size_t hash = ACSVM_StrHash(str, len); ACSVM_ModuleName name = {0}; name.s = ACSVM_StringTable_GetStringByData(strTab, str, len, hash); name.i = mapheaderinfo[mapID]->lumpnum; if (modules_len >= modules_size) { modules_size *= 2; modules = Z_Realloc(modules, modules_size * sizeof(ACSVM_Module *), PU_STATIC, &modules); } modules[modules_len] = ACSVM_Environment_GetModule(env, name); modules_len++; } if (modules_len > 0) { // Register the modules with map scope. ACSVM_MapScope_AddModules(map, modules, modules_len); } // Start OPEN scripts. ACSVM_MapScope_ScriptStartType(map, 1, NULL, 0, NULL, NULL); } /*-------------------------------------------------- void ACS_Tick(void) See header file for description. --------------------------------------------------*/ void ACS_Tick(void) { ACSVM_Environment *env = ACSenv; if (ACSVM_Environment_HasActiveThread(env) == false) { return; } ACSVM_Environment_Exec(env); }