Skip to content

Commit b33c29e

Browse files
authored
Add staff equipment grants with configurable permission (#32)
Co-authored-by: XxFran10xX <318299142+XxFran10xX@users.noreply.github.com>
1 parent 34c6ffe commit b33c29e

9 files changed

Lines changed: 387 additions & 6 deletions

File tree

‎README.md‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,26 @@ finished item.
2222
- **Profession progression** — connect recipes and materials to profession
2323
access and experience rewards.
2424

25+
## Staff equipment grants
26+
27+
`/ac give equipment <recipe> <ingredient.id|alloy.id> [player] [quality]`
28+
gives one finished item using a configured recipe and compatible main material.
29+
For example, `/ac give equipment heavy_chestplate ingredient.steel_ingot Alex 100`.
30+
Tab completion lists loaded recipes, compatible ingredients/alloys and online players.
31+
Omit the player to give to yourself; console must specify an online player.
32+
Quality defaults to 100 and accepts finite values from 0 to 100 (specify the player
33+
before quality). Alloy IDs are the loaded discovery IDs, not display names.
34+
35+
Secondary recipe ingredients use the lowest configured tier, then ingredient ID
36+
alphabetically to break ties. Their stats and appearance participate normally.
37+
The item retains normal stats, quality sockets, appearance, tier and crafting
38+
provenance; no materials are consumed and no XP or activity rewards are granted.
39+
Full inventories drop the item at the recipient's location.
40+
41+
Set `give-equipment-permission` in `config.yml` and run `/ac reload` to change
42+
access. Missing or blank settings default to `advancedcrafting.admin` (operators).
43+
Grant a custom permission only to staff; it does not grant other admin commands.
44+
2545
## Credits
2646

2747
Originally authored by **Drefvelin**.

‎src/main/java/net/tfminecraft/advancedcrafting/cache/Cache.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class Cache {
1414
public static String craftingStation;
1515
public static String alloyStation;
1616
public static String ingredientStation;
17+
public static String giveEquipmentPermission = "advancedcrafting.admin";
1718

1819
public static double alloyForgeBaseSuccess = 2.0;
1920
public static double alloyForgeBonusPerSqrtValue = 4.0;

‎src/main/java/net/tfminecraft/advancedcrafting/loaders/ConfigLoader.java‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ public void load(File configFile) {
2929
}
3030

3131
Cache.scrap = config.getString("scrap-path");
32+
Cache.giveEquipmentPermission = config.getString("give-equipment-permission", "advancedcrafting.admin").trim();
33+
if (Cache.giveEquipmentPermission.isEmpty()) {
34+
Cache.giveEquipmentPermission = "advancedcrafting.admin";
35+
}
3236

3337
Cache.craftingStation = config.getString("crafting-station", "v(ANVIL)");
3438
Cache.alloyStation = config.getString("alloy-station", "v(BLAST_FURNACE)");

‎src/main/java/net/tfminecraft/advancedcrafting/managers/CommandManager.java‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import net.tfminecraft.advancedcrafting.utils.CraftStatRefresher.RefreshResult;
3030

3131
public class CommandManager implements Listener, CommandExecutor, TabCompleter {
32+
private final EquipmentGiveCommand equipmentGive = new EquipmentGiveCommand();
3233
public String cmd1 = "ac";
3334
public String cmd2 = "alloy";
3435

@@ -51,6 +52,9 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String
5152
}
5253

5354
private boolean handleAcCommand(CommandSender sender, String[] args) {
55+
if (args.length >= 2 && args[0].equalsIgnoreCase("give") && args[1].equalsIgnoreCase("equipment")) {
56+
return equipmentGive.execute(sender, args);
57+
}
5458
if (args.length >= 1 && args[0].equalsIgnoreCase("reload")) {
5559
if (!AdminPermissions.require(sender)) {
5660
return true;
@@ -167,7 +171,19 @@ public List<String> onTabComplete(CommandSender sender, Command cmd, String labe
167171
}
168172

169173
private List<String> onAcTabComplete(CommandSender sender, String[] args) {
174+
if (args.length >= 2 && args[0].equalsIgnoreCase("give")) {
175+
if (args.length == 2) {
176+
List<String> options = new ArrayList<>();
177+
if (sender.hasPermission(AdminPermissions.PERMISSION)) options.add("alloy");
178+
if (equipmentGive.hasPermission(sender)) options.add("equipment");
179+
return filterPrefix(args[1], options);
180+
}
181+
if (args[1].equalsIgnoreCase("equipment")) return equipmentGive.complete(sender, args);
182+
}
170183
if (!sender.hasPermission(AdminPermissions.PERMISSION)) {
184+
if (args.length <= 1 && equipmentGive.hasPermission(sender)) {
185+
return filterPrefix(args.length == 0 ? "" : args[0], "give");
186+
}
171187
return List.of();
172188
}
173189
if (args.length == 0) {
@@ -181,9 +197,6 @@ private List<String> onAcTabComplete(CommandSender sender, String[] args) {
181197
if (sub.equals("sync")) {
182198
return filterPrefix(args[1], "recipes");
183199
}
184-
if (sub.equals("give")) {
185-
return filterPrefix(args[1], "alloy");
186-
}
187200
if (sub.equals("alloy")) {
188201
return filterPrefix(args[1], "info");
189202
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package net.tfminecraft.advancedcrafting.managers;
2+
3+
import java.util.Comparator;
4+
import java.util.HashMap;
5+
import java.util.List;
6+
import java.util.Locale;
7+
import java.util.stream.Stream;
8+
import java.util.logging.Level;
9+
10+
import org.bukkit.Bukkit;
11+
import org.bukkit.command.CommandSender;
12+
import org.bukkit.entity.Player;
13+
import org.bukkit.inventory.ItemStack;
14+
15+
import net.tfminecraft.advancedcrafting.AdvancedCrafting;
16+
import net.tfminecraft.advancedcrafting.cache.Cache;
17+
import net.tfminecraft.advancedcrafting.loaders.IngredientLoader;
18+
import net.tfminecraft.advancedcrafting.loaders.RecipeLoader;
19+
import net.tfminecraft.advancedcrafting.objects.crafting.CraftingRecipe;
20+
import net.tfminecraft.advancedcrafting.objects.crafting.CraftingStation;
21+
import net.tfminecraft.advancedcrafting.objects.ingredients.Ingredient;
22+
23+
/** Staff grants use the normal equipment builder, without crafting rewards or station state. */
24+
final class EquipmentGiveCommand {
25+
boolean hasPermission(CommandSender sender) {
26+
return sender.hasPermission(Cache.giveEquipmentPermission);
27+
}
28+
29+
boolean execute(CommandSender sender, String[] args) {
30+
if (!hasPermission(sender)) {
31+
sender.sendMessage("§cNo permission.");
32+
return true;
33+
}
34+
if (args.length < 4 || args.length > 6) {
35+
sender.sendMessage("§cUsage: /ac give equipment <recipe> <ingredient.id|alloy.id> [player] [quality 0-100]");
36+
return true;
37+
}
38+
Player target;
39+
if (args.length >= 5) {
40+
target = Bukkit.getPlayerExact(args[4]);
41+
if (target == null) {
42+
sender.sendMessage("§cPlayer not found: §f" + args[4]);
43+
return true;
44+
}
45+
} else if (sender instanceof Player player) {
46+
target = player;
47+
} else {
48+
sender.sendMessage("§cSpecify an online player from console.");
49+
return true;
50+
}
51+
double quality = 100;
52+
if (args.length == 6) {
53+
try {
54+
quality = Double.parseDouble(args[5]);
55+
} catch (NumberFormatException ex) {
56+
sender.sendMessage("§cQuality must be a finite number from 0 to 100.");
57+
return true;
58+
}
59+
if (!Double.isFinite(quality) || quality < 0 || quality > 100) {
60+
sender.sendMessage("§cQuality must be a finite number from 0 to 100.");
61+
return true;
62+
}
63+
}
64+
CraftingRecipe recipe = RecipeLoader.getByString(args[2].toLowerCase(Locale.ROOT));
65+
if (recipe == null) {
66+
sender.sendMessage("§cUnknown recipe: §f" + args[2]);
67+
return true;
68+
}
69+
String material = args[3].toLowerCase(Locale.ROOT);
70+
if (!materialsFor(recipe).contains(material)) {
71+
sender.sendMessage("§cUnknown or incompatible base material/alloy: §f" + args[3]);
72+
return true;
73+
}
74+
HashMap<String, Integer> inputs = new HashMap<>();
75+
for (var entry : recipe.getRecipe().entrySet()) {
76+
String key = material;
77+
if (!entry.getKey().equalsIgnoreCase(recipe.getMainType())) {
78+
Ingredient filler = IngredientLoader.get().stream()
79+
.filter(i -> i.getIngredientData().getType().getId().equalsIgnoreCase(entry.getKey()))
80+
.min(Comparator.comparingInt((Ingredient i) -> i.getIngredientData().getTier())
81+
.thenComparing(Ingredient::getId)).orElse(null);
82+
if (filler == null) {
83+
sender.sendMessage("§cNo configured ingredient for required type: §f" + entry.getKey());
84+
return true;
85+
}
86+
key = "ingredient." + filler.getId();
87+
}
88+
inputs.put(key, entry.getValue());
89+
}
90+
try {
91+
CraftingStation station = new CraftingStation(target.getLocation(), recipe, inputs, new HashMap<>());
92+
ItemStack item = station.buildCompletedItem(target, quality);
93+
for (ItemStack overflow : target.getInventory().addItem(item).values()) {
94+
target.getWorld().dropItemNaturally(target.getLocation(), overflow);
95+
}
96+
} catch (RuntimeException ex) {
97+
AdvancedCrafting.plugin.getLogger().log(Level.SEVERE, "Failed to give equipment for recipe " + recipe.getId(), ex);
98+
sender.sendMessage("§cEquipment could not be built. Check the server log and recipe/template configuration.");
99+
return true;
100+
}
101+
sender.sendMessage("§aGave §f" + recipe.getId() + "§a using §f" + material + "§a to §f" + target.getName() + "§a.");
102+
return true;
103+
}
104+
105+
private List<String> materialsFor(CraftingRecipe recipe) {
106+
if (!recipe.getRecipe().containsKey(recipe.getMainType())) return List.of();
107+
Stream<String> ingredients = IngredientLoader.get().stream()
108+
.filter(i -> i.getIngredientData().getType().getId().equalsIgnoreCase(recipe.getMainType()))
109+
.map(i -> "ingredient." + i.getId());
110+
Stream<String> alloys = AlloyManager.getAlloyIds().stream()
111+
.filter(id -> AlloyManager.getAlloyById(id).getData().getType().getId().equalsIgnoreCase(recipe.getMainType()))
112+
.map(id -> "alloy." + id);
113+
return Stream.concat(ingredients, alloys).sorted().toList();
114+
}
115+
116+
List<String> complete(CommandSender sender, String[] args) {
117+
if (!hasPermission(sender)) return List.of();
118+
List<String> options;
119+
switch (args.length) {
120+
case 3 -> options = RecipeLoader.get().keySet().stream().sorted().toList();
121+
case 4 -> {
122+
CraftingRecipe recipe = RecipeLoader.getByString(args[2].toLowerCase(Locale.ROOT));
123+
options = recipe == null ? List.of() : materialsFor(recipe);
124+
}
125+
case 5 -> options = Bukkit.getOnlinePlayers().stream().map(Player::getName).sorted().toList();
126+
case 6 -> options = List.of("0", "25", "50", "75", "90", "100");
127+
default -> options = List.of();
128+
}
129+
String prefix = args[args.length - 1].toLowerCase(Locale.ROOT);
130+
return options.stream().filter(o -> o.toLowerCase(Locale.ROOT).startsWith(prefix)).toList();
131+
}
132+
}

‎src/main/java/net/tfminecraft/advancedcrafting/objects/crafting/CraftingStation.java‎

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,21 @@ private void warnOvershootHits(Player p) {
385385
// Keep the existing legacy text representation, formatting, and exact-string comparisons.
386386
@SuppressWarnings("deprecation")
387387
private StationFeedback createItem(Player p, Double forcedQualityPercent) {
388+
return createItem(p, forcedQualityPercent, true);
389+
}
390+
391+
/** Builds staff equipment without consuming inventory, dropping an item, paying XP or firing progression events. */
392+
public ItemStack buildCompletedItem(Player p, double qualityPercent) {
393+
stats = CraftStatCalculator.compute(recipe, currentMaterials);
394+
StationFeedback feedback = createItem(p, qualityPercent, false);
395+
if (feedback != StationFeedback.SUCCESS) {
396+
throw new IllegalStateException("Cannot build equipment: " + feedback);
397+
}
398+
return result;
399+
}
400+
401+
@SuppressWarnings("deprecation")
402+
private StationFeedback createItem(Player p, Double forcedQualityPercent, boolean deliver) {
388403
if (!checkItems(p)) {
389404
return StationFeedback.LACKING_ITEMS;
390405
}
@@ -496,8 +511,11 @@ private StationFeedback createItem(Player p, Double forcedQualityPercent) {
496511
if (majorityTier > 0) {
497512
CraftTierLore.applyTierLine(finalItem, majorityTier);
498513
}
499-
Location dropLoc = loc.clone().add(0, 1, 0);
500-
dropLoc.getWorld().dropItem(dropLoc, finalItem);
514+
this.result = finalItem;
515+
if (deliver) {
516+
Location dropLoc = loc.clone().add(0, 1, 0);
517+
dropLoc.getWorld().dropItem(dropLoc, finalItem);
518+
}
501519
if (forcedQualityPercent == null) {
502520
CraftLifecycle.fireItemCrafted(p, recipe.getId(), recipe.getCategoryId());
503521
}

‎src/main/resources/config.yml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
scrap-path: crafting.scrap
22
branding-tool: m.tools.skin_branding_tool
33

4+
# Staff-only finished equipment command. Missing/blank values use advancedcrafting.admin.
5+
# Grant a custom node only to staff; it does not grant the other admin commands.
6+
give-equipment-permission: advancedcrafting.admin
7+
48
# TLibs block path for each station (iaf(...) for ItemsAdder furniture).
59
crafting-station: v(ANVIL)
610
alloy-station: iaf(tfmc:alloy_forge)

‎src/test/java/net/tfminecraft/advancedcrafting/CommandCoverageTest.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ void tabCompletionFiltersCommandsIdsAndOnlinePlayers() {
171171
AlloyManager.addAlloy(alloy);
172172
Map<List<String>, List<String>> cases = new LinkedHashMap<>();
173173
cases.put(List.of("sync", ""), List.of("recipes"));
174-
cases.put(List.of("give", ""), List.of("alloy"));
174+
cases.put(List.of("give", ""), List.of("alloy", "equipment"));
175175
cases.put(List.of("alloy", ""), List.of("info"));
176176
cases.put(List.of("craft", "1"), List.of("100"));
177177
cases.put(List.of("other", ""), List.of());

0 commit comments

Comments
 (0)