diff --git a/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java b/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java index b7ef15a7..9129c159 100755 --- a/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java +++ b/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java @@ -11,6 +11,8 @@ import net.minecraft.network.datasync.EntityDataManager; import net.minecraft.network.play.server.*; import net.minecraft.server.integrated.IntegratedServer; import net.minecraft.util.EnumHand; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.SoundEvent; import net.minecraft.util.math.BlockPos; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.entity.minecart.MinecartInteractEvent; @@ -69,6 +71,25 @@ public class RecordingEventHandler { } } + public void onClientSound(SoundEvent sound, SoundCategory category, + double x, double y, double z, float volume, float pitch) { + try { + // Send to all other players in ServerWorldEventHandler#playSoundToAllNearExcept + packetListener.save(new SPacketSoundEffect(sound, category, x, y, z, volume, pitch)); + } catch(Exception e) { + e.printStackTrace(); + } + } + + public void onClientEffect(int type, BlockPos pos, int data) { + try { + // Send to all other players in ServerWorldEventHandler#playEvent + packetListener.save(new SPacketEffect(type, pos, data, false)); + } catch(Exception e) { + e.printStackTrace(); + } + } + @SubscribeEvent public void onPlayerTick(PlayerTickEvent e) { try { diff --git a/src/main/java/com/replaymod/recording/mixin/MixinPlayerControllerMP.java b/src/main/java/com/replaymod/recording/mixin/MixinPlayerControllerMP.java new file mode 100644 index 00000000..366141e0 --- /dev/null +++ b/src/main/java/com/replaymod/recording/mixin/MixinPlayerControllerMP.java @@ -0,0 +1,28 @@ +package com.replaymod.recording.mixin; + +import com.replaymod.recording.handler.RecordingEventHandler; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.PlayerControllerMP; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(PlayerControllerMP.class) +public abstract class MixinPlayerControllerMP implements RecordingEventHandler.RecordingEventSender { + + @Shadow + private Minecraft mc; + + // Redirects the call to playEvent without the initial player argument to the method with that argument + // The new method will then play it and (if applicable) record it. (See MixinWorldClient) + // This is necessary for the block break event (particles and sound) to be recorded. Otherwise it looks like the + // event was emitted because of a packet (player will be null) and not as it actually was (by the player). + @Redirect(method = "onPlayerDestroyBlock", at = @At(value = "INVOKE", + target = "Lnet/minecraft/world/World;playEvent(ILnet/minecraft/util/math/BlockPos;I)V")) + public void replayModRecording_playEvent_fixed(World world, int type, BlockPos pos, int data) { + world.playEvent(mc.thePlayer, type, pos, data); + } +} diff --git a/src/main/java/com/replaymod/recording/mixin/MixinWorldClient.java b/src/main/java/com/replaymod/recording/mixin/MixinWorldClient.java new file mode 100644 index 00000000..74a5a57c --- /dev/null +++ b/src/main/java/com/replaymod/recording/mixin/MixinWorldClient.java @@ -0,0 +1,62 @@ +package com.replaymod.recording.mixin; + +import com.replaymod.recording.handler.RecordingEventHandler; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.WorldClient; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.profiler.Profiler; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.WorldProvider; +import net.minecraft.world.storage.ISaveHandler; +import net.minecraft.world.storage.WorldInfo; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(WorldClient.class) +public abstract class MixinWorldClient extends World implements RecordingEventHandler.RecordingEventSender { + @Shadow + private Minecraft mc; + + protected MixinWorldClient(ISaveHandler saveHandlerIn, WorldInfo info, WorldProvider providerIn, Profiler profilerIn, boolean client) { + super(saveHandlerIn, info, providerIn, profilerIn, client); + } + + private RecordingEventHandler replayModRecording_getRecordingEventHandler() { + return ((RecordingEventHandler.RecordingEventSender) mc.renderGlobal).getRecordingEventHandler(); + } + + // Sounds that are emitted by thePlayer no longer take the long way over the server + // but are instead played directly by the client. The server only sends these sounds to + // other clients so we have to record them manually. + // E.g. Block place sounds + @Inject(method = "playSound", at = @At("HEAD")) + public void replayModRecording_recordClientSound(EntityPlayer player, double x, double y, double z, SoundEvent sound, SoundCategory category, + float volume, float pitch, CallbackInfo ci) { + if (player == mc.thePlayer) { + RecordingEventHandler handler = replayModRecording_getRecordingEventHandler(); + if (handler != null) { + handler.onClientSound(sound, category, x, y, z, volume, pitch); + } + } + } + + // Same goes for level events (also called effects). E.g. door open, block break, etc. + // These are handled in the World class, so we override the method in WorldClient and add our special handling. + @Override + public void playEvent(EntityPlayer player, int type, BlockPos pos, int data) { + if (player == mc.thePlayer) { + // We caused this event, the server won't send it to us + RecordingEventHandler handler = replayModRecording_getRecordingEventHandler(); + if (handler != null) { + handler.onClientEffect(type, pos, data); + } + } + super.playEvent(player, type, pos, data); + } +} diff --git a/src/main/resources/mixins.recording.replaymod.json b/src/main/resources/mixins.recording.replaymod.json index a67ec80b..93d57722 100644 --- a/src/main/resources/mixins.recording.replaymod.json +++ b/src/main/resources/mixins.recording.replaymod.json @@ -5,7 +5,9 @@ "server": [], "client": [ "MixinNetHandlerPlayClient", - "MixinRenderGlobal" + "MixinPlayerControllerMP", + "MixinRenderGlobal", + "MixinWorldClient" ], "compatibilityLevel": "JAVA_8", "refmap": "mixins.replaymod.refmap.json"