REWRITE INIT.

This commit is contained in:
2026-02-04 00:05:54 +01:00
parent 34be34db9d
commit 8601626b16
18 changed files with 906 additions and 548 deletions

View File

@@ -1,29 +0,0 @@
package fyi.tiko.battletower;
import org.bukkit.plugin.java.JavaPlugin;
/**
* @author Tiko
* @since 03.02.2026, 19:33
*/
public class BattleTowerPlugin extends JavaPlugin {
private TowerSpawnManager spawnManager;
@Override
public void onEnable() {
saveDefaultConfig();
spawnManager = new TowerSpawnManager(this);
getServer().getPluginManager().registerEvents(
new ChunkPopulateListener(spawnManager),
this
);
getLogger().info("Enabled BattleTowerPlugin");
}
public TowerSpawnManager spawnManager() {
return spawnManager;
}
}

View File

@@ -1,22 +0,0 @@
package fyi.tiko.battletower;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.ChunkPopulateEvent;
/**
* @author Tiko
* @since 03.02.2026, 19:37
*/
public class ChunkPopulateListener implements Listener {
private final TowerSpawnManager spawnManager;
public ChunkPopulateListener(final TowerSpawnManager spawnManager) {
this.spawnManager = spawnManager;
}
@EventHandler
public void handleChunkPopulate(final ChunkPopulateEvent event) {
spawnManager.attemptSpawn(event.getChunk());
}
}

View File

@@ -1,296 +0,0 @@
package fyi.tiko.battletower;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.block.Chest;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import java.util.Random;
public class OriginalTowerGenerator {
private int floor = 1;
private int floorIterator = 0;
private boolean topFloor;
public boolean generate(World world, Random random, int ix, int surfaceY, int kz) {
TowerType type = TowerType.COBBLE;
Material WALL = type.wall();
Material FLOOR = type.floor();
Material PILLAR = type.pillar();
Material LIGHT = type.light();
Material STAIRS = type.stairs();
int startY = surfaceY - 6;
int maxHeight = 120;
floor = 1;
for (int builderHeight = startY; builderHeight < maxHeight; builderHeight += 7) {
topFloor = builderHeight + 7 >= maxHeight;
// ✅ FULL Forge Pattern Segment
buildSegment(world, random, ix, builderHeight, kz,
WALL, FLOOR, PILLAR, STAIRS);
// ✅ Spawner/Floor
if (!topFloor) {
placeSpawner(world, ix + 2, builderHeight + 6, kz + 2, random);
placeSpawner(world, ix - 3, builderHeight + 6, kz + 2, random);
}
// ✅ Chests
placeChests(world, ix, builderHeight + 7, kz);
// ✅ Lights
placeLights(world, ix, builderHeight, kz, LIGHT);
// ✅ Random Damage Holes (Original feel)
randomDamage(world, random, ix, builderHeight + 5, kz, FLOOR);
floor++;
}
// ✅ Boss Placeholder
spawnBoss(world, ix, maxHeight, kz);
return true;
}
/* ========================================================= */
private void buildSegment(World world, Random random,
int ix, int baseY, int kz,
Material WALL,
Material FLOOR,
Material PILLAR,
Material STAIRS) {
for (floorIterator = 0; floorIterator < 7; floorIterator++) {
if (floor == 1 && floorIterator < 4) {
continue;
}
for (int xIt = -7; xIt < 7; xIt++) {
for (int zIt = -7; zIt < 7; zIt++) {
int x = ix + xIt;
int y = baseY + floorIterator;
int z = kz + zIt;
// ==== EXACT FORGE SHAPE ====
if (zIt == -7) {
if (xIt > -5 && xIt < 4) {
wallPiece(world, x, y, z, WALL);
}
continue;
}
// Main interior
if (zIt != -6 && zIt != -5) {
// Window rows excluded
if (zIt != -4 && zIt != -3 && zIt != 2 && zIt != 3) {
if (zIt > -3 && zIt < 2) {
if (xIt != -7 && xIt != 6) {
if (floorIterator == 5) {
place(world, x, y, z, FLOOR);
} else {
place(world, x, y, z, Material.AIR);
}
} else {
wallPiece(world, x, y, z, WALL);
}
}
else if (zIt == 4) {
if (xIt != -5 && xIt != 4) {
if (floorIterator == 5) {
place(world, x, y, z, FLOOR);
} else {
place(world, x, y, z, Material.AIR);
}
} else {
wallPiece(world, x, y, z, WALL);
}
}
else if (zIt == 5) {
if (xIt != -4 && xIt != -3 && xIt != 2 && xIt != 3) {
if (xIt > -3 && xIt < 2) {
if (floorIterator == 5) {
place(world, x, y, z, FLOOR);
} else {
wallPiece(world, x, y, z, WALL);
}
}
} else {
wallPiece(world, x, y, z, WALL);
}
}
else if (zIt == 6 && xIt > -3 && xIt < 2) {
wallPiece(world, x, y, z, WALL);
}
}
// Window rows (-4,-3,2,3)
else {
if (xIt != -6 && xIt != 5) {
if (floorIterator == 5) {
place(world, x, y, z, FLOOR);
} else {
place(world, x, y, z, Material.AIR);
}
} else {
wallPiece(world, x, y, z, WALL);
}
}
}
// Stair gap
else {
if (xIt == -5 || xIt == 4) {
wallPiece(world, x, y, z, WALL);
continue;
}
if (zIt == -6) {
int stairX = (floorIterator + 1) % 7 - 3;
if (xIt == stairX) {
place(world, x, y, z, STAIRS);
} else if (xIt > -5 && xIt < 4) {
place(world, x, y, z, Material.AIR);
}
}
if (zIt == -5) {
wallPiece(world, x, y, z, WALL);
}
}
}
}
}
}
/* ========================================================= */
private void wallPiece(World world, int x, int y, int z, Material wall) {
// Mossy pillar accents
if ((x % 5 == 0) && (z % 5 == 0)) {
place(world, x, y, z, Material.MOSSY_COBBLESTONE);
} else {
place(world, x, y, z, wall);
}
// Fill base into ground
if (floor == 1 && floorIterator == 4) {
fillDown(world, x, y, z, wall);
}
}
private void fillDown(World world, int x, int y, int z, Material mat) {
for (int yy = y - 1; yy > 0; yy--) {
Block b = world.getBlockAt(x, yy, z);
if (b.getType().isSolid()) break;
b.setType(mat, false);
}
}
/* ========================================================= */
private void randomDamage(World world, Random random,
int ix, int y, int kz, Material floorMat) {
if (topFloor) return;
int holes = floor * 2;
for (int i = 0; i < holes; i++) {
int dx = random.nextInt(10) - 5;
int dz = random.nextInt(10) - 5;
if (Math.abs(dx) < 2 && Math.abs(dz) < 2) continue;
Block b = world.getBlockAt(ix + dx, y, kz + dz);
if (b.getType() == floorMat) {
b.setType(Material.AIR, false);
}
}
}
/* ================= Spawner ================= */
private void placeSpawner(World world, int x, int y, int z, Random random) {
Block b = world.getBlockAt(x, y, z);
b.setType(Material.SPAWNER);
CreatureSpawner spawner = (CreatureSpawner) b.getState();
spawner.setSpawnedType(EntityType.ZOMBIE);
spawner.update();
}
/* ================= Loot ================= */
private void placeChests(World world, int ix, int y, int kz) {
for (int i = 0; i < 2; i++) {
Block chestBlock = world.getBlockAt(ix - i, y, kz + 3);
chestBlock.setType(Material.CHEST);
Chest chest = (Chest) chestBlock.getState();
chest.getInventory().addItem(new ItemStack(Material.BREAD, 2));
chest.getInventory().addItem(new ItemStack(Material.IRON_INGOT, 2));
chest.update();
}
}
/* ================= Lights ================= */
private void placeLights(World world, int ix, int y, int kz, Material light) {
place(world, ix + 3, y + 1, kz - 6, light);
place(world, ix - 4, y + 1, kz - 6, light);
}
/* ================= Boss ================= */
private void spawnBoss(World world, int ix, int y, int kz) {
world.spawnEntity(
new Location(world, ix + 0.5, y + 2, kz + 0.5),
EntityType.IRON_GOLEM
);
}
/* ========================================================= */
private void place(World world, int x, int y, int z, Material mat) {
world.getBlockAt(x, y, z).setType(mat, false);
}
}

