- converted half of ClericHoly. (Making a commit before starting on the more complex stuff.)

- added a 'constructor' for color values.
This commit is contained in:
Christoph Oelckers 2016-11-26 13:18:48 +01:00
parent bc1e4eff72
commit 177aa6ec42
17 changed files with 455 additions and 368 deletions

View file

@ -386,6 +386,7 @@ enum ActorFlag7
MF7_NOKILLSCRIPTS = 0x01000000, // [JM] No "KILL" Script on death whatsoever, even if forced by GameInfo.
MF7_SPRITEANGLE = 0x02000000, // [MC] Utilize the SpriteAngle property and lock the rotation to the degrees specified.
MF7_SMASHABLE = 0x04000000, // dies if hitting the floor.
MF7_NOSHIELDREFLECT = 0x08000000, // will not be reflected by shields.
};
// --- mobj.renderflags ---

View file

@ -24,332 +24,8 @@ static FRandom pr_wraithvergedrop ("WraithvergeDrop");
void SpawnSpiritTail (AActor *spirit);
//==========================================================================
// Cleric's Wraithverge (Holy Symbol?) --------------------------------------
class ACWeapWraithverge : public AClericWeapon
{
DECLARE_CLASS (ACWeapWraithverge, AClericWeapon)
public:
void Serialize(FSerializer &arc)
{
Super::Serialize (arc);
arc("cholycount", CHolyCount);
}
PalEntry GetBlend ()
{
if (paletteflash & PF_HEXENWEAPONS)
{
if (CHolyCount == 3)
return PalEntry(128, 70, 70, 70);
else if (CHolyCount == 2)
return PalEntry(128, 100, 100, 100);
else if (CHolyCount == 1)
return PalEntry(128, 130, 130, 130);
else
return PalEntry(0, 0, 0, 0);
}
else
{
return PalEntry (CHolyCount * 128 / 3, 131, 131, 131);
}
}
BYTE CHolyCount;
};
IMPLEMENT_CLASS(ACWeapWraithverge, false, false)
// Holy Spirit --------------------------------------------------------------
IMPLEMENT_CLASS(AHolySpirit, false, false)
bool AHolySpirit::Slam(AActor *thing)
{
if (thing->flags&MF_SHOOTABLE && thing != target)
{
if (multiplayer && !deathmatch && thing->player && target->player)
{ // don't attack other co-op players
return true;
}
if (thing->flags2&MF2_REFLECTIVE
&& (thing->player || thing->flags2&MF2_BOSS))
{
tracer = target;
target = thing;
return true;
}
if (thing->flags3&MF3_ISMONSTER || thing->player)
{
tracer = thing;
}
if (pr_spiritslam() < 96)
{
int dam = 12;
if (thing->player || thing->flags2&MF2_BOSS)
{
dam = 3;
// ghost burns out faster when attacking players/bosses
health -= 6;
}
P_DamageMobj(thing, this, target, dam, NAME_Melee);
if (pr_spiritslam() < 128)
{
Spawn("HolyPuff", Pos(), ALLOW_REPLACE);
S_Sound(this, CHAN_WEAPON, "SpiritAttack", 1, ATTN_NORM);
if (thing->flags3&MF3_ISMONSTER && pr_spiritslam() < 128)
{
thing->Howl();
}
}
}
if (thing->health <= 0)
{
tracer = NULL;
}
}
return true;
}
bool AHolySpirit::SpecialBlastHandling (AActor *source, double strength)
{
if (tracer == source)
{
tracer = target;
target = source;
GC::WriteBarrier(this, source);
}
return true;
}
//============================================================================
//
// A_CHolyAttack2
//
// Spawns the spirits
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_CHolyAttack2)
{
PARAM_SELF_PROLOGUE(AActor);
int j;
AActor *mo;
for (j = 0; j < 4; j++)
{
mo = Spawn<AHolySpirit> (self->Pos(), ALLOW_REPLACE);
if (!mo)
{
continue;
}
switch (j)
{ // float bob index
case 0:
mo->WeaveIndexZ = pr_holyatk2() & 7; // upper-left
break;
case 1:
mo->WeaveIndexZ = 32 + (pr_holyatk2() & 7); // upper-right
break;
case 2:
mo->WeaveIndexXY = 32 + (pr_holyatk2() & 7); // lower-left
break;
case 3:
mo->WeaveIndexXY = 32 + (pr_holyatk2() & 7);
mo->WeaveIndexZ = 32 + (pr_holyatk2() & 7);
break;
}
mo->SetZ(self->Z());
mo->Angles.Yaw = self->Angles.Yaw + 67.5 - 45.*j;
mo->Thrust();
mo->target = self->target;
mo->args[0] = 10; // initial turn value
mo->args[1] = 0; // initial look angle
if (deathmatch)
{ // Ghosts last slightly less longer in DeathMatch
mo->health = 85;
}
if (self->tracer)
{
mo->tracer = self->tracer;
mo->flags |= MF_NOCLIP|MF_SKULLFLY;
mo->flags &= ~MF_MISSILE;
}
SpawnSpiritTail (mo);
}
return 0;
}
//============================================================================
//
// SpawnSpiritTail
//
//============================================================================
void SpawnSpiritTail (AActor *spirit)
{
AActor *tail, *next;
int i;
tail = Spawn ("HolyTail", spirit->Pos(), ALLOW_REPLACE);
tail->target = spirit; // parent
for (i = 1; i < 3; i++)
{
next = Spawn ("HolyTailTrail", spirit->Pos(), ALLOW_REPLACE);
tail->tracer = next;
tail = next;
}
tail->tracer = NULL; // last tail bit
}
//============================================================================
//
// A_CHolyAttack
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_CHolyAttack)
{
PARAM_ACTION_PROLOGUE(AActor);
player_t *player;
FTranslatedLineTarget t;
if (NULL == (player = self->player))
{
return 0;
}
ACWeapWraithverge *weapon = static_cast<ACWeapWraithverge *> (self->player->ReadyWeapon);
if (weapon != NULL)
{
if (!weapon->DepleteAmmo (weapon->bAltFire))
return 0;
}
AActor *missile = P_SpawnPlayerMissile (self, 0,0,0, PClass::FindActor("HolyMissile"), self->Angles.Yaw, &t);
if (missile != NULL && !t.unlinked)
{
missile->tracer = t.linetarget;
}
weapon->CHolyCount = 3;
S_Sound (self, CHAN_WEAPON, "HolySymbolFire", 1, ATTN_NORM);
return 0;
}
//============================================================================
//
// A_CHolyPalette
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_CHolyPalette)
{
PARAM_ACTION_PROLOGUE(AActor);
if (self->player != NULL)
{
ACWeapWraithverge *weapon = static_cast<ACWeapWraithverge *> (self->player->ReadyWeapon);
if (weapon != NULL && weapon->CHolyCount != 0)
{
weapon->CHolyCount--;
}
}
return 0;
}
//============================================================================
//
// CHolyTailFollow
//
//============================================================================
static void CHolyTailFollow(AActor *actor, double dist)
{
AActor *child;
DAngle an;
double oldDistance, newDistance;
while (actor)
{
child = actor->tracer;
if (child)
{
an = actor->AngleTo(child);
oldDistance = child->Distance2D(actor);
if (P_TryMove(child, actor->Pos().XY() + an.ToVector(dist), true))
{
newDistance = child->Distance2D(actor) - 1;
if (oldDistance < 1)
{
if (child->Z() < actor->Z())
{
child->SetZ(actor->Z() - dist);
}
else
{
child->SetZ(actor->Z() + dist);
}
}
else
{
child->SetZ(actor->Z() + (newDistance * (child->Z() - actor->Z()) / oldDistance));
}
}
}
actor = child;
dist -= 1;
}
}
//============================================================================
//
// CHolyTailRemove
//
//============================================================================
static void CHolyTailRemove (AActor *actor)
{
AActor *next;
while (actor)
{
next = actor->tracer;
actor->Destroy ();
actor = next;
}
}
//============================================================================
//
// A_CHolyTail
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_CHolyTail)
{
PARAM_SELF_PROLOGUE(AActor);
AActor *parent;
parent = self->target;
if (parent == NULL || parent->health <= 0) // better check for health than current state - it's safer!
{ // Ghost removed, so remove all tail parts
CHolyTailRemove (self);
return 0;
}
else
{
if (P_TryMove(self, parent->Vec2Angle(14., parent->Angles.Yaw, true), true))
{
self->SetZ(parent->Z() - 5.);
}
CHolyTailFollow(self, 10);
}
return 0;
}
//============================================================================
//
// CHolyFindTarget
@ -495,22 +171,3 @@ DEFINE_ACTION_FUNCTION(AActor, A_CHolyCheckScream)
return 0;
}
//============================================================================
//
// A_ClericAttack
// (for the ClericBoss)
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_ClericAttack)
{
PARAM_SELF_PROLOGUE(AActor);
if (!self->target) return 0;
AActor * missile = P_SpawnMissileZ (self, self->Z() + 40., self->target, PClass::FindActor ("HolyMissile"));
if (missile != NULL) missile->tracer = NULL; // No initial target
S_Sound (self, CHAN_WEAPON, "HolySymbolFire", 1, ATTN_NORM);
return 0;
}

