- scriptified Heretic's blaster.

- scriptified all Effect functions of Fastprojectile's children
- implemented access to class meta data.
- added a VM instruction to retrieve the class metadata, to eliminate the overhead of the function call that would otherwise be needed.
- made GetClass() a builtin so that it can use the new instruction

Important note about this commit: Scriptifying CFlameMissile::Effect revealed a problem with the virtual function interface: In order to work, this needs to be explicitly enabled for each single native class that may be used as a base for a scripted class. Needless to say, this will end up way too much work, as there are over 100 native classes, excluding those which will be scriptified. But in order to fix the problem this partially broken state needs to be committed first.
This commit is contained in:
Christoph Oelckers 2016-11-24 20:02:44 +01:00
parent 3f5bf88d69
commit 9ae272d753
23 changed files with 499 additions and 430 deletions

View file

@ -363,12 +363,6 @@ DEFINE_ACTION_FUNCTION(DObject, Destroy)
return 0;
}
DEFINE_ACTION_FUNCTION(DObject, GetClass)
{
PARAM_SELF_PROLOGUE(DObject);
ACTION_RETURN_OBJECT(self->GetClass());
}
//==========================================================================
//
//

View file

@ -46,133 +46,6 @@ void P_DSparilTeleport (AActor *actor);
extern bool P_AutoUseChaosDevice (player_t *player);
// Blaster FX 1 -------------------------------------------------------------
//----------------------------------------------------------------------------
//
// Thinker for the ultra-fast blaster PL2 ripper-spawning missile.
//
//----------------------------------------------------------------------------
class ABlasterFX1 : public AFastProjectile
{
DECLARE_CLASS(ABlasterFX1, AFastProjectile)
public:
void Effect ();
int DoSpecialDamage (AActor *target, int damage, FName damagetype);
};
int ABlasterFX1::DoSpecialDamage (AActor *target, int damage, FName damagetype)
{
if (target->IsKindOf (PClass::FindClass ("Ironlich")))
{ // Less damage to Ironlich bosses
damage = pr_bfx1() & 1;
if (!damage)
{
return -1;
}
}
return damage;
}
void ABlasterFX1::Effect ()
{
if (pr_bfx1t() < 64)
{
Spawn("BlasterSmoke", PosAtZ(MAX(Z() - 8., floorz)), ALLOW_REPLACE);
}
}
IMPLEMENT_CLASS(ABlasterFX1, false, false, false, false)
// Ripper -------------------------------------------------------------------
class ARipper : public AActor
{
DECLARE_CLASS (ARipper, AActor)
public:
int DoSpecialDamage (AActor *target, int damage, FName damagetype);
};
IMPLEMENT_CLASS(ARipper, false, false, false, false)
int ARipper::DoSpecialDamage (AActor *target, int damage, FName damagetype)
{
if (target->IsKindOf (PClass::FindClass ("Ironlich")))
{ // Less damage to Ironlich bosses
damage = pr_ripd() & 1;
if (!damage)
{
return -1;
}
}
return damage;
}
//----------------------------------------------------------------------------
//
// PROC A_FireBlasterPL1
//
//----------------------------------------------------------------------------
DEFINE_ACTION_FUNCTION(AActor, A_FireBlasterPL1)
{
PARAM_ACTION_PROLOGUE(AActor);
DAngle ang;
int damage;
player_t *player;
if (NULL == (player = self->player))
{
return 0;
}
AWeapon *weapon = self->player->ReadyWeapon;
if (weapon != NULL)
{
if (!weapon->DepleteAmmo (weapon->bAltFire))
return 0;
}
DAngle pitch = P_BulletSlope(self);
damage = pr_fb1.HitDice (4);
ang = self->Angles.Yaw;
if (player->refire)
{
ang += pr_fb1.Random2() * (5.625 / 256);
}
P_LineAttack (self, ang, PLAYERMISSILERANGE, pitch, damage, NAME_Hitscan, "BlasterPuff");
S_Sound (self, CHAN_WEAPON, "weapons/blastershoot", 1, ATTN_NORM);
return 0;
}
//----------------------------------------------------------------------------
//
// PROC A_SpawnRippers
//
//----------------------------------------------------------------------------
DEFINE_ACTION_FUNCTION(AActor, A_SpawnRippers)
{
PARAM_SELF_PROLOGUE(AActor);
unsigned int i;
DAngle ang;
AActor *ripper;
for(i = 0; i < 8; i++)
{
ripper = Spawn<ARipper> (self->Pos(), ALLOW_REPLACE);
ang = i*45.;
ripper->target = self->target;
ripper->Angles.Yaw = ang;
ripper->VelFromAngle();
P_CheckMissileSpawn (ripper, self->radius);
}
return 0;
}
// --- Skull rod ------------------------------------------------------------

