// 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 call-funcs.cpp /// \brief Action Code Script: CallFunc instructions #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include "../doomtype.h" #include "../doomdef.h" #include "../doomstat.h" #include "../d_think.h" #include "../p_mobj.h" #include "../p_tick.h" #include "../w_wad.h" #include "../m_random.h" #include "../g_game.h" #include "../d_player.h" #include "../r_defs.h" #include "../r_state.h" #include "../p_polyobj.h" #include "../taglist.h" #include "../p_local.h" #include "../deh_tables.h" #include "../fastcmp.h" #include "../hu_stuff.h" #include "../s_sound.h" #include "../r_textures.h" #include "../m_cond.h" #include "../r_skins.h" #include "../k_battle.h" #include "../k_podium.h" } #include "call-funcs.hpp" #include "environment.hpp" #include "thread.hpp" #include "../cxxutil.hpp" using namespace srb2::acs; /*-------------------------------------------------- static bool ACS_GetMobjTypeFromString(const char *word, mobjtype_t *type) Helper function for CallFunc_ThingCount. Gets an object type from a string. Input Arguments:- word: The mobj class string. type: Variable to store the result in. Return:- true if successful, otherwise false. --------------------------------------------------*/ static bool ACS_GetMobjTypeFromString(const char *word, mobjtype_t *type) { if (fastncmp("MT_", word, 3)) { // take off the MT_ word += 3; } for (int i = 0; i < NUMMOBJFREESLOTS; i++) { if (!FREE_MOBJS[i]) { break; } if (fastcmp(word, FREE_MOBJS[i])) { *type = static_cast(static_cast(MT_FIRSTFREESLOT) + i); return true; } } for (int i = 0; i < MT_FIRSTFREESLOT; i++) { if (fastcmp(word, MOBJTYPE_LIST[i] + 3)) { *type = static_cast(i); return true; } } return false; } /*-------------------------------------------------- static bool ACS_GetSFXFromString(const char *word, sfxenum_t *type) Helper function for sound playing functions. Gets a SFX id from a string. Input Arguments:- word: The sound effect string. type: Variable to store the result in. Return:- true if successful, otherwise false. --------------------------------------------------*/ static bool ACS_GetSFXFromString(const char *word, sfxenum_t *type) { if (fastncmp("SFX_", word, 4)) { // take off the SFX_ word += 4; } else if (fastncmp("DS", word, 2)) { // take off the DS word += 2; } for (int i = 0; i < NUMSFX; i++) { if (S_sfx[i].name && fasticmp(word, S_sfx[i].name)) { *type = static_cast(i); return true; } } return false; } /*-------------------------------------------------- static bool ACS_CountThing(mobj_t *mobj, mobjtype_t type) Helper function for CallFunc_ThingCount. Returns whenever or not to add this thing to the thing count. Input Arguments:- mobj: The mobj we want to count. type: Type exclusion. Return:- true if successful, otherwise false. --------------------------------------------------*/ static bool ACS_CountThing(mobj_t *mobj, mobjtype_t type) { if (type == MT_NULL || mobj->type == type) { // Don't count dead monsters if (mobj->info->spawnhealth > 0 && mobj->health <= 0) { // Note: Hexen checks for COUNTKILL. // SRB2 does not have an equivalent, so I'm checking // spawnhealth. Feel free to replace this condition // with literally anything else. return false; } // Count this object. return true; } return false; } /*-------------------------------------------------- static bool ACS_ActivatorIsLocal(ACSVM::Thread *thread) Helper function for many print functions. Returns whenever or not the activator of the thread is a display player or not. Input Arguments:- thread: The thread we're exeucting on. Return:- true if it's for a display player, otherwise false. --------------------------------------------------*/ static bool ACS_ActivatorIsLocal(ACSVM::Thread *thread) { auto info = &static_cast(thread)->info; if ((info != NULL) && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) && (info->mo->player != NULL)) { return P_IsDisplayPlayer(info->mo->player); } return false; } /*-------------------------------------------------- static UINT32 ACS_SectorThingCounter(sector_t *sec, bool (*filter)(mobj_t *)) Helper function for CallFunc_CountEnemies and CallFunc_CountPushables. Counts a number of things in the specified sector. Input Arguments:- sec: The sector to search in. filter: Filter function, total count is increased when this function returns true. Return:- Numbers of things matching the filter found. --------------------------------------------------*/ static UINT32 ACS_SectorThingCounter(sector_t *sec, bool (*filter)(mobj_t *)) { msecnode_t *node = sec->touching_thinglist; // things touching this sector UINT32 count = 0; while (node) { mobj_t *mo = node->m_thing; if (mo->z > sec->ceilingheight || mo->z + mo->height < sec->floorheight) { continue; } if (filter(mo) == true) { count++; } node = node->m_thinglist_next; } return count; } /*-------------------------------------------------- static UINT32 ACS_SectorTagThingCounter(mtag_t tag, bool (*filter)(mobj_t *)) Helper function for CallFunc_CountEnemies and CallFunc_CountPushables. Counts a number of things in the tagged sectors. Input Arguments:- tag: The sector tag to search in. filter: Filter function, total count is increased when this function returns true. Return:- Numbers of things matching the filter found. --------------------------------------------------*/ static UINT32 ACS_SectorTagThingCounter(mtag_t tag, bool (*filter)(mobj_t *)) { INT32 secnum = -1; UINT32 count = 0; size_t i; TAG_ITER_SECTORS(tag, secnum) { sector_t *sec = §ors[secnum]; boolean FOFsector = false; // Check the lines of this sector, to see if it is a FOF control sector. for (i = 0; i < sec->linecount; i++) { INT32 targetsecnum = -1; if (sec->lines[i]->special < 100 || sec->lines[i]->special >= 300) { continue; } FOFsector = true; TAG_ITER_SECTORS(sec->lines[i]->args[0], targetsecnum) { sector_t *targetsec = §ors[targetsecnum]; count += ACS_SectorThingCounter(targetsec, filter); } } if (FOFsector == false) { count += ACS_SectorThingCounter(sec, filter); } } return count; } /*-------------------------------------------------- bool CallFunc_Random(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) ACS wrapper for P_RandomRange. --------------------------------------------------*/ bool CallFunc_Random(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { INT32 low = 0; INT32 high = 0; (void)argC; low = argV[0]; high = argV[1]; thread->dataStk.push(P_RandomRange(PR_ACS, low, high)); return false; } /*-------------------------------------------------- bool CallFunc_ThingCount(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Counts the number of things of a particular type and tid. Both fields are optional; no type means indescriminate against type, no tid means search thru all thinkers. --------------------------------------------------*/ bool CallFunc_ThingCount(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { ACSVM::MapScope *map = NULL; ACSVM::String *str = NULL; const char *className = NULL; size_t classLen = 0; mobjtype_t type = MT_NULL; mtag_t tid = 0; size_t count = 0; (void)argC; map = thread->scopeMap; str = map->getString(argV[0]); className = str->str; classLen = str->len; if (classLen > 0) { bool success = ACS_GetMobjTypeFromString(className, &type); if (success == false) { // Exit early. CONS_Alert(CONS_WARNING, "Couldn't find object type \"%s\" for ThingCount.\n", className ); return false; } } tid = argV[1]; if (tid != 0) { mobj_t *mobj = nullptr; while ((mobj = P_FindMobjFromTID(tid, mobj, nullptr)) != nullptr) { if (ACS_CountThing(mobj, type) == true) { ++count; } } } else { // Search thinkers instead of tag lists. thinker_t *th = nullptr; mobj_t *mobj = nullptr; for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) { if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) { continue; } mobj = (mobj_t *)th; if (ACS_CountThing(mobj, type) == true) { ++count; } } } thread->dataStk.push(count); return false; } /*-------------------------------------------------- bool CallFunc_TagWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Pauses the thread until the tagged sector stops moving. --------------------------------------------------*/ bool CallFunc_TagWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { (void)argC; thread->state = { ACSVM::ThreadState::WaitTag, argV[0], ACS_TAGTYPE_SECTOR }; return true; // Execution interrupted } /*-------------------------------------------------- bool CallFunc_PolyWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Pauses the thread until the tagged polyobject stops moving. --------------------------------------------------*/ bool CallFunc_PolyWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { (void)argC; thread->state = { ACSVM::ThreadState::WaitTag, argV[0], ACS_TAGTYPE_POLYOBJ }; return true; // Execution interrupted } /*-------------------------------------------------- bool CallFunc_CameraWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Pauses the thread until the tagged camera is done moving. --------------------------------------------------*/ bool CallFunc_CameraWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { (void)argC; thread->state = { ACSVM::ThreadState::WaitTag, argV[0], ACS_TAGTYPE_CAMERA }; return true; // Execution interrupted } /*-------------------------------------------------- bool CallFunc_ChangeFloor(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Changes a floor texture. --------------------------------------------------*/ bool CallFunc_ChangeFloor(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { ACSVM::MapScope *map = nullptr; ACSVM::String *str = nullptr; const char *texName = nullptr; INT32 secnum = -1; mtag_t tag = 0; (void)argC; tag = argV[0]; map = thread->scopeMap; str = map->getString(argV[1]); texName = str->str; TAG_ITER_SECTORS(tag, secnum) { sector_t *sec = §ors[secnum]; sec->floorpic = P_AddLevelFlatRuntime(texName); } return false; } /*-------------------------------------------------- bool CallFunc_ChangeCeiling(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Changes a ceiling texture. --------------------------------------------------*/ bool CallFunc_ChangeCeiling(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { ACSVM::MapScope *map = NULL; ACSVM::String *str = NULL; const char *texName = NULL; INT32 secnum = -1; mtag_t tag = 0; (void)argC; tag = argV[0]; map = thread->scopeMap; str = map->getString(argV[1]); texName = str->str; TAG_ITER_SECTORS(tag, secnum) { sector_t *sec = §ors[secnum]; sec->ceilingpic = P_AddLevelFlatRuntime(texName); } return false; } /*-------------------------------------------------- bool CallFunc_LineSide(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Pushes which side of the linedef was activated. --------------------------------------------------*/ bool CallFunc_LineSide(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { auto info = &static_cast(thread)->info; (void)argV; (void)argC; thread->dataStk.push(info->side); return false; } /*-------------------------------------------------- bool CallFunc_ClearLineSpecial(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) If there is an activating linedef, set its special to 0. --------------------------------------------------*/ bool CallFunc_ClearLineSpecial(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { auto info = &static_cast(thread)->info; (void)argV; (void)argC; if (info->line != NULL) { // One time only. info->line->special = 0; } return false; } /*-------------------------------------------------- bool CallFunc_EndPrint(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) One of the ACS wrappers for CEcho. This version only prints if the activator is a display player. --------------------------------------------------*/ bool CallFunc_EndPrint(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { (void)argV; (void)argC; if (ACS_ActivatorIsLocal(thread) == true) HU_DoTitlecardCEcho(thread->printBuf.data()); thread->printBuf.drop(); return false; } /*-------------------------------------------------- bool CallFunc_PlayerCount(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Pushes the number of players to ACS. --------------------------------------------------*/ bool CallFunc_PlayerCount(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { UINT8 numPlayers = 0; UINT8 i; (void)argV; (void)argC; for (i = 0; i < MAXPLAYERS; i++) { player_t *player = NULL; if (playeringame[i] == false) { continue; } player = &players[i]; if (player->spectator == true) { continue; } numPlayers++; } thread->dataStk.push(numPlayers); return false; } /*-------------------------------------------------- bool CallFunc_GameType(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Pushes the current gametype to ACS. --------------------------------------------------*/ bool CallFunc_GameType(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { (void)argV; (void)argC; thread->dataStk.push(gametype); return false; } /*-------------------------------------------------- bool CallFunc_GameSpeed(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Pushes the current game speed to ACS. --------------------------------------------------*/ bool CallFunc_GameSpeed(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { (void)argV; (void)argC; thread->dataStk.push(gamespeed); return false; } /*-------------------------------------------------- bool CallFunc_Timer(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Pushes leveltime to ACS. --------------------------------------------------*/ bool CallFunc_Timer(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { (void)argV; (void)argC; thread->dataStk.push(leveltime); return false; } /*-------------------------------------------------- bool CallFunc_SectorSound(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Plays a point sound effect from a sector. --------------------------------------------------*/ bool CallFunc_SectorSound(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { auto info = &static_cast(thread)->info; ACSVM::MapScope *map = nullptr; ACSVM::String *str = nullptr; const char *sfxName = nullptr; size_t sfxLen = 0; sfxenum_t sfxId = sfx_None; INT32 vol = 0; mobj_t *origin = nullptr; (void)argC; map = thread->scopeMap; str = map->getString(argV[0]); sfxName = str->str; sfxLen = str->len; if (sfxLen > 0) { bool success = ACS_GetSFXFromString(sfxName, &sfxId); if (success == false) { // Exit early. CONS_Alert(CONS_WARNING, "Couldn't find sfx named \"%s\" for SectorSound.\n", sfxName ); return false; } } vol = argV[1]; if (info->sector != nullptr) { // New to Ring Racers: Use activating sector directly. origin = static_cast(static_cast(&info->sector->soundorg)); } else if (info->line != nullptr) { // Original Hexen behavior: Use line's frontsector. origin = static_cast(static_cast(&info->line->frontsector->soundorg)); } S_StartSoundAtVolume(origin, sfxId, vol); return false; } /*-------------------------------------------------- bool CallFunc_AmbientSound(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Plays a sound effect globally. --------------------------------------------------*/ bool CallFunc_AmbientSound(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { ACSVM::MapScope *map = nullptr; ACSVM::String *str = nullptr; const char *sfxName = nullptr; size_t sfxLen = 0; sfxenum_t sfxId = sfx_None; INT32 vol = 0; (void)argC; map = thread->scopeMap; str = map->getString(argV[0]); sfxName = str->str; sfxLen = str->len; if (sfxLen > 0) { bool success = ACS_GetSFXFromString(sfxName, &sfxId); if (success == false) { // Exit early. CONS_Alert(CONS_WARNING, "Couldn't find sfx named \"%s\" for AmbientSound.\n", sfxName ); return false; } } vol = argV[1]; S_StartSoundAtVolume(NULL, sfxId, vol); return false; } /*-------------------------------------------------- bool CallFunc_SetLineTexture(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Plays a sound effect globally. --------------------------------------------------*/ enum { SLT_POS_TOP, SLT_POS_MIDDLE, SLT_POS_BOTTOM }; bool CallFunc_SetLineTexture(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { mtag_t tag = 0; UINT8 sideId = 0; UINT8 texPos = 0; ACSVM::MapScope *map = NULL; ACSVM::String *str = NULL; const char *texName = NULL; INT32 texId = LUMPERROR; INT32 lineId = -1; (void)argC; tag = argV[0]; sideId = (argV[1] & 1); texPos = argV[2]; map = thread->scopeMap; str = map->getString(argV[3]); texName = str->str; texId = R_TextureNumForName(texName); TAG_ITER_LINES(tag, lineId) { line_t *line = &lines[lineId]; side_t *side = &sides[line->sidenum[sideId]]; switch (texPos) { case SLT_POS_MIDDLE: { side->midtexture = texId; break; } case SLT_POS_BOTTOM: { side->bottomtexture = texId; break; } case SLT_POS_TOP: default: { side->toptexture = texId; break; } } } return false; } /*-------------------------------------------------- bool CallFunc_SetLineSpecial(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Changes a linedef's special and arguments. --------------------------------------------------*/ bool CallFunc_SetLineSpecial(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { auto info = &static_cast(thread)->info; mtag_t tag = 0; INT32 spec = 0; size_t numArgs = 0; INT32 lineId = -1; tag = argV[0]; spec = argV[1]; numArgs = std::min(std::max((signed)(argC - 2), 0), NUMLINEARGS); TAG_ITER_LINES(tag, lineId) { line_t *line = &lines[lineId]; size_t i; if (info->line != nullptr && line == info->line) { continue; } line->special = spec; for (i = 0; i < numArgs; i++) { line->args[i] = argV[i + 2]; } } return false; } /*-------------------------------------------------- bool CallFunc_ThingSound(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Plays a sound effect for a tagged object. --------------------------------------------------*/ bool CallFunc_ThingSound(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { auto info = &static_cast(thread)->info; ACSVM::MapScope *map = nullptr; ACSVM::String *str = nullptr; const char *sfxName = nullptr; size_t sfxLen = 0; mtag_t tag = 0; sfxenum_t sfxId = sfx_None; INT32 vol = 0; mobj_t *mobj = nullptr; (void)argC; tag = argV[0]; map = thread->scopeMap; str = map->getString(argV[1]); sfxName = str->str; sfxLen = str->len; if (sfxLen > 0) { bool success = ACS_GetSFXFromString(sfxName, &sfxId); if (success == false) { // Exit early. CONS_Alert(CONS_WARNING, "Couldn't find sfx named \"%s\" for AmbientSound.\n", sfxName ); return false; } } vol = argV[2]; while ((mobj = P_FindMobjFromTID(tag, mobj, info->mo)) != nullptr) { S_StartSoundAtVolume(mobj, sfxId, vol); } return false; } /*-------------------------------------------------- bool CallFunc_EndPrintBold(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) One of the ACS wrappers for CEcho. This version prints for all players. --------------------------------------------------*/ bool CallFunc_EndPrintBold(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { (void)argV; (void)argC; HU_DoTitlecardCEcho(thread->printBuf.data()); thread->printBuf.drop(); return false; } /*-------------------------------------------------- bool CallFunc_PlayerTeam(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Returns the activating player's team ID. --------------------------------------------------*/ bool CallFunc_PlayerTeam(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { auto info = &static_cast(thread)->info; UINT8 teamID = 0; (void)argV; (void)argC; if ((info != NULL) && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) && (info->mo->player != NULL)) { teamID = info->mo->player->ctfteam; } thread->dataStk.push(teamID); return false; } /*-------------------------------------------------- bool CallFunc_PlayerRings(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Returns the activating player's ring count. --------------------------------------------------*/ bool CallFunc_PlayerRings(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { auto info = &static_cast(thread)->info; SINT8 rings = 0; (void)argV; (void)argC; if ((info != NULL) && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) && (info->mo->player != NULL)) { rings = info->mo->player->rings; } thread->dataStk.push(rings); return false; } /*-------------------------------------------------- bool CallFunc_PlayerScore(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Returns the activating player's ring count. --------------------------------------------------*/ bool CallFunc_PlayerScore(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { auto info = &static_cast(thread)->info; UINT32 score = 0; (void)argV; (void)argC; if ((info != NULL) && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) && (info->mo->player != NULL)) { score = info->mo->player->roundscore; } thread->dataStk.push(score); return false; } /*-------------------------------------------------- bool CallFunc_EndLog(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) One of the ACS wrappers for CONS_Printf. This version only prints if the activator is a display player. --------------------------------------------------*/ bool CallFunc_EndLog(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { (void)argV; (void)argC; CONS_Printf("%s\n", thread->printBuf.data()); thread->printBuf.drop(); return false; } /*-------------------------------------------------- bool CallFunc_strcmp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) ACS wrapper for strcmp. --------------------------------------------------*/ static int ACS_strcmp(ACSVM::String *a, ACSVM::String *b) { for (char const *sA = a->str, *sB = b->str; ; ++sA, ++sB) { char cA = *sA, cB = *sB; if (cA != cB) { return (cA < cB) ? -1 : 1; } if (!cA) { return 0; } } } bool CallFunc_strcmp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { ACSVM::MapScope *map = NULL; ACSVM::String *strA = nullptr; ACSVM::String *strB = nullptr; (void)argC; map = thread->scopeMap; strA = map->getString(argV[0]); strB = map->getString(argV[1]); thread->dataStk.push(ACS_strcmp(strA, strB)); return false; } /*-------------------------------------------------- bool CallFunc_strcasecmp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) ACS wrapper for strcasecmp / stricmp. --------------------------------------------------*/ static int ACS_strcasecmp(ACSVM::String *a, ACSVM::String *b) { for (char const *sA = a->str, *sB = b->str; ; ++sA, ++sB) { char cA = std::tolower(*sA), cB = std::tolower(*sB); if (cA != cB) { return (cA < cB) ? -1 : 1; } if (!cA) { return 0; } } } bool CallFunc_strcasecmp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { ACSVM::MapScope *map = NULL; ACSVM::String *strA = nullptr; ACSVM::String *strB = nullptr; (void)argC; map = thread->scopeMap; strA = map->getString(argV[0]); strB = map->getString(argV[1]); thread->dataStk.push(ACS_strcasecmp(strA, strB)); return false; } /*-------------------------------------------------- bool CallFunc_CountEnemies(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Returns the number of enemies in the tagged sectors. --------------------------------------------------*/ bool ACS_EnemyFilter(mobj_t *mo) { return ((mo->flags & (MF_ENEMY|MF_BOSS)) && mo->health > 0); } bool CallFunc_CountEnemies(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { mtag_t tag = 0; UINT32 count = 0; (void)argC; tag = argV[0]; count = ACS_SectorTagThingCounter(tag, ACS_EnemyFilter); thread->dataStk.push(count); return false; } /*-------------------------------------------------- bool CallFunc_CountPushables(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Returns the number of pushables in the tagged sectors. --------------------------------------------------*/ bool ACS_PushableFilter(mobj_t *mo) { return ((mo->flags & MF_PUSHABLE) || ((mo->info->flags & MF_PUSHABLE) && mo->fuse)); } bool CallFunc_CountPushables(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { mtag_t tag = 0; UINT32 count = 0; (void)argC; tag = argV[0]; count = ACS_SectorTagThingCounter(tag, ACS_PushableFilter); thread->dataStk.push(count); return false; } /*-------------------------------------------------- bool CallFunc_HaveUnlockableTrigger(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Returns if an unlockable trigger has been gotten. --------------------------------------------------*/ bool CallFunc_HaveUnlockableTrigger(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { UINT8 id = 0; bool unlocked = false; (void)argC; id = argV[0]; if (id < 0 || id > 31) // limited by 32 bit variable { CONS_Printf("Bad unlockable trigger ID %d\n", id); } else { unlocked = (unlocktriggers & (1 << id)); } thread->dataStk.push(unlocked); return false; } /*-------------------------------------------------- bool CallFunc_HaveUnlockable(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Returns if an unlockable has been gotten. --------------------------------------------------*/ bool CallFunc_HaveUnlockable(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { UINT8 id = 0; bool unlocked = false; (void)argC; id = argV[0]; if (id < 0 || id >= MAXUNLOCKABLES) { CONS_Printf("Bad unlockable ID %d\n", id); } else { unlocked = M_CheckNetUnlockByID(id); } thread->dataStk.push(unlocked); return false; } /*-------------------------------------------------- bool CallFunc_PlayerSkin(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Returns the activating player's skin name. --------------------------------------------------*/ bool CallFunc_PlayerSkin(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { Environment *env = &ACSEnv; auto info = &static_cast(thread)->info; (void)argV; (void)argC; if ((info != NULL) && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) && (info->mo->player != NULL)) { UINT8 skin = info->mo->player->skin; thread->dataStk.push(~env->getString( skins[skin].name )->idx); return false; } thread->dataStk.push(0); return false; } /*-------------------------------------------------- bool CallFunc_GetObjectDye(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Returns the activating object's current dye. --------------------------------------------------*/ bool CallFunc_GetObjectDye(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { Environment *env = &ACSEnv; auto info = &static_cast(thread)->info; UINT16 dye = SKINCOLOR_NONE; (void)argV; (void)argC; if ((info != NULL) && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false)) { dye = (info->mo->player != NULL) ? info->mo->player->dye : info->mo->color; } thread->dataStk.push(~env->getString( skincolors[dye].name )->idx); return false; } /*-------------------------------------------------- bool CallFunc_PlayerEmeralds(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Returns the activating player's number of Chaos Emeralds. --------------------------------------------------*/ bool CallFunc_PlayerEmeralds(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { auto info = &static_cast(thread)->info; UINT8 count = 0; (void)argV; (void)argC; if ((info != NULL) && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) && (info->mo->player != NULL)) { count = K_NumEmeralds(info->mo->player); } thread->dataStk.push(count); return false; } /*-------------------------------------------------- bool CallFunc_PlayerLap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Returns the activating player's current lap. --------------------------------------------------*/ bool CallFunc_PlayerLap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { auto info = &static_cast(thread)->info; UINT8 laps = 0; (void)argV; (void)argC; if ((info != NULL) && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) && (info->mo->player != NULL)) { laps = info->mo->player->laps; } thread->dataStk.push(laps); return false; } /*-------------------------------------------------- bool CallFunc_LowestLap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Returns the lowest lap of all of the players in-game. --------------------------------------------------*/ bool CallFunc_LowestLap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { (void)argV; (void)argC; thread->dataStk.push(P_FindLowestLap()); return false; } /*-------------------------------------------------- bool CallFunc_EncoreMode(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Returns if the map is in Encore Mode. --------------------------------------------------*/ bool CallFunc_EncoreMode(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { (void)argV; (void)argC; thread->dataStk.push(encoremode); return false; } /*-------------------------------------------------- bool CallFunc_PodiumPosition(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Returns the best position of all non-CPU players. --------------------------------------------------*/ bool CallFunc_PodiumPosition(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { UINT8 ret = MAXPLAYERS; INT32 i; (void)argV; (void)argC; for (i = 0; i < MAXPLAYERS; i++) { player_t *player = NULL; if (playeringame[i] == false) { continue; } player = &players[i]; if (player->spectator == true) { continue; } if (player->bot == true) { continue; } ret = std::min(ret, player->position); } thread->dataStk.push(ret); return false; } /*-------------------------------------------------- bool CallFunc_PodiumFinish(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) Ends the podium sequence. Doesn't do anything outside of podium maps. --------------------------------------------------*/ bool CallFunc_PodiumFinish(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { (void)argV; (void)argC; K_FinishCeremony(); return false; }