/* * Copyright (C) 2022-2023 NotEnoughUpdates contributors * * This file is part of NotEnoughUpdates. * * NotEnoughUpdates is free software: you can redistribute it * and/or modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 3 of the License, or (at your option) any later version. * * NotEnoughUpdates is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with NotEnoughUpdates. If not, see . */ package io.github.moulberry.notenoughupdates.util; import com.google.common.base.Splitter; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe; import io.github.moulberry.notenoughupdates.core.util.StringUtils; import io.github.moulberry.notenoughupdates.miscfeatures.tablisttutorial.TablistAPI; import lombok.var; import net.minecraftforge.client.event.ClientChatReceivedEvent; import net.minecraftforge.fml.common.eventhandler.EventPriority; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; import org.jetbrains.annotations.Nullable; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @NEUAutoSubscribe public class XPInformation { private static final XPInformation INSTANCE = new XPInformation(); public static XPInformation getInstance() { return INSTANCE; } public static class SkillInfo { public int level; public double totalXp; public double currentXp; public double currentXpMax; public boolean fromApi = false; } private final HashMap skillInfoMap = new HashMap<>(); public HashMap updateWithPercentage = new HashMap<>(); public HashMap increment = new HashMap<>(); private static final Splitter SPACE_SPLITTER = Splitter.on(" ").omitEmptyStrings().trimResults(); private static final Pattern SKILL_PATTERN = Pattern.compile( "\\+(\\d+(?:,\\d+)*(?:\\.\\d+)?) (.+) \\((\\d+(?:,\\d+)*(?:\\.\\d+)?)/(\\d+(?:,\\d+)*(?:\\.\\d+)?)\\)"); private static final Pattern SKILL_PATTERN_MULTIPLIER = Pattern.compile("\\+(\\d+(?:,\\d+)*(?:\\.\\d+)?) (.+) \\((\\d+(?:,\\d+)*(?:\\.\\d+)?)/(\\d+(?:k|m|b))\\)"); private static final Pattern SKILL_PATTERN_PERCENTAGE = Pattern.compile("\\+(\\d+(?:,\\d+)*(?:\\.\\d+)?) (.+) \\((\\d\\d?(?:\\.\\d\\d?)?)%\\)"); public HashMap getSkillInfoMap() { return skillInfoMap; } private Set failedSkills = new HashSet<>(); public @Nullable SkillInfo getSkillInfo(String skillName) { return getSkillInfo(skillName, false); } public @Nullable SkillInfo getSkillInfo(String skillName, boolean isHighlyInterested) { var obj = skillInfoMap.get(skillName.toLowerCase(Locale.ROOT)); if (isHighlyInterested && failedSkills.contains(skillName.toLowerCase(Locale.ROOT))) { TablistAPI.getWidgetLines(TablistAPI.WidgetNames.SKILLS); } return obj; } private String lastActionBar = null; @SubscribeEvent(priority = EventPriority.HIGHEST, receiveCanceled = true) public void onChatReceived(ClientChatReceivedEvent event) { if (event.type != 2) { return; } JsonObject leveling = Constants.LEVELING; if (leveling == null) return; String actionBar = StringUtils.cleanColour(event.message.getUnformattedText()); if (lastActionBar != null && lastActionBar.equalsIgnoreCase(actionBar)) { return; } lastActionBar = actionBar; List components = SPACE_SPLITTER.splitToList(actionBar); for (String component : components) { Matcher matcher = SKILL_PATTERN.matcher(component); if (matcher.matches()) { String skillS = matcher.group(2); String currentXpS = matcher.group(3).replace(",", ""); String maxXpS = matcher.group(4).replace(",", ""); float currentXp = Float.parseFloat(currentXpS); float maxXp = Float.parseFloat(maxXpS); SkillInfo skillInfo = new SkillInfo(); skillInfo.currentXp = currentXp; skillInfo.currentXpMax = maxXp; skillInfo.totalXp = currentXp; JsonArray levelingArray = leveling.getAsJsonArray("leveling_xp"); for (int i = 0; i < levelingArray.size(); i++) { float cap = levelingArray.get(i).getAsFloat(); if (maxXp > 0 && maxXp <= cap) { break; } skillInfo.totalXp += cap; skillInfo.level++; } skillInfoMap.put(skillS.toLowerCase(Locale.ROOT), skillInfo); return; } else { matcher = SKILL_PATTERN_PERCENTAGE.matcher(component); if (matcher.matches()) { String skillS = matcher.group(2); String xpPercentageS = matcher.group(3).replace(",", ""); float xpPercentage = Float.parseFloat(xpPercentageS); if (updateWithPercentage.containsKey(skillS.toLowerCase(Locale.ROOT))) { failedSkills.add(skillS.toLowerCase(Locale.ROOT)); } updateWithPercentage.put(skillS.toLowerCase(Locale.ROOT), xpPercentage); increment.put(skillS.toLowerCase(Locale.ROOT), Float.parseFloat(matcher.group(1).replace(",", ""))); } else { matcher = SKILL_PATTERN_MULTIPLIER.matcher(component); if (matcher.matches()) { String skillS = matcher.group(2); String currentXpS = matcher.group(3).replace(",", ""); String maxXpS = matcher.group(4).replace(",", ""); float maxMult = 1; if (maxXpS.endsWith("k")) { maxMult = 1000; maxXpS = maxXpS.substring(0, maxXpS.length() - 1); } else if (maxXpS.endsWith("m")) { maxMult = 1000000; maxXpS = maxXpS.substring(0, maxXpS.length() - 1); } else if (maxXpS.endsWith("b")) { maxMult = 1000000000; maxXpS = maxXpS.substring(0, maxXpS.length() - 1); } float currentXp = Float.parseFloat(currentXpS); float maxXp = Float.parseFloat(maxXpS) * maxMult; SkillInfo skillInfo = new SkillInfo(); skillInfo.currentXp = currentXp; skillInfo.currentXpMax = maxXp; skillInfo.totalXp = currentXp; JsonArray levelingArray = leveling.getAsJsonArray("leveling_xp"); for (int i = 0; i < levelingArray.size(); i++) { float cap = levelingArray.get(i).getAsFloat(); if (maxXp > 0 && maxXp <= cap) { break; } skillInfo.totalXp += cap; skillInfo.level++; } skillInfoMap.put(skillS.toLowerCase(Locale.ROOT), skillInfo); return; } } } } } private Pattern tablistSkillPattern = Pattern.compile( " (?[^ ]+) (?\\d+): (?:(?\\d+(\\.\\d+)?)%|(?[0-9,]+(\\.\\d+)?)/.*|(?MAX))"); // Car Pentry @SubscribeEvent public void onTick(TickEvent.ClientTickEvent event) { if (event.phase != TickEvent.Phase.END) return; var widgetLines = TablistAPI.getOptionalWidgetLines(TablistAPI.WidgetNames.SKILLS); for (String widgetLine : widgetLines) { Matcher matcher = tablistSkillPattern.matcher(Utils.cleanColour(widgetLine)); if (!matcher.matches()) continue; var type = matcher.group("type"); assert type != null; var level = Integer.parseInt(matcher.group("level")); var percentage = matcher.group("percentage"); var percentageAsNumber = percentage != null ? Double.parseDouble(percentage) / 100 : null; var amount = matcher.group("amount"); var amountAsNumber = amount != null ? Double.parseDouble(amount.replace(",", "")) : null; var isMax = matcher.group("max") != null; // TODO: use this extra information for good (not evil) updateLevel(type.toLowerCase(Locale.ROOT), level); } } public void updateLevel(String skill, int level) { if (updateWithPercentage.containsKey(skill)) { JsonObject leveling = Constants.LEVELING; if (leveling == null) return; SkillInfo newSkillInfo = new SkillInfo(); newSkillInfo.totalXp = 0; newSkillInfo.level = level; JsonArray levelingArray = leveling.getAsJsonArray("leveling_xp"); for (int i = 0; i < levelingArray.size(); i++) { float cap = levelingArray.get(i).getAsFloat(); if (i == level) { newSkillInfo.currentXp += updateWithPercentage.get(skill) / 100f * cap; newSkillInfo.totalXp += newSkillInfo.currentXp; newSkillInfo.currentXpMax = cap; break; } else { newSkillInfo.totalXp += cap; } } SkillInfo oldSkillInfo = skillInfoMap.get(skill.toLowerCase(Locale.ROOT)); float inc = increment.getOrDefault(skill.toLowerCase(Locale.ROOT), 0F); if (oldSkillInfo != null && oldSkillInfo.totalXp + inc > newSkillInfo.totalXp && oldSkillInfo.totalXp - inc * 5 < newSkillInfo.totalXp) { SkillInfo incrementedSkillInfo = new SkillInfo(); incrementedSkillInfo.totalXp = oldSkillInfo.totalXp + inc; boolean isNotLevelUp = oldSkillInfo.currentXp + inc < oldSkillInfo.currentXpMax; incrementedSkillInfo.level = (isNotLevelUp) ? oldSkillInfo.level : oldSkillInfo.level + 1; incrementedSkillInfo.currentXp = isNotLevelUp ? oldSkillInfo.currentXp + inc : oldSkillInfo.currentXp + inc - oldSkillInfo.currentXpMax; incrementedSkillInfo.currentXpMax = incrementedSkillInfo.level < levelingArray.size() && incrementedSkillInfo.level >= 0 ? levelingArray.get(incrementedSkillInfo.level).getAsFloat() : 0F; skillInfoMap.put(skill.toLowerCase(Locale.ROOT), incrementedSkillInfo); } else { skillInfoMap.put(skill.toLowerCase(Locale.ROOT), newSkillInfo); } failedSkills.remove(skill.toLowerCase(Locale.ROOT)); } updateWithPercentage.clear(); } }