RingRacers/src/acs/environment.cpp

449 lines
15 KiB
C++

// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity)
// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour
// Copyright (C) 2024 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 environment.cpp
/// \brief Action Code Script: Environment definition
#include <algorithm>
#include <vector>
#include "acsvm.hpp"
#include "../doomtype.h"
#include "../doomdef.h"
#include "../doomstat.h"
#include "../r_defs.h"
#include "../r_state.h"
#include "../g_game.h"
#include "../p_spec.h"
#include "../w_wad.h"
#include "../z_zone.h"
#include "../p_local.h"
#include "../k_dialogue.hpp"
#include "environment.hpp"
#include "thread.hpp"
#include "call-funcs.hpp"
#include "../cxxutil.hpp"
using namespace srb2::acs;
Environment ACSEnv;
Environment::Environment()
{
ACSVM::GlobalScope *global = getGlobalScope(0);
// Activate global scope immediately, since we don't want it off.
// Not that we're adding any modules to it, though. :p
global->active = true;
// Set a branch limit (same as ZDoom's instruction limit)
branchLimit = 2000000;
// Add the data & function pointers.
// Starting with raw ACS0 codes. I'm using this classic-style
// format here to have a blueprint for what needs implementing,
// but it'd also be fine to move these to new style.
// 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
addCodeDataACS0( 57, {"", 2, addCallFunc(CallFunc_Random)});
addCodeDataACS0( 58, {"WW", 0, addCallFunc(CallFunc_Random)});
addCodeDataACS0( 59, {"", 2, addCallFunc(CallFunc_ThingCount)});
addCodeDataACS0( 60, {"WW", 0, addCallFunc(CallFunc_ThingCount)});
addCodeDataACS0( 61, {"", 1, addCallFunc(CallFunc_TagWait)});
addCodeDataACS0( 62, {"W", 0, addCallFunc(CallFunc_TagWait)});
addCodeDataACS0( 63, {"", 1, addCallFunc(CallFunc_PolyWait)});
addCodeDataACS0( 64, {"W", 0, addCallFunc(CallFunc_PolyWait)});
addCodeDataACS0( 65, {"", 2, addCallFunc(CallFunc_ChangeFloor)});
addCodeDataACS0( 66, {"WWS", 0, addCallFunc(CallFunc_ChangeFloor)});
addCodeDataACS0( 67, {"", 2, addCallFunc(CallFunc_ChangeCeiling)});
addCodeDataACS0( 68, {"WWS", 0, addCallFunc(CallFunc_ChangeCeiling)});
// 69 to 79: Implemented by ACSVM
addCodeDataACS0( 80, {"", 0, addCallFunc(CallFunc_LineSide)});
// 81 to 82: Implemented by ACSVM
addCodeDataACS0( 83, {"", 0, addCallFunc(CallFunc_ClearLineSpecial)});
// 84 to 85: Implemented by ACSVM
addCodeDataACS0( 86, {"", 0, addCallFunc(CallFunc_EndPrint)});
// 87 to 89: Implemented by ACSVM
addCodeDataACS0( 90, {"", 0, addCallFunc(CallFunc_PlayerCount)});
addCodeDataACS0( 91, {"", 0, addCallFunc(CallFunc_GameType)});
addCodeDataACS0( 92, {"", 0, addCallFunc(CallFunc_GameSpeed)});
addCodeDataACS0( 93, {"", 0, addCallFunc(CallFunc_Timer)});
addCodeDataACS0( 94, {"", 2, addCallFunc(CallFunc_SectorSound)});
addCodeDataACS0( 95, {"", 2, addCallFunc(CallFunc_AmbientSound)});
addCodeDataACS0( 97, {"", 4, addCallFunc(CallFunc_SetLineTexture)});
addCodeDataACS0( 99, {"", 7, addCallFunc(CallFunc_SetLineSpecial)});
addCodeDataACS0(100, {"", 3, addCallFunc(CallFunc_ThingSound)});
addCodeDataACS0(101, {"", 0, addCallFunc(CallFunc_EndPrintBold)});
addCodeDataACS0(118, {"", 0, addCallFunc(CallFunc_IsNetworkGame)});
addCodeDataACS0(119, {"", 0, addCallFunc(CallFunc_PlayerTeam)});
addCodeDataACS0(120, {"", 0, addCallFunc(CallFunc_PlayerRings)});
addCodeDataACS0(122, {"", 0, addCallFunc(CallFunc_PlayerScore)});
// 136 to 137: Implemented by ACSVM
// 157: Implemented by ACSVM
// 167 to 173: Implemented by ACSVM
addCodeDataACS0(174, {"BB", 0, addCallFunc(CallFunc_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
addCodeDataACS0(247, {"", 0, addCallFunc(CallFunc_PlayerNumber)});
addCodeDataACS0(248, {"", 0, addCallFunc(CallFunc_ActivatorTID)});
// 253: Implemented by ACSVM
// 256 to 257: Implemented by ACSVM
// 263: Implemented by ACSVM
addCodeDataACS0(270, {"", 0, addCallFunc(CallFunc_EndLog)});
// 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
// Now for new style functions.
// This style is preferred for added functions
// that aren't mimicing one from Hexen's or ZDoom's
// ACS implementations.
addFuncDataACS0( 1, addCallFunc(CallFunc_GetLineProperty));
addFuncDataACS0( 2, addCallFunc(CallFunc_SetLineProperty));
addFuncDataACS0( 3, addCallFunc(CallFunc_GetLineUserProperty));
addFuncDataACS0( 4, addCallFunc(CallFunc_GetSectorProperty));
addFuncDataACS0( 5, addCallFunc(CallFunc_SetSectorProperty));
addFuncDataACS0( 6, addCallFunc(CallFunc_GetSectorUserProperty));
addFuncDataACS0( 7, addCallFunc(CallFunc_GetSideProperty));
addFuncDataACS0( 8, addCallFunc(CallFunc_SetSideProperty));
addFuncDataACS0( 9, addCallFunc(CallFunc_GetSideUserProperty));
addFuncDataACS0( 10, addCallFunc(CallFunc_GetThingProperty));
addFuncDataACS0( 11, addCallFunc(CallFunc_SetThingProperty));
addFuncDataACS0( 12, addCallFunc(CallFunc_GetThingUserProperty));
//addFuncDataACS0( 13, addCallFunc(CallFunc_GetPlayerProperty));
//addFuncDataACS0( 14, addCallFunc(CallFunc_SetPlayerProperty));
//addFuncDataACS0( 15, addCallFunc(CallFunc_GetPolyobjProperty));
//addFuncDataACS0( 16, addCallFunc(CallFunc_SetPolyobjProperty));
addFuncDataACS0( 100, addCallFunc(CallFunc_strcmp));
addFuncDataACS0( 101, addCallFunc(CallFunc_strcasecmp));
addFuncDataACS0( 300, addCallFunc(CallFunc_CountEnemies));
addFuncDataACS0( 301, addCallFunc(CallFunc_CountPushables));
addFuncDataACS0( 302, addCallFunc(CallFunc_HaveUnlockableTrigger));
addFuncDataACS0( 303, addCallFunc(CallFunc_HaveUnlockable));
addFuncDataACS0( 304, addCallFunc(CallFunc_PlayerSkin));
addFuncDataACS0( 305, addCallFunc(CallFunc_GetObjectDye));
addFuncDataACS0( 306, addCallFunc(CallFunc_PlayerEmeralds));
addFuncDataACS0( 307, addCallFunc(CallFunc_PlayerLap));
addFuncDataACS0( 308, addCallFunc(CallFunc_LowestLap));
addFuncDataACS0( 309, addCallFunc(CallFunc_EncoreMode));
addFuncDataACS0( 310, addCallFunc(CallFunc_PrisonBreak));
addFuncDataACS0( 311, addCallFunc(CallFunc_TimeAttack));
addFuncDataACS0( 312, addCallFunc(CallFunc_ThingCount));
addFuncDataACS0( 313, addCallFunc(CallFunc_GrandPrix));
addFuncDataACS0( 314, addCallFunc(CallFunc_GetGrabbedSprayCan));
addFuncDataACS0( 315, addCallFunc(CallFunc_PlayerBot));
addFuncDataACS0( 316, addCallFunc(CallFunc_PositionStart));
addFuncDataACS0( 317, addCallFunc(CallFunc_FreePlay));
addFuncDataACS0( 318, addCallFunc(CallFunc_CheckTutorialChallenge));
addFuncDataACS0( 319, addCallFunc(CallFunc_PlayerLosing));
addFuncDataACS0( 320, addCallFunc(CallFunc_PlayerExiting));
addFuncDataACS0( 500, addCallFunc(CallFunc_CameraWait));
addFuncDataACS0( 501, addCallFunc(CallFunc_PodiumPosition));
addFuncDataACS0( 502, addCallFunc(CallFunc_PodiumFinish));
addFuncDataACS0( 503, addCallFunc(CallFunc_SetLineRenderStyle));
addFuncDataACS0( 504, addCallFunc(CallFunc_MapWarp));
addFuncDataACS0( 505, addCallFunc(CallFunc_AddBot));
addFuncDataACS0( 506, addCallFunc(CallFunc_StopLevelExit));
addFuncDataACS0( 507, addCallFunc(CallFunc_ExitLevel));
addFuncDataACS0( 508, addCallFunc(CallFunc_MusicPlay));
addFuncDataACS0( 509, addCallFunc(CallFunc_MusicStopAll));
addFuncDataACS0( 510, addCallFunc(CallFunc_MusicRemap));
addFuncDataACS0( 511, addCallFunc(CallFunc_Freeze));
addFuncDataACS0( 512, addCallFunc(CallFunc_MusicDim));
addFuncDataACS0( 600, addCallFunc(CallFunc_DialogueSetSpeaker));
addFuncDataACS0( 601, addCallFunc(CallFunc_DialogueSetCustomSpeaker));
addFuncDataACS0( 602, addCallFunc(CallFunc_DialogueNewText));
addFuncDataACS0( 603, addCallFunc(CallFunc_DialogueWaitDismiss));
addFuncDataACS0( 604, addCallFunc(CallFunc_DialogueWaitText));
addFuncDataACS0( 605, addCallFunc(CallFunc_DialogueAutoDismiss));
addFuncDataACS0( 700, addCallFunc(CallFunc_AddMessage));
addFuncDataACS0( 701, addCallFunc(CallFunc_AddMessageForPlayer));
addFuncDataACS0( 702, addCallFunc(CallFunc_ClearPersistentMessages));
addFuncDataACS0( 703, addCallFunc(CallFunc_ClearPersistentMessageForPlayer));
}
ACSVM::Thread *Environment::allocThread()
{
return new Thread(this);
}
ACSVM::ModuleName Environment::getModuleName(char const *str, size_t len)
{
ACSVM::String *name = getString(str, len);
lumpnum_t lump = W_CheckNumForNameInFolder(str, "ACS/");
return { name, nullptr, static_cast<size_t>(lump) };
}
void Environment::loadModule(ACSVM::Module *module)
{
ACSVM::ModuleName *const name = &module->name;
size_t lumpLen = 0;
std::vector<ACSVM::Byte> data;
if (name->i == (size_t)LUMPERROR)
{
// No lump given for module.
CONS_Alert(CONS_WARNING, "Could not find ACS module \"%s\"; scripts will not function properly!\n", name->s->str);
return; //throw ACSVM::ReadError("file open failure");
}
lumpLen = W_LumpLength(name->i);
if (W_IsLumpWad(name->i) == true || lumpLen == 0)
{
CONS_Debug(DBG_SETUP, "Attempting to load ACS module from the BEHAVIOR lump of map '%s'...\n", name->s->str);
// The lump given is a virtual resource.
// Try to grab a BEHAVIOR lump from inside of it.
virtres_t *vRes = vres_GetMap(name->i);
auto _ = srb2::finally([vRes]() { vres_Free(vRes); });
virtlump_t *vLump = vres_Find(vRes, "BEHAVIOR");
if (vLump != nullptr && vLump->size > 0)
{
data.insert(data.begin(), vLump->data, vLump->data + vLump->size);
CONS_Debug(DBG_SETUP, "Successfully found BEHAVIOR lump.\n");
}
else
{
CONS_Debug(DBG_SETUP, "No BEHAVIOR lump found.\n");
}
}
else
{
CONS_Debug(DBG_SETUP, "Loading ACS module directly from lump '%s'...\n", name->s->str);
// It's a real lump.
ACSVM::Byte *lump = static_cast<ACSVM::Byte *>(Z_Calloc(lumpLen, PU_STATIC, nullptr));
auto _ = srb2::finally([lump]() { Z_Free(lump); });
W_ReadLump(name->i, lump);
data.insert(data.begin(), lump, lump + lumpLen);
}
if (data.empty() == false)
{
try
{
module->readBytecode(data.data(), data.size());
}
catch (const ACSVM::ReadError &e)
{
CONS_Alert(CONS_ERROR, "Failed to load ACS module '%s': %s\n", name->s->str, e.what());
throw ACSVM::ReadError("failed import");
}
}
else
{
// Unlike Hexen, a BEHAVIOR lump is not required.
// Simply ignore in this instance.
CONS_Debug(DBG_SETUP, "ACS module has no data, ignoring...\n");
}
}
bool Environment::checkTag(ACSVM::Word type, ACSVM::Word tag)
{
switch (type)
{
case ACS_TAGTYPE_SECTOR:
{
INT32 secnum = -1;
TAG_ITER_SECTORS(tag, secnum)
{
sector_t *sec = &sectors[secnum];
if (sec->floordata != nullptr || sec->ceilingdata != nullptr)
{
return false;
}
}
return true;
}
case ACS_TAGTYPE_POLYOBJ:
{
const polyobj_t *po = Polyobj_GetForNum(tag);
return (po == nullptr || po->thinker == nullptr);
}
case ACS_TAGTYPE_CAMERA:
{
const mobj_t *camera = P_FindObjectTypeFromTag(MT_ALTVIEWMAN, tag);
if (camera == nullptr)
{
return true;
}
return (camera->tracer == nullptr || P_MobjWasRemoved(camera->tracer) == true);
}
case ACS_TAGTYPE_DIALOGUE:
{
// TODO when we move away from g_dialogue
// See also call-funcs.cpp Dialogue_ValidCheck
if (netgame || !g_dialogue.EraIsValid(tag)) // cheeky reuse
{
return true;
}
if (g_dialogue.Dismissable())
{
// wait for dismissal
return (!g_dialogue.Active());
}
else
{
// wait for text to finish
return (g_dialogue.TextDone());
}
}
}
return true;
}
ACSVM::Word Environment::callSpecImpl
(
ACSVM::Thread *thread, ACSVM::Word spec,
const ACSVM::Word *argV, ACSVM::Word argC
)
{
auto info = &static_cast<Thread *>(thread)->info;
ACSVM::MapScope *const map = thread->scopeMap;
INT32 args[NUM_SCRIPT_ARGS] = {0};
char *stringargs[NUM_SCRIPT_STRINGARGS] = {0};
auto _ = srb2::finally(
[stringargs]()
{
for (int i = 0; i < NUM_SCRIPT_STRINGARGS; i++)
{
Z_Free(stringargs[i]);
}
}
);
activator_t *activator = static_cast<activator_t *>(Z_Calloc(sizeof(activator_t), PU_LEVEL, nullptr));
auto __ = srb2::finally(
[info, activator]()
{
if (info->thread_era == thinker_era)
{
P_SetTarget(&activator->mo, NULL);
Z_Free(activator);
}
}
);
int i = 0;
for (i = 0; i < std::min((signed)(argC), NUM_SCRIPT_STRINGARGS); i++)
{
ACSVM::String *strPtr = map->getString(argV[i]);
stringargs[i] = static_cast<char *>(Z_Malloc(strPtr->len + 1, PU_STATIC, nullptr));
M_Memcpy(stringargs[i], strPtr->str, strPtr->len + 1);
}
for (i = 0; i < std::min((signed)(argC), NUM_SCRIPT_ARGS); i++)
{
args[i] = argV[i];
}
P_SetTarget(&activator->mo, info->mo);
activator->line = info->line;
activator->side = info->side;
activator->sector = info->sector;
activator->po = info->po;
activator->fromLineSpecial = false;
P_ProcessSpecial(activator, spec, args, stringargs);
return 1;
}
void Environment::printKill(ACSVM::Thread *thread, ACSVM::Word type, ACSVM::Word data)
{
std::string scriptName;
ACSVM::String *scriptNamePtr = (thread->script != nullptr) ? (thread->script->name.s) : nullptr;
if (scriptNamePtr && scriptNamePtr->len)
scriptName = std::string(scriptNamePtr->str);
else
scriptName = std::to_string((int)thread->script->name.i);
ACSVM::KillType killType = static_cast<ACSVM::KillType>(type);
if (killType == ACSVM::KillType::BranchLimit)
{
CONS_Alert(CONS_ERROR, "Terminated runaway script %s\n", scriptName.c_str());
return;
}
else if (killType == ACSVM::KillType::UnknownCode)
{
CONS_Alert(CONS_ERROR, "ACSVM ERROR: Unknown opcode %d in script %s\n", data, scriptName.c_str());
}
else if (killType == ACSVM::KillType::UnknownFunc)
{
CONS_Alert(CONS_ERROR, "ACSVM ERROR: Unknown function %d in script %s\n", data, scriptName.c_str());
}
else if (killType == ACSVM::KillType::OutOfBounds)
{
CONS_Alert(CONS_ERROR, "ACSVM ERROR: Jumped to out of bounds location %s in script %s\n",
sizeu1(thread->codePtr - thread->module->codeV.data() - 1), scriptName.c_str());
}
else
{
CONS_Alert(CONS_ERROR, "ACSVM ERROR: Kill %u:%d at %s in script %s\n",
type, data, sizeu1(thread->codePtr - thread->module->codeV.data() - 1), scriptName.c_str());
}
CONS_Printf("Script terminated.\n");
}