View file

@ -25,38 +25,6 @@ void A_CFlameMissile (AActor *);
// Flame Missile ------------------------------------------------------------
class ACFlameMissile : public AFastProjectile
{
DECLARE_CLASS (ACFlameMissile, AFastProjectile)
public:
void BeginPlay ();
void Effect ();
};
IMPLEMENT_CLASS(ACFlameMissile, false, false, false, false)
void ACFlameMissile::BeginPlay ()
{
special1 = 2;
}
void ACFlameMissile::Effect ()
{
if (!--special1)
{
special1 = 4;
double newz = Z() - 12;
if (newz < floorz)
{
newz = floorz;
}
AActor *mo = Spawn ("CFlameFloor", PosAtZ(newz), ALLOW_REPLACE);
if (mo)
{
mo->Angles.Yaw = Angles.Yaw;
}
}
}
//============================================================================
//
@ -80,7 +48,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_CFlameAttack)
if (!weapon->DepleteAmmo (weapon->bAltFire))
return 0;
}
P_SpawnPlayerMissile (self, RUNTIME_CLASS(ACFlameMissile));
P_SpawnPlayerMissile (self, PClass::FindActor("CFlameMissile"));
S_Sound (self, CHAN_WEAPON, "ClericFlameFire", 1, ATTN_NORM);
return 0;
}

View file

@ -6,6 +6,7 @@
#include "p_lnspec.h"
#include "b_bot.h"
#include "p_checkposition.h"
#include "virtual.h"
IMPLEMENT_CLASS(AFastProjectile, false, false, false, false)
@ -132,7 +133,13 @@ void AFastProjectile::Tick ()
if (!frac.isZero() && ripcount <= 0)
{
ripcount = count >> 3;
Effect();
// call the scripted 'Effect' method.
VINDEX(AFastProjectile, Effect);
// Without the type cast this picks the 'void *' assignment...
VMValue params[1] = { (DObject*)this };
VMFrameStack stack;
stack.Call(VFUNC, params, 1, nullptr, 0, nullptr);
}
}
}
@ -153,35 +160,3 @@ void AFastProjectile::Tick ()
}
void AFastProjectile::Effect()
{
FName name = GetClass()->MissileName;
if (name != NAME_None)
{
double hitz = Z()-8;
if (hitz < floorz)
{
hitz = floorz;
}
// Do not clip this offset to the floor.
hitz += GetClass()->MissileHeight;
PClassActor *trail = PClass::FindActor(name);
if (trail != NULL)
{
AActor *act = Spawn (trail, PosAtZ(hitz), ALLOW_REPLACE);
if (act != nullptr)
{
if ((flags5 & MF5_GETOWNER) && (target != nullptr))
act->target = target;
else
act->target = this;
act->Angles.Pitch = Angles.Pitch;
act->Angles.Yaw = Angles.Yaw;
}
}
}
}

View file

@ -228,7 +228,6 @@ class AFastProjectile : public AActor
DECLARE_CLASS(AFastProjectile, AActor)
public:
void Tick ();
virtual void Effect();
};

View file

@ -230,7 +230,6 @@ PClassActor::PClassActor()
BurnHeight = -1;
GibHealth = INT_MIN;
WoundHealth = 6;
PoisonDamage = 0;
FastSpeed = -1.;
RDFactor = 1.;
CameraHeight = INT_MIN;
@ -291,7 +290,6 @@ void PClassActor::DeriveData(PClass *newclass)
newa->BloodColor = BloodColor;
newa->GibHealth = GibHealth;
newa->WoundHealth = WoundHealth;
newa->PoisonDamage = PoisonDamage;
newa->FastSpeed = FastSpeed;
newa->RDFactor = RDFactor;
newa->CameraHeight = CameraHeight;

View file

