Skip to content

Commit 721aa90

Browse files
author
Sayan
committed
Fix PvP, mob protections, events, add papi placeholders, buy/sell commands, and update docs
1 parent 3af7601 commit 721aa90

17 files changed

Lines changed: 621 additions & 11 deletions

docs/guide/commands.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ All commands use `/claim` (alias: `/c`) as the base.
2121
| `/claim abandon` | Delete your entire active claim profile and all its chunks |
2222
| `/claim unclaimall` | Unclaim all chunks and delete your active profile (requires `/claim unclaimall confirm`) |
2323
| `/claim pvp <on/off> [time]` | Toggle PvP in the claim, with optional duration in seconds |
24+
| `/claim buy claim [amount]` | Buy extra claim blocks |
25+
| `/claim buy role` | Buy an additional custom role slot for your active profile |
26+
| `/claim buy member` | Buy an additional member slot for your active profile |
27+
| `/claim buy warp` | Buy an additional warp slot for your active profile |
28+
| `/claim sell <profile> <price>` | List a claim profile on the server-wide marketplace |
2429
| `/unclaim` | Unclaim the chunk you're standing in |
2530
| `/unclaim all` | Unclaim all chunks belonging to your active profile |
2631

docs/guide/placeholders.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,26 @@ These return data about the player's own profile and limits.
7171
All internal placeholders support both bracket styles interchangeably in configuration files:
7272
* `<owner>` is the same as `{owner}`
7373
* `<limit>` is the same as `{limit}`
74+
75+
---
76+
77+
## 3. Claim Limits, Slots and Economy (Global)
78+
79+
These placeholders return data about claim limits, custom roles, members, warps, and the cost of buying additional slots.
80+
81+
**Identifier:** `claimplugin`
82+
83+
| Placeholder | Description |
84+
| :--- | :--- |
85+
| `%claimplugin_claims_current%` | Number of chunks claimed by the player |
86+
| `%claimplugin_claims_max%` | Maximum chunk limit for the player |
87+
| `%claimplugin_cost_next_claim%` | Cost of the next chunk |
88+
| `%claimplugin_roles_current%` | Number of custom roles in active profile |
89+
| `%claimplugin_roles_max%` | Maximum custom roles limit for active profile |
90+
| `%claimplugin_cost_next_role_slot%` | Cost to buy an additional custom role slot |
91+
| `%claimplugin_members_current%` | Number of members in active profile |
92+
| `%claimplugin_members_max%` | Maximum members limit for active profile |
93+
| `%claimplugin_cost_next_member_slot%` | Cost to buy an additional member slot |
94+
| `%claimplugin_warps_current%` | Number of warps in active profile |
95+
| `%claimplugin_warps_max%` | Maximum warps limit for active profile |
96+
| `%claimplugin_cost_next_warp_slot%` | Cost to buy an additional warp slot |
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package org.ayosynk.landClaimPlugin.commands;
2+
3+
import org.ayosynk.landClaimPlugin.LandClaimPlugin;
4+
import org.ayosynk.landClaimPlugin.managers.ClaimManager;
5+
import org.ayosynk.landClaimPlugin.managers.ConfigManager;
6+
import org.ayosynk.landClaimPlugin.models.ClaimProfile;
7+
import org.bukkit.Bukkit;
8+
import org.bukkit.entity.Player;
9+
import org.bukkit.plugin.Plugin;
10+
import org.incendo.cloud.Command;
11+
import org.incendo.cloud.paper.PaperCommandManager;
12+
import org.incendo.cloud.paper.util.sender.PlayerSource;
13+
import org.incendo.cloud.paper.util.sender.Source;
14+
15+
import java.lang.reflect.Method;
16+
17+
public class BuyCommand implements LandClaimCommand {
18+
19+
private final LandClaimPlugin plugin;
20+
private final ClaimManager claimManager;
21+
private final ConfigManager configManager;
22+
23+
public BuyCommand(LandClaimPlugin plugin, ClaimManager claimManager, ConfigManager configManager) {
24+
this.plugin = plugin;
25+
this.claimManager = claimManager;
26+
this.configManager = configManager;
27+
}
28+
29+
@Override
30+
public void register(PaperCommandManager<Source> manager, Command.Builder<PlayerSource> claimBuilder) {
31+
Command.Builder<PlayerSource> buyBuilder = claimBuilder.literal("buy");
32+
33+
// /claim buy claim [amount]
34+
manager.command(buyBuilder.literal("claim")
35+
.optional("amount", org.incendo.cloud.parser.standard.IntegerParser.integerParser(1))
36+
.handler(context -> {
37+
Player player = context.sender().source();
38+
int amount = context.getOrDefault("amount", 1);
39+
if (amount <= 0) {
40+
player.sendMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage()
41+
.deserialize("<red>Amount must be greater than 0."));
42+
return;
43+
}
44+
double costPer = getCost("claimBlockCost", 50.0);
45+
double totalCost = costPer * amount;
46+
if (!chargePlayer(player, totalCost, "BUY_CLAIM_BLOCKS")) {
47+
return;
48+
}
49+
50+
plugin.addBonusBlocks(player.getUniqueId(), amount).thenAccept(newTotal -> {
51+
player.sendMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage()
52+
.deserialize("<green>Successfully purchased <gold>" + amount + "</gold> claim blocks! New limit: <gold>" + plugin.getClaimManager().getClaimLimit(player) + "</gold>"));
53+
});
54+
}));
55+
56+
// /claim buy role
57+
manager.command(buyBuilder.literal("role")
58+
.handler(context -> {
59+
Player player = context.sender().source();
60+
ClaimProfile profile = claimManager.getActiveProfile(player);
61+
if (profile == null) {
62+
player.sendMessage(configManager.getMessage("no-profile"));
63+
return;
64+
}
65+
if (!profile.isOwner(player.getUniqueId())) {
66+
player.sendMessage(configManager.getMessage("not-owner"));
67+
return;
68+
}
69+
double cost = getCost("roleSlotCost", 150.0);
70+
if (!chargePlayer(player, cost, "BUY_ROLE_SLOT")) {
71+
return;
72+
}
73+
profile.setBonusRoleSlots(profile.getBonusRoleSlots() + 1);
74+
claimManager.saveAndSync(profile);
75+
player.sendMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage()
76+
.deserialize("<green>Successfully purchased an additional custom role slot! Total bonus slots: <gold>" + profile.getBonusRoleSlots() + "</gold>"));
77+
}));
78+
79+
// /claim buy member
80+
manager.command(buyBuilder.literal("member")
81+
.handler(context -> {
82+
Player player = context.sender().source();
83+
ClaimProfile profile = claimManager.getActiveProfile(player);
84+
if (profile == null) {
85+
player.sendMessage(configManager.getMessage("no-profile"));
86+
return;
87+
}
88+
if (!profile.isOwner(player.getUniqueId())) {
89+
player.sendMessage(configManager.getMessage("not-owner"));
90+
return;
91+
}
92+
double cost = getCost("memberSlotCost", 50.0);
93+
if (!chargePlayer(player, cost, "BUY_MEMBER_SLOT")) {
94+
return;
95+
}
96+
profile.setBonusMemberSlots(profile.getBonusMemberSlots() + 1);
97+
claimManager.saveAndSync(profile);
98+
player.sendMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage()
99+
.deserialize("<green>Successfully purchased an additional member slot! Total bonus slots: <gold>" + profile.getBonusMemberSlots() + "</gold>"));
100+
}));
101+
102+
// /claim buy warp
103+
manager.command(buyBuilder.literal("warp")
104+
.handler(context -> {
105+
Player player = context.sender().source();
106+
ClaimProfile profile = claimManager.getActiveProfile(player);
107+
if (profile == null) {
108+
player.sendMessage(configManager.getMessage("no-profile"));
109+
return;
110+
}
111+
if (!profile.isOwner(player.getUniqueId())) {
112+
player.sendMessage(configManager.getMessage("not-owner"));
113+
return;
114+
}
115+
double cost = getCost("warpSlotCost", 75.0);
116+
if (!chargePlayer(player, cost, "BUY_WARP_SLOT")) {
117+
return;
118+
}
119+
profile.setBonusWarpSlots(profile.getBonusWarpSlots() + 1);
120+
claimManager.saveAndSync(profile);
121+
player.sendMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage()
122+
.deserialize("<green>Successfully purchased an additional warp slot! Total bonus slots: <gold>" + profile.getBonusWarpSlots() + "</gold>"));
123+
}));
124+
}
125+
126+
private double getCost(String fieldName, double defaultValue) {
127+
try {
128+
Plugin ecoPlugin = Bukkit.getPluginManager().getPlugin("LandClaimPlugin-Economy");
129+
if (ecoPlugin != null && ecoPlugin.isEnabled()) {
130+
Object config = ecoPlugin.getClass().getMethod("getEconomyConfig").invoke(ecoPlugin);
131+
return (double) config.getClass().getField(fieldName).get(config);
132+
}
133+
} catch (Exception ignored) {}
134+
return defaultValue;
135+
}
136+
137+
private boolean chargePlayer(Player player, double cost, String actionType) {
138+
try {
139+
Plugin ecoPlugin = Bukkit.getPluginManager().getPlugin("LandClaimPlugin-Economy");
140+
if (ecoPlugin == null || !ecoPlugin.isEnabled()) {
141+
player.sendMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage()
142+
.deserialize("<red>Economy features are disabled."));
143+
return false;
144+
}
145+
Class<?> ecoHookClass = Class.forName("org.ayosynk.landclaimeconomy.util.EconomyHook");
146+
Method formatMethod = ecoHookClass.getMethod("format", double.class);
147+
148+
// Check balance
149+
double balance = (double) ecoHookClass.getMethod("getBalance", org.bukkit.OfflinePlayer.class).invoke(null, player);
150+
if (balance < cost) {
151+
String costStr = (String) formatMethod.invoke(null, cost);
152+
String balStr = (String) formatMethod.invoke(null, balance);
153+
player.sendMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage()
154+
.deserialize("<red>Insufficient funds! Cost: " + costStr + ", Balance: " + balStr));
155+
return false;
156+
}
157+
158+
// Withdraw
159+
boolean success = (boolean) ecoHookClass.getMethod("withdraw", org.bukkit.OfflinePlayer.class, double.class).invoke(null, player, cost);
160+
if (!success) {
161+
player.sendMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage()
162+
.deserialize("<red>Transaction failed."));
163+
return false;
164+
}
165+
166+
// Log transaction in database
167+
Object database = ecoPlugin.getClass().getMethod("getDatabase").invoke(ecoPlugin);
168+
Method logMethod = database.getClass().getMethod("logTransaction", String.class, String.class, String.class, double.class, String.class);
169+
logMethod.invoke(database, player.getUniqueId().toString(), null, actionType, cost, null);
170+
171+
return true;
172+
} catch (Exception e) {
173+
plugin.getLogger().severe("Error charging player: " + e.getMessage());
174+
e.printStackTrace();
175+
player.sendMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage()
176+
.deserialize("<red>An error occurred while processing the purchase."));
177+
return false;
178+
}
179+
}
180+
}

