Add Initial Better schematic format (in World)
Alle Prüfungen waren erfolgreich
SteamWarCI Build successful

Signed-off-by: yoyosource <yoyosource@nidido.de>
Dieser Commit ist enthalten in:
yoyosource 2023-01-29 00:13:15 +01:00
Ursprung 6e56ecdc99
Commit 91abbc25fc
6 geänderte Dateien mit 703 neuen und 0 gelöschten Zeilen

Datei anzeigen

@ -0,0 +1,72 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2023 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.cuboid;
import de.steamwar.bausystem.shared.Pair;
import de.steamwar.bausystem.utils.WorldEditUtils;
import de.steamwar.command.SWCommand;
import de.steamwar.linkage.Linked;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.GZIPOutputStream;
@Linked
public class Cuboid2Command extends SWCommand {
public Cuboid2Command() {
super("cuboid");
}
@Register
public void command(Player player) {
Pair<Location, Location> selection = WorldEditUtils.getSelection(player);
if (selection == null) {
player.sendMessage("§cDu musst erst eine Auswahl treffen!");
return;
}
Location min = selection.getKey();
Location max = selection.getValue();
Vector minVec = new Vector(Math.min(min.getX(), max.getX()), Math.min(min.getY(), max.getY()), Math.min(min.getZ(), max.getZ()));
Vector maxVec = new Vector(Math.max(min.getX(), max.getX()), Math.max(min.getY(), max.getY()), Math.max(min.getZ(), max.getZ()));
CuboidSchematicWriter cuboidSchematicWriter = new CuboidSchematicWriter(minVec, maxVec);
cuboidSchematicWriter.create(player, cuboidSchematic -> {
AtomicInteger counter = new AtomicInteger();
try {
cuboidSchematic.write(new GZIPOutputStream(new OutputStream() {
@Override
public void write(int b) throws IOException {
counter.incrementAndGet();
}
}));
} catch (IOException e) {
throw new RuntimeException(e);
}
player.sendMessage("§aDie Schematic ist " + counter.get() + " Bytes groß!");
System.out.println(cuboidSchematic);
});
}
}

Datei anzeigen

