mc root source folder cleanup
This commit is contained in:
+639
@@ -0,0 +1,639 @@
|
||||
package text
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/admin-else/strom/mc/nbt"
|
||||
)
|
||||
|
||||
// FIXME: this should use the mapstruct thing
|
||||
|
||||
// Component represents a Minecraft text component.
|
||||
// https://minecraft.wiki/w/Text_component_format
|
||||
type Component struct {
|
||||
// Common fields
|
||||
Bold *bool `json:"bold,omitempty" nbt:"bold,omitempty"`
|
||||
Italic *bool `json:"italic,omitempty" nbt:"italic,omitempty"`
|
||||
Underlined *bool `json:"underlined,omitempty" nbt:"underlined,omitempty"`
|
||||
Strikethrough *bool `json:"strikethrough,omitempty" nbt:"strikethrough,omitempty"`
|
||||
Obfuscated *bool `json:"obfuscated,omitempty" nbt:"obfuscated,omitempty"`
|
||||
Font string `json:"font,omitempty" nbt:"font,omitempty"`
|
||||
Color string `json:"color,omitempty" nbt:"color,omitempty"`
|
||||
Insertion string `json:"insertion,omitempty" nbt:"insertion,omitempty"`
|
||||
ClickEvent *ClickEvent `json:"clickEvent,omitempty" nbt:"clickEvent,omitempty"`
|
||||
HoverEvent *HoverEvent `json:"hoverEvent,omitempty" nbt:"hoverEvent,omitempty"`
|
||||
Extra []Component `json:"extra,omitempty" nbt:"extra,omitempty"`
|
||||
|
||||
// Content fields (one of these should be set)
|
||||
Text string `json:"text,omitempty" nbt:"text,omitempty"`
|
||||
Translate string `json:"translate,omitempty" nbt:"translate,omitempty"`
|
||||
With []Component `json:"with,omitempty" nbt:"with,omitempty"`
|
||||
Score *Score `json:"score,omitempty" nbt:"score,omitempty"`
|
||||
Selector string `json:"selector,omitempty" nbt:"selector,omitempty"`
|
||||
Keybind string `json:"keybind,omitempty" nbt:"keybind,omitempty"`
|
||||
NBT string `json:"nbt,omitempty" nbt:"nbt,omitempty"`
|
||||
Interpret bool `json:"interpret,omitempty" nbt:"interpret,omitempty"`
|
||||
Block string `json:"block,omitempty" nbt:"block,omitempty"`
|
||||
Entity string `json:"entity,omitempty" nbt:"entity,omitempty"`
|
||||
Storage string `json:"storage,omitempty" nbt:"storage,omitempty"`
|
||||
Separator *Component `json:"separator,omitempty" nbt:"separator,omitempty"`
|
||||
}
|
||||
|
||||
type ClickEvent struct {
|
||||
Action string `json:"action" nbt:"action"`
|
||||
Value string `json:"value" nbt:"value"`
|
||||
}
|
||||
|
||||
type HoverEvent struct {
|
||||
Action string `json:"action" nbt:"action"`
|
||||
Contents any `json:"contents,omitempty" nbt:"contents,omitempty"`
|
||||
Value any `json:"value,omitempty" nbt:"value,omitempty"` // Legacy
|
||||
}
|
||||
|
||||
type Score struct {
|
||||
Name string `json:"name" nbt:"name"`
|
||||
Objective string `json:"objective" nbt:"objective"`
|
||||
Value string `json:"value,omitempty" nbt:"value,omitempty"`
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (c *Component) MarshalJSON() ([]byte, error) {
|
||||
// If it's just a simple text component with no formatting, we can marshal it as a string
|
||||
if c.isSimpleText() {
|
||||
return json.Marshal(c.Text)
|
||||
}
|
||||
if c.hasFormattingButNoContent() {
|
||||
// just like component but text does not have omitempty
|
||||
type AliasWithText struct {
|
||||
Bold *bool `json:"bold,omitempty"`
|
||||
Italic *bool `json:"italic,omitempty"`
|
||||
Underlined *bool `json:"underlined,omitempty"`
|
||||
Strikethrough *bool `json:"strikethrough,omitempty"`
|
||||
Obfuscated *bool `json:"obfuscated,omitempty"`
|
||||
Font string `json:"font,omitempty"`
|
||||
Color string `json:"color,omitempty"`
|
||||
Insertion string `json:"insertion,omitempty"`
|
||||
ClickEvent *ClickEvent `json:"clickEvent,omitempty"`
|
||||
HoverEvent *HoverEvent `json:"hoverEvent,omitempty"`
|
||||
Extra []Component `json:"extra,omitempty"`
|
||||
|
||||
Text string `json:"text"`
|
||||
|
||||
Translate string `json:"translate,omitempty"`
|
||||
With []Component `json:"with,omitempty"`
|
||||
Score *Score `json:"score,omitempty"`
|
||||
Selector string `json:"selector,omitempty"`
|
||||
Keybind string `json:"keybind,omitempty"`
|
||||
NBT string `json:"nbt,omitempty"`
|
||||
Interpret bool `json:"interpret,omitempty"`
|
||||
Block string `json:"block,omitempty"`
|
||||
Entity string `json:"entity,omitempty"`
|
||||
Storage string `json:"storage,omitempty"`
|
||||
Separator *Component `json:"separator,omitempty"`
|
||||
}
|
||||
return json.Marshal(AliasWithText(*c))
|
||||
}
|
||||
type Alias Component
|
||||
|
||||
return json.Marshal(Alias(*c))
|
||||
}
|
||||
|
||||
// hasFormattingButNoContent checks if the component has formatting/styling but no content fields
|
||||
func (c *Component) hasFormattingButNoContent() bool {
|
||||
hasFormatting := c.Bold != nil || c.Italic != nil || c.Underlined != nil ||
|
||||
c.Strikethrough != nil || c.Obfuscated != nil || c.Font != "" ||
|
||||
c.Color != "" || c.Insertion != "" || c.ClickEvent != nil ||
|
||||
c.HoverEvent != nil || len(c.Extra) > 0 || c.Separator != nil
|
||||
|
||||
hasContent := c.Text != "" || c.Translate != "" || len(c.With) > 0 ||
|
||||
c.Score != nil || c.Selector != "" || c.Keybind != "" ||
|
||||
c.NBT != "" || c.Block != "" || c.Entity != "" || c.Storage != ""
|
||||
|
||||
return hasFormatting && !hasContent
|
||||
}
|
||||
|
||||
func (c *Component) isSimpleText() bool {
|
||||
return c.Text != "" &&
|
||||
c.Bold == nil && c.Italic == nil && c.Underlined == nil && c.Strikethrough == nil && c.Obfuscated == nil &&
|
||||
c.Font == "" && c.Color == "" && c.Insertion == "" && c.ClickEvent == nil && c.HoverEvent == nil &&
|
||||
len(c.Extra) == 0 && c.Translate == "" && c.Score == nil && c.Selector == "" && c.Keybind == "" &&
|
||||
c.NBT == "" && !c.Interpret && c.Block == "" && c.Entity == "" && c.Storage == "" && c.Separator == nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (c *Component) UnmarshalJSON(data []byte) error {
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Try string
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err == nil {
|
||||
c.Text = s
|
||||
return nil
|
||||
}
|
||||
|
||||
// Try array
|
||||
var arr []Component
|
||||
if err := json.Unmarshal(data, &arr); err == nil {
|
||||
if len(arr) > 0 {
|
||||
*c = arr[0]
|
||||
if len(arr) > 1 {
|
||||
c.Extra = append(c.Extra, arr[1:]...)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Try object
|
||||
type Alias Component
|
||||
var aux Alias
|
||||
if err := json.Unmarshal(data, &aux); err != nil {
|
||||
return err
|
||||
}
|
||||
*c = Component(aux)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToNBT converts the component to a format suitable for the nbt package.
|
||||
func (c *Component) ToNBT() any {
|
||||
m := make(map[string]any)
|
||||
if c.Bold != nil {
|
||||
m["bold"] = boolToByte(*c.Bold)
|
||||
}
|
||||
if c.Italic != nil {
|
||||
m["italic"] = boolToByte(*c.Italic)
|
||||
}
|
||||
if c.Underlined != nil {
|
||||
m["underlined"] = boolToByte(*c.Underlined)
|
||||
}
|
||||
if c.Strikethrough != nil {
|
||||
m["strikethrough"] = boolToByte(*c.Strikethrough)
|
||||
}
|
||||
if c.Obfuscated != nil {
|
||||
m["obfuscated"] = boolToByte(*c.Obfuscated)
|
||||
}
|
||||
if c.Font != "" {
|
||||
m["font"] = c.Font
|
||||
}
|
||||
if c.Color != "" {
|
||||
m["color"] = c.Color
|
||||
}
|
||||
if c.Insertion != "" {
|
||||
m["insertion"] = c.Insertion
|
||||
}
|
||||
if c.ClickEvent != nil {
|
||||
m["clickEvent"] = map[string]any{
|
||||
"action": c.ClickEvent.Action,
|
||||
"value": c.ClickEvent.Value,
|
||||
}
|
||||
}
|
||||
if c.HoverEvent != nil {
|
||||
he := map[string]any{
|
||||
"action": c.HoverEvent.Action,
|
||||
}
|
||||
if c.HoverEvent.Contents != nil {
|
||||
he["contents"] = c.HoverEvent.Contents
|
||||
}
|
||||
if c.HoverEvent.Value != nil {
|
||||
he["value"] = c.HoverEvent.Value
|
||||
}
|
||||
m["hoverEvent"] = he
|
||||
}
|
||||
if len(c.Extra) > 0 {
|
||||
extra := make([]any, len(c.Extra))
|
||||
for i, e := range c.Extra {
|
||||
extra[i] = e.ToNBT()
|
||||
}
|
||||
m["extra"] = extra
|
||||
}
|
||||
|
||||
if c.Text != "" {
|
||||
m["text"] = c.Text
|
||||
}
|
||||
if c.Translate != "" {
|
||||
m["translate"] = c.Translate
|
||||
}
|
||||
if len(c.With) > 0 {
|
||||
with := make([]any, len(c.With))
|
||||
for i, e := range c.With {
|
||||
with[i] = e.ToNBT()
|
||||
}
|
||||
m["with"] = with
|
||||
}
|
||||
if c.Score != nil {
|
||||
s := map[string]any{
|
||||
"name": c.Score.Name,
|
||||
"objective": c.Score.Objective,
|
||||
}
|
||||
if c.Score.Value != "" {
|
||||
s["value"] = c.Score.Value
|
||||
}
|
||||
m["score"] = s
|
||||
}
|
||||
if c.Selector != "" {
|
||||
m["selector"] = c.Selector
|
||||
}
|
||||
if c.Keybind != "" {
|
||||
m["keybind"] = c.Keybind
|
||||
}
|
||||
if c.NBT != "" {
|
||||
m["nbt"] = c.NBT
|
||||
}
|
||||
if c.Interpret {
|
||||
m["interpret"] = boolToByte(c.Interpret)
|
||||
}
|
||||
if c.Block != "" {
|
||||
m["block"] = c.Block
|
||||
}
|
||||
if c.Entity != "" {
|
||||
m["entity"] = c.Entity
|
||||
}
|
||||
if c.Storage != "" {
|
||||
m["storage"] = c.Storage
|
||||
}
|
||||
if c.Separator != nil {
|
||||
m["separator"] = c.Separator.ToNBT()
|
||||
}
|
||||
|
||||
if c.hasFormattingButNoContent() {
|
||||
m["text"] = ""
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func boolToByte(b bool) int8 {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// FromNBT populates the component from an NBT-like structure (map[string]any or string).
|
||||
func (c *Component) FromNBT(v any) error {
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
c.Text = v
|
||||
return nil
|
||||
case map[string]any:
|
||||
if val, ok := v["bold"]; ok {
|
||||
c.Bold = ptr(byteToBool(val))
|
||||
}
|
||||
if val, ok := v["italic"]; ok {
|
||||
c.Italic = ptr(byteToBool(val))
|
||||
}
|
||||
if val, ok := v["underlined"]; ok {
|
||||
c.Underlined = ptr(byteToBool(val))
|
||||
}
|
||||
if val, ok := v["strikethrough"]; ok {
|
||||
c.Strikethrough = ptr(byteToBool(val))
|
||||
}
|
||||
if val, ok := v["obfuscated"]; ok {
|
||||
c.Obfuscated = ptr(byteToBool(val))
|
||||
}
|
||||
if val, ok := v["font"].(string); ok {
|
||||
c.Font = val
|
||||
}
|
||||
if val, ok := v["color"].(string); ok {
|
||||
c.Color = val
|
||||
}
|
||||
if val, ok := v["insertion"].(string); ok {
|
||||
c.Insertion = val
|
||||
}
|
||||
if val, ok := v["clickEvent"].(map[string]any); ok {
|
||||
c.ClickEvent = &ClickEvent{}
|
||||
if a, ok := val["action"].(string); ok {
|
||||
c.ClickEvent.Action = a
|
||||
}
|
||||
if v, ok := val["value"].(string); ok {
|
||||
c.ClickEvent.Value = v
|
||||
}
|
||||
}
|
||||
if val, ok := v["hoverEvent"].(map[string]any); ok {
|
||||
c.HoverEvent = &HoverEvent{}
|
||||
if a, ok := val["action"].(string); ok {
|
||||
c.HoverEvent.Action = a
|
||||
}
|
||||
if co, ok := val["contents"]; ok {
|
||||
c.HoverEvent.Contents = co
|
||||
}
|
||||
if v, ok := val["value"]; ok {
|
||||
c.HoverEvent.Value = v
|
||||
}
|
||||
}
|
||||
if val, ok := v["extra"].([]any); ok {
|
||||
c.Extra = make([]Component, len(val))
|
||||
for i, e := range val {
|
||||
if err := c.Extra[i].FromNBT(e); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if val, ok := v["text"].(string); ok {
|
||||
c.Text = val
|
||||
}
|
||||
if val, ok := v["translate"].(string); ok {
|
||||
c.Translate = val
|
||||
}
|
||||
if val, ok := v["with"].([]any); ok {
|
||||
c.With = make([]Component, len(val))
|
||||
for i, e := range val {
|
||||
if err := c.With[i].FromNBT(e); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if val, ok := v["score"].(map[string]any); ok {
|
||||
c.Score = &Score{}
|
||||
if n, ok := val["name"].(string); ok {
|
||||
c.Score.Name = n
|
||||
}
|
||||
if o, ok := val["objective"].(string); ok {
|
||||
c.Score.Objective = o
|
||||
}
|
||||
if v, ok := val["value"].(string); ok {
|
||||
c.Score.Value = v
|
||||
}
|
||||
}
|
||||
if val, ok := v["selector"].(string); ok {
|
||||
c.Selector = val
|
||||
}
|
||||
if val, ok := v["keybind"].(string); ok {
|
||||
c.Keybind = val
|
||||
}
|
||||
if val, ok := v["nbt"].(string); ok {
|
||||
c.NBT = val
|
||||
}
|
||||
if val, ok := v["interpret"]; ok {
|
||||
c.Interpret = byteToBool(val)
|
||||
}
|
||||
if val, ok := v["block"].(string); ok {
|
||||
c.Block = val
|
||||
}
|
||||
if val, ok := v["entity"].(string); ok {
|
||||
c.Entity = val
|
||||
}
|
||||
if val, ok := v["storage"].(string); ok {
|
||||
c.Storage = val
|
||||
}
|
||||
if val, ok := v["separator"]; ok {
|
||||
c.Separator = &Component{}
|
||||
if err := c.Separator.FromNBT(val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unsupported NBT type for component: %T", v)
|
||||
}
|
||||
}
|
||||
|
||||
func byteToBool(v any) bool {
|
||||
switch v := v.(type) {
|
||||
case int8:
|
||||
return v != 0
|
||||
case int32:
|
||||
return v != 0
|
||||
case bool:
|
||||
return v
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func ptr[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Pretty parses a legacy formatted string into a Component.
|
||||
// It uses the § symbol for color and formatting codes.
|
||||
func Pretty(s string) *Component {
|
||||
parts := strings.Split(s, "§")
|
||||
|
||||
root := Component{Text: parts[0]}
|
||||
|
||||
var current = &root
|
||||
for i := 1; i < len(parts); i++ {
|
||||
part := parts[i]
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
code := part[0]
|
||||
text := part[1:]
|
||||
|
||||
newComp := Component{}
|
||||
// Inherit formatting from current if it's just a style change
|
||||
if current != &root {
|
||||
newComp.Bold = current.Bold
|
||||
newComp.Italic = current.Italic
|
||||
newComp.Underlined = current.Underlined
|
||||
newComp.Strikethrough = current.Strikethrough
|
||||
newComp.Obfuscated = current.Obfuscated
|
||||
newComp.Color = current.Color
|
||||
}
|
||||
|
||||
switch code {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f':
|
||||
newComp.Color = codeToColor[code]
|
||||
// Colors reset formatting
|
||||
newComp.Bold = nil
|
||||
newComp.Italic = nil
|
||||
newComp.Underlined = nil
|
||||
newComp.Strikethrough = nil
|
||||
newComp.Obfuscated = nil
|
||||
case 'k':
|
||||
newComp.Obfuscated = ptr(true)
|
||||
case 'l':
|
||||
newComp.Bold = ptr(true)
|
||||
case 'm':
|
||||
newComp.Strikethrough = ptr(true)
|
||||
case 'n':
|
||||
newComp.Underlined = ptr(true)
|
||||
case 'o':
|
||||
newComp.Italic = ptr(true)
|
||||
case 'r':
|
||||
newComp = Component{} // Reset
|
||||
case 'x':
|
||||
// Hex color §x§r§r§g§g§b§b
|
||||
if i+6 < len(parts) {
|
||||
hex := "#"
|
||||
for j := 1; j <= 6; j++ {
|
||||
if len(parts[i+j]) > 0 {
|
||||
hex += string(parts[i+j][0])
|
||||
}
|
||||
}
|
||||
newComp.Color = hex
|
||||
i += 6
|
||||
// Hex colors also reset formatting
|
||||
newComp.Bold = nil
|
||||
newComp.Italic = nil
|
||||
newComp.Underlined = nil
|
||||
newComp.Strikethrough = nil
|
||||
newComp.Obfuscated = nil
|
||||
}
|
||||
}
|
||||
|
||||
if text != "" {
|
||||
newComp.Text = text
|
||||
root.Extra = append(root.Extra, newComp)
|
||||
current = &root.Extra[len(root.Extra)-1]
|
||||
} else {
|
||||
// Just a formatting code, update current state for next parts
|
||||
current = &newComp
|
||||
}
|
||||
}
|
||||
|
||||
return &root
|
||||
}
|
||||
|
||||
// PrettyF returns a Component from a formatted string.
|
||||
func PrettyF(format string, args ...any) *Component {
|
||||
s := fmt.Sprintf(format, args...)
|
||||
return Pretty(s)
|
||||
}
|
||||
|
||||
func replacePlaceholders(s string, values map[string]any) string {
|
||||
result := s
|
||||
for key, val := range values {
|
||||
formattedPrefix := "§%" + key + ":"
|
||||
for {
|
||||
idxFormatted := strings.Index(result, formattedPrefix)
|
||||
if idxFormatted == -1 {
|
||||
break
|
||||
}
|
||||
endIdx := strings.Index(result[idxFormatted+len(formattedPrefix):], "%")
|
||||
if endIdx == -1 {
|
||||
break
|
||||
}
|
||||
formatStr := result[idxFormatted+len(formattedPrefix) : idxFormatted+len(formattedPrefix)+endIdx]
|
||||
replaceWith := fmt.Sprintf("%"+formatStr, val)
|
||||
result = result[:idxFormatted] + replaceWith + result[idxFormatted+len(formattedPrefix)+endIdx+1:]
|
||||
}
|
||||
placeholder := "§%" + key + "%"
|
||||
for {
|
||||
idx := strings.Index(result, placeholder)
|
||||
if idx == -1 {
|
||||
break
|
||||
}
|
||||
replaceWith := fmt.Sprintf("%v", val)
|
||||
result = result[:idx] + replaceWith + result[idx+len(placeholder):]
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func PrettyPlaceholders(format string, placeholders map[string]any) *Component {
|
||||
s := replacePlaceholders(format, placeholders)
|
||||
return Pretty(s)
|
||||
}
|
||||
|
||||
var colorToCode = map[string]byte{
|
||||
"black": '0',
|
||||
"dark_blue": '1',
|
||||
"dark_green": '2',
|
||||
"dark_aqua": '3',
|
||||
"dark_red": '4',
|
||||
"dark_purple": '5',
|
||||
"gold": '6',
|
||||
"gray": '7',
|
||||
"dark_gray": '8',
|
||||
"blue": '9',
|
||||
"green": 'a',
|
||||
"aqua": 'b',
|
||||
"red": 'c',
|
||||
"light_purple": 'd',
|
||||
"yellow": 'e',
|
||||
"white": 'f',
|
||||
}
|
||||
|
||||
var codeToColor = map[byte]string{
|
||||
'0': "black",
|
||||
'1': "dark_blue",
|
||||
'2': "dark_green",
|
||||
'3': "dark_aqua",
|
||||
'4': "dark_red",
|
||||
'5': "dark_purple",
|
||||
'6': "gold",
|
||||
'7': "gray",
|
||||
'8': "dark_gray",
|
||||
'9': "blue",
|
||||
'a': "green",
|
||||
'b': "aqua",
|
||||
'c': "red",
|
||||
'd': "light_purple",
|
||||
'e': "yellow",
|
||||
'f': "white",
|
||||
}
|
||||
|
||||
func (c *Component) String() string {
|
||||
var sb strings.Builder
|
||||
c.writePretty(&sb)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (c *Component) writePretty(sb *strings.Builder) {
|
||||
if c.Color != "" {
|
||||
if code, ok := colorToCode[strings.ToLower(c.Color)]; ok {
|
||||
sb.WriteString("§")
|
||||
sb.WriteByte(code)
|
||||
} else if strings.HasPrefix(c.Color, "#") && len(c.Color) == 7 {
|
||||
// Hex color: §x§r§r§g§g§b§b
|
||||
sb.WriteString("§x")
|
||||
for _, char := range c.Color[1:] {
|
||||
sb.WriteString("§")
|
||||
sb.WriteRune(char)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c.Bold != nil && *c.Bold {
|
||||
sb.WriteString("§l")
|
||||
}
|
||||
if c.Italic != nil && *c.Italic {
|
||||
sb.WriteString("§o")
|
||||
}
|
||||
if c.Underlined != nil && *c.Underlined {
|
||||
sb.WriteString("§n")
|
||||
}
|
||||
if c.Strikethrough != nil && *c.Strikethrough {
|
||||
sb.WriteString("§m")
|
||||
}
|
||||
if c.Obfuscated != nil && *c.Obfuscated {
|
||||
sb.WriteString("§k")
|
||||
}
|
||||
|
||||
if c.Text != "" {
|
||||
sb.WriteString(c.Text)
|
||||
}
|
||||
|
||||
if c.Translate != "" {
|
||||
// This is a simplified version of translation handling
|
||||
// In a real client, this would look up the translation key
|
||||
sb.WriteString(c.Translate)
|
||||
sb.WriteRune('(')
|
||||
for _, w := range c.With {
|
||||
w.writePretty(sb)
|
||||
sb.WriteByte(',')
|
||||
}
|
||||
sb.WriteRune(')')
|
||||
}
|
||||
|
||||
for _, extra := range c.Extra {
|
||||
extra.writePretty(sb)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Component) AsNBT() nbt.Anon {
|
||||
return nbt.Anon{Value: c.ToNBT()}
|
||||
}
|
||||
|
||||
func (c *Component) AsOldNBT() nbt.Tag {
|
||||
return nbt.Tag{
|
||||
Name: "",
|
||||
Value: c.ToNBT(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
package text
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/admin-else/strom/mc/nbt"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestComponent_JSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
json string
|
||||
want Component
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "simple text",
|
||||
json: `"hello"`,
|
||||
want: Component{Text: "hello"},
|
||||
},
|
||||
{
|
||||
name: "object text",
|
||||
json: `{"text":"hello","bold":true}`,
|
||||
want: Component{Text: "hello", Bold: ptr(true)},
|
||||
},
|
||||
{
|
||||
name: "array of components",
|
||||
json: `["hello", {"text":" world","color":"red"}]`,
|
||||
want: Component{
|
||||
Text: "hello",
|
||||
Extra: []Component{
|
||||
{Text: " world", Color: "red"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "complex translation",
|
||||
json: `{"translate":"chat.type.text","with":["Player",{"text":"hello","italic":true}]}`,
|
||||
want: Component{
|
||||
Translate: "chat.type.text",
|
||||
With: []Component{
|
||||
{Text: "Player"},
|
||||
{Text: "hello", Italic: ptr(true)},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var got Component
|
||||
if err := json.Unmarshal([]byte(tt.json), &got); (err != nil) != tt.wantErr {
|
||||
t.Fatalf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("UnmarshalJSON() got = %+v, want %+v", got, tt.want)
|
||||
}
|
||||
|
||||
marshaled, err := json.Marshal(got)
|
||||
if err != nil {
|
||||
t.Fatalf("MarshalJSON() error = %v", err)
|
||||
}
|
||||
|
||||
var got2 Component
|
||||
if err := json.Unmarshal(marshaled, &got2); err != nil {
|
||||
t.Fatalf("UnmarshalJSON() back error = %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(got2, tt.want) {
|
||||
t.Errorf("Roundtrip failed: got = %+v, want %+v", got2, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestComponent_NBT(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
nbt any
|
||||
want Component
|
||||
}{
|
||||
{
|
||||
name: "simple text",
|
||||
nbt: "hello",
|
||||
want: Component{Text: "hello"},
|
||||
},
|
||||
{
|
||||
name: "object text",
|
||||
nbt: map[string]any{
|
||||
"text": "hello",
|
||||
"bold": int8(1),
|
||||
},
|
||||
want: Component{Text: "hello", Bold: ptr(true)},
|
||||
},
|
||||
{
|
||||
name: "extra components",
|
||||
nbt: map[string]any{
|
||||
"text": "hello",
|
||||
"extra": []any{
|
||||
map[string]any{"text": " world", "color": "red"},
|
||||
},
|
||||
},
|
||||
want: Component{
|
||||
Text: "hello",
|
||||
Extra: []Component{
|
||||
{Text: " world", Color: "red"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var got Component
|
||||
if err := got.FromNBT(tt.nbt); err != nil {
|
||||
t.Fatalf("FromNBT() error = %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("FromNBT() got = %+v, want %+v", got, tt.want)
|
||||
}
|
||||
|
||||
nbtVal := got.ToNBT()
|
||||
var got2 Component
|
||||
if err := got2.FromNBT(nbtVal); err != nil {
|
||||
t.Fatalf("FromNBT() back error = %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(got2, tt.want) {
|
||||
t.Errorf("Roundtrip failed: got = %+v, want %+v", got2, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPretty(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
s string
|
||||
want Component
|
||||
}{
|
||||
{
|
||||
name: "simple text",
|
||||
s: "Hello",
|
||||
want: Component{Text: "Hello"},
|
||||
},
|
||||
{
|
||||
name: "color and bold",
|
||||
s: "§c§lHello",
|
||||
want: Component{
|
||||
Extra: []Component{
|
||||
{Text: "Hello", Color: "red", Bold: ptr(true)},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := Pretty(tt.s)
|
||||
if got.Text != tt.want.Text {
|
||||
t.Errorf("Pretty().Text = %v, want %v", got.Text, tt.want.Text)
|
||||
}
|
||||
if !reflect.DeepEqual(got.Extra, tt.want.Extra) {
|
||||
t.Errorf("Pretty().Extra = %+v, want %+v", got.Extra, tt.want.Extra)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrettyF(t *testing.T) {
|
||||
got := PrettyF("Hello §b%s", "World")
|
||||
want := &Component{
|
||||
Text: "Hello ",
|
||||
Extra: []Component{
|
||||
{Text: "World", Color: "aqua"},
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("PrettyF() = \n%+v\n, want \n%+v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComponent_String(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
comp Component
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "simple text",
|
||||
comp: Component{Text: "Hello"},
|
||||
want: "Hello",
|
||||
},
|
||||
{
|
||||
name: "color and bold",
|
||||
comp: Component{
|
||||
Extra: []Component{
|
||||
{Text: "Hello", Color: "red", Bold: ptr(true)},
|
||||
},
|
||||
},
|
||||
want: "§c§lHello",
|
||||
},
|
||||
{
|
||||
name: "hex color",
|
||||
comp: Component{
|
||||
Color: "#ff0000",
|
||||
Text: "Red",
|
||||
},
|
||||
want: "§x§f§f§0§0§0§0Red",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.comp.String(); got != tt.want {
|
||||
t.Errorf("Component.String() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrettyToNbt(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
f string
|
||||
want nbt.Anon
|
||||
}{
|
||||
{
|
||||
name: "real data",
|
||||
f: `§dno gooning`,
|
||||
want: nbt.Anon{Value: map[string]interface{}{"extra": []interface{}{map[string]interface{}{"color": "light_purple", "text": "no gooning"}}, "text": ""}},
|
||||
},
|
||||
{
|
||||
name: "reset thing",
|
||||
f: "§3Any§bProtect§3 » §rffff",
|
||||
want: nbt.Anon{Value: map[string]interface{}{"extra": []interface{}{map[string]interface{}{"color": "dark_aqua", "text": "Any"}, map[string]interface{}{"color": "aqua", "text": "Protect"}, map[string]interface{}{"color": "dark_aqua", "text": " » "}, map[string]interface{}{"text": "ffff"}}, "text": ""}},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := Pretty(tt.f).AsNBT()
|
||||
b := tt.want
|
||||
if !cmp.Equal(a, b) {
|
||||
t.Errorf("PrettyToNbt() = %#v, want %#v", a, b)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user