View File

@@ -1,96 +0,0 @@
package fyi.tiko.battletower;
import org.bukkit.Location;
import org.bukkit.World;
/**
* @author Tiko
* @since 03.02.2026, 19:41
*/
public class TowerGenerator {
// 7 blöcke hoch pro etage
private static final int FLOOR_HEIGHT = 7;
// tower radius in blöcken
private static final int RADIUS = 7;
public boolean generate(final Location location) {
final var world = location.getWorld();
final var x = location.getBlockX();
final var z = location.getBlockZ();
final var surfaceY = world.getHighestBlockYAt(x, z);
if (surfaceY < 60) {
return false; // zu niedrig
}
// original: tower soll leicht im boden starten
final var startY = surfaceY - 6;
TowerType type = TowerType.COBBLE;
// 6 etagen hoch (zusammenbasteln hier)
for (var floor = 0; floor < 6; floor++) {
final var floorBaseY = startY + (floor * FLOOR_HEIGHT);
buildFloor(world, x, floorBaseY, z, type);
buildWalls(world, x, floorBaseY, z, type);
}
// top zusammenbasteln hier
buildTop(world, x, startY + (6 * FLOOR_HEIGHT), z, type);
return true;
}
private void buildFloor(
final World world,
final int centerX,
final int y,
final int centerZ,
final TowerType type
) {
for (var dx = -6; dx <= 6; dx++) {
for (var dz = -6; dz <= 6; dz++) {
// nur drinnen
if (Math.abs(dx) < 6 && Math.abs(dz) < 6) {
world.getBlockAt(centerX + dx, y, centerZ + dz).setType(type.floor());
}
}
}
}
private void buildWalls(
final World world,
final int centerX,
final int y,
final int centerZ,
final TowerType type
) {
for (var dy = 0; dy < FLOOR_HEIGHT; dy++) {
final var currentY = y + dy;
for (int dx = -RADIUS; dx <= RADIUS; dx++) {
for (int dz = -RADIUS; dz <= RADIUS; dz++) {
// outer border = wall
if (Math.abs(dx) == RADIUS || Math.abs(dz) == RADIUS) {
world.getBlockAt(centerX + dx, currentY, centerZ + dz).setType(type.wall());
}
}
}
}
}
private void buildTop(
final World world,
final int centerX,
final int y,
final int centerZ,
final TowerType type
) {
for(var dx = -6; dx <= 6; dx++) {
for(var dz = -6; dz <= 6; dz++) {
world.getBlockAt(centerX + dx, y, centerZ + dz).setType(type.floor());
}
}
}
}

