- 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_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_SPRITEANGLE = 0x02000000, // [MC] Utilize the SpriteAngle property and lock the rotation to the degrees specified.
MF7_SMASHABLE = 0x04000000, // dies if hitting the floor. MF7_SMASHABLE = 0x04000000, // dies if hitting the floor.
MF7_NOSHIELDREFLECT = 0x08000000, // will not be reflected by shields.
}; };
// --- mobj.renderflags --- // --- mobj.renderflags ---

View file

@ -24,332 +24,8 @@ static FRandom pr_wraithvergedrop ("WraithvergeDrop");
void SpawnSpiritTail (AActor *spirit); 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 -------------------------------------------------------------- // 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 // CHolyFindTarget
@ -495,22 +171,3 @@ DEFINE_ACTION_FUNCTION(AActor, A_CHolyCheckScream)
return 0; 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); 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 class AFighterWeapon : public AWeapon
{ {
DECLARE_CLASS (AFighterWeapon, 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[0] = 10; // initial turn value
spirit->args[1] = 0; // initial look angle spirit->args[1] = 0; // initial look angle
#if 0 // Temporarily deactivated.
// Spawn a tail for spirit // Spawn a tail for spirit
SpawnSpiritTail (spirit); HolyTail.SpawnSpiritTail (spirit);
#endif
} }
//============================================================================ //============================================================================

View file

@ -1324,6 +1324,27 @@ PalEntry AInventory::GetBlend ()
return 0; 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 // AInventory :: PrevItem

View file

@ -215,6 +215,7 @@ public:
virtual int AlterWeaponSprite (visstyle_t *vis); virtual int AlterWeaponSprite (visstyle_t *vis);
virtual PalEntry GetBlend (); virtual PalEntry GetBlend ();
PalEntry CallGetBlend();
protected: protected:
virtual bool TryPickup (AActor *&toucher); 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) bool AActor::Slam (AActor *thing)
{ {
flags &= ~MF_SKULLFLY; flags &= ~MF_SKULLFLY;
@ -3367,8 +3374,7 @@ bool AActor::AdjustReflectionAngle (AActor *thing, DAngle &angle)
if (absangle(angle, thing->Angles.Yaw) > 45) if (absangle(angle, thing->Angles.Yaw) > 45)
return true; // Let missile explode return true; // Let missile explode
if (thing->IsKindOf (RUNTIME_CLASS(AHolySpirit))) // shouldn't this be handled by another flag??? if (thing->flags7 & MF7_NOSHIELDREFLECT) return true;
return true;
if (pr_reflect () < 128) if (pr_reflect () < 128)
angle += 45; angle += 45;
@ -7349,6 +7355,16 @@ DEFINE_ACTION_FUNCTION(AActor, SetXYZ)
return 0; 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) DEFINE_ACTION_FUNCTION(AActor, Vec3Angle)
{ {
PARAM_SELF_PROLOGUE(AActor); PARAM_SELF_PROLOGUE(AActor);

View file

@ -6969,13 +6969,19 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
switch (MethodName) 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_Bool:
case NAME_Int: case NAME_Int:
case NAME_uInt: case NAME_uInt:
case NAME_Float: case NAME_Float:
case NAME_Double: case NAME_Double:
case NAME_Name: case NAME_Name:
case NAME_Color:
case NAME_Sound: case NAME_Sound:
case NAME_State: case NAME_State:
case NAME_SpriteID: case NAME_SpriteID:
@ -8090,6 +8096,76 @@ ExpEmit FxGetClass::Emit(VMFunctionBuilder *build)
return to; 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 // FxSequence :: Resolve

View file

@ -287,6 +287,7 @@ enum EFxType
EFX_CVar, EFX_CVar,
EFX_NamedNode, EFX_NamedNode,
EFX_GetClass, EFX_GetClass,
EFX_ColorLiteral,
EFX_COUNT EFX_COUNT
}; };
@ -1540,6 +1541,24 @@ public:
ExpEmit Emit(VMFunctionBuilder *build); 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 // FxVMFunctionCall

View file

@ -296,6 +296,7 @@ static FFlagDef ActorFlagDefs[]=
DEFINE_FLAG(MF7, NOKILLSCRIPTS, AActor, flags7), DEFINE_FLAG(MF7, NOKILLSCRIPTS, AActor, flags7),
DEFINE_FLAG(MF7, SPRITEANGLE, AActor, flags7), DEFINE_FLAG(MF7, SPRITEANGLE, AActor, flags7),
DEFINE_FLAG(MF7, SMASHABLE, AActor, flags7), DEFINE_FLAG(MF7, SMASHABLE, AActor, flags7),
DEFINE_FLAG(MF7, NOSHIELDREFLECT, AActor, flags7),
// Effect flags // Effect flags
DEFINE_FLAG(FX, VISIBILITYPULSE, AActor, effects), 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 /* Type names can also be used as identifiers in contexts where type names
* are not normally allowed. */ * are not normally allowed. */
%fallback IDENTIFIER %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 */ /* Aggregate types */
%type aggregate_type {ZCC_Type *} %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 // [RH] All powerups can affect the screen blending now
for (AInventory *item = CPlayer->mo->Inventory; item != NULL; item = item->Inventory) for (AInventory *item = CPlayer->mo->Inventory; item != NULL; item = item->Inventory)
{ {
PalEntry color = item->GetBlend (); PalEntry color = item->CallGetBlend ();
if (color.a != 0) if (color.a != 0)
{ {
V_AddBlend (color.r/255.f, color.g/255.f, color.b/255.f, color.a/255.f, blend); 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 void SetZ(double z);
native vector3 Vec3Offset(double x, double y, double z, bool absolute = false); 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 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 vector3 Vec2OffsetZ(double x, double y, double atz, bool absolute = false);
native void VelFromAngle(double speed = 0, double angle = 0); native void VelFromAngle(double speed = 0, double angle = 0);
native void Thrust(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 double DistanceBySpeed(Actor other, double speed);
native name GetSpecies(); native name GetSpecies();
native void PlayActiveSound(); native void PlayActiveSound();
native void Howl();
// DECORATE compatible functions // DECORATE compatible functions
native bool CheckClass(class<Actor> checkclass, int ptr_select = AAPTR_DEFAULT, bool match_superclass = false); 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_DoomChex = GAME_Doom|GAME_Chex,
GAME_DoomStrifeChex = GAME_Doom|GAME_Strife|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"; Obituary "$OBCBOSS";
} }
native void A_ClericAttack();
States States
{ {
Spawn: Spawn:
@ -79,4 +77,19 @@ class ClericBoss : Actor
FDTH V 4 Bright ; FDTH V 4 Bright ;
Stop; 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?) -------------------------------------- // Cleric's Wraithverge (Holy Symbol?) --------------------------------------
class CWeapWraithverge : ClericWeapon native class CWeapWraithverge : ClericWeapon
{ {
int CHolyCount;
Default Default
{ {
Health 3; Health 3;
@ -96,8 +98,6 @@ class CWeapWraithverge : ClericWeapon native
Inventory.PickupSound "WeaponBuild"; Inventory.PickupSound "WeaponBuild";
} }
action native void A_CHolyAttack();
action native void A_CHolyPalette();
States States
{ {
@ -122,6 +122,66 @@ class CWeapWraithverge : ClericWeapon native
CHLY G 2 Offset (0, 36) A_CHolyPalette; CHLY G 2 Offset (0, 36) A_CHolyPalette;
Goto Ready; 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 ------------------------------------------------------------- // Holy Missile -------------------------------------------------------------
@ -139,8 +199,6 @@ class HolyMissile : Actor
+EXTREMEDEATH +EXTREMEDEATH
} }
native void A_CHolyAttack2();
States States
{ {
Spawn: Spawn:
@ -149,6 +207,60 @@ class HolyMissile : Actor
SPIR P 1 Bright A_CHolyAttack2; SPIR P 1 Bright A_CHolyAttack2;
Stop; 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 -------------------------------------------------------- // Holy Missile Puff --------------------------------------------------------
@ -192,7 +304,7 @@ class HolyPuff : Actor
// Holy Spirit -------------------------------------------------------------- // Holy Spirit --------------------------------------------------------------
class HolySpirit : Actor native class HolySpirit : Actor
{ {
Default Default
{ {
@ -204,7 +316,7 @@ class HolySpirit : Actor native
Projectile; Projectile;
+RIPPER +SEEKERMISSILE +RIPPER +SEEKERMISSILE
+FOILINVUL +SKYEXPLODE +NOEXPLODEFLOOR +CANBLAST +FOILINVUL +SKYEXPLODE +NOEXPLODEFLOOR +CANBLAST
+EXTREMEDEATH +EXTREMEDEATH +NOSHIELDREFLECT
RenderStyle "Translucent"; RenderStyle "Translucent";
Alpha 0.4; Alpha 0.4;
DeathSound "SpiritDie"; DeathSound "SpiritDie";
@ -226,6 +338,70 @@ class HolySpirit : Actor native
SPIR FGHI 4; SPIR FGHI 4;
Stop; 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 ---------------------------------------------------------------- // Holy Tail ----------------------------------------------------------------
@ -242,8 +418,6 @@ class HolyTail : Actor
Alpha 0.6; Alpha 0.6;
} }
native void A_CHolyTail();
States States
{ {
Spawn: Spawn:
@ -253,6 +427,108 @@ class HolyTail : Actor
SPIR D -1; SPIR D -1;
Stop; 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 --------------------------------------------------------- // Holy Tail Trail ---------------------------------------------------------

View file

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