Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 93 additions & 14 deletions src/main/java/net/tfminecraft/vfbuilders/core/ActiveStation.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package net.tfminecraft.vfbuilders.core;

import java.util.UUID;
import java.util.function.BooleanSupplier;

import org.bukkit.Bukkit;
import org.bukkit.Location;
Expand All @@ -9,13 +10,16 @@
import org.bukkit.entity.TextDisplay;

import net.tfminecraft.vfbuilders.api.ConstructionFreezes;
import net.tfminecraft.vfbuilders.VFBuilders;
import net.tfminecraft.vfbuilders.display.StationTimerDisplay;
import net.tfminecraft.vfbuilders.events.VehicleConstructEvent;
import net.tfminecraft.vfbuilders.events.VehicleConstructionCancelEvent;
import net.tfminecraft.vfbuilders.loaders.BlueprintLoader;
import net.tfminecraft.vfbuilders.loaders.StationLoader;
import net.tfminecraft.vehicleframework.VFLogger;
import net.tfminecraft.vehicleframework.VehicleFramework;
import net.tfminecraft.vehicleframework.database.VehiclePersistence;
import net.tfminecraft.vehicleframework.enums.VehicleRemoveReason;
import net.tfminecraft.vehicleframework.managers.VehicleManager;
import net.tfminecraft.vehicleframework.vehicles.ActiveVehicle;

Expand All @@ -29,6 +33,9 @@ public class ActiveStation {
private Location spawnLoc;
private UUID constructorUuid;
private UUID constructorCharacterId;
private String outputUuid;
private boolean constructionEventSent;
private BooleanSupplier checkpoint = () -> true;

private TextDisplay timerDisplay;
private String freezeReason;
Expand Down Expand Up @@ -93,6 +100,8 @@ public void selectBlueprint(Blueprint b, UUID constructorUuid, UUID constructorC
timeLeft = b.getTime();
this.constructorUuid = constructorUuid;
this.constructorCharacterId = constructorCharacterId;
outputUuid = null;
constructionEventSent = false;
freezeReason = null;
ensureDisplay();
}
Expand Down Expand Up @@ -135,16 +144,46 @@ public void complete() {
}

VehicleManager manager = VehicleFramework.getVehicleManager();
ActiveVehicle vehicle = manager.spawn(spawnLoc, blueprint.getVehicle());
VehiclePersistence persistence = VehiclePersistence.current();
if (persistence == null) {
VFLogger.log("Cannot complete construction: SQLite is not open at " + loc);
return;
}
if (outputUuid != null && persistence.repository().find(outputUuid).isPresent()) {
VFLogger.info(VFBuilders.plugin, "Construction output already stored: station=" + loc
+ " vehicle=" + outputUuid);
clearConstructionDurably();
return;
}
ActiveVehicle vehicle = outputUuid == null ? null : manager.getByUUID(outputUuid);
if (vehicle == null) {
vehicle = manager.spawn(spawnLoc, blueprint.getVehicle());
if (vehicle != null) {
outputUuid = vehicle.getUUID();
constructionEventSent = false;
if (!checkpoint.getAsBoolean()) {
vehicle.remove(VehicleRemoveReason.UNLOAD);
outputUuid = null;
return;
}
}
}
if (vehicle == null) {
VFLogger.log("Failed to spawn vehicle for blueprint " + blueprint.getId() + " at station " + loc);
return;
}

Location completedSpawn = spawnLoc.clone();
Bukkit.getPluginManager().callEvent(
new VehicleConstructEvent(
constructorUuid, constructorCharacterId, vehicle, blueprint, completedSpawn, this));
if (!constructionEventSent) {
constructionEventSent = true;
Bukkit.getPluginManager().callEvent(
new VehicleConstructEvent(
constructorUuid, constructorCharacterId, vehicle, blueprint, completedSpawn, this));
}
if (!persistence.saveLive(vehicle)) {
VFLogger.log("Cannot save constructed vehicle " + outputUuid + " at " + loc);
return;
}

// Visual effects
Location center = completedSpawn.clone().add(0.5, 1, 0.5);
Expand All @@ -156,26 +195,66 @@ public void complete() {
center.getWorld().playSound(center, Sound.ENTITY_IRON_GOLEM_REPAIR, 1f, 1.2f);
center.getWorld().playSound(center, Sound.BLOCK_BEACON_ACTIVATE, 0.8f, 1.6f);

spawnLoc = null;
blueprint = null;
constructorUuid = null;
constructorCharacterId = null;
clearConstructionDurably();
}

public void cancelConstruction() {
if (blueprint != null) {
blueprint.drop(loc.clone().add(0.5, 1, 0.5));
if (outputUuid != null) {
VehiclePersistence persistence = VehiclePersistence.current();
if (persistence != null && persistence.repository().find(outputUuid).isPresent()) {
if (clearConstructionDurably()) {
removeHolograms();
freezeReason = null;
timeLeft = 0;
}
}
return;
}
Blueprint cancelled = blueprint;
UUID account = constructorUuid;
UUID character = constructorCharacterId;
if (!clearConstructionDurably()) return;
if (cancelled != null) {
cancelled.drop(loc.clone().add(0.5, 1, 0.5));
Bukkit.getPluginManager().callEvent(
new VehicleConstructionCancelEvent(
constructorUuid, constructorCharacterId, blueprint, this));
account, character, cancelled, this));
}
removeHolograms();
spawnLoc = null;
freezeReason = null;
timeLeft = 0;
}

