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
1 change: 1 addition & 0 deletions .github/dependencies.sha256
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ a37f7789fcdcd11c9e5890a2fac742aa4501749588af23182991953e06ee1d17 libs/MMOItems-
225aa7f75d4e6e6ae6b3ad87a7ca678c025c027f00374ab1062275d24f3a73c9 libs/MythicLib-1.7.1-SNAPSHOT.jar
5a01b37bd7442fb0f2c809cadac82ba70f75173956e04efac3336595cc070368 libs/ItemsAdder-4.0.18.jar
c571502e768640c7cff10c49b3eef904ccf3fd95bd81803913abfe43cfc09746 libs/NBTAPI-2.16.1.jar
81d511d0830951f988615b6533cc93e82d00a83fb31e8ccbb36441ae3e8157ef libs/MMOCore-1.13.1-SNAPSHOT.jar
3 changes: 3 additions & 0 deletions .github/scripts/install-local-dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ mvn -B --no-transfer-progress org.apache.maven.plugins:maven-install-plugin:3.1.
mvn -B --no-transfer-progress org.apache.maven.plugins:maven-install-plugin:3.1.4:install-file \
-Dfile="libs/NBTAPI-2.16.1.jar" -DgroupId="local" -DartifactId="item-nbt-api-plugin" \
-Dversion="2.16.1-tfmc-c571502e7686" -Dpackaging=jar -DgeneratePom=true "$@"
mvn -B --no-transfer-progress org.apache.maven.plugins:maven-install-plugin:3.1.4:install-file \
-Dfile="libs/MMOCore-1.13.1-SNAPSHOT.jar" -DgroupId="local" -DartifactId="MMOCore" \
-Dversion="1.13.1-SNAPSHOT-tfmc-81d511d08309" -Dpackaging=jar -DgeneratePom=true "$@"
1 change: 1 addition & 0 deletions .github/scripts/prepare-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ curl --fail --location --silent --show-error --retry 3 -H "Authorization: Bearer
curl --fail --location --silent --show-error --retry 3 -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github.raw+json" "https://api.github.com/repos/TF-Minecraft/ServerAssets/contents/jars/225aa7f75d4e/MythicLib-1.7.1-SNAPSHOT.jar?ref=$ref" > "libs/MythicLib-1.7.1-SNAPSHOT.jar"
curl --fail --location --silent --show-error --retry 3 -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github.raw+json" "https://api.github.com/repos/TF-Minecraft/ServerAssets/contents/jars/5a01b37bd744/ItemsAdder-4.0.18.jar?ref=$ref" > "libs/ItemsAdder-4.0.18.jar"
curl --fail --location --silent --show-error --retry 3 -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github.raw+json" "https://api.github.com/repos/TF-Minecraft/ServerAssets/contents/jars/c571502e7686/NBTAPI-2.16.1.jar?ref=$ref" > "libs/NBTAPI-2.16.1.jar"
curl --fail --location --silent --show-error --retry 3 -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github.raw+json" "https://api.github.com/repos/TF-Minecraft/ServerAssets/contents/jars/81d511d08309/MMOCore-1.13.1-SNAPSHOT.jar?ref=$ref" > "libs/MMOCore-1.13.1-SNAPSHOT.jar"
bash .github/scripts/install-local-dependencies.sh "$@"
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ finished item.
- **Crafting identity** — retain the recipe, materials, and quality behind a
crafted item, with support for naming, appearance, and branding.
- **Profession progression** — connect recipes and materials to profession
access and experience rewards.
access and experience rewards. Crafting XP scales with how well the piece
was worked.
- **d20 craft roll** — like `/roll`, finishing rolls a d20 plus a modifier from
the crafter's base Strength. A Masterwork needs perfect hits and a roll of 20
or more; a natural 20 always succeeds and a natural 1 always fails.

## Credits

Expand Down
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@
<version>1.1.1-tfmc-4e69696892b8</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>local</groupId>
<artifactId>MMOCore</artifactId>
<version>1.13.1-SNAPSHOT-tfmc-81d511d08309</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>local</groupId>
<artifactId>MMOItems</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ public class Cache {
/** Epoch millis; 0 turns the legacy base stats off. */
public static long alloyLegacyForgedBefore;

public static double hitOvershootWarnPercent = 30.0;
public static String hitOvershootWarnMessage = "§cYour over-reliance on %hit% ruins the result further";

public static boolean debugStatRefresh;

Expand All @@ -45,6 +43,8 @@ public class Cache {
public static Map<String, List<String>> legacyModels = new HashMap<>();

public static boolean showIngredientStats = true;
/** Crafting XP never drops below this percent of the set XP, however poor the Hits %. */
public static double craftXpMinPercent = 0;

/** Divisors applied to bucket-averaged stats before template factors (e.g. movement_speed: 100). */
public static Map<String, Double> globalStatOffsets = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ public enum StationFeedback {
WRONG_TYPE,
NONE,
LACKING_ITEMS,
LACKING_HITS,
INCOMPATIBLE_TYPE,
NO_PERMS,
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import net.tfminecraft.advancedcrafting.AdvancedCrafting;
import net.tfminecraft.advancedcrafting.cache.Cache;
import net.tfminecraft.advancedcrafting.objects.data.StatData;
import net.tfminecraft.advancedcrafting.utils.D20Roll;
import net.tfminecraft.advancedcrafting.objects.data.PermissionNamespace;
import net.tfminecraft.advancedcrafting.objects.ingredients.IngredientType;
import net.tfminecraft.advancedcrafting.utils.StatToString;
Expand Down Expand Up @@ -71,12 +72,8 @@ public void load(File configFile) {
}
}

Cache.hitOvershootWarnPercent = config.getDouble("hit-overshoot-warn-percent", 30.0);
String overshootMessage = config.getString("hit-overshoot-warn-message");
if (overshootMessage == null || overshootMessage.isBlank()) {
overshootMessage = "§cYour over-reliance on %hit% ruins the result further";
}
Cache.hitOvershootWarnMessage = overshootMessage;
D20Roll.load(config.getConfigurationSection("d20"));
Cache.craftXpMinPercent = Math.max(0, Math.min(100, config.getDouble("xp-min-percent", 0)));

Cache.debugStatRefresh = config.getBoolean("debug-stat-refresh", false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
Expand Down Expand Up @@ -45,6 +46,15 @@ public static Quality getByString(String id) {
if(map.containsKey(id)) return map.get(id);
return null;
}
/** The tier just below the given one, or null for the lowest or an unknown tier. */
public static Quality below(Quality quality) {
if (quality == null) return null;
return map.values().stream()
.filter(candidate -> candidate.getValue() < quality.getValue())
.max(Comparator.comparingInt(Quality::getValue))
.orElse(null);
}

public static Quality getByAmount(double a) {
Quality q = null;
int prev = -1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,7 @@ public void applyHit(PlayerInteractEvent e) {
currentStation.remove(p);
} else {
p.playSound(p.getLocation(), Sound.ENTITY_VILLAGER_NO, 1f, 1f);
p.sendMessage("§c" + (f.equals(StationFeedback.LACKING_HITS) ? "You need to complete all the hits before finishing" :
f.equals(StationFeedback.LACKING_ITEMS) ? "You have to add all the items before smithing" : ""));
p.sendMessage("§c" + (f.equals(StationFeedback.LACKING_ITEMS) ? "You have to add all the items before smithing" : ""));
}
return;
}
Expand All @@ -367,10 +366,6 @@ public void applyHit(PlayerInteractEvent e) {
p.sendMessage("§cThis tool is not needed for this craft");
p.playSound(p.getLocation(), Sound.ENTITY_VILLAGER_NO, 1f, 1f);
break;
case CAPACITY:
p.sendMessage("§cYou dont need more hits with this tool");
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 0.5f);
break;
default:
// Successful hit
p.getWorld().playSound(station.getLoc(), Sound.BLOCK_ANVIL_USE, 1f, 1f);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import net.tfminecraft.advancedcrafting.objects.stats.StatModifier;
import net.tfminecraft.advancedcrafting.utils.CraftStatCalculator;
import net.tfminecraft.advancedcrafting.utils.CraftTierLore;
import net.tfminecraft.advancedcrafting.utils.D20Roll;
import net.tfminecraft.advancedcrafting.utils.MMOStatApplicator;
import net.tfminecraft.advancedcrafting.utils.MajorityTierResolver;
import net.tfminecraft.advancedcrafting.utils.ModelApplier;
Expand Down Expand Up @@ -246,8 +247,8 @@ public StationFeedback addMaterial(Player p, ItemStack i) {
}

/**
* Lines the branding tool shows on right-click: the recipe and the material groups still to fill.
* Players have to find the hits themselves, so per-hit counts stay out of it.
* Lines the branding tool shows on right-click: the recipe, the material groups still to fill and the hits done.
* Players have to find the hits themselves, so every hit is listed with only what was done, never what is needed.
*/
public List<String> getStatusLines() {
List<String> lines = new ArrayList<>();
Expand All @@ -256,6 +257,15 @@ public List<String> getStatusLines() {
if (e.getKey() == null) continue;
lines.add(e.getKey().getName() + "§7: §e" + e.getValue().getCurrent() + "/" + e.getValue().getNeeded());
}
lines.add("§7Hits done:");
int total = 0;
for (CraftingHit h : HitLoader.get().values()) {
IntCounter c = hits.get(h);
int done = c == null ? 0 : c.getCurrent();
total += done;
lines.add(h.getName() + "§7: §e" + done);
}
lines.add("§7Total: §e" + total);
lines.add("§7Left-click branding to finish");
lines.add("§cSHIFT + LEFT CLICK with the branding tool to cancel the project!");
return lines;
Expand All @@ -274,15 +284,17 @@ public StationFeedback craft(Player p, Double forcedQualityPercent) {
StationFeedback f = createItem(p, forcedQualityPercent);
// Failed attempts keep the station's materials, so paying XP before the checks let every retry pay again.
if (f == StationFeedback.SUCCESS) {
giveXP(p);
// XP follows the work actually done: the set XP times the Hits %, at least xp-min-percent.
// A staff craft's set quality changes the piece, not the XP.
giveXP(p, Math.max(Cache.craftXpMinPercent, Math.min(100, calculatePercentage())));
}
return f;
}

// skill(amount), e.g. crafter(2.0)
private static final Pattern XP_FORMAT = Pattern.compile("([A-Za-z0-9_-]+)\\((\\d+(?:\\.\\d+)?)\\)");

private void giveXP(Player p) {
private void giveXP(Player p, double percent) {
// Map of skill name -> total XP to give
Map<String, Double> xpBySkill = new HashMap<>();

Expand Down Expand Up @@ -326,7 +338,8 @@ private void giveXP(Player p) {
ConsoleCommandSender console = Bukkit.getServer().getConsoleSender();
for (Map.Entry<String, Double> entry : xpBySkill.entrySet()) {
String skill = entry.getKey();
double xp = Math.round(entry.getValue() * 100) / 100.0; // round to 2 decimals
double xp = Math.round(entry.getValue() * percent) / 100.0; // scaled, rounded to 2 decimals
if (xp <= 0) continue;
String command = "mmocore admin exp give " + p.getName() + " " + skill + " " + xp;
Bukkit.dispatchCommand(console, command);
}
Expand All @@ -345,22 +358,12 @@ private boolean checkItems(Player p) {
return complete;
}

private boolean checkHits(Player p) {
boolean complete = true;
for(HitType t : hitTypes.keySet()) {
IntCounter c = hitTypes.get(t);
if(!c.isEqual()) {
complete = false;
p.sendMessage("§cYou only have "+c.getCurrent()+" out of "+c.getNeeded() + " " +t.getName()+ "§c hits.");
}
}
return complete;
}

private Quality getQuality(double d) {
return QualityLoader.getByAmount(d);
}
private double calculatePercentage() {
// A piece whose materials need no hits has nothing to get wrong.
if (hits.isEmpty()) return 100;
int counter = 0;
double amount = 0.0;
for(CraftingHit h : hits.keySet()) {
Expand All @@ -373,29 +376,6 @@ private double calculatePercentage() {
return Math.round((amount/counter));
}

private void warnOvershootHits(Player p) {
if (Cache.hitOvershootWarnPercent <= 0) {
return;
}
String template = Cache.hitOvershootWarnMessage;
if (template == null || template.isBlank()) {
return;
}
for (CraftingHit hit : hits.keySet()) {
IntCounter counter = hits.get(hit);
int needed = counter.getNeeded();
int current = counter.getCurrent();
if (needed <= 0 || current <= needed) {
continue;
}
double overshoot = ((double) (current - needed) / needed) * 100.0;
if (overshoot < Cache.hitOvershootWarnPercent) {
continue;
}
p.sendMessage(template.replace("%hit%", hit.getName()));
}
}

// Keep the existing legacy text representation, formatting, and exact-string comparisons.
@SuppressWarnings("deprecation")
private StationFeedback createItem(Player p, Double forcedQualityPercent) {
Expand All @@ -417,9 +397,6 @@ private StationFeedback createItem(Player p, Double forcedQualityPercent, boolea
if (!checkItems(p)) {
return StationFeedback.LACKING_ITEMS;
}
if (forcedQualityPercent == null && !checkHits(p)) {
return StationFeedback.LACKING_HITS;
}
ItemAPI api = TLibs.getItemAPI();
result = api.getCreator().getItemFromPath("m."+recipe.getTemplate());
MMOItem mmo = new LiveMMOItem(NBTItem.get(result));
Expand Down Expand Up @@ -483,11 +460,18 @@ private StationFeedback createItem(Player p, Double forcedQualityPercent, boolea
}
double percentage = forcedQualityPercent != null ? forcedQualityPercent : calculatePercentage();
Quality q = getQuality(percentage);
p.sendMessage("Quality: "+q.getName());
p.sendMessage("Hit Percenage: §e"+percentage+"%");
p.sendMessage("Hit Percentage: §e"+percentage+"%");
if (forcedQualityPercent == null) {
warnOvershootHits(p);
// Perfect hits alone are not a Masterwork: the craft roll must also meet the DC. Stats still come
// only from the materials, so every Masterwork has the best stats its materials give.
D20Roll roll = D20Roll.roll(p);
p.sendMessage("Craft roll: " + roll.describe());
if (percentage >= 100 && !roll.meets(D20Roll.masterworkDc)) {
Quality below = QualityLoader.below(q);
if (below != null) q = below;
}
}
p.sendMessage("Quality: "+q.getName());
List<String> sockets = new ArrayList<String>();
SocketGroup socketGroup = SocketGroupLoader.getByString(recipe.getSocketGroupId());
if(socketGroup == null) {
Expand Down Expand Up @@ -540,7 +524,6 @@ public StationFeedback hit(Player p, ItemStack i) {
CraftingHit hit = HitLoader.getByTool(nbt.getType()+"."+nbt.getString("MMOITEMS_ITEM_ID"));
if(hit == null) return StationFeedback.WRONG_TYPE;
if(!hitTypes.containsKey(hit.getType())) return StationFeedback.NONE;
if(hitTypes.get(hit.getType()).isEqual()) return StationFeedback.CAPACITY;
addHit(p, hit);
return StationFeedback.SUCCESS;

Expand Down
Loading
Loading