View File

@@ -1,64 +0,0 @@
package fyi.tiko.battletower;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.plugin.java.JavaPlugin;
/**
* @author Tiko
* @since 03.02.2026, 19:37
*/
public class TowerSpawnManager {
private final JavaPlugin plugin;
private final Random random = new Random();
private final Set<Location> towerPositions = new HashSet<>();
public TowerSpawnManager(final JavaPlugin plugin) {
this.plugin = plugin;
}
public void attemptSpawn(final Chunk chunk) {
final var chance = plugin.getConfig().getInt("spawn.chance-per-chunk");
if (random.nextInt(chance) != 0) {
return;
}
final var world = chunk.getWorld();
final var x = chunk.getX() * 16 + 8;
final var z = chunk.getZ() * 16 + 8;
final var y = world.getHighestBlockYAt(x, z);
final var baseLocation = new Location(world, x, y, z);
if (!canSpawnAt(baseLocation)) {
return;
}
final var generator = new OriginalTowerGenerator();
final var success = generator.generate(world, random, x, y, z);
if (success) {
towerPositions.add(baseLocation);
plugin.getLogger().info("Spawned tower at " + baseLocation);
}
}
private boolean canSpawnAt(final Location location) {
final var minDistance = plugin.getConfig().getInt("spawn.min-distance");
for (final var other : towerPositions) {
if (other.getWorld() != location.getWorld()) {
continue;
}
if (other.distanceSquared(location) < minDistance) {
return false;
}
}
return true;
}
}

View File

@@ -1,39 +0,0 @@
package fyi.tiko.battletower;
import org.bukkit.Material;
public enum TowerType {
COBBLE(
Material.COBBLESTONE,
Material.STONE_BRICKS,
Material.MOSSY_COBBLESTONE, // ✅ Pillars
Material.TORCH,
Material.COBBLESTONE_STAIRS
);
private final Material wall;
private final Material floor;
private final Material pillar;
private final Material light;
private final Material stairs;
TowerType(Material wall,
Material floor,
Material pillar,
Material light,
Material stairs) {
this.wall = wall;
this.floor = floor;
this.pillar = pillar;
this.light = light;
this.stairs = stairs;
}
public Material wall() { return wall; }
public Material floor() { return floor; }
public Material pillar() { return pillar; }
public Material light() { return light; }
public Material stairs() { return stairs; }
}

View File

@@ -0,0 +1,54 @@
package fyi.tiko.battletowers;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.plugin.java.JavaPlugin;
public final class BattleTowersPlugin extends JavaPlugin {
private static BattleTowersPlugin instance;
public static BattleTowersPlugin instance() {
return instance;
}
private TowerLootManager lootManager;
private NamespacedKey towerKey;
public NamespacedKey towerKey() {
return towerKey;
}
public TowerLootManager getLootManager() {
return lootManager;
}
@Override
public void onEnable() {
instance = this;
saveDefaultConfig();
towerKey = new NamespacedKey(this, "tower_location");
lootManager = new TowerLootManager();
Bukkit.getWorlds().forEach(world -> {
world.getPopulators().add(new TowerPopulator());
});
getServer().getPluginManager().registerEvents(
new TowerChestListener(), this
);
getServer().getPluginManager().registerEvents(
new TowerBossDeathListener(), this
);
getLogger().info("BattleTowers enabled!");
}
@Override
public void onDisable() {
getLogger().info("BattleTowers disabled!");
}
}

View File

@@ -0,0 +1,27 @@
package fyi.tiko.battletowers;
import org.bukkit.entity.IronGolem;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDeathEvent;
import static fyi.tiko.battletowers.BattleTowersPlugin.instance;
public class TowerBossDeathListener implements Listener {
@EventHandler
public void onBossDeath(EntityDeathEvent event) {
if (!(event.getEntity() instanceof IronGolem golem)) return;
var data = golem.getPersistentDataContainer().get(
instance().towerKey(),
org.bukkit.persistence.PersistentDataType.STRING
);
if (data == null) return;
// Tower Collapse Start
TowerDestroyer.startCollapse(golem.getLocation(), event.getEntity().getKiller());
}
}

View File

