1 /***********************************************
4 * "The better than roaming AI" *
6 ***********************************************/
10 This program is in the Public Domain. My crack legal
11 team would like to add:
13 RYAN "FRIKAC" SMITH IS PROVIDING THIS SOFTWARE "AS IS"
14 AND MAKES NO WARRANTY, EXPRESS OR IMPLIED, AS TO THE
15 ACCURACY, CAPABILITY, EFFICIENCY, MERCHANTABILITY, OR
16 FUNCTIONING OF THIS SOFTWARE AND/OR DOCUMENTATION. IN
17 NO EVENT WILL RYAN "FRIKAC" SMITH BE LIABLE FOR ANY
18 GENERAL, CONSEQUENTIAL, INDIRECT, INCIDENTAL,
19 EXEMPLARY, OR SPECIAL DAMAGES, EVEN IF RYAN "FRIKAC"
20 SMITH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
21 DAMAGES, IRRESPECTIVE OF THE CAUSE OF SUCH DAMAGES.
23 You accept this software on the condition that you
24 indemnify and hold harmless Ryan "FrikaC" Smith from
25 any and all liability or damages to third parties,
26 including attorney fees, court costs, and other
27 related costs and expenses, arising out of your use
28 of this software irrespective of the cause of said
31 The export from the United States or the subsequent
32 reexport of this software is subject to compliance
33 with United States export control and munitions
34 control restrictions. You agree that in the event you
35 seek to export this software, you assume full
36 responsibility for obtaining all necessary export
37 licenses and approvals and for assuring compliance
38 with applicable reexport restrictions.
40 Any reproduction of this software must contain
41 this notice in its entirety.
48 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
52 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
56 float (entity e1, entity e2) CheckLinked =
58 if ((e1 == e2) || (e2 == world) || (e1 == world))
60 else if (e1.target1 == e2)
62 if (e1.b_aiflags & AI_TELELINK_1)
66 else if (e1.target2 == e2)
68 if (e1.b_aiflags & AI_TELELINK_2)
72 else if (e1.target3 == e2)
74 if (e1.b_aiflags & AI_TELELINK_3)
78 else if (e1.target4 == e2)
80 if (e1.b_aiflags & AI_TELELINK_4)
89 float (entity e1, entity e2) LinkWays =
91 if ((e1 == e2) || (e2 == world) || (e1 == world))
93 else if (CheckLinked(e1, e2))
94 return FALSE; // already linked!!!
96 if (e1.target1 == world)
101 else if (e1.target2 == world)
106 else if (e1.target3 == world)
111 else if (e1.target4 == world)
119 // Link Ways part 2, used only for teleporters
121 float (entity e1, entity e2) TeleLinkWays =
123 if ((e1 == e2) || (e2 == world) || (e1 == world))
125 else if (CheckLinked(e1, e2))
126 return FALSE; // already linked!!!
128 if (e1.target1 == world)
131 e1.b_aiflags = e1.b_aiflags | AI_TELELINK_1;
134 else if (e1.target2 == world)
137 e1.b_aiflags = e1.b_aiflags | AI_TELELINK_2;
140 else if (e1.target3 == world)
143 e1.b_aiflags = e1.b_aiflags | AI_TELELINK_3;
146 else if (e1.target4 == world)
149 e1.b_aiflags = e1.b_aiflags | AI_TELELINK_4;
157 void (entity e1, entity e2) UnlinkWays =
159 if ((e1 == e2) || (e2 == world) || (e1 == world))
161 else if (!CheckLinked(e1, e2))
164 if (e1.target1 == e2)
166 e1.b_aiflags = e1.b_aiflags - (e1.b_aiflags & AI_TELELINK_1);
169 if (e1.target2 == e2)
171 e1.b_aiflags = e1.b_aiflags - (e1.b_aiflags & AI_TELELINK_2);
174 if (e1.target3 == e2)
176 e1.b_aiflags = e1.b_aiflags - (e1.b_aiflags & AI_TELELINK_3);
179 if (e1.target4 == e2)
181 e1.b_aiflags = e1.b_aiflags - (e1.b_aiflags & AI_TELELINK_4);
188 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
192 This is used quite a bit, by many different
193 functions big lag causer
195 Finds the closest, fisible, waypoint to e
197 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
200 entity(entity start) FindWayPoint =
204 local float dst, tdst;
207 org = realorigin(self);
212 dst = vlen(start.origin - org);
222 // real players cut through ignore types
225 if (!(t.b_aiflags & AI_IGNORE_TYPES) || self.ishuman)
227 tdst = vlen(t.origin - org);
243 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
245 Waypoint Spawning Code
247 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
250 entity way_foot; // Ugh. Do I need a foot for this or not?
252 entity(vector org) make_waypoint =
256 point.classname = "waypoint";
257 point.search_time = time; // don't double back for me;
258 point.solid = SOLID_TRIGGER;
259 point.movetype = MOVETYPE_NONE;
261 setorigin(point, org);
263 setsize(point, PL_MIN, PL_MAX);
264 waypoints = waypoints + 1;
272 way_foot._next = point;
273 point._last = way_foot;
277 point.count = waypoints;
278 if (waypoint_mode > WM_LOADED) // editor modes
279 setmodel(point, "progs/s_bubble.spr");
284 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
286 Dynamic Waypoint spawning and linking. Not
287 very good all things considered.
289 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
292 void() DynamicWaypoint =
295 local float dist, dynlink, dynpoint, editor;
297 if (self.teleport_time > self.portal_time)
299 if (!self.flags & FL_WATERJUMP)
304 bot_lost(self.target1, TRUE);
308 self.portal_time = self.teleport_time;
310 // stacking everything on waypoint_mode might've been good for the editor,
311 // but it sucks to beat hell for this code.
314 // convert waypoint_mode to something more usable..
315 if (waypoint_mode > WM_LOADED)
319 if (waypoint_mode == WM_EDITOR_DYNLINK)
321 else if (waypoint_mode == WM_EDITOR_DYNAMIC)
322 dynlink = dynpoint = 1;
326 else if (waypoint_mode == WM_DYNAMIC)
327 dynlink = dynpoint = 1;
329 // if there's nothing for dynamic to do..
335 // for speed sake, I won't have bots dynamic waypoint in coop
340 // don't waypoint in single player
344 else if (self.health <= 0)
348 if (self.current_way)
350 if (pointcontents(self.origin) < -4)
352 if (self.current_way.b_aiflags & AI_BLIND)
353 self.current_way.b_aiflags = self.current_way.b_aiflags | AI_PRECISION;
355 self.current_way.b_aiflags = self.current_way.b_aiflags | AI_BLIND;
359 self.dyn_dest = '0 0 0';
360 self.current_way = world;
365 // you shouldn't be making waypoints mid air
368 if (!((self.flags & FL_ONGROUND) || self.waterlevel == 3))
370 if (self.dyn_flags != 2)
377 // keep from doing the rest of this every frame
378 if (self.dyn_time > time)
380 self.dyn_time = time + 0.2;
382 // display the links for editor mode
385 if (self.current_way)
387 if (self.current_way.target1)
388 DeveloperLightning(self.current_way, self.current_way.target1, self.current_way.b_aiflags & AI_TELELINK_1);
389 if (self.current_way.target2)
390 DeveloperLightning(self.current_way, self.current_way.target2, self.current_way.b_aiflags & AI_TELELINK_2);
391 if (self.current_way.target3)
392 DeveloperLightning(self.current_way, self.current_way.target3, self.current_way.b_aiflags & AI_TELELINK_3);
393 if (self.current_way.target4)
394 DeveloperLightning(self.current_way, self.current_way.target4, self.current_way.b_aiflags & AI_TELELINK_4);
396 if (self.b_aiflags & AI_HOLD_SELECT)
400 t = FindWayPoint(self.current_way);
403 dist = vlen(self.origin - t.origin);
409 if (t != self.current_way)
415 if (wisible(t, self.current_way))
416 LinkWays(t, self.current_way);
418 if (self.dyn_flags == 2)
419 TeleLinkWays(self.current_way, t);
420 else if (wisible(t, self.current_way))
421 LinkWays(self.current_way, t);
425 setmodel(t, "progs/s_light.spr");
426 if (self.current_way)
427 setmodel(self.current_way, "progs/s_bubble.spr");
430 self.current_way = t;
433 self.dyn_dest = self.origin + self.view_ofs;
438 if (frik_recognize_plat(FALSE))
440 if (vlen(trace_ent.velocity) > 0)
444 self.dyn_plat = TRUE;
447 //bprint("on a plat!!!!!\n");
450 self.dyn_plat = FALSE;
453 self.dyn_plat = FALSE;
455 if (self.dyn_flags == 2)
456 self.dyn_dest = self.origin + self.view_ofs;
457 else if (self.dyn_dest == '0 0 0')
458 self.dyn_dest = self.origin + self.view_ofs;
461 t = make_waypoint(self.dyn_dest);
465 if (wisible(t, self.current_way))
466 LinkWays(t, self.current_way);
468 if (self.dyn_flags == 2)
469 TeleLinkWays(self.current_way, t);
470 else if (wisible(t, self.current_way))
471 LinkWays(self.current_way, t);
475 setmodel(t, "progs/s_light.spr");
476 if (self.current_way)
477 setmodel(self.current_way, "progs/s_bubble.spr");
479 self.current_way = t;
482 self.dyn_dest = self.origin + self.view_ofs;
484 if (frik_recognize_plat(FALSE))
486 if (trace_ent.classname == "door")
487 t.b_aiflags = t.b_aiflags | AI_DOORFLAG;
492 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
494 Waypoint Loading from file
496 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
499 void() ClearAllWays =
515 entity(float num) WaypointForNum =
531 void() FixThisWaypoint =
533 self.enemy.target1 = WaypointForNum(self.enemy.b_pants);
534 self.enemy.target2 = WaypointForNum(self.enemy.b_skill);
535 self.enemy.target3 = WaypointForNum(self.enemy.b_shirt);
536 self.enemy.target4 = WaypointForNum(self.enemy.b_frags);
537 self.enemy = self.enemy._next;
538 self.nextthink = time;
539 if (self.enemy == world)
546 void() FixWaypoints =
550 fixer.nextthink = time;
551 fixer.think = FixThisWaypoint;
552 fixer.enemy = way_head;
557 void(entity what) delete_waypoint =
561 if (way_head == what)
562 way_head = what._next;
563 if (way_foot == what)
564 way_foot = what._last;
566 what._last._next = what._next;
568 what._next._last = what._last;
573 t.count = waypoints = waypoints + 1;
574 if (CheckLinked(t, what))
578 if (self.current_way == what)
579 self.current_way = world;
584 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
586 FindRoute & FindThing used by the pathing code
589 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
593 entity(string s) FindThing =
596 local float tdst, dst;
600 t = find (world, classname, s);
603 tdst = vlen(((t.absmin + t.absmax) * 0.5) - self.origin);
609 t = find(t, classname, s);
615 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
617 FindRoute, this is a key function in the
618 pathing. The name is a bit misleading, this
619 code finds the closest waypoint that is part
620 of a route calculated by the begin_route and
621 end_route routines This is a definite path to
624 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
627 entity(entity lastone) FindRoute =
629 // kinda like FindWaypoint, only of this bots route though
630 local entity t, best;
631 local float dst, tdst, flag;
632 flag = ClientBitFlag(self.b_clientno);
638 tdst = vlen(t.origin - self.origin);
639 if ((tdst < dst) && (t.b_sound & flag))
641 if ((lastone == world) || (CheckLinked(lastone, t)))
652 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
654 Route & path table management
656 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
659 void() ClearRouteTable =
661 // cleans up route table
669 t.items = -1; // not in table
674 void() ClearMyRoute =
679 flag = ClientBitFlag(self.b_clientno);
684 t.b_sound = t.b_sound - (t.b_sound & flag);
691 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
695 After the route has been found, mark it with
696 bitflags so the table can be used for a
699 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
703 void(entity this) mark_path =
705 local entity t, oself;
712 t = FindWayPoint(this.current_way);
715 // ugh, better way to find players please!!!
716 if (this.classname != "player")
717 this.current_way = t;
719 if (t.enemy == world)
721 bot_lost(this, FALSE);
722 if (waypoint_mode == WM_DYNAMIC)
723 self.route_failed = TRUE;
727 flag = ClientBitFlag(self.b_clientno);
731 if (t.b_sound & flag)
733 if (t == self.last_way)
735 t.b_sound = t.b_sound | flag;
741 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
745 Calculates the routes. We use thinks to avoid
746 tripping the runaway loop counter
748 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
751 void(entity e2, float b_bit) FollowLink =
755 if (self.b_aiflags & b_bit)
758 dist = vlen(self.origin - e2.origin) + self.items;
760 // check if this is an RJ link
761 if (e2.b_aiflags & AI_SUPER_JUMP)
763 if (!bot_can_rj(route_table))
766 if (e2.b_aiflags & AI_DIFFICULT)
769 dist = dist + random() * 100; // add a little chaos
771 if ((dist < e2.items) || (e2.items == -1))
774 busy_waypoints = busy_waypoints + 1;
777 e2.think = WaypointThink;
783 void() WaypointThink =
787 if (self.items == -1)
790 if (self.b_aiflags & AI_TRACE_TEST)
794 traceline(self.origin, self.target1.origin, TRUE, self);
795 if (trace_fraction == 1)
796 FollowLink(self.target1, AI_TELELINK_1);
800 traceline(self.origin, self.target2.origin, TRUE, self);
801 if (trace_fraction == 1)
802 FollowLink(self.target2, AI_TELELINK_2);
806 traceline(self.origin, self.target3.origin, TRUE, self);
807 if (trace_fraction == 1)
808 FollowLink(self.target3, AI_TELELINK_3);
812 traceline(self.origin, self.target4.origin, TRUE, self);
813 if (trace_fraction == 1)
814 FollowLink(self.target4, AI_TELELINK_4);
820 FollowLink(self.target1, AI_TELELINK_1);
822 FollowLink(self.target2, AI_TELELINK_2);
824 FollowLink(self.target3, AI_TELELINK_3);
826 FollowLink(self.target4, AI_TELELINK_4);
829 busy_waypoints = busy_waypoints - 1;
832 if (busy_waypoints <= 0)
838 bot_get_path(self.target1, FALSE);
840 direct_route = FALSE;
846 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
848 begin_route and bot_get_path
850 PLEASE NOTE: bot_get_path replaces the old
851 calls to begin_route.
853 Routing isn't done all at once now, but in two
854 stages, the bot will calc a route *THEN*
855 choose a target, *THEN* mark a path.
859 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
862 float() begin_route =
864 if (busy_waypoints > 0)
867 if (route_table != world)
869 if (!route_table.ishuman)
871 if (route_table.b_clientno != -1)
878 self.last_way = FindWayPoint(self.current_way);
880 if (self.last_way != world)
882 self.last_way.items = vlen(self.last_way.origin - self.origin);
883 self.last_way.nextthink = time;
884 self.last_way.think = WaypointThink;
885 self.last_way.keys = TRUE;
897 void(entity this, float direct) bot_get_path =
902 if (route_table == self)
904 if (busy_waypoints <= 0)
916 bot_lost(this, FALSE);
922 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
924 BSP/QC Waypoint loading
926 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
931 self.search_time = time;
932 self.solid = SOLID_TRIGGER;
933 self.movetype = MOVETYPE_NONE;
934 setorigin(self, self.origin);
936 setsize(self, PL_MIN, PL_MAX);
937 waypoints = waypoints + 1;
945 way_foot._next = self;
946 self._last = way_foot;
950 self.count = waypoints;
951 waypoint_mode = WM_LOADED;
954 self.think = FixWaypoints; // wait until all bsp loaded points are spawned
955 self.nextthink = time;
959 void(vector org, vector bit1, float bit4, float flargs) make_way =
962 waypoint_mode = WM_LOADED;
963 y = make_waypoint(org);
964 y.b_aiflags = flargs;
971 y.think = FixWaypoints; // wait until all qc loaded points are spawned
978 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
980 Temporary Marker code
982 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
985 void(vector org) SpawnTempWaypoint =
990 self.temp_way = tep = spawn();
994 tep.classname = "temp_waypoint";
996 tep.solid = SOLID_TRIGGER;
997 tep.movetype = MOVETYPE_NOCLIP;
1000 setsize(tep, PL_MIN, PL_MAX); // FIXME: convert these to numerical