1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-03 06:20:43 +00:00

Improve run/walk/neutral animation handling. Initial parsing of Level… (#372)

* Improve run/walk/neutral animation handling. Initial parsing of LevelDetail records. Support for holding mouse buttons.

- Run/walk/neutral positions now map to a different animation mode(and speed) depending if in or out of town.

- Run/walk toggle which can be activated/deactivated with R key.

- Temporary(and incorrect) loading and mapping for LevelDetails records.

- Zone change label which shows the level name from LevelDetailsRecord when the player enters a different zone.

- Allow holding mouse left/right button to repeatedly generate an action.

* Remove duplicate load of LevelDetails. Replace numbers in zone change logic with their corresponding RegionIdType

* Move zone change check at the correct place

Co-authored-by: Presiyan Ivanov <presiyan-ivanov@users.noreply.github.com>
This commit is contained in:
presiyan-ivanov 2020-06-22 22:55:32 +03:00 committed by GitHub
parent 8dcec1f209
commit 2835ff4cf1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 215 additions and 55 deletions

View File

@ -197,6 +197,10 @@ type MouseButtonDownHandler interface {
OnMouseButtonDown(event MouseEvent) bool OnMouseButtonDown(event MouseEvent) bool
} }
type MouseButtonRepeatHandler interface {
OnMouseButtonRepeat(event MouseEvent) bool
}
type MouseButtonUpHandler interface { type MouseButtonUpHandler interface {
OnMouseButtonUp(event MouseEvent) bool OnMouseButtonUp(event MouseEvent) bool
} }

View File

