Skip to content

Commit

Permalink
Merge pull request #334 from vincent4vx/feature-fight-erosion
Browse files Browse the repository at this point in the history
feat(fight): Handle life erosion
  • Loading branch information
vincent4vx authored Feb 21, 2024
2 parents 7847129 + 85415be commit 0d1850d
Show file tree
Hide file tree
Showing 83 changed files with 1,043 additions and 227 deletions.
5 changes: 5 additions & 0 deletions config.ini.dist
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,11 @@ game.dbname = araknemu
; > nor minimal discernment requirement.
; > Default value : 1.0
;fight.rate.drop = 1.0
; > Get the initial erosion rate for fighters
; > This value is a percentage, representing the percent of damage that will be transformed to permanent life loss
; > It must be between 0 and 100, where 0 means no erosion and 100 means all damage are permanent
; > Default value : 10
;fight.initialErosion = 10

; Auto save
; ---------
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/fr/quatrevieux/araknemu/game/GameConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -440,5 +440,15 @@ public double xpRate() {
public double dropRate() {
return pool.decimal("fight.rate.drop", 1.0);
}

/**
* Get the initial erosion rate for fighters
* This value is a percentage, representing the percent of damage that will be transformed to permanent life loss
* It must be between 0 and 100, where 0 means no erosion and 100 means all damage are permanent
* Default value : 10
*/
public @NonNegative int initialErosion() {
return pool.nonNegativeInteger("fight.initialErosion", 10);
}
}
}
4 changes: 3 additions & 1 deletion src/main/java/fr/quatrevieux/araknemu/game/GameModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@
import fr.quatrevieux.araknemu.game.fight.module.AiModule;
import fr.quatrevieux.araknemu.game.fight.module.CarryingModule;
import fr.quatrevieux.araknemu.game.fight.module.CommonEffectsModule;
import fr.quatrevieux.araknemu.game.fight.module.FighterInitializationModule;
import fr.quatrevieux.araknemu.game.fight.module.IndirectSpellApplyEffectsModule;
import fr.quatrevieux.araknemu.game.fight.module.LaunchedSpellsModule;
import fr.quatrevieux.araknemu.game.fight.module.MonsterInvocationModule;
Expand Down Expand Up @@ -698,7 +699,8 @@ private void configureServices(ContainerConfigurator configurator) {
fight -> new AiModule(container.get(AiFactory.class)),
fight -> new MonsterInvocationModule(container.get(MonsterService.class), container.get(FighterFactory.class), fight),
SpiritualLeashModule::new,
CarryingModule::new
CarryingModule::new,
fight -> new FighterInitializationModule(container.get(GameConfiguration.class).fight())
),
container.get(FightService.FightFactory.class),
container.get(GameConfiguration.class).fight()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,20 @@
* Compute suffered damage
*
* Formula :
* (value * percent / 100 - fixed - reduce) * multiply
* ((value * percent / 100) * (0.9 ^ distance) - fixed - reduce) * multiply
*/
public final class Damage implements MultipliableDamage {
private final int value;
private final @NonNegative int value;
private final Element element;

private int multiply = 1;
private int fixed = 0;
private int percent = 100;
private int reduce = 0;
private int returned = 0;
private @NonNegative int percent = 100;
private @NonNegative int reduce = 0;
private @NonNegative int returned = 0;
private @NonNegative int distance = 0;

public Damage(int value, Element element) {
public Damage(@NonNegative int value, Element element) {
this.value = value;
this.element = element;
}
Expand All @@ -54,24 +54,34 @@ public Element element() {

@Override
public int value() {
final int base = (value * percent / 100 - fixed - reduce);
final int reducedDamage = (baseDamage() * percent / 100) - fixed - reduce;

if (base <= 0) {
if (reducedDamage <= 0) {
return 0;
}

return EffectsUtils.applyDistanceAttenuation(base, distance) * multiply;
return reducedDamage * multiply;
}

/**
* Get the base damage, before applying any reduction or multiplication
* Only the distance attenuation is applied
*/
public @NonNegative int baseDamage() {
final int value = this.value;

if (value == 0) {
return 0;
}

return EffectsUtils.applyDistanceAttenuation(value, distance);
}

/**
* Reduce damage in percent
*/
public Damage percent(int percent) {
if (percent > this.percent) {
this.percent = 0;
} else {
this.percent -= percent;
}
this.percent = Math.max(this.percent - percent, 0);

return this;
}
Expand All @@ -95,7 +105,7 @@ public Damage multiply(int factor) {
/**
* Reduce fixed damage with buff effect
*/
public Damage reduce(int value) {
public Damage reduce(@NonNegative int value) {
this.reduce += value;

return this;
Expand All @@ -104,7 +114,7 @@ public Damage reduce(int value) {
/**
* Add reflected damage
*/
public Damage reflect(int value) {
public Damage reflect(@NonNegative int value) {
this.returned += value;

return this;
Expand All @@ -124,14 +134,14 @@ public Damage distance(@NonNegative int distance) {
/**
* Get the damage reduction value from armor buff effects
*/
public int reducedDamage() {
public @NonNegative int reducedDamage() {
return reduce;
}

/**
* How much damage has been reflected by the target ?
*/
public int reflectedDamage() {
public @NonNegative int reflectedDamage() {
return returned;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public int apply(Fighter caster, SpellEffect effect, Fighter target, EffectValue
* @see fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onDirectDamage(Fighter, Damage) The called buff hook
* @see fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onDirectDamageApplied(Fighter, int) Buff called when damage are applied
*/
public int applyFixed(Fighter caster, int value, Fighter target) {
public int applyFixed(Fighter caster, @NonNegative int value, Fighter target) {
final Damage damage = createDamage(caster, target, value);

return applyDirectDamage(caster, damage, target);
Expand All @@ -119,7 +119,7 @@ public int applyFixed(Fighter caster, int value, Fighter target) {
* @see DamageApplier#applyFixed(Buff, int) Apply damage with the same way
* @see fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onIndirectDamage(Fighter, Damage) The called buff hook
*/
public int applyIndirectFixed(Fighter caster, int value, Fighter target) {
public int applyIndirectFixed(Fighter caster, @NonNegative int value, Fighter target) {
final Damage damage = createDamage(caster, target, value);

target.buffs().onIndirectDamage(caster, damage);
Expand Down Expand Up @@ -159,7 +159,7 @@ public int apply(Buff buff) {
*
* @see fr.quatrevieux.araknemu.game.fight.castable.effect.buff.Buffs#onBuffDamage(Buff, Damage) The called buff hook
*/
public int applyFixed(Buff buff, int value) {
public int applyFixed(Buff buff, @NonNegative int value) {
final Fighter target = buff.target();
final Damage damage = createDamage(buff.caster(), target, value);

Expand Down Expand Up @@ -208,7 +208,7 @@ private int applyDirectDamage(Fighter caster, Damage damage, Fighter target) {
target.buffs().onDirectDamage(caster, damage);

if (!caster.equals(target)) {
damage.reflect(target.characteristics().get(Characteristic.COUNTER_DAMAGE));
damage.reflect(Math.max(target.characteristics().get(Characteristic.COUNTER_DAMAGE), 0));
}

final int actualDamage = applyDamage(caster, damage, target);
Expand Down Expand Up @@ -243,13 +243,20 @@ private int applyDamage(Fighter caster, Damage damage, Fighter target) {
fight.send(ActionEffect.reducedDamage(target, damage.reducedDamage()));
}

final int lifeChange = target.life().alter(caster, -damage.value());
final int damageValue = damage.value();

if (lifeChange < 0 && !target.equals(caster) && damage.reflectedDamage() > 0) {
// Damage has been transformed to heal
if (damageValue < 0) {
return target.life().heal(caster, Asserter.castNonNegative(-damageValue));
}

final int actualDamage = target.life().damage(caster, damageValue, damage.baseDamage());

if (actualDamage > 0 && !target.equals(caster) && damage.reflectedDamage() > 0) {
applyReflectedDamage(target, caster, damage);
}

return lifeChange;
return -actualDamage;
}

/**
Expand All @@ -268,7 +275,14 @@ private void applyReflectedDamage(Fighter castTarget, Fighter caster, Damage dam

if (returnedDamage.baseValue() > 0) {
fight.send(ActionEffect.reflectedDamage(castTarget, returnedDamage.baseValue()));
returnedDamage.target().life().alter(castTarget, -returnedDamage.value());

final int actualReturnedDamage = returnedDamage.value();

if (actualReturnedDamage > 0) {
returnedDamage.target().life().damage(castTarget, actualReturnedDamage);
} else {
returnedDamage.target().life().heal(castTarget, Asserter.castNonNegative(-actualReturnedDamage));
}
}
}

Expand All @@ -281,7 +295,7 @@ private void applyReflectedDamage(Fighter castTarget, Fighter caster, Damage dam
*
* @return The damage object to apply
*/
private Damage createDamage(Fighter caster, Fighter target, int value) {
private Damage createDamage(Fighter caster, Fighter target, @NonNegative int value) {
final Damage damage = new Damage(value, element)
.percent(target.characteristics().get(element.percentResistance()))
.fixed(target.characteristics().get(element.fixedResistance()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public void handle(FightCastScope cast, FightCastScope.EffectScope effect) {

// This is a fixed effect, without any elements
// So it does not call any buff hooks
caster.life().alter(caster, -EffectValue.create(effect.effect(), caster, caster).value());
caster.life().damage(caster, EffectValue.create(effect.effect(), caster, caster).value());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public FixedDamageHandler(Fight fight) {
protected void applyOnTarget(FightCastScope cast, SpellEffect effect, Fighter target, EffectValue value) {
// This is a fixed effect, without any elements
// So it does not call any buff hooks
target.life().alter(cast.caster(), -value.value());
target.life().damage(cast.caster(), value.value());
}

@Override
Expand All @@ -54,7 +54,7 @@ public void buff(FightCastScope cast, FightCastScope.EffectScope effect) {

@Override
public boolean onStartTurn(Buff buff) {
buff.target().life().alter(buff.caster(), -EffectValue.create(buff.effect(), buff.caster(), buff.target()).value());
buff.target().life().damage(buff.caster(), EffectValue.create(buff.effect(), buff.caster(), buff.target()).value());

return !buff.target().dead();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ protected void applyOnTarget(FightCastScope cast, SpellEffect effect, Fighter ta
final FighterLife casterLife = caster.life();

final int damage = value.value();
final int actualDamage = target.life().alter(caster, -damage);
final int actualDamage = target.life().damage(caster, damage);

// This is a fixed effect, without any elements
// So it does not call any buff hooks
casterLife.alter(caster, -actualDamage);
casterLife.heal(caster, actualDamage);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

package fr.quatrevieux.araknemu.game.fight.castable.effect.handler.damage;

import fr.quatrevieux.araknemu.game.fight.fighter.FighterData;
import fr.quatrevieux.araknemu.game.fight.fighter.Fighter;

/**
* Base type for damage object which can be multiplied
Expand All @@ -31,7 +31,8 @@ public interface MultipliableDamage {
* @return The damage value. If negative, the damage is transformed to heal.
* If positive the number of life point to remove
*
* @see fr.quatrevieux.araknemu.game.fight.fighter.FighterLife#alter(FighterData, int)
* @see fr.quatrevieux.araknemu.game.fight.fighter.FighterLife#damage(Fighter, int)
* @see fr.quatrevieux.araknemu.game.fight.fighter.FighterLife#heal(Fighter, int)
*/
public int value();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public void handle(FightCastScope cast, FightCastScope.EffectScope effect) {
final SpellEffect spellEffect = effect.effect();
final EffectValue.Context context = EffectValue.preRoll(spellEffect, caster);
final FighterLife casterLife = caster.life();
final int lostLife = casterLife.max() - casterLife.current();
final int lostLife = Math.max(casterLife.max() - casterLife.current(), 0);

// Do not use AbstractPreRollEffectHandler to ensure that current life is not changed
// during the loop, so that damage are computed on the same life value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import fr.quatrevieux.araknemu.game.fight.fighter.Fighter;
import fr.quatrevieux.araknemu.game.fight.fighter.FighterLife;
import fr.quatrevieux.araknemu.game.spell.effect.SpellEffect;
import fr.quatrevieux.araknemu.util.Asserter;

/**
* Handle effect of punishment spell
Expand Down Expand Up @@ -71,7 +72,7 @@ public void handle(FightCastScope cast, FightCastScope.EffectScope effect) {
}

final double percent = context.forTarget(target).value() / 100d;
final int value = (int) (factor * percent);
final int value = Asserter.castNonNegative((int) (factor * percent));

applier.applyFixed(caster, value, target);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import fr.quatrevieux.araknemu.game.fight.fighter.Fighter;
import fr.quatrevieux.araknemu.game.world.creature.characteristics.Characteristics;
import org.checkerframework.checker.index.qual.NonNegative;

/**
* Compute reflected damage of a cast
Expand Down Expand Up @@ -52,7 +53,13 @@ public Fighter target() {
* This value is the effectively reflected value, not the applied one.
* A buff can change the real applied damage by using {@link ReflectedDamage#multiply(int)}
*/
public int baseValue() {
public @NonNegative int baseValue() {
final int baseCastDamage = castDamage.value();

if (baseCastDamage <= 0) {
return 0;
}

final Characteristics characteristics = target.characteristics();
final int percentResistance = characteristics.get(castDamage.element().percentResistance());
final int fixedResistance = characteristics.get(castDamage.element().fixedResistance());
Expand All @@ -65,7 +72,7 @@ public int baseValue() {
}

// Returned damage can be at most half of hit damage
return Math.min(base, castDamage.value() / 2);
return Math.min(base, baseCastDamage / 2);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,6 @@ private void applyCasterHeal(int damage, Fighter caster) {
}

// Minimal heal is 1
caster.life().alter(caster, Math.max(-damage / 2, 1));
caster.life().heal(caster, Math.max(-damage / 2, 1));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,6 @@ public boolean onStartTurn(Buff buff) {
}

private void apply(Fighter caster, SpellEffect effect, Fighter target) {
target.life().alter(caster, EffectValue.create(effect, caster, target).value());
target.life().heal(caster, EffectValue.create(effect, caster, target).value());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ public void handle(FightCastScope cast, FightCastScope.EffectScope effect) {
final Fighter caster = cast.caster();
final int heal = EffectValue.create(effect.effect(), caster, caster).value() * caster.life().current() / 100;

caster.life().alter(caster, -heal);
caster.life().damage(caster, heal);

for (Fighter target : effect.targets()) {
if (!target.equals(caster)) {
target.life().alter(caster, heal);
target.life().heal(caster, heal);
}
}
}
Expand Down
Loading

0 comments on commit 0d1850d

Please sign in to comment.