Files
mc/server/block/redstone_torch.go
T

295 lines
8.7 KiB
Go
Raw Normal View History

2026-07-09 08:33:57 +08:00
package block
import (
"math/rand/v2"
"time"
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/item"
"github.com/df-mc/dragonfly/server/world"
"github.com/df-mc/dragonfly/server/world/sound"
"github.com/go-gl/mathgl/mgl64"
)
// RedstoneTorch is a non-solid block that emits light and provides a full-strength redstone signal when lit.
type RedstoneTorch struct {
transparent
empty
// Facing is the direction from the torch to the block it is attached to.
Facing cube.Face
// Lit indicates whether the redstone torch is currently lit and emitting power.
Lit bool
}
// HasLiquidDrops returns whether the redstone torch drops its item when flowing liquid breaks it.
func (RedstoneTorch) HasLiquidDrops() bool {
return true
}
// LightEmissionLevel returns the light level emitted by the redstone torch (7 when lit, 0 when unlit).
func (t RedstoneTorch) LightEmissionLevel() uint8 {
if t.Lit {
return 7
}
return 0
}
// BreakInfo returns information about breaking the redstone torch.
func (t RedstoneTorch) BreakInfo() BreakInfo {
return newBreakInfo(0, alwaysHarvestable, nothingEffective, oneOf(t)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, _ item.User) {
tx.Redstone().ClearTorchBurnout(pos)
updateTorchRedstone(pos, tx)
})
}
// UseOnBlock handles the placement of a redstone torch on a block surface.
func (t RedstoneTorch) UseOnBlock(pos cube.Pos, face cube.Face, _ 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 := tx.Block(pos).(world.Liquid); ok {
return false
}
if !tx.Block(pos.Side(face.Opposite())).Model().FaceSolid(pos.Side(face.Opposite()), face, tx) {
fallbackFace, ok := findTorchPlacementFace(pos, tx)
if !ok {
return false
}
face = fallbackFace
}
t.Facing = face.Opposite()
t.Lit = true
place(tx, pos, t, user, ctx)
if placed(ctx) {
// Initialise the freshly placed torch state before propagating its output.
t.RedstoneUpdate(pos, tx)
updateTorchRedstone(pos, tx)
return true
}
return false
}
// NeighbourUpdateTick is called when a neighbouring block is updated.
func (t RedstoneTorch) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) {
if !tx.Block(pos.Side(t.Facing)).Model().FaceSolid(pos.Side(t.Facing), t.Facing.Opposite(), tx) {
tx.Redstone().ClearTorchBurnout(pos)
breakBlock(t, pos, tx)
return
}
if t.recoverFromBurnout(pos, tx) {
return
}
updateRedstone(pos, tx)
}
// RedstoneUpdate is called when the redstone power state changes nearby. This method ignores burned-out torches and
// schedules state changes for active torches.
func (t RedstoneTorch) RedstoneUpdate(pos cube.Pos, tx *world.Tx) {
currentTick := tx.CurrentTick()
if burnedOut, _ := tx.Redstone().TorchBurnoutStatus(pos, currentTick); burnedOut {
if t.updateSourceTouchesInput(pos, tx) {
t.recoverFromBurnout(pos, tx)
}
return
}
shouldBeLit := t.inputStrength(pos, tx) == 0
if shouldBeLit == t.Lit {
return
}
tx.Redstone().MarkTorchSelfTriggeredIfActive(pos)
tx.ScheduleBlockUpdate(pos, t, time.Millisecond*100)
}
// recoverFromBurnout relights a burned-out torch after a real neighbouring block update once its rapid-toggle history
// has expired. Redstone propagation alone may visit calculation-only positions, so it must not recover burned-out
// torches that did not receive an actual neighbour update.
func (RedstoneTorch) recoverFromBurnout(pos cube.Pos, tx *world.Tx) bool {
torch, ok := redstoneTorchAt(pos, tx)
if !ok {
return false
}
currentTick := tx.CurrentTick()
burnedOut, recoverable := tx.Redstone().TorchBurnoutStatus(pos, currentTick)
if !burnedOut {
return false
}
if !recoverable {
return true
}
tx.Redstone().ClearTorchBurnout(pos)
torch.Lit = torch.inputStrength(pos, tx) == 0
tx.SetBlock(pos, torch, nil)
updateTorchRedstone(pos, tx)
return true
}
// updateSourceTouchesInput reports whether the current redstone update came from the block the torch is attached to,
// or from a block directly beside it. Dust on top of the attached block can legitimately recover a burnout loop, while
// disconnected dust visited by the broad wire walk should not.
func (t RedstoneTorch) updateSourceTouchesInput(pos cube.Pos, tx *world.Tx) bool {
source, ok := tx.Redstone().UpdateSource()
if !ok {
return false
}
inputPos := pos.Side(t.Facing)
if source == inputPos {
return true
}
for _, face := range cube.Faces() {
if source == inputPos.Side(face) {
return true
}
}
return false
}
// ScheduledTick is called when a scheduled block update occurs.
// This method handles state changes and checks for burnout conditions.
func (RedstoneTorch) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) {
torch, ok := redstoneTorchAt(pos, tx)
if !ok {
return
}
currentTick := tx.CurrentTick()
if burnedOut, _ := tx.Redstone().TorchBurnoutStatus(pos, currentTick); burnedOut {
return
}
shouldBeLit := torch.inputStrength(pos, tx) == 0
if shouldBeLit == torch.Lit {
tx.Redstone().PruneTorchBurnout(pos, currentTick)
return
}
if tx.Redstone().RecordTorchToggle(pos, currentTick) {
torch.burnOut(pos, tx)
return
}
torch.Lit = !torch.Lit
tx.SetBlock(pos, torch, nil)
updateTorchRedstone(pos, tx)
}
// burnOut puts the redstone torch into burnout state, turning it off and playing effects.
func (RedstoneTorch) burnOut(pos cube.Pos, tx *world.Tx) {
torch, ok := redstoneTorchAt(pos, tx)
if !ok {
return
}
tx.Redstone().BurnOutTorch(pos)
torch.Lit = false
tx.PlaySound(pos.Vec3Centre(), sound.Fizz{})
tx.SetBlock(pos, torch, nil)
updateTorchRedstone(pos, tx)
}
// redstoneTorchAt returns the current torch at pos. Scheduled redstone updates carry an old block value, so mutation
// paths must reload the live world block before writing torch state back.
func redstoneTorchAt(pos cube.Pos, tx *world.Tx) (RedstoneTorch, bool) {
t, ok := tx.Block(pos).(RedstoneTorch)
if !ok {
tx.Redstone().ClearTorchBurnout(pos)
}
return t, ok
}
// updateTorchRedstone updates receivers around the torch and behind the block it strongly powers above.
func updateTorchRedstone(pos cube.Pos, tx *world.Tx) {
tx.Redstone().WithActiveTorchUpdate(pos, func() {
updateDirectionalRedstone(pos, tx, cube.FaceUp)
})
}
// EncodeItem encodes the redstone torch as an item.
func (RedstoneTorch) EncodeItem() (name string, meta int16) {
return "minecraft:redstone_torch", 0
}
// EncodeBlock encodes the redstone torch as a block for network transmission.
func (t RedstoneTorch) EncodeBlock() (name string, properties map[string]any) {
face := "unknown"
if t.Facing != unknownFace {
face = t.Facing.String()
if t.Facing == cube.FaceDown {
face = "top"
}
}
if t.Lit {
return "minecraft:redstone_torch", map[string]any{"torch_facing_direction": face}
}
return "minecraft:unlit_redstone_torch", map[string]any{"torch_facing_direction": face}
}
// RedstoneSource ...
func (t RedstoneTorch) RedstoneSource() bool {
return true
}
// WeakPower returns the weak redstone power level provided to adjacent blocks.
func (t RedstoneTorch) WeakPower(_ cube.Pos, face cube.Face, _ *world.Tx, _ bool) int {
if !t.Lit {
return 0
}
if face.Opposite() == t.Facing {
return 0
}
return 15
}
// StrongPower returns the strong redstone power level provided to the block above the torch.
func (t RedstoneTorch) StrongPower(_ cube.Pos, face cube.Face, _ *world.Tx, _ bool) int {
if t.Lit && face == cube.FaceDown {
return 15
}
return 0
}
// inputStrength returns the redstone power level received by the block the torch is attached to.
func (t RedstoneTorch) inputStrength(pos cube.Pos, tx *world.Tx) int {
return tx.RedstonePower(pos.Side(t.Facing), t.Facing, true)
}
// redstoneTorchFallbackSides lists the faces to check for placing a redstone torch on a non-solid block.
var redstoneTorchFallbackSides = [...]cube.Face{
cube.FaceSouth,
cube.FaceWest,
cube.FaceNorth,
cube.FaceEast,
cube.FaceDown,
}
// findTorchPlacementFace finds a valid face for placing a redstone torch on a non-solid block.
// It returns the face the torch should be placed on and whether it was found.
func findTorchPlacementFace(pos cube.Pos, tx *world.Tx) (cube.Face, bool) {
for _, side := range redstoneTorchFallbackSides {
if tx.Block(pos.Side(side)).Model().FaceSolid(pos.Side(side), side.Opposite(), tx) {
return side.Opposite(), true
}
}
return 0, false
}
// allRedstoneTorches returns all possible redstone torch block states.
func allRedstoneTorches() (all []world.Block) {
for _, f := range append(cube.Faces(), unknownFace) {
if f == cube.FaceUp {
continue
}
all = append(all, RedstoneTorch{Facing: f, Lit: true})
all = append(all, RedstoneTorch{Facing: f})
}
return
}