Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ boolean execute(CommandSender sender, String[] args) {
sender.sendMessage("§cNo permission.");
return true;
}
if (args.length < 4 || args.length > 6) {
sender.sendMessage("§cUsage: /ac give equipment <recipe> <ingredient.id|alloy.id> [player] [quality 0-100]");
if (args.length < 4 || args.length > 7) {
sender.sendMessage("§cUsage: /ac give equipment <recipe> <ingredient.id|alloy.id> [player] [quality 0-100] [filler tier]");
return true;
}
Player target;
Expand All @@ -49,7 +49,7 @@ boolean execute(CommandSender sender, String[] args) {
return true;
}
double quality = 100;
if (args.length == 6) {
if (args.length >= 6) {
try {
quality = Double.parseDouble(args[5]);
} catch (NumberFormatException ex) {
Expand All @@ -61,6 +61,18 @@ boolean execute(CommandSender sender, String[] args) {
return true;
}
}
Integer fillerTier = null;
if (args.length == 7) {
try {
fillerTier = Integer.parseInt(args[6]);
} catch (NumberFormatException ex) {
fillerTier = 0;
}
if (fillerTier < 1) {
sender.sendMessage("§cFiller tier must be a whole number of 1 or more.");
return true;
}
}
CraftingRecipe recipe = RecipeLoader.getByString(args[2].toLowerCase(Locale.ROOT));
if (recipe == null) {
sender.sendMessage("§cUnknown recipe: §f" + args[2]);
Expand All @@ -75,10 +87,7 @@ boolean execute(CommandSender sender, String[] args) {
for (var entry : recipe.getRecipe().entrySet()) {
String key = material;
if (!entry.getKey().equalsIgnoreCase(recipe.getMainType())) {
Ingredient filler = IngredientLoader.get().stream()
.filter(i -> i.getIngredientData().getType().getId().equalsIgnoreCase(entry.getKey()))
.min(Comparator.comparingInt((Ingredient i) -> i.getIngredientData().getTier())
.thenComparing(Ingredient::getId)).orElse(null);
Ingredient filler = filler(entry.getKey(), fillerTier);
if (filler == null) {
sender.sendMessage("§cNo configured ingredient for required type: §f" + entry.getKey());
return true;
Expand All @@ -102,6 +111,23 @@ boolean execute(CommandSender sender, String[] args) {
return true;
}

/**
* Lowest-tier ingredient of the type by default. With a filler tier, the highest tier at or below it
* (falling back to the lowest when every ingredient of the type is above it).
*/
private Ingredient filler(String type, Integer fillerTier) {
List<Ingredient> ofType = IngredientLoader.get().stream()
.filter(i -> i.getIngredientData().getType().getId().equalsIgnoreCase(type))
.toList();
Comparator<Ingredient> byTier = Comparator.comparingInt((Ingredient i) -> i.getIngredientData().getTier());
Ingredient lowest = ofType.stream().min(byTier.thenComparing(Ingredient::getId)).orElse(null);
if (fillerTier == null) return lowest;
return ofType.stream()
.filter(i -> i.getIngredientData().getTier() <= fillerTier)
.max(byTier.thenComparing(Ingredient::getId, Comparator.reverseOrder()))
.orElse(lowest);
}

private List<String> materialsFor(CraftingRecipe recipe) {
if (!recipe.getRecipe().containsKey(recipe.getMainType())) return List.of();
Stream<String> ingredients = IngredientLoader.get().stream()
Expand All @@ -124,6 +150,7 @@ List<String> complete(CommandSender sender, String[] args) {
}
case 5 -> options = Bukkit.getOnlinePlayers().stream().map(Player::getName).sorted().toList();
case 6 -> options = List.of("0", "25", "50", "75", "90", "100");
case 7 -> options = List.of("1", "2", "3", "4");
default -> options = List.of();
}
String prefix = args[args.length - 1].toLowerCase(Locale.ROOT);
Expand Down
18 changes: 9 additions & 9 deletions src/main/resources/ingredients.yml
Original file line number Diff line number Diff line change
Expand Up @@ -793,7 +793,7 @@ rare_leather:
scheme: leather
model-scheme: default
stats:
- armor_toughness(0.5)
- armor_toughness(0.25)
- critical_strike_chance(10.0)
- damage_reduction(4.0)
- movement_speed(0.4)
Expand All @@ -810,7 +810,7 @@ epic_leather:
scheme: leather
model-scheme: default
stats:
- armor_toughness(0.75)
- armor_toughness(0.375)
- critical_strike_chance(15.0)
- damage_reduction(8.0)
- movement_speed(0.5)
Expand All @@ -827,7 +827,7 @@ legendary_leather:
scheme: leather
model-scheme: default
stats:
- armor_toughness(1.0)
- armor_toughness(0.5)
- critical_strike_chance(20.0)
- damage_reduction(16.0)
- movement_speed(0.6)
Expand Down Expand Up @@ -861,7 +861,7 @@ rare_feather:
scheme: feather
model-scheme: default
stats:
- armor_toughness(0.5)
- armor_toughness(0.25)
- critical_strike_chance(10.0)
- damage_reduction(4.0)
- movement_speed(0.4)
Expand All @@ -879,7 +879,7 @@ epic_feather:
scheme: feather
model-scheme: default
stats:
- armor_toughness(0.75)
- armor_toughness(0.375)
- critical_strike_chance(15.0)
- damage_reduction(8.0)
- movement_speed(0.5)
Expand All @@ -897,7 +897,7 @@ legendary_feather:
scheme: feather
model-scheme: default
stats:
- armor_toughness(1.0)
- armor_toughness(0.5)
- critical_strike_chance(20.0)
- damage_reduction(16.0)
- movement_speed(0.6)
Expand Down Expand Up @@ -931,7 +931,7 @@ rare_wool:
scheme: wool
model-scheme: default
stats:
- armor_toughness(0.5)
- armor_toughness(0.25)
- critical_strike_chance(10.0)
- damage_reduction(4.0)
- movement_speed(0.4)
Expand All @@ -948,7 +948,7 @@ epic_wool:
scheme: wool
model-scheme: default
stats:
- armor_toughness(0.75)
- armor_toughness(0.375)
- critical_strike_chance(15.0)
- damage_reduction(8.0)
- movement_speed(0.5)
Expand All @@ -965,7 +965,7 @@ legendary_wool:
scheme: wool
model-scheme: default
stats:
- armor_toughness(1.0)
- armor_toughness(0.5)
- critical_strike_chance(20.0)
- damage_reduction(16.0)
- movement_speed(0.6)
Expand Down
7 changes: 3 additions & 4 deletions src/main/resources/stats.yml
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,6 @@ light_armor:
icon: v.chainmail_helmet
base-stats:
- armor(0.5)
- armor_toughness(0.5)
- movement_speed(0.4)
stats:
- armor
Expand All @@ -192,7 +191,7 @@ medium_armor:
icon: v.iron_chestplate
base-stats:
- armor(1.5)
- armor_toughness(1.0)
- armor_toughness(0.5)
- movement_speed(-0.2)
stats:
- armor
Expand All @@ -208,7 +207,7 @@ heavy_armor:
icon: v.netherite_chestplate
base-stats:
- armor(2.5)
- armor_toughness(2.0)
- armor_toughness(1.5)
- movement_speed(-0.4)
stats:
- armor
Expand All @@ -224,10 +223,10 @@ infantry_armor:
icon: v.leather_helmet
base-stats:
- armor(1.5)
- armor_toughness(0.5)
- movement_speed(0.2)
stats:
- armor
- armor_toughness
- max_health
- physical_damage_reduction
- projectile_damage_reduction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,11 @@ void rejectsUsageTargetsQualityRecipesAndMaterialsBeforeBuilding() throws Except
}
run(p, "sword", "ingredient.iron");
assertTrue(p.nextMessage().contains("No configured ingredient"));
run(p, "sword", "ingredient.iron", "Staff", "50", "extra");
for (String tier : List.of("extra", "0", "-1", "2.5")) {
run(p, "sword", "ingredient.iron", "Staff", "50", tier);
assertTrue(p.nextMessage().contains("Filler tier must"));
}
run(p, "sword", "ingredient.iron", "Staff", "50", "4", "extra");
assertTrue(p.nextMessage().contains("Usage"));
assertEquals(0, p.getInventory().all(Material.IRON_SWORD).size());
RecipeLoader.map.put("no_main", recipe("main-type: wood\nrecipe: ['metal.1']"));
Expand Down Expand Up @@ -130,7 +134,9 @@ void grantsSelfConsoleTargetsAlloysAndOverflowWithExactProvenance() throws Excep
assertEquals(List.of(), complete(p, "give", "equipment", "missing", ""));
assertEquals(List.of("Target"), complete(p, "give", "equipment", "sword", "", "t"));
assertEquals(List.of("100"), complete(p, "give", "equipment", "sword", "", "", "1"));
assertEquals(List.of(), complete(p, "give", "equipment", "sword", "", "", "", ""));
assertEquals(List.of("1", "2", "3", "4"), complete(p, "give", "equipment", "sword", "", "", "", ""));
assertEquals(List.of("4"), complete(p, "give", "equipment", "sword", "", "", "", "4"));
assertEquals(List.of(), complete(p, "give", "equipment", "sword", "", "", "", "", ""));
try (var nbt = mockStatic(NBTItem.class);
var mmos = mockConstruction(LiveMMOItem.class,
withSettings().defaultAnswer(RETURNS_DEEP_STUBS), (mmo, ctx) -> {
Expand All @@ -150,6 +156,15 @@ void grantsSelfConsoleTargetsAlloysAndOverflowWithExactProvenance() throws Excep
provenance.getInputs().stream().collect(java.util.stream.Collectors.toMap(
input -> input.getKind() + "." + input.getId(), CraftInput::getAmount)));
assertTrue(target.getWorld().getEntitiesByClass(Item.class).isEmpty());
run(p, "sword", "ingredient.iron", "Staff", "100", "4");
assertEquals(Map.of("ingredient.iron", 4, "ingredient.expensive_leather", 2), inputs(p.getInventory().getItem(1)));
run(p, "sword", "ingredient.iron", "Staff", "100", "1");
assertEquals(Map.of("ingredient.iron", 4, "ingredient.a_leather", 2), inputs(p.getInventory().getItem(2)));
TypeLoader.map.put("wool", new IngredientType("wool", yaml("name: Wool")));
ingredient("fine_wool", "type: wool\ntier: 3");
RecipeLoader.map.put("cloak", recipe("recipe: ['metal.4', 'wool.1']"));
run(p, "cloak", "ingredient.iron", "Staff", "100", "2");
assertEquals(Map.of("ingredient.iron", 4, "ingredient.fine_wool", 1), inputs(p.getInventory().getItem(3)));
var console = mock(CommandSender.class);
when(console.hasPermission(AdminPermissions.PERMISSION)).thenReturn(true);
run(console, "sword", "alloy.bronze", "Target", "0");
Expand All @@ -167,6 +182,11 @@ void grantsSelfConsoleTargetsAlloysAndOverflowWithExactProvenance() throws Excep
}
}

static Map<String, Integer> inputs(ItemStack item) {
return CraftProvenance.readFrom(item).getInputs().stream().collect(java.util.stream.Collectors.toMap(
input -> input.getKind() + "." + input.getId(), CraftInput::getAmount));
}

@Test
void buildingFailuresAreReportedWithoutGrantAndIncompleteBuilderRejects() throws Exception {
var p = server.addPlayer();
Expand Down
Loading