3 Commits

Author SHA1 Message Date
a316290c9a Trigger rebuild
All checks were successful
Build Minecraft Mod / build (push) Successful in 4m2s
Build Minecraft Mod / release (push) Successful in 16s
2026-04-15 11:37:55 +00:00
a8769a844d super beacon
Some checks failed
Build Minecraft Mod / build (push) Failing after 1m13s
Build Minecraft Mod / release (push) Has been skipped
2026-04-15 09:34:22 +02:00
cf267a959d ender armor
All checks were successful
Build Minecraft Mod / build (push) Successful in 4m16s
Build Minecraft Mod / release (push) Successful in 17s
2026-04-14 13:22:11 +02:00
36 changed files with 1080 additions and 3 deletions

View File

@@ -6,10 +6,11 @@ minecraft_version=1.20.1
yarn_mappings=1.20.1+build.10 yarn_mappings=1.20.1+build.10
loader_version=0.18.3 loader_version=0.18.3
# Mod Properties # Mod Properties
mod_version=26.4.14 mod_version=26.4.15
maven_group=dev.tggamesyt maven_group=dev.tggamesyt
archives_base_name=szar archives_base_name=szar
# Dependencies # Dependencies
# check this on https://modmuss50.me/fabric.html # check this on https://modmuss50.me/fabric.html
fabric_version=0.92.6+1.20.1 fabric_version=0.92.6+1.20.1
modmenu_version=7.2.2 modmenu_version=7.2.2

View File

@@ -0,0 +1,65 @@
package dev.tggamesyt.szar.client;
import dev.tggamesyt.szar.Szar;
import dev.tggamesyt.szar.SuperBeaconBlockEntity;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.block.entity.BeaconBlockEntityRenderer;
import net.minecraft.client.render.block.entity.BlockEntityRenderer;
import net.minecraft.client.render.block.entity.BlockEntityRendererFactory;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Identifier;
public class SuperBeaconBlockEntityRenderer implements BlockEntityRenderer<SuperBeaconBlockEntity> {
// Path: assets/szar/textures/entity/beacon_beam.png
public static final Identifier BEAM_TEXTURE = new Identifier(Szar.MOD_ID, "textures/entity/beacon_beam.png");
private static boolean loggedOnce = false;
public SuperBeaconBlockEntityRenderer(BlockEntityRendererFactory.Context ctx) {
Szar.LOGGER.info("[Szar] SuperBeaconBlockEntityRenderer constructed");
}
@Override
public void render(SuperBeaconBlockEntity be, float tickDelta, MatrixStack matrices,
VertexConsumerProvider vertexConsumers, int light, int overlay) {
if (!loggedOnce) {
Szar.LOGGER.info("[Szar] render() called once. level={}", be.getBeaconLevel());
loggedOnce = true;
}
if (be.getBeaconLevel() <= 0) return;
if (be.getWorld() == null) return;
long worldTime = be.getWorld().getTime();
// Vanilla beacon beam, 1.20.1 signature:
// public static void renderBeam(MatrixStack, VertexConsumerProvider, Identifier,
// float tickDelta, float heightScale, long worldTime, int yOffset, int maxY,
// float[] color, float innerRadius, float outerRadius)
BeaconBlockEntityRenderer.renderBeam(
matrices,
vertexConsumers,
BEAM_TEXTURE,
tickDelta,
1.0f,
worldTime,
0,
1024,
new float[]{1.0f, 1.0f, 1.0f},
0.2f,
0.25f
);
}
@Override
public boolean rendersOutsideBoundingBox(SuperBeaconBlockEntity blockEntity) {
return true;
}
@Override
public int getRenderDistance() {
return 256;
}
}

View File

