Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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 user User to search
* @return Found ammo
*/
static ItemStack findAmmo(IToolStackView tool, ItemStack bow, Player player, Predicate<ItemStack> predicate) {
static ItemStack findAmmo(IToolStackView tool, ItemStack bow, LivingEntity user, Predicate<ItemStack> predicate) {
Comment thread
lcy0x1 marked this conversation as resolved.
Outdated
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 = user.level();
boolean creative = user instanceof Player player && player.getAbilities().instabuild || level.isClientSide;

// first search, find what ammo type we want
ItemStack standardAmmo = player.getProjectile(bow);
ItemStack standardAmmo = user.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, user, 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, user, 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() && user 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, user, 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, user, 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, user, 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 (user instanceof Player player) {
player.getInventory().removeItem(standardAmmo);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to discuss this more; how is a non-player expected to remove the ammo from their inventory? Or are we just assuming they don't care about cleaning up empty stacks?

I know most mobs do infinite ammo, but imagine a dispenser like shooter.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say the mob needs to be able to clean up empty stacks themselves. What harm will it do to not remove empty stacks? They are empty and isEmpty() returns true

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly just memory waste, which is why vanilla typically does it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is a realistic method to clear mob inventory, as each mob would have different implementations. Sounds like it doesn't matter that much.

Though if you think this part should be part of the API, then maybe LauncherUserInfo would be a place to add it.

standardAmmo = ItemStack.EMPTY;
} else {
// found what we need, we are done
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package slimeknights.tconstruct.library.tools.item.ranged;

import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;

public interface LauncherUserInfo {
Comment thread
lcy0x1 marked this conversation as resolved.
Outdated

static LauncherUserInfo playerLike(LivingEntity user, float speed) {
return new PlayerLike(user, speed);
}

LivingEntity user();

default Level level() {
return user().level();
}

/**
* regular arrow speed at full charge. 3 for player with bow. 1.6 for skeletons. 3.15 for crossbows.
*/
float speedFactor();

/**
* shoot inaccuracy factor. 1 for player, and hostile mobs use a complex formula involving difficulty.
*/
float inaccuracyFactor();

/**
* Whether to make the arrow marked as no-pickup. True for infinite arrows or hostile mob arrows.
*/
boolean infinite();

/**
* Whether to damage weapon. True for hostile mobs.
*/
boolean damageWeapon();

record PlayerLike(LivingEntity user, float speedFactor) implements LauncherUserInfo {

@Override
public float inaccuracyFactor() {
return 1;
}

@Override
public boolean infinite() {
return user instanceof Player player && player.getAbilities().instabuild;
}

@Override
public boolean damageWeapon() {
return !infinite();
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,27 @@
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 +90,16 @@ public InteractionResultHolder<ItemStack> use(Level level, Player player, Intera
return InteractionResultHolder.consume(bow);
}

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 +135,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 +150,7 @@ public void releaseUsing(ItemStack bow, Level level, LivingEntity living, int ti
if (ammo.isEmpty()) {
ammo = new ItemStack(Items.ARROW);
}

// 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());
shootProjectiles(LauncherUserInfo.playerLike(player, 3f), tool, player.getUsedItemHand(), ammo, player.getViewVector(1.0f), charge);
}

// stats and sounds
Expand Down
Loading