package world import ( "fmt" "edgaru089.ml/go/gl01/internal/util/itype" ) // BlockRenderType is an enum describing the rendering process of a block type BlockRenderType int const ( OneTexture BlockRenderType = iota // Render with one texture of the same on all faces, "Name.png" ThreeTexture // Render with one texture on the top, one around the sides, and one on the bottom, "Name_top/side/bot.png," SixTexture // Render with six different textures on six faces, "Name_x+/x-/y+/y-/z+/z-.png" CustomRendering // Rendering calls BlockAppearance.CustomRenderAppend() ) // BlockAppearance describes basic appearance of a kind of block. type BlockAppearance struct { Name string // A short name, like "stone" or "dirt", used for texture lookups Transparent bool // Is block transparent? NotSolid bool // Is block not solid, i.e., has no solid hitbox? (this makes the zero value reasonable) Light int // The light level it emits, 0 is none Hitbox itype.Boxd // Hitbox, in block-local coordinates; empty slice means a default hitbox of 1x1x1 RenderType BlockRenderType // Rendering type, defaults to OneTexture (zero value) // Called on render if RenderType == CustomRendering. // // Be sure to return vertexArray at the end of the function (in case it got reallocated)!!!! CustomRenderAppend func( position itype.Vec3i, aux int, data itype.Dataset, world *World, vertexArray []Vertex, ) []Vertex } // BlockBehaviour describes a kind of block of the same Major ID. type BlockBehaviour interface { // Static returns if the Behaviour is "static", i.e., the Appearance does not // change with position, Minor ID or Dataset. Static Behaviours are cached // by the renderer and only generated once. // // Static implies RequireDataset = false and RequireBlockUpdate = false. Static() bool // RequireDataset returns if the type of block requires a Dataset attached. RequireDataset() bool // RequireBlockUpdate return if BlockUpdate should be called if a neighboring // block has changed. Blocks not requiring BlockUpdate does not change at all. RequireBlockUpdate() bool // Appearance returns the Appearance of the block at global position Position, // with Minor ID aux, and Dataset data. // // If RequireDataset if false, data is nil. Appearance(position itype.Vec3i, aux int, data itype.Dataset, world *World) BlockAppearance // BlockUpdate is called when RequireBlockUpdate is true and the block at // global position Position, with Minor ID aux, and Dataset data has a neighbor // that changed state. A block will only be updated once in a tick. // // If RequireDataset if false, data is nil. // // Return true if this block also changed state, false otherwise. BlockUpdate(position itype.Vec3i, aux int, data itype.Dataset, world *World) bool } type blockBehaviourStatic struct { app BlockAppearance } func (blockBehaviourStatic) Static() bool { return true } func (blockBehaviourStatic) RequireDataset() bool { return false } func (blockBehaviourStatic) RequireBlockUpdate() bool { return false } func (b blockBehaviourStatic) Appearance(position itype.Vec3i, aux int, data itype.Dataset, world *World) BlockAppearance { return b.app } func (blockBehaviourStatic) BlockUpdate(position itype.Vec3i, aux int, data itype.Dataset, world *World) bool { return false } // BlockBehaviourStatic returns a Static BlockBehaviour that has the given BlockAppearance. func BlockBehaviourStatic(app BlockAppearance) BlockBehaviour { return blockBehaviourStatic{app: app} } var behaviour map[int]BlockBehaviour = make(map[int]BlockBehaviour) var appearance map[int]BlockAppearance = make(map[int]BlockAppearance) var behaviourDoneRegister bool // RegisterBlockBehaviour registers behaviour with the given id. // // If the id is already taken, or id == 0, false is returned and nothing is done. // Otherwise, true is returned and the block is registered. func RegisterBlockBehaviour(id int, b BlockBehaviour) bool { if _, ok := behaviour[id]; behaviourDoneRegister || id == 0 || ok { return false } behaviour[id] = b return true } // DoneRegisteringBlockBehaviour is to be called after Registering BlockBehaviour, // i.e., in Post-Init() initializations. func DoneRegisteringBlockBehaviour() { for id, b := range behaviour { if b.Static() { appearance[id] = b.Appearance(itype.Vec3i{}, 0, nil, nil) } } behaviourDoneRegister = true } // GetBlockAppearance gets the block appearance of the given block in the fastest way possible. func GetBlockAppearance(position itype.Vec3i, id, aux int, data itype.Dataset, world *World) BlockAppearance { if app, ok := appearance[id]; ok { // Cache if app.Hitbox == (itype.Boxd{}) { app.Hitbox = itype.Boxd{ OffX: 0, OffY: 0, OffZ: 0, SizeX: 1, SizeY: 1, SizeZ: 1, } } return app } // Slow way b, ok := behaviour[id] if !ok { panic(fmt.Sprint("invalid block type ", id)) } app := b.Appearance(position, aux, data, world) if app.Hitbox == (itype.Boxd{}) { app.Hitbox = itype.Boxd{ OffX: 0, OffY: 0, OffZ: 0, SizeX: 1, SizeY: 1, SizeZ: 1, } } return app } // GetBlockBehaviour gets the block behaviour of the given id, or nil if not present. func GetBlockBehaviour(id int) BlockBehaviour { return behaviour[id] } // Block is a structure to store and pass Blocks around. type Block struct { Id, Aux int Dataset itype.Dataset Behaviour BlockBehaviour World *World } // Appearance is a shortcut for Behaviour.Appearance(). // It returns the Appearance of the block with the given parameters. func (b Block) Appearance(position itype.Vec3i) BlockAppearance { return b.Behaviour.Appearance(position, b.Aux, b.Dataset, b.World) } // BlockUpdate is a shortcut for Behaviour.BlockUpdate(). // It is called when RequireBlockUpdate is true and the block at // global position Position, with Minor ID aux, and Dataset data has a neighbor // that changed state. A block will only be updated once in a tick. // // If RequireDataset if false, data is nil. // // Return true if this block also changed state, false otherwise. func (b Block) BlockUpdate(position itype.Vec3i) bool { return b.Behaviour.BlockUpdate(position, b.Aux, b.Dataset, b.World) }