DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Saw) { PARAM_ACTION_PROLOGUE; PARAM_SOUND_OPT (fullsound) { fullsound = "weapons/sawfull"; } PARAM_SOUND_OPT (hitsound) { hitsound = "weapons/sawhit"; } PARAM_INT_OPT (damage) { damage = 2; } PARAM_CLASS_OPT (pufftype, AActor) { pufftype = NULL; } PARAM_INT_OPT (flags) { flags = 0; } PARAM_FLOAT_OPT (range) { range = 0; } PARAM_ANGLE_OPT (spread_xy) { spread_xy = 2.8125; } PARAM_ANGLE_OPT (spread_z) { spread_z = 0.; } PARAM_FLOAT_OPT (lifesteal) { lifesteal = 0; } PARAM_INT_OPT (lifestealmax) { lifestealmax = 0; } PARAM_CLASS_OPT (armorbonustype, ABasicArmorBonus) { armorbonustype = NULL; } DAngle angle; DAngle slope; player_t *player; FTranslatedLineTarget t; int actualdamage; if (NULL == (player = self->player)) { return 0; } if (pufftype == NULL) { pufftype = PClass::FindActor(NAME_BulletPuff); } if (damage == 0) { damage = 2; } if (!(flags & SF_NORANDOM)) { damage *= (pr_saw()%10+1); } if (range == 0) { range = SAWRANGE; } angle = self->Angles.Yaw + spread_xy * (pr_saw.Random2() / 255.); slope = P_AimLineAttack (self, angle, range, &t) + spread_z * (pr_saw.Random2() / 255.); AWeapon *weapon = self->player->ReadyWeapon; if ((weapon != NULL) && !(flags & SF_NOUSEAMMO) && !(!t.linetarget && (flags & SF_NOUSEAMMOMISS)) && !(weapon->WeaponFlags & WIF_DEHAMMO) && ACTION_CALL_FROM_WEAPON()) { if (!weapon->DepleteAmmo (weapon->bAltFire)) return 0; } P_LineAttack (self, angle, range, slope, damage, NAME_Melee, pufftype, false, &t, &actualdamage); if (!t.linetarget) { if ((flags & SF_RANDOMLIGHTMISS) && (pr_saw() > 64)) { player->extralight = !player->extralight; } S_Sound (self, CHAN_WEAPON, fullsound, 1, ATTN_NORM); return 0; } if (flags & SF_RANDOMLIGHTHIT) { int randVal = pr_saw(); if (randVal < 64) { player->extralight = 0; } else if (randVal < 160) { player->extralight = 1; } else { player->extralight = 2; } } if (lifesteal && !(t.linetarget->flags5 & MF5_DONTDRAIN)) { if (flags & SF_STEALARMOR) { if (armorbonustype == NULL) { armorbonustype = dyn_cast<ABasicArmorBonus::MetaClass>(PClass::FindClass("ArmorBonus")); } if (armorbonustype != NULL) { assert(armorbonustype->IsDescendantOf (RUNTIME_CLASS(ABasicArmorBonus))); ABasicArmorBonus *armorbonus = static_cast<ABasicArmorBonus *>(Spawn(armorbonustype)); armorbonus->SaveAmount = int(armorbonus->SaveAmount * actualdamage * lifesteal); armorbonus->MaxSaveAmount = lifestealmax <= 0 ? armorbonus->MaxSaveAmount : lifestealmax; armorbonus->flags |= MF_DROPPED; armorbonus->ClearCounters(); if (!armorbonus->CallTryPickup (self)) { armorbonus->Destroy (); } } } else { P_GiveBody (self, int(actualdamage * lifesteal), lifestealmax); } } S_Sound (self, CHAN_WEAPON, hitsound, 1, ATTN_NORM); // turn to face target if (!(flags & SF_NOTURN)) { DAngle anglediff = deltaangle(self->Angles.Yaw, t.angleFromSource); if (anglediff < 0.0) { if (anglediff < -4.5) self->Angles.Yaw = angle + 90.0 / 21; else self->Angles.Yaw -= 4.5; } else { if (anglediff > 4.5) self->Angles.Yaw = angle - 90.0 / 21; else self->Angles.Yaw += 4.5; } } if (!(flags & SF_NOPULLIN)) self->flags |= MF_JUSTATTACKED; return 0; }
// [MC] Was part of P_Thing_Projectile, now its own function for use in ZScript. // Aims mobj at targ based on speed and targ's velocity. static void VelIntercept(AActor *targ, AActor *mobj, double speed, bool aimpitch = false, bool oldvel = false, bool leadtarget = true) { if (targ == nullptr || mobj == nullptr) return; DVector3 aim = mobj->Vec3To(targ); aim.Z += targ->Height / 2; if (leadtarget && speed > 0 && !targ->Vel.isZero()) { // Aiming at the target's position some time in the future // is basically just an application of the law of sines: // a/sin(A) = b/sin(B) // Thanks to all those on the notgod phorum for helping me // with the math. I don't think I would have thought of using // trig alone had I been left to solve it by myself. DVector3 tvel = targ->Vel; if (!(targ->flags & MF_NOGRAVITY) && targ->waterlevel < 3) { // If the target is subject to gravity and not underwater, // assume that it isn't moving vertically. Thanks to gravity, // even if we did consider the vertical component of the target's // velocity, we would still miss more often than not. tvel.Z = 0.0; if (targ->Vel.X == 0 && targ->Vel.Y == 0) { InterceptDefaultAim(mobj, targ, aim, speed); return; } } double dist = aim.Length(); double targspeed = tvel.Length(); double ydotx = -aim | tvel; double a = g_acos(clamp(ydotx / targspeed / dist, -1.0, 1.0)); double multiplier = double(pr_leadtarget.Random2())*0.1 / 255 + 1.1; double sinb = -clamp(targspeed*multiplier * g_sin(a) / speed, -1.0, 1.0); DVector3 prevel = mobj->Vel; // Use the cross product of two of the triangle's sides to get a // rotation vector. DVector3 rv(tvel ^ aim); // The vector must be normalized. rv.MakeUnit(); // Now combine the rotation vector with angle b to get a rotation matrix. DMatrix3x3 rm(rv, g_cos(g_asin(sinb)), sinb); // And multiply the original aim vector with the matrix to get a // new aim vector that leads the target. DVector3 aimvec = rm * aim; // And make the projectile follow that vector at the desired speed. mobj->Vel = aimvec * (speed / dist); mobj->AngleFromVel(); if (oldvel) { mobj->Vel = prevel; } if (aimpitch) // [MC] Ripped right out of A_FaceMovementDirection { const DVector2 velocity = mobj->Vel.XY(); mobj->Angles.Pitch = -VecToAngle(velocity.Length(), mobj->Vel.Z); } } else { InterceptDefaultAim(mobj, targ, aim, speed); } }
DEFINE_ACTION_FUNCTION(AActor, A_LichAttack) { int i; AActor *fire; AActor *baseFire; AActor *mo; AActor *target; int randAttack; static const int atkResolve1[] = { 50, 150 }; static const int atkResolve2[] = { 150, 200 }; int dist; // Ice ball (close 20% : far 60%) // Fire column (close 40% : far 20%) // Whirlwind (close 40% : far 20%) // Distance threshold = 8 cells target = self->target; if (target == NULL) { return; } A_FaceTarget (self); if (self->CheckMeleeRange ()) { int damage = pr_atk.HitDice (6); int newdam = P_DamageMobj (target, self, self, damage, NAME_Melee); P_TraceBleed (newdam > 0 ? newdam : damage, target, self); return; } dist = P_AproxDistance (self->x-target->x, self->y-target->y) > 8*64*FRACUNIT; randAttack = pr_atk (); if (randAttack < atkResolve1[dist]) { // Ice ball P_SpawnMissile (self, target, PClass::FindClass("HeadFX1")); S_Sound (self, CHAN_BODY, "ironlich/attack2", 1, ATTN_NORM); } else if (randAttack < atkResolve2[dist]) { // Fire column baseFire = P_SpawnMissile (self, target, PClass::FindClass("HeadFX3")); if (baseFire != NULL) { baseFire->SetState (baseFire->FindState("NoGrow")); for (i = 0; i < 5; i++) { fire = Spawn("HeadFX3", baseFire->x, baseFire->y, baseFire->z, ALLOW_REPLACE); if (i == 0) { S_Sound (self, CHAN_BODY, "ironlich/attack1", 1, ATTN_NORM); } fire->target = baseFire->target; fire->angle = baseFire->angle; fire->velx = baseFire->velx; fire->vely = baseFire->vely; fire->velz = baseFire->velz; fire->Damage = 0; fire->health = (i+1) * 2; P_CheckMissileSpawn (fire, self->radius); } } } else { // Whirlwind mo = P_SpawnMissile (self, target, RUNTIME_CLASS(AWhirlwind)); if (mo != NULL) { mo->z -= 32*FRACUNIT; mo->tracer = target; mo->special1 = 60; mo->special2 = 50; // Timer for active sound mo->health = 20*TICRATE; // Duration S_Sound (self, CHAN_BODY, "ironlich/attack3", 1, ATTN_NORM); } } }
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Saw) { angle_t angle; angle_t slope; player_t *player; AActor *linetarget; int actualdamage; ACTION_PARAM_START(11); ACTION_PARAM_SOUND(fullsound, 0); ACTION_PARAM_SOUND(hitsound, 1); ACTION_PARAM_INT(damage, 2); ACTION_PARAM_CLASS(pufftype, 3); ACTION_PARAM_INT(Flags, 4); ACTION_PARAM_FIXED(Range, 5); ACTION_PARAM_ANGLE(Spread_XY, 6); ACTION_PARAM_ANGLE(Spread_Z, 7); ACTION_PARAM_FIXED(LifeSteal, 8); ACTION_PARAM_INT(lifestealmax, 9); ACTION_PARAM_CLASS(armorbonustype, 10); if (NULL == (player = self->player)) { return; } if (pufftype == NULL) pufftype = PClass::FindClass(NAME_BulletPuff); if (damage == 0) damage = 2; if (!(Flags & SF_NORANDOM)) damage *= (pr_saw()%10+1); // use meleerange + 1 so the puff doesn't skip the flash (i.e. plays all states) if (Range == 0) Range = MELEERANGE+1; angle = self->angle + (pr_saw.Random2() * (Spread_XY / 255)); slope = P_AimLineAttack (self, angle, Range, &linetarget) + (pr_saw.Random2() * (Spread_Z / 255)); AWeapon *weapon = self->player->ReadyWeapon; if ((weapon != NULL) && !(Flags & SF_NOUSEAMMO) && !(!linetarget && (Flags & SF_NOUSEAMMOMISS)) && !(weapon->WeaponFlags & WIF_DEHAMMO)) { if (!weapon->DepleteAmmo (weapon->bAltFire)) return; } P_LineAttack (self, angle, Range, slope, damage, NAME_Melee, pufftype, false, &linetarget, &actualdamage); if (!linetarget) { if ((Flags & SF_RANDOMLIGHTMISS) && (pr_saw() > 64)) { player->extralight = !player->extralight; } S_Sound (self, CHAN_WEAPON, fullsound, 1, ATTN_NORM); return; } if (Flags & SF_RANDOMLIGHTHIT) { int randVal = pr_saw(); if (randVal < 64) { player->extralight = 0; } else if (randVal < 160) { player->extralight = 1; } else { player->extralight = 2; } } if (LifeSteal && !(linetarget->flags5 & MF5_DONTDRAIN)) { if (Flags & SF_STEALARMOR) { if (!armorbonustype) armorbonustype = PClass::FindClass("ArmorBonus"); if (armorbonustype->IsDescendantOf (RUNTIME_CLASS(ABasicArmorBonus))) { ABasicArmorBonus *armorbonus = static_cast<ABasicArmorBonus *>(Spawn (armorbonustype, 0,0,0, NO_REPLACE)); armorbonus->SaveAmount *= (actualdamage * LifeSteal) >> FRACBITS; armorbonus->MaxSaveAmount = lifestealmax <= 0 ? armorbonus->MaxSaveAmount : lifestealmax; armorbonus->flags |= MF_DROPPED; armorbonus->ClearCounters(); if (!armorbonus->CallTryPickup (self)) { armorbonus->Destroy (); } } } else {
// Returns the amount of damage actually inflicted upon the target, or -1 if // the damage was cancelled. int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, FName mod, int flags) { unsigned ang; player_t *player = NULL; fixed_t thrust; int temp; int painchance = 0; FState * woundstate = NULL; PainChanceList * pc = NULL; bool justhit = false; if (target == NULL || !((target->flags & MF_SHOOTABLE) || (target->flags6 & MF6_VULNERABLE))) { // Shouldn't happen return -1; } // Spectral targets only take damage from spectral projectiles. if (target->flags4 & MF4_SPECTRAL && damage < TELEFRAG_DAMAGE) { if (inflictor == NULL || !(inflictor->flags4 & MF4_SPECTRAL)) { return -1; } } if (target->health <= 0) { if (inflictor && mod == NAME_Ice) { return -1; } else if (target->flags & MF_ICECORPSE) // frozen { target->tics = 1; target->flags6 |= MF6_SHATTERING; target->velx = target->vely = target->velz = 0; } return -1; } if ((target->flags2 & MF2_INVULNERABLE) && damage < TELEFRAG_DAMAGE && !(flags & DMG_FORCED)) { // actor is invulnerable if (target->player == NULL) { if (inflictor == NULL || (!(inflictor->flags3 & MF3_FOILINVUL) && !(flags & DMG_FOILINVUL))) { return -1; } } else { // Players are optionally excluded from getting thrust by damage. if (static_cast<APlayerPawn *>(target)->PlayerFlags & PPF_NOTHRUSTWHENINVUL) { return -1; } } } if (inflictor != NULL) { if (inflictor->flags5 & MF5_PIERCEARMOR) flags |= DMG_NO_ARMOR; } MeansOfDeath = mod; FriendlyFire = false; // [RH] Andy Baker's Stealth monsters if (target->flags & MF_STEALTH) { target->alpha = OPAQUE; target->visdir = -1; } if (target->flags & MF_SKULLFLY) { target->velx = target->vely = target->velz = 0; } if (!(flags & DMG_FORCED)) // DMG_FORCED skips all special damage checks { if (target->flags2 & MF2_DORMANT) { // Invulnerable, and won't wake up return -1; } player = target->player; if (player && damage > 1 && damage < TELEFRAG_DAMAGE) { // Take half damage in trainer mode damage = FixedMul(damage, G_SkillProperty(SKILLP_DamageFactor)); } // Special damage types if (inflictor) { if (inflictor->flags4 & MF4_SPECTRAL) { if (player != NULL) { if (!deathmatch && inflictor->FriendPlayer > 0) return -1; } else if (target->flags4 & MF4_SPECTRAL) { if (inflictor->FriendPlayer == 0 && !target->IsHostile(inflictor)) return -1; } } damage = inflictor->DoSpecialDamage (target, damage, mod); if (damage == -1) { return -1; } } // Handle active damage modifiers (e.g. PowerDamage) if (source != NULL && source->Inventory != NULL) { int olddam = damage; source->Inventory->ModifyDamage(olddam, mod, damage, false); if (olddam != damage && damage <= 0) { // Still allow FORCEPAIN if (MustForcePain(target, inflictor)) { goto dopain; } return -1; } } // Handle passive damage modifiers (e.g. PowerProtection) if (target->Inventory != NULL) { int olddam = damage; target->Inventory->ModifyDamage(olddam, mod, damage, true); if (olddam != damage && damage <= 0) { // Still allow FORCEPAIN if (MustForcePain(target, inflictor)) { goto dopain; } return -1; } } if (!(flags & DMG_NO_FACTOR)) { damage = FixedMul(damage, target->DamageFactor); if (damage >= 0) { damage = DamageTypeDefinition::ApplyMobjDamageFactor(damage, mod, target->GetClass()->ActorInfo->DamageFactors); } if (damage <= 0) { // Still allow FORCEPAIN if (MustForcePain(target, inflictor)) { goto dopain; } return -1; } } damage = target->TakeSpecialDamage (inflictor, source, damage, mod); } if (damage == -1) { return -1; } // Push the target unless the source's weapon's kickback is 0. // (i.e. Gauntlets/Chainsaw) if (inflictor && inflictor != target // [RH] Not if hurting own self && !(target->flags & MF_NOCLIP) && !(inflictor->flags2 & MF2_NODMGTHRUST) && !(flags & DMG_THRUSTLESS) && (source == NULL || source->player == NULL || !(source->flags2 & MF2_NODMGTHRUST))) { int kickback; if (inflictor && inflictor->projectileKickback) kickback = inflictor->projectileKickback; else if (!source || !source->player || !source->player->ReadyWeapon) kickback = gameinfo.defKickback; else kickback = source->player->ReadyWeapon->Kickback; if (kickback) { AActor *origin = (source && (flags & DMG_INFLICTOR_IS_PUFF))? source : inflictor; // If the origin and target are in exactly the same spot, choose a random direction. // (Most likely cause is from telefragging somebody during spawning because they // haven't moved from their spawn spot at all.) if (origin->x == target->x && origin->y == target->y) { ang = pr_kickbackdir.GenRand32(); } else { ang = R_PointToAngle2 (origin->x, origin->y, target->x, target->y); } // Calculate this as float to avoid overflows so that the // clamping that had to be done here can be removed. double fltthrust; fltthrust = mod == NAME_MDK ? 10 : 32; if (target->Mass > 0) { fltthrust = clamp((damage * 0.125 * kickback) / target->Mass, 0., fltthrust); } thrust = FLOAT2FIXED(fltthrust); // Don't apply ultra-small damage thrust if (thrust < FRACUNIT/100) thrust = 0; // make fall forwards sometimes if ((damage < 40) && (damage > target->health) && (target->z - origin->z > 64*FRACUNIT) && (pr_damagemobj()&1) // [RH] But only if not too fast and not flying && thrust < 10*FRACUNIT && !(target->flags & MF_NOGRAVITY) && (inflictor == NULL || !(inflictor->flags5 & MF5_NOFORWARDFALL)) ) { ang += ANG180; thrust *= 4; } ang >>= ANGLETOFINESHIFT; if (source && source->player && (flags & DMG_INFLICTOR_IS_PUFF) && source->player->ReadyWeapon != NULL && (source->player->ReadyWeapon->WeaponFlags & WIF_STAFF2_KICKBACK)) { // Staff power level 2 target->velx += FixedMul (10*FRACUNIT, finecosine[ang]); target->vely += FixedMul (10*FRACUNIT, finesine[ang]); if (!(target->flags & MF_NOGRAVITY)) { target->velz += 5*FRACUNIT; } } else { target->velx += FixedMul (thrust, finecosine[ang]); target->vely += FixedMul (thrust, finesine[ang]); } } }