@@ -0,0 +1,50 @@
package fyi.tiko.battletowers;
import org.bukkit.Location;
import org.bukkit.attribute.Attribute;
import org.bukkit.entity.IronGolem;
import org.bukkit.persistence.PersistentDataType;
import static fyi.tiko.battletowers.BattleTowersPlugin.instance;
public class TowerBossSpawner {
public static void spawnDormantBoss(org.bukkit.World world,
Location loc,
int towerX, int towerY, int towerZ) {
IronGolem golem = world.spawn(loc, IronGolem.class);
// ===== Boss Stats like Forge =====
golem.getAttribute(Attribute.MAX_HEALTH).setBaseValue(300.0);
golem.setHealth(300.0);
golem.getAttribute(Attribute.ATTACK_DAMAGE).setBaseValue(12.0);
golem.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(0.30);
golem.setCustomName("§6Battletower Guardian");
golem.setCustomNameVisible(true);
// ===== Dormant Mode =====
golem.setAI(false);
// Save tower coords inside entity
golem.getPersistentDataContainer().set(
instance().towerKey(),
PersistentDataType.STRING,
towerX + ";" + towerY + ";" + towerZ
);
}
public static void wakeUp(IronGolem golem) {
if (golem.hasAI()) return;
golem.setAI(true);
golem.getWorld().playSound(
golem.getLocation(),
"entity.iron_golem.repair",
3f, 0.6f
);
}
}

View File

@@ -0,0 +1,31 @@
package fyi.tiko.battletowers;
import org.bukkit.entity.IronGolem;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryOpenEvent;
public class TowerChestListener implements Listener {
@EventHandler
public void onChestOpen(InventoryOpenEvent event) {
if (!(event.getInventory().getHolder() instanceof org.bukkit.block.Chest)) {
return;
}
var player = event.getPlayer();
// Search nearby golems
for (var entity : player.getNearbyEntities(8, 8, 8)) {
if (entity instanceof IronGolem golem) {
if (!golem.hasAI()) {
TowerBossSpawner.wakeUp(golem);
golem.setTarget(player);
}
}
}
}
}

View File

@@ -0,0 +1,40 @@
package fyi.tiko.battletowers;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import static fyi.tiko.battletowers.BattleTowersPlugin.instance;
public class TowerDestroyer {
// TODO: die einzelnen floors werden nicht kaputt gemacht, sondern nur ganz oben + nachricht nur an spielern in reichweite von X blöcken...
public static void startCollapse(Location center, Player killer) {
World world = center.getWorld();
Bukkit.broadcastMessage("§cA Battletower's Guardian has fallen! The Tower will collapse...");
new BukkitRunnable() {
int floor = 6;
@Override
public void run() {
if (floor <= 0) {
cancel();
return;
}
double y = center.getY() - (floor * 7);
world.createExplosion(center, 10f);
floor--;
}
}.runTaskTimer(instance(), 20 * 15, 20 * 5);
}
}

View File

