//============================================================================== // // m_seraph_guard.c // // Heretic II // Copyright 1998 Raven Software // // jweier //============================================================================== #include "g_local.h" #include "m_seraph_guard.h" #include "m_seraph_guard_anim.h" #include "g_DefaultMessageHandler.h" #include "Utilities.h" #include "g_monster.h" #include "Random.h" #include "vector.h" #include "g_misc.h" #include "fx.h" #include "g_HitLocation.h" #include "m_stats.h" #include "g_playstats.h" #include "p_anim_branch.h" #include "p_anims.h" #include "p_actions.h" void MG_InitMoods(edict_t *self); static animmove_t *animations[NUM_ANIMS] = { &seraph_guard_move_stand, &seraph_guard_move_run, &seraph_guard_move_fjump, &seraph_guard_move_runmelee, &seraph_guard_move_walk, &seraph_guard_move_pain, &seraph_guard_move_melee, &seraph_guard_move_melee2, &seraph_guard_move_melee3, &seraph_guard_move_death1, &seraph_guard_move_death2_go, &seraph_guard_move_death2_loop, &seraph_guard_move_death2_end, &seraph_guard_move_backup, &seraph_guard_move_missile, &seraph_guard_move_delay, }; static int sounds[NUM_SOUNDS]; static ClassResourceInfo_t resInfo; #define NUM_PREDFRAMES 5 #define SERAPH_FLAG_GOLEM 4 /* ========================================================== Seraph Guard Helper functions ========================================================== */ /*----------------------------------------------- create_guard_proj -----------------------------------------------*/ // create the guts of morcalavin's projectile void create_guard_proj(edict_t *self,edict_t *proj) { void guard_beam_blocked( edict_t *self, trace_t *trace ); proj->svflags |= SVF_ALWAYS_SEND; proj->movetype = PHYSICSTYPE_FLY; proj->gravity = 0; proj->solid = SOLID_BBOX; proj->classname = "Guard_Missile"; proj->dmg = flrand(SGUARD_DMG_SPELL_MIN, SGUARD_DMG_SPELL_MAX); proj->s.scale = 1.0; proj->clipmask = MASK_SHOT; proj->nextthink = level.time + 0.1; proj->isBlocked = proj->isBlocking = proj->bounced = guard_beam_blocked; proj->s.effects=EF_MARCUS_FLAG1|EF_CAMERA_NO_CLIP; proj->enemy = self->enemy; VectorSet(proj->mins, -4.0, -4.0, -4.0); VectorSet(proj->maxs, 4.0, 4.0, 4.0); VectorCopy(self->s.origin, proj->s.origin); } /*----------------------------------------------- guard_beam_blocked -----------------------------------------------*/ void guard_beam_blocked( edict_t *self, trace_t *trace ) { //edict_t *proj; /* if(EntReflecting(trace->ent, true, true) && self->reflect_debounce_time) { proj = G_Spawn(); create_guard_proj(self,proj); proj->isBlocked = guard_beam_blocked; proj->classname = "M_ref_HMissile"; proj->owner = self->owner; proj->ideal_yaw = self->ideal_yaw; proj->classID = self->classID; Create_rand_relect_vect(self->velocity, proj->velocity); Vec3ScaleAssign(proj->ideal_yaw,proj->velocity); vectoangles(proj->velocity, proj->s.angles); gi.CreateEffect(&proj->s, FX_M_EFFECTS, CEF_OWNERS_ORIGIN, NULL, "bv", proj->classID, proj->velocity); proj->reflect_debounce_time = self->reflect_debounce_time -1; gi.linkentity(proj); G_SetToFree(self); return; } */ if (trace->ent->takedamage ) { vec3_t hitDir; VectorCopy( self->velocity, hitDir ); VectorNormalize( hitDir ); T_Damage(trace->ent, self, self->owner, hitDir, self->s.origin, trace->plane.normal, self->dmg, 0, DAMAGE_SPELL | DAMAGE_NO_KNOCKBACK,MOD_DIED); } gi.sound(self, CHAN_WEAPON, gi.soundindex("monsters/seraph/guard/spellhit.wav"), 1, ATTN_NORM, 0); gi.CreateEffect(&self->s, FX_M_EFFECTS, CEF_OWNERS_ORIGIN, self->s.origin, "bv", FX_M_MISC_EXPLODE, vec3_origin); G_SetToFree(self); } void guard_beam_think (edict_t *self) { self->think = NULL; self->nextthink = -1; } void guard_beam( edict_t *self) { edict_t *proj; vec3_t Forward, vf, vr; // Spawn the projectile proj = G_Spawn(); create_guard_proj(self,proj); proj->reflect_debounce_time = MAX_REFLECT; proj->classname = "M_Beam"; proj->isBlocked = guard_beam_blocked; proj->owner = self; AngleVectors(self->s.angles, vf, vr, NULL); VectorMA(proj->s.origin, 24, vf, proj->s.origin); VectorMA(proj->s.origin, 16, vr, proj->s.origin); proj->s.origin[2] += 10; if(!ahead(self, self->enemy)) VectorCopy(vf, Forward); else { VectorSubtract(self->enemy->s.origin, self->s.origin, Forward); VectorNormalize(Forward); } VectorScale(Forward, 500, proj->velocity); vectoangles(proj->velocity, proj->s.angles); proj->think = guard_beam_think; proj->nextthink = level.time + 1; gi.sound(self, CHAN_WEAPON, gi.soundindex("monsters/seraph/guard/spell.wav"), 1, ATTN_NORM, 0); gi.CreateEffect(&proj->s, FX_M_EFFECTS, CEF_OWNERS_ORIGIN, vec3_origin, "bv", FX_M_BEAM, proj->s.angles); gi.linkentity(proj); } /*-------------------------------------- seraph_guard_checkpoke ----------------------------------------*/ void seraph_guard_checkpoke ( edict_t *self ) { vec3_t attackVel, vf; float dist; int ret, chance; //Really, this is a given, but it could fail... if (M_ValidTarget(self, self->enemy)) { //Set this for any uses below AngleVectors(self->s.angles, vf, NULL, NULL); dist = M_DistanceToTarget(self, self->enemy); if (dist < 120) { VectorMA(vf, 1, vf, attackVel); ret = M_PredictTargetEvasion( self, self->enemy, attackVel, self->enemy->velocity, 150, NUM_PREDFRAMES ); if (ret) { chance = irand(0,100); if (chance < 40) SetAnim(self, ANIM_MELEE3); else if (chance < 60) SetAnim(self, ANIM_MELEE2); } else { if (irand(0,1)) SetAnim(self, ANIM_RUN_MELEE); else SetAnim(self, ANIM_MELEE2); } } return; } QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL); } /*-------------------------------------- seraph_guard_death_loop ----------------------------------------*/ void seraph_guard_death_loop ( edict_t *self ) { SetAnim(self, ANIM_DEATH2_LOOP); } /*-------------------------------------- seraph_guard_check_land ----------------------------------------*/ void seraph_guard_check_land ( edict_t *self ) { vec3_t endpos; trace_t trace; M_ChangeYaw(self); VectorCopy(self->s.origin, endpos); endpos[2] -= 48; gi.trace(self->s.origin, self->mins, self->maxs, endpos, self, MASK_MONSTERSOLID,&trace); if ( ( trace.fraction < 1 || trace.startsolid || trace.allsolid) && self->curAnimID != ANIM_DEATH2_END && self->curAnimID != ANIM_DEATH2_GO) { self->elasticity = 1.25; self->friction = 0.5; SetAnim(self, ANIM_DEATH2_END); } } /*-------------------------------------- seraph_guard_dead ----------------------------------------*/ void seraph_guard_dead ( edict_t *self ) { M_EndDeath(self); } /*-------------------------------------- seraph_guard_strike ----------------------------------------*/ void seraph_guard_strike( edict_t *self, float damage, float var2, float var3 ) { trace_t trace; edict_t *victim; vec3_t soff, eoff, mins, maxs, bloodDir, direction; qboolean knockback = true; // vec3_t dir, fwd, right, up; if(self->monsterinfo.aiflags & AI_NO_MELEE) return; damage *= self->s.scale; self->monsterinfo.attack_finished = level.time + (3 - skill->value) * 2 + flrand(0, 1); switch (self->curAnimID) { case ANIM_MELEE2: VectorSet(soff, 16, -16, 24); VectorSet(eoff, 124,-16, 16); break; case ANIM_MELEE3: VectorSet(soff, 32, -48, 34); VectorSet(eoff, 64, 64, -8); break; default: knockback = false; VectorSet(soff, 32, -16, 64); VectorSet(eoff, 72, 16, -8); break; } Vec3ScaleAssign(self->s.scale, soff); Vec3ScaleAssign(self->s.scale, eoff); VectorSet(mins, -4, -4, -4); VectorSet(maxs, 4, 4, 4); VectorSubtract(eoff, soff, bloodDir); VectorNormalize(bloodDir); victim = M_CheckMeleeLineHit(self, soff, eoff, mins, maxs, &trace, direction); //Did something get hit? if (victim) { if (victim == self) { //Create a spark effect gi.CreateEffect(NULL, FX_SPARKS, CEF_FLAG6, trace.endpos, "d", direction); gi.sound (self, CHAN_WEAPON, sounds[SND_HIT_WALL], 1, ATTN_NORM, 0); } else { // Get a proper angle for this attack from the absolute vector provided. /* AngleVectors(self->s.angles, fwd, right, up); VectorScale(fwd, hitDir[0], dir); VectorMA(dir, -hitDir[1], right, dir); VectorMA(dir, hitDir[2], up, dir);*/ if(self->curAnimID == ANIM_MELEE3) { gi.CreateEffect(NULL, FX_WEAPON_STAFF_STRIKE, 0, trace.endpos, "db", trace.plane.normal, 2); gi.sound(self,CHAN_WEAPON,gi.soundindex("weapons/staffhit_2.wav"),1,ATTN_NORM,0); } else gi.sound (self, CHAN_WEAPON, sounds[SND_ATTACK], 1, ATTN_NORM, 0); //Hurt whatever we were whacking away at damage *= ((skill->value + 1)/3) //skill 0 = 1/3, skill 3 = 1 1/3 * flrand(0.85, 1.15); // Add some variance to the hit, since it passes a constant. if(knockback) { T_Damage(victim, self, self, direction, trace.endpos, bloodDir, damage, damage*20, DAMAGE_DISMEMBER|DAMAGE_DOUBLE_DISMEMBER|DAMAGE_EXTRA_BLOOD|DAMAGE_EXTRA_KNOCKBACK,MOD_DIED); } else { T_Damage(victim, self, self, direction, trace.endpos, bloodDir, damage, 0, DAMAGE_NO_KNOCKBACK|DAMAGE_DISMEMBER|DAMAGE_DOUBLE_DISMEMBER|DAMAGE_EXTRA_BLOOD,MOD_DIED); } if(self->curAnimID == ANIM_MELEE3) { if(victim->client) { if(victim->health > 0) { if(victim->client->playerinfo.lowerseq != ASEQ_KNOCKDOWN && infront(self, victim)) { P_KnockDownPlayer(&victim->client->playerinfo); } } } } } } else { //Play swoosh sound gi.sound (self, CHAN_WEAPON, sounds[SND_ATTACK_MISS], 1, ATTN_NORM, 0); } } /*-------------------------------------- seraph_guard_pause ----------------------------------------*/ void seraphGuardApplyJump (edict_t *self) { self->jump_time = level.time + 0.75; VectorCopy(self->movedir, self->velocity); VectorNormalize(self->movedir); } void seraph_guard_pause( edict_t *self ) { if(self->spawnflags & MSF_FIXED && self->curAnimID == ANIM_DELAY && self->enemy) { self->monsterinfo.searchType = SEARCH_COMMON; MG_FaceGoal(self, true); } self->mood_think(self); switch (self->ai_mood) { case AI_MOOD_ATTACK: if(self->ai_mood_flags & AI_MOOD_FLAG_MISSILE) QPostMessage(self, MSG_MISSILE, PRI_DIRECTIVE, NULL); else QPostMessage(self, MSG_MELEE, PRI_DIRECTIVE, NULL); break; case AI_MOOD_PURSUE: case AI_MOOD_NAVIGATE: case AI_MOOD_WALK: case AI_MOOD_FLEE: QPostMessage(self, MSG_RUN, PRI_DIRECTIVE, NULL); break; case AI_MOOD_STAND: if (!self->enemy) QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL); break; case AI_MOOD_DELAY: SetAnim(self, ANIM_DELAY); break; case AI_MOOD_WANDER: SetAnim(self, ANIM_WALK); break; case AI_MOOD_JUMP: SetAnim(self, ANIM_FJUMP); break; default : #ifdef _DEVEL gi.dprintf("seraph guard: Unusable mood %d!\n", self->ai_mood); #endif break; } } void seraph_guard_check_mood (edict_t *self, G_Message_t *msg) { ParseMsgParms(msg, "i", &self->ai_mood); seraph_guard_pause(self); } /* ========================================================== Seraph Guard Message functions ========================================================== */ /*-------------------------------------- void seraph_guard_pain ----------------------------------------*/ void seraph_guard_pain(edict_t *self, G_Message_t *msg) { int temp, damage; int force_damage, soundID; ParseMsgParms(msg, "eeiii", &temp, &temp, &force_damage, &damage, &temp); //Weighted random based on health compared to the maximum it was at if (force_damage || ((irand(0, self->max_health+50) > self->health) && irand(0,2))) { soundID = irand(SND_PAIN1, SND_PAIN4); gi.sound (self, CHAN_WEAPON, sounds[soundID], 1, ATTN_NORM, 0); SetAnim(self, ANIM_PAIN); } } /*-------------------------------------- void seraph_guard_stand ----------------------------------------*/ void seraph_guard_stand(edict_t *self, G_Message_t *msg) { SetAnim(self, ANIM_STAND); } /*-------------------------------------- void seraph_guard_melee ----------------------------------------*/ void seraph_guard_melee(edict_t *self, G_Message_t *msg) { vec3_t attackVel, vf; float dist; int ret; //Don't interrupt a current animation with another melee call inside of it if (self->curAnimID == ANIM_MELEE1 || self->curAnimID == ANIM_MELEE2) return; if (M_ValidTarget(self, self->enemy)) { if(self->ai_mood == AI_MOOD_FLEE) { SetAnim(self, ANIM_BACKUP); return; } //Set this for any uses below AngleVectors(self->s.angles, vf, NULL, NULL); dist = M_DistanceToTarget(self, self->enemy); if (dist < 120) { if(self->s.fmnodeinfo[MESH__AXE].flags & FMNI_NO_DRAW) { SetAnim(self, ANIM_MISSILE); return; } VectorMA(vf, 0, vf, attackVel); ret = M_PredictTargetEvasion( self, self->enemy, attackVel, self->enemy->velocity, self->melee_range, NUM_PREDFRAMES ); if (ret) { if(dist < 88 && !irand(0, 3)) SetAnim(self, ANIM_MISSILE);//punch else if(irand(0, 4)) { if(irand(0, 10)) SetAnim(self, ANIM_MELEE1); else SetAnim(self, ANIM_MISSILE); } else SetAnim(self, ANIM_MELEE2); } else SetAnim(self, ANIM_RUN_MELEE); } else SetAnim(self, ANIM_RUN); return; } //If our enemy is dead, we need to stand QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL); } void morcalavin_beam( edict_t *self); void seraph_guard_fire (edict_t *self) { if(!self->enemy) return; if(M_DistanceToTarget(self, self->enemy) < self->min_missile_range) {//punch trace_t trace; edict_t *victim; vec3_t soff, eoff, mins, maxs, bloodDir, direction; float damage; damage = 15 * self->s.scale; VectorSet(soff, 12, -18, 24); VectorSet(eoff, 88, -4, -16); Vec3ScaleAssign(self->s.scale, soff); Vec3ScaleAssign(self->s.scale, eoff); VectorSet(mins, -4, -4, -4); VectorSet(maxs, 4, 4, 4); VectorSubtract(eoff, soff, bloodDir); VectorNormalize(bloodDir); victim = M_CheckMeleeLineHit(self, soff, eoff, mins, maxs, &trace, direction); //Did something get hit? if (victim) { if (victim == self) { gi.sound (self, CHAN_WEAPON, sounds[SND_FIST_HIT_WALL], 1, ATTN_NORM, 0); } else { //Hurt whatever we were whacking away at damage *= (skill->value + 1)/3;//skill 0 = 1/3, skill 3 = 1 1/3 T_Damage(victim, self, self, direction, trace.endpos, bloodDir, damage, damage*20, DAMAGE_EXTRA_KNOCKBACK, MOD_DIED); gi.sound (self, CHAN_WEAPON, sounds[SND_HIT_WALL], 1, ATTN_NORM, 0); if(victim->client) { if(victim->health > 0) { if(victim->client->playerinfo.lowerseq != ASEQ_KNOCKDOWN && infront(self, victim)) { P_KnockDownPlayer(&victim->client->playerinfo); } } } } } else { //Play swoosh sound gi.sound (self, CHAN_WEAPON, sounds[SND_ATTACK_MISS], 1, ATTN_NORM, 0); } } else guard_beam (self); } void seraph_guard_missile(edict_t *self, G_Message_t *msg) { vec3_t attackVel, vf; float dist; int ret; if (M_ValidTarget(self, self->enemy)) { if(self->ai_mood == AI_MOOD_FLEE) { SetAnim(self, ANIM_BACKUP); return; } //Set this for any uses below AngleVectors(self->s.angles, vf, NULL, NULL); dist = M_DistanceToTarget(self, self->enemy); if (dist < self->min_missile_range) { VectorMA(vf, 0, vf, attackVel); ret = M_PredictTargetEvasion( self, self->enemy, attackVel, self->enemy->velocity, self->melee_range, NUM_PREDFRAMES ); if(irand(0, 4)) { if(irand(0, 10)) SetAnim(self, ANIM_MELEE1); else SetAnim(self, ANIM_MISSILE); } else SetAnim(self, ANIM_MELEE2); } else if(ahead(self, self->enemy)) SetAnim(self, ANIM_MISSILE); return; } //If our enemy is dead, we need to stand QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL); } /*-------------------------------------- void seraph_guard_death ----------------------------------------*/ void seraph_guard_death_pain(edict_t *self, G_Message_t *msg) { if (self->health < -80) { BecomeDebris(self); return; } } void seraph_guard_dropweapon (edict_t *self); void seraph_guard_death(edict_t *self, G_Message_t *msg) { edict_t *targ, *inflictor, *attacker; vec3_t dVel, vf, yf; float damage; int soundID; ParseMsgParms(msg, "eeei", &targ, &inflictor, &attacker, &damage); M_StartDeath(self, ANIM_DEATH1); if (self->health < -80) { return; } else if (self->health < -10) { seraph_guard_dropweapon(self); SetAnim(self, ANIM_DEATH2_GO); VectorCopy(targ->velocity, vf); VectorNormalize(vf); VectorScale(vf, -1, yf); self->ideal_yaw = vectoyaw( yf ); self->yaw_speed = 24; VectorScale(vf, 300, dVel); dVel[2] = irand(150,200); VectorCopy(dVel, self->velocity); // self->groundentity = NULL; } else { SetAnim(self, ANIM_DEATH1); } soundID = irand(SND_DEATH1, SND_DEATH4); gi.sound (self, CHAN_BODY, sounds[soundID], 1, ATTN_NORM, 0); } /*-------------------------------------- void seraph_guard_run ----------------------------------------*/ void seraph_guard_run(edict_t *self, G_Message_t *msg) { trace_t trace; vec3_t attackVel, vf; float dist; int ret; if (M_ValidTarget(self, self->enemy)) { if(self->ai_mood == AI_MOOD_FLEE) { SetAnim(self, ANIM_RUN); return; } if(self->monsterinfo.attack_finished > level.time || self->monsterinfo.aiflags & AI_NO_MELEE) { SetAnim(self, ANIM_RUN); return; } //Set this for any uses below AngleVectors(self->s.angles, vf, NULL, NULL); dist = M_DistanceToTarget(self, self->enemy); if (dist < 100) { VectorMA(vf, 0, vf, attackVel); ret = M_PredictTargetEvasion( self, self->enemy, attackVel, self->enemy->velocity, self->melee_range, NUM_PREDFRAMES ); //See what the predicted outcome is if (ret && (M_CheckMeleeHit( self, self->melee_range, &trace) == self->enemy)) SetAnim(self, ANIM_MELEE1); else SetAnim(self, ANIM_RUN_MELEE); } else if (dist < 200) { VectorMA(vf, 150, vf, attackVel); ret = M_PredictTargetEvasion( self, self->enemy, attackVel, self->enemy->velocity, 150, NUM_PREDFRAMES ); //See what the predicted outcome is if (ret && (M_CheckMeleeHit( self, 150, &trace) == self->enemy)) SetAnim(self, ANIM_RUN_MELEE); else SetAnim(self, ANIM_RUN); } else { SetAnim(self, ANIM_RUN); } return; } //If our enemy is dead, we need to stand QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL); } int Bit_for_MeshNode_sg [NUM_MESH_NODES] = { BIT_BASEBIN, BIT_PITHEAD,//overlord head BIT_SHOULDPAD, BIT_GUARDHEAD,//guard head BIT_LHANDGRD,//left hand guard BIT_LHANDBOSS,//left hand overlord BIT_RHAND,//right hand BIT_FRTORSO, BIT_ARMSPIKES, BIT_LFTUPARM, BIT_RTLEG, BIT_RTARM, BIT_LFTLEG, BIT_BKTORSO, BIT_AXE,//axe BIT_WHIP//whip }; void seraph_guard_back (edict_t *self, float dist) { if(!MG_BoolWalkMove(self, self->s.angles[YAW] + 180, dist)) { if(!irand(0, 1000)) { self->monsterinfo.aiflags |= AI_COWARD; SetAnim(self, ANIM_RUN); } } } qboolean canthrownode_sg (edict_t *self, int BP, int *throw_nodes) {//see if it's on, if so, add it to throw_nodes //turn it off on thrower if(!(self->s.fmnodeinfo[BP].flags & FMNI_NO_DRAW)) { *throw_nodes |= Bit_for_MeshNode_sg[BP]; self->s.fmnodeinfo[BP].flags |= FMNI_NO_DRAW; return true; } return false; } //THROWS weapon, turns off those nodes, sets that weapon as gone void seraph_guard_dropweapon (edict_t *self) {//NO PART FLY FRAME! vec3_t handspot, forward, right, up; if(!(self->s.fmnodeinfo[MESH__AXE].flags & FMNI_NO_DRAW)) { VectorClear(handspot); AngleVectors(self->s.angles,forward,right,up); VectorMA(handspot,8,forward,handspot); VectorMA(handspot,5,right,handspot); VectorMA(handspot,12,up,handspot); ThrowWeapon(self, &handspot, BIT_AXE, 0, FRAME_partfly); self->s.fmnodeinfo[MESH__AXE].flags |= FMNI_NO_DRAW; return; } } void seraph_guard_dismember(edict_t *self, int damage, int HitLocation) { int throw_nodes = 0; vec3_t gore_spot, right; qboolean dismember_ok = false; if(HitLocation & hl_MeleeHit) { dismember_ok = true; HitLocation &= ~hl_MeleeHit; } if(HitLocation<1) return; if(HitLocation>hl_Max) return; if(HitLocation == hl_TorsoFront && dismember_ok) {//melee swing at chest during my melee swing if(self->curAnimID == ANIM_MELEE1 || self->curAnimID == ANIM_MELEE2 || self->curAnimID == ANIM_MELEE3|| self->curAnimID == ANIM_RUN_MELEE) { if(!irand(0, 2)) HitLocation = hl_ArmLowerLeft; else HitLocation = hl_ArmLowerRight; } } VectorClear(gore_spot); switch(HitLocation) { case hl_Head: if(self->s.fmnodeinfo[MESH__GUARDHEAD].flags & FMNI_NO_DRAW) break; if(self->s.fmnodeinfo[MESH__GUARDHEAD].flags & FMNI_USE_SKIN) damage*=1.5;//greater chance to cut off if previously damaged if(flrand(0,self->health)s.origin, gore_spot, gore_spot); SprayDebris(self,gore_spot,8,damage); if(self->health>0) { self->health = 1; T_Damage (self, self, self, vec3_origin, vec3_origin, vec3_origin, 10, 20,0,MOD_DIED); } return; } else { self->s.fmnodeinfo[MESH__GUARDHEAD].flags |= FMNI_USE_SKIN; self->s.fmnodeinfo[MESH__GUARDHEAD].skin = self->s.skinnum+1; } break; case hl_TorsoFront://split in half? if(self->s.fmnodeinfo[MESH__FRTORSO].flags & FMNI_USE_SKIN) damage*=1.5;//greater chance to cut off if previously damaged if(flrand(0,self->health)s.fmnodeinfo[MESH__FRTORSO].flags |= FMNI_USE_SKIN; self->s.fmnodeinfo[MESH__FRTORSO].skin = self->s.skinnum+1; } break; case hl_TorsoBack://split in half? if(self->s.fmnodeinfo[MESH__BKTORSO].flags & FMNI_USE_SKIN) damage*=1.5;//greater chance to cut off if previously damaged if(flrand(0,self->health) < damage) { self->s.fmnodeinfo[MESH__BKTORSO].flags |= FMNI_USE_SKIN; self->s.fmnodeinfo[MESH__BKTORSO].skin = self->s.skinnum+1; } break; case hl_ArmUpperLeft: if(flrand(0,self->health)s.fmnodeinfo[MESH__LFTUPARM].flags |= FMNI_USE_SKIN; self->s.fmnodeinfo[MESH__LFTUPARM].skin = self->s.skinnum+1; } if(flrand(0,self->health)s.angles,NULL,right,NULL); gore_spot[2]+=self->maxs[2]*0.3; VectorMA(gore_spot,-10,right,gore_spot); seraph_guard_dropweapon (self); ThrowBodyPart(self, &gore_spot, throw_nodes, damage, FRAME_partfly); } } break; case hl_ArmLowerLeft://left arm if(self->s.fmnodeinfo[MESH__LHANDGRD].flags & FMNI_NO_DRAW) break; if(self->s.fmnodeinfo[MESH__LHANDGRD].flags & FMNI_USE_SKIN) damage*=1.5;//greater chance to cut off if previously damaged if(flrand(0,self->health)s.angles,NULL,right,NULL); gore_spot[2]+=self->maxs[2]*0.3; VectorMA(gore_spot,-10,right,gore_spot); seraph_guard_dropweapon (self); ThrowBodyPart(self, &gore_spot, throw_nodes, damage, FRAME_partfly); } } else { self->s.fmnodeinfo[MESH__LHANDGRD].flags |= FMNI_USE_SKIN; self->s.fmnodeinfo[MESH__LHANDGRD].skin = self->s.skinnum+1; } break; case hl_ArmUpperRight: if(flrand(0,self->health)s.fmnodeinfo[MESH__RTARM].flags |= FMNI_USE_SKIN; self->s.fmnodeinfo[MESH__RTARM].skin = self->s.skinnum+1; } if(flrand(0,self->health)s.angles,NULL,right,NULL); gore_spot[2]+=self->maxs[2]*0.3; VectorMA(gore_spot,10,right,gore_spot); seraph_guard_dropweapon (self); ThrowBodyPart(self, &gore_spot, throw_nodes, damage, FRAME_partfly); } } break; case hl_ArmLowerRight://right arm if(self->s.fmnodeinfo[MESH__RHAND].flags & FMNI_NO_DRAW) break; if(flrand(0,self->health)s.angles,NULL,right,NULL); gore_spot[2]+=self->maxs[2]*0.3; VectorMA(gore_spot,10,right,gore_spot); seraph_guard_dropweapon (self); ThrowBodyPart(self, &gore_spot, throw_nodes, damage, FRAME_partfly); } } else { self->s.fmnodeinfo[MESH__RHAND].flags |= FMNI_USE_SKIN; self->s.fmnodeinfo[MESH__RHAND].skin = self->s.skinnum+1; } break; case hl_LegUpperLeft: case hl_LegLowerLeft://left leg if(self->s.fmnodeinfo[MESH__LFTLEG].flags & FMNI_USE_SKIN) break; self->s.fmnodeinfo[MESH__LFTLEG].flags |= FMNI_USE_SKIN; self->s.fmnodeinfo[MESH__LFTLEG].skin = self->s.skinnum+1; break; case hl_LegUpperRight: case hl_LegLowerRight://right leg if(self->s.fmnodeinfo[MESH__RTLEG].flags & FMNI_USE_SKIN) break; self->s.fmnodeinfo[MESH__RTLEG].flags |= FMNI_USE_SKIN; self->s.fmnodeinfo[MESH__RTLEG].skin = self->s.skinnum+1; break; } //FIXME: when get missile anim if(self->s.fmnodeinfo[MESH__RHAND].flags & FMNI_NO_DRAW) { self->monsterinfo.aiflags |= AI_NO_MISSILE; if(self->s.fmnodeinfo[MESH__AXE].flags & FMNI_NO_DRAW) self->monsterinfo.aiflags |= AI_NO_MELEE; } if(self->monsterinfo.aiflags & AI_NO_MELEE && self->monsterinfo.aiflags & AI_NO_MISSILE) { self->monsterinfo.aiflags |= AI_COWARD; SetAnim(self, ANIM_BACKUP); } } void ser_grd_SightSound(edict_t *self, G_Message_t *Msg) { gi.sound(self, CHAN_VOICE, sounds[irand(SND_SIGHT1, SND_SIGHT4)], 1, ATTN_NORM, 0); } /* ========================================================== Seraph Guard Spawn functions ========================================================== */ void SeraphGuardStaticsInit() { classStatics[CID_SERAPH_GUARD].msgReceivers[MSG_STAND] = seraph_guard_stand; classStatics[CID_SERAPH_GUARD].msgReceivers[MSG_RUN] = seraph_guard_run; classStatics[CID_SERAPH_GUARD].msgReceivers[MSG_MELEE] = seraph_guard_melee; classStatics[CID_SERAPH_GUARD].msgReceivers[MSG_MISSILE] = seraph_guard_missile; classStatics[CID_SERAPH_GUARD].msgReceivers[MSG_PAIN] = seraph_guard_pain; classStatics[CID_SERAPH_GUARD].msgReceivers[MSG_DEATH] = seraph_guard_death; classStatics[CID_SERAPH_GUARD].msgReceivers[MSG_DEATH_PAIN] = seraph_guard_death_pain; classStatics[CID_SERAPH_GUARD].msgReceivers[MSG_CHECK_MOOD] = seraph_guard_check_mood; classStatics[CID_SERAPH_GUARD].msgReceivers[MSG_DISMEMBER] = MG_parse_dismember_msg; classStatics[CID_SERAPH_GUARD].msgReceivers[MSG_VOICE_SIGHT] = ser_grd_SightSound; resInfo.numAnims = NUM_ANIMS; resInfo.animations = animations; resInfo.modelIndex = gi.modelindex("models/monsters/guard/tris.fm"); resInfo.numSounds = NUM_SOUNDS; resInfo.sounds = sounds; sounds[SND_ATTACK] = gi.soundindex("monsters/seraph/guard/attack.wav"); sounds[SND_ATTACK_MISS] = gi.soundindex("monsters/seraph/guard/attack_miss.wav"); sounds[SND_HIT_WALL] = gi.soundindex("weapons/staffhitwall.wav"); sounds[SND_MISSILE] = gi.soundindex("monsters/seraph/guard/spell.wav"); sounds[SND_MISSHIT] = gi.soundindex("monsters/seraph/guard/spellhit.wav"); sounds[SND_FIST_HIT_WALL] = gi.soundindex("objects/bam1.wav"); sounds[SND_DEATH1] = gi.soundindex("monsters/seraph/death1.wav"); sounds[SND_DEATH2] = gi.soundindex("monsters/seraph/death2.wav"); sounds[SND_DEATH3] = gi.soundindex("monsters/seraph/wimpdeath1.wav"); sounds[SND_DEATH4] = gi.soundindex("monsters/seraph/wimpdeath2.wav"); sounds[SND_PAIN1] = gi.soundindex("monsters/seraph/pain1.wav"); sounds[SND_PAIN2] = gi.soundindex("monsters/seraph/pain2.wav"); sounds[SND_PAIN3] = gi.soundindex("monsters/seraph/pain3.wav"); sounds[SND_PAIN4] = gi.soundindex("monsters/seraph/pain4.wav"); sounds[SND_SIGHT1] = gi.soundindex("monsters/seraph/guard/sight1.wav"); sounds[SND_SIGHT2] = gi.soundindex("monsters/seraph/guard/sight2.wav"); sounds[SND_SIGHT3] = gi.soundindex("monsters/seraph/guard/sight3.wav"); sounds[SND_SIGHT4] = gi.soundindex("monsters/seraph/guard/sight4.wav"); classStatics[CID_SERAPH_GUARD].resInfo = &resInfo; } void golem_awaken (edict_t *self, edict_t *other, edict_t *activator) { if (!monster_start(self)) // Initialization failed return; self->think = walkmonster_start_go; self->msgHandler = DefaultMsgHandler; self->ai_mood_flags |= AI_MOOD_FLAG_PREDICT; self->monsterinfo.dismember = seraph_guard_dismember; MG_InitMoods(self); QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL); self->melee_range *= self->s.scale; self->enemy = activator; FoundTarget(self, false); } /*QUAKED monster_seraph_guard(1 .5 0) (-24 -24 -34) (24 24 34) AMBUSH ASLEEP GOLEM 8 16 32 64 FIXED WANDER MELEE_LEAD STALK COWARD EXTRA1 EXTRA2 EXTRA3 EXTRA4 The big, ugly, brutal Guard.. AMBUSH - Will not be woken up by other monsters or shots from player ASLEEP - will not appear until triggered WALKING- Use WANDER instead WANDER - Monster will wander around aimlessly (but follows buoys) MELEE_LEAD - Monster will tryto cut you off when you're running and fighting him, works well if there are a few monsters in a group, half doing this, half not STALK - Monster will only approach and attack from behind- if you're facing the monster it will just stand there. Once the monster takes pain, however, it will stop this behaviour and attack normally COWARD - Monster starts off in flee mode- runs away from you when woken up "homebuoy" - monsters will head to this buoy if they don't have an enemy ("homebuoy" should be targetname of the buoy you want them to go to) mintel - monster intelligence- this basically tells a monster how many buoys away an enemy has to be for it to give up. melee_range - How close the player has to be, maximum, for the monster to go into melee. If this is zero, the monster will never melee. If it is negative, the monster will try to keep this distance from the player. If the monster has a backup, he'll use it if too clode, otherwise, a negative value here means the monster will just stop running at the player at this distance. Examples: melee_range = 60 - monster will start swinging it player is closer than 60 melee_range = 0 - monster will never do a mele attack melee_range = -100 - monster will never do a melee attack and will back away (if it has that ability) when player gets too close missile_range - Maximum distance the player can be from the monster to be allowed to use it's ranged attack. min_missile_range - Minimum distance the player can be from the monster to be allowed to use it's ranged attack. bypass_missile_chance - Chance that a monster will NOT fire it's ranged attack, even when it has a clear shot. This, in effect, will make the monster come in more often than hang back and fire. A percentage (0 = always fire/never close in, 100 = never fire/always close in).- must be whole number jump_chance - every time the monster has the opportunity to jump, what is the chance (out of 100) that he will... (100 = jump every time)- must be whole number wakeup_distance - How far (max) the player can be away from the monster before it wakes up. This just means that if the monster can see the player, at what distance should the monster actually notice him and go for him. DEFAULTS: mintel = 20 melee_range = 100 missile_range = 1024 min_missile_range = 64 bypass_missile_chance = 30 jump_chance = 20 wakeup_distance = 1024 NOTE: A value of zero will result in defaults, if you actually want zero as the value, use -1 */ void SP_monster_seraph_guard(edict_t *self) { if (self->spawnflags & SERAPH_FLAG_GOLEM) { self->classID = CID_SERAPH_GUARD; self->clipmask = MASK_MONSTERSOLID; if (!self->health) self->health = SGUARD_HEALTH * 2; self->max_health = self->health = MonsterHealth(self->health); self->mass = SGUARD_MASS * 10; self->yaw_speed = 12; self->movetype = PHYSICSTYPE_STEP; self->solid=SOLID_BBOX; if (!self->s.scale) { self->s.scale = self->monsterinfo.scale = 2.5; } self->s.skinnum = 3; VectorCopy(STDMinsForClass[self->classID], self->mins); VectorCopy(STDMaxsForClass[self->classID], self->maxs); self->materialtype = MAT_STONE; self->s.modelindex = classStatics[CID_SERAPH_GUARD].resInfo->modelIndex; self->s.skinnum = 0; self->monsterinfo.otherenemyname = "monster_rat"; //Turn off the Overlord pieces self->s.fmnodeinfo[MESH__WHIP].flags |= FMNI_NO_DRAW; self->s.fmnodeinfo[MESH__LHANDBOSS].flags |= FMNI_NO_DRAW; self->s.fmnodeinfo[MESH__PITHEAD].flags |= FMNI_NO_DRAW; self->s.frame = FRAME_idle; //enable this and use guards to make them wake up on command //self->use = golem_awaken; } else { self->classID = CID_SERAPH_GUARD; if (!monster_start(self)) return; // Failed initialization self->think = walkmonster_start_go; self->msgHandler = DefaultMsgHandler; if (!self->health) { self->health = SGUARD_HEALTH; } //Apply to the end result (whether designer set or not) self->max_health = self->health = MonsterHealth(self->health); self->mass = SGUARD_MASS; self->yaw_speed = 24; if(irand(0, 2)) self->ai_mood_flags |= AI_MOOD_FLAG_PREDICT; self->movetype = PHYSICSTYPE_STEP; self->solid=SOLID_BBOX; self->monsterinfo.dismember = seraph_guard_dismember; if (!self->s.scale) { self->s.scale = self->monsterinfo.scale = MODEL_SCALE; } VectorCopy(STDMinsForClass[self->classID], self->mins); VectorCopy(STDMaxsForClass[self->classID], self->maxs); self->materialtype = MAT_FLESH; self->s.modelindex = classStatics[CID_SERAPH_GUARD].resInfo->modelIndex; self->s.skinnum = 0; self->monsterinfo.otherenemyname = "monster_rat"; //Turn off the Overlord pieces self->s.fmnodeinfo[MESH__WHIP].flags |= FMNI_NO_DRAW; self->s.fmnodeinfo[MESH__LHANDBOSS].flags |= FMNI_NO_DRAW; self->s.fmnodeinfo[MESH__PITHEAD].flags |= FMNI_NO_DRAW; MG_InitMoods(self); QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL); self->melee_range *= self->s.scale; } }