View file

@ -5,14 +5,6 @@
void AdjustPlayerAngle(AActor *pmo, FTranslatedLineTarget *t);
class AHolySpirit : public AActor
{
DECLARE_CLASS (AHolySpirit, AActor)
public:
bool Slam (AActor *thing);
bool SpecialBlastHandling (AActor *source, double strength);
};
class AFighterWeapon : public AWeapon
{
DECLARE_CLASS (AFighterWeapon, AWeapon);

View file

@ -189,8 +189,11 @@ void KSpiritInit (AActor *spirit, AActor *korax)
spirit->args[0] = 10; // initial turn value
spirit->args[1] = 0; // initial look angle
#if 0 // Temporarily deactivated.
// Spawn a tail for spirit
SpawnSpiritTail (spirit);
HolyTail.SpawnSpiritTail (spirit);
#endif
}
//============================================================================

View file

@ -1324,6 +1324,27 @@ PalEntry AInventory::GetBlend ()
return 0;
}
DEFINE_ACTION_FUNCTION(AInventory, GetBlend)
{
PARAM_SELF_PROLOGUE(AInventory);
ACTION_RETURN_INT(self->GetBlend());
}
PalEntry AInventory::CallGetBlend()
{
IFVIRTUAL(AInventory, GetBlend)
{
VMValue params[1] = { (DObject*)this };
VMReturn ret;
VMFrameStack stack;
int retval;
ret.IntAt(&retval);
stack.Call(func, params, 1, &ret, 1, nullptr);
return retval;
}
else return GetBlend();
}
//===========================================================================
//
// AInventory :: PrevItem