@ -296,7 +296,6 @@ public:
PalEntry BloodColor; // Colorized blood
int GibHealth; // Negative health below which this monster dies an extreme death
int WoundHealth; // Health needed to enter wound state
int PoisonDamage; // Amount of poison damage
double FastSpeed; // speed in fast mode
double RDFactor; // Radius damage factor
double CameraHeight; // Height of camera when used as such

View file

@ -285,6 +285,7 @@ xx(FRandom)
xx(Random2)
xx(RandomPick)
xx(FRandomPick)
xx(GetClass)
xx(Exp)
xx(Log10)
xx(Ceil)

View file

@ -310,6 +310,28 @@ DEFINE_FIELD(AActor, ConversationRoot)
DEFINE_FIELD(AActor, Conversation)
DEFINE_FIELD(AActor, DecalGenerator)
DEFINE_FIELD(PClassActor, Obituary)
DEFINE_FIELD(PClassActor, HitObituary)
DEFINE_FIELD(PClassActor, DeathHeight)
DEFINE_FIELD(PClassActor, BurnHeight)
DEFINE_FIELD(PClassActor, BloodColor)
DEFINE_FIELD(PClassActor, GibHealth)
DEFINE_FIELD(PClassActor, WoundHealth)
DEFINE_FIELD(PClassActor, FastSpeed)
DEFINE_FIELD(PClassActor, RDFactor)
DEFINE_FIELD(PClassActor, CameraHeight)
DEFINE_FIELD(PClassActor, HowlSound)
DEFINE_FIELD(PClassActor, BloodType)
DEFINE_FIELD(PClassActor, BloodType2)
DEFINE_FIELD(PClassActor, BloodType3)
DEFINE_FIELD(PClassActor, DontHurtShooter)
DEFINE_FIELD(PClassActor, ExplosionRadius)
DEFINE_FIELD(PClassActor, ExplosionDamage)
DEFINE_FIELD(PClassActor, MeleeDamage)
DEFINE_FIELD(PClassActor, MeleeSound)
DEFINE_FIELD(PClassActor, MissileName)
DEFINE_FIELD(PClassActor, MissileHeight)
//==========================================================================
//
// AActor :: Serialize

View file

