Skip to content

Commit 63f0c3b

Browse files
committed
Fleshed out damage algorithm, should be interesting to test. Handles player and armor damage. Next up is ammo handling
1 parent 7082b59 commit 63f0c3b

File tree

2 files changed

+223
-35
lines changed

2 files changed

+223
-35
lines changed

src/main/java/com/programmerdan/minecraft/addgun/ammo/Bullet.java

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.bukkit.util.Vector;
3131

3232
import com.programmerdan.minecraft.addgun.AddGun;
33+
import com.programmerdan.minecraft.addgun.ArmorType;
3334
import com.programmerdan.minecraft.addgun.guns.HitPart;
3435

3536
/**
@@ -133,18 +134,31 @@ public class Bullet implements Comparable<Bullet>, Serializable {
133134
// bypass stuff is TODO
134135

135136
/**
136-
* % to which this bullet bypasses armor reduction, if defense points are at or below defensePointsBypassBegins
137+
* Base armor reduction % across all types
137138
*/
138-
private double armorBypass = 0.1d;
139+
private double baseArmorReduction = 0.0d;
140+
139141
/**
140-
* % of bypass occurring, assuming defensePointsBypassBegins is satisfied.
142+
* Configure armor damage reduction % for each type.
143+
* Note this is used to compute increasing reduction via enchantments
144+
* Note also that unlike normal damage, bullet damage is against a single piece of armor.
141145
*/
142-
private double armorBypassChance = 0.5d;
146+
private Map<ArmorType, Double> armorReduction = new ConcurrentHashMap<ArmorType, Double>();
147+
148+
/**
149+
* Base armor damage % across all types
150+
*/
151+
private double baseArmorDamage = 0.0d;
152+
143153
/**
144-
* Based on the "defense points" calculus, determines the armor point at which bypass can occur. This is _on a per piece_ basis. See also
145-
* how it's modified by the hitlocation modifiers
154+
* Configure armor damage for each type.
155+
* Based on the type of armor, indicates what % of damage is passed on to the armor.
156+
* Some enchantments may reduce this in the final accounting.
157+
* But generally, a good metric to follow is the more damage reduction it offers, the
158+
* more damage it takes.
146159
*/
147-
private double defensePointsBypassBegins = 5.0;
160+
private Map<ArmorType, Double> armorDamage = new ConcurrentHashMap<ArmorType, Double>();
161+
148162

