117 lines
5.1 KiB
Java
117 lines
5.1 KiB
Java
package eu.abdelbaki.cmmodular;
|
|
|
|
import net.minecraft.core.registries.Registries;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
import net.minecraft.world.entity.LivingEntity;
|
|
import net.minecraft.world.entity.ai.attributes.Attribute;
|
|
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
|
|
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
|
|
import net.minecraft.world.entity.ai.attributes.Attributes;
|
|
import net.minecraft.world.entity.ai.attributes.RangedAttribute;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.neoforged.bus.api.IEventBus;
|
|
import net.neoforged.neoforge.common.NeoForge;
|
|
import net.neoforged.neoforge.event.entity.EntityAttributeModificationEvent;
|
|
import net.neoforged.neoforge.event.entity.living.LivingIncomingDamageEvent;
|
|
import net.neoforged.neoforge.event.tick.PlayerTickEvent;
|
|
import net.neoforged.neoforge.registries.DeferredHolder;
|
|
import net.neoforged.neoforge.registries.DeferredRegister;
|
|
|
|
/**
|
|
* A sea serpent fang in hand is worth more in water than out of it: harder
|
|
* hits and a faster swing while the bearer is in it.
|
|
*
|
|
* <p>The mining half of the same idea needs nothing here - vanilla models
|
|
* underwater digging as an attribute of its own,
|
|
* {@code minecraft:player.submerged_mining_speed}, and the material simply
|
|
* adds to it. Damage and attack speed have no such switch, so this follows the
|
|
* pattern {@link FireImmunity} and {@link WaterBreathing} already set: an
|
|
* attribute of this mod's own that the material grants like any other, and a
|
|
* listener here that watches for it.
|
|
*
|
|
* <p>Damage is answered on the blow, which needs no state. Attack speed cannot
|
|
* be - it is read off the attribute map whenever the game wants to know how
|
|
* fast the bearer swings - so a modifier goes on and comes off as the bearer
|
|
* enters and leaves the water. It is transient and keyed by one id, so a
|
|
* player who never touches a fang never has one and a player who does never
|
|
* collects two.
|
|
*/
|
|
public final class WaterCombat {
|
|
private static final DeferredRegister<Attribute> ATTRIBUTES =
|
|
DeferredRegister.create(Registries.ATTRIBUTE, CMModular.MOD_ID);
|
|
|
|
/** No more than half again, however much fang the weapon is built from. */
|
|
private static final double MAX = 0.5D;
|
|
|
|
/** The fraction added to damage dealt and to swing speed, in water only. */
|
|
public static final DeferredHolder<Attribute, Attribute> WATER_COMBAT =
|
|
ATTRIBUTES.register("water_combat", id -> new RangedAttribute(
|
|
"attribute.cmmodular.water_combat", 0.0D, 0.0D, MAX).setSyncable(true));
|
|
|
|
/** The one attack speed modifier this puts on, so it can be taken off. */
|
|
private static final ResourceLocation SWING_ID =
|
|
ResourceLocation.fromNamespaceAndPath(CMModular.MOD_ID, "water_combat");
|
|
|
|
private WaterCombat() {
|
|
}
|
|
|
|
static void register(IEventBus modBus) {
|
|
ATTRIBUTES.register(modBus);
|
|
modBus.addListener(WaterCombat::attachToEntities);
|
|
NeoForge.EVENT_BUS.addListener(WaterCombat::onIncomingDamage);
|
|
NeoForge.EVENT_BUS.addListener(WaterCombat::onPlayerTick);
|
|
}
|
|
|
|
/** Attributes only exist on an entity if they were added to its type. */
|
|
private static void attachToEntities(EntityAttributeModificationEvent event) {
|
|
event.getTypes().forEach(type -> event.add(type, WATER_COMBAT));
|
|
}
|
|
|
|
private static void onIncomingDamage(LivingIncomingDamageEvent event) {
|
|
if (!(event.getSource().getEntity() instanceof LivingEntity attacker)
|
|
|| !attacker.isInWater()) {
|
|
return;
|
|
}
|
|
double bonus = boost(attacker);
|
|
if (bonus > 0.0D) {
|
|
event.setAmount((float) (event.getAmount() * (1.0D + bonus)));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Keeps the swing speed modifier in step with the water and the weapon.
|
|
*
|
|
* <p>Only players, because attack speed is only ever read for a player.
|
|
* The equipment is only consulted while the bearer is in water, so this
|
|
* costs nothing on dry land.
|
|
*/
|
|
private static void onPlayerTick(PlayerTickEvent.Post event) {
|
|
Player player = event.getEntity();
|
|
AttributeInstance speed = player.getAttribute(Attributes.ATTACK_SPEED);
|
|
if (speed == null) {
|
|
return;
|
|
}
|
|
double bonus = player.isInWater() ? boost(player) : 0.0D;
|
|
AttributeModifier applied = speed.getModifier(SWING_ID);
|
|
if (bonus <= 0.0D) {
|
|
if (applied != null) {
|
|
speed.removeModifier(SWING_ID);
|
|
}
|
|
return;
|
|
}
|
|
if (applied != null && applied.amount() == bonus) {
|
|
return;
|
|
}
|
|
// Weapon swapped for one with more fang in it, or the map was rebuilt
|
|
// out from under the modifier - either way, put the right one back.
|
|
speed.removeModifier(SWING_ID);
|
|
speed.addTransientModifier(new AttributeModifier(SWING_ID, bonus,
|
|
AttributeModifier.Operation.ADD_MULTIPLIED_BASE));
|
|
}
|
|
|
|
/** What the held modules are worth, clamped to what the attribute allows. */
|
|
private static double boost(LivingEntity entity) {
|
|
return Math.min(EquipmentAttributes.value(entity, WATER_COMBAT), MAX);
|
|
}
|
|
}
|