Signs.java
AKL City ยท akl-city.git
Branches > main > 918770c > files / src / main / java / dev / grahp / aklcity / Signs.java
package dev.grahp.aklcity;
import org.bukkit.event.Listener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.*;
import org.bukkit.plugin.java.JavaPlugin;
import net.kyori.adventure.text.*;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.block.Sign;
import org.bukkit.block.sign.Side;
import java.util.*;
import java.util.regex.*;
import dev.grahp.option.Optional;
import static dev.grahp.aklcity.utils.Paper.*;
// TODO is there a better interface than signs?
// Perhaps with text entities?
// Maybe they could even spawn their own signs that you can edit?
// Need a disk persistent mapping of addresses to maps of other things
// Keeps track of stuff on signs
// - How will the residents know what beds to go into?
class Signs implements Listener {
public static record Address(Long number, String street) { }
public static record Building(String city, Address address, Long houses, Long jobs) { }
// TODO so this needs to be persisted to the db.
private static class BuildingRegistry {
private final Map<Location, Building> buildings = new HashMap<>();
private final Map<String, Long> houses = new HashMap<>();
private final Map<String, Long> jobs = new HashMap<>();
@Override
public String toString() {
return "BuildingRegistry[buildings=" + buildings + ", houses=" + houses + ", jobs=" + jobs + "]";
}
// Don't just clear...
public void clear() {
buildings.clear();
houses.clear();
jobs.clear();
}
public void put(Location location, Building building) {
var old = buildings.put(location, building);
if (old != null) {
var oldCity = old.city();
houses.put(oldCity, houses.getOrDefault(oldCity, 0L) - old.houses());
jobs.put(oldCity, jobs.getOrDefault(oldCity, 0L) - old.jobs());
}
var city = building.city();
houses.put(city, houses.getOrDefault(city, 0L) + building.houses());
jobs.put(city, jobs.getOrDefault(city, 0L) + building.jobs());
// debug("houses", houses);
// debug("jobs", jobs);
}
public void remove(Location l) {
var b = buildings.remove(l);
if (b != null) {
var city = b.city();
houses.put(city, houses.getOrDefault(city, 0L) - b.houses());
jobs.put(city, jobs.getOrDefault(city, 0L) - b.jobs());
// debug("houses", houses);
// debug("jobs", jobs);
}
}
}
private static final BuildingRegistry buildings = new BuildingRegistry();
private static final Pattern POP_PATTERN = Pattern.compile("^T:\\s*(.+)$"); // T: Pine
private static final Pattern CITY_PATTERN = Pattern.compile("^P:\\s*(.+)$"); // P: Burg
private static final Pattern ADDRESS_PATTERN = Pattern.compile("^(\\d+)\\s+(.+)$"); // 322 Test
private static final Pattern UNITS_PATTERN = Pattern.compile("^(\\d+)\\s*/\\s*(\\d+)$"); // 6/7
private Location blockLocation(Sign block) {
return block.getLocation().toBlockLocation();
}
private Sign sign(BlockEvent e) {
return (Sign) e.getBlock().getState();
}
private boolean isPopulationSign(Sign sign) {
var lines = sign.getSide(Side.FRONT).getLines();
return POP_PATTERN.matcher(lines[0]).matches();
}
private Optional<Building> getSignBuilding(Sign sign) {
var lines = sign.getSide(Side.FRONT).getLines();
var addressLine = lines[1];
var cityMatcher = CITY_PATTERN.matcher(lines[0]);
var unitsMatcher = UNITS_PATTERN.matcher(lines[2]);
var addressMatcher = ADDRESS_PATTERN.matcher(addressLine);
if (cityMatcher.matches() && unitsMatcher.matches() && addressMatcher.matches()) {
var city = cityMatcher.group(1);
var houses = Long.parseLong(unitsMatcher.group(1));
var jobs = Long.parseLong(unitsMatcher.group(2));
var buildingNumber = Long.parseLong(addressMatcher.group(1));
var street = addressMatcher.group(2);
var address = new Address(buildingNumber, street);
var building = new Building(city, address, houses, jobs);
return new Optional(building);
} else {
return new Optional();
}
}
// TODO color the sign text red or gray or something once the sign has actually been confirmed
// That way it's obvious the sign is working as intended
// They would store that color though and thus load in inproperly
// Perhaps display a particle or something above working signs
// Particles could be laggy?
@EventHandler
public void onEditSign(SignChangeEvent e) {
var sign = sign(e);
// TODO building is the OLD building, not the new one!
getSignBuilding(sign).then(b -> {
buildings.put(blockLocation(sign), b);
var address = b.address();
e.getPlayer().sendMessage(Component.text("Saved Building '" + address.number().toString() + " " + address.street() + "'", NamedTextColor.GREEN));
});
}
// On breaking a block, send a message that the player broke a building
// Could it be possible to undo the sign break?
// Shift to break a sign?
// Or break it twice
@EventHandler
public void onBreakSign(BlockBreakEvent e) {
var sign = sign(e);
getSignBuilding(sign).then(b -> {
buildings.remove(blockLocation(sign));
var address = b.address();
e.getPlayer().sendMessage(Component.text("Deleted Building '" + address.number().toString() + " " + address.street() + "'", NamedTextColor.RED));
});
}
@EventHandler
public void onPlaceSign(BlockPlaceEvent e) {
var sign = sign(e);
getSignBuilding(sign).then(b -> {
buildings.put(blockLocation(sign), b);
var address = b.address();
e.getPlayer().sendMessage(Component.text("Added Building '" + address.number().toString() + " " + address.street() + "'", NamedTextColor.GREEN));
});
}
// On block place, add the sign as well
// On loading of the world, add all signs to the set
// On plugin reload, get all the signs in all loaded chunks again
public void scanWorldSigns() {
buildings.clear();
var populationSigns = new HashSet<Sign>();
for (var world : Bukkit.getWorlds()) {
for (var chunk : world.getLoadedChunks()) {
// TODO are signs still tile entities? After 26.2 or something.
for (var block : chunk.getTileEntities()) {
if (block instanceof Sign sign) {
getSignBuilding(sign)
.then(b -> { buildings.put(blockLocation(sign), b); },
() -> { populationSigns.add(sign); });
}
}
}
}
// for (var sign : populationSigns) {
// var front = sign.getSide(Side.FRONT);
// POP_PATTERN.matcher(front.line(0))
// var jobs = Long.parseLong(unitsMatcher.group(2));
// front.line(1, Component.text("Houses: " + ))
// }
// debug(buildings);
}
public Signs(JavaPlugin plugin) {
Bukkit.getScheduler().runTaskTimer(
plugin,
this::scanWorldSigns,
0L,
100L
);
}
}