// ai_subs.qc: subroutines for think frames void() sub_null = {}; void(entity attacker, float damage) sub_pain_null = {}; void() sub_remove = {remove(self);}; /* QuakeEd only writes a single float for angles(bad idea), so up and down are just constant angles. */ void() set_move_dir = { if(self.angles == '0 -1 0') { self.movedir = '0 0 1'; } else if(self.angles == '0 -2 0') { self.movedir = '0 0 -1'; } else { make_vectors(self.angles); self.movedir = v_forward; } self.angles = VEC_ORIGIN; }; /* ================ init_trigger ================ */ void() init_trigger = { // trigger angles are used for one-way touches. An angle of 0 is assumed // to mean no restrictions, so use a yaw of 360 instead. if(self.angles != VEC_ORIGIN) { set_move_dir(); } self.solid = SOLID_TRIGGER; set_model(self, self.model); // set size and link into world self.movetype = MOVETYPE_NONE; self.modelindex = 0; self.model = string_null; }; /* ============= sub_calc_move calculate self.velocity and self.nextthink to reach dest from self.origin traveling at speed =============== */ void(entity ent, vector tdest, float tspeed, void() func) sub_calc_move_ent = { entity stemp; stemp = self; self = ent; sub_calc_move(tdest, tspeed, func); self = stemp; }; void(vector tdest, float tspeed, void() func) sub_calc_move = { vector vdestdelta; float len, traveltime; if(!tspeed) { error_obj("No speed is defined!"); } self.think1 = func; self.finaldest = tdest; self.think = sub_calc_move_done; if(tdest == self.origin) { self.velocity = VEC_ORIGIN; self.nextthink = self.ltime + 0.1; return; } // set destdelta to the vector needed to move vdestdelta = tdest - self.origin; // calculate length of vector len = vec_len(vdestdelta); // divide by speed to get time to reach dest traveltime = len / tspeed; if(traveltime < 0.1) { self.velocity = VEC_ORIGIN; self.nextthink = self.ltime + 0.1; return; } // set nextthink to trigger a think when dest is reached self.nextthink = self.ltime + traveltime; // scale the destdelta vector by the time spent traveling to get velocity self.velocity = vdestdelta * (1 / traveltime); // qcc won't take vec/float }; /* ============ After moving, set origin to exact final destination ============ */ void() sub_calc_move_done = { set_origin(self, self.finaldest); self.velocity = VEC_ORIGIN; self.nextthink = -1; if(self.think1) { self.think1(); } }; /* ============= sub_calc_angle_move calculate self.avelocity and self.nextthink to reach destangle from self.angles rotating The calling function should make sure self.think is valid =============== */ void(entity ent, vector destangle, float tspeed, void() func) sub_calc_angle_moveEnt = { entity stemp; stemp = self; self = ent; sub_calc_angle_move(destangle, tspeed, func); self = stemp; }; void(vector destangle, float tspeed, void() func) sub_calc_angle_move = { vector destdelta; float len, traveltime; if(!tspeed) { error_obj("No speed is defined!"); } // set destdelta to the vector needed to move destdelta = destangle - self.angles; // calculate length of vector len = vec_len(destdelta); // divide by speed to get time to reach dest traveltime = len / tspeed; // set nextthink to trigger a think when dest is reached self.nextthink = self.ltime + traveltime; // scale the destdelta vector by the time spent traveling to get velocity self.avelocity = destdelta * (1 / traveltime); self.think1 = func; self.finalangle = destangle; self.think = sub_calc_angle_move_done; }; /* ============ After rotating, set angle to exact final angle ============ */ void() sub_calc_angle_move_done = { self.angles = self.finalangle; self.avelocity = VEC_ORIGIN; self.nextthink = -1; if(self.think1) { self.think1(); } }; //============================================================================= void() delay_think = { activator = self.enemy; sub_use_targets(); remove(self); }; /* ============================== sub_use_targets the global "activator" should be set to the entity that initiated the firing. If self.delay is set, a DelayedUse entity will be created that will actually do the sub_use_targets after that many seconds have passed. Centerprints any self.message to the activator. Removes all entities with a targetname that match self.killtarget, and removes them, so some events can remove other triggers. Search for(string)targetname in all entities that match(string)self.target and call their .use function ============================== */ void() sub_use_targets = { entity t, stemp, otemp, act; // // check for a delay // if(self.delay) { // create a temp object to fire at a later time t = spawn(); t.classname = "DelayedUse"; t.nextthink = time + self.delay; t.think = delay_think; t.enemy = activator; t.message = self.message; t.killtarget = self.killtarget; t.target = self.target; return; } // // print the message // if(activator.classname == "player" && self.message != string_null) { print_center(activator, self.message); if(!self.noise) { sound(activator, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM); } } // // kill the killtagets // if(self.killtarget) { t = world; do { t = find(t, targetname, self.killtarget); if(!t) { return; } remove(t); } while(1); } // // fire targets // if(self.target) { act = activator; t = world; do { t = find(t, targetname, self.target); if(!t) { return; } stemp = self; otemp = other; self = t; other = stemp; if(self.use != sub_null) { if(self.use) { self.use(); } } self = stemp; other = otemp; activator = act; } while(1); } }; /* in nightmare mode, all attack_finished times become 0 some monsters refire twice automatically */ void(float normal) sub_attack_finished = { self.cnt = 0; // refire count for nightmare if(skill != 3) { self.attack_finished = time + normal; } }; void(void() thinkst) sub_check_refire = { if(skill != 3) { return; } if(self.cnt == 1) { return; } if(!visible(self.enemy)) { return; } self.cnt = 1; self.think = thinkst; };