Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Static Chunk Recording #5

Merged
merged 27 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
cc0836b
Start working on ChunkRecorder
senseiwells Jan 29, 2024
1071928
ChunkRecorder working!
senseiwells Jan 30, 2024
b600b79
Fix entities not rendering
senseiwells Jan 30, 2024
35a3245
Make recorder player invisible
senseiwells Jan 30, 2024
365f317
Fixed entities being removed
senseiwells Jan 30, 2024
4dbe8ca
Remove prinlns
senseiwells Jan 30, 2024
a405056
Ensure chunks are loaded before recording starts. And Fix block state…
senseiwells Jan 30, 2024
36a8272
Fix chunk generation
senseiwells Jan 30, 2024
b87dd89
Update README
senseiwells Jan 30, 2024
0769f48
Add more commands
senseiwells Jan 30, 2024
0bfbf0b
Update readme
senseiwells Jan 30, 2024
4b21b46
Cleanup
senseiwells Jan 30, 2024
3ec0854
Cleanup accessors and invokers
senseiwells Jan 30, 2024
e3a05e1
Fix explosions and particles
senseiwells Jan 30, 2024
d317528
Fix mixins json
senseiwells Jan 30, 2024
e444c35
Bugfixing
senseiwells Jan 30, 2024
12784d7
Update readme
senseiwells Jan 30, 2024
0c65b8f
Fix old readme text
senseiwells Jan 30, 2024
8128321
Update README
senseiwells Jan 31, 2024
723ed62
Record raids
senseiwells Jan 31, 2024
a056a8d
Added tracking for in game boss events
senseiwells Feb 1, 2024
9b92ad3
Fix entity untracking when removeall
senseiwells Feb 1, 2024
3aa5c82
Add automatic chunk recording
senseiwells Feb 1, 2024
1c091dc
Update README
senseiwells Feb 1, 2024
973ec94
Update README again
senseiwells Feb 1, 2024
819d657
Fix linking issue
senseiwells Feb 1, 2024
5397b96
Finalize
senseiwells Feb 1, 2024
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@

build/
run/

*.psd
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2021 senseiwells
Copyright (c) 2024 senseiwells

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
255 changes: 195 additions & 60 deletions README.md

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.archivesName

plugins {
kotlin("jvm")
id("fabric-loom")
Expand Down Expand Up @@ -33,6 +35,7 @@ dependencies {
})

modImplementation("net.fabricmc:fabric-loader:${property("loader_version")}")
modImplementation("net.fabricmc:fabric-language-kotlin:${property("fabric_kotlin_version")}")
modImplementation("net.fabricmc.fabric-api:fabric-api:${property("fabric_version")}")

// I've had some issues with ReplayStudio and slf4j (in dev)
Expand All @@ -44,7 +47,6 @@ dependencies {
exclude(group = "com.google.guava", module = "guava")
exclude(group = "com.google.code.gson", module = "gson")
})
include(modImplementation("net.fabricmc:fabric-language-kotlin:${property("fabric_kotlin_version")}")!!)
include(modImplementation("me.lucko:fabric-permissions-api:${property("permissions_version")}")!!)

modImplementation("com.github.gnembon:fabric-carpet:${property("carpet_version")}")
Expand All @@ -58,6 +60,10 @@ loom {
accessWidenerPath.set(file("src/main/resources/serverreplay.accesswidener"))
}

tasks.remapJar {
archiveVersion.set("${project.version}+mc${project.property("minecraft_version")}")
}

