]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/bot/default/waypoints.qc
Bot waypoints: teach bots to jump gaps by implementing jump waypoints. Spawn it with...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / bot / default / waypoints.qc
index bd5d13e2ce91c035416197de55a642964ad0f265..4a73af1c7dbf0450091048e9c333a30412112559 100644 (file)
@@ -12,6 +12,8 @@
 #include "../../antilag.qh"
 
 #include <common/constants.qh>
+#include <common/debug.qh>
+#include <common/mapobjects/trigger/jumppads.qh>
 #include <common/net_linked.qh>
 #include <common/physics/player.qh>
 
@@ -30,7 +32,7 @@ void waypoint_unreachable(entity pl)
        entity e2 = navigation_findnearestwaypoint(pl, false);
        if(!e2)
        {
-               LOG_INFOF("Can't find any waypoint nearby\n");
+               LOG_INFO("Can't find any waypoint nearby\n");
                return;
        }
 
@@ -272,8 +274,10 @@ void waypoint_setupmodel(entity wp)
                        wp.colormod = '1 0 0';
                else if (wp.wpflags & WAYPOINTFLAG_GENERATED)
                        wp.colormod = '1 1 0';
+               else if (wp.wpflags & WAYPOINTFLAG_NORELINK)
+                       wp.colormod = '1 0.5 0'; // orange
                else if (wp.wphardwired)
-                       wp.colormod = '0.5 0 1';
+                       wp.colormod = '0.5 0 1'; // violet
                else
                        wp.colormod = '1 1 1';
        }
@@ -281,21 +285,30 @@ void waypoint_setupmodel(entity wp)
                wp.model = "";
 }
 
