Skip to content

Implement shulker boxes#20

Open
HashimTheArab wants to merge 59 commits into
HashimTheArab:masterfrom
mmm545:feature/shulker-box
Open

Implement shulker boxes#20
HashimTheArab wants to merge 59 commits into
HashimTheArab:masterfrom
mmm545:feature/shulker-box

Conversation

@HashimTheArab

@HashimTheArab HashimTheArab commented May 8, 2026

Copy link
Copy Markdown
Owner

Note

Medium Risk
Adds a new container block plus new player/viewer displacement APIs and a new network movement packet path, which could impact entity sync and plugin/handler implementations. Also changes NBT inventory encoding slice type, which may affect any code expecting the previous concrete type.

Overview
Implements ShulkerBox as a dyeable 27-slot container block (including undyed variant) that persists contents via NBT, registers all colour variants, and adds block hashing support for the new OptionalColour type.

Adds shulker lid collision/animation via a new model.Shulker bounding box that expands with open progress, scheduled ticks to drive open/close state, open/close sounds, and logic to push intersecting entities out of the opening lid.

Extends the entity movement pipeline with forced displacement: introduces HandleDisplace on player.Handler, Displace on session.Controllable, Viewer.ViewEntityDelta, and a Session.ViewEntityDelta implementation using MoveActorDelta (force-move), used by shulker lid pushes. Updates session container handling to recognise ContainerShulkerBox, and broadens nbtconv.InvToNBT to return []any.

Reviewed by Cursor Bugbot for commit daab568. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

Release Notes

  • New Features
    • Introduced shulker boxes as dyeable containers with animated lids, 27-item storage capacity, and interactive opening/closing mechanics

Review Change Stack

mmm545 and others added 30 commits December 4, 2024 16:59
Implemented inventory
Implemented sounds
Implemented opening/closing animations
Hope it doesn't break anything else
…nfly into feature/shulker-box

Sync local repositry with remote repositry
Co-authored-by: DaPigGuy <mcpepig123@gmail.com>
Merge master into feature/shulker-box to fix any possible PR merge conflicts
@coderabbitai

coderabbitai Bot commented May 8, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 154a36c4-ae65-4d81-a85b-d7a82e6790ac

📥 Commits

Reviewing files that changed from the base of the PR and between 5b7a40b and daab568.

📒 Files selected for processing (2)
  • server/block/model/shulker.go
  • server/block/shulker_box.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • server/block/model/shulker.go
  • server/block/shulker_box.go

📝 Walkthrough

Walkthrough

This pull request implements a complete shulker box container block with an animated lid, entity displacement mechanics, and full NBT persistence. The changes introduce an OptionalColour type system, extend displacement handling across player/entity/handler interfaces, and integrate shulker boxes into the block registry and container system.

Changes

Shulker Box Feature

Layer / File(s) Summary
Optional Colour Type System
server/item/optional_colour.go
OptionalColour type encodes absent-or-present dye colours as uint8 (0 = absent, 1..16 = colours). Provides conversion from Colour, extraction of (Colour, bool), uint8 exposure, colour-name prepending, and OptionalColours() enumeration of all 17 variants.
Displacement Handler Interfaces
server/player/handler.go, server/session/controllable.go, server/world/viewer.go
Handler.HandleDisplace() callback, Controllable.Displace() method, and Viewer.ViewEntityDelta() method extend interfaces for forced displacement. NopHandler and NopViewer provide no-op implementations.
Shulker Block Model & Animation Physics
server/block/model/shulker.go
Shulker model struct with Facing and Progress fields. ShulkerPhysicalPeak() maps progress [0,10] to cubic ease-out extension scaled by 0.5. BBox() applies extension along facing direction; FaceSolid() returns false.
Block Hash and Encoding
cmd/blockhash/main.go, server/block/hash.go
Hash generator recognizes OptionalColour field type with 5-bit allocation. hashShulkerBox constant added; ShulkerBox.Hash() returns block ID and colour as uint64.
Shulker Box Sound Events
server/world/sound/block.go
ShulkerBoxOpen and ShulkerBoxClose message types for lid animations.
ShulkerBox Block Implementation
server/block/shulker_box.go
27-slot container with dyeable properties (colour, facing, custom name), lazily created inventory, mutex-protected viewer tracking, and atomic animation state/progress. AddViewer/RemoveViewer manage opening/closing sequences and sound. ScheduledTick advances lid progress and displaces intersecting entities. Activate opens container for users implementing ContainerOpener with low light. UseOnBlock places block and sets facing. Full NBT persistence stores items, facing, optional custom name, and block ID.
Block and Item Registration
server/block/register.go
Registers all ShulkerBox block variants via allShulkerBoxes() and corresponding items for each OptionalColour.
Player Displacement Method
server/player/player.go
Adds Player.Displace() to forcibly offset position/rotation via mgl64.Vec3 and angle parameters. Calls Handler().HandleDisplace(), notifies viewers via ViewEntityDelta, conditionally updates velocity/collision for small displacements. Updates imports for dialogue and form packages.
Session Viewer Communication and Movement
server/session/world.go
ViewEntityMovement early-returns for hidden entities. New ViewEntityDelta helper calculates target position, derives MoveActorDelta flags from axis/rotation comparisons, forces move flag, and writes packet. Fixes sound.Totem case to return after packet write to prevent switch fallthrough.
Container and Inventory Integration
server/session/player.go, server/internal/nbtconv/item.go
Session.invByID() maps protocol.ContainerShulkerBox to opened window inventory when block is ShulkerBox. InvToNBT() return type generalized from []map[string]any to []any while preserving per-slot map encoding.