View file

@ -215,6 +215,7 @@ public:
virtual int AlterWeaponSprite (visstyle_t *vis);
virtual PalEntry GetBlend ();
PalEntry CallGetBlend();
protected:
virtual bool TryPickup (AActor *&toucher);

View file

@ -3284,6 +3284,13 @@ void AActor::Howl ()
}
}
DEFINE_ACTION_FUNCTION(AActor, Howl)
{
PARAM_SELF_PROLOGUE(AActor);
self->Howl();
return 0;
}
bool AActor::Slam (AActor *thing)
{
flags &= ~MF_SKULLFLY;
@ -3367,8 +3374,7 @@ bool AActor::AdjustReflectionAngle (AActor *thing, DAngle &angle)
if (absangle(angle, thing->Angles.Yaw) > 45)
return true; // Let missile explode
if (thing->IsKindOf (RUNTIME_CLASS(AHolySpirit))) // shouldn't this be handled by another flag???
return true;
if (thing->flags7 & MF7_NOSHIELDREFLECT) return true;
if (pr_reflect () < 128)
angle += 45;
@ -7349,6 +7355,16 @@ DEFINE_ACTION_FUNCTION(AActor, SetXYZ)
return 0;
}
DEFINE_ACTION_FUNCTION(AActor, Vec2Angle)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_FLOAT(length);
PARAM_ANGLE(angle);
PARAM_BOOL_DEF(absolute);
ACTION_RETURN_VEC2(self->Vec2Angle(length, angle, absolute));
}
DEFINE_ACTION_FUNCTION(AActor, Vec3Angle)
{
PARAM_SELF_PROLOGUE(AActor);

View file

@ -6969,13 +6969,19 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
switch (MethodName)
{
case NAME_Color:
if (ArgList.Size() == 3 || ArgList.Size() == 4)
{
func = new FxColorLiteral(ArgList, ScriptPosition);
break;
}
// fall through
case NAME_Bool:
case NAME_Int:
case NAME_uInt:
case NAME_Float:
case NAME_Double:
case NAME_Name:
case NAME_Color:
case NAME_Sound:
case NAME_State:
case NAME_SpriteID:
@ -8090,6 +8096,76 @@ ExpEmit FxGetClass::Emit(VMFunctionBuilder *build)
return to;
}
//==========================================================================
//
//
//==========================================================================
FxColorLiteral::FxColorLiteral(FArgumentList &args, FScriptPosition &sc)
:FxExpression(EFX_ColorLiteral, sc)
{
ArgList = std::move(args);
}
FxExpression *FxColorLiteral::Resolve(FCompileContext &ctx)
{
CHECKRESOLVED();
unsigned constelements = 0;
assert(ArgList.Size() == 3 || ArgList.Size() == 4);
if (ArgList.Size() == 3) ArgList.Insert(0, nullptr);
for (int i = 0; i < 4; i++)
{
if (ArgList[i] != nullptr)
{
SAFE_RESOLVE(ArgList[i], ctx);
if (!ArgList[i]->IsInteger())
{
ScriptPosition.Message(MSG_ERROR, "Integer expected for color component");
delete this;
return nullptr;
}
if (ArgList[i]->isConstant())
{
constval += clamp(static_cast<FxConstant *>(ArgList[i])->GetValue().GetInt(), 0, 255) << (24 - i * 8);
delete ArgList[i];
ArgList[i] = nullptr;
constelements++;
}
}
else constelements++;
}
if (constelements == 4)
{
auto x = new FxConstant(constval, ScriptPosition);
x->ValueType = TypeColor;
delete this;
return x;
}
ValueType = TypeColor;
return this;
}
ExpEmit FxColorLiteral::Emit(VMFunctionBuilder *build)
{
ExpEmit out(build, REGT_INT);
build->Emit(OP_LK, out.RegNum, build->GetConstantInt(constval));
for (int i = 0; i < 4; i++)
{
if (ArgList[i] != nullptr)
{
assert(!ArgList[i]->isConstant());
ExpEmit in = ArgList[i]->Emit(build);
in.Free(build);
ExpEmit work(build, REGT_INT);
build->Emit(OP_MAX_RK, work.RegNum, in.RegNum, build->GetConstantInt(0));
build->Emit(OP_MIN_RK, work.RegNum, work.RegNum, build->GetConstantInt(255));
if (i != 3) build->Emit(OP_SLL_RI, work.RegNum, work.RegNum, 24 - (i * 8));
build->Emit(OP_OR_RR, out.RegNum, out.RegNum, work.RegNum);
}
}
return out;
}
//==========================================================================
//
// FxSequence :: Resolve

View file

@ -287,6 +287,7 @@ enum EFxType
EFX_CVar,
EFX_NamedNode,
EFX_GetClass,
EFX_ColorLiteral,
EFX_COUNT
};
@ -1540,6 +1541,24 @@ public:
ExpEmit Emit(VMFunctionBuilder *build);
};
//==========================================================================
//
// FxColorLiteral
//
//==========================================================================
class FxColorLiteral : public FxExpression
{
FArgumentList ArgList;
int constval = 0;
public:
FxColorLiteral(FArgumentList &args, FScriptPosition &sc);
FxExpression *Resolve(FCompileContext&);
ExpEmit Emit(VMFunctionBuilder *build);
};
//==========================================================================
//
// FxVMFunctionCall

