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

This commit is contained in:
2026-07-09 08:33:57 +08:00
commit 26ed99fda6
845 changed files with 75419 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
package skin
import (
"image"
"image/color"
)
const (
// AnimationHead is an animation that is played over the head part of the skin.
AnimationHead AnimationType = iota
// AnimationBody32x32 is an animation that is played over the body of a skin with a 32x32(/64) size. This
// is the usual animation type for body animations.
AnimationBody32x32
// AnimationBody128x128 is an animation that is played over a body of a skin with a 128x128 size. This is
// the animation type for body animations with high resolution.
AnimationBody128x128
)
// AnimationType represents a type of the animation. It is one of the constants above, and specifies to what
// part of the body it is assigned.
type AnimationType int
// Animation represents an animation that plays over the skin every so often. It is assigned to a particular
// part of the skin, which is represented by one of the constants above.
type Animation struct {
w, h int
aType AnimationType
// Pix holds skin data for every frame of the animation. This is an RGBA byte slice, meaning that every
// first byte is a Red value, the second a Green value, the third a Blue value and the fourth an Alpha
// value.
Pix []uint8
// FrameCount is the amount of frames that the animation plays for. Exactly this amount of frames should
// be present in the Pix animation data.
FrameCount int
// AnimationExpression is the player's animation expression.
AnimationExpression int
}
// NewAnimation returns a new animation using the width and height passed, with the type specifying what part
// of the body to display it on.
// NewAnimation fills out the Pix field adequately and sets FrameCount to 1 by default.
func NewAnimation(width, height int, expression int, animationType AnimationType) Animation {
return Animation{
w: width,
h: height,
aType: animationType,
Pix: make([]uint8, width*height*4),
FrameCount: 1,
AnimationExpression: expression,
}
}
// Type returns the type of the animation, which is one of the constants above.
func (a Animation) Type() AnimationType {
return a.aType
}
// ColorModel ...
func (a Animation) ColorModel() color.Model {
return color.RGBAModel
}
// Bounds ...
func (a Animation) Bounds() image.Rectangle {
return image.Rectangle{
Max: image.Point{X: a.w, Y: a.h},
}
}
// At returns the colour at a given position in the animation data, provided the X and Y are within the bounds
// of the animation passed during construction.
// The concrete type returned by At is a color.RGBA value.
func (a Animation) At(x, y int) color.Color {
if x < 0 || y < 0 || x >= a.w || y >= a.h {
panic("pixel coordinates out of bounds")
}
offset := x*4 + a.w*y*4
return color.RGBA{
R: a.Pix[offset],
G: a.Pix[offset+1],
B: a.Pix[offset+2],
A: a.Pix[offset+3],
}
}
+52
View File
@@ -0,0 +1,52 @@
package skin
import (
"image"
"image/color"
)
// Cape represents the cape that a skin may additionally have. A skin is of a fixed size (always 32x64 bytes)
// and may be either empty or of that size.
type Cape struct {
w, h int
// Pix holds the colour data of the cape in an RGBA byte array, similarly to the way that the pixels of
// a Skin are stored.
// The size of Pix is always 32 * 64 * 4 bytes.
Pix []uint8
}
// NewCape initialises a new Cape using the width and height passed. The pixels are pre-allocated so that the
// Cape may be used immediately.
func NewCape(width, height int) Cape {
return Cape{w: width, h: height, Pix: make([]uint8, width*height*4)}
}
// ColorModel ...
func (c Cape) ColorModel() color.Model {
return color.RGBAModel
}
// Bounds returns the bounds of the cape, which is always 32x64 or 0x0, depending on if the cape has any data
// in it.
func (c Cape) Bounds() image.Rectangle {
return image.Rectangle{
Max: image.Point{X: c.w, Y: c.h},
}
}
// At returns the colour at a given position in the cape, provided the X and Y are within the bounds of the
// cape passed during construction.
// The concrete type returned by At is a color.RGBA value.
func (c Cape) At(x, y int) color.Color {
if x < 0 || y < 0 || x >= c.w || y >= c.h {
panic("pixel coordinates out of bounds")
}
offset := x*4 + c.w*y*4
return color.RGBA{
R: c.Pix[offset],
G: c.Pix[offset+1],
B: c.Pix[offset+2],
A: c.Pix[offset+3],
}
}
+37
View File
@@ -0,0 +1,37 @@
package skin
import (
"encoding/json"
)
// ModelConfig specifies the way that the model (geometry data) is used to form the complete skin. It does
// this by setting model names for specific keys found in the struct.
type ModelConfig struct {
// Default is the 'default' model to use. This model is essentially the model of the skin that will be
// used at all times, when nothing special is being done. (For example, an animation)
// The field holds the name of one of the models present in the JSON of the skin's model.
// This field should always be filled out.
Default string `json:"default"`
// AnimatedFace is the model of an animation played over the face. This field should be set if the model
// contains the model of an animation, in which case this field should hold the name of that model.
AnimatedFace string `json:"animated_face,omitempty"`
}
// modelConfigContainer is a container of the model config data when encoded.
type modelConfigContainer struct {
Geometry ModelConfig `json:"geometry"`
}
// Encode encodes a ModelConfig into its JSON representation.
func (cfg ModelConfig) Encode() []byte {
b, _ := json.Marshal(modelConfigContainer{Geometry: cfg})
return b
}
// DecodeModelConfig attempts to decode a ModelConfig from the JSON data passed. If not successful, an error
// is returned.
func DecodeModelConfig(b []byte) (ModelConfig, error) {
var m modelConfigContainer
err := json.Unmarshal(b, &m)
return m.Geometry, err
}
+77
View File
@@ -0,0 +1,77 @@
package skin
import (
"image"
"image/color"
)
// Skin holds the data of a skin that a player has equipped. It includes geometry data, the texture and the
// cape, if one is present.
// Skin implements the image.Image interface to ease working with the value as an image.
type Skin struct {
w, h int
// Persona specifies if the skin uses the persona skin system.
Persona bool
PlayFabID string
FullID string
// Pix holds the raw pixel data of the skin. This is an RGBA byte slice, meaning that every first byte is
// a Red value, the second a Green value, the third a Blue value and the fourth an Alpha value.
Pix []uint8
// ModelConfig specifies how the Model field below should be used to form the total skin.
ModelConfig ModelConfig
// Model holds the raw JSON data that represents the model of the skin. If empty, it means the skin holds
// the standard skin data (geometry.humanoid).
// TODO: Write a full API for this. The model should be able to be easily modified or created runtime.
Model []byte
// Cape holds the cape of the skin. By default, an empty cape is set in the skin. Cape.Exists() may be
// called to check if the cape actually has any data.
Cape Cape
// Animations holds a list of all animations that the skin has. These animations must be pointed to in the
// ModelConfig, in order to display them on the skin.
Animations []Animation
}
// New creates a new skin using the width and height passed. The dimensions passed must be either 64x32,
// 64x64 or 128x128. An error is returned if other dimensions are used.
// The skin pixels are initialised for the skin, and a random skin ID is picked. The model name and model is
// left empty.
func New(width, height int) Skin {
return Skin{
w: width,
h: height,
Pix: make([]uint8, width*height*4),
}
}
// Bounds returns the bounds of the skin. These are either 64x32, 64x64 or 128, depending on the bounds of the
// skin of the player.
func (s Skin) Bounds() image.Rectangle {
return image.Rectangle{
Max: image.Point{X: s.w, Y: s.h},
}
}
// ColorModel returns color.RGBAModel.
func (s Skin) ColorModel() color.Model {
return color.RGBAModel
}
// At returns the colour at a given position in the skin. The concrete value of the colour returned is a color.RGBA
// value.
// If the x or y values exceed the bounds of the skin, At will panic.
func (s Skin) At(x, y int) color.Color {
if x < 0 || y < 0 || x >= s.w || y >= s.h {
panic("pixel coordinates out of bounds")
}
offset := x*4 + s.w*y*4
return color.RGBA{
R: s.Pix[offset],
G: s.Pix[offset+1],
B: s.Pix[offset+2],
A: s.Pix[offset+3],
}
}