🎯 4 (Complex) | ⏱️ ~45 minutes

🐰 A shulker box hops in with flair,
Animated lid opens in the air,
Storing items, displacing foes,
Twenty-seven slots... and gravity flows!
Dyeable colours, NBT care,
Container dreams made whole and fair. 🎨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Implement shulker boxes' clearly and directly summarizes the main change: adding shulker box functionality to the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 81.82% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread server/block/shulker_box.go
Comment thread server/block/shulker_box.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
server/item/optional_colour.go (1)

41-45: ⚡ Quick win

Avoid hardcoding optional-colour count.

Line 42 hardcodes 17, which couples this helper to the current dye count. Derive from len(colours) to keep it future-proof.

Proposed fix
-	optionalColours := make([]OptionalColour, 17)
+	optionalColours := make([]OptionalColour, len(colours)+1)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/item/optional_colour.go` around lines 41 - 45, The slice length in
OptionalColours is hardcoded to 17; change it to derive from the colours slice
so it stays correct when dyes change: allocate optionalColours with length
len(colours)+1 (since the loop writes at index i+1), then populate with
NewOptionalColour(c) in the existing loop over colours; update OptionalColours,
referencing the colours variable and NewOptionalColour accordingly.
server/block/shulker_box.go (2)

167-184: 💤 Low value

Use the return value of Add and avoid the redundant trailing tick.

progress.Add(1)/Add(-1) already return the new value, so the extra Load() is unnecessary. Also, after the state transitions to shulkerStateOpened/shulkerStateClosed in this branch, the unconditional ScheduleBlockUpdate results in one wasted tick that just re-stores the boundary value before settling. You can scope the reschedule to the still-animating case.

♻️ Proposed cleanup
 	case shulkerStateOpening:
-		s.progress.Add(1)
+		p := s.progress.Add(1)
 		s.pushEntities(pos, tx)
-		if s.progress.Load() >= shulkerLidTicks {
+		if p >= shulkerLidTicks {
 			s.progress.Store(shulkerLidTicks)
 			s.animationStatus.Store(shulkerStateOpened)
+			return
 		}
 		tx.ScheduleBlockUpdate(pos, s, 0)
 	case shulkerStateOpened:
 		s.progress.Store(shulkerLidTicks)
 	case shulkerStateClosing:
-		s.progress.Add(-1)
-		if s.progress.Load() <= 0 {
+		p := s.progress.Add(-1)
+		if p <= 0 {
 			tx.PlaySound(pos.Vec3Centre(), sound.ShulkerBoxClose{})
 			s.progress.Store(0)
 			s.animationStatus.Store(shulkerStateClosed)
+			return
 		}
 		tx.ScheduleBlockUpdate(pos, s, 0)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/block/shulker_box.go` around lines 167 - 184, In shulkerStateOpening
