/*
* Copyright (C) 2022 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.profileviewer.ProfileViewer;
import net.minecraftforge.client.event.ClientChatReceivedEvent;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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 float totalXp;
public float currentXp;
public float currentXpMax;
public boolean fromApi = false;
}
private final HashMap skillInfoMap = new HashMap<>();
public HashMap updateWithPercentage = new HashMap<>();
public int correctionCounter = 0;
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;
}
public SkillInfo getSkillInfo(String skillName) {
return skillInfoMap.get(skillName.toLowerCase());
}
private String lastActionBar = null;
@SubscribeEvent(priority = EventPriority.HIGHEST, receiveCanceled = true)
public void onChatReceived(ClientChatReceivedEvent event) {
if (event.type == 2) {
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(), 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);
updateWithPercentage.put(skillS.toLowerCase(), xpPercentage);
} 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(), skillInfo);
return;
}
}
}
}
}
}
public void updateLevel(String skill, int level) {
if (updateWithPercentage.containsKey(skill)) {
JsonObject leveling = Constants.LEVELING;
if (leveling == null) return;
SkillInfo skillInfo = new SkillInfo();
skillInfo.totalXp = 0;
skillInfo.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) {
skillInfo.currentXp += updateWithPercentage.get(skill) / 100f * cap;
skillInfo.totalXp += skillInfo.currentXp;
skillInfo.currentXpMax = cap;
break;
} else {
skillInfo.totalXp += cap;
}
}
SkillInfo old = skillInfoMap.get(skill.toLowerCase());
if (old.totalXp <= skillInfo.totalXp) {
correctionCounter--;
if (correctionCounter < 0) correctionCounter = 0;
skillInfoMap.put(skill.toLowerCase(), skillInfo);
} else if (++correctionCounter >= 10) {
correctionCounter = 0;
skillInfoMap.put(skill.toLowerCase(), skillInfo);
}
}
updateWithPercentage.clear();
}
public void tick() {
ProfileApiSyncer.getInstance().requestResync("xpinformation", 5 * 60 * 1000,
() -> {
}, this::onApiUpdated
);
}
private static final String[] skills = {
"taming",
"mining",
"foraging",
"enchanting",
"carpentry",
"farming",
"combat",
"fishing",
"alchemy",
"runecrafting"
};
private void onApiUpdated(ProfileViewer.Profile profile) {
Map skyblockInfo = profile.getSkyblockInfo(null);
for (String skill : skills) {
SkillInfo info = new SkillInfo();
ProfileViewer.Level levelInfo = skyblockInfo.get(skill);
float level = levelInfo.level;
info.totalXp = levelInfo.totalXp;
info.currentXpMax = levelInfo.maxXpForLevel;
info.level = (int) level;
info.currentXp = (level % 1) * info.currentXpMax;
info.fromApi = true;
skillInfoMap.put(skill.toLowerCase(), info);
}
}
}