diff --git a/d2common/math.go b/d2common/math.go index 59d7f2b4..eff88a44 100644 --- a/d2common/math.go +++ b/d2common/math.go @@ -1,6 +1,8 @@ package d2common -import "math" +import ( + "math" +) func MinInt(a, b int) int { if a < b { @@ -89,3 +91,36 @@ func GetRadiansBetween(p1X, p1Y, p2X, p2Y float64) float64 { func AlmostEqual(a, b, threshold float64) bool { return math.Abs(a-b) <= threshold } + +// Return the new adjusted value, as well as any remaining amount after the max +func AdjustWithRemainder(sourceValue, adjustment, targetvalue float64) (newValue, remainder float64) { + if adjustment == 0 || math.Abs(adjustment) < 0.000001 { + return sourceValue, 0 + } + adjustNegative := adjustment < 0.0 + maxNegative := targetvalue-sourceValue < 0.0 + if adjustNegative != maxNegative { + // FIXME: This shouldn't happen but it happens all the time.. + return sourceValue, 0 + //panic("Cannot move towards the opposite direction...") + } + + finalValue := sourceValue + adjustment + if !adjustNegative { + if finalValue > targetvalue { + diff := finalValue - targetvalue // RoundToDecial(finalValue-targetvalue, 6) + return targetvalue, diff + } + return finalValue, 0 + } + + if finalValue < targetvalue { + return targetvalue, RoundToDecial(finalValue-targetvalue, 6) + } + return finalValue, 0 +} + +func RoundToDecial(f float64, d int) float64 { + digits := float64(math.Pow10(d)) + return math.Trunc(f*digits) / digits +} diff --git a/d2common/math_test.go b/d2common/math_test.go new file mode 100644 index 00000000..1abe1a42 --- /dev/null +++ b/d2common/math_test.go @@ -0,0 +1,25 @@ +package d2common + +import ( + "testing" +) + +type TestRecord struct { + source, adjust, max, expectedResult, expectedRemain float64 +} + +func TestSomething(t *testing.T) { + var testValues = []TestRecord{ + {100, 10, 100.2, 100.2, 9.8}, + } + for _, test := range testValues { + res, remain := AdjustWithRemainder(test.source, test.adjust, test.max) + if res != test.expectedResult { + t.Errorf("Expected result of %f but got %f", test.expectedResult, res) + } + if remain != test.expectedRemain { + t.Errorf("Expected result of %f but got %f", test.expectedRemain, remain) + } + } + +} diff --git a/d2core/d2asset/composite.go b/d2core/d2asset/composite.go index 9fd63d5d..5db8b2c9 100644 --- a/d2core/d2asset/composite.go +++ b/d2core/d2asset/composite.go @@ -62,6 +62,10 @@ func (c *Composite) Render(target d2render.Surface) error { return nil } +func (c Composite) GetAnimationMode() string { + return c.mode.animationMode +} + func (c *Composite) SetMode(animationMode, weaponClass string, direction int) error { if c.mode != nil && c.mode.animationMode == animationMode && c.mode.weaponClass == weaponClass && c.mode.direction == direction { return nil diff --git a/d2core/d2map/animated_composite.go b/d2core/d2map/animated_composite.go index 69d590f7..5ca9237b 100644 --- a/d2core/d2map/animated_composite.go +++ b/d2core/d2map/animated_composite.go @@ -10,9 +10,11 @@ import ( // AnimatedComposite represents a composite of animations that can be projected onto the map. type AnimatedComposite struct { mapEntity - animationMode string - composite *d2asset.Composite - direction int + //animationMode string + composite *d2asset.Composite + direction int + player *Player + objectLookup *d2datadict.ObjectLookupRecord } // CreateAnimatedComposite creates an instance of AnimatedComposite @@ -23,21 +25,27 @@ func CreateAnimatedComposite(x, y int, object *d2datadict.ObjectLookupRecord, pa } entity := &AnimatedComposite{ - mapEntity: createMapEntity(x, y), - composite: composite, + mapEntity: createMapEntity(x, y), + composite: composite, + objectLookup: object, } entity.mapEntity.directioner = entity.rotate return entity, nil } +func (ac *AnimatedComposite) SetPlayer(player *Player) { + ac.player = player +} + func (ac *AnimatedComposite) SetAnimationMode(animationMode string) error { return ac.composite.SetMode(animationMode, ac.weaponClass, ac.direction) } // SetMode changes the graphical mode of this animated entity func (ac *AnimatedComposite) SetMode(animationMode, weaponClass string, direction int) error { - ac.animationMode = animationMode + ac.composite.SetMode(animationMode, weaponClass, direction) ac.direction = direction + ac.weaponClass = weaponClass err := ac.composite.SetMode(animationMode, weaponClass, direction) if err != nil { @@ -61,19 +69,24 @@ 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.animationMode + newAnimationMode := ac.composite.GetAnimationMode() if !ac.IsAtTarget() { - newAnimationMode = d2enum.AnimationModeMonsterWalk.String() - } - - if newAnimationMode != ac.animationMode { - ac.SetMode(newAnimationMode, ac.weaponClass, ac.direction) + 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) - if newDirection != ac.direction { - ac.SetMode(ac.animationMode, ac.weaponClass, newDirection) + if newAnimationMode != ac.composite.GetAnimationMode() || newDirection != ac.direction { + ac.SetMode(newAnimationMode, ac.weaponClass, newDirection) } + } func (ac *AnimatedComposite) Advance(elapsed float64) { diff --git a/d2core/d2map/map_entity.go b/d2core/d2map/map_entity.go index 856fc30f..e6d77da7 100644 --- a/d2core/d2map/map_entity.go +++ b/d2core/d2map/map_entity.go @@ -62,7 +62,7 @@ func (m *mapEntity) getStepLength(tickTime float64) (float64, float64) { } func (m *mapEntity) IsAtTarget() bool { - return m.LocationX == m.TargetX && m.LocationY == m.TargetY && !m.HasPathFinding() + return math.Abs(m.LocationX-m.TargetX) < 0.0001 && math.Abs(m.LocationY-m.TargetY) < 0.0001 && !m.HasPathFinding() } func (m *mapEntity) Step(tickTime float64) { @@ -75,40 +75,48 @@ func (m *mapEntity) Step(tickTime float64) { } stepX, stepY := m.getStepLength(tickTime) - - if d2common.AlmostEqual(m.LocationX, m.TargetX, stepX) { - m.LocationX = m.TargetX - } - if d2common.AlmostEqual(m.LocationY, m.TargetY, stepY) { - m.LocationY = m.TargetY - } - if m.LocationX != m.TargetX { - m.LocationX += stepX - } - if m.LocationY != m.TargetY { - m.LocationY += stepY - } - - m.subcellX = 1 + math.Mod(m.LocationX, 5) - m.subcellY = 1 + math.Mod(m.LocationY, 5) - m.TileX = int(m.LocationX / 5) - m.TileY = int(m.LocationY / 5) - - if (m.LocationX != m.TargetX) || (m.LocationY != m.TargetY) { - return - } - - if len(m.path) > 0 { - m.SetTarget(m.path[0].(*PathTile).X*5, m.path[0].(*PathTile).Y*5, m.done) - - if len(m.path) > 1 { - m.path = m.path[1:] - } else { - m.path = []astar.Pather{} + looped := false + for { + looped = looped + if d2common.AlmostEqual(m.LocationX-m.TargetX, 0, 0.0001) { + stepX = 0 } - return - } + if d2common.AlmostEqual(m.LocationY-m.TargetY, 0, 0.0001) { + stepY = 0 + } + m.LocationX, stepX = d2common.AdjustWithRemainder(m.LocationX, stepX, m.TargetX) + m.LocationY, stepY = d2common.AdjustWithRemainder(m.LocationY, stepY, m.TargetY) + m.subcellX = 1 + math.Mod(m.LocationX, 5) + m.subcellY = 1 + math.Mod(m.LocationY, 5) + m.TileX = int(m.LocationX / 5) + m.TileY = int(m.LocationY / 5) + + if d2common.AlmostEqual(m.LocationX, m.TargetX, 0.01) && d2common.AlmostEqual(m.LocationY, m.TargetY, 0.01) { + if len(m.path) > 0 { + m.SetTarget(m.path[0].(*PathTile).X*5, m.path[0].(*PathTile).Y*5, m.done) + + if len(m.path) > 1 { + m.path = m.path[1:] + } else { + m.path = []astar.Pather{} + } + } else { + m.LocationX = m.TargetX + m.LocationY = m.TargetY + m.subcellX = 1 + math.Mod(m.LocationX, 5) + m.subcellY = 1 + math.Mod(m.LocationY, 5) + m.TileX = int(m.LocationX / 5) + m.TileY = int(m.LocationY / 5) + } + } + + if stepX == 0 && stepY == 0 { + break + } + looped = true + + } } func (m *mapEntity) HasPathFinding() bool { diff --git a/d2core/d2map/npc.go b/d2core/d2map/npc.go index 80ea1f01..c50b95d0 100644 --- a/d2core/d2map/npc.go +++ b/d2core/d2map/npc.go @@ -91,7 +91,7 @@ func (v *NPC) next() { v.repetitions = 0 } - if v.animationMode != newAnimationMode.String() { + if v.composite.GetAnimationMode() != newAnimationMode.String() { v.SetMode(newAnimationMode.String(), v.weaponClass, v.direction) } } diff --git a/d2core/d2map/player.go b/d2core/d2map/player.go index e27ff61a..b44ead6a 100644 --- a/d2core/d2map/player.go +++ b/d2core/d2map/player.go @@ -13,17 +13,19 @@ import ( type Player struct { *AnimatedComposite - Equipment d2inventory.CharacterEquipment - Id string - mode d2enum.AnimationMode - direction int - Name string - nameLabel d2ui.Label + Equipment d2inventory.CharacterEquipment + Id string + direction int + Name string + nameLabel d2ui.Label + lastPathSize int + isInTown bool + animationMode string } func CreatePlayer(id, name string, x, y int, direction int, heroType d2enum.Hero, equipment d2inventory.CharacterEquipment) *Player { object := &d2datadict.ObjectLookupRecord{ - Mode: d2enum.AnimationModePlayerNeutral.String(), + Mode: d2enum.AnimationModePlayerTownNeutral.String(), Base: "/data/global/chars", Token: heroType.GetToken(), Class: equipment.RightHand.GetWeaponClass(), @@ -47,7 +49,6 @@ func CreatePlayer(id, name string, x, y int, direction int, heroType d2enum.Hero Id: id, AnimatedComposite: entity, Equipment: equipment, - mode: d2enum.AnimationModePlayerTownNeutral, direction: direction, Name: name, nameLabel: d2ui.CreateLabel(d2resource.FontFormal11, d2resource.PaletteStatic), @@ -55,17 +56,32 @@ func CreatePlayer(id, name string, x, y int, direction int, heroType d2enum.Hero result.nameLabel.Alignment = d2ui.LabelAlignCenter result.nameLabel.SetText(name) result.nameLabel.Color = color.White - err = result.SetMode(result.mode.String(), equipment.RightHand.GetWeaponClass(), direction) + result.SetPlayer(result) + err = result.SetMode(d2enum.AnimationModePlayerTownNeutral.String(), equipment.RightHand.GetWeaponClass(), direction) if err != nil { panic(err) } return result } +func (p *Player) SetIsInTown(isInTown bool) { + p.isInTown = isInTown +} + +func (p Player) IsInTown() bool { + return p.isInTown +} + func (v *Player) Advance(tickTime float64) { v.Step(tickTime) v.AnimatedComposite.Advance(tickTime) + if v.lastPathSize != len(v.path) { + v.lastPathSize = len(v.path) + } + if v.AnimatedComposite.composite.GetAnimationMode() != v.animationMode { + v.animationMode = v.AnimatedComposite.composite.GetAnimationMode() + } } func (v *Player) Render(target d2render.Surface) { diff --git a/d2core/d2map/region.go b/d2core/d2map/region.go index ce227183..a2629e39 100644 --- a/d2core/d2map/region.go +++ b/d2core/d2map/region.go @@ -483,7 +483,7 @@ func (mr *MapRegion) renderWall(tile d2ds1.WallRecord, viewport *Viewport, targe return } - viewport.PushTranslationOrtho(-80, float64(tile.YAdjust)) + viewport.PushTranslationOrtho(-80, float64(tile.YAdjust)-8) defer viewport.PopTranslation() target.PushTranslation(viewport.GetTranslationScreen()) @@ -571,12 +571,15 @@ func (mr *MapRegion) renderTileDebug(x, y int, debugVisLevel int, viewport *View for xx := 0; xx < 5; xx++ { isoX := (xx - yy) * 16 isoY := (xx + yy) * 8 - target.PushTranslation(isoX-3, isoY+4) - var walkableArea = mr.walkableArea[yy+(ay*5)][xx+(ax*5)] - if !walkableArea.Walkable { - target.DrawRect(5, 5, tileCollisionColor) + if !((len(mr.walkableArea) <= yy+(ay*5)) || (len(mr.walkableArea[yy+(ay*5)]) <= xx+(ax*5))) { + var walkableArea = mr.walkableArea[yy+(ay*5)][xx+(ax*5)] + if !walkableArea.Walkable { + target.PushTranslation(isoX-3, isoY+4) + target.DrawRect(5, 5, tileCollisionColor) + target.Pop() + } } - target.Pop() + } } } diff --git a/d2discord.png b/d2discord.png new file mode 100644 index 00000000..1bdcc0e8 Binary files /dev/null and b/d2discord.png differ diff --git a/d2game/d2gamescreen/game.go b/d2game/d2gamescreen/game.go index 19c66162..2b929f37 100644 --- a/d2game/d2gamescreen/game.go +++ b/d2game/d2gamescreen/game.go @@ -1,7 +1,6 @@ package d2gamescreen import ( - "fmt" "image/color" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" @@ -63,19 +62,20 @@ func (v *Game) Advance(tickTime float64) error { v.gameClient.MapEngine.Advance(tickTime) // TODO: Hack v.ticksSinceLevelCheck += tickTime - if v.ticksSinceLevelCheck > 2.0 { + if v.ticksSinceLevelCheck > 1.0 { v.ticksSinceLevelCheck = 0 if v.localPlayer != nil { region := v.gameClient.MapEngine.GetRegionAtTile(v.localPlayer.TileX, v.localPlayer.TileY) if region != nil { levelType := region.GetLevelType().Id - fmt.Printf("Level checked: %d (%s)\t%d, %d\n", levelType, region.GetLevelType().Name, v.localPlayer.TileX, v.localPlayer.TileY) if levelType != v.lastLevelType { v.lastLevelType = levelType switch levelType { case 1: // Rogue encampent + v.localPlayer.SetIsInTown(true) d2audio.PlayBGM("/data/global/music/Act1/town1.wav") case 2: // Blood Moore + v.localPlayer.SetIsInTown(false) d2audio.PlayBGM("/data/global/music/Act1/wild.wav") } } diff --git a/d2logo.png b/d2logo.png index 3913d81b..f4eafb8b 100644 Binary files a/d2logo.png and b/d2logo.png differ diff --git a/d2networking/d2client/game_client.go b/d2networking/d2client/game_client.go index 0c417e0b..6627795f 100644 --- a/d2networking/d2client/game_client.go +++ b/d2networking/d2client/game_client.go @@ -86,9 +86,11 @@ func (g *GameClient) OnPacketReceived(packet d2netpacket.NetPacket) error { path, _, found := g.MapEngine.PathFind(movePlayer.StartX, movePlayer.StartY, movePlayer.DestX, movePlayer.DestY) if found { player.AnimatedComposite.SetPath(path, func() { - player.AnimatedComposite.SetAnimationMode( - d2enum.AnimationModeObjectNeutral.String(), - ) + if g.MapEngine.GetRegionAtTile(player.TileX, player.TileY).GetLevelType().Id == int(d2enum.RegionAct1Town) { + player.AnimatedComposite.SetAnimationMode(d2enum.AnimationModePlayerTownNeutral.String()) + } else { + player.AnimatedComposite.SetAnimationMode(d2enum.AnimationModePlayerNeutral.String()) + } }) } break