@ -0,0 +1,200 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2023 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.cuboid;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.shared.Pair;
import de.steamwar.bausystem.utils.WorldEditUtils;
import de.steamwar.command.SWCommand;
import lombok.ToString;
import org.bukkit.Axis;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
import java.util.*;
import java.util.function.Supplier;
// @Linked
public class CuboidCommand extends SWCommand {
public CuboidCommand() {
super("cuboid");
}
@Register
public void genericCommand(Player player) {
Pair<Location, Location> selection = WorldEditUtils.getSelection(player);
if (selection == null) {
player.sendMessage("§cDu musst erst eine Auswahl treffen!");
return;
}
Location min = selection.getKey();
Location max = selection.getValue();
Vector minVec = new Vector(Math.min(min.getX(), max.getX()), Math.min(min.getY(), max.getY()), Math.min(min.getZ(), max.getZ()));
Vector maxVec = new Vector(Math.max(min.getX(), max.getX()), Math.max(min.getY(), max.getY()), Math.max(min.getZ(), max.getZ()));
Map<Vector, Material> blocks = new HashMap<>();
for (int x = min.getBlockX(); x <= max.getBlockX(); x++) {
for (int y = min.getBlockY(); y <= max.getBlockY(); y++) {
for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) {
blocks.put(new Vector(x, y, z), player.getWorld().getBlockAt(x, y, z).getType());
}
}
}
List<Vector> left = new ArrayList<>();
left.add(minVec);
long time = System.currentTimeMillis();
List<BlockCuboid> cuboids = new ArrayList<>();
runTickEfficient(left::isEmpty, () -> {
System.out.println(left.size());
Vector current = left.remove(0);
if (current.getX() > maxVec.getX() || current.getY() > maxVec.getY() || current.getZ() > maxVec.getZ()) {
return;
}
BlockCuboid cuboid = new BlockCuboid(current, blocks.get(current));
expand(cuboid, maxVec, blocks);
cuboids.add(cuboid);
left.add(new Vector(cuboid.min.getX(), cuboid.min.getY(), cuboid.max.getZ() + 1));
left.add(new Vector(cuboid.min.getX(), cuboid.max.getY() + 1, cuboid.min.getZ()));
left.add(new Vector(cuboid.max.getX() + 1, cuboid.min.getY(), cuboid.min.getZ()));
}, () -> {
System.out.println((System.currentTimeMillis() - time) + " ms " + cuboids);
});
}
private void runTickEfficient(Supplier<Boolean> finished, Runnable next, Runnable finishedCallback) {
new BukkitRunnable() {
@Override
public void run() {
if (finished.get()) {
cancel();
finishedCallback.run();
return;
}
long time = System.currentTimeMillis();
while (System.currentTimeMillis() - time < 50) {
if (finished.get()) {
cancel();
finishedCallback.run();
return;
}
next.run();
}
}
}.runTaskTimer(BauSystem.getInstance(), 0, 1);
}
@ToString
private static class BlockCuboid {
private final Vector min;
private final Vector max;
private final Material material;
public BlockCuboid(Vector min, Material material) {
this.min = min.clone();
this.max = min.clone();
this.material = material;
}
}
private void expand(BlockCuboid cuboid, Vector max, Map<Vector, Material> blocks) {
List<Axis> axes = new ArrayList<>();
axes.add(Axis.X);
axes.add(Axis.Y);
axes.add(Axis.Z);
do {
Vector sizes = cuboid.max.clone().subtract(cuboid.min).add(new Vector(1, 1, 1));
axes.sort(Comparator.comparing(axis -> {
switch (axis) {
case X:
return sizes.getX();
case Y:
return sizes.getY();
case Z:
return sizes.getZ();
default:
return 0.0;
}
}));
axes.removeIf(axis -> {
Vector direction = new Vector();
switch (axis) {
case X:
direction.setX(1);
break;
case Y:
direction.setY(1);
break;
case Z:
direction.setZ(1);
break;
}
return !expand(cuboid, max, direction, blocks);
});
} while (!axes.isEmpty());
}
private boolean expand(BlockCuboid cuboid, Vector max, Vector direction, Map<Vector, Material> blocks) {
if (direction.getX() == 1) {
if (cuboid.max.getX() + 1 > max.getX()) {
return false;
}
for (double y = cuboid.min.getY(); y <= cuboid.max.getY(); y++) {
for (double z = cuboid.min.getZ(); z <= cuboid.max.getZ(); z++) {
if (blocks.get(new Vector(cuboid.max.getX() + 1, y, z)) != cuboid.material) {
return false;
}
}
}
} else if (direction.getY() == 1) {
if (cuboid.max.getY() + 1 > max.getY()) {
return false;
}
for (double x = cuboid.min.getX(); x <= cuboid.max.getX(); x++) {
for (double z = cuboid.min.getZ(); z <= cuboid.max.getZ(); z++) {
if (blocks.get(new Vector(x, cuboid.max.getY() + 1, z)) != cuboid.material) {
return false;
}
}
}
} else if (direction.getZ() == 1) {
if (cuboid.max.getZ() + 1 > max.getZ()) {
return false;
}
for (double x = cuboid.min.getX(); x <= cuboid.max.getX(); x++) {
for (double y = cuboid.min.getY(); y <= cuboid.max.getY(); y++) {
if (blocks.get(new Vector(x, y, cuboid.max.getZ() + 1)) != cuboid.material) {
return false;
}
}
}
}
cuboid.max.add(direction);
return true;
}
}

Datei anzeigen