@@ -0,0 +1,113 @@
package dev.tggamesyt.szar.client;
import com.mojang.blaze3d.systems.RenderSystem;
import dev.tggamesyt.szar.Szar;
import dev.tggamesyt.szar.SuperBeaconBlockEntity;
import dev.tggamesyt.szar.SuperBeaconScreenHandler;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
public class SuperBeaconScreen extends HandledScreen<SuperBeaconScreenHandler> {
private static final Identifier TEXTURE = new Identifier(Szar.MOD_ID, "textures/gui/super_beacon.png");
private static final int GUI_WIDTH = 176;
private static final int GUI_HEIGHT = 222;
private final ButtonWidget[] activateButtons = new ButtonWidget[4];
public SuperBeaconScreen(SuperBeaconScreenHandler handler, PlayerInventory inventory, Text title) {
super(handler, inventory, title);
this.backgroundWidth = GUI_WIDTH;
this.backgroundHeight = GUI_HEIGHT;
}
@Override
protected void init() {
super.init();
this.titleX = (this.backgroundWidth - this.textRenderer.getWidth(this.title)) / 2;
this.titleY = 5;
this.playerInventoryTitleY = this.backgroundHeight - 94;
for (int row = 0; row < 4; row++) {
final int r = row;
int buttonX = this.x + 108;
int buttonY = this.y + 14 + row * 28;
activateButtons[row] = ButtonWidget.builder(
getButtonText(row),
button -> onActivateClicked(r)
).dimensions(buttonX, buttonY, 56, 20).build();
addDrawableChild(activateButtons[row]);
}
}
private Text getButtonText(int row) {
int level = handler.getBeaconLevel();
if (level < row + 1) return Text.literal("Locked");
return handler.isRowActive(row) ? Text.literal("Deactivate") : Text.literal("Activate");
}
private void onActivateClicked(int row) {
Szar.LOGGER.info("[Szar][CLIENT] activate button clicked: row={}, pos={}", row, handler.getPos());
PacketByteBuf buf = PacketByteBufs.create();
buf.writeBlockPos(handler.getPos());
buf.writeInt(row);
ClientPlayNetworking.send(Szar.ACTIVATE_ROW_PACKET, buf);
Szar.LOGGER.info("[Szar][CLIENT] packet sent");
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
renderBackground(context);
super.render(context, mouseX, mouseY, delta);
drawMouseoverTooltip(context, mouseX, mouseY);
// Live updates
for (int row = 0; row < 4; row++) {
int level = handler.getBeaconLevel();
// Always clickable so we can debug — actual lock check is server-side
activateButtons[row].active = (level >= row + 1);
activateButtons[row].setMessage(getButtonText(row));
}
// Indicators + timer
for (int row = 0; row < 4; row++) {
int indicatorX = this.x + 26;
int indicatorY = this.y + 20 + row * 28;
int level = handler.getBeaconLevel();
int color;
if (level < row + 1) color = 0xFF555555;
else if (handler.isRowActive(row)) color = 0xFF00FF00;
else color = 0xFFFF0000;
context.fill(indicatorX, indicatorY, indicatorX + 12, indicatorY + 12, color);
if (handler.isRowActive(row)) {
int timer = handler.getFuelTimer(row);
int remainingTicks = SuperBeaconBlockEntity.FUEL_INTERVAL - timer;
int remainingSec = Math.max(0, remainingTicks / 20);
int min = remainingSec / 60;
int sec = remainingSec % 60;
String timeStr = String.format("%d:%02d", min, sec);
context.drawText(this.textRenderer, timeStr,
indicatorX - 2, indicatorY + 14, 0xFFFFFF, true);
}
}
}
@Override
protected void drawBackground(DrawContext context, float delta, int mouseX, int mouseY) {
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
context.drawTexture(TEXTURE, this.x, this.y, 0, 0, backgroundWidth, backgroundHeight, GUI_WIDTH, GUI_HEIGHT);
}
}

View File

@@ -12,6 +12,7 @@ import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.fabricmc.fabric.api.client.model.ModelLoadingRegistry; import net.fabricmc.fabric.api.client.model.ModelLoadingRegistry;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.client.rendering.v1.BlockEntityRendererRegistry;
import net.fabricmc.fabric.api.client.rendering.v1.EntityModelLayerRegistry; import net.fabricmc.fabric.api.client.rendering.v1.EntityModelLayerRegistry;
import net.fabricmc.fabric.api.client.rendering.v1.EntityRendererRegistry; import net.fabricmc.fabric.api.client.rendering.v1.EntityRendererRegistry;
import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback;
@@ -616,6 +617,10 @@ public class SzarClient implements ClientModInitializer {
Szar.CANNABIS_BLOCK, Szar.CANNABIS_BLOCK,
RenderLayer.getCutout() RenderLayer.getCutout()
); );
BlockRenderLayerMap.INSTANCE.putBlock(
SUPER_BEACON_BLOCK,
RenderLayer.getCutout()
);
BlockRenderLayerMap.INSTANCE.putBlock( BlockRenderLayerMap.INSTANCE.putBlock(
CONNECT_FOUR_BLOCK, CONNECT_FOUR_BLOCK,
RenderLayer.getCutout() RenderLayer.getCutout()
@@ -738,6 +743,8 @@ public class SzarClient implements ClientModInitializer {
(dispatcher, registryAccess) -> PanoramaClientCommand.register(dispatcher) (dispatcher, registryAccess) -> PanoramaClientCommand.register(dispatcher)
); );
} }
HandledScreens.register(Szar.SUPER_BEACON_SCREEN_HANDLER, SuperBeaconScreen::new);
BlockEntityRendererFactories.register(Szar.SUPER_BEACON_BLOCK_ENTITY, SuperBeaconBlockEntityRenderer::new);
} }
private boolean isDebugEnabled() { private boolean isDebugEnabled() {

View File

@@ -0,0 +1,57 @@
package dev.tggamesyt.szar;
import net.minecraft.item.ArmorItem;
import net.minecraft.item.ArmorMaterial;
import net.minecraft.recipe.Ingredient;
import net.minecraft.sound.SoundEvent;
import net.minecraft.sound.SoundEvents;
import static dev.tggamesyt.szar.Szar.MOD_ID;
public class EnderArmorMaterial implements ArmorMaterial {
public static final EnderArmorMaterial INSTANCE = new EnderArmorMaterial();
private static final int[] BASE_DURABILITY = {14, 17, 18, 12};
private static final int[] PROTECTION = {4, 9, 11, 4};
@Override
public int getDurability(ArmorItem.Type type) {
return BASE_DURABILITY[type.getEquipmentSlot().getEntitySlotId()] * 42;
}
@Override
public int getProtection(ArmorItem.Type type) {
return PROTECTION[type.getEquipmentSlot().getEntitySlotId()];
}
@Override
public int getEnchantability() {
return 20;
}
@Override
public SoundEvent getEquipSound() {
return SoundEvents.ITEM_ARMOR_EQUIP_NETHERITE;
}
@Override
public Ingredient getRepairIngredient() {
return Ingredient.ofItems(Szar.ENDER_INGOT);
}
@Override
public String getName() {
return MOD_ID + ":ender";
}
@Override
public float getToughness() {
return 4F;
}
@Override
public float getKnockbackResistance() {
return 0.2F;
}
}

View File

@@ -0,0 +1,50 @@
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.screen.NamedScreenHandlerFactory;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.ItemScatterer;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
public class SuperBeaconBlock extends BlockWithEntity implements BlockEntityProvider {
public SuperBeaconBlock(Settings settings) { super(settings); }
@Override public BlockRenderType getRenderType(BlockState state) { return BlockRenderType.MODEL; }
@Nullable @Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return new SuperBeaconBlockEntity(pos, state);
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
if (!world.isClient()) {
NamedScreenHandlerFactory factory = state.createScreenHandlerFactory(world, pos);
if (factory != null) player.openHandledScreen(factory);
}
return ActionResult.SUCCESS;
}
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
if (!state.isOf(newState.getBlock())) {
BlockEntity be = world.getBlockEntity(pos);
if (be instanceof SuperBeaconBlockEntity sbe) ItemScatterer.spawn(world, pos, sbe);
super.onStateReplaced(state, world, pos, newState, moved);
}
}
@Nullable @Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(World world, BlockState state, BlockEntityType<T> type) {
return checkType(type, Szar.SUPER_BEACON_BLOCK_ENTITY, SuperBeaconBlockEntity::tick);
}
}

View File

@@ -0,0 +1,383 @@
package dev.tggamesyt.szar;
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.effect.StatusEffect;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.entity.effect.StatusEffects;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.Inventories;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.potion.PotionUtil;
import net.minecraft.registry.tag.BlockTags;
import net.minecraft.screen.PropertyDelegate;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import java.util.*;
public class SuperBeaconBlockEntity extends BlockEntity implements Inventory, ExtendedScreenHandlerFactory {
public static final int FUEL_INTERVAL = 27000;
private static final Set<StatusEffect> PERSISTENT_EFFECTS = new HashSet<>();
static {
PERSISTENT_EFFECTS.add(StatusEffects.NAUSEA);
PERSISTENT_EFFECTS.add(StatusEffects.HEALTH_BOOST);
}
private final DefaultedList<ItemStack> inventory = DefaultedList.ofSize(8, ItemStack.EMPTY);
private final boolean[] rowActive = new boolean[4];
private final int[] fuelTimers = new int[4];
private int beaconLevel = 0;
// 4 unlock thresholds. Row N unlocks at level >= N+1.
// After lvl >= 4 every additional layer just expands range.
// Range formula: 2^(level + 5), capped at level 4 = 512 for unlock check, but range keeps growing.
private final Map<UUID, Map<Integer, Set<StatusEffect>>> persistentTracking = new HashMap<>();
private final PropertyDelegate propertyDelegate = new PropertyDelegate() {
@Override public int get(int i) {
return switch (i) {
case 0 -> beaconLevel;
case 1, 2, 3, 4 -> rowActive[i - 1] ? 1 : 0;
case 5, 6, 7, 8 -> fuelTimers[i - 5];
default -> 0;
};
}
@Override public void set(int i, int v) {
switch (i) {
case 0 -> beaconLevel = v;
case 1, 2, 3, 4 -> rowActive[i - 1] = v != 0;
case 5, 6, 7, 8 -> fuelTimers[i - 5] = v;
}
}
@Override public int size() { return 9; }
};
public PropertyDelegate getPropertyDelegate() { return propertyDelegate; }
public SuperBeaconBlockEntity(BlockPos pos, BlockState state) {
super(Szar.SUPER_BEACON_BLOCK_ENTITY, pos, state);
}
// No level cap - check from 1 up to limit (until invalid layer found)
public int computeBeaconLevel() {
if (world == null) return 0;
int levels = 0;
// Hard upper cap of 320 just to prevent infinite loops; world height ~384 max
for (int layer = 1; layer <= 320; layer++) {
int y = pos.getY() - layer;
// Stop if outside world
if (y < world.getBottomY()) break;
boolean valid = true;
for (int x = -layer; x <= layer && valid; x++) {
for (int z = -layer; z <= layer; z++) {
BlockState bs = world.getBlockState(new BlockPos(pos.getX() + x, y, pos.getZ() + z));
if (!bs.isIn(BlockTags.BEACON_BASE_BLOCKS)) { valid = false; break; }
}
}
if (valid) levels = layer; else break;
}
return levels;
}
public int getBeaconLevel() { return beaconLevel; }
public boolean isRowActive(int row) { return row >= 0 && row < 4 && rowActive[row]; }
public int getFuelTimer(int row) { return row >= 0 && row < 4 ? fuelTimers[row] : 0; }
// 2^(level+5): lvl1=64, lvl2=128, lvl3=256, lvl4=512, lvl5=1024 ... no cap
public double getEffectRadius() {
if (beaconLevel <= 0) return 0.0;
// Use long math then cast to double to avoid int overflow at high levels
return (double)(1L << (beaconLevel + 5));
}
public static void tick(World world, BlockPos pos, BlockState state, SuperBeaconBlockEntity be) {
if (world.isClient()) return;
if (world.getTime() % 20 == 0) {
int newLevel = be.computeBeaconLevel();
if (newLevel != be.beaconLevel) {
be.beaconLevel = newLevel;
be.markDirty();
}
}
for (int row = 0; row < 4; row++) {
if (!be.rowActive[row]) continue;
// Row N requires level >= N+1
if (be.beaconLevel < row + 1 || be.getStack(row * 2).isEmpty()) {
be.deactivateRow(row);
continue;
}
be.fuelTimers[row]++;
if (be.fuelTimers[row] >= FUEL_INTERVAL) {
ItemStack fuelItem = be.getStack(row * 2 + 1);
if (fuelItem.isEmpty()) { be.deactivateRow(row); continue; }
fuelItem.decrement(1);
be.fuelTimers[row] = 0;
if (fuelItem.isEmpty()) be.setStack(row * 2 + 1, ItemStack.EMPTY);
be.markDirty();
}
}
if (world.getTime() % 80 == 0) be.applyAllEffects();
if (world.getTime() % 100 == 0) be.checkPersistentRangeAll();
if (world.getTime() % 100 == 0) be.cleanupPersistentTracking();
}
// Collects effects from all active rows, summing amplifiers when same effect appears
// in multiple rows. Stacking: lvl1 + lvl1 = lvl2 → amp_sum = amp1 + amp2 + 1.
private Map<StatusEffect, Integer> collectCombinedEffects() {
Map<StatusEffect, Integer> combined = new HashMap<>();
for (int row = 0; row < 4; row++) {
if (!rowActive[row]) continue;
ItemStack effectItem = getStack(row * 2);
List<StatusEffectInstance> effects = getEffectsFromItem(effectItem);
for (StatusEffectInstance e : effects) {
StatusEffect type = e.getEffectType();
int amp = e.getAmplifier();
if (combined.containsKey(type)) {
// Stack: each row contributes (amp+1) levels
combined.put(type, combined.get(type) + amp + 1);
} else {
combined.put(type, amp);
}
}
}
return combined;
}
private void applyAllEffects() {
if (world == null) return;
Map<StatusEffect, Integer> combined = collectCombinedEffects();
if (combined.isEmpty()) return;
double radius = getEffectRadius();
Box box = new Box(pos).expand(radius);
List<PlayerEntity> players = world.getEntitiesByClass(PlayerEntity.class, box, p -> true);
for (PlayerEntity player : players) {
for (Map.Entry<StatusEffect, Integer> e : combined.entrySet()) {
StatusEffect type = e.getKey();
int amp = Math.min(e.getValue(), 255); // amp cap
if (PERSISTENT_EFFECTS.contains(type)) {
applyPersistent(player, type, amp);
} else {
player.addStatusEffect(new StatusEffectInstance(
type, 120, amp, true, true, true));
}
}
}
}
// Persistent tracking: key by effect type, store current applied amplifier
// so we re-apply when amplifier changes (row added/removed)
private void applyPersistent(PlayerEntity player, StatusEffect type, int amp) {
UUID uuid = player.getUuid();
Map<Integer, Set<StatusEffect>> bucket = persistentTracking.computeIfAbsent(uuid, k -> new HashMap<>());
// Use single bucket key 0 to track all persistent effects per player
Set<StatusEffect> tracked = bucket.computeIfAbsent(0, k -> new HashSet<>());
StatusEffectInstance current = player.getStatusEffect(type);
if (current == null || current.getAmplifier() != amp) {
player.removeStatusEffect(type);
player.addStatusEffect(new StatusEffectInstance(
type, Integer.MAX_VALUE, amp, true, true, true));
tracked.add(type);
}
}
private void checkPersistentRangeAll() {
if (world == null) return;
double radius = getEffectRadius();
Vec3d center = new Vec3d(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5);
Map<StatusEffect, Integer> combined = collectCombinedEffects();
for (Map.Entry<UUID, Map<Integer, Set<StatusEffect>>> entry : persistentTracking.entrySet()) {
Set<StatusEffect> tracked = entry.getValue().get(0);
if (tracked == null || tracked.isEmpty()) continue;
PlayerEntity player = world.getPlayerByUuid(entry.getKey());
if (player == null) continue;
boolean outOfRange = player.getPos().distanceTo(center) > radius;
// Remove effects that are out of range OR no longer provided by any active row
Iterator<StatusEffect> it = tracked.iterator();
while (it.hasNext()) {
StatusEffect e = it.next();
if (outOfRange || !combined.containsKey(e)) {
player.removeStatusEffect(e);
it.remove();
}
}
}
}
private void cleanupPersistentTracking() {
persistentTracking.entrySet().removeIf(entry -> {
entry.getValue().entrySet().removeIf(e -> e.getValue().isEmpty());
return entry.getValue().isEmpty();
});
}
private void removePersistentForRow(int row) {
// Recompute combined effects (without this row, since it's already deactivated by caller)
// and remove any tracked effects no longer provided.
if (world == null) return;
Map<StatusEffect, Integer> combined = collectCombinedEffects();
for (Map.Entry<UUID, Map<Integer, Set<StatusEffect>>> entry : persistentTracking.entrySet()) {
Set<StatusEffect> tracked = entry.getValue().get(0);
if (tracked == null) continue;
PlayerEntity player = world.getPlayerByUuid(entry.getKey());
if (player == null) { tracked.clear(); continue; }
Iterator<StatusEffect> it = tracked.iterator();
while (it.hasNext()) {
StatusEffect e = it.next();
if (!combined.containsKey(e)) {
player.removeStatusEffect(e);
it.remove();
}
}
}
}
private List<StatusEffectInstance> getEffectsFromItem(ItemStack stack) {
List<StatusEffectInstance> potionEffects = PotionUtil.getPotionEffects(stack);
if (!potionEffects.isEmpty()) return potionEffects;
if (stack.getItem().getFoodComponent() != null) {
return stack.getItem().getFoodComponent().getStatusEffects().stream()
.map(pair -> pair.getFirst()).toList();
}
return List.of();
}
public static boolean isValidFuel(ItemStack stack) {
return stack.isOf(Items.IRON_INGOT) || stack.isOf(Items.GOLD_INGOT) ||
stack.isOf(Items.DIAMOND) || stack.isOf(Items.EMERALD) ||
stack.isOf(Items.NETHERITE_INGOT);
}
public static boolean isValidEffectItem(ItemStack stack) {
if (!PotionUtil.getPotionEffects(stack).isEmpty()) return true;
if (stack.getItem().getFoodComponent() != null) {
return !stack.getItem().getFoodComponent().getStatusEffects().isEmpty();
}
return false;
}
public void toggleRow(int row) {
Szar.LOGGER.info("[Szar] toggleRow called: row={}, level={}, active={}", row, beaconLevel, rowActive[row]);
if (row < 0 || row >= 4) { Szar.LOGGER.warn("[Szar] row out of bounds"); return; }
if (beaconLevel < row + 1) { Szar.LOGGER.warn("[Szar] row locked, need level {}", row + 1); return; }
if (rowActive[row]) {
deactivateRow(row);
Szar.LOGGER.info("[Szar] deactivated row {}", row);
} else {
ItemStack effectItem = getStack(row * 2);
ItemStack fuelItem = getStack(row * 2 + 1);
if (effectItem.isEmpty()) { Szar.LOGGER.warn("[Szar] effect slot empty"); return; }
if (fuelItem.isEmpty()) { Szar.LOGGER.warn("[Szar] fuel slot empty"); return; }
if (!isValidEffectItem(effectItem)) { Szar.LOGGER.warn("[Szar] effect item invalid: {}", effectItem); return; }
if (!isValidFuel(fuelItem)) { Szar.LOGGER.warn("[Szar] fuel item invalid: {}", fuelItem); return; }
fuelItem.decrement(1);
if (fuelItem.isEmpty()) setStack(row * 2 + 1, ItemStack.EMPTY);
rowActive[row] = true;
fuelTimers[row] = 0;
markDirty();
Szar.LOGGER.info("[Szar] activated row {}", row);
}
}
private void deactivateRow(int row) {
rowActive[row] = false;
fuelTimers[row] = 0;
removePersistentForRow(row);
markDirty();
}
@Override public int size() { return 8; }
@Override public boolean isEmpty() { return inventory.stream().allMatch(ItemStack::isEmpty); }
@Override public ItemStack getStack(int slot) { return inventory.get(slot); }
@Override public ItemStack removeStack(int slot, int amount) {
ItemStack r = Inventories.splitStack(inventory, slot, amount); markDirty(); return r;
}
@Override public ItemStack removeStack(int slot) { return Inventories.removeStack(inventory, slot); }
@Override public void setStack(int slot, ItemStack stack) {
inventory.set(slot, stack);
if (stack.getCount() > getMaxCountPerStack()) stack.setCount(getMaxCountPerStack());
markDirty();
}
@Override public boolean canPlayerUse(PlayerEntity player) {
return player.squaredDistanceTo(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5) <= 64.0;
}
@Override public void clear() { inventory.clear(); }
@Override public boolean isValid(int slot, ItemStack stack) {
if (slot % 2 == 0) return isValidEffectItem(stack);
return isValidFuel(stack);
}
@Override
public void writeNbt(NbtCompound nbt) {
super.writeNbt(nbt);
Inventories.writeNbt(nbt, inventory);
nbt.putIntArray("FuelTimers", fuelTimers);
byte[] a = new byte[4];
for (int i = 0; i < 4; i++) a[i] = (byte) (rowActive[i] ? 1 : 0);
nbt.putByteArray("RowActive", a);
nbt.putInt("BeaconLevel", beaconLevel);
}
@Override
public void readNbt(NbtCompound nbt) {
super.readNbt(nbt);
inventory.clear();
Inventories.readNbt(nbt, inventory);
if (nbt.contains("FuelTimers")) {
int[] t = nbt.getIntArray("FuelTimers");
System.arraycopy(t, 0, fuelTimers, 0, Math.min(t.length, 4));
}
if (nbt.contains("RowActive")) {
byte[] a = nbt.getByteArray("RowActive");
for (int i = 0; i < Math.min(a.length, 4); i++) rowActive[i] = a[i] != 0;
}
beaconLevel = nbt.getInt("BeaconLevel");
}
@Override public Text getDisplayName() { return Text.translatable("block.szar.super_beacon"); }
@Override
public ScreenHandler createMenu(int syncId, PlayerInventory inv, PlayerEntity player) {
return new SuperBeaconScreenHandler(syncId, inv, this, propertyDelegate, pos);
}
@Override
public void writeScreenOpeningData(ServerPlayerEntity player, PacketByteBuf buf) {
buf.writeBlockPos(pos);
}
}

View File

@@ -0,0 +1,89 @@
package dev.tggamesyt.szar;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.Inventory;
import net.minecraft.inventory.SimpleInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.screen.ArrayPropertyDelegate;
import net.minecraft.screen.PropertyDelegate;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.slot.Slot;
import net.minecraft.util.math.BlockPos;
public class SuperBeaconScreenHandler extends ScreenHandler {
private final Inventory inventory;
private final PropertyDelegate propertyDelegate;
private final BlockPos pos;
// Client ctor
public SuperBeaconScreenHandler(int syncId, PlayerInventory playerInventory, PacketByteBuf buf) {
this(syncId, playerInventory, new SimpleInventory(8),
new ArrayPropertyDelegate(9), buf.readBlockPos());
}
// Server ctor
public SuperBeaconScreenHandler(int syncId, PlayerInventory playerInventory, Inventory inventory,
PropertyDelegate propertyDelegate, BlockPos pos) {
super(Szar.SUPER_BEACON_SCREEN_HANDLER, syncId);
this.inventory = inventory;
this.propertyDelegate = propertyDelegate;
this.pos = pos;
checkSize(inventory, 8);
inventory.onOpen(playerInventory.player);
for (int row = 0; row < 4; row++) {
addSlot(new EffectSlot(inventory, row * 2, 44, 18 + row * 28));
addSlot(new FuelSlot(inventory, row * 2 + 1, 80, 18 + row * 28));
}
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 9; col++) {
addSlot(new Slot(playerInventory, col + row * 9 + 9, 8 + col * 18, 140 + row * 18));
}
}
for (int col = 0; col < 9; col++) {
addSlot(new Slot(playerInventory, col, 8 + col * 18, 198));
}
addProperties(propertyDelegate);
}
public int getBeaconLevel() { return propertyDelegate.get(0); }
public boolean isRowActive(int row) { return propertyDelegate.get(1 + row) != 0; }
public int getFuelTimer(int row) { return propertyDelegate.get(5 + row); }
public BlockPos getPos() { return pos; }
@Override
public ItemStack quickMove(PlayerEntity player, int slotIndex) {
ItemStack newStack = ItemStack.EMPTY;
Slot slot = this.slots.get(slotIndex);
if (slot.hasStack()) {
ItemStack original = slot.getStack();
newStack = original.copy();
if (slotIndex < 8) {
if (!insertItem(original, 8, 44, true)) return ItemStack.EMPTY;
} else {
if (!insertItem(original, 0, 8, false)) return ItemStack.EMPTY;
}
if (original.isEmpty()) slot.setStack(ItemStack.EMPTY); else slot.markDirty();
}
return newStack;
}
@Override public boolean canUse(PlayerEntity player) { return inventory.canPlayerUse(player); }
private static class EffectSlot extends Slot {
public EffectSlot(Inventory inv, int i, int x, int y) { super(inv, i, x, y); }
@Override public boolean canInsert(ItemStack stack) { return SuperBeaconBlockEntity.isValidEffectItem(stack); }
@Override public int getMaxItemCount() { return 1; }
}
private static class FuelSlot extends Slot {
public FuelSlot(Inventory inv, int i, int x, int y) { super(inv, i, x, y); }
@Override public boolean canInsert(ItemStack stack) { return SuperBeaconBlockEntity.isValidFuel(stack); }
}
}

View File

@@ -26,6 +26,7 @@ import net.fabricmc.fabric.api.object.builder.v1.entity.FabricDefaultAttributeRe
import net.fabricmc.fabric.api.object.builder.v1.entity.FabricEntityTypeBuilder; import net.fabricmc.fabric.api.object.builder.v1.entity.FabricEntityTypeBuilder;
import net.fabricmc.fabric.api.object.builder.v1.trade.TradeOfferHelper; import net.fabricmc.fabric.api.object.builder.v1.trade.TradeOfferHelper;
import net.fabricmc.fabric.api.object.builder.v1.world.poi.PointOfInterestHelper; import net.fabricmc.fabric.api.object.builder.v1.world.poi.PointOfInterestHelper;
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerType;
import net.fabricmc.fabric.api.screenhandler.v1.ScreenHandlerRegistry; import net.fabricmc.fabric.api.screenhandler.v1.ScreenHandlerRegistry;
import net.fabricmc.fabric.impl.biome.TheEndBiomeData; import net.fabricmc.fabric.impl.biome.TheEndBiomeData;
import net.minecraft.advancement.Advancement; import net.minecraft.advancement.Advancement;
@@ -415,8 +416,13 @@ public class Szar implements ModInitializer {
entries.add(Szar.ENDER_ORE_ITEM); entries.add(Szar.ENDER_ORE_ITEM);
entries.add(Szar.RAW_ENDER); entries.add(Szar.RAW_ENDER);
entries.add(Szar.ENDER_INGOT); entries.add(Szar.ENDER_INGOT);
entries.add(Szar.ENDER_HELMET);
entries.add(Szar.ENDER_CHESTPLATE);
entries.add(Szar.ENDER_LEGGINGS);
entries.add(Szar.ENDER_BOOTS);
entries.add(Szar.SUPER_DIAMOND); entries.add(Szar.SUPER_DIAMOND);
entries.add(Szar.SUPER_APPLE); entries.add(Szar.SUPER_APPLE);
entries.add(Szar.SUPER_BEACON_ITEM);
// blueprint stuff // blueprint stuff
entries.add(BlueprintBlocks.BLUEPRINT); entries.add(BlueprintBlocks.BLUEPRINT);
entries.add(BlueprintBlocks.BLUEPRINT_DOOR_ITEM); entries.add(BlueprintBlocks.BLUEPRINT_DOOR_ITEM);
@@ -1457,7 +1463,124 @@ public class Szar implements ModInitializer {
} }
}); });
TheEndBiomeData.addEndBiomeReplacement(BiomeKeys.END_HIGHLANDS, Szar.CHORUS_FOREST, 0.7); TheEndBiomeData.addEndBiomeReplacement(BiomeKeys.END_HIGHLANDS, Szar.CHORUS_FOREST, 0.7);
ServerLivingEntityEvents.ALLOW_DAMAGE.register((entity, source, amount) -> {
if (!(entity instanceof PlayerEntity player)) return true;
// Check if wearing full Ender armor (optional — remove if you want any piece to trigger)
boolean wearingFullSet =
player.getInventory().getArmorStack(0).isOf(ENDER_BOOTS) &&
player.getInventory().getArmorStack(1).isOf(ENDER_LEGGINGS) &&
player.getInventory().getArmorStack(2).isOf(ENDER_CHESTPLATE) &&
player.getInventory().getArmorStack(3).isOf(ENDER_HELMET);
if (!wearingFullSet) return true;
// If player would drop to <= 1 heart (2 HP)
if (player.getHealth() - amount <= 2.0F) {
if (player.getWorld() instanceof ServerWorld world) {
teleportRandomly(player, world);
}
return false; // CANCEL DAMAGE
}
return true;
});
Registry.register(Registries.BLOCK, new Identifier(MOD_ID, "super_beacon"), SUPER_BEACON_BLOCK);
Registry.register(Registries.ITEM, new Identifier(MOD_ID, "super_beacon"), SUPER_BEACON_ITEM);
SUPER_BEACON_BLOCK_ENTITY = Registry.register(
Registries.BLOCK_ENTITY_TYPE,
new Identifier(MOD_ID, "super_beacon"),
FabricBlockEntityTypeBuilder.create(SuperBeaconBlockEntity::new, SUPER_BEACON_BLOCK).build()
);
SUPER_BEACON_SCREEN_HANDLER = Registry.register(
Registries.SCREEN_HANDLER,
new Identifier(MOD_ID, "super_beacon"),
new ExtendedScreenHandlerType<>(SuperBeaconScreenHandler::new)
);
ServerPlayNetworking.registerGlobalReceiver(ACTIVATE_ROW_PACKET, (server, player, handler, buf, responseSender) -> {
BlockPos pos = buf.readBlockPos();
int row = buf.readInt();
server.execute(() -> {
LOGGER.info("[Szar] activate_row: pos={}, row={}", pos, row);
if (player.getServerWorld().getBlockEntity(pos) instanceof SuperBeaconBlockEntity be) {
if (be.canPlayerUse(player)) {
boolean wasActive = be.isRowActive(row);
be.toggleRow(row);
LOGGER.info("[Szar] row {} {} -> {} (level={}, effect={}, fuel={})",
row, wasActive, be.isRowActive(row), be.getBeaconLevel(),
be.getStack(row * 2), be.getStack(row * 2 + 1));
} else {
LOGGER.warn("[Szar] player too far");
}
} else {
LOGGER.warn("[Szar] no block entity at {}", pos);
}
});
});
LOGGER.info("[Szar] Initialized");
} }
public static final Block SUPER_BEACON_BLOCK = new SuperBeaconBlock(
FabricBlockSettings.copyOf(Blocks.BEACON).luminance(15)
);
public static final Item SUPER_BEACON_ITEM = new BlockItem(SUPER_BEACON_BLOCK, new FabricItemSettings());
public static BlockEntityType<SuperBeaconBlockEntity> SUPER_BEACON_BLOCK_ENTITY;
public static ScreenHandlerType<SuperBeaconScreenHandler> SUPER_BEACON_SCREEN_HANDLER;
public static final Identifier ACTIVATE_ROW_PACKET = new Identifier(MOD_ID, "activate_row");
private static void teleportRandomly(PlayerEntity player, ServerWorld world) {
Random RANDOM = new Random();
double x = player.getX();
double y = player.getY();
double z = player.getZ();
for (int i = 0; i < 16; i++) {
double newX = x + (RANDOM.nextDouble() - 0.5) * 32;
double newY = y + RANDOM.nextInt(16) - 8;
double newZ = z + (RANDOM.nextDouble() - 0.5) * 32;
BlockPos pos = BlockPos.ofFloored(newX, newY, newZ);
if (world.isAir(pos)) {
player.teleport(newX, newY, newZ);
world.playSound(null, player.getBlockPos(),
net.minecraft.sound.SoundEvents.ENTITY_ENDERMAN_TELEPORT,
player.getSoundCategory(), 1.0F, 1.0F);
break;
}
}
}
public static final Item ENDER_HELMET = Registry.register(
Registries.ITEM,
new Identifier(Szar.MOD_ID, "ender_helmet"),
new ArmorItem(EnderArmorMaterial.INSTANCE, ArmorItem.Type.HELMET, new Item.Settings())
);
public static final Item ENDER_CHESTPLATE = Registry.register(
Registries.ITEM,
new Identifier(Szar.MOD_ID, "ender_chestplate"),
new ArmorItem(EnderArmorMaterial.INSTANCE, ArmorItem.Type.CHESTPLATE, new Item.Settings())
);
public static final Item ENDER_LEGGINGS = Registry.register(
Registries.ITEM,
new Identifier(Szar.MOD_ID, "ender_leggings"),
new ArmorItem(EnderArmorMaterial.INSTANCE, ArmorItem.Type.LEGGINGS, new Item.Settings())
);
public static final Item ENDER_BOOTS = Registry.register(
Registries.ITEM,
new Identifier(Szar.MOD_ID, "ender_boots"),
new ArmorItem(EnderArmorMaterial.INSTANCE, ArmorItem.Type.BOOTS, new Item.Settings())
);
public static final Block TIC_TAC_TOE_BLOCK = Registry.register( public static final Block TIC_TAC_TOE_BLOCK = Registry.register(
Registries.BLOCK, new Identifier(MOD_ID, "tictactoe"), Registries.BLOCK, new Identifier(MOD_ID, "tictactoe"),

View File

@@ -0,0 +1,7 @@
{
"variants": {
"": {
"model": "szar:block/super_beacon"
}
}
}

View File

@@ -218,5 +218,11 @@
"item.szar.hun_disc": "Music Disc", "item.szar.hun_disc": "Music Disc",
"item.szar.hun_disc.desc": "Kölcsey Ferenc - Hungarian Anthem", "item.szar.hun_disc.desc": "Kölcsey Ferenc - Hungarian Anthem",
"item.szar.orban_spawn_egg": "Orbán Spawn Egg", "item.szar.orban_spawn_egg": "Orbán Spawn Egg",
"item.szar.magyar_spawn_egg": "Magyar Spawn Egg" "item.szar.magyar_spawn_egg": "Magyar Spawn Egg",
"item.szar.ender_chestplate": "Ender Chestplate",
"item.szar.ender_leggings": "Ender Leggings",
"item.szar.ender_boots": "Ender Boots",
"item.szar.ender_helmet": "Ender Helmet",
"block.szar.super_beacon": "Super Beacon"
} }

View File

@@ -0,0 +1,67 @@
{
"format_version": "1.21.11",
"credit": "Made with Blockbench",
"parent": "block/block",
"textures": {
"3": "szar:block/enchanting_table_top",
"4": "szar:block/enchanting_table_side",
"particle": "szar:block/glass",
"glass": "szar:block/glass",
"obsidian": "szar:block/obsidian",
"beacon": "szar:block/beacon"
},
"elements": [
{
"name": "Glass shell",
"from": [0, 0, 0],
"to": [16, 16, 16],
"faces": {
"north": {"uv": [0, 0, 16, 16], "texture": "#glass"},
"east": {"uv": [0, 0, 16, 16], "texture": "#glass"},
"south": {"uv": [0, 0, 16, 16], "texture": "#glass"},
"west": {"uv": [0, 0, 16, 16], "texture": "#glass"},
"up": {"uv": [0, 0, 16, 16], "texture": "#glass"},
"down": {"uv": [0, 0, 16, 16], "texture": "#glass"}
}
},
{
"name": "Obsidian base",
"from": [2, 0.1, 2],
"to": [14, 3, 14],
"faces": {
"north": {"uv": [2, 13, 14, 16], "texture": "#obsidian"},
"east": {"uv": [2, 13, 14, 16], "texture": "#obsidian"},
"south": {"uv": [2, 13, 14, 16], "texture": "#obsidian"},
"west": {"uv": [2, 13, 14, 16], "texture": "#obsidian"},
"up": {"uv": [2, 2, 14, 14], "texture": "#obsidian"},
"down": {"uv": [2, 2, 14, 14], "texture": "#obsidian"}
}
},
{
"name": "Enchanting Table",
"from": [3, 3, 3],
"to": [13, 11, 13],
"faces": {
"north": {"uv": [0, 4, 16, 16], "texture": "#4"},
"east": {"uv": [0, 4, 16, 16], "texture": "#4"},
"south": {"uv": [0, 4, 16, 16], "texture": "#4"},
"west": {"uv": [0, 4, 16, 16], "texture": "#4"},
"up": {"uv": [0, 0, 16, 16], "texture": "#3"},
"down": {"uv": [3, 3, 13, 13], "texture": "#3"}
}
},
{
"name": "Inner beacon texture",
"from": [6, 11, 6],
"to": [10, 15, 10],
"faces": {
"north": {"uv": [0, 0, 16, 16], "texture": "#beacon"},
"east": {"uv": [0, 0, 16, 16], "texture": "#beacon"},
"south": {"uv": [0, 0, 16, 16], "texture": "#beacon"},
"west": {"uv": [0, 0, 16, 16], "texture": "#beacon"},
"up": {"uv": [0, 0, 16, 16], "texture": "#beacon"},
"down": {"uv": [0, 0, 16, 16], "texture": "#beacon"}
}
}
]
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "szar:item/ender_boots"
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "szar:item/ender_chestplate"
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "szar:item/ender_helmet"
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "szar:item/ender_leggings"
}
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

