diff --git a/src/actor.h b/src/actor.h index f6020d292..92e277d48 100644 --- a/src/actor.h +++ b/src/actor.h @@ -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 --- diff --git a/src/g_hexen/a_clericholy.cpp b/src/g_hexen/a_clericholy.cpp index 52ff4179d..8ffb5be27 100644 --- a/src/g_hexen/a_clericholy.cpp +++ b/src/g_hexen/a_clericholy.cpp @@ -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 (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 (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 (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; -} - diff --git a/src/g_hexen/a_hexenglobal.h b/src/g_hexen/a_hexenglobal.h index 3455b684d..dc174b32a 100644 --- a/src/g_hexen/a_hexenglobal.h +++ b/src/g_hexen/a_hexenglobal.h @@ -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); diff --git a/src/g_hexen/a_korax.cpp b/src/g_hexen/a_korax.cpp index 2824d8c0a..8a459ffd4 100644 --- a/src/g_hexen/a_korax.cpp +++ b/src/g_hexen/a_korax.cpp @@ -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 + } //============================================================================ diff --git a/src/g_shared/a_pickups.cpp b/src/g_shared/a_pickups.cpp index ee68a16db..b026a61d7 100644 --- a/src/g_shared/a_pickups.cpp +++ b/src/g_shared/a_pickups.cpp @@ -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 diff --git a/src/g_shared/a_pickups.h b/src/g_shared/a_pickups.h index f035efeea..bd252437d 100644 --- a/src/g_shared/a_pickups.h +++ b/src/g_shared/a_pickups.h @@ -215,6 +215,7 @@ public: virtual int AlterWeaponSprite (visstyle_t *vis); virtual PalEntry GetBlend (); + PalEntry CallGetBlend(); protected: virtual bool TryPickup (AActor *&toucher); diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index e9c6a2980..7e9b8a96b 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -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); diff --git a/src/scripting/codegeneration/codegen.cpp b/src/scripting/codegeneration/codegen.cpp index 8a5eddd2d..4562f3ef7 100644 --- a/src/scripting/codegeneration/codegen.cpp +++ b/src/scripting/codegeneration/codegen.cpp @@ -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(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 diff --git a/src/scripting/codegeneration/codegen.h b/src/scripting/codegeneration/codegen.h index d7547a198..1110cd6b6 100644 --- a/src/scripting/codegeneration/codegen.h +++ b/src/scripting/codegeneration/codegen.h @@ -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 diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index a6c9ff9f1..7a3d3a7c6 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -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), diff --git a/src/scripting/zscript/zcc-parse.lemon b/src/scripting/zscript/zcc-parse.lemon index 379d1516e..d22746cf8 100644 --- a/src/scripting/zscript/zcc-parse.lemon +++ b/src/scripting/zscript/zcc-parse.lemon @@ -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 *} diff --git a/src/v_blend.cpp b/src/v_blend.cpp index c13ed547c..ffc16cd5c 100644 --- a/src/v_blend.cpp +++ b/src/v_blend.cpp @@ -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); diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt index 62d88237f..ccf3fb374 100644 --- a/wadsrc/static/zscript/actor.txt +++ b/wadsrc/static/zscript/actor.txt @@ -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 checkclass, int ptr_select = AAPTR_DEFAULT, bool match_superclass = false); diff --git a/wadsrc/static/zscript/constants.txt b/wadsrc/static/zscript/constants.txt index eadf30bc7..5f86282a8 100644 --- a/wadsrc/static/zscript/constants.txt +++ b/wadsrc/static/zscript/constants.txt @@ -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, +}; diff --git a/wadsrc/static/zscript/hexen/clericboss.txt b/wadsrc/static/zscript/hexen/clericboss.txt index a2019bae5..4bf223517 100644 --- a/wadsrc/static/zscript/hexen/clericboss.txt +++ b/wadsrc/static/zscript/hexen/clericboss.txt @@ -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); + } } diff --git a/wadsrc/static/zscript/hexen/clericholy.txt b/wadsrc/static/zscript/hexen/clericholy.txt index 1a4372f4d..593462d00 100644 --- a/wadsrc/static/zscript/hexen/clericholy.txt +++ b/wadsrc/static/zscript/hexen/clericholy.txt @@ -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 --------------------------------------------------------- diff --git a/wadsrc/static/zscript/shared/inventory.txt b/wadsrc/static/zscript/shared/inventory.txt index c2c99453f..f87331470 100644 --- a/wadsrc/static/zscript/shared/inventory.txt +++ b/wadsrc/static/zscript/shared/inventory.txt @@ -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.