Skip to content

Feat/crystals+anchors#31

Open
HashimTheArab wants to merge 16 commits into
HashimTheArab:masterfrom
cqdetdev:feat/crystals+anchors
Open

Feat/crystals+anchors#31
HashimTheArab wants to merge 16 commits into
HashimTheArab:masterfrom
cqdetdev:feat/crystals+anchors

Conversation

@HashimTheArab

@HashimTheArab HashimTheArab commented May 24, 2026

Copy link
Copy Markdown
Owner

Note

Medium Risk
Touches player respawn persistence, cross-dimension world resolution, and explosion/entity damage paths—important gameplay and data migration surface, though changes are localized to spawn and combat helpers.

Overview
Adds respawn anchors (glowstone charging, Nether spawn, safe spawn with charge depletion, overworld misuse explosion) and End crystals (placeable item, entity with beam/base metadata, End fire, vanilla-style explosion).

Player respawn is now dimension-aware: PlayerSpawn / provider persistence store position and dimension (SpawnDimension in LevelDB); beds and anchors only update spawn when dimension matches; respawn resolves the correct world via optional WorldByDimension on players (wired from the server).

Explosion gains End-crystal block clipping on obsidian/bedrock, optional entity-damage skip, and exposure divide-by-zero guard. Combat/projectiles use shared HurtEntity / damageableEntity so non-Living behaviours (e.g. End crystals) can be hit; melee attacks non-living damageables too.

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

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Respawn Anchor block for setting spawn points in the Nether dimension (charge with glowstone)
    • Added End Crystal block for placement in the End dimension
    • Added Piercing enchantment for crossbows (levels 1–4) enabling projectiles to pierce multiple entities
    • Furnaces, blast furnaces, and smokers now emit light when actively burning
  • Balance Changes

    • Multishot and Piercing enchantments are now mutually incompatible
  • Bug Fixes

    • Golden apple absorption health is now properly capped

Review Change Stack

cqdetdev and others added 14 commits May 9, 2026 13:55
- Implement end crystals as behavior
- Add EndCrystal to explosion config
Constraint: PR df-mc#1247 had merge conflicts against upstream/master in block registration, projectile behavior, and sound declarations.
Rejected: Dropping upstream redstone/piercing changes | conflict resolution must preserve current base behavior.
Confidence: high
Scope-risk: moderate
Directive: Keep projectile piercing collision tracking and non-living damageable entities in sync when editing ProjectileBehaviour.
Tested: go test ./...; git diff --check
Not-tested: In-game Bedrock client interaction.
End crystal placement and damage behavior needed source-backed parity before PR df-mc#1247 could be reviewed cleanly. This keeps the fixes isolated from the upstream merge already pushed to the contributor branch.

Constraint: Primary source guidance came from ../AGENTS.md: Minecraft Wiki and mcsrc.dev first, with Dragonfly conventions preserved where code patterns already existed.
Rejected: Keeping duplicate non-living damage helpers | player and projectile paths both need the same Behaviour-backed damage semantics.
Confidence: high
Scope-risk: moderate
Directive: Keep End crystal zero-damage, explosion-damage, placement-fire, and continuous-End-fire behavior covered when changing entity or item placement paths.
Tested: go test ./...
Not-tested: Live Bedrock client interaction; dedicated regression tests per requester direction.
…fixes