src/main/java/org/ayosynk/landClaimPlugin/commands/CommandHandler.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,9 @@ public CommandHandler(LandClaimPlugin plugin, ClaimManager claimManager,
119119
new AllyCommand(plugin, claimManager, configManager),
120120
new AbandonCommand(plugin, claimManager, configManager),
121121
new BanCommand(plugin, claimManager, configManager),
122-
new UnstuckCommand(plugin, claimManager, configManager));
122+
new UnstuckCommand(plugin, claimManager, configManager),
123+
new BuyCommand(plugin, claimManager, configManager),
124+
new SellCommand(plugin, claimManager, configManager));
123125

124126
// Register all commands via the shared /claim builder
125127
Command.Builder<PlayerSource> claimBuilder = commandManager.commandBuilder("claim", "c")

src/main/java/org/ayosynk/landClaimPlugin/commands/MemberCommand.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,13 @@ public void register(PaperCommandManager<Source> manager, Command.Builder<Player
102102
return;
103103
}
104104

105+
int maxMembers = configManager.getPluginConfig().maxClaimMembers + profile.getBonusMemberSlots();
106+
if (profile.getMemberRoles().size() >= maxMembers && !player.hasPermission("landclaim.admin")) {
107+
player.sendMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage()
108+
.deserialize("<red>You have reached your maximum member limit (" + maxMembers + " members). Upgrade your claim limit to invite more."));
109+
return;
110+
}
111+
105112
claimManager.sendMemberInvite(player, target, profile);
106113
}));
107114

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package org.ayosynk.landClaimPlugin.commands;
2+
3+
import org.ayosynk.landClaimPlugin.LandClaimPlugin;
4+
import org.ayosynk.landClaimPlugin.managers.ClaimManager;
5+
import org.ayosynk.landClaimPlugin.managers.ConfigManager;
6+
import org.ayosynk.landClaimPlugin.models.ClaimProfile;
7+
import org.bukkit.Bukkit;
8+
import org.bukkit.entity.Player;
9+
import org.bukkit.plugin.Plugin;
10+
import org.incendo.cloud.Command;
11+
import org.incendo.cloud.paper.PaperCommandManager;
12+
import org.incendo.cloud.paper.util.sender.PlayerSource;
13+
import org.incendo.cloud.paper.util.sender.Source;
14+
15+
import java.lang.reflect.Method;
16+
17+
public class SellCommand implements LandClaimCommand {
18+
19+
private final LandClaimPlugin plugin;
20+
private final ClaimManager claimManager;
21+
private final ConfigManager configManager;
22+
23+
public SellCommand(LandClaimPlugin plugin, ClaimManager claimManager, ConfigManager configManager) {
24+
this.plugin = plugin;
25+
this.claimManager = claimManager;
26+
this.configManager = configManager;
27+
}
28+
29+
@Override
30+
public void register(PaperCommandManager<Source> manager, Command.Builder<PlayerSource> claimBuilder) {
31+
manager.command(claimBuilder.literal("sell")
32+
.required("profile", org.incendo.cloud.parser.standard.StringParser.stringParser())
33+
.required("price", org.incendo.cloud.parser.standard.DoubleParser.doubleParser(0.01))
34+
.handler(context -> {
35+
Player player = context.sender().source();
36+
String profileName = context.get("profile");
37+
double price = context.get("price");
38+
39+
// 1. Find profile by name and check ownership
40+
ClaimProfile targetProfile = null;
41+
for (ClaimProfile cp : claimManager.getAllProfiles()) {
42+
if (cp.isOwner(player.getUniqueId()) && cp.getName().equalsIgnoreCase(profileName)) {
43+
targetProfile = cp;
44+
break;
45+
}
46+
}
47+
48+
if (targetProfile == null) {
49+
player.sendMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage()
50+
.deserialize("<red>Could not find a claim profile owned by you named: " + profileName));
51+
return;
52+
}
53+
54+
ClaimProfile activeProfile = claimManager.getActiveProfile(player);
55+
boolean isActive = activeProfile != null && activeProfile.getProfileId().equals(targetProfile.getProfileId());
56+
57+
final ClaimProfile profileToSell = targetProfile;
58+
59+
if (isActive) {
60+
// Prompt for confirmation using ConfirmationGUI
61+
org.ayosynk.landClaimPlugin.gui.ConfirmationGUI.open(player, "§cSell Active Profile?",
62+
() -> executeSell(player, profileToSell, price),
63+
() -> player.sendMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize("<gray>Listing cancelled."))
64+
);
65+
} else {
66+
executeSell(player, profileToSell, price);
67+
}
68+
}));
69+
}
70+
71+
private void executeSell(Player player, ClaimProfile profile, double price) {
72+
try {
73+
Plugin ecoPlugin = Bukkit.getPluginManager().getPlugin("LandClaimPlugin-Economy");
74+
if (ecoPlugin == null || !ecoPlugin.isEnabled()) {
75+
player.sendMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage()
76+
.deserialize("<red>Economy features are disabled."));
77+
return;
78+
}
79+
80+
Object marketManager = ecoPlugin.getClass().getMethod("getMarketManager").invoke(ecoPlugin);
81+
Method listMethod = marketManager.getClass().getMethod("list", Player.class, ClaimProfile.class, double.class);
82+
listMethod.invoke(marketManager, player, profile, price);
83+
} catch (Exception e) {
84+
plugin.getLogger().severe("Error listing profile for sale: " + e.getMessage());
85+
e.printStackTrace();
86+
player.sendMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage()
87+
.deserialize("<red>An error occurred while listing the claim."));
88+
}
89+
}
90+
}

src/main/java/org/ayosynk/landClaimPlugin/config/PluginConfig.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ public static class WildernessProtectionConfig extends OkaeriConfig {
143143
@Comment("Maximum number of claims a player is allowed to join as a trusted member (default: 1).")
144144
public int maxMemberships = 1;
145145

146+
@Comment("Default maximum members allowed per claim profile.")
147+
public int maxClaimMembers = 5;
148+
146149
@Comment("Database connection and backend settings (Supported backends: SQLITE, MYSQL, MARIADB).")
147150
public DatabaseConfig database = new DatabaseConfig();
148151

0 commit comments

Comments
 (0)