diff --git a/core/assets/bundles/bundle_hu.properties b/core/assets/bundles/bundle_hu.properties index 9c20815937..d8f446b6f4 100644 --- a/core/assets/bundles/bundle_hu.properties +++ b/core/assets/bundles/bundle_hu.properties @@ -3413,7 +3413,7 @@ name.token.read = olvasás name.token.write = írás name.token.from = innen: name.token.char = karakter -name.token.linknum = hivatkozás\# +name.token.linknum = hivatkozás# name.token.draw = rajzolás name.token.clear = törlés name.token.apply = alkalmaz @@ -3472,7 +3472,7 @@ name.token.volume = hangerő name.token.pitch = hangmagasság name.token.pan = térhatás name.token.limit = korlát -name.token.of-id- = azonosító alapján\# +name.token.of-id- = azonosító alapján# name.token.replace = csere? name.token.for = ezért: name.token.success = siker diff --git a/core/src/mindustry/content/UnitTypes.java b/core/src/mindustry/content/UnitTypes.java index 2e98e68367..ac15c2bb6a 100644 --- a/core/src/mindustry/content/UnitTypes.java +++ b/core/src/mindustry/content/UnitTypes.java @@ -355,19 +355,25 @@ public class UnitTypes{ nova = new UnitType("nova"){{ canBoost = true; - boostMultiplier = 1.5f; + boostMultiplier = 2f; speed = 0.55f; hitSize = 8f; - health = 120f; + health = 200f; buildSpeed = 0.3f; armor = 1f; - abilities.add(new RepairFieldAbility(10f, 60f * 4, 60f)); + abilities.add(new RepairFieldAbility(20f, 60f * 2, 100f){{ + sameTypeHealMult = 0.15f; + maxTargets = 6; + + smartHeal = true; + smartDowntime = 60 * 4f; + }}); weapons.add(new Weapon("heal-weapon"){{ top = false; shootY = 2f; - reload = 24f; + reload = 30f; x = 4.5f; alternate = false; ejectEffect = Fx.none; @@ -377,6 +383,9 @@ public class UnitTypes{ bullet = new LaserBoltBulletType(5.2f, 13){{ lifetime = 30f; healPercent = 5f; + pierce = true; + pierceBuilding = true; + pierceCap = 2; collidesTeam = true; backColor = Pal.heal; frontColor = Color.white; @@ -1370,7 +1379,11 @@ public class UnitTypes{ mineSpeed = 3.5f; wreckSoundVolume = 0.9f; - abilities.add(new RepairFieldAbility(5f, 60f * 8, 50f)); + abilities.add(new RepairFieldAbility(){{ + amount = 5f; + reload = 60f * 8f; + range = 50f; + }}); weapons.add(new Weapon("poly-weapon"){{ top = false; diff --git a/core/src/mindustry/entities/abilities/RepairFieldAbility.java b/core/src/mindustry/entities/abilities/RepairFieldAbility.java index 9c0b7131e4..9ff08d5f40 100644 --- a/core/src/mindustry/entities/abilities/RepairFieldAbility.java +++ b/core/src/mindustry/entities/abilities/RepairFieldAbility.java @@ -4,10 +4,12 @@ import arc.*; import arc.audio.*; import arc.math.*; import arc.scene.ui.layout.*; +import arc.struct.*; import arc.util.*; import mindustry.content.*; import mindustry.entities.*; import mindustry.gen.*; +import java.util.*; import static mindustry.Vars.*; @@ -20,11 +22,29 @@ public class RepairFieldAbility extends Ability{ public boolean parentizeEffects = false; /** Multiplies healing to units of the same type by this amount. */ public float sameTypeHealMult = 1f; + /** Maximum number of units healed. */ + public int maxTargets = -1; - protected float timer; - protected boolean wasHealed = false; + /** If true, this ability will consider missing hp, number of targets, cooldowns...etc. */ + public boolean smartHeal = false; + /** If a damaged unit health % is lower than this heal it as soon as possible. Values close to 1f will likely waste healing potential. */ + public float smartHealPercent = 0f; + /** A multiplier on potential healing efficiency. High values -> efficient aoe healing. Low values -> efficient single target healing. */ + public float smartHealStrength = 1f; + /** If all damaged units havent been hurt for at least this amount of time, force healing them, regardless of thresholds. */ + public float smartDowntime = 60f * 8f; + /** How often to check for healing when at least 1 damaged unit is found. */ + public float smartInterval = 20f; + /** Random initial reload multiplier to check for damaged units. Useful for desyncing healer timers once. */ + public float randDesync = -1f; - RepairFieldAbility(){} + //a high capacity is needed to account for t1 spam in custom gamemodes, but set too high it becomes expensive + protected static Seq targets = new Seq<>(50); + protected float healTimer, downTimer; + protected float healthMissing, sumMaxHealth, sumTypeMult, healthChange; + protected boolean hasHealed, healNow; + + public RepairFieldAbility(){} public RepairFieldAbility(float amount, float reload, float range){ this.amount = amount; @@ -57,30 +77,80 @@ public class RepairFieldAbility extends Ability{ t.row(); t.add(abilityStat("sametypehealmultiplier", (sameTypeHealMult < 1f ? "[negstat]" : "") + Strings.autoFixed(sameTypeHealMult * 100f, 2))); } + if(maxTargets > 0){ + t.row(); + t.add(abilityStat("maxtargets", maxTargets)); + } } @Override public void update(Unit unit){ - timer += Time.delta; + healTimer += Time.delta; + downTimer = smartHeal && healthChange >= healthMissing && healthMissing > 0f ? downTimer + Time.delta : 0f; - if(timer >= reload){ - wasHealed = false; + if(healTimer >= reload){ + targets.clear(); + hasHealed = healNow = false; + healthChange = healthMissing; + healthMissing = sumMaxHealth = sumTypeMult = 0f; + + boolean limitTargets = maxTargets >= 0; + float healPercentMult = healPercent / 100f; Units.nearby(unit.team, unit.x, unit.y, range, other -> { + //check for 2 more targets just in case + if(limitTargets && targets.size >= maxTargets + 2) return; + if(other.damaged()){ - healEffect.at(other, parentizeEffects); - wasHealed = true; + targets.add(other); + if(smartHeal){ + float maxHealth = other.maxHealth(); + healthMissing += maxHealth - other.health(); + sumMaxHealth += maxHealth; + sumTypeMult += unit.type == other.type ? sameTypeHealMult : 1f; + if(other.healthf() < smartHealPercent) healNow = true; + } } - float healMult = unit.type == other.type ? sameTypeHealMult : 1f; - other.heal((amount + healPercent / 100f * other.maxHealth()) * healMult); }); + int targetCount = targets.size; - if(wasHealed){ - activeEffect.at(unit, range); - sound.at(unit, 1f + Mathf.range(0.1f), soundVolume); + //mixed approach, care both about groups and single low hp units + float ratio = amount + healPercentMult * sumMaxHealth * (sumTypeMult / targetCount); + float requiredHeals = (healthMissing * 0.7f + healthMissing / (limitTargets ? maxTargets : targetCount) * 0.3f) / smartHealStrength / ratio; + + if(requiredHeals >= 1f || !smartHeal || healNow || downTimer >= smartDowntime){ + + //sort closest if number of targets is limited + if(limitTargets){ + boolean isSameType = sameTypeHealMult < 1f; + targets.sort(u -> u.dst2(unit.x, unit.y) + (isSameType && u.type() == unit.type ? 6400f : 0f)); + } + + int len = limitTargets ? Math.min(targetCount, maxTargets) : targetCount; + for(int i = 0; i < len; i++){ + Unit other = targets.get(i); + if(other.damaged()){ + float maxHealth = other.maxHealth(); + float healMult = unit.type == other.type ? sameTypeHealMult : 1f; + other.heal((amount + healPercentMult * maxHealth) * healMult); + healEffect.at(other, parentizeEffects); + hasHealed = true; + } + } + if(hasHealed){ + healTimer = 0f; + activeEffect.at(unit, range); + sound.at(unit, 1f + Mathf.range(0.1f), soundVolume); + } + + //increase how often this checks if there are damaged units but still below the healing threshold + }else if(smartHeal && targetCount > 0){ + healTimer = reload >= (2f * smartInterval) ? reload - smartInterval : smartInterval; + }else if(randDesync > 0){ + healTimer = Mathf.random(randDesync) * reload; + }else{ + healTimer = 0; } - - timer = 0f; } } -} +} \ No newline at end of file