- implemented pass-by-reference arguments - so far only for memory based variables.

- changed Dehacked weapon function lookup to check the symbol table instead of directly referencing the VM functions. Once scriptified these pointers will no longer be available.
- removed all special ATAGs from the VM. While well intentioned any pointer tagged with them is basically unusable because it'd trigger asserts all over the place.
- scriptified A_Punch for testing pass-by-reference parameters and stack variables.
This commit is contained in:
Christoph Oelckers 2016-11-19 01:23:56 +01:00
parent 7ff5069617
commit 3ce699bf9b
20 changed files with 205 additions and 126 deletions

View file

@ -166,36 +166,27 @@ static TArray<CodePointerAlias> MBFCodePointers;
struct AmmoPerAttack
{
VMNativeFunction **func;
ENamedName func;
int ammocount;
VMFunction *ptr;
};
DECLARE_ACTION(A_Punch)
DECLARE_ACTION(A_FirePistol)
DECLARE_ACTION(A_FireShotgun)
DECLARE_ACTION(A_FireShotgun2)
DECLARE_ACTION(A_FireCGun)
DECLARE_ACTION(A_FireMissile)
DECLARE_ACTION(A_Saw)
DECLARE_ACTION(A_FirePlasma)
DECLARE_ACTION(A_FireBFG)
DECLARE_ACTION(A_FireOldBFG)
DECLARE_ACTION(A_FireRailgun)
// Default ammo use of the various weapon attacks
static AmmoPerAttack AmmoPerAttacks[] = {
{ &AActor_A_Punch_VMPtr, 0},
{ &AActor_A_FirePistol_VMPtr, 1},
{ &AActor_A_FireShotgun_VMPtr, 1},
{ &AActor_A_FireShotgun2_VMPtr, 2},
{ &AActor_A_FireCGun_VMPtr, 1},
{ &AActor_A_FireMissile_VMPtr, 1},
{ &AActor_A_Saw_VMPtr, 0},
{ &AActor_A_FirePlasma_VMPtr, 1},
{ &AActor_A_FireBFG_VMPtr, -1}, // uses deh.BFGCells
{ &AActor_A_FireOldBFG_VMPtr, 1},
{ &AActor_A_FireRailgun_VMPtr, 1},
{ NULL, 0}
{ NAME_A_Punch, 0},
{ NAME_A_FirePistol, 1},
{ NAME_A_FireShotgun, 1},
{ NAME_A_FireShotgun2, 2},
{ NAME_A_FireCGun, 1},
{ NAME_A_FireMissile, 1},
{ NAME_A_Saw, 0},
{ NAME_A_FirePlasma, 1},
{ NAME_A_FireBFG, -1}, // uses deh.BFGCells
{ NAME_A_FireOldBFG, 1},
{ NAME_A_FireRailgun, 1},
{ NAME_None, 0}
};
@ -3095,7 +3086,12 @@ void FinishDehPatch ()
StateVisited[state] = true;
for(unsigned j = 0; AmmoPerAttacks[j].func != NULL; j++)
{
if (state->ActionFunc == *AmmoPerAttacks[j].func)
if (AmmoPerAttacks[i].ptr == nullptr)
{
auto p = dyn_cast<PFunction>(RUNTIME_CLASS(AStateProvider)->Symbols.FindSymbol(AmmoPerAttacks[i].func, true));
if (p != nullptr) AmmoPerAttacks[i].ptr = p->Variants[0].Implementation;
}
if (state->ActionFunc == AmmoPerAttacks[j].ptr)
{
found = true;
int use = AmmoPerAttacks[j].ammocount;

View file

@ -253,11 +253,10 @@ enum
WF_USER4OK = 1 << 11,
};
#define WPIECE1 1
#define WPIECE2 2
#define WPIECE3 4
#define WP_NOCHANGE ((AWeapon*)~0)
// The VM cannot deal with this as an invalid pointer because it performs a read barrier on every object pointer read.
// This doesn't have to point to a valid weapon, though, because WP_NOCHANGE is never dereferenced, but it must point to a valid object
// and the class descriptor just works fine for that.
#define WP_NOCHANGE ((AWeapon*)RUNTIME_CLASS_CASTLESS(AWeapon))
#define MAXPLAYERNAME 15

View file

@ -32,6 +32,7 @@ enum
VARF_Static = (1<<13), // static class data (by necessity read only.)
VARF_InternalAccess = (1<<14), // overrides VARF_ReadOnly for internal script code.
VARF_Override = (1<<15), // overrides a virtual function from the parent class.
VARF_Ref = (1<<16), // argument is passed by reference.
};
// Symbol information -------------------------------------------------------

