- scriptified the remaining Doom weapon code.

- implemented method calls from struct instances.
- optimized disassembly of VM call instructions to print the function's name at the end where it is more visible and does not need to be truncated. Also use the printable name for script functions here.
This commit is contained in:
Christoph Oelckers 2016-11-20 12:27:26 +01:00
parent ab6b2f369e
commit bb25c5faaa
11 changed files with 359 additions and 376 deletions

View File

@ -20,7 +20,6 @@
#include "g_shared/a_pickups.h"
// Include all the other Doom stuff here to reduce compile time
#include "a_doomweaps.cpp"
#include "a_painelemental.cpp"
#include "a_scriptedmarine.cpp"

View File

@ -1,251 +0,0 @@
/*
#include "actor.h"
#include "info.h"
#include "s_sound.h"
#include "m_random.h"
#include "a_pickups.h"
#include "d_player.h"
#include "p_pspr.h"
#include "p_local.h"
#include "gstrings.h"
#include "p_effect.h"
#include "gi.h"
#include "templates.h"
#include "vm.h"
#include "doomstat.h"
*/
void P_SetSafeFlash(AWeapon *weapon, player_t *player, FState *flashstate, int index);
static FRandom pr_firerail ("FireRail");
static FRandom pr_bfgspray ("BFGSpray");
static FRandom pr_oldbfg ("OldBFG");
//
// [RH] A_FireRailgun
//
static void FireRailgun(AActor *self, int offset_xy, bool fromweapon)
{
int damage;
player_t *player;
if (NULL == (player = self->player))
{
return;
}
AWeapon *weapon = self->player->ReadyWeapon;
if (weapon != NULL && fromweapon)
{
if (!weapon->DepleteAmmo (weapon->bAltFire, true, 1))
return;
FState *flash = weapon->FindState(NAME_Flash);
if (flash != NULL)
{
P_SetSafeFlash(weapon, player, flash, (pr_firerail()&1));
}
}
damage = deathmatch ? 100 : 150;
FRailParams p;
p.source = self;
p.damage = damage;
p.offset_xy = offset_xy;
P_RailAttack (&p);
}
DEFINE_ACTION_FUNCTION(AActor, A_FireRailgun)
{
PARAM_ACTION_PROLOGUE(AActor);
FireRailgun(self, 0, ACTION_CALL_FROM_PSPRITE());
return 0;
}
DEFINE_ACTION_FUNCTION(AActor, A_FireRailgunRight)
{
PARAM_ACTION_PROLOGUE(AActor);
FireRailgun(self, 10, ACTION_CALL_FROM_PSPRITE());
return 0;
}
DEFINE_ACTION_FUNCTION(AActor, A_FireRailgunLeft)
{
PARAM_ACTION_PROLOGUE(AActor);
FireRailgun(self, -10, ACTION_CALL_FROM_PSPRITE());
return 0;
}
//
// A_FireBFG
//
DEFINE_ACTION_FUNCTION(AActor, A_FireBFG)
{
PARAM_ACTION_PROLOGUE(AActor);
player_t *player;
if (NULL == (player = self->player))
{
return 0;
}
AWeapon *weapon = self->player->ReadyWeapon;
if (weapon != NULL && ACTION_CALL_FROM_PSPRITE())
{
if (!weapon->DepleteAmmo (weapon->bAltFire, true, deh.BFGCells))
return 0;
}
P_SpawnPlayerMissile (self, 0, 0, 0, PClass::FindActor("BFGBall"), self->Angles.Yaw, NULL, NULL, !!(dmflags2 & DF2_NO_FREEAIMBFG));
return 0;
}
//
// A_BFGSpray
// Spawn a BFG explosion on every monster in view
//
enum BFG_Flags
{
BFGF_HURTSOURCE = 1,
BFGF_MISSILEORIGIN = 2,
};
DEFINE_ACTION_FUNCTION(AActor, A_BFGSpray)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_CLASS_DEF (spraytype, AActor)
PARAM_INT_DEF (numrays)
PARAM_INT_DEF (damagecnt)
PARAM_ANGLE_DEF (angle)
PARAM_FLOAT_DEF (distance)
PARAM_ANGLE_DEF (vrange)
PARAM_INT_DEF (defdamage)
PARAM_INT_DEF (flags)
int i;
int j;
int damage;
DAngle an;
FTranslatedLineTarget t;
AActor *originator;
if (spraytype == NULL) spraytype = PClass::FindActor("BFGExtra");
if (numrays <= 0) numrays = 40;
if (damagecnt <= 0) damagecnt = 15;
if (angle == 0) angle = 90.;
if (distance <= 0) distance = 16 * 64;
if (vrange == 0) vrange = 32.;
// [RH] Don't crash if no target
if (!self->target)
return 0;
// [XA] Set the originator of the rays to the projectile (self) if
// the new flag is set, else set it to the player (self->target)
originator = (flags & BFGF_MISSILEORIGIN) ? self : (AActor *)(self->target);
// offset angles from its attack angle
for (i = 0; i < numrays; i++)
{
an = self->Angles.Yaw - angle / 2 + angle / numrays*i;
P_AimLineAttack(originator, an, distance, &t, vrange);
if (t.linetarget != NULL)
{
AActor *spray = Spawn(spraytype, t.linetarget->PosPlusZ(t.linetarget->Height / 4), ALLOW_REPLACE);
int dmgFlags = 0;
FName dmgType = NAME_BFGSplash;
if (spray != NULL)
{
if ((spray->flags6 & MF6_MTHRUSPECIES && self->target->GetSpecies() == t.linetarget->GetSpecies()) ||
(!(flags & BFGF_HURTSOURCE) && self->target == t.linetarget)) // [XA] Don't hit oneself unless we say so.
{
spray->Destroy(); // [MC] Remove it because technically, the spray isn't trying to "hit" them.
continue;
}
if (spray->flags5 & MF5_PUFFGETSOWNER) spray->target = self->target;
if (spray->flags3 & MF3_FOILINVUL) dmgFlags |= DMG_FOILINVUL;
if (spray->flags7 & MF7_FOILBUDDHA) dmgFlags |= DMG_FOILBUDDHA;
dmgType = spray->DamageType;
}
if (defdamage == 0)
{
damage = 0;
for (j = 0; j < damagecnt; ++j)
damage += (pr_bfgspray() & 7) + 1;
}
else
{
// if this is used, damagecnt will be ignored
damage = defdamage;
}
int newdam = P_DamageMobj(t.linetarget, originator, self->target, damage, dmgType, dmgFlags|DMG_USEANGLE, t.angleFromSource.Degrees);
P_TraceBleed(newdam > 0 ? newdam : damage, &t, self);
}
}
return 0;
}
//
// A_FireOldBFG
//
// This function emulates Doom's Pre-Beta BFG
// By Lee Killough 6/6/98, 7/11/98, 7/19/98, 8/20/98
//
// This code may not be used in other mods without appropriate credit given.
// Code leeches will be telefragged.
DEFINE_ACTION_FUNCTION(AActor, A_FireOldBFG)
{
PARAM_ACTION_PROLOGUE(AActor);
PClassActor *plasma[] = { PClass::FindActor("PlasmaBall1"), PClass::FindActor("PlasmaBall2") };
AActor * mo = NULL;
player_t *player;
bool doesautoaim = false;
if (NULL == (player = self->player))
{
return 0;
}
AWeapon *weapon = self->player->ReadyWeapon;
if (!ACTION_CALL_FROM_PSPRITE()) weapon = NULL;
if (weapon != NULL)
{
if (!weapon->DepleteAmmo (weapon->bAltFire, true, 1))
return 0;
doesautoaim = !(weapon->WeaponFlags & WIF_NOAUTOAIM);
weapon->WeaponFlags |= WIF_NOAUTOAIM; // No autoaiming that gun
}
self->player->extralight = 2;
// Save values temporarily
DAngle SavedPlayerAngle = self->Angles.Yaw;
DAngle SavedPlayerPitch = self->Angles.Pitch;
for (int i = 0; i < 2; i++) // Spawn two plasma balls in sequence
{
self->Angles.Yaw += ((pr_oldbfg()&127) - 64) * (90./768);
self->Angles.Pitch += ((pr_oldbfg()&127) - 64) * (90./640);
mo = P_SpawnPlayerMissile (self, plasma[i]);
// Restore saved values
self->Angles.Yaw = SavedPlayerAngle;
self->Angles.Pitch = SavedPlayerPitch;
}
if (doesautoaim && weapon != NULL)
{ // Restore autoaim setting
weapon->WeaponFlags &= ~WIF_NOAUTOAIM;
}
return 0;
}

