From 7dc51e6f1f280276d782e3c644099758ff01bdb7 Mon Sep 17 00:00:00 2001 From: Gravy Boat Date: Thu, 23 Sep 2021 19:25:26 -0230 Subject: Initial Commit --- .../java/dev/asbyth/template/ForgeTemplate.java | 15 - .../thatgravyboat/rewardclaim/MappedImageCache.kt | 16 ++ .../tech/thatgravyboat/rewardclaim/RewardClaim.kt | 56 ++++ .../rewardclaim/RewardConfiguration.kt | 49 ++++ .../thatgravyboat/rewardclaim/RewardLanguage.kt | 20 ++ .../rewardclaim/adapter/KotlinLanguageAdapter.kt | 27 ++ .../thatgravyboat/rewardclaim/types/GameMode.kt | 42 +++ .../thatgravyboat/rewardclaim/types/RewardData.kt | 82 ++++++ .../thatgravyboat/rewardclaim/types/RewardImage.kt | 15 + .../rewardclaim/types/RewardRarity.kt | 11 + .../thatgravyboat/rewardclaim/types/StreakData.kt | 3 + .../thatgravyboat/rewardclaim/types/WebData.kt | 60 ++++ .../thatgravyboat/rewardclaim/ui/RewardClaimGui.kt | 308 +++++++++++++++++++++ .../tech/thatgravyboat/rewardclaim/ui/UIButton.kt | 51 ++++ .../tech/thatgravyboat/rewardclaim/ui/UIPopup.kt | 70 +++++ .../tech/thatgravyboat/rewardclaim/ui/UIReward.kt | 103 +++++++ .../rewardclaim/ui/UISelectedReward.kt | 86 ++++++ src/main/resources/mcmod.info | 6 +- src/main/resources/rewardclaim/external_link.png | Bin 0 -> 187 bytes 19 files changed, 1002 insertions(+), 18 deletions(-) delete mode 100644 src/main/java/dev/asbyth/template/ForgeTemplate.java create mode 100644 src/main/kotlin/tech/thatgravyboat/rewardclaim/MappedImageCache.kt create mode 100644 src/main/kotlin/tech/thatgravyboat/rewardclaim/RewardClaim.kt create mode 100644 src/main/kotlin/tech/thatgravyboat/rewardclaim/RewardConfiguration.kt create mode 100644 src/main/kotlin/tech/thatgravyboat/rewardclaim/RewardLanguage.kt create mode 100644 src/main/kotlin/tech/thatgravyboat/rewardclaim/adapter/KotlinLanguageAdapter.kt create mode 100644 src/main/kotlin/tech/thatgravyboat/rewardclaim/types/GameMode.kt create mode 100644 src/main/kotlin/tech/thatgravyboat/rewardclaim/types/RewardData.kt create mode 100644 src/main/kotlin/tech/thatgravyboat/rewardclaim/types/RewardImage.kt create mode 100644 src/main/kotlin/tech/thatgravyboat/rewardclaim/types/RewardRarity.kt create mode 100644 src/main/kotlin/tech/thatgravyboat/rewardclaim/types/StreakData.kt create mode 100644 src/main/kotlin/tech/thatgravyboat/rewardclaim/types/WebData.kt create mode 100644 src/main/kotlin/tech/thatgravyboat/rewardclaim/ui/RewardClaimGui.kt create mode 100644 src/main/kotlin/tech/thatgravyboat/rewardclaim/ui/UIButton.kt create mode 100644 src/main/kotlin/tech/thatgravyboat/rewardclaim/ui/UIPopup.kt create mode 100644 src/main/kotlin/tech/thatgravyboat/rewardclaim/ui/UIReward.kt create mode 100644 src/main/kotlin/tech/thatgravyboat/rewardclaim/ui/UISelectedReward.kt create mode 100644 src/main/resources/rewardclaim/external_link.png (limited to 'src/main') diff --git a/src/main/java/dev/asbyth/template/ForgeTemplate.java b/src/main/java/dev/asbyth/template/ForgeTemplate.java deleted file mode 100644 index 959cdbc..0000000 --- a/src/main/java/dev/asbyth/template/ForgeTemplate.java +++ /dev/null @@ -1,15 +0,0 @@ -package dev.asbyth.template; - -import net.minecraft.client.Minecraft; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.common.event.FMLInitializationEvent; - -@Mod(modid = "forgetemplate", name = "Forge Template", version = "1.0") -public class ForgeTemplate { - @Mod.EventHandler - public void onFMLInitialization(FMLInitializationEvent event) { - // $USER = The username of the currently logged in user. - // Simply prints out Hello, $USER. - System.out.println("Hello, " + Minecraft.getMinecraft().getSession().getUsername() + "!"); - } -} diff --git a/src/main/kotlin/tech/thatgravyboat/rewardclaim/MappedImageCache.kt b/src/main/kotlin/tech/thatgravyboat/rewardclaim/MappedImageCache.kt new file mode 100644 index 0000000..e47c649 --- /dev/null +++ b/src/main/kotlin/tech/thatgravyboat/rewardclaim/MappedImageCache.kt @@ -0,0 +1,16 @@ +package tech.thatgravyboat.rewardclaim + +import gg.essential.elementa.components.image.ImageCache +import java.awt.image.BufferedImage +import java.net.URL + +object MappedImageCache : ImageCache { + + private val IMAGES = hashMapOf() + + override fun get(url: URL) = IMAGES[url] + + override fun set(url: URL, image: BufferedImage) { + IMAGES[url] = image + } +} \ No newline at end of file diff --git a/src/main/kotlin/tech/thatgravyboat/rewardclaim/RewardClaim.kt b/src/main/kotlin/tech/thatgravyboat/rewardclaim/RewardClaim.kt new file mode 100644 index 0000000..29628ae --- /dev/null +++ b/src/main/kotlin/tech/thatgravyboat/rewardclaim/RewardClaim.kt @@ -0,0 +1,56 @@ +package tech.thatgravyboat.rewardclaim + +import gg.essential.api.EssentialAPI +import net.minecraft.client.gui.GuiScreenBook +import net.minecraftforge.client.event.ClientChatReceivedEvent +import net.minecraftforge.client.event.GuiOpenEvent +import net.minecraftforge.common.MinecraftForge +import net.minecraftforge.fml.common.Mod +import net.minecraftforge.fml.common.event.FMLInitializationEvent +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import tech.thatgravyboat.rewardclaim.ui.RewardClaimGui + +@Mod(name = "RewardClaim", modid = "gravyrewardclaim", version = "1.0.0", modLanguageAdapter = "tech.thatgravyboat.rewardclaim.adapter.KotlinLanguageAdapter") +object ForgeTemplate { + + private var rewardClaimTime: Long = 0 + + @Mod.EventHandler + fun onFMLInitialization(event: FMLInitializationEvent?) { + MinecraftForge.EVENT_BUS.register(this) + } + + @Mod.EventHandler + fun onPreInit(event: FMLPreInitializationEvent?) { + RewardConfiguration.loadData() + } + + @SubscribeEvent + fun onChatMessage(event: ClientChatReceivedEvent) { + val rewardMatcher = RewardConfiguration.rewardMessageRegex.matcher(event.message.unformattedText.trim()) + val rewardMissedMatcher = RewardConfiguration.rewardMissedMessageRegex.matcher(event.message.unformattedText.trim()) + + if (rewardMatcher.matches()) { + EssentialAPI.getGuiUtil().openScreen(RewardClaimGui(rewardMatcher.group("id"))) + rewardClaimTime = System.currentTimeMillis() + } + if (rewardMissedMatcher.matches()) { + EssentialAPI.getNotifications().push( + "Reward Claim Missed!", + "You missed a reward claim, click on this to open the reward claim gui to claim your reward.", + { EssentialAPI.getGuiUtil().openScreen(RewardClaimGui(rewardMissedMatcher.group("id"))) } + ) + event.isCanceled = true + } + } + + @SubscribeEvent + fun onScreen(event: GuiOpenEvent) { + if (EssentialAPI.getGuiUtil().openedScreen() is RewardClaimGui && event.gui is GuiScreenBook && System.currentTimeMillis() - rewardClaimTime <= 3000){ + event.isCanceled = true + rewardClaimTime = 0 + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/tech/thatgravyboat/rewardclaim/RewardConfiguration.kt b/src/main/kotlin/tech/thatgravyboat/rewardclaim/RewardConfiguration.kt new file mode 100644 index 0000000..3d8a79a --- /dev/null +++ b/src/main/kotlin/tech/thatgravyboat/rewardclaim/RewardConfiguration.kt @@ -0,0 +1,49 @@ +package tech.thatgravyboat.rewardclaim + +import com.google.gson.Gson +import com.google.gson.JsonObject +import net.minecraft.util.JsonUtils +import tech.thatgravyboat.rewardclaim.types.RewardImage +import java.io.IOException +import java.net.URL +import java.util.* +import java.util.regex.Pattern + +private val GSON = Gson() + +object RewardConfiguration { + val TEXTURES: MutableMap = HashMap() + var rewardMessageRegex: Pattern = Pattern.compile("Click the link to visit our website and claim your reward: https://rewards\\.hypixel\\.net/claim-reward/(?[A-Za-z0-9]{8})") + var rewardMissedMessageRegex: Pattern = Pattern.compile("We noticed you haven't claimed your free Daily Reward yet!\\nTo choose your reward you have to click the link to visit our website! As a reminder, here's your link for today: https://rewards\\.hypixel\\.net/claim-reward/(?[A-Za-z0-9]{8})") + var userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.54 Safari/537.36" + + fun loadData() { + val json = GSON.fromJson(readData(), JsonObject::class.java) + + json.get("textures")?.apply { + for (texture in asJsonArray) { + val image = texture.asJsonObject + TEXTURES[image["id"].asString] = RewardImage(image["url"].asString, JsonUtils.getInt(image, "height", 142)) + } + } + + json.get("rewardRegex")?.apply { rewardMessageRegex = Pattern.compile(asString) } + json.get("missedRewardRegex")?.apply { rewardMissedMessageRegex = Pattern.compile(asString) } + json.get("userAgent")?.apply { userAgent = asString } + } + + private fun readData(): String { + try { + Scanner( + URL("https://gist.githubusercontent.com/ThatGravyBoat/05cf118ea1daced936f040a41a648819/raw/2410c868444b073fd212fbed1da5a063d79dc816/data.json").openStream(), + "UTF-8" + ).use { scanner -> + scanner.useDelimiter("\\A") + return if (scanner.hasNext()) scanner.next() else "" + } + } catch (e: IOException) { + e.printStackTrace() + } + return "" + } +} \ No newline at end of file diff --git a/src/main/kotlin/tech/thatgravyboat/rewardclaim/RewardLanguage.kt b/src/main/kotlin/tech/thatgravyboat/rewardclaim/RewardLanguage.kt new file mode 100644 index 0000000..6361bc5 --- /dev/null +++ b/src/main/kotlin/tech/thatgravyboat/rewardclaim/RewardLanguage.kt @@ -0,0 +1,20 @@ +package tech.thatgravyboat.rewardclaim + +import java.util.regex.Pattern + +private val TRANSLATION_LINE_REGEX = Pattern.compile("\"(?.*)\": ?\"(?.*)\",?") + +class RewardLanguage(translationDataFromHtml: String) { + + private val translations = hashMapOf() + + fun translate(key: String) = translations.getValue(key) + + //We have to do it this way as this is easier than fixing all the things that isn't + //valid in normal json but is valid in javascript objects. Such as escaped single quotes and trailing commas. + init { + TRANSLATION_LINE_REGEX.matcher(translationDataFromHtml.replace("\\'", "'")).apply { + while (find()) translations[group("key")] = group("text") + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/tech/thatgravyboat/rewardclaim/adapter/KotlinLanguageAdapter.kt b/src/main/kotlin/tech/thatgravyboat/rewardclaim/adapter/KotlinLanguageAdapter.kt new file mode 100644 index 0000000..11b3188 --- /dev/null +++ b/src/main/kotlin/tech/thatgravyboat/rewardclaim/adapter/KotlinLanguageAdapter.kt @@ -0,0 +1,27 @@ +package tech.thatgravyboat.rewardclaim.adapter + +import net.minecraftforge.fml.common.FMLModContainer +import net.minecraftforge.fml.common.ILanguageAdapter +import net.minecraftforge.fml.common.ModContainer +import net.minecraftforge.fml.relauncher.Side +import java.lang.reflect.Field +import java.lang.reflect.Method + +class KotlinLanguageAdapter : ILanguageAdapter { + override fun supportsStatics() = false + + override fun setProxy(target: Field, proxyTarget: Class<*>, proxy: Any) { + target.set(proxyTarget.getDeclaredField("INSTANCE").get(null), proxy) + } + + override fun getNewInstance( + container: FMLModContainer, + objectClass: Class<*>, + classLoader: ClassLoader, + factoryMarkedAnnotation: Method? + ): Any { + return objectClass.fields.find { it.name == "INSTANCE" }?.get(null) ?: objectClass.newInstance() + } + + override fun setInternalProxies(mod: ModContainer?, side: Side?, loader: ClassLoader?) {} +} \ No newline at end of file diff --git a/src/main/kotlin/tech/thatgravyboat/rewardclaim/types/GameMode.kt b/src/main/kotlin/tech/thatgravyboat/rewardclaim/types/GameMode.kt new file mode 100644 index 0000000..fca199b --- /dev/null +++ b/src/main/kotlin/tech/thatgravyboat/rewardclaim/types/GameMode.kt @@ -0,0 +1,42 @@ +package tech.thatgravyboat.rewardclaim.types + +@Suppress("unused") +enum class GameMode(val displayName: String) { + ERROR("Error"), + BEDWARS("Bed Wars"), + SKYWARS("SkyWars"), + PROTOTYPE("Prototype"), + SKYBLOCK("SkyBlock"), + MAIN("Main"), + MURDER_MYSTERY("Murder Mystery"), + HOUSING("Housing"), + ARCADE("Arcade"), + BUILD_BATTLE("Build Battle"), + DUELS("Duels"), + PIT("PIT"), + UHC("UHC"), + SPEED_UHC("Speed UHC"), + TNTGAMES("TNT Games"), + LEGACY("Classic"), + QUAKECRAFT("Quakecraft"), + WALLS("Walls"), + PAINTBALL("Paintball"), + VAMPIREZ("VampireZ"), + ARENA("Arena"), + GINGERBREAD("Turbo Kart Racers"), + MCGO("Cops and Crims"), + SURVIVAL_GAMES("Blitz SG"), + WALLS3("Mega Walls"), + SUPER_SMASH("Smash Heroes"), + BATTLEGROUND("Warlords"); + + companion object { + fun getModeFromId(id: String?): GameMode { + return try { + valueOf(id!!) + } catch (e: Exception) { + ERROR + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/tech/thatgravyboat/rewardclaim/types/RewardData.kt b/src/main/kotlin/tech/thatgravyboat/rewardclaim/types/RewardData.kt new file mode 100644 index 0000000..cc89422 --- /dev/null +++ b/src/main/kotlin/tech/thatgravyboat/rewardclaim/types/RewardData.kt @@ -0,0 +1,82 @@ +package tech.thatgravyboat.rewardclaim.types + +import tech.thatgravyboat.rewardclaim.RewardConfiguration +import tech.thatgravyboat.rewardclaim.RewardLanguage +import java.util.* +import java.util.regex.Pattern + + +private val ARMOR_PIECE_REGEX = Pattern.compile("^[a-z0-9_]+_([a-z]+)$", Pattern.CASE_INSENSITIVE) +private val ARMOR_REGEX = Pattern.compile("_([a-z]+)$", Pattern.CASE_INSENSITIVE) + +class RewardData(val rarity: RewardRarity, + private val reward : String, + val amount : Int?, + val boxes : Int?, + private val gameMode : GameMode?, + private val rewardPackage : String?, + private val rewardKey : String? + ) { + + fun getDisplayName(language: RewardLanguage): String { + rewardPackage?.let { item -> + if (reward.equals("housing_package", ignoreCase = true)) { + return "${rarity.color}${language.translate("housing.skull." + item.replace("specialoccasion_reward_card_skull_", ""))}" + } + } + rewardKey?.let { key -> + if (reward.equals("add_vanity", ignoreCase = true)) { + val pieceMatcher = ARMOR_PIECE_REGEX.matcher(key) + val armorMatcher = ARMOR_REGEX.matcher(key) + if ("suit" in key && pieceMatcher.find() && armorMatcher.find()) { + return "${rarity.color}${language.translate("vanity." + armorMatcher.group(1))} ${language.translate("vanity.armor." + pieceMatcher.group(1))}" + } else if ("emote" in key || "taunt" in key) { + return "${rarity.color}${language.translate("vanity.$key")}" + } + } + } + return if (reward.equals("tokens", ignoreCase = true) || reward.equals("coins", ignoreCase = true)) { + "${rarity.color}${language.translate("type.$reward").replace("{\$game}", gameMode!!.displayName)}" + } else { + "${rarity.color}${language.translate("type.$reward")}" + } + } + + fun getDescription(language: RewardLanguage): String { + rewardKey?.let { key -> + if (reward.equals("add_vanity", ignoreCase = true)) { + when { + "suit" in key -> return language.translate("vanity.suits.description") + "emote" in key -> return language.translate("vanity.emotes.description") + "taunt" in key -> return language.translate("vanity.gestures.description") + } + } + } + return if (reward.equals("tokens", ignoreCase = true) || reward.equals("coins", ignoreCase = true)) { + "${rarity.color}${language.translate("type.$reward.description").replace("{\$game}", gameMode!!.displayName)}" + } else { + "${rarity.color}${language.translate("type.$reward.description")}" + } + } + + val image: RewardImage? + get() { + var id = reward.lowercase(Locale.ROOT) + rewardKey?.let { key -> + if (id == "add_vanity") { + when { + "suit" in key -> id += "_suit" + "emote" in key -> id += "_emote" + "taunt" in key -> id += "_taunt" + } + } + } + rewardPackage?.let { item -> + if (id == "housing_package") { + val packageId = item.replace("specialoccasion_reward_card_skull_", "") + RewardConfiguration.TEXTURES[id + "_" + packageId]?.let { image -> return image } + } + } + return RewardConfiguration.TEXTURES[id] + } +} \ No newline at end of file diff --git a/src/main/kotlin/tech/thatgravyboat/rewardclaim/types/RewardImage.kt b/src/main/kotlin/tech/thatgravyboat/rewardclaim/types/RewardImage.kt new file mode 100644 index 0000000..fc8446a --- /dev/null +++ b/src/main/kotlin/tech/thatgravyboat/rewardclaim/types/RewardImage.kt @@ -0,0 +1,15 @@ +package tech.thatgravyboat.rewardclaim.types + +import java.net.MalformedURLException +import java.net.URL + +class RewardImage(private val urlString: String, val height: Int) { + val url: URL? + get() { + return try { + URL(urlString) + } catch (ignored: MalformedURLException) { + null + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/tech/thatgravyboat/rewardclaim/types/RewardRarity.kt b/src/main/kotlin/tech/thatgravyboat/rewardclaim/types/RewardRarity.kt new file mode 100644 index 0000000..d4e45b1 --- /dev/null +++ b/src/main/kotlin/tech/thatgravyboat/rewardclaim/types/RewardRarity.kt @@ -0,0 +1,11 @@ +package tech.thatgravyboat.rewardclaim.types + +import gg.essential.universal.ChatColor +import java.awt.Color + +enum class RewardRarity(val translationKey: String, val color: ChatColor) { + COMMON("rarity.common", ChatColor.WHITE), + RARE("rarity.rare", ChatColor.AQUA), + EPIC("rarity.epic", ChatColor.DARK_PURPLE), + LEGENDARY("rarity.legendary", ChatColor.GOLD); +} \ No newline at end of file diff --git a/src/main/kotlin/tech/thatgravyboat/rewardclaim/types/StreakData.kt b/src/main/kotlin/tech/thatgravyboat/rewardclaim/types/StreakData.kt new file mode 100644 index 0000000..4a5cb98 --- /dev/null +++ b/src/main/kotlin/tech/thatgravyboat/rewardclaim/types/StreakData.kt @@ -0,0 +1,3 @@ +package tech.thatgravyboat.rewardclaim.types + +class StreakData(val progress : Int, val current : Int, val highest : Int) \ No newline at end of file diff --git a/src/main/kotlin/tech/thatgravyboat/rewardclaim/types/WebData.kt b/src/main/kotlin/tech/thatgravyboat/rewardclaim/types/WebData.kt new file mode 100644 index 0000000..38318d1 --- /dev/null +++ b/src/main/kotlin/tech/thatgravyboat/rewardclaim/types/WebData.kt @@ -0,0 +1,60 @@ +package tech.thatgravyboat.rewardclaim.types + +import com.google.gson.Gson +import com.google.gson.JsonObject +import net.minecraft.util.JsonUtils +import tech.thatgravyboat.rewardclaim.RewardLanguage +import java.util.regex.Matcher + +private val GSON = Gson() + +class WebData(security : Matcher, data : Matcher, i18n : Matcher) { + + val securityToken : String = security.group("token") + val language : RewardLanguage = RewardLanguage(i18n.group("translations")) + + val rewards = mutableListOf() + var streak : StreakData = StreakData(0,0,0) + var activeAd : Int = 0 + var adLink : String = "https://store.hypixel.net/?utm_source=rewards-video&utm_medium=website&utm_content=TRsCiBNYY7M&utm_campaign=Rewards" + var skippable : Boolean = false + var duration : Int = 30 + + init { + val json = GSON.fromJson(data.group("data"), JsonObject::class.java) + json.get("rewards")?.let { + if (it.isJsonArray) { + it.asJsonArray.forEach { jsonObject -> + val rewardObject = jsonObject.asJsonObject + rewards.add(RewardData( + RewardRarity.valueOf(rewardObject.get("rarity").asString!!), + rewardObject.get("reward").asString!!, + if (rewardObject.has("amount")) rewardObject.get("amount").asInt else null, + if (rewardObject.has("intlist")) rewardObject.get("intlist").asJsonArray.size() else null, + if (rewardObject.has("gameType")) GameMode.getModeFromId(rewardObject.get("gameType").asString) else null, + JsonUtils.getString(rewardObject, "package", null), + JsonUtils.getString(rewardObject, "key", null), + )) + } + } + } + + json.get("dailyStreak")?.let { + val streakObject = it.asJsonObject + streak = StreakData(JsonUtils.getInt(streakObject, "value", 0), + JsonUtils.getInt(streakObject, "score", 0), + JsonUtils.getInt(streakObject, "highScore", 0) + ) + } + + json.get("activeAd")?.let { activeAd = it.asInt } + json.get("ad")?.let { jsonObject -> + val adObject = jsonObject.asJsonObject + adObject.get("link")?.let { adLink = it.asString } + adObject.get("duration")?.let { duration = it.asInt } + } + json.get("skippable")?.let { skippable = it.asBoolean } + + } + +} \ No newline at end of file diff --git a/src/main/kotlin/tech/thatgravyboat/rewardclaim/ui/RewardClaimGui.kt b/src/main/kotlin/tech/thatgravyboat/rewardclaim/ui/RewardClaimGui.kt new file mode 100644 index 0000000..8f1d535 --- /dev/null +++ b/src/main/kotlin/tech/thatgravyboat/rewardclaim/ui/RewardClaimGui.kt @@ -0,0 +1,308 @@ +package tech.thatgravyboat.rewardclaim.ui + +import gg.essential.api.utils.Multithreading.runAsync +import gg.essential.api.utils.Multithreading.schedule +import gg.essential.elementa.WindowScreen +import gg.essential.elementa.components.* +import gg.essential.elementa.constraints.CenterConstraint +import gg.essential.elementa.dsl.* +import gg.essential.universal.ChatColor +import gg.essential.universal.UDesktop +import gg.essential.vigilance.gui.VigilancePalette +import org.apache.commons.io.IOUtils +import tech.thatgravyboat.rewardclaim.RewardConfiguration +import tech.thatgravyboat.rewardclaim.types.WebData +import java.awt.Color +import java.net.* +import java.nio.charset.Charset +import java.util.concurrent.TimeUnit +import java.util.regex.Matcher +import java.util.regex.Pattern + +private val BUTTON_HOVER = Color(0, 212, 105) + +private val SECURITY_REGEX = Pattern.compile("window\\.securityToken = \"(?.*)\";") +private val DATA_REGEX = Pattern.compile("window\\.appData = '(?\\{.*})';") +private val I18N_REGEX = Pattern.compile("window.i18n = \\{(?.*)};", Pattern.DOTALL) + +class RewardClaimGui(private val id : String) : WindowScreen() { + + private var state: State = State.LOADING + private var selected = -1 + + //Raw Data + private lateinit var data: WebData + + init { + runAsync(Runnable { + try { + val cookieManager = CookieManager() + CookieHandler.setDefault(cookieManager) + val url = URL("https://rewards.hypixel.net/claim-reward/$id") + (url.openConnection() as HttpURLConnection).apply { + requestMethod = "GET" + useCaches = true + addRequestProperty("User-Agent", RewardConfiguration.userAgent) + readTimeout = 15000 + connectTimeout = 15000 + doOutput = true + inputStream.use { + val html = IOUtils.toString(it, Charset.defaultCharset()) + val securityMatcher: Matcher = SECURITY_REGEX.matcher(html) + val dataMatcher: Matcher = DATA_REGEX.matcher(html) + val i18nMatcher: Matcher = I18N_REGEX.matcher(html) + val securityFound = securityMatcher.find() + val dataFound = dataMatcher.find() + val i18nFound = i18nMatcher.find() + + if (securityFound && dataFound && i18nFound) { + data = WebData(securityMatcher, dataMatcher, i18nMatcher) + + if (data.rewards.isEmpty()) { + state = State.FAILED_REWARDS + errorPopup("Rewards were empty.") + }else { + state = State.SUCCESSFUL + updateElements() + } + + if (data.skippable || data.duration == 0){ + adPopup(true) + }else { + schedule({ adPopup(true) }, data.duration.toLong(), TimeUnit.SECONDS) + } + } else { + state = State.FAILED_REWARDS + errorPopup("Regex could not be found.\nSecurity: $securityFound\nI18n: $i18nFound\nData: $dataFound") + } + } + } + } catch (e: Exception) { + state = State.FAILED + errorPopup("Error: " + e.message) + e.printStackTrace() + } + }) + adPopup(false) + } + + private val background = UIBlock(VigilancePalette.getBackground()).constrain { + width = 100.percent() + height = 100.percent() + } childOf this.window + + private val selectedReward = UISelectedReward(30.percent() - 0.5.pixel()) childOf background + + init { + UIBlock(VigilancePalette.getDivider()).constrain { + width = 1.pixel() + height = 75.percent() + x = 50.percent() - 0.5.pixel() + y = 12.5.percent() + } childOf background + + UIBlock(VigilancePalette.getDivider()).constrain { + width = 1.pixel() + height = 75.percent() + x = 10.percent() - 0.5.pixel() + y = 12.5.percent() + } childOf background + + UIBlock(VigilancePalette.getDivider()).constrain { + width = 30.percent() - 1.pixel() + height = 1.pixel() + x = 15.percent() + y = 70.percent() + } childOf background + + UIText("Click to claim 1 of the 3 Rewards").apply { + constrain { + x = 30.percent() - 0.5.pixel() - (getTextWidth() / 2f).pixel() + y = 15.percent() + } childOf background + } + + UIText("Reward Options").apply { + constrain { + x = 75.percent() - (getTextWidth() / 2f).pixel() + y = 15.percent() + } childOf background + } + + UIText("§lClaim Reward", false).let { text -> + UIBlock(VigilancePalette.getAccent()).let { button -> + button.constrain { + width = text.getTextWidth().pixel() + 18.pixel() + height = 17.pixel() + y = 60.percent() + x = 30.percent() - 0.5.pixel() - (text.getTextWidth() / 2f + 9f).pixel() + } childOf background + + text.constrain { + x = CenterConstraint() + y = CenterConstraint() + } childOf button + + button.onMouseEnter { setColor(BUTTON_HOVER) } + button.onMouseLeave { setColor(VigilancePalette.getAccent()) } + button.onMouseClick { event -> + if (event.mouseButton == 0 && selected != -1) confirmPopup() + event.stopPropagation() + } + } + } + + UIText("Daily Streak").apply { + constrain { + x = 30.percent() - 0.5.pixel() - (getTextWidth() / 2f).pixel() + y = 72.percent() + } childOf background + } + + adPopup(false) + } + + //region Daily Streak + + private val streakSubHeading = UIText(String.format("Current: %d Highest: %d", 0, 0)).apply { + constrain { + x = 30.percent() - 0.5.pixel() - (getTextWidth() / 2f).pixel() + y = 77.percent() + } childOf background + } + + private val streaks = Array(9){ i -> + UICircle(5f, VigilancePalette.getDivider(), 15).let { + it.constrain { + x = ((15.percent() + (15.percent() - 0.5.pixel())) - 45.pixel()) + (it.getWidth() * i).pixel() + y = 83.percent() + } childOf background + } + } + + //endregion + + private val rewards = Array(3){ i -> UIReward(57.5.percent(), 30.percent() + (18 * i).percent()) childOf background}.also { + for (reward in it) { + reward.onMouseClick { event -> + if (event.mouseButton == 0 && state == State.SUCCESSFUL) { + for (j in 0..2) { + it[j].setSelected(it[j] == reward) + if (it[j] != event.currentTarget) continue + selected = j + selectedReward.updateInfo(data.rewards[selected], data.language) + } + } + } + reward.hide(true) + } + } + + private fun updateElements() { + data.let { + for (i in 0 until data.streak.progress) streaks[i].setColor(VigilancePalette.getAccent()) + streakSubHeading.setText(String.format("Current: %d Highest: %d", data.streak.current, data.streak.highest)) + streakSubHeading.setX(30.percent() - 0.5.pixel() - (streakSubHeading.getTextWidth() / 2f).pixel()) + + for (i in 0 until 3) { + rewards[i].let { + it.setData(data.rewards[i], data.language) + it.unhide(true) + } + } + } + } + + private var popup : UIPopup? = null + + private fun errorPopup(error : String) { + Window.enqueueRenderOperation { + popup?.let { + if (it.parent.hasParent){ + it.parent.removeChild(it) + popup = null + } + } + popup = UIPopup( + "${ChatColor.RED}An Error Occurred!", error, + UIImage.ofResourceCached("/vigilance/cancel.png"), + { restorePreviousScreen() }, + "${ChatColor.BOLD}Close", + UIImage.ofResourceCached("/rewardclaim/external_link.png"), + { UDesktop.browse(URI("https://rewards.hypixel.net/claim-reward/${id}")) }, + "${ChatColor.BOLD}Reward" + ) childOf this.window + } + } + + private fun confirmPopup() { + popup = UIPopup("Confirmation", "Are you sure you want to claim this reward. Click 'Back' if you would like to change which reward you would like to claim or 'Continue' if you like to go ahead with your reward.", + null, { removePopup() }, "${ChatColor.BOLD}Back", + null, { + runAsync { + try { + (URL("https://rewards.hypixel.net/claim-reward/claim?option=$selected&id=$id&activeAd=${data.activeAd}&_csrf=${data.securityToken}&watchedFallback=false").openConnection() as HttpURLConnection).apply { + requestMethod = "POST" + useCaches = true + addRequestProperty("User-Agent", RewardConfiguration.userAgent) + readTimeout = 15000 + connectTimeout = 15000 + responseCode + CookieManager.setDefault(null) + restorePreviousScreen() + } + }catch (ignored : Exception){ + //IGNORED + } + } + }, "${ChatColor.BOLD}Continue" + ) childOf this.window + } + + private fun removePopup() { + Window.enqueueRenderOperation { + popup?.let { + if (it.parent.hasParent){ + it.parent.removeChild(it) + popup = null + } + } + } + } + + private fun adPopup(skip: Boolean) { + Window.enqueueRenderOperation { + popup?.let { + if (it.parent.hasParent){ + it.parent.removeChild(it) + popup = null + } + } + popup = if (skip) { + UIPopup( + "Reward Claim AD", + "Hypixel is a great server and as such we don't want to remove their ad for their store. You can click the skip button when it appears to remove this message and claim your reward.", + UIImage.ofResourceCached("/rewardclaim/external_link.png"), { UDesktop.browse(URI(data.adLink)) }, "${ChatColor.BOLD}Store", + null, { removePopup() }, "${ChatColor.BOLD}Skip" + ) childOf this.window + } else { + UIPopup( + "Reward Claim AD", + "Hypixel is a great server and as such we don't want to remove their ad for their store. You can click the skip button when it appears to remove this message and claim your reward.", + UIImage.ofResourceCached("/rewardclaim/external_link.png"), { UDesktop.browse(URI(data.adLink)) }, "${ChatColor.BOLD}Store" + ) childOf this.window + } + } + } + + override fun onDrawScreen(mouseX: Int, mouseY: Int, partialTicks: Float) { + this.window.draw() + } + + private enum class State { + LOADING, + FAILED, + SUCCESSFUL, + FAILED_REWARDS + } +} \ No newline at end of file diff --git a/src/main/kotlin/tech/thatgravyboat/rewardclaim/ui/UIButton.kt b/src/main/kotlin/tech/thatgravyboat/rewardclaim/ui/UIButton.kt new file mode 100644 index 0000000..e1037d0 --- /dev/null +++ b/src/main/kotlin/tech/thatgravyboat/rewardclaim/ui/UIButton.kt @@ -0,0 +1,51 @@ +package tech.thatgravyboat.rewardclaim.ui + +import gg.essential.elementa.components.UIBlock +import gg.essential.elementa.components.UIImage +import gg.essential.elementa.components.UIText +import gg.essential.elementa.constraints.CenterConstraint +import gg.essential.elementa.constraints.XConstraint +import gg.essential.elementa.dsl.* +import gg.essential.vigilance.gui.VigilancePalette +import java.awt.Color + +private val BUTTON_HOVER = Color(0, 212, 105) + +class UIButton(private val image : UIImage?, private val widthIn : Float, private var xConstraint : XConstraint, private val text : UIText, alignment: Alignment) : UIBlock(VigilancePalette.getAccent()) { + + init { + if (alignment == Alignment.LEFT) { + xConstraint += (widthIn / 2f + 9f).pixel() + } else if (alignment == Alignment.RIGHT) { + xConstraint -= (widthIn / 2f + 9f).pixel() + } + + constrain { + width = widthIn.pixel() + height = 17.pixel() + x = xConstraint + y = 95.percent() - 17.pixel() + } + + text.constrain { + x = CenterConstraint() - (if (image != null) 9 else 0).pixel() + y = CenterConstraint() + } childOf this + + image?.let { + it.constrain { + width = 9.pixel() + height = 9.pixel() + x = 100.percent() + 9.pixel() + y = CenterConstraint() + } childOf text + } + this.onMouseEnter { this.setColor(BUTTON_HOVER) } + this.onMouseLeave { this.setColor(VigilancePalette.getAccent()) } + } + +} + +enum class Alignment { + LEFT, RIGHT, MIDDLE +} \ No newline at end of file diff --git a/src/main/kotlin/tech/thatgravyboat/rewardclaim/ui/UIPopup.kt b/src/main/kotlin/tech/thatgravyboat/rewardclaim/ui/UIPopup.kt new file mode 100644 index 0000000..a3b4363 --- /dev/null +++ b/src/main/kotlin/tech/thatgravyboat/rewardclaim/ui/UIPopup.kt @@ -0,0 +1,70 @@ +package tech.thatgravyboat.rewardclaim.ui + +import gg.essential.elementa.UIComponent +import gg.essential.elementa.components.UIBlock +import gg.essential.elementa.components.UIImage +import gg.essential.elementa.components.UIText +import gg.essential.elementa.components.UIWrappedText +import gg.essential.elementa.constraints.CenterConstraint +import gg.essential.elementa.dsl.* +import gg.essential.elementa.events.UIClickEvent +import gg.essential.elementa.utils.withAlpha +import gg.essential.vigilance.gui.VigilancePalette +import gg.essential.vigilance.gui.VigilancePalette.getBackground + +class UIPopup private constructor(title : String, text : String) : UIBlock(getBackground().withAlpha(200)) { + + private val box: UIBlock + + constructor(title : String, text : String, image : UIImage? = null, event : (UIComponent.(event: UIClickEvent) -> Unit), buttonText : String + ) : this(title, text) { + + val btnText = UIText(buttonText, false) + val width = btnText.getTextWidth() + 18 + if (image != null) 18 else 0 + + UIButton(image, width, CenterConstraint(), btnText, Alignment.MIDDLE).onMouseClick(event) childOf box + } + + constructor(title : String, text : String, + image1 : UIImage? = null, event1 : (UIComponent.(event: UIClickEvent) -> Unit), buttonText : String, + image2 : UIImage? = null, event2 : (UIComponent.(event: UIClickEvent) -> Unit), buttonText2 : String + ) : this(title, text) { + + val btn1Text = UIText(buttonText, false) + val btn1Width = btn1Text.getTextWidth() + 18 + if (image1 != null) 18 else 0 + val btn2Text = UIText(buttonText2, false) + val btn2Width = btn2Text.getTextWidth() + 18 + if (image2 != null) 18 else 0 + + val width = btn1Width.coerceAtLeast(btn2Width) + + UIButton(image1, width, CenterConstraint() - 5.percent(), btn1Text, Alignment.RIGHT).onMouseClick(event1) childOf box + UIButton(image2, width, CenterConstraint() + 5.percent(), btn2Text, Alignment.LEFT).onMouseClick(event2) childOf box + } + + init { + constrain { + width = 100.percent() + height = 100.percent() + } + + box = UIBlock(VigilancePalette.getHighlight()).constrain { + width = 50.percent() + height = 45.percent() + x = CenterConstraint() + y = CenterConstraint() + } childOf this + + UIText(title).constrain { + y = 5.percent() + x = CenterConstraint() + } childOf box + + UIWrappedText(text, true, centered = true, trimText = true).constrain { + width = 90.percent() + height = 80.percent() + x = 5.percent() + y = 5.percent() + 11.pixel() + } childOf box + } + +} \ No newline at end of file diff --git a/src/main/kotlin/tech/thatgravyboat/rewardclaim/ui/UIReward.kt b/src/main/kotlin/tech/thatgravyboat/rewardclaim/ui/UIReward.kt new file mode 100644 index 0000000..da5014f --- /dev/null +++ b/src/main/kotlin/tech/thatgravyboat/rewardclaim/ui/UIReward.kt @@ -0,0 +1,103 @@ +package tech.thatgravyboat.rewardclaim.ui + +import gg.essential.elementa.components.UIBlock +import gg.essential.elementa.components.UIImage +import gg.essential.elementa.components.UIText +import gg.essential.elementa.constraints.XConstraint +import gg.essential.elementa.constraints.YConstraint +import gg.essential.elementa.dsl.* +import gg.essential.elementa.effects.OutlineEffect +import gg.essential.elementa.utils.withAlpha +import gg.essential.universal.ChatColor +import gg.essential.vigilance.gui.VigilancePalette +import tech.thatgravyboat.rewardclaim.MappedImageCache +import tech.thatgravyboat.rewardclaim.RewardLanguage +import tech.thatgravyboat.rewardclaim.types.RewardData + +class UIReward(xConstraint: XConstraint, yConstraint: YConstraint) : UIBlock(VigilancePalette.getHighlight().withAlpha(204)) { + + private val imageBackground : UIBlock + private val title : UIText + private val rarityDesc : UIText + private val amountDesc : UIText + + init { + constrain { + width = 35.percent() + height = 16.percent() + x = xConstraint + y = yConstraint + } + + imageBackground = UIBlock(VigilancePalette.getBrightHighlight()).constrain { + width = 20.57.percent() + height = 80.percent() + x = 2.545.percent() + y = 10.percent() + } childOf this + + UIBlock(VigilancePalette.getDivider()).constrain { + width = 1.pixel() + height = 95.percent() + x = 25.66.percent() + y = 2.5.percent() + } childOf this + + val rightSideStart = (25.66.percent() + 2.pixel()) + 1.28.percent() + + UIBlock(VigilancePalette.getDivider()).constrain { + width = 71.78.percent() - 1.pixel() + height = 1.pixel() + x = rightSideStart + y = 26.percent() + } childOf this + + title = UIText("Unknown Reward").constrain { + x = rightSideStart + y = 26.percent() - (getHeight() + 1).pixel() + } childOf this + + rarityDesc = UIText("Rarity: Unknown").constrain { + x = rightSideStart + y = 26.percent() + 3.pixel() + } childOf this + + amountDesc = UIText("Amount: ${ChatColor.GOLD}0").constrain { + x = rightSideStart + y = 26.percent() + (5+rarityDesc.getHeight()).pixel() + } childOf this + amountDesc.hide(true) + } + + fun setSelected(selected : Boolean) { + if (selected) { + enableEffect(OutlineEffect(VigilancePalette.getAccent(), 1F)) + }else { + removeEffect() + } + } + + fun setData(data : RewardData, language : RewardLanguage) { + title.setText(data.getDisplayName(language)) + rarityDesc.setText("Rarity: ${data.rarity.color}${language.translate(data.rarity.translationKey)}") + + data.amount?.let { + amountDesc.setText("Amount: ${ChatColor.GOLD}$it") + amountDesc.unhide(true) + } + + data.boxes?.let { + amountDesc.setText("Boxes: ${ChatColor.GOLD}$it") + amountDesc.unhide(true) + } + + data.image?.let { + it.url?.let { url -> + UIImage.ofURL(url, MappedImageCache).constrain { + width = 100.percent() + height = it.height.percent() + } childOf imageBackground + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/tech/thatgravyboat/rewardclaim/ui/UISelectedReward.kt b/src/main/kotlin/tech/thatgravyboat/rewardclaim/ui/UISelectedReward.kt new file mode 100644 index 0000000..47763dc --- /dev/null +++ b/src/main/kotlin/tech/thatgravyboat/rewardclaim/ui/UISelectedReward.kt @@ -0,0 +1,86 @@ +package tech.thatgravyboat.rewardclaim.ui + +import gg.essential.elementa.components.UIBlock +import gg.essential.elementa.components.UIImage +import gg.essential.elementa.components.UIText +import gg.essential.elementa.components.UIWrappedText +import gg.essential.elementa.constraints.XConstraint +import gg.essential.elementa.dsl.* +import gg.essential.elementa.effects.OutlineEffect +import gg.essential.elementa.utils.withAlpha +import gg.essential.universal.ChatColor +import gg.essential.vigilance.gui.VigilancePalette +import tech.thatgravyboat.rewardclaim.MappedImageCache +import tech.thatgravyboat.rewardclaim.RewardLanguage +import tech.thatgravyboat.rewardclaim.types.RewardData + +class UISelectedReward(middle : XConstraint) : UIBlock(VigilancePalette.getHighlight().withAlpha(204)) { + + private var image : UIImage? = null + + private val displayName = UIText("Select a Reward!").constrain { + x = 31.percent() + y = 10.3.percent() + } childOf this + + private val imageBackground = UIBlock(VigilancePalette.getBrightHighlight()).constrain { + x = 5.percent() + y = 10.3.percent() + width = 20.5.percent() + height = 42.6.percent() + } childOf this + + private val rarity = UIText().constrain { + x = 31.percent() + y = 10.3.percent() + 13.pixel() + } childOf this + + private val amount = UIText().constrain { + x = 31.percent() + y = 10.3.percent() + 25.pixel() + } childOf this + + private val desc = UIWrappedText().constrain { + x = 5.percent() + y = 52.9.percent() + 3.pixel() + width = 90.percent() + } childOf this + + init { + constrain { + width = 35.percent() + height = 30.percent() + x = middle - 17.5.percent() + y = 25.percent() + } + + UIBlock(VigilancePalette.getDivider()).constrain { + width = 64.5.percent() + height = 1.pixel() + x = 30.5.percent() + y = 10.3.percent() + 11.pixel() + } childOf this + } + + fun updateInfo(data: RewardData, language: RewardLanguage) { + displayName.setText(data.getDisplayName(language)) + enableEffect(OutlineEffect(data.rarity.color.color!!, 1F)) + rarity.setText("Rarity: ${data.rarity.color}${language.translate(data.rarity.translationKey)}") + + if (data.amount != null) amount.setText("Amount: ${ChatColor.GOLD}${data.amount}") + else if (data.boxes != null) amount.setText("Boxes: ${ChatColor.GOLD}${data.boxes}") + else amount.setText("") + desc.setText(data.getDescription(language)) + + data.image?.let { + it.url?.let { url -> + image?.let(imageBackground::removeChild) + image = UIImage.ofURL(url, MappedImageCache).constrain { + height = it.height.percent() + width = 100.percent() + } childOf imageBackground + } + } + + } +} \ No newline at end of file diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info index b6aabfc..ddc5f9d 100644 --- a/src/main/resources/mcmod.info +++ b/src/main/resources/mcmod.info @@ -1,8 +1,8 @@ [ { - "modid": "forgetemplate", - "name": "ForgeTemplate", - "description": "An example template", + "modid": "gravyrewardclaim", + "name": "Gravy's Reward Claim", + "description": "Allows you to claim Hypixel rewards in-game.", "version": "${version}", "mcversion": "${mcversion}", "url": "", diff --git a/src/main/resources/rewardclaim/external_link.png b/src/main/resources/rewardclaim/external_link.png new file mode 100644 index 0000000..e67caae Binary files /dev/null and b/src/main/resources/rewardclaim/external_link.png differ -- cgit