OiOiOiAEyeEye

This commit is contained in:
2026-03-07 14:28:02 +01:00
parent a7aa7d36f9
commit 76803eb460
20 changed files with 196 additions and 106 deletions

View File

@@ -96,6 +96,7 @@ public class SzarClient implements ClientModInitializer {
});
});
VideoManager.init();
ClientTickEvents.END_CLIENT_TICK.register(client -> {
VideoManager.tick();
});
@@ -441,7 +442,7 @@ public class SzarClient implements ClientModInitializer {
(dispatcher, registryAccess) -> PanoramaClientCommand.register(dispatcher)
);
}
ClientTickEvents.END_CLIENT_TICK.register(client -> {
/*ClientTickEvents.END_CLIENT_TICK.register(client -> {
if (addedFeature) return; // only run once
MinecraftClient mc = MinecraftClient.getInstance();
if (mc.getEntityRenderDispatcher() == null) return;
@@ -453,7 +454,7 @@ public class SzarClient implements ClientModInitializer {
}
addedFeature = true; // prevent running again
});
});*/
}
private boolean isDebugEnabled() {

View File

@@ -2,6 +2,7 @@ package dev.tggamesyt.szar.client;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.client.render.OverlayTexture;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.RenderLayer;
@@ -26,7 +27,6 @@ public class VideoHeadFeature extends FeatureRenderer<AbstractClientPlayerEntity
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light,
AbstractClientPlayerEntity player, float limbAngle, float limbDistance,
float tickDelta, float animationProgress, float headYaw, float headPitch) {
// Only render if the player is playing a video
if (!VideoManager.isPlaying(player.getUuid())) return;
@@ -40,16 +40,43 @@ public class VideoHeadFeature extends FeatureRenderer<AbstractClientPlayerEntity
// Position quad slightly in front of the face
float size = 0.5f;
matrices.translate(-size / 2f, -size / 2f, -0.25f);
matrices.translate(-size / 2f, -size / 2f - 0.24f, -0.30f);
// Render the video frame
VertexConsumer vc = vertexConsumers.getBuffer(RenderLayer.getEntityCutoutNoCull(frame));
Matrix4f matrix = matrices.peek().getPositionMatrix();
vc.vertex(matrix, 0, 0, 0).texture(0, 1).light(light).next();
vc.vertex(matrix, size, 0, 0).texture(1, 1).light(light).next();
vc.vertex(matrix, size, size, 0).texture(1, 0).light(light).next();
vc.vertex(matrix, 0, size, 0).texture(0, 0).light(light).next();
vc.vertex(matrix, 0, 0, 0)
.color(255,255,255,255)
.texture(0,0)
.overlay(OverlayTexture.DEFAULT_UV)
.light(light)
.normal(0,0,1)
.next();
vc.vertex(matrix, size, 0, 0)
.color(255,255,255,255)
.texture(1,0)
.overlay(OverlayTexture.DEFAULT_UV)
.light(light)
.normal(0,0,1)
.next();
vc.vertex(matrix, size, size, 0)
.color(255,255,255,255)
.texture(1,1)
.overlay(OverlayTexture.DEFAULT_UV)
.light(light)
.normal(0,0,1)
.next();
vc.vertex(matrix, 0, size, 0)
.color(255,255,255,255)
.texture(0,1)
.overlay(OverlayTexture.DEFAULT_UV)
.light(light)
.normal(0,0,1)
.next();
matrices.pop();
}

View File

@@ -4,9 +4,9 @@ import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.client.sound.PositionedSoundInstance;
import net.minecraft.client.sound.SoundInstance;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvent;
import net.minecraft.sound.SoundEvents;
import net.minecraft.util.Identifier;
import java.util.*;
@@ -19,151 +19,144 @@ public class VideoManager {
private static final int TOTAL_FRAMES = 193;
private static final int TICKS_PER_FRAME = 1;
private static final int SOUND_DELAY_TICKS = 4;
private static final Map<UUID, VideoInstance> activeVideos = new HashMap<>();
private static final Map<UUID, SoundInstance> activeSounds = new HashMap<>();
private static final List<Identifier> FRAMES = new ArrayList<>();
/*
* Load frames (call once during client init)
*/
/* Load frames (call once during client init) */
public static void init() {
if (!FRAMES.isEmpty()) return;
for (int i = 0; i < TOTAL_FRAMES; i++) {
for (int i = 1; i <= TOTAL_FRAMES; i++) {
String frame = String.format("textures/video/frame_%03d.png", i);
FRAMES.add(new Identifier(MOD_ID, frame));
}
}
/*
* Start playing the video on a player
*/
/* Start or restart video for a player */
public static void startVideo(String playerUuid) {
if (client.world == null) return;
UUID uuid = UUID.fromString(playerUuid);
activeVideos.put(uuid, new VideoInstance(uuid));
playSound(uuid);
}
/*
* Tick method (call every client tick)
*/
public static void tick() {
if (activeVideos.isEmpty()) return;
Iterator<Map.Entry<UUID, VideoInstance>> iterator = activeVideos.entrySet().iterator();
while (iterator.hasNext()) {
VideoInstance instance = iterator.next().getValue();
instance.tick();
if (instance.finished()) {
iterator.remove();
}
// Stop existing video and sound if playing
if (activeVideos.containsKey(uuid)) {
stopVideo(uuid);
}
// Start new video with sound delay
VideoInstance instance = new VideoInstance(uuid, SOUND_DELAY_TICKS);
activeVideos.put(uuid, instance);
}
/* Tick method (call every client tick) */
public static void tick() {
if (activeVideos.isEmpty()) return;
List<UUID> finishedVideos = new ArrayList<>();
for (Map.Entry<UUID, VideoInstance> entry : activeVideos.entrySet()) {
VideoInstance instance = entry.getValue();
instance.tick();
// Start sound after delay
if (!activeSounds.containsKey(entry.getKey()) && instance.shouldPlaySound()) {
playSound(entry.getKey());
}
if (instance.finished()) {
finishedVideos.add(entry.getKey());
}
}
// Stop finished videos and their sounds
for (UUID uuid : finishedVideos) {
stopVideo(uuid);
}
}
/*
* Check if a player currently has a video playing
*/
public static boolean isPlaying(UUID player) {
return activeVideos.containsKey(player);
}
/*
* Get current frame texture for player
*/
public static Identifier getCurrentFrame(UUID player) {
VideoInstance instance = activeVideos.get(player);
if (instance == null) return null;
int frameIndex = instance.frame;
if (frameIndex >= FRAMES.size()) return null;
return FRAMES.get(frameIndex);
}
private static void stopVideo(UUID playerUuid) {
activeVideos.remove(playerUuid);
SoundInstance sound = activeSounds.remove(playerUuid);
if (sound != null) {
client.getSoundManager().stop(sound);
}
}
/*
* Play sound from the player's location
*/
private static void playSound(UUID playerUuid) {
if (activeSounds.containsKey(playerUuid)) return;
ClientWorld world = client.world;
if (world == null) return;
var player = world.getPlayerByUuid(playerUuid);
ClientPlayerEntity player = (ClientPlayerEntity) world.getPlayerByUuid(playerUuid);
if (player == null) return;
Identifier soundId = new Identifier(MOD_ID, "firtana");
SoundEvent soundEvent = SoundEvent.of(soundId);
client.getSoundManager().play(
new PositionedSoundInstance(
soundEvent,
SoundCategory.PLAYERS,
1.0f, // volume
1.0f, // pitch
player.getRandom(),
player.getX(),
player.getY(),
player.getZ()
)
PositionedSoundInstance soundInstance = new PositionedSoundInstance(
soundEvent,
SoundCategory.PLAYERS,
1.0f,
1.0f,
player.getRandom(),
player.getX(),
player.getY(),
player.getZ()
);
client.getSoundManager().play(soundInstance);
activeSounds.put(playerUuid, soundInstance);
}
/*
* Video instance for each player
*/
/* Video instance with sound delay support */
private static class VideoInstance {
UUID player;
int frame = 0;
int tickCounter = 0;
int soundDelay;
VideoInstance(UUID player) {
VideoInstance(UUID player, int soundDelayTicks) {
this.player = player;
this.soundDelay = soundDelayTicks;
}
void tick() {
tickCounter++;
if (tickCounter >= TICKS_PER_FRAME) {
frame++;
tickCounter = 0;
}
if (soundDelay > 0) {
soundDelay--;
}
}
boolean shouldPlaySound() {
return soundDelay <= 0;
}
boolean finished() {
return frame >= TOTAL_FRAMES;
}
}
}

View File

@@ -0,0 +1,21 @@
package dev.tggamesyt.szar.client.mixin;
import dev.tggamesyt.szar.client.VideoHeadFeature;
import net.minecraft.client.render.entity.EntityRendererFactory;
import net.minecraft.client.render.entity.PlayerEntityRenderer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(PlayerEntityRenderer.class)
public abstract class PlayerEntityRendererMixin {
@Inject(method = "<init>", at = @At("RETURN"))
private void addVideoFeature(EntityRendererFactory.Context ctx, boolean slim, CallbackInfo ci) {
PlayerEntityRenderer renderer = (PlayerEntityRenderer)(Object)this;
renderer.addFeature(new VideoHeadFeature(renderer));
}
}

View File

@@ -6,6 +6,7 @@
"client": [
"ItemRendererMixin",
"MouseMixin",
"PlayerEntityRendererMixin",
"PlayerModelMixin",
"RadiatedItemRendererMixin",
"RadiationHeartMixin",