That's one heck of a commit you've got there...

I may have got a bit behind with version control. A lot behind, in fact. Maybe I'll go back and split this sometime - then again, I probably won't. But hey, at least it's here!
This commit is contained in:
Electroblob77
2019-08-18 00:04:18 +01:00
parent 2680d02304
commit f37812be3e
1564 changed files with 47652 additions and 12984 deletions
@@ -0,0 +1,35 @@
package electroblob.wizardry.client.audio;
import net.minecraft.client.audio.MovingSound;
import net.minecraft.entity.Entity;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
// Copied from MovingSoundMinecart; if it ever breaks between updates take a look at that.
//@SideOnly(Side.CLIENT)
public class MovingSoundEntity<T extends Entity> extends MovingSound {
protected final T source;
protected float distance = 0.0F;
public MovingSoundEntity(T entity, SoundEvent sound, SoundCategory category, float volume, float pitch, boolean repeat){
super(sound, category);
this.source = entity;
this.repeat = repeat;
this.volume = volume;
this.pitch = pitch;
this.repeatDelay = 0;
}
@Override
public void update(){
if(this.source.isDead){
this.donePlaying = true;
}else{
this.xPosF = (float)this.source.posX;
this.yPosF = (float)this.source.posY;
this.zPosF = (float)this.source.posZ;
}
}
}
@@ -0,0 +1,94 @@
package electroblob.wizardry.client.audio;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.ISound;
import net.minecraft.util.ITickable;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.relauncher.Side;
import java.util.HashSet;
import java.util.Set;
/**
* Instances of this class represent a set of sounds which together form a looped sound: start, loop and end.
* Currently this is only used for continuous spell sounds in wizardry itself, but feel free to make your own
* implementations - this class will take care of the internals.
*
* @since Wizardry 4.2
* @author Electroblob
*/
// See MusicTicker, this is the same idea of just storing the sounds statically client-side and ticking them
@Mod.EventBusSubscriber(Side.CLIENT)
public abstract class SoundLoop implements ITickable {
private static final Set<SoundLoop> activeLoops = new HashSet<>();
private final ISound start;
private final ISound loop;
private final ISound end;
private boolean looping = false;
private boolean needsRemoving = false;
public SoundLoop(SoundEvent start, SoundEvent loop, SoundEvent end, SoundCategory category, ISoundFactory factory){
// The reason I've gone to the effort of having a factory for these is that we need SoundLoop to have control
// over which sounds are repeated and which aren't whilst keeping them private.
this.start = factory.create(start, category, false);
this.loop = factory.create(loop, category, true);
this.end = factory.create(end, category, false);
}
@Override
public void update(){
// Check every tick if the start sound is done playing and if so, start the loop sound
if(!looping && !Minecraft.getMinecraft().getSoundHandler().isSoundPlaying(start)){
Minecraft.getMinecraft().getSoundHandler().playSound(loop);
looping = true;
}
}
/** Stops the loop part of the sound immediately and starts playing the end part. This may be called from subclasses
* or externally depending on the implementation. */
// For continuous spell sounds it's internally
public void endLoop(){
Minecraft.getMinecraft().getSoundHandler().stopSound(start);
Minecraft.getMinecraft().getSoundHandler().stopSound(loop);
Minecraft.getMinecraft().getSoundHandler().playSound(end);
// Can't modify activeLoops directly since we'll probably be calling this method from update(), which is
// during iteration of activeLoops so it could cause a ConcurrentModificationException
this.markForRemoval();
}
/** Marks this sound loop to be removed next tick. */
protected void markForRemoval(){
this.needsRemoving = true;
}
// Static methods
public static void addLoop(SoundLoop loop){
activeLoops.add(loop);
// Do this here rather than in the constructor in case someone wants to play the loop later or reuse it
Minecraft.getMinecraft().getSoundHandler().playSound(loop.start);
}
@SubscribeEvent
public static void tick(TickEvent.ClientTickEvent event){
// Using the END phase means we can check for stopped sounds as soon as they are stopped (effectively),
// meaning we don't get the 'cut' between the start and loop sounds
// FIXME: Apparently this only works for dispensers. What the heck is the difference?!
if(event.phase == TickEvent.Phase.END){
activeLoops.forEach(SoundLoop::update);
activeLoops.removeIf(s -> s.needsRemoving);
}
}
@FunctionalInterface
public interface ISoundFactory {
ISound create(SoundEvent sound, SoundCategory category, boolean repeat);
}
}
@@ -0,0 +1,117 @@
package electroblob.wizardry.client.audio;
import electroblob.wizardry.data.DispenserCastingData;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.audio.PositionedSound;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
/** Abstract base class for sound loops associated with spells; see subclasses below for implementations. */
public abstract class SoundLoopSpell extends SoundLoop {
private final Spell spell;
public SoundLoopSpell(SoundEvent start, SoundEvent loop, SoundEvent end, ISoundFactory factory, Spell spell){
super(start, loop, end, WizardrySounds.SPELLS, factory);
this.spell = spell;
}
@Override
public void update(){
// This may be a bit overkill but I might as well put functionality in superclasses where possible
if(stillCasting(spell)){
// This can't called in the same tick as endLoop because otherwise, if the spell casting stops during the
// same tick as the transition to the loop sound it will try and stop the loop immediately after it has
// started - and for whatever reason, this causes the 'Channel null in method stop' error.
super.update();
}else{
endLoop();
}
}
protected abstract boolean stillCasting(Spell spell);
/** Implements a sound loop for continuous spells cast by entities. */
public static class SoundLoopSpellEntity extends SoundLoopSpell {
private final EntityLivingBase source;
public SoundLoopSpellEntity(SoundEvent start, SoundEvent loop, SoundEvent end, Spell spell, EntityLivingBase source, float volume, float pitch){
super(start, loop, end, (sound, category, repeat) -> new MovingSoundEntity<>(source, sound, category, volume, pitch, repeat), spell);
this.source = source;
}
@Override
protected boolean stillCasting(Spell spell){
return WizardryUtilities.isCasting(source, spell);
}
}
public static abstract class SoundLoopSpellPosition extends SoundLoopSpell {
public SoundLoopSpellPosition (SoundEvent start, SoundEvent loop, SoundEvent end, Spell spell,
double x, double y, double z, float sndVolume, float sndPitch){
// Huh, I actually found a use for a non-static initialiser block - hence the double curly brackets...
super(start, loop, end, (sound, category, r) -> new PositionedSound(sound, category){{
// ...et voila, we can just set protected fields as we please using external variables
this.xPosF = (float)x;
this.yPosF = (float)y;
this.zPosF = (float)z;
this.repeat = r;
this.volume = sndVolume;
this.pitch = sndPitch;
}}, spell);
}
}
/** Implements a sound loop for continuous spells cast by dispensers. */
public static class SoundLoopSpellDispenser extends SoundLoopSpellPosition {
private final TileEntityDispenser source;
public SoundLoopSpellDispenser(SoundEvent start, SoundEvent loop, SoundEvent end, Spell spell, World world,
double x, double y, double z, float sndVolume, float sndPitch){
super(start, loop, end, spell, x, y, z, sndVolume, sndPitch);
TileEntity tileentity = world.getTileEntity(new BlockPos(x, y, z));
if(tileentity instanceof TileEntityDispenser) this.source = (TileEntityDispenser)tileentity;
else throw new NullPointerException(String.format("Playing continuous spell sound: no dispenser found at %s, %s, %s", x, y, z));
}
@Override
protected boolean stillCasting(Spell spell){
return DispenserCastingData.get(source).currentlyCasting() == spell;
}
}
/** Implements a sound loop for continuous spells cast at a position (via commands). */
public static class SoundLoopSpellPosTimed extends SoundLoopSpellPosition {
private int timeLeft;
public SoundLoopSpellPosTimed(SoundEvent start, SoundEvent loop, SoundEvent end, Spell spell, int duration,
double x, double y, double z, float sndVolume, float sndPitch){
super(start, loop, end, spell, x, y, z, sndVolume, sndPitch);
this.timeLeft = duration;
}
@Override
public void update(){
super.update();
timeLeft--;
}
@Override
protected boolean stillCasting(Spell spell){
return timeLeft > 0;
}
}
}