Files
quakemapping/mod_ad_dev/my_progs/mon_jim.qc
2019-12-30 22:24:44 +01:00

587 lines
21 KiB
Plaintext

/*==============================================================================
Jim (Heavily inspired by bob from Quoth - Kell/Necros/Preach)
New models, skins and animations, code
==============================================================================*/
// Slight movement of wings
$frame idle1 idle2 idle3 idle4 idle5 idle6
// Twist wings backward and evil eyes
$frame idle7 idle8 idle9 idle10 idle11 idle12
// Wobble gun (+/- 5 degrees) and evil eyes
$frame idle13 idle14 idle15 idle16 idle17 idle18
// Idle while gun is point left or right
$frame idleleft1 idleleft2 idleleft3 idleleft4 idleleft5 idleleft6
$frame idleright1 idleright2 idleright3 idleright4 idleright5 idleright6
// Idle and turn gun left or right 30 degrees
$frame idleturnl1 idleturnl2 idleturnl3 idleturnl4 idleturnl5 idleturnl6
$frame idleturnr1 idleturnr2 idleturnr3 idleturnr4 idleturnr5 idleturnr6
// Custom wing movements for combat
$frame movedn1 movedn2 movedn3 movedn4 movedn5 movedn6
$frame moveup1 moveup2 moveup3 moveup4 moveup5 moveup6
// Flap wings and get angry eyes
$frame pain1 pain2 pain3 pain4 pain5 pain6
// Downward spiral with twisted wings
$frame zdeath1 zdeath2 zdeath3 zdeath4 zdeath5 zdeath6
float JS_IDLE = 0; // Default state
float JS_IDLE2 = 1; // Special idle (stationary)
float JS_IDLE3 = 2; // Special idle (stationary)
float JS_IDLELEFT = 3; // Looking left
float JS_IDLERIGHT = 4; // Looking right
float JS_TURNLEFT = 5; // Turning left (not looped)
float JS_TURNRIGHT = 6; // Turning right (not looped)
float JS_DOWN = 7; // Combat move
float JS_UP = 8; // Combat move
float JS_PAIN = 9; // Resets current animation
float JS_DYING = 10; // Final animation
// Size of each animation block
float JS_ANIMBLOCK = 6;
//======================================================================
// Update Jim every frame with skin/sound updates
// fstate (see above for animation block details)
//======================================================================
void(float fstate) jim_update =
{
// If jim is dead, no more updates
if (self.health < 1) return;
// Update ther gun idle (buzzing) sound
if (self.t_width < time) {
self.t_width = time + 0.9;
// Play on auto channel so it overlaps with existing sound
sound (self, CHAN_AUTO, "jim/gunidle2.wav", 0.3, ATTN_WEAPON);
}
// Time for an idle sound?
if (self.idletimer < time) monster_idle_sound();
// Jim has a glowing gun and eye skin feature!
if (self.t_length < time) {
self.t_length = time + 0.2;
self.exactskin = self.exactskin + 1;
if (self.exactskin > 2) self.exactskin = 0;
self.skin = self.exactskin;
}
// In combat?
if (self.enemy) {
self.idlebusy = FALSE;
if (fstate == JS_IDLE) {
if (self.origin_z > self.oldorigin_z) fstate = JS_UP;
else if (self.origin_z < self.oldorigin_z) fstate = JS_DOWN;
}
}
else {
// At beginning of frame block and reversing?
if (self.count == 0 && self.idlereverse) {
if (self.idlebusy == JS_TURNLEFT || self.idlebusy == JS_TURNRIGHT) {
self.idlebusy = JS_IDLE;
self.idlereverse = FALSE;
self.waitmin = time + 2 + random() * 4;
}
}
// At end of frame block and going forward?
else if (self.count == JS_ANIMBLOCK-1 && !self.idlereverse) {
// Staring forward, doing nothing
if (self.idlebusy == JS_IDLE && self.waitmin < time) {
// Only start idle animations if stationary
if (self.velocity_x == 0 && self.velocity_y == 0) {
if (self.idletimer < time) monster_idle_sound();
self.lip = random();
if (self.lip < 0.3) self.idlebusy = JS_TURNLEFT;
else if (self.lip < 0.6) self.idlebusy = JS_TURNRIGHT;
else if (self.lip < 0.8) self.idlebusy = JS_IDLE2;
else self.idlebusy = JS_IDLE3;
}
}
// Return from wing flex, gun swing idle animations
else if (self.idlebusy == JS_IDLE2 || self.idlebusy == JS_IDLE3) {
self.idlebusy = JS_IDLE;
self.waitmin = time + 2 + random() * 4;
}
// Turning eyes/gun in left direction
else if (self.idlebusy == JS_TURNLEFT) {
self.idlebusy = JS_IDLELEFT;
self.waitmin = time + 2 + random() * 4;
}
// Turning eyes/gun to right direction
else if (self.idlebusy == JS_TURNRIGHT) {
self.idlebusy = JS_IDLERIGHT;
self.waitmin = time + 2 + random() * 4;
}
// Looking left and randomly think about returning
else if (self.idlebusy == JS_IDLELEFT) {
if (self.waitmin < time && random() < 0.5) {
self.idlebusy = JS_TURNLEFT;
self.idlereverse = TRUE;
}
}
// Looking right and randomly think about returning
else if (self.idlebusy == JS_IDLERIGHT) {
if (self.waitmin < time && random() < 0.5) {
self.idlebusy = JS_TURNRIGHT;
self.idlereverse = TRUE;
}
}
}
// Change the movement type so that jim can easily move up/down
// using velocity, forced origin movement is really jerky!
if (self.velocity_x == 0 && self.velocity_y == 0) {
if (self.attack_timer < time) {
self.attack_timer = time + 1;
if (self.lip < 1) self.lip = 1;
else self.lip = -1;
self.velocity_z = 2 * self.lip;
}
}
}
// Check frame direction and update counter
if (self.idlereverse) self.count = self.count - 1;
else self.count = self.count + 1;
if (self.count >= JS_ANIMBLOCK) self.count = 0;
if (self.count < 0) self.count = JS_ANIMBLOCK-1;
// Busy with an idle animation?
if (self.idlebusy > 0) fstate = self.idlebusy;
// Update frame animation block with frame counter
self.frame = fstate * JS_ANIMBLOCK + self.count;
// Store current origin position
self.oldorigin = self.origin;
};
//======================================================================
// All stand, walk and run functions are condensed down to one entry
// because the robot has a constant skin/sound update that has to
// happen at specific intervals
//
void() jim_stand1 =[ $idle1, jim_stand1 ] {
// Standing idle has gentle bobbing up and down
self.solid = SOLID_SLIDEBOX;
self.movetype = MOVETYPE_FLY;
jim_update(JS_IDLE); ai_stand();
};
void() jim_walk1 =[ $idle1, jim_walk1 ] {
// Movement is steps, not velocity
self.solid = SOLID_SLIDEBOX;
self.movetype = MOVETYPE_STEP;
jim_update(JS_IDLE); ai_walk(8);
};
void() jim_run1 =[ $idle1, jim_run1 ] {
// Movement is steps, only velocity when firing weapon
self.solid = SOLID_SLIDEBOX;
self.movetype = MOVETYPE_STEP;
jim_update(JS_IDLE);
ai_run(16);
if (self.movespeed != 0) ai_face();
};
//======================================================================
// Range (LASERS or ROCKETS)
//======================================================================
void() jim_laser =
{
local vector org, dir, vec;
if (!self.enemy) return;
if (self.health < 1) return;
// Always make sure there is no monster or obstacle in the way
// Cannot use enemy entity direct, enemytarget will be active
if ( !visxray(SUB_entEnemyTarget(), self.attack_offset, '0 0 12', FALSE) ) return;
self.effects = self.effects | EF_MUZZLEFLASH;
sound (self, CHAN_WEAPON, "enforcer/enfire.wav", 1, ATTN_NORM);
makevectors (self.angles);
org = self.origin + attack_vector(self.attack_offset);
// Aim high to catch jumping players
dir = SUB_orgEnemyTarget() + '0 0 12';
vec = normalize(dir - org);
// Variable laser speed, original unit was just 800 every skill
self.attack_speed = SPEED_JIMPROJ + (skill * SPEED_JIMPROJSKILL);
launch_projectile(org, vec, CT_PROJ_LASER, self.attack_speed);
};
//----------------------------------------------------------------------
// Straight aim rocket, no pre-calculation or steering (skill based speed)
//----------------------------------------------------------------------
void() jim_rocket =
{
local vector org, dir, vec;
if (!self.enemy) return;
if (self.health < 1) return;
// Always make sure there is no monster or obstacle in the way
// Cannot use enemy entity direct, enemytarget will be active
if ( !visxray(SUB_entEnemyTarget(), self.attack_offset, '0 0 -12', FALSE) ) return;
self.effects = self.effects | EF_MUZZLEFLASH;
sound (self, CHAN_WEAPON, "jim/rocket_fire.wav", 1, ATTN_NORM);
makevectors (self.angles);
org = self.origin + attack_vector(self.attack_offset);
// Aim low to catch players with splash damage
dir = SUB_orgEnemyTarget() - '0 0 12';
vec = normalize(dir - org);
// Variable rocket speed, matching laser (very nasty)
self.attack_speed = SPEED_JIMPROJ + (skill * SPEED_JIMPROJSKILL);
Launch_Missile (org, vec, '0 0 0', CT_PROJ_JIM2, self.attack_speed);
};
//----------------------------------------------------------------------------
void(float vspeed) jim_vel = {
// Turn and face enemy and update attack velocity
ai_face();
if (self.movespeed == 0)
self.velocity = self.attack_track * vspeed;
};
//----------------------------------------------------------------------------
// Calculate new attack vector for firing lasers at the player
//----------------------------------------------------------------------------
void() jim_attack =
{
self.solid = SOLID_SLIDEBOX;
if (self.movespeed == 0) self.movetype = MOVETYPE_FLY;
makevectors(self.angles);
// Always fly upwards away from the player
self.pos1 = v_up * (50 + random() * 100);
// Randomly pick a left or right strafe direction
self.pos2 = v_right * (crandom() * 200);
// Always try to back away from the player
self.pos3 = v_forward * (random() * 100);
// Merge all the randomness together
self.attack_track = self.pos1 + self.pos2 + self.pos3;
// Check nothing is in the way, estimate vector of attack
traceline(self.origin, self.origin+self.attack_track, FALSE, self);
};
//----------------------------------------------------------------------------
void() jim_fire1 =[ $idle1, jim_fire2 ] {jim_update(JS_IDLE);
if (self.classtype == CT_MONJIM)
sound (self, CHAN_WEAPON, "jim/laser_load.wav", 1, ATTN_NORM);
else sound (self, CHAN_WEAPON, "jim/rocket_load.wav", 1, ATTN_NORM);
};
void() jim_fire2 =[ $idle1, jim_fire3 ] {jim_update(JS_IDLE);jim_attack();
if (skill < SKILL_HARD) jim_vel(0.4);
else jim_vel(1.5);
};
void() jim_fire3 =[ $idle1, jim_fire4 ] {
jim_update(JS_IDLE); if (skill < SKILL_HARD) jim_vel(0.6);};
void() jim_fire4 =[ $idle1, jim_fire5 ] { jim_update(JS_IDLE);
if (skill < SKILL_HARD) jim_vel(0.8);};
void() jim_fire5 =[ $idle1, jim_fire6 ] {jim_update(JS_IDLE);jim_vel(1.0);};
void() jim_fire6 =[ $idle1, jim_fire7 ] {
jim_update(JS_IDLE); if (skill < SKILL_HARD) jim_vel(1.2);
if (self.classtype == CT_MONJIM) jim_laser();
else jim_rocket();
};
void() jim_fire7 =[ $idle1, jim_fire8 ] {jim_update(JS_IDLE);
if (skill < SKILL_HARD) jim_vel(1.4);
if (self.classtype == CT_MONJIM) jim_laser();
};
void() jim_fire8 =[ $idle1, jim_fire9 ] {jim_update(JS_IDLE);
if (skill < SKILL_HARD) jim_vel(1.3);
if (self.classtype == CT_MONJIM && skill >= SKILL_NORMAL) jim_laser();
};
void() jim_fire9 =[ $idle1, jim_fire10] {jim_update(JS_IDLE);
if (skill < SKILL_HARD) jim_vel(1.1);
if (self.classtype == CT_MONJIM && skill >= SKILL_HARD) jim_laser();
};
void() jim_fire10 =[ $idle1, jim_fire11]{jim_update(JS_IDLE);jim_vel(0.5);};
void() jim_fire11 =[ $idle1, jim_run1 ] {jim_update(JS_IDLE);
// Chance of bob instantly firing again
if (random() < 0.8) SUB_AttackFinished( 1 + random() );
else SUB_AttackFinished( 1 + random()*0.5 );
};
//============================================================================
// ROBOT PAIN!?!
//============================================================================
void() jim_inpain =
{
// Keep cycling the pain animation
self.think = jim_inpain;
self.nextthink = time + 0.1;
// Start of pain cycle
if (self.inpain == 0) {
// Spawn a pile of sparks and dust falling down
particle_dust(self.origin, 10+random()*10, PARTICLE_BURST_YELLOW);
self.pos1 = '0 0 0';
self.pos1_x = random() * self.lefty; // Pitch away from dmg
self.pos1_z = crandom() * -10; // Roll left / right
}
// Finished, back to combat
else if (self.inpain >= 5) {
// reset pitch/roll
self.angles_x = self.angles_z = 0;
self.think = self.th_run;
}
else {
// Keep moving in the pitch/roll direction
self.pos1 = self.pos1 * 1.2;
self.angles = self.angles + self.pos1;
}
// Update pain frame
jim_update(JS_PAIN);
// Next pain animation
self.inpain = self.inpain + 1;
};
//----------------------------------------------------------------------------
void(entity inflictor, entity attacker, float damage) jim_pain =
{
// Check all pain conditions and set up what to do next
monster_pain_check(attacker, damage);
// Stop any ai_run velocity and reset movetype
self.velocity = '0 0 0';
self.solid = SOLID_SLIDEBOX;
self.movetype = MOVETYPE_STEP;
if (random() < 0.5) sound (self, CHAN_VOICE, self.pain_sound, 1, ATTN_NORM);
else sound (self, CHAN_VOICE, self.pain_sound2, 1, ATTN_NORM);
self.inpain = 0;
// Work out which direction the damage came from (enemy)
if (infront(attacker)) self.lefty = 10; // Hit from infront
else self.lefty = -10; // Hit from behind
jim_inpain();
};
//============================================================================
// SLOW DYING ROBOT!?!
//============================================================================
void() jim_dying2;
void() jim_is_dead;
//----------------------------------------------------------------------------
void() jim_dying1 =
{
// Setup correct death frame
self.frame = JS_DYING + self.cnt;
self.cnt = self.cnt + 1;
// Start spinning the robot out of control
// This update works with avelocity to create spiral effect
makevectors(self.angles);
self.velocity = (v_forward * 300) + (v_up * -30);
// Keep spinning out of control (constant direction)
if (self.lefty == 1) self.angles_y = (self.angles_y + 10);
else self.angles_y = (self.angles_y - 10);
// Spawn some extra smoke trails
if (random() < 0.3) SpawnProjectileSmoke(self.origin, 150, 50, 150);
// Keep looping round
self.think = jim_dying1;
self.nextthink = time + 0.1;
// Next frame/function?
if (self.cnt > 3) {
self.think = jim_dying2; // Next function
self.waitmin = time + 1; // Setup timer for next part
self.cnt = 0; // Reset death animation
}
};
//----------------------------------------------------------------------------
void() jim_dying2 =
{
// Setup correct death frame
self.frame = JS_DYING + self.cnt;
self.cnt = self.cnt + 1;
if (self.cnt > 5) self.cnt = 0;
// Medium explosion + sound OR smoke projectile trail
if (random() > 0.7) SpawnExplosion(EXPLODE_MED, self.origin, "jim/explode_minor.wav");
else if (random() < 0.3) SpawnProjectileSmoke(self.origin, 150, 50, 150);
// Turn down towards the ground
makevectors(self.angles);
self.solid = SOLID_BBOX; // Collision + touch
// Slow down the velocity to accent the spinning
self.velocity = (self.velocity * 0.2) + ((v_forward * 100) * 0.8);
self.velocity = normalize(self.velocity) * 300;
self.velocity_z = self.velocity_z - 200;
// Keep spinning out of control (constant direction)
if (self.lefty == 1) self.angles_y = (self.angles_y + 10);
else self.angles_y = (self.angles_y - 10);
// Keep looping round
self.nextthink = time + 0.1;
if (self.waitmin < time) self.think = jim_is_dead;
else self.think = jim_dying2;
};
//----------------------------------------------------------------------------
void() jim_is_dead =
{
self.think = self.touch = SUB_Null;
// Final fireworks!
particle_dust(self.origin, 10+random()*10, PARTICLE_BURST_YELLOW);
SpawnProjectileSmoke(self.origin, 150, 50, 150);
SpawnProjectileSmoke(self.origin, 150, 50, 150);
SpawnExplosion(EXPLODE_BIG, self.origin, "jim/explode_major.wav");
// Goto final resting place
entity_hide(self);
};
//----------------------------------------------------------------------------
void() jim_die =
{
// Pre-check routine to tidy up extra entities
monster_death_precheck();
// Flush every sound channel to kill gun buzzing sound
sound(self, CHAN_WEAPON, SOUND_EMPTY, 1, ATTN_NORM);
sound(self, CHAN_VOICE, SOUND_EMPTY, 1, ATTN_NORM);
sound(self, CHAN_ITEM, SOUND_EMPTY, 1, ATTN_NORM);
sound(self, CHAN_FEET, SOUND_EMPTY, 1, ATTN_NORM);
sound(self, CHAN_EXTRA1, SOUND_EMPTY, 1, ATTN_NORM);
sound(self, CHAN_EXTRA2, SOUND_EMPTY, 1, ATTN_NORM);
// Special explosive death event
// First function creates the upward spiral (negative avelocity)
// Second function accents the spiral (1s forward movement)
// Third final impact (touch function goes here as well)
//
sound (self, CHAN_VOICE, "jim/death1.wav", 1, ATTN_NORM);
self.solid = SOLID_NOT; // No world colision
self.movetype = MOVETYPE_FLY; // Free velocity movement
self.touch = jim_is_dead; // still can touch stuff
self.cnt = 0; // Death frame 0
self.movespeed = 0; // Make sure turret mode off
// The avelocity works better when negative (twisting upward)
self.avelocity_x = (random() * 50) - 100;
self.avelocity_y = (random() * 50) - 100;
self.avelocity_z = (random() * 50) - 100;
// Decide which direction to spin
if (random() < 0.5) self.lefty = 1;
else self.lefty = 0;
// Spawn dust and sprite explosion
particle_dust(self.origin, 10+random()*10, PARTICLE_BURST_YELLOW);
SpawnExplosion(EXPLODE_BIG, self.origin, "jim/explode_minor.wav");
SpawnProjectileSmoke(self.origin, 150, 50, 150);
jim_dying1();
};
/*======================================================================
/*QUAKED monster_jim (1 0 0) (-16 -16 -24) (16 16 24) Ambush
======================================================================*/
void() monster_jim =
{
if (deathmatch) { remove(self); return; }
if (self.spawnflags & MON_JIM_ROCKET) self.mdl = "progs/mon_jimrock.mdl";
else self.mdl = "progs/mon_jim.mdl";
precache_model (self.mdl);
// death/pain/attack sounds
precache_sound("jim/death1.wav");
self.pain_sound = "jim/pain1.wav";
self.pain_sound2 = "jim/pain2.wav";
precache_sound(self.pain_sound);
precache_sound(self.pain_sound2);
precache_sound("jim/explode_minor.wav");
precache_sound("jim/explode_major.wav");
precache_sound("jim/gunidle2.wav"); // Long buzzing sound
self.sight_sound = "jim/sight1.wav";
precache_sound (self.sight_sound);
self.solid = SOLID_NOT; // No interaction with world
self.movetype = MOVETYPE_NONE; // Static item, no movement
if (self.bboxtype < 1) self.bboxtype = BBOX_SHORT;
self.gibhealth = MON_NEVERGIB; // Cannot be gibbed by weapons
self.gibbed = FALSE;
self.pain_flinch = 30; // Sometimes flinch
self.steptype = FS_FLYING; // Silent feet
self.pain_longanim = FALSE; // No long pain animation
self.blockudeath = TRUE; // No humanoid death sound
if (self.height < 1) self.height = MONAI_ABOVEDIST; // Custom height
self.oldorigin = self.origin; // Used for wing angles
self.idlebusy = 0; // Rotate gun idle states
self.idlereverse = FALSE; // Reverse direction for idle
self.attack_offset = '20 0 -10'; // front of laser/rocket
self.poisonous = FALSE; // Robots are not poisonous
// Always reset Ammo Resistance to be consistent
self.resist_shells = self.resist_nails = 0;
self.resist_rockets = self.resist_cells = 0;
self.th_checkattack = JimCheckAttack;
self.th_stand = jim_stand1;
self.th_walk = jim_walk1;
self.th_run = jim_run1;
self.th_pain = jim_pain;
self.th_die = jim_die;
self.th_missile = jim_fire1;
self.classgroup = CG_ROBOT;
self.classmove = MON_MOVEFLY;
if (self.spawnflags & MON_JIM_ROCKET) {
self.classtype = CT_MONJIMROCKET;
if (self.health < 1) self.health = 50;
self.deathstring = " was blown away by Jim\n";
// Classic idle sound + new variant
self.idle_sound = "jim/idle1.wav";
self.idle_sound2 = "jim/idle3.wav";
precache_sound (self.idle_sound);
precache_sound (self.idle_sound2);
// Projectile fire and impact (used in projectiles)
precache_sound("jim/rocket_load.wav"); // Wind up
precache_sound("jim/rocket_fire.wav"); // Fire
precache_sound("jim/rocket_hit.wav"); // Impact
}
else {
self.classtype = CT_MONJIM;
if (self.health < 1) self.health = 100;
self.deathstring = " was cauterized by Jim\n";
// Classic idle sound + new variant
self.idle_sound = "jim/idle1.wav";
self.idle_sound2 = "jim/idle2.wav";
precache_sound (self.idle_sound);
precache_sound (self.idle_sound2);
// Projectile fire and impact (used in projectiles)
precache_model (MODEL_PROJ_LASER); // Enforcer laser model
precache_sound("jim/laser_load.wav"); // Wind up
precache_sound ("enforcer/enfire.wav"); // Fire
precache_sound ("enforcer/enfstop.wav"); // Impact
}
monster_start();
};
// Re-direct all Quoth Bob requests to Jim!
void() monster_bob = { monster_jim(); }