Skip to content

fix(GotrPlugin): guard region checks against null player location (1.5.11) - #659

Merged
chsami merged 2 commits into
developmentfrom
claude/err-gotr-ishugemine-null-player-location
Oct 19, 2026
Merged

chsami merged 2 commits into
developmentfrom
claude/err-gotr-ishugemine-null-player-location

Conversation

@chsami

@chsami chsami commented Oct 19, 2026

Copy link
Copy Markdown
Owner

Problem

Client error telemetry group 2663 (1 report, first and last seen 2026-10-19, Microbot 2.6.34, GotrPlugin 1.5.10, thread GotrScript-6) logged a java.lang.NullPointerException from Global Sleep::

Cannot invoke "net.runelite.api.coords.WorldPoint.getRegionID()" because the return value of Rs2Player.getWorldLocation() is null
  at GotrScript.isInHugeMine:677
  at GotrScript.lambda$usePortal$11:375
  at Global.sleepUntil:176
  at GotrScript.usePortal:375
  at GotrScript.lambda$run$4:177

Root cause

Rs2Player.getWorldLocation() returns null while the local player is unavailable, which happens during the region load that follows a portal teleport into the huge mine (and when entering or leaving an altar). isInHugeMine, isInLargeMine, isOutsideBarrier and isInMainRegion all dereferenced that value without a check, and the client-thread lambdas in isInHugeMine and isInLargeMine also dereferenced getLocalPlayer() directly. The sleepUntil(() -> isInHugeMine()) in usePortal polls exactly while the region is loading, so the predicate threw.

Change

  • isInHugeMine, isInLargeMine, isOutsideBarrier and isInMainRegion read the location once and return false when it is null. isInHugeMine and isInLargeMine take the X coordinate from that same location instead of a separate client-thread getLocalPlayer().getWorldLocation() call.
  • New package-private static boolean isPlayerLocationKnown(). The main loop returns early (if (!isPlayerLocationKnown()) return;) right after super.run(), so a tick where the location is still unknown is skipped instead of reaching isInMiniGame = !isOutsideBarrier() && isInMainRegion(). Without this, an unknown location would read as "not in the minigame" and the loop could click the barrier (enterMinigame) or call resetPlugin() (waitForMinigameToStart). Skipping the tick is the same outcome the old NPE + catch block produced, minus the error.
  • The non-inverted waits (sleepUntil(isInHugeMine) in usePortal, sleepUntilTrue(isInMainRegion)) now keep polling until the region loads, which fixes the reported stack trace.
  • GotrPlugin version 1.5.10 -> 1.5.11.

Validation

  • ./gradlew test --tests 'net.runelite.client.plugins.microbot.gotr.*' build -PpluginList=GotrPlugin passes.
  • New GotrRegionCheckTest (6 tests): all four region checks return false for a null location, isPlayerLocationKnown() is false for null and true for a known location. Existing GotR tests (GotrAltarRenderableTest, GotrPluginRoundEventTest, GotrPortalClockTest) pass.
  • Not live-tested in game.

Known gaps

  • The gate runs at the top of the tick, but the minigame decision is made later, after checkPouches/repairPouches, which can sleep for ~1.5s. If the location goes null inside that window, isInMiniGame can still evaluate false for that one tick. The window is narrow (teleports happen inside the usePortal/enterAltar sleeps) and the next tick re-checks at the gate.
  • Inverted predicates such as !isInMainRegion() in enterAltar and !isInHugeMine() in leaveHugeMine now evaluate true while the location is null, so those waits can end early. This is not a regression: previously Global.sleepUntil caught the NPE and returned false immediately, and the next tick re-checks.
  • The test covers the helper, not the scheduled loop lambda itself.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

chsami and others added 2 commits October 19, 2026 06:01
…5.11)

isInHugeMine, isInLargeMine, isOutsideBarrier and isInMainRegion
dereferenced Rs2Player.getWorldLocation() without a null check, so the
usePortal sleepUntil threw an NPE while the region loaded after the
portal teleport. Read the location once, return false when it is null,
and use its X coordinate instead of a client-thread lookup on
getLocalPlayer().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV
The region predicates now return false for a null location, so the main
loop read the region-load window after a portal teleport or altar trip as
"not in the minigame" and could click the barrier or reset plugin state.
Bail out of the iteration until Rs2Player.getWorldLocation() is known.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV
@chsami
chsami merged commit 5b5a200 into development Oct 19, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant