Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
waypoints.qc
Go to the documentation of this file.
1#include "waypoints.qh"
2
4#include <common/constants.qh>
5#include <common/debug.qh>
10#include <common/state.qh>
11#include <common/stats.qh>
15#include <server/antilag.qh>
19#include <server/items/items.qh>
20#include <server/spawnpoints.qh>
22
29{
30 IL_EACH(g_waypoints, true,
31 {
32 it.colormod = '0.5 0.5 0.5';
33 it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
34 });
35
37 if(!e2)
38 {
39 LOG_INFO("Can't find any waypoint nearby\n");
40 return;
41 }
42
44
45 int j = 0;
46 int m = 0;
47 IL_EACH(g_waypoints, it.wpcost >= 10000000,
48 {
49 LOG_INFO("unreachable: ", etos(it), " ", vtos(it.origin), "\n");
50 it.colormod_z = 8;
51 it.effects |= EF_NODEPTHTEST | EF_BLUE;
52 j++;
53 m++;
54 });
55 if (j) LOG_INFOF("%d waypoints cannot be reached from here in any way (marked with blue light)\n", j);
57
58 j = 0;
59 IL_EACH(g_waypoints, it.wpcost >= 10000000,
60 {
61 LOG_INFO("cannot reach me: ", etos(it), " ", vtos(it.origin), "\n");
62 it.colormod_x = 8;
63 if (!(it.effects & EF_NODEPTHTEST)) // not already reported before
64 m++;
65 it.effects |= EF_NODEPTHTEST | EF_RED;
66 j++;
67 });
68 if (j) LOG_INFOF("%d waypoints cannot walk to here in any way (marked with red light)\n", j);
69 if (m) LOG_INFOF("%d waypoints have been marked total\n", m);
70
71 j = 0;
73 {
74 if (navigation_findnearestwaypoint(it, false))
75 {
76 if(it.spawnpointmodel)
77 {
78 delete(it.spawnpointmodel);
79 it.spawnpointmodel = NULL;
80 }
81 }
82 else
83 {
84 if(!it.spawnpointmodel)
85 {
86 tracebox(it.origin, PL_MIN_CONST, PL_MAX_CONST, it.origin - '0 0 512', MOVE_NOMONSTERS, NULL);
87 entity e = new(spawnpointmodel);
89 setorigin(e, org);
90 e.solid = SOLID_TRIGGER;
91 it.spawnpointmodel = e;
92 }
93 LOG_INFO("spawn without waypoint: ", etos(it), " ", vtos(it.origin), "\n");
94 it.spawnpointmodel.effects |= EF_NODEPTHTEST;
95 _setmodel(it.spawnpointmodel, pl.model);
96 it.spawnpointmodel.frame = ANIM_idle.m_id;
97 it.spawnpointmodel.skin = pl.skin;
98 it.spawnpointmodel.colormap = 1024 + 68;
99 it.spawnpointmodel.glowmod = '1 0 0';
100 it.spawnpointmodel.angles = it.angles;
101 setsize(it.spawnpointmodel, PL_MIN_CONST, PL_MAX_CONST);
102 j++;
103 }
104 });
105 if (j) LOG_INFOF("%d spawnpoints have no nearest waypoint (marked by player model)\n", j);
106
107 j = 0;
108 IL_EACH(g_items, true,
109 {
110 it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
111 it.colormod = '0.5 0.5 0.5';
112 });
113 IL_EACH(g_items, true,
114 {
115 if (navigation_findnearestwaypoint(it, false))
116 continue;
117 LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n");
118 it.effects |= EF_NODEPTHTEST | EF_RED;
119 it.colormod_x = 8;
120 j++;
121 });
122 if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked away from (marked with red light)\n", j);
123
124 j = 0;
125 IL_EACH(g_items, true,
126 {
127 if (navigation_findnearestwaypoint(it, true))
128 continue;
129 LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n");
130 it.effects |= EF_NODEPTHTEST | EF_BLUE;
131 it.colormod_z = 8;
132 j++;
133 });
134 if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked to (marked with blue light)\n", j);
135}
136
137void waypoint_getSymmetricalAxis_cmd(entity caller, bool save, int arg_idx)
138{
139 vector v1 = stov(argv(arg_idx++));
140 vector v2 = stov(argv(arg_idx++));
141 vector mid = (v1 + v2) * 0.5;
142
143 float diffy = (v2.y - v1.y);
144 float diffx = (v2.x - v1.x);
145 if (v1.y == v2.y)
146 diffy = 0.000001;
147 if (v1.x == v2.x)
148 diffx = 0.000001;
149 float m = - diffx / diffy;
150 float q = - m * mid.x + mid.y;
151 if (fabs(m) <= 0.000001) m = 0;
152 if (fabs(q) <= 0.000001) q = 0;
153
154 string axis_str = strcat(ftos(m), " ", ftos(q));
155 if (save)
156 cvar_set("g_waypointeditor_symmetrical_axis", axis_str);
157 axis_str = strcat("\"", axis_str, "\"");
158 sprint(caller, strcat("Axis of symmetry based on input points: ", axis_str, "\n"));
159 if (save)
160 sprint(caller, sprintf(" ^3saved to %s\n", "g_waypointeditor_symmetrical_axis"));
161 if (save)
162 {
163 cvar_set("g_waypointeditor_symmetrical", "-2");
164 sprint(caller, strcat("g_waypointeditor_symmetrical", " has been set to ",
165 cvar_string("g_waypointeditor_symmetrical"), "\n"));
166 }
167}
168
169void waypoint_getSymmetricalOrigin_cmd(entity caller, bool save, int arg_idx)
170{
171 vector org = '0 0 0';
172 int ctf_flags = 0;
173 for (int i = 0; i < 6; ++i)
174 {
175 if (argv(arg_idx + i) != "")
176 ctf_flags++;
177 }
178 if (ctf_flags < 2)
179 {
180 ctf_flags = 0;
182 if (argv(arg_idx) != "")
183 sprint(caller, "WARNING: Ignoring single input point\n");
185 {
186 sprint(caller, "Origin of symmetry can't be automatically determined\n");
187 return;
188 }
189 }
190 else
191 {
192 vector v1, v2, v3, v4, v5, v6;
193 // TODO: Consider inlining this away after https://github.com/graphitemaster/gmqcc/issues/210.
194 float r_ctf_flags = 1 / ctf_flags;
195 for (int i = 1; i <= ctf_flags; ++i)
196 {
197 if (i == 1) { v1 = stov(argv(arg_idx++)); org = v1 * r_ctf_flags; }
198 else if (i == 2) { v2 = stov(argv(arg_idx++)); org += v2 * r_ctf_flags; }
199 else if (i == 3) { v3 = stov(argv(arg_idx++)); org += v3 * r_ctf_flags; }
200 else if (i == 4) { v4 = stov(argv(arg_idx++)); org += v4 * r_ctf_flags; }
201 else if (i == 5) { v5 = stov(argv(arg_idx++)); org += v5 * r_ctf_flags; }
202 else if (i == 6) { v6 = stov(argv(arg_idx++)); org += v6 * r_ctf_flags; }
203 }
204 }
205
206 if (fabs(org.x) <= 0.000001) org.x = 0;
207 if (fabs(org.y) <= 0.000001) org.y = 0;
208 string org_str = strcat(ftos(org.x), " ", ftos(org.y));
209 if (save)
210 {
211 cvar_set("g_waypointeditor_symmetrical_origin", org_str);
212 cvar_set("g_waypointeditor_symmetrical_order", ftos(ctf_flags));
213 }
214 org_str = strcat("\"", org_str, "\"");
215
216 if (ctf_flags < 2)
217 sprint(caller, strcat("Origin of symmetry based on flag positions: ", org_str, "\n"));
218 else
219 sprint(caller, strcat("Origin of symmetry based on input points: ", org_str, "\n"));
220 if (save)
221 sprint(caller, sprintf(" ^3saved to %s\n", "g_waypointeditor_symmetrical_origin"));
222
223 if (ctf_flags < 2)
224 sprint(caller, "Order of symmetry: 0 (autodetected)\n");
225 else
226 sprint(caller, strcat("Order of symmetry: ", ftos(ctf_flags), "\n"));
227 if (save)
228 sprint(caller, sprintf(" ^3saved to %s\n", "g_waypointeditor_symmetrical_order"));
229
230 if (save)
231 {
232 if (ctf_flags < 2)
233 cvar_set("g_waypointeditor_symmetrical", "0");
234 else
235 cvar_set("g_waypointeditor_symmetrical", "-1");
236 sprint(caller, strcat("g_waypointeditor_symmetrical", " has been set to ",
237 cvar_string("g_waypointeditor_symmetrical"), "\n"));
238 }
239}
240
242{
243 vector new_org = org;
245 {
246 vector map_center = havocbot_middlepoint;
249
250 new_org = Rotate(org - map_center, 2 * M_PI / ctf_flags) + map_center;
251 }
253 {
254 float m = havocbot_symmetry_axis_m;
255 float q = havocbot_symmetry_axis_q;
257 {
260 }
261
262 new_org.x = (1 / (1 + m*m)) * ((1 - m*m) * org.x + 2 * m * org.y - 2 * m * q);
263 new_org.y = (1 / (1 + m*m)) * (2 * m * org.x + (m*m - 1) * org.y + 2 * q);
264 }
265 new_org.z = org.z;
266 return new_org;
267}
268
271{
273 pl.wp_locked = trace_ent;
274}
275
277{
278 if (!wp)
279 return false;
280 return (wp.wphw00 != NULL);
281}
282
284{
285 if (!(wp_from && wp_to))
286 return false;
287
288 if (!wp_from.wphw00) return false; else if (wp_from.wphw00 == wp_to) return true;
289 if (!wp_from.wphw01) return false; else if (wp_from.wphw01 == wp_to) return true;
290 if (!wp_from.wphw02) return false; else if (wp_from.wphw02 == wp_to) return true;
291 if (!wp_from.wphw03) return false; else if (wp_from.wphw03 == wp_to) return true;
292 if (!wp_from.wphw04) return false; else if (wp_from.wphw04 == wp_to) return true;
293 if (!wp_from.wphw05) return false; else if (wp_from.wphw05 == wp_to) return true;
294 if (!wp_from.wphw06) return false; else if (wp_from.wphw06 == wp_to) return true;
295 if (!wp_from.wphw07) return false; else if (wp_from.wphw07 == wp_to) return true;
296
297 return false;
298}
299
302{
303 if (!(wp_from && wp_to))
304 return;
305
306 if (!wp_from.wphw00 || wp_from.wphw00 == wp_to) { wp_from.wphw00 = wp_to; waypoint_setupmodel(wp_from); return; }
307 if (!wp_from.wphw01 || wp_from.wphw01 == wp_to) { wp_from.wphw01 = wp_to; return; }
308 if (!wp_from.wphw02 || wp_from.wphw02 == wp_to) { wp_from.wphw02 = wp_to; return; }
309 if (!wp_from.wphw03 || wp_from.wphw03 == wp_to) { wp_from.wphw03 = wp_to; return; }
310 if (!wp_from.wphw04 || wp_from.wphw04 == wp_to) { wp_from.wphw04 = wp_to; return; }
311 if (!wp_from.wphw05 || wp_from.wphw05 == wp_to) { wp_from.wphw05 = wp_to; return; }
312 if (!wp_from.wphw06 || wp_from.wphw06 == wp_to) { wp_from.wphw06 = wp_to; return; }
313 if (!wp_from.wphw07 || wp_from.wphw07 == wp_to) { wp_from.wphw07 = wp_to; return; }
314
315 return;
316}
317
319{
320 if (!(wp_from && wp_to))
321 return;
322
323 int removed = -1;
324 if (removed < 0 && wp_from.wphw00 == wp_to) removed = 0;
325 if (removed < 0 && wp_from.wphw01 == wp_to) removed = 1;
326 if (removed < 0 && wp_from.wphw02 == wp_to) removed = 2;
327 if (removed < 0 && wp_from.wphw03 == wp_to) removed = 3;
328 if (removed < 0 && wp_from.wphw04 == wp_to) removed = 4;
329 if (removed < 0 && wp_from.wphw05 == wp_to) removed = 5;
330 if (removed < 0 && wp_from.wphw06 == wp_to) removed = 6;
331 if (removed < 0 && wp_from.wphw07 == wp_to) removed = 7;
332
333 if (removed >= 0)
334 {
335 if (removed <= 0) wp_from.wphw00 = wp_from.wphw01;
336 if (removed <= 1) wp_from.wphw01 = wp_from.wphw02;
337 if (removed <= 2) wp_from.wphw02 = wp_from.wphw03;
338 if (removed <= 3) wp_from.wphw03 = wp_from.wphw04;
339 if (removed <= 4) wp_from.wphw04 = wp_from.wphw05;
340 if (removed <= 5) wp_from.wphw05 = wp_from.wphw06;
341 if (removed <= 6) wp_from.wphw06 = wp_from.wphw07;
342 if (removed <= 7) wp_from.wphw07 = NULL;
343 if (!wp_from.wphw00)
344 waypoint_setupmodel(wp_from);
345 }
346
347 return;
348}
349
351{
352 if (wp.wphw00) waypoint_addlink(wp, wp.wphw00);
353 if (wp.wphw01) waypoint_addlink(wp, wp.wphw01);
354 if (wp.wphw02) waypoint_addlink(wp, wp.wphw02);
355 if (wp.wphw03) waypoint_addlink(wp, wp.wphw03);
356 if (wp.wphw04) waypoint_addlink(wp, wp.wphw04);
357 if (wp.wphw05) waypoint_addlink(wp, wp.wphw05);
358 if (wp.wphw06) waypoint_addlink(wp, wp.wphw06);
359 if (wp.wphw07) waypoint_addlink(wp, wp.wphw07);
360}
361
363{
365 {
366 // TODO: add some sort of visible box in edit mode for box waypoints
367 vector m1 = wp.mins;
368 vector m2 = wp.maxs;
369 setmodel(wp, MDL_WAYPOINT);
370 setsize(wp, m1, m2);
371 wp.effects = EF_LOWPRECISION;
372 if (wp.wpflags & WAYPOINTFLAG_ITEM)
373 wp.colormod = '1 0 0'; // red
374 else if (wp.wpflags & WAYPOINTFLAG_GENERATED)
375 wp.colormod = '1 1 0'; // yellow
376 else if (wp.wpflags & WAYPOINTFLAG_SUPPORT)
377 wp.colormod = '0 1 0'; // green
378 else if (wp.wpflags & WAYPOINTFLAG_CUSTOM_JP)
379 wp.colormod = '1 0.5 0'; // orange
380 else if (wp.wpflags & WAYPOINTFLAG_TELEPORT)
381 wp.colormod = '1 0.5 0'; // orange
382 else if (wp.wpflags & WAYPOINTFLAG_LADDER)
383 wp.colormod = '1 0.5 0'; // orange
384 else if (wp.wpflags & WAYPOINTFLAG_JUMP)
385 wp.colormod = '1 0.5 0'; // orange
386 else if (wp.wpflags & WAYPOINTFLAG_CROUCH)
387 wp.colormod = '0 1 1'; // cyan
388 else if (waypoint_has_hardwiredlinks(wp))
389 wp.colormod = '0.5 0 1'; // purple
390 else
391 wp.colormod = '1 1 1';
392 }
393 else
394 wp.model = "";
395}
396
398{
399 if (wp.wpflags & WAYPOINTFLAG_ITEM) return "^1Item waypoint";
400 else if (wp.wpflags & WAYPOINTFLAG_CROUCH) return "^5Crouch waypoint";
401 else if (wp.wpflags & WAYPOINTFLAG_JUMP) return "^xf80Jump waypoint";
402 else if (wp.wpflags & WAYPOINTFLAG_SUPPORT) return "^2Support waypoint";
403 else if (waypoint_has_hardwiredlinks(wp)) return "^x80fHardwired waypoint";
404 else if (wp.wpflags & WAYPOINTFLAG_LADDER) return "^3Ladder waypoint";
405 else if (wp.wpflags & WAYPOINTFLAG_TELEPORT)
406 {
407 if (!wp.wpisbox) return "^3Warpzone waypoint";
408 else if (wp.wpflags & WAYPOINTFLAG_CUSTOM_JP) return "^3Custom jumppad waypoint";
409 else
410 {
411 IL_EACH(g_jumppads, boxesoverlap(wp.absmin, wp.absmax, it.absmin, it.absmax),
412 { return "^3Jumppad waypoint"; });
413 return "^3Teleport waypoint";
414 }
415 }
416
417 return "^7Waypoint";
418}
419
421{
422 if (m1 == m2)
423 {
424 m1 -= '8 8 8';
425 m2 += '8 8 8';
426 }
427 IL_EACH(g_waypoints, boxesoverlap(m1, m2, it.absmin, it.absmax), { return it; });
428
429 return NULL;
430}
431
434{
435 if(!(f & (WAYPOINTFLAG_PERSONAL | WAYPOINTFLAG_GENERATED)) && m1 == m2)
436 {
437 entity wp_found = waypoint_get(m1, m2);
438 if (wp_found)
439 return wp_found;
440 }
441 // spawn only one destination waypoint for teleports teleporting player to the exact same spot
442 // otherwise links loaded from file would be applied only to the first destination
443 // waypoint since link format doesn't specify waypoint entities but just positions
444 if((f & WAYPOINTFLAG_GENERATED) && !(f & (WPFLAGMASK_NORELINK | WAYPOINTFLAG_PERSONAL)) && m1 == m2)
445 {
446 IL_EACH(g_waypoints, boxesoverlap(m1, m2, it.absmin, it.absmax),
447 {
448 return it;
449 });
450 }
451
452 entity w = new(waypoint);
455 w.wpflags = f;
456 w.solid = SOLID_TRIGGER;
457 w.createdtime = time;
458 setorigin(w, (m1 + m2) * 0.5);
459 setsize(w, m1 - w.origin, m2 - w.origin);
460 if (w.size)
461 w.wpisbox = true;
462
463 if(!w.wpisbox)
464 {
465 if (f & WAYPOINTFLAG_CROUCH)
466 setsize(w, PL_CROUCH_MIN_CONST - '1 1 0', PL_CROUCH_MAX_CONST + '1 1 0');
467 else
468 setsize(w, PL_MIN_CONST - '1 1 0', PL_MAX_CONST + '1 1 0');
469 if(!move_out_of_solid(w))
470 {
471 if(!(f & WAYPOINTFLAG_GENERATED))
472 {
473 LOG_TRACE("Killed a waypoint that was stuck in solid at ", vtos(w.origin));
474 delete(w);
475 return NULL;
476 }
477 else
478 {
479 if(autocvar_developer > 0)
480 {
481 LOG_INFO("A generated waypoint is stuck in solid at ", vtos(w.origin));
482 backtrace("Waypoint stuck");
483 }
484 }
485 }
486 setsize(w, '0 0 0', '0 0 0');
487 }
488
490 //waypoint_schedulerelink(w);
491
493
494 return w;
495}
496
497float trigger_push_get_push_time(entity this, vector endpos);
499{
500 entity jp = NULL;
501 IL_EACH(g_jumppads, boxesoverlap(wp_from.absmin, wp_from.absmax, it.absmin, it.absmax),
502 {
503 jp = it;
504 break;
505 });
506 if (!jp)
507 return;
508
509 float cost = trigger_push_get_push_time(jp, wp_to.origin);
510 wp_from.wp00 = wp_to;
511 wp_from.wp00mincost = cost;
512 jp.nearestwaypoint = wp_from;
513 jp.nearestwaypointtimeout = -1;
514}
515
520
522{
523 start_wp_is_spawned = false;
524 start_wp_origin = '0 0 0';
525 pl.wp_locked = NULL;
526 start_wp_is_hardwired = false;
527 start_wp_is_support = false;
528 if (warn)
529 LOG_INFO("^xf80Start waypoint has been cleared.\n");
530}
531
532void waypoint_start_hardwiredlink(entity pl, bool at_crosshair)
533{
534 entity wp = pl.nearestwaypoint;
535 if (at_crosshair)
536 {
538 wp = trace_ent;
539 }
540 string err = "";
542 err = "can't hardwire while in the process of creating a special link";
543 else if (!wp)
544 {
545 if (at_crosshair)
546 err = "couldn't find any waypoint at crosshair";
547 else
548 err = "couldn't find any waypoint nearby";
549 }
550 else if (wp.wpflags & WPFLAGMASK_NORELINK)
551 err = "can't hardwire a waypoint with special links";
552
553 if (err == "")
554 {
556 start_wp_is_spawned = true;
557 start_wp_origin = wp.origin;
558 pl.wp_locked = wp;
559 LOG_INFOF("^x80fWaypoint %s marked as hardwired link origin.\n", vtos(wp.origin));
560 }
561 else
562 {
563 start_wp_is_hardwired = false;
564 LOG_INFO("Error: ", err, "\n");
565 }
566}
567
568void waypoint_spawn_fromeditor(entity pl, bool at_crosshair, bool is_jump_wp, bool is_crouch_wp, bool is_support_wp)
569{
571 {
572 LOG_INFOF("^1Editing waypoints with a higher version number (%f) is not allowed.\n"
573 "Update Xonotic to make them editable.", waypoint_version_loaded);
574 return;
575 }
576
577 entity e = NULL, jp = NULL;
578 vector org = pl.origin;
579 if (at_crosshair)
580 {
583 if (!trace_ent)
584 org.z -= PL_MIN_CONST.z;
586 IL_EACH(g_jumppads, boxesoverlap(org + PL_MIN_CONST, org + PL_MAX_CONST, it.absmin, it.absmax),
587 {
588 jp = it;
589 break;
590 });
591 if (!jp && !start_wp_is_spawned && trace_ent)
592 {
593 if (trace_ent.wpflags & (WAYPOINTFLAG_JUMP))
594 is_jump_wp = true;
595 else if (trace_ent.wpflags & (WAYPOINTFLAG_SUPPORT))
596 is_support_wp = true;
597 }
598 }
599 if (jp || is_jump_wp || is_support_wp)
600 {
602 start_wp_is_spawned = false;
603 LOG_INFO("^xf80Spawning start waypoint...\n");
604 }
605 int ctf_flags = havocbot_symmetry_origin_order;
606 bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
610 if (sym && ctf_flags < 2)
611 ctf_flags = 2;
612 int wp_num = ctf_flags;
613
614 if(!PHYS_INPUT_BUTTON_CROUCH(pl) && !at_crosshair && !is_jump_wp && !is_support_wp)
615 {
616 // snap waypoint to item's origin if close enough
617 IL_EACH(g_items, true,
618 {
619 vector item_org = (it.absmin + it.absmax) * 0.5;
620 item_org.z = it.absmin.z - PL_MIN_CONST.z;
621 if (vlen(item_org - org) < 20)
622 {
623 org = item_org;
624 break;
625 }
626 });
627 }
628
629 vector start_org = '0 0 0';
631 {
633 LOG_INFO("^xf80Spawning destination waypoint...\n");
634 start_org = start_wp_origin;
635 }
636
637 // save org as it can be modified spawning symmetrycal waypoints
638 vector initial_origin = '0 0 0';
639 bool initial_origin_is_set = false;
640
641 LABEL(add_wp);
642
643 if (jp)
644 {
645 e = NULL;
647 && boxesoverlap(org + PL_MIN_CONST, org + PL_MAX_CONST, it.absmin, it.absmax),
648 {
649 e = it; break;
650 });
651 if (!e)
652 e = waypoint_spawn(jp.absmin - PL_MAX_CONST, jp.absmax - PL_MIN_CONST, WAYPOINTFLAG_TELEPORT);
653 if (!pl.wp_locked)
654 pl.wp_locked = e;
655 }
656 else if (is_jump_wp || is_support_wp)
657 {
658 int type_flag = (is_jump_wp) ? WAYPOINTFLAG_JUMP : WAYPOINTFLAG_SUPPORT;
659
660 entity wp_found = waypoint_get(org, org);
661 if (wp_found && !(wp_found.wpflags & type_flag))
662 {
663 LOG_INFOF("Error: can't spawn a %s waypoint over an existent waypoint of a different type\n", (is_jump_wp) ? "Jump" : "Support");
664 return;
665 }
666 e = waypoint_spawn(org, org, type_flag);
667 if (!pl.wp_locked)
668 pl.wp_locked = e;
669 }
670 else
671 e = waypoint_spawn(org, org, (is_crouch_wp) ? WAYPOINTFLAG_CROUCH : 0);
672 if(!e)
673 {
674 LOG_INFOF("Couldn't spawn waypoint at %v\n", org);
677 return;
678 }
679
680 if (!initial_origin_is_set)
681 {
682 initial_origin = e.origin;
683 initial_origin_is_set = true;
684 }
685
686 entity start_wp = NULL;
688 {
690 && boxesoverlap(start_org, start_org, it.absmin, it.absmax),
691 {
692 start_wp = it; break;
693 });
694 if(!start_wp)
695 {
696 // should not happen
697 LOG_INFOF("Couldn't find start waypoint at %v\n", start_org);
699 return;
700 }
702 {
703 if (waypoint_is_hardwiredlink(start_wp, e))
704 {
706 waypoint_removelink(start_wp, e);
707 string s = strcat(vtos(start_wp.origin), "*", vtos(e.origin));
708 LOG_INFOF("^x80fRemoved hardwired link %s.\n", s);
709 }
710 else
711 {
712 if (e.createdtime == time)
713 {
714 LOG_INFO("Error: hardwired links can be created only between 2 existing (and unconnected) waypoints.\n");
717 waypoint_spawn_fromeditor(pl, at_crosshair, is_jump_wp, is_crouch_wp, is_support_wp);
718 return;
719 }
720 if (start_wp == e)
721 {
722 LOG_INFO("Error: start and destination waypoints coincide.\n");
724 return;
725 }
726 if (waypoint_islinked(start_wp, e))
727 {
728 LOG_INFO("Error: waypoints are already linked.\n");
730 return;
731 }
732 waypoint_addlink(start_wp, e);
733 waypoint_mark_hardwiredlink(start_wp, e);
734 string s = strcat(vtos(start_wp.origin), "*", vtos(e.origin));
735 LOG_INFOF("^x80fAdded hardwired link %s.\n", s);
736 }
737 }
738 else
739 {
741 {
742 if (e.SUPPORT_WP)
743 {
744 LOG_INFOF("Waypoint %v has already a support waypoint, delete it first.\n", e.origin);
746 return;
747 }
748 // clear all links to e
749 IL_EACH(g_waypoints, it != e,
750 {
751 if (waypoint_islinked(it, e) && !waypoint_is_hardwiredlink(it, e))
752 waypoint_removelink(it, e);
753 });
754 }
755 waypoint_addlink(start_wp, e);
756 }
757 }
758
759 if (!(jp || is_jump_wp || is_support_wp || start_wp_is_hardwired))
761
762 string wp_type_str = waypoint_get_type_name(e);
763
764 bprint(strcat(wp_type_str, "^7 spawned at ", vtos(e.origin), "\n"));
765
767 {
768 pl.wp_locked = NULL;
770 waypoint_schedulerelink(start_wp);
771 if (start_wp.wpflags & WAYPOINTFLAG_TELEPORT)
772 {
773 if (start_wp.wp00_original == start_wp.wp00)
774 start_wp.wpflags &= ~WAYPOINTFLAG_CUSTOM_JP;
775 else
776 start_wp.wpflags |= WAYPOINTFLAG_CUSTOM_JP;
777 }
778 }
779
780 if (sym)
781 {
783 if (jp)
784 {
785 IL_EACH(g_jumppads, boxesoverlap(org + PL_MIN_CONST, org + PL_MAX_CONST, it.absmin, it.absmax),
786 {
787 jp = it; break;
788 });
789 }
791 start_org = waypoint_getSymmetricalPoint(start_org, ctf_flags);
792 if (vdist(org - pl.origin, >, 32))
793 {
794 if(wp_num > 2)
795 wp_num--;
796 else
797 sym = false;
798 goto add_wp;
799 }
800 }
801 if (jp || is_jump_wp || is_support_wp)
802 {
804 {
805 // we've just created a custom jumppad waypoint
806 // the next one created by the user will be the destination waypoint
807 start_wp_is_spawned = true;
808 start_wp_origin = initial_origin;
809 if (is_support_wp)
810 start_wp_is_support = true;
811 }
812 }
813 else if (start_wp_is_spawned)
814 {
816 }
817}
818
820{
821 IL_EACH(g_waypoints, it != wp,
822 {
823 if (it.SUPPORT_WP == wp)
824 {
825 it.SUPPORT_WP = NULL;
826 waypoint_schedulerelink(it); // restore incoming links
827 }
828 if (waypoint_islinked(it, wp))
829 {
830 if (waypoint_is_hardwiredlink(it, wp))
832 waypoint_removelink(it, wp);
833 }
834 });
835 delete(wp);
836}
837
839{
841 {
842 LOG_INFOF("^1Editing waypoints with a higher version number (%f) is not allowed.\n"
843 "Update Xonotic to make them editable.", waypoint_version_loaded);
844 return;
845 }
846
848
849 int ctf_flags = havocbot_symmetry_origin_order;
850 bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
854 if (sym && ctf_flags < 2)
855 ctf_flags = 2;
856 int wp_num = ctf_flags;
857
858 LABEL(remove_wp);
859 if (!e) return;
860
861 if (e.wpflags & WAYPOINTFLAG_GENERATED)
862 {
865 return;
866 }
867
869 {
870 LOG_INFO("Can't remove a waypoint with hardwired links, remove links with \"wpeditor hardwire\" first\n");
871 return;
872 }
873
874 entity wp_sym = NULL;
875 if (sym)
876 {
877 vector org = waypoint_getSymmetricalPoint(e.origin, ctf_flags);
878 FOREACH_ENTITY_CLASS("waypoint", !(it.wpflags & WAYPOINTFLAG_GENERATED), {
879 if(vdist(org - it.origin, <, 3))
880 {
881 wp_sym = it;
882 break;
883 }
884 });
885 }
886
887 bprint(strcat("Waypoint removed at ", vtos(e.origin), "\n"));
888 te_explosion(e.origin);
890
891 if (sym && wp_sym)
892 {
893 e = wp_sym;
894 if(wp_num > 2)
895 wp_num--;
896 else
897 sym = false;
898 goto remove_wp;
899 }
900
903}
904
906{
907 if (from == to || ((from.wpflags & WPFLAGMASK_NORELINK) && !(from.wpflags & (WAYPOINTFLAG_JUMP | WAYPOINTFLAG_SUPPORT))))
908 return;
909
910 entity fromwp31_prev = from.wp31;
911
912 switch (waypoint_getlinknum(from, to))
913 {
914 // fallthrough all the way
915 case 0: from.wp00 = from.wp01; from.wp00mincost = from.wp01mincost;
916 case 1: from.wp01 = from.wp02; from.wp01mincost = from.wp02mincost;
917 case 2: from.wp02 = from.wp03; from.wp02mincost = from.wp03mincost;
918 case 3: from.wp03 = from.wp04; from.wp03mincost = from.wp04mincost;
919 case 4: from.wp04 = from.wp05; from.wp04mincost = from.wp05mincost;
920 case 5: from.wp05 = from.wp06; from.wp05mincost = from.wp06mincost;
921 case 6: from.wp06 = from.wp07; from.wp06mincost = from.wp07mincost;
922 case 7: from.wp07 = from.wp08; from.wp07mincost = from.wp08mincost;
923 case 8: from.wp08 = from.wp09; from.wp08mincost = from.wp09mincost;
924 case 9: from.wp09 = from.wp10; from.wp09mincost = from.wp10mincost;
925 case 10: from.wp10 = from.wp11; from.wp10mincost = from.wp11mincost;
926 case 11: from.wp11 = from.wp12; from.wp11mincost = from.wp12mincost;
927 case 12: from.wp12 = from.wp13; from.wp12mincost = from.wp13mincost;
928 case 13: from.wp13 = from.wp14; from.wp13mincost = from.wp14mincost;
929 case 14: from.wp14 = from.wp15; from.wp14mincost = from.wp15mincost;
930 case 15: from.wp15 = from.wp16; from.wp15mincost = from.wp16mincost;
931 case 16: from.wp16 = from.wp17; from.wp16mincost = from.wp17mincost;
932 case 17: from.wp17 = from.wp18; from.wp17mincost = from.wp18mincost;
933 case 18: from.wp18 = from.wp19; from.wp18mincost = from.wp19mincost;
934 case 19: from.wp19 = from.wp20; from.wp19mincost = from.wp20mincost;
935 case 20: from.wp20 = from.wp21; from.wp20mincost = from.wp21mincost;
936 case 21: from.wp21 = from.wp22; from.wp21mincost = from.wp22mincost;
937 case 22: from.wp22 = from.wp23; from.wp22mincost = from.wp23mincost;
938 case 23: from.wp23 = from.wp24; from.wp23mincost = from.wp24mincost;
939 case 24: from.wp24 = from.wp25; from.wp24mincost = from.wp25mincost;
940 case 25: from.wp25 = from.wp26; from.wp25mincost = from.wp26mincost;
941 case 26: from.wp26 = from.wp27; from.wp26mincost = from.wp27mincost;
942 case 27: from.wp27 = from.wp28; from.wp27mincost = from.wp28mincost;
943 case 28: from.wp28 = from.wp29; from.wp28mincost = from.wp29mincost;
944 case 29: from.wp29 = from.wp30; from.wp29mincost = from.wp30mincost;
945 case 30: from.wp30 = from.wp31; from.wp30mincost = from.wp31mincost;
946 case 31: from.wp31 = NULL; from.wp31mincost = 10000000;
947 }
948
949 if (fromwp31_prev && !from.wp31)
951}
952
954{
955 if (from.wp00 == to) return 0; if (from.wp01 == to) return 1; if (from.wp02 == to) return 2; if (from.wp03 == to) return 3;
956 if (from.wp04 == to) return 4; if (from.wp05 == to) return 5; if (from.wp06 == to) return 6; if (from.wp07 == to) return 7;
957 if (from.wp08 == to) return 8; if (from.wp09 == to) return 9; if (from.wp10 == to) return 10; if (from.wp11 == to) return 11;
958 if (from.wp12 == to) return 12; if (from.wp13 == to) return 13; if (from.wp14 == to) return 14; if (from.wp15 == to) return 15;
959 if (from.wp16 == to) return 16; if (from.wp17 == to) return 17; if (from.wp18 == to) return 18; if (from.wp19 == to) return 19;
960 if (from.wp20 == to) return 20; if (from.wp21 == to) return 21; if (from.wp22 == to) return 22; if (from.wp23 == to) return 23;
961 if (from.wp24 == to) return 24; if (from.wp25 == to) return 25; if (from.wp26 == to) return 26; if (from.wp27 == to) return 27;
962 if (from.wp28 == to) return 28; if (from.wp29 == to) return 29; if (from.wp30 == to) return 30; if (from.wp31 == to) return 31;
963 return -1;
964}
965
967{
968 return (waypoint_getlinknum(from, to) >= 0);
969}
970
972{
974 {
975 if(it.wp00) it.wp00mincost = waypoint_getlinkcost(it, it.wp00);
976 if(it.wp01) it.wp01mincost = waypoint_getlinkcost(it, it.wp01);
977 if(it.wp02) it.wp02mincost = waypoint_getlinkcost(it, it.wp02);
978 if(it.wp03) it.wp03mincost = waypoint_getlinkcost(it, it.wp03);
979 if(it.wp04) it.wp04mincost = waypoint_getlinkcost(it, it.wp04);
980 if(it.wp05) it.wp05mincost = waypoint_getlinkcost(it, it.wp05);
981 if(it.wp06) it.wp06mincost = waypoint_getlinkcost(it, it.wp06);
982 if(it.wp07) it.wp07mincost = waypoint_getlinkcost(it, it.wp07);
983 if(it.wp08) it.wp08mincost = waypoint_getlinkcost(it, it.wp08);
984 if(it.wp09) it.wp09mincost = waypoint_getlinkcost(it, it.wp09);
985 if(it.wp10) it.wp10mincost = waypoint_getlinkcost(it, it.wp10);
986 if(it.wp11) it.wp11mincost = waypoint_getlinkcost(it, it.wp11);
987 if(it.wp12) it.wp12mincost = waypoint_getlinkcost(it, it.wp12);
988 if(it.wp13) it.wp13mincost = waypoint_getlinkcost(it, it.wp13);
989 if(it.wp14) it.wp14mincost = waypoint_getlinkcost(it, it.wp14);
990 if(it.wp15) it.wp15mincost = waypoint_getlinkcost(it, it.wp15);
991 if(it.wp16) it.wp16mincost = waypoint_getlinkcost(it, it.wp16);
992 if(it.wp17) it.wp17mincost = waypoint_getlinkcost(it, it.wp17);
993 if(it.wp18) it.wp18mincost = waypoint_getlinkcost(it, it.wp18);
994 if(it.wp19) it.wp19mincost = waypoint_getlinkcost(it, it.wp19);
995 if(it.wp20) it.wp20mincost = waypoint_getlinkcost(it, it.wp20);
996 if(it.wp21) it.wp21mincost = waypoint_getlinkcost(it, it.wp21);
997 if(it.wp22) it.wp22mincost = waypoint_getlinkcost(it, it.wp22);
998 if(it.wp23) it.wp23mincost = waypoint_getlinkcost(it, it.wp23);
999 if(it.wp24) it.wp24mincost = waypoint_getlinkcost(it, it.wp24);
1000 if(it.wp25) it.wp25mincost = waypoint_getlinkcost(it, it.wp25);
1001 if(it.wp26) it.wp26mincost = waypoint_getlinkcost(it, it.wp26);
1002 if(it.wp27) it.wp27mincost = waypoint_getlinkcost(it, it.wp27);
1003 if(it.wp28) it.wp28mincost = waypoint_getlinkcost(it, it.wp28);
1004 if(it.wp29) it.wp29mincost = waypoint_getlinkcost(it, it.wp29);
1005 if(it.wp30) it.wp30mincost = waypoint_getlinkcost(it, it.wp30);
1006 if(it.wp31) it.wp31mincost = waypoint_getlinkcost(it, it.wp31);
1007 });
1008}
1009
1010float waypoint_getlinearcost(float dist)
1011{
1013 return dist / (autocvar_sv_maxspeed * 1.25);
1014 return dist / autocvar_sv_maxspeed;
1015}
1016
1018{
1019 // NOTE: underwater speed factor is hardcoded in the engine too, see SV_WaterMove
1020 return dist / (autocvar_sv_maxspeed * 0.7);
1021}
1022
1024{
1025 return dist / (autocvar_sv_maxspeed * 0.5);
1026}
1027
1028float waypoint_gettravelcost(vector from, vector to, entity from_ent, entity to_ent)
1029{
1030 bool submerged_from = navigation_check_submerged_state(from_ent, from);
1031 bool submerged_to = navigation_check_submerged_state(to_ent, to);
1032
1033 if (submerged_from && submerged_to)
1034 return waypoint_getlinearcost_underwater(vlen(to - from));
1035
1036 if ((from_ent.wpflags & WAYPOINTFLAG_CROUCH) && (to_ent.wpflags & WAYPOINTFLAG_CROUCH))
1037 return waypoint_getlinearcost_crouched(vlen(to - from));
1038
1039 float c = waypoint_getlinearcost(vlen(to - from));
1040
1041 float height = from.z - to.z;
1043 {
1044 float height_cost; // fall cost
1045 if (from_ent.wpflags & WAYPOINTFLAG_JUMP)
1046 height_cost = jumpheight_time + sqrt((height + jumpheight_vec.z) / (autocvar_sv_gravity / 2));
1047 else
1048 height_cost = sqrt(height / (autocvar_sv_gravity / 2));
1049 c = waypoint_getlinearcost(vlen(vec2(to - from))); // xy distance cost
1050 if(height_cost > c)
1051 c = height_cost;
1052 }
1053
1054 // consider half path underwater
1055 if (submerged_from || submerged_to)
1056 return (c + waypoint_getlinearcost_underwater(vlen(to - from))) / 2;
1057
1058 // consider half path crouched
1059 if ((from_ent.wpflags & WAYPOINTFLAG_CROUCH) || (to_ent.wpflags & WAYPOINTFLAG_CROUCH))
1060 return (c + waypoint_getlinearcost_crouched(vlen(to - from))) / 2;
1061
1062 return c;
1063}
1064
1066{
1067 vector v1 = from.origin;
1068 vector v2 = to.origin;
1069 if (from.wpisbox)
1070 {
1071 vector m1 = from.absmin, m2 = from.absmax;
1072 v1.x = bound(m1.x, v2.x, m2.x);
1073 v1.y = bound(m1.y, v2.y, m2.y);
1074 v1.z = bound(m1.z, v2.z, m2.z);
1075 }
1076 if (to.wpisbox)
1077 {
1078 vector m1 = to.absmin, m2 = to.absmax;
1079 v2.x = bound(m1.x, v1.x, m2.x);
1080 v2.y = bound(m1.y, v1.y, m2.y);
1081 v2.z = bound(m1.z, v1.z, m2.z);
1082 }
1083 return waypoint_gettravelcost(v1, v2, from, to);
1084}
1085
1086// add a new link to the spawnfunc_waypoint, replacing the furthest link it already has
1087// if c == -1 automatically determine cost of the link
1089{
1090 if (from == to || waypoint_islinked(from, to))
1091 return;
1092 if (c == -1 && (from.wpflags & WPFLAGMASK_NORELINK) && !(from.wpflags & (WAYPOINTFLAG_JUMP | WAYPOINTFLAG_SUPPORT)))
1093 return;
1094
1095 if(c == -1)
1096 c = waypoint_getlinkcost(from, to);
1097
1098 if (from.wp31mincost < c) return;
1099 if (from.wp30mincost < c) {from.wp31 = to;from.wp31mincost = c;return;} from.wp31 = from.wp30;from.wp31mincost = from.wp30mincost;
1100 if (from.wp29mincost < c) {from.wp30 = to;from.wp30mincost = c;return;} from.wp30 = from.wp29;from.wp30mincost = from.wp29mincost;
1101 if (from.wp28mincost < c) {from.wp29 = to;from.wp29mincost = c;return;} from.wp29 = from.wp28;from.wp29mincost = from.wp28mincost;
1102 if (from.wp27mincost < c) {from.wp28 = to;from.wp28mincost = c;return;} from.wp28 = from.wp27;from.wp28mincost = from.wp27mincost;
1103 if (from.wp26mincost < c) {from.wp27 = to;from.wp27mincost = c;return;} from.wp27 = from.wp26;from.wp27mincost = from.wp26mincost;
1104 if (from.wp25mincost < c) {from.wp26 = to;from.wp26mincost = c;return;} from.wp26 = from.wp25;from.wp26mincost = from.wp25mincost;
1105 if (from.wp24mincost < c) {from.wp25 = to;from.wp25mincost = c;return;} from.wp25 = from.wp24;from.wp25mincost = from.wp24mincost;
1106 if (from.wp23mincost < c) {from.wp24 = to;from.wp24mincost = c;return;} from.wp24 = from.wp23;from.wp24mincost = from.wp23mincost;
1107 if (from.wp22mincost < c) {from.wp23 = to;from.wp23mincost = c;return;} from.wp23 = from.wp22;from.wp23mincost = from.wp22mincost;
1108 if (from.wp21mincost < c) {from.wp22 = to;from.wp22mincost = c;return;} from.wp22 = from.wp21;from.wp22mincost = from.wp21mincost;
1109 if (from.wp20mincost < c) {from.wp21 = to;from.wp21mincost = c;return;} from.wp21 = from.wp20;from.wp21mincost = from.wp20mincost;
1110 if (from.wp19mincost < c) {from.wp20 = to;from.wp20mincost = c;return;} from.wp20 = from.wp19;from.wp20mincost = from.wp19mincost;
1111 if (from.wp18mincost < c) {from.wp19 = to;from.wp19mincost = c;return;} from.wp19 = from.wp18;from.wp19mincost = from.wp18mincost;
1112 if (from.wp17mincost < c) {from.wp18 = to;from.wp18mincost = c;return;} from.wp18 = from.wp17;from.wp18mincost = from.wp17mincost;
1113 if (from.wp16mincost < c) {from.wp17 = to;from.wp17mincost = c;return;} from.wp17 = from.wp16;from.wp17mincost = from.wp16mincost;
1114 if (from.wp15mincost < c) {from.wp16 = to;from.wp16mincost = c;return;} from.wp16 = from.wp15;from.wp16mincost = from.wp15mincost;
1115 if (from.wp14mincost < c) {from.wp15 = to;from.wp15mincost = c;return;} from.wp15 = from.wp14;from.wp15mincost = from.wp14mincost;
1116 if (from.wp13mincost < c) {from.wp14 = to;from.wp14mincost = c;return;} from.wp14 = from.wp13;from.wp14mincost = from.wp13mincost;
1117 if (from.wp12mincost < c) {from.wp13 = to;from.wp13mincost = c;return;} from.wp13 = from.wp12;from.wp13mincost = from.wp12mincost;
1118 if (from.wp11mincost < c) {from.wp12 = to;from.wp12mincost = c;return;} from.wp12 = from.wp11;from.wp12mincost = from.wp11mincost;
1119 if (from.wp10mincost < c) {from.wp11 = to;from.wp11mincost = c;return;} from.wp11 = from.wp10;from.wp11mincost = from.wp10mincost;
1120 if (from.wp09mincost < c) {from.wp10 = to;from.wp10mincost = c;return;} from.wp10 = from.wp09;from.wp10mincost = from.wp09mincost;
1121 if (from.wp08mincost < c) {from.wp09 = to;from.wp09mincost = c;return;} from.wp09 = from.wp08;from.wp09mincost = from.wp08mincost;
1122 if (from.wp07mincost < c) {from.wp08 = to;from.wp08mincost = c;return;} from.wp08 = from.wp07;from.wp08mincost = from.wp07mincost;
1123 if (from.wp06mincost < c) {from.wp07 = to;from.wp07mincost = c;return;} from.wp07 = from.wp06;from.wp07mincost = from.wp06mincost;
1124 if (from.wp05mincost < c) {from.wp06 = to;from.wp06mincost = c;return;} from.wp06 = from.wp05;from.wp06mincost = from.wp05mincost;
1125 if (from.wp04mincost < c) {from.wp05 = to;from.wp05mincost = c;return;} from.wp05 = from.wp04;from.wp05mincost = from.wp04mincost;
1126 if (from.wp03mincost < c) {from.wp04 = to;from.wp04mincost = c;return;} from.wp04 = from.wp03;from.wp04mincost = from.wp03mincost;
1127 if (from.wp02mincost < c) {from.wp03 = to;from.wp03mincost = c;return;} from.wp03 = from.wp02;from.wp03mincost = from.wp02mincost;
1128 if (from.wp01mincost < c) {from.wp02 = to;from.wp02mincost = c;return;} from.wp02 = from.wp01;from.wp02mincost = from.wp01mincost;
1129 if (from.wp00mincost < c) {from.wp01 = to;from.wp01mincost = c;return;} from.wp01 = from.wp00;from.wp01mincost = from.wp00mincost;
1130 from.wp00 = to;from.wp00mincost = c;return;
1131}
1132
1134{
1135 if ((from.wpflags & WPFLAGMASK_NORELINK) && !(from.wpflags & (WAYPOINTFLAG_JUMP | WAYPOINTFLAG_SUPPORT)))
1137 else
1138 waypoint_addlink_customcost(from, to, -1);
1139
1140 if (from.wpflags & WAYPOINTFLAG_SUPPORT)
1141 to.SUPPORT_WP = from;
1142}
1143
1144// relink this spawnfunc_waypoint
1145// (precompile a list of all reachable waypoints from this spawnfunc_waypoint)
1146// (SLOW!)
1148{
1149 vector sv = '0 0 0', sv2 = '0 0 0', ev = '0 0 0', ev2 = '0 0 0', dv;
1150 float sv2_height = 0, ev2_height = 0;
1151
1153
1154 int dphitcontentsmask_save = this.dphitcontentsmask;
1156
1158
1159 //dprint("waypoint_think wpisbox = ", ftos(this.wpisbox), "\n");
1160 IL_EACH(g_waypoints, this != it,
1161 {
1162 if (boxesoverlap(this.absmin, this.absmax, it.absmin, it.absmax))
1163 {
1164 if (!(this.wpflags & WPFLAGMASK_NORELINK))
1165 waypoint_addlink(this, it);
1166 if (!(it.wpflags & WPFLAGMASK_NORELINK))
1167 waypoint_addlink(it, this);
1168 }
1169 else
1170 {
1171 ++relink_total;
1172 if(!checkpvs(this.origin, it))
1173 {
1175 continue;
1176 }
1177
1178 sv = set_tracewalk_dest_2(this, it.origin);
1179 sv2 = tracewalk_dest;
1180 sv2_height = tracewalk_dest_height;
1181 ev = set_tracewalk_dest_2(it, this.origin);
1182 ev2 = tracewalk_dest;
1183 ev2_height = tracewalk_dest_height;
1184
1185 dv = ev - sv;
1186 dv.z = 0;
1187 int maxdist = 1050;
1188 vector m1 = PL_MIN_CONST;
1189 vector m2 = PL_MAX_CONST;
1190
1191 if ((this.wpflags & WAYPOINTFLAG_CROUCH) || (it.wpflags & WAYPOINTFLAG_CROUCH))
1192 {
1195 // links from crouch wp to normal wp (and viceversa) are very short to avoid creating many links
1196 // that would be wasted due to rough travel cost calculation (the longer link is, the higher cost is)
1197 // links from crouch wp to crouch wp can be as long as normal links
1198 if (!((this.wpflags & WAYPOINTFLAG_CROUCH) && (it.wpflags & WAYPOINTFLAG_CROUCH)))
1199 maxdist = 100;
1200 }
1201
1202 if (vdist(dv, >=, maxdist)) // max search distance in XY
1203 {
1205 continue;
1206 }
1207
1209
1210 //traceline(this.origin, it.origin, false, NULL);
1211 //if (trace_fraction == 1)
1212 if (this.wpisbox || (this.wpflags & (WAYPOINTFLAG_JUMP | WAYPOINTFLAG_SUPPORT)) // forbid outgoing links
1213 || it.SUPPORT_WP) // forbid incoming links
1214 {
1215 relink_walkculled += 0.5;
1216 }
1217 else
1218 {
1219 if (tracewalk(this, sv, m1, m2, ev2, ev2_height, MOVE_NOMONSTERS))
1220 waypoint_addlink(this, it);
1221 else
1222 relink_walkculled += 0.5;
1223 }
1224
1225 // reverse direction
1226 if (it.wpisbox || (it.wpflags & (WAYPOINTFLAG_JUMP | WAYPOINTFLAG_SUPPORT)) // forbid incoming links
1227 || this.SUPPORT_WP) // forbid outgoing links
1228 {
1229 relink_walkculled += 0.5;
1230 }
1231 else
1232 {
1233 if (tracewalk(this, ev, m1, m2, sv2, sv2_height, MOVE_NOMONSTERS))
1234 waypoint_addlink(it, this);
1235 else
1236 relink_walkculled += 0.5;
1237 }
1238 }
1239 });
1240
1241 // waypoint_clearlinks preserves references to old hardwired links (.wphwXX links)
1242 // so they can be restored here when a wp is spawned over an existing one
1244
1246 this.wplinked = true;
1247 this.dphitcontentsmask = dphitcontentsmask_save;
1248
1249 setthink(this, func_null);
1250 this.nextthink = 0;
1251}
1252
1254{
1255 // clear links to other waypoints
1256 float f = 10000000;
1257 wp.wp00 = wp.wp01 = wp.wp02 = wp.wp03 = wp.wp04 = wp.wp05 = wp.wp06 = wp.wp07 = NULL;
1258 wp.wp08 = wp.wp09 = wp.wp10 = wp.wp11 = wp.wp12 = wp.wp13 = wp.wp14 = wp.wp15 = NULL;
1259 wp.wp16 = wp.wp17 = wp.wp18 = wp.wp19 = wp.wp20 = wp.wp21 = wp.wp22 = wp.wp23 = NULL;
1260 wp.wp24 = wp.wp25 = wp.wp26 = wp.wp27 = wp.wp28 = wp.wp29 = wp.wp30 = wp.wp31 = NULL;
1261
1262 wp.wp00mincost = wp.wp01mincost = wp.wp02mincost = wp.wp03mincost = wp.wp04mincost = wp.wp05mincost = wp.wp06mincost = wp.wp07mincost = f;
1263 wp.wp08mincost = wp.wp09mincost = wp.wp10mincost = wp.wp11mincost = wp.wp12mincost = wp.wp13mincost = wp.wp14mincost = wp.wp15mincost = f;
1264 wp.wp16mincost = wp.wp17mincost = wp.wp18mincost = wp.wp19mincost = wp.wp20mincost = wp.wp21mincost = wp.wp22mincost = wp.wp23mincost = f;
1265 wp.wp24mincost = wp.wp25mincost = wp.wp26mincost = wp.wp27mincost = wp.wp28mincost = wp.wp29mincost = wp.wp30mincost = wp.wp31mincost = f;
1266
1267 // don't remove references to hardwired links (.wphwXX fields)
1268
1269 wp.wplinked = false;
1270}
1271
1272// tell a spawnfunc_waypoint to relink
1274{
1275 if (wp == NULL)
1276 return;
1277
1279 wp.wpisbox = vdist(wp.size, >, 0);
1280 wp.enemy = NULL;
1281 if (!(wp.wpflags & WAYPOINTFLAG_PERSONAL))
1282 wp.owner = NULL;
1283 if (!(wp.wpflags & WPFLAGMASK_NORELINK))
1285 // schedule an actual relink on next frame
1287 wp.nextthink = time;
1288 wp.effects = EF_LOWPRECISION;
1289}
1290
1291// spawnfunc_waypoint map entity
1292spawnfunc(waypoint)
1293{
1294 IL_PUSH(g_waypoints, this);
1295
1296 setorigin(this, this.origin);
1297 // schedule a relink after other waypoints have had a chance to spawn
1298 waypoint_clearlinks(this);
1299 //waypoint_schedulerelink(this);
1300}
1301
1302// tell all waypoints to relink
1303// actually this is useful only to update relink_* stats
1313
1314#define GET_GAMETYPE_EXTENSION() ((g_race) ? ".race" : "")
1315
1316// Load waypoint links from file
1318{
1319 string s;
1320 float file, tokens, c = 0, found;
1321 entity wp_from = NULL, wp_to;
1322 vector wp_to_pos, wp_from_pos;
1323
1324 string gt_ext = GET_GAMETYPE_EXTENSION();
1325
1326 string filename = sprintf("maps/%s.waypoints.cache", strcat(mapname, gt_ext));
1327 file = fopen(filename, FILE_READ);
1328
1329 if (gt_ext != "" && file < 0)
1330 {
1331 // if race waypoint file doesn't exist load the default one
1332 filename = sprintf("maps/%s.waypoints.cache", mapname);
1333 file = fopen(filename, FILE_READ);
1334 }
1335
1336 if (file < 0)
1337 {
1338 LOG_TRACE("waypoint links load from ", filename, " failed");
1340 return false;
1341 }
1342
1343 bool parse_comments = true;
1344 float ver = 0;
1345 string links_time = string_null;
1346
1347 while ((s = fgets(file)))
1348 {
1349 if(parse_comments)
1350 {
1351 if(substring(s, 0, 2) == "//")
1352 {
1353 if(substring(s, 2, 17) == "WAYPOINT_VERSION ")
1354 ver = stof(substring(s, 19, -1));
1355 else if(substring(s, 2, 14) == "WAYPOINT_TIME ")
1356 links_time = substring(s, 16, -1);
1357 continue;
1358 }
1359 else
1360 {
1361 if(ver < WAYPOINT_VERSION || links_time != waypoint_time)
1362 {
1363 if (links_time != waypoint_time)
1364 LOG_TRACE("waypoint links for this map are not made for these waypoints.");
1365 else
1366 LOG_TRACE("waypoint links for this map are outdated.");
1367 if (g_assault)
1368 {
1369 LOG_TRACE("Assault waypoint links need to be manually updated in the editor");
1370 }
1371 else
1372 {
1373 LOG_TRACE("automatically updating...");
1375 fclose(file);
1376 return false;
1377 }
1378 }
1379 parse_comments = false;
1380 }
1381 }
1382
1383 tokens = tokenizebyseparator(s, "*");
1384
1385 if (tokens!=2)
1386 {
1387 // bad file format
1388 fclose(file);
1389 waypoint_schedulerelinkall(); // link all the autogenerated waypoints (teleporters)
1390 return false;
1391 }
1392
1393 wp_from_pos = stov(argv(0));
1394 wp_to_pos = stov(argv(1));
1395
1396 // Search "from" waypoint
1397 if(!wp_from || wp_from.origin!=wp_from_pos)
1398 {
1399 wp_from = findradius(wp_from_pos, 1);
1400 found = false;
1401 while(wp_from)
1402 {
1403 if(vdist(wp_from.origin - wp_from_pos, <, 1))
1404 if(wp_from.classname == "waypoint")
1405 {
1406 found = true;
1407 break;
1408 }
1409 wp_from = wp_from.chain;
1410 }
1411
1412 if(!found)
1413 {
1414 LOG_TRACE("waypoint_load_links: couldn't find 'from' waypoint at ", vtos(wp_from_pos));
1415 continue;
1416 }
1417 }
1418
1419 // Search "to" waypoint
1420 wp_to = findradius(wp_to_pos, 1);
1421 found = false;
1422 while(wp_to)
1423 {
1424 if(vdist(wp_to.origin - wp_to_pos, <, 1))
1425 if(wp_to.classname == "waypoint")
1426 {
1427 found = true;
1428 break;
1429 }
1430 wp_to = wp_to.chain;
1431 }
1432
1433 if(!found)
1434 {
1435 LOG_TRACE("waypoint_load_links: couldn't find 'to' waypoint at ", vtos(wp_to_pos));
1436 continue;
1437 }
1438
1439 ++c;
1440 waypoint_addlink(wp_from, wp_to);
1441 if (wp_from.wp00_original && wp_from.wp00_original != wp_from.wp00)
1442 wp_from.wpflags |= WAYPOINTFLAG_CUSTOM_JP;
1443 }
1444
1445 fclose(file);
1446
1447 LOG_TRACE("loaded ", ftos(c), " waypoint links from ", filename);
1448
1449 bool scheduled = false;
1451 {
1452 if (!it.wp00)
1453 {
1454 waypoint_schedulerelink(it);
1455 scheduled = true;
1456 }
1457 });
1458 if (scheduled)
1459 return false;
1460
1462 return true;
1463}
1464
1466{
1467 string s;
1468 float file, tokens, c = 0, found;
1469 entity wp_from = NULL, wp_to;
1470 vector wp_to_pos, wp_from_pos;
1471
1472 string gt_ext = GET_GAMETYPE_EXTENSION();
1473
1474 string filename = sprintf("maps/%s.waypoints.hardwired", strcat(mapname, gt_ext));
1475 file = fopen(filename, FILE_READ);
1476
1477 if (gt_ext != "" && file < 0)
1478 {
1479 // if race waypoint file doesn't exist load the default one
1480 filename = sprintf("maps/%s.waypoints.hardwired", mapname);
1481 file = fopen(filename, FILE_READ);
1482 }
1483
1485
1486 if (file < 0)
1487 {
1488 LOG_TRACE("waypoint links load from ", filename, " failed");
1489 return;
1490 }
1491
1492 bool is_special = false;
1493 while ((s = fgets(file)))
1494 {
1495 if(substring(s, 0, 2)=="//")
1496 continue;
1497
1498 if(substring(s, 0, 1)=="#")
1499 continue;
1500
1501 // special links start with *, so old xonotic versions don't load them
1502 is_special = false;
1503 if (substring(s, 0, 1) == "*")
1504 {
1505 is_special = true;
1506 s = substring(s, 1, -1);
1507 }
1508
1509 tokens = tokenizebyseparator(s, "*");
1510
1511 if (tokens!=2)
1512 continue;
1513
1514 wp_from_pos = stov(argv(0));
1515 wp_to_pos = stov(argv(1));
1516
1517 // Search "from" waypoint
1518 if(!wp_from || wp_from.origin!=wp_from_pos)
1519 {
1520 wp_from = findradius(wp_from_pos, 5);
1521 found = false;
1522 while(wp_from)
1523 {
1524 if(vdist(wp_from.origin - wp_from_pos, <, 5))
1525 if(wp_from.classname == "waypoint")
1526 {
1527 found = true;
1528 break;
1529 }
1530 wp_from = wp_from.chain;
1531 }
1532
1533 if(!found)
1534 {
1535 s = strcat(((is_special) ? "special link " : "hardwired link "), s);
1536 LOG_INFO("NOTICE: Can not find origin waypoint of the ", s, ". Path skipped");
1537 continue;
1538 }
1539 }
1540
1541 // Search "to" waypoint
1542 wp_to = findradius(wp_to_pos, 5);
1543 found = false;
1544 while(wp_to)
1545 {
1546 if(vdist(wp_to.origin - wp_to_pos, <, 5))
1547 if(wp_to.classname == "waypoint")
1548 {
1549 found = true;
1550 break;
1551 }
1552 wp_to = wp_to.chain;
1553 }
1554
1555 if(!found)
1556 {
1557 s = strcat(((is_special) ? "special link " : "hardwired link "), s);
1558 LOG_INFO("NOTICE: Can not find destination waypoint of the ", s, ". Path skipped");
1559 continue;
1560 }
1561
1562 ++c;
1563
1564 if (!is_special)
1565 {
1566 waypoint_addlink(wp_from, wp_to);
1567 waypoint_mark_hardwiredlink(wp_from, wp_to);
1568 }
1569 else if ((wp_from.wpflags & WPFLAGMASK_NORELINK) && ((wp_from.wpflags & (WAYPOINTFLAG_JUMP | WAYPOINTFLAG_SUPPORT))
1570 || (wp_from.wpisbox && (wp_from.wpflags & WAYPOINTFLAG_TELEPORT))))
1571 {
1572 waypoint_addlink(wp_from, wp_to);
1573 }
1574 }
1575
1576 fclose(file);
1577
1578 LOG_TRACE("loaded ", ftos(c), " waypoint links from maps/", mapname, ".waypoints.hardwired");
1579}
1580
1582{
1583 switch(i)
1584 {
1585 case 0: return w.wp00mincost;
1586 case 1: return w.wp01mincost;
1587 case 2: return w.wp02mincost;
1588 case 3: return w.wp03mincost;
1589 case 4: return w.wp04mincost;
1590 case 5: return w.wp05mincost;
1591 case 6: return w.wp06mincost;
1592 case 7: return w.wp07mincost;
1593 case 8: return w.wp08mincost;
1594 case 9: return w.wp09mincost;
1595 case 10: return w.wp10mincost;
1596 case 11: return w.wp11mincost;
1597 case 12: return w.wp12mincost;
1598 case 13: return w.wp13mincost;
1599 case 14: return w.wp14mincost;
1600 case 15: return w.wp15mincost;
1601 case 16: return w.wp16mincost;
1602 case 17: return w.wp17mincost;
1603 case 18: return w.wp18mincost;
1604 case 19: return w.wp19mincost;
1605 case 20: return w.wp20mincost;
1606 case 21: return w.wp21mincost;
1607 case 22: return w.wp22mincost;
1608 case 23: return w.wp23mincost;
1609 case 24: return w.wp24mincost;
1610 case 25: return w.wp25mincost;
1611 case 26: return w.wp26mincost;
1612 case 27: return w.wp27mincost;
1613 case 28: return w.wp28mincost;
1614 case 29: return w.wp29mincost;
1615 case 30: return w.wp30mincost;
1616 case 31: return w.wp31mincost;
1617 default: return -1;
1618 }
1619}
1620
1622{
1623 switch(i)
1624 {
1625 case 0:return w.wp00;
1626 case 1:return w.wp01;
1627 case 2:return w.wp02;
1628 case 3:return w.wp03;
1629 case 4:return w.wp04;
1630 case 5:return w.wp05;
1631 case 6:return w.wp06;
1632 case 7:return w.wp07;
1633 case 8:return w.wp08;
1634 case 9:return w.wp09;
1635 case 10:return w.wp10;
1636 case 11:return w.wp11;
1637 case 12:return w.wp12;
1638 case 13:return w.wp13;
1639 case 14:return w.wp14;
1640 case 15:return w.wp15;
1641 case 16:return w.wp16;
1642 case 17:return w.wp17;
1643 case 18:return w.wp18;
1644 case 19:return w.wp19;
1645 case 20:return w.wp20;
1646 case 21:return w.wp21;
1647 case 22:return w.wp22;
1648 case 23:return w.wp23;
1649 case 24:return w.wp24;
1650 case 25:return w.wp25;
1651 case 26:return w.wp26;
1652 case 27:return w.wp27;
1653 case 28:return w.wp28;
1654 case 29:return w.wp29;
1655 case 30:return w.wp30;
1656 case 31:return w.wp31;
1657 default:return NULL;
1658 }
1659}
1660
1661// Save all hardwired waypoint links to a file
1663{
1664 string gt_ext = GET_GAMETYPE_EXTENSION();
1665
1666 string filename = sprintf("maps/%s.waypoints.hardwired", strcat(mapname, gt_ext));
1667 int file = fopen(filename, FILE_WRITE);
1668 if (file < 0)
1669 {
1670 LOG_TRACE("waypoint hardwired links ", filename, " creation failed");
1671 return;
1672 }
1673
1674 // write hardwired links to file
1675 int count = 0;
1676 fputs(file, "// HARDWIRED LINKS\n");
1678 {
1679 for (int j = 0; j < 32; ++j)
1680 {
1681 entity link = waypoint_get_link(it, j);
1682 if (waypoint_is_hardwiredlink(it, link))
1683 {
1684 // NOTE: vtos rounds vector components to 1 decimal place
1685 string s = strcat(vtos(it.origin), "*", vtos(link.origin), "\n");
1686 fputs(file, s);
1687 ++count;
1688 }
1689 }
1690 });
1691
1692 // write special links to file
1693 int count2 = 0;
1694 fputs(file, "\n// SPECIAL LINKS\n");
1696 {
1697 for (int j = 0; j < 32; ++j)
1698 {
1699 entity link = waypoint_get_link(it, j);
1700 if (link)
1701 {
1702 // NOTE: vtos rounds vector components to 1 decimal place
1703 string s = strcat("*", vtos(it.origin), "*", vtos(link.origin), "\n");
1704 fputs(file, s);
1705 ++count2;
1706 }
1707 }
1708 });
1709
1710 fclose(file);
1711
1712 LOG_INFOF("saved %d hardwired links and %d special links to %s", count, count2, filename);
1713}
1714
1715// Save all waypoint links to a file
1717{
1718 string gt_ext = GET_GAMETYPE_EXTENSION();
1719
1720 string filename = sprintf("maps/%s.waypoints.cache", strcat(mapname, gt_ext));
1721 int file = fopen(filename, FILE_WRITE);
1722 if (file < 0)
1723 {
1724 LOG_INFOF("waypoint link save to %s failed", filename);
1725 return;
1726 }
1727
1728 fputs(file, strcat("//", "WAYPOINT_VERSION ", ftos_decimals(WAYPOINT_VERSION, 2), "\n"));
1729 if (waypoint_time != "")
1730 fputs(file, strcat("//", "WAYPOINT_TIME ", waypoint_time, "\n"));
1731
1732 int c = 0;
1734 {
1735 for(int j = 0; j < 32; ++j)
1736 {
1737 entity link = waypoint_get_link(it, j);
1738 if (link && !waypoint_is_hardwiredlink(it, link))
1739 {
1740 // NOTE: vtos rounds vector components to 1 decimal place
1741 string s = strcat(vtos(it.origin), "*", vtos(link.origin), "\n");
1742 fputs(file, s);
1743 ++c;
1744 }
1745 }
1746 });
1747 fclose(file);
1748
1750
1751 LOG_INFOF("saved %d waypoint links to %s", c, filename);
1752}
1753
1754// save waypoints to gamedir/data/maps/mapname.waypoints
1756{
1758 {
1759 LOG_INFOF("^1Overwriting waypoints with a higher version number (%f) is not allowed.\n"
1760 "Update Xonotic to make them editable.", waypoint_version_loaded);
1761 return;
1762 }
1763 string gt_ext = GET_GAMETYPE_EXTENSION();
1764
1765 string filename = sprintf("maps/%s.waypoints", strcat(mapname, gt_ext));
1766 int file = fopen(filename, FILE_WRITE);
1767 if (file < 0)
1768 {
1769 waypoint_save_links(); // save anyway?
1771
1772 LOG_INFOF("waypoint links: save to %s failed", filename);
1773 return;
1774 }
1775
1777 string sym_str = ftos(sym);
1778 if (sym == -1 || (sym == 1 && autocvar_g_waypointeditor_symmetrical_order >= 2))
1779 {
1780 if (sym == 1)
1781 {
1782 sym_str = cons(sym_str, "-");
1783 sym_str = cons(sym_str, "-");
1784 }
1785 else
1786 {
1789 }
1792 }
1794 {
1797 }
1798
1799 // a group of 3 comments doesn't break compatibility with older Xonotic versions
1800 // (they are read as a waypoint with origin '0 0 0' and flag 0 though)
1801 fputs(file, strcat("//", "WAYPOINT_VERSION ", ftos_decimals(WAYPOINT_VERSION, 2), "\n"));
1802 fputs(file, strcat("//", "WAYPOINT_SYMMETRY ", sym_str, "\n"));
1803
1804 strcpy(waypoint_time, strftime(true, "%Y-%m-%d %H:%M:%S"));
1805 fputs(file, strcat("//", "WAYPOINT_TIME ", waypoint_time, "\n"));
1806 //fputs(file, strcat("//", "\n"));
1807 //fputs(file, strcat("//", "\n"));
1808 //fputs(file, strcat("//", "\n"));
1809
1810 int c = 0;
1811 IL_EACH(g_waypoints, true,
1812 {
1813 if(it.wpflags & WAYPOINTFLAG_GENERATED)
1814 continue;
1815
1816 string s;
1817 // NOTE: vtos rounds vector components to 1 decimal place
1818 s = strcat(vtos(it.origin + it.mins), "\n");
1819 s = strcat(s, vtos(it.origin + it.maxs));
1820 s = strcat(s, "\n");
1821 s = strcat(s, ftos(it.wpflags));
1822 s = strcat(s, "\n");
1823 fputs(file, s);
1824 c++;
1825 });
1826 fclose(file);
1829
1831
1833 LOG_INFOF("saved %d waypoints to %s", c, filename);
1834}
1835
1836// load waypoints from file
1838{
1839 string s;
1840 int file, cwp, cwb, fl;
1841 vector m1, m2;
1842 cwp = 0;
1843 cwb = 0;
1844
1845 string gt_ext = GET_GAMETYPE_EXTENSION();
1846
1847 string filename = sprintf("maps/%s.waypoints", strcat(mapname, gt_ext));
1848 file = fopen(filename, FILE_READ);
1849
1850 if (gt_ext != "" && file < 0)
1851 {
1852 // if race waypoint file doesn't exist load the default one
1853 filename = sprintf("maps/%s.waypoints", mapname);
1854 file = fopen(filename, FILE_READ);
1855 }
1856
1857 if (file < 0)
1858 {
1859 LOG_TRACE("waypoint load from ", filename, " failed");
1860 return 0;
1861 }
1862
1863 bool parse_comments = true;
1864 float ver = 0;
1865 float sym = 0;
1866 float sym_param1 = 0, sym_param2 = 0, sym_param3 = 0;
1867
1868 while ((s = fgets(file)))
1869 {
1870 if(parse_comments)
1871 {
1872 if(substring(s, 0, 2) == "//")
1873 {
1874 if(substring(s, 2, 17) == "WAYPOINT_VERSION ")
1875 ver = stof(substring(s, 19, -1));
1876 else if(substring(s, 2, 18) == "WAYPOINT_SYMMETRY ")
1877 {
1878 int tokens = tokenizebyseparator(substring(s, 20, -1), " ");
1879 if (tokens) { sym = stof(argv(0)); }
1880 if (tokens > 1) { sym_param1 = stof(argv(1)); }
1881 if (tokens > 2) { sym_param2 = stof(argv(2)); }
1882 if (tokens > 3) { sym_param3 = stof(argv(3)); }
1883 }
1884 else if(substring(s, 2, 14) == "WAYPOINT_TIME ")
1885 strcpy(waypoint_time, substring(s, 16, -1));
1886 continue;
1887 }
1888 else
1889 {
1890 if(floor(ver) < floor(WAYPOINT_VERSION))
1891 {
1892 LOG_TRACE("waypoints for this map are outdated");
1893 LOG_TRACE("please update them in the editor");
1894 }
1895 parse_comments = false;
1896 }
1897 }
1898 m1 = stov(s);
1899 s = fgets(file);
1900 if (!s)
1901 break;
1902 m2 = stov(s);
1903 s = fgets(file);
1904 if (!s)
1905 break;
1906 fl = stof(s);
1908 waypoint_spawn(m1, m2, fl);
1909 if (m1 == m2)
1910 cwp = cwp + 1;
1911 else
1912 cwb = cwb + 1;
1913 }
1914 fclose(file);
1916 LOG_TRACE("loaded ", ftos(cwp), " waypoints and ", ftos(cwb), " wayboxes from maps/", mapname, ".waypoints");
1917
1919 {
1920 string sym_str = "";
1921 cvar_set("g_waypointeditor_symmetrical", ftos(sym));
1922 if (sym == 1 && sym_param3 < 2)
1923 cvar_set("g_waypointeditor_symmetrical_order", "0"); // make sure this is reset if not loaded
1924 if (sym == -1 || (sym == 1 && sym_param3 >= 2))
1925 {
1926 string params;
1927 if (sym == 1)
1928 params = cons("-", "-");
1929 else
1930 {
1931 params = cons(ftos(sym_param1), ftos(sym_param2));
1932 cvar_set("g_waypointeditor_symmetrical_origin", params);
1933 }
1934 cvar_set("g_waypointeditor_symmetrical_order", ftos(sym_param3));
1935 sym_str = strcat(ftos(sym), " with origin ", params, " and order ", ftos(sym_param3));
1936 }
1937 else if (sym == -2)
1938 {
1939 string params = strcat(ftos(sym_param1), " ", ftos(sym_param2));
1940 cvar_set("g_waypointeditor_symmetrical_axis", params);
1941 sym_str = strcat(ftos(sym), " with axis ", params);
1942 }
1943 else
1944 sym_str = ftos(sym);
1945 if (sym_str != "")
1946 LOG_INFO("Waypoint editor: loaded symmetry ", sym_str);
1947 LOG_INFO(strcat("g_waypointeditor_symmetrical", " has been set to ", cvar_string("g_waypointeditor_symmetrical")));
1948 }
1949
1951 LOG_INFOF("^1Editing waypoints with a higher version number (%f) is not allowed.\n"
1952 "Update Xonotic to make them editable.", waypoint_version_loaded);
1953
1954 return cwp + cwb;
1955}
1956
1957#define waypoint_fixorigin(position, tracetest_ent) \
1958 waypoint_fixorigin_down_dir(position, tracetest_ent, '0 0 -1')
1959
1960vector waypoint_fixorigin_down_dir(vector position, entity tracetest_ent, vector down_dir)
1961{
1962 vector endpos = position + down_dir * 3000;
1963 tracebox(position + '0 0 1', PL_MIN_CONST, PL_MAX_CONST, endpos, MOVE_NOMONSTERS, tracetest_ent);
1965 tracebox(position + '0 0 1' * (1 - PL_MIN_CONST.z / 2), PL_MIN_CONST, PL_MAX_CONST, endpos, MOVE_NOMONSTERS, tracetest_ent);
1967 tracebox(position + '0 0 1' * (1 - PL_MIN_CONST.z), PL_MIN_CONST, PL_MAX_CONST, endpos, MOVE_NOMONSTERS, tracetest_ent);
1968 if(trace_fraction < 1)
1969 position = trace_endpos;
1970 return position;
1971}
1972
1974{
1975 // Fix the waypoint altitude if necessary
1977
1978 // don't spawn an item spawnfunc_waypoint if it already exists
1979 IL_EACH(g_waypoints, true,
1980 {
1981 if(it.wpisbox)
1982 {
1983 if(boxesoverlap(org, org, it.absmin, it.absmax))
1984 {
1985 e.nearestwaypoint = it;
1986 return;
1987 }
1988 }
1989 else
1990 {
1991 if(vdist(it.origin - org, <, 16))
1992 {
1993 e.nearestwaypoint = it;
1994 return;
1995 }
1996 }
1997 });
1998
2000}
2001
2003{
2005 return;
2006
2007 waypoint_spawnforitem_force(e, e.origin);
2008}
2009
2010void waypoint_spawnforteleporter_boxes(entity e, int teleport_flag, vector org1, vector org2, vector destination1, vector destination2, float timetaken)
2011{
2012 entity w;
2013 entity dw;
2014 w = waypoint_spawn(org1, org2, WAYPOINTFLAG_GENERATED | teleport_flag);
2015 dw = waypoint_spawn(destination1, destination2, WAYPOINTFLAG_GENERATED);
2016 // one way link to the destination
2017 w.wp00_original = dw;
2018 w.wp00 = dw;
2019 w.wp00mincost = timetaken; // this is just for jump pads
2020 // the teleporter's nearest spawnfunc_waypoint is this one
2021 // (teleporters are not goals, so this is probably useless)
2022 e.nearestwaypoint = w;
2023 e.nearestwaypointtimeout = -1;
2024}
2025
2027{
2028 float src_angle = e.warpzone_angles.x;
2029 while (src_angle < -180) src_angle += 360;
2030 while (src_angle > 180) src_angle -= 360;
2031
2032 float dest_angle = e.enemy.warpzone_angles.x;
2033 while (dest_angle < -180) dest_angle += 360;
2034 while (dest_angle > 180) dest_angle -= 360;
2035
2036 // no waypoints for warpzones pointing upwards, they can't be used by the bots
2037 if (src_angle == -90 || dest_angle == -90)
2038 return;
2039
2040 makevectors(e.warpzone_angles);
2041 vector src = (e.absmin + e.absmax) * 0.5;
2042 src += ((e.warpzone_origin - src) * v_forward) * v_forward + 16 * v_right;
2043 vector down_dir_src = -v_up;
2044
2045 makevectors(e.enemy.warpzone_angles);
2046 vector dest = (e.enemy.absmin + e.enemy.absmax) * 0.5;
2047 dest += ((e.enemy.warpzone_origin - dest) * v_forward) * v_forward - 16 * v_right;
2048 vector down_dir_dest = -v_up;
2049
2050 int extra_flag = 0;
2051 // don't snap to the ground waypoints for source warpzones pointing downwards
2052 if (src_angle != 90)
2053 {
2054 src = waypoint_fixorigin_down_dir(src, tracetest_ent, down_dir_src);
2055 dest = waypoint_fixorigin_down_dir(dest, tracetest_ent, down_dir_dest);
2056 // oblique warpzones need a jump otherwise bots gets stuck
2057 if (src_angle != 0)
2058 extra_flag = WAYPOINTFLAG_JUMP;
2059 }
2060
2061 waypoint_spawnforteleporter_boxes(e, WAYPOINTFLAG_TELEPORT | extra_flag, src, src, dest, dest, 0);
2062}
2063
2064void waypoint_spawnforteleporter(entity e, vector destination, float timetaken, entity tracetest_ent)
2065{
2066 destination = waypoint_fixorigin(destination, tracetest_ent);
2067 waypoint_spawnforteleporter_boxes(e, WAYPOINTFLAG_TELEPORT, e.absmin - PL_MAX_CONST, e.absmax - PL_MIN_CONST, destination, destination, timetaken);
2068}
2069
2071{
2072 entity w;
2073
2074 // drop the waypoint to a proper location:
2075 // first move it up by a player height
2076 // then move it down to hit the floor with player bbox size
2077 position = waypoint_fixorigin(position, this);
2078
2080 w.nearestwaypoint = NULL;
2081 w.nearestwaypointtimeout = 0;
2082 w.owner = this;
2083
2085
2086 return w;
2087}
2088
2089void waypoint_showlink(entity wp1, entity wp2, int display_type)
2090{
2091 if (!(wp1 && wp2))
2092 return;
2093
2095 te_beam(NULL, wp1.origin, wp2.origin);
2096 else if (display_type == 1)
2097 te_lightning2(NULL, wp1.origin, wp2.origin);
2098}
2099
2100void waypoint_showlinks_to(entity wp, int display_type)
2101{
2102 IL_EACH(g_waypoints, it != wp,
2103 {
2104 if (waypoint_islinked(it, wp))
2105 waypoint_showlink(it, wp, display_type);
2106 });
2107}
2108
2109void waypoint_showlinks_from(entity wp, int display_type)
2110{
2111 waypoint_showlink(wp, wp.wp00, display_type); waypoint_showlink(wp, wp.wp16, display_type);
2112 waypoint_showlink(wp, wp.wp01, display_type); waypoint_showlink(wp, wp.wp17, display_type);
2113 waypoint_showlink(wp, wp.wp02, display_type); waypoint_showlink(wp, wp.wp18, display_type);
2114 waypoint_showlink(wp, wp.wp03, display_type); waypoint_showlink(wp, wp.wp19, display_type);
2115 waypoint_showlink(wp, wp.wp04, display_type); waypoint_showlink(wp, wp.wp20, display_type);
2116 waypoint_showlink(wp, wp.wp05, display_type); waypoint_showlink(wp, wp.wp21, display_type);
2117 waypoint_showlink(wp, wp.wp06, display_type); waypoint_showlink(wp, wp.wp22, display_type);
2118 waypoint_showlink(wp, wp.wp07, display_type); waypoint_showlink(wp, wp.wp23, display_type);
2119 waypoint_showlink(wp, wp.wp08, display_type); waypoint_showlink(wp, wp.wp24, display_type);
2120 waypoint_showlink(wp, wp.wp09, display_type); waypoint_showlink(wp, wp.wp25, display_type);
2121 waypoint_showlink(wp, wp.wp10, display_type); waypoint_showlink(wp, wp.wp26, display_type);
2122 waypoint_showlink(wp, wp.wp11, display_type); waypoint_showlink(wp, wp.wp27, display_type);
2123 waypoint_showlink(wp, wp.wp12, display_type); waypoint_showlink(wp, wp.wp28, display_type);
2124 waypoint_showlink(wp, wp.wp13, display_type); waypoint_showlink(wp, wp.wp29, display_type);
2125 waypoint_showlink(wp, wp.wp14, display_type); waypoint_showlink(wp, wp.wp30, display_type);
2126 waypoint_showlink(wp, wp.wp15, display_type); waypoint_showlink(wp, wp.wp31, display_type);
2127}
2128
2130{
2131 IL_EACH(g_waypoints, true, {
2132 it.solid = SOLID_BSP;
2133 if (!it.wpisbox)
2134 setsize(it, '-16 -16 -16', '16 16 16');
2135 });
2136
2138
2139 IL_EACH(g_waypoints, true, {
2140 it.solid = SOLID_TRIGGER;
2141 if (!it.wpisbox)
2142 setsize(it, '0 0 0', '0 0 0');
2143 });
2144
2145 if (trace_ent.classname != "waypoint")
2146 trace_ent = NULL;
2147 else if (!trace_ent.wpisbox)
2148 trace_endpos = trace_ent.origin;
2149}
2150
2152{
2154 return;
2156 FOREACH_CLIENT(IS_PLAYER(it) && !it.isbot,
2157 {
2158 int display_type = 0;
2159 if (wasfreed(it.wp_aimed))
2160 it.wp_aimed = NULL;
2161 if (wasfreed(it.wp_locked))
2162 it.wp_locked = NULL;
2163 entity head = it.wp_locked;
2164 if (!head)
2165 head = navigation_findnearestwaypoint(it, false);
2166 it.nearestwaypoint = head; // mainly useful for debug
2167 it.nearestwaypointtimeout = time + 2; // while I'm at it...
2168 if (IS_ONGROUND(it) || it.waterlevel > WATERLEVEL_NONE || it.wp_locked)
2169 display_type = 1; // default
2170 else if(waypoint_has_hardwiredlinks(head))
2171 display_type = 2; // only hardwired
2172
2173 if (display_type)
2174 {
2175 //navigation_testtracewalk = true;
2176 //print("currently selected WP is ", etos(head), "\n");
2177 //navigation_testtracewalk = false;
2178 if (head)
2179 {
2180 te_lightning2(NULL, head.origin, it.origin);
2181 if(PHYS_INPUT_BUTTON_CROUCH(it))
2182 waypoint_showlinks_to(head, display_type);
2183 else
2184 waypoint_showlinks_from(head, display_type);
2185 }
2186 }
2187 string str;
2188 entity wp = NULL;
2189 if (vdist(vec2(it.velocity), <, autocvar_sv_maxspeed * 1.1))
2190 {
2191 crosshair_trace_waypoints(it);
2192 if (trace_ent)
2193 {
2194 wp = trace_ent;
2195 if (wp != it.wp_aimed)
2196 {
2197 string wp_type_str = waypoint_get_type_name(wp);
2198 str = sprintf("\necho Entity %d: %s^7, flags: %d, origin: %s\n", etof(wp), wp_type_str, wp.wpflags, vtos(wp.origin));
2199 if (wp.wpisbox)
2200 str = strcat(str, sprintf("echo \" absmin: %s, absmax: %s\"\n", vtos(wp.absmin), vtos(wp.absmax)));
2201 stuffcmd(it, str);
2202 str = sprintf("Entity %d: %s^7\nflags: %d\norigin: %s", etof(wp), wp_type_str, wp.wpflags, vtos(wp.origin));
2203 if (wp.wpisbox)
2204 str = strcat(str, sprintf(" \nabsmin: %s\nabsmax: %s", vtos(wp.absmin), vtos(wp.absmax)));
2205 debug_text_3d(wp.origin, str, 0, 7, '0 0 0');
2206 }
2207 }
2208 }
2209 if (it.wp_aimed != wp)
2210 it.wp_aimed = wp;
2211 });
2212}
2213
2215{
2216 tracebox(v, PL_MIN_CONST, PL_MAX_CONST, v + '0 0 -64', MOVE_NOMONSTERS, NULL);
2217 if(trace_fraction >= 1)
2218 return 0;
2219 return 1;
2220}
2221
2223{
2224 IL_EACH(g_waypoints, boxesoverlap(v - '32 32 32', v + '32 32 32', it.absmin, it.absmax),
2225 {
2226 // if a matching spawnfunc_waypoint already exists, don't add a duplicate
2227 return 0;
2228 });
2229
2230 waypoint_schedulerelink(p.(fld) = waypoint_spawn(v, v, f));
2231 return 1;
2232}
2233
2234// return value:
2235// 1 = WP created
2236// 0 = no action needed
2237// -1 = temp fail, try from world too
2238// -2 = permanent fail, do not retry
2239float botframe_autowaypoints_fix_from(entity p, float walkfromwp, entity wp, .entity fld)
2240{
2241 // make it possible to go from p to wp, if we can
2242 // if wp is NULL, nearest is chosen
2243
2244 entity w;
2245 vector porg;
2246 float t, tmin, tmax;
2247 vector o;
2248 vector save;
2249
2250 if(!botframe_autowaypoints_fixdown(p.origin))
2251 return -2;
2252 porg = trace_endpos;
2253
2254 if(wp)
2255 {
2256 // if any WP w fulfills wp -> w -> porg and w is closer than wp, then switch from wp to w
2257
2258 // if wp -> porg, then OK
2259 float maxdist;
2260 if(navigation_waypoint_will_link(wp.origin, porg, p, porg, 0, wp.origin, 0, walkfromwp, 1050))
2261 {
2262 // we may find a better one
2263 maxdist = vlen(wp.origin - porg);
2264 }
2265 else
2266 {
2267 // accept any "good"
2268 maxdist = 2100;
2269 }
2270
2271 float bestdist = maxdist;
2272 IL_EACH(g_waypoints, it != wp && !(it.wpflags & WPFLAGMASK_NORELINK),
2273 {
2274 float d = vlen(wp.origin - it.origin) + vlen(it.origin - porg);
2275 if(d < bestdist)
2276 if(navigation_waypoint_will_link(wp.origin, it.origin, p, it.origin, 0, wp.origin, 0, walkfromwp, 1050))
2277 if(navigation_waypoint_will_link(it.origin, porg, p, porg, 0, it.origin, 0, walkfromwp, 1050))
2278 {
2279 bestdist = d;
2280 p.(fld) = it;
2281 }
2282 });
2283 if(bestdist < maxdist)
2284 {
2285 LOG_INFO("update chain to new nearest WP ", etos(p.(fld)));
2286 return 0;
2287 }
2288
2289 if(bestdist < 2100)
2290 {
2291 // we know maxdist < 2100
2292 // so wp -> porg is still valid
2293 // all is good
2294 p.(fld) = wp;
2295 return 0;
2296 }
2297
2298 // otherwise, no existing WP can fix our issues
2299 }
2300 else
2301 {
2302 save = p.origin;
2303 setorigin(p, porg);
2304 w = navigation_findnearestwaypoint(p, walkfromwp);
2305 setorigin(p, save);
2306 if(w)
2307 {
2308 p.(fld) = w;
2309 return 0;
2310 }
2311 }
2312
2313 tmin = 0;
2314 tmax = 1;
2315 for (;;)
2316 {
2317 if(tmax - tmin < 0.001)
2318 {
2319 // did not get a good candidate
2320 return -1;
2321 }
2322
2323 t = (tmin + tmax) * 0.5;
2324 o = antilag_takebackorigin(p, CS(p), time - t);
2326 return -2;
2327 o = trace_endpos;
2328
2329 if(wp)
2330 {
2331 if(!navigation_waypoint_will_link(wp.origin, o, p, o, 0, wp.origin, 0, walkfromwp, 1050))
2332 {
2333 // we cannot walk from wp.origin to o
2334 // get closer to tmax
2335 tmin = t;
2336 continue;
2337 }
2338 }
2339 else
2340 {
2341 save = p.origin;
2342 setorigin(p, o);
2343 w = navigation_findnearestwaypoint(p, walkfromwp);
2344 setorigin(p, save);
2345 if(!w)
2346 {
2347 // we cannot walk from any WP to o
2348 // get closer to tmax
2349 tmin = t;
2350 continue;
2351 }
2352 }
2353
2354 // if we get here, o is valid regarding waypoints
2355 // check if o is connected right to the player
2356 // we break if it succeeds, as that means o is a good waypoint location
2357 if(navigation_waypoint_will_link(o, porg, p, porg, 0, o, 0, walkfromwp, 1050))
2358 break;
2359
2360 // o is no good, we need to get closer to the player
2361 tmax = t;
2362 }
2363
2364 LOG_INFO("spawning a waypoint for connecting to ", etos(wp));
2365 botframe_autowaypoints_createwp(o, p, fld, 0);
2366 return 1;
2367}
2368
2369// automatically create missing waypoints
2370void botframe_autowaypoints_fix(entity p, float walkfromwp, .entity fld)
2371{
2372 float r = botframe_autowaypoints_fix_from(p, walkfromwp, p.(fld), fld);
2373 if(r != -1)
2374 return;
2375 r = botframe_autowaypoints_fix_from(p, walkfromwp, NULL, fld);
2376 if(r != -1)
2377 return;
2378
2379 LOG_INFO("emergency: got no good nearby WP to build a link from, starting a new chain");
2380 if(!botframe_autowaypoints_fixdown(p.origin))
2381 return; // shouldn't happen, caught above
2383}
2384
2386{
2387 IL_EACH(g_items, it.bot_pickup,
2388 {
2389 // NOTE: this protects waypoints if they're the ONLY nearest
2390 // waypoint. That's the intention.
2391 navigation_findnearestwaypoint(it, false); // Walk TO item.
2392 navigation_findnearestwaypoint(it, true); // Walk FROM item.
2393 });
2394 IL_EACH(g_waypoints, true,
2395 {
2396 it.wpflags |= WAYPOINTFLAG_DEAD_END;
2397 it.wpflags &= ~WAYPOINTFLAG_USEFUL;
2398 // WP is useful if:
2399 if (it.wpflags & WAYPOINTFLAG_ITEM)
2400 it.wpflags |= WAYPOINTFLAG_USEFUL;
2401 if (it.wpflags & WAYPOINTFLAG_TELEPORT)
2402 it.wpflags |= WAYPOINTFLAG_USEFUL;
2403 if (it.wpflags & WAYPOINTFLAG_LADDER)
2404 it.wpflags |= WAYPOINTFLAG_USEFUL;
2405 if (it.wpflags & WAYPOINTFLAG_PROTECTED)
2406 it.wpflags |= WAYPOINTFLAG_USEFUL;
2407 // b) WP is closest WP for an item/spawnpoint/other entity
2408 // This has been done above by protecting these WPs.
2409 });
2410 // c) There are w1, w, w2 so that w1 -> w, w -> w2 and not w1 -> w2.
2411 IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_PERSONAL),
2412 {
2413 for (int m = 0; m < 32; ++m)
2414 {
2415 entity w = waypoint_get_link(it, m);
2416 if (!w)
2417 break;
2418 if (w.wpflags & WAYPOINTFLAG_PERSONAL)
2419 continue;
2420 if (w.wpflags & WAYPOINTFLAG_USEFUL)
2421 continue;
2422 for (int j = 0; j < 32; ++j)
2423 {
2424 entity w2 = waypoint_get_link(w, j);
2425 if (!w2)
2426 break;
2427 if (it == w2)
2428 continue;
2429 if (w2.wpflags & WAYPOINTFLAG_PERSONAL)
2430 continue;
2431 // If we got here, it != w2 exist with it -> w
2432 // and w -> w2. That means the waypoint is not
2433 // a dead end.
2434 w.wpflags &= ~WAYPOINTFLAG_DEAD_END;
2435 for (int k = 0; k < 32; ++k)
2436 {
2437 if (waypoint_get_link(it, k) == w2)
2438 continue;
2439 // IF WE GET HERE, w is proven useful
2440 // to get from it to w2!
2441 w.wpflags |= WAYPOINTFLAG_USEFUL;
2442 goto next;
2443 }
2444 }
2445LABEL(next)
2446 }
2447 });
2448 // d) The waypoint is a dead end. Dead end waypoints must be kept as
2449 // they are needed to complete routes while autowaypointing.
2450
2452 {
2453 LOG_INFOF("Removed a waypoint at %v. Try again for more!", it.origin);
2454 te_explosion(it.origin);
2455 waypoint_remove(it);
2456 break;
2457 });
2458
2459 IL_EACH(g_waypoints, true,
2460 {
2461 it.wpflags &= ~(WAYPOINTFLAG_USEFUL | WAYPOINTFLAG_DEAD_END); // temp flag
2462 });
2463}
2464
2465//.entity botframe_autowaypoints_lastwp0;
2468{
2469 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && !IS_DEAD(it), {
2470 // going back is broken, so only fix waypoints to walk TO the player
2471 //botframe_autowaypoints_fix(p, false, botframe_autowaypoints_lastwp0);
2473 //te_explosion(p.botframe_autowaypoints_lastwp0.origin);
2474 });
2475
2478 }
2479}
2480
vector antilag_takebackorigin(entity e, entity store, float t)
Definition antilag.qc:60
const int WAYPOINTFLAG_CROUCH
Definition api.qh:22
const int WAYPOINTFLAG_PERSONAL
Definition api.qh:15
const int WAYPOINTFLAG_LADDER
Definition api.qh:19
const int WAYPOINTFLAG_DEAD_END
Definition api.qh:18
bool bot_waypoints_for_items
Definition api.qh:9
entity navigation_findnearestwaypoint(entity ent, float walkfromwp)
const int WAYPOINTFLAG_TELEPORT
Definition api.qh:13
const int WAYPOINTFLAG_PROTECTED
Definition api.qh:16
const int WAYPOINTFLAG_CUSTOM_JP
Definition api.qh:21
void navigation_markroutes_inverted(entity fixed_source_waypoint)
float havocbot_symmetry_axis_q
Definition api.qh:94
const int WAYPOINTFLAG_NORELINK__DEPRECATED
Definition api.qh:28
float havocbot_symmetry_axis_m
Definition api.qh:93
const int WAYPOINTFLAG_SUPPORT
Definition api.qh:23
bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float end_height, float movemode)
vector havocbot_middlepoint
Definition api.qh:91
void navigation_markroutes(entity this, entity fixed_source_waypoint)
float havocbot_symmetry_origin_order
Definition api.qh:95
float skill
Definition api.qh:35
const int WAYPOINTFLAG_USEFUL
Definition api.qh:17
const int WAYPOINTFLAG_ITEM
Definition api.qh:12
const int WAYPOINTFLAG_GENERATED
Definition api.qh:11
const int WPFLAGMASK_NORELINK
Definition api.qh:29
entity waypoint_spawn(vector m1, vector m2, float f)
Definition waypoints.qc:433
vector set_tracewalk_dest_2(entity ent, vector org)
const int WAYPOINTFLAG_JUMP
Definition api.qh:20
int wpflags
Definition api.qh:64
float havocbot_middlepoint_radius
Definition api.qh:92
IntrusiveList g_waypoints
Definition api.qh:148
#define g_assault
Definition assault.qh:27
float height
Definition bobbing.qc:3
void bot_calculate_stepheightvec()
Definition bot.qc:615
float createdtime
Definition bot.qh:61
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
float count
Definition powerups.qc:22
#define setmodel(this, m)
Definition model.qh:26
#define PHYS_INPUT_BUTTON_CROUCH(s)
Definition player.qh:154
#define IS_DEAD(s)
Definition player.qh:245
float autocvar_sv_maxspeed
Definition player.qh:53
#define IS_PLAYER(s)
Definition player.qh:243
#define autocvar_sv_gravity
Definition stats.qh:421
#define LABEL(id)
Definition compiler.qh:34
const vector PL_MIN_CONST
Definition constants.qh:56
const vector PL_CROUCH_MIN_CONST
Definition constants.qh:58
const vector PL_CROUCH_MAX_CONST
Definition constants.qh:57
const vector PL_MAX_CONST
Definition constants.qh:55
vector v_up
const float MOVE_NOMONSTERS
entity trace_ent
float DPCONTENTS_BOTCLIP
const float SOLID_TRIGGER
float DPCONTENTS_SOLID
string mapname
const float MOVE_NORMAL
const float EF_RED
float DPCONTENTS_BODY
const float FILE_READ
const float FILE_WRITE
float DPCONTENTS_PLAYERCLIP
float time
vector v_right
vector trace_endpos
float checkpvs(vector viewpos, entity viewee)
float trace_startsolid
float nextthink
const float EF_BLUE
vector absmax
vector v_forward
const float EF_NODEPTHTEST
vector origin
float trace_fraction
vector absmin
const float SOLID_BSP
float dphitcontentsmask
bool autocvar_bot_navigation_ignoreplayers
Definition cvars.qh:50
int autocvar_g_waypointeditor_auto
Definition cvars.qh:60
float autocvar_bot_ai_bunnyhop_skilloffset
Definition cvars.qh:19
float EF_LOWPRECISION
#define tokenizebyseparator
ERASEABLE entity IL_PUSH(IntrusiveList this, entity it)
Push to tail.
#define IL_EACH(this, cond, body)
#define FOREACH_ENTITY_CLASS(class, cond, body)
Definition iter.qh:189
IntrusiveList g_jumppads
Definition jumppads.qh:18
vector dest
Definition jumppads.qh:54
#define move_out_of_solid(e)
Definition common.qh:110
#define LOG_INFO(...)
Definition log.qh:65
noref int autocvar_developer
Definition log.qh:96
#define LOG_TRACE(...)
Definition log.qh:76
#define backtrace(msg)
Definition log.qh:99
#define LOG_INFOF(...)
Definition log.qh:66
#define M_PI
Definition mathlib.qh:108
void cvar_set(string name, string value)
string fgets(float fhandle)
void fclose(float fhandle)
float stof(string val,...)
void fputs(float fhandle, string s)
float bound(float min, float value, float max)
string substring(string s, float start, float length)
void sprint(float clientnum, string text,...)
float fopen(string filename, float mode)
vector stov(string s)
void bprint(string text,...)
const string cvar_string(string name)
float vlen(vector v)
float sqrt(float f)
string vtos(vector v)
string ftos(float f)
float fabs(float f)
float floor(float f)
string argv(float n)
string etos(entity e)
float navigation_waypoint_will_link(vector v, vector org, entity ent, vector v2, float v2_height, vector o2, float o2_height, float walkfromwp, float bestdist)
bool navigation_check_submerged_state(entity ent, vector pos)
float bot_navigation_movemode
Definition navigation.qh:7
vector jumpheight_vec
Definition navigation.qh:12
float jumpheight_time
Definition navigation.qh:13
vector tracewalk_dest
Definition navigation.qh:66
float tracewalk_dest_height
Definition navigation.qh:67
float navigation_testtracewalk
Definition navigation.qh:8
var void func_null()
string string_null
Definition nil.qh:9
strcat(_("^F4Countdown stopped!"), "\n^BG", _("Teams are too unbalanced."))
#define NULL
Definition post.qh:14
#define makevectors
Definition post.qh:21
entity err
Definition promise.qc:44
#define setthink(e, f)
vector
Definition self.qh:92
vector org
Definition self.qh:92
IntrusiveList g_items
Definition items.qh:125
IntrusiveList g_spawnpoints
#define spawnfunc(id)
Definition spawnfunc.qh:96
ClientState CS(Client this)
Definition state.qh:47
#define STATIC_INIT(func)
during worldspawn
Definition static.qh:32
ERASEABLE string ftos_decimals(float number, int decimals)
converts a number to a string with the indicated number of decimals
Definition string.qh:469
#define strcpy(this, s)
Definition string.qh:52
ERASEABLE string cons(string a, string b)
Definition string.qh:276
if(frag_attacker.flagcarried)
Definition sv_ctf.qc:2325
void WarpZone_crosshair_trace(entity pl)
Definition tracing.qc:585
#define IS_REAL_CLIENT(v)
Definition utils.qh:17
#define FOREACH_CLIENT(cond, body)
Definition utils.qh:50
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition vector.qh:8
const vector eZ
Definition vector.qh:46
ERASEABLE float boxesoverlap(vector m1, vector m2, vector m3, vector m4)
requires that m2>m1 in all coordinates, and that m4>m3
Definition vector.qh:73
ERASEABLE vector Rotate(vector v, float a)
Definition vector.qh:105
#define vec2(...)
Definition vector.qh:90
void botframe_autowaypoints_fix(entity p, float walkfromwp,.entity fld)
void waypoint_lock(entity pl)
Definition waypoints.qc:270
bool waypoint_load_links()
float waypoint_get_assigned_link_cost(entity w, float i)
void waypoint_unmark_hardwiredlink(entity wp_from, entity wp_to)
Definition waypoints.qc:318
void waypoint_spawnforitem(entity e)
void waypoint_schedulerelinkall()
void waypoint_getSymmetricalAxis_cmd(entity caller, bool save, int arg_idx)
Definition waypoints.qc:137
bool waypoint_has_hardwiredlinks(entity wp)
Definition waypoints.qc:276
bool start_wp_is_support
Definition waypoints.qc:519
#define GET_GAMETYPE_EXTENSION()
void waypoint_showlink(entity wp1, entity wp2, int display_type)
void waypoint_addlink_customcost(entity from, entity to, float c)
void waypoint_clearlinks(entity wp)
float waypoint_getlinearcost_underwater(float dist)
entity waypoint_get(vector m1, vector m2)
Definition waypoints.qc:420
bool waypoint_is_hardwiredlink(entity wp_from, entity wp_to)
Definition waypoints.qc:283
float botframe_autowaypoints_createwp(vector v, entity p,.entity fld, float f)
void botframe_deleteuselesswaypoints()
float waypoint_gettravelcost(vector from, vector to, entity from_ent, entity to_ent)
entity botframe_autowaypoints_lastwp1
bool waypoint_islinked(entity from, entity to)
Definition waypoints.qc:966
void waypoint_getSymmetricalOrigin_cmd(entity caller, bool save, int arg_idx)
Definition waypoints.qc:169
void waypoint_updatecost_foralllinks()
Definition waypoints.qc:971
void waypoint_spawnforteleporter(entity e, vector destination, float timetaken, entity tracetest_ent)
void waypoint_restore_hardwiredlinks(entity wp)
Definition waypoints.qc:350
#define waypoint_fixorigin(position, tracetest_ent)
void waypoint_unreachable(entity pl)
Definition waypoints.qc:28
string waypoint_get_type_name(entity wp)
Definition waypoints.qc:397
void waypoint_addlink(entity from, entity to)
float botframe_autowaypoints_fixdown(vector v)
void waypoint_think(entity this)
void waypoint_showlinks_from(entity wp, int display_type)
void waypoint_schedulerelink(entity wp)
void waypoint_removelink(entity from, entity to)
Definition waypoints.qc:905
void waypoint_spawnforteleporter_wz(entity e, entity tracetest_ent)
entity spawnpointmodel
Definition waypoints.qc:27
entity waypoint_get_link(entity w, float i)
float waypoint_loadall()
void waypoint_load_hardwiredlinks()
bool start_wp_is_hardwired
Definition waypoints.qc:518
void waypoint_save_hardwiredlinks()
void waypoint_addlink_for_custom_jumppad(entity wp_from, entity wp_to)
Definition waypoints.qc:498
void waypoint_spawnforteleporter_boxes(entity e, int teleport_flag, vector org1, vector org2, vector destination1, vector destination2, float timetaken)
int waypoint_getlinknum(entity from, entity to)
Definition waypoints.qc:953
float waypoint_getlinkcost(entity from, entity to)
void waypoint_showlinks_to(entity wp, int display_type)
float trigger_push_get_push_time(entity this, vector endpos)
Definition jumppads.qc:541
entity waypoint_spawnpersonal(entity this, vector position)
entity waypoint_spawn(vector m1, vector m2, float f)
Definition waypoints.qc:433
vector waypoint_getSymmetricalPoint(vector org, int ctf_flags)
Definition waypoints.qc:241
void waypoint_setupmodel(entity wp)
Definition waypoints.qc:362
float waypoint_getlinearcost_crouched(float dist)
void waypoint_remove_fromeditor(entity pl)
Definition waypoints.qc:838
vector start_wp_origin
Definition waypoints.qc:517
void waypoint_spawnforitem_force(entity e, vector org)
void waypoint_mark_hardwiredlink(entity wp_from, entity wp_to)
Definition waypoints.qc:301
void waypoint_spawn_fromeditor(entity pl, bool at_crosshair, bool is_jump_wp, bool is_crouch_wp, bool is_support_wp)
Definition waypoints.qc:568
void botframe_showwaypointlinks()
void waypoint_clear_start_wp_globals(entity pl, bool warn)
Definition waypoints.qc:521
float waypoint_getlinearcost(float dist)
void waypoint_saveall()
void waypoint_start_hardwiredlink(entity pl, bool at_crosshair)
Definition waypoints.qc:532
void botframe_autowaypoints()
vector waypoint_fixorigin_down_dir(vector position, entity tracetest_ent, vector down_dir)
bool start_wp_is_spawned
Definition waypoints.qc:516
void waypoint_remove(entity wp)
Definition waypoints.qc:819
void crosshair_trace_waypoints(entity pl)
float botframe_autowaypoints_fix_from(entity p, float walkfromwp, entity wp,.entity fld)
void waypoint_save_links()
float relink_total
Definition waypoints.qh:20
bool waypointeditor_enabled
Definition waypoints.qh:3
vector autocvar_g_waypointeditor_symmetrical_axis
Definition waypoints.qh:10
float botframe_cachedwaypointlinks
Definition waypoints.qh:24
float botframe_loadedforcedlinks
Definition waypoints.qh:23
float waypoint_version_loaded
Definition waypoints.qh:16
const float WAYPOINT_VERSION
Definition waypoints.qh:15
int wpisbox
Definition waypoints.qh:45
bool autocvar_g_waypointeditor_symmetrical_allowload
Definition waypoints.qh:7
float botframe_waypointeditorlightningtime
Definition waypoints.qh:22
float relink_lengthculled
Definition waypoints.qh:20
float relink_walkculled
Definition waypoints.qh:20
float relink_pvsculled
Definition waypoints.qh:20
bool autocvar_g_waypointeditor_symmetrical
Definition waypoints.qh:6
bool autocvar_g_waypointeditor
Definition waypoints.qh:5
vector autocvar_g_waypointeditor_symmetrical_origin
Definition waypoints.qh:8
string waypoint_time
Definition waypoints.qh:17
int autocvar_g_waypointeditor_symmetrical_order
Definition waypoints.qh:9
int wplinked
Definition waypoints.qh:45