Files
neolitematica/src/main/java/de/lucas/litematica/placement/PlacementManager.java
T
2026-11-04 13:36:01 +01:00

207 lines
7.2 KiB
Java

package de.lucas.litematica.placement;
import de.lucas.litematica.schematic.SchematicFiles;
import de.lucas.litematica.schematic.SchematicManager;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
/**
* All placements of the current world/server. Main thread only. Placements are persisted per world and restored
* on the next login, see {@link PlacementStorage}.
*/
public final class PlacementManager {
private static final PlacementManager INSTANCE = new PlacementManager();
private static final int SAVE_DELAY_TICKS = 40;
private final List<SchematicPlacement> placements = new ArrayList<>();
@Nullable
private UUID selected;
@Nullable
private String worldKey;
private int saveCountdown = -1;
public static PlacementManager get() {
return INSTANCE;
}
public List<SchematicPlacement> all() {
return Collections.unmodifiableList(this.placements);
}
/** Placements in the player's current dimension. */
public List<SchematicPlacement> inCurrentDimension() {
Minecraft mc = Minecraft.getInstance();
if (mc.level == null) {
return List.of();
}
ResourceLocation dim = mc.level.dimension().location();
return this.placements.stream().filter(p -> p.dimension().equals(dim)).toList();
}
@Nullable
public SchematicPlacement selected() {
for (SchematicPlacement p : this.placements) {
if (p.id().equals(this.selected)) {
return p;
}
}
return null;
}
public void select(@Nullable SchematicPlacement placement) {
this.selected = placement == null ? null : placement.id();
this.markDirty();
}
/** Selects the next placement in the current dimension, wrapping around. */
public void selectNext() {
List<SchematicPlacement> list = this.inCurrentDimension();
if (list.isEmpty()) {
return;
}
int index = list.indexOf(this.selected());
this.select(list.get((index + 1) % list.size()));
}
/** Places a schematic file with its minimum corner at {@code origin}. The file is loaded in the background. */
public SchematicPlacement place(Path file, BlockPos origin) {
Minecraft mc = Minecraft.getInstance();
ResourceLocation dim = mc.level != null ? mc.level.dimension().location() : ResourceLocation.withDefaultNamespace("overworld");
SchematicPlacement placement = new SchematicPlacement(UUID.randomUUID(), SchematicFiles.relativeName(file), dim,
SchematicFiles.baseName(file), origin.immutable(), origin.immutable(), PlacementTransform.IDENTITY, true);
this.placements.add(placement);
this.selected = placement.id();
this.loadSchematic(placement, true);
this.markDirty();
return placement;
}
public void remove(SchematicPlacement placement) {
this.placements.remove(placement);
if (placement.id().equals(this.selected)) {
this.selected = this.placements.isEmpty() ? null : this.placements.getLast().id();
}
this.markDirty();
}
public void move(SchematicPlacement placement, int dx, int dy, int dz) {
this.setOrigin(placement, placement.origin().offset(dx, dy, dz));
}
public void setOrigin(SchematicPlacement placement, BlockPos origin) {
placement.setOrigin(origin);
this.markDirty();
}
public void resetOrigin(SchematicPlacement placement) {
this.setOrigin(placement, placement.originalOrigin());
}
/** Makes the current position the one {@link #resetOrigin} returns to. */
public void storeOriginAsOriginal(SchematicPlacement placement) {
placement.setOriginalOrigin(placement.origin());
this.markDirty();
}
public void setTransform(SchematicPlacement placement, PlacementTransform transform) {
placement.setTransform(transform);
this.markDirty();
}
public void setEnabled(SchematicPlacement placement, boolean enabled) {
placement.setEnabled(enabled);
this.markDirty();
}
public void rename(SchematicPlacement placement, String name) {
if (!name.isBlank()) {
placement.setName(name.trim());
this.markDirty();
}
}
/** Reloads all placements that use the given file, e.g. after it was replaced on disk. */
public void reloadFile(Path file) {
String relative = SchematicFiles.relativeName(file);
SchematicManager.get().invalidate(file);
for (SchematicPlacement p : this.placements) {
if (p.schematicFile().equals(relative)) {
this.loadSchematic(p, false);
}
}
}
public void tick() {
if (this.saveCountdown >= 0 && --this.saveCountdown < 0) {
this.save();
}
}
public void onLogin() {
this.placements.clear();
this.worldKey = PlacementStorage.currentWorldKey();
PlacementStorage.Loaded loaded = PlacementStorage.load(this.worldKey);
this.placements.addAll(loaded.placements());
this.selected = loaded.selected();
for (SchematicPlacement p : this.placements) {
this.loadSchematic(p, false);
}
}
public void onLogout() {
this.save();
this.placements.clear();
this.selected = null;
this.worldKey = null;
}
private void markDirty() {
this.saveCountdown = SAVE_DELAY_TICKS;
}
private void save() {
this.saveCountdown = -1;
if (this.worldKey != null) {
PlacementStorage.save(this.worldKey, this.placements, this.selected);
}
}
private void loadSchematic(SchematicPlacement placement, boolean announce) {
Minecraft mc = Minecraft.getInstance();
Path file = SchematicFiles.resolve(placement.schematicFile());
SchematicManager.get().load(file).whenComplete((schematic, error) -> mc.execute(() -> {
if (!this.placements.contains(placement)) {
return;
}
if (error != null) {
String message = SchematicManager.errorMessage(error);
placement.setSchematic(null, message);
notify(Component.translatable("neolitematica.message.load_failed", placement.schematicFile(), message).withStyle(ChatFormatting.RED));
} else {
placement.setSchematic(schematic, null);
if (announce) {
notify(Component.translatable("neolitematica.message.placed", placement.name(),
placement.origin().getX(), placement.origin().getY(), placement.origin().getZ()));
}
}
}));
}
private static void notify(Component message) {
Minecraft mc = Minecraft.getInstance();
if (mc.player != null) {
mc.player.displayClientMessage(message, false);
}
}
}