private boolean clearConstructionDurably() {
Blueprint previousBlueprint = blueprint;
Location previousSpawn = spawnLoc;
UUID previousAccount = constructorUuid;
UUID previousCharacter = constructorCharacterId;
String previousOutput = outputUuid;
blueprint = null;
spawnLoc = null;
constructorUuid = null;
constructorCharacterId = null;
freezeReason = null;
timeLeft = 0;
outputUuid = null;
if (checkpoint.getAsBoolean()) return true;
blueprint = previousBlueprint;
spawnLoc = previousSpawn;
constructorUuid = previousAccount;
constructorCharacterId = previousCharacter;
outputUuid = previousOutput;
return false;
}

public void setCheckpoint(BooleanSupplier checkpoint) {
this.checkpoint = checkpoint;
}

public String getOutputUuid() {
return outputUuid;
}

public void setOutputUuid(String outputUuid) {
this.outputUuid = outputUuid;
}

public UUID getUuid() {
Expand Down
63 changes: 44 additions & 19 deletions src/main/java/net/tfminecraft/vfbuilders/data/Database.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

import java.io.*;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.*;

public class Database {
Expand All @@ -22,28 +25,48 @@ public class Database {
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();

public static void saveStations(Map<Location, ActiveStation> stations) {
List<StationData> dataList = new ArrayList<>();

for (ActiveStation station : stations.values()) {
station.removeHolograms();
StationData data = new StationData();
data.location = toSerializableLoc(station.getLocation());
data.stationId = station.getStation().getId(); // Save station identifier
data.spawnLocation = station.hasSpawnLocation() ? toSerializableLoc(station.getSpawnLocation()) : null;
data.blueprintId = station.hasBlueprint() ? station.getBlueprint().getId() : null;
data.timeLeft = station.getTimeLeft();
if (station.hasBlueprint()) {
data.constructorUuid = toText(station.getConstructorUuid());
data.constructorCharacterId = toText(station.getConstructorCharacterId());
}
saveStationsNow(stations);
}

dataList.add(data);
}
public static boolean saveStationsNow(Map<Location, ActiveStation> stations) {
try {
List<StationData> dataList = new ArrayList<>();

for (ActiveStation station : stations.values()) {
StationData data = new StationData();
data.location = toSerializableLoc(station.getLocation());
data.stationId = station.getStation().getId(); // Save station identifier
data.spawnLocation = station.hasSpawnLocation() ? toSerializableLoc(station.getSpawnLocation()) : null;
data.blueprintId = station.hasBlueprint() ? station.getBlueprint().getId() : null;
data.timeLeft = station.getTimeLeft();
data.outputUuid = station.getOutputUuid();
if (station.hasBlueprint()) {
data.constructorUuid = toText(station.getConstructorUuid());
data.constructorCharacterId = toText(station.getConstructorCharacterId());
}

dataList.add(data);
}

try (Writer writer = new FileWriter(file)) {
gson.toJson(dataList, writer);
} catch (IOException e) {
var directory = file.toPath().getParent();
Files.createDirectories(directory);
var temporary = Files.createTempFile(directory, "stations-", ".tmp");
boolean replaced = false;
try {
try (FileOutputStream output = new FileOutputStream(temporary.toFile())) {
output.write(gson.toJson(dataList).getBytes(StandardCharsets.UTF_8));
output.getFD().sync();
}
Files.move(temporary, file.toPath(),
StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
replaced = true;
} finally {
if (!replaced) Files.deleteIfExists(temporary);
}
return true;
} catch (IOException | RuntimeException e) {
e.printStackTrace();
return false;
}
}

Expand Down Expand Up @@ -71,6 +94,7 @@ public static HashMap<Location, ActiveStation> loadStations() {
// Stations saved before 2.2.0 have no character; SimpleFactions resolves the heir.
toUuid(data.constructorCharacterId)
);
station.setOutputUuid(data.outputUuid);

loaded.put(loc, station);
}
Expand Down Expand Up @@ -125,6 +149,7 @@ private static class StationData {
int timeLeft;
String constructorUuid;
String constructorCharacterId;
String outputUuid;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import net.tfminecraft.vfbuilders.loaders.BlueprintLoader;
import net.tfminecraft.vfbuilders.loaders.CategoryLoader;
import net.tfminecraft.vfbuilders.loaders.StationLoader;
import net.tfminecraft.vehicleframework.VFLogger;

public class StationManager implements Listener {
private HashMap<Location, ActiveStation> stations = new HashMap<>();
Expand All @@ -64,6 +65,7 @@ public ActiveStation getStation(Block b) {
if(checker.checkBlock(b, station.getBlock())) {
ActiveStation a = new ActiveStation(b.getLocation(), station);
stations.put(b.getLocation(), a);
bindCheckpoint(a);
return a;
}
}
Expand All @@ -72,9 +74,18 @@ public ActiveStation getStation(Block b) {

public void start() {
stations = Database.loadStations();
stations.values().forEach(this::bindCheckpoint);
refreshAllDisplays();
startStationTrailLoop();
tickCycle();
new BukkitRunnable() {
@Override
public void run() {
if (stations.values().stream().anyMatch(ActiveStation::hasBlueprint)) {
Database.saveStationsNow(stations);
}
}
}.runTaskTimer(VFBuilders.plugin, 600L, 600L);
}

public void stop() {
Expand All @@ -89,6 +100,7 @@ public void rebindAfterReload(Runnable reloadDefinitions) {
for (ActiveStation station : stations.values()) {
station.rebindDefinitions();
}
Database.saveStationsNow(stations);
refreshAllDisplays();
}

Expand All @@ -104,6 +116,10 @@ public void run() {
}.runTaskTimer(VFBuilders.plugin, 0L, 20L); // every 5 ticks (0.25s)
}

private void bindCheckpoint(ActiveStation station) {
station.setCheckpoint(() -> Database.saveStationsNow(stations));
}

public void startStationTrailLoop() {
new BukkitRunnable() {
@Override
Expand Down Expand Up @@ -262,6 +278,11 @@ public void onPlayerLeftClick(PlayerInteractEvent e) {
}

ActiveStation station = placement.getStation();
if (station.hasBlueprint()) {
activePlacements.remove(uuid);
p.sendMessage("§cThis station is already constructing a vehicle.");
return;
}
// Credit the character playing now; without a current session the build still starts.
UUID characterId = ConstructorCharacter.of(p);
BeginVehicleConstructionEvent beginEvent = new BeginVehicleConstructionEvent(
Expand All @@ -279,6 +300,18 @@ public void onPlayerLeftClick(PlayerInteractEvent e) {
placement.getStation().setSpawnLocation(clickLoc);
placement.setFinalSpawnLocation(clickLoc);
station.selectBlueprint(blueprint, p.getUniqueId(), characterId);
if (!Database.saveStationsNow(stations)) {
// The atomic write kept the previous idle snapshot; undo only this rejected start.
station.setCheckpoint(() -> true);
station.cancelConstruction();
bindCheckpoint(station);
activePlacements.remove(uuid);
p.sendMessage("§cConstruction could not be saved. Your materials have been returned.");
return;
}
VFLogger.info(VFBuilders.plugin, "Construction started: station=" + stationLoc
+ " blueprint=" + blueprint.getId() + " account=" + p.getUniqueId()
+ " character=" + characterId);
p.sendMessage("§aBuilding site chosen!");
p.sendTitle("", "§eStarted Constructing "+blueprint.getVehicle().getName(), 10, 60, 10);
p.playSound(p, Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
Expand Down Expand Up @@ -360,8 +393,11 @@ public void onBlockBreak(BlockBreakEvent e) {
Location loc = block.getLocation();

if (!stations.containsKey(loc)) return;
e.getPlayer().sendMessage("§cStation removed.");
remove(loc);
if (remove(loc)) e.getPlayer().sendMessage("§cStation removed.");
else {
e.setCancelled(true);
e.getPlayer().sendMessage("§cThe station cannot be removed right now. Try again.");
}
}

@EventHandler
Expand All @@ -370,19 +406,29 @@ public void onFurnitureBreak(FurnitureBreakEvent e) {
Location loc = e.getBukkitEntity().getLocation().getBlock().getLocation();

if (!stations.containsKey(loc)) return;
e.getPlayer().sendMessage("§cStation removed.");
remove(loc);
if (remove(loc)) e.getPlayer().sendMessage("§cStation removed.");
else {
e.setCancelled(true);
e.getPlayer().sendMessage("§cThe station cannot be removed right now. Try again.");
}
}

private void remove(Location loc) {
ActiveStation station = stations.remove(loc);
private boolean remove(Location loc) {
ActiveStation station = stations.get(loc);
station.removeHolograms();

if (station.hasBlueprint()) {
station.cancelConstruction();
if (station.hasBlueprint()) return false;
}

stations.remove(loc);
station.setSpawnLocation(null); // Important to stop the particle trail
if (!Database.saveStationsNow(stations)) {
stations.put(loc, station);
return false;
}
return true;
}

}
Loading