View File

@@ -0,0 +1,20 @@
{
"type": "minecraft:block",
"pools": [
{
"bonus_rolls": 0.0,
"conditions": [
{
"condition": "minecraft:survives_explosion"
}
],
"entries": [
{
"type": "minecraft:item",
"name": "szar:super_beacon"
}
],
"rolls": 1.0
}
]
}

View File

@@ -0,0 +1,15 @@
{
"type": "minecraft:crafting_shaped",
"pattern": [
"X X",
"X X"
],
"key": {
"X": {
"item": "szar:ender_ingot"
}
},
"result": {
"item": "szar:ender_boots"
}
}

View File

@@ -0,0 +1,16 @@
{
"type": "minecraft:crafting_shaped",
"pattern": [
"X X",
"XXX",
"XXX"
],
"key": {
"X": {
"item": "szar:ender_ingot"
}
},
"result": {
"item": "szar:ender_chestplate"
}
}

View File

@@ -0,0 +1,15 @@
{
"type": "minecraft:crafting_shaped",
"pattern": [
"XXX",
"X X"
],
"key": {
"X": {
"item": "szar:ender_ingot"
}
},
"result": {
"item": "szar:ender_helmet"
}
}

View File

@@ -0,0 +1,16 @@
{
"type": "minecraft:crafting_shaped",
"pattern": [
"XXX",
"X X",
"X X"
],
"key": {
"X": {
"item": "szar:ender_ingot"
}
},
"result": {
"item": "szar:ender_leggings"
}
}

View File

@@ -20,7 +20,7 @@
} }
}, },
"result": { "result": {
"item": "minecraft:bedrock", "item": "szar:super_beacon",
"count": 1 "count": 1
} }
} }