- scriptified the scripted marines.

- fixed symbol name generation for native functions.
- moved PrintableName to VMFunction so that native functions also have this information.
This commit is contained in:
Christoph Oelckers 2016-11-21 19:09:58 +01:00
parent 97763b5a2b
commit 360436c201
20 changed files with 658 additions and 756 deletions

View file

@ -867,7 +867,6 @@ set( NOT_COMPILED_SOURCE_FILES
sc_man_scanner.h
sc_man_scanner.re
g_doom/a_painelemental.cpp
g_doom/a_scriptedmarine.cpp
g_heretic/a_chicken.cpp
g_heretic/a_dsparil.cpp
g_heretic/a_hereticartifacts.cpp

View file

@ -7,21 +7,6 @@ class AScriptedMarine : public AActor
{
DECLARE_CLASS (AScriptedMarine, AActor)
public:
enum EMarineWeapon
{
WEAPON_Dummy,
WEAPON_Fist,
WEAPON_BerserkFist,
WEAPON_Chainsaw,
WEAPON_Pistol,
WEAPON_Shotgun,
WEAPON_SuperShotgun,
WEAPON_Chaingun,
WEAPON_RocketLauncher,
WEAPON_PlasmaRifle,
WEAPON_Railgun,
WEAPON_BFG
};
void Activate (AActor *activator);
void Deactivate (AActor *activator);

View file

@ -9,7 +9,6 @@
#include "gstrings.h"
#include "g_level.h"
#include "p_enemy.h"
#include "a_doomglobal.h"
#include "a_specialspot.h"
#include "templates.h"
#include "m_bbox.h"
@ -21,5 +20,3 @@
// Include all the other Doom stuff here to reduce compile time
#include "a_painelemental.cpp"
#include "a_scriptedmarine.cpp"

View file

@ -1,654 +0,0 @@
/*
#include "actor.h"
#include "p_enemy.h"
#include "a_action.h"
#include "m_random.h"
#include "p_local.h"
#include "a_doomglobal.h"
#include "s_sound.h"
#include "r_data/r_translate.h"
#include "vm.h"
#include "g_level.h"
*/
#define MARINE_PAIN_CHANCE 160
static FRandom pr_m_refire ("SMarineRefire");
static FRandom pr_m_punch ("SMarinePunch");
static FRandom pr_m_gunshot ("SMarineGunshot");
static FRandom pr_m_saw ("SMarineSaw");
static FRandom pr_m_fireshotgun2 ("SMarineFireSSG");
IMPLEMENT_CLASS(AScriptedMarine, false, false, false, false)
void AScriptedMarine::Serialize(FSerializer &arc)
{
Super::Serialize (arc);
auto def = (AScriptedMarine*)GetDefault();
arc.Sprite("spriteoverride", SpriteOverride, &def->SpriteOverride)
("currentweapon", CurrentWeapon, def->CurrentWeapon);
}
void AScriptedMarine::Activate (AActor *activator)
{
if (flags2 & MF2_DORMANT)
{
flags2 &= ~MF2_DORMANT;
tics = 1;
}
}
void AScriptedMarine::Deactivate (AActor *activator)
{
if (!(flags2 & MF2_DORMANT))
{
flags2 |= MF2_DORMANT;
tics = -1;
}
}
bool AScriptedMarine::GetWeaponStates(int weap, FState *&melee, FState *&missile)
{
static ENamedName WeaponNames[] =
{
NAME_None,
NAME_Fist,
NAME_Berserk,
NAME_Chainsaw,
NAME_Pistol,
NAME_Shotgun,
NAME_SSG,
NAME_Chaingun,
NAME_Rocket,
NAME_Plasma,
NAME_Railgun,
NAME_BFG
};
if (weap < WEAPON_Dummy || weap > WEAPON_BFG) weap = WEAPON_Dummy;
melee = FindState(NAME_Melee, WeaponNames[weap], true);
missile = FindState(NAME_Missile, WeaponNames[weap], true);
return melee != NULL || missile != NULL;
}
void AScriptedMarine::BeginPlay ()
{
Super::BeginPlay ();
// Set the current weapon
for(int i=WEAPON_Dummy; i<=WEAPON_BFG; i++)
{
FState *melee, *missile;
if (GetWeaponStates(i, melee, missile))
{
if (melee == MeleeState && missile == MissileState)
{
CurrentWeapon = i;
}
}
}
}
void AScriptedMarine::Tick ()
{
Super::Tick ();
// Override the standard sprite, if desired
if (SpriteOverride != 0 && sprite == SpawnState->sprite)
{
sprite = SpriteOverride;
}
if (special1 != 0)
{
if (CurrentWeapon == WEAPON_SuperShotgun)
{ // Play SSG reload sounds
int ticks = level.maptime - special1;
if (ticks < 47)
{
switch (ticks)
{
case 14:
S_Sound (this, CHAN_WEAPON, "weapons/sshoto", 1, ATTN_NORM);
break;
case 28:
S_Sound (this, CHAN_WEAPON, "weapons/sshotl", 1, ATTN_NORM);
break;
case 41:
S_Sound (this, CHAN_WEAPON, "weapons/sshotc", 1, ATTN_NORM);
break;
}
}
else
{
special1 = 0;
}
}
else
{ // Wait for a long refire time
if (level.maptime >= special1)
{
special1 = 0;
}
else
{
flags |= MF_JUSTATTACKED;
}
}
}
}
//============================================================================
//
// A_M_Refire
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_Refire)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_BOOL_DEF(ignoremissile);
if (self->target == NULL || self->target->health <= 0)
{
if (self->MissileState && pr_m_refire() < 160)
{ // Look for a new target most of the time
if (P_LookForPlayers (self, true, NULL) && P_CheckMissileRange (self))
{ // Found somebody new and in range, so don't stop shooting
return 0;
}
}
self->SetState (self->state + 1);
return 0;
}
if (((ignoremissile || self->MissileState == NULL) && !self->CheckMeleeRange ()) ||
!P_CheckSight (self, self->target) ||
pr_m_refire() < 4) // Small chance of stopping even when target not dead
{
self->SetState (self->state + 1);
}
return 0;
}
//============================================================================
//
// A_M_SawRefire
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_SawRefire)
{
PARAM_SELF_PROLOGUE(AActor);
if (self->target == NULL || self->target->health <= 0)
{
self->SetState (self->state + 1);
return 0;
}
if (!self->CheckMeleeRange ())
{
self->SetState (self->state + 1);
}
return 0;
}
//============================================================================
//
// A_MarineNoise
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_MarineNoise)
{
PARAM_SELF_PROLOGUE(AActor);
if (static_cast<AScriptedMarine *>(self)->CurrentWeapon == AScriptedMarine::WEAPON_Chainsaw)
{
S_Sound (self, CHAN_WEAPON, "weapons/sawidle", 1, ATTN_NORM);
}
return 0;
}
//============================================================================
//
// A_MarineChase
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_MarineChase)
{
PARAM_SELF_PROLOGUE(AActor);
CALL_ACTION(A_MarineNoise, self);
A_Chase (stack, self);
return 0;
}
//============================================================================
//
// A_MarineLook
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_MarineLook)
{
PARAM_SELF_PROLOGUE(AActor);
CALL_ACTION(A_MarineNoise, self);
CALL_ACTION(A_Look, self);
return 0;
}
//============================================================================
//
// A_M_Saw
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_Saw)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_SOUND_DEF (fullsound)
PARAM_SOUND_DEF (hitsound)
PARAM_INT_DEF (damage)
PARAM_CLASS_DEF (pufftype, AActor)
if (self->target == NULL)
return 0;
if (pufftype == NULL)
{
pufftype = PClass::FindActor(NAME_BulletPuff);
}
if (damage == 0)
{
damage = 2;
}
A_FaceTarget (self);
if (self->CheckMeleeRange ())
{
DAngle angle;
FTranslatedLineTarget t;
damage *= (pr_m_saw()%10+1);
angle = self->Angles.Yaw + pr_m_saw.Random2() * (5.625 / 256);
P_LineAttack (self, angle, SAWRANGE,
P_AimLineAttack (self, angle, SAWRANGE), damage,
NAME_Melee, pufftype, false, &t);
if (!t.linetarget)
{
S_Sound (self, CHAN_WEAPON, fullsound, 1, ATTN_NORM);
return 0;
}
S_Sound (self, CHAN_WEAPON, hitsound, 1, ATTN_NORM);
// turn to face target
angle = t.angleFromSource;
DAngle anglediff = deltaangle(self->Angles.Yaw, angle);
if (anglediff < 0.0)
{
if (anglediff < -4.5)
self->Angles.Yaw = angle + 90.0 / 21;
else
self->Angles.Yaw -= 4.5;
}
else
{
if (anglediff > 4.5)
self->Angles.Yaw = angle - 90.0 / 21;
else
self->Angles.Yaw += 4.5;
}
}
else
{
S_Sound (self, CHAN_WEAPON, fullsound, 1, ATTN_NORM);
}
//A_Chase (self);
return 0;
}
//============================================================================
//
// A_M_Punch
//
//============================================================================
static void MarinePunch(AActor *self, int damagemul)
{
DAngle angle;
int damage;
DAngle pitch;
FTranslatedLineTarget t;
if (self->target == NULL)
return;
damage = ((pr_m_punch()%10+1) << 1) * damagemul;
A_FaceTarget (self);
angle = self->Angles.Yaw + pr_m_punch.Random2() * (5.625 / 256);
pitch = P_AimLineAttack (self, angle, MELEERANGE);
P_LineAttack (self, angle, MELEERANGE, pitch, damage, NAME_Melee, NAME_BulletPuff, true, &t);
// turn to face target
if (t.linetarget)
{
S_Sound (self, CHAN_WEAPON, "*fist", 1, ATTN_NORM);
self->Angles.Yaw = t.angleFromSource;
}
}
DEFINE_ACTION_FUNCTION(AActor, A_M_Punch)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(mult);
MarinePunch(self, mult);
return 0;
}
//============================================================================
//
// P_GunShot2
//
//============================================================================
void P_GunShot2 (AActor *mo, bool accurate, DAngle pitch, PClassActor *pufftype)
{
DAngle angle;
int damage;
damage = 5*(pr_m_gunshot()%3+1);
angle = mo->Angles.Yaw;
if (!accurate)
{
angle += pr_m_gunshot.Random2() * (5.625 / 256);
}
P_LineAttack (mo, angle, MISSILERANGE, pitch, damage, NAME_Hitscan, pufftype);
}
//============================================================================
//
// A_M_FirePistol
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_FirePistol)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_BOOL(accurate);
if (self->target == NULL)
return 0;
S_Sound (self, CHAN_WEAPON, "weapons/pistol", 1, ATTN_NORM);
A_FaceTarget (self);
P_GunShot2 (self, accurate, P_AimLineAttack (self, self->Angles.Yaw, MISSILERANGE),
PClass::FindActor(NAME_BulletPuff));
return 0;
}
//============================================================================
//
// A_M_FireShotgun
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_FireShotgun)
{
PARAM_SELF_PROLOGUE(AActor);
DAngle pitch;
if (self->target == NULL)
return 0;
S_Sound (self, CHAN_WEAPON, "weapons/shotgf", 1, ATTN_NORM);
A_FaceTarget (self);
pitch = P_AimLineAttack (self, self->Angles.Yaw, MISSILERANGE);
for (int i = 0; i < 7; ++i)
{
P_GunShot2 (self, false, pitch, PClass::FindActor(NAME_BulletPuff));
}
self->special1 = level.maptime + 27;
return 0;
}
//============================================================================
//
// A_M_CheckAttack
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_CheckAttack)
{
PARAM_SELF_PROLOGUE(AActor);
if (self->special1 != 0 || self->target == NULL)
{
self->SetState (self->FindState("SkipAttack"));
}
else
{
A_FaceTarget (self);
}
return 0;
}
//============================================================================
//
// A_M_FireShotgun2
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_FireShotgun2)
{
PARAM_SELF_PROLOGUE(AActor);
DAngle pitch;
if (self->target == NULL)
return 0;
S_Sound (self, CHAN_WEAPON, "weapons/sshotf", 1, ATTN_NORM);
A_FaceTarget (self);
pitch = P_AimLineAttack (self, self->Angles.Yaw, MISSILERANGE);
for (int i = 0; i < 20; ++i)
{
int damage = 5*(pr_m_fireshotgun2()%3+1);
DAngle angle = self->Angles.Yaw + pr_m_fireshotgun2.Random2() * (11.25 / 256);
P_LineAttack (self, angle, MISSILERANGE,
pitch + pr_m_fireshotgun2.Random2() * (7.097 / 256), damage,
NAME_Hitscan, NAME_BulletPuff);
}
self->special1 = level.maptime;
return 0;
}
//============================================================================
//
// A_M_FireCGun
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_FireCGun)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_BOOL(accurate);
if (self->target == NULL)
return 0;
S_Sound (self, CHAN_WEAPON, "weapons/chngun", 1, ATTN_NORM);
A_FaceTarget (self);
P_GunShot2 (self, accurate, P_AimLineAttack (self, self->Angles.Yaw, MISSILERANGE),
PClass::FindActor(NAME_BulletPuff));
return 0;
}
//============================================================================
//
// A_M_FireMissile
//
// Giving a marine a rocket launcher is probably a bad idea unless you pump
// up his health, because he's just as likely to kill himself as he is to
// kill anything else with it.
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_FireMissile)
{
PARAM_SELF_PROLOGUE(AActor);
if (self->target == NULL)
return 0;
if (self->CheckMeleeRange ())
{ // If too close, punch it
MarinePunch(self, 1);
}
else
{
A_FaceTarget (self);
P_SpawnMissile (self, self->target, PClass::FindActor("Rocket"));
}
return 0;
}
//============================================================================
//
// A_M_FireRailgun
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_FireRailgun)
{
PARAM_SELF_PROLOGUE(AActor);
if (self->target == NULL)
return 0;
CALL_ACTION(A_MonsterRail, self);
self->special1 = level.maptime + 50;
return 0;
}
//============================================================================
//
// A_M_FirePlasma
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_FirePlasma)
{
PARAM_SELF_PROLOGUE(AActor);
if (self->target == NULL)
return 0;
A_FaceTarget (self);
P_SpawnMissile (self, self->target, PClass::FindActor("PlasmaBall"));
self->special1 = level.maptime + 20;
return 0;
}
//============================================================================
//
// A_M_BFGsound
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_BFGsound)
{
PARAM_SELF_PROLOGUE(AActor);
if (self->target == NULL)
return 0;
if (self->special1 != 0)
{
self->SetState (self->SeeState);
}
else
{
A_FaceTarget (self);
S_Sound (self, CHAN_WEAPON, "weapons/bfgf", 1, ATTN_NORM);
// Don't interrupt the firing sequence
self->PainChance = 0;
}
return 0;
}
//============================================================================
//
// A_M_FireBFG
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_FireBFG)
{
PARAM_SELF_PROLOGUE(AActor);
if (self->target == NULL)
return 0;
A_FaceTarget (self);
P_SpawnMissile (self, self->target, PClass::FindActor("BFGBall"));
self->special1 = level.maptime + 30;
self->PainChance = MARINE_PAIN_CHANCE;
return 0;
}
//---------------------------------------------------------------------------
void AScriptedMarine::SetWeapon (EMarineWeapon type)
{
if (GetWeaponStates(type, MeleeState, MissileState))
{
static const char *classes[] = {
"ScriptedMarine",
"MarineFist",
"MarineBerserk",
"MarineChainsaw",
"MarinePistol",
"MarineShotgun",
"MarineSSG",
"MarineChaingun",
"MarineRocket",
"MarinePlasma",
"MarineRailgun",
"MarineBFG"
};
const PClass *cls = PClass::FindClass(classes[type]);
if (cls != NULL)
DecalGenerator = GetDefaultByType(cls)->DecalGenerator;
else
DecalGenerator = NULL;
}
}
void AScriptedMarine::SetSprite (PClassActor *source)
{
if (source == NULL)
{ // A valid actor class wasn't passed, so use the standard sprite
SpriteOverride = sprite = GetClass()->OwnedStates[0].sprite;
// Copy the standard scaling
Scale = GetDefault()->Scale;
}
else
{ // Use the same sprite and scaling the passed class spawns with
SpriteOverride = sprite = GetDefaultByType (source)->SpawnState->sprite;
Scale = GetDefaultByType(source)->Scale;
}
}

