Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude/agents/code-reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ Project architecture is provided via the blpc-overview skill.
- Proper use of `ModLog` categories for logging
- Trust level / trust action consistency

### Java 17 Syntax (Mandatory)
- All Java 17 features enforced per blpc-overview skill (switch expressions, pattern matching instanceof, `var`, multi-label case)
### Java 25 Syntax (Mandatory)
- All Java 25 features enforced per blpc-overview skill (switch expressions, pattern matching instanceof, `var`, multi-label case)

### Comments & Javadoc
- Public API classes/interfaces (`api/` package) have Javadoc with `@param`, `@return`, `@throws` as appropriate
Expand Down
2 changes: 1 addition & 1 deletion .claude/agents/implementer.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ You receive tasks from the QA lead or the user. Your job is to:
## Key Rules

- **Do not edit `build.gradle`** (auto-generated)
- **Java 17 syntax is mandatory** per blpc-overview skill (switch expressions, pattern matching instanceof, `var`)
- **Java 25 syntax is mandatory** per blpc-overview skill (switch expressions, pattern matching instanceof, `var`)
- New network messages: append (never insert).
- **C→S** — IMessage + handler both in `common/network/`. Append to `ModNetwork.init()` C→S block.
- **S→C** — IMessage in `common/network/` (no `@SideOnly` types in bytecode!). Handler in `client/network/<Name>ClientHandler.java` with `@SideOnly(Side.CLIENT)`. Append to **both** `ModNetwork.CLIENT_BOUND_MESSAGES` and `ClientPacketHandlers.installAll()` in identical order.
Expand Down
99 changes: 75 additions & 24 deletions .claude/skills/blpc-overview/SKILL.md

Large diffs are not rendered by default.

60 changes: 30 additions & 30 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
@@ -1,50 +1,50 @@
name: Claude Code

on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
pull_request:
types: [opened]

permissions:
contents: write
pull-requests: write
issues: write
id-token: write
actions: read

jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: write
id-token: write
actions: read # Required for Claude to read CI results on PRs
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 1
fetch-depth: 0

- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
prompt: "/review"

# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read
# Optional: Customize the trigger phrase (default: @claude)
# trigger_phrase: "/claude"

# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
# prompt: 'Update the pull request description to include a summary of changes.'
# Optional: Trigger when specific user is assigned to an issue
# assignee_trigger: "claude-bot"

# Optional: Add claude_args to customize behavior and configuration
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options
# claude_args: '--allowed-tools Bash(gh pr:*)'
# Optional: Configure Claude's behavior with CLI arguments
# claude_args: |
# --model claude-opus-4-1-20250805
# --max-turns 10
# --allowedTools "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)"
# --system-prompt "Follow our coding standards. Ensure all new code has tests. Use TypeScript for new files."

