roulette update

This commit is contained in:
2026-03-08 18:29:09 +01:00
parent 76803eb460
commit 26ab8e0576
31 changed files with 1055 additions and 19 deletions

View File

@@ -0,0 +1,270 @@
package dev.tggamesyt.szar.client;
import dev.tggamesyt.szar.*;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.RotationAxis;
public class RouletteScreen extends HandledScreen<RouletteScreenHandler> {
private static final Identifier BG_TEXTURE =
new Identifier(Szar.MOD_ID, "textures/gui/roulette.png");
// Add these fields to the class
private int dotAnimFrame = 0;
private int dotAnimTick = 0;
private static final int DOT_FRAME_DURATION = 20;
private static final String[] DOT_FRAMES = {
"...", "⋅..", "⋅⋅.", ".⋅⋅", "..⋅", "...", "...", "...", "..."
};
private boolean showingWinner = false;
private int stoppedTick = 0; // elapsed ticks since spin stopped
private final PlayerInventory inventory;
private final RouletteBlockEntity blockEntity;
private final RouletteScreenHandler handler;
public String spinString = "Next spin in: 0s";
public int nextspinTime = 0;
public boolean isIntermission = true;
private int winnernum = 0;
private float wheelDeg = 0f;
private float ballDeg = 0f;
// Spin start positions (captured when spin begins)
private float wheelStartDeg = 0f;
private float ballStartDeg = 0f;
// Total distance each will travel over the entire spin duration
private float wheelTotalDist = 0f;
private float ballTotalDist = 0f;
// Total ticks for the active spin (rollingTicks + halfWaitTicks)
private int spinTotalTicks = 0;
// How many ticks of the spin are "locked still" at the end (second half of wait)
private int spinStopTicks = 0;
private boolean spinInitialized = false;
private int lastElapsed = -1;
// Full speed in degrees/tick — used to compute how far wheel/ball travel
// during the constant-speed portion so total distance lands on target
private static final float WHEEL_FULL_SPEED = 8.0f;
private static final float BALL_FULL_SPEED = 14.0f;
public RouletteScreen(RouletteScreenHandler handler,
PlayerInventory inventory,
Text title) {
super(handler, inventory, title);
this.handler = handler;
this.blockEntity = handler.blockEntity;
this.backgroundWidth = 326;
this.backgroundHeight = 194;
this.inventory = inventory;
}
// Ease curve: constant speed phase (0→splitT) then ease-out (splitT→1)
// At t=splitT the curve transitions smoothly — the ease-out's initial
// derivative is set to match FULL_SPEED so there is no speed discontinuity.
//
// For t in [0, splitT]: pos(t) = fullSpeed * t * totalTicks
// For t in [splitT, 1]: pos(t) = pos(splitT) + decelDist * easeOut((t-splitT)/(1-splitT))
//
// easeOut(u) = 2u - u² → derivative at u=0 is 2/totalDecelTicks * decelDist
// We need that to equal fullSpeed * totalTicks (per-tick speed at split point).
// This is satisfied automatically because we set decelDist so the curve lands on target,
// and the split is at the rollingTicks boundary.
private void onSpinStart(int rollingTicks, int halfWaitTicks) {
spinTotalTicks = rollingTicks + halfWaitTicks;
spinStopTicks = blockEntity.wheelWaitSeconds * 20 - halfWaitTicks; // second half of wait
wheelStartDeg = wheelDeg;
ballStartDeg = ballDeg;
float targetWheelDeg = RouletteBlockEntity.getWheelDegrees(winnernum);
float targetBallDeg = 0f;
// Distance covered during constant phase (phase1)
float wheelPhase1Dist = WHEEL_FULL_SPEED * rollingTicks;
float ballPhase1Dist = BALL_FULL_SPEED * rollingTicks;
// Where each ends up after phase1
float wheelAfterPhase1 = (wheelStartDeg + wheelPhase1Dist) % 360f;
float ballAfterPhase1 = (ballStartDeg + ballPhase1Dist) % 360f;
// Phase2 must cover exactly the right distance to land on target.
// Pick the smallest distance >= a minimum so the wheel visibly decelerates.
float minDecelRotations = 1.0f; // at least 1 full extra rotation during decel
float wheelPhase2Dist = positiveMod(targetWheelDeg - wheelAfterPhase1, 360f);
if (wheelPhase2Dist < 360f * minDecelRotations) wheelPhase2Dist += 360f;
float ballPhase2Dist = positiveMod(targetBallDeg - ballAfterPhase1, 360f);
if (ballPhase2Dist < 360f * minDecelRotations) ballPhase2Dist += 360f;
wheelTotalDist = wheelPhase1Dist + wheelPhase2Dist;
ballTotalDist = ballPhase1Dist + ballPhase2Dist;
spinInitialized = true;
}
// Returns position fraction [0,1] along the total distance given elapsed ticks.
// Uses a piecewise curve: linear up to splitTick, then ease-out after.
// The ease-out's initial slope matches the linear slope → no speed jump.
private float curvePosition(int elapsed, int splitTick, int totalTicks,
float phase1Dist, float totalDist) {
if (elapsed <= 0) return 0f;
if (elapsed >= totalTicks) return 1f;
float phase2Dist = totalDist - phase1Dist;
if (elapsed <= splitTick) {
// Linear phase
return (phase1Dist * ((float) elapsed / splitTick)) / totalDist;
} else {
// Ease-out phase: u goes 0→1 over (totalTicks - splitTick) ticks
float u = (float)(elapsed - splitTick) / (totalTicks - splitTick);
// easeOut(u) = 2u - u² (starts at slope 2, ends at slope 0)
float eased = 2f * u - u * u;
return (phase1Dist + eased * phase2Dist) / totalDist;
}
}
public void tickScreen() {
nextspinTime = handler.getPropertyDelegate().get(1);
isIntermission = handler.getPropertyDelegate().get(0) == 1;
winnernum = handler.getPropertyDelegate().get(2);
if (isIntermission) {
spinString = "Next spin in: " + (nextspinTime + 19) / 20 + "s";
spinInitialized = false;
lastElapsed = -1;
showingWinner = false;
stoppedTick = 0;
wheelDeg = RouletteBlockEntity.getWheelDegrees(winnernum);
ballDeg = 0f;
return;
}
int rollingTicks = blockEntity.wheelRollingSeconds * 20;
int waitTicks = blockEntity.wheelWaitSeconds * 20;
int halfWaitTicks = waitTicks / 2;
int elapsed = -nextspinTime;
if (!spinInitialized || lastElapsed < 0) {
dotAnimFrame = 0;
dotAnimTick = 0;
showingWinner = false;
stoppedTick = 0;
onSpinStart(rollingTicks, halfWaitTicks);
}
lastElapsed = elapsed;
if (elapsed >= spinTotalTicks) {
// Wheel has stopped — count ticks and show winner at halfWaitTicks
stoppedTick++;
wheelDeg = RouletteBlockEntity.getWheelDegrees(winnernum);
ballDeg = 0f;
if (stoppedTick >= halfWaitTicks) {
showingWinner = true;
}
spinString = showingWinner ? "Winner: " + winnernum + " (" + RouletteBlockEntity.NUMBER_COLORS[winnernum].toUpperCase() + ")" : "Spinning" + DOT_FRAMES[dotAnimFrame];
return;
}
// Animate dots while spinning
dotAnimTick++;
if (dotAnimTick >= DOT_FRAME_DURATION) {
dotAnimTick = 0;
dotAnimFrame = (dotAnimFrame + 1) % DOT_FRAMES.length;
}
spinString = "Spinning" + DOT_FRAMES[dotAnimFrame];
float wheelFrac = curvePosition(elapsed, rollingTicks, spinTotalTicks,
WHEEL_FULL_SPEED * rollingTicks, wheelTotalDist);
float ballFrac = curvePosition(elapsed, rollingTicks, spinTotalTicks,
BALL_FULL_SPEED * rollingTicks, ballTotalDist);
wheelDeg = (wheelStartDeg + wheelFrac * wheelTotalDist) % 360f;
ballDeg = (ballStartDeg + ballFrac * ballTotalDist) % 360f;
}
private static float positiveMod(float value, float mod) {
return ((value % mod) + mod) % mod;
}
// ----------------------------
// BACKGROUND
// ----------------------------
@Override
protected void drawBackground(DrawContext context, float delta, int mouseX, int mouseY) {
int guiLeft = (width - backgroundWidth) / 2;
int guiTop = (height - backgroundHeight) / 2;
context.getMatrices().push();
context.getMatrices().translate(guiLeft, guiTop, 0);
context.getMatrices().scale(2.0f, 2.0f, 1.0f);
context.drawTexture(BG_TEXTURE, 0, 0, 0, 0, backgroundWidth, backgroundHeight);
context.getMatrices().pop();
}
protected void drawText(DrawContext context) {
int guiLeft = (width - backgroundWidth) / 2;
int guiTop = (height - backgroundHeight) / 2;
context.drawText(textRenderer, Text.literal(spinString),
guiLeft + 190, guiTop + 115, 0x373737, false);
}
protected void drawWheel(DrawContext context) {
int cx = ((width - backgroundWidth) / 2) + 255;
int cy = ((height - backgroundHeight) / 2) + 155;
Identifier wheelTex = new Identifier(Szar.MOD_ID, "textures/gui/roulette_wheel.png");
Identifier ballTex = new Identifier(Szar.MOD_ID, "textures/gui/roulette_ball.png");
int imgWidth = 64;
int imgHeight = 64;
context.getMatrices().push();
context.getMatrices().translate(cx, cy, 0);
context.getMatrices().multiply(RotationAxis.POSITIVE_Z.rotationDegrees(wheelDeg));
context.drawTexture(wheelTex, -imgWidth / 2, -imgHeight / 2, 0, 0,
imgWidth, imgHeight, imgWidth, imgHeight);
context.getMatrices().pop();
context.getMatrices().push();
context.getMatrices().translate(cx, cy, 0);
context.getMatrices().multiply(RotationAxis.POSITIVE_Z.rotationDegrees(ballDeg));
context.drawTexture(ballTex, -imgWidth / 2, -imgHeight / 2, 0, 0,
imgWidth, imgHeight, imgWidth, imgHeight);
context.getMatrices().pop();
}
// ----------------------------
// RENDER
// ----------------------------
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
renderBackground(context);
super.render(context, mouseX, mouseY, delta);
drawWheel(context);
drawText(context);
drawMouseoverTooltip(context, mouseX, mouseY);
tickScreen();
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
return super.mouseClicked(mouseX, mouseY, button);
}
@Override
public void removed() {
super.removed();
}
}