@ -5637,12 +5637,6 @@ FxExpression *FxIdentifier::ResolveMember(FCompileContext &ctx, PStruct *classct
return nullptr;
}
if (vsym->Flags & VARF_Static)
{
// todo. For now these cannot be defined so let's just exit.
ScriptPosition.Message(MSG_ERROR, "Static members not implemented yet.");
return nullptr;
}
auto x = isclass ? new FxClassMember(object, vsym, ScriptPosition) : new FxStructMember(object, vsym, ScriptPosition);
object = nullptr;
return x->Resolve(ctx);
@ -5918,40 +5912,14 @@ FxExpression *FxClassDefaults::Resolve(FCompileContext& ctx)
//
//==========================================================================
int BuiltinGetDefault(VMFrameStack *stack, VMValue *param, TArray<VMValue> &defaultparam, int numparam, VMReturn *ret, int numret)
{
assert(numparam == 1);
PARAM_POINTER_AT(0, obj, DObject);
ACTION_RETURN_OBJECT(obj->GetClass()->Defaults);
}
//==========================================================================
//
//
//
//==========================================================================
ExpEmit FxClassDefaults::Emit(VMFunctionBuilder *build)
{
EmitParameter(build, obj, ScriptPosition);
PSymbol *sym = FindBuiltinFunction(NAME_BuiltinGetDefault, BuiltinGetDefault);
assert(sym->IsKindOf(RUNTIME_CLASS(PSymbolVMFunction)));
assert(((PSymbolVMFunction *)sym)->Function != nullptr);
auto callfunc = ((PSymbolVMFunction *)sym)->Function;
int opcode = (EmitTail ? OP_TAIL_K : OP_CALL_K);
build->Emit(opcode, build->GetConstantAddress(callfunc, ATAG_OBJECT), 1, 1);
if (EmitTail)
{
ExpEmit call;
call.Final = true;
return call;
}
ExpEmit out(build, REGT_POINTER);
build->Emit(OP_RESULT, 0, REGT_POINTER, out.RegNum);
return out;
ExpEmit ob = obj->Emit(build);
ob.Free(build);
ExpEmit meta(build, REGT_POINTER);
build->Emit(OP_META, meta.RegNum, ob.RegNum);
build->Emit(OP_LO, meta.RegNum, meta.RegNum, build->GetConstantInt(myoffsetof(PClass, Defaults)));
return meta;
}
@ -6272,6 +6240,11 @@ FxStructMember::~FxStructMember()
bool FxStructMember::RequestAddress(FCompileContext &ctx, bool *writable)
{
// Cannot take the address of metadata variables.
if (membervar->Flags & VARF_Static)
{
return false;
}
AddressRequested = true;
if (writable != nullptr) *writable = (AddressWritable && !ctx.CheckReadOnly(membervar->Flags) &&
(!classx->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) || !static_cast<PPointer*>(classx->ValueType)->IsConst));
@ -6411,6 +6384,14 @@ ExpEmit FxStructMember::Emit(VMFunctionBuilder *build)
obj = newobj;
}
if (membervar->Flags & VARF_Static)
{
obj.Free(build);
ExpEmit meta(build, REGT_POINTER);
build->Emit(OP_META, meta.RegNum, obj.RegNum);
obj = meta;
}
if (AddressRequested)
{
if (membervar->Offset == 0)
@ -6953,6 +6934,13 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
}
break;
case NAME_GetClass:
if (CheckArgSize(NAME_GetClass, ArgList, 0, 0, ScriptPosition))
{
func = new FxGetClass(new FxSelf(ScriptPosition));
}
break;
case NAME_Random:
// allow calling Random without arguments to default to (0, 255)
if (ArgList.Size() == 0)
@ -7132,6 +7120,12 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
// handle builtins: Vectors got 2: Length and Unit.
if (MethodName == NAME_Length || MethodName == NAME_Unit)
{
if (ArgList.Size() > 0)
{
ScriptPosition.Message(MSG_ERROR, "too many parameters in call to %s", MethodName.GetChars());
delete this;
return nullptr;
}
auto x = new FxVectorBuiltin(Self, MethodName);
Self = nullptr;
delete this;
@ -7144,6 +7138,17 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
auto ptype = static_cast<PPointer *>(Self->ValueType)->PointedType;
if (ptype->IsKindOf(RUNTIME_CLASS(PStruct)))
{
if (ptype->IsKindOf(RUNTIME_CLASS(PClass)) && MethodName == NAME_GetClass)
{
if (ArgList.Size() > 0)
{
ScriptPosition.Message(MSG_ERROR, "too many parameters in call to %s", MethodName.GetChars());
delete this;
return nullptr;
}
auto x = new FxGetClass(Self);
return x->Resolve(ctx);
}
cls = static_cast<PStruct *>(ptype);
}
else
@ -7976,6 +7981,44 @@ ExpEmit FxVectorBuiltin::Emit(VMFunctionBuilder *build)
return to;
}
//==========================================================================
//
//
//==========================================================================
FxGetClass::FxGetClass(FxExpression *self)
:FxExpression(EFX_GetClass, self->ScriptPosition)
{
Self = self;
}
FxGetClass::~FxGetClass()
{
SAFE_DELETE(Self);
}
FxExpression *FxGetClass::Resolve(FCompileContext &ctx)
{
SAFE_RESOLVE(Self, ctx);
if (!Self->IsObject())
{
ScriptPosition.Message(MSG_ERROR, "GetClass() requires an object");
delete this;
return nullptr;
}
ValueType = NewClassPointer(static_cast<PClass*>(static_cast<PPointer*>(Self->ValueType)->PointedType));
return this;
}
ExpEmit FxGetClass::Emit(VMFunctionBuilder *build)
{
ExpEmit op = Self->Emit(build);
op.Free(build);
ExpEmit to(build, REGT_POINTER);
build->Emit(OP_META, to.RegNum, op.RegNum);
return to;
}
//==========================================================================
//
// FxSequence :: Resolve

View file

