2020-06-07 18:08:34 +01:00
|
|
|
package game
|
|
|
|
|
2020-06-07 18:33:44 +01:00
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
2020-06-07 22:36:11 +01:00
|
|
|
"math/rand"
|
2020-06-07 18:33:44 +01:00
|
|
|
)
|
2020-06-07 18:08:34 +01:00
|
|
|
|
|
|
|
// Chunk represents a fixed square grid of tiles
|
|
|
|
type Chunk struct {
|
|
|
|
// Tiles represents the tiles within the chunk
|
2020-06-07 18:38:46 +01:00
|
|
|
Tiles []Tile `json:"tiles"`
|
2020-06-07 18:08:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Atlas represents a grid of Chunks
|
|
|
|
type Atlas struct {
|
|
|
|
// Chunks represents all chunks in the world
|
|
|
|
// This is intentionally not a 2D array so it can be expanded in all directions
|
|
|
|
Chunks []Chunk `json:"chunks"`
|
|
|
|
|
|
|
|
// size is the current width/height of the given atlas
|
|
|
|
Size int `json:"size"`
|
|
|
|
|
|
|
|
// ChunkSize is the dimensions of each chunk
|
|
|
|
ChunkSize int `json:"chunksize"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewAtlas creates a new empty atlas
|
2020-06-07 18:33:44 +01:00
|
|
|
func NewAtlas(size int, chunkSize int) Atlas {
|
|
|
|
if size%2 != 0 {
|
|
|
|
log.Fatal("atlas size must always be even")
|
|
|
|
}
|
|
|
|
|
2020-06-07 18:08:34 +01:00
|
|
|
a := Atlas{
|
|
|
|
Size: size,
|
|
|
|
Chunks: make([]Chunk, size*size),
|
|
|
|
ChunkSize: chunkSize,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialise all the chunks
|
|
|
|
for i := range a.Chunks {
|
|
|
|
a.Chunks[i] = Chunk{
|
2020-06-07 18:38:46 +01:00
|
|
|
Tiles: make([]Tile, chunkSize*chunkSize),
|
2020-06-07 18:08:34 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
|
2020-06-08 23:02:09 +01:00
|
|
|
// SpawnRocks peppers the world with rocks
|
|
|
|
func (a *Atlas) SpawnRocks() error {
|
2020-06-07 18:57:44 +01:00
|
|
|
extent := a.ChunkSize * (a.Size / 2)
|
2020-06-07 22:36:11 +01:00
|
|
|
|
|
|
|
// Pepper the current world with rocks
|
|
|
|
for i := -extent; i < extent; i++ {
|
|
|
|
for j := -extent; j < extent; j++ {
|
2020-06-07 23:05:36 +01:00
|
|
|
if rand.Intn(16) == 0 {
|
2020-06-07 22:36:11 +01:00
|
|
|
if err := a.SetTile(Vector{i, j}, TileRock); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-08 23:02:09 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SpawnWalls spawns the around the world
|
|
|
|
func (a *Atlas) SpawnWalls() error {
|
|
|
|
extent := a.ChunkSize * (a.Size / 2)
|
|
|
|
|
2020-06-07 18:57:44 +01:00
|
|
|
// Surround the atlas in walls
|
|
|
|
for i := -extent; i < extent; i++ {
|
|
|
|
|
2020-06-08 23:02:09 +01:00
|
|
|
if err := a.SetTile(Vector{i, extent - 1}, TileWall); err != nil { // N
|
2020-06-07 18:57:44 +01:00
|
|
|
return err
|
2020-06-08 23:02:09 +01:00
|
|
|
} else if a.SetTile(Vector{extent - 1, i}, TileWall); err != nil { // E
|
2020-06-07 18:57:44 +01:00
|
|
|
return err
|
2020-06-08 23:02:09 +01:00
|
|
|
} else if a.SetTile(Vector{i, -extent}, TileWall); err != nil { // S
|
2020-06-07 18:57:44 +01:00
|
|
|
return err
|
2020-06-08 23:02:09 +01:00
|
|
|
} else if a.SetTile(Vector{-extent, i}, TileWall); err != nil { // W
|
2020-06-07 18:57:44 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-06-07 18:08:34 +01:00
|
|
|
// SetTile sets an individual tile's kind
|
2020-06-07 18:38:46 +01:00
|
|
|
func (a *Atlas) SetTile(v Vector, tile Tile) error {
|
2020-06-07 18:08:34 +01:00
|
|
|
chunk := a.ToChunk(v)
|
|
|
|
if chunk >= len(a.Chunks) {
|
|
|
|
return fmt.Errorf("location outside of allocated atlas")
|
|
|
|
}
|
|
|
|
|
|
|
|
local := a.ToChunkLocal(v)
|
|
|
|
tileId := local.X + local.Y*a.ChunkSize
|
|
|
|
if tileId >= len(a.Chunks[chunk].Tiles) {
|
|
|
|
return fmt.Errorf("location outside of allocated chunk")
|
|
|
|
}
|
|
|
|
a.Chunks[chunk].Tiles[tileId] = tile
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetTile will return an individual tile
|
2020-06-07 18:38:46 +01:00
|
|
|
func (a *Atlas) GetTile(v Vector) (Tile, error) {
|
2020-06-07 18:08:34 +01:00
|
|
|
chunk := a.ToChunk(v)
|
2020-06-07 22:30:03 +01:00
|
|
|
if chunk >= len(a.Chunks) {
|
2020-06-07 18:08:34 +01:00
|
|
|
return 0, fmt.Errorf("location outside of allocated atlas")
|
|
|
|
}
|
|
|
|
|
|
|
|
local := a.ToChunkLocal(v)
|
|
|
|
tileId := local.X + local.Y*a.ChunkSize
|
2020-06-07 22:30:03 +01:00
|
|
|
if tileId >= len(a.Chunks[chunk].Tiles) {
|
2020-06-07 18:08:34 +01:00
|
|
|
return 0, fmt.Errorf("location outside of allocated chunk")
|
|
|
|
}
|
|
|
|
|
|
|
|
return a.Chunks[chunk].Tiles[tileId], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ToChunkLocal gets a chunk local coordinate for a tile
|
|
|
|
func (a *Atlas) ToChunkLocal(v Vector) Vector {
|
|
|
|
return Vector{Pmod(v.X, a.ChunkSize), Pmod(v.Y, a.ChunkSize)}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetChunkLocal gets a chunk local coordinate for a tile
|
|
|
|
func (a *Atlas) ToWorld(local Vector, chunk int) Vector {
|
|
|
|
return a.ChunkOrigin(chunk).Added(local)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetChunkID gets the chunk ID for a position in the world
|
|
|
|
func (a *Atlas) ToChunk(v Vector) int {
|
|
|
|
local := a.ToChunkLocal(v)
|
|
|
|
// Get the chunk origin itself
|
|
|
|
origin := v.Added(local.Negated())
|
|
|
|
// Divided it by the number of chunks
|
|
|
|
origin = origin.Divided(a.ChunkSize)
|
|
|
|
// Shift it by our size (our origin is in the middle)
|
|
|
|
origin = origin.Added(Vector{a.Size / 2, a.Size / 2})
|
|
|
|
// Get the ID based on the final values
|
|
|
|
return (a.Size * origin.Y) + origin.X
|
|
|
|
}
|
|
|
|
|
|
|
|
// ChunkOrigin gets the chunk origin for a given chunk index
|
|
|
|
func (a *Atlas) ChunkOrigin(chunk int) Vector {
|
|
|
|
v := Vector{
|
|
|
|
X: Pmod(chunk, a.Size) - (a.Size / 2),
|
|
|
|
Y: (chunk / a.Size) - (a.Size / 2),
|
|
|
|
}
|
|
|
|
|
|
|
|
return v.Multiplied(a.ChunkSize)
|
|
|
|
}
|
|
|
|
|
2020-06-08 18:14:24 +01:00
|
|
|
// GetWorldExtent gets the min and max valid coordinates of world
|
|
|
|
func (a *Atlas) GetWorldExtents() (min Vector, max Vector) {
|
|
|
|
min = Vector{
|
|
|
|
-(a.Size / 2) * a.ChunkSize,
|
|
|
|
-(a.Size / 2) * a.ChunkSize,
|
|
|
|
}
|
|
|
|
max = Vector{
|
|
|
|
-min.X - 1,
|
|
|
|
-min.Y - 1,
|
|
|
|
}
|
|
|
|
return
|
2020-06-07 22:30:03 +01:00
|
|
|
}
|
|
|
|
|
2020-06-07 18:33:44 +01:00
|
|
|
// Grow will return a grown copy of the current atlas
|
|
|
|
func (a *Atlas) Grow(size int) error {
|
|
|
|
if size%2 != 0 {
|
|
|
|
return fmt.Errorf("atlas size must always be even")
|
|
|
|
}
|
|
|
|
delta := size - a.Size
|
2020-06-07 18:08:34 +01:00
|
|
|
if delta < 0 {
|
2020-06-07 18:33:44 +01:00
|
|
|
return fmt.Errorf("Cannot shrink an atlas")
|
2020-06-07 18:08:34 +01:00
|
|
|
} else if delta == 0 {
|
2020-06-07 18:33:44 +01:00
|
|
|
return nil
|
2020-06-07 18:08:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new atlas
|
2020-06-07 18:33:44 +01:00
|
|
|
newAtlas := NewAtlas(size, a.ChunkSize)
|
2020-06-07 18:08:34 +01:00
|
|
|
|
|
|
|
// Copy old chunks into new chunks
|
|
|
|
for index, chunk := range a.Chunks {
|
|
|
|
// Calculate the new chunk location and copy over the data
|
|
|
|
newAtlas.Chunks[newAtlas.ToChunk(a.ChunkOrigin(index))] = chunk
|
|
|
|
}
|
|
|
|
|
2020-06-07 18:33:44 +01:00
|
|
|
// Copy the new atlas data into this one
|
|
|
|
*a = newAtlas
|
|
|
|
|
2020-06-07 18:08:34 +01:00
|
|
|
// Return the new atlas
|
2020-06-07 18:33:44 +01:00
|
|
|
return nil
|
2020-06-07 18:08:34 +01:00
|
|
|
}
|