D2map lint warnings (#566)
* Comments and newlines in engine.go * Comments and newlines in object.go * Comments and newlines in animated_entity.go * Comments and newlines in missile.go * Comments and newlines in npc.go * Comments and newlines in player.go * Removed object.go (incorrectly merged it in during rebase). * Comments and newlines in renderer.go. * Comments and newlines in map_entity.go. * Comments and newlines in walk_mesh.go. * Comments and newlines in viewport.go and tile_cache.go. * Comments and newlines in stamp.go and wilderness_tile_types.go. * Comments and newlines in everything else.
This commit is contained in:
parent
029cb62972
commit
6104adc700
|
@ -18,35 +18,40 @@ import (
|
|||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
|
||||
)
|
||||
|
||||
// Represents the map data for a specific location
|
||||
// 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
|
||||
tiles []d2ds1.TileRecord // The map tiles
|
||||
size d2common.Size // The size of the map, in tiles
|
||||
levelType d2datadict.LevelTypeRecord // The level type of this map
|
||||
dt1TileData []d2dt1.Tile // The DT1 tile data
|
||||
walkMesh []d2common.PathTile // The walk mesh
|
||||
startSubTileX int // The starting X position
|
||||
startSubTileY int // The starting Y position
|
||||
dt1Files []string // The list of DS1 strings
|
||||
tiles []d2ds1.TileRecord // Map tiles
|
||||
size d2common.Size // Size of the map, in tiles
|
||||
levelType d2datadict.LevelTypeRecord // Level type of this map
|
||||
dt1TileData []d2dt1.Tile // DT1 tile data
|
||||
walkMesh []d2common.PathTile // Sub tiles representing the walkable map area
|
||||
startSubTileX int // Starting X position
|
||||
startSubTileY int // Starting Y position
|
||||
dt1Files []string // List of DS1 strings
|
||||
}
|
||||
|
||||
// Creates a new instance of the map engine
|
||||
// CreateMapEngine creates a new instance of the map engine and
|
||||
// returns a pointer to it.
|
||||
func CreateMapEngine() *MapEngine {
|
||||
engine := &MapEngine{}
|
||||
return engine
|
||||
}
|
||||
|
||||
// WalkMesh returns a pointer to a slice with the map's PathTiles.
|
||||
func (m *MapEngine) WalkMesh() *[]d2common.PathTile {
|
||||
return &m.walkMesh
|
||||
}
|
||||
|
||||
// Returns the starting position on the map in sub-tiles
|
||||
// GetStartingPosition returns the starting position on the map in
|
||||
// sub-tiles.
|
||||
func (m *MapEngine) GetStartingPosition() (int, int) {
|
||||
return m.startSubTileX, m.startSubTileY
|
||||
}
|
||||
|
||||
// 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.levelType = d2datadict.LevelTypes[levelType]
|
||||
|
@ -59,13 +64,13 @@ func (m *MapEngine) ResetMap(levelType d2enum.RegionIdType, width, height int) {
|
|||
for idx := range m.levelType.Files {
|
||||
m.addDT1(m.levelType.Files[idx])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *MapEngine) addDT1(fileName string) {
|
||||
if len(fileName) == 0 || fileName == "0" {
|
||||
return
|
||||
}
|
||||
|
||||
fileName = strings.ToLower(fileName)
|
||||
for i := 0; i < len(m.dt1Files); i++ {
|
||||
if m.dt1Files[i] == fileName {
|
||||
|
@ -77,13 +82,17 @@ func (m *MapEngine) addDT1(fileName string) {
|
|||
if err != nil {
|
||||
log.Printf("Could not load /data/global/tiles/%s", fileName)
|
||||
return
|
||||
//panic(err)
|
||||
// panic(err)
|
||||
}
|
||||
|
||||
dt1, _ := d2dt1.LoadDT1(fileData)
|
||||
m.dt1TileData = append(m.dt1TileData, dt1.Tiles...)
|
||||
m.dt1Files = append(m.dt1Files, fileName)
|
||||
}
|
||||
|
||||
// AddDS1 loads DT1 files and performs string replacements on them. It
|
||||
// appends the tile data and files to MapEngine.dt1TileData and
|
||||
// MapEngine.dt1Files.
|
||||
func (m *MapEngine) AddDS1(fileName string) {
|
||||
if len(fileName) == 0 || fileName == "0" {
|
||||
return
|
||||
|
@ -93,7 +102,9 @@ func (m *MapEngine) AddDS1(fileName string) {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ds1, _ := d2ds1.LoadDS1(fileData)
|
||||
|
||||
for idx := range ds1.Files {
|
||||
dt1File := ds1.Files[idx]
|
||||
dt1File = strings.ToLower(dt1File)
|
||||
|
@ -104,42 +115,47 @@ func (m *MapEngine) AddDS1(fileName string) {
|
|||
}
|
||||
}
|
||||
|
||||
// FindTile returns the tile of given stye, sequence and tileType.
|
||||
func (m *MapEngine) FindTile(style, sequence, tileType int32) d2dt1.Tile {
|
||||
for idx := range m.dt1TileData {
|
||||
if m.dt1TileData[idx].Style == style && m.dt1TileData[idx].Sequence == sequence && m.dt1TileData[idx].Type == tileType {
|
||||
return m.dt1TileData[idx]
|
||||
}
|
||||
}
|
||||
|
||||
panic("Could not find the requested tile!")
|
||||
}
|
||||
|
||||
// Returns the level type of this map
|
||||
// LevelType returns the level type of this map.
|
||||
func (m *MapEngine) LevelType() d2datadict.LevelTypeRecord {
|
||||
return m.levelType
|
||||
}
|
||||
|
||||
// Sets the seed of the map for generation
|
||||
// SetSeed sets the seed of the map for generation.
|
||||
func (m *MapEngine) SetSeed(seed int64) {
|
||||
log.Printf("Setting map engine seed to %d", seed)
|
||||
m.seed = seed
|
||||
}
|
||||
|
||||
// Returns the size of the map (in sub-tiles)
|
||||
// Size returns the size of the map in sub-tiles.
|
||||
func (m *MapEngine) Size() d2common.Size {
|
||||
return m.size
|
||||
}
|
||||
|
||||
// Tile returns the TileRecord containing the data
|
||||
// for a single map tile.
|
||||
func (m *MapEngine) Tile(x, y int) *d2ds1.TileRecord {
|
||||
return &m.tiles[x+(y*m.size.Width)]
|
||||
}
|
||||
|
||||
// Returns the map's tiles
|
||||
// Tiles returns a pointer to a slice contaning all
|
||||
// map tile data.
|
||||
func (m *MapEngine) Tiles() *[]d2ds1.TileRecord {
|
||||
return &m.tiles
|
||||
}
|
||||
|
||||
// Places a stamp at the specified location.
|
||||
// Also adds any entities from the stamp to the map engine
|
||||
// PlaceStamp places a map stamp at the specified location, creating both entities
|
||||
// and tiles. Stamps are pre-defined map areas, see d2mapstamp.
|
||||
func (m *MapEngine) PlaceStamp(stamp *d2mapstamp.Stamp, tileOffsetX, tileOffsetY int) {
|
||||
stampSize := stamp.Size()
|
||||
stampW := stampSize.Width
|
||||
|
@ -180,54 +196,62 @@ func (m *MapEngine) tileIndexToCoordinate(index int) (int, int) {
|
|||
return (index % m.size.Width), (index / m.size.Width)
|
||||
}
|
||||
|
||||
// Returns a reference to a map tile based on the tile X,Y coordinate
|
||||
// TileAt returns a pointer to the data for the map tile at the given
|
||||
// x and y index.
|
||||
func (m *MapEngine) TileAt(tileX, tileY int) *d2ds1.TileRecord {
|
||||
idx := m.tileCoordinateToIndex(tileX, tileY)
|
||||
if idx < 0 || idx >= len(m.tiles) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &m.tiles[idx]
|
||||
}
|
||||
|
||||
// Returns a reference to the map entities
|
||||
// Entities returns a pointer a slice of all map entities.
|
||||
func (m *MapEngine) Entities() *[]d2interface.MapEntity {
|
||||
return &m.entities
|
||||
}
|
||||
|
||||
// Returns the map engine's seed
|
||||
// Seed returns the map generation seed.
|
||||
func (m *MapEngine) Seed() int64 {
|
||||
return m.seed
|
||||
}
|
||||
|
||||
// Adds an entity to the map engine
|
||||
// AddEntity adds an entity to a slice containing all entities.
|
||||
func (m *MapEngine) AddEntity(entity d2interface.MapEntity) {
|
||||
m.entities = append(m.entities, entity)
|
||||
}
|
||||
|
||||
// Removes an entity from the map engine
|
||||
// RemoveEntity is not currently implemented.
|
||||
func (m *MapEngine) RemoveEntity(entity d2interface.MapEntity) {
|
||||
if entity == nil {
|
||||
return
|
||||
}
|
||||
//panic("Removing entities is not currently implemented")
|
||||
//m.entities.Remove(entity)
|
||||
// m.entities.Remove(entity)
|
||||
}
|
||||
|
||||
// GetTiles returns a slice of all tiles matching the given style,
|
||||
// sequence and tileType.
|
||||
func (m *MapEngine) GetTiles(style, sequence, tileType int32) []d2dt1.Tile {
|
||||
var tiles []d2dt1.Tile
|
||||
|
||||
for idx := range m.dt1TileData {
|
||||
if m.dt1TileData[idx].Style != style || m.dt1TileData[idx].Sequence != sequence || m.dt1TileData[idx].Type != tileType {
|
||||
continue
|
||||
}
|
||||
|
||||
tiles = append(tiles, m.dt1TileData[idx])
|
||||
}
|
||||
|
||||
if len(tiles) == 0 {
|
||||
log.Printf("Unknown tile ID [%d %d %d]\n", style, sequence, tileType)
|
||||
return nil
|
||||
}
|
||||
|
||||
return tiles
|
||||
}
|
||||
|
||||
// GetStartPosition returns the spawn point on entering the current map.
|
||||
func (m *MapEngine) GetStartPosition() (float64, float64) {
|
||||
for tileY := 0; tileY < m.size.Height; tileY++ {
|
||||
for tileX := 0; tileX < m.size.Width; tileX++ {
|
||||
|
@ -243,32 +267,37 @@ func (m *MapEngine) GetStartPosition() (float64, float64) {
|
|||
return m.GetCenterPosition()
|
||||
}
|
||||
|
||||
// Returns the center of the map
|
||||
// GetCenterPosition returns the center point of the map.
|
||||
func (m *MapEngine) GetCenterPosition() (float64, float64) {
|
||||
return float64(m.size.Width) / 2.0, float64(m.size.Height) / 2.0
|
||||
}
|
||||
|
||||
// Advances time on the map engine
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if a tile exists
|
||||
// TileExists returns true if the tile at the given coordinates exists.
|
||||
func (m *MapEngine) TileExists(tileX, tileY int) bool {
|
||||
tileIndex := m.tileCoordinateToIndex(tileX, tileY)
|
||||
|
||||
if valid := (tileIndex >= 0) && (tileIndex <= len(m.tiles)); valid {
|
||||
tile := m.tiles[tileIndex]
|
||||
numFeatures := len(tile.Floors)
|
||||
numFeatures += len(tile.Shadows)
|
||||
numFeatures += len(tile.Walls)
|
||||
numFeatures += len(tile.Substitutions)
|
||||
|
||||
return numFeatures > 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GenerateMap clears the map and places the specified stamp.
|
||||
func (m *MapEngine) GenerateMap(regionType d2enum.RegionIdType, levelPreset int, fileIndex int, cacheTiles bool) {
|
||||
region := d2mapstamp.LoadStamp(regionType, levelPreset, fileIndex)
|
||||
regionSize := region.Size()
|
||||
|
@ -276,11 +305,13 @@ func (m *MapEngine) GenerateMap(regionType d2enum.RegionIdType, levelPreset int,
|
|||
m.PlaceStamp(region, 0, 0)
|
||||
}
|
||||
|
||||
// GetTileData returns the tile with the given style, sequence and tileType.
|
||||
func (m *MapEngine) GetTileData(style int32, sequence int32, tileType d2enum.TileType) *d2dt1.Tile {
|
||||
for idx := range m.dt1TileData {
|
||||
if m.dt1TileData[idx].Style == style && m.dt1TileData[idx].Sequence == sequence && m.dt1TileData[idx].Type == int32(tileType) {
|
||||
return &m.dt1TileData[idx]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -8,31 +8,39 @@ import (
|
|||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
)
|
||||
|
||||
// RegenerateWalkPaths based on current tile data.
|
||||
func (m *MapEngine) RegenerateWalkPaths() {
|
||||
for subTileY := 0; subTileY < m.size.Height*5; subTileY++ {
|
||||
tileY := int(float64(subTileY) / 5.0)
|
||||
|
||||
for subTileX := 0; subTileX < m.size.Width*5; subTileX++ {
|
||||
tileX := int(float64(subTileX) / 5.0)
|
||||
tile := m.TileAt(tileX, tileY)
|
||||
isBlocked := false
|
||||
|
||||
for _, floor := range tile.Floors {
|
||||
tileData := m.GetTileData(int32(floor.Style), int32(floor.Sequence), d2enum.Floor)
|
||||
if tileData == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
tileSubAttributes := tileData.GetSubTileFlags(subTileX%5, subTileY%5)
|
||||
|
||||
isBlocked = isBlocked || tileSubAttributes.BlockWalk
|
||||
if isBlocked {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isBlocked {
|
||||
for _, wall := range tile.Walls {
|
||||
tileData := m.GetTileData(int32(wall.Style), int32(wall.Sequence), wall.Type)
|
||||
if tileData == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
tileSubAttributes := tileData.GetSubTileFlags(subTileX%5, subTileY%5)
|
||||
|
||||
isBlocked = isBlocked || tileSubAttributes.BlockWalk
|
||||
if isBlocked {
|
||||
break
|
||||
|
@ -52,14 +60,17 @@ func (m *MapEngine) RegenerateWalkPaths() {
|
|||
m.walkMesh[index].Up = &m.walkMesh[index-ySkew]
|
||||
m.walkMesh[index-ySkew].Down = &m.walkMesh[index]
|
||||
}
|
||||
|
||||
if !isBlocked && subTileX > 0 && m.walkMesh[index-1].Walkable {
|
||||
m.walkMesh[index].Left = &m.walkMesh[index-1]
|
||||
m.walkMesh[index-1].Right = &m.walkMesh[index]
|
||||
}
|
||||
|
||||
if !isBlocked && subTileX > 0 && subTileY > 0 && m.walkMesh[(index-ySkew)-1].Walkable {
|
||||
m.walkMesh[index].UpLeft = &m.walkMesh[(index-ySkew)-1]
|
||||
m.walkMesh[(index-ySkew)-1].DownRight = &m.walkMesh[index]
|
||||
}
|
||||
|
||||
if !isBlocked && subTileY > 0 && subTileX < (m.size.Width*5)-1 && m.walkMesh[(index-ySkew)+1].Walkable {
|
||||
m.walkMesh[index].UpRight = &m.walkMesh[(index-ySkew)+1]
|
||||
m.walkMesh[(index-ySkew)+1].DownLeft = &m.walkMesh[index]
|
||||
|
@ -68,26 +79,32 @@ func (m *MapEngine) RegenerateWalkPaths() {
|
|||
}
|
||||
}
|
||||
|
||||
// Finds a walkable path between two points
|
||||
// PathFind finds a walkable path between two points.
|
||||
func (m *MapEngine) PathFind(startX, startY, endX, endY float64) (path []d2astar.Pather, distance float64, found bool) {
|
||||
startTileX := int(math.Floor(startX))
|
||||
startTileY := int(math.Floor(startY))
|
||||
|
||||
if !m.TileExists(startTileX, startTileY) {
|
||||
return
|
||||
}
|
||||
|
||||
startSubtileX := int((startX - float64(int(startX))) * 5)
|
||||
startSubtileY := int((startY - float64(int(startY))) * 5)
|
||||
startNodeIndex := ((startSubtileY + (startTileY * 5)) * m.size.Width * 5) + startSubtileX + ((startTileX) * 5)
|
||||
|
||||
if startNodeIndex < 0 || startNodeIndex >= len(m.walkMesh) {
|
||||
return
|
||||
}
|
||||
|
||||
startNode := &m.walkMesh[startNodeIndex]
|
||||
|
||||
endTileX := int(math.Floor(endX))
|
||||
endTileY := int(math.Floor(endY))
|
||||
|
||||
if !m.TileExists(endTileX, endTileY) {
|
||||
return
|
||||
}
|
||||
|
||||
endSubtileX := int((endX - float64(int(endX))) * 5)
|
||||
endSubtileY := int((endY - float64(int(endY))) * 5)
|
||||
|
||||
|
@ -95,17 +112,19 @@ func (m *MapEngine) PathFind(startX, startY, endX, endY float64) (path []d2astar
|
|||
if endNodeIndex < 0 || endNodeIndex >= len(m.walkMesh) {
|
||||
return
|
||||
}
|
||||
|
||||
endNode := &m.walkMesh[endNodeIndex]
|
||||
|
||||
path, distance, found = d2astar.Path(startNode, endNode, 80)
|
||||
if path != nil {
|
||||
// Reverse the path to fit what the game expects.
|
||||
for i := len(path)/2-1; i >= 0; i-- {
|
||||
opp := len(path)-1-i
|
||||
for i := len(path)/2 - 1; i >= 0; i-- {
|
||||
opp := len(path) - 1 - i
|
||||
path[i], path[opp] = path[opp], path[i]
|
||||
}
|
||||
|
||||
path = path[1:]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ func CreateAnimatedEntity(x, y int, animation d2interface.Animation) *AnimatedEn
|
|||
animation: animation,
|
||||
}
|
||||
entity.mapEntity.directioner = entity.rotate
|
||||
|
||||
return entity
|
||||
}
|
||||
|
||||
|
@ -34,6 +35,7 @@ func (ae *AnimatedEntity) Render(target d2interface.Surface) {
|
|||
ae.animation.Render(target)
|
||||
}
|
||||
|
||||
// GetDirection returns the current facing direction of this entity.
|
||||
func (ae *AnimatedEntity) GetDirection() int {
|
||||
return ae.direction
|
||||
}
|
||||
|
@ -45,6 +47,8 @@ func (ae *AnimatedEntity) rotate(direction int) {
|
|||
ae.animation.SetDirection(ae.direction)
|
||||
}
|
||||
|
||||
// Advance is called once per frame and processes a
|
||||
// single game tick.
|
||||
func (ae *AnimatedEntity) Advance(elapsed float64) {
|
||||
ae.animation.Advance(elapsed)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
)
|
||||
|
||||
// mapEntity represents an entity on the map that can be animated
|
||||
// TODO: Has a coordinate (issue #456)
|
||||
type mapEntity struct {
|
||||
LocationX float64
|
||||
LocationY float64
|
||||
|
@ -27,6 +28,7 @@ type mapEntity struct {
|
|||
// createMapEntity creates an instance of mapEntity
|
||||
func createMapEntity(x, y int) mapEntity {
|
||||
locX, locY := float64(x), float64(y)
|
||||
|
||||
return mapEntity{
|
||||
LocationX: locX,
|
||||
LocationY: locY,
|
||||
|
@ -42,23 +44,29 @@ func createMapEntity(x, y int) mapEntity {
|
|||
}
|
||||
}
|
||||
|
||||
// GetLayer returns the draw layer for this entity.
|
||||
func (m *mapEntity) GetLayer() int {
|
||||
return m.drawLayer
|
||||
}
|
||||
|
||||
// SetPath sets the entity movement path. done() is called when the entity reaches it's path destination. For example,
|
||||
// when the player entity reaches the point a player clicked.
|
||||
func (m *mapEntity) SetPath(path []d2astar.Pather, done func()) {
|
||||
m.path = path
|
||||
m.done = done
|
||||
}
|
||||
|
||||
// ClearPath clears the entity movement path.
|
||||
func (m *mapEntity) ClearPath() {
|
||||
m.path = nil
|
||||
}
|
||||
|
||||
// SetSpeed sets the entity movement speed.
|
||||
func (m *mapEntity) SetSpeed(speed float64) {
|
||||
m.Speed = speed
|
||||
}
|
||||
|
||||
// GetSpeed returns the entity movement speed.
|
||||
func (m *mapEntity) GetSpeed() float64 {
|
||||
return m.Speed
|
||||
}
|
||||
|
@ -75,30 +83,37 @@ func (m *mapEntity) getStepLength(tickTime float64) (float64, float64) {
|
|||
radians := (math.Pi / 180.0) * float64(angle)
|
||||
oneStepX := length * math.Cos(radians)
|
||||
oneStepY := length * math.Sin(radians)
|
||||
|
||||
return oneStepX, oneStepY
|
||||
}
|
||||
|
||||
// IsAtTarget returns true if the entity is within a 0.0002 square of it's target and has a path.
|
||||
func (m *mapEntity) IsAtTarget() bool {
|
||||
return math.Abs(m.LocationX-m.TargetX) < 0.0001 && math.Abs(m.LocationY-m.TargetY) < 0.0001 && !m.HasPathFinding()
|
||||
}
|
||||
|
||||
// Step moves the entity along it's path by one tick. If the path is complete it calls entity.done() then returns.
|
||||
func (m *mapEntity) Step(tickTime float64) {
|
||||
if m.IsAtTarget() {
|
||||
if m.done != nil {
|
||||
m.done()
|
||||
m.done = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
stepX, stepY := m.getStepLength(tickTime)
|
||||
|
||||
for {
|
||||
if d2common.AlmostEqual(m.LocationX-m.TargetX, 0, 0.0001) {
|
||||
stepX = 0
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
|
@ -129,15 +144,15 @@ func (m *mapEntity) Step(tickTime float64) {
|
|||
if stepX == 0 && stepY == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// HasPathFinding returns false if the length of the entity movement path is 0.
|
||||
func (m *mapEntity) HasPathFinding() bool {
|
||||
return len(m.path) > 0
|
||||
}
|
||||
|
||||
// SetTarget sets target coordinates and changes animation based on proximity and direction
|
||||
// SetTarget sets target coordinates and changes animation based on proximity and direction.
|
||||
func (m *mapEntity) SetTarget(tx, ty float64, done func()) {
|
||||
m.TargetX, m.TargetY = tx, ty
|
||||
m.done = done
|
||||
|
@ -168,21 +183,26 @@ func angleToDirection(angle float64) int {
|
|||
return newDirection
|
||||
}
|
||||
|
||||
// GetPosition returns the entity's current tile position.
|
||||
func (m *mapEntity) GetPosition() (float64, float64) {
|
||||
return float64(m.TileX), float64(m.TileY)
|
||||
}
|
||||
|
||||
// GetPositionF returns the entity's current sub tile position.
|
||||
func (m *mapEntity) GetPositionF() (float64, float64) {
|
||||
return float64(m.TileX) + (float64(m.subcellX) / 5.0), float64(m.TileY) + (float64(m.subcellY) / 5.0)
|
||||
}
|
||||
|
||||
// Name returns the NPC's in-game name (e.g. "Deckard Cain") or an empty string if it does not have a name
|
||||
func (m *mapEntity) Name() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Highlight is not currently implemented.
|
||||
func (m *mapEntity) Highlight() {
|
||||
}
|
||||
|
||||
// Selectable returns true if the object can be highlighted/selected.
|
||||
func (m *mapEntity) Selectable() bool {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -10,11 +10,14 @@ import (
|
|||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
||||
)
|
||||
|
||||
// Missile is a simple animated entity representing a projectile,
|
||||
// such as a spell or arrow.
|
||||
type Missile struct {
|
||||
*AnimatedEntity
|
||||
record *d2datadict.MissileRecord
|
||||
}
|
||||
|
||||
// CreateMissile creates a new Missile and initializes it's animation.
|
||||
func CreateMissile(x, y int, record *d2datadict.MissileRecord) (*Missile, error) {
|
||||
animation, err := d2asset.LoadAnimation(
|
||||
fmt.Sprintf("%s/%s.dcc", d2resource.MissileData, record.Animation.CelFileName),
|
||||
|
@ -39,9 +42,12 @@ func CreateMissile(x, y int, record *d2datadict.MissileRecord) (*Missile, error)
|
|||
record: record,
|
||||
}
|
||||
result.Speed = float64(record.Velocity)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// SetRadians adjusts the entity target based on it's range, rotating it's
|
||||
// current destination by the value of angle in radians.
|
||||
func (m *Missile) SetRadians(angle float64, done func()) {
|
||||
r := float64(m.record.Range)
|
||||
|
||||
|
@ -51,6 +57,8 @@ func (m *Missile) SetRadians(angle float64, done func()) {
|
|||
m.SetTarget(x, y, done)
|
||||
}
|
||||
|
||||
// Advance is called once per frame and processes a
|
||||
// single game tick.
|
||||
func (m *Missile) Advance(tickTime float64) {
|
||||
// TODO: collision detection
|
||||
m.Step(tickTime)
|
||||
|
|
|
@ -12,6 +12,8 @@ import (
|
|||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
||||
)
|
||||
|
||||
// NPC is a passive complex entity with which the player can interact.
|
||||
// For example, Deckard Cain.
|
||||
type NPC struct {
|
||||
mapEntity
|
||||
composite *d2asset.Composite
|
||||
|
@ -26,6 +28,7 @@ type NPC struct {
|
|||
name string
|
||||
}
|
||||
|
||||
// CreateNPC creates a new NPC and returns a pointer to it.
|
||||
func CreateNPC(x, y int, monstat *d2datadict.MonStatsRecord, direction int) *NPC {
|
||||
result := &NPC{
|
||||
mapEntity: createMapEntity(x, y),
|
||||
|
@ -67,6 +70,7 @@ func selectEquip(slice []string) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
// Render renders this entity's animated composite.
|
||||
func (v *NPC) Render(target d2interface.Surface) {
|
||||
target.PushTranslation(
|
||||
v.offsetX+int((v.subcellX-v.subcellY)*16),
|
||||
|
@ -76,10 +80,12 @@ func (v *NPC) Render(target d2interface.Surface) {
|
|||
v.composite.Render(target)
|
||||
}
|
||||
|
||||
// Path returns the current part of the entity's path.
|
||||
func (v *NPC) Path() d2common.Path {
|
||||
return v.Paths[v.path]
|
||||
}
|
||||
|
||||
// NextPath returns the next part of the entity's path.
|
||||
func (v *NPC) NextPath() d2common.Path {
|
||||
v.path++
|
||||
if v.path == len(v.Paths) {
|
||||
|
@ -89,12 +95,17 @@ func (v *NPC) NextPath() d2common.Path {
|
|||
return v.Paths[v.path]
|
||||
}
|
||||
|
||||
// SetPaths sets the entity's paths to the given slice. It also sets flags
|
||||
// on the entity indicating that it has paths and has completed the
|
||||
// previous none.
|
||||
func (v *NPC) SetPaths(paths []d2common.Path) {
|
||||
v.Paths = paths
|
||||
v.HasPaths = len(paths) > 0
|
||||
v.isDone = true
|
||||
}
|
||||
|
||||
// Advance is called once per frame and processes a
|
||||
// single game tick.
|
||||
func (v *NPC) Advance(tickTime float64) {
|
||||
v.Step(tickTime)
|
||||
v.composite.Advance(tickTime)
|
||||
|
@ -108,6 +119,7 @@ func (v *NPC) Advance(tickTime float64) {
|
|||
float64(path.Y),
|
||||
v.next,
|
||||
)
|
||||
|
||||
v.action = path.Action
|
||||
}
|
||||
}
|
||||
|
@ -160,14 +172,17 @@ func (v *NPC) rotate(direction int) {
|
|||
}
|
||||
}
|
||||
|
||||
// Selectable returns true if the object can be highlighted/selected.
|
||||
func (m *NPC) Selectable() bool {
|
||||
// is there something handy that determines selectable npc's?
|
||||
if m.name != "" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Name returns the NPC's in-game name (e.g. "Deckard Cain") or an empty string if it does not have a name.
|
||||
func (m *NPC) Name() string {
|
||||
return m.name
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory"
|
||||
)
|
||||
|
||||
// Player is the player character entity.
|
||||
type Player struct {
|
||||
mapEntity
|
||||
composite *d2asset.Composite
|
||||
|
@ -32,6 +33,7 @@ type Player struct {
|
|||
var baseWalkSpeed = 6.0
|
||||
var baseRunSpeed = 9.0
|
||||
|
||||
// CreatePlayer creates a new player entity and returns a pointer to it.
|
||||
func CreatePlayer(id, name string, x, y int, direction int, heroType d2enum.Hero, stats d2hero.HeroStatsState, equipment d2inventory.CharacterEquipment) *Player {
|
||||
layerEquipment := &[d2enum.CompositeTypeMax]string{
|
||||
d2enum.CompositeTypeHead: equipment.Head.GetArmorClass(),
|
||||
|
@ -72,31 +74,40 @@ func CreatePlayer(id, name string, x, y int, direction int, heroType d2enum.Hero
|
|||
//result.nameLabel.SetText(name)
|
||||
//result.nameLabel.Color = color.White
|
||||
err = composite.SetMode(d2enum.AnimationModePlayerTownNeutral.String(), equipment.RightHand.GetWeaponClass())
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
composite.SetDirection(direction)
|
||||
composite.Equip(layerEquipment)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// SetIsInTown sets a flag indicating that the player is in town.
|
||||
func (p *Player) SetIsInTown(isInTown bool) {
|
||||
p.isInTown = isInTown
|
||||
}
|
||||
|
||||
// ToggleRunWalk sets a flag indicating whether the player is running.
|
||||
func (p *Player) ToggleRunWalk() {
|
||||
p.isRunToggled = !p.isRunToggled
|
||||
}
|
||||
|
||||
// IsRunToggled returns true if the UI button to toggle running is,
|
||||
// toggled i.e. not in it's default state.
|
||||
func (p *Player) IsRunToggled() bool {
|
||||
return p.isRunToggled
|
||||
}
|
||||
|
||||
// IsRunning returns true if the player is currently
|
||||
func (p *Player) IsRunning() bool {
|
||||
return p.isRunning
|
||||
}
|
||||
|
||||
// SetIsRunning alters the player speed and sets a flag indicating
|
||||
// that the player is running.
|
||||
func (p *Player) SetIsRunning(isRunning bool) {
|
||||
p.isRunning = isRunning
|
||||
|
||||
|
@ -107,17 +118,22 @@ func (p *Player) SetIsRunning(isRunning bool) {
|
|||
}
|
||||
}
|
||||
|
||||
// IsInTown returns true if the player is currently in town.
|
||||
func (p Player) IsInTown() bool {
|
||||
return p.isInTown
|
||||
}
|
||||
|
||||
// Advance is called once per frame and processes a
|
||||
// single game tick.
|
||||
func (v *Player) Advance(tickTime float64) {
|
||||
v.Step(tickTime)
|
||||
|
||||
if v.IsCasting() && v.composite.GetPlayedCount() >= 1 {
|
||||
v.isCasting = false
|
||||
v.SetAnimationMode(v.GetAnimationMode().String())
|
||||
}
|
||||
v.composite.Advance(tickTime)
|
||||
|
||||
if v.lastPathSize != len(v.path) {
|
||||
v.lastPathSize = len(v.path)
|
||||
}
|
||||
|
@ -127,6 +143,7 @@ func (v *Player) Advance(tickTime float64) {
|
|||
}
|
||||
}
|
||||
|
||||
// Render renders the animated composite for this entity.
|
||||
func (v *Player) Render(target d2interface.Surface) {
|
||||
target.PushTranslation(
|
||||
v.offsetX+int((v.subcellX-v.subcellY)*16),
|
||||
|
@ -134,11 +151,12 @@ func (v *Player) Render(target d2interface.Surface) {
|
|||
)
|
||||
defer target.Pop()
|
||||
v.composite.Render(target)
|
||||
//v.nameLabel.X = v.offsetX
|
||||
//v.nameLabel.Y = v.offsetY - 100
|
||||
//v.nameLabel.Render(target)
|
||||
// v.nameLabel.X = v.offsetX
|
||||
// v.nameLabel.Y = v.offsetY - 100
|
||||
// v.nameLabel.Render(target)
|
||||
}
|
||||
|
||||
// GetAnimationMode returns the current animation mode based on what the player is doing and where they are.
|
||||
func (v *Player) GetAnimationMode() d2enum.PlayerAnimationMode {
|
||||
if v.IsRunning() && !v.IsAtTarget() {
|
||||
return d2enum.AnimationModePlayerRun
|
||||
|
@ -163,6 +181,7 @@ func (v *Player) GetAnimationMode() d2enum.PlayerAnimationMode {
|
|||
return d2enum.AnimationModePlayerNeutral
|
||||
}
|
||||
|
||||
// SetAnimationMode sets the animation mode for this entity's animated composite.
|
||||
func (v *Player) SetAnimationMode(animationMode string) error {
|
||||
return v.composite.SetMode(animationMode, v.composite.GetWeaponClass())
|
||||
}
|
||||
|
@ -180,19 +199,24 @@ func (v *Player) rotate(direction int) {
|
|||
}
|
||||
}
|
||||
|
||||
// Name returns the player name.
|
||||
func (v *Player) Name() string {
|
||||
return v.name
|
||||
}
|
||||
|
||||
// IsCasting returns true if
|
||||
func (v *Player) IsCasting() bool {
|
||||
return v.isCasting
|
||||
}
|
||||
|
||||
// SetCasting sets a flag indicating the player is casting a skill and
|
||||
// sets the animation mode to the casting animation.
|
||||
func (v *Player) SetCasting() {
|
||||
v.isCasting = true
|
||||
v.SetAnimationMode(d2enum.AnimationModePlayerCast.String())
|
||||
}
|
||||
|
||||
// Selectable returns true if the player is in town.
|
||||
func (v *Player) Selectable() bool {
|
||||
// Players are selectable when in town
|
||||
return v.IsInTown()
|
||||
|
|
|
@ -20,13 +20,16 @@ func loadPreset(mapEngine *d2mapengine.MapEngine, id, index int) *d2mapstamp.Sta
|
|||
for _, file := range d2datadict.LevelPreset(id).Files {
|
||||
mapEngine.AddDS1(file)
|
||||
}
|
||||
|
||||
return d2mapstamp.LoadStamp(d2enum.RegionAct1Wilderness, id, index)
|
||||
}
|
||||
|
||||
// GenerateAct1Overworld generates the map and entities for the first town and surrounding area.
|
||||
func GenerateAct1Overworld(mapEngine *d2mapengine.MapEngine) {
|
||||
|
||||
rand.Seed(mapEngine.Seed())
|
||||
|
||||
wilderness1Details := d2datadict.GetLevelDetails(2)
|
||||
|
||||
mapEngine.ResetMap(d2enum.RegionAct1Town, 150, 150)
|
||||
mapWidth := mapEngine.Size().Width
|
||||
mapHeight := mapEngine.Size().Height
|
||||
|
@ -40,7 +43,6 @@ func GenerateAct1Overworld(mapEngine *d2mapengine.MapEngine) {
|
|||
// East Exit
|
||||
mapEngine.PlaceStamp(townStamp, 0, 0)
|
||||
generateWilderness1TownEast(mapEngine, townSize.Width, 0)
|
||||
|
||||
} else if strings.Contains(townStamp.RegionPath(), "S1") {
|
||||
// South Exit
|
||||
mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, 0)
|
||||
|
@ -54,12 +56,11 @@ func GenerateAct1Overworld(mapEngine *d2mapengine.MapEngine) {
|
|||
mapEngine.PlaceStamp(rightWaterBorderStamp2, mapWidth-9, y)
|
||||
}
|
||||
generateWilderness1TownSouth(mapEngine, mapWidth-wilderness1Details.SizeXNormal-14, townSize.Height)
|
||||
|
||||
} else if strings.Contains(townStamp.RegionPath(), "W1") {
|
||||
// West Exit
|
||||
mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, mapHeight-townSize.Height)
|
||||
|
||||
generateWilderness1TownWest(mapEngine, mapWidth-townSize.Width - wilderness1Details.SizeXNormal, mapHeight-wilderness1Details.SizeYNormal)
|
||||
generateWilderness1TownWest(mapEngine, mapWidth-townSize.Width-wilderness1Details.SizeXNormal, mapHeight-wilderness1Details.SizeYNormal)
|
||||
} else {
|
||||
// North Exit
|
||||
mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, mapHeight-townSize.Height)
|
||||
|
@ -102,30 +103,30 @@ func generateWilderness1TownEast(mapEngine *d2mapengine.MapEngine, startX, start
|
|||
|
||||
areaRect := d2common.Rectangle{
|
||||
Left: startX,
|
||||
Top: startY+9,
|
||||
Top: startY + 9,
|
||||
Width: levelDetails.SizeXNormal,
|
||||
Height: levelDetails.SizeYNormal-3,
|
||||
Height: levelDetails.SizeYNormal - 3,
|
||||
}
|
||||
generateWilderness1Contents(mapEngine, areaRect)
|
||||
|
||||
// Draw the north and south fence
|
||||
for i := 0; i < 9; i++ {
|
||||
mapEngine.PlaceStamp(fenceNorthStamp[rand.Intn(3)], startX+(i*9), startY)
|
||||
mapEngine.PlaceStamp(fenceSouthStamp[rand.Intn(3)], startX+(i*9), startY + (levelDetails.SizeYNormal +6))
|
||||
mapEngine.PlaceStamp(fenceSouthStamp[rand.Intn(3)], startX+(i*9), startY+(levelDetails.SizeYNormal+6))
|
||||
}
|
||||
|
||||
// West fence
|
||||
for i := 1; i < 6; i++ {
|
||||
mapEngine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, startY+ (levelDetails.SizeYNormal+6) - (i * 9))
|
||||
mapEngine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, startY+(levelDetails.SizeYNormal+6)-(i*9))
|
||||
}
|
||||
|
||||
// East Fence
|
||||
for i := 1; i < 10; i++ {
|
||||
mapEngine.PlaceStamp(fenceEastStamp[rand.Intn(3)], startX + levelDetails.SizeXNormal, startY+(i*9))
|
||||
mapEngine.PlaceStamp(fenceEastStamp[rand.Intn(3)], startX+levelDetails.SizeXNormal, startY+(i*9))
|
||||
}
|
||||
|
||||
mapEngine.PlaceStamp(fenceSouthWestStamp, startX, startY+ levelDetails.SizeYNormal+6)
|
||||
mapEngine.PlaceStamp(fenceWestEdge, startX, startY+ (levelDetails.SizeYNormal-3) - 45)
|
||||
mapEngine.PlaceStamp(fenceSouthWestStamp, startX, startY+levelDetails.SizeYNormal+6)
|
||||
mapEngine.PlaceStamp(fenceWestEdge, startX, startY+(levelDetails.SizeYNormal-3)-45)
|
||||
mapEngine.PlaceStamp(fenceNorthEastStamp, startX+levelDetails.SizeXNormal, startY)
|
||||
mapEngine.PlaceStamp(fenceSouthEastStamp, startX+levelDetails.SizeXNormal, startY+levelDetails.SizeYNormal+6)
|
||||
}
|
||||
|
@ -218,35 +219,35 @@ func generateWilderness1TownWest(mapEngine *d2mapengine.MapEngine, startX, start
|
|||
// Draw the north and south fences
|
||||
for i := 0; i < 9; i++ {
|
||||
if i > 0 && i < 8 {
|
||||
mapEngine.PlaceStamp(fenceNorthStamp[rand.Intn(3)], startX + (i*9)-1, startY-15)
|
||||
mapEngine.PlaceStamp(fenceNorthStamp[rand.Intn(3)], startX+(i*9)-1, startY-15)
|
||||
}
|
||||
|
||||
mapEngine.PlaceStamp(fenceSouthStamp[rand.Intn(3)], startX+(i*9)-1, startY+levelDetails.SizeYNormal-12)
|
||||
}
|
||||
|
||||
// Draw the east fence
|
||||
for i := 0; i < 6; i++ {
|
||||
mapEngine.PlaceStamp(fenceEastStamp[rand.Intn(3)], startX + levelDetails.SizeXNormal-9, startY + (i*9)-6)
|
||||
mapEngine.PlaceStamp(fenceEastStamp[rand.Intn(3)], startX+levelDetails.SizeXNormal-9, startY+(i*9)-6)
|
||||
}
|
||||
|
||||
// Draw the west fence
|
||||
for i := 0; i < 9; i++ {
|
||||
mapEngine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, startY + (i*9)-6)
|
||||
mapEngine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, startY+(i*9)-6)
|
||||
}
|
||||
|
||||
// Draw the west fence
|
||||
mapEngine.PlaceStamp(fenceEastEdge, startX + levelDetails.SizeXNormal-9, startY + 39)
|
||||
mapEngine.PlaceStamp(fenceEastEdge, startX+levelDetails.SizeXNormal-9, startY+39)
|
||||
mapEngine.PlaceStamp(fenceNorthWestStamp, startX, startY-15)
|
||||
mapEngine.PlaceStamp(fenceSouthWestStamp, startX, startY+levelDetails.SizeYNormal-12)
|
||||
mapEngine.PlaceStamp(fenceNorthEastStamp, startX+levelDetails.SizeXNormal-9, startY-15)
|
||||
|
||||
areaRect := d2common.Rectangle{
|
||||
Left: startX + 9,
|
||||
Top: startY-10,
|
||||
Top: startY - 10,
|
||||
Width: levelDetails.SizeXNormal - 9,
|
||||
Height: levelDetails.SizeYNormal - 2,
|
||||
}
|
||||
generateWilderness1Contents(mapEngine, areaRect)
|
||||
|
||||
}
|
||||
|
||||
func generateWilderness1Contents(mapEngine *d2mapengine.MapEngine, rect d2common.Rectangle) {
|
||||
|
@ -295,10 +296,10 @@ func generateWilderness1Contents(mapEngine *d2mapengine.MapEngine, rect d2common
|
|||
for numPlaced < 25 {
|
||||
stamp := stuff[rand.Intn(len(stuff))]
|
||||
|
||||
stampRect := d2common.Rectangle {
|
||||
Left: rect.Left+ rand.Intn(rect.Width) - stamp.Size().Width,
|
||||
Top: rect.Top+rand.Intn(rect.Height) - stamp.Size().Height,
|
||||
Width: stamp.Size().Width,
|
||||
stampRect := d2common.Rectangle{
|
||||
Left: rect.Left + rand.Intn(rect.Width) - stamp.Size().Width,
|
||||
Top: rect.Top + rand.Intn(rect.Height) - stamp.Size().Height,
|
||||
Width: stamp.Size().Width,
|
||||
Height: stamp.Size().Height,
|
||||
}
|
||||
|
||||
|
@ -307,7 +308,6 @@ func generateWilderness1Contents(mapEngine *d2mapengine.MapEngine, rect d2common
|
|||
numPlaced++
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func areaEmpty(mapEngine *d2mapengine.MapEngine, rect d2common.Rectangle) bool {
|
||||
|
@ -323,7 +323,9 @@ func areaEmpty(mapEngine *d2mapengine.MapEngine, rect d2common.Rectangle) bool {
|
|||
if len(mapEngine.Tile(x, y).Floors) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
floor := mapEngine.Tile(x, y).Floors[0]
|
||||
|
||||
if floor.Style != 0 || floor.Sequence != 0 || floor.Prop1 != 1 {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package d2wilderness
|
||||
|
||||
// nolint: golint // these probably don't require individual explanations.
|
||||
const (
|
||||
TreeBorderSouth int = iota + 4
|
||||
TreeBorderWest
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
// Package d2mapgen provides map generator implementations
|
||||
// for use with the map engine
|
||||
// Package d2mapgen provides map generator implementations for use with the map engine.
|
||||
package d2mapgen
|
||||
|
|
|
@ -1,20 +1,25 @@
|
|||
package d2maprenderer
|
||||
|
||||
// Camera is the position of the camera perspective in orthogonal world space. See viewport.go.
|
||||
// TODO: Has a coordinate (issue #456)
|
||||
type Camera struct {
|
||||
x float64
|
||||
y float64
|
||||
}
|
||||
|
||||
// MoveTo sets the position of the camera to the given x and y coordinates.
|
||||
func (c *Camera) MoveTo(x, y float64) {
|
||||
c.x = x
|
||||
c.y = y
|
||||
}
|
||||
|
||||
// MoveBy adds the given vector to the current position of the camera.
|
||||
func (c *Camera) MoveBy(x, y float64) {
|
||||
c.x += x
|
||||
c.y += y
|
||||
}
|
||||
|
||||
// GetPosition returns the camera x and y position.
|
||||
func (c *Camera) GetPosition() (float64, float64) {
|
||||
return c.x, c.y
|
||||
}
|
||||
|
|
|
@ -14,10 +14,12 @@ func (mr *MapRenderer) decodeTileGfxData(blocks []d2dt1.Block, pixels *[]byte, t
|
|||
x := int32(0)
|
||||
y := int32(0)
|
||||
idx := 0
|
||||
|
||||
for length > 0 {
|
||||
x = xjump[y]
|
||||
n := nbpix[y]
|
||||
length -= n
|
||||
|
||||
for n > 0 {
|
||||
offset := (((blockY + y + tileYOffset) * tileWidth) + (blockX + x))
|
||||
(*pixels)[offset] = block.EncodedData[idx]
|
||||
|
@ -27,6 +29,7 @@ func (mr *MapRenderer) decodeTileGfxData(blocks []d2dt1.Block, pixels *[]byte, t
|
|||
}
|
||||
y++
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
// RLE Encoding
|
||||
|
@ -36,18 +39,23 @@ func (mr *MapRenderer) decodeTileGfxData(blocks []d2dt1.Block, pixels *[]byte, t
|
|||
y := int32(0)
|
||||
idx := 0
|
||||
length := block.Length
|
||||
|
||||
for length > 0 {
|
||||
b1 := block.EncodedData[idx]
|
||||
b2 := block.EncodedData[idx+1]
|
||||
idx += 2
|
||||
length -= 2
|
||||
|
||||
if (b1 | b2) == 0 {
|
||||
x = 0
|
||||
y++
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
x += int32(b1)
|
||||
length -= int32(b2)
|
||||
|
||||
for b2 > 0 {
|
||||
offset := (((blockY + y + tileYOffset) * tileWidth) + (blockX + x))
|
||||
(*pixels)[offset] = block.EncodedData[idx]
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
var imageCacheRecords map[uint32]d2interface.Surface
|
||||
|
||||
// Invalidates the global region image cache. Call this when you are changing regions
|
||||
// InvalidateImageCache the global region image cache. Call this when you are changing regions.
|
||||
func InvalidateImageCache() {
|
||||
imageCacheRecords = nil
|
||||
}
|
||||
|
@ -19,8 +19,10 @@ func (mr *MapRenderer) getImageCacheRecord(style, sequence byte, tileType d2enum
|
|||
|
||||
func (mr *MapRenderer) setImageCacheRecord(style, sequence byte, tileType d2enum.TileType, randomIndex byte, image d2interface.Surface) {
|
||||
lookupIndex := uint32(style)<<24 | uint32(sequence)<<16 | uint32(tileType)<<8 | uint32(randomIndex)
|
||||
|
||||
if imageCacheRecords == nil {
|
||||
imageCacheRecords = make(map[uint32]d2interface.Surface)
|
||||
}
|
||||
|
||||
imageCacheRecords[lookupIndex] = image
|
||||
}
|
||||
|
|
|
@ -14,19 +14,19 @@ import (
|
|||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine"
|
||||
)
|
||||
|
||||
// The map renderer, used to render the map
|
||||
// MapRenderer manages the game viewport and camera. It requests tile and entity data from MapEngine and renders it.
|
||||
type MapRenderer struct {
|
||||
renderer d2interface.Renderer // The renderer to use for drawing operations
|
||||
renderer d2interface.Renderer // Used for drawing operations
|
||||
mapEngine *d2mapengine.MapEngine // The map engine that is being rendered
|
||||
palette d2interface.Palette // The palette used for this map
|
||||
viewport *Viewport // The viewport for the map renderer (used for rendering offsets)
|
||||
camera Camera // The camera for this map renderer (used to determine where on the map we are rendering)
|
||||
palette d2interface.Palette // The palette used for this map
|
||||
viewport *Viewport // Used for rendering offsets
|
||||
camera Camera // Used to determine where on the map we are rendering
|
||||
debugVisLevel int // Debug visibility index (0=none, 1=tiles, 2=sub-tiles)
|
||||
lastFrameTime float64 // The last time the map was rendered
|
||||
currentFrame int // The current render frame (for animations)
|
||||
currentFrame int // Current render frame (for animations)
|
||||
}
|
||||
|
||||
// Creates an instance of the map renderer
|
||||
// CreateMapRenderer creates a new MapRenderer, sets the required fields and returns a pointer to it.
|
||||
func CreateMapRenderer(renderer d2interface.Renderer, mapEngine *d2mapengine.MapEngine, term d2interface.Terminal) *MapRenderer {
|
||||
result := &MapRenderer{
|
||||
renderer: renderer,
|
||||
|
@ -47,54 +47,77 @@ func CreateMapRenderer(renderer d2interface.Renderer, mapEngine *d2mapengine.Map
|
|||
return result
|
||||
}
|
||||
|
||||
// RegenerateTileCache calls MapRenderer.generateTileCache().
|
||||
func (mr *MapRenderer) RegenerateTileCache() {
|
||||
mr.generateTileCache()
|
||||
}
|
||||
|
||||
// SetMapEngine sets the MapEngine this renderer is rendering.
|
||||
func (mr *MapRenderer) SetMapEngine(mapEngine *d2mapengine.MapEngine) {
|
||||
mr.mapEngine = mapEngine
|
||||
mr.generateTileCache()
|
||||
}
|
||||
|
||||
// Render determines the width and height of map tiles that should be rendered. The following four render passes are
|
||||
// made in succession:
|
||||
//
|
||||
// Pass 1: Lower wall tiles, tile shadows and floor tiles.
|
||||
//
|
||||
// Pass 2: Entities below walls.
|
||||
//
|
||||
// Pass 3: Upper wall tiles and entities above walls.
|
||||
//
|
||||
// Pass 4: Roof tiles.
|
||||
func (mr *MapRenderer) Render(target d2interface.Surface) {
|
||||
mapSize := mr.mapEngine.Size()
|
||||
|
||||
stxf, styf := mr.viewport.ScreenToWorld(400, -200)
|
||||
etxf, etyf := mr.viewport.ScreenToWorld(400, 1050)
|
||||
|
||||
startX := int(math.Max(0, math.Floor(stxf)))
|
||||
startY := int(math.Max(0, math.Floor(styf)))
|
||||
|
||||
endX := int(math.Min(float64(mapSize.Width), math.Ceil(etxf)))
|
||||
endY := int(math.Min(float64(mapSize.Height), math.Ceil(etyf)))
|
||||
|
||||
mr.renderPass1(target, startX, startY, endX, endY)
|
||||
mr.renderPass2(target, startX, startY, endX, endY)
|
||||
|
||||
if mr.debugVisLevel > 0 {
|
||||
mr.renderDebug(mr.debugVisLevel, target, startX, startY, endX, endY)
|
||||
}
|
||||
|
||||
mr.renderPass3(target, startX, startY, endX, endY)
|
||||
mr.renderPass4(target, startX, startY, endX, endY)
|
||||
}
|
||||
|
||||
// MoveCameraTo sets the position of the camera to the given x and y coordinates.
|
||||
func (mr *MapRenderer) MoveCameraTo(x, y float64) {
|
||||
mr.camera.MoveTo(x, y)
|
||||
}
|
||||
|
||||
// MoveCameraBy adds the given vector to the current position of the camera.
|
||||
func (mr *MapRenderer) MoveCameraBy(x, y float64) {
|
||||
mr.camera.MoveBy(x, y)
|
||||
}
|
||||
|
||||
// ScreenToWorld returns the world position for the given screen (pixel) position.
|
||||
func (mr *MapRenderer) ScreenToWorld(x, y int) (float64, float64) {
|
||||
return mr.viewport.ScreenToWorld(x, y)
|
||||
}
|
||||
|
||||
// ScreenToOrtho returns the orthogonal position, without accounting for the isometric angle, for the given screen
|
||||
// (pixel) position.
|
||||
func (mr *MapRenderer) ScreenToOrtho(x, y int) (float64, float64) {
|
||||
return mr.viewport.ScreenToOrtho(x, y)
|
||||
}
|
||||
|
||||
// WorldToOrtho returns the orthogonal position for the given isometric world position.
|
||||
func (mr *MapRenderer) WorldToOrtho(x, y float64) (float64, float64) {
|
||||
return mr.viewport.WorldToOrtho(x, y)
|
||||
}
|
||||
|
||||
// Lower wall tiles, tile shadews, floor tiles
|
||||
// Lower wall tiles, tile shadows and floor tiles.
|
||||
func (mr *MapRenderer) renderPass1(target d2interface.Surface, startX, startY, endX, endY int) {
|
||||
for tileY := startY; tileY < endY; tileY++ {
|
||||
for tileX := startX; tileX < endX; tileX++ {
|
||||
|
@ -106,7 +129,7 @@ func (mr *MapRenderer) renderPass1(target d2interface.Surface, startX, startY, e
|
|||
}
|
||||
}
|
||||
|
||||
// Objects below walls
|
||||
// Entities below walls.
|
||||
func (mr *MapRenderer) renderPass2(target d2interface.Surface, startX, startY, endX, endY int) {
|
||||
for tileY := startY; tileY < endY; tileY++ {
|
||||
for tileX := startX; tileX < endX; tileX++ {
|
||||
|
@ -115,22 +138,26 @@ func (mr *MapRenderer) renderPass2(target d2interface.Surface, startX, startY, e
|
|||
// TODO: Do not loop over every entity every frame
|
||||
for _, mapEntity := range *mr.mapEngine.Entities() {
|
||||
entityX, entityY := mapEntity.GetPosition()
|
||||
|
||||
if mapEntity.GetLayer() != 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
if (int(entityX) != tileX) || (int(entityY) != tileY) {
|
||||
continue
|
||||
}
|
||||
|
||||
target.PushTranslation(mr.viewport.GetTranslationScreen())
|
||||
mapEntity.Render(target)
|
||||
target.Pop()
|
||||
}
|
||||
|
||||
mr.viewport.PopTranslation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Upper wall tiles, objects that are on top of walls
|
||||
// Upper wall tiles and entities above walls.
|
||||
func (mr *MapRenderer) renderPass3(target d2interface.Surface, startX, startY, endX, endY int) {
|
||||
for tileY := startY; tileY < endY; tileY++ {
|
||||
for tileX := startX; tileX < endX; tileX++ {
|
||||
|
@ -141,22 +168,26 @@ func (mr *MapRenderer) renderPass3(target d2interface.Surface, startX, startY, e
|
|||
// TODO: Do not loop over every entity every frame
|
||||
for _, mapEntity := range *mr.mapEngine.Entities() {
|
||||
entityX, entityY := mapEntity.GetPosition()
|
||||
|
||||
if mapEntity.GetLayer() == 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
if (int(entityX) != tileX) || (int(entityY) != tileY) {
|
||||
continue
|
||||
}
|
||||
|
||||
target.PushTranslation(mr.viewport.GetTranslationScreen())
|
||||
mapEntity.Render(target)
|
||||
target.Pop()
|
||||
}
|
||||
|
||||
mr.viewport.PopTranslation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Roof tiles
|
||||
// Roof tiles.
|
||||
func (mr *MapRenderer) renderPass4(target d2interface.Surface, startX, startY, endX, endY int) {
|
||||
for tileY := startY; tileY < endY; tileY++ {
|
||||
for tileX := startX; tileX < endX; tileX++ {
|
||||
|
@ -164,10 +195,8 @@ func (mr *MapRenderer) renderPass4(target d2interface.Surface, startX, startY, e
|
|||
mr.viewport.PushTranslationWorld(float64(tileX), float64(tileY))
|
||||
mr.renderTilePass3(tile, target)
|
||||
mr.viewport.PopTranslation()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (mr *MapRenderer) renderTilePass1(tile *d2ds1.TileRecord, target d2interface.Surface) {
|
||||
|
@ -213,6 +242,7 @@ func (mr *MapRenderer) renderFloor(tile d2ds1.FloorShadowRecord, target d2interf
|
|||
} else {
|
||||
img = mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, byte(mr.currentFrame))
|
||||
}
|
||||
|
||||
if img == nil {
|
||||
log.Printf("Render called on uncached floor {%v,%v}", tile.Style, tile.Sequence)
|
||||
return
|
||||
|
@ -254,6 +284,7 @@ func (mr *MapRenderer) renderShadow(tile d2ds1.FloorShadowRecord, target d2inter
|
|||
|
||||
target.PushTranslation(mr.viewport.GetTranslationScreen())
|
||||
target.PushColor(color.RGBA{R: 255, G: 255, B: 255, A: 160})
|
||||
|
||||
defer target.PopN(2)
|
||||
|
||||
target.Render(img)
|
||||
|
@ -269,10 +300,12 @@ func (mr *MapRenderer) renderDebug(debugVisLevel int, target d2interface.Surface
|
|||
}
|
||||
}
|
||||
|
||||
// WorldToScreen returns the screen (pixel) position for the given isometric world position as two ints.
|
||||
func (mr *MapRenderer) WorldToScreen(x, y float64) (int, int) {
|
||||
return mr.viewport.WorldToScreen(x, y)
|
||||
}
|
||||
|
||||
// WorldToScreenF returns the screen (pixel) position for the given isometric world position as two float64s.
|
||||
func (mr *MapRenderer) WorldToScreenF(x, y float64) (float64, float64) {
|
||||
return mr.viewport.WorldToScreenF(x, y)
|
||||
}
|
||||
|
@ -311,11 +344,11 @@ func (mr *MapRenderer) renderTileDebug(ax, ay int, debugVisLevel int, target d2i
|
|||
|
||||
tile := mr.mapEngine.TileAt(ax, ay)
|
||||
|
||||
//for i, floor := range tile.Floors {
|
||||
// target.PushTranslation(-20, 10+(i+1)*14)
|
||||
// target.DrawText("f: %v-%v", floor.Style, floor.Sequence)
|
||||
// target.Pop()
|
||||
//}
|
||||
/*for i, floor := range tile.Floors {
|
||||
target.PushTranslation(-20, 10+(i+1)*14)
|
||||
target.DrawText("f: %v-%v", floor.Style, floor.Sequence)
|
||||
target.Pop()
|
||||
}*/
|
||||
|
||||
for i, wall := range tile.Walls {
|
||||
if wall.Type.Special() {
|
||||
|
@ -329,7 +362,9 @@ func (mr *MapRenderer) renderTileDebug(ax, ay int, debugVisLevel int, target d2i
|
|||
for xx := 0; xx < 5; xx++ {
|
||||
isoX := (xx - yy) * 16
|
||||
isoY := (xx + yy) * 8
|
||||
|
||||
var walkableArea = (*mr.mapEngine.WalkMesh())[((yy+(ay*5))*mr.mapEngine.Size().Width*5)+xx+(ax*5)]
|
||||
|
||||
if !walkableArea.Walkable {
|
||||
target.PushTranslation(isoX-3, isoY+4)
|
||||
target.DrawRect(5, 5, tileCollisionColor)
|
||||
|
@ -340,6 +375,7 @@ func (mr *MapRenderer) renderTileDebug(ax, ay int, debugVisLevel int, target d2i
|
|||
}
|
||||
}
|
||||
|
||||
// Advance is called once per frame and maintains the MapRenderer's record previous render timestamp and current frame.
|
||||
func (mr *MapRenderer) Advance(elapsed float64) {
|
||||
frameLength := 0.1
|
||||
|
||||
|
@ -355,6 +391,7 @@ func (mr *MapRenderer) Advance(elapsed float64) {
|
|||
|
||||
func loadPaletteForAct(levelType d2enum.RegionIdType) (d2interface.Palette, error) {
|
||||
var palettePath string
|
||||
|
||||
switch levelType {
|
||||
case d2enum.RegionAct1Town, d2enum.RegionAct1Wilderness, d2enum.RegionAct1Cave, d2enum.RegionAct1Crypt,
|
||||
d2enum.RegionAct1Monestary, d2enum.RegionAct1Courtyard, d2enum.RegionAct1Barracks,
|
||||
|
@ -378,14 +415,17 @@ func loadPaletteForAct(levelType d2enum.RegionIdType) (d2interface.Palette, erro
|
|||
return d2asset.LoadPalette(palettePath)
|
||||
}
|
||||
|
||||
// ViewportToLeft moves the viewport to the left.
|
||||
func (mr *MapRenderer) ViewportToLeft() {
|
||||
mr.viewport.toLeft()
|
||||
}
|
||||
|
||||
// ViewportToRight moves the viewport to the right.
|
||||
func (mr *MapRenderer) ViewportToRight() {
|
||||
mr.viewport.toRight()
|
||||
}
|
||||
|
||||
// ViewportDefault resets the viewport to it's default position.
|
||||
func (mr *MapRenderer) ViewportDefault() {
|
||||
mr.viewport.resetAlign()
|
||||
}
|
||||
|
|
|
@ -17,16 +17,19 @@ func (mr *MapRenderer) generateTileCache() {
|
|||
for idx, tile := range *mr.mapEngine.Tiles() {
|
||||
tileX := idx % mapEngineSize.Width
|
||||
tileY := (idx - tileX) / mapEngineSize.Width
|
||||
|
||||
for i := range tile.Floors {
|
||||
if !tile.Floors[i].Hidden && tile.Floors[i].Prop1 != 0 {
|
||||
mr.generateFloorCache(&tile.Floors[i], tileX, tileY)
|
||||
}
|
||||
}
|
||||
|
||||
for i := range tile.Shadows {
|
||||
if !tile.Shadows[i].Hidden && tile.Shadows[i].Prop1 != 0 {
|
||||
mr.generateShadowCache(&tile.Shadows[i], tileX, tileY)
|
||||
}
|
||||
}
|
||||
|
||||
for i := range tile.Walls {
|
||||
if !tile.Walls[i].Hidden && tile.Walls[i].Prop1 != 0 {
|
||||
mr.generateWallCache(&tile.Walls[i], tileX, tileY)
|
||||
|
@ -37,11 +40,14 @@ func (mr *MapRenderer) generateTileCache() {
|
|||
|
||||
func (mr *MapRenderer) generateFloorCache(tile *d2ds1.FloorShadowRecord, tileX, tileY int) {
|
||||
tileOptions := mr.mapEngine.GetTiles(int32(tile.Style), int32(tile.Sequence), 0)
|
||||
|
||||
var tileData []*d2dt1.Tile
|
||||
|
||||
var tileIndex byte
|
||||
|
||||
if tileOptions == nil {
|
||||
log.Printf("Could not locate tile Style:%d, Seq: %d, Type: %d\n", tile.Style, tile.Sequence, 0)
|
||||
|
||||
tileData = append(tileData, &d2dt1.Tile{})
|
||||
tileData[0].Width = 10
|
||||
tileData[0].Height = 10
|
||||
|
@ -63,14 +69,19 @@ func (mr *MapRenderer) generateFloorCache(tile *d2ds1.FloorShadowRecord, tileX,
|
|||
} else {
|
||||
tileIndex = byte(tileData[i].RarityFrameIndex)
|
||||
}
|
||||
|
||||
cachedImage := mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, tileIndex)
|
||||
|
||||
if cachedImage != nil {
|
||||
return
|
||||
}
|
||||
|
||||
tileYMinimum := int32(0)
|
||||
|
||||
for _, block := range tileData[i].Blocks {
|
||||
tileYMinimum = d2common.MinInt32(tileYMinimum, int32(block.Y))
|
||||
}
|
||||
|
||||
tileYOffset := d2common.AbsInt32(tileYMinimum)
|
||||
tileHeight := d2common.AbsInt32(tileData[i].Height)
|
||||
image, _ := mr.renderer.NewSurface(int(tileData[i].Width), int(tileHeight), d2enum.FilterNearest)
|
||||
|
@ -85,8 +96,11 @@ func (mr *MapRenderer) generateFloorCache(tile *d2ds1.FloorShadowRecord, tileX,
|
|||
|
||||
func (mr *MapRenderer) generateShadowCache(tile *d2ds1.FloorShadowRecord, tileX, tileY int) {
|
||||
tileOptions := mr.mapEngine.GetTiles(int32(tile.Style), int32(tile.Sequence), 13)
|
||||
|
||||
var tileIndex byte
|
||||
|
||||
var tileData *d2dt1.Tile
|
||||
|
||||
if tileOptions == nil {
|
||||
return
|
||||
} else {
|
||||
|
@ -101,10 +115,12 @@ func (mr *MapRenderer) generateShadowCache(tile *d2ds1.FloorShadowRecord, tileX,
|
|||
tile.RandomIndex = tileIndex
|
||||
tileMinY := int32(0)
|
||||
tileMaxY := int32(0)
|
||||
|
||||
for _, block := range tileData.Blocks {
|
||||
tileMinY = d2common.MinInt32(tileMinY, int32(block.Y))
|
||||
tileMaxY = d2common.MaxInt32(tileMaxY, int32(block.Y+32))
|
||||
}
|
||||
|
||||
tileYOffset := -tileMinY
|
||||
tileHeight := int(tileMaxY - tileMinY)
|
||||
tile.YAdjust = int(tileMinY + 80)
|
||||
|
@ -124,8 +140,11 @@ func (mr *MapRenderer) generateShadowCache(tile *d2ds1.FloorShadowRecord, tileX,
|
|||
|
||||
func (mr *MapRenderer) generateWallCache(tile *d2ds1.WallRecord, tileX, tileY int) {
|
||||
tileOptions := mr.mapEngine.GetTiles(int32(tile.Style), int32(tile.Sequence), int32(tile.Type))
|
||||
|
||||
var tileIndex byte
|
||||
|
||||
var tileData *d2dt1.Tile
|
||||
|
||||
if tileOptions == nil {
|
||||
return
|
||||
} else {
|
||||
|
@ -134,6 +153,7 @@ func (mr *MapRenderer) generateWallCache(tile *d2ds1.WallRecord, tileX, tileY in
|
|||
}
|
||||
|
||||
tile.RandomIndex = tileIndex
|
||||
|
||||
var newTileData *d2dt1.Tile = nil
|
||||
|
||||
if tile.Type == 3 {
|
||||
|
@ -158,7 +178,7 @@ func (mr *MapRenderer) generateWallCache(tile *d2ds1.WallRecord, tileX, tileY in
|
|||
|
||||
realHeight := d2common.MaxInt32(d2common.AbsInt32(tileData.Height), tileMaxY-tileMinY)
|
||||
tileYOffset := -tileMinY
|
||||
//tileHeight := int(tileMaxY - tileMinY)
|
||||
/*tileHeight := int(tileMaxY - tileMinY)*/
|
||||
|
||||
if tile.Type == 15 {
|
||||
tile.YAdjust = -int(tileData.RoofHeight)
|
||||
|
@ -207,6 +227,7 @@ func (mr *MapRenderer) getRandomTile(tiles []d2dt1.Tile, x, y int, seed int64) b
|
|||
tileSeed ^= tileSeed << 5
|
||||
|
||||
weightSum := 0
|
||||
|
||||
for _, tile := range tiles {
|
||||
weightSum += int(tile.RarityFrameIndex)
|
||||
}
|
||||
|
@ -218,6 +239,7 @@ func (mr *MapRenderer) getRandomTile(tiles []d2dt1.Tile, x, y int, seed int64) b
|
|||
random := tileSeed % uint64(weightSum)
|
||||
|
||||
sum := 0
|
||||
|
||||
for i, tile := range tiles {
|
||||
sum += int(tile.RarityFrameIndex)
|
||||
if sum >= int(random) {
|
||||
|
|
|
@ -17,6 +17,8 @@ const (
|
|||
right = 2
|
||||
)
|
||||
|
||||
// Viewport is used for converting vectors between screen (pixel), orthogonal (camera) and world (isometric) space.
|
||||
// TODO: Has a coordinate (issue #456)
|
||||
type Viewport struct {
|
||||
defaultScreenRect d2common.Rectangle
|
||||
screenRect d2common.Rectangle
|
||||
|
@ -26,6 +28,7 @@ type Viewport struct {
|
|||
align int
|
||||
}
|
||||
|
||||
// NewViewport creates a new Viewport with the given parameters and returns a pointer to it.
|
||||
func NewViewport(x, y, width, height int) *Viewport {
|
||||
return &Viewport{
|
||||
screenRect: d2common.Rectangle{
|
||||
|
@ -43,98 +46,125 @@ func NewViewport(x, y, width, height int) *Viewport {
|
|||
}
|
||||
}
|
||||
|
||||
// SetCamera sets the current camera to the given value.
|
||||
func (v *Viewport) SetCamera(camera *Camera) {
|
||||
v.camera = camera
|
||||
}
|
||||
|
||||
// WorldToScreen returns the screen space for the given world coordinates as two integers.
|
||||
func (v *Viewport) WorldToScreen(x, y float64) (int, int) {
|
||||
return v.OrthoToScreen(v.WorldToOrtho(x, y))
|
||||
}
|
||||
|
||||
// WorldToScreenF returns the screen space for the given world coordinates as two float64s.
|
||||
func (v *Viewport) WorldToScreenF(x, y float64) (float64, float64) {
|
||||
return v.OrthoToScreenF(v.WorldToOrtho(x, y))
|
||||
}
|
||||
|
||||
// ScreenToWorld returns the world position for the given screen coordinates.
|
||||
func (v *Viewport) ScreenToWorld(x, y int) (float64, float64) {
|
||||
return v.OrthoToWorld(v.ScreenToOrtho(x, y))
|
||||
}
|
||||
|
||||
// OrthoToWorld returns the world position for the given orthogonal coordinates.
|
||||
func (v *Viewport) OrthoToWorld(x, y float64) (float64, float64) {
|
||||
worldX := (x/80 + y/40) / 2
|
||||
worldY := (y/40 - x/80) / 2
|
||||
|
||||
return worldX, worldY
|
||||
}
|
||||
|
||||
// WorldToOrtho returns the orthogonal position for the given world coordinates.
|
||||
func (v *Viewport) WorldToOrtho(x, y float64) (float64, float64) {
|
||||
orthoX := (x - y) * 80
|
||||
orthoY := (x + y) * 40
|
||||
|
||||
return orthoX, orthoY
|
||||
}
|
||||
|
||||
// ScreenToOrtho returns the orthogonal position for the given screen coordinates.
|
||||
func (v *Viewport) ScreenToOrtho(x, y int) (float64, float64) {
|
||||
camX, camY := v.getCameraOffset()
|
||||
screenX := float64(x) + camX - float64(v.screenRect.Left)
|
||||
screenY := float64(y) + camY - float64(v.screenRect.Top)
|
||||
|
||||
return screenX, screenY
|
||||
}
|
||||
|
||||
// OrthoToScreen returns the screen position for the given orthogonal coordinates as two ints.
|
||||
func (v *Viewport) OrthoToScreen(x, y float64) (int, int) {
|
||||
camOrthoX, camOrthoY := v.getCameraOffset()
|
||||
orthoX := int(math.Floor(x - camOrthoX + float64(v.screenRect.Left)))
|
||||
orthoY := int(math.Floor(y - camOrthoY + float64(v.screenRect.Top)))
|
||||
|
||||
return orthoX, orthoY
|
||||
}
|
||||
|
||||
// OrthoToScreenF returns the screen position for the given orthogonal coordinates as two float64s.
|
||||
func (v *Viewport) OrthoToScreenF(x, y float64) (float64, float64) {
|
||||
camOrthoX, camOrthoY := v.getCameraOffset()
|
||||
orthoX := x - camOrthoX + float64(v.screenRect.Left)
|
||||
orthoY := y - camOrthoY + float64(v.screenRect.Top)
|
||||
|
||||
return orthoX, orthoY
|
||||
}
|
||||
|
||||
// IsTileVisible returns false if no part of the tile is within the game screen.
|
||||
func (v *Viewport) IsTileVisible(x, y float64) bool {
|
||||
orthoX1, orthoY1 := v.WorldToOrtho(x-3, y)
|
||||
orthoX2, orthoY2 := v.WorldToOrtho(x+3, y)
|
||||
|
||||
return v.IsOrthoRectVisible(orthoX1, orthoY1, orthoX2, orthoY2)
|
||||
}
|
||||
|
||||
// IsTileRectVisible returns false if none of the tiles rects are within the game screen.
|
||||
func (v *Viewport) IsTileRectVisible(rect d2common.Rectangle) bool {
|
||||
left := float64((rect.Left - rect.Bottom()) * 80)
|
||||
top := float64((rect.Left + rect.Top) * 40)
|
||||
right := float64((rect.Right() - rect.Top) * 80)
|
||||
bottom := float64((rect.Right() + rect.Bottom()) * 40)
|
||||
|
||||
return v.IsOrthoRectVisible(left, top, right, bottom)
|
||||
}
|
||||
|
||||
// IsOrthoRectVisible returns false if the given orthogonal position is outside the game screen.
|
||||
func (v *Viewport) IsOrthoRectVisible(x1, y1, x2, y2 float64) bool {
|
||||
screenX1, screenY1 := v.OrthoToScreen(x1, y1)
|
||||
screenX2, screenY2 := v.OrthoToScreen(x2, y2)
|
||||
|
||||
return !(screenX1 >= v.defaultScreenRect.Width || screenX2 < 0 || screenY1 >= v.defaultScreenRect.Height || screenY2 < 0)
|
||||
}
|
||||
|
||||
// GetTranslationOrtho returns the viewport's current orthogonal space translation.
|
||||
func (v *Viewport) GetTranslationOrtho() (float64, float64) {
|
||||
return v.transCurrent.x, v.transCurrent.y
|
||||
}
|
||||
|
||||
// GetTranslationScreen returns the viewport's current screen space translation.
|
||||
func (v *Viewport) GetTranslationScreen() (int, int) {
|
||||
return v.OrthoToScreen(v.transCurrent.x, v.transCurrent.y)
|
||||
}
|
||||
|
||||
// PushTranslationOrtho adds a new orthogonal translation to the stack.
|
||||
func (v *Viewport) PushTranslationOrtho(x, y float64) *Viewport {
|
||||
v.transStack = append(v.transStack, v.transCurrent)
|
||||
v.transCurrent.x += x
|
||||
v.transCurrent.y += y
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// PushTranslationWorld adds a new world translation to the stack, converting it to orthogonal space.
|
||||
func (v *Viewport) PushTranslationWorld(x, y float64) {
|
||||
v.PushTranslationOrtho(v.WorldToOrtho(x, y))
|
||||
}
|
||||
|
||||
// PushTranslationScreen adds a new screen translation to the stack, converting it to orthogonal space.
|
||||
func (v *Viewport) PushTranslationScreen(x, y int) {
|
||||
v.PushTranslationOrtho(v.ScreenToOrtho(x, y))
|
||||
}
|
||||
|
||||
// PopTranslation pops a translation from the stack.
|
||||
func (v *Viewport) PopTranslation() {
|
||||
count := len(v.transStack)
|
||||
if count == 0 {
|
||||
|
@ -161,6 +191,7 @@ func (v *Viewport) toLeft() {
|
|||
if v.align == left {
|
||||
return
|
||||
}
|
||||
|
||||
v.screenRect.Width = v.defaultScreenRect.Width / 2
|
||||
v.screenRect.Left = v.defaultScreenRect.Left + v.defaultScreenRect.Width/2
|
||||
v.align = left
|
||||
|
@ -170,6 +201,7 @@ func (v *Viewport) toRight() {
|
|||
if v.align == right {
|
||||
return
|
||||
}
|
||||
|
||||
v.screenRect.Width = v.defaultScreenRect.Width / 2
|
||||
v.align = right
|
||||
}
|
||||
|
@ -178,6 +210,7 @@ func (v *Viewport) resetAlign() {
|
|||
if v.align == center {
|
||||
return
|
||||
}
|
||||
|
||||
v.screenRect.Width = v.defaultScreenRect.Width
|
||||
v.screenRect.Left = v.defaultScreenRect.Left
|
||||
v.align = center
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
||||
)
|
||||
|
||||
// Represents a pre-fabricated map stamp that can be placed on a map
|
||||
// Stamp represents a pre-fabricated map stamp that can be placed on a map.
|
||||
type Stamp struct {
|
||||
regionPath string // The file path of the region
|
||||
levelType d2datadict.LevelTypeRecord // The level type id for this stamp
|
||||
|
@ -26,12 +26,13 @@ type Stamp struct {
|
|||
ds1 *d2ds1.DS1 // The backing DS1 file for this stamp
|
||||
}
|
||||
|
||||
// Loads a stamp based on the supplied parameters
|
||||
// LoadStamp loads the Stamp data from file.
|
||||
func LoadStamp(levelType d2enum.RegionIdType, levelPreset int, fileIndex int) *Stamp {
|
||||
stamp := &Stamp{
|
||||
levelType: d2datadict.LevelTypes[levelType],
|
||||
levelPreset: d2datadict.LevelPresets[levelPreset],
|
||||
}
|
||||
|
||||
for _, levelTypeDt1 := range stamp.levelType.Files {
|
||||
if len(levelTypeDt1) != 0 && levelTypeDt1 != "" && levelTypeDt1 != "0" {
|
||||
fileData, err := d2asset.LoadFile("/data/global/tiles/" + levelTypeDt1)
|
||||
|
@ -46,6 +47,7 @@ func LoadStamp(levelType d2enum.RegionIdType, levelPreset int, fileIndex int) *S
|
|||
}
|
||||
|
||||
var levelFilesToPick []string
|
||||
|
||||
for _, fileRecord := range stamp.levelPreset.Files {
|
||||
if len(fileRecord) != 0 && fileRecord != "" && fileRecord != "0" {
|
||||
levelFilesToPick = append(levelFilesToPick, fileRecord)
|
||||
|
@ -63,9 +65,11 @@ func LoadStamp(levelType d2enum.RegionIdType, levelPreset int, fileIndex int) *S
|
|||
|
||||
stamp.regionPath = levelFilesToPick[levelIndex]
|
||||
fileData, err := d2asset.LoadFile("/data/global/tiles/" + stamp.regionPath)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
stamp.ds1, _ = d2ds1.LoadDS1(fileData)
|
||||
|
||||
// Update the region info for the tiles
|
||||
|
@ -78,41 +82,43 @@ func LoadStamp(levelType d2enum.RegionIdType, levelPreset int, fileIndex int) *S
|
|||
return stamp
|
||||
}
|
||||
|
||||
// Returns the size of the stamp, in tiles
|
||||
// Size returns the size of the stamp in tiles.
|
||||
func (mr *Stamp) Size() d2common.Size {
|
||||
return d2common.Size{int(mr.ds1.Width), int(mr.ds1.Height)}
|
||||
}
|
||||
|
||||
// Gets the level preset id
|
||||
// LevelPreset returns the level preset ID.
|
||||
func (mr *Stamp) LevelPreset() d2datadict.LevelPresetRecord {
|
||||
return mr.levelPreset
|
||||
}
|
||||
|
||||
// Returns the level type id
|
||||
// LevelType returns the level type ID.
|
||||
func (mr *Stamp) LevelType() d2datadict.LevelTypeRecord {
|
||||
return mr.levelType
|
||||
}
|
||||
|
||||
// Gets the file path of the region
|
||||
// RegionPath returns the file path of the region.
|
||||
func (mr *Stamp) RegionPath() string {
|
||||
return mr.regionPath
|
||||
}
|
||||
|
||||
// Returns the specified tile
|
||||
// Tile returns the tile at the given x and y tile coordinates.
|
||||
func (mr *Stamp) Tile(x, y int) *d2ds1.TileRecord {
|
||||
return &mr.ds1.Tiles[y][x]
|
||||
}
|
||||
|
||||
// Returns tile data based on the supplied paramters
|
||||
// TileData returns the tile data for the tile with given style, sequence and type.
|
||||
func (mr *Stamp) TileData(style int32, sequence int32, tileType d2enum.TileType) *d2dt1.Tile {
|
||||
for _, tile := range mr.tiles {
|
||||
if tile.Style == style && tile.Sequence == sequence && tile.Type == int32(tileType) {
|
||||
return &tile
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Entities spawns all entities and objects in this tile on the map.
|
||||
func (mr *Stamp) Entities(tileOffsetX, tileOffsetY int) []d2interface.MapEntity {
|
||||
entities := make([]d2interface.MapEntity, 0)
|
||||
|
||||
|
|
|
@ -92,6 +92,7 @@ func (ob *Object) setMode(animationMode string, direction int, randomFrame bool)
|
|||
return err
|
||||
}
|
||||
|
||||
// Highlight sets the entity highlighted flag to true.
|
||||
func (ob *Object) Highlight() {
|
||||
ob.highlight = true
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue