/* ============================= ewai.qc coded by Michael Rogers a.k.a Xsniper ssj_xsniper@yahoo.com xsniper.virtualave.net Description: This file's holds important new artificial intelligence functions for Eternal War. Things like better dodging code, better chase code, and code to move monsters out of walls when they become stuck. Also contains some other miscellaneous functions. ============================= */ //predefining functions void() bossf1_flipb1; //void() bossf1_block; void() bossf1_atakd1; //summon shadow clones void(float atype) Apprent_StartMissile; //apprentice missile attacks void() CastLightning; //boss lightning attack /* ============================================================= Teleportation and Monster Unsticking Code Below ============================================================= */ /* ==================== monster_check_teleport Check to see if we can teleport Returns 0 if we can't teleport Returns 1 if we can teleport float ttype: variable controlling what type of teleport 0: teleport to his left 1: teleport to his right 2: teleport behind his self.enemy 3: teleport in slow increments infront of him with no sound or fog 4: teleport in slow increments behind him with no sound or fog 5: teleport in slow increments to his left with no sound or fog 6: teleport in slow increments to his right with no sound or fog ==================== */ float(float ttype) monster_check_teleport = { local vector start, end; //set up angles stuff self.v_angle = self.angles; makevectors (self.angles); //we need to check for walls so that we don't teleport inside them //check based on ttype if (ttype == 0) { //check for teleport to his left start = self.origin; end = start - (v_right * 80); } else if (ttype == 1) { //check for teleport to his right start = self.origin; end = start + (v_right * 80); } else if (ttype == 2) { //check for teleport behind his self.enemy start = self.enemy.origin; end = start + (v_forward * 80); } // Do the check traceline (start, end, TRUE, self); // If there is a wall or obstacle then return NOTELEPORT if (trace_fraction != 1.0) return (0); //NOTELEPORT return (1); //TELEPORT }; /* ==================== monster_teleport Teleports monster based on ttype float ttype: variable controlling what type of teleport 0: teleport to his left 1: teleport to his right 2: teleport behind his self.enemy 3: teleport in slow increments infront of him with no sound or fog 4: teleport in slow increments behind him with no sound or fog 5: teleport in slow increments to his left with no sound or fog 6: teleport in slow increments to his right with no sound or fog ==================== */ void(float ttype) monster_teleport = { //teleport based on ttype if (ttype == 0) { //check to see if we can teleport to his left if (monster_check_teleport(0) == 1) { //its time to teleport spawn_tfog (self.origin); //spawn tfog at our old position //change our origin to our new position setorigin (self, self.origin - (v_right * 80)); spawn_tfog (self.origin); //spawn tfog at our new position } } else if (ttype == 1) { //check to see if we can teleport to his right if (monster_check_teleport(1) == 1) { //its time to teleport spawn_tfog (self.origin); //spawn tfog at our old position //change our origin to our new position setorigin (self, self.origin + (v_right * 80)); spawn_tfog (self.origin); //spawn tfog at our new position } } else if (ttype == 2) { //check to see if we can teleport behind his self.enemy if (monster_check_teleport(2) == 1) { //its time to teleport spawn_tfog (self.origin); //spawn tfog at our old position //change our origin to our new position setorigin (self, self.enemy.origin + (v_forward * 80)); spawn_tfog (self.origin); //spawn tfog at our new position } } //for these last 4 types don't check if possible to teleport, just do it ;) else if (ttype == 3) setorigin (self, self.origin + (v_forward * 2)); //teleport 2 units forward else if (ttype == 4) setorigin (self, self.origin - (v_forward * 2)); //teleport 2 units backward else if (ttype == 5) setorigin (self, self.origin - (v_right * 2)); //teleport 2 units left else if (ttype == 6) setorigin (self, self.origin + (v_right * 2)); //teleport 2 units right }; /* ==================== monster_check_stuck Check in direction "d" a maximum of "max" units to see if we are stuck Return 0 if not stuck Return 1 if stuck float d: determines which direction to check in 0: check infront 1: check behind 2: check left 3: check right float max: the max distance to check in "d" direction max also must be an even number divisible by 2. ==================== */ float(float d, float max) monster_check_stuck = { local vector start, end; local float x; //initialize x x = 2; //set up angles stuff self.v_angle = self.angles; makevectors (self.angles); //starting point start = self.origin; //check based on direction if (d == 0) //check infront { end = start + (v_forward * x); //set initial end point //keep checking until we hit something or reach max while (x <= max) { traceline (start, end, TRUE, self); // If there is a wall or obstacle then return STUCK if (trace_fraction != 1.0) return (1); //STUCK //haven't found anything yet so increment x and reset end x = x + 2; end = start + (v_forward * x); } } else if (d == 1) //check behind { end = start - (v_forward * x); //keep checking until we hit something or reach max while (x <= max) { traceline (start, end, TRUE, self); // If there is a wall or obstacle then return STUCK if (trace_fraction != 1.0) return (1); //STUCK //haven't found anything yet so increment x and reset end x = x + 2; end = start - (v_forward * x); } } else if (d == 2) //check left { end = start - (v_right * x); //keep checking until we hit something or reach max while (x <= max) { traceline (start, end, TRUE, self); // If there is a wall or obstacle then return STUCK if (trace_fraction != 1.0) return (1); //STUCK //haven't found anything yet so increment x and reset end x = x + 2; end = start - (v_right * x); } } else if (d == 3) //check right { end = start + (v_right * max); //keep checking until we hit something or reach max while (x <= max) { traceline (start, end, TRUE, self); // If there is a wall or obstacle then return STUCK if (trace_fraction != 1.0) return (1); //STUCK //haven't found anything yet so increment x and reset end x = x + 2; end = start + (v_right * max); } } return (0); //NOTSTUCK }; /* ==================== monster_stuck Because of changes in size of monsters in ew compared to their quake counterparts some of the monsters may be stuck in walls when playing on old quake levels. This function is designed to check if the monster is stuck, and if he is then try to unstick him. float d: determines which direction to check in 0: check infront 1: check behind 2: check left 3: check right float max: the maximum distance to check in "d" direction max also must be an even number divisible by 2. note: d and max are used for monster_check_stuck function above, and was put here for reference purposes. ==================== */ void() monster_stuck = { local float max; max = 40; //check in 4 different directions and if we are stuck in any one of those directions then //teleport quietly in the opposite direction while (monster_check_stuck(0,max) == 1) //check infront max units monster_teleport(4); //teleport backwards quietly in slow increments while (monster_check_stuck(1,max) == 1) //check behind max units monster_teleport(3); //teleport forwards quietly in slow increments while (monster_check_stuck(2,max) == 1) //check left max units monster_teleport(6); //teleport right quietly in slow increments while (monster_check_stuck(3,max) == 1) //check right max units monster_teleport(5); //teleport left quietly in slow increments }; void() th_stuck = { monster_stuck(); self.think = self.th_stand; self.nextthink = time + 0.1 + random(); }; /* ============================================================= Liquid Checking Code Below ============================================================= */ void() check_for_liquid = { local float p, dweller; local entity player; //initialize dweller dweller = 0; //find the player if (self.monflag == "TRUE") player = find(world, classname, "player"); //little hack for bots if (self.classname == "xsniperbot") player = find(world, monflag, "TRUE"); //check for water, slime, or lava makevectors(self.angles); p = pointcontents(self.origin + v_forward*16); if (p != CONTENT_WATER && p != CONTENT_SLIME && p != CONTENT_LAVA) { self.air_finished = time + 5; return; } if (p == CONTENT_WATER && time > self.air_finished) { //check for dwellers if (self.classname == "monster_dweller") dweller = 1; //make the damage come from the player T_Damage (self, player, player, 5); self.pain_finished = time + 2; } if (p == CONTENT_SLIME) { //check for dwellers if (self.classname == "monster_dweller") dweller = 1; T_Damage (self, player, player, 10); //self.pain_finished = time + 1; } if (p == CONTENT_LAVA) { //check for dwellers if (self.classname == "monster_dweller") dweller = 1; T_Damage (self, player, player, 20); //self.pain_finished = time + 0.5; } //kill dwellers instantly if (dweller == 1) T_Damage (self, player, player, 50000); }; /* ============================================================= Dodging Code Below ============================================================= */ /* ================= ai_strafe monster sidesteps to avoid enemy fire ================= */ void(float dist) ai_strafe = { local float ofs; // this is a cool strafing routine if (self.lefty) ofs = 90; else ofs = -90; if (walkmove (self.angles_y + ofs, dist)) { if (ofs == -90) { makevectors(self.angles); self.flags = self.flags - (self.flags & FL_ONGROUND); self.velocity = v_right * self.speed; } else { makevectors(self.angles); self.flags = self.flags - (self.flags & FL_ONGROUND); self.velocity = v_right * self.speed * -1; } return; } self.lefty = 1 - self.lefty; walkmove (self.angles_y - ofs, dist); if (ofs == -90) { makevectors(self.angles); self.flags = self.flags - (self.flags & FL_ONGROUND); self.velocity = v_right * self.speed; } else { makevectors(self.angles); self.flags = self.flags - (self.flags & FL_ONGROUND); self.velocity = v_right * self.speed * -1; } }; // ---------------------- void(float dist) ai_left = // ---------------------- { walkmove(self.angles_y + 90, dist); makevectors(self.angles); self.flags = self.flags - (self.flags & FL_ONGROUND); self.velocity = v_right * self.speed * -1; }; // ---------------------- void(float dist) ai_right = // ---------------------- { walkmove(self.angles_y - 90, dist); makevectors(self.angles); self.flags = self.flags - (self.flags & FL_ONGROUND); self.velocity = v_right * self.speed; }; void(float dist) ai_back_strafe = { //back up a little ai_back(dist); //go either left or right if (random() <= 0.5) ai_left(dist); //go left else ai_right(dist); //go right //back up a little more ai_back(dist); }; void(float dist) ai_forward_strafe = { //move forward a little ai_forward(dist); //go either left or right if (random() <= 0.5) ai_left(dist); //go left else ai_right(dist); //go right //move forward a little more ai_forward(dist); }; void() ai_teleport_strafe = { //pick random way to teleport if (random() <= 0.5) monster_teleport(0); //teleport left else monster_teleport(1); //teleport right monster_stuck(); //see if I'm stuck }; /* ==================== ai_dodge Dodge attacks if being shot at. float dist: variable controlling distance to move .float dtype: variable controlling how to dodge each monster has his own .dtype 0: sidestep left and right 1: back up and to the left or right 2: move forward and to the left or right 3: teleport left and right 4: do nothing ==================== */ void(float dist) ai_dodge = { local float r; local float d; //calculate d d = dist + 10 + (random() * 10); //calculate r based on skill level if (cvar("skill") == 0) r = 0.2; else if (cvar("skill") == 1) r = 0.4; else if (cvar("skill") == 2) r = 0.6; else if (cvar("skill") == 3) r = 0.9; //Chances to dodge an attack are based off of the skill level if (time < self.enemy.attack_finished && random() <= r && visible(self.enemy)) { //now to dodge based on dtype if (self.dtype == 0) //sidestep left and right ai_strafe(d); else if (self.dtype == 1) //back up and then sidestep ai_back_strafe(d); else if (self.dtype == 2) //move forward and then sidestep ai_forward_strafe(d); else if (self.dtype == 3) //teleport to the left or right ai_teleport_strafe(); else if (self.dtype == 4) //don't dodge at all return; } //If we are the boss monster then our dodge type will be 5 //So lets flip backwards, alter time, and teleport behind the player if (time < self.enemy.attack_finished && random() <= 0.7 && infront(self.enemy) && self.dtype == 5) bossf1_flipb1(); }; /* ============================================================= Summoning Code Below ============================================================= */ void() ai_summon = { if (self.classname == "monster_bossf1" || self.classname == "monster_shadowclone") monster_stuck(); //if i'm not supposed to be here then kick me out if (self.classname != "monster_bossf1") return; //random chance of summoning if (random() <= 0.4) { //random chance of attacking while summoning if (random() <= 0.2) { //attack while summoning //apprentice skull blast attack ai_face(); Apprent_StartMissile(1); //lightning attack sound (self, CHAN_WEAPON, "weapons/holy/lightarc.wav", 1, ATTN_NORM); CastLightning(); //summon shadow clones bossf1_atakd1(); } else bossf1_atakd1(); //summon shadow clones } }; /* ============================================================= Boss Level Teleport Code Below ============================================================= */ void() reset_telespot = { local entity telespot; //search for info_teleport_destination 's telespot = find(world, classname, "info_teleport_destination"); while(telespot) { if (telespot.active == 1) telespot.active = 0; //deactivate the teleporter telespot = telespot.chain; } }; void(float spot) boss_teleport = { local entity telespot, player; local float r; local float failed; if (spot == 0) //teleport to random spot in arena { //set up random variable r = random(); //pick spot to teleport to at random if (r <= 0.2) //teleport northarena { telespot = find(world, targetname, "northarena"); if (telespot.active == 0) { //activate this teleporter telespot.active = 1; //its time to teleport spawn_tfog (self.origin); //spawn tfog at our old position //change our origin to our new position setorigin (self, telespot.origin); spawn_tfog (self.origin); //spawn tfog at our new position spawn_tdeath (self.origin, self); //spawn tdeath at our new position } else failed = 1; } else if (r <= 0.4) { telespot = find(world, targetname, "southarena"); if (telespot.active == 0) { //activate this teleporter telespot.active = 1; //its time to teleport spawn_tfog (self.origin); //spawn tfog at our old position //change our origin to our new position setorigin (self, telespot.origin); spawn_tfog (self.origin); //spawn tfog at our new position spawn_tdeath (self.origin, self); //spawn tdeath at our new position } else failed = 1; } else if (r <= 0.6) { telespot = find(world, targetname, "eastarena"); if (telespot.active == 0) { //activate this teleporter telespot.active = 1; //its time to teleport spawn_tfog (self.origin); //spawn tfog at our old position //change our origin to our new position setorigin (self, telespot.origin); spawn_tfog (self.origin); //spawn tfog at our new position spawn_tdeath (self.origin, self); //spawn tdeath at our new position } else failed = 1; } else if (r <= 0.8) { telespot = find(world, targetname, "westarena"); if (telespot.active == 0) { //activate this teleporter telespot.active = 1; //its time to teleport spawn_tfog (self.origin); //spawn tfog at our old position //change our origin to our new position setorigin (self, telespot.origin); spawn_tfog (self.origin); //spawn tfog at our new position spawn_tdeath (self.origin, self); //spawn tdeath at our new position } else failed = 1; } else { telespot = find(world, targetname, "centerarena"); if (telespot.active == 0) { //activate this teleporter telespot.active = 1; //its time to teleport spawn_tfog (self.origin); //spawn tfog at our old position //change our origin to our new position setorigin (self, telespot.origin); spawn_tfog (self.origin); //spawn tfog at our new position spawn_tdeath (self.origin, self); //spawn tdeath at our new position } else failed = 1; } } else if (spot == 1) //teleport to center arena { telespot = find(world, targetname, "centerarena"); //its time to teleport spawn_tfog (self.origin); //spawn tfog at our old position //change our origin to our new position setorigin (self, telespot.origin); spawn_tfog (self.origin); //spawn tfog at our new position spawn_tdeath (self.origin, self); //spawn tdeath at our new position } else if (spot == 2) //teleport to bossdeathview { //Find the player player = find(world, classname, "player"); //Find the spot telespot = find(world, targetname, "bossdeathview"); //its time to teleport spawn_tfog (player.origin); //spawn tfog at our old position //change our origin to our new position setorigin (player, telespot.origin); spawn_tfog (player.origin); //spawn tfog at our new position spawn_tdeath (player.origin, player); //spawn tdeath at our new position player.angles = telespot.mangle; //player.angles = '0 180 0'; bprint(vtos(player.angles)); bprint("\n"); player.fixangle = 1; } //if we get here then that means all the teleporters were active //so lets teleport behind the player and then reset all the teleporters if (failed == 1) { //teleport behind the player monster_teleport(2); //reset teleporters to inactive state reset_telespot(); } }; /* ============= ai_face_player Find the player and turn towards him ============= */ void() ai_face_player = { local entity player; //search for the player player = find(world, classname, "player"); //turn towards the player self.ideal_yaw = vectoyaw(player.origin - self.origin); ChangeYaw (); }; /* ============================================================= Roaming Code Below This code is only used in Flood Co-op, at all other times monsters use default movement code. ============================================================= */ void() monster_regen_item = { //unflag our owner self.owner.count = 0; //remove ourself remove(self); }; /* ================================ search_for_bots Monsters will search for bots to attack. ================================ */ void() search_for_bots = { local entity found, foe; // bots aren't clients, so we have to check fo them manually // we just see if any of the bots in the entity list are visible if (self.enemy) return; found = world; foe = find(world, classname, "xsniperbot"); while(foe) { if (visible(foe) && foe.health > 0) found = foe; foe = find(foe, classname, "xsniperbot"); } if (found != world) { self.enemy = found; self.goalentity = found; self.think = self.th_run; self.nextthink = time + 0.1; } }; /* ================================= monster_search_for_items Have the monster look around for items. This is one way to get him moving around a level. ================================= */ void() monster_search_for_items = { local entity item; if (time > self.search_time) self.goalentity = world; if (self.goalentity) return; item = findradius(self.origin, 1500); while(item) { if ( (item.flags & FL_ITEM) && visible(item) && item.count != 1) { self.goalentity = item; self.search_time = time + 30; } item = item.chain; } }; /* =========================== monster_grab_items When monsters reach an item they will grab it, or atleast act like they are grabbing it. =========================== */ void() monster_grab_items = { local entity monster_regen; if (self.goalentity == world) return; if (vlen(self.origin - self.goalentity.origin) <= 70) { //bring regeneration entity into world monster_regen = spawn(); //put it where self.goalentity is setorigin(monster_regen, self.goalentity.origin); //make our owner be the goalentity monster_regen.owner = self.goalentity; //other stuff monster_regen.solid = SOLID_NOT; setmodel(monster_regen,""); //flag the goalentity so we don't try to pick it up anymore self.goalentity.count = 1; //set up our thoughts monster_regen.nextthink = time + 20; monster_regen.think = monster_regen_item; self.goalentity = world; } };