aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de/hysky/skyblocker/skyblock/fancybars/BarPositioner.java
blob: 00b09dcfbbd74d2b62010f8fc0ec34197338cbfd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
package de.hysky.skyblocker.skyblock.fancybars;

import net.minecraft.client.gui.ScreenPos;
import net.minecraft.client.gui.ScreenRect;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

public class BarPositioner {

    private final Map<BarAnchor, LinkedList<LinkedList<StatusBar>>> map = new HashMap<>(BarAnchor.values().length);

    public BarPositioner() {
        for (BarAnchor value : BarAnchor.values()) {
            map.put(value, new LinkedList<>());
        }
    }


    public int getRowCount(@NotNull BarAnchor barAnchor) {
        return map.get(barAnchor).size();
    }

    /**
     * Adds a row to the end of an anchor
     *
     * @param barAnchor the anchor
     */
    public void addRow(@NotNull BarAnchor barAnchor) {
        map.get(barAnchor).add(new LinkedList<>());
    }

    /**
     * Adds a row at the specified index
     *
     * @param barAnchor the anchor
     * @param row       row index
     */
    public void addRow(@NotNull BarAnchor barAnchor, int row) {
        map.get(barAnchor).add(row, new LinkedList<>());
    }

    /**
     * adds a bar to the end of a row
     *
     * @param barAnchor the anchor
     * @param row       the row
     * @param bar       the bar to add
     */
    public void addBar(@NotNull BarAnchor barAnchor, int row, StatusBar bar) {
        LinkedList<StatusBar> statusBars = map.get(barAnchor).get(row);
        statusBars.add(bar);
        bar.gridY = row;
        bar.gridX = statusBars.lastIndexOf(bar); // optimization baby, start with the end!
        bar.anchor = barAnchor;
    }

    /**
     * adds a bar to the specified x in a row
     *
     * @param barAnchor the anchor
     * @param row       the row
     * @param x         the index in the row
     * @param bar       the bar to add
     */
    public void addBar(@NotNull BarAnchor barAnchor, int row, int x, StatusBar bar) {
        LinkedList<StatusBar> statusBars = map.get(barAnchor).get(row);
        statusBars.add(x, bar);
        bar.gridY = row;
        bar.gridX = statusBars.indexOf(bar);
        bar.anchor = barAnchor;
    }

    /**
     * removes the specified bar at x on the row. If it's row is empty after being removed, the row will be auto removed
     *
     * @param barAnchor the anchor
     * @param row       dah row
     * @param x         dah x
     */
    public void removeBar(@NotNull BarAnchor barAnchor, int row, int x) {
        LinkedList<StatusBar> statusBars = map.get(barAnchor).get(row);
        StatusBar remove = statusBars.remove(x);
        remove.anchor = null;
        for (int i = x; i < statusBars.size(); i++) {
            statusBars.get(i).gridX--;
        }
        if (statusBars.isEmpty()) removeRow(barAnchor, row);
    }

    /**
     * removes the specified bar on the row. If it's row is empty after being removed, the row will be auto removed
     *
     * @param barAnchor the anchor
     * @param row       dah row
     * @param bar       dah bar
     */
    public void removeBar(@NotNull BarAnchor barAnchor, int row, StatusBar bar) {
        LinkedList<StatusBar> barRow = map.get(barAnchor).get(row);
        int x = barRow.indexOf(bar);
        if (x < 0) return; // probably a bad idea

        barRow.remove(bar);
        bar.anchor = null;
        for (int i = x; i < barRow.size(); i++) {
            barRow.get(i).gridX--;
        }
        if (barRow.isEmpty()) removeRow(barAnchor, row);
    }

    /**
     * row must be empty
     *
     * @param barAnchor the anchor
     * @param row       the row to remove
     */
    public void removeRow(@NotNull BarAnchor barAnchor, int row) {
        LinkedList<StatusBar> barRow = map.get(barAnchor).get(row);
        if (!barRow.isEmpty())
            throw new IllegalStateException("Can't remove a non-empty row (" + barAnchor + "," + row + ")");
        map.get(barAnchor).remove(row);
        for (int i = row; i < map.get(barAnchor).size(); i++) {
            for (StatusBar statusBar : map.get(barAnchor).get(i)) {
                statusBar.gridY--;
            }
        }
    }


    public LinkedList<StatusBar> getRow(@NotNull BarAnchor barAnchor, int row) {
        return map.get(barAnchor).get(row);
    }

    public StatusBar getBar(@NotNull BarAnchor barAnchor, int row, int x) {
        return map.get(barAnchor).get(row).get(x);
    }

    public boolean hasNeighbor(@NotNull BarAnchor barAnchor, int row, int x, boolean right) {
        LinkedList<StatusBar> statusBars = map.get(barAnchor).get(row);
        if (barAnchor.isRight()) {
            return (right && x < statusBars.size() - 1) || (!right && x > 0);
        } else {
            return (right && x > 0) || (!right && x < statusBars.size() - 1);
        }
    }


    public enum BarAnchor {
        HOTBAR_LEFT(true, false,
                (scaledWidth, scaledHeight) -> new ScreenPos(scaledWidth / 2 - 91 - 2, scaledHeight - 5),
                SizeRule.freeSize(25, 2, 6)),

        HOTBAR_RIGHT(true, true,
                (scaledWidth, scaledHeight) -> new ScreenPos(scaledWidth / 2 + 91 + 2, scaledHeight - 5),
                SizeRule.freeSize(25, 2, 6)),

