mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-02-07 00:56:51 -05:00
Setup NPCs to follow paths (#243)
Change location to contain canonical location, add field to get rounded location for tile rendering. If NPC has path, loop through path.
This commit is contained in:
parent
88b9fb6207
commit
b5db51800c
@ -77,7 +77,8 @@ func (v *Game) Load() []func() {
|
||||
v.mapEngine = d2mapengine.CreateMapEngine(v.gameState, v.soundManager, v.fileProvider)
|
||||
// TODO: This needs to be different depending on the act of the player
|
||||
v.mapEngine.GenerateMap(d2enum.RegionAct1Town, 1, 0)
|
||||
region := v.mapEngine.GetRegion(0)
|
||||
v.mapEngine.SetRegion(0)
|
||||
region := v.mapEngine.Region()
|
||||
rx, ry := d2helper.IsoToScreen(region.Region.StartX, region.Region.StartY, 0, 0)
|
||||
v.mapEngine.CenterCameraOn(rx, ry)
|
||||
v.mapEngine.Hero = d2core.CreateHero(
|
||||
@ -108,13 +109,34 @@ func (v *Game) Update(tickTime float64) {
|
||||
v.mapEngine.Hero.AnimatedEntity.Step(tickTime)
|
||||
}
|
||||
|
||||
for _, npc := range v.mapEngine.Region().Region.NPCs {
|
||||
|
||||
if npc.HasPaths &&
|
||||
npc.AnimatedEntity.LocationX == npc.AnimatedEntity.TargetX &&
|
||||
npc.AnimatedEntity.LocationY == npc.AnimatedEntity.TargetY {
|
||||
// If at the target, set target to the next path.
|
||||
// TODO: pause at target, figure out how to use Path.Action
|
||||
path := npc.NextPath()
|
||||
npc.AnimatedEntity.SetTarget(
|
||||
float64(path.X),
|
||||
float64(path.Y),
|
||||
)
|
||||
}
|
||||
|
||||
if npc.AnimatedEntity.LocationX != npc.AnimatedEntity.TargetX ||
|
||||
npc.AnimatedEntity.LocationY != npc.AnimatedEntity.TargetY {
|
||||
npc.AnimatedEntity.Step(tickTime)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
|
||||
mx, my := ebiten.CursorPosition()
|
||||
px, py := d2helper.ScreenToIso(float64(mx)-v.mapEngine.OffsetX, float64(my)-v.mapEngine.OffsetY)
|
||||
|
||||
v.mapEngine.Hero.AnimatedEntity.SetTarget(px, py)
|
||||
v.mapEngine.Hero.AnimatedEntity.SetTarget(px*5, py*5)
|
||||
}
|
||||
|
||||
rx, ry := d2helper.IsoToScreen(v.mapEngine.Hero.AnimatedEntity.LocationX, v.mapEngine.Hero.AnimatedEntity.LocationY, 0, 0)
|
||||
v.mapEngine.CenterCameraOn(float64(rx), float64(ry))
|
||||
rx, ry := d2helper.IsoToScreen(v.mapEngine.Hero.AnimatedEntity.LocationX/5, v.mapEngine.Hero.AnimatedEntity.LocationY/5, 0, 0)
|
||||
v.mapEngine.CenterCameraOn(rx, ry)
|
||||
}
|
||||
|
@ -11,19 +11,36 @@ import (
|
||||
|
||||
type NPC struct {
|
||||
AnimatedEntity d2render.AnimatedEntity
|
||||
HasPaths bool
|
||||
Paths []d2common.Path
|
||||
path int
|
||||
}
|
||||
|
||||
func CreateNPC(x, y int32, object *d2datadict.ObjectLookupRecord, fileProvider d2interface.FileProvider, direction int) *NPC {
|
||||
result := &NPC{
|
||||
AnimatedEntity: d2render.CreateAnimatedEntity(x, y, object, fileProvider, d2enum.Units),
|
||||
HasPaths: false,
|
||||
}
|
||||
result.AnimatedEntity.SetMode(object.Mode, object.Class, direction)
|
||||
return result
|
||||
}
|
||||
|
||||
func (v *NPC) Path() d2common.Path {
|
||||
return v.Paths[v.path]
|
||||
}
|
||||
|
||||
func (v *NPC) NextPath() d2common.Path {
|
||||
v.path++
|
||||
if v.path == len(v.Paths) {
|
||||
v.path = 0
|
||||
}
|
||||
|
||||
return v.Paths[v.path]
|
||||
}
|
||||
|
||||
func (v *NPC) SetPaths(paths []d2common.Path) {
|
||||
v.Paths = paths
|
||||
v.HasPaths = len(paths) > 0
|
||||
}
|
||||
|
||||
func (v *NPC) Render(target *ebiten.Image, offsetX, offsetY int) {
|
||||
|
@ -33,12 +33,11 @@ type LayerCacheEntry struct {
|
||||
|
||||
// AnimatedEntity represents an entity on the map that can be animated
|
||||
type AnimatedEntity struct {
|
||||
fileProvider d2interface.FileProvider
|
||||
// LocationX represents the tile X position of the entity
|
||||
LocationX float64
|
||||
// LocationY represents the tile Y position of the entity
|
||||
subcellX, subcellY float64 // Subcell coordinates within the current tile
|
||||
fileProvider d2interface.FileProvider
|
||||
LocationX float64
|
||||
LocationY float64
|
||||
TileX, TileY int // Coordinates of the tile the unit is within
|
||||
subcellX, subcellY float64 // Subcell coordinates within the current tile
|
||||
dccLayers map[string]d2dcc.DCC
|
||||
Cof *d2cof.COF
|
||||
palette d2enum.PaletteType
|
||||
@ -72,13 +71,15 @@ func CreateAnimatedEntity(x, y int32, object *d2datadict.ObjectLookupRecord, fil
|
||||
//frameLocations: []d2common.Rectangle{},
|
||||
}
|
||||
result.dccLayers = make(map[string]d2dcc.DCC)
|
||||
result.LocationX = float64(x) / 5
|
||||
result.LocationY = float64(y) / 5
|
||||
result.LocationX = float64(x)
|
||||
result.LocationY = float64(y)
|
||||
result.TargetX = result.LocationX
|
||||
result.TargetY = result.LocationY
|
||||
|
||||
result.subcellX = 1 + math.Mod(float64(x), 5)
|
||||
result.subcellY = 1 + math.Mod(float64(y), 5)
|
||||
result.TileX = int(result.LocationX / 5)
|
||||
result.TileY = int(result.LocationY / 5)
|
||||
result.subcellX = 1 + math.Mod(result.LocationX, 5)
|
||||
result.subcellY = 1 + math.Mod(result.LocationY, 5)
|
||||
|
||||
return result
|
||||
}
|
||||
@ -309,7 +310,7 @@ func (v AnimatedEntity) GetDirection() int {
|
||||
}
|
||||
|
||||
func (v *AnimatedEntity) getStepLength(tickTime float64) (float64, float64) {
|
||||
speed := 2.5
|
||||
speed := 6.0
|
||||
length := tickTime * speed
|
||||
|
||||
angle := 359 - d2helper.GetAngleBetween(
|
||||
@ -318,7 +319,7 @@ func (v *AnimatedEntity) getStepLength(tickTime float64) (float64, float64) {
|
||||
v.TargetX,
|
||||
v.TargetY,
|
||||
)
|
||||
radians := (math.Pi / 180.0) * float64(angle)
|
||||
radians := (math.Pi / 180.0) * angle
|
||||
oneStepX := length * math.Cos(radians)
|
||||
oneStepY := length * math.Sin(radians)
|
||||
return oneStepX, oneStepY
|
||||
@ -339,9 +340,15 @@ func (v *AnimatedEntity) Step(tickTime float64) {
|
||||
if v.LocationY != v.TargetY {
|
||||
v.LocationY += stepY
|
||||
}
|
||||
|
||||
v.subcellX = 1 + math.Mod(v.LocationX, 5)
|
||||
v.subcellY = 1 + math.Mod(v.LocationY, 5)
|
||||
v.TileX = int(v.LocationX / 5)
|
||||
v.TileY = int(v.LocationY / 5)
|
||||
|
||||
if v.LocationX == v.TargetX && v.LocationY == v.TargetY {
|
||||
if v.animationMode != d2enum.AnimationModePlayerTownNeutral.String() {
|
||||
v.SetMode(d2enum.AnimationModePlayerTownNeutral.String(), v.weaponClass, v.direction)
|
||||
if v.animationMode != d2enum.AnimationModeObjectNeutral.String() {
|
||||
v.SetMode(d2enum.AnimationModeObjectNeutral.String(), v.weaponClass, v.direction)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -354,13 +361,28 @@ func (v *AnimatedEntity) SetTarget(tx, ty float64) {
|
||||
tx,
|
||||
ty,
|
||||
)
|
||||
newAnimationMode := d2enum.AnimationModePlayerTownNeutral.String()
|
||||
// TODO: Check if is in town and if is player.
|
||||
newAnimationMode := d2enum.AnimationModeMonsterWalk.String()
|
||||
if tx != v.LocationX || ty != v.LocationY {
|
||||
v.TargetX, v.TargetY = tx, ty
|
||||
newAnimationMode = d2enum.AnimationModePlayerTownWalk.String()
|
||||
newAnimationMode = d2enum.AnimationModeMonsterWalk.String()
|
||||
}
|
||||
newDirection := int((float64(angle) / 360.0) * 16.0)
|
||||
|
||||
newDirection := angleToDirection(angle, v.Cof.NumberOfDirections)
|
||||
if newDirection != v.GetDirection() || newAnimationMode != v.animationMode {
|
||||
v.SetMode(newAnimationMode, v.weaponClass, newDirection)
|
||||
}
|
||||
}
|
||||
|
||||
func angleToDirection(angle float64, numberOfDirections int) int {
|
||||
degreesPerDirection := 360.0 / float64(numberOfDirections)
|
||||
offset := 45.0 - (degreesPerDirection / 2)
|
||||
newDirection := int((angle - offset) / degreesPerDirection)
|
||||
if newDirection >= numberOfDirections {
|
||||
newDirection = newDirection - numberOfDirections
|
||||
} else if newDirection < 0 {
|
||||
newDirection = numberOfDirections + newDirection
|
||||
}
|
||||
|
||||
return newDirection
|
||||
}
|
||||
|
54
d2render/animated_entity_test.go
Normal file
54
d2render/animated_entity_test.go
Normal file
@ -0,0 +1,54 @@
|
||||
package d2render
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAngleToDirection_16Directions(t *testing.T) {
|
||||
|
||||
numberOfDirections := 16
|
||||
|
||||
angle := 45.0
|
||||
for i := 0; i < numberOfDirections; i++ {
|
||||
assert.Equal(t, i, angleToDirection(angle, numberOfDirections))
|
||||
angle += 22.5
|
||||
}
|
||||
|
||||
angle = 50.0
|
||||
for i := 0; i < numberOfDirections; i++ {
|
||||
assert.Equal(t, i, angleToDirection(angle, numberOfDirections))
|
||||
angle += 22.5
|
||||
}
|
||||
|
||||
angle = 40.0
|
||||
for i := 0; i < numberOfDirections; i++ {
|
||||
assert.Equal(t, i, angleToDirection(angle, numberOfDirections))
|
||||
angle += 22.5
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAngleToDirection_8Directions(t *testing.T) {
|
||||
|
||||
numberOfDirections := 8
|
||||
|
||||
angle := 45.0
|
||||
for i := 0; i < numberOfDirections; i++ {
|
||||
assert.Equal(t, i, angleToDirection(angle, numberOfDirections))
|
||||
angle += 45
|
||||
}
|
||||
|
||||
angle = 50.0
|
||||
for i := 0; i < numberOfDirections; i++ {
|
||||
assert.Equal(t, i, angleToDirection(angle, numberOfDirections))
|
||||
angle += 45
|
||||
}
|
||||
|
||||
angle = 40.0
|
||||
for i := 0; i < numberOfDirections; i++ {
|
||||
assert.Equal(t, i, angleToDirection(angle, numberOfDirections))
|
||||
angle += 45
|
||||
}
|
||||
|
||||
}
|
@ -3,7 +3,6 @@ package d2mapengine
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||
@ -35,6 +34,7 @@ type Engine struct {
|
||||
soundManager *d2audio.Manager
|
||||
gameState *d2core.GameState
|
||||
fileProvider d2interface.FileProvider
|
||||
region int
|
||||
regions []EngineRegion
|
||||
OffsetX float64
|
||||
OffsetY float64
|
||||
@ -52,6 +52,14 @@ func CreateMapEngine(gameState *d2core.GameState, soundManager *d2audio.Manager,
|
||||
return result
|
||||
}
|
||||
|
||||
func (v *Engine) Region() *EngineRegion {
|
||||
return &v.regions[v.region]
|
||||
}
|
||||
|
||||
func (v *Engine) SetRegion(region int) {
|
||||
v.region = region
|
||||
}
|
||||
|
||||
func (v *Engine) GetRegion(regionIndex int) *EngineRegion {
|
||||
return &v.regions[regionIndex]
|
||||
}
|
||||
@ -244,17 +252,17 @@ func (v *Engine) RenderPass2(region *Region, offX, offY, x, y int, target *ebite
|
||||
}
|
||||
|
||||
for _, obj := range region.AnimationEntities {
|
||||
if int(math.Floor(obj.LocationX)) == x && int(math.Floor(obj.LocationY)) == y {
|
||||
if obj.TileX == x && obj.TileY == y {
|
||||
obj.Render(target, offX+int(v.OffsetX), offY+int(v.OffsetY))
|
||||
}
|
||||
}
|
||||
for _, npc := range region.NPCs {
|
||||
if int(math.Floor(npc.AnimatedEntity.LocationX)) == x && int(math.Floor(npc.AnimatedEntity.LocationY)) == y {
|
||||
if npc.AnimatedEntity.TileX == x && npc.AnimatedEntity.TileY == y {
|
||||
npc.Render(target, offX+int(v.OffsetX), offY+int(v.OffsetY))
|
||||
}
|
||||
}
|
||||
if v.Hero != nil && int(v.Hero.AnimatedEntity.LocationX) == x && int(v.Hero.AnimatedEntity.LocationY) == y {
|
||||
v.Hero.Render(target, 400, 300)
|
||||
if v.Hero != nil && v.Hero.AnimatedEntity.TileX == x && v.Hero.AnimatedEntity.TileY == y {
|
||||
v.Hero.Render(target, offX+int(v.OffsetX), offY+int(v.OffsetY))
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user