|
| 1 | +/* |
| 2 | + * This file is part of OneConfig. |
| 3 | + * OneConfig - Next Generation Config Library for Minecraft: Java Edition |
| 4 | + * Copyright (C) 2021~2023 Polyfrost. |
| 5 | + * <https://polyfrost.cc> <https://github.com/Polyfrost/> |
| 6 | + * |
| 7 | + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| 8 | + * |
| 9 | + * OneConfig is licensed under the terms of version 3 of the GNU Lesser |
| 10 | + * General Public License as published by the Free Software Foundation, AND |
| 11 | + * under the Additional Terms Applicable to OneConfig, as published by Polyfrost, |
| 12 | + * either version 1.0 of the Additional Terms, or (at your option) any later |
| 13 | + * version. |
| 14 | + * |
| 15 | + * This program is distributed in the hope that it will be useful, |
| 16 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 17 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 18 | + * Lesser General Public License for more details. |
| 19 | + * |
| 20 | + * You should have received a copy of the GNU Lesser General Public |
| 21 | + * License. If not, see <https://www.gnu.org/licenses/>. You should |
| 22 | + * have also received a copy of the Additional Terms Applicable |
| 23 | + * to OneConfig, as published by Polyfrost. If not, see |
| 24 | + * <https://polyfrost.cc/legal/oneconfig/additional-terms> |
| 25 | + */ |
| 26 | + |
| 27 | +package cc.polyfrost.oneconfig.internal.hacks; |
| 28 | + |
| 29 | +import cc.polyfrost.oneconfig.internal.assets.Colors; |
| 30 | +import cc.polyfrost.oneconfig.libs.universal.UResolution; |
| 31 | +import cc.polyfrost.oneconfig.platform.Platform; |
| 32 | +import cc.polyfrost.oneconfig.renderer.NanoVGHelper; |
| 33 | +import cc.polyfrost.oneconfig.renderer.font.Fonts; |
| 34 | +import cc.polyfrost.oneconfig.utils.NetworkUtils; |
| 35 | +import org.apache.commons.lang3.StringUtils; |
| 36 | +import org.lwjgl.input.Mouse; |
| 37 | + |
| 38 | +import java.awt.*; |
| 39 | +import java.io.File; |
| 40 | +import java.time.LocalDate; |
| 41 | +import java.time.temporal.ChronoUnit; |
| 42 | + |
| 43 | +public class ByeSkyClientHack { |
| 44 | + |
| 45 | + private static final File crashPatchSkyClientFile = new File("./OneConfig/CrashPatch/SKYCLIENT"); |
| 46 | + private static final File oldCrashPatchSkyClientFile = new File("./W-OVERFLOW/CrashPatch/SKYCLIENT"); |
| 47 | + public static final ByeSkyClientHack INSTANCE = new ByeSkyClientHack(); |
| 48 | + |
| 49 | + private static final String[] CAROUSEL_IMAGES = { |
| 50 | + "/oneclient1.jpg", |
| 51 | + "/oneclient2.jpg", |
| 52 | + "/oneclient3.jpg" |
| 53 | + }; |
| 54 | + |
| 55 | + private static final float[] CAROUSEL_RATIOS = { |
| 56 | + 644f / 1050f, |
| 57 | + 803f / 1281f, |
| 58 | + 803f / 1281f |
| 59 | + }; |
| 60 | + |
| 61 | + private static final String DOWNLOAD_URL = "https://polyfrost.org/projects/oneclient"; |
| 62 | + private static final String DISCORD_URL = "https://discord.gg/polyfrost"; |
| 63 | + private static final String FAQ_URL = "https://polyfrost.org/blog/oneclient-announcement"; |
| 64 | + |
| 65 | + private boolean dismissed = false; |
| 66 | + private boolean wasMouseDown = false; |
| 67 | + |
| 68 | + private int currentImage = 0; |
| 69 | + private long lastImageSwitch = 0; |
| 70 | + private static final long AUTO_SWITCH_MS = 5000; |
| 71 | + |
| 72 | + private float scrollY = 0; |
| 73 | + private float targetScrollY = 0; |
| 74 | + private long firstShownTime = 0; |
| 75 | + |
| 76 | + public void initialize() { |
| 77 | + } |
| 78 | + |
| 79 | + public void overwriteGui(int mouseX, int mouseY, float partialTicks) { |
| 80 | + NanoVGHelper nvg = NanoVGHelper.INSTANCE; |
| 81 | + |
| 82 | + float scaleFactor = (float) UResolution.getScaleFactor(); |
| 83 | + float mx = mouseX * scaleFactor; |
| 84 | + float my = mouseY * scaleFactor; |
| 85 | + |
| 86 | + int dWheel = Mouse.getDWheel(); |
| 87 | + if (dWheel != 0) { |
| 88 | + targetScrollY += dWheel > 0 ? 40 : -40; |
| 89 | + if (targetScrollY > 0) targetScrollY = 0; |
| 90 | + } |
| 91 | + scrollY += (targetScrollY - scrollY) * 0.3f; |
| 92 | + if (scrollY > 0) scrollY = 0; |
| 93 | + |
| 94 | + boolean isMouseDown = Mouse.isButtonDown(0); |
| 95 | + boolean clicked = !isMouseDown && wasMouseDown; |
| 96 | + wasMouseDown = isMouseDown; |
| 97 | + |
| 98 | + nvg.setupAndDraw(vg -> { |
| 99 | + float windowW = UResolution.getWindowWidth(); |
| 100 | + float windowH = UResolution.getWindowHeight(); |
| 101 | + |
| 102 | + if (firstShownTime == 0) firstShownTime = System.currentTimeMillis(); |
| 103 | + |
| 104 | + nvg.drawRect(vg, 0, 0, windowW, windowH, Colors.GRAY_900); |
| 105 | + |
| 106 | + float contentW = Math.min(700, windowW - 60); |
| 107 | + float contentX = (windowW - contentW) / 2f; |
| 108 | + |
| 109 | + LocalDate endOfSupportDate = LocalDate.of(2026, 2, 24); |
| 110 | + LocalDate today = LocalDate.now(); |
| 111 | + long daysDiff = ChronoUnit.DAYS.between(endOfSupportDate, today); |
| 112 | + String daysText; |
| 113 | + if (daysDiff > 0) { |
| 114 | + daysText = daysDiff + " days ago"; |
| 115 | + } else if (daysDiff == 0) { |
| 116 | + daysText = "today"; |
| 117 | + } else { |
| 118 | + daysText = "in " + Math.abs(daysDiff) + " days"; |
| 119 | + } |
| 120 | + |
| 121 | + float yBase = 40; |
| 122 | + float y = yBase + scrollY; |
| 123 | + |
| 124 | + float titleSize = 32; |
| 125 | + nvg.drawCenteredText(vg, "SkyClient is moving to OneClient", |
| 126 | + windowW / 2f, y + titleSize / 2f, Colors.WHITE, titleSize, Fonts.BOLD); |
| 127 | + y += titleSize + 20; |
| 128 | + |
| 129 | + nvg.drawRect(vg, contentX, y, contentW, 1, Colors.GRAY_600); |
| 130 | + y += 16; |
| 131 | + |
| 132 | + float descSize = 14; |
| 133 | + String desc1 = "Starting from February 24th, 2026 (" + daysText |
| 134 | + + "), Hypixel SkyBlock will be removing 1.8 support. " |
| 135 | + + "As a result, we are ending support for SkyClient."; |
| 136 | + float[] desc1Bounds = nvg.getWrappedStringBounds(vg, desc1, contentW, descSize, 1.4f, Fonts.REGULAR); |
| 137 | + float desc1Height = desc1Bounds[3] - desc1Bounds[1]; |
| 138 | + nvg.drawWrappedString(vg, desc1, contentX, y + descSize / 2f, |
| 139 | + contentW, Colors.WHITE_80, descSize, 1.4f, Fonts.REGULAR); |
| 140 | + y += desc1Height + 20; |
| 141 | + |
| 142 | + float desc2Size = 16; |
| 143 | + String desc2 = "We have been developing OneClient, the 1.21 version of SkyClient " |
| 144 | + + "with its own launcher and updated to the latest mods. We recommend trying it " |
| 145 | + + "as it is the closest thing now to SkyClient and made by the same team."; |
| 146 | + float[] desc2Bounds = nvg.getWrappedStringBounds(vg, desc2, contentW, desc2Size, 1.4f, Fonts.MEDIUM); |
| 147 | + float desc2Height = desc2Bounds[3] - desc2Bounds[1]; |
| 148 | + nvg.drawWrappedString(vg, desc2, contentX, y + desc2Size / 2f, |
| 149 | + contentW, Colors.WHITE, desc2Size, 1.4f, Fonts.MEDIUM); |
| 150 | + y += desc2Height + 28; |
| 151 | + |
| 152 | + float imgW = contentW; |
| 153 | + float imgH = imgW * CAROUSEL_RATIOS[currentImage]; |
| 154 | + |
| 155 | + long now = System.currentTimeMillis(); |
| 156 | + if (lastImageSwitch == 0) lastImageSwitch = now; |
| 157 | + if (now - lastImageSwitch > AUTO_SWITCH_MS) { |
| 158 | + currentImage = (currentImage + 1) % CAROUSEL_IMAGES.length; |
| 159 | + lastImageSwitch = now; |
| 160 | + } |
| 161 | + |
| 162 | + nvg.drawDropShadow(vg, contentX, y, imgW, imgH, 12, 4, 12); |
| 163 | + nvg.drawRoundImage(vg, CAROUSEL_IMAGES[currentImage], |
| 164 | + contentX, y, imgW, imgH, 12, ByeSkyClientHack.class); |
| 165 | + |
| 166 | + float arrowAreaW = 50; |
| 167 | + float arrowCenterY = y + imgH / 2f; |
| 168 | + boolean leftHover = mx >= contentX && mx <= contentX + arrowAreaW && my >= y && my <= y + imgH; |
| 169 | + int leftArrowColor = leftHover |
| 170 | + ? new Color(255, 255, 255, 180).getRGB() |
| 171 | + : new Color(255, 255, 255, 80).getRGB(); |
| 172 | + nvg.drawCenteredText(vg, "<", contentX + arrowAreaW / 2f, |
| 173 | + arrowCenterY, leftArrowColor, 28, Fonts.BOLD); |
| 174 | + if (clicked && leftHover) { |
| 175 | + currentImage = (currentImage - 1 + CAROUSEL_IMAGES.length) % CAROUSEL_IMAGES.length; |
| 176 | + lastImageSwitch = System.currentTimeMillis(); |
| 177 | + } |
| 178 | + |
| 179 | + float rightArrowX = contentX + imgW - arrowAreaW; |
| 180 | + boolean rightHover = mx >= rightArrowX && mx <= contentX + imgW && my >= y && my <= y + imgH; |
| 181 | + int rightArrowColor = rightHover |
| 182 | + ? new Color(255, 255, 255, 180).getRGB() |
| 183 | + : new Color(255, 255, 255, 80).getRGB(); |
| 184 | + nvg.drawCenteredText(vg, ">", rightArrowX + arrowAreaW / 2f, |
| 185 | + arrowCenterY, rightArrowColor, 28, Fonts.BOLD); |
| 186 | + if (clicked && rightHover) { |
| 187 | + currentImage = (currentImage + 1) % CAROUSEL_IMAGES.length; |
| 188 | + lastImageSwitch = System.currentTimeMillis(); |
| 189 | + } |
| 190 | + |
| 191 | + float dotAreaY = y + imgH + 12; |
| 192 | + float dotRadius = 4; |
| 193 | + float dotSpacing = 16; |
| 194 | + float dotsWidth = CAROUSEL_IMAGES.length * dotRadius * 2 |
| 195 | + + (CAROUSEL_IMAGES.length - 1) * (dotSpacing - dotRadius * 2); |
| 196 | + float dotStartX = (windowW - dotsWidth) / 2f; |
| 197 | + for (int i = 0; i < CAROUSEL_IMAGES.length; i++) { |
| 198 | + float dotCenterX = dotStartX + i * dotSpacing + dotRadius; |
| 199 | + float dotCenterY = dotAreaY + dotRadius; |
| 200 | + int dotColor = (i == currentImage) ? Colors.PRIMARY_500 : Colors.GRAY_500; |
| 201 | + nvg.drawCircle(vg, dotCenterX, dotCenterY, dotRadius, dotColor); |
| 202 | + |
| 203 | + if (clicked && mx >= dotCenterX - dotRadius - 4 && mx <= dotCenterX + dotRadius + 4 |
| 204 | + && my >= dotCenterY - dotRadius - 4 && my <= dotCenterY + dotRadius + 4) { |
| 205 | + currentImage = i; |
| 206 | + lastImageSwitch = System.currentTimeMillis(); |
| 207 | + } |
| 208 | + } |
| 209 | + |
| 210 | + y = dotAreaY + dotRadius * 2 + 24; |
| 211 | + |
| 212 | + float btnH = 40; |
| 213 | + float btnSpacing = 12; |
| 214 | + float btnRadius = 8; |
| 215 | + float btnFontSize = 14; |
| 216 | + |
| 217 | + boolean downloadHovered = mx >= contentX && mx <= contentX + contentW |
| 218 | + && my >= y && my <= y + btnH; |
| 219 | + nvg.drawRoundedRect(vg, contentX, y, contentW, btnH, |
| 220 | + downloadHovered ? Colors.PRIMARY_500 : Colors.PRIMARY_600, btnRadius); |
| 221 | + nvg.drawCenteredText(vg, "Download OneClient", |
| 222 | + contentX + contentW / 2f, y + btnH / 2f, Colors.WHITE, btnFontSize, Fonts.SEMIBOLD); |
| 223 | + if (clicked && downloadHovered) { |
| 224 | + NetworkUtils.browseLink(DOWNLOAD_URL); |
| 225 | + } |
| 226 | + y += btnH + btnSpacing; |
| 227 | + |
| 228 | + float halfW = (contentW - btnSpacing) / 2f; |
| 229 | + float discordBtnX = contentX; |
| 230 | + float faqBtnX = contentX + halfW + btnSpacing; |
| 231 | + |
| 232 | + boolean discordHovered = mx >= discordBtnX && mx <= discordBtnX + halfW |
| 233 | + && my >= y && my <= y + btnH; |
| 234 | + nvg.drawRoundedRect(vg, discordBtnX, y, halfW, btnH, |
| 235 | + discordHovered ? Colors.GRAY_300 : Colors.GRAY_500, btnRadius); |
| 236 | + nvg.drawCenteredText(vg, "Discord", |
| 237 | + discordBtnX + halfW / 2f, y + btnH / 2f, Colors.WHITE, btnFontSize, Fonts.SEMIBOLD); |
| 238 | + if (clicked && discordHovered) { |
| 239 | + NetworkUtils.browseLink(DISCORD_URL); |
| 240 | + } |
| 241 | + |
| 242 | + boolean faqHovered = mx >= faqBtnX && mx <= faqBtnX + halfW |
| 243 | + && my >= y && my <= y + btnH; |
| 244 | + nvg.drawRoundedRect(vg, faqBtnX, y, halfW, btnH, |
| 245 | + faqHovered ? Colors.GRAY_300 : Colors.GRAY_500, btnRadius); |
| 246 | + nvg.drawCenteredText(vg, "FAQ", |
| 247 | + faqBtnX + halfW / 2f, y + btnH / 2f, Colors.WHITE, btnFontSize, Fonts.SEMIBOLD); |
| 248 | + if (clicked && faqHovered) { |
| 249 | + NetworkUtils.browseLink(FAQ_URL); |
| 250 | + } |
| 251 | + y += btnH + btnSpacing; |
| 252 | + |
| 253 | + boolean continueHovered = mx >= contentX && mx <= contentX + contentW |
| 254 | + && my >= y && my <= y + btnH; |
| 255 | + nvg.drawRoundedRect(vg, contentX, y, contentW, btnH, |
| 256 | + continueHovered ? Colors.GRAY_300 : Colors.GRAY_600, btnRadius); |
| 257 | + nvg.drawCenteredText(vg, "Continue using SkyClient", |
| 258 | + contentX + contentW / 2f, y + btnH / 2f, Colors.WHITE_60, btnFontSize, Fonts.MEDIUM); |
| 259 | + if (clicked && continueHovered) { |
| 260 | + dismissed = true; |
| 261 | + } |
| 262 | + |
| 263 | + float totalContentHeight = (y - scrollY) + btnH + 40; |
| 264 | + float maxScroll = totalContentHeight - windowH; |
| 265 | + if (maxScroll > 0) { |
| 266 | + if (targetScrollY < -maxScroll) targetScrollY = -maxScroll; |
| 267 | + |
| 268 | + float trackW = 4; |
| 269 | + float trackX = windowW - 12; |
| 270 | + nvg.drawRoundedRect(vg, trackX, 0, trackW, windowH, Colors.GRAY_700, 2); |
| 271 | + |
| 272 | + float viewRatio = windowH / totalContentHeight; |
| 273 | + float thumbH = Math.max(30, windowH * viewRatio); |
| 274 | + float scrollFraction = -scrollY / maxScroll; |
| 275 | + float thumbY = scrollFraction * (windowH - thumbH); |
| 276 | + nvg.drawRoundedRect(vg, trackX, thumbY, trackW, thumbH, Colors.GRAY_400, 2); |
| 277 | + |
| 278 | + if (-scrollY < maxScroll) { |
| 279 | + nvg.drawGradientRect(vg, 0, windowH - 80, windowW, 80, |
| 280 | + Colors.GRAY_900 & 0x00FFFFFF, Colors.GRAY_900, |
| 281 | + NanoVGHelper.GradientDirection.DOWN); |
| 282 | + } |
| 283 | + |
| 284 | + long elapsed = System.currentTimeMillis() - firstShownTime; |
| 285 | + long fadeDuration = 500; |
| 286 | + long holdDuration = 3000; |
| 287 | + if (elapsed < holdDuration + fadeDuration) { |
| 288 | + float hintAlpha; |
| 289 | + if (elapsed < holdDuration) { |
| 290 | + hintAlpha = 1f; |
| 291 | + } else { |
| 292 | + hintAlpha = 1f - (float)(elapsed - holdDuration) / fadeDuration; |
| 293 | + } |
| 294 | + int hintColor = new Color(1f, 1f, 1f, hintAlpha * 0.7f).getRGB(); |
| 295 | + float hintSize = 13; |
| 296 | + String hintText = "Scroll down \u2193"; |
| 297 | + float hintX = windowW / 2f; |
| 298 | + float hintY = windowH - 18; |
| 299 | + nvg.drawCenteredText(vg, hintText, hintX, hintY, hintColor, hintSize, Fonts.MEDIUM); |
| 300 | + } |
| 301 | + } else { |
| 302 | + targetScrollY = 0; |
| 303 | + } |
| 304 | + }); |
| 305 | + } |
| 306 | + |
| 307 | + public boolean isSkyClient() { |
| 308 | + if (dismissed) return false; |
| 309 | + if (crashPatchSkyClientFile.exists()) return true; |
| 310 | + if (oldCrashPatchSkyClientFile.exists()) return true; |
| 311 | + return Platform.getLoaderPlatform().getLoadedMods().stream().anyMatch( |
| 312 | + mod -> mod != null && (StringUtils.contains(mod.id, "skyclient") |
| 313 | + || StringUtils.contains(mod.id, "skyblockclient") |
| 314 | + || StringUtils.equals(mod.id, "scc"))); |
| 315 | + } |
| 316 | +} |
0 commit comments