@@ -0,0 +1,528 @@
package fyi.tiko.battletowers;
import java.util.Random;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
public class TowerGenerator {
private static final int MAX_HOLE_DEPTH = 22;
private static final int[][] CANDIDATES = {
{4, -5}, {4, 0}, {4, 5},
{0, -5}, {0, 0}, {0, 5},
{-4, -5}, {-4, 0}, {-4, 5}
};
private final Random random = new Random();
public void trySpawnTower(World world, Random random, int x, int z) {
int surfaceY = getSurfaceY(world, x, z);
if (surfaceY < 60) {
return;
}
TowerType type = pickTowerType(world, random, x, z, surfaceY);
if (type == null) {
return;
}
boolean underground = random.nextInt(100) < 15;
generate(world, x, surfaceY, z, type, underground);
Bukkit.getLogger().info("Spawned BattleTower at " + x + " " + z + " type=" + type);
}
private TowerType pickTowerType(World world, Random random, int x, int z, int centerY) {
int water = 0, snow = 0, sand = 0, foliage = 0, other = 0;
for (int[] pair : CANDIDATES) {
int checkY = getSurfaceY(world, x + pair[0], z + pair[1]);
Block block = world.getBlockAt(x + pair[0], checkY, z + pair[1]);
Material mat = block.getType();
if (mat == Material.SNOW || mat == Material.ICE) {
snow++;
} else if (mat == Material.SAND || mat == Material.SANDSTONE) {
sand++;
} else if (mat == Material.WATER) {
water++;
} else if (mat.name().contains("LEAVES") || mat.name().contains("LOG")) {
foliage++;
} else {
other++;
}
if (Math.abs(checkY - centerY) > MAX_HOLE_DEPTH) {
return null;
}
}
if (sand >= snow && sand >= water && sand >= foliage) {
return TowerType.SANDSTONE;
}
if (snow >= water && snow >= foliage) {
return TowerType.ICE;
}
if (water >= foliage) {
return TowerType.MOSSY;
}
if (random.nextInt(10) == 0) {
return TowerType.NETHER;
}
return random.nextInt(5) == 0 ? TowerType.SMOOTH : TowerType.COBBLE;
}
private int getSurfaceY(World world, int x, int z) {
int y = world.getHighestBlockYAt(x, z);
return y;
}
// ✅ 1:1 Block Placement Core
public void generate(World world, int ix, int jy, int kz, TowerType towerChosen, boolean underground) {
Material towerWallBlock = towerChosen.wall();
Material towerLightBlock = towerChosen.light();
Material towerFloorBlock = towerChosen.floor();
Material towerStairBlock = towerChosen.stair();
int startingHeight = underground ? Math.max(jy - 70, 15) : jy - 6;
int maximumHeight = underground ? jy + 7 : 120;
int floor = 1;
for (int builderHeight = startingHeight; builderHeight < maximumHeight; builderHeight += 7) {
boolean topFloor = builderHeight + 7 >= maximumHeight;
// ============================
// BUILD FLOOR (7 BLOCK HIGH)
// ============================
for (int floorIterator = 0; floorIterator < 7; floorIterator++) {
// initial floor skip like Forge
if (floor == 1 && floorIterator < 4) {
continue;
}
for (int xIterator = -7; xIterator < 7; xIterator++) {
for (int zIterator = -7; zIterator < 7; zIterator++) {
int x = ix + xIterator;
int y = builderHeight + floorIterator;
int z = kz + zIterator;
// ============================
// ORIGINAL FORGE SHAPE LOGIC
// ============================
// ---- Back wall row z=-7
if (zIterator == -7) {
if (xIterator > -5 && xIterator < 4) {
buildWallPiece(world, x, y, z, towerWallBlock, floor, floorIterator);
}
continue;
}
// ---- Stairwell rows z=-6,-5
if (zIterator == -6 || zIterator == -5) {
// Outer wall supports
if (xIterator == -5 || xIterator == 4) {
buildWallPiece(world, x, y, z, towerWallBlock, floor, floorIterator);
continue;
}
// Stairwell special row z=-6
if (zIterator == -6) {
// Stairwell column formula
if (xIterator == (floorIterator + 1) % 7 - 3) {
if (!(underground && floor == 1)) {
placeStair(world, x, y, z, towerStairBlock, floorIterator);
}
// floorIterator=5 → platform extension
if (floorIterator == 5) {
setBlock(world, x - 7, y, z, towerFloorBlock);
}
// Top ledge
if (floorIterator == 6 && topFloor) {
buildWallPiece(world, x, y, z, towerWallBlock, floor, floorIterator);
}
continue;
}
// Tower inside air clearing
if (xIterator < 4 && xIterator > -5) {
setBlock(world, x, y, z, Material.AIR);
}
continue;
}
// row z=-5 outer bounds
if (xIterator <= -5 || xIterator >= 5) {
continue;
}
// under stairwell blocks
if ((floorIterator != 0 && floorIterator != 6)
|| (xIterator != -4 && xIterator != 3)) {
if (floorIterator == 5 && (xIterator == 3 || xIterator == -4)) {
buildFloorPiece(world, x, y, z, towerFloorBlock);
} else {
buildWallPiece(world, x, y, z, towerWallBlock, floor, floorIterator);
}
} else {
// Stairwell space
setBlock(world, x, y, z, Material.AIR);
}
continue;
}
// ---- Side rows z=-4,-3,z=2,3
if (zIterator == -4 || zIterator == -3 || zIterator == 2 || zIterator == 3) {
if (xIterator == -6 || xIterator == 5) {
buildWallPiece(world, x, y, z, towerWallBlock, floor, floorIterator);
continue;
}
if (xIterator <= -6 || xIterator >= 5) {
continue;
}
if (floorIterator == 5) {
buildFloorPiece(world, x, y, z, towerFloorBlock);
continue;
}
// NEU: SCHUTZ FÜR DIE KISTEN
// Wenn wir im neuen Stockwerk ganz unten sind (floorIterator == 0),
// dürfen wir an der Stelle x=0 und x=-1 bei z=3 die Luft nicht löschen,
// weil dort die Kisten vom Stockwerk darunter stehen.
if (floorIterator == 0 && zIterator == 3 && (xIterator == 0 || xIterator == -1)) {
continue;
}
setBlock(world, x, y, z, Material.AIR);
continue;
}
// ---- Middle rows z=-2,-1,0,1
if (zIterator > -3 && zIterator < 2) {
if (xIterator == -7 || xIterator == 6) {
// Window logic
if (floorIterator < 0 || floorIterator > 3
|| underground
|| (zIterator != -1 && zIterator != 0)) {
buildWallPiece(world, x, y, z, towerWallBlock, floor, floorIterator);
} else {
setBlock(world, x, y, z, Material.AIR);
}
continue;
}
if (xIterator <= -7 || xIterator >= 6) {
continue;
}
if (floorIterator == 5) {
buildFloorPiece(world, x, y, z, towerFloorBlock);
} else {
setBlock(world, x, y, z, Material.AIR);
}
continue;
}
// ---- Front row z=4
if (zIterator == 4) {
if (xIterator == -5 || xIterator == 4) {
buildWallPiece(world, x, y, z, towerWallBlock, floor, floorIterator);
continue;
}
if (xIterator <= -5 || xIterator >= 4) {
continue;
}
if (floorIterator == 5) {
buildFloorPiece(world, x, y, z, towerFloorBlock);
} else {
setBlock(world, x, y, z, Material.AIR);
}
continue;
}
// ---- Row z=5 entrance ring
if (zIterator == 5) {
if (xIterator == -4 || xIterator == -3 || xIterator == 2 || xIterator == 3) {
buildWallPiece(world, x, y, z, towerWallBlock, floor, floorIterator);
continue;
}
if (xIterator <= -3 || xIterator >= 2) {
continue;
}
if (floorIterator == 5) {
buildFloorPiece(world, x, y, z, towerFloorBlock);
} else {
buildWallPiece(world, x, y, z, towerWallBlock, floor, floorIterator);
}
continue;
}
// ---- Final row z=6
if (zIterator == 6 && xIterator > -3 && xIterator < 2) {
buildWallPiece(world, x, y, z, towerWallBlock, floor, floorIterator);
}
}
}
}
// ============================
// FLOOR LIGHTS (EXACT)
// ============================
setBlock(world, ix + 3, builderHeight + 2, kz - 6, towerLightBlock);
setBlock(world, ix - 4, builderHeight + 2, kz - 6, towerLightBlock);
setBlock(world, ix + 1, builderHeight + 2, kz - 4, towerLightBlock);
setBlock(world, ix - 2, builderHeight + 2, kz - 4, towerLightBlock);
// ============================
// SPAWNERS OR BOSS
// ============================
if ((!underground && topFloor) || (underground && floor == 1)) {
// ===== TOP FLOOR → BOSS GOLEM =====
TowerBossSpawner.spawnDormantBoss(world,
new Location(world, ix + 0.5, builderHeight + 6, kz + 0.5),
ix, builderHeight + 6, kz);
} else {
// ===== NORMAL FLOOR → 2 SPAWNERS =====
spawnMobSpawner(world, ix + 2, builderHeight + 6, kz + 2);
spawnMobSpawner(world, ix - 3, builderHeight + 6, kz + 2);
}
// ============================
// ALWAYS PLACE FLOOR CHESTS
// ============================
// ============================
// FIXED: PLACE FLOOR CHESTS
// ============================
// Wir übergeben 'false' für topFloor, damit wir die Logik in der Methode selbst regeln können
// oder nutzen einfach deine Logik weiter, aber rufen es sicher auf.
placeFloorChests(world, ix, builderHeight, kz, floor, underground, topFloor);
// ============================
// CHEST PETAL (PODEST) - WIEDER EINGEFÜGT
// ============================
// Das Podest kommt auf Höhe +6 (einen Block über dem normalen Boden)
setBlock(world, ix, builderHeight + 6, kz + 3, towerFloorBlock);
setBlock(world, ix - 1, builderHeight + 6, kz + 3, towerFloorBlock);
floor++;
}
}
private void placeStair(World world, int x, int y, int z, Material stairMat, int floorIterator) {
var stair = (org.bukkit.block.data.type.Stairs) stairMat.createBlockData();
// KORREKTUR:
// Da deine Treppen sich in der `generate`-Methode nur entlang der X-Achse bewegen
// (Z ist fest auf -6), ist es eine gerade Treppe.
// Die X-Werte steigen an (-2, -1, 0...), also laufen wir nach OSTEN hoch.
// Deshalb müssen alle Treppen stur nach Osten zeigen.
stair.setFacing(BlockFace.EAST);
stair.setHalf(org.bukkit.block.data.type.Stairs.Half.BOTTOM);
world.getBlockAt(x, y, z).setBlockData(stair, false);
}
private void placeFloorChests(World world,
int ix,
int builderHeight,
int kz,
int floor,
boolean underground,
boolean topFloor
) {
TowerLootManager loot = BattleTowersPlugin.instance().getLootManager();
TowerStageItemManager floorChestManager;
// 1. Loot-Logik (Original Forge Style)
if (!underground) {
floorChestManager = topFloor
? loot.getManagerForFloor(10, random)
: loot.getManagerForFloor(floor, random);
} else {
floorChestManager = (floor == 1)
? loot.getManagerForFloor(10, random)
: loot.getManagerForFloor(Math.abs(11 - floor), random);
}
// 2. Koordinaten berechnen
// cy = builderHeight + 7 stellt die Kisten auf das Podest (welches auf +6 ist)
int cx = ix;
int cy = builderHeight + 7;
int cz = kz + 3;
// 3. Blöcke initial auf Kiste setzen
world.getBlockAt(cx, cy, cz).setType(Material.CHEST, false);
world.getBlockAt(cx - 1, cy, cz).setType(Material.CHEST, false);
// 4. Doppelkisten-Daten anwenden (Verbindung korrigieren)
// Von vorne (Norden) betrachtet:
// cx - 1 ist LINKS (Westen) -> Type.LEFT
// cx ist RECHTS (Osten) -> Type.RIGHT
// Linke Hälfte (cx - 1)
var leftData = (org.bukkit.block.data.type.Chest) Material.CHEST.createBlockData();
leftData.setFacing(BlockFace.NORTH);
leftData.setType(org.bukkit.block.data.type.Chest.Type.LEFT);
// Rechte Hälfte (cx)
var rightData = (org.bukkit.block.data.type.Chest) Material.CHEST.createBlockData();
rightData.setFacing(BlockFace.NORTH);
rightData.setType(org.bukkit.block.data.type.Chest.Type.RIGHT);
// Daten auf die Blöcke schreiben
world.getBlockAt(cx - 1, cy, cz).setBlockData(leftData, false);
world.getBlockAt(cx, cy, cz).setBlockData(rightData, false);
// 5. Inventar füllen
fillChest(world, cx, cy, cz, floorChestManager, underground);
fillChest(world, cx - 1, cy, cz, floorChestManager, underground);
}
private void fillChest(World world,
int x,
int y,
int z,
TowerStageItemManager manager,
boolean underground) {
var state = world.getBlockAt(x, y, z).getState();
if (!(state instanceof org.bukkit.block.Chest chest)) return;
int attempts = BattleTowersPlugin.instance()
.getConfig()
.getInt("tower.itemGenerateAttemptsPerFloor");
if (underground) attempts *= 2;
for (int i = 0; i < attempts; i++) {
ItemStack item = manager.getStageItem(random);
if (item == null) continue;
chest.getInventory().setItem(
random.nextInt(chest.getInventory().getSize()),
item
);
}
}
private void spawnMobSpawner(World world, int x, int y, int z) {
Block block = world.getBlockAt(x, y, z);
block.setType(Material.SPAWNER, false);
if (block.getState() instanceof CreatureSpawner spawner) {
EntityType mob = switch (new Random().nextInt(4)) {
case 0 -> EntityType.SKELETON;
case 1 -> EntityType.ZOMBIE;
case 2 -> EntityType.SPIDER;
default -> EntityType.CAVE_SPIDER;
};
spawner.setSpawnedType(mob);
spawner.update(true);
}
}
private void buildFloorPiece(World world, int x, int y, int z, Material floor) {
setBlock(world, x, y, z, floor);
}
private void buildWallPiece(World world, int x, int y, int z,
Material wall, int floor, int floorIterator) {
setBlock(world, x, y, z, wall);
// Forge: fill base downwards
if (floor == 1 && floorIterator == 4) {
fillTowerBaseToGround(world, x, y, z, wall);
}
}
private void fillTowerBaseToGround(World world, int x, int y, int z, Material wall) {
int yy = y - 1;
while (yy > 0) {
Material below = world.getBlockAt(x, yy, z).getType();
if (isBuildableBlock(below)) {
break;
}
setBlock(world, x, yy, z, wall);
yy--;
}
}
private boolean isBuildableBlock(Material mat) {
return mat == Material.STONE
|| mat == Material.GRASS_BLOCK
|| mat == Material.DIRT
|| mat == Material.SAND
|| mat == Material.SANDSTONE
|| mat == Material.GRAVEL;
}
private void setBlock(World world, int x, int y, int z, Material mat) {
world.getBlockAt(x, y, z).setType(mat, false);
}
}

View File

@@ -0,0 +1,36 @@
package fyi.tiko.battletowers;
import fyi.tiko.battletowers.BattleTowersPlugin;
import java.util.Random;
public class TowerLootManager {
private final TowerStageItemManager[] floors = new TowerStageItemManager[10];
public TowerLootManager() {
var cfg = BattleTowersPlugin.instance().getConfig();
floors[0] = new TowerStageItemManager(cfg.getString("loot.floor1"));
floors[1] = new TowerStageItemManager(cfg.getString("loot.floor2"));
floors[2] = new TowerStageItemManager(cfg.getString("loot.floor3"));
floors[3] = new TowerStageItemManager(cfg.getString("loot.floor4"));
floors[4] = new TowerStageItemManager(cfg.getString("loot.floor5"));
floors[5] = new TowerStageItemManager(cfg.getString("loot.floor6"));
floors[6] = new TowerStageItemManager(cfg.getString("loot.floor7"));
floors[7] = new TowerStageItemManager(cfg.getString("loot.floor8"));
floors[8] = new TowerStageItemManager(cfg.getString("loot.floor9"));
floors[9] = new TowerStageItemManager(cfg.getString("loot.top"));
}
public TowerStageItemManager getManagerForFloor(int floor, Random rand) {
floor--; // Forge subtract
if (floor < 0) floor = 0;
if (floor >= floors.length) floor = floors.length - 1;
return new TowerStageItemManager(floors[floor]);
}
}