View file

@ -15,7 +15,6 @@
#include "doomstat.h"
*/
static FRandom pr_punch ("Punch");
static FRandom pr_saw ("Saw");
static FRandom pr_fireshotgun2 ("FireSG2");
static FRandom pr_fireplasma ("FirePlasma");
@ -23,47 +22,6 @@ static FRandom pr_firerail ("FireRail");
static FRandom pr_bfgspray ("BFGSpray");
static FRandom pr_oldbfg ("OldBFG");
//
// A_Punch
//
DEFINE_ACTION_FUNCTION(AActor, A_Punch)
{
PARAM_ACTION_PROLOGUE(AActor);
DAngle angle;
int damage;
DAngle pitch;
FTranslatedLineTarget t;
if (self->player != NULL)
{
AWeapon *weapon = self->player->ReadyWeapon;
if (weapon != NULL && !(weapon->WeaponFlags & WIF_DEHAMMO) && ACTION_CALL_FROM_PSPRITE())
{
if (!weapon->DepleteAmmo (weapon->bAltFire))
return 0;
}
}
damage = (pr_punch()%10+1)<<1;
if (self->FindInventory<APowerStrength>())
damage *= 10;
angle = self->Angles.Yaw + pr_punch.Random2() * (5.625 / 256);
pitch = P_AimLineAttack (self, angle, MELEERANGE);
P_LineAttack (self, angle, MELEERANGE, pitch, damage, NAME_Melee, NAME_BulletPuff, LAF_ISMELEEATTACK, &t);
// turn to face target
if (t.linetarget)
{
S_Sound (self, CHAN_WEAPON, "*fist", 1, ATTN_NORM);
self->Angles.Yaw = t.angleFromSource;
}
return 0;
}
//
// A_FirePistol
//

View file

@ -289,6 +289,7 @@ public:
class AWeapon : public AStateProvider
{
DECLARE_CLASS_WITH_META(AWeapon, AStateProvider, PClassWeapon)
HAS_FIELDS
HAS_OBJECT_POINTERS
public:
DWORD WeaponFlags;

View file

@ -17,10 +17,13 @@
#include "g_level.h"
#include "d_net.h"
#include "serializer.h"
#include "thingdef.h"
#define BONUSADD 6
IMPLEMENT_CLASS(AWeapon, false, true, false, false)
extern FFlagDef WeaponFlagDefs[];
IMPLEMENT_CLASS(AWeapon, false, true, true, false)
IMPLEMENT_POINTERS_START(AWeapon)
IMPLEMENT_POINTER(Ammo1)
@ -28,6 +31,26 @@ IMPLEMENT_POINTERS_START(AWeapon)
IMPLEMENT_POINTER(SisterWeapon)
IMPLEMENT_POINTERS_END
void AWeapon::InitNativeFields()
{
auto meta = RUNTIME_CLASS(AWeapon);
meta->AddNativeField("bAltFire", TypeBool, myoffsetof(AWeapon, bAltFire));
// synthesize a symbol for each flag from the flag name tables to avoid redundant declaration of them.
for (size_t i = 0; WeaponFlagDefs[i].flagbit != 0xffffffff; i++)
{
if (WeaponFlagDefs[i].structoffset > 0)
{
meta->AddNativeField(FStringf("b%s", WeaponFlagDefs[i].name), (WeaponFlagDefs[i].fieldsize == 4 ? TypeSInt32 : TypeSInt16), WeaponFlagDefs[i].structoffset, WeaponFlagDefs[i].varflags, WeaponFlagDefs[i].flagbit);
}
}
// This flag is not accessible through actor definitions.
meta->AddNativeField("bDehAmmo", TypeSInt32, myoffsetof(AWeapon, WeaponFlags), VARF_ReadOnly, WIF_DEHAMMO);
}
FString WeaponSection;
TArray<FString> KeyConfWeapons;
FWeaponSlots *PlayingKeyConf;
@ -651,6 +674,15 @@ bool AWeapon::DepleteAmmo (bool altFire, bool checkEnough, int ammouse)
return true;
}
DEFINE_ACTION_FUNCTION(AWeapon, DepleteAmmo)
{
PARAM_SELF_PROLOGUE(AWeapon);
PARAM_BOOL(altfire);
PARAM_BOOL_DEF(checkenough);
PARAM_INT_DEF(ammouse);
ACTION_RETURN_BOOL(self->DepleteAmmo(altfire, checkenough, ammouse));
}
//===========================================================================
//