Fix source-backed crystal and anchor parity issues
Constraint: PR df-mc#1247 had already merged before this review cleanup, so this follow-up keeps the change limited to the offset declaration.
Rejected: Keeping generated offset initialization | the explicit priority table is clearer and avoids an init-time helper for fixed vanilla order.
Confidence: high
Scope-risk: narrow
Directive: Preserve the column layer-1/layer-2 priority before layer 3 when editing respawn anchor spawn offsets.
Tested: go test ./...; git diff --check
Not-tested: Live Bedrock client respawn flow.
Constraint: PR df-mc#1247 follow-up review requested documentation for helper tables and questioned whether a missing End crystal beam target should serialize as 0,0,0.
Rejected: Sending a zero BlockTarget for crystals without a beam target | zero is a concrete position and can be interpreted as a beam target.
Confidence: high
Scope-risk: narrow
Directive: Omit EntityDataKeyBlockTarget unless an End crystal has an explicit beam target.
Tested: go test ./...; git diff --check
Not-tested: Live Bedrock client metadata rendering.
Constraint: PR df-mc#1247 follow-up review requested documentation for helper tables and questioned whether a missing End crystal beam target should serialize as 0,0,0.
Rejected: Sending a zero BlockTarget for crystals without a beam target | zero is a concrete position and can be interpreted as a beam target.
Confidence: high
Scope-risk: narrow
Directive: Omit EntityDataKeyBlockTarget unless an End crystal has an explicit beam target.
Tested: go test ./...; git diff --check
Not-tested: Live Bedrock client metadata rendering.
Constraint: Follow-up PR review requested clarity on HurtEntity return values without changing behavior.
Rejected: Adding explicit ProjectileDamageSource ExplodesEndCrystal true | End crystal explosion behavior is opt-out by default, so explicit true methods add noise.
Confidence: high
Scope-risk: narrow
Directive: Keep HurtEntity return tuple semantics aligned with Living.Hurt plus the damageable ok flag.
Tested: go test ./...; git diff --check
Not-tested: Live Bedrock client interaction.
Constraint: Respawn anchors and beds share the same safe-spawn contract in player respawn logic.
Rejected: Keeping the anonymous inline interface | It obscures the reusable local contract and makes the respawn path harder to scan.
Confidence: high
Scope-risk: narrow
Directive: Keep this interface private unless a non-player package needs to refer to the contract directly.
Tested: go test ./...; git diff --check
Not-tested: Live Bedrock client respawn interaction.
Constraint: Bedrock lang sources keep tile.bed.notValid scoped to bed wording while respawn anchors have their own notValid key.
Rejected: Java-style combined bed and anchor fallback | It does not match Dragonfly's original fallback or current Bedrock sample text.
Confidence: high
Scope-risk: narrow
Directive: Keep chat translation fallbacks aligned with Bedrock language keys, not wiki prose when they differ.
Tested: go test ./...; git diff --check
Not-tested: Live Bedrock client locale rendering.
@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds End Crystals and Respawn Anchors as complete feature implementations, introduces Piercing enchantment with arrow configuration consolidation, refactors projectile piercing behavior, and provides supporting improvements including light emission levels, explosion Y-level clipping, absorption mechanics, and block registry optimizations.

Changes

End Crystal Entity and Item

Layer / File(s) Summary
End Crystal Entity Type and Registration
server/entity/end_crystal.go, server/entity/register.go
EndCrystalType implements world.EntityType with bounding box definition, open/entity ID methods, and NBT serialization for ShowBottom and optional beam target coordinates; NewEndCrystal spawner registers the entity in DefaultRegistry.
End Crystal Behaviour and Damage Handling
server/entity/end_crystal_behaviour.go, server/entity/damage.go, server/session/entity_metadata.go
endCrystalBehaviour places fire, handles damage by closing on void/explosion sources, triggers explosions with Y-level clipping, exposes beam state via ShowBase/BeamTarget; metadata encoding includes visibility flag and beam target position.
End Crystal Item and Placement
server/item/end_crystal.go, server/item/register.go
EndCrystal item validates placement on obsidian/bedrock with replaceable space above, checks for intersecting entities, schedules fire block updates, spawns the entity, and registers as a world item.

Respawn Anchor Block and Integration

Layer / File(s) Summary
Respawn Anchor Block Definition and Registry
server/block/respawn_anchor.go, server/block/hash.go, server/block/register.go
RespawnAnchor block with Activate handling glowstone charging (0–4 charges), SafeSpawn detecting safe respawn locations via offset validation and collision checks, light emission by charge count, and NBT encoding; Hash() and registration wired in block/item registries.
Respawn Anchor Player Integration and Audio
server/player/player.go, server/world/sound/block.go, server/player/chat/translate.go, server/session/world.go
spawnLocation checks respawnBlock interface for SafeSpawn/CanRespawnOn; three sound events (Charge/Deplete/SetSpawn) mapped in session; respawn-point-set message translation added; sound event types defined.

Projectile Piercing Enchantment System

