diff options
author | Linnea Gräf <nea@nea.moe> | 2024-02-15 18:05:28 +0100 |
---|---|---|
committer | Linnea Gräf <nea@nea.moe> | 2024-02-15 18:05:28 +0100 |
commit | ee5d205578117d6fab9b2f89871e5442e480644f (patch) | |
tree | 60753f880972e85e39ed9c1e98bb534253cbd535 /src | |
download | money-ledger-ee5d205578117d6fab9b2f89871e5442e480644f.tar.gz money-ledger-ee5d205578117d6fab9b2f89871e5442e480644f.tar.bz2 money-ledger-ee5d205578117d6fab9b2f89871e5442e480644f.zip |
Initial commit
Diffstat (limited to 'src')
-rw-r--r-- | src/main/java/moe/nea/ledger/init/AutoDiscoveryMixinPlugin.java | 188 | ||||
-rw-r--r-- | src/main/kotlin/moe/nea/ledger/BankDetection.kt | 40 | ||||
-rw-r--r-- | src/main/kotlin/moe/nea/ledger/ChatReceived.kt | 15 | ||||
-rw-r--r-- | src/main/kotlin/moe/nea/ledger/Ledger.kt | 51 | ||||
-rw-r--r-- | src/main/kotlin/moe/nea/ledger/LedgerLogger.kt | 34 | ||||
-rw-r--r-- | src/main/kotlin/moe/nea/ledger/NumberUtil.kt | 32 | ||||
-rw-r--r-- | src/main/resources/mcmod.info | 18 | ||||
-rw-r--r-- | src/main/resources/mixins.ledger.json | 8 |
8 files changed, 386 insertions, 0 deletions
diff --git a/src/main/java/moe/nea/ledger/init/AutoDiscoveryMixinPlugin.java b/src/main/java/moe/nea/ledger/init/AutoDiscoveryMixinPlugin.java new file mode 100644 index 0000000..2c3a9c7 --- /dev/null +++ b/src/main/java/moe/nea/ledger/init/AutoDiscoveryMixinPlugin.java @@ -0,0 +1,188 @@ +package moe.nea.ledger.init; + +import org.spongepowered.asm.lib.tree.ClassNode; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * A mixin plugin to automatically discover all mixins in the current JAR. + * <p> + * This mixin plugin automatically scans your entire JAR (or class directory, in case of an in-IDE launch) for classes inside of your + * mixin package and registers those. It does this recursively for sub packages of the mixin package as well. This means you will need + * to only have mixin classes inside of your mixin package, which is good style anyway. + * + * @author Linnea Gräf + */ +public class AutoDiscoveryMixinPlugin implements IMixinConfigPlugin { + private static final List<AutoDiscoveryMixinPlugin> mixinPlugins = new ArrayList<>(); + + public static List<AutoDiscoveryMixinPlugin> getMixinPlugins() { + return mixinPlugins; + } + + private String mixinPackage; + + @Override + public void onLoad(String mixinPackage) { + this.mixinPackage = mixinPackage; + mixinPlugins.add(this); + } + + /** + * Resolves the base class root for a given class URL. This resolves either the JAR root, or the class file root. + * In either case the return value of this + the class name will resolve back to the original class url, or to other + * class urls for other classes. + */ + public URL getBaseUrlForClassUrl(URL classUrl) { + String string = classUrl.toString(); + if (classUrl.getProtocol().equals("jar")) { + try { + return new URL(string.substring(4).split("!")[0]); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + if (string.endsWith(".class")) { + try { + return new URL(string.replace("\\", "/") + .replace(getClass().getCanonicalName() + .replace(".", "/") + ".class", "")); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + return classUrl; + } + + /** + * Get the package that contains all the mixins. This value is set by mixin itself using {@link #onLoad}. + */ + public String getMixinPackage() { + return mixinPackage; + } + + /** + * Get the path inside the class root to the mixin package + */ + public String getMixinBaseDir() { + return mixinPackage.replace(".", "/"); + } + + /** + * A list of all discovered mixins. + */ + private List<String> mixins = null; + + /** + * Try to add mixin class ot the mixins based on the filepath inside of the class root. + * Removes the {@code .class} file suffix, as well as the base mixin package. + * <p><b>This method cannot be called after mixin initialization.</p> + * + * @param className the name or path of a class to be registered as a mixin. + */ + public void tryAddMixinClass(String className) { + String norm = (className.endsWith(".class") ? className.substring(0, className.length() - ".class".length()) : className) + .replace("\\", "/") + .replace("/", "."); + if (norm.startsWith(getMixinPackage() + ".") && !norm.endsWith(".")) { + mixins.add(norm.substring(getMixinPackage().length() + 1)); + } + } + + /** + * Search through the JAR or class directory to find mixins contained in {@link #getMixinPackage()} + */ + @Override + public List<String> getMixins() { + if (mixins != null) return mixins; + System.out.println("Trying to discover mixins"); + mixins = new ArrayList<>(); + URL classUrl = getClass().getProtectionDomain().getCodeSource().getLocation(); + System.out.println("Found classes at " + classUrl); + Path file; + try { + file = Paths.get(getBaseUrlForClassUrl(classUrl).toURI()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + System.out.println("Base directory found at " + file); + if (Files.isDirectory(file)) { + walkDir(file); + } else { + walkJar(file); + } + System.out.println("Found mixins: " + mixins); + + return mixins; + } + + /** + * Search through directory for mixin classes based on {@link #getMixinBaseDir}. + * + * @param classRoot The root directory in which classes are stored for the default package. + */ + private void walkDir(Path classRoot) { + System.out.println("Trying to find mixins from directory"); + try (Stream<Path> classes = Files.walk(classRoot.resolve(getMixinBaseDir()))) { + classes.map(it -> classRoot.relativize(it).toString()) + .forEach(this::tryAddMixinClass); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Read through a JAR file, trying to find all mixins inside. + */ + private void walkJar(Path file) { + System.out.println("Trying to find mixins from jar file"); + try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(file))) { + ZipEntry next; + while ((next = zis.getNextEntry()) != null) { + tryAddMixinClass(next.getName()); + zis.closeEntry(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + + } + + @Override + public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + + } + + @Override + public String getRefMapperConfig() { + return null; + } + + @Override + public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { + return true; + } + + @Override + public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) { + + } +} diff --git a/src/main/kotlin/moe/nea/ledger/BankDetection.kt b/src/main/kotlin/moe/nea/ledger/BankDetection.kt new file mode 100644 index 0000000..6e54539 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/BankDetection.kt @@ -0,0 +1,40 @@ +package moe.nea.ledger + +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import java.util.regex.Pattern + +class BankDetection(val ledger: LedgerLogger) { + + /* + You have withdrawn 1M coins! You now have 518M coins in your account! + You have deposited 519M coins! You now have 519M coins in your account! + */ + + + val withdrawPattern = + Pattern.compile("^You have withdrawn (?<amount>$SHORT_NUMBER_PATTERN) coins?! You now have (?<newtotal>$SHORT_NUMBER_PATTERN) coins? in your account!$") + val depositPattern = + Pattern.compile("^You have deposited (?<amount>$SHORT_NUMBER_PATTERN) coins?! You now have (?<newtotal>$SHORT_NUMBER_PATTERN) coins? in your account!$") + @SubscribeEvent + fun onChat(event: ChatReceived) { + withdrawPattern.useMatcher(event.message) { + ledger.logEntry( + LedgerEntry( + "BANK_WITHDRAW", + event.timestamp, + parseShortNumber(group("amount")), + ) + ) + } + depositPattern.useMatcher(event.message) { + ledger.logEntry( + LedgerEntry( + "BANK_DEPOSIT", + event.timestamp, + parseShortNumber(group("amount")), + ) + ) + } + } + +} diff --git a/src/main/kotlin/moe/nea/ledger/ChatReceived.kt b/src/main/kotlin/moe/nea/ledger/ChatReceived.kt new file mode 100644 index 0000000..5e19083 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/ChatReceived.kt @@ -0,0 +1,15 @@ +package moe.nea.ledger + +import net.minecraftforge.client.event.ClientChatReceivedEvent +import net.minecraftforge.fml.common.eventhandler.Event +import java.time.Instant + +data class ChatReceived( + val message: String, + val timestamp: Instant = Instant.now() +) : Event() { + constructor(event: ClientChatReceivedEvent) : this( + event.message.unformattedText + .replace("§.".toRegex(), "") + ) +}
\ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/Ledger.kt b/src/main/kotlin/moe/nea/ledger/Ledger.kt new file mode 100644 index 0000000..63be626 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/Ledger.kt @@ -0,0 +1,51 @@ +package moe.nea.ledger + +import net.minecraftforge.client.event.ClientChatReceivedEvent +import net.minecraftforge.common.MinecraftForge +import net.minecraftforge.fml.common.Mod +import net.minecraftforge.fml.common.event.FMLInitializationEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +@Mod(modid = "ledger", useMetadata = true) +class Ledger { + /* + You have withdrawn 1M coins! You now have 518M coins in your account! + You have deposited 519M coins! You now have 519M coins in your account! + + // ORDERS: + + [Bazaar] Buy Order Setup! 160x Wheat for 720.0 coins. + [Bazaar] Claimed 160x Wheat worth 720.0 coins bought for 4.5 each! + + [Bazaar] Sell Offer Setup! 160x Wheat for 933.4 coins. + [Bazaar] Claimed 34,236,799 coins from selling 176x Hyper Catalyst at 196,741 each! + + // INSTABUY: + + [Bazaar] Bought 64x Wheat for 377.6 coins! + [Bazaar] Sold 64x Wheat for 268.8 coins! + + // AUCTION HOUSE: + + You collected 8,712,000 coins from selling Ultimate Carrot Candy Upgrade to [VIP] kodokush in an auction! + You purchased 2x Walnut for 69 coins! + You purchased ◆ Ice Rune I for 4,000 coins! + + TODO: TRADING, FORGE, COOKIE_EATEN, NPC_SELL, NPC_BUY + */ + + @Mod.EventHandler + fun init(event: FMLInitializationEvent) { + val ledger = LedgerLogger() + listOf( + this, + BankDetection(ledger), + ).forEach(MinecraftForge.EVENT_BUS::register) + } + + @SubscribeEvent + fun onChat(event: ClientChatReceivedEvent) { + if (event.type != 2.toByte()) + MinecraftForge.EVENT_BUS.post(ChatReceived(event)) + } +} diff --git a/src/main/kotlin/moe/nea/ledger/LedgerLogger.kt b/src/main/kotlin/moe/nea/ledger/LedgerLogger.kt new file mode 100644 index 0000000..4692a13 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/LedgerLogger.kt @@ -0,0 +1,34 @@ +package moe.nea.ledger + +import net.minecraft.client.Minecraft +import net.minecraft.util.ChatComponentText +import java.time.Instant + +class LedgerLogger { + fun printOut(text: String) { + Minecraft.getMinecraft().ingameGUI?.chatGUI?.printChatMessage(ChatComponentText(text)) + } + + fun logEntry(entry: LedgerEntry) { + printOut( + """ + §e================= TRANSACTION START + §eTYPE: §a${entry.transactionType} + §eTIMESTAMP: §a${entry.timestamp} + §eTOTAL VALUE: §a${entry.totalTransactionCoins} + §eITEM ID: §a${entry.itemId} + §eITEM AMOUNT: §a${entry.itemAmount} + §e================= TRANSACTION END + """.trimIndent() + ) + } + +} + +data class LedgerEntry( + val transactionType: String, + val timestamp: Instant, + val totalTransactionCoins: Double, + val itemId: String? = null, + val itemAmount: Int? = null, +) diff --git a/src/main/kotlin/moe/nea/ledger/NumberUtil.kt b/src/main/kotlin/moe/nea/ledger/NumberUtil.kt new file mode 100644 index 0000000..8aa9bb8 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/NumberUtil.kt @@ -0,0 +1,32 @@ +package moe.nea.ledger + +import java.util.regex.Matcher +import java.util.regex.Pattern + +// language=regexp +val SHORT_NUMBER_PATTERN = "[0-9]+(?:,[0-9]+)*(?:\\.[0-9]+)?[kKmMbB]?" + +val siScalars = mapOf( + 'k' to 1_000.0, + 'K' to 1_000.0, + 'm' to 1_000_000.0, + 'M' to 1_000_000.0, + 'b' to 1_000_000_000.0, + 'B' to 1_000_000_000.0, +) + +fun parseShortNumber(string: String): Double { + var k = string.replace(",", "") + val scalar = k.last() + var scalarMultiplier = siScalars[scalar] + if (scalarMultiplier == null) { + scalarMultiplier = 1.0 + } else { + k = k.dropLast(1) + } + return k.toDouble() * scalarMultiplier +} + +fun <T> Pattern.useMatcher(string: String, block: Matcher.() -> T): T? = + matcher(string).takeIf { it.matches() }?.let(block) + diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info new file mode 100644 index 0000000..42456c4 --- /dev/null +++ b/src/main/resources/mcmod.info @@ -0,0 +1,18 @@ +[ + { + "modid": "${modid}", + "name": "Simple Ledger Mod", + "description": "Export all your activities into a file", + "version": "${version}", + "mcversion": "${mcversion}", + "url": "https://github.com/romangraef/Forge1.8.9Template/", + "updateUrl": "", + "authorList": [ + "nea89" + ], + "credits": "", + "logoFile": "", + "screenshots": [], + "dependencies": [] + } +]
\ No newline at end of file diff --git a/src/main/resources/mixins.ledger.json b/src/main/resources/mixins.ledger.json new file mode 100644 index 0000000..5ea0c57 --- /dev/null +++ b/src/main/resources/mixins.ledger.json @@ -0,0 +1,8 @@ +{ + "package": "${basePackage}.mixin", + "plugin": "${basePackage}.init.AutoDiscoveryMixinPlugin", + "refmap": "mixins.${modid}.refmap.json", + "minVersion": "0.7", + "compatibilityLevel": "JAVA_8", + "__comment": "You do not need to manually register mixins in this template. Check the auto discovery mixin plugin for more info." +} |