diff --git a/Sample Reward Json/Notes.txt b/Sample Reward Json/Notes.txt index 83804c6..d79e0be 100644 --- a/Sample Reward Json/Notes.txt +++ b/Sample Reward Json/Notes.txt @@ -5,14 +5,43 @@ ========== Field Meanings ========== - rewards.*.rarity: Rarity of the reward (CardType) [all] + - "COMMON", "RARE", "EPIC", or "LEGENDARY" (in order of worst to best) + - This field is always present - rewards.*.reward: Type of reward (RewardType) [all] + - Determines the main icon on the card (with the exception of housing_package; see below) + - This field is always present -- rewards.*.gameType: Game that the reward is for (GameType) [coins and tokens only] -- rewards.*.amount: Amount of type rewarded [all except housing_package and add_vanity] -- rewards.*.intlist: Unknown; has something to do with amount [unknown; likely same as amount] +- rewards.*.gameType: Game that the reward is for + - Only present for coins and tokens (not adsense_token) + - Determines the mini GameType icon + +- rewards.*.amount: Amount of type rewarded + - Present on every reward except housing_package, add_vanity, and mystery_box (and potentially gift_box too; see intlist) +- rewards.*.intlist: An array of integers + - Only present for mystery_box (and potentially gift_box, but still unknown) + - The length of this array is displayed as the amount + - Each int in the array represents the 5-star value of a mystery box + - 0 = 1-star + - 4 = 5-star - rewards.*.package: Specifies the specific reward within the reward type [housing_package only] + - Housing block IDs are always prefixed by "specialoccasion_reward_card_skull_" + - The 18 housing blocks are grouped into 3 groups of 6 + - Each group is represented by a colored treasure chest (red/green/blue) for the type icon + - The group has nothing to do with rarity - rewards.*.key: ID of the lobby cosmetic to reward [add_vanity only] + - For a suit piece, this looks like "suit_treasure_chestplate" + - For other cosmetics, this looks like "emote_moustache" + +========== Reward Types ========== +- add_vanity [no sample yet, but see FORGED for assumed JSON] +- housing_package [see 6d302bf1] +- mystery_box [uses intlist; see a1cfa0d3] +- gift_box [no sample yet; unknown if uses intlist or amount] -========== I18n Notes ========== -- rewards.*.key for a suit piece looks like "suit_treasure_chestplate" \ No newline at end of file +- coins [see 04f79703] +- tokens [see 11a346e4] +- dust [see 04f79703] +- experience [see 8c3d7a5f] +- adsense_token [see a25797d1] +- souls [see a25797d1] \ No newline at end of file diff --git a/Sample Reward Json/a18258f7.json b/Sample Reward Json/a18258f7.json new file mode 100644 index 0000000..61c7dd4 --- /dev/null +++ b/Sample Reward Json/a18258f7.json @@ -0,0 +1,38 @@ +{ + "rewards": [ + { + "gameType": "SKYWARS", + "amount": 4000, + "rarity": "COMMON", + "reward": "coins" + }, + { + "package": "specialoccasion_reward_card_skull_pocket_galaxy", + "rarity": "EPIC", + "reward": "housing_package" + }, + { + "gameType": "BATTLEGROUND", + "amount": 6000, + "rarity": "RARE", + "reward": "coins" + } + ], + "id": "a18258f7", + "skippable": true, + "dailyStreak": { + "value": 1, + "score": 10, + "highScore": 37, + "keeps": true, + "token": true + }, + "ad": { + "video": "GNhbZ5guqO0", + "duration": 30, + "link": "https://store.hypixel.net/?utm_source=rewards-video&utm_medium=website&utm_content=GNhbZ5guqO0&utm_campaign=Rewards", + "buttonText": "Visit the Store" + }, + "activeAd": 2, + "playwire": false +} \ No newline at end of file diff --git a/Sample Reward Json/a1cfa0d3.json b/Sample Reward Json/a1cfa0d3.json new file mode 100644 index 0000000..65914c6 --- /dev/null +++ b/Sample Reward Json/a1cfa0d3.json @@ -0,0 +1,45 @@ +{ + "rewards": [ + { + "intlist": [ + 4, + 4, + 4, + 4, + 4, + 4 + ], + "rarity": "LEGENDARY", + "reward": "mystery_box" + }, + { + "gameType": "TNTGAMES", + "amount": 4000, + "rarity": "COMMON", + "reward": "coins" + }, + { + "gameType": "SKYWARS", + "amount": 8000, + "rarity": "RARE", + "reward": "coins" + } + ], + "id": "a1cfa0d3", + "skippable": false, + "dailyStreak": { + "value": 0, + "score": 9, + "highScore": 9, + "keeps": true, + "token": false + }, + "ad": { + "video": "GNhbZ5guqO0", + "duration": 30, + "link": "https://store.hypixel.net/?utm_source=rewards-video&utm_medium=website&utm_content=GNhbZ5guqO0&utm_campaign=Rewards", + "buttonText": "Visit the Store" + }, + "activeAd": 2, + "playwire": false +} \ No newline at end of file diff --git a/Sample Reward Json/a25797d1.json b/Sample Reward Json/a25797d1.json new file mode 100644 index 0000000..999c1a9 --- /dev/null +++ b/Sample Reward Json/a25797d1.json @@ -0,0 +1,36 @@ +{ + "rewards": [ + { + "amount": 100, + "rarity": "RARE", + "reward": "souls" + }, + { + "amount": 1, + "rarity": "RARE", + "reward": "adsense_token" + }, + { + "amount": 10, + "rarity": "COMMON", + "reward": "dust" + } + ], + "id": "a25797d1", + "skippable": true, + "dailyStreak": { + "value": 0, + "score": 9, + "highScore": 37, + "keeps": true, + "token": false + }, + "ad": { + "video": "GNhbZ5guqO0", + "duration": 30, + "link": "https://store.hypixel.net/?utm_source=rewards-video&utm_medium=website&utm_content=GNhbZ5guqO0&utm_campaign=Rewards", + "buttonText": "Visit the Store" + }, + "activeAd": 2, + "playwire": false +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index f302a9b..a4e012a 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ compileJava { } archivesBaseName = 'RewardClaim' -version = '0.1' +version = '0.1.1' group = 'me.dancedog.rewardclaim' minecraft { version = '1.8.9-11.15.1.1722' diff --git a/src/main/java/me/dancedog/rewardclaim/Mod.java b/src/main/java/me/dancedog/rewardclaim/Mod.java index 1600f1a..c002377 100644 --- a/src/main/java/me/dancedog/rewardclaim/Mod.java +++ b/src/main/java/me/dancedog/rewardclaim/Mod.java @@ -20,7 +20,7 @@ public class Mod { public static final String MODID = "rewardclaim"; - public static final String VERSION = "0.1"; + public static final String VERSION = "0.1.1"; static final String MODNAME = "RewardClaim"; @Getter @@ -51,7 +51,7 @@ public static void printWarning(String message, Throwable t, boolean inChat) { if (inChat && Minecraft.getMinecraft().thePlayer != null) { ChatComponentText chatMessage = new ChatComponentText("[" + MODNAME + "] " + message); - chatMessage.getChatStyle().setItalic(true).setColor(EnumChatFormatting.RED); + chatMessage.getChatStyle().setBold(true).setColor(EnumChatFormatting.RED); Minecraft.getMinecraft().thePlayer.addChatMessage(chatMessage); } } diff --git a/src/main/java/me/dancedog/rewardclaim/RewardListener.java b/src/main/java/me/dancedog/rewardclaim/RewardListener.java index 2cdb2de..8be50fc 100644 --- a/src/main/java/me/dancedog/rewardclaim/RewardListener.java +++ b/src/main/java/me/dancedog/rewardclaim/RewardListener.java @@ -1,7 +1,5 @@ package me.dancedog.rewardclaim; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import java.io.IOException; import java.net.URL; import java.util.Date; @@ -31,7 +29,7 @@ public class RewardListener { "§r§6Click the link to visit our website and claim your reward: §r§bhttp://rewards\\.hypixel\\.net/claim-reward/([A-Za-z0-9]+)§r"); private long lastRewardOpenedMs = new Date().getTime(); - private final AtomicReference rawRewardSessionData = new AtomicReference<>(); + private final AtomicReference sessionData = new AtomicReference<>(); /** * Fetches & scrapes the reward page in a separate thread. The resulting json is then stored in @@ -45,11 +43,12 @@ private void fetchRewardSession(String sessionId) { try { URL url = new URL("https://rewards.hypixel.net/claim-reward/" + sessionId); Response response = new Request(url, Method.GET, null).execute(); + if (response.getStatusCode() >= 200 && response.getStatusCode() < 300) { Document document = Jsoup.parse(response.getBody()); - JsonObject rawRewardData = RewardScraper.parseRewardPage(document); - rawRewardData.addProperty("_cookie", response.getNewCookies()); - rawRewardSessionData.set(rawRewardData); + RewardSession session = RewardScraper + .parseSessionFromRewardPage(document, response.getNewCookies()); + sessionData.set(session); } else { Mod.printWarning("Server sent back a " + response.getStatusCode() + " status code. Received the following body:\n" + response.getBody(), null, false); @@ -67,16 +66,18 @@ private void fetchRewardSession(String sessionId) { */ @SubscribeEvent public void onClientTick(ClientTickEvent event) { - if (rawRewardSessionData.get() != null) { - JsonElement error = rawRewardSessionData.get().get("error"); - if (error != null) { - Mod.printWarning("Failed to get reward: " + error.getAsString(), null, true); + if (Minecraft.getMinecraft().theWorld == null) { + return; + } + + RewardSession currentSessionData = sessionData.getAndSet(null); + if (currentSessionData != null) { + if (currentSessionData.getError() != null) { + Mod.printWarning("Failed to get reward: " + currentSessionData.getError(), null, true); return; } - RewardSession session = SessionDataParser.parseRewardSessionData(rawRewardSessionData.get()); - rawRewardSessionData.set(null); Minecraft.getMinecraft() - .displayGuiScreen(new GuiScreenRewardSession(session)); + .displayGuiScreen(new GuiScreenRewardSession(currentSessionData)); } } @@ -103,7 +104,8 @@ public void onChatReceived(ClientChatReceivedEvent event) { @SubscribeEvent public void onGuiInit(GuiOpenEvent event) { // Check for the reward book notification up to 10 seconds after the reward's chat link was received - if (event.gui instanceof GuiScreenBook + if (Minecraft.getMinecraft().thePlayer != null + && event.gui instanceof GuiScreenBook && (System.currentTimeMillis() - lastRewardOpenedMs) <= 10000) { event.setCanceled(true); lastRewardOpenedMs = 0; diff --git a/src/main/java/me/dancedog/rewardclaim/RewardScraper.java b/src/main/java/me/dancedog/rewardclaim/RewardScraper.java index 13c46aa..daba935 100644 --- a/src/main/java/me/dancedog/rewardclaim/RewardScraper.java +++ b/src/main/java/me/dancedog/rewardclaim/RewardScraper.java @@ -1,11 +1,10 @@ package me.dancedog.rewardclaim; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; import com.google.gson.JsonParser; import java.util.regex.Matcher; import java.util.regex.Pattern; +import me.dancedog.rewardclaim.model.RewardSession; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -18,53 +17,76 @@ class RewardScraper { private static final Pattern CSRF_TOKEN_PATTERN = Pattern .compile("window\\.securityToken = ['\"](.*?)['\"]"); - private static final Pattern REWARD_JSON_PATTERN = Pattern + private static final Pattern SESSION_JSON_PATTERN = Pattern .compile("window\\.appData = ['\"](.*?})['\"]"); private static final JsonParser jsonParser = new JsonParser(); /** - * Read the reward session json from a daily reward page + * Parse the session data from a daily reward page * - * @param document Document to get the session json from - * @return JsonObject of the session data on the provided document, or null if - * @throws JsonParseException If the document's session data was not valid json - * @throws IllegalStateException If the document's session data was not a JsonObject + * @param document Document representing the reward page + * @param cookie Cookie to be passed into the returned session (used to make claim request) + * @return The session parsed from the provided page */ - static JsonObject parseRewardPage(Document document) { + static RewardSession parseSessionFromRewardPage(Document document, String cookie) { + JsonObject rawSessionData; + if (document == null || document.body() == null) { - return createErrorJson("Document was null"); - } - Element infoScript = document.body().selectFirst("script"); - if (infoScript == null) { - return createErrorJson("Unable to locate reward data"); + rawSessionData = createErrorJson("Document was null"); + + } else { + // The "props" script contains information useful the daily reward react app + // This includes the rewards data, csrf token, i18n messages, etc + Element propsScript = document.body().selectFirst("script"); + if (propsScript == null) { + rawSessionData = createErrorJson("Unable to locate reward data"); + + } else { + String propsScriptContents = propsScript.data(); + rawSessionData = getRawSessionData(propsScriptContents); + rawSessionData.addProperty("_csrf", getCsrfToken(propsScriptContents)); + } } - JsonObject data = new JsonObject(); + return new RewardSession(rawSessionData, cookie); + } - // Get the JSON data for the reward session - String infoScriptContents = infoScript.data(); - Matcher rewardJsonMatcher = REWARD_JSON_PATTERN.matcher(infoScriptContents); + /** + * Extract the reward session's json data from the prop script's contents + * + * @param propsScriptContents String contents of the prop script element + * @return JsonObject of the session's data, or a JsonObject containing an error message if none + * was found + */ + private static JsonObject getRawSessionData(String propsScriptContents) { + Matcher rewardJsonMatcher = SESSION_JSON_PATTERN.matcher(propsScriptContents); if (rewardJsonMatcher.find()) { - JsonObject parsedRewardJson = jsonParser.parse(rewardJsonMatcher.group(1)).getAsJsonObject(); - JsonElement errorMessage = parsedRewardJson.get("error"); - if (errorMessage != null) { - return createErrorJson(errorMessage.getAsString()); - } - data.add("session_data", parsedRewardJson); + return jsonParser.parse(rewardJsonMatcher.group(1)).getAsJsonObject(); } else { return createErrorJson("Unable to locate reward data"); } + } - // Get the CSRF token needed to claim the reward - Matcher csrfTokenMatcher = CSRF_TOKEN_PATTERN.matcher(infoScriptContents); + /** + * Extract the csrf token from the prop script's contents + * + * @param propsScriptContents String contents of the prop script element + * @return Csrf token string, or null if none was found + */ + private static String getCsrfToken(String propsScriptContents) { + Matcher csrfTokenMatcher = CSRF_TOKEN_PATTERN.matcher(propsScriptContents); if (csrfTokenMatcher.find()) { - data.addProperty("csrf_token", csrfTokenMatcher.group(1)); - } else { - return createErrorJson("Unable to locate csrf token"); + return csrfTokenMatcher.group(1); } - - return data; + return null; } + /** + * Utility method to create a JsonObject containing an error message (same format that rewards + * page uses) + * + * @param errorMsg + * @return JsonObject containing the message + */ private static JsonObject createErrorJson(String errorMsg) { JsonObject json = new JsonObject(); json.addProperty("error", errorMsg); diff --git a/src/main/java/me/dancedog/rewardclaim/SessionDataParser.java b/src/main/java/me/dancedog/rewardclaim/SessionDataParser.java deleted file mode 100644 index 73ab164..0000000 --- a/src/main/java/me/dancedog/rewardclaim/SessionDataParser.java +++ /dev/null @@ -1,230 +0,0 @@ -package me.dancedog.rewardclaim; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import me.dancedog.rewardclaim.model.Reward; -import me.dancedog.rewardclaim.model.RewardSession; -import me.dancedog.rewardclaim.types.CardRarity; -import me.dancedog.rewardclaim.types.GameType; -import me.dancedog.rewardclaim.types.HousingSkullType; -import me.dancedog.rewardclaim.types.RewardType; -import net.minecraft.client.resources.I18n; -import net.minecraft.util.ResourceLocation; - -/** - * Utility methods for parsing the JSON scraped from the rewards page - *

- * Created by DanceDog / Ben on 3/23/20 @ 3:23 PM - */ -final class SessionDataParser { - - private SessionDataParser() { - } - - private static final String VANITY_SUIT_PIECE_SUFFIX_REPLACEMENT_REGEX = "_([A-Za-z]+)$"; // Replaces "_leggings", etc - private static final String VANITY_SUIT_PIECE_PREFIX_REPLACEMENT_REGEX = "([A-Za-z]+_)(?!$)"; // Replaces everything except "leggings", etc - private static final String HOUSING_BLOCK_REPLACEMENT_REGEX = "specialoccasion_reward_card_skull_|'"; - - static RewardSession parseRewardSessionData(JsonObject input) { - JsonElement rawSessionJson = input.get("session_data"); - JsonElement csrfTokenElement = input.get("csrf_token"); - if (rawSessionJson == null - || !rawSessionJson.isJsonObject() - || csrfTokenElement == null - || !csrfTokenElement.isJsonPrimitive() - || rawSessionJson.getAsJsonObject().get("rewards") == null - || !rawSessionJson.getAsJsonObject().get("rewards").isJsonArray()) { - Mod.printWarning("Invalid reward session data", null, true); - return null; - } - - // Create session - RewardSession session = new RewardSession(); - session.setId(rawSessionJson.getAsJsonObject().get("id").getAsString()); - session.setCsrfToken(csrfTokenElement.getAsString()); - session - .setCookies(input.get("_cookie").getAsString()); // Our own value passed in through the JSON - - JsonArray rawRewardArray = rawSessionJson.getAsJsonObject().get("rewards") - .getAsJsonArray(); - for (JsonElement item : rawRewardArray) { - if (!item.isJsonObject()) { - Mod.printWarning("Reward item was not a JsonObject", null, true); - return null; - } - JsonObject rawRewardJson = item.getAsJsonObject(); - - // Create reward card & related variables - Reward card = new Reward(); - RewardType rewardType = RewardType.fromName(getStringFromJsonObject("reward", rawRewardJson)); - GameType gameType = GameType.fromName(getStringFromJsonObject("gameType", rawRewardJson)); - String vanityKey = getStringFromJsonObject("key", rawRewardJson).toLowerCase(); - HousingSkullType housingSkullType = HousingSkullType.UNKNOWN; - if (rewardType == RewardType.HOUSING_PACKAGE) { - String housingPackageName = getStringFromJsonObject("package", rawRewardJson) - .replaceAll(HOUSING_BLOCK_REPLACEMENT_REGEX, ""); - housingSkullType = HousingSkullType.fromName(housingPackageName); - } - - // Set card text - card.setRarity(CardRarity.fromName(getStringFromJsonObject("rarity", rawRewardJson))); - card.setTitle(getTitle(rewardType, gameType, vanityKey)); - card.setSubtitle(getSubtitle(rewardType, housingSkullType, vanityKey, rawRewardJson)); - card.setDescription(getDescription(rewardType, gameType, vanityKey)); - - // Set card images - card.setTypeIcon(getTypeIcon(rewardType, gameType, vanityKey, housingSkullType)); - if (rewardType == RewardType.COINS) { - card.setGameIcon(gameType.getResource()); - } - if (rewardType == RewardType.ADD_VANITY && vanityKey.contains("suit")) { - card.setItemBg(Mod.getGuiTexture("bg_armor.png")); - String armorType = vanityKey.replaceAll(VANITY_SUIT_PIECE_PREFIX_REPLACEMENT_REGEX, "") - .toUpperCase(); - card.setItemIcon(Mod.getGuiTexture("reward_sub/armor/" + armorType + ".png")); - - } else if (rewardType == RewardType.HOUSING_PACKAGE) { - card - .setItemBg(Mod.getGuiTexture("bg_housing.png")); - card.setItemIcon(housingSkullType.getResource()); - } - - session.getRewards().add(card); - } - return session; - } - - /** - * Determine a reward's title (top text on the card) - * - * @param rewardType Type of reward - * @param gameType GameType of reward (coins/tokens) - * @param vanityKey Vanity key of the reward (if add_vanity) - * @return The reward's formatted title - */ - private static String getTitle(RewardType rewardType, GameType gameType, String vanityKey) { - String titleKey; - - if (rewardType == RewardType.COINS || rewardType == RewardType.TOKENS) { - return I18n.format(rewardType.getTitleKey(), gameType.getProperName()); - - } else if (rewardType == RewardType.ADD_VANITY) { - titleKey = "vanity."; - if (vanityKey.contains("suit")) { - titleKey += vanityKey.replaceAll(VANITY_SUIT_PIECE_SUFFIX_REPLACEMENT_REGEX, ""); - } else { - titleKey += vanityKey; - } - - } else { - titleKey = rewardType.getTitleKey(); - } - - return I18n.format(titleKey); - } - - /** - * Determine a reward's subtitle (bottom text on the card) - * - * @param rewardType Type of reward - * @param housingSkull GameType of reward (coins/tokens) - * @param vanityKey Vanity key of the reward (if add_vanity) - * @param rewardJson Original reward session JsonObject - * @return The reward's formatted subtitle - */ - private static String getSubtitle(RewardType rewardType, HousingSkullType housingSkull, - String vanityKey, JsonObject rewardJson) { - String subtitleKey; - - if (rewardType == RewardType.HOUSING_PACKAGE) { - subtitleKey = "housing.skull." + housingSkull.name().toLowerCase(); - - } else if (rewardType == RewardType.ADD_VANITY) { - if (vanityKey.contains("suit")) { - subtitleKey = - "vanity.armor." + vanityKey.replaceAll(VANITY_SUIT_PIECE_PREFIX_REPLACEMENT_REGEX, ""); - } else { - subtitleKey = "vanity." + vanityKey; - } - - } else { - subtitleKey = "" + rewardJson.get("amount").getAsInt(); - } - - return I18n.format(subtitleKey); - } - - /** - * Determine a reward's description (bottom of card's tooltip) - * - * @param rewardType Type of reward - * @param gameType gameType GameType of reward (coins/tokens) - * @param vanityKey Vanity key of the reward (if add_vanity) - * @return The reward's formatted description - */ - private static String getDescription(RewardType rewardType, GameType gameType, String vanityKey) { - String descriptionKeyBase; - - if (rewardType == RewardType.COINS || rewardType == RewardType.TOKENS) { - return I18n.format(rewardType.getTitleKey() + ".description", gameType.getProperName()); - - } else if (rewardType == RewardType.ADD_VANITY) { - if (vanityKey.contains("suit")) { - descriptionKeyBase = "vanity.suits"; - } else if (vanityKey.contains("emote")) { - descriptionKeyBase = "vanity.emotes"; - } else if (vanityKey.contains("taunt")) { - descriptionKeyBase = "vanity.gestures"; - } else { - descriptionKeyBase = "vanity.unknown"; - } - - } else { - descriptionKeyBase = rewardType.getTitleKey(); - } - - return I18n.format(descriptionKeyBase + ".description"); - } - - private static ResourceLocation getTypeIcon(RewardType rewardType, GameType gameType, - String vanityKey, HousingSkullType housingSkull) { - ResourceLocation typeIcon; - - if (rewardType == RewardType.TOKENS) { - if (gameType == GameType.SKYWARS || gameType == GameType.LEGACY) { - typeIcon = Mod.getGuiTexture("reward_base/TOKENS_" + gameType.name() + ".png"); - } else { - typeIcon = RewardType.COINS.getIcon(); - } - - } else if (rewardType == RewardType.ADD_VANITY) { - String typeName; - if (vanityKey.contains("suit")) { - typeName = "SUIT"; - } else if (vanityKey.contains("emote")) { - typeName = "EMOTE"; - } else if (vanityKey.contains("taunt")) { - typeName = "GESTURE"; - } else { - typeName = "UNKNOWN"; - } - typeIcon = Mod.getGuiTexture("reward_base/VANITY_" + typeName + ".png"); - - } else if (rewardType == RewardType.HOUSING_PACKAGE) { - typeIcon = housingSkull.getGroup().getResource(); - } else { - typeIcon = rewardType.getIcon(); - } - - return typeIcon; - } - - private static String getStringFromJsonObject(String memberName, JsonObject jsonObject) { - JsonElement member = jsonObject.get(memberName); - if (member == null) { - return ""; - } - return member.getAsString(); - } -} \ No newline at end of file diff --git a/src/main/java/me/dancedog/rewardclaim/model/Reward.java b/src/main/java/me/dancedog/rewardclaim/model/Reward.java deleted file mode 100644 index 4e5edee..0000000 --- a/src/main/java/me/dancedog/rewardclaim/model/Reward.java +++ /dev/null @@ -1,25 +0,0 @@ -package me.dancedog.rewardclaim.model; - -import lombok.Data; -import me.dancedog.rewardclaim.types.CardRarity; -import net.minecraft.util.ResourceLocation; - -/** - * Created by DanceDog / Ben on 3/21/20 @ 10:10 PM - */ -@SuppressWarnings("unused") -@Data -public class Reward { - - // Card text - private String title; - private String subtitle; // Amount / Skull Name (ie "Rubik's Cube") / Armor Piece (ie "Leggings") / Vanity Item Name (ie "Moustache Emote") - private String description; - private CardRarity rarity; - - // Assets - private ResourceLocation typeIcon; // Main image of the card - private ResourceLocation gameIcon; // GameType icon of coin/token's game - private ResourceLocation itemBg; // Background of the skull/armor's item preview - private ResourceLocation itemIcon; // Preview icon of skull/armor's icon -} diff --git a/src/main/java/me/dancedog/rewardclaim/model/RewardCard.java b/src/main/java/me/dancedog/rewardclaim/model/RewardCard.java new file mode 100644 index 0000000..1313143 --- /dev/null +++ b/src/main/java/me/dancedog/rewardclaim/model/RewardCard.java @@ -0,0 +1,300 @@ +package me.dancedog.rewardclaim.model; + +import com.google.gson.JsonObject; +import lombok.Getter; +import lombok.NonNull; +import me.dancedog.rewardclaim.Mod; +import me.dancedog.rewardclaim.types.CardRarity; +import me.dancedog.rewardclaim.types.GameType; +import me.dancedog.rewardclaim.types.HousingSkull; +import me.dancedog.rewardclaim.types.RewardType; +import net.minecraft.client.resources.I18n; +import net.minecraft.util.ResourceLocation; + +/** + * Created by DanceDog / Ben on 3/31/20 @ 6:51 PM + */ +public class RewardCard { + + private static final String RARITY_JSON_KEY = "rarity"; + private static final String TYPE_JSON_KEY = "reward"; + private static final String VANITYKEY_JSON_KEY = "key"; + private static final String GAMETYPE_JSON_KEY = "gameType"; + private static final String HOUSINGPKG_JSON_KEY = "package"; + private static final String INTLIST_JSON_KEY = "intlist"; + private static final String AMOUNT_JSON_KEY = "amount"; + + /** + * This card's reward rarity value + */ + @NonNull + @Getter + private CardRarity rarity; + + /** + * The base type of this card's reward + */ + @Getter + private RewardType rewardType; + + /** + * The minigame that this reward is for Only applies to coin and token rewards + */ + @Getter + private GameType gameType; + + /** + * The main text to display on the bottom of this card + */ + @Getter + private String title; + + /** + * The text to display under the title + */ + @Getter + private String subtitle; + + /** + * A description of this card's rewardType + */ + @Getter + private String description; + + /** + * The large texture to display in the center of this card + */ + @Getter + private ResourceLocation typeIcon; + + /** + * The texture of the smaller icon to display next to the typeIcon Only applies to housing blocks + * and vanity suit pieces + */ + @Getter + private ResourceLocation itemIcon; + + /** + * The texture to display behind the item icon Only applies to housing blocks and vanity suit + * pieces + */ + @Getter + private ResourceLocation itemIconBg; + + RewardCard(JsonObject raw) { + if (!validate(raw)) { + this.rarity = CardRarity.ERROR; + return; + } + this.rewardType = RewardType.fromName(raw.get(TYPE_JSON_KEY).getAsString()); + this.rarity = CardRarity.fromName(raw.get(RARITY_JSON_KEY).getAsString()); + if (this.rewardType == RewardType.COINS || rewardType == RewardType.TOKENS) { + this.gameType = GameType.fromName(raw.get(GAMETYPE_JSON_KEY).getAsString()); + } + + setUnformattedTitle(raw); + setUnformattedSubtitle(raw); + setUnformattedDescription(raw); + setTypeIcon(raw); + setItemIcons(raw); + + // Format text + this.title = this.gameType != null + ? I18n.format(this.title, this.gameType.getProperName()) + : I18n.format(this.title); + this.subtitle = I18n.format(this.subtitle); + this.description = this.gameType != null + ? I18n.format(this.description, this.gameType.getProperName()) + : I18n.format(this.description); + } + + /** + * Determines this card's title text (unformatted) + * + * @param raw Raw JSON represented by this card + */ + private void setUnformattedTitle(JsonObject raw) { + if (this.rewardType == RewardType.ADD_VANITY) { + String key = raw.get(VANITYKEY_JSON_KEY).getAsString(); + if (key.contains("suit")) { + this.title = "vanity." + key.replaceAll("_([A-Za-z]+)$", ""); + } else { + this.title = "vanity." + key; + } + } else { + this.title = this.rewardType.getTitle(); + } + } + + /** + * Determines this card's subtitle text (unformatted) + * + * @param raw Raw JSON represented by this card + */ + private void setUnformattedSubtitle(JsonObject raw) { + if (this.rewardType == RewardType.ADD_VANITY) { + String key = raw.get(VANITYKEY_JSON_KEY).getAsString(); + if (key.contains("suit")) { + this.subtitle = "vanity.armor." + key.replaceAll("([A-Za-z]+_)(?!$)", ""); + } else { + this.subtitle = this.title; + } + + } else if (this.rewardType == RewardType.HOUSING_PACKAGE) { + this.subtitle = "housing.skull." + getHousingSkull(raw).name().toLowerCase(); + + } else if (raw.has(INTLIST_JSON_KEY) && !raw.has(AMOUNT_JSON_KEY)) { + this.subtitle = String.valueOf(raw.get(INTLIST_JSON_KEY).getAsJsonArray().size()); + + } else { + this.subtitle = raw.get(AMOUNT_JSON_KEY).getAsString(); + } + } + + /** + * Determines this card's description text (unformatted) + * + * @param raw Raw JSON represented by this card + */ + private void setUnformattedDescription(JsonObject raw) { + if (this.rewardType == RewardType.ADD_VANITY) { + String key = raw.get(VANITYKEY_JSON_KEY).getAsString(); + if (key.contains("suit")) { + description = "vanity.suits"; + } else if (key.contains("emote")) { + description = "vanity.emotes"; + } else if (key.contains("taunt")) { + description = "vanity.gestures"; + } else { + description = "vanity.unknown"; + } + + } else { + description = title; + } + + description += ".description"; + } + + /** + * Determines this card's main reward texture + * + * @param raw Raw JSON represented by this card + */ + private void setTypeIcon(JsonObject raw) { + String textureName; + if (this.rewardType == RewardType.ADD_VANITY) { + String key = raw.get(VANITYKEY_JSON_KEY).getAsString(); + if (key.contains("suit")) { + textureName = "SUIT"; + } else if (key.contains("emote")) { + textureName = "EMOTE"; + } else if (key.contains("taunt")) { + textureName = "GESTURE"; + } else { + textureName = "UNKNOWN"; + } + textureName = "VANITY_" + textureName; + + } else if (this.rewardType == RewardType.HOUSING_PACKAGE) { + textureName = "HOUSING_" + getHousingSkull(raw).getGroup().name(); + + } else if (this.rewardType == RewardType.TOKENS) { + textureName = "TOKENS_" + gameType.name(); + + } else { + textureName = this.rewardType.name(); + } + + this.typeIcon = Mod.getGuiTexture("reward_base/" + textureName + ".png"); + } + + /** + * Determines this card's mini item texture & background + * + * @param raw Raw JSON represented by this card + */ + private void setItemIcons(JsonObject raw) { + if (this.rewardType == RewardType.ADD_VANITY) { + String key = raw.get(VANITYKEY_JSON_KEY).getAsString(); + if (key.contains("suit")) { + this.itemIcon = Mod.getGuiTexture( + "reward_sub/armor/" + key.replaceAll("([A-Za-z]+_)(?!$)", "").toUpperCase() + ".png"); + this.itemIconBg = Mod.getGuiTexture("bg_armor.png"); + } + + } else if (this.rewardType == RewardType.HOUSING_PACKAGE) { + this.itemIcon = getHousingSkull(raw).getResource(); + this.itemIconBg = Mod.getGuiTexture("bg_housing.png"); + } + } + + /** + * Ensure that the data this card represents is valid + * + * @param raw Raw JSON represented by this card + * @return Whether or not the passed data can be properly displayed + */ + private static boolean validate(JsonObject raw) { + if (raw == null) { + return false; + } + if (!raw.has(RARITY_JSON_KEY) || !raw.has(TYPE_JSON_KEY)) { + return false; + } + + CardRarity rarity = CardRarity.fromName(raw.get(RARITY_JSON_KEY).getAsString()); + RewardType rewardType = RewardType.fromName(raw.get(TYPE_JSON_KEY).getAsString()); + if (rarity == null || rewardType == null) { + return false; + } + switch (rewardType) { + case ADD_VANITY: + if (!raw.has(VANITYKEY_JSON_KEY)) { + return false; + } + break; + + case TOKENS: + case COINS: + if (!raw.has(AMOUNT_JSON_KEY)) { + return false; + } + if (!raw.has(GAMETYPE_JSON_KEY)) { + return false; + } + if (GameType.fromName(raw.get(GAMETYPE_JSON_KEY).getAsString()) == null) { + return false; + } + break; + + case HOUSING_PACKAGE: + if (!raw.has(HOUSINGPKG_JSON_KEY)) { + return false; + } + if (getHousingSkull(raw) == null) { + return false; + } + break; + + default: + if (!raw.has(AMOUNT_JSON_KEY) && !raw.has(INTLIST_JSON_KEY)) { + return false; + } + } + + return true; + } + + /** + * Utility method to get a housing skull type from a reward object + * + * @param raw Raw reward object to get the skull from + * @return The raw reward's housing skull, or null if there was no skull with that name + */ + private static HousingSkull getHousingSkull(JsonObject raw) { + return HousingSkull.fromName( + raw.get(HOUSINGPKG_JSON_KEY).getAsString() + .replaceAll("specialoccasion_reward_card_skull_|'", "")); + } +} diff --git a/src/main/java/me/dancedog/rewardclaim/model/RewardSession.java b/src/main/java/me/dancedog/rewardclaim/model/RewardSession.java index 8f98d61..d575eb2 100644 --- a/src/main/java/me/dancedog/rewardclaim/model/RewardSession.java +++ b/src/main/java/me/dancedog/rewardclaim/model/RewardSession.java @@ -1,10 +1,12 @@ package me.dancedog.rewardclaim.model; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.List; -import lombok.Data; +import lombok.Getter; import me.dancedog.rewardclaim.Mod; import me.dancedog.rewardclaim.fetch.Request; import me.dancedog.rewardclaim.fetch.Request.Method; @@ -13,14 +15,55 @@ /** * Created by DanceDog / Ben on 3/22/20 @ 8:52 PM */ -@Data public class RewardSession { + @Getter + String error; + @Getter private String id; + @Getter + private List cards; + @Getter private String csrfToken; - private String adVideoId; - private List rewards = new ArrayList<>(); - private String cookies; + @Getter + private String cookie; + + /** + * Create a new reward session object from the session json (rewards, ad, streak, etc), the + * session's csrf token and the session's cookie + * + * @param raw The session's raw json representation + * @param cookie The cookie received from the original reward request + */ + public RewardSession(JsonObject raw, String cookie) { + if (!validateSessionData(raw)) { + if (raw != null && raw.has("error")) { + this.error = raw.get("error").getAsString(); + } else { + this.error = "Invalid reward session data"; + } + return; + } + + this.id = raw.get("id").getAsString(); + this.cards = new ArrayList<>(); + for (JsonElement rewardElement : raw.get("rewards").getAsJsonArray()) { + this.cards + .add(new RewardCard(rewardElement != null ? rewardElement.getAsJsonObject() : null)); + } + this.csrfToken = raw.get("_csrf").getAsString(); + this.cookie = cookie; + } + + private static boolean validateSessionData(JsonObject raw) { + return raw != null + && raw.has("id") + && raw.has("activeAd") + && raw.has("ad") + && raw.has("skippable") + && raw.has("rewards") + && raw.get("rewards").getAsJsonArray().size() == 3; + } public void claimReward(int option) { new Thread(() -> { @@ -34,7 +77,7 @@ public void claimReward(int option) { + "&watchedFallback=false"; URL url = new URL(urlStr); - Response response = new Request(url, Method.POST, this.cookies).execute(); + Response response = new Request(url, Method.POST, this.cookie).execute(); if (response.getStatusCode() >= 200 && response.getStatusCode() < 300) { Mod.getLogger().info("Successfully claimed reward"); } else { diff --git a/src/main/java/me/dancedog/rewardclaim/types/CardRarity.java b/src/main/java/me/dancedog/rewardclaim/types/CardRarity.java index 2d6c95d..df215b8 100644 --- a/src/main/java/me/dancedog/rewardclaim/types/CardRarity.java +++ b/src/main/java/me/dancedog/rewardclaim/types/CardRarity.java @@ -15,7 +15,9 @@ public enum CardRarity { COMMON("rarity.common", 0xff9b4e, EnumChatFormatting.YELLOW), RARE("rarity.rare", 0x62e2de, EnumChatFormatting.AQUA), EPIC("rarity.epic", 0xc633e8, EnumChatFormatting.DARK_PURPLE), - LEGENDARY("rarity.legendary", 0xe2b751, EnumChatFormatting.GOLD); + LEGENDARY("rarity.legendary", 0xe2b751, EnumChatFormatting.GOLD), + + ERROR(null, 0, EnumChatFormatting.DARK_RED); @Getter private final String displayName; @@ -42,14 +44,13 @@ public enum CardRarity { public static CardRarity fromName(String name) { if (name == null || name.isEmpty()) { - return COMMON; + return null; } - try { return valueOf(name.toUpperCase()); } catch (IllegalArgumentException e) { e.printStackTrace(); - return COMMON; + return null; } } } diff --git a/src/main/java/me/dancedog/rewardclaim/types/GameType.java b/src/main/java/me/dancedog/rewardclaim/types/GameType.java index 703b064..bbf10d8 100644 --- a/src/main/java/me/dancedog/rewardclaim/types/GameType.java +++ b/src/main/java/me/dancedog/rewardclaim/types/GameType.java @@ -11,9 +11,6 @@ */ @SuppressWarnings("unused") public enum GameType { - // Fallback - UNKNOWN("Unknown Game"), - // Possible reward games QUAKECRAFT("Quakecraft"), WALLS("Walls"), @@ -56,14 +53,13 @@ public enum GameType { public static GameType fromName(String name) { if (name == null || name.isEmpty()) { - return UNKNOWN; + return null; } - try { return valueOf(name.toUpperCase()); } catch (IllegalArgumentException e) { e.printStackTrace(); - return UNKNOWN; + return null; } } } diff --git a/src/main/java/me/dancedog/rewardclaim/types/HousingSkullType.java b/src/main/java/me/dancedog/rewardclaim/types/HousingSkull.java similarity index 92% rename from src/main/java/me/dancedog/rewardclaim/types/HousingSkullType.java rename to src/main/java/me/dancedog/rewardclaim/types/HousingSkull.java index 072555b..194c32c 100644 --- a/src/main/java/me/dancedog/rewardclaim/types/HousingSkullType.java +++ b/src/main/java/me/dancedog/rewardclaim/types/HousingSkull.java @@ -10,7 +10,7 @@ * Created by DanceDog / Ben on 3/23/20 @ 4:50 PM */ @SuppressWarnings("unused") -public enum HousingSkullType { +public enum HousingSkull { // Fallback UNKNOWN(HousingSkullGroup.RED), @@ -43,12 +43,12 @@ public enum HousingSkullType { @Getter private final ResourceLocation resource; - HousingSkullType(HousingSkullGroup group) { + HousingSkull(HousingSkullGroup group) { this.group = group; this.resource = Mod.getGuiTexture("reward_sub/housing_skull/" + name().toUpperCase() + ".png"); } - public static HousingSkullType fromName(String name) { + public static HousingSkull fromName(String name) { if (name == null || name.isEmpty()) { return UNKNOWN; } diff --git a/src/main/java/me/dancedog/rewardclaim/types/RewardType.java b/src/main/java/me/dancedog/rewardclaim/types/RewardType.java index 2c33baf..ad62de6 100644 --- a/src/main/java/me/dancedog/rewardclaim/types/RewardType.java +++ b/src/main/java/me/dancedog/rewardclaim/types/RewardType.java @@ -11,9 +11,6 @@ */ @SuppressWarnings("unused") public enum RewardType { - // Fallback - UNKNOWN("type.unknown", "UNKNOWN", false), - // Generic rewards DUST("type.dust", "DUST"), EXPERIENCE("type.experience", "EXPERIENCE"), @@ -29,7 +26,7 @@ public enum RewardType { ADD_VANITY("type.add_vanity", null, false); @Getter - private final String titleKey; + private final String title; @Getter private ResourceLocation icon; private final boolean hasAmount; @@ -42,7 +39,7 @@ public enum RewardType { } RewardType(String title, String iconName, boolean hasAmount) { - this.titleKey = title; + this.title = title; this.hasAmount = hasAmount; if (iconName != null) { @@ -56,14 +53,13 @@ public boolean hasAmount() { public static RewardType fromName(String name) { if (name == null || name.isEmpty()) { - return UNKNOWN; + return null; } - try { return valueOf(name.toUpperCase()); } catch (IllegalArgumentException e) { e.printStackTrace(); - return UNKNOWN; + return null; } } } diff --git a/src/main/java/me/dancedog/rewardclaim/ui/GuiRewardCard.java b/src/main/java/me/dancedog/rewardclaim/ui/GuiRewardCard.java index f57fd66..ea132ea 100644 --- a/src/main/java/me/dancedog/rewardclaim/ui/GuiRewardCard.java +++ b/src/main/java/me/dancedog/rewardclaim/ui/GuiRewardCard.java @@ -5,7 +5,7 @@ import lombok.Getter; import lombok.Setter; import me.dancedog.rewardclaim.Mod; -import me.dancedog.rewardclaim.model.Reward; +import me.dancedog.rewardclaim.model.RewardCard; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.Gui; @@ -26,9 +26,10 @@ class GuiRewardCard extends Gui { private static final ResourceLocation tooltipTexture = Mod .getGuiTexture("cards/card_tooltip.png"); - private final Reward cardInfo; - private final int posX; - private final int posY; + private final RewardCard cardInfo; + private int posX = 0; + private int posY = 0; + @Setter private boolean isFlipped = false; @Setter @@ -38,16 +39,19 @@ class GuiRewardCard extends Gui { @Setter private boolean showTooltip = false; - GuiRewardCard(Reward cardInfo, int posX, int posY) { + GuiRewardCard(RewardCard cardInfo) { this.cardInfo = cardInfo; - this.posX = posX; - this.posY = posY; tooltipLines = new ArrayList<>(); tooltipLines .add(EnumChatFormatting.WHITE + "" + EnumChatFormatting.ITALIC + cardInfo.getDescription()); } + void initGui(int posX, int posY) { + this.posX = posX; + this.posY = posY; + } + void drawRewardCard(int mouseX, int mouseY) { if (isEnabled && isHovered(mouseX, mouseY)) { isFlipped = true; @@ -125,15 +129,15 @@ private void drawCardTextures() { CARD_HEIGHT); // Game icon for coins - if (cardInfo.getGameIcon() != null) { - mc.getTextureManager().bindTexture(cardInfo.getGameIcon()); + if (cardInfo.getGameType() != null) { + mc.getTextureManager().bindTexture(cardInfo.getGameType().getResource()); drawModalRectWithCustomSizedTexture(posX + 65, posY + 63, 0, 0, 21, 21, 21, 21); } // Item icon for armor & housing blocks if (cardInfo.getItemIcon() != null) { // Background - mc.getTextureManager().bindTexture(cardInfo.getItemBg()); + mc.getTextureManager().bindTexture(cardInfo.getItemIconBg()); drawModalRectWithCustomSizedTexture(posX + 64, posY + 63, 0, 0, 28, 28, 28, 28); // Draw the item inside the background diff --git a/src/main/java/me/dancedog/rewardclaim/ui/GuiScreenRewardSession.java b/src/main/java/me/dancedog/rewardclaim/ui/GuiScreenRewardSession.java index 1219ddf..2314998 100644 --- a/src/main/java/me/dancedog/rewardclaim/ui/GuiScreenRewardSession.java +++ b/src/main/java/me/dancedog/rewardclaim/ui/GuiScreenRewardSession.java @@ -2,6 +2,7 @@ import java.io.IOException; import lombok.Getter; +import me.dancedog.rewardclaim.Mod; import me.dancedog.rewardclaim.model.RewardSession; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiScreen; @@ -18,6 +19,7 @@ public class GuiScreenRewardSession extends GuiScreen { private State guiState = State.INITIAL; + private int chosenCard = -1; private RewardSession session; private GuiRewardCard[] cards = new GuiRewardCard[3]; @@ -26,15 +28,16 @@ public class GuiScreenRewardSession extends GuiScreen { public GuiScreenRewardSession(RewardSession session) { this.session = session; + for (int i = 0; i < 3; i++) { + this.cards[i] = new GuiRewardCard(session.getCards().get(i)); + } } @Override public void initGui() { - guiState = State.INITIAL; - - int middleCardX = width / 2 - (GuiRewardCard.CARD_WIDTH - / 2); // used with spacing to offset first and last cards - int cardY = height / 2 - (GuiRewardCard.CARD_HEIGHT / 2); + // Determine card position and spacing + int middleCardX = width / 2 - (GuiRewardCard.CARD_WIDTH / 2); + int posY = height / 2 - (GuiRewardCard.CARD_HEIGHT / 2); int cardSpacing = 20; // Reward cards @@ -53,11 +56,7 @@ public void initGui() { default: posX = 0; } - this.cards[i] = new GuiRewardCard( - session.getRewards().get(i), - posX, - cardY - ); + this.cards[i].initGui(posX, posY); } // Close button ("X") @@ -80,6 +79,8 @@ public void initGui() { this.buttonList.add(closeButton); this.buttonList.add(activationButton); + + refreshState(); } @Override @@ -88,6 +89,13 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { this.drawGradientRect(0, 0, this.width, this.height, -1072689136, -804253680); super.drawScreen(mouseX, mouseY, partialTicks); + // Header + drawCenteredString(fontRendererObj, + EnumChatFormatting.GOLD + "" + EnumChatFormatting.BOLD + guiState.titleMessage, + width / 2, + (height / 2 - (GuiRewardCard.CARD_HEIGHT / 2)) / 2 - 5, + 0); + // Determine tooltip states if (guiState != State.INITIAL) { for (GuiRewardCard rewardCard : cards) { @@ -100,12 +108,7 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { rewardCard.drawRewardCard(mouseX, mouseY); } - drawCenteredString(fontRendererObj, - EnumChatFormatting.GOLD + "" + EnumChatFormatting.BOLD + guiState.titleMessage, - width / 2, - (height / 2 - (GuiRewardCard.CARD_HEIGHT / 2)) / 2 - 5, - 0); - + // Clickable text for reward page drawString(fontRendererObj, EnumChatFormatting.DARK_GRAY + "" + EnumChatFormatting.ITALIC + "Reward ID: " + session .getId(), @@ -144,14 +147,10 @@ protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOEx if (cards[i].isHovered(mouseX, mouseY)) { guiState = State.FINAL; + this.chosenCard = i; + refreshState(); - for (GuiRewardCard rewardCard : cards) { - rewardCard.setFlipped(true); - rewardCard.setEnabled(false); - } - - // Re-enable the selected card & claim its reward - cards[i].setEnabled(true); + Mod.getLogger().debug("Card {} was claimed", i); this.session.claimReward(i); } } @@ -165,12 +164,30 @@ protected void actionPerformed(GuiButton button) { this.mc.setIngameFocus(); } else if (button == activationButton) { - this.activationButton.enabled = false; this.guiState = State.CHOOSING; + refreshState(); + } + } + + /** + * Updates all elements according to the GUI's current state This prevents the GUI from resetting + * when the window gets resized + */ + private void refreshState() { + if (this.guiState != State.INITIAL) { + this.activationButton.enabled = false; for (GuiRewardCard rewardCard : cards) { rewardCard.setEnabled(true); } } + + if (this.guiState == State.FINAL) { + for (GuiRewardCard rewardCard : cards) { + rewardCard.setFlipped(true); + rewardCard.setEnabled(false); + } + cards[chosenCard].setEnabled(true); + } } @Override diff --git a/src/main/resources/assets/rewardclaim/lang/en_US.lang b/src/main/resources/assets/rewardclaim/lang/en_US.lang index 4050dad..1f3ab5c 100644 --- a/src/main/resources/assets/rewardclaim/lang/en_US.lang +++ b/src/main/resources/assets/rewardclaim/lang/en_US.lang @@ -47,16 +47,16 @@ type.tokens.description=In-game tokens for %s # Lobby Cosmetics type.add_vanity=Lobby Cosmetics -type.add_vanity.description=Unknown Cosmetic vanity.suits.description=1 of 4 unique cosmetic suit parts vanity.emotes.description=A unique animated Emote item vanity.gestures.description=A unique animated Gesture item -vanity.unknown.description=Unknown cosmetic +vanity.unknown.description=An unknown lobby cosmetic vanity.suit_treasure=Treasure Hunter Suit vanity.emote_moustache=Moustache Emote vanity.taunt_treasure=Dig for Treasure Gesture +vanity.unknown=Unknown Cosmetic vanity.armor.helmet=Helmet vanity.armor.chestplate=Chestplate @@ -87,9 +87,4 @@ housing.skull.jewelery_box=Jewelery Box housing.skull.crown=Crown housing.skull.molten_core=Molten Core housing.skull.mana_potion=Mana Potion -housing.skull.unknown=Unknown Block - -# Fallback - -type.unknown=Unknown -type.unknown.description=Unknown Reward \ No newline at end of file +housing.skull.unknown=Unknown Block \ No newline at end of file diff --git a/src/main/resources/assets/rewardclaim/textures/gui/cards/cardback_error.png b/src/main/resources/assets/rewardclaim/textures/gui/cards/cardback_error.png new file mode 100644 index 0000000..fc77622 Binary files /dev/null and b/src/main/resources/assets/rewardclaim/textures/gui/cards/cardback_error.png differ diff --git a/src/main/resources/assets/rewardclaim/textures/gui/cards/cardfront_error.png b/src/main/resources/assets/rewardclaim/textures/gui/cards/cardfront_error.png new file mode 100644 index 0000000..cf19daf Binary files /dev/null and b/src/main/resources/assets/rewardclaim/textures/gui/cards/cardfront_error.png differ diff --git a/src/main/resources/assets/rewardclaim/textures/gui/reward_sub/armor/UNKNOWN.png b/src/main/resources/assets/rewardclaim/textures/gui/reward_sub/armor/UNKNOWN.png deleted file mode 100644 index 8e80047..0000000 Binary files a/src/main/resources/assets/rewardclaim/textures/gui/reward_sub/armor/UNKNOWN.png and /dev/null differ