Skip to content

Commit

Permalink
Lockette migration
Browse files Browse the repository at this point in the history
  • Loading branch information
pop4959 committed Feb 11, 2024
1 parent 5177395 commit c0ddeb6
Show file tree
Hide file tree
Showing 2 changed files with 354 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.popcraft.bolt.BoltPlugin;
import org.popcraft.bolt.command.Arguments;
import org.popcraft.bolt.command.BoltCommand;
import org.popcraft.bolt.data.migration.lockette.LocketteMigration;
import org.popcraft.bolt.data.migration.lwc.BoltMigration;
import org.popcraft.bolt.data.migration.lwc.LWCMigration;
import org.popcraft.bolt.lang.Translation;
Expand All @@ -17,9 +18,11 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

public class AdminConvertCommand extends BoltCommand {
private final Set<String> locketteNames = Set.of("lockette", "lockettepro", "deadbolt", "blocklocker");
private final AtomicBoolean isConverting = new AtomicBoolean();
private LWCMigration lastMigration;

Expand Down Expand Up @@ -56,8 +59,9 @@ public void execute(CommandSender sender, Arguments arguments) {
lastMigration = null;
return;
}
final boolean convertBack = "back".equalsIgnoreCase(arg);
if (convertBack) {
final boolean convertBolt = "bolt".equalsIgnoreCase(arg);
final boolean lockette = arg != null && locketteNames.contains(arg.toLowerCase());
if (convertBolt) {
if (!plugin.getServer().getPluginManager().isPluginEnabled("LWC")) {
BoltComponents.sendMessage(sender, Translation.MIGRATION_LWC_MISSING);
return;
Expand All @@ -77,6 +81,32 @@ public void execute(CommandSender sender, Arguments arguments) {
}
BoltComponents.sendMessage(sender, Translation.MIGRATION_COMPLETED);
}, SchedulerUtil.executor(plugin, sender));
} else if (lockette) {
final LocketteMigration migration = new LocketteMigration(plugin);
final String pluginName = switch(arg.toLowerCase()) {
case "lockette" -> "Lockette";
case "lockettepro" -> "LockettePro";
case "deadbolt" -> "DeadBolt";
case "blocklocker" -> "BlockLocker";
default -> throw new IllegalStateException();
};
BoltComponents.sendMessage(
sender,
Translation.MIGRATION_STARTED,
Placeholder.component(Translation.Placeholder.OLD_PLUGIN, Component.text(pluginName)),
Placeholder.component(Translation.Placeholder.NEW_PLUGIN, Component.text("Bolt"))
);
isConverting.set(true);
migration.convertAsync().whenCompleteAsync(((memoryStore, throwable) -> {
isConverting.set(false);
if (throwable != null) {
throwable.printStackTrace();
}
for (final BlockProtection blockProtection : memoryStore.loadBlockProtections().join()) {
plugin.saveProtection(blockProtection);
}
BoltComponents.sendMessage(sender, Translation.MIGRATION_COMPLETED);
}), SchedulerUtil.executor(plugin, sender));
} else {
final LWCMigration migration = new LWCMigration(plugin);
BoltComponents.sendMessage(
Expand Down Expand Up @@ -117,7 +147,8 @@ public List<String> suggestions(CommandSender sender, Arguments arguments) {
}
arguments.next();
if (arguments.remaining() == 0) {
final List<String> suggestions = new ArrayList<>(List.of("back"));
final List<String> suggestions = new ArrayList<>(List.of("lwc", "bolt"));
suggestions.addAll(locketteNames);
if (lastMigration != null && lastMigration.hasEntityBlocks()) {
suggestions.add("entities");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
package org.popcraft.bolt.data.migration.lockette;

import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.type.WallSign;
import org.popcraft.bolt.BoltPlugin;
import org.popcraft.bolt.data.MemoryStore;
import org.popcraft.bolt.protection.BlockProtection;
import org.popcraft.bolt.source.Source;
import org.popcraft.bolt.util.BlockLocation;
import org.popcraft.bolt.util.ChunkPos;
import org.popcraft.bolt.util.PaperUtil;
import org.popcraft.bolt.util.Profiles;
import org.popcraft.chunky.nbt.ByteTag;
import org.popcraft.chunky.nbt.CompoundTag;
import org.popcraft.chunky.nbt.IntTag;
import org.popcraft.chunky.nbt.ListTag;
import org.popcraft.chunky.nbt.LongArrayTag;
import org.popcraft.chunky.nbt.StringTag;
import org.popcraft.chunky.nbt.Tag;
import org.popcraft.chunky.nbt.util.RegionFile;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Semaphore;
import java.util.stream.Stream;

public class LocketteMigration {
private final BoltPlugin plugin;

public LocketteMigration(final BoltPlugin plugin) {
this.plugin = plugin;
}

public CompletableFuture<MemoryStore> convertAsync() {
return CompletableFuture.supplyAsync(this::convert);
}

private MemoryStore convert() {
final MemoryStore store = new MemoryStore();
Bukkit.getServer().getWorlds().forEach(world -> {
final Optional<Path> regionDirectory = findRegionDirectory(world);
if (regionDirectory.isPresent()) {
final List<LocketteProtection> locketteProtections = new ArrayList<>();
try (final Stream<Path> regionWalker = Files.walk(regionDirectory.get())) {
regionWalker.filter(path -> {
final String fileName = path.getFileName().toString();
return fileName.startsWith("r.") && fileName.endsWith(".mca");
}).forEach(region -> {
final RegionFile regionFile = new RegionFile(region.toFile());
regionFile.getChunks().forEach(chunk -> chunk.getData().getList("block_entities")
.map(ListTag::value)
.ifPresent(blockEntityTags -> {
for (final Tag tag : blockEntityTags) {
if (!(tag instanceof final CompoundTag blockEntityCompound)) {
continue;
}
final String id = blockEntityCompound.getString("id").map(StringTag::value).orElse(null);
if (!"minecraft:sign".equals(id)) {
continue;
}
final LocketteProtection pdcProtection = fromPersistentData(blockEntityCompound);
if (pdcProtection != null) {
locketteProtections.add(pdcProtection);
} else {
final LocketteProtection messagesProtection = fromSignMessages(blockEntityCompound);
if (messagesProtection != null) {
locketteProtections.add(messagesProtection);
}
}
}
}));
});
} catch (IOException e) {
e.printStackTrace();
}
final int permits = 10;
final Semaphore working = new Semaphore(permits);
for (final LocketteProtection locketteProtection : locketteProtections) {
final int chunkX = locketteProtection.x() >> 4;
final int chunkZ = locketteProtection.z() >> 4;
final ChunkPos chunkPos = new ChunkPos(world.getName(), chunkX, chunkZ);
try {
working.acquire();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
PaperUtil.getChunkAtAsync(world, chunkX, chunkZ)
.thenAccept(ignored -> {
final Block signBlock = world.getBlockAt(locketteProtection.x(), locketteProtection.y(), locketteProtection.z());
if (signBlock.getBlockData() instanceof final WallSign wallSign) {
final BlockFace facing = wallSign.getFacing();
final Block block = signBlock.getRelative(facing.getOppositeFace());
final BlockLocation blockLocation = new BlockLocation(world.getName(), block.getX(), block.getY(), block.getZ());
final BlockProtection existing = store.loadBlockProtection(blockLocation).join();
if (existing == null) {
final BlockProtection protection = plugin.createProtection(block, locketteProtection.owner(), locketteProtection.type());
protection.setAccess(locketteProtection.access());
store.saveBlockProtection(protection);
} else {
if (Profiles.NIL_UUID.equals(existing.getOwner())) {
existing.setOwner(locketteProtection.owner());
}
existing.getAccess().putAll(locketteProtection.access());
if ("public".equals(locketteProtection.type())) {
existing.setType("public");
}
store.saveBlockProtection(existing);
}
}
})
.thenRun(working::release);
}
working.acquireUninterruptibly(permits);
}
});
return store;
}

private LocketteProtection fromPersistentData(final CompoundTag sign) {
final Integer x = sign.getInt("x").map(IntTag::value).orElse(null);
final Integer y = sign.getInt("y").map(IntTag::value).orElse(null);
final Integer z = sign.getInt("z").map(IntTag::value).orElse(null);
if (x == null || y == null || z == null) {
return null;
}
final CompoundTag pdcCompound = sign.getCompound("PublicBukkitValues")
.orElse(null);
if (pdcCompound == null) {
return null;
}
final String header = pdcCompound.getString("blocklocker:header")
.map(StringTag::value)
.orElse(null);
if (header == null) {
return null;
}
boolean isPublic = false;
boolean isRedstone = false;
UUID owner = null;
final Map<String, String> access = new HashMap<>();
for (int i = 0; i < 6; ++i) {
final int profileId = i + 1;
final CompoundTag profileCompound = pdcCompound.getCompound("blocklocker:profile_%d".formatted(profileId))
.orElse(null);
if (profileCompound == null) {
continue;
}
// Player profile name
final String n = profileCompound.getString("blocklocker:n")
.map(StringTag::value)
.orElse(null);
// Player profile uuid
final long[] u = profileCompound.getLongArray("blocklocker:u")
.map(LongArrayTag::value)
.orElse(null);
// Everyone
final Byte e = profileCompound.getByte("blocklocker:e")
.map(ByteTag::value)
.orElse(null);
// Group leader
final String l = profileCompound.getString("blocklocker:l")
.map(StringTag::value)
.orElse(null);
// Group
final String g = profileCompound.getString("blocklocker:g")
.map(StringTag::value)
.orElse(null);
// Redstone
final Byte r = profileCompound.getByte("blocklocker:r")
.map(ByteTag::value)
.orElse(null);
// Timer
final Integer t = profileCompound.getInt("blocklocker:t")
.map(IntTag::value)
.orElse(null);
if (n != null) {
// Player profile
final UUID uuid;
if (u == null) {
uuid = Profiles.findOrLookupProfileByName(n).join().uuid();
} else {
uuid = new UUID(u[0], u[1]);
}
if (uuid != null) {
if ("PRIVATE".equals(header) && owner == null) {
owner = uuid;
} else {
access.put(Source.player(uuid).toString(), "normal");
}
}
} else if (e != null && e == 1) {
// Everyone
isPublic = true;
} else if (l != null) {
// Group leader
access.put(Source.of("permission", "group.%s".formatted(l)).toString(), "normal");
} else if (g != null) {
// Group
access.put(Source.of("permission", "group.%s".formatted(g)).toString(), "normal");
} else if (r != null && r == 1) {
// Redstone
isRedstone = true;
} else if (t != null) {
// Timer
access.put(Source.of("door").toString(), "autoclose");
}
}
final String type = isPublic ? "public" : "private";
return new LocketteProtection(x, y, z, Objects.requireNonNullElse(owner, Profiles.NIL_UUID), type, access);
}

private LocketteProtection fromSignMessages(final CompoundTag sign) {
final Integer x = sign.getInt("x").map(IntTag::value).orElse(null);
final Integer y = sign.getInt("y").map(IntTag::value).orElse(null);
final Integer z = sign.getInt("z").map(IntTag::value).orElse(null);
if (x == null || y == null || z == null) {
return null;
}
final List<String> messages = new ArrayList<>(sign.getCompound("front_text")
.flatMap(compound -> compound.getList("messages"))
.map(ListTag::value)
.map(list -> {
final List<String> messageList = new ArrayList<>();
for (final Tag tag : list) {
if (tag instanceof final StringTag message && !message.value().isBlank()) {
messageList.add(message.value());
}
}
return messageList;
})
.orElse(List.of()));
messages.addAll(sign.getCompound("back_text")
.flatMap(compound -> compound.getList("messages"))
.map(ListTag::value)
.map(list -> {
final List<String> messageList = new ArrayList<>();
for (final Tag tag : list) {
if (tag instanceof final StringTag message && !message.value().isBlank()) {
messageList.add(message.value());
}
}
return messageList;
})
.orElse(List.of()));
boolean isValid = false;
boolean isPublic = false;
boolean isRedstone = false;
boolean hasPrivateHeader = false;
UUID owner = null;
final Map<String, String> access = new HashMap<>();
for (final String message : messages) {
final String cleaned = message.replaceAll("\"", "");
final boolean privateHeader = cleaned.contains("[Private]");
final boolean moreUsersHeader = cleaned.contains("[More Users]");
if (privateHeader || moreUsersHeader) {
isValid = true;
hasPrivateHeader = privateHeader;
} else if (cleaned.contains("[Everyone]")) {
isPublic = true;
} else if (cleaned.contains("[Redstone]")) {
isRedstone = true;
} else if (cleaned.contains("Timer")) {
access.put(Source.of("door").toString(), "autoclose");
} else {
UUID uuid;
final boolean isLocketteProFormat = cleaned.contains("#");
if (isLocketteProFormat) {
final int uuidStart = cleaned.indexOf("#") + 1;
try {
final String name = cleaned.substring(uuidStart).trim().replace(" ", "");
uuid = UUID.fromString(name);
} catch (IllegalArgumentException ignore) {
uuid = null;
}
} else {
final String name = cleaned.trim().replace(" ", "");
uuid = Profiles.findOrLookupProfileByName(name).join().uuid();
}
if (uuid != null) {
if (hasPrivateHeader && owner == null) {
owner = uuid;
} else {
access.put(Source.player(uuid).toString(), "normal");
}
}
}
}
if (!isValid) {
return null;
}
final String type = isPublic ? "public" : "private";
return new LocketteProtection(x, y, z, Objects.requireNonNullElse(owner, Profiles.NIL_UUID), type, access);
}

private Optional<Path> findRegionDirectory(final World world) {
try (final Stream<Path> paths = Files.walk(world.getWorldFolder().toPath())) {
return paths.filter(Files::isDirectory)
.filter(path -> "region".equals(path.getFileName().toString()))
.findFirst();
} catch (IOException e) {
e.printStackTrace();
}
return Optional.empty();
}

private record LocketteProtection(int x, int y, int z, UUID owner, String type, Map<String, String> access) {
}
}

0 comments on commit c0ddeb6

Please sign in to comment.