]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/common/weapons/weapon/devastator.qc
Merge branch 'terencehill/bot_ai' into 'master'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / weapons / weapon / devastator.qc
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;
     }
 }