]> git.rm.cloudns.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Bot AI: improve a lot crylink skills
authorterencehill <piuntn@gmail.com>
Wed, 6 Dec 2023 23:18:59 +0000 (00:18 +0100)
committerterencehill <piuntn@gmail.com>
Wed, 6 Dec 2023 23:18:59 +0000 (00:18 +0100)
qcsrc/common/weapons/weapon/crylink.qc
qcsrc/common/weapons/weapon/crylink.qh
xonotic-server.cfg

index 6a69a7c570936f87b24e92f5666fceeb11275c74..22d2cc16539d0e5b2be23dc9ea993d2b290be197 100644 (file)
@@ -209,6 +209,8 @@ void W_Crylink_LinkJoinEffect_Think(entity this)
                        }
                }
        }
+       if (this.crylink_owner)
+               this.crylink_owner.crylink_released_for_bot_time = time;
        delete(this);
 }
 
@@ -262,12 +264,22 @@ void W_Crylink_Touch(entity this, entity toucher)
                if(this == this.crylink_owner.(weaponentity).crylink_lastgroup)
                        this.crylink_owner.(weaponentity).crylink_lastgroup = NULL;
                W_Crylink_LinkExplode(this.queuenext, this, toucher);
+
+               // crylink_released_for_bot_time is set whenever a projectile explodes because it's
+               // the only way to tell bots to release the fire button
+               // NOTE: setting this field to human players too doesn't hurt (it's never checked) and
+               // is cheaper than checking IS_BOT_CLIENT(e.crylink_owner)
+               if (this.crylink_owner)
+                       this.crylink_owner.crylink_released_for_bot_time = time;
+
                this.classname = "spike_oktoremove";
                delete(this);
                return;
        }
        else if(finalhit)
        {
+               if (this.crylink_owner)
+                       this.crylink_owner.crylink_released_for_bot_time = time;
                // just unlink
                delete(this);
                return;
@@ -519,12 +531,74 @@ void W_Crylink_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
        }
 }
 
+#define bot_aim_CRYLINK_PRI() \
+       bot_aim(actor, weaponentity, WEP_CVAR_PRI(crylink, speed), 0, WEP_CVAR_PRI(crylink, middle_lifetime), false, false)
+#define bot_aim_CRYLINK_SEC() \
+       bot_aim(actor, weaponentity, WEP_CVAR_SEC(crylink, speed), 0, WEP_CVAR_SEC(crylink, middle_lifetime), false, true)
+
 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, true);
-    else
-        PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, WEP_CVAR_SEC(crylink, speed), 0, WEP_CVAR_SEC(crylink, middle_lifetime), false, true);
+       if(WEP_CVAR_PRI(crylink, joinexplode) && WEP_CVAR_PRI(crylink, shots) > 1)
+       {
+               // less skilled bots prefer primary attack even with greater distances
+               float atck_radius = WEP_CVAR_PRI(crylink, speed) * map_bound_ranges(skill, 0, 10, 0.9, 0.6);
+               if (vdist(actor.origin - actor.enemy.origin, >, atck_radius))
+               {
+                       PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim_CRYLINK_SEC();
+                       return;
+               }
+
+               PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim_CRYLINK_PRI();
+
+               entity first_proj = NULL;
+               IL_EACH(g_projectiles, true,
+               {
+                       if (it.crylink_owner == actor && actor.(weaponentity).crylink_waitrelease == 1)
+                       {
+                               first_proj = it; // this is always the middle one
+                               break;
+                       }
+               });
+               if (!first_proj)
+               {
+                       // keep the fire button released for a short while otherwise the weapon may not
+                       // detect the release event at all and doesn't shoot again
+                       if (time < actor.crylink_released_for_bot_time + 0.05)
+                               PHYS_INPUT_BUTTON_ATCK(actor) = false;
+                       return;
+               }
+
+               float fired_time = first_proj.teleport_time - WEP_CVAR_PRI(crylink, joindelay);
+               if (time > fired_time + map_bound_ranges(skill, 0, 10, 1.2, 0.8)) // release after this time anyway
+               {
+                       PHYS_INPUT_BUTTON_ATCK(actor) = false;
+                       actor.crylink_released_for_bot_time = time;
+                       return;
+               }
+
+               float pred_time = max(0.01, 200 / WEP_CVAR_PRI(crylink, speed));
+               IL_EACH(g_bot_targets, it.bot_attack && it != actor,
+               {
+                       vector target_pos = it.origin + (it.maxs - it.mins) * 0.5;
+                       float target_radius = map_bound_ranges(skill, 0, 10, 10, 50);
+                       if (vdist(target_pos - (first_proj.origin + first_proj.velocity * pred_time), <=, target_radius))
+                       {
+                               PHYS_INPUT_BUTTON_ATCK(actor) = false;
+                               actor.crylink_released_for_bot_time = time;
+                               return;
+                       }
+               });
+
+               PHYS_INPUT_BUTTON_ATCK(actor) = true; // keep it pressed
+               return;
+       }
+
+       // if join explode is not available bots use a shorter primary attack radius
+       float atck_radius = WEP_CVAR_PRI(crylink, speed) * map_bound_ranges(skill, 0, 10, 0.7, 0.4);
+       if (vdist(actor.origin - actor.enemy.origin, >, atck_radius))
+               PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim_CRYLINK_PRI();
+       else
+               PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim_CRYLINK_SEC();
 }
 METHOD(Crylink, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
 {
index 2329c364e2998f16d425856292da6c6431b181e3..900bfdf6cbc2a0cd61fdd55c20c06e59eeefdc24 100644 (file)
@@ -5,7 +5,7 @@ CLASS(Crylink, Weapon)
 /* ammotype  */ ATTRIB(Crylink, ammo_type, Resource, RES_CELLS);
 /* impulse   */ ATTRIB(Crylink, impulse, int, 6);
 /* flags     */ ATTRIB(Crylink, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH | WEP_FLAG_CANCLIMB);
-/* rating    */ ATTRIB(Crylink, bot_pickupbasevalue, float, 6000);
+/* rating    */ ATTRIB(Crylink, bot_pickupbasevalue, float, 7000);
 /* color     */ ATTRIB(Crylink, wpcolor, vector, '1 0.5 1');
 /* modelname */ ATTRIB(Crylink, mdl, string, "crylink");
 #ifdef GAMEQC
@@ -70,6 +70,7 @@ SPAWNFUNC_WEAPON(weapon_crylink, WEP_CRYLINK)
 .entity crylink_lastgroup;
 
 .entity crylink_owner; // we can't use realowner, as that's subject to change
+.float crylink_released_for_bot_time;
 
 .entity queuenext;
 .entity queueprev;
index a72143db3849a387a10fad28f8440f6816600081..9c6e5503cfee56ba7d46478f13292f8a07a9154f 100644 (file)
@@ -148,8 +148,8 @@ 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"
-set bot_ai_custom_weapon_priority_mid   "vaporizer devastator oknex vortex fireball seeker mortar electro okmachinegun machinegun arc crylink hlac hagar okshotgun shotgun shockwave blaster rifle tuba minelayer" "Desired weapons for middle distances ordered by priority"
-set bot_ai_custom_weapon_priority_close "vaporizer oknex vortex okshotgun shotgun shockwave okmachinegun machinegun arc hlac tuba seeker hagar crylink mortar electro devastator blaster fireball rifle minelayer" "Desired weapons for close distances ordered by priority"
+set bot_ai_custom_weapon_priority_mid   "vaporizer devastator oknex vortex fireball seeker mortar crylink electro okmachinegun machinegun arc hlac hagar okshotgun shotgun shockwave blaster rifle tuba minelayer" "Desired weapons for middle distances ordered by priority"
+set bot_ai_custom_weapon_priority_close "vaporizer oknex vortex okshotgun shotgun shockwave okmachinegun machinegun crylink arc hlac tuba seeker hagar mortar electro devastator blaster fireball rifle minelayer" "Desired weapons for close distances ordered by priority"
 set bot_ai_weapon_combo 1 "Enable bots to do weapon combos"
 set bot_ai_weapon_combo_threshold 0.4 "Try to make a combo N seconds after the last attack"
 set bot_ai_friends_aware_pickup_radius "500" "Bots will not pickup items if a team mate is this distance near the item"