and shulkerStateClosing use the return value of s.progress.Add(...) instead of
calling s.progress.Load() again, and only call tx.ScheduleBlockUpdate(pos, s, 0)
when the new progress value is strictly between 0 and shulkerLidTicks (i.e.,
still animating); when progress reaches the boundaries, set s.progress.Store(0
or shulkerLidTicks), update s.animationStatus to
shulkerStateOpened/shulkerStateClosed, play the close sound in the closing
branch as now, and avoid the unconditional ScheduleBlockUpdate that causes a
redundant tick. Ensure you reference s.progress.Add, shulkerLidTicks,
shulkerStateOpened, shulkerStateClosed, shulkerStateOpening,
shulkerStateClosing, tx.ScheduleBlockUpdate, and sound.ShulkerBoxClose when
making the changes.

188-198: 💤 Low value

Remove the [0] index concern and clarify the 0.35 growth value.

The BBox method returns a single-element slice, so the [0] index is always safe—not an assumption. However, the 0.35 growth value merits documentation: it appears to be an intentional margin for entity hitboxes around the extended lid (which itself reaches 0 to 0.5 blocks). Consider extracting this as a named constant (e.g., shulkerPushMargin) to clarify intent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/block/shulker_box.go` around lines 188 - 198, Replace the magic 0.35
literal with a named constant and make the single-element BBox usage explicit:
define a file-level constant shulkerPushMargin (e.g., const shulkerPushMargin =
0.35) with a short comment explaining it’s the extra margin to cover entity
hitboxes around the extended lid, then in pushEntities use the single returned
bounding box explicitly (bbox := s.Model().BBox(pos, tx)[0]) and call
bbox.Translate(pos.Vec3()).Grow(shulkerPushMargin) so the intent and safety of
the single-element BBox and the margin are clear.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/block/model/shulker.go`:
- Around line 27-30: The ShulkerPhysicalPeak function computes a peak from an
unvalidated progress value; clamp the input progress to the valid range [0,10]
before computing t so out-of-range values cannot produce negative or >0.5
results. Modify ShulkerPhysicalPeak to bound the int32 progress (e.g., if
progress < 0 set to 0, if progress > 10 set to 10) and then convert the clamped
value to float64 for the existing formula, keeping the rest of the computation
in the same function.

In `@server/block/shulker_box.go`:
- Around line 221-234: The current Y-axis logic skips downward-facing shulkers
(when s.Facing.Offset().Y() == -1) so entities touching a ceiling-mounted
shulker aren't moved; add a branch for offset.Y() < 0 that mirrors the upward
case but pushes entities downward so their top is at or below
shulkerBBox.Min().Y(). Specifically, in the function using s.Facing.Offset(),
entityPos := e.Position(), shulkerBBox and mover.Displace, compute targetY :=
shulkerBBox.Min().Y() for the downward case and call mover.Displace with a
negative Y displacement when entityPos.Y() (adjusted for whether entityPos
represents feet or center in this codebase) is above targetY; keep the existing
early-returns for the other Y cases.
- Around line 62-72: The inventory currently suppresses viewer updates for
ShulkerBox items but still accepts them server-side; update the inventory
creation for s.inventory (the inventory.New callback) to instead install a slot
validator that rejects ShulkerBox placements by calling
s.inventory.SlotValidatorFunc and returning false when item.Item() is a
ShulkerBox, and keep the existing viewer notification loop (which calls
viewer.ViewSlotChange) unchanged so clients and server stay in sync.
- Around line 205-210: The current logic checks for the Displace method on e
(mover, ok := e.(interface { Displace(deltaPos mgl64.Vec3, deltaYaw, deltaPitch
float64) })) and returns for any entity that doesn't implement it, causing
non-player entities to be ignored; fix this by ensuring all pushable entity
types implement Displace (or add Displace to the common entity base/interface)
so mobs, projectiles, and items are handled, or change the shulker-box
displacement path to fall back when ok==false by applying movement via a shared
API (e.g., a generic Translate/SetPosition or ApplyVelocity method) using the
same deltaPos (mgl64.Vec3) and rotation deltas; update the implementations for
entity types that lack Displace (or add the fallback branch) so no entity is
skipped.

In `@server/item/optional_colour.go`:
- Around line 20-37: The Colour() and Prepend() methods on OptionalColour
currently index colours[(oc-1)] without checking an upper bound; add a bounds
check in both OptionalColour.Colour() and OptionalColour.Prepend() to ensure (oc
> 0) and (int(oc-1) < len(colours)) before indexing, returning (Colour{}, false)
from Colour() and the original str from Prepend() when the value is out of range
to avoid panics from malformed input.

