]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'terencehill/bot_ai' into 'master'
authorterencehill <piuntn@gmail.com>
Sun, 16 Oct 2022 15:38:34 +0000 (15:38 +0000)
committerterencehill <piuntn@gmail.com>
Sun, 16 Oct 2022 15:38:34 +0000 (15:38 +0000)
Bot aim improvements

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

35 files changed:
.gitlab-ci.yml
qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc
qcsrc/common/mutators/mutator/overkill/okhmg.qc
qcsrc/common/mutators/mutator/overkill/okmachinegun.qc
qcsrc/common/mutators/mutator/overkill/oknex.qc
qcsrc/common/mutators/mutator/overkill/okrpc.qc
qcsrc/common/mutators/mutator/overkill/okshotgun.qc
qcsrc/common/weapons/weapon/arc.qc
qcsrc/common/weapons/weapon/blaster.qc
qcsrc/common/weapons/weapon/crylink.qc
qcsrc/common/weapons/weapon/devastator.qc
qcsrc/common/weapons/weapon/electro.qc
qcsrc/common/weapons/weapon/fireball.qc
qcsrc/common/weapons/weapon/hagar.qc
qcsrc/common/weapons/weapon/hlac.qc
qcsrc/common/weapons/weapon/machinegun.qc
qcsrc/common/weapons/weapon/minelayer.qc
qcsrc/common/weapons/weapon/mortar.qc
qcsrc/common/weapons/weapon/porto.qc
qcsrc/common/weapons/weapon/rifle.qc
qcsrc/common/weapons/weapon/seeker.qc
qcsrc/common/weapons/weapon/shockwave.qc
qcsrc/common/weapons/weapon/shotgun.qc
qcsrc/common/weapons/weapon/vaporizer.qc
qcsrc/common/weapons/weapon/vortex.qc
qcsrc/server/bot/api.qh
qcsrc/server/bot/default/aim.qc
qcsrc/server/bot/default/aim.qh
qcsrc/server/bot/default/bot.qc
qcsrc/server/bot/default/bot.qh
qcsrc/server/bot/default/cvars.qh
qcsrc/server/bot/default/havocbot/havocbot.qc
qcsrc/server/bot/null/bot_null.qc
qcsrc/server/damage.qc
xonotic-server.cfg

index 87bb7bb07c412caf7368bec7950418fa7f2eb4b2..5d394c8ea6101e2ba286d4894e36559c4aa4a14a 100644 (file)
@@ -55,7 +55,7 @@ test_sv_game:
     - wget -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints\r
     - wget -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache\r
 \r
-    - EXPECT=458e9e611a757c745da05c85a37e576d\r
+    - EXPECT=5bbaf1e67c9a18ac159614a7e59b8d1e\r
     - HASH=$(${ENGINE} +timestamps 1 +exec serverbench.cfg\r
       | tee /dev/stderr\r
       | sed -e 's,^\[[^]]*\] ,,'\r
index 670b00fa42c0a2b56877c395efa355b535b88259..a1e1c59900ba4ac5ac435e8dcdc73d431f0bc57a 100644 (file)
@@ -1753,7 +1753,7 @@ void havocbot_role_ctf_carrier(entity this)
 
                entity goal = this.goalentity;
                if (havocbot_ctf_is_basewaypoint(goal) && vdist(goal.origin - this.origin, <, 100))
-                       this.goalentity_lock_timeout = time + ((this.bot_aimtarg) ? 2 : 3);
+                       this.goalentity_lock_timeout = time + ((this.enemy) ? 2 : 3);
 
                if (goal)
                        this.havocbot_cantfindflag = time + 10;
index 6bfafed67f9cab6b360f435ae3e15474148bf3fb..d5a2ba2e32f53142357695adbc8834dcab63c1dc 100644 (file)
@@ -55,7 +55,7 @@ void W_OverkillHeavyMachineGun_Attack_Auto(Weapon thiswep, entity actor, .entity
 METHOD(OverkillHeavyMachineGun, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
 {
        if(vdist(actor.origin - actor.enemy.origin, <, 3000 - bound(0, skill, 10) * 200))
-               PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false);
+               PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false, true);
 }
 
 METHOD(OverkillHeavyMachineGun, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
index 597d17e2e1ea87f3732bd5ea63ad9737fd537268..20db8d961863b3a37d6235eee7b38fb891ddad55 100644 (file)
@@ -49,7 +49,7 @@ void W_OverkillMachineGun_Attack_Auto(Weapon thiswep, entity actor, .entity weap
 METHOD(OverkillMachineGun, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
 {
        if(vdist(actor.origin - actor.enemy.origin, <, 3000 - bound(0, skill, 10) * 200))
-               PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false);
+               PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false, true);
 }
 
 METHOD(OverkillMachineGun, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
index aefc7234692f4e606075d2493dd785ea492dff30..4e001a65caff82f20594a097866aec95091e8b90 100644 (file)
@@ -117,7 +117,7 @@ void W_OverkillNex_Attack(Weapon thiswep, entity actor, .entity weaponentity, fl
 
 METHOD(OverkillNex, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
 {
-       if(bot_aim(actor, weaponentity, 1000000, 0, 1, false))
+       if(bot_aim(actor, weaponentity, 1000000, 0, 1, false, true))
                PHYS_INPUT_BUTTON_ATCK(actor) = true;
        else
        {
index ea700a9ca37a3a85fc27780223d0d67561bd26cb..4610a0b1d852924623d7d2e22b1985e07b1cc1d5 100644 (file)
@@ -130,7 +130,7 @@ void W_OverkillRocketPropelledChainsaw_Attack(Weapon thiswep, entity actor, .ent
 
 METHOD(OverkillRocketPropelledChainsaw, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
 {
-    PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(okrpc, speed), 0, WEP_CVAR_PRI(okrpc, lifetime), false);
+    PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(okrpc, speed), 0, WEP_CVAR_PRI(okrpc, lifetime), false, true);
 }
 
 METHOD(OverkillRocketPropelledChainsaw, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
index 627c14f20d47142047b98680bad575d35d20548f..6b4b3aa8e76ee0d4063ff4d6816f72e0607bc377 100644 (file)
@@ -5,11 +5,11 @@ METHOD(OverkillShotgun, wr_aim, void(entity thiswep, entity actor, .entity weapo
 {
        if (vdist(actor.origin - actor.enemy.origin, >, WEP_CVAR_PRI(okshotgun, bot_range)))
        {
-               PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false);
+               PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false, false);
        }
        else
        {
-               PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false);
+               PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false, false);
        }
 }
 
index 981130caee955341d2b82ea8e4d90852dda88a66..aa1a637c908a5b55f5fe754da206d8163e26e5b9 100644 (file)
@@ -611,7 +611,7 @@ METHOD(Arc, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
             WEP_CVAR(arc, beam_botaimspeed),
             0,
             WEP_CVAR(arc, beam_botaimlifetime),
-            false
+            false, true
         );
     }
     else
@@ -622,7 +622,7 @@ METHOD(Arc, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
             1000000,
             0,
             0.001,
-            false
+            false, true
         );
     }
 }
index e0fd2282baedab6b219b19de8b47f0a4541f96c4..6902f48532d0e13ea3f1deb4c148530a28e63a2c 100644 (file)
@@ -112,12 +112,12 @@ METHOD(Blaster, wr_aim, void(entity thiswep, entity actor, .entity weaponentity)
     if(WEP_CVAR(blaster, secondary))
     {
         if((random() * (WEP_CVAR_PRI(blaster, damage) + WEP_CVAR_SEC(blaster, damage))) > WEP_CVAR_PRI(blaster, damage))
-            { PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, WEP_CVAR_SEC(blaster, speed), 0, WEP_CVAR_SEC(blaster, lifetime), false); }
+            { PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, WEP_CVAR_SEC(blaster, speed), 0, WEP_CVAR_SEC(blaster, lifetime), false, true); }
         else
-            { PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(blaster, speed), 0, WEP_CVAR_PRI(blaster, lifetime), false); }
+            { PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(blaster, speed), 0, WEP_CVAR_PRI(blaster, lifetime), false, true); }
     }
     else