@ -286,6 +286,7 @@ enum EFxType
EFX_StaticArrayVariable,
EFX_CVar,
EFX_NamedNode,
EFX_GetClass,
EFX_COUNT
};
@ -320,6 +321,7 @@ public:
bool IsPointer() const { return ValueType->GetRegType() == REGT_POINTER; }
bool IsVector() const { return ValueType == TypeVector2 || ValueType == TypeVector3; };
bool IsBoolCompat() const { return ValueType->GetRegCount() == 1 && (ValueType->GetRegType() == REGT_INT || ValueType->GetRegType() == REGT_FLOAT || ValueType->GetRegType() == REGT_POINTER); }
bool IsObject() const { return ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) && !ValueType->IsKindOf(RUNTIME_CLASS(PClassPointer)) && ValueType != TypeNullPtr && static_cast<PPointer*>(ValueType)->PointedType->IsKindOf(RUNTIME_CLASS(PClass)); }
virtual ExpEmit Emit(VMFunctionBuilder *build);
@ -1520,6 +1522,24 @@ public:
ExpEmit Emit(VMFunctionBuilder *build);
};
//==========================================================================
//
// FxFlopFunctionCall
//
//==========================================================================
class FxGetClass : public FxExpression
{
FxExpression *Self;
public:
FxGetClass(FxExpression *self);
~FxGetClass();
FxExpression *Resolve(FCompileContext&);
ExpEmit Emit(VMFunctionBuilder *build);
};
//==========================================================================
//
// FxVMFunctionCall

View file

@ -75,6 +75,7 @@
#define ASSERTF(x) assert((unsigned)(x) < f->NumRegF)
#define ASSERTA(x) assert((unsigned)(x) < f->NumRegA)
#define ASSERTS(x) assert((unsigned)(x) < f->NumRegS)
#define ASSERTO(x) assert((unsigned)(x) < f->NumRegA && reg.atag[x] == ATAG_OBJECT)
#define ASSERTKD(x) assert(sfunc != NULL && (unsigned)(x) < sfunc->NumKonstD)
#define ASSERTKF(x) assert(sfunc != NULL && (unsigned)(x) < sfunc->NumKonstF)

View file

@ -110,6 +110,12 @@ begin:
reg.atag[a] = ATAG_GENERIC; // using ATAG_FRAMEPOINTER will cause endless asserts.
NEXTOP;
OP(META):
ASSERTA(a); ASSERTO(B);
reg.a[a] = ((DObject*)reg.a[B])->GetClass(); // I wish this could be done without a special opcode but there's really no good way to guarantee initialization of the Class pointer...
reg.atag[a] = ATAG_OBJECT;
NEXTOP;
OP(LB):
ASSERTD(a); ASSERTA(B); ASSERTKD(C);
GETADDR(PB,KC,X_READ_NIL);

View file

@ -23,6 +23,7 @@ xx(LKF_R, lk, RFRII8, NOP, 0, 0), // load float constant indexed
xx(LKS_R, lk, RSRII8, NOP, 0, 0), // load string constant indexed
xx(LKP_R, lk, RPRII8, NOP, 0, 0), // load pointer constant indexed
xx(LFP, lf, LFP, NOP, 0, 0), // load frame pointer
xx(META, meta, RPRP, NOP, 0, 0), // load a class's meta class address
// Load from memory. rA = *(rB + rkC)
xx(LB, lb, RIRPKI, LB_R, 4, REGT_INT), // load byte

View file