View file

@ -296,6 +296,7 @@ static FFlagDef ActorFlagDefs[]=
DEFINE_FLAG(MF7, NOKILLSCRIPTS, AActor, flags7),
DEFINE_FLAG(MF7, SPRITEANGLE, AActor, flags7),
DEFINE_FLAG(MF7, SMASHABLE, AActor, flags7),
DEFINE_FLAG(MF7, NOSHIELDREFLECT, AActor, flags7),
// Effect flags
DEFINE_FLAG(FX, VISIBILITYPULSE, AActor, effects),

View file

@ -740,7 +740,7 @@ type_name(X) ::= DOT dottable_id(A).
/* Type names can also be used as identifiers in contexts where type names
* are not normally allowed. */
%fallback IDENTIFIER
SBYTE BYTE SHORT USHORT INT UINT BOOL FLOAT DOUBLE STRING VECTOR2 VECTOR3 NAME MAP ARRAY VOID STATE.
SBYTE BYTE SHORT USHORT INT UINT BOOL FLOAT DOUBLE STRING VECTOR2 VECTOR3 NAME MAP ARRAY VOID STATE COLOR UINT8 INT8 UINT16 INT16.
/* Aggregate types */
%type aggregate_type {ZCC_Type *}

View file

@ -104,7 +104,7 @@ void V_AddPlayerBlend (player_t *CPlayer, float blend[4], float maxinvalpha, int
// [RH] All powerups can affect the screen blending now
for (AInventory *item = CPlayer->mo->Inventory; item != NULL; item = item->Inventory)
{
PalEntry color = item->GetBlend ();
PalEntry color = item->CallGetBlend ();
if (color.a != 0)
{
V_AddBlend (color.r/255.f, color.g/255.f, color.b/255.f, color.a/255.f, blend);

View file

@ -339,6 +339,7 @@ class Actor : Thinker native
native void SetZ(double z);
native vector3 Vec3Offset(double x, double y, double z, bool absolute = false);
native vector3 Vec3Angle(double length, double angle, double z = 0, bool absolute = false);
native vector2 Vec2Angle(double length, double angle, bool absolute = false);
native vector3 Vec2OffsetZ(double x, double y, double atz, bool absolute = false);
native void VelFromAngle(double speed = 0, double angle = 0);
native void Thrust(double speed = 0, double angle = 0);
@ -351,6 +352,7 @@ class Actor : Thinker native
native double DistanceBySpeed(Actor other, double speed);
native name GetSpecies();
native void PlayActiveSound();
native void Howl();
// DECORATE compatible functions
native bool CheckClass(class<Actor> checkclass, int ptr_select = AAPTR_DEFAULT, bool match_superclass = false);

View file

@ -1007,3 +1007,11 @@ enum EGameType
GAME_DoomChex = GAME_Doom|GAME_Chex,
GAME_DoomStrifeChex = GAME_Doom|GAME_Strife|GAME_Chex
}
enum PaletteFlashFlags
{
PF_HEXENWEAPONS = 1,
PF_POISON = 2,
PF_ICE = 4,
PF_HAZARD = 8,
};

View file

@ -18,8 +18,6 @@ class ClericBoss : Actor
Obituary "$OBCBOSS";
}
native void A_ClericAttack();
States
{
Spawn:
@ -79,4 +77,19 @@ class ClericBoss : Actor
FDTH V 4 Bright ;
Stop;
}
//============================================================================
//
// A_ClericAttack
//
//============================================================================
void A_ClericAttack()
{
if (!target) return;
Actor missile = SpawnMissileZ (pos.z + 40., target, "HolyMissile");
if (missile != null) missile.tracer = null; // No initial target
A_PlaySound ("HolySymbolFire", CHAN_WEAPON);
}
}