View File

@@ -250,6 +250,7 @@ public class SzarClient implements ClientModInitializer {
SlotMachineRenderer::new
);*/
HandledScreens.register(Szar.SLOT_MACHINE_SCREEN_HANDLER_TYPE, SlotMachineScreen::new);
HandledScreens.register(Szar.ROULETTE_SCREEN_HANDLER_TYPE, RouletteScreen::new);
EntityRendererRegistry.register(
Szar.NiggerEntityType,

View File

@@ -0,0 +1,204 @@
package dev.tggamesyt.szar;
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory;
import net.minecraft.block.Block;
import net.minecraft.block.BlockEntityProvider;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
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.entity.player.PlayerInventory;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.screen.NamedScreenHandlerFactory;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.state.StateManager;
import net.minecraft.state.property.DirectionProperty;
import net.minecraft.state.property.Properties;
import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import net.minecraft.util.BlockMirror;
import net.minecraft.util.BlockRotation;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
public class RouletteBlock extends Block implements BlockEntityProvider {
public static final DirectionProperty FACING = Properties.HORIZONTAL_FACING;
public RouletteBlock(Settings settings) {
super(settings);
setDefaultState(getStateManager().getDefaultState().with(FACING, Direction.NORTH));
}
VoxelShape shape23 = VoxelShapes.cuboid(0.25f, 0f, 0f, 0.75f, 0.25f, 0.125f);
VoxelShape shape24 = VoxelShapes.cuboid(0.125f, 0f, 0.125f, 0.875f, 0.125f, 0.25f);
VoxelShape shape25 = VoxelShapes.cuboid(0f, 0f, 0.25f, 1f, 0.125f, 0.75f);
VoxelShape shape26 = VoxelShapes.cuboid(0.125f, 0f, 0.75f, 0.875f, 0.125f, 0.875f);
VoxelShape shape27 = VoxelShapes.cuboid(0.25f, 0f, 0.875f, 0.75f, 0.25f, 1f);
VoxelShape shape28 = VoxelShapes.cuboid(0f, 0.125f, 0.25f, 0.125f, 0.25f, 0.75f);
VoxelShape shape29 = VoxelShapes.cuboid(0.875f, 0.125f, 0.25f, 1f, 0.25f, 0.75f);
VoxelShape shape30 = VoxelShapes.cuboid(0.75f, 0.125f, 0.125f, 0.875f, 0.25f, 0.25f);
VoxelShape shape31 = VoxelShapes.cuboid(0.75f, 0.125f, 0.75f, 0.875f, 0.25f, 0.875f);
VoxelShape shape32 = VoxelShapes.cuboid(0.125f, 0.125f, 0.125f, 0.25f, 0.25f, 0.25f);
VoxelShape shape33 = VoxelShapes.cuboid(0.125f, 0.125f, 0.75f, 0.25f, 0.25f, 0.875f);
VoxelShape shape34 = VoxelShapes.cuboid(0.3125f, 0.313125f, 0.3125f, 0.6875f, 0.313125f, 0.6875f);
VoxelShape shape35 = VoxelShapes.cuboid(0.4375f, 0.125f, 0.4375f, 0.5625f, 0.3125f, 0.5625f);
VoxelShape BASE_SHAPE = VoxelShapes.union(shape23, shape24, shape25, shape26, shape27, shape28, shape29, shape30, shape31, shape32, shape33, shape34, shape35);
private static VoxelShape rotateShape(Direction from, Direction to, VoxelShape shape) {
VoxelShape[] buffer = new VoxelShape[]{shape, VoxelShapes.empty()};
int times = (to.getHorizontal() - from.getHorizontal() + 4) % 4;
for (int i = 0; i < times; i++) {
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) ->
buffer[1] = VoxelShapes.union(buffer[1],
VoxelShapes.cuboid(1 - maxZ, minY, minX, 1 - minZ, maxY, maxX))
);
buffer[0] = buffer[1];
buffer[1] = VoxelShapes.empty();
}
return buffer[0];
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return rotateShape(Direction.NORTH, state.get(FACING), BASE_SHAPE);
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return getCollisionShape(state, world, pos, context);
}
// ===== ROTATION =====
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return getDefaultState().with(FACING, ctx.getHorizontalPlayerFacing().getOpposite());
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
builder.add(FACING);
}
@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return new RouletteBlockEntity(pos, state);
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos,
PlayerEntity player, Hand hand, BlockHitResult hit) {
if (hand != Hand.MAIN_HAND) return ActionResult.PASS;
BlockEntity blockEntity = world.getBlockEntity(pos);
if (!(blockEntity instanceof RouletteBlockEntity be)) {
return ActionResult.PASS;
}
Vec3d hitVec = hit.getPos().subtract(pos.getX(), pos.getY(), pos.getZ());
Direction facing = state.get(FACING);
double x = hitVec.x;
double y = hitVec.y;
double z = hitVec.z;
// Rotate based on facing (proper Minecraft rotation logic)
switch (facing) {
case NORTH -> {
// no change
}
case SOUTH -> {
x = 1 - x;
z = 1 - z;
}
case WEST -> {
double temp = x;
x = z;
z = 1 - temp;
}
case EAST -> {
double temp = x;
x = 1 - z;
z = temp;
}
}
boolean isHandle =
x >= 0.0625 && x <= 0.25 &&
y >= 0.5 && y <= 0.6875 &&
z >= 0.4375 && z <= 1.1875;
if (!world.isClient) {
// Open the GUI (client will receive block position)
player.openHandledScreen(state.createScreenHandlerFactory(world, pos));
}
return ActionResult.SUCCESS;
}
@Override
public NamedScreenHandlerFactory createScreenHandlerFactory(BlockState state, World world, BlockPos pos) {
BlockEntity be = world.getBlockEntity(pos);
if (!(be instanceof RouletteBlockEntity slotBe)) return null;
// Return an ExtendedScreenHandlerFactory that sends the BlockPos to the client
return new ExtendedScreenHandlerFactory() {
@Override
public void writeScreenOpeningData(ServerPlayerEntity player, PacketByteBuf buf) {
buf.writeBlockPos(pos); // send the block pos to client for the constructor
}
@Override
public Text getDisplayName() {
return Text.literal("Roulette");
}
@Override
public ScreenHandler createMenu(int syncId, PlayerInventory inv, PlayerEntity player) {
return new RouletteScreenHandler(syncId, inv, slotBe);
}
};
}
@Override
public BlockState rotate(BlockState state, BlockRotation rotation) {
if (state.contains(FACING)) {
return state.with(FACING, rotation.rotate(state.get(FACING)));
}
return state;
}
@Override
public BlockState mirror(BlockState state, BlockMirror mirror) {
if (state.contains(FACING)) {
return state.rotate(mirror.getRotation(state.get(FACING)));
}
return state;
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(
World world,
BlockState state,
BlockEntityType<T> type) {
return type == Szar.ROULETTE_BLOCKENTITY
? (world1, pos, state1, blockEntity) ->
RouletteBlockEntity.tick(world1, pos, state1, (RouletteBlockEntity) blockEntity)
: null;
}
}

View File

@@ -0,0 +1,318 @@
package dev.tggamesyt.szar;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.inventory.SimpleInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtList;
import net.minecraft.network.listener.ClientPlayPacketListener;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket;
import net.minecraft.screen.ArrayPropertyDelegate;
import net.minecraft.screen.PropertyDelegate;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
public class RouletteBlockEntity extends BlockEntity {
public int wheelWaitSeconds = 10;
public int wheelRollingSeconds = 5;
public int intermissionSeconds = 20;
private static final int[] WHEEL_ORDER = {
0, 26, 3, 35, 12, 28, 7, 29, 18, 22, 9,
31, 14, 20, 1, 33, 16, 24, 5, 10, 23, 8,
30, 11, 36, 13, 27, 6, 34, 17, 25, 2, 21,
4, 19, 15, 32
};
// 0=green, then alternating red/black in standard European roulette order
// Index = the number itself (0-36)
public static final String[] NUMBER_COLORS = {
"green", // 0
"red", "black", "red", "black", "red", // 1-5
"black", "red", "black", "red", "black",// 6-10
"black", "red", "black", "red", "black",// 11-15
"red", "black", "red", "black", "black",// 16-20
"red", "black", "red", "black", "red", // 21-25
"black", "red", "red", "black", "red", // 26-30
"black", "red", "black", "red", "black",// 31-35
"red" // 36
};
public static float getWheelDegrees(int winnerNum) {
for (int i = 0; i < WHEEL_ORDER.length; i++) {
if (WHEEL_ORDER[i] == winnerNum) return i * 9.73f;
}
return 0.0f;
}
private int winnernum = 0;
public boolean isIntermission = true;
public int nextspinTime = intermissionSeconds * 20;
final PropertyDelegate propertyDelegate;
private final Map<UUID, PlayerBetInventories> playerInventories = new HashMap<>();
public RouletteBlockEntity(BlockPos pos, BlockState state) {
super(Szar.ROULETTE_BLOCKENTITY, pos, state);
this.propertyDelegate = new ArrayPropertyDelegate(3);
}
public static class PlayerBetInventories {
public final SimpleInventory fullbet = new SimpleInventory(37);
public final SimpleInventory twelves = new SimpleInventory(3);
public final SimpleInventory halves = new SimpleInventory(2);
public final SimpleInventory evenodd = new SimpleInventory(2);
public final SimpleInventory blackred = new SimpleInventory(2);
public final SimpleInventory thirds = new SimpleInventory(3);
public void clear() {
fullbet.clear();
twelves.clear();
halves.clear();
evenodd.clear();
blackred.clear();
thirds.clear();
}
public void writeNbt(NbtCompound nbt) {
nbt.put("fullbet", inventoryToNbt(fullbet));
nbt.put("twelves", inventoryToNbt(twelves));
nbt.put("halves", inventoryToNbt(halves));
nbt.put("evenodd", inventoryToNbt(evenodd));
nbt.put("blackred", inventoryToNbt(blackred));
nbt.put("thirds", inventoryToNbt(thirds));
}
public void readNbt(NbtCompound nbt) {
nbtToInventory(nbt.getList("fullbet", 10), fullbet);
nbtToInventory(nbt.getList("twelves", 10), twelves);
nbtToInventory(nbt.getList("halves", 10), halves);
nbtToInventory(nbt.getList("evenodd", 10), evenodd);
nbtToInventory(nbt.getList("blackred", 10), blackred);
nbtToInventory(nbt.getList("thirds", 10), thirds);
}
private static NbtList inventoryToNbt(SimpleInventory inv) {
NbtList list = new NbtList();
for (int i = 0; i < inv.size(); i++) {
NbtCompound slot = new NbtCompound();
slot.putByte("Slot", (byte) i);
inv.getStack(i).writeNbt(slot);
list.add(slot);
}
return list;
}
private static void nbtToInventory(NbtList list, SimpleInventory inv) {
for (int i = 0; i < list.size(); i++) {
NbtCompound slot = list.getCompound(i);
int index = slot.getByte("Slot") & 0xFF;
if (index < inv.size()) {
inv.setStack(index, ItemStack.fromNbt(slot));
}
}
}
}
public PlayerBetInventories getInventoriesFor(UUID uuid) {
return playerInventories.computeIfAbsent(uuid, id -> new PlayerBetInventories());
}
public PlayerBetInventories getInventoriesFor(net.minecraft.entity.player.PlayerEntity player) {
return getInventoriesFor(player.getUuid());
}
// ─── Payout logic ────────────────────────────────────────────────────────
private static void giveItems(ServerPlayerEntity player, ItemStack stack, int multiplier) {
if (stack.isEmpty()) return;
int total = stack.getCount() * multiplier;
while (total > 0) {
int batchSize = Math.min(total, stack.getMaxCount());
ItemStack give = new ItemStack(stack.getItem(), batchSize);
player.getInventory().offerOrDrop(give);
total -= batchSize;
}
}
private void resolvePayouts(World world, int winner) {
String winnerColor = NUMBER_COLORS[winner];
for (Map.Entry<UUID, PlayerBetInventories> entry : playerInventories.entrySet()) {
ServerPlayerEntity player = (ServerPlayerEntity) world.getPlayerByUuid(entry.getKey());
if (player == null) {
continue;
}
PlayerBetInventories inv = entry.getValue();
// fullbet
ItemStack fullbetBet = inv.fullbet.getStack(winner).copy();
inv.fullbet.setStack(winner, ItemStack.EMPTY);
if (!fullbetBet.isEmpty()) {
giveItems(player, fullbetBet, 36);
}
// twelves
if (winner >= 1 && winner <= 12) {
ItemStack bet = inv.twelves.getStack(0).copy();
inv.twelves.setStack(0, ItemStack.EMPTY);
if (!bet.isEmpty()) { giveItems(player, bet, 3); }
} else if (winner >= 13 && winner <= 24) {
ItemStack bet = inv.twelves.getStack(1).copy();
inv.twelves.setStack(1, ItemStack.EMPTY);
if (!bet.isEmpty()) { giveItems(player, bet, 3); }
} else if (winner >= 25 && winner <= 36) {
ItemStack bet = inv.twelves.getStack(2).copy();
inv.twelves.setStack(2, ItemStack.EMPTY);
if (!bet.isEmpty()) { giveItems(player, bet, 3); }
}
// halves
if (winner >= 1 && winner <= 18) {
ItemStack bet = inv.halves.getStack(0).copy();
inv.halves.setStack(0, ItemStack.EMPTY);
if (!bet.isEmpty()) { giveItems(player, bet, 2); }
} else if (winner >= 19 && winner <= 36) {
ItemStack bet = inv.halves.getStack(1).copy();
inv.halves.setStack(1, ItemStack.EMPTY);
if (!bet.isEmpty()) { giveItems(player, bet, 2); }
}
// evenodd
if (winner != 0) {
if (winner % 2 == 0) {
ItemStack bet = inv.evenodd.getStack(0).copy();
inv.evenodd.setStack(0, ItemStack.EMPTY);
if (!bet.isEmpty()) { giveItems(player, bet, 2); }
} else {
ItemStack bet = inv.evenodd.getStack(1).copy();
inv.evenodd.setStack(1, ItemStack.EMPTY);
if (!bet.isEmpty()) { giveItems(player, bet, 2); }
}
}
// blackred
if (winnerColor.equals("red")) {
ItemStack bet = inv.blackred.getStack(0).copy();
inv.blackred.setStack(0, ItemStack.EMPTY);
if (!bet.isEmpty()) { giveItems(player, bet, 2); }
} else if (winnerColor.equals("black")) {
ItemStack bet = inv.blackred.getStack(1).copy();
inv.blackred.setStack(1, ItemStack.EMPTY);
if (!bet.isEmpty()) { giveItems(player, bet, 2); }
}
// thirds
if (winner != 0) {
if (winner % 3 == 0) {
ItemStack bet = inv.thirds.getStack(0).copy();
inv.thirds.setStack(0, ItemStack.EMPTY);
if (!bet.isEmpty()) { giveItems(player, bet, 2); }
} else if ((winner + 1) % 3 == 0) {
ItemStack bet = inv.thirds.getStack(1).copy();
inv.thirds.setStack(1, ItemStack.EMPTY);
if (!bet.isEmpty()) { giveItems(player, bet, 2); }
} else if ((winner + 2) % 3 == 0) {
ItemStack bet = inv.thirds.getStack(2).copy();
inv.thirds.setStack(2, ItemStack.EMPTY);
if (!bet.isEmpty()) { giveItems(player, bet, 2); }
}
}
inv.clear();
}
}
// ─── Tick ─────────────────────────────────────────────────────────────────
public static void tick(World world, BlockPos pos, BlockState state, RouletteBlockEntity be) {
if (world.isClient) return;
be.nextspinTime--;
if (be.isIntermission) {
if (be.nextspinTime > 0) {
// still counting down to spin
} else if (be.nextspinTime == 0) {
// Spin is starting — pick winner and clear all bets
be.winnernum = new Random().nextInt(37);
be.isIntermission = false;
}
} else {
// Spinning phase — nextspinTime is negative here
int spinTicks = (be.wheelWaitSeconds + be.wheelRollingSeconds) * 20;
if (be.nextspinTime <= -spinTicks) {
// Spin ended — resolve payouts then go back to intermission
be.resolvePayouts(world, be.winnernum);
be.isIntermission = true;
be.nextspinTime = be.intermissionSeconds * 20;
}
}
be.propertyDelegate.set(0, be.isIntermission ? 1 : 0);
be.propertyDelegate.set(1, be.nextspinTime);
be.propertyDelegate.set(2, be.winnernum);
be.markDirty();
}
private void clearAllBets() {
for (PlayerBetInventories inv : playerInventories.values()) {
inv.clear();
}
}
// ─── NBT ──────────────────────────────────────────────────────────────────
@Override
public void writeNbt(NbtCompound nbt) {
super.writeNbt(nbt);
nbt.putBoolean("isIntermission", isIntermission);
nbt.putInt("nextspinTime", nextspinTime);
nbt.putInt("winnernum", winnernum);
NbtCompound playersNbt = new NbtCompound();
for (Map.Entry<UUID, PlayerBetInventories> entry : playerInventories.entrySet()) {
NbtCompound playerNbt = new NbtCompound();
entry.getValue().writeNbt(playerNbt);
playersNbt.put(entry.getKey().toString(), playerNbt);
}
nbt.put("playerInventories", playersNbt);
}
@Override
public void readNbt(NbtCompound nbt) {
super.readNbt(nbt);
isIntermission = nbt.getBoolean("isIntermission");
nextspinTime = nbt.getInt("nextspinTime");
winnernum = nbt.getInt("winnernum");
playerInventories.clear();
NbtCompound playersNbt = nbt.getCompound("playerInventories");
for (String key : playersNbt.getKeys()) {
UUID uuid = UUID.fromString(key);
PlayerBetInventories invs = new PlayerBetInventories();
invs.readNbt(playersNbt.getCompound(key));
playerInventories.put(uuid, invs);
}
}
@Override
public Packet<ClientPlayPacketListener> toUpdatePacket() {
return BlockEntityUpdateS2CPacket.create(this);
}
@Override
public NbtCompound toInitialChunkDataNbt() {
NbtCompound nbt = new NbtCompound();
writeNbt(nbt);
return nbt;
}
}

View File

@@ -0,0 +1,143 @@
package dev.tggamesyt.szar;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.ItemStack;
import net.minecraft.screen.PropertyDelegate;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.slot.Slot;
public class RouletteScreenHandler extends ScreenHandler {
public final RouletteBlockEntity blockEntity;
public static final int SLOT_SIZE = 18;
public static final int GRID_START_X = 60;
public static final int GRID_START_Y = 8;
private static int gx(int col) { return GRID_START_X + (col - 1) * SLOT_SIZE; }
private static int gy(int row) { return GRID_START_Y + (row - 1) * SLOT_SIZE; }
// Slot that locks itself when the block entity is spinning
private class BetSlot extends Slot {
public BetSlot(Inventory inventory, int index, int x, int y) {
super(inventory, index, x, y);
}
private boolean isSpinning() {
return !blockEntity.isIntermission;
}
@Override
public boolean canInsert(ItemStack stack) {
return !isSpinning();
}
@Override
public boolean canTakeItems(PlayerEntity playerEntity) {
return !isSpinning();
}
@Override
public ItemStack takeStack(int amount) {
if (isSpinning()) return ItemStack.EMPTY;
return super.takeStack(amount);
}
}
public RouletteScreenHandler(int syncId, PlayerInventory playerInv, RouletteBlockEntity blockEntity) {
super(Szar.ROULETTE_SCREEN_HANDLER_TYPE, syncId);
this.blockEntity = blockEntity;
this.addProperties(blockEntity.propertyDelegate);
RouletteBlockEntity.PlayerBetInventories inv =
blockEntity.getInventoriesFor(playerInv.player);
// === fullbetInventory ===
this.addSlot(new BetSlot(inv.fullbet, 0, gx(1), gy(2)));
int fbIdx = 1;
for (int col = 2; col <= 13; col++) {
this.addSlot(new BetSlot(inv.fullbet, fbIdx++, gx(col), gy(3)));
this.addSlot(new BetSlot(inv.fullbet, fbIdx++, gx(col), gy(2)));
this.addSlot(new BetSlot(inv.fullbet, fbIdx++, gx(col), gy(1)));
}
// === twelvesInventory ===
this.addSlot(new BetSlot(inv.twelves, 0, gx(3), gy(4)));
this.addSlot(new BetSlot(inv.twelves, 1, gx(7), gy(4)));
this.addSlot(new BetSlot(inv.twelves, 2, gx(11), gy(4)));
// === halvesInventory ===
this.addSlot(new BetSlot(inv.halves, 0, gx(2), gy(5)));
this.addSlot(new BetSlot(inv.halves, 1, gx(12), gy(5)));
// === evenoddInventory ===
this.addSlot(new BetSlot(inv.evenodd, 0, gx(4), gy(5)));
this.addSlot(new BetSlot(inv.evenodd, 1, gx(10), gy(5)));
// === blackredInventory ===
this.addSlot(new BetSlot(inv.blackred, 0, gx(6), gy(5)));
this.addSlot(new BetSlot(inv.blackred, 1, gx(8), gy(5)));
// === thirdsInventory ===
this.addSlot(new BetSlot(inv.thirds, 0, gx(14), gy(1)));
this.addSlot(new BetSlot(inv.thirds, 1, gx(14), gy(2)));
this.addSlot(new BetSlot(inv.thirds, 2, gx(14), gy(3)));
// === Player inventory ===
int playerInvY = GRID_START_Y + 5 * SLOT_SIZE + 14;
for (int y = 0; y < 3; y++)
for (int x = 0; x < 9; x++)
this.addSlot(new Slot(playerInv, x + y * 9 + 9, 8 + x * 18, playerInvY + y * 18));
for (int x = 0; x < 9; x++)
this.addSlot(new Slot(playerInv, x, 8 + x * 18, playerInvY + 58));
}
@Override
public boolean onButtonClick(PlayerEntity player, int id) {
return true;
}
@Override
public boolean canUse(PlayerEntity player) {
return true;
}
@Override
public ItemStack quickMove(PlayerEntity player, int index) {
ItemStack newStack = ItemStack.EMPTY;
Slot slot = this.slots.get(index);
if (slot.hasStack()) {
ItemStack originalStack = slot.getStack();
newStack = originalStack.copy();
int playerStart = 49;
int totalSlots = this.slots.size();
if (index >= playerStart) {
if (!this.insertItem(originalStack, 0, 1, false)) {
return ItemStack.EMPTY;
}
} else if (index == 0) {
if (!this.insertItem(originalStack, playerStart, totalSlots, true)) {
return ItemStack.EMPTY;
}
}
if (originalStack.isEmpty()) {
slot.setStack(ItemStack.EMPTY);
} else {
slot.markDirty();
}
}
return newStack;
}
public PropertyDelegate getPropertyDelegate() {
return blockEntity.propertyDelegate;
}
}

View File

@@ -31,6 +31,7 @@ import net.minecraft.entity.damage.DamageType;
import net.minecraft.entity.data.DataTracker;
import net.minecraft.entity.data.TrackedData;
import net.minecraft.entity.data.TrackedDataHandlerRegistry;
import net.minecraft.entity.decoration.painting.PaintingVariant;
import net.minecraft.entity.effect.StatusEffect;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.entity.passive.VillagerEntity;
@@ -718,8 +719,50 @@ public class Szar implements ModInitializer {
})
);
});
Registry.register(
Registries.PAINTING_VARIANT,
new Identifier(MOD_ID, "axolotl"),
new PaintingVariant(32, 32)
);
Registry.register(
Registries.PAINTING_VARIANT,
new Identifier(MOD_ID, "bloc"),
new PaintingVariant(32, 32)
);
Registry.register(
Registries.PAINTING_VARIANT,
new Identifier(MOD_ID, "block_wave"),
new PaintingVariant(32, 32)
);
Registry.register(
Registries.PAINTING_VARIANT,
new Identifier(MOD_ID, "bounce"),
new PaintingVariant(32, 32)
);
Registry.register(
Registries.PAINTING_VARIANT,
new Identifier(MOD_ID, "chicken_jokey"),
new PaintingVariant(16, 16)
);
Registry.register(
Registries.PAINTING_VARIANT,
new Identifier(MOD_ID, "frogs"),
new PaintingVariant(32, 32)
);
Registry.register(
Registries.PAINTING_VARIANT,
new Identifier(MOD_ID, "matrix"),
new PaintingVariant(32, 32)
);
Registry.register(
Registries.PAINTING_VARIANT,
new Identifier(MOD_ID, "nyansniffer"),
new PaintingVariant(32, 32)
);
}
public static ObeliskCoreBlockEntity findNearestObelisk(ServerWorld world, BlockPos center, int radius) {
ObeliskCoreBlockEntity closest = null;
double closestDistance = Double.MAX_VALUE;
@@ -817,29 +860,34 @@ public class Szar implements ModInitializer {
new Identifier(MOD_ID, "firtana"),
new FirtanaItem(new Item.Settings())
);
static VoxelShape shape23 = VoxelShapes.cuboid(0.25f, 0f, 0f, 0.75f, 0.25f, 0.125f);
static VoxelShape shape24 = VoxelShapes.cuboid(0.125f, 0f, 0.125f, 0.875f, 0.125f, 0.25f);
static VoxelShape shape25 = VoxelShapes.cuboid(0f, 0f, 0.25f, 1f, 0.125f, 0.75f);
static VoxelShape shape26 = VoxelShapes.cuboid(0.125f, 0f, 0.75f, 0.875f, 0.125f, 0.875f);
static VoxelShape shape27 = VoxelShapes.cuboid(0.25f, 0f, 0.875f, 0.75f, 0.25f, 1f);
static VoxelShape shape28 = VoxelShapes.cuboid(0f, 0.125f, 0.25f, 0.125f, 0.25f, 0.75f);
static VoxelShape shape29 = VoxelShapes.cuboid(0.875f, 0.125f, 0.25f, 1f, 0.25f, 0.75f);
static VoxelShape shape30 = VoxelShapes.cuboid(0.75f, 0.125f, 0.125f, 0.875f, 0.25f, 0.25f);
static VoxelShape shape31 = VoxelShapes.cuboid(0.75f, 0.125f, 0.75f, 0.875f, 0.25f, 0.875f);
static VoxelShape shape32 = VoxelShapes.cuboid(0.125f, 0.125f, 0.125f, 0.25f, 0.25f, 0.25f);
static VoxelShape shape33 = VoxelShapes.cuboid(0.125f, 0.125f, 0.75f, 0.25f, 0.25f, 0.875f);
static VoxelShape shape34 = VoxelShapes.cuboid(0.3125f, 0.313125f, 0.3125f, 0.6875f, 0.313125f, 0.6875f);
static VoxelShape shape35 = VoxelShapes.cuboid(0.4375f, 0.125f, 0.4375f, 0.5625f, 0.3125f, 0.5625f);
static VoxelShape ROULETTE_SHAPE = VoxelShapes.union(shape23, shape24, shape25, shape26, shape27, shape28, shape29, shape30, shape31, shape32, shape33, shape34, shape35);
public static final ScreenHandlerType<RouletteScreenHandler> ROULETTE_SCREEN_HANDLER_TYPE =
ScreenHandlerRegistry.registerExtended(
new Identifier(Szar.MOD_ID, "roulette"),
(syncId, inv, buf) -> {
BlockPos pos = buf.readBlockPos();
BlockEntity be = inv.player.getWorld().getBlockEntity(pos);
if (!(be instanceof RouletteBlockEntity blockEntity)) {
throw new IllegalStateException("BlockEntity is not a RouletteBlockEntity");
}
return new RouletteScreenHandler(syncId, inv, blockEntity);
}
);
public static final Block ROULETTE_BLOCK = Registry.register(
Registries.BLOCK,
new Identifier(MOD_ID, "roulette"),
new BasicRotatableModelBlock(
new RouletteBlock(
AbstractBlock.Settings
.copy(Blocks.OAK_WOOD),
ROULETTE_SHAPE
.copy(Blocks.IRON_BLOCK)
)
);
public static final BlockEntityType<RouletteBlockEntity> ROULETTE_BLOCKENTITY = Registry.register(
Registries.BLOCK_ENTITY_TYPE,
new Identifier(MOD_ID, "roulette"),
FabricBlockEntityTypeBuilder.create(
RouletteBlockEntity::new,
ROULETTE_BLOCK
).build(null)
);
public static final Item ROULETTE = Registry.register(
Registries.ITEM,
new Identifier(MOD_ID, "roulette"),

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

View File

@@ -0,0 +1,5 @@
{
"animation": {
"frametime": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

View File

@@ -0,0 +1,5 @@
{
"animation": {
"frametime": 2
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,5 @@
{
"animation": {
"frametime": 2
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,4 @@
{
"animation": {
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,5 @@
{
"animation": {
"frametime": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -0,0 +1,5 @@
{
"animation": {
"frametime": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -0,0 +1,5 @@
{
"animation": {
"frametime": 2
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,5 @@
{
"animation": {
"frametime": 2
}
}

View File

@@ -0,0 +1,13 @@
{
"replace": false,
"values": [
"szar:axolotl",
"szar:bloc",
"szar:block_wave",
"szar:bounce",
"szar:chicken_jokey",
"szar:frogs",
"szar:matrix",
"szar:nyansniffer"
]
}