diff --git a/README.md b/README.md index 5c9efb9b8b..e9a05a3a2f 100644 --- a/README.md +++ b/README.md @@ -12,4 +12,7 @@ Wires, transformers, capacitors! - Sound for the excavator ore conveyor is by tosha73 on [freesound.org](https://freesound.org/people/tosha73/sounds/584592/), licensed under CC BY 4.0 - Sound for the electromagnet is by _MC5_ on [freesound.org](https://freesound.org/people/_MC5_/sounds/672082/), licensed under CC BY 4.0 - Assets for several blocks are derived from @stfwi assets in [Engineer's Decor](https://github.com/stfwi/engineers-decor), licensed under MIT - - These assets include clinker bricks, slag bricks, and grit sand \ No newline at end of file + - These assets include clinker bricks, slag bricks, and grit sand +- Sound for the drill motor is by DrinkingWindGames on [freesound.org](https://freesound.org/people/DrinkingWindGames/sounds/463735/), licensed under CC BY 4.0 +- Sound for the buzzsaw motor is by Audionautics on [freesound.org](https://freesound.org/people/Audionautics/sounds/171652/), licensed under CC BY 3.0 +- Sound for the buzzsaw harvesting is by gecop on [freesound.org](https://freesound.org/people/gecop/sounds/609919/), licensed under CC BY 4.0 \ No newline at end of file diff --git a/src/api/java/blusunrize/immersiveengineering/api/tool/INoisyTool.java b/src/api/java/blusunrize/immersiveengineering/api/tool/INoisyTool.java new file mode 100644 index 0000000000..d78f33c64c --- /dev/null +++ b/src/api/java/blusunrize/immersiveengineering/api/tool/INoisyTool.java @@ -0,0 +1,72 @@ +/* + * BluSunrize + * Copyright (c) 2024 + * + * This code is licensed under "Blu's License of Common Sense" + * Details can be found in the license file in the root folder of this project + */ + +package blusunrize.immersiveengineering.api.tool; + +import net.minecraft.core.Holder; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.world.item.ItemStack; + +public interface INoisyTool +{ + static final float TEST_VOLUME_ADJUSTMENT = 1.0f; //temporary measure, remove after settling on a volume for the PR and re-adjusting the sounds themselves + + Holder getIdleSound(ItemStack stack); + + Holder getBusySound(ItemStack stack); + + /** + * Due to lacking information on sound duration, the duration is hard coded. Any Fading sounds need to be more than 1.0s in duration. + * The sound cuts off after 1.0s, but a little bit of excess duration (~0.01s) is required for the noisy tool sound stage machine to work correctly + * + * @param stack + * @return fading sound + */ + Holder getFadingSound(ItemStack stack); + + /** + * Due to lacking information on sound duration, the duration is hard coded. Any Attack sounds need to be more than 0.35s in duration. + * The sound cuts off after 0.35s, but a little bit of excess duration (~0.01s) is required for the noisy tool sound stage machine to work correctly + * + * @param stack + * @return attack sound + */ + Holder getAttackSound(ItemStack stack); + + Holder getHarvestSound(ItemStack stack); + + boolean ableToMakeNoise(ItemStack stack); + + static boolean isAbleNoisyTool(ItemStack stack) + { + return stack.getItem() instanceof INoisyTool noisyTool&&noisyTool.ableToMakeNoise(stack); + } + + /** + * When an ItemStack gets modified server side (i.e. takes damage, changes tags (i.e. uses fuel), etc.), it creates a new ItemStack on the client side. + * There is no unreasonably involved way to check if the new ItemStack is actually just the old ItemStack, but modified. + * So this for these cases, this default implementation checks the next best thing: Item equality and sound equality. + * + * It is encouraged to override this with a simpler check. + * + * This check also assumes, that it has already been checked and confirmed, that the stacks are not identical + * + * @param mainStack + * @param otherStack + * @return true if stacks are considered the same stack. By default: if stacks produce the same sounds. + */ + default boolean noisySameStack(ItemStack mainStack, ItemStack otherStack) + { + return mainStack.getItem() instanceof INoisyTool noisyTool&&noisyTool.equals(otherStack.getItem()) + &&noisyTool.getIdleSound(mainStack).equals(noisyTool.getIdleSound(otherStack)) + &&noisyTool.getBusySound(mainStack).equals(noisyTool.getBusySound(otherStack)) + &&noisyTool.getFadingSound(mainStack).equals(noisyTool.getFadingSound(otherStack)) + &&noisyTool.getAttackSound(mainStack).equals(noisyTool.getAttackSound(otherStack)) + &&noisyTool.getHarvestSound(mainStack).equals(noisyTool.getHarvestSound(otherStack)); + } +} diff --git a/src/generated/resources/.cache/b42e1ae3eb4defee251f4fded9b88ae0c946b758 b/src/generated/resources/.cache/b42e1ae3eb4defee251f4fded9b88ae0c946b758 index e811d213a2..8354feef7f 100644 --- a/src/generated/resources/.cache/b42e1ae3eb4defee251f4fded9b88ae0c946b758 +++ b/src/generated/resources/.cache/b42e1ae3eb4defee251f4fded9b88ae0c946b758 @@ -1,2 +1,2 @@ -// 1.21.1 2024-08-25T13:16:39.344823415 Languages: en_us for mod: immersiveengineering -19ac3c8635b9828efa9843eff95ff4c2666b644c assets/immersiveengineering/lang/en_us.json +// 1.21.1 2024-12-12T18:44:04.399770494 Languages: en_us for mod: immersiveengineering +6bcea5f396eab517d13c955ac34f7682aeaf606a assets/immersiveengineering/lang/en_us.json diff --git a/src/generated/resources/assets/immersiveengineering/lang/en_us.json b/src/generated/resources/assets/immersiveengineering/lang/en_us.json index fd68b19477..37986137ac 100644 --- a/src/generated/resources/assets/immersiveengineering/lang/en_us.json +++ b/src/generated/resources/assets/immersiveengineering/lang/en_us.json @@ -1845,12 +1845,19 @@ "subtitle.immersiveengineering.birthdayParty": "Yaaay, party!", "subtitle.immersiveengineering.bottling": "Bottling Machine fills a container", "subtitle.immersiveengineering.buzzer": "Buzzer warning", + "subtitle.immersiveengineering.buzzsaw_attack": "Buzzsaw revs up high", + "subtitle.immersiveengineering.buzzsaw_harvest_grinding": "Grinding", + "subtitle.immersiveengineering.buzzsaw_harvest_sawing": "Sawing", + "subtitle.immersiveengineering.buzzsaw_motor": "Buzzsaw motor running", "subtitle.immersiveengineering.chargeFast": "Charging capacitors", "subtitle.immersiveengineering.chargeSlow": "Charging capacitors", "subtitle.immersiveengineering.chute": "Item clunks through chute", "subtitle.immersiveengineering.crusher": "Crusher is decimating ore", "subtitle.immersiveengineering.dieselGenerator": "Diesel Generator is being loud and obnoxious", "subtitle.immersiveengineering.direSwitch": "Direwolf20 imitating a knifeswitch", + "subtitle.immersiveengineering.drill_attack": "Drill revs up high", + "subtitle.immersiveengineering.drill_harvest": "Drilling", + "subtitle.immersiveengineering.drill_motor": "Drill motor running", "subtitle.immersiveengineering.electromagnet": "Electromagnet hums", "subtitle.immersiveengineering.fermenter": "Fermenter whirrs", "subtitle.immersiveengineering.glider": "Glider taking damage", diff --git a/src/main/java/blusunrize/immersiveengineering/ImmersiveEngineering.java b/src/main/java/blusunrize/immersiveengineering/ImmersiveEngineering.java index b16b0193e7..04398f6914 100644 --- a/src/main/java/blusunrize/immersiveengineering/ImmersiveEngineering.java +++ b/src/main/java/blusunrize/immersiveengineering/ImmersiveEngineering.java @@ -227,6 +227,8 @@ private void setupNetwork(RegisterPayloadHandlersEvent ev) registerMessage(registrar, MessageOpenManual.ID, MessageOpenManual.CODEC, CLIENTBOUND); registerMessage(registrar, MessagePowerpackAntenna.ID, MessagePowerpackAntenna.CODEC, CLIENTBOUND); // registerMessage(registrar, MessageCrateName.ID, MessageCrateName::new, SERVERBOUND); + registerMessage(registrar, MessageNoisyToolHarvestUpdate.ID, MessageNoisyToolHarvestUpdate.CODEC, CLIENTBOUND); + registerMessage(registrar, MessageNoisyToolAttack.ID, MessageNoisyToolAttack.CODEC, CLIENTBOUND); } private void registerMessage( diff --git a/src/main/java/blusunrize/immersiveengineering/common/items/BuzzsawItem.java b/src/main/java/blusunrize/immersiveengineering/common/items/BuzzsawItem.java index 911183c3cc..8ed509a244 100644 --- a/src/main/java/blusunrize/immersiveengineering/common/items/BuzzsawItem.java +++ b/src/main/java/blusunrize/immersiveengineering/common/items/BuzzsawItem.java @@ -20,6 +20,8 @@ import blusunrize.immersiveengineering.common.register.IEDataComponents; import blusunrize.immersiveengineering.common.register.IEItems.Misc; import blusunrize.immersiveengineering.common.register.IEItems.Tools; +import blusunrize.immersiveengineering.common.util.IESounds; +//import blusunrize.immersiveengineering.common.util.ItemNBTHelper; import blusunrize.immersiveengineering.common.util.Utils; import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; @@ -287,6 +289,51 @@ public int getHeadDamage(ItemStack stack) return !sawblade.isEmpty()?sawblade.getDamageValue(): 0; } + @Override + public Holder getIdleSound(ItemStack stack) + { + return IESounds.buzzsaw_idle; + } + + @Override + public Holder getBusySound(ItemStack stack) + { + return IESounds.buzzsaw_busy; + } + + @Override + public Holder getFadingSound(ItemStack stack) + { + return IESounds.buzzsaw_fade; + } + + @Override + public Holder getAttackSound(ItemStack stack) + { + return IESounds.buzzsaw_attack; + } + + @Override + public Holder getHarvestSound(ItemStack stack) + { + Item headitem = getHead(stack).getItem(); + if(headitem instanceof GrindingDiskItem||headitem instanceof RockcutterItem) + return IESounds.buzzsaw_harvest_grind; + return IESounds.buzzsaw_harvest_saw; + } + + @Override + public boolean ableToMakeNoise(ItemStack stack) + { + return canToolBeUsed(stack); + } + + @Override + public boolean noisySameStack(ItemStack mainStack, ItemStack otherStack) + { + return mainStack.getItem() instanceof BuzzsawItem buzzsawItem&&buzzsawItem.equals(otherStack.getItem())&&getHead(mainStack).getItem().equals(getHead(otherStack).getItem()); + } + @Override public boolean mineBlock(ItemStack stack, Level world, BlockState state, BlockPos pos, LivingEntity living) { diff --git a/src/main/java/blusunrize/immersiveengineering/common/items/DieselToolItem.java b/src/main/java/blusunrize/immersiveengineering/common/items/DieselToolItem.java index 4c631491df..26009f8916 100644 --- a/src/main/java/blusunrize/immersiveengineering/common/items/DieselToolItem.java +++ b/src/main/java/blusunrize/immersiveengineering/common/items/DieselToolItem.java @@ -15,15 +15,18 @@ import blusunrize.immersiveengineering.api.shader.ShaderRegistry; import blusunrize.immersiveengineering.api.shader.ShaderRegistry.ShaderAndCase; import blusunrize.immersiveengineering.api.tool.upgrade.UpgradeEffect; +import blusunrize.immersiveengineering.api.tool.INoisyTool; import blusunrize.immersiveengineering.common.fluids.IEItemFluidHandler; import blusunrize.immersiveengineering.common.items.IEItemInterfaces.IAdvancedFluidItem; import blusunrize.immersiveengineering.common.register.IEDataComponents; import com.google.common.base.Preconditions; import net.minecraft.core.BlockPos; import net.minecraft.core.RegistryAccess; +import net.minecraft.core.Holder; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.EquipmentSlotGroup; +import net.minecraft.sounds.SoundEvent; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.ai.attributes.AttributeModifier; import net.minecraft.world.entity.ai.attributes.AttributeModifier.Operation; @@ -50,7 +53,7 @@ import javax.annotation.Nullable; import java.util.*; -public abstract class DieselToolItem extends UpgradeableToolItem implements IAdvancedFluidItem +public abstract class DieselToolItem extends UpgradeableToolItem implements IAdvancedFluidItem, INoisyTool { protected static final int CAPACITY = 2*FluidType.BUCKET_VOLUME; @@ -239,4 +242,22 @@ protected void consumeDurability( public abstract int getMaxHeadDamage(ItemStack stack); public abstract int getHeadDamage(ItemStack stack); + + @Override + public abstract Holder getIdleSound(ItemStack stack); + + @Override + public abstract Holder getBusySound(ItemStack stack); + + @Override + public abstract Holder getFadingSound(ItemStack stack); + + @Override + public abstract Holder getAttackSound(ItemStack stack); + + @Override + public abstract Holder getHarvestSound(ItemStack stack); + + @Override + public abstract boolean ableToMakeNoise(ItemStack stack); } diff --git a/src/main/java/blusunrize/immersiveengineering/common/items/DrillItem.java b/src/main/java/blusunrize/immersiveengineering/common/items/DrillItem.java index 3b4840a144..9d53fdd136 100644 --- a/src/main/java/blusunrize/immersiveengineering/common/items/DrillItem.java +++ b/src/main/java/blusunrize/immersiveengineering/common/items/DrillItem.java @@ -15,16 +15,19 @@ import blusunrize.immersiveengineering.api.tool.upgrade.UpgradeEffect; import blusunrize.immersiveengineering.common.fluids.IEItemFluidHandler; import blusunrize.immersiveengineering.common.gui.IESlot; +import blusunrize.immersiveengineering.common.util.IESounds; import blusunrize.immersiveengineering.common.util.Utils; import com.google.common.collect.ImmutableList; import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; import net.minecraft.core.RegistryAccess; import net.minecraft.core.component.DataComponents; import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundEvent; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResultHolder; import net.minecraft.world.entity.LivingEntity; @@ -230,6 +233,49 @@ protected void damageHead(ItemStack head, int amount, LivingEntity living) ((IDrillHead)head.getItem()).damageHead(head, amount); } + @Override + public Holder getIdleSound(ItemStack stack) + { + return IESounds.drill_idle; + } + + @Override + public Holder getBusySound(ItemStack stack) + { + return IESounds.drill_busy; + } + + @Override + public Holder getFadingSound(ItemStack stack) + { + return IESounds.drill_fade; + } + + + @Override + public Holder getAttackSound(ItemStack stack) + { + return IESounds.drill_attack; + } + + @Override + public Holder getHarvestSound(ItemStack stack) + { + return IESounds.drill_harvest; + } + + @Override + public boolean ableToMakeNoise(ItemStack stack) + { + return canToolBeUsed(stack); + } + + @Override + public boolean noisySameStack(ItemStack mainStack, ItemStack otherStack) + { + return mainStack.getItem() instanceof DrillItem drillItem&&drillItem.equals(otherStack.getItem()); + } + @Override public Tier getHarvestLevel(ItemStack stack, @Nullable Player player) { diff --git a/src/main/java/blusunrize/immersiveengineering/common/network/MessageNoisyToolAttack.java b/src/main/java/blusunrize/immersiveengineering/common/network/MessageNoisyToolAttack.java new file mode 100644 index 0000000000..4eb1721193 --- /dev/null +++ b/src/main/java/blusunrize/immersiveengineering/common/network/MessageNoisyToolAttack.java @@ -0,0 +1,52 @@ +/* + * BluSunrize + * Copyright (c) 2024 + * + * This code is licensed under "Blu's License of Common Sense" + * Details can be found in the license file in the root folder of this project + */ + +package blusunrize.immersiveengineering.common.network; + +import blusunrize.immersiveengineering.ImmersiveEngineering; +import blusunrize.immersiveengineering.common.util.sound.NoisyToolSoundHandler; +import io.netty.buffer.ByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.level.Level; +import net.neoforged.neoforge.network.handling.IPayloadContext; + +public record MessageNoisyToolAttack(int holderID) implements IMessage +{ + public static final Type ID = IMessage.createType("noisy_tool_attack"); + public static final StreamCodec CODEC = ByteBufCodecs.INT + .map(MessageNoisyToolAttack::new, MessageNoisyToolAttack::holderID); + + public MessageNoisyToolAttack(LivingEntity holder) + { + this(holder.getId()); + } + + @Override + public void process(IPayloadContext context) + { + context.enqueueWork(() -> { + Level world = ImmersiveEngineering.proxy.getClientWorld(); + if(world!=null) + { + Entity entity = world.getEntity(holderID); + if(entity instanceof LivingEntity holder) + NoisyToolSoundHandler.handleAttack(holder); + } + }); + } + + @Override + public Type type() + { + return ID; + } +} diff --git a/src/main/java/blusunrize/immersiveengineering/common/network/MessageNoisyToolHarvestUpdate.java b/src/main/java/blusunrize/immersiveengineering/common/network/MessageNoisyToolHarvestUpdate.java new file mode 100644 index 0000000000..79ce196b30 --- /dev/null +++ b/src/main/java/blusunrize/immersiveengineering/common/network/MessageNoisyToolHarvestUpdate.java @@ -0,0 +1,58 @@ +/* + * BluSunrize + * Copyright (c) 2024 + * + * This code is licensed under "Blu's License of Common Sense" + * Details can be found in the license file in the root folder of this project + */ + +package blusunrize.immersiveengineering.common.network; + +import blusunrize.immersiveengineering.ImmersiveEngineering; +import blusunrize.immersiveengineering.common.util.sound.NoisyToolSoundHandler; +import io.netty.buffer.ByteBuf; +import net.minecraft.core.BlockPos; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.level.Level; +import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent.LeftClickBlock; +import net.neoforged.neoforge.network.handling.IPayloadContext; + +public record MessageNoisyToolHarvestUpdate(int holderID, byte actionOrdinal, BlockPos targetPos) implements IMessage +{ + public static final Type ID = IMessage.createType("noisy_tool_harvesting_update"); + public static final StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.INT, MessageNoisyToolHarvestUpdate::holderID, + ByteBufCodecs.BYTE, MessageNoisyToolHarvestUpdate::actionOrdinal, + BlockPos.STREAM_CODEC, MessageNoisyToolHarvestUpdate::targetPos, + MessageNoisyToolHarvestUpdate::new + ); + + public MessageNoisyToolHarvestUpdate(LivingEntity holder, LeftClickBlock.Action action, BlockPos targetPos) + { + this(holder.getId(), (byte)action.ordinal(), targetPos); + } + + @Override + public void process(IPayloadContext context) + { + context.enqueueWork(() -> { + Level world = ImmersiveEngineering.proxy.getClientWorld(); + if(world!=null) + { + Entity entity = world.getEntity(holderID); + if(entity instanceof LivingEntity holder) + NoisyToolSoundHandler.handleHarvestAction(holder, LeftClickBlock.Action.class.getEnumConstants()[actionOrdinal], targetPos); + } + }); + } + + @Override + public Type type() + { + return ID; + } +} diff --git a/src/main/java/blusunrize/immersiveengineering/common/util/IESounds.java b/src/main/java/blusunrize/immersiveengineering/common/util/IESounds.java index 7007a9b4b8..d2c75d1b19 100644 --- a/src/main/java/blusunrize/immersiveengineering/common/util/IESounds.java +++ b/src/main/java/blusunrize/immersiveengineering/common/util/IESounds.java @@ -62,6 +62,17 @@ public class IESounds public static final Holder siren = registerSound("siren"); public static final Holder klaxon = registerSound("klaxon"); public static final Holder buzzer = registerSound("buzzer"); + public static final Holder drill_idle = registerSound("drill_idle"); + public static final Holder drill_busy = registerSound("drill_busy"); + public static final Holder drill_fade = registerSound("drill_fade"); + public static final Holder drill_attack = registerSound("drill_attack"); + public static final Holder drill_harvest = registerSound("drill_harvest"); + public static final Holder buzzsaw_idle = registerSound("buzzsaw_idle"); + public static final Holder buzzsaw_busy = registerSound("buzzsaw_busy"); + public static final Holder buzzsaw_fade = registerSound("buzzsaw_fade"); + public static final Holder buzzsaw_attack = registerSound("buzzsaw_attack"); + public static final Holder buzzsaw_harvest_saw = registerSound("buzzsaw_harvest_saw"); + public static final Holder buzzsaw_harvest_grind = registerSound("buzzsaw_harvest_grind"); public static void init(IEventBus modBus) diff --git a/src/main/java/blusunrize/immersiveengineering/common/util/sound/NoisyToolSoundGroup.java b/src/main/java/blusunrize/immersiveengineering/common/util/sound/NoisyToolSoundGroup.java new file mode 100644 index 0000000000..8a491ed65b --- /dev/null +++ b/src/main/java/blusunrize/immersiveengineering/common/util/sound/NoisyToolSoundGroup.java @@ -0,0 +1,292 @@ +/* + * BluSunrize + * Copyright (c) 2024 + * + * This code is licensed under "Blu's License of Common Sense" + * Details can be found in the license file in the root folder of this project + */ + +package blusunrize.immersiveengineering.common.util.sound; + +import blusunrize.immersiveengineering.api.tool.INoisyTool; +import net.minecraft.client.Minecraft; +import net.minecraft.client.resources.sounds.AbstractTickableSoundInstance; +import net.minecraft.client.resources.sounds.SoundInstance; +import net.minecraft.core.BlockPos; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; + +import javax.annotation.Nullable; +import java.util.Objects; + +import static blusunrize.immersiveengineering.common.util.sound.NoisyToolSoundGroup.ToolMotorState.*; + +public class NoisyToolSoundGroup +{ + private static final int ATTACK_DURATION = 7-1; + private static final int FADE_DURATION = 20-1; + private final INoisyTool noisyToolItem; + private ItemStack noisyToolStack; + private final int hotbarSlot; + private final LivingEntity holder; + private final int harvestTimeoutGrace; + + private ToolMotorState currentMotorState = OFF; + @Nullable + private BlockPos currentTargetPos = null; + private long groupLastTickHelper = 0; + + public NoisyToolSoundGroup(ItemStack noisyToolStack, LivingEntity holder, int hotbarSlot) + { + this.noisyToolStack = noisyToolStack; + this.noisyToolItem = (INoisyTool)noisyToolStack.getItem(); + this.holder = holder; + this.hotbarSlot = hotbarSlot; + // shut off remote player's harvesting sound after 2 minutes + // grace for local player to deal with hard 5 tick delay between LeftClickBlock.START and .CLIENT_HOLD action + this.harvestTimeoutGrace = holder.equals(Minecraft.getInstance().player)?(5-1): 2400; + } + + private static void play(AbstractTickableSoundInstance soundInstance) + { + Minecraft.getInstance().getSoundManager().play(soundInstance); + } + + public INoisyTool getItem() + { + return noisyToolItem; + } + + public boolean checkItemValid(ItemStack handItemStack, int hotbarSlot) + { + if(this.hotbarSlot!=hotbarSlot||!checkItemMatch(handItemStack)||!noisyToolItem.ableToMakeNoise(handItemStack)) + { + switchMotorOnOff(false); + return false; + } + return true; + } + + private boolean checkItemMatch(ItemStack handItemStack) + { + if (noisyToolStack == handItemStack) + { + return true; + } + else if (noisyToolItem.noisySameStack(noisyToolStack, handItemStack)) + { + noisyToolStack = handItemStack; + return true; + } + return false; + } + + public enum ToolMotorState + { + OFF, + IDLE, + BUSY, + FADING, + ATTACK, + TRANSITION // transient helper state to bridge away from timed sounds associated with FADING and ATTACK, cause transitioning directly gives a tick of no sound with current implementation + } + + public boolean triggerAttack() + { + return switchMotorState(true, true, true); + } + + public boolean switchMotorOnOff(boolean motorOn) + { + return switchMotorState(motorOn, false, true); + } + + private boolean switchMotorState(boolean motorOn, boolean attack, boolean propagate) + { + ToolMotorState newMotorState = motorOn? + (attack? + ATTACK + : (currentTargetPos==null? + (currentMotorState==BUSY||currentMotorState==FADING? + FADING + : IDLE) + : BUSY)) + : OFF; + + if((currentMotorState==newMotorState||(propagate&¤tMotorState==ATTACK))&&newMotorState!=ATTACK) + return false; + + currentMotorState = newMotorState; + + switch(newMotorState) + { + case OFF: + if(propagate) + updateHarvestState(null, false); + break; + case IDLE: + play(new NoisyToolMotorSoundLooping(noisyToolItem.getIdleSound(noisyToolStack).value(), newMotorState)); + break; + case BUSY: + play(new NoisyToolMotorSoundLooping(noisyToolItem.getBusySound(noisyToolStack).value(), newMotorState)); + break; + case FADING: + play(new NoisyToolMotorSoundFinite(noisyToolItem.getFadingSound(noisyToolStack).value(), newMotorState, FADE_DURATION)); + break; + case ATTACK: + if(propagate) + updateHarvestState(null, false); // todo: check if this is really necessary. Not that it hurts. + play(new NoisyToolMotorSoundFinite(noisyToolItem.getAttackSound(noisyToolStack).value(), newMotorState, ATTACK_DURATION)); + break; + } + return true; + } + + public boolean updateHarvestState(@Nullable BlockPos newTargetPos) + { + return updateHarvestState(newTargetPos, true); + } + + private boolean updateHarvestState(@Nullable BlockPos newTargetPos, boolean propagate) + { + groupLastTickHelper = holder.level().getGameTime(); + if(currentMotorState!=BUSY) + groupLastTickHelper += harvestTimeoutGrace; //initial start needs grace period before stopping for remote AND local players + + if(Objects.equals(currentTargetPos, newTargetPos)) + return false; + + currentTargetPos = newTargetPos; + + if(newTargetPos!=null) + { + if(propagate) + switchMotorState(true, false, false); + play(new NoisyToolHarvestSound(newTargetPos)); + } + return true; + } + + private abstract class NoisyToolSound extends AbstractTickableSoundInstance + { + + protected NoisyToolSound(SoundEvent sound) + { + super(sound, SoundSource.NEUTRAL, SoundInstance.createUnseededRandom()); + } + + @Override + public boolean canStartSilent() + { + return true; + } + } + + private abstract class NoisyToolMotorSound extends NoisyToolSound + { + protected final ToolMotorState state; + + protected NoisyToolMotorSound(SoundEvent sound, ToolMotorState state) + { + super(sound); + + this.x = holder.getX(); + this.y = holder.getY()+0.5d; //todo: get tool coordinates? + this.z = holder.getZ(); + this.state = state; + this.volume = INoisyTool.TEST_VOLUME_ADJUSTMENT; //TODO: remove me + } + + protected void updateCoordinates() + { + this.x = holder.getX(); + this.y = holder.getY()+0.5d; + this.z = holder.getZ(); + } + + @Override + public void tick() + { + if(!isStopped()) + { + if(currentMotorState==state) + { + updateCoordinates(); + } + else + { + this.stop(); + } + } + } + } + + private class NoisyToolMotorSoundLooping extends NoisyToolMotorSound + { + + protected NoisyToolMotorSoundLooping(SoundEvent sound, ToolMotorState state) + { + super(sound, state); + + looping = true; + } + } + + private class NoisyToolMotorSoundFinite extends NoisyToolMotorSound + { + private final long thisSoundsLastTick; + + protected NoisyToolMotorSoundFinite(SoundEvent sound, ToolMotorState state, int duration) + { + super(sound, state); + this.thisSoundsLastTick = holder.level().getGameTime()+duration; + groupLastTickHelper = thisSoundsLastTick; + + } + + @Override + public void updateCoordinates() + { + super.updateCoordinates(); + + if(state==ATTACK||state==FADING) // only check if currentMotorState==state + { + if(thisSoundsLastTick!=NoisyToolSoundGroup.this.groupLastTickHelper) //second attack happened. I hate this. + this.stop(); + else if(holder.level().getGameTime() > thisSoundsLastTick) + currentMotorState = ToolMotorState.TRANSITION; + } + } + } + + private class NoisyToolHarvestSound extends NoisyToolSound + { + private final BlockPos targetBlockPos; + + protected NoisyToolHarvestSound(BlockPos targetBlockPos) + { + super(noisyToolItem.getHarvestSound(noisyToolStack).value());//ApiUtils.RANDOM_SOURCE); + + this.targetBlockPos = targetBlockPos; + this.x = targetBlockPos.getX()+0.5d; + this.y = targetBlockPos.getY()+0.5d; + this.z = targetBlockPos.getZ()+0.5d; + this.looping = true; + this.volume = INoisyTool.TEST_VOLUME_ADJUSTMENT; //TODO: remove me + } + + @Override + public void tick() + { + if(!isStopped()) + { + if(currentTargetPos!=null&&(holder.level().getGameTime() > groupLastTickHelper||holder.level().getBlockState(currentTargetPos).isAir())) // air check is slapped on addition, because of creative insta break + currentTargetPos = null; + if(currentTargetPos==null||!Objects.equals(targetBlockPos, currentTargetPos)) + this.stop(); + } + } + } +} diff --git a/src/main/java/blusunrize/immersiveengineering/common/util/sound/NoisyToolSoundHandler.java b/src/main/java/blusunrize/immersiveengineering/common/util/sound/NoisyToolSoundHandler.java new file mode 100644 index 0000000000..dd72333782 --- /dev/null +++ b/src/main/java/blusunrize/immersiveengineering/common/util/sound/NoisyToolSoundHandler.java @@ -0,0 +1,207 @@ +/* + * BluSunrize + * Copyright (c) 2023 + * + * This code is licensed under "Blu's License of Common Sense" + * Details can be found in the license file in the root folder of this project + */ + +package blusunrize.immersiveengineering.common.util.sound; + +import blusunrize.immersiveengineering.api.tool.INoisyTool; +import blusunrize.immersiveengineering.common.network.MessageNoisyToolAttack; +import blusunrize.immersiveengineering.common.network.MessageNoisyToolHarvestUpdate; +import net.minecraft.client.Minecraft; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.EquipmentSlot.Type; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.fml.common.EventBusSubscriber.Bus; +import net.neoforged.neoforge.event.entity.EntityLeaveLevelEvent; +import net.neoforged.neoforge.event.entity.living.LivingIncomingDamageEvent; +import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent.LeftClickBlock; +import net.neoforged.neoforge.event.level.LevelEvent.Unload; +import net.neoforged.neoforge.event.tick.EntityTickEvent.Post; +import net.neoforged.neoforge.network.PacketDistributor; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; + +import static blusunrize.immersiveengineering.ImmersiveEngineering.MODID; + +@EventBusSubscriber(modid = MODID, bus = Bus.GAME) +public class NoisyToolSoundHandler +{ + private static Map> noisyToolSoundGroups = new HashMap<>(); + + private static Map getSafeNTSGs(LivingEntity entity) + { + Map result = noisyToolSoundGroups.get(entity); + if(result!=null) + return result; + if(INoisyTool.isAbleNoisyTool(entity.getMainHandItem())||INoisyTool.isAbleNoisyTool(entity.getOffhandItem())) + { + result = new HashMap<>(); + noisyToolSoundGroups.put(entity, result); + } + return result; + } + + /** + * For the given entity and slot: creates or returns an existing sound group, or null, if there should be none. + * Turns off sound groups that are obsolete and removes them from the mapping. + * This should generally be the only point where NoisyToolSoundGroups are accessed through. + * + * @param entity + * @param slot + * @return a NoisyToolSoundGroup for the given slot or null, if the provided slot does not hold a suitable item + */ + @Nullable + public static NoisyToolSoundGroup getSafeNTSG(LivingEntity entity, EquipmentSlot slot) + { + assert slot.getType().equals(Type.HAND); + Map ntsgs = getSafeNTSGs(entity); + if(ntsgs==null) + return null; + + NoisyToolSoundGroup soundGroup = ntsgs.get(slot); + ItemStack handItem = entity.getItemBySlot(slot); + int hotbarSlot = slot.equals(EquipmentSlot.MAINHAND)&&entity instanceof Player player?player.getInventory().selected: -1; + + if(soundGroup!=null) + { + if(!soundGroup.checkItemValid(handItem, hotbarSlot)) + { + ntsgs.remove(slot); + soundGroup = null; + if(ntsgs.isEmpty()) + noisyToolSoundGroups.remove(entity); + } + } + else if(INoisyTool.isAbleNoisyTool(handItem)) + { + soundGroup = new NoisyToolSoundGroup(handItem, entity, hotbarSlot); + ntsgs.put(slot, soundGroup); + } + + return soundGroup; + } + + public static void handleHarvestAction(LivingEntity holder, LeftClickBlock.Action action, BlockPos targetBlockPos) + { + NoisyToolSoundGroup ntsg = getSafeNTSG(holder, EquipmentSlot.MAINHAND); + + if(ntsg!=null) + { + switch(action) + { + case START: + ntsg.updateHarvestState(targetBlockPos); + break; + case STOP: // stop and abort fire only on the server / are sent from the server, and are non-client-main-player events + case ABORT: + ntsg.updateHarvestState(null); + break; + case CLIENT_HOLD: // fires only on the client + ntsg.updateHarvestState(targetBlockPos); + break; + } + } + } + + public static void handleAttack(LivingEntity holder) + { + NoisyToolSoundGroup ntsg = getSafeNTSG(holder, EquipmentSlot.MAINHAND); + + if(ntsg!=null) + ntsg.triggerAttack(); + } + + @SubscribeEvent + public static void toolHeldCheck(Post ev) + { + if(ev.getEntity() instanceof LivingEntity holder) //todo + { + if(!holder.level().isClientSide()) // client side only + return; + + NoisyToolSoundGroup ntsgMainHand = NoisyToolSoundHandler.getSafeNTSG(holder, EquipmentSlot.MAINHAND); + NoisyToolSoundGroup ntsgOffHand = NoisyToolSoundHandler.getSafeNTSG(holder, EquipmentSlot.OFFHAND); + + if(ntsgMainHand!=null) + ntsgMainHand.switchMotorOnOff(true); + if(ntsgOffHand!=null) + ntsgOffHand.switchMotorOnOff(true); + } + } + + @SubscribeEvent + public static void harvestCheck(LeftClickBlock ev) + { + if(INoisyTool.isAbleNoisyTool(ev.getItemStack())) + { + LivingEntity holder = ev.getEntity(); + if(holder instanceof Player player&&player.isCreative()) // skip for creative players, remote creative players don't send stop/abort on block break + return; + BlockPos targetPos = ev.getPos(); + if(ev.getLevel().isClientSide()&&holder.equals(Minecraft.getInstance().player)) + { + handleHarvestAction(holder, ev.getAction(), targetPos); + } + else + { + PacketDistributor.sendToPlayersTrackingEntity(holder, new MessageNoisyToolHarvestUpdate(holder, ev.getAction(), targetPos)); + } + } + } + + @SubscribeEvent + public static void attackCheck(LivingIncomingDamageEvent ev) + { + if(ev.getSource()!=null&&ev.getSource().getEntity() instanceof LivingEntity holder&&INoisyTool.isAbleNoisyTool(holder.getItemBySlot(EquipmentSlot.MAINHAND))) + { + if(holder.level().isClientSide()&&holder.equals(Minecraft.getInstance().player)) + { + handleAttack(holder); + } + else + { + PacketDistributor.sendToPlayersTrackingEntity(holder, new MessageNoisyToolAttack(holder)); + } + } + } + + /** + * handles stopping the sound instances and unlisting the sound group when the entity is removed + * consider checking entity.isRemoved() in the ticking sound instances (see MinecartSoundInstance.tick()) + * + * @param ev + */ + @OnlyIn(Dist.CLIENT) + @SubscribeEvent + public static void stopLeavingSoundSource(EntityLeaveLevelEvent ev) + { + Map ntsgs; + if(ev.getLevel().isClientSide()&&ev.getEntity() instanceof LivingEntity livingEntity&&(ntsgs = noisyToolSoundGroups.remove(livingEntity))!=null) + { + for(NoisyToolSoundGroup ntsg : ntsgs.values()) + { + ntsg.switchMotorOnOff(false); + } + } + } + + @OnlyIn(Dist.CLIENT) + @SubscribeEvent + public static void leaveLevel(Unload ev) + { + noisyToolSoundGroups.clear(); + } +} \ No newline at end of file diff --git a/src/main/resources/assets/immersiveengineering/lang_base/en_us.json b/src/main/resources/assets/immersiveengineering/lang_base/en_us.json index 0e82b2b25c..2d1bec65c0 100644 --- a/src/main/resources/assets/immersiveengineering/lang_base/en_us.json +++ b/src/main/resources/assets/immersiveengineering/lang_base/en_us.json @@ -462,6 +462,13 @@ "subtitle.immersiveengineering.chargeSlow": "Charging capacitors", "subtitle.immersiveengineering.spark": "Electrical sparks", "subtitle.immersiveengineering.railgunFire": "Railgun fire", + "subtitle.immersiveengineering.drill_motor": "Drill motor running", + "subtitle.immersiveengineering.drill_attack": "Drill revs up high", + "subtitle.immersiveengineering.drill_harvest": "Drilling", + "subtitle.immersiveengineering.buzzsaw_motor": "Buzzsaw motor running", + "subtitle.immersiveengineering.buzzsaw_attack": "Buzzsaw revs up high", + "subtitle.immersiveengineering.buzzsaw_harvest_sawing": "Sawing", + "subtitle.immersiveengineering.buzzsaw_harvest_grinding": "Grinding", "subtitle.immersiveengineering.tesla": "Tesla Coil zapping", "subtitle.immersiveengineering.direSwitch": "Direwolf20 imitating a knifeswitch", "subtitle.immersiveengineering.skyhook": "Skyhook slides along a wire", diff --git a/src/main/resources/assets/immersiveengineering/sounds.json b/src/main/resources/assets/immersiveengineering/sounds.json index 7e4caf3f77..bfef5156ab 100644 --- a/src/main/resources/assets/immersiveengineering/sounds.json +++ b/src/main/resources/assets/immersiveengineering/sounds.json @@ -105,6 +105,116 @@ "immersiveengineering:skyhook" ], "subtitle": "subtitle.immersiveengineering.skyhook" + }, + "drill_idle": { + "category": "neutral", + "sounds": [ + { + "name": "immersiveengineering:drill_idle", + "attenuation_distance": 22 + } + ], + "subtitle": "subtitle.immersiveengineering.drill_motor" + }, + "drill_busy": { + "category": "neutral", + "sounds": [ + { + "name": "immersiveengineering:drill_busy", + "attenuation_distance": 24 + } + ], + "subtitle": "subtitle.immersiveengineering.drill_motor" + }, + "drill_fade": { + "category": "neutral", + "sounds": [ + { + "name": "immersiveengineering:drill_fade", + "attenuation_distance": 24 + } + ], + "subtitle": "subtitle.immersiveengineering.drill_motor" + }, + "drill_attack": { + "category": "neutral", + "sounds": [ + { + "name": "immersiveengineering:drill_attack", + "attenuation_distance": 24 + } + ], + "subtitle": "subtitle.immersiveengineering.drill_attack" + }, + "drill_harvest": { + "category": "block", + "sounds": [ + { + "name": "immersiveengineering:drill_harvest", + "attenuation_distance": 28 + } + ], + "subtitle": "subtitle.immersiveengineering.drill_harvest" + }, + "buzzsaw_idle": { + "category": "neutral", + "sounds": [ + { + "name": "immersiveengineering:buzzsaw_idle", + "attenuation_distance": 22 + } + ], + "subtitle": "subtitle.immersiveengineering.buzzsaw_motor" + }, + "buzzsaw_busy": { + "category": "neutral", + "sounds": [ + { + "name": "immersiveengineering:buzzsaw_busy", + "attenuation_distance": 24 + } + ], + "subtitle": "subtitle.immersiveengineering.buzzsaw_motor" + }, + "buzzsaw_fade": { + "category": "neutral", + "sounds": [ + { + "name": "immersiveengineering:buzzsaw_fade", + "attenuation_distance": 24 + } + ], + "subtitle": "subtitle.immersiveengineering.buzzsaw_motor" + }, + "buzzsaw_attack": { + "category": "neutral", + "sounds": [ + { + "name": "immersiveengineering:buzzsaw_attack", + "attenuation_distance": 24 + } + ], + "subtitle": "subtitle.immersiveengineering.buzzsaw_attack" + }, + "buzzsaw_harvest_saw": { + "category": "block", + "sounds": [ + { + "name": "immersiveengineering:buzzsaw_harvest_saw", + "attenuation_distance": 28 + } + ], + "subtitle": "subtitle.immersiveengineering.buzzsaw_sawing" + }, + "buzzsaw_harvest_grind": { + "category": "block", + "sounds": [ + { + "name": "immersiveengineering:buzzsaw_harvest_grind", + "attenuation_distance": 28 + } + ], + "subtitle": "subtitle.immersiveengineering.buzzsaw_grinding" }, "tesla": { "category": "block", diff --git a/src/main/resources/assets/immersiveengineering/sounds/buzzsaw_attack.ogg b/src/main/resources/assets/immersiveengineering/sounds/buzzsaw_attack.ogg new file mode 100644 index 0000000000..5a2c7606d1 Binary files /dev/null and b/src/main/resources/assets/immersiveengineering/sounds/buzzsaw_attack.ogg differ diff --git a/src/main/resources/assets/immersiveengineering/sounds/buzzsaw_busy.ogg b/src/main/resources/assets/immersiveengineering/sounds/buzzsaw_busy.ogg new file mode 100644 index 0000000000..989809867b Binary files /dev/null and b/src/main/resources/assets/immersiveengineering/sounds/buzzsaw_busy.ogg differ diff --git a/src/main/resources/assets/immersiveengineering/sounds/buzzsaw_fade.ogg b/src/main/resources/assets/immersiveengineering/sounds/buzzsaw_fade.ogg new file mode 100644 index 0000000000..a1a4739643 Binary files /dev/null and b/src/main/resources/assets/immersiveengineering/sounds/buzzsaw_fade.ogg differ diff --git a/src/main/resources/assets/immersiveengineering/sounds/buzzsaw_harvest_grind.ogg b/src/main/resources/assets/immersiveengineering/sounds/buzzsaw_harvest_grind.ogg new file mode 100644 index 0000000000..1d06750266 Binary files /dev/null and b/src/main/resources/assets/immersiveengineering/sounds/buzzsaw_harvest_grind.ogg differ diff --git a/src/main/resources/assets/immersiveengineering/sounds/buzzsaw_harvest_saw.ogg b/src/main/resources/assets/immersiveengineering/sounds/buzzsaw_harvest_saw.ogg new file mode 100644 index 0000000000..52084e9450 Binary files /dev/null and b/src/main/resources/assets/immersiveengineering/sounds/buzzsaw_harvest_saw.ogg differ diff --git a/src/main/resources/assets/immersiveengineering/sounds/buzzsaw_idle.ogg b/src/main/resources/assets/immersiveengineering/sounds/buzzsaw_idle.ogg new file mode 100644 index 0000000000..84fffe15d5 Binary files /dev/null and b/src/main/resources/assets/immersiveengineering/sounds/buzzsaw_idle.ogg differ diff --git a/src/main/resources/assets/immersiveengineering/sounds/drill_attack.ogg b/src/main/resources/assets/immersiveengineering/sounds/drill_attack.ogg new file mode 100644 index 0000000000..47caf3c3f9 Binary files /dev/null and b/src/main/resources/assets/immersiveengineering/sounds/drill_attack.ogg differ diff --git a/src/main/resources/assets/immersiveengineering/sounds/drill_busy.ogg b/src/main/resources/assets/immersiveengineering/sounds/drill_busy.ogg new file mode 100644 index 0000000000..76f25de43a Binary files /dev/null and b/src/main/resources/assets/immersiveengineering/sounds/drill_busy.ogg differ diff --git a/src/main/resources/assets/immersiveengineering/sounds/drill_fade.ogg b/src/main/resources/assets/immersiveengineering/sounds/drill_fade.ogg new file mode 100644 index 0000000000..e71ab96d7d Binary files /dev/null and b/src/main/resources/assets/immersiveengineering/sounds/drill_fade.ogg differ diff --git a/src/main/resources/assets/immersiveengineering/sounds/drill_harvest.ogg b/src/main/resources/assets/immersiveengineering/sounds/drill_harvest.ogg new file mode 100644 index 0000000000..0d20f92ad7 Binary files /dev/null and b/src/main/resources/assets/immersiveengineering/sounds/drill_harvest.ogg differ diff --git a/src/main/resources/assets/immersiveengineering/sounds/drill_idle.ogg b/src/main/resources/assets/immersiveengineering/sounds/drill_idle.ogg new file mode 100644 index 0000000000..bfae4cf53a Binary files /dev/null and b/src/main/resources/assets/immersiveengineering/sounds/drill_idle.ogg differ diff --git a/src/main/resources/assets/immersiveengineering/sounds/sound_sources.md b/src/main/resources/assets/immersiveengineering/sounds/sound_sources.md index af5954cbbf..eba45e1735 100644 --- a/src/main/resources/assets/immersiveengineering/sounds/sound_sources.md +++ b/src/main/resources/assets/immersiveengineering/sounds/sound_sources.md @@ -13,7 +13,11 @@ - Automatic Workbench Drill: https://freesound.org/people/nuncaconoci/sounds/619243/ - Automatic Workbench Solder: https://freesound.org/people/soundsofscienceupf/sounds/460832/ - Eletromagnet: https://freesound.org/people/_MC5_/sounds/672082/ -- Waterwheel/Windmill Creak: https://freesound.org/people/phonoflora/sounds/535013/ -- Siren: https://freesound.org/people/FusionWolf3740/sounds/570464/ -- Klaxon: https://en.wikipedia.org/wiki/File:WWII_submarine_dive_klaxon.ogg -- Buzzer: https://freesound.org/people/guitarguy1985/sounds/54047/ + - Waterwheel/Windmill Creak: https://freesound.org/people/phonoflora/sounds/535013/ + - Siren: https://freesound.org/people/FusionWolf3740/sounds/570464/ + - Klaxon: https://en.wikipedia.org/wiki/File:WWII_submarine_dive_klaxon.ogg + - Buzzer: https://freesound.org/people/guitarguy1985/sounds/54047/ + - Drill motor: https://freesound.org/people/DrinkingWindGames/sounds/463735/ + - Drill harvesting https://freesound.org/people/jameswrowles/sounds/516602/ + - Buzzsaw motor https://freesound.org/people/Audionautics/sounds/171652/ + - Buzzsaw harvesting https://freesound.org/people/gecop/sounds/609919/ \ No newline at end of file