X-Git-Url: https://git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fcommon%2Fweapons%2Fweapon%2Fdevastator.qc;h=40a2e35d459a38b335589ee7925af388d89bff1f;hb=bf1d64ebc5c2867b079a0bfa09f448f076d6be01;hp=891c44583d65d1b491c372234c2e269979b77b19;hpb=ac7deb97b1a0e73ceea4684be73e72912fb3f1aa;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/common/weapons/weapon/devastator.qc b/qcsrc/common/weapons/weapon/devastator.qc index 891c44583..40a2e35d4 100644 --- a/qcsrc/common/weapons/weapon/devastator.qc +++ b/qcsrc/common/weapons/weapon/devastator.qc @@ -307,7 +307,7 @@ void W_Devastator_Attack(Weapon thiswep, entity actor, .entity weaponentity, int { W_DecreaseAmmo(thiswep, actor, WEP_CVAR(devastator, ammo), weaponentity); - W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', false, 5, SND_ROCKET_FIRE, CH_WEAPON_A, WEP_CVAR(devastator, damage), thiswep.m_id); + W_SetupShot_ProjectileSize(actor, weaponentity, ROCKET_MINS, ROCKET_MAXS, false, 5, SND_ROCKET_FIRE, CH_WEAPON_A, WEP_CVAR(devastator, damage), thiswep.m_id); W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir); entity missile = WarpZone_RefSys_SpawnSameRefSys(actor); @@ -333,7 +333,7 @@ void W_Devastator_Attack(Weapon thiswep, entity actor, .entity weaponentity, int set_movetype(missile, MOVETYPE_FLY); PROJECTILE_MAKETRIGGER(missile); missile.projectiledeathtype = thiswep.m_id; - setsize(missile, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot + setsize(missile, ROCKET_MINS, ROCKET_MAXS); // give it some size so it can be shot setorigin(missile, w_shotorg - v_forward * 3); // move it back so it hits the wall at the right point W_SetupProjVelocity_Basic(missile, WEP_CVAR(devastator, speedstart), 0); @@ -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(time > STAT(INVINCIBLE_FINISHED, actor) && time > actor.spawnshieldtime) - 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; } } @@ -548,8 +586,7 @@ METHOD(Devastator, wr_killmessage, Notification(entity thiswep)) METHOD(Devastator, wr_impacteffect, void(entity thiswep, entity actor)) { - vector org2; - org2 = w_org + w_backoff * 12; + vector org2 = w_org + w_backoff * 2; pointparticles(EFFECT_ROCKET_EXPLODE, org2, '0 0 0', 1); if(!w_issilent) sound(actor, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTN_NORM);