diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c8e62c3a44..056897f409 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -998,6 +998,7 @@ set (PCH_SOURCES s_sound.cpp serializer.cpp sc_man.cpp + scriptutil.cpp st_stuff.cpp statistics.cpp stats.cpp diff --git a/src/fragglescript/t_func.cpp b/src/fragglescript/t_func.cpp index c034b51161..be6c185201 100644 --- a/src/fragglescript/t_func.cpp +++ b/src/fragglescript/t_func.cpp @@ -48,6 +48,7 @@ #include "r_utility.h" #include "g_levellocals.h" #include "actorinlines.h" +#include "scriptutil.h" static FRandom pr_script("FScript"); @@ -283,6 +284,17 @@ static int T_GetPlayerNum(const svalue_t &arg) return playernum; } +APlayerPawn *T_GetPlayerActor(const svalue_t &arg) +{ + int num = T_GetPlayerNum(arg); + return num == -1 ? nullptr : players[num].mo; +} + +PClassActor *T_ClassType(const svalue_t &arg) +{ + return PClass::FindActor(stringvalue(arg)); +} + //========================================================================== // // Finds a sector from a tag. This has been extended to allow looking for @@ -2834,34 +2846,8 @@ void FParser::SF_SetWeapon() { if (CheckArgs(2)) { - int playernum=T_GetPlayerNum(t_argv[0]); - if (playernum!=-1) - { - AInventory *item = players[playernum].mo->FindInventory (PClass::FindActor (stringvalue(t_argv[1]))); - - if (item == NULL || !item->IsKindOf(NAME_Weapon)) - { - } - else if (players[playernum].ReadyWeapon == item) - { - // The weapon is already selected, so setweapon succeeds by default, - // but make sure the player isn't switching away from it. - players[playernum].PendingWeapon = WP_NOCHANGE; - t_return.value.i = 1; - } - else - { - auto weap = static_cast (item); - - if (weap->CheckAmmo (AWeapon::EitherFire, false)) - { - // There's enough ammo, so switch to it. - t_return.value.i = 1; - players[playernum].PendingWeapon = weap; - } - } - } - t_return.value.i = 0; + t_return.type = svt_int; + t_return.value.i = ScriptUtil::Exec(NAME_SetWeapon, ScriptUtil::Pointer, T_GetPlayerActor(t_argv[0]), ScriptUtil::Class, T_ClassType(t_argv[1]), ScriptUtil::End); } } diff --git a/src/namedef.h b/src/namedef.h index 2d0c2c1269..6dc22c1132 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -983,3 +983,8 @@ xx(snd_output) xx(snd_output_format) xx(snd_speakermode) xx(snd_resampler) + +// ScriptUtil entry points +xx(ScriptUtil) +xx(SetMarineWeapon) +xx(SetMarineSprite) diff --git a/src/p_acs.cpp b/src/p_acs.cpp index 22ada77127..b90558632c 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -74,6 +74,7 @@ #include "g_levellocals.h" #include "actorinlines.h" #include "types.h" +#include "scriptutil.h" // P-codes for ACS scripts enum @@ -6963,28 +6964,6 @@ static bool CharArrayParms(int &capacity, int &offset, int &a, FACSStackMemory& return true; } -static void SetMarineWeapon(AActor *marine, int weapon) -{ - static VMFunction *smw = nullptr; - if (smw == nullptr) PClass::FindFunction(&smw, NAME_ScriptedMarine, NAME_SetWeapon); - if (smw) - { - VMValue params[2] = { marine, weapon }; - VMCall(smw, params, 2, nullptr, 0); - } -} - -static void SetMarineSprite(AActor *marine, PClassActor *source) -{ - static VMFunction *sms = nullptr; - if (sms == nullptr) PClass::FindFunction(&sms, NAME_ScriptedMarine, NAME_SetSprite); - if (sms) - { - VMValue params[2] = { marine, source }; - VMCall(sms, params, 2, nullptr, 0); - } -} - int DLevelScript::RunScript () { DACSThinker *controller = DACSThinker::ActiveThinker; @@ -9832,93 +9811,16 @@ scriptwait: break; case PCD_SETWEAPON: - if (activator == NULL || activator->player == NULL) - { - STACK(1) = 0; - } - else - { - AInventory *item = activator->FindInventory (PClass::FindActor (FBehavior::StaticLookupString (STACK(1)))); - - if (item == NULL || !item->IsKindOf(NAME_Weapon)) - { - STACK(1) = 0; - } - else if (activator->player->ReadyWeapon == item) - { - // The weapon is already selected, so setweapon succeeds by default, - // but make sure the player isn't switching away from it. - activator->player->PendingWeapon = WP_NOCHANGE; - STACK(1) = 1; - } - else - { - AWeapon *weap = static_cast (item); - - if (weap->CheckAmmo (AWeapon::EitherFire, false)) - { - // There's enough ammo, so switch to it. - STACK(1) = 1; - activator->player->PendingWeapon = weap; - } - else - { - STACK(1) = 0; - } - } - } + STACK(1) = ScriptUtil::Exec(NAME_SetWeapon, ScriptUtil::Pointer, activator, ScriptUtil::ACSClass, STACK(1), ScriptUtil::End); break; case PCD_SETMARINEWEAPON: - if (STACK(2) != 0) - { - AActor *marine; - NActorIterator iterator(NAME_ScriptedMarine, STACK(2)); - - while ((marine = iterator.Next()) != NULL) - { - SetMarineWeapon(marine, STACK(1)); - } - } - else - { - if (activator != nullptr && activator->IsKindOf (NAME_ScriptedMarine)) - { - SetMarineWeapon(activator, STACK(1)); - } - } + ScriptUtil::Exec(NAME_SetMarineWeapon, ScriptUtil::Pointer, activator, ScriptUtil::Int, STACK(2), ScriptUtil::Int, STACK(1), ScriptUtil::End); sp -= 2; break; case PCD_SETMARINESPRITE: - { - PClassActor *type = PClass::FindActor(FBehavior::StaticLookupString (STACK(1))); - - if (type != NULL) - { - if (STACK(2) != 0) - { - AActor *marine; - NActorIterator iterator(NAME_ScriptedMarine, STACK(2)); - - while ((marine = iterator.Next()) != NULL) - { - SetMarineSprite(marine, type); - } - } - else - { - if (activator != nullptr && activator->IsKindOf(NAME_ScriptedMarine)) - { - SetMarineSprite(activator, type); - } - } - } - else - { - Printf ("Unknown actor type: %s\n", FBehavior::StaticLookupString (STACK(1))); - } - } + ScriptUtil::Exec(NAME_SetMarineSprite, ScriptUtil::Pointer, activator, ScriptUtil::Int, STACK(2), ScriptUtil::ACSClass, STACK(1), ScriptUtil::End); sp -= 2; break; diff --git a/src/p_acs.h b/src/p_acs.h index fc361a230f..9115a370be 100644 --- a/src/p_acs.h +++ b/src/p_acs.h @@ -43,6 +43,7 @@ class FFont; class FileReader; struct line_t; +class FSerializer; enum diff --git a/src/scripting/vm/vm.h b/src/scripting/vm/vm.h index 1e5b65b2e4..d52dc0f6f9 100644 --- a/src/scripting/vm/vm.h +++ b/src/scripting/vm/vm.h @@ -675,6 +675,23 @@ VMFunction *FindVMFunction(PClass *cls, const char *name); FString FStringFormat(VM_ARGS, int offset = 0); +#define IFVM(cls, funcname) \ + static VMFunction * func = nullptr; \ + if (func == nullptr) { \ + func = dyn_cast(RUNTIME_CLASS(cls)->FindSymbol(#funcname, false)); \ + assert(func); \ + } \ + if (func != nullptr) + +#define IFVMNAME(cls, funcname) \ + static VMFunction * func = nullptr; \ + if (func == nullptr) { \ + func = dyn_cast(PClass::FindClass(cls)->FindSymbol(#funcname, false)); \ + assert(func); \ + } \ + if (func != nullptr) + + unsigned GetVirtualIndex(PClass *cls, const char *funcname); diff --git a/src/scriptutil.cpp b/src/scriptutil.cpp new file mode 100644 index 0000000000..f5d88087bc --- /dev/null +++ b/src/scriptutil.cpp @@ -0,0 +1,104 @@ +//----------------------------------------------------------------------------- +// +// Copyright 2018 Christoph Oelckers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//----------------------------------------------------------------------------- +// +// +// DESCRIPTION: generalized interface for implementing ACS/FS functions +// in ZScript. +// +//----------------------------------------------------------------------------- + +#include "i_system.h" +#include "tarray.h" +#include "dobject.h" +#include "vm.h" +#include "scriptutil.h" +#include "p_acs.h" + + +static TArray parameters; +static TMap functions; + + +void ScriptUtil::BuildParameters(va_list ap) +{ + for(int type = va_arg(ap, int); type != End; type = va_arg(ap, int)) + { + switch (type) + { + case Int: + parameters.Push(VMValue(va_arg(ap, int))); + break; + + case Pointer: + case Class: // this is just a pointer. + case String: // must be passed by reference to a persistent location! + parameters.Push(VMValue(va_arg(ap, void*))); + break; + + case Float: + parameters.Push(VMValue(va_arg(ap, double))); + break; + + case ACSClass: + parameters.Push(VMValue(PClass::FindActor(FBehavior::StaticLookupString(va_arg(ap, int))))); + break; + } + } +} + +void ScriptUtil::RunFunction(FName functionname, unsigned paramstart, VMReturn &returns) +{ + VMFunction *func = nullptr; + auto check = functions.CheckKey(functionname); + if (!check) + { + PClass::FindFunction(&func, NAME_ScriptUtil, functionname); + if (func == nullptr) + { + I_Error("Call to undefined function ScriptUtil.%s", functionname.GetChars()); + } + functions.Insert(functionname, func); + } + else func = *check; + + VMCall(func, ¶meters[paramstart], parameters.Size() - paramstart, &returns, 1); +} + +int ScriptUtil::Exec(FName functionname, ...) +{ + unsigned paramstart = parameters.Size(); + va_list ap; + va_start(ap, functionname); + try + { + BuildParameters(ap); + int ret = 0; + VMReturn returns(&ret); + RunFunction(functionname, paramstart, returns); + va_end(ap); + parameters.Clamp(paramstart); + return ret; + } + catch(...) + { + va_end(ap); + parameters.Clamp(paramstart); + throw; + } +} diff --git a/src/scriptutil.h b/src/scriptutil.h new file mode 100644 index 0000000000..6d8f83cc8e --- /dev/null +++ b/src/scriptutil.h @@ -0,0 +1,27 @@ +#pragma once + + +#include +#include "name.h" + + +class ScriptUtil +{ + static void BuildParameters(va_list ap); + static void RunFunction(FName function, unsigned paramstart, VMReturn &returns); + +public: + enum + { + End, + Int, + Pointer, + Float, + String, + Class, + ACSString, // convenience helpers taking an ACS string index instead of a string + ACSClass, + }; + + static int Exec(FName functionname, ...); +}; diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt index 41b3851412..0cc9c8f6b0 100644 --- a/wadsrc/static/zscript.txt +++ b/wadsrc/static/zscript.txt @@ -260,3 +260,5 @@ version "3.7" #include "zscript/chex/chexitems.txt" #include "zscript/chex/chexdecorations.txt" #include "zscript/chex/chexplayer.txt" + +#include "zscript/scriptutil/scriptutil.txt" diff --git a/wadsrc/static/zscript/scriptutil/scriptutil.txt b/wadsrc/static/zscript/scriptutil/scriptutil.txt new file mode 100644 index 0000000000..a47f79486c --- /dev/null +++ b/wadsrc/static/zscript/scriptutil/scriptutil.txt @@ -0,0 +1,105 @@ + +// Container for utility functions used by ACS and FraggleScript. + +class ScriptUtil play +{ + + //========================================================================== + // + // + // + //========================================================================== + + static int SetWeapon(Actor activator, class cls) + { + if(activator != NULL && activator.player != NULL && cls != null) + { + let item = Weapon(activator.FindInventory(cls)); + + if(item != NULL) + { + if(activator.player.ReadyWeapon == item) + { + // The weapon is already selected, so setweapon succeeds by default, + // but make sure the player isn't switching away from it. + activator.player.PendingWeapon = WP_NOCHANGE; + return 1; + } + else + { + if(item.CheckAmmo(Weapon.EitherFire, false)) + { + // There's enough ammo, so switch to it. + activator.player.PendingWeapon = item; + return 1; + } + } + } + } + return 0; + } + + //========================================================================== + // + // + // + //========================================================================== + + static void SetMarineWeapon(Actor activator, int tid, int marineweapontype) + { + if (tid != 0) + { + let it = ActorIterator.Create(tid, 'ScriptedMarine'); + ScriptedMarine marine; + + while ((marine = ScriptedMarine(it.Next())) != NULL) + { + marine.SetWeapon(marineweapontype); + } + } + else + { + let marine = ScriptedMarine(activator); + if (marine != null) + { + marine.SetWeapon(marineweapontype); + } + } + } + + //========================================================================== + // + // + // + //========================================================================== + + static void SetMarineSprite(Actor activator, int tid, class type) + { + if (type != NULL) + { + if (tid != 0) + { + let it = ActorIterator.Create(tid, 'ScriptedMarine'); + ScriptedMarine marine; + + while ((marine = ScriptedMarine(it.Next())) != NULL) + { + marine.SetSprite(type); + } + } + else + { + let marine = ScriptedMarine(activator); + if (marine != null) + { + marine.SetSprite(type); + } + } + } + else + { + Console.Printf ("Unknown actor type: %s\n", type.GetClassName()); + } + } + +}