From a9f8d7f1b250aee6eb18e8b1c433827dd33a2e6a Mon Sep 17 00:00:00 2001 From: terencehill Date: Thu, 7 Dec 2023 00:18:59 +0100 Subject: [PATCH] Bot AI: improve a lot crylink skills --- qcsrc/common/weapons/weapon/crylink.qc | 82 ++++++++++++++++++++++++-- qcsrc/common/weapons/weapon/crylink.qh | 3 +- xonotic-server.cfg | 4 +- 3 files changed, 82 insertions(+), 7 deletions(-) diff --git a/qcsrc/common/weapons/weapon/crylink.qc b/qcsrc/common/weapons/weapon/crylink.qc index 6a69a7c57..22d2cc165 100644 --- a/qcsrc/common/weapons/weapon/crylink.qc +++ b/qcsrc/common/weapons/weapon/crylink.qc @@ -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)) { diff --git a/qcsrc/common/weapons/weapon/crylink.qh b/qcsrc/common/weapons/weapon/crylink.qh index 2329c364e..900bfdf6c 100644 --- a/qcsrc/common/weapons/weapon/crylink.qh +++ b/qcsrc/common/weapons/weapon/crylink.qh @@ -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; diff --git a/xonotic-server.cfg b/xonotic-server.cfg index a72143db3..9c6e5503c 100644 --- a/xonotic-server.cfg +++ b/xonotic-server.cfg @@ -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" -- 2.39.2