@ -126,6 +126,19 @@ func (im *inputManager) advance(elapsed float64) error {
}) })
} }
for button := ebiten.MouseButtonLeft; button < ebiten.MouseButtonMiddle; button++ {
if ebiten.IsMouseButtonPressed(button) {
event := MouseEvent{eventBase, MouseButton(button)}
im.propagate(func(handler Handler) bool {
if l, ok := handler.(MouseButtonRepeatHandler); ok {
return l.OnMouseButtonRepeat(event)
}
return false
})
}
}
if inpututil.IsMouseButtonJustReleased(button) { if inpututil.IsMouseButtonJustReleased(button) {
event := MouseEvent{eventBase, MouseButton(button)} event := MouseEvent{eventBase, MouseButton(button)}
im.propagate(func(handler Handler) bool { im.propagate(func(handler Handler) bool {

View File

@ -68,25 +68,51 @@ func (ac *AnimatedComposite) Render(target d2render.Surface) {
// rotate sets direction and changes animation // rotate sets direction and changes animation
func (ac *AnimatedComposite) rotate(angle float64) { func (ac *AnimatedComposite) rotate(angle float64) {
// TODO: Check if is in town and if is player. newAnimationMode := ac.GetAnimationMode().String()
newAnimationMode := ac.composite.GetAnimationMode()
if !ac.IsAtTarget() {
if ac.player != nil {
if ac.player.IsInTown() {
newAnimationMode = d2enum.AnimationModePlayerTownWalk.String()
} else {
newAnimationMode = d2enum.AnimationModePlayerWalk.String()
}
} else {
newAnimationMode = d2enum.AnimationModeMonsterWalk.String()
}
}
newDirection := angleToDirection(angle) newDirection := angleToDirection(angle)
if newAnimationMode != ac.composite.GetAnimationMode() || newDirection != ac.direction { if newAnimationMode != ac.composite.GetAnimationMode() || newDirection != ac.direction {
ac.SetMode(newAnimationMode, ac.weaponClass, newDirection) ac.SetMode(newAnimationMode, ac.weaponClass, newDirection)
} }
}
func (ac *AnimatedComposite) GetAnimationMode() d2enum.AnimationMode {
var newAnimationMode d2enum.AnimationMode
if ac.player != nil {
newAnimationMode = ac.GetPlayerAnimationMode()
} else {
newAnimationMode = ac.GetMonsterAnimationMode()
}
return newAnimationMode
}
func (ac *AnimatedComposite) GetPlayerAnimationMode() d2enum.AnimationMode {
if ac.player.IsRunning() && !ac.IsAtTarget(){
return d2enum.AnimationModePlayerRun
}
if ac.player.IsInTown() {
if !ac.IsAtTarget() {
return d2enum.AnimationModePlayerTownWalk
}
return d2enum.AnimationModePlayerTownNeutral
}
if !ac.IsAtTarget() {
return d2enum.AnimationModePlayerWalk
}
return d2enum.AnimationModePlayerNeutral
}
func (ac *AnimatedComposite) GetMonsterAnimationMode() d2enum.AnimationMode {
if !ac.IsAtTarget() {
return d2enum.AnimationModeMonsterWalk
}
return d2enum.AnimationModeMonsterNeutral
} }
func (ac *AnimatedComposite) Advance(elapsed float64) { func (ac *AnimatedComposite) Advance(elapsed float64) {

View File

@ -53,6 +53,14 @@ func (m *mapEntity) SetPath(path []astar.Pather, done func()) {
m.done = done m.done = done
} }
func (m *mapEntity) SetSpeed(speed float64) {
m.Speed = speed
}
func (m *mapEntity) GetSpeed() float64 {
return m.Speed
}
func (m *mapEntity) getStepLength(tickTime float64) (float64, float64) { func (m *mapEntity) getStepLength(tickTime float64) (float64, float64) {
length := tickTime * m.Speed length := tickTime * m.Speed

View File

@ -21,8 +21,14 @@ type Player struct {
lastPathSize int lastPathSize int
isInTown bool isInTown bool
animationMode string animationMode string
isRunToggled bool
isRunning bool
} }
// run speed should be walkspeed * 1.5, since in the original game it is 6 yards walk and 9 yards run.
var baseWalkSpeed = 6.0
var baseRunSpeed = 9.0
func CreatePlayer(id, name string, x, y int, direction int, heroType d2enum.Hero, equipment d2inventory.CharacterEquipment) *Player { func CreatePlayer(id, name string, x, y int, direction int, heroType d2enum.Hero, equipment d2inventory.CharacterEquipment) *Player {
object := &d2datadict.ObjectLookupRecord{ object := &d2datadict.ObjectLookupRecord{
Mode: d2enum.AnimationModePlayerTownNeutral.String(), Mode: d2enum.AnimationModePlayerTownNeutral.String(),
@ -44,6 +50,7 @@ func CreatePlayer(id, name string, x, y int, direction int, heroType d2enum.Hero
if err != nil { if err != nil {
panic(err) panic(err)
} }
entity.SetSpeed(baseRunSpeed)
result := &Player{ result := &Player{
Id: id, Id: id,
@ -52,6 +59,9 @@ func CreatePlayer(id, name string, x, y int, direction int, heroType d2enum.Hero
direction: direction, direction: direction,
Name: name, Name: name,
nameLabel: d2ui.CreateLabel(d2resource.FontFormal11, d2resource.PaletteStatic), nameLabel: d2ui.CreateLabel(d2resource.FontFormal11, d2resource.PaletteStatic),
isRunToggled: true,
isInTown: true,
isRunning: true,
} }
result.nameLabel.Alignment = d2ui.LabelAlignCenter result.nameLabel.Alignment = d2ui.LabelAlignCenter
result.nameLabel.SetText(name) result.nameLabel.SetText(name)
@ -68,6 +78,28 @@ func (p *Player) SetIsInTown(isInTown bool) {
p.isInTown = isInTown p.isInTown = isInTown
} }
func (p *Player) ToggleRunWalk() {
p.isRunToggled = !p.isRunToggled
}
func (p *Player) IsRunToggled() bool {
return p.isRunToggled
}
func (p *Player) IsRunning() bool {
return p.isRunning
}
func (p *Player) SetIsRunning(isRunning bool) {
p.isRunning = isRunning
if isRunning {
p.SetSpeed(baseRunSpeed)
} else {
p.SetSpeed(baseWalkSpeed)
}
}
func (p Player) IsInTown() bool { func (p Player) IsInTown() bool {
return p.isInTown return p.isInTown
} }

View File

@ -1,13 +1,15 @@
package d2gamescreen package d2gamescreen
import ( import (
"fmt"
"image/color" "image/color"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2maprenderer" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2input" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2input"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2maprenderer"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player" "github.com/OpenDiablo2/OpenDiablo2/d2game/d2player"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client"
@ -22,7 +24,7 @@ type Game struct {
mapRenderer *d2maprenderer.MapRenderer mapRenderer *d2maprenderer.MapRenderer
gameControls *d2player.GameControls // TODO: Hack gameControls *d2player.GameControls // TODO: Hack
localPlayer *d2mapentity.Player localPlayer *d2mapentity.Player
lastLevelType int lastRegionType d2enum.RegionIdType
ticksSinceLevelCheck float64 ticksSinceLevelCheck float64
} }
@ -31,7 +33,7 @@ func CreateGame(gameClient *d2client.GameClient) *Game {
gameClient: gameClient, gameClient: gameClient,
gameControls: nil, gameControls: nil,
localPlayer: nil, localPlayer: nil,
lastLevelType: -1, lastRegionType: d2enum.RegionNone,
ticksSinceLevelCheck: 0, ticksSinceLevelCheck: 0,
mapRenderer: d2maprenderer.CreateMapRenderer(gameClient.MapEngine), mapRenderer: d2maprenderer.CreateMapRenderer(gameClient.MapEngine),
} }
@ -61,6 +63,8 @@ func (v *Game) Render(screen d2render.Surface) error {
return nil return nil
} }
var hideZoneTextAfterSeconds = 2.0
func (v *Game) Advance(tickTime float64) error { func (v *Game) Advance(tickTime float64) error {
if !v.gameControls.InEscapeMenu() || len(v.gameClient.Players) != 1 { if !v.gameControls.InEscapeMenu() || len(v.gameClient.Players) != 1 {
v.gameClient.MapEngine.Advance(tickTime) // TODO: Hack v.gameClient.MapEngine.Advance(tickTime) // TODO: Hack
@ -77,13 +81,22 @@ func (v *Game) Advance(tickTime float64) error {
tile := v.gameClient.MapEngine.TileAt(v.localPlayer.TileX, v.localPlayer.TileY) tile := v.gameClient.MapEngine.TileAt(v.localPlayer.TileX, v.localPlayer.TileY)
if tile != nil { if tile != nil {
switch tile.RegionType { switch tile.RegionType {
case 1: // Rogue encampent case d2enum.RegionAct1Town: // Rogue encampent
v.localPlayer.SetIsInTown(true) v.localPlayer.SetIsInTown(true)
d2audio.PlayBGM("/data/global/music/Act1/town1.wav") d2audio.PlayBGM("/data/global/music/Act1/town1.wav")
case 2: // Blood Moore case d2enum.RegionAct1Wilderness: // Blood Moore
v.localPlayer.SetIsInTown(false) v.localPlayer.SetIsInTown(false)
d2audio.PlayBGM("/data/global/music/Act1/wild.wav") d2audio.PlayBGM("/data/global/music/Act1/wild.wav")
} }
// skip showing zone change text the first time we enter the world
if v.lastRegionType != d2enum.RegionNone && v.lastRegionType != tile.RegionType {
//TODO: Should not be using RegionType as an index - this will return incorrect LevelDetails record for most of the zones.
v.gameControls.SetZoneChangeText(fmt.Sprintf("Entering The %s", d2datadict.LevelDetails[int(tile.RegionType)].LevelDisplayName))
v.gameControls.ShowZoneChangeText()
v.gameControls.HideZoneChangeTextAfter(hideZoneTextAfterSeconds)
}
v.lastRegionType = tile.RegionType
} }
} }
} }

View File

@ -1,6 +1,9 @@
package d2player package d2player
import ( import (
"image/color"
"time"
"github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
@ -35,10 +38,12 @@ type GameControls struct {
FreeCam bool FreeCam bool
// UI // UI
globeSprite *d2ui.Sprite globeSprite *d2ui.Sprite
mainPanel *d2ui.Sprite mainPanel *d2ui.Sprite
menuButton *d2ui.Sprite menuButton *d2ui.Sprite
skillIcon *d2ui.Sprite skillIcon *d2ui.Sprite
zoneChangeText *d2ui.Label
isZoneTextShown bool
} }
func NewGameControls(hero *d2mapentity.Player, mapEngine *d2mapengine.MapEngine, mapRenderer *d2maprenderer.MapRenderer, inputListener InputCallbackListener) *GameControls { func NewGameControls(hero *d2mapentity.Player, mapEngine *d2mapengine.MapEngine, mapRenderer *d2maprenderer.MapRenderer, inputListener InputCallbackListener) *GameControls {
@ -46,14 +51,19 @@ func NewGameControls(hero *d2mapentity.Player, mapEngine *d2mapengine.MapEngine,
missileID = id missileID = id
}) })
label := d2ui.CreateLabel(d2resource.Font30, d2resource.PaletteUnits)
label.Color = color.RGBA{R: 255, G: 88, B: 82, A: 255}
label.Alignment = d2ui.LabelAlignCenter
gc := &GameControls{ gc := &GameControls{
hero: hero, hero: hero,
mapEngine: mapEngine, mapEngine: mapEngine,
inputListener: inputListener, inputListener: inputListener,
mapRenderer: mapRenderer, mapRenderer: mapRenderer,
inventory: NewInventory(), inventory: NewInventory(),
heroStats: NewHeroStats(), heroStats: NewHeroStats(),
escapeMenu: NewEscapeMenu(), escapeMenu: NewEscapeMenu(),
zoneChangeText: &label,
} }
d2term.BindAction("freecam", "toggle free camera movement", func() { d2term.BindAction("freecam", "toggle free camera movement", func() {
@ -116,9 +126,37 @@ func (g *GameControls) OnKeyDown(event d2input.KeyEvent) bool {
case d2input.KeyC: case d2input.KeyC:
g.heroStats.Toggle() g.heroStats.Toggle()
g.updateLayout() g.updateLayout()
case d2input.KeyR:
g.hero.ToggleRunWalk()
// TODO: change the running menu icon
g.hero.SetIsRunning(g.hero.IsRunToggled())
default: default:
return false return false
} }
return false
}
var lastLeftBtnActionTime float64 = 0
var lastRightBtnActionTime float64 = 0
var mouseBtnActionsTreshhold = 0.25
func (g *GameControls) OnMouseButtonRepeat(event d2input.MouseEvent) bool {
px, py := g.mapRenderer.ScreenToWorld(event.X, event.Y)
px = float64(int(px*10)) / 10.0
py = float64(int(py*10)) / 10.0
now := d2common.Now()
if event.Button == d2input.MouseButtonLeft && now-lastLeftBtnActionTime >= mouseBtnActionsTreshhold {
lastLeftBtnActionTime = now
g.inputListener.OnPlayerMove(px, py)
return true
}
if event.Button == d2input.MouseButtonRight && now-lastRightBtnActionTime >= mouseBtnActionsTreshhold {
lastRightBtnActionTime = now
g.ShootMissile(px, py)
return true
}
return true return true
} }
@ -138,37 +176,44 @@ func (g *GameControls) OnMouseButtonDown(event d2input.MouseEvent) bool {
py = float64(int(py*10)) / 10.0 py = float64(int(py*10)) / 10.0
if event.Button == d2input.MouseButtonLeft { if event.Button == d2input.MouseButtonLeft {
lastLeftBtnActionTime = d2common.Now()
g.inputListener.OnPlayerMove(px, py) g.inputListener.OnPlayerMove(px, py)
return true return true
} }
if event.Button == d2input.MouseButtonRight { if event.Button == d2input.MouseButtonRight {
missile, err := d2mapentity.CreateMissile( lastRightBtnActionTime = d2common.Now()
int(g.hero.AnimatedComposite.LocationX), return g.ShootMissile(px, py)
int(g.hero.AnimatedComposite.LocationY),
d2datadict.Missiles[missileID],
)
if err != nil {
return false
}
rads := d2common.GetRadiansBetween(
g.hero.AnimatedComposite.LocationX,
g.hero.AnimatedComposite.LocationY,
px*5,
py*5,
)
missile.SetRadians(rads, func() {
g.mapEngine.RemoveEntity(missile)
})
g.mapEngine.AddEntity(missile)
return true
} }
return false return false
} }
func (g *GameControls) ShootMissile(px float64, py float64) bool {
missile, err := d2mapentity.CreateMissile(
int(g.hero.AnimatedComposite.LocationX),
int(g.hero.AnimatedComposite.LocationY),
d2datadict.Missiles[missileID],
)
if err != nil {
return false
}
rads := d2common.GetRadiansBetween(
g.hero.AnimatedComposite.LocationX,
g.hero.AnimatedComposite.LocationY,
px*5,
py*5,
)
missile.SetRadians(rads, func() {
g.mapEngine.RemoveEntity(missile)
})
g.mapEngine.AddEntity(missile)
return true
}
func (g *GameControls) Load() { func (g *GameControls) Load() {
animation, _ := d2asset.LoadAnimation(d2resource.GameGlobeOverlap, d2resource.PaletteSky) animation, _ := d2asset.LoadAnimation(d2resource.GameGlobeOverlap, d2resource.PaletteSky)
g.globeSprite, _ = d2ui.LoadSprite(animation) g.globeSprite, _ = d2ui.LoadSprite(animation)
@ -290,6 +335,24 @@ func (g *GameControls) Render(target d2render.Surface) {
g.globeSprite.SetPosition(offset+8, height-8) g.globeSprite.SetPosition(offset+8, height-8)
g.globeSprite.Render(target) g.globeSprite.Render(target)
if g.isZoneTextShown {
g.zoneChangeText.SetPosition(width/2, height/4)
g.zoneChangeText.Render(target)
}
}
func (g *GameControls) SetZoneChangeText(text string) {
g.zoneChangeText.SetText(text)
}
func (g *GameControls) ShowZoneChangeText() {
g.isZoneTextShown = true
}
func (g *GameControls) HideZoneChangeTextAfter(delay float64) {
time.AfterFunc(time.Duration(delay)*time.Second, func() {
g.isZoneTextShown = false
})
} }
func (g *GameControls) InEscapeMenu() bool { func (g *GameControls) InEscapeMenu() bool {

View File

@ -95,10 +95,11 @@ func (g *GameClient) OnPacketReceived(packet d2netpacket.NetPacket) error {
regionType := tile.RegionType regionType := tile.RegionType
if regionType == d2enum.RegionAct1Town { if regionType == d2enum.RegionAct1Town {
player.AnimatedComposite.SetAnimationMode(d2enum.AnimationModePlayerTownNeutral.String()) player.SetIsInTown(true)
} else { } else {
player.AnimatedComposite.SetAnimationMode(d2enum.AnimationModePlayerNeutral.String()) player.SetIsInTown(false)
} }
player.AnimatedComposite.SetAnimationMode(player.GetAnimationMode().String())
}) })
} }
default: default: