Compare commits
2 Commits
a316290c9a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 33e2ae6d02 | |||
| 7e79bee3b9 |
@@ -6,7 +6,7 @@ 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.15
|
mod_version=26.4.16
|
||||||
maven_group=dev.tggamesyt
|
maven_group=dev.tggamesyt
|
||||||
archives_base_name=szar
|
archives_base_name=szar
|
||||||
# Dependencies
|
# Dependencies
|
||||||
|
|||||||
@@ -32,12 +32,27 @@ public class SuperBeaconBlockEntity extends BlockEntity implements Inventory, Ex
|
|||||||
|
|
||||||
public static final int FUEL_INTERVAL = 27000;
|
public static final int FUEL_INTERVAL = 27000;
|
||||||
|
|
||||||
|
// Static registry of all loaded super beacons for cross-beacon stacking
|
||||||
|
public static final Set<SuperBeaconBlockEntity> ACTIVE_BEACONS =
|
||||||
|
java.util.Collections.newSetFromMap(new java.util.concurrent.ConcurrentHashMap<>());
|
||||||
|
|
||||||
|
// GLOBAL tracking of which players have beacon-granted persistent effects.
|
||||||
|
// Survives beacon unload — the server tick handler in Szar.java checks this
|
||||||
|
// against ACTIVE_BEACONS to strip effects when no beacon covers the player.
|
||||||
|
// Key: player UUID. Value: set of effect types currently granted.
|
||||||
|
public static final Map<UUID, Set<StatusEffect>> GLOBAL_PERSISTENT_TRACKING =
|
||||||
|
new java.util.concurrent.ConcurrentHashMap<>();
|
||||||
|
|
||||||
private static final Set<StatusEffect> PERSISTENT_EFFECTS = new HashSet<>();
|
private static final Set<StatusEffect> PERSISTENT_EFFECTS = new HashSet<>();
|
||||||
static {
|
static {
|
||||||
PERSISTENT_EFFECTS.add(StatusEffects.NAUSEA);
|
PERSISTENT_EFFECTS.add(StatusEffects.NAUSEA);
|
||||||
PERSISTENT_EFFECTS.add(StatusEffects.HEALTH_BOOST);
|
PERSISTENT_EFFECTS.add(StatusEffects.HEALTH_BOOST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isPersistentEffect(StatusEffect e) {
|
||||||
|
return PERSISTENT_EFFECTS.contains(e);
|
||||||
|
}
|
||||||
|
|
||||||
private final DefaultedList<ItemStack> inventory = DefaultedList.ofSize(8, ItemStack.EMPTY);
|
private final DefaultedList<ItemStack> inventory = DefaultedList.ofSize(8, ItemStack.EMPTY);
|
||||||
private final boolean[] rowActive = new boolean[4];
|
private final boolean[] rowActive = new boolean[4];
|
||||||
private final int[] fuelTimers = new int[4];
|
private final int[] fuelTimers = new int[4];
|
||||||
@@ -110,6 +125,9 @@ public class SuperBeaconBlockEntity extends BlockEntity implements Inventory, Ex
|
|||||||
public static void tick(World world, BlockPos pos, BlockState state, SuperBeaconBlockEntity be) {
|
public static void tick(World world, BlockPos pos, BlockState state, SuperBeaconBlockEntity be) {
|
||||||
if (world.isClient()) return;
|
if (world.isClient()) return;
|
||||||
|
|
||||||
|
// Register self in static set for cross-beacon stacking
|
||||||
|
ACTIVE_BEACONS.add(be);
|
||||||
|
|
||||||
if (world.getTime() % 20 == 0) {
|
if (world.getTime() % 20 == 0) {
|
||||||
int newLevel = be.computeBeaconLevel();
|
int newLevel = be.computeBeaconLevel();
|
||||||
if (newLevel != be.beaconLevel) {
|
if (newLevel != be.beaconLevel) {
|
||||||
@@ -121,7 +139,6 @@ public class SuperBeaconBlockEntity extends BlockEntity implements Inventory, Ex
|
|||||||
for (int row = 0; row < 4; row++) {
|
for (int row = 0; row < 4; row++) {
|
||||||
if (!be.rowActive[row]) continue;
|
if (!be.rowActive[row]) continue;
|
||||||
|
|
||||||
// Row N requires level >= N+1
|
|
||||||
if (be.beaconLevel < row + 1 || be.getStack(row * 2).isEmpty()) {
|
if (be.beaconLevel < row + 1 || be.getStack(row * 2).isEmpty()) {
|
||||||
be.deactivateRow(row);
|
be.deactivateRow(row);
|
||||||
continue;
|
continue;
|
||||||
@@ -136,16 +153,17 @@ public class SuperBeaconBlockEntity extends BlockEntity implements Inventory, Ex
|
|||||||
if (fuelItem.isEmpty()) be.setStack(row * 2 + 1, ItemStack.EMPTY);
|
if (fuelItem.isEmpty()) be.setStack(row * 2 + 1, ItemStack.EMPTY);
|
||||||
be.markDirty();
|
be.markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (world.getTime() % 80 == 0) be.applyAllEffects();
|
// Only the "coordinator" beacon for each player applies effects.
|
||||||
|
// Coordinator = closest active super beacon covering that player.
|
||||||
|
if (world.getTime() % 80 == 0) be.applyEffectsAsCoordinator();
|
||||||
if (world.getTime() % 100 == 0) be.checkPersistentRangeAll();
|
if (world.getTime() % 100 == 0) be.checkPersistentRangeAll();
|
||||||
if (world.getTime() % 100 == 0) be.cleanupPersistentTracking();
|
if (world.getTime() % 100 == 0) be.cleanupPersistentTracking();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collects effects from all active rows, summing amplifiers when same effect appears
|
// Collects effects from this beacon's active rows, summing amplifiers for duplicates.
|
||||||
// in multiple rows. Stacking: lvl1 + lvl1 = lvl2 → amp_sum = amp1 + amp2 + 1.
|
// Stacking: lvl1 + lvl1 = lvl2 → amp_sum = amp1 + amp2 + 1.
|
||||||
private Map<StatusEffect, Integer> collectCombinedEffects() {
|
private Map<StatusEffect, Integer> collectCombinedEffects() {
|
||||||
Map<StatusEffect, Integer> combined = new HashMap<>();
|
Map<StatusEffect, Integer> combined = new HashMap<>();
|
||||||
for (int row = 0; row < 4; row++) {
|
for (int row = 0; row < 4; row++) {
|
||||||
@@ -156,7 +174,6 @@ public class SuperBeaconBlockEntity extends BlockEntity implements Inventory, Ex
|
|||||||
StatusEffect type = e.getEffectType();
|
StatusEffect type = e.getEffectType();
|
||||||
int amp = e.getAmplifier();
|
int amp = e.getAmplifier();
|
||||||
if (combined.containsKey(type)) {
|
if (combined.containsKey(type)) {
|
||||||
// Stack: each row contributes (amp+1) levels
|
|
||||||
combined.put(type, combined.get(type) + amp + 1);
|
combined.put(type, combined.get(type) + amp + 1);
|
||||||
} else {
|
} else {
|
||||||
combined.put(type, amp);
|
combined.put(type, amp);
|
||||||
@@ -166,19 +183,112 @@ public class SuperBeaconBlockEntity extends BlockEntity implements Inventory, Ex
|
|||||||
return combined;
|
return combined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyAllEffects() {
|
// Merges another beacon's effects into this map. Same stacking rule.
|
||||||
|
private static void mergeInto(Map<StatusEffect, Integer> target, Map<StatusEffect, Integer> source) {
|
||||||
|
for (Map.Entry<StatusEffect, Integer> e : source.entrySet()) {
|
||||||
|
if (target.containsKey(e.getKey())) {
|
||||||
|
target.put(e.getKey(), target.get(e.getKey()) + e.getValue() + 1);
|
||||||
|
} else {
|
||||||
|
target.put(e.getKey(), e.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search radius for finding other super beacons. A beacon only matters if its
|
||||||
|
// coverage could overlap ours, so we check distance against (our radius + their radius)
|
||||||
|
// when iterating the registry.
|
||||||
|
// Find all registered active super beacons whose radius overlaps ours.
|
||||||
|
// Includes self.
|
||||||
|
private List<SuperBeaconBlockEntity> findNearbyActiveBeacons() {
|
||||||
|
List<SuperBeaconBlockEntity> result = new ArrayList<>();
|
||||||
|
if (world == null) return result;
|
||||||
|
|
||||||
|
double myRadius = getEffectRadius();
|
||||||
|
|
||||||
|
for (SuperBeaconBlockEntity other : ACTIVE_BEACONS) {
|
||||||
|
if (other.world != this.world) continue;
|
||||||
|
if (!other.hasAnyActiveRow()) continue;
|
||||||
|
if (other == this) { result.add(other); continue; }
|
||||||
|
|
||||||
|
double dist = other.centerVec().distanceTo(centerVec());
|
||||||
|
// Only relevant if coverage regions could overlap for some player
|
||||||
|
if (dist <= myRadius + other.getEffectRadius()) {
|
||||||
|
result.add(other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasAnyActiveRow() {
|
||||||
|
for (boolean b : rowActive) if (b) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute all beacon effects currently covering a given player.
|
||||||
|
// Iterates the static ACTIVE_BEACONS registry (loaded beacons only).
|
||||||
|
public static Map<StatusEffect, Integer> computeCoveringEffects(PlayerEntity player) {
|
||||||
|
Map<StatusEffect, Integer> combined = new HashMap<>();
|
||||||
|
Vec3d playerPos = player.getPos();
|
||||||
|
for (SuperBeaconBlockEntity be : ACTIVE_BEACONS) {
|
||||||
|
if (be.world != player.getWorld()) continue;
|
||||||
|
if (!be.hasAnyActiveRow()) continue;
|
||||||
|
if (playerPos.distanceTo(be.centerVec()) > be.getEffectRadius()) continue;
|
||||||
|
mergeInto(combined, be.collectCombinedEffects());
|
||||||
|
}
|
||||||
|
return combined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vec3d centerVec() {
|
||||||
|
return new Vec3d(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if this beacon is the "coordinator" (closest active beacon covering player).
|
||||||
|
private boolean isCoordinatorFor(PlayerEntity player, List<SuperBeaconBlockEntity> allBeacons) {
|
||||||
|
Vec3d playerPos = player.getPos();
|
||||||
|
double myDist = playerPos.distanceTo(centerVec());
|
||||||
|
if (myDist > getEffectRadius()) return false;
|
||||||
|
|
||||||
|
SuperBeaconBlockEntity closest = this;
|
||||||
|
double closestDist = myDist;
|
||||||
|
|
||||||
|
for (SuperBeaconBlockEntity other : allBeacons) {
|
||||||
|
if (other == this) continue;
|
||||||
|
double d = playerPos.distanceTo(other.centerVec());
|
||||||
|
if (d > other.getEffectRadius()) continue;
|
||||||
|
if (d < closestDist) {
|
||||||
|
closest = other;
|
||||||
|
closestDist = d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return closest == this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyEffectsAsCoordinator() {
|
||||||
if (world == null) return;
|
if (world == null) return;
|
||||||
Map<StatusEffect, Integer> combined = collectCombinedEffects();
|
double myRadius = getEffectRadius();
|
||||||
if (combined.isEmpty()) return;
|
if (myRadius <= 0) return;
|
||||||
|
|
||||||
double radius = getEffectRadius();
|
List<SuperBeaconBlockEntity> nearbyBeacons = findNearbyActiveBeacons();
|
||||||
Box box = new Box(pos).expand(radius);
|
|
||||||
List<PlayerEntity> players = world.getEntitiesByClass(PlayerEntity.class, box, p -> true);
|
// We can only be coordinator for players WE cover.
|
||||||
|
Box searchBox = new Box(pos).expand(myRadius);
|
||||||
|
List<PlayerEntity> candidatePlayers = world.getEntitiesByClass(PlayerEntity.class, searchBox, p -> true);
|
||||||
|
|
||||||
|
for (PlayerEntity player : candidatePlayers) {
|
||||||
|
if (!isCoordinatorFor(player, nearbyBeacons)) continue;
|
||||||
|
|
||||||
|
Map<StatusEffect, Integer> combined = new HashMap<>(collectCombinedEffects());
|
||||||
|
Vec3d playerPos = player.getPos();
|
||||||
|
|
||||||
|
for (SuperBeaconBlockEntity other : nearbyBeacons) {
|
||||||
|
if (other == this) continue;
|
||||||
|
if (playerPos.distanceTo(other.centerVec()) > other.getEffectRadius()) continue;
|
||||||
|
mergeInto(combined, other.collectCombinedEffects());
|
||||||
|
}
|
||||||
|
|
||||||
for (PlayerEntity player : players) {
|
|
||||||
for (Map.Entry<StatusEffect, Integer> e : combined.entrySet()) {
|
for (Map.Entry<StatusEffect, Integer> e : combined.entrySet()) {
|
||||||
StatusEffect type = e.getKey();
|
StatusEffect type = e.getKey();
|
||||||
int amp = Math.min(e.getValue(), 255); // amp cap
|
int amp = Math.min(e.getValue(), 255);
|
||||||
|
|
||||||
if (PERSISTENT_EFFECTS.contains(type)) {
|
if (PERSISTENT_EFFECTS.contains(type)) {
|
||||||
applyPersistent(player, type, amp);
|
applyPersistent(player, type, amp);
|
||||||
@@ -190,28 +300,33 @@ public class SuperBeaconBlockEntity extends BlockEntity implements Inventory, Ex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Persistent tracking: key by effect type, store current applied amplifier
|
// Persistent tracking: re-apply when amplifier changes.
|
||||||
// so we re-apply when amplifier changes (row added/removed)
|
// Duration is finite (200t = 10s) so effect wears off if beacon stops ticking.
|
||||||
|
// Also writes to GLOBAL_PERSISTENT_TRACKING so the server-wide cleanup in Szar.java
|
||||||
|
// can strip effects when beacon is unloaded.
|
||||||
private void applyPersistent(PlayerEntity player, StatusEffect type, int amp) {
|
private void applyPersistent(PlayerEntity player, StatusEffect type, int amp) {
|
||||||
UUID uuid = player.getUuid();
|
UUID uuid = player.getUuid();
|
||||||
Map<Integer, Set<StatusEffect>> bucket = persistentTracking.computeIfAbsent(uuid, k -> new HashMap<>());
|
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<>());
|
Set<StatusEffect> tracked = bucket.computeIfAbsent(0, k -> new HashSet<>());
|
||||||
|
|
||||||
StatusEffectInstance current = player.getStatusEffect(type);
|
StatusEffectInstance current = player.getStatusEffect(type);
|
||||||
if (current == null || current.getAmplifier() != amp) {
|
if (current != null && current.getAmplifier() == amp) {
|
||||||
|
if (current.getDuration() < 160) {
|
||||||
|
player.addStatusEffect(new StatusEffectInstance(
|
||||||
|
type, 200, amp, true, true, true));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
player.removeStatusEffect(type);
|
player.removeStatusEffect(type);
|
||||||
player.addStatusEffect(new StatusEffectInstance(
|
player.addStatusEffect(new StatusEffectInstance(
|
||||||
type, Integer.MAX_VALUE, amp, true, true, true));
|
type, 200, amp, true, true, true));
|
||||||
tracked.add(type);
|
|
||||||
}
|
}
|
||||||
|
tracked.add(type);
|
||||||
|
GLOBAL_PERSISTENT_TRACKING.computeIfAbsent(uuid, k -> java.util.concurrent.ConcurrentHashMap.newKeySet()).add(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkPersistentRangeAll() {
|
private void checkPersistentRangeAll() {
|
||||||
if (world == null) return;
|
if (world == null) return;
|
||||||
double radius = getEffectRadius();
|
List<SuperBeaconBlockEntity> nearbyBeacons = findNearbyActiveBeacons();
|
||||||
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()) {
|
for (Map.Entry<UUID, Map<Integer, Set<StatusEffect>>> entry : persistentTracking.entrySet()) {
|
||||||
Set<StatusEffect> tracked = entry.getValue().get(0);
|
Set<StatusEffect> tracked = entry.getValue().get(0);
|
||||||
@@ -219,14 +334,21 @@ public class SuperBeaconBlockEntity extends BlockEntity implements Inventory, Ex
|
|||||||
|
|
||||||
PlayerEntity player = world.getPlayerByUuid(entry.getKey());
|
PlayerEntity player = world.getPlayerByUuid(entry.getKey());
|
||||||
if (player == null) continue;
|
if (player == null) continue;
|
||||||
|
if (!isCoordinatorFor(player, nearbyBeacons)) continue;
|
||||||
|
|
||||||
boolean outOfRange = player.getPos().distanceTo(center) > radius;
|
// Compute what combined effects *should* exist for this player
|
||||||
|
Map<StatusEffect, Integer> combined = new HashMap<>(collectCombinedEffects());
|
||||||
|
Vec3d playerPos = player.getPos();
|
||||||
|
for (SuperBeaconBlockEntity other : nearbyBeacons) {
|
||||||
|
if (other == this) continue;
|
||||||
|
if (playerPos.distanceTo(other.centerVec()) > other.getEffectRadius()) continue;
|
||||||
|
mergeInto(combined, other.collectCombinedEffects());
|
||||||
|
}
|
||||||
|
|
||||||
// Remove effects that are out of range OR no longer provided by any active row
|
|
||||||
Iterator<StatusEffect> it = tracked.iterator();
|
Iterator<StatusEffect> it = tracked.iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
StatusEffect e = it.next();
|
StatusEffect e = it.next();
|
||||||
if (outOfRange || !combined.containsKey(e)) {
|
if (!combined.containsKey(e)) {
|
||||||
player.removeStatusEffect(e);
|
player.removeStatusEffect(e);
|
||||||
it.remove();
|
it.remove();
|
||||||
}
|
}
|
||||||
@@ -289,27 +411,24 @@ public class SuperBeaconBlockEntity extends BlockEntity implements Inventory, Ex
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void toggleRow(int row) {
|
public void toggleRow(int row) {
|
||||||
Szar.LOGGER.info("[Szar] toggleRow called: row={}, level={}, active={}", row, beaconLevel, rowActive[row]);
|
if (row < 0 || row >= 4) {return; }
|
||||||
if (row < 0 || row >= 4) { Szar.LOGGER.warn("[Szar] row out of bounds"); return; }
|
if (beaconLevel < row + 1) { return; }
|
||||||
if (beaconLevel < row + 1) { Szar.LOGGER.warn("[Szar] row locked, need level {}", row + 1); return; }
|
|
||||||
|
|
||||||
if (rowActive[row]) {
|
if (rowActive[row]) {
|
||||||
deactivateRow(row);
|
deactivateRow(row);
|
||||||
Szar.LOGGER.info("[Szar] deactivated row {}", row);
|
|
||||||
} else {
|
} else {
|
||||||
ItemStack effectItem = getStack(row * 2);
|
ItemStack effectItem = getStack(row * 2);
|
||||||
ItemStack fuelItem = getStack(row * 2 + 1);
|
ItemStack fuelItem = getStack(row * 2 + 1);
|
||||||
if (effectItem.isEmpty()) { Szar.LOGGER.warn("[Szar] effect slot empty"); return; }
|
if (effectItem.isEmpty()) { return; }
|
||||||
if (fuelItem.isEmpty()) { Szar.LOGGER.warn("[Szar] fuel slot empty"); return; }
|
if (fuelItem.isEmpty()) { return; }
|
||||||
if (!isValidEffectItem(effectItem)) { Szar.LOGGER.warn("[Szar] effect item invalid: {}", effectItem); return; }
|
if (!isValidEffectItem(effectItem)) { return; }
|
||||||
if (!isValidFuel(fuelItem)) { Szar.LOGGER.warn("[Szar] fuel item invalid: {}", fuelItem); return; }
|
if (!isValidFuel(fuelItem)) { return; }
|
||||||
|
|
||||||
fuelItem.decrement(1);
|
fuelItem.decrement(1);
|
||||||
if (fuelItem.isEmpty()) setStack(row * 2 + 1, ItemStack.EMPTY);
|
if (fuelItem.isEmpty()) setStack(row * 2 + 1, ItemStack.EMPTY);
|
||||||
rowActive[row] = true;
|
rowActive[row] = true;
|
||||||
fuelTimers[row] = 0;
|
fuelTimers[row] = 0;
|
||||||
markDirty();
|
markDirty();
|
||||||
Szar.LOGGER.info("[Szar] activated row {}", row);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,6 +439,28 @@ public class SuperBeaconBlockEntity extends BlockEntity implements Inventory, Ex
|
|||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void markRemoved() {
|
||||||
|
super.markRemoved();
|
||||||
|
ACTIVE_BEACONS.remove(this);
|
||||||
|
// Strip any persistent effects we granted, since we won't tick anymore
|
||||||
|
clearAllPersistentEffects();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearAllPersistentEffects() {
|
||||||
|
if (world == null) return;
|
||||||
|
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) {
|
||||||
|
for (StatusEffect e : tracked) player.removeStatusEffect(e);
|
||||||
|
}
|
||||||
|
tracked.clear();
|
||||||
|
}
|
||||||
|
persistentTracking.clear();
|
||||||
|
}
|
||||||
|
|
||||||
@Override public int size() { return 8; }
|
@Override public int size() { return 8; }
|
||||||
@Override public boolean isEmpty() { return inventory.stream().allMatch(ItemStack::isEmpty); }
|
@Override public boolean isEmpty() { return inventory.stream().allMatch(ItemStack::isEmpty); }
|
||||||
@Override public ItemStack getStack(int slot) { return inventory.get(slot); }
|
@Override public ItemStack getStack(int slot) { return inventory.get(slot); }
|
||||||
@@ -380,4 +521,17 @@ public class SuperBeaconBlockEntity extends BlockEntity implements Inventory, Ex
|
|||||||
public void writeScreenOpeningData(ServerPlayerEntity player, PacketByteBuf buf) {
|
public void writeScreenOpeningData(ServerPlayerEntity player, PacketByteBuf buf) {
|
||||||
buf.writeBlockPos(pos);
|
buf.writeBlockPos(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Registry lifecycle ---
|
||||||
|
@Override
|
||||||
|
public void setWorld(World world) {
|
||||||
|
super.setWorld(world);
|
||||||
|
if (!world.isClient()) ACTIVE_BEACONS.add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancelRemoval() {
|
||||||
|
super.cancelRemoval();
|
||||||
|
if (world != null && !world.isClient()) ACTIVE_BEACONS.add(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1506,24 +1506,48 @@ public class Szar implements ModInitializer {
|
|||||||
BlockPos pos = buf.readBlockPos();
|
BlockPos pos = buf.readBlockPos();
|
||||||
int row = buf.readInt();
|
int row = buf.readInt();
|
||||||
server.execute(() -> {
|
server.execute(() -> {
|
||||||
LOGGER.info("[Szar] activate_row: pos={}, row={}", pos, row);
|
|
||||||
if (player.getServerWorld().getBlockEntity(pos) instanceof SuperBeaconBlockEntity be) {
|
if (player.getServerWorld().getBlockEntity(pos) instanceof SuperBeaconBlockEntity be) {
|
||||||
if (be.canPlayerUse(player)) {
|
if (be.canPlayerUse(player)) {
|
||||||
boolean wasActive = be.isRowActive(row);
|
|
||||||
be.toggleRow(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");
|
// Global cleanup: every 20 ticks, iterate all players with tracked persistent effects
|
||||||
|
// and strip effects not currently produced by any LOADED active beacon covering them.
|
||||||
|
// Handles the case where the granting beacon unloaded (and thus can't tick anymore).
|
||||||
|
ServerTickEvents.END_SERVER_TICK.register(server -> {
|
||||||
|
if (server.getTicks() % 20 != 0) return;
|
||||||
|
|
||||||
|
Iterator<Map.Entry<UUID, Set<StatusEffect>>> it =
|
||||||
|
SuperBeaconBlockEntity.GLOBAL_PERSISTENT_TRACKING.entrySet().iterator();
|
||||||
|
|
||||||
|
while (it.hasNext()) {
|
||||||
|
Map.Entry<UUID, Set<StatusEffect>> entry = it.next();
|
||||||
|
UUID uuid = entry.getKey();
|
||||||
|
Set<StatusEffect> tracked = entry.getValue();
|
||||||
|
|
||||||
|
ServerPlayerEntity player = server.getPlayerManager().getPlayer(uuid);
|
||||||
|
if (player == null) {
|
||||||
|
// Player offline - keep tracked set until they reconnect (effect will refresh or expire)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<StatusEffect, Integer> covering = SuperBeaconBlockEntity.computeCoveringEffects(player);
|
||||||
|
|
||||||
|
Iterator<StatusEffect> eit = tracked.iterator();
|
||||||
|
while (eit.hasNext()) {
|
||||||
|
StatusEffect e = eit.next();
|
||||||
|
if (!covering.containsKey(e)) {
|
||||||
|
player.removeStatusEffect(e);
|
||||||
|
eit.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tracked.isEmpty()) it.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
public static final Block SUPER_BEACON_BLOCK = new SuperBeaconBlock(
|
public static final Block SUPER_BEACON_BLOCK = new SuperBeaconBlock(
|
||||||
FabricBlockSettings.copyOf(Blocks.BEACON).luminance(15)
|
FabricBlockSettings.copyOf(Blocks.BEACON).luminance(15)
|
||||||
@@ -1725,11 +1749,10 @@ public class Szar implements ModInitializer {
|
|||||||
.hunger(20)
|
.hunger(20)
|
||||||
.alwaysEdible()
|
.alwaysEdible()
|
||||||
.saturationModifier(20F)
|
.saturationModifier(20F)
|
||||||
.statusEffect(new StatusEffectInstance(StatusEffects.REGENERATION,60*20, 255 ), 1F)
|
.statusEffect(new StatusEffectInstance(StatusEffects.REGENERATION,60*20, 2 ), 1F)
|
||||||
.statusEffect(new StatusEffectInstance(StatusEffects.HEALTH_BOOST,2*60*20, 4 ), 1F)
|
|
||||||
.statusEffect(new StatusEffectInstance(StatusEffects.RESISTANCE,5*60*20, 2), 1F)
|
.statusEffect(new StatusEffectInstance(StatusEffects.RESISTANCE,5*60*20, 2), 1F)
|
||||||
.statusEffect(new StatusEffectInstance(StatusEffects.FIRE_RESISTANCE,5*60*20, 2), 1F)
|
.statusEffect(new StatusEffectInstance(StatusEffects.FIRE_RESISTANCE,5*60*20, 2), 1F)
|
||||||
.statusEffect(new StatusEffectInstance(StatusEffects.ABSORPTION,5*60*20, 4), 1F)
|
.statusEffect(new StatusEffectInstance(StatusEffects.ABSORPTION,5*60*20, 10), 1F)
|
||||||
.statusEffect(new StatusEffectInstance(StatusEffects.STRENGTH,5*60*20, 2), 1F)
|
.statusEffect(new StatusEffectInstance(StatusEffects.STRENGTH,5*60*20, 2), 1F)
|
||||||
.build()
|
.build()
|
||||||
).rarity(Rarity.EPIC))
|
).rarity(Rarity.EPIC))
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
],
|
],
|
||||||
"placement": {
|
"placement": {
|
||||||
"type": "minecraft:random_spread",
|
"type": "minecraft:random_spread",
|
||||||
"spacing": 10,
|
"spacing": 3,
|
||||||
"separation": 1,
|
"separation": 0,
|
||||||
"salt": 398826349
|
"salt": 398826349
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user