1
1
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:
Ian 2021-01-04 00:43:56 -08:00 committed by GitHub
parent 3731e631cf
commit 0dee6518b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
80 changed files with 907 additions and 398 deletions

View File

@ -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)

View File

@ -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
}

View 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

View File

@ -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 {

View File

@ -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)
}

View File

@ -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,

View File

@ -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)

View File

@ -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)

View File

@ -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.

View File

@ -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
}

View File

@ -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()

View File

@ -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)
}

View File

@ -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() &&

View File

@ -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()

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}

View File

@ -2,6 +2,7 @@ package d2math
import "math"
// set up some constants for easy access
const (
PI = math.Pi
PI2 = PI * 2

View File

@ -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)

View File

@ -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]

View File

@ -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,

View File

@ -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}

View File

@ -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()

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,2 @@
// Package d2bitmapfont provides all of the necessary facilities for rendering text in the UI
package d2bitmapfont

View File

@ -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

View File

@ -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]
}

View File

@ -1,12 +1,8 @@
package d2button
type ButtonState int
// ButtonStates
const (
ButtonStatePressed = iota + 1
ButtonStateToggled
ButtonStatePressedToggled
)

View File

@ -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
View File

@ -0,0 +1,2 @@
// Package d2button provides all of the necessary facilities for creating buttons in the UI
package d2button

View 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)
}
}

View File

@ -0,0 +1,6 @@
package d2checkbox
const (
lightGreyAlpha75 = 0x808080c3
goldAlpha100 = 0xc7_b3_77_ff
)

2
d2core/d2checkbox/doc.go Normal file
View File

@ -0,0 +1,2 @@
// Package d2checkbox provides all of the necessary facilities for creating checkboxes in the UI
package d2checkbox

View File

@ -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
}

View File

@ -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)

View File

@ -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 {

View File

@ -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

View File

@ -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 {

View File

@ -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,
}
}

View File

@ -10,7 +10,7 @@ var _ akara.Component = &Locale{}
// Locale represents a file as a path
type Locale struct {
Code byte
Code byte
String string
}

View File

@ -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 {

View File

@ -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

View File

@ -1,3 +1,4 @@
//nolint:dupl,golint,stylecheck // component declarations are supposed to look the same
package d2components
import (

View File

@ -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

View File

@ -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),
}
}

View 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
View File

@ -0,0 +1,2 @@
// Package d2label provides all of the necessary facilities for creating labels in the UI
package d2label

View File

@ -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:

View File

@ -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()
}

View File

@ -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{})
}

View File

@ -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:

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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 (

View File

@ -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
}

View File

@ -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

View File

@ -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()
}

View File

@ -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
}
}

View File

@ -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 ...")

View File

@ -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)

View File

@ -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 {

View File

@ -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() {

View File

@ -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
}

View File

@ -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)

View File

@ -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
}

View File

@ -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)

View File

@ -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()

View File

@ -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)
}

View 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
}

View File

@ -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)

View File

@ -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()
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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