View file

@ -738,3 +738,15 @@ xx(Length)
xx(Unit)
xx(StateLabel)
xx(Overlay)
xx(A_Punch)
xx(A_FirePistol)
xx(A_FireShotgun)
xx(A_FireShotgun2)
xx(A_FireCGun)
xx(A_FireMissile)
xx(A_Saw)
xx(A_FirePlasma)
xx(A_FireBFG)
xx(A_FireOldBFG)
xx(A_FireRailgun)

View file

@ -1035,6 +1035,14 @@ AInventory *AActor::FindInventory (FName type)
return FindInventory(PClass::FindActor(type));
}
DEFINE_ACTION_FUNCTION(AActor, FindInventory)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_CLASS(type, AInventory);
PARAM_BOOL_DEF(subclass);
ACTION_RETURN_OBJECT(self->FindInventory(type, subclass));
}
//============================================================================
//
// AActor :: GiveInventoryType

View file

@ -3547,7 +3547,7 @@ ExpEmit FxShift::Emit(VMFunctionBuilder *build)
if (!op1.Konst)
{
op1.Free(build);
instr = InstrMap[index][op2.Konst? 0:2];
instr = InstrMap[index][op2.Konst? 2:0];
}
else
{
@ -7067,7 +7067,8 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx)
SAFE_RESOLVE_OPT(Self, ctx);
bool failed = false;
auto proto = Function->Variants[0].Proto;
auto argtypes = proto->ArgumentTypes;
auto &argtypes = proto->ArgumentTypes;
auto &argflags = Function->Variants[0].ArgFlags;
int implicit = Function->GetImplicitArgs();
@ -7100,8 +7101,29 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx)
}
assert(type != nullptr);
FxExpression *x = new FxTypeCast(ArgList[i], type, false);
FxExpression *x;
if (!(argflags[i + implicit] & VARF_Ref))
{
x = new FxTypeCast(ArgList[i], type, false);
x = x->Resolve(ctx);
}
else
{
bool writable;
ArgList[i] = ArgList[i]->Resolve(ctx); // nust be resolved before the address is requested.
ArgList[i]->RequestAddress(ctx, &writable);
ArgList[i]->ValueType = NewPointer(ArgList[i]->ValueType);
// For a reference argument the types must match 100%.
if (type != ArgList[i]->ValueType)
{
ScriptPosition.Message(MSG_ERROR, "Type mismatch in reference argument", Function->SymbolName.GetChars());
x = nullptr;
}
else
{
x = ArgList[i];
}
}
failed |= (x == nullptr);
ArgList[i] = x;
}

View file

