This commit is contained in:
2026-02-03 20:38:54 +01:00
commit 34be34db9d
20 changed files with 996 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
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

@@ -0,0 +1,22 @@
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

@@ -0,0 +1,296 @@
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

@@ -0,0 +1,96 @@
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

@@ -0,0 +1,64 @@
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

@@ -0,0 +1,39 @@
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,6 @@
spawn:
chance-per-chunk: 250 # 1/X chunks
min-distance: 196 # wie original
max-y-difference: 22
debug: true

View File

@@ -0,0 +1,7 @@
name: battle-towers-revamped
version: 0.0.1
main: fyi.tiko.battletower.BattleTowerPlugin
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