diff --git a/d2common/d2math/d2vector/vector.go b/d2common/d2math/d2vector/vector.go index c729a4ea..31ff3834 100644 --- a/d2common/d2math/d2vector/vector.go +++ b/d2common/d2math/d2vector/vector.go @@ -150,18 +150,14 @@ func (v *Vector) DivideScalar(s float64) *Vector { // Abs sets the vector to it's absolute (positive) equivalent. func (v *Vector) Abs() *Vector { - xm, ym := 1.0, 1.0 if v.x < 0 { - xm = -1 + v.x = -v.x } if v.y < 0 { - ym = -1 + v.y = -v.y } - v.x *= xm - v.y *= ym - return v } @@ -183,7 +179,8 @@ func (v *Vector) Length() float64 { return math.Sqrt(v.Dot(v)) } -// SetLength sets the length of this Vector without changing the direction. +// SetLength sets the length of this Vector without changing the direction. The length will be exact within +// d2math.Epsilon. See d2math.EqualsApprox. func (v *Vector) SetLength(length float64) *Vector { v.Normalize() v.Scale(length) diff --git a/d2common/d2math/d2vector/vector_test.go b/d2common/d2math/d2vector/vector_test.go index 3ce5eb73..71ed9d93 100644 --- a/d2common/d2math/d2vector/vector_test.go +++ b/d2common/d2math/d2vector/vector_test.go @@ -7,6 +7,19 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math" ) +/*func TestMain(m *testing.M) { + setup() + os.Exit(m.Run()) +}*/ + +var outVector Vector +var outFloat float64 + +/*func setup() { +}*/ + +// TODO: Remove these evaluate functions. Throwing test errors outside the relevant functions means otherwise handly links to failed tests now point here which is annoying. + func evaluateVector(description string, want, got Vector, t *testing.T) { if !got.Equals(want) { t.Errorf("%s: wanted %s: got %s", description, want, got) @@ -236,84 +249,158 @@ func TestScale(t *testing.T) { evaluateVector(fmt.Sprintf("scale %s by 2", v), want, got, t) } -func TestAbs(t *testing.T) { +func TestVector_Abs(t *testing.T) { v := NewVector(-1, 1) want := NewVector(1, 1) got := v.Clone() got.Abs() - evaluateVector(fmt.Sprintf("absolute value of %s", v), want, got, t) + if !want.Equals(got) { + t.Errorf("absolute value of %s: want %s: got %s", v, want, got) + } } -func TestNegate(t *testing.T) { +func BenchmarkVector_Abs(b *testing.B) { + v := NewVector(-1, -1) + + for n := 0; n < b.N; n++ { + outVector = *v.Abs() + } +} + +func TestVector_Negate(t *testing.T) { v := NewVector(-1, 1) want := NewVector(1, -1) got := v.Clone() got.Negate() - evaluateVector(fmt.Sprintf("inverse value of %s", v), want, got, t) + if !want.Equals(got) { + t.Errorf("inverse value of %s: want %s: got %s", v, want, got) + } } -func TestDistance(t *testing.T) { +func BenchmarkVector_Negate(b *testing.B) { + v := NewVector(1, 1) + + for n := 0; n < b.N; n++ { + outVector = *v.Negate() + } +} + +func TestVector_Distance(t *testing.T) { v := NewVector(1, 3) other := NewVector(1, -1) want := 4.0 c := v.Clone() got := c.Distance(other) - evaluateScalar(fmt.Sprintf("distance from %s to %s", v, other), want, got, t) + if got != want { + t.Errorf("distance from %s to %s: want %.3f: got %.3f", v, other, want, got) + } } -func TestLength(t *testing.T) { +func BenchmarkVector_Distance(b *testing.B) { + v := NewVector(1, 1) + d := NewVector(2, 2) + + for n := 0; n < b.N; n++ { + outFloat = v.Distance(d) + } +} + +func TestVector_Length(t *testing.T) { v := NewVector(2, 0) c := v.Clone() want := 2.0 - got := v.Length() + got := c.Length() - d := fmt.Sprintf("length of %s", c) + d := fmt.Sprintf("length of %s", v) - evaluateChanged(d, v, c, t) + if !c.Equals(v) { + t.Errorf("%s: changed vector %s to %s unexpectedly", d, v, c) + } - evaluateScalar(d, want, got, t) + if got != want { + t.Errorf("%s: want %.3f: got %.3f", d, want, got) + } } -func TestSetLength(t *testing.T) { +func BenchmarkVector_Length(b *testing.B) { + v := NewVector(1, 1) + + for n := 0; n < b.N; n++ { + outFloat = v.Length() + } +} + +func TestVector_SetLength(t *testing.T) { v := NewVector(1, 1) c := v.Clone() want := 2.0 - got := v.SetLength(want).Length() + got := c.SetLength(want).Length() - d := fmt.Sprintf("length of %s", c) - - evaluateScalarApprox(d, want, got, t) + if !d2math.EqualsApprox(got, want) { + t.Errorf("set length of %s to %.3f :want %.3f: got %.3f", v, want, want, got) + } } -func TestLerp(t *testing.T) { +func BenchmarkVector_SetLength(b *testing.B) { + v := NewVector(1, 1) + + for n := 0; n < b.N; n++ { + v.SetLength(5) + } +} + +func TestVector_Lerp(t *testing.T) { a := NewVector(0, 0) b := NewVector(-20, 10) - x := 0.3 + interp := 0.3 want := NewVector(-6, 3) - got := a.Lerp(&b, x) + got := a.Lerp(&b, interp) - evaluateVector(fmt.Sprintf("linear interpolation between %s and %s by %.2f", a, b, x), want, *got, t) + if !got.Equals(want) { + t.Errorf("linear interpolation between %s and %s by %.2f: want %s: got %s", a, b, interp, want, got) + } } -func TestDot(t *testing.T) { +func BenchmarkVector_Lerp(b *testing.B) { + v := NewVector(1, 1) + t := NewVector(1000, 1000) + + for n := 0; n < b.N; n++ { + v.Lerp(&t, 1.01) + } +} + +func TestVector_Dot(t *testing.T) { v := NewVector(1, 1) c := v.Clone() want := 2.0 - got := v.Dot(&v) + got := c.Dot(&c) - d := fmt.Sprintf("dot product of %s", c) + d := fmt.Sprintf("dot product of %s", v) - evaluateChanged(d, v, c, t) + if !c.Equals(v) { + t.Errorf("%s: changed vector %s to %s unexpectedly", d, v, c) + } - evaluateScalar(d, want, got, t) + if got != want { + t.Errorf("%s: want %.3f: got %.3f", d, want, got) + } } -func TestCross(t *testing.T) { +func BenchmarkVector_Dot(b *testing.B) { + v := NewVector(1, 1) + + for n := 0; n < b.N; n++ { + outFloat = v.Dot(&v) + } +} + +func TestVector_Cross(t *testing.T) { v := NewVector(1, 1) clock := NewVector(1, 0) @@ -322,88 +409,156 @@ func TestCross(t *testing.T) { want := -1.0 got := v.Cross(clock) - evaluateScalar(fmt.Sprintf("cross product of %s and %s", v, clock), want, got, t) + if got != want { + t.Errorf("cross product of %s and %s: want %.3f: got %.3f", v, clock, want, got) + } want = 1.0 got = v.Cross(anti) - evaluateScalar(fmt.Sprintf("cross product of %s and %s", v, anti), want, got, t) + if got != want { + t.Errorf("cross product of %s and %s: want %.3f: got %.3f", v, clock, want, got) + } } -func TestNormalize(t *testing.T) { +func BenchmarkVector_Cross(b *testing.B) { + v := NewVector(1, 1) + o := NewVector(0, 1) + + for n := 0; n < b.N; n++ { + outFloat = v.Cross(o) + } +} + +func TestVector_Normalize(t *testing.T) { v := NewVector(10, 0) c := v.Clone() want := NewVector(1, 0) - v.Normalize() + c.Normalize() - evaluateVector(fmt.Sprintf("normalize %s", c), want, v, t) + if !want.Equals(c) { + t.Errorf("normalize %s: want %s: got %s", v, want, c) + } v = NewVector(0, 10) c = v.Clone() want = NewVector(0, 1) - reverse := v.Normalize() + reverse := c.Normalize() - evaluateVector(fmt.Sprintf("normalize %s", c), want, v, t) + if !want.Equals(c) { + t.Errorf("normalize %s: want %s: got %s", v, want, c) + } want = NewVector(0, 10) - v.Scale(reverse) + c.Scale(reverse) - evaluateVector(fmt.Sprintf("reverse normalizing of %s", c), want, v, t) + if !want.Equals(c) { + t.Errorf("reverse normalizing of %s: want %s: got %s", v, want, c) + } v = NewVector(0, 0) c = v.Clone() want = NewVector(0, 0) - v.Normalize() + c.Normalize() - evaluateVector(fmt.Sprintf("normalize zero vector should do nothing %s", c), want, v, t) + if !want.Equals(c) { + t.Errorf("normalize zero vector %s should do nothing: want %s: got %s", v, want, c) + } } -func TestAngle(t *testing.T) { +func BenchmarkVector_Normalize(b *testing.B) { + v := NewVector(1, 1) + + for n := 0; n < b.N; n++ { + v.Normalize() + } +} + +func TestVector_Angle(t *testing.T) { v := NewVector(0, 1) c := v.Clone() other := NewVector(1, 0.3) - d := fmt.Sprintf("angle from %s to %s", c, other) - want := 1.2793395323170293 - got := v.Angle(other) + got := c.Angle(other) - evaluateScalar(d, want, got, t) - evaluateChanged(d, v, c, t) + d := fmt.Sprintf("angle from %s to %s", v, other) + + if got != want { + t.Errorf("%s: want %g: got %g", d, want, got) + } + + if !c.Equals(v) { + t.Errorf("%s: changed vector %s to %s unexpectedly", d, v, c) + } other.Set(-1, 0.3) - c = other.Clone() + co := other.Clone() + got = c.Angle(other) d = fmt.Sprintf("angle from %s to %s", c, other) - got = v.Angle(other) + if got != want { + t.Errorf("%s: want %g: got %g", d, want, got) + } - evaluateScalar(d, want, got, t) - evaluateChanged(d, other, c, t) + if !co.Equals(other) { + t.Errorf("%s: changed vector %s to %s unexpectedly", d, co, other) + } } -func TestSignedAngle(t *testing.T) { +func BenchmarkVector_Angle(b *testing.B) { + v := NewVector(1, 1) + o := NewVector(0, 1) + + for n := 0; n < b.N; n++ { + outFloat = v.Angle(o) + } +} + +func TestVector_SignedAngle(t *testing.T) { v := NewVector(0, 1) c := v.Clone() other := NewVector(1, 0.3) want := 1.2793395323170293 - got := v.SignedAngle(other) + got := c.SignedAngle(other) d := fmt.Sprintf("angle from %s to %s", v, other) - evaluateScalar(d, want, got, t) - evaluateChanged(d, v, c, t) + + if got != want { + t.Errorf("%s: want %g: got %g", d, want, got) + } + + if !c.Equals(v) { + t.Errorf("%s: changed vector %s to %s unexpectedly", d, v, c) + } other.Set(-1, 0.3) - c = other.Clone() + co := other.Clone() want = 5.0038457214660585 - got = v.SignedAngle(other) + got = c.SignedAngle(other) d = fmt.Sprintf("angle from %s to %s", v, other) - evaluateScalar(d, want, got, t) - evaluateChanged(d, other, c, t) + + if got != want { + t.Errorf("%s: want %g: got %g", d, want, got) + } + + if !co.Equals(other) { + t.Errorf("%s: changed vector %s to %s unexpectedly", d, co, other) + } +} + +func BenchmarkVector_SignedAngle(b *testing.B) { + v := NewVector(1, 1) + o := NewVector(0, 1) + + for n := 0; n < b.N; n++ { + outFloat = v.SignedAngle(o) + } } func TestReflect(t *testing.T) { diff --git a/d2common/d2math/math.go b/d2common/d2math/math.go index b98856aa..f5e42f29 100644 --- a/d2common/d2math/math.go +++ b/d2common/d2math/math.go @@ -1,7 +1,5 @@ package d2math -import "math" - const ( // Epsilon is used as the threshold for 'almost equal' operations. Epsilon float64 = 0.0001 @@ -16,7 +14,7 @@ const ( // EqualsApprox returns true if the difference between a and b is less than Epsilon. func EqualsApprox(a, b float64) bool { - return math.Abs(a-b) < Epsilon + return Abs(a-b) < Epsilon } // CompareFloat64Fuzzy returns an integer between -1 and 1 describing @@ -24,7 +22,8 @@ func EqualsApprox(a, b float64) bool { // absolute difference between a and b is less than Epsilon. func CompareFloat64Fuzzy(a, b float64) int { delta := a - b - if math.Abs(delta) < Epsilon { + + if Abs(delta) < Epsilon { return 0 } @@ -35,6 +34,15 @@ func CompareFloat64Fuzzy(a, b float64) int { return -1 } +// Abs returns the absolute value of a. It is a less CPU intensive version of the standard library math.Abs(). +func Abs(a float64) float64 { + if a < 0 { + return a * -1 + } + + return a +} + // ClampFloat64 returns a clamped to min and max. func ClampFloat64(a, min, max float64) float64 { if a > max { diff --git a/d2core/d2map/d2mapentity/animated_entity.go b/d2core/d2map/d2mapentity/animated_entity.go index 70b47f4e..d5b31804 100644 --- a/d2core/d2map/d2mapentity/animated_entity.go +++ b/d2core/d2map/d2mapentity/animated_entity.go @@ -17,7 +17,7 @@ type AnimatedEntity struct { // CreateAnimatedEntity creates an instance of AnimatedEntity func CreateAnimatedEntity(x, y int, animation d2interface.Animation) *AnimatedEntity { entity := &AnimatedEntity{ - mapEntity: createMapEntity(x, y), + mapEntity: newMapEntity(x, y), animation: animation, } entity.mapEntity.directioner = entity.rotate @@ -30,7 +30,7 @@ func (ae *AnimatedEntity) Render(target d2interface.Surface) { renderOffset := ae.Position.RenderOffset() target.PushTranslation( int((renderOffset.X()-renderOffset.Y())*16), - int(((renderOffset.X() + renderOffset.Y()) * 8)), + int(((renderOffset.X()+renderOffset.Y())*8)-5), ) defer target.Pop() diff --git a/d2core/d2map/d2mapentity/map_entity.go b/d2core/d2map/d2mapentity/map_entity.go index 719ca4d9..a34b0b82 100644 --- a/d2core/d2map/d2mapentity/map_entity.go +++ b/d2core/d2map/d2mapentity/map_entity.go @@ -11,6 +11,7 @@ import ( type mapEntity struct { Position d2vector.Position Target d2vector.Position + velocity d2vector.Vector Speed float64 path []d2astar.Pather @@ -20,8 +21,8 @@ type mapEntity struct { directioner func(direction int) } -// createMapEntity creates an instance of mapEntity -func createMapEntity(x, y int) mapEntity { +// newMapEntity creates an instance of mapEntity +func newMapEntity(x, y int) mapEntity { locX, locY := float64(x), float64(y) return mapEntity{ @@ -43,6 +44,7 @@ func (m *mapEntity) GetLayer() int { func (m *mapEntity) SetPath(path []d2astar.Pather, done func()) { m.path = path m.done = done + m.nextPath() } // ClearPath clears the entity movement path. @@ -60,14 +62,9 @@ func (m *mapEntity) GetSpeed() float64 { return m.Speed } -// IsAtTarget returns true if the distance between entity and target is almost zero. -func (m *mapEntity) IsAtTarget() bool { - return m.Position.EqualsApprox(m.Target.Vector) && !m.HasPathFinding() -} - // 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.IsAtTarget() { + if m.atTarget() && !m.hasPath() { if m.done != nil { m.done() m.done = nil @@ -76,47 +73,39 @@ func (m *mapEntity) Step(tickTime float64) { return } - velocity := m.velocity(tickTime) + // Set velocity (speed and direction) + m.setVelocity(tickTime * m.Speed) + // This loop handles the situation where the velocity exceeds the distance to the current target. Each repitition applies + // the remaining velocity in the direction of the next path target. for { - // Add the velocity to the position and set new velocity to remainder - applyVelocity(&m.Position.Vector, &velocity, &m.Target.Vector) + applyVelocity(&m.Position.Vector, &m.velocity, &m.Target.Vector) - // New position is at target - if m.Position.EqualsApprox(m.Target.Vector) { - if len(m.path) > 0 { - // Set next path node - m.SetTarget(m.path[0].(*d2common.PathTile).X*5, m.path[0].(*d2common.PathTile).Y*5, m.done) - - if len(m.path) > 1 { - m.path = m.path[1:] - } else { - m.path = []d2astar.Pather{} - } - } else { - // End of path. - m.Position.Copy(&m.Target.Vector) - } + if m.atTarget() { + m.nextPath() } - if velocity.IsZero() { + if m.velocity.IsZero() { break } } } -// velocity returns a vector describing the change in position this tick. -func (m *mapEntity) velocity(tickTime float64) d2vector.Vector { - length := tickTime * m.Speed - v := m.Target.Vector.Clone() - v.Subtract(&m.Position.Vector) - v.SetLength(length) - - return v +// 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) } -// applyVelocity adds velocity to position. If the new position extends beyond target from the original position, the -// position is set to the target and velocity is set to the overshot amount. +// 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) @@ -149,21 +138,49 @@ func applyVelocity(position, velocity, target *d2vector.Vector) { } } -// HasPathFinding returns false if the length of the entity movement path is 0. -func (m *mapEntity) HasPathFinding() bool { +// Returns false if the path has ended. +func (m *mapEntity) nextPath() { + if m.hasPath() { + // Set next path node + m.setTarget( + m.path[0].(*d2common.PathTile).X*5, + m.path[0].(*d2common.PathTile).Y*5, + m.done, + ) + + if len(m.path) > 1 { + m.path = m.path[1:] + } else { + m.path = []d2astar.Pather{} + } + } 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(tx, ty float64, done func()) { +// setTarget sets target coordinates and changes animation based on proximity and direction. +func (m *mapEntity) setTarget(tx, ty float64, done func()) { + // Set the target m.Target.Set(tx, ty) 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. diff --git a/d2core/d2map/d2mapentity/map_entity_test.go b/d2core/d2map/d2mapentity/map_entity_test.go new file mode 100644 index 00000000..229deba6 --- /dev/null +++ b/d2core/d2map/d2mapentity/map_entity_test.go @@ -0,0 +1,93 @@ +package d2mapentity + +import ( + "os" + "testing" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" + + "github.com/OpenDiablo2/OpenDiablo2/d2common" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2astar" +) + +var stepEntity mapEntity + +const ( + normalTickTime float64 = 0.05 +) + +func TestMain(m *testing.M) { + setup() + os.Exit(m.Run()) +} + +func setup() { + setupBenchmarkMapEntityStep() +} + +func entity() mapEntity { + return newMapEntity(10, 10) +} + +func movingEntity() mapEntity { + e := entity() + e.SetSpeed(9) + newPath := path(10, e.Position) + e.SetPath(newPath, func() {}) + + return e +} + +func path(length int, origin d2vector.Position) []d2astar.Pather { + path := make([]d2astar.Pather, length) + + for i := 0; i < length; i++ { + origin.AddScalar(float64(i+1) / 5) + tile := origin.World() + path[i] = pathTile(tile.X(), tile.Y()) + } + + return path +} + +func pathTile(x, y float64) *d2common.PathTile { + return &d2common.PathTile{X: x, Y: y} +} + +func TestMapEntity_Step(t *testing.T) { + stepCount := 10 + e := movingEntity() + start := e.Position.Clone() + + for i := 0; i < stepCount; i++ { + e.Step(normalTickTime) + } + + // velocity + change := d2vector.NewVector(0, 0) + change.Copy(&e.Target.Vector) + change.Subtract(&e.Position.Vector) + change.SetLength(e.Speed * normalTickTime) + // change in position + change.Scale(float64(stepCount)) + + want := change.Add(&start) + + if !e.Position.EqualsApprox(*want) { + t.Errorf("entity position after %d steps: want %s: got %s", stepCount, want, e.Position.Vector) + } + + if e.Position.Equals(start) { + t.Errorf("entity did not move, still at position %s", start) + } +} + +func setupBenchmarkMapEntityStep() { + stepEntity = movingEntity() +} + +func BenchmarkMapEntity_Step(b *testing.B) { + for n := 0; n < b.N; n++ { + stepEntity.Step(normalTickTime) + } +} diff --git a/d2core/d2map/d2mapentity/missile.go b/d2core/d2map/d2mapentity/missile.go index f1e221f6..992e2504 100644 --- a/d2core/d2map/d2mapentity/missile.go +++ b/d2core/d2map/d2mapentity/missile.go @@ -54,7 +54,7 @@ func (m *Missile) SetRadians(angle float64, done func()) { x := m.Position.X() + (r * math.Cos(angle)) y := m.Position.Y() + (r * math.Sin(angle)) - m.SetTarget(x, y, done) + m.setTarget(x, y, done) } // Advance is called once per frame and processes a diff --git a/d2core/d2map/d2mapentity/npc.go b/d2core/d2map/d2mapentity/npc.go index 1c3fc17f..52a47872 100644 --- a/d2core/d2map/d2mapentity/npc.go +++ b/d2core/d2map/d2mapentity/npc.go @@ -31,7 +31,7 @@ type NPC struct { // CreateNPC creates a new NPC and returns a pointer to it. func CreateNPC(x, y int, monstat *d2datadict.MonStatsRecord, direction int) *NPC { result := &NPC{ - mapEntity: createMapEntity(x, y), + mapEntity: newMapEntity(x, y), HasPaths: false, monstatRecord: monstat, monstatEx: d2datadict.MonStats2[monstat.ExtraDataKey], @@ -75,7 +75,7 @@ func (v *NPC) Render(target d2interface.Surface) { renderOffset := v.Position.RenderOffset() target.PushTranslation( int((renderOffset.X()-renderOffset.Y())*16), - int(((renderOffset.X() + renderOffset.Y()) * 8)), + int(((renderOffset.X()+renderOffset.Y())*8)-5), ) defer target.Pop() @@ -116,7 +116,7 @@ func (v *NPC) Advance(tickTime float64) { // If at the target, set target to the next path. v.isDone = false path := v.NextPath() - v.SetTarget( + v.setTarget( float64(path.X), float64(path.Y), v.next, @@ -159,7 +159,7 @@ func (v *NPC) next() { // rotate sets direction and changes animation func (v *NPC) rotate(direction int) { var newMode d2enum.MonsterAnimationMode - if !v.IsAtTarget() { + if !v.atTarget() { newMode = d2enum.MonsterAnimationModeWalk } else { newMode = d2enum.MonsterAnimationModeNeutral diff --git a/d2core/d2map/d2mapentity/player.go b/d2core/d2map/d2mapentity/player.go index 36ff178f..9f30d4b8 100644 --- a/d2core/d2map/d2mapentity/player.go +++ b/d2core/d2map/d2mapentity/player.go @@ -57,7 +57,7 @@ func CreatePlayer(id, name string, x, y int, direction int, heroType d2enum.Hero result := &Player{ Id: id, - mapEntity: createMapEntity(x, y), + mapEntity: newMapEntity(x, y), composite: composite, Equipment: equipment, Stats: stats, @@ -147,7 +147,7 @@ func (v *Player) Render(target d2interface.Surface) { renderOffset := v.Position.RenderOffset() target.PushTranslation( int((renderOffset.X()-renderOffset.Y())*16), - int(((renderOffset.X() + renderOffset.Y()) * 8)), + int(((renderOffset.X()+renderOffset.Y())*8)-5), ) defer target.Pop() @@ -159,19 +159,19 @@ func (v *Player) Render(target d2interface.Surface) { // GetAnimationMode returns the current animation mode based on what the player is doing and where they are. func (v *Player) GetAnimationMode() d2enum.PlayerAnimationMode { - if v.IsRunning() && !v.IsAtTarget() { + if v.IsRunning() && !v.atTarget() { return d2enum.PlayerAnimationModeRun } if v.IsInTown() { - if !v.IsAtTarget() { + if !v.atTarget() { return d2enum.PlayerAnimationModeTownWalk } return d2enum.PlayerAnimationModeTownNeutral } - if !v.IsAtTarget() { + if !v.atTarget() { return d2enum.PlayerAnimationModeWalk }