package com.toofifty.easygiantsfoundry; //import java.util.ArrayList; //import java.util.List; /** * Solves the heating/cooling action and predicts tick duration (index) * the naming convention is focused on the algorithm rather than in-game terminology for the context. *

* the dx_n refers to successive derivatives of an ordinary-differential-equations * https://en.wikipedia.org/wiki/Ordinary_differential_equation * also known as distance (dx0), speed (dx1), and acceleration (dx2). *

* dx0 - players current heat at tick * dx1 - dx0_current - dx0_last_tick, aka the first derivative * dx2 - dx1_current - dx1_last_tick, aka the second derivative *

* for context, here's what dx1 extracted directly from in-game dunking looks like. * the purpose of the HeatActionSolver.java is to accurately model this data. * int[] dx1 = { * 27, * 30, * 33, * 37, * 41, * 45, * 49, * 53, * 57, * 62, * 67, * 72, * 77, * 83, * 89, * 95, * 91, * }; */ /* The following code-snippet can be copy-pasted into runelite developer-tools "Shell" to extract dx1 import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Clipboard; import java.util.concurrent.atomic.AtomicInteger; int HEAT_ID = 13948; AtomicInteger tickCounter = new AtomicInteger(-1); AtomicInteger prevHeat = new AtomicInteger(client.getVarbitValue(HEAT_ID)); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); String output = ""; subscribe(VarbitChanged.class, ev -> { if (ev.getVarbitId() == HEAT_ID) { int deltaHeat = ev.getValue() - prevHeat.getAndSet(ev.getValue()); if (deltaHeat == -1) return; // ignore passive drain String str = "[" + tickCounter.incrementAndGet() + "] deltaHeat: " + deltaHeat; log.info(str); output = output + deltaHeat + "\n"; StringSelection selection = new StringSelection(output); clipboard.setContents(selection, selection); } }); */ public class HeatActionSolver { /** * Warning: this method prefers overshooting goal. For example, if goal is 957, * it will return index that reaches >957.
* This may be desirable if we're aiming to heat over range minimum, * but undesirable when cooling below range maximum; make sure to -1 the index if so. * * * * @param goal the desired heat destination * @param init_dx1 initial speed of heating/cooling. currently 7 for heat/cool, 27 for dunk/quench. * @param dx2_offset bonus acceleration. currently, 0 for heat/cool, 2 for dunk/quench. * @return Index here refers to tick. So an index of 10 means the goal can be reached in 10 ticks. */ public static int findDx0Index(int goal, int init_dx1, int dx2_offset) { int dx0 = 0; int dx1 = init_dx1; int count_index = 0; for (int dx2 = 1; dx0 <= goal; dx2++) { // Start from 1 up to the count inclusive int repetitions; if (dx2 == 1) { repetitions = 2; // The first number appears twice } else if (dx2 % 2 == 0) { repetitions = 6; // Even numbers appear six times } else { repetitions = 4; // Odd numbers (after 1) appear four times } for (int j = 0; j < repetitions && dx0 <= goal; j++) { dx0 += dx1; dx1 += dx2 + dx2_offset; // Sum the current number 'repetitions' times count_index += 1; } } return count_index; } /** * We can use the pattern to get the dx2 at a specific index numerically * * @param index the index/tick we want to calculate dx2 at * @return the acceleration of heating/cooling at index/tick */ public static int getDx2AtIndex(int index) { if (index <= 1) return 1; index -= 2; // 0 1 2 3 4 5 6 7 8 9 // e,e,e,e,e,e,o,o,o,o int block = index / 10; int block_idx = index % 10; int number = block * 2; if (block_idx <= 5) { return number + 2; } else { return number + 3; } } /** * We can use the pattern to get the dx1 at a specific index numerically * * @param index the index/tick we want to calculate the speed of heating/cooling * @param constant the initial speed of heating/cooling. * @return the speed of heating at index/tick */ public static int getDx1AtIndex(int index, int constant) { int _dx1 = constant; for (int i = 0; i < index; ++i) { _dx1 += getDx2AtIndex(i); } return _dx1; } // Methods below are functional, but only used to for debugging & development // public static int getDx0AtIndex(int index, int constant) // { // int dx0 = 0; // int dx1 = getDx1AtIndex(0, constant); // for (int i = 0; i < index; i++) // { // Start from 1 up to the count inclusive // int dx2 = getDx2AtIndex(i); // dx1 += dx2; // Sum the current number 'repetitions' times // dx0 += dx1; // } // return dx0; // } // We iteratively generate dx2 into a list // public static List generateDx2List(int count) // { // List pattern = new ArrayList<>(); // This will hold our pattern // for (int n = 1, i = 0; i < count; n++) // { // Start from 1 up to the count inclusive // int repetitions; // if (n == 1) // { // repetitions = 2; // The first number appears twice // } else if (n % 2 == 0) // { // repetitions = 6; // Even numbers appear six times // } else // { // repetitions = 4; // Odd numbers (after 1) appear four times // } // for (int j = 0; j < repetitions && i < count; j++, i++) // { // pattern.add(n); // Append the current number 'repetitions' times // } // } // return pattern; // } // public static int findDx0IndexContinue(int goal, int constant, int init_index) // { // int dx0 = getDx0AtIndex(init_index, constant); // int dx1 = getDx1AtIndex(init_index, constant); // int count_index = init_index; // for (; dx0 <= goal; count_index++) // { // Start from 1 up to the count inclusive // int dx2 = getDx2AtIndex(count_index); // dx1 += dx2; // Sum the current number 'repetitions' times // dx0 += dx1; // } // return count_index - init_index; // } }