tasks {
processResources {
inputs.property("version", project.version)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package me.senseiwells.replay.ducks;

import me.senseiwells.replay.chunk.ChunkRecordable;
import me.senseiwells.replay.chunk.ChunkRecorder;
import me.senseiwells.replay.chunk.ChunkRecorders;
import org.jetbrains.annotations.NotNull;

import java.util.Collection;

public interface ServerReplay$ChunkRecordable extends ChunkRecordable {
@NotNull
@Override
default Collection<ChunkRecorder> getRecorders() {
return this.replay$getRecorders();
}

@Override
default void addRecorder(@NotNull ChunkRecorder recorder) {
this.replay$addRecorder(recorder);
}

@Override
default void removeRecorder(@NotNull ChunkRecorder recorder) {
this.replay$removeRecorder(recorder);
}

@Override
default void removeAllRecorders() {
this.replay$removeAllRecorders();
}

Collection<ChunkRecorder> replay$getRecorders();

void replay$addRecorder(ChunkRecorder recorder);

void replay$removeRecorder(ChunkRecorder recorder);

void replay$removeAllRecorders();
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package me.senseiwells.replay.mixin;

import me.senseiwells.replay.chunk.ChunkRecorder;
import me.senseiwells.replay.chunk.ChunkRecorders;
import me.senseiwells.replay.config.ReplayConfig;
import me.senseiwells.replay.player.PlayerRecorder;
import me.senseiwells.replay.player.PlayerRecorders;
Expand All @@ -8,6 +10,7 @@
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

@Mixin(MinecraftServer.class)
public class MinecraftServerMixin {
Expand All @@ -21,17 +24,36 @@ public class MinecraftServerMixin {
)
private void onServerLoaded(CallbackInfo ci) {
ReplayConfig.read();

if (ReplayConfig.getEnabled()) {
ReplayConfig.startChunks((MinecraftServer) (Object) this);
}
}

@Inject(
method = "stopServer",
method = "saveAllChunks",
at = @At("TAIL")
)
private void onServerStopped(CallbackInfo ci) {
private void onSave(
boolean suppressLog,
boolean flush,
boolean forced,
CallbackInfoReturnable<Boolean> cir
) {
ReplayConfig.write();
}

@Inject(
method = "stopServer",
at = @At("TAIL")
)
private void onServerStopped(CallbackInfo ci) {
for (PlayerRecorder recorder : PlayerRecorders.all()) {
recorder.stop();
}

for (ChunkRecorder recorder : ChunkRecorders.all()) {
recorder.stop();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ private void onPacket(Packet<?> packet, PacketSendListener listener, CallbackInf
at = @At("TAIL")
)
private void onDisconnect(Component reason, CallbackInfo ci) {
PlayerRecorder recorder = PlayerRecorders.removeByUUID(this.playerProfile().getId());
PlayerRecorder recorder = PlayerRecorders.getByUUID(this.playerProfile().getId());
if (recorder != null) {
recorder.stop();
}
Expand Down
108 changes: 77 additions & 31 deletions src/main/java/me/senseiwells/replay/mixin/ServerLevelMixin.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,47 @@
package me.senseiwells.replay.mixin;

import com.llamalad7.mixinextras.sugar.Local;
import me.senseiwells.replay.chunk.ChunkRecorder;
import me.senseiwells.replay.chunk.ChunkRecorders;
import me.senseiwells.replay.player.PlayerRecorder;
import me.senseiwells.replay.player.PlayerRecorders;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.network.protocol.game.ClientboundBlockDestructionPacket;
import net.minecraft.network.protocol.game.ClientboundLevelEventPacket;
import net.minecraft.network.protocol.game.ClientboundSoundPacket;
import net.minecraft.network.protocol.game.ClientboundExplodePacket;
import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Explosion;
import net.minecraft.world.level.ExplosionDamageCalculator;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.storage.WritableLevelData;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
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;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import java.util.function.Supplier;

@Mixin(ServerLevel.class)
public abstract class ServerLevelMixin {
public abstract class ServerLevelMixin extends Level {
protected ServerLevelMixin(WritableLevelData levelData, ResourceKey<Level> dimension, RegistryAccess registryAccess, Holder<DimensionType> dimensionTypeRegistration, Supplier<ProfilerFiller> profiler, boolean isClientSide, boolean isDebug, long biomeZoomSeed, int maxChainedNeighborUpdates) {
super(levelData, dimension, registryAccess, dimensionTypeRegistration, profiler, isClientSide, isDebug, biomeZoomSeed, maxChainedNeighborUpdates);
}

@Shadow @Nullable public abstract Entity getEntity(int id);

@Inject(
Expand All @@ -36,42 +56,68 @@ private void onDestroyBlockProgress(int breakerId, BlockPos pos, int progress, C
recorder.record(new ClientboundBlockDestructionPacket(breakerId, pos, progress));
}
}

ChunkPos chunkPos = new ChunkPos(pos);
for (ChunkRecorder recorder : ChunkRecorders.containing(this.dimension(), chunkPos)) {
recorder.record(new ClientboundBlockDestructionPacket(breakerId, pos, progress));
}
}

@Inject(
method = "playSeededSound(Lnet/minecraft/world/entity/player/Player;DDDLnet/minecraft/core/Holder;Lnet/minecraft/sounds/SoundSource;FFJ)V",
at = @At("HEAD")
method = "explode",
at = @At("TAIL")
)
private void onPlaySound(
@Nullable Player player,
double x,
double y,
double z,
Holder<SoundEvent> sound,
SoundSource source,
float volume,
float pitch,
long seed,
CallbackInfo ci
private void onExplode(
Entity entity,
DamageSource source,
ExplosionDamageCalculator calculator,
double posX,
double posY,
double posZ,
float radius,
boolean causeFire,
Level.ExplosionInteraction interaction,
ParticleOptions smallParticles,
ParticleOptions largeParticles,
SoundEvent sound,
CallbackInfoReturnable<Explosion> cir,
@Local Explosion explosion
) {
if (player != null) {
PlayerRecorder recorder = PlayerRecorders.getByUUID(player.getUUID());
if (recorder != null) {
recorder.record(new ClientboundSoundPacket(sound, source, x, y, z, volume, pitch, seed));
}
ChunkPos chunkPos = new ChunkPos(BlockPos.containing(posX, posY, posZ));
for (ChunkRecorder recorder : ChunkRecorders.containing(this.dimension(), chunkPos)) {
recorder.record(new ClientboundExplodePacket(
posX, posY, posZ, radius,
explosion.getToBlow(),
// Knock-back
Vec3.ZERO,
explosion.getBlockInteraction(),
explosion.getSmallExplosionParticles(),
explosion.getLargeExplosionParticles(),
explosion.getExplosionSound()
));
}
}

@Inject(
method = "levelEvent",
at = @At("HEAD")
method = "sendParticles(Lnet/minecraft/core/particles/ParticleOptions;DDDIDDDD)I",
at = @At("TAIL")
)
private void onLevelEvent(@Nullable Player player, int type, BlockPos pos, int data, CallbackInfo ci) {
if (player != null) {
PlayerRecorder recorder = PlayerRecorders.getByUUID(player.getUUID());
if (recorder != null) {
recorder.record(new ClientboundLevelEventPacket(type, pos, data, false));
}
private <T extends ParticleOptions> void onSendParticles(
T type,
double posX,
double posY,
double posZ,
int particleCount,
double xOffset,
double yOffset,
double zOffset,
double speed,
CallbackInfoReturnable<Integer> cir,
@Local ClientboundLevelParticlesPacket packet
) {
ChunkPos chunkPos = new ChunkPos(BlockPos.containing(posX, posY, posZ));
for (ChunkRecorder recorder : ChunkRecorders.containing(this.dimension(), chunkPos)) {
recorder.record(packet);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package me.senseiwells.replay.mixin;

import com.mojang.authlib.GameProfile;
import me.senseiwells.replay.ServerReplay;
import me.senseiwells.replay.config.ReplayConfig;
import me.senseiwells.replay.player.PlayerRecorder;
import me.senseiwells.replay.player.PlayerRecorders;
import me.senseiwells.replay.player.predicates.ReplayPlayerContext;
import me.senseiwells.replay.recorder.ReplayRecorder;
import net.minecraft.network.protocol.login.ServerboundLoginAcknowledgedPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerLoginPacketListenerImpl;
Expand Down Expand Up @@ -35,9 +34,9 @@ private void onLoggedIn(
CallbackInfo ci
) {
GameProfile profile = this.authenticatedProfile;
if (profile != null && ReplayConfig.getEnabled() && PlayerRecorders.predicate.test(new ReplayPlayerContext(this.server, profile))) {
ServerReplay.logger.info("Started to record player '{}'", profile.getName());
PlayerRecorder recorder = PlayerRecorders.create(this.server, profile);
if (profile != null && ReplayConfig.getEnabled() && ReplayConfig.predicate.test(new ReplayPlayerContext(this.server, profile))) {
ReplayRecorder recorder = PlayerRecorders.create(this.server, profile);
recorder.logStart();
recorder.afterLogin();
}
}
Expand Down
Loading
Loading