From c66b38a4dcba3ad54a25e8cdb77e6870798254c2 Mon Sep 17 00:00:00 2001 From: Joshua Filby Date: Sat, 6 Jun 2026 22:47:59 -0500 Subject: [PATCH 1/7] cache: 238 world map support --- .../net/runelite/cache/WorldMapManager.java | 1 + .../loaders/WorldMapCompositeLoader.java | 10 ++++++- .../loaders/WorldMapDataLoader.java | 26 ++++++++++++++++--- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/cache/src/main/java/net/runelite/cache/WorldMapManager.java b/cache/src/main/java/net/runelite/cache/WorldMapManager.java index 75f328079e3..59eafbd0ecf 100644 --- a/cache/src/main/java/net/runelite/cache/WorldMapManager.java +++ b/cache/src/main/java/net/runelite/cache/WorldMapManager.java @@ -62,6 +62,7 @@ public void load() throws IOException Index index = store.getIndex(IndexType.WORLDMAP); Archive compositeMapArchive = index.findArchiveByName("compositemap"); WorldMapCompositeLoader worldMapCompositeLoader = new WorldMapCompositeLoader(); + worldMapCompositeLoader.configureForRevision(index.getRevision()); ArchiveFiles compositeMapFiles = compositeMapArchive.getFiles(storage.loadArchive(compositeMapArchive)); for (FSFile compositeFile : compositeMapFiles.getFiles()) diff --git a/cache/src/main/java/net/runelite/cache/definitions/loaders/WorldMapCompositeLoader.java b/cache/src/main/java/net/runelite/cache/definitions/loaders/WorldMapCompositeLoader.java index eb503322ebb..1cae3ca068a 100644 --- a/cache/src/main/java/net/runelite/cache/definitions/loaders/WorldMapCompositeLoader.java +++ b/cache/src/main/java/net/runelite/cache/definitions/loaders/WorldMapCompositeLoader.java @@ -31,9 +31,17 @@ public class WorldMapCompositeLoader { + private boolean rev238 = true; + + public WorldMapCompositeLoader configureForRevision(int rev) + { + rev238 = rev > 425; + return this; + } + public WorldMapCompositeDefinition load(byte[] buffer) { - WorldMapDataLoader worldMapDataLoader = new WorldMapDataLoader(); + WorldMapDataLoader worldMapDataLoader = new WorldMapDataLoader(rev238); WorldMapElementLoader worldMapElementLoader = new WorldMapElementLoader(); WorldMapCompositeDefinition worldMapCompositeDefinition = new WorldMapCompositeDefinition(); diff --git a/cache/src/main/java/net/runelite/cache/definitions/loaders/WorldMapDataLoader.java b/cache/src/main/java/net/runelite/cache/definitions/loaders/WorldMapDataLoader.java index 9277fe4ec9a..10571006c82 100644 --- a/cache/src/main/java/net/runelite/cache/definitions/loaders/WorldMapDataLoader.java +++ b/cache/src/main/java/net/runelite/cache/definitions/loaders/WorldMapDataLoader.java @@ -30,6 +30,18 @@ public class WorldMapDataLoader { + private final boolean rev238; + + public WorldMapDataLoader() + { + this(true); + } + + public WorldMapDataLoader(boolean rev238) + { + this.rev238 = rev238; + } + public MapSquareDefinition loadMapSquare(InputStream in) { int worldMapDataType = in.readUnsignedByte(); @@ -46,8 +58,11 @@ public MapSquareDefinition loadMapSquare(InputStream in) mapSquareDefinition.sourceSquareZ = in.readUnsignedShort(); mapSquareDefinition.displaySquareX = in.readUnsignedShort(); mapSquareDefinition.displaySquareZ = in.readUnsignedShort(); - mapSquareDefinition.groupId = in.readBigSmart2(); - mapSquareDefinition.fileId = in.readBigSmart2(); + if (!rev238) + { + mapSquareDefinition.groupId = in.readBigSmart2(); + mapSquareDefinition.fileId = in.readBigSmart2(); + } return mapSquareDefinition; } @@ -72,8 +87,11 @@ public ZoneDefinition loadZone(InputStream in) zoneDefinition.displaySquareZ = in.readUnsignedShort(); zoneDefinition.displayZoneX = in.readUnsignedByte(); zoneDefinition.displayZoneZ = in.readUnsignedByte(); - zoneDefinition.groupId = in.readBigSmart2(); - zoneDefinition.fileId = in.readBigSmart2(); + if (!rev238) + { + zoneDefinition.groupId = in.readBigSmart2(); + zoneDefinition.fileId = in.readBigSmart2(); + } return zoneDefinition; } From 7b0f1415f06e9cbdb9ae05c4f957ad1b7e288400 Mon Sep 17 00:00:00 2001 From: Levente Kurusa <849140+levex@users.noreply.github.com> Date: Wed, 10 Jun 2026 14:34:22 +0200 Subject: [PATCH 2/7] entity hider: add Leagues 6 thrall overrides (#20197) Signed-off-by: Levente Kurusa --- .../client/plugins/entityhider/EntityHiderPlugin.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderPlugin.java index 39b8cd1def1..22409efd790 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderPlugin.java @@ -60,7 +60,10 @@ public class EntityHiderPlugin extends Plugin private static final Set THRALL_IDS = ImmutableSet.of( NpcID.ARCEUUS_THRALL_GHOST_LESSER, NpcID.ARCEUUS_THRALL_SKELETON_LESSER, NpcID.ARCEUUS_THRALL_ZOMBIE_LESSER, // Lesser Thrall (ghost, skeleton, zombie) NpcID.ARCEUUS_THRALL_GHOST_SUPERIOR, NpcID.ARCEUUS_THRALL_SKELETON_SUPERIOR, NpcID.ARCEUUS_THRALL_ZOMBIE_SUPERIOR, // Superior Thrall (ghost, skeleton, zombie) - NpcID.ARCEUUS_THRALL_GHOST_GREATER, NpcID.ARCEUUS_THRALL_SKELETON_GREATER, NpcID.ARCEUUS_THRALL_ZOMBIE_GREATER // Greater Thrall (ghost, skeleton, zombie) + NpcID.ARCEUUS_THRALL_GHOST_GREATER, NpcID.ARCEUUS_THRALL_SKELETON_GREATER, NpcID.ARCEUUS_THRALL_ZOMBIE_GREATER, // Greater Thrall (ghost, skeleton, zombie) + NpcID.THRALL_IMP_MAGIC_LESSER, NpcID.THRALL_IMP_RANGED_LESSER, NpcID.THRALL_IMP_MELEE_LESSER, // Leagues 6 Cosmetic Override for lesser thralls + NpcID.THRALL_IMP_MAGIC_SUPERIOR, NpcID.THRALL_IMP_RANGED_SUPERIOR, NpcID.THRALL_IMP_MELEE_SUPERIOR, // Leagues 6 Cosmetic Override for superior thralls + NpcID.THRALL_IMP_MAGIC_GREATER, NpcID.THRALL_IMP_RANGED_GREATER, NpcID.THRALL_IMP_MELEE_GREATER // Leagues 6 Cosmetic Override for greater thralls ); private static final Set RANDOM_EVENT_NPC_IDS = ImmutableSet.of( NpcID.MACRO_BEEKEEPER_INVITATION, From f7b8283be1409de3249d7069655873a84cbf52c2 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 13 Jun 2026 19:02:54 -0400 Subject: [PATCH 3/7] discord: add sailing event --- .../runelite/client/plugins/discord/DiscordGameEventType.java | 1 + 1 file changed, 1 insertion(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordGameEventType.java b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordGameEventType.java index a84969b5e6e..c9838865461 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordGameEventType.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordGameEventType.java @@ -623,6 +623,7 @@ public static DiscordGameEventType fromSkill(final Skill skill) case RUNECRAFT: return TRAINING_RUNECRAFT; case HUNTER: return TRAINING_HUNTER; case CONSTRUCTION: return TRAINING_CONSTRUCTION; + case SAILING: return TRAINING_SAILING; default: return null; } } From 58177df57e645a2650b6e64ab2baedf5896193f2 Mon Sep 17 00:00:00 2001 From: Trentin Thomas Date: Wed, 10 Jun 2026 10:56:24 -0500 Subject: [PATCH 4/7] poh: add new portal nexus icons --- .../net/runelite/client/plugins/poh/PohIcons.java | 10 ++++++++++ .../runelite/client/plugins/poh/PohOverlay.java | 3 ++- .../net/runelite/client/plugins/poh/barbarian.png | Bin 0 -> 593 bytes .../net/runelite/client/plugins/poh/boat.png | Bin 0 -> 231 bytes .../net/runelite/client/plugins/poh/dareeyak.png | Bin 0 -> 169 bytes .../runelite/client/plugins/poh/iceplateau.png | Bin 0 -> 602 bytes .../net/runelite/client/plugins/poh/khazard.png | Bin 0 -> 592 bytes .../net/runelite/client/plugins/poh/lassar.png | Bin 0 -> 173 bytes .../net/runelite/client/plugins/poh/ourania.png | Bin 0 -> 672 bytes .../net/runelite/client/plugins/poh/paddewwa.png | Bin 0 -> 180 bytes .../net/runelite/client/plugins/poh/respawn.png | Bin 0 -> 180 bytes .../net/runelite/client/plugins/poh/trollheim.png | Bin 0 -> 178 bytes 12 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/poh/barbarian.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/poh/boat.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/poh/dareeyak.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/poh/iceplateau.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/poh/khazard.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/poh/lassar.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/poh/ourania.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/poh/paddewwa.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/poh/respawn.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/poh/trollheim.png diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/poh/PohIcons.java b/runelite-client/src/main/java/net/runelite/client/plugins/poh/PohIcons.java index 619b7140e0f..e79bb007173 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/poh/PohIcons.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/poh/PohIcons.java @@ -67,6 +67,16 @@ public enum PohIcons CIVITASILLAFORTIS("civitasillafortis", ObjectID.POH_PORTAL_TEAK_FORTIS, ObjectID.POH_PORTAL_MAG_FORTIS, ObjectID.POH_PORTAL_MARBLE_FORTIS, ObjectID.POH_PORTAL_LEAGUE_5_FORTIS ), + TROLLHEIM("trollheim", ObjectID.POH_PORTAL_TEAK_TROLLHEIM, ObjectID.POH_PORTAL_MAG_TROLLHEIM, ObjectID.POH_PORTAL_MARBLE_TROLLHEIM, ObjectID.POH_PORTAL_LEAGUE_5_TROLLHEIM), + PADDEWWA("paddewwa", ObjectID.POH_PORTAL_TEAK_PADDEWWA, ObjectID.POH_PORTAL_MAG_PADDEWWA, ObjectID.POH_PORTAL_MARBLE_PADDEWWA, ObjectID.POH_PORTAL_LEAGUE_5_PADDEWWA), + LASSAR("lassar", ObjectID.POH_PORTAL_TEAK_LASSAR, ObjectID.POH_PORTAL_MAG_LASSAR, ObjectID.POH_PORTAL_MARBLE_LASSAR, ObjectID.POH_PORTAL_LEAGUE_5_LASSAR), + DAREEYAK("dareeyak", ObjectID.POH_PORTAL_TEAK_DAREEYAK, ObjectID.POH_PORTAL_MAG_DAREEYAK, ObjectID.POH_PORTAL_MARBLE_DAREEYAK, ObjectID.POH_PORTAL_LEAGUE_5_DAREEYAK), + OURANIA("ourania", ObjectID.POH_PORTAL_TEAK_OURANIA, ObjectID.POH_PORTAL_MAG_OURANIA, ObjectID.POH_PORTAL_MARBLE_OURANIA, ObjectID.POH_PORTAL_LEAGUE_5_OURANIA), + BARBARIAN("barbarian", ObjectID.POH_PORTAL_TEAK_BARBARIAN, ObjectID.POH_PORTAL_MAG_BARBARIAN, ObjectID.POH_PORTAL_MARBLE_BARBARIAN, ObjectID.POH_PORTAL_LEAGUE_5_BARBARIAN), + KHAZARD("khazard", ObjectID.POH_PORTAL_TEAK_KHAZARD, ObjectID.POH_PORTAL_MAG_KHAZARD, ObjectID.POH_PORTAL_MARBLE_KHAZARD, ObjectID.POH_PORTAL_LEAGUE_5_KHAZARD), + ICEPLATEAU("iceplateau", ObjectID.POH_PORTAL_TEAK_ICEPLATEAU, ObjectID.POH_PORTAL_MAG_ICEPLATEAU, ObjectID.POH_PORTAL_MARBLE_ICEPLATEAU, ObjectID.POH_PORTAL_LEAGUE_5_ICEPLATEAU), + RESPAWN("respawn", ObjectID.POH_PORTAL_TEAK_RESPAWN, ObjectID.POH_PORTAL_MAG_RESPAWN, ObjectID.POH_PORTAL_MARBLE_RESPAWN, ObjectID.POH_PORTAL_LEAGUE_5_RESPAWN), + BOAT("boat", ObjectID.POH_PORTAL_TEAK_BOAT, ObjectID.POH_PORTAL_MAG_BOAT, ObjectID.POH_PORTAL_MARBLE_BOAT, ObjectID.POH_PORTAL_LEAGUE_5_BOAT), ALTAR("altar", ObjectID.POH_ALTAR_SARADOMIN_1, ObjectID.POH_ALTAR_ZAMORAK_1, ObjectID.POH_ALTAR_GUTHIX_1, ObjectID.POH_ALTAR_SARADOMIN_2, ObjectID.POH_ALTAR_ZAMORAK_2, ObjectID.POH_ALTAR_GUTHIX_2, ObjectID.POH_ALTAR_SARADOMIN_3, ObjectID.POH_ALTAR_ZAMORAK_3, ObjectID.POH_ALTAR_GUTHIX_3, ObjectID.POH_ALTAR_SARADOMIN_4, ObjectID.POH_ALTAR_ZAMORAK_4, ObjectID.POH_ALTAR_GUTHIX_4, ObjectID.POH_ALTAR_SARADOMIN_5, ObjectID.POH_ALTAR_ZAMORAK_5, ObjectID.POH_ALTAR_GUTHIX_5, ObjectID.POH_ALTAR_SARADOMIN_6, diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/poh/PohOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/poh/PohOverlay.java index 4b7bfafefb6..02809a260eb 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/poh/PohOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/poh/PohOverlay.java @@ -47,7 +47,8 @@ public class PohOverlay extends Overlay PohIcons.TROLLSTRONGHOLD, PohIcons.CARRALLANGER, PohIcons.CATHERBY, PohIcons.WEISS, PohIcons.GHORROCK, PohIcons.APEATOLLDUNGEON, PohIcons.BARROWS, PohIcons.BATTLEFRONT, PohIcons.CEMETERY, PohIcons.DRAYNORMANOR, PohIcons.FENKENSTRAINSCASTLE, PohIcons.HARMONYISLAND, PohIcons.ARCEUUSLIBRARY, PohIcons.MINDALTAR, PohIcons.SALVEGRAVEYARD, - PohIcons.WESTARDOUGNE, PohIcons.CIVITASILLAFORTIS, + PohIcons.WESTARDOUGNE, PohIcons.CIVITASILLAFORTIS, PohIcons.TROLLHEIM, PohIcons.PADDEWWA, PohIcons.LASSAR, PohIcons.DAREEYAK, + PohIcons.OURANIA, PohIcons.BARBARIAN, PohIcons.KHAZARD, PohIcons.ICEPLATEAU, PohIcons.RESPAWN, PohIcons.BOAT }; private static final int MAX_DISTANCE = 2350; diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/poh/barbarian.png b/runelite-client/src/main/resources/net/runelite/client/plugins/poh/barbarian.png new file mode 100644 index 0000000000000000000000000000000000000000..c49b38866ddf80b8552d947d0c7688f192412851 GIT binary patch literal 593 zcmeAS@N?(olHy`uVBq!ia0vp^{2lr+zFiG07O4zW5tzn4U%@DbX!L6UcwTr>6htX>)L-baL&@~L= z7A&zl868@fqO!xA1D zo)8~z=j0R;6l`U0&cwnb=gMMIz#O}aLEe^;je|8jBHY{Co0FSIUrWt3%$SvjQCyS5 zx`u^`m621N$;-n-L0&=AR8}K^fsL1uOPDEQBcqBp69*3~3n!D1GNWN4gJv)z3nwGL z44c;sX6q^zy;uhQ1O}4~2Af)DF&##3S*D;R3_i0Mtty!LHCVVLn2fSmBu&_uSeb6j zKQ|E+iX}mQ!3+-1ZlnP@yFFbTLpZJ{ConL&2_!Tyy1A(Vy(Tt;Ls?tenw_0pUEDmP zVPgCA{&DYpe4=G1k)1(pX<^sQww0Z6*tfTN@#M{o-K%HsZddo1=Xvq;?c>+`FWM^@ zcu1!xL>G8yBu7-Zq=cBdQ+`bC- auroNHkhokW;N}DL27{-opUXO@geCwPbC_uW literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/poh/boat.png b/runelite-client/src/main/resources/net/runelite/client/plugins/poh/boat.png new file mode 100644 index 0000000000000000000000000000000000000000..e1d905be62a45ee0b402297f9f610a8e7791df36 GIT binary patch literal 231 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh3?wzC-F*zCj01c^T!FNJfPQ_&|GLV5brt_= zDu0`r6{m4B+MepOZdtf~A{TltrPfw8Xg@Avx?s)5=VOM?7@862M7 zNCR?`JY5_^IIidRJMtY+;9zN0+7hKFl_l-x4yN4UZJffun(25X!TQOAA2VjA zZ2hb?adG8p# z42*^cF8u|HGL{7S1v5B2yO9RuxO%!chHzX@PDl_C5lLZSa#K`wWmM2z0t6h7w4|jO zSy`s~#57H1k!+db>=fH8BH7~8SYjZ=E84!Y`DOD=W^?f*eun+Oc;*(jT$KSD$l&Sf K=d#Wzp$PzAlP(Sb literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/poh/iceplateau.png b/runelite-client/src/main/resources/net/runelite/client/plugins/poh/iceplateau.png new file mode 100644 index 0000000000000000000000000000000000000000..528fab58fcaf44cc47ece954d6e82663534d657d GIT binary patch literal 602 zcmV-g0;T)vL~GBDx&l(oaiHXum&SMCxpf^srxOT>`0&0NSn_|ozX|8 z-!iH9D22ubBRdp%voEOlHIc*@K3D<@5(NbX5GXM&bDBY{@gsMy13FhMnc@*FHURUExz3TG zsT33yQCehzv&Ih>8xbckEQ+@U1_&oMKajG)S6EpB0s~run|Q_F4ILu}5g0F_?j=}s z2@w+u3k)nmN`1)qMun~e3J?exCI%!tKdSIcq18&FyIQ8zbieFYvgaK?O9K@pP_^+* zs^kkRMgai=2nh%pT73{LKLP~@Dq!B<00001bW%=J06^y0W&i*HxJg7oR0!8&U;qI| z5MY1-Mn)!PW@Z+c2qP;SI|l~`Cl@y(gv-Or$1fl#BrGBd5?~Y)myncVl$Mc|lLzt{ z71$M(lvPyK)HO6YwHO%~wK;Tj_4Ex4jf_o9&47G!4hu^wYa3fTdk04+u)MR2tD&2_ zho_e}NM4K6$Jft4ATTI6BvcDzAXiv;1Y=}WbWCg<*uHq4gv6xel+-knbcll(xi~X2 o1+%hqU=C(v$<510^%0B*0H(wm7z&Y?bN~PV07*qoM6N<$g2#u_(*OVf literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/poh/khazard.png b/runelite-client/src/main/resources/net/runelite/client/plugins/poh/khazard.png new file mode 100644 index 0000000000000000000000000000000000000000..e482b355ebedf1a424eccab24056e37105e1e17f GIT binary patch literal 592 zcmeAS@N?(olHy`uVBq!ia0vp^{2ty%%k^U8K}gq3`mLr!SA9#j(0VPj-s6f$QO z%wQ9;VdIiuV&-C!$>dNEXVs||Hkc^R$jm4l$09I+OR$Smpox=1mYIQ((RenWZmqDe z3k$m-lR!PEL@bN2JF{>ur(gm*GY6B57nevNvxqx03l}4w8H-3IJBt9Ls4lNY1dG9B zpi>z6HCXtHIrt6On3$MFL)b<7xtPV7xFs1K=L>n9RF;Y0<`8FM;bh|0V^UkkpV90&a*+%pBa>FCkeD?G z6DyN`rIi9GAWMS$f*Bm1-ADs+wtKobhHzX@PGDek6G&)abaPWn6H8N@!J(`zZOyLE zE^f{r(J--ndVjn_fJcN&2#?AE4LwCo)w3GP+Un~Wo)~LsX|7(eYGrmtR%Y=_10#c1 zMK4}TOGrsd&Ym%ACiBlTbHt{Fxm`2cR`!igC!x|^;r_{+N3WihRe68#>C2~YAHRMs zFCejm*~q{`#iYm3M#)IaiY-UpKPRTfDJiI^D^u%;W6zzK%$Jh)X3pv<_@18ne8De{ bW6TT-k4xT3y!(O`=miE(S3j3^P6zuxhbl;GAigU0RoOkTGG;t ztSnP~oSLVyNVZIIc5?0&k!rhrSEmNrjFD0FtkHZsmoSg)j}%h}r7%E-s4sj7#Dl#+Z| zv9CU$oi}iCf>2MqNl3!4vDohI?We7>S#^bdg`$p(jB;;iiF!=Lz&~eVD|>l@XlJi> zceTpP%YurhK_Fl`OIBB1V1IpjbZ%utK}4*rK81rdf^JcFa-D>Sr>dx@RB(VoKSNDc zTZDXVn1EhHKRjwrgT9&Ge$!&n3*@axPh479QBYD) zPEUYtSK7N~td~(WGA&6#FHlJ+qN6m$!a}>ZM5CZWzocQ|#%0~eU1(-9Dk>^RKPaQ2 zI>f<1%E&>&ze2XNKV4KqFD)r(WG;SuG+I_RFD@-6CMO>Z)};Ud00DGTPE!Ct=GbNc z006v6L_t&t*JEG+0Y(sDfB{BECT12^Hkb$_I|nBhHxDl#KUjcKKu}0nL{v;%LQ)DO zz$h&vD<`j@sHCi-3gk1YscUFzY3u0f=^GdtF)}b3o0yuJTUc6I+t}LK1Njb)PR=ft zu5Rugo?hNSzK^e;e?VYRa7buaI7mJsGAcSIHZDFPF)7&yWME2aT6#uiR(4Kq9@xJ8 zg2JNWlG3vBib{xs7^|vlYU}DN8ek4)Y;0<7X+`l7%zpsqkt7<-zIsmp0000K1cun literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/poh/respawn.png b/runelite-client/src/main/resources/net/runelite/client/plugins/poh/respawn.png new file mode 100644 index 0000000000000000000000000000000000000000..c93235a813ea1bc8d858a72ae57bf4be4723ad6a GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^d>}RpGmy;w8yq-|Ni}r zi}GOPV%KJW)(VthED7=pW^j0RBMr#$^>lFz;kcfhkdPpd!ocjN%E+P1v9Pt5_u!V! zZsUj>M~||q9DMUiD2ZW`UtuA$rhp%}FY~3It~9-qPD~ELEWNCaYMh#l4NA=lEDQ`y XCpm3mHi%6J8qDD7>gTe~DWM4f$+$7h literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/poh/trollheim.png b/runelite-client/src/main/resources/net/runelite/client/plugins/poh/trollheim.png new file mode 100644 index 0000000000000000000000000000000000000000..dc8427a9c88876eac4c68562eb2ff08d55908eb9 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^d?3ui3?$#C89V|~LIFM@u0UEqK)=4?zkzxF|NsAi z+|Kuta)A_MNswPKgTu2MX+Vyjr;B3<$Mxid1OX9|6b2?YMO9Zu1>Ge;!0|{+TAGoS zWvWlioT)65EmNFhVtPd+TYQ{joth_Rq$===h=&-Ja Date: Tue, 9 Jun 2026 09:59:54 -0400 Subject: [PATCH 5/7] overlay: add configurable overlay origins --- .../runelite/client/ui/overlay/Overlay.java | 9 + .../client/ui/overlay/OverlayManager.java | 312 +++++++++++++++++- .../client/ui/overlay/OverlayOrigin.java | 99 ++++++ .../client/ui/overlay/OverlayOriginX.java | 32 ++ .../client/ui/overlay/OverlayOriginY.java | 32 ++ .../client/ui/overlay/OverlayRenderer.java | 46 ++- .../client/ui/overlay/SnapCorner.java | 12 +- .../overlay/infobox/InfoBoxManagerTest.java | 5 + 8 files changed, 524 insertions(+), 23 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayOrigin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayOriginX.java create mode 100644 runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayOriginY.java diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/Overlay.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/Overlay.java index e1f2abce86c..db0f882a95e 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/Overlay.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/Overlay.java @@ -56,6 +56,15 @@ public abstract class Overlay implements LayoutableRenderableEntity @Nullable private final Plugin plugin; private Point preferredLocation; + @Getter(AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) + private OverlayOrigin origin = OverlayOrigin.AUTO; + @Getter(AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) + private OverlayOriginX originX = OverlayOriginX.LEFT; + @Getter(AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) + private OverlayOriginY originY = OverlayOriginY.TOP; private Dimension preferredSize; private OverlayPosition preferredPosition; private Rectangle bounds = new Rectangle(); diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayManager.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayManager.java index 2e70f6c6fdd..efb41c71f08 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayManager.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayManager.java @@ -26,6 +26,9 @@ import com.google.common.base.MoreObjects; import com.google.common.collect.ArrayListMultimap; +import com.google.common.graph.GraphBuilder; +import com.google.common.graph.Graphs; +import com.google.common.graph.MutableGraph; import java.awt.Dimension; import java.awt.Point; import java.util.ArrayList; @@ -40,8 +43,14 @@ import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.Menu; import net.runelite.api.gameval.InterfaceID; +import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.WidgetItem; +import net.runelite.client.chat.ChatMessageManager; +import net.runelite.client.chat.QueuedMessage; import net.runelite.client.config.ConfigGroup; import net.runelite.client.config.ConfigManager; import net.runelite.client.config.RuneLiteConfig; @@ -60,6 +69,9 @@ public class OverlayManager private static final String OVERLAY_CONFIG_PREFERRED_LOCATION = "_preferredLocation"; private static final String OVERLAY_CONFIG_PREFERRED_POSITION = "_preferredPosition"; + private static final String OVERLAY_CONFIG_ORIGIN = "_origin"; + private static final String OVERLAY_CONFIG_ORIGIN_X = "_originX"; + private static final String OVERLAY_CONFIG_ORIGIN_Y = "_originY"; private static final String OVERLAY_CONFIG_PREFERRED_SIZE = "_preferredSize"; private static final String RUNELITE_CONFIG_GROUP_NAME = RuneLiteConfig.class.getAnnotation(ConfigGroup.class).value(); @@ -105,12 +117,21 @@ public class OverlayManager private final ConfigManager configManager; private final RuneLiteConfig runeLiteConfig; + private final Client client; + private final ChatMessageManager chatMessageManager; @Inject - private OverlayManager(final ConfigManager configManager, final RuneLiteConfig runeLiteConfig) + private OverlayManager( + final ConfigManager configManager, + final RuneLiteConfig runeLiteConfig, + final Client client, + final ChatMessageManager chatMessageManager + ) { this.configManager = configManager; this.runeLiteConfig = runeLiteConfig; + this.client = client; + this.chatMessageManager = chatMessageManager; } @Subscribe @@ -245,12 +266,14 @@ public synchronized void clear() } /** - * Force save overlay data + * Save overlay data * * @param overlay overlay to save */ public synchronized void saveOverlay(final Overlay overlay) { + log.debug("Saving overlay {} origin: {} x: {} y: {}", overlay.getName(), overlay.getOrigin(), overlay.getOriginX(), overlay.getOriginY()); + saveOverlayPosition(overlay); saveOverlaySize(overlay); saveOverlayLocation(overlay); @@ -267,6 +290,9 @@ public synchronized void resetOverlay(final Overlay overlay) overlay.setPreferredPosition(null); overlay.setPreferredSize(null); overlay.setPreferredLocation(null); + overlay.setOrigin(OverlayOrigin.AUTO); + overlay.setOriginX(OverlayOriginX.LEFT); + overlay.setOriginY(OverlayOriginY.TOP); saveOverlay(overlay); overlay.revalidate(); } @@ -317,6 +343,35 @@ synchronized void rebuildOverlayLayers() this.overlayMap = overlayMap; } + void computeOverlayOrigins(Overlay overlay, int x, int y, int w, int h) + { + Dimension canvasDimensions = client.getRealDimensions(); + + // rough heuristic to determine overlay origins based on position + OverlayOriginX originX = OverlayOriginX.LEFT; + if (x + w / 2 > canvasDimensions.width * .55f) + { + originX = OverlayOriginX.RIGHT; + } + else if (x + w / 2 >= canvasDimensions.width * .45f) + { + originX = OverlayOriginX.CENTER; + } + + OverlayOriginY originY = OverlayOriginY.TOP; + if (y + h / 2 > canvasDimensions.height * .55f) + { + originY = OverlayOriginY.BOTTOM; + } + else if (y + h / 2 > canvasDimensions.height * .45f) + { + originY = OverlayOriginY.CENTER; + } + + overlay.setOriginX(originX); + overlay.setOriginY(originY); + } + private void loadOverlay(final Overlay overlay) { final Point location = loadOverlayLocation(overlay); @@ -325,12 +380,32 @@ private void loadOverlay(final Overlay overlay) if (overlay.isMovable()) { + OverlayOrigin originMode = loadOverlayOrigin(overlay); + OverlayOriginX originX = loadOverlayOriginX(overlay); + OverlayOriginY originY = loadOverlayOriginY(overlay); + + if (location != null && originMode != null && originX != null && originY != null) + { + overlay.setOrigin(originMode); + overlay.setOriginX(originX); + overlay.setOriginY(originY); + } + else + { + overlay.setOrigin(OverlayOrigin.AUTO); + overlay.setOriginX(OverlayOriginX.LEFT); + overlay.setOriginY(OverlayOriginY.TOP); + } + overlay.setPreferredLocation(location); } else if (location != null) { log.info("Resetting preferred location of non-movable overlay {} (class {})", overlay.getName(), overlay.getClass().getName()); overlay.setPreferredLocation(null); + overlay.setOrigin(OverlayOrigin.AUTO); + overlay.setOriginX(OverlayOriginX.LEFT); + overlay.setOriginY(OverlayOriginY.TOP); saveOverlayLocation(overlay); } @@ -357,6 +432,112 @@ private void updateOverlayConfig(final Overlay overlay) } } + private void convertOriginToAbsolute(Point p, OverlayOriginX originX, OverlayOriginY originY, Point out) + { + Dimension d = client.getRealDimensions(); + int ax = p.x; + if (originX == OverlayOriginX.RIGHT) + { + ax = d.width + p.x; + } + else if (originX == OverlayOriginX.CENTER) + { + ax = d.width / 2 + p.x; + } + + int ay = p.y; + if (originY == OverlayOriginY.BOTTOM) + { + ay = d.height + p.y; + } + else if (originY == OverlayOriginY.CENTER) + { + ay = d.height / 2 + p.y; + } + + out.setLocation(ax, ay); + } + + void computeAbsolutePosition(Overlay overlay, Point out) + { + OverlayOrigin origin = overlay.getOrigin(); + if (origin != OverlayOrigin.AUTO && origin != OverlayOrigin.MANUAL) + { + Widget w = origin.getWidget(client); + int wx, wy; + if (w != null) + { + var wp = w.getCanvasLocation(); + wx = wp.getX(); + wy = wp.getY(); + origin.coord.setLocation(wx, wy); + } + else + { + wx = origin.coord.x; + wy = origin.coord.y; + } + + var op = overlay.getPreferredLocation(); + out.setLocation(wx + op.x, wy + op.y); + } + else + { + convertOriginToAbsolute(overlay.getPreferredLocation(), overlay.getOriginX(), overlay.getOriginY(), out); + } + } + + private Point convertAbsoluteToOrigin(Point p, OverlayOriginX originX, OverlayOriginY originY) + { + Dimension d = client.getRealDimensions(); + int ox = p.x; + if (originX == OverlayOriginX.RIGHT) + { + ox = p.x - d.width; + } + else if (originX == OverlayOriginX.CENTER) + { + ox = p.x - d.width / 2; + } + + int oy = p.y; + if (originY == OverlayOriginY.BOTTOM) + { + oy = p.y - d.height; + } + else if (originY == OverlayOriginY.CENTER) + { + oy = p.y - d.height / 2; + } + + return new Point(ox, oy); + } + + Point computeOriginPosition(Point absPosition, OverlayOrigin origin, OverlayOriginX originX, OverlayOriginY originY) + { + if (origin != OverlayOrigin.AUTO && origin != OverlayOrigin.MANUAL) + { + Widget w = origin.getWidget(client); + int wx, wy; + if (w != null) + { + var wp = w.getCanvasLocation(); + wx = wp.getX(); + wy = wp.getY(); + } + else + { + wx = origin.coord.x; + wy = origin.coord.y; + } + return new Point(absPosition.x - wx, absPosition.y - wy); + } + else + { + return convertAbsoluteToOrigin(absPosition, originX, originY); + } + } + private void saveOverlayLocation(final Overlay overlay) { final String key = overlay.getName() + OVERLAY_CONFIG_PREFERRED_LOCATION; @@ -366,12 +547,24 @@ private void saveOverlayLocation(final Overlay overlay) RUNELITE_CONFIG_GROUP_NAME, key, overlay.getPreferredLocation()); + configManager.setConfiguration(RUNELITE_CONFIG_GROUP_NAME, + overlay.getName() + OVERLAY_CONFIG_ORIGIN, + overlay.getOrigin()); + configManager.setConfiguration(RUNELITE_CONFIG_GROUP_NAME, + overlay.getName() + OVERLAY_CONFIG_ORIGIN_X, + overlay.getOriginX()); + configManager.setConfiguration(RUNELITE_CONFIG_GROUP_NAME, + overlay.getName() + OVERLAY_CONFIG_ORIGIN_Y, + overlay.getOriginY()); } else { configManager.unsetConfiguration( RUNELITE_CONFIG_GROUP_NAME, key); + configManager.unsetConfiguration(RUNELITE_CONFIG_GROUP_NAME, overlay.getName() + OVERLAY_CONFIG_ORIGIN); + configManager.unsetConfiguration(RUNELITE_CONFIG_GROUP_NAME, overlay.getName() + OVERLAY_CONFIG_ORIGIN_X); + configManager.unsetConfiguration(RUNELITE_CONFIG_GROUP_NAME, overlay.getName() + OVERLAY_CONFIG_ORIGIN_Y); } } @@ -428,4 +621,119 @@ private OverlayPosition loadOverlayPosition(final Overlay overlay) final String locationKey = overlay.getName() + OVERLAY_CONFIG_PREFERRED_POSITION; return configManager.getConfiguration(RUNELITE_CONFIG_GROUP_NAME, locationKey, OverlayPosition.class); } + + private OverlayOrigin loadOverlayOrigin(final Overlay overlay) + { + return configManager.getConfiguration(RUNELITE_CONFIG_GROUP_NAME, overlay.getName() + OVERLAY_CONFIG_ORIGIN, OverlayOrigin.class); + } + + private OverlayOriginX loadOverlayOriginX(final Overlay overlay) + { + return configManager.getConfiguration(RUNELITE_CONFIG_GROUP_NAME, overlay.getName() + OVERLAY_CONFIG_ORIGIN_X, OverlayOriginX.class); + } + + private OverlayOriginY loadOverlayOriginY(final Overlay overlay) + { + return configManager.getConfiguration(RUNELITE_CONFIG_GROUP_NAME, overlay.getName() + OVERLAY_CONFIG_ORIGIN_Y, OverlayOriginY.class); + } + + void addOriginMenu(Overlay overlay) + { + Menu menu = client.getMenu(); + Menu sub = menu.createMenuEntry(-1) + .setOption("Overlay Origin") + .createSubMenu(); + String[] opts = {"Top left", "Top center", "Top right", "Bottom left", "Bottom center", "Bottom right"}; + OverlayOriginX[] originX = { + OverlayOriginX.LEFT, OverlayOriginX.CENTER, OverlayOriginX.RIGHT, + OverlayOriginX.LEFT, OverlayOriginX.CENTER, OverlayOriginX.RIGHT + }; + OverlayOriginY[] originY = { + OverlayOriginY.TOP, OverlayOriginY.TOP, OverlayOriginY.TOP, + OverlayOriginY.BOTTOM, OverlayOriginY.BOTTOM, OverlayOriginY.BOTTOM + }; + int off = 0; + for (int i = 0; i < opts.length; ++i) + { + OverlayOriginX ox = originX[i]; + OverlayOriginY oy = originY[i]; + sub.createMenuEntry(-1 - off++) + .setOption(opts[i]) + .onClick(e -> + { + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.CONSOLE) + .runeLiteFormattedMessage("This overlay will now be automatically repositioned relative to the " + + oy.name().toLowerCase() + " " + ox.name().toLowerCase() + " of the screen when the client is resized.") + .build()); + + Point p = overlay.getBounds().getLocation(); + p = computeOriginPosition(p, OverlayOrigin.MANUAL, ox, oy); + overlay.setPreferredLocation(p); + overlay.setPreferredPosition(null); + + overlay.setOrigin(OverlayOrigin.MANUAL); + overlay.setOriginX(ox); + overlay.setOriginY(oy); + saveOverlay(overlay); + }); + } + opts = new String[]{"Sidepanel", "Chatbox", "Minimap"}; + OverlayOrigin[] origins = {OverlayOrigin.SIDEPANEL, OverlayOrigin.CHATBOX, OverlayOrigin.MINIMAP}; + for (int i = 0; i < opts.length; ++i) + { + OverlayOrigin origin = origins[i]; + sub.createMenuEntry(-1 - off++) + .setOption(opts[i]) + .onClick(e -> + { + if (cycleCheck(overlay, origin)) + { + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.CONSOLE) + .runeLiteFormattedMessage("The origin of " + origin.name().toLowerCase() + " is already linked to this overlay, either directly, or indirectly through multiple other overlays. " + + "Introducing a circular dependency is not permitted.") + .build()); + return; + } + + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.CONSOLE) + .runeLiteFormattedMessage("This overlay will now be automatically repositioned relative to the " + + origin.name().toLowerCase() + ".") + .build()); + + Point p = overlay.getBounds().getLocation(); + p = computeOriginPosition(p, origin, null, null); + overlay.setPreferredLocation(p); + overlay.setPreferredPosition(null); + + overlay.setOrigin(origin); + saveOverlay(overlay); + }); + } + } + + private synchronized boolean cycleCheck(Overlay curOverlay, OverlayOrigin newOrigin) + { + MutableGraph g = GraphBuilder + .directed() + .allowsSelfLoops(true) + .build(); + for (Overlay overlay : overlays) + { + if (overlay instanceof WidgetOverlay) + { + OverlayOrigin origin = overlay == curOverlay ? newOrigin : overlay.getOrigin(); + Widget overlayWidget = client.getWidget(((WidgetOverlay) overlay).componentId); + Widget originWidget = origin.getWidget(client); + if (overlayWidget != null && originWidget != null) + { + g.putEdge(overlayWidget, originWidget); + } + } + } + return Graphs.hasCycle(g); + } + } \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayOrigin.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayOrigin.java new file mode 100644 index 00000000000..9ae45f61359 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayOrigin.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2026, Adam + * 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.ui.overlay; + +import java.awt.Point; +import net.runelite.api.Client; +import net.runelite.api.annotations.Component; +import net.runelite.api.gameval.InterfaceID; +import net.runelite.api.widgets.Widget; + +enum OverlayOrigin +{ + AUTO, + MANUAL, + SIDEPANEL + { + @Override + Widget getWidget(Client client) + { + return getComponent( + client, + InterfaceID.ToplevelOsrsStretch.SIDE_MENU, + InterfaceID.ToplevelPreEoc.SIDE_CONTAINER, + InterfaceID.Toplevel.SIDE + ); + } + }, + CHATBOX + { + @Override + Widget getWidget(Client client) + { + return getComponent( + client, + InterfaceID.ToplevelOsrsStretch.CHAT_CONTAINER, + InterfaceID.ToplevelPreEoc.CHAT_CONTAINER, + InterfaceID.Toplevel.CHAT_CONTAINER + ); + } + }, + MINIMAP + { + @Override + Widget getWidget(Client client) + { + return getComponent( + client, + InterfaceID.ToplevelOsrsStretch.MAP_CONTAINER, + InterfaceID.ToplevelPreEoc.MAP_CONTAINER, + InterfaceID.Toplevel.MAPCONTAINER + ); + } + }; + + Widget getWidget(Client client) + { + return null; + } + + private static Widget getComponent(Client client, @Component int stretch, @Component int eoc, @Component int fixed) + { + if (client.isResized()) + { + if (client.getTopLevelInterfaceId() == InterfaceID.TOPLEVEL_PRE_EOC) + { + return client.getWidget(eoc); + } + else + { + return client.getWidget(stretch); + } + } + return client.getWidget(fixed); + } + + final Point coord = new Point(); +} diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayOriginX.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayOriginX.java new file mode 100644 index 00000000000..dd9b9468588 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayOriginX.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026, Adam + * 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.ui.overlay; + +enum OverlayOriginX +{ + LEFT, + CENTER, + RIGHT, +} diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayOriginY.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayOriginY.java new file mode 100644 index 00000000000..c8ab83ee0e7 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayOriginY.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026, Adam + * 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.ui.overlay; + +enum OverlayOriginY +{ + TOP, + CENTER, + BOTTOM, +} diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java index 703d24b7587..6c827cbd226 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java @@ -190,6 +190,8 @@ protected void onClientTick(ClientTick t) return; } + overlayManager.addOriginMenu(overlay); + List menuEntries = overlay.getMenuEntries(); if (menuEntries.isEmpty()) { @@ -284,12 +286,12 @@ private void renderOverlays(final Graphics2D graphics, Collection overl final Rectangle clip = clipBounds(layer); graphics.setClip(clip); + final Point location = new Point(); for (Overlay overlay : overlays) { final OverlayPosition overlayPosition = getCorrectedOverlayPosition(overlay); final Rectangle bounds = overlay.getBounds(); final Point preferredLocation = overlay.getPreferredLocation(); - Point location; SnapCorner snapCorner = null; // If the final position is not modified, layout it @@ -297,15 +299,19 @@ private void renderOverlays(final Graphics2D graphics, Collection overl && overlayPosition != OverlayPosition.DETACHED && preferredLocation == null) { snapCorner = snapCorners.forPosition(overlayPosition); - location = snapCorner.getNextDrawPosition(bounds); + snapCorner.getNextDrawPosition(bounds, location); + } + else if (preferredLocation != null) + { + overlayManager.computeAbsolutePosition(overlay, location); } else { - location = preferredLocation != null ? preferredLocation : bounds.getLocation(); + location.setLocation(bounds.x, bounds.y); } // Clamp the overlay position to ensure it is on screen or within parent bounds - location = clampOverlayLocation(location.x, location.y, bounds.width, bounds.height, overlay); + clampOverlayLocation(location.x, location.y, bounds.width, bounds.height, overlay, location); if (overlay.getPreferredSize() != null) { @@ -559,8 +565,6 @@ public MouseEvent mouseDragged(MouseEvent mouseEvent) final int minOverlaySize = currentManagedOverlay.getMinimumSize(); final int widthOverflow = Math.max(0, minOverlaySize - width); final int heightOverflow = Math.max(0, minOverlaySize - height); - final int dx = x - originalX; - final int dy = y - originalY; // If this resize operation would cause the dimensions to go below the minimum width/height, reset the // dimensions and adjust the x/y position accordingly as needed @@ -568,7 +572,7 @@ public MouseEvent mouseDragged(MouseEvent mouseEvent) { width = minOverlaySize; - if (dx > 0) + if (x > originalX) { x -= widthOverflow; } @@ -577,7 +581,7 @@ public MouseEvent mouseDragged(MouseEvent mouseEvent) { height = minOverlaySize; - if (dy > 0) + if (y > originalY) { y -= heightOverflow; } @@ -586,9 +590,10 @@ public MouseEvent mouseDragged(MouseEvent mouseEvent) currentManagedBounds.setRect(x, y, width, height); currentManagedOverlay.setPreferredSize(new Dimension(currentManagedBounds.width, currentManagedBounds.height)); - if (currentManagedOverlay.getPreferredLocation() != null) + Point l = currentManagedOverlay.getPreferredLocation(); + if (l != null) { - currentManagedOverlay.setPreferredLocation(currentManagedBounds.getLocation()); + l.translate(x - originalX, y - originalY); } } else if (inOverlayDraggingMode) @@ -598,7 +603,17 @@ else if (inOverlayDraggingMode) // Clamp drag to parent component final Rectangle overlayBounds = currentManagedOverlay.getBounds(); - overlayPosition = clampOverlayLocation(overlayPosition.x, overlayPosition.y, overlayBounds.width, overlayBounds.height, currentManagedOverlay); + clampOverlayLocation(overlayPosition.x, overlayPosition.y, overlayBounds.width, overlayBounds.height, currentManagedOverlay, overlayPosition); + + if (currentManagedOverlay.getOrigin() == OverlayOrigin.AUTO) + { + // Compute the new origins for the overlay + overlayManager.computeOverlayOrigins(currentManagedOverlay, overlayPosition.x, overlayPosition.y, overlayBounds.width, overlayBounds.height); + } + + // Compute new relative position + overlayPosition = overlayManager.computeOriginPosition(overlayPosition, currentManagedOverlay.getOrigin(), currentManagedOverlay.getOriginX(), currentManagedOverlay.getOriginY()); + currentManagedOverlay.setPreferredPosition(null); currentManagedOverlay.setPreferredLocation(overlayPosition); } @@ -655,6 +670,9 @@ public MouseEvent mouseReleased(MouseEvent mouseEvent) currentManagedOverlay.setPreferredPosition(position); currentManagedOverlay.setPreferredLocation(null); // from dragging + currentManagedOverlay.setOrigin(OverlayOrigin.AUTO); + currentManagedOverlay.setOriginX(OverlayOriginX.LEFT); + currentManagedOverlay.setOriginY(OverlayOriginY.TOP); currentManagedOverlay.revalidate(); break; } @@ -844,9 +862,9 @@ private void positionSnapcorners() * @param overlayWidth * @param overlayHeight * @param overlay the overlay - * @return the clamped position + * @param out the clamped position */ - private Point clampOverlayLocation(int overlayX, int overlayY, int overlayWidth, int overlayHeight, Overlay overlay) + private void clampOverlayLocation(int overlayX, int overlayY, int overlayWidth, int overlayHeight, Overlay overlay, Point out) { int px, py, pw, ph; Rectangle parentBounds = overlay.getParentBounds(); @@ -867,7 +885,7 @@ private Point clampOverlayLocation(int overlayX, int overlayY, int overlayWidth, } // Constrain overlay position to be within the parent bounds - return new Point( + out.setLocation( Ints.constrainToRange(overlayX, px, Math.max(px, px + pw - overlayWidth)), Ints.constrainToRange(overlayY, py, diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/SnapCorner.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/SnapCorner.java index aba1fa97296..65adbb32c2a 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/SnapCorner.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/SnapCorner.java @@ -98,25 +98,23 @@ void shift(Rectangle overlayBounds, int padding) } // get position to draw the next overlay - Point getNextDrawPosition(Rectangle bounds) + void getNextDrawPosition(Rectangle bounds, Point out) { - Point result = new Point(px, py); + out.setLocation(px, py); if ((mode & ALIGNMENT_CENTER_HORIZONTAL) != 0) { - result.x -= bounds.width / 2; + out.x -= bounds.width / 2; } else if ((mode & ALIGNMENT_RIGHT) != 0) { - result.x -= bounds.width; + out.x -= bounds.width; } if ((mode & ALIGNMENT_BOTTOM) != 0) { - result.y -= bounds.height; + out.y -= bounds.height; } - - return result; } Rectangle corner(Dimension size) diff --git a/runelite-client/src/test/java/net/runelite/client/ui/overlay/infobox/InfoBoxManagerTest.java b/runelite-client/src/test/java/net/runelite/client/ui/overlay/infobox/InfoBoxManagerTest.java index a54af2ab8ae..a979306e187 100644 --- a/runelite-client/src/test/java/net/runelite/client/ui/overlay/infobox/InfoBoxManagerTest.java +++ b/runelite-client/src/test/java/net/runelite/client/ui/overlay/infobox/InfoBoxManagerTest.java @@ -36,6 +36,7 @@ import net.runelite.client.config.ConfigManager; import net.runelite.client.config.RuneLiteConfig; import net.runelite.client.plugins.Plugin; +import net.runelite.client.ui.overlay.OverlayManager; import static org.junit.Assert.assertEquals; import org.junit.Before; import org.junit.Test; @@ -63,6 +64,10 @@ public class InfoBoxManagerTest @Bind private Client client; + @Mock + @Bind + private OverlayManager overlayManager; + @Before public void before() { From c01380d15a276e4c3ab6e573b1123bd1d35b8465 Mon Sep 17 00:00:00 2001 From: Sami Date: Tue, 16 Jun 2026 21:34:37 +0200 Subject: [PATCH 6/7] Merge latest RuneLite master --- gradle.properties | 5 +++-- runelite-client/build.gradle.kts | 4 +++- .../microbot/util/reflection/menu-action-info.properties | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index 3e7d41a472a..d23db23f0ef 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,10 +28,11 @@ org.gradle.parallel=true org.gradle.caching=false project.build.group=net.runelite -project.build.version=1.12.28 +project.build.version=1.12.29 +runelite.injected-client.version=1.12.29-SNAPSHOT glslang.path= -microbot.version=2.6.5 +microbot.version=2.6.6 microbot.commit.sha=nogit microbot.repo.url=http://138.201.81.246:8081/repository/microbot-snapshot/ microbot.repo.username= diff --git a/runelite-client/build.gradle.kts b/runelite-client/build.gradle.kts index 4d7c8cd9aaa..10ba41663d9 100644 --- a/runelite-client/build.gradle.kts +++ b/runelite-client/build.gradle.kts @@ -38,6 +38,8 @@ fun loadRootProperty(name: String): String? { val microbotVersionProvider = providers.gradleProperty("microbot.version") .orElse(loadRootProperty("microbot.version") ?: "0.0.0") +val injectedClientVersionProvider = providers.gradleProperty("runelite.injected-client.version") + .orElse(loadRootProperty("runelite.injected-client.version") ?: project.version.toString()) plugins { java @@ -326,7 +328,7 @@ java { dependencies { api("net.runelite:runelite-api:${project.version}") implementation(project(":jshell")) - runtimeOnly("net.runelite:injected-client:${project.version}") + runtimeOnly("net.runelite:injected-client:${injectedClientVersionProvider.get()}") api(libs.rl.http.api) api(libs.rl.discord) diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/util/reflection/menu-action-info.properties b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/util/reflection/menu-action-info.properties index 66c5ecce8a3..5b8116d6666 100644 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/util/reflection/menu-action-info.properties +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/util/reflection/menu-action-info.properties @@ -1,5 +1,5 @@ #Build-time pre-seed for MenuActionInfoCache (do not edit by hand; regenerate via :client:seedMenuActionInfo) -#Fri May 08 19:37:18 CEST 2026 +#Tue Jun 16 21:33:16 CEST 2026 menuAction.garbageValue=910500823 menuAction.descriptor=(IIIIIILjava/lang/String;Ljava/lang/String;III)V menuAction.methodName=fa From 075f326013c2b1bf95ca75b315139d56957b6f65 Mon Sep 17 00:00:00 2001 From: Sami Date: Tue, 16 Jun 2026 22:00:21 +0200 Subject: [PATCH 7/7] chore(gradle): update project and injected client version to 1.12.28 --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index d23db23f0ef..14f677e894a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,8 +28,8 @@ org.gradle.parallel=true org.gradle.caching=false project.build.group=net.runelite -project.build.version=1.12.29 -runelite.injected-client.version=1.12.29-SNAPSHOT +project.build.version=1.12.28 +runelite.injected-client.version=1.12.28 glslang.path= microbot.version=2.6.6