/* ** thingdef_codeptr.cpp ** ** Code pointers for Actor definitions ** **--------------------------------------------------------------------------- ** Copyright 2002-2006 Christoph Oelckers ** Copyright 2004-2006 Randy Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** 3. The name of the author may not be used to endorse or promote products ** derived from this software without specific prior written permission. ** 4. When not used as part of ZDoom or a ZDoom derivative, this code will be ** covered by the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or (at ** your option) any later version. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **--------------------------------------------------------------------------- ** */ #include "gi.h" #include "g_level.h" #include "actor.h" #include "info.h" #include "sc_man.h" #include "tarray.h" #include "w_wad.h" #include "templates.h" #include "r_defs.h" #include "a_pickups.h" #include "s_sound.h" #include "cmdlib.h" #include "p_lnspec.h" #include "p_effect.h" #include "p_enemy.h" #include "decallib.h" #include "m_random.h" #include "i_system.h" #include "p_local.h" #include "c_console.h" #include "doomerrors.h" #include "a_sharedglobal.h" #include "v_video.h" #include "v_font.h" #include "doomstat.h" #include "v_palette.h" #include "g_shared/a_specialspot.h" #include "actorptrselect.h" #include "m_bbox.h" #include "r_data/r_translate.h" #include "p_trace.h" #include "p_setup.h" #include "gstrings.h" #include "d_player.h" #include "p_maputl.h" #include "p_spec.h" #include "templates.h" #include "v_text.h" #include "thingdef.h" #include "math/cmath.h" #include "g_levellocals.h" AActor *SingleActorFromTID(int tid, AActor *defactor); static FRandom pr_camissile ("CustomActorfire"); static FRandom pr_camelee ("CustomMelee"); static FRandom pr_cabullet ("CustomBullet"); static FRandom pr_cajump ("CustomJump"); static FRandom pr_cwbullet ("CustomWpBullet"); static FRandom pr_cwjump ("CustomWpJump"); static FRandom pr_cwpunch ("CustomWpPunch"); static FRandom pr_grenade ("ThrowGrenade"); static FRandom pr_crailgun ("CustomRailgun"); static FRandom pr_spawndebris ("SpawnDebris"); static FRandom pr_spawnitemex ("SpawnItemEx"); static FRandom pr_burst ("Burst"); static FRandom pr_monsterrefire ("MonsterRefire"); static FRandom pr_teleport("A_Teleport"); static FRandom pr_bfgselfdamage("BFGSelfDamage"); //========================================================================== // // ACustomInventory :: CallStateChain // // Executes the code pointers in a chain of states // until there is no next state // //========================================================================== bool AStateProvider::CallStateChain (AActor *actor, FState *state) { INTBOOL result = false; int counter = 0; VMValue params[3] = { actor, this, 0 }; // We accept return types of `state`, `(int|bool)` or `state, (int|bool)`. // The last one is for the benefit of A_Warp and A_Teleport. int retval, numret; FState *nextstate; VMReturn ret[2]; ret[0].PointerAt((void **)&nextstate); ret[1].IntAt(&retval); FState *savedstate = this->state; while (state != NULL) { if (!(state->UseFlags & SUF_ITEM)) { auto so = FState::StaticFindStateOwner(state); Printf(TEXTCOLOR_RED "State %s.%d not flagged for use in CustomInventory state chains.\n", so->TypeName.GetChars(), int(state - so->OwnedStates)); return false; } this->state = state; nextstate = NULL; // assume no jump if (state->ActionFunc != NULL) { if (state->ActionFunc->Unsafe) { // If an unsafe function (i.e. one that accesses user variables) is being detected, print a warning once and remove the bogus function. We may not call it because that would inevitably crash. auto owner = FState::StaticFindStateOwner(state); Printf(TEXTCOLOR_RED "Unsafe state call in state %s.%d to %s which accesses user variables. The action function has been removed from this state\n", owner->TypeName.GetChars(), int(state - owner->OwnedStates), state->ActionFunc->PrintableName.GetChars()); state->ActionFunc = nullptr; } PPrototype *proto = state->ActionFunc->Proto; VMReturn *wantret; FStateParamInfo stp = { state, STATE_StateChain, PSP_WEAPON }; params[2] = VMValue(&stp, ATAG_GENERIC); retval = true; // assume success wantret = NULL; // assume no return value wanted numret = 0; // For functions that return nothing (or return some type // we don't care about), we pretend they return true, // thanks to the values set just above. if (proto->ReturnTypes.Size() >= 2 && proto->ReturnTypes[0] == TypeState && (proto->ReturnTypes[1] == TypeSInt32 || proto->ReturnTypes[0] == TypeUInt32 || proto->ReturnTypes[1] == TypeBool)) { // Function returns a state and an int or bool wantret = &ret[0]; numret = 2; } else if (proto->ReturnTypes.Size() == 1 && proto->ReturnTypes[0] == TypeState) { // Function returns a state wantret = &ret[0]; retval = false; // this is a jump function which never affects the success state. numret = 1; } else if (proto->ReturnTypes.Size() >= 1 && (proto->ReturnTypes[0] == TypeSInt32 || proto->ReturnTypes[0] == TypeUInt32 || proto->ReturnTypes[0] == TypeBool)) { // Function returns an int or bool wantret = &ret[1]; numret = 1; } try { GlobalVMStack.Call(state->ActionFunc, params, state->ActionFunc->ImplicitArgs, wantret, numret); } catch (CVMAbortException &err) { err.MaybePrintMessage(); auto owner = FState::StaticFindStateOwner(state); int offs = int(state - owner->OwnedStates); err.stacktrace.AppendFormat("Called from state %s.%d in inventory state chain in %s\n", owner->TypeName.GetChars(), offs, GetClass()->TypeName.GetChars()); throw; } // As long as even one state succeeds, the whole chain succeeds unless aborted below. // A state that wants to jump does not count as "succeeded". if (nextstate == NULL) { result |= retval; } } // Since there are no delays it is a good idea to check for infinite loops here! counter++; if (counter >= 10000) break; if (nextstate == NULL) { nextstate = state->GetNextState(); if (state == nextstate) { // Abort immediately if the state jumps to itself! result = false; break; } } state = nextstate; } this->state = savedstate; return !!result; } DEFINE_ACTION_FUNCTION(ACustomInventory, CallStateChain) { PARAM_SELF_PROLOGUE(AStateProvider); PARAM_OBJECT(affectee, AActor); PARAM_POINTER(state, FState); ACTION_RETURN_BOOL(self->CallStateChain(affectee, state)); } //========================================================================== // // GetPointer // // resolve AAPTR_* // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, GetPointer) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT(ptr); ACTION_RETURN_OBJECT(COPY_AAPTR(self, ptr)); } //========================================================================== // // CheckClass // // NON-ACTION function to check a pointer's class. // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, CheckClass) { if (numret > 0) { assert(ret != NULL); PARAM_SELF_PROLOGUE(AActor); PARAM_CLASS (checktype, AActor); PARAM_INT_DEF (pick_pointer); PARAM_BOOL_DEF (match_superclass); self = COPY_AAPTR(self, pick_pointer); if (self == nullptr || checktype == nullptr) { ret->SetInt(false); } else if (match_superclass) { ret->SetInt(self->IsKindOf(checktype)); } else { ret->SetInt(self->GetClass() == checktype); } return 1; } return 0; } //========================================================================== // // CheckClass // // NON-ACTION function to calculate missile damage for the given actor // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, GetMissileDamage) { if (numret > 0) { assert(ret != NULL); PARAM_SELF_PROLOGUE(AActor); PARAM_INT(mask); PARAM_INT(add); PARAM_INT_DEF(pick_pointer); self = COPY_AAPTR(self, pick_pointer); if (self == NULL) { ret->SetInt(0); } else { ret->SetInt(self->GetMissileDamage(mask, add)); } return 1; } return 0; } //========================================================================== // // CountInv // // NON-ACTION function to return the inventory count of an item. // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, CountInv) { if (numret > 0) { assert(ret != NULL); PARAM_SELF_PROLOGUE(AActor); PARAM_CLASS(itemtype, AInventory); PARAM_INT_DEF(pick_pointer); self = COPY_AAPTR(self, pick_pointer); if (self == NULL || itemtype == NULL) { ret->SetInt(0); } else { AInventory *item = self->FindInventory(itemtype); ret->SetInt(item ? item->Amount : 0); } return 1; } return 0; } //========================================================================== // // GetDistance // // NON-ACTION function to get the distance in double. // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, GetDistance) { if (numret > 0) { assert(ret != NULL); PARAM_SELF_PROLOGUE(AActor); PARAM_BOOL(checkz); PARAM_INT_DEF(ptr); AActor *target = COPY_AAPTR(self, ptr); if (!target || target == self) { ret->SetFloat(0); } else { DVector3 diff = self->Vec3To(target); if (checkz) diff.Z += (target->Height - self->Height) / 2; else diff.Z = 0.; ret->SetFloat(diff.Length()); } return 1; } return 0; } //========================================================================== // // GetAngle // // NON-ACTION function to get the angle in degrees (normalized to -180..180) // //========================================================================== enum GAFlags { GAF_RELATIVE = 1, GAF_SWITCH = 1 << 1, }; DEFINE_ACTION_FUNCTION(AActor, GetAngle) { if (numret > 0) { assert(ret != NULL); PARAM_SELF_PROLOGUE(AActor); PARAM_INT(flags); PARAM_INT_DEF(ptr) AActor *target = COPY_AAPTR(self, ptr); if (!target || target == self) { ret->SetFloat(0); } else { DVector3 diff = (flags & GAF_SWITCH) ? target->Vec3To(self) : self->Vec3To(target); DAngle angto = diff.Angle(); DAngle yaw = (flags & GAF_SWITCH) ? target->Angles.Yaw : self->Angles.Yaw; if (flags & GAF_RELATIVE) angto = deltaangle(yaw, angto); ret->SetFloat(angto.Degrees); } return 1; } return 0; } //========================================================================== // // GetSpawnHealth // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, GetSpawnHealth) { if (numret > 0) { PARAM_SELF_PROLOGUE(AActor); ret->SetInt(self->SpawnHealth()); return 1; } return 0; } //========================================================================== // // GetGibHealth // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, GetGibHealth) { if (numret > 0) { PARAM_SELF_PROLOGUE(AActor); ret->SetInt(self->GetGibHealth()); return 1; } return 0; } //========================================================================== // // GetSpriteAngle // // NON-ACTION function returns the sprite angle of a pointer. //========================================================================== DEFINE_ACTION_FUNCTION(AActor, GetSpriteAngle) { if (numret > 0) { assert(ret != NULL); PARAM_SELF_PROLOGUE(AActor); PARAM_INT_DEF(ptr); AActor *target = COPY_AAPTR(self, ptr); if (target == nullptr) { ret->SetFloat(0.0); } else { const double ang = target->SpriteAngle.Degrees; ret->SetFloat(ang); } return 1; } return 0; } //========================================================================== // // GetSpriteRotation // // NON-ACTION function returns the sprite rotation of a pointer. //========================================================================== DEFINE_ACTION_FUNCTION(AActor, GetSpriteRotation) { if (numret > 0) { assert(ret != NULL); PARAM_SELF_PROLOGUE(AActor); PARAM_INT_DEF(ptr); AActor *target = COPY_AAPTR(self, ptr); if (target == nullptr) { ret->SetFloat(0.0); } else { const double ang = target->SpriteRotation.Degrees; ret->SetFloat(ang); } return 1; } return 0; } //========================================================================== // // GetZAt // // NON-ACTION function to get the floor or ceiling z at (x, y) with // relativity being an option. //========================================================================== enum GZFlags { GZF_ABSOLUTEPOS = 1, // Use the absolute position instead of an offsetted one. GZF_ABSOLUTEANG = 1 << 1, // Don't add the actor's angle to the parameter. GZF_CEILING = 1 << 2, // Check the ceiling instead of the floor. GZF_3DRESTRICT = 1 << 3, // Ignore midtextures and 3D floors above the pointer's z. GZF_NOPORTALS = 1 << 4, // Don't pass through any portals. GZF_NO3DFLOOR = 1 << 5, // Pass all 3D floors. }; DEFINE_ACTION_FUNCTION(AActor, GetZAt) { if (numret > 0) { assert(ret != NULL); PARAM_SELF_PROLOGUE(AActor); PARAM_FLOAT_DEF(px); PARAM_FLOAT_DEF(py); PARAM_ANGLE_DEF(angle); PARAM_INT_DEF(flags); PARAM_INT_DEF(pick_pointer); AActor *mobj = COPY_AAPTR(self, pick_pointer); if (mobj == nullptr) { ret->SetFloat(0); } else { // [MC] At any time, the NextLowest/Highest functions could be changed to include // more FFC flags to check. Don't risk it by just passing flags straight to it. DVector2 pos = { px, py }; double z = 0.; int pflags = (flags & GZF_3DRESTRICT) ? FFCF_3DRESTRICT : 0; if (flags & GZF_NOPORTALS) pflags |= FFCF_NOPORTALS; if (!(flags & GZF_ABSOLUTEPOS)) { if (!(flags & GZF_ABSOLUTEANG)) { angle += mobj->Angles.Yaw; } double s = angle.Sin(); double c = angle.Cos(); pos = mobj->Vec2Offset(pos.X * c + pos.Y * s, pos.X * s - pos.Y * c); } sector_t *sec = P_PointInSector(pos); if (sec) { if (flags & GZF_CEILING) { if ((flags & GZF_NO3DFLOOR) && (flags & GZF_NOPORTALS)) { z = sec->ceilingplane.ZatPoint(pos); } else if (flags & GZF_NO3DFLOOR) { z = sec->HighestCeilingAt(pos); } else { // [MC] Handle strict 3D floors and portal toggling via the flags passed to it. z = sec->NextHighestCeilingAt(pos.X, pos.Y, mobj->Z(), mobj->Top(), pflags); } } else { if ((flags & GZF_NO3DFLOOR) && (flags & GZF_NOPORTALS)) { z = sec->floorplane.ZatPoint(pos); } else if (flags & GZF_NO3DFLOOR) { z = sec->LowestFloorAt(pos); } else { z = sec->NextLowestFloorAt(pos.X, pos.Y, mobj->Z(), pflags, mobj->MaxStepHeight); } } } ret->SetFloat(z); return 1; } } return 0; } //========================================================================== // // GetCrouchFactor // // NON-ACTION function to retrieve a player's crouching factor. // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, GetCrouchFactor) { if (numret > 0) { assert(ret != NULL); PARAM_SELF_PROLOGUE(AActor); PARAM_INT_DEF(ptr); AActor *mobj = COPY_AAPTR(self, ptr); if (!mobj || !mobj->player) { ret->SetFloat(1); } else { ret->SetFloat(mobj->player->crouchfactor); } return 1; } return 0; } //========================================================================== // // GetCVar // // NON-ACTION function that works like ACS's GetCVar. // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, GetCVar) { if (numret > 0) { assert(ret != nullptr); PARAM_SELF_PROLOGUE(AActor); PARAM_STRING(cvarname); FBaseCVar *cvar = GetCVar(self, cvarname); if (cvar == nullptr) { ret->SetFloat(0); } else { ret->SetFloat(cvar->GetGenericRep(CVAR_Float).Float); } return 1; } return 0; } //========================================================================== // // GetPlayerInput // // NON-ACTION function that works like ACS's GetPlayerInput. // Takes a pointer as anyone may or may not be a player. //========================================================================== DEFINE_ACTION_FUNCTION(AActor, GetPlayerInput) { if (numret > 0) { assert(ret != nullptr); PARAM_SELF_PROLOGUE(AActor); PARAM_INT (inputnum); PARAM_INT_DEF(ptr); AActor *mobj = COPY_AAPTR(self, ptr); //Need a player. if (!mobj || !mobj->player) { ret->SetInt(0); } else { ret->SetInt(P_Thing_CheckInputNum(mobj->player, inputnum)); } return 1; } return 0; } //========================================================================== // // CountProximity // // NON-ACTION function of A_CheckProximity that returns how much it counts. // Takes a pointer as anyone may or may not be a player. //========================================================================== DEFINE_ACTION_FUNCTION(AActor, CountProximity) { if (numret > 0) { PARAM_SELF_PROLOGUE(AActor); PARAM_CLASS(classname, AActor); PARAM_FLOAT(distance); PARAM_INT_DEF(flags); PARAM_INT_DEF(ptr); AActor *mobj = COPY_AAPTR(self, ptr); if (mobj == nullptr) { ret->SetInt(0); } else { ret->SetInt(P_Thing_CheckProximity(self, classname, distance, 0, flags, ptr, true)); } return 1; } return 0; } //=========================================================================== // // __decorate_internal_int__ // __decorate_internal_bool__ // __decorate_internal_float__ // // Placeholders for forcing DECORATE to cast numbers. If actually called, // returns whatever was passed. // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, __decorate_internal_int__) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT(returnme); ACTION_RETURN_INT(returnme); } DEFINE_ACTION_FUNCTION(AActor, __decorate_internal_bool__) { PARAM_SELF_PROLOGUE(AActor); PARAM_BOOL(returnme); ACTION_RETURN_BOOL(returnme); } DEFINE_ACTION_FUNCTION(AActor, __decorate_internal_float__) { PARAM_SELF_PROLOGUE(AActor); PARAM_FLOAT(returnme); if (numret > 0) { ret->SetFloat(returnme); return 1; } return 0; } //========================================================================== // // A_RearrangePointers // // Allow an actor to change its relationship to other actors by // copying pointers freely between TARGET MASTER and TRACER. // Can also assign null value, but does not duplicate A_ClearTarget. // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_RearrangePointers) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT (ptr_target); PARAM_INT_DEF (ptr_master); PARAM_INT_DEF (ptr_tracer); PARAM_INT_DEF (flags); // Rearrange pointers internally // Fetch all values before modification, so that all fields can get original values AActor *gettarget = self->target, *getmaster = self->master, *gettracer = self->tracer; switch (ptr_target) // pick the new target { case AAPTR_MASTER: self->target = getmaster; if (!(PTROP_UNSAFETARGET & flags)) VerifyTargetChain(self); break; case AAPTR_TRACER: self->target = gettracer; if (!(PTROP_UNSAFETARGET & flags)) VerifyTargetChain(self); break; case AAPTR_NULL: self->target = NULL; // THIS IS NOT "A_ClearTarget", so no other targeting info is removed break; } // presently permitting non-monsters to set master switch (ptr_master) // pick the new master { case AAPTR_TARGET: self->master = gettarget; if (!(PTROP_UNSAFEMASTER & flags)) VerifyMasterChain(self); break; case AAPTR_TRACER: self->master = gettracer; if (!(PTROP_UNSAFEMASTER & flags)) VerifyMasterChain(self); break; case AAPTR_NULL: self->master = NULL; break; } switch (ptr_tracer) // pick the new tracer { case AAPTR_TARGET: self->tracer = gettarget; break; // no verification deemed necessary; the engine never follows a tracer chain(?) case AAPTR_MASTER: self->tracer = getmaster; break; // no verification deemed necessary; the engine never follows a tracer chain(?) case AAPTR_NULL: self->tracer = NULL; break; } return 0; } //========================================================================== // // A_TransferPointer // // Copy one pointer (MASTER, TARGET or TRACER) from this actor (SELF), // or from this actor's MASTER, TARGET or TRACER. // // You can copy any one of that actor's pointers // // Assign the copied pointer to any one pointer in SELF, // MASTER, TARGET or TRACER. // // Any attempt to make an actor point to itself will replace the pointer // with a null value. // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_TransferPointer) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT (ptr_source); PARAM_INT (ptr_recipient); PARAM_INT (ptr_sourcefield); PARAM_INT_DEF (ptr_recipientfield); PARAM_INT_DEF (flags); AActor *source, *recipient; // Exchange pointers with actors to whom you have pointers (or with yourself, if you must) source = COPY_AAPTR(self, ptr_source); recipient = COPY_AAPTR(self, ptr_recipient); // pick an actor to store the provided pointer value if (recipient == NULL) { return 0; } // convert source from dataprovider to data source = COPY_AAPTR(source, ptr_sourcefield); if (source == recipient) { // The recepient should not acquire a pointer to itself; will write NULL} source = NULL; } if (ptr_recipientfield == AAPTR_DEFAULT) { // If default: Write to same field as data was read from ptr_recipientfield = ptr_sourcefield; } ASSIGN_AAPTR(recipient, ptr_recipientfield, source, flags); return 0; } //========================================================================== // // A_CopyFriendliness // // Join forces with one of the actors you are pointing to (MASTER by default) // // Normal CopyFriendliness reassigns health. This function will not. // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_CopyFriendliness) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT_DEF (ptr_source); if (self->player != NULL) { return 0; } AActor *source = COPY_AAPTR(self, ptr_source); if (source != NULL) { // No change in current target or health self->CopyFriendliness(source, false, false); } return 0; } //========================================================================== // // Customizable attack functions which use actor parameters. // //========================================================================== static void DoAttack (AActor *self, bool domelee, bool domissile, int MeleeDamage, FSoundID MeleeSound, PClassActor *MissileType,double MissileHeight) { if (self->target == NULL) return; A_FaceTarget (self); if (domelee && MeleeDamage>0 && self->CheckMeleeRange ()) { int damage = pr_camelee.HitDice(MeleeDamage); if (MeleeSound) S_Sound (self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM); int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); } else if (domissile && MissileType != NULL) { // This seemingly senseless code is needed for proper aiming. double add = MissileHeight + self->GetBobOffset() - 32; self->AddZ(add); AActor *missile = P_SpawnMissileXYZ (self->PosPlusZ(32.), self, self->target, MissileType, false); self->AddZ(-add); if (missile) { // automatic handling of seeker missiles if (missile->flags2&MF2_SEEKERMISSILE) { missile->tracer=self->target; } P_CheckMissileSpawn(missile, self->radius); } } } DEFINE_ACTION_FUNCTION(AActor, A_MeleeAttack) { PARAM_SELF_PROLOGUE(AActor); int MeleeDamage = self->GetClass()->MeleeDamage; FSoundID MeleeSound = self->GetClass()->MeleeSound; DoAttack(self, true, false, MeleeDamage, MeleeSound, NULL, 0); return 0; } DEFINE_ACTION_FUNCTION(AActor, A_MissileAttack) { PARAM_SELF_PROLOGUE(AActor); PClassActor *MissileType = PClass::FindActor(self->GetClass()->MissileName); DoAttack(self, false, true, 0, 0, MissileType, self->GetClass()->MissileHeight); return 0; } DEFINE_ACTION_FUNCTION(AActor, A_ComboAttack) { PARAM_SELF_PROLOGUE(AActor); int MeleeDamage = self->GetClass()->MeleeDamage; FSoundID MeleeSound = self->GetClass()->MeleeSound; PClassActor *MissileType = PClass::FindActor(self->GetClass()->MissileName); DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, self->GetClass()->MissileHeight); return 0; } DEFINE_ACTION_FUNCTION(AActor, A_BasicAttack) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT (melee_damage); PARAM_SOUND (melee_sound); PARAM_CLASS (missile_type, AActor); PARAM_FLOAT (missile_height); if (missile_type != NULL) { DoAttack(self, true, true, melee_damage, melee_sound, missile_type, missile_height); } return 0; } //========================================================================== // // Custom sound functions. // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_PlaySound) { PARAM_SELF_PROLOGUE(AActor); PARAM_SOUND_DEF (soundid); PARAM_INT_DEF (channel); PARAM_FLOAT_DEF (volume); PARAM_BOOL_DEF (looping); PARAM_FLOAT_DEF (attenuation); PARAM_BOOL_DEF (local); if (!looping) { S_PlaySound(self, channel, soundid, (float)volume, (float)attenuation, local); } else { if (!S_IsActorPlayingSomething (self, channel&7, soundid)) { S_PlaySound(self, channel | CHAN_LOOP, soundid, (float)volume, (float)attenuation, local); } } return 0; } DEFINE_ACTION_FUNCTION(AActor, A_StopSound) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT_DEF(slot); S_StopSound(self, slot); return 0; } //========================================================================== // // These come from a time when DECORATE constants did not exist yet and // the sound interface was less flexible. As a result the parameters are // not optimal and these functions have been deprecated in favor of extending // A_PlaySound and A_StopSound. // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_PlaySoundEx) { PARAM_SELF_PROLOGUE(AActor); PARAM_SOUND (soundid); PARAM_NAME (channel); PARAM_BOOL_DEF (looping); PARAM_INT_DEF (attenuation_raw); float attenuation; switch (attenuation_raw) { case -1: attenuation = ATTN_STATIC; break; // drop off rapidly default: case 0: attenuation = ATTN_NORM; break; // normal case 1: case 2: attenuation = ATTN_NONE; break; // full volume } if (channel < NAME_Auto || channel > NAME_SoundSlot7) { channel = NAME_Auto; } if (!looping) { S_Sound (self, int(channel) - NAME_Auto, soundid, 1, attenuation); } else { if (!S_IsActorPlayingSomething (self, int(channel) - NAME_Auto, soundid)) { S_Sound (self, (int(channel) - NAME_Auto) | CHAN_LOOP, soundid, 1, attenuation); } } return 0; } DEFINE_ACTION_FUNCTION(AActor, A_StopSoundEx) { PARAM_SELF_PROLOGUE(AActor); PARAM_NAME(channel); if (channel > NAME_Auto && channel <= NAME_SoundSlot7) { S_StopSound (self, int(channel) - NAME_Auto); } return 0; } //========================================================================== // // Generic seeker missile function // //========================================================================== static FRandom pr_seekermissile ("SeekerMissile"); enum { SMF_LOOK = 1, SMF_PRECISE = 2, SMF_CURSPEED = 4, }; DEFINE_ACTION_FUNCTION(AActor, A_SeekerMissile) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT(ang1); PARAM_INT(ang2); PARAM_INT_DEF(flags); PARAM_INT_DEF(chance); PARAM_INT_DEF(distance); if ((flags & SMF_LOOK) && (self->tracer == 0) && (pr_seekermissile()tracer = P_RoughMonsterSearch (self, distance, true); } if (!P_SeekerMissile(self, clamp(ang1, 0, 90), clamp(ang2, 0, 90), !!(flags & SMF_PRECISE), !!(flags & SMF_CURSPEED))) { if (flags & SMF_LOOK) { // This monster is no longer seekable, so let us look for another one next time. self->tracer = NULL; } } return 0; } //========================================================================== // // Hitscan attack with a customizable amount of bullets (specified in damage) // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_BulletAttack) { PARAM_SELF_PROLOGUE(AActor); int i; if (!self->target) return 0; A_FaceTarget (self); DAngle slope = P_AimLineAttack (self, self->Angles.Yaw, MISSILERANGE); S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM); for (i = self->GetMissileDamage (0, 1); i > 0; --i) { DAngle angle = self->Angles.Yaw + pr_cabullet.Random2() * (5.625 / 256.); int damage = ((pr_cabullet()%5)+1)*3; P_LineAttack(self, angle, MISSILERANGE, slope, damage, NAME_Hitscan, NAME_BulletPuff); } return 0; } //========================================================================== // // State jump function // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_Jump) { PARAM_ACTION_PROLOGUE(AActor); PARAM_INT(maxchance); paramnum++; // Increment paramnum to point at the first jump target int count = numparam - paramnum; if (count > 0 && (maxchance >= 256 || pr_cajump() < maxchance)) { int jumpnum = (count == 1 ? 0 : (pr_cajump() % count)); PARAM_STATE_ACTION_AT(paramnum + jumpnum, jumpto); ACTION_RETURN_STATE(jumpto); } ACTION_RETURN_STATE(NULL); } //========================================================================== // // State jump function // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, CheckInventory) { PARAM_SELF_PROLOGUE(AActor); PARAM_CLASS (itemtype, AInventory); PARAM_INT (itemamount); PARAM_INT_DEF (setowner); if (itemtype == nullptr) { ACTION_RETURN_BOOL(false); } AActor *owner = COPY_AAPTR(self, setowner); if (owner == nullptr) { ACTION_RETURN_BOOL(false); } AInventory *item = owner->FindInventory(itemtype); if (item) { if (itemamount > 0) { if (item->Amount >= itemamount) { ACTION_RETURN_BOOL(true); } } else if (item->Amount >= item->MaxAmount) { ACTION_RETURN_BOOL(true); } } ACTION_RETURN_BOOL(false); } //========================================================================== // // Parameterized version of A_Explode // //========================================================================== enum { XF_HURTSOURCE = 1, XF_NOTMISSILE = 4, XF_NOACTORTYPE = 1 << 3, XF_NOSPLASH = 16, }; DEFINE_ACTION_FUNCTION(AActor, A_Explode) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT_DEF (damage); PARAM_INT_DEF (distance); PARAM_INT_DEF (flags); PARAM_BOOL_DEF (alert); PARAM_INT_DEF (fulldmgdistance); PARAM_INT_DEF (nails); PARAM_INT_DEF (naildamage); PARAM_CLASS_DEF (pufftype, AActor); PARAM_NAME_DEF (damagetype); if (damage < 0) // get parameters from metadata { damage = self->GetClass()->ExplosionDamage; distance = self->GetClass()->ExplosionRadius; flags = !self->GetClass()->DontHurtShooter; alert = false; } if (distance <= 0) distance = damage; // NailBomb effect, from SMMU but not from its source code: instead it was implemented and // generalized from the documentation at http://www.doomworld.com/eternity/engine/codeptrs.html if (nails) { DAngle ang; for (int i = 0; i < nails; i++) { ang = i*360./nails; // Comparing the results of a test wad with Eternity, it seems A_NailBomb does not aim P_LineAttack(self, ang, MISSILERANGE, 0., //P_AimLineAttack (self, ang, MISSILERANGE), naildamage, NAME_Hitscan, pufftype, (self->flags & MF_MISSILE) ? LAF_TARGETISSOURCE : 0); } } if (!(flags & XF_NOACTORTYPE) && damagetype == NAME_None) { damagetype = self->DamageType; } int pflags = 0; if (flags & XF_HURTSOURCE) pflags |= RADF_HURTSOURCE; if (flags & XF_NOTMISSILE) pflags |= RADF_SOURCEISSPOT; int count = P_RadiusAttack (self, self->target, damage, distance, damagetype, pflags, fulldmgdistance); if (!(flags & XF_NOSPLASH)) P_CheckSplash(self, distance); if (alert && self->target != NULL && self->target->player != NULL) { P_NoiseAlert(self->target, self); } ACTION_RETURN_INT(count); } //========================================================================== // // A_RadiusThrust // //========================================================================== enum { RTF_AFFECTSOURCE = 1, RTF_NOIMPACTDAMAGE = 2, RTF_NOTMISSILE = 4, }; DEFINE_ACTION_FUNCTION(AActor, A_RadiusThrust) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT_DEF (force); PARAM_INT_DEF (distance); PARAM_INT_DEF (flags); PARAM_INT_DEF (fullthrustdistance); bool sourcenothrust = false; if (force == 0) force = 128; if (distance <= 0) distance = abs(force); // Temporarily negate MF2_NODMGTHRUST on the shooter, since it renders this function useless. if (!(flags & RTF_NOTMISSILE) && self->target != NULL && self->target->flags2 & MF2_NODMGTHRUST) { sourcenothrust = true; self->target->flags2 &= ~MF2_NODMGTHRUST; } P_RadiusAttack (self, self->target, force, distance, self->DamageType, flags | RADF_NODAMAGE, fullthrustdistance); P_CheckSplash(self, distance); if (sourcenothrust) { self->target->flags2 |= MF2_NODMGTHRUST; } return 0; } //========================================================================== // // A_RadiusDamageSelf // //========================================================================== enum { RDSF_BFGDAMAGE = 1, }; DEFINE_ACTION_FUNCTION(AActor, A_RadiusDamageSelf) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT_DEF(damage); PARAM_FLOAT_DEF(distance); PARAM_INT_DEF(flags); PARAM_CLASS_DEF(flashtype, AActor); int i; int damageSteps; int actualDamage; double actualDistance; actualDistance = self->Distance3D(self->target); if (actualDistance < distance) { // [XA] Decrease damage with distance. Use the BFG damage // calculation formula if the flag is set (essentially // a generalization of SMMU's BFG11K behavior, used // with fraggle's blessing.) damageSteps = damage - int(damage * actualDistance / distance); if (flags & RDSF_BFGDAMAGE) { actualDamage = 0; for (i = 0; i < damageSteps; ++i) actualDamage += (pr_bfgselfdamage() & 7) + 1; } else { actualDamage = damageSteps; } // optional "flash" effect -- spawn an actor on // the player to indicate bad things happened. AActor *flash = NULL; if(flashtype != NULL) flash = Spawn(flashtype, self->target->PosPlusZ(self->target->Height / 4), ALLOW_REPLACE); int dmgFlags = 0; FName dmgType = NAME_BFGSplash; if (flash != NULL) { if (flash->flags5 & MF5_PUFFGETSOWNER) flash->target = self->target; if (flash->flags3 & MF3_FOILINVUL) dmgFlags |= DMG_FOILINVUL; if (flash->flags7 & MF7_FOILBUDDHA) dmgFlags |= DMG_FOILBUDDHA; dmgType = flash->DamageType; } int newdam = P_DamageMobj(self->target, self, self->target, actualDamage, dmgType, dmgFlags); P_TraceBleed(newdam > 0 ? newdam : actualDamage, self->target, self); } return 0; } //========================================================================== // // Execute a line special / script // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_CallSpecial) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT (special); PARAM_INT_DEF (arg1); PARAM_INT_DEF (arg2); PARAM_INT_DEF (arg3); PARAM_INT_DEF (arg4); PARAM_INT_DEF (arg5); bool res = !!P_ExecuteSpecial(special, NULL, self, false, arg1, arg2, arg3, arg4, arg5); ACTION_RETURN_BOOL(res); } //========================================================================== // // The ultimate code pointer: Fully customizable missiles! // //========================================================================== enum CM_Flags { CMF_AIMMODE = 3, CMF_TRACKOWNER = 4, CMF_CHECKTARGETDEAD = 8, CMF_ABSOLUTEPITCH = 16, CMF_OFFSETPITCH = 32, CMF_SAVEPITCH = 64, CMF_ABSOLUTEANGLE = 128, CMF_BADPITCH = 256 }; DEFINE_ACTION_FUNCTION(AActor, A_SpawnProjectile) { PARAM_SELF_PROLOGUE(AActor); PARAM_CLASS (ti, AActor); PARAM_FLOAT_DEF (Spawnheight); PARAM_FLOAT_DEF (Spawnofs_xy); PARAM_ANGLE_DEF (Angle); PARAM_INT_DEF (flags); PARAM_ANGLE_DEF (Pitch); PARAM_INT_DEF (ptr); AActor *ref = COPY_AAPTR(self, ptr); int aimmode = flags & CMF_AIMMODE; AActor * targ; AActor * missile = nullptr; if (ref != NULL || aimmode == 2) { if (ti) { DAngle angle = self->Angles.Yaw - 90; double x = Spawnofs_xy * angle.Cos(); double y = Spawnofs_xy * angle.Sin(); double z = Spawnheight + self->GetBobOffset() - 32 + (self->player? self->player->crouchoffset : 0.); DVector3 pos = self->Pos(); switch (aimmode) { case 0: default: // same adjustment as above (in all 3 directions this time) - for better aiming! self->SetXYZ(self->Vec3Offset(x, y, z)); missile = P_SpawnMissileXYZ(self->PosPlusZ(32.), self, ref, ti, false); self->SetXYZ(pos); break; case 1: missile = P_SpawnMissileXYZ(self->Vec3Offset(x, y, self->GetBobOffset() + Spawnheight), self, ref, ti, false); break; case 2: self->SetXYZ(self->Vec3Offset(x, y, 0.)); missile = P_SpawnMissileAngleZSpeed(self, self->Z() + self->GetBobOffset() + Spawnheight, ti, self->Angles.Yaw, 0, GetDefaultByType(ti)->Speed, self, false); self->SetXYZ(pos); flags |= CMF_ABSOLUTEPITCH; break; } if (missile != NULL) { // Use the actual velocity instead of the missile's Speed property // so that this can handle missiles with a high vertical velocity // component properly. double missilespeed; if ( (CMF_ABSOLUTEPITCH|CMF_OFFSETPITCH) & flags) { if (CMF_OFFSETPITCH & flags) { Pitch += missile->Vel.Pitch(); } missilespeed = fabs(Pitch.Cos() * missile->Speed); missile->Vel.Z = Pitch.Sin() * missile->Speed; if (!(flags & CMF_BADPITCH)) missile->Vel.Z *= -1; } else { missilespeed = missile->VelXYToSpeed(); } if (CMF_SAVEPITCH & flags) { missile->Angles.Pitch = Pitch; // In aimmode 0 and 1 without absolutepitch or offsetpitch, the pitch parameter // contains the unapplied parameter. In that case, it is set as pitch without // otherwise affecting the spawned actor. } missile->Angles.Yaw = (CMF_ABSOLUTEANGLE & flags) ? Angle : missile->Angles.Yaw + Angle; missile->VelFromAngle(missilespeed); // handle projectile shooting projectiles - track the // links back to a real owner if (self->isMissile(!!(flags & CMF_TRACKOWNER))) { AActor *owner = self ;//->target; while (owner->isMissile(!!(flags & CMF_TRACKOWNER)) && owner->target) owner = owner->target; targ = owner; missile->target = owner; // automatic handling of seeker missiles if (self->flags2 & missile->flags2 & MF2_SEEKERMISSILE) { missile->tracer = self->tracer; } } else if (missile->flags2 & MF2_SEEKERMISSILE) { // automatic handling of seeker missiles missile->tracer = self->target; } // we must redo the spectral check here because the owner is set after spawning so the FriendPlayer value may be wrong if (missile->flags4 & MF4_SPECTRAL) { if (missile->target != NULL) { missile->SetFriendPlayer(missile->target->player); } else { missile->FriendPlayer = 0; } } P_CheckMissileSpawn(missile, self->radius); } } } else if (flags & CMF_CHECKTARGETDEAD) { // Target is dead and the attack shall be aborted. if (self->SeeState != NULL && (self->health > 0 || !(self->flags3 & MF3_ISMONSTER))) self->SetState(self->SeeState); } ACTION_RETURN_OBJECT(missile); } //========================================================================== // // An even more customizable hitscan attack // //========================================================================== enum CBA_Flags { CBAF_AIMFACING = 1, CBAF_NORANDOM = 2, CBAF_EXPLICITANGLE = 4, CBAF_NOPITCH = 8, CBAF_NORANDOMPUFFZ = 16, CBAF_PUFFTARGET = 32, CBAF_PUFFMASTER = 64, CBAF_PUFFTRACER = 128, }; static void AimBulletMissile(AActor *proj, AActor *puff, int flags, bool temp, bool cba); DEFINE_ACTION_FUNCTION(AActor, A_CustomBulletAttack) { PARAM_SELF_PROLOGUE(AActor); PARAM_ANGLE (spread_xy); PARAM_ANGLE (spread_z); PARAM_INT (numbullets); PARAM_INT (damageperbullet); PARAM_CLASS_DEF (pufftype, AActor); PARAM_FLOAT_DEF (range); PARAM_INT_DEF (flags); PARAM_INT_DEF (ptr); PARAM_CLASS_DEF (missile, AActor); PARAM_FLOAT_DEF (Spawnheight); PARAM_FLOAT_DEF (Spawnofs_xy); AActor *ref = COPY_AAPTR(self, ptr); if (range == 0) range = MISSILERANGE; int i; DAngle bangle; DAngle bslope = 0.; int laflags = (flags & CBAF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; if (ref != NULL || (flags & CBAF_AIMFACING)) { if (!(flags & CBAF_AIMFACING)) { A_Face(self, ref); } bangle = self->Angles.Yaw; if (!(flags & CBAF_NOPITCH)) bslope = P_AimLineAttack (self, bangle, MISSILERANGE); if (pufftype == nullptr) pufftype = PClass::FindActor(NAME_BulletPuff); S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM); for (i = 0; i < numbullets; i++) { DAngle angle = bangle; DAngle slope = bslope; if (flags & CBAF_EXPLICITANGLE) { angle += spread_xy; slope += spread_z; } else { angle += spread_xy * (pr_cwbullet.Random2() / 255.); slope += spread_z * (pr_cwbullet.Random2() / 255.); } int damage = damageperbullet; if (!(flags & CBAF_NORANDOM)) damage *= ((pr_cabullet()%3)+1); AActor *puff = P_LineAttack(self, angle, range, slope, damage, NAME_Hitscan, pufftype, laflags); if (missile != nullptr && pufftype != nullptr) { double x = Spawnofs_xy * angle.Cos(); double y = Spawnofs_xy * angle.Sin(); DVector3 pos = self->Pos(); self->SetXYZ(self->Vec3Offset(x, y, 0.)); AActor *proj = P_SpawnMissileAngleZSpeed(self, self->Z() + self->GetBobOffset() + Spawnheight, missile, self->Angles.Yaw, 0, GetDefaultByType(missile)->Speed, self, false); self->SetXYZ(pos); if (proj) { bool temp = (puff == nullptr); if (!puff) { puff = P_LineAttack(self, angle, range, slope, 0, NAME_Hitscan, pufftype, laflags | LAF_NOINTERACT); } if (puff) { AimBulletMissile(proj, puff, flags, temp, true); } } } } } return 0; } //========================================================================== // // A fully customizable melee attack // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_CustomMeleeAttack) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT_DEF (damage); PARAM_SOUND_DEF (meleesound); PARAM_SOUND_DEF (misssound); PARAM_NAME_DEF (damagetype); PARAM_BOOL_DEF (bleed); if (damagetype == NAME_None) damagetype = NAME_Melee; // Melee is the default type if (!self->target) return 0; A_FaceTarget (self); if (self->CheckMeleeRange ()) { if (meleesound) S_Sound (self, CHAN_WEAPON, meleesound, 1, ATTN_NORM); int newdam = P_DamageMobj (self->target, self, self, damage, damagetype); if (bleed) P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); } else { if (misssound) S_Sound (self, CHAN_WEAPON, misssound, 1, ATTN_NORM); } return 0; } //========================================================================== // // A fully customizable combo attack // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_CustomComboAttack) { PARAM_SELF_PROLOGUE(AActor); PARAM_CLASS (ti, AActor); PARAM_FLOAT (spawnheight); PARAM_INT (damage); PARAM_SOUND_DEF (meleesound); PARAM_NAME_DEF (damagetype); PARAM_BOOL_DEF (bleed); if (!self->target) return 0; A_FaceTarget (self); if (self->CheckMeleeRange()) { if (damagetype == NAME_None) damagetype = NAME_Melee; // Melee is the default type if (meleesound) S_Sound (self, CHAN_WEAPON, meleesound, 1, ATTN_NORM); int newdam = P_DamageMobj (self->target, self, self, damage, damagetype); if (bleed) P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); } else if (ti) { // This seemingly senseless code is needed for proper aiming. double add = spawnheight + self->GetBobOffset() - 32; self->AddZ(add); AActor *missile = P_SpawnMissileXYZ (self->PosPlusZ(32.), self, self->target, ti, false); self->AddZ(-add); if (missile) { // automatic handling of seeker missiles if (missile->flags2 & MF2_SEEKERMISSILE) { missile->tracer = self->target; } P_CheckMissileSpawn(missile, self->radius); } } return 0; } //========================================================================== // // State jump function // //========================================================================== DEFINE_ACTION_FUNCTION(AStateProvider, A_JumpIfNoAmmo) { PARAM_ACTION_PROLOGUE(AStateProvider); PARAM_STATE_ACTION(jump); if (!ACTION_CALL_FROM_PSPRITE() || self->player->ReadyWeapon == nullptr) { ACTION_RETURN_STATE(NULL); } if (!self->player->ReadyWeapon->CheckAmmo(self->player->ReadyWeapon->bAltFire, false, true)) { ACTION_RETURN_STATE(jump); } ACTION_RETURN_STATE(NULL); } //========================================================================== // // An even more customizable hitscan attack // //========================================================================== enum FB_Flags { FBF_USEAMMO = 1, FBF_NORANDOM = 2, FBF_EXPLICITANGLE = 4, FBF_NOPITCH = 8, FBF_NOFLASH = 16, FBF_NORANDOMPUFFZ = 32, FBF_PUFFTARGET = 64, FBF_PUFFMASTER = 128, FBF_PUFFTRACER = 256, }; static void AimBulletMissile(AActor *proj, AActor *puff, int flags, bool temp, bool cba) { if (proj && puff) { if (proj) { // FAF_BOTTOM = 1 // Aim for the base of the puff as that's where blood puffs will spawn... roughly. A_Face(proj, puff, 0., 0., 0., 0., 1); proj->Vel3DFromAngle(proj->Angles.Pitch, proj->Speed); if (!temp) { if (cba) { if (flags & CBAF_PUFFTARGET) proj->target = puff; if (flags & CBAF_PUFFMASTER) proj->master = puff; if (flags & CBAF_PUFFTRACER) proj->tracer = puff; } else { if (flags & FBF_PUFFTARGET) proj->target = puff; if (flags & FBF_PUFFMASTER) proj->master = puff; if (flags & FBF_PUFFTRACER) proj->tracer = puff; } } } } if (puff && temp) { puff->Destroy(); } } DEFINE_ACTION_FUNCTION(AStateProvider, A_FireBullets) { PARAM_ACTION_PROLOGUE(AStateProvider); PARAM_ANGLE (spread_xy); PARAM_ANGLE (spread_z); PARAM_INT (numbullets); PARAM_INT (damageperbullet); PARAM_CLASS_DEF (pufftype, AActor); PARAM_INT_DEF (flags); PARAM_FLOAT_DEF (range); PARAM_CLASS_DEF (missile, AActor); PARAM_FLOAT_DEF (Spawnheight); PARAM_FLOAT_DEF (Spawnofs_xy); if (!self->player) return 0; player_t *player = self->player; AWeapon *weapon = player->ReadyWeapon; int i; DAngle bangle; DAngle bslope = 0.; int laflags = (flags & FBF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; if ((flags & FBF_USEAMMO) && weapon && ACTION_CALL_FROM_PSPRITE()) { if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return 0; // out of ammo } if (range == 0) range = PLAYERMISSILERANGE; if (!(flags & FBF_NOFLASH)) static_cast(self)->PlayAttacking2 (); if (!(flags & FBF_NOPITCH)) bslope = P_BulletSlope(self); bangle = self->Angles.Yaw; if (pufftype == NULL) pufftype = PClass::FindActor(NAME_BulletPuff); if (weapon != NULL) { S_Sound(self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); } if ((numbullets == 1 && !player->refire) || numbullets == 0) { int damage = damageperbullet; if (!(flags & FBF_NORANDOM)) damage *= ((pr_cwbullet()%3)+1); AActor *puff = P_LineAttack(self, bangle, range, bslope, damage, NAME_Hitscan, pufftype, laflags); if (missile != nullptr) { bool temp = false; DAngle ang = self->Angles.Yaw - 90; DVector2 ofs = ang.ToVector(Spawnofs_xy); AActor *proj = P_SpawnPlayerMissile(self, ofs.X, ofs.Y, Spawnheight, missile, bangle, nullptr, nullptr, false, true); if (proj) { if (!puff) { temp = true; puff = P_LineAttack(self, bangle, range, bslope, 0, NAME_Hitscan, pufftype, laflags | LAF_NOINTERACT); } AimBulletMissile(proj, puff, flags, temp, false); } } } else { if (numbullets < 0) numbullets = 1; for (i = 0; i < numbullets; i++) { DAngle angle = bangle; DAngle slope = bslope; if (flags & FBF_EXPLICITANGLE) { angle += spread_xy; slope += spread_z; } else { angle += spread_xy * (pr_cwbullet.Random2() / 255.); slope += spread_z * (pr_cwbullet.Random2() / 255.); } int damage = damageperbullet; if (!(flags & FBF_NORANDOM)) damage *= ((pr_cwbullet()%3)+1); AActor *puff = P_LineAttack(self, angle, range, slope, damage, NAME_Hitscan, pufftype, laflags); if (missile != nullptr) { bool temp = false; DAngle ang = self->Angles.Yaw - 90; DVector2 ofs = ang.ToVector(Spawnofs_xy); AActor *proj = P_SpawnPlayerMissile(self, ofs.X, ofs.Y, Spawnheight, missile, angle, nullptr, nullptr, false, true); if (proj) { if (!puff) { temp = true; puff = P_LineAttack(self, angle, range, slope, 0, NAME_Hitscan, pufftype, laflags | LAF_NOINTERACT); } AimBulletMissile(proj, puff, flags, temp, false); } } } } return 0; } //========================================================================== // // A_FireProjectile // //========================================================================== enum FP_Flags { FPF_AIMATANGLE = 1, FPF_TRANSFERTRANSLATION = 2, FPF_NOAUTOAIM = 4, }; DEFINE_ACTION_FUNCTION(AStateProvider, A_FireProjectile) { PARAM_ACTION_PROLOGUE(AStateProvider); PARAM_CLASS (ti, AActor); PARAM_ANGLE_DEF (angle); PARAM_BOOL_DEF (useammo); PARAM_FLOAT_DEF (spawnofs_xy); PARAM_FLOAT_DEF (spawnheight); PARAM_INT_DEF (flags); PARAM_ANGLE_DEF (pitch); if (!self->player) ACTION_RETURN_OBJECT(nullptr); player_t *player = self->player; AWeapon *weapon = player->ReadyWeapon; FTranslatedLineTarget t; // Only use ammo if called from a weapon if (useammo && ACTION_CALL_FROM_PSPRITE() && weapon) { if (!weapon->DepleteAmmo(weapon->bAltFire, true)) ACTION_RETURN_OBJECT(nullptr); // out of ammo } if (ti) { DAngle ang = self->Angles.Yaw - 90; DVector2 ofs = ang.ToVector(spawnofs_xy); DAngle shootangle = self->Angles.Yaw; if (flags & FPF_AIMATANGLE) shootangle += angle; // Temporarily adjusts the pitch DAngle saved_player_pitch = self->Angles.Pitch; self->Angles.Pitch -= pitch; AActor * misl=P_SpawnPlayerMissile (self, ofs.X, ofs.Y, spawnheight, ti, shootangle, &t, NULL, false, (flags & FPF_NOAUTOAIM) != 0); self->Angles.Pitch = saved_player_pitch; // automatic handling of seeker missiles if (misl) { if (flags & FPF_TRANSFERTRANSLATION) misl->Translation = self->Translation; if (t.linetarget && !t.unlinked && (misl->flags2 & MF2_SEEKERMISSILE)) misl->tracer = t.linetarget; if (!(flags & FPF_AIMATANGLE)) { // This original implementation is to aim straight ahead and then offset // the angle from the resulting direction. misl->Angles.Yaw += angle; misl->VelFromAngle(misl->VelXYToSpeed()); } } ACTION_RETURN_OBJECT(misl); } ACTION_RETURN_OBJECT(nullptr); } //========================================================================== // // A_CustomPunch // // Berserk is not handled here. That can be done with A_CheckIfInventory // //========================================================================== enum { CPF_USEAMMO = 1, CPF_DAGGER = 2, CPF_PULLIN = 4, CPF_NORANDOMPUFFZ = 8, CPF_NOTURN = 16, CPF_STEALARMOR = 32, }; DEFINE_ACTION_FUNCTION(AStateProvider, A_CustomPunch) { PARAM_ACTION_PROLOGUE(AStateProvider); PARAM_INT (damage); PARAM_BOOL_DEF (norandom); PARAM_INT_DEF (flags); PARAM_CLASS_DEF (pufftype, AActor); PARAM_FLOAT_DEF (range); PARAM_FLOAT_DEF (lifesteal); PARAM_INT_DEF (lifestealmax); PARAM_CLASS_DEF (armorbonustype, AActor); PARAM_SOUND_DEF (MeleeSound); PARAM_SOUND_DEF (MissSound); if (!self->player) return 0; player_t *player = self->player; AWeapon *weapon = player->ReadyWeapon; DAngle angle; DAngle pitch; FTranslatedLineTarget t; int actualdamage; if (!norandom) damage *= pr_cwpunch() % 8 + 1; angle = self->Angles.Yaw + pr_cwpunch.Random2() * (5.625 / 256); if (range == 0) range = DEFMELEERANGE; pitch = P_AimLineAttack (self, angle, range, &t); // only use ammo when actually hitting something! if ((flags & CPF_USEAMMO) && t.linetarget && weapon && ACTION_CALL_FROM_PSPRITE()) { if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return 0; // out of ammo } if (pufftype == NULL) pufftype = PClass::FindActor(NAME_BulletPuff); int puffFlags = LAF_ISMELEEATTACK | ((flags & CPF_NORANDOMPUFFZ) ? LAF_NORANDOMPUFFZ : 0); P_LineAttack (self, angle, range, pitch, damage, NAME_Melee, pufftype, puffFlags, &t, &actualdamage); if (!t.linetarget) { if (MissSound) S_Sound(self, CHAN_WEAPON, MissSound, 1, ATTN_NORM); } else { if (lifesteal > 0 && !(t.linetarget->flags5 & MF5_DONTDRAIN)) { if (flags & CPF_STEALARMOR) { if (armorbonustype == NULL) { armorbonustype = PClass::FindActor("ArmorBonus"); } if (armorbonustype != NULL) { auto armorbonus = Spawn(armorbonustype); armorbonus->IntVar(NAME_SaveAmount) *= int(actualdamage * lifesteal); if (lifestealmax > 0) armorbonus->IntVar("MaxSaveAmount") = lifestealmax; armorbonus->flags |= MF_DROPPED; armorbonus->ClearCounters(); if (!static_cast(armorbonus)->CallTryPickup(self)) { armorbonus->Destroy (); } } } else { P_GiveBody (self, int(actualdamage * lifesteal), lifestealmax); } } if (weapon != NULL) { if (MeleeSound) S_Sound(self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM); else S_Sound (self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); } if (!(flags & CPF_NOTURN)) { // turn to face target self->Angles.Yaw = t.angleFromSource; } if (flags & CPF_PULLIN) self->flags |= MF_JUSTATTACKED; if (flags & CPF_DAGGER) P_DaggerAlert (self, t.linetarget); } return 0; } //========================================================================== // // customizable railgun attack function // //========================================================================== DEFINE_ACTION_FUNCTION(AStateProvider, A_RailAttack) { PARAM_ACTION_PROLOGUE(AStateProvider); PARAM_INT (damage); PARAM_INT_DEF (spawnofs_xy); PARAM_BOOL_DEF (useammo); PARAM_COLOR_DEF (color1); PARAM_COLOR_DEF (color2); PARAM_INT_DEF (flags); PARAM_FLOAT_DEF (maxdiff); PARAM_CLASS_DEF (pufftype, AActor); PARAM_ANGLE_DEF (spread_xy); PARAM_ANGLE_DEF (spread_z); PARAM_FLOAT_DEF (range) ; PARAM_INT_DEF (duration); PARAM_FLOAT_DEF (sparsity); PARAM_FLOAT_DEF (driftspeed); PARAM_CLASS_DEF (spawnclass, AActor); PARAM_FLOAT_DEF (spawnofs_z); PARAM_INT_DEF (SpiralOffset); PARAM_INT_DEF (limit); if (range == 0) range = 8192; if (sparsity == 0) sparsity=1.0; if (self->player == NULL) return 0; AWeapon *weapon = self->player->ReadyWeapon; // only use ammo when actually hitting something! if (useammo && weapon != NULL && ACTION_CALL_FROM_PSPRITE()) { if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return 0; // out of ammo } if (!(flags & RAF_EXPLICITANGLE)) { spread_xy = spread_xy * pr_crailgun.Random2() / 255; spread_z = spread_z * pr_crailgun.Random2() / 255; } FRailParams p; p.source = self; p.damage = damage; p.offset_xy = spawnofs_xy; p.offset_z = spawnofs_z; p.color1 = color1; p.color2 = color2; p.maxdiff = maxdiff; p.flags = flags; p.puff = pufftype; p.angleoffset = spread_xy; p.pitchoffset = spread_z; p.distance = range; p.duration = duration; p.sparsity = sparsity; p.drift = driftspeed; p.spawnclass = spawnclass; p.SpiralOffset = SpiralOffset; p.limit = limit; P_RailAttack(&p); return 0; } //========================================================================== // // also for monsters // //========================================================================== enum { CRF_DONTAIM = 0, CRF_AIMPARALLEL = 1, CRF_AIMDIRECT = 2, CRF_EXPLICITANGLE = 4, }; DEFINE_ACTION_FUNCTION(AActor, A_CustomRailgun) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT (damage); PARAM_INT_DEF (spawnofs_xy) PARAM_COLOR_DEF (color1) PARAM_COLOR_DEF (color2) PARAM_INT_DEF (flags) PARAM_INT_DEF (aim) PARAM_FLOAT_DEF (maxdiff) PARAM_CLASS_DEF (pufftype, AActor) PARAM_ANGLE_DEF (spread_xy) PARAM_ANGLE_DEF (spread_z) PARAM_FLOAT_DEF (range) PARAM_INT_DEF (duration) PARAM_FLOAT_DEF (sparsity) PARAM_FLOAT_DEF (driftspeed) PARAM_CLASS_DEF (spawnclass, AActor) PARAM_FLOAT_DEF (spawnofs_z) PARAM_INT_DEF (SpiralOffset) PARAM_INT_DEF (limit) PARAM_FLOAT_DEF (veleffect) if (range == 0) range = 8192.; if (sparsity == 0) sparsity = 1; FTranslatedLineTarget t; DVector3 savedpos = self->Pos(); DAngle saved_angle = self->Angles.Yaw; DAngle saved_pitch = self->Angles.Pitch; if (aim && self->target == NULL) { return 0; } // [RH] Andy Baker's stealth monsters if (self->flags & MF_STEALTH) { self->visdir = 1; } self->flags &= ~MF_AMBUSH; if (aim) { self->Angles.Yaw = self->AngleTo(self->target); } self->Angles.Pitch = P_AimLineAttack (self, self->Angles.Yaw, MISSILERANGE, &t, 60., 0, aim ? self->target : NULL); if (t.linetarget == NULL && aim) { // We probably won't hit the target, but aim at it anyway so we don't look stupid. DVector2 xydiff = self->Vec2To(self->target); double zdiff = self->target->Center() - self->Center() - self->Floorclip; self->Angles.Pitch = -VecToAngle(xydiff.Length(), zdiff); } // Let the aim trail behind the player if (aim) { saved_angle = self->Angles.Yaw = self->AngleTo(self->target, -self->target->Vel.X * veleffect, -self->target->Vel.Y * veleffect); if (aim == CRF_AIMDIRECT) { // Tricky: We must offset to the angle of the current position // but then change the angle again to ensure proper aim. self->SetXY(self->Vec2Offset( spawnofs_xy * self->Angles.Yaw.Cos(), spawnofs_xy * self->Angles.Yaw.Sin())); spawnofs_xy = 0; self->Angles.Yaw = self->AngleTo(self->target,- self->target->Vel.X * veleffect, -self->target->Vel.Y * veleffect); } if (self->target->flags & MF_SHADOW) { DAngle rnd = pr_crailgun.Random2() * (45. / 256.); self->Angles.Yaw += rnd; } } if (!(flags & CRF_EXPLICITANGLE)) { spread_xy = spread_xy * pr_crailgun.Random2() / 255; spread_z = spread_z * pr_crailgun.Random2() / 255; } FRailParams p; p.source = self; p.damage = damage; p.offset_xy = spawnofs_xy; p.offset_z = spawnofs_z; p.color1 = color1; p.color2 = color2; p.maxdiff = maxdiff; p.flags = flags; p.puff = pufftype; p.angleoffset = spread_xy; p.pitchoffset = spread_z; p.distance = range; p.duration = duration; p.sparsity = sparsity; p.drift = driftspeed; p.spawnclass = spawnclass; p.SpiralOffset = SpiralOffset; p.limit = 0; P_RailAttack(&p); self->SetXYZ(savedpos); self->Angles.Yaw = saved_angle; self->Angles.Pitch = saved_pitch; return 0; } //=========================================================================== // // DoGiveInventory // //=========================================================================== static bool DoGiveInventory(AActor *receiver, bool orresult, VM_ARGS) { int paramnum = 0; PARAM_CLASS (mi, AInventory); PARAM_INT_DEF (amount) if (!orresult) { PARAM_INT_DEF(setreceiver) receiver = COPY_AAPTR(receiver, setreceiver); } if (receiver == NULL) { // If there's nothing to receive it, it's obviously a fail, right? return false; } // Owned inventory items cannot own anything because their Inventory pointer is repurposed for the owner's linked list. if (receiver->IsKindOf(RUNTIME_CLASS(AInventory)) && static_cast(receiver)->Owner != nullptr) { return false; } if (amount <= 0) { amount = 1; } if (mi) { AInventory *item = static_cast(Spawn(mi)); if (item == NULL) { return false; } if (item->IsKindOf(PClass::FindActor(NAME_Health))) { item->Amount *= amount; } else { item->Amount = amount; } item->flags |= MF_DROPPED; item->ClearCounters(); if (!item->CallTryPickup(receiver)) { item->Destroy(); return false; } else { return true; } } return false; } DEFINE_ACTION_FUNCTION(AActor, A_GiveInventory) { PARAM_SELF_PROLOGUE(AActor); ACTION_RETURN_BOOL(DoGiveInventory(self, false, VM_ARGS_NAMES)); } DEFINE_ACTION_FUNCTION(AActor, A_GiveToTarget) { PARAM_SELF_PROLOGUE(AActor); ACTION_RETURN_BOOL(DoGiveInventory(self->target, false, VM_ARGS_NAMES)); } DEFINE_ACTION_FUNCTION(AActor, A_GiveToChildren) { PARAM_SELF_PROLOGUE(AActor); TThinkerIterator it; AActor *mo; int count = 0; while ((mo = it.Next())) { if (mo->master == self) { count += DoGiveInventory(mo, true, VM_ARGS_NAMES); } } ACTION_RETURN_INT(count); } DEFINE_ACTION_FUNCTION(AActor, A_GiveToSiblings) { PARAM_SELF_PROLOGUE(AActor); TThinkerIterator it; AActor *mo; int count = 0; if (self->master != NULL) { while ((mo = it.Next())) { if (mo->master == self->master && mo != self) { count += DoGiveInventory(mo, true, VM_ARGS_NAMES); } } } ACTION_RETURN_INT(count); } //=========================================================================== // // A_SetInventory // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_SetInventory) { PARAM_SELF_PROLOGUE(AActor); PARAM_CLASS(itemtype, AInventory); PARAM_INT(amount); PARAM_INT_DEF(ptr); PARAM_BOOL_DEF(beyondMax); bool res = false; if (itemtype == nullptr) { ACTION_RETURN_BOOL(false); } AActor *mobj = COPY_AAPTR(self, ptr); if (mobj == nullptr) { ACTION_RETURN_BOOL(false); } // Do not run this function on voodoo dolls because the way they transfer the inventory to the player will not work with the code below. if (mobj->player != nullptr) { mobj = mobj->player->mo; } AInventory *item = mobj->FindInventory(itemtype); if (item != nullptr) { // A_SetInventory sets the absolute amount. // Subtract or set the appropriate amount as necessary. if (amount == item->Amount) { // Nothing was changed. ACTION_RETURN_BOOL(false); } else if (amount <= 0) { //Remove it all. res = (mobj->TakeInventory(itemtype, item->Amount, true, false)); ACTION_RETURN_BOOL(res); } else if (amount < item->Amount) { int amt = abs(item->Amount - amount); res = (mobj->TakeInventory(itemtype, amt, true, false)); ACTION_RETURN_BOOL(res); } else { item->Amount = (beyondMax ? amount : clamp(amount, 0, item->MaxAmount)); ACTION_RETURN_BOOL(true); } } else { if (amount <= 0) { ACTION_RETURN_BOOL(false); } item = static_cast(Spawn(itemtype)); if (item == nullptr) { ACTION_RETURN_BOOL(false); } else { item->Amount = amount; item->flags |= MF_DROPPED; item->ItemFlags |= IF_IGNORESKILL; item->ClearCounters(); if (!item->CallTryPickup(mobj)) { item->Destroy(); ACTION_RETURN_BOOL(false); } ACTION_RETURN_BOOL(true); } } ACTION_RETURN_BOOL(false); } //=========================================================================== // // A_TakeInventory // //=========================================================================== enum { TIF_NOTAKEINFINITE = 1, }; bool DoTakeInventory(AActor *receiver, bool orresult, VM_ARGS) { int paramnum = 0; PARAM_CLASS (itemtype, AInventory); PARAM_INT_DEF (amount); PARAM_INT_DEF (flags); if (itemtype == NULL) { return false; } if (!orresult) { PARAM_INT_DEF(setreceiver); receiver = COPY_AAPTR(receiver, setreceiver); } if (receiver == NULL) { return false; } return receiver->TakeInventory(itemtype, amount, true, (flags & TIF_NOTAKEINFINITE) != 0); } DEFINE_ACTION_FUNCTION(AActor, A_TakeInventory) { PARAM_SELF_PROLOGUE(AActor); ACTION_RETURN_BOOL(DoTakeInventory(self, false, VM_ARGS_NAMES)); } DEFINE_ACTION_FUNCTION(AActor, A_TakeFromTarget) { PARAM_SELF_PROLOGUE(AActor); ACTION_RETURN_BOOL(DoTakeInventory(self->target, false, VM_ARGS_NAMES)); } DEFINE_ACTION_FUNCTION(AActor, A_TakeFromChildren) { PARAM_SELF_PROLOGUE(AActor); TThinkerIterator it; AActor *mo; int count = 0; while ((mo = it.Next())) { if (mo->master == self) { count += DoTakeInventory(mo, true, VM_ARGS_NAMES); } } ACTION_RETURN_INT(count); } DEFINE_ACTION_FUNCTION(AActor, A_TakeFromSiblings) { PARAM_SELF_PROLOGUE(AActor); TThinkerIterator it; AActor *mo; int count = 0; if (self->master != NULL) { while ((mo = it.Next())) { if (mo->master == self->master && mo != self) { count += DoTakeInventory(mo, true, VM_ARGS_NAMES); } } } ACTION_RETURN_INT(count); } //=========================================================================== // // Common code for A_SpawnItem and A_SpawnItemEx // //=========================================================================== enum SIX_Flags { SIXF_TRANSFERTRANSLATION = 0x00000001, SIXF_ABSOLUTEPOSITION = 0x00000002, SIXF_ABSOLUTEANGLE = 0x00000004, SIXF_ABSOLUTEVELOCITY = 0x00000008, SIXF_SETMASTER = 0x00000010, SIXF_NOCHECKPOSITION = 0x00000020, SIXF_TELEFRAG = 0x00000040, SIXF_CLIENTSIDE = 0x00000080, // only used by Skulldronum SIXF_TRANSFERAMBUSHFLAG = 0x00000100, SIXF_TRANSFERPITCH = 0x00000200, SIXF_TRANSFERPOINTERS = 0x00000400, SIXF_USEBLOODCOLOR = 0x00000800, SIXF_CLEARCALLERTID = 0x00001000, SIXF_MULTIPLYSPEED = 0x00002000, SIXF_TRANSFERSCALE = 0x00004000, SIXF_TRANSFERSPECIAL = 0x00008000, SIXF_CLEARCALLERSPECIAL = 0x00010000, SIXF_TRANSFERSTENCILCOL = 0x00020000, SIXF_TRANSFERALPHA = 0x00040000, SIXF_TRANSFERRENDERSTYLE = 0x00080000, SIXF_SETTARGET = 0x00100000, SIXF_SETTRACER = 0x00200000, SIXF_NOPOINTERS = 0x00400000, SIXF_ORIGINATOR = 0x00800000, SIXF_TRANSFERSPRITEFRAME = 0x01000000, SIXF_TRANSFERROLL = 0x02000000, SIXF_ISTARGET = 0x04000000, SIXF_ISMASTER = 0x08000000, SIXF_ISTRACER = 0x10000000, }; static bool InitSpawnedItem(AActor *self, AActor *mo, int flags) { if (mo == NULL) { return false; } AActor *originator = self; if (!(mo->flags2 & MF2_DONTTRANSLATE)) { if (flags & SIXF_TRANSFERTRANSLATION) { mo->Translation = self->Translation; } else if (flags & SIXF_USEBLOODCOLOR) { // [XA] Use the spawning actor's BloodColor to translate the newly-spawned object. PalEntry bloodcolor = self->GetBloodColor(); mo->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a); } } if (flags & SIXF_TRANSFERPOINTERS) { mo->target = self->target; mo->master = self->master; // This will be overridden later if SIXF_SETMASTER is set mo->tracer = self->tracer; } mo->Angles.Yaw = self->Angles.Yaw; if (flags & SIXF_TRANSFERPITCH) { mo->Angles.Pitch = self->Angles.Pitch; } if (!(flags & SIXF_ORIGINATOR)) { while (originator && originator->isMissile()) { originator = originator->target; } } if (flags & SIXF_TELEFRAG) { P_TeleportMove(mo, mo->Pos(), true); // This is needed to ensure consistent behavior. // Otherwise it will only spawn if nothing gets telefragged flags |= SIXF_NOCHECKPOSITION; } if (mo->flags3 & MF3_ISMONSTER) { if (!(flags & SIXF_NOCHECKPOSITION) && !P_TestMobjLocation(mo)) { // The monster is blocked so don't spawn it at all! mo->ClearCounters(); mo->Destroy(); return false; } else if (originator && !(flags & SIXF_NOPOINTERS)) { if (originator->flags3 & MF3_ISMONSTER) { // If this is a monster transfer all friendliness information mo->CopyFriendliness(originator, true); } else if (originator->player) { // A player always spawns a monster friendly to him mo->flags |= MF_FRIENDLY; mo->SetFriendPlayer(originator->player); AActor * attacker=originator->player->attacker; if (attacker) { if (!(attacker->flags&MF_FRIENDLY) || (deathmatch && attacker->FriendPlayer!=0 && attacker->FriendPlayer!=mo->FriendPlayer)) { // Target the monster which last attacked the player mo->LastHeard = mo->target = attacker; } } } } } else if (!(flags & SIXF_TRANSFERPOINTERS)) { // If this is a missile or something else set the target to the originator mo->target = originator ? originator : self; } if (flags & SIXF_NOPOINTERS) { //[MC]Intentionally eliminate pointers. Overrides TRANSFERPOINTERS, but is overridden by SETMASTER/TARGET/TRACER. mo->LastHeard = NULL; //Sanity check. mo->target = NULL; mo->master = NULL; mo->tracer = NULL; } if (flags & SIXF_SETMASTER) { // don't let it attack you (optional)! mo->master = originator; } if (flags & SIXF_SETTARGET) { mo->target = originator; } if (flags & SIXF_SETTRACER) { mo->tracer = originator; } if (flags & SIXF_TRANSFERSCALE) { mo->Scale = self->Scale; } if (flags & SIXF_TRANSFERAMBUSHFLAG) { mo->flags = (mo->flags & ~MF_AMBUSH) | (self->flags & MF_AMBUSH); } if (flags & SIXF_CLEARCALLERTID) { self->RemoveFromHash(); self->tid = 0; } if (flags & SIXF_TRANSFERSPECIAL) { mo->special = self->special; memcpy(mo->args, self->args, sizeof(self->args)); } if (flags & SIXF_CLEARCALLERSPECIAL) { self->special = 0; memset(self->args, 0, sizeof(self->args)); } if (flags & SIXF_TRANSFERSTENCILCOL) { mo->fillcolor = self->fillcolor; } if (flags & SIXF_TRANSFERALPHA) { mo->Alpha = self->Alpha; } if (flags & SIXF_TRANSFERRENDERSTYLE) { mo->RenderStyle = self->RenderStyle; } if (flags & SIXF_TRANSFERSPRITEFRAME) { mo->sprite = self->sprite; mo->frame = self->frame; } if (flags & SIXF_TRANSFERROLL) { mo->Angles.Roll = self->Angles.Roll; } if (flags & SIXF_ISTARGET) { self->target = mo; } if (flags & SIXF_ISMASTER) { self->master = mo; } if (flags & SIXF_ISTRACER) { self->tracer = mo; } return true; } //=========================================================================== // // A_SpawnItem // // Spawns an item in front of the caller like Heretic's time bomb // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_SpawnItem) { PARAM_ACTION_PROLOGUE(AActor); PARAM_CLASS_DEF (missile, AActor) PARAM_FLOAT_DEF (distance) PARAM_FLOAT_DEF (zheight) PARAM_BOOL_DEF (useammo) PARAM_BOOL_DEF (transfer_translation); if (numret > 1) ret[1].SetPointer(nullptr, ATAG_OBJECT); if (missile == NULL) { if (numret > 0) ret[0].SetInt(false); return MIN(numret, 2); } // Don't spawn monsters if this actor has been massacred if (self->DamageType == NAME_Massacre && (GetDefaultByType(missile)->flags3 & MF3_ISMONSTER)) { if (numret > 0) ret[0].SetInt(true); return MIN(numret, 2); } if (ACTION_CALL_FROM_PSPRITE()) { // Used from a weapon, so use some ammo AWeapon *weapon = self->player->ReadyWeapon; if (weapon == NULL) { if (numret > 0) ret[0].SetInt(true); return MIN(numret, 2); } if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) { if (numret > 0) ret[0].SetInt(true); return MIN(numret, 2); } } AActor *mo = Spawn( missile, self->Vec3Angle(distance, self->Angles.Yaw, -self->Floorclip + self->GetBobOffset() + zheight), ALLOW_REPLACE); int flags = (transfer_translation ? SIXF_TRANSFERTRANSLATION : 0) + (useammo ? SIXF_SETMASTER : 0); bool res = InitSpawnedItem(self, mo, flags); // for an inventory item's use state if (numret > 0) ret[0].SetInt(res); if (numret > 1) ret[1].SetPointer(mo, ATAG_OBJECT); return MIN(numret, 2); } //=========================================================================== // // A_SpawnItemEx // // Enhanced spawning function // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_SpawnItemEx) { PARAM_SELF_PROLOGUE(AActor); PARAM_CLASS (missile, AActor); PARAM_FLOAT_DEF (xofs) PARAM_FLOAT_DEF (yofs) PARAM_FLOAT_DEF (zofs) PARAM_FLOAT_DEF (xvel) PARAM_FLOAT_DEF (yvel) PARAM_FLOAT_DEF (zvel) PARAM_ANGLE_DEF (angle) PARAM_INT_DEF (flags) PARAM_INT_DEF (chance) PARAM_INT_DEF (tid) if (numret > 1) ret[1].SetPointer(nullptr, ATAG_OBJECT); if (missile == NULL) { if (numret > 0) ret[0].SetInt(false); return MIN(numret, 2); } if (chance > 0 && pr_spawnitemex() < chance) { if (numret > 0) ret[0].SetInt(true); return MIN(numret, 2); } // Don't spawn monsters if this actor has been massacred if (self->DamageType == NAME_Massacre && (GetDefaultByType(missile)->flags3 & MF3_ISMONSTER)) { if (numret > 0) ret[0].SetInt(true); return MIN(numret, 2); } DVector2 pos; if (!(flags & SIXF_ABSOLUTEANGLE)) { angle += self->Angles.Yaw; } double s = angle.Sin(); double c = angle.Cos(); if (flags & SIXF_ABSOLUTEPOSITION) { pos = self->Vec2Offset(xofs, yofs); } else { // in relative mode negative y values mean 'left' and positive ones mean 'right' // This is the inverse orientation of the absolute mode! pos = self->Vec2Offset(xofs * c + yofs * s, xofs * s - yofs*c); } if (!(flags & SIXF_ABSOLUTEVELOCITY)) { // Same orientation issue here! double newxvel = xvel * c + yvel * s; yvel = xvel * s - yvel * c; xvel = newxvel; } AActor *mo = Spawn(missile, DVector3(pos, self->Z() - self->Floorclip + self->GetBobOffset() + zofs), ALLOW_REPLACE); bool res = InitSpawnedItem(self, mo, flags); if (res) { if (tid != 0) { assert(mo->tid == 0); mo->tid = tid; mo->AddToHash(); } mo->Vel = {xvel, yvel, zvel}; if (flags & SIXF_MULTIPLYSPEED) { mo->Vel *= mo->Speed; } mo->Angles.Yaw = angle; } if (numret > 0) ret[0].SetInt(res); if (numret > 1) ret[1].SetPointer(mo, ATAG_OBJECT); return MIN(numret, 2); } //=========================================================================== // // A_ThrowGrenade // // Throws a grenade (like Hexen's fighter flechette) // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_ThrowGrenade) { PARAM_ACTION_PROLOGUE(AActor); PARAM_CLASS (missile, AActor); PARAM_FLOAT_DEF (zheight) PARAM_FLOAT_DEF (xyvel) PARAM_FLOAT_DEF (zvel) PARAM_BOOL_DEF (useammo) if (numret > 1) ret[1].SetPointer(nullptr, ATAG_OBJECT); if (missile == NULL) { if (numret > 0) ret[0].SetInt(false); return MIN(numret, 2); } if (ACTION_CALL_FROM_PSPRITE()) { // Used from a weapon, so use some ammo AWeapon *weapon = self->player->ReadyWeapon; if (weapon == NULL) { if (numret > 0) ret[0].SetInt(true); return MIN(numret, 2); } if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) { if (numret > 0) ret[0].SetInt(true); return MIN(numret, 2); } } AActor *bo; bo = Spawn(missile, self->PosPlusZ(-self->Floorclip + self->GetBobOffset() + zheight + 35 + (self->player? self->player->crouchoffset : 0.)), ALLOW_REPLACE); if (bo) { P_PlaySpawnSound(bo, self); if (xyvel != 0) bo->Speed = xyvel; bo->Angles.Yaw = self->Angles.Yaw + (((pr_grenade()&7) - 4) * (360./256.)); DAngle pitch = -self->Angles.Pitch; DAngle angle = bo->Angles.Yaw; // There are two vectors we are concerned about here: xy and z. We rotate // them separately according to the shooter's pitch and then sum them to // get the final velocity vector to shoot with. double xy_xyscale = bo->Speed * pitch.Cos(); double xy_velz = bo->Speed * pitch.Sin(); double xy_velx = xy_xyscale * angle.Cos(); double xy_vely = xy_xyscale * angle.Sin(); pitch = self->Angles.Pitch; double z_xyscale = zvel * pitch.Sin(); double z_velz = zvel * pitch.Cos(); double z_velx = z_xyscale * angle.Cos(); double z_vely = z_xyscale * angle.Sin(); bo->Vel.X = xy_velx + z_velx + self->Vel.X / 2; bo->Vel.Y = xy_vely + z_vely + self->Vel.Y / 2; bo->Vel.Z = xy_velz + z_velz; bo->target = self; if (!P_CheckMissileSpawn(bo, self->radius)) bo = nullptr; if (numret > 0) ret[0].SetInt(true); if (numret > 1) ret[1].SetPointer(bo, ATAG_OBJECT); return MIN(numret, 2); } else { if (numret > 0) ret[0].SetInt(false); return MIN(numret, 2); } } //=========================================================================== // // A_Recoil // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_Recoil) { PARAM_SELF_PROLOGUE(AActor); PARAM_FLOAT(xyvel); self->Thrust(self->Angles.Yaw + 180., xyvel); return 0; } //=========================================================================== // // A_SelectWeapon // //=========================================================================== enum SW_Flags { SWF_SELECTPRIORITY = 1, }; DEFINE_ACTION_FUNCTION(AActor, A_SelectWeapon) { PARAM_SELF_PROLOGUE(AActor); PARAM_CLASS(cls, AWeapon); PARAM_INT_DEF(flags); bool selectPriority = !!(flags & SWF_SELECTPRIORITY); if ((!selectPriority && cls == NULL) || self->player == NULL) { ACTION_RETURN_BOOL(false); } AWeapon *weaponitem = static_cast(self->FindInventory(cls)); if (weaponitem != NULL && weaponitem->IsKindOf(RUNTIME_CLASS(AWeapon))) { if (self->player->ReadyWeapon != weaponitem) { self->player->PendingWeapon = weaponitem; } ACTION_RETURN_BOOL(true); } else if (selectPriority) { // [XA] if the named weapon cannot be found (or is a dummy like 'None'), // select the next highest priority weapon. This is basically // the same as A_CheckReload minus the ammo check. Handy. self->player->mo->PickNewWeapon(NULL); ACTION_RETURN_BOOL(true); } else { ACTION_RETURN_BOOL(false); } } //=========================================================================== // // A_Print // //=========================================================================== EXTERN_CVAR(Float, con_midtime) DEFINE_ACTION_FUNCTION(AActor, A_Print) { PARAM_SELF_PROLOGUE(AActor); PARAM_STRING (text); PARAM_FLOAT_DEF (time); PARAM_NAME_DEF (fontname); if (text[0] == '$') text = GStrings(&text[1]); if (self->CheckLocalView (consoleplayer) || (self->target != NULL && self->target->CheckLocalView (consoleplayer))) { float saved = con_midtime; FFont *font = NULL; if (fontname != NAME_None) { font = V_GetFont(fontname); } if (time > 0) { con_midtime = float(time); } FString formatted = strbin1(text); C_MidPrint(font != NULL ? font : SmallFont, formatted.GetChars()); con_midtime = saved; } return 0; } //=========================================================================== // // A_PrintBold // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_PrintBold) { PARAM_SELF_PROLOGUE(AActor); PARAM_STRING (text); PARAM_FLOAT_DEF (time); PARAM_NAME_DEF (fontname); float saved = con_midtime; FFont *font = NULL; if (text[0] == '$') text = GStrings(&text[1]); if (fontname != NAME_None) { font = V_GetFont(fontname); } if (time > 0) { con_midtime = float(time); } FString formatted = strbin1(text); C_MidPrintBold(font != NULL ? font : SmallFont, formatted.GetChars()); con_midtime = saved; return 0; } //=========================================================================== // // A_Log // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_Log) { PARAM_SELF_PROLOGUE(AActor); PARAM_STRING(text); PARAM_BOOL_DEF(local); if (local && !self->CheckLocalView(consoleplayer)) return 0; if (text[0] == '$') text = GStrings(&text[1]); FString formatted = strbin1(text); Printf("%s\n", formatted.GetChars()); return 0; } //========================================================================= // // A_LogInt // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_LogInt) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT(num); PARAM_BOOL_DEF(local); if (local && !self->CheckLocalView(consoleplayer)) return 0; Printf("%d\n", num); return 0; } //========================================================================= // // A_LogFloat // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_LogFloat) { PARAM_SELF_PROLOGUE(AActor); PARAM_FLOAT(num); PARAM_BOOL_DEF(local); if (local && !self->CheckLocalView(consoleplayer)) return 0; IGNORE_FORMAT_PRE Printf("%H\n", num); IGNORE_FORMAT_POST return 0; } //=========================================================================== // // A_SetTranslucent // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_SetTranslucent) { PARAM_SELF_PROLOGUE(AActor); PARAM_FLOAT (alpha); PARAM_INT_DEF (mode); mode = mode == 0 ? STYLE_Translucent : mode == 2 ? STYLE_Fuzzy : STYLE_Add; self->RenderStyle.Flags &= ~STYLEF_Alpha1; self->Alpha = clamp(alpha, 0., 1.); self->RenderStyle = ERenderStyle(mode); return 0; } //=========================================================================== // // A_SetRenderStyle // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_SetRenderStyle) { PARAM_SELF_PROLOGUE(AActor); PARAM_FLOAT(alpha); PARAM_INT_DEF(mode); self->Alpha = clamp(alpha, 0., 1.); self->RenderStyle = ERenderStyle(mode); return 0; } //=========================================================================== // // A_FadeIn // // Fades the actor in // //=========================================================================== enum FadeFlags { FTF_REMOVE = 1 << 0, FTF_CLAMP = 1 << 1, }; DEFINE_ACTION_FUNCTION(AActor, A_FadeIn) { PARAM_SELF_PROLOGUE(AActor); PARAM_FLOAT_DEF(reduce); PARAM_INT_DEF(flags); if (reduce == 0) { reduce = 0.1; } self->RenderStyle.Flags &= ~STYLEF_Alpha1; self->Alpha += reduce; if (self->Alpha >= 1.) { if (flags & FTF_CLAMP) { self->Alpha = 1.; } if (flags & FTF_REMOVE) { P_RemoveThing(self); } } return 0; } //=========================================================================== // // A_FadeOut // // fades the actor out and destroys it when done // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_FadeOut) { PARAM_SELF_PROLOGUE(AActor); PARAM_FLOAT_DEF(reduce); PARAM_INT_DEF(flags); if (reduce == 0) { reduce = 0.1; } self->RenderStyle.Flags &= ~STYLEF_Alpha1; self->Alpha -= reduce; if (self->Alpha <= 0) { if (flags & FTF_CLAMP) { self->Alpha = 0; } if (flags & FTF_REMOVE) { P_RemoveThing(self); } } return 0; } //=========================================================================== // // A_FadeTo // // fades the actor to a specified transparency by a specified amount and // destroys it if so desired // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_FadeTo) { PARAM_SELF_PROLOGUE(AActor); PARAM_FLOAT (target); PARAM_FLOAT_DEF (amount); PARAM_INT_DEF (flags); self->RenderStyle.Flags &= ~STYLEF_Alpha1; if (self->Alpha > target) { self->Alpha -= amount; if (self->Alpha < target) { self->Alpha = target; } } else if (self->Alpha < target) { self->Alpha += amount; if (self->Alpha > target) { self->Alpha = target; } } if (flags & FTF_CLAMP) { self->Alpha = clamp(self->Alpha, 0., 1.); } if (self->Alpha == target && (flags & FTF_REMOVE)) { P_RemoveThing(self); } return 0; } //=========================================================================== // // A_SpawnDebris // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_SpawnDebris) { PARAM_SELF_PROLOGUE(AActor); PARAM_CLASS (debris, AActor); PARAM_BOOL_DEF (transfer_translation) PARAM_FLOAT_DEF (mult_h) PARAM_FLOAT_DEF (mult_v) int i; AActor *mo; if (debris == NULL) return 0; // only positive values make sense here if (mult_v <= 0) mult_v = 1; if (mult_h <= 0) mult_h = 1; for (i = 0; i < GetDefaultByType(debris)->health; i++) { double xo = (pr_spawndebris() - 128) / 16.; double yo = (pr_spawndebris() - 128) / 16.; double zo = pr_spawndebris()*self->Height / 256 + self->GetBobOffset(); mo = Spawn(debris, self->Vec3Offset(xo, yo, zo), ALLOW_REPLACE); if (mo) { if (transfer_translation) { mo->Translation = self->Translation; } if (i < mo->GetClass()->NumOwnedStates) { mo->SetState (mo->GetClass()->OwnedStates + i); } mo->Vel.X = mult_h * pr_spawndebris.Random2() / 64.; mo->Vel.Y = mult_h * pr_spawndebris.Random2() / 64.; mo->Vel.Z = mult_v * ((pr_spawndebris() & 7) + 5); } } return 0; } //=========================================================================== // // A_SpawnParticle // //=========================================================================== enum SPFflag { SPF_FULLBRIGHT = 1, SPF_RELPOS = 1 << 1, SPF_RELVEL = 1 << 2, SPF_RELACCEL = 1 << 3, SPF_RELANG = 1 << 4, SPF_NOTIMEFREEZE = 1 << 5, }; DEFINE_ACTION_FUNCTION(AActor, A_SpawnParticle) { PARAM_SELF_PROLOGUE(AActor); PARAM_COLOR (color); PARAM_INT_DEF (flags) PARAM_INT_DEF (lifetime) PARAM_FLOAT_DEF (size) PARAM_ANGLE_DEF (angle) PARAM_FLOAT_DEF (xoff) PARAM_FLOAT_DEF (yoff) PARAM_FLOAT_DEF (zoff) PARAM_FLOAT_DEF (xvel) PARAM_FLOAT_DEF (yvel) PARAM_FLOAT_DEF (zvel) PARAM_FLOAT_DEF (accelx) PARAM_FLOAT_DEF (accely) PARAM_FLOAT_DEF (accelz) PARAM_FLOAT_DEF (startalpha) PARAM_FLOAT_DEF (fadestep) PARAM_FLOAT_DEF (sizestep) startalpha = clamp(startalpha, 0., 1.); if (fadestep > 0) fadestep = clamp(fadestep, 0., 1.); size = fabs(size); if (lifetime != 0) { if (flags & SPF_RELANG) angle += self->Angles.Yaw; double s = angle.Sin(); double c = angle.Cos(); DVector3 pos(xoff, yoff, zoff); DVector3 vel(xvel, yvel, zvel); DVector3 acc(accelx, accely, accelz); //[MC] Code ripped right out of A_SpawnItemEx. if (flags & SPF_RELPOS) { // in relative mode negative y values mean 'left' and positive ones mean 'right' // This is the inverse orientation of the absolute mode! pos.X = xoff * c + yoff * s; pos.Y = xoff * s - yoff * c; } if (flags & SPF_RELVEL) { vel.X = xvel * c + yvel * s; vel.Y = xvel * s - yvel * c; } if (flags & SPF_RELACCEL) { acc.X = accelx * c + accely * s; acc.Y = accelx * s - accely * c; } P_SpawnParticle(self->Vec3Offset(pos), vel, acc, color, startalpha, lifetime, size, fadestep, sizestep, flags); } return 0; } //=========================================================================== // // A_CheckSight // jumps if no player can see this actor // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, CheckIfSeen) { PARAM_SELF_PROLOGUE(AActor); for (int i = 0; i < MAXPLAYERS; i++) { if (playeringame[i]) { // Always check sight from each player. if (P_CheckSight(players[i].mo, self, SF_IGNOREVISIBILITY)) { ACTION_RETURN_BOOL(false); } // If a player is viewing from a non-player, then check that too. if (players[i].camera != NULL && players[i].camera->player == NULL && P_CheckSight(players[i].camera, self, SF_IGNOREVISIBILITY)) { ACTION_RETURN_BOOL(false); } } } ACTION_RETURN_BOOL(true); } //=========================================================================== // // A_CheckSightOrRange // Jumps if this actor is out of range of all players *and* out of sight. // Useful for maps with many multi-actor special effects. // //=========================================================================== static bool DoCheckSightOrRange(AActor *self, AActor *camera, double range, bool twodi, bool checksight) { if (camera == NULL) { return false; } // Check distance first, since it's cheaper than checking sight. DVector2 pos = camera->Vec2To(self); double dz; double eyez = camera->Center(); if (eyez > self->Top()) { dz = self->Top() - eyez; } else if (eyez < self->Z()) { dz = self->Z() - eyez; } else { dz = 0; } double distance = DVector3(pos, twodi? 0. : dz).LengthSquared(); if (distance <= range) { // Within range return true; } // Now check LOS. if (checksight && P_CheckSight(camera, self, SF_IGNOREVISIBILITY)) { // Visible return true; } return false; } DEFINE_ACTION_FUNCTION(AActor, CheckSightOrRange) { PARAM_SELF_PROLOGUE(AActor); PARAM_FLOAT(range); PARAM_BOOL_DEF(twodi); range *= range; for (int i = 0; i < MAXPLAYERS; ++i) { if (playeringame[i]) { // Always check from each player. if (DoCheckSightOrRange(self, players[i].mo, range, twodi, true)) { ACTION_RETURN_BOOL(false); } // If a player is viewing from a non-player, check that too. if (players[i].camera != NULL && players[i].camera->player == NULL && DoCheckSightOrRange(self, players[i].camera, range, twodi, true)) { ACTION_RETURN_BOOL(false); } } } ACTION_RETURN_BOOL(true); } DEFINE_ACTION_FUNCTION(AActor, CheckRange) { PARAM_SELF_PROLOGUE(AActor); PARAM_FLOAT(range); PARAM_BOOL_DEF(twodi); range *= range; for (int i = 0; i < MAXPLAYERS; ++i) { if (playeringame[i]) { // Always check from each player. if (DoCheckSightOrRange(self, players[i].mo, range, twodi, false)) { ACTION_RETURN_BOOL(false); } // If a player is viewing from a non-player, check that too. if (players[i].camera != NULL && players[i].camera->player == NULL && DoCheckSightOrRange(self, players[i].camera, range, twodi, false)) { ACTION_RETURN_BOOL(false); } } } ACTION_RETURN_BOOL(true); } //=========================================================================== // // Inventory drop // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_DropInventory) { PARAM_SELF_PROLOGUE(AActor); PARAM_CLASS(drop, AInventory); if (drop) { AInventory *inv = self->FindInventory(drop); if (inv) { self->DropInventory(inv); } } return 0; } //=========================================================================== // // A_SetBlend // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_SetBlend) { PARAM_SELF_PROLOGUE(AActor); PARAM_COLOR (color); PARAM_FLOAT (alpha); PARAM_INT (tics); PARAM_COLOR_DEF (color2); if (color == MAKEARGB(255,255,255,255)) color = 0; if (color2 == MAKEARGB(255,255,255,255)) color2 = 0; if (color2.a == 0) color2 = color; new DFlashFader(color.r/255.f, color.g/255.f, color.b/255.f, float(alpha), color2.r/255.f, color2.g/255.f, color2.b/255.f, 0, float(tics)/TICRATE, self); return 0; } //=========================================================================== // // A_CountdownArg // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_CountdownArg) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT(cnt); PARAM_STATE_DEF(state) if (cnt<0 || cnt >= 5) return 0; if (!self->args[cnt]--) { if (self->flags&MF_MISSILE) { P_ExplodeMissile(self, NULL, NULL); } else if (self->flags&MF_SHOOTABLE) { P_DamageMobj(self, NULL, NULL, self->health, NAME_None, DMG_FORCED); } else { if (state == nullptr) state = self->FindState(NAME_Death); self->SetState(state); } } return 0; } //============================================================================ // // A_Burst // //============================================================================ DEFINE_ACTION_FUNCTION(AActor, A_Burst) { PARAM_SELF_PROLOGUE(AActor); PARAM_CLASS(chunk, AActor); int i, numChunks; AActor * mo; if (chunk == NULL) { return 0; } self->Vel.Zero(); self->Height = self->GetDefault()->Height; // [RH] In Hexen, this creates a random number of shards (range [24,56]) // with no relation to the size of the self shattering. I think it should // base the number of shards on the size of the dead thing, so bigger // things break up into more shards than smaller things. // An self with radius 20 and height 64 creates ~40 chunks. numChunks = MAX (4, int(self->radius * self->Height)/32); i = (pr_burst.Random2()) % (numChunks/4); for (i = MAX (24, numChunks + i); i >= 0; i--) { double xo = (pr_burst() - 128) * self->radius / 128; double yo = (pr_burst() - 128) * self->radius / 128; double zo = (pr_burst() * self->Height / 255); mo = Spawn(chunk, self->Vec3Offset(xo, yo, zo), ALLOW_REPLACE); if (mo) { mo->Vel.Z = 4 * (mo->Z() - self->Z()) / self->Height; mo->Vel.X = pr_burst.Random2() / 128.; mo->Vel.Y = pr_burst.Random2() / 128.; mo->RenderStyle = self->RenderStyle; mo->Alpha = self->Alpha; mo->CopyFriendliness(self, true); } } // [RH] Do some stuff to make this more useful outside Hexen if (self->flags4 & MF4_BOSSDEATH) { A_BossDeath(self); } A_Unblock(self, true); self->Destroy (); return 0; } //=========================================================================== // // A_Stop // resets all velocity of the actor to 0 // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_Stop) { PARAM_SELF_PROLOGUE(AActor); self->Vel.Zero(); if (self->player && self->player->mo == self && !(self->player->cheats & CF_PREDICTING)) { self->player->mo->PlayIdle(); self->player->Vel.Zero(); } return 0; } static void CheckStopped(AActor *self) { if (self->player != NULL && self->player->mo == self && !(self->player->cheats & CF_PREDICTING) && !self->Vel.isZero()) { self->player->mo->PlayIdle(); self->player->Vel.Zero(); } } //=========================================================================== // // A_Respawn // //=========================================================================== enum RS_Flags { RSF_FOG=1, RSF_KEEPTARGET=2, RSF_TELEFRAG=4, }; DEFINE_ACTION_FUNCTION(AActor, A_Respawn) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT_DEF(flags); bool oktorespawn = false; DVector3 pos = self->Pos(); self->flags |= MF_SOLID; self->Height = self->GetDefault()->Height; self->radius = self->GetDefault()->radius; self->RestoreSpecialPosition(); if (flags & RSF_TELEFRAG) { // [KS] DIE DIE DIE DIE erm *ahem* =) oktorespawn = P_TeleportMove(self, self->Pos(), true, false); } else { oktorespawn = P_CheckPosition(self, self->Pos(), true); } if (oktorespawn) { AActor *defs = self->GetDefault(); self->health = defs->health; // [KS] Don't keep target, because it could be self if the monster committed suicide // ...Actually it's better off an option, so you have better control over monster behavior. if (!(flags & RSF_KEEPTARGET)) { self->target = NULL; self->LastHeard = NULL; self->lastenemy = NULL; } else { // Don't attack yourself (Re: "Marine targets itself after suicide") if (self->target == self) self->target = NULL; if (self->lastenemy == self) self->lastenemy = NULL; } self->flags = (defs->flags & ~MF_FRIENDLY) | (self->flags & MF_FRIENDLY); self->flags2 = defs->flags2; self->flags3 = (defs->flags3 & ~(MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS)) | (self->flags3 & (MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS)); self->flags4 = (defs->flags4 & ~MF4_NOHATEPLAYERS) | (self->flags4 & MF4_NOHATEPLAYERS); self->flags5 = defs->flags5; self->flags6 = defs->flags6; self->flags7 = defs->flags7; self->SetState (self->SpawnState); self->renderflags &= ~RF_INVISIBLE; if (flags & RSF_FOG) { P_SpawnTeleportFog(self, pos, true, true); P_SpawnTeleportFog(self, self->Pos(), false, true); } if (self->CountsAsKill()) { level.total_monsters++; } } else { self->flags &= ~MF_SOLID; } return 0; } //========================================================================== // // A_PlayerSkinCheck // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, PlayerSkinCheck) { PARAM_SELF_PROLOGUE(AActor); ACTION_RETURN_BOOL(self->player != NULL && skins[self->player->userinfo.GetSkin()].othergame); } // [KS] *** Start of my modifications *** //========================================================================== // // A_CheckLOF (state jump, int flags = CRF_AIM_VERT|CRF_AIM_HOR, // fixed range = 0, angle angle = 0, angle pitch = 0, // fixed offsetheight = 32, fixed offsetwidth = 0, // int ptr_target = AAPTR_DEFAULT (target) ) // //========================================================================== enum CLOF_flags { CLOFF_NOAIM_VERT = 0x00000001, CLOFF_NOAIM_HORZ = 0x00000002, CLOFF_JUMPENEMY = 0x00000004, CLOFF_JUMPFRIEND = 0x00000008, CLOFF_JUMPOBJECT = 0x00000010, CLOFF_JUMPNONHOSTILE = 0x00000020, CLOFF_SKIPENEMY = 0x00000040, CLOFF_SKIPFRIEND = 0x00000080, CLOFF_SKIPOBJECT = 0x00000100, CLOFF_SKIPNONHOSTILE = 0x00000200, CLOFF_MUSTBESHOOTABLE = 0x00000400, CLOFF_SKIPTARGET = 0x00000800, CLOFF_ALLOWNULL = 0x00001000, CLOFF_CHECKPARTIAL = 0x00002000, CLOFF_MUSTBEGHOST = 0x00004000, CLOFF_IGNOREGHOST = 0x00008000, CLOFF_MUSTBESOLID = 0x00010000, CLOFF_BEYONDTARGET = 0x00020000, CLOFF_FROMBASE = 0x00040000, CLOFF_MUL_HEIGHT = 0x00080000, CLOFF_MUL_WIDTH = 0x00100000, CLOFF_JUMP_ON_MISS = 0x00200000, CLOFF_AIM_VERT_NOOFFSET = 0x00400000, CLOFF_SETTARGET = 0x00800000, CLOFF_SETMASTER = 0x01000000, CLOFF_SETTRACER = 0x02000000, }; struct LOFData { AActor *Self; AActor *Target; int Flags; bool BadActor; }; ETraceStatus CheckLOFTraceFunc(FTraceResults &trace, void *userdata) { LOFData *data = (LOFData *)userdata; int flags = data->Flags; if (trace.HitType != TRACE_HitActor) { return TRACE_Stop; } if (trace.Actor == data->Target) { if (flags & CLOFF_SKIPTARGET) { if (flags & CLOFF_BEYONDTARGET) { return TRACE_Skip; } return TRACE_Abort; } return TRACE_Stop; } if (flags & CLOFF_MUSTBESHOOTABLE) { // all shootability checks go here if (!(trace.Actor->flags & MF_SHOOTABLE)) { return TRACE_Skip; } if (trace.Actor->flags2 & MF2_NONSHOOTABLE) { return TRACE_Skip; } } if ((flags & CLOFF_MUSTBESOLID) && !(trace.Actor->flags & MF_SOLID)) { return TRACE_Skip; } if (flags & CLOFF_MUSTBEGHOST) { if (!(trace.Actor->flags3 & MF3_GHOST)) { return TRACE_Skip; } } else if (flags & CLOFF_IGNOREGHOST) { if (trace.Actor->flags3 & MF3_GHOST) { return TRACE_Skip; } } if ( ((flags & CLOFF_JUMPENEMY) && data->Self->IsHostile(trace.Actor)) || ((flags & CLOFF_JUMPFRIEND) && data->Self->IsFriend(trace.Actor)) || ((flags & CLOFF_JUMPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) || ((flags & CLOFF_JUMPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor)) ) { return TRACE_Stop; } if ( ((flags & CLOFF_SKIPENEMY) && data->Self->IsHostile(trace.Actor)) || ((flags & CLOFF_SKIPFRIEND) && data->Self->IsFriend(trace.Actor)) || ((flags & CLOFF_SKIPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) || ((flags & CLOFF_SKIPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor)) ) { return TRACE_Skip; } data->BadActor = true; return TRACE_Abort; } DEFINE_ACTION_FUNCTION(AActor, CheckLOF) { // Check line of fire /* Not accounted for / I don't know how it works: FLOORCLIP */ AActor *target; DVector3 pos; DVector3 vel; PARAM_SELF_PROLOGUE(AActor); PARAM_INT_DEF (flags) PARAM_FLOAT_DEF (range) PARAM_FLOAT_DEF (minrange) PARAM_ANGLE_DEF (angle) PARAM_ANGLE_DEF (pitch) PARAM_FLOAT_DEF (offsetheight) PARAM_FLOAT_DEF (offsetwidth) PARAM_INT_DEF (ptr_target) PARAM_FLOAT_DEF (offsetforward) DAngle ang; target = COPY_AAPTR(self, ptr_target == AAPTR_DEFAULT ? AAPTR_TARGET|AAPTR_PLAYER_GETTARGET|AAPTR_NULL : ptr_target); // no player-support by default if (flags & CLOFF_MUL_HEIGHT) { if (self->player != NULL) { // Synced with hitscan: self->player->mo->height is strangely conscientious about getting the right actor for player offsetheight *= self->player->mo->Height * self->player->crouchfactor; } else { offsetheight *= self->Height; } } if (flags & CLOFF_MUL_WIDTH) { offsetforward *= self->radius; offsetwidth *= self->radius; } pos = self->PosPlusZ(offsetheight - self->Floorclip); if (!(flags & CLOFF_FROMBASE)) { // default to hitscan origin // Synced with hitscan: self->Height is strangely NON-conscientious about getting the right actor for player pos.Z += self->Height *0.5; if (self->player != NULL) { pos.Z += self->player->mo->AttackZOffset * self->player->crouchfactor; } else { pos.Z += 8; } } if (target) { if (range > 0 && !(flags & CLOFF_CHECKPARTIAL)) { double distance = self->Distance3D(target); if (distance > range) { ACTION_RETURN_BOOL(false); } } if (flags & CLOFF_NOAIM_HORZ) { ang = self->Angles.Yaw; } else ang = self->AngleTo (target); angle += ang; double s = ang.Sin(); double c = ang.Cos(); DVector2 xy = self->Vec2Offset(offsetforward * c + offsetwidth * s, offsetforward * s - offsetwidth * c); pos.X = xy.X; pos.Y = xy.Y; double xydist = self->Distance2D(target); if (flags & CLOFF_NOAIM_VERT) { pitch += self->Angles.Pitch; } else if (flags & CLOFF_AIM_VERT_NOOFFSET) { pitch -= VecToAngle(xydist, target->Center() - pos.Z + offsetheight); } else { pitch -= VecToAngle(xydist, target->Center() - pos.Z); } } else if (flags & CLOFF_ALLOWNULL) { angle += self->Angles.Yaw; pitch += self->Angles.Pitch; double s = angle.Sin(); double c = angle.Cos(); DVector2 xy = self->Vec2Offset(offsetforward * c + offsetwidth * s, offsetforward * s - offsetwidth * c); pos.X = xy.X; pos.Y = xy.Y; } else { ACTION_RETURN_BOOL(false); } double cp = pitch.Cos(); vel = { cp * angle.Cos(), cp * angle.Sin(), -pitch.Sin() }; /* Variable set: jump, flags, target pos (trace point of origin) vel (trace unit vector) range */ sector_t *sec = P_PointInSector(pos); if (range == 0) { range = (self->player != NULL) ? PLAYERMISSILERANGE : MISSILERANGE; } FTraceResults trace; LOFData lof_data; lof_data.Self = self; lof_data.Target = target; lof_data.Flags = flags; lof_data.BadActor = false; Trace(pos, sec, vel, range, ActorFlags::FromInt(0xFFFFFFFF), ML_BLOCKEVERYTHING, self, trace, TRACE_PortalRestrict, CheckLOFTraceFunc, &lof_data); if (trace.HitType == TRACE_HitActor || ((flags & CLOFF_JUMP_ON_MISS) && !lof_data.BadActor && trace.HitType != TRACE_HitNone)) { if (minrange > 0 && trace.Distance < minrange) { ACTION_RETURN_BOOL(false); } if ((trace.HitType == TRACE_HitActor) && (trace.Actor != NULL) && !(lof_data.BadActor)) { if (flags & (CLOFF_SETTARGET)) self->target = trace.Actor; if (flags & (CLOFF_SETMASTER)) self->master = trace.Actor; if (flags & (CLOFF_SETTRACER)) self->tracer = trace.Actor; } ACTION_RETURN_BOOL(true); } ACTION_RETURN_BOOL(false); } //========================================================================== // // A_JumpIfTargetInLOS (state label, optional fixed fov, optional int flags, // optional fixed dist_max, optional fixed dist_close) // // Jumps if the actor can see its target, or if the player has a linetarget. // ProjectileTarget affects how projectiles are treated. If set, it will use // the target of the projectile for seekers, and ignore the target for // normal projectiles. If not set, it will use the missile's owner instead // (the default). ProjectileTarget is now flag JLOSF_PROJECTILE. dist_max // sets the maximum distance that actor can see, 0 means forever. dist_close // uses special behavior if certain flags are set, 0 means no checks. // //========================================================================== enum JLOS_flags { JLOSF_PROJECTILE = 1 << 0, JLOSF_NOSIGHT = 1 << 1, JLOSF_CLOSENOFOV = 1 << 2, JLOSF_CLOSENOSIGHT = 1 << 3, JLOSF_CLOSENOJUMP = 1 << 4, JLOSF_DEADNOJUMP = 1 << 5, JLOSF_CHECKMASTER = 1 << 6, JLOSF_TARGETLOS = 1 << 7, JLOSF_FLIPFOV = 1 << 8, JLOSF_ALLYNOJUMP = 1 << 9, JLOSF_COMBATANTONLY = 1 << 10, JLOSF_NOAUTOAIM = 1 << 11, JLOSF_CHECKTRACER = 1 << 12, }; DEFINE_ACTION_FUNCTION(AActor, CheckIfTargetInLOS) { PARAM_SELF_PROLOGUE(AActor); PARAM_ANGLE_DEF (fov) PARAM_INT_DEF (flags) PARAM_FLOAT_DEF (dist_max) PARAM_FLOAT_DEF (dist_close) AActor *target, *viewport; FTranslatedLineTarget t; bool doCheckSight; if (!self->player) { if (flags & JLOSF_CHECKMASTER) { target = self->master; } else if ((self->flags & MF_MISSILE && (flags & JLOSF_PROJECTILE)) || (flags & JLOSF_CHECKTRACER)) { if ((self->flags2 & MF2_SEEKERMISSILE) || (flags & JLOSF_CHECKTRACER)) target = self->tracer; else target = NULL; } else { target = self->target; } if (target == NULL) { // [KS] Let's not call P_CheckSight unnecessarily in this case. ACTION_RETURN_BOOL(false); } if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) { ACTION_RETURN_BOOL(false); } doCheckSight = !(flags & JLOSF_NOSIGHT); } else { // Does the player aim at something that can be shot? P_AimLineAttack(self, self->Angles.Yaw, MISSILERANGE, &t, (flags & JLOSF_NOAUTOAIM) ? 0.5 : 0., ALF_PORTALRESTRICT); if (!t.linetarget) { ACTION_RETURN_BOOL(false); } target = t.linetarget; switch (flags & (JLOSF_TARGETLOS|JLOSF_FLIPFOV)) { case JLOSF_TARGETLOS|JLOSF_FLIPFOV: // target makes sight check, player makes fov check; player has verified fov fov = 0.; // fall-through case JLOSF_TARGETLOS: doCheckSight = !(flags & JLOSF_NOSIGHT); // The target is responsible for sight check and fov break; default: // player has verified sight and fov fov = 0.; // fall-through case JLOSF_FLIPFOV: // Player has verified sight, but target must verify fov doCheckSight = false; break; } } // [FDARI] If target is not a combatant, don't jump if ( (flags & JLOSF_COMBATANTONLY) && (!target->player) && !(target->flags3 & MF3_ISMONSTER)) { ACTION_RETURN_BOOL(false); } // [FDARI] If actors share team, don't jump if ((flags & JLOSF_ALLYNOJUMP) && self->IsFriend(target)) { ACTION_RETURN_BOOL(false); } double distance = self->Distance3D(target); if (dist_max && (distance > dist_max)) { ACTION_RETURN_BOOL(false); } if (dist_close && (distance < dist_close)) { if (flags & JLOSF_CLOSENOJUMP) { ACTION_RETURN_BOOL(false); } if (flags & JLOSF_CLOSENOFOV) fov = 0.; if (flags & JLOSF_CLOSENOSIGHT) doCheckSight = false; } if (flags & JLOSF_TARGETLOS) { viewport = target; target = self; } else { viewport = self; } if (doCheckSight && !P_CheckSight (viewport, target, SF_IGNOREVISIBILITY)) { ACTION_RETURN_BOOL(false); } if (flags & JLOSF_FLIPFOV) { if (viewport == self) { viewport = target; target = self; } else { target = viewport; viewport = self; } } fov = MIN(fov, 360.); if (fov > 0) { DAngle an = absangle(viewport->AngleTo(target), viewport->Angles.Yaw); if (an > (fov / 2)) { ACTION_RETURN_BOOL(false); // [KS] Outside of FOV - return } } ACTION_RETURN_BOOL(true); } //========================================================================== // // A_JumpIfInTargetLOS (state label, optional fixed fov, optional int flags // optional fixed dist_max, optional fixed dist_close) // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, CheckIfInTargetLOS) { PARAM_SELF_PROLOGUE(AActor); PARAM_ANGLE_DEF (fov) PARAM_INT_DEF (flags) PARAM_FLOAT_DEF (dist_max) PARAM_FLOAT_DEF (dist_close) AActor *target; if (flags & JLOSF_CHECKMASTER) { target = self->master; } else if (self->flags & MF_MISSILE && (flags & JLOSF_PROJECTILE)) { if (self->flags2 & MF2_SEEKERMISSILE) target = self->tracer; else target = NULL; } else { target = self->target; } if (target == NULL) { // [KS] Let's not call P_CheckSight unnecessarily in this case. ACTION_RETURN_BOOL(false); } if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) { ACTION_RETURN_BOOL(false); } double distance = self->Distance3D(target); if (dist_max && (distance > dist_max)) { ACTION_RETURN_BOOL(false); } bool doCheckSight = !(flags & JLOSF_NOSIGHT); if (dist_close && (distance < dist_close)) { if (flags & JLOSF_CLOSENOJUMP) { ACTION_RETURN_BOOL(false); } if (flags & JLOSF_CLOSENOFOV) fov = 0.; if (flags & JLOSF_CLOSENOSIGHT) doCheckSight = false; } if (fov > 0 && (fov < 360.)) { DAngle an = absangle(target->AngleTo(self), target->Angles.Yaw); if (an > (fov / 2)) { ACTION_RETURN_BOOL(false); // [KS] Outside of FOV - return } } if (doCheckSight && !P_CheckSight (target, self, SF_IGNOREVISIBILITY)) { ACTION_RETURN_BOOL(false); } ACTION_RETURN_BOOL(true); } //=========================================================================== // // Modified code pointer from Skulltag // //=========================================================================== DEFINE_ACTION_FUNCTION(AStateProvider, A_CheckForReload) { PARAM_ACTION_PROLOGUE(AStateProvider); if ( self->player == NULL || self->player->ReadyWeapon == NULL ) { ACTION_RETURN_STATE(NULL); } PARAM_INT (count); PARAM_STATE_ACTION (jump); PARAM_BOOL_DEF (dontincrement); if (numret > 0) { ret->SetPointer(NULL, ATAG_STATE); numret = 1; } AWeapon *weapon = self->player->ReadyWeapon; int ReloadCounter = weapon->ReloadCounter; if (!dontincrement || ReloadCounter != 0) ReloadCounter = (weapon->ReloadCounter+1) % count; else // 0 % 1 = 1? So how do we check if the weapon was never fired? We should only do this when we're not incrementing the counter though. ReloadCounter = 1; // If we have not made our last shot... if (ReloadCounter != 0) { // Go back to the refire frames, instead of continuing on to the reload frames. if (numret != 0) { ret->SetPointer(jump, ATAG_STATE); } } else { // We need to reload. However, don't reload if we're out of ammo. weapon->CheckAmmo(false, false); } if (!dontincrement) { weapon->ReloadCounter = ReloadCounter; } return numret; } //=========================================================================== // // Resets the counter for the above function // //=========================================================================== DEFINE_ACTION_FUNCTION(AStateProvider, A_ResetReloadCounter) { PARAM_ACTION_PROLOGUE(AStateProvider); if (self->player == NULL || self->player->ReadyWeapon == NULL) return 0; AWeapon *weapon = self->player->ReadyWeapon; weapon->ReloadCounter = 0; return 0; } //=========================================================================== // // A_ChangeFlag // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_ChangeFlag) { PARAM_SELF_PROLOGUE(AActor); PARAM_STRING (flagname); PARAM_BOOL (value); ModActorFlag(self, flagname, value); return 0; } //=========================================================================== // // A_CheckFlag // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, CheckFlag) { PARAM_SELF_PROLOGUE(AActor); PARAM_STRING (flagname); PARAM_INT_DEF (checkpointer); AActor *owner = COPY_AAPTR(self, checkpointer); ACTION_RETURN_BOOL(owner != nullptr && CheckActorFlag(owner, flagname)); } DEFINE_ACTION_FUNCTION(AActor, A_ChangeCountFlags) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT_DEF(kill); PARAM_INT_DEF(item); PARAM_INT_DEF(secret); if (self->CountsAsKill() && self->health > 0) --level.total_monsters; if (self->flags & MF_COUNTITEM) --level.total_items; if (self->flags5 & MF5_COUNTSECRET) --level.total_secrets; if (kill != -1) { if (kill == 0) self->flags &= ~MF_COUNTKILL; else self->flags |= MF_COUNTKILL; } if (item != -1) { if (item == 0) self->flags &= ~MF_COUNTITEM; else self->flags |= MF_COUNTITEM; } if (secret != -1) { if (secret == 0) self->flags5 &= ~MF5_COUNTSECRET; else self->flags5 |= MF5_COUNTSECRET; } if (self->CountsAsKill() && self->health > 0) ++level.total_monsters; if (self->flags & MF_COUNTITEM) ++level.total_items; if (self->flags5 & MF5_COUNTSECRET) ++level.total_secrets; return 0; } //=========================================================================== // // A_RaiseMaster // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_RaiseMaster) { PARAM_SELF_PROLOGUE(AActor); PARAM_BOOL_DEF(copy); if (self->master != NULL) { P_Thing_Raise(self->master, copy ? self : NULL); } return 0; } //=========================================================================== // // A_RaiseChildren // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_RaiseChildren) { PARAM_SELF_PROLOGUE(AActor); PARAM_BOOL_DEF(copy); TThinkerIterator it; AActor *mo; while ((mo = it.Next()) != NULL) { if (mo->master == self) { P_Thing_Raise(mo, copy ? self : NULL); } } return 0; } //=========================================================================== // // A_RaiseSiblings // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_RaiseSiblings) { PARAM_SELF_PROLOGUE(AActor); PARAM_BOOL_DEF(copy); TThinkerIterator it; AActor *mo; if (self->master != NULL) { while ((mo = it.Next()) != NULL) { if (mo->master == self->master && mo != self) { P_Thing_Raise(mo, copy ? self : NULL); } } } return 0; } //=========================================================================== // // A_MonsterRefire // // Keep firing unless target got out of sight // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_MonsterRefire) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT (prob); PARAM_STATE (jump); A_FaceTarget(self); if (pr_monsterrefire() < prob) { ACTION_RETURN_STATE(NULL); } if (self->target == NULL || P_HitFriend (self) || self->target->health <= 0 || !P_CheckSight (self, self->target, SF_SEEPASTBLOCKEVERYTHING|SF_SEEPASTSHOOTABLELINES) ) { ACTION_RETURN_STATE(jump); } ACTION_RETURN_STATE(NULL); } //=========================================================================== // // A_SetAngle // // Set actor's angle (in degrees). // //=========================================================================== enum { SPF_FORCECLAMP = 1, // players always clamp SPF_INTERPOLATE = 2, }; DEFINE_ACTION_FUNCTION(AActor, A_SetAngle) { PARAM_SELF_PROLOGUE(AActor); PARAM_FLOAT_DEF(angle); PARAM_INT_DEF(flags); PARAM_INT_DEF(ptr); AActor *ref = COPY_AAPTR(self, ptr); if (ref != NULL) { ref->SetAngle(angle, !!(flags & SPF_INTERPOLATE)); } return 0; } //=========================================================================== // // A_SetPitch // // Set actor's pitch (in degrees). // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_SetPitch) { PARAM_SELF_PROLOGUE(AActor); PARAM_FLOAT(pitch); PARAM_INT_DEF(flags); PARAM_INT_DEF(ptr); AActor *ref = COPY_AAPTR(self, ptr); if (ref != NULL) { ref->SetPitch(pitch, !!(flags & SPF_INTERPOLATE), !!(flags & SPF_FORCECLAMP)); } return 0; } //=========================================================================== // // [Nash] A_SetRoll // // Set actor's roll (in degrees). // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_SetRoll) { PARAM_SELF_PROLOGUE(AActor); PARAM_FLOAT (roll); PARAM_INT_DEF (flags); PARAM_INT_DEF (ptr) ; AActor *ref = COPY_AAPTR(self, ptr); if (ref != NULL) { ref->SetRoll(roll, !!(flags & SPF_INTERPOLATE)); } return 0; } //=========================================================================== // // A_ScaleVelocity // // Scale actor's velocity. // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_ScaleVelocity) { PARAM_SELF_PROLOGUE(AActor); PARAM_FLOAT(scale); PARAM_INT_DEF(ptr); AActor *ref = COPY_AAPTR(self, ptr); if (ref == NULL) { return 0; } bool was_moving = !ref->Vel.isZero(); ref->Vel *= scale; // If the actor was previously moving but now is not, and is a player, // update its player variables. (See A_Stop.) if (was_moving) { CheckStopped(ref); } return 0; } //=========================================================================== // // A_ChangeVelocity // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_ChangeVelocity) { PARAM_SELF_PROLOGUE(AActor); PARAM_FLOAT_DEF (x) PARAM_FLOAT_DEF (y) PARAM_FLOAT_DEF (z) PARAM_INT_DEF (flags) PARAM_INT_DEF (ptr) AActor *ref = COPY_AAPTR(self, ptr); if (ref == NULL) { return 0; } INTBOOL was_moving = !ref->Vel.isZero(); DVector3 vel(x, y, z); double sina = ref->Angles.Yaw.Sin(); double cosa = ref->Angles.Yaw.Cos(); if (flags & 1) // relative axes - make x, y relative to actor's current angle { vel.X = x*cosa - y*sina; vel.Y = x*sina + y*cosa; } if (flags & 2) // discard old velocity - replace old velocity with new velocity { ref->Vel = vel; } else // add new velocity to old velocity { ref->Vel += vel; } if (was_moving) { CheckStopped(ref); } return 0; } //=========================================================================== // // A_SetUserVar // //=========================================================================== static PField *GetVar(DObject *self, FName varname) { PField *var = dyn_cast(self->GetClass()->Symbols.FindSymbol(varname, true)); if (var == NULL || (var->Flags & (VARF_Native | VARF_Private | VARF_Protected | VARF_Static)) || !var->Type->IsKindOf(RUNTIME_CLASS(PBasicType))) { Printf("%s is not a user variable in class %s\n", varname.GetChars(), self->GetClass()->TypeName.GetChars()); return nullptr; } return var; } DEFINE_ACTION_FUNCTION(AActor, A_SetUserVar) { PARAM_SELF_PROLOGUE(DObject); PARAM_NAME (varname); PARAM_INT (value); // Set the value of the specified user variable. PField *var = GetVar(self, varname); if (var != nullptr) { var->Type->SetValue(reinterpret_cast(self) + var->Offset, value); } return 0; } DEFINE_ACTION_FUNCTION(AActor, A_SetUserVarFloat) { PARAM_SELF_PROLOGUE(DObject); PARAM_NAME (varname); PARAM_FLOAT (value); // Set the value of the specified user variable. PField *var = GetVar(self, varname); if (var != nullptr) { var->Type->SetValue(reinterpret_cast(self) + var->Offset, value); } return 0; } //=========================================================================== // // A_SetUserArray // //=========================================================================== static PField *GetArrayVar(DObject *self, FName varname, int pos) { PField *var = dyn_cast(self->GetClass()->Symbols.FindSymbol(varname, true)); if (var == NULL || (var->Flags & (VARF_Native | VARF_Private | VARF_Protected | VARF_Static)) || !var->Type->IsKindOf(RUNTIME_CLASS(PArray)) || !static_cast(var->Type)->ElementType->IsKindOf(RUNTIME_CLASS(PBasicType))) { Printf("%s is not a user array in class %s\n", varname.GetChars(), self->GetClass()->TypeName.GetChars()); return nullptr; } if ((unsigned)pos >= static_cast(var->Type)->ElementCount) { Printf("%d is out of bounds in array %s in class %s\n", pos, varname.GetChars(), self->GetClass()->TypeName.GetChars()); return nullptr; } return var; } DEFINE_ACTION_FUNCTION(AActor, A_SetUserArray) { PARAM_SELF_PROLOGUE(DObject); PARAM_NAME (varname); PARAM_INT (pos); PARAM_INT (value); // Set the value of the specified user array at index pos. PField *var = GetArrayVar(self, varname, pos); if (var != nullptr) { PArray *arraytype = static_cast(var->Type); arraytype->ElementType->SetValue(reinterpret_cast(self) + var->Offset + arraytype->ElementSize * pos, value); } return 0; } DEFINE_ACTION_FUNCTION(AActor, A_SetUserArrayFloat) { PARAM_SELF_PROLOGUE(DObject); PARAM_NAME (varname); PARAM_INT (pos); PARAM_FLOAT (value); // Set the value of the specified user array at index pos. PField *var = GetArrayVar(self, varname, pos); if (var != nullptr) { PArray *arraytype = static_cast(var->Type); arraytype->ElementType->SetValue(reinterpret_cast(self) + var->Offset + arraytype->ElementSize * pos, value); } return 0; } //=========================================================================== // // A_Teleport([state teleportstate, [class targettype, // [class fogtype, [int flags, [fixed mindist, // [fixed maxdist]]]]]]) // // Attempts to teleport to a targettype at least mindist away and at most // maxdist away (0 means unlimited). If successful, spawn a fogtype at old // location and place calling actor in teleportstate. // //=========================================================================== enum T_Flags { TF_TELEFRAG = 0x00000001, // Allow telefrag in order to teleport. TF_RANDOMDECIDE = 0x00000002, // Randomly fail based on health. (A_Srcr2Decide) TF_FORCED = 0x00000004, // Forget what's in the way. TF_Telefrag takes precedence though. TF_KEEPVELOCITY = 0x00000008, // Preserve velocity. TF_KEEPANGLE = 0x00000010, // Keep angle. TF_USESPOTZ = 0x00000020, // Set the z to the spot's z, instead of the floor. TF_NOSRCFOG = 0x00000040, // Don't leave any fog behind when teleporting. TF_NODESTFOG = 0x00000080, // Don't spawn any fog at the arrival position. TF_USEACTORFOG = 0x00000100, // Use the actor's TeleFogSourceType and TeleFogDestType fogs. TF_NOJUMP = 0x00000200, // Don't jump after teleporting. TF_OVERRIDE = 0x00000400, // Ignore NOTELEPORT. TF_SENSITIVEZ = 0x00000800, // Fail if the actor wouldn't fit in the position (for Z). }; DEFINE_ACTION_FUNCTION(AActor, A_Teleport) { PARAM_ACTION_PROLOGUE(AActor); PARAM_STATE_DEF (teleport_state) PARAM_CLASS_DEF (target_type, ASpecialSpot) PARAM_CLASS_DEF (fog_type, AActor) PARAM_INT_DEF (flags) PARAM_FLOAT_DEF (mindist) PARAM_FLOAT_DEF (maxdist) PARAM_INT_DEF (ptr) AActor *ref = COPY_AAPTR(self, ptr); // A_Teleport and A_Warp were the only codepointers that can state jump // *AND* have a meaningful inventory state chain result. Grrr. if (numret > 1) { ret[1].SetInt(false); numret = 2; } if (numret > 0) { ret[0].SetPointer(NULL, ATAG_STATE); } if (!ref) { return numret; } if ((ref->flags2 & MF2_NOTELEPORT) && !(flags & TF_OVERRIDE)) { return numret; } // Randomly choose not to teleport like A_Srcr2Decide. if (flags & TF_RANDOMDECIDE) { static const int chance[] = { 192, 120, 120, 120, 64, 64, 32, 16, 0 }; unsigned int chanceindex = ref->health / ((ref->SpawnHealth()/8 == 0) ? 1 : ref->SpawnHealth()/8); if (chanceindex >= countof(chance)) { chanceindex = countof(chance) - 1; } if (pr_teleport() >= chance[chanceindex]) { return numret; } } DSpotState *state = DSpotState::GetSpotState(); if (state == NULL) { return numret; } if (target_type == NULL) { target_type = PClass::FindActor("BossSpot"); } AActor *spot = state->GetSpotWithMinMaxDistance(target_type, ref->X(), ref->Y(), mindist, maxdist); if (spot == NULL) { return numret; } // [MC] By default, the function adjusts the actor's Z if it's below the floor or above the ceiling. // This can be an issue as actors designed to maintain specific z positions wind up teleporting // anyway when they should not, such as a floor rising above or ceiling lowering below the position // of the spot. if (flags & TF_SENSITIVEZ) { double posz = (flags & TF_USESPOTZ) ? spot->Z() : spot->floorz; if ((posz + ref->Height > spot->ceilingz) || (posz < spot->floorz)) { return numret; } } DVector3 prev = ref->Pos(); double aboveFloor = spot->Z() - spot->floorz; double finalz = spot->floorz + aboveFloor; if (spot->Top() > spot->ceilingz) finalz = spot->ceilingz - ref->Height; else if (spot->Z() < spot->floorz) finalz = spot->floorz; DVector3 tpos = spot->PosAtZ(finalz); //Take precedence and cooperate with telefragging first. bool tele_result = P_TeleportMove(ref, tpos, !!(flags & TF_TELEFRAG)); if (!tele_result && (flags & TF_FORCED)) { //If for some reason the original move didn't work, regardless of telefrag, force it to move. ref->SetOrigin(tpos, false); tele_result = true; } AActor *fog1 = NULL, *fog2 = NULL; if (tele_result) { //If a fog type is defined in the parameter, or the user wants to use the actor's predefined fogs, //and if there's no desire to be fogless, spawn a fog based upon settings. if (fog_type || (flags & TF_USEACTORFOG)) { if (!(flags & TF_NOSRCFOG)) { if (flags & TF_USEACTORFOG) P_SpawnTeleportFog(ref, prev, true, true); else { fog1 = Spawn(fog_type, prev, ALLOW_REPLACE); if (fog1 != NULL) fog1->target = ref; } } if (!(flags & TF_NODESTFOG)) { if (flags & TF_USEACTORFOG) P_SpawnTeleportFog(ref, ref->Pos(), false, true); else { fog2 = Spawn(fog_type, ref->Pos(), ALLOW_REPLACE); if (fog2 != NULL) fog2->target = ref; } } } ref->SetZ((flags & TF_USESPOTZ) ? spot->Z() : ref->floorz, false); if (!(flags & TF_KEEPANGLE)) ref->Angles.Yaw = spot->Angles.Yaw; if (!(flags & TF_KEEPVELOCITY)) ref->Vel.Zero(); if (!(flags & TF_NOJUMP)) //The state jump should only happen with the calling actor. { if (teleport_state == NULL) { // Default to Teleport. teleport_state = self->FindState("Teleport"); // If still nothing, then return. if (teleport_state == NULL) { return numret; } } if (numret > 0) { ret[0].SetPointer(teleport_state, ATAG_STATE); } return numret; } } if (numret > 1) { ret[1].SetInt(tele_result); } return numret; } //=========================================================================== // // A_Quake // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_Quake) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT (intensity); PARAM_INT (duration); PARAM_INT (damrad); PARAM_INT (tremrad); PARAM_SOUND_DEF (sound); P_StartQuake(self, 0, intensity, duration, damrad, tremrad, sound); return 0; } //=========================================================================== // // A_QuakeEx // // Extended version of A_Quake. Takes individual axis into account and can // take flags. //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_QuakeEx) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT(intensityX); PARAM_INT(intensityY); PARAM_INT(intensityZ); PARAM_INT(duration); PARAM_INT(damrad); PARAM_INT(tremrad); PARAM_SOUND_DEF(sound); PARAM_INT_DEF(flags); PARAM_FLOAT_DEF(mulWaveX); PARAM_FLOAT_DEF(mulWaveY); PARAM_FLOAT_DEF(mulWaveZ); PARAM_INT_DEF(falloff); PARAM_INT_DEF(highpoint); PARAM_FLOAT_DEF(rollIntensity); PARAM_FLOAT_DEF(rollWave); P_StartQuakeXYZ(self, 0, intensityX, intensityY, intensityZ, duration, damrad, tremrad, sound, flags, mulWaveX, mulWaveY, mulWaveZ, falloff, highpoint, rollIntensity, rollWave); return 0; } //=========================================================================== // // A_Weave // //=========================================================================== void A_Weave(AActor *self, int xyspeed, int zspeed, double xydist, double zdist) { DVector2 newpos; int weaveXY, weaveZ; DAngle angle; double dist; weaveXY = self->WeaveIndexXY & 63; weaveZ = self->WeaveIndexZ & 63; angle = self->Angles.Yaw + 90; if (xydist != 0 && xyspeed != 0) { dist = BobSin(weaveXY) * xydist; newpos = self->Pos().XY() - angle.ToVector(dist); weaveXY = (weaveXY + xyspeed) & 63; dist = BobSin(weaveXY) * xydist; newpos += angle.ToVector(dist); if (!(self->flags5 & MF5_NOINTERACTION)) { P_TryMove (self, newpos, true); } else { FLinkContext ctx; self->UnlinkFromWorld (&ctx); self->flags |= MF_NOBLOCKMAP; // We need to do portal offsetting here explicitly, because SetXY cannot do that. newpos -= self->Pos().XY(); self->SetXY(self->Vec2Offset(newpos.X, newpos.Y)); self->LinkToWorld (&ctx); } self->WeaveIndexXY = weaveXY; } if (zdist != 0 && zspeed != 0) { self->AddZ(-BobSin(weaveZ) * zdist); weaveZ = (weaveZ + zspeed) & 63; self->AddZ(BobSin(weaveZ) * zdist); self->WeaveIndexZ = weaveZ; } } DEFINE_ACTION_FUNCTION(AActor, A_Weave) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT (xspeed); PARAM_INT (yspeed); PARAM_FLOAT (xdist); PARAM_FLOAT (ydist); A_Weave(self, xspeed, yspeed, xdist, ydist); return 0; } //=========================================================================== // // A_LineEffect // // This allows linedef effects to be activated inside deh frames. // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_LineEffect) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT_DEF(special); PARAM_INT_DEF(tag); line_t junk; maplinedef_t oldjunk; bool res = false; if (!(self->flags6 & MF6_LINEDONE)) // Unless already used up { if ((oldjunk.special = special)) // Linedef type { oldjunk.tag = tag; // Sector tag for linedef P_TranslateLineDef(&junk, &oldjunk); // Turn into native type res = !!P_ExecuteSpecial(junk.special, NULL, self, false, junk.args[0], junk.args[1], junk.args[2], junk.args[3], junk.args[4]); if (res && !(junk.flags & ML_REPEAT_SPECIAL)) // If only once, self->flags6 |= MF6_LINEDONE; // no more for this thing } } ACTION_RETURN_BOOL(res); } //========================================================================== // // A Wolf3D-style attack codepointer // //========================================================================== enum WolfAttackFlags { WAF_NORANDOM = 1, WAF_USEPUFF = 2, }; DEFINE_ACTION_FUNCTION(AActor, A_WolfAttack) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT_DEF (flags) PARAM_SOUND_DEF (sound) PARAM_FLOAT_DEF (snipe) PARAM_INT_DEF (maxdamage) PARAM_INT_DEF (blocksize) PARAM_INT_DEF (pointblank) PARAM_INT_DEF (longrange) PARAM_FLOAT_DEF (runspeed) PARAM_CLASS_DEF (pufftype, AActor) if (!self->target) return 0; // Enemy can't see target if (!P_CheckSight(self, self->target)) return 0; A_FaceTarget (self); // Target can dodge if it can see enemy DAngle angle = absangle(self->target->Angles.Yaw, self->target->AngleTo(self)); bool dodge = (P_CheckSight(self->target, self) && angle < 30. * 256. / 360.); // 30 byteangles ~ 21° // Distance check is simplistic DVector2 vec = self->Vec2To(self->target); double dx = fabs (vec.X); double dy = fabs (vec.Y); double dist = dx > dy ? dx : dy; // Some enemies are more precise dist *= snipe; // Convert distance into integer number of blocks int idist = int(dist / blocksize); // Now for the speed accuracy thingie double speed = self->target->Vel.LengthSquared(); int hitchance = speed < runspeed ? 256 : 160; // Distance accuracy (factoring dodge) hitchance -= idist * (dodge ? 16 : 8); // While we're here, we may as well do something for this: if (self->target->flags & MF_SHADOW) { hitchance >>= 2; } // The attack itself if (pr_cabullet() < hitchance) { // Compute position for spawning blood/puff DAngle angle = self->target->AngleTo(self); DVector3 BloodPos = self->target->Vec3Angle(self->target->radius, angle, self->target->Height/2); int damage = flags & WAF_NORANDOM ? maxdamage : (1 + (pr_cabullet() % maxdamage)); if (dist >= pointblank) damage >>= 1; if (dist >= longrange) damage >>= 1; FName mod = NAME_None; bool spawnblood = !((self->target->flags & MF_NOBLOOD) || (self->target->flags2 & (MF2_INVULNERABLE|MF2_DORMANT))); if (flags & WAF_USEPUFF && pufftype) { AActor *dpuff = GetDefaultByType(pufftype->GetReplacement()); mod = dpuff->DamageType; if (dpuff->flags2 & MF2_THRUGHOST && self->target->flags3 & MF3_GHOST) damage = 0; if ((0 && dpuff->flags3 & MF3_PUFFONACTORS) || !spawnblood) { spawnblood = false; P_SpawnPuff(self, pufftype, BloodPos, angle, angle, 0); } } else if (self->target->flags3 & MF3_GHOST) damage >>= 2; if (damage) { int newdam = P_DamageMobj(self->target, self, self, damage, mod, DMG_THRUSTLESS); if (spawnblood) { P_SpawnBlood(BloodPos, angle, newdam > 0 ? newdam : damage, self->target); P_TraceBleed(newdam > 0 ? newdam : damage, self->target, self); } } } // And finally, let's play the sound S_Sound (self, CHAN_WEAPON, sound, 1, ATTN_NORM); return 0; } //========================================================================== // // A_Warp // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_Warp) { PARAM_ACTION_PROLOGUE(AActor); PARAM_INT(destination_selector); PARAM_FLOAT_DEF(xofs) PARAM_FLOAT_DEF(yofs) PARAM_FLOAT_DEF(zofs) PARAM_ANGLE_DEF(angle) PARAM_INT_DEF(flags) PARAM_STATE_DEF(success_state) PARAM_FLOAT_DEF(heightoffset) PARAM_FLOAT_DEF(radiusoffset) PARAM_ANGLE_DEF(pitch) AActor *reference; // A_Teleport and A_Warp were the only codepointers that can state jump // *AND* have a meaningful inventory state chain result. Grrr. if (numret > 1) { ret[1].SetInt(false); numret = 2; } if (numret > 0) { ret[0].SetPointer(NULL, ATAG_STATE); } if ((flags & WARPF_USETID)) { reference = SingleActorFromTID(destination_selector, self); } else { reference = COPY_AAPTR(self, destination_selector); } //If there is no actor to warp to, fail. if (!reference) { return numret; } if (P_Thing_Warp(self, reference, xofs, yofs, zofs, angle, flags, heightoffset, radiusoffset, pitch)) { if (success_state) { // Jumps should never set the result for inventory state chains! // in this case, you have the statejump to help you handle all the success anyway. if (numret > 0) { ret[0].SetPointer(success_state, ATAG_STATE); } } else if (numret > 1) { ret[1].SetInt(true); } } return numret; } static bool DoCheckSpecies(AActor *mo, FName filterSpecies, bool exclude) { FName actorSpecies = mo->GetSpecies(); if (filterSpecies == NAME_None) return true; return exclude ? (actorSpecies != filterSpecies) : (actorSpecies == filterSpecies); } static bool DoCheckClass(AActor *mo, PClassActor *filterClass, bool exclude) { const PClass *actorClass = mo->GetClass(); if (filterClass == NULL) return true; return exclude ? (actorClass != filterClass) : (actorClass == filterClass); } //========================================================================== // // A_RadiusGive(item, distance, flags, amount, filter, species) // // Uses code roughly similar to A_Explode (but without all the compatibility // baggage and damage computation code) to give an item to all eligible mobjs // in range. // //========================================================================== enum RadiusGiveFlags { RGF_GIVESELF = 1 << 0, RGF_PLAYERS = 1 << 1, RGF_MONSTERS = 1 << 2, RGF_OBJECTS = 1 << 3, RGF_VOODOO = 1 << 4, RGF_CORPSES = 1 << 5, RGF_NOTARGET = 1 << 6, RGF_NOTRACER = 1 << 7, RGF_NOMASTER = 1 << 8, RGF_CUBE = 1 << 9, RGF_NOSIGHT = 1 << 10, RGF_MISSILES = 1 << 11, RGF_INCLUSIVE = 1 << 12, RGF_ITEMS = 1 << 13, RGF_KILLED = 1 << 14, RGF_EXFILTER = 1 << 15, RGF_EXSPECIES = 1 << 16, RGF_EITHER = 1 << 17, RGF_MASK = /*2111*/ RGF_GIVESELF | RGF_PLAYERS | RGF_MONSTERS | RGF_OBJECTS | RGF_VOODOO | RGF_CORPSES | RGF_KILLED | RGF_MISSILES | RGF_ITEMS, }; static bool DoRadiusGive(AActor *self, AActor *thing, PClassActor *item, int amount, double distance, int flags, PClassActor *filter, FName species, double mindist) { bool doPass = false; // Always allow self to give, no matter what other flags are specified. Otherwise, not at all. if (thing == self) { if (!(flags & RGF_GIVESELF)) return false; doPass = true; } else if (thing->flags & MF_MISSILE) { if (!(flags & RGF_MISSILES)) return false; doPass = true; } else if (((flags & RGF_ITEMS) && thing->IsKindOf(RUNTIME_CLASS(AInventory))) || ((flags & RGF_CORPSES) && thing->flags & MF_CORPSE) || ((flags & RGF_KILLED) && thing->flags6 & MF6_KILLED)) { doPass = true; } else if ((flags & (RGF_MONSTERS | RGF_OBJECTS | RGF_PLAYERS | RGF_VOODOO))) { // Make sure it's alive as we're not looking for corpses or killed here. if (!doPass && thing->health > 0) { if (thing->player != nullptr) { if (((flags & RGF_PLAYERS) && (thing->player->mo == thing)) || ((flags & RGF_VOODOO) && (thing->player->mo != thing))) { doPass = true; } } else { if (((flags & RGF_MONSTERS) && (thing->flags3 & MF3_ISMONSTER)) || ((flags & RGF_OBJECTS) && (!(thing->flags3 & MF3_ISMONSTER)) && (thing->flags & MF_SHOOTABLE || thing->flags6 & MF6_VULNERABLE))) { doPass = true; } } } } // Nothing matched up so don't bother with the rest. if (!doPass) return false; //[MC] Check for a filter, species, and the related exfilter/expecies/either flag(s). bool filterpass = DoCheckClass(thing, filter, !!(flags & RGF_EXFILTER)), speciespass = DoCheckSpecies(thing, species, !!(flags & RGF_EXSPECIES)); if ((flags & RGF_EITHER) ? (!(filterpass || speciespass)) : (!(filterpass && speciespass))) { if (thing != self) //Don't let filter and species obstruct RGF_GIVESELF. return false; } if ((thing != self) && (flags & (RGF_NOTARGET | RGF_NOMASTER | RGF_NOTRACER))) { //Check for target, master, and tracer flagging. bool targetPass = true; bool masterPass = true; bool tracerPass = true; bool ptrPass = false; if ((thing == self->target) && (flags & RGF_NOTARGET)) targetPass = false; if ((thing == self->master) && (flags & RGF_NOMASTER)) masterPass = false; if ((thing == self->tracer) && (flags & RGF_NOTRACER)) tracerPass = false; ptrPass = (flags & RGF_INCLUSIVE) ? (targetPass || masterPass || tracerPass) : (targetPass && masterPass && tracerPass); //We should not care about what the actor is here. It's safe to abort this actor. if (!ptrPass) return false; } if (doPass) { DVector3 diff = self->Vec3To(thing); diff.Z += thing->Height *0.5; if (flags & RGF_CUBE) { // check if inside a cube double dx = fabs(diff.X); double dy = fabs(diff.Y); double dz = fabs(diff.Z); if ((dx > distance || dy > distance || dz > distance) || (mindist && (dx < mindist && dy < mindist && dz < mindist))) { return false; } } else { // check if inside a sphere double lengthsquared = diff.LengthSquared(); if (lengthsquared > distance*distance || (mindist && (lengthsquared < mindist*mindist))) { return false; } } if ((flags & RGF_NOSIGHT) || P_CheckSight(thing, self, SF_IGNOREVISIBILITY | SF_IGNOREWATERBOUNDARY)) { // OK to give; target is in direct path, or the monster doesn't care about it being in line of sight. AInventory *gift = static_cast(Spawn(item)); if (gift->IsKindOf(PClass::FindActor(NAME_Health))) { gift->Amount *= amount; } else { gift->Amount = amount; } gift->flags |= MF_DROPPED; gift->ClearCounters(); if (!gift->CallTryPickup(thing)) { gift->Destroy(); return false; } else { return true; } } } return false; } DEFINE_ACTION_FUNCTION(AActor, A_RadiusGive) { PARAM_SELF_PROLOGUE(AActor); PARAM_CLASS (item, AInventory); PARAM_FLOAT (distance); PARAM_INT (flags); PARAM_INT_DEF (amount); PARAM_CLASS_DEF (filter, AActor); PARAM_NAME_DEF (species); PARAM_FLOAT_DEF (mindist); PARAM_INT_DEF (limit); // We need a valid item, valid targets, and a valid range if (item == nullptr || (flags & RGF_MASK) == 0 || !flags || distance <= 0 || mindist >= distance) { ACTION_RETURN_INT(0); } bool unlimited = (limit <= 0); if (amount == 0) { amount = 1; } AActor *thing; int given = 0; if (flags & RGF_MISSILES) { TThinkerIterator it; while ((thing = it.Next()) && ((unlimited) || (given < limit))) { given += DoRadiusGive(self, thing, item, amount, distance, flags, filter, species, mindist); } } else { FPortalGroupArray check(FPortalGroupArray::PGA_Full3d); double mid = self->Center(); FMultiBlockThingsIterator it(check, self->X(), self->Y(), mid-distance, mid+distance, distance, false, self->Sector); FMultiBlockThingsIterator::CheckResult cres; while ((it.Next(&cres)) && ((unlimited) || (given < limit))) { given += DoRadiusGive(self, cres.thing, item, amount, distance, flags, filter, species, mindist); } } ACTION_RETURN_INT(given); } //========================================================================== // // A_SetTics // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_SetTics) { PARAM_ACTION_PROLOGUE(AActor); PARAM_INT(tics_to_set); if (ACTION_CALL_FROM_PSPRITE()) { DPSprite *pspr = self->player->FindPSprite(stateinfo->mPSPIndex); if (pspr != nullptr) { pspr->Tics = tics_to_set; return 0; } } else if (ACTION_CALL_FROM_ACTOR()) { // Just set tics for self. self->tics = tics_to_set; } // for inventory state chains this needs to be ignored. return 0; } //========================================================================== // // A_DropItem // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_DropItem) { PARAM_SELF_PROLOGUE(AActor); PARAM_CLASS (spawntype, AActor); PARAM_INT_DEF(amount); PARAM_INT_DEF(chance); P_DropItem(self, spawntype, amount, chance); return 0; } //=========================================================================== // // Common A_Damage handler // // A_Damage* (int amount, str damagetype, int flags, str filter, str species) // Damages the specified actor by the specified amount. Negative values heal. // Flags: See below. // Filter: Specified actor is the only type allowed to be affected. // Species: Specified species is the only type allowed to be affected. // // Examples: // A_Damage(20,"Normal",DMSS_FOILINVUL,0,"DemonicSpecies") <--Only actors // with a species "DemonicSpecies" will be affected. Use 0 to not filter by actor. // //=========================================================================== enum DMSS { DMSS_FOILINVUL = 1, //Foil invulnerability DMSS_AFFECTARMOR = 2, //Make it affect armor DMSS_KILL = 4, //Damages them for their current health DMSS_NOFACTOR = 8, //Ignore DamageFactors DMSS_FOILBUDDHA = 16, //Can kill actors with Buddha flag, except the player. DMSS_NOPROTECT = 32, //Ignores PowerProtection entirely DMSS_EXFILTER = 64, //Changes filter into a blacklisted class instead of whitelisted. DMSS_EXSPECIES = 128, // ^ but with species instead. DMSS_EITHER = 256, //Allow either type or species to be affected. DMSS_INFLICTORDMGTYPE = 512, //Ignore the passed damagetype and use the inflictor's instead. }; static void DoDamage(AActor *dmgtarget, AActor *inflictor, AActor *source, int amount, FName DamageType, int flags, PClassActor *filter, FName species) { bool filterpass = DoCheckClass(dmgtarget, filter, !!(flags & DMSS_EXFILTER)), speciespass = DoCheckSpecies(dmgtarget, species, !!(flags & DMSS_EXSPECIES)); if ((flags & DMSS_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass)) { int dmgFlags = 0; if (flags & DMSS_FOILINVUL) dmgFlags |= DMG_FOILINVUL; if (flags & DMSS_FOILBUDDHA) dmgFlags |= DMG_FOILBUDDHA; if (flags & (DMSS_KILL | DMSS_NOFACTOR)) //Kill implies NoFactor dmgFlags |= DMG_NO_FACTOR; if (!(flags & DMSS_AFFECTARMOR) || (flags & DMSS_KILL)) //Kill overrides AffectArmor dmgFlags |= DMG_NO_ARMOR; if (flags & DMSS_KILL) //Kill adds the value of the damage done to it. Allows for more controlled extreme death types. amount += dmgtarget->health; if (flags & DMSS_NOPROTECT) //Ignore PowerProtection. dmgFlags |= DMG_NO_PROTECT; if (amount > 0) { //Should wind up passing them through just fine. if (flags & DMSS_INFLICTORDMGTYPE) DamageType = inflictor->DamageType; P_DamageMobj(dmgtarget, inflictor, source, amount, DamageType, dmgFlags); } else if (amount < 0) { amount = -amount; P_GiveBody(dmgtarget, amount); } } } //=========================================================================== // // // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_DamageSelf) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT (amount); PARAM_NAME_DEF (damagetype) PARAM_INT_DEF (flags) PARAM_CLASS_DEF (filter, AActor) PARAM_NAME_DEF (species) PARAM_INT_DEF (src) PARAM_INT_DEF (inflict) AActor *source = COPY_AAPTR(self, src); AActor *inflictor = COPY_AAPTR(self, inflict); DoDamage(self, inflictor, source, amount, damagetype, flags, filter, species); return 0; } //=========================================================================== // // // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_DamageTarget) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT (amount); PARAM_NAME_DEF (damagetype) PARAM_INT_DEF (flags) PARAM_CLASS_DEF (filter, AActor) PARAM_NAME_DEF (species) PARAM_INT_DEF (src) PARAM_INT_DEF (inflict) AActor *source = COPY_AAPTR(self, src); AActor *inflictor = COPY_AAPTR(self, inflict); if (self->target != NULL) DoDamage(self->target, inflictor, source, amount, damagetype, flags, filter, species); return 0; } //=========================================================================== // // // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_DamageTracer) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT (amount); PARAM_NAME_DEF (damagetype) PARAM_INT_DEF (flags) PARAM_CLASS_DEF (filter, AActor) PARAM_NAME_DEF (species) PARAM_INT_DEF (src) PARAM_INT_DEF (inflict) AActor *source = COPY_AAPTR(self, src); AActor *inflictor = COPY_AAPTR(self, inflict); if (self->tracer != NULL) DoDamage(self->tracer, inflictor, source, amount, damagetype, flags, filter, species); return 0; } //=========================================================================== // // // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_DamageMaster) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT (amount); PARAM_NAME_DEF (damagetype) PARAM_INT_DEF (flags) PARAM_CLASS_DEF (filter, AActor) PARAM_NAME_DEF (species) PARAM_INT_DEF (src) PARAM_INT_DEF (inflict) AActor *source = COPY_AAPTR(self, src); AActor *inflictor = COPY_AAPTR(self, inflict); if (self->master != NULL) DoDamage(self->master, inflictor, source, amount, damagetype, flags, filter, species); return 0; } //=========================================================================== // // // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_DamageChildren) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT (amount); PARAM_NAME_DEF (damagetype) PARAM_INT_DEF (flags) PARAM_CLASS_DEF (filter, AActor) PARAM_NAME_DEF (species) PARAM_INT_DEF (src) PARAM_INT_DEF (inflict) AActor *source = COPY_AAPTR(self, src); AActor *inflictor = COPY_AAPTR(self, inflict); TThinkerIterator it; AActor *mo; while ( (mo = it.Next()) ) { if (mo->master == self) DoDamage(mo, inflictor, source, amount, damagetype, flags, filter, species); } return 0; } //=========================================================================== // // // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_DamageSiblings) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT (amount); PARAM_NAME_DEF (damagetype) PARAM_INT_DEF (flags) PARAM_CLASS_DEF (filter, AActor) PARAM_NAME_DEF (species) PARAM_INT_DEF (src) PARAM_INT_DEF (inflict) AActor *source = COPY_AAPTR(self, src); AActor *inflictor = COPY_AAPTR(self, inflict); TThinkerIterator it; AActor *mo; if (self->master != NULL) { while ((mo = it.Next())) { if (mo->master == self->master && mo != self) DoDamage(mo, inflictor, source, amount, damagetype, flags, filter, species); } } return 0; } //=========================================================================== // // A_Kill*(damagetype, int flags) // //=========================================================================== enum KILS { KILS_FOILINVUL = 1 << 0, KILS_KILLMISSILES = 1 << 1, KILS_NOMONSTERS = 1 << 2, KILS_FOILBUDDHA = 1 << 3, KILS_EXFILTER = 1 << 4, KILS_EXSPECIES = 1 << 5, KILS_EITHER = 1 << 6, }; static void DoKill(AActor *killtarget, AActor *inflictor, AActor *source, FName damagetype, int flags, PClassActor *filter, FName species) { bool filterpass = DoCheckClass(killtarget, filter, !!(flags & KILS_EXFILTER)), speciespass = DoCheckSpecies(killtarget, species, !!(flags & KILS_EXSPECIES)); if ((flags & KILS_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass)) //Check this first. I think it'll save the engine a lot more time this way. { int dmgFlags = DMG_NO_ARMOR | DMG_NO_FACTOR; if (KILS_FOILINVUL) dmgFlags |= DMG_FOILINVUL; if (KILS_FOILBUDDHA) dmgFlags |= DMG_FOILBUDDHA; if ((killtarget->flags & MF_MISSILE) && (flags & KILS_KILLMISSILES)) { //[MC] Now that missiles can set masters, lets put in a check to properly destroy projectiles. BUT FIRST! New feature~! //Check to see if it's invulnerable. Disregarded if foilinvul is on, but never works on a missile with NODAMAGE //since that's the whole point of it. if ((!(killtarget->flags2 & MF2_INVULNERABLE) || (flags & KILS_FOILINVUL)) && (!(killtarget->flags7 & MF7_BUDDHA) || (flags & KILS_FOILBUDDHA)) && !(killtarget->flags5 & MF5_NODAMAGE)) { P_ExplodeMissile(killtarget, NULL, NULL); } } if (!(flags & KILS_NOMONSTERS)) { P_DamageMobj(killtarget, inflictor, source, killtarget->health, damagetype, dmgFlags); } } } //=========================================================================== // // A_KillTarget(damagetype, int flags) // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_KillTarget) { PARAM_SELF_PROLOGUE(AActor); PARAM_NAME_DEF (damagetype) PARAM_INT_DEF (flags) PARAM_CLASS_DEF (filter, AActor) PARAM_NAME_DEF (species) PARAM_INT_DEF (src) PARAM_INT_DEF (inflict) AActor *source = COPY_AAPTR(self, src); AActor *inflictor = COPY_AAPTR(self, inflict); if (self->target != NULL) DoKill(self->target, inflictor, source, damagetype, flags, filter, species); return 0; } //=========================================================================== // // A_KillTracer(damagetype, int flags) // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_KillTracer) { PARAM_SELF_PROLOGUE(AActor); PARAM_NAME_DEF (damagetype) PARAM_INT_DEF (flags) PARAM_CLASS_DEF (filter, AActor) PARAM_NAME_DEF (species) PARAM_INT_DEF (src) PARAM_INT_DEF (inflict) AActor *source = COPY_AAPTR(self, src); AActor *inflictor = COPY_AAPTR(self, inflict); if (self->tracer != NULL) DoKill(self->tracer, inflictor, source, damagetype, flags, filter, species); return 0; } //=========================================================================== // // A_KillMaster(damagetype, int flags) // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_KillMaster) { PARAM_SELF_PROLOGUE(AActor); PARAM_NAME_DEF (damagetype) PARAM_INT_DEF (flags) PARAM_CLASS_DEF (filter, AActor) PARAM_NAME_DEF (species) PARAM_INT_DEF (src) PARAM_INT_DEF (inflict) AActor *source = COPY_AAPTR(self, src); AActor *inflictor = COPY_AAPTR(self, inflict); if (self->master != NULL) DoKill(self->master, inflictor, source, damagetype, flags, filter, species); return 0; } //=========================================================================== // // A_KillChildren(damagetype, int flags) // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_KillChildren) { PARAM_SELF_PROLOGUE(AActor); PARAM_NAME_DEF (damagetype) PARAM_INT_DEF (flags) PARAM_CLASS_DEF (filter, AActor) PARAM_NAME_DEF (species) PARAM_INT_DEF (src) PARAM_INT_DEF (inflict) AActor *source = COPY_AAPTR(self, src); AActor *inflictor = COPY_AAPTR(self, inflict); TThinkerIterator it; AActor *mo; while ( (mo = it.Next()) ) { if (mo->master == self) { DoKill(mo, inflictor, source, damagetype, flags, filter, species); } } return 0; } //=========================================================================== // // A_KillSiblings(damagetype, int flags) // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_KillSiblings) { PARAM_SELF_PROLOGUE(AActor); PARAM_NAME_DEF (damagetype) PARAM_INT_DEF (flags) PARAM_CLASS_DEF (filter, AActor) PARAM_NAME_DEF (species) PARAM_INT_DEF (src) PARAM_INT_DEF (inflict) AActor *source = COPY_AAPTR(self, src); AActor *inflictor = COPY_AAPTR(self, inflict); TThinkerIterator it; AActor *mo; if (self->master != NULL) { while ( (mo = it.Next()) ) { if (mo->master == self->master && mo != self) { DoKill(mo, inflictor, source, damagetype, flags, filter, species); } } } return 0; } //=========================================================================== // // DoRemove // //=========================================================================== enum RMVF_flags { RMVF_MISSILES = 1 << 0, RMVF_NOMONSTERS = 1 << 1, RMVF_MISC = 1 << 2, RMVF_EVERYTHING = 1 << 3, RMVF_EXFILTER = 1 << 4, RMVF_EXSPECIES = 1 << 5, RMVF_EITHER = 1 << 6, }; static void DoRemove(AActor *removetarget, int flags, PClassActor *filter, FName species) { bool filterpass = DoCheckClass(removetarget, filter, !!(flags & RMVF_EXFILTER)), speciespass = DoCheckSpecies(removetarget, species, !!(flags & RMVF_EXSPECIES)); if ((flags & RMVF_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass)) { if ((flags & RMVF_EVERYTHING)) { P_RemoveThing(removetarget); } if ((flags & RMVF_MISC) && !((removetarget->flags3 & MF3_ISMONSTER) && (removetarget->flags & MF_MISSILE))) { P_RemoveThing(removetarget); } if ((removetarget->flags3 & MF3_ISMONSTER) && !(flags & RMVF_NOMONSTERS)) { P_RemoveThing(removetarget); } if ((removetarget->flags & MF_MISSILE) && (flags & RMVF_MISSILES)) { P_RemoveThing(removetarget); } } } //=========================================================================== // // A_RemoveTarget // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_RemoveTarget) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT_DEF(flags); PARAM_CLASS_DEF(filter, AActor); PARAM_NAME_DEF(species); if (self->target != NULL) { DoRemove(self->target, flags, filter, species); } return 0; } //=========================================================================== // // A_RemoveTracer // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_RemoveTracer) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT_DEF(flags); PARAM_CLASS_DEF(filter, AActor); PARAM_NAME_DEF(species); if (self->tracer != NULL) { DoRemove(self->tracer, flags, filter, species); } return 0; } //=========================================================================== // // A_RemoveMaster // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_RemoveMaster) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT_DEF(flags); PARAM_CLASS_DEF(filter, AActor); PARAM_NAME_DEF(species); if (self->master != NULL) { DoRemove(self->master, flags, filter, species); } return 0; } //=========================================================================== // // A_RemoveChildren // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_RemoveChildren) { PARAM_SELF_PROLOGUE(AActor); PARAM_BOOL_DEF(removeall); PARAM_INT_DEF(flags); PARAM_CLASS_DEF(filter, AActor); PARAM_NAME_DEF(species); TThinkerIterator it; AActor *mo; while ((mo = it.Next()) != NULL) { if (mo->master == self && (mo->health <= 0 || removeall)) { DoRemove(mo, flags, filter, species); } } return 0; } //=========================================================================== // // A_RemoveSiblings // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_RemoveSiblings) { PARAM_SELF_PROLOGUE(AActor); PARAM_BOOL_DEF(removeall); PARAM_INT_DEF(flags); PARAM_CLASS_DEF(filter, AActor); PARAM_NAME_DEF(species); TThinkerIterator it; AActor *mo; if (self->master != NULL) { while ((mo = it.Next()) != NULL) { if (mo->master == self->master && mo != self && (mo->health <= 0 || removeall)) { DoRemove(mo, flags, filter, species); } } } return 0; } //=========================================================================== // // A_Remove // //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_Remove) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT(removee); PARAM_INT_DEF(flags); PARAM_CLASS_DEF(filter, AActor); PARAM_NAME_DEF(species); AActor *reference = COPY_AAPTR(self, removee); if (reference != NULL) { DoRemove(reference, flags, filter, species); } return 0; } //=========================================================================== // // A_SetTeleFog // // Sets the teleport fog(s) for the calling actor. // Takes a name of the classes for the source and destination. //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_SetTeleFog) { PARAM_SELF_PROLOGUE(AActor); PARAM_CLASS(oldpos, AActor); PARAM_CLASS(newpos, AActor); self->TeleFogSourceType = oldpos; self->TeleFogDestType = newpos; return 0; } //=========================================================================== // // A_SwapTeleFog // // Switches the source and dest telefogs around. //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_SwapTeleFog) { PARAM_SELF_PROLOGUE(AActor); if ((self->TeleFogSourceType != self->TeleFogDestType)) //Does nothing if they're the same. { PClassActor *temp = self->TeleFogSourceType; self->TeleFogSourceType = self->TeleFogDestType; self->TeleFogDestType = temp; } return 0; } //=========================================================================== // // A_SetFloatBobPhase // // Changes the FloatBobPhase of the actor. //=========================================================================== //=========================================================================== // A_SetHealth // // Changes the health of the actor. // Takes a pointer as well. //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_SetHealth) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT (health); PARAM_INT_DEF (ptr); AActor *mobj = COPY_AAPTR(self, ptr); if (!mobj) { return 0; } player_t *player = mobj->player; if (player) { if (health <= 0) player->mo->health = mobj->health = player->health = 1; //Copied from the buddha cheat. else player->mo->health = mobj->health = player->health = health; } else if (mobj) { if (health <= 0) mobj->health = 1; else mobj->health = health; } return 0; } //=========================================================================== // A_ResetHealth // // Resets the health of the actor to default, except if their dead. // Takes a pointer. //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_ResetHealth) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT_DEF(ptr); AActor *mobj = COPY_AAPTR(self, ptr); if (!mobj) { return 0; } player_t *player = mobj->player; if (player && (player->mo->health > 0)) { player->health = player->mo->health = player->mo->GetDefault()->health; //Copied from the resurrect cheat. } else if (mobj && (mobj->health > 0)) { mobj->health = mobj->SpawnHealth(); } return 0; } //=========================================================================== // A_SetSpecies(str species, ptr) // // Sets the species of the calling actor('s pointer). //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_SetSpecies) { PARAM_SELF_PROLOGUE(AActor); PARAM_NAME(species); PARAM_INT_DEF(ptr); AActor *mobj = COPY_AAPTR(self, ptr); if (!mobj) { return 0; } mobj->Species = species; return 0; } //=========================================================================== // // A_SetChaseThreshold(int threshold, bool def, int ptr) // // Sets the current chase threshold of the actor (pointer). If def is true, // changes the default threshold which the actor resets to once it switches // targets and doesn't have the +QUICKTORETALIATE flag. //=========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_SetChaseThreshold) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT(threshold); PARAM_BOOL_DEF(def); PARAM_INT_DEF(ptr); AActor *mobj = COPY_AAPTR(self, ptr); if (!mobj) { return 0; } if (def) mobj->DefThreshold = (threshold >= 0) ? threshold : 0; else mobj->threshold = (threshold >= 0) ? threshold : 0; return 0; } //========================================================================== // // A_CheckProximity(jump, classname, distance, count, flags, ptr) // // Checks to see if a certain actor class is close to the // actor/pointer within distance, in numbers. //========================================================================== DEFINE_ACTION_FUNCTION(AActor, CheckProximity) { PARAM_SELF_PROLOGUE(AActor); PARAM_CLASS(classname, AActor); PARAM_FLOAT(distance); PARAM_INT_DEF(count); PARAM_INT_DEF(flags); PARAM_INT_DEF(ptr); ACTION_RETURN_BOOL(!!P_Thing_CheckProximity(self, classname, distance, count, flags, ptr)); } /*=========================================================================== A_CheckBlock (state block, int flags, int ptr) Checks if something is blocking the actor('s pointer) 'ptr'. The SET pointer flags only affect the caller, not the pointer. ===========================================================================*/ enum CBF { CBF_NOLINES = 1 << 0, //Don't check lines. CBF_SETTARGET = 1 << 1, //Sets the caller/pointer's target to the actor blocking it. Actors only. CBF_SETMASTER = 1 << 2, //^ but with master. CBF_SETTRACER = 1 << 3, //^ but with tracer. CBF_SETONPTR = 1 << 4, //Sets the pointer change on the actor doing the checking instead of self. CBF_DROPOFF = 1 << 5, //Check for dropoffs. CBF_NOACTORS = 1 << 6, //Don't check actors. CBF_ABSOLUTEPOS = 1 << 7, //Absolute position for offsets. CBF_ABSOLUTEANGLE = 1 << 8, //Absolute angle for offsets. }; DEFINE_ACTION_FUNCTION(AActor, CheckBlock) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT_DEF(flags) PARAM_INT_DEF(ptr) PARAM_FLOAT_DEF(xofs) PARAM_FLOAT_DEF(yofs) PARAM_FLOAT_DEF(zofs) PARAM_ANGLE_DEF(angle) AActor *mobj = COPY_AAPTR(self, ptr); //Needs at least one state jump to work. if (!mobj) { ACTION_RETURN_BOOL(false); } if (!(flags & CBF_ABSOLUTEANGLE)) { angle += self->Angles.Yaw; } DVector3 oldpos = mobj->Pos(); DVector3 pos; if (flags & CBF_ABSOLUTEPOS) { pos = { xofs, yofs, zofs }; } else { double s = angle.Sin(); double c = angle.Cos(); pos = mobj->Vec3Offset(xofs * c + yofs * s, xofs * s - yofs * c, zofs); } // Next, try checking the position based on the sensitivity desired. // If checking for dropoffs, set the z so we can have maximum flexibility. // Otherwise, set origin and set it back after testing. bool checker = false; if (flags & CBF_DROPOFF) { // Unfortunately, whenever P_CheckMove returned false, that means it could // ignore a variety of flags mainly because of P_CheckPosition. This // results in picking up false positives due to actors or lines being in the way // when they clearly should not be. int fpass = PCM_DROPOFF; if (flags & CBF_NOACTORS) fpass |= PCM_NOACTORS; if (flags & CBF_NOLINES) fpass |= PCM_NOLINES; mobj->SetZ(pos.Z); checker = P_CheckMove(mobj, pos, fpass); mobj->SetZ(oldpos.Z); } else { mobj->SetOrigin(pos, true); checker = P_TestMobjLocation(mobj); mobj->SetOrigin(oldpos, true); } if (checker) { ACTION_RETURN_BOOL(false); } if (mobj->BlockingMobj) { AActor *setter = (flags & CBF_SETONPTR) ? mobj : self; if (setter) { if (flags & CBF_SETTARGET) setter->target = mobj->BlockingMobj; if (flags & CBF_SETMASTER) setter->master = mobj->BlockingMobj; if (flags & CBF_SETTRACER) setter->tracer = mobj->BlockingMobj; } } //[MC] I don't know why I let myself be persuaded not to include a flag. //If an actor is loaded with pointers, they don't really have any options to spare. //Also, fail if a dropoff or a step is too great to pass over when checking for dropoffs. ACTION_RETURN_BOOL((!(flags & CBF_NOACTORS) && (mobj->BlockingMobj)) || (!(flags & CBF_NOLINES) && mobj->BlockingLine != NULL) || ((flags & CBF_DROPOFF) && !checker)); } //=========================================================================== // // A_FaceMovementDirection(angle offset, bool pitch, ptr) // // Sets the actor('s pointer) to face the direction of travel. //=========================================================================== enum FMDFlags { FMDF_NOPITCH = 1 << 0, FMDF_INTERPOLATE = 1 << 1, FMDF_NOANGLE = 1 << 2, }; DEFINE_ACTION_FUNCTION(AActor, A_FaceMovementDirection) { PARAM_SELF_PROLOGUE(AActor); PARAM_ANGLE_DEF(offset) PARAM_ANGLE_DEF(anglelimit) PARAM_ANGLE_DEF(pitchlimit) PARAM_INT_DEF(flags) PARAM_INT_DEF(ptr) AActor *mobj = COPY_AAPTR(self, ptr); //Need an actor. if (!mobj || ((flags & FMDF_NOPITCH) && (flags & FMDF_NOANGLE))) { ACTION_RETURN_BOOL(false); } //Don't bother calculating this if we don't have any horizontal movement. if (!(flags & FMDF_NOANGLE) && (mobj->Vel.X != 0 || mobj->Vel.Y != 0)) { DAngle current = mobj->Angles.Yaw; DAngle angle = mobj->Vel.Angle(); //Done because using anglelimit directly causes a signed/unsigned mismatch. //Code borrowed from A_Face*. if (anglelimit > 0) { DAngle delta = -deltaangle(current, angle); if (fabs(delta) > anglelimit) { if (delta < 0) { current += anglelimit + offset; } else if (delta > 0) { current -= anglelimit + offset; } mobj->SetAngle(current, !!(flags & FMDF_INTERPOLATE)); } else mobj->SetAngle(angle + offset, !!(flags & FMDF_INTERPOLATE)); } else mobj->SetAngle(angle + offset, !!(flags & FMDF_INTERPOLATE)); } if (!(flags & FMDF_NOPITCH)) { DAngle current = mobj->Angles.Pitch; const DVector2 velocity = mobj->Vel.XY(); DAngle pitch = -VecToAngle(velocity.Length(), mobj->Vel.Z); if (pitchlimit > 0) { DAngle pdelta = deltaangle(current, pitch); if (fabs(pdelta) > pitchlimit) { if (pdelta > 0) { current -= MIN(pitchlimit, pdelta); } else //if (pdelta < 0) { current += MIN(pitchlimit, -pdelta); } mobj->SetPitch(current, !!(flags & FMDF_INTERPOLATE)); } else { mobj->SetPitch(pitch, !!(flags & FMDF_INTERPOLATE)); } } else { mobj->SetPitch(pitch, !!(flags & FMDF_INTERPOLATE)); } } ACTION_RETURN_BOOL(true); } //========================================================================== // // A_CopySpriteFrame(from, to, flags) // // Copies the sprite and/or frame from one pointer to another. //========================================================================== enum CPSFFlags { CPSF_NOSPRITE = 1, CPSF_NOFRAME = 1 << 1, }; DEFINE_ACTION_FUNCTION(AActor, A_CopySpriteFrame) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT(from); PARAM_INT(to); PARAM_INT_DEF(flags); AActor *copyfrom = COPY_AAPTR(self, from); AActor *copyto = COPY_AAPTR(self, to); if (copyfrom == copyto || copyfrom == nullptr || copyto == nullptr || ((flags & CPSF_NOSPRITE) && (flags & CPSF_NOFRAME))) { ACTION_RETURN_BOOL(false); } if (!(flags & CPSF_NOSPRITE)) copyto->sprite = copyfrom->sprite; if (!(flags & CPSF_NOFRAME)) copyto->frame = copyfrom->frame; ACTION_RETURN_BOOL(true); } //========================================================================== // // A_SetMaskRotation(anglestart, angleend, pitchstart, pitchend, flags, ptr) // // Specifies how much to fake a sprite rotation. //========================================================================== enum VRFFlags { VRF_NOANGLESTART = 1, VRF_NOANGLEEND = 1 << 1, VRF_NOPITCHSTART = 1 << 2, VRF_NOPITCHEND = 1 << 3, }; DEFINE_ACTION_FUNCTION(AActor, A_SetVisibleRotation) { PARAM_SELF_PROLOGUE(AActor); PARAM_ANGLE_DEF(anglestart) PARAM_ANGLE_DEF(angleend) PARAM_ANGLE_DEF(pitchstart) PARAM_ANGLE_DEF(pitchend) PARAM_INT_DEF(flags) PARAM_INT_DEF(ptr) AActor *mobj = COPY_AAPTR(self, ptr); if (mobj == nullptr) { ACTION_RETURN_BOOL(false); } if (!(flags & VRF_NOANGLESTART)) { mobj->VisibleStartAngle = anglestart; } if (!(flags & VRF_NOANGLEEND)) { mobj->VisibleEndAngle = angleend; } if (!(flags & VRF_NOPITCHSTART)) { mobj->VisibleStartPitch = pitchstart; } if (!(flags & VRF_NOPITCHEND)) { mobj->VisibleEndPitch = pitchend; } ACTION_RETURN_BOOL(true); } //========================================================================== // // // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_SetTranslation) { PARAM_SELF_PROLOGUE(AActor); PARAM_NAME(trname); self->SetTranslation(trname); return 0; } //========================================================================== // // // //========================================================================== DEFINE_ACTION_FUNCTION(AActor, A_CheckTerrain) { PARAM_SELF_PROLOGUE(AActor); sector_t *sec = self->Sector; if (self->Z() == sec->floorplane.ZatPoint(self) && sec->PortalBlocksMovement(sector_t::floor)) { if (sec->special == Damage_InstantDeath) { P_DamageMobj(self, NULL, NULL, 999, NAME_InstantDeath); } else if (sec->special == Scroll_StrifeCurrent) { int anglespeed = tagManager.GetFirstSectorTag(sec) - 100; double speed = (anglespeed % 10) / 16.; DAngle an = (anglespeed / 10) * (360 / 8.); self->Thrust(an, speed); } } return 0; } DEFINE_ACTION_FUNCTION(AActor, A_SetSize) { PARAM_SELF_PROLOGUE(AActor); PARAM_FLOAT(newradius); PARAM_FLOAT_DEF(newheight); PARAM_BOOL_DEF(testpos); if (newradius < 0.) newradius = self->radius; if (newheight < 0.) newheight = self->Height; double oldradius = self->radius; double oldheight = self->Height; FLinkContext ctx; self->UnlinkFromWorld(&ctx); self->radius = newradius; self->Height = newheight; self->LinkToWorld(&ctx); if (testpos && !P_TestMobjLocation(self)) { self->UnlinkFromWorld(&ctx); self->radius = oldradius; self->Height = oldheight; self->LinkToWorld(&ctx); ACTION_RETURN_BOOL(false); } if (self->player && self->player->mo == self) { self->player->mo->FullHeight = newheight; } ACTION_RETURN_BOOL(true); }