        HOTBAR_TOP(true, true,
                (scaledWidth, scaledHeight) -> new ScreenPos(scaledWidth / 2 - 91, scaledHeight - (FancyStatusBars.isExperienceFancyBarVisible() ? 23 : 35)),
                SizeRule.targetSize(12, 182, 2),
                anchorPosition -> new ScreenRect(anchorPosition.x(), anchorPosition.y() - 20, 182, 20)),

        SCREEN_TOP_LEFT(false, true,
                ((scaledWidth, scaledHeight) -> new ScreenPos(5, 5)),
                SizeRule.freeSize(25, 2, 6)
        ),
        SCREEN_TOP_RIGHT(false, false,
                ((scaledWidth, scaledHeight) -> new ScreenPos(scaledWidth - 5, 5)),
                SizeRule.freeSize(25, 2, 6)
        ),
        SCREEN_BOTTOM_LEFT(true, true,
                ((scaledWidth, scaledHeight) -> new ScreenPos(5, scaledHeight - 5)),
                SizeRule.freeSize(25, 2, 6)
        ),
        SCREEN_BOTTOM_RIGHT(true, false,
                ((scaledWidth, scaledHeight) -> new ScreenPos(scaledWidth - 5, scaledHeight - 5)),
                SizeRule.freeSize(25, 2, 6)
        );

        private final AnchorPositionProvider positionProvider;
        private final AnchorHitboxProvider hitboxProvider;
        private final boolean up;
        private final boolean right;
        private final SizeRule sizeRule;

        /**
         * @param up               whether the rows stack towards the top of the screen from the anchor (false is bottom)
         * @param right            whether the bars are line up towards the right of the screen from the anchor (false is left)
         * @param positionProvider provides the position of the anchor for a give screen size
         * @param sizeRule         the rule the bars should follow. See {@link SizeRule}
         * @param hitboxProvider   provides the hitbox for when the anchor has no bars for the config screen
         */
        BarAnchor(boolean up, boolean right, AnchorPositionProvider positionProvider, SizeRule sizeRule, AnchorHitboxProvider hitboxProvider) {
            this.positionProvider = positionProvider;
            this.up = up;
            this.right = right;
            this.hitboxProvider = hitboxProvider;
            this.sizeRule = sizeRule;
        }

        BarAnchor(boolean up, boolean right, AnchorPositionProvider positionProvider, SizeRule sizeRule) {
            this(up, right, positionProvider, sizeRule,
                    anchorPosition -> new ScreenRect(anchorPosition.x() - (right ? 0 : 20), anchorPosition.y() - (up ? 20 : 0), 20, 20));
        }

        public ScreenPos getAnchorPosition(int scaledWidth, int scaledHeight) {
            return positionProvider.getPosition(scaledWidth, scaledHeight);
        }

        public ScreenRect getAnchorHitbox(ScreenPos anchorPosition) {
            return hitboxProvider.getHitbox(anchorPosition);
        }

        /**
         * whether the rows stack towards the top of the screen from the anchor (false is bottom)
         *
         * @return true if towards the top, false otherwise
         */
        public boolean isUp() {
            return up;
        }

        /**
         * whether the bars are line up towards the right of the screen from the anchor (false is left)
         *
         * @return true if towards the right, false otherwise
         */
        public boolean isRight() {
            return right;
        }

        public SizeRule getSizeRule() {
            return sizeRule;
        }

        private static final List<BarAnchor> cached = List.of(values());

        /**
         * cached version of {@link BarAnchor#values()}
         *
         * @return the list of anchors
         */
        public static List<BarAnchor> allAnchors() {
            return cached;
        }
    }

    /**
     * The rules the bars on an anchor should follow
     *
     * @param isTargetSize whether the bars went to fit to a target width
     * @param targetSize   the size of all the bars on a row should add up to this (target size)
     * @param totalWidth   the total width taken by all the bars on the row (target size)
     * @param widthPerSize the width of each size "unit" (free size)
     * @param minSize      the minimum (free and target size)
     * @param maxSize      the maximum (free and target size, THIS SHOULD BE THE SAME AS {@code targetSize} FOR {@code isTargetSize = true})
     */
    public record SizeRule(boolean isTargetSize, int targetSize, int totalWidth, int widthPerSize, int minSize, int maxSize) {
        public static SizeRule freeSize(int widthPerSize, int minSize, int maxSize) {
            return new SizeRule(false, -1, -1, widthPerSize, minSize, maxSize);
        }

        public static SizeRule targetSize(int targetSize, int totalWidth, int minSize) {
            return new SizeRule(true, targetSize, totalWidth, -1, minSize, targetSize);
        }
    }

    /**
     * A record representing a snapshot of a bar's position
     *
     * @param barAnchor
     * @param x
     * @param y         the row
     */
    public record BarLocation(@Nullable BarAnchor barAnchor, int x, int y) {

        public static final BarLocation NULL = new BarLocation(null, -1, -1);

        public static BarLocation of(StatusBar bar) {
            return new BarLocation(bar.anchor, bar.gridX, bar.gridY);
        }

        public boolean equals(BarAnchor barAnchor, int x, int y) {
            return x == this.x && y == this.y && barAnchor == this.barAnchor;
        }
    }

    /**
     * provides the position of the anchor for a give screen size
     */
    @FunctionalInterface
    interface AnchorPositionProvider {

        ScreenPos getPosition(int scaledWidth, int scaledHeight);
    }

    @FunctionalInterface
    interface AnchorHitboxProvider {

        /**
         * The hitbox, as in how large the area of "snapping" is if there are no bars on this anchor
         *
         * @param anchorPosition the position of the anchor
         * @return the rectangle that represents the hitbox
         */
        ScreenRect getHitbox(ScreenPos anchorPosition);
    }
}