Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 58b7d44d8f |
@@ -3,7 +3,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "party.cybsec"
|
group = "party.cybsec"
|
||||||
version = "1.3.0"
|
version = "1.3.1"
|
||||||
description = "deterministic item-for-item chest barter"
|
description = "deterministic item-for-item chest barter"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
|||||||
57
oyeOwner/INTEGRATION_GUIDE.md
Normal file
57
oyeOwner/INTEGRATION_GUIDE.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# oyeOwner Integration Guide for AI Assistants
|
||||||
|
|
||||||
|
This document provides concise instructions for integrating the **oyeOwner** API into other Bukkit/Spigot plugins.
|
||||||
|
|
||||||
|
## 1. Dependency Configuration (Maven)
|
||||||
|
Add the `oyeOwner` project as a dependency in your `pom.xml`.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<dependency>
|
||||||
|
<groupId>party.cybsec</groupId>
|
||||||
|
<artifactId>oyeOwner</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Plugin Configuration (plugin.yml)
|
||||||
|
Add `oyeOwner` as a dependency to ensure it loads before your plugin.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
depend: [oyeOwner]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Java API Usage
|
||||||
|
|
||||||
|
### Accessing the API
|
||||||
|
The API is accessible via a static getter in the main class: `party.cybsec.OyeOwner.getAPI()`.
|
||||||
|
|
||||||
|
### Sync Lookup (Blocking)
|
||||||
|
Use this if you are already in an asynchronous task or if a tiny delay is acceptable.
|
||||||
|
```java
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import party.cybsec.OyeOwner;
|
||||||
|
|
||||||
|
// Returns String username or null
|
||||||
|
String owner = OyeOwner.getAPI().getBlockOwner(block);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Async Lookup (Non-blocking)
|
||||||
|
Recommended for use on the main thread to avoid lag.
|
||||||
|
```java
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import party.cybsec.OyeOwner;
|
||||||
|
|
||||||
|
OyeOwner.getAPI().getBlockOwnerAsync(block).thenAccept(owner -> {
|
||||||
|
if (owner != null) {
|
||||||
|
// Player name found: owner
|
||||||
|
} else {
|
||||||
|
// No ownership data found
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Summary of Capabilities
|
||||||
|
- **Lookback Period**: 60 days.
|
||||||
|
- **Action Tracked**: Block Placement (Action ID 1).
|
||||||
|
- **Core Engine**: Powered by CoreProtect with a reflection-based safe hook.
|
||||||
73
oyeOwner/pom.xml
Normal file
73
oyeOwner/pom.xml
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>party.cybsec</groupId>
|
||||||
|
<artifactId>oyeOwner</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>oyeOwner</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>21</java.version>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>spigotmc-repo</id>
|
||||||
|
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>sonatype</id>
|
||||||
|
<url>https://oss.sonatype.org/content/groups/public/</url>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>playpro</id>
|
||||||
|
<url>https://maven.playpro.com</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.spigotmc</groupId>
|
||||||
|
<artifactId>spigot-api</artifactId>
|
||||||
|
<version>1.21.1-R0.1-SNAPSHOT</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.coreprotect</groupId>
|
||||||
|
<artifactId>coreprotect</artifactId>
|
||||||
|
<version>23.1</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.13.0</version>
|
||||||
|
<configuration>
|
||||||
|
<source>${java.version}</source>
|
||||||
|
<target>${java.version}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>3.4.2</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
56
oyeOwner/src/main/java/party/cybsec/CoreProtectHook.java
Normal file
56
oyeOwner/src/main/java/party/cybsec/CoreProtectHook.java
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package party.cybsec;
|
||||||
|
|
||||||
|
import net.coreprotect.CoreProtectAPI;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
public class CoreProtectHook {
|
||||||
|
|
||||||
|
private final OyeOwner plugin;
|
||||||
|
|
||||||
|
public CoreProtectHook(OyeOwner plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CoreProtectAPI getCoreProtect() {
|
||||||
|
Plugin cpPlugin = Bukkit.getPluginManager().getPlugin("CoreProtect");
|
||||||
|
|
||||||
|
// Check that CoreProtect is loaded and enabled
|
||||||
|
if (cpPlugin == null || !cpPlugin.isEnabled()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use reflection to get the API to avoid NoClassDefFoundError at load time
|
||||||
|
try {
|
||||||
|
// Verify it's the right class name
|
||||||
|
if (!cpPlugin.getClass().getName().equals("net.coreprotect.CoreProtect")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Method getAPIMethod = cpPlugin.getClass().getMethod("getAPI");
|
||||||
|
Object apiObject = getAPIMethod.invoke(cpPlugin);
|
||||||
|
|
||||||
|
if (apiObject instanceof CoreProtectAPI) {
|
||||||
|
CoreProtectAPI api = (CoreProtectAPI) apiObject;
|
||||||
|
|
||||||
|
// Check that the API is enabled
|
||||||
|
if (!api.isEnabled()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that a compatible version of the API is loaded
|
||||||
|
if (api.APIVersion() < 11) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return api;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().warning("Failed to hook into CoreProtect API via reflection: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
50
oyeOwner/src/main/java/party/cybsec/OyeOwner.java
Normal file
50
oyeOwner/src/main/java/party/cybsec/OyeOwner.java
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package party.cybsec;
|
||||||
|
|
||||||
|
import party.cybsec.command.WhoCommand;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public class OyeOwner extends JavaPlugin {
|
||||||
|
|
||||||
|
private static OyeOwner instance;
|
||||||
|
private CoreProtectHook coreProtectHook;
|
||||||
|
private OyeOwnerAPI api;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
instance = this;
|
||||||
|
Logger logger = getLogger();
|
||||||
|
logger.info("oyeOwner is enabling...");
|
||||||
|
|
||||||
|
this.coreProtectHook = new CoreProtectHook(this);
|
||||||
|
this.api = new OyeOwnerAPI(this);
|
||||||
|
|
||||||
|
if (coreProtectHook.getCoreProtect() == null) {
|
||||||
|
logger.severe("CoreProtect not found or incompatible! Disabling oyeOwner.");
|
||||||
|
getServer().getPluginManager().disablePlugin(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCommand("who").setExecutor(new WhoCommand(this));
|
||||||
|
|
||||||
|
logger.info("oyeOwner enabled successfully.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
getLogger().info("oyeOwner disabled.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public CoreProtectHook getCoreProtectHook() {
|
||||||
|
return coreProtectHook;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OyeOwnerAPI getOyeAPI() {
|
||||||
|
return api;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OyeOwnerAPI getAPI() {
|
||||||
|
return instance.api;
|
||||||
|
}
|
||||||
|
}
|
||||||
59
oyeOwner/src/main/java/party/cybsec/OyeOwnerAPI.java
Normal file
59
oyeOwner/src/main/java/party/cybsec/OyeOwnerAPI.java
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package party.cybsec;
|
||||||
|
|
||||||
|
import net.coreprotect.CoreProtectAPI;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
public class OyeOwnerAPI {
|
||||||
|
|
||||||
|
private final OyeOwner plugin;
|
||||||
|
|
||||||
|
public OyeOwnerAPI(OyeOwner plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the owner (player who placed) of a block.
|
||||||
|
* Searches back 60 days.
|
||||||
|
*
|
||||||
|
* @param block The block to check.
|
||||||
|
* @return The username of the player who placed the block, or null if not
|
||||||
|
* found/error.
|
||||||
|
*/
|
||||||
|
public String getBlockOwner(Block block) {
|
||||||
|
CoreProtectAPI api = plugin.getCoreProtectHook().getCoreProtect();
|
||||||
|
if (api == null || block == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 60 days in seconds
|
||||||
|
int time = 60 * 24 * 60 * 60;
|
||||||
|
|
||||||
|
List<String[]> lookup = api.blockLookup(block, time);
|
||||||
|
if (lookup == null || lookup.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String[] result : lookup) {
|
||||||
|
CoreProtectAPI.ParseResult parsed = api.parseResult(result);
|
||||||
|
// Action ID 1 is "Placement"
|
||||||
|
if (parsed.getActionId() == 1) {
|
||||||
|
return parsed.getPlayer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the owner of a block asynchronously.
|
||||||
|
*
|
||||||
|
* @param block The block to check.
|
||||||
|
* @return A CompletableFuture containing the username or null.
|
||||||
|
*/
|
||||||
|
public CompletableFuture<String> getBlockOwnerAsync(Block block) {
|
||||||
|
return CompletableFuture.supplyAsync(() -> getBlockOwner(block));
|
||||||
|
}
|
||||||
|
}
|
||||||
47
oyeOwner/src/main/java/party/cybsec/command/WhoCommand.java
Normal file
47
oyeOwner/src/main/java/party/cybsec/command/WhoCommand.java
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package party.cybsec.command;
|
||||||
|
|
||||||
|
import party.cybsec.OyeOwner;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class WhoCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final OyeOwner plugin;
|
||||||
|
|
||||||
|
public WhoCommand(OyeOwner plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
if (!(sender instanceof Player)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "Only players can use this command.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player player = (Player) sender;
|
||||||
|
Block targetBlock = player.getTargetBlockExact(5);
|
||||||
|
|
||||||
|
if (targetBlock == null || targetBlock.getType() == Material.AIR) {
|
||||||
|
player.sendMessage(ChatColor.RED + "You must be looking at a block.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String owner = plugin.getOyeAPI().getBlockOwner(targetBlock);
|
||||||
|
|
||||||
|
if (owner != null) {
|
||||||
|
player.sendMessage(ChatColor.DARK_AQUA + "--- Block Owner Info ---");
|
||||||
|
player.sendMessage(ChatColor.GOLD + "user: " + ChatColor.WHITE + owner);
|
||||||
|
player.sendMessage(ChatColor.DARK_AQUA + "------------------------");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.sendMessage(ChatColor.GRAY + "No placement records found for this block.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
14
oyeOwner/src/main/resources/plugin.yml
Normal file
14
oyeOwner/src/main/resources/plugin.yml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
name: oyeOwner
|
||||||
|
version: '1.0'
|
||||||
|
main: party.cybsec.OyeOwner
|
||||||
|
api-version: '1.21'
|
||||||
|
depend: [CoreProtect]
|
||||||
|
commands:
|
||||||
|
who:
|
||||||
|
description: check who placed the block you are looking at
|
||||||
|
permission: oyeowner.use
|
||||||
|
usage: /<command>
|
||||||
|
permissions:
|
||||||
|
oyeowner.use:
|
||||||
|
description: Allows player to use the /who command
|
||||||
|
default: op
|
||||||
BIN
oyeOwner/target/classes/party/cybsec/CoreProtectHook.class
Normal file
BIN
oyeOwner/target/classes/party/cybsec/CoreProtectHook.class
Normal file
Binary file not shown.
BIN
oyeOwner/target/classes/party/cybsec/OyeOwner.class
Normal file
BIN
oyeOwner/target/classes/party/cybsec/OyeOwner.class
Normal file
Binary file not shown.
BIN
oyeOwner/target/classes/party/cybsec/OyeOwnerAPI.class
Normal file
BIN
oyeOwner/target/classes/party/cybsec/OyeOwnerAPI.class
Normal file
Binary file not shown.
BIN
oyeOwner/target/classes/party/cybsec/command/WhoCommand.class
Normal file
BIN
oyeOwner/target/classes/party/cybsec/command/WhoCommand.class
Normal file
Binary file not shown.
14
oyeOwner/target/classes/plugin.yml
Normal file
14
oyeOwner/target/classes/plugin.yml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
name: oyeOwner
|
||||||
|
version: '1.0'
|
||||||
|
main: party.cybsec.OyeOwner
|
||||||
|
api-version: '1.21'
|
||||||
|
depend: [CoreProtect]
|
||||||
|
commands:
|
||||||
|
who:
|
||||||
|
description: check who placed the block you are looking at
|
||||||
|
permission: oyeowner.use
|
||||||
|
usage: /<command>
|
||||||
|
permissions:
|
||||||
|
oyeowner.use:
|
||||||
|
description: Allows player to use the /who command
|
||||||
|
default: op
|
||||||
3
oyeOwner/target/maven-archiver/pom.properties
Normal file
3
oyeOwner/target/maven-archiver/pom.properties
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
artifactId=oyeOwner
|
||||||
|
groupId=party.cybsec
|
||||||
|
version=1.0-SNAPSHOT
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
party/cybsec/command/WhoCommand.class
|
||||||
|
party/cybsec/OyeOwner.class
|
||||||
|
party/cybsec/CoreProtectHook.class
|
||||||
|
party/cybsec/OyeOwnerAPI.class
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/Users/jacktotonchi/oyeOwner/src/main/java/party/cybsec/CoreProtectHook.java
|
||||||
|
/Users/jacktotonchi/oyeOwner/src/main/java/party/cybsec/OyeOwner.java
|
||||||
|
/Users/jacktotonchi/oyeOwner/src/main/java/party/cybsec/OyeOwnerAPI.java
|
||||||
|
/Users/jacktotonchi/oyeOwner/src/main/java/party/cybsec/command/WhoCommand.java
|
||||||
@@ -48,13 +48,15 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
|||||||
case "notify" -> handleNotifyToggle(sender);
|
case "notify" -> handleNotifyToggle(sender);
|
||||||
case "info" -> handleInfo(sender);
|
case "info" -> handleInfo(sender);
|
||||||
case "help" -> handleHelp(sender);
|
case "help" -> handleHelp(sender);
|
||||||
case "setup" -> handleSetup(sender);
|
case "setup" -> handleSetup(sender, args);
|
||||||
case "config" -> handleConfig(sender);
|
case "config" -> handleConfig(sender, args);
|
||||||
case "reload" -> handleReload(sender);
|
case "reload" -> handleReload(sender);
|
||||||
case "inspect", "i" -> handleInspect(sender);
|
case "inspect", "i" -> handleInspect(sender);
|
||||||
case "spoof", "s" -> handleSpoof(sender);
|
case "spoof", "s" -> handleSpoof(sender);
|
||||||
case "unregister", "delete", "remove" -> handleUnregister(sender, args);
|
case "unregister", "delete", "remove" -> handleUnregister(sender, args);
|
||||||
|
case "tpshop" -> handleTpShop(sender, args);
|
||||||
case "_activate" -> handleActivate(sender, args);
|
case "_activate" -> handleActivate(sender, args);
|
||||||
|
case "toggleplacement", "toggle-placement" -> handleTogglePlacement(sender);
|
||||||
default -> handleHelp(sender);
|
default -> handleHelp(sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,6 +95,10 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
|||||||
.append(Component.text(" - disable a shop", NamedTextColor.GRAY)));
|
.append(Component.text(" - disable a shop", NamedTextColor.GRAY)));
|
||||||
sender.sendMessage(Component.text("/oyeshops unregister <id>", NamedTextColor.YELLOW)
|
sender.sendMessage(Component.text("/oyeshops unregister <id>", NamedTextColor.YELLOW)
|
||||||
.append(Component.text(" - delete a shop", NamedTextColor.GRAY)));
|
.append(Component.text(" - delete a shop", NamedTextColor.GRAY)));
|
||||||
|
sender.sendMessage(Component.text("/oyeshops tpshop <owner> <shop>", NamedTextColor.YELLOW)
|
||||||
|
.append(Component.text(" - teleport to a shop", NamedTextColor.GRAY)));
|
||||||
|
sender.sendMessage(Component.text("/oyeshops toggle-placement", NamedTextColor.YELLOW)
|
||||||
|
.append(Component.text(" - toggle container placement requirement", NamedTextColor.GRAY)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,10 +228,13 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
|||||||
private void handleActivate(CommandSender sender, String[] args) {
|
private void handleActivate(CommandSender sender, String[] args) {
|
||||||
if (!(sender instanceof Player player))
|
if (!(sender instanceof Player player))
|
||||||
return;
|
return;
|
||||||
if (args.length < 2)
|
if (args.length < 3) {
|
||||||
|
player.sendMessage(Component.text("usage: /oyes _activate <action> <name>", NamedTextColor.RED));
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
String action = args[1].toLowerCase();
|
String action = args[1].toLowerCase();
|
||||||
|
String name = args[2];
|
||||||
PendingActivation activation = plugin.getActivationManager().getAndRemove(player.getUniqueId());
|
PendingActivation activation = plugin.getActivationManager().getAndRemove(player.getUniqueId());
|
||||||
|
|
||||||
if (activation == null) {
|
if (activation == null) {
|
||||||
@@ -235,10 +244,10 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
|||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "accept" -> {
|
case "accept" -> {
|
||||||
finalizeShop(player, activation);
|
finalizeShop(player, activation, name);
|
||||||
}
|
}
|
||||||
case "invert" -> {
|
case "invert" -> {
|
||||||
finalizeShop(player, activation.invert());
|
finalizeShop(player, activation.invert(), name);
|
||||||
}
|
}
|
||||||
case "cancel" -> {
|
case "cancel" -> {
|
||||||
player.sendMessage(
|
player.sendMessage(
|
||||||
@@ -247,8 +256,8 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void finalizeShop(Player player, PendingActivation activation) {
|
private void finalizeShop(Player player, PendingActivation activation, String shopName) {
|
||||||
plugin.getShopActivationListener().finalizeShop(player, activation);
|
plugin.getShopActivationListener().finalizeShop(player, activation, shopName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleEnable(CommandSender sender, String[] args) {
|
private void handleEnable(CommandSender sender, String[] args) {
|
||||||
@@ -275,7 +284,7 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sender.sendMessage(Component.text("usage: /oyeshops enable <id>", NamedTextColor.RED));
|
sender.sendMessage(Component.text("usage: /oyeshops enable <id/name>", NamedTextColor.RED));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -285,18 +294,25 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
String target = args[1];
|
||||||
int shopId = Integer.parseInt(args[1]);
|
Shop shop = plugin.getShopRegistry().getShopByName(target);
|
||||||
Shop shop = plugin.getShopRegistry().getShopById(shopId);
|
|
||||||
if (shop == null) {
|
if (shop == null) {
|
||||||
sender.sendMessage(Component.text("shop not found: " + shopId, NamedTextColor.RED));
|
try {
|
||||||
|
int id = Integer.parseInt(target);
|
||||||
|
shop = plugin.getShopRegistry().getShopById(id);
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shop == null) {
|
||||||
|
sender.sendMessage(Component.text("shop not found: " + target, NamedTextColor.RED));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
shop.setEnabled(true);
|
shop.setEnabled(true);
|
||||||
plugin.getShopRepository().setEnabled(shopId, true);
|
plugin.getShopRepository().setEnabled(shop.getId(), true);
|
||||||
sender.sendMessage(Component.text("shop #" + shopId + " enabled", NamedTextColor.GREEN));
|
sender.sendMessage(Component.text("shop '" + shop.getName() + "' enabled", NamedTextColor.GREEN));
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
sender.sendMessage(Component.text("invalid shop id", NamedTextColor.RED));
|
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
sender.sendMessage(Component.text("database error", NamedTextColor.RED));
|
sender.sendMessage(Component.text("database error", NamedTextColor.RED));
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -315,7 +331,7 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sender.sendMessage(Component.text("usage: /oyeshops disable <id>", NamedTextColor.RED));
|
sender.sendMessage(Component.text("usage: /oyeshops disable <id/name>", NamedTextColor.RED));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -325,18 +341,25 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
String target = args[1];
|
||||||
int shopId = Integer.parseInt(args[1]);
|
Shop shop = plugin.getShopRegistry().getShopByName(target);
|
||||||
Shop shop = plugin.getShopRegistry().getShopById(shopId);
|
|
||||||
if (shop == null) {
|
if (shop == null) {
|
||||||
sender.sendMessage(Component.text("shop not found: " + shopId, NamedTextColor.RED));
|
try {
|
||||||
|
int id = Integer.parseInt(target);
|
||||||
|
shop = plugin.getShopRegistry().getShopById(id);
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shop == null) {
|
||||||
|
sender.sendMessage(Component.text("shop not found: " + target, NamedTextColor.RED));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
shop.setEnabled(false);
|
shop.setEnabled(false);
|
||||||
plugin.getShopRepository().setEnabled(shopId, false);
|
plugin.getShopRepository().setEnabled(shop.getId(), false);
|
||||||
sender.sendMessage(Component.text("shop #" + shopId + " disabled", NamedTextColor.YELLOW));
|
sender.sendMessage(Component.text("shop '" + shop.getName() + "' disabled", NamedTextColor.YELLOW));
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
sender.sendMessage(Component.text("invalid shop id", NamedTextColor.RED));
|
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
sender.sendMessage(Component.text("database error", NamedTextColor.RED));
|
sender.sendMessage(Component.text("database error", NamedTextColor.RED));
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -350,22 +373,29 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (args.length < 2) {
|
if (args.length < 2) {
|
||||||
sender.sendMessage(Component.text("usage: /oyeshops unregister <id>", NamedTextColor.RED));
|
sender.sendMessage(Component.text("usage: /oyeshops unregister <id/name>", NamedTextColor.RED));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String target = args[1];
|
||||||
|
Shop shop = plugin.getShopRegistry().getShopByName(target);
|
||||||
|
if (shop == null) {
|
||||||
|
try {
|
||||||
|
int id = Integer.parseInt(target);
|
||||||
|
shop = plugin.getShopRegistry().getShopById(id);
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shop == null) {
|
||||||
|
sender.sendMessage(Component.text("shop not found: " + target, NamedTextColor.RED));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
int shopId = Integer.parseInt(args[1]);
|
|
||||||
Shop shop = plugin.getShopRegistry().getShopById(shopId);
|
|
||||||
if (shop == null) {
|
|
||||||
sender.sendMessage(Component.text("shop not found: " + shopId, NamedTextColor.RED));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
plugin.getShopRegistry().unregister(shop);
|
plugin.getShopRegistry().unregister(shop);
|
||||||
plugin.getShopRepository().deleteShop(shopId);
|
plugin.getShopRepository().deleteShop(shop.getId());
|
||||||
sender.sendMessage(Component.text("shop #" + shopId + " deleted", NamedTextColor.RED));
|
sender.sendMessage(Component.text("shop '" + shop.getName() + "' deleted", NamedTextColor.RED));
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
sender.sendMessage(Component.text("invalid shop id", NamedTextColor.RED));
|
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
sender.sendMessage(Component.text("database error", NamedTextColor.RED));
|
sender.sendMessage(Component.text("database error", NamedTextColor.RED));
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -379,7 +409,8 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
|||||||
List<String> subCommands = new ArrayList<>(
|
List<String> subCommands = new ArrayList<>(
|
||||||
List.of("help", "setup", "config", "on", "off", "toggle", "notify", "info", "enable", "disable"));
|
List.of("help", "setup", "config", "on", "off", "toggle", "notify", "info", "enable", "disable"));
|
||||||
if (PermissionManager.isAdmin(sender)) {
|
if (PermissionManager.isAdmin(sender)) {
|
||||||
subCommands.addAll(List.of("reload", "inspect", "spoof", "unregister"));
|
subCommands.addAll(List.of("reload", "inspect", "spoof", "unregister", "tpshop", "toggle-placement",
|
||||||
|
"toggleplacement"));
|
||||||
}
|
}
|
||||||
String partial = args[0].toLowerCase();
|
String partial = args[0].toLowerCase();
|
||||||
for (String sub : subCommands) {
|
for (String sub : subCommands) {
|
||||||
@@ -388,20 +419,61 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
|||||||
}
|
}
|
||||||
} else if (args.length == 2) {
|
} else if (args.length == 2) {
|
||||||
String subCommand = args[0].toLowerCase();
|
String subCommand = args[0].toLowerCase();
|
||||||
if (PermissionManager.isAdmin(sender) && (subCommand.equals("enable") || subCommand.equals("disable")
|
if (subCommand.equals("config") && sender instanceof Player player) {
|
||||||
|| subCommand.equals("unregister"))) {
|
String partial = args[1].toLowerCase();
|
||||||
String partial = args[1];
|
|
||||||
for (Shop shop : plugin.getShopRegistry().getAllShops()) {
|
for (Shop shop : plugin.getShopRegistry().getAllShops()) {
|
||||||
|
if (shop.getOwner().equals(player.getUniqueId())
|
||||||
|
|| shop.getContributors().contains(player.getUniqueId())) {
|
||||||
|
if (shop.getName().toLowerCase().startsWith(partial)) {
|
||||||
|
completions.add(shop.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (PermissionManager.isAdmin(sender) && (subCommand.equals("enable") || subCommand.equals("disable")
|
||||||
|
|| subCommand.equals("unregister"))) {
|
||||||
|
String partial = args[1].toLowerCase();
|
||||||
|
for (Shop shop : plugin.getShopRegistry().getAllShops()) {
|
||||||
|
if (shop.getName().toLowerCase().startsWith(partial)) {
|
||||||
|
completions.add(shop.getName());
|
||||||
|
}
|
||||||
String id = String.valueOf(shop.getId());
|
String id = String.valueOf(shop.getId());
|
||||||
if (id.startsWith(partial))
|
if (id.startsWith(partial))
|
||||||
completions.add(id);
|
completions.add(id);
|
||||||
}
|
}
|
||||||
|
} else if (subCommand.equals("tpshop") && PermissionManager.isAdmin(sender)) {
|
||||||
|
String partial = args[1].toLowerCase();
|
||||||
|
java.util.Set<String> owners = new java.util.HashSet<>();
|
||||||
|
for (Shop shop : plugin.getShopRegistry().getAllShops()) {
|
||||||
|
org.bukkit.OfflinePlayer owner = org.bukkit.Bukkit.getOfflinePlayer(shop.getOwner());
|
||||||
|
if (owner.getName() != null) {
|
||||||
|
owners.add(owner.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (String name : owners) {
|
||||||
|
if (name.toLowerCase().startsWith(partial)) {
|
||||||
|
completions.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (args.length == 3) {
|
||||||
|
String subCommand = args[0].toLowerCase();
|
||||||
|
if (subCommand.equals("tpshop") && PermissionManager.isAdmin(sender)) {
|
||||||
|
String ownerName = args[1];
|
||||||
|
String partial = args[2].toLowerCase();
|
||||||
|
for (Shop shop : plugin.getShopRegistry().getAllShops()) {
|
||||||
|
org.bukkit.OfflinePlayer owner = org.bukkit.Bukkit.getOfflinePlayer(shop.getOwner());
|
||||||
|
if (ownerName.equalsIgnoreCase(owner.getName())) {
|
||||||
|
if (shop.getName().toLowerCase().startsWith(partial)) {
|
||||||
|
completions.add(shop.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return completions;
|
return completions;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleSetup(CommandSender sender) {
|
private void handleSetup(CommandSender sender, String[] args) {
|
||||||
if (!(sender instanceof Player player)) {
|
if (!(sender instanceof Player player)) {
|
||||||
sender.sendMessage(Component.text("players only", NamedTextColor.RED));
|
sender.sendMessage(Component.text("players only", NamedTextColor.RED));
|
||||||
return;
|
return;
|
||||||
@@ -412,6 +484,25 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (args.length < 2) {
|
||||||
|
player.sendMessage(Component.text("usage: /oyes setup <name>", NamedTextColor.RED));
|
||||||
|
player.sendMessage(Component.text("example: /oyes setup myEpicBambooShop", NamedTextColor.GREEN));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String shopName = args[1];
|
||||||
|
|
||||||
|
if (shopName.equalsIgnoreCase("myEpicBambooShop")) {
|
||||||
|
player.sendMessage(
|
||||||
|
Component.text("hey! that's cybsec's shop! choose a different name", NamedTextColor.RED));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plugin.getShopRegistry().getShopByOwnerAndName(player.getUniqueId(), shopName) != null) {
|
||||||
|
player.sendMessage(Component.text("you already have a shop with that name!", NamedTextColor.RED));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Block block = player.getTargetBlockExact(5);
|
Block block = player.getTargetBlockExact(5);
|
||||||
if (block == null || !(block.getBlockData() instanceof WallSign wallSign)) {
|
if (block == null || !(block.getBlockData() instanceof WallSign wallSign)) {
|
||||||
player.sendMessage(Component.text("you must look at a wall sign to use the wizard", NamedTextColor.RED));
|
player.sendMessage(Component.text("you must look at a wall sign to use the wizard", NamedTextColor.RED));
|
||||||
@@ -427,7 +518,7 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!PermissionManager.isAdmin(player)) {
|
if (plugin.getConfigManager().isRequirePlacement() && !PermissionManager.isAdmin(player)) {
|
||||||
java.util.UUID sessionOwner = plugin.getContainerMemoryManager().getOwner(attachedBlock.getLocation());
|
java.util.UUID sessionOwner = plugin.getContainerMemoryManager().getOwner(attachedBlock.getLocation());
|
||||||
if (sessionOwner == null || !sessionOwner.equals(player.getUniqueId())) {
|
if (sessionOwner == null || !sessionOwner.equals(player.getUniqueId())) {
|
||||||
player.sendMessage(
|
player.sendMessage(
|
||||||
@@ -437,39 +528,110 @@ public class AdminCommands implements CommandExecutor, TabCompleter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SetupDialog.open(player, block, plugin);
|
SetupDialog.open(player, block, plugin, shopName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleConfig(CommandSender sender) {
|
private void handleTogglePlacement(CommandSender sender) {
|
||||||
|
if (!PermissionManager.isAdmin(sender)) {
|
||||||
|
sender.sendMessage(Component.text("you don't have permission to do this", NamedTextColor.RED));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean current = plugin.getConfigManager().isRequirePlacement();
|
||||||
|
boolean newValue = !current;
|
||||||
|
plugin.getConfigManager().setRequirePlacement(newValue);
|
||||||
|
|
||||||
|
sender.sendMessage(Component.text("global container placement requirement is now ", NamedTextColor.GREEN)
|
||||||
|
.append(Component.text(newValue ? "ENABLED" : "DISABLED",
|
||||||
|
newValue ? NamedTextColor.YELLOW : NamedTextColor.RED,
|
||||||
|
net.kyori.adventure.text.format.TextDecoration.BOLD)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleConfig(CommandSender sender, String[] args) {
|
||||||
if (!(sender instanceof Player player)) {
|
if (!(sender instanceof Player player)) {
|
||||||
sender.sendMessage(Component.text("players only", NamedTextColor.RED));
|
sender.sendMessage(Component.text("players only", NamedTextColor.RED));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Shop shop = null;
|
||||||
|
if (args.length > 1) {
|
||||||
|
// remote config by name (lookup by owner)
|
||||||
|
shop = plugin.getShopRegistry().getShopByOwnerAndName(player.getUniqueId(), args[1]);
|
||||||
|
if (shop == null && PermissionManager.isAdmin(player)) {
|
||||||
|
// admins can lookup any shop by name if not found in their own
|
||||||
|
shop = plugin.getShopRegistry().getShopByName(args[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shop == null) {
|
||||||
|
player.sendMessage(Component.text("shop not found: " + args[1], NamedTextColor.RED));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// target block config
|
||||||
Block block = player.getTargetBlockExact(5);
|
Block block = player.getTargetBlockExact(5);
|
||||||
if (block == null) {
|
if (block == null) {
|
||||||
player.sendMessage(Component.text("you must look at a shop sign or its container", NamedTextColor.RED));
|
player.sendMessage(
|
||||||
|
Component.text("you must look at a shop sign or its container, or use /oyes config <name>",
|
||||||
|
NamedTextColor.RED));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Shop shop = null;
|
|
||||||
if (block.getBlockData() instanceof WallSign) {
|
if (block.getBlockData() instanceof WallSign) {
|
||||||
shop = plugin.getShopRegistry().getShop(block.getLocation());
|
shop = plugin.getShopRegistry().getShop(block.getLocation());
|
||||||
} else if (party.cybsec.oyeshops.listener.ShopActivationListener.isContainer(block.getType())) {
|
} else if (party.cybsec.oyeshops.listener.ShopActivationListener.isContainer(block.getType())) {
|
||||||
// try to find shop on any of the adjacent wall signs
|
|
||||||
shop = plugin.getShopRegistry().getShopByContainer(block);
|
shop = plugin.getShopRegistry().getShopByContainer(block);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (shop == null) {
|
if (shop == null) {
|
||||||
player.sendMessage(Component.text("that is not a part of any shop", NamedTextColor.RED));
|
player.sendMessage(Component.text("that is not a part of any shop", NamedTextColor.RED));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shop.getOwner().equals(player.getUniqueId()) && !PermissionManager.isAdmin(player)) {
|
if (!shop.getOwner().equals(player.getUniqueId()) && !shop.getContributors().contains(player.getUniqueId())
|
||||||
player.sendMessage(Component.text("you do not own this shop", NamedTextColor.RED));
|
&& !PermissionManager.isAdmin(player)) {
|
||||||
|
player.sendMessage(Component.text("you do not own or contribute to this shop", NamedTextColor.RED));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigDialog.open(player, shop, plugin);
|
ConfigDialog.open(player, shop, plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleTpShop(CommandSender sender, String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage(Component.text("players only", NamedTextColor.RED));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PermissionManager.isAdmin(player)) {
|
||||||
|
player.sendMessage(Component.text("no permission", NamedTextColor.RED));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length < 3) {
|
||||||
|
player.sendMessage(Component.text("usage: /oyes tpshop <ownerName> <shopName>", NamedTextColor.RED));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String ownerName = args[1];
|
||||||
|
String shopName = args[2];
|
||||||
|
Shop targetShop = null;
|
||||||
|
|
||||||
|
for (Shop shop : plugin.getShopRegistry().getAllShops()) {
|
||||||
|
org.bukkit.OfflinePlayer owner = org.bukkit.Bukkit.getOfflinePlayer(shop.getOwner());
|
||||||
|
if (ownerName.equalsIgnoreCase(owner.getName()) && shopName.equalsIgnoreCase(shop.getName())) {
|
||||||
|
targetShop = shop;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetShop == null) {
|
||||||
|
player.sendMessage(Component.text("shop not found for that owner and name", NamedTextColor.RED));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.teleport(targetShop.getSignLocation().clone().add(0.5, 0, 0.5));
|
||||||
|
player.sendMessage(
|
||||||
|
Component.text("teleporting to shop '" + shopName + "' by " + ownerName, NamedTextColor.GREEN));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,4 +37,13 @@ public class ConfigManager {
|
|||||||
public int getMaxTransactionsPerShop() {
|
public int getMaxTransactionsPerShop() {
|
||||||
return config.getInt("transactions.max-per-shop", 100);
|
return config.getInt("transactions.max-per-shop", 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isRequirePlacement() {
|
||||||
|
return config.getBoolean("require-placement", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequirePlacement(boolean require) {
|
||||||
|
config.set("require-placement", require);
|
||||||
|
save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,10 +109,18 @@ public class DatabaseManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
stmt.execute("alter table shops add column disc text");
|
stmt.execute("alter table shops add column name text");
|
||||||
} catch (SQLException ignored) {
|
} catch (SQLException ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
stmt.execute("alter table shops add column contributors text");
|
||||||
|
} catch (SQLException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// migration: set name to shop_id for existing shops where name is null
|
||||||
|
stmt.execute("update shops set name = cast(shop_id as text) where name is null");
|
||||||
|
|
||||||
// indexes
|
// indexes
|
||||||
stmt.execute("""
|
stmt.execute("""
|
||||||
create index if not exists idx_shop_location
|
create index if not exists idx_shop_location
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ public class ShopRepository {
|
|||||||
insert into shops (world_uuid, sign_x, sign_y, sign_z, owner_uuid,
|
insert into shops (world_uuid, sign_x, sign_y, sign_z, owner_uuid,
|
||||||
price_item, price_quantity, product_item, product_quantity,
|
price_item, price_quantity, product_item, product_quantity,
|
||||||
owed_amount, enabled, created_at, custom_title,
|
owed_amount, enabled, created_at, custom_title,
|
||||||
cosmetic_sign, disc)
|
cosmetic_sign, disc, name, contributors)
|
||||||
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""";
|
""";
|
||||||
|
|
||||||
try (PreparedStatement stmt = dbManager.getConnection().prepareStatement(sql,
|
try (PreparedStatement stmt = dbManager.getConnection().prepareStatement(sql,
|
||||||
@@ -52,6 +52,8 @@ public class ShopRepository {
|
|||||||
stmt.setString(13, shop.getCustomTitle());
|
stmt.setString(13, shop.getCustomTitle());
|
||||||
stmt.setBoolean(14, shop.isCosmeticSign());
|
stmt.setBoolean(14, shop.isCosmeticSign());
|
||||||
stmt.setString(15, shop.getDisc());
|
stmt.setString(15, shop.getDisc());
|
||||||
|
stmt.setString(16, shop.getName());
|
||||||
|
stmt.setString(17, serializeContributors(shop.getContributors()));
|
||||||
|
|
||||||
stmt.executeUpdate();
|
stmt.executeUpdate();
|
||||||
|
|
||||||
@@ -175,14 +177,16 @@ public class ShopRepository {
|
|||||||
*/
|
*/
|
||||||
public void updateShopConfig(Shop shop) throws SQLException {
|
public void updateShopConfig(Shop shop) throws SQLException {
|
||||||
String sql = """
|
String sql = """
|
||||||
update shops set custom_title = ?, disc = ?
|
update shops set custom_title = ?, disc = ?, name = ?, contributors = ?
|
||||||
where shop_id = ?
|
where shop_id = ?
|
||||||
""";
|
""";
|
||||||
|
|
||||||
try (PreparedStatement stmt = dbManager.getConnection().prepareStatement(sql)) {
|
try (PreparedStatement stmt = dbManager.getConnection().prepareStatement(sql)) {
|
||||||
stmt.setString(1, shop.getCustomTitle());
|
stmt.setString(1, shop.getCustomTitle());
|
||||||
stmt.setString(2, shop.getDisc());
|
stmt.setString(2, shop.getDisc());
|
||||||
stmt.setInt(3, shop.getId());
|
stmt.setString(3, shop.getName());
|
||||||
|
stmt.setString(4, serializeContributors(shop.getContributors()));
|
||||||
|
stmt.setInt(5, shop.getId());
|
||||||
stmt.executeUpdate();
|
stmt.executeUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,16 +255,47 @@ public class ShopRepository {
|
|||||||
return shops;
|
return shops;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* serialize list of UUIDs to comma-separated string
|
||||||
|
*/
|
||||||
|
private String serializeContributors(List<UUID> contributors) {
|
||||||
|
if (contributors == null || contributors.isEmpty())
|
||||||
|
return "";
|
||||||
|
List<String> uuids = new ArrayList<>();
|
||||||
|
for (UUID uuid : contributors) {
|
||||||
|
uuids.add(uuid.toString());
|
||||||
|
}
|
||||||
|
return String.join(",", uuids);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* deserialize comma-separated string to list of UUIDs
|
||||||
|
*/
|
||||||
|
private List<UUID> deserializeContributors(String data) {
|
||||||
|
List<UUID> list = new ArrayList<>();
|
||||||
|
if (data == null || data.isEmpty())
|
||||||
|
return list;
|
||||||
|
for (String s : data.split(",")) {
|
||||||
|
try {
|
||||||
|
list.add(UUID.fromString(s));
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* convert result set to shop object
|
* convert result set to shop object
|
||||||
*/
|
*/
|
||||||
private Shop shopFromResultSet(ResultSet rs) throws SQLException {
|
private Shop shopFromResultSet(ResultSet rs) throws SQLException {
|
||||||
int id = rs.getInt("shop_id");
|
int id = rs.getInt("shop_id");
|
||||||
|
String name = rs.getString("name");
|
||||||
UUID worldUuid = UUID.fromString(rs.getString("world_uuid"));
|
UUID worldUuid = UUID.fromString(rs.getString("world_uuid"));
|
||||||
int x = rs.getInt("sign_x");
|
int x = rs.getInt("sign_x");
|
||||||
int y = rs.getInt("sign_y");
|
int y = rs.getInt("sign_y");
|
||||||
int z = rs.getInt("sign_z");
|
int z = rs.getInt("sign_z");
|
||||||
UUID ownerUuid = UUID.fromString(rs.getString("owner_uuid"));
|
UUID ownerUuid = UUID.fromString(rs.getString("owner_uuid"));
|
||||||
|
List<UUID> contributors = deserializeContributors(rs.getString("contributors"));
|
||||||
Material priceItem = Material.valueOf(rs.getString("price_item"));
|
Material priceItem = Material.valueOf(rs.getString("price_item"));
|
||||||
int priceQty = rs.getInt("price_quantity");
|
int priceQty = rs.getInt("price_quantity");
|
||||||
Material productItem = Material.valueOf(rs.getString("product_item"));
|
Material productItem = Material.valueOf(rs.getString("product_item"));
|
||||||
@@ -280,7 +315,7 @@ public class ShopRepository {
|
|||||||
Location location = new Location(world, x, y, z);
|
Location location = new Location(world, x, y, z);
|
||||||
Trade trade = new Trade(priceItem, priceQty, productItem, productQty);
|
Trade trade = new Trade(priceItem, priceQty, productItem, productQty);
|
||||||
|
|
||||||
return new Shop(id, location, ownerUuid, trade, owedAmount, enabled, createdAt,
|
return new Shop(id, name, location, ownerUuid, contributors, trade, owedAmount, enabled, createdAt,
|
||||||
customTitle, cosmeticSign, disc);
|
customTitle, cosmeticSign, disc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ import party.cybsec.oyeshops.OyeShopsPlugin;
|
|||||||
import party.cybsec.oyeshops.model.Shop;
|
import party.cybsec.oyeshops.model.Shop;
|
||||||
|
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* config dialog for shop settings
|
* config dialog for shop settings
|
||||||
@@ -32,17 +34,34 @@ public class ConfigDialog {
|
|||||||
*/
|
*/
|
||||||
public static void open(Player player, Shop shop, OyeShopsPlugin plugin) {
|
public static void open(Player player, Shop shop, OyeShopsPlugin plugin) {
|
||||||
Dialog dialog = Dialog.create(builder -> builder.empty()
|
Dialog dialog = Dialog.create(builder -> builder.empty()
|
||||||
.base(DialogBase.builder(Component.text("shop #" + shop.getId() + " config", NamedTextColor.GOLD))
|
.base(DialogBase.builder(
|
||||||
|
Component.text("config: " + shop.getName(), NamedTextColor.GOLD))
|
||||||
.inputs(List.of(
|
.inputs(List.of(
|
||||||
|
DialogInput.text("name",
|
||||||
|
Component.text("rename shop",
|
||||||
|
NamedTextColor.YELLOW))
|
||||||
|
.initial(shop.getName())
|
||||||
|
.build(),
|
||||||
DialogInput.text("custom_title",
|
DialogInput.text("custom_title",
|
||||||
Component.text("custom title (optional)", NamedTextColor.YELLOW))
|
Component.text("custom title (optional)",
|
||||||
.initial(shop.getCustomTitle() != null ? shop.getCustomTitle() : "")
|
NamedTextColor.YELLOW))
|
||||||
|
.initial(shop.getCustomTitle() != null
|
||||||
|
? shop.getCustomTitle()
|
||||||
|
: "")
|
||||||
.build(),
|
.build(),
|
||||||
DialogInput.text("disc",
|
DialogInput.text("disc",
|
||||||
Component.text(
|
Component.text(
|
||||||
"music disc: none/blocks/chirp/far/mall/mellohi/stal/strad/ward/wait",
|
"music disc: none/blocks/chirp/far/mall/mellohi/stal/strad/ward/wait",
|
||||||
NamedTextColor.AQUA))
|
NamedTextColor.AQUA))
|
||||||
.initial(shop.getDisc() != null ? shop.getDisc() : "none")
|
.initial(shop.getDisc() != null
|
||||||
|
? shop.getDisc()
|
||||||
|
: "none")
|
||||||
|
.build(),
|
||||||
|
DialogInput.text("contributors",
|
||||||
|
Component.text("contributors (comma separated names)",
|
||||||
|
NamedTextColor.LIGHT_PURPLE))
|
||||||
|
.initial(getContributorNames(
|
||||||
|
shop.getContributors()))
|
||||||
.build()))
|
.build()))
|
||||||
.build())
|
.build())
|
||||||
.type(DialogType.confirmation(
|
.type(DialogType.confirmation(
|
||||||
@@ -50,46 +69,137 @@ public class ConfigDialog {
|
|||||||
.tooltip(Component.text("save configuration"))
|
.tooltip(Component.text("save configuration"))
|
||||||
.action(DialogAction.customClick((view, audience) -> {
|
.action(DialogAction.customClick((view, audience) -> {
|
||||||
Player p = (Player) audience;
|
Player p = (Player) audience;
|
||||||
|
String newName = view.getText("name");
|
||||||
String title = view.getText("custom_title");
|
String title = view.getText("custom_title");
|
||||||
String disc = view.getText("disc");
|
String disc = view.getText("disc");
|
||||||
|
String contributorsStr = view
|
||||||
|
.getText("contributors");
|
||||||
|
|
||||||
// validate disc
|
if (newName == null || newName.isEmpty()) {
|
||||||
disc = disc.toLowerCase().trim();
|
p.sendMessage(Component.text(
|
||||||
if (!disc.isEmpty() && !VALID_DISCS.contains(disc)) {
|
"shop name cannot be empty",
|
||||||
p.sendMessage(Component.text("invalid disc: " + disc
|
|
||||||
+ ". valid options: none, blocks, chirp, far, mall, mellohi, stal, strad, ward, wait",
|
|
||||||
NamedTextColor.RED));
|
NamedTextColor.RED));
|
||||||
open(p, shop, plugin); // reopen dialog
|
open(p, shop, plugin);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
shop.setCustomTitle(title.isEmpty() ? null : title);
|
if (newName.equalsIgnoreCase(
|
||||||
shop.setDisc(disc.isEmpty() || disc.equals("none") ? null : disc);
|
"myEpicBambooShop")) {
|
||||||
|
p.sendMessage(Component.text(
|
||||||
|
"hey! that's cybsec's shop! choose a different name",
|
||||||
|
NamedTextColor.RED));
|
||||||
|
open(p, shop, plugin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
// validate name uniqueness if changed (player
|
||||||
|
// scoped)
|
||||||
|
if (!newName.equalsIgnoreCase(shop.getName())) {
|
||||||
|
if (plugin.getShopRegistry()
|
||||||
|
.getShopByOwnerAndName(
|
||||||
|
shop.getOwner(),
|
||||||
|
newName) != null) {
|
||||||
|
p.sendMessage(Component.text(
|
||||||
|
"you already have a shop with that name!",
|
||||||
|
NamedTextColor.RED));
|
||||||
|
open(p, shop, plugin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate disc
|
||||||
|
disc = disc.toLowerCase().trim();
|
||||||
|
if (!disc.isEmpty() && !VALID_DISCS
|
||||||
|
.contains(disc)) {
|
||||||
|
p.sendMessage(Component.text(
|
||||||
|
"invalid disc: " + disc
|
||||||
|
+ ". valid options: none, blocks, chirp, far, mall, mellohi, stal, strad, ward, wait",
|
||||||
|
NamedTextColor.RED));
|
||||||
|
open(p, shop, plugin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
shop.setName(newName);
|
||||||
|
shop.setCustomTitle(
|
||||||
|
title.isEmpty() ? null : title);
|
||||||
|
shop.setDisc(disc.isEmpty()
|
||||||
|
|| disc.equals("none") ? null
|
||||||
|
: disc);
|
||||||
|
|
||||||
|
// resolve contributors
|
||||||
|
List<UUID> contributorUuids = resolveContributors(
|
||||||
|
contributorsStr);
|
||||||
|
shop.getContributors().clear();
|
||||||
|
shop.getContributors().addAll(contributorUuids);
|
||||||
|
|
||||||
|
plugin.getServer().getScheduler()
|
||||||
|
.runTaskAsynchronously(plugin,
|
||||||
|
() -> {
|
||||||
try {
|
try {
|
||||||
plugin.getShopRepository().updateShopConfig(shop);
|
plugin.getShopRepository()
|
||||||
plugin.getServer().getScheduler().runTask(plugin, () -> {
|
.updateShopConfig(
|
||||||
|
shop);
|
||||||
|
plugin.getServer()
|
||||||
|
.getScheduler()
|
||||||
|
.runTask(plugin, () -> {
|
||||||
p.sendMessage(
|
p.sendMessage(
|
||||||
Component.text("shop config saved", NamedTextColor.GREEN));
|
Component.text("shop config saved",
|
||||||
|
NamedTextColor.GREEN));
|
||||||
});
|
});
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
plugin.getServer().getScheduler().runTask(plugin, () -> {
|
plugin.getServer()
|
||||||
p.sendMessage(Component.text("database error", NamedTextColor.RED));
|
.getScheduler()
|
||||||
|
.runTask(plugin, () -> {
|
||||||
|
p.sendMessage(Component
|
||||||
|
.text("database error",
|
||||||
|
NamedTextColor.RED));
|
||||||
});
|
});
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, ClickCallback.Options.builder().uses(1).build()))
|
}, ClickCallback.Options.builder().uses(1).build()))
|
||||||
.build(),
|
.build(),
|
||||||
ActionButton.builder(Component.text("cancel", TextColor.color(0xFFA0B1)))
|
ActionButton.builder(
|
||||||
|
Component.text("cancel", TextColor.color(0xFFA0B1)))
|
||||||
.tooltip(Component.text("discard changes"))
|
.tooltip(Component.text("discard changes"))
|
||||||
.action(DialogAction.customClick((view, audience) -> {
|
.action(DialogAction.customClick((view, audience) -> {
|
||||||
((Player) audience)
|
((Player) audience)
|
||||||
.sendMessage(Component.text("config cancelled", NamedTextColor.YELLOW));
|
.sendMessage(Component.text(
|
||||||
|
"config cancelled",
|
||||||
|
NamedTextColor.YELLOW));
|
||||||
}, ClickCallback.Options.builder().build()))
|
}, ClickCallback.Options.builder().build()))
|
||||||
.build())));
|
.build())));
|
||||||
|
|
||||||
player.showDialog(dialog);
|
player.showDialog(dialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String getContributorNames(List<UUID> uuids) {
|
||||||
|
if (uuids == null || uuids.isEmpty())
|
||||||
|
return "";
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
for (UUID uuid : uuids) {
|
||||||
|
org.bukkit.OfflinePlayer op = org.bukkit.Bukkit.getOfflinePlayer(uuid);
|
||||||
|
if (op.getName() != null) {
|
||||||
|
names.add(op.getName());
|
||||||
|
} else {
|
||||||
|
names.add(uuid.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return String.join(", ", names);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<UUID> resolveContributors(String data) {
|
||||||
|
List<UUID> list = new ArrayList<>();
|
||||||
|
if (data == null || data.isEmpty())
|
||||||
|
return list;
|
||||||
|
for (String part : data.split(",")) {
|
||||||
|
String name = part.trim();
|
||||||
|
if (name.isEmpty())
|
||||||
|
continue;
|
||||||
|
// try to resolve by name
|
||||||
|
org.bukkit.OfflinePlayer op = org.bukkit.Bukkit.getOfflinePlayer(name);
|
||||||
|
list.add(op.getUniqueId());
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
package party.cybsec.oyeshops.gui;
|
|
||||||
|
|
||||||
import net.kyori.adventure.text.Component;
|
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.inventory.Inventory;
|
|
||||||
import org.bukkit.inventory.InventoryHolder;
|
|
||||||
import org.bukkit.inventory.ItemStack;
|
|
||||||
import org.bukkit.inventory.meta.ItemMeta;
|
|
||||||
import party.cybsec.oyeshops.model.Shop;
|
|
||||||
import party.cybsec.oyeshops.model.Trade;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* confirmation GUI for shop purchases
|
|
||||||
* supports batch purchases with units
|
|
||||||
*/
|
|
||||||
public class ConfirmationGui implements InventoryHolder {
|
|
||||||
private final Shop shop;
|
|
||||||
private final int units;
|
|
||||||
private final Inventory inventory;
|
|
||||||
|
|
||||||
public ConfirmationGui(Shop shop, int units) {
|
|
||||||
this.shop = shop;
|
|
||||||
this.units = Math.max(1, units);
|
|
||||||
this.inventory = createInventory();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Inventory createInventory() {
|
|
||||||
Trade trade = shop.getTrade();
|
|
||||||
int totalPrice = trade.priceQuantity() * units;
|
|
||||||
int totalProduct = trade.productQuantity() * units;
|
|
||||||
|
|
||||||
String title = units > 1
|
|
||||||
? "confirm: " + units + " units"
|
|
||||||
: "confirm purchase";
|
|
||||||
|
|
||||||
Inventory inv = Bukkit.createInventory(this, 27, Component.text(title));
|
|
||||||
|
|
||||||
// fill with gray glass
|
|
||||||
ItemStack filler = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
|
|
||||||
ItemMeta fillerMeta = filler.getItemMeta();
|
|
||||||
fillerMeta.displayName(Component.text(" "));
|
|
||||||
filler.setItemMeta(fillerMeta);
|
|
||||||
for (int i = 0; i < 27; i++) {
|
|
||||||
inv.setItem(i, filler.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
// price item display (left side - slot 3)
|
|
||||||
ItemStack priceDisplay = new ItemStack(trade.priceItem(), Math.min(totalPrice, 64));
|
|
||||||
ItemMeta priceMeta = priceDisplay.getItemMeta();
|
|
||||||
priceMeta.displayName(Component.text("you pay:", NamedTextColor.RED));
|
|
||||||
List<Component> priceLore = new ArrayList<>();
|
|
||||||
priceLore.add(Component.text(totalPrice + " " + formatMaterial(trade.priceItem()), NamedTextColor.WHITE));
|
|
||||||
if (units > 1) {
|
|
||||||
priceLore.add(Component.text("(" + trade.priceQuantity() + " × " + units + " units)", NamedTextColor.GRAY));
|
|
||||||
}
|
|
||||||
priceMeta.lore(priceLore);
|
|
||||||
priceDisplay.setItemMeta(priceMeta);
|
|
||||||
inv.setItem(3, priceDisplay);
|
|
||||||
|
|
||||||
// product item display (right side - slot 5)
|
|
||||||
ItemStack productDisplay = new ItemStack(trade.productItem(), Math.min(totalProduct, 64));
|
|
||||||
ItemMeta productMeta = productDisplay.getItemMeta();
|
|
||||||
productMeta.displayName(Component.text("you get:", NamedTextColor.GREEN));
|
|
||||||
List<Component> productLore = new ArrayList<>();
|
|
||||||
productLore.add(Component.text(totalProduct + " " + formatMaterial(trade.productItem()), NamedTextColor.WHITE));
|
|
||||||
if (units > 1) {
|
|
||||||
productLore.add(
|
|
||||||
Component.text("(" + trade.productQuantity() + " × " + units + " units)", NamedTextColor.GRAY));
|
|
||||||
}
|
|
||||||
productMeta.lore(productLore);
|
|
||||||
productDisplay.setItemMeta(productMeta);
|
|
||||||
inv.setItem(5, productDisplay);
|
|
||||||
|
|
||||||
// confirm button (green - bottom left)
|
|
||||||
ItemStack confirm = new ItemStack(Material.LIME_STAINED_GLASS_PANE);
|
|
||||||
ItemMeta confirmMeta = confirm.getItemMeta();
|
|
||||||
confirmMeta.displayName(Component.text("CONFIRM", NamedTextColor.GREEN));
|
|
||||||
confirmMeta.lore(List.of(Component.text("click to complete purchase", NamedTextColor.GRAY)));
|
|
||||||
confirm.setItemMeta(confirmMeta);
|
|
||||||
inv.setItem(11, confirm);
|
|
||||||
inv.setItem(12, confirm.clone());
|
|
||||||
|
|
||||||
// cancel button (red - bottom right)
|
|
||||||
ItemStack cancel = new ItemStack(Material.RED_STAINED_GLASS_PANE);
|
|
||||||
ItemMeta cancelMeta = cancel.getItemMeta();
|
|
||||||
cancelMeta.displayName(Component.text("CANCEL", NamedTextColor.RED));
|
|
||||||
cancelMeta.lore(List.of(Component.text("click to cancel", NamedTextColor.GRAY)));
|
|
||||||
cancel.setItemMeta(cancelMeta);
|
|
||||||
inv.setItem(14, cancel);
|
|
||||||
inv.setItem(15, cancel.clone());
|
|
||||||
|
|
||||||
return inv;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String formatMaterial(Material material) {
|
|
||||||
return material.name().toLowerCase().replace("_", " ");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Shop getShop() {
|
|
||||||
return shop;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getUnits() {
|
|
||||||
return units;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Inventory getInventory() {
|
|
||||||
return inventory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -24,10 +24,16 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class SetupDialog {
|
public class SetupDialog {
|
||||||
|
|
||||||
public static void open(Player player, Block signBlock, OyeShopsPlugin plugin) {
|
public static void open(Player player, Block signBlock, OyeShopsPlugin plugin, String shopName) {
|
||||||
Dialog dialog = Dialog.create(builder -> builder.empty()
|
Dialog dialog = Dialog.create(builder -> builder.empty()
|
||||||
.base(DialogBase.builder(Component.text("shop setup wizard", NamedTextColor.GOLD))
|
.base(DialogBase.builder(Component.text("shop setup wizard", NamedTextColor.GOLD))
|
||||||
.inputs(List.of(
|
.inputs(List.of(
|
||||||
|
// shop name
|
||||||
|
DialogInput.text("shop_name",
|
||||||
|
Component.text("shop name (unique)",
|
||||||
|
NamedTextColor.YELLOW))
|
||||||
|
.initial(shopName)
|
||||||
|
.build(),
|
||||||
// product (selling)
|
// product (selling)
|
||||||
DialogInput.text("product_item",
|
DialogInput.text("product_item",
|
||||||
Component.text("selling what? (e.g. dirt)",
|
Component.text("selling what? (e.g. dirt)",
|
||||||
@@ -41,22 +47,13 @@ public class SetupDialog {
|
|||||||
|
|
||||||
// price (buying)
|
// price (buying)
|
||||||
DialogInput.text("price_item",
|
DialogInput.text("price_item",
|
||||||
Component.text("what do you want? (e.g. diamond)",
|
Component.text("what do you want? (prop: diamond)",
|
||||||
NamedTextColor.GREEN))
|
NamedTextColor.GREEN))
|
||||||
.build(),
|
.build(),
|
||||||
DialogInput.text("price_qty",
|
DialogInput.text("price_qty",
|
||||||
Component.text("how many? (e.g. 10)",
|
Component.text("how many? (e.g. 10)",
|
||||||
NamedTextColor.GREEN))
|
NamedTextColor.GREEN))
|
||||||
.initial("1")
|
.initial("1")
|
||||||
.build(),
|
|
||||||
|
|
||||||
// cosmetic sign toggle
|
|
||||||
DialogInput.bool("cosmetic_sign",
|
|
||||||
Component.text("cosmetic sign? (don't rewrite text)",
|
|
||||||
NamedTextColor.AQUA))
|
|
||||||
.initial(false)
|
|
||||||
.onTrue("enabled")
|
|
||||||
.onFalse("disabled")
|
|
||||||
.build()))
|
.build()))
|
||||||
.build())
|
.build())
|
||||||
.type(DialogType.confirmation(
|
.type(DialogType.confirmation(
|
||||||
@@ -65,6 +62,7 @@ public class SetupDialog {
|
|||||||
.tooltip(Component
|
.tooltip(Component
|
||||||
.text("click to confirm trade details"))
|
.text("click to confirm trade details"))
|
||||||
.action(DialogAction.customClick((view, audience) -> {
|
.action(DialogAction.customClick((view, audience) -> {
|
||||||
|
String newShopName = view.getText("shop_name");
|
||||||
String productStr = view
|
String productStr = view
|
||||||
.getText("product_item");
|
.getText("product_item");
|
||||||
String productQtyStr = view
|
String productQtyStr = view
|
||||||
@@ -73,6 +71,36 @@ public class SetupDialog {
|
|||||||
String priceQtyStr = view.getText("price_qty");
|
String priceQtyStr = view.getText("price_qty");
|
||||||
Player p = (Player) audience;
|
Player p = (Player) audience;
|
||||||
|
|
||||||
|
// shop name validation
|
||||||
|
if (newShopName == null
|
||||||
|
|| newShopName.isEmpty()) {
|
||||||
|
p.sendMessage(Component.text(
|
||||||
|
"shop name cannot be empty",
|
||||||
|
NamedTextColor.RED));
|
||||||
|
open(p, signBlock, plugin, shopName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newShopName.equalsIgnoreCase(
|
||||||
|
"myEpicBambooShop")) {
|
||||||
|
p.sendMessage(Component.text(
|
||||||
|
"hey! that's cybsec's shop! choose a different name",
|
||||||
|
NamedTextColor.RED));
|
||||||
|
open(p, signBlock, plugin, newShopName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plugin.getShopRegistry()
|
||||||
|
.getShopByOwnerAndName(
|
||||||
|
p.getUniqueId(),
|
||||||
|
newShopName) != null) {
|
||||||
|
p.sendMessage(Component.text(
|
||||||
|
"you already have a shop with that name!",
|
||||||
|
NamedTextColor.RED));
|
||||||
|
open(p, signBlock, plugin, newShopName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 1. parse price qty
|
// 1. parse price qty
|
||||||
int priceQty;
|
int priceQty;
|
||||||
try {
|
try {
|
||||||
@@ -82,7 +110,7 @@ public class SetupDialog {
|
|||||||
p.sendMessage(Component.text(
|
p.sendMessage(Component.text(
|
||||||
"invalid price quantity",
|
"invalid price quantity",
|
||||||
NamedTextColor.RED));
|
NamedTextColor.RED));
|
||||||
open(p, signBlock, plugin);
|
open(p, signBlock, plugin, newShopName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,11 +122,11 @@ public class SetupDialog {
|
|||||||
"invalid payment item: "
|
"invalid payment item: "
|
||||||
+ priceStr,
|
+ priceStr,
|
||||||
NamedTextColor.RED));
|
NamedTextColor.RED));
|
||||||
open(p, signBlock, plugin);
|
open(p, signBlock, plugin, newShopName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Regular parsing logic
|
// 3. parse product qty
|
||||||
int productQty;
|
int productQty;
|
||||||
try {
|
try {
|
||||||
productQty = Integer.parseInt(
|
productQty = Integer.parseInt(
|
||||||
@@ -107,38 +135,36 @@ public class SetupDialog {
|
|||||||
p.sendMessage(Component.text(
|
p.sendMessage(Component.text(
|
||||||
"invalid product quantity",
|
"invalid product quantity",
|
||||||
NamedTextColor.RED));
|
NamedTextColor.RED));
|
||||||
open(p, signBlock, plugin);
|
open(p, signBlock, plugin, newShopName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Material productMat = plugin
|
Material productMat = plugin.getSignParser()
|
||||||
.getSignParser()
|
|
||||||
.parseMaterial(productStr);
|
.parseMaterial(productStr);
|
||||||
if (productMat == null) {
|
if (productMat == null) {
|
||||||
p.sendMessage(Component.text(
|
p.sendMessage(Component.text(
|
||||||
"invalid product: "
|
"invalid product: "
|
||||||
+ productStr,
|
+ productStr,
|
||||||
NamedTextColor.RED));
|
NamedTextColor.RED));
|
||||||
open(p, signBlock, plugin);
|
open(p, signBlock, plugin, newShopName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Trade trade = new Trade(priceMat, priceQty,
|
Trade trade = new Trade(priceMat, priceQty,
|
||||||
productMat, productQty);
|
productMat, productQty);
|
||||||
|
|
||||||
boolean cosmeticSign = view
|
|
||||||
.getBoolean("cosmetic_sign");
|
|
||||||
|
|
||||||
PendingActivation activation = new PendingActivation(
|
PendingActivation activation = new PendingActivation(
|
||||||
p.getUniqueId(),
|
p.getUniqueId(),
|
||||||
signBlock.getLocation(),
|
signBlock.getLocation(),
|
||||||
trade,
|
trade,
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
cosmeticSign);
|
true); // cosmeticSign always
|
||||||
|
// true
|
||||||
|
|
||||||
// 4. finalize
|
// 4. finalize
|
||||||
plugin.getServer().getScheduler()
|
plugin.getServer().getScheduler()
|
||||||
.runTask(plugin, () -> {
|
.runTask(plugin, () -> {
|
||||||
plugin.getShopActivationListener()
|
plugin.getShopActivationListener()
|
||||||
.finalizeShop(p, activation);
|
.finalizeShop(p, activation,
|
||||||
|
newShopName);
|
||||||
});
|
});
|
||||||
}, ClickCallback.Options.builder().uses(1).build()))
|
}, ClickCallback.Options.builder().uses(1).build()))
|
||||||
.build(),
|
.build(),
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import org.bukkit.inventory.Inventory;
|
|||||||
import org.bukkit.inventory.InventoryHolder;
|
import org.bukkit.inventory.InventoryHolder;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import party.cybsec.oyeshops.OyeShopsPlugin;
|
import party.cybsec.oyeshops.OyeShopsPlugin;
|
||||||
import party.cybsec.oyeshops.gui.ConfirmationGui;
|
|
||||||
import party.cybsec.oyeshops.model.Shop;
|
import party.cybsec.oyeshops.model.Shop;
|
||||||
import party.cybsec.oyeshops.model.Trade;
|
import party.cybsec.oyeshops.model.Trade;
|
||||||
import party.cybsec.oyeshops.permission.PermissionManager;
|
import party.cybsec.oyeshops.permission.PermissionManager;
|
||||||
@@ -40,8 +39,11 @@ import java.util.Map;
|
|||||||
public class ChestInteractionListener implements Listener {
|
public class ChestInteractionListener implements Listener {
|
||||||
private final OyeShopsPlugin plugin;
|
private final OyeShopsPlugin plugin;
|
||||||
|
|
||||||
// track players viewing fake shop inventories
|
// track active shop sessions for players
|
||||||
private final Map<Player, Shop> viewingShop = new HashMap<>();
|
private final Map<Player, ShopSession> activeSessions = new HashMap<>();
|
||||||
|
|
||||||
|
public record ShopSession(Shop shop, int unitsTraded, Inventory realInventory) {
|
||||||
|
}
|
||||||
|
|
||||||
public ChestInteractionListener(OyeShopsPlugin plugin) {
|
public ChestInteractionListener(OyeShopsPlugin plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
@@ -63,8 +65,7 @@ public class ChestInteractionListener implements Listener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if there's a shop sign attached to this container or its double chest
|
// check if there's a shop sign attached to this container
|
||||||
// partner
|
|
||||||
Shop shop = findShopForContainer(block);
|
Shop shop = findShopForContainer(block);
|
||||||
if (shop == null) {
|
if (shop == null) {
|
||||||
return; // not a shop container
|
return; // not a shop container
|
||||||
@@ -79,16 +80,18 @@ public class ChestInteractionListener implements Listener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if player is owner (unless spoofing)
|
// check if player is owner or contributor (unless spoofing)
|
||||||
boolean isOwner = shop.getOwner().equals(player.getUniqueId())
|
boolean isOwner = shop.getOwner().equals(player.getUniqueId())
|
||||||
&& !plugin.getSpoofManager().isSpoofing(player);
|
&& !plugin.getSpoofManager().isSpoofing(player);
|
||||||
|
boolean isContributor = shop.getContributors().contains(player.getUniqueId())
|
||||||
|
&& !plugin.getSpoofManager().isSpoofing(player);
|
||||||
|
|
||||||
if (isOwner) {
|
if (isOwner || isContributor) {
|
||||||
// owner interaction - check for owed items and dispense
|
// owner/contributor interaction - check for owed items and dispense
|
||||||
if (shop.getOwedAmount() > 0) {
|
if (shop.getOwedAmount() > 0) {
|
||||||
withdrawOwedItems(player, shop);
|
withdrawOwedItems(player, shop);
|
||||||
}
|
}
|
||||||
// let owner open the chest normally - don't cancel event
|
// let them open the chest normally
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,18 +176,13 @@ public class ChestInteractionListener implements Listener {
|
|||||||
title = Component.text(shop.getCustomTitle());
|
title = Component.text(shop.getCustomTitle());
|
||||||
}
|
}
|
||||||
|
|
||||||
// get real container inventory (handles double chests correctly)
|
// get real container inventory
|
||||||
Inventory realInventory = getContainerInventory(containerBlock);
|
Inventory realInventory = getContainerInventory(containerBlock);
|
||||||
if (realInventory == null) {
|
if (realInventory == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine inventory size based on container type
|
int invSize = Math.min(54, Math.max(27, realInventory.getSize()));
|
||||||
int invSize = realInventory.getSize();
|
|
||||||
if (invSize > 54)
|
|
||||||
invSize = 54; // cap at double chest
|
|
||||||
if (invSize < 27)
|
|
||||||
invSize = 27; // minimum single chest
|
|
||||||
|
|
||||||
// create fake inventory showing only product items
|
// create fake inventory showing only product items
|
||||||
Inventory shopInventory = Bukkit.createInventory(
|
Inventory shopInventory = Bukkit.createInventory(
|
||||||
@@ -192,17 +190,26 @@ public class ChestInteractionListener implements Listener {
|
|||||||
invSize,
|
invSize,
|
||||||
title);
|
title);
|
||||||
|
|
||||||
// copy matching items to fake inventory
|
// populate with units (productQuantity per slot)
|
||||||
int shopSlot = 0;
|
int totalStock = 0;
|
||||||
for (ItemStack item : realInventory.getContents()) {
|
for (ItemStack item : realInventory.getContents()) {
|
||||||
if (shopSlot >= invSize)
|
|
||||||
break;
|
|
||||||
if (item != null && trade.matchesProduct(item.getType())) {
|
if (item != null && trade.matchesProduct(item.getType())) {
|
||||||
shopInventory.setItem(shopSlot++, item.clone());
|
totalStock += item.getAmount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
viewingShop.put(player, shop);
|
int unitsAvailable = totalStock / trade.productQuantity();
|
||||||
|
int slotsToFill = Math.min(invSize, unitsAvailable);
|
||||||
|
|
||||||
|
// get representative item with NBT for display
|
||||||
|
ItemStack displayItem = plugin.getTransactionManager().getRepresentativeItem(realInventory, trade.productItem(),
|
||||||
|
trade.productQuantity());
|
||||||
|
|
||||||
|
for (int i = 0; i < slotsToFill; i++) {
|
||||||
|
shopInventory.setItem(i, displayItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
activeSessions.put(player, new ShopSession(shop, 0, realInventory));
|
||||||
player.openInventory(shopInventory);
|
player.openInventory(shopInventory);
|
||||||
|
|
||||||
// play shop owner's configured disc if set
|
// play shop owner's configured disc if set
|
||||||
@@ -256,97 +263,147 @@ public class ChestInteractionListener implements Listener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
InventoryHolder holder = event.getInventory().getHolder();
|
ShopSession session = activeSessions.get(player);
|
||||||
|
if (session == null) {
|
||||||
// handle shop inventory clicks
|
return;
|
||||||
if (holder instanceof ShopInventoryHolder shopHolder) {
|
}
|
||||||
event.setCancelled(true);
|
|
||||||
Shop shop = shopHolder.shop();
|
InventoryHolder holder = event.getInventory().getHolder();
|
||||||
|
if (!(holder instanceof ShopInventoryHolder)) {
|
||||||
// if clicked in the shop inventory area
|
|
||||||
if (event.getRawSlot() < event.getInventory().getSize()) {
|
|
||||||
ItemStack clicked = event.getCurrentItem();
|
|
||||||
Trade trade = shop.getTrade();
|
|
||||||
if (clicked != null && trade.matchesProduct(clicked.getType())) {
|
|
||||||
// determine quantity based on click type
|
|
||||||
int units = 1;
|
|
||||||
if (event.isShiftClick()) {
|
|
||||||
// shift-click: calculate max units based on items clicked
|
|
||||||
units = clicked.getAmount() / trade.productQuantity();
|
|
||||||
if (units < 1)
|
|
||||||
units = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// open confirmation gui
|
|
||||||
player.closeInventory();
|
|
||||||
openConfirmationGui(player, shop, units);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// clicked in player inventory - play negative feedback sound
|
|
||||||
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1.0f, 1.0f);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle confirmation GUI clicks
|
|
||||||
if (holder instanceof ConfirmationGui confirmGui) {
|
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
Shop shop = confirmGui.getShop();
|
|
||||||
|
|
||||||
int slot = event.getRawSlot();
|
int slot = event.getRawSlot();
|
||||||
|
if (slot >= event.getInventory().getSize()) {
|
||||||
|
return; // Clicked player inventory
|
||||||
|
}
|
||||||
|
|
||||||
// green pane = confirm (slot 11-15 or specifically slot 11)
|
ItemStack clicked = event.getCurrentItem();
|
||||||
if (slot == 11 || slot == 12 || slot == 13) {
|
if (clicked == null || clicked.getType() == Material.AIR) {
|
||||||
player.closeInventory();
|
return;
|
||||||
executeTransaction(player, shop, confirmGui.getUnits());
|
|
||||||
}
|
}
|
||||||
// red pane = cancel (slot 15 or right side)
|
|
||||||
else if (slot == 14 || slot == 15 || slot == 16) {
|
Shop shop = session.shop();
|
||||||
player.closeInventory();
|
Trade trade = shop.getTrade();
|
||||||
player.sendMessage(Component.text("purchase cancelled", NamedTextColor.YELLOW));
|
TransactionManager tm = plugin.getTransactionManager();
|
||||||
|
|
||||||
|
// Case 1: Clicked a Product (to BUY)
|
||||||
|
if (trade.matchesProduct(clicked.getType())) {
|
||||||
|
// 1. Check if player has payment
|
||||||
|
if (!tm.hasItems(player.getInventory(), trade.priceItem(), trade.priceQuantity())) {
|
||||||
|
player.sendMessage(Component.text("you don't have enough to pay!", NamedTextColor.RED));
|
||||||
|
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1.0f, 1.0f);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Check if player has space for product
|
||||||
|
if (!tm.hasSpace(player.getInventory(), trade.productItem(), trade.productQuantity())) {
|
||||||
|
player.sendMessage(Component.text("your inventory is full!", NamedTextColor.RED));
|
||||||
|
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1.0f, 1.0f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Remove product from REAL chest (NBT PRESERVED)
|
||||||
|
if (tm.hasItems(session.realInventory(), trade.productItem(), trade.productQuantity())) {
|
||||||
|
ItemStack[] products = tm.removeItemsAndReturn(session.realInventory(), trade.productItem(),
|
||||||
|
trade.productQuantity());
|
||||||
|
|
||||||
|
// 4. Give product to Player
|
||||||
|
player.getInventory().addItem(products);
|
||||||
|
|
||||||
|
// 5. Remove payment from Player (NBT preserved if needed)
|
||||||
|
tm.removeItems(player.getInventory(), trade.priceItem(), trade.priceQuantity());
|
||||||
|
|
||||||
|
// Success!
|
||||||
|
player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f);
|
||||||
|
|
||||||
|
// Show "Payment" in GUI slot to allow reversal
|
||||||
|
ItemStack paymentPlaceholder = new ItemStack(trade.priceItem(), trade.priceQuantity());
|
||||||
|
event.getInventory().setItem(slot, paymentPlaceholder);
|
||||||
|
|
||||||
|
activeSessions.put(player, new ShopSession(shop, session.unitsTraded() + 1, session.realInventory()));
|
||||||
|
} else {
|
||||||
|
player.sendMessage(Component.text("shop is out of stock!", NamedTextColor.RED));
|
||||||
|
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1.0f, 1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Case 2: Clicked a Payment (to REFUND)
|
||||||
|
else if (clicked.getType() == trade.priceItem()) {
|
||||||
|
// 1. Check if player has product to return
|
||||||
|
if (!tm.hasItems(player.getInventory(), trade.productItem(), trade.productQuantity())) {
|
||||||
|
player.sendMessage(Component.text("you don't have the product to return!", NamedTextColor.RED));
|
||||||
|
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1.0f, 1.0f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check if chest has space for product
|
||||||
|
if (!tm.hasSpace(session.realInventory(), trade.productItem(), trade.productQuantity())) {
|
||||||
|
player.sendMessage(Component.text("the shop chest is full! cannot return!", NamedTextColor.RED));
|
||||||
|
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1.0f, 1.0f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Return product from Player -> Chest (NBT PRESERVED)
|
||||||
|
ItemStack[] products = tm.removeItemsAndReturn(player.getInventory(), trade.productItem(),
|
||||||
|
trade.productQuantity());
|
||||||
|
session.realInventory().addItem(products);
|
||||||
|
|
||||||
|
// 4. Return payment to Player
|
||||||
|
tm.removeItems(session.realInventory(), trade.priceItem(), trade.priceQuantity()); // Not used currently but
|
||||||
|
// for completeness
|
||||||
|
player.getInventory().addItem(tm.createItemStacks(trade.priceItem(), trade.priceQuantity()));
|
||||||
|
|
||||||
|
// Success refund!
|
||||||
|
player.playSound(player.getLocation(), Sound.BLOCK_CHEST_CLOSE, 1.0f, 1.2f);
|
||||||
|
|
||||||
|
// Restore Product to GUI slot (with NBT)
|
||||||
|
ItemStack productPreview = tm.getRepresentativeItem(session.realInventory(), trade.productItem(),
|
||||||
|
trade.productQuantity());
|
||||||
|
event.getInventory().setItem(slot, productPreview);
|
||||||
|
|
||||||
|
activeSessions.put(player, new ShopSession(shop, session.unitsTraded() - 1, session.realInventory()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onInventoryClose(InventoryCloseEvent event) {
|
public void onInventoryClose(InventoryCloseEvent event) {
|
||||||
if (event.getPlayer() instanceof Player player) {
|
if (!(event.getPlayer() instanceof Player player)) {
|
||||||
// stop disc when closing shop
|
return;
|
||||||
Shop shop = viewingShop.remove(player);
|
}
|
||||||
if (shop != null) {
|
|
||||||
|
ShopSession session = activeSessions.remove(player);
|
||||||
|
if (session == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Shop shop = session.shop();
|
||||||
|
|
||||||
|
// Finalize transaction in database if any units were traded
|
||||||
|
if (session.unitsTraded() > 0) {
|
||||||
|
try {
|
||||||
|
int totalPaid = session.unitsTraded() * shop.getTrade().priceQuantity();
|
||||||
|
plugin.getShopRepository().updateOwedAmount(shop.getId(), shop.getOwedAmount() + totalPaid);
|
||||||
|
shop.setOwedAmount(shop.getOwedAmount() + totalPaid);
|
||||||
|
|
||||||
|
plugin.getTransactionRepository().recordTransaction(
|
||||||
|
shop.getId(),
|
||||||
|
player.getUniqueId(),
|
||||||
|
session.unitsTraded());
|
||||||
|
|
||||||
|
player.sendMessage(Component.text("transaction finalized!", NamedTextColor.GREEN));
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
player.sendMessage(Component.text("error saving transaction to database", NamedTextColor.RED));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop disc
|
||||||
String discName = shop.getDisc();
|
String discName = shop.getDisc();
|
||||||
if (discName != null && !discName.isEmpty()) {
|
if (discName != null && !discName.isEmpty()) {
|
||||||
stopDisc(player, discName);
|
stopDisc(player, discName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* open confirmation GUI
|
|
||||||
*/
|
|
||||||
private void openConfirmationGui(Player player, Shop shop, int units) {
|
|
||||||
ConfirmationGui gui = new ConfirmationGui(shop, units);
|
|
||||||
player.openInventory(gui.getInventory());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* execute the transaction
|
|
||||||
*/
|
|
||||||
private void executeTransaction(Player player, Shop shop, int units) {
|
|
||||||
TransactionManager.Result result = plugin.getTransactionManager().execute(player, shop, units);
|
|
||||||
|
|
||||||
switch (result) {
|
|
||||||
case SUCCESS -> player.sendMessage(Component.text("purchase complete!", NamedTextColor.GREEN));
|
|
||||||
case INSUFFICIENT_FUNDS ->
|
|
||||||
player.sendMessage(Component.text("you don't have enough items to pay", NamedTextColor.RED));
|
|
||||||
case INSUFFICIENT_STOCK -> player.sendMessage(Component.text("shop is out of stock", NamedTextColor.RED));
|
|
||||||
case INVENTORY_FULL -> player.sendMessage(Component.text("your inventory is full", NamedTextColor.RED));
|
|
||||||
case DATABASE_ERROR ->
|
|
||||||
player.sendMessage(Component.text("transaction failed - database error", NamedTextColor.RED));
|
|
||||||
case SHOP_DISABLED -> player.sendMessage(Component.text("this shop is disabled", NamedTextColor.RED));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* find shop attached to a container block (handles double chests)
|
* find shop attached to a container block (handles double chests)
|
||||||
|
|||||||
@@ -39,8 +39,7 @@ public class LoginListener implements Listener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
// find all shops owned by player and check stock on main thread
|
||||||
// find all shops owned by player
|
|
||||||
List<Shop> lowStockShops = new ArrayList<>();
|
List<Shop> lowStockShops = new ArrayList<>();
|
||||||
for (Shop shop : plugin.getShopRegistry().getAllShops()) {
|
for (Shop shop : plugin.getShopRegistry().getAllShops()) {
|
||||||
if (shop.getOwner().equals(player.getUniqueId())) {
|
if (shop.getOwner().equals(player.getUniqueId())) {
|
||||||
@@ -51,16 +50,10 @@ public class LoginListener implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!lowStockShops.isEmpty()) {
|
if (!lowStockShops.isEmpty()) {
|
||||||
final int count = lowStockShops.size();
|
|
||||||
plugin.getServer().getScheduler().runTask(plugin, () -> {
|
|
||||||
if (player.isOnline()) {
|
|
||||||
player.sendMessage(
|
player.sendMessage(
|
||||||
Component.text("notification: " + count + " of your shops are low on stock.",
|
Component.text("notification: " + lowStockShops.size() + " of your shops are low on stock.",
|
||||||
NamedTextColor.YELLOW));
|
NamedTextColor.YELLOW));
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isLowStock(Shop shop) {
|
private boolean isLowStock(Shop shop) {
|
||||||
|
|||||||
@@ -92,9 +92,9 @@ public class ShopActivationListener implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. session-based ownership check
|
// 2. session-based ownership check
|
||||||
if (!PermissionManager.isAdmin(player)) {
|
if (plugin.getConfigManager().isRequirePlacement() && !PermissionManager.isAdmin(player)) {
|
||||||
UUID sessionOwner = plugin.getContainerMemoryManager().getOwner(attachedBlock.getLocation());
|
UUID sessionOwner = plugin.getContainerMemoryManager().getOwner(attachedBlock.getLocation());
|
||||||
if (sessionOwner == null) {
|
if (sessionOwner == null || !sessionOwner.equals(player.getUniqueId())) {
|
||||||
// block was placed before the server started or by no one
|
// block was placed before the server started or by no one
|
||||||
player.sendMessage(Component.text("you can only create shops on containers you placed this session",
|
player.sendMessage(Component.text("you can only create shops on containers you placed this session",
|
||||||
NamedTextColor.RED));
|
NamedTextColor.RED));
|
||||||
@@ -127,7 +127,7 @@ public class ShopActivationListener implements Listener {
|
|||||||
|
|
||||||
// clear sign text to avoid "setup" staying on the sign
|
// clear sign text to avoid "setup" staying on the sign
|
||||||
event.line(0, Component.text(""));
|
event.line(0, Component.text(""));
|
||||||
SetupDialog.open(player, block, plugin);
|
SetupDialog.open(player, block, plugin, "shop-" + (plugin.getShopRegistry().getAllShops().size() + 1));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +158,16 @@ public class ShopActivationListener implements Listener {
|
|||||||
private void sendConfirmationMessage(Player player, Trade trade) {
|
private void sendConfirmationMessage(Player player, Trade trade) {
|
||||||
String priceText = trade.priceQuantity() + " " + formatMaterial(trade.priceItem());
|
String priceText = trade.priceQuantity() + " " + formatMaterial(trade.priceItem());
|
||||||
String productText = trade.productQuantity() + " " + formatMaterial(trade.productItem());
|
String productText = trade.productQuantity() + " " + formatMaterial(trade.productItem());
|
||||||
|
String shopName = "shop-" + (plugin.getShopRegistry().getAllShops().size() + 1);
|
||||||
|
|
||||||
|
// check uniqueness for default name (player scoped)
|
||||||
|
if (plugin.getShopRegistry().getShopByOwnerAndName(player.getUniqueId(), shopName) != null) {
|
||||||
|
int i = 2;
|
||||||
|
while (plugin.getShopRegistry().getShopByOwnerAndName(player.getUniqueId(), shopName + "-" + i) != null) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
shopName = shopName + "-" + i;
|
||||||
|
}
|
||||||
|
|
||||||
// clear display: buyer pays vs buyer gets
|
// clear display: buyer pays vs buyer gets
|
||||||
player.sendMessage(Component.text("shop detected!", NamedTextColor.GOLD));
|
player.sendMessage(Component.text("shop detected!", NamedTextColor.GOLD));
|
||||||
@@ -170,17 +180,17 @@ public class ShopActivationListener implements Listener {
|
|||||||
.append(Component.text("[accept]", NamedTextColor.GREEN, TextDecoration.BOLD)
|
.append(Component.text("[accept]", NamedTextColor.GREEN, TextDecoration.BOLD)
|
||||||
.hoverEvent(HoverEvent.showText(
|
.hoverEvent(HoverEvent.showText(
|
||||||
Component.text("create shop exactly as shown above", NamedTextColor.WHITE)))
|
Component.text("create shop exactly as shown above", NamedTextColor.WHITE)))
|
||||||
.clickEvent(ClickEvent.runCommand("/oyes _activate accept")))
|
.clickEvent(ClickEvent.runCommand("/oyes _activate accept " + shopName)))
|
||||||
.append(Component.text(" "))
|
.append(Component.text(" "))
|
||||||
.append(Component.text("[invert]", NamedTextColor.GOLD, TextDecoration.BOLD)
|
.append(Component.text("[invert]", NamedTextColor.GOLD, TextDecoration.BOLD)
|
||||||
.hoverEvent(HoverEvent.showText(Component.text(
|
.hoverEvent(HoverEvent.showText(Component.text(
|
||||||
"swap: buyer pays " + productText + " and gets " + priceText, NamedTextColor.WHITE)))
|
"swap: buyer pays " + productText + " and gets " + priceText, NamedTextColor.WHITE)))
|
||||||
.clickEvent(ClickEvent.runCommand("/oyes _activate invert")))
|
.clickEvent(ClickEvent.runCommand("/oyes _activate invert " + shopName)))
|
||||||
.append(Component.text(" "))
|
.append(Component.text(" "))
|
||||||
.append(Component.text("[cancel]", NamedTextColor.RED, TextDecoration.BOLD)
|
.append(Component.text("[cancel]", NamedTextColor.RED, TextDecoration.BOLD)
|
||||||
.hoverEvent(HoverEvent.showText(
|
.hoverEvent(HoverEvent.showText(
|
||||||
Component.text("ignore sign. no shop created.", NamedTextColor.WHITE)))
|
Component.text("ignore sign. no shop created.", NamedTextColor.WHITE)))
|
||||||
.clickEvent(ClickEvent.runCommand("/oyes _activate cancel")));
|
.clickEvent(ClickEvent.runCommand("/oyes _activate cancel " + shopName)));
|
||||||
|
|
||||||
player.sendMessage(buttons);
|
player.sendMessage(buttons);
|
||||||
}
|
}
|
||||||
@@ -189,7 +199,7 @@ public class ShopActivationListener implements Listener {
|
|||||||
* complete the shop creation process
|
* complete the shop creation process
|
||||||
* called from AdminCommands when user clicks [accept] or [invert]
|
* called from AdminCommands when user clicks [accept] or [invert]
|
||||||
*/
|
*/
|
||||||
public void finalizeShop(Player player, PendingActivation activation) {
|
public void finalizeShop(Player player, PendingActivation activation, String shopName) {
|
||||||
Location signLocation = activation.location();
|
Location signLocation = activation.location();
|
||||||
Block block = signLocation.getBlock();
|
Block block = signLocation.getBlock();
|
||||||
|
|
||||||
@@ -201,8 +211,9 @@ public class ShopActivationListener implements Listener {
|
|||||||
|
|
||||||
Trade trade = activation.trade();
|
Trade trade = activation.trade();
|
||||||
long createdAt = System.currentTimeMillis();
|
long createdAt = System.currentTimeMillis();
|
||||||
Shop shop = new Shop(-1, signLocation, player.getUniqueId(), trade, 0, true, createdAt, null,
|
// default cosmeticSign to true as requested
|
||||||
activation.cosmeticSign(), null);
|
Shop shop = new Shop(-1, shopName, signLocation, player.getUniqueId(), new ArrayList<>(), trade, 0, true,
|
||||||
|
createdAt, null, true, null);
|
||||||
|
|
||||||
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
try {
|
try {
|
||||||
@@ -210,22 +221,24 @@ public class ShopActivationListener implements Listener {
|
|||||||
plugin.getShopRepository().deleteShopByLocation(signLocation);
|
plugin.getShopRepository().deleteShopByLocation(signLocation);
|
||||||
|
|
||||||
int shopId = plugin.getShopRepository().createShop(shop);
|
int shopId = plugin.getShopRepository().createShop(shop);
|
||||||
plugin.getLogger().info("DEBUG: created shop id " + shopId + " at " + signLocation);
|
plugin.getLogger().info("DEBUG: created shop id " + shopId + " (" + shopName + ") at " + signLocation);
|
||||||
|
|
||||||
plugin.getServer().getScheduler().runTask(plugin, () -> {
|
plugin.getServer().getScheduler().runTask(plugin, () -> {
|
||||||
// re-verify sign on main thread
|
// re-verify sign on main thread
|
||||||
if (!(signLocation.getBlock().getState() instanceof Sign finalSign)) {
|
if (!(signLocation.getBlock().getState() instanceof Sign)) {
|
||||||
plugin.getLogger().info("DEBUG: sign missing at " + signLocation);
|
plugin.getLogger().info("DEBUG: sign missing at " + signLocation);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Shop registeredShop = new Shop(shopId, signLocation, player.getUniqueId(), trade, 0, true,
|
Shop registeredShop = new Shop(shopId, shopName, signLocation, player.getUniqueId(),
|
||||||
createdAt, null, activation.cosmeticSign(), null);
|
new ArrayList<>(), trade, 0, true, createdAt, null, true, null);
|
||||||
plugin.getShopRegistry().register(registeredShop);
|
plugin.getShopRegistry().register(registeredShop);
|
||||||
plugin.getLogger().info("DEBUG: registered shop " + shopId + " in registry");
|
plugin.getLogger().info("DEBUG: registered shop " + shopId + " in registry");
|
||||||
|
|
||||||
rewriteSignLines(finalSign, registeredShop);
|
// rewriteSignLines(finalSign, registeredShop); // REMOVED: Sign should just be
|
||||||
player.sendMessage(Component.text("shop #" + shopId + " initialized!", NamedTextColor.GREEN));
|
// left alone
|
||||||
|
player.sendMessage(Component.text("shop '" + shopName + "' (#" + shopId + ") initialized!",
|
||||||
|
NamedTextColor.GREEN));
|
||||||
});
|
});
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
plugin.getServer().getScheduler().runTask(plugin, () -> {
|
plugin.getServer().getScheduler().runTask(plugin, () -> {
|
||||||
@@ -237,20 +250,7 @@ public class ShopActivationListener implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void rewriteSignLines(Sign sign, Shop shop) {
|
public void rewriteSignLines(Sign sign, Shop shop) {
|
||||||
if (shop.isCosmeticSign()) {
|
// REMOVED: Sign should just be left alone as requested
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Trade trade = shop.getTrade();
|
|
||||||
String pricePart = trade.priceQuantity() + " " + abbreviateMaterial(trade.priceItem());
|
|
||||||
|
|
||||||
String productPart = trade.productQuantity() + " " + abbreviateMaterial(trade.productItem());
|
|
||||||
|
|
||||||
sign.line(0, Component.text(pricePart));
|
|
||||||
sign.line(1, Component.text("for."));
|
|
||||||
sign.line(2, Component.text(productPart));
|
|
||||||
sign.line(3, Component.text(""));
|
|
||||||
sign.update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -355,15 +355,6 @@ public class ShopActivationListener implements Listener {
|
|||||||
return bestQuantity;
|
return bestQuantity;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String abbreviateMaterial(Material material) {
|
|
||||||
String name = material.name().toLowerCase().replace("_", " ");
|
|
||||||
name = name.replace("diamond", "dia").replace("emerald", "em").replace("netherite", "neth")
|
|
||||||
.replace("ingot", "").replace("block", "blk").replace("pickaxe", "pick")
|
|
||||||
.replace("chestplate", "chest").replace("leggings", "legs");
|
|
||||||
name = name.trim();
|
|
||||||
return name.length() > 14 ? name.substring(0, 14) : name;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Inventory getContainerInventory(Block block) {
|
private Inventory getContainerInventory(Block block) {
|
||||||
if (block.getState() instanceof Chest chest)
|
if (block.getState() instanceof Chest chest)
|
||||||
return chest.getInventory();
|
return chest.getInventory();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package party.cybsec.oyeshops.model;
|
|||||||
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -9,8 +10,10 @@ import java.util.UUID;
|
|||||||
*/
|
*/
|
||||||
public class Shop {
|
public class Shop {
|
||||||
private final int id;
|
private final int id;
|
||||||
|
private String name;
|
||||||
private final Location signLocation;
|
private final Location signLocation;
|
||||||
private final UUID owner;
|
private final UUID owner;
|
||||||
|
private final List<UUID> contributors;
|
||||||
private final Trade trade;
|
private final Trade trade;
|
||||||
private int owedAmount;
|
private int owedAmount;
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
@@ -19,11 +22,13 @@ public class Shop {
|
|||||||
private boolean cosmeticSign;
|
private boolean cosmeticSign;
|
||||||
private String disc;
|
private String disc;
|
||||||
|
|
||||||
public Shop(int id, Location signLocation, UUID owner, Trade trade, int owedAmount, boolean enabled,
|
public Shop(int id, String name, Location signLocation, UUID owner, List<UUID> contributors, Trade trade,
|
||||||
long createdAt, String customTitle, boolean cosmeticSign, String disc) {
|
int owedAmount, boolean enabled, long createdAt, String customTitle, boolean cosmeticSign, String disc) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
this.signLocation = signLocation;
|
this.signLocation = signLocation;
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
|
this.contributors = contributors;
|
||||||
this.trade = trade;
|
this.trade = trade;
|
||||||
this.owedAmount = owedAmount;
|
this.owedAmount = owedAmount;
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
@@ -33,6 +38,18 @@ public class Shop {
|
|||||||
this.disc = disc;
|
this.disc = disc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<UUID> getContributors() {
|
||||||
|
return contributors;
|
||||||
|
}
|
||||||
|
|
||||||
public int getId() {
|
public int getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -97,8 +114,6 @@ public class Shop {
|
|||||||
* get chest location from sign location
|
* get chest location from sign location
|
||||||
*/
|
*/
|
||||||
public Location getChestLocation() {
|
public Location getChestLocation() {
|
||||||
// chest is attached to the sign
|
return signLocation.clone();
|
||||||
// we'll determine this from the sign's attached block face
|
|
||||||
return signLocation.clone(); // placeholder - will be properly implemented
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,34 @@ public class ShopRegistry {
|
|||||||
return shopsById.get(shopId);
|
return shopsById.get(shopId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get shop by name (first match)
|
||||||
|
*/
|
||||||
|
public Shop getShopByName(String name) {
|
||||||
|
if (name == null)
|
||||||
|
return null;
|
||||||
|
for (Shop shop : shopsById.values()) {
|
||||||
|
if (name.equalsIgnoreCase(shop.getName())) {
|
||||||
|
return shop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get shop by owner and name
|
||||||
|
*/
|
||||||
|
public Shop getShopByOwnerAndName(java.util.UUID owner, String name) {
|
||||||
|
if (name == null || owner == null)
|
||||||
|
return null;
|
||||||
|
for (Shop shop : shopsById.values()) {
|
||||||
|
if (owner.equals(shop.getOwner()) && name.equalsIgnoreCase(shop.getName())) {
|
||||||
|
return shop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get all shops
|
* get all shops
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -33,7 +33,41 @@ public class TransactionManager {
|
|||||||
INSUFFICIENT_STOCK,
|
INSUFFICIENT_STOCK,
|
||||||
INVENTORY_FULL,
|
INVENTORY_FULL,
|
||||||
DATABASE_ERROR,
|
DATABASE_ERROR,
|
||||||
SHOP_DISABLED
|
SHOP_DISABLED,
|
||||||
|
INVALID_AMOUNT
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check if inventory has enough space for a material and amount
|
||||||
|
*/
|
||||||
|
public boolean hasSpace(Inventory inventory, Material material, int amount) {
|
||||||
|
int space = 0;
|
||||||
|
int maxStack = material.getMaxStackSize();
|
||||||
|
|
||||||
|
for (ItemStack item : inventory.getStorageContents()) {
|
||||||
|
if (item == null || item.getType() == Material.AIR) {
|
||||||
|
space += maxStack;
|
||||||
|
} else if (item.getType() == material) {
|
||||||
|
space += (maxStack - item.getAmount());
|
||||||
|
}
|
||||||
|
if (space >= amount)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return space >= amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* transfers items from one inventory to another atomically
|
||||||
|
* returns true if successful
|
||||||
|
*/
|
||||||
|
public boolean transferItems(Inventory from, Inventory to, Material material, int amount) {
|
||||||
|
if (!hasItems(from, material, amount) || !hasSpace(to, material, amount)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack[] items = removeItemsAndReturn(from, material, amount);
|
||||||
|
to.addItem(items);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,15 +114,15 @@ public class TransactionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// remove price items from buyer
|
// remove price items from buyer (NBT preserved if needed, though usually
|
||||||
removeItems(buyer.getInventory(), trade.priceItem(), totalPrice);
|
// currency)
|
||||||
|
removeItemsAndReturn(buyer.getInventory(), trade.priceItem(), totalPrice);
|
||||||
|
|
||||||
// remove product items from chest
|
// remove product items from chest (NBT CRITICAL HERE)
|
||||||
removeItems(chestInventory, trade.productItem(), totalProduct);
|
ItemStack[] productItems = removeItemsAndReturn(chestInventory, trade.productItem(), totalProduct);
|
||||||
|
|
||||||
// add product items to buyer
|
// add product items to buyer
|
||||||
HashMap<Integer, ItemStack> overflow = buyer.getInventory().addItem(
|
HashMap<Integer, ItemStack> overflow = buyer.getInventory().addItem(productItems);
|
||||||
createItemStacks(trade.productItem(), totalProduct));
|
|
||||||
|
|
||||||
if (!overflow.isEmpty()) {
|
if (!overflow.isEmpty()) {
|
||||||
// rollback
|
// rollback
|
||||||
@@ -130,7 +164,7 @@ public class TransactionManager {
|
|||||||
/**
|
/**
|
||||||
* check if inventory has required items
|
* check if inventory has required items
|
||||||
*/
|
*/
|
||||||
private boolean hasItems(Inventory inventory, Material material, int amount) {
|
public boolean hasItems(Inventory inventory, Material material, int amount) {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (ItemStack item : inventory.getContents()) {
|
for (ItemStack item : inventory.getContents()) {
|
||||||
if (item != null && item.getType() == material) {
|
if (item != null && item.getType() == material) {
|
||||||
@@ -144,15 +178,20 @@ public class TransactionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* remove items from inventory
|
* remove items from inventory and return them (preserving NBT)
|
||||||
*/
|
*/
|
||||||
private void removeItems(Inventory inventory, Material material, int amount) {
|
public ItemStack[] removeItemsAndReturn(Inventory inventory, Material material, int amount) {
|
||||||
int remaining = amount;
|
int remaining = amount;
|
||||||
|
java.util.List<ItemStack> removed = new java.util.ArrayList<>();
|
||||||
|
|
||||||
for (int i = 0; i < inventory.getSize() && remaining > 0; i++) {
|
for (int i = 0; i < inventory.getSize() && remaining > 0; i++) {
|
||||||
ItemStack item = inventory.getItem(i);
|
ItemStack item = inventory.getItem(i);
|
||||||
if (item != null && item.getType() == material) {
|
if (item != null && item.getType() == material) {
|
||||||
int toRemove = Math.min(item.getAmount(), remaining);
|
int toRemove = Math.min(item.getAmount(), remaining);
|
||||||
|
ItemStack clone = item.clone();
|
||||||
|
clone.setAmount(toRemove);
|
||||||
|
removed.add(clone);
|
||||||
|
|
||||||
if (toRemove == item.getAmount()) {
|
if (toRemove == item.getAmount()) {
|
||||||
inventory.setItem(i, null);
|
inventory.setItem(i, null);
|
||||||
} else {
|
} else {
|
||||||
@@ -161,12 +200,32 @@ public class TransactionManager {
|
|||||||
remaining -= toRemove;
|
remaining -= toRemove;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return removed.toArray(new ItemStack[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create item stacks for given total amount
|
* remove items from inventory
|
||||||
*/
|
*/
|
||||||
private ItemStack[] createItemStacks(Material material, int totalAmount) {
|
public void removeItems(Inventory inventory, Material material, int amount) {
|
||||||
|
removeItemsAndReturn(inventory, material, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get a sample item from inventory for display (preserving NBT)
|
||||||
|
*/
|
||||||
|
public ItemStack getRepresentativeItem(Inventory inventory, Material material, int amount) {
|
||||||
|
for (ItemStack item : inventory.getContents()) {
|
||||||
|
if (item != null && item.getType() == material) {
|
||||||
|
ItemStack preview = item.clone();
|
||||||
|
preview.setAmount(amount);
|
||||||
|
return preview;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fallback to blank material if not found
|
||||||
|
return new ItemStack(material, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemStack[] createItemStacks(Material material, int totalAmount) {
|
||||||
int maxStack = material.getMaxStackSize();
|
int maxStack = material.getMaxStackSize();
|
||||||
int fullStacks = totalAmount / maxStack;
|
int fullStacks = totalAmount / maxStack;
|
||||||
int remainder = totalAmount % maxStack;
|
int remainder = totalAmount % maxStack;
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ transactions:
|
|||||||
auto-prune: false
|
auto-prune: false
|
||||||
max-per-shop: 100
|
max-per-shop: 100
|
||||||
|
|
||||||
|
# whether to require players to have placed the container themselves this session
|
||||||
|
require-placement: true
|
||||||
|
|
||||||
# custom material aliases (add your own shortcuts)
|
# custom material aliases (add your own shortcuts)
|
||||||
aliases:
|
aliases:
|
||||||
# example:
|
# example:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: oyeShops
|
name: oyeShops
|
||||||
version: 1.3.0
|
version: 1.3.1
|
||||||
main: party.cybsec.oyeshops.OyeShopsPlugin
|
main: party.cybsec.oyeshops.OyeShopsPlugin
|
||||||
api-version: '1.21'
|
api-version: '1.21'
|
||||||
description: deterministic item-for-item chest barter
|
description: deterministic item-for-item chest barter
|
||||||
@@ -9,7 +9,7 @@ website: https://party.cybsec
|
|||||||
commands:
|
commands:
|
||||||
oyeshops:
|
oyeshops:
|
||||||
description: oyeShops commands
|
description: oyeShops commands
|
||||||
usage: /oyeshops <on|off|toggle|reload|inspect|spoof|enable|disable|unregister>
|
usage: /oyeshops <on|off|toggle|reload|setup|config|tpshop|toggle-placement>
|
||||||
aliases: [oyes, oshop]
|
aliases: [oyes, oshop]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
|||||||
Reference in New Issue
Block a user