@ -1298,8 +1298,12 @@ bool ZCCCompiler::CompileFields(PStruct *type, TArray<ZCC_VarDeclarator *> &Fiel
if (field->Flags & ZCC_Meta)
{
varflags |= VARF_ReadOnly; // metadata implies readonly
// todo: this needs to go into the metaclass and needs some handling
varflags |= VARF_Static|VARF_ReadOnly; // metadata implies readonly
if (!(field->Flags & ZCC_Native))
{
// Non-native meta data is not implemented yet and requires some groundwork in the class copy code.
Error(field, "Metadata member %s must be native", FName(field->Names->Name).GetChars());
}
}
if (field->Type->ArraySize != nullptr)
@ -1320,7 +1324,8 @@ bool ZCCCompiler::CompileFields(PStruct *type, TArray<ZCC_VarDeclarator *> &Fiel
if (varflags & VARF_Native)
{
fd = FindField(type, FName(name->Name).GetChars());
auto querytype = (varflags & VARF_Static) ? type->GetClass() : type;
fd = FindField(querytype, FName(name->Name).GetChars());
if (fd == nullptr)
{
Error(field, "The member variable '%s.%s' has not been exported from the executable.", type->TypeName.GetChars(), FName(name->Name).GetChars());

View file

@ -31,6 +31,7 @@ zscript/shared/setcolor.txt
zscript/shared/sectoraction.txt
zscript/shared/ice.txt
zscript/shared/dog.txt
zscript/shared/fastprojectile.txt
zscript/doom/doomplayer.txt
zscript/doom/possessed.txt
@ -100,6 +101,7 @@ zscript/heretic/weaponwand.txt
zscript/heretic/weaponcrossbow.txt
zscript/heretic/weapongauntlets.txt
zscript/heretic/weaponmace.txt
zscript/heretic/weaponblaster.txt
zscript/hexen/baseweapons.txt
zscript/hexen/korax.txt

View file

@ -145,6 +145,29 @@ class Actor : Thinker native
native readonly State SeeState;
native State MeleeState;
native State MissileState;
native meta String Obituary; // Player was killed by this actor
native meta String HitObituary; // Player was killed by this actor in melee
native meta double DeathHeight; // Height on normal death
native meta double BurnHeight; // Height on burning death
native meta color BloodColor; // Colorized blood
native meta int GibHealth; // Negative health below which this monster dies an extreme death
native meta int WoundHealth; // Health needed to enter wound state
native meta double FastSpeed; // speed in fast mode
native meta double RDFactor; // Radius damage factor
native meta double CameraHeight; // Height of camera when used as such
native meta Sound HowlSound; // Sound being played when electrocuted or poisoned
native meta Name BloodType; // Blood replacement type
native meta Name BloodType2; // Bloopsplatter replacement type
native meta Name BloodType3; // AxeBlood replacement type
native meta bool DontHurtShooter;
native meta int ExplosionRadius;
native meta int ExplosionDamage;
native meta int MeleeDamage;
native meta Sound MeleeSound;
native meta Name MissileName;
native meta double MissileHeight;
// need some definition work first
//FRenderStyle RenderStyle;

View file

@ -7,7 +7,6 @@ class Object native
native static double G_SkillPropertyFloat(int p);
virtual native void Destroy();
native class<Object> GetClass();
}
class Thinker : Object native

View file

@ -7,179 +7,6 @@ class HereticWeapon : Weapon
}
}
// Blaster ------------------------------------------------------------------
class Blaster : HereticWeapon
{
Default
{
+BLOODSPLATTER
Weapon.SelectionOrder 500;
Weapon.AmmoUse 1;
Weapon.AmmoGive 30;
Weapon.YAdjust 15;
Weapon.AmmoType "BlasterAmmo";
Weapon.SisterWeapon "BlasterPowered";
Inventory.PickupMessage "$TXT_WPNBLASTER";
Tag "$TAG_BLASTER";
Obituary "$OB_MPBLASTER";
}
action native void A_FireBlasterPL1();
States
{
Spawn:
WBLS A -1;
Stop;
Ready:
BLSR A 1 A_WeaponReady;
Loop;
Deselect:
BLSR A 1 A_Lower;
Loop;
Select:
BLSR A 1 A_Raise;
Loop;
Fire:
BLSR BC 3;
Hold:
BLSR D 2 A_FireBlasterPL1;
BLSR CB 2;
BLSR A 0 A_ReFire;
Goto Ready;
}
}
class BlasterPowered : Blaster
{
Default
{
+WEAPON.POWERED_UP
Weapon.AmmoUse 5;
Weapon.AmmoGive 0;
Weapon.SisterWeapon "Blaster";
Tag "$TAG_BLASTERP";
}
States
{
Fire:
BLSR BC 0;
Hold:
BLSR D 3 A_FireCustomMissile("BlasterFX1");
BLSR CB 4;
BLSR A 0 A_ReFire;
Goto Ready;
}
}
// Blaster FX 1 -------------------------------------------------------------
class BlasterFX1 : FastProjectile native
{
Default
{
Radius 12;
Height 8;
Speed 184;
Damage 2;
SeeSound "weapons/blastershoot";
DeathSound "weapons/blasterhit";
+SPAWNSOUNDSOURCE
Obituary "$OB_MPPBLASTER";
}
native void A_SpawnRippers();
States
{
Spawn:
ACLO E 200;
Loop;
Death:
FX18 A 3 BRIGHT A_SpawnRippers;
FX18 B 3 BRIGHT;
FX18 CDEFG 4 BRIGHT;
Stop;
}
}
// Blaster smoke ------------------------------------------------------------
class BlasterSmoke : Actor
{
Default
{
+NOBLOCKMAP
+NOGRAVITY
+NOTELEPORT
+CANNOTPUSH
RenderStyle "Translucent";
Alpha 0.4;
}
States
{
Spawn:
FX18 HIJKL 4;
Stop;
}
}
// Ripper -------------------------------------------------------------------
class Ripper : Actor native
{
Default
{
Radius 8;
Height 6;
Speed 14;
Damage 1;
Projectile;
+RIPPER
DeathSound "weapons/blasterpowhit";
Obituary "$OB_MPPBLASTER";
}
States
{
Spawn:
FX18 M 4;
FX18 N 5;
Loop;
Death:
FX18 OPQRS 4 BRIGHT;
Stop;
}
}
// Blaster Puff -------------------------------------------------------------
class BlasterPuff : Actor
{
Default
{
+NOBLOCKMAP
+NOGRAVITY
+PUFFONACTORS
RenderStyle "Add";
SeeSound "weapons/blasterhit";
}
States
{
Crash:
FX17 ABCDE 4 BRIGHT;
Stop;
Spawn:
FX17 FG 3 BRIGHT;
FX17 HIJKL 4 BRIGHT;
Stop;
}
}
// Skull (Horn) Rod ---------------------------------------------------------

View file

@ -0,0 +1,259 @@
// Blaster ------------------------------------------------------------------
class Blaster : HereticWeapon
{
Default
{
+BLOODSPLATTER
Weapon.SelectionOrder 500;
Weapon.AmmoUse 1;
Weapon.AmmoGive 30;
Weapon.YAdjust 15;
Weapon.AmmoType "BlasterAmmo";
Weapon.SisterWeapon "BlasterPowered";
Inventory.PickupMessage "$TXT_WPNBLASTER";
Tag "$TAG_BLASTER";
Obituary "$OB_MPBLASTER";
}
States
{
Spawn:
WBLS A -1;
Stop;
Ready:
BLSR A 1 A_WeaponReady;
Loop;
Deselect:
BLSR A 1 A_Lower;
Loop;
Select:
BLSR A 1 A_Raise;
Loop;
Fire:
BLSR BC 3;
Hold:
BLSR D 2 A_FireBlasterPL1;
BLSR CB 2;
BLSR A 0 A_ReFire;
Goto Ready;
}
//----------------------------------------------------------------------------
//
// PROC A_FireBlasterPL1
//
//----------------------------------------------------------------------------
action void A_FireBlasterPL1()
{
if (player == null)
{
return;
}
Weapon weapon = player.ReadyWeapon;
if (weapon != null)
{
if (!weapon.DepleteAmmo (weapon.bAltFire))
return;
}
double pitch = BulletSlope();
int damage = random[FireBlaster](1, 8) * 4;
double ang = angle;
if (player.refire)
{
ang += Random2[FireBlaster]() * (5.625 / 256);
}
LineAttack (ang, PLAYERMISSILERANGE, pitch, damage, 'Hitscan', "BlasterPuff");
A_PlaySound ("weapons/blastershoot", CHAN_WEAPON);
}
}
class BlasterPowered : Blaster
{
Default
{
+WEAPON.POWERED_UP
Weapon.AmmoUse 5;
Weapon.AmmoGive 0;
Weapon.SisterWeapon "Blaster";
Tag "$TAG_BLASTERP";
}
States
{
Fire:
BLSR BC 0;
Hold:
BLSR D 3 A_FireCustomMissile("BlasterFX1");
BLSR CB 4;
BLSR A 0 A_ReFire;
Goto Ready;
}
}
// Blaster FX 1 -------------------------------------------------------------
class BlasterFX1 : FastProjectile
{
Default
{
Radius 12;
Height 8;
Speed 184;
Damage 2;
SeeSound "weapons/blastershoot";
DeathSound "weapons/blasterhit";
+SPAWNSOUNDSOURCE
Obituary "$OB_MPPBLASTER";
}
States
{
Spawn:
ACLO E 200;
Loop;
Death:
FX18 A 3 BRIGHT A_SpawnRippers;
FX18 B 3 BRIGHT;
FX18 CDEFG 4 BRIGHT;
Stop;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
override int DoSpecialDamage (Actor target, int damage, Name damagetype)
{
if (target is "Ironlich")
{ // Less damage to Ironlich bosses
damage = random[BlasterFX]() & 1;
if (!damage)
{
return -1;
}
}
return damage;
}
override void Effect ()
{
if (random[BlasterFX]() < 64)
{
Spawn("BlasterSmoke", (pos.xy, max(pos.z - 8, floorz)), ALLOW_REPLACE);
}
}
//----------------------------------------------------------------------------
//
// PROC A_SpawnRippers
//
//----------------------------------------------------------------------------
void A_SpawnRippers()
{
for(int i = 0; i < 8; i++)
{
Actor ripper = Spawn("Ripper", pos, ALLOW_REPLACE);
ripper.target = target;
ripper.angle = i*45;
ripper.VelFromAngle();
ripper.CheckMissileSpawn (radius);
}
}
}
// Blaster smoke ------------------------------------------------------------
class BlasterSmoke : Actor
{
Default
{
+NOBLOCKMAP
+NOGRAVITY
+NOTELEPORT
+CANNOTPUSH
RenderStyle "Translucent";
Alpha 0.4;
}
States
{
Spawn:
FX18 HIJKL 4;
Stop;
}
}
// Ripper -------------------------------------------------------------------
class Ripper : Actor
{
Default
{
Radius 8;
Height 6;
Speed 14;
Damage 1;
Projectile;
+RIPPER
DeathSound "weapons/blasterpowhit";
Obituary "$OB_MPPBLASTER";
}
States
{
Spawn:
FX18 M 4;
FX18 N 5;
Loop;
Death:
FX18 OPQRS 4 BRIGHT;
Stop;
}
int DoSpecialDamage (Actor target, int damage, Name damagetype)
{
if (target is "Ironlich")
{ // Less damage to Ironlich bosses
damage = random[Ripper]() & 1;
if (!damage)
{
return -1;
}
}
return damage;
}
}
// Blaster Puff -------------------------------------------------------------
class BlasterPuff : Actor
{
Default
{
+NOBLOCKMAP
+NOGRAVITY
+PUFFONACTORS
RenderStyle "Add";
SeeSound "weapons/blasterhit";
}
States
{
Crash:
FX17 ABCDE 4 BRIGHT;
Stop;
Spawn:
FX17 FG 3 BRIGHT;
FX17 HIJKL 4 BRIGHT;
Stop;
}
}

View file

@ -170,7 +170,7 @@ class CircleFlame : Actor
// Flame Missile ------------------------------------------------------------
class CFlameMissile : FastProjectile native
class CFlameMissile : FastProjectile
{
Default
{
@ -208,4 +208,28 @@ class CFlameMissile : FastProjectile native
CFFX M 3 Bright;
Stop;
}
override void BeginPlay ()
{
special1 = 2;
}
override void Effect ()
{
if (!--special1)
{
special1 = 4;
double newz = pos.z - 12;
if (newz < floorz)
{
newz = floorz;
}
Actor mo = Spawn ("CFlameFloor", (pos.xy, newz), ALLOW_REPLACE);
if (mo)
{
mo.angle = angle;
}
}
}
}

View file

@ -0,0 +1,41 @@
// Fast projectiles --------------------------------------------------------
class FastProjectile : Actor native
{
Default
{
Projectile;
MissileHeight 0;
}
virtual void Effect()
{
class<Actor> trail = MissileName;
if (trail != null)
{
double hitz = pos.z - 8;
if (hitz < floorz)
{
hitz = floorz;
}
// Do not clip this offset to the floor.
hitz += MissileHeight;
Actor act = Spawn (trail, (pos.xy, hitz), ALLOW_REPLACE);
if (act != null)
{
if (bGetOwner && target != null)
act.target = target;
else
act.target = self;
act.angle = angle;
act.pitch = pitch;
}
}
}
}

View file

@ -179,17 +179,6 @@ class RandomSpawner : Actor native
}
}
// Fast projectiles --------------------------------------------------------
class FastProjectile : Actor native
{
Default
{
Projectile;
MissileHeight 0;
}
}
// Sector flag setter ------------------------------------------------------
class SectorFlagSetter : Actor native