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

648 lines
22 KiB
Plaintext

/*==============================================================================
LOST SOUL
==============================================================================*/
// moving jaw up and down (talking)
$frame idle1 idle2 idle3 idle4 idle5 idle6
// Tilt head backwards to look up
$frame idleB1 idleB2 idleB3 idleB4 idleB5 idleB6
// Tilt head downwards to look below
$frame idleF1 idleF2 idleF3 idleF4 idleF5 idleF6
// Keep looking left or right
$frame idleleft1 idleleft2 idleleft3 idleleft4 idleleft5 idleleft6
$frame idleright1 idleright2 idleright3 idleright4 idleright5 idleright6
// Turn head left or right 30 degrees
$frame idleturnl1 idleturnl2 idleturnl3 idleturnl4 idleturnl5 idleturnl6
$frame idleturnr1 idleturnr2 idleturnr3 idleturnr4 idleturnr5 idleturnr6
// mouth wide open and head tilted back
$frame charge1 charge2 charge3 charge4 charge5 charge6
// obvious chewing motion
$frame chew1 chew2 chew3 chew4 chew5 chew6
// mouth open and rotated Backward or Forward
$frame pain1 pain2 pain3 pain4 pain5 pain6
$frame pain7 pain8 pain9 pain10 pain11 pain12
// Spawning from nothing
$frame grow1 grow2 grow3 grow4 grow5 grow6 grow7 grow8 grow9 grow10
float LOST_IDLE = 0; // Default state
float LOST_IDLEB = 1; // Looking UP
float LOST_IDLEF = 2; // Looking DOWN
float LOST_IDLELEFT = 3; // Looking left
float LOST_IDLERIGHT = 4; // Looking right
float LOST_TURNLEFT = 5; // Turning left (not looped)
float LOST_TURNRIGHT = 6; // Turning right (not looped)
float LOST_CHARGE = 7; // Mouth wide open and charging
float LOST_MELEE = 8; // Mouth chomping open/close
float LOST_PAINB = 9; // Roll backwards
float LOST_PAINF = 10; // Roll forward
// Size of each animation block
float LOST_ANIMBLOCK = 6;
// Different states and fire particle quantity
float LOST_STATE_STAND = 2;
float LOST_STATE_WALK = 4;
float LOST_STATE_GUARD = 3;
float LOST_STATE_RUN = 5;
float LOST_STATE_PAIN = 6;
//======================================================================
// Update Lost Souls every frame with velocity movements and glow eyes!
// fstate (see above for animation block details)
//======================================================================
void(float fstate) lost_update =
{
// If Lost is dead, no more updates
if (self.health < 1) return;
// Time for an idle sound? (run only)
if (self.idletimer < time && self.state == LOST_STATE_RUN)
monster_idle_sound();
// Update glowing eyes! (skin types = 0,1,2,3,2,1)
if (self.t_length < time) {
// fire particle quantity linked to AI state
particle_explode(self.origin+'0 0 12', self.state, 1, PARTICLE_BURST_YELLOW, PARTICLE_BURST_LOSTUP);
self.t_length = time + 0.2;
self.exactskin = self.exactskin + 1;
if (self.exactskin > 5) self.exactskin = 0;
self.skin = self.exactskin;
}
// While standing still or guarding, gently float up and down
if (self.state == LOST_STATE_STAND || self.state == LOST_STATE_GUARD) {
// While standing around idle, gently move up and 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;
}
}
}
// Lost sight of enemy during combat, wait and look around
if (self.state == LOST_STATE_GUARD) {
if (self.meleeattack < time) {
self.meleeattack = time + 4 + (random() + random() + random() * 4);
if (random() < 0.5) self.lefty = 1;
else self.lefty = -1;
self.avelocity = '0 0 0';
self.avelocity_y = self.lefty*50;
self.attack_sidestep = 0;
self.attack_sidedeny = rint(4 + random()*4);
}
// Keep increasing rotation angle velocity
self.attack_sidestep = self.attack_sidestep + 1;
if (self.attack_sidestep > self.attack_sidedeny) self.avelocity = '0 0 0';
else self.avelocity_y = self.avelocity_y * 1.25;
// Check lost idle timer after combat
if (self.lostenemy && self.losttimer < time) {
self.losttimer = LARGE_TIMER;
if (self.lostenemy.health < 0) self.lostenemy = world;
else {
// Restore previous enemy and start hunting
self.enemy = self.lostenemy;
self.think = self.th_run;
self.state = LOST_STATE_RUN;
// Setup goals and DO NOT warn other monsters
FoundHuntTarget(FALSE);
}
}
}
// Work through all the different AI states
if (self.state == LOST_STATE_STAND || self.state == LOST_STATE_WALK) {
// At beginning of frame block and reversing?
if (self.count == 0 && self.idlereverse) {
if (self.idlebusy == LOST_TURNLEFT || self.idlebusy == LOST_TURNRIGHT) {
self.idlebusy = LOST_IDLE;
self.idlereverse = FALSE;
self.waitmin = time + 2 + random() * 4;
}
}
// At end of frame block and going forward?
else if (self.count == LOST_ANIMBLOCK-1 && !self.idlereverse) {
// Always reset turn left/right animation while walking
if (self.state == LOST_STATE_WALK) {
if (self.idlebusy == LOST_IDLELEFT) {
self.idlebusy = LOST_TURNLEFT;
self.idlereverse = TRUE;
}
else if (self.idlebusy == LOST_IDLERIGHT) {
self.idlebusy = LOST_TURNRIGHT;
self.idlereverse = TRUE;
}
// Return from all other idle animations
else {
self.idlebusy = LOST_IDLE;
self.waitmin = time + 2 + random() * 4;
}
}
// Idle animation designed for standing around
else {
if (self.idlebusy == LOST_IDLE && self.waitmin < time) {
// Randonly pick an idle animation (difference stand/walk)
self.lip = random();
if (self.lip < 0.4) self.idlebusy = LOST_TURNLEFT;
else if (self.lip < 0.6) self.idlebusy = LOST_TURNRIGHT;
else if (self.lip < 0.8) self.idlebusy = LOST_IDLEB;
else self.idlebusy = LOST_IDLEF;
}
// Return from opening/closing mouth idle animations
else if (self.idlebusy == LOST_IDLEB || self.idlebusy == LOST_IDLEF) {
self.idlebusy = LOST_IDLE;
self.waitmin = time + 2 + random() * 4;
}
// Turning head left direction
else if (self.idlebusy == LOST_TURNLEFT) {
self.idlebusy = LOST_IDLELEFT;
self.waitmin = time + 2 + random() * 4;
}
// Turning head right direction
else if (self.idlebusy == LOST_TURNRIGHT) {
self.idlebusy = LOST_IDLERIGHT;
self.waitmin = time + 2 + random() * 4;
}
// Looking left and randomly think about returning
else if (self.idlebusy == LOST_IDLELEFT) {
if (self.waitmin < time && random() < 0.5) {
self.idlebusy = LOST_TURNLEFT;
self.idlereverse = TRUE;
}
}
// Looking right and randomly think about returning
else if (self.idlebusy == LOST_IDLERIGHT) {
if (self.waitmin < time && random() < 0.5) {
self.idlebusy = LOST_TURNRIGHT;
self.idlereverse = TRUE;
}
}
}
}
}
else {
// All other states (RUN, PAIN, GUARD)
// Reset any previous idle animations
self.idlereverse = FALSE;
self.idlebusy = FALSE;
}
// Check frame direction and update counter
if (self.idlereverse) self.count = self.count - 1;
else self.count = self.count + 1;
if (self.count >= LOST_ANIMBLOCK) self.count = 0;
if (self.count < 0) self.count = LOST_ANIMBLOCK-1;
// Busy with an idle animation?
if (self.idlebusy > 0) fstate = self.idlebusy;
// Update frame animation block with frame counter
self.frame = fstate * LOST_ANIMBLOCK + self.count;
// Store current origin position
self.oldorigin = self.origin;
};
//----------------------------------------------------------------------------
void() lost_flymode =
{
self.solid = SOLID_SLIDEBOX; // Reset just in case
self.movetype = MOVETYPE_FLY; // Turn body into projectile
// make sure onground is not set, cannot use velocity otherwise
self.flags = self.flags - (self.flags & FL_ONGROUND);
};
//----------------------------------------------------------------------------
void() lost_stepmode =
{
self.velocity = '0 0 0'; // Back to regular movement
self.avelocity = '0 0 0'; // Cancel any rotation velocity
self.angles_x = self.angles_z = 0; // Reset any X/Y angles
self.solid = SOLID_SLIDEBOX; // Always reset
self.movetype = MOVETYPE_STEP; // Back to discreet movement
};
//======================================================================
// All stand, walk and run functions are condensed down to one entry
// so that the lost souls can move dynamic and have skin updates
//
void() lost_stand1 = [ $idle1, lost_stand1 ] {
if (self.state != LOST_STATE_GUARD) self.state = LOST_STATE_STAND;
lost_flymode(); // allow velocity updates
lost_update(LOST_IDLE); // Update skin + velocity
ai_stand();
};
void() lost_guard1 = { self.state = LOST_STATE_GUARD; lost_stand1(); };
void() lost_walk1 = [ $idle1, lost_walk1 ] {
self.state = LOST_STATE_WALK;
lost_stepmode(); // back to discreet step mode
lost_update(LOST_IDLE); // Update skin
ai_walk(8);
};
void() lost_run1 = [ $idle1, lost_run1 ] {
self.state = LOST_STATE_RUN;
lost_stepmode(); // back to discreet step mode
lost_update(LOST_IDLE); // Update skin
ai_run(8);
};
//============================================================================
// Range Charge attack
//============================================================================
void() lost_charge_touch =
{
// Stop anymore touching
self.touch = SUB_Null;
self.think = self.th_run;
// Do not damage other souls with charge attacks
// Prevents packs from killing themselves
if (self.classtype != other.classtype && other.takedamage) {
T_Damage (other, self, self, DAMAGE_LOSTCHARGE, DAMARMOR);
spawn_touchblood (self, other, DAMAGE_LOSTCHARGE*3);
// If touched a player, nudge them backwards
if (other.flags & FL_CLIENT) {
self.lostsearch = FALSE;
makevectors (self.angles);
other.velocity = other.velocity + (v_forward * 200 + '0 0 50');
}
}
};
//----------------------------------------------------------------------------
void() lost_charge1 =
{
// Play hiss sound, sighted enemy, charging
sound (self, CHAN_WEAPON, "lostsoul/charge.wav", 1, ATTN_NORM);
self.count = 0; // reset start of animation block
lost_flymode(); // allow velocity updates
lost_update(LOST_CHARGE); // Update skin
// Stop tracking if easy/normal skill
if (skill < SKILL_HARD) self.lostsearch = FALSE;
self.cnt = 0; // reset speed updates
self.attack_speed = SPEED_LOSTSTART; // Initial speed
self.pos1 = SUB_orgEnemyTarget() + '0 0 12'; // Aim towards head
self.pos2 = normalize(self.pos1 - self.origin); // Vector direction
self.touch = lost_charge_touch; // Damage/end function
self.velocity = self.pos2 * self.attack_speed; // Launch body missile!
self.angles = vectoangles(self.velocity); // Turn straight away
self.think = self.th_charge;
self.nextthink = time + 0.1;
};
//----------------------------------------------------------------------------
void() lost_charge2 =
{
if (self.health < 1) return;
lost_update(LOST_CHARGE);
self.nextthink = time + 0.1;
self.cnt = self.cnt + 1; // speed updates
if (self.cnt > 12) self.think = self.th_run; // End of charge attack
// Keep travelling forward regardless of enemy
else {
// Keep increasing speed every 0.1s (check for max speed)
if (self.attack_speed < SPEED_LOSTMAX)
self.attack_speed = self.attack_speed + SPEED_LOSTCHARGE;
self.velocity = self.pos2 * self.attack_speed;
self.think = self.th_charge;
}
};
//======================================================================
// MELEE ATTACK - Biting
//======================================================================
void() lost_bite =
{
local float ldmg;
local entity enemy_targ;
enemy_targ = SUB_entEnemyTarget();
if (!enemy_targ) return;
if (self.health < 1) return;
ai_charge(10); // Get closer for extra bite
ai_damagebreakable(10); // Damage any breakables
if (!ai_checkmelee(MONAI_MELEELOSTSOUL)) return; // Too far away
// Can the target bleed?
if (enemy_targ.takedamage < 1) return;
sound (self, CHAN_WEAPON, "lostsoul/bite.wav", 1, ATTN_NORM);
// Lost Soul bite is very weak
ldmg = (random() + random() + random()) * 3;
T_Damage (enemy_targ, self, self, ldmg, DAMARMOR);
// Check for poisonous attribute (new poison version)
if (self.poisonous) PoisonDeBuff(self.enemy);
// Spawn blood at mouth of Lost Soul
spawn_touchblood (self, enemy_targ, ldmg*3);
};
//----------------------------------------------------------------------------
void() lost_melee2 =
{
if (self.health < 1) return;
lost_update(LOST_MELEE); // Update skin
self.cnt = self.cnt + 1; // Next frame
// Check for bite attack
if (self.cnt == 3) lost_bite();
// End of melee attack?
if (self.cnt >= 5) {
// One chop of the jaws and then fly away time
self.think = self.th_run;
// Check if enemy is still within range for another chop!
// if (ai_checkmelee(MONAI_MELEELOSTSOUL) && SUB_healthEnemyTarget() > 0)
// self.think = self.th_melee;
// else self.think = self.th_run;
}
else self.think = lost_melee2;
self.nextthink = time + 0.1;
};
//----------------------------------------------------------------------------
void() lost_melee1 =
{
self.count = 0; // reset start of animation block
self.cnt = 0; // Reset bite frame counter
lost_flymode(); // stay in flymode just in case collison probs
lost_update(LOST_MELEE); // Update skin
self.think = lost_melee2;
self.nextthink = time + 0.1;
};
//======================================================================
// MINION - Grow and spin up from nothing
//======================================================================
void() lost_growangle = {self.angles_y = self.angles_y + self.lefty;};
void() lost_grow1 = [ $grow1, lost_grow2 ] {};
void() lost_grow2 = [ $grow2, lost_grow3 ] {lost_growangle();};
void() lost_grow3 = [ $grow3, lost_grow4 ] {lost_growangle();};
void() lost_grow4 = [ $grow4, lost_grow5 ] {lost_growangle();};
void() lost_grow5 = [ $grow5, lost_grow6 ] {lost_growangle();};
void() lost_grow6 = [ $grow6, lost_grow7 ] {lost_growangle();};
void() lost_grow7 = [ $grow7, lost_grow8 ] {lost_growangle();};
void() lost_grow8 = [ $grow8, lost_grow9 ] {lost_growangle();};
void() lost_grow9 = [ $grow9, lost_grow10] {lost_growangle();};
void() lost_grow10= [ $grow10, lost_run1 ] {
// Is the lost stuck? cannot move?
if (pointcontents(self.origin) == CONTENT_SOLID) {
// Time to die!
self.health = self.gibhealth;
Killed(self, self);
}
else {
self.state = LOST_STATE_RUN;
self.count = 0;
lost_update(LOST_IDLE); // Update skin
// Finally spin back to original position
self.angles_y = self.angles_y + self.lefty;
// Setup goals and warn other monsters
FoundHuntTarget(TRUE);
// Restore all think state functions
self.th_stand = lost_stand1;
self.th_altstand = lost_guard1;
self.th_walk = lost_walk1;
self.th_run = lost_run1;
self.th_melee = lost_melee1;
self.th_missile = lost_charge1;
self.th_charge = lost_charge2;
// Start charging at player if not dead and got an enemy
if (self.health > 0 && self.enemy) {
ai_face();
self.think = self.th_missile;
}
}
};
//----------------------------------------------------------------------------
void() lost_grow =
{
// Only call wakeup function once
self.th_stand = self.th_walk = self.th_run = SUB_Null;
if (random() < 0.5) self.lefty = 36;
else self.lefty = -36;
monster_sightsound();
lost_stepmode();
lost_grow1();
};
//============================================================================
// PAIN in the head!
//============================================================================
void() lost_inpain =
{
// Update pain frame
lost_update(self.attack_rage);
// Next pain animation
self.inpain = self.inpain + 1;
if (self.inpain >= 5) self.think = self.th_run;
else self.think = lost_inpain;
self.nextthink = time + 0.1;
};
//----------------------------------------------------------------------
void(entity inflictor, entity attacker, float damage) lost_pain =
{
// Check all pain conditions and set up what to do next
monster_pain_check(attacker, damage);
if (self.state != LOST_STATE_PAIN) {
// Stop all velocity and reset movetype
lost_stepmode();
sound (self, CHAN_VOICE, self.pain_sound, 1, ATTN_NORM);
}
// Work out which direction the damage came from (enemy)
if (infront(attacker)) self.attack_rage = LOST_PAINB; // Hit from infront
else self.attack_rage = LOST_PAINF; // Hit from behind
self.state = LOST_STATE_PAIN;
// Reset pain and current frame counter
self.inpain = self.count = 0;
lost_inpain();
};
//============================================================================
void() lost_die1 = [ $pain1, lost_die2 ] {};
void() lost_die2 = [ $pain2, lost_die2 ] {
// Lost souls explode when they die
T_RadiusDamage (self, self, DAMAGE_LOSTSOUL, world, DAMAGEALL);
if (random() < 0.5) self.lip = EXPLODE_SMALL;
else if (random() < 0.9) self.lip = EXPLODE_MED;
else self.lip = EXPLODE_BIG;
SpawnExplosion(self.lip, self.origin, "lostsoul/death.wav");
// Goto final resting place
entity_remove(self, 0.1);
};
//----------------------------------------------------------------------
void() lost_die =
{
// Pre-check routine to tidy up extra entities
monster_death_precheck();
// Make sure body does not drop to the ground
self.flags = self.flags | FL_FLY;
self.takedamage = DAMAGE_NO;
lost_die1();
};
/*======================================================================
QUAKED monster_lostsoul (1 0.2 0) (-16 -16 -24) (16 16 24)
======================================================================*/
void() setup_lostsoul;
void() monster_lostsoul =
{
if (deathmatch) { remove(self); return; }
self.mdl = "progs/mon_lostsoul.mdl";
precache_model (self.mdl);
self.idle_sound = "lostsoul/idle1.wav";
self.idle_sound2 = "lostsoul/idle2.wav";
precache_sound (self.idle_sound);
precache_sound (self.idle_sound2);
precache_sound ("lostsoul/charge.wav"); // charge hiss/scream
precache_sound ("lostsoul/bite.wav"); // chomp with teeth
self.pain_sound = "lostsoul/pain1.wav";
precache_sound (self.pain_sound);
precache_sound ("lostsoul/death.wav");
// Cache lost soul is a special class used for precache only
if (self.classtype != CT_CACHELOSTSOUL) setup_lostsoul();
};
//----------------------------------------------------------------------------
void() monster_skullwizminion = {
self.classtype = CT_CACHELOSTSOUL;
monster_lostsoul();
};
//----------------------------------------------------------------------------
void() setup_lostsoul =
{
self.solid = SOLID_NOT; // No interaction with world
self.movetype = MOVETYPE_NONE; // Static item, no movement
if (self.bboxtype < 1) self.bboxtype = BBOX_TINY;
if (self.health < 1) self.health = 30;
self.yaw_speed = 35; // Really fast at turning
self.gibhealth = MON_NEVERGIB; // Cannot be gibbed by weapons
self.gibbed = FALSE; // Still in one piece
self.pain_flinch = 10; // Always go into pain
self.blockudeath = TRUE; // no humanoid death sound
self.steptype = FS_FLYING; // Silent feet
self.pain_longanim = FALSE; // No long pain animation
if (self.height < 1) self.height = MONAI_ABOVEDIST; // Custom height
self.meleeattack = time + 4; // no rotation at spawn
self.mangle = self.angles; // idle rotate left/right
self.idlebusy = 0; // Tilt/turn head animations
self.idlereverse = FALSE; // Reverse direction for idle
self.exactskin = rint(random()*5); // Randomize eye glow
self.lostenemy = world; // Setup old enemy
self.losttimer = LARGE_TIMER; // Stop lost timer
self.lostsearch = FALSE; // Not searching for an enemy
self.deathstring = " was found by a Lost Soul\n";
// I can understand lost souls would be the perfect candidate for
// poison attacks, but they are tricky already! default = disabled.
self.poisonous = FALSE;
// Always reset Ammo Resistance to be consistent
self.resist_shells = self.resist_nails = 0;
self.resist_rockets = self.resist_cells = 0;
self.th_checkattack = LostCheckAttack;
self.th_pain = lost_pain;
self.th_die = lost_die;
//----------------------------------------------------------------------
// Special spawning minion need to start spinning
if (self.classtype == CT_MINIONLOSTSOUL) {
self.th_stand = self.th_walk = self.th_run = lost_grow;
self.th_missile = SUB_Null;
}
//----------------------------------------------------------------------
// Default lost soul setup
else {
self.th_stand = lost_stand1;
self.th_altstand = lost_guard1;
self.th_walk = lost_walk1;
self.th_run = lost_run1;
self.th_melee = lost_melee1;
self.th_missile = lost_charge1;
self.th_charge = lost_charge2;
}
self.classtype = CT_MONLOSTSOUL;
self.classgroup = CG_WIZARD;
self.classmove = MON_MOVEFLY;
monster_start();
};
//----------------------------------------------------------------------------
// A code way to spawn lost souls (requires monster_skullwizminion entity)
//----------------------------------------------------------------------------
void(vector minion_org, entity minion_targ) minion_lostsoul =
{
local entity minion;
// Check if there is space to spawn entity
if (entity_pcontent(minion_org)) return;
// Self = skull wizard, there is no egg
update_minioncount(self, 1); // Update spawn counters
minion = spawn();
minion.classname = "monster_lostsoul"; // For function searching
setorigin(minion, minion_org); // Move to new location
minion.owner = self; // Spawner Parent Link
minion.effects = minion.flags = 0; // make sure are blank
minion.classtype = CT_MINIONLOSTSOUL; // Special minion class
minion.enemy = minion_targ; // Target to attack
minion.spawnflags = 0;
minion.classgroup = CG_WIZARD; // Don't turn on master
minion.minion_active = TRUE; // Minion flag
minion.bodyfadeaway = TRUE; // Get rid of body
minion.mdl = "progs/mon_lostsoul.mdl";
minion.pain_sound = "lostsoul/pain1.wav";
minion.idle_sound = "lostsoul/idle1.wav";
minion.idle_sound2 = "lostsoul/idle2.wav";
minion.nextthink = time + 0.01;
minion.think = setup_lostsoul;
};