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
43 changes: 43 additions & 0 deletions .github/workflows/modrinth-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Publish

on:
release:
types: [published]

jobs:
publish:
name: Publish
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: 21
distribution: adopt
cache: maven

# This step will take the version tag from the release and replace it in `pom.xml` before building.
#- name: Set version from release tag
# run: mvn -B versions:set -DnewVersion=${{ github.event.release.tag_name }} -DgenerateBackupPoms=false

- name: Build and package with Maven
run: mvn -B clean package -DskipTests -DgenerateBackupPoms=false -Pmaster --file pom.xml

- name: Upload to Modrinth
uses: cloudnode-pro/modrinth-publish@v2
with:
token: ${{ secrets.MODRINTH_TOKEN }}
project: F4I5miJX
name: ${{ github.event.release.name }}
version: ${{ github.event.release.tag_name }}
changelog: ${{ github.event.release.body }}
loaders: |-
paper
spigot
game-versions: |-
1.21.4
1.21.5
files: /home/runner/work/Boxed/Boxed/target/Boxed-${{ github.event.release.tag_name }}.jar
1 change: 1 addition & 0 deletions src/main/java/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/.DS_Store
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,27 @@
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.Stack;

import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.block.BlockState;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.structure.Mirror;
import org.bukkit.block.structure.StructureRotation;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.structure.Structure;
import org.bukkit.util.BlockTransformer;
import org.bukkit.util.Vector;

import com.google.common.base.Enums;

