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