-        { PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(blaster, speed), 0, WEP_CVAR_PRI(blaster, lifetime), false); }
+        { PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(blaster, speed), 0, WEP_CVAR_PRI(blaster, lifetime), false, true); }
 }
 
 METHOD(Blaster, wr_think, void(Blaster thiswep, entity actor, .entity weaponentity, int fire))
index 5458a2496b92e9ab241660b0079fdcad09324092..86dd8fc929db6113253b97ddd37ae402cf31cc4a 100644 (file)
@@ -522,9 +522,9 @@ void W_Crylink_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
 METHOD(Crylink, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
 {
     if(random() < 0.10)
-        PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(crylink, speed), 0, WEP_CVAR_PRI(crylink, middle_lifetime), false);
+        PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(crylink, speed), 0, WEP_CVAR_PRI(crylink, middle_lifetime), false, true);
     else
-        PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, WEP_CVAR_SEC(crylink, speed), 0, WEP_CVAR_SEC(crylink, middle_lifetime), false);
+        PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, WEP_CVAR_SEC(crylink, speed), 0, WEP_CVAR_SEC(crylink, middle_lifetime), false, true);
 }
 METHOD(Crylink, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
 {
index 2337e6273370acf237605d172c0433d7ee965a8f..9ad68db2741d4e73f2c3514e15177b5e5f4ec14d 100644 (file)
@@ -362,79 +362,117 @@ void W_Devastator_Attack(Weapon thiswep, entity actor, .entity weaponentity, int
 
 METHOD(Devastator, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
 {
+    if (!WEP_CVAR(devastator, guidestop) && !actor.(weaponentity).rl_release)
+    {
+        int fired_rockets = 0;
+        IL_EACH(g_projectiles, it.realowner == actor && it.classname == "rocket",
+        {
+            fired_rockets++;
+        });
+        // release PHYS_INPUT_BUTTON_ATCK after all fired rocket exploded otherwise bot can't fire again
+        if (!fired_rockets)
+            return;
+    }
+
     // aim and decide to fire if appropriate
-    PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR(devastator, speed), 0, WEP_CVAR(devastator, lifetime), false);
+    float spd = WEP_CVAR(devastator, speed);
+    // simulate rocket guide by calculating rocket trajectory with higher speed
+    // 20 times faster at 90 degrees guide rate
+    if (WEP_CVAR(devastator, guiderate) > 0)
+        spd *= sqrt(WEP_CVAR(devastator, guiderate)) * (20 / 9.489); // 9.489 ~= sqrt(90)
+    // no need to fire with high accuracy on large distances if rockets can be guided
+    bool shot_accurate = (WEP_CVAR(devastator, guiderate) < 50);
+    PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, spd, 0, WEP_CVAR(devastator, lifetime), false, shot_accurate);
+    float pred_time = bound(0.02, 0.02 + (8 - skill) * 0.01, 0.1);
     if(skill >= 2) // skill 0 and 1 bots won't detonate rockets!
     {
         // decide whether to detonate rockets
-        float edgedamage, coredamage, edgeradius, recipricoledgeradius;
-        float selfdamage, teamdamage, enemydamage;
-        edgedamage = WEP_CVAR(devastator, edgedamage);
-        coredamage = WEP_CVAR(devastator, damage);
-        edgeradius = WEP_CVAR(devastator, radius);
-        recipricoledgeradius = 1 / edgeradius;
-        selfdamage = 0;
-        teamdamage = 0;
-        enemydamage = 0;
+        float selfdamage = 0, teamdamage = 0, enemydamage = 0;
+        float pred_selfdamage = 0, pred_teamdamage = 0, pred_enemydamage = 0;
+        float edgedamage = WEP_CVAR(devastator, edgedamage);
+        float coredamage = WEP_CVAR(devastator, damage);
+        float edgeradius = WEP_CVAR(devastator, radius);
         IL_EACH(g_projectiles, it.realowner == actor && it.classname == "rocket",
         {
             entity rocket = it;
             IL_EACH(g_bot_targets, it.bot_attack,
             {
-               float d = vlen(it.origin + (it.mins + it.maxs) * 0.5 - rocket.origin);
-               d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000);
-               // count potential damage according to type of target
-               if(it == actor)
-                       selfdamage = selfdamage + d;
-               else if(SAME_TEAM(it, actor))
-                       teamdamage = teamdamage + d;
-               else if(bot_shouldattack(actor, it))
-                       enemydamage = enemydamage + d;
-            });
-        });
-        float desirabledamage;
-        desirabledamage = enemydamage;
-        if(StatusEffects_active(STATUSEFFECT_Shield, actor) && !StatusEffects_active(STATUSEFFECT_SpawnShield, actor))
-            desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent;
-        if(teamplay && actor.team)
-            desirabledamage = desirabledamage - teamdamage;
-
-        makevectors(actor.v_angle);
-        IL_EACH(g_projectiles, it.realowner == actor && it.classname == "rocket",
-        {
-            if(skill > 9) // normal players only do this for the target they are tracking
-            {
-                   entity rocket = it;
-                   IL_EACH(g_bot_targets, it.bot_attack,
-                   {
-                       if((v_forward * normalize(rocket.origin - it.origin) < 0.1)
-                           && desirabledamage > 0.1 * coredamage
-                           ) PHYS_INPUT_BUTTON_ATCK2(actor) = true;
-                   });
-               }
-               else
-               {
-                //As the distance gets larger, a correct detonation gets near imposible
-                //Bots are assumed to use the rocket spawnfunc_light to see if the rocket gets near a player
-                if((v_forward * normalize(it.origin - actor.enemy.origin) < 0.1)
-                       && IS_PLAYER(actor.enemy)
-                       && (desirabledamage >= 0.1 * coredamage)
-                       )
+                // code to calculate damage is similar to the one used in RadiusDamageForSource with some simplifications
+                vector target_pos = it.origin + (it.maxs - it.mins) * 0.5;
+
+                float dist = vlen(target_pos - rocket.origin);
+                float dmg = 0;
+                if (dist <= edgeradius)
                 {
-                       float distance = bound(300, vlen(actor.origin - actor.enemy.origin), 30000);
-                       if(random() / distance * 300 > frametime * bound(0, (10 - skill) * 0.2, 1))
-                               PHYS_INPUT_BUTTON_ATCK2(actor) = true;
+                    float f = (edgeradius > 0) ? max(0, 1 - (dist / edgeradius)) : 1;
+                    dmg = coredamage * f + edgedamage * (1 - f);
                 }
-               }
+
+                float pred_dist = vlen(target_pos + it.velocity * pred_time - (rocket.origin + rocket.velocity * pred_time));
+                float pred_dmg = 0;
+                if (pred_dist <= edgeradius)
+                {
+                    float f = (edgeradius > 0) ? max(0, 1 - (pred_dist / edgeradius)) : 1;
+                    pred_dmg = coredamage * f + edgedamage * (1 - f);
+                }
+
+                // count potential damage according to type of target
+                if(it == actor)
+                {
+                    if(StatusEffects_active(STATUSEFFECT_Strength, it))
+                        dmg *= autocvar_g_balance_powerup_strength_damage;
+                    if(StatusEffects_active(STATUSEFFECT_Shield, it))
+                        dmg *= autocvar_g_balance_powerup_invincible_takedamage;
+                    // self damage reduction factor will be applied later to the total damage
+                    selfdamage += dmg;
+                    pred_selfdamage += pred_dmg;
+                }
+                else if(SAME_TEAM(it, actor))
+                {
+                    if(StatusEffects_active(STATUSEFFECT_Shield, it))
+                        dmg *= autocvar_g_balance_powerup_invincible_takedamage;
+                    // bot strength factor will be applied later to the total damage
+                    teamdamage += dmg;
+                    pred_teamdamage += pred_dmg;
+                }
+                else if(bot_shouldattack(actor, it))
+                {
+                    if(StatusEffects_active(STATUSEFFECT_Shield, it))
+                        dmg *= autocvar_g_balance_powerup_invincible_takedamage;
+                    // bot strength factor will be applied later to the total damage
+                    enemydamage += dmg;
+                    pred_enemydamage += pred_dmg;
+                }
+            });
         });
-        // if we would be doing at X percent of the core damage, detonate it
-        // but don't fire a new shot at the same time!
-        if(desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events
+
+        selfdamage *= autocvar_g_balance_selfdamagepercent;
+        pred_selfdamage *= autocvar_g_balance_selfdamagepercent;
+        if(StatusEffects_active(STATUSEFFECT_Strength, actor))
+        {
+            // FIXME bots don't know whether team damage is enabled or not
+            teamdamage *= autocvar_g_balance_powerup_strength_damage;
+            pred_teamdamage *= autocvar_g_balance_powerup_strength_damage;
+            enemydamage *= autocvar_g_balance_powerup_strength_damage;
+            pred_enemydamage *= autocvar_g_balance_powerup_strength_damage;
+        }
+
+        float good_damage = enemydamage;
+        float pred_good_damage = pred_enemydamage;
+        float bad_damage = selfdamage + teamdamage;
+        float pred_bad_damage = pred_selfdamage + pred_teamdamage;
+
+        // detonate if predicted good damage is lower (current good damage is maximum)
+        // or if predicted bad damage is too much
+        if(good_damage > coredamage * 0.1 && good_damage > bad_damage * 1.5
+            && (pred_good_damage < good_damage + 2 || pred_good_damage < pred_bad_damage * 1.5))
+        {
             PHYS_INPUT_BUTTON_ATCK2(actor) = true;
-        if((skill > 6.5) && (selfdamage > GetResource(actor, RES_HEALTH)))
+        }
+        if(skill >= 7 && selfdamage > GetResource(actor, RES_HEALTH))
             PHYS_INPUT_BUTTON_ATCK2(actor) = false;
-        //if(PHYS_INPUT_BUTTON_ATCK2(actor) == true)
-        //     dprint(ftos(desirabledamage),"\n");
+
+        // don't fire a new shot at the same time!
         if(PHYS_INPUT_BUTTON_ATCK2(actor)) PHYS_INPUT_BUTTON_ATCK(actor) = false;
     }
 }