+entity waypoint_get(vector m1, vector m2)
+{
+       if (m1 == m2)
+       {
+               m1 -= '8 8 8';
+               m2 += '8 8 8';
+       }
+       IL_EACH(g_waypoints, boxesoverlap(m1, m2, it.absmin, it.absmax), { return it; });
+
+       return NULL;
+}
+
 entity waypoint_spawn(vector m1, vector m2, float f)
 {
        if(!(f & (WAYPOINTFLAG_PERSONAL | WAYPOINTFLAG_GENERATED)) && m1 == m2)
        {
-               vector em1 = m1 - '8 8 8';
-               vector em2 = m2 + '8 8 8';
-               IL_EACH(g_waypoints, boxesoverlap(em1, em2, it.absmin, it.absmax),
-               {
-                       return it;
-               });
+               entity wp_found = waypoint_get(m1, m2);
+               if (wp_found)
+                       return wp_found;
        }
        // spawn only one destination waypoint for teleports teleporting player to the exact same spot
        // otherwise links loaded from file would be applied only to the first destination
        // waypoint since link format doesn't specify waypoint entities but just positions
-       if((f & WAYPOINTFLAG_GENERATED) && !(f & WAYPOINTFLAG_NORELINK) && m1 == m2)
+       if((f & WAYPOINTFLAG_GENERATED) && !(f & (WAYPOINTFLAG_NORELINK | WAYPOINTFLAG_PERSONAL)) && m1 == m2)
        {
                IL_EACH(g_waypoints, boxesoverlap(m1, m2, it.absmin, it.absmax),
                {
@@ -344,10 +357,57 @@ entity waypoint_spawn(vector m1, vector m2, float f)
        return w;
 }
 
-void waypoint_spawn_fromeditor(entity pl)
+float trigger_push_get_push_time(entity this, vector endpos);
+void waypoint_addlink_for_custom_jumppad(entity wp_from, entity wp_to)
+{
+       entity jp = NULL;
+       IL_EACH(g_jumppads, boxesoverlap(wp_from.absmin, wp_from.absmax, it.absmin, it.absmax),
+       {
+               jp = it;
+               break;
+       });
+       if (!jp)
+               return;
+
+       float cost = trigger_push_get_push_time(jp, wp_to.origin);
+       wp_from.wp00 = wp_to;
+       wp_from.wp00mincost = cost;
+       jp.nearestwaypoint = wp_from;
+       jp.nearestwaypointtimeout = -1;
+}
+
+bool start_wp_is_spawned;
+vector start_wp_origin;
+
+void waypoint_clear_start_wp(entity pl, bool warn)
+{
+       start_wp_is_spawned = false;
+       start_wp_origin = '0 0 0';
+       pl.wp_locked = NULL;
+       if (warn)
+               LOG_INFO("^xf80Start waypoint has been cleared.\n");
+}
+
+void waypoint_spawn_fromeditor(entity pl, bool at_crosshair, bool is_jump_wp)
 {
-       entity e;
+       entity e = NULL, jp = NULL;
        vector org = pl.origin;
+       if (at_crosshair)
+       {
+               crosshair_trace(pl);
+               org = trace_endpos - eZ * PL_MIN_CONST.z;
+               IL_EACH(g_jumppads, boxesoverlap(org + PL_MIN_CONST, org + PL_MAX_CONST, it.absmin, it.absmax),
+               {
+                       jp = it;
+                       break;
+               });
+       }
+       if (jp || is_jump_wp)
+       {
+               if (start_wp_is_spawned)
+                       start_wp_is_spawned = false;
+               LOG_INFO("^xf80Spawning start waypoint...\n");
+       }
        int ctf_flags = havocbot_symmetry_origin_order;
        bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
                   || (autocvar_g_waypointeditor_symmetrical < 0));
@@ -357,7 +417,7 @@ void waypoint_spawn_fromeditor(entity pl)
                ctf_flags = 2;
        int wp_num = ctf_flags;
 
-       if(!PHYS_INPUT_BUTTON_CROUCH(pl))
+       if(!PHYS_INPUT_BUTTON_CROUCH(pl) && !at_crosshair && !is_jump_wp)
        {
                // snap waypoint to item's origin if close enough
                IL_EACH(g_items, true,
@@ -372,18 +432,100 @@ void waypoint_spawn_fromeditor(entity pl)
                });
        }
 
+       vector start_org = '0 0 0';
+       if (start_wp_is_spawned)
+       {
+               LOG_INFO("^xf80Spawning destination waypoint...\n");
+               start_org = start_wp_origin;
+       }
+
+       // save org as it can be modified spawning symmetrycal waypoints
+       vector initial_origin = '0 0 0';
+       bool initial_origin_is_set = false;
+
        LABEL(add_wp);
-       e = waypoint_spawn(org, org, 0);
+
+       if (jp)
+       {
+               e = NULL;
+               IL_EACH(g_waypoints, it.wpflags & WAYPOINTFLAG_NORELINK
+                       && boxesoverlap(org + PL_MIN_CONST, org + PL_MAX_CONST, it.absmin, it.absmax),
+               {
+                       e = it; break;
+               });
+               if (!e)
+                       e = waypoint_spawn(jp.absmin - PL_MAX_CONST + '1 1 1', jp.absmax - PL_MIN_CONST + '-1 -1 -1', WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_NORELINK);
+               if (!pl.wp_locked)
+                       pl.wp_locked = e;
+       }
+       else if (is_jump_wp)
+       {
+               entity wp_found = waypoint_get(org, org);
+               if (wp_found && !(wp_found.wpflags & WAYPOINTFLAG_JUMP))
+               {
+                       LOG_INFO("Error: can't spawn a jump waypoint over an existent waypoint of a different type\n");
+                       return;
+               }
+               e = waypoint_spawn(org, org, WAYPOINTFLAG_JUMP | WAYPOINTFLAG_NORELINK);
+               if (!pl.wp_locked)
+                       pl.wp_locked = e;
+       }
+       else
+               e = waypoint_spawn(org, org, 0);
        if(!e)
        {
                LOG_INFOF("Couldn't spawn waypoint at %v\n", org);
+               if (start_wp_is_spawned)
+                       waypoint_clear_start_wp(pl, true);
                return;
        }
-       waypoint_schedulerelink(e);
+
+       if (!initial_origin_is_set)
+       {
+               initial_origin = e.origin;
+               initial_origin_is_set = true;
+       }
+
+       entity start_wp = NULL;
+       if (start_wp_is_spawned)
+       {
+               IL_EACH(g_waypoints, it.wpflags & WAYPOINTFLAG_NORELINK
+                       && boxesoverlap(start_org, start_org, it.absmin, it.absmax),
+               {
+                       start_wp = it; break;
+               });
+               if(!start_wp)
+               {
+                       // should not happen
+                       LOG_INFOF("Couldn't find start waypoint at %v\n", start_org);
+                       waypoint_clear_start_wp(pl, true);
+                       return;
+               }
+               waypoint_addlink(start_wp, e);
+       }
+
+       if (!(jp || is_jump_wp))
+               waypoint_schedulerelink(e);
        bprint(strcat("Waypoint spawned at ", vtos(e.origin), "\n"));
-       if(sym)
+
+       if (start_wp_is_spawned)
+       {
+               pl.wp_locked = NULL;
+               waypoint_schedulerelink(start_wp);
+       }
+
+       if (sym)
        {
-               org = waypoint_getSymmetricalPoint(e.origin, ctf_flags);
+               org = waypoint_getSymmetricalPoint(org, ctf_flags);
+               if (jp)
+               {
+                       IL_EACH(g_jumppads, boxesoverlap(org + PL_MIN_CONST, org + PL_MAX_CONST, it.absmin, it.absmax),
+                       {
+                               jp = it; break;
+                       });
+               }
+               if (start_wp_is_spawned)
+                       start_org = waypoint_getSymmetricalPoint(start_org, ctf_flags);
                if (vdist(org - pl.origin, >, 32))
                {
                        if(wp_num > 2)
@@ -393,6 +535,20 @@ void waypoint_spawn_fromeditor(entity pl)
                        goto add_wp;
                }
        }
+       if (jp || is_jump_wp)
+       {
+               if (!start_wp_is_spawned)
+               {
+                       // we've just created a custom jumppad waypoint
+                       // the next one created by the user will be the destination waypoint
+                       start_wp_is_spawned = true;
+                       start_wp_origin = initial_origin;
+               }
+       }
+       else if (start_wp_is_spawned)
+       {
+               waypoint_clear_start_wp(pl, false);
+       }
 }
 
 void waypoint_remove(entity wp)
@@ -420,7 +576,12 @@ void waypoint_remove_fromeditor(entity pl)
 
        LABEL(remove_wp);
        if (!e) return;
-       if (e.wpflags & WAYPOINTFLAG_GENERATED) return;
+       if (e.wpflags & WAYPOINTFLAG_GENERATED)
+       {
+               if (start_wp_is_spawned)
+                       waypoint_clear_start_wp(pl, true);
+               return;
+       }
 
        if (e.wphardwired)
        {
@@ -453,11 +614,14 @@ void waypoint_remove_fromeditor(entity pl)
                        sym = false;
                goto remove_wp;
        }
+
+       if (start_wp_is_spawned)
+               waypoint_clear_start_wp(pl, true);
 }
 
 void waypoint_removelink(entity from, entity to)
 {
-       if (from == to || (from.wpflags & WAYPOINTFLAG_NORELINK))
+       if (from == to || (from.wpflags & WAYPOINTFLAG_NORELINK && !(from.wpflags & WAYPOINTFLAG_JUMP)))
                return;
 
        entity fromwp31_prev = from.wp31;
@@ -585,7 +749,11 @@ float waypoint_gettravelcost(vector from, vector to, entity from_ent, entity to_
        float height = from.z - to.z;
        if(height > jumpheight_vec.z && autocvar_sv_gravity > 0)
        {
-               float height_cost = sqrt(height / (autocvar_sv_gravity / 2));
+               float height_cost;
+               if (boolean(from_ent.wpflags & WAYPOINTFLAG_JUMP))
+                       height_cost = jumpheight_time + sqrt((height + jumpheight_vec.z) / (autocvar_sv_gravity / 2));
+               else
+                       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;
@@ -623,7 +791,7 @@ 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))
+       if (c == -1 && (from.wpflags & WAYPOINTFLAG_NORELINK) && !(from.wpflags & WAYPOINTFLAG_JUMP))
                return;
 
        if(c == -1)
@@ -666,7 +834,10 @@ void waypoint_addlink_customcost(entity from, entity to, float c)
 
 void waypoint_addlink(entity from, entity to)
 {
-       waypoint_addlink_customcost(from, to, -1);
+       if ((from.wpflags & WAYPOINTFLAG_NORELINK) && !(from.wpflags & (WAYPOINTFLAG_JUMP)))
+               waypoint_addlink_for_custom_jumppad(from, to);
+       else
+               waypoint_addlink_customcost(from, to, -1);
 }
 
 // relink this spawnfunc_waypoint
@@ -720,7 +891,7 @@ void waypoint_think(entity this)
 
                        //traceline(this.origin, it.origin, false, NULL);
                        //if (trace_fraction == 1)
-                       if (this.wpisbox)
+                       if (this.wpisbox || this.wpflags & WAYPOINTFLAG_JUMP)
                                relink_walkculled += 0.5;
                        else
                        {
@@ -730,7 +901,7 @@ void waypoint_think(entity this)
                                        relink_walkculled += 0.5;
                        }
 
-                       if (it.wpisbox)
+                       if (it.wpisbox || it.wpflags & WAYPOINTFLAG_JUMP)
                                relink_walkculled += 0.5;
                        else
                        {
@@ -744,6 +915,9 @@ void waypoint_think(entity this)
        navigation_testtracewalk = 0;
        this.wplinked = true;
        this.dphitcontentsmask = dphitcontentsmask_save;
+
+       setthink(this, func_null);
+       this.nextthink = 0;
 }
 
 void waypoint_clearlinks(entity wp)
@@ -1530,6 +1704,10 @@ void botframe_showwaypointlinks()
        FOREACH_CLIENT(IS_PLAYER(it) && !it.isbot,
        {
                int display_type = 0;
+               if (wasfreed(it.wp_aimed))
+                       it.wp_aimed = NULL;
+               if (wasfreed(it.wp_locked))
+                       it.wp_locked = NULL;
                if (PHYS_INPUT_BUTTON_USE(it))
                        it.wp_locked = it.wp_aimed;
                entity head = it.wp_locked;