diff --git a/src/common/engine/namedef.h b/src/common/engine/namedef.h index dff0b91ac..489507061 100644 --- a/src/common/engine/namedef.h +++ b/src/common/engine/namedef.h @@ -1111,3 +1111,4 @@ xx(zoomsize) xx(ScreenJobRunner) xx(RazeStatusBar) xx(RipSound) +xx(Archvile) diff --git a/src/gamedata/d_dehacked.cpp b/src/gamedata/d_dehacked.cpp index 375e650ef..7efb82fea 100644 --- a/src/gamedata/d_dehacked.cpp +++ b/src/gamedata/d_dehacked.cpp @@ -66,6 +66,7 @@ #include "types.h" #include "m_argv.h" #include "actorptrselect.h" +#include "g_levellocals.h" void JitDumpLog(FILE *file, VMScriptFunction *func); @@ -147,14 +148,15 @@ struct MBFParamState { FState *state; int pointer; + int args[8]; + int argsused; }; static TArray MBFParamStates; // Data on how to correctly modify the codepointers struct CodePointerAlias { FName name; - char alias[20]; - uint8_t params; + FString alias; }; static TArray MBFCodePointers; @@ -630,7 +632,7 @@ static int GetLine (void) // misc1 = vrange (arg +3), misc2 = hrange (arg+4) -static void CreateMushroomFunc(FunctionCallEmitter &emitters, int value1, int value2) +static void CreateMushroomFunc(FunctionCallEmitter &emitters, int value1, int value2, int* args, int argsused) { // A_Mushroom emitters.AddParameterPointerConst(PClass::FindClass("FatShot")); // itemtype emitters.AddParameterIntConst(0); // numspawns @@ -640,7 +642,7 @@ static void CreateMushroomFunc(FunctionCallEmitter &emitters, int value1, int va } // misc1 = type (arg +0), misc2 = Z-pos (arg +2) -static void CreateSpawnFunc(FunctionCallEmitter &emitters, int value1, int value2) +static void CreateSpawnFunc(FunctionCallEmitter &emitters, int value1, int value2, int* args, int argsused) { // A_SpawnItem if (InfoNames[value1-1] == nullptr) { @@ -655,13 +657,13 @@ static void CreateSpawnFunc(FunctionCallEmitter &emitters, int value1, int value // misc1 = angle (in degrees) (arg +0 but factor in current actor angle too) -static void CreateTurnFunc(FunctionCallEmitter &emitters, int value1, int value2) +static void CreateTurnFunc(FunctionCallEmitter &emitters, int value1, int value2, int* args, int argsused) { // A_Turn emitters.AddParameterFloatConst(value1); // angle } // misc1 = angle (in degrees) (arg +0) -static void CreateFaceFunc(FunctionCallEmitter &emitters, int value1, int value2) +static void CreateFaceFunc(FunctionCallEmitter &emitters, int value1, int value2, int* args, int argsused) { // A_SetAngle emitters.AddParameterFloatConst(value1); // angle emitters.AddParameterIntConst(0); // flags @@ -669,7 +671,7 @@ static void CreateFaceFunc(FunctionCallEmitter &emitters, int value1, int value2 } // misc1 = damage, misc 2 = sound -static void CreateScratchFunc(FunctionCallEmitter &emitters, int value1, int value2) +static void CreateScratchFunc(FunctionCallEmitter &emitters, int value1, int value2, int* args, int argsused) { // A_CustomMeleeAttack emitters.AddParameterIntConst(value1); // damage emitters.AddParameterIntConst(value2 ? (int)SoundMap[value2 - 1] : 0); // hit sound @@ -679,7 +681,7 @@ static void CreateScratchFunc(FunctionCallEmitter &emitters, int value1, int val } // misc1 = sound, misc2 = attenuation none (true) or normal (false) -static void CreatePlaySoundFunc(FunctionCallEmitter &emitters, int value1, int value2) +static void CreatePlaySoundFunc(FunctionCallEmitter &emitters, int value1, int value2, int* args, int argsused) { // A_PlaySound emitters.AddParameterIntConst(value1 ? (int)SoundMap[value1 - 1] : 0); // soundid emitters.AddParameterIntConst(CHAN_BODY); // channel @@ -691,7 +693,7 @@ static void CreatePlaySoundFunc(FunctionCallEmitter &emitters, int value1, int v } // misc1 = state, misc2 = probability -static void CreateRandomJumpFunc(FunctionCallEmitter &emitters, int value1, int value2) +static void CreateRandomJumpFunc(FunctionCallEmitter &emitters, int value1, int value2, int* args, int argsused) { // A_Jump auto symlabel = StateLabels.AddPointer(FindState(value1)); @@ -701,7 +703,7 @@ static void CreateRandomJumpFunc(FunctionCallEmitter &emitters, int value1, int } // misc1 = Boom linedef type, misc2 = sector tag -static void CreateLineEffectFunc(FunctionCallEmitter &emitters, int value1, int value2) +static void CreateLineEffectFunc(FunctionCallEmitter &emitters, int value1, int value2, int* args, int argsused) { // A_LineEffect // This is the second MBF codepointer that couldn't be translated easily. // Calling P_TranslateLineDef() here was a simple matter, as was adding an @@ -713,7 +715,7 @@ static void CreateLineEffectFunc(FunctionCallEmitter &emitters, int value1, int } // No misc, but it's basically A_Explode with an added effect -static void CreateNailBombFunc(FunctionCallEmitter &emitters, int value1, int value2) +static void CreateNailBombFunc(FunctionCallEmitter &emitters, int value1, int value2, int* args, int argsused) { // A_Explode // This one does not actually have MBF-style parameters. But since // we're aliasing it to an extension of A_Explode... @@ -728,8 +730,17 @@ static void CreateNailBombFunc(FunctionCallEmitter &emitters, int value1, int va emitters.AddParameterIntConst(NAME_None); // damage type } +static PClassActor* GetDehType(int num) +{ + PClassActor* type = nullptr; + if (num >= 0 && num < int(InfoNames.Size())) type = InfoNames[num]; + if (type == nullptr) type = RUNTIME_CLASS(AActor); + +} + + // This array must be in sync with the Aliases array in DEHSUPP. -static void (*MBFCodePointerFactories[])(FunctionCallEmitter&, int, int) = +static void (*MBFCodePointerFactories[])(FunctionCallEmitter&, int, int, int*, int) = { // Die and Detonate are not in this list because these codepointers have // no dehacked arguments and therefore do not need special handling. @@ -742,12 +753,12 @@ static void (*MBFCodePointerFactories[])(FunctionCallEmitter&, int, int) = CreatePlaySoundFunc, CreateRandomJumpFunc, CreateLineEffectFunc, - CreateNailBombFunc + CreateNailBombFunc, }; // Creates new functions for the given state so as to convert MBF-args (misc1 and misc2) into real args. -static void SetDehParams(FState *state, int codepointer, VMDisassemblyDumper &disasmdump) +static void SetDehParams(FState *state, int codepointer, VMDisassemblyDumper &disasmdump, int* args, int argsused) { static const uint8_t regts[] = { REGT_POINTER, REGT_POINTER, REGT_POINTER }; int value1 = state->GetMisc1(); @@ -786,7 +797,7 @@ static void SetDehParams(FState *state, int codepointer, VMDisassemblyDumper &di emitters.AddParameterPointer(i, false); } // Emit code for action parameters. - MBFCodePointerFactories[codepointer](emitters, value1, value2); + MBFCodePointerFactories[codepointer](emitters, value1, value2, args, argsused); auto where = emitters.EmitCall(&buildit); if (!returnsState) buildit.Emit(OP_RET, RET_FINAL, REGT_NIL, 0); else buildit.Emit(OP_RET, RET_FINAL, EncodeRegType(where), where.RegNum); @@ -1039,7 +1050,7 @@ static int PatchThing (int thingy) } else if (linelen == 11 && stricmp(Line1, "melee range") == 0) { - info->meleerange = DEHToDouble(val); + info->meleerange = DEHToDouble(val) - 20; // -20 is needed because DSDA subtracts it in P_CheckMeleeRange, while GZDoom does not. } else if (linelen == 10 && stricmp(Line1, "MBF21 Bits") == 0) { @@ -1502,6 +1513,8 @@ DehBits sbits[] = { static int PatchFrame (int frameNum) { + int args[8]{}; + int argsused = 0; int result; int tics, misc1, frame; FState *info, dummy; @@ -1583,6 +1596,19 @@ static int PatchFrame (int frameNum) { frame = val; } + else if (keylen == 5 && strnicmp(Line1, "Args", 4) == 0) + { + int arg = Line1[4] - '1'; + if (arg < 0 || arg >= 8) + { + Printf("Invalid frame arg %d\n", arg); + } + else + { + args[arg] = val; + argsused |= (1 << arg); + } + } else if (stricmp(Line1, "MBF21 Bits") == 0) { uint32_t value = 0; @@ -1962,12 +1988,11 @@ static int PatchWeapon (int weapNum) return result; } -static void SetPointer(FState *state, PFunction *sym, int frame = 0) +static int SetPointer(FState *state, PFunction *sym, int frame = 0) { if (sym == NULL) { state->ClearAction(); - return; } else { @@ -1977,14 +2002,12 @@ static void SetPointer(FState *state, PFunction *sym, int frame = 0) { if (sym->SymbolName == MBFCodePointers[i].name) { - MBFParamState newstate; - newstate.state = state; - newstate.pointer = i; - MBFParamStates.Push(newstate); - break; // No need to cycle through the rest of the list. + MBFParamState newstate = { state, int(i) }; + return MBFParamStates.Push(newstate); } } } + return -1; } static int PatchPointer (int ptrNum) @@ -2885,7 +2908,7 @@ static void UnloadDehSupp () // Handle MBF params here, before the required arrays are cleared for (unsigned int i=0; i < MBFParamStates.Size(); i++) { - SetDehParams(MBFParamStates[i].state, MBFParamStates[i].pointer, disasmdump); + SetDehParams(MBFParamStates[i].state, MBFParamStates[i].pointer, disasmdump, MBFParamStates[i].args, MBFParamStates[i].argsused); } MBFParamStates.Clear(); MBFParamStates.ShrinkToFit(); @@ -3202,14 +3225,10 @@ static bool LoadDehSupp () { CodePointerAlias temp; sc.MustGetString(); - strncpy(temp.alias, sc.String, 19); - temp.alias[19]=0; + temp.alias = sc.String; sc.MustGetStringName(","); sc.MustGetString(); temp.name = sc.String; - sc.MustGetStringName(","); - sc.MustGetNumber(); - temp.params = sc.Number; MBFCodePointers.Push(temp); if (sc.CheckString("}")) break; sc.MustGetStringName(","); @@ -3401,3 +3420,241 @@ DEFINE_ACTION_FUNCTION(ADehackedPickup, DetermineType) } ACTION_RETURN_POINTER(nullptr); } + +// Handling the flags is not as trivial as it seems... + +struct FlagHandler +{ + void (*setter)(AActor*); + void (*clearer)(AActor*); + bool (*checker)(AActor*); +}; + +// cut down on boilerplate +#define F(flag) { [](AActor* a) { a->flags |= flag; }, [](AActor* a) { a->flags &= ~flag; }, [](AActor* a)->bool { return a->flags & flag; } } +#define F2(flag) { [](AActor* a) { a->flags2 |= flag; }, [](AActor* a) { a->flags2 &= ~flag; }, [](AActor* a)->bool { return a->flags2 & flag; } } +#define F3(flag) { [](AActor* a) { a->flags3 |= flag; }, [](AActor* a) { a->flags3 &= ~flag; }, [](AActor* a)->bool { return a->flags3 & flag; } } +#define F4(flag) { [](AActor* a) { a->flags4 |= flag; }, [](AActor* a) { a->flags4 &= ~flag; }, [](AActor* a)->bool { return a->flags4 & flag; } } +#define F8(flag) { [](AActor* a) { a->flags8 |= flag; }, [](AActor* a) { a->flags8 &= ~flag; }, [](AActor* a)->bool { return a->flags8 & flag; } } +#define DEPF(flag) { [](AActor* a) { HandleDeprecatedFlags(a, nullptr, true, flag); }, [](AActor* a) { HandleDeprecatedFlags(a, nullptr, false, flag); }, [](AActor* a)->bool { return CheckDeprecatedFlags(a, nullptr, flag); } } + +void SetNoSector(AActor* a) +{ + a->UnlinkFromWorld(nullptr); + a->flags |= MF_NOSECTOR; + a->LinkToWorld(nullptr); +} + +void ClearNoSector(AActor* a) +{ + a->UnlinkFromWorld(nullptr); + a->flags &= ~MF_NOSECTOR; + a->LinkToWorld(nullptr); +} + +void SetNoBlockmap(AActor* a) +{ + a->UnlinkFromWorld(nullptr); + a->flags |= MF_NOBLOCKMAP; + a->LinkToWorld(nullptr); +} + +void ClearNoBlockmap(AActor* a) +{ + a->UnlinkFromWorld(nullptr); + a->flags &= ~MF_NOBLOCKMAP; + a->LinkToWorld(nullptr); +} + +void SetCountkill(AActor* a) +{ + if (a->CountsAsKill() && a->health > 0) a->Level->total_monsters--; + a->flags |= MF_COUNTKILL; + if (a->CountsAsKill() && a->health > 0) a->Level->total_monsters++; +} + +void ClearCountkill(AActor* a) +{ + if (a->CountsAsKill() && a->health > 0) a->Level->total_monsters--; + a->flags &= ~MF_COUNTKILL; + if (a->CountsAsKill() && a->health > 0) a->Level->total_monsters++; +} + +void SetCountitem(AActor* a) +{ + if (!(a->flags & MF_COUNTITEM)) + { + a->flags |= MF_COUNTITEM; + a->Level->total_items++; + } +} + +void ClearCountitem(AActor* a) +{ + if (a->flags & MF_COUNTITEM) + { + a->flags |= MF_COUNTITEM; + a->Level->total_items--; + } +} + +void SetFriendly(AActor* a) +{ + if (a->CountsAsKill() && a->health > 0) a->Level->total_monsters--; + a->flags |= MF_FRIENDLY; + if (a->CountsAsKill() && a->health > 0) a->Level->total_monsters++; +} + +void ClearFriendly(AActor* a) +{ + if (a->CountsAsKill() && a->health > 0) a->Level->total_monsters--; + a->flags &= ~MF_FRIENDLY; + if (a->CountsAsKill() && a->health > 0) a->Level->total_monsters++; +} + +void SetFullVol(AActor* a) +{ + a->flags8 |= MF8_FULLVOLSEE; + a->flags3 |= MF3_FULLVOLDEATH; +} + +void ClearFullVol(AActor* a) +{ + a->flags8 &= ~MF8_FULLVOLSEE; + a->flags3 &= ~MF3_FULLVOLDEATH; +} + + +static FlagHandler flag1handlers[32] = { + F(MF_SPECIAL), + F(MF_SOLID), + F(MF_SHOOTABLE), + { SetNoSector, ClearNoSector, [](AActor* a)->bool { return a->flags & MF_NOSECTOR; } }, + { SetNoBlockmap, ClearNoBlockmap, [](AActor* a)->bool { return a->flags & MF_NOBLOCKMAP; } }, + F(MF_AMBUSH), + F(MF_JUSTHIT), + F(MF_JUSTATTACKED), + F(MF_SPAWNCEILING), + F(MF_NOGRAVITY), + F(MF_DROPOFF), + F(MF_PICKUP), + F(MF_NOCLIP), + { nullptr, nullptr, nullptr}, // MF_SLIDE no longer exists. + F(MF_FLOAT), + F(MF_TELEPORT), + F(MF_MISSILE), + F(MF_DROPPED), + F(MF_SHADOW), + F(MF_NOBLOOD), + F(MF_CORPSE), + F(MF_INFLOAT), + { SetCountkill, ClearCountkill, [](AActor* a)->bool { return a->flags & MF_COUNTKILL; } }, + { SetCountitem, ClearCountitem, [](AActor* a)->bool { return a->flags & MF_COUNTITEM; } }, + F(MF_SKULLFLY), + F(MF_NOTDMATCH), + // translation, unused and translucent are no longer checkable in any way. Pity. +}; + +static FlagHandler flag2handlers[32] = { + DEPF(DEPF_LOWGRAVITY), + DEPF(DEPF_SHORTMISSILERANGE), + F3(MF3_NOTARGET), + F3(MF3_NORADIUSDMG), + F4(MF4_FORCERADIUSDMG), + DEPF(DEPF_HIGHERMPROB), + F4(MF4_MISSILEMORE), + F4(MF4_QUICKTORETALIATE), + DEPF(DEPF_LONGMELEERANGE), + F2(MF2_BOSS), + F8(MF8_MAP07BOSS1), + F8(MF8_MAP07BOSS2), + F8(MF8_E1M8BOSS), + F8(MF8_E2M8BOSS), + F8(MF8_E3M8BOSS), + F8(MF8_E4M6BOSS), + F8(MF8_E4M8BOSS), + F2(MF2_RIP), + { SetFullVol, ClearFullVol, [](AActor* a)->bool { return a->flags8 & MF8_FULLVOLSEE; } }, // checking one of the two flags should suffice here. +}; + + +// +// A_JumpIfFlagsSet +// Jumps to a state if caller has the specified thing flags set. +// args[0]: State to jump to +// args[1]: Standard Flag(s) to check +// args[2]: MBF21 Flag(s) to check +// +DEFINE_ACTION_FUNCTION(AActor, MBF21_JumpIfFlagsSet) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_POINTER(tstate, FState); + PARAM_INT(flags); + PARAM_INT(flags2); + + for (int i = 0; i < 32; i++) + { + if (flags & (1 << i) && flag1handlers[i].checker) + { + if (!flag1handlers[i].checker(self)) return 0; + } + if (flags2 & (1 << i) && flag2handlers[i].checker) + { + if (!flag2handlers[i].checker(self)) return 0; + } + } + self->SetState(tstate); + return 0; +} + +// +// A_AddFlags +// Adds the specified thing flags to the caller. +// args[0]: Standard Flag(s) to add +// args[1]: MBF21 Flag(s) to add +// +DEFINE_ACTION_FUNCTION(AActor, MBF21_AddFlags) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_INT(flags); + PARAM_INT(flags2); + + for (int i = 0; i < 32; i++) + { + if (flags & (1 << i) && flag1handlers[i].setter) + { + flag1handlers[i].setter(self); + } + if (flags2 & (1 << i) && flag2handlers[i].setter) + { + flag2handlers[i].setter(self); + } + } + return 0; +} + +// +// A_RemoveFlags +// Removes the specified thing flags from the caller. +// args[0]: Flag(s) to remove +// args[1]: MBF21 Flag(s) to remove +// +DEFINE_ACTION_FUNCTION(AActor, MBF21_RemoveFlags) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_INT(flags); + PARAM_INT(flags2); + + for (int i = 0; i < 32; i++) + { + if (flags & (1 << i) && flag1handlers[i].clearer) + { + flag1handlers[i].clearer(self); + } + if (flags2 & (1 << i) && flag2handlers[i].clearer) + { + flag2handlers[i].clearer(self); + } + } + return 0; +} diff --git a/src/playsim/actor.h b/src/playsim/actor.h index ed1f3dc2a..8b7b64344 100644 --- a/src/playsim/actor.h +++ b/src/playsim/actor.h @@ -697,7 +697,7 @@ public: void AlterWeaponSprite(visstyle_t *vis); // Returns true if this actor is within melee range of its target - bool CheckMeleeRange(); + bool CheckMeleeRange(double range = -1); bool CheckNoDelay(); diff --git a/src/playsim/p_enemy.cpp b/src/playsim/p_enemy.cpp index 9a7ef9657..6881ae789 100644 --- a/src/playsim/p_enemy.cpp +++ b/src/playsim/p_enemy.cpp @@ -260,7 +260,7 @@ void P_NoiseAlert (AActor *emitter, AActor *target, bool splash, double maxdist) // //---------------------------------------------------------------------------- -bool AActor::CheckMeleeRange () +bool AActor::CheckMeleeRange (double range) { AActor *pl = target; @@ -270,8 +270,9 @@ bool AActor::CheckMeleeRange () return false; dist = Distance2D (pl); + if (range < 0) range = meleerange; - if (dist >= meleerange + pl->radius) + if (dist >= range + pl->radius) return false; // [RH] If moving toward goal, then we've reached it. @@ -300,7 +301,8 @@ bool AActor::CheckMeleeRange () DEFINE_ACTION_FUNCTION(AActor, CheckMeleeRange) { PARAM_SELF_PROLOGUE(AActor); - ACTION_RETURN_INT(self->CheckMeleeRange()); + PARAM_FLOAT(range); + ACTION_RETURN_INT(self->CheckMeleeRange(range)); } //============================================================================= @@ -2616,7 +2618,7 @@ bool P_CanResurrect(AActor *raiser, AActor *thing) // //========================================================================== -bool P_CheckForResurrection(AActor *self, bool usevilestates) +bool P_CheckForResurrection(AActor* self, bool usevilestates, FState* state = nullptr, FSoundID sound = 0) { const AActor *info; AActor *temp; @@ -2702,8 +2704,8 @@ bool P_CheckForResurrection(AActor *self, bool usevilestates) self->target = temp; // Make the state the monster enters customizable. - FState * state = self->FindState(NAME_Heal); - if (state != NULL) + if (state == nullptr) state = self->FindState(NAME_Heal); + if (state != nullptr) { self->SetState(state); } @@ -2711,13 +2713,14 @@ bool P_CheckForResurrection(AActor *self, bool usevilestates) { // For Dehacked compatibility this has to use the Arch Vile's // heal state as a default if the actor doesn't define one itself. - PClassActor *archvile = PClass::FindActor("Archvile"); + PClassActor *archvile = PClass::FindActor(NAME_Archvile); if (archvile != NULL) { self->SetState(archvile->FindState(NAME_Heal)); } } - S_Sound(corpsehit, CHAN_BODY, 0, "vile/raise", 1, ATTN_IDLE); + if (sound == 0) sound = "vile/raise"; + S_Sound(corpsehit, CHAN_BODY, 0, sound, 1, ATTN_IDLE); info = corpsehit->GetDefault(); if (GetTranslationType(corpsehit->Translation) == TRANSLATION_Blood) diff --git a/src/playsim/p_enemy.h b/src/playsim/p_enemy.h index 6826637e8..c1b425f09 100644 --- a/src/playsim/p_enemy.h +++ b/src/playsim/p_enemy.h @@ -72,7 +72,7 @@ void A_DoChase(AActor *actor, bool fastchase, FState *meleestate, FState *missil void A_Chase(AActor *self); void A_FaceTarget(AActor *actor); void A_Face(AActor *self, AActor *other, DAngle max_turn = 0., DAngle max_pitch = 270., DAngle ang_offset = 0., DAngle pitch_offset = 0., int flags = 0, double z_add = 0); -bool P_CheckForResurrection(AActor *self, bool usevilestates); +class FSoundID; int CheckBossDeath (AActor *); int P_Massacre (bool baddies = false, PClassActor *cls = nullptr); diff --git a/src/playsim/p_local.h b/src/playsim/p_local.h index 79f530202..8b0012d68 100644 --- a/src/playsim/p_local.h +++ b/src/playsim/p_local.h @@ -228,8 +228,9 @@ enum PCM }; +int P_CheckFov(AActor* t1, AActor* t2, double fov); AActor *P_BlockmapSearch (AActor *mo, int distance, AActor *(*check)(AActor*, int, void *), void *params = NULL); -AActor *P_RoughMonsterSearch (AActor *mo, int distance, bool onlyseekable=false, bool frontonly = false); +AActor *P_RoughMonsterSearch (AActor *mo, int distance, bool onlyseekable=false, bool frontonly = false, double fov = 0); // // P_MAP diff --git a/src/playsim/p_maputl.cpp b/src/playsim/p_maputl.cpp index 1dec3bf6b..34fde5045 100644 --- a/src/playsim/p_maputl.cpp +++ b/src/playsim/p_maputl.cpp @@ -1679,6 +1679,23 @@ FPathTraverse::~FPathTraverse() } +// +// P_CheckFov +// Returns true if t2 is within t1's field of view. +// +int P_CheckFov(AActor* t1, AActor* t2, double fov) +{ + return absangle(t1->AngleTo(t2), t1->Angles.Yaw) <= fov; +} + +DEFINE_ACTION_FUNCTION(AActor, CheckFov, P_CheckFov) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_POINTER(t, AActor); + PARAM_FLOAT(fov); + ACTION_RETURN_BOOL(P_CheckFov(self, t, fov)); +} + //=========================================================================== // // P_RoughMonsterSearch @@ -1784,6 +1801,7 @@ struct BlockCheckInfo bool onlyseekable; bool frontonly; divline_t frontline; + double fov; }; //=========================================================================== @@ -1810,6 +1828,12 @@ static AActor *RoughBlockCheck (AActor *mo, int index, void *param) { continue; } + // skip actors outside of specified FOV + if (info->fov > 0 && !P_CheckFov(mo, link->Me, info->fov)) + { + continue; + } + if (mo->IsOkayToAttack (link->Me)) { return link->Me; @@ -1819,7 +1843,7 @@ static AActor *RoughBlockCheck (AActor *mo, int index, void *param) return NULL; } -AActor *P_RoughMonsterSearch(AActor *mo, int distance, bool onlyseekable, bool frontonly) +AActor *P_RoughMonsterSearch(AActor *mo, int distance, bool onlyseekable, bool frontonly, double fov) { BlockCheckInfo info; info.onlyseekable = onlyseekable; diff --git a/src/playsim/p_mobj.cpp b/src/playsim/p_mobj.cpp index b01abc2ff..4c772f323 100644 --- a/src/playsim/p_mobj.cpp +++ b/src/playsim/p_mobj.cpp @@ -3306,7 +3306,7 @@ bool AActor::IsOkayToAttack (AActor *link) // its target; and for a summoned minion, its tracer. AActor * Friend; if (flags5 & MF5_SUMMONEDMONSTER) Friend = tracer; - else if (flags2 & MF2_SEEKERMISSILE) Friend = target; + else if (flags & MF_MISSILE) Friend = target; else if ((flags & MF_FRIENDLY) && FriendPlayer) Friend = Level->Players[FriendPlayer-1]->mo; else Friend = this; diff --git a/src/scripting/thingdef.h b/src/scripting/thingdef.h index 345b4513d..8d0c04461 100644 --- a/src/scripting/thingdef.h +++ b/src/scripting/thingdef.h @@ -228,6 +228,7 @@ enum DEPF_HEXENBOUNCE = 10, DEPF_DOOMBOUNCE = 11, DEPF_INTERHUBSTRIP = 12, + DEPF_HIGHERMPROB = 13, }; // Types of old style decorations diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index 802782766..28ed925c9 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -397,6 +397,7 @@ static FFlagDef MoreFlagDefs[] = DEFINE_DEPRECATED_FLAG(HERETICBOUNCE), DEFINE_DEPRECATED_FLAG(HEXENBOUNCE), DEFINE_DEPRECATED_FLAG(DOOMBOUNCE), + DEFINE_DEPRECATED_FLAG(HIGHERMPROB), // Deprecated flags with no more existing functionality. DEFINE_DUMMY_FLAG(FASTER, true), // obsolete, replaced by 'Fast' state flag diff --git a/src/scripting/thingdef_properties.cpp b/src/scripting/thingdef_properties.cpp index 64d44e669..0b5e306ec 100644 --- a/src/scripting/thingdef_properties.cpp +++ b/src/scripting/thingdef_properties.cpp @@ -325,6 +325,10 @@ void HandleDeprecatedFlags(AActor *defaults, PClassActor *info, bool set, int in defaults->IntVar(NAME_InterHubAmount) = set ? 0 : 1; break; + case DEPF_HIGHERMPROB: + defaults->MinMissileChance = set ? 160 : 200; + break; + default: break; // silence GCC } @@ -384,6 +388,9 @@ bool CheckDeprecatedFlags(AActor *actor, PClassActor *info, int index) case DEPF_INTERHUBSTRIP: return !(actor->IntVar(NAME_InterHubAmount)); + + case DEPF_HIGHERMPROB: + return actor->MinMissileChance <= 160; } return false; // Any entirely unknown flag is not set diff --git a/src/scripting/vmthunks_actors.cpp b/src/scripting/vmthunks_actors.cpp index 656e5bd9f..378496b0c 100644 --- a/src/scripting/vmthunks_actors.cpp +++ b/src/scripting/vmthunks_actors.cpp @@ -59,6 +59,7 @@ DVector2 AM_GetPosition(); int Net_GetLatency(int *ld, int *ad); void PrintPickupMessage(bool localview, const FString &str); +bool P_CheckForResurrection(AActor* self, bool usevilestates, FState* state = nullptr, FSoundID sound = 0); // FCheckPosition requires explicit construction and destruction when used in the VM @@ -328,6 +329,18 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetDamage, SetDamage) return 0; } +static double PitchFromVel(AActor* self) +{ + return self->Vel.Pitch().Degrees; +} + +DEFINE_ACTION_FUNCTION_NATIVE(AActor, PitchFromVel, PitchFromVel) +{ + PARAM_SELF_PROLOGUE(AActor); + ACTION_RETURN_FLOAT(PitchFromVel(self)); +} + + // This combines all 3 variations of the internal function static void VelFromAngle(AActor *self, double speed, double angle) { @@ -1373,7 +1386,8 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, RoughMonsterSearch, P_RoughMonsterSearch) PARAM_INT(distance); PARAM_BOOL(onlyseekable); PARAM_BOOL(frontonly); - ACTION_RETURN_OBJECT(P_RoughMonsterSearch(self, distance, onlyseekable, frontonly)); + PARAM_FLOAT(fov); + ACTION_RETURN_OBJECT(P_RoughMonsterSearch(self, distance, onlyseekable, frontonly, fov)); } DEFINE_ACTION_FUNCTION_NATIVE(AActor, CheckSight, P_CheckSight) @@ -1576,15 +1590,17 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, A_ExtChase, A_ExtChase) return 0; } -int CheckForResurrection(AActor *self) +int CheckForResurrection(AActor *self, FState* state, int sound) { - return P_CheckForResurrection(self, false); + return P_CheckForResurrection(self, false, state, sound); } DEFINE_ACTION_FUNCTION_NATIVE(AActor, A_CheckForResurrection, CheckForResurrection) { PARAM_SELF_PROLOGUE(AActor); - ACTION_RETURN_BOOL(P_CheckForResurrection(self, false)); + PARAM_STATE(state); + PARAM_INT(sound); + ACTION_RETURN_BOOL(P_CheckForResurrection(self, false, state, sound)); } static void ZS_Face(AActor *self, AActor *faceto, double max_turn, double max_pitch, double ang_offset, double pitch_offset, int flags, double z_add) diff --git a/wadsrc/static/dehsupp.txt b/wadsrc/static/dehsupp.txt index 37e6713f7..68c322ce1 100644 --- a/wadsrc/static/dehsupp.txt +++ b/wadsrc/static/dehsupp.txt @@ -1185,13 +1185,27 @@ WeaponNames // when parsing a DEHACKED lump. Aliases { - A_Mushroom, A_Mushroom, 5, - A_Spawn, A_SpawnItem, 5, - A_Turn, A_Turn, 1, - A_Face, A_SetAngle, 1, - A_Scratch, A_CustomMeleeAttack, 5, // 19 characters for "A_CustomMeleeAttack"! - A_PlaySound, A_PlaySound, 5, // A function name any longer and the definition - A_RandomJump, A_Jump, 3, // for CodePointerAlias would need to be updated. - A_LineEffect, A_LineEffect, 2, - A_NailBomb, A_Explode, 7, + A_Mushroom, A_Mushroom, + A_Spawn, A_SpawnItem, + A_Turn, A_Turn, + A_Face, A_SetAngle, + A_Scratch, A_CustomMeleeAttack, + A_PlaySound, A_PlaySound, + A_RandomJump, A_Jump, + A_LineEffect, A_LineEffect, + A_NailBomb, A_Explode, + A_SpawnObject, MBF21_SpawnObject, + A_MonsterProjectile, MBF21_MonsterProjectile, + A_MonsterBulletAttack, MBF21_MonsterBulletAttack, + A_MonsterMeleeAttack, MBF21_MonsterMeleeAttack + A_RadiusDamage, A_Explode, + A_NoiseAlert, SoundAlert, + A_HealChase, MBF21_HealChase, + A_SeekTracer, A_SeekerMissile, + A_FindTracer, A_FindTracer, + A_JumpIfHealthBelow, MBF21_JumpIfHealthBelow, + A_JumpIfTracerInSight, MBF21_JumpIfTracerInSight, + A_JumpIfTracerCloser, MBF21_JumpIfTracerCloser, + A_JumpIfTracerInSight, MBF21_JumpIfTracerInSight, + A_JumpIfTracerCloser, MBF21_JumpIfTracerCloser, }; diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt index 52b2441ff..ce07c7bba 100644 --- a/wadsrc/static/zscript.txt +++ b/wadsrc/static/zscript.txt @@ -46,6 +46,7 @@ version "4.5" #include "zscript/actors/actions.zs" #include "zscript/actors/attacks.zs" #include "zscript/actors/morph.zs" +#include "zscript/actors/mbf21.zs" #include "zscript/actors/inventory/inventory.zs" #include "zscript/actors/inventory/inv_misc.zs" diff --git a/wadsrc/static/zscript/actors/actor.zs b/wadsrc/static/zscript/actors/actor.zs index 67b509247..c2d2ebd9b 100644 --- a/wadsrc/static/zscript/actors/actor.zs +++ b/wadsrc/static/zscript/actors/actor.zs @@ -682,10 +682,11 @@ class Actor : Thinker native native Actor SpawnSubMissile(Class type, Actor target); native Actor, Actor SpawnPlayerMissile(class type, double angle = 1e37, double x = 0, double y = 0, double z = 0, out FTranslatedLineTarget pLineTarget = null, bool nofreeaim = false, bool noautoaim = false, int aimflags = 0); native void SpawnTeleportFog(Vector3 pos, bool beforeTele, bool setTarget); - native Actor RoughMonsterSearch(int distance, bool onlyseekable = false, bool frontonly = false); + native Actor RoughMonsterSearch(int distance, bool onlyseekable = false, bool frontonly = false, double fov = 0); native int ApplyDamageFactor(Name damagetype, int damage); native int GetModifiedDamage(Name damagetype, int damage, bool passive, Actor inflictor = null, Actor source = null, int flags = 0); native bool CheckBossDeath(); + native bool CheckFov(Actor target, double fov); void A_Light(int extralight) { if (player) player.extralight = clamp(extralight, -20, 20); } void A_Light0() { if (player) player.extralight = 0; } @@ -706,7 +707,7 @@ class Actor : Thinker native native void TraceBleedAngle(int damage, double angle, double pitch); native void SetIdle(bool nofunction = false); - native bool CheckMeleeRange(); + native bool CheckMeleeRange(double range = -1); native bool TriggerPainChance(Name mod, bool forcedPain = false); native virtual int DamageMobj(Actor inflictor, Actor source, int damage, Name mod, int flags = 0, double angle = 0); native void PoisonMobj (Actor inflictor, Actor source, int damage, int duration, int period, Name type); @@ -759,6 +760,7 @@ class Actor : Thinker native native clearscope vector2 Vec2Angle(double length, double angle, bool absolute = false) const; native clearscope vector2 Vec2Offset(double x, double y, bool absolute = false) const; native clearscope vector3 Vec2OffsetZ(double x, double y, double atz, bool absolute = false) const; + native double PitchFromVel(); native void VelIntercept(Actor targ, double speed = -1, bool aimpitch = true, bool oldvel = false, bool resetvel = false); native void VelFromAngle(double speed = 1e37, double angle = 1e37); native void Vel3DFromAngle(double speed, double angle, double pitch); @@ -1061,7 +1063,7 @@ class Actor : Thinker native native void A_Look(); native void A_Chase(statelabel melee = '_a_chase_default', statelabel missile = '_a_chase_default', int flags = 0); native void A_VileChase(); - native bool A_CheckForResurrection(); + native bool A_CheckForResurrection(State state = null, Sound snd = 0); native void A_BossDeath(); bool A_CallSpecial(int special, int arg1=0, int arg2=0, int arg3=0, int arg4=0, int arg5=0) { diff --git a/wadsrc/static/zscript/actors/inventory/weapons.zs b/wadsrc/static/zscript/actors/inventory/weapons.zs index 8b822b5d1..9c3519b48 100644 --- a/wadsrc/static/zscript/actors/inventory/weapons.zs +++ b/wadsrc/static/zscript/actors/inventory/weapons.zs @@ -80,6 +80,7 @@ class Weapon : StateProvider flagdef NoDeathDeselect: WeaponFlags, 16; // Don't jump to the Deselect state when the player dies flagdef NoDeathInput: WeaponFlags, 17; // The weapon cannot be fired/reloaded/whatever when the player is dead flagdef CheatNotWeapon: WeaponFlags, 18; // Give cheat considers this not a weapon (used by Sigil) + flagdef NoAutoSwitchTo : WeaponFlags, 19; // do not auto switch to this weapon ever! // no-op flags flagdef NoLMS: none, 0; diff --git a/wadsrc/static/zscript/actors/mbf21.zs b/wadsrc/static/zscript/actors/mbf21.zs new file mode 100644 index 000000000..8a44c81d0 --- /dev/null +++ b/wadsrc/static/zscript/actors/mbf21.zs @@ -0,0 +1,288 @@ +// no attempt has been made to merge this with existing code. +extend class Actor +{ + // + // [XA] New mbf21 codepointers + // + + // + // A_SpawnObject + // Basically just A_Spawn with better behavior and more args. + // args[0]: Type of actor to spawn + // args[1]: Angle (degrees, in fixed point), relative to calling actor's angle + // args[2]: X spawn offset (fixed point), relative to calling actor + // args[3]: Y spawn offset (fixed point), relative to calling actor + // args[4]: Z spawn offset (fixed point), relative to calling actor + // args[5]: X velocity (fixed point) + // args[6]: Y velocity (fixed point) + // args[7]: Z velocity (fixed point) + // + deprecated("2.3", "for Dehacked use only") + void MBF21_SpawnObject(class type, double angle, double xofs, double yofs, double zofs, double xvel, double yvel, double zvel) + { + if (type == null) + return; + + // Don't spawn monsters if this actor has been massacred + if (DamageType == 'Massacre' && GetDefaultByType(type).bIsMonster) + { + return; + } + + // calculate position offsets + angle += self.Angle; + double s = sin(angle); + double c = cos(angle); + let pos = Vec2Offset(xofs * c - yofs * s, xofs * s + yofs * c); + + // spawn it, yo + let mo = Spawn(type, (pos, self.pos.Z - Floorclip + GetBobOffset() + zofs), ALLOW_REPLACE); + if (!mo) + return; + + // angle dangle + mo.angle = angle; + + // set velocity + // Same orientation issue here! + mo.vel.X = xvel * c - yvel * s; + mo.vel.Y = xvel * s + yvel * c; + mo.vel.Z = zvel; + + // if spawned object is a missile, set target+tracer + if (mo.bMissile || mo.bMbfBouncer) + { + // if spawner is also a missile, copy 'em + if (bMissile || bMbfBouncer) + { + mo.target = self.target; + mo.tracer = self.tracer; + } + // otherwise, set 'em as if a monster fired 'em + else + { + mo.target = self; + mo.tracer = self.target; + } + } + + // [XA] don't bother with the dont-inherit-friendliness hack + // that exists in A_Spawn, 'cause WTF is that about anyway? + + // unfortunately this means that this function cannot transfer friendliness at all. Oh well... + } + + // + // A_MonsterProjectile + // A parameterized monster projectile attack. + // args[0]: Type of actor to spawn + // args[1]: Angle (degrees, in fixed point), relative to calling actor's angle + // args[2]: Pitch (degrees, in fixed point), relative to calling actor's pitch; approximated + // args[3]: X/Y spawn offset, relative to calling actor's angle + // args[4]: Z spawn offset, relative to actor's default projectile fire height + // + deprecated("2.3", "for Dehacked use only") + void MBF21_MonsterProjectile(class type, double angle, double pitch, double spawnofs_xy, double spawnofs_z) + { + if (!target || !type) + return; + + A_FaceTarget(); + let mo = SpawnMissile(target, type); + if (!mo) + return; + + // adjust angle + mo.angle += angle; + + Pitch += mo.PitchFromVel(); + let missilespeed = abs(cos(Pitch) * mo.Speed); + mo.Vel3DFromAngle(mo.Speed, mo.angle, Pitch); + + // adjust position + double x = Spawnofs_xy * cos(self.angle); + double y = Spawnofs_xy * sin(self.angle); + mo.SetOrigin(mo.Vec3Offset(x, y, Spawnofs_z), false); + + // always set the 'tracer' field, so this pointer + // can be used to fire seeker missiles at will. + mo.tracer = target; + } + + // + // A_MonsterBulletAttack + // A parameterized monster bullet attack. + // args[0]: Horizontal spread (degrees, in fixed point) + // args[1]: Vertical spread (degrees, in fixed point) + // args[2]: Number of bullets to fire; if not set, defaults to 1 + // args[3]: Base damage of attack (e.g. for 3d5, customize the 3); if not set, defaults to 3 + // args[4]: Attack damage modulus (e.g. for 3d5, customize the 5); if not set, defaults to 5 + // + deprecated("2.3", "for Dehacked use only") + void MBF21_MonsterBulletAttack(double hspread, double vspread, int numbullets, int damagebase, int damagemod) + { + if (!target) + return; + + A_FaceTarget(); + A_StartSound(AttackSound, CHAN_WEAPON); + + let bangle = angle; + let slope = AimLineAttack(bangle, MISSILERANGE); + + for (int i = 0; i < numbullets; i++) + { + int damage = (random[mbf21]() % damagemod + 1) * damagebase; + let pangle = bangle + hspread * Random2[mbf21]() / 255.; + let pslope = slope + vspread * Random2[mbf21]() / 255.; + LineAttack(pangle, MISSILERANGE, pslope, damage, "Hitscan", "Bulletpuff"); + } + } + + // + // A_MonsterMeleeAttack + // A parameterized monster melee attack. + // args[0]: Base damage of attack (e.g. for 3d8, customize the 3); if not set, defaults to 3 + // args[1]: Attack damage modulus (e.g. for 3d8, customize the 8); if not set, defaults to 8 + // args[2]: Sound to play if attack hits + // args[3]: Range (fixed point); if not set, defaults to monster's melee range + // + deprecated("2.3", "for Dehacked use only") + void MBF21_MonsterMeleeAttack(int damagebase, int damagemod, Sound hitsound, double range) + { + let targ = target; + if (!targ) + return; + + if (range == 0) range = meleerange; + else range -= 20; // DSDA always subtracts 20 from the melee range. + + A_FaceTarget(); + if (!CheckMeleeRange(range)) + return; + + A_StartSound(hitsound, CHAN_WEAPON); + + int damage = (random[mbf21]() % damagemod + 1) * damagebase; + int newdam = targ.DamageMobj(self, self, damage, "Melee"); + targ.TraceBleed(newdam > 0 ? newdam : damage, self); + } + + // + // A_HealChase + // A parameterized version of A_VileChase. + // args[0]: State to jump to on the calling actor when resurrecting a corpse + // args[1]: Sound to play when resurrecting a corpse + // + deprecated("2.3", "for Dehacked use only") + void MBF21_HealChase(State healstate, Sound healsound) + { + if (!A_CheckForResurrection(healstate, healsound)) + A_Chase(); + } + + // + // A_FindTracer + // Search for a valid tracer (seek target), if the calling actor doesn't already have one. + // args[0]: field-of-view to search in (degrees, in fixed point); if zero, will search in all directions + // args[1]: distance to search (map blocks, i.e. 128 units) + // + void A_FindTracer(double fov, int dist) + { + if (!tracer) tracer = RoughMonsterSearch(dist, fov: fov); + } + + // + // A_ClearTracer + // Clear current tracer (seek target). + // + void A_ClearTracer() + { + tracer = NULL; + } + + // + // A_JumpIfHealthBelow + // Jumps to a state if caller's health is below the specified threshold. + // args[0]: State to jump to + // args[1]: Health threshold + // + deprecated("2.3", "for Dehacked use only") + void MBF21_JumpIfHealthBelow(State tstate, int health) + { + if (self.health < health) self.SetState(tstate); + } + + // + // A_JumpIfTargetInSight + // Jumps to a state if caller's target is in line-of-sight. + // args[0]: State to jump to + // args[1]: Field-of-view to check (degrees, in fixed point); if zero, will check in all directions + // + deprecated("2.3", "for Dehacked use only") + void MBF21_JumpIfTargetInSight(State tstate, double fov) + { + if (!target) + return; + + // Check FOV first since it's faster + if (fov > 0 && !CheckFov(target, fov)) + return; + + if (CheckSight(target)) self.SetState(tstate); + } + + // + // A_JumpIfTargetCloser + // Jumps to a state if caller's target is closer than the specified distance. + // args[0]: State to jump to + // args[1]: Distance threshold + // + deprecated("2.3", "for Dehacked use only") + void MBF21_JumpIfTargetCloser(State tstate, double dist) + { + if (!target) + return; + + if (dist > Distance2D(target)) self.SetState(tstate); + } + + // + // A_JumpIfTracerInSight + // Jumps to a state if caller's tracer (seek target) is in line-of-sight. + // args[0]: State to jump to + // args[1]: Field-of-view to check (degrees, in fixed point); if zero, will check in all directions + // + deprecated("2.3", "for Dehacked use only") + void MBF21_JumpIfTracerInSight(State tstate, double fov) + { + if (!tracer) + return; + + // Check FOV first since it's faster + if (fov > 0 && !CheckFov(tracer, fov)) + return; + + if (CheckSight(tracer)) self.SetState(tstate); + } + + // + // A_JumpIfTracerCloser + // Jumps to a state if caller's tracer (seek target) is closer than the specified distance. + // args[0]: State to jump to + // args[1]: Distance threshold (fixed point) + // + deprecated("2.3", "for Dehacked use only") + void MBF21_JumpIfTracerCloser(State tstate, double dist) + { + if (!tracer) + return; + + if (dist > Distance2D(tracer)) self.SetState(tstate); + } + + // These are native to lock away the insanity. + deprecated("2.3", "for Dehacked use only") native void MBF21_JumpIfFlagsSet(State tstate, int flags, int flags2); + deprecated("2.3", "for Dehacked use only") native void MBF21_AddFlags(int flags, int flags2); + deprecated("2.3", "for Dehacked use only") native void MBF21_RemoveFlags(int flags, int flags2); +} \ No newline at end of file diff --git a/wadsrc/static/zscript/actors/player/player.zs b/wadsrc/static/zscript/actors/player/player.zs index cbe6a5c3d..67b07b282 100644 --- a/wadsrc/static/zscript/actors/player/player.zs +++ b/wadsrc/static/zscript/actors/player/player.zs @@ -333,8 +333,8 @@ class PlayerPawn : Actor (player.ReadyWeapon == NULL || player.ReadyWeapon.bWimpy_Weapon)) { let best = BestWeapon (ammotype); - if (best != NULL && && !best.bNoAutoSwitchTo && (player.ReadyWeapon == NULL || - best.SelectionOrder < player.ReadyWeapon.SelectionOrder)) + if (best != NULL && !best.bNoAutoSwitchTo && + (player.ReadyWeapon == NULL || best.SelectionOrder < player.ReadyWeapon.SelectionOrder)) { player.PendingWeapon = best; }