mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-02-04 23:56:40 -05: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:
parent
8dcec1f209
commit
2835ff4cf1
@ -197,6 +197,10 @@ type MouseButtonDownHandler interface {
|
||||
OnMouseButtonDown(event MouseEvent) bool
|
||||
}
|
||||
|
||||
type MouseButtonRepeatHandler interface {
|
||||
OnMouseButtonRepeat(event MouseEvent) bool
|
||||
}
|
||||
|
||||
type MouseButtonUpHandler interface {
|
||||
OnMouseButtonUp(event MouseEvent) bool
|
||||
}
|
||||
|
@ -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) {
|
||||
event := MouseEvent{eventBase, MouseButton(button)}
|
||||
im.propagate(func(handler Handler) bool {
|
||||
|
@ -68,25 +68,51 @@ func (ac *AnimatedComposite) Render(target d2render.Surface) {
|
||||
|
||||
// rotate sets direction and changes animation
|
||||
func (ac *AnimatedComposite) rotate(angle float64) {
|
||||
// TODO: Check if is in town and if is player.
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
newAnimationMode := ac.GetAnimationMode().String()
|
||||
newDirection := angleToDirection(angle)
|
||||
|
||||
if newAnimationMode != ac.composite.GetAnimationMode() || newDirection != ac.direction {
|
||||
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) {
|
||||
|
@ -53,6 +53,14 @@ func (m *mapEntity) SetPath(path []astar.Pather, done func()) {
|
||||
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) {
|
||||
length := tickTime * m.Speed
|
||||
|
||||
|
@ -21,8 +21,14 @@ type Player struct {
|
||||
lastPathSize int
|
||||
isInTown bool
|
||||
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 {
|
||||
object := &d2datadict.ObjectLookupRecord{
|
||||
Mode: d2enum.AnimationModePlayerTownNeutral.String(),
|
||||
@ -44,6 +50,7 @@ func CreatePlayer(id, name string, x, y int, direction int, heroType d2enum.Hero
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
entity.SetSpeed(baseRunSpeed)
|
||||
|
||||
result := &Player{
|
||||
Id: id,
|
||||
@ -52,6 +59,9 @@ func CreatePlayer(id, name string, x, y int, direction int, heroType d2enum.Hero
|
||||
direction: direction,
|
||||
Name: name,
|
||||
nameLabel: d2ui.CreateLabel(d2resource.FontFormal11, d2resource.PaletteStatic),
|
||||
isRunToggled: true,
|
||||
isInTown: true,
|
||||
isRunning: true,
|
||||
}
|
||||
result.nameLabel.Alignment = d2ui.LabelAlignCenter
|
||||
result.nameLabel.SetText(name)
|
||||
@ -68,6 +78,28 @@ func (p *Player) SetIsInTown(isInTown bool) {
|
||||
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 {
|
||||
return p.isInTown
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
package d2gamescreen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2maprenderer"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio"
|
||||
"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/d2game/d2player"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client"
|
||||
@ -22,7 +24,7 @@ type Game struct {
|
||||
mapRenderer *d2maprenderer.MapRenderer
|
||||
gameControls *d2player.GameControls // TODO: Hack
|
||||
localPlayer *d2mapentity.Player
|
||||
lastLevelType int
|
||||
lastRegionType d2enum.RegionIdType
|
||||
ticksSinceLevelCheck float64
|
||||
}
|
||||
|
||||
@ -31,7 +33,7 @@ func CreateGame(gameClient *d2client.GameClient) *Game {
|
||||
gameClient: gameClient,
|
||||
gameControls: nil,
|
||||
localPlayer: nil,
|
||||
lastLevelType: -1,
|
||||
lastRegionType: d2enum.RegionNone,
|
||||
ticksSinceLevelCheck: 0,
|
||||
mapRenderer: d2maprenderer.CreateMapRenderer(gameClient.MapEngine),
|
||||
}
|
||||
@ -61,6 +63,8 @@ func (v *Game) Render(screen d2render.Surface) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var hideZoneTextAfterSeconds = 2.0
|
||||
|
||||
func (v *Game) Advance(tickTime float64) error {
|
||||
if !v.gameControls.InEscapeMenu() || len(v.gameClient.Players) != 1 {
|
||||
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)
|
||||
if tile != nil {
|
||||
switch tile.RegionType {
|
||||
case 1: // Rogue encampent
|
||||
case d2enum.RegionAct1Town: // Rogue encampent
|
||||
v.localPlayer.SetIsInTown(true)
|
||||
d2audio.PlayBGM("/data/global/music/Act1/town1.wav")
|
||||
case 2: // Blood Moore
|
||||
case d2enum.RegionAct1Wilderness: // Blood Moore
|
||||
v.localPlayer.SetIsInTown(false)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
package d2player
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"time"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
@ -35,10 +38,12 @@ type GameControls struct {
|
||||
FreeCam bool
|
||||
|
||||
// UI
|
||||
globeSprite *d2ui.Sprite
|
||||
mainPanel *d2ui.Sprite
|
||||
menuButton *d2ui.Sprite
|
||||
skillIcon *d2ui.Sprite
|
||||
globeSprite *d2ui.Sprite
|
||||
mainPanel *d2ui.Sprite
|
||||
menuButton *d2ui.Sprite
|
||||
skillIcon *d2ui.Sprite
|
||||
zoneChangeText *d2ui.Label
|
||||
isZoneTextShown bool
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
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{
|
||||
hero: hero,
|
||||
mapEngine: mapEngine,
|
||||
inputListener: inputListener,
|
||||
mapRenderer: mapRenderer,
|
||||
inventory: NewInventory(),
|
||||
heroStats: NewHeroStats(),
|
||||
escapeMenu: NewEscapeMenu(),
|
||||
hero: hero,
|
||||
mapEngine: mapEngine,
|
||||
inputListener: inputListener,
|
||||
mapRenderer: mapRenderer,
|
||||
inventory: NewInventory(),
|
||||
heroStats: NewHeroStats(),
|
||||
escapeMenu: NewEscapeMenu(),
|
||||
zoneChangeText: &label,
|
||||
}
|
||||
|
||||
d2term.BindAction("freecam", "toggle free camera movement", func() {
|
||||
@ -116,9 +126,37 @@ func (g *GameControls) OnKeyDown(event d2input.KeyEvent) bool {
|
||||
case d2input.KeyC:
|
||||
g.heroStats.Toggle()
|
||||
g.updateLayout()
|
||||
case d2input.KeyR:
|
||||
g.hero.ToggleRunWalk()
|
||||
// TODO: change the running menu icon
|
||||
g.hero.SetIsRunning(g.hero.IsRunToggled())
|
||||
default:
|
||||
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
|
||||
}
|
||||
@ -138,37 +176,44 @@ func (g *GameControls) OnMouseButtonDown(event d2input.MouseEvent) bool {
|
||||
py = float64(int(py*10)) / 10.0
|
||||
|
||||
if event.Button == d2input.MouseButtonLeft {
|
||||
lastLeftBtnActionTime = d2common.Now()
|
||||
g.inputListener.OnPlayerMove(px, py)
|
||||
return true
|
||||
}
|
||||
|
||||
if event.Button == d2input.MouseButtonRight {
|
||||
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
|
||||
lastRightBtnActionTime = d2common.Now()
|
||||
return g.ShootMissile(px, py)
|
||||
}
|
||||
|
||||
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() {
|
||||
animation, _ := d2asset.LoadAnimation(d2resource.GameGlobeOverlap, d2resource.PaletteSky)
|
||||
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.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 {
|
||||
|
@ -95,10 +95,11 @@ func (g *GameClient) OnPacketReceived(packet d2netpacket.NetPacket) error {
|
||||
|
||||
regionType := tile.RegionType
|
||||
if regionType == d2enum.RegionAct1Town {
|
||||
player.AnimatedComposite.SetAnimationMode(d2enum.AnimationModePlayerTownNeutral.String())
|
||||
player.SetIsInTown(true)
|
||||
} else {
|
||||
player.AnimatedComposite.SetAnimationMode(d2enum.AnimationModePlayerNeutral.String())
|
||||
player.SetIsInTown(false)
|
||||
}
|
||||
player.AnimatedComposite.SetAnimationMode(player.GetAnimationMode().String())
|
||||
})
|
||||
}
|
||||
default:
|
||||
|
Loading…
Reference in New Issue
Block a user