# Optional: Advanced settings configuration
# settings: |
# {
# "env": {
# "NODE_ENV": "test"
# }
# }
91 changes: 91 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node-terminal",
"name": "1. Setup Workspace",
"request": "launch",
"command": "./gradlew setupDecompWorkspace; exit",
"windows": { "command": "gradlew.bat setupDecompWorkspace" },
"cwd": "${workspaceFolder}"
},
{
"type": "node-terminal",
"name": "2. Run Client",
"request": "launch",
"command": "./gradlew runClient; exit",
"windows": { "command": "gradlew.bat runClient" },
"cwd": "${workspaceFolder}"
},
{
"type": "node-terminal",
"name": "3. Run Server",
"request": "launch",
"command": "./gradlew runServer; exit",
"windows": { "command": "gradlew.bat runServer" },
"cwd": "${workspaceFolder}"
},
{
"type": "node-terminal",
"name": "4. Run Obfuscated Client",
"request": "launch",
"command": "./gradlew runObfClient; exit",
"windows": { "command": "gradlew.bat runObfClient" },
"cwd": "${workspaceFolder}"
},
{
"type": "node-terminal",
"name": "5. Run Obfuscated Server",
"request": "launch",
"command": "./gradlew runObfServer; exit",
"windows": { "command": "gradlew.bat runObfServer" },
"cwd": "${workspaceFolder}"
},
{
"type": "node-terminal",
"name": "6. Apply Spotless",
"request": "launch",
"command": "./gradlew spotlessApply; exit",
"windows": { "command": "gradlew.bat spotlessApply" },
"cwd": "${workspaceFolder}"
},
{
"type": "node-terminal",
"name": "7. Build Jars",
"request": "launch",
"command": "./gradlew build; exit",
"windows": { "command": "gradlew.bat build" },
"cwd": "${workspaceFolder}"
},
{
"type": "node-terminal",
"name": "Update Buildscript",
"request": "launch",
"command": "./gradlew updateBuildScript; exit",
"windows": { "command": "gradlew.bat updateBuildScript" },
"cwd": "${workspaceFolder}"
},
{
"type": "node-terminal",
"name": "FAQ",
"request": "launch",
"command": "./gradlew faq; exit",
"windows": { "command": "gradlew.bat faq" },
"cwd": "${workspaceFolder}"
},
{
"type": "java",
"name": "Attach to Client",
"request": "attach",
"hostName": "localhost",
"port": 5005
},
{
"type": "java",
"name": "Attach to Server",
"request": "attach",
"hostName": "localhost",
"port": 5006
}
]
}
17 changes: 17 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"gradle.nestedProjects": false,
"java.gradle.buildServer.enabled": "on",
"java.compile.nullAnalysis.mode": "automatic",
"files.watcherExclude": {
"**/build/**": true,
"**/.gradle/**": true
},
"search.exclude": {
"**/build": true,
"**/.gradle": true
},
"files.exclude": {
"**/build": true,
"**/.gradle": true
}
}
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* * *

## [0.14.0]

### Added

- **Claim/party data persists across reconnects**
- The chunk map and party menu now show your last-known claims and party info immediately after reconnecting to a server, instead of a blank screen while the server's fresh sync is in flight.
- Cached separately per server/world, so switching between servers never mixes up their claim data.
- **Force-loaded areas stand out on JourneyMap**
- Claim regions where every chunk is force-loaded now render with a bolder, fully opaque outline on JourneyMap, and the region label is now properly localized.
- **Fair play settings**
- New config options let server admins tune area-control potion effects and transit toast notifications independently, for servers that want PvP without a home-field advantage.
- Optional on-screen indicator shows whether you're currently standing in a claimed chunk and who owns it, so PvP fights always make protection status clear.
- **Team waypoint sharing on JourneyMap**
- With JourneyMap installed, a party's waypoints can now be shared with every online member — only the party owner can add, move, or remove them, and members always see the up-to-date result on their own map.
- Toggleable per-player in the Addons menu, under JourneyMap.

### Changed

- **Claiming a chunk now requires a party.** Chunk protection is a party-sharing feature, so you must create or join a party before claiming. Singleplayer is unaffected by default (a party is still auto-created on first login).

### Fixed

- **BQu-linked parties could drift out of sync with BLPC.** A player who joined an already-linked BetterQuesting party through BQu's own party screen (rather than BLPC's) was previously invisible to BLPC's protection, claim-limit, and party-management logic — they could be wrongly denied access to their own party's claims, get a separate personal claim limit instead of sharing the party's pool, and be unable to use party settings, disband, or unlink through BLPC's UI. Party membership is now resolved consistently between BQu and BLPC in all of these paths.

[0.14.0]: https://github.com/gtexpert/BetterLinkPartyClaim/releases/tag/v0.14.0

* * *

## [0.13.0]

### Added
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ RetroFuturaGradle (RFG) + GTNH Buildscripts. **Do not edit `build.gradle`** (aut

## Key Rules

- **Java 17 syntax mandatory** (Jabel → JVM 8): switch expressions (`->`), pattern matching `instanceof`, `var` for obvious types. Details in `.claude/skills/blpc-overview/SKILL.md`.
- **Local builds need JDK 17**: spotless' googlejavaformat can't parse switch expressions on an older daemon JVM. If the Gradle daemon is Java 11/8, run with `-Dorg.gradle.java.home=<jdk17>` (e.g. `/usr/lib/jvm/zulu-17`). Compilation uses the Java 17 toolchain regardless.
- **Java 25 syntax mandatory** (Jabel → JVM 8): switch expressions (`->`), pattern matching `instanceof`, `var` for obvious types. Details in `.claude/skills/blpc-overview/SKILL.md`.
- **Local builds need JDK 25**: spotless' googlejavaformat can't parse switch expressions on an older daemon JVM. If the Gradle daemon is an older Java, run with `-Dorg.gradle.java.home=<jdk25>` (e.g. `/usr/lib/jvm/zulu-25`). Compilation uses the Java 25 toolchain regardless.
- **Imports**: Always use `import` statements, not FQCN. Spotless enforces ordering.
- **GUI entry points**: open screens through `client/gui/Screens` (the single catalog — `openMap()`, `partyMain(...)`), never `ClientGUI.open(new …)` ad-hoc. Reuse shared drawables from `client/gui/BLPCGuiTextures` (incl. `ICON_*` from ModularUI's `GuiTextures` atlas) instead of inlining drawables.
- **GUI colors**: No ModularUI theme system — BLPC ships a single **light** look with colors defined directly in Java. `client/gui/BLPCColors` holds the **semantic** party/map colors (`text()`, `owner()`, `admin()`, `warning()`, `subtext()`, `inactive()`, `divider()`, `mapBackground()`, `mapBorder()`, `textShadow()`) as fixed constants. `client/gui/GuiColors` holds **fixed vanilla-context** colors (`WHITE`/`GOLD`/`GREEN`/`RED`/`GRAY` for toasts, map counters, tooltips, map grid). Use these holders — never inline `0x…` literals (the only exceptions are dynamic per-party `getColor()` ARGB composition). Buttons use ModularUI's default theme; black party text reads against it. Visual changes need `runClient` to verify.
Expand Down
1 change: 1 addition & 0 deletions dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ dependencies {

// Debug Journey Map
api(libs.journeymap.api)
compileOnly rfg.deobf(libs.journeymap.mod.get().toString())
if (runtimeEnabled(project.debug_jmap)) {
runtimeOnly rfg.deobf(libs.journeymap.mod.get().toString())
}
Expand Down
9 changes: 6 additions & 3 deletions src/main/java/com/github/gtexpert/blpc/BLPCMod.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import net.minecraftforge.fml.common.event.FMLServerStoppingEvent;

import com.github.gtexpert.blpc.api.util.Mods;
import com.github.gtexpert.blpc.client.gui.ProtectionStatusHud;
import com.github.gtexpert.blpc.client.input.KeyInputHandler;
import com.github.gtexpert.blpc.modules.ModuleManager;
import com.github.gtexpert.blpc.modules.Modules;
Expand All @@ -26,16 +27,17 @@
* Functionality lives in modules ({@code CoreModule}, {@code BQuModule}, ...) —
* this class only wires Forge into the module pipeline.
* <p>
* Client-only handlers ({@link KeyInputHandler}) are registered during
* {@link #init} on the client side. ModularUI is a hard
* Client-only handlers ({@link KeyInputHandler}, {@link ProtectionStatusHud}) are registered
* during {@link #init} on the client side. ModularUI is a hard
* dependency; BetterQuesting and JourneyMap are soft dependencies whose
* integrations live behind their own modules.
*/
@Mod(modid = Tags.MODID,
version = Tags.VERSION,
name = Tags.MODNAME,
acceptedMinecraftVersions = "[1.12.2]",
dependencies = "required-after:" + Mods.Names.MODULAR_UI + ";" + "after:" + Mods.Names.BETTER_QUESTING + ";" +
dependencies = "required-after:" + Mods.Names.MODULAR_UI + ";" +
"after:" + Mods.Names.BETTER_QUESTING + ";" +
"after:" + Mods.Names.JOURNEY_MAP + ";")
public class BLPCMod {

Expand Down Expand Up @@ -64,6 +66,7 @@ public void init(FMLInitializationEvent event) {
if (event.getSide().isClient()) {
KeyInputHandler.init();
MinecraftForge.EVENT_BUS.register(new KeyInputHandler());
MinecraftForge.EVENT_BUS.register(new ProtectionStatusHud());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,35 @@ public interface IPartyProvider {
@Nullable
String getRole(UUID playerUUID);

/**
* Returns a stable identifier for the player's party, or {@code null} if they have no party.
* Unlike the display-facing {@link Party} objects synced to clients (whose id can vary by
* which member's data happened to seed it — see {@code BQuPartyProvider#serializeForClient}),
* this id is guaranteed identical for every member of the same real party, making it safe as
* a server-side storage key (e.g. {@code WaypointManagerData}) even for members who have
* never had their own BLPC-side party record created.
*/
@Nullable
default UUID getPartyId(UUID playerUUID) {
return null;
}

/**
* Returns the player's fully-populated {@link Party} (members, trust levels, allies/enemies,
* claim-limit settings) for authoritative server-side checks — chunk protection trust
* resolution, additive claim/force-load limits, and chunk-transit relation notifications.
* <p>
* Unlike reading a self-managed {@code PartyManagerData} record directly, this is guaranteed
* to reflect live membership even for a member who joined entirely through a delegate's own
* UI (e.g. BQu's native party screen) and so has no BLPC-side {@link Party} record of their
* own — see {@code BQuPartyProvider#getEffectiveParty}. Returns {@code null} if the player has
* no party.
*/
@Nullable
default Party getEffectiveParty(UUID playerUUID) {
return null;
}

/** Returns the party with the given name, or null if none exists. */
@Nullable
default Party findByName(String name) {
Expand Down Expand Up @@ -84,6 +113,20 @@ default boolean hasNativeParty(UUID playerUUID) {
return getPartyName(playerUUID) != null;
}

/**
* Returns true if this player's current native party has been linked to BLPC (i.e. any of
* its members opted in via {@code ACTION_TOGGLE_BQU_LINK}), so mutations for this player
* should route through this provider instead of the self-managed fallback.
* <p>
* Unlike a per-player flag snapshotted at link time, this reflects the party's <em>current</em>
* membership: a player who joins an already-linked native party afterward (e.g. through BQu's
* own party screen, entirely outside BLPC) is recognized immediately, without requiring a
* separate propagation step every time membership changes elsewhere.
*/
default boolean isLinkedParty(UUID playerUUID) {
return false;
}

/**
* Ensures a native party exists for the owner with the same members as the
* given BLPC party. Creates the native party if absent, adds missing
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.github.gtexpert.blpc.client.cache;

import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;

/**
* Derives a filesystem-safe identifier for the server the client is currently connected to,
* so per-server claim/party caches never mix data between different worlds or servers.
*/
@SideOnly(Side.CLIENT)
public final class ClientCacheKey {

private ClientCacheKey() {}

/**
* Returns a sanitized identifier for the current connection, or {@code null} if not
* connected to any world (e.g. on the main menu).
*/
public static String current() {
Minecraft mc = Minecraft.getMinecraft();
if (mc.isIntegratedServerRunning()) {
var server = mc.getIntegratedServer();
if (server == null) return null;
return "sp_" + sanitize(server.getFolderName());
}
ServerData data = mc.getCurrentServerData();
if (data == null || data.serverIP == null) return null;
return "mp_" + sanitize(data.serverIP);
}

private static String sanitize(String raw) {
return raw.replaceAll("[^a-zA-Z0-9._-]", "_");
}
}
Loading
Loading