View File

@ -4660,6 +4660,17 @@ void P_TraceBleed(int damage, FTranslatedLineTarget *t, AActor *puff)
P_TraceBleed(damage, t->linetarget->PosPlusZ(t->linetarget->Height/2), t->linetarget, t->angleFromSource, pitch);
}
DEFINE_ACTION_FUNCTION(_FTranslatedLineTarget, TraceBleed)
{
PARAM_SELF_STRUCT_PROLOGUE(FTranslatedLineTarget);
PARAM_INT(damage);
PARAM_OBJECT(missile, AActor);
P_TraceBleed(damage, self, missile);
return 0;
}
//==========================================================================
//
//

View File

@ -6878,6 +6878,22 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
return nullptr;
}
}
else if (Self->ValueType->IsKindOf(RUNTIME_CLASS(PStruct)))
{
bool writable;
if (Self->RequestAddress(ctx, &writable) && writable)
{
cls = static_cast<PStruct*>(Self->ValueType);
Self->ValueType = NewPointer(Self->ValueType);
}
else
{
// Cannot be made writable so we cannot use its methods.
ScriptPosition.Message(MSG_ERROR, "Invalid expression on left hand side of %s\n", MethodName.GetChars());
delete this;
return nullptr;
}
}
else
{
ScriptPosition.Message(MSG_ERROR, "Invalid expression on left hand side of %s\n", MethodName.GetChars());
@ -7250,18 +7266,22 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx)
{
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)
if (ArgList[i]->ValueType != TypeNullPtr)
{
ScriptPosition.Message(MSG_ERROR, "Type mismatch in reference argument", Function->SymbolName.GetChars());
x = nullptr;
}
else
{
x = ArgList[i];
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];
}
}
else x = ArgList[i];
}
failed |= (x == nullptr);
ArgList[i] = x;
@ -7305,13 +7325,6 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx)
{
ValueType = TypeVoid;
}
// If self is a struct, it will be a value type, not a reference, so we need to make an addresss request.
if (Self != nullptr && Self->ValueType->IsKindOf(RUNTIME_CLASS(PStruct)) && !Self->ValueType->IsKindOf(RUNTIME_CLASS(PClass)))
{
bool writable;
Self->RequestAddress(ctx, &writable);
}
return this;
}
@ -8852,6 +8865,13 @@ FxExpression *FxClassPtrCast::Resolve(FCompileContext &ctx)
return this;
}
}
else if (basex->ValueType == TypeString || basex->ValueType == TypeName)
{
FxExpression *x = new FxClassTypeCast(to, basex);
basex = nullptr;
delete this;
return x->Resolve(ctx);
}
// Everything else is an error.
ScriptPosition.Message(MSG_ERROR, "Cannot cast %s to %s. The types are incompatible.", basex->ValueType->DescriptiveName(), to->DescriptiveName());
delete this;