149163
public Bullet(ConfigurationSection config) {
150164
this.name = config.getName();
@@ -174,15 +188,30 @@ public Bullet(ConfigurationSection config) {
174188
this.knockback = config.getInt("knockback.level", knockback);
175189

176190
// TODO: damage bypass.
191+
this.baseArmorReduction = config.getDouble("reduction.base", baseArmorReduction);
192+
for (ArmorType armor : ArmorType.values()) {
193+
if (config.contains("reduction." + armor.toString())) {
194+
double armorReduce = config.getDouble("reduction." + armor.toString());
195+
this.armorReduction.put(armor, armorReduce);
196+
}
197+
}
177198

178-
this.baseAvgHitDamage = config.getDouble("basedamage.avg", baseAvgHitDamage);
179-
this.baseSpreadHitDamage = config.getDouble("basedamage.spread", baseSpreadHitDamage);
199+
this.baseArmorDamage = config.getDouble("durability.base", baseArmorDamage);
200+
for (ArmorType armor : ArmorType.values()) {
201+
if (config.contains("durability." + armor.toString())) {
202+
double armorDamage = config.getDouble("durability." + armor.toString());
203+
this.armorDamage.put(armor, armorDamage);
204+
}
205+
}
206+
207+
this.baseAvgHitDamage = config.getDouble("damage.avg", baseAvgHitDamage);
208+
this.baseSpreadHitDamage = config.getDouble("damage.spread", baseSpreadHitDamage);
180209

181210
for (HitPart hit : HitPart.values()) {
182-
ConfigurationSection hitConfig = config.getConfigurationSection(hit.toString());
211+
ConfigurationSection hitConfig = config.getConfigurationSection("damage." + hit.toString());
183212
if (hitConfig == null) continue;
184-
double avgHitDamage = hitConfig.getDouble("damage.avg");
185-
double spreadHitDamage = hitConfig.getDouble("damage.spread");
213+
double avgHitDamage = hitConfig.getDouble("avg");
214+
double spreadHitDamage = hitConfig.getDouble("spread");
186215

187216
this.avgHitDamage.put(hit, avgHitDamage);
188217
this.spreadHitDamage.put(hit, spreadHitDamage);
@@ -207,6 +236,26 @@ public double getAvgHitDamage(HitPart hit) {
207236
public double getSpreadHitDamage(HitPart hit) {
208237
return this.baseSpreadHitDamage + this.spreadHitDamage.getOrDefault(hit, 0.0d);
209238
}
239+
240+
/**
241+
* Gets the % reduction of damage that armor confers as its base impact.
242+
*
243+
* @param armor the armor type being tested
244+
* @return the % reduction (0 to 1)
245+
*/
246+
public double getArmorReduction(ArmorType armor) {
247+
return this.armorReduction.getOrDefault(armor, this.baseArmorReduction);
248+
}
249+
250+
/**
251+
* Gets the % of damage to apply to the armor.
252+
*
253+
* @param armor the armor type absorbing damage
254+
* @return the % damage conferred to the armor (0 to 1)
255+
*/
256+
public double getArmorDamage(ArmorType armor) {
257+
return this.armorReduction.getOrDefault(armor, this.baseArmorDamage);
258+
}
210259

211260
/**
212261
* Gets the configured unique name for this Bullet instance.

src/main/java/com/programmerdan/minecraft/addgun/guns/StandardGun.java

Lines changed: 162 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,33 @@
4545
import com.programmerdan.minecraft.addgun.ammo.Clip;
4646

4747
import static com.programmerdan.minecraft.addgun.guns.Utilities.getArmorType;
48+
import static com.programmerdan.minecraft.addgun.guns.Utilities.computeTotalXP;
4849

4950
public class StandardGun implements BasicGun {
51+
52+
private static double[] protectionCurve = new double[] {
53+
0.0d,
54+
-0.25 * Math.log(1.0 / (1.0 + 1.0)),
55+
-0.25 * Math.log(1.0 / (2.0 + 1.0)),
56+
-0.25 * Math.log(1.0 / (3.0 + 1.0)),
57+
-0.25 * Math.log(1.0 / (4.0 + 1.0)),
58+
-0.25 * Math.log(1.0 / (5.0 + 1.0)),
59+
-0.25 * Math.log(1.0 / (6.0 + 1.0)),
60+
-0.25 * Math.log(1.0 / (7.0 + 1.0)),
61+
-0.25 * Math.log(1.0 / (8.0 + 1.0)),
62+
-0.25 * Math.log(1.0 / (9.0 + 1.0)),
63+
-0.25 * Math.log(1.0 / (10.0 + 1.0)),
64+
-0.25 * Math.log(1.0 / (11.0 + 1.0)),
65+
-0.25 * Math.log(1.0 / (12.0 + 1.0)),
66+
-0.25 * Math.log(1.0 / (13.0 + 1.0)),
67+
-0.25 * Math.log(1.0 / (14.0 + 1.0)),
68+
-0.25 * Math.log(1.0 / (15.0 + 1.0)),
69+
};
70+
71+
private static double baseProjectileProtection = 1.0d;
72+
private static double baseEnvironmentProtection = 0.25d;
73+
private static double baseUnbreakingProtection = 0.33d;
74+
5075
/**
5176
* Is this gun enabled?
5277
*/
@@ -203,7 +228,9 @@ protected StandardGun(String name) {
203228
+ Integer.toHexString(this.getName().hashCode() + this.getName().length());
204229
}
205230

206-
public abstract ItemStack generateGun();
231+
public ItemStack generateGun() {
232+
return gunExample.clone();
233+
}
207234

208235
public boolean isEnabled() {
209236
return enabled;
@@ -441,7 +468,11 @@ public void manageHit(HitDigest hitData, Entity hit, Projectile bullet, Bullet b
441468
}
442469

443470
/**
444-
* Override this class, you use it to manage what happens when something damageable is hit.
471+
* This is a complex method. It handles the various hitvectors and computations of damage both to
472+
* player/entity and armor. I'll write up a full explanation in the documentation of configuration.
473+
*
474+
* For now it suffices that depending on _where_ the damage is done, and what is being worn there, depends
475+
* on the nature of the damage inflicted.
445476
*
446477
* @param hitData the Data matrix showing hit information
447478
* @param hit the damageable entity that was struck
@@ -465,9 +496,14 @@ public void manageDamage(HitDigest hitData, Damageable hit, Projectile bullet, B
465496
ItemStack shield = null;
466497
double shieldEffectiveness = 0.0d;
467498

499+
LivingEntity living = null;
500+
EntityEquipment equipment = null;
501+
502+
HumanEntity human = null;
503+
468504
if (hit instanceof LivingEntity) {
469-
LivingEntity living = (LivingEntity) hit;
470-
EntityEquipment equipment = living.getEquipment();
505+
living = (LivingEntity) hit;
506+
equipment = living.getEquipment();
471507
// now reductions?
472508
switch(hitData.nearestHitPart) {
473509
case BODY:
@@ -498,14 +534,14 @@ public void manageDamage(HitDigest hitData, Damageable hit, Projectile bullet, B
498534
break;
499535
case MISS: // no hit?
500536
nearMiss(hitData, hit, bullet, bulletType);
501-
break;
537+
return;
502538
default:
503539
break;
504540
}
505541

506542
if (shield != null && Material.SHIELD.equals(shield.getType())) {
507543
if (hit instanceof HumanEntity) {
508-
HumanEntity human = (HumanEntity) hit;
544+
human = (HumanEntity) hit;
509545
if (human.isBlocking()) {
510546
shieldEffectiveness = 1.0d;
511547
} else if (human.isHandRaised()) {
@@ -516,28 +552,134 @@ public void manageDamage(HitDigest hitData, Damageable hit, Projectile bullet, B
516552
shield = null;
517553
}
518554
}
555+
556+
if (shield != null) {
557+
// check bypass?
558+
double angle = Math.toDegrees(hit.getLocation().getDirection().angle(speed));
559+
// TODO?!?! check this
560+
if (angle >= -135 && angle < 135) {
561+
// no deflection!
562+
shieldEffectiveness = 0.0d;
563+
}
564+
565+
finalDamage *= (1.0 - shieldEffectiveness);
566+
567+
AddGun.getPlugin().debug(String.format("Base damage of %.2f, Has shield %s, angle of %.2f, effectiveness %.2f, final Damage %.2f",
568+
baseRealDamage, shield, angle, shieldEffectiveness, finalDamage));
569+
570+
int unbLevel = shield.getEnchantmentLevel(Enchantment.DURABILITY);
571+
572+
// Basically, unbreaking reduces the amount of durability damage that the armor will sustain
573+
double unbReduction = (1.0 + StandardGun.baseUnbreakingProtection * StandardGun.protectionCurve[unbLevel]);
574+
double finalDuraDamage = baseRealDamage * (1.0 - (1.0 - bulletType.getArmorDamage(ArmorType.SHIELD)) * unbReduction);
575+
if (shield.getDurability() < finalDuraDamage) {
576+
// broken.
577+
double directDamage = finalDuraDamage - shield.getDurability();
578+
579+
finalDamage += directDamage; // this is the bit of damage the shield was meant to absorb, but didn't.
580+
581+
shield = null;
582+
583+
AddGun.getPlugin().debug(String.format("Shield broken by dura damage %.2f, adding the remaining %.2f to the player damage",
584+
finalDuraDamage, directDamage));
585+
} else {
586+
shield.setDurability((short) (shield.getDurability() - Math.round(finalDuraDamage)));
587+
588+
AddGun.getPlugin().debug(String.format("Shield damaged by %.2f", finalDuraDamage));
589+
}
590+
}
591+
592+
// reset base damage
593+
baseRealDamage = finalDamage;
519594

520-
if (armorHit != null) {
595+
if (armorHit != null && baseRealDamage > 0.0d) {
521596
// check bypass?
522597

523598
// check armor type
524599
ArmorType grade = getArmorType(armorHit.getType());
525600
int protLevel = armorHit.getEnchantmentLevel(Enchantment.PROTECTION_ENVIRONMENTAL);
526601
int projLevel = armorHit.getEnchantmentLevel(Enchantment.PROTECTION_PROJECTILE);
602+
int unbLevel = armorHit.getEnchantmentLevel(Enchantment.DURABILITY);
603+
604+
if (projLevel > 15) projLevel = 15;
605+
if (protLevel > 15) protLevel = 15;
606+
if (unbLevel > 15) unbLevel = 15;
607+
608+
// get base modifier from Bullet in terms of how much a type of armor protects you from this type of bullet
609+
// then adjust based on prot and proj levels.
610+
double reduction = bulletType.getArmorReduction(grade);
611+
double protReduction = (1.0 + StandardGun.baseEnvironmentProtection * StandardGun.protectionCurve[protLevel]);
612+
double projReduction = (1.0 + StandardGun.baseProjectileProtection * StandardGun.protectionCurve[projLevel]);
527613

528-
// TODO: calculate
614+
double finalReduce = reduction * protReduction * projReduction;
615+
616+
if (finalReduce > 1.0) finalReduce = 1.0;
617+
618+
finalDamage *= (1.0 - finalReduce);
619+
620+
AddGun.getPlugin().debug(String.format("Has armor %s, baseDamage of %.2f, base reduction %.2f, prot %.2f, proj %.2f, final Damage %.2f",
621+
armorHit, baseRealDamage, reduction, protReduction, projReduction, finalDamage));
622+
623+
// Basically, unbreaking reduces the amount of durability damage that the armor will sustain
624+
double unbReduction = (1.0 + StandardGun.baseUnbreakingProtection * StandardGun.protectionCurve[unbLevel]);
625+
double finalDuraDamage = baseRealDamage * (1.0 - (1.0 - bulletType.getArmorDamage(grade)) * unbReduction);
626+
if (armorHit.getDurability() < finalDuraDamage) {
627+
// broken.
628+
double directDamage = finalDuraDamage - armorHit.getDurability();
629+
630+
finalDamage += directDamage; // this is the bit of damage the armor was meant to absorb, but didn't.
631+
632+
armorHit = null;
633+
634+
AddGun.getPlugin().debug(String.format("Armor broken by dura damage %.2f, adding the remaining %.2f to the player damage",
635+
finalDuraDamage, directDamage));
636+
} else {
637+
armorHit.setDurability((short) (armorHit.getDurability() - Math.round(finalDuraDamage)));
638+
639+
AddGun.getPlugin().debug(String.format("Armor damaged by %.2f", finalDuraDamage));
640+
}
529641
}
530642

531-
if (shield != null) {
532-
// check bypass?
533-
double angle = Math.toDegrees(hit.getLocation().getDirection().angle(speed));
534-
// TODO?!?! check this
535-
if (angle >= -135 && angle < 135) {
536-
// no deflection!
537-
shieldEffectiveness = 0.0d;
643+
if (equipment != null) {
644+
ItemStack tShield = equipment.getItemInOffHand();
645+
646+
switch(hitData.nearestHitPart) {
647+
case BODY:
648+
case CHEST_PLATE:
649+
case LEFT_ARM:
650+
case LEFT_FOOT:
651+
case LEFT_HAND:
652+
case RIGHT_ARM:
653+
case RIGHT_FOOT:
654+
case RIGHT_HAND:// all variants on body atm
655+
equipment.setLeggings(armorHit);
656+
if (tShield != null && Material.SHIELD.equals(tShield.getType())) {
657+
equipment.setItemInOffHand(shield);
658+
}
659+
break;
660+
case BOOTS:
661+
case FEET: // just feet
662+
equipment.setBoots(armorHit);
663+
break;
664+
case HEAD:
665+
case HELMET: // just head
666+
equipment.setHelmet(armorHit);
667+
if (tShield != null && Material.SHIELD.equals(tShield.getType())) {
668+
equipment.setItemInOffHand(shield);
669+
}
670+
break;
671+
case LEGGINGS:
672+
case LEFT_LEG:
673+
case RIGHT_LEG:
674+
case LEGS: // just legs
675+
equipment.setLeggings(armorHit);
676+
break;
677+
case MISS: // no hit?
678+
nearMiss(hitData, hit, bullet, bulletType);
679+
break;
680+
default:
681+
break;
538682
}
539-
540-
finalDamage *= (1.0 - shieldEffectiveness);
541683
}
542684

543685
//TODO: player states? custom shit? event?
@@ -557,7 +699,7 @@ public void manageDamage(HitDigest hitData, Damageable hit, Projectile bullet, B
557699
* @param bullet the "Projectile" bullet doing the hitting
558700
* @param bulletType the "Bullet" type of the projectile.
559701
*/
560-
abstract void postHit(HitDigest hitData, Entity hit, Projectile bullet, Bullet bulletType);
702+
public void postHit(HitDigest hitData, Entity hit, Projectile bullet, Bullet bulletType) {}
561703

562704
/**
563705
* Any post-miss cleanup can be handled here. Misses will automatically run this function, and
@@ -569,8 +711,8 @@ public void manageDamage(HitDigest hitData, Damageable hit, Projectile bullet, B
569711
* @param continueBullet Since the original projectile is removed, this is a "continue" bullet spawned _after_ the miss entity.
570712
* @param bulletType the "Bullet" type of the projectiles.
571713
*/
572-
abstract void postMiss(HitDigest hitData, Entity hit, Projectile bullet, Projectile continueBullet,
573-
Bullet bulletType);
714+
public void postMiss(HitDigest hitData, Entity hit, Projectile bullet, Projectile continueBullet,
715+
Bullet bulletType) {}
574716

575717

576718
/**
@@ -598,9 +740,6 @@ public Projectile shoot(Location begin, Bullet bulletType, ProjectileSource shoo
598740
newBullet.setVelocity(velocity);
599741
}
600742

601-
travelPaths.put(newBullet.getUniqueId(), begin);
602-
inFlightBullets.put(newBullet.getUniqueId(), bulletType);
603-
604743
return newBullet;
605744
}
606745

0 commit comments

Comments
 (0)