View file

@ -76,8 +76,10 @@ class WraithvergeDrop : Actor
// Cleric's Wraithverge (Holy Symbol?) --------------------------------------
class CWeapWraithverge : ClericWeapon native
class CWeapWraithverge : ClericWeapon
{
int CHolyCount;
Default
{
Health 3;
@ -96,8 +98,6 @@ class CWeapWraithverge : ClericWeapon native
Inventory.PickupSound "WeaponBuild";
}
action native void A_CHolyAttack();
action native void A_CHolyPalette();
States
{
@ -122,6 +122,66 @@ class CWeapWraithverge : ClericWeapon native
CHLY G 2 Offset (0, 36) A_CHolyPalette;
Goto Ready;
}
override color GetBlend ()
{
if (paletteflash & PF_HEXENWEAPONS)
{
if (CHolyCount == 3)
return Color(128, 70, 70, 70);
else if (CHolyCount == 2)
return Color(128, 100, 100, 100);
else if (CHolyCount == 1)
return Color(128, 130, 130, 130);
else
return Color(0, 0, 0, 0);
}
else
{
return Color(CHolyCount * 128 / 3, 131, 131, 131);
}
}
//============================================================================
//
// A_CHolyAttack
//
//============================================================================
action void A_CHolyAttack()
{
FTranslatedLineTarget t;
if (player == null)
{
return;
}
Weapon weapon = player.ReadyWeapon;
if (weapon != null)
{
if (!weapon.DepleteAmmo (weapon.bAltFire))
return;
}
Actor missile = SpawnPlayerMissile ("HolyMissile", angle, pLineTarget:t);
if (missile != null && !t.unlinked)
{
missile.tracer = t.linetarget;
}
invoker.CHolyCount = 3;
A_PlaySound ("HolySymbolFire", CHAN_WEAPON);
}
//============================================================================
//
// A_CHolyPalette
//
//============================================================================
action void A_CHolyPalette()
{
if (invoker.CHolyCount > 0) invoker.CHolyCount--;
}
}
// Holy Missile -------------------------------------------------------------
@ -139,8 +199,6 @@ class HolyMissile : Actor
+EXTREMEDEATH
}
native void A_CHolyAttack2();
States
{
Spawn:
@ -149,6 +207,60 @@ class HolyMissile : Actor
SPIR P 1 Bright A_CHolyAttack2;
Stop;
}
//============================================================================
//
// A_CHolyAttack2
//
// Spawns the spirits
//============================================================================
void A_CHolyAttack2()
{
for (int j = 0; j < 4; j++)
{
Actor mo = Spawn("HolySpirit", Pos, ALLOW_REPLACE);
if (!mo)
{
continue;
}
switch (j)
{ // float bob index
case 0:
mo.WeaveIndexZ = random[HolyAtk2]() & 7; // upper-left
break;
case 1:
mo.WeaveIndexZ = 32 + (random[HolyAtk2]() & 7); // upper-right
break;
case 2:
mo.WeaveIndexXY = 32 + (random[HolyAtk2]() & 7); // lower-left
break;
case 3:
mo.WeaveIndexXY = 32 + (random[HolyAtk2]() & 7);
mo.WeaveIndexZ = 32 + (random[HolyAtk2]() & 7);
break;
}
mo.SetZ(pos.z);
mo.angle = angle + 67.5 - 45.*j;
mo.Thrust();
mo.target = target;
mo.args[0] = 10; // initial turn value
mo.args[1] = 0; // initial look angle
if (deathmatch)
{ // Ghosts last slightly less longer in DeathMatch
mo.health = 85;
}
if (tracer)
{
mo.tracer = tracer;
mo.bNoClip = true;
mo.bSkullFly = true;
mo.bMissile = false;
}
HolyTail.SpawnSpiritTail (mo);
}
}
}
// Holy Missile Puff --------------------------------------------------------
@ -192,7 +304,7 @@ class HolyPuff : Actor
// Holy Spirit --------------------------------------------------------------
class HolySpirit : Actor native
class HolySpirit : Actor
{
Default
{
@ -204,7 +316,7 @@ class HolySpirit : Actor native
Projectile;
+RIPPER +SEEKERMISSILE
+FOILINVUL +SKYEXPLODE +NOEXPLODEFLOOR +CANBLAST
+EXTREMEDEATH
+EXTREMEDEATH +NOSHIELDREFLECT
RenderStyle "Translucent";
Alpha 0.4;
DeathSound "SpiritDie";
@ -226,6 +338,70 @@ class HolySpirit : Actor native
SPIR FGHI 4;
Stop;
}
//============================================================================
//
//
//
//============================================================================
override bool Slam(Actor thing)
{
if (thing.bShootable && thing != target)
{
if (multiplayer && !deathmatch && thing.player && target.player)
{ // don't attack other co-op players
return true;
}
if (thing.bReflective && (thing.player || thing.bBoss))
{
tracer = target;
target = thing;
return true;
}
if (thing.bIsMonster || thing.player)
{
tracer = thing;
}
if (random[SpiritSlam]() < 96)
{
int dam = 12;
if (thing.player || thing.bBoss)
{
dam = 3;
// ghost burns out faster when attacking players/bosses
health -= 6;
}
thing.DamageMobj(self, target, dam, 'Melee');
if (random[SpiritSlam]() < 128)
{
Spawn("HolyPuff", Pos, ALLOW_REPLACE);
A_PlaySound("SpiritAttack", CHAN_WEAPON);
if (thing.bIsMonster && random[SpiritSlam]() < 128)
{
thing.Howl();
}
}
}
if (thing.health <= 0)
{
tracer = null;
}
}
return true;
}
override bool SpecialBlastHandling (Actor source, double strength)
{
if (tracer == source)
{
tracer = target;
target = source;
}
return true;
}
}
// Holy Tail ----------------------------------------------------------------
@ -242,8 +418,6 @@ class HolyTail : Actor
Alpha 0.6;
}
native void A_CHolyTail();
States
{
Spawn:
@ -253,6 +427,108 @@ class HolyTail : Actor
SPIR D -1;
Stop;
}
//============================================================================
//
// SpawnSpiritTail
//
//============================================================================
static void SpawnSpiritTail (Actor spirit)
{
Actor tail = Spawn ("HolyTail", spirit.Pos, ALLOW_REPLACE);
tail.target = spirit; // parent
for (int i = 1; i < 3; i++)
{
Actor next = Spawn ("HolyTailTrail", spirit.Pos, ALLOW_REPLACE);
tail.tracer = next;
tail = next;
}
tail.tracer = null; // last tail bit
}
//============================================================================
//
// CHolyTailFollow
//
//============================================================================
private void CHolyTailFollow(double dist)
{
Actor mo = self;
while (mo)
{
Actor child = mo.tracer;
if (child)
{
double an = mo.AngleTo(child);
double oldDistance = child.Distance2D(mo);
if (child.TryMove(mo.Pos.XY + AngleToVector(an, dist), true))
{
double newDistance = child.Distance2D(mo) - 1;
if (oldDistance < 1)
{
if (child.pos.z < mo.pos.z)
{
child.SetZ(mo.pos.z - dist);
}
else
{
child.SetZ(mo.pos.z + dist);
}
}
else
{
child.SetZ(mo.pos.z + (newDistance * (child.pos.z - mo.pos.z) / oldDistance));
}
}
}
mo = child;
dist -= 1;
}
}
//============================================================================
//
// CHolyTailRemove
//
//============================================================================
private void CHolyTailRemove ()
{
Actor mo = self;
while (mo)
{
Actor next = mo.tracer;
mo.Destroy ();
mo = next;
}
}
//============================================================================
//
// A_CHolyTail
//
//============================================================================
void A_CHolyTail()
{
Actor parent = self.target;
if (parent == null || parent.health <= 0) // better check for health than current state - it's safer!
{ // Ghost removed, so remove all tail parts
CHolyTailRemove ();
return;
}
else
{
if (TryMove(parent.Vec2Angle(14., parent.Angle, true), true))
{
self.SetZ(parent.pos.z - 5.);
}
CHolyTailFollow(10);
}
}
}
// Holy Tail Trail ---------------------------------------------------------

View file

@ -26,6 +26,7 @@ class Inventory : Actor native
}
virtual native bool Use (bool pickup);
virtual native color GetBlend ();
// These are regular functions for the item itself.