File Details
claimmyland-neoforge-1.21.1-2.6.0.jar
- R
- Apr 16, 2026
- 813.76 KB
- 50
- 1.21.1
- NeoForge
File Name
claimmyland-neoforge-1.21.1-2.6.0.jar
Supported Versions
- 1.21.1
Curse Maven Snippet
Changelog for Claim My Land 1.21.1
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
> ⚠️ IMPORTANT — Breaking Change & Backup Warning > > v2.x is not save-compatible with v1.x. Parcel data saved by any v1 release will not load in v2. If you are upgrading an existing world, your claimed parcels will be lost. > > Back up your world before installing v2. Copy your entire world folder to a safe location before upgrading. Once you have loaded the world in v2, downgrading back to v1 is not supported.
> 💡 Recommended: Since this mod is heavily command-based, we recommend using 🔗 Chat Plus for a better command history and larger chat window.
[2.6.0] - 2026-04-16
🎉 Highlights
- JM fullscreen tooltip picks the correct nested parcel — was Y-coordinate dependent, now uses 2D point-in-footprint with smallest-volume tiebreaker.
- Citizen deeds carry the bound Nation name in their tooltip — players can see at a glance which Nation a Citizen deed will join.
- Phantom Player parcel after relinquished-Citizen reclaim no longer appears on the placing player's JM.
- Reclaim preview no longer falsely renders red when the relinquished Citizen has an adjacent same-owner sibling.
- JM tooltip ghost text cleaned up — no more floating "No Ow…" fragments near the cursor on the fullscreen map.
- Relinquished parcels no longer disappear from
/cml-ops parcel relinquishsuggestions — duplicate estate names produced by the relinquish split now usedefaultName(ownerId)for guaranteed uniqueness. - Red conflict borders no longer appear on committed parcels after periodic
resync or relog —
syncAllParcelsToPlayerwas callingresolveConflictStateon committed parcels (same root cause as the v2.5.1onChunkWatchfix in a different code path). - Red conflict borders no longer appear on a neighbor's committed parcel when
placing a border stone —
placeParcelBorder()was callingresolveConflictStateunconditionally and passing the result to the committed- parcel broadcast path. ThirdresolveConflictStatecall-site fix. - PlayerDeed → Citizen conversions now fire purple Citizen fireworks instead
of green Player fireworks. The celebration code in
Deed.useOnwas reading off the transient pre-claim parcel reference; post-claim re-resolve now uses a chained lookup strategy that handles both conversion paths. - Conflict preview overlays no longer broadcast to all players — only the placing player sees red/orange conflict highlights in JM and in-world.
- JM orange conflict overlays now clear correctly on distance timeout after the foundation stone is removed.
- Foundation stone repositioning now works correctly — old border stones are removed from the client when a new position is selected.
- Deed placement now gives informative failure messages —
canPlaceAt()returns a richPlacementResultenum so players know exactly why a deed cannot be placed (closed Nation, blacklisted, wrong parent type, etc.).
➕ Added
PlacementResult enum
- New enum
PlacementResultwith valuesSUCCESS,OUTSIDE_WORLD,NATION_CLOSED,NATION_BLACKLISTED,OUTSIDE_VALID_PARENT,INVALID_PARENT_TYPE,ACCESS_DENIED,UNKNOWN_FAILURE. Parcel.canPlaceAt()default changed frombooleantoPlacementResult.NationParcel,ZoneParcel,CitizenParcel,PlayerParceleach overridecanPlaceAt()to return the appropriate specific value.Deed.useOn()maps the result to a lang key viaplacementLangKey(PlacementResult).- Exhaustive
switchinplacementLangKey— adding a new enum value produces a compile error, not a silent fallthrough.
Citizen deed Nation name tooltip
DeedFactory.createCitizenDeed(Box size, UUID nationId, String nationName)— addednationNameparameter, persisted toCustomDataalongside the existingNATION_ESTATE_IDvia the read-modify-writeback pattern.- New
Deed.NATION_ESTATE_NAME = "nationEstateName"constant. CitizenDeed.appendHoverTextreadsNATION_ESTATE_NAMEand renders a goldNation: <name>line. No-op when absent (old deeds without the bound name).- New lang key
tooltip.claimmyland.deed.nation.
ClientParcelRegistry.findAt(int x, int z, String dimension) overload
- 2D point-in-footprint lookup for the JourneyMap fullscreen tooltip. Ignores Y entirely — the fullscreen map is a top-down view. When multiple parcels' footprints contain the point, the smallest by area wins (same "smallest containing parcel" rule used by the in-world HUD).
🐛 Fixed
JM fullscreen tooltip showed Zone instead of nested Citizen depending on cursor Y
- Root cause:
JourneyMapOverlayHandler.onMapMouseMoved()was callingClientParcelRegistry.findAt(x, y, z, dimension)with the cursor's full 3D BlockPos. JM provides the Y of the topmost block under the cursor (surface block, tree leaf, tower top — whichever is highest). The 3D containment check was therefore inconsistent: cursor on a tall block inside a Citizen's Y range picked Citizen, cursor on bare ground below the Citizen's Y range picked the enclosing Nation. - Fix: added 2D
findAt(int x, int z, String dimension)overload toClientParcelRegistrythat ignores Y.onMapMouseMovednow calls the 2D overload.
Phantom Player parcel after relinquished-Citizen reclaim
- Root cause:
Deed.useOn()was explicitly callingCMLNetwork.syncParcelToPlayer(serverLevel, serverPlayer, parcel)after a successful claim, whereparcelis the transient pre-claim object created from the deed item. In the normal claim path this was a redundant duplicate of the broadcast already performed bynameAndRegister()→ParcelRegistry.register(ServerLevel, Parcel)→syncParcelToTrackingPlayers. In the relinquished-Citizen reclaim path (AbstractClaimableParcel.claimRelinquishedCitizenParcel→ParcelRegistry.transferParcelOwnership) and thePlayerParcel.claimWithinZone→ freshCitizenParcelconversion path, the registered parcel is a different object thanparcel. The explicit sync added the transient pre-claim object to the placing player'sClientParcelRegistryas a phantom green overlay that persisted until the next periodic resync (~5 minutes). - Fix: deleted the explicit
syncParcelToPlayer(parcel)block from theif (claimResult.isSuccess())branch inDeed.useOn(). The placing player is always withinTRACKING_CHUNKrange of their own claim, so the existingsyncParcelToTrackingPlayersbroadcast insidenameAndRegister/transferParcelOwnershipalready reaches them.
Reclaim preview shows red conflict against adjacent same-owner sibling
- Root cause: when reclaiming a relinquished Citizen adjacent to a
non-relinquished sibling Citizen owned by the original (relinquishing) player,
the Foundation Stone preview rendered red even though the commit succeeded.
resolveConflictState()correctly excluded the relinquished Citizen itself from the direct-overlap check viaexcludeParcelId, but the buffer/inflated checks still found the adjacent sibling and evaluatedisConflict(citizen, citizen, reclaimer, originalOwner)— which returned true because the same-owner sibling exception that allowed the original adjacency no longer applied (the reclaimer is a different owner). - Fix: added a reclaim exemption at the top of
resolveConflictState()that returns0immediately whenexcludeParcelIdresolves to a relinquished Citizen whose geometry exactly matches the proposed box. The exemption is narrow — geometric equality is required — so it never fires for non-reclaim placements.
JM tooltip ghost text and polygon label cleanup
- Root cause: During earlier debugging we added
setTitle(buildFullLabel(parcel))to JM polygon overlays. JM'sOverlay.setTitle()is a rollover text JM renders independently of ourParcelMapTooltipRenderer. With both active, they competed and clipped each other, producing a small floating "No Ow…" fragment near the cursor. - Fix: removed
.setTitle(...)frombuildOverlay(). Also changedsetLabel(parcel.estateName())tosetLabel("")on the fullscreen overlay since the rich custom tooltip already shows the estate name on hover. Minimap overlay unchanged — it has no hover tooltip and the label is the only way to identify parcels at a glance.
Relinquished parcels disappear from /cml-ops parcel relinquish suggestions
- Root cause:
RelinquishParcelSubCommand.relinquishParcel()peels the relinquished parcel off its shared estate by creating a newEstateobject, but copied the old name verbatim vianewEstate.setName(oldEstate.getName()). When two Citizens shared an estate and one was relinquished, the result was two estates with identical names but different UUIDs. The suggestion providers (OPS_OWNER_CITIZEN_ESTATE_NAMES,OWNER_CITIZEN_ESTATE_NAMES) collect names into aSet<String>keyed onEstate::getName, collapsing duplicates to a single entry. The user-visible symptom was that one of the two estates became unreachable from the relinquish command suggestions, even though the parcel data was intact in the registry.transferParcelOwnership()had the same bug on the reclaim side and was patched in parallel. - Fix: both
RelinquishParcelSubCommand.relinquishParcel()andParcelRegistry.transferParcelOwnership()now callnewEstate.defaultName(ownerId), which uses the authoritativePlayerRegistry.nextEstateNameIndex(ownerId)counter and is collision-free by construction. Relinquished and reclaimed estates are now indistinguishable in form from any other freshly-created estate.
Red conflict borders on committed parcels after periodic resync or relog
- Root cause:
CMLNetwork.syncAllParcelsToPlayer()calledParcelRegistry.resolveConflictState()on every committed parcel before building the resync packets.resolveConflictState()is designed for Foundation Stone placement preview only — when called on a committed Nation parcel whose box contains nested Zones or Citizens, the children overlap the parent box and the method returns1, which the client renders as red borders. This is the same bug the v2.5.1 fix patched inonChunkWatch, reproduced in a different code path. - Fix: both branches of the per-parcel
map(...)insyncAllParcelsToPlayer()now pass0literally forconflictStateinstead of callingresolveConflictState(). Mirrors the v2.5.1onChunkWatchfix.
Committed parcel border stone placement broadcasts wrong conflictState to tracking players
- Root cause:
BorderStoneBlockEntity.placeParcelBorder()calledresolveConflictState()unconditionally at the top of the method and passed the result into both the committed-parcel branch and the preview branch.resolveConflictState()is valid only for Foundation Stone placement preview — when called for a committed parcel with an adjacent different-owner sibling, it returns1, whichsyncBorderVisibilityToTrackingPlayersAndSelfbroadcast to all tracking players. The affected player saw the adjacent committed parcel render red in JM. Self-healed on the ~5-minute periodic resync. Same root cause as the v2.5.1onChunkWatchfix and the v2.6syncAllParcelsToPlayerfix — the thirdresolveConflictStatecall-site on committed parcels. - Fix:
resolveConflictState()moved into the preview (else if) branch only. Both committed-parcel send paths (placingPlayer != nullandplacingPlayer == null) now pass0literally forconflictState.
PlayerDeed → Citizen fireworks render green instead of purple
- Root cause:
Deed.useOn()'s success branch re-resolved the registered parcel viaParcelRegistry.findByParcelId(parcel.getId()), but this missed in both conversion paths. In thePlayerParcel.claimWithinZone()→CitizenParcelpath, the pre-claimPlayerParcelbuilt from the deed item has no UUID (getId()returnsnull), sofindByParcelId(null)returned empty. In the relinquished-Citizen reclaim path (transferParcelOwnership), the registered parcel retains the relinquished parcel's UUID — the UUID of the foundation stone's existing block entity — not the freshly pre-assigned UUID on the transientPlayerParcel. Both cases fell back to the stalePlayerParcelvia.orElse(parcel)and fired green fireworks. - Fix:
Deed.useOn()pre-assignsUUID.randomUUID()to the pre-claim parcel whengetId() == null, beforehandleClaim()is called. The re-resolve in the success branch then chains two lookups: firstfindByParcelId(parcel.getId())(finds the registeredCitizenParcelin theclaimWithinZonepath), thenfindByParcelId(foundationStoneBlockEntity .getParcelId())(finds the transferred parcel in the relinquished reclaim path, whose UUID is the foundation stone's pre-existing parcel ID). TheorElse(parcel)defensive fallback is retained as last resort.
Conflict preview overlays broadcast to all tracking-chunk players
- Root cause:
BorderStoneBlockEntity.placeParcelBorder()calledCMLNetwork.syncPreviewParcelToTrackingPlayersAndSelf(...)with the realconflictStatefor all players. Every client receiving a preview packet withconflictState > 0ranshowConflictOverlays(), so all nearby players saw the red/orange conflict highlights in JM — not just the placing player. - Fix: split the preview send into two calls. Tracking players (excluding
the placer) receive
conflictState=0so they see the JM preview outline but never conflict highlighting. The placing player receives a separate player-targeted send with the realconflictState. BothsyncPreviewParcelToTrackingPlayersandsyncPreviewParcelToOwnerare used; the former excludes the placing player from the tracking broadcast.
JM orange conflict overlays persist after foundation stone removal and distance clear
- Root cause:
rebuildTransientOverlays()(called frompushAllOverlays()when the JM map is opened) did a rawCONFLICT_OVERLAYS.clear()andCONFLICT_BUFFER_OVERLAYS.clear()without first callingjmApi.remove()on the old overlay objects. This orphaned the original overlay objects in JM — they remained visible and could never be removed because no reference to them survived. WhenclearConflictOverlays()later fired (on distance/timeout), it removed the replacement objects but the originals persisted as orange fills. - Fix:
rebuildTransientOverlays()now callsjmApi.remove()on all entries in both conflict maps before clearing them, matching the pattern used byremoveAllPermanentOverlays(). Same remove-before-clear discipline now applies consistently across all overlay lifecycle paths.
Foundation stone not removed from client when repositioning with deed
- Root cause:
Deed.useOn()fell through toreturn super.useOn(context)(which returnsInteractionResult.PASS) after a successful foundation stone placement.PASStells NeoForge the interaction was not handled, suppressing the block-change sync to the client. The server correctly placed the new stone and removed the old one (including border stones), but the client never received the updates and continued to show the old border stones. They appeared unbreakable (mod protection active on preview parcels) and disappeared on relog when the client received fresh chunk data. This matched the v2.4 code which returnedInteractionResult.SUCCESSexplicitly. - Fix: the foundation stone placement branch now returns
InteractionResult.SUCCESSon success andInteractionResult.FAILon failure, never falling through tosuper.useOn(context).
Deed placement fails silently with no player message
- Root cause:
canPlaceAt()returnedboolean— callers knew placement was denied but not why. Players using a Citizen deed directly inside a Nation (outside any Zone), inside a closed Nation, or while blacklisted received no feedback. - Fix:
canPlaceAt()now returnsPlacementResult. Each concrete parcel type's override returns the specific failure reason.Deed.useOn()maps the result to a lang key and sends an informative failure message.
Removed
Dead-code overloads in CMLNetwork
Two CMLNetwork overloads were latent traps with no live callers:
syncBorderVisibleToOwner(ServerLevel, Parcel, int borderStoneY)— 3-arg version that resolvedconflictStateinternally viaresolveConflictState().syncBorderVisibleToOwnerAndPlacer(ServerLevel, Parcel, int borderStoneY, UUID placingPlayerId)— 4-arg version that resolvedconflictStateinternally. Both methods calledresolveConflictState()on what would have been committed parcels (any caller would have been a Border Stone toggle path), reproducing the same bug as the v2.5.1onChunkWatchand v2.6syncAllParcelsToPlayerfixes. With no callers, the cleanest action is deletion — the surviving explicit-conflictStateoverloads (which take the value as a parameter) cover every legitimate use, and callers always know whether they're in a preview or committed context.
Stale @Deprecated on INationParcel.getBlacklist() / setBlacklist()
The blacklist mechanism is current and in active use. The @Deprecated(forRemoval = true, since = "2.0") annotations on getBlacklist(), setBlacklist(), and
the corresponding NationParcel.blacklist field were incorrect. Removed.
[2.5.1] - 2026-04-09
🎉 Highlights
- Cold-start red borders fixed — parcel borders no longer render red for the owning player on a fresh server + client start when nested parcels exist inside a Nation.
- Overlapping parcel claim prevented — placement tools (CitizenTool, ZoningTool) now correctly block claiming a parcel that visually shows as conflicting with an existing sibling parcel. Previously the conflict preview was client-side only and the server allowed the claim through.
- Placement blocks preserved on failed Zone claim — Zone placement blocks are no longer removed when a Zone claim fails due to conflict. Players can reposition and retry without replacing the blocks.
🐛 Fixed
Committed parcel borders render red on cold server + client start —
onChunkWatchwas callingresolveConflictState()on already-committed parcels when sending border visibility to joining players.resolveConflictState()is designed for Foundation Stone placement preview only; when called on committed parcels with nested children (e.g. Zones and Citizens inside a Nation), it incorrectly returned conflict1because the child parcels overlap the parent's box without being the same parcel. Fix: passconflictState=0unconditionally for committed border stone visibility inonChunkWatch. Preview parcels (Foundation Stone not yet claimed) still callresolveConflictState()correctly since they are handled byFoundationStoneBlockEntityevents, notonChunkWatch. Likely also present in Forge 1.20.1 branch.Overlapping sibling parcel claim allowed via CitizenTool and ZoningTool —
claimWithinZone()inCitizenParcelandPlayerParcel, andhandleEmbeddedClaim()inZoneParcel, were missing a direct box overlap check against sibling parcels. Only buffer zone checks were performed, so a directly overlapping same-owner same-type sibling was never caught —ParcelConflictResolver.isConflict()returnsfalsefor same-owner same-type pairs, and the buffer check alone does not cover direct overlap. Fix: addedParcelHelper.checkDirectSiblingOverlap()and called it from all three claim paths before the buffer checks. The helper takes anexcludeZonesparameter —truefor Citizen/Player (zone is a valid parent, not a conflict),falsefor Zone (sibling zones inside a Nation must be checked against each other). Likely also present in Forge 1.20.1 branch.Zone placement blocks removed on failed claim —
ZoningTool.handleZoneCreation()calledclear()unconditionally aftertryCreateZoneParcel()regardless of whether the claim succeeded or failed. Fix:tryCreateZoneParcel()now returnsInteractionResult.CONSUMEon success andInteractionResult.SUCCESSon failure;handleZoneCreation()only callsclear()and removes item tag coords onCONSUME. Players whose Zone claim fails due to conflict now retain their placement blocks and receive an appropriate failure message.Citizen placement blocks removed on failed claim —
CitizenTool.tryCreateCitizenParcel()always returnedInteractionResult.SUCCESSregardless ofClaimResult, andhandleParcelCreation()cleared placement blocks and item tag coords unconditionally after the call. With the v2.5.1checkDirectSiblingOverlap()fix now surfacing real failure results fromclaimWithinZone(), this caused players to lose both Citizen placement blocks on a failed claim. Fix:tryCreateCitizenParcel()now returnsInteractionResult.CONSUMEon success andInteractionResult.SUCCESSon failure;handleParcelCreation()only callsclear()and removesCOORDS1/COORDS2onCONSUME. Mirrors the v2.5.1 ZoningTool fix. Likely also present in Forge 1.20.1 branch.Citizen claim disconnect on failed placement —
EncoderException: Failed to encode packet 'clientbound/minecraft:system_chat'when a Citizen deed claim failed inside a Nation. Root cause:Deed.useOn()passed rawCoordsobjects as translation args toComponent.translatable("...unable_to_claim.detail", parcel.getMinCoords(), ModUtil.getSize(...)). NeoForge 1.21.1'sComponentSerializationcodec is strict and only acceptsComponent/String/Number/Booleanargs — any other type throws"This value needs to be parsed as component"at packet encode time, disconnecting the client. Fix: added a defensivecoerceArgs()helper inCommandResponseFormatterthat wraps any non-primitive/non-Component arg inComponent.literal(arg.toString())before passing toComponent.translatable(...). Applied at the single chokepoint whereargsmeetsComponent.translatable, so all callers ofsendFailure/sendSuccessare covered without call-site edits. NeoForge 1.21.1 only — Forge 1.20.1's serializer tolerated raw object args viatoString()fallback.
🔧 Technical Notes
onChunkWatchiteratesActiveBorderStoneRegistrywhich tracks Border Stones only — Foundation Stone preview parcels never appear here, so the unconditionalconflictState=0is safe for all parcels reached via this path.ParcelHelper.checkDirectSiblingOverlap()mirrors the direct overlap logic inParcel.handleClaim()for top-level parcels, closing the parity gap between top-level and embedded claim validation.excludeZones=trueis the safe default for the convenience overload — Citizen and Player parcels should never treat a containing Zone as a sibling conflict.ZoningTool.tryCreateZoneParcel()previously always returnedInteractionResult.SUCCESSregardless ofClaimResult— the distinction between success and failure now relies onCONSUMEvsSUCCESSso callers can guard cleanup correctly.
Requires GottschCore v2.6 or later.