Layer / File(s) Summary
Piercing Enchantment Definition and Compatibility
server/item/enchantment/piercing.go, server/item/enchantment/register.go, server/item/enchantment/multishot.go
Piercing enchantment with max level 4, common rarity, cost scaling, crossbow-only compatibility, and Pierces() enabled; registered as ID 34; Multishot updated to block Piercing compatibility.
Arrow Spawn Configuration Refactoring
server/world/entity.go, server/entity/register.go
ArrowSpawnConfig struct consolidates damage, owner, critical, pickup flags, punch level, piercing level, and potion tip; EntityRegistryConfig.Arrow refactored to accept unified config instead of expanded parameter list.
Projectile Piercing Behavior and Collision Tracking
server/entity/projectile.go
Projectiles track collidedEntities, refactor hitEntity to use HurtEntity abstraction, close after exceeding PiercingLevel, only zero velocity when PiercingLevel is 0, and filter already-collided entities in trace ignores.
Bow and Crossbow Arrow Spawning
server/item/bow.go, server/item/crossbow.go
Bow.Release spawns arrows via ArrowSpawnConfig with explicit parameters; Crossbow.ReleaseCharge computes pierceLevel from enchantments, constructs ArrowSpawnConfig with piercing, fires multiple arrows on multishot with pickup disabled for subsequent shots.

Entity Damage Mechanics and Abstraction

Layer / File(s) Summary
Entity Damage Abstraction and Predicates
server/entity/damageable.go
HurtEntity helper routes damage to Living.Hurt or behavior-based Hurt via behaviourDamageable interface; damageableEntity predicate reports eligibility; supports non-living entities like end crystals receiving damage.
Player Damage and Non-Living Entity Attacks
server/player/player.go
Player.Hurt explicitly computes and clamps absorption from prior absorption and damageLeft, removes absorption effect when depleted; Player.AttackEntity uses HurtEntity for non-living targets, plays sound based on damage dealt, applies exhaustion on vulnerability.

Supporting Mechanics and Enhancements

Layer / File(s) Summary
Block Light Emission Levels
server/block/blast_furnace.go, server/block/furnace.go, server/block/smoker.go
BlastFurnace, Furnace, and Smoker blocks implement LightEmissionLevel() returning 13 when lit and 0 when unlit.
Explosion End Crystal Y-Level Clipping Mode
server/block/explosion.go
ExplosionConfig.EndCrystal field enables Y-level filtering: entity collection excludes below-origin entities, ray propagation stops below origin Y, exposure samples ignore below-origin points, guards division by zero on zero checks.
Absorption Mechanics and Item Consumer Interface
server/item/item.go, server/item/golden_apple.go
Consumer interface extended with Absorption() and SetAbsorption() methods; GoldenApple.Consume explicitly manages absorption with bounded clamping preventing drops below prior value or exceeding 16.
Enchantment Stack Compatibility and Block Registry Optimization
server/item/stack.go, server/world/block_registry.go
Stack.WithEnchantments validates mutual compatibility between new and existing enchantments; BlockRegistry.RuntimeIDToHash provides fast network hash lookup via precomputed ridsToNetworkhash table populated at finalization.

Sequence Diagram(s)

sequenceDiagram
  participant Player
  participant EndCrystal as End Crystal
  participant World
  participant Explosion
  
  Player->>EndCrystal: Attack / Damage
  EndCrystal->>EndCrystal: Hurt (damage, source)
  alt VoidDamageSource or ExplosionDamageSource
    EndCrystal->>World: Close entity
  else Other source (check endCrystalExploder)
    EndCrystal->>Explosion: Trigger explosion (EndCrystal: true, size 6)
    Explosion->>World: Clip entities below origin Y
    Explosion->>World: Clip blocks below origin Y
  end
  Explosion->>World: Deal damage in radius
