thirtyflightsofloving/missionpack/g_monster.c
Knightmare66 0d4e872ce9 Added LMCTF / LM Escape plasma rifle to missionpack DLL.
Added plasma guards (monster_soldier_plasma_re and monster_soldier_plasma_sp) from LM Escape to missionpack DLL.
Added Zaero items/weapons to missionpack DLL.
Added support for Zaero doors  to missionpack DLL.
Fixed crash caused by killtargeting sentien (laser edict not freed) in missionpack DLL.
Fixed bug with broken Rogue turrets in missionpack DLL.
Fixed crash in g_combat.c->M_ReactToDamage() caused by attacker with NULL classname in missionpack DLL.
2020-08-09 02:45:19 -04:00

2347 lines
63 KiB
C

#include "g_local.h"
#define MONSTER_TRIGGER_SPAWN 2
void InitiallyDead (edict_t *self);
// Lazarus: If worldspawn CORPSE_SINK effects flag is set,
// monsters/actors fade out and sink into the floor
// 30 seconds after death
#define SINKAMT 1
#ifdef KMQUAKE2_ENGINE_MOD
void FadeSink (edict_t *ent)
{
ent->count++;
if ((ent->count % 2) == 0)
ent->s.origin[2] -= SINKAMT;
ent->s.alpha -= 0.05;
if (ent->s.alpha < 0.0)
ent->s.alpha = 0.0;
if (ent->s.alpha <= 0.0)
{
G_FreeEdict (ent);
return;
}
if (ent->count == 20)
ent->think = G_FreeEdict;
ent->think = FadeSink;
ent->nextthink = level.time + 2*FRAMETIME;
}
void FadeDieSink (edict_t *ent)
{
ent->takedamage = DAMAGE_NO; // can't gib 'em once they start sinking
ent->s.effects &= ~EF_FLIES;
ent->s.sound = 0;
ent->s.origin[2] -= SINKAMT;
if (ent->s.renderfx & RF_TRANSLUCENT)
ent->s.alpha = 0.70;
else if (ent->s.effects & EF_SPHERETRANS)
ent->s.alpha = 0.30;
else if (!(ent->s.alpha) || ent->s.alpha <= 0.0)
ent->s.alpha = 1.00;
ent->think = FadeSink;
ent->nextthink = level.time + FRAMETIME;
ent->count = 0;
}
#else
void FadeSink (edict_t *ent)
{
ent->count++;
ent->s.origin[2] -= SINKAMT;
ent->think = FadeSink;
if (ent->count == 5)
{
ent->s.renderfx &= ~RF_TRANSLUCENT;
ent->s.effects |= EF_SPHERETRANS;
}
if (ent->count == 10)
ent->think = G_FreeEdict;
ent->nextthink = level.time+FRAMETIME;
}
void FadeDieSink (edict_t *ent)
{
ent->takedamage = DAMAGE_NO; // can't gib 'em once they start sinking
ent->s.effects &= ~EF_FLIES;
ent->s.sound = 0;
ent->s.origin[2] -= SINKAMT;
ent->s.renderfx = RF_TRANSLUCENT;
ent->think = FadeSink;
ent->nextthink = level.time + FRAMETIME;
ent->count = 0;
}
#endif
// Lazarus: M_SetDeath is used to restore the death movement,
// bounding box, and a few other parameters for dead
// monsters that change levels with a trigger_transition
qboolean M_SetDeath(edict_t *self, mmove_t **deathmoves)
{
mmove_t *move=NULL;
mmove_t *dmove;
if (self->health > 0)
return false;
while(*deathmoves && !move)
{
dmove = *deathmoves;
if ( (self->s.frame >= dmove->firstframe) &&
(self->s.frame <= dmove->lastframe) )
move = dmove;
else
deathmoves++;
}
if (move)
{
self->monsterinfo.currentmove = move;
if (self->monsterinfo.currentmove->endfunc)
self->monsterinfo.currentmove->endfunc(self);
self->s.frame = move->lastframe;
self->s.skinnum |= 1;
return true;
}
return false;
}
//
// monster weapons
//
//FIXME mosnters should call these with a totally accurate direction
// and we can mess it up based on skill. Spread should be for normal
// and we can tighten or loosen based on skill. We could muck with
// the damages too, but I'm not sure that's such a good idea.
void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype)
{
fire_bullet (self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype)
{
fire_shotgun (self, start, aimdir, damage, kick, hspread, vspread, count, MOD_UNKNOWN);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect, int color)
{
// Zaero add
if (EMPNukeCheck(self, start))
{
gi.sound (self, CHAN_AUTO, gi.soundindex("items/empnuke/emp_missfire.wav"), 1, ATTN_NORM, 0);
return;
}
// end Zaero
fire_blaster (self, start, dir, damage, speed, effect, false, color);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype)
{
fire_grenade (self, start, aimdir, damage, speed, 2.5, damage+40, false);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, edict_t *homing_target)
{
// Zaero add
if (EMPNukeCheck(self, start))
{
gi.sound (self, CHAN_AUTO, gi.soundindex("items/empnuke/emp_missfire.wav"), 1, ATTN_NORM, 0);
return;
}
// end Zaero
fire_rocket (self, start, dir, damage, speed, damage+20, damage, homing_target);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
// Knightmare added
void monster_fire_missile (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, edict_t *homing_target)
{
// Zaero add
if (EMPNukeCheck(self, start))
{
gi.sound (self, CHAN_AUTO, gi.soundindex("items/empnuke/emp_missfire.wav"), 1, ATTN_NORM, 0);
return;
}
// end Zaero
fire_missile (self, start, dir, damage, speed, damage+20, damage, homing_target);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype)
{
// Zaero add
if (EMPNukeCheck(self, start))
{
gi.sound (self, CHAN_AUTO, gi.soundindex("items/empnuke/emp_missfire.wav"), 1, ATTN_NORM, 0);
return;
}
// end Zaero
// PMM
if (!(gi.pointcontents (start) & MASK_SOLID))
fire_rail (self, start, aimdir, damage, kick);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype)
{
// Zaero add
if (EMPNukeCheck(self, start))
{
gi.sound (self, CHAN_AUTO, gi.soundindex("items/empnuke/emp_missfire.wav"), 1, ATTN_NORM, 0);
return;
}
// end Zaero
fire_bfg (self, start, aimdir, damage, speed, damage_radius);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
// RAFAEL
void monster_fire_blueblaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect)
{
// Zaero add
if (EMPNukeCheck(self, start))
{
gi.sound (self, CHAN_AUTO, gi.soundindex("items/empnuke/emp_missfire.wav"), 1, ATTN_NORM, 0);
return;
}
// end Zaero
fire_blueblaster (self, start, dir, damage, speed, effect);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (MZ_BLUEHYPERBLASTER);
gi.multicast (start, MULTICAST_PVS);
}
// RAFAEL
void monster_fire_ionripper (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect)
{
// Zaero add
if (EMPNukeCheck(self, start))
{
gi.sound (self, CHAN_AUTO, gi.soundindex("items/empnuke/emp_missfire.wav"), 1, ATTN_NORM, 0);
return;
}
// end Zaero
fire_ionripper (self, start, dir, damage, speed, effect);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
// RAFAEL
void monster_fire_rocket_heat (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype)
{
// Zaero add
if (EMPNukeCheck(self, start))
{
gi.sound (self, CHAN_AUTO, gi.soundindex("items/empnuke/emp_missfire.wav"), 1, ATTN_NORM, 0);
return;
}
// end Zaero
fire_rocket_heat (self, start, dir, damage, speed, damage, damage);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
// RAFAEL
void dabeam_hit (edict_t *self)
{
edict_t *ignore;
vec3_t start;
vec3_t end;
trace_t tr;
int count;
static vec3_t lmins = {-4, -4, -4};
static vec3_t lmaxs = {4, 4, 4};
if (self->spawnflags & 0x80000000)
count = 8;
else
count = 4;
ignore = self;
VectorCopy (self->s.origin, start);
VectorMA (start, 2048, self->movedir, end);
while(1)
{ //Knightmare- double trace here, needed to make sure clipping works- must be compiler weirdness
tr = gi.trace (start, NULL, NULL, end, ignore, (CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER));
tr = gi.trace (start, NULL, NULL, end, ignore, (CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER));
if (!tr.ent)
break;
// hurt it if we can
if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) && (tr.ent != self->owner))
T_Damage (tr.ent, self, self->owner, self->movedir, tr.endpos, vec3_origin, self->dmg, skill->value, DAMAGE_ENERGY, MOD_TARGET_LASER);
if (self->dmg < 0) // healer ray
{
// when player is at 100 health
// just undo health fix
// keeping fx
if (tr.ent->client && tr.ent->health > 100)
tr.ent->health += self->dmg;
}
// if we hit something that's not a monster or player or is immune to lasers, we're done
if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
{
if (self->spawnflags & 0x80000000)
{
self->spawnflags &= ~0x80000000;
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_LASER_SPARKS);
gi.WriteByte (10);
gi.WritePosition (tr.endpos);
gi.WriteDir (tr.plane.normal);
gi.WriteByte (self->s.skinnum);
gi.multicast (tr.endpos, MULTICAST_PVS);
}
break;
}
ignore = tr.ent;
VectorCopy (tr.endpos, start);
}
VectorCopy (tr.endpos, self->s.old_origin);
self->nextthink = level.time + 0.1;
self->think = G_FreeEdict;
}
// RAFAEL
void monster_dabeam (edict_t *self)
{
vec3_t last_movedir;
vec3_t point;
self->movetype = MOVETYPE_NONE;
self->solid = SOLID_NOT;
self->s.renderfx |= RF_BEAM|RF_TRANSLUCENT;
self->s.modelindex = 1;
self->s.frame = 2;
if (self->owner->monsterinfo.aiflags & AI_MEDIC)
self->s.skinnum = 0xf3f3f1f1;
else
self->s.skinnum = 0xf2f2f0f0;
if (self->enemy)
{
VectorCopy (self->movedir, last_movedir);
VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point);
if (self->owner->monsterinfo.aiflags & AI_MEDIC)
point[0] += sin (level.time) * 8;
VectorSubtract (point, self->s.origin, self->movedir);
VectorNormalize (self->movedir);
if (!VectorCompare(self->movedir, last_movedir))
self->spawnflags |= 0x80000000;
}
else
G_SetMovedir (self->s.angles, self->movedir);
self->think = dabeam_hit;
self->nextthink = level.time + 0.1;
VectorSet (self->mins, -8, -8, -8);
VectorSet (self->maxs, 8, 8, 8);
gi.linkentity (self);
self->spawnflags |= 0x80000001;
self->svflags &= ~SVF_NOCLIENT;
}
// ROGUE
void monster_fire_blaster2 (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect)
{
// Zaero add
if (EMPNukeCheck(self, start))
{
gi.sound (self, CHAN_AUTO, gi.soundindex("items/empnuke/emp_missfire.wav"), 1, ATTN_NORM, 0);
return;
}
// end Zaero
fire_blaster2 (self, start, dir, damage, speed, effect, false);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
// FIXME -- add muzzle flash
void monster_fire_tracker (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, edict_t *enemy, int flashtype)
{
// Zaero add
if (EMPNukeCheck(self, start))
{
gi.sound (self, CHAN_AUTO, gi.soundindex("items/empnuke/emp_missfire.wav"), 1, ATTN_NORM, 0);
return;
}
// end Zaero
fire_tracker (self, start, dir, damage, speed, enemy);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
void monster_fire_heat (edict_t *self, vec3_t start, vec3_t dir, vec3_t offset, int damage, int kick, int flashtype)
{
// Zaero add
if (EMPNukeCheck(self, start))
{
gi.sound (self, CHAN_AUTO, gi.soundindex("items/empnuke/emp_missfire.wav"), 1, ATTN_NORM, 0);
return;
}
// end Zaero
fire_heat (self, start, dir, offset, damage, kick, true);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
// ROGUE
// SKWiD MOD
void monster_fire_plasma_rifle (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, qboolean spread)
{
// Zaero add
if (EMPNukeCheck(self, start))
{
gi.sound (self, CHAN_AUTO, gi.soundindex("items/empnuke/emp_missfire.wav"), 1, ATTN_NORM, 0);
return;
}
// end Zaero
fire_plasma_rifle (self, start, dir, damage, speed, spread);
gi.WriteByte (svc_muzzleflash2);
gi.WriteShort (self - g_edicts);
gi.WriteByte (flashtype);
gi.multicast (start, MULTICAST_PVS);
}
//
// Monster utility functions
//
void M_FliesOff (edict_t *self)
{
self->s.effects &= ~EF_FLIES;
self->s.sound = 0;
}
void M_FliesOn (edict_t *self)
{
if (self->waterlevel)
return;
self->s.effects |= EF_FLIES;
self->s.sound = gi.soundindex ("infantry/inflies1.wav");
self->think = M_FliesOff;
self->nextthink = level.time + 60;
}
void M_FlyCheck (edict_t *self)
{
// Knightmare- keep running lava check
self->postthink = deadmonster_think;
if (self->monsterinfo.flies > 1.0)
{
// should ALREADY have flies
self->think = M_FliesOff;
self->nextthink = level.time + 60;
return;
}
if (self->waterlevel)
return;
if (random() > self->monsterinfo.flies) // was 0.33
return;
if (world->effects & FX_WORLDSPAWN_CORPSEFADE)
return;
self->think = M_FliesOn;
self->nextthink = level.time + 5 + 10 * random();
}
void AttackFinished (edict_t *self, float time)
{
self->monsterinfo.attack_finished = level.time + time;
}
void M_CheckGround (edict_t *ent)
{
vec3_t point;
trace_t trace;
if (level.time < ent->gravity_debounce_time)
return;
if (ent->flags & (FL_SWIM|FL_FLY))
return;
#ifdef ROGUE_GRAVITY
if ((ent->velocity[2] * ent->gravityVector[2]) < -100) // PGM
#else
if (ent->velocity[2] > 100)
#endif
{
ent->groundentity = NULL;
return;
}
// if the hull point one-quarter unit down is solid the entity is on ground
point[0] = ent->s.origin[0];
point[1] = ent->s.origin[1];
#ifdef ROGUE_GRAVITY
point[2] = ent->s.origin[2] + (0.25 * ent->gravityVector[2]); //PGM
#else
point[2] = ent->s.origin[2] - 0.25;
#endif
trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_MONSTERSOLID);
// check steepness
#ifdef ROGUE_GRAVITY
//PGM
if ( ent->gravityVector[2] < 0) // normal gravity
{
if ( trace.plane.normal[2] < 0.7 && !trace.startsolid)
{
ent->groundentity = NULL;
return;
}
}
else // inverted gravity
{
if ( trace.plane.normal[2] > -0.7 && !trace.startsolid)
{
ent->groundentity = NULL;
return;
}
}
//PGM
#else
if ( trace.plane.normal[2] < 0.7 && !trace.startsolid)
{
ent->groundentity = NULL;
return;
}
#endif
// Lazarus: The following 2 lines were in the original code and commented out
// by id. However, the effect of this is that a player walking over
// a dead monster who is laying on a brush model will cause the
// dead monster to drop through the brush model. This change *may*
// have other consequences, though, so watch out for this.
// Knightmare- this wrecks aiming for many/most monster_turrets in a map!
// Leave this commented out, or disable it for turrets!
// ent->groundentity = trace.ent;
// ent->groundentity_linkcount = trace.ent->linkcount;
// if (!trace.startsolid && !trace.allsolid)
// VectorCopy (trace.endpos, ent->s.origin);
if (!trace.startsolid && !trace.allsolid)
{
VectorCopy (trace.endpos, ent->s.origin);
ent->groundentity = trace.ent;
ent->groundentity_linkcount = trace.ent->linkcount;
ent->velocity[2] = 0;
}
}
void M_CatagorizePosition (edict_t *ent)
{
vec3_t point;
int cont;
//
// get waterlevel
//
point[0] = ent->s.origin[0];
point[1] = ent->s.origin[1];
point[2] = ent->s.origin[2] + ent->mins[2] + 1;
cont = gi.pointcontents (point);
if (!(cont & MASK_WATER))
{
ent->waterlevel = 0;
ent->watertype = 0;
return;
}
ent->watertype = cont;
ent->waterlevel = 1;
point[2] += 26;
cont = gi.pointcontents (point);
if (!(cont & MASK_WATER))
return;
ent->waterlevel = 2;
point[2] += 22;
cont = gi.pointcontents (point);
if (cont & MASK_WATER)
ent->waterlevel = 3;
}
void M_WorldEffects (edict_t *ent)
{
int dmg;
if (ent->health > 0)
{
if (!(ent->flags & FL_SWIM))
{
if (ent->waterlevel < 3)
{
ent->air_finished = level.time + 12;
}
else if (ent->air_finished < level.time)
{ // drown!
if (ent->pain_debounce_time < level.time)
{
dmg = 2 + 2 * floor(level.time - ent->air_finished);
if (dmg > 15)
dmg = 15;
T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
ent->pain_debounce_time = level.time + 1;
}
}
}
else
{
if (ent->waterlevel > 0)
{
ent->air_finished = level.time + 9;
}
else if (ent->air_finished < level.time)
{ // suffocate!
if (ent->pain_debounce_time < level.time)
{
dmg = 2 + 2 * floor(level.time - ent->air_finished);
if (dmg > 15)
dmg = 15;
T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
ent->pain_debounce_time = level.time + 1;
}
}
}
}
// Knightmare added- all monsters are IR visible
ent->s.renderfx |= RF_IR_VISIBLE;
if (ent->waterlevel == 0)
{
if (ent->flags & FL_INWATER)
{
if (ent->watertype & CONTENTS_MUD)
gi.sound (ent, CHAN_BODY, gi.soundindex("mud/mud_out1.wav"), 1, ATTN_NORM, 0);
else
gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0);
ent->flags &= ~FL_INWATER;
}
return;
}
if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA))
{
if (ent->damage_debounce_time < level.time)
{
ent->damage_debounce_time = level.time + 0.2;
T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 10*ent->waterlevel, 0, 0, MOD_LAVA);
}
}
// No slime damage for dead monsters
if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME) && !(ent->svflags & SVF_DEADMONSTER))
{
if (ent->damage_debounce_time < level.time)
{
ent->damage_debounce_time = level.time + 1;
T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 4*ent->waterlevel, 0, 0, MOD_SLIME);
}
}
if ( !(ent->flags & FL_INWATER) )
{
if (!(ent->svflags & SVF_DEADMONSTER))
{
if ((ent->watertype & CONTENTS_LAVA) && (ent->health > 0))
{
if (random() <= 0.5)
gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0);
else
gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0);
}
else if (ent->watertype & CONTENTS_SLIME)
gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
else if (ent->watertype & CONTENTS_MUD)
gi.sound (ent, CHAN_BODY, gi.soundindex("mud/mud_in2.wav"), 1, ATTN_NORM, 0);
else if (ent->watertype & CONTENTS_WATER)
gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
}
ent->flags |= FL_INWATER;
ent->old_watertype = ent->watertype;
ent->damage_debounce_time = 0;
}
}
void M_droptofloor (edict_t *ent)
{
vec3_t end;
trace_t trace;
#ifdef ROGUE_GRAVITY
//PGM
if (ent->gravityVector[2] < 0)
{
ent->s.origin[2] += 1;
VectorCopy (ent->s.origin, end);
end[2] -= 256;
}
else
{
ent->s.origin[2] -= 1;
VectorCopy (ent->s.origin, end);
end[2] += 256;
}
//PGM
#else
ent->s.origin[2] += 1;
VectorCopy (ent->s.origin, end);
end[2] -= 256;
#endif
trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
if (trace.fraction == 1 || trace.allsolid)
return;
VectorCopy (trace.endpos, ent->s.origin);
gi.linkentity (ent);
M_CheckGround (ent);
M_CatagorizePosition (ent);
}
void M_SetEffects (edict_t *ent)
{
int remaining;
ent->s.effects &= ~(EF_COLOR_SHELL|EF_POWERSCREEN|EF_DOUBLE|EF_QUAD|EF_PENT);
ent->s.renderfx &= ~(RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE|RF_SHELL_DOUBLE);
if (ent->monsterinfo.aiflags & AI_RESURRECTING)
{
ent->s.effects |= EF_COLOR_SHELL;
ent->s.renderfx |= RF_SHELL_RED;
}
if (ent->health <= 0)
return;
if (ent->powerarmor_time > level.time)
{
if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SCREEN)
{
ent->s.effects |= EF_POWERSCREEN;
}
else if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SHIELD)
{
ent->s.effects |= EF_COLOR_SHELL;
ent->s.renderfx |= RF_SHELL_GREEN;
}
}
// PMM - new monster powerups
if (ent->monsterinfo.quad_framenum > level.framenum)
{
remaining = ent->monsterinfo.quad_framenum - level.framenum;
if (remaining > 30 || (remaining & 4) )
ent->s.effects |= EF_QUAD;
}
else
ent->s.effects &= ~EF_QUAD;
if (ent->monsterinfo.double_framenum > level.framenum)
{
remaining = ent->monsterinfo.double_framenum - level.framenum;
if (remaining > 30 || (remaining & 4) )
ent->s.effects |= EF_DOUBLE;
}
else
ent->s.effects &= ~EF_DOUBLE;
if (ent->monsterinfo.invincible_framenum > level.framenum)
{
remaining = ent->monsterinfo.invincible_framenum - level.framenum;
if (remaining > 30 || (remaining & 4) )
ent->s.effects |= EF_PENT;
}
else
ent->s.effects &= ~EF_PENT;
// PMM
// PMM - testing
// ent->s.effects |= EF_COLOR_SHELL;
// ent->s.renderfx |= RF_SHELL_HALF_DAM;
/*
if (fmod (level.time, 4.0) > 2.0)
{
gi.dprintf ("invulnerable ");
ent->s.renderfx |= RF_SHELL_RED;
}
else
ent->s.renderfx &= ~RF_SHELL_RED;
if (fmod (level.time, 8.0) > 4.0)
{
gi.dprintf ("shield ");
ent->s.renderfx |= RF_SHELL_GREEN;
}
else
ent->s.renderfx &= ~RF_SHELL_GREEN;
if (fmod (level.time, 16.0) > 8.0)
{
gi.dprintf ("quad ");
ent->s.renderfx |= RF_SHELL_BLUE;\
}
else
ent->s.renderfx &= ~RF_SHELL_BLUE;
if (fmod (level.time, 32.0) > 16.0)
{
gi.dprintf ("double ");
ent->s.renderfx |= RF_SHELL_DOUBLE;
}
else
ent->s.renderfx &= ~RF_SHELL_DOUBLE;
if (fmod (level.time, 64.0) > 32.0)
{
gi.dprintf ("half ");
ent->s.renderfx |= RF_SHELL_HALF_DAM;
}
else
ent->s.renderfx &= ~RF_SHELL_HALF_DAM;
gi.dprintf ("\n");
*/
}
char *G_CopyString (char *in);
void stuffcmd(edict_t *ent,char *command);
float *tv (float x, float y, float z);
char *vtos (vec3_t v);
float vectoyaw (vec3_t vec);
void vectoangles (vec3_t vec, vec3_t angles);
qboolean point_infront (edict_t *self, vec3_t point);
void AnglesNormalize(vec3_t vec);
float SnapToEights(float x);
//ROGUE
void M_MoveFrame (edict_t *self)
{
mmove_t *move;
int index;
// Lazarus: For live monsters weaker than gladiator who aren't already running from
// something, evade live grenades on the ground.
if ((self->health > 0) && (self->max_health < 400) && !(self->monsterinfo.aiflags & AI_CHASE_THING) && self->monsterinfo.run)
Grenade_Evade (self);
move = self->monsterinfo.currentmove;
self->nextthink = level.time + FRAMETIME;
if ((self->monsterinfo.nextframe) && (self->monsterinfo.nextframe >= move->firstframe) && (self->monsterinfo.nextframe <= move->lastframe))
{
self->s.frame = self->monsterinfo.nextframe;
self->monsterinfo.nextframe = 0;
}
else
{
if (self->s.frame == move->lastframe)
{
if (move->endfunc)
{
move->endfunc (self);
// regrab move, endfunc is very likely to change it
move = self->monsterinfo.currentmove;
// check for death
if (self->svflags & SVF_DEADMONSTER)
return;
}
}
if (self->s.frame < move->firstframe || self->s.frame > move->lastframe)
{
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
self->s.frame = move->firstframe;
}
else
{
if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
{
self->s.frame++;
if (self->s.frame > move->lastframe)
self->s.frame = move->firstframe;
}
}
}
index = self->s.frame - move->firstframe;
if (move->frame[index].aifunc)
if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
move->frame[index].aifunc (self, move->frame[index].dist * self->monsterinfo.scale);
else
move->frame[index].aifunc (self, 0);
if (move->frame[index].thinkfunc)
move->frame[index].thinkfunc (self);
}
void monster_think (edict_t *self)
{
M_MoveFrame (self);
if (self->linkcount != self->monsterinfo.linkcount)
{
self->monsterinfo.linkcount = self->linkcount;
M_CheckGround (self);
}
M_CatagorizePosition (self);
M_WorldEffects (self);
M_SetEffects (self);
// Zaero add
// decrease blindness
if (self->monsterinfo.flashTime > 0)
self->monsterinfo.flashTime--;
// end Zaero
}
// Knightmare- for dead monsters to check
// if they've fallen into lava, etc.
void deadmonster_think (edict_t *self)
{
M_CatagorizePosition (self);
M_WorldEffects (self);
M_SetEffects (self);
}
/*
================
monster_use
Using a monster makes it angry at the current activator
================
*/
void monster_use (edict_t *self, edict_t *other, edict_t *activator)
{
if (self->enemy)
return;
if (self->health <= 0)
return;
if (activator->flags & FL_NOTARGET)
return;
if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY))
return;
if (activator->flags & FL_DISGUISED) // PGM
return; // PGM
// if monster is "used" by player, turn off good guy stuff
if (activator->client)
{ // Knightmare- gekks and stalkers use different spawnflag
if (UseSpecialGoodGuyFlag(self)) {
self->spawnflags &= ~16;
}
else if (UseRegularGoodGuyFlag(self)) {
self->spawnflags &= ~SF_MONSTER_GOODGUY;
}
// Knightmare- don't include goodguy monsters turned bad in body count
if (self->monsterinfo.aiflags & AI_GOOD_GUY)
self->monsterinfo.monsterflags |= MFL_DO_NOT_COUNT;
self->monsterinfo.aiflags &= ~(AI_GOOD_GUY + AI_FOLLOW_LEADER);
if (self->dmgteam && !Q_stricmp(self->dmgteam,"player"))
self->dmgteam = NULL;
}
// delay reaction so if the monster is teleported, its sound is still heard
self->enemy = activator;
FoundTarget (self);
}
void monster_start_go (edict_t *self);
void monster_triggered_spawn (edict_t *self)
{
self->s.origin[2] += 1;
KillBox (self);
self->solid = SOLID_BBOX;
self->movetype = MOVETYPE_STEP;
self->svflags &= ~SVF_NOCLIENT;
self->air_finished = level.time + 12;
// Knightmare- teleport effect for Q1 monsters
if (self->flags & FL_Q1_MONSTER) {
#ifdef KMQUAKE2_ENGINE_MOD
self->s.event = EV_PLAYER_TELEPORT2;
#else
self->s.event = EV_PLAYER_TELEPORT;
Q1TeleportSounds(self);
#endif
}
// end Knightmare
// Zaero add
/* if ( IsZaeroMap() ) {
self->s.event = EV_PLAYER_TELEPORT;
MonsterPlayerKillBox (self);
}*/
// end Zaero
gi.linkentity (self);
monster_start_go (self);
if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET))
{
if (!(self->enemy->flags & FL_DISGUISED)) // PGM
FoundTarget (self);
else // PMM - just in case, make sure to clear the enemy so FindTarget doesn't get confused
self->enemy = NULL;
}
else
{
self->enemy = NULL;
}
}
void monster_triggered_spawn_use (edict_t *self, edict_t *other, edict_t *activator)
{
// we have a one frame delay here so we don't telefrag the guy who activated us
self->think = monster_triggered_spawn;
self->nextthink = level.time + FRAMETIME;
// Knightmare- good guy monsters shouldn't have an enemy from this
if (activator->client && !(self->monsterinfo.aiflags & AI_GOOD_GUY))
self->enemy = activator;
// Lazarus: Add 'em up
// if (!(self->monsterinfo.aiflags & AI_GOOD_GUY))
// level.total_monsters++;
self->use = monster_use;
}
void monster_triggered_start (edict_t *self)
{
self->solid = SOLID_NOT;
self->movetype = MOVETYPE_NONE;
self->svflags |= SVF_NOCLIENT;
self->nextthink = 0;
self->use = monster_triggered_spawn_use;
}
/*
================
monster_death_use
When a monster dies, it fires all of its targets with the current
enemy as activator.
================
*/
void monster_death_use (edict_t *self)
{
edict_t *player;
int i;
if (!strcmp(self->classname, "monster_turret"))
gi.dprintf("monster_turret firing targets\n");
self->flags &= ~(FL_FLY|FL_SWIM);
self->monsterinfo.aiflags &= AI_GOOD_GUY;
//Knightmare- remove powerscreen and other effects from dead monsters
self->s.effects &= ~(EF_COLOR_SHELL|EF_POWERSCREEN|EF_DOUBLE|EF_QUAD|EF_PENT);
self->s.renderfx &= ~(RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE|RF_SHELL_DOUBLE);
// Lazarus: If actor/monster is being used as a camera by a player,
// turn camera off for that player
for(i=0,player=g_edicts+1; i<maxclients->value; i++, player++)
{
if (player->client && player->client->spycam == self)
camera_off(player);
}
if (self->item)
{
Drop_Item (self, self->item);
self->item = NULL;
}
if (self->deathtarget)
self->target = self->deathtarget;
if (!self->target)
return;
G_UseTargets (self, self->enemy);
}
//============================================================================
qboolean monster_start (edict_t *self)
{
if (deathmatch->value)
{
G_FreeEdict (self);
return false;
}
// Lazarus: Already gibbed monsters passed across levels via trigger_transition:
if ( (self->max_health > 0) && (self->health <= self->gib_health) && !(self->spawnflags & SF_MONSTER_NOGIB) )
{
void SP_gibhead(edict_t *);
SP_gibhead(self);
return true;
}
// Lazarus: Good guys
// Knightmare- gekks and stalkers use different spawnflag
if ( (UseRegularGoodGuyFlag(self) && (self->spawnflags & SF_MONSTER_GOODGUY))
|| (UseSpecialGoodGuyFlag(self) && (self->spawnflags & 16)) )
{
self->monsterinfo.aiflags |= AI_GOOD_GUY;
if (!self->dmgteam)
{
self->dmgteam = gi.TagMalloc(8*sizeof(char), TAG_LEVEL);
strcpy(self->dmgteam,"player");
}
}
// Lazarus: Max range for sight/attack
if (self->distance)
st.distance = self->distance;
if (st.distance)
self->monsterinfo.max_range = max(500,st.distance);
else
self->monsterinfo.max_range = 1280; // Q2 default is 1000. We're mean.
// Lazarus: We keep SIGHT to mean what old AMBUSH does, and AMBUSH additionally
// now means don't play idle sounds
/* if ((self->spawnflags & MONSTER_SIGHT) && !(self->monsterinfo.aiflags & AI_GOOD_GUY))
{
self->spawnflags &= ~MONSTER_SIGHT;
self->spawnflags |= MONSTER_AMBUSH;
} */
if ( (self->spawnflags & SF_MONSTER_AMBUSH) && !(self->monsterinfo.aiflags & AI_GOOD_GUY) )
self->spawnflags |= SF_MONSTER_SIGHT;
// Zaero- spawnflag 16 = do not count
// if ( !(self->monsterinfo.aiflags & AI_GOOD_GUY) && !(self->monsterinfo.aiflags & AI_DO_NOT_COUNT) )
// if ( !(self->monsterinfo.aiflags & AI_GOOD_GUY) && !(self->monsterinfo.monsterflags & MFL_DO_NOT_COUNT)/* && !(self->spawnflags & SF_MONSTER_TRIGGER_SPAWN*/ )
// Zaero- spawnflag 16 = do not count
if ( !(self->monsterinfo.aiflags & AI_GOOD_GUY) && !(self->monsterinfo.monsterflags & MFL_DO_NOT_COUNT) && !(IsZaeroMap() && (self->spawnflags & 16)) )
level.total_monsters++;
self->nextthink = level.time + FRAMETIME;
self->svflags |= SVF_MONSTER;
self->s.renderfx |= RF_FRAMELERP;
self->takedamage = DAMAGE_AIM;
self->air_finished = level.time + 12;
self->use = monster_use;
// Lazarus - don't reset max_health unnecessarily
if (!self->max_health)
self->max_health = self->health;
/* if (self->health < (self->max_health / 2))
self->s.skinnum |= 1;
else
self->s.skinnum &= ~1;*/
self->clipmask = MASK_MONSTERSOLID;
if (self->s.skinnum < 1)
self->s.skinnum = 0;
self->deadflag = DEAD_NO;
self->svflags &= ~SVF_DEADMONSTER;
if (self->monsterinfo.flies > 1.0)
{
self->s.effects |= EF_FLIES;
self->s.sound = gi.soundindex ("infantry/inflies1.wav");
}
// Lazarus
if (self->health <= 0)
{
self->svflags |= SVF_DEADMONSTER;
self->movetype = MOVETYPE_TOSS;
self->takedamage = DAMAGE_YES;
self->monsterinfo.pausetime = 100000000;
self->monsterinfo.aiflags &= ~AI_RESPAWN_FINDPLAYER;
if (self->max_health > 0)
{
// This must be a dead monster who changed levels
// via trigger_transition
self->nextthink = 0;
self->deadflag = DEAD_DEAD;
}
if (self->s.effects & EF_FLIES && self->monsterinfo.flies <= 1.0)
{
self->think = M_FliesOff;
self->nextthink = level.time + 1 + random()*60;
}
return true;
}
else
{
// make sure red shell is turned off in case medic got confused:
self->monsterinfo.aiflags &= ~AI_RESURRECTING;
self->svflags &= ~SVF_DEADMONSTER;
self->takedamage = DAMAGE_AIM;
}
if (!self->monsterinfo.checkattack)
self->monsterinfo.checkattack = M_CheckAttack;
VectorCopy (self->s.origin, self->s.old_origin);
if (st.item)
{
self->item = FindItemByClassname (st.item);
if (!self->item)
gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
}
// randomize what frame they start on
if (self->monsterinfo.currentmove)
self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1));
// PMM - get this so I don't have to do it in all of the monsters
self->monsterinfo.base_height = self->maxs[2];
// PMM - clear these
self->monsterinfo.quad_framenum = 0;
self->monsterinfo.double_framenum = 0;
self->monsterinfo.invincible_framenum = 0;
return true;
}
void monster_start_go (edict_t *self)
{
vec3_t v;
if (self->health <= 0)
{
if (self->max_health <= 0)
InitiallyDead(self);
return;
}
// Lazarus: move_origin for func_monitor
if (!VectorLength(self->move_origin))
VectorSet(self->move_origin,0,0,self->viewheight);
// check for target to combat_point and change to combattarget
if (self->target)
{
qboolean notcombat;
qboolean fixup;
edict_t *target;
target = NULL;
notcombat = false;
fixup = false;
while ((target = G_Find (target, FOFS(targetname), self->target)) != NULL)
{
if (strcmp(target->classname, "point_combat") == 0)
{
self->combattarget = self->target;
fixup = true;
}
else
{
notcombat = true;
}
}
if (notcombat && self->combattarget)
gi.dprintf("%s at %s has target with mixed types\n", self->classname, vtos(self->s.origin));
if (fixup)
self->target = NULL;
}
// validate combattarget
if (self->combattarget)
{
edict_t *target;
target = NULL;
while ((target = G_Find (target, FOFS(targetname), self->combattarget)) != NULL)
{
if (strcmp(target->classname, "point_combat") != 0)
{
gi.dprintf("%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n",
self->classname, (int)self->s.origin[0], (int)self->s.origin[1], (int)self->s.origin[2],
self->combattarget, target->classname, (int)target->s.origin[0], (int)target->s.origin[1],
(int)target->s.origin[2]);
}
}
}
if (self->target)
{
self->goalentity = self->movetarget = G_PickTarget(self->target);
if (!self->movetarget)
{
gi.dprintf ("%s can't find target %s at %s\n", self->classname, self->target, vtos(self->s.origin));
self->target = NULL;
self->monsterinfo.pausetime = 100000000;
self->monsterinfo.stand (self);
}
else if (strcmp (self->movetarget->classname, "path_corner") == 0)
{
// Lazarus: Don't wipe out target for trigger spawned monsters
// that aren't triggered yet
if ( !(self->spawnflags & MONSTER_TRIGGER_SPAWN) )
{
VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v);
self->monsterinfo.walk (self);
self->target = NULL;
}
}
else
{
self->goalentity = self->movetarget = NULL;
self->monsterinfo.pausetime = 100000000;
self->monsterinfo.stand (self);
}
}
else
{
self->monsterinfo.pausetime = 100000000;
}
self->think = monster_think;
self->nextthink = level.time + FRAMETIME;
}
void walkmonster_start_go (edict_t *self)
{
if (!(self->spawnflags & 2) && level.time < 1)
{
M_droptofloor (self);
if (self->groundentity)
if (!M_walkmove (self, 0, 0))
gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
}
if (!self->yaw_speed)
self->yaw_speed = 20;
// PMM - stalkers are too short for this
if (!(strcmp(self->classname, "monster_stalker")))
self->viewheight = 15;
else
self->viewheight = 25;
monster_start_go (self);
if (self->spawnflags & MONSTER_TRIGGER_SPAWN)
monster_triggered_start (self);
}
void walkmonster_start (edict_t *self)
{
self->think = walkmonster_start_go;
monster_start (self);
}
void flymonster_start_go (edict_t *self)
{
if (!M_walkmove (self, 0, 0))
gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
if (!self->yaw_speed)
self->yaw_speed = 10;
self->viewheight = 25;
monster_start_go (self);
if (self->spawnflags & MONSTER_TRIGGER_SPAWN)
monster_triggered_start (self);
}
void flymonster_start (edict_t *self)
{
self->flags |= FL_FLY;
self->think = flymonster_start_go;
monster_start (self);
}
void swimmonster_start_go (edict_t *self)
{
if (!self->yaw_speed)
self->yaw_speed = 10;
self->viewheight = 10;
monster_start_go (self);
if (self->spawnflags & MONSTER_TRIGGER_SPAWN)
monster_triggered_start (self);
}
void swimmonster_start (edict_t *self)
{
self->flags |= FL_SWIM;
self->think = swimmonster_start_go;
monster_start (self);
}
//ROGUE
void stationarymonster_start_go (edict_t *self);
void stationarymonster_triggered_spawn (edict_t *self)
{
KillBox (self);
self->solid = SOLID_BBOX;
self->movetype = MOVETYPE_NONE;
self->svflags &= ~SVF_NOCLIENT;
self->air_finished = level.time + 12;
gi.linkentity (self);
// FIXME - why doesn't this happen with real monsters?
self->spawnflags &= ~2;
stationarymonster_start_go (self);
if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET))
{
if (!(self->enemy->flags & FL_DISGUISED)) // PGM
FoundTarget (self);
else // PMM - just in case, make sure to clear the enemy so FindTarget doesn't get confused
self->enemy = NULL;
}
else
{
self->enemy = NULL;
}
}
void stationarymonster_triggered_spawn_use (edict_t *self, edict_t *other, edict_t *activator)
{
// we have a one frame delay here so we don't telefrag the guy who activated us
self->think = stationarymonster_triggered_spawn;
self->nextthink = level.time + FRAMETIME;
// Knightmare- good guy monsters shouldn't have an enemy from this
if (activator->client && !(self->monsterinfo.aiflags & AI_GOOD_GUY))
self->enemy = activator;
// Lazarus: Add 'em up
// if (!(self->monsterinfo.aiflags & AI_GOOD_GUY))
// level.total_monsters++;
self->use = monster_use;
}
void stationarymonster_triggered_start (edict_t *self)
{
self->solid = SOLID_NOT;
self->movetype = MOVETYPE_NONE;
self->svflags |= SVF_NOCLIENT;
self->nextthink = 0;
self->use = stationarymonster_triggered_spawn_use;
}
void stationarymonster_start_go (edict_t *self)
{
// PGM - only turrets use this, so remove the error message. They're supposed to be in solid.
// if (!M_walkmove (self, 0, 0))
// gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
if (!self->yaw_speed)
self->yaw_speed = 20;
// self->viewheight = 25;
monster_start_go (self);
if (self->spawnflags & MONSTER_TRIGGER_SPAWN)
stationarymonster_triggered_start (self);
}
void stationarymonster_start (edict_t *self)
{
self->think = stationarymonster_start_go;
monster_start (self);
}
void monster_done_dodge (edict_t *self)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("%s done dodging\n", self->classname);
self->monsterinfo.aiflags &= ~AI_DODGING;
}
// Following functions unique to Lazarus
void InitiallyDead (edict_t *self)
{
int damage;
if (self->max_health > 0)
return;
// gi.dprintf("InitiallyDead on %s at %s\n",self->classname,vtos(self->s.origin));
// initially dead bad guys shouldn't count against totals
if ((self->max_health <= 0) && !(self->monsterinfo.aiflags & AI_GOOD_GUY))
{
level.total_monsters--;
if (self->deadflag != DEAD_DEAD)
level.killed_monsters--;
}
if (self->deadflag != DEAD_DEAD)
{
damage = 1 - self->health;
self->health = 1;
T_Damage (self, world, world, vec3_origin, self->s.origin, vec3_origin, damage, 0, DAMAGE_NO_ARMOR, 0);
if (self->svflags & SVF_MONSTER)
{
self->svflags |= SVF_DEADMONSTER;
self->think = monster_think;
self->nextthink = level.time + FRAMETIME;
}
}
gi.linkentity(self);
}
#define MAX_SKINS 24 //max is 32, but we only need 24
#define MAX_SKINNAME 64
#include "pak.h"
int PatchMonsterModel (char *modelname)
{
cvar_t *gamedir;
int j;
int numskins; // number of skin entries
char skins[MAX_SKINS][MAX_SKINNAME]; // skin entries
char infilename[MAX_OSPATH];
char outfilename[MAX_OSPATH];
char tempname[MAX_OSPATH];
char *p;
FILE *infile;
FILE *outfile;
dmdl_t model; // model header
byte *data; // model data
int datasize; // model data size (bytes)
int newoffset; // model data offset (after skins)
qboolean is_tank = false;
qboolean is_soldier = false;
// Knightmare added
qboolean is_brain = false;
qboolean is_gekk = false;
qboolean is_fixbot = false;
qboolean is_chick = false;
qboolean is_soldierh = false;
qboolean is_carrier = false;
qboolean is_hover = false;
qboolean is_medic = false;
qboolean is_turret = false;
qboolean is_zboss_mech = false;
qboolean is_zboss_pilot = false;
qboolean gamedirpakfile = false;
// get game (moddir) name
gamedir = gi.cvar("game", "", 0);
if (!*gamedir->string)
return 0; // we're in baseq2
// Com_sprintf (outfilename, sizeof(outfilename), "%s/%s", gamedir->string, modelname);
Com_sprintf (tempname, sizeof(tempname), modelname);
SavegameDirRelativePath (tempname, outfilename, sizeof(outfilename));
if (outfile = fopen (outfilename, "rb"))
{
// output file already exists, move along
fclose (outfile);
// gi.dprintf ("PatchMonsterModel: Could not save %s, file already exists\n", outfilename);
return 0;
}
numskins = 8;
// special cases
if (!strcmp(modelname,"models/monsters/tank/tris.md2"))
{
is_tank = true;
numskins = 16;
}
else if (!strcmp(modelname,"models/monsters/soldier/tris.md2"))
{
is_soldier = true;
numskins = 32; // was 24
}
// Knightmare added
#ifdef CITADELMOD_FEATURES
else if (!strcmp(modelname,"models/monsters/brain/tris.md2"))
{
is_brain = true;
numskins = 16;
}
#endif
else if (!strcmp(modelname,"models/monsters/gekk/tris.md2"))
{
is_gekk = true;
numskins = 12;
}
else if (!strcmp(modelname,"models/monsters/fixbot/tris.md2"))
{
is_fixbot = true;
numskins = 4;
}
else if (!strcmp(modelname,"models/monsters/bitch/tris.md2")
|| !strcmp(modelname,"models/monsters/bitch2/tris.md2"))
{
is_chick = true;
numskins = 16;
}
else if (!strcmp(modelname,"models/monsters/soldierh/tris.md2"))
{
is_soldierh = true;
numskins = 24;
}
else if (!strcmp(modelname,"models/monsters/carrier/tris.md2"))
{
is_carrier = true;
numskins = 8;
}
else if (!strcmp(modelname,"models/monsters/hover/tris.md2"))
{
is_hover = true;
numskins = 16;
}
else if (!strcmp(modelname,"models/monsters/medic/tris.md2"))
{
is_medic = true;
numskins = 16;
}
else if (!strcmp(modelname,"models/monsters/turret/tris.md2"))
{
is_turret = true;
numskins = 12;
}
else if (!strcmp(modelname,"models/monsters/bossz/mech/tris.md2"))
{
is_zboss_mech = true;
numskins = 12;
}
else if (!strcmp(modelname,"models/monsters/bossz/pilot/tris.md2"))
{
is_zboss_pilot = true;
numskins = 12;
}
// end Knightmare
for (j=0; j<numskins; j++)
{
memset (skins[j], 0, MAX_SKINNAME);
Com_strcpy( skins[j], sizeof(skins[j]), modelname );
p = strstr( skins[j], "tris.md2" );
if (!p)
{
fclose (outfile);
gi.dprintf( "Error patching %s\n",modelname);
return 0;
}
*p = 0;
if (is_soldier)
{
switch (j)
{
case 0:
Com_strcat (skins[j], sizeof(skins[j]), "skin_lt.pcx"); break;
case 1:
Com_strcat (skins[j], sizeof(skins[j]), "skin_ltp.pcx"); break;
case 2:
Com_strcat (skins[j], sizeof(skins[j]), "skin.pcx"); break;
case 3:
Com_strcat (skins[j], sizeof(skins[j]), "pain.pcx"); break;
case 4:
Com_strcat (skins[j], sizeof(skins[j]), "skin_ss.pcx"); break;
case 5:
Com_strcat (skins[j], sizeof(skins[j]), "skin_ssp.pcx"); break;
case 6:
Com_strcat (skins[j], sizeof(skins[j]), "skin_pl.pcx"); break;
case 7:
Com_strcat (skins[j], sizeof(skins[j]), "skin_plp.pcx"); break;
case 8:
Com_strcat (skins[j], sizeof(skins[j]), "custom1_lt.pcx"); break;
case 9:
Com_strcat (skins[j], sizeof(skins[j]), "custompain1_lt.pcx"); break;
case 10:
Com_strcat (skins[j], sizeof(skins[j]), "custom1.pcx"); break;
case 11:
Com_strcat (skins[j], sizeof(skins[j]), "custompain1.pcx"); break;
case 12:
Com_strcat (skins[j], sizeof(skins[j]), "custom1_ss.pcx"); break;
case 13:
Com_strcat (skins[j], sizeof(skins[j]), "custompain1_ss.pcx"); break;
case 14:
Com_strcat (skins[j], sizeof(skins[j]), "custom1_pl.pcx"); break;
case 15:
Com_strcat (skins[j], sizeof(skins[j]), "custompain1_pl.pcx"); break;
case 16:
Com_strcat (skins[j], sizeof(skins[j]), "custom2_lt.pcx"); break;
case 17:
Com_strcat (skins[j], sizeof(skins[j]), "custompain2_lt.pcx"); break;
case 18:
Com_strcat (skins[j], sizeof(skins[j]), "custom2.pcx"); break;
case 19:
Com_strcat (skins[j], sizeof(skins[j]), "custompain2.pcx"); break;
case 20:
Com_strcat (skins[j], sizeof(skins[j]), "custom2_ss.pcx"); break;
case 21:
Com_strcat (skins[j], sizeof(skins[j]), "custompain2_ss.pcx"); break;
case 22:
Com_strcat (skins[j], sizeof(skins[j]), "custom2_pl.pcx"); break;
case 23:
Com_strcat (skins[j], sizeof(skins[j]), "custompain2_pl.pcx"); break;
case 24:
Com_strcat (skins[j], sizeof(skins[j]), "custom3_lt.pcx"); break;
case 25:
Com_strcat (skins[j], sizeof(skins[j]), "custompain3_lt.pcx"); break;
case 26:
Com_strcat (skins[j], sizeof(skins[j]), "custom3.pcx"); break;
case 27:
Com_strcat (skins[j], sizeof(skins[j]), "custompain3.pcx"); break;
case 28:
Com_strcat (skins[j], sizeof(skins[j]), "custom3_ss.pcx"); break;
case 29:
Com_strcat (skins[j], sizeof(skins[j]), "custompain3_ss.pcx"); break;
case 30:
Com_strcat (skins[j], sizeof(skins[j]), "custom3_pl.pcx"); break;
case 31:
Com_strcat (skins[j], sizeof(skins[j]), "custompain3_pl.pcx"); break;
}
}
else if (is_tank)
{
switch (j)
{
case 0:
Com_strcat (skins[j], sizeof(skins[j]), "skin.pcx"); break;
case 1:
Com_strcat (skins[j], sizeof(skins[j]), "pain.pcx"); break;
case 2:
Com_strcat (skins[j], sizeof(skins[j]), "../ctank/skin.pcx"); break;
case 3:
Com_strcat (skins[j], sizeof(skins[j]), "../ctank/pain.pcx"); break;
case 4:
Com_strcat (skins[j], sizeof(skins[j]), "custom1.pcx"); break;
case 5:
Com_strcat (skins[j], sizeof(skins[j]), "custompain1.pcx"); break;
case 6:
Com_strcat (skins[j], sizeof(skins[j]), "../ctank/custom1.pcx"); break;
case 7:
Com_strcat (skins[j], sizeof(skins[j]), "../ctank/custompain1.pcx"); break;
case 8:
Com_strcat (skins[j], sizeof(skins[j]), "custom2.pcx"); break;
case 9:
Com_strcat (skins[j], sizeof(skins[j]), "custompain2.pcx"); break;
case 10:
Com_strcat (skins[j], sizeof(skins[j]), "../ctank/custom2.pcx"); break;
case 11:
Com_strcat (skins[j], sizeof(skins[j]), "../ctank/custompain2.pcx"); break;
case 12:
Com_strcat (skins[j], sizeof(skins[j]), "custom3.pcx"); break;
case 13:
Com_strcat (skins[j], sizeof(skins[j]), "custompain3.pcx"); break;
case 14:
Com_strcat (skins[j], sizeof(skins[j]), "../ctank/custom3.pcx"); break;
case 15:
Com_strcat (skins[j], sizeof(skins[j]), "../ctank/custompain3.pcx"); break;
}
}
// Knightmare added
#ifdef CITADELMOD_FEATURES
else if (is_brain)
{
switch (j)
{
case 0:
Com_strcat (skins[j], sizeof(skins[j]), "skin.pcx"); break;
case 1:
Com_strcat (skins[j], sizeof(skins[j]), "pain.pcx"); break;
case 2:
Com_strcat (skins[j], sizeof(skins[j]), "beta.pcx"); break;
case 3:
Com_strcat (skins[j], sizeof(skins[j]), "betapain.pcx"); break;
case 4:
Com_strcat (skins[j], sizeof(skins[j]), "custom1.pcx"); break;
case 5:
Com_strcat (skins[j], sizeof(skins[j]), "custompain1.pcx"); break;
case 6:
Com_strcat (skins[j], sizeof(skins[j]), "custombeta1.pcx"); break;
case 7:
Com_strcat (skins[j], sizeof(skins[j]), "custombeta_p1.pcx"); break;
case 8:
Com_strcat (skins[j], sizeof(skins[j]), "custom2.pcx"); break;
case 9:
Com_strcat (skins[j], sizeof(skins[j]), "custompain2.pcx"); break;
case 10:
Com_strcat (skins[j], sizeof(skins[j]), "custombeta2.pcx"); break;
case 11:
Com_strcat (skins[j], sizeof(skins[j]), "custombeta_p2.pcx"); break;
case 12:
Com_strcat (skins[j], sizeof(skins[j]), "custom3.pcx"); break;
case 13:
Com_strcat (skins[j], sizeof(skins[j]), "custompain3.pcx"); break;
case 14:
Com_strcat (skins[j], sizeof(skins[j]), "custombeta3.pcx"); break;
case 15:
Com_strcat (skins[j], sizeof(skins[j]), "custombeta_p3.pcx"); break;
}
}
#endif
else if (is_gekk)
{
switch (j)
{
case 0:
Com_strcat (skins[j], sizeof(skins[j]), "gekk.pcx"); break;
case 1:
Com_strcat (skins[j], sizeof(skins[j]), "gekpain1.pcx"); break;
case 2:
Com_strcat (skins[j], sizeof(skins[j]), "gekpain2.pcx"); break;
case 3:
Com_strcat (skins[j], sizeof(skins[j]), "custom1.pcx"); break;
case 4:
Com_strcat (skins[j], sizeof(skins[j]), "custom1pain1.pcx"); break;
case 5:
Com_strcat (skins[j], sizeof(skins[j]), "custom1pain2.pcx"); break;
case 6:
Com_strcat (skins[j], sizeof(skins[j]), "custom2.pcx"); break;
case 7:
Com_strcat (skins[j], sizeof(skins[j]), "custom2pain1.pcx"); break;
case 8:
Com_strcat (skins[j], sizeof(skins[j]), "custom2pain2.pcx"); break;
case 9:
Com_strcat (skins[j], sizeof(skins[j]), "custom3.pcx"); break;
case 10:
Com_strcat (skins[j], sizeof(skins[j]), "custom3pain1.pcx"); break;
case 11:
Com_strcat (skins[j], sizeof(skins[j]), "custom3pain2.pcx"); break;
}
}
else if (is_fixbot)
{
switch (j)
{
case 0:
Com_strcat (skins[j], sizeof(skins[j]), "droid.pcx"); break;
case 1:
Com_strcat (skins[j], sizeof(skins[j]), "custom1.pcx"); break;
case 2:
Com_strcat (skins[j], sizeof(skins[j]), "custom2.pcx"); break;
case 3:
Com_strcat (skins[j], sizeof(skins[j]), "custom3.pcx"); break;
}
}
else if (is_chick)
{
switch (j)
{
case 0:
Com_strcat (skins[j], sizeof(skins[j]), "skin.pcx"); break;
case 1:
Com_strcat (skins[j], sizeof(skins[j]), "pain.pcx"); break;
case 2:
Com_strcpy (skins[j], sizeof(skins[j]), "models/monsters/bitch/bi_sk3.pcx"); break;
case 3:
Com_strcpy (skins[j], sizeof(skins[j]), "models/monsters/bitch/bi_pain.pcx"); break;
case 4:
Com_strcat (skins[j], sizeof(skins[j]), "custom1.pcx"); break;
case 5:
Com_strcat (skins[j], sizeof(skins[j]), "custompain1.pcx"); break;
case 6:
Com_strcat (skins[j], sizeof(skins[j]), "custombeta1.pcx"); break;
case 7:
Com_strcat (skins[j], sizeof(skins[j]), "custombeta_p1.pcx"); break;
case 8:
Com_strcat (skins[j], sizeof(skins[j]), "custom2.pcx"); break;
case 9:
Com_strcat (skins[j], sizeof(skins[j]), "custompain2.pcx"); break;
case 10:
Com_strcat (skins[j], sizeof(skins[j]), "custombeta2.pcx"); break;
case 11:
Com_strcat (skins[j], sizeof(skins[j]), "custombeta_p2.pcx"); break;
case 12:
Com_strcat (skins[j], sizeof(skins[j]), "custom3.pcx"); break;
case 13:
Com_strcat (skins[j], sizeof(skins[j]), "custompain3.pcx"); break;
case 14:
Com_strcat (skins[j], sizeof(skins[j]), "custombeta3.pcx"); break;
case 15:
Com_strcat (skins[j], sizeof(skins[j]), "custombeta_p3.pcx"); break;
}
}
else if (is_soldierh)
{
switch (j)
{
case 0:
Com_strcat (skins[j], sizeof(skins[j]), "sold01.pcx"); break;
case 1:
Com_strcat (skins[j], sizeof(skins[j]), "sold01_p.pcx"); break;
case 2:
Com_strcat (skins[j], sizeof(skins[j]), "sold02.pcx"); break;
case 3:
Com_strcat (skins[j], sizeof(skins[j]), "sold02_p.pcx"); break;
case 4:
Com_strcat (skins[j], sizeof(skins[j]), "sold03.pcx"); break;
case 5:
Com_strcat (skins[j], sizeof(skins[j]), "sold03_p.pcx"); break;
case 6:
Com_strcat (skins[j], sizeof(skins[j]), "custom1sold1.pcx"); break;
case 7:
Com_strcat (skins[j], sizeof(skins[j]), "custom1sold1_p.pcx"); break;
case 8:
Com_strcat (skins[j], sizeof(skins[j]), "custom1sold2.pcx"); break;
case 9:
Com_strcat (skins[j], sizeof(skins[j]), "custom1sold2_p.pcx"); break;
case 10:
Com_strcat (skins[j], sizeof(skins[j]), "custom1sold3.pcx"); break;
case 11:
Com_strcat (skins[j], sizeof(skins[j]), "custom1sold3_p.pcx"); break;
case 12:
Com_strcat (skins[j], sizeof(skins[j]), "custom2sold1.pcx"); break;
case 13:
Com_strcat (skins[j], sizeof(skins[j]), "custom2sold1_p.pcx"); break;
case 14:
Com_strcat (skins[j], sizeof(skins[j]), "custom2sold2.pcx"); break;
case 15:
Com_strcat (skins[j], sizeof(skins[j]), "custom2sold2_p.pcx"); break;
case 16:
Com_strcat (skins[j], sizeof(skins[j]), "custom2sold3.pcx"); break;
case 17:
Com_strcat (skins[j], sizeof(skins[j]), "custom2sold3_p.pcx"); break;
case 18:
Com_strcat (skins[j], sizeof(skins[j]), "custom3sold1.pcx"); break;
case 19:
Com_strcat (skins[j], sizeof(skins[j]), "custom3sold1_p.pcx"); break;
case 20:
Com_strcat (skins[j], sizeof(skins[j]), "custom3sold2.pcx"); break;
case 21:
Com_strcat (skins[j], sizeof(skins[j]), "custom3sold2_p.pcx"); break;
case 22:
Com_strcat (skins[j], sizeof(skins[j]), "custom3sold3.pcx"); break;
case 23:
Com_strcat (skins[j], sizeof(skins[j]), "custom3sold3_p.pcx"); break;
}
}
else if (is_carrier)
{
switch (j)
{
case 0:
Com_strcat (skins[j], sizeof(skins[j]), "skin.pcx"); break;
case 1:
Com_strcat (skins[j], sizeof(skins[j]), "pain2.pcx"); break;
case 2:
Com_strcat (skins[j], sizeof(skins[j]), "custom1.pcx"); break;
case 3:
Com_strcat (skins[j], sizeof(skins[j]), "custompain1.pcx"); break;
case 4:
Com_strcat (skins[j], sizeof(skins[j]), "custom2.pcx"); break;
case 5:
Com_strcat (skins[j], sizeof(skins[j]), "custompain2.pcx"); break;
case 6:
Com_strcat (skins[j], sizeof(skins[j]), "custom3.pcx"); break;
case 7:
Com_strcat (skins[j], sizeof(skins[j]), "custompain3.pcx"); break;
}
}
else if (is_hover || is_medic)
{
switch (j)
{
case 0:
Com_strcat (skins[j], sizeof(skins[j]), "skin.pcx"); break;
case 1:
Com_strcat (skins[j], sizeof(skins[j]), "pain.pcx"); break;
case 2:
Com_strcat (skins[j], sizeof(skins[j]), "rskin.pcx"); break;
case 3:
Com_strcat (skins[j], sizeof(skins[j]), "rpain.pcx"); break;
case 4:
Com_strcat (skins[j], sizeof(skins[j]), "custom1.pcx"); break;
case 5:
Com_strcat (skins[j], sizeof(skins[j]), "custompain1.pcx"); break;
case 6:
Com_strcat (skins[j], sizeof(skins[j]), "custombeta1.pcx"); break;
case 7:
Com_strcat (skins[j], sizeof(skins[j]), "custombeta_p1.pcx"); break;
case 8:
Com_strcat (skins[j], sizeof(skins[j]), "custom2.pcx"); break;
case 9:
Com_strcat (skins[j], sizeof(skins[j]), "custompain2.pcx"); break;
case 10:
Com_strcat (skins[j], sizeof(skins[j]), "custombeta2.pcx"); break;
case 11:
Com_strcat (skins[j], sizeof(skins[j]), "custombeta_p2.pcx"); break;
case 12:
Com_strcat (skins[j], sizeof(skins[j]), "custom3.pcx"); break;
case 13:
Com_strcat (skins[j], sizeof(skins[j]), "custompain3.pcx"); break;
case 14:
Com_strcat (skins[j], sizeof(skins[j]), "custombeta2.pcx"); break;
case 15:
Com_strcat (skins[j], sizeof(skins[j]), "custombeta_p2.pcx"); break;
}
}
else if (is_turret)
{
switch (j)
{
case 0:
Com_strcat (skins[j], sizeof(skins[j]), "skin.pcx"); break;
case 1:
Com_strcat (skins[j], sizeof(skins[j]), "skin1.pcx"); break;
case 2:
Com_strcat (skins[j], sizeof(skins[j]), "skin2.pcx"); break;
case 3:
Com_strcat (skins[j], sizeof(skins[j]), "custom1_1.pcx"); break;
case 4:
Com_strcat (skins[j], sizeof(skins[j]), "custom1_2.pcx"); break;
case 5:
Com_strcat (skins[j], sizeof(skins[j]), "custom1_3.pcx"); break;
case 6:
Com_strcat (skins[j], sizeof(skins[j]), "custom2_1.pcx"); break;
case 7:
Com_strcat (skins[j], sizeof(skins[j]), "custom2_2.pcx"); break;
case 8:
Com_strcat (skins[j], sizeof(skins[j]), "custom2_3.pcx"); break;
case 9:
Com_strcat (skins[j], sizeof(skins[j]), "custom3_1.pcx"); break;
case 10:
Com_strcat (skins[j], sizeof(skins[j]), "custom3_2.pcx"); break;
case 11:
Com_strcat (skins[j], sizeof(skins[j]), "custom3_3.pcx"); break;
}
}
else if (is_zboss_mech || is_zboss_pilot)
{
switch (j)
{
case 0:
Com_strcat (skins[j], sizeof(skins[j]), "skin.pcx"); break;
case 1:
Com_strcat (skins[j], sizeof(skins[j]), "pain1.pcx"); break;
case 2:
Com_strcat (skins[j], sizeof(skins[j]), "pain2.pcx"); break;
case 3:
Com_strcat (skins[j], sizeof(skins[j]), "custom1.pcx"); break;
case 4:
Com_strcat (skins[j], sizeof(skins[j]), "custom1_p1.pcx"); break;
case 5:
Com_strcat (skins[j], sizeof(skins[j]), "custom1_p2.pcx"); break;
case 6:
Com_strcat (skins[j], sizeof(skins[j]), "custom2.pcx"); break;
case 7:
Com_strcat (skins[j], sizeof(skins[j]), "custom2_p1.pcx"); break;
case 8:
Com_strcat (skins[j], sizeof(skins[j]), "custom2_p2.pcx"); break;
case 9:
Com_strcat (skins[j], sizeof(skins[j]), "custom3.pcx"); break;
case 10:
Com_strcat (skins[j], sizeof(skins[j]), "custom3_p1.pcx"); break;
case 11:
Com_strcat (skins[j], sizeof(skins[j]), "custom3_p2.pcx"); break;
}
}
// end Knightmare
else
{
switch (j)
{
case 0:
Com_strcat (skins[j], sizeof(skins[j]), "skin.pcx"); break;
case 1:
Com_strcat (skins[j], sizeof(skins[j]), "pain.pcx"); break;
case 2:
Com_strcat (skins[j], sizeof(skins[j]), "custom1.pcx"); break;
case 3:
Com_strcat (skins[j], sizeof(skins[j]), "custompain1.pcx"); break;
case 4:
Com_strcat (skins[j], sizeof(skins[j]), "custom2.pcx"); break;
case 5:
Com_strcat (skins[j], sizeof(skins[j]), "custompain2.pcx"); break;
case 6:
Com_strcat (skins[j], sizeof(skins[j]), "custom3.pcx"); break;
case 7:
Com_strcat (skins[j], sizeof(skins[j]), "custompain3.pcx"); break;
}
}
}
// load original model from baseq2
Com_sprintf (infilename, sizeof(infilename), "baseq2/%s", modelname);
// Knightmare- if not in baseq2, look in current gamedir
// can't do this- the output file already exists
//if ( !(infile = fopen (infilename, "rb")) )
// GameDirRelativePath (modelname, infilename, sizeof(infilename));
// If file doesn't exist on user's hard disk, it must be in
// a pak file
if ( !(infile = fopen (infilename, "rb")) )
{
pak_header_t pakheader;
pak_item_t pakitem;
FILE *fpak;
int i, k, numitems;
char pakfile[MAX_OSPATH];
// Knightmare- look in all pak files in baseq2
for (i=0; i<10; i++)
{
Com_sprintf (pakfile, sizeof(pakfile), "baseq2/pak%i.pak", i);
fpak = fopen(pakfile, "rb");
if (!fpak && (i == 0)) // if pak0.pak isn't on hard disk, try CD
{
cvar_t *cddir;
cddir = gi.cvar("cddir", "", 0);
Com_sprintf(pakfile, sizeof(pakfile), "%s/baseq2/pak0.pak",cddir->string);
fpak = fopen(pakfile,"rb");
if (!fpak)
{
gi.dprintf("PatchMonsterModel: Cannot find pak0.pak on CD\n");
// return 0;
continue;
}
}
else if (!fpak) // this pak not found, go on to next
continue;
fread(&pakheader,1,sizeof(pak_header_t),fpak);
numitems = pakheader.dsize/sizeof(pak_item_t);
fseek(fpak,pakheader.dstart,SEEK_SET);
data = NULL;
for (k=0; k<numitems && !data; k++)
{
fread(&pakitem,1,sizeof(pak_item_t),fpak);
if (!Q_stricmp(pakitem.name,modelname))
{
fseek(fpak,pakitem.start,SEEK_SET);
fread(&model, sizeof(dmdl_t), 1, fpak);
datasize = model.ofs_end - model.ofs_skins;
if ( !(data = malloc (datasize)) ) // make sure freed locally
{
fclose(fpak);
gi.dprintf ("PatchMonsterModel: Could not allocate memory for model\n");
return 0;
}
fread (data, sizeof (byte), datasize, fpak);
}
}
fclose(fpak);
if (data) // we found it, so stop searching
break;
}
if (!data) // if not in baseq2 pak file, check pakfiles in current gamedir
{
char pakname[MAX_OSPATH];
// check all pakfiles in current gamedir
for (i=0; i<10; i++)
{
Com_sprintf (pakname, sizeof(pakname), "pak%i.pak", i);
GameDirRelativePath (pakname, pakfile, sizeof(pakfile));
fpak = fopen(pakfile,"rb");
if (!fpak) // this pak not found, go on to next
continue;
fread(&pakheader,1,sizeof(pak_header_t),fpak);
numitems = pakheader.dsize/sizeof(pak_item_t);
fseek(fpak,pakheader.dstart,SEEK_SET);
data = NULL;
for (k=0; k<numitems && !data; k++)
{
fread(&pakitem,1,sizeof(pak_item_t),fpak);
if (!Q_stricmp(pakitem.name,modelname))
{
fseek(fpak,pakitem.start,SEEK_SET);
fread(&model, sizeof(dmdl_t), 1, fpak);
datasize = model.ofs_end - model.ofs_skins;
if ( !(data = malloc (datasize)) ) // make sure freed locally
{
fclose(fpak);
gi.dprintf ("PatchMonsterModel: Could not allocate memory for model\n");
return 0;
}
fread (data, sizeof (byte), datasize, fpak);
}
}
fclose(fpak);
if (data) // we found it, so stop searching
{
gamedirpakfile = true;
break;
}
}
if (!data)
{
gi.dprintf("PatchMonsterModel: Could not find %s in pak file(s)\n",modelname);
return 0;
}
}
}
else
{
fread (&model, sizeof (dmdl_t), 1, infile);
datasize = model.ofs_end - model.ofs_skins;
if ( !(data = malloc (datasize)) ) // make sure freed locally
{
gi.dprintf ("PatchMonsterModel: Could not allocate memory for model\n");
return 0;
}
fread (data, sizeof (byte), datasize, infile);
fclose (infile);
}
// update model info
model.num_skins = numskins;
newoffset = numskins * MAX_SKINNAME;
model.ofs_st += newoffset;
model.ofs_tris += newoffset;
model.ofs_frames += newoffset;
model.ofs_glcmds += newoffset;
model.ofs_end += newoffset;
// save new model
/* Com_sprintf (outfilename, sizeof(outfilename), "%s/models", gamedir->string); // make some dirs if needed
_mkdir (outfilename);
Com_strcat (outfilename, sizeof(outfilename), "/monsters");
_mkdir (outfilename);
Com_sprintf (outfilename, sizeof(outfilename), "%s/%s", gamedir->string, modelname);
p = strstr(outfilename,"/tris.md2");
*p = 0;
_mkdir (outfilename);
Com_sprintf (outfilename, sizeof(outfilename), "%s/%s", gamedir->string, modelname);
*/
Com_sprintf (tempname, sizeof(tempname), modelname);
SavegameDirRelativePath (tempname, outfilename, sizeof(outfilename));
CreatePath (outfilename);
if ( !(outfile = fopen (outfilename, "wb")) )
{
// file couldn't be created for some other reason
gi.dprintf ("PatchMonsterModel: Could not save %s\n", outfilename);
free (data);
return 0;
}
fwrite (&model, sizeof (dmdl_t), 1, outfile);
fwrite (skins, sizeof (char), newoffset, outfile);
fwrite (data, sizeof (byte), datasize, outfile);
fclose (outfile);
// Knightmare- if we loaded the model from a pak file in the same gamedir,
// then we need to insert it into a higher-numbered pakfile, otherwise it won't be loaded
gi.dprintf ("PatchMonsterModel: Saved %s\n", outfilename);
free (data);
return 1;
}