View File

@@ -0,0 +1,24 @@
package fyi.tiko.battletowers;
import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.generator.BlockPopulator;
import java.util.Random;
public class TowerPopulator extends BlockPopulator {
private final TowerGenerator generator = new TowerGenerator();
@Override
public void populate(World world, Random random, Chunk chunk) {
// Chance pro Chunk (erstmal test)
if (random.nextInt(250) != 0) return;
int x = chunk.getX() * 16 + 8;
int z = chunk.getZ() * 16 + 8;
generator.trySpawnTower(world, random, x, z);
}
}

View File

@@ -0,0 +1,69 @@
package fyi.tiko.battletowers;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class TowerStageItemManager {
private final List<LootEntry> entries = new ArrayList<>();
private int curIndex = 0;
public TowerStageItemManager(String configString) {
String[] elements = configString.split(";");
for (String element : elements) {
String[] data = element.trim().split("-");
if (data.length < 5) {
System.err.println("Invalid loot entry: " + element);
continue;
}
Material mat = Material.matchMaterial(data[0].toUpperCase());
if (mat == null) {
System.err.println("Unknown material: " + data[0]);
continue;
}
int damage = Integer.parseInt(data[1]); // ignored in modern MC
int chance = Integer.parseInt(data[2]);
int min = Integer.parseInt(data[3]);
int max = Integer.parseInt(data[4]);
entries.add(new LootEntry(mat, chance, min, max));
}
}
public TowerStageItemManager(TowerStageItemManager copy) {
entries.addAll(copy.entries);
}
public boolean floorHasItemsLeft() {
return curIndex < entries.size();
}
public ItemStack getStageItem(Random rand) {
if (!floorHasItemsLeft()) return null;
LootEntry entry = entries.get(curIndex);
ItemStack result = null;
if (rand.nextInt(100) < entry.chance) {
int amount = entry.min + rand.nextInt(entry.max - entry.min + 1);
result = new ItemStack(entry.material, amount);
}
curIndex++;
return result;
}
private record LootEntry(Material material, int chance, int min, int max) {}
}