Expand Down Expand Up @@ -52,6 +60,8 @@ public class AdminPlaceStructureCommand extends CompositeCommand {
private Mirror mirror = Mirror.NONE;
private boolean noMobs;

private final Stack<StructureRecord> placedStructures = new Stack<>();

public AdminPlaceStructureCommand(CompositeCommand parent) {
super(parent, "place");
}
Expand All @@ -68,15 +78,20 @@ public void setup() {

@Override
public boolean canExecute(User user, String label, List<String> args) {
if (args.size() == 1 && args.get(0).equalsIgnoreCase("undo")) {
return true; // Allow "undo" command without additional checks
}

// Initialize
sr = StructureRotation.NONE;
mirror = Mirror.NONE;

// Check world
if (!((Boxed)getAddon()).inWorld(getWorld())) {
if (!((Boxed) getAddon()).inWorld(getWorld())) {
user.sendMessage("boxed.commands.boxadmin.place.wrong-world");
return false;
}

/*
* Acceptable syntax with number of args:
* 1. place <structure>
Expand All @@ -85,49 +100,56 @@ public boolean canExecute(User user, String label, List<String> args) {
* 6. place <structure> ~ ~ ~ ROTATION MIRROR
* 7. place <structure> ~ ~ ~ ROTATION MIRROR NO_MOBS
*/
// Format is place <structure> ~ ~ ~ or coords
if (args.isEmpty() || args.size() == 2 || args.size() == 3 || args.size() > 6) {
this.showHelp(this, user);
return false;
}

// First arg must always be the structure name
List<String> options = Bukkit.getStructureManager().getStructures().keySet().stream().map(NamespacedKey::getKey).toList();
if (!options.contains(args.get(0).toLowerCase(Locale.ENGLISH))) {
user.sendMessage("boxed.commands.boxadmin.place.unknown-structure");
return false;
}

// If that is all we have, we're done
if (args.size() == 1) {
return true;
}

// Next come the coordinates - there must be at least 3 of them
if ((!args.get(1).equals("~") && !Util.isInteger(args.get(1), true))
|| (!args.get(2).equals("~") && !Util.isInteger(args.get(2), true))
|| (!args.get(3).equals("~") && !Util.isInteger(args.get(3), true))) {
user.sendMessage("boxed.commands.boxadmin.place.use-integers");
return false;
}

// If that is all we have, we're done
if (args.size() == 4) {
return true;
}
// But there is more!

// Handle rotation
sr = Enums.getIfPresent(StructureRotation.class, args.get(4).toUpperCase(Locale.ENGLISH)).orNull();
if (sr == null) {
user.sendMessage("boxed.commands.boxadmin.place.unknown-rotation");
Arrays.stream(StructureRotation.values()).map(StructureRotation::name).forEach(user::sendRawMessage);
return false;
}

if (args.size() == 5) {
return true;
}
// But there is more!

// Handle mirror
mirror = Enums.getIfPresent(Mirror.class, args.get(5).toUpperCase(Locale.ENGLISH)).orNull();
if (mirror == null) {
user.sendMessage("boxed.commands.boxadmin.place.unknown-mirror");
Arrays.stream(Mirror.values()).map(Mirror::name).forEach(user::sendRawMessage);
return false;
}

if (args.size() == 7) {
if (args.get(6).toUpperCase(Locale.ENGLISH).equals("NO_MOBS")) {
noMobs = true;
Expand All @@ -136,20 +158,40 @@ public boolean canExecute(User user, String label, List<String> args) {
return false;
}
}

// Syntax is okay
return true;
}

@Override
public boolean execute(User user, String label, List<String> args) {
if (args.size() == 1 && args.get(0).equalsIgnoreCase("undo")) {
return undoLastPlacement(user);
}

NamespacedKey tag = NamespacedKey.fromString(args.get(0).toLowerCase(Locale.ENGLISH));
Structure s = Bukkit.getStructureManager().loadStructure(tag);
int x = args.size() == 1 || args.get(1).equals("~") ? user.getLocation().getBlockX() : Integer.parseInt(args.get(1).trim());
int y = args.size() == 1 || args.get(2).equals("~") ? user.getLocation().getBlockY() : Integer.parseInt(args.get(2).trim());
int z = args.size() == 1 || args.get(3).equals("~") ? user.getLocation().getBlockZ() : Integer.parseInt(args.get(3).trim());
int x = args.size() == 1 || args.get(1).equals("~") ? user.getLocation().getBlockX()
: Integer.parseInt(args.get(1).trim());
int y = args.size() == 1 || args.get(2).equals("~") ? user.getLocation().getBlockY()
: Integer.parseInt(args.get(2).trim());
int z = args.size() == 1 || args.get(3).equals("~") ? user.getLocation().getBlockZ()
: Integer.parseInt(args.get(3).trim());
Location spot = new Location(user.getWorld(), x, y, z);
s.place(spot, true, sr, mirror, PALETTE, INTEGRITY, new Random());
NewAreaListener.removeJigsaw(new StructureRecord(tag.getKey(), tag.getKey(), spot, sr, mirror, noMobs));
Map<Vector, BlockData> removedBlocks = new HashMap<>();
BlockTransformer store = (region, xx, yy, zz, current, state) -> {
// Store the state
removedBlocks.put(new Vector(xx, yy, zz), region.getBlockData(xx, yy, zz));
return state.getOriginal();
};

s.place(spot, true, sr, mirror, PALETTE, INTEGRITY, new Random(), Collections.singleton(store), // Transformer to store blocks
Collections.emptyList() // No entity transformers
);
NewAreaListener
.removeJigsaw(new StructureRecord(tag.getKey(), tag.getKey(), spot, sr, mirror, noMobs, removedBlocks));
placedStructures.push(new StructureRecord(tag.getKey(), tag.getKey(), spot, sr, mirror, noMobs, removedBlocks)); // Track the placement

boolean result = saveStructure(spot, tag, user, sr, mirror);
if (result) {
user.sendMessage("boxed.commands.boxadmin.place.saved");
Expand Down Expand Up @@ -184,9 +226,74 @@ private boolean saveStructure(Location spot, NamespacedKey tag, User user, Struc

}

private boolean undoLastPlacement(User user) {
if (placedStructures.isEmpty()) {
user.sendMessage("boxed.commands.boxadmin.place.no-undo");
return false;
}

StructureRecord lastRecord = placedStructures.pop();
NamespacedKey tag = NamespacedKey.fromString(lastRecord.name());
Structure s = Bukkit.getStructureManager().loadStructure(tag);

if (s == null) {
user.sendMessage("boxed.commands.boxadmin.place.undo-failed");
return false;
}

BlockTransformer erase = (region, x, y, z, current, state) -> {
Vector v = new Vector(x, y, z);
if (lastRecord.removedBlocks().containsKey(v)) {
return lastRecord.removedBlocks().get(v).createBlockState();
}
BlockState airState = Material.AIR.createBlockData().createBlockState();
return airState;
};

s.place(
lastRecord.location(),
false, // Don't respawn entities
lastRecord.rot(), lastRecord.mirror(),
PALETTE,
1.0f, // Integrity = 1 means "place everything"
new Random(),
Collections.singleton(erase), // Transformer to erase blocks
Collections.emptyList() // No entity transformers
);
lastRecord.removedBlocks().clear();
removeStructure(lastRecord.location(), tag, user); // Remove from config

user.sendMessage("boxed.commands.boxadmin.place.undo-success");
return true;
}

private boolean removeStructure(Location spot, NamespacedKey tag, User user) {
return getAddon().getIslands().getIslandAt(spot).map(i -> {
int xx = spot.getBlockX() - i.getCenter().getBlockX();
int zz = spot.getBlockZ() - i.getCenter().getBlockZ();
File structures = new File(getAddon().getDataFolder(), STRUCTURE_FILE);
YamlConfiguration config = new YamlConfiguration();
try {
config.load(structures);
String key = spot.getWorld().getEnvironment().name().toLowerCase(Locale.ENGLISH) + "." + xx + "," + spot.getBlockY() + "," + zz;
if (config.contains(key)) {
config.set(key, null); // Remove the entry
config.save(structures);
return true;
}
} catch (IOException | InvalidConfigurationException e) {
e.printStackTrace();
}
return false;
}).orElse(false);
}

@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args)
{
if (args.size() == 1) {
return Optional.of(Util.tabLimit(Arrays.asList("undo"), args.get(0)));
}
String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : "";
if (args.size() == 2) {
return Optional.of(Util.tabLimit(Bukkit.getStructureManager().getStructures().keySet().stream().map(NamespacedKey::getKey).toList(), lastArg));
Expand All @@ -205,4 +312,7 @@ public Optional<List<String>> tabComplete(User user, String alias, List<String>
}
return Optional.of(Collections.emptyList());
}



}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
Expand Down Expand Up @@ -388,9 +389,11 @@ private void place(ConfigurationSection section, Location center, Environment en
Location location = new Location(world, x, y, z);
// Structure will be placed at location
readyToBuild.computeIfAbsent(new Pair<>(x >> 4, z >> 4), k -> new ArrayList<>())
.add(new StructureRecord(name, "minecraft:" + name, location, rotation, mirror, noMobs));
.add(new StructureRecord(name, "minecraft:" + name, location, rotation, mirror, noMobs,
Collections.emptyMap()));
this.itemsToBuild
.add(new StructureRecord(name, "minecraft:" + name, location, rotation, mirror, noMobs));
.add(new StructureRecord(name, "minecraft:" + name, location, rotation, mirror, noMobs,
Collections.emptyMap()));
} else {
addon.logError("Structure file syntax error: " + vector + ": " + Arrays.toString(coords));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
import java.util.Map;

import org.bukkit.Location;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.structure.Mirror;
import org.bukkit.block.structure.StructureRotation;
import org.bukkit.util.Vector;

import com.google.gson.annotations.Expose;

Expand Down Expand Up @@ -36,7 +38,8 @@ public class ToBePlacedStructures implements DataObject {
* @param noMobs - if false, mobs not pasted
*/
public record StructureRecord(@Expose String name, @Expose String structure, @Expose Location location,
@Expose StructureRotation rot, @Expose Mirror mirror, @Expose Boolean noMobs) {
@Expose StructureRotation rot, @Expose Mirror mirror, @Expose Boolean noMobs,
Map<Vector, BlockData> removedBlocks) {
}

@Expose
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/locales/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,7 @@ boxed:
saved: '&a Placed and saved to structures.yml'
failed: '&c Could not be saved to structures.yml. Check console for error'
unknown: '&c Unknown parameter: [label]'
undo-success: '&a Undone. Some changes cannot be undone.''
island:
go:
parameters: '[home number]'
Expand Down