up3
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
package blockinternal
|
||||
|
||||
import (
|
||||
"github.com/df-mc/dragonfly/server/item/category"
|
||||
"maps"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// ComponentBuilder represents a builder that can be used to construct a block components map to be sent to a client.
|
||||
type ComponentBuilder struct {
|
||||
permutations map[string]map[string]any
|
||||
properties []map[string]any
|
||||
components map[string]any
|
||||
blockID int32
|
||||
|
||||
identifier string
|
||||
menuCategory category.Category
|
||||
}
|
||||
|
||||
// NewComponentBuilder returns a new component builder with the provided block data, using the provided components map
|
||||
// as a base.
|
||||
func NewComponentBuilder(identifier string, components map[string]any, blockID int32) *ComponentBuilder {
|
||||
if components == nil {
|
||||
components = map[string]any{}
|
||||
}
|
||||
return &ComponentBuilder{
|
||||
permutations: make(map[string]map[string]any),
|
||||
components: components,
|
||||
blockID: blockID,
|
||||
|
||||
identifier: identifier,
|
||||
menuCategory: category.Construction(),
|
||||
}
|
||||
}
|
||||
|
||||
// AddProperty adds the provided block property to the builder.
|
||||
func (builder *ComponentBuilder) AddProperty(name string, values []any) {
|
||||
builder.properties = append(builder.properties, map[string]any{
|
||||
"name": name,
|
||||
"enum": values,
|
||||
})
|
||||
}
|
||||
|
||||
// AddComponent adds the provided component to the builder. If the component already exists, it will be overwritten.
|
||||
func (builder *ComponentBuilder) AddComponent(name string, value any) {
|
||||
builder.components[name] = value
|
||||
}
|
||||
|
||||
// AddPermutation adds a permutation to the builder. If there is already an existing permutation for the provided
|
||||
// condition, the new components will be added to the existing permutation.
|
||||
func (builder *ComponentBuilder) AddPermutation(condition string, components map[string]any) {
|
||||
if len(builder.permutations) == 0 {
|
||||
// This trigger really does not matter at all, the component just needs to be set for custom block placements to
|
||||
// function as expected client-side, when permutations are applied.
|
||||
builder.AddComponent("minecraft:on_player_placing", map[string]any{
|
||||
"triggerType": "placement_trigger",
|
||||
})
|
||||
}
|
||||
if builder.permutations[condition] == nil {
|
||||
builder.permutations[condition] = map[string]any{}
|
||||
}
|
||||
for key, value := range components {
|
||||
builder.permutations[condition][key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// SetMenuCategory sets the creative category for the current block.
|
||||
func (builder *ComponentBuilder) SetMenuCategory(category category.Category) {
|
||||
builder.menuCategory = category
|
||||
}
|
||||
|
||||
// Construct constructs the final block components map that is ready to be sent to the client.
|
||||
func (builder *ComponentBuilder) Construct() map[string]any {
|
||||
properties := slices.Clone(builder.properties)
|
||||
components := maps.Clone(builder.components)
|
||||
|
||||
result := map[string]any{
|
||||
"components": components,
|
||||
"molangVersion": int32(10),
|
||||
"menu_category": map[string]any{
|
||||
"category": builder.menuCategory.String(),
|
||||
"group": builder.menuCategory.Group(),
|
||||
},
|
||||
"vanilla_block_data": map[string]any{
|
||||
"block_id": builder.blockID,
|
||||
},
|
||||
}
|
||||
if len(properties) > 0 {
|
||||
result["properties"] = properties
|
||||
}
|
||||
|
||||
permutations := maps.Clone(builder.permutations)
|
||||
if len(permutations) > 0 {
|
||||
result["permutations"] = []map[string]any{}
|
||||
for condition, values := range permutations {
|
||||
result["permutations"] = append(result["permutations"].([]map[string]any), map[string]any{
|
||||
"condition": condition,
|
||||
"components": values,
|
||||
})
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package blockinternal
|
||||
|
||||
import (
|
||||
"github.com/df-mc/dragonfly/server/block"
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/block/customblock"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/go-gl/mathgl/mgl64"
|
||||
)
|
||||
|
||||
// Components returns all the components for the custom block, including permutations and properties.
|
||||
func Components(identifier string, b world.CustomBlock, blockID int32) map[string]any {
|
||||
components := componentsFromProperties(b.Properties())
|
||||
builder := NewComponentBuilder(identifier, components, blockID)
|
||||
if emitter, ok := b.(block.LightEmitter); ok {
|
||||
builder.AddComponent("minecraft:block_light_emission", map[string]any{
|
||||
"emission": float32(emitter.LightEmissionLevel() / 15),
|
||||
})
|
||||
}
|
||||
if diffuser, ok := b.(block.LightDiffuser); ok {
|
||||
builder.AddComponent("minecraft:block_light_filter", map[string]any{
|
||||
"lightLevel": int32(diffuser.LightDiffusionLevel()),
|
||||
})
|
||||
}
|
||||
if breakable, ok := b.(block.Breakable); ok {
|
||||
info := breakable.BreakInfo()
|
||||
builder.AddComponent("minecraft:destructible_by_mining", map[string]any{"value": float32(info.Hardness)})
|
||||
}
|
||||
if frictional, ok := b.(block.Frictional); ok {
|
||||
builder.AddComponent("minecraft:friction", map[string]any{"value": float32(frictional.Friction())})
|
||||
}
|
||||
if flammable, ok := b.(block.Flammable); ok {
|
||||
info := flammable.FlammabilityInfo()
|
||||
builder.AddComponent("minecraft:flammable", map[string]any{
|
||||
"flame_odds": int32(info.Encouragement),
|
||||
"burn_odds": int32(info.Flammability),
|
||||
})
|
||||
}
|
||||
if permutable, ok := b.(block.Permutable); ok {
|
||||
for name, values := range permutable.States() {
|
||||
builder.AddProperty(name, values)
|
||||
}
|
||||
for _, permutation := range permutable.Permutations() {
|
||||
builder.AddPermutation(permutation.Condition, componentsFromProperties(permutation.Properties))
|
||||
}
|
||||
}
|
||||
if item, ok := b.(world.CustomItem); ok {
|
||||
builder.SetMenuCategory(item.Category())
|
||||
}
|
||||
return builder.Construct()
|
||||
}
|
||||
|
||||
// componentsFromProperties builds a base components map that includes all the common data between a regular block and
|
||||
// a custom permutation.
|
||||
func componentsFromProperties(props customblock.Properties) map[string]any {
|
||||
components := make(map[string]any)
|
||||
if props.CollisionBox != (cube.BBox{}) {
|
||||
components["minecraft:collision_box"] = collisionBoxComponent(props.CollisionBox)
|
||||
}
|
||||
if props.SelectionBox != (cube.BBox{}) {
|
||||
components["minecraft:selection_box"] = selectionBoxComponent(props.SelectionBox)
|
||||
}
|
||||
if props.Geometry != "" {
|
||||
components["minecraft:geometry"] = map[string]any{"identifier": props.Geometry}
|
||||
} else if props.Cube {
|
||||
components["minecraft:unit_cube"] = map[string]any{}
|
||||
}
|
||||
if props.MapColour != "" {
|
||||
components["minecraft:map_color"] = map[string]any{"value": props.MapColour}
|
||||
}
|
||||
if props.Textures != nil {
|
||||
materials := map[string]any{}
|
||||
for target, material := range props.Textures {
|
||||
materials[target] = material.Encode()
|
||||
}
|
||||
components["minecraft:material_instances"] = map[string]any{
|
||||
"mappings": map[string]any{},
|
||||
"materials": materials,
|
||||
}
|
||||
}
|
||||
transformation := make(map[string]any)
|
||||
if props.Rotation != (cube.Pos{}) {
|
||||
transformation["RX"] = int32(props.Rotation.X())
|
||||
transformation["RY"] = int32(props.Rotation.Y())
|
||||
transformation["RZ"] = int32(props.Rotation.Z())
|
||||
}
|
||||
if props.Translation != (mgl64.Vec3{}) {
|
||||
transformation["TX"] = float32(props.Translation.X())
|
||||
transformation["TY"] = float32(props.Translation.Y())
|
||||
transformation["TZ"] = float32(props.Translation.Z())
|
||||
}
|
||||
if props.Scale != (mgl64.Vec3{}) {
|
||||
transformation["SX"] = float32(props.Scale.X())
|
||||
transformation["SY"] = float32(props.Scale.Y())
|
||||
transformation["SZ"] = float32(props.Scale.Z())
|
||||
} else if len(transformation) > 0 {
|
||||
transformation["SX"] = float32(1.0)
|
||||
transformation["SY"] = float32(1.0)
|
||||
transformation["SZ"] = float32(1.0)
|
||||
}
|
||||
if len(transformation) > 0 {
|
||||
components["minecraft:transformation"] = transformation
|
||||
}
|
||||
return components
|
||||
}
|
||||
|
||||
// collisionBoxComponent returns the component data for a collision box, using absolute min/max coordinates in pixels.
|
||||
func collisionBoxComponent(box cube.BBox) map[string]any {
|
||||
min, max := box.Min(), box.Max()
|
||||
return map[string]any{
|
||||
"enabled": true,
|
||||
"boxes": []map[string]any{
|
||||
{
|
||||
"minX": float32(min.X() * 16),
|
||||
"minY": float32(min.Y() * 16),
|
||||
"minZ": float32(min.Z() * 16),
|
||||
"maxX": float32(max.X() * 16),
|
||||
"maxY": float32(max.Y() * 16),
|
||||
"maxZ": float32(max.Z() * 16),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// selectionBoxComponent returns the component data for a selection box, translating coordinates to the origin/size
|
||||
// format the client expects.
|
||||
func selectionBoxComponent(box cube.BBox) map[string]any {
|
||||
min, max := box.Min(), box.Max()
|
||||
originX, originY, originZ := min.X()*16, min.Y()*16, min.Z()*16
|
||||
sizeX, sizeY, sizeZ := (max.X()-min.X())*16, (max.Y()-min.Y())*16, (max.Z()-min.Z())*16
|
||||
return map[string]any{
|
||||
"enabled": true,
|
||||
"origin": []float32{float32(originX) - 8, float32(originY), float32(originZ) - 8},
|
||||
"size": []float32{float32(sizeX), float32(sizeY), float32(sizeZ)},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package iteminternal
|
||||
|
||||
import (
|
||||
"github.com/df-mc/dragonfly/server/item/category"
|
||||
"maps"
|
||||
)
|
||||
|
||||
// ComponentBuilder represents a builder that can be used to construct an item components map to be sent to a client.
|
||||
type ComponentBuilder struct {
|
||||
name string
|
||||
identifier string
|
||||
category category.Category
|
||||
|
||||
properties map[string]any
|
||||
components map[string]any
|
||||
}
|
||||
|
||||
// NewComponentBuilder returns a new component builder with the provided item data.
|
||||
func NewComponentBuilder(name, identifier string, category category.Category) *ComponentBuilder {
|
||||
return &ComponentBuilder{
|
||||
name: name,
|
||||
identifier: identifier,
|
||||
category: category,
|
||||
|
||||
properties: make(map[string]any),
|
||||
components: make(map[string]any),
|
||||
}
|
||||
}
|
||||
|
||||
// AddProperty adds the provided property to the builder.
|
||||
func (builder *ComponentBuilder) AddProperty(name string, value any) {
|
||||
builder.properties[name] = value
|
||||
}
|
||||
|
||||
// AddComponent adds the provided component to the builder.
|
||||
func (builder *ComponentBuilder) AddComponent(name string, value any) {
|
||||
builder.components[name] = value
|
||||
}
|
||||
|
||||
// Construct constructs the final item components map and returns it. It also applies the default properties required
|
||||
// for the item to work without modifying the original maps in the builder.
|
||||
func (builder *ComponentBuilder) Construct() map[string]any {
|
||||
properties := maps.Clone(builder.properties)
|
||||
components := maps.Clone(builder.components)
|
||||
builder.applyDefaultProperties(properties)
|
||||
builder.applyDefaultComponents(components, properties)
|
||||
return map[string]any{"components": components}
|
||||
}
|
||||
|
||||
// applyDefaultProperties applies the default properties to the provided map. It is important that this method does
|
||||
// not modify the builder's properties map directly otherwise Empty() will return false in future use of the builder.
|
||||
func (builder *ComponentBuilder) applyDefaultProperties(x map[string]any) {
|
||||
x["minecraft:icon"] = map[string]any{
|
||||
"textures": map[string]any{
|
||||
"default": builder.identifier,
|
||||
},
|
||||
}
|
||||
x["creative_group"] = builder.category.Group()
|
||||
x["creative_category"] = int32(builder.category.Uint8())
|
||||
if _, ok := x["max_stack_size"]; !ok {
|
||||
x["max_stack_size"] = int32(64)
|
||||
}
|
||||
}
|
||||
|
||||
// applyDefaultComponents applies the default components to the provided map. It is important that this method does not
|
||||
// modify the builder's components map directly otherwise Empty() will return false in future use of the builder.
|
||||
func (builder *ComponentBuilder) applyDefaultComponents(x, properties map[string]any) {
|
||||
x["item_properties"] = properties
|
||||
x["minecraft:display_name"] = map[string]any{
|
||||
"value": builder.name,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package iteminternal
|
||||
|
||||
import (
|
||||
"github.com/df-mc/dragonfly/server/item"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Components returns all the components of the given custom item. If the item has no components, a nil map and false
|
||||
// are returned.
|
||||
func Components(it world.CustomItem) map[string]any {
|
||||
category := it.Category()
|
||||
identifier, _ := it.EncodeItem()
|
||||
name := strings.Split(identifier, ":")[1]
|
||||
|
||||
builder := NewComponentBuilder(it.Name(), identifier, category)
|
||||
|
||||
if x, ok := it.(item.Armour); ok {
|
||||
builder.AddComponent("minecraft:armor", map[string]any{
|
||||
"protection": int32(x.DefencePoints()),
|
||||
})
|
||||
|
||||
var slot string
|
||||
switch it.(type) {
|
||||
case item.HelmetType:
|
||||
slot = "slot.armor.head"
|
||||
case item.ChestplateType:
|
||||
slot = "slot.armor.chest"
|
||||
case item.LeggingsType:
|
||||
slot = "slot.armor.legs"
|
||||
case item.BootsType:
|
||||
slot = "slot.armor.feet"
|
||||
}
|
||||
builder.AddComponent("minecraft:wearable", map[string]any{
|
||||
"slot": slot,
|
||||
})
|
||||
}
|
||||
if x, ok := it.(item.Consumable); ok {
|
||||
builder.AddProperty("use_duration", int32(x.ConsumeDuration().Seconds()*20))
|
||||
builder.AddComponent("minecraft:food", map[string]any{
|
||||
"can_always_eat": x.AlwaysConsumable(),
|
||||
})
|
||||
|
||||
if y, ok := it.(item.Drinkable); ok && y.Drinkable() {
|
||||
builder.AddProperty("use_animation", int32(2))
|
||||
} else {
|
||||
builder.AddProperty("use_animation", int32(1))
|
||||
}
|
||||
}
|
||||
if x, ok := it.(item.Cooldown); ok {
|
||||
builder.AddComponent("minecraft:cooldown", map[string]any{
|
||||
"category": name,
|
||||
"duration": float32(x.Cooldown().Seconds()),
|
||||
})
|
||||
}
|
||||
if x, ok := it.(item.Durable); ok {
|
||||
builder.AddComponent("minecraft:durability", map[string]any{
|
||||
"max_durability": int32(x.DurabilityInfo().MaxDurability),
|
||||
})
|
||||
}
|
||||
if x, ok := it.(item.MaxCounter); ok {
|
||||
builder.AddProperty("max_stack_size", int32(x.MaxCount()))
|
||||
}
|
||||
if x, ok := it.(item.OffHand); ok {
|
||||
builder.AddProperty("allow_off_hand", x.OffHand())
|
||||
}
|
||||
if x, ok := it.(item.Throwable); ok {
|
||||
// The data in minecraft:projectile is only used by vanilla server-side, but we must send at least an empty map
|
||||
// so the client will play the throwing animation.
|
||||
builder.AddComponent("minecraft:projectile", map[string]any{})
|
||||
builder.AddComponent("minecraft:throwable", map[string]any{
|
||||
"do_swing_animation": x.SwingAnimation(),
|
||||
})
|
||||
}
|
||||
if x, ok := it.(item.Glinted); ok {
|
||||
builder.AddProperty("foil", x.Glinted())
|
||||
}
|
||||
if x, ok := it.(item.HandEquipped); ok {
|
||||
builder.AddProperty("hand_equipped", x.HandEquipped())
|
||||
}
|
||||
return builder.Construct()
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package nbtconv
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
// Int32FromRGBA converts a color.RGBA into an int32. These int32s are present in, for example, signs.
|
||||
func Int32FromRGBA(x color.RGBA) int32 {
|
||||
if x.R == 0 && x.G == 0 && x.B == 0 {
|
||||
// Default to black colour. The default (0x000000) is a transparent colour. Text with this colour will not show
|
||||
// up on the sign.
|
||||
return int32(-0x1000000)
|
||||
}
|
||||
return int32(binary.BigEndian.Uint32([]byte{x.A, x.R, x.G, x.B}))
|
||||
}
|
||||
|
||||
// RGBAFromInt32 converts an int32 into a color.RGBA. These int32s are present in, for example, signs.
|
||||
func RGBAFromInt32(x int32) color.RGBA {
|
||||
b := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(b, uint32(x))
|
||||
|
||||
return color.RGBA{A: b[0], R: b[1], G: b[2], B: b[3]}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package nbtconv
|
||||
|
||||
import (
|
||||
"github.com/df-mc/dragonfly/server/item/inventory"
|
||||
)
|
||||
|
||||
// InvFromNBT decodes the data of an NBT slice into the inventory passed.
|
||||
func InvFromNBT(inv *inventory.Inventory, items []any) {
|
||||
for _, itemData := range items {
|
||||
data, _ := itemData.(map[string]any)
|
||||
it := Item(data, nil)
|
||||
if it.Empty() {
|
||||
continue
|
||||
}
|
||||
_ = inv.SetItem(int(Uint8(data, "Slot")), it)
|
||||
}
|
||||
}
|
||||
|
||||
// InvToNBT encodes an inventory to a data slice which may be encoded as NBT.
|
||||
func InvToNBT(inv *inventory.Inventory) []map[string]any {
|
||||
var items []map[string]any
|
||||
for index, i := range inv.Slots() {
|
||||
if i.Empty() {
|
||||
continue
|
||||
}
|
||||
data := WriteItem(i, true)
|
||||
data["Slot"] = byte(index)
|
||||
items = append(items, data)
|
||||
}
|
||||
return items
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
package nbtconv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"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/go-gl/mathgl/mgl64"
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
// Bool reads a uint8 value from a map at key k and returns true if it equals 1.
|
||||
func Bool(m map[string]any, k string) bool {
|
||||
return Uint8(m, k) == 1
|
||||
}
|
||||
|
||||
// Uint8 reads a uint8 value from a map at key k.
|
||||
func Uint8(m map[string]any, k string) uint8 {
|
||||
v, _ := m[k].(uint8)
|
||||
return v
|
||||
}
|
||||
|
||||
// String reads a string value from a map at key k.
|
||||
func String(m map[string]any, k string) string {
|
||||
v, _ := m[k].(string)
|
||||
return v
|
||||
}
|
||||
|
||||
// Int16 reads an int16 value from a map at key k.
|
||||
func Int16(m map[string]any, k string) int16 {
|
||||
v, _ := m[k].(int16)
|
||||
return v
|
||||
}
|
||||
|
||||
// Int32 reads an int32 value from a map at key k.
|
||||
func Int32(m map[string]any, k string) int32 {
|
||||
v, _ := m[k].(int32)
|
||||
return v
|
||||
}
|
||||
|
||||
// Int64 reads an int16 value from a map at key k.
|
||||
func Int64(m map[string]any, k string) int64 {
|
||||
v, _ := m[k].(int64)
|
||||
return v
|
||||
}
|
||||
|
||||
// TickDuration reads a uint8/int16/in32 value from a map at key k and converts
|
||||
// it from ticks to a time.Duration.
|
||||
func TickDuration[T constraints.Integer](m map[string]any, k string) time.Duration {
|
||||
var v time.Duration
|
||||
switch any(*new(T)).(type) {
|
||||
case uint8:
|
||||
v = time.Duration(Uint8(m, k))
|
||||
case int16:
|
||||
v = time.Duration(Int16(m, k))
|
||||
case int32:
|
||||
v = time.Duration(Int32(m, k))
|
||||
default:
|
||||
panic("invalid tick duration value type")
|
||||
}
|
||||
return v * time.Millisecond * 50
|
||||
}
|
||||
|
||||
// Float32 reads a float32 value from a map at key k.
|
||||
func Float32(m map[string]any, k string) float32 {
|
||||
v, _ := m[k].(float32)
|
||||
return v
|
||||
}
|
||||
|
||||
// Rotation reads a cube.Rotation from the map passed.
|
||||
func Rotation(m map[string]any) cube.Rotation {
|
||||
return cube.Rotation{float64(Float32(m, "Yaw")), float64(Float32(m, "Pitch"))}
|
||||
}
|
||||
|
||||
// Float64 reads a float64 value from a map at key k.
|
||||
func Float64(m map[string]any, k string) float64 {
|
||||
v, _ := m[k].(float64)
|
||||
return v
|
||||
}
|
||||
|
||||
// Slice reads a []any value from a map at key k.
|
||||
func Slice(m map[string]any, k string) []any {
|
||||
v, _ := m[k].([]any)
|
||||
return v
|
||||
}
|
||||
|
||||
// Vec3 converts x, y and z values in an NBT map to an mgl64.Vec3.
|
||||
func Vec3(x map[string]any, k string) mgl64.Vec3 {
|
||||
if i, ok := x[k].([]any); ok {
|
||||
if len(i) != 3 {
|
||||
return mgl64.Vec3{}
|
||||
}
|
||||
var v mgl64.Vec3
|
||||
for index, f := range i {
|
||||
f32, _ := f.(float32)
|
||||
v[index] = float64(f32)
|
||||
}
|
||||
return v
|
||||
} else if i, ok := x[k].([]float32); ok {
|
||||
if len(i) != 3 {
|
||||
return mgl64.Vec3{}
|
||||
}
|
||||
return mgl64.Vec3{float64(i[0]), float64(i[1]), float64(i[2])}
|
||||
}
|
||||
return mgl64.Vec3{}
|
||||
}
|
||||
|
||||
// Vec3ToFloat32Slice converts an mgl64.Vec3 to a []float32 with 3 elements.
|
||||
func Vec3ToFloat32Slice(x mgl64.Vec3) []float32 {
|
||||
return []float32{float32(x[0]), float32(x[1]), float32(x[2])}
|
||||
}
|
||||
|
||||
// Pos converts x, y and z values in an NBT map to a cube.Pos.
|
||||
func Pos(x map[string]any, k string) cube.Pos {
|
||||
if i, ok := x[k].([]any); ok {
|
||||
if len(i) != 3 {
|
||||
return cube.Pos{}
|
||||
}
|
||||
var v cube.Pos
|
||||
for index, f := range i {
|
||||
f32, _ := f.(int32)
|
||||
v[index] = int(f32)
|
||||
}
|
||||
return v
|
||||
} else if i, ok := x[k].([]int32); ok {
|
||||
if len(i) != 3 {
|
||||
return cube.Pos{}
|
||||
}
|
||||
return cube.Pos{int(i[0]), int(i[1]), int(i[2])}
|
||||
}
|
||||
return cube.Pos{}
|
||||
}
|
||||
|
||||
// PosToInt32Slice converts a cube.Pos to a []int32 with 3 elements.
|
||||
func PosToInt32Slice(x cube.Pos) []int32 {
|
||||
return []int32{int32(x[0]), int32(x[1]), int32(x[2])}
|
||||
}
|
||||
|
||||
// MapItem converts an item's name, count, damage (and properties when it is a block) in a map obtained by decoding NBT
|
||||
// to a world.Item.
|
||||
func MapItem(x map[string]any, k string) item.Stack {
|
||||
if m, ok := x[k].(map[string]any); ok {
|
||||
return Item(m, nil)
|
||||
}
|
||||
return item.Stack{}
|
||||
}
|
||||
|
||||
// Item decodes the data of an item into an item stack.
|
||||
func Item(data map[string]any, s *item.Stack) item.Stack {
|
||||
disk, tag := s == nil, data
|
||||
if disk {
|
||||
t, ok := data["tag"].(map[string]any)
|
||||
if !ok {
|
||||
t = map[string]any{}
|
||||
}
|
||||
tag = t
|
||||
|
||||
a := readItemStack(data, tag)
|
||||
s = &a
|
||||
}
|
||||
|
||||
readAnvilCost(tag, s)
|
||||
readDamage(tag, s, disk)
|
||||
readDisplay(tag, s)
|
||||
readDragonflyData(tag, s)
|
||||
readEnchantments(tag, s)
|
||||
readUnbreakable(tag, s)
|
||||
return *s
|
||||
}
|
||||
|
||||
// Block decodes the data of a block into a world.Block.
|
||||
func Block(m map[string]any, k string) world.Block {
|
||||
if mk, ok := m[k].(map[string]any); ok {
|
||||
name, _ := mk["name"].(string)
|
||||
properties, _ := mk["states"].(map[string]any)
|
||||
b, _ := world.BlockByName(name, properties)
|
||||
return b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readItemStack reads an item.Stack from the NBT in the map passed.
|
||||
func readItemStack(m, t map[string]any) item.Stack {
|
||||
var it world.Item
|
||||
if blockItem, ok := Block(m, "Block").(world.Item); ok {
|
||||
it = blockItem
|
||||
}
|
||||
if v, ok := world.ItemByName(String(m, "Name"), Int16(m, "Damage")); ok {
|
||||
it = v
|
||||
}
|
||||
if it == nil {
|
||||
return item.Stack{}
|
||||
}
|
||||
if n, ok := it.(world.NBTer); ok {
|
||||
it = n.DecodeNBT(t).(world.Item)
|
||||
}
|
||||
return item.NewStack(it, int(Uint8(m, "Count")))
|
||||
}
|
||||
|
||||
// readDamage reads the damage value stored in the NBT with the Damage tag and saves it to the item.Stack passed.
|
||||
func readDamage(m map[string]any, s *item.Stack, disk bool) {
|
||||
if disk {
|
||||
*s = s.Damage(int(Int16(m, "Damage")))
|
||||
return
|
||||
}
|
||||
*s = s.Damage(int(Int32(m, "Damage")))
|
||||
}
|
||||
|
||||
// readAnvilCost ...
|
||||
func readAnvilCost(m map[string]any, s *item.Stack) {
|
||||
*s = s.WithAnvilCost(int(Int32(m, "RepairCost")))
|
||||
}
|
||||
|
||||
// readEnchantments reads the enchantments stored in the ench tag of the NBT passed and stores it into an item.Stack.
|
||||
func readEnchantments(m map[string]any, s *item.Stack) {
|
||||
enchantments, ok := m["ench"].([]map[string]any)
|
||||
if !ok {
|
||||
for _, e := range Slice(m, "ench") {
|
||||
if v, ok := e.(map[string]any); ok {
|
||||
enchantments = append(enchantments, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, ench := range enchantments {
|
||||
if t, ok := item.EnchantmentByID(int(Int16(ench, "id"))); ok {
|
||||
*s = s.WithForcedEnchantments(item.NewEnchantment(t, int(Int16(ench, "lvl"))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readDisplay reads the display data present in the display field in the NBT. It includes a custom name of the item
|
||||
// and the lore.
|
||||
func readDisplay(m map[string]any, s *item.Stack) {
|
||||
if display, ok := m["display"].(map[string]any); ok {
|
||||
if name, ok := display["Name"].(string); ok {
|
||||
// Only add the custom name if actually set.
|
||||
*s = s.WithCustomName(name)
|
||||
}
|
||||
if lore, ok := display["Lore"].([]string); ok {
|
||||
*s = s.WithLore(lore...)
|
||||
} else if lore, ok := display["Lore"].([]any); ok {
|
||||
loreLines := make([]string, 0, len(lore))
|
||||
for _, l := range lore {
|
||||
loreLines = append(loreLines, l.(string))
|
||||
}
|
||||
*s = s.WithLore(loreLines...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readDragonflyData reads data written to the dragonflyData field in the NBT of an item and adds it to the item.Stack
|
||||
// passed.
|
||||
func readDragonflyData(m map[string]any, s *item.Stack) {
|
||||
if customData, ok := m["dragonflyData"]; ok {
|
||||
d, ok := customData.([]byte)
|
||||
if !ok {
|
||||
if itf, ok := customData.([]any); ok {
|
||||
for _, v := range itf {
|
||||
b, _ := v.(byte)
|
||||
d = append(d, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
var values []mapValue
|
||||
if err := gob.NewDecoder(bytes.NewBuffer(d)).Decode(&values); err != nil {
|
||||
panic("error decoding item user data: " + err.Error())
|
||||
}
|
||||
for _, val := range values {
|
||||
*s = s.WithValue(val.K, val.V)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readUnbreakable reads the unbreakable value stored in the NBT with the Unbreakable tag and saves it to the item.Stack
|
||||
// passed.
|
||||
func readUnbreakable(m map[string]any, s *item.Stack) {
|
||||
if Bool(m, "Unbreakable") {
|
||||
*s = s.AsUnbreakable()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package nbtconv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"github.com/df-mc/dragonfly/server/item"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/df-mc/dragonfly/server/world/chunk"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// WriteItem encodes an item stack into a map that can be encoded using NBT.
|
||||
func WriteItem(s item.Stack, disk bool) map[string]any {
|
||||
tag := make(map[string]any)
|
||||
if s.Empty() {
|
||||
return tag
|
||||
}
|
||||
if nbt, ok := s.Item().(world.NBTer); ok {
|
||||
for k, v := range nbt.EncodeNBT() {
|
||||
tag[k] = v
|
||||
}
|
||||
}
|
||||
writeAnvilCost(tag, s)
|
||||
writeDamage(tag, s, disk)
|
||||
writeDisplay(tag, s)
|
||||
writeDragonflyData(tag, s)
|
||||
writeEnchantments(tag, s)
|
||||
writeUnbreakable(tag, s)
|
||||
|
||||
data := make(map[string]any)
|
||||
if disk {
|
||||
writeItemStack(data, tag, s)
|
||||
} else {
|
||||
for k, v := range tag {
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// WriteBlock encodes a world.Block into a map that can be encoded using NBT.
|
||||
func WriteBlock(b world.Block) map[string]any {
|
||||
name, properties := b.EncodeBlock()
|
||||
return map[string]any{
|
||||
"name": name,
|
||||
"states": properties,
|
||||
"version": chunk.CurrentBlockVersion,
|
||||
}
|
||||
}
|
||||
|
||||
// writeItemStack writes the name, metadata value, count and NBT of an item to a map ready for NBT encoding.
|
||||
func writeItemStack(m, t map[string]any, s item.Stack) {
|
||||
m["Name"], m["Damage"] = s.Item().EncodeItem()
|
||||
if b, ok := s.Item().(world.Block); ok {
|
||||
v := map[string]any{}
|
||||
writeBlock(v, b)
|
||||
m["Block"] = v
|
||||
}
|
||||
m["Count"] = byte(s.Count())
|
||||
if len(t) > 0 {
|
||||
m["tag"] = t
|
||||
}
|
||||
}
|
||||
|
||||
// writeBlock writes the name, properties and version of a block to a map ready for NBT encoding.
|
||||
func writeBlock(m map[string]any, b world.Block) {
|
||||
m["name"], m["states"] = b.EncodeBlock()
|
||||
m["version"] = chunk.CurrentBlockVersion
|
||||
}
|
||||
|
||||
// writeDragonflyData writes additional data associated with an item.Stack to a map for NBT encoding.
|
||||
func writeDragonflyData(m map[string]any, s item.Stack) {
|
||||
if v := s.Values(); len(v) != 0 {
|
||||
buf := new(bytes.Buffer)
|
||||
if err := gob.NewEncoder(buf).Encode(mapToSlice(v)); err != nil {
|
||||
panic("error encoding item user data: " + err.Error())
|
||||
}
|
||||
m["dragonflyData"] = buf.Bytes()
|
||||
}
|
||||
}
|
||||
|
||||
// mapToSlice converts a map to a slice of the type mapValue and orders the slice by the keys in the map to ensure a
|
||||
// deterministic order.
|
||||
func mapToSlice(m map[string]any) []mapValue {
|
||||
values := make([]mapValue, 0, len(m))
|
||||
for k, v := range m {
|
||||
values = append(values, mapValue{K: k, V: v})
|
||||
}
|
||||
sort.Slice(values, func(i, j int) bool {
|
||||
return values[i].K < values[j].K
|
||||
})
|
||||
return values
|
||||
}
|
||||
|
||||
// mapValue represents a value in a map. It is used to convert maps to a slice and order the slice before encoding to
|
||||
// NBT to ensure a deterministic output.
|
||||
type mapValue struct {
|
||||
K string
|
||||
V any
|
||||
}
|
||||
|
||||
// writeEnchantments writes the enchantments of an item to a map for NBT encoding.
|
||||
func writeEnchantments(m map[string]any, s item.Stack) {
|
||||
if len(s.Enchantments()) != 0 {
|
||||
var enchantments []map[string]any
|
||||
for _, e := range s.Enchantments() {
|
||||
if eType, ok := item.EnchantmentID(e.Type()); ok {
|
||||
enchantments = append(enchantments, map[string]any{
|
||||
"id": int16(eType),
|
||||
"lvl": int16(e.Level()),
|
||||
})
|
||||
}
|
||||
}
|
||||
m["ench"] = enchantments
|
||||
}
|
||||
}
|
||||
|
||||
// writeDisplay writes the display name and lore of an item to a map for NBT encoding.
|
||||
func writeDisplay(m map[string]any, s item.Stack) {
|
||||
name, lore := s.CustomName(), s.Lore()
|
||||
v := map[string]any{}
|
||||
if name != "" {
|
||||
v["Name"] = name
|
||||
}
|
||||
if len(lore) != 0 {
|
||||
v["Lore"] = lore
|
||||
}
|
||||
if len(v) != 0 {
|
||||
m["display"] = v
|
||||
}
|
||||
}
|
||||
|
||||
// writeDamage writes the damage to an item.Stack (either an int16 for disk or int32 for network) to a map for NBT
|
||||
// encoding.
|
||||
func writeDamage(m map[string]any, s item.Stack, disk bool) {
|
||||
if v, ok := m["Damage"]; !ok || v.(int16) == 0 {
|
||||
if _, ok := s.Item().(item.Durable); ok {
|
||||
if disk {
|
||||
m["Damage"] = int16(s.MaxDurability() - s.Durability())
|
||||
} else {
|
||||
m["Damage"] = int32(s.MaxDurability() - s.Durability())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// writeAnvilCost ...
|
||||
func writeAnvilCost(m map[string]any, s item.Stack) {
|
||||
if cost := s.AnvilCost(); cost > 0 {
|
||||
m["RepairCost"] = int32(cost)
|
||||
}
|
||||
}
|
||||
|
||||
// writeUnbreakable writes the unbreakable tag to an item stack if it is unbreakable.
|
||||
func writeUnbreakable(m map[string]any, s item.Stack) {
|
||||
if s.Unbreakable() {
|
||||
m["Unbreakable"] = byte(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package packbuilder
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/png"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
_ "unsafe" // Imported for compiler directives.
|
||||
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
)
|
||||
|
||||
// buildBlocks builds all the block-related files for the resource pack. This includes textures, geometries, language
|
||||
// entries and terrain texture atlas.
|
||||
func buildBlocks(reg world.BlockRegistry, dir string) (count int, lang []string) {
|
||||
if err := os.MkdirAll(filepath.Join(dir, "models/blocks"), os.ModePerm); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(dir, "textures/blocks"), os.ModePerm); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
textureData := make(map[string]any)
|
||||
for identifier, blk := range reg.CustomBlocks() {
|
||||
b, ok := blk.(world.CustomBlockBuildable)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
name := strings.Split(identifier, ":")[1]
|
||||
lang = append(lang, fmt.Sprintf("tile.%s.name=%s", identifier, b.Name()))
|
||||
for name, texture := range b.Textures() {
|
||||
textureData[name] = map[string]string{"textures": "textures/blocks/" + name}
|
||||
buildBlockTexture(dir, name, texture)
|
||||
}
|
||||
if b.Geometry() != nil {
|
||||
if err := os.WriteFile(filepath.Join(dir, "models/blocks", fmt.Sprintf("%s.geo.json", name)), b.Geometry(), 0666); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
count++
|
||||
}
|
||||
|
||||
buildBlockAtlas(dir, map[string]any{
|
||||
"resource_pack_name": "vanilla",
|
||||
"texture_name": "atlas.terrain",
|
||||
"padding": 8,
|
||||
"num_mip_levels": 4,
|
||||
"texture_data": textureData,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// buildBlockTexture creates a PNG file for the block from the provided image and name and writes it to the pack.
|
||||
func buildBlockTexture(dir, name string, img image.Image) {
|
||||
texture, err := os.Create(filepath.Join(dir, fmt.Sprintf("textures/blocks/%s.png", name)))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := png.Encode(texture, img); err != nil {
|
||||
_ = texture.Close()
|
||||
panic(err)
|
||||
}
|
||||
if err := texture.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// buildBlockAtlas creates the identifier to texture mapping and writes it to the pack.
|
||||
func buildBlockAtlas(dir string, atlas map[string]any) {
|
||||
b, err := json.Marshal(atlas)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "textures/terrain_texture.json"), b, 0666); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package packbuilder
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"image"
|
||||
"image/png"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
_ "unsafe" // Imported for compiler directives.
|
||||
)
|
||||
|
||||
// buildItems builds all the item-related files for the resource pack. This includes textures, language
|
||||
// entries and item atlas.
|
||||
func buildItems(dir string) (count int, lang []string) {
|
||||
if err := os.Mkdir(filepath.Join(dir, "items"), os.ModePerm); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(dir, "textures/items"), os.ModePerm); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
textureData := make(map[string]any)
|
||||
for _, item := range world.CustomItems() {
|
||||
identifier, _ := item.EncodeItem()
|
||||
lang = append(lang, fmt.Sprintf("item.%s.name=%s", identifier, item.Name()))
|
||||
|
||||
name := strings.Split(identifier, ":")[1]
|
||||
textureData[identifier] = map[string]string{"textures": fmt.Sprintf("textures/items/%s.png", name)}
|
||||
|
||||
buildItemTexture(dir, name, item.Texture())
|
||||
|
||||
count++
|
||||
}
|
||||
|
||||
buildItemAtlas(dir, map[string]any{
|
||||
"resource_pack_name": "vanilla",
|
||||
"texture_name": "atlas.items",
|
||||
"texture_data": textureData,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// buildItemTexture creates a PNG file for the item from the provided image and name and writes it to the pack.
|
||||
func buildItemTexture(dir, name string, img image.Image) {
|
||||
texture, err := os.Create(filepath.Join(dir, "textures/items", name+".png"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := png.Encode(texture, img); err != nil {
|
||||
_ = texture.Close()
|
||||
panic(err)
|
||||
}
|
||||
if err := texture.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// buildItemAtlas creates the identifier to texture mapping and writes it to the pack.
|
||||
func buildItemAtlas(dir string, atlas map[string]any) {
|
||||
b, err := json.Marshal(atlas)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "textures/item_texture.json"), b, 0666); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package packbuilder
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// buildLanguageFile creates a lang file and writes all of the language entries to the pack.
|
||||
func buildLanguageFile(dir string, lang []string) {
|
||||
if err := os.Mkdir(filepath.Join(dir, "texts"), os.ModePerm); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "texts/en_US.lang"), []byte(strings.Join(lang, "\n")), 0666); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package packbuilder
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/google/uuid"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sandertv/gophertunnel/minecraft/resource"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// buildManifest creates a JSON manifest file for the client to be able to read the resource pack. It creates
|
||||
// basic information and writes it to the pack.
|
||||
func buildManifest(dir string, headerUUID, moduleUUID uuid.UUID) {
|
||||
m, err := json.Marshal(resource.Manifest{
|
||||
FormatVersion: 2,
|
||||
Header: resource.Header{
|
||||
Name: "dragonfly auto-generated resource pack",
|
||||
Description: "This resource pack contains auto-generated content from dragonfly",
|
||||
UUID: headerUUID,
|
||||
Version: [3]int{0, 0, 1},
|
||||
MinimumGameVersion: parseVersion(protocol.CurrentVersion),
|
||||
},
|
||||
Modules: []resource.Module{
|
||||
{
|
||||
UUID: moduleUUID.String(),
|
||||
Description: "This resource pack contains auto-generated content from dragonfly",
|
||||
Type: "resources",
|
||||
Version: [3]int{0, 0, 1},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "manifest.json"), m, 0666); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// parseVersion parses the version passed in the format of a.b.c as a [3]int.
|
||||
func parseVersion(ver string) [3]int {
|
||||
frag := strings.Split(ver, ".")
|
||||
if len(frag) != 3 {
|
||||
panic("invalid version number " + ver)
|
||||
}
|
||||
a, _ := strconv.ParseInt(frag[0], 10, 64)
|
||||
b, _ := strconv.ParseInt(frag[1], 10, 64)
|
||||
c, _ := strconv.ParseInt(frag[2], 10, 64)
|
||||
return [3]int{int(a), int(b), int(c)}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
@@ -0,0 +1,52 @@
|
||||
package packbuilder
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"os"
|
||||
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/sandertv/gophertunnel/minecraft/resource"
|
||||
"golang.org/x/mod/sumdb/dirhash"
|
||||
)
|
||||
|
||||
//go:embed pack_icon.png
|
||||
var packIcon []byte
|
||||
|
||||
// BuildResourcePack builds a resource pack based on custom features that have been registered to the server.
|
||||
// It creates a UUID based on the hash of the directory so the client will only be prompted to download it
|
||||
// once it is changed.
|
||||
func BuildResourcePack(reg world.BlockRegistry) (*resource.Pack, bool) {
|
||||
dir, err := os.MkdirTemp("", "dragonfly_resource_pack-")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
var assets int
|
||||
var lang []string
|
||||
|
||||
itemCount, itemLang := buildItems(dir)
|
||||
assets += itemCount
|
||||
lang = append(lang, itemLang...)
|
||||
|
||||
blockCount, blockLang := buildBlocks(reg, dir)
|
||||
assets += blockCount
|
||||
lang = append(lang, blockLang...)
|
||||
|
||||
if assets > 0 {
|
||||
buildLanguageFile(dir, lang)
|
||||
if err := os.WriteFile(dir+"/pack_icon.png", packIcon, 0666); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
hash, err := dirhash.HashDir(dir, "", dirhash.Hash1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var header, module [16]byte
|
||||
copy(header[:], hash)
|
||||
copy(module[:], hash[16:])
|
||||
buildManifest(dir, header, module)
|
||||
return resource.MustReadPath(dir), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package sliceutil
|
||||
|
||||
import "slices"
|
||||
|
||||
// Convert converts a slice of type B to a slice of type A. Convert panics if B
|
||||
// cannot be type asserted to type A.
|
||||
func Convert[A, B any, S ~[]B](v S) []A {
|
||||
a := make([]A, len(v))
|
||||
for i, b := range v {
|
||||
a[i] = (any)(b).(A)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// SearchValue iterates through slice v, calling function f for every element.
|
||||
// If true is returned in this function, the respective element is returned and
|
||||
// ok is true. If the function f does not return true for any element, false is
|
||||
// returned.
|
||||
func SearchValue[A any, S ~[]A](v S, f func(a A) bool) (a A, ok bool) {
|
||||
for _, val := range v {
|
||||
if f(val) {
|
||||
return val, true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Filter iterates over elements of collection, returning an array of all
|
||||
// elements function c returns true for.
|
||||
func Filter[E any](s []E, c func(E) bool) []E {
|
||||
a := make([]E, 0, len(s))
|
||||
for _, e := range s {
|
||||
if c(e) {
|
||||
a = append(a, e)
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// DeleteVal deletes the first occurrence of a value in a slice of the type E
|
||||
// and returns a new slice without the value.
|
||||
func DeleteVal[E any](s []E, v E) []E {
|
||||
for i, vs := range s {
|
||||
if (any)(v) == (any)(vs) {
|
||||
return slices.Delete(s, i, i+1)
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
Reference in New Issue
Block a user