Skip to content

Commit

Permalink
Implement generic conditional stat hook
Browse files Browse the repository at this point in the history
In the future, will implement on some pre-existing stats where practical, though I suspect melee will want more context
Use for ranged version of dwarven, momentum, conducting, raging, and airborne
  • Loading branch information
KnightMiner committed Dec 17, 2022
1 parent c563aca commit c6fffed
Show file tree
Hide file tree
Showing 12 changed files with 242 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import net.minecraft.client.renderer.item.ItemPropertyFunction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import slimeknights.tconstruct.library.modifiers.hook.ConditionalStatModifierHook;
import slimeknights.tconstruct.library.tools.nbt.ToolStack;
import slimeknights.tconstruct.library.tools.stat.ToolStats;

Expand All @@ -13,10 +14,11 @@ public class TinkerItemProperties {
private static final ResourceLocation PULL_ID = new ResourceLocation("pull");
/** Property for bow pull amount */
private static final ItemPropertyFunction PULL = (stack, level, holder, seed) -> {
if (holder == null) {
if (holder == null || holder.getUseItem() != stack) {
return 0.0F;
}
return holder.getUseItem() != stack ? 0.0F : (float)(stack.getUseDuration() - holder.getUseItemRemainingTicks()) * ToolStack.from(stack).getStats().get(ToolStats.DRAW_SPEED) / 20.0F;
float drawSpeed = ConditionalStatModifierHook.getModifiedStat(ToolStack.from(stack), holder, ToolStats.DRAW_SPEED);
return (float)(stack.getUseDuration() - holder.getUseItemRemainingTicks()) * drawSpeed / 20.0F;
};

/** ID for the pulling property */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import slimeknights.tconstruct.TConstruct;
import slimeknights.tconstruct.library.modifiers.hook.ArmorWalkModifierHook;
import slimeknights.tconstruct.library.modifiers.hook.ArrowLaunchModifierHook;
import slimeknights.tconstruct.library.modifiers.hook.ConditionalStatModifierHook;
import slimeknights.tconstruct.library.modifiers.hook.ElytraFlightModifierHook;
import slimeknights.tconstruct.library.modifiers.hook.HarvestEnchantmentsModifierHook;
import slimeknights.tconstruct.library.modifiers.hook.InteractModifierHook;
Expand All @@ -30,6 +31,12 @@
public class TinkerHooks {
private TinkerHooks() {}


/* General */

/** Generic hook for stats conditioned on the entity holding the tool */
public static final ModifierHook<ConditionalStatModifierHook> CONDITIONAL_STAT = register("conditional_stat", ConditionalStatModifierHook.class, ConditionalStatModifierHook.ALL_MERGER, ConditionalStatModifierHook.EMPTY);

/* Loot */

/** Hook for a tool boosting the looting value */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package slimeknights.tconstruct.library.modifiers.hook;

import net.minecraft.world.entity.LivingEntity;
import slimeknights.tconstruct.library.modifiers.ModifierEntry;
import slimeknights.tconstruct.library.modifiers.TinkerHooks;
import slimeknights.tconstruct.library.tools.nbt.IToolStackView;
import slimeknights.tconstruct.library.tools.stat.FloatToolStat;

import java.util.Collection;
import java.util.function.Function;

/** Hook for modifying a stat conditioned on the holder */
public interface ConditionalStatModifierHook {
/** Default behavior */
ConditionalStatModifierHook EMPTY = (tool, modifier, living, stat, baseValue, multiplier) -> baseValue;

/** Constructor for a merger that runs all hooks from the children */
Function<Collection<ConditionalStatModifierHook>, ConditionalStatModifierHook> ALL_MERGER = AllMerger::new;

/**
* Method to modify a stat as the tool is being used
* @param tool Tool instance
* @param modifier Modifier instance
* @param living Entity holding the tool
* @param stat Stat to be modified, safe to do instance equality
* @param baseValue Value before this hook modified the stat
* @param multiplier Global multiplier, same value contained in the tool, but fetched for convenience as it's commonly needed for stat bonuses
* @return New value of the stat, or baseValue if you choose not to modify this stat
*/
float modifyStat(IToolStackView tool, ModifierEntry modifier, LivingEntity living, FloatToolStat stat, float baseValue, float multiplier);

/** Gets the given stat from the tool, as modified by this hook */
static float getModifiedStat(IToolStackView tool, LivingEntity living, FloatToolStat stat, float value) {
float multiplier = tool.getMultiplier(stat);
for (ModifierEntry entry : tool.getModifierList()) {
value = entry.getHook(TinkerHooks.CONDITIONAL_STAT).modifyStat(tool, entry, living, stat, value, multiplier);
}
return stat.clamp(value);
}

/** Gets the given stat from the tool, as modified by this hook */
static float getModifiedStat(IToolStackView tool, LivingEntity living, FloatToolStat stat) {
return getModifiedStat(tool, living, stat, tool.getStats().get(stat));
}

/** All hook merger: runs hooks of all children */
record AllMerger(Collection<ConditionalStatModifierHook> modules) implements ConditionalStatModifierHook {
@Override
public float modifyStat(IToolStackView tool, ModifierEntry modifier, LivingEntity living, FloatToolStat stat, float baseValue, float multiplier) {
for (ConditionalStatModifierHook hook : modules) {
baseValue = hook.modifyStat(tool, modifier, living, stat, baseValue, multiplier);
}
return baseValue;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ public TinkerModifiers() {
public static RegistryObject<MagneticEffect> magneticEffect = MOB_EFFECTS.register("magnetic", MagneticEffect::new);
public static RegistryObject<RepulsiveEffect> replusiveEffect = MOB_EFFECTS.register("replusive", RepulsiveEffect::new);
public static RegistryObject<TinkerEffect> momentumEffect = MOB_EFFECTS.register("momentum", MARKER_EFFECT.apply(0x60496b));
public static RegistryObject<TinkerEffect> momentumRangedEffect = MOB_EFFECTS.register("momentum_ranged", MARKER_EFFECT.apply(0x60496b));
public static RegistryObject<TinkerEffect> insatiableEffect = MOB_EFFECTS.register("insatiable", MARKER_EFFECT.apply(0x9261cc));
public static RegistryObject<TinkerEffect> enderferenceEffect = MOB_EFFECTS.register("enderference", () -> new NoMilkEffect(MobEffectCategory.HARMFUL, 0x8F648F, true));
public static RegistryObject<TinkerEffect> teleportCooldownEffect = MOB_EFFECTS.register("teleport_cooldown", () -> new NoMilkEffect(MobEffectCategory.HARMFUL, 0xCC00FA, true));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import net.minecraftforge.event.ForgeEventFactory;
import slimeknights.tconstruct.library.modifiers.ModifierEntry;
import slimeknights.tconstruct.library.modifiers.TinkerHooks;
import slimeknights.tconstruct.library.modifiers.hook.ConditionalStatModifierHook;
import slimeknights.tconstruct.library.tools.definition.ToolDefinition;
import slimeknights.tconstruct.library.tools.helper.ToolBuildHandler;
import slimeknights.tconstruct.library.tools.helper.ToolDamageUtil;
Expand Down Expand Up @@ -102,12 +103,12 @@ public void releaseUsing(ItemStack stack, Level level, LivingEntity living, int

// calculate arrow power
StatsNBT stats = tool.getStats();
float charge = chargeTime * stats.get(ToolStats.DRAW_SPEED) / 20f;
float charge = chargeTime * ConditionalStatModifierHook.getModifiedStat(tool, living, ToolStats.DRAW_SPEED) / 20f;
charge = (charge * charge + charge * 2) / 3;
if (charge > 1) {
charge = 1;
}
float velocity = stats.get(ToolStats.VELOCITY);
float velocity = ConditionalStatModifierHook.getModifiedStat(tool, living, ToolStats.VELOCITY);
float power = charge * velocity;
if (power < 0.1f) {
return;
Expand All @@ -118,14 +119,15 @@ public void releaseUsing(ItemStack stack, Level level, LivingEntity living, int
if (!level.isClientSide) {
ArrowItem arrowItem = ammo.getItem() instanceof ArrowItem arrow ? arrow : (ArrowItem)Items.ARROW;
AbstractArrow arrowEntity = arrowItem.createArrow(level, ammo, player);
arrowEntity.shootFromRotation(player, player.getXRot(), player.getYRot(), 0.0F, power * 3.0F, 3*(1/stats.get(ToolStats.ACCURACY)-1) * velocity);
float accuracy = ConditionalStatModifierHook.getModifiedStat(tool, living, ToolStats.ACCURACY);
arrowEntity.shootFromRotation(player, player.getXRot(), player.getYRot(), 0.0F, power * 3.0F, 3*(1/accuracy-1) * velocity);
// TODO: modifier hook to add arrow properties
if (charge == 1.0F) {
arrowEntity.setCritArrow(true);
}
// vanilla arrows have a base damage of 2, cancel that out then add in our base damage to account for custom arrows with higher base damage
float baseArrowDamage = (float)(arrowEntity.baseDamage - 2 + tool.getStats().get(ToolStats.PROJECTILE_DAMAGE));
arrowEntity.setBaseDamage(baseArrowDamage);
arrowEntity.setBaseDamage(ConditionalStatModifierHook.getModifiedStat(tool, living, ToolStats.PROJECTILE_DAMAGE, baseArrowDamage));

// let modifiers such as fiery and punch set properties
for (ModifierEntry entry : tool.getModifierList()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
package slimeknights.tconstruct.tools.modifiers.traits.harvest;

import net.minecraft.core.Direction;
import net.minecraft.world.entity.LivingEntity;
import net.minecraftforge.event.entity.player.PlayerEvent.BreakSpeed;
import slimeknights.tconstruct.library.modifiers.ModifierEntry;
import slimeknights.tconstruct.library.modifiers.TinkerHooks;
import slimeknights.tconstruct.library.modifiers.hook.ConditionalStatModifierHook;
import slimeknights.tconstruct.library.modifiers.impl.NoLevelsModifier;
import slimeknights.tconstruct.library.modifiers.util.ModifierHookMap.Builder;
import slimeknights.tconstruct.library.tools.nbt.IToolStackView;
import slimeknights.tconstruct.library.tools.stat.FloatToolStat;
import slimeknights.tconstruct.library.tools.stat.ToolStats;

public class AirborneModifier extends NoLevelsModifier {
public class AirborneModifier extends NoLevelsModifier implements ConditionalStatModifierHook {
@Override
public int getPriority() {
return 75; // runs after other modifiers
}

@Override
protected void registerHooks(Builder hookBuilder) {
hookBuilder.addHook(this, TinkerHooks.CONDITIONAL_STAT);
}

@Override
public void onBreakSpeed(IToolStackView tool, int level, BreakSpeed event, Direction sideHit, boolean isEffective, float miningSpeedModifier) {
// the speed is reduced when not on the ground, cancel out
if (!event.getEntity().isOnGround()) {
event.setNewSpeed(event.getNewSpeed() * 5);
}
}

@Override
public float modifyStat(IToolStackView tool, ModifierEntry modifier, LivingEntity living, FloatToolStat stat, float baseValue, float multiplier) {
if (stat == ToolStats.ACCURACY && !living.isOnGround() && !living.onClimbable()) {
return baseValue + 0.5f;
}
return baseValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,50 @@
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import net.minecraftforge.event.entity.player.PlayerEvent.BreakSpeed;
import slimeknights.tconstruct.TConstruct;
import slimeknights.tconstruct.common.TinkerTags;
import slimeknights.tconstruct.library.modifiers.Modifier;
import slimeknights.tconstruct.library.modifiers.ModifierEntry;
import slimeknights.tconstruct.library.modifiers.TinkerHooks;
import slimeknights.tconstruct.library.modifiers.hook.ConditionalStatModifierHook;
import slimeknights.tconstruct.library.modifiers.util.ModifierHookMap.Builder;
import slimeknights.tconstruct.library.tools.nbt.IToolStackView;
import slimeknights.tconstruct.library.tools.stat.FloatToolStat;
import slimeknights.tconstruct.library.tools.stat.ToolStats;
import slimeknights.tconstruct.library.utils.TooltipKey;

import javax.annotation.Nullable;
import java.util.List;

public class DwarvenModifier extends Modifier {
public class DwarvenModifier extends Modifier implements ConditionalStatModifierHook {
private static final Component MINING_SPEED = TConstruct.makeTranslation("modifier", "dwarven.mining_speed");
private static final Component VELOCITY = TConstruct.makeTranslation("modifier", "dwarven.velocity");
/** Distance below sea level to get boost */
private static final float BOOST_DISTANCE = 64f;
/** Blocks above 0 when debuff starts, and the range of debuff in the world */
private static final float DEBUFF_RANGE = 128f;
/** Boost when at distance, gets larger when lower */
private static final float BONUS = 6;
/** Mining speed boost when at distance, gets larger when lower */
private static final float MINING_BONUS = 6;
/** Velocity boost when at distance, gets larger when lower */
private static final float VELOCITY_BONUS = 0.05f;

@Override
protected void registerHooks(Builder hookBuilder) {
hookBuilder.addHook(this, TinkerHooks.CONDITIONAL_STAT);
}

/** Gets the boost for the given level and height, can go negative */
private static float getBoost(Level world, int y, int level, float baseSpeed, float modifier) {
private static float getBoost(Level world, float y, int level, float baseSpeed, float modifier, float bonus) {
// grants 0 bonus at 64, 1x at -BOOST_DISTANCE, 2x at -2*BOOST_DISTANCE
// prevents the modifier from getting too explosive in tall worlds, clamp between -6 and 12
if (y < BOOST_DISTANCE) {
float scale = Mth.clamp((BOOST_DISTANCE - y) / BOOST_DISTANCE, 0, 2);
return baseSpeed + (level * scale * BONUS * modifier);
return baseSpeed + (level * scale * bonus * modifier);
}

// start the debuff 128 blocks below the top, but for short worlds start it 128 blocks above the full boost (so we have 64 blocks of neutral)
Expand All @@ -57,29 +71,40 @@ public void onBreakSpeed(IToolStackView tool, int level, BreakSpeed event, Direc
if (!isEffective) {
return;
}
event.setNewSpeed(getBoost(event.getPlayer().level, event.getPos().getY(), level, event.getNewSpeed(), miningSpeedModifier * tool.getMultiplier(ToolStats.MINING_SPEED)));
event.setNewSpeed(getBoost(event.getPlayer().level, event.getPos().getY(), level, event.getNewSpeed(), miningSpeedModifier * tool.getMultiplier(ToolStats.MINING_SPEED), MINING_BONUS));
}

@Override
public float modifyStat(IToolStackView tool, ModifierEntry modifier, LivingEntity living, FloatToolStat stat, float baseValue, float multiplier) {
if (stat == ToolStats.VELOCITY) {
return getBoost(living.level, (float)living.getY(), modifier.getLevel(), baseValue, multiplier, VELOCITY_BONUS);
}
return baseValue;
}

@Override
public void addInformation(IToolStackView tool, int level, @Nullable Player player, List<Component> tooltip, TooltipKey key, TooltipFlag tooltipFlag) {
if (tool.hasTag(TinkerTags.Items.HARVEST)) {
boolean harvest = tool.hasTag(TinkerTags.Items.HARVEST);
if (harvest || tool.hasTag(TinkerTags.Items.RANGED)) {
Component prefix = harvest ? MINING_SPEED : VELOCITY;
float baseBoost = harvest ? MINING_BONUS : VELOCITY_BONUS;
double boost;
if (player != null && key == TooltipKey.SHIFT) {
// passing in 1 means greater than 1 is a boost, and less than 1 is a percentage
// the -1 means for percentage, the range is now 0 to -75%, and for flat boost its properly 0 to BOOST
boost = getBoost(player.level, (int)player.getY(), level, 1, 1f) - 1;
// the -1 means for percentage, the range is now 0 to -75%, and for flat boost its properly 0 to baseBoost
boost = getBoost(player.level, (float)player.getY(), level, 1, 1f, baseBoost) - 1;
if (boost < 0) {
// goes from 0 to -75%, don't show 0%
if (boost <= -0.01) {
addPercentTooltip(MINING_SPEED, boost, tooltip);
addPercentTooltip(prefix, boost, tooltip);
}
return;
}
} else {
boost = BONUS * level;
boost = baseBoost * level;
}
if (boost >= 0.01) {
addFlatBoost(MINING_SPEED, boost * tool.getMultiplier(ToolStats.MINING_SPEED), tooltip);
addFlatBoost(prefix, boost * tool.getMultiplier(ToolStats.MINING_SPEED), tooltip);
}
}
}
Expand Down

0 comments on commit c6fffed

Please sign in to comment.