View File

@@ -0,0 +1,30 @@
package fyi.tiko.battletowers;
import org.bukkit.Material;
public enum TowerType {
COBBLE(Material.COBBLESTONE, Material.STONE_BRICKS, Material.TORCH, Material.STONE_STAIRS),
MOSSY(Material.MOSSY_COBBLESTONE, Material.STONE_BRICKS, Material.TORCH, Material.STONE_STAIRS),
SANDSTONE(Material.SANDSTONE, Material.CUT_SANDSTONE, Material.TORCH, Material.SANDSTONE_STAIRS),
ICE(Material.ICE, Material.CLAY, Material.AIR, Material.OAK_STAIRS),
SMOOTH(Material.STONE, Material.SMOOTH_STONE, Material.TORCH, Material.STONE_STAIRS),
NETHER(Material.NETHERRACK, Material.SOUL_SAND, Material.GLOWSTONE, Material.NETHER_BRICK_STAIRS);
private final Material wall;
private final Material floor;
private final Material light;
private final Material stair;
TowerType(Material wall, Material floor, Material light, Material stair) {
this.wall = wall;
this.floor = floor;
this.light = light;
this.stair = stair;
}
public Material wall() { return wall; }
public Material floor() { return floor; }
public Material light() { return light; }
public Material stair() { return stair; }
}

View File

@@ -3,4 +3,19 @@ spawn:
min-distance: 196 # wie original
max-y-difference: 22
debug: true
debug: true
tower:
itemGenerateAttemptsPerFloor: 7
loot:
floor1: "stick-0-75-5-6;wheat_seeds-0-75-3-5;oak_planks-0-75-5-6;sugar_cane-0-75-3-5"
floor2: "stone_pickaxe-0-50-1-1;stone_axe-0-50-1-1;torch-0-80-3-3;stone_button-0-50-2-2"
floor3: "bowl-0-75-2-4;coal-0-90-4-4;string-0-80-5-5;white_wool-0-75-2-2"
floor4: "glass-0-75-3-3;feather-0-75-4-4;bread-0-75-2-2;apple-0-75-2-2"
floor5: "brown_mushroom-0-75-2-2;red_mushroom-0-75-2-2;oak_sapling-0-90-3-3;wheat-0-75-4-4"
floor6: "oak_sign-0-50-1-2;fishing_rod-0-75-1-1;pumpkin_seeds-0-60-2-2;melon_seeds-0-60-3-3"
floor7: "iron_sword-0-60-1-1;gunpowder-0-75-3-3;leather-0-75-4-4;cod-0-75-3-3;blue_dye-0-60-1-2"
floor8: "chainmail_helmet-0-40-1-1;chainmail_chestplate-0-40-1-1;chainmail_leggings-0-40-1-1;chainmail_boots-0-40-1-1"
floor9: "bookshelf-0-70-1-3;redstone_lamp-0-60-2-2;lily_pad-0-75-3-3;brewing_stand-0-50-1-1"
top: "ender_pearl-0-50-2-2;diamond-0-70-2-2;redstone-0-75-5-5;gold_ingot-0-90-8-8"

View File

@@ -1,6 +1,6 @@
name: battle-towers-revamped
version: 0.0.1
main: fyi.tiko.battletower.BattleTowerPlugin
main: fyi.tiko.battletowers.BattleTowersPlugin
api-version: 1.21
author: tiko
description: A revamped version of the old battle towers mod for the old modpack "hexxit". Ported from 1.5.2 to paper 1.21