thirtyflightsofloving/missionpack/m_medic.c
Knightmare66 add4c7cc46 Reworked 2D pic drawing using a new, merged R_DrawPic() function. Moved old pic drawing functions to r_draw_removed.c.
Added new SCR_DrawPic() variants in cl_screen.c.
Added new graphics for text fields and sliders in menus.
Improved mouse interaction for menu sliders.
Added resettargets developer command to default Lazarus and  missionpack DLLs.
Added hint_test developer command to missionpack DLL.
Fixed freeze developer command in default Lazarus and missionpack DLLs so it can be used more than once.
More tweaks to Tactician Gunner prox mine safety checks in misssionpack DLL.
2021-08-07 00:43:46 -04:00

2208 lines
59 KiB
C

/*
==============================================================================
MEDIC
==============================================================================
*/
#include "g_local.h"
#include "m_medic.h"
#define MEDIC_MIN_DISTANCE 32
#define MEDIC_MAX_HEAL_DISTANCE 400
#define MEDIC_TRY_TIME 10.0
// FIXME -
//
// owner moved to monsterinfo.healer instead
//
// For some reason, the healed monsters are rarely ending up in the floor
//
// 5/15/1998 I think I fixed these, keep an eye on them
qboolean visible (edict_t *self, edict_t *other);
void M_SetEffects (edict_t *ent);
qboolean FindTarget (edict_t *self);
void HuntTarget (edict_t *self);
void FoundTarget (edict_t *self);
char *ED_NewString (char *string);
void spawngrow_think (edict_t *self);
void SpawnGrow_Spawn (vec3_t startpos, int size);
void ED_CallSpawn (edict_t *ent);
void M_FliesOff (edict_t *self);
void M_FliesOn (edict_t *self);
static int sound_idle1;
static int sound_pain1;
static int sound_pain2;
static int sound_die;
static int sound_sight;
static int sound_search;
static int sound_hook_launch;
static int sound_hook_hit;
static int sound_hook_heal;
static int sound_hook_retract;
// PMM - commander sounds
static int commander_sound_idle1;
static int commander_sound_pain1;
static int commander_sound_pain2;
static int commander_sound_die;
static int commander_sound_sight;
static int commander_sound_search;
static int commander_sound_hook_launch;
static int commander_sound_hook_hit;
static int commander_sound_hook_heal;
static int commander_sound_hook_retract;
static int commander_sound_spawn;
char * reinforcements[] = {
{"monster_soldier_light"}, // 0
{"monster_soldier"}, // 1
{"monster_soldier_ss"}, // 2
{"monster_infantry"}, // 3
{"monster_gunner"}, // 4
// {"monster_chick"}, // 4
{"monster_medic"}, // 5
{"monster_gladiator"} // 6
};
vec3_t reinforcement_mins[] = {
{-16, -16, -24},
{-16, -16, -24},
{-16, -16, -24},
{-16, -16, -24},
{-16, -16, -24},
{-16, -16, -24},
{-32, -32, -24}
};
vec3_t reinforcement_maxs[] = {
{16, 16, 32},
{16, 16, 32},
{16, 16, 32},
{16, 16, 32},
{16, 16, 32},
{16, 16, 32},
{32, 32, 64}
};
vec3_t reinforcement_position[] = {
{80, 0, 0},
{40, 60, 0},
{40, -60, 0},
{0, 80, 0},
{0, -80, 0}
};
void cleanupHeal (edict_t *self, qboolean change_frame)
{
// clean up target, if we have one and it's legit
if (self->enemy && self->enemy->inuse)
{
self->enemy->monsterinfo.healer = NULL;
self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING;
self->enemy->takedamage = DAMAGE_YES;
M_SetEffects (self->enemy);
}
if (change_frame)
self->monsterinfo.nextframe = FRAME_attack52;
}
void abortHeal (edict_t *self, qboolean change_frame, qboolean gib, qboolean mark)
{
int hurt;
static vec3_t pain_normal = { 0, 0, 1 };
// clean up target
cleanupHeal (self, change_frame);
// gib em!
if ((mark) && (self->enemy) && (self->enemy->inuse))
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("%s - marking target as bad\n", self->classname);
// if the first badMedic slot is filled by a medic, skip it and use the second one
if ((self->enemy->monsterinfo.badMedic1) && (self->enemy->monsterinfo.badMedic1->inuse)
&& (!strncmp(self->enemy->monsterinfo.badMedic1->classname, "monster_medic", 13)) )
{
self->enemy->monsterinfo.badMedic2 = self;
}
else
{
self->enemy->monsterinfo.badMedic1 = self;
}
}
if ((gib) && (self->enemy) && (self->enemy->inuse))
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("%s - gibbing bad heal target", self->classname);
if (self->enemy->gib_health)
hurt = - self->enemy->gib_health;
else
hurt = 500;
T_Damage (self->enemy, self, self, vec3_origin, self->enemy->s.origin,
pain_normal, hurt, 0, 0, MOD_UNKNOWN);
}
// clean up self
self->monsterinfo.aiflags &= ~AI_MEDIC;
if ((self->oldenemy) && (self->oldenemy->inuse) && (self->oldenemy != self->enemy))
{
self->enemy = self->oldenemy;
self->oldenemy = NULL;
}
else
self->enemy = NULL;
self->monsterinfo.medicTries = 0;
}
/*
qboolean canReach (edict_t *self, edict_t *other)
{
vec3_t spot1;
vec3_t spot2;
trace_t trace;
VectorCopy (self->s.origin, spot1);
spot1[2] += self->viewheight;
VectorCopy (other->s.origin, spot2);
spot2[2] += other->viewheight;
trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_SHOT|MASK_WATER);
if (trace.fraction == 1.0 || trace.ent == other) // PGM
return true;
return false;
}*/
// Lazarus: embedded returns true if argument entity's bounding box intersects
// a solid.
qboolean embedded (edict_t *ent)
{
trace_t tr;
tr = gi.trace(ent->s.origin,ent->mins,ent->maxs,ent->s.origin,ent,MASK_MONSTERSOLID);
if (tr.startsolid)
return true;
else
return false;
}
edict_t *medic_FindDeadMonster (edict_t *self)
{
float radius;
edict_t *ent = NULL;
edict_t *best = NULL;
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
radius = MEDIC_MAX_HEAL_DISTANCE;
else
radius = 1024;
while ((ent = findradius(ent, self->s.origin, radius)) != NULL)
{
if (ent == self)
continue;
if (!(ent->svflags & SVF_MONSTER))
continue;
if (ent->monsterinfo.aiflags & AI_GOOD_GUY)
continue;
// check to make sure we haven't bailed on this guy already
if ((ent->monsterinfo.badMedic1 == self) || (ent->monsterinfo.badMedic2 == self))
continue;
if (ent->monsterinfo.healer)
// FIXME - this is correcting a bug that is somewhere else
// if the healer is a monster, and it's in medic mode .. continue .. otherwise
// we will override the healer, if it passes all the other tests
if ((ent->monsterinfo.healer->inuse) && (ent->monsterinfo.healer->health > 0) &&
(ent->monsterinfo.healer->svflags & SVF_MONSTER) && (ent->monsterinfo.healer->monsterinfo.aiflags & AI_MEDIC))
continue;
if (ent->health > 0)
continue;
if ((ent->nextthink) && !((ent->think == M_FliesOn) || (ent->think == M_FliesOff)))
continue;
if (!visible(self, ent))
// if (!canReach(self, ent))
continue;
if (!strncmp(ent->classname, "player", 6)) // stop it from trying to heal player_noise entities
continue;
// FIXME - there's got to be a better way ..
// make sure we don't spawn people right on top of us
if (realrange(self, ent) <= MEDIC_MIN_DISTANCE)
continue;
if (!best)
{
best = ent;
continue;
}
if (ent->max_health <= best->max_health)
continue;
best = ent;
}
// Knightmare- backup player enemy
if ((self->enemy) && (self->enemy->client) && (self->enemy->health > 0) && !self->monsterinfo.last_player_enemy)
self->monsterinfo.last_player_enemy = self->enemy;
// Knightamre- don't overwrite oldenemy if not necessary
if (best && !((self->enemy) && (self->oldenemy) && (self->enemy == self->oldenemy)) )
{
self->oldenemy = self->enemy;
self->enemy = best;
self->enemy->owner = best;
self->monsterinfo.aiflags |= AI_MEDIC;
self->monsterinfo.aiflags &= ~AI_MEDIC_PATROL;
self->monsterinfo.medicTries = 0;
self->movetarget = self->goalentity = NULL;
self->enemy->monsterinfo.healer = self;
self->timestamp = level.time + MEDIC_TRY_TIME;
FoundTarget (self);
if (developer->value)
gi.dprintf("medic found dead monster: %s at %s\n",
best->classname,vtos(best->s.origin));
}
return best;
}
void medic_StopPatrolling (edict_t *self)
{
self->goalentity = NULL;
self->movetarget = NULL;
self->monsterinfo.aiflags &= ~AI_MEDIC_PATROL;
if (!(self->monsterinfo.aiflags & AI_MEDIC))
{
if (medic_FindDeadMonster(self))
return;
}
if (has_valid_enemy(self))
{
if (visible(self, self->enemy))
{
FoundTarget (self);
return;
}
HuntTarget (self);
return;
}
if (self->monsterinfo.aiflags & AI_MEDIC)
abortHeal(self,false,false,false);
}
void medic_NextPatrolPoint (edict_t *self, edict_t *hint)
{
edict_t *next=NULL;
edict_t *e;
vec3_t dir;
qboolean switch_paths=false;
self->monsterinfo.aiflags &= ~AI_MEDIC_PATROL;
// if (self->monsterinfo.aiflags & AI_MEDIC)
// return;
if (self->goalentity == hint)
self->goalentity = NULL;
if (self->movetarget == hint)
self->movetarget = NULL;
if (!(self->monsterinfo.aiflags & AI_MEDIC))
{
if (medic_FindDeadMonster(self))
return;
}
if (self->monsterinfo.pathdir == 1)
{
if (hint->hint_chain)
next = hint->hint_chain;
else
{
self->monsterinfo.pathdir = -1;
switch_paths = true;
}
}
if (self->monsterinfo.pathdir == -1)
{
e = hint_path_start[hint->hint_chain_id];
while (e)
{
if (e->hint_chain == hint)
{
next = e;
break;
}
e = e->hint_chain;
}
}
if (!next)
{
self->monsterinfo.pathdir = 1;
next = hint->hint_chain;
switch_paths = true;
}
// If switch_paths is true, we reached the end of a hint_chain. Just for grins,
// search for *another* visible hint_path chain and use it if it's reasonably close
if (switch_paths && num_hint_paths > 1)
{
edict_t *e;
edict_t *alternate=NULL;
float dist;
vec3_t dir;
int i;
float bestdistance=512;
for (i=game.maxclients+1; i<globals.num_edicts; i++)
{
e = &g_edicts[i];
if (!e->inuse)
continue;
if (Q_stricmp(e->classname,"hint_path"))
continue;
if (next && (e->hint_chain_id == next->hint_chain_id))
continue;
if (!visible(self,e))
continue;
if (!canReach(self,e))
continue;
VectorSubtract(e->s.origin,self->s.origin,dir);
dist = VectorLength(dir);
if (dist < bestdistance)
{
alternate = e;
bestdistance = dist;
}
}
if (alternate)
next = alternate;
}
if (next)
{
self->hint_chain_id = next->hint_chain_id;
VectorSubtract(next->s.origin, self->s.origin, dir);
self->ideal_yaw = vectoyaw(dir);
self->goalentity = self->movetarget = next;
self->monsterinfo.pausetime = 0;
self->monsterinfo.aiflags |= AI_MEDIC_PATROL;
self->monsterinfo.aiflags &= ~(AI_SOUND_TARGET | AI_PURSUIT_LAST_SEEN | AI_PURSUE_NEXT | AI_PURSUE_TEMP);
// run for it
self->monsterinfo.run (self);
}
else
{
self->monsterinfo.pausetime = level.time + 100000000;
self->monsterinfo.stand (self);
}
}
void medic_idle (edict_t *self)
{
//edict_t *ent;
if (!(self->spawnflags & SF_MONSTER_AMBUSH))
{
// PMM - commander sounds
// if (strcmp(self->classname, "monster_medic_commander"))
if ( !(self->moreflags & FL2_COMMANDER) )
gi.sound (self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0);
else
gi.sound (self, CHAN_VOICE, commander_sound_idle1, 1, ATTN_IDLE, 0);
}
if (self->monsterinfo.aiflags & AI_MEDIC)
{
// Then we must have reached this point after losing sight
// of our patient.
abortHeal (self, false, false, false);
}
/* if (!self->oldenemy)
{
ent = medic_FindDeadMonster(self);
if (ent)
{
self->oldenemy = self->enemy;
self->enemy = ent;
self->enemy->monsterinfo.healer = self;
self->monsterinfo.aiflags |= AI_MEDIC;
FoundTarget (self);
}
}*/
if (medic_FindDeadMonster(self))
return;
// If the map has hint_paths, AND the medic isn't at a HOLD point_combat,
// AND the medic has previously called FoundTarget (trail_time set to
// level.time), then look for hint_path chain and follow it, hopefully
// to find monsters to resurrect
if (self->monsterinfo.aiflags2 & AI2_HINT_TEST)
return;
if (hint_paths_present && !(self->monsterinfo.aiflags & AI_STAND_GROUND)
&& ((self->monsterinfo.trail_time > 0) /*|| medic_test*/) )
{
edict_t *e;
edict_t *hint=NULL;
float dist;
vec3_t dir;
int i;
float bestdistance=99999;
for (i=game.maxclients+1; i<globals.num_edicts; i++)
{
e = &g_edicts[i];
if (!e->inuse)
continue;
if (Q_stricmp(e->classname,"hint_path"))
continue;
if (!visible(self,e))
continue;
if (!canReach(self,e))
continue;
VectorSubtract(e->s.origin,self->s.origin,dir);
dist = VectorLength(dir);
if (dist < bestdistance)
{
hint = e;
bestdistance = dist;
}
}
if (hint)
{
self->hint_chain_id = hint->hint_chain_id;
if (!self->monsterinfo.pathdir)
self->monsterinfo.pathdir = 1;
VectorSubtract(hint->s.origin, self->s.origin, dir);
self->ideal_yaw = vectoyaw(dir);
self->goalentity = self->movetarget = hint;
self->monsterinfo.pausetime = 0;
self->monsterinfo.aiflags |= AI_MEDIC_PATROL;
self->monsterinfo.aiflags &= ~(AI_SOUND_TARGET | AI_PURSUIT_LAST_SEEN | AI_PURSUE_NEXT | AI_PURSUE_TEMP);
// run for it
self->monsterinfo.run (self);
}
}
}
void medic_search (edict_t *self)
{
edict_t *ent;
// PMM - commander sounds
// if (strcmp(self->classname, "monster_medic_commander"))
if ( !(self->moreflags & FL2_COMMANDER) )
gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_IDLE, 0);
else
gi.sound (self, CHAN_VOICE, commander_sound_search, 1, ATTN_IDLE, 0);
if (!self->oldenemy)
{
ent = medic_FindDeadMonster(self);
if (ent)
{
self->oldenemy = self->enemy;
self->enemy = ent;
self->enemy->monsterinfo.healer = self;
self->monsterinfo.aiflags |= AI_MEDIC;
FoundTarget (self);
}
}
}
void medic_sight (edict_t *self, edict_t *other)
{
// PMM - commander sounds
// if (strcmp(self->classname, "monster_medic_commander"))
if ( !(self->moreflags & FL2_COMMANDER) )
gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
else
gi.sound (self, CHAN_VOICE, commander_sound_sight, 1, ATTN_NORM, 0);
}
mframe_t medic_frames_stand [] =
{
ai_stand, 0, medic_idle,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
ai_stand, 0, NULL,
};
mmove_t medic_move_stand = {FRAME_wait1, FRAME_wait90, medic_frames_stand, NULL};
void medic_stand (edict_t *self)
{
self->monsterinfo.currentmove = &medic_move_stand;
}
mframe_t medic_frames_walk [] =
{
ai_walk, 6.2, NULL,
ai_walk, 18.1, NULL,
ai_walk, 1, NULL,
ai_walk, 9, NULL,
ai_walk, 10, NULL,
ai_walk, 9, NULL,
ai_walk, 11, NULL,
ai_walk, 11.6, NULL,
ai_walk, 2, NULL,
ai_walk, 9.9, NULL,
ai_walk, 14, NULL,
ai_walk, 9.3, NULL
};
mmove_t medic_move_walk = {FRAME_walk1, FRAME_walk12, medic_frames_walk, NULL};
void medic_walk (edict_t *self)
{
self->monsterinfo.currentmove = &medic_move_walk;
}
mframe_t medic_frames_run [] =
{
ai_run, 18, NULL,
ai_run, 22.5, NULL,
ai_run, 25.4, monster_done_dodge,
ai_run, 23.4, NULL,
ai_run, 24, NULL,
ai_run, 35.6, NULL //pmm
};
mmove_t medic_move_run = {FRAME_run1, FRAME_run6, medic_frames_run, NULL};
void medic_run (edict_t *self)
{
monster_done_dodge (self);
if (!(self->monsterinfo.aiflags & AI_MEDIC))
{
edict_t *ent;
ent = medic_FindDeadMonster(self);
// Knightmare- don't overwrite oldenemy if not necessary
if (ent && !((self->enemy) && (self->oldenemy) && (self->enemy == self->oldenemy)) )
{
self->oldenemy = self->enemy;
self->enemy = ent;
self->enemy->monsterinfo.healer = self;
self->monsterinfo.aiflags |= AI_MEDIC;
FoundTarget (self);
return;
}
}
// else if (!canReach(self, self->enemy))
// {
// abortHeal (self, 0);
// }
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
self->monsterinfo.currentmove = &medic_move_stand;
else
self->monsterinfo.currentmove = &medic_move_run;
}
mframe_t medic_frames_pain1 [] =
{
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL
};
mmove_t medic_move_pain1 = {FRAME_paina1, FRAME_paina8, medic_frames_pain1, medic_run};
mframe_t medic_frames_pain2 [] =
{
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL
};
mmove_t medic_move_pain2 = {FRAME_painb1, FRAME_painb15, medic_frames_pain2, medic_run};
void medic_pain (edict_t *self, edict_t *other, float kick, int damage)
{
monster_done_dodge (self);
if ((self->health < (self->max_health / 2)))
self->s.skinnum |= 1;
/*if (strcmp(self->classname, "monster_medic_commander") == 0)
self->s.skinnum = 3;
else
self->s.skinnum = 1;*/
if (level.time < self->pain_debounce_time)
return;
self->pain_debounce_time = level.time + 3;
if (skill->value == 3)
return; // no pain anims in nightmare
// if we're healing someone, we ignore pain
if (self->monsterinfo.aiflags & AI_MEDIC)
return;
// if (strcmp(self->classname, "monster_medic_commander") == 0)
if (self->moreflags & FL2_COMMANDER)
{
if (damage < 35)
{
gi.sound (self, CHAN_VOICE, commander_sound_pain1, 1, ATTN_NORM, 0);
return;
}
self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
gi.sound (self, CHAN_VOICE, commander_sound_pain2, 1, ATTN_NORM, 0);
if (random() < (min(((float)damage * 0.005), 0.5))) // no more than 50% chance of big pain
self->monsterinfo.currentmove = &medic_move_pain2;
else
self->monsterinfo.currentmove = &medic_move_pain1;
}
else if (random() < 0.5)
{
gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
self->monsterinfo.currentmove = &medic_move_pain1;
}
else
{
gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
self->monsterinfo.currentmove = &medic_move_pain2;
}
// PMM - clear duck flag
if (self->monsterinfo.aiflags & AI_DUCKED)
monster_duck_up(self);
}
void medic_fire_blaster (edict_t *self)
{
vec3_t start;
vec3_t forward, right;
vec3_t end;
vec3_t dir;
int effect;
int damage = 2;
// paranoia checking
if (!(self->enemy && self->enemy->inuse))
return;
if ((self->s.frame == FRAME_attack9) || (self->s.frame == FRAME_attack12))
effect = EF_BLASTER;
else if ((self->s.frame == FRAME_attack19) || (self->s.frame == FRAME_attack22) || (self->s.frame == FRAME_attack25) || (self->s.frame == FRAME_attack28))
effect = EF_HYPERBLASTER;
else
effect = 0;
AngleVectors (self->s.angles, forward, right, NULL);
G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_MEDIC_BLASTER_1], forward, right, start);
VectorCopy (self->enemy->s.origin, end);
end[2] += self->enemy->viewheight;
// Lazarus fog reduction of accuracy
if (self->monsterinfo.visibility < FOG_CANSEEGOOD)
{
end[0] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
end[1] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
end[2] += crandom() * 320 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
}
VectorSubtract (end, start, dir);
if (!strcmp(self->enemy->classname, "tesla"))
damage = 3;
// medic commander shoots blaster2
// if (strcmp(self->classname, "monster_medic_commander") == 0)
if (self->moreflags & FL2_COMMANDER)
// monster_fire_blaster (self, start, dir, damage, 1000, MZ2_MEDIC_BLASTER_2, (effect!=0)?(effect|EF_TRACKER):0, BLASTER_GREEN);
monster_fire_blaster2 (self, start, dir, damage, 1000, MZ2_MEDIC_BLASTER_2, effect);
else
monster_fire_blaster (self, start, dir, damage, 1000, MZ2_MEDIC_BLASTER_1, effect, BLASTER_ORANGE);
}
void medic_dead (edict_t *self)
{
VectorSet (self->mins, -16, -16, -24);
VectorSet (self->maxs, 16, 16, -8);
self->movetype = MOVETYPE_TOSS;
self->svflags |= SVF_DEADMONSTER;
self->nextthink = 0;
gi.linkentity (self);
M_FlyCheck (self);
// Lazarus monster fade
if (world->effects & FX_WORLDSPAWN_CORPSEFADE)
{
self->think=FadeDieSink;
self->nextthink=level.time+corpse_fadetime->value;
}
}
mframe_t medic_frames_death [] =
{
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL
};
mmove_t medic_move_death = {FRAME_death1, FRAME_death30, medic_frames_death, medic_dead};
void medic_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
int n;
/*if (strcmp(self->classname, "monster_medic_commander") == 0)
self->s.skinnum = 3;
else
self->s.skinnum = 1*/
self->monsterinfo.power_armor_type = POWER_ARMOR_NONE;
// if we had a pending patient, he was already freed up in Killed
// check for gib
if (self->health <= self->gib_health && !(self->spawnflags & SF_MONSTER_NOGIB))
{
gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
for (n = 0; n < 2; n++)
ThrowGib (self, "models/objects/gibs/bone/tris.md2", 0, 0, damage, GIB_ORGANIC);
for (n = 0; n < 4; n++)
ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", 0, 0, damage, GIB_ORGANIC);
ThrowHead (self, "models/objects/gibs/head2/tris.md2", 0, 0, damage, GIB_ORGANIC);
self->deadflag = DEAD_DEAD;
return;
}
if (self->deadflag == DEAD_DEAD)
return;
// regular death
// PMM
// if (strcmp(self->classname, "monster_medic_commander"))
if ( !(self->moreflags & FL2_COMMANDER) )
gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
else
gi.sound (self, CHAN_VOICE, commander_sound_die, 1, ATTN_NORM, 0);
self->s.skinnum |= 1;
self->deadflag = DEAD_DEAD;
self->takedamage = DAMAGE_YES;
self->monsterinfo.currentmove = &medic_move_death;
}
mframe_t medic_frames_duck [] =
{
ai_move, -1, NULL,
ai_move, -1, NULL,
ai_move, -1, monster_duck_down,
ai_move, -1, monster_duck_hold,
ai_move, -1, NULL,
ai_move, -1, NULL,
ai_move, -1, NULL, // PMM - duck up used to be here
ai_move, -1, NULL,
ai_move, -1, NULL,
ai_move, -1, NULL,
ai_move, -1, NULL,
ai_move, -1, NULL,
ai_move, -1, NULL,
ai_move, -1, monster_duck_up,
ai_move, -1, NULL,
ai_move, -1, NULL
};
mmove_t medic_move_duck = {FRAME_duck1, FRAME_duck16, medic_frames_duck, medic_run};
// PMM -- moved dodge code to after attack code so I can reference attack frames
mframe_t medic_frames_attackHyperBlaster [] =
{
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, medic_fire_blaster,
ai_charge, 0, medic_fire_blaster,
ai_charge, 0, medic_fire_blaster,
ai_charge, 0, medic_fire_blaster,
ai_charge, 0, medic_fire_blaster,
ai_charge, 0, medic_fire_blaster,
ai_charge, 0, medic_fire_blaster,
ai_charge, 0, medic_fire_blaster,
ai_charge, 0, medic_fire_blaster,
ai_charge, 0, medic_fire_blaster,
ai_charge, 0, medic_fire_blaster,
ai_charge, 0, medic_fire_blaster
};
mmove_t medic_move_attackHyperBlaster = {FRAME_attack15, FRAME_attack30, medic_frames_attackHyperBlaster, medic_run};
void medic_continue (edict_t *self)
{
if (visible (self, self->enemy) )
if (random() <= 0.95)
self->monsterinfo.currentmove = &medic_move_attackHyperBlaster;
}
mframe_t medic_frames_attackBlaster [] =
{
ai_charge, 0, NULL,
ai_charge, 5, NULL,
ai_charge, 5, NULL,
ai_charge, 3, NULL,
ai_charge, 2, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, medic_fire_blaster,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, medic_fire_blaster,
ai_charge, 0, NULL,
ai_charge, 0, medic_continue // Change to medic_continue... Else, go to frame 32
};
mmove_t medic_move_attackBlaster = {FRAME_attack1, FRAME_attack14, medic_frames_attackBlaster, medic_run};
void medic_hook_launch (edict_t *self)
{
// PMM - commander sounds
// if (strcmp(self->classname, "monster_medic_commander"))
if ( !(self->moreflags & FL2_COMMANDER) )
gi.sound (self, CHAN_WEAPON, sound_hook_launch, 1, ATTN_NORM, 0);
else
gi.sound (self, CHAN_WEAPON, commander_sound_hook_launch, 1, ATTN_NORM, 0);
}
static vec3_t medic_cable_offsets[] =
{
45.0, -9.2, 15.5,
48.4, -9.7, 15.2,
47.8, -9.8, 15.8,
47.3, -9.3, 14.3,
45.4, -10.1, 13.1,
41.9, -12.7, 12.0,
37.8, -15.8, 11.2,
34.3, -18.4, 10.7,
32.7, -19.7, 10.4,
32.7, -19.7, 10.4
};
void medic_cable_attack (edict_t *self)
{
vec3_t offset, start, end, f, r;
trace_t tr;
vec3_t dir;
// vec3_t angles;
float distance;
qboolean targ_nogib = false;
if ((!self->enemy) || (!self->enemy->inuse) || (self->enemy->svflags & SVF_GIB))
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("medic - aborting heal due to target's disappearance\n");
abortHeal (self, true, false, false);
return;
}
//Knightmare- don't heal insanes or actors or critters
if (!strcmp (self->enemy->classname, "misc_insane") || !strcmp (self->enemy->classname, "misc_actor")
|| !strcmp (self->enemy->classname, "monster_mutant") || !strcmp (self->enemy->classname, "monster_flipper")
|| !strcmp (self->enemy->classname, "monster_gekk"))
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("medic - not healing insane or actor or critter\n");
abortHeal (self, true, false, true);
return;
}
// Lazarus: check embeddment
if (embedded(self->enemy))
{
// gi.dprintf ("dead monster embedded in solid, aborting heal\n");
abortHeal (self, true, false, true);
return;
}
// see if our enemy has changed to a client, or our target has more than 0 health,
// abort it .. we got switched to someone else due to damage
if ((self->enemy->client) || (self->enemy->health > 0))
{
// gi.dprintf ("medic - aborting heal due to target health > 0 or client\n");
abortHeal (self, true, false, false);
return;
}
AngleVectors (self->s.angles, f, r, NULL);
VectorCopy (medic_cable_offsets[self->s.frame - FRAME_attack42], offset);
G_ProjectSource (self->s.origin, offset, f, r, start);
// check for max distance
// not needed, done in checkattack
// check for min distance
VectorSubtract (start, self->enemy->s.origin, dir);
distance = VectorLength(dir);
if (distance < MEDIC_MIN_DISTANCE)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("medic - aborting heal due to proximity to target ");
abortHeal (self, true, false, false );
return;
}
/*if ((g_showlogic)&&(g_showlogic->value))
gi.dprintf ("distance to target is %f\n", distance);
if (distance > 300)
return;*/
// Lazarus: Check for enemy behind muzzle... don't do these guys, 'cause usually this
// results in monster entanglement
/*VectorNormalize(dir);
if (DotProduct(dir,f) < 0.)
{
gi.dprintf ("medic - aborting heal due to proximity to target ");
abortHeal (self, true, false, false);
return;
}*.
// check for min/max pitch
// PMM -- took out since it doesn't look bad when it fails
/*vectoangles (dir, angles);
if (angles[0] < -180)
angles[0] += 360;
if (fabs(angles[0]) > 45)
{
if ((g_showlogic) && (g_showlogic->value))
gi.dprintf ("medic - aborting heal due to bad angle\n");
abortHeal(self);
return;
}*/
tr = gi.trace (start, NULL, NULL, self->enemy->s.origin, self, MASK_SOLID);
if (tr.fraction != 1.0 && tr.ent != self->enemy)
{
if (tr.ent == world)
{
// give up on second try
if (self->monsterinfo.medicTries > 1)
{
abortHeal (self, true, false, true);
return;
}
self->monsterinfo.medicTries++;
cleanupHeal (self, 1);
return;
}
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("medic_commander - aborting heal due to beamus interruptus\n");
abortHeal (self, true, false, false);
return;
}
if (self->s.frame == FRAME_attack43)
{
// PMM - commander sounds
// if (strcmp(self->classname, "monster_medic_commander"))
if ( !(self->moreflags & FL2_COMMANDER) )
gi.sound (self->enemy, CHAN_AUTO, sound_hook_hit, 1, ATTN_NORM, 0);
else
gi.sound (self->enemy, CHAN_AUTO, commander_sound_hook_hit, 1, ATTN_NORM, 0);
self->enemy->monsterinfo.aiflags |= AI_RESURRECTING;
self->enemy->takedamage = DAMAGE_NO;
M_SetEffects (self->enemy);
}
else if (self->s.frame == FRAME_attack50)
{
vec3_t maxs;
// Knightmare- remember nogib flag for monsters that get resurrected
if ((!strcmp(self->enemy->classname, "monster_gekk") || !strcmp(self->enemy->classname, "monster_stalker"))
&& (self->enemy->spawnflags & 32))
targ_nogib = true;
else if (self->enemy->spawnflags & SF_MONSTER_NOGIB)
targ_nogib = true;
self->enemy->spawnflags = 0;
// now restore nogib flag
if ((!strcmp(self->enemy->classname, "monster_gekk") || !strcmp(self->enemy->classname, "monster_stalker"))
&& targ_nogib)
self->enemy->spawnflags |= 32;
else if (targ_nogib)
self->enemy->spawnflags |= SF_MONSTER_NOGIB;
self->enemy->monsterinfo.aiflags = 0;
self->enemy->target = NULL;
self->enemy->targetname = NULL;
self->enemy->combattarget = NULL;
self->enemy->deathtarget = NULL;
self->enemy->monsterinfo.healer = self;
// self->enemy->owner = self;
VectorCopy (self->enemy->maxs, maxs);
maxs[2] += 48; // compensate for change when they die
tr = gi.trace (self->enemy->s.origin, self->enemy->mins, maxs, self->enemy->s.origin, self->enemy, MASK_MONSTERSOLID);
if (tr.startsolid || tr.allsolid)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("Spawn point obstructed, aborting heal!\n");
abortHeal (self, true, true, false);
return;
}
else if (tr.ent != world)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf("heal in entity %s\n", tr.ent->classname);
abortHeal (self, true, true, false);
return;
}
/* else if (tr.ent == world)
{
if ((g_showlogic) && (g_showlogic->value))
gi.dprintf ("heal in world, aborting!\n");
abortHeal (self, 1);
return;
}*/
else
{
// self->enemy->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
self->enemy->monsterinfo.monsterflags |= MFL_DO_NOT_COUNT;
// Lazarus: reset initially dead monsters to use the INVERSE of their
// initial health, and force gib_health to default value
if (self->enemy->max_health < 0)
{
self->enemy->max_health = -self->enemy->max_health;
self->enemy->gib_health = 0;
}
self->enemy->health = self->enemy->max_health;
self->enemy->s.skinnum = 0;
self->enemy->takedamage = DAMAGE_AIM;
self->enemy->flags &= ~FL_NO_KNOCKBACK;
self->enemy->pain_debounce_time = 0;
self->enemy->damage_debounce_time = 0;
self->enemy->deadflag = DEAD_NO;
// turn off flies
if (self->enemy->s.effects & EF_FLIES)
M_FliesOff(self->enemy);
ED_CallSpawn (self->enemy);
self->enemy->monsterinfo.healer = NULL;
// self->enemy->owner = NULL;
// Knightmare- disable deadmonster_think
if (self->enemy->postthink)
self->enemy->postthink = NULL;
if (self->enemy->think)
{
self->enemy->nextthink = level.time;
self->enemy->think (self->enemy);
}
self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING;
M_SetEffects(self->enemy);
// self->enemy->monsterinfo.aiflags |= AI_IGNORE_SHOTS|AI_DO_NOT_COUNT;
self->enemy->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
self->enemy->monsterinfo.monsterflags |= MFL_DO_NOT_COUNT;
if ((self->oldenemy) && (self->oldenemy->inuse) && (self->oldenemy->health > 0)
&& (self->oldenemy != self->enemy)) // Knightmare- don't have monster attack himself
{ // turn healed monster on our enemy
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("setting heal target's enemy to %s\n", self->oldenemy->classname);
self->enemy->enemy = self->oldenemy;
FoundTarget (self->enemy);
}
// Knightmare- maybe this will fix it
else if (self->monsterinfo.last_player_enemy && (self->monsterinfo.last_player_enemy->health > 0))
{
// gi.dprintf ("setting medic's healmonster's enemy to last player\n");
self->enemy->enemy = self->monsterinfo.last_player_enemy;
self->oldenemy = NULL;
FoundTarget (self->enemy);
}
else
{
// if (g_showlogic && g_showlogic->value)
// gi.dprintf ("no valid enemy to set!\n");
self->enemy->enemy = NULL;
if (!FindTarget (self->enemy))
{
// no valid enemy, so stop acting
self->enemy->monsterinfo.pausetime = level.time + 100000000;
self->enemy->monsterinfo.stand (self->enemy);
}
self->enemy = NULL;
self->oldenemy = NULL;
if (!FindTarget (self))
{
// no valid enemy, so stop acting
self->monsterinfo.pausetime = level.time + 100000000;
self->monsterinfo.stand (self);
return;
}
}
}
}
else
{
if (self->s.frame == FRAME_attack44)
// PMM - medic commander sounds
// if (strcmp(self->classname, "monster_medic_commander"))
if ( !(self->moreflags & FL2_COMMANDER) )
gi.sound (self, CHAN_WEAPON, sound_hook_heal, 1, ATTN_NORM, 0);
else
gi.sound (self, CHAN_WEAPON, commander_sound_hook_heal, 1, ATTN_NORM, 0);
}
// adjust start for beam origin being in middle of a segment
VectorMA (start, 8, f, start);
// Knightmare- if enemy went away, like after returning from another level, return
if (!self->enemy)
return;
// adjust end z for end spot since the monster is currently dead
VectorCopy (self->enemy->s.origin, end);
end[2] = self->enemy->absmin[2] + self->enemy->size[2] / 2;
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_MEDIC_CABLE_ATTACK);
gi.WriteShort (self - g_edicts);
gi.WritePosition (start);
gi.WritePosition (end);
gi.multicast (self->s.origin, MULTICAST_PVS);
}
void medic_hook_retract (edict_t *self)
{
// if (strcmp(self->classname, "monster_medic_commander"))
if ( !(self->moreflags & FL2_COMMANDER) )
gi.sound (self, CHAN_WEAPON, sound_hook_retract, 1, ATTN_NORM, 0);
else
gi.sound (self, CHAN_WEAPON, sound_hook_retract, 1, ATTN_NORM, 0);
self->monsterinfo.aiflags &= ~AI_MEDIC;
if ((self->oldenemy) && (self->oldenemy->inuse)
// Knightmare- don't attack monster we just healed
&& (self->oldenemy != self->enemy))
{
// gi.dprintf ("setting medic's enemy to oldenemy\n");
self->enemy = self->oldenemy;
self->oldenemy = NULL;
FoundTarget (self);
}
// Knightmare- maybe this will fix it
else if (self->monsterinfo.last_player_enemy && (self->monsterinfo.last_player_enemy->health > 0))
{
// gi.dprintf ("setting medic's enemy to last player\n");
self->enemy = self->monsterinfo.last_player_enemy;
self->monsterinfo.last_player_enemy = NULL;
self->oldenemy = NULL;
FoundTarget (self);
}
else
{
self->enemy = NULL;
self->oldenemy = NULL;
if (!FindTarget (self))
{
// no valid enemy, so stop acting
self->monsterinfo.pausetime = level.time + 100000000;
self->monsterinfo.stand (self);
return;
}
}
}
mframe_t medic_frames_attackCable [] =
{
// ROGUE - negated 36-40 so he scoots back from his target a little
// ROGUE - switched 33-36 to ai_charge
// ROGUE - changed frame 52 to 0 to compensate for changes in 36-40
ai_charge, 2, NULL, //33
ai_charge, 3, NULL,
ai_charge, 5, NULL,
ai_charge, -4.4, NULL, //36
ai_charge, -4.7, NULL, //37
ai_charge, -5, NULL,
ai_charge, -6, NULL,
ai_charge, -4, NULL, //40
ai_charge, 0, NULL,
ai_move, 0, medic_hook_launch, //42
ai_move, 0, medic_cable_attack, //43
ai_move, 0, medic_cable_attack,
ai_move, 0, medic_cable_attack,
ai_move, 0, medic_cable_attack,
ai_move, 0, medic_cable_attack,
ai_move, 0, medic_cable_attack,
ai_move, 0, medic_cable_attack,
ai_move, 0, medic_cable_attack,
ai_move, 0, medic_cable_attack, //51
ai_move, 0, medic_hook_retract, //52
ai_move, -1.5, NULL,
ai_move, -1.2, NULL,
ai_move, -3, NULL,
ai_move, -2, NULL,
ai_move, 0.3, NULL,
ai_move, 0.7, NULL,
ai_move, 1.2, NULL,
ai_move, 1.3, NULL //60
};
mmove_t medic_move_attackCable = {FRAME_attack33, FRAME_attack60, medic_frames_attackCable, medic_run};
void medic_start_spawn (edict_t *self)
{
gi.sound (self, CHAN_WEAPON, commander_sound_spawn, 1, ATTN_NORM, 0);
self->monsterinfo.nextframe = FRAME_attack48;
}
void medic_determine_spawn (edict_t *self)
{
vec3_t f, r, offset, startpoint, spawnpoint;
float lucky;
int summonStr;
int count;
int inc;
int num_summoned; // should be 1, 3, or 5
int num_success = 0;
lucky = random();
summonStr = skill->value;
// bell curve - 0 = 67%, 1 = 93%, 2 = 99% -- too steep
// this ends up with
// -3 = 5%
// -2 = 10%
// -1 = 15%
// 0 = 40%
// +1 = 15%
// +2 = 10%
// +3 = 5%
if (lucky < 0.05)
summonStr -= 3;
else if (lucky < 0.15)
summonStr -= 2;
else if (lucky < 0.3)
summonStr -= 1;
else if (lucky > 0.95)
summonStr += 3;
else if (lucky > 0.85)
summonStr += 2;
else if (lucky > 0.7)
summonStr += 1;
if (summonStr < 0)
summonStr = 0;
//FIXME - need to remember this, might as well use this int that isn't used for monsters
self->plat2flags = summonStr;
AngleVectors (self->s.angles, f, r, NULL);
// this yields either 1, 3, or 5
if (summonStr)
num_summoned = (summonStr - 1) + (summonStr % 2);
else
num_summoned = 1;
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("medic_commander: summonStr = %d num = %d\n", summonStr, num_summoned);
for (count = 0; count < num_summoned; count++)
{
inc = count + (count%2); // 0, 2, 2, 4, 4
VectorCopy (reinforcement_position[count], offset);
G_ProjectSource (self->s.origin, offset, f, r, startpoint);
// a little off the ground
startpoint[2] += 10;
if (FindSpawnPoint (startpoint, reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], spawnpoint, 32))
{
if (CheckGroundSpawnPoint(spawnpoint,
reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc],
256, -1))
{
num_success++;
// we found a spot, we're done here
count = num_summoned;
}
// else if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("medic_commander: CheckGroundSpawnPoint says bad stuff down there!\n");
}
}
if (num_success == 0)
{
for (count = 0; count < num_summoned; count++)
{
inc = count + (count%2); // 0, 2, 2, 4, 4
VectorCopy (reinforcement_position[count], offset);
// check behind
offset[0] *= -1.0;
offset[1] *= -1.0;
G_ProjectSource (self->s.origin, offset, f, r, startpoint);
// a little off the ground
startpoint[2] += 10;
if (FindSpawnPoint (startpoint, reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], spawnpoint, 32))
{
if (CheckGroundSpawnPoint(spawnpoint,
reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc],
256, -1))
{
num_success++;
// we found a spot, we're done here
count = num_summoned;
}
}
}
if (num_success)
{
self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
self->ideal_yaw = anglemod(self->s.angles[YAW]) + 180;
if (self->ideal_yaw > 360.0)
self->ideal_yaw -= 360.0;
// self->plat2flags *= -1;
}
}
if (num_success == 0)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("medic_commander: failed to find any spawn points, aborting!\n");
self->monsterinfo.nextframe = FRAME_attack53;
}
}
void medic_spawngrows (edict_t *self)
{
vec3_t f, r, offset, startpoint, spawnpoint;
int summonStr;
int count;
int inc;
int num_summoned; // should be 1, 3, or 5
int num_success = 0;
float current_yaw;
qboolean behind = false;
// if we've been directed to turn around
if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
{
current_yaw = anglemod(self->s.angles[YAW]);
if (fabs(current_yaw - self->ideal_yaw) > 0.1)
{
self->monsterinfo.aiflags |= AI_HOLD_FRAME;
return;
}
// done turning around
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
}
// if (self->plat2flags < 0)
// {
// summonStr = -1.0 * self->plat2flags;
// behind = true;
// }
// else
summonStr = self->plat2flags;
AngleVectors (self->s.angles, f, r, NULL);
// num_summoned = ((((summonStr-1)/2)+1)*2)-1; // this yields either 1, 3, or 5
if (summonStr)
num_summoned = (summonStr - 1) + (summonStr % 2);
else
num_summoned = 1;
for (count = 0; count < num_summoned; count++)
{
inc = count + (count%2); // 0, 2, 2, 4, 4
VectorCopy (reinforcement_position[count], offset);
G_ProjectSource (self->s.origin, offset, f, r, startpoint);
// a little off the ground
startpoint[2] += 10;
if (FindSpawnPoint (startpoint, reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], spawnpoint, 32))
{
if (CheckGroundSpawnPoint(spawnpoint,
reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc],
256, -1))
{
num_success++;
if ((summonStr-inc) > 3)
SpawnGrow_Spawn (spawnpoint, 1); // big monster
else
SpawnGrow_Spawn (spawnpoint, 0); // normal size
}
}
}
if (num_success == 0)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("medic_commander: spawngrows bad, aborting!\n");
self->monsterinfo.nextframe = FRAME_attack53;
}
}
void medic_finish_spawn (edict_t *self)
{
edict_t *ent;
vec3_t f, r, offset, startpoint, spawnpoint;
int summonStr;
int count;
int inc;
int num_summoned; // should be 1, 3, or 5
qboolean behind = false;
edict_t *designated_enemy;
// trace_t tr;
// vec3_t mins, maxs;
// this is one bigger than the soldier's real mins .. just for paranoia's sake
// VectorSet (mins, -17, -17, -25);
// VectorSet (maxs, 17, 17, 33);
//FIXME - better place to store this info?
if (self->plat2flags < 0)
{
behind = true;
self->plat2flags *= -1;
}
summonStr = self->plat2flags;
AngleVectors (self->s.angles, f, r, NULL);
// num_summoned = ((((summonStr-1)/2)+1)*2)-1; // this yields either 1, 3, or 5
if (summonStr)
num_summoned = (summonStr - 1) + (summonStr % 2);
else
num_summoned = 1;
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("medic_commander: summonStr = %d num = %d\n", summonStr, num_summoned);
for (count = 0; count < num_summoned; count++)
{
inc = count + (count%2); // 0, 2, 2, 4, 4
VectorCopy (reinforcement_position[count], offset);
G_ProjectSource (self->s.origin, offset, f, r, startpoint);
// a little off the ground
startpoint[2] += 10;
ent = NULL;
if (FindSpawnPoint (startpoint, reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], spawnpoint, 32))
{
if (CheckSpawnPoint (spawnpoint, reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc]))
ent = CreateGroundMonster (spawnpoint, self->s.angles,
reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc],
reinforcements[summonStr-inc], 256);
// else if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("CheckSpawnPoint failed volume check!\n");
}
else
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("FindSpawnPoint failed to find a point!\n");
}
if (!ent)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("Spawn point obstructed for %s, aborting!\n", reinforcements[summonStr-inc]);
continue;
}
// gi.sound (self, CHAN_WEAPON, commander_sound_spawn, 1, ATTN_NORM, 0);
if (ent->think)
{
ent->nextthink = level.time;
ent->think (ent);
}
//ent->monsterinfo.aiflags |= AI_IGNORE_SHOTS|AI_DO_NOT_COUNT|AI_SPAWNED_MEDIC_C;
ent->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
ent->monsterinfo.monsterflags |= MFL_DO_NOT_COUNT|MFL_SPAWNED_MEDIC_C;
ent->monsterinfo.commander = self;
self->monsterinfo.monster_slots--;
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("medic_commander: %d slots remaining\n", self->monsterinfo.monster_slots);
if (self->monsterinfo.aiflags & AI_MEDIC)
designated_enemy = self->oldenemy;
else
designated_enemy = self->enemy;
if (coop && coop->value)
{
designated_enemy = PickCoopTarget(ent);
if (designated_enemy)
{
// try to avoid using my enemy
if (designated_enemy == self->enemy)
{
designated_enemy = PickCoopTarget(ent);
if (designated_enemy)
{
/* if ((g_showlogic) && (g_showlogic->value))
{
gi.dprintf ("PickCoopTarget returned a %s - ", designated_enemy->classname);
if (designated_enemy->client)
gi.dprintf ("with name %s\n", designated_enemy->client->pers.netname);
else
gi.dprintf ("NOT A CLIENT\n");
} */
}
else
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("pick coop failed, using my current enemy\n");
designated_enemy = self->enemy;
}
}
}
else
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("pick coop failed, using my current enemy\n");
designated_enemy = self->enemy;
}
}
if ((designated_enemy) && (designated_enemy->inuse) && (designated_enemy->health > 0))
{
// fixme
// if ((g_showlogic) && (g_showlogic -> value))
// gi.dprintf ("setting enemy to %s\n", designated_enemy->classname);
ent->enemy = designated_enemy;
FoundTarget (ent);
}
else
{
ent->enemy = NULL;
ent->monsterinfo.stand (ent);
}
// ent->s.event = EV_PLAYER_TELEPORT;
}
}
mframe_t medic_frames_callReinforcements [] =
{
// ROGUE - 33-36 now ai_charge
ai_charge, 2, NULL, //33
ai_charge, 3, NULL,
ai_charge, 5, NULL,
ai_charge, 4.4, NULL, //36
ai_charge, 4.7, NULL,
ai_charge, 5, NULL,
ai_charge, 6, NULL,
ai_charge, 4, NULL, //40
ai_charge, 0, NULL,
ai_move, 0, medic_start_spawn, //42
ai_move, 0, NULL, //43 -- 43 through 47 are skipped
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, medic_determine_spawn, //48
ai_charge, 0, medic_spawngrows, //49
ai_move, 0, NULL, //50
ai_move, 0, NULL, //51
ai_move, -15, medic_finish_spawn, //52
ai_move, -1.5, NULL,
ai_move, -1.2, NULL,
ai_move, -3, NULL,
ai_move, -2, NULL,
ai_move, 0.3, NULL,
ai_move, 0.7, NULL,
ai_move, 1.2, NULL,
ai_move, 1.3, NULL //60
};
mmove_t medic_move_callReinforcements = {FRAME_attack33, FRAME_attack60, medic_frames_callReinforcements, medic_run};
void medic_attack (edict_t *self)
{
int enemy_range;
float r;
monster_done_dodge (self);
enemy_range = range(self, self->enemy);
// signal from checkattack to spawn
if (self->monsterinfo.aiflags & AI_BLOCKED)
{
self->monsterinfo.currentmove = &medic_move_callReinforcements;
self->monsterinfo.aiflags &= ~AI_BLOCKED;
}
r = random();
if (self->monsterinfo.aiflags & AI_MEDIC)
{
// if ((strcmp(self->classname, "monster_medic_commander") == 0) && (r > 0.8) && (self->monsterinfo.monster_slots > 2))
if ((self->moreflags & FL2_COMMANDER) && (r > 0.8) && (self->monsterinfo.monster_slots > 2))
self->monsterinfo.currentmove = &medic_move_callReinforcements;
else
self->monsterinfo.currentmove = &medic_move_attackCable;
}
else
{
if (self->monsterinfo.attack_state == AS_BLIND)
{
self->monsterinfo.currentmove = &medic_move_callReinforcements;
return;
}
// if ((strcmp(self->classname, "monster_medic_commander") == 0) && (r > 0.2) && (enemy_range != RANGE_MELEE) && (self->monsterinfo.monster_slots > 2))
if ((self->moreflags & FL2_COMMANDER) && (r > 0.2) && (enemy_range != RANGE_MELEE) && (self->monsterinfo.monster_slots > 2))
self->monsterinfo.currentmove = &medic_move_callReinforcements;
else
self->monsterinfo.currentmove = &medic_move_attackBlaster;
}
}
qboolean medic_checkattack (edict_t *self)
{
if (self->monsterinfo.aiflags & AI_MEDIC)
{
// if our target went away
if ((!self->enemy) || (!self->enemy->inuse))
{
// if (g_showlogic && g_showlogic->value)
// gi.dprintf ("aborting heal target due to gib\n");
abortHeal (self, true, false, false);
return false;
}
// if we ran out of time, give up
if (self->timestamp < level.time)
{
// if (g_showlogic && g_showlogic->value)
// gi.dprintf ("aborting heal target (%s) due to time\n", self->enemy->classname);
abortHeal (self, true, false, true);
self->timestamp = 0;
return false;
}
if (realrange(self, self->enemy) < MEDIC_MAX_HEAL_DISTANCE+10)
{
medic_attack(self);
return true;
}
else
{
self->monsterinfo.attack_state = AS_STRAIGHT;
return false;
}
}
if (self->enemy->client && !visible (self, self->enemy) && (self->monsterinfo.monster_slots > 2))
{
self->monsterinfo.attack_state = AS_BLIND;
return true;
}
// give a LARGE bias to spawning things when we have room
// use AI_BLOCKED as a signal to attack to spawn
if ((random() < 0.8) && (self->monsterinfo.monster_slots > 5) && (realrange(self, self->enemy) > 150))
{
self->monsterinfo.aiflags |= AI_BLOCKED;
self->monsterinfo.attack_state = AS_MISSILE;
return true;
}
// ROGUE
// since his idle animation looks kinda bad in combat, if we're not in easy mode, always attack
// when he's on a combat point
if (skill->value > 0)
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
{
self->monsterinfo.attack_state = AS_MISSILE;
return true;
}
return M_CheckAttack (self);
}
/*
void medic_dodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr)
{
if (random() > 0.25)
return;
if (!self->enemy)
self->enemy = attacker;
self->monsterinfo.currentmove = &medic_move_duck;
//===========
//PMM - rogue rewrite of dodge code.
float r;
float height;
int shooting = 0;
// this needs to be before the AI_MEDIC check, because
// a) if AI_MEDIC is set, we should have an enemy anyway and
// b) FoundTarget calls medic_run, which can set AI_MEDIC
if (!self->enemy)
{
self->enemy = attacker;
FoundTarget (self);
}
// don't dodge if you're healing
if (self->monsterinfo.aiflags & AI_MEDIC)
return;
// PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was
// seeing numbers like 13 and 14)
if ((eta < 0.1) || (eta > 5))
return;
r = random();
if (r > (0.25*((skill->value)+1)))
return;
if ((self->monsterinfo.currentmove == &medic_move_attackHyperBlaster) ||
(self->monsterinfo.currentmove == &medic_move_attackCable) ||
(self->monsterinfo.currentmove == &medic_move_attackBlaster))
{
shooting = 1;
}
if (self->monsterinfo.aiflags & AI_DODGING)
{
height = self->absmax[2];
}
else
{
height = self->absmax[2]-32-1; // the -1 is because the absmax is s.origin + maxs + 1
}
// check to see if it makes sense to duck
if (tr->endpos[2] <= height)
{
vec3_t right, diff;
if (shooting)
{
self->monsterinfo.attack_state = AS_SLIDING;
return;
}
AngleVectors (self->s.angles, NULL, right, NULL);
VectorSubtract (tr->endpos, self->s.origin, diff);
if (DotProduct (right, diff) < 0)
{
self->monsterinfo.lefty = 1;
}
// if it doesn't sense to duck, try to strafe away
monster_done_dodge (self);
self->monsterinfo.currentmove = &medic_move_run;
self->monsterinfo.attack_state = AS_SLIDING;
return;
}
if (skill->value == 0)
{
self->monsterinfo.currentmove = &medic_move_duck;
// PMM - stupid dodge
self->monsterinfo.duck_wait_time = level.time + eta + 1;
self->monsterinfo.aiflags |= AI_DODGING;
return;
}
if (!shooting)
{
self->monsterinfo.currentmove = &medic_move_duck;
self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
self->monsterinfo.aiflags |= AI_DODGING;
}
return;
//PMM
//===========
}
*/
void MedicCommanderCache (void)
{
//edict_t *newEnt;
int modelidx;
//int i;
//Knightmare- this is not needed and crashes some maps
/* //FIXME - better way to do this? this is quick and dirty
for (i=0; i < 7; i++)
{
newEnt = G_Spawn();
VectorCopy(vec3_origin, newEnt->s.origin);
VectorCopy(vec3_origin, newEnt->s.angles);
newEnt->classname = ED_NewString (reinforcements[i]);
//newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
newEnt->monsterinfo.monsterflags |= MFL_DO_NOT_COUNT;
ED_CallSpawn(newEnt);
// FIXME - could copy mins/maxs into reinforcements from here
G_FreeEdict (newEnt);
}
*/
modelidx = gi.modelindex("models/items/spawngro/tris.md2");
modelidx = gi.modelindex("models/items/spawngro2/tris.md2");
}
void medic_duck (edict_t *self, float eta)
{
// don't dodge if you're healing
if (self->monsterinfo.aiflags & AI_MEDIC)
return;
if ((self->monsterinfo.currentmove == &medic_move_attackHyperBlaster) ||
(self->monsterinfo.currentmove == &medic_move_attackCable) ||
(self->monsterinfo.currentmove == &medic_move_attackBlaster) ||
(self->monsterinfo.currentmove == &medic_move_callReinforcements))
{
// he ignores skill
self->monsterinfo.aiflags &= ~AI_DUCKED;
return;
}
if (skill->value == 0)
// PMM - stupid dodge
self->monsterinfo.duck_wait_time = level.time + eta + 1;
else
self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
// has to be done immediately otherwise he can get stuck
monster_duck_down(self);
self->monsterinfo.nextframe = FRAME_duck1;
self->monsterinfo.currentmove = &medic_move_duck;
return;
}
void medic_sidestep (edict_t *self)
{
if ((self->monsterinfo.currentmove == &medic_move_attackHyperBlaster) ||
(self->monsterinfo.currentmove == &medic_move_attackCable) ||
(self->monsterinfo.currentmove == &medic_move_attackBlaster) ||
(self->monsterinfo.currentmove == &medic_move_callReinforcements))
{
// if we're shooting, and not on easy, don't dodge
if (skill->value)
{
self->monsterinfo.aiflags &= ~AI_DODGING;
return;
}
}
if (self->monsterinfo.currentmove != &medic_move_run)
self->monsterinfo.currentmove = &medic_move_run;
}
//===========
//PGM
qboolean medic_blocked (edict_t *self, float dist)
{
if (blocked_checkshot (self, 0.25 + (0.05 * skill->value) ))
return true;
if (blocked_checkplat (self, dist))
return true;
return false;
}
//PGM
//===========
/*QUAKED monster_medic_commander (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight GoodGuy NoGib
*/
/*QUAKED monster_medic (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight GoodGuy NoGib
*/
void SP_monster_medic (edict_t *self)
{
if (deathmatch->value)
{
G_FreeEdict (self);
return;
}
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
if (strcmp(self->classname, "monster_medic_commander") == 0)
{
self->s.skinnum = 2;
self->moreflags |= FL2_COMMANDER;
}
else {
self->s.skinnum = 0;
}
// Lazarus: special purpose skins
if ( self->style )
{
PatchMonsterModel("models/monsters/medic/tris.md2");
self->s.skinnum += self->style * 4;
}
self->s.modelindex = gi.modelindex ("models/monsters/medic/tris.md2");
VectorSet (self->mins, -24, -24, -24);
VectorSet (self->maxs, 24, 24, 32);
//PMM
if (strcmp(self->classname, "monster_medic_commander") == 0)
{
if (skill->value == 0)
self->monsterinfo.monster_slots = 3;
else if (skill->value == 1)
self->monsterinfo.monster_slots = 4;
else if (skill->value == 2)
self->monsterinfo.monster_slots = 6;
else if (skill->value == 3)
self->monsterinfo.monster_slots = 8;
// commander sounds
commander_sound_idle1 = gi.soundindex ("medic_commander/medidle.wav");
commander_sound_pain1 = gi.soundindex ("medic_commander/medpain1.wav");
commander_sound_pain2 = gi.soundindex ("medic_commander/medpain2.wav");
commander_sound_die = gi.soundindex ("medic_commander/meddeth.wav");
commander_sound_sight = gi.soundindex ("medic_commander/medsght.wav");
commander_sound_search = gi.soundindex ("medic_commander/medsrch.wav");
commander_sound_hook_launch = gi.soundindex ("medic_commander/medatck2c.wav");
commander_sound_hook_hit = gi.soundindex ("medic_commander/medatck3a.wav");
commander_sound_hook_heal = gi.soundindex ("medic_commander/medatck4a.wav");
commander_sound_hook_retract = gi.soundindex ("medic_commander/medatck5a.wav");
commander_sound_spawn = gi.soundindex ("medic_commander/monsterspawn1.wav");
gi.soundindex ("tank/tnkatck3.wav");
MedicCommanderCache ();
// Knightmare- precache blaster bolt
gi.modelindex ("models/proj/laser2/tris.md2");
if (!self->health)
self->health = 600;
if (!self->gib_health)
self->gib_health = -150;
if (!self->mass)
self->mass = 600;
self->yaw_speed = 40; // default is 20
}
else
{
sound_idle1 = gi.soundindex ("medic/idle.wav");
sound_pain1 = gi.soundindex ("medic/medpain1.wav");
sound_pain2 = gi.soundindex ("medic/medpain2.wav");
sound_die = gi.soundindex ("medic/meddeth1.wav");
sound_sight = gi.soundindex ("medic/medsght1.wav");
sound_search = gi.soundindex ("medic/medsrch1.wav");
sound_hook_launch = gi.soundindex ("medic/medatck2.wav");
sound_hook_hit = gi.soundindex ("medic/medatck3.wav");
sound_hook_heal = gi.soundindex ("medic/medatck4.wav");
sound_hook_retract = gi.soundindex ("medic/medatck5.wav");
gi.soundindex ("medic/medatck1.wav");
if (!self->health)
self->health = 300;
if (!self->gib_health)
self->gib_health = -150;
if (!self->mass)
self->mass = 400;
}
self->pain = medic_pain;
self->die = medic_die;
self->monsterinfo.stand = medic_stand;
self->monsterinfo.walk = medic_walk;
self->monsterinfo.run = medic_run;
// pmm
self->monsterinfo.dodge = M_MonsterDodge;
self->monsterinfo.duck = medic_duck;
self->monsterinfo.unduck = monster_duck_up;
self->monsterinfo.sidestep = medic_sidestep;
// self->monsterinfo.dodge = medic_dodge;
// pmm
self->monsterinfo.attack = medic_attack;
self->monsterinfo.melee = NULL;
self->monsterinfo.sight = medic_sight;
self->monsterinfo.idle = medic_idle;
self->monsterinfo.search = medic_search;
self->monsterinfo.checkattack = medic_checkattack;
self->monsterinfo.blocked = medic_blocked;
// Knightmare- added sparks and blood type
if (!self->blood_type)
self->blood_type = 3; // sparks and blood
// Lazarus
if (self->powerarmor)
{
if (self->powerarmortype == 1)
self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
else
self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
self->monsterinfo.power_armor_power = self->powerarmor;
}
if ( !self->monsterinfo.flies && strcmp(self->classname, "monster_medic_commander") == 0 )
self->monsterinfo.flies = 0.10;
else if (!self->monsterinfo.flies)
self->monsterinfo.flies = 0.15;
gi.linkentity (self);
self->monsterinfo.currentmove = &medic_move_stand;
if (self->health < 0)
{
mmove_t *deathmoves[] = {&medic_move_death,
NULL};
M_SetDeath (self, (mmove_t **)&deathmoves);
}
self->monsterinfo.scale = MODEL_SCALE;
if (strcmp(self->classname, "monster_medic_commander") == 0) {
self->common_name = "Medic Commander";
self->class_id = ENTITY_MONSTER_MEDIC_COMMANDER;
}
else {
self->common_name = "Medic";
self->class_id = ENTITY_MONSTER_MEDIC;
}
walkmonster_start (self);
// PMM
self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
}