---

Nitpick comments:
In `@server/block/shulker_box.go`:
- Around line 167-184: In shulkerStateOpening and shulkerStateClosing use the
return value of s.progress.Add(...) instead of calling s.progress.Load() again,
and only call tx.ScheduleBlockUpdate(pos, s, 0) when the new progress value is
strictly between 0 and shulkerLidTicks (i.e., still animating); when progress
reaches the boundaries, set s.progress.Store(0 or shulkerLidTicks), update
s.animationStatus to shulkerStateOpened/shulkerStateClosed, play the close sound
in the closing branch as now, and avoid the unconditional ScheduleBlockUpdate
that causes a redundant tick. Ensure you reference s.progress.Add,
shulkerLidTicks, shulkerStateOpened, shulkerStateClosed, shulkerStateOpening,
shulkerStateClosing, tx.ScheduleBlockUpdate, and sound.ShulkerBoxClose when
making the changes.
- Around line 188-198: Replace the magic 0.35 literal with a named constant and
make the single-element BBox usage explicit: define a file-level constant
shulkerPushMargin (e.g., const shulkerPushMargin = 0.35) with a short comment
explaining it’s the extra margin to cover entity hitboxes around the extended
lid, then in pushEntities use the single returned bounding box explicitly (bbox
:= s.Model().BBox(pos, tx)[0]) and call
bbox.Translate(pos.Vec3()).Grow(shulkerPushMargin) so the intent and safety of
the single-element BBox and the margin are clear.

In `@server/item/optional_colour.go`:
- Around line 41-45: The slice length in OptionalColours is hardcoded to 17;
change it to derive from the colours slice so it stays correct when dyes change:
allocate optionalColours with length len(colours)+1 (since the loop writes at
index i+1), then populate with NewOptionalColour(c) in the existing loop over
colours; update OptionalColours, referencing the colours variable and
NewOptionalColour accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fc065ec6-85aa-46f4-b872-45aa53834ace

📥 Commits

Reviewing files that changed from the base of the PR and between b4a4d55 and 5e67ecd.

📒 Files selected for processing (18)
  • cmd/blockhash/main.go
  • server/block/cube/face.go
  • server/block/hash.go
  • server/block/model/shulker.go
  • server/block/model/stair.go
  • server/block/register.go
  • server/block/shulker_box.go
  • server/internal/nbtconv/item.go
  • server/item/optional_colour.go
  • server/player/handler.go
  • server/player/player.go
  • server/session/controllable.go
  • server/session/handler_player_auth_input.go
  • server/session/player.go
  • server/session/session.go
  • server/session/world.go
  • server/world/sound/block.go
  • server/world/viewer.go

Comment thread server/block/model/shulker.go
Comment thread server/block/shulker_box.go
Comment thread server/block/shulker_box.go
Comment thread server/block/shulker_box.go Outdated
Comment thread server/item/optional_colour.go
Comment thread server/item/optional_colour.go
Comment thread server/block/cube/face.go Outdated
Comment thread server/session/world.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/session/world.go`:
- Around line 222-230: The position flags currently test against zero; change
them to compare previous vs new coordinates so flags are set whenever a
coordinate changed (even if the new value is 0). In the method where flags are
built (world.go, the MoveActorDelta assembly code using targetPos and
currentPos/currentPosVec), replace checks like !mgl64.FloatEqual(targetPos.X(),
0) with change-detection against the previous value (e.g.
!mgl64.FloatEqual(targetPos.X(), currentPos.X())), and do the same for Y and Z,
leaving the existing rotation-change checks (rot.Pitch() != currentRot.Pitch()
etc.) as-is; ensure you still set packet.MoveActorDeltaFlagHasX / HasY / HasZ
when differences are detected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 98d73c4b-4fb8-4720-b029-4b18f8d9c42b

📥 Commits

Reviewing files that changed from the base of the PR and between 5e67ecd and 5b7a40b.

📒 Files selected for processing (3)
  • server/block/shulker_box.go
  • server/session/world.go
  • server/world/viewer.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • server/world/viewer.go
  • server/block/shulker_box.go

Comment thread server/session/world.go

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f29b359. Configure here.

Comment thread server/block/cube/face.go Outdated
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.

4 participants