diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/performancestats/Performance.java b/runelite-client/src/main/java/net/runelite/client/plugins/performancestats/Performance.java new file mode 100644 index 0000000000..2f7967f8f1 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/performancestats/Performance.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2019, TheStonedTurtle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.performancestats; + +import lombok.Getter; +import lombok.Setter; + +@Getter +class Performance +{ + private static final double TICK_LENGTH = 0.6; + + String username; + + double damageDealt = 0; + double highestHitDealt = 0; + + double damageTaken = 0; + double highestHitTaken = 0; + + int lastActivityTick = -1; + @Setter + double ticksSpent = 0; + + void addDamageDealt(double a, int currentTick) + { + damageDealt += a; + if (a > highestHitDealt) + { + highestHitDealt = a; + } + + this.lastActivityTick = currentTick; + } + + void addDamageTaken(double a, int currentTick) + { + damageTaken += a; + if (a > highestHitTaken) + { + highestHitTaken = a; + } + + this.lastActivityTick = currentTick; + } + + void incrementTicksSpent() + { + ticksSpent++; + } + + void reset() + { + damageDealt = 0; + highestHitDealt = 0; + damageTaken = 0; + highestHitTaken = 0; + lastActivityTick = -1; + ticksSpent = 0; + } + + double getSecondsSpent() + { + return Math.round(this.ticksSpent * TICK_LENGTH); + } + + double getDPS() + { + return Math.round( (this.damageDealt / this.getSecondsSpent()) * 100) / 100.00; + } + + String getHumanReadableSecondsSpent() + { + final double secondsSpent = getSecondsSpent(); + if (secondsSpent <= 60) + { + return String.format("%2.0f", secondsSpent) + "s"; + } + + final double s = secondsSpent % 3600 % 60; + final double m = Math.floor(secondsSpent % 3600 / 60); + final double h = Math.floor(secondsSpent / 3600); + + return h < 1 ? String.format("%2.0f:%02.0f", m, s) : String.format("%2.0f:%02.0f:%02.0f", h, m, s); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/performancestats/PerformanceStatsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/performancestats/PerformanceStatsConfig.java new file mode 100644 index 0000000000..1c6aea301d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/performancestats/PerformanceStatsConfig.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019, TheStonedTurtle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.performancestats; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("performancestats") +public interface PerformanceStatsConfig extends Config +{ + @ConfigItem( + position = 0, + keyName = "submitTimeout", + name = "Submit Timeout (seconds)", + description = "Submits after this many seconds of inactivity" + ) + default int submitTimeout() + { + return 30; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/performancestats/PerformanceStatsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/performancestats/PerformanceStatsOverlay.java new file mode 100644 index 0000000000..63e94e2fc0 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/performancestats/PerformanceStatsOverlay.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2018, TheStonedTurtle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.performancestats; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import javax.inject.Inject; +import net.runelite.api.MenuAction; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayMenuEntry; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayPriority; +import net.runelite.client.ui.overlay.components.ComponentConstants; +import net.runelite.client.ui.overlay.components.PanelComponent; +import net.runelite.client.ui.overlay.components.table.TableAlignment; +import net.runelite.client.ui.overlay.components.table.TableComponent; + +public class PerformanceStatsOverlay extends Overlay +{ + private static final String TARGET = "Performance Stats"; + private static final String[] COLUMNS = { + "Player", "Dealt", "Taken", "DPS", "Elapsed" + }; + + private final PerformanceStatsPlugin tracker; + private final PanelComponent panelComponent = new PanelComponent(); + private final TableComponent tableComponent = new TableComponent(); + + @Inject + PerformanceStatsOverlay(PerformanceStatsPlugin tracker) + { + super(tracker); + setPosition(OverlayPosition.TOP_RIGHT); + setPriority(OverlayPriority.LOW); + this.tracker = tracker; + + getMenuEntries().add(new OverlayMenuEntry(MenuAction.RUNELITE_OVERLAY, "Pause", TARGET)); + getMenuEntries().add(new OverlayMenuEntry(MenuAction.RUNELITE_OVERLAY, "Reset", TARGET)); + getMenuEntries().add(new OverlayMenuEntry(MenuAction.RUNELITE_OVERLAY, "Submit", TARGET)); + + panelComponent.setPreferredSize(new Dimension(350, 0)); + panelComponent.setBackgroundColor(ComponentConstants.STANDARD_BACKGROUND_COLOR); + + tableComponent.setDefaultAlignment(TableAlignment.CENTER); + tableComponent.setColumns(COLUMNS); + + panelComponent.getChildren().add(tableComponent); + } + + @Override + public String getName() + { + return TARGET; + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (!tracker.isEnabled()) + { + return null; + } + + final Performance performance = tracker.getPerformance(); + graphics.setColor(Color.WHITE); + + tableComponent.getRows().clear(); + + final String[] rowElements = createRowElements(performance); + tableComponent.addRow(rowElements); + + return panelComponent.render(graphics); + } + + private String[] createRowElements(Performance performance) + { + return new String[] + { + performance.getUsername(), + String.valueOf((int) Math.round(performance.getDamageDealt())) + " | " + String.valueOf((int) Math.round(performance.getHighestHitDealt())), + String.valueOf((int) Math.round(performance.getDamageTaken())) + " | " + String.valueOf((int) Math.round(performance.getHighestHitTaken())), + String.valueOf(performance.getDPS()), + performance.getHumanReadableSecondsSpent() + }; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/performancestats/PerformanceStatsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/performancestats/PerformanceStatsPlugin.java new file mode 100644 index 0000000000..37b70eb03d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/performancestats/PerformanceStatsPlugin.java @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2018, TheStonedTurtle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.performancestats; + +import com.google.inject.Provides; +import java.text.DecimalFormat; +import javax.inject.Inject; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Actor; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.NPC; +import net.runelite.api.Skill; +import net.runelite.api.WorldType; +import net.runelite.api.events.ExperienceChanged; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.HitsplatApplied; +import net.runelite.api.events.ScriptCallbackEvent; +import net.runelite.client.chat.ChatColorType; +import net.runelite.client.chat.ChatMessageBuilder; +import net.runelite.client.chat.ChatMessageManager; +import net.runelite.client.chat.QueuedMessage; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.OverlayMenuClicked; +import net.runelite.client.game.NPCManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; + +@PluginDescriptor( + name = "Performance Stats", + description = "Displays your current performance stats", + tags = {"performance", "stats", "dps", "damage", "combat"}, + enabledByDefault = false +) +@Slf4j +public class PerformanceStatsPlugin extends Plugin +{ + // For every damage point dealt 1.33 experience is given to the player's hitpoints (base rate) + private static final double HITPOINT_RATIO = 1.33; + private static final double DMM_MULTIPLIER_RATIO = 10; + + private static final double GAME_TICK_SECONDS = 0.6; + private static final DecimalFormat numberFormat = new DecimalFormat("#,###"); + + @Inject + private Client client; + + @Inject + private ChatMessageManager chatMessageManager; + + @Inject + private PerformanceStatsConfig config; + + @Inject + private PerformanceStatsOverlay performanceTrackerOverlay; + + @Inject + private OverlayManager overlayManager; + + @Inject + private NPCManager npcManager; + + @Getter + private boolean enabled = false; + @Getter + private boolean paused = false; + @Getter + private final Performance performance = new Performance(); + + // Keep track of actor last tick as sometimes getInteracting can return null when hp xp event is triggered + // as the player clicked away at the perfect time + private Actor oldTarget; + private double hpExp; + private boolean hopping; + private int pausedTicks = 0; + + @Provides + PerformanceStatsConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(PerformanceStatsConfig.class); + } + + @Override + protected void startUp() + { + overlayManager.add(performanceTrackerOverlay); + } + + @Override + protected void shutDown() + { + overlayManager.remove(performanceTrackerOverlay); + disable(); + reset(); + } + + @Subscribe + public void onGameStateChanged(GameStateChanged event) + { + switch (event.getGameState()) + { + case LOGIN_SCREEN: + disable(); + break; + case HOPPING: + hopping = true; + break; + } + } + + @Subscribe + public void onHitsplatApplied(HitsplatApplied e) + { + if (isPaused()) + { + return; + } + + if (e.getActor().equals(client.getLocalPlayer())) + { + // Auto enables when hitsplat is applied to player + if (!isEnabled()) + { + enable(); + } + + performance.addDamageTaken(e.getHitsplat().getAmount(), client.getTickCount()); + } + } + + @Subscribe + public void onExperienceChanged(ExperienceChanged c) + { + if (isPaused() || hopping) + { + return; + } + + if (c.getSkill().equals(Skill.HITPOINTS)) + { + final double oldExp = hpExp; + hpExp = client.getSkillExperience(Skill.HITPOINTS); + + // Ignore initial login + if (client.getTickCount() < 2) + { + return; + } + + final double diff = hpExp - oldExp; + if (diff < 1) + { + return; + } + + // Auto enables when player receives hp exp + if (!isEnabled()) + { + enable(); + } + + final double damageDealt = calculateDamageDealt(diff); + performance.addDamageDealt(damageDealt, client.getTickCount()); + } + } + + @Subscribe + public void onScriptCallbackEvent(ScriptCallbackEvent e) + { + // Handles Fake XP drops (Ironman in PvP, DMM Cap, 200m xp, etc) + if (isPaused()) + { + return; + } + + if (!"fakeXpDrop".equals(e.getEventName())) + { + return; + } + + final int[] intStack = client.getIntStack(); + final int intStackSize = client.getIntStackSize(); + + final int skillId = intStack[intStackSize - 2]; + final Skill skill = Skill.values()[skillId]; + if (skill.equals(Skill.HITPOINTS)) + { + // Auto enables when player would have received hp exp + if (!isEnabled()) + { + enable(); + } + + final int exp = intStack[intStackSize - 1]; + performance.addDamageDealt(calculateDamageDealt(exp), client.getTickCount()); + } + } + + @Subscribe + public void onGameTick(GameTick t) + { + oldTarget = client.getLocalPlayer().getInteracting(); + + if (!isEnabled()) + { + return; + } + + if (isPaused()) + { + pausedTicks++; + return; + } + + performance.incrementTicksSpent(); + hopping = false; + + final int timeout = config.submitTimeout(); + if (timeout > 0) + { + final double tickTimeout = timeout / GAME_TICK_SECONDS; + final int activityDiff = (client.getTickCount() - pausedTicks) - performance.getLastActivityTick(); + if (activityDiff > tickTimeout) + { + // offset the tracker time to account for idle timeout + // Leave an additional tick to pad elapsed time + final double offset = tickTimeout - GAME_TICK_SECONDS; + performance.setTicksSpent(performance.getTicksSpent() - offset); + + submit(); + } + } + } + + @Subscribe + public void onOverlayMenuClicked(OverlayMenuClicked c) + { + if (!c.getOverlay().equals(performanceTrackerOverlay)) + { + return; + } + + switch (c.getEntry().getOption()) + { + case "Pause": + togglePaused(); + break; + case "Reset": + reset(); + break; + case "Submit": + submit(); + break; + } + } + + private void enable() + { + this.enabled = true; + hpExp = client.getSkillExperience(Skill.HITPOINTS); + } + + private void disable() + { + this.enabled = false; + } + + private void togglePaused() + { + this.paused = !this.paused; + } + + private void reset() + { + this.enabled = false; + this.paused = false; + + this.performance.reset(); + pausedTicks = 0; + } + + private void submit() + { + final String message = createPerformanceMessage(performance); + + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.GAMEMESSAGE) + .runeLiteFormattedMessage(message) + .build()); + + reset(); + } + + /** + * Calculates damage dealt based on HP xp gained accounting for multipliers such as DMM mode + * @param diff HP xp gained + * @return damage dealt + */ + private double calculateDamageDealt(double diff) + { + double damageDealt = diff / HITPOINT_RATIO; + // DeadMan mode has an XP modifier + if (client.getWorldType().contains(WorldType.DEADMAN)) + { + damageDealt = damageDealt / DMM_MULTIPLIER_RATIO; + } + + // Some NPCs have an XP modifier, account for it here. + Actor a = client.getLocalPlayer().getInteracting(); + if (!(a instanceof NPC)) + { + // If we are interacting with nothing we may have clicked away at the perfect time fall back to last tick + if (!(oldTarget instanceof NPC)) + { + log.warn("Couldn't find current or past target for experienced gain..."); + return damageDealt; + } + + a = oldTarget; + } + + final int npcId = ((NPC) a).getId(); + return damageDealt / npcManager.getXpModifier(npcId); + } + + private String createPerformanceMessage(final Performance p) + { + // Expected result: Damage Dealt: ## (Max: ##), Damage Taken: ## (Max: ##), Time Spent: ##:## (DPS: ##.##) + return new ChatMessageBuilder() + .append(ChatColorType.NORMAL) + .append("Damage dealt: ") + .append(ChatColorType.HIGHLIGHT) + .append(numberFormat.format(p.getDamageDealt())) + .append(ChatColorType.NORMAL) + .append(" (Max: ") + .append(ChatColorType.HIGHLIGHT) + .append(numberFormat.format(p.getHighestHitDealt())) + .append(ChatColorType.NORMAL) + .append("), Damage Taken: ") + .append(ChatColorType.HIGHLIGHT) + .append(numberFormat.format(p.getDamageTaken())) + .append(ChatColorType.NORMAL) + .append(" (Max: ") + .append(ChatColorType.HIGHLIGHT) + .append(numberFormat.format(p.getHighestHitTaken())) + .append(ChatColorType.NORMAL) + .append("), Time Spent: ") + .append(ChatColorType.HIGHLIGHT) + .append(p.getHumanReadableSecondsSpent()) + .append(ChatColorType.NORMAL) + .append(" (DPS: ") + .append(ChatColorType.HIGHLIGHT) + .append(String.valueOf(p.getDPS())) + .append(ChatColorType.NORMAL) + .append(")") + .build(); + } +} diff --git a/runelite-client/src/main/scripts/FakeXPDrops.hash b/runelite-client/src/main/scripts/FakeXPDrops.hash new file mode 100644 index 0000000000..cf5e37e931 --- /dev/null +++ b/runelite-client/src/main/scripts/FakeXPDrops.hash @@ -0,0 +1 @@ +32FBC48F8C6D8E62E02BCF09F444BA036F76133B6596396F0AB9E474687D9F3F \ No newline at end of file diff --git a/runelite-client/src/main/scripts/FakeXPDrops.rs2asm b/runelite-client/src/main/scripts/FakeXPDrops.rs2asm new file mode 100644 index 0000000000..0a61b8b13a --- /dev/null +++ b/runelite-client/src/main/scripts/FakeXPDrops.rs2asm @@ -0,0 +1,256 @@ +.id 2091 +.int_stack_count 2 +.string_stack_count 0 +.int_var_count 2 +.string_var_count 0 + iload 0 + iload 1 + sconst "fakeXpDrop" + runelite_callback ; + pop_int + pop_int + iconst 105 + iconst 83 + iconst 681 + get_varc_int 207 + coordx + enum + iload 0 + if_icmpeq LABEL9 + jump LABEL16 +LABEL9: + get_varc_int 207 + iconst 0 + iconst 0 + iload 1 + movecoord + set_varc_int 207 + jump LABEL216 +LABEL16: + iconst 105 + iconst 83 + iconst 681 + get_varc_int 208 + coordx + enum + iload 0 + if_icmpeq LABEL25 + jump LABEL32 +LABEL25: + get_varc_int 208 + iconst 0 + iconst 0 + iload 1 + movecoord + set_varc_int 208 + jump LABEL216 +LABEL32: + iconst 105 + iconst 83 + iconst 681 + get_varc_int 209 + coordx + enum + iload 0 + if_icmpeq LABEL41 + jump LABEL48 +LABEL41: + get_varc_int 209 + iconst 0 + iconst 0 + iload 1 + movecoord + set_varc_int 209 + jump LABEL216 +LABEL48: + iconst 105 + iconst 83 + iconst 681 + get_varc_int 210 + coordx + enum + iload 0 + if_icmpeq LABEL57 + jump LABEL64 +LABEL57: + get_varc_int 210 + iconst 0 + iconst 0 + iload 1 + movecoord + set_varc_int 210 + jump LABEL216 +LABEL64: + iconst 105 + iconst 83 + iconst 681 + get_varc_int 211 + coordx + enum + iload 0 + if_icmpeq LABEL73 + jump LABEL80 +LABEL73: + get_varc_int 211 + iconst 0 + iconst 0 + iload 1 + movecoord + set_varc_int 211 + jump LABEL216 +LABEL80: + iconst 105 + iconst 83 + iconst 681 + get_varc_int 212 + coordx + enum + iload 0 + if_icmpeq LABEL89 + jump LABEL96 +LABEL89: + get_varc_int 212 + iconst 0 + iconst 0 + iload 1 + movecoord + set_varc_int 212 + jump LABEL216 +LABEL96: + iconst 105 + iconst 83 + iconst 681 + get_varc_int 213 + coordx + enum + iload 0 + if_icmpeq LABEL105 + jump LABEL112 +LABEL105: + get_varc_int 213 + iconst 0 + iconst 0 + iload 1 + movecoord + set_varc_int 213 + jump LABEL216 +LABEL112: + get_varc_int 207 + iconst -1 + if_icmpeq LABEL116 + jump LABEL127 +LABEL116: + iconst 0 + iconst 83 + iconst 105 + iconst 81 + iload 0 + enum + iconst 0 + iload 1 + movecoord + set_varc_int 207 + jump LABEL216 +LABEL127: + get_varc_int 208 + iconst -1 + if_icmpeq LABEL131 + jump LABEL142 +LABEL131: + iconst 0 + iconst 83 + iconst 105 + iconst 81 + iload 0 + enum + iconst 0 + iload 1 + movecoord + set_varc_int 208 + jump LABEL216 +LABEL142: + get_varc_int 209 + iconst -1 + if_icmpeq LABEL146 + jump LABEL157 +LABEL146: + iconst 0 + iconst 83 + iconst 105 + iconst 81 + iload 0 + enum + iconst 0 + iload 1 + movecoord + set_varc_int 209 + jump LABEL216 +LABEL157: + get_varc_int 210 + iconst -1 + if_icmpeq LABEL161 + jump LABEL172 +LABEL161: + iconst 0 + iconst 83 + iconst 105 + iconst 81 + iload 0 + enum + iconst 0 + iload 1 + movecoord + set_varc_int 210 + jump LABEL216 +LABEL172: + get_varc_int 211 + iconst -1 + if_icmpeq LABEL176 + jump LABEL187 +LABEL176: + iconst 0 + iconst 83 + iconst 105 + iconst 81 + iload 0 + enum + iconst 0 + iload 1 + movecoord + set_varc_int 211 + jump LABEL216 +LABEL187: + get_varc_int 212 + iconst -1 + if_icmpeq LABEL191 + jump LABEL202 +LABEL191: + iconst 0 + iconst 83 + iconst 105 + iconst 81 + iload 0 + enum + iconst 0 + iload 1 + movecoord + set_varc_int 212 + jump LABEL216 +LABEL202: + get_varc_int 213 + iconst -1 + if_icmpeq LABEL206 + jump LABEL216 +LABEL206: + iconst 0 + iconst 83 + iconst 105 + iconst 81 + iload 0 + enum + iconst 0 + iload 1 + movecoord + set_varc_int 213 +LABEL216: + return