1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-09 17:30:43 +00: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:
nicholas-eden 2019-12-06 06:44:52 -08:00 committed by Tim Sarbin
parent 88b9fb6207
commit b5db51800c
5 changed files with 148 additions and 25 deletions

View File

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

View File

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

View File

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

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

View File

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