@ -0,0 +1,94 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2023 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.cuboid;
import com.sk89q.jnbt.*;
import lombok.Getter;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Getter
public class CuboidSchematic {
private Pos offset;
private Pos size;
private Map<String, List<TypedCuboid>> cuboids = new HashMap<>();
public CuboidSchematic(Pos offset, Pos size) {
this.offset = offset;
this.size = size;
}
public void addCuboid(TypedCuboid cuboid) {
cuboids.computeIfAbsent(cuboid.blockData, s -> new ArrayList<>()).add(cuboid);
}
public void write(OutputStream outputStream) {
Map<String, Tag> schematic = new HashMap<>();
schematic.put("OffsetX", new IntTag(offset.x));
schematic.put("OffsetY", new IntTag(offset.y));
schematic.put("OffsetZ", new IntTag(offset.z));
schematic.put("SizeX", new IntTag(size.x));
schematic.put("SizeY", new IntTag(size.y));
schematic.put("SizeZ", new IntTag(size.z));
Map<String, Tag> cuboidMap = new HashMap<>();
for (Map.Entry<String, List<TypedCuboid>> entry : cuboids.entrySet()) {
byte[] cuboidArray = new byte[entry.getValue().size() * 15];
for (int i = 0; i < entry.getValue().size(); i++) {
TypedCuboid cuboid = entry.getValue().get(i);
List<Byte> bytes = new ArrayList<>();
writeVarInt(cuboid.x, bytes);
writeVarInt(cuboid.y, bytes);
writeVarInt(cuboid.z, bytes);
writeVarInt(cuboid.dx, bytes);
writeVarInt(cuboid.dy, bytes);
writeVarInt(cuboid.dz, bytes);
}
cuboidMap.put(entry.getKey(), new ByteArrayTag(cuboidArray));
}
schematic.put("Cuboids", new CompoundTag(cuboidMap));
CompoundTag compoundTag = new CompoundTag(schematic);
try {
NBTOutputStream nbtOutputStream = new NBTOutputStream(outputStream);
nbtOutputStream.writeTag(compoundTag);
nbtOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void writeVarInt(int value, List<Byte> bytes) {
while (true) {
if (value < 128) {
bytes.add((byte) value);
return;
} else {
bytes.add((byte) (value & 127 | 128));
value >>>= 7;
}
}
}
}

Datei anzeigen

@ -0,0 +1,199 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2023 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.cuboid;
import de.steamwar.bausystem.BauSystem;
import org.bukkit.Axis;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Consumer;
import org.bukkit.util.Vector;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public class CuboidSchematicWriter {
private static final List<Axis[]> AXES = Arrays.asList(
new Axis[]{Axis.X, Axis.Y, Axis.Z},
new Axis[]{Axis.X, Axis.Z, Axis.Y},
new Axis[]{Axis.Y, Axis.X, Axis.Z},
new Axis[]{Axis.Y, Axis.Z, Axis.X},
new Axis[]{Axis.Z, Axis.X, Axis.Y},
new Axis[]{Axis.Z, Axis.Y, Axis.X}
);
private Pos min;
private Pos max;
public CuboidSchematicWriter(Vector min, Vector max) {
this.min = new Pos(min);
this.max = new Pos(max);
}
public void create(Player player, Consumer<CuboidSchematic> consumer) {
// Setup
Pos playerPos = new Pos(player.getLocation());
Pos offset = min.sub(playerPos);
Map<Pos, String> blocks = CuboidSchematicWriter.getBlocks(player, min, max);
Map<String, Set<Pos>> reverseBlocks = reverse(blocks);
// Create CuboidSchematic
CuboidSchematic schematic = new CuboidSchematic(offset, max.sub(min));
// Create Cuboids
long time = System.currentTimeMillis();
List<Pos> left = new ArrayList<>();
left.add(new Pos(0, 0, 0));
runTickEfficient(left::isEmpty, () -> {
Pos current = left.remove(0);
String block = blocks.remove(current);
if (block == null) {
return;
}
TypedCuboid cuboid = null;
for (Axis[] axes : AXES) {
TypedCuboid currentCuboid = new TypedCuboid(current.x, current.y, current.z, block);
expand(currentCuboid, new ArrayList<>(Arrays.asList(axes)), reverseBlocks);
if (cuboid == null || currentCuboid.size() > cuboid.size()) {
cuboid = currentCuboid;
}
}
Set<Pos> cuboidBlocks = reverseBlocks.get(block);
for (int x = cuboid.x; x < cuboid.x + cuboid.dx; x++) {
for (int y = cuboid.y; y < cuboid.y + cuboid.dy; y++) {
for (int z = cuboid.z; z < cuboid.z + cuboid.dz; z++) {
Pos pos = new Pos(x, y, z);
cuboidBlocks.remove(pos);
blocks.remove(pos);
}
}
}
schematic.addCuboid(cuboid);
left.add(new Pos(current.x + cuboid.dx + 1, current.y, current.z));
left.add(new Pos(current.x, current.y + cuboid.dy + 1, current.z));
left.add(new Pos(current.x, current.y, current.z + cuboid.dz + 1));
}, () -> {
long elapsed = System.currentTimeMillis() - time;
long cuboids = schematic.getCuboids().values().stream().mapToLong(List::size).sum();
System.out.println("Finished in " + elapsed + "ms with " + cuboids + " cuboids");
System.out.println("Average: " + (cuboids / elapsed) + " cuboids per ms");
System.out.println("Types: " + schematic.getCuboids().entrySet().stream().map(e -> e.getKey() + ": " + e.getValue().size()).collect(Collectors.joining(", ")));
consumer.accept(schematic);
});
}
private void runTickEfficient(Supplier<Boolean> finished, Runnable next, Runnable finishedCallback) {
new BukkitRunnable() {
@Override
public void run() {
long time = System.currentTimeMillis();
while (System.currentTimeMillis() - time < 50) {
if (finished.get()) {
cancel();
finishedCallback.run();
return;
}
next.run();
}
}
}.runTaskTimer(BauSystem.getInstance(), 0, 1);
}
private static Map<Pos, String> getBlocks(Player player, Pos min, Pos max) {
Map<Pos, String> blocks = new HashMap<>();
for (int x = min.x; x <= max.x; x++) {
for (int y = min.y; y <= max.y; y++) {
for (int z = min.z; z <= max.z; z++) {
blocks.put(new Pos(x - min.x, y - min.y, z - min.z), player.getWorld().getBlockAt(x, y, z).getBlockData().getAsString());
}
}
}
return blocks;
}
private static <K, V> Map<V, Set<K>> reverse(Map<K, V> input) {
Map<V, Set<K>> output = new HashMap<>();
for (Map.Entry<K, V> entry : input.entrySet()) {
output.computeIfAbsent(entry.getValue(), k -> new HashSet<>()).add(entry.getKey());
}
return output;
}
private static void expand(TypedCuboid cuboid, List<Axis> axes, Map<String, Set<Pos>> reverseBlocks) {
while (!axes.isEmpty()) {
axes.removeIf(axis -> {
Vector direction = new Vector();
switch (axis) {
case X:
direction.setX(1);
break;
case Y:
direction.setY(1);
break;
case Z:
direction.setZ(1);
break;
}
return !expand(cuboid, direction, reverseBlocks);
});
}
}
private static boolean expand(TypedCuboid cuboid, Vector direction, Map<String, Set<Pos>> reverseBlocks) {
Set<Pos> blocks = reverseBlocks.get(cuboid.blockData);
if (direction.getX() == 1) {
for (int y = cuboid.y; y < cuboid.y + cuboid.dy; y++) {
for (int z = cuboid.z; z < cuboid.z + cuboid.dz; z++) {
if (!blocks.contains(new Pos(cuboid.x + cuboid.dx + 1, y, z))) {
return false;
}
}
}
} else if (direction.getY() == 1) {
for (int x = cuboid.x; x < cuboid.x + cuboid.dx; x++) {
for (int z = cuboid.z; z < cuboid.z + cuboid.dz; z++) {
if (!blocks.contains(new Pos(x, cuboid.y + cuboid.dy + 1, z))) {
return false;
}
}
}
} else if (direction.getZ() == 1) {
for (int x = cuboid.x; x < cuboid.x + cuboid.dx; x++) {
for (int y = cuboid.y; y < cuboid.y + cuboid.dy; y++) {
if (!blocks.contains(new Pos(x, y, cuboid.z + cuboid.dz + 1))) {
return false;
}
}
}
}
if (direction.getX() == 1) {
cuboid.dx++;
} else if (direction.getY() == 1) {
cuboid.dy++;
} else if (direction.getZ() == 1) {
cuboid.dz++;
}
return true;
}
}

Datei anzeigen

@ -0,0 +1,67 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2023 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.cuboid;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.bukkit.Location;
import org.bukkit.util.Vector;
@ToString
@EqualsAndHashCode
public class Pos {
public final int x;
public final int y;
public final int z;
public Pos(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
public Pos(Location loc) {
this.x = loc.getBlockX();
this.y = loc.getBlockY();
this.z = loc.getBlockZ();
}
public Pos(Vector vec) {
this.x = vec.getBlockX();
this.y = vec.getBlockY();
this.z = vec.getBlockZ();
}
public Pos add(int x, int y, int z) {
return new Pos(this.x + x, this.y + y, this.z + z);
}
public Pos add(Pos pos) {
return new Pos(this.x + pos.x, this.y + pos.y, this.z + pos.z);
}
public Pos sub(int x, int y, int z) {
return new Pos(this.x - x, this.y - y, this.z - z);
}
public Pos sub(Pos pos) {
return new Pos(this.x - pos.x, this.y - pos.y, this.z - pos.z);
}
}

Datei anzeigen

@ -0,0 +1,71 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2023 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.cuboid;
import lombok.ToString;
@ToString
public class TypedCuboid {
public int x;
public int y;
public int z;
public int dx;
public int dy;
public int dz;
public String blockData;
public TypedCuboid(int x, int y, int z, String blockData) {
this.x = x;
this.y = y;
this.z = z;
this.dx = 1;
this.dy = 1;
this.dz = 1;
this.blockData = blockData;
}
public boolean intersects(TypedCuboid cuboid) {
int minx = x - cuboid.dx;
int miny = y - cuboid.dy;
int minz = z - cuboid.dz;
int maxx = minx + dx + cuboid.dx;
int maxy = miny + dy + cuboid.dy;
int maxz = minz + dz + cuboid.dz;
return maxx > cuboid.x && maxy > cuboid.y && maxz > cuboid.z && minx < cuboid.x && miny < cuboid.y && minz < cuboid.z;
}
public boolean intersects(Pos pos) {
int minx = x - dx;
int miny = y - dy;
int minz = z - dz;
int maxx = minx + dx;
int maxy = miny + dy;
int maxz = minz + dz;
return maxx > pos.x && maxy > pos.y && maxz > pos.z && minx < pos.x && miny < pos.y && minz < pos.z;
}
public long size() {
return (long) dx * (long) dy * (long) dz;
}
}