View file

@ -359,7 +359,7 @@ struct FStateLabelStorage
}
}
FState *GetState(int pos, PClassActor *cls);
FState *GetState(int pos, PClassActor *cls, bool exact = false);
};
extern FStateLabelStorage StateLabels;

View file

@ -58,7 +58,6 @@
#include "sbar.h"
#include "m_swap.h"
#include "a_sharedglobal.h"
#include "a_doomglobal.h"
#include "a_strifeglobal.h"
#include "v_video.h"
#include "w_wad.h"
@ -6110,6 +6109,34 @@ static bool CharArrayParms(int &capacity, int &offset, int &a, int *Stack, int &
return true;
}
static void SetMarineWeapon(AActor *marine, int weapon)
{
static VMFunction *smw = nullptr;
if (smw == nullptr)
{
auto cls = PClass::FindActor("ScriptedMarine");
auto func = dyn_cast<PFunction>(cls->Symbols.FindSymbol("SetWeapon", true));
smw = func->Variants[0].Implementation;
}
VMValue params[2] = { marine, weapon };
VMFrameStack stack;
stack.Call(smw, params, 2, nullptr, 0, nullptr);
}
static void SetMarineSprite(AActor *marine, PClassActor *source)
{
static VMFunction *sms = nullptr;
if (sms == nullptr)
{
auto cls = PClass::FindActor("ScriptedMarine");
auto func = dyn_cast<PFunction>(cls->Symbols.FindSymbol("SetSprite", true));
sms = func->Variants[0].Implementation;
}
VMValue params[2] = { marine, source };
VMFrameStack stack;
stack.Call(sms, params, 2, nullptr, 0, nullptr);
}
int DLevelScript::RunScript ()
{
DACSThinker *controller = DACSThinker::ActiveThinker;
@ -8946,20 +8973,19 @@ scriptwait:
case PCD_SETMARINEWEAPON:
if (STACK(2) != 0)
{
AScriptedMarine *marine;
TActorIterator<AScriptedMarine> iterator (STACK(2));
AActor *marine;
NActorIterator iterator("ScriptedMarine", STACK(2));
while ((marine = iterator.Next()) != NULL)
{
marine->SetWeapon ((AScriptedMarine::EMarineWeapon)STACK(1));
SetMarineWeapon(marine, STACK(1));
}
}
else
{
if (activator != NULL && activator->IsKindOf (RUNTIME_CLASS(AScriptedMarine)))
if (activator != nullptr && activator->IsKindOf (PClass::FindClass("ScriptedMarine")))
{
barrier_cast<AScriptedMarine *>(activator)->SetWeapon (
(AScriptedMarine::EMarineWeapon)STACK(1));
SetMarineWeapon(activator, STACK(1));
}
}
sp -= 2;
@ -8973,19 +8999,19 @@ scriptwait:
{
if (STACK(2) != 0)
{
AScriptedMarine *marine;
TActorIterator<AScriptedMarine> iterator (STACK(2));
AActor *marine;
NActorIterator iterator("ScriptedMarine", STACK(2));
while ((marine = iterator.Next()) != NULL)
{
marine->SetSprite (type);
SetMarineSprite(marine, type);
}
}
else
{
if (activator != NULL && activator->IsKindOf (RUNTIME_CLASS(AScriptedMarine)))
if (activator != nullptr && activator->IsKindOf(PClass::FindClass("ScriptedMarine")))
{
barrier_cast<AScriptedMarine *>(activator)->SetSprite (type);
SetMarineSprite(activator, type);
}
}
}

View file

@ -143,7 +143,7 @@ bool ACustomInventory::CallStateChain (AActor *actor, FState *state)
// If an unsafe function (i.e. one that accesses user variables) is being detected, print a warning once and remove the bogus function. We may not call it because that would inevitably crash.
auto owner = FState::StaticFindStateOwner(state);
Printf(TEXTCOLOR_RED "Unsafe state call in state %s.%d to %s which accesses user variables. The action function has been removed from this state\n",
owner->TypeName.GetChars(), state - owner->OwnedStates, static_cast<VMScriptFunction *>(state->ActionFunc)->PrintableName.GetChars());
owner->TypeName.GetChars(), state - owner->OwnedStates, state->ActionFunc->PrintableName.GetChars());
state->ActionFunc = nullptr;
}

