]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into terencehill/bot_waypoints
authorterencehill <piuntn@gmail.com>
Mon, 17 Jul 2017 22:23:08 +0000 (00:23 +0200)
committerterencehill <piuntn@gmail.com>
Mon, 17 Jul 2017 22:23:08 +0000 (00:23 +0200)
1  2 
qcsrc/server/bot/default/bot.qc
qcsrc/server/bot/default/havocbot/havocbot.qc
qcsrc/server/command/sv_cmd.qc
qcsrc/server/impulse.qc

index 3b9dd7c60ccd8a5b878bb9a01a5e3e2b4a91442c,a1d7b10a4c156c22d09e1cac733a1ce460f92b32..b3395c671ff383bb980ba3a832860c864ce37696
@@@ -74,7 -74,7 +74,7 @@@ void bot_think(entity this
  
        if (!IS_PLAYER(this) || (autocvar_g_campaign && !campaign_bots_may_start))
        {
-               this.movement = '0 0 0';
+               CS(this).movement = '0 0 0';
                this.bot_nextthink = time + 0.5;
                return;
        }
@@@ -94,7 -94,7 +94,7 @@@
        // (simulated network latency + naturally delayed reflexes)
        //this.ping = 0.7 - bound(0, 0.05 * skill, 0.5); // moved the reflexes to bot_aimdir (under the name 'think')
        // minimum ping 20+10 random
-       this.ping = bound(0,0.07 - bound(0, (skill + this.bot_pingskill) * 0.005,0.05)+random()*0.01,0.65); // Now holds real lag to server, and higer skill players take a less laggy server
+       CS(this).ping = bound(0,0.07 - bound(0, (skill + this.bot_pingskill) * 0.005,0.05)+random()*0.01,0.65); // Now holds real lag to server, and higer skill players take a less laggy server
        // skill 10 = ping 0.2 (adrenaline)
        // skill 0 = ping 0.7 (slightly drunk)
  
        if (time < game_starttime)
        {
                // block the bot during the countdown to game start
-               this.movement = '0 0 0';
+               CS(this).movement = '0 0 0';
                this.bot_nextthink = game_starttime;
                return;
        }
        // if dead, just wait until we can respawn
        if (IS_DEAD(this))
        {
-               this.movement = '0 0 0';
+               CS(this).movement = '0 0 0';
                if (this.deadflag == DEAD_DEAD)
                {
                        PHYS_INPUT_BUTTON_JUMP(this) = true; // press jump to respawn
@@@ -571,8 -571,9 +571,8 @@@ void autoskill(float factor
  void bot_calculate_stepheightvec()
  {
        stepheightvec = autocvar_sv_stepheight * '0 0 1';
 -      jumpstepheightvec = stepheightvec +
 -              ((autocvar_sv_jumpvelocity * autocvar_sv_jumpvelocity) / (2 * autocvar_sv_gravity)) * '0 0 0.85';
 -              // 0.75 factor is for safety to make the jumps easy
 +      jumpheight_vec = (autocvar_sv_jumpvelocity ** 2) / (2 * autocvar_sv_gravity) * '0 0 1';
 +      jumpstepheightvec = stepheightvec + jumpheight_vec * 0.85; // reduce it a bit to make the jumps easy
  }
  
  float bot_fixcount()
@@@ -678,19 -679,6 +678,19 @@@ void bot_serverframe(
        if (time < 2)
                return;
  
 +      if(autocvar_skill != skill)
 +      {
 +              float wpcost_update = false;
 +              if(skill >= autocvar_bot_ai_bunnyhop_skilloffset && autocvar_skill < autocvar_bot_ai_bunnyhop_skilloffset)
 +                      wpcost_update = true;
 +              if(skill < autocvar_bot_ai_bunnyhop_skilloffset && autocvar_skill >= autocvar_bot_ai_bunnyhop_skilloffset)
 +                      wpcost_update = true;
 +
 +              skill = autocvar_skill;
 +              if (wpcost_update)
 +                      waypoint_updatecost_foralllinks();
 +      }
 +
        bot_calculate_stepheightvec();
        bot_navigation_movemode = ((autocvar_bot_navigation_ignoreplayers) ? MOVE_NOMONSTERS : MOVE_NORMAL);
  
                botframe_spawnedwaypoints = true;
                waypoint_loadall();
                if(!waypoint_load_links())
 -                      waypoint_schedulerelinkall();
 +                      waypoint_schedulerelinkall(); // link all the autogenerated waypoints (teleporters)
        }
  
        if (bot_list)
index 06cf57992f5b707dc24711f2ecd166891ac183dc,cede62366848b6407f7e8924863290c62a905099..f0b229bae48e151018ca143584bfef4019522a28
@@@ -44,6 -44,7 +44,6 @@@ void havocbot_ai(entity this
                                this.havocbot_role(this); // little too far down the rabbit hole
                }
  
 -              // TODO: tracewalk() should take care of this job (better path finding under water)
                // if we don't have a goal and we're under water look for a waypoint near the "shore" and push it
                if(!(IS_DEAD(this) || STAT(FROZEN, this)))
                if(!this.goalcurrent)
  
                vector now,v,next;//,heading;
                float aimdistance,skillblend,distanceblend,blend;
 -              next = now = ( (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5) - (this.origin + this.view_ofs);
 +
 +              SET_DESTCOORDS(this.goalcurrent, this.origin, now);
 +              next = now = now - (this.origin + this.view_ofs);
                aimdistance = vlen(now);
                //heading = this.velocity;
                //dprint(this.goalstack01.classname,etos(this.goalstack01),"\n");
                        // we are currently holding a weapon that's not fully loaded, reload it
                        if(skill >= 2) // bots can only reload the held weapon on purpose past this skill
                        if(this.(weaponentity).clip_load < this.(weaponentity).clip_size)
-                               this.impulse = IMP_weapon_reload.impulse; // not sure if this is done right
+                               CS(this).impulse = IMP_weapon_reload.impulse; // not sure if this is done right
  
                        // if we're not reloading a weapon, switch to any weapon in our invnetory that's not fully loaded to reload it next
                        // the code above executes next frame, starting the reloading then
@@@ -213,7 -212,7 +213,7 @@@ void havocbot_keyboard_movement(entity 
                                        + 0.05 / max(1, sk + this.havocbot_keyboardskill)
                                        + random() * 0.025 / max(0.00025, skill + this.havocbot_keyboardskill)
                        , time);
-               keyboard = this.movement / autocvar_sv_maxspeed;
+               keyboard = CS(this).movement / autocvar_sv_maxspeed;
  
                float trigger = autocvar_bot_ai_keyboard_threshold;
                float trigger1 = -trigger;
  
        keyboard = this.havocbot_keyboard;
        float blend = bound(0, vlen(destorg - this.origin) / autocvar_bot_ai_keyboard_distance, 1); // When getting close move with 360 degree
-       //dprint("movement ", vtos(this.movement), " keyboard ", vtos(keyboard), " blend ", ftos(blend), "\n");
-       this.movement = this.movement + (keyboard - this.movement) * blend;
+       //dprint("movement ", vtos(CS(this).movement), " keyboard ", vtos(keyboard), " blend ", ftos(blend), "\n");
+       CS(this).movement = CS(this).movement + (keyboard - CS(this).movement) * blend;
  }
  
  void havocbot_bunnyhop(entity this, vector dir)
                this.bot_timelastseengoal = 0;
        }
  
 -      gco = (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5;
 +      SET_DESTCOORDS(this.goalcurrent, this.origin, gco);
        bunnyhopdistance = vlen(this.origin - gco);
  
        // Run only to visible goals
        if(IS_ONGROUND(this))
 -      if(vlen(this.velocity - eZ * this.velocity.z) >= autocvar_sv_maxspeed) // if -really- running
 +      if(vdist(vec2(this.velocity), >=, autocvar_sv_maxspeed)) // if -really- running
        if(checkpvs(this.origin + this.view_ofs, this.goalcurrent))
        {
                        this.bot_lastseengoal = this.goalcurrent;
                        while (deviation.y > 180) deviation.y = deviation.y - 360;
  
                        if(fabs(deviation.y)>10)
-                               this.movement_x = 0;
+                               CS(this).movement_x = 0;
  
                        if(deviation.y>10)
-                               this.movement_y = maxspeed * -1;
+                               CS(this).movement_y = maxspeed * -1;
                        else if(deviation.y<10)
-                               this.movement_y = maxspeed;
+                               CS(this).movement_y = maxspeed;
  
                }
        }
  #endif
  }
  
 -.entity goalcurrent_prev;
 -.float goalcurrent_distance;
 -.float goalcurrent_distance_time;
 +// return true when bot isn't getting closer to the current goal
 +bool havocbot_checkgoaldistance(entity this, vector gco)
 +{
 +      float curr_dist = vlen(this.origin - gco);
 +      if(curr_dist > this.goalcurrent_distance)
 +      {
 +              if(!this.goalcurrent_distance_time)
 +                      this.goalcurrent_distance_time = time;
 +              else if (time - this.goalcurrent_distance_time > 0.5)
 +                      return true;
 +      }
 +      else
 +      {
 +              // reduce it a little bit so it works even with very small approaches to the goal
 +              this.goalcurrent_distance = max(20, curr_dist - 15);
 +              this.goalcurrent_distance_time = 0;
 +      }
 +      return false;
 +}
 +
  void havocbot_movetogoal(entity this)
  {
        vector destorg;
        vector diff;
        vector dir;
        vector flatdir;
 -      vector m1;
 -      vector m2;
        vector evadeobstacle;
        vector evadelava;
        float maxspeed;
        vector dodge;
        //if (this.goalentity)
        //      te_lightning2(this, this.origin, (this.goalentity.absmin + this.goalentity.absmax) * 0.5);
-       this.movement = '0 0 0';
+       CS(this).movement = '0 0 0';
        maxspeed = autocvar_sv_maxspeed;
  
        // Jetpack navigation
                                // Brake
                                if(fabs(this.velocity.x)>maxspeed*0.3)
                                {
-                                       this.movement_x = dir * v_forward * -maxspeed;
+                                       CS(this).movement_x = dir * v_forward * -maxspeed;
                                        return;
                                }
                                // Switch to normal mode
                PHYS_INPUT_BUTTON_HOOK(this) = true;
                if(this.navigation_jetpack_point.z - STAT(PL_MAX, this).z + STAT(PL_MIN, this).z < this.origin.z)
                {
-                       this.movement_x = dir * v_forward * maxspeed;
-                       this.movement_y = dir * v_right * maxspeed;
+                       CS(this).movement_x = dir * v_forward * maxspeed;
+                       CS(this).movement_y = dir * v_right * maxspeed;
                }
                return;
        }
        // Handling of jump pads
        if(this.jumppadcount)
        {
 -              // If got stuck on the jump pad try to reach the farthest visible waypoint
 -              // but with some randomness so it can try out different paths
 -              if(this.aistatus & AI_STATUS_OUT_JUMPPAD)
 +              if(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
                {
 -                      if(fabs(this.velocity.z)<50)
 +                      this.aistatus |= AI_STATUS_OUT_JUMPPAD;
 +                      navigation_poptouchedgoals(this);
 +                      return;
 +              }
 +              else if(this.aistatus & AI_STATUS_OUT_JUMPPAD)
 +              {
 +                      // If got stuck on the jump pad try to reach the farthest visible waypoint
 +                      // but with some randomness so it can try out different paths
 +                      if(!this.goalcurrent)
                        {
                                entity newgoal = NULL;
 -                              if (vdist(this.origin - this.goalcurrent.origin, <, 150))
 -                                      this.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
 -                              else IL_EACH(g_waypoints, vdist(it.origin - this.origin, <=, 1000),
 +                              IL_EACH(g_waypoints, vdist(it.origin - this.origin, <=, 1000),
                                {
 +                                      if(it.wpflags & WAYPOINTFLAG_TELEPORT)
 +                                      if(it.origin.z < this.origin.z - 100 && vdist(vec2(it.origin - this.origin), <, 100))
 +                                              continue;
 +
                                        traceline(this.origin + this.view_ofs, ((it.absmin + it.absmax) * 0.5), true, this);
  
                                        if(trace_fraction < 1)
                                }
                        }
                        else
 -                              return;
 +                      {
 +                              gco = (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5;
 +                              if (this.origin.z > gco.z && vdist(vec2(this.velocity), <, autocvar_sv_maxspeed))
 +                                      this.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
 +                              else if(havocbot_checkgoaldistance(this, gco))
 +                              {
 +                                      navigation_clearroute(this);
 +                                      this.bot_strategytime = 0;
 +                              }
 +                              else
 +                                      return;
 +                      }
                }
                else
                {
 -                      if(time - this.lastteleporttime > 0.3 && this.velocity.z > 0)
 +                      if(time - this.lastteleporttime > 0.2 && this.velocity.z > 0)
                        {
                                vector velxy = this.velocity; velxy_z = 0;
                                if(vdist(velxy, <, autocvar_sv_maxspeed * 0.2))
                                tracebox(this.origin, this.mins, this.maxs, this.origin + (dir * maxspeed * 3), MOVE_NOMONSTERS, this);
                                if(trace_fraction==1)
                                {
-                                       this.movement_x = dir * v_forward * maxspeed;
-                                       this.movement_y = dir * v_right * maxspeed;
+                                       CS(this).movement_x = dir * v_forward * maxspeed;
+                                       CS(this).movement_y = dir * v_right * maxspeed;
                                        if (skill < 10)
                                                havocbot_keyboard_movement(this, this.origin + dir * 100);
                                }
  
                                        if(client_hasweapon(this, WEP_DEVASTATOR, weaponentity, true, false))
                                        {
-                                               this.movement_x = maxspeed;
+                                               CS(this).movement_x = maxspeed;
  
                                                if(this.rocketjumptime)
                                                {
                {
                        // If there is no goal try to move forward
                        if(this.goalcurrent==NULL)
-                               this.movement_x = maxspeed;
+                               CS(this).movement_x = maxspeed;
                }
        }
  
                else
                        PHYS_INPUT_BUTTON_JUMP(this) = false;
                makevectors(this.v_angle.y * '0 1 0');
-               this.movement_x = dir * v_forward * maxspeed;
-               this.movement_y = dir * v_right * maxspeed;
-               this.movement_z = dir * v_up * maxspeed;
+               CS(this).movement_x = dir * v_forward * maxspeed;
+               CS(this).movement_y = dir * v_right * maxspeed;
+               CS(this).movement_z = dir * v_up * maxspeed;
        }
  
        // if there is nowhere to go, exit
        if(autocvar_bot_debug_goalstack)
                debuggoalstack(this);
  
 -      m1 = this.goalcurrent.origin + this.goalcurrent.mins;
 -      m2 = this.goalcurrent.origin + this.goalcurrent.maxs;
 -      destorg = this.origin;
 -      destorg.x = bound(m1_x, destorg.x, m2_x);
 -      destorg.y = bound(m1_y, destorg.y, m2_y);
 -      destorg.z = bound(m1_z, destorg.z, m2_z);
 +      SET_DESTCOORDS(this.goalcurrent, this.origin, destorg);
 +
 +      // in case bot ends up inside the teleport waypoint without touching
 +      // the teleport itself, head to the teleport origin
 +      if(destorg == this.origin)
 +              destorg = this.goalcurrent.origin;
 +
        diff = destorg - this.origin;
        //dist = vlen(diff);
        dir = normalize(diff);
  
        //if (this.bot_dodgevector_time < time)
        {
 -      //      this.bot_dodgevector_time = time + cvar("bot_ai_dodgeupdateinterval");
 -      //      this.bot_dodgevector_jumpbutton = 1;
 +              //this.bot_dodgevector_time = time + cvar("bot_ai_dodgeupdateinterval");
 +              //this.bot_dodgevector_jumpbutton = 1;
                evadeobstacle = '0 0 0';
                evadelava = '0 0 0';
  
                {
                        if(this.waterlevel>WATERLEVEL_SWIMMING)
                        {
 -                      //      flatdir_z = 1;
 +                              //flatdir_z = 1;
                                this.aistatus |= AI_STATUS_OUT_WATER;
                        }
                        else
                        {
 +                              dir = flatdir;
                                if(this.velocity.z >= 0 && !(this.watertype == CONTENT_WATER && gco.z < this.origin.z) &&
                                        ( !(this.waterlevel == WATERLEVEL_WETFEET && this.watertype == CONTENT_WATER) || this.aistatus & AI_STATUS_OUT_WATER))
                                        PHYS_INPUT_BUTTON_JUMP(this) = true;
                                else
                                        PHYS_INPUT_BUTTON_JUMP(this) = false;
                        }
 -                      dir = normalize(flatdir);
                }
                else
                {
                        }
  
                        // if bot for some reason doesn't get close to the current goal find another one
 -                      if(!IS_PLAYER(this.goalcurrent) && !(this.goalcurrent.bot_pickup_respawning && this.goalcurrent_distance < 50))
 +                      if(!this.jumppadcount && !IS_PLAYER(this.goalcurrent) && !(this.goalcurrent.bot_pickup_respawning && this.goalcurrent_distance < 50))
 +                      if(havocbot_checkgoaldistance(this, gco))
                        {
 -                              float curr_dist = vlen(this.origin - this.goalcurrent.origin);
 -                              if(this.goalcurrent != this.goalcurrent_prev)
 -                              {
 -                                      this.goalcurrent_prev = this.goalcurrent;
 -                                      this.goalcurrent_distance = curr_dist;
 -                                      this.goalcurrent_distance_time = 0;
 -                              }
 -                              else if(curr_dist > this.goalcurrent_distance)
 -                              {
 -                                      if(!this.goalcurrent_distance_time)
 -                                              this.goalcurrent_distance_time = time;
 -                                      else if (time - this.goalcurrent_distance_time > 0.5)
 -                                      {
 -                                              this.goalcurrent_prev = NULL;
 -                                              navigation_clearroute(this);
 -                                              this.bot_strategytime = 0;
 -                                              return;
 -                                      }
 -                              }
 -                              else
 -                              {
 -                                      // reduce it a little bit so it works even with very small approaches to the goal
 -                                      this.goalcurrent_distance = max(20, curr_dist - 15);
 -                                      this.goalcurrent_distance_time = 0;
 -                              }
 +                              navigation_clearroute(this);
 +                              this.bot_strategytime = 0;
 +                              return;
                        }
  
                        // Check for water/slime/lava and dangerous edges
        //dir = this.bot_dodgevector;
        //if (this.bot_dodgevector_jumpbutton)
        //      PHYS_INPUT_BUTTON_JUMP(this) = true;
-       this.movement_x = dir * v_forward * maxspeed;
-       this.movement_y = dir * v_right * maxspeed;
-       this.movement_z = dir * v_up * maxspeed;
+       CS(this).movement_x = dir * v_forward * maxspeed;
+       CS(this).movement_y = dir * v_right * maxspeed;
+       CS(this).movement_z = dir * v_up * maxspeed;
  
        // Emulate keyboard interface
        if (skill < 10)
@@@ -1217,10 -1202,10 +1217,10 @@@ void havocbot_aim(entity this
                vector enemyvel = this.enemy.velocity;
                if (!this.enemy.waterlevel)
                        enemyvel.z = 0;
-               lag_additem(this, time + this.ping, 0, 0, this.enemy, this.origin, myvel, (this.enemy.absmin + this.enemy.absmax) * 0.5, enemyvel);
+               lag_additem(this, time + CS(this).ping, 0, 0, this.enemy, this.origin, myvel, (this.enemy.absmin + this.enemy.absmax) * 0.5, enemyvel);
        }
        else
-               lag_additem(this, time + this.ping, 0, 0, NULL, this.origin, myvel, ( this.goalcurrent.absmin + this.goalcurrent.absmax ) * 0.5, '0 0 0');
+               lag_additem(this, time + CS(this).ping, 0, 0, NULL, this.origin, myvel, ( this.goalcurrent.absmin + this.goalcurrent.absmax ) * 0.5, '0 0 0');
  }
  
  bool havocbot_moveto_refresh_route(entity this)
@@@ -1278,9 -1263,7 +1278,9 @@@ float havocbot_moveto(entity this, vect
                        debuggoalstack(this);
  
                // Heading
 -              vector dir = ( ( this.goalcurrent.absmin + this.goalcurrent.absmax ) * 0.5 ) - (this.origin + this.view_ofs);
 +              vector dir;
 +              SET_DESTCOORDS(this.goalcurrent, this.origin, dir);
 +              dir = dir - (this.origin + this.view_ofs);
                dir.z = 0;
                bot_aimdir(this, dir, -1);
  
index 92bb25bec635fca06ea55f18fe9f4b8cb65e22e1,4d181c4659d7dca887f99ca1c1c6f888911b7781..9a82a162a14387fd8be0c3cf562576f143bfca3b
@@@ -1144,7 -1144,7 +1144,7 @@@ void GameCommand_nospectators(float req
                        FOREACH_CLIENT(IS_REAL_CLIENT(it) && (IS_SPEC(it) || IS_OBSERVER(it)) && !it.caplayer, LAMBDA(
                                if(!it.caplayer)
                                {
-                                       it.spectatortime = time;
+                                       CS(it).spectatortime = time;
                                        Send_Notification(NOTIF_ONE_ONLY, it, MSG_INFO, INFO_SPECTATE_WARNING, autocvar_g_maxplayers_spectator_blocktime);
                                }
                        ));
@@@ -1564,13 -1564,11 +1564,13 @@@ void GameCommand_trace(float request, f
  
                                case "walk":
                                {
 -                                      if (argc == 4)
 +                                      if (argc == 4 || argc == 5)
                                        {
                                                e = nextent(NULL);
 -                                              if (tracewalk(e, stov(argv(2)), e.mins, e.maxs, stov(argv(3)), MOVE_NORMAL)) LOG_INFO("can walk\n");
 -                                              else LOG_INFO("cannot walk\n");
 +                                              if (tracewalk(e, stov(argv(2)), e.mins, e.maxs, stov(argv(3)), stof(argv(4)), MOVE_NORMAL))
 +                                                      LOG_INFO("can walk\n");
 +                                              else
 +                                                      LOG_INFO("cannot walk\n");
                                                return;
                                        }
                                }
                        LOG_INFO("Incorrect parameters for ^2trace^7\n");
                case CMD_REQUEST_USAGE:
                {
 -                      LOG_INFO("\nUsage:^3 sv_cmd trace command (startpos endpos)\n");
 +                      LOG_INFO("\nUsage:^3 sv_cmd trace command [startpos endpos] [endpos_height]\n");
 +                      LOG_INFO("  Where startpos and endpos are parameters for 'walk' and 'showline' commands,\n");
 +                      LOG_INFO("  'endpos_height' is an optional parameter for 'walk' command,\n");
                        LOG_INFO("  Full list of commands here: \"debug, debug2, walk, showline.\"\n");
                        LOG_INFO("See also: ^2bbox, gettaginfo^7\n");
                        return;
diff --combined qcsrc/server/impulse.qc
index efa0f8425b86cb89413b834e6fcfeadbbbbfbf2c,d9036dea519ac5ade2431f2d31be037625523676..f528f07b4eb32c9537c798ce6afc727cc6c955c4
@@@ -352,9 -352,9 +352,9 @@@ void ImpulseCommands(entity this
  {
        if (game_stopped) return;
  
-       int imp = this.impulse;
+       int imp = CS(this).impulse;
        if (!imp) return;
-       this.impulse = 0;
+       CS(this).impulse = 0;
  
        if (MinigameImpulse(this, imp)) return;
  
@@@ -571,16 -571,114 +571,16 @@@ IMPULSE(waypoint_clear
        sprint(this, "all waypoints cleared\n");
  }
  
 -vector waypoint_getSymmetricalOrigin(vector org, int ctf_flags)
 -{
 -      vector new_org = org;
 -      if (fabs(autocvar_g_waypointeditor_symmetrical) == 1)
 -      {
 -              vector map_center = havocbot_middlepoint;
 -              if (autocvar_g_waypointeditor_symmetrical == -1)
 -                      map_center = autocvar_g_waypointeditor_symmetrical_origin;
 -
 -              new_org = Rotate(org - map_center, 360 * DEG2RAD / ctf_flags) + map_center;
 -      }
 -      else if (fabs(autocvar_g_waypointeditor_symmetrical) == 2)
 -      {
 -              float m = havocbot_symmetryaxis_equation.x;
 -              float q = havocbot_symmetryaxis_equation.y;
 -              if (autocvar_g_waypointeditor_symmetrical == -2)
 -              {
 -                      m = autocvar_g_waypointeditor_symmetrical_axis.x;
 -                      q = autocvar_g_waypointeditor_symmetrical_axis.y;
 -              }
 -
 -              new_org.x = (1 / (1 + m*m)) * ((1 - m*m) * org.x + 2 * m * org.y - 2 * m * q);
 -              new_org.y = (1 / (1 + m*m)) * (2 * m * org.x + (m*m - 1) * org.y + 2 * q);
 -      }
 -      new_org.z = org.z;
 -      return new_org;
 -}
 -
  IMPULSE(navwaypoint_spawn)
  {
        if (!autocvar_g_waypointeditor) return;
 -      entity e;
 -      vector org = this.origin;
 -      int ctf_flags = havocbot_symmetryaxis_equation.z;
 -      bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
 -                 || (autocvar_g_waypointeditor_symmetrical < 0));
 -      int order = ctf_flags;
 -      if(autocvar_g_waypointeditor_symmetrical_order >= 2)
 -      {
 -              order = autocvar_g_waypointeditor_symmetrical_order;
 -              ctf_flags = order;
 -      }
 -
 -      LABEL(add_wp);
 -      e = waypoint_spawn(org, org, 0);
 -      waypoint_schedulerelink(e);
 -      bprint(strcat("Waypoint spawned at ", vtos(org), "\n"));
 -      if(sym)
 -      {
 -              org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
 -              if (vdist(org - this.origin, >, 32))
 -              {
 -                      if(order > 2)
 -                              order--;
 -                      else
 -                              sym = false;
 -                      goto add_wp;
 -              }
 -      }
 +      waypoint_spawn_fromeditor(this);
  }
  
  IMPULSE(navwaypoint_remove)
  {
        if (!autocvar_g_waypointeditor) return;
 -      entity e = navigation_findnearestwaypoint(this, false);
 -      int ctf_flags = havocbot_symmetryaxis_equation.z;
 -      bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
 -                 || (autocvar_g_waypointeditor_symmetrical < 0));
 -      int order = ctf_flags;
 -      if(autocvar_g_waypointeditor_symmetrical_order >= 2)
 -      {
 -              order = autocvar_g_waypointeditor_symmetrical_order;
 -              ctf_flags = order;
 -      }
 -
 -      LABEL(remove_wp);
 -      if (!e) return;
 -      if (e.wpflags & WAYPOINTFLAG_GENERATED) return;
 -
 -      if (e.wphardwired)
 -      {
 -              LOG_INFO("^1Warning: ^7Removal of hardwired waypoints is not allowed in the editor. Please remove links from/to this waypoint (", vtos(e.origin), ") by hand from maps/", mapname, ".waypoints.hardwired\n");
 -              return;
 -      }
 -
 -      entity wp_sym = NULL;
 -      if (sym)
 -      {
 -              vector org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
 -              IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_GENERATED), {
 -                      if(vdist(org - it.origin, <, 3))
 -                      {
 -                              wp_sym = it;
 -                              break;
 -                      }
 -              });
 -      }
 -      bprint(strcat("Waypoint removed at ", vtos(e.origin), "\n"));
 -      waypoint_remove(e);
 -      if (sym && wp_sym)
 -      {
 -              e = wp_sym;
 -              if(order > 2)
 -                      order--;
 -              else
 -                      sym = false;
 -              goto remove_wp;
 -      }
 +      waypoint_remove_fromeditor(this);
  }
  
  IMPULSE(navwaypoint_relink)
@@@ -598,5 -696,93 +598,5 @@@ IMPULSE(navwaypoint_save
  IMPULSE(navwaypoint_unreachable)
  {
        if (!autocvar_g_waypointeditor) return;
 -      IL_EACH(g_waypoints, true,
 -      {
 -              it.colormod = '0.5 0.5 0.5';
 -              it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
 -      });
 -      entity e2 = navigation_findnearestwaypoint(this, false);
 -      navigation_markroutes(this, e2);
 -
 -      int j, m;
 -
 -      j = 0;
 -      m = 0;
 -      IL_EACH(g_waypoints, it.wpcost >= 10000000,
 -      {
 -              LOG_INFO("unreachable: ", etos(it), " ", vtos(it.origin), "\n");
 -              it.colormod_z = 8;
 -              it.effects |= EF_NODEPTHTEST | EF_BLUE;
 -              ++j;
 -              ++m;
 -      });
 -      if (j) LOG_INFOF("%d waypoints cannot be reached from here in any way (marked with blue light)\n", j);
 -      navigation_markroutes_inverted(e2);
 -
 -      j = 0;
 -      IL_EACH(g_waypoints, it.wpcost >= 10000000,
 -      {
 -              LOG_INFO("cannot reach me: ", etos(it), " ", vtos(it.origin), "\n");
 -              it.colormod_x = 8;
 -              if (!(it.effects & EF_NODEPTHTEST))  // not already reported before
 -                      ++m;
 -              it.effects |= EF_NODEPTHTEST | EF_RED;
 -              ++j;
 -      });
 -      if (j) LOG_INFOF("%d waypoints cannot walk to here in any way (marked with red light)\n", j);
 -      if (m) LOG_INFOF("%d waypoints have been marked total\n", m);
 -
 -      j = 0;
 -      IL_EACH(g_spawnpoints, true,
 -      {
 -              vector org = it.origin;
 -              tracebox(it.origin, PL_MIN_CONST, PL_MAX_CONST, it.origin - '0 0 512', MOVE_NOMONSTERS, NULL);
 -              setorigin(it, trace_endpos);
 -              if (navigation_findnearestwaypoint(it, false))
 -              {
 -                      setorigin(it, org);
 -                      it.effects &= ~EF_NODEPTHTEST;
 -                      it.model = "";
 -              }
 -              else
 -              {
 -                      setorigin(it, org);
 -                      LOG_INFO("spawn without waypoint: ", etos(it), " ", vtos(it.origin), "\n");
 -                      it.effects |= EF_NODEPTHTEST;
 -                      _setmodel(it, this.model);
 -                      it.frame = this.frame;
 -                      it.skin = this.skin;
 -                      it.colormod = '8 0.5 8';
 -                      setsize(it, '0 0 0', '0 0 0');
 -                      ++j;
 -              }
 -      });
 -      if (j) LOG_INFOF("%d spawnpoints have no nearest waypoint (marked by player model)\n", j);
 -
 -      j = 0;
 -      IL_EACH(g_items, true,
 -      {
 -              it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE);
 -              it.colormod = '0.5 0.5 0.5';
 -      });
 -      IL_EACH(g_items, true,
 -      {
 -              if (navigation_findnearestwaypoint(it, false)) continue;
 -              LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n");
 -              it.effects |= EF_NODEPTHTEST | EF_RED;
 -              it.colormod_x = 8;
 -              ++j;
 -      });
 -      if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked away from (marked with red light)\n", j);
 -
 -      j = 0;
 -      IL_EACH(g_items, true,
 -      {
 -              if (navigation_findnearestwaypoint(it, true)) continue;
 -              LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n");
 -              it.effects |= EF_NODEPTHTEST | EF_BLUE;
 -              it.colormod_z = 8;
 -              ++j;
 -      });
 -      if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked to (marked with blue light)\n", j);
 +      waypoint_unreachable(this);
  }