create-mekanism-modular/src/main/java/eu/abdelbaki/cmmodular/WaterCombat.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);
}
}