From d29dd6f1b7d1166969ebba96c7ca562dd2f40721 Mon Sep 17 00:00:00 2001 From: psyopcutie Date: Sat, 9 May 2026 11:30:20 +0200 Subject: [PATCH] Final commit --- client/src/main/java/plugin/api/API.java | 102 +++ client/src/main/java/rt4/GlModel.java | 121 +-- client/src/main/java/rt4/MiniMenu.java | 6 + client/src/main/java/rt4/SceneGraph.java | 20 +- client/src/main/java/rt4/ScriptRunner.java | 104 ++- client/src/main/java/rt4/SoftwareModel.java | 27 +- .../plugin/api/RoofVisibilityHandler.kt | 33 + .../src/main/kotlin/SmartRoofs/RoofHider.kt | 703 ++++++++++++++++++ .../src/main/kotlin/SmartRoofs/plugin.kt | 51 ++ .../main/kotlin/SmartRoofs/plugin.properties | 3 + 10 files changed, 1070 insertions(+), 100 deletions(-) create mode 100644 client/src/main/kotlin/plugin/api/RoofVisibilityHandler.kt create mode 100644 plugin-playground/src/main/kotlin/SmartRoofs/RoofHider.kt create mode 100644 plugin-playground/src/main/kotlin/SmartRoofs/plugin.kt create mode 100644 plugin-playground/src/main/kotlin/SmartRoofs/plugin.properties diff --git a/client/src/main/java/plugin/api/API.java b/client/src/main/java/plugin/api/API.java index 78ec7b3..cd7489c 100644 --- a/client/src/main/java/plugin/api/API.java +++ b/client/src/main/java/plugin/api/API.java @@ -20,6 +20,10 @@ import static rt4.Player.plane; * @author ceikry */ public class API { + public static final int TILE_FLAG_UNDER_ROOF = 0x4; + private static final int DEFAULT_ROOF_VISIBILITY_GROUP_LIMIT = 2; + private static RoofVisibilityHandler roofVisibilityHandler = null; + public static Runnable[] miniMenuCustomActions = new Runnable[10]; public static int customMiniMenuIndex = 0; public static ArrayList registeredKeyListeners = new ArrayList<>(); @@ -246,6 +250,104 @@ public class API { return client.gameState == 30; } + public static int[] GetMapFlagTile() { + if (LoginManager.mapFlagX == 0) { + return null; + } + return new int[]{LoginManager.mapFlagX, LoginManager.mapFlagZ}; + } + + public static void SetRoofVisibilityHandler(RoofVisibilityHandler handler) { + roofVisibilityHandler = handler; + } + + public static void ClearRoofVisibilityHandler(RoofVisibilityHandler handler) { + if (roofVisibilityHandler == handler) { + roofVisibilityHandler = null; + } + } + + public static int GetRoofVisibilityGroupLimit() { + if (roofVisibilityHandler == null) { + return DEFAULT_ROOF_VISIBILITY_GROUP_LIMIT; + } + return Math.max(DEFAULT_ROOF_VISIBILITY_GROUP_LIMIT, roofVisibilityHandler.getGroupLimit()); + } + + public static boolean IsRoofVisibilityActive() { + return roofVisibilityHandler != null && roofVisibilityHandler.isActive(); + } + + public static void DisableRoofVisibilityIfExpired() { + if (roofVisibilityHandler != null) { + roofVisibilityHandler.disableIfExpired(); + } + } + + public static void EnsureRoofVisibilityBuffers() { + if (roofVisibilityHandler != null) { + roofVisibilityHandler.ensureBuffers(); + } + } + + public static void ApplyRoofVisibilityRequests() { + if (roofVisibilityHandler != null) { + roofVisibilityHandler.applyRequests(); + } + } + + public static boolean IsRoofVisibilityPicking() { + return roofVisibilityHandler != null && roofVisibilityHandler.isPicking(); + } + + public static int GetRoofVisibilityPickScreenX() { + return roofVisibilityHandler == null ? 0 : roofVisibilityHandler.getPickScreenX(); + } + + public static int GetRoofVisibilityPickScreenY() { + return roofVisibilityHandler == null ? 0 : roofVisibilityHandler.getPickScreenY(); + } + + public static void ReportRoofVisibilityTile(int sceneX, int sceneZ, int plane) { + if (roofVisibilityHandler != null) { + roofVisibilityHandler.reportTile(sceneX, sceneZ, plane); + } + } + + public static boolean IsRoofVisibilityLocPickable(long key) { + return roofVisibilityHandler != null && roofVisibilityHandler.isLocPickable(key); + } + + public static void ReportRoofVisibilityLoc(long key, int plane) { + if (roofVisibilityHandler != null) { + roofVisibilityHandler.reportLoc(key, plane); + } + } + + public static void BeginRoofVisibilityGroup(int group) { + if (roofVisibilityHandler != null) { + roofVisibilityHandler.beginGroup(group); + } + } + + public static void AddRoofVisibilityGroupTile(int group, int plane, int sceneX, int sceneZ) { + if (roofVisibilityHandler != null) { + roofVisibilityHandler.addGroupTile(group, plane, sceneX, sceneZ); + } + } + + public static void SetDestinationRoofTarget(int sceneX, int sceneZ) { + if (roofVisibilityHandler != null) { + roofVisibilityHandler.setDestinationTarget(sceneX, sceneZ); + } + } + + public static void ClearDestinationRoofTarget() { + if (roofVisibilityHandler != null) { + roofVisibilityHandler.clearDestinationTarget(); + } + } + public static void StoreData(String key, Object value) { PluginRepository.pluginStorage.put(key, value); PluginRepository.pluginStorage.put("_keystoreDirty", true); diff --git a/client/src/main/java/rt4/GlModel.java b/client/src/main/java/rt4/GlModel.java index 51ab917..73da52b 100644 --- a/client/src/main/java/rt4/GlModel.java +++ b/client/src/main/java/rt4/GlModel.java @@ -5,6 +5,7 @@ import org.openrs2.deob.annotation.OriginalArg; import org.openrs2.deob.annotation.OriginalClass; import org.openrs2.deob.annotation.OriginalMember; import org.openrs2.deob.annotation.Pc; +import plugin.api.API; import java.nio.ByteBuffer; @@ -972,7 +973,9 @@ public final class GlModel extends Model { local161 = MathUtils.sin[arg0]; local163 = MathUtils.cos[arg0]; } - if (arg8 > 0L && RawModel.allowInput && local70 > 0) { + @Pc(165) boolean roofVisibilityLocPick = API.IsRoofVisibilityLocPickable(arg8); + @Pc(168) boolean miniMenuPick = arg8 > 0L; + if ((miniMenuPick || roofVisibilityLocPick) && RawModel.allowInput && local70 > 0) { @Pc(187) int local187; @Pc(191) int local191; if (local84 > 0) { @@ -1046,63 +1049,79 @@ public final class GlModel extends Model { } if (anInt3582 >= local187 && anInt3582 <= local191 && RawModel.anInt1053 >= local206 && RawModel.anInt1053 <= local210) { if (this.pickable) { - Model.aLongArray11[MiniMenu.anInt7++] = arg8; - } else { - if (anIntArray468.length < this.anInt5296) { - anIntArray468 = new int[this.anInt5296]; - anIntArray467 = new int[this.anInt5296]; + if (miniMenuPick) { + Model.aLongArray11[MiniMenu.anInt7++] = arg8; } - local362 = 0; - label118: - while (true) { - if (local362 >= this.vertexCount) { - local362 = 0; - while (true) { - if (local362 >= this.triangleCount) { - break label118; + if (roofVisibilityLocPick) { + API.ReportRoofVisibilityLoc(arg8, arg9); + } + } else { + if (this.vertexX == null || this.vertexY == null || this.vertexZ == null || this.anIntArray462 == null || this.aShortArray81 == null) { + if (roofVisibilityLocPick) { + API.ReportRoofVisibilityLoc(arg8, arg9); + } + } else { + if (anIntArray468.length < this.anInt5296) { + anIntArray468 = new int[this.anInt5296]; + anIntArray467 = new int[this.anInt5296]; + } + local362 = 0; + label118: + while (true) { + if (local362 >= this.vertexCount) { + local362 = 0; + while (true) { + if (local362 >= this.triangleCount) { + break label118; + } + @Pc(698) short local698 = this.aShortArray77[local362]; + @Pc(703) short local703 = this.aShortArray82[local362]; + @Pc(708) short local708 = this.aShortArray83[local362]; + if (this.method4118(anInt3582, RawModel.anInt1053, anIntArray467[local698], anIntArray467[local703], anIntArray467[local708], anIntArray468[local698], anIntArray468[local703], anIntArray468[local708])) { + if (miniMenuPick) { + Model.aLongArray11[MiniMenu.anInt7++] = arg8; + } + if (roofVisibilityLocPick) { + API.ReportRoofVisibilityLoc(arg8, arg9); + } + break label118; + } + local362++; } - @Pc(698) short local698 = this.aShortArray77[local362]; - @Pc(703) short local703 = this.aShortArray82[local362]; - @Pc(708) short local708 = this.aShortArray83[local362]; - if (this.method4118(anInt3582, RawModel.anInt1053, anIntArray467[local698], anIntArray467[local703], anIntArray467[local708], anIntArray468[local698], anIntArray468[local703], anIntArray468[local708])) { - Model.aLongArray11[MiniMenu.anInt7++] = arg8; - break label118; - } - local362++; } - } - local369 = this.vertexX[local362]; - local373 = this.vertexY[local362]; - local377 = this.vertexZ[local362]; - if (arg0 != 0) { - local389 = local377 * local161 + local369 * local163 >> 16; - local377 = local377 * local163 - local369 * local161 >> 16; + local369 = this.vertexX[local362]; + local373 = this.vertexY[local362]; + local377 = this.vertexZ[local362]; + if (arg0 != 0) { + local389 = local377 * local161 + local369 * local163 >> 16; + local377 = local377 * local163 - local369 * local161 >> 16; + local369 = local389; + } + local369 += arg5; + local373 += arg6; + local377 += arg7; + local389 = local377 * arg3 + local369 * arg4 >> 16; + local377 = local377 * arg4 - local369 * arg3 >> 16; local369 = local389; - } - local369 += arg5; - local373 += arg6; - local377 += arg7; - local389 = local377 * arg3 + local369 * arg4 >> 16; - local377 = local377 * arg4 - local369 * arg3 >> 16; - local369 = local389; - local389 = local373 * arg2 - local377 * arg1 >> 16; - local377 = local373 * arg1 + local377 * arg2 >> 16; - if (local377 < 50) { - break; - } - local465 = (local369 << 9) / local377; - local471 = (local389 << 9) / local377; - @Pc(652) int local652 = this.anIntArray462[local362]; - @Pc(659) int local659 = this.anIntArray462[local362 + 1]; - for (@Pc(661) int local661 = local652; local661 < local659; local661++) { - @Pc(671) int local671 = this.aShortArray81[local661] - 1; - if (local671 == -1) { + local389 = local373 * arg2 - local377 * arg1 >> 16; + local377 = local373 * arg1 + local377 * arg2 >> 16; + if (local377 < 50) { break; } - anIntArray468[local671] = local465; - anIntArray467[local671] = local471; + local465 = (local369 << 9) / local377; + local471 = (local389 << 9) / local377; + @Pc(652) int local652 = this.anIntArray462[local362]; + @Pc(659) int local659 = this.anIntArray462[local362 + 1]; + for (@Pc(661) int local661 = local652; local661 < local659; local661++) { + @Pc(671) int local671 = this.aShortArray81[local661] - 1; + if (local671 == -1) { + break; + } + anIntArray468[local671] = local465; + anIntArray467[local671] = local471; + } + local362++; } - local362++; } } } diff --git a/client/src/main/java/rt4/MiniMenu.java b/client/src/main/java/rt4/MiniMenu.java index 519c68b..9bf74f5 100644 --- a/client/src/main/java/rt4/MiniMenu.java +++ b/client/src/main/java/rt4/MiniMenu.java @@ -606,11 +606,17 @@ public class MiniMenu { } if (actionCode == WALK_HERE) { if (local36 == 0) { + if (API.IsRoofVisibilityActive()) { + API.ClearDestinationRoofTarget(); + } method3556(Player.plane, local15, local19); } else if (local36 == 1) { if (LoginManager.staffModLevel > 0 && Keyboard.pressedKeys[Keyboard.KEY_CTRL] && Keyboard.pressedKeys[Keyboard.KEY_SHIFT]) { Cheat.teleport(Camera.originX + local15, Camera.originZ + local19, Player.plane); } else if (PathFinder.findPath(PlayerList.self.movementQueueZ[0], 0, 0, true, 0, local15, 0, 0, 1, local19, PlayerList.self.movementQueueX[0])) { + if (API.IsRoofVisibilityActive()) { + API.SetDestinationRoofTarget(local15, local19); + } Protocol.outboundBuffer.p1(InterfaceList.anInt5); Protocol.outboundBuffer.p1(anInt2878); Protocol.outboundBuffer.p2((int) Camera.yawTarget); diff --git a/client/src/main/java/rt4/SceneGraph.java b/client/src/main/java/rt4/SceneGraph.java index d2e22c3..62ca01c 100644 --- a/client/src/main/java/rt4/SceneGraph.java +++ b/client/src/main/java/rt4/SceneGraph.java @@ -4,6 +4,7 @@ import com.jogamp.opengl.GL2; import org.openrs2.deob.annotation.OriginalArg; import org.openrs2.deob.annotation.OriginalMember; import org.openrs2.deob.annotation.Pc; +import plugin.api.API; public class SceneGraph { @@ -1744,7 +1745,7 @@ public class SceneGraph { } if (local153.plainTile == null) { if (local153.shapedTile != null) { - method2762(local153.shapedTile, anInt2886, anInt3038, anInt5205, anInt2222, local18, local21, method187(0, local18, local21)); + method2762(local153.shapedTile, anInt2886, anInt3038, anInt5205, anInt2222, local18, local21, 0, method187(0, local18, local21)); } } else method2610(local153.plainTile, 0, anInt2886, anInt3038, anInt5205, anInt2222, local18, local21, method187(0, local18, local21)); @@ -1776,10 +1777,10 @@ public class SceneGraph { if (local8.plainTile == null) { if (local8.shapedTile != null) { if (method187(local27, local18, local21)) { - method2762(local8.shapedTile, anInt2886, anInt3038, anInt5205, anInt2222, local18, local21, true); + method2762(local8.shapedTile, anInt2886, anInt3038, anInt5205, anInt2222, local18, local21, local27, true); } else { var24 = true; - method2762(local8.shapedTile, anInt2886, anInt3038, anInt5205, anInt2222, local18, local21, false); + method2762(local8.shapedTile, anInt2886, anInt3038, anInt5205, anInt2222, local18, local21, local27, false); } } } else if (method187(local27, local18, local21)) { @@ -3915,6 +3916,9 @@ public class SceneGraph { MiniMenu.anInt1742 = arg6; MiniMenu.anInt2954 = arg7; } + if (API.IsRoofVisibilityPicking() && method583(API.GetRoofVisibilityPickScreenX() + Rasteriser.centerX, API.GetRoofVisibilityPickScreenY() + Rasteriser.centerY, local315, local331, local299, local307, local323, local291)) { + API.ReportRoofVisibilityTile(arg6, arg7, arg1); + } if (!GlRenderer.enabled && !arg8) { Rasteriser.testX = local307 < 0 || local323 < 0 || local291 < 0 || local307 > Rasteriser.width || local323 > Rasteriser.width || local291 > Rasteriser.width; if (arg0.anInt4869 == -1) { @@ -3938,6 +3942,9 @@ public class SceneGraph { MiniMenu.anInt1742 = arg6; MiniMenu.anInt2954 = arg7; } + if (API.IsRoofVisibilityPicking() && method583(API.GetRoofVisibilityPickScreenX() + Rasteriser.centerX, API.GetRoofVisibilityPickScreenY() + Rasteriser.centerY, local283, local299, local331, local275, local291, local323)) { + API.ReportRoofVisibilityTile(arg6, arg7, arg1); + } if (GlRenderer.enabled || arg8) { return; } @@ -4225,7 +4232,7 @@ public class SceneGraph { } @OriginalMember(owner = "client!lh", name = "a", descriptor = "(Lclient!fg;IIIIIIZ)V") - public static void method2762(@OriginalArg(0) ShapedTile arg0, @OriginalArg(1) int arg1, @OriginalArg(2) int arg2, @OriginalArg(3) int arg3, @OriginalArg(4) int arg4, @OriginalArg(5) int arg5, @OriginalArg(6) int arg6, @OriginalArg(7) boolean arg7) { + public static void method2762(@OriginalArg(0) ShapedTile arg0, @OriginalArg(1) int arg1, @OriginalArg(2) int arg2, @OriginalArg(3) int arg3, @OriginalArg(4) int arg4, @OriginalArg(5) int arg5, @OriginalArg(6) int arg6, int arg7, @OriginalArg(7) boolean arg8) { @Pc(3) int local3 = arg0.anIntArray168.length; @Pc(5) int local5; @Pc(15) int local15; @@ -4268,7 +4275,10 @@ public class SceneGraph { MiniMenu.anInt1742 = arg5; MiniMenu.anInt2954 = arg6; } - if (!GlRenderer.enabled && !arg7) { + if (API.IsRoofVisibilityPicking() && method583(API.GetRoofVisibilityPickScreenX() + Rasteriser.centerX, API.GetRoofVisibilityPickScreenY() + Rasteriser.centerY, local156, local160, local164, local39, local148, local152)) { + API.ReportRoofVisibilityTile(arg5, arg6, arg7); + } + if (!GlRenderer.enabled && !arg8) { Rasteriser.testX = local39 < 0 || local148 < 0 || local152 < 0 || local39 > Rasteriser.width || local148 > Rasteriser.width || local152 > Rasteriser.width; if (arg0.anIntArray161 == null || arg0.anIntArray161[local5] == -1) { if (arg0.anIntArray167[local5] != 12345678) { diff --git a/client/src/main/java/rt4/ScriptRunner.java b/client/src/main/java/rt4/ScriptRunner.java index cd55e4e..db4e557 100644 --- a/client/src/main/java/rt4/ScriptRunner.java +++ b/client/src/main/java/rt4/ScriptRunner.java @@ -4,6 +4,7 @@ import org.openrs2.deob.annotation.OriginalArg; import org.openrs2.deob.annotation.OriginalMember; import org.openrs2.deob.annotation.Pc; import plugin.PluginRepository; +import plugin.api.API; import java.nio.charset.StandardCharsets; import java.util.Calendar; @@ -116,15 +117,15 @@ public final class ScriptRunner { @OriginalMember(owner = "client!vg", name = "c", descriptor = "Z") public static boolean aBoolean299 = false; @OriginalMember(owner = "client!lj", name = "z", descriptor = "[I") - public static int[] anIntArray338 = new int[2]; + public static int[] anIntArray338 = new int[8]; @OriginalMember(owner = "client!vl", name = "i", descriptor = "[I") - public static int[] anIntArray518 = new int[2]; + public static int[] anIntArray518 = new int[8]; @OriginalMember(owner = "client!tk", name = "K", descriptor = "[I") - public static int[] anIntArray476 = new int[2]; + public static int[] anIntArray476 = new int[8]; @OriginalMember(owner = "client!e", name = "xc", descriptor = "[I") - public static int[] anIntArray134 = new int[2]; + public static int[] anIntArray134 = new int[8]; @OriginalMember(owner = "client!ge", name = "k", descriptor = "[I") - public static int[] anIntArray205 = new int[2]; + public static int[] anIntArray205 = new int[8]; @OriginalMember(owner = "client!nm", name = "W", descriptor = "Lclient!na;") public static JagString url; @OriginalMember(owner = "client!bf", name = "B", descriptor = "I") @@ -976,12 +977,16 @@ public final class ScriptRunner { method2608(); } else { method960((byte) (anInt3325 - 4 & 0xFF)); - method3993(2); + method3993(API.GetRoofVisibilityGroupLimit()); } } @OriginalMember(owner = "client!tc", name = "a", descriptor = "(B)I") public static int method4047() { + return getBaseRoofMode(); + } + + private static int getBaseRoofMode() { if (neverRemoveRoofs) { return 0; } else if (SceneGraph.allLevelsAreVisible()) { @@ -991,6 +996,11 @@ public final class ScriptRunner { } } + public static boolean canUseSelectiveRoofHiding() { + return getBaseRoofMode() == 2; + } + + @OriginalMember(owner = "client!ok", name = "a", descriptor = "(IIB)Lclient!ce;") public static SecondaryLinkedList method3333(@OriginalArg(0) int arg0, @OriginalArg(1) int arg1) { @Pc(9) SecondaryLinkedList local9 = new SecondaryLinkedList(); @@ -1042,18 +1052,31 @@ public final class ScriptRunner { @OriginalMember(owner = "client!uj", name = "a", descriptor = "(BZII[[[Lclient!bj;I)Z") public static boolean method4348(@OriginalArg(1) boolean arg0, @OriginalArg(2) int arg1, @OriginalArg(3) int arg2, @OriginalArg(4) Tile[][][] arg3, @OriginalArg(5) int arg4) { - @Pc(14) byte local14 = arg0 ? 1 : (byte) (anInt3325 & 0xFF); - if (local14 == aByteArrayArrayArray15[Player.plane][arg1][arg2]) { + return method4348(arg0, arg1, arg2, arg3, arg4, Player.plane); + } + + + public static boolean hideRoofAt(int sceneX, int sceneZ, int group, int plane) { + return method4348(false, sceneX, sceneZ, SceneGraph.tiles, group, plane); + } + + private static boolean method4348(boolean arg0, int arg1, int arg2, Tile[][][] arg3, int arg4, int plane) { + if (plane < 0 || plane >= 3) { return false; - } else if ((SceneGraph.renderFlags[Player.plane][arg1][arg2] & 0x4) == 0) { + } + @Pc(14) byte local14 = arg0 ? 1 : (byte) (anInt3325 & 0xFF); + if (local14 == aByteArrayArrayArray15[plane][arg1][arg2]) { + return false; + } else if ((SceneGraph.renderFlags[plane][arg1][arg2] & API.TILE_FLAG_UNDER_ROOF) == 0) { return false; } else { + API.BeginRoofVisibilityGroup(arg4); @Pc(47) int local47 = 0; @Pc(49) byte local49 = 0; PathFinder.queueX[0] = arg1; @Pc(69) int local69 = local49 + 1; PathFinder.queueZ[0] = arg2; - aByteArrayArrayArray15[Player.plane][arg1][arg2] = local14; + aByteArrayArrayArray15[plane][arg1][arg2] = local14; while (local47 != local69) { @Pc(94) int local94 = PathFinder.queueX[local47] >> 16 & 0xFF; @Pc(102) int local102 = PathFinder.queueX[local47] >> 24 & 0xFF; @@ -1063,13 +1086,13 @@ public final class ScriptRunner { local47 = local47 + 1 & 0xFFF; @Pc(130) boolean local130 = false; @Pc(132) boolean local132 = false; - if ((SceneGraph.renderFlags[Player.plane][local108][local122] & 0x4) == 0) { + if ((SceneGraph.renderFlags[plane][local108][local122] & API.TILE_FLAG_UNDER_ROOF) == 0) { local130 = true; } @Pc(150) int local150; @Pc(191) int local191; label238: - for (local150 = Player.plane + 1; local150 <= 3; local150++) { + for (local150 = plane + 1; local150 <= 3; local150++) { if ((SceneGraph.renderFlags[local150][local108][local122] & 0x8) == 0) { @Pc(227) int local227; @Pc(358) int local358; @@ -1124,8 +1147,8 @@ public final class ScriptRunner { } } if (local132) { - if (SceneGraph.tileHeights[Player.plane + 1][local108][local122] > anIntArray205[arg4]) { - anIntArray205[arg4] = SceneGraph.tileHeights[Player.plane + 1][local108][local122]; + if (SceneGraph.tileHeights[plane + 1][local108][local122] > anIntArray205[arg4]) { + anIntArray205[arg4] = SceneGraph.tileHeights[plane + 1][local108][local122]; } local150 = local108 << 7; if (local150 < anIntArray338[arg4]) { @@ -1139,60 +1162,61 @@ public final class ScriptRunner { } else if (anIntArray134[arg4] < local191) { anIntArray134[arg4] = local191; } + API.AddRoofVisibilityGroupTile(arg4, plane, local108, local122); } if (!local130) { - if (local108 >= 1 && aByteArrayArrayArray15[Player.plane][local108 - 1][local122] != local14) { + if (local108 >= 1 && aByteArrayArrayArray15[plane][local108 - 1][local122] != local14) { PathFinder.queueX[local69] = local108 - 1 | 0x120000 | 0xD3000000; PathFinder.queueZ[local69] = local122 | 0x130000; local69 = local69 + 1 & 0xFFF; - aByteArrayArrayArray15[Player.plane][local108 - 1][local122] = local14; + aByteArrayArrayArray15[plane][local108 - 1][local122] = local14; } local122++; if (local122 < 104) { - if (local108 - 1 >= 0 && local14 != aByteArrayArrayArray15[Player.plane][local108 - 1][local122] && (SceneGraph.renderFlags[Player.plane][local108][local122] & 0x4) == 0 && (SceneGraph.renderFlags[Player.plane][local108 - 1][local122 - 1] & 0x4) == 0) { + if (local108 - 1 >= 0 && local14 != aByteArrayArrayArray15[plane][local108 - 1][local122] && (SceneGraph.renderFlags[plane][local108][local122] & API.TILE_FLAG_UNDER_ROOF) == 0 && (SceneGraph.renderFlags[plane][local108 - 1][local122 - 1] & API.TILE_FLAG_UNDER_ROOF) == 0) { PathFinder.queueX[local69] = 0x52000000 | 0x120000 | local108 - 1; PathFinder.queueZ[local69] = local122 | 0x130000; - aByteArrayArrayArray15[Player.plane][local108 - 1][local122] = local14; + aByteArrayArrayArray15[plane][local108 - 1][local122] = local14; local69 = local69 + 1 & 0xFFF; } - if (local14 != aByteArrayArrayArray15[Player.plane][local108][local122]) { + if (local14 != aByteArrayArrayArray15[plane][local108][local122]) { PathFinder.queueX[local69] = local108 | 0x13000000 | 0x520000; PathFinder.queueZ[local69] = local122 | 0x530000; local69 = local69 + 1 & 0xFFF; - aByteArrayArrayArray15[Player.plane][local108][local122] = local14; + aByteArrayArrayArray15[plane][local108][local122] = local14; } - if (local108 + 1 < 104 && aByteArrayArrayArray15[Player.plane][local108 + 1][local122] != local14 && (SceneGraph.renderFlags[Player.plane][local108][local122] & 0x4) == 0 && (SceneGraph.renderFlags[Player.plane][local108 + 1][local122 - 1] & 0x4) == 0) { + if (local108 + 1 < 104 && aByteArrayArrayArray15[plane][local108 + 1][local122] != local14 && (SceneGraph.renderFlags[plane][local108][local122] & API.TILE_FLAG_UNDER_ROOF) == 0 && (SceneGraph.renderFlags[plane][local108 + 1][local122 - 1] & API.TILE_FLAG_UNDER_ROOF) == 0) { PathFinder.queueX[local69] = 0x92000000 | 0x520000 | local108 + 1; PathFinder.queueZ[local69] = local122 | 0x530000; - aByteArrayArrayArray15[Player.plane][local108 + 1][local122] = local14; + aByteArrayArrayArray15[plane][local108 + 1][local122] = local14; local69 = local69 + 1 & 0xFFF; } } local122--; - if (local108 + 1 < 104 && local14 != aByteArrayArrayArray15[Player.plane][local108 + 1][local122]) { + if (local108 + 1 < 104 && local14 != aByteArrayArrayArray15[plane][local108 + 1][local122]) { PathFinder.queueX[local69] = local108 + 1 | 0x920000 | 0x53000000; PathFinder.queueZ[local69] = local122 | 0x930000; - aByteArrayArrayArray15[Player.plane][local108 + 1][local122] = local14; + aByteArrayArrayArray15[plane][local108 + 1][local122] = local14; local69 = local69 + 1 & 0xFFF; } local122--; if (local122 >= 0) { - if (local108 - 1 >= 0 && aByteArrayArrayArray15[Player.plane][local108 - 1][local122] != local14 && (SceneGraph.renderFlags[Player.plane][local108][local122] & 0x4) == 0 && (SceneGraph.renderFlags[Player.plane][local108 - 1][local122 + 1] & 0x4) == 0) { + if (local108 - 1 >= 0 && aByteArrayArrayArray15[plane][local108 - 1][local122] != local14 && (SceneGraph.renderFlags[plane][local108][local122] & API.TILE_FLAG_UNDER_ROOF) == 0 && (SceneGraph.renderFlags[plane][local108 - 1][local122 + 1] & API.TILE_FLAG_UNDER_ROOF) == 0) { PathFinder.queueX[local69] = local108 - 1 | 0xD20000 | 0x12000000; PathFinder.queueZ[local69] = local122 | 0xD30000; - aByteArrayArrayArray15[Player.plane][local108 - 1][local122] = local14; + aByteArrayArrayArray15[plane][local108 - 1][local122] = local14; local69 = local69 + 1 & 0xFFF; } - if (local14 != aByteArrayArrayArray15[Player.plane][local108][local122]) { + if (local14 != aByteArrayArrayArray15[plane][local108][local122]) { PathFinder.queueX[local69] = local108 | 0xD20000 | 0x93000000; PathFinder.queueZ[local69] = local122 | 0xD30000; local69 = local69 + 1 & 0xFFF; - aByteArrayArrayArray15[Player.plane][local108][local122] = local14; + aByteArrayArrayArray15[plane][local108][local122] = local14; } - if (local108 + 1 < 104 && aByteArrayArrayArray15[Player.plane][local108 + 1][local122] != local14 && (SceneGraph.renderFlags[Player.plane][local108][local122] & 0x4) == 0 && (SceneGraph.renderFlags[Player.plane][local108 + 1][local122 + 1] & 0x4) == 0) { + if (local108 + 1 < 104 && aByteArrayArrayArray15[plane][local108 + 1][local122] != local14 && (SceneGraph.renderFlags[plane][local108][local122] & API.TILE_FLAG_UNDER_ROOF) == 0 && (SceneGraph.renderFlags[plane][local108 + 1][local122 + 1] & API.TILE_FLAG_UNDER_ROOF) == 0) { PathFinder.queueX[local69] = local108 + 1 | 0xD2000000 | 0x920000; PathFinder.queueZ[local69] = local122 | 0x930000; - aByteArrayArrayArray15[Player.plane][local108 + 1][local122] = local14; + aByteArrayArrayArray15[plane][local108 + 1][local122] = local14; local69 = local69 + 1 & 0xFFF; } } @@ -1316,9 +1340,12 @@ public final class ScriptRunner { @OriginalMember(owner = "client!uh", name = "f", descriptor = "(I)V") public static void method4302() { + API.DisableRoofVisibilityIfExpired(); if (method4047() != 2) { return; } + API.EnsureRoofVisibilityBuffers(); + boolean roofVisibilityActive = API.IsRoofVisibilityActive(); @Pc(27) byte local27 = (byte) (anInt3325 - 4 & 0xFF); @Pc(31) int local31 = anInt3325 % 104; @Pc(33) int local33; @@ -1331,7 +1358,7 @@ public final class ScriptRunner { if (Player.plane == 3) { return; } - for (local33 = 0; local33 < 2; local33++) { + for (local33 = 0; local33 < anIntArray205.length; local33++) { anIntArray205[local33] = -1000000; anIntArray338[local33] = 1000000; anIntArray518[local33] = 0; @@ -1340,14 +1367,17 @@ public final class ScriptRunner { } if (Camera.cameraType != 1) { local33 = SceneGraph.getTileHeight(Player.plane, Camera.renderX, Camera.renderZ); - if (local33 - Camera.anInt40 < 800 && (SceneGraph.renderFlags[Player.plane][Camera.renderX >> 7][Camera.renderZ >> 7] & 0x4) != 0) { + if (local33 - Camera.anInt40 < 800 && (SceneGraph.renderFlags[Player.plane][Camera.renderX >> 7][Camera.renderZ >> 7] & API.TILE_FLAG_UNDER_ROOF) != 0) { method4348(false, Camera.renderX >> 7, Camera.renderZ >> 7, SceneGraph.tiles, 1); } return; } - if ((SceneGraph.renderFlags[Player.plane][PlayerList.self.xFine >> 7][PlayerList.self.zFine >> 7] & 0x4) != 0) { + if ((SceneGraph.renderFlags[Player.plane][PlayerList.self.xFine >> 7][PlayerList.self.zFine >> 7] & API.TILE_FLAG_UNDER_ROOF) != 0) { method4348(false, PlayerList.self.xFine >> 7, PlayerList.self.zFine >> 7, SceneGraph.tiles, 0); } + if (roofVisibilityActive) { + API.ApplyRoofVisibilityRequests(); + } if (Camera.cameraPitch >= 310) { return; } @@ -1378,7 +1408,7 @@ public final class ScriptRunner { } else if (local40 > local135) { local40--; } - if ((SceneGraph.renderFlags[Player.plane][local33][local40] & 0x4) != 0) { + if ((SceneGraph.renderFlags[Player.plane][local33][local40] & API.TILE_FLAG_UNDER_ROOF) != 0) { method4348(false, local33, local40, SceneGraph.tiles, 1); break; } @@ -1390,7 +1420,7 @@ public final class ScriptRunner { local33--; } local186 -= 65536; - if ((SceneGraph.renderFlags[Player.plane][local33][local40] & 0x4) != 0) { + if ((SceneGraph.renderFlags[Player.plane][local33][local40] & API.TILE_FLAG_UNDER_ROOF) != 0) { method4348(false, local33, local40, SceneGraph.tiles, 1); break; } @@ -1406,7 +1436,7 @@ public final class ScriptRunner { } else if (local33 > local162) { local33--; } - if ((SceneGraph.renderFlags[Player.plane][local33][local40] & 0x4) != 0) { + if ((SceneGraph.renderFlags[Player.plane][local33][local40] & API.TILE_FLAG_UNDER_ROOF) != 0) { method4348(false, local33, local40, SceneGraph.tiles, 1); break; } @@ -1418,7 +1448,7 @@ public final class ScriptRunner { local40--; } local186 -= 65536; - if ((SceneGraph.renderFlags[Player.plane][local33][local40] & 0x4) != 0) { + if ((SceneGraph.renderFlags[Player.plane][local33][local40] & API.TILE_FLAG_UNDER_ROOF) != 0) { method4348(false, local33, local40, SceneGraph.tiles, 1); break; } diff --git a/client/src/main/java/rt4/SoftwareModel.java b/client/src/main/java/rt4/SoftwareModel.java index ab42e89..6f1a5b3 100644 --- a/client/src/main/java/rt4/SoftwareModel.java +++ b/client/src/main/java/rt4/SoftwareModel.java @@ -4,6 +4,7 @@ import org.openrs2.deob.annotation.OriginalArg; import org.openrs2.deob.annotation.OriginalClass; import org.openrs2.deob.annotation.OriginalMember; import org.openrs2.deob.annotation.Pc; +import plugin.api.API; @OriginalClass("client!w") public final class SoftwareModel extends Model { @@ -833,7 +834,9 @@ public final class SoftwareModel extends Model { @Pc(223) int x; @Pc(208) int y; @Pc(227) int z; - if (key > 0L && RawModel.allowInput && d > 0) { + @Pc(229) boolean roofVisibilityLocPick = API.IsRoofVisibilityLocPickable(key); + @Pc(232) boolean miniMenuPick = key > 0L; + if ((miniMenuPick || roofVisibilityLocPick) && RawModel.allowInput && d > 0) { if (e > 0) { v = maxScreenX / c; y = minScreenX / d; @@ -893,7 +896,12 @@ public final class SoftwareModel extends Model { } if (GlModel.anInt3582 >= v && GlModel.anInt3582 <= y && RawModel.anInt1053 >= x && RawModel.anInt1053 <= z) { if (this.pickable) { - Model.aLongArray11[MiniMenu.anInt7++] = key; + if (miniMenuPick) { + Model.aLongArray11[MiniMenu.anInt7++] = key; + } + if (roofVisibilityLocPick) { + API.ReportRoofVisibilityLoc(key, arg9); + } } else { local190 = true; } @@ -933,7 +941,7 @@ public final class SoftwareModel extends Model { } } try { - this.draw(project1, local190, key, b - d, c - d + 2, arg10); + this.draw(project1, local190, key, b - d, c - d + 2, arg9, arg10); } catch (@Pc(713) Exception ex) { ex.printStackTrace(); } @@ -1148,7 +1156,7 @@ public final class SoftwareModel extends Model { projectSceneZ[local52] = local71; } } - this.draw(false, arg6 >= 0L, arg6, this.aShort35, this.aShort35 << 1, null); + this.draw(false, arg6 >= 0L || API.IsRoofVisibilityLocPickable(arg6), arg6, this.aShort35, this.aShort35 << 1, Player.plane, null); } catch (@Pc(240) RuntimeException local240) { } } @@ -1583,7 +1591,7 @@ public final class SoftwareModel extends Model { } @OriginalMember(owner = "client!w", name = "a", descriptor = "(ZZJIILclient!ga;)V") - private void draw(@OriginalArg(0) boolean arg0, @OriginalArg(1) boolean arg1, @OriginalArg(2) long arg2, @OriginalArg(3) int arg3, @OriginalArg(4) int arg4, @OriginalArg(5) ParticleSystem arg5) { + private void draw(@OriginalArg(0) boolean arg0, @OriginalArg(1) boolean arg1, @OriginalArg(2) long arg2, @OriginalArg(3) int arg3, @OriginalArg(4) int arg4, int roofHidePlane, @OriginalArg(5) ParticleSystem arg5) { if (arg4 >= 1600) { return; } @@ -1659,7 +1667,12 @@ public final class SoftwareModel extends Model { } } else { if (arg1 && this.pointWithinTriangle(GlModel.anInt3582 + Rasteriser.centerX, RawModel.anInt1053 + Rasteriser.centerY, vertexScreenY[local51], vertexScreenY[local56], vertexScreenY[pri], local65, local69, local73)) { - Model.aLongArray11[MiniMenu.anInt7++] = arg2; + if (arg2 >= 0L) { + Model.aLongArray11[MiniMenu.anInt7++] = arg2; + } + if (API.IsRoofVisibilityLocPickable(arg2)) { + API.ReportRoofVisibilityLoc(arg2, roofHidePlane); + } arg1 = false; } if ((local65 - local69) * (vertexScreenY[pri] - vertexScreenY[local56]) - (vertexScreenY[local51] - vertexScreenY[local56]) * (local73 - local69) > 0) { @@ -1946,7 +1959,7 @@ public final class SoftwareModel extends Model { projectSceneZ[local52] = local71; } } - this.draw(false, false, 0L, this.aShort35, this.aShort35 << 1, null); + this.draw(false, false, 0L, this.aShort35, this.aShort35 << 1, Player.plane, null); } catch (@Pc(234) RuntimeException local234) { } } diff --git a/client/src/main/kotlin/plugin/api/RoofVisibilityHandler.kt b/client/src/main/kotlin/plugin/api/RoofVisibilityHandler.kt new file mode 100644 index 0000000..4d2b9c5 --- /dev/null +++ b/client/src/main/kotlin/plugin/api/RoofVisibilityHandler.kt @@ -0,0 +1,33 @@ +package plugin.api + +interface RoofVisibilityHandler { + fun getGroupLimit(): Int + + fun isActive(): Boolean + + fun disableIfExpired() + + fun ensureBuffers() + + fun applyRequests() + + fun isPicking(): Boolean + + fun getPickScreenX(): Int + + fun getPickScreenY(): Int + + fun reportTile(sceneX: Int, sceneZ: Int, plane: Int) + + fun isLocPickable(key: Long): Boolean + + fun reportLoc(key: Long, plane: Int) + + fun beginGroup(group: Int) + + fun addGroupTile(group: Int, plane: Int, sceneX: Int, sceneZ: Int) + + fun setDestinationTarget(sceneX: Int, sceneZ: Int) + + fun clearDestinationTarget() +} diff --git a/plugin-playground/src/main/kotlin/SmartRoofs/RoofHider.kt b/plugin-playground/src/main/kotlin/SmartRoofs/RoofHider.kt new file mode 100644 index 0000000..eb94cb7 --- /dev/null +++ b/plugin-playground/src/main/kotlin/SmartRoofs/RoofHider.kt @@ -0,0 +1,703 @@ +package SmartRoofs + +import plugin.api.API +import plugin.api.MiniMenuAction +import plugin.api.MiniMenuEntry +import plugin.api.RoofVisibilityHandler +import rt4.Camera +import rt4.Cs1ScriptRunner +import rt4.LocType +import rt4.MathUtils +import rt4.Player +import rt4.PlayerList +import rt4.SceneGraph +import rt4.ScriptRunner +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min + +object RoofHider { + private const val TILE_FLAG_UNDER_ROOF = API.TILE_FLAG_UNDER_ROOF + private const val SMART_ROOFS_ACTIVE_GRACE_MS = 750L + + private const val MAP_FLAG_ROOF_GROUP = 2 + + private const val HOVER_GROUP_START = 3 + private const val HOVER_TARGET_LIMIT = 8 + private const val HOVER_HOLD_MS = 125L + private const val HOVER_PRIMARY_HIT_DISTANCE = 64 + private const val HOVER_BUILDING_FOOTPRINT_PRIMARY_HIT_DISTANCE = 12 + private const val HOVER_UPPER_PLANE_TILE_SEARCH_RADIUS = 2 + private const val HOVER_BUILDING_FOOTPRINT_X_PADDING = 2 + private const val HOVER_BUILDING_FOOTPRINT_TOP_PADDING = 64 + private const val HOVER_BUILDING_FOOTPRINT_BOTTOM_PADDING = 4 + private const val HOVER_OUTSIDE_GROUND_HIT_MS = 50L + + private const val DESTINATION_TARGET_GROUP = HOVER_GROUP_START + HOVER_TARGET_LIMIT + const val SELECTIVE_ROOF_GROUP_COUNT = DESTINATION_TARGET_GROUP + 1 + private const val DESTINATION_HOLD_MS = 20000L + private const val DESTINATION_REACHED_DISTANCE = 1 + + @JvmField + val HANDLER: RoofVisibilityHandler = object : RoofVisibilityHandler { + override fun getGroupLimit(): Int = SELECTIVE_ROOF_GROUP_COUNT + + override fun isActive(): Boolean = isSmartRoofsActive() + + override fun disableIfExpired() = disableSmartRoofsIfExpired() + + override fun ensureBuffers() = ensureSelectiveRoofBuffers() + + override fun applyRequests() { + hideMapFlagRoof() + hideHoverTargets() + hideDestinationTarget() + } + + override fun isPicking(): Boolean = isHoverPicking() + + override fun getPickScreenX(): Int = getHoverPickScreenX() + + override fun getPickScreenY(): Int = getHoverPickScreenY() + + override fun reportTile(sceneX: Int, sceneZ: Int, plane: Int) { + setHoverTile(sceneX, sceneZ, plane) + } + + override fun isLocPickable(key: Long): Boolean = isHoverLocPickable(key) + + override fun reportLoc(key: Long, plane: Int) { + setHoverLoc(key, plane) + } + + override fun beginGroup(group: Int) = beginBuildingFootprint(group) + + override fun addGroupTile(group: Int, plane: Int, sceneX: Int, sceneZ: Int) { + addBuildingFootprintTile(group, plane, sceneX, sceneZ) + } + + override fun setDestinationTarget(sceneX: Int, sceneZ: Int) { + this@RoofHider.setDestinationTarget(sceneX, sceneZ) + } + + override fun clearDestinationTarget() = this@RoofHider.clearDestinationTarget() + + } + + private var smartRoofsEnabled = false + private var smartRoofsActiveUntil = 0L + private var smartRoofsWasActive = false + + private var mapFlagRoofX = -1 + private var mapFlagRoofZ = -1 + private var mapFlagRoofRadius = 0 + + private var hoverPickActive = false + private var hoverPickScreenX = 0 + private var hoverPickScreenY = 0 + private val hoverTargetX = IntArray(HOVER_TARGET_LIMIT) { -1 } + private val hoverTargetZ = IntArray(HOVER_TARGET_LIMIT) { -1 } + private val hoverTargetPlane = IntArray(HOVER_TARGET_LIMIT) { -1 } + private val hoverPrimaryHitX = IntArray(HOVER_TARGET_LIMIT) { Int.MIN_VALUE } + private val hoverPrimaryHitY = IntArray(HOVER_TARGET_LIMIT) { Int.MIN_VALUE } + private val hoverTargetUntil = LongArray(HOVER_TARGET_LIMIT) + private val buildingFootprintMinX = IntArray(HOVER_TARGET_LIMIT) { Int.MAX_VALUE } + private val buildingFootprintMaxX = IntArray(HOVER_TARGET_LIMIT) { Int.MIN_VALUE } + private val buildingFootprintMinY = IntArray(HOVER_TARGET_LIMIT) { Int.MAX_VALUE } + private val buildingFootprintMaxY = IntArray(HOVER_TARGET_LIMIT) { Int.MIN_VALUE } + private val buildingFootprintTiles = Array(HOVER_TARGET_LIMIT) { Array(104) { BooleanArray(104) } } + private var lastHoverTileX = -1 + private var lastHoverTileZ = -1 + private var lastHoverTilePlane = -1 + private var lastHoverTileValid = false + private var lastHoverTileAt = 0L + private var lastHoverHitAt = 0L + + private var destinationTargetX = -1 + private var destinationTargetZ = -1 + private var destinationTargetUntil = 0L + + fun isSmartRoofsActive(): Boolean { + return smartRoofsEnabled && isSmartRoofsActive(System.currentTimeMillis()) && ScriptRunner.canUseSelectiveRoofHiding() + } + + private fun isSmartRoofsActive(now: Long): Boolean { + return now <= smartRoofsActiveUntil + } + + private fun updateSmartRoofsPulse(now: Long) { + smartRoofsActiveUntil = now + SMART_ROOFS_ACTIVE_GRACE_MS + smartRoofsWasActive = true + } + + private fun disableSmartRoofsIfExpired() { + val now = System.currentTimeMillis() + if (!smartRoofsWasActive || isSmartRoofsActive(now)) { + return + } + clearSmartRoofsState() + smartRoofsWasActive = false + ScriptRunner.method2218() + } + + private fun clearSmartRoofsState() { + clearMapFlagRoof() + clearHoverRequest() + clearDestinationTarget() + smartRoofsEnabled = false + } + + fun setSmartRoofsEnabled(enabled: Boolean) { + if (!enabled) { + clearSmartRoofsState() + return + } + smartRoofsEnabled = true + } + + fun updateSmartRoofs() { + if (!smartRoofsEnabled) { + clearSmartRoofsState() + return + } + val now = System.currentTimeMillis() + val wasActive = isSmartRoofsActive(now) + updateSmartRoofsPulse(now) + if (!ScriptRunner.canUseSelectiveRoofHiding()) { + clearSmartRoofsState() + return + } + val rebuild = !wasActive || + ScriptRunner.method4047() != 2 || + ScriptRunner.aByteArrayArrayArray15 == null || + ScriptRunner.anIntArray205.size < SELECTIVE_ROOF_GROUP_COUNT + if (rebuild) { + ScriptRunner.method2218() + } else { + ensureSelectiveRoofBuffers() + } + } + + private fun ensureSelectiveRoofBuffers() { + if (ScriptRunner.aByteArrayArrayArray15 == null) { + ScriptRunner.method960(((ScriptRunner.anInt3325 - 4) and 0xFF).toByte()) + } + if (ScriptRunner.anIntArray205.size < SELECTIVE_ROOF_GROUP_COUNT) { + ScriptRunner.method3993(SELECTIVE_ROOF_GROUP_COUNT) + } + } + + fun setMapFlagRoof(sceneX: Int, sceneZ: Int, radius: Int) { + if (!isSmartRoofsActive()) { + clearMapFlagRoof() + return + } + if (!isInSceneBounds(sceneX, sceneZ)) { + clearMapFlagRoof() + return + } + mapFlagRoofX = sceneX + mapFlagRoofZ = sceneZ + mapFlagRoofRadius = radius.coerceIn(0, 8) + } + + fun clearMapFlagRoof() { + mapFlagRoofX = -1 + mapFlagRoofZ = -1 + mapFlagRoofRadius = 0 + } + + private fun hideMapFlagRoof() { + hideRoofFromSeedTile(mapFlagRoofX, mapFlagRoofZ, mapFlagRoofRadius, MAP_FLAG_ROOF_GROUP, Player.plane) + } + + private fun hideRoofFromSeedTile(sceneX: Int, sceneZ: Int, radius: Int, group: Int, plane: Int): Boolean { + if (sceneX == -1 || !isRoofHidePlane(plane) || ScriptRunner.anIntArray205.size <= group) { + return false + } + for (currentRadius in 0..radius) { + for (dx in -currentRadius..currentRadius) { + for (dz in -currentRadius..currentRadius) { + if (abs(dx) != currentRadius && abs(dz) != currentRadius) { + continue + } + if (tryHideRoofFromTile(sceneX + dx, sceneZ + dz, group, plane)) { + return true + } + } + } + } + return false + } + + private fun tryHideRoofFromTile(sceneX: Int, sceneZ: Int, group: Int, plane: Int): Boolean { + if (!isHideableRoofTile(sceneX, sceneZ, plane)) { + return false + } + val masks = ScriptRunner.aByteArrayArrayArray15 ?: return false + val currentRoofMask = (ScriptRunner.anInt3325 and 0xFF).toByte() + if (masks[plane][sceneX][sceneZ] == currentRoofMask) { + return true + } + return ScriptRunner.hideRoofAt(sceneX, sceneZ, group, plane) + } + + fun updateHoverPick(entries: Array?) { + if (!isSmartRoofsActive()) { + clearHoverRequest() + return + } + hoverPickActive = false + if (entries == null) { + clearExpiredHoverTargets(System.currentTimeMillis()) + return + } + for (entry in entries) { + if (entry.action == MiniMenuAction.WALK_HERE && entry.subjectIndex == 0L) { + hoverPickActive = true + hoverPickScreenX = entry.intArg1 + hoverPickScreenY = entry.intArg2 + return + } + } + clearExpiredHoverTargets(System.currentTimeMillis()) + } + + private fun isHoverPicking(): Boolean { + return isSmartRoofsActive() && hoverPickActive + } + + private fun getHoverPickScreenX(): Int = hoverPickScreenX + + private fun getHoverPickScreenY(): Int = hoverPickScreenY + + private fun setHoverTile(sceneX: Int, sceneZ: Int, plane: Int) { + if (!isSmartRoofsActive() || !hoverPickActive || !isInSceneBounds(sceneX, sceneZ)) { + return + } + val valid = updateHoverTarget(sceneX, sceneZ, Player.plane, plane) + rememberHoverTileHit(sceneX, sceneZ, plane, valid) + if (valid) { + markHoverHit() + return + } + clearExpiredHoverTargets(System.currentTimeMillis()) + } + + private fun isHoverLocPickable(key: Long): Boolean { + if (!isSmartRoofsActive() || !hoverPickActive || key == 0L) { + return false + } + val entityType = (key.toInt() shr 29) and 0x3 + val locShape = ((key shr 14) and 0x3F).toInt() + return entityType == 2 && isBuildingLocShape(locShape) + } + + private fun setHoverLoc(key: Long, plane: Int) { + if (!isHoverLocPickable(key)) { + return + } + val sceneX = (key and 0x7F).toInt() + val sceneZ = ((key shr 7) and 0x7F).toInt() + if (!isInSceneBounds(sceneX, sceneZ)) { + return + } + if (updateHoverTarget(sceneX, sceneZ, Player.plane, plane)) { + markHoverHit() + } + } + + private fun updateHoverTarget(sceneX: Int, sceneZ: Int, plane: Int, hitPlane: Int): Boolean { + var targetX = sceneX + var targetZ = sceneZ + val now = System.currentTimeMillis() + if (!isHideableRoofTile(targetX, targetZ, plane)) { + val packed = findNearbyHideableRoofTile(sceneX, sceneZ, plane, hitPlane) + if (packed == -1) { + return false + } + targetX = packed shr 7 + targetZ = packed and 0x7F + } + if (!isHideableRoofTile(targetX, targetZ, plane)) { + return false + } + + val duplicateSlot = findHoverTargetByBuildingFootprint(targetX, targetZ, plane) + if (duplicateSlot != -1) { + updateHoverPrimaryHit(duplicateSlot) + extendHoverTarget(duplicateSlot, now) + return true + } + + var oldestSlot = 0 + var oldestUntil = Long.MAX_VALUE + for (i in 0 until HOVER_TARGET_LIMIT) { + if (hoverTargetX[i] == targetX && hoverTargetZ[i] == targetZ && hoverTargetPlane[i] == plane) { + updateHoverPrimaryHit(i) + extendHoverTarget(i, now) + return true + } + if (hoverTargetX[i] == -1 || now > hoverTargetUntil[i]) { + hoverTargetX[i] = targetX + hoverTargetZ[i] = targetZ + hoverTargetPlane[i] = plane + clearBuildingFootprint(i) + updateHoverPrimaryHit(i) + extendHoverTarget(i, now) + return true + } + if (hoverTargetUntil[i] < oldestUntil) { + oldestUntil = hoverTargetUntil[i] + oldestSlot = i + } + } + hoverTargetX[oldestSlot] = targetX + hoverTargetZ[oldestSlot] = targetZ + hoverTargetPlane[oldestSlot] = plane + clearBuildingFootprint(oldestSlot) + updateHoverPrimaryHit(oldestSlot) + extendHoverTarget(oldestSlot, now) + return true + } + + private fun findNearbyHideableRoofTile(sceneX: Int, sceneZ: Int, plane: Int, hitPlane: Int): Int { + if (hitPlane <= plane) { + return -1 + } + for (radius in 1..HOVER_UPPER_PLANE_TILE_SEARCH_RADIUS) { + for (dx in -radius..radius) { + for (dz in -radius..radius) { + if (abs(dx) != radius && abs(dz) != radius) { + continue + } + val targetX = sceneX + dx + val targetZ = sceneZ + dz + if (isHideableRoofTile(targetX, targetZ, plane)) { + return (targetX shl 7) or targetZ + } + } + } + } + return -1 + } + + private fun rememberHoverTileHit(sceneX: Int, sceneZ: Int, plane: Int, valid: Boolean) { + lastHoverTileX = sceneX + lastHoverTileZ = sceneZ + lastHoverTilePlane = plane + lastHoverTileValid = valid + lastHoverTileAt = System.currentTimeMillis() + } + + private fun markHoverHit() { + lastHoverHitAt = System.currentTimeMillis() + } + + private fun clearHoverRequest() { + hoverPickActive = false + clearHoverTargets() + } + + private fun findHoverTargetByBuildingFootprint(sceneX: Int, sceneZ: Int, plane: Int): Int { + for (i in 0 until HOVER_TARGET_LIMIT) { + if (hoverTargetX[i] == -1 || hoverTargetPlane[i] != plane) { + continue + } + if (isBuildingFootprintTile(i, sceneX, sceneZ)) { + return i + } + } + return -1 + } + + private fun extendHoverTarget(index: Int, now: Long) { + hoverTargetUntil[index] = now + HOVER_HOLD_MS + } + + private fun getHoverGroup(index: Int): Int = HOVER_GROUP_START + index + + private fun getHoverIndex(group: Int): Int { + val index = group - HOVER_GROUP_START + return if (index >= 0 && index < HOVER_TARGET_LIMIT) index else -1 + } + + private fun clearExpiredHoverTargets(now: Long) { + for (i in 0 until HOVER_TARGET_LIMIT) { + if (hoverTargetX[i] != -1 && now > hoverTargetUntil[i]) { + if (isPickInsideBuildingFootprint(i)) { + extendHoverTarget(i, now) + continue + } + clearHoverTarget(i) + } + } + } + + private fun clearHoverTargets() { + for (i in 0 until HOVER_TARGET_LIMIT) { + clearHoverTarget(i) + } + lastHoverTileX = -1 + lastHoverTileZ = -1 + lastHoverTilePlane = -1 + lastHoverTileValid = false + lastHoverTileAt = 0L + lastHoverHitAt = 0L + } + + private fun clearHoverTarget(index: Int) { + hoverTargetX[index] = -1 + hoverTargetZ[index] = -1 + hoverTargetPlane[index] = -1 + hoverPrimaryHitX[index] = Int.MIN_VALUE + hoverPrimaryHitY[index] = Int.MIN_VALUE + hoverTargetUntil[index] = 0L + clearBuildingFootprint(index) + } + + private fun hideHoverTargets() { + val now = System.currentTimeMillis() + val keepAlive = Cs1ScriptRunner.aBoolean108 + for (i in 0 until HOVER_TARGET_LIMIT) { + if (hoverTargetX[i] == -1) { + continue + } + val hasBuildingArea = hasBuildingFootprint(i) + val buildingFootprintKeepAlive = isPickInsideBuildingFootprint(i) + val primaryHitKeepAlive = isPickNearPrimaryHit(i, if (hasBuildingArea) HOVER_BUILDING_FOOTPRINT_PRIMARY_HIT_DISTANCE else HOVER_PRIMARY_HIT_DISTANCE) + if (hasRecentOutsideGroundHit(i, now, buildingFootprintKeepAlive || hasBuildingArea && primaryHitKeepAlive)) { + clearHoverTarget(i) + continue + } + if (keepAlive || primaryHitKeepAlive || buildingFootprintKeepAlive) { + extendHoverTarget(i, now) + } else if (now > hoverTargetUntil[i]) { + clearHoverTarget(i) + continue + } + val group = getHoverGroup(i) + hideRoofFromSeedTile(hoverTargetX[i], hoverTargetZ[i], 0, group, hoverTargetPlane[i]) + } + } + + private fun updateHoverPrimaryHit(index: Int) { + hoverPrimaryHitX[index] = hoverPickScreenX + hoverPrimaryHitY[index] = hoverPickScreenY + } + + private fun isPickNearPrimaryHit(index: Int, distance: Int): Boolean { + if (!hoverPickActive || hoverPrimaryHitX[index] == Int.MIN_VALUE) { + return false + } + val dx = hoverPickScreenX - hoverPrimaryHitX[index] + val dy = hoverPickScreenY - hoverPrimaryHitY[index] + return dx * dx + dy * dy <= distance * distance + } + + private fun isPickInsideBuildingFootprint(index: Int): Boolean { + if (!hoverPickActive || !hasBuildingFootprint(index)) { + return false + } + for (x in 0 until 104) { + for (z in 0 until 104) { + if (buildingFootprintTiles[index][x][z] && isPickInsideBuildingFootprintTile(index, x, z)) { + return true + } + } + } + return false + } + + private fun hasBuildingFootprint(index: Int): Boolean { + return buildingFootprintMinX[index] != Int.MAX_VALUE + } + + private fun clearBuildingFootprint(index: Int) { + buildingFootprintMinX[index] = Int.MAX_VALUE + buildingFootprintMaxX[index] = Int.MIN_VALUE + buildingFootprintMinY[index] = Int.MAX_VALUE + buildingFootprintMaxY[index] = Int.MIN_VALUE + for (x in 0 until 104) { + buildingFootprintTiles[index][x].fill(false) + } + } + + private fun beginBuildingFootprint(group: Int) { + val index = getHoverIndex(group) + if (index != -1) { + clearBuildingFootprint(index) + } + } + + private fun addBuildingFootprintTile(group: Int, plane: Int, sceneX: Int, sceneZ: Int) { + val index = getHoverIndex(group) + if (index == -1 || plane + 1 >= SceneGraph.tileHeights.size) { + return + } + buildingFootprintTiles[index][sceneX][sceneZ] = true + addBuildingFootprintTileBounds(index, plane, sceneX, sceneZ) + } + + private fun addBuildingFootprintTileBounds(index: Int, plane: Int, sceneX: Int, sceneZ: Int) { + addBuildingFootprintBoundPoint(index, projectBuildingFootprintPoint(sceneX shl 7, sceneZ shl 7, SceneGraph.tileHeights[plane + 1][sceneX][sceneZ])) + addBuildingFootprintBoundPoint(index, projectBuildingFootprintPoint((sceneX + 1) shl 7, sceneZ shl 7, SceneGraph.tileHeights[plane + 1][sceneX + 1][sceneZ])) + addBuildingFootprintBoundPoint(index, projectBuildingFootprintPoint((sceneX + 1) shl 7, (sceneZ + 1) shl 7, SceneGraph.tileHeights[plane + 1][sceneX + 1][sceneZ + 1])) + addBuildingFootprintBoundPoint(index, projectBuildingFootprintPoint(sceneX shl 7, (sceneZ + 1) shl 7, SceneGraph.tileHeights[plane + 1][sceneX][sceneZ + 1])) + } + + private fun isPickInsideBuildingFootprintTile(index: Int, sceneX: Int, sceneZ: Int): Boolean { + val plane = hoverTargetPlane[index] + if (!isRoofHidePlane(plane) || plane + 1 >= SceneGraph.tileHeights.size) { + return false + } + val point0 = projectBuildingFootprintPoint(sceneX shl 7, sceneZ shl 7, SceneGraph.tileHeights[plane + 1][sceneX][sceneZ]) + val point1 = projectBuildingFootprintPoint((sceneX + 1) shl 7, sceneZ shl 7, SceneGraph.tileHeights[plane + 1][sceneX + 1][sceneZ]) + val point2 = projectBuildingFootprintPoint((sceneX + 1) shl 7, (sceneZ + 1) shl 7, SceneGraph.tileHeights[plane + 1][sceneX + 1][sceneZ + 1]) + val point3 = projectBuildingFootprintPoint(sceneX shl 7, (sceneZ + 1) shl 7, SceneGraph.tileHeights[plane + 1][sceneX][sceneZ + 1]) + if (point0 == Long.MIN_VALUE || point1 == Long.MIN_VALUE || point2 == Long.MIN_VALUE || point3 == Long.MIN_VALUE) { + return false + } + val minX = min(min((point0 shr 32).toInt(), (point1 shr 32).toInt()), min((point2 shr 32).toInt(), (point3 shr 32).toInt())) + val maxX = max(max((point0 shr 32).toInt(), (point1 shr 32).toInt()), max((point2 shr 32).toInt(), (point3 shr 32).toInt())) + val minY = min(min(point0.toInt(), point1.toInt()), min(point2.toInt(), point3.toInt())) + val maxY = max(max(point0.toInt(), point1.toInt()), max(point2.toInt(), point3.toInt())) + return hoverPickScreenX >= minX - HOVER_BUILDING_FOOTPRINT_X_PADDING && + hoverPickScreenX <= maxX + HOVER_BUILDING_FOOTPRINT_X_PADDING && + hoverPickScreenY >= minY - HOVER_BUILDING_FOOTPRINT_TOP_PADDING && + hoverPickScreenY <= maxY + HOVER_BUILDING_FOOTPRINT_BOTTOM_PADDING + } + + private fun hasRecentOutsideGroundHit(index: Int, now: Long, pickInsideBuildingFootprint: Boolean): Boolean { + if (pickInsideBuildingFootprint || lastHoverTileAt == 0L || now - lastHoverTileAt > HOVER_OUTSIDE_GROUND_HIT_MS) { + return false + } + if (lastHoverTileValid || lastHoverTileAt <= lastHoverHitAt || now - lastHoverHitAt <= HOVER_OUTSIDE_GROUND_HIT_MS) { + return false + } + if (lastHoverTilePlane != hoverTargetPlane[index]) { + return false + } + return !isBuildingFootprintTile(index, lastHoverTileX, lastHoverTileZ) + } + + private fun isBuildingFootprintTile(index: Int, sceneX: Int, sceneZ: Int): Boolean { + return isInSceneBounds(sceneX, sceneZ) && buildingFootprintTiles[index][sceneX][sceneZ] + } + + private fun addBuildingFootprintBoundPoint(index: Int, packedPoint: Long) { + if (packedPoint == Long.MIN_VALUE) { + return + } + val screenX = (packedPoint shr 32).toInt() + val screenY = packedPoint.toInt() + if (screenX < buildingFootprintMinX[index]) { + buildingFootprintMinX[index] = screenX + } + if (screenX > buildingFootprintMaxX[index]) { + buildingFootprintMaxX[index] = screenX + } + if (screenY < buildingFootprintMinY[index]) { + buildingFootprintMinY[index] = screenY + } + if (screenY > buildingFootprintMaxY[index]) { + buildingFootprintMaxY[index] = screenY + } + } + + private fun projectBuildingFootprintPoint(xFine: Int, zFine: Int, yFine: Int): Long { + val localX = xFine - Camera.renderX + val localY = yFine - Camera.anInt40 + val localZ = zFine - Camera.renderZ + val sinPitch = MathUtils.sin[Camera.cameraPitch] + val cosPitch = MathUtils.cos[Camera.cameraPitch] + val sinYaw = MathUtils.sin[Camera.cameraYaw] + val cosYaw = MathUtils.cos[Camera.cameraYaw] + val rotatedX = localX * cosYaw + localZ * sinYaw shr 16 + val rotatedZ = localZ * cosYaw - localX * sinYaw shr 16 + val screenYDepth = rotatedZ * cosPitch + localY * sinPitch shr 16 + if (screenYDepth < 50) { + return Long.MIN_VALUE + } + val rotatedY = localY * cosPitch - rotatedZ * sinPitch shr 16 + val screenX = (rotatedX shl 9) / screenYDepth + val screenY = (rotatedY shl 9) / screenYDepth + return (screenX.toLong() shl 32) or (screenY.toLong() and 0xFFFFFFFFL) + } + + fun setDestinationTarget(sceneX: Int, sceneZ: Int) { + if (!isSmartRoofsActive()) { + clearDestinationTarget() + return + } + if (!isInSceneBounds(sceneX, sceneZ)) { + clearDestinationTarget() + return + } + destinationTargetX = sceneX + destinationTargetZ = sceneZ + destinationTargetUntil = System.currentTimeMillis() + DESTINATION_HOLD_MS + } + + fun clearDestinationTarget() { + destinationTargetX = -1 + destinationTargetZ = -1 + destinationTargetUntil = 0L + } + + private fun hasDestinationTarget(): Boolean { + if (destinationTargetX == -1) { + return false + } + if (System.currentTimeMillis() > destinationTargetUntil) { + clearDestinationTarget() + return false + } + val self = PlayerList.self + if (self != null && + abs(self.movementQueueX[0] - destinationTargetX) <= DESTINATION_REACHED_DISTANCE && + abs(self.movementQueueZ[0] - destinationTargetZ) <= DESTINATION_REACHED_DISTANCE + ) { + clearDestinationTarget() + return false + } + return true + } + + private fun hideDestinationTarget() { + if (hasDestinationTarget()) { + hideRoofFromSeedTile(destinationTargetX, destinationTargetZ, 0, DESTINATION_TARGET_GROUP, Player.plane) + } + } + + private fun isInSceneBounds(sceneX: Int, sceneZ: Int): Boolean { + return sceneX in 0 until 104 && sceneZ in 0 until 104 + } + + private fun isHideableRoofTile(sceneX: Int, sceneZ: Int, plane: Int): Boolean { + if (ScriptRunner.aByteArrayArrayArray15 == null) { + return false + } + return isRoofHidePlane(plane) && + isInSceneBounds(sceneX, sceneZ) && + (SceneGraph.renderFlags[plane][sceneX][sceneZ].toInt() and TILE_FLAG_UNDER_ROOF) != 0 + } + + private fun isRoofHidePlane(plane: Int): Boolean { + return plane in 0 until 3 + } + + private fun isBuildingLocShape(shape: Int): Boolean { + return shape == LocType.WALL_STRAIGHT || + shape == LocType.WALL_DIAGONALCORNER || + shape == LocType.WALL_L || + shape == LocType.WALL_SQUARECORNER || + shape == LocType.WALL_DIAGONAL || + shape == LocType.WALLDECOR_STRAIGHT_XOFFSET || + shape == LocType.WALLDECOR_STRAIGHT_ZOFFSET || + shape == LocType.WALLDECOR_DIAGONAL_XOFFSET || + shape == LocType.WALLDECOR_DIAGONAL_ZOFFSET || + shape == LocType.WALLDECOR_DIAGONAL_BOTH + } + +} diff --git a/plugin-playground/src/main/kotlin/SmartRoofs/plugin.kt b/plugin-playground/src/main/kotlin/SmartRoofs/plugin.kt new file mode 100644 index 0000000..0fe8e30 --- /dev/null +++ b/plugin-playground/src/main/kotlin/SmartRoofs/plugin.kt @@ -0,0 +1,51 @@ +package SmartRoofs + +import plugin.Plugin +import plugin.api.API +import plugin.api.MiniMenuEntry + +class plugin : Plugin() { + override fun Init() { + API.SetRoofVisibilityHandler(RoofHider.HANDLER) + } + + override fun Draw(timeDelta: Long) { + if (!API.IsLoggedIn()) { + clearTargets() + return + } + API.SetRoofVisibilityHandler(RoofHider.HANDLER) + RoofHider.setSmartRoofsEnabled(true) + RoofHider.updateSmartRoofs() + + val mapFlag = API.GetMapFlagTile() + if (mapFlag == null) { + RoofHider.clearMapFlagRoof() + } else { + RoofHider.setMapFlagRoof(mapFlag[0], mapFlag[1], MAP_FLAG_RADIUS) + } + } + + override fun OnMiniMenuCreate(currentEntries: Array?) { + RoofHider.updateHoverPick(currentEntries) + } + + override fun OnLogout() { + clearTargets() + } + + override fun OnPluginsReloaded(): Boolean { + clearTargets() + API.ClearRoofVisibilityHandler(RoofHider.HANDLER) + return false + } + + private fun clearTargets() { + RoofHider.clearMapFlagRoof() + RoofHider.setSmartRoofsEnabled(false) + } + + companion object { + private const val MAP_FLAG_RADIUS = 0 + } +} diff --git a/plugin-playground/src/main/kotlin/SmartRoofs/plugin.properties b/plugin-playground/src/main/kotlin/SmartRoofs/plugin.properties new file mode 100644 index 0000000..2846981 --- /dev/null +++ b/plugin-playground/src/main/kotlin/SmartRoofs/plugin.properties @@ -0,0 +1,3 @@ +AUTHOR='Edith' +DESCRIPTION='Hides roofs on cursor hover and destination travel.' +VERSION=1.0