+
+ if (fromwp31_prev && !from.wp31)
+ waypoint_schedulerelink(from);
+}
+
+int waypoint_getlinknum(entity from, entity to)
+{
+ if (from.wp00 == to) return 0; if (from.wp01 == to) return 1; if (from.wp02 == to) return 2; if (from.wp03 == to) return 3;
+ if (from.wp04 == to) return 4; if (from.wp05 == to) return 5; if (from.wp06 == to) return 6; if (from.wp07 == to) return 7;
+ if (from.wp08 == to) return 8; if (from.wp09 == to) return 9; if (from.wp10 == to) return 10; if (from.wp11 == to) return 11;
+ if (from.wp12 == to) return 12; if (from.wp13 == to) return 13; if (from.wp14 == to) return 14; if (from.wp15 == to) return 15;
+ if (from.wp16 == to) return 16; if (from.wp17 == to) return 17; if (from.wp18 == to) return 18; if (from.wp19 == to) return 19;
+ if (from.wp20 == to) return 20; if (from.wp21 == to) return 21; if (from.wp22 == to) return 22; if (from.wp23 == to) return 23;
+ if (from.wp24 == to) return 24; if (from.wp25 == to) return 25; if (from.wp26 == to) return 26; if (from.wp27 == to) return 27;
+ if (from.wp28 == to) return 28; if (from.wp29 == to) return 29; if (from.wp30 == to) return 30; if (from.wp31 == to) return 31;
+ return -1;
+}
+
+bool waypoint_islinked(entity from, entity to)
+{
+ return (waypoint_getlinknum(from, to) >= 0);
+}
+
+void waypoint_updatecost_foralllinks()
+{
+ IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_TELEPORT),
+ {
+ if(it.wp00) it.wp00mincost = waypoint_getlinkcost(it, it.wp00);
+ if(it.wp01) it.wp01mincost = waypoint_getlinkcost(it, it.wp01);
+ if(it.wp02) it.wp02mincost = waypoint_getlinkcost(it, it.wp02);
+ if(it.wp03) it.wp03mincost = waypoint_getlinkcost(it, it.wp03);
+ if(it.wp04) it.wp04mincost = waypoint_getlinkcost(it, it.wp04);
+ if(it.wp05) it.wp05mincost = waypoint_getlinkcost(it, it.wp05);
+ if(it.wp06) it.wp06mincost = waypoint_getlinkcost(it, it.wp06);
+ if(it.wp07) it.wp07mincost = waypoint_getlinkcost(it, it.wp07);
+ if(it.wp08) it.wp08mincost = waypoint_getlinkcost(it, it.wp08);
+ if(it.wp09) it.wp09mincost = waypoint_getlinkcost(it, it.wp09);
+ if(it.wp10) it.wp10mincost = waypoint_getlinkcost(it, it.wp10);
+ if(it.wp11) it.wp11mincost = waypoint_getlinkcost(it, it.wp11);
+ if(it.wp12) it.wp12mincost = waypoint_getlinkcost(it, it.wp12);
+ if(it.wp13) it.wp13mincost = waypoint_getlinkcost(it, it.wp13);
+ if(it.wp14) it.wp14mincost = waypoint_getlinkcost(it, it.wp14);
+ if(it.wp15) it.wp15mincost = waypoint_getlinkcost(it, it.wp15);
+ if(it.wp16) it.wp16mincost = waypoint_getlinkcost(it, it.wp16);
+ if(it.wp17) it.wp17mincost = waypoint_getlinkcost(it, it.wp17);
+ if(it.wp18) it.wp18mincost = waypoint_getlinkcost(it, it.wp18);
+ if(it.wp19) it.wp19mincost = waypoint_getlinkcost(it, it.wp19);
+ if(it.wp20) it.wp20mincost = waypoint_getlinkcost(it, it.wp20);
+ if(it.wp21) it.wp21mincost = waypoint_getlinkcost(it, it.wp21);
+ if(it.wp22) it.wp22mincost = waypoint_getlinkcost(it, it.wp22);
+ if(it.wp23) it.wp23mincost = waypoint_getlinkcost(it, it.wp23);
+ if(it.wp24) it.wp24mincost = waypoint_getlinkcost(it, it.wp24);
+ if(it.wp25) it.wp25mincost = waypoint_getlinkcost(it, it.wp25);
+ if(it.wp26) it.wp26mincost = waypoint_getlinkcost(it, it.wp26);
+ if(it.wp27) it.wp27mincost = waypoint_getlinkcost(it, it.wp27);
+ if(it.wp28) it.wp28mincost = waypoint_getlinkcost(it, it.wp28);
+ if(it.wp29) it.wp29mincost = waypoint_getlinkcost(it, it.wp29);
+ if(it.wp30) it.wp30mincost = waypoint_getlinkcost(it, it.wp30);
+ if(it.wp31) it.wp31mincost = waypoint_getlinkcost(it, it.wp31);
+ });
+}
+
+float waypoint_getlinearcost(float dist)
+{
+ if(skill >= autocvar_bot_ai_bunnyhop_skilloffset)
+ return dist / (autocvar_sv_maxspeed * 1.25);
+ return dist / autocvar_sv_maxspeed;
+}
+float waypoint_getlinearcost_underwater(float dist)
+{
+ // NOTE: underwater speed factor is hardcoded in the engine too, see SV_WaterMove
+ return dist / (autocvar_sv_maxspeed * 0.7);
+}
+
+float waypoint_gettravelcost(vector from, vector to, entity from_ent, entity to_ent)
+{
+ bool submerged_from = navigation_check_submerged_state(from_ent, from);
+ bool submerged_to = navigation_check_submerged_state(to_ent, to);
+
+ if (submerged_from && submerged_to)
+ return waypoint_getlinearcost_underwater(vlen(to - from));
+
+ float c = waypoint_getlinearcost(vlen(to - from));
+
+ float height = from.z - to.z;
+ if(height > jumpheight_vec.z && autocvar_sv_gravity > 0)
+ {
+ float height_cost = sqrt(height / (autocvar_sv_gravity / 2));
+ c = waypoint_getlinearcost(vlen(vec2(to - from))); // xy distance cost
+ if(height_cost > c)
+ c = height_cost;
+ }
+
+ if (submerged_from || submerged_to)
+ return (c + waypoint_getlinearcost_underwater(vlen(to - from))) / 2;
+ return c;
+}
+
+float waypoint_getlinkcost(entity from, entity to)
+{
+ vector v1 = from.origin;
+ vector v2 = to.origin;
+ if (from.wpisbox)
+ {
+ vector m1 = from.absmin, m2 = from.absmax;
+ v1.x = bound(m1.x, v2.x, m2.x);
+ v1.y = bound(m1.y, v2.y, m2.y);
+ v1.z = bound(m1.z, v2.z, m2.z);
+ }
+ if (to.wpisbox)
+ {
+ vector m1 = to.absmin, m2 = to.absmax;
+ v2.x = bound(m1.x, v1.x, m2.x);
+ v2.y = bound(m1.y, v1.y, m2.y);
+ v2.z = bound(m1.z, v1.z, m2.z);
+ }
+ return waypoint_gettravelcost(v1, v2, from, to);
+}
+
+// add a new link to the spawnfunc_waypoint, replacing the furthest link it already has
+// if c == -1 automatically determine cost of the link
+void waypoint_addlink_customcost(entity from, entity to, float c)
+{
+ if (from == to || waypoint_islinked(from, to))
+ return;
+ if (c == -1 && (from.wpflags & WAYPOINTFLAG_NORELINK))
+ return;
+
+ if(c == -1)
+ c = waypoint_getlinkcost(from, to);