mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-06-03 14:30:42 +00:00
WIP: Add checkboxes, de-lint ecs branch (#1017)
* Add checkboxes, checkbox test scene * De-lint ecs branch
This commit is contained in:
parent
3731e631cf
commit
0dee6518b4
|
@ -1,10 +1,12 @@
|
|||
package d2app
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2systems"
|
||||
"github.com/gravestench/akara"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2systems"
|
||||
)
|
||||
|
||||
// Run initializes the ECS framework
|
||||
func Run() {
|
||||
cfg := akara.NewWorldConfig().With(&d2systems.AppBootstrap{})
|
||||
akara.NewWorld(cfg)
|
||||
|
|
|
@ -10,10 +10,8 @@ func Deconstruct(r *Rectangle, to []*point.Point) []*point.Point {
|
|||
to = make([]*point.Point, 0)
|
||||
}
|
||||
|
||||
to = append(to, point.New(r.X, r.Y))
|
||||
to = append(to, point.New(r.Right(), r.Y))
|
||||
to = append(to, point.New(r.Right(), r.Bottom()))
|
||||
to = append(to, point.New(r.X, r.Bottom()))
|
||||
to = append(to, point.New(r.X, r.Y), point.New(r.Right(), r.Y),
|
||||
point.New(r.Right(), r.Bottom()), point.New(r.X, r.Bottom()))
|
||||
|
||||
return to
|
||||
}
|
||||
|
|
4
d2common/d2geom/rectangle/doc.go
Normal file
4
d2common/d2geom/rectangle/doc.go
Normal file
|
@ -0,0 +1,4 @@
|
|||
// Package rectangle provides an abstraction of a rectangle in 2D space,
|
||||
// with methods for performing various operations and comparisons
|
||||
// between different Rectangles.
|
||||
package rectangle
|
|
@ -1,6 +1,6 @@
|
|||
package rectangle
|
||||
|
||||
// Adjusts rectangle `a`, changing its width, height and position,
|
||||
// FitInside Adjusts rectangle `a`, changing its width, height and position,
|
||||
// so that it fits inside the area of rectangle `b`, while maintaining its original
|
||||
// aspect ratio.
|
||||
func FitInside(a, b *Rectangle) *Rectangle {
|
||||
|
|
|
@ -4,6 +4,7 @@ import "github.com/gravestench/pho/geom/point"
|
|||
|
||||
// GetPoint calculates the coordinates of a point at a certain `position` on the
|
||||
// Rectangle's perimeter, assigns to and returns the given point, or creates a point if nil.
|
||||
//nolint:gomnd // math
|
||||
func GetPoint(r *Rectangle, position float64, p *point.Point) *point.Point {
|
||||
if p == nil {
|
||||
p = point.New(0, 0)
|
||||
|
@ -16,7 +17,8 @@ func GetPoint(r *Rectangle, position float64, p *point.Point) *point.Point {
|
|||
|
||||
perimeter := Perimeter(r) * position
|
||||
|
||||
if position > 0.5 {
|
||||
switch {
|
||||
case position > 0.5:
|
||||
perimeter -= r.Width + r.Height
|
||||
|
||||
if perimeter <= r.Width {
|
||||
|
@ -26,10 +28,10 @@ func GetPoint(r *Rectangle, position float64, p *point.Point) *point.Point {
|
|||
// face 4
|
||||
p.X, p.Y = r.X, r.Bottom()-(perimeter-r.Width)
|
||||
}
|
||||
} else if position <= r.Width {
|
||||
case position <= r.Width:
|
||||
// face 1
|
||||
p.X, p.Y = r.X+perimeter, r.Y
|
||||
} else {
|
||||
default:
|
||||
// face 2
|
||||
p.X, p.Y = r.Right(), r.Y+(perimeter-r.Width)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"github.com/gravestench/pho/geom/point"
|
||||
)
|
||||
|
||||
// ByStepRate is a special value that tells GetPoints to use the stepRate instead of quantity
|
||||
// for generating perimeter points
|
||||
const ByStepRate = -1
|
||||
|
||||
// GetPoints returns a slice of points from the perimeter of the Rectangle,
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
)
|
||||
|
||||
// GetRandomPoint returns a random point within the Rectangle's bounds.
|
||||
//nolint:gosec // not crypto/security-related, it's okay if we use a weak random number generator
|
||||
func GetRandomPoint(r *Rectangle, p *point.Point) *point.Point {
|
||||
if p == nil {
|
||||
p = point.New(0, 0)
|
||||
|
|
|
@ -7,8 +7,9 @@ import (
|
|||
"github.com/gravestench/pho/phomath"
|
||||
)
|
||||
|
||||
// Calculates a random point that lies within the `outer` Rectangle, but outside of the `inner`
|
||||
// GetRandomPointOutside calculates a random point that lies within the `outer` Rectangle, but outside of the `inner`
|
||||
// Rectangle. The inner Rectangle must be fully contained within the outer rectangle.
|
||||
//nolint:gosec // not crypto/security-related, it's okay if we use a weak random number generator
|
||||
func GetRandomPointOutside(outer, inner *Rectangle, out *point.Point) *point.Point {
|
||||
if out == nil {
|
||||
out = point.New(0, 0)
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"github.com/gravestench/pho/geom/intersects"
|
||||
)
|
||||
|
||||
// Takes two Rectangles and first checks to see if they intersect.
|
||||
// Intersection takes two Rectangles and first checks to see if they intersect.
|
||||
// If they intersect it will return the area of intersection in the `out` Rectangle.
|
||||
// If they do not intersect, the `out` Rectangle will have a width and height of zero.
|
||||
// The given `output` rectangle will be assigned the intsersect values and returned.
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
package rectangle
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/gravestench/pho/geom/point"
|
||||
)
|
||||
|
||||
const (
|
||||
AutoStep = -1
|
||||
AutoQuantity = -1
|
||||
)
|
||||
|
||||
// Returns an array of points from the perimeter of the Rectangle,
|
||||
// where each point is spaced out based on either the `step` value, or the `quantity`.
|
||||
func MarchingAnts(r *Rectangle, step float64, quantity int, out []*point.Point) []*point.Point {
|
||||
if step <= 0 {
|
||||
step = AutoStep
|
||||
}
|
||||
|
||||
if quantity <= 0 {
|
||||
quantity = AutoQuantity
|
||||
}
|
||||
|
||||
if out == nil {
|
||||
out = make([]*point.Point, quantity)
|
||||
}
|
||||
|
||||
if step == AutoStep && quantity == AutoQuantity {
|
||||
return /* bail */ out
|
||||
}
|
||||
|
||||
if step == AutoStep {
|
||||
step = Perimeter(r) / float64(quantity)
|
||||
} else {
|
||||
quantity = int(math.Round(Perimeter(r) / step))
|
||||
}
|
||||
|
||||
const (
|
||||
top = iota
|
||||
right
|
||||
bottom
|
||||
left
|
||||
numFaces
|
||||
)
|
||||
|
||||
x, y := r.X, r.Y
|
||||
face := top
|
||||
|
||||
for idx := 0; idx < quantity; idx++ {
|
||||
out = append(out, point.New(x, y))
|
||||
|
||||
switch face {
|
||||
case top:
|
||||
x += step
|
||||
|
||||
if x >= r.Right() {
|
||||
face = (face + 1) % numFaces
|
||||
y += x - r.Right()
|
||||
x = r.Right()
|
||||
}
|
||||
case right:
|
||||
y += step
|
||||
|
||||
if y >= r.Bottom() {
|
||||
face = (face + 1) % numFaces
|
||||
x -= y - r.Bottom()
|
||||
y = r.Bottom()
|
||||
}
|
||||
case bottom:
|
||||
x -= step
|
||||
|
||||
if x <= r.Left() {
|
||||
face = (face + 1) % numFaces
|
||||
y -= r.Left() - x
|
||||
x = r.Left()
|
||||
}
|
||||
case left:
|
||||
y -= step
|
||||
|
||||
if y <= r.Top() {
|
||||
face = (face + 1) % numFaces
|
||||
y = r.Top()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
|
@ -6,7 +6,7 @@ import (
|
|||
"github.com/gravestench/pho/geom/point"
|
||||
)
|
||||
|
||||
// Merges a Rectangle with a list of points by repositioning and/or resizing
|
||||
// MergePoints merges a Rectangle with a list of points by repositioning and/or resizing
|
||||
// it such that all points are located on or within its bounds.
|
||||
func MergePoints(r *Rectangle, points []*point.Point) *Rectangle {
|
||||
minX, maxX, minY, maxY := r.X, r.Right(), r.Y, r.Bottom()
|
||||
|
|
|
@ -2,7 +2,8 @@ package rectangle
|
|||
|
||||
import "github.com/gravestench/pho/geom/point"
|
||||
|
||||
type RectangleNamespace interface {
|
||||
// Interface defines the generic interface for a Rectangle
|
||||
type Interface interface {
|
||||
New(x, y, w, h float64) *Rectangle
|
||||
Contains(r *Rectangle, x, y float64) bool
|
||||
GetPoint(r *Rectangle, position float64, p *point.Point) *point.Point
|
||||
|
@ -28,6 +29,7 @@ type RectangleNamespace interface {
|
|||
Union(r *Rectangle, other *Rectangle) *Rectangle
|
||||
}
|
||||
|
||||
// Namespace implements rectangle.Interface
|
||||
type Namespace struct{}
|
||||
|
||||
// New creates a new Rectangle instance.
|
||||
|
@ -75,7 +77,7 @@ func (*Namespace) ContainsPoint(r *Rectangle, p *point.Point) bool {
|
|||
return Contains(r, p.X, p.Y)
|
||||
}
|
||||
|
||||
// ContainsRect checks if a given point is inside a Rectangle's bounds.
|
||||
// ContainsRectangle checks if a given point is inside a Rectangle's bounds.
|
||||
func (*Namespace) ContainsRectangle(r, other *Rectangle) bool {
|
||||
return ContainsRectangle(r, other)
|
||||
}
|
||||
|
@ -97,18 +99,21 @@ func (*Namespace) Equals(a, b *Rectangle) bool {
|
|||
return Equals(a, b)
|
||||
}
|
||||
|
||||
// Adjusts rectangle, changing its width, height and position,
|
||||
// FitInside adjusts rectangle, changing its width, height and position,
|
||||
// so that it fits inside the area of the source rectangle, while maintaining its original
|
||||
// aspect ratio.
|
||||
func (*Namespace) FitInside(inner, outer *Rectangle) *Rectangle {
|
||||
return FitInside(inner, outer)
|
||||
}
|
||||
|
||||
// Inflate increases the size of a Rectangle by a specified amount.
|
||||
// The center of the Rectangle stays the same. The amounts are added to each side,
|
||||
// so the actual increase in width or height is two times bigger than the respective argument.
|
||||
func (*Namespace) Inflate(r *Rectangle, x, y float64) *Rectangle {
|
||||
return Inflate(r, x, y)
|
||||
}
|
||||
|
||||
// Takes two Rectangles and first checks to see if they intersect.
|
||||
// Intersection takes two Rectangles and first checks to see if they intersect.
|
||||
// If they intersect it will return the area of intersection in the `out` Rectangle.
|
||||
// If they do not intersect, the `out` Rectangle will have a width and height of zero.
|
||||
// The given `intersect` rectangle will be assigned the intsersect values and returned.
|
||||
|
@ -125,7 +130,7 @@ func (*Namespace) MergePoints(r *Rectangle, points []*point.Point) *Rectangle {
|
|||
|
||||
// MergeRectangle merges the given rectangle into this rectangle and returns this rectangle.
|
||||
// Neither rectangle should have a negative width or height.
|
||||
func (*Namespace) MergeRectangle(r *Rectangle, other *Rectangle) *Rectangle {
|
||||
func (*Namespace) MergeRectangle(r, other *Rectangle) *Rectangle {
|
||||
return MergeRectangle(r, other)
|
||||
}
|
||||
|
||||
|
@ -146,8 +151,8 @@ func (*Namespace) OffsetPoint(r *Rectangle, p *point.Point) *Rectangle {
|
|||
return OffsetPoint(r, p)
|
||||
}
|
||||
|
||||
// Checks if this Rectangle overlaps with another rectangle.
|
||||
func (*Namespace) Overlaps(r *Rectangle, other *Rectangle) bool {
|
||||
// Overlaps checks if this Rectangle overlaps with another rectangle.
|
||||
func (*Namespace) Overlaps(r, other *Rectangle) bool {
|
||||
return Overlaps(r, other)
|
||||
}
|
||||
|
||||
|
@ -156,7 +161,7 @@ func (*Namespace) PerimeterPoint(r *Rectangle, angle float64, p *point.Point) *p
|
|||
return PerimeterPoint(r, angle, p)
|
||||
}
|
||||
|
||||
// Calculates a random point that lies within the `outer` Rectangle, but outside of the `inner`
|
||||
// GetRandomPointOutside calculates a random point that lies within the `outer` Rectangle, but outside of the `inner`
|
||||
// Rectangle. The inner Rectangle must be fully contained within the outer rectangle.
|
||||
func (*Namespace) GetRandomPointOutside(r, other *Rectangle, out *point.Point) *point.Point {
|
||||
var outer, inner *Rectangle
|
||||
|
@ -183,6 +188,6 @@ func (*Namespace) Scale(r *Rectangle, x, y float64) *Rectangle {
|
|||
|
||||
// Union creates a new Rectangle or repositions and/or resizes an existing Rectangle so that it
|
||||
// encompasses the two given Rectangles, i.e. calculates their union.
|
||||
func (*Namespace) Union(r *Rectangle, other *Rectangle) *Rectangle {
|
||||
func (*Namespace) Union(r, other *Rectangle) *Rectangle {
|
||||
return Union(r, other, r)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package rectangle
|
||||
|
||||
// Checks if two Rectangles overlap. If a Rectangle is within another Rectangle,
|
||||
// Overlaps checks if two Rectangles overlap. If a Rectangle is within another Rectangle,
|
||||
// the two will be considered overlapping. Thus, the Rectangles are treated as "solid".
|
||||
func Overlaps(a, b *Rectangle) bool {
|
||||
return a.X < b.Right() &&
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/gravestench/pho/phomath"
|
||||
)
|
||||
|
||||
// PerimeterPoint returns a Point from the perimeter of the Rectangle based on the given angle.
|
||||
func PerimeterPoint(r *Rectangle, angle float64, out *point.Point) *point.Point {
|
||||
if out == nil {
|
||||
out = point.New(0, 0)
|
||||
|
@ -15,7 +16,7 @@ func PerimeterPoint(r *Rectangle, angle float64, out *point.Point) *point.Point
|
|||
angle = phomath.DegToRad(angle)
|
||||
polarity := map[bool]float64{true: 1, false: -1}
|
||||
s, c := math.Sin(angle), math.Cos(angle)
|
||||
dx, dy := r.Width/2*polarity[c > 0], r.Height/2*polarity[s > 0]
|
||||
dx, dy := r.Width/2*polarity[c > 0], r.Height/2*polarity[s > 0] //nolint:gomnd // just halving things...
|
||||
|
||||
out.X = dx + r.CenterX()
|
||||
out.Y = dy + r.CenterY()
|
||||
|
|
|
@ -18,7 +18,7 @@ func New(x, y, w, h float64) *Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Encapsulates a 2D rectangle defined by its corner point in the top-left and its extends
|
||||
// Rectangle encapsulates a 2D rectangle defined by its corner point in the top-left and its extends
|
||||
// in x (width) and y (height)
|
||||
type Rectangle struct {
|
||||
Type geom.ShapeType
|
||||
|
@ -45,6 +45,7 @@ func (r *Rectangle) SetLeft(value float64) *Rectangle {
|
|||
}
|
||||
|
||||
r.X = value
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
|
@ -98,21 +99,25 @@ func (r *Rectangle) SetBottom(value float64) *Rectangle {
|
|||
return r
|
||||
}
|
||||
|
||||
// CenterX returns the X-coordinate of the center of the rectangle
|
||||
func (r *Rectangle) CenterX() float64 {
|
||||
return r.X + r.Width/2
|
||||
return r.X + r.Width/2 //nolint:gomnd // just halving things...
|
||||
}
|
||||
|
||||
// SetCenterX sets the rectangle's position so that the X-coordinate of the center matches the given value
|
||||
func (r *Rectangle) SetCenterX(value float64) *Rectangle {
|
||||
r.X = value - r.Width/2
|
||||
r.X = value - r.Width/2 //nolint:gomnd // just halving things...
|
||||
return r
|
||||
}
|
||||
|
||||
// CenterY returns the Y-coordinate of the center of the rectangle
|
||||
func (r *Rectangle) CenterY() float64 {
|
||||
return r.Y + r.Height/2
|
||||
return r.Y + r.Height/2 //nolint:gomnd // just halving things...
|
||||
}
|
||||
|
||||
// SetCenterY sets the rectangle's position so that the Y-coordinate of the center matches the given value
|
||||
func (r *Rectangle) SetCenterY(value float64) *Rectangle {
|
||||
r.Y = value - r.Height/2
|
||||
r.Y = value - r.Height/2 //nolint:gomnd // just halving things...
|
||||
|
||||
return r
|
||||
}
|
||||
|
@ -155,7 +160,7 @@ func (r *Rectangle) SetEmpty() *Rectangle {
|
|||
return r.SetTo(0, 0, 0, 0)
|
||||
}
|
||||
|
||||
// SetPosition sets the position of the rectangle.
|
||||
// SetPosition sets the position of the rectangle.
|
||||
func (r *Rectangle) SetPosition(x, y float64) *Rectangle {
|
||||
r.X, r.Y = x, y
|
||||
return r
|
||||
|
@ -243,7 +248,7 @@ func (r *Rectangle) ContainsPoint(p *point.Point) bool {
|
|||
return Contains(r, p.X, p.Y)
|
||||
}
|
||||
|
||||
// ContainsRect checks if a given point is inside a Rectangle's bounds.
|
||||
// ContainsRectangle checks if a given point is inside a Rectangle's bounds.
|
||||
func (r *Rectangle) ContainsRectangle(other *Rectangle) bool {
|
||||
return ContainsRectangle(r, other)
|
||||
}
|
||||
|
@ -265,7 +270,7 @@ func (r *Rectangle) Equals(other *Rectangle) bool {
|
|||
return Equals(r, other)
|
||||
}
|
||||
|
||||
// Adjusts rectangle, changing its width, height and position,
|
||||
// FitInside adjusts rectangle, changing its width, height and position,
|
||||
// so that it fits inside the area of the source rectangle, while maintaining its original
|
||||
// aspect ratio.
|
||||
func (r *Rectangle) FitInside(other *Rectangle) *Rectangle {
|
||||
|
@ -283,11 +288,14 @@ func (r *Rectangle) GetSize() *point.Point {
|
|||
return GetSize(r)
|
||||
}
|
||||
|
||||
// Inflate increases the size of a Rectangle by a specified amount.
|
||||
// The center of the Rectangle stays the same. The amounts are added to each side,
|
||||
// so the actual increase in width or height is two times bigger than the respective argument.
|
||||
func (r *Rectangle) Inflate(x, y float64) *Rectangle {
|
||||
return Inflate(r, x, y)
|
||||
}
|
||||
|
||||
// Takes two Rectangles and first checks to see if they intersect.
|
||||
// Intersection takes two Rectangles and first checks to see if they intersect.
|
||||
// If they intersect it will return the area of intersection in the `out` Rectangle.
|
||||
// If they do not intersect, the `out` Rectangle will have a width and height of zero.
|
||||
// The given `intersect` rectangle will be assigned the intsersect values and returned.
|
||||
|
@ -325,7 +333,7 @@ func (r *Rectangle) OffsetPoint(p *point.Point) *Rectangle {
|
|||
return OffsetPoint(r, p)
|
||||
}
|
||||
|
||||
// Checks if this Rectangle overlaps with another rectangle.
|
||||
// Overlaps checks if this Rectangle overlaps with another rectangle.
|
||||
func (r *Rectangle) Overlaps(other *Rectangle) bool {
|
||||
return Overlaps(r, other)
|
||||
}
|
||||
|
@ -335,7 +343,7 @@ func (r *Rectangle) PerimeterPoint(angle float64, p *point.Point) *point.Point {
|
|||
return PerimeterPoint(r, angle, p)
|
||||
}
|
||||
|
||||
// Calculates a random point that lies within the `outer` Rectangle, but outside of the `inner`
|
||||
// GetRandomPointOutside calculates a random point that lies within the `outer` Rectangle, but outside of the `inner`
|
||||
// Rectangle. The inner Rectangle must be fully contained within the outer rectangle.
|
||||
func (r *Rectangle) GetRandomPointOutside(other *Rectangle, out *point.Point) *point.Point {
|
||||
var outer, inner *Rectangle
|
||||
|
|
|
@ -23,4 +23,5 @@ type Renderer interface {
|
|||
ShowPanicScreen(message string)
|
||||
Print(target *ebiten.Image, str string) error
|
||||
PrintAt(target *ebiten.Image, str string, x, y int)
|
||||
GetWindowSize() (int, int)
|
||||
}
|
||||
|
|
|
@ -39,8 +39,10 @@ type Sprite interface {
|
|||
SetPlaySpeed(playSpeed time.Duration)
|
||||
SetPlayLength(playLength time.Duration)
|
||||
SetColorMod(colorMod color.Color)
|
||||
GetColorMod() color.Color
|
||||
GetPlayedCount() int
|
||||
ResetPlayedCount()
|
||||
SetEffect(effect d2enum.DrawEffect)
|
||||
GetEffect() d2enum.DrawEffect
|
||||
SetShadow(shadow bool)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package d2math
|
|||
|
||||
import "math"
|
||||
|
||||
// set up some constants for easy access
|
||||
const (
|
||||
PI = math.Pi
|
||||
PI2 = PI * 2
|
||||
|
|
|
@ -2,6 +2,7 @@ package d2math
|
|||
|
||||
import "math"
|
||||
|
||||
// define the different euler orders
|
||||
const (
|
||||
EulerOrderXYZ = iota
|
||||
EulerOrderYXZ
|
||||
|
@ -12,6 +13,7 @@ const (
|
|||
numEulerOrders
|
||||
)
|
||||
|
||||
// and define the default
|
||||
const (
|
||||
EulerOrderDefault = EulerOrderXYZ
|
||||
)
|
||||
|
@ -21,6 +23,7 @@ func eulerNoop(_ *Euler) { /* do nothing */ }
|
|||
// static check that euler is Vector3Like
|
||||
var _ Vector3Like = &Euler{}
|
||||
|
||||
// NewEuler creates a Euler
|
||||
func NewEuler(x, y, z float64, order int) *Euler {
|
||||
return &Euler{
|
||||
X: x,
|
||||
|
@ -31,6 +34,7 @@ func NewEuler(x, y, z float64, order int) *Euler {
|
|||
}
|
||||
}
|
||||
|
||||
// Euler is an abstraction of a Euler angle
|
||||
type Euler struct {
|
||||
X, Y, Z float64
|
||||
Order int
|
||||
|
@ -38,7 +42,7 @@ type Euler struct {
|
|||
}
|
||||
|
||||
// XY returns the x and y components of the quaternion
|
||||
func (e *Euler) XY() (float64, float64) {
|
||||
func (e *Euler) XY() (x, y float64) {
|
||||
return e.X, e.Y
|
||||
}
|
||||
|
||||
|
@ -47,22 +51,27 @@ func (e *Euler) XYZ() (x, y, z float64) {
|
|||
return e.X, e.Y, e.Z
|
||||
}
|
||||
|
||||
// SetX sets the x component
|
||||
func (e *Euler) SetX(v float64) *Euler {
|
||||
return e.Set(v, e.Y, e.Z, e.Order)
|
||||
}
|
||||
|
||||
// SetY sets the y component
|
||||
func (e *Euler) SetY(v float64) *Euler {
|
||||
return e.Set(e.X, v, e.Z, e.Order)
|
||||
}
|
||||
|
||||
// SetZ sets the z component
|
||||
func (e *Euler) SetZ(v float64) *Euler {
|
||||
return e.Set(e.X, e.Y, v, e.Order)
|
||||
}
|
||||
|
||||
// SetOrder sets the order of the components
|
||||
func (e *Euler) SetOrder(v int) *Euler {
|
||||
return e.Set(e.X, e.Y, e.Z, v)
|
||||
}
|
||||
|
||||
// Set sets the x, y, and z components, as well as the order
|
||||
func (e *Euler) Set(x, y, z float64, order int) *Euler {
|
||||
order = int(Clamp(float64(order), 0, numEulerOrders-1))
|
||||
e.X, e.Y, e.Z, e.Order = x, y, z, order
|
||||
|
@ -72,10 +81,12 @@ func (e *Euler) Set(x, y, z float64, order int) *Euler {
|
|||
return e
|
||||
}
|
||||
|
||||
// Copy copies the values and order from the given Euler into this one
|
||||
func (e *Euler) Copy(other *Euler) *Euler {
|
||||
return e.Set(other.X, other.Y, other.Z, other.Order)
|
||||
}
|
||||
|
||||
// SetFromQuaternion sets the values from a Quarternion in the specified order
|
||||
func (e *Euler) SetFromQuaternion(q *Quaternion, order int) *Euler {
|
||||
tmpMat4 := NewMatrix4(nil)
|
||||
|
||||
|
@ -84,6 +95,7 @@ func (e *Euler) SetFromQuaternion(q *Quaternion, order int) *Euler {
|
|||
return e.SetFromRotationMatrix(tmpMat4, order)
|
||||
}
|
||||
|
||||
// SetFromRotationMatrix sets the values from a matrix in the specified order
|
||||
func (e *Euler) SetFromRotationMatrix(m4 *Matrix4, order int) *Euler {
|
||||
m := m4.Values
|
||||
|
||||
|
@ -100,6 +112,7 @@ func (e *Euler) SetFromRotationMatrix(m4 *Matrix4, order int) *Euler {
|
|||
switch e.Order {
|
||||
case EulerOrderYXZ:
|
||||
x = math.Asin(-Clamp(m23, -1, 1))
|
||||
|
||||
if math.Abs(m23) < epsilon {
|
||||
y = math.Atan2(m13, m33)
|
||||
z = math.Atan2(m21, m22)
|
||||
|
@ -108,6 +121,7 @@ func (e *Euler) SetFromRotationMatrix(m4 *Matrix4, order int) *Euler {
|
|||
}
|
||||
case EulerOrderZXY:
|
||||
x = math.Asin(Clamp(m32, -1, 1))
|
||||
|
||||
if math.Abs(m32) < epsilon {
|
||||
y = math.Atan2(-m31, m33)
|
||||
z = math.Atan2(-m12, m22)
|
||||
|
@ -116,6 +130,7 @@ func (e *Euler) SetFromRotationMatrix(m4 *Matrix4, order int) *Euler {
|
|||
}
|
||||
case EulerOrderZYX:
|
||||
y = math.Asin(-Clamp(m31, -1, 1))
|
||||
|
||||
if math.Abs(m31) < epsilon {
|
||||
x = math.Atan2(m32, m33)
|
||||
z = math.Atan2(m21, m11)
|
||||
|
@ -124,6 +139,7 @@ func (e *Euler) SetFromRotationMatrix(m4 *Matrix4, order int) *Euler {
|
|||
}
|
||||
case EulerOrderYZX:
|
||||
z = math.Asin(Clamp(m21, -1, 1))
|
||||
|
||||
if math.Abs(m21) < epsilon {
|
||||
x = math.Atan2(-m23, m22)
|
||||
y = math.Atan2(-m31, m11)
|
||||
|
@ -132,6 +148,7 @@ func (e *Euler) SetFromRotationMatrix(m4 *Matrix4, order int) *Euler {
|
|||
}
|
||||
case EulerOrderXZY:
|
||||
z = math.Asin(-Clamp(m12, -1, 1))
|
||||
|
||||
if math.Abs(m12) < epsilon {
|
||||
x = math.Atan2(m32, m22)
|
||||
y = math.Atan2(m13, m11)
|
||||
|
@ -139,9 +156,10 @@ func (e *Euler) SetFromRotationMatrix(m4 *Matrix4, order int) *Euler {
|
|||
x = math.Atan2(-m23, m33)
|
||||
}
|
||||
case EulerOrderXYZ:
|
||||
fallthrough
|
||||
fallthrough //nolint:gocritic // it's better to be explicit and include the fallthrough to default
|
||||
default:
|
||||
y = math.Asin(Clamp(m13, -1, 1))
|
||||
|
||||
if math.Abs(m13) < epsilon {
|
||||
x = math.Atan2(-m23, m33)
|
||||
z = math.Atan2(-m12, m11)
|
||||
|
|
|
@ -26,6 +26,7 @@ func (m *Matrix3) Clone() *Matrix3 {
|
|||
}
|
||||
|
||||
// Copy the values of a given Matrix into this Matrix.
|
||||
//nolint:dupl // functions are similar but they are for different things
|
||||
func (m *Matrix3) Copy(other *Matrix3) *Matrix3 {
|
||||
m.Values[0] = other.Values[0]
|
||||
m.Values[1] = other.Values[1]
|
||||
|
@ -61,6 +62,7 @@ func (m *Matrix3) Identity() *Matrix3 {
|
|||
}
|
||||
|
||||
// FromMatrix4 copies the values of a given Matrix4 into this Matrix3.
|
||||
//nolint:dupl // functions are similar but they are for different things
|
||||
func (m *Matrix3) FromMatrix4(m4 *Matrix4) *Matrix3 {
|
||||
m.Values[0] = m4.Values[0]
|
||||
m.Values[1] = m4.Values[1]
|
||||
|
|
|
@ -64,6 +64,7 @@ func (m *Matrix4) Copy(other *Matrix4) *Matrix4 {
|
|||
}
|
||||
|
||||
a := other.Values
|
||||
|
||||
return m.SetValues(
|
||||
a[0], a[1], a[2], a[3],
|
||||
a[4], a[5], a[6], a[7],
|
||||
|
@ -385,6 +386,7 @@ func (m *Matrix4) MultiplyMatrices(a, b *Matrix4) *Matrix4 {
|
|||
b43 := b.Values[11]
|
||||
b44 := b.Values[15]
|
||||
|
||||
//nolint:dupl // similar to another line in this file...
|
||||
return m.SetValues(
|
||||
a11*b11+a12*b21+a13*b31+a14*b41,
|
||||
a21*b11+a22*b21+a23*b31+a24*b41,
|
||||
|
@ -493,6 +495,7 @@ func (m *Matrix4) Rotate(radians float64, axis Vector3Like) *Matrix4 {
|
|||
}
|
||||
|
||||
// RotateX rotates this matrix on its X axis.
|
||||
//nolint:dupl // RotateX, RotateY, and RotateZ are similar, but not duplicates
|
||||
func (m *Matrix4) RotateX(radians float64) *Matrix4 {
|
||||
c, s := math.Cos(radians), math.Sin(radians)
|
||||
|
||||
|
@ -513,6 +516,7 @@ func (m *Matrix4) RotateX(radians float64) *Matrix4 {
|
|||
}
|
||||
|
||||
// RotateY rotates this matrix on its X axis.
|
||||
//nolint:dupl // RotateX, RotateY, and RotateZ are similar, but not duplicates
|
||||
func (m *Matrix4) RotateY(radians float64) *Matrix4 {
|
||||
c, s := math.Cos(radians), math.Sin(radians)
|
||||
|
||||
|
@ -533,6 +537,7 @@ func (m *Matrix4) RotateY(radians float64) *Matrix4 {
|
|||
}
|
||||
|
||||
// RotateZ rotates this matrix on its X axis.
|
||||
//nolint:dupl // RotateX, RotateY, and RotateZ are similar, but not duplicates
|
||||
func (m *Matrix4) RotateZ(radians float64) *Matrix4 {
|
||||
c, s := math.Cos(radians), math.Sin(radians)
|
||||
|
||||
|
@ -601,7 +606,7 @@ func (m *Matrix4) Frustum(left, right, bottom, top, near, far float64) *Matrix4
|
|||
|
||||
// Perspective generates a perspective projection matrix with the given bounds.
|
||||
func (m *Matrix4) Perspective(fovy, aspect, near, far float64) *Matrix4 {
|
||||
f, nf := 1/math.Tan(fovy/2), 1/(near-far)
|
||||
f, nf := 1/math.Tan(fovy/2), 1/(near-far) //nolint:gomnd // halving things
|
||||
|
||||
return m.SetValues(
|
||||
f/aspect, 0, 0, 0,
|
||||
|
@ -638,6 +643,7 @@ func (m *Matrix4) Ortho(left, right, bottom, top, near, far float64) *Matrix4 {
|
|||
nf = 1 / nf
|
||||
}
|
||||
|
||||
//nolint:gomnd // it's math
|
||||
return m.SetValues(
|
||||
-2*lr, 0, 0, 0,
|
||||
0, -2*bt, 0, 0,
|
||||
|
@ -688,7 +694,7 @@ func (m *Matrix4) LookAtRightHanded(eye, target, up *Vector3) *Matrix4 {
|
|||
return m
|
||||
}
|
||||
|
||||
// LookAt generates a look-at matrix with the given eye position, target, and up axis.
|
||||
// LookAt generates a look-at matrix with the given eye position, target, and up axis.
|
||||
func (m *Matrix4) LookAt(eye, target, up *Vector3) *Matrix4 {
|
||||
ex, ey, ez := eye.XYZ()
|
||||
tx, ty, tz := target.XYZ()
|
||||
|
@ -802,6 +808,7 @@ func (m *Matrix4) MultiplyToMatrix4(src, out *Matrix4) *Matrix4 {
|
|||
sv[8], sv[9], sv[10], sv[11],
|
||||
sv[12], sv[13], sv[14], sv[15]
|
||||
|
||||
//nolint:dupl // similar to another line in this file...
|
||||
return out.SetValues(
|
||||
b00*a00+b01*a10+b02*a20+b03*a30,
|
||||
b01*a01+b01*a11+b02*a21+b03*a31,
|
||||
|
|
|
@ -4,6 +4,7 @@ import "math"
|
|||
|
||||
func qNoop(_ *Quaternion) { /* no operation, the default OnChangeCallback */ }
|
||||
|
||||
// NewQuaternion returns a new Quaternion
|
||||
func NewQuaternion(x, y, z, w float64) *Quaternion {
|
||||
return &Quaternion{
|
||||
X: x,
|
||||
|
@ -24,7 +25,7 @@ type Quaternion struct {
|
|||
}
|
||||
|
||||
// XY returns the x and y components of the quaternion
|
||||
func (q *Quaternion) XY() (float64, float64) {
|
||||
func (q *Quaternion) XY() (x, y float64) {
|
||||
return q.X, q.Y
|
||||
}
|
||||
|
||||
|
@ -147,7 +148,8 @@ func (q *Quaternion) Lerp(other *Quaternion, t float64) *Quaternion {
|
|||
func (q *Quaternion) RotationTo(a, b *Vector3) *Quaternion {
|
||||
dot := a.Dot(b)
|
||||
|
||||
if dot < (-1 + Epsilon) {
|
||||
switch {
|
||||
case dot < (-1 + Epsilon):
|
||||
tmpVec, xunit, yunit := NewVector3(0, 0, 0), NewVector3Right(), NewVector3Down()
|
||||
|
||||
if tmpVec.Copy(xunit).Cross(a).Length() < Epsilon {
|
||||
|
@ -157,9 +159,9 @@ func (q *Quaternion) RotationTo(a, b *Vector3) *Quaternion {
|
|||
tmpVec.Normalize()
|
||||
|
||||
return q.SetAxisAngle(tmpVec, PI)
|
||||
} else if dot > (1 - Epsilon) {
|
||||
case dot > (1 - Epsilon):
|
||||
return q.Identity()
|
||||
} else {
|
||||
default:
|
||||
tmpVec := NewVector3(0, 0, 0).Copy(a).Cross(b)
|
||||
|
||||
q.Set(
|
||||
|
@ -201,7 +203,7 @@ func (q *Quaternion) Identity() *Quaternion {
|
|||
|
||||
// SetAxisAngle sets the axis angle of this Quaternion.
|
||||
func (q *Quaternion) SetAxisAngle(axis *Vector3, radians float64) *Quaternion {
|
||||
radians = radians / 2
|
||||
radians /= 2
|
||||
s := math.Sin(radians)
|
||||
|
||||
return q.Set(
|
||||
|
@ -246,8 +248,10 @@ func (q *Quaternion) Slerp(other Vector4Like, t float64) *Quaternion {
|
|||
// calculate coefficients
|
||||
if (1 - cosom) > Epsilon {
|
||||
// standard case (slerp)
|
||||
var omega = math.Acos(cosom)
|
||||
var sinom = math.Sin(omega)
|
||||
var (
|
||||
omega = math.Acos(cosom)
|
||||
sinom = math.Sin(omega)
|
||||
)
|
||||
|
||||
scale0 = math.Sin((1.0-t)*omega) / sinom
|
||||
scale1 = math.Sin(t*omega) / sinom
|
||||
|
@ -323,6 +327,7 @@ func (q *Quaternion) CalculateW() *Quaternion {
|
|||
}
|
||||
|
||||
// SetFromEuler sets this Quaternion from the given Euler, based on Euler order.
|
||||
//nolint:gomnd // math
|
||||
func (q *Quaternion) SetFromEuler(e *Euler) *Quaternion {
|
||||
x, y, z := e.X/2, e.Y/2, e.Z/2
|
||||
c1, c2, c3 := math.Cos(x), math.Cos(y), math.Cos(z)
|
||||
|
@ -365,7 +370,7 @@ func (q *Quaternion) SetFromEuler(e *Euler) *Quaternion {
|
|||
c1*c2*c3+s1*s2*s3,
|
||||
)
|
||||
case EulerOrderXYZ:
|
||||
fallthrough
|
||||
fallthrough //nolint:gocritic // it's better to be explicit and include the fallthrough to default
|
||||
default:
|
||||
q.Set(
|
||||
s1*c2*c3+c1*s2*s3,
|
||||
|
@ -379,7 +384,10 @@ func (q *Quaternion) SetFromEuler(e *Euler) *Quaternion {
|
|||
}
|
||||
|
||||
// SetFromRotationMatrix sets the rotation of this Quaternion from the given Matrix4.
|
||||
//nolint:gomnd // math
|
||||
func (q *Quaternion) SetFromRotationMatrix(m4 *Matrix4) *Quaternion {
|
||||
var s float64
|
||||
|
||||
m11 := m4.Values[0]
|
||||
m12 := m4.Values[4]
|
||||
m13 := m4.Values[8]
|
||||
|
@ -391,9 +399,9 @@ func (q *Quaternion) SetFromRotationMatrix(m4 *Matrix4) *Quaternion {
|
|||
m33 := m4.Values[10]
|
||||
|
||||
trace := m11 + m22 + m33
|
||||
var s float64
|
||||
|
||||
if trace > 0 {
|
||||
switch {
|
||||
case trace > 0:
|
||||
s = 0.5 / math.Sqrt(trace+1.0)
|
||||
|
||||
return q.Set(
|
||||
|
@ -402,7 +410,7 @@ func (q *Quaternion) SetFromRotationMatrix(m4 *Matrix4) *Quaternion {
|
|||
(m21-m12)*s,
|
||||
0.25/s,
|
||||
)
|
||||
} else if m11 > m22 && m11 > m33 {
|
||||
case m11 > m22 && m11 > m33:
|
||||
s = 2.0 * math.Sqrt(1.0+m11-m22-m33)
|
||||
|
||||
return q.Set(
|
||||
|
@ -411,7 +419,7 @@ func (q *Quaternion) SetFromRotationMatrix(m4 *Matrix4) *Quaternion {
|
|||
(m13+m31)/s,
|
||||
(m32-m23)/s,
|
||||
)
|
||||
} else if m22 > m33 {
|
||||
case m22 > m33:
|
||||
s = 2.0 * math.Sqrt(1.0+m22-m11-m33)
|
||||
|
||||
return q.Set(
|
||||
|
@ -430,14 +438,15 @@ func (q *Quaternion) SetFromRotationMatrix(m4 *Matrix4) *Quaternion {
|
|||
0.25*s,
|
||||
(m21-m12)/s,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
// FromMatrix3 converts the given Matrix into this Quaternion.
|
||||
//nolint:gomnd // math
|
||||
func (q *Quaternion) FromMatrix3(m3 *Matrix3) *Quaternion {
|
||||
var fRoot float64
|
||||
|
||||
m := m3.Values
|
||||
fTrace := m[0] + m[4] + m[8]
|
||||
var fRoot float64
|
||||
|
||||
siNext, tmp := []int{1, 2, 0}, []float64{0, 0, 0}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ func (v *Vector2) Clone() *Vector2 {
|
|||
return NewVector2(v.X, v.Y)
|
||||
}
|
||||
|
||||
// Copy makes a clone of this Vector2.
|
||||
// Copy copies the values from the given vector into this vector
|
||||
func (v *Vector2) Copy(source *Vector2) *Vector2 {
|
||||
return v.Set(source.X, source.Y)
|
||||
}
|
||||
|
@ -136,7 +136,7 @@ func (v *Vector2) LengthSquared() float64 {
|
|||
return v.X*v.X + v.Y*v.Y
|
||||
}
|
||||
|
||||
// Length calculates the length (or magnitude) of this Vector.
|
||||
// SetLength sets the length of the vector and returns the length (or magnitude) of this Vector.
|
||||
func (v *Vector2) SetLength(l float64) *Vector2 {
|
||||
return v.Normalize().Scale(l)
|
||||
}
|
||||
|
@ -205,6 +205,7 @@ func (v *Vector2) Limit(l float64) *Vector2 {
|
|||
}
|
||||
|
||||
// Reflect this Vector off a line defined by a normal.
|
||||
//nolint:gomnd // math
|
||||
func (v *Vector2) Reflect(other *Vector2) *Vector2 {
|
||||
normal := other.Clone().Normalize()
|
||||
|
||||
|
|
|
@ -166,7 +166,9 @@ func (v *Vector3) SetFromMatrix4(m *Matrix4) *Vector3 {
|
|||
// SetFromMatrix4Column sets the components of this Vector3 from the column of the given Matrix4.
|
||||
func (v *Vector3) SetFromMatrix4Column(m *Matrix4, column int) *Vector3 {
|
||||
const m4order = 4
|
||||
|
||||
column = int(Clamp(float64(column), 0, m4order-1))
|
||||
|
||||
return v.SetFromSlice(m.Values[:], column*m4order)
|
||||
}
|
||||
|
||||
|
@ -227,7 +229,7 @@ func (v *Vector3) Distance(other *Vector3) float64 {
|
|||
return math.Sqrt(v.DistanceSquared(other))
|
||||
}
|
||||
|
||||
// Length calculates the length (or magnitude) of this Vector, squared.
|
||||
// LengthSquared calculates the length (or magnitude) of this Vector, squared.
|
||||
func (v *Vector3) LengthSquared() float64 {
|
||||
return v.X*v.X + v.Y*v.Y + v.Z*v.Z
|
||||
}
|
||||
|
@ -391,6 +393,7 @@ func (v *Vector3) UnprojectViewMatrix(projection, world *Matrix4) *Vector3 {
|
|||
// be combined, i.e. projection * view * model.
|
||||
// After this operation, this vector's (x, y, z) components will
|
||||
// represent the unprojected 3D coordinate.
|
||||
//nolint:gomnd // math
|
||||
func (v *Vector3) Unproject(viewport *Vector4, invProjectionView *Matrix4) *Vector3 {
|
||||
viewX := viewport.X
|
||||
viewY := viewport.Y
|
||||
|
|
|
@ -2,6 +2,7 @@ package d2math
|
|||
|
||||
import "math"
|
||||
|
||||
// Vector4Like is a generic interface for things like are like a Vector4
|
||||
type Vector4Like interface {
|
||||
Vector2Like
|
||||
Vector3Like
|
||||
|
@ -26,8 +27,8 @@ type Vector4 struct {
|
|||
X, Y, Z, W float64
|
||||
}
|
||||
|
||||
// XYZ returns the x and y components of the vector
|
||||
func (v *Vector4) XY() (float64, float64) {
|
||||
// XY returns the x and y components of the vector
|
||||
func (v *Vector4) XY() (x, y float64) {
|
||||
return v.X, v.Y
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ func (n *Node) removeChild(m *Node) *Node {
|
|||
return n
|
||||
}
|
||||
|
||||
for idx := len(n.children)-1; idx >= 0; idx-- {
|
||||
for idx := len(n.children) - 1; idx >= 0; idx-- {
|
||||
if n.children[idx] != m {
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package d2scene
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
||||
"testing"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
||||
)
|
||||
|
||||
func TestNewNode(t *testing.T) {
|
||||
|
|
|
@ -266,7 +266,8 @@ func (a *Sprite) GetCurrentFrameSize() (width, height int) {
|
|||
return width, height
|
||||
}
|
||||
|
||||
func (a *Sprite) GetCurrentFrameOffset() (int, int) {
|
||||
// GetCurrentFrameOffset returns the X and Y offsets of the sprite's current frame
|
||||
func (a *Sprite) GetCurrentFrameOffset() (x, y int) {
|
||||
f := a.directions[a.directionIndex].frames[a.frameIndex]
|
||||
return f.offsetX, f.offsetY
|
||||
}
|
||||
|
@ -395,6 +396,11 @@ func (a *Sprite) SetColorMod(colorMod color.Color) {
|
|||
a.colorMod = colorMod
|
||||
}
|
||||
|
||||
// GetColorMod returns the Sprite's color mod
|
||||
func (a *Sprite) GetColorMod() color.Color {
|
||||
return a.colorMod
|
||||
}
|
||||
|
||||
// GetPlayedCount gets the number of times the application played
|
||||
func (a *Sprite) GetPlayedCount() int {
|
||||
return a.playedCount
|
||||
|
@ -410,6 +416,11 @@ func (a *Sprite) SetEffect(e d2enum.DrawEffect) {
|
|||
a.effect = e
|
||||
}
|
||||
|
||||
// GetEffect returns the Sprite's current DrawEffect
|
||||
func (a *Sprite) GetEffect() d2enum.DrawEffect {
|
||||
return a.effect
|
||||
}
|
||||
|
||||
// SetShadow sets bool for whether or not to draw a shadow
|
||||
func (a *Sprite) SetShadow(shadow bool) {
|
||||
a.hasShadow = shadow
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
||||
)
|
||||
|
||||
// New creates a new BitmapFont
|
||||
func New(s d2interface.Sprite, table []byte, col color.Color) *BitmapFont {
|
||||
return &BitmapFont{
|
||||
Sprite: s,
|
||||
|
@ -17,6 +18,7 @@ func New(s d2interface.Sprite, table []byte, col color.Color) *BitmapFont {
|
|||
}
|
||||
}
|
||||
|
||||
// Glyph is an abstraction of one glyph
|
||||
type Glyph struct {
|
||||
frame int
|
||||
width int
|
||||
|
|
2
d2core/d2bitmapfont/doc.go
Normal file
2
d2core/d2bitmapfont/doc.go
Normal file
|
@ -0,0 +1,2 @@
|
|||
// Package d2bitmapfont provides all of the necessary facilities for rendering text in the UI
|
||||
package d2bitmapfont
|
|
@ -17,11 +17,10 @@ type Button struct {
|
|||
PressedToggled d2interface.Surface
|
||||
Disabled d2interface.Surface
|
||||
}
|
||||
callback buttonCallback
|
||||
width, height int
|
||||
enabled bool
|
||||
pressed bool
|
||||
toggled bool
|
||||
callback buttonCallback
|
||||
enabled bool
|
||||
pressed bool
|
||||
toggled bool
|
||||
}
|
||||
|
||||
// New creates an instance of Button
|
||||
|
@ -30,31 +29,10 @@ func New() *Button {
|
|||
enabled: true,
|
||||
}
|
||||
|
||||
//buttonLayout := GetLayout(t)
|
||||
//btn.Layout = buttonLayout
|
||||
//
|
||||
//btn.normalSurface = ui.renderer.NewSurface(btn.width, btn.height)
|
||||
//
|
||||
//buttonSprite.SetPosition(0, 0)
|
||||
//buttonSprite.SetEffect(d2enum.DrawEffectModulate)
|
||||
//
|
||||
//btn.createTooltip()
|
||||
//
|
||||
//ui.addWidget(btn) // important that this comes before prerenderStates!
|
||||
//
|
||||
//btn.prerenderStates(buttonSprite, &buttonLayout, lbl)
|
||||
|
||||
return btn
|
||||
}
|
||||
|
||||
type buttonStateDescriptor struct {
|
||||
baseFrame int
|
||||
offsetX, offsetY int
|
||||
prerenderdestination *d2interface.Surface
|
||||
fmtErr string
|
||||
}
|
||||
|
||||
// this is some jank shit, and if things go wrong you should suspect this func first
|
||||
// GetButtonSize - this is some jank shit, and if things go wrong you should suspect this func first
|
||||
func (v *Button) GetButtonSize() (w, h int) {
|
||||
if v.Sprite == nil {
|
||||
return 0, 0
|
||||
|
@ -117,7 +95,7 @@ func (v *Button) GetToggled() bool {
|
|||
return v.toggled
|
||||
}
|
||||
|
||||
// Advance advances the button state
|
||||
// GetCurrentTexture returns the relevant Surface, depending on the button's state
|
||||
func (v *Button) GetCurrentTexture() d2interface.Surface {
|
||||
if !v.enabled {
|
||||
return v.Surfaces.Disabled
|
||||
|
|
|
@ -7,18 +7,18 @@ import (
|
|||
|
||||
// ButtonLayout defines the type of buttons
|
||||
type ButtonLayout struct {
|
||||
SpritePath string
|
||||
PalettePath string
|
||||
FontPath string
|
||||
ClickableRect *rectangle.Rectangle
|
||||
XSegments int
|
||||
YSegments int
|
||||
BaseFrame int
|
||||
DisabledFrame int
|
||||
DisabledColor uint32
|
||||
TextOffset int
|
||||
FixedWidth int
|
||||
FixedHeight int
|
||||
SpritePath string
|
||||
PalettePath string
|
||||
FontPath string
|
||||
ClickableRect *rectangle.Rectangle
|
||||
XSegments int
|
||||
YSegments int
|
||||
BaseFrame int
|
||||
DisabledFrame int
|
||||
DisabledColor uint32
|
||||
TextOffset int
|
||||
FixedWidth int
|
||||
FixedHeight int
|
||||
LabelColor uint32
|
||||
Toggleable bool
|
||||
AllowFrameChange bool
|
||||
|
@ -94,17 +94,12 @@ const (
|
|||
buttonGoldCoinSegmentsY = 1
|
||||
buttonGoldCoinDisabledFrame = -1
|
||||
|
||||
pressedButtonOffset = 2
|
||||
pressedButtonOffset = 1 // nolint:varcheck,deadcode,unused // will be used eventually
|
||||
)
|
||||
|
||||
// nolint:funlen // cant reduce
|
||||
// GetLayout is a wrapper around GetLayouts for retrieving a specific layout (note: not necessary, can be removed)
|
||||
// nolint:funlen // will not be hard-coded in here forever, can't really reduce this right now
|
||||
func GetLayout(t ButtonType) ButtonLayout {
|
||||
layouts := GetLayouts()
|
||||
|
||||
return layouts[t]
|
||||
}
|
||||
|
||||
func GetLayouts() map[ButtonType]ButtonLayout {
|
||||
const (
|
||||
buyButtonBaseFrame = 2 // base frame offset of the "buy" button dc6
|
||||
sellButtonBaseFrame = 4 // base frame offset of the "sell" button dc6
|
||||
|
@ -118,7 +113,7 @@ func GetLayouts() map[ButtonType]ButtonLayout {
|
|||
squelchChatButtonBaseFrame = 20 // base frame offset of the "?" button dc6
|
||||
)
|
||||
|
||||
return map[ButtonType]ButtonLayout{
|
||||
layouts := map[ButtonType]ButtonLayout{
|
||||
ButtonTypeWide: {
|
||||
XSegments: buttonWideSegmentsX,
|
||||
YSegments: buttonWideSegmentsY,
|
||||
|
@ -631,4 +626,6 @@ func GetLayouts() map[ButtonType]ButtonLayout {
|
|||
LabelColor: whiteAlpha100,
|
||||
},
|
||||
}
|
||||
|
||||
return layouts[t]
|
||||
}
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
package d2button
|
||||
|
||||
type ButtonState int
|
||||
|
||||
|
||||
// ButtonStates
|
||||
const (
|
||||
ButtonStatePressed = iota + 1
|
||||
ButtonStateToggled
|
||||
ButtonStatePressedToggled
|
||||
)
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package d2button
|
||||
|
||||
const (
|
||||
buttonTooltipNone int = iota
|
||||
buttonTooltipNone int = iota // nolint:varcheck,deadcode // will be used eventually
|
||||
buttonTooltipClose
|
||||
buttonTooltipOk
|
||||
buttonTooltipBuy
|
||||
|
|
2
d2core/d2button/doc.go
Normal file
2
d2core/d2button/doc.go
Normal file
|
@ -0,0 +1,2 @@
|
|||
// Package d2button provides all of the necessary facilities for creating buttons in the UI
|
||||
package d2button
|
155
d2core/d2checkbox/checkbox.go
Normal file
155
d2core/d2checkbox/checkbox.go
Normal file
|
@ -0,0 +1,155 @@
|
|||
package d2checkbox
|
||||
|
||||
import (
|
||||
"github.com/gravestench/akara"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom/rectangle"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2label"
|
||||
)
|
||||
|
||||
// set up defaults
|
||||
const (
|
||||
CheckboxDefaultTextOffset = 18
|
||||
CheckboxDefaultWidth = 16
|
||||
CheckboxDefaultHeight = 15
|
||||
)
|
||||
|
||||
type callbackFunc = func(this akara.Component) (preventPropagation bool)
|
||||
|
||||
// Checkbox defines a standard wide UI button
|
||||
type Checkbox struct {
|
||||
Layout CheckboxLayout
|
||||
Sprite d2interface.Sprite
|
||||
Label *d2label.Label
|
||||
callback callbackFunc
|
||||
pressed bool
|
||||
enabled bool
|
||||
}
|
||||
|
||||
// CheckboxLayout defines the type of buttons
|
||||
type CheckboxLayout struct {
|
||||
X float64
|
||||
Y float64
|
||||
SpritePath string
|
||||
PalettePath string
|
||||
FontPath string
|
||||
ClickableRect *rectangle.Rectangle
|
||||
XSegments int
|
||||
YSegments int
|
||||
BaseFrame int
|
||||
DisabledFrame int
|
||||
DisabledColor uint32
|
||||
TextOffset float64
|
||||
FixedWidth int
|
||||
FixedHeight int
|
||||
LabelColor uint32
|
||||
Toggleable bool
|
||||
AllowFrameChange bool
|
||||
HasImage bool
|
||||
Tooltip int
|
||||
TooltipXOffset int
|
||||
TooltipYOffset int
|
||||
}
|
||||
|
||||
// New creates an instance of Button
|
||||
func New() *Checkbox {
|
||||
checkbox := &Checkbox{
|
||||
Layout: GetDefaultLayout(),
|
||||
}
|
||||
|
||||
return checkbox
|
||||
}
|
||||
|
||||
// GetDefaultLayout returns the default layout of a checkbox.
|
||||
func GetDefaultLayout() CheckboxLayout {
|
||||
return CheckboxLayout{
|
||||
X: 0,
|
||||
Y: 0,
|
||||
SpritePath: d2resource.Checkbox,
|
||||
PalettePath: d2resource.PaletteFechar,
|
||||
FontPath: d2resource.FontExocet10,
|
||||
XSegments: 1,
|
||||
YSegments: 1,
|
||||
BaseFrame: 0,
|
||||
DisabledFrame: -1,
|
||||
DisabledColor: lightGreyAlpha75,
|
||||
TextOffset: CheckboxDefaultTextOffset,
|
||||
FixedWidth: CheckboxDefaultWidth,
|
||||
FixedHeight: CheckboxDefaultHeight,
|
||||
LabelColor: goldAlpha100,
|
||||
Toggleable: true,
|
||||
AllowFrameChange: true,
|
||||
HasImage: true,
|
||||
Tooltip: 0,
|
||||
TooltipXOffset: 0,
|
||||
TooltipYOffset: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// OnActivated defines the callback handler for the activate event
|
||||
func (v *Checkbox) OnActivated(callback callbackFunc) {
|
||||
v.callback = callback
|
||||
}
|
||||
|
||||
// Activate calls the on activated callback handler, if any
|
||||
func (v *Checkbox) Activate(thisComponent akara.Component) bool {
|
||||
if v.GetEnabled() {
|
||||
v.Toggle()
|
||||
}
|
||||
|
||||
if v.callback != nil {
|
||||
return v.callback(thisComponent)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Toggle negates the toggled state of the button
|
||||
func (v *Checkbox) Toggle() {
|
||||
v.SetPressed(!v.GetPressed())
|
||||
}
|
||||
|
||||
// GetEnabled returns the enabled state
|
||||
func (v *Checkbox) GetEnabled() bool {
|
||||
return v.enabled
|
||||
}
|
||||
|
||||
// SetEnabled sets the enabled state
|
||||
func (v *Checkbox) SetEnabled(enabled bool) {
|
||||
v.enabled = enabled
|
||||
}
|
||||
|
||||
// GetPressed returns the enabled state
|
||||
func (v *Checkbox) GetPressed() bool {
|
||||
return v.pressed
|
||||
}
|
||||
|
||||
// SetPressed sets the enabled state
|
||||
func (v *Checkbox) SetPressed(pressed bool) {
|
||||
v.pressed = pressed
|
||||
}
|
||||
|
||||
// Update updates the checkbox's sprite in accordance with the checkbox's current state.
|
||||
// This ensures that the checkbox rendered in the UI accurately reflects the state of the checkbox.
|
||||
func (v *Checkbox) Update() {
|
||||
if v.Sprite == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case v.GetEnabled() && v.GetPressed():
|
||||
// checked, enabled
|
||||
_ = v.Sprite.SetCurrentFrame(1)
|
||||
case v.GetEnabled():
|
||||
// unchecked, enabled
|
||||
_ = v.Sprite.SetCurrentFrame(0)
|
||||
case v.GetPressed():
|
||||
// checked, disabled
|
||||
_ = v.Sprite.SetCurrentFrame(1)
|
||||
default:
|
||||
// unchecked, disabled
|
||||
_ = v.Sprite.SetCurrentFrame(0)
|
||||
}
|
||||
}
|
6
d2core/d2checkbox/colors.go
Normal file
6
d2core/d2checkbox/colors.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package d2checkbox
|
||||
|
||||
const (
|
||||
lightGreyAlpha75 = 0x808080c3
|
||||
goldAlpha100 = 0xc7_b3_77_ff
|
||||
)
|
2
d2core/d2checkbox/doc.go
Normal file
2
d2core/d2checkbox/doc.go
Normal file
|
@ -0,0 +1,2 @@
|
|||
// Package d2checkbox provides all of the necessary facilities for creating checkboxes in the UI
|
||||
package d2checkbox
|
|
@ -1,3 +1,4 @@
|
|||
//nolint:dupl,golint,stylecheck // component declarations are supposed to look the same
|
||||
package d2components
|
||||
|
||||
import (
|
||||
|
@ -39,4 +40,3 @@ func (m *BitmapFontFactory) Get(id akara.EID) (*BitmapFont, bool) {
|
|||
|
||||
return component.(*BitmapFont), found
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ const (
|
|||
defaultCameraHeight = 600
|
||||
defaultCameraNear = -100
|
||||
defaultCameraFar = 100
|
||||
defaultCameraZ = -200
|
||||
defaultCameraZ = -200 //nolint:varcheck,deadcode // unused for now
|
||||
)
|
||||
|
||||
// static check that Camera implements Component
|
||||
|
@ -30,8 +30,8 @@ type Camera struct {
|
|||
// The camera defaults to position (0,0), 800x600 resolution, and zoom of 1.0
|
||||
func (*Camera) New() akara.Component {
|
||||
c := &Camera{
|
||||
Size: d2math.NewVector2(defaultCameraWidth, defaultCameraHeight),
|
||||
Clip: d2math.NewVector2(defaultCameraNear, defaultCameraFar),
|
||||
Size: d2math.NewVector2(defaultCameraWidth, defaultCameraHeight),
|
||||
Clip: d2math.NewVector2(defaultCameraNear, defaultCameraFar),
|
||||
}
|
||||
|
||||
w, h := c.Size.XY()
|
||||
|
@ -39,7 +39,7 @@ func (*Camera) New() akara.Component {
|
|||
|
||||
c.PerspectiveMatrix = d2math.NewMatrix4(nil).PerspectiveLH(w, h, n, f)
|
||||
|
||||
l, r, t, b := -(w / 2), w/2, -(h / 2), h/2
|
||||
l, r, t, b := -(w / 2), w/2, -(h / 2), h/2 //nolint:gomnd // halving things
|
||||
|
||||
c.OrthogonalMatrix = d2math.NewMatrix4(nil).Ortho(l, r, t, b, n, f)
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
var _ akara.Component = &Dirty{}
|
||||
|
||||
// Dirty is a flag component that is used to denote a "dirty" state
|
||||
type Dirty struct {}
|
||||
type Dirty struct{}
|
||||
|
||||
// New creates a new Dirty. By default, IsDirty is false.
|
||||
func (*Dirty) New() akara.Component {
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
//nolint:dupl,golint,stylecheck // component declarations are supposed to look the same
|
||||
package d2components
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/gravestench/akara"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
)
|
||||
|
||||
// static check that DrawEffect implements Component
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
var _ akara.Component = &FileLoaded{}
|
||||
|
||||
// FileLoaded is used to flag file entities as having been loaded. it is an empty struct.
|
||||
type FileLoaded struct {}
|
||||
type FileLoaded struct{}
|
||||
|
||||
// New returns a FileLoaded component. By default, it contains an empty string.
|
||||
func (*FileLoaded) New() akara.Component {
|
||||
|
|
|
@ -3,6 +3,8 @@ package d2components
|
|||
import (
|
||||
"github.com/gravestench/akara"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom/rectangle"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2input"
|
||||
)
|
||||
|
||||
|
@ -13,19 +15,21 @@ func noop() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// Interactive is used to flag file entities with a file type
|
||||
// Interactive is used to define an input state and a callback function to execute when that state is reached
|
||||
type Interactive struct {
|
||||
Enabled bool
|
||||
*d2input.InputVector
|
||||
Callback func() (preventPropagation bool)
|
||||
CursorPosition *rectangle.Rectangle
|
||||
Callback func() (preventPropagation bool)
|
||||
}
|
||||
|
||||
// New returns a Interactive component. By default, it contains a nil instance.
|
||||
func (*Interactive) New() akara.Component {
|
||||
return &Interactive{
|
||||
Enabled: true,
|
||||
InputVector: d2input.NewInputVector(),
|
||||
Callback: noop,
|
||||
Enabled: true,
|
||||
InputVector: d2input.NewInputVector(),
|
||||
CursorPosition: nil,
|
||||
Callback: noop,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ var _ akara.Component = &Locale{}
|
|||
|
||||
// Locale represents a file as a path
|
||||
type Locale struct {
|
||||
Code byte
|
||||
Code byte
|
||||
String string
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ var _ akara.Component = &Ready{}
|
|||
|
||||
// Ready is used to signify when a UI component is ready to be used.
|
||||
// (files are loaded, surfaces rendered)
|
||||
type Ready struct {}
|
||||
type Ready struct{}
|
||||
|
||||
// New returns a Ready component. This component is an empty tag component.
|
||||
func (*Ready) New() akara.Component {
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
package d2components
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom/rectangle"
|
||||
"github.com/gravestench/akara"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom/rectangle"
|
||||
)
|
||||
|
||||
// static check that Rectangle implements Component
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//nolint:dupl,golint,stylecheck // component declarations are supposed to look the same
|
||||
package d2components
|
||||
|
||||
import (
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
package d2components
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
||||
"github.com/gravestench/akara"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
||||
)
|
||||
|
||||
// static check that Size implements Component
|
||||
|
|
|
@ -13,8 +13,8 @@ var _ akara.Component = &Transform{}
|
|||
// Transform contains a vec3 for Translation, Rotation, and Scale
|
||||
type Transform struct {
|
||||
Translation *d2math.Vector3
|
||||
Rotation *d2math.Vector3
|
||||
Scale *d2math.Vector3
|
||||
Rotation *d2math.Vector3
|
||||
Scale *d2math.Vector3
|
||||
}
|
||||
|
||||
func (t *Transform) GetMatrix() *d2math.Matrix4 {
|
||||
|
@ -30,8 +30,8 @@ func (t *Transform) GetMatrix() *d2math.Matrix4 {
|
|||
func (*Transform) New() akara.Component {
|
||||
return &Transform{
|
||||
Translation: d2math.NewVector3(0, 0, 0),
|
||||
Rotation: d2math.NewVector3(0, 0, 0),
|
||||
Scale: d2math.NewVector3(1, 1, 1),
|
||||
Rotation: d2math.NewVector3(0, 0, 0),
|
||||
Scale: d2math.NewVector3(1, 1, 1),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
44
d2core/d2components/ui_checkbox.go
Normal file
44
d2core/d2components/ui_checkbox.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
//nolint:dupl,golint,stylecheck // component declarations are supposed to look the same
|
||||
package d2components
|
||||
|
||||
import (
|
||||
"github.com/gravestench/akara"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2checkbox"
|
||||
)
|
||||
|
||||
// static check that Checkbox implements Component
|
||||
var _ akara.Component = &Checkbox{}
|
||||
|
||||
// Checkbox represents a UI checkbox. It contains an embedded *d2checkbox.Checkbox
|
||||
type Checkbox struct {
|
||||
*d2checkbox.Checkbox
|
||||
}
|
||||
|
||||
// New returns a Checkbox component. This contains an embedded *d2checkbox.Checkbox
|
||||
func (*Checkbox) New() akara.Component {
|
||||
return &Checkbox{
|
||||
Checkbox: d2checkbox.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// CheckboxFactory is a wrapper for the generic component factory that returns Checkbox component instances.
|
||||
// This can be embedded inside of a system to give them the methods for adding, retrieving, and removing a Checkbox.
|
||||
type CheckboxFactory struct {
|
||||
*akara.ComponentFactory
|
||||
}
|
||||
|
||||
// Add adds a Checkbox component to the given entity and returns it
|
||||
func (m *CheckboxFactory) Add(id akara.EID) *Checkbox {
|
||||
return m.ComponentFactory.Add(id).(*Checkbox)
|
||||
}
|
||||
|
||||
// Get returns the Button component for the given entity, and a bool for whether or not it exists
|
||||
func (m *CheckboxFactory) Get(id akara.EID) (*Checkbox, bool) {
|
||||
component, found := m.ComponentFactory.Get(id)
|
||||
if !found {
|
||||
return nil, found
|
||||
}
|
||||
|
||||
return component.(*Checkbox), found
|
||||
}
|
2
d2core/d2label/doc.go
Normal file
2
d2core/d2label/doc.go
Normal file
|
@ -0,0 +1,2 @@
|
|||
// Package d2label provides all of the necessary facilities for creating labels in the UI
|
||||
package d2label
|
|
@ -1,11 +1,12 @@
|
|||
package d2label
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"image/color"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2bitmapfont"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
|
||||
|
@ -14,14 +15,14 @@ import (
|
|||
// New creates a new label, initializing the unexported fields
|
||||
func New() *Label {
|
||||
return &Label{
|
||||
colors: map[int]color.Color{0: color.White},
|
||||
colors: map[int]color.Color{0: color.White},
|
||||
backgroundColor: color.Transparent,
|
||||
}
|
||||
}
|
||||
|
||||
// Label represents a user interface label
|
||||
type Label struct {
|
||||
dirty bool // used to flag when to re-render the label
|
||||
dirty bool // used to flag when to re-render the label
|
||||
text string // has color tokens
|
||||
rawText string // unmodified text
|
||||
Alignment d2ui.HorizontalAlign
|
||||
|
@ -30,6 +31,7 @@ type Label struct {
|
|||
backgroundColor color.Color
|
||||
}
|
||||
|
||||
// Render renders the label on the given Surface
|
||||
func (v *Label) Render(target d2interface.Surface) {
|
||||
lines := strings.Split(v.text, "\n")
|
||||
yOffset := 0
|
||||
|
@ -107,7 +109,7 @@ func (v *Label) SetBackgroundColor(c color.Color) {
|
|||
r1, g1, b1, a1 := c.RGBA()
|
||||
r2, g2, b2, a2 := v.backgroundColor.RGBA()
|
||||
|
||||
if (r1==r2) && (g1==g2) && (b1==b2) && (a1==a2) {
|
||||
if (r1 == r2) && (g1 == g2) && (b1 == b2) && (a1 == a2) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -155,6 +157,7 @@ func (v *Label) processColorTokens(str string) string {
|
|||
return withoutTokens
|
||||
}
|
||||
|
||||
// GetAlignOffset returns the offset necessary to render the label with its set alignment
|
||||
func (v *Label) GetAlignOffset(textWidth int) int {
|
||||
switch v.Alignment {
|
||||
case d2ui.HorizontalAlignLeft:
|
||||
|
|
|
@ -183,3 +183,8 @@ func (r *Renderer) ShowPanicScreen(message string) {
|
|||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// GetWindowSize returns the current window resolution
|
||||
func (r *Renderer) GetWindowSize() (w, h int) {
|
||||
return ebiten.WindowSize()
|
||||
}
|
||||
|
|
|
@ -2,13 +2,15 @@ package d2systems
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/pkg/profile"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/profile"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/gravestench/akara"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
|
@ -37,9 +39,9 @@ const (
|
|||
skipSplashArg = "nosplash"
|
||||
skipSplashDesc = "skip the ebiten splash screen"
|
||||
|
||||
logLevelArg = "loglevel"
|
||||
logLevelShort = 'l'
|
||||
logLevelDesc = "sets the logging level for all loggers at startup"
|
||||
logLevelArg = "loglevel"
|
||||
logLevelShort = 'l'
|
||||
logLevelDesc = "sets the logging level for all loggers at startup"
|
||||
|
||||
profilerArg = "profile"
|
||||
profilerDesc = "Profiles the program, one of (cpu, mem, block, goroutine, trace, thread, mutex)"
|
||||
|
@ -126,6 +128,7 @@ func (m *AppBootstrap) setupSubscriptions() {
|
|||
m.subscribedConfigs = m.World.AddSubscription(gameConfigs)
|
||||
}
|
||||
|
||||
// nolint:dupl // setting up component factories looks very similar across different systems
|
||||
func (m *AppBootstrap) setupFactories() {
|
||||
m.Debug("setting up component factories")
|
||||
|
||||
|
@ -285,6 +288,9 @@ func (m *AppBootstrap) parseCommandLineArgs() {
|
|||
case "buttons":
|
||||
m.Info("running button test scene")
|
||||
m.World.AddSystem(NewButtonTestScene())
|
||||
case "checkbox":
|
||||
m.Info("running checkbox test scene")
|
||||
m.World.AddSystem(NewCheckboxTestScene())
|
||||
default:
|
||||
m.World.AddSystem(&GameClientBootstrap{})
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package d2systems
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/gravestench/akara"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache"
|
||||
|
@ -43,32 +44,32 @@ var _ akara.System = &AssetLoaderSystem{}
|
|||
type AssetLoaderSystem struct {
|
||||
akara.BaseSubscriberSystem
|
||||
*d2util.Logger
|
||||
fileSub *akara.Subscription
|
||||
sourceSub *akara.Subscription
|
||||
gameConfigs *akara.Subscription
|
||||
cache *d2cache.Cache
|
||||
fileSub *akara.Subscription
|
||||
sourceSub *akara.Subscription
|
||||
gameConfigs *akara.Subscription
|
||||
cache *d2cache.Cache
|
||||
localeString string // related to file "/data/local/use"
|
||||
Components struct {
|
||||
File d2components.FileFactory
|
||||
FileType d2components.FileTypeFactory
|
||||
FileHandle d2components.FileHandleFactory
|
||||
FileSource d2components.FileSourceFactory
|
||||
GameConfig d2components.GameConfigFactory
|
||||
StringTable d2components.StringTableFactory
|
||||
FontTable d2components.FontTableFactory
|
||||
DataDictionary d2components.DataDictionaryFactory
|
||||
Palette d2components.PaletteFactory
|
||||
Components struct {
|
||||
File d2components.FileFactory
|
||||
FileType d2components.FileTypeFactory
|
||||
FileHandle d2components.FileHandleFactory
|
||||
FileSource d2components.FileSourceFactory
|
||||
GameConfig d2components.GameConfigFactory
|
||||
StringTable d2components.StringTableFactory
|
||||
FontTable d2components.FontTableFactory
|
||||
DataDictionary d2components.DataDictionaryFactory
|
||||
Palette d2components.PaletteFactory
|
||||
PaletteTransform d2components.PaletteTransformFactory
|
||||
Cof d2components.CofFactory
|
||||
Dc6 d2components.Dc6Factory
|
||||
Dcc d2components.DccFactory
|
||||
Ds1 d2components.Ds1Factory
|
||||
Dt1 d2components.Dt1Factory
|
||||
Wav d2components.WavFactory
|
||||
AnimationData d2components.AnimationDataFactory
|
||||
Locale d2components.LocaleFactory
|
||||
BitmapFont d2components.BitmapFontFactory
|
||||
FileLoaded d2components.FileLoadedFactory
|
||||
Cof d2components.CofFactory
|
||||
Dc6 d2components.Dc6Factory
|
||||
Dcc d2components.DccFactory
|
||||
Ds1 d2components.Ds1Factory
|
||||
Dt1 d2components.Dt1Factory
|
||||
Wav d2components.WavFactory
|
||||
AnimationData d2components.AnimationDataFactory
|
||||
Locale d2components.LocaleFactory
|
||||
BitmapFont d2components.BitmapFontFactory
|
||||
FileLoaded d2components.FileLoadedFactory
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,6 +150,7 @@ func (m *AssetLoaderSystem) Update() {
|
|||
|
||||
for _, eid := range m.fileSub.GetEntities() {
|
||||
m.loadAsset(eid)
|
||||
|
||||
if time.Since(start) > maxTimePerUpdate {
|
||||
break
|
||||
}
|
||||
|
@ -249,7 +251,7 @@ func (m *AssetLoaderSystem) assignFromCache(id akara.EID, path string, t d2enum.
|
|||
return found
|
||||
}
|
||||
|
||||
//nolint:gocyclo // this big switch statement is unfortunate, but necessary
|
||||
//nolint:gocyclo,funlen // this big switch statement is unfortunate, but necessary
|
||||
func (m *AssetLoaderSystem) parseAndCache(id akara.EID, path string, t d2enum.FileType, data []byte) {
|
||||
switch t {
|
||||
case d2enum.FileTypeStringTable:
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package d2systems
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
|
@ -41,20 +42,20 @@ const (
|
|||
type FileHandleResolver struct {
|
||||
akara.BaseSubscriberSystem
|
||||
*d2util.Logger
|
||||
cache *d2cache.Cache
|
||||
filesToLoad *akara.Subscription
|
||||
sourcesToUse *akara.Subscription
|
||||
localesToCheck *akara.Subscription
|
||||
locale struct {
|
||||
cache *d2cache.Cache
|
||||
filesToLoad *akara.Subscription
|
||||
sourcesToUse *akara.Subscription
|
||||
localesToCheck *akara.Subscription
|
||||
locale struct {
|
||||
charset string
|
||||
language string
|
||||
}
|
||||
Components struct {
|
||||
File d2components.FileFactory
|
||||
FileType d2components.FileTypeFactory
|
||||
File d2components.FileFactory
|
||||
FileType d2components.FileTypeFactory
|
||||
FileSource d2components.FileSourceFactory
|
||||
FileHandle d2components.FileHandleFactory
|
||||
Locale d2components.LocaleFactory
|
||||
Locale d2components.LocaleFactory
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,6 +107,7 @@ func (m *FileHandleResolver) setupSubscriptions() {
|
|||
m.localesToCheck = m.World.AddSubscription(localesToCheck)
|
||||
}
|
||||
|
||||
// nolint:dupl // setting up component factories looks very similar across different systems
|
||||
func (m *FileHandleResolver) setupFactories() {
|
||||
m.Debug("setting up component factories")
|
||||
|
||||
|
@ -128,7 +130,7 @@ func (m *FileHandleResolver) Update() {
|
|||
for _, eid := range locales {
|
||||
locale, _ := m.Components.Locale.Get(eid)
|
||||
m.locale.language = locale.String
|
||||
m.locale.charset = d2resource.GetFontCharset(locale.String)
|
||||
m.locale.charset = d2resource.GetFontCharset(locale.String)
|
||||
m.RemoveEntity(eid)
|
||||
}
|
||||
|
||||
|
@ -146,8 +148,10 @@ func (m *FileHandleResolver) Update() {
|
|||
}
|
||||
}
|
||||
|
||||
// try to load a file with a source, returns true if loaded
|
||||
// try to load a file with a source, returns true if successfully loaded from either
|
||||
// the filesystem or from the cache
|
||||
func (m *FileHandleResolver) loadFileWithSource(fileID, sourceID akara.EID) bool {
|
||||
// verify file and source exist
|
||||
fp, found := m.Components.File.Get(fileID)
|
||||
if !found {
|
||||
return false
|
||||
|
@ -175,11 +179,17 @@ func (m *FileHandleResolver) loadFileWithSource(fileID, sourceID akara.EID) bool
|
|||
fp.Path = strings.ReplaceAll(fp.Path, d2resource.LanguageTableToken, m.locale.language)
|
||||
}
|
||||
|
||||
cacheKey := m.makeCacheKey(fp.Path, sourceFp.Path)
|
||||
if entry, found := m.cache.Retrieve(cacheKey); found {
|
||||
component := m.Components.FileHandle.Add(fileID)
|
||||
component.Data = entry.(d2interface.DataStream)
|
||||
if m.loadFile(fileID, ft, fp, sourceFp, source) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *FileHandleResolver) loadFile(fileID akara.EID, fileType *d2components.FileType,
|
||||
fp, sourceFp *d2components.File, source *d2components.FileSource) bool {
|
||||
// check the cache first
|
||||
if m.fileIsLoaded(fileID, fp.Path, sourceFp.Path) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -187,7 +197,7 @@ func (m *FileHandleResolver) loadFileWithSource(fileID, sourceID akara.EID) bool
|
|||
if err != nil {
|
||||
// HACK: sound environment stuff doesnt specify the path, just the filename
|
||||
// so we gotta check this edge case
|
||||
if ft.Type != d2enum.FileTypeWAV {
|
||||
if fileType.Type != d2enum.FileTypeWAV {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -198,12 +208,8 @@ func (m *FileHandleResolver) loadFileWithSource(fileID, sourceID akara.EID) bool
|
|||
tryPath := strings.ReplaceAll(fp.Path, "sfx", "music")
|
||||
tmpComponent := &d2components.File{Path: tryPath}
|
||||
|
||||
cacheKey = m.makeCacheKey(tryPath, sourceFp.Path)
|
||||
if entry, found := m.cache.Retrieve(cacheKey); found {
|
||||
component := m.Components.FileHandle.Add(fileID)
|
||||
component.Data = entry.(d2interface.DataStream)
|
||||
if m.fileIsLoaded(fileID, tryPath, sourceFp.Path) {
|
||||
fp.Path = tryPath
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -220,6 +226,7 @@ func (m *FileHandleResolver) loadFileWithSource(fileID, sourceID akara.EID) bool
|
|||
component := m.Components.FileHandle.Add(fileID)
|
||||
component.Data = data
|
||||
|
||||
cacheKey := m.makeCacheKey(fp.Path, sourceFp.Path)
|
||||
if err := m.cache.Insert(cacheKey, data, fileHandleCacheEntryWeight); err != nil {
|
||||
m.Error(err.Error())
|
||||
}
|
||||
|
@ -231,3 +238,16 @@ func (m *FileHandleResolver) makeCacheKey(path, source string) string {
|
|||
const sep = "->"
|
||||
return strings.Join([]string{source, path}, sep)
|
||||
}
|
||||
|
||||
// check if the given file is already cached
|
||||
func (m *FileHandleResolver) fileIsLoaded(fileID akara.EID, path, source string) bool {
|
||||
cacheKey := m.makeCacheKey(path, source)
|
||||
if entry, found := m.cache.Retrieve(cacheKey); found {
|
||||
component := m.Components.FileHandle.Add(fileID)
|
||||
component.Data = entry.(d2interface.DataStream)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -25,10 +25,10 @@ const (
|
|||
type FileSourceResolver struct {
|
||||
akara.BaseSubscriberSystem
|
||||
*d2util.Logger
|
||||
filesToCheck *akara.Subscription
|
||||
Components struct {
|
||||
File d2components.FileFactory
|
||||
FileType d2components.FileTypeFactory
|
||||
filesToCheck *akara.Subscription
|
||||
Components struct {
|
||||
File d2components.FileFactory
|
||||
FileType d2components.FileTypeFactory
|
||||
FileSource d2components.FileSourceFactory
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
package d2systems
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
|
||||
"github.com/gravestench/akara"
|
||||
|
@ -31,8 +32,8 @@ type FileTypeResolver struct {
|
|||
akara.BaseSubscriberSystem
|
||||
*d2util.Logger
|
||||
filesToCheck *akara.Subscription
|
||||
Components struct {
|
||||
File d2components.FileFactory
|
||||
Components struct {
|
||||
File d2components.FileFactory
|
||||
FileType d2components.FileTypeFactory
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package d2systems
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
"github.com/gravestench/akara"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -33,13 +33,13 @@ type GameConfigSystem struct {
|
|||
*d2util.Logger
|
||||
filesToCheck *akara.Subscription
|
||||
gameConfigs *akara.Subscription
|
||||
Components struct {
|
||||
Components struct {
|
||||
GameConfig d2components.GameConfigFactory
|
||||
File d2components.FileFactory
|
||||
FileType d2components.FileTypeFactory
|
||||
File d2components.FileFactory
|
||||
FileType d2components.FileTypeFactory
|
||||
FileHandle d2components.FileHandleFactory
|
||||
FileSource d2components.FileSourceFactory
|
||||
Dirty d2components.DirtyFactory
|
||||
Dirty d2components.DirtyFactory
|
||||
}
|
||||
activeConfig *d2components.GameConfig
|
||||
}
|
||||
|
|
|
@ -19,8 +19,8 @@ type GameObjectFactory struct {
|
|||
akara.BaseSystem
|
||||
*d2util.Logger
|
||||
Sprites *SpriteFactory
|
||||
Shapes *ShapeSystem
|
||||
UI *UIWidgetFactory
|
||||
Shapes *ShapeSystem
|
||||
UI *UIWidgetFactory
|
||||
}
|
||||
|
||||
// Init will initialize the Game Object Factory by injecting all of the factory subsystems into the world
|
||||
|
|
|
@ -25,9 +25,9 @@ type InputSystem struct {
|
|||
d2interface.InputService
|
||||
configs *akara.Subscription
|
||||
interactives *akara.Subscription
|
||||
inputState *d2input.InputVector
|
||||
Components struct {
|
||||
GameConfig d2components.GameConfigFactory
|
||||
inputState *d2input.InputVector
|
||||
Components struct {
|
||||
GameConfig d2components.GameConfigFactory
|
||||
Interactive d2components.InteractiveFactory
|
||||
}
|
||||
}
|
||||
|
@ -143,9 +143,18 @@ func (m *InputSystem) applyInputState(id akara.EID) (preventPropagation bool) {
|
|||
return false
|
||||
}
|
||||
|
||||
// verify that the current inputState matches the state specified in the InputVector
|
||||
if !v.Enabled || !m.inputState.Contains(v.InputVector) {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if this Interactive specified a particular cursor position that the input must occur in
|
||||
if v.CursorPosition != nil {
|
||||
cursorX, cursorY := m.CursorPosition()
|
||||
if !v.CursorPosition.Contains(float64(cursorX), float64(cursorY)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return v.Callback()
|
||||
}
|
||||
|
|
|
@ -22,9 +22,9 @@ type MovementSystem struct {
|
|||
akara.BaseSubscriberSystem
|
||||
*d2util.Logger
|
||||
movableEntities *akara.Subscription
|
||||
Components struct {
|
||||
Components struct {
|
||||
Transform d2components.TransformFactory
|
||||
Velocity d2components.VelocityFactory
|
||||
Velocity d2components.VelocityFactory
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -221,6 +221,7 @@ func (m *RenderSystem) updateWorld() error {
|
|||
return m.World.Update(elapsed)
|
||||
}
|
||||
|
||||
// StartGameLoop starts the game loop
|
||||
func (m *RenderSystem) StartGameLoop() error {
|
||||
m.Info("starting game loop ...")
|
||||
|
||||
|
|
|
@ -60,10 +60,12 @@ type sceneComponents struct {
|
|||
Alpha d2components.AlphaFactory
|
||||
DrawEffect d2components.DrawEffectFactory
|
||||
Rectangle d2components.RectangleFactory
|
||||
Label d2components.LabelFactory
|
||||
Checkbox d2components.CheckboxFactory
|
||||
Color d2components.ColorFactory
|
||||
CommandRegistration d2components.CommandRegistrationFactory
|
||||
Dirty d2components.DirtyFactory
|
||||
GameConfig d2components.GameConfigFactory
|
||||
GameConfig d2components.GameConfigFactory
|
||||
}
|
||||
|
||||
// BaseScene encapsulates common behaviors for systems that are considered "scenes",
|
||||
|
@ -87,7 +89,7 @@ type BaseScene struct {
|
|||
SceneObjects []akara.EID
|
||||
Graph *d2scene.Node // the root node
|
||||
backgroundColor color.Color
|
||||
gameConfigs *akara.Subscription
|
||||
gameConfigs *akara.Subscription
|
||||
}
|
||||
|
||||
// Booted returns whether or not the scene has booted
|
||||
|
@ -218,6 +220,8 @@ func (s *BaseScene) setupFactories() {
|
|||
s.InjectComponent(&d2components.Sprite{}, &s.Components.Sprite.ComponentFactory)
|
||||
s.InjectComponent(&d2components.SegmentedSprite{}, &s.Components.SegmentedSprite.ComponentFactory)
|
||||
s.InjectComponent(&d2components.Rectangle{}, &s.Components.Rectangle.ComponentFactory)
|
||||
s.InjectComponent(&d2components.Checkbox{}, &s.Components.Checkbox.ComponentFactory)
|
||||
s.InjectComponent(&d2components.Label{}, &s.Components.Label.ComponentFactory)
|
||||
s.InjectComponent(&d2components.Color{}, &s.Components.Color.ComponentFactory)
|
||||
s.InjectComponent(&d2components.CommandRegistration{}, &s.Components.CommandRegistration.ComponentFactory)
|
||||
s.InjectComponent(&d2components.Dirty{}, &s.Components.Dirty.ComponentFactory)
|
||||
|
@ -448,6 +452,7 @@ func (s *BaseScene) renderViewportsToMainViewport() {
|
|||
}
|
||||
}
|
||||
|
||||
// RegisterTerminalCommand registers a command that can be executed from the terminal
|
||||
func (s *BaseScene) RegisterTerminalCommand(name, desc string, fn interface{}) {
|
||||
regID := s.NewEntity()
|
||||
reg := s.Components.CommandRegistration.Add(regID)
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package d2systems
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2input"
|
||||
"github.com/gravestench/akara"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"github.com/gravestench/akara"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2input"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
)
|
||||
|
||||
|
@ -15,7 +17,7 @@ const (
|
|||
|
||||
const (
|
||||
splashDelaySeconds = 0.5
|
||||
splashTimeout = 3
|
||||
splashTimeout = 3
|
||||
)
|
||||
|
||||
// static check that EbitenSplashScene implements the scene interface
|
||||
|
@ -26,7 +28,7 @@ var _ d2interface.Scene = &EbitenSplashScene{}
|
|||
func NewEbitenSplashScene() *EbitenSplashScene {
|
||||
scene := &EbitenSplashScene{
|
||||
BaseScene: NewBaseScene(sceneKeyEbitenSplash),
|
||||
delay: splashDelaySeconds,
|
||||
delay: splashDelaySeconds,
|
||||
}
|
||||
|
||||
scene.backgroundColor = color.Black
|
||||
|
@ -37,10 +39,10 @@ func NewEbitenSplashScene() *EbitenSplashScene {
|
|||
// EbitenSplashScene represents the in-game terminal for typing commands
|
||||
type EbitenSplashScene struct {
|
||||
*BaseScene
|
||||
booted bool
|
||||
squares []akara.EID
|
||||
booted bool
|
||||
squares []akara.EID
|
||||
timeElapsed float64
|
||||
delay float64
|
||||
delay float64
|
||||
}
|
||||
|
||||
// Init the terminal
|
||||
|
@ -105,7 +107,7 @@ func (s *EbitenSplashScene) createSplash() {
|
|||
size := 10
|
||||
|
||||
totalW, totalH := len(flags[0])*size, len(flags)*size
|
||||
ox, oy := (800-totalW)/2, (600-totalH)/2
|
||||
ox, oy := (800-totalW)/2, (600-totalH)/2 //nolint:gomnd // halving things...
|
||||
|
||||
for y, row := range flags {
|
||||
for x, col := range row {
|
||||
|
@ -148,6 +150,7 @@ func (s *EbitenSplashScene) updateSplash() {
|
|||
if s.timeElapsed >= splashTimeout {
|
||||
vpAlpha, _ := s.Components.Alpha.Get(s.Viewports[0])
|
||||
vpAlpha.Alpha -= 0.0425
|
||||
|
||||
if vpAlpha.Alpha <= 0 {
|
||||
vpAlpha.Alpha = 0
|
||||
|
||||
|
@ -161,8 +164,9 @@ func (s *EbitenSplashScene) updateSplash() {
|
|||
|
||||
// fade all of the squares
|
||||
for idx, id := range s.squares {
|
||||
a := math.Sin(s.timeElapsed*2 + -90 + (float64(idx)/numSquares))
|
||||
a = (a+1)/2 // clamp between 0..1
|
||||
a := math.Sin(s.timeElapsed*2 + -90 + (float64(idx) / numSquares))
|
||||
// clamp between 0..1
|
||||
a = (a + 1) / 2 //nolint:gomnd // halving things
|
||||
|
||||
alpha, found := s.Components.Alpha.Get(id)
|
||||
if !found {
|
||||
|
|
|
@ -155,6 +155,7 @@ func (s *LoadingScene) updateLoadProgress() {
|
|||
s.progress = 1 - ((untyped + unhandled + unparsed) / 3 / loaded)
|
||||
}
|
||||
|
||||
//nolint:gomnd // arbitrary numbers for test scene
|
||||
func (s *LoadingScene) updateViewportAlpha() {
|
||||
if len(s.Viewports) < 1 {
|
||||
return
|
||||
|
@ -196,15 +197,15 @@ func (s *LoadingScene) updateLoadingSpritePosition() {
|
|||
return
|
||||
}
|
||||
|
||||
centerX, centerY := viewport.Width/2, viewport.Height/2
|
||||
centerX, centerY := viewport.Width/2, viewport.Height/2 //nolint:gomnd // divide by two to get half, self-explanatory
|
||||
frameW, frameH := sprite.GetCurrentFrameSize()
|
||||
|
||||
// we add the frameH in the Y because sprites are supposed to be drawn from bottom to top
|
||||
transform.Translation.Set(
|
||||
float64(centerX-(frameW/2)),
|
||||
float64(centerY+(frameH/2)),
|
||||
float64(centerX-(frameW/2)), //nolint:gomnd // halving things...
|
||||
float64(centerY+(frameH/2)), //nolint:gomnd // halving things...
|
||||
transform.Translation.Z,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func (s *LoadingScene) updateLoadingSpriteFrame() {
|
||||
|
|
|
@ -15,6 +15,7 @@ const (
|
|||
sceneKeyMainMenu = "Main Menu"
|
||||
)
|
||||
|
||||
//nolint:varcheck,deadcode,unused // unused for now
|
||||
const (
|
||||
viewportMainBackground = iota + 1
|
||||
viewportTrademark
|
||||
|
@ -111,6 +112,7 @@ func (s *MainMenuScene) createButtons() {
|
|||
s.Debug("creating buttons")
|
||||
}
|
||||
|
||||
//nolint:gomnd // arbitrary numbers for test scene
|
||||
func (s *MainMenuScene) createTrademarkScreen() {
|
||||
s.Debug("creating trademark screen")
|
||||
|
||||
|
@ -134,12 +136,14 @@ func (s *MainMenuScene) createTrademarkScreen() {
|
|||
alpha := s.Components.Alpha.Add(s.sprites.trademark)
|
||||
|
||||
go func() {
|
||||
minAlphaThreshold := 1e-3
|
||||
alpha.Alpha = 1.0
|
||||
|
||||
for alpha.Alpha > 0 {
|
||||
alpha.Alpha *= 0.725
|
||||
|
||||
if alpha.Alpha <= 1e-3 {
|
||||
if alpha.Alpha <= minAlphaThreshold {
|
||||
// if it's close enough to zero, just set it to zero
|
||||
alpha.Alpha = 0
|
||||
return
|
||||
}
|
||||
|
|
|
@ -16,9 +16,9 @@ const (
|
|||
|
||||
const (
|
||||
fadeTimeout = time.Second * 4
|
||||
fadeTime = time.Second
|
||||
)
|
||||
|
||||
// NewMouseCursorScene creates a mouse cursor scene
|
||||
func NewMouseCursorScene() *MouseCursorScene {
|
||||
scene := &MouseCursorScene{
|
||||
BaseScene: NewBaseScene(sceneKeyMouseCursor),
|
||||
|
@ -30,17 +30,18 @@ func NewMouseCursorScene() *MouseCursorScene {
|
|||
// static check that MouseCursorScene implements the scene interface
|
||||
var _ d2interface.Scene = &MouseCursorScene{}
|
||||
|
||||
// MouseCursorScene is a scene that renders a mouse cursor in the window
|
||||
type MouseCursorScene struct {
|
||||
*BaseScene
|
||||
booted bool
|
||||
cursor akara.EID
|
||||
lastTimeMoved time.Time
|
||||
debug struct {
|
||||
*BaseScene
|
||||
cursor akara.EID
|
||||
booted bool
|
||||
debug struct {
|
||||
enabled bool
|
||||
}
|
||||
test bool
|
||||
}
|
||||
|
||||
// Init does basic scene initialization
|
||||
func (s *MouseCursorScene) Init(world *akara.World) {
|
||||
s.World = world
|
||||
|
||||
|
@ -65,6 +66,7 @@ func (s *MouseCursorScene) createMouseCursor() {
|
|||
s.cursor = s.Add.Sprite(0, 0, d2resource.CursorDefault, d2resource.PaletteUnits)
|
||||
}
|
||||
|
||||
// Update updates the state of the scene
|
||||
func (s *MouseCursorScene) Update() {
|
||||
for _, id := range s.Viewports {
|
||||
s.Components.Priority.Add(id).Priority = scenePriorityMouseCursor
|
||||
|
@ -98,13 +100,6 @@ func (s *MouseCursorScene) updateCursorTransform() {
|
|||
|
||||
if int(tx) != cx || int(ty) != cy {
|
||||
s.lastTimeMoved = time.Now()
|
||||
|
||||
switch s.debug.enabled {
|
||||
case true:
|
||||
s.Infof("transform: (%d, %d)", int(tx), int(ty))
|
||||
default:
|
||||
s.Debugf("transform: (%d, %d)", int(tx), int(ty))
|
||||
}
|
||||
}
|
||||
|
||||
transform.Translation.X, transform.Translation.Y = float64(cx), float64(cy)
|
||||
|
@ -116,15 +111,15 @@ func (s *MouseCursorScene) handleCursorFade() {
|
|||
return
|
||||
}
|
||||
|
||||
shouldFadeOut := time.Now().Sub(s.lastTimeMoved) > fadeTimeout
|
||||
shouldFadeOut := time.Since(s.lastTimeMoved) > fadeTimeout
|
||||
|
||||
if shouldFadeOut {
|
||||
alpha.Alpha = math.Max(alpha.Alpha*0.825, 0)
|
||||
alpha.Alpha = math.Max(alpha.Alpha*0.825, 0) // nolint:gomnd // arbitrary example number for test scene
|
||||
} else {
|
||||
alpha.Alpha = math.Min(alpha.Alpha+0.125, 1)
|
||||
alpha.Alpha = math.Min(alpha.Alpha+0.125, 1) // nolint:gomnd // arbitrary example number for test scene
|
||||
}
|
||||
|
||||
if alpha.Alpha > 1e-1 && alpha.Alpha < 1 {
|
||||
if alpha.Alpha > 1e-1 && alpha.Alpha < 1 { // nolint:gomnd // arbitrary example number for test scene
|
||||
switch s.debug.enabled {
|
||||
case true:
|
||||
s.Infof("fading %.2f", alpha.Alpha)
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package d2systems
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2button"
|
||||
"image/color"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2button"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2checkbox"
|
||||
|
||||
"github.com/gravestench/akara"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
|
@ -52,6 +54,7 @@ func (s *sceneObjectFactory) Viewport(priority, width, height int) akara.EID {
|
|||
|
||||
eid := s.NewEntity()
|
||||
s.Components.Viewport.Add(eid)
|
||||
|
||||
s.Components.Priority.Add(eid).Priority = priority
|
||||
|
||||
if priority == mainViewport {
|
||||
|
@ -102,7 +105,7 @@ func (s *sceneObjectFactory) Button(x, y float64, btnType d2button.ButtonType, t
|
|||
s.addBasicComponents(buttonEID)
|
||||
|
||||
btnTRS := s.Components.Transform.Add(buttonEID)
|
||||
btnTRS.Translation.X, btnTRS.Translation.Y = float64(x), float64(y)
|
||||
btnTRS.Translation.X, btnTRS.Translation.Y = x, y
|
||||
|
||||
btnNode := s.Components.SceneGraphNode.Add(buttonEID)
|
||||
|
||||
|
@ -126,3 +129,31 @@ func (s *sceneObjectFactory) Label(text, fontSpritePath, palettePath string) aka
|
|||
|
||||
return eid
|
||||
}
|
||||
|
||||
// Checkbox creates a Checkbox in the scene, with an attached Label
|
||||
func (s *sceneObjectFactory) Checkbox(x, y float64, checkedState, enabled bool,
|
||||
text string, callback func(akara.Component) bool) akara.EID {
|
||||
checkboxEID := s.sceneSystems.UI.Checkbox(x, y, checkedState, enabled, callback)
|
||||
s.SceneObjects = append(s.SceneObjects, checkboxEID)
|
||||
|
||||
s.addBasicComponents(checkboxEID)
|
||||
|
||||
checkboxNode := s.Components.SceneGraphNode.Add(checkboxEID)
|
||||
|
||||
// create a Label as a child of the Checkbox if text was given
|
||||
if text != "" {
|
||||
layout := d2checkbox.GetDefaultLayout()
|
||||
labelEID := s.Label(text, layout.FontPath, layout.PalettePath)
|
||||
labelNode := s.Components.SceneGraphNode.Add(labelEID)
|
||||
labelNode.SetParent(checkboxNode.Node)
|
||||
|
||||
labelTrs := s.Components.Transform.Add(labelEID)
|
||||
labelTrs.Translation.X = layout.TextOffset
|
||||
|
||||
label, _ := s.Components.Label.Get(labelEID)
|
||||
checkbox, _ := s.Components.Checkbox.Get(checkboxEID)
|
||||
checkbox.Label = label.Label
|
||||
}
|
||||
|
||||
return checkboxEID
|
||||
}
|
||||
|
|
|
@ -29,13 +29,13 @@ type ShapeSystem struct {
|
|||
akara.BaseSubscriberSystem
|
||||
*d2util.Logger
|
||||
RenderSystem *RenderSystem
|
||||
Components struct {
|
||||
Components struct {
|
||||
Transform d2components.TransformFactory
|
||||
Color d2components.ColorFactory
|
||||
Color d2components.ColorFactory
|
||||
Rectangle d2components.RectangleFactory
|
||||
Texture d2components.TextureFactory
|
||||
Size d2components.SizeFactory
|
||||
Origin d2components.OriginFactory
|
||||
Texture d2components.TextureFactory
|
||||
Size d2components.SizeFactory
|
||||
Origin d2components.OriginFactory
|
||||
}
|
||||
loadQueue spriteLoadQueue
|
||||
shapesToRender *akara.Subscription
|
||||
|
@ -90,8 +90,8 @@ func (t *ShapeSystem) Update() {
|
|||
}
|
||||
}
|
||||
|
||||
// ComponentFactory queues a sprite spriteation to be loaded
|
||||
func (t *ShapeSystem) Rectangle(x, y, width, height int, color color.Color) akara.EID {
|
||||
// Rectangle creates a rectangle to be rendered in the scene
|
||||
func (t *ShapeSystem) Rectangle(x, y, width, height int, rectangleColor color.Color) akara.EID {
|
||||
t.Debug("creating rectangle")
|
||||
|
||||
eid := t.NewEntity()
|
||||
|
@ -101,7 +101,7 @@ func (t *ShapeSystem) Rectangle(x, y, width, height int, color color.Color) akar
|
|||
r.Width, r.Height = float64(width), float64(height)
|
||||
|
||||
c := t.Components.Color.Add(eid)
|
||||
c.Color = color
|
||||
c.Color = rectangleColor
|
||||
|
||||
texture := t.Components.Texture.Add(eid)
|
||||
texture.Texture = t.RenderSystem.renderer.NewSurface(width, height)
|
||||
|
|
|
@ -2,10 +2,12 @@ package d2systems
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gravestench/akara"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
||||
"github.com/gravestench/akara"
|
||||
"time"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2sprite"
|
||||
|
@ -98,7 +100,7 @@ func (t *SpriteFactory) setupSubscriptions() {
|
|||
Build()
|
||||
|
||||
spritesToUpdate := t.NewComponentFilter().
|
||||
Require(&d2components.Sprite{}). // we want to process entities that have an sprite ...
|
||||
Require(&d2components.Sprite{}). // we want to process entities that have an sprite ...
|
||||
Require(&d2components.Texture{}). // ... but are missing a surface
|
||||
Build()
|
||||
|
||||
|
@ -136,7 +138,7 @@ func (t *SpriteFactory) Update() {
|
|||
}
|
||||
}
|
||||
|
||||
// ComponentFactory queues a sprite spriteation to be loaded
|
||||
// Sprite creates a sprite to be rendered in the scene
|
||||
func (t *SpriteFactory) Sprite(x, y float64, imgPath, palPath string) akara.EID {
|
||||
spriteID := t.NewEntity()
|
||||
|
||||
|
@ -155,7 +157,7 @@ func (t *SpriteFactory) Sprite(x, y float64, imgPath, palPath string) akara.EID
|
|||
return spriteID
|
||||
}
|
||||
|
||||
// ComponentFactory queues a segmented sprite spriteation to be loaded.
|
||||
// SegmentedSprite queues a segmented sprite spriteation to be loaded.
|
||||
// A segmented sprite is a sprite that has many frames that form the entire sprite.
|
||||
func (t *SpriteFactory) SegmentedSprite(x, y float64, imgPath, palPath string, xseg, yseg, frame int) akara.EID {
|
||||
spriteID := t.Sprite(x, y, imgPath, palPath)
|
||||
|
@ -260,6 +262,8 @@ func (t *SpriteFactory) tryRenderingSprite(eid akara.EID) {
|
|||
}
|
||||
|
||||
func (t *SpriteFactory) renderSegmentedSprite(id akara.EID, seg *d2components.SegmentedSprite) {
|
||||
fmtErr := "SetCurrentFrame error %s: \n\tsprite: %v\n\tframe count: %v\n\tframe tried: %v\n\t%v"
|
||||
|
||||
sprite, found := t.Components.Sprite.Get(id)
|
||||
if !found {
|
||||
return
|
||||
|
@ -277,6 +281,7 @@ func (t *SpriteFactory) renderSegmentedSprite(id akara.EID, seg *d2components.Se
|
|||
// first, we're going to determine the width and height of the texture we need
|
||||
for y := 0; y < segmentsY; y++ {
|
||||
fullWidth = 0
|
||||
|
||||
for x := 0; x < segmentsX; x++ {
|
||||
idx := x + y*segmentsX + frameOffset
|
||||
if idx >= numFrames {
|
||||
|
@ -284,8 +289,6 @@ func (t *SpriteFactory) renderSegmentedSprite(id akara.EID, seg *d2components.Se
|
|||
}
|
||||
|
||||
if err := sprite.SetCurrentFrame(idx); err != nil {
|
||||
fmtErr := "SetCurrentFrame error %s: \n\tsprite: %v\n\tframe count: %v\n\tframe tried: %v\n\t%v"
|
||||
|
||||
t.Errorf(fmtErr, err.Error(), sprite.SpritePath, sprite.GetFrameCount(), idx, seg)
|
||||
}
|
||||
|
||||
|
@ -310,12 +313,11 @@ func (t *SpriteFactory) renderSegmentedSprite(id akara.EID, seg *d2components.Se
|
|||
}
|
||||
|
||||
if err := sprite.SetCurrentFrame(idx); err != nil {
|
||||
fmtErr := "SetCurrentFrame error %s: \n\tsprite: %v\n\tframe count: %v\n\tframe tried: %v\n\t%v"
|
||||
|
||||
t.Errorf(fmtErr, err.Error(), sprite.SpritePath, sprite.GetFrameCount(), idx, seg)
|
||||
}
|
||||
|
||||
target.PushTranslation(x+offsetX, y+offsetY)
|
||||
|
||||
target.Render(sprite.GetCurrentFrameSurface())
|
||||
target.Pop()
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package d2systems
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2button"
|
||||
"github.com/gravestench/akara"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2button"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2components"
|
||||
)
|
||||
|
@ -29,7 +30,7 @@ var _ d2interface.Scene = &ButtonTestScene{}
|
|||
// or start the map engine test.
|
||||
type ButtonTestScene struct {
|
||||
*BaseScene
|
||||
booted bool
|
||||
booted bool
|
||||
buttons *akara.Subscription
|
||||
}
|
||||
|
||||
|
@ -61,7 +62,7 @@ func (s *ButtonTestScene) boot() {
|
|||
}
|
||||
|
||||
func (s *ButtonTestScene) createButtons() {
|
||||
s.Add.Button(100, 100, d2button.ButtonTypeBuy, "Test")
|
||||
s.Add.Button(100, 100, d2button.ButtonTypeBuy, "Test") //nolint:gomnd // arbitrary example numbers for test scene
|
||||
}
|
||||
|
||||
// Update the main menu scene
|
||||
|
@ -74,7 +75,7 @@ func (s *ButtonTestScene) Update() {
|
|||
s.boot()
|
||||
}
|
||||
|
||||
for _, eid := range s.buttons.GetEntities() {
|
||||
for _, eid := range s.buttons.GetEntities() {
|
||||
s.updateButtonPosition(eid)
|
||||
}
|
||||
|
||||
|
|
107
d2core/d2systems/scene_test_checkbox.go
Normal file
107
d2core/d2systems/scene_test_checkbox.go
Normal file
|
@ -0,0 +1,107 @@
|
|||
package d2systems
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"log"
|
||||
|
||||
"github.com/gravestench/akara"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2components"
|
||||
)
|
||||
|
||||
const (
|
||||
sceneKeyCheckboxTest = "Checkbox Test Scene"
|
||||
)
|
||||
|
||||
// NewCheckboxTestScene creates a new main menu scene. This is the first screen that the user
|
||||
// will see when launching the game.
|
||||
func NewCheckboxTestScene() *CheckboxTestScene {
|
||||
scene := &CheckboxTestScene{
|
||||
BaseScene: NewBaseScene(sceneKeyCheckboxTest),
|
||||
}
|
||||
|
||||
return scene
|
||||
}
|
||||
|
||||
// static check that CheckboxTestScene implements the scene interface
|
||||
var _ d2interface.Scene = &CheckboxTestScene{}
|
||||
|
||||
// CheckboxTestScene represents the game's main menu, where users can select single or multi player,
|
||||
// or start the map engine test.
|
||||
type CheckboxTestScene struct {
|
||||
*BaseScene
|
||||
booted bool
|
||||
checkboxes *akara.Subscription
|
||||
}
|
||||
|
||||
// Init the main menu scene
|
||||
func (s *CheckboxTestScene) Init(world *akara.World) {
|
||||
s.World = world
|
||||
|
||||
checkboxes := s.World.NewComponentFilter().
|
||||
Require(&d2components.Checkbox{}).
|
||||
Require(&d2components.Ready{}).
|
||||
Build()
|
||||
|
||||
s.checkboxes = s.World.AddSubscription(checkboxes)
|
||||
|
||||
s.Debug("initializing ...")
|
||||
}
|
||||
|
||||
func (s *CheckboxTestScene) boot() {
|
||||
if !s.BaseScene.booted {
|
||||
s.BaseScene.boot()
|
||||
return
|
||||
}
|
||||
|
||||
viewport, found := s.Components.Viewport.Get(s.Viewports[0])
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
s.AddSystem(NewMouseCursorScene())
|
||||
|
||||
s.Add.Rectangle(0, 0, viewport.Width, viewport.Height, color.White)
|
||||
|
||||
s.createCheckboxes()
|
||||
|
||||
s.booted = true
|
||||
}
|
||||
|
||||
//nolint:gomnd // arbitrary example numbers for test
|
||||
func (s *CheckboxTestScene) createCheckboxes() {
|
||||
s.Add.Checkbox(100, 100, true, true, "Expansion character", checkboxClickCallback)
|
||||
s.Add.Checkbox(100, 120, false, true, "Hardcore", checkboxClickCallback)
|
||||
s.Add.Checkbox(100, 140, true, false, "disabled checked test", checkboxClickCallback)
|
||||
s.Add.Checkbox(100, 160, false, false, "disabled unchecked test",
|
||||
checkboxClickCallback)
|
||||
}
|
||||
|
||||
// Update the main menu scene
|
||||
func (s *CheckboxTestScene) Update() {
|
||||
if s.Paused() {
|
||||
return
|
||||
}
|
||||
|
||||
if !s.booted {
|
||||
s.boot()
|
||||
}
|
||||
|
||||
s.BaseScene.Update()
|
||||
}
|
||||
|
||||
func checkboxClickCallback(thisComponent akara.Component) bool {
|
||||
this := thisComponent.(*d2components.Checkbox)
|
||||
if this.Checkbox.GetEnabled() {
|
||||
text := this.Checkbox.Label.GetText()
|
||||
|
||||
if this.Checkbox.GetPressed() {
|
||||
log.Printf("%s enabled", text)
|
||||
} else {
|
||||
log.Printf("%s disabled", text)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
|
@ -33,7 +33,7 @@ var _ d2interface.Scene = &LabelTestScene{}
|
|||
type LabelTestScene struct {
|
||||
*BaseScene
|
||||
booted bool
|
||||
labels *akara.Subscription
|
||||
labels *akara.Subscription
|
||||
velocity d2components.VelocityFactory
|
||||
}
|
||||
|
||||
|
@ -62,6 +62,7 @@ func (s *LabelTestScene) boot() {
|
|||
s.booted = true
|
||||
}
|
||||
|
||||
//nolint:gosec,gomnd // test scene, weak RNG is fine
|
||||
func (s *LabelTestScene) createLabels() {
|
||||
fonts := []string{
|
||||
d2resource.Font6,
|
||||
|
@ -86,11 +87,15 @@ func (s *LabelTestScene) createLabels() {
|
|||
|
||||
c := s.Components.Color.Add(labelEID)
|
||||
|
||||
r, g, b, a := uint8(rand.Intn(255)), uint8(rand.Intn(255)), uint8(rand.Intn(255)), uint8(rand.Intn(255))
|
||||
r, g, b, a := uint8(rand.Intn(255)), uint8(rand.Intn(255)),
|
||||
uint8(rand.Intn(255)), uint8(rand.Intn(255))
|
||||
c.Color = color.RGBA{r, g, b, a}
|
||||
|
||||
windowWidth, windowHeight := s.Render.renderer.GetWindowSize()
|
||||
trs := s.Components.Transform.Add(labelEID)
|
||||
trs.Translation.Set(rand.Float64()*800, rand.Float64()*600, 1)
|
||||
trs.Translation.Set(rand.Float64()*float64(windowWidth),
|
||||
rand.Float64()*float64(windowHeight),
|
||||
1)
|
||||
|
||||
v := s.velocity.Add(labelEID)
|
||||
|
||||
|
|
|
@ -2,23 +2,27 @@ package d2systems
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"time"
|
||||
|
||||
"github.com/gravestench/akara"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom/rectangle"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2input"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2bitmapfont"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2button"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2components"
|
||||
"github.com/gravestench/akara"
|
||||
"image/color"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
fontCacheBudget = 64
|
||||
)
|
||||
|
||||
// NewWidgetFactory creates a new ui widget factory which is intended
|
||||
// NewUIWidgetFactory creates a new ui widget factory which is intended
|
||||
// to be embedded in the game object factory system.
|
||||
func NewUIWidgetFactory(
|
||||
b akara.BaseSystem,
|
||||
|
@ -27,12 +31,13 @@ func NewUIWidgetFactory(
|
|||
shapeFactory *ShapeSystem,
|
||||
) *UIWidgetFactory {
|
||||
sys := &UIWidgetFactory{
|
||||
Logger: l,
|
||||
SpriteFactory: spriteFactory,
|
||||
ShapeSystem: shapeFactory,
|
||||
bitmapFontCache: d2cache.CreateCache(fontCacheBudget),
|
||||
buttonLoadQueue: make(buttonLoadQueue),
|
||||
labelLoadQueue: make(labelLoadQueue),
|
||||
Logger: l,
|
||||
SpriteFactory: spriteFactory,
|
||||
ShapeSystem: shapeFactory,
|
||||
bitmapFontCache: d2cache.CreateCache(fontCacheBudget),
|
||||
buttonLoadQueue: make(buttonLoadQueue),
|
||||
checkboxLoadQueue: make(checkboxLoadQueue),
|
||||
labelLoadQueue: make(labelLoadQueue),
|
||||
}
|
||||
|
||||
sys.BaseSystem = b
|
||||
|
@ -48,6 +53,12 @@ type buttonLoadQueueEntry struct {
|
|||
|
||||
type buttonLoadQueue = map[akara.EID]buttonLoadQueueEntry
|
||||
|
||||
type checkboxLoadQueueEntry struct {
|
||||
sprite akara.EID
|
||||
}
|
||||
|
||||
type checkboxLoadQueue = map[akara.EID]checkboxLoadQueueEntry
|
||||
|
||||
type labelLoadQueueEntry struct {
|
||||
table, sprite akara.EID
|
||||
}
|
||||
|
@ -62,12 +73,14 @@ type UIWidgetFactory struct {
|
|||
*SpriteFactory
|
||||
*ShapeSystem
|
||||
buttonLoadQueue
|
||||
checkboxLoadQueue
|
||||
labelLoadQueue
|
||||
bitmapFontCache d2interface.Cache
|
||||
labelsToUpdate *akara.Subscription
|
||||
buttonsToUpdate *akara.Subscription
|
||||
booted bool
|
||||
Components struct {
|
||||
bitmapFontCache d2interface.Cache
|
||||
labelsToUpdate *akara.Subscription
|
||||
buttonsToUpdate *akara.Subscription
|
||||
checkboxesToUpdate *akara.Subscription
|
||||
booted bool
|
||||
Components struct {
|
||||
File d2components.FileFactory
|
||||
Transform d2components.TransformFactory
|
||||
Interactive d2components.InteractiveFactory
|
||||
|
@ -76,6 +89,7 @@ type UIWidgetFactory struct {
|
|||
BitmapFont d2components.BitmapFontFactory
|
||||
Label d2components.LabelFactory
|
||||
Button d2components.ButtonFactory
|
||||
Checkbox d2components.CheckboxFactory
|
||||
Sprite d2components.SpriteFactory
|
||||
Color d2components.ColorFactory
|
||||
Texture d2components.TextureFactory
|
||||
|
@ -103,6 +117,7 @@ func (t *UIWidgetFactory) setupFactories() {
|
|||
t.InjectComponent(&d2components.BitmapFont{}, &t.Components.BitmapFont.ComponentFactory)
|
||||
t.InjectComponent(&d2components.Label{}, &t.Components.Label.ComponentFactory)
|
||||
t.InjectComponent(&d2components.Button{}, &t.Components.Button.ComponentFactory)
|
||||
t.InjectComponent(&d2components.Checkbox{}, &t.Components.Checkbox.ComponentFactory)
|
||||
t.InjectComponent(&d2components.Sprite{}, &t.Components.Sprite.ComponentFactory)
|
||||
t.InjectComponent(&d2components.Color{}, &t.Components.Color.ComponentFactory)
|
||||
t.InjectComponent(&d2components.Ready{}, &t.Components.Ready.ComponentFactory)
|
||||
|
@ -121,8 +136,14 @@ func (t *UIWidgetFactory) setupSubscriptions() {
|
|||
Require(&d2components.Ready{}).
|
||||
Build()
|
||||
|
||||
checkboxesToUpdate := t.NewComponentFilter().
|
||||
Require(&d2components.Checkbox{}).
|
||||
Require(&d2components.Ready{}).
|
||||
Build()
|
||||
|
||||
t.labelsToUpdate = t.AddSubscription(labelsToUpdate)
|
||||
t.buttonsToUpdate = t.AddSubscription(buttonsToUpdate)
|
||||
t.checkboxesToUpdate = t.AddSubscription(checkboxesToUpdate)
|
||||
}
|
||||
|
||||
func (t *UIWidgetFactory) boot() {
|
||||
|
@ -155,6 +176,14 @@ func (t *UIWidgetFactory) Update() {
|
|||
t.processButton(buttonEID)
|
||||
}
|
||||
|
||||
for checkboxEID := range t.checkboxLoadQueue {
|
||||
if time.Since(start) > maxTimePerUpdate {
|
||||
return
|
||||
}
|
||||
|
||||
t.processCheckbox(checkboxEID)
|
||||
}
|
||||
|
||||
for labelEID := range t.labelLoadQueue {
|
||||
if time.Since(start) > maxTimePerUpdate {
|
||||
return
|
||||
|
@ -171,6 +200,14 @@ func (t *UIWidgetFactory) Update() {
|
|||
t.updateButton(buttonEID)
|
||||
}
|
||||
|
||||
for _, checkboxEID := range t.checkboxesToUpdate.GetEntities() {
|
||||
if time.Since(start) > maxTimePerUpdate {
|
||||
return
|
||||
}
|
||||
|
||||
t.updateCheckbox(checkboxEID)
|
||||
}
|
||||
|
||||
for _, labelEID := range t.labelsToUpdate.GetEntities() {
|
||||
if time.Since(start) > maxTimePerUpdate {
|
||||
return
|
||||
|
@ -406,6 +443,7 @@ func (t *UIWidgetFactory) processButton(buttonEID akara.EID) {
|
|||
sprite, found := t.Components.Sprite.Get(spriteEID)
|
||||
if found {
|
||||
button.Sprite = sprite.Sprite
|
||||
|
||||
t.Components.SceneGraphNode.Add(spriteEID).SetParent(buttonNode.Node)
|
||||
}
|
||||
|
||||
|
@ -431,32 +469,24 @@ func (t *UIWidgetFactory) processButtonStates(buttonEID akara.EID) {
|
|||
img, pal := button.Layout.SpritePath, button.Layout.PalettePath
|
||||
sx, sy := button.Layout.XSegments, button.Layout.YSegments
|
||||
|
||||
var normal, pressed, toggled, pressedToggled, disabled akara.EID
|
||||
|
||||
normal = t.SegmentedSprite(0, 0, img, pal, sx, sy, baseFrame)
|
||||
|
||||
// by default, all other states are whatever the normal state is
|
||||
pressed = normal
|
||||
toggled = normal
|
||||
pressedToggled = normal
|
||||
disabled = normal
|
||||
|
||||
normal := t.SegmentedSprite(0, 0, img, pal, sx, sy, baseFrame)
|
||||
button.States.Normal = normal
|
||||
button.States.Pressed = pressed
|
||||
button.States.Toggled = toggled
|
||||
button.States.PressedToggled = pressedToggled
|
||||
button.States.Disabled = disabled
|
||||
button.States.Pressed = normal
|
||||
button.States.Toggled = normal
|
||||
button.States.PressedToggled = normal
|
||||
button.States.Disabled = normal
|
||||
|
||||
// if it's got other states (most buttons do...), then we handle it
|
||||
if button.Layout.HasImage && button.Layout.AllowFrameChange {
|
||||
pressed = t.SegmentedSprite(0, 0, img, pal, sx, sy, baseFrame+d2button.ButtonStatePressed)
|
||||
toggled = t.SegmentedSprite(0, 0, img, pal, sx, sy, baseFrame+d2button.ButtonStateToggled)
|
||||
pressedToggled = t.SegmentedSprite(0, 0, img, pal, sx, sy, baseFrame+d2button.ButtonStatePressedToggled)
|
||||
button.States.Pressed = t.SegmentedSprite(0, 0, img, pal, sx, sy, baseFrame+d2button.ButtonStatePressed)
|
||||
button.States.Toggled = t.SegmentedSprite(0, 0, img, pal, sx, sy, baseFrame+d2button.ButtonStateToggled)
|
||||
button.States.PressedToggled = t.SegmentedSprite(0, 0, img, pal, sx, sy, baseFrame+d2button.ButtonStatePressedToggled)
|
||||
|
||||
// also, not all buttons have a disabled state
|
||||
// this stupid fucking -1 needs to be a constant
|
||||
if button.Layout.DisabledFrame != isNotSegmented {
|
||||
disabled = t.SegmentedSprite(0, 0, img, pal, sx, sy, button.Layout.DisabledFrame)
|
||||
button.States.Disabled = t.SegmentedSprite(0, 0, img, pal, sx, sy, button.Layout.DisabledFrame)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -481,11 +511,10 @@ func (t *UIWidgetFactory) renderButtonStates(buttonEID akara.EID) {
|
|||
delete(t.buttonLoadQueue, buttonEID)
|
||||
}
|
||||
|
||||
|
||||
func (t *UIWidgetFactory) updateButton(buttonEID akara.EID) {
|
||||
button, btnFound := t.Components.Button.Get(buttonEID)
|
||||
|
||||
if ! btnFound {
|
||||
if !btnFound {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -512,3 +541,91 @@ func (t *UIWidgetFactory) updateButton(buttonEID akara.EID) {
|
|||
|
||||
texture.Texture = button.GetCurrentTexture()
|
||||
}
|
||||
|
||||
// Checkbox creates a checkbox ui widget. A Checkbox widget is composed of a Checkbox component that tracks the logic,
|
||||
// and a SegmentedSprite to be displayed in the scene.
|
||||
func (t *UIWidgetFactory) Checkbox(x, y float64, checkedState, enabled bool, callback func(akara.Component) bool) akara.EID {
|
||||
checkboxEID := t.NewEntity()
|
||||
|
||||
checkbox := t.Components.Checkbox.Add(checkboxEID)
|
||||
checkbox.Layout.X, checkbox.Layout.Y = x, y
|
||||
checkbox.Layout.ClickableRect = rectangle.New(x, y, float64(checkbox.Layout.FixedWidth), float64(checkbox.Layout.FixedHeight))
|
||||
|
||||
checkboxTrs := t.Components.Transform.Add(checkboxEID)
|
||||
checkboxTrs.Translation.X, checkboxTrs.Translation.Y = x, y
|
||||
|
||||
checkbox.Checkbox.SetPressed(checkedState)
|
||||
checkbox.Checkbox.SetEnabled(enabled)
|
||||
checkbox.Checkbox.OnActivated(callback)
|
||||
|
||||
img, pal := checkbox.Layout.SpritePath, checkbox.Layout.PalettePath
|
||||
sx, sy, base := checkbox.Layout.XSegments, checkbox.Layout.YSegments, checkbox.Layout.BaseFrame
|
||||
|
||||
spriteEID := t.SpriteFactory.SegmentedSprite(x, y, img, pal, sx, sy, base)
|
||||
|
||||
entry := checkboxLoadQueueEntry{
|
||||
sprite: spriteEID,
|
||||
}
|
||||
|
||||
t.checkboxLoadQueue[checkboxEID] = entry
|
||||
|
||||
return checkboxEID
|
||||
}
|
||||
|
||||
// processCheckbox creates a checkbox after all of the prerequisite components are ready.
|
||||
// This adds interactivity and prepares the checkbox for rendering in the scene.
|
||||
func (t *UIWidgetFactory) processCheckbox(checkboxEID akara.EID) {
|
||||
// get the queue entry
|
||||
entry, found := t.checkboxLoadQueue[checkboxEID]
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
spriteEID := entry.sprite
|
||||
|
||||
// check if sprite is ready to be used
|
||||
if _, spriteReady := t.Components.Ready.Get(spriteEID); !spriteReady {
|
||||
return
|
||||
}
|
||||
|
||||
checkbox, found := t.Components.Checkbox.Get(checkboxEID)
|
||||
if !found {
|
||||
checkbox = t.Components.Checkbox.Add(checkboxEID)
|
||||
}
|
||||
|
||||
checkboxNode := t.Components.SceneGraphNode.Add(checkboxEID)
|
||||
|
||||
sprite, found := t.Components.Sprite.Get(spriteEID)
|
||||
if found {
|
||||
checkbox.Sprite = sprite.Sprite
|
||||
|
||||
t.Components.SceneGraphNode.Add(spriteEID).SetParent(checkboxNode.Node)
|
||||
}
|
||||
|
||||
interactive := t.Components.Interactive.Add(checkboxEID)
|
||||
interactive.InputVector.SetMouseButton(d2input.MouseButtonLeft)
|
||||
interactive.CursorPosition = checkbox.Layout.ClickableRect
|
||||
interactive.Callback = func() bool {
|
||||
return checkbox.Activate(checkbox)
|
||||
}
|
||||
|
||||
t.Components.Texture.Add(checkboxEID)
|
||||
|
||||
t.Components.Ready.Add(checkboxEID)
|
||||
|
||||
delete(t.checkboxLoadQueue, checkboxEID)
|
||||
}
|
||||
|
||||
// updateCheckbox refreshes the rendering logic for a checkbox,
|
||||
// causing any changes that have occurred to appear in the scene.
|
||||
func (t *UIWidgetFactory) updateCheckbox(checkboxEID akara.EID) {
|
||||
checkbox, found := t.Components.Checkbox.Get(checkboxEID)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
checkbox.Update()
|
||||
|
||||
checkboxTexture, _ := t.Components.Texture.Get(checkboxEID)
|
||||
checkboxTexture.Texture = checkbox.Sprite.GetCurrentFrameSurface()
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package d2systems
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2components"
|
||||
"time"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2components"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
|
||||
"github.com/gravestench/akara"
|
||||
|
@ -26,9 +27,9 @@ var _ akara.System = &TimeScaleSystem{}
|
|||
type TimeScaleSystem struct {
|
||||
akara.BaseSystem
|
||||
*d2util.Logger
|
||||
scale float64
|
||||
scale float64
|
||||
Components struct {
|
||||
Dirty d2components.DirtyFactory
|
||||
Dirty d2components.DirtyFactory
|
||||
CommandRegistration d2components.CommandRegistrationFactory
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ const (
|
|||
ColorTokenCharacterType = ColorTokenGreen
|
||||
)
|
||||
|
||||
// nolint:golint // these constants are self-explanatory
|
||||
const (
|
||||
ColorGrey100Alpha = 0x69_69_69_ff
|
||||
ColorWhite100Alpha = 0xff_ff_ff_ff
|
||||
|
|
|
@ -562,7 +562,7 @@ func (s *QuestLog) cordsToQuestID(act, number int) int {
|
|||
return key
|
||||
}
|
||||
|
||||
//nolint:deadcode,unused // I think, it will be used, if not, we can just remove it
|
||||
//nolint:varcheck,unused // I think, it will be used, if not, we can just remove it
|
||||
func (s *QuestLog) questIDToCords(id int) (act, number int) {
|
||||
act = 1
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user