]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'LegendaryGuard/remove_spawnfunc_check_fields' into 'master'
authorbones_was_here <bones_was_here@xonotic.au>
Sun, 28 May 2023 05:18:47 +0000 (05:18 +0000)
committerbones_was_here <bones_was_here@xonotic.au>
Sun, 28 May 2023 05:18:47 +0000 (05:18 +0000)
Remove map entity whitelist checks

See merge request xonotic/xonotic-data.pk3dir!1180

13 files changed:
.gitlab-ci.yml
commands.cfg
qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qc
qcsrc/common/gamemodes/gamemode/freezetag/sv_freezetag.qh
qcsrc/server/bot/default/aim.qc
qcsrc/server/bot/default/bot.qc
qcsrc/server/bot/default/bot.qh
qcsrc/server/bot/default/havocbot/havocbot.qc
qcsrc/server/bot/default/havocbot/havocbot.qh
qcsrc/server/command/vote.qc
qcsrc/server/command/vote.qh
qcsrc/server/world.qc
xonotic-server.cfg

index b56f2b44d1b4b126e5b11f3be048aa75e21feaca..1feae253cbcd4231b69ae551e0466917e3ee043e 100644 (file)
@@ -75,7 +75,7 @@ test_sv_game:
     - wget -nv -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints\r
     - wget -nv -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache\r
 \r
-    - EXPECT=f2226bf353b6ff3dd3c489a742ce4e6a\r
+    - EXPECT=20c9c4ea0364fbb611656cc0b876e02b\r
     - HASH=$(${ENGINE} +exec serverbench.cfg\r
       | tee /dev/stderr\r
       | grep '^:'\r
index d5b03ee3e4dc89d5b120f75c9e211088240ae850..5d4a7271ee0e9ff3902e1dbac5cb292fb4352c62 100644 (file)
@@ -369,6 +369,7 @@ set sv_vote_stop 15 "a player can not call a vote again for this many seconds wh
 set sv_vote_majority_factor 0.5        "What percentage of the PLAYERS constitute a majority? (Must be at least 0.5, recommended: 0.5)"
 set sv_vote_majority_factor_of_voted 0.5 "What percentage of the VOTERS constitute a majority too? (Must be at least 0.5, recommended: 0.5)"
 set sv_vote_gamestart 0 "Allow voting during map change"
+set sv_vote_debug 0 "count votes by bots too for debugging purposes (to get a bot to vote exec this command: bot_cmd 1 cc vote yes)
 // when disabled, don't allow game type changes "note: set these two equal to JUST support simple majorities"
 set sv_vote_override_mostrecent 0
 
index 444268a2dd3da910f9b98c287c6ef7ad89056fd4..f1e6b1ce2ec0f5b7fb3f361f9674806459d525e6 100644 (file)
@@ -351,32 +351,39 @@ MUTATOR_HOOKFUNCTION(ft, PlayerDies)
        frag_target.respawn_time = time + 1;
        frag_target.respawn_flags |= RESPAWN_FORCE;
 
-       // Cases DEATH_TEAMCHANGE and DEATH_AUTOTEAMCHANGE are needed to fix a bug whe
-       // you succeed changing team through the menu: you both really die (gibbing) and get frozen
-       if(ITEM_DAMAGE_NEEDKILL(frag_deathtype)
-               || frag_deathtype == DEATH_TEAMCHANGE.m_id || frag_deathtype == DEATH_AUTOTEAMCHANGE.m_id)
+       // let the player die, they will be automatically frozen when they respawn
+       // it fixes a bug where you both really die (gibbing) and get frozen
+       // if you succeed changing team through the menu
+       if (frag_deathtype == DEATH_TEAMCHANGE.m_id || frag_deathtype == DEATH_AUTOTEAMCHANGE.m_id)
        {
-               // let the player die, they will be automatically frozen when they respawn
-               if (STAT(FROZEN, frag_target) != FROZEN_NORMAL)
-               {
-                       freezetag_Add_Score(frag_target, frag_attacker);
-                       freezetag_count_alive_players();
-                       freezetag_LastPlayerForTeam_Notify(frag_target);
-                       frag_target.freezetag_frozen_timeout = -2; // freeze on respawn
-               }
-               else
-               {
-                       float t = frag_target.freezetag_frozen_timeout;
-                       float t2 = frag_target.freezetag_frozen_time;
-                       Unfreeze(frag_target, false); // remove ice
-                       // keep timeout value so it can be restored when player will be refrozen on respawn
-                       // NOTE this can't be exactly -2 since game starts from time 2
-                       frag_target.freezetag_frozen_timeout = -t;
-                       frag_target.freezetag_frozen_time = t2;
-               }
+               freezetag_Add_Score(frag_target, frag_attacker);
+               freezetag_count_alive_players();
+               freezetag_LastPlayerForTeam_Notify(frag_target);
+               frag_target.freezetag_frozen_timeout = -2; // freeze on respawn
                return true;
        }
 
+       if(ITEM_DAMAGE_NEEDKILL(frag_deathtype))
+       {
+               // by restoring some health right after player death (soft-kill)
+               // weapons and ammo won't be reset
+               SetResourceExplicit(frag_target, RES_HEALTH, 1);
+               // restore armor as it was removed in PlayerDamage
+               SetResourceExplicit(frag_target, RES_ARMOR, frag_target.freezetag_frozen_armor);
+
+               // relocate
+               entity spot = SelectSpawnPoint(frag_target, true);
+               setorigin(frag_target, spot.origin);
+               frag_target.oldorigin = frag_target.origin;
+               frag_target.fixangle = true; // turn this way immediately
+               frag_target.angles = vec2(spot.angles);
+               frag_target.velocity = '0 0 0';
+               frag_target.oldvelocity = frag_target.velocity; // prevents fall damage, see CreatureFrame_FallDamage
+               frag_target.avelocity = '0 0 0';
+               frag_target.punchangle = '0 0 0';
+               frag_target.punchvector = '0 0 0';
+       }
+
        if (STAT(FROZEN, frag_target) == FROZEN_NORMAL)
                return true;
 
@@ -406,14 +413,7 @@ MUTATOR_HOOKFUNCTION(ft, PlayerSpawn)
 
        if(player.freezetag_frozen_timeout <= -2) // player was dead
        {
-               float t = player.freezetag_frozen_timeout;
-               float t2 = player.freezetag_frozen_time;
                freezetag_Freeze(player, NULL);
-               if (t < -2)
-               {
-                       player.freezetag_frozen_timeout = -t;
-                       player.freezetag_frozen_time = t2;
-               }
                return true;
        }
 
@@ -463,6 +463,8 @@ MUTATOR_HOOKFUNCTION(ft, Damage_Calculate)
        //float frag_damage = M_ARGV(4, float);
        vector frag_force = M_ARGV(6, vector);
 
+       frag_target.freezetag_frozen_armor = GetResource(frag_target, RES_ARMOR);
+
        if (STAT(FROZEN, frag_target) == FROZEN_NORMAL && autocvar_g_freezetag_revive_auto_reducible
                && autocvar_g_freezetag_frozen_maxtime > 0 && autocvar_g_freezetag_revive_auto)
        {
index 6fdd693cc044a5c5b7a04d92be38758837f92660..049f037147e19b22fa68d1c0c02d6004f9b30bb7 100644 (file)
@@ -31,6 +31,7 @@ REGISTER_MUTATOR(ft, false)
 .float freezetag_frozen_time;
 .float freezetag_frozen_timeout;
 .float freezetag_frozen_force;
+.float freezetag_frozen_armor;
 const float ICE_MAX_ALPHA = 1;
 const float ICE_MIN_ALPHA = 0.1;
 float freezetag_teams;
index 1d0f78b74615647ecc501160b7f35ab215aeceef..c058c06f55c5a5df7f8b167d8766fa9adc27092c 100644 (file)
@@ -161,6 +161,22 @@ void bot_aimdir(entity this, vector v, float maxfiredeviation)
        if (this.bot_prevaimtime == time)
                return;
 
+       // if skill is high enough bots will not have any aim smoothing or aim errors
+       if (SUPERBOT)
+       {
+               this.v_angle = vectoangles(normalize(v));
+
+               this.v_angle.x *= -1;
+
+               makevectors(this.v_angle);
+               shotorg = this.origin + this.view_ofs;
+               shotdir = v_forward;
+
+               // bot will fire on the next tick
+               this.bot_firetimer = time + 0.001;
+               return;
+       }
+
        // invalid aim dir (can happen when bot overlaps target)
        if(!v) return;
 
index 5bfa79aa55edbb3ace3986e6fe60c8721d259329..2a663a10dc7e02b377704e7540ab6e730e32d526 100644 (file)
@@ -65,7 +65,11 @@ void bot_think(entity this)
        if(autocvar_bot_god)
                this.flags |= FL_GODMODE;
 
-       this.bot_nextthink = max(time, this.bot_nextthink) + max(0.01, autocvar_bot_ai_thinkinterval * min(14 / (skill + this.bot_aiskill + 14), 1));
+       // if bot skill is high enough don't limit their think frequency
+       if (SUPERBOT)
+               this.bot_nextthink = max(time, this.bot_nextthink) + 0.005;
+       else
+               this.bot_nextthink = max(time, this.bot_nextthink) + max(0.01, autocvar_bot_ai_thinkinterval * min(14 / (skill + this.bot_aiskill + 14), 1));
 
        if (!IS_PLAYER(this) || (autocvar_g_campaign && !campaign_bots_may_start))
        {
@@ -91,13 +95,19 @@ void bot_think(entity this)
        this.dmg_save = 0;
        this.dmg_inflictor = NULL;
 
-       // calculate an aiming latency based on the skill setting
-       // (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
-       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 bot skill is high enough don't assign latency to them
+       if (SUPERBOT)
+               CS(this).ping = 0;
+       else
+       {
+               // calculate an aiming latency based on the skill setting
+               // (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
+               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 higher skill players take a less laggy server
+               // skill 10 = ping 0.2 (adrenaline)
+               // skill 0 = ping 0.7 (slightly drunk)
+       }
 
        // clear buttons
        PHYS_INPUT_BUTTON_ATCK(this) = false;
@@ -285,6 +295,24 @@ void bot_setnameandstuff(entity this)
        READSKILL(bot_thinkskill, 1, 0.5); // think skill
        READSKILL(bot_aiskill, 2, 0); // "ai" skill
 
+       // if bot skill is high enough don't limit their skill
+       if (SUPERBOT)
+       {
+               // commented out means they're meaningless with this high skill
+               // no reason to set them, uncomment if this changes
+               //this.havocbot_keyboardskill = 10;
+               //this.bot_moveskill = 10; //midair modifier sets this to 0 to disable bhop
+               //this.bot_dodgeskill = 10;
+               //this.bot_pingskill = 10;
+               //this.bot_weaponskill = 10;
+               //this.bot_aggresskill = 10;
+               this.bot_rangepreference = 1; // no range preference modification
+               //this.bot_aimskill = 10;
+               //this.bot_offsetskill = 10;
+               //this.bot_mouseskill = 10;
+               //this.bot_thinkskill = 10;
+               //this.bot_aiskill = 10;
+       }
        if (file >= 0 && argv(prio) != "")
                LOG_INFOF("^1Warning^7: too many parameters for bot %s, please check format of %s", bot_name, autocvar_bot_config_file);
 
index 95744035abebca2514da2256254f9edddb5d1719..6205e41b73b20bb86b731a939237de6d6bb4d92d 100644 (file)
@@ -20,6 +20,8 @@ const int AI_STATUS_STUCK                      = BIT(11); // Cannot reach any go
 .int aistatus;
 
 // Skill system
+#define SUPERBOT (skill > 100)
+
 float autoskill_nextthink;
 
 // havocbot_keyboardskill // keyboard movement
index 57c18f096f219b702a8e72cac6e66d1fc891cd93..3fd55f12a35639dd85e19246170f2cc5697e989a 100644 (file)
@@ -394,19 +394,66 @@ entity havocbot_select_an_item_of_group(entity this, int gr)
        return selected;
 }
 
+// Check for water/slime/lava and dangerous edges
+// (only when the bot is on the ground or jumping intentionally)
+// returns true for danger
+bool havocbot_checkdanger(entity this, vector dst_ahead)
+{
+       vector dst_down = dst_ahead - '0 0 3000';
+       traceline(this.origin + this.view_ofs, dst_ahead, true, NULL);
+
+       float s = CONTENT_SOLID;
+       if (trace_fraction == 1 && !this.jumppadcount
+               && !waypoint_is_hardwiredlink(this.goalcurrent_prev, this.goalcurrent)
+               && !(this.goalcurrent_prev && (this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP)))
+       if ((IS_ONGROUND(this)) || (this.aistatus & AI_STATUS_RUNNING) || (this.aistatus & AI_STATUS_ROAMING) || PHYS_INPUT_BUTTON_JUMP(this))
+       {
+               // Look downwards
+               traceline(dst_ahead , dst_down, true, NULL);
+               //te_lightning2(NULL, this.origin + this.view_ofs, dst_ahead); // Draw "ahead" look
+               //te_lightning2(NULL, dst_ahead, trace_endpos); // Draw "downwards" look
+               if (trace_endpos.z < this.origin.z + this.mins.z)
+               {
+                       if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
+                               return true;
+                       else if (trace_endpos.z < min(this.origin.z + this.mins.z, this.goalcurrent.origin.z) - 100)
+                               return true;
+                       else
+                       {
+                               s = pointcontents(trace_endpos + '0 0 1');
+                               if (s != CONTENT_SOLID)
+                               {
+                                       if (s == CONTENT_LAVA || s == CONTENT_SLIME)
+                                               return true;
+                                       else if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
+                                       {
+                                               // the traceline check isn't enough but is good as optimization,
+                                               // when not true (most of the time) this tracebox call is avoided
+                                               tracebox(dst_ahead, this.mins, this.maxs, dst_down, true, this);
+                                               if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
+                                               {
+                                                       return true;
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+       return false;
+}
+
 void havocbot_movetogoal(entity this)
 {
        vector diff;
        vector dir;
        vector flatdir;
        float dodge_enemy_factor = 1;
-       float maxspeed;
+       float maxspeed = autocvar_sv_maxspeed;
        //float dist;
        vector dodge;
        //if (this.goalentity)
        //      te_lightning2(this, this.origin, (this.goalentity.absmin + this.goalentity.absmax) * 0.5);
        CS(this).movement = '0 0 0';
-       maxspeed = autocvar_sv_maxspeed;
 
        PHYS_INPUT_BUTTON_CROUCH(this) = boolean(this.goalcurrent.wpflags & WAYPOINTFLAG_CROUCH);
 
@@ -530,7 +577,7 @@ void havocbot_movetogoal(entity this)
                                        }
                                }
                                vector gco = (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5;
-                               if (this.origin.z > gco.z && vdist(vec2(this.velocity), <, autocvar_sv_maxspeed))
+                               if (this.origin.z > gco.z && vdist(vec2(this.velocity), <, maxspeed))
                                {
                                        if (this.velocity.z < 0)
                                                this.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
@@ -549,7 +596,7 @@ void havocbot_movetogoal(entity this)
                        if(this.velocity.z > 0 && this.origin.z - this.lastteleport_origin.z > (this.maxs.z - this.mins.z) * 0.5)
                        {
                                vector velxy = this.velocity; velxy_z = 0;
-                               if(vdist(velxy, <, autocvar_sv_maxspeed * 0.2))
+                               if(vdist(velxy, <, maxspeed * 0.2))
                                {
                                        LOG_TRACE("Warning: ", this.netname, " got stuck on a jumppad (velocity in xy is ", vtos(velxy), "), trying to get out of it now");
                                        this.aistatus |= AI_STATUS_OUT_JUMPPAD;
@@ -921,7 +968,7 @@ void havocbot_movetogoal(entity this)
                }
                else
                {
-                       float s;
+                       float s = 0;
                        vector offset;
                        if(this.aistatus & AI_STATUS_OUT_WATER)
                                this.aistatus &= ~AI_STATUS_OUT_WATER;
@@ -1087,55 +1134,18 @@ void havocbot_movetogoal(entity this)
 
                        // Check for water/slime/lava and dangerous edges
                        // (only when the bot is on the ground or jumping intentionally)
-
                        offset = (vdist(this.velocity, >, 32) ? this.velocity * 0.2 : flatdir * 32);
                        vector dst_ahead = this.origin + this.view_ofs + offset;
-                       vector dst_down = dst_ahead - '0 0 3000';
-                       traceline(this.origin + this.view_ofs, dst_ahead, true, NULL);
-
                        bool unreachable = false;
-                       s = CONTENT_SOLID;
-                       if (trace_fraction == 1 && !this.jumppadcount
-                               && !waypoint_is_hardwiredlink(this.goalcurrent_prev, this.goalcurrent)
-                               && !(this.goalcurrent_prev && (this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP)))
-                       if((IS_ONGROUND(this)) || (this.aistatus & AI_STATUS_RUNNING) || (this.aistatus & AI_STATUS_ROAMING) || PHYS_INPUT_BUTTON_JUMP(this))
+                       if (havocbot_checkdanger(this, dst_ahead))
                        {
-                               // Look downwards
-                               traceline(dst_ahead , dst_down, true, NULL);
-                               //te_lightning2(NULL, this.origin + this.view_ofs, dst_ahead); // Draw "ahead" look
-                               //te_lightning2(NULL, dst_ahead, trace_endpos); // Draw "downwards" look
-                               if(trace_endpos.z < this.origin.z + this.mins.z)
+                               if (destorg.z > this.origin.z + jumpstepheightvec.z)
                                {
-                                       if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
-                                               danger_detected = true;
-                                       else if (trace_endpos.z < min(this.origin.z + this.mins.z, this.goalcurrent.origin.z) - 100)
-                                               danger_detected = true;
-                                       else
-                                       {
-                                               s = pointcontents(trace_endpos + '0 0 1');
-                                               if (s != CONTENT_SOLID)
-                                               {
-                                                       if (s == CONTENT_LAVA || s == CONTENT_SLIME)
-                                                               danger_detected = true;
-                                                       else if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
-                                                       {
-                                                               // the traceline check isn't enough but is good as optimization,
-                                                               // when not true (most of the time) this tracebox call is avoided
-                                                               tracebox(dst_ahead, this.mins, this.maxs, dst_down, true, this);
-                                                               if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
-                                                               {
-                                                                       if (destorg.z > this.origin.z + jumpstepheightvec.z)
-                                                                       {
-                                                                               // the goal is probably on an upper platform, assume bot can't get there
-                                                                               unreachable = true;
-                                                                       }
-                                                                       else
-                                                                               danger_detected = true;
-                                                               }
-                                                       }
-                                               }
-                                       }
+                                       // the goal is probably on an upper platform, assume bot can't get there
+                                       unreachable = true;
                                }
+                               else
+                                       danger_detected = true;
                        }
 
                        dir = flatdir;
@@ -1171,14 +1181,23 @@ void havocbot_movetogoal(entity this)
                dodge = havocbot_dodge(this);
                if (dodge)
                        dodge *= bound(0, 0.5 + (skill + this.bot_dodgeskill) * 0.1, 1);
+               // midair sets moveskill to 0 so avoid jumping when dodging in midair mutator
+               if (dodge.z > 0 && this.bot_moveskill == 0)
+                       dodge.z = 0;
                if (this.enemy)
                {
                        traceline(this.origin, (this.enemy.absmin + this.enemy.absmax) * 0.5, true, NULL);
                        if (IS_PLAYER(trace_ent))
                                dodge_enemy_factor = bound(0, (skill + this.bot_dodgeskill) / 7, 1);
                }
-       //      this.bot_dodgevector = dir;
-       //      this.bot_dodgevector_jumpbutton = PHYS_INPUT_BUTTON_JUMP(this);
+               //this.bot_dodgevector = dir;
+               //this.bot_dodgevector_jumpbutton = PHYS_INPUT_BUTTON_JUMP(this);
+
+               // don't dodge to danger
+               if (havocbot_checkdanger(this, this.origin + this.view_ofs + dodge * 32))
+               {
+                       dodge = '0 0 0';
+               }
        }
 
        float ladder_zdir = 0;
@@ -1254,6 +1273,36 @@ void havocbot_movetogoal(entity this)
        CS(this).movement_y = dir * v_right * maxspeed;
        CS(this).movement_z = dir * v_up * maxspeed;
 
+       // when high enough skill bots engage in combat they move randomly
+       if (SUPERBOT && this.aistatus == AI_STATUS_ATTACKING && !dodge)
+       {
+               if (!this.randomdirectiontime || this.randomdirectiontime + 0.35 < time)
+               {
+                       // 75% chance to generate a random direction to follow for
+                       // 0.3 seconds, there's a 15% chance to fail the generation
+                       // and only generation attempt one every 0.35s so bots move
+                       // towards their goal slightly
+                       if (random() < 0.15)
+                               this.randomdirection = '0 0 0';
+                       else
+                       {
+                               // random values from -1 to 1
+                               this.randomdirection.x = crandom() * maxspeed;
+                               this.randomdirection.y = crandom() * maxspeed;
+                               //this.randomdirection.z = crandom() * maxspeed;
+                       }
+
+                       this.randomdirectiontime = time;
+               }
+               if (this.randomdirectiontime + 0.3 >= time && this.randomdirection)
+               {
+                       CS(this).movement_x = this.randomdirection.x;
+                       CS(this).movement_y = this.randomdirection.y;
+                       // no random vertical direction
+               }
+       }
+
+
        // Emulate keyboard interface
        if (skill < 10)
                havocbot_keyboard_movement(this, destorg);
@@ -1269,7 +1318,12 @@ void havocbot_movetogoal(entity this)
                if (dodge * v_up > 0 && random() * frametime >= 0.2 * bound(0, (10 - skill - this.bot_dodgeskill) * 0.1, 1))
                        PHYS_INPUT_BUTTON_JUMP(this) = true;
                if (dodge * v_up < 0 && random() * frametime >= 0.5 * bound(0, (10 - skill - this.bot_dodgeskill) * 0.1, 1))
+               {
+                       if(IS_ONGROUND(this))
+                               PHYS_INPUT_BUTTON_JUMP(this) = false;
                        this.havocbot_ducktime = time + 0.3 / bound(0.1, skill + this.bot_dodgeskill, 10);
+                       PHYS_INPUT_BUTTON_CROUCH(this) = true;
+               }
        }
 }
 
@@ -1308,7 +1362,12 @@ void havocbot_chooseenemy(entity this)
        }
        if (time < this.havocbot_chooseenemy_finished)
                return;
-       this.havocbot_chooseenemy_finished = time + autocvar_bot_ai_enemydetectioninterval;
+       // don't limit the detection interval to several seconds for bots with enough skill
+       if (SUPERBOT)
+               this.havocbot_chooseenemy_finished = time + 0.1;
+       else
+               this.havocbot_chooseenemy_finished = time + autocvar_bot_ai_enemydetectioninterval;
+
        vector eye = this.origin + this.view_ofs;
        entity best = NULL;
        float bestrating = autocvar_bot_ai_enemydetectionradius ** 2;
@@ -1341,14 +1400,37 @@ void havocbot_chooseenemy(entity this)
                                continue;
 
                        vector v = (it.absmin + it.absmax) * 0.5;
-                       float rating = vlen2(v - eye);
-                       if (rating < bestrating && bot_shouldattack(this, it))
+                       float distance = vlen2(v - eye);
+
+                       if (SUPERBOT)
+                       {
+                               if (bot_shouldattack(this, it))
+                               {
+                                       // skilled enough bots take account target health and distance
+                                       float health = GetResource(it, RES_HEALTH);
+                                       float armor  = GetResource(it, RES_ARMOR);
+                                       float rating = bound(50, health + armor, 250) * distance;
+                                       if (!best || (rating < bestrating))
+                                       {
+                                               traceline(eye, v, true, this);
+                                               if (trace_ent == it || trace_fraction >= 1)
+                                               {
+                                                       best = it;
+                                                       bestrating = rating;
+                                               }
+                                       }
+                               }
+                       }
+                       else
                        {
-                               traceline(eye, v, true, this);
-                               if (trace_ent == it || trace_fraction >= 1)
+                               if (distance < bestrating && bot_shouldattack(this, it))
                                {
-                                       best = it;
-                                       bestrating = rating;
+                                       traceline(eye, v, true, this);
+                                       if (trace_ent == it || trace_fraction >= 1)
+                                       {
+                                               best = it;
+                                               bestrating = distance;
+                                       }
                                }
                        }
                });
@@ -1358,6 +1440,7 @@ void havocbot_chooseenemy(entity this)
                        scan_secondary_targets = true;
                        // restart the loop
                        bestrating = autocvar_bot_ai_enemydetectionradius ** 2;
+
                        goto scan_targets;
                }
 
@@ -1406,6 +1489,7 @@ float havocbot_chooseweapon_checkreload(entity this, .entity weaponentity, int n
 void havocbot_chooseweapon(entity this, .entity weaponentity)
 {
        int i;
+       float w;
 
        // ;)
        if(g_weaponarena_weapons == WEPSET(TUBA))
@@ -1415,17 +1499,33 @@ void havocbot_chooseweapon(entity this, .entity weaponentity)
        }
 
        // TODO: clean this up by moving it to weapon code
-       if(this.enemy==NULL)
+       if (this.enemy == NULL)
        {
-               // If no weapon was chosen get the first available weapon
-               if(this.(weaponentity).m_weapon==WEP_Null)
-               FOREACH(Weapons, it != WEP_Null, {
-                       if(client_hasweapon(this, it, weaponentity, true, false))
+               // Choose the first available weapon from medium range weaponlist
+               // TODO: don't do this but don't make bots hold out a blaster out either
+               for (i = 0; i < REGISTRY_COUNT(Weapons) && bot_weapons_mid[i] != -1 ; ++i){
+                       w = bot_weapons_mid[i];
+                       if (bot_custom_weapon)
                        {
-                               this.(weaponentity).m_switchweapon = it;
-                               return;
+                               if (client_hasweapon(this, REGISTRY_GET(Weapons, w), weaponentity, true, false))
+                               {
+                                       if ((this.(weaponentity).m_weapon == WEP_Null) || havocbot_chooseweapon_checkreload(this, weaponentity, w))
+                                               continue;
+                                       this.(weaponentity).m_switchweapon = REGISTRY_GET(Weapons, w);
+                                       return;
+                               }
                        }
-               });
+               }
+
+               // If no weapon was chosen get the first available weapon
+               if (this.(weaponentity).m_weapon == WEP_Null)
+                       FOREACH(Weapons, it != WEP_Null, {
+                               if (client_hasweapon(this, it, weaponentity, true, false))
+                               {
+                                       this.(weaponentity).m_switchweapon = it;
+                                       return;
+                               }
+                       });
                return;
        }
 
@@ -1434,7 +1534,6 @@ void havocbot_chooseweapon(entity this, .entity weaponentity)
        if(f < 1)
                return;
 
-       float w;
        float distance; distance=bound(10,vlen(this.origin-this.enemy.origin)-200,10000);
 
        // Should it do a weapon combo?
@@ -1672,8 +1771,11 @@ void havocbot_setupbot(entity this)
 vector havocbot_dodge(entity this)
 {
        // LordHavoc: disabled because this is too expensive
-       return '0 0 0';
-#if 0
+       // Dr. Jaska: re-enable this but only for bots with high enough skill
+       if (!SUPERBOT)
+               return '0 0 0';
+
+#if 1
        entity head;
        vector dodge, v, n;
        float danger, bestdanger, vl, d;
@@ -1694,7 +1796,8 @@ vector havocbot_dodge(entity this)
                                if (d > (0 - head.bot_dodgerating))
                                if (d < (vl * 0.2 + head.bot_dodgerating))
                                {
-                                       // calculate direction and distance from the flight path, by removing the forward axis
+                                       // calculate direction and distance from the
+                                       // flight path by removing the forward axis
                                        v = v - (n * (v * n));
                                        danger = head.bot_dodgerating - vlen(v);
                                        if (bestdanger < danger)
@@ -1718,5 +1821,7 @@ vector havocbot_dodge(entity this)
                head = head.chain;
        }
        return dodge;
+#else
+       return '0 0 0';
 #endif
 }
index 02c0f3e7d76b3d8912afcea77f8f0ea5fb779df1..d28d4d84b45d36cdab6e3ed9f6a8625e8034cfac 100644 (file)
@@ -29,6 +29,9 @@
 
 .vector havocbot_keyboard;
 
+.float randomdirectiontime;
+.vector randomdirection;
+
 /*
  * Functions
  */
index 6af0a28e4a79deb902c1e2e90ecfdaf7bc715f6a..02cb8e1cec9c730f967833becf5b93b14ea8308d 100644 (file)
@@ -124,8 +124,15 @@ string OriginalCallerName()
 //  Game logic for voting
 // =======================
 
-void VoteReset()
+void VoteStop(entity stopper, bool show_name);
+void VoteReset(bool verbose)
 {
+       if (verbose && vote_called)
+       {
+               VoteStop(NULL, true);
+               return;
+       }
+
        FOREACH_CLIENT(true, { it.vote_selection = 0; });
 
        if (vote_called)
@@ -145,13 +152,16 @@ void VoteReset()
        Nagger_VoteChanged();
 }
 
-void VoteStop(entity stopper)
+void VoteStop(entity stopper, bool canceled)
 {
-       bprint("\{1}^2* ^3", GetCallerName(stopper), "^2 stopped ^3", OriginalCallerName(), "^2's vote\n");
+       if (canceled)
+               bprint("\{1}^2* ^3", OriginalCallerName(), "^2's vote was canceled\n");
+       else
+               bprint("\{1}^2* ^3", GetCallerName(stopper), "^2 stopped ^3", OriginalCallerName(), "^2's vote\n");
        if (autocvar_sv_eventlog)   GameLogEcho(strcat(":vote:vstop:", ftos(stopper.playerid)));
        // Don't force them to wait for next vote, this way they can e.g. correct their vote.
        if ((vote_caller) && (stopper == vote_caller))   vote_caller.vote_waittime = time + autocvar_sv_vote_stop;
-       VoteReset();
+       VoteReset(false);
 }
 
 void VoteAccept()
@@ -163,21 +173,21 @@ void VoteAccept()
 
        if (vote_caller)   vote_caller.vote_waittime = 0;  // people like your votes, you don't need to wait to vote again
 
-       VoteReset();
+       VoteReset(false);
        Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_ACCEPT);
 }
 
 void VoteReject()
 {
        bprint("\{1}^2* ^3", OriginalCallerName(), "^2's vote for ", vote_called_display, "^2 was rejected\n");
-       VoteReset();
+       VoteReset(false);
        Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_FAIL);
 }
 
 void VoteTimeout()
 {
        bprint("\{1}^2* ^3", OriginalCallerName(), "^2's vote for ", vote_called_display, "^2 timed out\n");
-       VoteReset();
+       VoteReset(false);
        Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_FAIL);
 }
 
@@ -205,7 +215,6 @@ void VoteSpam(float notvoters, float mincount, string result)
 
 void VoteCount(float first_count)
 {
-       // declarations
        vote_accept_count = vote_reject_count = vote_abstain_count = 0;
 
        float vote_player_count = 0, notvoters = 0;
@@ -217,7 +226,7 @@ void VoteCount(float first_count)
        Nagger_VoteCountChanged();
 
        // add up all the votes from each connected client
-       FOREACH_CLIENT(IS_REAL_CLIENT(it), {
+       FOREACH_CLIENT(IS_REAL_CLIENT(it) || autocvar_sv_vote_debug, {
                ++vote_player_count;
                if (IS_PLAYER(it) || INGAME(it)) ++vote_real_player_count;
                switch (it.vote_selection)
@@ -243,7 +252,7 @@ void VoteCount(float first_count)
        {
                if (vote_caller)   vote_caller.vote_waittime = 0;
                print_to(vote_caller, "^1There are not enough players on this server to allow you to become vote master.");
-               VoteReset();
+               VoteReset(false);
                return;
        }
 
@@ -427,7 +436,7 @@ void ReadyRestart_force(bool is_fake_round_start)
        if (!is_fake_round_start && !autocvar_g_campaign)
                Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_COUNTDOWN_RESTART);
 
-       VoteReset();
+       VoteReset(true);
 
        // clear overtime, we have to decrease timelimit to its original value again.
        if (checkrules_overtimesadded > 0 && g_race_qualifying != 2)
@@ -968,6 +977,8 @@ void VoteCommand_call(int request, entity caller, int argc, string vote_command)
                                FOREACH_CLIENT(IS_REAL_CLIENT(it), { ++tmp_playercount; });
 
                                bprint("\{1}^2* ^3", OriginalCallerName(), "^2 calls a vote for ", vote_called_display, "\n");
+                               if (autocvar_sv_vote_debug)
+                                       bprint("\{1}^2* ^3", "^6DEBUG MODE ACTIVE: bots can vote too\n"); // so servers don't forget it on
                                if (autocvar_sv_eventlog)
                                        GameLogEcho(strcat(":vote:vcall:", ftos(vote_caller.playerid), ":", vote_called_display));
                                Nagger_VoteChanged();
@@ -1134,7 +1145,7 @@ void VoteCommand_no(int request, entity caller)  // CLIENT ONLY
                        }
                        else if (((caller == vote_caller) || caller.vote_master) && autocvar_sv_vote_no_stops_vote)
                        {
-                               VoteStop(caller);
+                               VoteStop(caller, true);
                        }
 
                        else  // everything went okay, continue changing vote
@@ -1188,7 +1199,7 @@ void VoteCommand_stop(int request, entity caller)  // BOTH
                case CMD_REQUEST_COMMAND:
                {
                        if (!vote_called)   print_to(caller, "^1No vote called.");
-                       else if ((caller == vote_caller) || !caller || caller.vote_master)   VoteStop(caller);
+                       else if ((caller == vote_caller) || !caller || caller.vote_master)   VoteStop(caller, false);
                        else   print_to(caller, "^1You are not allowed to stop that vote.");
                        return;
                }
index 3e8aaa33f3b1e7f4dc50d822f579c72d452123fa..509d4d233ab559f2b590ca54b9fda25a93d2c7e3 100644 (file)
@@ -3,6 +3,8 @@
 bool autocvar_sv_vote_call;
 bool autocvar_sv_vote_change;
 string autocvar_sv_vote_commands;
+bool autocvar_sv_vote_debug;
+bool autocvar_sv_vote_gamestart;
 int autocvar_sv_vote_limit;
 float autocvar_sv_vote_majority_factor;
 float autocvar_sv_vote_majority_factor_of_voted;
@@ -19,7 +21,6 @@ bool autocvar_sv_vote_singlecount;
 float autocvar_sv_vote_stop;
 float autocvar_sv_vote_timeout;
 float autocvar_sv_vote_wait;
-bool autocvar_sv_vote_gamestart;
 
 // definitions for command selection between progs
 const float VC_ASGNMNT_BOTH = 1;
@@ -56,7 +57,7 @@ string vote_parsed_display; // visual string which is fixed after being parsed
 
 // allow functions to be used in other code like world.qc and teamplay.qc
 void VoteThink();
-void VoteReset();
+void VoteReset(bool verbose);
 void VoteCommand(int request, entity caller, int argc, string vote_command);
 
 // warmup and nagger stuff
index b3b31ff1b9381c80761fd80ff416bbbb97130d79..56d293f98076b0091ffdd50efb848f6a5142dec0 100644 (file)
@@ -687,7 +687,7 @@ void GameplayMode_DelayedInit(entity this)
 
 void InitGameplayMode()
 {
-       VoteReset();
+       VoteReset(false);
 
        // find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds
        get_mi_min_max(1);
@@ -1337,7 +1337,7 @@ void NextLevel()
 
        //pos = FindIntermission ();
 
-       VoteReset();
+       VoteReset(true);
 
        DumpStats(true);
 
index 08987e7796f3efd9b27e0e8f02816d9bdddd15b5..981a95042e232fd2acd08601a574bd0b41e6317a 100644 (file)
@@ -395,11 +395,20 @@ set sv_waypointsprite_limitedrange 5120 "default maximum viewing distance of way
 
 set sv_itemstime 1 "enable networking of time left until respawn for items such as mega health/armor and powerups"
 
+// bans
 set g_ban_default_bantime 5400 "90 minutes"
 set g_ban_default_masksize 3 "masksize 0 means banning by UID only, 1 means banning by /8 (IPv6: /32) network, 2 means banning by /16 (IPv6: /48) network, 3 means banning by /24 (IPv6: /56) network, 4 means banning by single IP (IPv6: /64 network)"
+set g_ban_sync_uri "" "sync using this ban list provider (empty string to disable)"
+set g_ban_sync_interval 5 "sync every 5 minutes"
+set g_ban_sync_trusted_servers "" "request ban lists from these xonotic servers (do not include your own server there, or unbanning may fail)"
+set g_ban_sync_timeout 45 "time out in seconds for the ban sync requests"
+set g_ban_sync_trusted_servers_verify 0 "when set to 1, additional bans sent by the servers are ignored, and only bans for the requested IP are used"
 set g_ban_telluser 1 "notify the banned player about it when they try to join"
 set g_banned_list "" "format: IP remainingtime IP remainingtime ..."
 set g_banned_list_idmode "1" "when set, the IP banning system always uses the ID over the IP address (so a user in a banned IP range can connect if they have a valid signed ID)"
+set g_muteban_list "" "list of banned players from chat, format: IP playerkey ..."
+set g_playban_list "" "list of banned players from playing (forced to spectate), format: IP playerkey ..."
+set g_voteban_list "" "list of banned players from voting, format: IP playerkey ..."
 
 // useful vote aliases
 set timelimit_increment 5 "number of minutes added to the timer when voting for extendmatchtime"
@@ -440,12 +449,6 @@ set g_maplist_allow_frustrating 0 "allow impossible maps to be, e.g., voted for
 
 set sv_clones 0 "number of clones a player may make (reset by the \"kill\" command)"
 
-set g_ban_sync_uri "" "sync using this ban list provider (empty string to disable)"
-set g_ban_sync_interval 5 "sync every 5 minutes"
-set g_ban_sync_trusted_servers "" "request ban lists from these xonotic servers (do not include your own server there, or unbanning may fail)"
-set g_ban_sync_timeout 45 "time out in seconds for the ban sync requests"
-set g_ban_sync_trusted_servers_verify 0 "when set to 1, additional bans sent by the servers are ignored, and only bans for the requested IP are used"
-
 set g_showweaponspawns 1 "1: display waypoints for weapon spawns found on the map when a weapon key is pressed and the weapon is not owned; 2: for dropped weapons too; 3: for all the weapons sharing the same impulse"
 
 set g_ballistics_mindistance 2 "when shooting through walls thinner than this, treat them as this thick (useful because patches (curved surfaces) have no thickness)"