Loading
sequenceDiagram
  participant Crossbow
  participant Arrow as Projectile
  participant Entity
  participant World
  
  Crossbow->>Arrow: Spawn with PiercingLevel
  Note over Arrow: Store collidedEntities = []
  Arrow->>World: Tick and move
  World->>Entity: Check collision
  Entity->>Arrow: Is damageable?
  alt Yes and not in collidedEntities
    Arrow->>Entity: HurtEntity (damage, source)
    Entity->>Arrow: Record in collidedEntities
    alt collidedEntities count > PiercingLevel
      Arrow->>World: Close entity
    else Continue
      Arrow->>Arrow: Maintain velocity, continue
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Crystals shine in the End so bright,
Anchors respawn, charging with light,
Arrows pierce through, piercing they go,
Dragonfly soars, putting on quite a show! ✨🔮

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feat/crystals+anchors' is vague and uses a non-descriptive naming convention that doesn't clearly convey the specific changes made. Use a clear, descriptive title that summarizes the main change, such as 'Add end crystal entity and respawn anchor block support' or 'Implement end crystals and respawn anchor functionality'.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

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

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

❤️ Share

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

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

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

Reviewed by Cursor Bugbot for commit 6f5385b. Configure here.

Comment thread server/entity/end_crystal_behaviour.go
Comment thread server/player/player.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

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

Inline comments:
In `@server/entity/projectile.go`:
- Around line 179-183: The collision bookkeeping and hit handling are
incorrectly gated on lt.conf.Damage >= 0 which prevents non-damaging projectiles
from registering hits; remove the Damage >= 0 guard so that
lt.hitEntity(r.Entity(), e, vel) is called on entity collision for all
projectiles and ensure lt.collidedEntities = append(lt.collidedEntities,
r.Entity().H()) is executed when damageableEntity(r.Entity()) is true regardless
of lt.conf.Damage; apply the same change to the other similar blocks that
reference lt.conf.Damage, lt.hitEntity, lt.collidedEntities, and
damageableEntity so collisions are always recorded even for negative-damage
configs.

In `@server/entity/register.go`:
- Around line 52-56: The Arrow spawn function currently does an unchecked type
assertion tip := arrow.Tip.(potion.Potion) and directly calls arrow.Owner.H(),
which can panic if Tip is nil/unset or Owner is nil; update the Arrow function
to defensively check arrow.Tip's type (use a type assertion with ok or a nil
check) before assigning to tip and fall back to a safe default potion or leave
conf.Potion unset, and also validate arrow.Owner is non-nil before calling
arrow.Owner.H() (or use a safe owner handle getter); ensure you still set
conf.Damage, conf.KnockBackForceAddend, and other fields only after these checks
so spawn-time panics are prevented.

In `@server/player/player.go`:
- Around line 1793-1803: The branch for non-living entities returns before the
item's durability is updated; when entity.HurtEntity(e, i.AttackDamage(), ...)
succeeds you must invoke the same durability-wear logic used for living targets
before returning true. Modify the non-living branch (the block using
entity.HurtEntity, p.tx.PlaySound and p.Exhaust) to call the existing
item-durability handler/mutation that the living-target path uses (i.e., the
same code that updates the item stack and triggers break/consume) so durable
items are decremented on successful hits against non-living entities as well.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cf686165-893e-4c43-90d8-aaab341ed2a3

📥 Commits

Reviewing files that changed from the base of the PR and between ff14ddc and 6f5385b.

📒 Files selected for processing (30)
  • server/block/blast_furnace.go
  • server/block/explosion.go
  • server/block/furnace.go
  • server/block/hash.go
  • server/block/register.go
  • server/block/respawn_anchor.go
  • server/block/smoker.go
  • server/entity/damage.go
  • server/entity/damageable.go
  • server/entity/end_crystal.go
  • server/entity/end_crystal_behaviour.go
  • server/entity/projectile.go
  • server/entity/register.go
  • server/item/bow.go
  • server/item/crossbow.go
  • server/item/enchantment/multishot.go
  • server/item/enchantment/piercing.go
  • server/item/enchantment/register.go
  • server/item/end_crystal.go
  • server/item/golden_apple.go
  • server/item/item.go
  • server/item/register.go
  • server/item/stack.go
  • server/player/chat/translate.go
  • server/player/player.go
  • server/session/entity_metadata.go
  • server/session/world.go
  • server/world/block_registry.go
  • server/world/entity.go
  • server/world/sound/block.go

