Skip to content

Commit

Permalink
Lock camera, change background, implement simple audio support, fix s…
Browse files Browse the repository at this point in the history
…ome text not rendering correctly
  • Loading branch information
Patbox committed Dec 22, 2024
1 parent 569268b commit 1685dd1
Show file tree
Hide file tree
Showing 11 changed files with 183 additions and 61 deletions.
8 changes: 4 additions & 4 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ mod_version = 1.0.0
org.gradle.jvmargs = -Xmx1G

# Versions
minecraft_version = 1.21.3
yarn_mappings = 1.21.3+build.2
minecraft_version = 1.21.4
yarn_mappings = 1.21.4+build.2
loader_version = 0.16.9
fabric_version = 0.110.0+1.21.3
fabric_version = 0.112.0+1.21.4

plasmid_version = 0.6.1+1.21.3
plasmid_version = 0.6.2+1.21.4
wasmtime_version = 0.18.0
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,20 @@
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.entity.effect.StatusEffects;
import net.minecraft.entity.passive.MuleEntity;
import net.minecraft.entity.player.PlayerPosition;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.c2s.play.PlayerInputC2SPacket;
import net.minecraft.network.packet.c2s.play.PlayerLoadedC2SPacket;
import net.minecraft.network.packet.s2c.play.GameStateChangeS2CPacket;
import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket;
import net.minecraft.network.packet.s2c.play.PositionFlag;
import net.minecraft.network.packet.s2c.play.SetCameraEntityS2CPacket;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.PlayerInput;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.GameMode;
import net.minecraft.world.dimension.DimensionType;
import net.minecraft.world.dimension.DimensionTypes;
import xyz.nucleoid.fantasy.RuntimeWorldConfig;
import xyz.nucleoid.fantasy.util.VoidChunkGenerator;
import xyz.nucleoid.plasmid.api.game.*;
Expand All @@ -39,27 +40,24 @@
import xyz.nucleoid.stimuli.event.player.PlayerDamageEvent;
import xyz.nucleoid.stimuli.event.player.PlayerDeathEvent;

import java.util.Set;