@ -131,7 +131,7 @@ void SetImplicitArgs(TArray<PType *> *args, TArray<DWORD> *argflags, TArray<FNam
{
args->Push(NewPointer(cls));
}
args->Push(TypeState/*Info*/); // fixme: TypeState is not the correct type here!!!
args->Push(NewPointer(NewStruct("FStateParamInfo", nullptr)));
}
if (argflags != nullptr)
{

View file

@ -388,7 +388,7 @@ static FFlagDef InventoryFlagDefs[] =
DEFINE_DEPRECATED_FLAG(INTERHUBSTRIP),
};
static FFlagDef WeaponFlagDefs[] =
FFlagDef WeaponFlagDefs[] =
{
// Weapon flags
DEFINE_FLAG(WIF, NOAUTOFIRE, AWeapon, WeaponFlags),
@ -411,12 +411,14 @@ static FFlagDef WeaponFlagDefs[] =
DEFINE_FLAG(WIF, NOAUTOAIM, AWeapon, WeaponFlags),
DEFINE_FLAG(WIF, NODEATHDESELECT, AWeapon, WeaponFlags),
DEFINE_FLAG(WIF, NODEATHINPUT, AWeapon, WeaponFlags),
DEFINE_FLAG(WIF, ALT_USES_BOTH, AWeapon, WeaponFlags),
DEFINE_DUMMY_FLAG(NOLMS, false),
DEFINE_FLAG(WIF, ALT_USES_BOTH, AWeapon, WeaponFlags),
DEFINE_DUMMY_FLAG(ALLOW_WITH_RESPAWN_INVUL, false),
};
static FFlagDef PlayerPawnFlagDefs[] =
{
// PlayerPawn flags

View file

@ -157,13 +157,15 @@ enum
ATAG_OBJECT, // pointer to an object; will be followed by GC
// The following are all for documentation during debugging and are
// functionally no different than ATAG_GENERIC.
// functionally no different than ATAG_GENERIC (meaning they are useless because they trigger asserts all over the place.)
/*
ATAG_FRAMEPOINTER, // pointer to extra stack frame space for this function
ATAG_DREGISTER, // pointer to a data register
ATAG_FREGISTER, // pointer to a float register
ATAG_SREGISTER, // pointer to a string register
ATAG_AREGISTER, // pointer to an address register
*/
ATAG_RNG, // pointer to FRandom
ATAG_STATE = ATAG_GENERIC, // pointer to FState (cannot have its own type because there's no means to track inside the VM.)

View file

@ -87,7 +87,7 @@ begin:
OP(LFP):
ASSERTA(a); assert(sfunc != NULL); assert(sfunc->ExtraSpace > 0);
reg.a[a] = f->GetExtra();
reg.atag[a] = ATAG_FRAMEPOINTER;
reg.atag[a] = ATAG_GENERIC; // using ATAG_FRAMEPOINTER will cause endless asserts.
NEXTOP;
OP(LB):
@ -461,7 +461,7 @@ begin:
break;
case REGT_INT | REGT_ADDROF:
assert(C < f->NumRegD);
::new(param) VMValue(&reg.d[C], ATAG_DREGISTER);
::new(param) VMValue(&reg.d[C], ATAG_GENERIC);
break;
case REGT_INT | REGT_KONST:
assert(C < sfunc->NumKonstD);
@ -473,7 +473,7 @@ begin:
break;
case REGT_STRING | REGT_ADDROF:
assert(C < f->NumRegS);
::new(param) VMValue(&reg.s[C], ATAG_SREGISTER);
::new(param) VMValue(&reg.s[C], ATAG_GENERIC);
break;
case REGT_STRING | REGT_KONST:
assert(C < sfunc->NumKonstS);
@ -485,7 +485,7 @@ begin:
break;
case REGT_POINTER | REGT_ADDROF:
assert(C < f->NumRegA);
::new(param) VMValue(&reg.a[C], ATAG_AREGISTER);
::new(param) VMValue(&reg.a[C], ATAG_GENERIC);
break;
case REGT_POINTER | REGT_KONST:
assert(C < sfunc->NumKonstA);
@ -512,7 +512,7 @@ begin:
break;
case REGT_FLOAT | REGT_ADDROF:
assert(C < f->NumRegF);
::new(param) VMValue(&reg.f[C], ATAG_FREGISTER);
::new(param) VMValue(&reg.f[C], ATAG_GENERIC);
break;
case REGT_FLOAT | REGT_KONST:
assert(C < sfunc->NumKonstF);

View file

@ -2160,6 +2160,7 @@ void ZCCCompiler::InitFunctions()
if ((flags & VARF_Out) || (type != TypeVector2 && type != TypeVector3))
{
type = NewPointer(type);
flags |= VARF_Ref;
}
else if (type == TypeVector2)
{

View file

@ -48,6 +48,7 @@ zscript/doom/cyberdemon.txt
zscript/doom/spidermaster.txt
zscript/doom/keen.txt
zscript/doom/bossbrain.txt
zscript/doom/weaponfist.txt
zscript/doom/deadthings.txt
zscript/doom/doomammo.txt

View file

@ -133,6 +133,7 @@ class Actor : Thinker native
// DECORATE compatible functions
native bool CheckClass(class<Actor> checkclass, int ptr_select = AAPTR_DEFAULT, bool match_superclass = false);
native Inventory FindInventory(class<Inventory> itemtype, bool subclass = false);
native int CountInv(class<Inventory> itemtype, int ptr_select = AAPTR_DEFAULT);
native float GetDistance(bool checkz, int ptr = AAPTR_TARGET);
native float GetAngle(int flags, int ptr = AAPTR_DEFAULT);
@ -471,7 +472,6 @@ class Actor : Thinker native
native void A_DeQueueCorpse();
native void A_ClearLastHeard();
native bool A_SelectWeapon(class<Weapon> whichweapon, int flags = 0);
action native void A_Punch();
native void A_Feathers();
native void A_ClassBossHealth();
native void A_ShootGun();

View file

@ -829,7 +829,7 @@ enum EStateType
struct FStateParamInfo
{
state mCallingState;
EStateType mStateType;
/*EStateType*/int mStateType;
int mPSPIndex;
}

View file

@ -12,44 +12,6 @@ class DoomWeapon : Weapon
}
}
// --------------------------------------------------------------------------
//
// Fist
//
// --------------------------------------------------------------------------
class Fist : Weapon
{
Default
{
Weapon.SelectionOrder 3700;
Weapon.Kickback 100;
Obituary "$OB_MPFIST";
Tag "$TAG_FIST";
+WEAPON.WIMPY_WEAPON
+WEAPON.MELEEWEAPON
}
States
{
Ready:
PUNG A 1 A_WeaponReady;
Loop;
Deselect:
PUNG A 1 A_Lower;
Loop;
Select:
PUNG A 1 A_Raise;
Loop;
Fire:
PUNG B 4;
PUNG C 4 A_Punch;
PUNG D 5;
PUNG C 4;
PUNG B 5 A_ReFire;
Goto Ready;
}
}
// --------------------------------------------------------------------------
//

View file

@ -0,0 +1,80 @@
// --------------------------------------------------------------------------
//
// Fist
//
// --------------------------------------------------------------------------
class Fist : Weapon
{
Default
{
Weapon.SelectionOrder 3700;
Weapon.Kickback 100;
Obituary "$OB_MPFIST";
Tag "$TAG_FIST";
+WEAPON.WIMPY_WEAPON
+WEAPON.MELEEWEAPON
}
States
{
Ready:
PUNG A 1 A_WeaponReady;
Loop;
Deselect:
PUNG A 1 A_Lower;
Loop;
Select:
PUNG A 1 A_Raise;
Loop;
Fire:
PUNG B 4;
PUNG C 4 A_Punch;
PUNG D 5;
PUNG C 4;
PUNG B 5 A_ReFire;
Goto Ready;
}
}
//===========================================================================
//
// Code (must be attached to Actor)
//
//===========================================================================
extend class Actor
{
action void A_Punch()
{
FTranslatedLineTarget t;
if (player != null)
{
Weapon weap = player.ReadyWeapon;
if (weap != null && !weap.bDehAmmo && invoker == weap && stateinfo != null && stateinfo.mStateType == STATE_Psprite)
{
if (!weap.DepleteAmmo (weap.bAltFire))
return;
}
}
int damage = random[Punch](1, 10) << 1;
if (FindInventory("PowerStrength"))
damage *= 10;
double ang = angle + Random2[Punch]() * (5.625 / 256);
double pitch = AimLineAttack (ang, MELEERANGE);
LineAttack (ang, MELEERANGE, pitch, damage, 'Melee', "BulletPuff", LAF_ISMELEEATTACK, t);
// turn to face target
if (t.linetarget)
{
A_PlaySound ("*fist", CHAN_WEAPON);
angle = t.angleFromSource;
}
}
}

View file

@ -473,6 +473,8 @@ class Weapon : StateProvider native
Stop;
}
native bool DepleteAmmo(bool altFire, bool checkEnough = true, int ammouse = -1);
native action void A_ZoomFactor(float scale = 1, int flags = 0);
native action void A_SetCrosshair(int xhair);
const ZOOM_INSTANT = 1;