View File

@ -323,15 +323,15 @@ void VMDisasm(FILE *out, const VMOP *code, int codesize, const VMScriptFunction
case OP_CALL_K:
case OP_TAIL_K:
{
callfunc = (VMFunction *)func->KonstA[code[i].a].o;
callname = callfunc->Name != NAME_None ? callfunc->Name : "[anonfunc]";
col = printf_wrapper(out, "%.23s,%d", callname, code[i].b);
col = printf_wrapper(out, "[%p],%d", callfunc, code[i].b);
if (code[i].op == OP_CALL_K)
{
col += printf_wrapper(out, ",%d", code[i].c);
}
break;
}
case OP_RET:
if (code[i].b != REGT_NIL)
{
@ -494,7 +494,8 @@ void VMDisasm(FILE *out, const VMOP *code, int codesize, const VMScriptFunction
}
else if (code[i].op == OP_CALL_K || code[i].op == OP_TAIL_K)
{
printf_wrapper(out, " [%p]\n", callfunc);
callname = callfunc->IsKindOf(RUNTIME_CLASS(VMScriptFunction)) ? static_cast<VMScriptFunction*>(callfunc)->PrintableName : callfunc->Name != NAME_None ? callfunc->Name : "[anonfunc]";
printf_wrapper(out, " [%s]\n", callname);
}
else
{

View File

@ -56,6 +56,7 @@ zscript/doom/weaponchaingun.txt
zscript/doom/weaponchainsaw.txt
zscript/doom/weaponrlaunch.txt
zscript/doom/weaponplasma.txt
zscript/doom/weaponbfg.txt
zscript/doom/deadthings.txt
zscript/doom/doomammo.txt

View File

@ -364,7 +364,6 @@ class Actor : Thinker native
// End of MBF redundant functions.
native void A_MonsterRail();
native void A_BFGSpray(class<Actor> spraytype = "BFGExtra", int numrays = 40, int damagecount = 15, float angle = 90, float distance = 16*64, float vrange = 32, int damage = 0, int flags = 0);
native void A_Pain();
native void A_NoBlocking(bool drop = true);
void A_Fall() { A_NoBlocking(); }

View File

@ -840,6 +840,8 @@ struct FTranslatedLineTarget
Actor linetarget;
double angleFromSource;
bool unlinked; // found by a trace that went through an unlinked portal.
native void TraceBleed(int damage, Actor missile);
}
enum EAimFlags

View File

@ -1,6 +1,6 @@
// --------------------------------------------------------------------------
//
// Doom weapon base class
// Doom weap base class
//
// --------------------------------------------------------------------------
@ -13,102 +13,51 @@ class DoomWeapon : Weapon
}
// --------------------------------------------------------------------------
//
// BFG 9000
//
// --------------------------------------------------------------------------
class BFG9000 : DoomWeapon
extend class StateProvider
{
Default
{
Height 20;
Weapon.SelectionOrder 2800;
Weapon.AmmoUse 40;
Weapon.AmmoGive 40;
Weapon.AmmoType "Cell";
+WEAPON.NOAUTOFIRE;
Inventory.PickupMessage "$GOTBFG9000";
Tag "$TAG_BFG9000";
}
States
{
Ready:
BFGG A 1 A_WeaponReady;
Loop;
Deselect:
BFGG A 1 A_Lower;
Loop;
Select:
BFGG A 1 A_Raise;
Loop;
Fire:
BFGG A 20 A_BFGsound;
BFGG B 10 A_GunFlash;
BFGG B 10 A_FireBFG;
BFGG B 20 A_ReFire;
Goto Ready;
Flash:
BFGF A 11 Bright A_Light1;
BFGF B 6 Bright A_Light2;
Goto LightDone;
Spawn:
BFUG A -1;
Stop;
OldFire:
BFGG A 10 A_BFGsound;
BFGG BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 1 A_FireOldBFG;
BFGG B 0 A_Light0;
BFGG B 20 A_ReFire;
Goto Ready;
}
}
//
// [RH] A_FireRailgun
//
action void A_FireRailgun(int offset_xy = 0)
{
if (player == null)
{
return;
}
class BFGBall : Actor
{
Default
{
Radius 13;
Height 8;
Speed 25;
Damage 100;
Projectile;
+RANDOMIZE
RenderStyle "Add";
Alpha 0.75;
DeathSound "weapons/bfgx";
Obituary "$OB_MPBFG_BOOM";
}
States
{
Spawn:
BFS1 AB 4 Bright;
Loop;
Death:
BFE1 AB 8 Bright;
BFE1 C 8 Bright A_BFGSpray;
BFE1 DEF 8 Bright;
Stop;
}
}
class BFGExtra : Actor
{
Default
{
+NOBLOCKMAP
+NOGRAVITY
RenderStyle "Add";
Alpha 0.75;
DamageType "BFGSplash";
}
States
{
Spawn:
BFE2 ABCD 8 Bright;
Stop;
}
}
Weapon weap = player.ReadyWeapon;
if (weap != null && invoker == weap && stateinfo != null && stateinfo.mStateType == STATE_Psprite)
{
if (!weap.DepleteAmmo (weap.bAltFire, true, 1))
return;
State flash = weap.FindState('Flash');
if (flash != null)
{
player.SetSafeFlash(weap, flash, random[FireRail]()&1);
}
}
int damage = GetCVar("deathmatch") ? 100 : 150;
A_RailAttack(damage, offset_xy, false); // note that this function handles ammo depletion itself for Dehacked compatibility purposes.
}
action void A_FireRailgunLeft()
{
A_FireRailgun(-10);
}
action void A_FireRailgunRight()
{
A_FireRailgun(10);
}
action void A_RailWait()
{
// only here to satisfy old Dehacked patches.
}
}