public class ConsoleBoxGame implements GamePlayerEvents.Add, GameActivityEvents.Destroy, GameActivityEvents.Tick, GameActivityEvents.Enable, GamePlayerEvents.Remove, GamePlayerEvents.Accept, PlayerDamageEvent, PlayerDeathEvent, PlayerC2SPacketEvent {
private final Thread thread;

private final GameSpace gameSpace;
private final ServerWorld world;
private final ConsoleBoxConfig config;

private final GameCanvas canvas;
private final VirtualDisplay display;

private final Entity cameraEntity;
private final ServerPlayerEntity[] players = new ServerPlayerEntity[4];
private volatile boolean runs = true;
private int playerCount = 0;

public ConsoleBoxGame(GameSpace gameSpace, ServerWorld world, ConsoleBoxConfig config, GameCanvas canvas, VirtualDisplay display) {
public ConsoleBoxGame(GameSpace gameSpace, ServerWorld world, ConsoleBoxConfig config, GameCanvas canvas, Entity cameraEntity, VirtualDisplay display) {
this.gameSpace = gameSpace;
this.world = world;
this.config = config;

this.cameraEntity = cameraEntity;
this.canvas = canvas;
this.display = display;
this.thread = new Thread(this::runThread);
Expand All @@ -70,6 +68,7 @@ public static void setRules(GameActivity activity) {
activity.deny(GameRuleType.BREAK_BLOCKS);
activity.deny(GameRuleType.CRAFTING);
activity.deny(GameRuleType.DISMOUNT_VEHICLE);
activity.deny(GameRuleType.STOP_SPECTATING_ENTITY);
activity.deny(GameRuleType.FALL_DAMAGE);
activity.deny(GameRuleType.FIRE_TICK);
activity.deny(GameRuleType.FLUID_FLOW);
Expand All @@ -92,6 +91,7 @@ public static GameOpenProcedure open(GameOpenContext<ConsoleBoxConfig> context)
ConsoleBoxConfig config = context.config();

RuntimeWorldConfig worldConfig = new RuntimeWorldConfig()
.setDimensionType(DimensionTypes.OVERWORLD_CAVES)
.setGenerator(new VoidChunkGenerator(context.server()));


Expand All @@ -103,8 +103,27 @@ public static GameOpenProcedure open(GameOpenContext<ConsoleBoxConfig> context)
.invisible()
.build();

ConsoleBoxGame phase = new ConsoleBoxGame(activity.getGameSpace(), world, config, canvas, display);
audioController.setOutput(phase);
var camera = EntityType.ITEM_DISPLAY.create(world, SpawnReason.LOAD);
assert camera != null;
camera.setInvisible(true);
camera.setPosition(canvas.getSpawnPos());
camera.setYaw(canvas.getSpawnAngle());
world.spawnEntity(camera);

var leftAudio = EntityType.ITEM_DISPLAY.create(world, SpawnReason.LOAD);
assert leftAudio != null;
leftAudio.setInvisible(true);
leftAudio.setPosition(canvas.getSpawnPos().add(2, 0, 0));
world.spawnEntity(leftAudio);

var rightAudio = EntityType.ITEM_DISPLAY.create(world, SpawnReason.LOAD);
assert rightAudio != null;
rightAudio.setInvisible(true);
rightAudio.setPosition(canvas.getSpawnPos().add(-2, 0, 0));
world.spawnEntity(rightAudio);

ConsoleBoxGame phase = new ConsoleBoxGame(activity.getGameSpace(), world, config, canvas, camera, display);
audioController.setOutput(camera, leftAudio, rightAudio, activity.getGameSpace().getPlayers()::sendPacket);
ConsoleBoxGame.setRules(activity);

PlayerLimiter.addTo(activity, new PlayerLimiterConfig(phase.players.length));
Expand All @@ -130,6 +149,7 @@ public void onAddPlayer(ServerPlayerEntity player) {
this.display.addPlayer(player);
this.display.getCanvas().addPlayer(player);
player.networkHandler.sendPacket(new GameStateChangeS2CPacket(GameStateChangeS2CPacket.GAME_MODE_CHANGED, GameMode.SPECTATOR.getId()));
player.networkHandler.sendPacket(new SetCameraEntityS2CPacket(this.cameraEntity));
}

@Override
Expand Down Expand Up @@ -161,20 +181,18 @@ public EventResult onPacket(ServerPlayerEntity player, Packet<?> packet) {

this.canvas.updateGamepad(id, input.forward(), input.left(), input.backward(), input.right(),
isSneaking, isJumping);
} else if (packet instanceof PlayerLoadedC2SPacket) {
player.networkHandler.sendPacket(new SetCameraEntityS2CPacket(this.cameraEntity));
}

return EventResult.PASS;
}

@Override
public void onTick() {
for (var player : this.players) {
if (player != null) {
PlayerPosition pos = new PlayerPosition(Vec3d.ZERO, Vec3d.ZERO, 180, 0);
Set<PositionFlag> flags = Set.of();

player.networkHandler.sendPacket(
new PlayerPositionLookS2CPacket(0, pos, flags));
for (var player : this.gameSpace.getPlayers()) {
if (player.getCameraEntity() != this.cameraEntity && this.cameraEntity.age > 2) {
player.setCameraEntity(this.cameraEntity);
}
}
}
Expand Down Expand Up @@ -213,9 +231,8 @@ public JoinAcceptorResult onAcceptPlayers(JoinAcceptor acceptor) {
return acceptor.teleport(this.world, spawnPos).thenRunForEach(player -> {
this.players[x] = player;
this.playerCount++;

this.spawnMount(spawnPos, this.players[x]);
this.initializePlayer(this.players[x], GameMode.ADVENTURE);
this.spawnMount(spawnPos.add(0, 10, 0), this.players[x]);
this.initializePlayer(this.players[x], GameMode.SPECTATOR);
});
}
}
Expand Down Expand Up @@ -261,17 +278,18 @@ public void onRemovePlayer(ServerPlayerEntity player) {
}

// Utilities
private Entity spawnMount(Vec3d playerPos, ServerPlayerEntity player) {
private void spawnMount(Vec3d playerPos, ServerPlayerEntity player) {
MuleEntity mount = EntityType.MULE.create(this.world, SpawnReason.JOCKEY);
mount.calculateDimensions();
double y = playerPos.getY() - 1.25f;
mount.setPos(playerPos.getX(), y, playerPos.getZ());
mount.setPos(playerPos.getX(), y, playerPos.getZ() + 2);
mount.setYaw(this.canvas.getSpawnAngle());

mount.setAiDisabled(true);
mount.setNoGravity(true);
mount.setSilent(true);
mount.setPersistent();
mount.getAttributeInstance(EntityAttributes.SCALE).setBaseValue(0);

// Prevent mount from being visible
mount.addStatusEffect(this.createInfiniteStatusEffect(StatusEffects.INVISIBILITY));
Expand All @@ -283,7 +301,6 @@ private Entity spawnMount(Vec3d playerPos, ServerPlayerEntity player) {
this.world.spawnEntity(mount);
player.startRiding(mount, true);

return null;
}

private void initializePlayer(ServerPlayerEntity player, GameMode gameMode) {
Expand Down
75 changes: 56 additions & 19 deletions src/main/java/io/github/haykam821/consolebox/game/GameCanvas.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import io.github.haykam821.consolebox.ConsoleBox;
import io.github.haykam821.consolebox.game.audio.AudioChannel;
import io.github.haykam821.consolebox.game.audio.AudioController;
import io.github.haykam821.consolebox.game.audio.ToneDuty;
import io.github.haykam821.consolebox.game.audio.TonePan;
import io.github.haykam821.consolebox.game.palette.GamePalette;
import io.github.haykam821.consolebox.game.render.FramebufferRendering;
import io.github.kawamuray.wasmtime.Module;
Expand All @@ -22,8 +24,6 @@
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
Expand All @@ -33,26 +33,28 @@
public class GameCanvas {
private static final Logger LOGGER = LoggerFactory.getLogger("GameCanvas");

private static final CanvasImage BACKGROUND;
private static final CanvasImage DEFAULT_BACKGROUND = readImage("default_background");
private static final CanvasImage DEFAULT_OVERLAY = readImage("default_overlay");
private static final int BACKGROUND_SCALE = 2;

static {
private static CanvasImage readImage(String path) {
CanvasImage temp;
try {
temp = CanvasImage.from(ImageIO.read(
Files.newInputStream(FabricLoader.getInstance().getModContainer(ConsoleBox.MOD_ID).get().findPath("background.png").get())));
Files.newInputStream(FabricLoader.getInstance().getModContainer(ConsoleBox.MOD_ID).get().findPath("data/consolebox/background/" + path + ".png").get())));
} catch (Throwable e) {
temp = null;
temp = new CanvasImage(128, 128);

e.printStackTrace();
}
BACKGROUND = temp;
return temp;
}

private static final int RENDER_SCALE = 1;
private static final int MAP_SIZE = FilledMapItem.field_30907;
private static final int SECTION_SIZE = MathHelper.ceil(HardwareConstants.SCREEN_WIDTH * RENDER_SCALE / (double) MAP_SIZE);
private static final int SECTION_HEIGHT = 3;
private static final int SECTION_WIDTH = 4;
private static final int SECTION_HEIGHT = 6;
private static final int SECTION_WIDTH = 8;

private static final Consumer0 EMPTY_CALLBACK = () -> {
};
Expand Down Expand Up @@ -88,8 +90,25 @@ public GameCanvas(ConsoleBoxConfig config, AudioController audioController) {
this.palette = new GamePalette(this.memory);
this.canvas = DrawableCanvas.create(SECTION_WIDTH, SECTION_HEIGHT);
CanvasUtils.clear(this.canvas, CanvasColor.GRAY_HIGH);
if (BACKGROUND != null) {
CanvasUtils.draw(this.canvas, 0, 0, BACKGROUND);
if (DEFAULT_BACKGROUND != null) {
var background = DEFAULT_BACKGROUND;
var width = background.getWidth() * BACKGROUND_SCALE;
var height = background.getHeight() * BACKGROUND_SCALE;
var repeatsX = Math.ceilDiv(this.canvas.getWidth(), width);
var repeatsY = Math.ceilDiv(this.canvas.getHeight(), height);

for (int x = 0; x < repeatsX; x++) {
for (int y = 0; y < repeatsY; y++) {
CanvasUtils.draw(this.canvas, x * width, y * height, width, height, background);
}
}
}

if (DEFAULT_OVERLAY != null) {
var background = DEFAULT_OVERLAY;
var width = background.getWidth() * BACKGROUND_SCALE;
var height = background.getHeight() * BACKGROUND_SCALE;
CanvasUtils.draw(this.canvas, this.canvas.getWidth() / 2 - width / 2, this.canvas.getHeight() / 2 - height / 2, width, height, background);
}

var text = """
Expand Down Expand Up @@ -226,23 +245,23 @@ private void rect(int x, int y, int width, int height) {
FramebufferRendering.drawRect(buffer, fillColor, strokeColor, x, y, width, height);
}

private void drawText(String string, int x, int y) {
// This function needs to work on raw bytes, as Java strips invalid chars
private void drawText(byte[] string, int x, int y) {
ByteBuffer buffer = this.memory.getFramebuffer();
int drawColors = this.memory.readDrawColors();

FramebufferRendering.drawText(buffer, drawColors, string, x, y);
}

private void text(int string, int x, int y) {
this.drawText(this.memory.readString(string), x, y);
this.drawText(this.memory.readStringRaw(string), x, y);
}

private void textUtf8(int string, int length, int x, int y) {
this.drawText(this.memory.readUnterminatedString(string, length, StandardCharsets.UTF_8), x, y);
this.drawText(this.memory.readUnterminatedStringRaw8(string, length), x, y);
}

private void textUtf16(int string, int length, int x, int y) {
this.drawText(this.memory.readUnterminatedString(string, length, StandardCharsets.UTF_16LE), x, y);
this.drawText(this.memory.readUnterminatedStringRaw16LE(string, length), x, y);
}

private void tone(int frequency, int duration, int volume, int flags) {
Expand All @@ -253,12 +272,30 @@ private void tone(int frequency, int duration, int volume, int flags) {
default -> AudioChannel.NOISE;
};

var duty = switch ((flags >> 2) & 0b11) {
case 0 -> ToneDuty.MODE_12_5;
case 1 -> ToneDuty.MODE_25;
case 2 -> ToneDuty.MODE_50;
default -> ToneDuty.MODE_75;
};

var pan = switch ((flags >> 4) & 0b11) {
case 1 -> TonePan.LEFT;
case 2 -> TonePan.RIGHT;
default -> TonePan.CENTER;
};


var freq1 = frequency & 0xFFFF;
var freq2 = (frequency >> 16) & 0xFFFF;

var sustainTime = duration & 0xFF;

this.audioController.playSound(channel, freq1, sustainTime);
var volumeActual = volume & 0xFF;
var volumePeak = (volume >> 8) & 0xFF;


this.audioController.playSound(channel, duty, pan, freq1, freq2, sustainTime, volumeActual, volumePeak);

// Intentionally empty as sound is unsupported
}
Expand Down Expand Up @@ -400,13 +437,13 @@ public void start() {
}

public BlockPos getDisplayPos() {
return new BlockPos(-SECTION_WIDTH, SECTION_HEIGHT, 0);
return new BlockPos(-SECTION_WIDTH, SECTION_HEIGHT + 100, 0);
}

public Vec3d getSpawnPos() {
BlockPos displayPos = this.getDisplayPos();

return new Vec3d(displayPos.getX() + SECTION_WIDTH * 0.5, displayPos.getY() - SECTION_HEIGHT * 0.5f, 1.1);
return new Vec3d(displayPos.getX() + SECTION_WIDTH * 0.5, displayPos.getY() - SECTION_HEIGHT * 0.5f + 1, 1.5);
}

public int getSpawnAngle() {
Expand Down
24 changes: 21 additions & 3 deletions src/main/java/io/github/haykam821/consolebox/game/GameMemory.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ public boolean readSystemPreserveFramebuffer() {
}

public String readString(int start) {
return new String(readStringRaw(start), StandardCharsets.US_ASCII);
}
public byte[] readStringRaw(int start) {
int length = 0;

while (this.buffer.hasRemaining()) {
Expand All @@ -87,14 +90,29 @@ public String readString(int start) {
if (character == 0x00) {
byte[] bytes = new byte[length];
this.buffer.get(start, bytes, 0, length);

return new String(bytes, StandardCharsets.US_ASCII);
return bytes;
} else {
length += 1;
}
}

return "";
return new byte[0];
}

public byte[] readUnterminatedStringRaw8(int start, int length) {
var bytes = new byte[length];
for (int i = 0; i < length; i++) {
bytes[i] = this.buffer.get(start + i);
}
return bytes;
}

public byte[] readUnterminatedStringRaw16LE(int start, int length) {
var bytes = new byte[length];
for (int i = 0; i < length; i++) {
bytes[i] = this.buffer.get(start + i * 2);
}
return bytes;
}

public String readUnterminatedString(int start, int length, Charset charset) {
Expand Down
Loading

0 comments on commit 1685dd1

Please sign in to comment.