index 3a70920e07839ed32bee603e7ec368c640b9bac2..e122077b36d68c5d8f91ed12c449f931679ba2bb 100644 (file)
@@ -509,9 +509,9 @@ METHOD(Electro, wr_aim, void(entity thiswep, entity actor, .entity weaponentity)
         float shoot;
 
         if(WEP_CVAR_PRI(electro, speed))
-            shoot = bot_aim(actor, weaponentity, WEP_CVAR_PRI(electro, speed), 0, WEP_CVAR_PRI(electro, lifetime), false);
+            shoot = bot_aim(actor, weaponentity, WEP_CVAR_PRI(electro, speed), 0, WEP_CVAR_PRI(electro, lifetime), false, true);
         else
-            shoot = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false);
+            shoot = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false, true);
 
         if(shoot)
         {
@@ -521,7 +521,7 @@ METHOD(Electro, wr_aim, void(entity thiswep, entity actor, .entity weaponentity)
     }
     else
     {
-        if(bot_aim(actor, weaponentity, WEP_CVAR_SEC(electro, speed), WEP_CVAR_SEC(electro, speed_up), WEP_CVAR_SEC(electro, lifetime), true))
+        if(bot_aim(actor, weaponentity, WEP_CVAR_SEC(electro, speed), WEP_CVAR_SEC(electro, speed_up), WEP_CVAR_SEC(electro, lifetime), true, true))
         {
             PHYS_INPUT_BUTTON_ATCK2(actor) = true;
             if(random() < 0.03) actor.bot_secondary_electromooth = 0;
index b4a3da78a82e29c34afcd999ade1437cb2ef176c..391aa7294c2a24b6170acc68ba312f2e2680cf43 100644 (file)
@@ -342,7 +342,7 @@ METHOD(Fireball, wr_aim, void(entity thiswep, entity actor, .entity weaponentity
     PHYS_INPUT_BUTTON_ATCK2(actor) = false;
     if(actor.bot_primary_fireballmooth == 0)
     {
-        if(bot_aim(actor, weaponentity, WEP_CVAR_PRI(fireball, speed), 0, WEP_CVAR_PRI(fireball, lifetime), false))
+        if(bot_aim(actor, weaponentity, WEP_CVAR_PRI(fireball, speed), 0, WEP_CVAR_PRI(fireball, lifetime), false, false))
         {
             PHYS_INPUT_BUTTON_ATCK(actor) = true;
             if(random() < 0.02) actor.bot_primary_fireballmooth = 0;
@@ -350,7 +350,7 @@ METHOD(Fireball, wr_aim, void(entity thiswep, entity actor, .entity weaponentity
     }
     else
     {
-        if(bot_aim(actor, weaponentity, WEP_CVAR_SEC(fireball, speed), WEP_CVAR_SEC(fireball, speed_up), WEP_CVAR_SEC(fireball, lifetime), true))
+        if(bot_aim(actor, weaponentity, WEP_CVAR_SEC(fireball, speed), WEP_CVAR_SEC(fireball, speed_up), WEP_CVAR_SEC(fireball, lifetime), true, false))
         {
             PHYS_INPUT_BUTTON_ATCK2(actor) = true;
             if(random() < 0.01) actor.bot_primary_fireballmooth = 1;
index bfda2124de9119b3c7d87804a25f895a88786510..82f75f3633e63e70dc68792a7611ce701f0f5c5d 100644 (file)
@@ -387,9 +387,9 @@ void W_Hagar_Attack_Auto(Weapon thiswep, entity actor, .entity weaponentity, int
 METHOD(Hagar, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
 {
     if(random()>0.15)
-        PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(hagar, speed), 0, WEP_CVAR_PRI(hagar, lifetime), false);
+        PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(hagar, speed), 0, WEP_CVAR_PRI(hagar, lifetime), false, true);
     else // not using secondary_speed since these are only 15% and should cause some ricochets without re-aiming
-        PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(hagar, speed), 0, WEP_CVAR_PRI(hagar, lifetime), false);
+        PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(hagar, speed), 0, WEP_CVAR_PRI(hagar, lifetime), false, true);
 }
 METHOD(Hagar, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
 {
index d89cdb1287051f2d49ed6d12f89d9f66a15492f2..e4db316af9dc49aa36444720cd97d3fd8cd5cc1f 100644 (file)
@@ -153,7 +153,7 @@ void W_HLAC_Attack_Frame(Weapon thiswep, entity actor, .entity weaponentity, int
 
 METHOD(HLAC, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
 {
-    PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(hlac, speed), 0, WEP_CVAR_PRI(hlac, lifetime), false);
+    PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(hlac, speed), 0, WEP_CVAR_PRI(hlac, lifetime), false, true);
 }
 METHOD(HLAC, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
 {
index 684ae22bc740858542996ce53647b45491b23ab8..15a4d6a3c180f809cd9ade5fd6de424c00e972a2 100644 (file)
@@ -138,9 +138,9 @@ void W_MachineGun_Attack_Burst(Weapon thiswep, entity actor, .entity weaponentit
 METHOD(MachineGun, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
 {
     if(vdist(actor.origin - actor.enemy.origin, <, 3000 - bound(0, skill, 10) * 200))
-        PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false);
+        PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false, true);
     else
-        PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false);
+        PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false, true);
 }
 METHOD(MachineGun, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
 {
index 8a8d4839cd4c307a6f62a418884abf551569fc92..9ab5533f1c0e4ea9e33ea06b4acaa416ea8feb6d 100644 (file)
@@ -346,7 +346,7 @@ METHOD(MineLayer, wr_aim, void(entity thiswep, entity actor, .entity weaponentit
     if(minecount >= WEP_CVAR(minelayer, limit))
         PHYS_INPUT_BUTTON_ATCK(actor) = false;
     else
-        PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR(minelayer, speed), 0, WEP_CVAR(minelayer, lifetime), false);
+        PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR(minelayer, speed), 0, WEP_CVAR(minelayer, lifetime), false, false);
     if(skill >= 2) // skill 0 and 1 bots won't detonate mines!
     {
         // decide whether to detonate mines
index 6f37469f032dd6cc94e1bee71149d1d25215bcba..a1264748f3204cf4a90ce357d967b539dbea94ee 100644 (file)
@@ -256,7 +256,7 @@ METHOD(Mortar, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
     PHYS_INPUT_BUTTON_ATCK2(actor) = false;
     if(actor.bot_secondary_grenademooth == 0) // WEAPONTODO: merge this into using WEP_CVAR_BOTH
     {
-        if(bot_aim(actor, weaponentity, WEP_CVAR_PRI(mortar, speed), WEP_CVAR_PRI(mortar, speed_up), WEP_CVAR_PRI(mortar, lifetime), true))
+        if(bot_aim(actor, weaponentity, WEP_CVAR_PRI(mortar, speed), WEP_CVAR_PRI(mortar, speed_up), WEP_CVAR_PRI(mortar, lifetime), true, true))
         {
             PHYS_INPUT_BUTTON_ATCK(actor) = true;
             if(random() < 0.01) actor.bot_secondary_grenademooth = 1;
@@ -264,7 +264,7 @@ METHOD(Mortar, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
     }
     else
     {
-        if(bot_aim(actor, weaponentity, WEP_CVAR_SEC(mortar, speed), WEP_CVAR_SEC(mortar, speed_up), WEP_CVAR_SEC(mortar, lifetime), true))
+        if(bot_aim(actor, weaponentity, WEP_CVAR_SEC(mortar, speed), WEP_CVAR_SEC(mortar, speed_up), WEP_CVAR_SEC(mortar, lifetime), true, true))
         {
             PHYS_INPUT_BUTTON_ATCK2(actor) = true;
             if(random() < 0.02) actor.bot_secondary_grenademooth = 0;
index b161d1054d8e1f1b6d608d173196072a8aca11e8..7ee4162157077a97024dc710403f51ff8e845df1 100644 (file)
@@ -338,7 +338,7 @@ METHOD(PortoLaunch, wr_aim, void(entity thiswep, entity actor, .entity weaponent
     PHYS_INPUT_BUTTON_ATCK(actor) = false;
     PHYS_INPUT_BUTTON_ATCK2(actor) = false;
     if(!WEP_CVAR(porto, secondary))
-        if(bot_aim(actor, weaponentity, WEP_CVAR_PRI(porto, speed), 0, WEP_CVAR_PRI(porto, lifetime), false))
+        if(bot_aim(actor, weaponentity, WEP_CVAR_PRI(porto, speed), 0, WEP_CVAR_PRI(porto, lifetime), false, true))
             PHYS_INPUT_BUTTON_ATCK(actor) = true;
 }
 METHOD(PortoLaunch, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
index 3efa61102d76eecaaf2b29c81a239b127f54622b..803930cc0cfc6880a01df26f36109f5efe794169 100644 (file)
@@ -94,7 +94,7 @@ METHOD(Rifle, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
         actor.bot_secondary_riflemooth = 0;
     if(actor.bot_secondary_riflemooth == 0)
     {
-        if(bot_aim(actor, weaponentity, 1000000, 0, 0.001, false))
+        if(bot_aim(actor, weaponentity, 1000000, 0, 0.001, false, true))
         {
             PHYS_INPUT_BUTTON_ATCK(actor) = true;
             if(random() < 0.01) actor.bot_secondary_riflemooth = 1;
@@ -102,7 +102,7 @@ METHOD(Rifle, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
     }
     else
     {
-        if(bot_aim(actor, weaponentity, 1000000, 0, 0.001, false))
+        if(bot_aim(actor, weaponentity, 1000000, 0, 0.001, false, true))
         {
             PHYS_INPUT_BUTTON_ATCK2(actor) = true;
             if(random() < 0.03) actor.bot_secondary_riflemooth = 0;
index b65b0fe61b745ac934920a82aa6b58eb46a92066..bf57fb9c1c5bf0e66efad77f3aa54415ec517354 100644 (file)
@@ -533,12 +533,12 @@ METHOD(Seeker, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
     if(WEP_CVAR(seeker, type) == 1)
     {
         if(W_Seeker_Tagged_Info(actor, weaponentity, actor.enemy) != NULL)
-            PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR(seeker, missile_speed_max), 0, WEP_CVAR(seeker, missile_lifetime), false);
+            PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR(seeker, missile_speed_max), 0, WEP_CVAR(seeker, missile_lifetime), false, false);
         else
-            PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, WEP_CVAR(seeker, tag_speed), 0, WEP_CVAR(seeker, tag_lifetime), false);
+            PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, WEP_CVAR(seeker, tag_speed), 0, WEP_CVAR(seeker, tag_lifetime), false, false);
     }
     else
-        PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR(seeker, tag_speed), 0, WEP_CVAR(seeker, tag_lifetime), false);
+        PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR(seeker, tag_speed), 0, WEP_CVAR(seeker, tag_lifetime), false, true);
 }
 METHOD(Seeker, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
 {
index 2429a301775621f979893f2eab1be96c61488a46..7863de55e33f73a9ba234ede32b57dea60fe6e9e 100644 (file)
@@ -587,9 +587,9 @@ void W_Shockwave_Attack(Weapon thiswep, entity actor, .entity weaponentity)
 METHOD(Shockwave, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
 {
     if(vdist(actor.origin - actor.enemy.origin, <=, WEP_CVAR(shockwave, melee_range)))
-        { PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false); }
+        { PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false, false); }
     else
-        { PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false); }
+        { PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false, false); }
 }
 METHOD(Shockwave, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
 {
index 45431597e529f9caa731c49c252a73a89e8da3a6..e304c4c2b4520d663cc6147068f4439cc81ae7a9 100644 (file)
@@ -196,9 +196,9 @@ void W_Shotgun_Attack3_Frame1(Weapon thiswep, entity actor, .entity weaponentity
 METHOD(Shotgun, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
 {
     if(vdist(actor.origin - actor.enemy.origin, <=, WEP_CVAR_SEC(shotgun, melee_range)))
-        PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false);
+        PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false, false);
     else
-        PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false);
+        PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false, false);
 }
 
 METHOD(Shotgun, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
index 09d8ef36b696e04e142611f3df648f3464a01f26..25aa1b6658c62367fe0f9787f7230f2be7678a4c 100644 (file)
@@ -245,9 +245,9 @@ void W_RocketMinsta_Attack(entity actor, .entity weaponentity, int mode)
 METHOD(Vaporizer, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
 {
     if((actor.items & IT_UNLIMITED_AMMO) || GetResource(actor, thiswep.ammo_type) > 0)
-        PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 1, false);
+        PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 1, false, true);
     else
-        PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, WEP_CVAR_SEC(vaporizer, speed), 0, WEP_CVAR_SEC(vaporizer, lifetime), false); // WEAPONTODO: replace with proper vaporizer cvars
+        PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, WEP_CVAR_SEC(vaporizer, speed), 0, WEP_CVAR_SEC(vaporizer, lifetime), false, true); // WEAPONTODO: replace with proper vaporizer cvars
 }
 METHOD(Vaporizer, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
 {
index 5ba28f9c72ab990a5200ea8460021f2e9a3457df..98762a03e13f4fe1c96a7a187348fdc069e1d81f 100644 (file)
@@ -169,13 +169,8 @@ void W_Vortex_Charge(entity actor, .entity weaponentity, float dt)
 
 METHOD(Vortex, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
 {
-    if(bot_aim(actor, weaponentity, 1000000, 0, 1, false))
+    if(bot_aim(actor, weaponentity, 1000000, 0, 1, false, true))
         PHYS_INPUT_BUTTON_ATCK(actor) = true;
-    else
-    {
-        if(WEP_CVAR(vortex, charge))
-            PHYS_INPUT_BUTTON_ATCK2(actor) = true;
-    }
 }
 METHOD(Vortex, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
 {
index 65ab46bad95a642a76c9c7e981994fe1bfb4627d..db1cb939b5217f6ad051bcbcd8150fa85651346b 100644 (file)
@@ -64,7 +64,7 @@ float skill;
 .int wpflags;
 .entity wphw00, wphw01, wphw02, wphw03, wphw04, wphw05, wphw06, wphw07;
 
-bool bot_aim(entity this, .entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, float applygravity);
+bool bot_aim(entity this, .entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, float applygravity, bool shot_accurate);
 void bot_aim_reset(entity this);
 void bot_clientconnect(entity this);
 void bot_clientdisconnect(entity this);
index 6b1488bac494ec2e2b68987aa13f47122a6dc105..1d0f78b74615647ecc501160b7f35ab215aeceef 100644 (file)
@@ -89,26 +89,6 @@ float findtrajectorywithleading(vector org, vector m1, vector m2, entity targ, f
        return false;
 }
 
-void lag_update(entity this)
-{
-       if (this.lag1_time && time > this.lag1_time) { this.lag_func(this, this.lag1_time, this.lag1_float1, this.lag1_float2, this.lag1_entity1, this.lag1_vec1, this.lag1_vec2, this.lag1_vec3, this.lag1_vec4); this.lag1_time = 0; }
-       if (this.lag2_time && time > this.lag2_time) { this.lag_func(this, this.lag2_time, this.lag2_float1, this.lag2_float2, this.lag2_entity1, this.lag2_vec1, this.lag2_vec2, this.lag2_vec3, this.lag2_vec4); this.lag2_time = 0; }
-       if (this.lag3_time && time > this.lag3_time) { this.lag_func(this, this.lag3_time, this.lag3_float1, this.lag3_float2, this.lag3_entity1, this.lag3_vec1, this.lag3_vec2, this.lag3_vec3, this.lag3_vec4); this.lag3_time = 0; }
-       if (this.lag4_time && time > this.lag4_time) { this.lag_func(this, this.lag4_time, this.lag4_float1, this.lag4_float2, this.lag4_entity1, this.lag4_vec1, this.lag4_vec2, this.lag4_vec3, this.lag4_vec4); this.lag4_time = 0; }
-       if (this.lag5_time && time > this.lag5_time) { this.lag_func(this, this.lag5_time, this.lag5_float1, this.lag5_float2, this.lag5_entity1, this.lag5_vec1, this.lag5_vec2, this.lag5_vec3, this.lag5_vec4); this.lag5_time = 0; }
-}
-
-float lag_additem(entity this, float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4)
-{
-       if (this.lag1_time == 0) {this.lag1_time = t;this.lag1_float1 = f1;this.lag1_float2 = f2;this.lag1_entity1 = e1;this.lag1_vec1 = v1;this.lag1_vec2 = v2;this.lag1_vec3 = v3;this.lag1_vec4 = v4;return true;}
-       if (this.lag2_time == 0) {this.lag2_time = t;this.lag2_float1 = f1;this.lag2_float2 = f2;this.lag2_entity1 = e1;this.lag2_vec1 = v1;this.lag2_vec2 = v2;this.lag2_vec3 = v3;this.lag2_vec4 = v4;return true;}
-       if (this.lag3_time == 0) {this.lag3_time = t;this.lag3_float1 = f1;this.lag3_float2 = f2;this.lag3_entity1 = e1;this.lag3_vec1 = v1;this.lag3_vec2 = v2;this.lag3_vec3 = v3;this.lag3_vec4 = v4;return true;}
-       if (this.lag4_time == 0) {this.lag4_time = t;this.lag4_float1 = f1;this.lag4_float2 = f2;this.lag4_entity1 = e1;this.lag4_vec1 = v1;this.lag4_vec2 = v2;this.lag4_vec3 = v3;this.lag4_vec4 = v4;return true;}
-       if (this.lag5_time == 0) {this.lag5_time = t;this.lag5_float1 = f1;this.lag5_float2 = f2;this.lag5_entity1 = e1;this.lag5_vec1 = v1;this.lag5_vec2 = v2;this.lag5_vec3 = v3;this.lag5_vec4 = v4;return true;}
-       // no room for it (what is the best thing to do here??)
-       return false;
-}
-
 bool bot_shouldattack(entity this, entity targ)
 {
        if (targ.team == this.team)
@@ -148,24 +128,6 @@ bool bot_shouldattack(entity this, entity targ)
        return true;
 }
 
-void bot_lagfunc(entity this, float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4)
-{
-       this.bot_aimtarg = e1;
-       this.bot_aimlatency = CS(this).ping; // FIXME?  Shouldn't this be in the lag item?
-       //this.bot_aimorigin = v1;
-       //this.bot_aimvelocity = v2;
-       this.bot_aimtargorigin = v3;
-       this.bot_aimtargvelocity = v4;
-       if(skill <= 0)
-               this.bot_canfire = (random() < 0.8);
-       else if(skill <= 1)
-               this.bot_canfire = (random() < 0.9);
-       else if(skill <= 2)
-               this.bot_canfire = (random() < 0.95);
-       else
-               this.bot_canfire = 1;
-}
-
 // this function should be called after bot_aim so the aim is reset the next frame
 void bot_aim_reset(entity this)
 {
@@ -204,19 +166,23 @@ void bot_aimdir(entity this, vector v, float maxfiredeviation)
 
        float skill_save = skill;
        // allow turning in a more natural way when bot is walking
-       if (!this.bot_aimtarg)
+       if (!this.enemy)
                skill = max(4, skill);
 
        // get the desired angles to aim at
        //dprint(" at:", vtos(v));
-       v = normalize(v);
+       //v = normalize(v);
        //te_lightning2(NULL, this.origin + this.view_ofs, this.origin + this.view_ofs + v * 200);
        if (time >= this.bot_badaimtime)
        {
-               this.bot_badaimtime = max(this.bot_badaimtime + 0.3, time);
-               this.bot_badaimoffset = randomvec() * bound(0, 5 - 0.5 * (skill+this.bot_offsetskill), 5) * autocvar_bot_ai_aimskill_offset;
+               this.bot_badaimtime = max(this.bot_badaimtime + 0.2 + 0.3 * random(), time);
+               int f = bound(0, 1 - 0.1 * (skill + this.bot_offsetskill), 1);
+               this.bot_badaimoffset = randomvec() * f * autocvar_bot_ai_aimskill_offset;
+               this.bot_badaimoffset.x *= 0.7; // smaller vertical offset
        }
-       desiredang = vectoangles(v) + this.bot_badaimoffset;
+       float enemy_factor = ((this.enemy) ? 5 : 2);
+       // apply enemy_factor every frame so that the bigger offset is applied instantly when the bot aims to a new target
+       desiredang = vectoangles(v) + this.bot_badaimoffset * enemy_factor;
        //dprint(" desired:", vtos(desiredang));
        if (desiredang.x >= 180)
                desiredang.x = desiredang.x - 360;
@@ -309,28 +275,12 @@ void bot_aimdir(entity this, vector v, float maxfiredeviation)
        //this.v_angle = this.v_angle + diffang * bound(frametime, r * frametime * (2+skill*skill*0.05-random()*0.05*(10-skill)), 1);
        r = bound(delta_t, r * delta_t * (2 + ((skill + this.bot_mouseskill) ** 3) * 0.005 - random()), 1);
        this.v_angle += diffang * (r + (1 - r) * bound(0, 1 - autocvar_bot_ai_aimskill_mouse, 1));
-
        this.v_angle_z = 0;
        this.v_angle_y = this.v_angle.y - floor(this.v_angle.y / 360) * 360;
        //dprint(" turn:", vtos(this.v_angle));
 
-       makevectors(this.v_angle);
-       shotorg = this.origin + this.view_ofs;
-       shotdir = v_forward;
-
-       //dprint(" dir:", vtos(v_forward));
-       //te_lightning2(NULL, shotorg, shotorg + shotdir * 100);
-
-       // calculate turn angles again
-       //diffang = desiredang - this.v_angle;
-       //diffang_y = diffang_y - floor(diffang_y / 360) * 360;
-       //if (diffang_y >= 180)
-       //      diffang_y = diffang_y - 360;
-
        skill = skill_save;
 
-       //dprint("e ", vtos(diffang), " < ", ftos(maxfiredeviation), "\n");
-
        if (maxfiredeviation <= 0)
                return;
 
@@ -340,8 +290,18 @@ void bot_aimdir(entity this, vector v, float maxfiredeviation)
                return;
        }
 
+       makevectors(this.v_angle);
+       shotorg = this.origin + this.view_ofs;
+       shotdir = v_forward;
+
        // decide whether to fire this time
-       if (v * shotdir > cos(maxfiredeviation * DEG2RAD))
+       // v is the calculated trajectory, shotdir is bot view direction
+       // NOTE: checking if (v * shotdir > cos(maxfiredeviation * DEG2RAD)) would be cheaper
+       // but it gets evaluated to true even if v and shotdir have nearly opposite direction
+       vector deviation = vectoangles(v) - vectoangles(shotdir);
+       while (deviation.x < -180) deviation.x += 360; while (deviation.x > 180) deviation.x -= 360;
+       while (deviation.y < -180) deviation.y += 360; while (deviation.y > 180) deviation.y -= 360;
+       if (fabs(deviation.x) < maxfiredeviation && fabs(deviation.y) < maxfiredeviation)
        {
                traceline(shotorg, shotorg + shotdir * 1000, false, NULL);
                if (vdist(trace_endpos - shotorg, <, 500 + 500 * bound(0, skill + this.bot_aggresskill, 10))
@@ -350,8 +310,6 @@ void bot_aimdir(entity this, vector v, float maxfiredeviation)
                        this.bot_firetimer = time + bound(0.1, 0.5 - (skill + this.bot_aggresskill) * 0.05, 0.5);
                }
        }
-       //dprint(ftos(maxfiredeviation),"\n");
-       //dprint(" diff:", vtos(diffang), "\n");
 }
 
 vector bot_shotlead(vector targorigin, vector targvelocity, float shotspeed, float shotdelay)
@@ -360,9 +318,9 @@ vector bot_shotlead(vector targorigin, vector targvelocity, float shotspeed, flo
        return targorigin + targvelocity * (shotdelay + vlen(targorigin - shotorg) / shotspeed);
 }
 
-bool bot_aim(entity this, .entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, bool applygravity)
+bool bot_aim(entity this, .entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, bool applygravity, bool shot_accurate)
 {
-       float r, hf, distanceratio;
+       float hf, distanceratio;
        vector v;
        hf = this.dphitcontentsmask;
        this.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
@@ -383,31 +341,40 @@ bool bot_aim(entity this, .entity weaponentity, float shotspeed, float shotspeed
        makevectors(this.v_angle);
        shotorg = this.origin + this.view_ofs;
        shotdir = v_forward;
-       v = bot_shotlead(this.bot_aimtargorigin, this.bot_aimtargvelocity, shotspeed, this.bot_aimlatency);
-       distanceratio = sqrt(bound(0,skill,10000))*0.3*(vlen(v-shotorg)-100)/autocvar_bot_ai_aimskill_firetolerance_distdegrees;
-       distanceratio = bound(0,distanceratio,1);
-       r =  (autocvar_bot_ai_aimskill_firetolerance_maxdegrees-autocvar_bot_ai_aimskill_firetolerance_mindegrees)
-               * (1-distanceratio) + autocvar_bot_ai_aimskill_firetolerance_mindegrees;
-       if (applygravity && this.bot_aimtarg)
+       vector enemy_org = (this.enemy.absmin + this.enemy.absmax) * 0.5;
+       v = bot_shotlead(enemy_org, this.enemy.velocity, shotspeed, this.bot_aimlatency);
+
+       // this formula was created starting from empiric values of distance and max hit angle
+       // with a player as target (32 qu wide) from the center of it right in front of the bot
+       //  distance: 32  50    75   100  150  200  300  400  500
+       //   max ang: 44  24  15.1  10.5  6.5  4.9  3.1  2.3  1.8
+       float dist = max(10, vlen(v - shotorg));
+       float maxfiredeviation = 1000 / (dist - 9) - 0.35;
+
+       float f = (shot_accurate) ? 1 : 1.6;
+       f += bound(0, (10 - (skill + this.bot_aimskill)) * 0.3, 3);
+       maxfiredeviation = min(90, maxfiredeviation * f);
+
+       if (applygravity && this.enemy)
        {
-               if (!findtrajectorywithleading(shotorg, '0 0 0', '0 0 0', this.bot_aimtarg, shotspeed, shotspeedupward, maxshottime, 0, this))
+               if (!findtrajectorywithleading(shotorg, '0 0 0', '0 0 0', this.enemy, shotspeed, shotspeedupward, maxshottime, 0, this))
                {
                        this.dphitcontentsmask = hf;
                        return false;
                }
 
-               bot_aimdir(this, findtrajectory_velocity - shotspeedupward * '0 0 1', r);
+               bot_aimdir(this, findtrajectory_velocity - shotspeedupward * '0 0 1', maxfiredeviation);
        }
        else
        {
-               bot_aimdir(this, v - shotorg, r);
-               //dprint("AIM: ");dprint(vtos(this.bot_aimtargorigin));dprint(" + ");dprint(vtos(this.bot_aimtargvelocity));dprint(" * ");dprint(ftos(this.bot_aimlatency + vlen(this.bot_aimtargorigin - shotorg) / shotspeed));dprint(" = ");dprint(vtos(v));dprint(" : aimdir = ");dprint(vtos(normalize(v - shotorg)));dprint(" : ");dprint(vtos(shotdir));dprint("\n");
+               bot_aimdir(this, v - shotorg, maxfiredeviation);
+               //dprint("AIM: ");dprint(vtos(enemy_org));dprint(" + ");dprint(vtos(this.enemy.velocity));dprint(" * ");dprint(ftos(this.bot_aimlatency + vlen(this.enemy.origin - shotorg) / shotspeed));dprint(" = ");dprint(vtos(v));dprint(" : aimdir = ");dprint(vtos(normalize(v - shotorg)));dprint(" : ");dprint(vtos(shotdir));dprint("\n");
                //traceline(shotorg, shotorg + shotdir * 10000, false, this);
                //if (trace_ent.takedamage)
                //if (trace_fraction < 1)
                //if (!bot_shouldattack(this, trace_ent))
                //      return false;
-               traceline(shotorg, this.bot_aimtargorigin, false, this);
+               traceline(shotorg, enemy_org, false, this);
                if (trace_fraction < 1)
                if (trace_ent != this.enemy)
                if (!bot_shouldattack(this, trace_ent))
index 2a3145176258a6aa523efd3c95a252c8251ba897..3d1e8894ab2685c714b842cd9389156c7fa7d653 100644 (file)
@@ -7,58 +7,9 @@ entity tracetossent;
 entity tracetossfaketarget;
 vector findtrajectory_velocity;
 
-
-
 vector shotorg;
 vector shotdir;
 
-// lag simulation
-// upto 5 queued messages
-.float lag1_time;
-.float lag1_float1;
-.float lag1_float2;
-.entity lag1_entity1;
-.vector lag1_vec1;
-.vector lag1_vec2;
-.vector lag1_vec3;
-.vector lag1_vec4;
-
-.float lag2_time;
-.float lag2_float1;
-.float lag2_float2;
-.entity lag2_entity1;
-.vector lag2_vec1;
-.vector lag2_vec2;
-.vector lag2_vec3;
-.vector lag2_vec4;
-
-.float lag3_time;
-.float lag3_float1;
-.float lag3_float2;
-.entity lag3_entity1;
-.vector lag3_vec1;
-.vector lag3_vec2;
-.vector lag3_vec3;
-.vector lag3_vec4;
-
-.float lag4_time;
-.float lag4_float1;
-.float lag4_float2;
-.entity lag4_entity1;
-.vector lag4_vec1;
-.vector lag4_vec2;
-.vector lag4_vec3;
-.vector lag4_vec4;
-
-.float lag5_time;
-.float lag5_float1;
-.float lag5_float2;
-.entity lag5_entity1;
-.vector lag5_vec1;
-.vector lag5_vec2;
-.vector lag5_vec3;
-.vector lag5_vec4;
-
 .bool bot_aimdir_executed;
 .float bot_badaimtime;
 .float bot_aimthinktime;
@@ -75,27 +26,15 @@ vector shotdir;
 .vector bot_5th_order_aimfilter;
 .vector bot_olddesiredang;
 
-//.vector bot_aimorigin;
-//.vector bot_aimvelocity;
-.vector bot_aimtargorigin;
-.vector bot_aimtargvelocity;
-
-.entity bot_aimtarg;
 
 /*
  * Functions
  */
 
-float lag_additem(entity this, float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4);
-void lag_update(entity this);
-void bot_lagfunc(entity this, float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4);
-
 float bot_shouldattack(entity this, entity targ);
 void bot_aimdir(entity this, vector v, float maxfiredeviation);
-bool bot_aim(entity this, .entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, bool applygravity);
+bool bot_aim(entity this, .entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, bool applygravity, bool shot_accurate);
 void bot_aim_reset(entity this);
 float findtrajectorywithleading(vector org, vector m1, vector m2, entity targ, float shotspeed, float shotspeedupward, float maxtime, float shotdelay, entity ignore);
 
 vector bot_shotlead(vector targorigin, vector targvelocity, float shotspeed, float shotdelay);
-
-.void(entity this, float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4) lag_func;
index 01a66b2c12587764d8bd1f94f1050b46db0ba9f7..b81507890f302da772d225f28f44c12a73ac85ae 100644 (file)
@@ -64,7 +64,7 @@ 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 * (0.5 ** this.bot_aiskill) * min(14 / (skill + 14), 1));
+       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))
        {
@@ -442,7 +442,6 @@ void bot_clientconnect(entity this)
        if (!IS_BOT_CLIENT(this)) return;
        this.bot_preferredcolors = this.clientcolors;
        this.bot_nextthink = time - random();
-       this.lag_func = bot_lagfunc;
        this.isbot = true;
        this.createdtime = this.bot_nextthink;
 
index 74f7e0fa944e67d3ef270b0709b2db7548993463..95744035abebca2514da2256254f9edddb5d1719 100644 (file)
@@ -65,7 +65,6 @@ entity bot_list;
 .float bot_pickup;
 .float bot_pickupbasevalue;
 .bool bot_pickup_respawning;
-.float bot_canfire;
 .float bot_strategytime;
 .float bot_jump_time;
 
index 4b3888986afcc1ac72bbda89be1a274c46f3593b..bd408da4218f283f381bdbecee835e050e2d742d 100644 (file)
@@ -2,9 +2,6 @@
 
 float autocvar_bot_ai_aimskill_blendrate;
 int autocvar_bot_ai_aimskill_firetolerance;
-float autocvar_bot_ai_aimskill_firetolerance_distdegrees;
-float autocvar_bot_ai_aimskill_firetolerance_maxdegrees;
-float autocvar_bot_ai_aimskill_firetolerance_mindegrees;
 float autocvar_bot_ai_aimskill_fixedrate;
 float autocvar_bot_ai_aimskill_mouse;
 float autocvar_bot_ai_aimskill_offset;
index 689f0c179ffcfc3ea65155ec04153bddb6ce1716..f9a56195b2e3e1abf6b70b792ebf0719ee48958d 100644 (file)
@@ -115,7 +115,6 @@ void havocbot_ai(entity this)
                if (this.goalcurrent)
                        navigation_clearroute(this);
                this.enemy = NULL;
-               this.bot_aimtarg = NULL;
                return;
        }
 
@@ -132,9 +131,8 @@ void havocbot_ai(entity this)
                }
        }
        havocbot_aim(this);
-       lag_update(this);
 
-       if (this.bot_aimtarg)
+       if (this.enemy)
        {
                this.aistatus |= AI_STATUS_ATTACKING;
                this.aistatus &= ~AI_STATUS_ROAMING;
@@ -162,8 +160,8 @@ void havocbot_ai(entity this)
                }
                else
                {
-                       if(IS_PLAYER(this.bot_aimtarg))
-                               bot_aimdir(this, this.bot_aimtarg.origin + this.bot_aimtarg.view_ofs - this.origin - this.view_ofs, 0);
+                       if(IS_PLAYER(this.enemy))
+                               bot_aimdir(this, this.enemy.origin + this.enemy.view_ofs - this.origin - this.view_ofs, 0);
                }
        }
        else if (this.goalcurrent)
@@ -1275,26 +1273,6 @@ void havocbot_movetogoal(entity this)
        }
 }
 
-entity havocbot_gettarget(entity this, bool secondary)
-{
-       entity best = NULL;
-       vector eye = CENTER_OR_VIEWOFS(this);
-       IL_EACH(g_bot_targets, boolean((secondary) ? it.classname == "misc_breakablemodel" : it.classname != "misc_breakablemodel"),
-       {
-               vector v = CENTER_OR_VIEWOFS(it);
-               if(vdist(v - eye, <, autocvar_bot_ai_enemydetectionradius))
-               if(!best || vlen2(CENTER_OR_VIEWOFS(best) - eye) > vlen2(v - eye))
-               if(bot_shouldattack(this, it))
-               {
-                       traceline(eye, v, true, this);
-                       if (trace_ent == it || trace_fraction >= 1)
-                               best = it;
-               }
-       });
-
-       return best;
-}
-
 void havocbot_chooseenemy(entity this)
 {
        if (autocvar_bot_nofire || IS_INDEPENDENT_PLAYER(this))
@@ -1348,7 +1326,7 @@ void havocbot_chooseenemy(entity this)
        while(true)
        {
                scan_secondary_targets = false;
-LABEL(scan_targets)
+               LABEL(scan_targets)
                IL_EACH(g_bot_targets, it.bot_attack,
                {
                        if(!scan_secondary_targets)
@@ -1528,9 +1506,13 @@ void havocbot_chooseweapon(entity this, .entity weaponentity)
 
 void havocbot_aim(entity this)
 {
-       if (time < this.nextaim)
+       // if aim rate is a multiple of think rate due to precision errors the sum of multiple think times
+       // can be slightly greater than aim time and cause a jump of a whole think time (bot_netxtthink)
+       // in that case this small tolerance time makes so that aim time snaps to think time
+       if (time < this.nextaim - 0.01)
                return;
        this.nextaim = time + 0.1;
+
        vector myvel = this.velocity;
        if (!this.waterlevel)
                myvel.z = 0;
@@ -1540,10 +1522,7 @@ void havocbot_aim(entity this)
                vector enemyvel = this.enemy.velocity;
                if (!this.enemy.waterlevel)
                        enemyvel.z = 0;
-               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 + 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)
index a27e00c42a24a386f372758bd2ca5ea9e23727c5..f820848ade919f127086d09d607a25f5cab6a27b 100644 (file)
@@ -1,7 +1,7 @@
 #include "bot_null.qh"
 
 #if 0
-bool bot_aim(entity this, .entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, float applygravity) { return false; }
+bool bot_aim(entity this, .entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, float applygravity, bool shot_accurate) { return false; }
 void bot_clientconnect(entity this) { }
 void bot_clientdisconnect(entity this) { }
 void bot_cmdhelp(string scmd) { }
index 3cbc07c57d3dddb989bd6b954ad58df86e6186f0..d93d00bf5f58d368a1f338c970d1657368ec46b9 100644 (file)
@@ -931,11 +931,9 @@ float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector in
                        float dist = max(0, vlen(diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS));
                        if (dist <= rad)
                        {
-                               float power = 1;
-                               if (rad > 0)
-                                       power -= (dist / rad);
-                               // at this point power can't be < 0 or > 1
-                               float finaldmg = coredamage * power + edgedamage * (1 - power);
+                               float f = (rad > 0) ? 1 - (dist / rad) : 1;
+                               // at this point f can't be < 0 or > 1
+                               float finaldmg = coredamage * f + edgedamage * (1 - f);
                                if (finaldmg > 0)
                                {
                                        float a;
index c39c94322052db0ac7b5d381023e521b6541ece0..fed4d794a0a266e5cc591342799b539cb125f492 100644 (file)
@@ -138,14 +138,11 @@ set bot_ai_dangerdetectioninterval 0.25 "How often scan for waypoints with dange
 set bot_ai_dangerdetectionupdates 64 "How many waypoints will be considered for danger detection"
 set bot_ai_aimskill_blendrate 2 "How much correction will be applied to the aiming angle"
 set bot_ai_aimskill_fixedrate 15 "Distance based scale from which correction will be applied to the aiming angle"
-set bot_ai_aimskill_firetolerance 0 "enable fire tolerance"
-set bot_ai_aimskill_firetolerance_distdegrees 100 "Rate at which the aiming angle is updated, scales by skill"
-set bot_ai_aimskill_firetolerance_mindegrees 2 "Minimum angle tolerance. Used on large distances"
-set bot_ai_aimskill_firetolerance_maxdegrees 60 "Maximum firing angle. Used on close range"
+set bot_ai_aimskill_firetolerance 1 "enable fire tolerance"
 set bot_ai_aimskill_mouse 1 "How much of the aiming filters are applied"
 set bot_ai_keyboard_distance 250 "Keyboard emulation is disabled after this distance to the goal"
 set bot_ai_keyboard_threshold 0.57
-set bot_ai_aimskill_offset 0.3 "Amount of error induced to the bots aim"
+set bot_ai_aimskill_offset 1.8 "Amount of error induced to the bots aim"
 set bot_ai_aimskill_think 1 "Aiming velocity. Use values below 1 for slower aiming"
 set bot_ai_custom_weapon_priority_distances "300 850" "Define close and far distances in any order. Based on the distance to the enemy bots will choose different weapons"
 set bot_ai_custom_weapon_priority_far   "vaporizer oknex vortex rifle electro devastator mortar hagar hlac crylink blaster okmachinegun machinegun fireball seeker okshotgun shotgun shockwave tuba minelayer" "Desired weapons for far distances ordered by priority"