805 lines
29 KiB
Plaintext
805 lines
29 KiB
Plaintext
/*==============================================================================
|
|
Seeker (Heavily inspired by Hunter from RRP - ijed)
|
|
==============================================================================*/
|
|
// Slow moving run/walk animation
|
|
$frame run1 run2 run3 run4 run5 run6 run7 run8
|
|
$frame run9 run10 run11 run12 run13 run14 run15 run16 run17 run18 run19
|
|
|
|
// stationary fire range attack (Not used)
|
|
$frame fire1 fire2 fire3 fire4 fire5 fire6 fire7 fire8
|
|
$frame fire9 fire10 fire11 fire12 fire13 fire14 fire15 fire16
|
|
$frame fire17 fire18 fire19 fire20 fire21 fire22 fire23 fire24
|
|
$frame fire25 fire26 fire27 fire28 fire29 fire30
|
|
|
|
// right arm punch
|
|
$frame punch1 punch2 punch3 punch4 punch5 punch6 punch7 punch8
|
|
$frame punch9 punch10
|
|
|
|
// fall backwards and die
|
|
$frame death1 death2 death3 death4 death5 death6 death7 death8
|
|
$frame death9 death10 death11 death12
|
|
|
|
// Stationary classic idle
|
|
$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8
|
|
$frame stand9 stand10 stand11 stand12 stand13 stand14 stand15 stand16
|
|
$frame stand17 stand18 stand19
|
|
|
|
// Run with LEFT arm missing
|
|
$frame runL1 runL2 runL3 runL4 runL5 runL6 runL7 runL8
|
|
$frame runL9 runL10 runL11 runL12 runL13 runL14 runL15 runL16
|
|
$frame runL17 runL18 runL19
|
|
|
|
// Run with RIGHT arm missing
|
|
$frame runR1 runR2 runR3 runR4 runR5 runR6 runR7 runR8
|
|
$frame runR9 runR10 runR11 runR12 runR13 runR14 runR15 runR16
|
|
$frame runR17 runR18 runR19
|
|
|
|
// Attack with LEFT arm missing
|
|
$frame fireL1 fireL2 fireL3 fireL4 fireL5 fireL6 fireL7 fireL8
|
|
$frame fireL9 fireL10 fireL11
|
|
|
|
// Attack with RIGHT arm missing
|
|
$frame fireR1 fireR2 fireR3 fireR4 fireR5 fireR6 fireR7 fireR8
|
|
$frame fireR9 fireR10 fireR11
|
|
|
|
// Attack with BOTH arms active (NEW)
|
|
$frame fireB1 fireB2 fireB3 fireB4 fireB5 fireB6 fireB7 fireB8
|
|
$frame fireB9 fireB10 fireB11
|
|
|
|
// Death with LEFT arm missing
|
|
$frame deathL1 deathL2 deathL3 deathL4 deathL5 deathL6 deathL7 deathL8
|
|
$frame deathL9 deathL10 deathL11 deathL12
|
|
|
|
// Death with RIGHT arm missing
|
|
$frame deathR1 deathR2 deathR3 deathR4 deathR5 deathR6 deathR7 deathR8
|
|
$frame deathR9 deathR10 deathR11 deathR12
|
|
|
|
// Death with BOTH arm missing (special event)
|
|
$frame deathB1 deathB2 deathB3 deathB4 deathB5 deathB6 deathB7 deathB8
|
|
$frame deathB9 deathB10 deathB11 deathB12
|
|
|
|
// Death with HEAD missing (special event)
|
|
$frame deathH1 deathH2 deathH3 deathH4 deathH5 deathH6 deathH7 deathH8
|
|
$frame deathH9 deathH10 deathH11 deathH12
|
|
|
|
// Idle with LEFT arm missing
|
|
$frame standL1 standL2 standL3 standL4 standL5 standL6 standL7 standL8
|
|
$frame standL9 standL10 standL11 standL12 standL13 standL14 standL15 standL16
|
|
$frame standL17 standL18 standL19
|
|
|
|
// Idle with RIGHT arm missing
|
|
$frame standR1 standR2 standR3 standR4 standR5 standR6 standR7 standR8
|
|
$frame standR9 standR10 standR11 standR12 standR13 standR14 standR15 standR16
|
|
$frame standR17 standR18 standR19
|
|
|
|
float SEEKER_ARMS_BOTH = 0;
|
|
float SEEKER_ARMS_LEFT = 1;
|
|
float SEEKER_ARMS_RIGHT = 2;
|
|
float SEEKER_ARMSLESS = 3;
|
|
float SEEKER_HEADLESS = 4;
|
|
|
|
float SEEKER_IDLE = 0;
|
|
float SEEKER_WALK = 1;
|
|
float SEEKER_FIRE = 2;
|
|
float SEEKER_DIE = 3;
|
|
|
|
//============================================================================
|
|
// Spark effect from missing arm joint
|
|
// Low chance out of combat (idle state)
|
|
//============================================================================
|
|
void() seek_sparks_fade1 = [0, seek_sparks_fade2] {self.alpha = 0.8; self.nextthink = time + 0.05;};
|
|
void() seek_sparks_fade2 = [0, seek_sparks_fade3] {self.alpha = 0.6; self.nextthink = time + 0.05;};
|
|
void() seek_sparks_fade3 = [0, seek_sparks_fade4] {self.alpha = 0.4; self.nextthink = time + 0.05;};
|
|
void() seek_sparks_fade4 = [0, SUB_Remove] {self.alpha = 0.2; self.nextthink = time + 0.05;};
|
|
|
|
void() seek_spark =
|
|
{
|
|
local float loopvar;
|
|
local entity spark;
|
|
|
|
if (self.state == SEEKER_ARMS_BOTH) return;
|
|
// Less chance of sparks when not in combat
|
|
if (random() > 0.1 && !self.enemy) return;
|
|
if (random() > 0.4 && self.enemy) return;
|
|
|
|
// Light flash and sparky sound
|
|
self.effects = self.effects | EF_MUZZLEFLASH;
|
|
sound (self, CHAN_BODY, self.noise3, 1, ATTN_STATIC);
|
|
makevectors (self.angles);
|
|
|
|
// Change origin and spark direction for each arm
|
|
if (self.state == SEEKER_ARMS_LEFT) {
|
|
self.pos1 = self.origin + attack_vector('-4 -30 48');
|
|
self.pos2 = -v_right*25;
|
|
}
|
|
else {
|
|
self.pos1 = self.origin + attack_vector('-4 30 48');
|
|
self.pos2 = v_right*25;
|
|
}
|
|
|
|
// Work out how many sparks to spawn
|
|
loopvar = rint(5 + random()*5);
|
|
while (loopvar > 0) {
|
|
spark = spawn();
|
|
spark.classtype = CT_TEMPSPARK;
|
|
spark.classgroup = CG_TEMPENT;
|
|
spark.owner = self;
|
|
spark.movetype = MOVETYPE_BOUNCE;
|
|
spark.solid = SOLID_TRIGGER;
|
|
setmodel (spark, self.noise2);
|
|
setorigin (spark, self.pos1);
|
|
setsize (spark, VEC_ORIGIN, VEC_ORIGIN);
|
|
spark.skin = rint(random()*3);
|
|
spark.gravity = 0.3;
|
|
spark.velocity = vecrand(0,25,TRUE);
|
|
spark.velocity = spark.velocity + self.pos2;
|
|
spark.avelocity = '300 300 300';
|
|
spark.nextthink = time + 0.5 + 1.5*random();
|
|
spark.think = seek_sparks_fade1;
|
|
|
|
// If DP engine active remove particle shadow
|
|
if (engine == ENG_DPEXT) spark.effects = spark.effects + EF_NOSHADOW;
|
|
|
|
// Keep looping for more sparks
|
|
loopvar = loopvar - 1;
|
|
}
|
|
};
|
|
|
|
//============================================================================
|
|
// Attachment management (create, finish and delete)
|
|
//============================================================================
|
|
void() seek_create_attachment =
|
|
{
|
|
// Are the attachments setup yet?
|
|
if (!self.attachment) {
|
|
self.attachment = spawn();
|
|
self.attachment.owner = self;
|
|
self.attachment.classtype = CT_ATTACHMENT;
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() seek_finish_attachment =
|
|
{
|
|
if (self.attachment) {
|
|
setmodel(self.attachment, "");
|
|
self.attachment.state = STATE_OFF;
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() seek_remove_attachment =
|
|
{
|
|
if (self.attachment) {
|
|
self.attachment.think = SUB_Remove;
|
|
self.attachment.nextthink = time + 0.1;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
void() seek_setup_attachment =
|
|
{
|
|
// Check if attachment has been setup yet
|
|
if (!self.attachment) seek_create_attachment();
|
|
self.attachment.state = STATE_ON;
|
|
setmodel(self.attachment, self.weaponglow);
|
|
self.attachment.skin = 0;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
void() seek_update_attachment =
|
|
{
|
|
setorigin(self.attachment, self.origin);
|
|
self.attachment.angles = self.angles;
|
|
self.attachment.frame = self.frame;
|
|
self.attachment.alpha = 0.3 + random()*0.7;
|
|
};
|
|
|
|
//============================================================================
|
|
void() seek_standframe =
|
|
{
|
|
// type of animation state
|
|
self.attack_timer = SEEKER_IDLE;
|
|
|
|
// Check for arm status
|
|
if (self.state == SEEKER_ARMS_LEFT) self.frame = $standL1 + self.walkframe;
|
|
else if (self.state == SEEKER_ARMS_RIGHT) self.frame = $standR1 + self.walkframe;
|
|
else self.frame = $stand1 + self.walkframe;
|
|
|
|
// Beginning of animation block
|
|
if (self.walkframe == 0) monster_idle_sound();
|
|
else if (self.walkframe == 4) seek_spark();
|
|
|
|
// Move frame forward, check for conditions
|
|
self.walkframe = self.walkframe + 1;
|
|
if (self.walkframe > 18) self.walkframe = 0;
|
|
self.nextthink = time + 0.1;
|
|
self.think = seek_standframe;
|
|
|
|
if (self.spawnflags & MON_SEEK_SHIELD) seek_update_attachment();
|
|
else ai_stand();
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() seek_stand = {
|
|
self.walkframe = 0;
|
|
if (self.spawnflags & MON_SEEK_SHIELD) seek_setup_attachment();
|
|
seek_standframe();
|
|
};
|
|
|
|
//============================================================================
|
|
void() seek_walkframe =
|
|
{
|
|
// If dead, no more updates
|
|
if (self.health < 1) return;
|
|
|
|
// type of animation state
|
|
self.attack_timer = SEEKER_WALK;
|
|
|
|
// Check for arm status
|
|
if (self.state == SEEKER_ARMS_LEFT) self.frame = $runL1 + self.walkframe;
|
|
else if (self.state == SEEKER_ARMS_RIGHT) self.frame = $runR1 + self.walkframe;
|
|
else self.frame = $run1 + self.walkframe;
|
|
|
|
// Beginning of animation block
|
|
if (self.walkframe == 0) monster_idle_sound();
|
|
if (self.walkframe == 2 || self.walkframe == 12) monster_footstep(FALSE);
|
|
if (self.walkframe == 4) seek_spark();
|
|
|
|
// Move frame forward, check for conditions
|
|
self.walkframe = self.walkframe + 1;
|
|
if (self.walkframe > 18) self.walkframe = 0;
|
|
self.nextthink = time + 0.1;
|
|
self.think = seek_walkframe;
|
|
|
|
if (self.walkframe == 1) self.distance = 0;
|
|
else if (self.walkframe == 2) self.distance = 5;
|
|
else if (self.walkframe == 3) self.distance = 5;
|
|
else if (self.walkframe == 4) self.distance = 15;
|
|
else if (self.walkframe == 5) self.distance = 5;
|
|
|
|
else if (self.walkframe == 6) self.distance = 7;
|
|
else if (self.walkframe == 7) self.distance = 12;
|
|
else if (self.walkframe == 8) self.distance = 7;
|
|
else if (self.walkframe == 9) self.distance = 5;
|
|
else if (self.walkframe == 10) self.distance = 3;
|
|
|
|
else if (self.walkframe == 11) self.distance = 0;
|
|
else if (self.walkframe == 12) self.distance = 5;
|
|
else if (self.walkframe == 13) self.distance = 5;
|
|
else if (self.walkframe == 14) self.distance = 15;
|
|
else if (self.walkframe == 15) self.distance = 3;
|
|
|
|
else if (self.walkframe == 16) self.distance = 7;
|
|
else if (self.walkframe == 17) self.distance = 12;
|
|
else if (self.walkframe == 18) self.distance = 7;
|
|
else if (self.walkframe == 0) self.distance = 8;
|
|
|
|
if (self.lefty == FALSE) ai_walk(self.distance);
|
|
else ai_run(self.distance*2);
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() seek_walk = { self.walkframe = 0; self.lefty = FALSE; seek_walkframe(); };
|
|
void() seek_run = { self.walkframe = 0; self.lefty = TRUE; seek_walkframe(); };
|
|
|
|
//============================================================================
|
|
// Melee attack
|
|
//============================================================================
|
|
void() seek_punch =
|
|
{
|
|
local float ldmg;
|
|
|
|
if (!self.enemy) return;
|
|
if (self.health < 1) return;
|
|
ai_face (); // Turn towards enemy target
|
|
ai_damagebreakable(50); // Damage any breakables
|
|
if (!ai_checkmelee(MONAI_MELEESEEKER)) return;// Too far away
|
|
|
|
// Melee hit sound
|
|
sound (self, CHAN_WEAPON, "seeker/punch1.wav", 1, ATTN_NORM);
|
|
// Damage 1-60
|
|
ldmg = (random() + random() + random()) * 20;
|
|
if (ldmg < 1) ldmg = 1;
|
|
T_Damage (self.enemy, self, self, ldmg, DAMARMOR);
|
|
SpawnMeatSpray (self, self.enemy, -150);
|
|
SpawnMeatSpray (self, self.enemy, -150);
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() seek_melee1 = [ $punch1, seek_melee2 ] {ai_face();
|
|
self.pain_finished = time + 1; // Block pain animation
|
|
sound (self, CHAN_VOICE, "seeker/melee1.wav", 1, ATTN_NORM);};
|
|
void() seek_melee2 = [ $punch2, seek_melee3 ] {ai_charge(15);};
|
|
void() seek_melee3 = [ $punch3, seek_melee4 ] {ai_charge(20);};
|
|
void() seek_melee4 = [ $punch4, seek_melee5 ] {ai_charge(25);};
|
|
void() seek_melee5 = [ $punch5, seek_melee6 ] {ai_charge(30);};
|
|
void() seek_melee6 = [ $punch6, seek_melee7 ] {ai_charge(30);};
|
|
void() seek_melee7 = [ $punch7, seek_melee8 ] {ai_charge(30);seek_punch();};
|
|
void() seek_melee8 = [ $punch8, seek_melee9 ] {};
|
|
void() seek_melee9 = [ $punch9, seek_melee10 ] {};
|
|
void() seek_melee10 = [ $punch10, seek_run ] {};
|
|
|
|
//============================================================================
|
|
// Range attack - 3 rockets from each arm
|
|
//============================================================================
|
|
void(vector leftofs, vector rightofs) seek_attack =
|
|
{
|
|
local vector org, dir, vec;
|
|
|
|
if (!self.enemy) return;
|
|
if (self.health < 1) return;
|
|
|
|
self.effects = self.effects | EF_MUZZLEFLASH;
|
|
// Setup different speeds for projectiles (rockets/laz0rs)
|
|
if (self.weaponstate == TRUE)
|
|
self.attack_speed = SPEED_SEEKLAZ + (skill * SPEED_SEEKLAZSKILL);
|
|
else
|
|
self.attack_speed = SPEED_SEEKROCK + (skill * SPEED_SEEKROCKSKILL);
|
|
|
|
// turn and face your enemy!
|
|
ai_face();
|
|
makevectors (self.angles);
|
|
|
|
// Does the LEFT arm exist?
|
|
if (self.state == SEEKER_ARMS_BOTH || self.state == SEEKER_ARMS_LEFT) {
|
|
// Fire left arm rocket
|
|
org = self.origin + attack_vector(leftofs);
|
|
// Fire rocket slightly to the LEFT of player
|
|
vec = v_right*(10+random()*10);
|
|
dir = normalize((self.enemy.origin + vec) - org);
|
|
// Double check nothing (world/monsters) is blocking the rocket
|
|
if (visxray(self.enemy, leftofs, '0 0 0', FALSE)) {
|
|
if (self.weaponstate == TRUE) {
|
|
if (skill > SKILL_NORMAL) {
|
|
sound (self, CHAN_WEAPON, SOUND_PLASMA_FIRE, 1, ATTN_NORM);
|
|
launch_plasma(org, dir, CT_MONSEEKER, self.attack_speed);
|
|
}
|
|
else {
|
|
sound (self, CHAN_WEAPON, "enforcer/enfire.wav", 1, ATTN_NORM);
|
|
launch_projectile(org, dir, CT_PROJ_LASER, self.attack_speed);
|
|
}
|
|
}
|
|
else {
|
|
sound (self, CHAN_WEAPON, "seeker/rocket_fire.wav", 1, ATTN_NORM);
|
|
Launch_Missile (org, dir, '0 0 0', CT_PROJ_SEEKER, self.attack_speed);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Does the RIGHT arm exist?
|
|
if (self.state == SEEKER_ARMS_BOTH || self.state == SEEKER_ARMS_RIGHT) {
|
|
org = self.origin + attack_vector(rightofs);
|
|
// Fire rocket slightly to the RIGHT of player
|
|
vec = v_right*(10+random()*10);
|
|
dir = normalize((self.enemy.origin - vec) - org);
|
|
// Double check nothing (world/monsters) is blocking the rocket
|
|
if (visxray(self.enemy, rightofs, '0 0 0', FALSE)) {
|
|
if (self.weaponstate == TRUE) {
|
|
if (skill > SKILL_NORMAL) {
|
|
sound (self, CHAN_WEAPON, SOUND_PLASMA_FIRE, 1, ATTN_NORM);
|
|
launch_plasma(org, dir, CT_PROJ_PLASMA, self.attack_speed);
|
|
}
|
|
else {
|
|
sound (self, CHAN_WEAPON, "enforcer/enfire.wav", 1, ATTN_NORM);
|
|
launch_projectile(org, dir, CT_PROJ_LASER, self.attack_speed);
|
|
}
|
|
}
|
|
else {
|
|
sound (self, CHAN_WEAPON, "seeker/rocket_fire.wav", 1, ATTN_NORM);
|
|
Launch_Missile (org, dir, '0 0 0', CT_PROJ_SEEKER, self.attack_speed);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() seek_fireframe =
|
|
{
|
|
// If dead, no more updates
|
|
if (self.health < 1) return;
|
|
|
|
// type of animation state
|
|
self.attack_timer = SEEKER_FIRE;
|
|
|
|
// Check for arm status
|
|
if (self.state == SEEKER_ARMS_LEFT) self.frame = $fireL1 + self.walkframe;
|
|
else if (self.state == SEEKER_ARMS_RIGHT) self.frame = $fireR1 + self.walkframe;
|
|
else self.frame = $fireB1 + self.walkframe;
|
|
|
|
// Animation trigger events
|
|
if (self.walkframe == 0) {
|
|
// Work out which attack to do? lasers or rockets
|
|
self.enemydist = range_distance(self.enemy, TRUE);
|
|
if (self.enemydist < MONAI_RANGESWSEEKER) {
|
|
// Upgrade lasers to plasma with hard skill+
|
|
if (skill > SKILL_NORMAL)
|
|
sound (self, CHAN_VOICE, "seeker/range_plasma.wav", 1, ATTN_NORM);
|
|
// Lasers at close range ONLY
|
|
else sound (self, CHAN_VOICE, "seeker/range_laser.wav", 1, ATTN_NORM);
|
|
self.weaponstate = TRUE;
|
|
}
|
|
else {
|
|
sound (self, CHAN_VOICE, "seeker/range_rocket.wav", 1, ATTN_NORM);
|
|
self.weaponstate = FALSE;
|
|
}
|
|
}
|
|
else if (self.walkframe == 5) seek_attack('40 33 30','25 -23 25');
|
|
else if (self.walkframe == 7) seek_attack('33 30 28','23 -30 25');
|
|
else if (self.walkframe == 9) seek_attack('33 36 27','35 -26 26');
|
|
else ai_face();
|
|
|
|
// Move frame forward, check for conditions
|
|
self.walkframe = self.walkframe + 1;
|
|
self.nextthink = time + 0.1;
|
|
if (self.walkframe > 10) self.think = seek_run;
|
|
else self.think = seek_fireframe;
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() seek_fire = { self.walkframe = 0; seek_fireframe(); };
|
|
|
|
//============================================================================
|
|
// This robot does not feel pain, just exploding debris
|
|
//============================================================================
|
|
void() seek_arm_explode =
|
|
{
|
|
particle_dust(self.pos2, 10+random()*10, PARTICLE_BURST_YELLOW);
|
|
SpawnExplosion(EXPLODE_BIG, self.pos2, "seeker/explode_major.wav");
|
|
SpawnProjectileSmoke(self.pos2, 150, 50, 150);
|
|
SpawnProjectileSmoke(self.pos2, 150, 50, 150);
|
|
SpawnProjectileSmoke(self.pos2, 150, 50, 150);
|
|
// Throw 2 small metal blob + arm
|
|
self.gib1origin = self.gib2origin = self.pos2;
|
|
ThrowGib(11, 2);
|
|
ThrowGib(12, 1);
|
|
|
|
// Make sure previous function is restored so correct
|
|
// arm animation is setup after one has been destroyed
|
|
if (self.attack_timer == SEEKER_WALK) self.think = seek_walkframe;
|
|
else if (self.attack_timer == SEEKER_FIRE) self.think = seek_fireframe;
|
|
else self.think = self.th_run;
|
|
self.nextthink = time + 0.1;
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void(entity inflictor, entity attacker, float damage) seek_pain =
|
|
{
|
|
// Check all pain conditions and set up what to do next
|
|
monster_pain_check(attacker, damage);
|
|
|
|
// Any pain animation/sound required?
|
|
if (self.pain_check > 0) {
|
|
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);
|
|
|
|
// There are no checks for shadow axe damage (pain_check = 1/2)
|
|
if (self.state == SEEKER_ARMS_BOTH) {
|
|
// Check for the chance to blow one of the arms off
|
|
self.pos1_x = self.health / self.max_health;
|
|
self.pos1_y = skill / 10;
|
|
self.pos1_z = self.pos1_x + self.pos1_y;
|
|
// dprint("Damage ("); dprint(ftos(damage));
|
|
// dprint(") Chance ("); dprint(ftos(self.pos1_z));
|
|
// dprint(")\n");
|
|
|
|
// Check for body damage and total damage done
|
|
// Is there a chance one of the arms will fall off?
|
|
if (damage > 25 && random() > self.pos1_z) {
|
|
if (random() > 0.5) {
|
|
self.state = SEEKER_ARMS_LEFT;
|
|
makevectors (self.angles);
|
|
self.pos2 = self.origin + attack_vector('0 -35 44');
|
|
seek_arm_explode();
|
|
}
|
|
else {
|
|
self.state = SEEKER_ARMS_RIGHT;
|
|
makevectors (self.angles);
|
|
self.pos2 = self.origin + attack_vector('0 35 44');
|
|
seek_arm_explode();
|
|
}
|
|
}
|
|
else {
|
|
// Spawn dust and sprite explosion
|
|
particle_dust(inflictor.origin, 10+random()*10, PARTICLE_BURST_YELLOW);
|
|
SpawnProjectileSmoke(inflictor.origin, 150, 50, 150);
|
|
SpawnProjectileSmoke(inflictor.origin, 150, 50, 150);
|
|
SpawnProjectileSmoke(inflictor.origin, 150, 50, 150);
|
|
}
|
|
}
|
|
else {
|
|
// Once down to one arm, just sparks and dust
|
|
particle_dust(inflictor.origin, 10+random()*10, PARTICLE_BURST_YELLOW);
|
|
SpawnProjectileSmoke(inflictor.origin, 150, 50, 150);
|
|
SpawnProjectileSmoke(inflictor.origin, 150, 50, 150);
|
|
SpawnProjectileSmoke(inflictor.origin, 150, 50, 150);
|
|
}
|
|
}
|
|
};
|
|
|
|
//============================================================================
|
|
void() seek_explode =
|
|
{
|
|
// Hide and Seek
|
|
entity_hide(self);
|
|
|
|
// Regular ID particle explosion
|
|
WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
|
|
WriteByte (MSG_BROADCAST, TE_EXPLOSION);
|
|
WriteCoord (MSG_BROADCAST, self.origin_x);
|
|
WriteCoord (MSG_BROADCAST, self.origin_y);
|
|
WriteCoord (MSG_BROADCAST, self.origin_z);
|
|
T_RadiusDamage (self, self, self.death_dmg, world, DAMAGEALL);
|
|
|
|
// Classic sprite/DP explosion
|
|
SpawnExplosion(EXPLODE_BIG, self.origin, "seeker/explode_major.wav");
|
|
SpawnProjectileSmoke(self.origin, 150, 150, 150);
|
|
SpawnProjectileSmoke(self.origin, 150, 150, 150);
|
|
SpawnProjectileSmoke(self.origin, 150, 150, 150);
|
|
|
|
// Throw a couple of random body pieces
|
|
if (random() < 0.5) ThrowGib(11, 1);
|
|
else ThrowGib(11, 2);
|
|
// Lets throw our arms in the air like we don't care!
|
|
if (self.state != SEEKER_ARMSLESS) ThrowGib(12, 1);
|
|
// Spew random metal1_2 rubble up
|
|
ThrowGib(13, 5 + rint(random()*5));
|
|
ThrowGib(14, 5 + rint(random()*5));
|
|
// If head still on shoulders, throw that as well!
|
|
if (self.state != SEEKER_HEADLESS) ThrowGib(25, 1);
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() seek_dieframe =
|
|
{
|
|
// type of animation state
|
|
self.attack_timer = SEEKER_DIE;
|
|
|
|
if (self.walkframe == 2) self.solid = SOLID_NOT;
|
|
|
|
// Check for arm status
|
|
if (self.state == SEEKER_ARMS_LEFT) self.frame = $deathL1 + self.walkframe;
|
|
else if (self.state == SEEKER_ARMS_RIGHT) self.frame = $deathR1 + self.walkframe;
|
|
else if (self.state == SEEKER_ARMSLESS) self.frame = $deathB1 + self.walkframe;
|
|
else if (self.state == SEEKER_HEADLESS) self.frame = $deathH1 + self.walkframe;
|
|
else self.frame = $death1 + self.walkframe;
|
|
|
|
// Move frame forward, check for conditions
|
|
self.walkframe = self.walkframe + 1;
|
|
if (self.walkframe < 12) {
|
|
self.nextthink = time + 0.1;
|
|
self.think = seek_dieframe;
|
|
}
|
|
else {
|
|
// Explode after a while
|
|
self.nextthink = time + 3 + random()*2;
|
|
self.think = seek_explode;
|
|
}
|
|
|
|
// Spawn some extra smoke trails
|
|
if (self.walkframe > 1 && random() < 0.5) {
|
|
// make sure the origin is coming out of the mech body
|
|
self.pos1 = self.origin + (crandom()*'16 16 0') + (random()*'0 0 24');
|
|
SpawnProjectileSmoke(self.pos1, 200, 250, 100);
|
|
SpawnProjectileSmoke(self.pos1, 200, 250, 100);
|
|
if (random() < 0.5)
|
|
SpawnExplosion(EXPLODE_MED, self.pos1, "seeker/explode_minor.wav");
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() seek_die =
|
|
{
|
|
// Pre-check routine to tidy up extra entities
|
|
monster_death_precheck();
|
|
|
|
// Long death sound with comedy bing at the end!
|
|
sound (self, CHAN_VOICE, "seeker/death1.wav", 1, ATTN_NORM);
|
|
// reset custom origins for body/arm gib parts
|
|
self.gib1origin = self.gib2origin = '0 0 0';
|
|
self.max_health = MON_GIBFOUNTAIN;
|
|
self.health = -100;
|
|
|
|
// Spawn dust and sprite explosion from chest area
|
|
makevectors(self.angles);
|
|
self.pos1 = self.origin + attack_vector('8 0 24');
|
|
particle_dust(self.pos1, 10+random()*10, PARTICLE_BURST_YELLOW);
|
|
SpawnExplosion(EXPLODE_BIG, self.pos1, "seeker/explode_major.wav");
|
|
SpawnProjectileSmoke(self.pos1, 200, 250, 100);
|
|
|
|
// Random chance of head flying off body with both arms!
|
|
if (self.state == SEEKER_ARMS_BOTH && random() > 0.6) {
|
|
self.state = SEEKER_HEADLESS;
|
|
ThrowGib(25, 1);
|
|
}
|
|
// Random chance that remaining left/right arm falls off
|
|
else if (self.state < SEEKER_ARMSLESS && random() > 0.9) {
|
|
self.state = SEEKER_ARMSLESS;
|
|
ThrowGib(12, 1);
|
|
}
|
|
|
|
// Start death sequence
|
|
self.walkframe = 0;
|
|
seek_dieframe ();
|
|
};
|
|
|
|
//============================================================================
|
|
void() seek_shield =
|
|
{
|
|
// Setup particle emitter for shield effect
|
|
self.part_active = PARTICLE_STYLE_FFIELD;
|
|
self.spr_frame = PARTICLE_BURST_BLUE;
|
|
self.part_vol = '24 24 32'; // Bounding box volume
|
|
self.part_limit = 100; // Large amount of particles
|
|
self.part_life = 4; // Longer lifetime
|
|
self.part_ofs = '0 0 32'; // Move up from monster origin
|
|
self.part_vel = '0 0 4'; // Move up from monster origin
|
|
self.part_emitter = spawn_pemitter(self, self, self.part_active, PARTICLE_START_ON);
|
|
seek_stand();
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() seek_wakeup =
|
|
{
|
|
// Check for sudden death!
|
|
if (self.health < 0) return;
|
|
|
|
self.use = SUB_Null; // no re-trigger
|
|
seek_finish_attachment(); // Remove shield + spawnflag
|
|
self.spawnflags = self.spawnflags - (self.spawnflags & MON_SEEK_SHIELD);
|
|
|
|
// Switch off shield sound
|
|
sound (self, CHAN_WEAPON, "ambience/power_off.wav", 1, ATTN_NORM);
|
|
// Switch OFF any particle emitter
|
|
if (self.part_emitter) misc_particle_off(self.part_emitter);
|
|
|
|
// Check for any trigger off events (trigger_once)
|
|
if (self.target2 != "") trigger_strs(self.target2, self);
|
|
self.target2 = "";
|
|
|
|
// Restore all think functions
|
|
self.th_stand = seek_stand;
|
|
self.th_walk = seek_walk;
|
|
self.th_run = seek_run;
|
|
self.th_melee = seek_melee1; // Punch attack
|
|
self.th_missile = seek_fire; // Rockets incoming!
|
|
self.th_pain = seek_pain;
|
|
self.th_die = seek_die;
|
|
self.takedamage = DAMAGE_YES; // Can receive damage
|
|
monster_targets(); // Check for targets
|
|
};
|
|
|
|
/*======================================================================
|
|
QUAKED monster_seeker (1 0 0) (-32 -32 -24) (32 32 64) Ambush
|
|
======================================================================*/
|
|
void() monster_seeker =
|
|
{
|
|
if (deathmatch) { remove(self); return; }
|
|
|
|
self.mdl = "progs/mon_seeker.mdl";
|
|
self.headmdl = "progs/h_seeker.mdl";
|
|
self.weaponglow = "progs/mon_seeker_glow.mdl";
|
|
// 4 poses 0=generic blob, 1=generic square, 2=generic blob2, 3=arm
|
|
self.gib1mdl = "progs/gib_seekchunk.mdl"; // types 0-2
|
|
self.gib2mdl = "progs/gib_seekarm.mdl"; // arm only
|
|
self.gib3mdl = "progs/gib_metal1.mdl"; // Breakable metal
|
|
self.gib4mdl = "progs/gib_metal3.mdl"; // Breakable metal
|
|
|
|
precache_model (self.mdl);
|
|
precache_model (self.headmdl);
|
|
precache_model (self.weaponglow); // Glowing blue shield
|
|
precache_model (self.gib1mdl); // gib chunks
|
|
precache_model (self.gib2mdl); // Arms
|
|
precache_model (self.gib3mdl); // Generic metal1_2
|
|
precache_model (self.gib4mdl); // Generic metal1_2
|
|
self.gib1frame = 2; // 3 frames choose from
|
|
self.gibtype = GIBTYPE_METAL; // Metal impact sounds
|
|
|
|
self.idle_sound = "seeker/idle1.wav";
|
|
self.idle_sound2 = "seeker/idle2.wav";
|
|
precache_sound (self.idle_sound);
|
|
precache_sound (self.idle_sound2);
|
|
|
|
self.pain_sound = "seeker/pain1.wav";
|
|
self.pain_sound2 = "seeker/pain2.wav";
|
|
precache_sound (self.pain_sound);
|
|
precache_sound (self.pain_sound2);
|
|
|
|
precache_sound ("seeker/death1.wav");
|
|
precache_sound ("seeker/explode_minor.wav");
|
|
precache_sound ("seeker/explode_major.wav");
|
|
// Power off sound for shield setup
|
|
precache_sound ("ambience/power_off.wav");
|
|
|
|
// Range attack 1 - rockets
|
|
precache_sound("seeker/range_rocket.wav");
|
|
precache_sound("seeker/rocket_fire.wav");
|
|
precache_sound("seeker/rocket_hit.wav");
|
|
// Range attack 2 - lasers
|
|
precache_model (MODEL_PROJ_LASER);
|
|
precache_sound("seeker/range_laser.wav");
|
|
precache_sound ("enforcer/enfire.wav");
|
|
precache_sound ("enforcer/enfstop.wav");
|
|
// Range attack 3 - plasma
|
|
precache_model (MODEL_PROJ_PLASMA);
|
|
precache_sound ("seeker/range_plasma.wav");
|
|
precache_sound (SOUND_PLASMA_FIRE);
|
|
precache_sound (SOUND_PLASMA_HIT);
|
|
|
|
// Melee attack - giant punch
|
|
precache_sound ("seeker/melee1.wav");
|
|
precache_sound ("seeker/punch1.wav");
|
|
|
|
// Sparking from broken arm
|
|
self.noise2 = "progs/misc_spark.mdl";
|
|
self.noise3 = "misc/spark.wav";
|
|
precache_model (self.noise2);
|
|
precache_sound (self.noise3);
|
|
|
|
self.sight_sound = "seeker/sight1.wav";
|
|
precache_sound (self.sight_sound);
|
|
|
|
// Check for any custom arm setups
|
|
if (self.state < SEEKER_ARMS_LEFT || self.state > SEEKER_ARMS_RIGHT)
|
|
self.state = SEEKER_ARMS_BOTH; // Both arms are active
|
|
|
|
self.solid = SOLID_NOT; // No interaction with world
|
|
self.movetype = MOVETYPE_NONE; // Static item, no movement
|
|
if (self.bboxtype < 1) self.bboxtype = BBOX_GIANT;
|
|
if (self.health < 1) {
|
|
// if spawns with one arm, start with less HP
|
|
if (self.state == SEEKER_ARMS_BOTH) self.health = 500;
|
|
else self.health = 400;
|
|
}
|
|
|
|
self.gibhealth = MON_NEVERGIB; // Cannot be gibbed by weapons
|
|
self.gibbed = FALSE; // Starts in one piece
|
|
self.pain_flinch = 400; // takes alot to pain
|
|
self.pain_timeout = 2; // High pain threshold
|
|
self.blockudeath = TRUE; // no humanoid death sound
|
|
self.pain_longanim = FALSE; // cannot be chopped with shadow axe
|
|
if (self.death_dmg < 1) self.death_dmg = DAMAGE_SEEKER;
|
|
self.bouncegrenade = TRUE; // Grenades bounce
|
|
self.bleedcolour = MON_BCOLOR_WHITE; // Grey dust
|
|
self.poisonous = FALSE; // Robots are not poisonous
|
|
self.deathstring = " was destroyed by a Seeker\n";
|
|
|
|
// Always reset Ammo Resistance to be consistent
|
|
self.resist_shells = self.resist_nails = 0;
|
|
self.resist_rockets = 0.5; self.resist_cells = 0;
|
|
|
|
// Custom feet sounds (specific order)
|
|
self.stepc1 = "seeker/footstep1.wav";
|
|
self.stepc2 = "seeker/footstep2.wav";
|
|
self.stepc3 = "seeker/footstep1.wav";
|
|
self.stepc4 = "seeker/footstep2.wav";
|
|
self.stepc5 = "seeker/footstep3.wav";
|
|
precache_sound (self.stepc1);
|
|
precache_sound (self.stepc2);
|
|
precache_sound (self.stepc3);
|
|
precache_sound (self.stepc4);
|
|
precache_sound (self.stepc5);
|
|
self.steptype = FS_TYPECUSTOM;
|
|
|
|
if (self.spawnflags & MON_SEEK_SHIELD) {
|
|
self.th_stand = seek_shield; // Setup Shield
|
|
self.think1 = seek_wakeup; // Wakeup function when triggered
|
|
}
|
|
else {
|
|
self.th_stand = seek_stand;
|
|
self.th_walk = seek_walk;
|
|
self.th_run = seek_run;
|
|
self.th_melee = seek_melee1; // Punch attack
|
|
self.th_missile = seek_fire; // Rockets incoming!
|
|
self.th_pain = seek_pain;
|
|
self.th_die = seek_die;
|
|
}
|
|
|
|
self.th_checkattack = SeekerCheckAttack;
|
|
self.classtype = CT_MONSEEKER;
|
|
self.classgroup = CG_ROBOT;
|
|
self.classmove = MON_MOVEWALK;
|
|
|
|
monster_start();
|
|
};
|