diff --git a/d2common/d2interface/map_entity.go b/d2common/d2interface/map_entity.go index 918a6744..bd413f05 100644 --- a/d2common/d2interface/map_entity.go +++ b/d2common/d2interface/map_entity.go @@ -4,6 +4,7 @@ import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" // MapEntity is something that can be positioned on and rendered on the game map type MapEntity interface { + ID() string Render(target Surface) Advance(tickTime float64) GetPosition() d2vector.Position diff --git a/d2common/rgba_color.go b/d2common/rgba_color.go new file mode 100644 index 00000000..6fc5f266 --- /dev/null +++ b/d2common/rgba_color.go @@ -0,0 +1,28 @@ +package d2common + +import "image/color" + +func Color(rgba uint32) color.RGBA { + result := color.RGBA{} + a, b, g, r := 0, 1, 2, 3 + byteWidth := 8 + byteMask := 0xff + + for idx := 0; idx < 4; idx++ { + shift := idx * byteWidth + component := uint8(rgba>>shift) & uint8(byteMask) + + switch idx { + case a: + result.A = component + case b: + result.B = component + case g: + result.G = component + case r: + result.R = component + } + } + + return result +} diff --git a/d2core/d2map/d2mapengine/engine.go b/d2core/d2map/d2mapengine/engine.go index ae1945f9..f7c7bd18 100644 --- a/d2core/d2map/d2mapengine/engine.go +++ b/d2core/d2map/d2mapengine/engine.go @@ -20,8 +20,8 @@ import ( // MapEngine loads the tiles which make up the isometric map and the entities type MapEngine struct { - seed int64 // The map seed - entities []d2interface.MapEntity // Entities on the map + seed int64 // The map seed + entities map[string]d2interface.MapEntity // Entities on the map tiles []MapTile size d2common.Size // Size of the map, in tiles levelType d2datadict.LevelTypeRecord // Level type of this map @@ -44,7 +44,7 @@ func (m *MapEngine) GetStartingPosition() (x, y int) { // ResetMap clears all map and entity data and reloads it from the cached files. func (m *MapEngine) ResetMap(levelType d2enum.RegionIdType, width, height int) { - m.entities = make([]d2interface.MapEntity, 0) + m.entities = make(map[string]d2interface.MapEntity) m.levelType = d2datadict.LevelTypes[levelType] m.size = d2common.Size{Width: width, Height: height} m.tiles = make([]MapTile, width*height) @@ -155,7 +155,7 @@ func (m *MapEngine) PlaceStamp(stamp *d2mapstamp.Stamp, tileOffsetX, tileOffsetY // Copy over the map tile data for y := 0; y < stampH; y++ { for x := 0; x < stampW; x++ { - targetTileIndex := m.tileCoordinateToIndex((x + xMin), (y + yMin)) + targetTileIndex := m.tileCoordinateToIndex(x+xMin, y+yMin) stampTile := *stamp.Tile(x, y) m.tiles[targetTileIndex].RegionType = stamp.RegionID() m.tiles[targetTileIndex].Components = stampTile @@ -164,7 +164,11 @@ func (m *MapEngine) PlaceStamp(stamp *d2mapstamp.Stamp, tileOffsetX, tileOffsetY } // Copy over the entities - m.entities = append(m.entities, stamp.Entities(tileOffsetX, tileOffsetY)...) + stampEntities := stamp.Entities(tileOffsetX, tileOffsetY) + for idx := range stampEntities { + e := stampEntities[idx] + m.entities[e.ID()] = e + } } // converts x,y tile coordinate into index in MapEngine.tiles @@ -172,7 +176,7 @@ func (m *MapEngine) tileCoordinateToIndex(x, y int) int { return x + (y * m.size.Width) } -// converts tile index from MapEngine.tiles to x,y coordinate +// tileIndexToCoordinate converts tile index from MapEngine.tiles to x,y coordinate func (m *MapEngine) tileIndexToCoordinate(index int) (x, y int) { return index % m.size.Width, index / m.size.Width } @@ -196,8 +200,8 @@ func (m *MapEngine) TileAt(tileX, tileY int) *MapTile { } // Entities returns a pointer a slice of all map entities. -func (m *MapEngine) Entities() *[]d2interface.MapEntity { - return &m.entities +func (m *MapEngine) Entities() map[string]d2interface.MapEntity { + return m.entities } // Seed returns the map generation seed. @@ -207,15 +211,16 @@ func (m *MapEngine) Seed() int64 { // AddEntity adds an entity to a slice containing all entities. func (m *MapEngine) AddEntity(entity d2interface.MapEntity) { - m.entities = append(m.entities, entity) + m.entities[entity.ID()] = entity } -// RemoveEntity is not currently implemented. +// RemoveEntity removes an entity from the map engine func (m *MapEngine) RemoveEntity(entity d2interface.MapEntity) { if entity == nil { return } - // m.entities.Remove(entity) + + delete(m.entities, entity.ID()) } // GetTiles returns a slice of all tiles matching the given style, @@ -264,8 +269,8 @@ func (m *MapEngine) GetCenterPosition() (x, y float64) { // Advance calls the Advance() method for all entities, // processing a single tick. func (m *MapEngine) Advance(tickTime float64) { - for idx := range m.entities { - m.entities[idx].Advance(tickTime) + for ID := range m.entities { + m.entities[ID].Advance(tickTime) } } diff --git a/d2core/d2map/d2mapentity/d2mapentity.go b/d2core/d2map/d2mapentity/d2mapentity.go index 08724ba9..c922a546 100644 --- a/d2core/d2map/d2mapentity/d2mapentity.go +++ b/d2core/d2map/d2mapentity/d2mapentity.go @@ -3,7 +3,6 @@ package d2mapentity import ( "errors" "fmt" - "github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" @@ -50,7 +49,6 @@ func NewPlayer(id, name string, x, y, direction int, heroType d2enum.Hero, stats.Stamina = stats.MaxStamina result := &Player{ - ID: id, mapEntity: newMapEntity(x, y), composite: composite, Equipment: equipment, @@ -62,6 +60,8 @@ func NewPlayer(id, name string, x, y, direction int, heroType d2enum.Hero, isInTown: true, isRunning: true, } + + result.mapEntity.uuid = id result.SetSpeed(baseRunSpeed) result.mapEntity.directioner = result.rotate err = composite.SetMode(d2enum.PlayerAnimationModeTownNeutral, equipment.RightHand.GetWeaponClass()) diff --git a/d2core/d2map/d2mapentity/item.go b/d2core/d2map/d2mapentity/item.go index 86c42a4e..1d518d06 100644 --- a/d2core/d2map/d2mapentity/item.go +++ b/d2core/d2map/d2mapentity/item.go @@ -19,6 +19,11 @@ type Item struct { Item *diablo2item.Item } +// ID returns the item uuid +func (i *Item) ID() string { + return i.AnimatedEntity.uuid +} + // GetPosition returns the item position vector func (i *Item) GetPosition() d2vector.Position { return i.AnimatedEntity.Position diff --git a/d2core/d2map/d2mapentity/map_entity.go b/d2core/d2map/d2mapentity/map_entity.go index 9a362d8a..b1698ce8 100644 --- a/d2core/d2map/d2mapentity/map_entity.go +++ b/d2core/d2map/d2mapentity/map_entity.go @@ -1,6 +1,8 @@ package d2mapentity import ( + uuid "github.com/satori/go.uuid" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" ) @@ -10,6 +12,7 @@ const ( // mapEntity represents an entity on the map that can be animated type mapEntity struct { + uuid string Position d2vector.Position Target d2vector.Position velocity d2vector.Vector @@ -29,6 +32,7 @@ func newMapEntity(x, y int) mapEntity { pos := d2vector.NewPosition(float64(x), float64(y)) return mapEntity{ + uuid: uuid.NewV4().String(), Position: pos, Target: pos, velocity: *d2vector.VectorZero(), diff --git a/d2core/d2map/d2mapentity/missile.go b/d2core/d2map/d2mapentity/missile.go index 6ea6caca..a4f2558f 100644 --- a/d2core/d2map/d2mapentity/missile.go +++ b/d2core/d2map/d2mapentity/missile.go @@ -14,6 +14,11 @@ type Missile struct { record *d2datadict.MissileRecord } +// ID returns the missile uuid +func (m *Missile) ID() string { + return m.AnimatedEntity.uuid +} + // GetPosition returns the position of the missile func (m *Missile) GetPosition() d2vector.Position { return m.AnimatedEntity.Position diff --git a/d2core/d2map/d2mapentity/npc.go b/d2core/d2map/d2mapentity/npc.go index 056ee13d..e16dcc43 100644 --- a/d2core/d2map/d2mapentity/npc.go +++ b/d2core/d2map/d2mapentity/npc.go @@ -43,6 +43,11 @@ func selectEquip(slice []string) string { return "" } +// ID returns the NPC uuid +func (v *NPC) ID() string { + return v.mapEntity.uuid +} + // Render renders this entity's animated composite. func (v *NPC) Render(target d2interface.Surface) { renderOffset := v.Position.RenderOffset() diff --git a/d2core/d2map/d2mapentity/player.go b/d2core/d2map/d2mapentity/player.go index 29aa0757..f7dbb382 100644 --- a/d2core/d2map/d2mapentity/player.go +++ b/d2core/d2map/d2mapentity/player.go @@ -14,7 +14,6 @@ import ( // Player is the player character entity. type Player struct { mapEntity - ID string name string animationMode string composite *d2asset.Composite @@ -35,6 +34,11 @@ type Player struct { const baseWalkSpeed = 6.0 const baseRunSpeed = 9.0 +// ID returns the Player uuid +func (p *Player) ID() string { + return p.mapEntity.uuid +} + // SetIsInTown sets a flag indicating that the player is in town. func (p *Player) SetIsInTown(isInTown bool) { p.isInTown = isInTown @@ -86,7 +90,7 @@ func (p *Player) Advance(tickTime float64) { } if err := p.composite.Advance(tickTime); err != nil { - fmt.Printf("failed to advance composite animation of player: %s, err: %v\n", p.ID, err) + fmt.Printf("failed to advance composite animation of player: %s, err: %v\n", p.ID(), err) } if p.lastPathSize != len(p.path) { @@ -109,7 +113,7 @@ func (p *Player) Render(target d2interface.Surface) { defer target.Pop() if err := p.composite.Render(target); err != nil { - fmt.Printf("failed to render the composite of player: %s, err: %v\n", p.ID, err) + fmt.Printf("failed to render the composite of player: %s, err: %v\n", p.ID(), err) } } @@ -173,7 +177,8 @@ func (p *Player) IsCasting() bool { func (p *Player) SetCasting() { p.isCasting = true if err := p.SetAnimationMode(d2enum.PlayerAnimationModeCast); err != nil { - fmt.Printf("failed to set animationMode of player: %s to: %d, err: %v\n", p.ID, d2enum.PlayerAnimationModeCast, err) + fmtStr := "failed to set animationMode of player: %s to: %d, err: %v\n" + fmt.Printf(fmtStr, p.ID(), d2enum.PlayerAnimationModeCast, err) } } diff --git a/d2core/d2map/d2maprenderer/renderer.go b/d2core/d2map/d2maprenderer/renderer.go index 55e6f9f4..fa792652 100644 --- a/d2core/d2map/d2maprenderer/renderer.go +++ b/d2core/d2map/d2maprenderer/renderer.go @@ -7,6 +7,7 @@ import ( "log" "math" + "github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" @@ -19,6 +20,31 @@ import ( const ( screenMiddleX = 400 + two = 2 + + dbgOffsetXY = 40 + dbgBoxWidth = 220 + dbgBoxHeight = 60 + dbgBoxPadding = 10 + + dbgCollisionSize = 5 + dbgCollisionOffsetX = -3 + dbgCollisionOffsetY = 4 + + whiteHalfOpacity = 0xffffff7f + blackQuarterOpacity = 0x00000040 + lightGreenFullOpacity = 0x40ff00ff + magentaFullOpacity = 0xff00ffff + yellowFullOpacity = 0xffff00ff + lightBlueQuarterOpacity = 0x5050ff32 + whiteQuarterOpacity = 0xffffff64 + redQuarterOpacity = 0x74000064 + + subtilesPerTile = 5 + orthoSubTileWidth = 16 + orthoSubTileHeight = 8 + orthoTileWidth = subtilesPerTile * orthoSubTileWidth + orthoTileHeight = subtilesPerTile * orthoSubTileHeight ) // MapRenderer manages the game viewport and Camera. It requests tile and entity data from MapEngine and renders it. @@ -172,7 +198,7 @@ func (mr *MapRenderer) renderPass2(target d2interface.Surface, startX, startY, e mr.viewport.PushTranslationWorld(float64(tileX), float64(tileY)) // TODO: Do not loop over every entity every frame - for _, mapEntity := range *mr.mapEngine.Entities() { + for _, mapEntity := range mr.mapEngine.Entities() { pos := mapEntity.GetPosition() vec := pos.World() entityX, entityY := vec.X(), vec.Y() @@ -204,7 +230,7 @@ func (mr *MapRenderer) renderPass3(target d2interface.Surface, startX, startY, e mr.renderTilePass2(tile, target) // TODO: Do not loop over every entity every frame - for _, mapEntity := range *mr.mapEngine.Entities() { + for _, mapEntity := range mr.mapEngine.Entities() { pos := mapEntity.GetPosition() vec := pos.World() entityX, entityY := vec.X(), vec.Y() @@ -327,9 +353,10 @@ func (mr *MapRenderer) renderShadow(tile d2ds1.FloorShadowRecord, target d2inter defer mr.viewport.PushTranslationOrtho(-80, float64(tile.YAdjust)).PopTranslation() target.PushTranslation(mr.viewport.GetTranslationScreen()) - target.PushColor(color.RGBA{R: 255, G: 255, B: 255, A: 160}) //nolint:gomnd // Not a magic number... + defer target.Pop() - defer target.PopN(2) + target.PushColor(color.RGBA{R: 255, G: 255, B: 255, A: 160}) //nolint:gomnd // Not a magic number... + defer target.Pop() if err := target.Render(img); err != nil { fmt.Printf("failed to render the shadow, err: %v\n", err) @@ -346,26 +373,27 @@ func (mr *MapRenderer) renderMapDebug(mapDebugVisLevel int, target d2interface.S } } +//nolint:funlen // doesn't make sense to split this function func (mr *MapRenderer) renderEntityDebug(target d2interface.Surface) { - entities := *mr.mapEngine.Entities() + entities := mr.mapEngine.Entities() for idx := range entities { e := entities[idx] pos := e.GetPosition() world := pos - x, y := world.X()/5, world.Y()/5 + x, y := world.X()/subtilesPerTile, world.Y()/subtilesPerTile velocity := e.GetVelocity() velocity = *velocity.Clone() vx, vy := mr.viewport.WorldToOrtho(velocity.X(), velocity.Y()) screenX, screenY := mr.viewport.WorldToScreen(x, y) - offX, offY := 40, -40 + offX, offY := dbgOffsetXY, -dbgOffsetXY entScreenXf, entScreenYf := mr.WorldToScreenF(e.GetPositionF()) entScreenX := int(math.Floor(entScreenXf)) entScreenY := int(math.Floor(entScreenYf)) entityWidth, entityHeight := e.GetSize() - halfWidth, halfHeight := entityWidth/2, entityHeight/2 + halfWidth, halfHeight := entityWidth/two, entityHeight/two l, r := entScreenX-halfWidth, entScreenX+halfWidth t, b := entScreenY-halfHeight, entScreenY+halfHeight mx, my := mr.renderer.GetCursorPos() @@ -373,8 +401,8 @@ func (mr *MapRenderer) renderEntityDebug(target d2interface.Surface) { yWithin := (t <= my) && (b >= my) within := xWithin && yWithin - boxLineColor := color.RGBA{255, 0, 255, 128} - boxHoverColor := color.RGBA{255, 255, 0, 128} + boxLineColor := d2common.Color(magentaFullOpacity) + boxHoverColor := d2common.Color(yellowFullOpacity) boxColor := boxLineColor @@ -382,30 +410,39 @@ func (mr *MapRenderer) renderEntityDebug(target d2interface.Surface) { boxColor = boxHoverColor } + stack := 0 // box mr.viewport.PushTranslationWorld(x, y) + target.PushTranslation(screenX, screenY) + stack++ + target.PushTranslation(-halfWidth, -halfHeight) + stack++ + target.DrawLine(0, entityHeight, boxColor) target.DrawLine(entityWidth, 0, boxColor) + target.PushTranslation(entityWidth, entityHeight) + stack++ + target.DrawLine(-entityWidth, 0, boxColor) target.DrawLine(0, -entityHeight, boxColor) - target.PopN(3) + target.PopN(stack) mr.viewport.PopTranslation() // hover if within { mr.viewport.PushTranslationWorld(x, y) target.PushTranslation(screenX, screenY) - target.DrawLine(offX, offY, color.RGBA{255, 255, 255, 128}) - target.PushTranslation(offX+10, offY-20) - target.PushTranslation(-10, -10) - target.DrawRect(200, 50, color.RGBA{0, 0, 0, 64}) + target.DrawLine(offX, offY, d2common.Color(whiteHalfOpacity)) + target.PushTranslation(offX+dbgBoxPadding, offY-dbgBoxPadding*two) + target.PushTranslation(-dbgOffsetXY, -dbgOffsetXY) + target.DrawRect(dbgBoxWidth, dbgBoxHeight, d2common.Color(blackQuarterOpacity)) target.Pop() target.DrawTextf("World (%.2f, %.2f)\nVelocity (%.2f, %.2f)", x, y, vx, vy) target.Pop() - target.DrawLine(int(vx), int(vy), color.RGBA{64, 255, 0, 255}) + target.DrawLine(int(vx), int(vy), d2common.Color(lightGreenFullOpacity)) target.Pop() mr.viewport.PopTranslation() } @@ -423,9 +460,9 @@ func (mr *MapRenderer) WorldToScreenF(x, y float64) (screenX, screenY float64) { } func (mr *MapRenderer) renderTileDebug(ax, ay, debugVisLevel int, target d2interface.Surface) { - subTileColor := color.RGBA{R: 80, G: 80, B: 255, A: 50} - tileColor := color.RGBA{R: 255, G: 255, B: 255, A: 100} - tileCollisionColor := color.RGBA{R: 128, G: 0, B: 0, A: 100} + subTileColor := d2common.Color(lightBlueQuarterOpacity) + tileColor := d2common.Color(whiteQuarterOpacity) + tileCollisionColor := d2common.Color(redQuarterOpacity) screenX1, screenY1 := mr.viewport.WorldToScreen(float64(ax), float64(ay)) screenX2, screenY2 := mr.viewport.WorldToScreen(float64(ax+1), float64(ay)) @@ -442,15 +479,15 @@ func (mr *MapRenderer) renderTileDebug(ax, ay, debugVisLevel int, target d2inter if debugVisLevel > 1 { for i := 1; i <= 4; i++ { - x2 := i * 16 - y2 := i * 8 + x2 := i * orthoSubTileWidth + y2 := i * orthoSubTileHeight target.PushTranslation(-x2, y2) - target.DrawLine(80, 40, subTileColor) + target.DrawLine(orthoTileWidth, orthoTileHeight, subTileColor) target.Pop() target.PushTranslation(x2, y2) - target.DrawLine(-80, 40, subTileColor) + target.DrawLine(-orthoTileWidth, orthoTileHeight, subTileColor) target.Pop() } @@ -458,7 +495,7 @@ func (mr *MapRenderer) renderTileDebug(ax, ay, debugVisLevel int, target d2inter for i, wall := range tile.Components.Walls { if wall.Type.Special() { - target.PushTranslation(-20, 10+(i+1)*14) + target.PushTranslation(-20, 10+(i+1)*14) // what are these magic numbers?? target.DrawTextf("s: %v-%v", wall.Style, wall.Sequence) target.Pop() } @@ -466,14 +503,14 @@ func (mr *MapRenderer) renderTileDebug(ax, ay, debugVisLevel int, target d2inter for yy := 0; yy < 5; yy++ { for xx := 0; xx < 5; xx++ { - isoX := (xx - yy) * 16 - isoY := (xx + yy) * 8 + isoX := (xx - yy) * orthoSubTileWidth + isoY := (xx + yy) * orthoSubTileHeight blocked := tile.GetSubTileFlags(xx, yy).BlockWalk if blocked { - target.PushTranslation(isoX-3, isoY+4) - target.DrawRect(5, 5, tileCollisionColor) + target.PushTranslation(isoX+dbgCollisionOffsetX, isoY+dbgCollisionOffsetY) + target.DrawRect(dbgCollisionSize, dbgCollisionSize, tileCollisionColor) target.Pop() } } diff --git a/d2core/d2object/object.go b/d2core/d2object/object.go index 16c2ca1a..8bfb1470 100644 --- a/d2core/d2object/object.go +++ b/d2core/d2object/object.go @@ -4,6 +4,8 @@ package d2object import ( "math/rand" + uuid "github.com/satori/go.uuid" + "github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" @@ -15,6 +17,7 @@ import ( // Object represents a composite of animations that can be projected onto the map. type Object struct { + uuid string Position d2vector.Position composite *d2asset.Composite highlight bool @@ -28,6 +31,7 @@ type Object struct { func CreateObject(x, y int, objectRec *d2datadict.ObjectRecord, palettePath string) (*Object, error) { locX, locY := float64(x), float64(y) entity := &Object{ + uuid: uuid.NewV4().String(), objectRecord: objectRec, Position: d2vector.NewPosition(locX, locY), name: d2common.TranslateString(objectRec.Name), @@ -84,6 +88,11 @@ func (ob *Object) setMode(animationMode d2enum.ObjectAnimationMode, direction in return err } +// ID returns the object uuid +func (ob *Object) ID() string { + return ob.uuid +} + // Highlight sets the entity highlighted flag to true. func (ob *Object) Highlight() { ob.highlight = true diff --git a/d2game/d2gamescreen/game.go b/d2game/d2gamescreen/game.go index 0cc83367..64020d55 100644 --- a/d2game/d2gamescreen/game.go +++ b/d2game/d2gamescreen/game.go @@ -56,7 +56,7 @@ func CreateGame( // find the local player and its initial location var startX, startY float64 for _, player := range gameClient.Players { - if player.ID != gameClient.PlayerID { + if player.ID() != gameClient.PlayerID { continue } worldPosition := player.Position.World() @@ -237,7 +237,7 @@ func (v *Game) Advance(elapsed float64) error { func (v *Game) bindGameControls() error { for _, player := range v.gameClient.Players { - if player.ID != v.gameClient.PlayerID { + if player.ID() != v.gameClient.PlayerID { continue } @@ -253,7 +253,7 @@ func (v *Game) bindGameControls() error { v.gameControls.Load() if err := v.inputManager.BindHandler(v.gameControls); err != nil { - fmt.Printf(bindControlsErrStr, player.ID) + fmt.Printf(bindControlsErrStr, player.ID()) } break diff --git a/d2game/d2player/game_controls.go b/d2game/d2player/game_controls.go index 3501a2c7..ca3f803d 100644 --- a/d2game/d2player/game_controls.go +++ b/d2game/d2player/game_controls.go @@ -421,8 +421,8 @@ func (g *GameControls) isInActiveMenusRect(px int, py int) bool { // TODO: consider caching the panels to single image that is reused. func (g *GameControls) Render(target d2interface.Surface) error { - for entityIdx := range *g.mapEngine.Entities() { - entity := (*g.mapEngine.Entities())[entityIdx] + for entityIdx := range g.mapEngine.Entities() { + entity := (g.mapEngine.Entities())[entityIdx] if !entity.Selectable() { continue } diff --git a/d2networking/d2client/game_client.go b/d2networking/d2client/game_client.go index d7c3652d..7478c4bb 100644 --- a/d2networking/d2client/game_client.go +++ b/d2networking/d2client/game_client.go @@ -171,7 +171,7 @@ func (g *GameClient) handleAddPlayerPacket(packet d2netpacket.NetPacket) error { newPlayer := d2mapentity.NewPlayer(player.ID, player.Name, player.X, player.Y, 0, player.HeroType, player.Stats, &player.Equipment) - g.Players[newPlayer.ID] = newPlayer + g.Players[newPlayer.ID()] = newPlayer g.MapEngine.AddEntity(newPlayer) return nil @@ -214,7 +214,8 @@ func (g *GameClient) handleMovePlayerPacket(packet d2netpacket.NetPacket) error err := player.SetAnimationMode(player.GetAnimationMode()) if err != nil { - log.Printf("GameClient: error setting animation mode for player %s: %s", player.ID, err) + fmtStr := "GameClient: error setting animation mode for player %s: %s" + log.Printf(fmtStr, player.ID(), err) } }) }