forked from df-mc/dragonfly
-
Notifications
You must be signed in to change notification settings - Fork 0
Implement shulker boxes #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
HashimTheArab
wants to merge
59
commits into
HashimTheArab:master
Choose a base branch
from
mmm545:feature/shulker-box
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
59 commits
Select commit
Hold shift + click to select a range
809039a
Implement shulker boxes (no behaviour)
mmm545 d93e10e
Forgot BreakInfo
mmm545 180fd74
Oops
mmm545 dc2f99b
Implement most of shulker box functionality
mmm545 58f7dbb
Oops
mmm545 5bba5e7
Ignore inspection
mmm545 d955b44
Implement custom names
mmm545 c42eb4d
Fixed items disappearing after breaking shulker box
mmm545 60313b9
Make shulker box unstackable
mmm545 bd0063d
Gotta learn how to type good code
mmm545 8e3c84e
Fixed inability to place colored shulker boxes
mmm545 6e2f173
Add docs
mmm545 fd50d99
Refactore allShulkerBox to allShulkerBoxes
mmm545 7655008
Fixed doc typo
mmm545 12091d7
Fixed the sound and closing action being out of sync
mmm545 1f7c0ba
Add missing doc for ScheduledTick
mmm545 3018fb6
Fixed doc typo for CustomName field
mmm545 6c6dd6b
Validate that the shulker box actaully exists
mmm545 828690d
Make shulker boxes transparent and water loggable
mmm545 da2d235
Merge branch 'feature/shulker-box' of https://github.com/mmm545/drago…
mmm545 cb436a9
Remove unnecessary withBlastResistance()
mmm545 5209327
Merge branch 'master' into feature/shulker-box
mmm545 87b507a
Update tx.ScheduleBlockUpdate()
mmm545 e2c8418
Add ShulkerBox model, fix conflict
152a97f
Merge branch 'df-mc:master' into feature/shulker-box
Superomarking 4b2f6e7
Re-added sounds
e479142
Fix bounding box
689fa9b
Merge branch 'master' into feature/shulker-box
Superomarking 09d3f5c
Merge branch 'df-mc:master' into feature/shulker-box
Superomarking 27c31c7
fix doc
06f2d86
Merge remote-tracking branch 'origin/feature/shulker-box' into featur…
e6a30f2
Merge branch 'df-mc:master' into feature/shulker-box
Superomarking 9cb1de1
Merge branch 'df-mc:master' into feature/shulker-box
Superomarking 795c06f
Merge remote-tracking branch 'upstream' into feature/shulker-box
didntpot c13e884
Merge remote-tracking branch 'upstream' into feature/shulker-box
didntpot 80ad7bf
feature/shulker-box: Implement entity pushing, slight clean-up.
didntpot 5939ca6
Merge branch 'master' into feature/shulker-box
HashimTheArab 602c4c9
simplify code and fix things
HashimTheArab 5a9dcfb
code improvements
HashimTheArab 27a9cad
feat: add optionalcolour abstraction
HashimTheArab f57051a
rename var
HashimTheArab 2943189
Merge branch 'master' into feature/shulker-box
HashimTheArab d1db73e
Merge branch 'master' into feature/shulker-box
HashimTheArab 3652b9e
Update server/block/shulker_box.go
HashimTheArab a47e8e3
address review comments
HashimTheArab 92f32b8
docs
HashimTheArab 20e2a6f
rename forcemove to displace and add handler
HashimTheArab 1e29aa1
various changes
HashimTheArab 86fc952
fmt
HashimTheArab 402ceea
movement change
HashimTheArab 5e67ecd
Merge branch 'master' into feature/shulker-box
didntpot 9201f30
revert move thing
HashimTheArab 4d6646a
simplify code
HashimTheArab f84e5cb
fix shit code
HashimTheArab 5b7a40b
simplify code (verified working)
HashimTheArab fd06799
fix -0
HashimTheArab f29b359
fix: proper inventory validation check
HashimTheArab 2cdbb6d
remove cube face offset
HashimTheArab daab568
fix incorrect bbox
HashimTheArab File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package model | ||
|
|
||
| import ( | ||
| "github.com/df-mc/dragonfly/server/block/cube" | ||
| "github.com/df-mc/dragonfly/server/world" | ||
| ) | ||
|
|
||
| // Shulker is the model of a shulker box. The bounding box grows along the | ||
| // facing axis as the lid opens. | ||
| type Shulker struct { | ||
| // Facing is the direction that the lid opens towards. | ||
| Facing cube.Face | ||
| // Progress is the lid animation progress, ranging from 0 (closed) to 10 (fully open). | ||
| Progress int32 | ||
| } | ||
|
|
||
| // BBox returns a single bounding box that extends outward along Facing as the | ||
| // lid opens. | ||
| func (s Shulker) BBox(cube.Pos, world.BlockSource) []cube.BBox { | ||
| peak := ShulkerPhysicalPeak(s.Progress) | ||
| return []cube.BBox{full.ExtendTowards(s.Facing, peak)} | ||
| } | ||
|
|
||
| // ShulkerPhysicalPeak returns the lid extension along the facing axis for a | ||
| // given Progress in [0, 10]. The curve eases out cubically so the lid moves | ||
| // quickly and settles. | ||
| func ShulkerPhysicalPeak(progress int32) float64 { | ||
| t := float64(progress) / 10.0 | ||
| return (1.0 - (1.0-t)*(1.0-t)*(1.0-t)) * 0.5 | ||
| } | ||
|
|
||
| // FaceSolid always returns false. | ||
| func (Shulker) FaceSolid(cube.Pos, cube.Face, world.BlockSource) bool { | ||
| return false | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,294 @@ | ||
| package block | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "math/rand/v2" | ||
| "strings" | ||
| "sync" | ||
| "sync/atomic" | ||
|
|
||
| "github.com/df-mc/dragonfly/server/block/cube" | ||
| "github.com/df-mc/dragonfly/server/block/model" | ||
| "github.com/df-mc/dragonfly/server/internal/nbtconv" | ||
| "github.com/df-mc/dragonfly/server/item" | ||
| "github.com/df-mc/dragonfly/server/item/inventory" | ||
| "github.com/df-mc/dragonfly/server/world" | ||
| "github.com/df-mc/dragonfly/server/world/sound" | ||
| "github.com/go-gl/mathgl/mgl64" | ||
| ) | ||
|
|
||
| const ( | ||
| shulkerStateClosed int32 = iota | ||
| shulkerStateOpening | ||
| shulkerStateOpened | ||
| shulkerStateClosing | ||
| ) | ||
|
|
||
| // shulkerLidTicks is the number of scheduled ticks between fully closed and fully open. | ||
| const shulkerLidTicks int32 = 10 | ||
|
|
||
| // ShulkerBox is a dye-able block that stores items. Unlike other blocks, it keeps its contents when broken. | ||
| type ShulkerBox struct { | ||
| transparent | ||
| sourceWaterDisplacer | ||
|
|
||
| // Colour is the colour of the shulker box. A zero OptionalColour represents | ||
| // the undyed variant (minecraft:undyed_shulker_box). | ||
| Colour item.OptionalColour | ||
| // Facing is the direction that the shulker box is facing. | ||
| Facing cube.Face | ||
| // CustomName is the custom name of the shulker box. This name is displayed when the shulker box is opened, and may | ||
| // include colour codes. | ||
| CustomName string | ||
|
|
||
| inventory *inventory.Inventory | ||
| viewerMu *sync.RWMutex | ||
| viewers map[ContainerViewer]struct{} | ||
| // progress is the lid opening progress in [0, 10]. | ||
| progress *atomic.Int32 | ||
| // animationStatus is the current openness state of the shulker box (whether it's opened, closing, etc.). | ||
| animationStatus *atomic.Int32 | ||
| } | ||
|
|
||
| // NewShulkerBox creates a new initialised shulker box. The inventory is properly initialised. | ||
| func NewShulkerBox() ShulkerBox { | ||
| s := ShulkerBox{ | ||
| viewerMu: new(sync.RWMutex), | ||
| viewers: make(map[ContainerViewer]struct{}, 1), | ||
| progress: new(atomic.Int32), | ||
| animationStatus: new(atomic.Int32), | ||
| } | ||
|
|
||
| s.inventory = inventory.New(27, func(slot int, _, after item.Stack) { | ||
| s.viewerMu.RLock() | ||
| defer s.viewerMu.RUnlock() | ||
| for viewer := range s.viewers { | ||
| viewer.ViewSlotChange(slot, after) | ||
| } | ||
|
HashimTheArab marked this conversation as resolved.
|
||
| }) | ||
|
HashimTheArab marked this conversation as resolved.
|
||
| s.inventory.SlotValidatorFunc(canStoreInShulkerBox) | ||
|
|
||
| return s | ||
| } | ||
|
|
||
| // canStoreInShulkerBox rejects nested shulker boxes. | ||
| func canStoreInShulkerBox(s item.Stack, _ int) bool { | ||
| if s.Empty() { | ||
| return true | ||
| } | ||
| _, nested := s.Item().(ShulkerBox) | ||
| return !nested | ||
| } | ||
|
|
||
| func (s ShulkerBox) Model() world.BlockModel { | ||
| return model.Shulker{Facing: s.Facing, Progress: s.progress.Load()} | ||
| } | ||
|
|
||
| func (s ShulkerBox) WithName(a ...any) world.Item { | ||
| s.CustomName = strings.TrimSuffix(fmt.Sprintln(a...), "\n") | ||
| return s | ||
| } | ||
|
|
||
| func (s ShulkerBox) AddViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) { | ||
| s.viewerMu.Lock() | ||
| defer s.viewerMu.Unlock() | ||
| if len(s.viewers) == 0 { | ||
| s.open(tx, pos) | ||
| } | ||
| s.viewers[v] = struct{}{} | ||
| } | ||
|
|
||
| func (s ShulkerBox) RemoveViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) { | ||
| s.viewerMu.Lock() | ||
| defer s.viewerMu.Unlock() | ||
| if len(s.viewers) == 0 { | ||
| return | ||
| } | ||
| delete(s.viewers, v) | ||
| if len(s.viewers) == 0 { | ||
| s.close(tx, pos) | ||
| } | ||
| } | ||
|
|
||
| func (s ShulkerBox) Inventory(*world.Tx, cube.Pos) *inventory.Inventory { | ||
| return s.inventory | ||
| } | ||
|
|
||
| func (s ShulkerBox) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.User, _ *item.UseContext) bool { | ||
| opener, ok := u.(ContainerOpener) | ||
| if !ok { | ||
| return false | ||
| } | ||
| if d, ok := tx.Block(pos.Side(s.Facing)).(LightDiffuser); ok && d.LightDiffusionLevel() <= 2 { | ||
| opener.OpenBlockContainer(pos, tx) | ||
| } | ||
| return true | ||
| } | ||
|
|
||
| func (s ShulkerBox) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { | ||
| pos, _, used = firstReplaceable(tx, pos, face, s) | ||
| if !used { | ||
| return | ||
| } | ||
| s = s.initialised() | ||
| s.Facing = face | ||
| place(tx, pos, s, user, ctx) | ||
| return placed(ctx) | ||
| } | ||
|
|
||
| // initialised lazily populates runtime fields on values created via struct | ||
| // literal (e.g. those returned from allShulkerBoxes). | ||
| func (s ShulkerBox) initialised() ShulkerBox { | ||
| if s.inventory != nil { | ||
| return s | ||
| } | ||
| n := NewShulkerBox() | ||
| n.Colour, n.Facing, n.CustomName = s.Colour, s.Facing, s.CustomName | ||
| return n | ||
| } | ||
|
|
||
| // open opens the shulker box, displaying the animation and playing a sound. | ||
| func (s ShulkerBox) open(tx *world.Tx, pos cube.Pos) { | ||
| s.animationStatus.Store(shulkerStateOpening) | ||
| for _, v := range tx.Viewers(pos.Vec3()) { | ||
| v.ViewBlockAction(pos, OpenAction{}) | ||
| } | ||
| tx.PlaySound(pos.Vec3Centre(), sound.ShulkerBoxOpen{}) | ||
| tx.ScheduleBlockUpdate(pos, s, 0) | ||
| } | ||
|
|
||
| // close closes the shulker box, displaying the animation and playing a sound. | ||
| func (s ShulkerBox) close(tx *world.Tx, pos cube.Pos) { | ||
| s.animationStatus.Store(shulkerStateClosing) | ||
| for _, v := range tx.Viewers(pos.Vec3()) { | ||
| v.ViewBlockAction(pos, CloseAction{}) | ||
| } | ||
| tx.ScheduleBlockUpdate(pos, s, 0) | ||
| } | ||
|
|
||
| func (s ShulkerBox) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { | ||
| switch s.animationStatus.Load() { | ||
| case shulkerStateClosed: | ||
| s.progress.Store(0) | ||
| case shulkerStateOpening: | ||
| s.progress.Add(1) | ||
| s.pushEntities(pos, tx) | ||
| if s.progress.Load() >= shulkerLidTicks { | ||
| s.progress.Store(shulkerLidTicks) | ||
| s.animationStatus.Store(shulkerStateOpened) | ||
| } | ||
| tx.ScheduleBlockUpdate(pos, s, 0) | ||
| case shulkerStateOpened: | ||
| s.progress.Store(shulkerLidTicks) | ||
| case shulkerStateClosing: | ||
| s.progress.Add(-1) | ||
| if s.progress.Load() <= 0 { | ||
| tx.PlaySound(pos.Vec3Centre(), sound.ShulkerBoxClose{}) | ||
| s.progress.Store(0) | ||
| s.animationStatus.Store(shulkerStateClosed) | ||
| } | ||
| tx.ScheduleBlockUpdate(pos, s, 0) | ||
| } | ||
| } | ||
|
|
||
| // pushEntities pushes all entities touching the shulker box lid during opening. | ||
| func (s ShulkerBox) pushEntities(pos cube.Pos, tx *world.Tx) { | ||
| shulkerBBoxes := s.Model().BBox(pos, tx) | ||
| if len(shulkerBBoxes) == 0 { | ||
| return | ||
| } | ||
| searchBox := shulkerBBoxes[0].Translate(pos.Vec3()).Grow(0.35) | ||
| for e := range tx.EntitiesWithin(searchBox) { | ||
| s.push(pos, tx, e) | ||
| } | ||
| } | ||
|
|
||
| // push pushes entities when the shulker box lid is opening. | ||
| func (s ShulkerBox) push(pos cube.Pos, tx *world.Tx, e world.Entity) { | ||
| if s.animationStatus.Load() != shulkerStateOpening { | ||
| return | ||
| } | ||
| mover, ok := e.(interface { | ||
| Displace(deltaPos mgl64.Vec3, deltaYaw, deltaPitch float64) | ||
| }) | ||
| if !ok { | ||
| return | ||
| } | ||
|
HashimTheArab marked this conversation as resolved.
|
||
| shulkerBBoxes := s.Model().BBox(pos, tx) | ||
| if len(shulkerBBoxes) == 0 { | ||
| return | ||
| } | ||
| shulkerBBox := shulkerBBoxes[0].Translate(pos.Vec3()) | ||
| entityBBox := e.H().Type().BBox(e).Translate(e.Position()) | ||
| if !shulkerBBox.IntersectsWith(entityBBox) { | ||
| return | ||
| } | ||
|
|
||
| // Move the entity out along the lid's facing axis by the penetration depth | ||
| // between the shulker lid box and the entity box. | ||
| var delta mgl64.Vec3 | ||
| switch s.Facing { | ||
| case cube.FaceUp: | ||
| delta[1] = shulkerBBox.Max().Y() - entityBBox.Min().Y() | ||
| case cube.FaceEast: | ||
| delta[0] = shulkerBBox.Max().X() - entityBBox.Min().X() | ||
| case cube.FaceWest: | ||
| delta[0] = shulkerBBox.Min().X() - entityBBox.Max().X() | ||
| case cube.FaceSouth: | ||
| delta[2] = shulkerBBox.Max().Z() - entityBBox.Min().Z() | ||
| case cube.FaceNorth: | ||
| delta[2] = shulkerBBox.Min().Z() - entityBBox.Max().Z() | ||
| } | ||
| if delta != (mgl64.Vec3{}) { | ||
| mover.Displace(delta, 0, 0) | ||
| } | ||
| } | ||
|
|
||
| func (s ShulkerBox) BreakInfo() BreakInfo { | ||
| return newBreakInfo(2, alwaysHarvestable, pickaxeEffective, oneOf(s)) | ||
| } | ||
|
|
||
| func (s ShulkerBox) MaxCount() int { | ||
| return 1 | ||
| } | ||
|
|
||
| func (s ShulkerBox) EncodeBlock() (name string, properties map[string]any) { | ||
| if c, ok := s.Colour.Colour(); ok { | ||
| return "minecraft:" + c.String() + "_shulker_box", nil | ||
| } | ||
| return "minecraft:undyed_shulker_box", nil | ||
| } | ||
|
|
||
| func (s ShulkerBox) EncodeItem() (id string, meta int16) { | ||
| name, _ := s.EncodeBlock() | ||
| return name, 0 | ||
| } | ||
|
|
||
| func (s ShulkerBox) DecodeNBT(data map[string]any) any { | ||
| s = s.initialised() | ||
| nbtconv.InvFromNBT(s.inventory, nbtconv.Slice(data, "Items")) | ||
| s.Facing = cube.Face(nbtconv.Uint8(data, "facing")) | ||
| s.CustomName = nbtconv.String(data, "CustomName") | ||
| return s | ||
| } | ||
|
|
||
| func (s ShulkerBox) EncodeNBT() map[string]any { | ||
| s = s.initialised() | ||
| m := map[string]any{ | ||
| "Items": nbtconv.InvToNBT(s.inventory), | ||
| "id": "ShulkerBox", | ||
| "facing": uint8(s.Facing), | ||
| } | ||
| if s.CustomName != "" { | ||
| m["CustomName"] = s.CustomName | ||
| } | ||
| return m | ||
| } | ||
|
|
||
| // allShulkerBoxes returns one shulker box per item.OptionalColour, including the undyed variant. | ||
| func allShulkerBoxes() (boxes []world.Block) { | ||
| for _, c := range item.OptionalColours() { | ||
| boxes = append(boxes, ShulkerBox{Colour: c}) | ||
| } | ||
| return boxes | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.