From a13eb389cec57678e6e90d207196b64fb10ac96f Mon Sep 17 00:00:00 2001 From: Sandertv Date: Wed, 16 Oct 2024 21:03:29 +0200 Subject: [PATCH] Refactored a ton of blocks/items to use world.Tx instead of world.World. --- server/block/anvil.go | 16 +-- server/block/banner.go | 26 ++--- server/block/barrel.go | 36 +++---- server/block/barrier.go | 2 +- server/block/basalt.go | 6 +- server/block/beacon.go | 28 ++--- server/block/beetroot_seeds.go | 24 ++--- server/block/blast_furnace.go | 20 ++-- server/block/block.go | 44 ++++---- server/block/bone.go | 6 +- server/block/break_info.go | 4 +- server/block/cactus.go | 42 ++++---- server/block/cake.go | 26 ++--- server/block/campfire.go | 58 +++++------ server/block/carpet.go | 18 ++-- server/block/carrot.go | 28 ++--- server/block/chain.go | 8 +- server/block/chest.go | 70 ++++++------- server/block/cocoa_bean.go | 24 ++--- server/block/composter.go | 44 ++++---- server/block/concrete_powder.go | 12 +-- server/block/container.go | 6 +- server/block/coral.go | 30 +++--- server/block/coral_block.go | 12 +-- server/block/crafting_table.go | 2 +- server/block/crop.go | 28 ++--- server/block/cube/trace/block.go | 4 +- server/block/cube/trace/result.go | 8 +- server/block/deadbush.go | 20 ++-- server/block/decorated_pot.go | 6 +- server/block/deepslate.go | 6 +- server/block/dirt_path.go | 6 +- server/block/double_flower.go | 38 +++---- server/block/double_tall_grass.go | 32 +++--- server/block/dragon_egg.go | 26 ++--- server/block/enchanting_table.go | 4 +- server/block/ender_chest.go | 30 +++--- server/block/explosion.go | 45 ++++---- server/block/farmland.go | 28 ++--- server/block/fern.go | 24 ++--- server/block/fire.go | 126 +++++++++++------------ server/block/flower.go | 28 ++--- server/block/froglight.go | 6 +- server/block/furnace.go | 20 ++-- server/block/glass_pane.go | 2 +- server/block/glazed_terracotta.go | 6 +- server/block/grass.go | 20 ++-- server/block/gravel.go | 4 +- server/block/grindstone.go | 18 ++-- server/block/hay_bale.go | 8 +- server/block/hopper.go | 56 +++++----- server/block/iron_bars.go | 2 +- server/block/item_frame.go | 38 +++---- server/block/jukebox.go | 30 +++--- server/block/kelp.go | 48 ++++----- server/block/ladder.go | 24 ++--- server/block/lantern.go | 26 ++--- server/block/lava.go | 56 +++++----- server/block/leaves.go | 30 +++--- server/block/lectern.go | 26 ++--- server/block/light.go | 2 +- server/block/liquid.go | 86 ++++++++-------- server/block/lit_pumpkin.go | 6 +- server/block/log.go | 6 +- server/block/loom.go | 8 +- server/block/melon_seeds.go | 40 +++---- server/block/moss_carpet.go | 16 +-- server/block/muddy_mangrove_roots.go | 6 +- server/block/nether_brick_fence.go | 2 +- server/block/nether_sprouts.go | 14 +-- server/block/nether_wart.go | 18 ++-- server/block/note.go | 18 ++-- server/block/potato.go | 26 ++--- server/block/pumpkin.go | 6 +- server/block/pumpkin_seeds.go | 40 +++---- server/block/purpur.go | 6 +- server/block/quartz.go | 6 +- server/block/sand.go | 4 +- server/block/sea_pickle.go | 48 ++++----- server/block/short_grass.go | 24 ++--- server/block/sign.go | 34 +++--- server/block/skull.go | 8 +- server/block/slab.go | 16 +-- server/block/smelter.go | 16 +-- server/block/smithing_table.go | 2 +- server/block/smoker.go | 20 ++-- server/block/sponge.go | 30 +++--- server/block/spore_blossom.go | 16 +-- server/block/stained_glass_pane.go | 2 +- server/block/stairs.go | 10 +- server/block/stonecutter.go | 8 +- server/block/sugar_cane.go | 50 ++++----- server/block/tnt.go | 20 ++-- server/block/torch.go | 20 ++-- server/block/wall.go | 40 +++---- server/block/water.go | 42 ++++---- server/block/wheat_seeds.go | 24 ++--- server/block/wood.go | 6 +- server/block/wood_door.go | 48 ++++----- server/block/wood_fence.go | 2 +- server/block/wood_fence_gate.go | 30 +++--- server/block/wood_trapdoor.go | 16 +-- server/entity/bottle_of_enchanting.go | 4 +- server/entity/falling_block_behaviour.go | 20 ++-- server/entity/projectile.go | 2 +- server/entity/splashable.go | 4 +- server/item/apple.go | 2 +- server/item/axe.go | 8 +- server/item/baked_potato.go | 2 +- server/item/beef.go | 2 +- server/item/beetroot.go | 2 +- server/item/beetroot_soup.go | 2 +- server/item/bone_meal.go | 8 +- server/item/boots.go | 2 +- server/item/bottle_of_enchanting.go | 8 +- server/item/bow.go | 5 +- server/item/bread.go | 2 +- server/item/bucket.go | 24 ++--- server/item/chestplate.go | 2 +- server/item/chicken.go | 2 +- server/item/cod.go | 2 +- server/item/cookie.go | 2 +- server/item/dried_kelp.go | 2 +- server/item/dye.go | 6 +- server/item/egg.go | 8 +- server/item/elytra.go | 2 +- server/item/enchanted_apple.go | 2 +- server/item/ender_pearl.go | 8 +- server/item/fire_charge.go | 14 +-- server/item/firework.go | 18 ++-- server/item/flint_and_steel.go | 14 +-- server/item/glass_bottle.go | 6 +- server/item/goat_horn.go | 10 +- server/item/golden_apple.go | 2 +- server/item/golden_carrot.go | 2 +- server/item/helmet.go | 2 +- server/item/hoe.go | 10 +- server/item/honeycomb.go | 8 +- server/item/ink_sac.go | 6 +- server/item/item.go | 12 +-- server/item/leggings.go | 2 +- server/item/lingering_potion.go | 8 +- server/item/melon_slice.go | 2 +- server/item/mushroom_stew.go | 2 +- server/item/mutton.go | 2 +- server/item/poisonous_potato.go | 2 +- server/item/porkchop.go | 2 +- server/item/potion.go | 2 +- server/item/pufferfish.go | 2 +- server/item/pumpkin_pie.go | 2 +- server/item/rabbit.go | 2 +- server/item/rabbit_stew.go | 2 +- server/item/rotten_flesh.go | 2 +- server/item/salmon.go | 2 +- server/item/shears.go | 6 +- server/item/shovel.go | 10 +- server/item/snowball.go | 8 +- server/item/spider_eye.go | 2 +- server/item/splash_potion.go | 8 +- server/item/suspicious_stew.go | 2 +- server/item/tropical_fish.go | 2 +- server/item/turtle_shell.go | 2 +- server/player/player.go | 2 +- server/session/session.go | 8 ++ server/world/block.go | 14 +-- server/world/conf.go | 4 +- server/world/entity.go | 2 +- server/world/loader.go | 45 ++++---- server/world/tick.go | 28 +++-- server/world/weather.go | 22 ++-- server/world/world.go | 54 +++++----- 171 files changed, 1401 insertions(+), 1402 deletions(-) diff --git a/server/block/anvil.go b/server/block/anvil.go index 7a903d083..b26440375 100644 --- a/server/block/anvil.go +++ b/server/block/anvil.go @@ -31,7 +31,7 @@ func (a Anvil) BreakInfo() BreakInfo { } // Activate ... -func (Anvil) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.User, _ *item.UseContext) bool { +func (Anvil) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { if opener, ok := u.(ContainerOpener); ok { opener.OpenBlockContainer(pos) return true @@ -40,19 +40,19 @@ func (Anvil) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.User, _ } // UseOnBlock ... -func (a Anvil) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, _, used = firstReplaceable(w, pos, face, a) +func (a Anvil) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, a) if !used { return } a.Facing = user.Rotation().Direction().RotateLeft() - place(w, pos, a, user, ctx) + place(tx, pos, a, user, ctx) return placed(ctx) } // NeighbourUpdateTick ... -func (a Anvil) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - a.fall(a, pos, w) +func (a Anvil) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + a.fall(a, pos, tx) } // Damage returns the damage per block fallen of the anvil and the maximum damage the anvil can deal. @@ -75,8 +75,8 @@ func (a Anvil) Break() world.Block { } // Landed is called when a falling anvil hits the ground, used to, for example, play a sound. -func (Anvil) Landed(w *world.World, pos cube.Pos) { - w.PlaySound(pos.Vec3Centre(), sound.AnvilLand{}) +func (Anvil) Landed(tx *world.Tx, pos cube.Pos) { + tx.PlaySound(pos.Vec3Centre(), sound.AnvilLand{}) } // EncodeItem ... diff --git a/server/block/banner.go b/server/block/banner.go index ad265fa2c..e92067c6d 100644 --- a/server/block/banner.go +++ b/server/block/banner.go @@ -41,36 +41,36 @@ func (Banner) FuelInfo() item.FuelInfo { } // UseOnBlock ... -func (b Banner) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, face, used = firstReplaceable(w, pos, face, b) +func (b Banner) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, face, used = firstReplaceable(tx, pos, face, b) if !used || face == cube.FaceDown { return false } if face == cube.FaceUp { b.Attach = StandingAttachment(user.Rotation().Orientation().Opposite()) - place(w, pos, b, user, ctx) + place(tx, pos, b, user, ctx) return } b.Attach = WallAttachment(face.Direction()) - place(w, pos, b, user, ctx) + place(tx, pos, b, user, ctx) return placed(ctx) } // NeighbourUpdateTick ... -func (b Banner) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { +func (b Banner) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { if b.Attach.hanging { - if _, ok := w.Block(pos.Side(b.Attach.facing.Opposite().Face())).(Air); ok { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: b}) - dropItem(w, item.NewStack(b, 1), pos.Vec3Centre()) + if _, ok := tx.Block(pos.Side(b.Attach.facing.Opposite().Face())).(Air); ok { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: b}) + dropItem(tx, item.NewStack(b, 1), pos.Vec3Centre()) } return } - if _, ok := w.Block(pos.Side(cube.FaceDown)).(Air); ok { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: b}) - dropItem(w, item.NewStack(b, 1), pos.Vec3Centre()) + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(Air); ok { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: b}) + dropItem(tx, item.NewStack(b, 1), pos.Vec3Centre()) } } diff --git a/server/block/barrel.go b/server/block/barrel.go index 17847970c..c3bedfcca 100644 --- a/server/block/barrel.go +++ b/server/block/barrel.go @@ -51,7 +51,7 @@ func NewBarrel() Barrel { } // Inventory returns the inventory of the barrel. The size of the inventory will be 27. -func (b Barrel) Inventory(*world.World, cube.Pos) *inventory.Inventory { +func (b Barrel) Inventory(*world.Tx, cube.Pos) *inventory.Inventory { return b.inventory } @@ -62,32 +62,32 @@ func (b Barrel) WithName(a ...any) world.Item { } // open opens the barrel, displaying the animation and playing a sound. -func (b Barrel) open(w *world.World, pos cube.Pos) { +func (b Barrel) open(tx *world.Tx, pos cube.Pos) { b.Open = true - w.PlaySound(pos.Vec3Centre(), sound.BarrelOpen{}) - w.SetBlock(pos, b, nil) + tx.PlaySound(pos.Vec3Centre(), sound.BarrelOpen{}) + tx.SetBlock(pos, b, nil) } // close closes the barrel, displaying the animation and playing a sound. -func (b Barrel) close(w *world.World, pos cube.Pos) { +func (b Barrel) close(tx *world.Tx, pos cube.Pos) { b.Open = false - w.PlaySound(pos.Vec3Centre(), sound.BarrelClose{}) - w.SetBlock(pos, b, nil) + tx.PlaySound(pos.Vec3Centre(), sound.BarrelClose{}) + tx.SetBlock(pos, b, nil) } // AddViewer adds a viewer to the barrel, so that it is updated whenever the inventory of the barrel is changed. -func (b Barrel) AddViewer(v ContainerViewer, w *world.World, pos cube.Pos) { +func (b Barrel) AddViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) { b.viewerMu.Lock() defer b.viewerMu.Unlock() if len(b.viewers) == 0 { - b.open(w, pos) + b.open(tx, pos) } b.viewers[v] = struct{}{} } // RemoveViewer removes a viewer from the barrel, so that slot updates in the inventory are no longer sent to // it. -func (b Barrel) RemoveViewer(v ContainerViewer, w *world.World, pos cube.Pos) { +func (b Barrel) RemoveViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) { b.viewerMu.Lock() defer b.viewerMu.Unlock() if len(b.viewers) == 0 { @@ -95,12 +95,12 @@ func (b Barrel) RemoveViewer(v ContainerViewer, w *world.World, pos cube.Pos) { } delete(b.viewers, v) if len(b.viewers) == 0 { - b.close(w, pos) + b.close(tx, pos) } } // Activate ... -func (b Barrel) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.User, _ *item.UseContext) bool { +func (b Barrel) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { if opener, ok := u.(ContainerOpener); ok { opener.OpenBlockContainer(pos) return true @@ -109,8 +109,8 @@ func (b Barrel) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.User, } // UseOnBlock ... -func (b Barrel) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, _, used = firstReplaceable(w, pos, face, b) +func (b Barrel) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, b) if !used { return } @@ -118,15 +118,15 @@ func (b Barrel) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world. b = NewBarrel() b.Facing = calculateFace(user, pos) - place(w, pos, b, user, ctx) + place(tx, pos, b, user, ctx) return placed(ctx) } // BreakInfo ... func (b Barrel) BreakInfo() BreakInfo { - return newBreakInfo(2.5, alwaysHarvestable, axeEffective, oneOf(b)).withBreakHandler(func(pos cube.Pos, w *world.World, u item.User) { - for _, i := range b.Inventory(w, pos).Clear() { - dropItem(w, i, pos.Vec3()) + return newBreakInfo(2.5, alwaysHarvestable, axeEffective, oneOf(b)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { + for _, i := range b.Inventory(tx, pos).Clear() { + dropItem(tx, i, pos.Vec3()) } }) } diff --git a/server/block/barrier.go b/server/block/barrier.go index 2d645e02f..b74d4b1ab 100644 --- a/server/block/barrier.go +++ b/server/block/barrier.go @@ -13,7 +13,7 @@ type Barrier struct { } // SideClosed ... -func (Barrier) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (Barrier) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } diff --git a/server/block/basalt.go b/server/block/basalt.go index 47e7db466..2711849da 100644 --- a/server/block/basalt.go +++ b/server/block/basalt.go @@ -19,14 +19,14 @@ type Basalt struct { } // UseOnBlock ... -func (b Basalt) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, face, used = firstReplaceable(w, pos, face, b) +func (b Basalt) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, face, used = firstReplaceable(tx, pos, face, b) if !used { return } b.Axis = face.Axis() - place(w, pos, b, user, ctx) + place(tx, pos, b, user, ctx) return placed(ctx) } diff --git a/server/block/beacon.go b/server/block/beacon.go index e391d5e53..9dcd6a510 100644 --- a/server/block/beacon.go +++ b/server/block/beacon.go @@ -39,7 +39,7 @@ func (b Beacon) BreakInfo() BreakInfo { } // Activate manages the opening of a beacon by activating it. -func (b Beacon) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.User, _ *item.UseContext) bool { +func (b Beacon) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { if opener, ok := u.(ContainerOpener); ok { opener.OpenBlockContainer(pos) return true @@ -75,7 +75,7 @@ func (b Beacon) EncodeNBT() map[string]any { } // SideClosed ... -func (b Beacon) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (b Beacon) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } @@ -91,32 +91,32 @@ func (b Beacon) Level() int { // Tick recalculates level, recalculates the active state of the beacon, and powers players, // once every 80 ticks (4 seconds). -func (b Beacon) Tick(currentTick int64, pos cube.Pos, w *world.World) { +func (b Beacon) Tick(currentTick int64, pos cube.Pos, tx *world.Tx) { if currentTick%80 == 0 { before := b.level // Recalculating pyramid level and powering up players in range once every 4 seconds. - b.level = b.recalculateLevel(pos, w) + b.level = b.recalculateLevel(pos, tx) if before != b.level { - w.SetBlock(pos, b, nil) + tx.SetBlock(pos, b, nil) } if b.level == 0 { return } - if !b.obstructed(pos, w) { - b.broadcastBeaconEffects(pos, w) + if !b.obstructed(pos, tx) { + b.broadcastBeaconEffects(pos, tx) } } } // recalculateLevel recalculates the level of the beacon's pyramid and returns it. The level can be 0-4. -func (b Beacon) recalculateLevel(pos cube.Pos, w *world.World) int { +func (b Beacon) recalculateLevel(pos cube.Pos, tx *world.Tx) int { var lvl int iter := 1 // This loop goes over all 4 possible pyramid levels. for y := pos.Y() - 1; y >= pos.Y()-4; y-- { for x := pos.X() - iter; x <= pos.X()+iter; x++ { for z := pos.Z() - iter; z <= pos.Z()+iter; z++ { - if s, ok := w.Block(cube.Pos{x, y, z}).(BeaconSource); !ok || !s.PowersBeacon() { + if s, ok := tx.Block(cube.Pos{x, y, z}).(BeaconSource); !ok || !s.PowersBeacon() { return lvl } } @@ -128,19 +128,19 @@ func (b Beacon) recalculateLevel(pos cube.Pos, w *world.World) int { } // obstructed determines whether the beacon is currently obstructed. -func (b Beacon) obstructed(pos cube.Pos, w *world.World) bool { +func (b Beacon) obstructed(pos cube.Pos, tx *world.Tx) bool { // Fast obstructed light calculation. - if w.SkyLight(pos.Side(cube.FaceUp)) == 15 { + if tx.Skylight(pos.Side(cube.FaceUp)) == 15 { return false } // Slow obstructed light calculation, if the fast way out didn't suffice. - return w.HighestLightBlocker(pos.X(), pos.Z()) > pos[1] + return tx.HighestLightBlocker(pos.X(), pos.Z()) > pos[1] } // broadcastBeaconEffects determines the entities in range which could receive the beacon's powers, and // determines the powers (effects) that these entities could get. Afterwards, the entities in range that are // beaconAffected get their according effect(s). -func (b Beacon) broadcastBeaconEffects(pos cube.Pos, w *world.World) { +func (b Beacon) broadcastBeaconEffects(pos cube.Pos, tx *world.Tx) { seconds := 9 + b.level*2 if b.level == 4 { seconds-- @@ -184,7 +184,7 @@ func (b Beacon) broadcastBeaconEffects(pos cube.Pos, w *world.World) { // Finding entities in range. r := 10 + (b.level * 10) - entitiesInRange := w.EntitiesWithin(cube.Box( + entitiesInRange := tx.EntitiesWithin(cube.Box( float64(pos.X()-r), -math.MaxFloat64, float64(pos.Z()-r), float64(pos.X()+r), math.MaxFloat64, float64(pos.Z()+r), ), nil) diff --git a/server/block/beetroot_seeds.go b/server/block/beetroot_seeds.go index 2c0316fc4..adcd081a0 100644 --- a/server/block/beetroot_seeds.go +++ b/server/block/beetroot_seeds.go @@ -21,30 +21,30 @@ func (BeetrootSeeds) SameCrop(c Crop) bool { } // BoneMeal ... -func (b BeetrootSeeds) BoneMeal(pos cube.Pos, w *world.World) bool { +func (b BeetrootSeeds) BoneMeal(pos cube.Pos, tx *world.Tx) bool { if b.Growth == 7 { return false } if rand.Float64() < 0.75 { b.Growth++ - w.SetBlock(pos, b, nil) + tx.SetBlock(pos, b, nil) return true } return false } // UseOnBlock ... -func (b BeetrootSeeds) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, b) +func (b BeetrootSeeds) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, b) if !used { return false } - if _, ok := w.Block(pos.Side(cube.FaceDown)).(Farmland); !ok { + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(Farmland); !ok { return false } - place(w, pos, b, user, ctx) + place(tx, pos, b, user, ctx) return placed(ctx) } @@ -69,13 +69,13 @@ func (b BeetrootSeeds) EncodeItem() (name string, meta int16) { } // RandomTick ... -func (b BeetrootSeeds) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { - if w.Light(pos) < 8 { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: b}) - } else if b.Growth < 7 && r.Intn(3) > 0 && r.Float64() <= b.CalculateGrowthChance(pos, w) { +func (b BeetrootSeeds) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { + if tx.Light(pos) < 8 { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: b}) + } else if b.Growth < 7 && r.Intn(3) > 0 && r.Float64() <= b.CalculateGrowthChance(pos, tx) { b.Growth++ - w.SetBlock(pos, b, nil) + tx.SetBlock(pos, b, nil) } } diff --git a/server/block/blast_furnace.go b/server/block/blast_furnace.go index ffc7104f1..8b1925930 100644 --- a/server/block/blast_furnace.go +++ b/server/block/blast_furnace.go @@ -34,15 +34,15 @@ func NewBlastFurnace(face cube.Direction) BlastFurnace { } // Tick is called to check if the blast furnace should update and start or stop smelting. -func (b BlastFurnace) Tick(_ int64, pos cube.Pos, w *world.World) { +func (b BlastFurnace) Tick(currentTick int64, pos cube.Pos, tx *world.Tx) { if b.Lit && rand.Float64() <= 0.016 { // Every three or so seconds. - w.PlaySound(pos.Vec3Centre(), sound.BlastFurnaceCrackle{}) + tx.PlaySound(pos.Vec3Centre(), sound.BlastFurnaceCrackle{}) } if lit := b.smelter.tickSmelting(time.Second*5, time.Millisecond*200, b.Lit, func(i item.SmeltInfo) bool { return i.Ores }); b.Lit != lit { b.Lit = lit - w.SetBlock(pos, b, nil) + tx.SetBlock(pos, b, nil) } } @@ -60,28 +60,28 @@ func (b BlastFurnace) EncodeBlock() (name string, properties map[string]interfac } // UseOnBlock ... -func (b BlastFurnace) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, b) +func (b BlastFurnace) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, b) if !used { return false } - place(w, pos, NewBlastFurnace(user.Rotation().Direction().Opposite()), user, ctx) + place(tx, pos, NewBlastFurnace(user.Rotation().Direction().Opposite()), user, ctx) return placed(ctx) } // BreakInfo ... func (b BlastFurnace) BreakInfo() BreakInfo { xp := b.Experience() - return newBreakInfo(3.5, alwaysHarvestable, pickaxeEffective, oneOf(b)).withXPDropRange(xp, xp).withBreakHandler(func(pos cube.Pos, w *world.World, u item.User) { - for _, i := range b.Inventory(w, pos).Clear() { - dropItem(w, i, pos.Vec3()) + return newBreakInfo(3.5, alwaysHarvestable, pickaxeEffective, oneOf(b)).withXPDropRange(xp, xp).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { + for _, i := range b.Inventory(tx, pos).Clear() { + dropItem(tx, i, pos.Vec3()) } }) } // Activate ... -func (b BlastFurnace) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.User, _ *item.UseContext) bool { +func (b BlastFurnace) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { if opener, ok := u.(ContainerOpener); ok { opener.OpenBlockContainer(pos) return true diff --git a/server/block/block.go b/server/block/block.go index 4fa16ba14..c69f1553b 100644 --- a/server/block/block.go +++ b/server/block/block.go @@ -18,7 +18,7 @@ type Activatable interface { // Activate activates the block at a specific block position. The face clicked is passed, as well as the // world in which the block was activated and the viewer that activated it. // Activate returns a bool indicating if activating the block was used successfully. - Activate(pos cube.Pos, clickedFace cube.Face, w *world.World, u item.User, ctx *item.UseContext) bool + Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool } // Pickable represents a block that may give a different item then the block itself when picked. @@ -32,7 +32,7 @@ type Pickable interface { type Punchable interface { // Punch punches the block at a specific block position. The face clicked is passed, as well as the // world in which the block was punched and the viewer that punched it. - Punch(pos cube.Pos, clickedFace cube.Face, w *world.World, u item.User) + Punch(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User) } // LightEmitter represents a block that emits light when placed. Blocks such as torches or lanterns implement @@ -64,14 +64,14 @@ type Replaceable interface { // EntityLander represents a block that reacts to an entity landing on it after falling. type EntityLander interface { // EntityLand is called when an entity lands on the block. - EntityLand(pos cube.Pos, w *world.World, e world.Entity, distance *float64) + EntityLand(pos cube.Pos, tx *world.Tx, e world.Entity, distance *float64) } // EntityInsider represents a block that reacts to an entity going inside its 1x1x1 axis // aligned bounding box. type EntityInsider interface { // EntityInside is called when an entity goes inside the block's 1x1x1 axis aligned bounding box. - EntityInside(pos cube.Pos, w *world.World, e world.Entity) + EntityInside(pos cube.Pos, tx *world.Tx, e world.Entity) } // Frictional represents a block that may have a custom friction value. Friction is used for entity drag when the @@ -118,11 +118,11 @@ func abs(x int) int { } // replaceableWith checks if the block at the position passed is replaceable with the block passed. -func replaceableWith(w *world.World, pos cube.Pos, with world.Block) bool { - if pos.OutOfBounds(w.Range()) { +func replaceableWith(tx *world.Tx, pos cube.Pos, with world.Block) bool { + if pos.OutOfBounds(tx.Range()) { return false } - b := w.Block(pos) + b := tx.Block(pos) if replaceable, ok := b.(Replaceable); ok { return replaceable.ReplaceableBy(with) && b != with } @@ -132,14 +132,14 @@ func replaceableWith(w *world.World, pos cube.Pos, with world.Block) bool { // firstReplaceable finds the first replaceable block position eligible to have a block placed on it after // clicking on the position and face passed. // If none can be found, the bool returned is false. -func firstReplaceable(w *world.World, pos cube.Pos, face cube.Face, with world.Block) (cube.Pos, cube.Face, bool) { - if replaceableWith(w, pos, with) { +func firstReplaceable(tx *world.Tx, pos cube.Pos, face cube.Face, with world.Block) (cube.Pos, cube.Face, bool) { + if replaceableWith(tx, pos, with) { // A replaceableWith block was clicked, so we can replace it. This will then be assumed to be placed on // the top face. (Torches, for example, will get attached to the floor when clicking tall grass.) return pos, cube.FaceUp, true } side := pos.Side(face) - if replaceableWith(w, side, with) { + if replaceableWith(tx, side, with) { return side, face, true } return pos, face, false @@ -147,13 +147,13 @@ func firstReplaceable(w *world.World, pos cube.Pos, face cube.Face, with world.B // place places the block passed at the position passed. If the user implements the block.Placer interface, it // will use its PlaceBlock method. If not, the block is placed without interaction from the user. -func place(w *world.World, pos cube.Pos, b world.Block, user item.User, ctx *item.UseContext) { +func place(tx *world.Tx, pos cube.Pos, b world.Block, user item.User, ctx *item.UseContext) { if placer, ok := user.(Placer); ok { placer.PlaceBlock(pos, b, ctx) return } - w.SetBlock(pos, b, nil) - w.PlaySound(pos.Vec3(), sound.BlockPlace{Block: b}) + tx.SetBlock(pos, b, nil) + tx.PlaySound(pos.Vec3(), sound.BlockPlace{Block: b}) } // horizontalDirection returns the horizontal direction of the given direction. This is a legacy type still used in @@ -206,17 +206,17 @@ func (transparent) LightDiffusionLevel() uint8 { type gravityAffected struct{} // Solidifies ... -func (g gravityAffected) Solidifies(cube.Pos, *world.World) bool { +func (g gravityAffected) Solidifies(cube.Pos, *world.Tx) bool { return false } // fall spawns a falling block entity at the given position. -func (g gravityAffected) fall(b world.Block, pos cube.Pos, w *world.World) { - _, air := w.Block(pos.Side(cube.FaceDown)).Model().(model.Empty) - _, liquid := w.Liquid(pos.Side(cube.FaceDown)) +func (g gravityAffected) fall(b world.Block, pos cube.Pos, tx *world.Tx) { + _, air := tx.Block(pos.Side(cube.FaceDown)).Model().(model.Empty) + _, liquid := tx.Liquid(pos.Side(cube.FaceDown)) if air || liquid { - w.SetBlock(pos, nil, nil) - w.AddEntity(w.EntityRegistry().Config().FallingBlock(b, pos.Vec3Centre())) + tx.SetBlock(pos, nil, nil) + tx.AddEntity(tx.World().EntityRegistry().Config().FallingBlock(b, pos.Vec3Centre())) } } @@ -269,9 +269,9 @@ type flammableEntity interface { } // dropItem ... -func dropItem(w *world.World, it item.Stack, pos mgl64.Vec3) { - create := w.EntityRegistry().Config().Item - w.AddEntity(create(it, pos, mgl64.Vec3{rand.Float64()*0.2 - 0.1, 0.2, rand.Float64()*0.2 - 0.1})) +func dropItem(tx *world.Tx, it item.Stack, pos mgl64.Vec3) { + create := tx.World().EntityRegistry().Config().Item + tx.AddEntity(create(it, pos, mgl64.Vec3{rand.Float64()*0.2 - 0.1, 0.2, rand.Float64()*0.2 - 0.1})) } // bass is a struct that may be embedded for blocks that create a bass sound. diff --git a/server/block/bone.go b/server/block/bone.go index 9bdac8644..9063a9c6e 100644 --- a/server/block/bone.go +++ b/server/block/bone.go @@ -22,14 +22,14 @@ func (b Bone) Instrument() sound.Instrument { } // UseOnBlock handles the rotational placing of bone blocks. -func (b Bone) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, face, used = firstReplaceable(w, pos, face, b) +func (b Bone) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, face, used = firstReplaceable(tx, pos, face, b) if !used { return } b.Axis = face.Axis() - place(w, pos, b, user, ctx) + place(tx, pos, b, user, ctx) return placed(ctx) } diff --git a/server/block/break_info.go b/server/block/break_info.go index fceeb8160..dc1c5ab82 100644 --- a/server/block/break_info.go +++ b/server/block/break_info.go @@ -86,7 +86,7 @@ type BreakInfo struct { // Drops is a function called to get the drops of the block if it is broken using the item passed. Drops func(t item.Tool, enchantments []item.Enchantment) []item.Stack // BreakHandler is called after the block has broken. - BreakHandler func(pos cube.Pos, w *world.World, u item.User) + BreakHandler func(pos cube.Pos, w *world.Tx, u item.User) // XPDrops is the range of XP a block can drop when broken. XPDrops XPDropRange // BlastResistance is the blast resistance of the block, which influences the block's ability to withstand an @@ -119,7 +119,7 @@ func (b BreakInfo) withBlastResistance(res float64) BreakInfo { } // withBreakHandler sets the BreakHandler field of the BreakInfo struct to the passed value. -func (b BreakInfo) withBreakHandler(handler func(pos cube.Pos, w *world.World, u item.User)) BreakInfo { +func (b BreakInfo) withBreakHandler(handler func(pos cube.Pos, w *world.Tx, u item.User)) BreakInfo { b.BreakHandler = handler return b } diff --git a/server/block/cactus.go b/server/block/cactus.go index 7c9711b1c..d9c1dc988 100644 --- a/server/block/cactus.go +++ b/server/block/cactus.go @@ -19,63 +19,63 @@ type Cactus struct { } // UseOnBlock handles making sure the neighbouring blocks are air. -func (c Cactus) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, _, used = firstReplaceable(w, pos, face, c) +func (c Cactus) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, c) if !used { return false } - if !c.canGrowHere(pos, w, true) { + if !c.canGrowHere(pos, tx, true) { return false } - place(w, pos, c, user, ctx) + place(tx, pos, c, user, ctx) return placed(ctx) } // NeighbourUpdateTick ... -func (c Cactus) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if !c.canGrowHere(pos, w, true) { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: c}) - dropItem(w, item.NewStack(c, 1), pos.Vec3Centre()) +func (c Cactus) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if !c.canGrowHere(pos, tx, true) { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: c}) + dropItem(tx, item.NewStack(c, 1), pos.Vec3Centre()) } } // RandomTick ... -func (c Cactus) RandomTick(pos cube.Pos, w *world.World, _ *rand.Rand) { +func (c Cactus) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { if c.Age < 15 { c.Age++ } else if c.Age == 15 { c.Age = 0 - if c.canGrowHere(pos.Side(cube.FaceDown), w, false) { + if c.canGrowHere(pos.Side(cube.FaceDown), tx, false) { for y := 1; y < 3; y++ { - if _, ok := w.Block(pos.Add(cube.Pos{0, y})).(Air); ok { - w.SetBlock(pos.Add(cube.Pos{0, y}), Cactus{Age: 0}, nil) + if _, ok := tx.Block(pos.Add(cube.Pos{0, y})).(Air); ok { + tx.SetBlock(pos.Add(cube.Pos{0, y}), Cactus{Age: 0}, nil) break - } else if _, ok := w.Block(pos.Add(cube.Pos{0, y})).(Cactus); !ok { + } else if _, ok := tx.Block(pos.Add(cube.Pos{0, y})).(Cactus); !ok { break } } } } - w.SetBlock(pos, c, nil) + tx.SetBlock(pos, c, nil) } // canGrowHere implements logic to check if cactus can live/grow here. -func (c Cactus) canGrowHere(pos cube.Pos, w *world.World, recursive bool) bool { +func (c Cactus) canGrowHere(pos cube.Pos, tx *world.Tx, recursive bool) bool { for _, face := range cube.HorizontalFaces() { - if _, ok := w.Block(pos.Side(face)).(Air); !ok { + if _, ok := tx.Block(pos.Side(face)).(Air); !ok { return false } } - if _, ok := w.Block(pos.Side(cube.FaceDown)).(Cactus); ok && recursive { - return c.canGrowHere(pos.Side(cube.FaceDown), w, recursive) + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(Cactus); ok && recursive { + return c.canGrowHere(pos.Side(cube.FaceDown), tx, recursive) } - return supportsVegetation(c, w.Block(pos.Sub(cube.Pos{0, 1}))) + return supportsVegetation(c, tx.Block(pos.Sub(cube.Pos{0, 1}))) } // EntityInside ... -func (c Cactus) EntityInside(_ cube.Pos, _ *world.World, e world.Entity) { +func (c Cactus) EntityInside(pos cube.Pos, tx *world.Tx, e world.Entity) { if l, ok := e.(livingEntity); ok && !l.AttackImmune() { l.Hurt(0.5, DamageSource{Block: c}) } diff --git a/server/block/cake.go b/server/block/cake.go index c1e880744..f77bc00b2 100644 --- a/server/block/cake.go +++ b/server/block/cake.go @@ -20,46 +20,46 @@ type Cake struct { } // SideClosed ... -func (c Cake) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (c Cake) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } // UseOnBlock ... -func (c Cake) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, c) +func (c Cake) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, c) if !used { return false } - if _, air := w.Block(pos.Side(cube.FaceDown)).(Air); air { + if _, air := tx.Block(pos.Side(cube.FaceDown)).(Air); air { return false } - place(w, pos, c, user, ctx) + place(tx, pos, c, user, ctx) return placed(ctx) } // NeighbourUpdateTick ... -func (c Cake) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if _, air := w.Block(pos.Side(cube.FaceDown)).(Air); air { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: c}) +func (c Cake) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if _, air := tx.Block(pos.Side(cube.FaceDown)).(Air); air { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: c}) } } // Activate ... -func (c Cake) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, _ *item.UseContext) bool { +func (c Cake) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { if i, ok := u.(interface { Saturate(food int, saturation float64) }); ok { i.Saturate(2, 0.4) - w.PlaySound(u.Position().Add(mgl64.Vec3{0, 1.5}), sound.Burp{}) + tx.PlaySound(u.Position().Add(mgl64.Vec3{0, 1.5}), sound.Burp{}) c.Bites++ if c.Bites > 6 { - w.SetBlock(pos, nil, nil) + tx.SetBlock(pos, nil, nil) return true } - w.SetBlock(pos, c, nil) + tx.SetBlock(pos, c, nil) return true } return false diff --git a/server/block/campfire.go b/server/block/campfire.go index 93e37c7ca..f2426152b 100644 --- a/server/block/campfire.go +++ b/server/block/campfire.go @@ -44,7 +44,7 @@ func (Campfire) Model() world.BlockModel { } // SideClosed ... -func (Campfire) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (Campfire) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } @@ -80,50 +80,50 @@ func (c Campfire) LightEmissionLevel() uint8 { } // Ignite ... -func (c Campfire) Ignite(pos cube.Pos, w *world.World, _ world.Entity) bool { - w.PlaySound(pos.Vec3(), sound.Ignite{}) +func (c Campfire) Ignite(pos cube.Pos, tx *world.Tx, igniter world.Entity) bool { + tx.PlaySound(pos.Vec3(), sound.Ignite{}) if !c.Extinguished { return false } - if _, ok := w.Liquid(pos); ok { + if _, ok := tx.Liquid(pos); ok { return false } c.Extinguished = false - w.SetBlock(pos, c, nil) + tx.SetBlock(pos, c, nil) return true } // Splash ... -func (c Campfire) Splash(w *world.World, pos cube.Pos) { +func (c Campfire) Splash(tx *world.Tx, pos cube.Pos) { if c.Extinguished { return } - c.extinguish(pos, w) + c.extinguish(pos, tx) } // extinguish extinguishes the campfire. -func (c Campfire) extinguish(pos cube.Pos, w *world.World) { - w.PlaySound(pos.Vec3Centre(), sound.FireExtinguish{}) +func (c Campfire) extinguish(pos cube.Pos, tx *world.Tx) { + tx.PlaySound(pos.Vec3Centre(), sound.FireExtinguish{}) c.Extinguished = true for i := range c.Items { c.Items[i].Time = time.Second * 30 } - w.SetBlock(pos, c, nil) + tx.SetBlock(pos, c, nil) } // Activate ... -func (c Campfire) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, ctx *item.UseContext) bool { +func (c Campfire) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { held, _ := u.HeldItems() if held.Empty() { return false } if _, ok := held.Item().(item.Shovel); ok && !c.Extinguished { - c.extinguish(pos, w) + c.extinguish(pos, tx) ctx.DamageItem(1) return true } @@ -142,8 +142,8 @@ func (c Campfire) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.Use ctx.SubtractFromCount(1) - w.PlaySound(pos.Vec3Centre(), sound.ItemAdd{}) - w.SetBlock(pos, c, nil) + tx.PlaySound(pos.Vec3Centre(), sound.ItemAdd{}) + tx.SetBlock(pos, c, nil) return true } } @@ -151,27 +151,27 @@ func (c Campfire) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.Use } // UseOnBlock ... -func (c Campfire) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, _, used = firstReplaceable(w, pos, face, c) +func (c Campfire) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, c) if !used { return } - if _, ok := w.Block(pos.Side(cube.FaceDown)).(Campfire); ok { + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(Campfire); ok { return false } c.Facing = user.Rotation().Direction().Opposite() - place(w, pos, c, user, ctx) + place(tx, pos, c, user, ctx) return placed(ctx) } // Tick is called to cook the items within the campfire. -func (c Campfire) Tick(_ int64, pos cube.Pos, w *world.World) { +func (c Campfire) Tick(currentTick int64, pos cube.Pos, tx *world.Tx) { if c.Extinguished { // Extinguished, do nothing. return } if rand.Float64() <= 0.016 { // Every three or so seconds. - w.PlaySound(pos.Vec3Centre(), sound.CampfireCrackle{}) + tx.PlaySound(pos.Vec3Centre(), sound.CampfireCrackle{}) } updated := false @@ -187,31 +187,31 @@ func (c Campfire) Tick(_ int64, pos cube.Pos, w *world.World) { } if food, ok := it.Item.Item().(item.Smeltable); ok { - dropItem(w, food.SmeltInfo().Product, pos.Vec3Middle()) + dropItem(tx, food.SmeltInfo().Product, pos.Vec3Middle()) } c.Items[i].Item = item.Stack{} } if updated { - w.SetBlock(pos, c, nil) + tx.SetBlock(pos, c, nil) } } // NeighbourUpdateTick ... -func (c Campfire) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - _, ok := w.Liquid(pos) - liquid, okTwo := w.Liquid(pos.Side(cube.FaceUp)) +func (c Campfire) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + _, ok := tx.Liquid(pos) + liquid, okTwo := tx.Liquid(pos.Side(cube.FaceUp)) if (ok || (okTwo && liquid.LiquidType() == "water")) && !c.Extinguished { - c.extinguish(pos, w) + c.extinguish(pos, tx) } } // EntityInside ... -func (c Campfire) EntityInside(pos cube.Pos, w *world.World, e world.Entity) { +func (c Campfire) EntityInside(pos cube.Pos, tx *world.Tx, e world.Entity) { if flammable, ok := e.(flammableEntity); ok { if flammable.OnFireDuration() > 0 && c.Extinguished { c.Extinguished = false - w.PlaySound(pos.Vec3(), sound.Ignite{}) - w.SetBlock(pos, c, nil) + tx.PlaySound(pos.Vec3(), sound.Ignite{}) + tx.SetBlock(pos, c, nil) } if !c.Extinguished { if l, ok := e.(livingEntity); ok && !l.AttackImmune() { diff --git a/server/block/carpet.go b/server/block/carpet.go index 6a1cace0d..925a0d407 100644 --- a/server/block/carpet.go +++ b/server/block/carpet.go @@ -23,7 +23,7 @@ func (c Carpet) FlammabilityInfo() FlammabilityInfo { } // SideClosed ... -func (Carpet) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (Carpet) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } @@ -48,25 +48,25 @@ func (Carpet) HasLiquidDrops() bool { } // NeighbourUpdateTick ... -func (c Carpet) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if _, ok := w.Block(pos.Side(cube.FaceDown)).(Air); ok { - w.SetBlock(pos, nil, nil) - dropItem(w, item.NewStack(c, 1), pos.Vec3Centre()) +func (c Carpet) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(Air); ok { + tx.SetBlock(pos, nil, nil) + dropItem(tx, item.NewStack(c, 1), pos.Vec3Centre()) } } // UseOnBlock handles not placing carpets on top of air blocks. -func (c Carpet) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, _, used = firstReplaceable(w, pos, face, c) +func (c Carpet) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, c) if !used { return } - if _, ok := w.Block(pos.Side(cube.FaceDown)).(Air); ok { + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(Air); ok { return } - place(w, pos, c, user, ctx) + place(tx, pos, c, user, ctx) return placed(ctx) } diff --git a/server/block/carrot.go b/server/block/carrot.go index ce9bc1e6a..6287a53d4 100644 --- a/server/block/carrot.go +++ b/server/block/carrot.go @@ -32,33 +32,33 @@ func (c Carrot) ConsumeDuration() time.Duration { } // Consume ... -func (c Carrot) Consume(_ *world.World, consumer item.Consumer) item.Stack { - consumer.Saturate(3, 3.6) +func (c Carrot) Consume(tx *world.Tx, co item.Consumer) item.Stack { + co.Saturate(3, 3.6) return item.Stack{} } // BoneMeal ... -func (c Carrot) BoneMeal(pos cube.Pos, w *world.World) bool { +func (c Carrot) BoneMeal(pos cube.Pos, tx *world.Tx) bool { if c.Growth == 7 { return false } c.Growth = min(c.Growth+rand.Intn(4)+2, 7) - w.SetBlock(pos, c, nil) + tx.SetBlock(pos, c, nil) return true } // UseOnBlock ... -func (c Carrot) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, c) +func (c Carrot) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, c) if !used { return false } - if _, ok := w.Block(pos.Side(cube.FaceDown)).(Farmland); !ok { + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(Farmland); !ok { return false } - place(w, pos, c, user, ctx) + place(tx, pos, c, user, ctx) return placed(ctx) } @@ -83,13 +83,13 @@ func (c Carrot) EncodeItem() (name string, meta int16) { } // RandomTick ... -func (c Carrot) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { - if w.Light(pos) < 8 { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: c}) - } else if c.Growth < 7 && r.Float64() <= c.CalculateGrowthChance(pos, w) { +func (c Carrot) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { + if tx.Light(pos) < 8 { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: c}) + } else if c.Growth < 7 && r.Float64() <= c.CalculateGrowthChance(pos, tx) { c.Growth++ - w.SetBlock(pos, c, nil) + tx.SetBlock(pos, c, nil) } } diff --git a/server/block/chain.go b/server/block/chain.go index 1aa0551f2..2a27e870a 100644 --- a/server/block/chain.go +++ b/server/block/chain.go @@ -18,19 +18,19 @@ type Chain struct { } // SideClosed ... -func (Chain) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (Chain) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } // UseOnBlock ... -func (c Chain) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, face, used = firstReplaceable(w, pos, face, c) +func (c Chain) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, face, used = firstReplaceable(tx, pos, face, c) if !used { return } c.Axis = face.Axis() - place(w, pos, c, user, ctx) + place(tx, pos, c, user, ctx) return placed(ctx) } diff --git a/server/block/chest.go b/server/block/chest.go index 9b3b9cdfd..b216169d9 100644 --- a/server/block/chest.go +++ b/server/block/chest.go @@ -57,16 +57,16 @@ func NewChest() Chest { // Inventory returns the inventory of the chest. The size of the inventory will be 27 or 54, depending on // whether the chest is single or double. -func (c Chest) Inventory(w *world.World, pos cube.Pos) *inventory.Inventory { +func (c Chest) Inventory(tx *world.Tx, pos cube.Pos) *inventory.Inventory { if c.paired { if c.pairInv == nil { - if ch, pair, ok := c.pair(w, pos, c.pairPos(pos)); ok { + if ch, pair, ok := c.pair(tx, pos, c.pairPos(pos)); ok { c = ch - w.SetBlock(pos, ch, nil) - w.SetBlock(c.pairPos(pos), pair, nil) + tx.SetBlock(pos, ch, nil) + tx.SetBlock(c.pairPos(pos), pair, nil) } else { c.paired = false - w.SetBlock(pos, c, nil) + tx.SetBlock(pos, c, nil) return c.inventory } } @@ -82,45 +82,45 @@ func (c Chest) WithName(a ...any) world.Item { } // SideClosed ... -func (Chest) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (Chest) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } // open opens the chest, displaying the animation and playing a sound. -func (c Chest) open(w *world.World, pos cube.Pos) { - for _, v := range w.Viewers(pos.Vec3()) { +func (c Chest) open(tx *world.Tx, pos cube.Pos) { + for _, v := range tx.Viewers(pos.Vec3()) { if c.paired { v.ViewBlockAction(c.pairPos(pos), OpenAction{}) } v.ViewBlockAction(pos, OpenAction{}) } - w.PlaySound(pos.Vec3Centre(), sound.ChestOpen{}) + tx.PlaySound(pos.Vec3Centre(), sound.ChestOpen{}) } // close closes the chest, displaying the animation and playing a sound. -func (c Chest) close(w *world.World, pos cube.Pos) { - for _, v := range w.Viewers(pos.Vec3()) { +func (c Chest) close(tx *world.Tx, pos cube.Pos) { + for _, v := range tx.Viewers(pos.Vec3()) { if c.paired { v.ViewBlockAction(c.pairPos(pos), CloseAction{}) } v.ViewBlockAction(pos, CloseAction{}) } - w.PlaySound(pos.Vec3Centre(), sound.ChestClose{}) + tx.PlaySound(pos.Vec3Centre(), sound.ChestClose{}) } // AddViewer adds a viewer to the chest, so that it is updated whenever the inventory of the chest is changed. -func (c Chest) AddViewer(v ContainerViewer, w *world.World, pos cube.Pos) { +func (c Chest) AddViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) { c.viewerMu.Lock() defer c.viewerMu.Unlock() if len(c.viewers) == 0 { - c.open(w, pos) + c.open(tx, pos) } c.viewers[v] = struct{}{} } // RemoveViewer removes a viewer from the chest, so that slot updates in the inventory are no longer sent to // it. -func (c Chest) RemoveViewer(v ContainerViewer, w *world.World, pos cube.Pos) { +func (c Chest) RemoveViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) { c.viewerMu.Lock() defer c.viewerMu.Unlock() if len(c.viewers) == 0 { @@ -128,19 +128,19 @@ func (c Chest) RemoveViewer(v ContainerViewer, w *world.World, pos cube.Pos) { } delete(c.viewers, v) if len(c.viewers) == 0 { - c.close(w, pos) + c.close(tx, pos) } } // Activate ... -func (c Chest) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, _ *item.UseContext) bool { +func (c Chest) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { if opener, ok := u.(ContainerOpener); ok { if c.paired { - if d, ok := w.Block(c.pairPos(pos).Side(cube.FaceUp)).(LightDiffuser); !ok || d.LightDiffusionLevel() > 2 { + if d, ok := tx.Block(c.pairPos(pos).Side(cube.FaceUp)).(LightDiffuser); !ok || d.LightDiffusionLevel() > 2 { return false } } - if d, ok := w.Block(pos.Side(cube.FaceUp)).(LightDiffuser); ok && d.LightDiffusionLevel() <= 2 { + if d, ok := tx.Block(pos.Side(cube.FaceUp)).(LightDiffuser); ok && d.LightDiffusionLevel() <= 2 { opener.OpenBlockContainer(pos) } return true @@ -149,8 +149,8 @@ func (c Chest) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, } // UseOnBlock ... -func (c Chest) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, _, used = firstReplaceable(w, pos, face, c) +func (c Chest) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, c) if !used { return } @@ -160,30 +160,30 @@ func (c Chest) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.W // Check both sides of the chest to see if it is possible to pair with another chest. for _, dir := range []cube.Direction{c.Facing.RotateLeft(), c.Facing.RotateRight()} { - if ch, pair, ok := c.pair(w, pos, pos.Side(dir.Face())); ok { - place(w, pos, ch, user, ctx) - w.SetBlock(ch.pairPos(pos), pair, nil) + if ch, pair, ok := c.pair(tx, pos, pos.Side(dir.Face())); ok { + place(tx, pos, ch, user, ctx) + tx.SetBlock(ch.pairPos(pos), pair, nil) return placed(ctx) } } - place(w, pos, c, user, ctx) + place(tx, pos, c, user, ctx) return placed(ctx) } // BreakInfo ... func (c Chest) BreakInfo() BreakInfo { - return newBreakInfo(2.5, alwaysHarvestable, axeEffective, oneOf(c)).withBreakHandler(func(pos cube.Pos, w *world.World, u item.User) { + return newBreakInfo(2.5, alwaysHarvestable, axeEffective, oneOf(c)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { if c.paired { pairPos := c.pairPos(pos) - if _, pair, ok := c.unpair(w, pos); ok { + if _, pair, ok := c.unpair(tx, pos); ok { c.paired = false - w.SetBlock(pairPos, pair, nil) + tx.SetBlock(pairPos, pair, nil) } } - for _, i := range c.Inventory(w, pos).Clear() { - dropItem(w, i, pos.Vec3Centre()) + for _, i := range c.Inventory(tx, pos).Clear() { + dropItem(tx, i, pos.Vec3Centre()) } }) } @@ -204,8 +204,8 @@ func (c Chest) Paired() bool { } // pair pairs this chest with the given chest position. -func (c Chest) pair(w *world.World, pos, pairPos cube.Pos) (ch, pair Chest, ok bool) { - pair, ok = w.Block(pairPos).(Chest) +func (c Chest) pair(tx *world.Tx, pos, pairPos cube.Pos) (ch, pair Chest, ok bool) { + pair, ok = tx.Block(pairPos).(Chest) if !ok || c.Facing != pair.Facing || pair.paired && (pair.pairX != pos[0] || pair.pairZ != pos[2]) { return c, pair, false } @@ -241,18 +241,18 @@ func (c Chest) pair(w *world.World, pos, pairPos cube.Pos) (ch, pair Chest, ok b } // unpair unpairs this chest from the chest it is currently paired with. -func (c Chest) unpair(w *world.World, pos cube.Pos) (ch, pair Chest, ok bool) { +func (c Chest) unpair(tx *world.Tx, pos cube.Pos) (ch, pair Chest, ok bool) { if !c.paired { return c, Chest{}, false } - pair, ok = w.Block(c.pairPos(pos)).(Chest) + pair, ok = tx.Block(c.pairPos(pos)).(Chest) if !ok || c.Facing != pair.Facing || pair.paired && (pair.pairX != pos[0] || pair.pairZ != pos[2]) { return c, pair, false } if len(c.viewers) != 0 { - c.close(w, pos) + c.close(tx, pos) } c.inventory = c.inventory.Clone(func(slot int, _, after item.Stack) { diff --git a/server/block/cocoa_bean.go b/server/block/cocoa_bean.go index 9ff3c6584..fd887d574 100644 --- a/server/block/cocoa_bean.go +++ b/server/block/cocoa_bean.go @@ -20,12 +20,12 @@ type CocoaBean struct { } // BoneMeal ... -func (c CocoaBean) BoneMeal(pos cube.Pos, w *world.World) bool { +func (c CocoaBean) BoneMeal(pos cube.Pos, tx *world.Tx) bool { if c.Age == 2 { return false } c.Age++ - w.SetBlock(pos, c, nil) + tx.SetBlock(pos, c, nil) return true } @@ -35,22 +35,22 @@ func (c CocoaBean) HasLiquidDrops() bool { } // NeighbourUpdateTick ... -func (c CocoaBean) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { +func (c CocoaBean) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { var woodType WoodType - switch b := w.Block(pos.Side(c.Facing.Face())).(type) { + switch b := tx.Block(pos.Side(c.Facing.Face())).(type) { case Log: woodType = b.Wood case Wood: woodType = b.Wood } if woodType != JungleWood() { - w.SetBlock(pos, nil, nil) + tx.SetBlock(pos, nil, nil) } } // UseOnBlock ... -func (c CocoaBean) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, c) +func (c CocoaBean) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, c) if !used { return false } @@ -61,16 +61,16 @@ func (c CocoaBean) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *wor var woodType WoodType oppositePos := pos.Side(face.Opposite()) - if log, ok := w.Block(oppositePos).(Log); ok { + if log, ok := tx.Block(oppositePos).(Log); ok { woodType = log.Wood - } else if wood, ok := w.Block(oppositePos).(Wood); ok { + } else if wood, ok := tx.Block(oppositePos).(Wood); ok { woodType = wood.Wood } if woodType == JungleWood() { c.Facing = face.Opposite().Direction() ctx.IgnoreBBox = true - place(w, pos, c, user, ctx) + place(tx, pos, c, user, ctx) return placed(ctx) } @@ -78,10 +78,10 @@ func (c CocoaBean) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *wor } // RandomTick ... -func (c CocoaBean) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { +func (c CocoaBean) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { if c.Age < 2 && r.Intn(5) == 0 { c.Age++ - w.SetBlock(pos, c, nil) + tx.SetBlock(pos, c, nil) } } diff --git a/server/block/composter.go b/server/block/composter.go index 6491ed9bc..505392e24 100644 --- a/server/block/composter.go +++ b/server/block/composter.go @@ -23,7 +23,7 @@ type Composter struct { } // InsertItem ... -func (c Composter) InsertItem(h Hopper, pos cube.Pos, w *world.World) bool { +func (c Composter) InsertItem(h Hopper, pos cube.Pos, tx *world.Tx) bool { if c.Level >= 7 || h.Facing != cube.FaceDown { return false } @@ -33,7 +33,7 @@ func (c Composter) InsertItem(h Hopper, pos cube.Pos, w *world.World) bool { continue } - if c.fill(sourceStack, pos, w) { + if c.fill(sourceStack, pos, tx) { _ = h.inventory.SetItem(sourceSlot, sourceStack.Grow(-1)) return true } @@ -43,7 +43,7 @@ func (c Composter) InsertItem(h Hopper, pos cube.Pos, w *world.World) bool { } // ExtractItem ... -func (c Composter) ExtractItem(h Hopper, pos cube.Pos, w *world.World) bool { +func (c Composter) ExtractItem(h Hopper, pos cube.Pos, tx *world.Tx) bool { if c.Level == 8 { _, err := h.inventory.AddItem(item.NewStack(item.BoneMeal{}, 1)) if err != nil { @@ -52,8 +52,8 @@ func (c Composter) ExtractItem(h Hopper, pos cube.Pos, w *world.World) bool { } c.Level = 0 - w.SetBlock(pos.Side(cube.FaceUp), c, nil) - w.PlaySound(pos.Side(cube.FaceUp).Vec3(), sound.ComposterEmpty{}) + tx.SetBlock(pos.Side(cube.FaceUp), c, nil) + tx.PlaySound(pos.Side(cube.FaceUp).Vec3(), sound.ComposterEmpty{}) return true } @@ -76,32 +76,32 @@ func (c Composter) FlammabilityInfo() FlammabilityInfo { } // SideClosed ... -func (c Composter) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (c Composter) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } // BreakInfo ... func (c Composter) BreakInfo() BreakInfo { - return newBreakInfo(2, alwaysHarvestable, axeEffective, oneOf(c)).withBreakHandler(func(pos cube.Pos, w *world.World, u item.User) { + return newBreakInfo(2, alwaysHarvestable, axeEffective, oneOf(c)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { if c.Level == 8 { - dropItem(w, item.NewStack(item.BoneMeal{}, 1), pos.Side(cube.FaceUp).Vec3Middle()) + dropItem(tx, item.NewStack(item.BoneMeal{}, 1), pos.Side(cube.FaceUp).Vec3Middle()) } }) } // Activate ... -func (c Composter) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, ctx *item.UseContext) bool { +func (c Composter) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { if c.Level >= 7 { if c.Level == 8 { c.Level = 0 - w.SetBlock(pos, c, nil) - dropItem(w, item.NewStack(item.BoneMeal{}, 1), pos.Side(cube.FaceUp).Vec3Middle()) - w.PlaySound(pos.Vec3(), sound.ComposterEmpty{}) + tx.SetBlock(pos, c, nil) + dropItem(tx, item.NewStack(item.BoneMeal{}, 1), pos.Side(cube.FaceUp).Vec3Middle()) + tx.PlaySound(pos.Vec3(), sound.ComposterEmpty{}) } return false } it, _ := u.HeldItems() - if c.fill(it, pos, w) { + if c.fill(it, pos, tx) { ctx.SubtractFromCount(1) return true } @@ -109,32 +109,32 @@ func (c Composter) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.Us } // Fill fills up the composter. -func (c Composter) fill(it item.Stack, pos cube.Pos, w *world.World) bool { +func (c Composter) fill(it item.Stack, pos cube.Pos, tx *world.Tx) bool { compostable, ok := it.Item().(item.Compostable) if !ok { return false } - w.AddParticle(pos.Vec3(), particle.BoneMeal{}) + tx.AddParticle(pos.Vec3(), particle.BoneMeal{}) if rand.Float64() > compostable.CompostChance() { - w.PlaySound(pos.Vec3(), sound.ComposterFill{}) + tx.PlaySound(pos.Vec3(), sound.ComposterFill{}) return true } c.Level++ - w.SetBlock(pos, c, nil) - w.PlaySound(pos.Vec3(), sound.ComposterFillLayer{}) + tx.SetBlock(pos, c, nil) + tx.PlaySound(pos.Vec3(), sound.ComposterFillLayer{}) if c.Level == 7 { - w.ScheduleBlockUpdate(pos, time.Second) + tx.ScheduleBlockUpdate(pos, time.Second) } return true } // ScheduledTick ... -func (c Composter) ScheduledTick(pos cube.Pos, w *world.World, _ *rand.Rand) { +func (c Composter) ScheduledTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { if c.Level == 7 { c.Level = 8 - w.SetBlock(pos, c, nil) - w.PlaySound(pos.Vec3(), sound.ComposterReady{}) + tx.SetBlock(pos, c, nil) + tx.PlaySound(pos.Vec3(), sound.ComposterReady{}) } } diff --git a/server/block/concrete_powder.go b/server/block/concrete_powder.go index 65d4a4987..c45e87a92 100644 --- a/server/block/concrete_powder.go +++ b/server/block/concrete_powder.go @@ -18,20 +18,20 @@ type ConcretePowder struct { } // Solidifies ... -func (c ConcretePowder) Solidifies(pos cube.Pos, w *world.World) bool { - _, water := w.Block(pos).(Water) +func (c ConcretePowder) Solidifies(pos cube.Pos, tx *world.Tx) bool { + _, water := tx.Block(pos).(Water) return water } // NeighbourUpdateTick ... -func (c ConcretePowder) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { +func (c ConcretePowder) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { for i := cube.Face(0); i < 6; i++ { - if _, ok := w.Block(pos.Side(i)).(Water); ok { - w.SetBlock(pos, Concrete{Colour: c.Colour}, nil) + if _, ok := tx.Block(pos.Side(i)).(Water); ok { + tx.SetBlock(pos, Concrete{Colour: c.Colour}, nil) return } } - c.fall(c, pos, w) + c.fall(c, pos, tx) } // BreakInfo ... diff --git a/server/block/container.go b/server/block/container.go index 03c1e3a7c..8d00b0ef1 100644 --- a/server/block/container.go +++ b/server/block/container.go @@ -24,7 +24,7 @@ type ContainerOpener interface { // Container represents a container of items, typically a block such as a chest. Containers may have their // inventory opened by viewers. type Container interface { - AddViewer(v ContainerViewer, w *world.World, pos cube.Pos) - RemoveViewer(v ContainerViewer, w *world.World, pos cube.Pos) - Inventory(w *world.World, pos cube.Pos) *inventory.Inventory + AddViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) + RemoveViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) + Inventory(tx *world.Tx, pos cube.Pos) *inventory.Inventory } diff --git a/server/block/coral.go b/server/block/coral.go index 93cba9edb..98625ae1b 100644 --- a/server/block/coral.go +++ b/server/block/coral.go @@ -24,15 +24,15 @@ type Coral struct { } // UseOnBlock ... -func (c Coral) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, c) +func (c Coral) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, c) if !used { return false } - if !w.Block(pos.Side(cube.FaceDown)).Model().FaceSolid(pos.Side(cube.FaceDown), cube.FaceUp, w) { + if !tx.Block(pos.Side(cube.FaceDown)).Model().FaceSolid(pos.Side(cube.FaceDown), cube.FaceUp, tx) { return false } - if liquid, ok := w.Liquid(pos); ok { + if liquid, ok := tx.Liquid(pos); ok { if water, ok := liquid.(Water); ok { if water.Depth != 8 { return false @@ -40,7 +40,7 @@ func (c Coral) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.W } } - place(w, pos, c, user, ctx) + place(tx, pos, c, user, ctx) return placed(ctx) } @@ -50,40 +50,40 @@ func (c Coral) HasLiquidDrops() bool { } // SideClosed ... -func (c Coral) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (c Coral) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } // NeighbourUpdateTick ... -func (c Coral) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if !w.Block(pos.Side(cube.FaceDown)).Model().FaceSolid(pos.Side(cube.FaceDown), cube.FaceUp, w) { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: c}) +func (c Coral) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if !tx.Block(pos.Side(cube.FaceDown)).Model().FaceSolid(pos.Side(cube.FaceDown), cube.FaceUp, tx) { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: c}) return } if c.Dead { return } - w.ScheduleBlockUpdate(pos, time.Second*5/2) + tx.ScheduleBlockUpdate(pos, time.Second*5/2) } // ScheduledTick ... -func (c Coral) ScheduledTick(pos cube.Pos, w *world.World, _ *rand.Rand) { +func (c Coral) ScheduledTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { if c.Dead { return } adjacentWater := false pos.Neighbours(func(neighbour cube.Pos) { - if liquid, ok := w.Liquid(neighbour); ok { + if liquid, ok := tx.Liquid(neighbour); ok { if _, ok := liquid.(Water); ok { adjacentWater = true } } - }, w.Range()) + }, tx.Range()) if !adjacentWater { c.Dead = true - w.SetBlock(pos, c, nil) + tx.SetBlock(pos, c, nil) } } diff --git a/server/block/coral_block.go b/server/block/coral_block.go index ab4b93908..46c54090d 100644 --- a/server/block/coral_block.go +++ b/server/block/coral_block.go @@ -19,30 +19,30 @@ type CoralBlock struct { } // NeighbourUpdateTick ... -func (c CoralBlock) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { +func (c CoralBlock) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { if c.Dead { return } - w.ScheduleBlockUpdate(pos, time.Second*5/2) + tx.ScheduleBlockUpdate(pos, time.Second*5/2) } // ScheduledTick ... -func (c CoralBlock) ScheduledTick(pos cube.Pos, w *world.World, _ *rand.Rand) { +func (c CoralBlock) ScheduledTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { if c.Dead { return } adjacentWater := false pos.Neighbours(func(neighbour cube.Pos) { - if liquid, ok := w.Liquid(neighbour); ok { + if liquid, ok := tx.Liquid(neighbour); ok { if _, ok := liquid.(Water); ok { adjacentWater = true } } - }, w.Range()) + }, tx.Range()) if !adjacentWater { c.Dead = true - w.SetBlock(pos, c, nil) + tx.SetBlock(pos, c, nil) } } diff --git a/server/block/crafting_table.go b/server/block/crafting_table.go index 7c1b7de4e..7269b80e9 100644 --- a/server/block/crafting_table.go +++ b/server/block/crafting_table.go @@ -34,7 +34,7 @@ func (CraftingTable) FuelInfo() item.FuelInfo { } // Activate ... -func (c CraftingTable) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.User, _ *item.UseContext) bool { +func (c CraftingTable) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { if opener, ok := u.(ContainerOpener); ok { opener.OpenBlockContainer(pos) return true diff --git a/server/block/crop.go b/server/block/crop.go index 26de4361f..6b46bbc67 100644 --- a/server/block/crop.go +++ b/server/block/crop.go @@ -24,13 +24,13 @@ type crop struct { } // NeighbourUpdateTick ... -func (c crop) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if _, ok := w.Block(pos.Side(cube.FaceDown)).(Farmland); !ok { - b := w.Block(pos) - w.SetBlock(pos, nil, nil) +func (c crop) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(Farmland); !ok { + b := tx.Block(pos) + tx.SetBlock(pos, nil, nil) if breakable, ok := b.(Breakable); ok { for _, drop := range breakable.BreakInfo().Drops(item.ToolNone{}, []item.Enchantment{}) { - dropItem(w, drop, pos.Vec3Centre()) + dropItem(tx, drop, pos.Vec3Centre()) } } } @@ -47,15 +47,15 @@ func (c crop) GrowthStage() int { } // CalculateGrowthChance calculates the chance the crop will grow during a random tick. -func (c crop) CalculateGrowthChance(pos cube.Pos, w *world.World) float64 { +func (c crop) CalculateGrowthChance(pos cube.Pos, tx *world.Tx) float64 { points := 0.0 - block := w.Block(pos) + block := tx.Block(pos) under := pos.Side(cube.FaceDown) for x := -1; x <= 1; x++ { for z := -1; z <= 1; z++ { - block := w.Block(under.Add(cube.Pos{x, 0, z})) + block := tx.Block(under.Add(cube.Pos{x, 0, z})) if farmland, ok := block.(Farmland); ok { farmlandPoints := 0.0 if farmland.Hydration > 0 { @@ -74,15 +74,15 @@ func (c crop) CalculateGrowthChance(pos cube.Pos, w *world.World) float64 { north := pos.Side(cube.FaceNorth) south := pos.Side(cube.FaceSouth) - northSouth := sameCrop(block, w.Block(north)) || sameCrop(block, w.Block(south)) - westEast := sameCrop(block, w.Block(pos.Side(cube.FaceWest))) || sameCrop(block, w.Block(pos.Side(cube.FaceEast))) + northSouth := sameCrop(block, tx.Block(north)) || sameCrop(block, tx.Block(south)) + westEast := sameCrop(block, tx.Block(pos.Side(cube.FaceWest))) || sameCrop(block, tx.Block(pos.Side(cube.FaceEast))) if northSouth && westEast { points /= 2 } else { - diagonal := sameCrop(block, w.Block(north.Side(cube.FaceWest))) || - sameCrop(block, w.Block(north.Side(cube.FaceEast))) || - sameCrop(block, w.Block(south.Side(cube.FaceWest))) || - sameCrop(block, w.Block(south.Side(cube.FaceEast))) + diagonal := sameCrop(block, tx.Block(north.Side(cube.FaceWest))) || + sameCrop(block, tx.Block(north.Side(cube.FaceEast))) || + sameCrop(block, tx.Block(south.Side(cube.FaceWest))) || + sameCrop(block, tx.Block(south.Side(cube.FaceEast))) if diagonal { points /= 2 } diff --git a/server/block/cube/trace/block.go b/server/block/cube/trace/block.go index 8cc85dc24..cca13ee48 100644 --- a/server/block/cube/trace/block.go +++ b/server/block/cube/trace/block.go @@ -40,8 +40,8 @@ func (r BlockResult) BlockPosition() cube.Pos { // that the ray collided with. // BlockIntercept returns a BlockResult with the block collided with and with the colliding vector closest to the start position, // if no colliding point was found, a zero BlockResult is returned and ok is false. -func BlockIntercept(pos cube.Pos, w *world.World, b world.Block, start, end mgl64.Vec3) (result BlockResult, ok bool) { - bbs := b.Model().BBox(pos, w) +func BlockIntercept(pos cube.Pos, tx *world.Tx, b world.Block, start, end mgl64.Vec3) (result BlockResult, ok bool) { + bbs := b.Model().BBox(pos, tx) if len(bbs) == 0 { return } diff --git a/server/block/cube/trace/result.go b/server/block/cube/trace/result.go index 0afc019f0..c0094ebfa 100644 --- a/server/block/cube/trace/result.go +++ b/server/block/cube/trace/result.go @@ -20,13 +20,13 @@ type Result interface { // Perform performs a ray trace between start and end, checking if any blocks or entities collided with the // ray. The physics.BBox that's passed is used for checking if any entity within the bounding box collided // with the ray. -func Perform(start, end mgl64.Vec3, w *world.World, box cube.BBox, ignored func(world.Entity) bool) (hit Result, ok bool) { +func Perform(start, end mgl64.Vec3, tx *world.Tx, box cube.BBox, ignored func(world.Entity) bool) (hit Result, ok bool) { // Check if there's any blocks that we may collide with. TraverseBlocks(start, end, func(pos cube.Pos) (cont bool) { - b := w.Block(pos) + b := tx.Block(pos) // Check if we collide with the block's model. - if result, ok := BlockIntercept(pos, w, b, start, end); ok { + if result, ok := BlockIntercept(pos, tx, b, start, end); ok { hit = result end = hit.Position() return false @@ -37,7 +37,7 @@ func Perform(start, end mgl64.Vec3, w *world.World, box cube.BBox, ignored func( // Now check for any entities that we may collide with. dist := math.MaxFloat64 bb := box.Translate(start).Extend(end.Sub(start)) - for _, entity := range w.EntitiesWithin(bb.Grow(8.0), ignored) { + for _, entity := range tx.EntitiesWithin(bb.Grow(8.0), ignored) { if ignored != nil && ignored(entity) || !entity.Type().BBox(entity).Translate(entity.Position()).IntersectsWith(bb) { continue } diff --git a/server/block/deadbush.go b/server/block/deadbush.go index 70cc367bd..252478403 100644 --- a/server/block/deadbush.go +++ b/server/block/deadbush.go @@ -18,32 +18,32 @@ type DeadBush struct { } // NeighbourUpdateTick ... -func (d DeadBush) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if !supportsVegetation(d, w.Block(pos.Side(cube.FaceDown))) { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) +func (d DeadBush) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if !supportsVegetation(d, tx.Block(pos.Side(cube.FaceDown))) { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) if amount := rand.Intn(3); amount != 0 { - dropItem(w, item.NewStack(item.Stick{}, amount), pos.Vec3Centre()) + dropItem(tx, item.NewStack(item.Stick{}, amount), pos.Vec3Centre()) } } } // UseOnBlock ... -func (d DeadBush) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, d) +func (d DeadBush) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, d) if !used { return false } - if !supportsVegetation(d, w.Block(pos.Side(cube.FaceDown))) { + if !supportsVegetation(d, tx.Block(pos.Side(cube.FaceDown))) { return false } - place(w, pos, d, user, ctx) + place(tx, pos, d, user, ctx) return placed(ctx) } // SideClosed ... -func (d DeadBush) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (d DeadBush) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } diff --git a/server/block/decorated_pot.go b/server/block/decorated_pot.go index d90484edc..beb8473a0 100644 --- a/server/block/decorated_pot.go +++ b/server/block/decorated_pot.go @@ -52,14 +52,14 @@ func (p DecoratedPot) Model() world.BlockModel { } // UseOnBlock ... -func (p DecoratedPot) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, _, used = firstReplaceable(w, pos, face, p) +func (p DecoratedPot) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, p) if !used { return } p.Facing = user.Rotation().Direction().Opposite() - place(w, pos, p, user, ctx) + place(tx, pos, p, user, ctx) return placed(ctx) } diff --git a/server/block/deepslate.go b/server/block/deepslate.go index c0e62b09a..63a9a4fbf 100644 --- a/server/block/deepslate.go +++ b/server/block/deepslate.go @@ -35,8 +35,8 @@ func (d Deepslate) SmeltInfo() item.SmeltInfo { } // UseOnBlock ... -func (d Deepslate) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, face, used = firstReplaceable(w, pos, face, d) +func (d Deepslate) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, face, used = firstReplaceable(tx, pos, face, d) if !used { return } @@ -44,7 +44,7 @@ func (d Deepslate) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *wor d.Axis = face.Axis() } - place(w, pos, d, user, ctx) + place(tx, pos, d, user, ctx) return placed(ctx) } diff --git a/server/block/dirt_path.go b/server/block/dirt_path.go index cde1d497e..a311ae732 100644 --- a/server/block/dirt_path.go +++ b/server/block/dirt_path.go @@ -17,11 +17,11 @@ func (p DirtPath) Till() (world.Block, bool) { } // NeighbourUpdateTick handles the turning from dirt path into dirt if a block is placed on top of it. -func (p DirtPath) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { +func (p DirtPath) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { up := pos.Side(cube.FaceUp) - if w.Block(up).Model().FaceSolid(up, cube.FaceDown, w) { + if tx.Block(up).Model().FaceSolid(up, cube.FaceDown, tx) { // A block with a solid side at the bottom was placed onto this one. - w.SetBlock(pos, Dirt{}, nil) + tx.SetBlock(pos, Dirt{}, nil) } } diff --git a/server/block/double_flower.go b/server/block/double_flower.go index 90f1e8d07..f62af4ab8 100644 --- a/server/block/double_flower.go +++ b/server/block/double_flower.go @@ -25,47 +25,47 @@ func (d DoubleFlower) FlammabilityInfo() FlammabilityInfo { } // BoneMeal ... -func (d DoubleFlower) BoneMeal(pos cube.Pos, w *world.World) bool { - dropItem(w, item.NewStack(d, 1), pos.Vec3Centre()) +func (d DoubleFlower) BoneMeal(pos cube.Pos, tx *world.Tx) bool { + dropItem(tx, item.NewStack(d, 1), pos.Vec3Centre()) return true } // NeighbourUpdateTick ... -func (d DoubleFlower) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { +func (d DoubleFlower) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { if d.UpperPart { - if bottom, ok := w.Block(pos.Side(cube.FaceDown)).(DoubleFlower); !ok || bottom.Type != d.Type || bottom.UpperPart { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) - dropItem(w, item.NewStack(d, 1), pos.Vec3Middle()) + if bottom, ok := tx.Block(pos.Side(cube.FaceDown)).(DoubleFlower); !ok || bottom.Type != d.Type || bottom.UpperPart { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) + dropItem(tx, item.NewStack(d, 1), pos.Vec3Middle()) } return } - if upper, ok := w.Block(pos.Side(cube.FaceUp)).(DoubleFlower); !ok || upper.Type != d.Type || !upper.UpperPart { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) + if upper, ok := tx.Block(pos.Side(cube.FaceUp)).(DoubleFlower); !ok || upper.Type != d.Type || !upper.UpperPart { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) return } - if !supportsVegetation(d, w.Block(pos.Side(cube.FaceDown))) { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) + if !supportsVegetation(d, tx.Block(pos.Side(cube.FaceDown))) { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) } } // UseOnBlock ... -func (d DoubleFlower) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, d) +func (d DoubleFlower) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, d) if !used { return false } - if !replaceableWith(w, pos.Side(cube.FaceUp), d) { + if !replaceableWith(tx, pos.Side(cube.FaceUp), d) { return false } - if !supportsVegetation(d, w.Block(pos.Side(cube.FaceDown))) { + if !supportsVegetation(d, tx.Block(pos.Side(cube.FaceDown))) { return false } - place(w, pos, d, user, ctx) - place(w, pos.Side(cube.FaceUp), DoubleFlower{Type: d.Type, UpperPart: true}, user, ctx) + place(tx, pos, d, user, ctx) + place(tx, pos.Side(cube.FaceUp), DoubleFlower{Type: d.Type, UpperPart: true}, user, ctx) return placed(ctx) } diff --git a/server/block/double_tall_grass.go b/server/block/double_tall_grass.go index 5e274eda4..80ea55a13 100644 --- a/server/block/double_tall_grass.go +++ b/server/block/double_tall_grass.go @@ -27,40 +27,40 @@ func (d DoubleTallGrass) HasLiquidDrops() bool { } // NeighbourUpdateTick ... -func (d DoubleTallGrass) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { +func (d DoubleTallGrass) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { if d.UpperPart { - if bottom, ok := w.Block(pos.Side(cube.FaceDown)).(DoubleTallGrass); !ok || bottom.Type != d.Type || bottom.UpperPart { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) + if bottom, ok := tx.Block(pos.Side(cube.FaceDown)).(DoubleTallGrass); !ok || bottom.Type != d.Type || bottom.UpperPart { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) } return } - if upper, ok := w.Block(pos.Side(cube.FaceUp)).(DoubleTallGrass); !ok || upper.Type != d.Type || !upper.UpperPart { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) + if upper, ok := tx.Block(pos.Side(cube.FaceUp)).(DoubleTallGrass); !ok || upper.Type != d.Type || !upper.UpperPart { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) return } - if !supportsVegetation(d, w.Block(pos.Side(cube.FaceDown))) { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) + if !supportsVegetation(d, tx.Block(pos.Side(cube.FaceDown))) { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) } } // UseOnBlock ... -func (d DoubleTallGrass) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, d) +func (d DoubleTallGrass) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, d) if !used { return false } - if !replaceableWith(w, pos.Side(cube.FaceUp), d) { + if !replaceableWith(tx, pos.Side(cube.FaceUp), d) { return false } - if !supportsVegetation(d, w.Block(pos.Side(cube.FaceDown))) { + if !supportsVegetation(d, tx.Block(pos.Side(cube.FaceDown))) { return false } - place(w, pos, d, user, ctx) - place(w, pos.Side(cube.FaceUp), DoubleTallGrass{Type: d.Type, UpperPart: true}, user, ctx) + place(tx, pos, d, user, ctx) + place(tx, pos.Side(cube.FaceUp), DoubleTallGrass{Type: d.Type, UpperPart: true}, user, ctx) return placed(ctx) } diff --git a/server/block/dragon_egg.go b/server/block/dragon_egg.go index f2b446b1f..d0711fe07 100644 --- a/server/block/dragon_egg.go +++ b/server/block/dragon_egg.go @@ -18,24 +18,24 @@ type DragonEgg struct { } // NeighbourUpdateTick ... -func (d DragonEgg) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - d.fall(d, pos, w) +func (d DragonEgg) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + d.fall(d, pos, tx) } // SideClosed ... -func (d DragonEgg) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (d DragonEgg) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } // teleport ... -func (d DragonEgg) teleport(pos cube.Pos, w *world.World) { +func (d DragonEgg) teleport(pos cube.Pos, tx *world.Tx) { for i := 0; i < 1000; i++ { - newPos := pos.Add(cube.Pos{rand.Intn(31) - 15, max(w.Range()[0]-pos.Y(), min(w.Range()[1]-pos.Y(), rand.Intn(15)-7)), rand.Intn(31) - 15}) + newPos := pos.Add(cube.Pos{rand.Intn(31) - 15, max(tx.Range()[0]-pos.Y(), min(tx.Range()[1]-pos.Y(), rand.Intn(15)-7)), rand.Intn(31) - 15}) - if _, ok := w.Block(newPos).(Air); ok { - w.SetBlock(newPos, d, nil) - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3(), particle.DragonEggTeleport{Diff: pos.Sub(newPos)}) + if _, ok := tx.Block(newPos).(Air); ok { + tx.SetBlock(newPos, d, nil) + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3(), particle.DragonEggTeleport{Diff: pos.Sub(newPos)}) return } } @@ -47,16 +47,16 @@ func (d DragonEgg) LightEmissionLevel() uint8 { } // Punch ... -func (d DragonEgg) Punch(pos cube.Pos, _ cube.Face, w *world.World, u item.User) { +func (d DragonEgg) Punch(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User) { if gm, ok := u.(interface{ GameMode() world.GameMode }); ok && gm.GameMode().CreativeInventory() { return } - d.teleport(pos, w) + d.teleport(pos, tx) } // Activate ... -func (d DragonEgg) Activate(pos cube.Pos, _ cube.Face, w *world.World, _ item.User, _ *item.UseContext) bool { - d.teleport(pos, w) +func (d DragonEgg) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { + d.teleport(pos, tx) return true } diff --git a/server/block/enchanting_table.go b/server/block/enchanting_table.go index 86c7f01a9..58aa2edb4 100644 --- a/server/block/enchanting_table.go +++ b/server/block/enchanting_table.go @@ -26,7 +26,7 @@ func (e EnchantingTable) BreakInfo() BreakInfo { } // SideClosed ... -func (EnchantingTable) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (EnchantingTable) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } @@ -36,7 +36,7 @@ func (EnchantingTable) LightEmissionLevel() uint8 { } // Activate ... -func (EnchantingTable) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.User, _ *item.UseContext) bool { +func (EnchantingTable) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { if opener, ok := u.(ContainerOpener); ok { opener.OpenBlockContainer(pos) return true diff --git a/server/block/ender_chest.go b/server/block/ender_chest.go index 7ad3e6622..5ef33e109 100644 --- a/server/block/ender_chest.go +++ b/server/block/ender_chest.go @@ -46,13 +46,13 @@ func (c EnderChest) LightEmissionLevel() uint8 { } // SideClosed ... -func (EnderChest) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (EnderChest) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } // UseOnBlock ... -func (c EnderChest) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, _, used = firstReplaceable(w, pos, face, c) +func (c EnderChest) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, c) if !used { return } @@ -60,12 +60,12 @@ func (c EnderChest) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *wo c = NewEnderChest() c.Facing = user.Rotation().Direction().Opposite() - place(w, pos, c, user, ctx) + place(tx, pos, c, user, ctx) return placed(ctx) } // Activate ... -func (c EnderChest) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.User, _ *item.UseContext) bool { +func (c EnderChest) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { if opener, ok := u.(enderChestOwner); ok { opener.OpenBlockContainer(pos) return true @@ -74,36 +74,36 @@ func (c EnderChest) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.U } // AddViewer ... -func (c EnderChest) AddViewer(w *world.World, pos cube.Pos) { +func (c EnderChest) AddViewer(tx *world.Tx, pos cube.Pos) { if c.viewers.Add(1) == 1 { - c.open(w, pos) + c.open(tx, pos) } } // RemoveViewer ... -func (c EnderChest) RemoveViewer(w *world.World, pos cube.Pos) { +func (c EnderChest) RemoveViewer(tx *world.Tx, pos cube.Pos) { if c.viewers.Load() == 0 { return } if c.viewers.Add(-1) == 0 { - c.close(w, pos) + c.close(tx, pos) } } // open opens the ender chest, displaying the animation and playing a sound. -func (c EnderChest) open(w *world.World, pos cube.Pos) { - for _, v := range w.Viewers(pos.Vec3()) { +func (c EnderChest) open(tx *world.Tx, pos cube.Pos) { + for _, v := range tx.Viewers(pos.Vec3()) { v.ViewBlockAction(pos, OpenAction{}) } - w.PlaySound(pos.Vec3Centre(), sound.EnderChestOpen{}) + tx.PlaySound(pos.Vec3Centre(), sound.EnderChestOpen{}) } // close closes the ender chest, displaying the animation and playing a sound. -func (c EnderChest) close(w *world.World, pos cube.Pos) { - for _, v := range w.Viewers(pos.Vec3()) { +func (c EnderChest) close(tx *world.Tx, pos cube.Pos) { + for _, v := range tx.Viewers(pos.Vec3()) { v.ViewBlockAction(pos, CloseAction{}) } - w.PlaySound(pos.Vec3Centre(), sound.EnderChestClose{}) + tx.PlaySound(pos.Vec3Centre(), sound.EnderChestClose{}) } // EncodeNBT ... diff --git a/server/block/explosion.go b/server/block/explosion.go index b24e4bd1a..8aba8a39e 100644 --- a/server/block/explosion.go +++ b/server/block/explosion.go @@ -45,7 +45,7 @@ type ExplodableEntity interface { // Explodable represents a block that can be exploded. type Explodable interface { // Explode is called when an explosion occurs. The block can react to the explosion using the configuration passed. - Explode(explosionPos mgl64.Vec3, pos cube.Pos, w *world.World, c ExplosionConfig) + Explode(explosionPos mgl64.Vec3, pos cube.Pos, tx *world.Tx, c ExplosionConfig) } // rays ... @@ -66,7 +66,7 @@ func init() { } // Explode performs the explosion as specified by the configuration. -func (c ExplosionConfig) Explode(w *world.World, explosionPos mgl64.Vec3) { +func (c ExplosionConfig) Explode(tx *world.Tx, explosionPos mgl64.Vec3) { if c.Sound == nil { c.Sound = sound.Explosion{} } @@ -90,7 +90,7 @@ func (c ExplosionConfig) Explode(w *world.World, explosionPos mgl64.Vec3) { math.Ceil(explosionPos[2]+d+1), ) - for _, e := range w.EntitiesWithin(box.Grow(2), nil) { + for _, e := range tx.EntitiesWithin(box.Grow(2), nil) { pos := e.Position() if !e.Type().BBox(e).Translate(pos).IntersectsWith(box) { continue @@ -100,7 +100,7 @@ func (c ExplosionConfig) Explode(w *world.World, explosionPos mgl64.Vec3) { continue } if explodable, ok := e.(ExplodableEntity); ok { - impact := (1 - dist/d) * exposure(pos, e) + impact := (1 - dist/d) * exposure(tx, pos, e) explodable.Explode(explosionPos, impact, c) } } @@ -110,10 +110,10 @@ func (c ExplosionConfig) Explode(w *world.World, explosionPos mgl64.Vec3) { pos := explosionPos for blastForce := c.Size * (0.7 + r.Float64()*0.6); blastForce > 0.0; blastForce -= 0.225 { current := cube.PosFromVec3(pos) - currentBlock := w.Block(current) + currentBlock := tx.Block(current) resistance := 0.0 - if l, ok := w.Liquid(current); ok { + if l, ok := tx.Liquid(current); ok { resistance = l.BlastResistance() } else if i, ok := currentBlock.(Breakable); ok { resistance = i.BreakInfo().BlastResistance @@ -129,14 +129,14 @@ func (c ExplosionConfig) Explode(w *world.World, explosionPos mgl64.Vec3) { } } for _, pos := range affectedBlocks { - bl := w.Block(pos) + bl := tx.Block(pos) if explodable, ok := bl.(Explodable); ok { - explodable.Explode(explosionPos, pos, w, c) + explodable.Explode(explosionPos, pos, tx, c) } else if breakable, ok := bl.(Breakable); ok { - w.SetBlock(pos, nil, nil) + tx.SetBlock(pos, nil, nil) if !c.DisableItemDrops && 1/c.Size > r.Float64() { for _, drop := range breakable.BreakInfo().Drops(item.ToolNone{}, nil) { - dropItem(w, drop, pos.Vec3Centre()) + dropItem(tx, drop, pos.Vec3Centre()) } } @@ -144,18 +144,18 @@ func (c ExplosionConfig) Explode(w *world.World, explosionPos mgl64.Vec3) { if cb, ok := bl.(Chest); ok { if cb.Paired() { pairPos := cb.pairPos(pos) - if _, pair, ok := cb.unpair(w, pos); ok { + if _, pair, ok := cb.unpair(tx, pos); ok { cb.paired = false - w.SetBlock(pairPos, pair, nil) + tx.SetBlock(pairPos, pair, nil) } } - for _, i := range cb.Inventory(w, pos).Clear() { - dropItem(w, i, pos.Vec3()) + for _, i := range cb.Inventory(tx, pos).Clear() { + dropItem(tx, i, pos.Vec3()) } } else { - for _, i := range container.Inventory(w, pos).Clear() { - dropItem(w, i, pos.Vec3()) + for _, i := range container.Inventory(tx, pos).Clear() { + dropItem(tx, i, pos.Vec3()) } } } @@ -164,20 +164,19 @@ func (c ExplosionConfig) Explode(w *world.World, explosionPos mgl64.Vec3) { if c.SpawnFire { for _, pos := range affectedBlocks { if r.Intn(3) == 0 { - if _, ok := w.Block(pos).(Air); ok && w.Block(pos.Side(cube.FaceDown)).Model().FaceSolid(pos, cube.FaceUp, w) { - w.SetBlock(pos, Fire{}, nil) + if _, ok := tx.Block(pos).(Air); ok && tx.Block(pos.Side(cube.FaceDown)).Model().FaceSolid(pos, cube.FaceUp, tx) { + tx.SetBlock(pos, Fire{}, nil) } } } } - w.AddParticle(explosionPos, c.Particle) - w.PlaySound(explosionPos, c.Sound) + tx.AddParticle(explosionPos, c.Particle) + tx.PlaySound(explosionPos, c.Sound) } // exposure returns the exposure of an explosion to an entity, used to calculate the impact of an explosion. -func exposure(origin mgl64.Vec3, e world.Entity) float64 { - w := e.World() +func exposure(tx *world.Tx, origin mgl64.Vec3, e world.Entity) float64 { pos := e.Position() box := e.Type().BBox(e).Translate(pos) @@ -204,7 +203,7 @@ func exposure(origin mgl64.Vec3, e world.Entity) float64 { var collided bool trace.TraverseBlocks(origin, point, func(pos cube.Pos) (con bool) { - _, air := w.Block(pos).(Air) + _, air := tx.Block(pos).(Air) collided = !air return air }) diff --git a/server/block/farmland.go b/server/block/farmland.go index 7bc6576ab..4ecec3059 100644 --- a/server/block/farmland.go +++ b/server/block/farmland.go @@ -28,37 +28,37 @@ func (f Farmland) SoilFor(block world.Block) bool { } // NeighbourUpdateTick ... -func (f Farmland) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if solid := w.Block(pos.Side(cube.FaceUp)).Model().FaceSolid(pos.Side(cube.FaceUp), cube.FaceDown, w); solid { - w.SetBlock(pos, Dirt{}, nil) +func (f Farmland) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if solid := tx.Block(pos.Side(cube.FaceUp)).Model().FaceSolid(pos.Side(cube.FaceUp), cube.FaceDown, tx); solid { + tx.SetBlock(pos, Dirt{}, nil) } } // RandomTick ... -func (f Farmland) RandomTick(pos cube.Pos, w *world.World, _ *rand.Rand) { - if !f.hydrated(pos, w) { +func (f Farmland) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { + if !f.hydrated(pos, tx) { if f.Hydration > 0 { f.Hydration-- - w.SetBlock(pos, f, nil) + tx.SetBlock(pos, f, nil) } else { - blockAbove := w.Block(pos.Side(cube.FaceUp)) + blockAbove := tx.Block(pos.Side(cube.FaceUp)) if _, cropAbove := blockAbove.(Crop); !cropAbove { - w.SetBlock(pos, Dirt{}, nil) + tx.SetBlock(pos, Dirt{}, nil) } } } else { f.Hydration = 7 - w.SetBlock(pos, f, nil) + tx.SetBlock(pos, f, nil) } } // hydrated checks for water within 4 blocks in each direction from the farmland. -func (f Farmland) hydrated(pos cube.Pos, w *world.World) bool { +func (f Farmland) hydrated(pos cube.Pos, tx *world.Tx) bool { posX, posY, posZ := pos.X(), pos.Y(), pos.Z() for y := 0; y <= 1; y++ { for x := -4; x <= 4; x++ { for z := -4; z <= 4; z++ { - if liquid, ok := w.Liquid(cube.Pos{posX + x, posY + y, posZ + z}); ok { + if liquid, ok := tx.Liquid(cube.Pos{posX + x, posY + y, posZ + z}); ok { if _, ok := liquid.(Water); ok { return true } @@ -70,12 +70,12 @@ func (f Farmland) hydrated(pos cube.Pos, w *world.World) bool { } // EntityLand ... -func (f Farmland) EntityLand(pos cube.Pos, w *world.World, e world.Entity, distance *float64) { +func (f Farmland) EntityLand(pos cube.Pos, tx *world.Tx, e world.Entity, distance *float64) { if living, ok := e.(livingEntity); ok { if fall, ok := living.(fallDistanceEntity); ok && rand.Float64() < fall.FallDistance()-0.5 { ctx := event.C() - if w.Handler().HandleCropTrample(ctx, pos); !ctx.Cancelled() { - w.SetBlock(pos, Dirt{}, nil) + if tx.World().Handler().HandleCropTrample(ctx, pos); !ctx.Cancelled() { + tx.SetBlock(pos, Dirt{}, nil) } } } diff --git a/server/block/fern.go b/server/block/fern.go index 0086edcf0..48611a37c 100644 --- a/server/block/fern.go +++ b/server/block/fern.go @@ -35,11 +35,11 @@ func (g Fern) BreakInfo() BreakInfo { } // BoneMeal attempts to affect the block using a bone meal item. -func (g Fern) BoneMeal(pos cube.Pos, w *world.World) bool { +func (g Fern) BoneMeal(pos cube.Pos, tx *world.Tx) bool { upper := DoubleTallGrass{Type: FernDoubleTallGrass(), UpperPart: true} - if replaceableWith(w, pos.Side(cube.FaceUp), upper) { - w.SetBlock(pos, DoubleTallGrass{Type: FernDoubleTallGrass()}, nil) - w.SetBlock(pos.Side(cube.FaceUp), upper, nil) + if replaceableWith(tx, pos.Side(cube.FaceUp), upper) { + tx.SetBlock(pos, DoubleTallGrass{Type: FernDoubleTallGrass()}, nil) + tx.SetBlock(pos.Side(cube.FaceUp), upper, nil) return true } return false @@ -51,10 +51,10 @@ func (g Fern) CompostChance() float64 { } // NeighbourUpdateTick ... -func (g Fern) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if !supportsVegetation(g, w.Block(pos.Side(cube.FaceDown))) { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: g}) +func (g Fern) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if !supportsVegetation(g, tx.Block(pos.Side(cube.FaceDown))) { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: g}) } } @@ -64,16 +64,16 @@ func (g Fern) HasLiquidDrops() bool { } // UseOnBlock ... -func (g Fern) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, g) +func (g Fern) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, g) if !used { return false } - if !supportsVegetation(g, w.Block(pos.Side(cube.FaceDown))) { + if !supportsVegetation(g, tx.Block(pos.Side(cube.FaceDown))) { return false } - place(w, pos, g, user, ctx) + place(tx, pos, g, user, ctx) return placed(ctx) } diff --git a/server/block/fire.go b/server/block/fire.go index 94b0f5dba..e1a5f45ba 100644 --- a/server/block/fire.go +++ b/server/block/fire.go @@ -32,26 +32,18 @@ func flammableBlock(block world.Block) bool { } // neighboursFlammable returns true if one a block adjacent to the passed position is flammable. -func neighboursFlammable(pos cube.Pos, w *world.World) bool { +func neighboursFlammable(pos cube.Pos, tx *world.Tx) bool { for _, i := range cube.Faces() { - if flammableBlock(w.Block(pos.Side(i))) { + if flammableBlock(tx.Block(pos.Side(i))) { return true } } return false } -// max ... -func max(a, b int) int { - if a > b { - return a - } - return b -} - // infinitelyBurning returns true if fire can infinitely burn at the specified position. -func infinitelyBurning(pos cube.Pos, w *world.World) bool { - switch block := w.Block(pos.Side(cube.FaceDown)).(type) { +func infinitelyBurning(pos cube.Pos, tx *world.Tx) bool { + switch block := tx.Block(pos.Side(cube.FaceDown)).(type) { // TODO: Magma Block case Netherrack: return true @@ -62,70 +54,70 @@ func infinitelyBurning(pos cube.Pos, w *world.World) bool { } // burn attempts to burn a block. -func (f Fire) burn(from, to cube.Pos, w *world.World, r *rand.Rand, chanceBound int) { - if flammable, ok := w.Block(to).(Flammable); ok && r.Intn(chanceBound) < flammable.FlammabilityInfo().Flammability { - if r.Intn(f.Age+10) < 5 && !rainingAround(to, w) { - f.spread(from, to, w, r) +func (f Fire) burn(from, to cube.Pos, tx *world.Tx, r *rand.Rand, chanceBound int) { + if flammable, ok := tx.Block(to).(Flammable); ok && r.Intn(chanceBound) < flammable.FlammabilityInfo().Flammability { + if r.Intn(f.Age+10) < 5 && !rainingAround(to, tx) { + f.spread(from, to, tx, r) return } if t, ok := flammable.(TNT); ok { - t.Ignite(to, w, nil) + t.Ignite(to, tx, nil) return } - w.SetBlock(to, nil, nil) + tx.SetBlock(to, nil, nil) } } // rainingAround checks if it is raining either at the cube.Pos passed or at any of its horizontal neighbours. -func rainingAround(pos cube.Pos, w *world.World) bool { - raining := w.RainingAt(pos) +func rainingAround(pos cube.Pos, tx *world.Tx) bool { + raining := tx.RainingAt(pos) for _, face := range cube.HorizontalFaces() { if raining { break } - raining = w.RainingAt(pos.Side(face)) + raining = tx.RainingAt(pos.Side(face)) } return raining } // tick ... -func (f Fire) tick(pos cube.Pos, w *world.World, r *rand.Rand) { +func (f Fire) tick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { if f.Type == SoulFire() { return } - infinitelyBurns := infinitelyBurning(pos, w) - if !infinitelyBurns && (20+f.Age*3) > r.Intn(100) && rainingAround(pos, w) { + infinitelyBurns := infinitelyBurning(pos, tx) + if !infinitelyBurns && (20+f.Age*3) > r.Intn(100) && rainingAround(pos, tx) { // Fire is extinguished by the rain. - w.SetBlock(pos, nil, nil) + tx.SetBlock(pos, nil, nil) return } if f.Age < 15 && r.Intn(3) == 0 { f.Age++ - w.SetBlock(pos, f, nil) + tx.SetBlock(pos, f, nil) } - w.ScheduleBlockUpdate(pos, time.Duration(30+r.Intn(10))*time.Second/20) + tx.ScheduleBlockUpdate(pos, time.Duration(30+r.Intn(10))*time.Second/20) if !infinitelyBurns { - _, waterBelow := w.Block(pos.Side(cube.FaceDown)).(Water) + _, waterBelow := tx.Block(pos.Side(cube.FaceDown)).(Water) if waterBelow { - w.SetBlock(pos, nil, nil) + tx.SetBlock(pos, nil, nil) return } - if !neighboursFlammable(pos, w) { - if !w.Block(pos.Side(cube.FaceDown)).Model().FaceSolid(pos, cube.FaceUp, w) || f.Age > 3 { - w.SetBlock(pos, nil, nil) + if !neighboursFlammable(pos, tx) { + if !tx.Block(pos.Side(cube.FaceDown)).Model().FaceSolid(pos, cube.FaceUp, tx) || f.Age > 3 { + tx.SetBlock(pos, nil, nil) } return } - if !flammableBlock(w.Block(pos.Side(cube.FaceDown))) && f.Age == 15 && r.Intn(4) == 0 { - w.SetBlock(pos, nil, nil) + if !flammableBlock(tx.Block(pos.Side(cube.FaceDown))) && f.Age == 15 && r.Intn(4) == 0 { + tx.SetBlock(pos, nil, nil) return } } - humid := w.Biome(pos).Rainfall() > 0.85 + humid := tx.Biome(pos).Rainfall() > 0.85 s := 0 if humid { @@ -133,9 +125,9 @@ func (f Fire) tick(pos cube.Pos, w *world.World, r *rand.Rand) { } for _, face := range cube.Faces() { if face == cube.FaceUp || face == cube.FaceDown { - f.burn(pos, pos.Side(face), w, r, 300-s) + f.burn(pos, pos.Side(face), tx, r, 300-s) } else { - f.burn(pos, pos.Side(face), w, r, 250-s) + f.burn(pos, pos.Side(face), tx, r, 250-s) } } @@ -151,28 +143,28 @@ func (f Fire) tick(pos cube.Pos, w *world.World, r *rand.Rand) { continue } blockPos := pos.Add(cube.Pos{x, y, z}) - block := w.Block(blockPos) + block := tx.Block(blockPos) if _, ok := block.(Air); !ok { continue } encouragement := 0 blockPos.Neighbours(func(neighbour cube.Pos) { - if flammable, ok := w.Block(neighbour).(Flammable); ok { + if flammable, ok := tx.Block(neighbour).(Flammable); ok { encouragement = max(encouragement, flammable.FlammabilityInfo().Encouragement) } - }, w.Range()) + }, tx.Range()) if encouragement <= 0 { continue } - maxChance := (encouragement + 40 + w.Difficulty().FireSpreadIncrease()) / (f.Age + 30) + maxChance := (encouragement + 40 + tx.World().Difficulty().FireSpreadIncrease()) / (f.Age + 30) if humid { maxChance /= 2 } - if maxChance > 0 && r.Intn(randomBound) <= maxChance && !rainingAround(blockPos, w) { - f.spread(pos, blockPos, w, r) + if maxChance > 0 && r.Intn(randomBound) <= maxChance && !rainingAround(blockPos, tx) { + f.spread(pos, blockPos, tx, r) } } } @@ -181,23 +173,23 @@ func (f Fire) tick(pos cube.Pos, w *world.World, r *rand.Rand) { // spread attempts to spread fire from a cube.Pos to another. If the block burn or fire spreading events are cancelled, // this might end up not happening. -func (f Fire) spread(from, to cube.Pos, w *world.World, r *rand.Rand) { - if _, air := w.Block(to).(Air); !air { +func (f Fire) spread(from, to cube.Pos, tx *world.Tx, r *rand.Rand) { + if _, air := tx.Block(to).(Air); !air { ctx := event.C() - if w.Handler().HandleBlockBurn(ctx, to); ctx.Cancelled() { + if tx.World().Handler().HandleBlockBurn(ctx, to); ctx.Cancelled() { return } } ctx := event.C() - if w.Handler().HandleFireSpread(ctx, from, to); ctx.Cancelled() { + if tx.World().Handler().HandleFireSpread(ctx, from, to); ctx.Cancelled() { return } - w.SetBlock(to, Fire{Type: f.Type, Age: min(15, f.Age+r.Intn(5)/4)}, nil) - w.ScheduleBlockUpdate(to, time.Duration(30+r.Intn(10))*time.Second/20) + tx.SetBlock(to, Fire{Type: f.Type, Age: min(15, f.Age+r.Intn(5)/4)}, nil) + tx.ScheduleBlockUpdate(to, time.Duration(30+r.Intn(10))*time.Second/20) } // EntityInside ... -func (f Fire) EntityInside(_ cube.Pos, _ *world.World, e world.Entity) { +func (f Fire) EntityInside(pos cube.Pos, tx *world.Tx, e world.Entity) { if flammable, ok := e.(flammableEntity); ok { if l, ok := e.(livingEntity); ok && !l.AttackImmune() { l.Hurt(f.Type.Damage(), FireDamageSource{}) @@ -209,33 +201,33 @@ func (f Fire) EntityInside(_ cube.Pos, _ *world.World, e world.Entity) { } // ScheduledTick ... -func (f Fire) ScheduledTick(pos cube.Pos, w *world.World, r *rand.Rand) { - f.tick(pos, w, r) +func (f Fire) ScheduledTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { + f.tick(pos, tx, r) } // RandomTick ... -func (f Fire) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { - f.tick(pos, w, r) +func (f Fire) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { + f.tick(pos, tx, r) } // NeighbourUpdateTick ... -func (f Fire) NeighbourUpdateTick(pos, neighbour cube.Pos, w *world.World) { - below := w.Block(pos.Side(cube.FaceDown)) - if diffuser, ok := below.(LightDiffuser); (ok && diffuser.LightDiffusionLevel() != 15) && (!neighboursFlammable(pos, w) || f.Type == SoulFire()) { - w.SetBlock(pos, nil, nil) +func (f Fire) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + below := tx.Block(pos.Side(cube.FaceDown)) + if diffuser, ok := below.(LightDiffuser); (ok && diffuser.LightDiffusionLevel() != 15) && (!neighboursFlammable(pos, tx) || f.Type == SoulFire()) { + tx.SetBlock(pos, nil, nil) return } switch below.(type) { case SoulSand, SoulSoil: f.Type = SoulFire() - w.SetBlock(pos, f, nil) + tx.SetBlock(pos, f, nil) case Water: - if neighbour == pos { - w.SetBlock(pos, nil, nil) + if changedNeighbour == pos { + tx.SetBlock(pos, nil, nil) } default: if f.Type == SoulFire() { - w.SetBlock(pos, nil, nil) + tx.SetBlock(pos, nil, nil) return } } @@ -264,15 +256,15 @@ func (f Fire) EncodeBlock() (name string, properties map[string]any) { // Start starts a fire at a position in the world. The position passed must be either air or tall grass and conditions // for a fire to be present must be present. -func (f Fire) Start(w *world.World, pos cube.Pos) { - b := w.Block(pos) +func (f Fire) Start(tx *world.Tx, pos cube.Pos) { + b := tx.Block(pos) _, air := b.(Air) _, shortGrass := b.(ShortGrass) _, fern := b.(Fern) if air || shortGrass || fern { - below := w.Block(pos.Side(cube.FaceDown)) - if below.Model().FaceSolid(pos, cube.FaceUp, w) || neighboursFlammable(pos, w) { - w.SetBlock(pos, Fire{}, nil) + below := tx.Block(pos.Side(cube.FaceDown)) + if below.Model().FaceSolid(pos, cube.FaceUp, tx) || neighboursFlammable(pos, tx) { + tx.SetBlock(pos, Fire{}, nil) } } } diff --git a/server/block/flower.go b/server/block/flower.go index be848e53c..ce28c058c 100644 --- a/server/block/flower.go +++ b/server/block/flower.go @@ -22,7 +22,7 @@ type Flower struct { } // EntityInside ... -func (f Flower) EntityInside(_ cube.Pos, _ *world.World, e world.Entity) { +func (f Flower) EntityInside(pos cube.Pos, tx *world.Tx, e world.Entity) { if f.Type == WitherRose() { if living, ok := e.(interface { AddEffect(effect.Effect) @@ -33,17 +33,17 @@ func (f Flower) EntityInside(_ cube.Pos, _ *world.World, e world.Entity) { } // BoneMeal ... -func (f Flower) BoneMeal(pos cube.Pos, w *world.World) (success bool) { +func (f Flower) BoneMeal(pos cube.Pos, tx *world.Tx) (success bool) { if f.Type == WitherRose() { return } for i := 0; i < 8; i++ { p := pos.Add(cube.Pos{rand.Intn(7) - 3, rand.Intn(3) - 1, rand.Intn(7) - 3}) - if _, ok := w.Block(p).(Air); !ok { + if _, ok := tx.Block(p).(Air); !ok { continue } - if _, ok := w.Block(p.Side(cube.FaceDown)).(Grass); !ok { + if _, ok := tx.Block(p.Side(cube.FaceDown)).(Grass); !ok { continue } flowerType := f.Type @@ -54,32 +54,32 @@ func (f Flower) BoneMeal(pos cube.Pos, w *world.World) (success bool) { flowerType = Dandelion() } } - w.SetBlock(p, Flower{Type: flowerType}, nil) + tx.SetBlock(p, Flower{Type: flowerType}, nil) success = true } return } // NeighbourUpdateTick ... -func (f Flower) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if !supportsVegetation(f, w.Block(pos.Side(cube.FaceDown))) { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: f}) - dropItem(w, item.NewStack(f, 1), pos.Vec3Centre()) +func (f Flower) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if !supportsVegetation(f, tx.Block(pos.Side(cube.FaceDown))) { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: f}) + dropItem(tx, item.NewStack(f, 1), pos.Vec3Centre()) } } // UseOnBlock ... -func (f Flower) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, f) +func (f Flower) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, f) if !used { return false } - if !supportsVegetation(f, w.Block(pos.Side(cube.FaceDown))) { + if !supportsVegetation(f, tx.Block(pos.Side(cube.FaceDown))) { return false } - place(w, pos, f, user, ctx) + place(tx, pos, f, user, ctx) return placed(ctx) } diff --git a/server/block/froglight.go b/server/block/froglight.go index b8ffa2223..4c724cd2b 100644 --- a/server/block/froglight.go +++ b/server/block/froglight.go @@ -23,14 +23,14 @@ func (f Froglight) LightEmissionLevel() uint8 { } // UseOnBlock ... -func (f Froglight) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, face, used = firstReplaceable(w, pos, face, f) +func (f Froglight) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, face, used = firstReplaceable(tx, pos, face, f) if !used { return } f.Axis = face.Axis() - place(w, pos, f, user, ctx) + place(tx, pos, f, user, ctx) return placed(ctx) } diff --git a/server/block/furnace.go b/server/block/furnace.go index 5feb026f3..1abf8960f 100644 --- a/server/block/furnace.go +++ b/server/block/furnace.go @@ -33,15 +33,15 @@ func NewFurnace(face cube.Direction) Furnace { } // Tick is called to check if the furnace should update and start or stop smelting. -func (f Furnace) Tick(_ int64, pos cube.Pos, w *world.World) { +func (f Furnace) Tick(currentTick int64, pos cube.Pos, tx *world.Tx) { if f.Lit && rand.Float64() <= 0.016 { // Every three or so seconds. - w.PlaySound(pos.Vec3Centre(), sound.FurnaceCrackle{}) + tx.PlaySound(pos.Vec3Centre(), sound.FurnaceCrackle{}) } if lit := f.smelter.tickSmelting(time.Second*10, time.Millisecond*100, f.Lit, func(item.SmeltInfo) bool { return true }); f.Lit != lit { f.Lit = lit - w.SetBlock(pos, f, nil) + tx.SetBlock(pos, f, nil) } } @@ -59,28 +59,28 @@ func (f Furnace) EncodeBlock() (name string, properties map[string]interface{}) } // UseOnBlock ... -func (f Furnace) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, f) +func (f Furnace) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, f) if !used { return false } - place(w, pos, NewFurnace(user.Rotation().Direction().Opposite()), user, ctx) + place(tx, pos, NewFurnace(user.Rotation().Direction().Opposite()), user, ctx) return placed(ctx) } // BreakInfo ... func (f Furnace) BreakInfo() BreakInfo { xp := f.Experience() - return newBreakInfo(3.5, alwaysHarvestable, pickaxeEffective, oneOf(f)).withXPDropRange(xp, xp).withBreakHandler(func(pos cube.Pos, w *world.World, u item.User) { - for _, i := range f.Inventory(w, pos).Clear() { - dropItem(w, i, pos.Vec3()) + return newBreakInfo(3.5, alwaysHarvestable, pickaxeEffective, oneOf(f)).withXPDropRange(xp, xp).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { + for _, i := range f.Inventory(tx, pos).Clear() { + dropItem(tx, i, pos.Vec3()) } }) } // Activate ... -func (f Furnace) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.User, _ *item.UseContext) bool { +func (f Furnace) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { if opener, ok := u.(ContainerOpener); ok { opener.OpenBlockContainer(pos) return true diff --git a/server/block/glass_pane.go b/server/block/glass_pane.go index f9196c5d8..fe546407d 100644 --- a/server/block/glass_pane.go +++ b/server/block/glass_pane.go @@ -14,7 +14,7 @@ type GlassPane struct { } // SideClosed ... -func (p GlassPane) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (p GlassPane) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } diff --git a/server/block/glazed_terracotta.go b/server/block/glazed_terracotta.go index f61491da7..f563b8c53 100644 --- a/server/block/glazed_terracotta.go +++ b/server/block/glazed_terracotta.go @@ -34,14 +34,14 @@ func (t GlazedTerracotta) EncodeBlock() (name string, properties map[string]any) } // UseOnBlock ensures the proper facing is used when placing a glazed terracotta block, by using the opposite of the player. -func (t GlazedTerracotta) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, _, used = firstReplaceable(w, pos, face, t) +func (t GlazedTerracotta) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, t) if !used { return } t.Facing = user.Rotation().Direction().Opposite() - place(w, pos, t, user, ctx) + place(tx, pos, t, user, ctx) return placed(ctx) } diff --git a/server/block/grass.go b/server/block/grass.go index 3bd2155fe..08b6b6c02 100644 --- a/server/block/grass.go +++ b/server/block/grass.go @@ -44,11 +44,11 @@ func (g Grass) SoilFor(block world.Block) bool { } // RandomTick handles the ticking of grass, which may or may not result in the spreading of grass onto dirt. -func (g Grass) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { - aboveLight := w.Light(pos.Side(cube.FaceUp)) +func (g Grass) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { + aboveLight := tx.Light(pos.Side(cube.FaceUp)) if aboveLight < 4 { // The light above the block is too low: The grass turns to dirt. - w.SetBlock(pos, Dirt{}, nil) + tx.SetBlock(pos, Dirt{}, nil) return } if aboveLight < 9 { @@ -66,26 +66,26 @@ func (g Grass) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { spreadPos := pos.Add(cube.Pos{x - 1, y - 3, z - 1}) // Don't spread grass to locations where dirt is exposed to hardly any light. - if w.Light(spreadPos.Side(cube.FaceUp)) < 4 { + if tx.Light(spreadPos.Side(cube.FaceUp)) < 4 { continue } - b := w.Block(spreadPos) + b := tx.Block(spreadPos) if dirt, ok := b.(Dirt); !ok || dirt.Coarse { continue } - w.SetBlock(spreadPos, g, nil) + tx.SetBlock(spreadPos, g, nil) } } // BoneMeal ... -func (g Grass) BoneMeal(pos cube.Pos, w *world.World) bool { +func (g Grass) BoneMeal(pos cube.Pos, tx *world.Tx) bool { for i := 0; i < 14; i++ { c := pos.Add(cube.Pos{rand.Intn(6) - 3, 0, rand.Intn(6) - 3}) above := c.Side(cube.FaceUp) - _, air := w.Block(above).(Air) - _, grass := w.Block(c).(Grass) + _, air := tx.Block(above).(Air) + _, grass := tx.Block(c).(Grass) if air && grass { - w.SetBlock(above, plantSelection[rand.Intn(len(plantSelection))], nil) + tx.SetBlock(above, plantSelection[rand.Intn(len(plantSelection))], nil) } } diff --git a/server/block/gravel.go b/server/block/gravel.go index 693110321..282193082 100644 --- a/server/block/gravel.go +++ b/server/block/gravel.go @@ -15,8 +15,8 @@ type Gravel struct { } // NeighbourUpdateTick ... -func (g Gravel) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - g.fall(g, pos, w) +func (g Gravel) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + g.fall(g, pos, tx) } // BreakInfo ... diff --git a/server/block/grindstone.go b/server/block/grindstone.go index 785c7f85e..ee2a7090f 100644 --- a/server/block/grindstone.go +++ b/server/block/grindstone.go @@ -26,7 +26,7 @@ func (g Grindstone) BreakInfo() BreakInfo { } // Activate ... -func (g Grindstone) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.User, _ *item.UseContext) bool { +func (g Grindstone) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { if opener, ok := u.(ContainerOpener); ok { opener.OpenBlockContainer(pos) return true @@ -35,8 +35,8 @@ func (g Grindstone) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.U } // UseOnBlock ... -func (g Grindstone) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, face, used = firstReplaceable(w, pos, face, g) +func (g Grindstone) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, face, used = firstReplaceable(tx, pos, face, g) if !used { return false } @@ -47,22 +47,22 @@ func (g Grindstone) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *wo g.Attach = WallGrindstoneAttachment() g.Facing = face.Direction() } - place(w, pos, g, user, ctx) + place(tx, pos, g, user, ctx) return placed(ctx) } // NeighbourUpdateTick ... -func (g Grindstone) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { +func (g Grindstone) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { supportFace := g.Facing.Face().Opposite() if g.Attach == HangingGrindstoneAttachment() { supportFace = cube.FaceUp } else if g.Attach == StandingGrindstoneAttachment() { supportFace = cube.FaceDown } - if _, ok := w.Block(pos.Side(supportFace)).Model().(model.Empty); ok { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: g}) - dropItem(w, item.NewStack(g, 1), pos.Vec3Centre()) + if _, ok := tx.Block(pos.Side(supportFace)).Model().(model.Empty); ok { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: g}) + dropItem(tx, item.NewStack(g, 1), pos.Vec3Centre()) } } diff --git a/server/block/hay_bale.go b/server/block/hay_bale.go index b905a6eff..e8fea0a78 100644 --- a/server/block/hay_bale.go +++ b/server/block/hay_bale.go @@ -23,21 +23,21 @@ func (HayBale) Instrument() sound.Instrument { } // EntityLand ... -func (h HayBale) EntityLand(_ cube.Pos, _ *world.World, e world.Entity, distance *float64) { +func (h HayBale) EntityLand(pos cube.Pos, tx *world.Tx, e world.Entity, distance *float64) { if _, ok := e.(fallDistanceEntity); ok { *distance *= 0.2 } } // UseOnBlock ... -func (h HayBale) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, face, used = firstReplaceable(w, pos, face, h) +func (h HayBale) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, face, used = firstReplaceable(tx, pos, face, h) if !used { return } h.Axis = face.Axis() - place(w, pos, h, user, ctx) + place(tx, pos, h, user, ctx) return placed(ctx) } diff --git a/server/block/hopper.go b/server/block/hopper.go index c62a0e31e..35a036c06 100644 --- a/server/block/hopper.go +++ b/server/block/hopper.go @@ -63,21 +63,21 @@ func (Hopper) Model() world.BlockModel { } // SideClosed ... -func (Hopper) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (Hopper) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } // BreakInfo ... func (h Hopper) BreakInfo() BreakInfo { - return newBreakInfo(3, pickaxeHarvestable, pickaxeEffective, oneOf(h)).withBlastResistance(24).withBreakHandler(func(pos cube.Pos, w *world.World, u item.User) { - for _, i := range h.Inventory(w, pos).Clear() { - dropItem(w, i, pos.Vec3()) + return newBreakInfo(3, pickaxeHarvestable, pickaxeEffective, oneOf(h)).withBlastResistance(24).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { + for _, i := range h.Inventory(tx, pos).Clear() { + dropItem(tx, i, pos.Vec3()) } }) } // Inventory returns the inventory of the hopper. -func (h Hopper) Inventory(*world.World, cube.Pos) *inventory.Inventory { +func (h Hopper) Inventory(*world.Tx, cube.Pos) *inventory.Inventory { return h.inventory } @@ -88,21 +88,21 @@ func (h Hopper) WithName(a ...any) world.Item { } // AddViewer adds a viewer to the hopper, so that it is updated whenever the inventory of the hopper is changed. -func (h Hopper) AddViewer(v ContainerViewer, _ *world.World, _ cube.Pos) { +func (h Hopper) AddViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) { h.viewerMu.Lock() defer h.viewerMu.Unlock() h.viewers[v] = struct{}{} } // RemoveViewer removes a viewer from the hopper, so that slot updates in the inventory are no longer sent to it. -func (h Hopper) RemoveViewer(v ContainerViewer, _ *world.World, _ cube.Pos) { +func (h Hopper) RemoveViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) { h.viewerMu.Lock() defer h.viewerMu.Unlock() delete(h.viewers, v) } // Activate ... -func (Hopper) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.User, _ *item.UseContext) bool { +func (Hopper) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { if opener, ok := u.(ContainerOpener); ok { opener.OpenBlockContainer(pos) return true @@ -111,8 +111,8 @@ func (Hopper) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.User, _ } // UseOnBlock ... -func (h Hopper) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, h) +func (h Hopper) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, h) if !used { return false } @@ -124,40 +124,40 @@ func (h Hopper) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world. h.Facing = face.Opposite() } - place(w, pos, h, user, ctx) + place(tx, pos, h, user, ctx) return placed(ctx) } // Tick ... -func (h Hopper) Tick(currentTick int64, pos cube.Pos, w *world.World) { +func (h Hopper) Tick(currentTick int64, pos cube.Pos, tx *world.Tx) { h.TransferCooldown-- h.CollectCooldown-- h.LastTick = currentTick if !h.Powered && h.TransferCooldown <= 0 { - inserted := h.insertItem(pos, w) - extracted := h.extractItem(pos, w) + inserted := h.insertItem(pos, tx) + extracted := h.extractItem(pos, tx) if inserted || extracted { h.TransferCooldown = 8 } } - w.SetBlock(pos, h, nil) + tx.SetBlock(pos, h, nil) } // HopperInsertable represents a block that can have its contents inserted into by a hopper. type HopperInsertable interface { // InsertItem handles the insert logic for that block. - InsertItem(h Hopper, pos cube.Pos, w *world.World) bool + InsertItem(h Hopper, pos cube.Pos, tx *world.Tx) bool } // insertItem inserts an item into a block that can receive contents from the hopper. -func (h Hopper) insertItem(pos cube.Pos, w *world.World) bool { +func (h Hopper) insertItem(pos cube.Pos, tx *world.Tx) bool { destPos := pos.Side(h.Facing) - dest := w.Block(destPos) + dest := tx.Block(destPos) if e, ok := dest.(HopperInsertable); ok { - return e.InsertItem(h, pos.Side(h.Facing), w) + return e.InsertItem(h, pos.Side(h.Facing), tx) } if container, ok := dest.(Container); ok { @@ -166,7 +166,7 @@ func (h Hopper) insertItem(pos cube.Pos, w *world.World) bool { continue } - _, err := container.Inventory(w, pos).AddItem(sourceStack.Grow(-sourceStack.Count() + 1)) + _, err := container.Inventory(tx, pos).AddItem(sourceStack.Grow(-sourceStack.Count() + 1)) if err != nil { // The destination is full. return false @@ -176,7 +176,7 @@ func (h Hopper) insertItem(pos cube.Pos, w *world.World) bool { if hopper, ok := dest.(Hopper); ok { hopper.TransferCooldown = 8 - w.SetBlock(destPos, hopper, nil) + tx.SetBlock(destPos, hopper, nil) } return true @@ -188,20 +188,20 @@ func (h Hopper) insertItem(pos cube.Pos, w *world.World) bool { // HopperExtractable represents a block that can have its contents extracted by a hopper. type HopperExtractable interface { // ExtractItem handles the extract logic for that block. - ExtractItem(h Hopper, pos cube.Pos, w *world.World) bool + ExtractItem(h Hopper, pos cube.Pos, tx *world.Tx) bool } // extractItem extracts an item from a container into the hopper. -func (h Hopper) extractItem(pos cube.Pos, w *world.World) bool { +func (h Hopper) extractItem(pos cube.Pos, tx *world.Tx) bool { originPos := pos.Side(cube.FaceUp) - origin := w.Block(originPos) + origin := tx.Block(originPos) if e, ok := origin.(HopperExtractable); ok { - return e.ExtractItem(h, pos, w) + return e.ExtractItem(h, pos, tx) } if containerOrigin, ok := origin.(Container); ok { - for slot, stack := range containerOrigin.Inventory(w, originPos).Slots() { + for slot, stack := range containerOrigin.Inventory(tx, originPos).Slots() { if stack.Empty() { // We don't have any items to extract. continue @@ -213,11 +213,11 @@ func (h Hopper) extractItem(pos cube.Pos, w *world.World) bool { continue } - _ = containerOrigin.Inventory(w, originPos).SetItem(slot, stack.Grow(-1)) + _ = containerOrigin.Inventory(tx, originPos).SetItem(slot, stack.Grow(-1)) if hopper, ok := origin.(Hopper); ok { hopper.TransferCooldown = 8 - w.SetBlock(originPos, hopper, nil) + tx.SetBlock(originPos, hopper, nil) } return true diff --git a/server/block/iron_bars.go b/server/block/iron_bars.go index 83c1dad31..24ecdceb3 100644 --- a/server/block/iron_bars.go +++ b/server/block/iron_bars.go @@ -18,7 +18,7 @@ func (i IronBars) BreakInfo() BreakInfo { } // SideClosed ... -func (i IronBars) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (i IronBars) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } diff --git a/server/block/item_frame.go b/server/block/item_frame.go index aef2a71d5..0b3395c69 100644 --- a/server/block/item_frame.go +++ b/server/block/item_frame.go @@ -32,26 +32,26 @@ type ItemFrame struct { } // Activate ... -func (i ItemFrame) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, ctx *item.UseContext) bool { +func (i ItemFrame) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { if !i.Item.Empty() { // TODO: Item frames with maps can only be rotated four times. i.Rotations = (i.Rotations + 1) % 8 - w.PlaySound(pos.Vec3Centre(), sound.ItemFrameRotate{}) + tx.PlaySound(pos.Vec3Centre(), sound.ItemFrameRotate{}) } else if held, _ := u.HeldItems(); !held.Empty() { i.Item = held.Grow(-held.Count() + 1) // TODO: When maps are implemented, check the item is a map, and if so, display the large version of the frame. ctx.SubtractFromCount(1) - w.PlaySound(pos.Vec3Centre(), sound.ItemAdd{}) + tx.PlaySound(pos.Vec3Centre(), sound.ItemAdd{}) } else { return true } - w.SetBlock(pos, i, nil) + tx.SetBlock(pos, i, nil) return true } // Punch ... -func (i ItemFrame) Punch(pos cube.Pos, _ cube.Face, w *world.World, u item.User) { +func (i ItemFrame) Punch(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User) { if i.Item.Empty() { return } @@ -60,28 +60,28 @@ func (i ItemFrame) Punch(pos cube.Pos, _ cube.Face, w *world.World, u item.User) GameMode() world.GameMode }); ok { if rand.Float64() <= i.DropChance && !g.GameMode().CreativeInventory() { - dropItem(w, i.Item, pos.Vec3Centre()) + dropItem(tx, i.Item, pos.Vec3Centre()) } } i.Item, i.Rotations = item.Stack{}, 0 - w.PlaySound(pos.Vec3Centre(), sound.ItemFrameRemove{}) - w.SetBlock(pos, i, nil) + tx.PlaySound(pos.Vec3Centre(), sound.ItemFrameRemove{}) + tx.SetBlock(pos, i, nil) } // UseOnBlock ... -func (i ItemFrame) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, face, used := firstReplaceable(w, pos, face, i) +func (i ItemFrame) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, face, used := firstReplaceable(tx, pos, face, i) if !used { return false } - if _, ok := w.Block(pos.Side(face.Opposite())).Model().(model.Empty); ok { + if _, ok := tx.Block(pos.Side(face.Opposite())).Model().(model.Empty); ok { // TODO: Allow exceptions for pressure plates. return false } i.Facing = face.Opposite() i.DropChance = 1.0 - place(w, pos, i, user, ctx) + place(tx, pos, i, user, ctx) return placed(ctx) } @@ -144,19 +144,19 @@ func (i ItemFrame) Pick() item.Stack { } // SideClosed ... -func (ItemFrame) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (ItemFrame) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } // NeighbourUpdateTick ... -func (i ItemFrame) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if _, ok := w.Block(pos.Side(i.Facing)).Model().(model.Empty); ok { +func (i ItemFrame) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if _, ok := tx.Block(pos.Side(i.Facing)).Model().(model.Empty); ok { // TODO: Allow exceptions for pressure plates. - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: i}) - dropItem(w, item.NewStack(i, 1), pos.Vec3Centre()) + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: i}) + dropItem(tx, item.NewStack(i, 1), pos.Vec3Centre()) if !i.Item.Empty() { - dropItem(w, i.Item, pos.Vec3Centre()) + dropItem(tx, i.Item, pos.Vec3Centre()) } } } diff --git a/server/block/jukebox.go b/server/block/jukebox.go index 67acb96ae..2dcd947ca 100644 --- a/server/block/jukebox.go +++ b/server/block/jukebox.go @@ -20,7 +20,7 @@ type Jukebox struct { } // InsertItem ... -func (j Jukebox) InsertItem(h Hopper, pos cube.Pos, w *world.World) bool { +func (j Jukebox) InsertItem(h Hopper, pos cube.Pos, tx *world.Tx) bool { if !j.Item.Empty() { return false } @@ -32,9 +32,9 @@ func (j Jukebox) InsertItem(h Hopper, pos cube.Pos, w *world.World) bool { if m, ok := sourceStack.Item().(item.MusicDisc); ok { j.Item = sourceStack - w.SetBlock(pos, j, nil) + tx.SetBlock(pos, j, nil) _ = h.inventory.SetItem(sourceSlot, sourceStack.Grow(-1)) - w.PlaySound(pos.Vec3Centre(), sound.MusicDiscPlay{DiscType: m.DiscType}) + tx.PlaySound(pos.Vec3Centre(), sound.MusicDiscPlay{DiscType: m.DiscType}) return true } } @@ -43,8 +43,8 @@ func (j Jukebox) InsertItem(h Hopper, pos cube.Pos, w *world.World) bool { } // ExtractItem ... -func (j Jukebox) ExtractItem(h Hopper, pos cube.Pos, w *world.World) bool { - //TODO: This functionality requires redstone to be implemented. +func (j Jukebox) ExtractItem(h Hopper, pos cube.Pos, tx *world.Tx) bool { + // TODO: This functionality requires redstone to be implemented. return false } @@ -59,10 +59,10 @@ func (j Jukebox) BreakInfo() BreakInfo { if !j.Item.Empty() { d = append(d, j.Item) } - return newBreakInfo(0.8, alwaysHarvestable, axeEffective, simpleDrops(d...)).withBreakHandler(func(pos cube.Pos, w *world.World, u item.User) { + return newBreakInfo(0.8, alwaysHarvestable, axeEffective, simpleDrops(d...)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { if _, hasDisc := j.Disc(); hasDisc { - dropItem(w, j.Item, pos.Vec3()) - w.PlaySound(pos.Vec3Centre(), sound.MusicDiscEnd{}) + dropItem(tx, j.Item, pos.Vec3()) + tx.PlaySound(pos.Vec3Centre(), sound.MusicDiscEnd{}) } }) } @@ -75,22 +75,22 @@ type jukeboxUser interface { } // Activate ... -func (j Jukebox) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, ctx *item.UseContext) bool { +func (j Jukebox) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { if _, hasDisc := j.Disc(); hasDisc { - dropItem(w, j.Item, pos.Side(cube.FaceUp).Vec3Middle()) + dropItem(tx, j.Item, pos.Side(cube.FaceUp).Vec3Middle()) j.Item = item.Stack{} - w.SetBlock(pos, j, nil) - w.PlaySound(pos.Vec3Centre(), sound.MusicDiscEnd{}) + tx.SetBlock(pos, j, nil) + tx.PlaySound(pos.Vec3Centre(), sound.MusicDiscEnd{}) } else if held, _ := u.HeldItems(); !held.Empty() { if m, ok := held.Item().(item.MusicDisc); ok { j.Item = held - w.SetBlock(pos, j, nil) - w.PlaySound(pos.Vec3Centre(), sound.MusicDiscEnd{}) + tx.SetBlock(pos, j, nil) + tx.PlaySound(pos.Vec3Centre(), sound.MusicDiscEnd{}) ctx.SubtractFromCount(1) - w.PlaySound(pos.Vec3Centre(), sound.MusicDiscPlay{DiscType: m.DiscType}) + tx.PlaySound(pos.Vec3Centre(), sound.MusicDiscPlay{DiscType: m.DiscType}) if u, ok := u.(jukeboxUser); ok { u.SendJukeboxPopup(fmt.Sprintf("Now playing: %v - %v", m.DiscType.Author(), m.DiscType.DisplayName())) } diff --git a/server/block/kelp.go b/server/block/kelp.go index 84ce49c24..6d8b8fa70 100644 --- a/server/block/kelp.go +++ b/server/block/kelp.go @@ -24,10 +24,10 @@ func (k Kelp) SmeltInfo() item.SmeltInfo { } // BoneMeal ... -func (k Kelp) BoneMeal(pos cube.Pos, w *world.World) bool { - for y := pos.Y(); y <= w.Range()[1]; y++ { +func (k Kelp) BoneMeal(pos cube.Pos, tx *world.Tx) bool { + for y := pos.Y(); y <= tx.Range()[1]; y++ { currentPos := cube.Pos{pos.X(), y, pos.Z()} - block := w.Block(currentPos) + block := tx.Block(currentPos) if kelp, ok := block.(Kelp); ok { if kelp.Age == 25 { break @@ -35,7 +35,7 @@ func (k Kelp) BoneMeal(pos cube.Pos, w *world.World) bool { continue } if water, ok := block.(Water); ok && water.Depth == 8 { - w.SetBlock(currentPos, Kelp{Age: k.Age + 1}, nil) + tx.SetBlock(currentPos, Kelp{Age: k.Age + 1}, nil) return true } break @@ -64,7 +64,7 @@ func (k Kelp) EncodeBlock() (name string, properties map[string]any) { } // SideClosed will always return false since kelp doesn't close any side. -func (Kelp) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (Kelp) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } @@ -75,21 +75,21 @@ func (k Kelp) withRandomAge() Kelp { } // UseOnBlock ... -func (k Kelp) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, _, used = firstReplaceable(w, pos, face, k) +func (k Kelp) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, k) if !used { return } below := pos.Side(cube.FaceDown) - belowBlock := w.Block(below) + belowBlock := tx.Block(below) if _, kelp := belowBlock.(Kelp); !kelp { - if !belowBlock.Model().FaceSolid(below, cube.FaceUp, w) { + if !belowBlock.Model().FaceSolid(below, cube.FaceUp, tx) { return false } } - liquid, ok := w.Liquid(pos) + liquid, ok := tx.Liquid(pos) if !ok { return false } else if _, ok := liquid.(Water); !ok || liquid.LiquidDepth() < 8 { @@ -97,48 +97,48 @@ func (k Kelp) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.Wo } // When first placed, kelp gets a random age between 0 and 24. - place(w, pos, k.withRandomAge(), user, ctx) + place(tx, pos, k.withRandomAge(), user, ctx) return placed(ctx) } // NeighbourUpdateTick ... -func (k Kelp) NeighbourUpdateTick(pos, changed cube.Pos, w *world.World) { - if _, ok := w.Liquid(pos); !ok { - w.SetBlock(pos, nil, nil) +func (k Kelp) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if _, ok := tx.Liquid(pos); !ok { + tx.SetBlock(pos, nil, nil) return } - if changed.Y()-1 == pos.Y() { + if changedNeighbour[1]-1 == pos.Y() { // When a kelp block is broken above, the kelp block underneath it gets a new random age. - w.SetBlock(pos, k.withRandomAge(), nil) + tx.SetBlock(pos, k.withRandomAge(), nil) } below := pos.Side(cube.FaceDown) - belowBlock := w.Block(below) + belowBlock := tx.Block(below) if _, kelp := belowBlock.(Kelp); !kelp { - if !belowBlock.Model().FaceSolid(below, cube.FaceUp, w) { - w.SetBlock(pos, nil, nil) + if !belowBlock.Model().FaceSolid(below, cube.FaceUp, tx) { + tx.SetBlock(pos, nil, nil) } } } // RandomTick ... -func (k Kelp) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { +func (k Kelp) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { // Every random tick, there's a 14% chance for Kelp to grow if its age is below 25. if r.Intn(100) < 15 && k.Age < 25 { abovePos := pos.Side(cube.FaceUp) - liquid, ok := w.Liquid(abovePos) + liquid, ok := tx.Liquid(abovePos) // For kelp to grow, there must be only water above. if !ok { return } else if _, ok := liquid.(Water); ok { - switch w.Block(abovePos).(type) { + switch tx.Block(abovePos).(type) { case Air, Water: - w.SetBlock(abovePos, Kelp{Age: k.Age + 1}, nil) + tx.SetBlock(abovePos, Kelp{Age: k.Age + 1}, nil) if liquid.LiquidDepth() < 8 { // When kelp grows into a water block, the water block becomes a source block. - w.SetLiquid(abovePos, Water{Still: true, Depth: 8, Falling: false}) + tx.SetLiquid(abovePos, Water{Still: true, Depth: 8, Falling: false}) } } } diff --git a/server/block/ladder.go b/server/block/ladder.go index 48e26076b..56caf9324 100644 --- a/server/block/ladder.go +++ b/server/block/ladder.go @@ -22,27 +22,27 @@ type Ladder struct { } // NeighbourUpdateTick ... -func (l Ladder) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if _, ok := w.Block(pos.Side(l.Facing.Opposite())).(LightDiffuser); ok { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: l}) - dropItem(w, item.NewStack(l, 1), pos.Vec3Centre()) +func (l Ladder) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if _, ok := tx.Block(pos.Side(l.Facing.Opposite())).(LightDiffuser); ok { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: l}) + dropItem(tx, item.NewStack(l, 1), pos.Vec3Centre()) } } // UseOnBlock ... -func (l Ladder) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, face, used := firstReplaceable(w, pos, face, l) +func (l Ladder) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, face, used := firstReplaceable(tx, pos, face, l) if !used { return false } if face == cube.FaceUp || face == cube.FaceDown { return false } - if _, ok := w.Block(pos.Side(face.Opposite())).(LightDiffuser); ok { + if _, ok := tx.Block(pos.Side(face.Opposite())).(LightDiffuser); ok { found := false for _, i := range []cube.Face{cube.FaceSouth, cube.FaceNorth, cube.FaceEast, cube.FaceWest} { - if diffuser, ok := w.Block(pos.Side(i)).(LightDiffuser); !ok || diffuser.LightDiffusionLevel() == 15 { + if diffuser, ok := tx.Block(pos.Side(i)).(LightDiffuser); !ok || diffuser.LightDiffusionLevel() == 15 { found = true face = i.Opposite() break @@ -54,19 +54,19 @@ func (l Ladder) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world. } l.Facing = face - place(w, pos, l, user, ctx) + place(tx, pos, l, user, ctx) return placed(ctx) } // EntityInside ... -func (l Ladder) EntityInside(_ cube.Pos, _ *world.World, e world.Entity) { +func (l Ladder) EntityInside(pos cube.Pos, tx *world.Tx, e world.Entity) { if fallEntity, ok := e.(fallDistanceEntity); ok { fallEntity.ResetFallDistance() } } // SideClosed ... -func (l Ladder) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (l Ladder) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } diff --git a/server/block/lantern.go b/server/block/lantern.go index 18c574e4a..206c838ab 100644 --- a/server/block/lantern.go +++ b/server/block/lantern.go @@ -25,18 +25,18 @@ func (l Lantern) Model() world.BlockModel { } // NeighbourUpdateTick ... -func (l Lantern) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { +func (l Lantern) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { if l.Hanging { up := pos.Side(cube.FaceUp) - if _, ok := w.Block(up).(Chain); !ok && !w.Block(up).Model().FaceSolid(up, cube.FaceDown, w) { - w.SetBlock(pos, nil, nil) - dropItem(w, item.NewStack(l, 1), pos.Vec3Centre()) + if _, ok := tx.Block(up).(Chain); !ok && !tx.Block(up).Model().FaceSolid(up, cube.FaceDown, tx) { + tx.SetBlock(pos, nil, nil) + dropItem(tx, item.NewStack(l, 1), pos.Vec3Centre()) } } else { down := pos.Side(cube.FaceDown) - if !w.Block(down).Model().FaceSolid(down, cube.FaceUp, w) { - w.SetBlock(pos, nil, nil) - dropItem(w, item.NewStack(l, 1), pos.Vec3Centre()) + if !tx.Block(down).Model().FaceSolid(down, cube.FaceUp, tx) { + tx.SetBlock(pos, nil, nil) + dropItem(tx, item.NewStack(l, 1), pos.Vec3Centre()) } } } @@ -47,31 +47,31 @@ func (l Lantern) LightEmissionLevel() uint8 { } // UseOnBlock ... -func (l Lantern) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, face, used := firstReplaceable(w, pos, face, l) +func (l Lantern) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, face, used := firstReplaceable(tx, pos, face, l) if !used { return false } if face == cube.FaceDown { upPos := pos.Side(cube.FaceUp) - if _, ok := w.Block(upPos).(Chain); !ok && !w.Block(upPos).Model().FaceSolid(upPos, cube.FaceDown, w) { + if _, ok := tx.Block(upPos).(Chain); !ok && !tx.Block(upPos).Model().FaceSolid(upPos, cube.FaceDown, tx) { face = cube.FaceUp } } if face != cube.FaceDown { downPos := pos.Side(cube.FaceDown) - if !w.Block(downPos).Model().FaceSolid(downPos, cube.FaceUp, w) { + if !tx.Block(downPos).Model().FaceSolid(downPos, cube.FaceUp, tx) { return false } } l.Hanging = face == cube.FaceDown - place(w, pos, l, user, ctx) + place(tx, pos, l, user, ctx) return placed(ctx) } // SideClosed ... -func (l Lantern) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (l Lantern) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } diff --git a/server/block/lava.go b/server/block/lava.go index 2cfd934a8..69c0b9ba2 100644 --- a/server/block/lava.go +++ b/server/block/lava.go @@ -26,9 +26,9 @@ type Lava struct { } // neighboursLavaFlammable returns true if one a block adjacent to the passed position is flammable. -func neighboursLavaFlammable(pos cube.Pos, w *world.World) bool { +func neighboursLavaFlammable(pos cube.Pos, tx *world.Tx) bool { for i := cube.Face(0); i < 6; i++ { - if flammable, ok := w.Block(pos.Side(i)).(Flammable); ok && flammable.FlammabilityInfo().LavaFlammable { + if flammable, ok := tx.Block(pos.Side(i)).(Flammable); ok && flammable.FlammabilityInfo().LavaFlammable { return true } } @@ -36,7 +36,7 @@ func neighboursLavaFlammable(pos cube.Pos, w *world.World) bool { } // EntityInside ... -func (l Lava) EntityInside(_ cube.Pos, _ *world.World, e world.Entity) { +func (l Lava) EntityInside(pos cube.Pos, tx *world.Tx, e world.Entity) { if fallEntity, ok := e.(fallDistanceEntity); ok { fallEntity.ResetFallDistance() } @@ -49,23 +49,23 @@ func (l Lava) EntityInside(_ cube.Pos, _ *world.World, e world.Entity) { } // RandomTick ... -func (l Lava) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { +func (l Lava) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { i := r.Intn(3) if i > 0 { for j := 0; j < i; j++ { pos = pos.Add(cube.Pos{r.Intn(3) - 1, 1, r.Intn(3) - 1}) - if _, ok := w.Block(pos).(Air); ok { - if neighboursLavaFlammable(pos, w) { - w.SetBlock(pos, Fire{}, nil) + if _, ok := tx.Block(pos).(Air); ok { + if neighboursLavaFlammable(pos, tx) { + tx.SetBlock(pos, Fire{}, nil) } } } } else { for j := 0; j < 3; j++ { pos = pos.Add(cube.Pos{r.Intn(3) - 1, 0, r.Intn(3) - 1}) - if _, ok := w.Block(pos.Side(cube.FaceUp)).(Air); ok { - if flammable, ok := w.Block(pos).(Flammable); ok && flammable.FlammabilityInfo().LavaFlammable && flammable.FlammabilityInfo().Encouragement > 0 { - w.SetBlock(pos, Fire{}, nil) + if _, ok := tx.Block(pos.Side(cube.FaceUp)).(Air); ok { + if flammable, ok := tx.Block(pos).(Flammable); ok && flammable.FlammabilityInfo().LavaFlammable && flammable.FlammabilityInfo().Encouragement > 0 { + tx.SetBlock(pos, Fire{}, nil) } } } @@ -88,16 +88,16 @@ func (Lava) LightEmissionLevel() uint8 { } // NeighbourUpdateTick ... -func (l Lava) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if !l.Harden(pos, w, nil) { - w.ScheduleBlockUpdate(pos, w.Dimension().LavaSpreadDuration()) +func (l Lava) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if !l.Harden(pos, tx, nil) { + tx.ScheduleBlockUpdate(pos, tx.World().Dimension().LavaSpreadDuration()) } } // ScheduledTick ... -func (l Lava) ScheduledTick(pos cube.Pos, w *world.World, _ *rand.Rand) { - if !l.Harden(pos, w, nil) { - tickLiquid(l, pos, w) +func (l Lava) ScheduledTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { + if !l.Harden(pos, tx, nil) { + tickLiquid(l, pos, tx) } } @@ -135,24 +135,24 @@ func (Lava) LiquidType() string { } // Harden handles the hardening logic of lava. -func (l Lava) Harden(pos cube.Pos, w *world.World, flownIntoBy *cube.Pos) bool { +func (l Lava) Harden(pos cube.Pos, tx *world.Tx, flownIntoBy *cube.Pos) bool { var ok bool var water, b world.Block if flownIntoBy == nil { var water, b world.Block - _, soulSoilFound := w.Block(pos.Side(cube.FaceDown)).(SoulSoil) + _, soulSoilFound := tx.Block(pos.Side(cube.FaceDown)).(SoulSoil) pos.Neighbours(func(neighbour cube.Pos) { if b != nil || neighbour[1] == pos[1]-1 { return } - if _, ok := w.Block(neighbour).(BlueIce); ok { + if _, ok := tx.Block(neighbour).(BlueIce); ok { if soulSoilFound { b = Basalt{} } return } - if waterBlock, ok := w.Block(neighbour).(Water); ok { + if waterBlock, ok := tx.Block(neighbour).(Water); ok { water = waterBlock if l.Depth == 8 && !l.Falling { b = Obsidian{} @@ -160,19 +160,19 @@ func (l Lava) Harden(pos cube.Pos, w *world.World, flownIntoBy *cube.Pos) bool { } b = Cobblestone{} } - }, w.Range()) + }, tx.Range()) if b != nil { ctx := event.C() - if w.Handler().HandleLiquidHarden(ctx, pos, l, water, b); ctx.Cancelled() { + if tx.World().Handler().HandleLiquidHarden(ctx, pos, l, water, b); ctx.Cancelled() { return false } - w.PlaySound(pos.Vec3Centre(), sound.Fizz{}) - w.SetBlock(pos, b, nil) + tx.PlaySound(pos.Vec3Centre(), sound.Fizz{}) + tx.SetBlock(pos, b, nil) return true } return false } - water, ok = w.Block(*flownIntoBy).(Water) + water, ok = tx.Block(*flownIntoBy).(Water) if !ok { return false } @@ -183,11 +183,11 @@ func (l Lava) Harden(pos cube.Pos, w *world.World, flownIntoBy *cube.Pos) bool { b = Cobblestone{} } ctx := event.C() - if w.Handler().HandleLiquidHarden(ctx, pos, l, water, b); ctx.Cancelled() { + if tx.World().Handler().HandleLiquidHarden(ctx, pos, l, water, b); ctx.Cancelled() { return false } - w.SetBlock(pos, b, nil) - w.PlaySound(pos.Vec3Centre(), sound.Fizz{}) + tx.SetBlock(pos, b, nil) + tx.PlaySound(pos.Vec3Centre(), sound.Fizz{}) return true } diff --git a/server/block/leaves.go b/server/block/leaves.go index 70ea49c86..f8868615b 100644 --- a/server/block/leaves.go +++ b/server/block/leaves.go @@ -24,19 +24,19 @@ type Leaves struct { } // UseOnBlock makes leaves persistent when they are placed so that they don't decay. -func (l Leaves) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, _, used = firstReplaceable(w, pos, face, l) +func (l Leaves) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, l) if !used { return } l.Persistent = true - place(w, pos, l, user, ctx) + place(tx, pos, l, user, ctx) return placed(ctx) } // findLog ... -func findLog(pos cube.Pos, w *world.World, visited *[]cube.Pos, distance int) bool { +func findLog(pos cube.Pos, tx *world.Tx, visited *[]cube.Pos, distance int) bool { for _, v := range *visited { if v == pos { return false @@ -44,38 +44,38 @@ func findLog(pos cube.Pos, w *world.World, visited *[]cube.Pos, distance int) bo } *visited = append(*visited, pos) - if log, ok := w.Block(pos).(Log); ok && !log.Stripped { + if log, ok := tx.Block(pos).(Log); ok && !log.Stripped { return true } - if _, ok := w.Block(pos).(Leaves); !ok || distance > 6 { + if _, ok := tx.Block(pos).(Leaves); !ok || distance > 6 { return false } logFound := false pos.Neighbours(func(neighbour cube.Pos) { - if !logFound && findLog(neighbour, w, visited, distance+1) { + if !logFound && findLog(neighbour, tx, visited, distance+1) { logFound = true } - }, w.Range()) + }, tx.Range()) return logFound } // RandomTick ... -func (l Leaves) RandomTick(pos cube.Pos, w *world.World, _ *rand.Rand) { +func (l Leaves) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { if !l.Persistent && l.ShouldUpdate { - if findLog(pos, w, &[]cube.Pos{}, 0) { + if findLog(pos, tx, &[]cube.Pos{}, 0) { l.ShouldUpdate = false - w.SetBlock(pos, l, nil) + tx.SetBlock(pos, l, nil) } else { - w.SetBlock(pos, nil, nil) + tx.SetBlock(pos, nil, nil) } } } // NeighbourUpdateTick ... -func (l Leaves) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { +func (l Leaves) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { if !l.Persistent && !l.ShouldUpdate { l.ShouldUpdate = true - w.SetBlock(pos, l, nil) + tx.SetBlock(pos, l, nil) } } @@ -117,7 +117,7 @@ func (Leaves) LightDiffusionLevel() uint8 { } // SideClosed ... -func (Leaves) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (Leaves) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } diff --git a/server/block/lectern.go b/server/block/lectern.go index 9ea0fc449..91abd39bd 100644 --- a/server/block/lectern.go +++ b/server/block/lectern.go @@ -38,7 +38,7 @@ func (Lectern) FuelInfo() item.FuelInfo { } // SideClosed ... -func (Lectern) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (Lectern) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } @@ -52,13 +52,13 @@ func (l Lectern) BreakInfo() BreakInfo { } // UseOnBlock ... -func (l Lectern) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, _, used = firstReplaceable(w, pos, face, l) +func (l Lectern) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, l) if !used { return false } l.Facing = user.Rotation().Direction().Opposite() - place(w, pos, l, user, ctx) + place(tx, pos, l, user, ctx) return placed(ctx) } @@ -72,7 +72,7 @@ type readableBook interface { } // Activate ... -func (l Lectern) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, ctx *item.UseContext) bool { +func (l Lectern) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { if !l.Book.Empty() { // We can't put a book on the lectern if it's full. return false @@ -85,29 +85,29 @@ func (l Lectern) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User } l.Book, l.Page = held, 0 - w.SetBlock(pos, l, nil) + tx.SetBlock(pos, l, nil) - w.PlaySound(pos.Vec3Centre(), sound.LecternBookPlace{}) + tx.PlaySound(pos.Vec3Centre(), sound.LecternBookPlace{}) ctx.SubtractFromCount(1) return true } // Punch ... -func (l Lectern) Punch(pos cube.Pos, _ cube.Face, w *world.World, _ item.User) { +func (l Lectern) Punch(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User) { if l.Book.Empty() { // We can't remove a book from the lectern if there isn't one. return } - dropItem(w, l.Book, pos.Side(cube.FaceUp).Vec3Middle()) + dropItem(tx, l.Book, pos.Side(cube.FaceUp).Vec3Middle()) l.Book = item.Stack{} - w.SetBlock(pos, l, nil) - w.PlaySound(pos.Vec3Centre(), sound.Attack{}) + tx.SetBlock(pos, l, nil) + tx.PlaySound(pos.Vec3Centre(), sound.Attack{}) } // TurnPage updates the page the lectern is currently on to the page given. -func (l Lectern) TurnPage(pos cube.Pos, w *world.World, page int) error { +func (l Lectern) TurnPage(pos cube.Pos, tx *world.Tx, page int) error { if page == l.Page { // We're already on the correct page, so we don't need to do anything. return nil @@ -119,7 +119,7 @@ func (l Lectern) TurnPage(pos cube.Pos, w *world.World, page int) error { return fmt.Errorf("page number %d is out of bounds", page) } l.Page = page - w.SetBlock(pos, l, nil) + tx.SetBlock(pos, l, nil) return nil } diff --git a/server/block/light.go b/server/block/light.go index 49302ed71..3eb4b4b1a 100644 --- a/server/block/light.go +++ b/server/block/light.go @@ -19,7 +19,7 @@ type Light struct { } // SideClosed ... -func (Light) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (Light) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } diff --git a/server/block/liquid.go b/server/block/liquid.go index fdebc4ed2..fd22378f1 100644 --- a/server/block/liquid.go +++ b/server/block/liquid.go @@ -36,28 +36,28 @@ func (s flowingWaterDisplacer) CanDisplace(b world.Liquid) bool { // tickLiquid ticks the liquid block passed at a specific position in the world. Depending on the surroundings // and the liquid block, the liquid will either spread or decrease in depth. Additionally, the liquid might // be turned into a solid block if a different liquid is next to it. -func tickLiquid(b world.Liquid, pos cube.Pos, w *world.World) { - if !source(b) && !sourceAround(b, pos, w) { +func tickLiquid(b world.Liquid, pos cube.Pos, tx *world.Tx) { + if !source(b) && !sourceAround(b, pos, tx) { var res world.Liquid if b.LiquidDepth()-4 > 0 { res = b.WithDepth(b.LiquidDepth()-2*b.SpreadDecay(), false) } ctx := event.C() - if w.Handler().HandleLiquidDecay(ctx, pos, b, res); ctx.Cancelled() { + if tx.World().Handler().HandleLiquidDecay(ctx, pos, b, res); ctx.Cancelled() { return } - w.SetLiquid(pos, res) + tx.SetLiquid(pos, res) return } - displacer, _ := w.Block(pos).(world.LiquidDisplacer) + displacer, _ := tx.Block(pos).(world.LiquidDisplacer) - canFlowBelow := canFlowInto(b, w, pos.Side(cube.FaceDown), false) + canFlowBelow := canFlowInto(b, tx, pos.Side(cube.FaceDown), false) if b.LiquidFalling() && !canFlowBelow { b = b.WithDepth(8, true) } else if canFlowBelow { below := pos.Side(cube.FaceDown) - if displacer == nil || !displacer.SideClosed(pos, below, w) { - flowInto(b.WithDepth(8, true), pos, below, w, true) + if displacer == nil || !displacer.SideClosed(pos, below, tx) { + flowInto(b.WithDepth(8, true), pos, below, tx, true) } } @@ -67,16 +67,16 @@ func tickLiquid(b world.Liquid, pos cube.Pos, w *world.World) { return } if source(b) || !canFlowBelow { - paths := calculateLiquidPaths(b, pos, w, displacer) + paths := calculateLiquidPaths(b, pos, tx, displacer) if len(paths) == 0 { - spreadOutwards(b, pos, w, displacer) + spreadOutwards(b, pos, tx, displacer) return } smallestLen := len(paths[0]) for _, path := range paths { if len(path) <= smallestLen { - flowInto(b, pos, path[0], w, false) + flowInto(b, pos, path[0], tx, false) } } } @@ -88,28 +88,28 @@ func source(b world.Liquid) bool { } // spreadOutwards spreads the liquid outwards into the horizontal directions. -func spreadOutwards(b world.Liquid, pos cube.Pos, w *world.World, displacer world.LiquidDisplacer) { +func spreadOutwards(b world.Liquid, pos cube.Pos, tx *world.Tx, displacer world.LiquidDisplacer) { pos.Neighbours(func(neighbour cube.Pos) { if neighbour[1] == pos[1] { - if displacer == nil || !displacer.SideClosed(pos, neighbour, w) { - flowInto(b, pos, neighbour, w, false) + if displacer == nil || !displacer.SideClosed(pos, neighbour, tx) { + flowInto(b, pos, neighbour, tx, false) } } - }, w.Range()) + }, tx.Range()) } // sourceAround checks if there is a source in the blocks around the position passed. -func sourceAround(b world.Liquid, pos cube.Pos, w *world.World) (sourcePresent bool) { +func sourceAround(b world.Liquid, pos cube.Pos, tx *world.Tx) (sourcePresent bool) { pos.Neighbours(func(neighbour cube.Pos) { if neighbour[1] == pos[1]-1 { // We don't care about water below this one. return } - side, ok := w.Liquid(neighbour) + side, ok := tx.Liquid(neighbour) if !ok || side.LiquidType() != b.LiquidType() { return } - if displacer, ok := w.Block(neighbour).(world.LiquidDisplacer); ok && displacer.SideClosed(neighbour, pos, w) { + if displacer, ok := tx.Block(neighbour).(world.LiquidDisplacer); ok && displacer.SideClosed(neighbour, pos, tx) { // The side towards this liquid was closed, so this cannot function as a source for this // liquid. return @@ -117,13 +117,13 @@ func sourceAround(b world.Liquid, pos cube.Pos, w *world.World) (sourcePresent b if neighbour[1] == pos[1]+1 || source(side) || side.LiquidDepth() > b.LiquidDepth() { sourcePresent = true } - }, w.Range()) + }, tx.Range()) return } // flowInto makes the liquid passed flow into the position passed in a world. If successful, the block at that // position will be broken and the liquid with a lower depth will replace it. -func flowInto(b world.Liquid, src, pos cube.Pos, w *world.World, falling bool) bool { +func flowInto(b world.Liquid, src, pos cube.Pos, tx *world.Tx, falling bool) bool { newDepth := b.LiquidDepth() - b.SpreadDecay() if falling { newDepth = b.LiquidDepth() @@ -131,7 +131,7 @@ func flowInto(b world.Liquid, src, pos cube.Pos, w *world.World, falling bool) b if newDepth <= 0 && !falling { return false } - existing := w.Block(pos) + existing := tx.Block(pos) if existingLiquid, alsoLiquid := existing.(world.Liquid); alsoLiquid && existingLiquid.LiquidType() == b.LiquidType() { if existingLiquid.LiquidDepth() >= newDepth || existingLiquid.LiquidFalling() { // The existing liquid had a higher depth than the one we're propagating, or it was falling @@ -139,18 +139,18 @@ func flowInto(b world.Liquid, src, pos cube.Pos, w *world.World, falling bool) b return true } ctx := event.C() - if w.Handler().HandleLiquidFlow(ctx, src, pos, b.WithDepth(newDepth, falling), existing); ctx.Cancelled() { + if tx.World().Handler().HandleLiquidFlow(ctx, src, pos, b.WithDepth(newDepth, falling), existing); ctx.Cancelled() { return false } - w.SetLiquid(pos, b.WithDepth(newDepth, falling)) + tx.SetLiquid(pos, b.WithDepth(newDepth, falling)) return true } else if alsoLiquid { - existingLiquid.Harden(pos, w, &src) + existingLiquid.Harden(pos, tx, &src) return false } displacer, isDisplacer := existing.(world.LiquidDisplacer) if isDisplacer { - if _, ok := w.Liquid(pos); ok { + if _, ok := tx.Liquid(pos); ok { // We've got a liquid displacer, and it's got a liquid within it, so we can't flow into this. return false } @@ -161,25 +161,25 @@ func flowInto(b world.Liquid, src, pos cube.Pos, w *world.World, falling bool) b return false } ctx := event.C() - if w.Handler().HandleLiquidFlow(ctx, src, pos, b.WithDepth(newDepth, falling), existing); ctx.Cancelled() { + if tx.World().Handler().HandleLiquidFlow(ctx, src, pos, b.WithDepth(newDepth, falling), existing); ctx.Cancelled() { return false } if isRemovable { if _, air := existing.(Air); !air { - w.SetBlock(pos, nil, nil) + tx.SetBlock(pos, nil, nil) } if removable.HasLiquidDrops() { if b, ok := existing.(Breakable); ok { for _, d := range b.BreakInfo().Drops(item.ToolNone{}, nil) { - dropItem(w, d, pos.Vec3Centre()) + dropItem(tx, d, pos.Vec3Centre()) } } else { panic("liquid drops should always implement breakable") } } } - w.SetLiquid(pos, b.WithDepth(newDepth, falling)) + tx.SetLiquid(pos, b.WithDepth(newDepth, falling)) return true } @@ -190,7 +190,7 @@ type liquidPath []cube.Pos // calculateLiquidPaths calculates paths in the world that the liquid passed can flow in to reach lower // grounds, starting at the position passed. // If none of these paths can be found, the returned slice has a length of 0. -func calculateLiquidPaths(b world.Liquid, pos cube.Pos, w *world.World, displacer world.LiquidDisplacer) []liquidPath { +func calculateLiquidPaths(b world.Liquid, pos cube.Pos, tx *world.Tx, displacer world.LiquidDisplacer) []liquidPath { queue := liquidQueuePool.Get().(*liquidQueue) defer func() { queue.Reset() @@ -208,26 +208,26 @@ func calculateLiquidPaths(b world.Liquid, pos cube.Pos, w *world.World, displace } node := queue.Front() neighA, neighB, neighC, neighD := node.neighbours(decay * 2) - if !first || (displacer == nil || !displacer.SideClosed(pos, cube.Pos{neighA.x, pos[1], neighA.z}, w)) { - if spreadNeighbour(b, pos, w, neighA, queue) { + if !first || (displacer == nil || !displacer.SideClosed(pos, cube.Pos{neighA.x, pos[1], neighA.z}, tx)) { + if spreadNeighbour(b, pos, tx, neighA, queue) { queue.shortestPath = neighA.Len() paths = append(paths, neighA.Path(pos)) } } - if !first || (displacer == nil || !displacer.SideClosed(pos, cube.Pos{neighB.x, pos[1], neighB.z}, w)) { - if spreadNeighbour(b, pos, w, neighB, queue) { + if !first || (displacer == nil || !displacer.SideClosed(pos, cube.Pos{neighB.x, pos[1], neighB.z}, tx)) { + if spreadNeighbour(b, pos, tx, neighB, queue) { queue.shortestPath = neighB.Len() paths = append(paths, neighB.Path(pos)) } } - if !first || (displacer == nil || !displacer.SideClosed(pos, cube.Pos{neighC.x, pos[1], neighC.z}, w)) { - if spreadNeighbour(b, pos, w, neighC, queue) { + if !first || (displacer == nil || !displacer.SideClosed(pos, cube.Pos{neighC.x, pos[1], neighC.z}, tx)) { + if spreadNeighbour(b, pos, tx, neighC, queue) { queue.shortestPath = neighC.Len() paths = append(paths, neighC.Path(pos)) } } - if !first || (displacer == nil || !displacer.SideClosed(pos, cube.Pos{neighD.x, pos[1], neighD.z}, w)) { - if spreadNeighbour(b, pos, w, neighD, queue) { + if !first || (displacer == nil || !displacer.SideClosed(pos, cube.Pos{neighD.x, pos[1], neighD.z}, tx)) { + if spreadNeighbour(b, pos, tx, neighD, queue) { queue.shortestPath = neighD.Len() paths = append(paths, neighD.Path(pos)) } @@ -239,7 +239,7 @@ func calculateLiquidPaths(b world.Liquid, pos cube.Pos, w *world.World, displace // spreadNeighbour attempts to spread a path node into the neighbour passed. Note that this does not spread // the liquid, it only spreads the node used to calculate flow paths. -func spreadNeighbour(b world.Liquid, src cube.Pos, w *world.World, node liquidNode, queue *liquidQueue) bool { +func spreadNeighbour(b world.Liquid, src cube.Pos, tx *world.Tx, node liquidNode, queue *liquidQueue) bool { if node.depth+3 <= 0 { // Depth has reached zero or below, can't spread any further. return false @@ -249,12 +249,12 @@ func spreadNeighbour(b world.Liquid, src cube.Pos, w *world.World, node liquidNo return false } pos := cube.Pos{node.x, src[1], node.z} - if !canFlowInto(b, w, pos, true) { + if !canFlowInto(b, tx, pos, true) { // Can't flow into this block, can't spread any further. return false } pos[1]-- - if canFlowInto(b, w, pos, false) { + if canFlowInto(b, tx, pos, false) { return true } queue.PushBack(node) @@ -262,8 +262,8 @@ func spreadNeighbour(b world.Liquid, src cube.Pos, w *world.World, node liquidNo } // canFlowInto checks if a liquid can flow into the block present in the world at a specific block position. -func canFlowInto(b world.Liquid, w *world.World, pos cube.Pos, sideways bool) bool { - bl := w.Block(pos) +func canFlowInto(b world.Liquid, tx *world.Tx, pos cube.Pos, sideways bool) bool { + bl := tx.Block(pos) if _, air := bl.(Air); air { // Fast route for air: A type assert to a concrete type is much faster than a type assert to an interface. return true diff --git a/server/block/lit_pumpkin.go b/server/block/lit_pumpkin.go index fd61c6e56..4bdbf2cf7 100644 --- a/server/block/lit_pumpkin.go +++ b/server/block/lit_pumpkin.go @@ -21,14 +21,14 @@ func (l LitPumpkin) LightEmissionLevel() uint8 { } // UseOnBlock ... -func (l LitPumpkin) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, _, used = firstReplaceable(w, pos, face, l) +func (l LitPumpkin) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, l) if !used { return } l.Facing = user.Rotation().Direction().Opposite() - place(w, pos, l, user, ctx) + place(tx, pos, l, user, ctx) return placed(ctx) } diff --git a/server/block/log.go b/server/block/log.go index 42ac29cc1..8a84b861d 100644 --- a/server/block/log.go +++ b/server/block/log.go @@ -52,14 +52,14 @@ func (Log) FuelInfo() item.FuelInfo { } // UseOnBlock handles the rotational placing of logs. -func (l Log) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, face, used = firstReplaceable(w, pos, face, l) +func (l Log) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, face, used = firstReplaceable(tx, pos, face, l) if !used { return } l.Axis = face.Axis() - place(w, pos, l, user, ctx) + place(tx, pos, l, user, ctx) return placed(ctx) } diff --git a/server/block/loom.go b/server/block/loom.go index 40af82022..ececa1b64 100644 --- a/server/block/loom.go +++ b/server/block/loom.go @@ -29,7 +29,7 @@ func (l Loom) BreakInfo() BreakInfo { } // Activate ... -func (Loom) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.User, _ *item.UseContext) bool { +func (Loom) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { if opener, ok := u.(ContainerOpener); ok { opener.OpenBlockContainer(pos) return true @@ -38,13 +38,13 @@ func (Loom) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.User, _ * } // UseOnBlock ... -func (l Loom) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, _, used = firstReplaceable(w, pos, face, l) +func (l Loom) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, l) if !used { return } l.Facing = user.Rotation().Direction().Opposite() - place(w, pos, l, user, ctx) + place(tx, pos, l, user, ctx) return placed(ctx) } diff --git a/server/block/melon_seeds.go b/server/block/melon_seeds.go index 3c6a4a616..21f2b1c6c 100644 --- a/server/block/melon_seeds.go +++ b/server/block/melon_seeds.go @@ -24,39 +24,39 @@ func (MelonSeeds) SameCrop(c Crop) bool { } // NeighbourUpdateTick ... -func (m MelonSeeds) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if _, ok := w.Block(pos.Side(cube.FaceDown)).(Farmland); !ok { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: m}) +func (m MelonSeeds) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(Farmland); !ok { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: m}) } else if m.Direction != cube.FaceDown { - if _, ok := w.Block(pos.Side(m.Direction)).(Melon); !ok { + if _, ok := tx.Block(pos.Side(m.Direction)).(Melon); !ok { m.Direction = cube.FaceDown - w.SetBlock(pos, m, nil) + tx.SetBlock(pos, m, nil) } } } // RandomTick ... -func (m MelonSeeds) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { - if r.Float64() <= m.CalculateGrowthChance(pos, w) && w.Light(pos) >= 8 { +func (m MelonSeeds) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { + if r.Float64() <= m.CalculateGrowthChance(pos, tx) && tx.Light(pos) >= 8 { if m.Growth < 7 { m.Growth++ - w.SetBlock(pos, m, nil) + tx.SetBlock(pos, m, nil) } else { directions := cube.Directions() for _, i := range directions { - if _, ok := w.Block(pos.Side(i.Face())).(Melon); ok { + if _, ok := tx.Block(pos.Side(i.Face())).(Melon); ok { return } } direction := directions[r.Intn(len(directions))].Face() stemPos := pos.Side(direction) - if _, ok := w.Block(stemPos).(Air); ok { - switch w.Block(stemPos.Side(cube.FaceDown)).(type) { + if _, ok := tx.Block(stemPos).(Air); ok { + switch tx.Block(stemPos.Side(cube.FaceDown)).(type) { case Farmland, Dirt, Grass: m.Direction = direction - w.SetBlock(pos, m, nil) - w.SetBlock(stemPos, Melon{}, nil) + tx.SetBlock(pos, m, nil) + tx.SetBlock(stemPos, Melon{}, nil) } } } @@ -64,27 +64,27 @@ func (m MelonSeeds) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { } // BoneMeal ... -func (m MelonSeeds) BoneMeal(pos cube.Pos, w *world.World) bool { +func (m MelonSeeds) BoneMeal(pos cube.Pos, tx *world.Tx) bool { if m.Growth == 7 { return false } m.Growth = min(m.Growth+rand.Intn(4)+2, 7) - w.SetBlock(pos, m, nil) + tx.SetBlock(pos, m, nil) return true } // UseOnBlock ... -func (m MelonSeeds) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, m) +func (m MelonSeeds) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, m) if !used { return false } - if _, ok := w.Block(pos.Side(cube.FaceDown)).(Farmland); !ok { + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(Farmland); !ok { return false } - place(w, pos, m, user, ctx) + place(tx, pos, m, user, ctx) return placed(ctx) } diff --git a/server/block/moss_carpet.go b/server/block/moss_carpet.go index c93199f07..c0e5a5d13 100644 --- a/server/block/moss_carpet.go +++ b/server/block/moss_carpet.go @@ -15,7 +15,7 @@ type MossCarpet struct { } // SideClosed ... -func (MossCarpet) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (MossCarpet) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } @@ -25,23 +25,23 @@ func (MossCarpet) HasLiquidDrops() bool { } // NeighbourUpdateTick ... -func (MossCarpet) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if _, ok := w.Block(pos.Side(cube.FaceDown)).(Air); ok { - w.SetBlock(pos, nil, nil) +func (MossCarpet) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(Air); ok { + tx.SetBlock(pos, nil, nil) } } // UseOnBlock ... -func (m MossCarpet) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, _, used = firstReplaceable(w, pos, face, m) +func (m MossCarpet) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, m) if !used { return } - if _, ok := w.Block(pos.Side(cube.FaceDown)).(Air); ok { + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(Air); ok { return } - place(w, pos, m, user, ctx) + place(tx, pos, m, user, ctx) return placed(ctx) } diff --git a/server/block/muddy_mangrove_roots.go b/server/block/muddy_mangrove_roots.go index 1c84ba335..1538f84e9 100644 --- a/server/block/muddy_mangrove_roots.go +++ b/server/block/muddy_mangrove_roots.go @@ -30,14 +30,14 @@ func (MuddyMangroveRoots) SoilFor(block world.Block) bool { } // UseOnBlock ... -func (m MuddyMangroveRoots) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, face, used = firstReplaceable(w, pos, face, m) +func (m MuddyMangroveRoots) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, face, used = firstReplaceable(tx, pos, face, m) if !used { return } m.Axis = face.Axis() - place(w, pos, m, user, ctx) + place(tx, pos, m, user, ctx) return placed(ctx) } diff --git a/server/block/nether_brick_fence.go b/server/block/nether_brick_fence.go index 2f25ef6b6..841b1cb39 100644 --- a/server/block/nether_brick_fence.go +++ b/server/block/nether_brick_fence.go @@ -18,7 +18,7 @@ func (n NetherBrickFence) BreakInfo() BreakInfo { } // SideClosed ... -func (NetherBrickFence) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (NetherBrickFence) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } diff --git a/server/block/nether_sprouts.go b/server/block/nether_sprouts.go index 11b051dd0..6cd4865da 100644 --- a/server/block/nether_sprouts.go +++ b/server/block/nether_sprouts.go @@ -15,23 +15,23 @@ type NetherSprouts struct { } // NeighbourUpdateTick ... -func (n NetherSprouts) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if !supportsVegetation(n, w.Block(pos.Side(cube.FaceDown))) { - w.SetBlock(pos, nil, nil) // TODO: Nylium & mycelium +func (n NetherSprouts) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if !supportsVegetation(n, tx.Block(pos.Side(cube.FaceDown))) { + tx.SetBlock(pos, nil, nil) // TODO: Nylium & mycelium } } // UseOnBlock ... -func (n NetherSprouts) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, n) +func (n NetherSprouts) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, n) if !used { return false } - if !supportsVegetation(n, w.Block(pos.Side(cube.FaceDown))) { + if !supportsVegetation(n, tx.Block(pos.Side(cube.FaceDown))) { return false // TODO: Nylium & mycelium } - place(w, pos, n, user, ctx) + place(tx, pos, n, user, ctx) return placed(ctx) } diff --git a/server/block/nether_wart.go b/server/block/nether_wart.go index 71adcd19d..9bd6250b9 100644 --- a/server/block/nether_wart.go +++ b/server/block/nether_wart.go @@ -23,31 +23,31 @@ func (n NetherWart) HasLiquidDrops() bool { } // RandomTick ... -func (n NetherWart) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { +func (n NetherWart) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { if n.Age < 3 && r.Float64() < 0.1 { n.Age++ - w.SetBlock(pos, n, nil) + tx.SetBlock(pos, n, nil) } } // UseOnBlock ... -func (n NetherWart) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, n) +func (n NetherWart) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, n) if !used { return false } - if _, ok := w.Block(pos.Side(cube.FaceDown)).(SoulSand); !ok { + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(SoulSand); !ok { return false } - place(w, pos, n, user, ctx) + place(tx, pos, n, user, ctx) return placed(ctx) } // NeighbourUpdateTick ... -func (n NetherWart) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if _, ok := w.Block(pos.Side(cube.FaceDown)).(SoulSand); !ok { - w.SetBlock(pos, nil, nil) +func (n NetherWart) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(SoulSand); !ok { + tx.SetBlock(pos, nil, nil) } } diff --git a/server/block/note.go b/server/block/note.go index 11d4a0ae5..72f399030 100644 --- a/server/block/note.go +++ b/server/block/note.go @@ -20,14 +20,14 @@ type Note struct { } // playNote ... -func (n Note) playNote(pos cube.Pos, w *world.World) { - w.PlaySound(pos.Vec3(), sound.Note{Instrument: n.instrument(pos, w), Pitch: n.Pitch}) - w.AddParticle(pos.Vec3(), particle.Note{Instrument: n.Instrument(), Pitch: n.Pitch}) +func (n Note) playNote(pos cube.Pos, tx *world.Tx) { + tx.PlaySound(pos.Vec3(), sound.Note{Instrument: n.instrument(pos, tx), Pitch: n.Pitch}) + tx.AddParticle(pos.Vec3(), particle.Note{Instrument: n.Instrument(), Pitch: n.Pitch}) } // updateInstrument ... -func (n Note) instrument(pos cube.Pos, w *world.World) sound.Instrument { - if instrumentBlock, ok := w.Block(pos.Side(cube.FaceDown)).(interface { +func (n Note) instrument(pos cube.Pos, tx *world.Tx) sound.Instrument { + if instrumentBlock, ok := tx.Block(pos.Side(cube.FaceDown)).(interface { Instrument() sound.Instrument }); ok { return instrumentBlock.Instrument() @@ -47,13 +47,13 @@ func (n Note) EncodeNBT() map[string]any { } // Activate ... -func (n Note) Activate(pos cube.Pos, _ cube.Face, w *world.World, _ item.User, _ *item.UseContext) bool { - if _, ok := w.Block(pos.Side(cube.FaceUp)).(Air); !ok { +func (n Note) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { + if _, ok := tx.Block(pos.Side(cube.FaceUp)).(Air); !ok { return false } n.Pitch = (n.Pitch + 1) % 25 - n.playNote(pos, w) - w.SetBlock(pos, n, &world.SetOpts{DisableBlockUpdates: true, DisableLiquidDisplacement: true}) + n.playNote(pos, tx) + tx.SetBlock(pos, n, &world.SetOpts{DisableBlockUpdates: true, DisableLiquidDisplacement: true}) return true } diff --git a/server/block/potato.go b/server/block/potato.go index dd8b44a6c..282a4dc9c 100644 --- a/server/block/potato.go +++ b/server/block/potato.go @@ -37,33 +37,33 @@ func (p Potato) ConsumeDuration() time.Duration { } // Consume ... -func (p Potato) Consume(_ *world.World, c item.Consumer) item.Stack { +func (p Potato) Consume(tx *world.Tx, c item.Consumer) item.Stack { c.Saturate(1, 0.6) return item.Stack{} } // BoneMeal ... -func (p Potato) BoneMeal(pos cube.Pos, w *world.World) bool { +func (p Potato) BoneMeal(pos cube.Pos, tx *world.Tx) bool { if p.Growth == 7 { return false } p.Growth = min(p.Growth+rand.Intn(4)+2, 7) - w.SetBlock(pos, p, nil) + tx.SetBlock(pos, p, nil) return true } // UseOnBlock ... -func (p Potato) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, p) +func (p Potato) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, p) if !used { return false } - if _, ok := w.Block(pos.Side(cube.FaceDown)).(Farmland); !ok { + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(Farmland); !ok { return false } - place(w, pos, p, user, ctx) + place(tx, pos, p, user, ctx) return placed(ctx) } @@ -88,13 +88,13 @@ func (p Potato) EncodeItem() (name string, meta int16) { } // RandomTick ... -func (p Potato) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { - if w.Light(pos) < 8 { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: p}) - } else if p.Growth < 7 && r.Float64() <= p.CalculateGrowthChance(pos, w) { +func (p Potato) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { + if tx.Light(pos) < 8 { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: p}) + } else if p.Growth < 7 && r.Float64() <= p.CalculateGrowthChance(pos, tx) { p.Growth++ - w.SetBlock(pos, p, nil) + tx.SetBlock(pos, p, nil) } } diff --git a/server/block/pumpkin.go b/server/block/pumpkin.go index e0381c96c..76b7cebca 100644 --- a/server/block/pumpkin.go +++ b/server/block/pumpkin.go @@ -27,14 +27,14 @@ func (p Pumpkin) Instrument() sound.Instrument { } // UseOnBlock ... -func (p Pumpkin) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, _, used = firstReplaceable(w, pos, face, p) +func (p Pumpkin) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, p) if !used { return } p.Facing = user.Rotation().Direction().Opposite() - place(w, pos, p, user, ctx) + place(tx, pos, p, user, ctx) return placed(ctx) } diff --git a/server/block/pumpkin_seeds.go b/server/block/pumpkin_seeds.go index 29a325f8c..fa90c7cd0 100644 --- a/server/block/pumpkin_seeds.go +++ b/server/block/pumpkin_seeds.go @@ -24,39 +24,39 @@ func (PumpkinSeeds) SameCrop(c Crop) bool { } // NeighbourUpdateTick ... -func (p PumpkinSeeds) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if _, ok := w.Block(pos.Side(cube.FaceDown)).(Farmland); !ok { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: p}) +func (p PumpkinSeeds) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(Farmland); !ok { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: p}) } else if p.Direction != cube.FaceDown { - if pumpkin, ok := w.Block(pos.Side(p.Direction)).(Pumpkin); !ok || pumpkin.Carved { + if pumpkin, ok := tx.Block(pos.Side(p.Direction)).(Pumpkin); !ok || pumpkin.Carved { p.Direction = cube.FaceDown - w.SetBlock(pos, p, nil) + tx.SetBlock(pos, p, nil) } } } // RandomTick ... -func (p PumpkinSeeds) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { - if r.Float64() <= p.CalculateGrowthChance(pos, w) && w.Light(pos) >= 8 { +func (p PumpkinSeeds) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { + if r.Float64() <= p.CalculateGrowthChance(pos, tx) && tx.Light(pos) >= 8 { if p.Growth < 7 { p.Growth++ - w.SetBlock(pos, p, nil) + tx.SetBlock(pos, p, nil) } else { directions := []cube.Direction{cube.North, cube.South, cube.West, cube.East} for _, i := range directions { - if _, ok := w.Block(pos.Side(i.Face())).(Pumpkin); ok { + if _, ok := tx.Block(pos.Side(i.Face())).(Pumpkin); ok { return } } direction := directions[r.Intn(len(directions))].Face() stemPos := pos.Side(direction) - if _, ok := w.Block(stemPos).(Air); ok { - switch w.Block(stemPos.Side(cube.FaceDown)).(type) { + if _, ok := tx.Block(stemPos).(Air); ok { + switch tx.Block(stemPos.Side(cube.FaceDown)).(type) { case Farmland, Dirt, Grass: p.Direction = direction - w.SetBlock(pos, p, nil) - w.SetBlock(stemPos, Pumpkin{}, nil) + tx.SetBlock(pos, p, nil) + tx.SetBlock(stemPos, Pumpkin{}, nil) } } } @@ -64,27 +64,27 @@ func (p PumpkinSeeds) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { } // BoneMeal ... -func (p PumpkinSeeds) BoneMeal(pos cube.Pos, w *world.World) bool { +func (p PumpkinSeeds) BoneMeal(pos cube.Pos, tx *world.Tx) bool { if p.Growth == 7 { return false } p.Growth = min(p.Growth+rand.Intn(4)+2, 7) - w.SetBlock(pos, p, nil) + tx.SetBlock(pos, p, nil) return true } // UseOnBlock ... -func (p PumpkinSeeds) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, p) +func (p PumpkinSeeds) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, p) if !used { return false } - if _, ok := w.Block(pos.Side(cube.FaceDown)).(Farmland); !ok { + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(Farmland); !ok { return false } - place(w, pos, p, user, ctx) + place(tx, pos, p, user, ctx) return placed(ctx) } diff --git a/server/block/purpur.go b/server/block/purpur.go index e7fce21c5..9d99afbeb 100644 --- a/server/block/purpur.go +++ b/server/block/purpur.go @@ -39,14 +39,14 @@ func (p Purpur) EncodeBlock() (name string, properties map[string]interface{}) { } // UseOnBlock ... -func (p PurpurPillar) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, face, used = firstReplaceable(w, pos, face, p) +func (p PurpurPillar) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, face, used = firstReplaceable(tx, pos, face, p) if !used { return } p.Axis = face.Axis() - place(w, pos, p, user, ctx) + place(tx, pos, p, user, ctx) return placed(ctx) } diff --git a/server/block/quartz.go b/server/block/quartz.go index 6e5f6e92e..5cb5686c7 100644 --- a/server/block/quartz.go +++ b/server/block/quartz.go @@ -31,14 +31,14 @@ type ( ) // UseOnBlock handles the rotational placing of quartz pillar blocks. -func (q QuartzPillar) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, face, used = firstReplaceable(w, pos, face, q) +func (q QuartzPillar) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, face, used = firstReplaceable(tx, pos, face, q) if !used { return } q.Axis = face.Axis() - place(w, pos, q, user, ctx) + place(tx, pos, q, user, ctx) return placed(ctx) } diff --git a/server/block/sand.go b/server/block/sand.go index c5e9899ae..a188bf6df 100644 --- a/server/block/sand.go +++ b/server/block/sand.go @@ -26,8 +26,8 @@ func (s Sand) SoilFor(block world.Block) bool { } // NeighbourUpdateTick ... -func (s Sand) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - s.fall(s, pos, w) +func (s Sand) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + s.fall(s, pos, tx) } // BreakInfo ... diff --git a/server/block/sea_pickle.go b/server/block/sea_pickle.go index 781b7bf5e..27c2db570 100644 --- a/server/block/sea_pickle.go +++ b/server/block/sea_pickle.go @@ -25,9 +25,9 @@ type SeaPickle struct { } // canSurvive ... -func (SeaPickle) canSurvive(pos cube.Pos, w *world.World) bool { - below := w.Block(pos.Side(cube.FaceDown)) - if !below.Model().FaceSolid(pos.Side(cube.FaceDown), cube.FaceUp, w) { +func (SeaPickle) canSurvive(pos cube.Pos, tx *world.Tx) bool { + below := tx.Block(pos.Side(cube.FaceDown)) + if !below.Model().FaceSolid(pos.Side(cube.FaceDown), cube.FaceUp, tx) { return false } if emitter, ok := below.(LightDiffuser); ok && emitter.LightDiffusionLevel() != 15 { @@ -37,17 +37,17 @@ func (SeaPickle) canSurvive(pos cube.Pos, w *world.World) bool { } // BoneMeal ... -func (s SeaPickle) BoneMeal(pos cube.Pos, w *world.World) bool { +func (s SeaPickle) BoneMeal(pos cube.Pos, tx *world.Tx) bool { if s.Dead { return false } - if coral, ok := w.Block(pos.Side(cube.FaceDown)).(CoralBlock); !ok || coral.Dead { + if coral, ok := tx.Block(pos.Side(cube.FaceDown)).(CoralBlock); !ok || coral.Dead { return false } if s.AdditionalCount != 3 { s.AdditionalCount = 3 - w.SetBlock(pos, s, nil) + tx.SetBlock(pos, s, nil) } for x := -2; x <= 2; x++ { @@ -59,13 +59,13 @@ func (s SeaPickle) BoneMeal(pos cube.Pos, w *world.World) bool { } newPos := pos.Add(cube.Pos{x, y, z}) - if _, ok := w.Block(newPos).(Water); !ok { + if _, ok := tx.Block(newPos).(Water); !ok { continue } - if coral, ok := w.Block(newPos.Side(cube.FaceDown)).(CoralBlock); !ok || coral.Dead { + if coral, ok := tx.Block(newPos.Side(cube.FaceDown)).(CoralBlock); !ok || coral.Dead { continue } - w.SetBlock(newPos, SeaPickle{AdditionalCount: rand.Intn(3) + 1}, nil) + tx.SetBlock(newPos, SeaPickle{AdditionalCount: rand.Intn(3) + 1}, nil) } } } @@ -74,51 +74,51 @@ func (s SeaPickle) BoneMeal(pos cube.Pos, w *world.World) bool { } // UseOnBlock ... -func (s SeaPickle) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - if existing, ok := w.Block(pos).(SeaPickle); ok { +func (s SeaPickle) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + if existing, ok := tx.Block(pos).(SeaPickle); ok { if existing.AdditionalCount >= 3 { return false } existing.AdditionalCount++ - place(w, pos, existing, user, ctx) + place(tx, pos, existing, user, ctx) return placed(ctx) } - pos, _, used := firstReplaceable(w, pos, face, s) + pos, _, used := firstReplaceable(tx, pos, face, s) if !used { return false } - if !s.canSurvive(pos, w) { + if !s.canSurvive(pos, tx) { return false } s.Dead = true - if liquid, ok := w.Liquid(pos); ok { + if liquid, ok := tx.Liquid(pos); ok { _, ok = liquid.(Water) s.Dead = !ok } - place(w, pos, s, user, ctx) + place(tx, pos, s, user, ctx) return placed(ctx) } // NeighbourUpdateTick ... -func (s SeaPickle) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if !s.canSurvive(pos, w) { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: s}) - dropItem(w, item.NewStack(s, s.AdditionalCount+1), pos.Vec3Centre()) +func (s SeaPickle) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if !s.canSurvive(pos, tx) { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: s}) + dropItem(tx, item.NewStack(s, s.AdditionalCount+1), pos.Vec3Centre()) return } alive := false - if liquid, ok := w.Liquid(pos); ok { + if liquid, ok := tx.Liquid(pos); ok { _, alive = liquid.(Water) } if s.Dead == alive { s.Dead = !alive - w.SetBlock(pos, s, nil) + tx.SetBlock(pos, s, nil) } } @@ -128,7 +128,7 @@ func (SeaPickle) HasLiquidDrops() bool { } // SideClosed ... -func (SeaPickle) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (SeaPickle) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } diff --git a/server/block/short_grass.go b/server/block/short_grass.go index dbcf79f3e..57147590a 100644 --- a/server/block/short_grass.go +++ b/server/block/short_grass.go @@ -37,11 +37,11 @@ func (g ShortGrass) BreakInfo() BreakInfo { } // BoneMeal attempts to affect the block using a bone meal item. -func (g ShortGrass) BoneMeal(pos cube.Pos, w *world.World) bool { +func (g ShortGrass) BoneMeal(pos cube.Pos, tx *world.Tx) bool { upper := DoubleTallGrass{Type: NormalDoubleTallGrass(), UpperPart: true} - if replaceableWith(w, pos.Side(cube.FaceUp), upper) { - w.SetBlock(pos, DoubleTallGrass{Type: NormalDoubleTallGrass()}, nil) - w.SetBlock(pos.Side(cube.FaceUp), upper, nil) + if replaceableWith(tx, pos.Side(cube.FaceUp), upper) { + tx.SetBlock(pos, DoubleTallGrass{Type: NormalDoubleTallGrass()}, nil) + tx.SetBlock(pos.Side(cube.FaceUp), upper, nil) return true } return false @@ -53,10 +53,10 @@ func (g ShortGrass) CompostChance() float64 { } // NeighbourUpdateTick ... -func (g ShortGrass) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if !supportsVegetation(g, w.Block(pos.Side(cube.FaceDown))) { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: g}) +func (g ShortGrass) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if !supportsVegetation(g, tx.Block(pos.Side(cube.FaceDown))) { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: g}) } } @@ -66,16 +66,16 @@ func (g ShortGrass) HasLiquidDrops() bool { } // UseOnBlock ... -func (g ShortGrass) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, g) +func (g ShortGrass) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, g) if !used { return false } - if !supportsVegetation(g, w.Block(pos.Side(cube.FaceDown))) { + if !supportsVegetation(g, tx.Block(pos.Side(cube.FaceDown))) { return false } - place(w, pos, g, user, ctx) + place(tx, pos, g, user, ctx) return placed(ctx) } diff --git a/server/block/sign.go b/server/block/sign.go index fe65be77e..d84983831 100644 --- a/server/block/sign.go +++ b/server/block/sign.go @@ -49,7 +49,7 @@ type SignText struct { } // SideClosed ... -func (s Sign) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (s Sign) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } @@ -120,11 +120,11 @@ func (s Sign) Wax(cube.Pos, mgl64.Vec3) (world.Block, bool) { } // Activate ... -func (s Sign) Activate(pos cube.Pos, _ cube.Face, w *world.World, user item.User, _ *item.UseContext) bool { - if editor, ok := user.(SignEditor); ok && !s.Waxed { - editor.OpenSign(pos, s.EditingFrontSide(pos, user.Position())) +func (s Sign) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { + if editor, ok := u.(SignEditor); ok && !s.Waxed { + editor.OpenSign(pos, s.EditingFrontSide(pos, u.Position())) } else if s.Waxed { - w.PlaySound(pos.Vec3(), sound.WaxedSignFailedInteraction{}) + tx.PlaySound(pos.Vec3(), sound.WaxedSignFailedInteraction{}) } return true } @@ -141,8 +141,8 @@ type SignEditor interface { } // UseOnBlock ... -func (s Sign) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, face, used = firstReplaceable(w, pos, face, s) +func (s Sign) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, face, used = firstReplaceable(tx, pos, face, s) if !used || face == cube.FaceDown { return false } @@ -152,7 +152,7 @@ func (s Sign) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.Wo } else { s.Attach = WallAttachment(face.Direction()) } - place(w, pos, s, user, ctx) + place(tx, pos, s, user, ctx) if editor, ok := user.(SignEditor); ok { editor.OpenSign(pos, true) } @@ -160,19 +160,19 @@ func (s Sign) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.Wo } // NeighbourUpdateTick ... -func (s Sign) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { +func (s Sign) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { if s.Attach.hanging { - if _, ok := w.Block(pos.Side(s.Attach.facing.Opposite().Face())).(Air); ok { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: s}) - dropItem(w, item.NewStack(Sign{Wood: s.Wood}, 1), pos.Vec3Centre()) + if _, ok := tx.Block(pos.Side(s.Attach.facing.Opposite().Face())).(Air); ok { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: s}) + dropItem(tx, item.NewStack(Sign{Wood: s.Wood}, 1), pos.Vec3Centre()) } return } - if _, ok := w.Block(pos.Side(cube.FaceDown)).(Air); ok { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: s}) - dropItem(w, item.NewStack(Sign{Wood: s.Wood}, 1), pos.Vec3Centre()) + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(Air); ok { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: s}) + dropItem(tx, item.NewStack(Sign{Wood: s.Wood}, 1), pos.Vec3Centre()) } } diff --git a/server/block/skull.go b/server/block/skull.go index ce7771ee7..b6e8a4763 100644 --- a/server/block/skull.go +++ b/server/block/skull.go @@ -51,8 +51,8 @@ func (s Skull) Model() world.BlockModel { } // UseOnBlock ... -func (s Skull) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, face, used = firstReplaceable(w, pos, face, s) +func (s Skull) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, face, used = firstReplaceable(tx, pos, face, s) if !used || face == cube.FaceDown { return false } @@ -62,12 +62,12 @@ func (s Skull) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.W } else { s.Attach = WallAttachment(face.Direction()) } - place(w, pos, s, user, ctx) + place(tx, pos, s, user, ctx) return placed(ctx) } // SideClosed ... -func (Skull) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (Skull) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } diff --git a/server/block/slab.go b/server/block/slab.go index 25b41a856..3e7744797 100644 --- a/server/block/slab.go +++ b/server/block/slab.go @@ -23,31 +23,31 @@ type Slab struct { // UseOnBlock handles the placement of slabs with relation to them being upside down or not and handles slabs // being turned into double slabs. -func (s Slab) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { +func (s Slab) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { id, meta := s.EncodeItem() - clickedBlock := w.Block(pos) + clickedBlock := tx.Block(pos) if clickedSlab, ok := clickedBlock.(Slab); ok && !s.Double { clickedId, clickedMeta := clickedSlab.EncodeItem() if !clickedSlab.Double && id == clickedId && meta == clickedMeta && ((face == cube.FaceUp && !clickedSlab.Top) || (face == cube.FaceDown && clickedSlab.Top)) { // A half slab of the same type was clicked at the top, so we can make it full. clickedSlab.Double = true - place(w, pos, clickedSlab, user, ctx) + place(tx, pos, clickedSlab, user, ctx) return placed(ctx) } } - if sideSlab, ok := w.Block(pos.Side(face)).(Slab); ok && !replaceableWith(w, pos, s) && !s.Double { + if sideSlab, ok := tx.Block(pos.Side(face)).(Slab); ok && !replaceableWith(tx, pos, s) && !s.Double { sideId, sideMeta := sideSlab.EncodeItem() // The block on the side of the one clicked was a slab and the block clicked was not replaceableWith, so // the slab on the side must've been half and may now be filled if the wood types are the same. if !sideSlab.Double && id == sideId && meta == sideMeta { sideSlab.Double = true - place(w, pos.Side(face), sideSlab, user, ctx) + place(tx, pos.Side(face), sideSlab, user, ctx) return placed(ctx) } } - pos, face, used = firstReplaceable(w, pos, face, s) + pos, face, used = firstReplaceable(tx, pos, face, s) if !used { return } @@ -55,7 +55,7 @@ func (s Slab) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, w *w s.Top = true } - place(w, pos, s, user, ctx) + place(tx, pos, s, user, ctx) return placed(ctx) } @@ -90,7 +90,7 @@ func (s Slab) CanDisplace(b world.Liquid) bool { } // SideClosed ... -func (s Slab) SideClosed(pos, side cube.Pos, _ *world.World) bool { +func (s Slab) SideClosed(pos, side cube.Pos, tx *world.Tx) bool { // Only returns true if the side is below the slab and if the slab is not upside down. return !s.Top && side[1] == pos[1]-1 } diff --git a/server/block/smelter.go b/server/block/smelter.go index 6a61986ab..cad90e06c 100644 --- a/server/block/smelter.go +++ b/server/block/smelter.go @@ -39,7 +39,7 @@ func newSmelter() *smelter { } // InsertItem ... -func (s *smelter) InsertItem(h Hopper, pos cube.Pos, w *world.World) bool { +func (s *smelter) InsertItem(h Hopper, pos cube.Pos, tx *world.Tx) bool { for sourceSlot, sourceStack := range h.inventory.Slots() { var slot int @@ -54,7 +54,7 @@ func (s *smelter) InsertItem(h Hopper, pos cube.Pos, w *world.World) bool { } stack := sourceStack.Grow(-sourceStack.Count() + 1) - it, _ := s.Inventory(w, pos).Item(slot) + it, _ := s.Inventory(tx, pos).Item(slot) if slot == 1 { if _, ok := sourceStack.Item().(item.Fuel); !ok { // The item is not fuel. @@ -73,7 +73,7 @@ func (s *smelter) InsertItem(h Hopper, pos cube.Pos, w *world.World) bool { stack = it.Grow(1) } - _ = s.Inventory(w, pos).SetItem(slot, stack) + _ = s.Inventory(tx, pos).SetItem(slot, stack) _ = h.inventory.SetItem(sourceSlot, sourceStack.Grow(-1)) return true } @@ -82,7 +82,7 @@ func (s *smelter) InsertItem(h Hopper, pos cube.Pos, w *world.World) bool { } // ExtractItem ... -func (s *smelter) ExtractItem(h Hopper, pos cube.Pos, w *world.World) bool { +func (s *smelter) ExtractItem(h Hopper, pos cube.Pos, tx *world.Tx) bool { for sourceSlot, sourceStack := range s.inventory.Slots() { if sourceStack.Empty() { continue @@ -105,7 +105,7 @@ func (s *smelter) ExtractItem(h Hopper, pos cube.Pos, w *world.World) bool { continue } - _ = s.Inventory(w, pos).SetItem(sourceSlot, sourceStack.Grow(-1)) + _ = s.Inventory(tx, pos).SetItem(sourceSlot, sourceStack.Grow(-1)) return true } @@ -136,12 +136,12 @@ func (s *smelter) ResetExperience() int { } // Inventory returns the inventory of the furnace. -func (s *smelter) Inventory(*world.World, cube.Pos) *inventory.Inventory { +func (s *smelter) Inventory(*world.Tx, cube.Pos) *inventory.Inventory { return s.inventory } // AddViewer adds a viewer to the furnace, so that it is updated whenever the inventory of the furnace is changed. -func (s *smelter) AddViewer(v ContainerViewer, _ *world.World, _ cube.Pos) { +func (s *smelter) AddViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) { s.mu.Lock() defer s.mu.Unlock() s.viewers[v] = struct{}{} @@ -149,7 +149,7 @@ func (s *smelter) AddViewer(v ContainerViewer, _ *world.World, _ cube.Pos) { // RemoveViewer removes a viewer from the furnace, so that slot updates in the inventory are no longer sent to // it. -func (s *smelter) RemoveViewer(v ContainerViewer, _ *world.World, _ cube.Pos) { +func (s *smelter) RemoveViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) { s.mu.Lock() defer s.mu.Unlock() if len(s.viewers) == 0 { diff --git a/server/block/smithing_table.go b/server/block/smithing_table.go index 46f98b9ac..ed4e9cb51 100644 --- a/server/block/smithing_table.go +++ b/server/block/smithing_table.go @@ -29,7 +29,7 @@ func (s SmithingTable) BreakInfo() BreakInfo { } // Activate ... -func (SmithingTable) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.User, _ *item.UseContext) bool { +func (SmithingTable) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { if opener, ok := u.(ContainerOpener); ok { opener.OpenBlockContainer(pos) return true diff --git a/server/block/smoker.go b/server/block/smoker.go index a43a206fd..f2a1db0f3 100644 --- a/server/block/smoker.go +++ b/server/block/smoker.go @@ -34,15 +34,15 @@ func NewSmoker(face cube.Direction) Smoker { } // Tick is called to check if the smoker should update and start or stop smelting. -func (s Smoker) Tick(_ int64, pos cube.Pos, w *world.World) { +func (s Smoker) Tick(currentTick int64, pos cube.Pos, tx *world.Tx) { if s.Lit && rand.Float64() <= 0.016 { // Every three or so seconds. - w.PlaySound(pos.Vec3Centre(), sound.SmokerCrackle{}) + tx.PlaySound(pos.Vec3Centre(), sound.SmokerCrackle{}) } if lit := s.smelter.tickSmelting(time.Second*5, time.Millisecond*200, s.Lit, func(i item.SmeltInfo) bool { return i.Food }); s.Lit != lit { s.Lit = lit - w.SetBlock(pos, s, nil) + tx.SetBlock(pos, s, nil) } } @@ -60,28 +60,28 @@ func (s Smoker) EncodeBlock() (name string, properties map[string]interface{}) { } // UseOnBlock ... -func (s Smoker) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, s) +func (s Smoker) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, s) if !used { return false } - place(w, pos, NewSmoker(user.Rotation().Direction().Opposite()), user, ctx) + place(tx, pos, NewSmoker(user.Rotation().Direction().Opposite()), user, ctx) return placed(ctx) } // BreakInfo ... func (s Smoker) BreakInfo() BreakInfo { xp := s.Experience() - return newBreakInfo(3.5, alwaysHarvestable, pickaxeEffective, oneOf(s)).withXPDropRange(xp, xp).withBreakHandler(func(pos cube.Pos, w *world.World, u item.User) { - for _, i := range s.Inventory(w, pos).Clear() { - dropItem(w, i, pos.Vec3()) + return newBreakInfo(3.5, alwaysHarvestable, pickaxeEffective, oneOf(s)).withXPDropRange(xp, xp).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { + for _, i := range s.Inventory(tx, pos).Clear() { + dropItem(tx, i, pos.Vec3()) } }) } // Activate ... -func (s Smoker) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.User, _ *item.UseContext) bool { +func (s Smoker) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { if opener, ok := u.(ContainerOpener); ok { opener.OpenBlockContainer(pos) return true diff --git a/server/block/sponge.go b/server/block/sponge.go index 6cde1ade8..730975d8b 100644 --- a/server/block/sponge.go +++ b/server/block/sponge.go @@ -48,50 +48,50 @@ func (s Sponge) EncodeBlock() (string, map[string]any) { // UseOnBlock places the sponge, absorbs nearby water if it's still dry and flags it as wet if any water has been // absorbed. -func (s Sponge) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { +func (s Sponge) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { var particles = false - pos, _, used = firstReplaceable(w, pos, face, s) + pos, _, used = firstReplaceable(tx, pos, face, s) if !used { return } // Check if the Sponge is placed in the Nether and if so, turn it into a normal Sponge instantly. - if w.Dimension().WaterEvaporates() && s.Wet { + if tx.World().Dimension().WaterEvaporates() && s.Wet { s.Wet = false particles = true } - place(w, pos, s, user, ctx) + place(tx, pos, s, user, ctx) if particles && placed(ctx) { - w.AddParticle(pos.Side(cube.FaceUp).Vec3(), particle.Evaporate{}) + tx.AddParticle(pos.Side(cube.FaceUp).Vec3(), particle.Evaporate{}) } return placed(ctx) } // NeighbourUpdateTick checks for nearby water flow. If water could be found and the sponge is dry, it will absorb the // water and be flagged as wet. -func (s Sponge) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { +func (s Sponge) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { // The sponge is dry, so it can absorb nearby water. if !s.Wet { - if s.absorbWater(pos, w) > 0 { + if s.absorbWater(pos, tx) > 0 { // Water has been absorbed, so we flag the sponge as wet. - s.setWet(pos, w) + s.setWet(pos, tx) } } } // setWet flags a sponge as wet. It replaces the block at pos by a wet sponge block and displays a block break // particle at the sponge's position with an offset of 0.5 on each axis. -func (s Sponge) setWet(pos cube.Pos, w *world.World) { +func (s Sponge) setWet(pos cube.Pos, tx *world.Tx) { s.Wet = true - w.SetBlock(pos, s, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: Water{Depth: 1}}) + tx.SetBlock(pos, s, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: Water{Depth: 1}}) } // absorbWater replaces water blocks near the sponge by air out to a taxicab geometry of 7 in all directions. // The maximum for absorbed blocks is 65. // The returned int specifies the amount of replaced water blocks. -func (s Sponge) absorbWater(pos cube.Pos, w *world.World) int { +func (s Sponge) absorbWater(pos cube.Pos, tx *world.Tx) int { // distanceToSponge binds a world.Pos to its distance from the sponge's position. type distanceToSponge struct { block cube.Pos @@ -113,17 +113,17 @@ func (s Sponge) absorbWater(pos cube.Pos, w *world.World) int { queue = queue[1:] next.block.Neighbours(func(neighbour cube.Pos) { - liquid, found := w.Liquid(neighbour) + liquid, found := tx.Liquid(neighbour) if found { if _, isWater := liquid.(Water); isWater { - w.SetLiquid(neighbour, nil) + tx.SetLiquid(neighbour, nil) replaced++ if next.distance < 7 { queue = append(queue, distanceToSponge{neighbour, next.distance + 1}) } } } - }, w.Range()) + }, tx.Range()) } return replaced diff --git a/server/block/spore_blossom.go b/server/block/spore_blossom.go index 2138eddc3..60ebb3582 100644 --- a/server/block/spore_blossom.go +++ b/server/block/spore_blossom.go @@ -20,24 +20,24 @@ func (s SporeBlossom) HasLiquidDrops() bool { } // NeighbourUpdateTick ... -func (s SporeBlossom) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if !w.Block(pos.Side(cube.FaceUp)).Model().FaceSolid(pos.Side(cube.FaceUp), cube.FaceDown, w) { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: s}) +func (s SporeBlossom) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if !tx.Block(pos.Side(cube.FaceUp)).Model().FaceSolid(pos.Side(cube.FaceUp), cube.FaceDown, tx) { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: s}) } } // UseOnBlock ... -func (s SporeBlossom) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, _, used = firstReplaceable(w, pos, face, s) +func (s SporeBlossom) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, s) if !used { return } - if !w.Block(pos.Side(cube.FaceUp)).Model().FaceSolid(pos.Side(cube.FaceUp), cube.FaceDown, w) { + if !tx.Block(pos.Side(cube.FaceUp)).Model().FaceSolid(pos.Side(cube.FaceUp), cube.FaceDown, tx) { return } - place(w, pos, s, user, ctx) + place(tx, pos, s, user, ctx) return placed(ctx) } diff --git a/server/block/stained_glass_pane.go b/server/block/stained_glass_pane.go index d0ef7ddce..919d2520f 100644 --- a/server/block/stained_glass_pane.go +++ b/server/block/stained_glass_pane.go @@ -18,7 +18,7 @@ type StainedGlassPane struct { } // SideClosed ... -func (p StainedGlassPane) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (p StainedGlassPane) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } diff --git a/server/block/stairs.go b/server/block/stairs.go index 33a3bf695..d35f3b429 100644 --- a/server/block/stairs.go +++ b/server/block/stairs.go @@ -26,8 +26,8 @@ type Stairs struct { // UseOnBlock handles the directional placing of stairs and makes sure they are properly placed upside down // when needed. -func (s Stairs) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, face, used = firstReplaceable(w, pos, face, s) +func (s Stairs) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, face, used = firstReplaceable(tx, pos, face, s) if !used { return } @@ -36,7 +36,7 @@ func (s Stairs) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, w s.UpsideDown = true } - place(w, pos, s, user, ctx) + place(tx, pos, s, user, ctx) return placed(ctx) } @@ -119,8 +119,8 @@ func toStairsDirection(v cube.Direction) int32 { } // SideClosed ... -func (s Stairs) SideClosed(pos, side cube.Pos, w *world.World) bool { - return s.Model().FaceSolid(pos, pos.Face(side), w) +func (s Stairs) SideClosed(pos, side cube.Pos, tx *world.Tx) bool { + return s.Model().FaceSolid(pos, pos.Face(side), tx) } // allStairs returns all states of stairs. diff --git a/server/block/stonecutter.go b/server/block/stonecutter.go index b474cd588..53f2632a8 100644 --- a/server/block/stonecutter.go +++ b/server/block/stonecutter.go @@ -28,7 +28,7 @@ func (s Stonecutter) BreakInfo() BreakInfo { } // Activate ... -func (Stonecutter) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.User, _ *item.UseContext) bool { +func (Stonecutter) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { if opener, ok := u.(ContainerOpener); ok { opener.OpenBlockContainer(pos) return true @@ -37,13 +37,13 @@ func (Stonecutter) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.Us } // UseOnBlock ... -func (s Stonecutter) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, _, used = firstReplaceable(w, pos, face, s) +func (s Stonecutter) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, s) if !used { return } s.Facing = user.Rotation().Direction().Opposite() - place(w, pos, s, user, ctx) + place(tx, pos, s, user, ctx) return placed(ctx) } diff --git a/server/block/sugar_cane.go b/server/block/sugar_cane.go index e481f9dcf..253a8bcb7 100644 --- a/server/block/sugar_cane.go +++ b/server/block/sugar_cane.go @@ -19,57 +19,57 @@ type SugarCane struct { } // UseOnBlock ensures the placement of the block is OK. -func (c SugarCane) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, _, used = firstReplaceable(w, pos, face, c) +func (c SugarCane) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, c) if !used { return false } - if !c.canGrowHere(pos, w, true) { + if !c.canGrowHere(pos, tx, true) { return false } - place(w, pos, c, user, ctx) + place(tx, pos, c, user, ctx) return placed(ctx) } // NeighbourUpdateTick ... -func (c SugarCane) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if !c.canGrowHere(pos, w, true) { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: c}) - dropItem(w, item.NewStack(c, 1), pos.Vec3Centre()) +func (c SugarCane) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if !c.canGrowHere(pos, tx, true) { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: c}) + dropItem(tx, item.NewStack(c, 1), pos.Vec3Centre()) } } // RandomTick ... -func (c SugarCane) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { +func (c SugarCane) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { if c.Age < 15 { c.Age++ } else if c.Age == 15 { c.Age = 0 - if c.canGrowHere(pos.Side(cube.FaceDown), w, false) { + if c.canGrowHere(pos.Side(cube.FaceDown), tx, false) { for y := 1; y < 3; y++ { - if _, ok := w.Block(pos.Add(cube.Pos{0, y})).(Air); ok { - w.SetBlock(pos.Add(cube.Pos{0, y}), SugarCane{}, nil) + if _, ok := tx.Block(pos.Add(cube.Pos{0, y})).(Air); ok { + tx.SetBlock(pos.Add(cube.Pos{0, y}), SugarCane{}, nil) break - } else if _, ok := w.Block(pos.Add(cube.Pos{0, y})).(SugarCane); !ok { + } else if _, ok := tx.Block(pos.Add(cube.Pos{0, y})).(SugarCane); !ok { break } } } } - w.SetBlock(pos, c, nil) + tx.SetBlock(pos, c, nil) } // BoneMeal ... -func (c SugarCane) BoneMeal(pos cube.Pos, w *world.World) bool { - for _, ok := w.Block(pos.Side(cube.FaceDown)).(SugarCane); ok; _, ok = w.Block(pos.Side(cube.FaceDown)).(SugarCane) { +func (c SugarCane) BoneMeal(pos cube.Pos, tx *world.Tx) bool { + for _, ok := tx.Block(pos.Side(cube.FaceDown)).(SugarCane); ok; _, ok = tx.Block(pos.Side(cube.FaceDown)).(SugarCane) { pos = pos.Side(cube.FaceDown) } - if c.canGrowHere(pos.Side(cube.FaceDown), w, false) { + if c.canGrowHere(pos.Side(cube.FaceDown), tx, false) { for y := 1; y < 3; y++ { - if _, ok := w.Block(pos.Add(cube.Pos{0, y})).(Air); ok { - w.SetBlock(pos.Add(cube.Pos{0, y}), SugarCane{}, nil) + if _, ok := tx.Block(pos.Add(cube.Pos{0, y})).(Air); ok { + tx.SetBlock(pos.Add(cube.Pos{0, y}), SugarCane{}, nil) } } return true @@ -78,14 +78,14 @@ func (c SugarCane) BoneMeal(pos cube.Pos, w *world.World) bool { } // canGrowHere implements logic to check if sugar cane can live/grow here. -func (c SugarCane) canGrowHere(pos cube.Pos, w *world.World, recursive bool) bool { - if _, ok := w.Block(pos.Side(cube.FaceDown)).(SugarCane); ok && recursive { - return c.canGrowHere(pos.Side(cube.FaceDown), w, recursive) +func (c SugarCane) canGrowHere(pos cube.Pos, tx *world.Tx, recursive bool) bool { + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(SugarCane); ok && recursive { + return c.canGrowHere(pos.Side(cube.FaceDown), tx, recursive) } - if supportsVegetation(c, w.Block(pos.Sub(cube.Pos{0, 1}))) { + if supportsVegetation(c, tx.Block(pos.Sub(cube.Pos{0, 1}))) { for _, face := range cube.HorizontalFaces() { - if liquid, ok := w.Liquid(pos.Side(face).Side(cube.FaceDown)); ok { + if liquid, ok := tx.Liquid(pos.Side(face).Side(cube.FaceDown)); ok { if _, ok := liquid.(Water); ok { return true } diff --git a/server/block/tnt.go b/server/block/tnt.go index ff22c4f77..8a222358f 100644 --- a/server/block/tnt.go +++ b/server/block/tnt.go @@ -18,10 +18,10 @@ type TNT struct { } // Activate ... -func (t TNT) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, ctx *item.UseContext) bool { +func (t TNT) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { held, _ := u.HeldItems() if _, ok := held.Enchantment(enchantment.FireAspect{}); ok { - t.Ignite(pos, w, u) + t.Ignite(pos, tx, u) ctx.DamageItem(1) return true } @@ -29,9 +29,9 @@ func (t TNT) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, ct } // Ignite ... -func (t TNT) Ignite(pos cube.Pos, w *world.World, igniter world.Entity) bool { +func (t TNT) Ignite(pos cube.Pos, tx *world.Tx, igniter world.Entity) bool { t.igniter = igniter - spawnTnt(pos, w, time.Second*4, t.igniter) + spawnTnt(pos, tx, time.Second*4, t.igniter) return true } @@ -42,8 +42,8 @@ func (t TNT) Igniter() world.Entity { } // Explode ... -func (t TNT) Explode(_ mgl64.Vec3, pos cube.Pos, w *world.World, _ ExplosionConfig) { - spawnTnt(pos, w, time.Second/2+time.Duration(rand.Intn(int(time.Second+time.Second/2))), t.igniter) +func (t TNT) Explode(explosionPos mgl64.Vec3, pos cube.Pos, tx *world.Tx, c ExplosionConfig) { + spawnTnt(pos, tx, time.Second/2+time.Duration(rand.Intn(int(time.Second+time.Second/2))), t.igniter) } // BreakInfo ... @@ -67,8 +67,8 @@ func (t TNT) EncodeBlock() (name string, properties map[string]interface{}) { } // spawnTnt creates a new TNT entity at the given position with the given fuse duration. -func spawnTnt(pos cube.Pos, w *world.World, fuse time.Duration, igniter world.Entity) { - w.PlaySound(pos.Vec3Centre(), sound.TNT{}) - w.SetBlock(pos, nil, nil) - w.AddEntity(w.EntityRegistry().Config().TNT(pos.Vec3Centre(), fuse, igniter)) +func spawnTnt(pos cube.Pos, tx *world.Tx, fuse time.Duration, igniter world.Entity) { + tx.PlaySound(pos.Vec3Centre(), sound.TNT{}) + tx.SetBlock(pos, nil, nil) + tx.AddEntity(tx.World().EntityRegistry().Config().TNT(pos.Vec3Centre(), fuse, igniter)) } diff --git a/server/block/torch.go b/server/block/torch.go index 98056d195..24124a0aa 100644 --- a/server/block/torch.go +++ b/server/block/torch.go @@ -34,21 +34,21 @@ func (t Torch) LightEmissionLevel() uint8 { } // UseOnBlock ... -func (t Torch) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, face, used := firstReplaceable(w, pos, face, t) +func (t Torch) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, face, used := firstReplaceable(tx, pos, face, t) if !used { return false } if face == cube.FaceDown { return false } - if _, ok := w.Block(pos).(world.Liquid); ok { + if _, ok := tx.Block(pos).(world.Liquid); ok { return false } - if !w.Block(pos.Side(face.Opposite())).Model().FaceSolid(pos.Side(face.Opposite()), face, w) { + if !tx.Block(pos.Side(face.Opposite())).Model().FaceSolid(pos.Side(face.Opposite()), face, tx) { found := false for _, i := range []cube.Face{cube.FaceSouth, cube.FaceWest, cube.FaceNorth, cube.FaceEast, cube.FaceDown} { - if w.Block(pos.Side(i)).Model().FaceSolid(pos.Side(i), i.Opposite(), w) { + if tx.Block(pos.Side(i)).Model().FaceSolid(pos.Side(i), i.Opposite(), tx) { found = true face = i.Opposite() break @@ -60,15 +60,15 @@ func (t Torch) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.W } t.Facing = face.Opposite() - place(w, pos, t, user, ctx) + place(tx, pos, t, user, ctx) return placed(ctx) } // NeighbourUpdateTick ... -func (t Torch) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if !w.Block(pos.Side(t.Facing)).Model().FaceSolid(pos.Side(t.Facing), t.Facing.Opposite(), w) { - w.SetBlock(pos, nil, nil) - dropItem(w, item.NewStack(t, 1), pos.Vec3Centre()) +func (t Torch) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if !tx.Block(pos.Side(t.Facing)).Model().FaceSolid(pos.Side(t.Facing), t.Facing.Opposite(), tx) { + tx.SetBlock(pos, nil, nil) + dropItem(tx, item.NewStack(t, 1), pos.Vec3Centre()) } } diff --git a/server/block/wall.go b/server/block/wall.go index 332edbe12..61c7e884a 100644 --- a/server/block/wall.go +++ b/server/block/wall.go @@ -76,28 +76,28 @@ func (w Wall) BreakInfo() BreakInfo { } // NeighbourUpdateTick ... -func (w Wall) NeighbourUpdateTick(pos, _ cube.Pos, wo *world.World) { - w, connectionsUpdated := w.calculateConnections(wo, pos) - w, postUpdated := w.calculatePost(wo, pos) +func (w Wall) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + w, connectionsUpdated := w.calculateConnections(tx, pos) + w, postUpdated := w.calculatePost(tx, pos) if connectionsUpdated || postUpdated { - wo.SetBlock(pos, w, nil) + tx.SetBlock(pos, w, nil) } } // UseOnBlock ... -func (w Wall) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, wo *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, _, used = firstReplaceable(wo, pos, face, w) +func (w Wall) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, w) if !used { return } - w, _ = w.calculateConnections(wo, pos) - w, _ = w.calculatePost(wo, pos) - place(wo, pos, w, user, ctx) + w, _ = w.calculateConnections(tx, pos) + w, _ = w.calculatePost(tx, pos) + place(tx, pos, w, user, ctx) return placed(ctx) } // SideClosed ... -func (Wall) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (Wall) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } @@ -133,22 +133,22 @@ func (w Wall) WithConnectionType(direction cube.Direction, connection WallConnec // calculateConnections calculates the correct connections for the wall at a given position in a world. The updated wall // is returned and a bool to determine if any changes were made. -func (w Wall) calculateConnections(wo *world.World, pos cube.Pos) (Wall, bool) { +func (w Wall) calculateConnections(tx *world.Tx, pos cube.Pos) (Wall, bool) { var updated bool abovePos := pos.Add(cube.Pos{0, 1, 0}) - above := wo.Block(abovePos) + above := tx.Block(abovePos) for _, face := range cube.HorizontalFaces() { sidePos := pos.Side(face) - side := wo.Block(sidePos) + side := tx.Block(sidePos) // A wall can only connect to a block if the side is solid, with the only exception being thin blocks (such as // glass panes and iron bars) as well as the sides of fence gates. - connected := side.Model().FaceSolid(sidePos, face.Opposite(), wo) + connected := side.Model().FaceSolid(sidePos, face.Opposite(), tx) if !connected { - if _, ok := wo.Block(sidePos).(Wall); ok { + if _, ok := tx.Block(sidePos).(Wall); ok { connected = true - } else if gate, ok := wo.Block(sidePos).(WoodFenceGate); ok { + } else if gate, ok := tx.Block(sidePos).(WoodFenceGate); ok { connected = gate.Facing.Face().Axis() != face.Axis() - } else if _, ok := wo.Block(sidePos).Model().(model.Thin); ok { + } else if _, ok := tx.Block(sidePos).Model().(model.Thin); ok { connected = true } } @@ -157,7 +157,7 @@ func (w Wall) calculateConnections(wo *world.World, pos cube.Pos) (Wall, bool) { // If the wall is connected to the side, it has the possibility of having a tall connection. This is // calculated by checking for any overlapping blocks in the area of the connection. connectionType = ShortWallConnection() - boxes := above.Model().BBox(abovePos, wo) + boxes := above.Model().BBox(abovePos, tx) for _, bb := range boxes { if bb.Min().Y() == 0 { xOverlap := bb.Min().X() < 0.75 && bb.Max().X() > 0.25 @@ -191,10 +191,10 @@ func (w Wall) calculateConnections(wo *world.World, pos cube.Pos) (Wall, bool) { // calculatePost calculates the correct post bit for the wall at a given position in a world. The updated wall is // returned and a bool to determine if any changes were made. -func (w Wall) calculatePost(wo *world.World, pos cube.Pos) (Wall, bool) { +func (w Wall) calculatePost(tx *world.Tx, pos cube.Pos) (Wall, bool) { var updated bool abovePos := pos.Add(cube.Pos{0, 1, 0}) - above := wo.Block(abovePos) + above := tx.Block(abovePos) connections := len(sliceutil.Filter(cube.HorizontalFaces(), func(face cube.Face) bool { return w.ConnectionType(face.Direction()) != NoWallConnection() })) diff --git a/server/block/water.go b/server/block/water.go index ff27ae4bd..085cf4221 100644 --- a/server/block/water.go +++ b/server/block/water.go @@ -27,7 +27,7 @@ type Water struct { } // EntityInside ... -func (w Water) EntityInside(_ cube.Pos, _ *world.World, e world.Entity) { +func (w Water) EntityInside(pos cube.Pos, tx *world.Tx, e world.Entity) { if fallEntity, ok := e.(fallDistanceEntity); ok { fallEntity.ResetFallDistance() } @@ -83,43 +83,43 @@ func (Water) LightDiffusionLevel() uint8 { } // ScheduledTick ... -func (w Water) ScheduledTick(pos cube.Pos, wo *world.World, _ *rand.Rand) { +func (w Water) ScheduledTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { if w.Depth == 7 { // Attempt to form new water source blocks. count := 0 pos.Neighbours(func(neighbour cube.Pos) { if neighbour[1] == pos[1] { - if liquid, ok := wo.Liquid(neighbour); ok { + if liquid, ok := tx.Liquid(neighbour); ok { if water, ok := liquid.(Water); ok && water.Depth == 8 && !water.Falling { count++ } } } - }, wo.Range()) + }, tx.Range()) if count >= 2 { - if !canFlowInto(w, wo, pos.Side(cube.FaceDown), true) { + if !canFlowInto(w, tx, pos.Side(cube.FaceDown), true) { // Only form a new source block if there either is no water below this block, or if the water // below this is not falling (full source block). res := Water{Depth: 8, Still: true} ctx := event.C() - if wo.Handler().HandleLiquidFlow(ctx, pos, pos, res, w); ctx.Cancelled() { + if tx.World().Handler().HandleLiquidFlow(ctx, pos, pos, res, w); ctx.Cancelled() { return } - wo.SetLiquid(pos, res) + tx.SetLiquid(pos, res) } } } - tickLiquid(w, pos, wo) + tickLiquid(w, pos, tx) } // NeighbourUpdateTick ... -func (Water) NeighbourUpdateTick(pos, _ cube.Pos, wo *world.World) { - if wo.Dimension().WaterEvaporates() { +func (Water) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if tx.World().Dimension().WaterEvaporates() { // Particles are spawned client-side. - wo.SetLiquid(pos, nil) + tx.SetLiquid(pos, nil) return } - wo.ScheduleBlockUpdate(pos, time.Second/4) + tx.ScheduleBlockUpdate(pos, time.Second/4) } // LiquidType ... @@ -128,25 +128,25 @@ func (Water) LiquidType() string { } // Harden hardens the water if lava flows into it. -func (w Water) Harden(pos cube.Pos, wo *world.World, flownIntoBy *cube.Pos) bool { +func (w Water) Harden(pos cube.Pos, tx *world.Tx, flownIntoBy *cube.Pos) bool { if flownIntoBy == nil { return false } - if lava, ok := wo.Block(pos.Side(cube.FaceUp)).(Lava); ok { + if lava, ok := tx.Block(pos.Side(cube.FaceUp)).(Lava); ok { ctx := event.C() - if wo.Handler().HandleLiquidHarden(ctx, pos, w, lava, Stone{}); ctx.Cancelled() { + if tx.World().Handler().HandleLiquidHarden(ctx, pos, w, lava, Stone{}); ctx.Cancelled() { return false } - wo.SetBlock(pos, Stone{}, nil) - wo.PlaySound(pos.Vec3Centre(), sound.Fizz{}) + tx.SetBlock(pos, Stone{}, nil) + tx.PlaySound(pos.Vec3Centre(), sound.Fizz{}) return true - } else if lava, ok := wo.Block(*flownIntoBy).(Lava); ok { + } else if lava, ok := tx.Block(*flownIntoBy).(Lava); ok { ctx := event.C() - if wo.Handler().HandleLiquidHarden(ctx, pos, w, lava, Cobblestone{}); ctx.Cancelled() { + if tx.World().Handler().HandleLiquidHarden(ctx, pos, w, lava, Cobblestone{}); ctx.Cancelled() { return false } - wo.SetBlock(*flownIntoBy, Cobblestone{}, nil) - wo.PlaySound(pos.Vec3Centre(), sound.Fizz{}) + tx.SetBlock(*flownIntoBy, Cobblestone{}, nil) + tx.PlaySound(pos.Vec3Centre(), sound.Fizz{}) return true } return false diff --git a/server/block/wheat_seeds.go b/server/block/wheat_seeds.go index ecf91ffbe..e8b78b9ee 100644 --- a/server/block/wheat_seeds.go +++ b/server/block/wheat_seeds.go @@ -21,27 +21,27 @@ func (WheatSeeds) SameCrop(c Crop) bool { } // BoneMeal ... -func (s WheatSeeds) BoneMeal(pos cube.Pos, w *world.World) bool { +func (s WheatSeeds) BoneMeal(pos cube.Pos, tx *world.Tx) bool { if s.Growth == 7 { return false } s.Growth = min(s.Growth+rand.Intn(4)+2, 7) - w.SetBlock(pos, s, nil) + tx.SetBlock(pos, s, nil) return true } // UseOnBlock ... -func (s WheatSeeds) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, s) +func (s WheatSeeds) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, s) if !used { return false } - if _, ok := w.Block(pos.Side(cube.FaceDown)).(Farmland); !ok { + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(Farmland); !ok { return false } - place(w, pos, s, user, ctx) + place(tx, pos, s, user, ctx) return placed(ctx) } @@ -66,13 +66,13 @@ func (s WheatSeeds) EncodeItem() (name string, meta int16) { } // RandomTick ... -func (s WheatSeeds) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { - if w.Light(pos) < 8 { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: s}) - } else if s.Growth < 7 && r.Float64() <= s.CalculateGrowthChance(pos, w) { +func (s WheatSeeds) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { + if tx.Light(pos) < 8 { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: s}) + } else if s.Growth < 7 && r.Float64() <= s.CalculateGrowthChance(pos, tx) { s.Growth++ - w.SetBlock(pos, s, nil) + tx.SetBlock(pos, s, nil) } } diff --git a/server/block/wood.go b/server/block/wood.go index 6b8a9ef83..4f6bd6568 100644 --- a/server/block/wood.go +++ b/server/block/wood.go @@ -51,14 +51,14 @@ func (Wood) FuelInfo() item.FuelInfo { } // UseOnBlock ... -func (w Wood) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, wo *world.World, user item.User, ctx *item.UseContext) (used bool) { - pos, face, used = firstReplaceable(wo, pos, face, w) +func (w Wood) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, face, used = firstReplaceable(tx, pos, face, w) if !used { return } w.Axis = face.Axis() - place(wo, pos, w, user, ctx) + place(tx, pos, w, user, ctx) return placed(ctx) } diff --git a/server/block/wood_door.go b/server/block/wood_door.go index 0a595b760..eaef2910c 100644 --- a/server/block/wood_door.go +++ b/server/block/wood_door.go @@ -49,40 +49,40 @@ func (d WoodDoor) Model() world.BlockModel { } // NeighbourUpdateTick ... -func (d WoodDoor) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { +func (d WoodDoor) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { if d.Top { - if _, ok := w.Block(pos.Side(cube.FaceDown)).(WoodDoor); !ok { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(WoodDoor); !ok { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) } return } - if solid := w.Block(pos.Side(cube.FaceDown)).Model().FaceSolid(pos.Side(cube.FaceDown), cube.FaceUp, w); !solid { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) - } else if _, ok := w.Block(pos.Side(cube.FaceUp)).(WoodDoor); !ok { - w.SetBlock(pos, nil, nil) - w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) + if solid := tx.Block(pos.Side(cube.FaceDown)).Model().FaceSolid(pos.Side(cube.FaceDown), cube.FaceUp, tx); !solid { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) + } else if _, ok := tx.Block(pos.Side(cube.FaceUp)).(WoodDoor); !ok { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) } } // UseOnBlock handles the directional placing of doors -func (d WoodDoor) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { +func (d WoodDoor) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { if face != cube.FaceUp { // Doors can only be placed when clicking the top face. return false } below := pos pos = pos.Side(cube.FaceUp) - if !replaceableWith(w, pos, d) || !replaceableWith(w, pos.Side(cube.FaceUp), d) { + if !replaceableWith(tx, pos, d) || !replaceableWith(tx, pos.Side(cube.FaceUp), d) { return false } - if !w.Block(below).Model().FaceSolid(below, cube.FaceUp, w) { + if !tx.Block(below).Model().FaceSolid(below, cube.FaceUp, tx) { return false } d.Facing = user.Rotation().Direction() - left := w.Block(pos.Side(d.Facing.RotateLeft().Face())) - right := w.Block(pos.Side(d.Facing.RotateRight().Face())) + left := tx.Block(pos.Side(d.Facing.RotateLeft().Face())) + right := tx.Block(pos.Side(d.Facing.RotateRight().Face())) if door, ok := left.(WoodDoor); ok { if door.Wood == d.Wood { d.Right = true @@ -98,28 +98,28 @@ func (d WoodDoor) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *worl } ctx.IgnoreBBox = true - place(w, pos, d, user, ctx) - place(w, pos.Side(cube.FaceUp), WoodDoor{Wood: d.Wood, Facing: d.Facing, Top: true, Right: d.Right}, user, ctx) + place(tx, pos, d, user, ctx) + place(tx, pos.Side(cube.FaceUp), WoodDoor{Wood: d.Wood, Facing: d.Facing, Top: true, Right: d.Right}, user, ctx) ctx.SubtractFromCount(1) return placed(ctx) } // Activate ... -func (d WoodDoor) Activate(pos cube.Pos, _ cube.Face, w *world.World, _ item.User, _ *item.UseContext) bool { +func (d WoodDoor) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { d.Open = !d.Open - w.SetBlock(pos, d, nil) + tx.SetBlock(pos, d, nil) otherPos := pos.Side(cube.Face(boolByte(!d.Top))) - other := w.Block(otherPos) + other := tx.Block(otherPos) if door, ok := other.(WoodDoor); ok { door.Open = d.Open - w.SetBlock(otherPos, door, nil) + tx.SetBlock(otherPos, door, nil) } if d.Open { - w.PlaySound(pos.Vec3Centre(), sound.DoorOpen{Block: d}) + tx.PlaySound(pos.Vec3Centre(), sound.DoorOpen{Block: d}) return true } - w.PlaySound(pos.Vec3Centre(), sound.DoorClose{Block: d}) + tx.PlaySound(pos.Vec3Centre(), sound.DoorClose{Block: d}) return true } @@ -129,7 +129,7 @@ func (d WoodDoor) BreakInfo() BreakInfo { } // SideClosed ... -func (d WoodDoor) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (d WoodDoor) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } diff --git a/server/block/wood_fence.go b/server/block/wood_fence.go index dfbd3886e..e72264b45 100644 --- a/server/block/wood_fence.go +++ b/server/block/wood_fence.go @@ -26,7 +26,7 @@ func (w WoodFence) BreakInfo() BreakInfo { } // SideClosed ... -func (WoodFence) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (WoodFence) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } diff --git a/server/block/wood_fence_gate.go b/server/block/wood_fence_gate.go index 8f25d4402..863e79868 100644 --- a/server/block/wood_fence_gate.go +++ b/server/block/wood_fence_gate.go @@ -46,51 +46,51 @@ func (WoodFenceGate) FuelInfo() item.FuelInfo { } // UseOnBlock ... -func (f WoodFenceGate) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, _, used := firstReplaceable(w, pos, face, f) +func (f WoodFenceGate) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, f) if !used { return false } f.Facing = user.Rotation().Direction() - f.Lowered = f.shouldBeLowered(pos, w) + f.Lowered = f.shouldBeLowered(pos, tx) - place(w, pos, f, user, ctx) + place(tx, pos, f, user, ctx) return placed(ctx) } // NeighbourUpdateTick ... -func (f WoodFenceGate) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { - if f.shouldBeLowered(pos, w) != f.Lowered { +func (f WoodFenceGate) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if f.shouldBeLowered(pos, tx) != f.Lowered { f.Lowered = !f.Lowered - w.SetBlock(pos, f, nil) + tx.SetBlock(pos, f, nil) } } // shouldBeLowered returns if the fence gate should be lowered or not, based on the neighbouring walls. -func (f WoodFenceGate) shouldBeLowered(pos cube.Pos, w *world.World) bool { +func (f WoodFenceGate) shouldBeLowered(pos cube.Pos, tx *world.Tx) bool { leftSide := f.Facing.RotateLeft().Face() - _, left := w.Block(pos.Side(leftSide)).(Wall) - _, right := w.Block(pos.Side(leftSide.Opposite())).(Wall) + _, left := tx.Block(pos.Side(leftSide)).(Wall) + _, right := tx.Block(pos.Side(leftSide.Opposite())).(Wall) return left || right } // Activate ... -func (f WoodFenceGate) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, _ *item.UseContext) bool { +func (f WoodFenceGate) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { f.Open = !f.Open if f.Open && f.Facing.Opposite() == u.Rotation().Direction() { f.Facing = f.Facing.Opposite() } - w.SetBlock(pos, f, nil) + tx.SetBlock(pos, f, nil) if f.Open { - w.PlaySound(pos.Vec3Centre(), sound.FenceGateOpen{Block: f}) + tx.PlaySound(pos.Vec3Centre(), sound.FenceGateOpen{Block: f}) return true } - w.PlaySound(pos.Vec3Centre(), sound.FenceGateClose{Block: f}) + tx.PlaySound(pos.Vec3Centre(), sound.FenceGateClose{Block: f}) return true } // SideClosed ... -func (f WoodFenceGate) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (f WoodFenceGate) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } diff --git a/server/block/wood_trapdoor.go b/server/block/wood_trapdoor.go index 1eb7fec84..594ceedf5 100644 --- a/server/block/wood_trapdoor.go +++ b/server/block/wood_trapdoor.go @@ -43,27 +43,27 @@ func (t WoodTrapdoor) Model() world.BlockModel { // UseOnBlock handles the directional placing of trapdoors and makes sure they are properly placed upside down // when needed. -func (t WoodTrapdoor) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { - pos, face, used := firstReplaceable(w, pos, face, t) +func (t WoodTrapdoor) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, face, used := firstReplaceable(tx, pos, face, t) if !used { return false } t.Facing = user.Rotation().Direction().Opposite() t.Top = (clickPos.Y() > 0.5 && face != cube.FaceUp) || face == cube.FaceDown - place(w, pos, t, user, ctx) + place(tx, pos, t, user, ctx) return placed(ctx) } // Activate ... -func (t WoodTrapdoor) Activate(pos cube.Pos, _ cube.Face, w *world.World, _ item.User, _ *item.UseContext) bool { +func (t WoodTrapdoor) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { t.Open = !t.Open - w.SetBlock(pos, t, nil) + tx.SetBlock(pos, t, nil) if t.Open { - w.PlaySound(pos.Vec3Centre(), sound.TrapdoorOpen{Block: t}) + tx.PlaySound(pos.Vec3Centre(), sound.TrapdoorOpen{Block: t}) return true } - w.PlaySound(pos.Vec3Centre(), sound.TrapdoorClose{Block: t}) + tx.PlaySound(pos.Vec3Centre(), sound.TrapdoorClose{Block: t}) return true } @@ -78,7 +78,7 @@ func (WoodTrapdoor) FuelInfo() item.FuelInfo { } // SideClosed ... -func (t WoodTrapdoor) SideClosed(cube.Pos, cube.Pos, *world.World) bool { +func (t WoodTrapdoor) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } diff --git a/server/entity/bottle_of_enchanting.go b/server/entity/bottle_of_enchanting.go index 3c970e592..1494ce452 100644 --- a/server/entity/bottle_of_enchanting.go +++ b/server/entity/bottle_of_enchanting.go @@ -27,10 +27,10 @@ var bottleOfEnchantingConf = ProjectileBehaviourConfig{ // spawnExperience spawns experience orbs with a value of 3-11 at the target of // a trace.Result. -func spawnExperience(e *Ent, target trace.Result) { +func spawnExperience(e *Ent, tx *world.Tx, target trace.Result) { for _, orb := range NewExperienceOrbs(target.Position(), rand.Intn(9)+3) { orb.SetVelocity(mgl64.Vec3{(rand.Float64()*0.2 - 0.1) * 2, rand.Float64() * 0.4, (rand.Float64()*0.2 - 0.1) * 2}) - e.World().AddEntity(orb) + tx.AddEntity(orb) } } diff --git a/server/entity/falling_block_behaviour.go b/server/entity/falling_block_behaviour.go index 5283ad767..4c3c475d9 100644 --- a/server/entity/falling_block_behaviour.go +++ b/server/entity/falling_block_behaviour.go @@ -61,34 +61,34 @@ func (f *FallingBlockBehaviour) tick(e *Ent) { // also deals damage to any entities standing at that position. If the block at // the position could not be replaced by the falling block, the block will drop // as an item. -func (f *FallingBlockBehaviour) solidify(e *Ent, pos mgl64.Vec3, w *world.World) { +func (f *FallingBlockBehaviour) solidify(e *Ent, pos mgl64.Vec3, tx *world.Tx) { bpos := cube.PosFromVec3(pos) if d, ok := f.block.(damager); ok { - f.damageEntities(e, d, pos, w) + f.damageEntities(e, d, pos, tx) } if l, ok := f.block.(landable); ok { - l.Landed(w, bpos) + l.Landed(tx, bpos) } f.passive.close = true - if r, ok := w.Block(bpos).(replaceable); ok && r.ReplaceableBy(f.block) { - w.SetBlock(bpos, f.block, nil) + if r, ok := tx.Block(bpos).(replaceable); ok && r.ReplaceableBy(f.block) { + tx.SetBlock(bpos, f.block, nil) } else if i, ok := f.block.(world.Item); ok { - w.AddEntity(NewItem(item.NewStack(i, 1), bpos.Vec3Middle())) + tx.AddEntity(NewItem(item.NewStack(i, 1), bpos.Vec3Middle())) } } // damageEntities attempts to damage any entities standing below the falling // block. This functionality is used by falling anvils. -func (f *FallingBlockBehaviour) damageEntities(e *Ent, d damager, pos mgl64.Vec3, w *world.World) { +func (f *FallingBlockBehaviour) damageEntities(e *Ent, d damager, pos mgl64.Vec3, tx *world.Tx) { damagePerBlock, maxDamage := d.Damage() dist := math.Ceil(f.passive.fallDistance - 1.0) if dist <= 0 { return } dmg := math.Min(math.Floor(dist*damagePerBlock), maxDamage) - targets := w.EntitiesWithin(e.Type().BBox(e).Translate(pos).Grow(0.05), func(entity world.Entity) bool { + targets := tx.EntitiesWithin(e.Type().BBox(e).Translate(pos).Grow(0.05), func(entity world.Entity) bool { _, ok := entity.(Living) return !ok || entity == e }) @@ -107,7 +107,7 @@ func (f *FallingBlockBehaviour) damageEntities(e *Ent, d damager, pos mgl64.Vec3 type Solidifiable interface { // Solidifies returns whether the falling block can solidify at the position it is currently in. If so, // the block will immediately stop falling. - Solidifies(pos cube.Pos, w *world.World) bool + Solidifies(pos cube.Pos, tx *world.Tx) bool } type replaceable interface { @@ -126,5 +126,5 @@ type breakable interface { // landable ... type landable interface { - Landed(w *world.World, pos cube.Pos) + Landed(tx *world.Tx, pos cube.Pos) } diff --git a/server/entity/projectile.go b/server/entity/projectile.go index a6dfec91c..a928f9e26 100644 --- a/server/entity/projectile.go +++ b/server/entity/projectile.go @@ -54,7 +54,7 @@ type ProjectileBehaviourConfig struct { // (the trace.Result). The target is either of the type trace.EntityResult // or trace.BlockResult. Hit may be set to run additional behaviour when a // projectile hits a target. - Hit func(e *Ent, target trace.Result) + Hit func(e *Ent, tx *world.Tx, target trace.Result) // SurviveBlockCollision specifies if a projectile with this // ProjectileBehaviour should survive collision with a block. If set to // false, the projectile will break when hitting a block (like a snowball). diff --git a/server/entity/splashable.go b/server/entity/splashable.go index 9e5ae8840..e85bb38ad 100644 --- a/server/entity/splashable.go +++ b/server/entity/splashable.go @@ -14,14 +14,14 @@ import ( type SplashableBlock interface { world.Block // Splash is called when a water bottle splashes onto a block. - Splash(w *world.World, pos cube.Pos) + Splash(tx *world.Tx, pos cube.Pos) } // SplashableEntity is an entity that can be splashed with a splash bottle. type SplashableEntity interface { world.Entity // Splash is called when a water bottle splashes onto an entity. - Splash(w *world.World, pos mgl64.Vec3) + Splash(tx *world.Tx, pos mgl64.Vec3) } // potionSplash returns a function that creates a potion splash with a specific diff --git a/server/item/apple.go b/server/item/apple.go index 1b4151b92..fdc63fce9 100644 --- a/server/item/apple.go +++ b/server/item/apple.go @@ -10,7 +10,7 @@ type Apple struct { } // Consume ... -func (a Apple) Consume(_ *world.World, c Consumer) Stack { +func (a Apple) Consume(tx *world.Tx, c Consumer) Stack { c.Saturate(4, 2.4) return Stack{} } diff --git a/server/item/axe.go b/server/item/axe.go index 65fe5f884..4744e991f 100644 --- a/server/item/axe.go +++ b/server/item/axe.go @@ -16,11 +16,11 @@ type Axe struct { } // UseOnBlock handles the stripping of logs when a player clicks a log with an axe. -func (a Axe) UseOnBlock(pos cube.Pos, _ cube.Face, _ mgl64.Vec3, w *world.World, _ User, ctx *UseContext) bool { - if s, ok := w.Block(pos).(strippable); ok { +func (a Axe) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user User, ctx *UseContext) bool { + if s, ok := tx.Block(pos).(strippable); ok { if res, ok := s.Strip(); ok { - w.SetBlock(pos, res, nil) - w.PlaySound(pos.Vec3(), sound.ItemUseOn{Block: res}) + tx.SetBlock(pos, res, nil) + tx.PlaySound(pos.Vec3(), sound.ItemUseOn{Block: res}) ctx.DamageItem(1) return true diff --git a/server/item/baked_potato.go b/server/item/baked_potato.go index dfc840c96..d4e5ada8f 100644 --- a/server/item/baked_potato.go +++ b/server/item/baked_potato.go @@ -8,7 +8,7 @@ type BakedPotato struct { } // Consume ... -func (BakedPotato) Consume(_ *world.World, c Consumer) Stack { +func (BakedPotato) Consume(tx *world.Tx, c Consumer) Stack { c.Saturate(5, 6) return Stack{} } diff --git a/server/item/beef.go b/server/item/beef.go index 3f1bb6b0e..42fc40d60 100644 --- a/server/item/beef.go +++ b/server/item/beef.go @@ -11,7 +11,7 @@ type Beef struct { } // Consume ... -func (b Beef) Consume(_ *world.World, c Consumer) Stack { +func (b Beef) Consume(tx *world.Tx, c Consumer) Stack { if b.Cooked { c.Saturate(8, 12.8) } else { diff --git a/server/item/beetroot.go b/server/item/beetroot.go index 1e5359b73..dc1bf7766 100644 --- a/server/item/beetroot.go +++ b/server/item/beetroot.go @@ -10,7 +10,7 @@ type Beetroot struct { } // Consume ... -func (b Beetroot) Consume(_ *world.World, c Consumer) Stack { +func (b Beetroot) Consume(tx *world.Tx, c Consumer) Stack { c.Saturate(1, 1.2) return Stack{} } diff --git a/server/item/beetroot_soup.go b/server/item/beetroot_soup.go index 99d8a3836..5586474d4 100644 --- a/server/item/beetroot_soup.go +++ b/server/item/beetroot_soup.go @@ -15,7 +15,7 @@ func (BeetrootSoup) MaxCount() int { } // Consume ... -func (BeetrootSoup) Consume(_ *world.World, c Consumer) Stack { +func (BeetrootSoup) Consume(tx *world.Tx, c Consumer) Stack { c.Saturate(6, 7.2) return NewStack(Bowl{}, 1) } diff --git a/server/item/bone_meal.go b/server/item/bone_meal.go index 30a002d9a..bf003e249 100644 --- a/server/item/bone_meal.go +++ b/server/item/bone_meal.go @@ -13,14 +13,14 @@ type BoneMeal struct{} // BoneMealAffected represents a block that is affected when bone meal is used on it. type BoneMealAffected interface { // BoneMeal attempts to affect the block using a bone meal item. - BoneMeal(pos cube.Pos, w *world.World) bool + BoneMeal(pos cube.Pos, tx *world.Tx) bool } // UseOnBlock ... -func (b BoneMeal) UseOnBlock(pos cube.Pos, _ cube.Face, _ mgl64.Vec3, w *world.World, _ User, ctx *UseContext) bool { - if bm, ok := w.Block(pos).(BoneMealAffected); ok && bm.BoneMeal(pos, w) { +func (b BoneMeal) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user User, ctx *UseContext) bool { + if bm, ok := tx.Block(pos).(BoneMealAffected); ok && bm.BoneMeal(pos, tx) { ctx.SubtractFromCount(1) - w.AddParticle(pos.Vec3(), particle.BoneMeal{}) + tx.AddParticle(pos.Vec3(), particle.BoneMeal{}) return true } return false diff --git a/server/item/boots.go b/server/item/boots.go index 938980246..76ce8f4a1 100644 --- a/server/item/boots.go +++ b/server/item/boots.go @@ -13,7 +13,7 @@ type Boots struct { } // Use handles the auto-equipping of boots in the armour slot when using it. -func (b Boots) Use(_ *world.World, _ User, ctx *UseContext) bool { +func (b Boots) Use(tx *world.Tx, user User, ctx *UseContext) bool { ctx.SwapHeldWithArmour(3) return false } diff --git a/server/item/bottle_of_enchanting.go b/server/item/bottle_of_enchanting.go index db0326ba7..ac0604095 100644 --- a/server/item/bottle_of_enchanting.go +++ b/server/item/bottle_of_enchanting.go @@ -9,10 +9,10 @@ import ( type BottleOfEnchanting struct{} // Use ... -func (b BottleOfEnchanting) Use(w *world.World, user User, ctx *UseContext) bool { - create := w.EntityRegistry().Config().BottleOfEnchanting - w.AddEntity(create(eyePosition(user), user.Rotation().Vec3().Mul(0.7), user)) - w.PlaySound(user.Position(), sound.ItemThrow{}) +func (b BottleOfEnchanting) Use(tx *world.Tx, user User, ctx *UseContext) bool { + create := tx.World().EntityRegistry().Config().BottleOfEnchanting + tx.AddEntity(create(eyePosition(user), user.Rotation().Vec3().Mul(0.7), user)) + tx.PlaySound(user.Position(), sound.ItemThrow{}) ctx.SubtractFromCount(1) return true diff --git a/server/item/bow.go b/server/item/bow.go index 5d8a8cbe7..e7719d0a8 100644 --- a/server/item/bow.go +++ b/server/item/bow.go @@ -3,6 +3,7 @@ package item import ( "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/item/potion" + "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/sound" "math" "time" @@ -30,7 +31,7 @@ func (Bow) FuelInfo() FuelInfo { } // Release ... -func (Bow) Release(releaser Releaser, duration time.Duration, ctx *UseContext) { +func (Bow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration) { creative := releaser.GameMode().CreativeInventory() ticks := duration.Milliseconds() / 50 if ticks < 3 { @@ -94,7 +95,7 @@ func (Bow) Release(releaser Releaser, duration time.Duration, ctx *UseContext) { } releaser.PlaySound(sound.BowShoot{}) - releaser.World().AddEntity(projectile) + tx.AddEntity(projectile) } // EnchantmentValue ... diff --git a/server/item/bread.go b/server/item/bread.go index 63ca39545..f7bc4923e 100644 --- a/server/item/bread.go +++ b/server/item/bread.go @@ -8,7 +8,7 @@ type Bread struct { } // Consume ... -func (Bread) Consume(_ *world.World, c Consumer) Stack { +func (Bread) Consume(tx *world.Tx, c Consumer) Stack { c.Saturate(5, 6) return Stack{} } diff --git a/server/item/bucket.go b/server/item/bucket.go index 575aa0abc..177b704a9 100644 --- a/server/item/bucket.go +++ b/server/item/bucket.go @@ -78,7 +78,7 @@ func (b Bucket) ConsumeDuration() time.Duration { } // Consume ... -func (b Bucket) Consume(_ *world.World, c Consumer) Stack { +func (b Bucket) Consume(tx *world.Tx, c Consumer) Stack { for _, effect := range c.Effects() { c.RemoveEffect(effect.Type()) } @@ -99,23 +99,23 @@ func (b Bucket) FuelInfo() FuelInfo { } // UseOnBlock handles the bucket filling and emptying logic. -func (b Bucket) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, _ User, ctx *UseContext) bool { +func (b Bucket) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user User, ctx *UseContext) bool { if b.Content.milk { return false } if b.Empty() { - return b.fillFrom(pos, w, ctx) + return b.fillFrom(pos, tx, ctx) } liq := b.Content.liquid.WithDepth(8, false) - if bl := w.Block(pos); canDisplace(bl, liq) || replaceableWith(bl, liq) { - w.SetLiquid(pos, liq) - } else if bl := w.Block(pos.Side(face)); canDisplace(bl, liq) || replaceableWith(bl, liq) { - w.SetLiquid(pos.Side(face), liq) + if bl := tx.Block(pos); canDisplace(bl, liq) || replaceableWith(bl, liq) { + tx.SetLiquid(pos, liq) + } else if bl := tx.Block(pos.Side(face)); canDisplace(bl, liq) || replaceableWith(bl, liq) { + tx.SetLiquid(pos.Side(face), liq) } else { return false } - w.PlaySound(pos.Vec3Centre(), sound.BucketEmpty{Liquid: b.Content.liquid}) + tx.PlaySound(pos.Vec3Centre(), sound.BucketEmpty{Liquid: b.Content.liquid}) ctx.NewItem = NewStack(Bucket{}, 1) ctx.NewItemSurvivalOnly = true ctx.SubtractFromCount(1) @@ -124,8 +124,8 @@ func (b Bucket) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world. // fillFrom fills a bucket from the liquid at the position passed in the world. If there is no liquid or if // the liquid is no source, fillFrom returns false. -func (b Bucket) fillFrom(pos cube.Pos, w *world.World, ctx *UseContext) bool { - liquid, ok := w.Liquid(pos) +func (b Bucket) fillFrom(pos cube.Pos, tx *world.Tx, ctx *UseContext) bool { + liquid, ok := tx.Liquid(pos) if !ok { return false } @@ -133,8 +133,8 @@ func (b Bucket) fillFrom(pos cube.Pos, w *world.World, ctx *UseContext) bool { // Only allow picking up liquid source blocks. return false } - w.SetLiquid(pos, nil) - w.PlaySound(pos.Vec3Centre(), sound.BucketFill{Liquid: liquid}) + tx.SetLiquid(pos, nil) + tx.PlaySound(pos.Vec3Centre(), sound.BucketFill{Liquid: liquid}) ctx.NewItem = NewStack(Bucket{Content: LiquidBucketContent(liquid)}, 1) ctx.NewItemSurvivalOnly = true diff --git a/server/item/chestplate.go b/server/item/chestplate.go index 43cf8295e..b0e01c03e 100644 --- a/server/item/chestplate.go +++ b/server/item/chestplate.go @@ -13,7 +13,7 @@ type Chestplate struct { } // Use handles the using of a chestplate to auto-equip it in the designated armour slot. -func (c Chestplate) Use(_ *world.World, _ User, ctx *UseContext) bool { +func (c Chestplate) Use(tx *world.Tx, user User, ctx *UseContext) bool { ctx.SwapHeldWithArmour(1) return false } diff --git a/server/item/chicken.go b/server/item/chicken.go index 48fe27da7..ce5e361a5 100644 --- a/server/item/chicken.go +++ b/server/item/chicken.go @@ -16,7 +16,7 @@ type Chicken struct { } // Consume ... -func (c Chicken) Consume(_ *world.World, co Consumer) Stack { +func (c Chicken) Consume(tx *world.Tx, co Consumer) Stack { if c.Cooked { co.Saturate(6, 7.2) } else { diff --git a/server/item/cod.go b/server/item/cod.go index 49c2e3c82..75ad79f49 100644 --- a/server/item/cod.go +++ b/server/item/cod.go @@ -11,7 +11,7 @@ type Cod struct { } // Consume ... -func (c Cod) Consume(_ *world.World, co Consumer) Stack { +func (c Cod) Consume(tx *world.Tx, co Consumer) Stack { if c.Cooked { co.Saturate(5, 6) } else { diff --git a/server/item/cookie.go b/server/item/cookie.go index ecb812ffc..febe8d7fc 100644 --- a/server/item/cookie.go +++ b/server/item/cookie.go @@ -8,7 +8,7 @@ type Cookie struct { } // Consume ... -func (Cookie) Consume(_ *world.World, c Consumer) Stack { +func (Cookie) Consume(tx *world.Tx, c Consumer) Stack { c.Saturate(2, 0.4) return Stack{} } diff --git a/server/item/dried_kelp.go b/server/item/dried_kelp.go index 14c9ae9b4..aebc7cd8c 100644 --- a/server/item/dried_kelp.go +++ b/server/item/dried_kelp.go @@ -19,7 +19,7 @@ func (DriedKelp) ConsumeDuration() time.Duration { } // Consume ... -func (DriedKelp) Consume(_ *world.World, c Consumer) Stack { +func (DriedKelp) Consume(tx *world.Tx, c Consumer) Stack { c.Saturate(1, 0.2) return Stack{} } diff --git a/server/item/dye.go b/server/item/dye.go index 97045acb2..0c94e7f04 100644 --- a/server/item/dye.go +++ b/server/item/dye.go @@ -13,10 +13,10 @@ type Dye struct { } // UseOnBlock implements the colouring behaviour of signs. -func (d Dye) UseOnBlock(pos cube.Pos, _ cube.Face, _ mgl64.Vec3, w *world.World, user User, ctx *UseContext) bool { - if dy, ok := w.Block(pos).(dyeable); ok { +func (d Dye) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user User, ctx *UseContext) bool { + if dy, ok := tx.Block(pos).(dyeable); ok { if res, ok := dy.Dye(pos, user.Position(), d.Colour); ok { - w.SetBlock(pos, res, nil) + tx.SetBlock(pos, res, nil) ctx.SubtractFromCount(1) return true } diff --git a/server/item/egg.go b/server/item/egg.go index dec5929ad..c2a121c08 100644 --- a/server/item/egg.go +++ b/server/item/egg.go @@ -14,10 +14,10 @@ func (e Egg) MaxCount() int { } // Use ... -func (e Egg) Use(w *world.World, user User, ctx *UseContext) bool { - create := w.EntityRegistry().Config().Egg - w.AddEntity(create(eyePosition(user), user.Rotation().Vec3().Mul(1.5), user)) - w.PlaySound(user.Position(), sound.ItemThrow{}) +func (e Egg) Use(tx *world.Tx, user User, ctx *UseContext) bool { + create := tx.World().EntityRegistry().Config().Egg + tx.AddEntity(create(eyePosition(user), user.Rotation().Vec3().Mul(1.5), user)) + tx.PlaySound(user.Position(), sound.ItemThrow{}) ctx.SubtractFromCount(1) return true diff --git a/server/item/elytra.go b/server/item/elytra.go index 46f1dae98..893a8c08d 100644 --- a/server/item/elytra.go +++ b/server/item/elytra.go @@ -6,7 +6,7 @@ import "github.com/df-mc/dragonfly/server/world" type Elytra struct{} // Use handles the using of an elytra to auto-equip it in an armour slot. -func (Elytra) Use(_ *world.World, _ User, ctx *UseContext) bool { +func (Elytra) Use(tx *world.Tx, user User, ctx *UseContext) bool { ctx.SwapHeldWithArmour(1) return false } diff --git a/server/item/enchanted_apple.go b/server/item/enchanted_apple.go index 562d53528..344d9b630 100644 --- a/server/item/enchanted_apple.go +++ b/server/item/enchanted_apple.go @@ -20,7 +20,7 @@ func (EnchantedApple) ConsumeDuration() time.Duration { } // Consume ... -func (EnchantedApple) Consume(_ *world.World, c Consumer) Stack { +func (EnchantedApple) Consume(tx *world.Tx, c Consumer) Stack { c.Saturate(4, 9.6) c.AddEffect(effect.New(effect.Absorption{}, 4, 2*time.Minute)) c.AddEffect(effect.New(effect.Regeneration{}, 5, 30*time.Second)) diff --git a/server/item/ender_pearl.go b/server/item/ender_pearl.go index 810b3bcd0..1ed29d5a1 100644 --- a/server/item/ender_pearl.go +++ b/server/item/ender_pearl.go @@ -10,10 +10,10 @@ import ( type EnderPearl struct{} // Use ... -func (e EnderPearl) Use(w *world.World, user User, ctx *UseContext) bool { - create := w.EntityRegistry().Config().EnderPearl - w.AddEntity(create(eyePosition(user), user.Rotation().Vec3().Mul(1.5), user)) - w.PlaySound(user.Position(), sound.ItemThrow{}) +func (e EnderPearl) Use(tx *world.Tx, user User, ctx *UseContext) bool { + create := tx.World().EntityRegistry().Config().EnderPearl + tx.AddEntity(create(eyePosition(user), user.Rotation().Vec3().Mul(1.5), user)) + tx.PlaySound(user.Position(), sound.ItemThrow{}) ctx.SubtractFromCount(1) return true diff --git a/server/item/fire_charge.go b/server/item/fire_charge.go index 7e1e0c70c..e0e3ad456 100644 --- a/server/item/fire_charge.go +++ b/server/item/fire_charge.go @@ -19,16 +19,16 @@ func (f FireCharge) EncodeItem() (name string, meta int16) { } // UseOnBlock ... -func (f FireCharge) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, u User, ctx *UseContext) bool { - if l, ok := w.Block(pos).(ignitable); ok && l.Ignite(pos, w, u) { +func (f FireCharge) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user User, ctx *UseContext) bool { + if l, ok := tx.Block(pos).(ignitable); ok && l.Ignite(pos, tx, user) { ctx.SubtractFromCount(1) - w.PlaySound(pos.Vec3Centre(), sound.FireCharge{}) + tx.PlaySound(pos.Vec3Centre(), sound.FireCharge{}) return true - } else if s := pos.Side(face); w.Block(s) == air() { + } else if s := pos.Side(face); tx.Block(s) == air() { ctx.SubtractFromCount(1) - w.PlaySound(s.Vec3Centre(), sound.FireCharge{}) - w.SetBlock(s, fire(), nil) - w.ScheduleBlockUpdate(s, time.Duration(30+rand.Intn(10))*time.Second/20) + tx.PlaySound(s.Vec3Centre(), sound.FireCharge{}) + tx.SetBlock(s, fire(), nil) + tx.ScheduleBlockUpdate(s, time.Duration(30+rand.Intn(10))*time.Second/20) return true } return false diff --git a/server/item/firework.go b/server/item/firework.go index 539d02fa1..dcffb7073 100644 --- a/server/item/firework.go +++ b/server/item/firework.go @@ -19,7 +19,7 @@ type Firework struct { } // Use ... -func (f Firework) Use(w *world.World, user User, ctx *UseContext) bool { +func (f Firework) Use(tx *world.Tx, user User, ctx *UseContext) bool { if g, ok := user.(interface { Gliding() bool }); !ok || !g.Gliding() { @@ -28,20 +28,20 @@ func (f Firework) Use(w *world.World, user User, ctx *UseContext) bool { pos := user.Position() - w.PlaySound(pos, sound.FireworkLaunch{}) - create := w.EntityRegistry().Config().Firework - w.AddEntity(create(pos, user.Rotation(), true, f, user)) + tx.PlaySound(pos, sound.FireworkLaunch{}) + create := tx.World().EntityRegistry().Config().Firework + tx.AddEntity(create(pos, user.Rotation(), true, f, user)) ctx.SubtractFromCount(1) return true } // UseOnBlock ... -func (f Firework) UseOnBlock(blockPos cube.Pos, _ cube.Face, clickPos mgl64.Vec3, w *world.World, user User, ctx *UseContext) bool { - pos := blockPos.Vec3().Add(clickPos) - create := w.EntityRegistry().Config().Firework - w.AddEntity(create(pos, cube.Rotation{rand.Float64() * 360, 90}, false, f, user)) - w.PlaySound(pos, sound.FireworkLaunch{}) +func (f Firework) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user User, ctx *UseContext) bool { + fpos := pos.Vec3().Add(clickPos) + create := tx.World().EntityRegistry().Config().Firework + tx.AddEntity(create(fpos, cube.Rotation{rand.Float64() * 360, 90}, false, f, user)) + tx.PlaySound(fpos, sound.FireworkLaunch{}) ctx.SubtractFromCount(1) return true diff --git a/server/item/flint_and_steel.go b/server/item/flint_and_steel.go index 9f600b97f..03bb11448 100644 --- a/server/item/flint_and_steel.go +++ b/server/item/flint_and_steel.go @@ -28,18 +28,18 @@ func (f FlintAndSteel) DurabilityInfo() DurabilityInfo { // ignitable represents a block that can be lit by a fire emitter, such as flint and steel. type ignitable interface { // Ignite is called when the block is lit by flint and steel. - Ignite(pos cube.Pos, w *world.World, igniter world.Entity) bool + Ignite(pos cube.Pos, tx *world.Tx, igniter world.Entity) bool } // UseOnBlock ... -func (f FlintAndSteel) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, u User, ctx *UseContext) bool { +func (f FlintAndSteel) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user User, ctx *UseContext) bool { ctx.DamageItem(1) - if l, ok := w.Block(pos).(ignitable); ok && l.Ignite(pos, w, u) { + if l, ok := tx.Block(pos).(ignitable); ok && l.Ignite(pos, tx, user) { return true - } else if s := pos.Side(face); w.Block(s) == air() { - w.PlaySound(s.Vec3Centre(), sound.Ignite{}) - w.SetBlock(s, fire(), nil) - w.ScheduleBlockUpdate(s, time.Duration(30+rand.Intn(10))*time.Second/20) + } else if s := pos.Side(face); tx.Block(s) == air() { + tx.PlaySound(s.Vec3Centre(), sound.Ignite{}) + tx.SetBlock(s, fire(), nil) + tx.ScheduleBlockUpdate(s, time.Duration(30+rand.Intn(10))*time.Second/20) return true } return false diff --git a/server/item/glass_bottle.go b/server/item/glass_bottle.go index 7aea9ed98..d2e41023c 100644 --- a/server/item/glass_bottle.go +++ b/server/item/glass_bottle.go @@ -19,15 +19,15 @@ type bottleFiller interface { } // UseOnBlock ... -func (g GlassBottle) UseOnBlock(pos cube.Pos, _ cube.Face, _ mgl64.Vec3, w *world.World, _ User, ctx *UseContext) bool { - bl := w.Block(pos) +func (g GlassBottle) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user User, ctx *UseContext) bool { + bl := tx.Block(pos) if b, ok := bl.(bottleFiller); ok { var res world.Block if res, ctx.NewItem, ok = b.FillBottle(); ok { ctx.SubtractFromCount(1) if res != bl { // Some blocks (think a cauldron) change when using a bottle on it. - w.SetBlock(pos, res, nil) + tx.SetBlock(pos, res, nil) } } } diff --git a/server/item/goat_horn.go b/server/item/goat_horn.go index 7fd91a910..ce7b693c3 100644 --- a/server/item/goat_horn.go +++ b/server/item/goat_horn.go @@ -26,15 +26,15 @@ func (GoatHorn) Cooldown() time.Duration { } // Use ... -func (g GoatHorn) Use(w *world.World, u User, _ *UseContext) bool { - w.PlaySound(u.Position(), sound.GoatHorn{Horn: g.Type}) +func (g GoatHorn) Use(tx *world.Tx, user User, ctx *UseContext) bool { + tx.PlaySound(user.Position(), sound.GoatHorn{Horn: g.Type}) time.AfterFunc(time.Second, func() { - if !u.UsingItem() { + if !user.UsingItem() { // We aren't using the goat horn anymore. return } - held, _ := u.HeldItems() + held, _ := user.HeldItems() if _, ok := held.Item().(GoatHorn); !ok { // We aren't holding the goat horn anymore. return @@ -42,7 +42,7 @@ func (g GoatHorn) Use(w *world.World, u User, _ *UseContext) bool { // The goat horn is forcefully released by the server after a second. If the client released the item itself, // before a second, this shouldn't do anything. - u.ReleaseItem() + user.ReleaseItem() }) return true } diff --git a/server/item/golden_apple.go b/server/item/golden_apple.go index b756d4453..807e945c1 100644 --- a/server/item/golden_apple.go +++ b/server/item/golden_apple.go @@ -20,7 +20,7 @@ func (e GoldenApple) ConsumeDuration() time.Duration { } // Consume ... -func (e GoldenApple) Consume(_ *world.World, c Consumer) Stack { +func (e GoldenApple) Consume(tx *world.Tx, c Consumer) Stack { c.Saturate(4, 9.6) c.AddEffect(effect.New(effect.Absorption{}, 1, 2*time.Minute)) c.AddEffect(effect.New(effect.Regeneration{}, 2, 5*time.Second)) diff --git a/server/item/golden_carrot.go b/server/item/golden_carrot.go index 85f969bea..03bef5429 100644 --- a/server/item/golden_carrot.go +++ b/server/item/golden_carrot.go @@ -9,7 +9,7 @@ type GoldenCarrot struct { } // Consume ... -func (GoldenCarrot) Consume(_ *world.World, c Consumer) Stack { +func (GoldenCarrot) Consume(tx *world.Tx, c Consumer) Stack { c.Saturate(6, 14.4) return Stack{} } diff --git a/server/item/helmet.go b/server/item/helmet.go index c1968961e..9ed907294 100644 --- a/server/item/helmet.go +++ b/server/item/helmet.go @@ -13,7 +13,7 @@ type Helmet struct { } // Use handles the using of a helmet to auto-equip it in an armour slot. -func (h Helmet) Use(_ *world.World, _ User, ctx *UseContext) bool { +func (h Helmet) Use(tx *world.Tx, user User, ctx *UseContext) bool { ctx.SwapHeldWithArmour(0) return false } diff --git a/server/item/hoe.go b/server/item/hoe.go index f47864298..5720e98c8 100644 --- a/server/item/hoe.go +++ b/server/item/hoe.go @@ -15,19 +15,19 @@ type Hoe struct { } // UseOnBlock will turn a dirt or grass block into a farmland if the necessary properties are met. -func (h Hoe) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, _ User, ctx *UseContext) bool { - if b, ok := w.Block(pos).(tillable); ok { +func (h Hoe) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user User, ctx *UseContext) bool { + if b, ok := tx.Block(pos).(tillable); ok { if res, ok := b.Till(); ok { if face == cube.FaceDown { // Tilled land isn't created when the bottom face is clicked. return false } - if w.Block(pos.Side(cube.FaceUp)) != air() { + if tx.Block(pos.Side(cube.FaceUp)) != air() { // Tilled land can only be created if air is above the grass block. return false } - w.SetBlock(pos, res, nil) - w.PlaySound(pos.Vec3(), sound.ItemUseOn{Block: res}) + tx.SetBlock(pos, res, nil) + tx.PlaySound(pos.Vec3(), sound.ItemUseOn{Block: res}) ctx.DamageItem(1) return true } diff --git a/server/item/honeycomb.go b/server/item/honeycomb.go index 807888e37..3ce1bf397 100644 --- a/server/item/honeycomb.go +++ b/server/item/honeycomb.go @@ -12,11 +12,11 @@ type Honeycomb struct{} // UseOnBlock handles the logic of using an ink sac on a sign. Glowing ink sacs turn the text of these signs glowing, // whereas normal ink sacs revert them back to non-glowing text. -func (Honeycomb) UseOnBlock(pos cube.Pos, _ cube.Face, _ mgl64.Vec3, w *world.World, user User, ctx *UseContext) bool { - if wa, ok := w.Block(pos).(waxable); ok { +func (Honeycomb) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user User, ctx *UseContext) bool { + if wa, ok := tx.Block(pos).(waxable); ok { if res, ok := wa.Wax(pos, user.Position()); ok { - w.SetBlock(pos, res, nil) - w.PlaySound(pos.Vec3(), sound.SignWaxed{}) + tx.SetBlock(pos, res, nil) + tx.PlaySound(pos.Vec3(), sound.SignWaxed{}) ctx.SubtractFromCount(1) return true } diff --git a/server/item/ink_sac.go b/server/item/ink_sac.go index acec4c8e1..d62de0058 100644 --- a/server/item/ink_sac.go +++ b/server/item/ink_sac.go @@ -15,10 +15,10 @@ type InkSac struct { // UseOnBlock handles the logic of using an ink sac on a sign. Glowing ink sacs turn the text of these signs glowing, // whereas normal ink sacs revert them back to non-glowing text. -func (i InkSac) UseOnBlock(pos cube.Pos, _ cube.Face, _ mgl64.Vec3, w *world.World, user User, ctx *UseContext) bool { - if in, ok := w.Block(pos).(inkable); ok { +func (i InkSac) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user User, ctx *UseContext) bool { + if in, ok := tx.Block(pos).(inkable); ok { if res, ok := in.Ink(pos, user.Position(), i.Glowing); ok { - w.SetBlock(pos, res, nil) + tx.SetBlock(pos, res, nil) ctx.SubtractFromCount(1) return true } diff --git a/server/item/item.go b/server/item/item.go index 6370b8483..027c4b885 100644 --- a/server/item/item.go +++ b/server/item/item.go @@ -28,7 +28,7 @@ type UsableOnBlock interface { // The position of the block that was clicked, along with the clicked face and the position clicked // relative to the corner of the block are passed. // UseOnBlock returns a bool indicating if the item was used successfully. - UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, w *world.World, user User, ctx *UseContext) bool + UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user User, ctx *UseContext) bool } // UsableOnEntity represents an item that may be used on an entity. If an item implements this interface, the @@ -37,7 +37,7 @@ type UsableOnEntity interface { // UseOnEntity is called when an item is used on an entity. The world passed is the world that the item is // used in, and the entity clicked and the user of the item are also passed. // UseOnEntity returns a bool indicating if the item was used successfully. - UseOnEntity(e world.Entity, w *world.World, user User, ctx *UseContext) bool + UseOnEntity(e world.Entity, tx *world.Tx, user User, ctx *UseContext) bool } // Usable represents an item that may be used 'in the air'. If an item implements this interface, the Use @@ -46,7 +46,7 @@ type Usable interface { // Use is called when the item is used in the air. The user that used the item and the world that the item // was used in are passed to the method. // Use returns a bool indicating if the item was used successfully. - Use(w *world.World, user User, ctx *UseContext) bool + Use(tx *world.Tx, user User, ctx *UseContext) bool } // Throwable represents a custom item that can be thrown such as a projectile. This will only have an effect on @@ -74,7 +74,7 @@ type Consumable interface { ConsumeDuration() time.Duration // Consume consumes one item of the Stack that the Consumable is in. The Stack returned is added back to // the inventory after consuming the item. For potions, for example, an empty bottle is returned. - Consume(w *world.World, c Consumer) Stack + Consume(tx *world.Tx, c Consumer) Stack } // Consumer represents a User that is able to consume Consumable items. @@ -152,7 +152,7 @@ type Releaser interface { // Releasable represents an item that can be released. type Releasable interface { // Release is called when an item is released. - Release(releaser Releaser, duration time.Duration, ctx *UseContext) + Release(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration) // Requirements returns the required items to release this item. Requirements() []Stack } @@ -191,7 +191,7 @@ type Compostable interface { // nopReleasable represents a releasable item that does nothing. type nopReleasable struct{} -func (nopReleasable) Release(Releaser, time.Duration, *UseContext) {} +func (nopReleasable) Release(Releaser, *world.Tx, *UseContext, time.Duration) {} func (nopReleasable) Requirements() []Stack { return []Stack{} } diff --git a/server/item/leggings.go b/server/item/leggings.go index 3e7b10123..1b8c8ac62 100644 --- a/server/item/leggings.go +++ b/server/item/leggings.go @@ -13,7 +13,7 @@ type Leggings struct { } // Use handles the auto-equipping of leggings in an armour slot by using the item. -func (l Leggings) Use(_ *world.World, _ User, ctx *UseContext) bool { +func (l Leggings) Use(tx *world.Tx, user User, ctx *UseContext) bool { ctx.SwapHeldWithArmour(2) return false } diff --git a/server/item/lingering_potion.go b/server/item/lingering_potion.go index 3ab6e6b04..0d5026f19 100644 --- a/server/item/lingering_potion.go +++ b/server/item/lingering_potion.go @@ -19,10 +19,10 @@ func (l LingeringPotion) MaxCount() int { } // Use ... -func (l LingeringPotion) Use(w *world.World, user User, ctx *UseContext) bool { - create := w.EntityRegistry().Config().LingeringPotion - w.AddEntity(create(eyePosition(user), user.Rotation().Vec3().Mul(0.5), l.Type, user)) - w.PlaySound(user.Position(), sound.ItemThrow{}) +func (l LingeringPotion) Use(tx *world.Tx, user User, ctx *UseContext) bool { + create := tx.World().EntityRegistry().Config().LingeringPotion + tx.AddEntity(create(eyePosition(user), user.Rotation().Vec3().Mul(0.5), l.Type, user)) + tx.PlaySound(user.Position(), sound.ItemThrow{}) ctx.SubtractFromCount(1) return true diff --git a/server/item/melon_slice.go b/server/item/melon_slice.go index 734c47541..817fe1ac4 100644 --- a/server/item/melon_slice.go +++ b/server/item/melon_slice.go @@ -19,7 +19,7 @@ func (m MelonSlice) ConsumeDuration() time.Duration { } // Consume ... -func (m MelonSlice) Consume(_ *world.World, c Consumer) Stack { +func (m MelonSlice) Consume(tx *world.Tx, c Consumer) Stack { c.Saturate(2, 1.2) return Stack{} } diff --git a/server/item/mushroom_stew.go b/server/item/mushroom_stew.go index bf365e0b5..56354de91 100644 --- a/server/item/mushroom_stew.go +++ b/server/item/mushroom_stew.go @@ -15,7 +15,7 @@ func (MushroomStew) MaxCount() int { } // Consume ... -func (MushroomStew) Consume(_ *world.World, c Consumer) Stack { +func (MushroomStew) Consume(tx *world.Tx, c Consumer) Stack { c.Saturate(6, 7.2) return NewStack(Bowl{}, 1) } diff --git a/server/item/mutton.go b/server/item/mutton.go index b85ab652b..815b0d1db 100644 --- a/server/item/mutton.go +++ b/server/item/mutton.go @@ -11,7 +11,7 @@ type Mutton struct { } // Consume ... -func (m Mutton) Consume(_ *world.World, c Consumer) Stack { +func (m Mutton) Consume(tx *world.Tx, c Consumer) Stack { if m.Cooked { c.Saturate(6, 9.6) } else { diff --git a/server/item/poisonous_potato.go b/server/item/poisonous_potato.go index bfcf7bf19..c921a41a4 100644 --- a/server/item/poisonous_potato.go +++ b/server/item/poisonous_potato.go @@ -13,7 +13,7 @@ type PoisonousPotato struct { } // Consume ... -func (p PoisonousPotato) Consume(_ *world.World, c Consumer) Stack { +func (p PoisonousPotato) Consume(tx *world.Tx, c Consumer) Stack { c.Saturate(2, 1.2) if rand.Float64() < 0.6 { c.AddEffect(effect.New(effect.Poison{}, 1, 5*time.Second)) diff --git a/server/item/porkchop.go b/server/item/porkchop.go index 9f47ad384..d95bd23dd 100644 --- a/server/item/porkchop.go +++ b/server/item/porkchop.go @@ -11,7 +11,7 @@ type Porkchop struct { } // Consume ... -func (p Porkchop) Consume(_ *world.World, c Consumer) Stack { +func (p Porkchop) Consume(tx *world.Tx, c Consumer) Stack { if p.Cooked { c.Saturate(8, 12.8) } else { diff --git a/server/item/potion.go b/server/item/potion.go index 4e8e0e022..7e877c0b6 100644 --- a/server/item/potion.go +++ b/server/item/potion.go @@ -28,7 +28,7 @@ func (p Potion) ConsumeDuration() time.Duration { } // Consume ... -func (p Potion) Consume(_ *world.World, c Consumer) Stack { +func (p Potion) Consume(tx *world.Tx, c Consumer) Stack { for _, effect := range p.Type.Effects() { c.AddEffect(effect) } diff --git a/server/item/pufferfish.go b/server/item/pufferfish.go index 1b3ded380..292e6be89 100644 --- a/server/item/pufferfish.go +++ b/server/item/pufferfish.go @@ -12,7 +12,7 @@ type Pufferfish struct { } // Consume ... -func (p Pufferfish) Consume(_ *world.World, c Consumer) Stack { +func (p Pufferfish) Consume(tx *world.Tx, c Consumer) Stack { c.Saturate(1, 0.2) c.AddEffect(effect.New(effect.Hunger{}, 3, 15*time.Second)) c.AddEffect(effect.New(effect.Poison{}, 2, time.Minute)) diff --git a/server/item/pumpkin_pie.go b/server/item/pumpkin_pie.go index 4a2cfe0dc..f5bc82ab6 100644 --- a/server/item/pumpkin_pie.go +++ b/server/item/pumpkin_pie.go @@ -8,7 +8,7 @@ type PumpkinPie struct { } // Consume ... -func (PumpkinPie) Consume(_ *world.World, c Consumer) Stack { +func (PumpkinPie) Consume(tx *world.Tx, c Consumer) Stack { c.Saturate(8, 4.8) return Stack{} } diff --git a/server/item/rabbit.go b/server/item/rabbit.go index 0d45044c7..2d25f6430 100644 --- a/server/item/rabbit.go +++ b/server/item/rabbit.go @@ -11,7 +11,7 @@ type Rabbit struct { } // Consume ... -func (r Rabbit) Consume(_ *world.World, c Consumer) Stack { +func (r Rabbit) Consume(tx *world.Tx, c Consumer) Stack { if r.Cooked { c.Saturate(5, 6) } else { diff --git a/server/item/rabbit_stew.go b/server/item/rabbit_stew.go index 599e01a60..800a83753 100644 --- a/server/item/rabbit_stew.go +++ b/server/item/rabbit_stew.go @@ -15,7 +15,7 @@ func (RabbitStew) MaxCount() int { } // Consume ... -func (RabbitStew) Consume(_ *world.World, c Consumer) Stack { +func (RabbitStew) Consume(tx *world.Tx, c Consumer) Stack { c.Saturate(10, 12) return NewStack(Bowl{}, 1) } diff --git a/server/item/rotten_flesh.go b/server/item/rotten_flesh.go index 1a52dff8c..0ffd51cfd 100644 --- a/server/item/rotten_flesh.go +++ b/server/item/rotten_flesh.go @@ -13,7 +13,7 @@ type RottenFlesh struct { } // Consume ... -func (RottenFlesh) Consume(_ *world.World, c Consumer) Stack { +func (RottenFlesh) Consume(tx *world.Tx, c Consumer) Stack { c.Saturate(4, 0.8) if rand.Float64() < 0.8 { c.AddEffect(effect.New(effect.Hunger{}, 1, 30*time.Second)) diff --git a/server/item/salmon.go b/server/item/salmon.go index 56717b51d..216c28e3d 100644 --- a/server/item/salmon.go +++ b/server/item/salmon.go @@ -11,7 +11,7 @@ type Salmon struct { } // Consume ... -func (s Salmon) Consume(_ *world.World, c Consumer) Stack { +func (s Salmon) Consume(tx *world.Tx, c Consumer) Stack { if s.Cooked { c.Saturate(6, 9.6) } else { diff --git a/server/item/shears.go b/server/item/shears.go index 0c8b1f2f4..583c97f44 100644 --- a/server/item/shears.go +++ b/server/item/shears.go @@ -10,15 +10,15 @@ import ( type Shears struct{} // UseOnBlock ... -func (s Shears) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, _ User, ctx *UseContext) bool { +func (s Shears) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user User, ctx *UseContext) bool { if face == cube.FaceUp || face == cube.FaceDown { // Pumpkins can only be carved when one of the horizontal faces is clicked. return false } - if c, ok := w.Block(pos).(carvable); ok { + if c, ok := tx.Block(pos).(carvable); ok { if res, ok := c.Carve(face); ok { // TODO: Drop pumpkin seeds. - w.SetBlock(pos, res, nil) + tx.SetBlock(pos, res, nil) ctx.DamageItem(1) return true diff --git a/server/item/shovel.go b/server/item/shovel.go index b453c9bd4..cbcebe4a2 100644 --- a/server/item/shovel.go +++ b/server/item/shovel.go @@ -16,19 +16,19 @@ type Shovel struct { } // UseOnBlock handles the creation of dirt path blocks from dirt or grass blocks. -func (s Shovel) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, _ User, ctx *UseContext) bool { - if b, ok := w.Block(pos).(shovellable); ok { +func (s Shovel) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user User, ctx *UseContext) bool { + if b, ok := tx.Block(pos).(shovellable); ok { if res, ok := b.Shovel(); ok { if face == cube.FaceDown { // Dirt paths are not created when the bottom face is clicked. return false } - if w.Block(pos.Side(cube.FaceUp)) != air() { + if tx.Block(pos.Side(cube.FaceUp)) != air() { // Dirt paths can only be created if air is above the grass block. return false } - w.SetBlock(pos, res, nil) - w.PlaySound(pos.Vec3(), sound.ItemUseOn{Block: res}) + tx.SetBlock(pos, res, nil) + tx.PlaySound(pos.Vec3(), sound.ItemUseOn{Block: res}) ctx.DamageItem(1) return true diff --git a/server/item/snowball.go b/server/item/snowball.go index 6bd80933c..6a3e55d6b 100644 --- a/server/item/snowball.go +++ b/server/item/snowball.go @@ -14,10 +14,10 @@ func (s Snowball) MaxCount() int { } // Use ... -func (s Snowball) Use(w *world.World, user User, ctx *UseContext) bool { - create := w.EntityRegistry().Config().Snowball - w.AddEntity(create(eyePosition(user), user.Rotation().Vec3().Mul(1.5), user)) - w.PlaySound(user.Position(), sound.ItemThrow{}) +func (s Snowball) Use(tx *world.Tx, user User, ctx *UseContext) bool { + create := tx.World().EntityRegistry().Config().Snowball + tx.AddEntity(create(eyePosition(user), user.Rotation().Vec3().Mul(1.5), user)) + tx.PlaySound(user.Position(), sound.ItemThrow{}) ctx.SubtractFromCount(1) return true diff --git a/server/item/spider_eye.go b/server/item/spider_eye.go index 101434e6d..d64259bac 100644 --- a/server/item/spider_eye.go +++ b/server/item/spider_eye.go @@ -12,7 +12,7 @@ type SpiderEye struct { } // Consume ... -func (SpiderEye) Consume(_ *world.World, c Consumer) Stack { +func (SpiderEye) Consume(tx *world.Tx, c Consumer) Stack { c.Saturate(2, 3.2) c.AddEffect(effect.New(effect.Poison{}, 1, time.Second*5)) return Stack{} diff --git a/server/item/splash_potion.go b/server/item/splash_potion.go index d8d4a216e..87a4a0895 100644 --- a/server/item/splash_potion.go +++ b/server/item/splash_potion.go @@ -18,10 +18,10 @@ func (s SplashPotion) MaxCount() int { } // Use ... -func (s SplashPotion) Use(w *world.World, user User, ctx *UseContext) bool { - create := w.EntityRegistry().Config().SplashPotion - w.AddEntity(create(eyePosition(user), user.Rotation().Vec3().Mul(0.5), s.Type, user)) - w.PlaySound(user.Position(), sound.ItemThrow{}) +func (s SplashPotion) Use(tx *world.Tx, user User, ctx *UseContext) bool { + create := tx.World().EntityRegistry().Config().SplashPotion + tx.AddEntity(create(eyePosition(user), user.Rotation().Vec3().Mul(0.5), s.Type, user)) + tx.PlaySound(user.Position(), sound.ItemThrow{}) ctx.SubtractFromCount(1) return true diff --git a/server/item/suspicious_stew.go b/server/item/suspicious_stew.go index c4000a7f2..d76d9cb19 100644 --- a/server/item/suspicious_stew.go +++ b/server/item/suspicious_stew.go @@ -28,7 +28,7 @@ func (s SuspiciousStew) EncodeItem() (name string, meta int16) { } // Consume ... -func (s SuspiciousStew) Consume(_ *world.World, c Consumer) Stack { +func (s SuspiciousStew) Consume(tx *world.Tx, c Consumer) Stack { for _, effect := range s.Type.Effects() { c.AddEffect(effect) } diff --git a/server/item/tropical_fish.go b/server/item/tropical_fish.go index 1c633bbec..cf7d3759d 100644 --- a/server/item/tropical_fish.go +++ b/server/item/tropical_fish.go @@ -8,7 +8,7 @@ type TropicalFish struct { } // Consume ... -func (TropicalFish) Consume(_ *world.World, c Consumer) Stack { +func (TropicalFish) Consume(tx *world.Tx, c Consumer) Stack { c.Saturate(1, 0.2) return Stack{} } diff --git a/server/item/turtle_shell.go b/server/item/turtle_shell.go index 5dc6fe102..4aacdaf4d 100644 --- a/server/item/turtle_shell.go +++ b/server/item/turtle_shell.go @@ -7,7 +7,7 @@ import "github.com/df-mc/dragonfly/server/world" type TurtleShell struct{} // Use handles the using of a turtle shell to auto-equip it in an armour slot. -func (TurtleShell) Use(_ *world.World, _ User, ctx *UseContext) bool { +func (TurtleShell) Use(tx *world.Tx, user User, ctx *UseContext) bool { ctx.SwapHeldWithArmour(0) return false } diff --git a/server/player/player.go b/server/player/player.go index cf8a71a8b..9a26431a3 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -1309,7 +1309,7 @@ func (p *Player) ReleaseItem() { p.usingItem = false ctx := p.useContext() i, _ := p.HeldItems() - i.Item().(item.Releasable).Release(p, p.useDuration(), ctx) + i.Item().(item.Releasable).Release(p, p.useDuration(), nil, ctx) p.handleUseContext(ctx) p.updateState() diff --git a/server/session/session.go b/server/session/session.go index dd6b015b5..e8bf8f115 100644 --- a/server/session/session.go +++ b/server/session/session.go @@ -197,6 +197,14 @@ func (s *Session) Spawn(c Controllable, tx *world.Tx) { s.entityRuntimeIDs[ent] = selfEntityRuntimeID s.entities[selfEntityRuntimeID] = ent + pos := c.Position() + s.chunkLoader = world.NewLoader(int(s.chunkRadius), tx, s) + s.chunkLoader.Move(pos) + s.writePacket(&packet.NetworkChunkPublisherUpdate{ + Position: protocol.BlockPos{int32(pos[0]), int32(pos[1]), int32(pos[2])}, + Radius: uint32(s.chunkRadius) << 4, + }) + s.initPlayerList() c.SetGameMode(c.GameMode()) diff --git a/server/world/block.go b/server/world/block.go index cfec471ea..02a865dc8 100644 --- a/server/world/block.go +++ b/server/world/block.go @@ -42,7 +42,7 @@ type CustomBlockBuildable interface { CustomBlock // Name is the name displayed to clients using the block. Name() string - // Geometries is the geometries for the block that define the shape of the block. If false is returned, no custom + // Geometry is the geometries for the block that define the shape of the block. If false is returned, no custom // geometry will be applied. Permutation-specific geometry can be defined by returning a map of permutations to // geometry. Geometry() []byte @@ -72,7 +72,7 @@ type Liquid interface { LiquidType() string // Harden checks if the block should harden when looking at the surrounding blocks and sets the position // to the hardened block when adequate. If the block was hardened, the method returns true. - Harden(pos cube.Pos, w *World, flownIntoBy *cube.Pos) bool + Harden(pos cube.Pos, tx *Tx, flownIntoBy *cube.Pos) bool } // hashes holds a list of runtime IDs indexed by the hash of the Block that implements the blocks pointed to by those @@ -238,7 +238,7 @@ func air() Block { type RandomTicker interface { // RandomTick handles a random tick of the block at the position passed. Additionally, a rand.Rand // instance is passed which may be used to generate values randomly without locking. - RandomTick(pos cube.Pos, w *World, r *rand.Rand) + RandomTick(pos cube.Pos, tx *Tx, r *rand.Rand) } // ScheduledTicker represents a block that executes an action when it has a block update scheduled, such as @@ -247,14 +247,14 @@ type ScheduledTicker interface { // ScheduledTick handles a scheduled tick initiated by an event in one of the neighbouring blocks, such as // when a block is placed or broken. Additionally, a rand.Rand instance is passed which may be used to // generate values randomly without locking. - ScheduledTick(pos cube.Pos, w *World, r *rand.Rand) + ScheduledTick(pos cube.Pos, tx *Tx, r *rand.Rand) } // TickerBlock is an implementation of NBTer with an additional Tick method that is called on every world // tick for loaded blocks that implement this interface. type TickerBlock interface { NBTer - Tick(currentTick int64, pos cube.Pos, w *World) + Tick(currentTick int64, pos cube.Pos, tx *Tx) } // NeighbourUpdateTicker represents a block that is updated when a block adjacent to it is updated, either @@ -262,7 +262,7 @@ type TickerBlock interface { type NeighbourUpdateTicker interface { // NeighbourUpdateTick handles a neighbouring block being updated. The position of that block and the // position of this block is passed. - NeighbourUpdateTick(pos, changedNeighbour cube.Pos, w *World) + NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *Tx) } // NBTer represents either an item or a block which may decode NBT data and encode to NBT data. Typically, @@ -283,7 +283,7 @@ type LiquidDisplacer interface { // SideClosed checks if a position on the side of the block placed in the world at a specific position is // closed. When this returns true (for example, when the side is below the position and the block is a // slab), liquid inside the displacer won't flow from pos into side. - SideClosed(pos, side cube.Pos, w *World) bool + SideClosed(pos, side cube.Pos, tx *Tx) bool } // lightEmitter is identical to a block.LightEmitter. diff --git a/server/world/conf.go b/server/world/conf.go index 6fda50149..1bf2d2e59 100644 --- a/server/world/conf.go +++ b/server/world/conf.go @@ -75,11 +75,11 @@ func (conf Config) New() *World { ra: conf.Dim.Range(), set: s, } - w.weather, w.ticker = weather{w: w}, ticker{w: w} + w.weather, w.ticker = weather{w: w}, ticker{} var h Handler = NopHandler{} w.handler.Store(&h) - go w.tickLoop() // TODO: Put this in a transaction. + go w.tickLoop(w) // TODO: Put this in a transaction. go w.chunkCacheJanitor() // TODO: Put this in a transaction. go w.handleTransactions() diff --git a/server/world/entity.go b/server/world/entity.go index 344debf0e..08e74d81a 100644 --- a/server/world/entity.go +++ b/server/world/entity.go @@ -112,7 +112,7 @@ type SaveableEntityType interface { type TickerEntity interface { Entity // Tick ticks the entity with the current World and tick passed. - Tick(w *World, current int64) + Tick(tx *Tx, current int64) } // EntityAction represents an action that may be performed by an entity. Typically, these actions are sent to diff --git a/server/world/loader.go b/server/world/loader.go index ae47ae9ee..83d2db778 100644 --- a/server/world/loader.go +++ b/server/world/loader.go @@ -55,7 +55,7 @@ func (l *Loader) ChangeRadius(new int) { defer l.mu.Unlock() l.r = new - l.evictUnused() + l.w.Exec(l.evictUnused) l.populateLoadQueue() } @@ -69,7 +69,7 @@ func (l *Loader) Move(pos mgl64.Vec3) { return } l.pos = chunkPos - l.evictUnused() + l.w.Exec(l.evictUnused) l.populateLoadQueue() } @@ -83,23 +83,26 @@ func (l *Loader) Load(n int) { if l.closed || l.w == nil { return } - for i := 0; i < n; i++ { - if len(l.loadQueue) == 0 { - break - } - pos := l.loadQueue[0] - c := l.w.chunk(pos) + l.w.Exec(func(tx *Tx) { + for i := 0; i < n; i++ { + if len(l.loadQueue) == 0 { + break + } + + pos := l.loadQueue[0] + c := l.w.chunk(pos) - l.viewer.ViewChunk(pos, c.Chunk, c.BlockEntities) - l.w.addViewer(c, l) + l.viewer.ViewChunk(pos, c.Chunk, c.BlockEntities) + l.w.addViewer(tx, c, l) - l.loaded[pos] = c + l.loaded[pos] = c - // Shift the first element from the load queue off so that we can take a new one during the next - // iteration. - l.loadQueue = l.loadQueue[1:] - } + // Shift the first element from the load queue off so that we can take a new one during the next + // iteration. + l.loadQueue = l.loadQueue[1:] + } + }) } // Chunk attempts to return a chunk at the given ChunkPos. If the chunk is not loaded, the second return value will @@ -135,9 +138,11 @@ func (l *Loader) Reset() { // reset clears the Loader so that it may be used as if it was created again with NewLoader. func (l *Loader) reset() { - for pos := range l.loaded { - l.w.removeViewer(pos, l) - } + l.w.Exec(func(tx *Tx) { + for pos := range l.loaded { + l.w.removeViewer(tx, pos, l) + } + }) l.loaded = map[ChunkPos]*Column{} delete(l.w.viewers, l) } @@ -152,13 +157,13 @@ func (l *Loader) world(new *World) { // evictUnused gets rid of chunks in the loaded map which are no longer within the chunk radius of the loader, // and should therefore be removed. -func (l *Loader) evictUnused() { +func (l *Loader) evictUnused(tx *Tx) { for pos := range l.loaded { diffX, diffZ := pos[0]-l.pos[0], pos[1]-l.pos[1] dist := math.Sqrt(float64(diffX*diffX) + float64(diffZ*diffZ)) if int(dist) > l.r { delete(l.loaded, pos) - l.w.removeViewer(pos, l) + l.w.removeViewer(tx, pos, l) } } } diff --git a/server/world/tick.go b/server/world/tick.go index 7896b6776..2feaebfe4 100644 --- a/server/world/tick.go +++ b/server/world/tick.go @@ -5,7 +5,6 @@ import ( "github.com/df-mc/dragonfly/server/internal/sliceutil" "golang.org/x/exp/maps" "math/rand" - "slices" "time" ) @@ -65,7 +64,7 @@ func (t ticker) tick(tx *Tx) { } } if thunder { - tx.World().tickLightning() + tx.World().tickLightning(tx) } t.tickEntities(tx, tick) @@ -75,22 +74,22 @@ func (t ticker) tick(tx *Tx) { } // tickScheduledBlocks executes scheduled block updates in chunks that are currently loaded. -func (t ticker) tickScheduledBlocks(tick int64) { - positions := make([]cube.Pos, 0, len(tx.scheduledUpdates)/4) - for pos, scheduledTick := range tx.scheduledUpdates { +func (t ticker) tickScheduledBlocks(tx *Tx, tick int64) { + positions := make([]cube.Pos, 0, len(tx.World().scheduledUpdates)/4) + for pos, scheduledTick := range tx.World().scheduledUpdates { if scheduledTick <= tick { positions = append(positions, pos) - delete(tx.scheduledUpdates, pos) + delete(tx.World().scheduledUpdates, pos) } } for _, pos := range positions { if ticker, ok := tx.Block(pos).(ScheduledTicker); ok { - ticker.ScheduledTick(pos, t.w, tx.r) + ticker.ScheduledTick(pos, tx, tx.World().r) } - if liquid, ok := tx.additionalLiquid(pos); ok { + if liquid, ok := tx.World().additionalLiquid(pos); ok { if ticker, ok := liquid.(ScheduledTicker); ok { - ticker.ScheduledTick(pos, t.w, tx.r) + ticker.ScheduledTick(pos, tx, tx.World().r) } } } @@ -98,9 +97,6 @@ func (t ticker) tickScheduledBlocks(tick int64) { // performNeighbourUpdates performs all block updates that came as a result of a neighbouring block being changed. func (t ticker) performNeighbourUpdates(tx *Tx) { - positions := slices.Clone(tx.World().neighbourUpdates) - tx.World().neighbourUpdates = tx.World().neighbourUpdates[:0] - for _, update := range tx.World().neighbourUpdates { pos, changedNeighbour := update.pos, update.neighbour if ticker, ok := tx.Block(pos).(NeighbourUpdateTicker); ok { @@ -112,6 +108,8 @@ func (t ticker) performNeighbourUpdates(tx *Tx) { } } } + clear(tx.World().neighbourUpdates) + tx.World().neighbourUpdates = tx.World().neighbourUpdates[:0] } // tickBlocksRandomly executes random block ticks in each sub chunk in the world that has at least one viewer @@ -164,7 +162,7 @@ func (t ticker) tickBlocksRandomly(tx *Tx, loaders []*Loader, tick int64) { // Only generate new coordinates if a tickable block was actually found. If not, we can just re-use // the coordinates for the next sub chunk. - x, y, z = g.uint4(tx.r), g.uint4(tx.r), g.uint4(tx.r) + x, y, z = g.uint4(tx.World().r), g.uint4(tx.World().r), g.uint4(tx.World().r) } } } @@ -172,7 +170,7 @@ func (t ticker) tickBlocksRandomly(tx *Tx, loaders []*Loader, tick int64) { for _, pos := range randomBlocks { if rb, ok := tx.Block(pos).(RandomTicker); ok { - rb.RandomTick(pos, tx, tx.r) + rb.RandomTick(pos, tx, tx.World().r) } } for _, pos := range blockEntities { @@ -219,7 +217,7 @@ func (t ticker) tickEntities(tx *Tx, tick int64) { // where the old chunk of the entity was not loaded. In this case, it should be safe simply to ignore // the loaders from the old chunk. We can assume they never saw the entity in the first place. if old, ok := tx.World().chunks[lastPos]; ok { - old.Entities = sliceutil.DeleteVal(old.Entities, e) + old.Entities = sliceutil.DeleteVal(old.Entities, handle) viewers = old.viewers } diff --git a/server/world/weather.go b/server/world/weather.go index c9ce899fc..d2fc18c0c 100644 --- a/server/world/weather.go +++ b/server/world/weather.go @@ -155,7 +155,7 @@ func (w weather) enableWeatherCycle(v bool) { } // tickLightning iterates over all loaded chunks in the World, striking lightning in each one with a 1/100,000 chance. -func (w weather) tickLightning() { +func (w weather) tickLightning(tx *Tx) { positions := make([]ChunkPos, 0, len(w.w.chunks)/100000) for pos := range w.w.chunks { // Wiki: For each loaded chunk, every tick there is a 1⁄100,000 chance of an attempted lightning strike @@ -166,27 +166,27 @@ func (w weather) tickLightning() { } for _, pos := range positions { - w.w.strikeLightning(pos) + w.w.strikeLightning(tx, pos) } } // strikeLightning attempts to strike lightning in the world at a specific ChunkPos. The final position is influenced by // living entities that might be near the lightning strike. If there is no rain at the final position selected, the // lightning strike will fail. -func (w weather) strikeLightning(c ChunkPos) { - if pos := w.lightningPosition(c); w.ThunderingAt(cube.PosFromVec3(pos)) { - w.w.AddEntity(w.w.conf.Entities.conf.Lightning(pos)) +func (w weather) strikeLightning(tx *Tx, c ChunkPos) { + if pos := w.lightningPosition(tx, c); tx.ThunderingAt(cube.PosFromVec3(pos)) { + tx.AddEntity(w.w.conf.Entities.conf.Lightning(pos)) } } // lightningPosition finds a random position in the ChunkPos to strike lightning and adjusts the position to any of the // living entities found in or above the position if any are found. -func (w weather) lightningPosition(c ChunkPos) mgl64.Vec3 { +func (w weather) lightningPosition(tx *Tx, c ChunkPos) mgl64.Vec3 { v := w.w.r.Int31() x, z := float64(c[0]<<4+(v&0xf)), float64(c[1]<<4+((v>>8)&0xf)) - vec := w.adjustPositionToEntities(mgl64.Vec3{x, float64(w.w.HighestBlock(int(x), int(z)) + 1), z}) - if pos := cube.PosFromVec3(vec); len(w.w.Block(pos).Model().BBox(pos, w.w)) != 0 { + vec := w.adjustPositionToEntities(tx, mgl64.Vec3{x, float64(tx.HighestBlock(int(x), int(z)) + 1), z}) + if pos := cube.PosFromVec3(vec); len(tx.Block(pos).Model().BBox(pos, tx)) != 0 { // If lightning is about to strike inside a block that is not fully transparent. In this case, move the // lightning up by one block so that it strikes above the block. return vec.Add(mgl64.Vec3{0, 1}) @@ -196,9 +196,9 @@ func (w weather) lightningPosition(c ChunkPos) mgl64.Vec3 { // adjustPositionToEntities adjusts the mgl64.Vec3 passed to the position of any entity found in the 3x3 column upwards // from the mgl64.Vec3. If multiple entities are found, the position of one of the entities is selected randomly. -func (w weather) adjustPositionToEntities(vec mgl64.Vec3) mgl64.Vec3 { +func (w weather) adjustPositionToEntities(tx *Tx, vec mgl64.Vec3) mgl64.Vec3 { max := vec.Add(mgl64.Vec3{0, float64(w.w.Range().Max())}) - ent := w.w.entitiesWithin(cube.Box(vec[0], vec[1], vec[2], max[0], max[1], max[2]).GrowVec3(mgl64.Vec3{3, 3, 3}), nil) + ent := tx.EntitiesWithin(cube.Box(vec[0], vec[1], vec[2], max[0], max[1], max[2]).GrowVec3(mgl64.Vec3{3, 3, 3}), nil) list := make([]mgl64.Vec3, 0, len(ent)/3) for _, e := range ent { @@ -206,7 +206,7 @@ func (w weather) adjustPositionToEntities(vec mgl64.Vec3) mgl64.Vec3 { // Any (living) entity that is positioned higher than the highest block at its position is eligible to be // struck by lightning. We first save all entity positions where this is the case. pos := cube.PosFromVec3(e.Position()) - if w.w.highestBlock(pos[0], pos[1]) < pos[2] { + if tx.HighestBlock(pos[0], pos[1]) < pos[2] { list = append(list, e.Position()) } } diff --git a/server/world/world.go b/server/world/world.go index 68011efa4..66a9923e4 100644 --- a/server/world/world.go +++ b/server/world/world.go @@ -142,7 +142,7 @@ func (w *World) EntityRegistry() EntityRegistry { // loaded, or generated if it could not be found in the world save, and the block returned. Chunks will be // loaded synchronously. func (w *World) block(pos cube.Pos) Block { - if w == nil || pos.OutOfBounds(w.Range()) { + if pos.OutOfBounds(w.Range()) { // Fast way out. return air() } @@ -152,20 +152,16 @@ func (w *World) block(pos cube.Pos) Block { if nbtBlocks[rid] { // The block was also a block entity, so we look it up in the block entity map. if nbtB, ok := c.BlockEntities[pos]; ok { - c.Unlock() return nbtB } b, _ := BlockByRuntimeID(rid) nbtB := b.(NBTer).DecodeNBT(map[string]any{}).(Block) c.BlockEntities[pos] = nbtB - viewers := slices.Clone(c.viewers) - c.Unlock() - for _, v := range viewers { + for _, v := range c.viewers { v.ViewBlockUpdate(pos, nbtB, 0) } return nbtB } - c.Unlock() b, _ := BlockByRuntimeID(rid) return b } @@ -223,7 +219,8 @@ func (w *World) highestObstructingBlock(x, z int) int { for y := yHigh; y >= w.Range()[0]; y-- { pos := cube.Pos{x, y, z} m := w.block(pos).Model() - if m.FaceSolid(pos, cube.FaceUp, w) || m.FaceSolid(pos, cube.FaceDown, w) { + // TODO: Work out how to pass a proper BlockSource here. + if m.FaceSolid(pos, cube.FaceUp, nil) || m.FaceSolid(pos, cube.FaceDown, nil) { return y } } @@ -952,9 +949,13 @@ func (w *World) PortalDestination(dim Dimension) *World { // Save saves the World to the provider. func (w *World) Save() { + w.Exec(w.save) +} + +func (w *World) save(tx *Tx) { w.conf.Log.Debug("Saving chunks in memory to disk...") for pos, c := range w.chunks { - w.saveChunk(pos, c, false) + w.saveChunk(tx, pos, c, false) } } @@ -1019,7 +1020,7 @@ func (w *World) addWorldViewer(l *Loader) { // addViewer adds a viewer to the world at a given position. Any events that happen in the chunk at that // position, such as block changes, entity changes etc., will be sent to the viewer. -func (w *World) addViewer(c *Column, loader *Loader) { +func (w *World) addViewer(tx *Tx, c *Column, loader *Loader) { if w == nil { return } @@ -1027,13 +1028,13 @@ func (w *World) addViewer(c *Column, loader *Loader) { c.loaders = append(c.loaders, loader) for _, entity := range c.Entities { - showEntity(entity, loader.viewer) + showEntity(entity.Entity(tx), loader.viewer) } } // removeViewer removes a viewer from the world at a given position. All entities will be hidden from the // viewer and no more calls will be made when events in the chunk happen. -func (w *World) removeViewer(pos ChunkPos, loader *Loader) { +func (w *World) removeViewer(tx *Tx, pos ChunkPos, loader *Loader) { if w == nil { return } @@ -1048,7 +1049,7 @@ func (w *World) removeViewer(pos ChunkPos, loader *Loader) { // After removing the loader from the chunk, we also need to hide all entities from the viewer. for _, entity := range c.Entities { - loader.viewer.HideEntity(entity) + loader.viewer.HideEntity(entity.Entity(tx)) } } @@ -1172,7 +1173,7 @@ func (w *World) spreadLight(pos ChunkPos) { // saveChunk is called when a chunk is removed from the cache. We first compact the chunk, then we write it to // the provider. -func (w *World) saveChunk(pos ChunkPos, c *Column, closeEntities bool) { +func (w *World) saveChunk(tx *Tx, pos ChunkPos, c *Column, closeEntities bool) { if !w.conf.ReadOnly && c.modified { c.Compact() if err := w.provider().StoreColumn(pos, w.conf.Dim, c); err != nil { @@ -1181,7 +1182,7 @@ func (w *World) saveChunk(pos ChunkPos, c *Column, closeEntities bool) { } if closeEntities { for _, e := range c.Entities { - _ = e.Close() + _ = e.Entity(tx).Close() } clear(c.Entities) } @@ -1193,24 +1194,10 @@ func (w *World) chunkCacheJanitor() { defer t.Stop() w.running.Add(1) - chunksToRemove := map[ChunkPos]*Column{} for { select { case <-t.C: - for pos, c := range w.chunks { - v := len(c.viewers) - if v == 0 { - chunksToRemove[pos] = c - delete(w.chunks, pos) - if w.lastPos == pos { - w.lastChunk = nil - } - } - } - for pos, c := range chunksToRemove { - w.saveChunk(pos, c, true) - delete(chunksToRemove, pos) - } + w.Exec(w.saveUnusedChunks) case <-w.closing: w.running.Done() return @@ -1218,6 +1205,15 @@ func (w *World) chunkCacheJanitor() { } } +func (w *World) saveUnusedChunks(tx *Tx) { + for pos, c := range w.chunks { + if len(c.viewers) == 0 { + delete(w.chunks, pos) + w.saveChunk(tx, pos, c, true) + } + } +} + // Column represents the data of a chunk including the block entities and loaders. This data is protected // by the mutex present in the chunk.Chunk held. type Column struct {