Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -100,21 +100,21 @@ private static ItemStack findMatchingAmmo(ItemStack bow, LivingEntity living, Pr
* @param tool Tool instance
* @param bow Bow stack instance
* @param predicate Predicate for valid ammo
* @param player Player to search
* @param holder Weapon holder to search ammo from
* @return Found ammo
*/
static ItemStack findAmmo(IToolStackView tool, ItemStack bow, Player player, Predicate<ItemStack> predicate) {
static ItemStack findAmmo(IToolStackView tool, ItemStack bow, LivingEntity holder, Predicate<ItemStack> predicate) {
int projectilesDesired = 1 + (2 * tool.getModifierLevel(TinkerModifiers.multishot.getId()));
// treat client side as creative, no need to shrink the stacks clientside
Level level = player.level();
boolean creative = player.getAbilities().instabuild || level.isClientSide;
Level level = holder.level();
boolean creative = holder instanceof Player player && player.getAbilities().instabuild || level.isClientSide;

// first search, find what ammo type we want
ItemStack standardAmmo = player.getProjectile(bow);
ItemStack standardAmmo = holder.getProjectile(bow);
ItemStack resultStack = ItemStack.EMPTY;
for (ModifierEntry entry : tool.getModifierList()) {
BowAmmoModifierHook hook = entry.getHook(ModifierHooks.BOW_AMMO);
ItemStack ammo = hook.findAmmo(tool, entry, player, standardAmmo, predicate);
ItemStack ammo = hook.findAmmo(tool, entry, holder, standardAmmo, predicate);
if (!ammo.isEmpty()) {
// if creative, we are done, just return the ammo with the given size
if (creative) {
Expand All @@ -123,7 +123,7 @@ static ItemStack findAmmo(IToolStackView tool, ItemStack bow, Player player, Pre

// not creative, split out the desired amount. We may have to do more work if it is too small
resultStack = ItemHandlerHelper.copyStackWithSize(ammo, Math.min(projectilesDesired, ammo.getCount()));
hook.shrinkAmmo(tool, entry, player, ammo, resultStack.getCount());
hook.shrinkAmmo(tool, entry, holder, ammo, resultStack.getCount());
break;
}
}
Expand All @@ -140,7 +140,7 @@ static ItemStack findAmmo(IToolStackView tool, ItemStack bow, Player player, Pre
}
// make a copy of the result, up to the desired size
resultStack = standardAmmo.split(projectilesDesired);
if (standardAmmo.isEmpty()) {
if (standardAmmo.isEmpty() && holder instanceof Player player) {
player.getInventory().removeItem(standardAmmo);
}
}
Expand All @@ -159,17 +159,17 @@ static ItemStack findAmmo(IToolStackView tool, ItemStack bow, Player player, Pre
do {
// if standard ammo is empty, try finding a matching stack again
if (standardAmmo.isEmpty()) {
standardAmmo = findMatchingAmmo(bow, player, predicate);
standardAmmo = findMatchingAmmo(bow, holder, predicate);
}
// next, try asking modifiers if they have anything new again
int needed = projectilesDesired - resultStack.getCount();
for (ModifierEntry entry : tool.getModifierList()) {
BowAmmoModifierHook hook = entry.getHook(ModifierHooks.BOW_AMMO);
ItemStack ammo = hook.findAmmo(tool, entry, player, standardAmmo, predicate);
ItemStack ammo = hook.findAmmo(tool, entry, holder, standardAmmo, predicate);
if (!ammo.isEmpty()) {
// consume as much of the stack as we need then continue, loop condition will stop if we are now done
int gained = Math.min(needed, ammo.getCount());
hook.shrinkAmmo(tool, entry, player, ammo, gained);
hook.shrinkAmmo(tool, entry, holder, ammo, gained);
resultStack.grow(gained);
continue hasEnough;
}
Expand All @@ -183,7 +183,10 @@ static ItemStack findAmmo(IToolStackView tool, ItemStack bow, Player player, Pre
if (needed > standardAmmo.getCount()) {
// consume the whole stack
resultStack.grow(standardAmmo.getCount());
player.getInventory().removeItem(standardAmmo);
standardAmmo.setCount(0);
if (holder instanceof Player player) {
player.getInventory().removeItem(standardAmmo);
}
standardAmmo = ItemStack.EMPTY;
} else {
// found what we need, we are done
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,28 @@
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.stats.Stats;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.AbstractArrow;
import net.minecraft.world.item.ArrowItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.ProjectileWeaponItem;
import net.minecraft.world.item.UseAnim;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.ToolActions;
import net.minecraftforge.event.ForgeEventFactory;
import org.joml.Vector3f;
import slimeknights.tconstruct.common.Sounds;
import slimeknights.tconstruct.library.modifiers.ModifierEntry;
import slimeknights.tconstruct.library.modifiers.ModifierHooks;
import slimeknights.tconstruct.library.modifiers.hook.build.ConditionalStatModifierHook;
import slimeknights.tconstruct.library.modifiers.hook.interaction.GeneralInteractionModifierHook;
import slimeknights.tconstruct.library.modifiers.hook.ranged.BowAmmoModifierHook;
import slimeknights.tconstruct.library.tools.capability.EntityModifierCapability;
import slimeknights.tconstruct.library.tools.capability.PersistentDataCapability;
import slimeknights.tconstruct.library.tools.definition.ToolDefinition;
import slimeknights.tconstruct.library.tools.helper.ModifierUtil;
import slimeknights.tconstruct.library.tools.helper.ToolDamageUtil;
import slimeknights.tconstruct.library.tools.nbt.ModifierNBT;
import slimeknights.tconstruct.library.tools.nbt.ModDataNBT;
import slimeknights.tconstruct.library.tools.nbt.ToolStack;
import slimeknights.tconstruct.library.tools.stat.ToolStats;
import slimeknights.tconstruct.tools.modifiers.ability.interaction.BlockingModifier;
Expand Down Expand Up @@ -95,6 +91,17 @@ public InteractionResultHolder<ItemStack> use(Level level, Player player, Intera
return InteractionResultHolder.consume(bow);
}

@Override
public void playShotSound(LivingEntity user, float charge, float angle, RandomSource random) {
user.level().playSound(null, user.getX(), user.getY(), user.getZ(), SoundEvents.ARROW_SHOOT, SoundSource.PLAYERS, 1.0F,
1.0F / (random.nextFloat() * 0.4F + 1.2F) + charge * 0.5F + (angle / 10f));
}

@Override
public Vector3f modifyShootAngle(LivingEntity user, Vec3 target, float angle) {
return target.xRot(angle * Mth.DEG_TO_RAD).toVector3f();
}

@Override
public void releaseUsing(ItemStack bow, Level level, LivingEntity living, int timeLeft) {
// clear zoom regardless, does not matter if the tool broke, we should not be zooming
Expand Down Expand Up @@ -130,8 +137,9 @@ public void releaseUsing(ItemStack bow, Level level, LivingEntity living, int ti
// calculate arrow power
float charge = GeneralInteractionModifierHook.getToolCharge(tool, chargeTime);
tool.getPersistentData().remove(KEY_DRAWTIME);
float velocity = ConditionalStatModifierHook.getModifiedStat(tool, living, ToolStats.VELOCITY);
float power = charge * velocity;

// may we just use charge to test if we can fire the arrows?
float power = charge * ConditionalStatModifierHook.getModifiedStat(tool, living, ToolStats.VELOCITY);
if (power < 0.1f) {
return;
}
Expand All @@ -144,45 +152,11 @@ public void releaseUsing(ItemStack bow, Level level, LivingEntity living, int ti
if (ammo.isEmpty()) {
ammo = new ItemStack(Items.ARROW);
}
int damage = shootProjectiles(player, tool, ammo, player.getViewVector(1.0f), charge, 3, 1, creative);
if (!creative) {
ToolDamageUtil.damageAnimated(tool, damage, player, player.getUsedItemHand());

// prepare the arrows
ArrowItem arrowItem = ammo.getItem() instanceof ArrowItem arrow ? arrow : (ArrowItem)Items.ARROW;
float inaccuracy = ModifierUtil.getInaccuracy(tool, living);
float startAngle = getAngleStart(ammo.getCount());
int primaryIndex = ammo.getCount() / 2;
for (int arrowIndex = 0; arrowIndex < ammo.getCount(); arrowIndex++) {
AbstractArrow arrow = arrowItem.createArrow(level, ammo, player);
float angle = startAngle + (10 * arrowIndex);
arrow.shootFromRotation(player, player.getXRot() + angle, player.getYRot(), 0, power * 3.0F, inaccuracy);
if (charge == 1.0F) {
arrow.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
// calculate it just once as all four arrows are the same item, they should have the same damage
float baseArrowDamage = (float)(arrow.getBaseDamage() - 2 + tool.getStats().get(ToolStats.PROJECTILE_DAMAGE));
arrow.setBaseDamage(ConditionalStatModifierHook.getModifiedStat(tool, player, ToolStats.PROJECTILE_DAMAGE, baseArrowDamage));

// just store all modifiers on the tool for simplicity
ModifierNBT modifiers = tool.getModifiers();
arrow.getCapability(EntityModifierCapability.CAPABILITY).ifPresent(cap -> cap.setModifiers(modifiers));

// fetch the persistent data for the arrow as modifiers may want to store data
ModDataNBT arrowData = PersistentDataCapability.getOrWarn(arrow);

// if infinite, skip pickup
if (creative) {
arrow.pickup = AbstractArrow.Pickup.CREATIVE_ONLY;
}

// let modifiers such as fiery and punch set properties
for (ModifierEntry entry : modifiers.getModifiers()) {
entry.getHook(ModifierHooks.PROJECTILE_LAUNCH).onProjectileLaunch(tool, entry, living, arrow, arrow, arrowData, arrowIndex == primaryIndex);
}
level.addFreshEntity(arrow);
level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.ARROW_SHOOT, SoundSource.PLAYERS, 1.0F, 1.0F / (level.getRandom().nextFloat() * 0.4F + 1.2F) + charge * 0.5F + (angle / 10f));
}
ToolDamageUtil.damageAnimated(tool, ammo.getCount(), player, player.getUsedItemHand());
}

// stats and sounds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.AbstractArrow;
import net.minecraft.world.entity.projectile.AbstractArrow.Pickup;
import net.minecraft.world.entity.projectile.FireworkRocketEntity;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.item.ArrowItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.TooltipFlag;
Expand All @@ -33,21 +31,14 @@
import org.joml.Vector3f;
import slimeknights.mantle.client.TooltipKey;
import slimeknights.tconstruct.TConstruct;
import slimeknights.tconstruct.library.modifiers.ModifierEntry;
import slimeknights.tconstruct.library.modifiers.ModifierHooks;
import slimeknights.tconstruct.library.modifiers.hook.build.ConditionalStatModifierHook;
import slimeknights.tconstruct.library.modifiers.hook.interaction.GeneralInteractionModifierHook;
import slimeknights.tconstruct.library.modifiers.hook.ranged.BowAmmoModifierHook;
import slimeknights.tconstruct.library.tools.capability.EntityModifierCapability;
import slimeknights.tconstruct.library.tools.capability.PersistentDataCapability;
import slimeknights.tconstruct.library.tools.definition.ToolDefinition;
import slimeknights.tconstruct.library.tools.helper.ModifierUtil;
import slimeknights.tconstruct.library.tools.helper.ToolDamageUtil;
import slimeknights.tconstruct.library.tools.nbt.IToolStackView;
import slimeknights.tconstruct.library.tools.nbt.ModDataNBT;
import slimeknights.tconstruct.library.tools.nbt.ModifierNBT;
import slimeknights.tconstruct.library.tools.nbt.ToolStack;
import slimeknights.tconstruct.library.tools.stat.ToolStats;
import slimeknights.tconstruct.tools.TinkerModifiers;
import slimeknights.tconstruct.tools.modifiers.ability.interaction.BlockingModifier;
import slimeknights.tconstruct.tools.modifiers.upgrades.ranged.ScopeModifier;
Expand Down Expand Up @@ -101,12 +92,33 @@ public boolean useOnRelease(ItemStack stack) {

/* Arrow launching */

/** Gets the arrow pitch */
private static float getRandomShotPitch(float angle, RandomSource pRandom) {
if (angle == 0) {
return 1.0f;
@Override
public void playShotSound(LivingEntity user, float charge, float angle, RandomSource pRandom) {
float pitch = angle == 0 ? 1 : 1.0F / (pRandom.nextFloat() * 0.5F + 1.8F) + 0.53f + (angle / 10f);
user.level().playSound(null, user.getX(), user.getY(), user.getZ(),
SoundEvents.CROSSBOW_SHOOT, SoundSource.PLAYERS, 1.0F, pitch);
}

@Override
public ProjectileData createProjectile(Level level, LivingEntity user, ItemStack ammo) {
if (ammo.is(Items.FIREWORK_ROCKET)) {
// TODO: don't hardcode fireworks, perhaps use a map or a JSON behavior list
Projectile projectile = new FireworkRocketEntity(level, ammo, user, user.getX(), user.getEyeY() - 0.15f, user.getZ(), true);
return new ProjectileData(projectile, 0.5f, 3);
} else {
ProjectileData info = super.createProjectile(level, user, ammo);
if (info.projectile() instanceof AbstractArrow arrow) {
arrow.setSoundEvent(SoundEvents.CROSSBOW_HIT);
arrow.setShotFromCrossbow(true);
}
return info;
}
return 1.0F / (pRandom.nextFloat() * 0.5F + 1.8F) + 0.53f + (angle / 10f);
}

@Override
public Vector3f modifyShootAngle(LivingEntity user, Vec3 target, float angle) {
Vec3 upVector = user.getUpVector(1.0f);
return target.toVector3f().rotate((new Quaternionf()).setAngleAxis(angle * Math.PI / 180F, upVector.x, upVector.y, upVector.z));
}

@Override
Expand Down Expand Up @@ -171,82 +183,20 @@ public InteractionResultHolder<ItemStack> use(Level level, Player player, Intera
* @param hand Hand fired from
* @param heldAmmo Ammo used to fire, should be non-empty
*/
public static void fireCrossbow(IToolStackView tool, Player player, InteractionHand hand, CompoundTag heldAmmo) {
// ammo already loaded? time to fire
public void fireCrossbow(IToolStackView tool, Player player, InteractionHand hand, CompoundTag heldAmmo) {
Level level = player.level();
boolean creative = player.getAbilities().instabuild;
if (!level.isClientSide) {
// shoot the projectile
int damage = 0;

// don't need to calculate these multiple times
float velocity = ConditionalStatModifierHook.getModifiedStat(tool, player, ToolStats.VELOCITY);
float inaccuracy = ModifierUtil.getInaccuracy(tool, player);
boolean creative = player.getAbilities().instabuild;

// the ammo has a stack size that may be greater than 1 (meaning multishot)
// when creating the ammo stacks, we use split, so its getting smaller each time
ItemStack ammo = ItemStack.of(heldAmmo);
float startAngle = getAngleStart(ammo.getCount());
int primaryIndex = ammo.getCount() / 2;
for (int arrowIndex = 0; arrowIndex < ammo.getCount(); arrowIndex++) {
// setup projectile
AbstractArrow arrow = null;
Projectile projectile;
float speed;
if (ammo.is(Items.FIREWORK_ROCKET)) {
// TODO: don't hardcode fireworks, perhaps use a map or a JSON behavior list
projectile = new FireworkRocketEntity(level, ammo, player, player.getX(), player.getEyeY() - 0.15f, player.getZ(), true);
speed = 1.5f;
damage += 3;
} else {
ArrowItem arrowItem = ammo.getItem() instanceof ArrowItem a ? a : (ArrowItem)Items.ARROW;
arrow = arrowItem.createArrow(level, ammo, player);
projectile = arrow;
arrow.setCritArrow(true);
arrow.setSoundEvent(SoundEvents.CROSSBOW_HIT);
arrow.setShotFromCrossbow(true);
speed = 3f;
damage += 1;

// 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)(arrow.getBaseDamage() - 2 + tool.getStats().get(ToolStats.PROJECTILE_DAMAGE));
arrow.setBaseDamage(ConditionalStatModifierHook.getModifiedStat(tool, player, ToolStats.PROJECTILE_DAMAGE, baseArrowDamage));

// fortunately, don't need to deal with vanilla infinity here, our infinity was dealt with during loading
if (creative) {
arrow.pickup = Pickup.CREATIVE_ONLY;
}
}

// TODO: can we get piglins/illagers to use our crossbow?

// setup projectile
Vec3 upVector = player.getUpVector(1.0f);
float angle = startAngle + (10 * arrowIndex);
Vector3f targetVector = player.getViewVector(1.0f).toVector3f().rotate((new Quaternionf()).setAngleAxis(angle * Math.PI / 180F, upVector.x, upVector.y, upVector.z));
projectile.shoot(targetVector.x(), targetVector.y(), targetVector.z(), velocity * speed, inaccuracy);

// add modifiers to the projectile, will let us use them on impact
ModifierNBT modifiers = tool.getModifiers();
projectile.getCapability(EntityModifierCapability.CAPABILITY).ifPresent(cap -> cap.setModifiers(modifiers));

// fetch the persistent data for the arrow as modifiers may want to store data
ModDataNBT projectileData = PersistentDataCapability.getOrWarn(projectile);

// let modifiers set properties
for (ModifierEntry entry : modifiers.getModifiers()) {
entry.getHook(ModifierHooks.PROJECTILE_LAUNCH).onProjectileLaunch(tool, entry, player, projectile, arrow, projectileData, arrowIndex == primaryIndex);
}

// finally, fire the projectile
level.addFreshEntity(projectile);
level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.CROSSBOW_SHOOT, SoundSource.PLAYERS, 1.0F, getRandomShotPitch(angle, player.getRandom()));
// fire arrows, damage bow, play sound
int damage = shootProjectiles(player, tool, ammo, player.getViewVector(1.0f), 1, 3.15f, 1, creative);
if (!creative) {
ToolDamageUtil.damageAnimated(tool, damage, player, hand);
}

// clear the ammo, damage the bow
// clear the ammo
tool.getPersistentData().remove(KEY_CROSSBOW_AMMO);
ToolDamageUtil.damageAnimated(tool, damage, player, hand);

// stats
if (player instanceof ServerPlayer serverPlayer) {
CriteriaTriggers.SHOT_CROSSBOW.trigger(serverPlayer, player.getItemInHand(hand));
Expand Down
Loading