tictactoe and cosmetics system fix

This commit is contained in:
2026-03-22 16:00:25 +01:00
parent 858b8b9169
commit 4bd026f40a
20 changed files with 738 additions and 97 deletions

View File

@@ -57,6 +57,8 @@ public class ServerCosmetics {
int size = buf.readInt();
List<MojangCape> list = new ArrayList<>();
System.out.println("SZAR: server received Mojang capes for " + uuid + ", count=" + size);
for (int i = 0; i < size; i++) {
MojangCape c = new MojangCape();
c.id = buf.readString();
@@ -67,25 +69,6 @@ public class ServerCosmetics {
PLAYER_MOJANG_CAPES.put(uuid, list);
});
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
ServerPlayerEntity player = handler.getPlayer();
// Send this player's own cosmetics to themselves
UserCosmetics user = USERS.get(player.getUuid());
if (user != null) {
sync(player, user);
}
// Optionally: send all other players' cosmetics to this new player
for (ServerPlayerEntity other : server.getPlayerManager().getPlayerList()) {
if (other.equals(player)) continue;
UserCosmetics otherUser = USERS.get(other.getUuid());
if (otherUser != null) {
sync(player, otherUser); // send other players cosmetics to the new player
}
}
});
}
/* ---------------- LOAD JSON ---------------- */
@@ -152,20 +135,27 @@ public class ServerCosmetics {
private static void registerCommand() {
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) ->
dispatcher.register(CommandManager.literal("cape")
.then(CommandManager.argument("id", StringArgumentType.greedyString()) // <-- change here
.then(CommandManager.argument("id", StringArgumentType.greedyString())
.suggests((ctx, builder) -> {
ServerPlayerEntity player = ctx.getSource().getPlayer();
if (player == null) return builder.buildFuture();
// Custom capes
for (String id : CAPES.keySet())
builder.suggest(id);
UserCosmetics user = USERS.get(player.getUuid());
// Mojang capes
// Only suggest capes this player actually owns
if (user != null) {
for (String id : user.ownedCapes) {
builder.suggest(id);
}
}
// Mojang capes from server-side map
List<MojangCape> mojang = PLAYER_MOJANG_CAPES.get(player.getUuid());
System.out.println("SZAR: suggestions - mojang capes for " + player.getName().getString() + ": " + (mojang == null ? "null" : mojang.size()));
if (mojang != null) {
for (MojangCape c : mojang)
for (MojangCape c : mojang) {
builder.suggest(c.name);
}
}
builder.suggest("none");
@@ -173,10 +163,15 @@ public class ServerCosmetics {
})
.executes(ctx -> {
ServerPlayerEntity player = ctx.getSource().getPlayer();
String id = StringArgumentType.getString(ctx, "id"); // this now includes spaces
String id = StringArgumentType.getString(ctx, "id");
UserCosmetics user = USERS.get(player.getUuid());
if (user == null) return 0;
// Create a default entry if player has no cosmetics profile
UserCosmetics user = USERS.computeIfAbsent(player.getUuid(), k -> {
UserCosmetics u = new UserCosmetics();
u.nameType = NameType.STATIC;
u.ownedCapes = new ArrayList<>();
return u;
});
// Deselect
if (id.equalsIgnoreCase("none")) {
@@ -186,12 +181,12 @@ public class ServerCosmetics {
return 1;
}
// Mojang cape selection (only from fetched list)
// Mojang cape selection
List<MojangCape> mojang = PLAYER_MOJANG_CAPES.get(player.getUuid());
if (mojang != null) {
for (MojangCape c : mojang) {
if (c.name.equalsIgnoreCase(id)) {
user.selectedCape = c.id; // vanilla cape
user.selectedCape = c.id;
sync(player, user);
player.sendMessage(Text.literal("Equipped Mojang cape: " + c.name), false);
return 1;
@@ -199,7 +194,7 @@ public class ServerCosmetics {
}
}
// Custom
// Custom cape check
if (!user.ownedCapes.contains(id)) {
player.sendMessage(Text.literal("You don't own this cape."), false);
return 0;
@@ -216,49 +211,47 @@ public class ServerCosmetics {
/* ---------------- SYNC ---------------- */
public static void sync(ServerPlayerEntity player, UserCosmetics user) {
List<ServerPlayerEntity> original =
player.getServer().getPlayerManager().getPlayerList();
List<ServerPlayerEntity> list = new ArrayList<>(original);
if (!list.contains(player)) {list.add(player);}
for (ServerPlayerEntity p : list) {
PacketByteBuf buf = PacketByteBufs.create();
// Write player UUID first
buf.writeUuid(player.getUuid());
// Cosmetic data
buf.writeEnumConstant(user.nameType);
buf.writeBoolean(user.staticColor != null);
if (user.staticColor != null) buf.writeInt(user.staticColor);
buf.writeBoolean(user.gradientStart != null);
if (user.gradientStart != null) {
buf.writeInt(user.gradientStart);
buf.writeInt(user.gradientEnd);
}
String textureUrl = null;
if (user.selectedCape != null) {
textureUrl = CAPES.get(user.selectedCape);
if (textureUrl == null) {
List<MojangCape> mojang = PLAYER_MOJANG_CAPES.get(player.getUuid());
if (mojang != null) {
for (MojangCape c : mojang) {
if (c.id.equalsIgnoreCase(user.selectedCape)) {
textureUrl = c.url;
break;
}
}
}
}
}
buf.writeString(textureUrl == null ? "" : textureUrl);
ServerPlayNetworking.send(p, SYNC_PACKET, buf);
// Send ONE player's cosmetics to ALL online players (used for /cape changes)
public static void sync(ServerPlayerEntity owner, UserCosmetics user) {
for (ServerPlayerEntity p : owner.getServer().getPlayerManager().getPlayerList()) {
syncTo(p, owner, user);
}
}
public enum NameType {
STATIC,
GRADIENT
}
// Send ONE player's cosmetics to ONE recipient
public static void syncTo(ServerPlayerEntity recipient, ServerPlayerEntity owner,
UserCosmetics user) {
PacketByteBuf buf = PacketByteBufs.create();
buf.writeUuid(owner.getUuid()); // whose cosmetics these are
buf.writeEnumConstant(user.nameType);
buf.writeBoolean(user.staticColor != null);
if (user.staticColor != null) buf.writeInt(user.staticColor);
buf.writeBoolean(user.gradientStart != null);
if (user.gradientStart != null) {
buf.writeInt(user.gradientStart);
buf.writeInt(user.gradientEnd);
}
String textureUrl = null;
if (user.selectedCape != null) {
textureUrl = CAPES.get(user.selectedCape);
if (textureUrl == null) {
List<MojangCape> mojang = PLAYER_MOJANG_CAPES.get(owner.getUuid());
if (mojang != null) {
for (MojangCape c : mojang) {
if (c.id.equalsIgnoreCase(user.selectedCape)) {
textureUrl = c.url;
break;
}
}
}
}
}
buf.writeString(textureUrl == null ? "" : textureUrl);
ServerPlayNetworking.send(recipient, SYNC_PACKET, buf);
}
}

View File

@@ -186,6 +186,7 @@ public class Szar implements ModInitializer {
RegistryKeys.DIMENSION_TYPE,
new Identifier(MOD_ID, "backrooms")
);
public static final Map<UUID, BlockPos> tttActivePlayers = new java.util.HashMap<>();
public static final Block SZAR_BLOCK =
new SzarBlock();
public static final Block URANIUM_BLOCK =
@@ -383,6 +384,7 @@ public class Szar implements ModInitializer {
entries.add(Szar.CAN_OF_BEANS);
entries.add(Szar.ALMOND_WATER);
entries.add(Szar.KEBAB);
entries.add(Szar.TIC_TAC_TOE_ITEM);
// crazy weponary
entries.add(Szar.BULLET_ITEM);
entries.add(Szar.AK47);
@@ -611,19 +613,32 @@ public class Szar implements ModInitializer {
PlayerMovementManager.init();
ServerCosmetics.init();
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
ServerPlayerEntity player = handler.getPlayer();
ServerPlayerEntity joiner = handler.getPlayer();
ServerCosmetics.UserCosmetics user = USERS.get(player.getUuid());
if (user != null) {
// AUTO SELECT FIRST CAPE IF NONE SELECTED
if (user.selectedCape == null && !user.ownedCapes.isEmpty()) {
user.selectedCape = user.ownedCapes.get(0);
} else {
user.selectedCape = null;
// Send joiner's own cosmetics to themselves
ServerCosmetics.UserCosmetics joinerUser = USERS.get(joiner.getUuid());
if (joinerUser != null) {
if (joinerUser.selectedCape == null && !joinerUser.ownedCapes.isEmpty()) {
joinerUser.selectedCape = joinerUser.ownedCapes.get(0);
}
ServerCosmetics.syncTo(joiner, joiner, joinerUser);
}
sync(player, user);
// Send all other online players' cosmetics to the joiner
for (ServerPlayerEntity other : server.getPlayerManager().getPlayerList()) {
if (other.equals(joiner)) continue;
ServerCosmetics.UserCosmetics otherUser = USERS.get(other.getUuid());
if (otherUser != null) {
ServerCosmetics.syncTo(joiner, other, otherUser);
}
}
// Send joiner's cosmetics to all other online players
if (joinerUser != null) {
for (ServerPlayerEntity other : server.getPlayerManager().getPlayerList()) {
if (other.equals(joiner)) continue;
ServerCosmetics.syncTo(other, joiner, joinerUser);
}
}
});
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
@@ -1313,7 +1328,38 @@ public class Szar implements ModInitializer {
});
ServerTickEvents.END_SERVER_TICK.register(DrunkEffect::tick);
FartManager.register();
ServerPlayNetworking.registerGlobalReceiver(TTT_MAKE_MOVE, (server, player, handler, buf, sender) -> {
BlockPos pos = buf.readBlockPos();
int cell = buf.readInt();
server.execute(() -> {
if (player.getWorld().getBlockEntity(pos) instanceof TicTacToeBlockEntity be) {
be.handleMove(player, cell);
}
});
});
}
public static final Block TIC_TAC_TOE_BLOCK = Registry.register(
Registries.BLOCK, new Identifier(MOD_ID, "tictactoe"),
new TicTacToeBlock(AbstractBlock.Settings.create().strength(2f))
);
public static final BlockItem TIC_TAC_TOE_ITEM = Registry.register(
Registries.ITEM, new Identifier(MOD_ID, "tictactoe"),
new BlockItem(TIC_TAC_TOE_BLOCK, new Item.Settings())
);
public static final BlockEntityType<TicTacToeBlockEntity> TIC_TAC_TOE_ENTITY =
Registry.register(
Registries.BLOCK_ENTITY_TYPE, new Identifier(MOD_ID, "tictactoe"),
FabricBlockEntityTypeBuilder.create(TicTacToeBlockEntity::new,
TIC_TAC_TOE_BLOCK).build()
);
// Networking
public static final Identifier TTT_OPEN_SCREEN = new Identifier(MOD_ID, "ttt_open");
public static final Identifier TTT_MAKE_MOVE = new Identifier(MOD_ID, "ttt_move");
public static final Identifier TTT_STATE_SYNC = new Identifier(MOD_ID, "ttt_sync");
public static final Identifier TTT_CLOSE_SCREEN = new Identifier(MOD_ID, "ttt_close");
// Blocks
public static final TrackerBlock TRACKER_BLOCK = Registry.register(
Registries.BLOCK, new Identifier(MOD_ID, "tracker"),

View File

@@ -0,0 +1,82 @@
package dev.tggamesyt.szar;
import net.minecraft.block.*;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityTicker;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
public class TicTacToeBlock extends BlockWithEntity {
public TicTacToeBlock(Settings settings) {
super(settings);
}
@Override
public BlockRenderType getRenderType(BlockState state) {
return BlockRenderType.MODEL;
}
private static final VoxelShape SHAPE = VoxelShapes.union(
VoxelShapes.cuboid(0f, 0f, 0f, 1f, 0.75f, 1f)
);
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world,
BlockPos pos, ShapeContext ctx) {
return SHAPE;
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockView world,
BlockPos pos, ShapeContext ctx) {
return SHAPE;
}
@Nullable
@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return new TicTacToeBlockEntity(pos, state);
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos,
PlayerEntity player, Hand hand, BlockHitResult hit) {
if (world.isClient) return ActionResult.SUCCESS;
if (!(player instanceof ServerPlayerEntity serverPlayer)) return ActionResult.PASS;
if (!(world.getBlockEntity(pos) instanceof TicTacToeBlockEntity be)) return ActionResult.PASS;
be.handlePlayerJoin(serverPlayer, pos);
return ActionResult.SUCCESS;
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(
World world, BlockState state, BlockEntityType<T> type) {
if (world.isClient) return null;
return type == Szar.TIC_TAC_TOE_ENTITY
? (w, pos, s, be) -> TicTacToeBlockEntity.tick(w, pos, s,
(TicTacToeBlockEntity) be)
: null;
}
@Override
public void onBreak(World world, BlockPos pos, BlockState state, PlayerEntity player) {
if (!world.isClient && world.getBlockEntity(pos) instanceof TicTacToeBlockEntity be) {
be.closeScreenForAll(world.getServer());
if (be.player1 != null) Szar.tttActivePlayers.remove(be.player1);
if (be.player2 != null) Szar.tttActivePlayers.remove(be.player2);
}
super.onBreak(world, pos, state, player);
}
}

View File

@@ -0,0 +1,270 @@
package dev.tggamesyt.szar;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import java.util.UUID;
public class TicTacToeBlockEntity extends BlockEntity {
// 0 = empty, 1 = O (player1), 2 = X (player2)
public int[] board = new int[9];
public UUID player1 = null; // O
public UUID player2 = null; // X
public int currentTurn = 1; // 1 = O's turn, 2 = X's turn
public int winner = 0; // 0 = ongoing, 1 = O wins, 2 = X wins, 3 = draw
public final java.util.Set<UUID> spectators = new java.util.HashSet<>();
public int resetTimer = -1; // -1 = no reset pending
public TicTacToeBlockEntity(BlockPos pos, BlockState state) {
super(Szar.TIC_TAC_TOE_ENTITY, pos, state);
}
public static void tick(World world, BlockPos pos, BlockState state,
TicTacToeBlockEntity entity) {
if (!world.isClient && entity.resetTimer > 0) {
entity.resetTimer--;
if (entity.resetTimer == 0) {
entity.resetTimer = -1;
entity.resetGame(((net.minecraft.server.world.ServerWorld) world).getServer());
entity.syncToPlayers(((net.minecraft.server.world.ServerWorld) world).getServer());
}
}
}
public void handlePlayerJoin(ServerPlayerEntity player, BlockPos pos) {
UUID uuid = player.getUuid();
// Check if already in a different game
BlockPos activePos = Szar.tttActivePlayers.get(uuid);
if (activePos != null && !activePos.equals(pos)) {
player.sendMessage(Text.literal("§cYou are already in a game at another board!"), true);
return;
}
// Rejoin existing game
if (uuid.equals(player1) || uuid.equals(player2)) {
openScreen(player);
return;
}
if (player1 == null) {
player1 = uuid;
Szar.tttActivePlayers.put(uuid, pos);
player.sendMessage(Text.literal("§aYou are §bO§a! Waiting for second player..."), true);
markDirty();
return;
}
if (player2 == null && !uuid.equals(player1)) {
player2 = uuid;
Szar.tttActivePlayers.put(uuid, pos);
player.sendMessage(Text.literal("§aYou are §cX§a! Game starting!"), true);
ServerPlayerEntity p1 = getServer(player).getPlayerManager().getPlayer(player1);
if (p1 != null) {
p1.sendMessage(Text.literal("§aSecond player joined! Your turn!"), true);
}
openScreenForBoth(player);
markDirty();
return;
}
// At the bottom where it says "game is full", replace with:
if (player1 != null && player2 != null) {
spectators.add(uuid);
player.sendMessage(Text.literal("§7Spectating the match..."), true);
openScreen(player);
markDirty();
}
}
private net.minecraft.server.MinecraftServer getServer(ServerPlayerEntity player) {
return player.getServer();
}
public void openScreenForBoth(ServerPlayerEntity joiner) {
openScreen(joiner);
ServerPlayerEntity p1 = joiner.getServer().getPlayerManager().getPlayer(player1);
if (p1 != null) openScreen(p1);
}
public void openScreen(ServerPlayerEntity player) {
PacketByteBuf buf = PacketByteBufs.create();
buf.writeBlockPos(this.pos);
writeStateToBuf(buf, player.getUuid());
ServerPlayNetworking.send(player, Szar.TTT_OPEN_SCREEN, buf);
}
public void handleMove(ServerPlayerEntity player, int cell) {
if (winner != 0) return;
if (cell < 0 || cell > 8) return;
if (board[cell] != 0) return;
UUID uuid = player.getUuid();
int playerNum = uuid.equals(player1) ? 1 : uuid.equals(player2) ? 2 : 0;
if (playerNum == 0) return;
if (playerNum != currentTurn) {
player.sendMessage(Text.literal("§cNot your turn!"), true);
return;
}
board[cell] = playerNum;
currentTurn = (currentTurn == 1) ? 2 : 1;
checkWinner();
markDirty();
syncToPlayers(player.getServer());
}
private void checkWinner() {
int[][] lines = {
{0,1,2},{3,4,5},{6,7,8},
{0,3,6},{1,4,7},{2,5,8},
{0,4,8},{2,4,6}
};
for (int[] line : lines) {
int a = board[line[0]], b = board[line[1]], c = board[line[2]];
if (a != 0 && a == b && b == c) {
winner = a;
scheduleReset();
return;
}
}
boolean full = true;
for (int cell : board) {
if (cell == 0) { full = false; break; }
}
if (full) {
winner = 3;
scheduleReset();
}
}
private void scheduleReset() {
resetTimer = 60;
markDirty();
}
public void syncToPlayers(net.minecraft.server.MinecraftServer server) {
sendToPlayer(server, player1);
sendToPlayer(server, player2);
for (UUID uuid : spectators) {
sendToPlayer(server, uuid);
}
}
private void sendToPlayer(net.minecraft.server.MinecraftServer server, UUID uuid) {
if (uuid == null) return;
ServerPlayerEntity p = server.getPlayerManager().getPlayer(uuid);
if (p == null) return;
PacketByteBuf buf = PacketByteBufs.create();
buf.writeBlockPos(this.pos);
writeStateToBuf(buf, uuid);
ServerPlayNetworking.send(p, Szar.TTT_STATE_SYNC, buf);
}
public void writeStateToBuf(PacketByteBuf buf, UUID viewerUuid) {
for (int cell : board) buf.writeInt(cell);
buf.writeBoolean(player1 != null);
if (player1 != null) buf.writeUuid(player1);
buf.writeBoolean(player2 != null);
if (player2 != null) buf.writeUuid(player2);
buf.writeInt(currentTurn);
buf.writeInt(winner);
// Is the viewer a spectator?
boolean isSpectator = viewerUuid != null
&& !viewerUuid.equals(player1)
&& !viewerUuid.equals(player2);
buf.writeBoolean(isSpectator);
}
public static State readStateFromBuf(PacketByteBuf buf) {
State s = new State();
s.board = new int[9];
for (int i = 0; i < 9; i++) s.board[i] = buf.readInt();
if (buf.readBoolean()) s.player1 = buf.readUuid();
if (buf.readBoolean()) s.player2 = buf.readUuid();
s.currentTurn = buf.readInt();
s.winner = buf.readInt();
s.isSpectator = buf.readBoolean();
return s;
}
public static class State {
public int[] board;
public UUID player1, player2;
public int currentTurn, winner;
public boolean isSpectator;
}
@Override
public void writeNbt(NbtCompound nbt) {
super.writeNbt(nbt);
nbt.putIntArray("Board", board);
if (player1 != null) nbt.putUuid("Player1", player1);
if (player2 != null) nbt.putUuid("Player2", player2);
nbt.putInt("Turn", currentTurn);
nbt.putInt("Winner", winner);
nbt.putInt("ResetTimer", resetTimer);
}
@Override
public void readNbt(NbtCompound nbt) {
super.readNbt(nbt);
int[] saved = nbt.getIntArray("Board");
if (saved.length == 9) board = saved;
if (nbt.containsUuid("Player1")) player1 = nbt.getUuid("Player1");
if (nbt.containsUuid("Player2")) player2 = nbt.getUuid("Player2");
currentTurn = nbt.getInt("Turn");
winner = nbt.getInt("Winner");
resetTimer = nbt.getInt("ResetTimer");
}
@Override
public NbtCompound toInitialChunkDataNbt() { return createNbt(); }
@Override
public BlockEntityUpdateS2CPacket toUpdatePacket() {
return BlockEntityUpdateS2CPacket.create(this);
}
public void resetGame(net.minecraft.server.MinecraftServer server) {
closeScreenForAll(server); // kick everyone from screen first
if (player1 != null) Szar.tttActivePlayers.remove(player1);
if (player2 != null) Szar.tttActivePlayers.remove(player2);
spectators.clear();
board = new int[9];
player1 = null;
player2 = null;
currentTurn = 1;
winner = 0;
resetTimer = -1;
markDirty();
}
public void closeScreenForAll(net.minecraft.server.MinecraftServer server) {
closeScreenForPlayer(server, player1);
closeScreenForPlayer(server, player2);
for (UUID uuid : spectators) {
closeScreenForPlayer(server, uuid);
}
}
private void closeScreenForPlayer(net.minecraft.server.MinecraftServer server, UUID uuid) {
if (uuid == null) return;
ServerPlayerEntity p = server.getPlayerManager().getPlayer(uuid);
if (p == null) return;
ServerPlayNetworking.send(p, Szar.TTT_CLOSE_SCREEN, PacketByteBufs.empty());
}
}

View File

@@ -0,0 +1,5 @@
{
"variants": {
"": { "model": "szar:block/tictactoe" }
}
}

View File

@@ -185,5 +185,7 @@
"advancement.szar.dontknow.description": "Ask a question from Merl",
"advancement.szar.backrooms.title": "Where did the world go?",
"advancement.szar.backrooms.description": "Step into the Backrooms"
"advancement.szar.backrooms.description": "Step into the Backrooms",
"block.szar.tictactoe": "Tic Tac Toe"
}

View File

@@ -0,0 +1,24 @@
{
"parent": "block/block",
"format_version": "1.9.0",
"credit": "Made with Blockbench",
"textures": {
"0": "szar:block/tictactoe",
"1": "szar:block/plank",
"particle": "szar:block/tictactoe"
},
"elements": [
{
"from": [0, 0, 0],
"to": [16, 12, 16],
"faces": {
"north": {"uv": [0, 0, 16, 12], "texture": "#1"},
"east": {"uv": [0, 0, 16, 12], "texture": "#1"},
"south": {"uv": [0, 0, 16, 12], "texture": "#1"},
"west": {"uv": [0, 0, 16, 12], "texture": "#1"},
"up": {"uv": [0, 0, 16, 16], "texture": "#0"},
"down": {"uv": [0, 0, 16, 16], "texture": "#1"}
}
}
]
}

View File

@@ -0,0 +1,3 @@
{
"parent": "szar:block/tictactoe"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

View File

@@ -1,6 +1,7 @@
{
"values": [
"szar:roulette",
"szar:slot_machine"
"szar:slot_machine",
"szar:tictactoe"
]
}

View File

@@ -0,0 +1,14 @@
{
"type": "minecraft:block",
"pools": [
{
"rolls": 1,
"entries": [
{
"type": "minecraft:item",
"name": "szar:tictactoe"
}
]
}
]
}

View File

@@ -0,0 +1,23 @@
{
"type": "minecraft:crafting_shaped",
"pattern": [
"RBR",
"BPB",
"RBR"
],
"key": {
"R": {
"item": "minecraft:red_dye"
},
"B": {
"item": "minecraft:blue_dye"
},
"P": {
"tag": "minecraft:planks"
}
},
"result": {
"item": "szar:tictactoe",
"count": 1
}
}