]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/bot/waypoints.qc
merge impulse 107 and 108 into one
[divverent/nexuiz.git] / data / qcsrc / server / bot / waypoints.qc
1 // create a new spawnfunc_waypoint and automatically link it to other waypoints, and link
2 // them back to it as well
3 // (suitable for spawnfunc_waypoint editor)
4 entity waypoint_spawn(vector m1, vector m2, float f)
5 {
6         local entity w;
7         local vector org;
8         w = find(world, classname, "waypoint");
9
10         if not(f & WAYPOINTFLAG_PERSONAL)
11         while (w)
12         {
13                 // if a matching spawnfunc_waypoint already exists, don't add a duplicate
14                 if (boxesoverlap(m1, m2, w.absmin, w.absmax))
15                         return w;
16                 w = find(w, classname, "waypoint");
17         }
18
19         w = spawn();
20         w.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP;
21         w.classname = "waypoint";
22         w.wpflags = f;
23         setorigin(w, (m1 + m2) * 0.5);
24         setsize(w, m1 - w.origin, m2 - w.origin);
25         if (vlen(w.size) > 0)
26                 w.wpisbox = TRUE;
27
28         if(!(f & WAYPOINTFLAG_GENERATED))
29         if(!w.wpisbox)
30         {
31                 traceline(w.origin + '0 0 1', w.origin + PL_MIN_z * '0 0 1' - '0 0 1', MOVE_NOMONSTERS, w);
32                 if (trace_fraction < 1)
33                         setorigin(w, trace_endpos - PL_MIN_z * '0 0 1');
34
35                 // check if the start position is stuck
36                 tracebox(w.origin, PL_MIN + '-1 -1 -1', PL_MAX + '1 1 1', w.origin, MOVE_NOMONSTERS, w);
37                 if (trace_startsolid)
38                 {
39                         org = w.origin + '0 0 26';
40                         tracebox(org, PL_MIN, PL_MAX, w.origin, MOVE_WORLDONLY, w);
41                         if(trace_startsolid)
42                         {
43                                 org = w.origin + '2 2 2';
44                                 tracebox(org, PL_MIN, PL_MAX, w.origin, MOVE_WORLDONLY, w);
45                                 if(trace_startsolid)
46                                 {
47                                         org = w.origin + '-2 -2 2';
48                                         tracebox(org, PL_MIN, PL_MAX, w.origin, MOVE_WORLDONLY, w);
49                                         if(trace_startsolid)
50                                         {
51                                                 org = w.origin + '-2 2 2';
52                                                 tracebox(org, PL_MIN, PL_MAX, w.origin, MOVE_WORLDONLY, w);
53                                                 if(trace_startsolid)
54                                                 {
55                                                         org = w.origin + '2 -2 2';
56                                                         tracebox(org, PL_MIN, PL_MAX, w.origin, MOVE_WORLDONLY, w);
57                                                         if(trace_startsolid)
58                                                         {
59                                                                 // this WP is in solid, refuse it
60                                                                 dprint("Killed a waypoint that was stuck in solid at ", vtos(org), "\n");
61                                                                 remove(w);
62                                                                 return world;
63                                                         }
64                                                 }
65                                         }
66                                 }
67                         }
68                         setorigin(w, org * 0.05 + trace_endpos * 0.95); // don't trust the trace fully
69                 }
70
71                 tracebox(w.origin, PL_MIN, PL_MAX, w.origin - '0 0 128', MOVE_WORLDONLY, w);
72                 if(trace_startsolid)
73                 {
74                         dprint("Killed a waypoint that was stuck in solid ", vtos(w.origin), "\n");
75                         remove(w);
76                         return world;
77                 }
78                 if (!trace_inwater)
79                 {
80                         if(trace_fraction == 1)
81                         {
82                                 dprint("Killed a waypoint that was stuck in air at ", vtos(w.origin), "\n");
83                                 remove(w);
84                                 return world;
85                         }
86                         trace_endpos_z += 0.1; // don't trust the trace fully
87 //                      dprint("Moved waypoint at ", vtos(w.origin), " by ", ftos(vlen(w.origin - trace_endpos)));
88 //                      dprint(" direction: ", vtos((trace_endpos - w.origin)), "\n");
89                         setorigin(w, trace_endpos);
90                 }
91         }
92
93         waypoint_clearlinks(w);
94         //waypoint_schedulerelink(w);
95
96         if (cvar("g_waypointeditor"))
97         {
98                 m1 = w.mins;
99                 m2 = w.maxs;
100                 setmodel(w, "models/runematch/rune.mdl"); w.effects = EF_LOWPRECISION;
101                 setsize(w, m1, m2);
102                 if (w.wpflags & WAYPOINTFLAG_ITEM)
103                         w.colormod = '1 0 0';
104                 else if (w.wpflags & WAYPOINTFLAG_GENERATED)
105                         w.colormod = '1 1 0';
106                 else
107                         w.colormod = '1 1 1';
108         }
109         else
110                 w.model = "";
111
112         return w;
113 };
114
115 // add a new link to the spawnfunc_waypoint, replacing the furthest link it already has
116 void waypoint_addlink(entity from, entity to)
117 {
118         local float c;
119
120         if (from == to)
121                 return;
122         if (from.wpflags & WAYPOINTFLAG_NORELINK)
123                 return;
124
125         if (from.wp00 == to) return;if (from.wp01 == to) return;if (from.wp02 == to) return;if (from.wp03 == to) return;
126         if (from.wp04 == to) return;if (from.wp05 == to) return;if (from.wp06 == to) return;if (from.wp07 == to) return;
127         if (from.wp08 == to) return;if (from.wp09 == to) return;if (from.wp10 == to) return;if (from.wp11 == to) return;
128         if (from.wp12 == to) return;if (from.wp13 == to) return;if (from.wp14 == to) return;if (from.wp15 == to) return;
129         if (from.wp16 == to) return;if (from.wp17 == to) return;if (from.wp18 == to) return;if (from.wp19 == to) return;
130         if (from.wp20 == to) return;if (from.wp21 == to) return;if (from.wp22 == to) return;if (from.wp23 == to) return;
131         if (from.wp24 == to) return;if (from.wp25 == to) return;if (from.wp26 == to) return;if (from.wp27 == to) return;
132         if (from.wp28 == to) return;if (from.wp29 == to) return;if (from.wp30 == to) return;if (from.wp31 == to) return;
133
134         if (to.wpisbox || from.wpisbox)
135         {
136                 // if either is a box we have to find the nearest points on them to
137                 // calculate the distance properly
138                 local vector v1, v2, m1, m2;
139                 v1 = from.origin;
140                 m1 = to.absmin;
141                 m2 = to.absmax;
142                 v1_x = bound(m1_x, v1_x, m2_x);
143                 v1_y = bound(m1_y, v1_y, m2_y);
144                 v1_z = bound(m1_z, v1_z, m2_z);
145                 v2 = to.origin;
146                 m1 = from.absmin;
147                 m2 = from.absmax;
148                 v2_x = bound(m1_x, v2_x, m2_x);
149                 v2_y = bound(m1_y, v2_y, m2_y);
150                 v2_z = bound(m1_z, v2_z, m2_z);
151                 v2 = to.origin;
152                 c = vlen(v2 - v1);
153         }
154         else
155                 c = vlen(to.origin - from.origin);
156
157         if (from.wp31mincost < c) return;
158         if (from.wp30mincost < c) {from.wp31 = to;from.wp31mincost = c;return;} from.wp31 = from.wp30;from.wp31mincost = from.wp30mincost;
159         if (from.wp29mincost < c) {from.wp30 = to;from.wp30mincost = c;return;} from.wp30 = from.wp29;from.wp30mincost = from.wp29mincost;
160         if (from.wp28mincost < c) {from.wp29 = to;from.wp29mincost = c;return;} from.wp29 = from.wp28;from.wp29mincost = from.wp28mincost;
161         if (from.wp27mincost < c) {from.wp28 = to;from.wp28mincost = c;return;} from.wp28 = from.wp27;from.wp28mincost = from.wp27mincost;
162         if (from.wp26mincost < c) {from.wp27 = to;from.wp27mincost = c;return;} from.wp27 = from.wp26;from.wp27mincost = from.wp26mincost;
163         if (from.wp25mincost < c) {from.wp26 = to;from.wp26mincost = c;return;} from.wp26 = from.wp25;from.wp26mincost = from.wp25mincost;
164         if (from.wp24mincost < c) {from.wp25 = to;from.wp25mincost = c;return;} from.wp25 = from.wp24;from.wp25mincost = from.wp24mincost;
165         if (from.wp23mincost < c) {from.wp24 = to;from.wp24mincost = c;return;} from.wp24 = from.wp23;from.wp24mincost = from.wp23mincost;
166         if (from.wp22mincost < c) {from.wp23 = to;from.wp23mincost = c;return;} from.wp23 = from.wp22;from.wp23mincost = from.wp22mincost;
167         if (from.wp21mincost < c) {from.wp22 = to;from.wp22mincost = c;return;} from.wp22 = from.wp21;from.wp22mincost = from.wp21mincost;
168         if (from.wp20mincost < c) {from.wp21 = to;from.wp21mincost = c;return;} from.wp21 = from.wp20;from.wp21mincost = from.wp20mincost;
169         if (from.wp19mincost < c) {from.wp20 = to;from.wp20mincost = c;return;} from.wp20 = from.wp19;from.wp20mincost = from.wp19mincost;
170         if (from.wp18mincost < c) {from.wp19 = to;from.wp19mincost = c;return;} from.wp19 = from.wp18;from.wp19mincost = from.wp18mincost;
171         if (from.wp17mincost < c) {from.wp18 = to;from.wp18mincost = c;return;} from.wp18 = from.wp17;from.wp18mincost = from.wp17mincost;
172         if (from.wp16mincost < c) {from.wp17 = to;from.wp17mincost = c;return;} from.wp17 = from.wp16;from.wp17mincost = from.wp16mincost;
173         if (from.wp15mincost < c) {from.wp16 = to;from.wp16mincost = c;return;} from.wp16 = from.wp15;from.wp16mincost = from.wp15mincost;
174         if (from.wp14mincost < c) {from.wp15 = to;from.wp15mincost = c;return;} from.wp15 = from.wp14;from.wp15mincost = from.wp14mincost;
175         if (from.wp13mincost < c) {from.wp14 = to;from.wp14mincost = c;return;} from.wp14 = from.wp13;from.wp14mincost = from.wp13mincost;
176         if (from.wp12mincost < c) {from.wp13 = to;from.wp13mincost = c;return;} from.wp13 = from.wp12;from.wp13mincost = from.wp12mincost;
177         if (from.wp11mincost < c) {from.wp12 = to;from.wp12mincost = c;return;} from.wp12 = from.wp11;from.wp12mincost = from.wp11mincost;
178         if (from.wp10mincost < c) {from.wp11 = to;from.wp11mincost = c;return;} from.wp11 = from.wp10;from.wp11mincost = from.wp10mincost;
179         if (from.wp09mincost < c) {from.wp10 = to;from.wp10mincost = c;return;} from.wp10 = from.wp09;from.wp10mincost = from.wp09mincost;
180         if (from.wp08mincost < c) {from.wp09 = to;from.wp09mincost = c;return;} from.wp09 = from.wp08;from.wp09mincost = from.wp08mincost;
181         if (from.wp07mincost < c) {from.wp08 = to;from.wp08mincost = c;return;} from.wp08 = from.wp07;from.wp08mincost = from.wp07mincost;
182         if (from.wp06mincost < c) {from.wp07 = to;from.wp07mincost = c;return;} from.wp07 = from.wp06;from.wp07mincost = from.wp06mincost;
183         if (from.wp05mincost < c) {from.wp06 = to;from.wp06mincost = c;return;} from.wp06 = from.wp05;from.wp06mincost = from.wp05mincost;
184         if (from.wp04mincost < c) {from.wp05 = to;from.wp05mincost = c;return;} from.wp05 = from.wp04;from.wp05mincost = from.wp04mincost;
185         if (from.wp03mincost < c) {from.wp04 = to;from.wp04mincost = c;return;} from.wp04 = from.wp03;from.wp04mincost = from.wp03mincost;
186         if (from.wp02mincost < c) {from.wp03 = to;from.wp03mincost = c;return;} from.wp03 = from.wp02;from.wp03mincost = from.wp02mincost;
187         if (from.wp01mincost < c) {from.wp02 = to;from.wp02mincost = c;return;} from.wp02 = from.wp01;from.wp02mincost = from.wp01mincost;
188         if (from.wp00mincost < c) {from.wp01 = to;from.wp01mincost = c;return;} from.wp01 = from.wp00;from.wp01mincost = from.wp00mincost;
189         from.wp00 = to;from.wp00mincost = c;return;
190 };
191
192 // relink this spawnfunc_waypoint
193 // (precompile a list of all reachable waypoints from this spawnfunc_waypoint)
194 // (SLOW!)
195 void waypoint_think()
196 {
197         local entity e;
198         local vector sv, sm1, sm2, ev, em1, em2, dv;
199
200         stepheightvec = cvar("sv_stepheight") * '0 0 1';
201         bot_navigation_movemode = ((cvar("bot_navigation_ignoreplayers")) ? MOVE_NOMONSTERS : MOVE_NORMAL);
202
203         //dprint("waypoint_think wpisbox = ", ftos(self.wpisbox), "\n");
204         sm1 = self.origin + self.mins;
205         sm2 = self.origin + self.maxs;
206         for(e = world; (e = find(e, classname, "waypoint")); )
207         {
208                 if (boxesoverlap(self.absmin, self.absmax, e.absmin, e.absmax))
209                 {
210                         waypoint_addlink(self, e);
211                         waypoint_addlink(e, self);
212                 }
213                 else
214                 {
215                         ++relink_total;
216                         if(!checkpvs(self.origin, e))
217                         {
218                                 ++relink_pvsculled;
219                                 continue;
220                         }
221                         sv = e.origin;
222                         sv_x = bound(sm1_x, sv_x, sm2_x);
223                         sv_y = bound(sm1_y, sv_y, sm2_y);
224                         sv_z = bound(sm1_z, sv_z, sm2_z);
225                         ev = self.origin;
226                         em1 = e.origin + e.mins;
227                         em2 = e.origin + e.maxs;
228                         ev_x = bound(em1_x, ev_x, em2_x);
229                         ev_y = bound(em1_y, ev_y, em2_y);
230                         ev_z = bound(em1_z, ev_z, em2_z);
231                         dv = ev - sv;
232                         dv_z = 0;
233                         if (vlen(dv) >= 1050) // max search distance in XY
234                         {
235                                 ++relink_lengthculled;
236                                 continue;
237                         }
238                         navigation_testtracewalk = 0;
239                         if (!self.wpisbox)
240                         {
241                                 tracebox(sv - PL_MIN_z * '0 0 1', PL_MIN, PL_MAX, sv, FALSE, self);
242                                 if (!trace_startsolid)
243                                 {
244                                         //dprint("sv deviation", vtos(trace_endpos - sv), "\n");
245                                         sv = trace_endpos + '0 0 1';
246                                 }
247                         }
248                         if (!e.wpisbox)
249                         {
250                                 tracebox(ev - PL_MIN_z * '0 0 1', PL_MIN, PL_MAX, ev, FALSE, e);
251                                 if (!trace_startsolid)
252                                 {
253                                         //dprint("ev deviation", vtos(trace_endpos - ev), "\n");
254                                         ev = trace_endpos + '0 0 1';
255                                 }
256                         }
257                         //traceline(self.origin, e.origin, FALSE, world);
258                         //if (trace_fraction == 1)
259                         if (!self.wpisbox && tracewalk(self, sv, PL_MIN, PL_MAX, ev, MOVE_NOMONSTERS))
260                                 waypoint_addlink(self, e);
261                         else
262                                 relink_walkculled += 0.5;
263                         if (!e.wpisbox && tracewalk(e, ev, PL_MIN, PL_MAX, sv, MOVE_NOMONSTERS))
264                                 waypoint_addlink(e, self);
265                         else
266                                 relink_walkculled += 0.5;
267                 }
268         }
269         navigation_testtracewalk = 0;
270         self.wplinked = TRUE;
271 };
272
273 void waypoint_clearlinks(entity wp)
274 {
275         // clear links to other waypoints
276         local float f;
277         f = 10000000;
278         wp.wp00 = wp.wp01 = wp.wp02 = wp.wp03 = wp.wp04 = wp.wp05 = wp.wp06 = wp.wp07 = world;
279         wp.wp08 = wp.wp09 = wp.wp10 = wp.wp11 = wp.wp12 = wp.wp13 = wp.wp14 = wp.wp15 = world;
280         wp.wp16 = wp.wp17 = wp.wp18 = wp.wp19 = wp.wp20 = wp.wp21 = wp.wp22 = wp.wp23 = world;
281         wp.wp24 = wp.wp25 = wp.wp26 = wp.wp27 = wp.wp28 = wp.wp29 = wp.wp30 = wp.wp31 = world;
282
283         wp.wp00mincost = wp.wp01mincost = wp.wp02mincost = wp.wp03mincost = wp.wp04mincost = wp.wp05mincost = wp.wp06mincost = wp.wp07mincost = f;
284         wp.wp08mincost = wp.wp09mincost = wp.wp10mincost = wp.wp11mincost = wp.wp12mincost = wp.wp13mincost = wp.wp14mincost = wp.wp15mincost = f;
285         wp.wp16mincost = wp.wp17mincost = wp.wp18mincost = wp.wp19mincost = wp.wp20mincost = wp.wp21mincost = wp.wp22mincost = wp.wp23mincost = f;
286         wp.wp24mincost = wp.wp25mincost = wp.wp26mincost = wp.wp27mincost = wp.wp28mincost = wp.wp29mincost = wp.wp30mincost = wp.wp31mincost = f;
287
288         wp.wplinked = FALSE;
289 };
290
291 // tell a spawnfunc_waypoint to relink
292 void waypoint_schedulerelink(entity wp)
293 {
294         if (wp == world)
295                 return;
296         // TODO: add some sort of visible box in edit mode for box waypoints
297         if (cvar("g_waypointeditor"))
298         {
299                 local vector m1, m2;
300                 m1 = wp.mins;
301                 m2 = wp.maxs;
302                 setmodel(wp, "models/runematch/rune.mdl"); wp.effects = EF_LOWPRECISION;
303                 setsize(wp, m1, m2);
304                 if (wp.wpflags & WAYPOINTFLAG_ITEM)
305                         wp.colormod = '1 0 0';
306                 else if (wp.wpflags & WAYPOINTFLAG_GENERATED)
307                         wp.colormod = '1 1 0';
308                 else
309                         wp.colormod = '1 1 1';
310         }
311         else
312                 wp.model = "";
313         wp.wpisbox = vlen(wp.size) > 0;
314         wp.enemy = world;
315         if (!(wp.wpflags & WAYPOINTFLAG_PERSONAL))
316                 wp.owner = world;
317         if (!(wp.wpflags & WAYPOINTFLAG_NORELINK))
318                 waypoint_clearlinks(wp);
319         // schedule an actual relink on next frame
320         wp.think = waypoint_think;
321         wp.nextthink = time;
322         wp.effects = EF_LOWPRECISION;
323 }
324
325 // spawnfunc_waypoint map entity
326 void spawnfunc_waypoint()
327 {
328         setorigin(self, self.origin);
329         // schedule a relink after other waypoints have had a chance to spawn
330         waypoint_clearlinks(self);
331         //waypoint_schedulerelink(self);
332 };
333
334 // remove a spawnfunc_waypoint, and schedule all neighbors to relink
335 void waypoint_remove(entity e)
336 {
337         // tell all linked waypoints that they need to relink
338         waypoint_schedulerelink(e.wp00);
339         waypoint_schedulerelink(e.wp01);
340         waypoint_schedulerelink(e.wp02);
341         waypoint_schedulerelink(e.wp03);
342         waypoint_schedulerelink(e.wp04);
343         waypoint_schedulerelink(e.wp05);
344         waypoint_schedulerelink(e.wp06);
345         waypoint_schedulerelink(e.wp07);
346         waypoint_schedulerelink(e.wp08);
347         waypoint_schedulerelink(e.wp09);
348         waypoint_schedulerelink(e.wp10);
349         waypoint_schedulerelink(e.wp11);
350         waypoint_schedulerelink(e.wp12);
351         waypoint_schedulerelink(e.wp13);
352         waypoint_schedulerelink(e.wp14);
353         waypoint_schedulerelink(e.wp15);
354         waypoint_schedulerelink(e.wp16);
355         waypoint_schedulerelink(e.wp17);
356         waypoint_schedulerelink(e.wp18);
357         waypoint_schedulerelink(e.wp19);
358         waypoint_schedulerelink(e.wp20);
359         waypoint_schedulerelink(e.wp21);
360         waypoint_schedulerelink(e.wp22);
361         waypoint_schedulerelink(e.wp23);
362         waypoint_schedulerelink(e.wp24);
363         waypoint_schedulerelink(e.wp25);
364         waypoint_schedulerelink(e.wp26);
365         waypoint_schedulerelink(e.wp27);
366         waypoint_schedulerelink(e.wp28);
367         waypoint_schedulerelink(e.wp29);
368         waypoint_schedulerelink(e.wp30);
369         waypoint_schedulerelink(e.wp31);
370         // and now remove the spawnfunc_waypoint
371         remove(e);
372 };
373
374 // empties the map of waypoints
375 void waypoint_removeall()
376 {
377         local entity head, next;
378         head = findchain(classname, "waypoint");
379         while (head)
380         {
381                 next = head.chain;
382                 remove(head);
383                 head = next;
384         }
385 };
386
387 // tell all waypoints to relink
388 // (is this useful at all?)
389 void waypoint_schedulerelinkall()
390 {
391         local entity head;
392         relink_total = relink_walkculled = relink_pvsculled = relink_lengthculled = 0;
393         head = findchain(classname, "waypoint");
394         while (head)
395         {
396                 waypoint_schedulerelink(head);
397                 head = head.chain;
398         }
399 };
400
401 // Load waypoint links from file
402 float waypoint_load_links()
403 {
404         local string filename, s;
405         local float file, tokens, c, found;
406         local entity wp_from, wp_to;
407         local vector wp_to_pos, wp_from_pos;
408         filename = strcat("maps/", mapname);
409         filename = strcat(filename, ".waypoints.cache");
410         file = fopen(filename, FILE_READ);
411
412         if (file < 0)
413         {
414                 dprint("waypoint links load from ");
415                 dprint(filename);
416                 dprint(" failed\n");
417                 return FALSE;
418         }
419
420         while (1)
421         {
422                 s = fgets(file);
423                 if (!s)
424                         break;
425
426                 tokens = tokenizebyseparator(s, "*");
427
428                 if (tokens!=2)
429                 {
430                         // bad file format
431                         fclose(file);
432                         return FALSE;
433                 }
434
435                 wp_from_pos     = stov(argv(0));
436                 wp_to_pos       = stov(argv(1));
437
438                 // Search "from" waypoint
439                 if(wp_from.origin!=wp_from_pos)
440                 {
441                         wp_from = findradius(wp_from_pos, 1);
442                         found = FALSE;
443                         while(wp_from)
444                         {
445                                 if(vlen(wp_from.origin-wp_from_pos)<1)
446                                 if(wp_from.classname == "waypoint")
447                                 {
448                                         found = TRUE;
449                                         break;
450                                 }
451                                 wp_from = wp_from.chain;
452                         }
453
454                         if(!found)
455                         {
456                                 // can't find that waypoint
457                                 fclose(file);
458                                 return FALSE;
459                         }
460                 }
461
462                 // Search "to" waypoint
463                 wp_to = findradius(wp_to_pos, 1);
464                 found = FALSE;
465                 while(wp_to)
466                 {
467                         if(vlen(wp_to.origin-wp_to_pos)<1)
468                         if(wp_to.classname == "waypoint")
469                         {
470                                 found = TRUE;
471                                 break;
472                         }
473                         wp_to = wp_to.chain;
474                 }
475
476                 if(!found)
477                 {
478                         // can't find that waypoint
479                         fclose(file);
480                         return FALSE;
481                 }
482
483                 ++c;
484                 waypoint_addlink(wp_from, wp_to);
485         }
486
487         fclose(file);
488
489         dprint("loaded ");
490         dprint(ftos(c));
491         dprint(" waypoint links from maps/");
492         dprint(mapname);
493         dprint(".waypoints.cache\n");
494
495         botframe_cachedwaypointlinks = TRUE;
496         return TRUE;
497 };
498
499 void waypoint_load_links_hardwired()
500 {
501         local string filename, s;
502         local float file, tokens, c, found;
503         local entity wp_from, wp_to;
504         local vector wp_to_pos, wp_from_pos;
505         filename = strcat("maps/", mapname);
506         filename = strcat(filename, ".waypoints.hardwired");
507         file = fopen(filename, FILE_READ);
508
509         botframe_loadedforcedlinks = TRUE;
510
511         if (file < 0)
512         {
513                 dprint("waypoint links load from ");
514                 dprint(filename);
515                 dprint(" failed\n");
516                 return;
517         }
518
519         for (;;)
520         {
521                 s = fgets(file);
522                 if (!s)
523                         break;
524
525                 if(substring(s, 0, 2)=="//")
526                         continue;
527
528                 if(substring(s, 0, 1)=="#")
529                         continue;
530
531                 tokens = tokenizebyseparator(s, "*");
532
533                 if (tokens!=2)
534                         continue;
535
536                 wp_from_pos     = stov(argv(0));
537                 wp_to_pos       = stov(argv(1));
538
539                 // Search "from" waypoint
540                 if(wp_from.origin!=wp_from_pos)
541                 {
542                         wp_from = findradius(wp_from_pos, 1);
543                         found = FALSE;
544                         while(wp_from)
545                         {
546                                 if(vlen(wp_from.origin-wp_from_pos)<1)
547                                 if(wp_from.classname == "waypoint")
548                                 {
549                                         found = TRUE;
550                                         break;
551                                 }
552                                 wp_from = wp_from.chain;
553                         }
554
555                         if(!found)
556                         {
557                                 print(strcat("NOTICE: Can not find waypoint at ", vtos(wp_from_pos), ". Path skipped\n"));
558                                 continue;
559                         }
560                 }
561
562                 // Search "to" waypoint
563                 wp_to = findradius(wp_to_pos, 1);
564                 found = FALSE;
565                 while(wp_to)
566                 {
567                         if(vlen(wp_to.origin-wp_to_pos)<1)
568                         if(wp_to.classname == "waypoint")
569                         {
570                                 found = TRUE;
571                                 break;
572                         }
573                         wp_to = wp_to.chain;
574                 }
575
576                 if(!found)
577                 {
578                         print(strcat("NOTICE: Can not find waypoint at ", vtos(wp_to_pos), ". Path skipped\n"));
579                         continue;
580                 }
581
582                 ++c;
583                 waypoint_addlink(wp_from, wp_to);
584         }
585
586         fclose(file);
587
588         dprint("loaded ");
589         dprint(ftos(c));
590         dprint(" waypoint links from maps/");
591         dprint(mapname);
592         dprint(".waypoints.hardwired\n");
593 };
594
595 // Save all waypoint links to a file
596 void waypoint_save_links()
597 {
598         local string filename, s;
599         local float file, c, i;
600         local entity w, link;
601         filename = strcat("maps/", mapname);
602         filename = strcat(filename, ".waypoints.cache");
603         file = fopen(filename, FILE_WRITE);
604         if (file < 0)
605         {
606                 print("waypoint links save to ");
607                 print(filename);
608                 print(" failed\n");
609         }
610         c = 0;
611         w = findchain(classname, "waypoint");
612         while (w)
613         {
614                 for(i=0;i<32;++i)
615                 {
616                         // :S
617                         switch(i)
618                         {
619                                 //      for i in $(seq -w 0 31); do echo "case $i:link = w.wp$i; break;"; done;
620                                 case 00:link = w.wp00; break;
621                                 case 01:link = w.wp01; break;
622                                 case 02:link = w.wp02; break;
623                                 case 03:link = w.wp03; break;
624                                 case 04:link = w.wp04; break;
625                                 case 05:link = w.wp05; break;
626                                 case 06:link = w.wp06; break;
627                                 case 07:link = w.wp07; break;
628                                 case 08:link = w.wp08; break;
629                                 case 09:link = w.wp09; break;
630                                 case 10:link = w.wp10; break;
631                                 case 11:link = w.wp11; break;
632                                 case 12:link = w.wp12; break;
633                                 case 13:link = w.wp13; break;
634                                 case 14:link = w.wp14; break;
635                                 case 15:link = w.wp15; break;
636                                 case 16:link = w.wp16; break;
637                                 case 17:link = w.wp17; break;
638                                 case 18:link = w.wp18; break;
639                                 case 19:link = w.wp19; break;
640                                 case 20:link = w.wp20; break;
641                                 case 21:link = w.wp21; break;
642                                 case 22:link = w.wp22; break;
643                                 case 23:link = w.wp23; break;
644                                 case 24:link = w.wp24; break;
645                                 case 25:link = w.wp25; break;
646                                 case 26:link = w.wp26; break;
647                                 case 27:link = w.wp27; break;
648                                 case 28:link = w.wp28; break;
649                                 case 29:link = w.wp29; break;
650                                 case 30:link = w.wp30; break;
651                                 case 31:link = w.wp31; break;
652                         }
653
654                         if(link==world)
655                                 continue;
656
657                         s = strcat(vtos(w.origin), "*", vtos(link.origin), "\n");
658                         fputs(file, s);
659                         ++c;
660                 }
661                 w = w.chain;
662         }
663         fclose(file);
664         botframe_cachedwaypointlinks = TRUE;
665
666         print("saved ");
667         print(ftos(c));
668         print(" waypoints links to maps/");
669         print(mapname);
670         print(".waypoints.cache\n");
671 };
672
673 // save waypoints to gamedir/data/maps/mapname.waypoints
674 void waypoint_saveall()
675 {
676         local string filename, s;
677         local float file, c;
678         local entity w;
679         filename = strcat("maps/", mapname);
680         filename = strcat(filename, ".waypoints");
681         file = fopen(filename, FILE_WRITE);
682         if (file >= 0)
683         {
684                 c = 0;
685                 w = findchain(classname, "waypoint");
686                 while (w)
687                 {
688                         if (!(w.wpflags & WAYPOINTFLAG_GENERATED))
689                         {
690                                 s = strcat(vtos(w.origin + w.mins), "\n");
691                                 s = strcat(s, vtos(w.origin + w.maxs));
692                                 s = strcat(s, "\n");
693                                 s = strcat(s, ftos(w.wpflags));
694                                 s = strcat(s, "\n");
695                                 fputs(file, s);
696                                 c = c + 1;
697                         }
698                         w = w.chain;
699                 }
700                 fclose(file);
701                 bprint("saved ");
702                 bprint(ftos(c));
703                 bprint(" waypoints to maps/");
704                 bprint(mapname);
705                 bprint(".waypoints\n");
706         }
707         else
708         {
709                 bprint("waypoint save to ");
710                 bprint(filename);
711                 bprint(" failed\n");
712         }
713         waypoint_save_links();
714         botframe_loadedforcedlinks = FALSE;
715 };
716
717 // load waypoints from file
718 float waypoint_loadall()
719 {
720         local string filename, s;
721         local float file, cwp, cwb, fl;
722         local vector m1, m2;
723         cwp = 0;
724         cwb = 0;
725         filename = strcat("maps/", mapname);
726         filename = strcat(filename, ".waypoints");
727         file = fopen(filename, FILE_READ);
728         if (file >= 0)
729         {
730                 while (1)
731                 {
732                         s = fgets(file);
733                         if (!s)
734                                 break;
735                         m1 = stov(s);
736                         s = fgets(file);
737                         if (!s)
738                                 break;
739                         m2 = stov(s);
740                         s = fgets(file);
741                         if (!s)
742                                 break;
743                         fl = stof(s);
744                         waypoint_spawn(m1, m2, fl);
745                         if (m1 == m2)
746                                 cwp = cwp + 1;
747                         else
748                                 cwb = cwb + 1;
749                 }
750                 fclose(file);
751                 dprint("loaded ");
752                 dprint(ftos(cwp));
753                 dprint(" waypoints and ");
754                 dprint(ftos(cwb));
755                 dprint(" wayboxes from maps/");
756                 dprint(mapname);
757                 dprint(".waypoints\n");
758         }
759         else
760         {
761                 dprint("waypoint load from ");
762                 dprint(filename);
763                 dprint(" failed\n");
764         }
765         return cwp + cwb;
766 };
767
768 vector waypoint_fixorigin(vector position)
769 {
770         tracebox(position + '0 0 1' * (-1 - PL_MIN_z), PL_MIN, PL_MAX, position + '0 0 -512', MOVE_NOMONSTERS, world);
771         if(trace_fraction < 1)
772                 position = trace_endpos;
773         return position;
774 }
775
776 void waypoint_spawnforitem_force(entity e, vector org)
777 {
778         local entity w;
779
780         // Fix the waypoint altitude if necessary
781         org = waypoint_fixorigin(org);
782
783         // don't spawn an item spawnfunc_waypoint if it already exists
784         w = findchain(classname, "waypoint");
785         while (w)
786         {
787                 if (w.wpisbox)
788                 {
789                         if (boxesoverlap(org, org, w.absmin, w.absmax))
790                         {
791                                 e.nearestwaypoint = w;
792                                 return;
793                         }
794                 }
795                 else
796                 {
797                         if (vlen(w.origin - org) < 16)
798                         {
799                                 e.nearestwaypoint = w;
800                                 return;
801                         }
802                 }
803                 w = w.chain;
804         }
805         e.nearestwaypoint = waypoint_spawn(org, org, WAYPOINTFLAG_GENERATED | WAYPOINTFLAG_ITEM);
806 }
807
808 void waypoint_spawnforitem(entity e)
809 {
810         if(!bot_waypoints_for_items)
811                 return;
812
813         waypoint_spawnforitem_force(e, e.origin);
814 };
815
816 void waypoint_spawnforteleporter(entity e, vector destination, float timetaken)
817 {
818         local entity w;
819         local entity dw;
820         w = waypoint_spawn(e.absmin, e.absmax, WAYPOINTFLAG_GENERATED | WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_NORELINK);
821         dw = waypoint_spawn(destination, destination, WAYPOINTFLAG_GENERATED);
822         // one way link to the destination
823         w.wp00 = dw;
824         w.wp00mincost = timetaken; // this is just for jump pads
825         // the teleporter's nearest spawnfunc_waypoint is this one
826         // (teleporters are not goals, so this is probably useless)
827         e.nearestwaypoint = w;
828         e.nearestwaypointtimeout = time + 1000000000;
829 };
830
831 entity waypoint_spawnpersonal(vector position)
832 {
833         entity w;
834
835         // drop the waypoint to a proper location:
836         //   first move it up by a player height
837         //   then move it down to hit the floor with player bbox size
838         position = waypoint_fixorigin(position);
839
840         w = waypoint_spawn(position, position, WAYPOINTFLAG_GENERATED | WAYPOINTFLAG_PERSONAL);
841         w.nearestwaypoint = world;
842         w.nearestwaypointtimeout = 0;
843         w.owner = self;
844
845         waypoint_schedulerelink(w);
846
847         return w;
848 };
849
850 void botframe_showwaypointlinks()
851 {
852         local entity player, head, w;
853         if (time < botframe_waypointeditorlightningtime)
854                 return;
855         botframe_waypointeditorlightningtime = time + 0.5;
856         player = find(world, classname, "player");
857         while (player)
858         {
859                 if (!player.isbot)
860                 if (player.flags & FL_ONGROUND || player.waterlevel > WATERLEVEL_NONE)
861                 {
862                         //navigation_testtracewalk = TRUE;
863                         head = navigation_findnearestwaypoint(player, FALSE);
864                 //      print("currently selected WP is ", etos(head), "\n");
865                         //navigation_testtracewalk = FALSE;
866                         if (head)
867                         {
868                                 w = head     ;if (w) te_lightning2(world, w.origin, player.origin);
869                                 w = head.wp00;if (w) te_lightning2(world, w.origin, head.origin);
870                                 w = head.wp01;if (w) te_lightning2(world, w.origin, head.origin);
871                                 w = head.wp02;if (w) te_lightning2(world, w.origin, head.origin);
872                                 w = head.wp03;if (w) te_lightning2(world, w.origin, head.origin);
873                                 w = head.wp04;if (w) te_lightning2(world, w.origin, head.origin);
874                                 w = head.wp05;if (w) te_lightning2(world, w.origin, head.origin);
875                                 w = head.wp06;if (w) te_lightning2(world, w.origin, head.origin);
876                                 w = head.wp07;if (w) te_lightning2(world, w.origin, head.origin);
877                                 w = head.wp08;if (w) te_lightning2(world, w.origin, head.origin);
878                                 w = head.wp09;if (w) te_lightning2(world, w.origin, head.origin);
879                                 w = head.wp10;if (w) te_lightning2(world, w.origin, head.origin);
880                                 w = head.wp11;if (w) te_lightning2(world, w.origin, head.origin);
881                                 w = head.wp12;if (w) te_lightning2(world, w.origin, head.origin);
882                                 w = head.wp13;if (w) te_lightning2(world, w.origin, head.origin);
883                                 w = head.wp14;if (w) te_lightning2(world, w.origin, head.origin);
884                                 w = head.wp15;if (w) te_lightning2(world, w.origin, head.origin);
885                                 w = head.wp16;if (w) te_lightning2(world, w.origin, head.origin);
886                                 w = head.wp17;if (w) te_lightning2(world, w.origin, head.origin);
887                                 w = head.wp18;if (w) te_lightning2(world, w.origin, head.origin);
888                                 w = head.wp19;if (w) te_lightning2(world, w.origin, head.origin);
889                                 w = head.wp20;if (w) te_lightning2(world, w.origin, head.origin);
890                                 w = head.wp21;if (w) te_lightning2(world, w.origin, head.origin);
891                                 w = head.wp22;if (w) te_lightning2(world, w.origin, head.origin);
892                                 w = head.wp23;if (w) te_lightning2(world, w.origin, head.origin);
893                                 w = head.wp24;if (w) te_lightning2(world, w.origin, head.origin);
894                                 w = head.wp25;if (w) te_lightning2(world, w.origin, head.origin);
895                                 w = head.wp26;if (w) te_lightning2(world, w.origin, head.origin);
896                                 w = head.wp27;if (w) te_lightning2(world, w.origin, head.origin);
897                                 w = head.wp28;if (w) te_lightning2(world, w.origin, head.origin);
898                                 w = head.wp29;if (w) te_lightning2(world, w.origin, head.origin);
899                                 w = head.wp30;if (w) te_lightning2(world, w.origin, head.origin);
900                                 w = head.wp31;if (w) te_lightning2(world, w.origin, head.origin);
901                         }
902                 }
903                 player = find(player, classname, "player");
904         }
905 };