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

925 lines
35 KiB
Plaintext

/*==============================================================================
SKULL WIZARD (Hexen2 model by Raven Software)
==============================================================================*/
// Fall backwards, vanish and robes fall down
$frame death1 death2 death3 death4 death5 death6 death7 death8
$frame death9 death10 death11 death12 death13 death14 death15
// Summon minion
$frame summon1 summon2 summon3 summon4 summon5 summon6 summon7 summon8
$frame summon9 summon10 summon11 summon12 summon13 summon14 summon15 summon16
$frame summon17 summon18 summon19 summon20 summon21 summon22 summon23 summon24
$frame summon25 summon26 summon27 summon28 summon29 summon30
// Pain A
$frame painA1 painA2 painA3 painA4 painA5 painA6 painA7 painA8
$frame painA9 painA10 painA11 painA12
// Transition - Move from upright to crouch
$frame transA1 transA2 transA3 transA4 transA5 transA6 transA7 transA8
$frame transA9 transA10 transA11 transA12
// Cast fireball spell (frame 12 - fire)
$frame spellA1 spellA2 spellA3 spellA4 spellA5 spellA6 spellA7 spellA8
$frame spellA9 spellA10 spellA11 spellA12 spellA13 spellA14 spellA15
// Cast teleport spell (touching book)
$frame spellB1 spellB2 spellB3 spellB4 spellB5 spellB6 spellB7 spellB8
$frame spellB9 spellB10 spellB11 spellB12 spellB13 spellB14 spellB15
// Transition - Move from crouch to upright
$frame transB1 transB2 transB3 transB4 transB5 transB6 transB7
// Default stand - breathing
$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8
$frame stand9 stand10 stand11 stand12 stand13 stand14 stand15 stand16
$frame stand17 stand18 stand19
// Default walk - very slow
$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8
$frame walk9 walk10 walk11 walk12 walk13 walk14 walk15 walk16
$frame walk17 walk18 walk19 walk20 walk21 walk22 walk23 walk24
// Teleport
$frame tele1 tele2 tele3 tele4 tele5 tele6 tele7 tele8 tele9
float SKULLW_STAND = 1;
float SKULLW_WALK = 2;
float SKULLW_HEIGHT = 96;
float SKULLW_DISTMIN = 256;
float SKULLW_DISTMAX = 512;
//============================================================================
void() skullw_stand1 =[ $stand1, skullw_stand2 ] {
self.idlebusy=SKULLW_STAND; monster_idle_sound();ai_stand();};
void() skullw_stand2 =[ $stand18, skullw_stand3 ] {ai_stand();};
void() skullw_stand3 =[ $stand16, skullw_stand4 ] {ai_stand();};
void() skullw_stand4 =[ $stand14, skullw_stand5 ] {ai_stand();};
void() skullw_stand5 =[ $stand12, skullw_stand6 ] {ai_stand();};
void() skullw_stand6 =[ $stand3, skullw_stand7 ] {ai_stand();};
void() skullw_stand7 =[ $stand5, skullw_stand8 ] {ai_stand();};
void() skullw_stand8 =[ $stand7, skullw_stand9 ] {ai_stand();};
void() skullw_stand9 =[ $stand9, skullw_stand10 ] {ai_stand();};
void() skullw_stand10 =[ $stand11, skullw_stand11 ] {ai_stand();};
void() skullw_stand11 =[ $stand13, skullw_stand12 ] {ai_stand();};
void() skullw_stand12 =[ $stand15, skullw_stand13 ] {ai_stand();};
void() skullw_stand13 =[ $stand17, skullw_stand14 ] {ai_stand();};
void() skullw_stand14 =[ $stand19, skullw_stand1 ] {ai_stand();};
//============================================================================
void() skullw_walk1 =[ $walk1, skullw_walk2 ] {monster_footstep(FALSE);
self.idlebusy=SKULLW_WALK; monster_idle_sound();ai_walk(2);};
void() skullw_walk2 =[ $walk3, skullw_walk3 ] {ai_walk(5);};
void() skullw_walk3 =[ $walk4, skullw_walk4 ] {ai_walk(5);};
void() skullw_walk4 =[ $walk5, skullw_walk5 ] {ai_walk(4);};
void() skullw_walk5 =[ $walk7, skullw_walk6 ] {ai_walk(4);};
void() skullw_walk6 =[ $walk8, skullw_walk7 ] {ai_walk(2);};
void() skullw_walk7 =[ $walk9, skullw_walk8 ] {ai_walk(2);};
void() skullw_walk8 =[ $walk11, skullw_walk9 ] {ai_walk(2);};
void() skullw_walk9 =[ $walk12, skullw_walk10 ] {monster_footstep(FALSE); ai_walk(2);};
void() skullw_walk10 =[ $walk13, skullw_walk11 ] {ai_walk(5);};
void() skullw_walk11 =[ $walk15, skullw_walk12 ] {ai_walk(5);};
void() skullw_walk12 =[ $walk16, skullw_walk13 ] {ai_walk(4);};
void() skullw_walk13 =[ $walk17, skullw_walk14 ] {ai_walk(4);};
void() skullw_walk14 =[ $walk19, skullw_walk15 ] {ai_walk(2);};
void() skullw_walk15 =[ $walk20, skullw_walk16 ] {ai_walk(2);};
void() skullw_walk16 =[ $walk22, skullw_walk1 ] {ai_walk(3);};
//============================================================================
// WALK -> STAND
void() skullw_transA1 =[ $transA5, skullw_transA2 ] {};
void() skullw_transA2 =[ $transA7, skullw_transA3 ] {};
void() skullw_transA3 =[ $transA9, skullw_transA4 ] {};
void() skullw_transA4 =[ $transA11, skullw_stand1 ] {};
// STAND -> WALK
void() skullw_transB1 =[ $transB3, skullw_transB2 ] {};
void() skullw_transB2 =[ $transB5, skullw_transB3 ] {};
void() skullw_transB3 =[ $transB7, skullw_walk1 ] {};
//----------------------------------------------------------------------
void() skullw_stand =
{
if (self.idlebusy == SKULLW_WALK) skullw_transA1();
else skullw_stand1();
};
//----------------------------------------------------------------------
void() skullw_walk =
{
if (self.idlebusy == SKULLW_STAND) skullw_transB1();
else skullw_walk1();
};
//============================================================================
void() skullw_run1 =[ $stand1, skullw_run2 ] {ai_face();
monster_idle_sound();ai_run(0);};
void() skullw_run2 =[ $stand18, skullw_run3 ] {ai_face();ai_run(0);};
void() skullw_run3 =[ $stand16, skullw_run4 ] {ai_run(0);};
void() skullw_run4 =[ $stand14, skullw_run5 ] {ai_run(0);};
void() skullw_run5 =[ $stand12, skullw_run6 ] {ai_face();ai_run(0);};
void() skullw_run6 =[ $stand3, skullw_run7 ] {ai_face();ai_run(0);};
void() skullw_run7 =[ $stand5, skullw_run8 ] {ai_run(0);};
void() skullw_run8 =[ $stand7, skullw_run9 ] {ai_run(0);};
void() skullw_run9 =[ $stand9, skullw_run10] {ai_face();ai_run(0);};
void() skullw_run10=[ $stand11, skullw_run11] {ai_face();ai_run(0);};
void() skullw_run11=[ $stand13, skullw_run12] {ai_run(0);};
void() skullw_run12=[ $stand15, skullw_run13] {ai_run(0);};
void() skullw_run13=[ $stand17, skullw_run14] {ai_run(0);};
void() skullw_run14=[ $stand19, skullw_run1 ] {ai_run(0);};
//============================================================================
// MELEE - Teleportation
//============================================================================
void() skullw_tfin1 =[ $tele9, skullw_tfin2 ] {
self.velocity = '0 0 0';
// Always phase in facing the enemy
self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);
self.angles_y = self.ideal_yaw;
sound (self, CHAN_AUTO, "skullwiz/blinkin.wav", 1, ATTN_NORM);
};
void() skullw_tfin2 =[ $tele7, skullw_tfin3 ] {ai_face();};
void() skullw_tfin3 =[ $tele5, skullw_tfin4 ] {ai_face();};
void() skullw_tfin4 =[ $tele3, skullw_tfin5 ] {ai_face();};
void() skullw_tfin5 =[ $tele1, skullw_run1 ] {ai_face();
self.bodyphased = MONAI_SKULLWSOLID;
self.takedamage = DAMAGE_AIM;
};
//----------------------------------------------------------------------
// Find new teleport location
//----------------------------------------------------------------------
void() skullw_blink = {
local float dist, edist, blinkfinished, blinkcount;
local vector org, org_ofs, angle_vec;
blinkfinished = TRUE; blinkcount = 0;
org = angle_vec = org_ofs = '0 0 0';
org_ofs_z = self.height;
self.oldorigin = self.origin;
// Trace upwards to find ceiling or maximum teleport height
traceline(self.origin, self.origin+org_ofs, TRUE, self);
org_ofs = trace_endpos;
// Loop around for possible locations
while(blinkfinished) {
blinkcount = blinkcount + 1; // next loop
angle_vec_y = random() * 360; // Random direction
makevectors(angle_vec); // make vector
// Generate random angle and trace teleport direction
dist = self.distmin + (random() * self.distmax);
org = org_ofs + (v_forward * dist);
traceline(org_ofs, org, TRUE, self);
// Check distance is far enough away from enemy
// Move away from any walls (traceline is surface contact)
dist = vlen(trace_endpos - org_ofs) - 32;
org = org_ofs + (v_forward * dist);
edist = vlen(self.enemy.origin - org);
// Is the enemy far enough away?
if (edist > MONAI_MELEESKULLW) {
// Trace down to floor below new point
traceline(org, org - '0 0 512', TRUE, self);
org = trace_endpos + self.view_ofs;
// The teleport fuction is working with a radius command
// and this will ignore walls and just find any position
// in all directions. This can lead to the skull wizard
// teleporting behind walls or other parts of the map!
// This can be prevent in two ways:
// 1) linesight between skull wizard and player
// - can leads to less interesting locations (always insight)
// 2) linesight between new location and old
// - This allows skull wizard to teleport around corner
// and still be within the same area!
//
// Trace through monsters and check trace fraction
traceline(org, self.origin, TRUE, self);
if (trace_fraction == 1) blinkfinished = FALSE;
}
// Been looping too long, back to original location
if (blinkcount > 10) {
blinkfinished = FALSE;
org = self.origin;
}
}
// Move to new location
self.solid = SOLID_SLIDEBOX; // Standard monster movement
setmodel(self, self.mdl); // Setup model
setsize (self, self.bbmins, self.bbmaxs); // Restore BB size
setorigin(self, org); // move to new location
// Test if new location is solid?
if (!walkmove (0, 0)) {
self.solid = SOLID_NOT; // No world interaction yet
setmodel(self,""); // Turn off model
setorigin(self, self.oldorigin); // restore original location
self.nextthink = time + 1; // Keep checking
self.think = skullw_blink;
}
else skullw_tfin1();
};
//----------------------------------------------------------------------
// Cycle around teleport chain lists instead
//----------------------------------------------------------------------
void() skullw_waitforlink =
{
self.nextthink = time + 0.5 + random();
self.think = skullw_waitforlink;
setorigin(self, self.movelast.origin); // move to new location
self.enemydist = range_distance(self.enemy, FALSE);
// Is the enemy too close or dead?
if (self.enemydist > self.movelast.distance || self.enemy.health < 1) {
// Move to new location
self.solid = SOLID_SLIDEBOX; // Standard monster movement
setmodel(self, self.mdl); // Setup model
setsize (self, self.bbmins, self.bbmaxs); // Restore BB size
// Test if new location is solid?
if (!walkmove (0, 0)) {
self.solid = SOLID_NOT; // No world interaction yet
setmodel(self,""); // Turn off model
}
else skullw_tfin1();
}
};
//----------------------------------------------------------------------
void() skullw_checkblinklist = {
// Does the blink list exist already?
if (self.movelast.classtype != CT_SKULLTELEPORT) {
self.movelast = find(world,targetname,self.target2);
// Does the entity chain exist?
if (self.movelast.classtype == CT_SKULLTELEPORT)
skullw_waitforlink();
else {
// target2 is not valid, do regular teleport
self.target2 = "";
skullw_blink();
}
}
else {
// Move forward in chain and try next spawn location
self.movelast = self.movelast.enemy;
skullw_waitforlink();
}
};
//----------------------------------------------------------------------
// touch book and phase out
//----------------------------------------------------------------------
void() skullw_tele1 =[ $spellB3, skullw_tele2 ] {
self.pain_finished = time + 1; // no pain during teleportation
if (random() < 0.5) sound (self, CHAN_VOICE, "skullwiz/blinkspk1.wav", 1, ATTN_NORM);
else sound (self, CHAN_VOICE, "skullwiz/blinkspk2.wav", 1, ATTN_NORM);
};
void() skullw_tele2 =[ $spellB5, skullw_tele3 ] {};
void() skullw_tele3 =[ $spellB7, skullw_tele4 ] {};
void() skullw_tele4 =[ $spellB9, skullw_tele6 ] {};
void() skullw_tele6 =[ $spellB13, skullw_tele8 ] {};
void() skullw_tele8 =[ $tele1, skullw_tele9 ] {
self.takedamage = DAMAGE_NO; // No damage or impact, phasing out
self.solid = SOLID_NOT;
self.bodyphased = MONAI_SKULLWINVIS;
particle_explode(self.origin, 100, 1, self.part_style, PARTICLE_BURST_SKULLUP);
sound (self, CHAN_VOICE, "skullwiz/blinkout.wav", 1, ATTN_NORM);
};
void() skullw_tele9 =[ $tele3, skullw_tele10 ] {};
void() skullw_tele10=[ $tele5, skullw_tele11 ] {};
void() skullw_tele11=[ $tele7, skullw_tele12 ] {};
void() skullw_tele12=[ $tele9, skullw_tele13 ] {};
void() skullw_tele13 = {
setmodel(self,"");
// Check if a chain list has been defined
if (self.target2 != "") skullw_checkblinklist();
else skullw_blink();
};
//============================================================================
// Rocket Skull Attack (Regular)
//============================================================================
void(vector orgofs) skullw_fire =
{
local vector org, dir;
ai_face();
self.effects = self.effects | EF_MUZZLEFLASH;
if (random() < 0.5) sound (self, CHAN_WEAPON, "skullwiz/skull1.wav", 1, ATTN_NORM);
else sound (self, CHAN_WEAPON, "skullwiz/skull2.wav", 1, ATTN_NORM);
makevectors (self.angles);
org = self.origin + attack_vector(orgofs);
dir = normalize(self.enemy.origin - org);
self.attack_speed = SPEED_SWIZMISSILE + (skill * SPEED_SWIZSKILL);
Launch_Missile (org, dir, '0 0 0', CT_PROJ_SKULLW, self.attack_speed);
};
//----------------------------------------------------------------------
void() skullw_attack1 =[ $spellA3, skullw_attack2 ] {
sound (self, CHAN_VOICE, "skullwiz/attack.wav", 1, ATTN_NORM);};
void() skullw_attack2 =[ $spellA5, skullw_attack3 ] {ai_face();};
void() skullw_attack3 =[ $spellA7, skullw_attack4 ] {ai_face();};
void() skullw_attack4 =[ $spellA10, skullw_attack5 ] {ai_face();};
void() skullw_attack5 =[ $spellA11, skullw_attack6 ] {ai_face();};
void() skullw_attack6 =[ $spellA12, skullw_attack7 ] {skullw_fire('30 0 16');};
void() skullw_attack7 =[ $spellA13, skullw_attack8 ] {};
void() skullw_attack8 =[ $spellA15, skullw_run1 ] {};
//============================================================================
// Poison Skull Attack (Guardians)
//============================================================================
void() skullw_poison_touch =
{
if (other.solid == SOLID_TRIGGER) return;
if (other.classtype == CT_TEMPSTREAM) return;
if (other.health > 0) return;
if (self.attack_finished > time) return;
self.attack_finished = time + 1;
self.modelindex = 0; // Make sure no model
self.model = "";
self.think = SUB_Remove;
self.nextthink = time + 0.1;
};
//----------------------------------------------------------------------
void() skullw_poison_think =
{
// Randomly drift upwards and slow down forward movement
self.velocity_z = self.velocity_z + 10 + (random() * 20);
self.velocity = self.velocity - (self.velocity * 0.125);
self.frame = self.frame + 1;
if (self.frame > self.count) SUB_Remove();
else self.nextthink = time + 0.1;
};
//----------------------------------------------------------------------
void() skullw_guardian =
{
local float poison_loop;
local vector poison_angles, org, dir;
// Double check the skull wizard has poisonous flag set
if (!self.poisonous) return;
// Remove projectile
entity_remove(self, 1);
// Play explosion sound
sound (self, CHAN_WEAPON, "skullwiz/poison_hit.wav", 1, ATTN_NORM);
// Apply any direct damage and poison effect
if (other.takedamage) {
T_Damage (other, self, self.owner, DAMAGE_SWIZPOISON, DAMARMOR);
if (other.flags & FL_CLIENT) PoisonDeBuff(other);
}
poison_loop = 0;
poison_angles = '0 0 0';
while (poison_loop < 360) {
newmis = spawn();
newmis.classtype = CT_TEMPSTREAM;
newmis.movetype = MOVETYPE_FLY; // Fly, no gravity
newmis.solid = SOLID_TRIGGER;
setmodel(newmis, SBURST_POISON);
org = self.origin;
org_z = org_z + (crandom() * 24);
setorigin(newmis, org);
setsize (newmis, VEC_ORIGIN, VEC_ORIGIN);
poison_angles_y = poison_loop;
makevectors(poison_angles);
dir = vecrand(0,50,FALSE);
dir = dir + (v_forward * (400 + random() * 50));
newmis.velocity = dir;
newmis.count = 6;
newmis.frame = rint(random()*2);
// If DP engine active remove particle shadow
if (engine == ENG_DPEXT) newmis.effects = newmis.effects + EF_NOSHADOW;
newmis.think = skullw_poison_think;
newmis.touch = skullw_poison_touch;
newmis.nextthink = time + 0.1;
poison_loop = poison_loop + rint(random()*15);
}
};
//============================================================================
// SUMMON MINION - Purple magic ball
//============================================================================
void() skullw_create_attachment =
{
// Are the attachments setup yet?
if (!self.attachment) {
self.attachment = spawn();
self.attachment.owner = self;
self.attachment.classtype = CT_ATTACHMENT;
self.attachment.mdl = MODEL_PROJ_SWBALL;
}
};
//----------------------------------------------------------------------
void() skullw_finish_attachment =
{
if (self.attachment) {
setmodel(self.attachment, "");
self.attachment.state = STATE_OFF;
}
};
//----------------------------------------------------------------------
void() skullw_remove_attachment =
{
if (self.attachment) {
self.attachment.think = SUB_Remove;
self.attachment.nextthink = time + 0.1;
}
};
//----------------------------------------------------------------------
// Display a purple ball in the hand of the skull wizard
//----------------------------------------------------------------------
void(vector orgofs, float dbframe) skullw_sumball =
{
local vector org;
// Frame 0 is start of the sequence (move everything into place)
if (dbframe == 0) {
self.attachment.state = STATE_ON;
setorigin(self.attachment, self.pos2);
setmodel(self.attachment, self.attachment.mdl);
setsize (self.attachment, VEC_ORIGIN, VEC_ORIGIN);
self.attachment.movetype = MOVETYPE_NONE;
self.attachment.solid = SOLID_NOT;
self.attachment.skin = 0;
self.attachment.alpha = 0.85;
}
// Generate attachment in hand (left)
makevectors(self.angles);
org = self.origin + attack_vector(orgofs);
setorigin(self.attachment, org);
self.attachment.angles_y = rint(random()*359);
self.attachment.frame = dbframe;
// Create particles inside of skull wizard robes floating up
// once lost soul is spawning, then switch particle location
if (self.aflag == TRUE) particle_explode(self.pos2, 3, 1, self.part_style, PARTICLE_BURST_MINOTAUR);
// Need to cut down on particles, too much visual noise
// else particle_debuff(self.origin, 16, rint(4+random()*4), self.part_style);
};
//----------------------------------------------------------------------
// Raise hand up to the sky and spawn purple particles from body
// Always check if there is space to spawn a lost soul first
// Block monster pain function so that the summons is completed
//----------------------------------------------------------------------
void() skullw_summon1 =[ $summon1, skullw_summon2 ] {
ai_face();self.pain_finished = time + 2;};
void() skullw_summon2 =[ $summon3, skullw_summon3 ] {
ai_face();skullw_sumball('22 -2 6',0);};
void() skullw_summon3 =[ $summon5, skullw_summon4 ] {
ai_face(); skullw_sumball('23 -3 7',1);
sound (self, CHAN_BODY, "skullwiz/summon.wav", 1, ATTN_NORM);
/* The touch trigger is created early (4 frames) so its gets
// a chance for a client to touch its bounding box
// This is a giant trigger area to catch speedy players
makevectors(self.angles);
self.pos1 = self.origin + self.view_ofs + v_forward*64 + v_up*16;
setup_minionspace(self.pos1, '-32 -32 -32', '32 32 32'); */
};
void() skullw_summon4 =[ $summon7, skullw_summon5 ] {skullw_sumball('24 -4 9',2);};
void() skullw_summon5 =[ $summon9, skullw_summon6 ] {skullw_sumball('26 -4 10',3);};
void() skullw_summon6 =[ $summon11, skullw_summon7 ] {skullw_sumball('26 -4 12',3);};
void() skullw_summon7 =[ $summon13, skullw_summon8 ] {
// Find out if there is enough space to spawn a lost soul
// This is really a tricky thing to test for ...
// * pointcontents only checks against world space
// * walkmove does not work properly on the lower plane face
// * touch triggers only work when something touches not inside
// * findradius will find a lot of entities depending on radius
// Touch trigger version
//self.aflag = test_minionspace();
// Findradius version
makevectors(self.angles);
self.pos1 = self.origin + self.view_ofs + v_forward*64 + v_up*16;
self.aflag = find_minionspace(self.pos1);
// If the spawn locaiton all clear, spawn something!
if (self.aflag == TRUE) {
minion_lostsoul(self.pos1, self.enemy);
self.pos2 = self.pos1 - '0 0 16'; // Where to spawn particles
skullw_sumball('26 -4 14',4);
}
};
void() skullw_summon8 =[ $summon15, skullw_summon9 ] {skullw_sumball('26 -4 16',4);};
void() skullw_summon9 =[ $summon17, skullw_summon10 ] {skullw_sumball('26 -4 19',4);};
void() skullw_summon10 =[ $summon19, skullw_summon11 ] {skullw_sumball('26 -5 23',4);};
void() skullw_summon11 =[ $summon21, skullw_summon12 ] {skullw_sumball('26 -6 30',3);};
void() skullw_summon12 =[ $summon23, skullw_summon13 ] {skullw_sumball('19 -7 32',1);};
void() skullw_summon13 =[ $summon25, skullw_summon14 ] {skullw_finish_attachment();
if (self.aflag == TRUE) particle_explode(self.pos1, 15, 2, self.part_style, PARTICLE_BURST_MINOTAUR);};
void() skullw_summon14 =[ $summon27, skullw_summon15 ] {ai_face();};
void() skullw_summon15 =[ $summon29, skullw_run1 ] {
ai_face();SUB_AttackFinished (1 + 2*random());};
//----------------------------------------------------------------------
// Both type of skull wizards go through the same magic function
//----------------------------------------------------------------------
void() skullw_magic =
{
local entity miniondef;
// Make sure the attachments are setup ready
skullw_create_attachment();
if (self.spawnflags & MON_SKULLWIZ_MINIONS && !self.minion_active) {
// Check for minion template first
miniondef = find(world,classname,"monster_skullwizminion");
if (miniondef.classtype == CT_CACHELOSTSOUL) setup_minionsupport();
else {
// If template no available, warn and remove feature
dprint("\b[SKULLWIZ]\b Cannot find minion template!\n");
self.spawnflags = self.spawnflags - MON_SKULLWIZ_MINIONS;
}
}
// Is the target the player or monster?
if (self.enemy.flags & FL_CLIENT) {
if (self.spawnflags & MON_SKULLWIZ_MINIONS) {
// has the lost soul limit been reached?
if (query_minionactive(self) == TRUE) skullw_summon1();
else {
if (self.minion_baseattack > 0 ) skullw_attack1();
else skullw_run1();
}
}
// Red Skull wizards attacks with skull rockets
else skullw_attack1();
}
// Always attack monsters with skull rockets
else skullw_attack1();
};
//============================================================================
void() skullw_phased = {
self.nextthink = time + 0.1; // Keep looking for player
self.think = skullw_phased; // Keep looping
if (random() < 0.02)
particle_explode(self.origin, rint(10+random()*10), 1, self.part_style, PARTICLE_BURST_SKULLUP);
monster_idle_sound(); // High chance of idle sound
ai_stand(); // Keep looking
};
//----------------------------------------------------------------------
void() skullw_wakeup =
{
// Reset all monster state functions back to default
self.th_stand = skullw_stand;
self.th_walk = skullw_walk;
self.th_run = skullw_run1;
self.th_missile = skullw_magic;
self.th_melee = skullw_tele1;
self.th_updmissile = skullw_guardian;
// Temporarily restore model/bound box
self.solid = SOLID_SLIDEBOX;
self.movetype = MOVETYPE_STEP;
setmodel(self, self.mdl);
setsize (self, self.bbmins, self.bbmaxs);
// Can the skull wizard move?
if (!walkmove (0, 0)) {
// Something blocking, teleport elsewhere
self.solid = SOLID_NOT;
setmodel(self,"");
self.nextthink = time + 0.1;
self.think = skullw_blink;
}
else skullw_tfin1();
};
//============================================================================
// Short body recoil (6 frames)
void() skullw_painA1 =[ $painA2, skullw_painA2 ] {};
void() skullw_painA2 =[ $painA4, skullw_painA3 ] {};
void() skullw_painA3 =[ $painA6, skullw_painA4 ] {};
void() skullw_painA4 =[ $painA8, skullw_painA5 ] {};
void() skullw_painA5 =[ $painA10, skullw_painA6 ] {};
void() skullw_painA6 =[ $painA12, skullw_run1 ] {};
// Long expanded out version (10 frames)
void() skullw_painB1 =[ $painA2, skullw_painB3 ] {};
void() skullw_painB3 =[ $painA3, skullw_painB4 ] {};
void() skullw_painB4 =[ $painA4, skullw_painB5 ] {};
void() skullw_painB5 =[ $painA5, skullw_painB6 ] {};
void() skullw_painB6 =[ $painA6, skullw_painB7 ] {};
void() skullw_painB7 =[ $painA7, skullw_painB8 ] {};
void() skullw_painB8 =[ $painA8, skullw_painB9 ] {};
void() skullw_painB9 =[ $painA9, skullw_painB10 ] {};
void() skullw_painB10 =[ $painA10, skullw_painB11 ] {};
void() skullw_painB11 =[ $painA11, skullw_run1 ] {};
//----------------------------------------------------------------------
void(entity inflictor, entity attacker, float damage) skullw_pain =
{
// Cannot feel pain when phased out
if (self.bodyphased == MONAI_SKULLWINVIS) return;
// Check all pain conditions and set up what to do next
monster_pain_check(attacker, damage);
skullw_finish_attachment();
// 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);
if (self.pain_check == 1 || self.pain_check == 2) {
if (random() < 0.85) skullw_painA1(); // classic, body recoil
else skullw_painB1(); // Really long version
}
}
};
//============================================================================
void() skullw_fadeaway =
{
// sound (self, CHAN_BODY, "skullwiz/fadeaway.wav", 1, ATTN_NORM);
// Particle burst upwards as robes fade away
particle_explode(self.origin, 100, 3, self.part_style, PARTICLE_BURST_SKULLUP);
self.think = model_fade;
self.nextthink = time + 0.1;
self.ltime = self.nextthink;
};
//----------------------------------------------------------------------
void() skullw_explode =
{
self.health = MON_NOGIBVELOCITY; // Low gib directional modifier
particle_explode(self.origin, 100, 3, PARTICLE_BURST_WHITE, PARTICLE_BURST_SKULLUP);
self.gib2skin = self.exactskin; // Update book skin
ThrowGib(11,1); ThrowGib(12,1); // Throw head and book
};
//----------------------------------------------------------------------
void() skullw_death1 =[ $death2, skullw_death2 ] {ai_face();};
void() skullw_death2 =[ $death3, skullw_death3 ] {ai_face();};
void() skullw_death3 =[ $death4, skullw_death4 ] {ai_face();self.solid = SOLID_NOT;};
void() skullw_death4 =[ $death5, skullw_death5 ] {ai_face();};
void() skullw_death5 =[ $death6, skullw_death6 ] {skullw_explode();};
void() skullw_death6 =[ $death7, skullw_death7 ] {};
void() skullw_death7 =[ $death8, skullw_death8 ] {};
void() skullw_death8 =[ $death9, skullw_death9 ] {};
void() skullw_death9 =[ $death10, skullw_death10 ] {};
void() skullw_death10 =[ $death11, skullw_death11 ] {};
void() skullw_death11 =[ $death12, skullw_death12 ] {};
void() skullw_death12 =[ $death13, skullw_death13 ] {};
void() skullw_death13 =[ $death14, skullw_death14 ] {};
void() skullw_death14 =[ $death15, skullw_death14] {
// Random timer for robes to fade away
self.think = skullw_fadeaway;
self.nextthink = time + 8 + random() * 8;
};
//----------------------------------------------------------------------
void() skullw_die =
{
// Play fade away/death sound
sound (self, CHAN_VOICE, "skullwiz/death.wav", 1, ATTN_NORM);
// Pre-check routine to tidy up extra entities
monster_death_precheck();
skullw_remove_attachment();
// Special death, Instantly vaporize cloak
// This is really a gib death without the gibs!
if (self.health < -40) {
// Typical Grenade 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);
SpawnExplosion(EXPLODE_MED, self.origin, SOUND_REXP3);
self.health = MON_NOGIBVELOCITY; // Low gib directional modifier
self.gib2skin = self.exactskin; // Update book skin
ThrowGib(11,1); ThrowGib(12,1); // Throw head and book
entity_hide(self);
}
// Regular death, collapse and fade away
else {
// Large particle explosion upwards
particle_explode(self.origin, 100, 3, self.part_style, PARTICLE_BURST_SKULLUP);
skullw_death1();
}
};
/*======================================================================
QUAKED monster_skullwiz (0.5 0 1) (-16 -16 -24) (16 16 40)
======================================================================*/
void() monster_skullwiz =
{
if (deathmatch) { remove(self); return; }
self.mdl = "progs/mon_skullwiz.mdl";
precache_model (self.mdl);
precache_model (MODEL_PROJ_SWSKULL); // explosive flaming skull
precache_model (MODEL_PROJ_SWSKULLP); // explosive poison skull
precache_model (MODEL_PROJ_SWBALL); // Purple minion ball
precache_model (SBURST_POISON); // Poison explosion
self.gib1mdl = "progs/h_skullwiz.mdl"; // Skull head
self.gib2mdl = "progs/w_skullbook.mdl"; // Unique book item
precache_model (self.gib1mdl);
precache_model (self.gib2mdl);
self.idle_sound = "skullwiz/idle1.wav";
self.idle_sound2 = "skullwiz/idle2.wav";
precache_sound (self.idle_sound);
precache_sound (self.idle_sound2);
precache_sound ("skullwiz/death.wav");
self.pain_sound = "skullwiz/pain1.wav";
self.pain_sound2 = "skullwiz/pain2.wav";
precache_sound (self.pain_sound);
precache_sound (self.pain_sound2);
precache_sound ("skullwiz/attack.wav"); // fire spell
precache_sound ("skullwiz/skull1.wav"); // Screaming skull attack
precache_sound ("skullwiz/skull2.wav");
precache_sound ("skullwiz/summon.wav");
precache_sound ("skullwiz/poison_hit.wav"); // Guardian Explosion
precache_sound ("skullwiz/blinkspk1.wav"); // Talk casting blink spell
precache_sound ("skullwiz/blinkspk2.wav");
precache_sound ("skullwiz/blinkout.wav"); // Sound of blink animation
precache_sound ("skullwiz/blinkin.wav");
self.sight_sound = "skullwiz/sight.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_TALL;
if (self.health < 1) self.health = 120;
self.gibhealth = MON_NEVERGIB; // Cannot be gibbed by weapons
self.gibbed = FALSE; // Still in one piece
self.pain_flinch = 50; // Armour strength robes!
self.steptype = FS_TYPEMEDIUM; // Humanoid footsteps
self.pain_longanim = FALSE; // No long pain animation
self.poisonous = FALSE; // Only Guardians can be poisonous
self.deathstring = " was blown apart by a Skull Wizard\n";
// Always reset Ammo Resistance to be consistent
self.resist_shells = self.resist_nails = 0;
self.resist_rockets = self.resist_cells = 0;
// Skull wizards have special animation movement sets
// Need extra parameters for their teleporting ability
// Can be setup to start phased out and surprise the player
self.idlebusy = SKULLW_STAND;
if (self.height == 0) self.height = SKULLW_HEIGHT;
if (self.distmin == 0) self.distmin = SKULLW_DISTMIN;
if (self.distmax == 0) self.distmax = SKULLW_DISTMAX;
if (!self.bodyphased) self.bodyphased = MONAI_SKULLWSOLID;
else self.bodyphased = MONAI_SKULLWINVIS;
self.th_checkattack = SkullWizCheckAttack;
self.th_updmissile = skullw_guardian;
self.th_pain = skullw_pain;
self.th_die = skullw_die;
// Does the skull wizard start phased out?
// Need to intercept idle/combat functions
if (self.bodyphased == MONAI_SKULLWINVIS) {
self.th_stand = skullw_phased;
self.th_walk = skullw_phased;
self.th_run = skullw_wakeup;
self.th_missile = skullw_wakeup;
self.th_melee = skullw_wakeup;
}
else {
self.th_stand = skullw_stand;
self.th_walk = skullw_walk;
self.th_run = skullw_run1;
self.th_missile = skullw_magic;
self.th_melee = skullw_tele1;
}
self.classtype = CT_MONSKULLW;
self.classgroup = CG_WIZARD;
self.classmove = MON_MOVEWALK;
// Special black robes for skull spawner
if (self.spawnflags & MON_SKULLWIZ_MINIONS) {
self.health = self.health * 1.5; // 150% HP
if (!self.exactskin) self.exactskin = 2; // Black robes
}
// Special green robes for rune guardians
else if (self.spawnflags & MON_SKULLWIZ_GUARDIAN) {
self.health = self.health * 2; // 200% HP
if (!self.exactskin) self.exactskin = 3; // Green robes
self.poisonous = TRUE; // Explosive Poison
}
// Make sure the particles match the skin
if (self.exactskin == 1) self.part_style = PARTICLE_BURST_GREEN;
else if (self.exactskin == 2) self.part_style = PARTICLE_BURST_PURPLE;
else if (self.exactskin == 3) self.part_style = PARTICLE_BURST_GREEN;
else if (self.exactskin == 4) self.part_style = PARTICLE_BURST_WHITE;
else self.part_style = PARTICLE_BURST_RED;
monster_start();
};
/*======================================================================
/*QUAKED info_skullwiz_destination (0.6 0 0.8) (-16 -16 -24) (16 16 40) x
Teleport destination for skull wizard ONLY
-------- KEYS --------
targetname : part of a chain group (required)
target : next destination in the chain
distance : Minimum distance enemy needs to be before spawning
-------- SPAWNFLAGS --------
-------- NOTES --------
Teleport destination for skull wizard ONLY
======================================================================*/
void() info_skullwiz_link =
{
local entity currlink, master;
local float loopcondition;
// Teleport destination already been linked
if (self.enemy) return;
// The master entity is the start of the chain
// the currlink is a link in the chain
master = self;
loopcondition = TRUE;
while (loopcondition)
{
// Setup next link to master and find next link
self.enemy = master;
currlink = find(world, targetname, self.target);
// reached end of list?
if (!currlink || currlink == master)
loopcondition = FALSE;
else {
// Move forward in the chain
self.enemy = currlink;
self = currlink;
}
}
};
//----------------------------------------------------------------------
void() info_skullwiz_destination =
{
// All teleportation points need to be part of a chain
// Otherwise the skull Wizard would have to use findradius
// which is expensive if lots of entities are around
if (self.targetname == "" || self.target == "") {
dprint("\b[SKULLWIZ_DEST]\b missing target or targetname\n");
spawn_marker(self.origin, SPNMARK_YELLOW);
remove(self);
return;
}
self.classtype = CT_SKULLTELEPORT;
self.mangle = self.angles;
// Setup minimum player spawn distance
if (self.distance <=0) self.distance = MONAI_MELEESKULLW;
// Setup if the destination is active or not
self.state = FALSE;
// Check for targetname->target loops
if (self.targetname == self.target) {
self.enemy = self;
self.owner = self;
}
else {
// Wait for all targets to spawn
self.think = info_skullwiz_link;
self.nextthink = time + 0.2 + random();
}
};