mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-11-09 19:57:20 -05:00
228 lines
5.4 KiB
Go
228 lines
5.4 KiB
Go
package d2mapentity
|
|
|
|
import (
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
|
|
)
|
|
|
|
const (
|
|
minHitboxSize = 30
|
|
)
|
|
|
|
// mapEntity represents an entity on the map that can be animated
|
|
type mapEntity struct {
|
|
uuid string
|
|
Position d2vector.Position
|
|
Target d2vector.Position
|
|
velocity d2vector.Vector
|
|
|
|
Speed float64
|
|
path []d2vector.Position
|
|
drawLayer int
|
|
|
|
done func()
|
|
directioner func(direction int)
|
|
|
|
highlight bool
|
|
}
|
|
|
|
// newMapEntity creates an instance of mapEntity
|
|
func newMapEntity(x, y int) mapEntity {
|
|
pos := d2vector.NewPosition(float64(x), float64(y))
|
|
|
|
return mapEntity{
|
|
uuid: uuid.New().String(),
|
|
Position: pos,
|
|
Target: pos,
|
|
velocity: *d2vector.VectorZero(),
|
|
}
|
|
}
|
|
|
|
// GetLayer returns the draw layer for this entity.
|
|
func (m *mapEntity) GetLayer() int {
|
|
return m.drawLayer
|
|
}
|
|
|
|
// SetPath sets the entity movement path. done() is called when the entity reaches it's path destination. For example,
|
|
// when the player entity reaches the point a player clicked.
|
|
func (m *mapEntity) SetPath(path []d2vector.Position, done func()) {
|
|
m.path = path
|
|
m.done = done
|
|
m.nextPath()
|
|
}
|
|
|
|
// ClearPath clears the entity movement path.
|
|
func (m *mapEntity) ClearPath() {
|
|
m.path = nil
|
|
}
|
|
|
|
// StopMoving will clear the path and target of the entity.
|
|
func (m *mapEntity) StopMoving() {
|
|
m.ClearPath()
|
|
m.setTarget(m.Position, nil)
|
|
}
|
|
|
|
// SetSpeed sets the entity movement speed.
|
|
func (m *mapEntity) SetSpeed(speed float64) {
|
|
m.Speed = speed
|
|
}
|
|
|
|
// GetSpeed returns the entity movement speed.
|
|
func (m *mapEntity) GetSpeed() float64 {
|
|
return m.Speed
|
|
}
|
|
|
|
// Step moves the entity along it's path by one tick. If the path is complete it calls entity.done() then returns.
|
|
func (m *mapEntity) Step(tickTime float64) {
|
|
if m.atTarget() && !m.hasPath() {
|
|
if m.done != nil {
|
|
m.done()
|
|
m.done = nil
|
|
}
|
|
|
|
m.velocity.SetLength(0)
|
|
|
|
return
|
|
}
|
|
|
|
m.setVelocity(tickTime * m.Speed)
|
|
|
|
v := m.velocity.Clone() // Create a new vector
|
|
|
|
for {
|
|
applyVelocity(&m.Position.Vector, v, &m.Target.Vector) // Pass the new vector to the function which alters it
|
|
|
|
if m.atTarget() {
|
|
m.nextPath()
|
|
}
|
|
|
|
if v.IsZero() { // Check if the new vector is zero (keeping this as m.velocity.IsZero() would break the test (infinite loop)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// atTarget returns true if the distance between entity and target is almost zero.
|
|
func (m *mapEntity) atTarget() bool {
|
|
return m.Position.EqualsApprox(&m.Target.Vector)
|
|
}
|
|
|
|
// setVelocity returns a vector describing the given length and the direction to the current target.
|
|
func (m *mapEntity) setVelocity(length float64) {
|
|
m.velocity.Copy(&m.Target.Vector)
|
|
m.velocity.Subtract(&m.Position.Vector)
|
|
m.velocity.SetLength(length)
|
|
}
|
|
|
|
// applyVelocity adds velocity to position. If the new position extends beyond the target: Target is set to the next
|
|
// path node, Position is set to target and velocity is set to the over-extended length with the direction of to the
|
|
// next node.
|
|
func applyVelocity(position, velocity, target *d2vector.Vector) {
|
|
// Set velocity values to zero if almost zero
|
|
x, y := position.CompareApprox(target)
|
|
vx, vy := velocity.X(), velocity.Y()
|
|
|
|
if x == 0 {
|
|
vx = 0
|
|
}
|
|
|
|
if y == 0 {
|
|
vy = 0
|
|
}
|
|
|
|
velocity.Set(vx, vy)
|
|
|
|
dest := position.Clone()
|
|
dest.Add(velocity)
|
|
|
|
destDistance := position.Distance(dest)
|
|
targetDistance := position.Distance(target)
|
|
|
|
if destDistance > targetDistance {
|
|
// Destination overshot target. Set position to target and velocity to overshot amount.
|
|
position.Copy(target)
|
|
velocity.Copy(dest.Subtract(target))
|
|
} else {
|
|
// At or before target, set position to destination and velocity to zero.
|
|
position.Copy(dest)
|
|
velocity.Set(0, 0)
|
|
}
|
|
}
|
|
|
|
// Returns false if the path has ended.
|
|
func (m *mapEntity) nextPath() {
|
|
if m.hasPath() {
|
|
// Set next path node
|
|
m.setTarget(
|
|
m.path[0],
|
|
m.done,
|
|
)
|
|
|
|
if len(m.path) > 1 {
|
|
m.path = m.path[1:]
|
|
} else {
|
|
m.path = []d2vector.Position{}
|
|
}
|
|
} else {
|
|
// End of path.
|
|
m.Position.Copy(&m.Target.Vector)
|
|
}
|
|
}
|
|
|
|
// hasPath returns false if the length of the entity movement path is 0.
|
|
func (m *mapEntity) hasPath() bool {
|
|
return len(m.path) > 0
|
|
}
|
|
|
|
// setTarget sets target coordinates and changes animation based on proximity and direction.
|
|
func (m *mapEntity) setTarget(p d2vector.Position, done func()) {
|
|
// Set the target
|
|
m.Target.Copy(&p.Vector)
|
|
m.done = done
|
|
|
|
// Update the direction
|
|
if m.directioner != nil {
|
|
d := m.Position.DirectionTo(m.Target.Vector)
|
|
|
|
m.directioner(d)
|
|
}
|
|
|
|
// Update the velocity direction
|
|
if !m.velocity.IsZero() {
|
|
m.setVelocity(m.velocity.Length())
|
|
}
|
|
}
|
|
|
|
// GetPosition returns the entity's current tile position, always a whole number.
|
|
func (m *mapEntity) GetPosition() (x, y float64) {
|
|
t := m.Position.Tile()
|
|
return t.X(), t.Y()
|
|
}
|
|
|
|
// GetPositionF returns the entity's current tile position where 0.2 is one sub tile.
|
|
func (m *mapEntity) GetPositionF() (x, y float64) {
|
|
w := m.Position.World()
|
|
return w.X(), w.Y()
|
|
}
|
|
|
|
// Label returns the NPC's in-game name (e.g. "Deckard Cain") or an empty string if it does not have a name
|
|
func (m *mapEntity) Label() string {
|
|
return ""
|
|
}
|
|
|
|
// Highlight is not currently implemented.
|
|
func (m *mapEntity) Highlight() {
|
|
m.highlight = true
|
|
}
|
|
|
|
// Selectable returns true if the object can be highlighted/selected.
|
|
func (m *mapEntity) Selectable() bool {
|
|
return false
|
|
}
|
|
|
|
// GetSize returns the current frame size
|
|
func (m *mapEntity) GetSize() (width, height int) {
|
|
return minHitboxSize, minHitboxSize
|
|
}
|