View File

@ -0,0 +1,259 @@
// --------------------------------------------------------------------------
//
// BFG 9000
//
// --------------------------------------------------------------------------
class BFG9000 : DoomWeapon
{
Default
{
Height 20;
Weapon.SelectionOrder 2800;
Weapon.AmmoUse 40;
Weapon.AmmoGive 40;
Weapon.AmmoType "Cell";
+WEAPON.NOAUTOFIRE;
Inventory.PickupMessage "$GOTBFG9000";
Tag "$TAG_BFG9000";
}
States
{
Ready:
BFGG A 1 A_WeaponReady;
Loop;
Deselect:
BFGG A 1 A_Lower;
Loop;
Select:
BFGG A 1 A_Raise;
Loop;
Fire:
BFGG A 20 A_BFGsound;
BFGG B 10 A_GunFlash;
BFGG B 10 A_FireBFG;
BFGG B 20 A_ReFire;
Goto Ready;
Flash:
BFGF A 11 Bright A_Light1;
BFGF B 6 Bright A_Light2;
Goto LightDone;
Spawn:
BFUG A -1;
Stop;
OldFire:
BFGG A 10 A_BFGsound;
BFGG BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 1 A_FireOldBFG;
BFGG B 0 A_Light0;
BFGG B 20 A_ReFire;
Goto Ready;
}
}
//===========================================================================
//
// Weapon code (must be attached to StateProvider)
//
//===========================================================================
extend class StateProvider
{
action void A_BFGsound()
{
A_PlaySound("weapons/bfgf", CHAN_WEAPON);
}
//
// A_FireBFG
//
action void A_FireBFG()
{
if (player == null)
{
return;
}
Weapon weap = player.ReadyWeapon;
if (weap != null && invoker == weap && stateinfo != null && stateinfo.mStateType == STATE_Psprite)
{
if (!weap.DepleteAmmo (weap.bAltFire, true, 1))
return;
}
SpawnPlayerMissile("BFGBall", angle, 0, 0, 0, null, false, GetCVar("sv_nobfgaim"));
}
//
// A_FireOldBFG
//
// This function emulates Doom's Pre-Beta BFG
// By Lee Killough 6/6/98, 7/11/98, 7/19/98, 8/20/98
//
// This code may not be used in other mods without appropriate credit given.
// Code leeches will be telefragged.
action void A_FireOldBFG()
{
bool doesautoaim = false;
if (player == null)
{
return;
}
Weapon weap = player.ReadyWeapon;
if (invoker != weap || stateinfo == null || stateinfo.mStateType != STATE_Psprite) weap = null;
if (weap != null)
{
if (!weap.DepleteAmmo (weap.bAltFire, true, 1))
return;
doesautoaim = weap.bNoAutoaim;
weap.bNoAutoaim = true;
}
player.extralight = 2;
// Save values temporarily
double SavedPlayerAngle = angle;
double SavedPlayerPitch = pitch;
for (int i = 0; i < 2; i++) // Spawn two plasma balls in sequence
{
angle += ((random[OldBFG]() & 127) - 64) * (90./768);
pitch += ((random[OldBFG]() & 127) - 64) * (90./640);
SpawnPlayerMissile (i == 0? (class<Actor>)("PlasmaBall1") : (class<Actor>)("PlasmaBall2"));
// Restore saved values
angle = SavedPlayerAngle;
pitch = SavedPlayerPitch;
}
// Restore autoaim setting
if (weap != null) weap.bNoAutoaim = doesautoaim;
}
}
class BFGBall : Actor
{
Default
{
Radius 13;
Height 8;
Speed 25;
Damage 100;
Projectile;
+RANDOMIZE
RenderStyle "Add";
Alpha 0.75;
DeathSound "weapons/bfgx";
Obituary "$OB_MPBFG_BOOM";
}
States
{
Spawn:
BFS1 AB 4 Bright;
Loop;
Death:
BFE1 AB 8 Bright;
BFE1 C 8 Bright A_BFGSpray;
BFE1 DEF 8 Bright;
Stop;
}
}
class BFGExtra : Actor
{
Default
{
+NOBLOCKMAP
+NOGRAVITY
RenderStyle "Add";
Alpha 0.75;
DamageType "BFGSplash";
}
States
{
Spawn:
BFE2 ABCD 8 Bright;
Stop;
}
}
//===========================================================================
//
// Code (must be attached to Actor)
//
//===========================================================================
extend class Actor
{
//
// A_BFGSpray
// Spawn a BFG explosion on every monster in view
//
void A_BFGSpray(class<Actor> spraytype = "BFGExtra", int numrays = 40, int damagecnt = 15, double ang = 90, double distance = 16*64, double vrange = 32, int defdamage = 0, int flags = 0)
{
int damage;
FTranslatedLineTarget t;
// validate parameters
if (spraytype == null) spraytype = "BFGExtra";
if (numrays <= 0) numrays = 40;
if (damagecnt <= 0) damagecnt = 15;
if (ang == 0) ang = 90.;
if (distance <= 0) distance = 16 * 64;
if (vrange == 0) vrange = 32.;
// [RH] Don't crash if no target
if (!target) return;
// [XA] Set the originator of the rays to the projectile (self) if
// the new flag is set, else set it to the player (target)
Actor originator = (flags & BFGF_MISSILEORIGIN) ? self : target;
// offset angles from its attack ang
for (int i = 0; i < numrays; i++)
{
double an = angle - ang / 2 + ang / numrays*i;
originator.AimLineAttack(an, distance, t, vrange);
if (t.linetarget != null)
{
Actor spray = Spawn(spraytype, t.linetarget.pos + (0, 0, t.linetarget.Height / 4), ALLOW_REPLACE);
int dmgFlags = 0;
Name dmgType = 'BFGSplash';
if (spray != null)
{
if ((spray.bMThruSpecies && target.GetSpecies() == t.linetarget.GetSpecies()) ||
(!(flags & BFGF_HURTSOURCE) && target == t.linetarget)) // [XA] Don't hit oneself unless we say so.
{
spray.Destroy(); // [MC] Remove it because technically, the spray isn't trying to "hit" them.
continue;
}
if (spray.bPuffGetsOwner) spray.target = target;
if (spray.bFoilInvul) dmgFlags |= DMG_FOILINVUL;
if (spray.bFoilBuddha) dmgFlags |= DMG_FOILBUDDHA;
dmgType = spray.DamageType;
}
if (defdamage == 0)
{
damage = 0;
for (int j = 0; j < damagecnt; ++j)
damage += Random[BFGSpray](1, 8);
}
else
{
// if this is used, damagecnt will be ignored
damage = defdamage;
}
int newdam = t.linetarget.DamageMobj(originator, target, damage, dmgType, dmgFlags|DMG_USEANGLE, t.angleFromSource);
t.TraceBleed(newdam > 0 ? newdam : damage, self);
}
}
}
}

View File

@ -51,13 +51,6 @@ class StateProvider : Inventory native
action native void A_Lower();
action native void A_Raise();
action native void A_FireRailgun();
action native void A_FireRailgunLeft();
action native void A_FireRailgunRight();
action void A_RailWait() {}
action void A_BFGsound() { A_PlaySound("weapons/bfgf", CHAN_WEAPON); }
action native void A_FireBFG();
action native void A_FireOldBFG();
action native void A_ReFire(statelabel flash = null);
action native void A_ClearReFire();
action native void A_CheckReload();