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)
|
v.mapEngine = d2mapengine.CreateMapEngine(v.gameState, v.soundManager, v.fileProvider)
|
||||||
// TODO: This needs to be different depending on the act of the player
|
// TODO: This needs to be different depending on the act of the player
|
||||||
v.mapEngine.GenerateMap(d2enum.RegionAct1Town, 1, 0)
|
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)
|
rx, ry := d2helper.IsoToScreen(region.Region.StartX, region.Region.StartY, 0, 0)
|
||||||
v.mapEngine.CenterCameraOn(rx, ry)
|
v.mapEngine.CenterCameraOn(rx, ry)
|
||||||
v.mapEngine.Hero = d2core.CreateHero(
|
v.mapEngine.Hero = d2core.CreateHero(
|
||||||
@ -108,13 +109,34 @@ func (v *Game) Update(tickTime float64) {
|
|||||||
v.mapEngine.Hero.AnimatedEntity.Step(tickTime)
|
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) {
|
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
|
||||||
mx, my := ebiten.CursorPosition()
|
mx, my := ebiten.CursorPosition()
|
||||||
px, py := d2helper.ScreenToIso(float64(mx)-v.mapEngine.OffsetX, float64(my)-v.mapEngine.OffsetY)
|
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)
|
rx, ry := d2helper.IsoToScreen(v.mapEngine.Hero.AnimatedEntity.LocationX/5, v.mapEngine.Hero.AnimatedEntity.LocationY/5, 0, 0)
|
||||||
v.mapEngine.CenterCameraOn(float64(rx), float64(ry))
|
v.mapEngine.CenterCameraOn(rx, ry)
|
||||||
}
|
}
|
||||||
|
@ -11,19 +11,36 @@ import (
|
|||||||
|
|
||||||
type NPC struct {
|
type NPC struct {
|
||||||
AnimatedEntity d2render.AnimatedEntity
|
AnimatedEntity d2render.AnimatedEntity
|
||||||
|
HasPaths bool
|
||||||
Paths []d2common.Path
|
Paths []d2common.Path
|
||||||
|
path int
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateNPC(x, y int32, object *d2datadict.ObjectLookupRecord, fileProvider d2interface.FileProvider, direction int) *NPC {
|
func CreateNPC(x, y int32, object *d2datadict.ObjectLookupRecord, fileProvider d2interface.FileProvider, direction int) *NPC {
|
||||||
result := &NPC{
|
result := &NPC{
|
||||||
AnimatedEntity: d2render.CreateAnimatedEntity(x, y, object, fileProvider, d2enum.Units),
|
AnimatedEntity: d2render.CreateAnimatedEntity(x, y, object, fileProvider, d2enum.Units),
|
||||||
|
HasPaths: false,
|
||||||
}
|
}
|
||||||
result.AnimatedEntity.SetMode(object.Mode, object.Class, direction)
|
result.AnimatedEntity.SetMode(object.Mode, object.Class, direction)
|
||||||
return result
|
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) {
|
func (v *NPC) SetPaths(paths []d2common.Path) {
|
||||||
v.Paths = paths
|
v.Paths = paths
|
||||||
|
v.HasPaths = len(paths) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *NPC) Render(target *ebiten.Image, offsetX, offsetY int) {
|
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
|
// AnimatedEntity represents an entity on the map that can be animated
|
||||||
type AnimatedEntity struct {
|
type AnimatedEntity struct {
|
||||||
fileProvider d2interface.FileProvider
|
fileProvider d2interface.FileProvider
|
||||||
// LocationX represents the tile X position of the entity
|
LocationX float64
|
||||||
LocationX float64
|
|
||||||
// LocationY represents the tile Y position of the entity
|
|
||||||
subcellX, subcellY float64 // Subcell coordinates within the current tile
|
|
||||||
LocationY 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
|
dccLayers map[string]d2dcc.DCC
|
||||||
Cof *d2cof.COF
|
Cof *d2cof.COF
|
||||||
palette d2enum.PaletteType
|
palette d2enum.PaletteType
|
||||||
@ -72,13 +71,15 @@ func CreateAnimatedEntity(x, y int32, object *d2datadict.ObjectLookupRecord, fil
|
|||||||
//frameLocations: []d2common.Rectangle{},
|
//frameLocations: []d2common.Rectangle{},
|
||||||
}
|
}
|
||||||
result.dccLayers = make(map[string]d2dcc.DCC)
|
result.dccLayers = make(map[string]d2dcc.DCC)
|
||||||
result.LocationX = float64(x) / 5
|
result.LocationX = float64(x)
|
||||||
result.LocationY = float64(y) / 5
|
result.LocationY = float64(y)
|
||||||
result.TargetX = result.LocationX
|
result.TargetX = result.LocationX
|
||||||
result.TargetY = result.LocationY
|
result.TargetY = result.LocationY
|
||||||
|
|
||||||
result.subcellX = 1 + math.Mod(float64(x), 5)
|
result.TileX = int(result.LocationX / 5)
|
||||||
result.subcellY = 1 + math.Mod(float64(y), 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
|
return result
|
||||||
}
|
}
|
||||||
@ -309,7 +310,7 @@ func (v AnimatedEntity) GetDirection() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *AnimatedEntity) getStepLength(tickTime float64) (float64, float64) {
|
func (v *AnimatedEntity) getStepLength(tickTime float64) (float64, float64) {
|
||||||
speed := 2.5
|
speed := 6.0
|
||||||
length := tickTime * speed
|
length := tickTime * speed
|
||||||
|
|
||||||
angle := 359 - d2helper.GetAngleBetween(
|
angle := 359 - d2helper.GetAngleBetween(
|
||||||
@ -318,7 +319,7 @@ func (v *AnimatedEntity) getStepLength(tickTime float64) (float64, float64) {
|
|||||||
v.TargetX,
|
v.TargetX,
|
||||||
v.TargetY,
|
v.TargetY,
|
||||||
)
|
)
|
||||||
radians := (math.Pi / 180.0) * float64(angle)
|
radians := (math.Pi / 180.0) * angle
|
||||||
oneStepX := length * math.Cos(radians)
|
oneStepX := length * math.Cos(radians)
|
||||||
oneStepY := length * math.Sin(radians)
|
oneStepY := length * math.Sin(radians)
|
||||||
return oneStepX, oneStepY
|
return oneStepX, oneStepY
|
||||||
@ -339,9 +340,15 @@ func (v *AnimatedEntity) Step(tickTime float64) {
|
|||||||
if v.LocationY != v.TargetY {
|
if v.LocationY != v.TargetY {
|
||||||
v.LocationY += stepY
|
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.LocationX == v.TargetX && v.LocationY == v.TargetY {
|
||||||
if v.animationMode != d2enum.AnimationModePlayerTownNeutral.String() {
|
if v.animationMode != d2enum.AnimationModeObjectNeutral.String() {
|
||||||
v.SetMode(d2enum.AnimationModePlayerTownNeutral.String(), v.weaponClass, v.direction)
|
v.SetMode(d2enum.AnimationModeObjectNeutral.String(), v.weaponClass, v.direction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -354,13 +361,28 @@ func (v *AnimatedEntity) SetTarget(tx, ty float64) {
|
|||||||
tx,
|
tx,
|
||||||
ty,
|
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 {
|
if tx != v.LocationX || ty != v.LocationY {
|
||||||
v.TargetX, v.TargetY = tx, ty
|
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 {
|
if newDirection != v.GetDirection() || newAnimationMode != v.animationMode {
|
||||||
v.SetMode(newAnimationMode, v.weaponClass, newDirection)
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||||
@ -35,6 +34,7 @@ type Engine struct {
|
|||||||
soundManager *d2audio.Manager
|
soundManager *d2audio.Manager
|
||||||
gameState *d2core.GameState
|
gameState *d2core.GameState
|
||||||
fileProvider d2interface.FileProvider
|
fileProvider d2interface.FileProvider
|
||||||
|
region int
|
||||||
regions []EngineRegion
|
regions []EngineRegion
|
||||||
OffsetX float64
|
OffsetX float64
|
||||||
OffsetY float64
|
OffsetY float64
|
||||||
@ -52,6 +52,14 @@ func CreateMapEngine(gameState *d2core.GameState, soundManager *d2audio.Manager,
|
|||||||
return result
|
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 {
|
func (v *Engine) GetRegion(regionIndex int) *EngineRegion {
|
||||||
return &v.regions[regionIndex]
|
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 {
|
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))
|
obj.Render(target, offX+int(v.OffsetX), offY+int(v.OffsetY))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, npc := range region.NPCs {
|
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))
|
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 {
|
if v.Hero != nil && v.Hero.AnimatedEntity.TileX == x && v.Hero.AnimatedEntity.TileY == y {
|
||||||
v.Hero.Render(target, 400, 300)
|
v.Hero.Render(target, offX+int(v.OffsetX), offY+int(v.OffsetY))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user