View file

@ -315,10 +315,11 @@ void AActor::InitNativeFields()
meta->AddNativeField("TelefogDestType", TypeActorClass, myoffsetof(AActor, TeleFogDestType));
meta->AddNativeField("SpawnState", TypeState, myoffsetof(AActor, SpawnState), VARF_ReadOnly);
meta->AddNativeField("SeeState", TypeState, myoffsetof(AActor, SeeState), VARF_ReadOnly);
meta->AddNativeField("MeleeState", TypeState, myoffsetof(AActor, MeleeState), VARF_ReadOnly);
meta->AddNativeField("MissileState", TypeState, myoffsetof(AActor, MissileState), VARF_ReadOnly);
meta->AddNativeField("MeleeState", TypeState, myoffsetof(AActor, MeleeState));
meta->AddNativeField("MissileState", TypeState, myoffsetof(AActor, MissileState));
//int ConversationRoot; // THe root of the current dialogue
//FStrifeDialogueNode *Conversation; // [RH] The dialogue to show when this actor is "used."
meta->AddNativeField("DecalGenerator", NewPointer(TypeVoid), myoffsetof(AActor, DecalGenerator));
//FDecalBase *DecalGenerator;
// synthesize a symbol for each flag from the flag name tables to avoid redundant declaration of them.

View file

@ -388,7 +388,7 @@ void DPSprite::SetState(FState *newstate, bool pending)
// If an unsafe function (i.e. one that accesses user variables) is being detected, print a warning once and remove the bogus function. We may not call it because that would inevitably crash.
auto owner = FState::StaticFindStateOwner(newstate);
Printf(TEXTCOLOR_RED "Unsafe state call in state %s.%d to %s which accesses user variables. The action function has been removed from this state\n",
owner->TypeName.GetChars(), newstate - owner->OwnedStates, static_cast<VMScriptFunction *>(newstate->ActionFunc)->PrintableName.GetChars());
owner->TypeName.GetChars(), newstate - owner->OwnedStates, newstate->ActionFunc->PrintableName.GetChars());
newstate->ActionFunc = nullptr;
}
if (newstate->CallAction(Owner->mo, Caller, &stp, &nextstate))

View file

@ -289,7 +289,7 @@ static bool VerifyJumpTarget(PClassActor *cls, FState *CallingState, int index)
//
//==========================================================================
FState *FStateLabelStorage::GetState(int pos, PClassActor *cls)
FState *FStateLabelStorage::GetState(int pos, PClassActor *cls, bool exact)
{
if (pos > 0x10000000)
{
@ -322,7 +322,7 @@ FState *FStateLabelStorage::GetState(int pos, PClassActor *cls)
else if (cls != nullptr)
{
FName *labels = (FName*)&Storage[pos + sizeof(int)];
return cls->FindState(val, labels, false);
return cls->FindState(val, labels, exact);
}
}
return nullptr;
@ -337,8 +337,9 @@ FState *FStateLabelStorage::GetState(int pos, PClassActor *cls)
DEFINE_ACTION_FUNCTION(AActor, FindState)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_STATE(newstate);
ACTION_RETURN_STATE(newstate);
PARAM_INT(newstate);
PARAM_BOOL_DEF(exact)
ACTION_RETURN_STATE(StateLabels.GetState(newstate, self->GetClass(), exact));
}
// same as above but context aware.

View file

@ -230,6 +230,7 @@ static PSymbol *FindBuiltinFunction(FName funcname, VMNativeFunction::NativeCall
{
PSymbolVMFunction *symfunc = new PSymbolVMFunction(funcname);
VMNativeFunction *calldec = new VMNativeFunction(func, funcname);
calldec->PrintableName = funcname.GetChars();
symfunc->Function = calldec;
sym = symfunc;
GlobalSymbols.AddSymbol(sym);
@ -1428,6 +1429,14 @@ FxExpression *FxTypeCast::Resolve(FCompileContext &ctx)
delete this;
return x;
}
else if (ValueType == TypeSpriteID && basex->IsInteger())
{
basex->ValueType = TypeSpriteID;
auto x = basex;
basex = nullptr;
delete this;
return x;
}
else if (ValueType == TypeStateLabel)
{
if (basex->ValueType == TypeNullPtr)

View file

@ -581,17 +581,21 @@ AFuncDesc *FindFunction(PStruct *cls, const char * string)
{
for (int i = 0; i < 2; i++)
{
// Since many functions have been declared with Actor as owning class, despited being members of something else, let's hack around this until they have been fixed or exported.
// Since many functions have been declared with Actor as owning class, despite being members of something else, let's hack around this until they have been fixed or exported.
// Since most of these are expected to be scriptified anyway, there's no point fixing them all before they get exported.
if (i == 1 && !cls->IsKindOf(RUNTIME_CLASS(PClassActor))) break;
if (i == 1)
{
if (!cls->IsKindOf(RUNTIME_CLASS(PClassActor))) break;
cls = RUNTIME_CLASS(AActor);
}
FStringf fullname("%s_%s", i == 0 ? cls->TypeName.GetChars() : "Actor", string);
int min = 0, max = AFTable.Size() - 1;
while (min <= max)
{
int mid = (min + max) / 2;
int lexval = stricmp(fullname, AFTable[mid].Name + 1);
int lexval = stricmp(cls->TypeName.GetChars(), AFTable[mid].ClassName + 1);
if (lexval == 0) lexval = stricmp(string, AFTable[mid].FuncName);
if (lexval == 0)
{
return &AFTable[mid];
@ -641,7 +645,10 @@ static int propcmp(const void * a, const void * b)
static int funccmp(const void * a, const void * b)
{
return stricmp(((AFuncDesc*)a)->Name + 1, ((AFuncDesc*)b)->Name + 1); // +1 to get past the prefix letter of the native class name, which gets omitted by the FName for the class.
// +1 to get past the prefix letter of the native class name, which gets omitted by the FName for the class.
int res = stricmp(((AFuncDesc*)a)->ClassName + 1, ((AFuncDesc*)b)->ClassName + 1);
if (res == 0) res = stricmp(((AFuncDesc*)a)->FuncName, ((AFuncDesc*)b)->FuncName);
return res;
}
//==========================================================================
@ -700,7 +707,8 @@ void InitThingdef()
{
AFuncDesc *afunc = (AFuncDesc *)*probe;
assert(afunc->VMPointer != NULL);
*(afunc->VMPointer) = new VMNativeFunction(afunc->Function, afunc->Name);
*(afunc->VMPointer) = new VMNativeFunction(afunc->Function, afunc->FuncName);
(*(afunc->VMPointer))->PrintableName.Format("%s.%s [Native]", afunc->ClassName+1, afunc->FuncName);
AFTable.Push(*afunc);
}
AFTable.ShrinkToFit();

View file

@ -669,6 +669,7 @@ public:
int VirtualIndex = -1;
FName Name;
TArray<VMValue> DefaultArgs;
FString PrintableName; // so that the VM can print meaningful info if something in this function goes wrong.
class PPrototype *Proto;
@ -822,7 +823,6 @@ public:
VM_UHALF NumKonstA;
VM_UHALF MaxParam; // Maximum number of parameters this function has on the stack at once
VM_UBYTE NumArgs; // Number of arguments this function takes
FString PrintableName; // so that the VM can print meaningful info if something in this function goes wrong.
TArray<FTypeAndOffset> SpecialInits; // list of all contents on the extra stack which require construction and destruction
void InitExtra(void *addr);
@ -1015,7 +1015,8 @@ typedef int(*actionf_p)(VMFrameStack *stack, VMValue *param, TArray<VMValue> &de
struct AFuncDesc
{
const char *Name;
const char *ClassName;
const char *FuncName;
actionf_p Function;
VMNativeFunction **VMPointer;
};
@ -1037,7 +1038,7 @@ struct AFuncDesc
#define DEFINE_ACTION_FUNCTION(cls, name) \
static int AF_##cls##_##name(VM_ARGS); \
VMNativeFunction *cls##_##name##_VMPtr; \
static const AFuncDesc cls##_##name##_Hook = { #cls "_" #name, AF_##cls##_##name, &cls##_##name##_VMPtr }; \
static const AFuncDesc cls##_##name##_Hook = { #cls, #name, AF_##cls##_##name, &cls##_##name##_VMPtr }; \
extern AFuncDesc const *const cls##_##name##_HookPtr; \
MSVC_ASEG AFuncDesc const *const cls##_##name##_HookPtr GCC_ASEG = &cls##_##name##_Hook; \
static int AF_##cls##_##name(VM_ARGS)

View file

@ -887,7 +887,7 @@ void FFunctionBuildList::Build()
catch (CRecoverableError &err)
{
// catch errors from the code generator and pring something meaningful.
item.Code->ScriptPosition.Message(MSG_ERROR, "%s in %s", err.GetMessage(), item.PrintableName);
item.Code->ScriptPosition.Message(MSG_ERROR, "%s in %s", err.GetMessage(), item.PrintableName.GetChars());
}
}
delete item.Code;

View file

@ -261,7 +261,6 @@ void VMDumpConstants(FILE *out, const VMScriptFunction *func)
void VMDisasm(FILE *out, const VMOP *code, int codesize, const VMScriptFunction *func)
{
VMFunction *callfunc;
const char *callname;
const char *name;
int col;
int mode;
@ -497,8 +496,7 @@ 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)
{
callname = callfunc->IsKindOf(RUNTIME_CLASS(VMScriptFunction)) ? static_cast<VMScriptFunction*>(callfunc)->PrintableName : callfunc->Name != NAME_None ? callfunc->Name : "[anonfunc]";
printf_wrapper(out, " [%s]\n", callname);
printf_wrapper(out, " [%s]\n", callfunc->PrintableName.GetChars());
}
else
{

View file

@ -787,11 +787,11 @@ type_list(X) ::= type_list(A) COMMA type_or_array(B). { X = A; /*X-overwrites-A*
type_list_or_void(X) ::= VOID. { X = NULL; }
type_list_or_void(X) ::= type_list(X).
array_size_expr(X) ::= LBRACKET opt_expr(A) RBRACKET.
array_size_expr(X) ::= LBRACKET(L) opt_expr(A) RBRACKET.
{
if (A == NULL)
{
NEW_AST_NODE(Expression,nil,A);
NEW_AST_NODE(Expression,nil,L.SourceLoc);
nil->Operation = PEX_Nil;
nil->Type = NULL;
X = nil;

View file

@ -2304,14 +2304,14 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool
{
if (vindex == -1)
{
Error(p, "Attempt to override non-existent virtual function %s", FName(f->Name).GetChars());
Error(f, "Attempt to override non-existent virtual function %s", FName(f->Name).GetChars());
}
else
{
auto oldfunc = clstype->Virtuals[vindex];
if (oldfunc->Final)
{
Error(p, "Attempt to override final function %s", FName(f->Name).GetChars());
Error(f, "Attempt to override final function %s", FName(f->Name).GetChars());
}
clstype->Virtuals[vindex] = sym->Variants[0].Implementation;
sym->Variants[0].Implementation->VirtualIndex = vindex;

View file

@ -55,7 +55,7 @@ inline int GetVirtualIndex(PClass *cls, const char *funcname)
static int VIndex = -1; \
if (VIndex < 0) { \
VIndex = GetVirtualIndex(RUNTIME_CLASS(cls), #funcname); \
if (VIndex < 0) I_Error("Unable to find virtual function in " #cls, #funcname); \
if (VIndex < 0) I_Error("Unable to find virtual function %s in " #cls, #funcname); \
}
#define VFUNC this->GetClass()->Virtuals[VIndex]

View file

@ -115,7 +115,7 @@ class Actor : Thinker native
native void NewChaseDir();
native bool CheckMissileRange();
native bool SetState(state st, bool nofunction = false);
native state FindState(statelabel st); // do we need exact later?
native state FindState(statelabel st, bool exact = false); // do we need exact later?
bool SetStateLabel(statelabel st, bool nofunction = false) { return SetState(FindState(st), nofunction); }
native action state ResolveState(statelabel st); // this one, unlike FindState, is context aware.
native void LinkToWorld();
@ -369,8 +369,6 @@ class Actor : Thinker native
native void A_Detonate();
native bool A_CallSpecial(int special, int arg1=0, int arg2=0, int arg3=0, int arg4=0, int arg5=0);
native void A_M_Saw(sound fullsound = "weapons/sawfull", sound hitsound = "weapons/sawhit", int damage = 2, class<Actor> pufftype = "BulletPuff");
native void A_ActiveSound();
native void A_FastChase();

View file

@ -1,8 +1,35 @@
// Scriptable marine -------------------------------------------------------
class ScriptedMarine : Actor native
class ScriptedMarine : Actor
{
const MARINE_PAIN_CHANCE = 160;
enum EMarineWeapon
{
WEAPON_Dummy,
WEAPON_Fist,
WEAPON_BerserkFist,
WEAPON_Chainsaw,
WEAPON_Pistol,
WEAPON_Shotgun,
WEAPON_SuperShotgun,
WEAPON_Chaingun,
WEAPON_RocketLauncher,
WEAPON_PlasmaRifle,
WEAPON_Railgun,
WEAPON_BFG
};
struct WeaponStates
{
state melee;
state missile;
}
int CurrentWeapon;
SpriteID SpriteOverride;
Default
{
Health 100;
@ -10,7 +37,7 @@ class ScriptedMarine : Actor native
Height 56;
Mass 100;
Speed 8;
Painchance 160;
Painchance MARINE_PAIN_CHANCE;
MONSTER;
-COUNTKILL
Translation 0;
@ -19,23 +46,6 @@ class ScriptedMarine : Actor native
PainSound "*pain50";
}
native void A_M_Refire (bool ignoremissile=false);
native void A_M_CheckAttack ();
native void A_MarineChase ();
native void A_MarineLook ();
native void A_MarineNoise ();
native void A_M_Punch (int force);
native void A_M_SawRefire ();
native void A_M_FirePistol (bool accurate);
native void A_M_FireShotgun ();
native void A_M_FireShotgun2 ();
native void A_M_FireCGun(bool accurate);
native void A_M_FireMissile ();
native void A_M_FirePlasma ();
native void A_M_FireRailgun ();
native void A_M_BFGsound ();
native void A_M_FireBFG ();
States
{
Spawn:
@ -58,25 +68,22 @@ class ScriptedMarine : Actor native
PLAY E 4 A_FaceTarget;
PLAY E 4 A_M_Punch(1);
PLAY A 9;
PLAY A 0 A_M_Refire(1);
PLAY A 0 A_M_Refire(1, "FistEnd");
Loop;
FistEnd:
PLAY A 5 A_FaceTarget;
Goto See;
Melee.Berserk:
PLAY E 4 A_FaceTarget;
PLAY E 4 A_M_Punch(10);
PLAY A 9;
PLAY A 0 A_M_Refire(1);
PLAY A 0 A_M_Refire(1, "FistEnd");
Loop;
PLAY A 5 A_FaceTarget;
Goto See;
Melee.Chainsaw:
PLAY E 4 A_MarineNoise;
PLAY E 4 A_M_Saw;
PLAY E 0 A_M_SawRefire;
goto Melee.Chainsaw+1;
PLAY A 0;
Goto See;
Missile:
Missile.None:
@ -88,16 +95,15 @@ class ScriptedMarine : Actor native
PLAY E 4 A_FaceTarget;
PLAY F 6 BRIGHT A_M_FirePistol(1);
PLAY A 4 A_FaceTarget;
PLAY A 0 A_M_Refire;
PLAY A 0 A_M_Refire(0, "ShootEnd");
ShootEnd:
PLAY A 5;
Goto See;
Fireloop.Pistol:
PLAY F 6 BRIGHT A_M_FirePistol(0);
PLAY A 4 A_FaceTarget;
PLAY A 0 A_M_Refire;
PLAY A 0 A_M_Refire(0, "ShootEnd");
Goto Fireloop.Pistol;
PLAY A 5;
Goto See;
Missile.Shotgun:
PLAY E 3 A_M_CheckAttack;
PLAY F 7 BRIGHT A_M_FireShotgun;
@ -110,26 +116,20 @@ class ScriptedMarine : Actor native
PLAY E 4 A_FaceTarget;
PLAY FF 4 BRIGHT A_M_FireCGun(1);
PLAY FF 4 BRIGHT A_M_FireCGun(0);
PLAY A 0 A_M_Refire;
PLAY A 0 A_M_Refire(0, "See");
Goto Missile.Chaingun+3;
PLAY A 0;
Goto See;
Missile.Rocket:
PLAY E 8;
PLAY F 6 BRIGHT A_M_FireMissile;
PLAY E 6;
PLAY A 0 A_M_Refire;
PLAY A 0 A_M_Refire(0, "See");
Loop;
PLAY A 0;
Goto See;
Missile.Plasma:
PLAY E 2 A_FaceTarget;
PLAY E 0 A_FaceTarget;
PLAY F 3 BRIGHT A_M_FirePlasma;
PLAY A 0 A_M_Refire;
PLAY A 0 A_M_Refire(0, "See");
Goto Missile.Plasma+1;
PLAY A 0;
Goto See;
Missile.Railgun:
PLAY E 4 A_M_CheckAttack;
PLAY F 6 BRIGHT A_M_FireRailgun;
@ -139,10 +139,8 @@ class ScriptedMarine : Actor native
PLAY EEEEE 5 A_FaceTarget;
PLAY F 6 BRIGHT A_M_FireBFG;
PLAY A 4 A_FaceTarget;
PLAY A 0 A_M_Refire;
PLAY A 0 A_M_Refire(0, "See");
Loop;
PLAY A 0;
Goto See;
SkipAttack:
PLAY A 1;
@ -169,6 +167,541 @@ class ScriptedMarine : Actor native
PLAY MLKJIH 5;
Goto See;
}
//============================================================================
//
//
//
//============================================================================
private bool GetWeaponStates(int weap, WeaponStates wstates)
{
static const statelabel MeleeNames[] =
{
"Melee.None", "Melee.Fist", "Melee.Berserk", "Melee.Chainsaw", "Melee.Pistol", "Melee.Shotgun",
"Melee.SSG", "Melee.Chaingun", "Melee.Rocket", "Melee.Plasma", "Melee.Railgun", "Melee.BFG"
};
static const statelabel MissileNames[] =
{
"Missile.None", "Missile.Fist", "Missile.Berserk", "Missile.Chainsaw", "Missile.Pistol", "Missile.Shotgun",
"Missile.SSG", "Missile.Chaingun", "Missile.Rocket", "Missile.Plasma", "Missile.Railgun", "Missile.BFG"
};
if (weap < WEAPON_Dummy || weap > WEAPON_BFG) weap = WEAPON_Dummy;
wstates.melee = FindState(MeleeNames[weap], true);
wstates.missile = FindState(MissileNames[weap], true);
return wstates.melee != null || wstates.missile != null;
}
//============================================================================
//
//
//
//============================================================================
override void BeginPlay ()
{
Super.BeginPlay ();
// Set the current weapon
for(int i = WEAPON_Dummy; i <= WEAPON_BFG; i++)
{
WeaponStates wstates;
if (GetWeaponStates(i, wstates))
{
if (wstates.melee == MeleeState && wstates.missile == MissileState)
{
CurrentWeapon = i;
}
}
}
}
//============================================================================
//
//
//
//============================================================================
// placeholder to make it compile for the time being.
SpriteID GetSprite(State st)
{
return SpriteID(0);
}
override void Tick ()
{
Super.Tick ();
// Override the standard sprite, if desired
if (SpriteOverride != 0 && sprite == GetSprite(SpawnState))
{
sprite = SpriteOverride;
}
if (special1 != 0)
{
if (CurrentWeapon == WEAPON_SuperShotgun)
{ // Play SSG reload sounds
int ticks = level.maptime - special1;
if (ticks < 47)
{
switch (ticks)
{
case 14:
A_PlaySound ("weapons/sshoto", CHAN_WEAPON);
break;
case 28:
A_PlaySound ("weapons/sshotl", CHAN_WEAPON);
break;
case 41:
A_PlaySound ("weapons/sshotc", CHAN_WEAPON);
break;
}
}
else
{
special1 = 0;
}
}
else
{ // Wait for a long refire time
if (level.maptime >= special1)
{
special1 = 0;
}
else
{
bJustAttacked = true;
}
}
}
}
//============================================================================
//
// A_M_Refire
//
//============================================================================
void A_M_Refire (bool ignoremissile, statelabel jumpto)
{
if (target == null || target.health <= 0)
{
if (MissileState && random[SMarineRefire]() < 160)
{ // Look for a new target most of the time
if (LookForPlayers (true) && CheckMissileRange ())
{ // Found somebody new and in range, so don't stop shooting
return;
}
}
SetStateLabel (jumpto);
return;
}
if (((ignoremissile || MissileState == null) && !CheckMeleeRange ()) ||
!CheckSight (target) || random[SMarineRefire]() < 4) // Small chance of stopping even when target not dead
{
SetStateLabel (jumpto);
}
}
//============================================================================
//
// A_M_SawRefire
//
//============================================================================
void A_M_SawRefire ()
{
if (target == null || target.health <= 0 || !CheckMeleeRange ())
{
SetStateLabel ("See");
}
}
//============================================================================
//
// A_MarineNoise
//
//============================================================================
void A_MarineNoise ()
{
if (CurrentWeapon == WEAPON_Chainsaw)
{
A_PlaySound ("weapons/sawidle", CHAN_WEAPON);
}
}
//============================================================================
//
// A_MarineChase
//
//============================================================================
void A_MarineChase ()
{
A_MarineNoise();
A_Chase ();
}
//============================================================================
//
// A_MarineLook
//
//============================================================================
void A_MarineLook ()
{
A_MarineNoise();
A_Look();
}
//============================================================================
//
// A_M_Punch (also used in the rocket attack.)
//
//============================================================================
void A_M_Punch(int damagemul)
{
FTranslatedLineTarget t;
if (target == null)
return;
int damage = (random[SMarinePunch](1, 10) << 1) * damagemul;
A_FaceTarget ();
double ang = angle + random2[SMarinePunch]() * (5.625 / 256);
double pitch = AimLineAttack (ang, MELEERANGE);
LineAttack (ang, MELEERANGE, pitch, damage, 'Melee', "BulletPuff", true, t);
// turn to face target
if (t.linetarget)
{
A_PlaySound ("*fist", CHAN_WEAPON);
angle = t.angleFromSource;
}
}
//============================================================================
//
// P_GunShot2
//
//============================================================================
private void GunShot2 (bool accurate, double pitch, class<Actor> pufftype)
{
int damage = 5 * random[SMarineGunshot](1,3);
double ang = angle;
if (!accurate)
{
ang += Random2[SMarineGunshot]() * (5.625 / 256);
}
LineAttack (ang, MISSILERANGE, pitch, damage, 'Hitscan', pufftype);
}
//============================================================================
//
// A_M_FirePistol
//
//============================================================================
void A_M_FirePistol (bool accurate)
{
if (target == null)
return;
A_PlaySound ("weapons/pistol", CHAN_WEAPON);
A_FaceTarget ();
GunShot2 (accurate, AimLineAttack (angle, MISSILERANGE), "BulletPuff");
}
//============================================================================
//
// A_M_FireShotgun
//
//============================================================================
void A_M_FireShotgun ()
{
if (target == null)
return;
A_PlaySound ("weapons/shotgf", CHAN_WEAPON);
A_FaceTarget ();
double pitch = AimLineAttack (angle, MISSILERANGE);
for (int i = 0; i < 7; ++i)
{
GunShot2 (false, pitch, "BulletPuff");
}
special1 = level.maptime + 27;
}
//============================================================================
//
// A_M_CheckAttack
//
//============================================================================
void A_M_CheckAttack ()
{
if (special1 != 0 || target == null)
{
SetStateLabel ("SkipAttack");
}
else
{
A_FaceTarget ();
}
}
//============================================================================
//
// A_M_FireShotgun2
//
//============================================================================
void A_M_FireShotgun2 ()
{
if (target == null)
return;
A_PlaySound ("weapons/sshotf", CHAN_WEAPON);
A_FaceTarget ();
double pitch = AimLineAttack (angle, MISSILERANGE);
for (int i = 0; i < 20; ++i)
{
int damage = 5*(random[SMarineFireSSG]()%3+1);
double ang = angle + Random2[SMarineFireSSG]() * (11.25 / 256);
LineAttack (ang, MISSILERANGE, pitch + Random2[SMarineFireSSG]() * (7.097 / 256), damage, 'Hitscan', "BulletPuff");
}
special1 = level.maptime;
}
//============================================================================
//
// A_M_FireCGun
//
//============================================================================
void A_M_FireCGun(bool accurate)
{
if (target == null)
return;
A_PlaySound ("weapons/chngun", CHAN_WEAPON);
A_FaceTarget ();
GunShot2 (accurate, AimLineAttack (angle, MISSILERANGE), "BulletPuff");
}
//============================================================================
//
// A_M_FireMissile
//
// Giving a marine a rocket launcher is probably a bad idea unless you pump
// up his health, because he's just as likely to kill himself as he is to
// kill anything else with it.
//
//============================================================================
void A_M_FireMissile ()
{
if (target == null)
return;
if (CheckMeleeRange ())
{ // If too close, punch it
A_M_Punch(1);
}
else
{
A_FaceTarget ();
SpawnMissile (target, "Rocket");
}
}
//============================================================================
//
// A_M_FireRailgun
//
//============================================================================
void A_M_FireRailgun ()
{
if (target == null)
return;
A_MonsterRail();
special1 = level.maptime + 50;
}
//============================================================================
//
// A_M_FirePlasma
//
//============================================================================
void A_M_FirePlasma ()
{
if (target == null)
return;
A_FaceTarget ();
SpawnMissile (target, "PlasmaBall");
special1 = level.maptime + 20;
}
//============================================================================
//
// A_M_BFGsound
//
//============================================================================
void A_M_BFGsound ()
{
if (target == null)
return;
if (special1 != 0)
{
SetState (SeeState);
}
else
{
A_FaceTarget ();
A_PlaySound ("weapons/bfgf", CHAN_WEAPON);
// Don't interrupt the firing sequence
PainChance = 0;
}
}
//============================================================================
//
// A_M_FireBFG
//
//============================================================================
void A_M_FireBFG ()
{
if (target == null)
return;
A_FaceTarget ();
SpawnMissile (target, "BFGBall");
special1 = level.maptime + 30;
PainChance = MARINE_PAIN_CHANCE;
}
//---------------------------------------------------------------------------
final void SetWeapon (int type)
{
WeaponStates wstates;
if (GetWeaponStates(type, wstates))
{
static const class<Actor> classes[] = {
"ScriptedMarine",
"MarineFist",
"MarineBerserk",
"MarineChainsaw",
"MarinePistol",
"MarineShotgun",
"MarineSSG",
"MarineChaingun",
"MarineRocket",
"MarinePlasma",
"MarineRailgun",
"MarineBFG"
};
MeleeState = wstates.melee;
MissileState = wstates.missile;
DecalGenerator = GetDefaultByType(classes[type]).DecalGenerator;
}
}
final void SetSprite (class<Actor> source)
{
if (source == null)
{ // A valid actor class wasn't passed, so use the standard sprite
SpriteOverride = sprite = GetSprite(SpawnState);
// Copy the standard scaling
Scale = Default.Scale;
}
else
{ // Use the same sprite and scaling the passed class spawns with
readonly<Actor> def = GetDefaultByType (source);
SpriteOverride = sprite = GetSprite(def.SpawnState);
Scale = def.Scale;
}
}
}
extend class Actor
{
//============================================================================
//
// A_M_Saw (this is globally exported)
//
//============================================================================
void A_M_Saw(sound fullsound = "weapons/sawfull", sound hitsound = "weapons/sawhit", int damage = 2, class<Actor> pufftype = "BulletPuff")
{
if (target == null)
return;
if (pufftype == null) pufftype = "BulletPuff";
if (damage == 0) damage = 2;
A_FaceTarget ();
if (CheckMeleeRange ())
{
FTranslatedLineTarget t;
damage *= random[SMarineSaw](1, 10);
double ang = angle + Random2[SMarineSaw]() * (5.625 / 256);
LineAttack (angle, SAWRANGE, AimLineAttack (angle, SAWRANGE), damage, 'Melee', pufftype, false, t);
if (!t.linetarget)
{
A_PlaySound (fullsound, 1, CHAN_WEAPON);
return;
}
A_PlaySound (hitsound, CHAN_WEAPON);
// turn to face target
ang = t.angleFromSource;
double anglediff = deltaangle(angle, ang);
if (anglediff < 0.0)
{
if (anglediff < -4.5)
angle = ang + 90.0 / 21;
else
angle -= 4.5;
}
else
{
if (anglediff > 4.5)
angle = ang - 90.0 / 21;
else
angle += 4.5;
}
}
else
{
A_PlaySound (fullsound, 1, CHAN_WEAPON);
}
}
}
//---------------------------------------------------------------------------