- 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
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;
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))
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))
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
{
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))
{
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);
}
}
WEP_CVAR(arc, beam_botaimspeed),
0,
WEP_CVAR(arc, beam_botaimlifetime),
- false
+ false, true
);
}
else
1000000,
0,
0.001,
- false
+ false, true
);
}
}
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))
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))
{
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;
}
}
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)
{
}
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;
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;
}
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;
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))
{
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))
{
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))
{
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
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;
}
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;
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))
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;
}
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;
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))
{
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))
{
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))
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))
{
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))
{
.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);
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)
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)
{
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;
//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;
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))
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)
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;
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))
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;
.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;
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))
{
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;
.float bot_pickup;
.float bot_pickupbasevalue;
.bool bot_pickup_respawning;
-.float bot_canfire;
.float bot_strategytime;
.float bot_jump_time;
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;
if (this.goalcurrent)
navigation_clearroute(this);
this.enemy = NULL;
- this.bot_aimtarg = NULL;
return;
}
}
}
havocbot_aim(this);
- lag_update(this);
- if (this.bot_aimtarg)
+ if (this.enemy)
{
this.aistatus |= AI_STATUS_ATTACKING;
this.aistatus &= ~AI_STATUS_ROAMING;
}
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)
}
}
-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))
while(true)
{
scan_secondary_targets = false;
-LABEL(scan_targets)
+ LABEL(scan_targets)
IL_EACH(g_bot_targets, it.bot_attack,
{
if(!scan_secondary_targets)
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;
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)
#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) { }
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;
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"