Comment on lines +179 to +183
if lt.conf.Damage >= 0 {
lt.hitEntity(r.Entity(), e, vel)
if damageableEntity(r.Entity()) {
lt.collidedEntities = append(lt.collidedEntities, r.Entity().H())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Non-damaging projectiles no longer terminate on entity collision

Line 179 gates collision bookkeeping behind Damage >= 0, but Line 200 closes using collidedEntities length. For configs using negative damage, hits are never counted, so the projectile never closes after entity impact (and with Line 333 it can just stop moving in-place).

🔧 Proposed fix
 	switch r := result.(type) {
 	case trace.EntityResult:
-		if lt.conf.Damage >= 0 {
-			lt.hitEntity(r.Entity(), e, vel)
-			if damageableEntity(r.Entity()) {
-				lt.collidedEntities = append(lt.collidedEntities, r.Entity().H())
-			}
-		}
+		if damageableEntity(r.Entity()) {
+			if lt.conf.Damage >= 0 {
+				lt.hitEntity(r.Entity(), e, vel)
+			}
+			lt.collidedEntities = append(lt.collidedEntities, r.Entity().H())
+		}

Also applies to: 200-202, 333-335

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

In `@server/entity/projectile.go` around lines 179 - 183, The collision
bookkeeping and hit handling are incorrectly gated on lt.conf.Damage >= 0 which
prevents non-damaging projectiles from registering hits; remove the Damage >= 0
guard so that lt.hitEntity(r.Entity(), e, vel) is called on entity collision for
all projectiles and ensure lt.collidedEntities = append(lt.collidedEntities,
r.Entity().H()) is executed when damageableEntity(r.Entity()) is true regardless
of lt.conf.Damage; apply the same change to the other similar blocks that
reference lt.conf.Damage, lt.hitEntity, lt.collidedEntities, and
damageableEntity so collisions are always recorded even for negative-damage
configs.

Comment thread server/entity/register.go
Comment on lines +52 to +56
Arrow: func(opts world.EntitySpawnOpts, arrow world.ArrowSpawnConfig) *world.EntityHandle {
tip := arrow.Tip.(potion.Potion)
conf := arrowConf
conf.Damage, conf.Potion, conf.Owner = damage, tip.(potion.Potion), owner.H()
conf.KnockBackForceAddend = float64(punchLevel) * enchantment.Punch.KnockBackMultiplier()
conf.DisablePickup = disallowPickup
if obtainArrowOnPickup {
conf.PickupItem = item.NewStack(item.Arrow{Tip: tip.(potion.Potion)}, 1)
conf.Damage, conf.Potion, conf.Owner = arrow.Damage, tip, arrow.Owner.H()
conf.KnockBackForceAddend = float64(arrow.PunchLevel) * enchantment.Punch.KnockBackMultiplier()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify all ArrowSpawnConfig callsites and whether Tip/Owner are always set.
fd -e go | xargs rg -nP 'ArrowSpawnConfig\s*{' -C3
fd -e go | xargs rg -nP 'EntityRegistry\(\)\.Config\(\)\.Arrow\s*\(' -C3

Repository: HashimTheArab/dragonfly

Length of output: 822


Defensively handle world.ArrowSpawnConfig fields to avoid spawn-time panics.

server/entity/register.go does tip := arrow.Tip.(potion.Potion), but server/item/crossbow.go constructs world.ArrowSpawnConfig{...} without setting Tip, so this can panic when spawning arrows. Guard the type assertion (and arrow.Owner.H() for safety).

💡 Suggested hardening
 Arrow: func(opts world.EntitySpawnOpts, arrow world.ArrowSpawnConfig) *world.EntityHandle {
-	tip := arrow.Tip.(potion.Potion)
+	tip, _ := arrow.Tip.(potion.Potion)
 	conf := arrowConf
-	conf.Damage, conf.Potion, conf.Owner = arrow.Damage, tip, arrow.Owner.H()
+	conf.Damage, conf.Potion = arrow.Damage, tip
+	if arrow.Owner != nil {
+		conf.Owner = arrow.Owner.H()
+	}
 	conf.KnockBackForceAddend = float64(arrow.PunchLevel) * enchantment.Punch.KnockBackMultiplier()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Arrow: func(opts world.EntitySpawnOpts, arrow world.ArrowSpawnConfig) *world.EntityHandle {
tip := arrow.Tip.(potion.Potion)
conf := arrowConf
conf.Damage, conf.Potion, conf.Owner = damage, tip.(potion.Potion), owner.H()
conf.KnockBackForceAddend = float64(punchLevel) * enchantment.Punch.KnockBackMultiplier()
conf.DisablePickup = disallowPickup
if obtainArrowOnPickup {
conf.PickupItem = item.NewStack(item.Arrow{Tip: tip.(potion.Potion)}, 1)
conf.Damage, conf.Potion, conf.Owner = arrow.Damage, tip, arrow.Owner.H()
conf.KnockBackForceAddend = float64(arrow.PunchLevel) * enchantment.Punch.KnockBackMultiplier()
Arrow: func(opts world.EntitySpawnOpts, arrow world.ArrowSpawnConfig) *world.EntityHandle {
tip, _ := arrow.Tip.(potion.Potion)
conf := arrowConf
conf.Damage, conf.Potion = arrow.Damage, tip
if arrow.Owner != nil {
conf.Owner = arrow.Owner.H()
}
conf.KnockBackForceAddend = float64(arrow.PunchLevel) * enchantment.Punch.KnockBackMultiplier()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/entity/register.go` around lines 52 - 56, The Arrow spawn function
currently does an unchecked type assertion tip := arrow.Tip.(potion.Potion) and
directly calls arrow.Owner.H(), which can panic if Tip is nil/unset or Owner is
nil; update the Arrow function to defensively check arrow.Tip's type (use a type
assertion with ok or a nil check) before assigning to tip and fall back to a
safe default potion or leave conf.Potion unset, and also validate arrow.Owner is
non-nil before calling arrow.Owner.H() (or use a safe owner handle getter);
ensure you still set conf.Damage, conf.KnockBackForceAddend, and other fields
only after these checks so spawn-time panics are prevented.

Comment thread server/player/player.go
Comment on lines 1793 to 1803
if !isLiving {
return false
n, vulnerable, ok := entity.HurtEntity(e, i.AttackDamage(), entity.AttackDamageSource{Attacker: p})
if !ok {
return false
}
p.tx.PlaySound(entity.EyePosition(e), sound.Attack{Damage: !mgl64.FloatEqual(n, 0)})
if vulnerable {
p.Exhaust(0.1)
}
return true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Durability isn’t consumed when attacking non-living entities.

This new branch returns before the durability logic, so durable items never wear when damaging non-living targets.

Proposed fix
 	if !isLiving {
 		n, vulnerable, ok := entity.HurtEntity(e, i.AttackDamage(), entity.AttackDamageSource{Attacker: p})
 		if !ok {
 			return false
 		}
+		if durable, ok := i.Item().(item.Durable); ok {
+			_, left := p.HeldItems()
+			p.SetHeldItems(p.damageItem(i, durable.DurabilityInfo().AttackDurability), left)
+		}
 		p.tx.PlaySound(entity.EyePosition(e), sound.Attack{Damage: !mgl64.FloatEqual(n, 0)})
 		if vulnerable {
 			p.Exhaust(0.1)
 		}
 		return true
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/player/player.go` around lines 1793 - 1803, The branch for non-living
entities returns before the item's durability is updated; when
entity.HurtEntity(e, i.AttackDamage(), ...) succeeds you must invoke the same
durability-wear logic used for living targets before returning true. Modify the
non-living branch (the block using entity.HurtEntity, p.tx.PlaySound and
p.Exhaust) to call the existing item-durability handler/mutation that the
living-target path uses (i.e., the same code that updates the item stack and
triggers break/consume) so durable items are decremented on successful hits
against non-living entities as well.

HashimTheArab and others added 2 commits May 24, 2026 20:13
Constraint: Respawn anchors must resolve in their saved dimension, End crystal placement checks the two-block column above the base, End crystal block clipping is source-backed only for supported crystals, and water must suppress End crystal entity impact.
Rejected: Keeping position-only player spawn provider methods | Spawn position without dimension cannot model Bedrock's saved respawn target once anchors are supported.
Confidence: medium
Scope-risk: moderate
Directive: Keep player spawn dimension persistence aligned with Bedrock player data and do not reapply End crystal Y clipping to entity exposure without a primary source.
Tested: go test ./...; git diff --check
Not-tested: Live Bedrock client respawn across dimensions; in-game End crystal water neutralization.
Fix respawn and End crystal parity gaps
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants