mirror of
https://github.com/mvvasilev/last_light.git
synced 2025-04-19 12:49:52 +03:00
Split logic up a bit
This commit is contained in:
parent
5864ab41ad
commit
b7b269e8b8
19 changed files with 402 additions and 228 deletions
|
@ -31,16 +31,18 @@ func CreateGameContext() *GameContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *GameContext) Run() {
|
func (gc *GameContext) Run() {
|
||||||
|
lastLoop := time.Now()
|
||||||
lastTick := time.Now()
|
lastTick := time.Now()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
deltaTime := 1 + time.Since(lastTick).Microseconds()
|
deltaTime := 1 + time.Since(lastLoop).Microseconds()
|
||||||
lastTick = time.Now()
|
lastLoop = time.Now()
|
||||||
|
|
||||||
for _, e := range gc.renderContext.CollectInputEvents() {
|
for _, e := range gc.renderContext.CollectInputEvents() {
|
||||||
gc.game.Input(e)
|
gc.game.Input(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if time.Since(lastTick).Milliseconds() >= TICK_RATE {
|
||||||
stop := !gc.game.Tick(deltaTime)
|
stop := !gc.game.Tick(deltaTime)
|
||||||
|
|
||||||
if stop {
|
if stop {
|
||||||
|
@ -49,6 +51,9 @@ func (gc *GameContext) Run() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastTick = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
drawables := gc.game.CollectDrawables()
|
drawables := gc.game.CollectDrawables()
|
||||||
gc.renderContext.Draw(deltaTime, drawables)
|
gc.renderContext.Draw(deltaTime, drawables)
|
||||||
}
|
}
|
||||||
|
|
12
game/logic/logic_snippet.go
Normal file
12
game/logic/logic_snippet.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mvvasilev/last_light/game/model"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogicSnippet[T model.Entity] interface {
|
||||||
|
Input(e *tcell.EventKey)
|
||||||
|
Tick(dt int64, entity T)
|
||||||
|
}
|
|
@ -1,7 +0,0 @@
|
||||||
package model
|
|
||||||
|
|
||||||
type Dungeon struct {
|
|
||||||
player *Player
|
|
||||||
|
|
||||||
levels []Map
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
package model
|
|
||||||
|
|
||||||
import "mvvasilev/last_light/util"
|
|
||||||
|
|
||||||
type EmptyDungeonLevel struct {
|
|
||||||
level *BasicMap
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateEmptyDungeonLevel(width, height int) *EmptyDungeonLevel {
|
|
||||||
m := new(EmptyDungeonLevel)
|
|
||||||
|
|
||||||
tiles := make([][]Tile, height)
|
|
||||||
|
|
||||||
for h := range height {
|
|
||||||
tiles[h] = make([]Tile, width)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.level = CreateBasicMap(tiles)
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edl *EmptyDungeonLevel) Size() util.Size {
|
|
||||||
return edl.level.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edl *EmptyDungeonLevel) SetTileAt(x int, y int, t Tile) {
|
|
||||||
edl.level.SetTileAt(x, y, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edl *EmptyDungeonLevel) TileAt(x int, y int) Tile {
|
|
||||||
return edl.level.TileAt(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edl *EmptyDungeonLevel) Tick() {
|
|
||||||
|
|
||||||
}
|
|
|
@ -40,5 +40,7 @@ type Entity interface {
|
||||||
|
|
||||||
type MovableEntity interface {
|
type MovableEntity interface {
|
||||||
Position() util.Position
|
Position() util.Position
|
||||||
Move(dir Direction)
|
MoveTo(newPosition util.Position)
|
||||||
|
|
||||||
|
Entity
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"mvvasilev/last_light/util"
|
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FlatGroundDungeonLevel struct {
|
|
||||||
tiles [][]Tile
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateFlatGroundDungeonLevel(width, height int) *FlatGroundDungeonLevel {
|
|
||||||
level := new(FlatGroundDungeonLevel)
|
|
||||||
|
|
||||||
level.tiles = make([][]Tile, height)
|
|
||||||
|
|
||||||
for h := range height {
|
|
||||||
level.tiles[h] = make([]Tile, width)
|
|
||||||
|
|
||||||
for w := range width {
|
|
||||||
if w == 0 || h == 0 || w >= width-1 || h >= height-1 {
|
|
||||||
level.tiles[h][w] = CreateStaticTile(w, h, TileTypeRock())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
level.tiles[h][w] = genRandomGroundTile(w, h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return level
|
|
||||||
}
|
|
||||||
|
|
||||||
func genRandomGroundTile(width, height int) Tile {
|
|
||||||
switch rand.Intn(2) {
|
|
||||||
case 0:
|
|
||||||
return CreateStaticTile(width, height, TileTypeGround())
|
|
||||||
case 1:
|
|
||||||
return CreateStaticTile(width, height, TileTypeGrass())
|
|
||||||
default:
|
|
||||||
return CreateStaticTile(width, height, TileTypeGround())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edl *FlatGroundDungeonLevel) Size() util.Size {
|
|
||||||
return util.SizeOf(len(edl.tiles[0]), len(edl.tiles))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edl *FlatGroundDungeonLevel) SetTileAt(x int, y int, t Tile) {
|
|
||||||
if len(edl.tiles) <= y || len(edl.tiles[0]) <= x {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
edl.tiles[y][x] = t
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edl *FlatGroundDungeonLevel) TileAt(x int, y int) Tile {
|
|
||||||
if y < 0 || y >= len(edl.tiles) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if x < 0 || x >= len(edl.tiles[y]) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return edl.tiles[y][x]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edl *FlatGroundDungeonLevel) Input(e *tcell.EventKey) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edl *FlatGroundDungeonLevel) Tick() {
|
|
||||||
}
|
|
|
@ -32,8 +32,8 @@ func (p *Player) Position() util.Position {
|
||||||
return p.position
|
return p.position
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) Move(dir Direction) {
|
func (p *Player) MoveTo(newPos util.Position) {
|
||||||
p.position = p.Position().WithOffset(MovementDirectionOffset(dir))
|
p.position = newPos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) Presentation() (rune, tcell.Style) {
|
func (p *Player) Presentation() (rune, tcell.Style) {
|
||||||
|
|
22
game/state/look_state.go
Normal file
22
game/state/look_state.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mvvasilev/last_light/render"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LookState struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LookState) OnInput(e *tcell.EventKey) {
|
||||||
|
panic("not implemented") // TODO: Implement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LookState) OnTick(dt int64) GameState {
|
||||||
|
panic("not implemented") // TODO: Implement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LookState) CollectDrawables() []render.Drawable {
|
||||||
|
panic("not implemented") // TODO: Implement
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package state
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"mvvasilev/last_light/game/model"
|
"mvvasilev/last_light/game/model"
|
||||||
|
"mvvasilev/last_light/game/world"
|
||||||
"mvvasilev/last_light/render"
|
"mvvasilev/last_light/render"
|
||||||
"mvvasilev/last_light/util"
|
"mvvasilev/last_light/util"
|
||||||
|
|
||||||
|
@ -12,7 +13,8 @@ import (
|
||||||
|
|
||||||
type PlayingState struct {
|
type PlayingState struct {
|
||||||
player *model.Player
|
player *model.Player
|
||||||
level *model.MultilevelMap
|
entityMap *world.EntityMap
|
||||||
|
level *world.MultilevelMap
|
||||||
|
|
||||||
viewport *render.Viewport
|
viewport *render.Viewport
|
||||||
|
|
||||||
|
@ -27,11 +29,11 @@ func BeginPlayingState() *PlayingState {
|
||||||
|
|
||||||
mapSize := util.SizeOf(128, 128)
|
mapSize := util.SizeOf(128, 128)
|
||||||
|
|
||||||
dungeonLevel := model.CreateBSPDungeonLevel(mapSize.Width(), mapSize.Height(), 4)
|
dungeonLevel := world.CreateBSPDungeonMap(mapSize.Width(), mapSize.Height(), 4)
|
||||||
|
|
||||||
itemTiles := spawnItems(dungeonLevel)
|
itemTiles := spawnItems(dungeonLevel)
|
||||||
|
|
||||||
itemLevel := model.CreateEmptyDungeonLevel(mapSize.Width(), mapSize.Height())
|
itemLevel := world.CreateEmptyDungeonLevel(mapSize.Width(), mapSize.Height())
|
||||||
|
|
||||||
for _, it := range itemTiles {
|
for _, it := range itemTiles {
|
||||||
itemLevel.SetTileAt(it.Position().X(), it.Position().Y(), it)
|
itemLevel.SetTileAt(it.Position().X(), it.Position().Y(), it)
|
||||||
|
@ -39,13 +41,15 @@ func BeginPlayingState() *PlayingState {
|
||||||
|
|
||||||
s.player = model.CreatePlayer(dungeonLevel.PlayerSpawnPoint().XY())
|
s.player = model.CreatePlayer(dungeonLevel.PlayerSpawnPoint().XY())
|
||||||
|
|
||||||
s.level = model.CreateMultilevelMap(
|
s.entityMap = world.CreateEntityMap(mapSize.WH())
|
||||||
|
|
||||||
|
s.level = world.CreateMultilevelMap(
|
||||||
dungeonLevel,
|
dungeonLevel,
|
||||||
itemLevel,
|
itemLevel,
|
||||||
model.CreateEmptyDungeonLevel(mapSize.WH()),
|
s.entityMap,
|
||||||
)
|
)
|
||||||
|
|
||||||
s.level.SetTileAtHeight(dungeonLevel.PlayerSpawnPoint().X(), dungeonLevel.PlayerSpawnPoint().Y(), 2, s.player)
|
s.entityMap.AddEntity(s.player, '@', tcell.StyleDefault)
|
||||||
|
|
||||||
s.viewport = render.CreateViewport(
|
s.viewport = render.CreateViewport(
|
||||||
util.PositionAt(0, 0),
|
util.PositionAt(0, 0),
|
||||||
|
@ -57,7 +61,7 @@ func BeginPlayingState() *PlayingState {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func spawnItems(level *model.BSPDungeonLevel) []model.Tile {
|
func spawnItems(level *world.BSPDungeonMap) []world.Tile {
|
||||||
rooms := level.Rooms()
|
rooms := level.Rooms()
|
||||||
|
|
||||||
genTable := make(map[float32]*model.ItemType)
|
genTable := make(map[float32]*model.ItemType)
|
||||||
|
@ -67,7 +71,7 @@ func spawnItems(level *model.BSPDungeonLevel) []model.Tile {
|
||||||
genTable[0.051] = model.ItemTypeLongsword()
|
genTable[0.051] = model.ItemTypeLongsword()
|
||||||
genTable[0.052] = model.ItemTypeKey()
|
genTable[0.052] = model.ItemTypeKey()
|
||||||
|
|
||||||
itemTiles := make([]model.Tile, 0, 10)
|
itemTiles := make([]world.Tile, 0, 10)
|
||||||
|
|
||||||
for _, r := range rooms {
|
for _, r := range rooms {
|
||||||
maxItems := int(0.10 * float64(r.Size().Area()))
|
maxItems := int(0.10 * float64(r.Size().Area()))
|
||||||
|
@ -90,7 +94,7 @@ func spawnItems(level *model.BSPDungeonLevel) []model.Tile {
|
||||||
util.RandInt(r.Position().Y()+1, r.Position().Y()+r.Size().Height()-1),
|
util.RandInt(r.Position().Y()+1, r.Position().Y()+r.Size().Height()-1),
|
||||||
)
|
)
|
||||||
|
|
||||||
itemTiles = append(itemTiles, model.CreateItemTile(
|
itemTiles = append(itemTiles, world.CreateItemTile(
|
||||||
pos, itemType, 1,
|
pos, itemType, 1,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -117,14 +121,12 @@ func (ps *PlayingState) MovePlayer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
newPlayerPos := ps.player.Position().WithOffset(model.MovementDirectionOffset(ps.movePlayerDirection))
|
newPlayerPos := ps.player.Position().WithOffset(model.MovementDirectionOffset(ps.movePlayerDirection))
|
||||||
|
|
||||||
tileAtMovePos := ps.level.TileAt(newPlayerPos.XY())
|
tileAtMovePos := ps.level.TileAt(newPlayerPos.XY())
|
||||||
|
|
||||||
if tileAtMovePos.Passable() {
|
if tileAtMovePos.Passable() {
|
||||||
ps.level.SetTileAtHeight(ps.player.Position().X(), ps.player.Position().Y(), 2, nil)
|
dx, dy := model.MovementDirectionOffset(ps.movePlayerDirection)
|
||||||
ps.player.Move(ps.movePlayerDirection)
|
ps.entityMap.MoveEntity(ps.player.UniqueId(), dx, dy)
|
||||||
ps.viewport.SetCenter(ps.player.Position())
|
ps.viewport.SetCenter(ps.player.Position())
|
||||||
ps.level.SetTileAtHeight(ps.player.Position().X(), ps.player.Position().Y(), 2, ps.player)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ps.movePlayerDirection = model.DirectionNone
|
ps.movePlayerDirection = model.DirectionNone
|
||||||
|
@ -134,7 +136,7 @@ func (ps *PlayingState) PickUpItemUnderPlayer() {
|
||||||
pos := ps.player.Position()
|
pos := ps.player.Position()
|
||||||
tile := ps.level.TileAtHeight(pos.X(), pos.Y(), 1)
|
tile := ps.level.TileAtHeight(pos.X(), pos.Y(), 1)
|
||||||
|
|
||||||
itemTile, ok := tile.(*model.ItemTile)
|
itemTile, ok := tile.(*world.ItemTile)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
|
@ -178,17 +180,6 @@ func (ps *PlayingState) OnInput(e *tcell.EventKey) {
|
||||||
ps.movePlayerDirection = model.DirectionLeft
|
ps.movePlayerDirection = model.DirectionLeft
|
||||||
case tcell.KeyRight:
|
case tcell.KeyRight:
|
||||||
ps.movePlayerDirection = model.DirectionRight
|
ps.movePlayerDirection = model.DirectionRight
|
||||||
case tcell.KeyRune:
|
|
||||||
switch e.Rune() {
|
|
||||||
case 'w':
|
|
||||||
ps.movePlayerDirection = model.DirectionUp
|
|
||||||
case 'a':
|
|
||||||
ps.movePlayerDirection = model.DirectionLeft
|
|
||||||
case 's':
|
|
||||||
ps.movePlayerDirection = model.DirectionDown
|
|
||||||
case 'd':
|
|
||||||
ps.movePlayerDirection = model.DirectionRight
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
35
game/world/bsp_map.go
Normal file
35
game/world/bsp_map.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package world
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mvvasilev/last_light/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BSPDungeonMap struct {
|
||||||
|
level *BasicMap
|
||||||
|
|
||||||
|
playerSpawnPoint util.Position
|
||||||
|
rooms []util.Room
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bsp *BSPDungeonMap) PlayerSpawnPoint() util.Position {
|
||||||
|
return bsp.playerSpawnPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bsp *BSPDungeonMap) Size() util.Size {
|
||||||
|
return bsp.level.Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bsp *BSPDungeonMap) SetTileAt(x int, y int, t Tile) {
|
||||||
|
bsp.level.SetTileAt(x, y, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bsp *BSPDungeonMap) TileAt(x int, y int) Tile {
|
||||||
|
return bsp.level.TileAt(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bsp *BSPDungeonMap) Tick(dt int64) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bsp *BSPDungeonMap) Rooms() []util.Room {
|
||||||
|
return bsp.rooms
|
||||||
|
}
|
15
game/world/dungeon.go
Normal file
15
game/world/dungeon.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package world
|
||||||
|
|
||||||
|
import "mvvasilev/last_light/game/model"
|
||||||
|
|
||||||
|
type dungeonLevel struct {
|
||||||
|
groundLevel Map
|
||||||
|
entityLevel *EntityMap
|
||||||
|
itemLevel *Map
|
||||||
|
}
|
||||||
|
|
||||||
|
type Dungeon struct {
|
||||||
|
player *model.Player
|
||||||
|
|
||||||
|
levels []*dungeonLevel
|
||||||
|
}
|
23
game/world/empty_map.go
Normal file
23
game/world/empty_map.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package world
|
||||||
|
|
||||||
|
import "mvvasilev/last_light/util"
|
||||||
|
|
||||||
|
type EmptyDungeonMap struct {
|
||||||
|
level *BasicMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (edl *EmptyDungeonMap) Size() util.Size {
|
||||||
|
return edl.level.Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (edl *EmptyDungeonMap) SetTileAt(x int, y int, t Tile) {
|
||||||
|
edl.level.SetTileAt(x, y, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (edl *EmptyDungeonMap) TileAt(x int, y int) Tile {
|
||||||
|
return edl.level.TileAt(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (edl *EmptyDungeonMap) Tick(dt int64) {
|
||||||
|
|
||||||
|
}
|
101
game/world/entity_map.go
Normal file
101
game/world/entity_map.go
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
package world
|
||||||
|
|
||||||
|
import (
|
||||||
|
"maps"
|
||||||
|
"mvvasilev/last_light/game/model"
|
||||||
|
"mvvasilev/last_light/util"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EntityMap struct {
|
||||||
|
entities map[int]EntityTile
|
||||||
|
|
||||||
|
util.Sized
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateEntityMap(width, height int) *EntityMap {
|
||||||
|
return &EntityMap{
|
||||||
|
entities: make(map[int]EntityTile, 0),
|
||||||
|
Sized: util.WithSize(util.SizeOf(width, height)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (em *EntityMap) SetTileAt(x int, y int, t Tile) {
|
||||||
|
// if !em.FitsWithin(x, y) {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// index := em.Size().AsArrayIndex(x, y)
|
||||||
|
|
||||||
|
// TODO? May not be necessary
|
||||||
|
}
|
||||||
|
|
||||||
|
func (em *EntityMap) FindEntityByUuid(uuid uuid.UUID) (key int, entity EntityTile) {
|
||||||
|
for i, e := range em.entities {
|
||||||
|
if e.Entity().UniqueId() == uuid {
|
||||||
|
return i, e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (em *EntityMap) AddEntity(entity model.MovableEntity, presentation rune, style tcell.Style) {
|
||||||
|
if !em.FitsWithin(entity.Position().XY()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
key := em.Size().AsArrayIndex(entity.Position().XY())
|
||||||
|
et := CreateBasicEntityTile(entity, presentation, style)
|
||||||
|
|
||||||
|
em.entities[key] = et
|
||||||
|
}
|
||||||
|
|
||||||
|
func (em *EntityMap) DropEntity(uuid uuid.UUID) {
|
||||||
|
maps.DeleteFunc(em.entities, func(i int, et EntityTile) bool {
|
||||||
|
if et.Entity().UniqueId() == uuid {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (em *EntityMap) MoveEntity(uuid uuid.UUID, dx, dy int) {
|
||||||
|
oldKey, e := em.FindEntityByUuid(uuid)
|
||||||
|
|
||||||
|
if e == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !em.FitsWithin(e.Entity().Position().WithOffset(dx, dy).XY()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(em.entities, oldKey)
|
||||||
|
|
||||||
|
newPos := e.Entity().Position().WithOffset(dx, dy)
|
||||||
|
e.Entity().MoveTo(newPos)
|
||||||
|
|
||||||
|
newKey := em.Size().AsArrayIndex(e.Entity().Position().XY())
|
||||||
|
|
||||||
|
em.entities[newKey] = e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (em *EntityMap) TileAt(x int, y int) Tile {
|
||||||
|
if !em.FitsWithin(x, y) {
|
||||||
|
return CreateStaticTile(x, y, TileTypeVoid())
|
||||||
|
}
|
||||||
|
|
||||||
|
key := em.Size().AsArrayIndex(x, y)
|
||||||
|
|
||||||
|
return em.entities[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (em *EntityMap) Tick(dt int64) {
|
||||||
|
for _, e := range em.entities {
|
||||||
|
e.Entity().Tick(dt)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package model
|
package world
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
@ -16,7 +16,7 @@ type bspNode struct {
|
||||||
origin util.Position
|
origin util.Position
|
||||||
size util.Size
|
size util.Size
|
||||||
|
|
||||||
room Room
|
room util.Room
|
||||||
hasRoom bool
|
hasRoom bool
|
||||||
|
|
||||||
left *bspNode
|
left *bspNode
|
||||||
|
@ -25,27 +25,7 @@ type bspNode struct {
|
||||||
splitDir splitDirection
|
splitDir splitDirection
|
||||||
}
|
}
|
||||||
|
|
||||||
type Room struct {
|
func CreateBSPDungeonMap(width, height int, numSplits int) *BSPDungeonMap {
|
||||||
position util.Position
|
|
||||||
size util.Size
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r Room) Size() util.Size {
|
|
||||||
return r.size
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r Room) Position() util.Position {
|
|
||||||
return r.position
|
|
||||||
}
|
|
||||||
|
|
||||||
type BSPDungeonLevel struct {
|
|
||||||
level *BasicMap
|
|
||||||
|
|
||||||
playerSpawnPoint util.Position
|
|
||||||
rooms []Room
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateBSPDungeonLevel(width, height int, numSplits int) *BSPDungeonLevel {
|
|
||||||
root := new(bspNode)
|
root := new(bspNode)
|
||||||
|
|
||||||
root.origin = util.PositionAt(0, 0)
|
root.origin = util.PositionAt(0, 0)
|
||||||
|
@ -59,7 +39,7 @@ func CreateBSPDungeonLevel(width, height int, numSplits int) *BSPDungeonLevel {
|
||||||
tiles[h] = make([]Tile, width)
|
tiles[h] = make([]Tile, width)
|
||||||
}
|
}
|
||||||
|
|
||||||
rooms := make([]Room, 0, 2^numSplits)
|
rooms := make([]util.Room, 0, 2^numSplits)
|
||||||
|
|
||||||
iterateBspLeaves(root, func(leaf *bspNode) {
|
iterateBspLeaves(root, func(leaf *bspNode) {
|
||||||
x := util.RandInt(leaf.origin.X(), leaf.origin.X()+leaf.size.Width()/4)
|
x := util.RandInt(leaf.origin.X(), leaf.origin.X()+leaf.size.Width()/4)
|
||||||
|
@ -75,9 +55,9 @@ func CreateBSPDungeonLevel(width, height int, numSplits int) *BSPDungeonLevel {
|
||||||
h = h - (y + h - height) - 1
|
h = h - (y + h - height) - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
room := Room{
|
room := util.Room{
|
||||||
position: util.PositionAt(x, y),
|
Positioned: util.WithPosition(util.PositionAt(x, y)),
|
||||||
size: util.SizeOf(w, h),
|
Sized: util.WithSize(util.SizeOf(w, h)),
|
||||||
}
|
}
|
||||||
|
|
||||||
rooms = append(rooms, room)
|
rooms = append(rooms, room)
|
||||||
|
@ -95,36 +75,32 @@ func CreateBSPDungeonLevel(width, height int, numSplits int) *BSPDungeonLevel {
|
||||||
zCorridor(
|
zCorridor(
|
||||||
tiles,
|
tiles,
|
||||||
util.PositionAt(
|
util.PositionAt(
|
||||||
roomLeft.position.X()+roomLeft.size.Width()/2,
|
roomLeft.Position().X()+roomLeft.Size().Width()/2,
|
||||||
roomLeft.position.Y()+roomLeft.size.Height()/2,
|
roomLeft.Position().Y()+roomLeft.Size().Height()/2,
|
||||||
),
|
),
|
||||||
util.PositionAt(
|
util.PositionAt(
|
||||||
roomRight.position.X()+roomRight.size.Width()/2,
|
roomRight.Position().X()+roomRight.Size().Width()/2,
|
||||||
roomRight.position.Y()+roomRight.size.Height()/2,
|
roomRight.Position().Y()+roomRight.Size().Height()/2,
|
||||||
),
|
),
|
||||||
parent.splitDir,
|
parent.splitDir,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
bsp := new(BSPDungeonLevel)
|
bsp := new(BSPDungeonMap)
|
||||||
|
|
||||||
spawnRoom := findRoom(root.left)
|
spawnRoom := findRoom(root.left)
|
||||||
|
|
||||||
bsp.rooms = rooms
|
bsp.rooms = rooms
|
||||||
bsp.level = CreateBasicMap(tiles)
|
bsp.level = CreateBasicMap(tiles)
|
||||||
bsp.playerSpawnPoint = util.PositionAt(
|
bsp.playerSpawnPoint = util.PositionAt(
|
||||||
spawnRoom.position.X()+spawnRoom.size.Width()/2,
|
spawnRoom.Position().X()+spawnRoom.Size().Width()/2,
|
||||||
spawnRoom.position.Y()+spawnRoom.size.Height()/2,
|
spawnRoom.Position().Y()+spawnRoom.Size().Height()/2,
|
||||||
)
|
)
|
||||||
|
|
||||||
return bsp
|
return bsp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bsp *BSPDungeonLevel) PlayerSpawnPoint() util.Position {
|
func findRoom(parent *bspNode) util.Room {
|
||||||
return bsp.playerSpawnPoint
|
|
||||||
}
|
|
||||||
|
|
||||||
func findRoom(parent *bspNode) Room {
|
|
||||||
if parent.hasRoom {
|
if parent.hasRoom {
|
||||||
return parent.room
|
return parent.room
|
||||||
}
|
}
|
||||||
|
@ -280,11 +256,11 @@ func placeWallAtIfNotPassable(tiles [][]Tile, x, y int) {
|
||||||
tiles[y][x] = CreateStaticTile(x, y, TileTypeWall())
|
tiles[y][x] = CreateStaticTile(x, y, TileTypeWall())
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeRoom(tiles [][]Tile, room Room) {
|
func makeRoom(tiles [][]Tile, room util.Room) {
|
||||||
width := room.size.Width()
|
width := room.Size().Width()
|
||||||
height := room.size.Height()
|
height := room.Size().Height()
|
||||||
x := room.position.X()
|
x := room.Position().X()
|
||||||
y := room.position.Y()
|
y := room.Position().Y()
|
||||||
|
|
||||||
for w := x; w < x+width+1; w++ {
|
for w := x; w < x+width+1; w++ {
|
||||||
tiles[y][w] = CreateStaticTile(w, y, TileTypeWall())
|
tiles[y][w] = CreateStaticTile(w, y, TileTypeWall())
|
||||||
|
@ -302,22 +278,3 @@ func makeRoom(tiles [][]Tile, room Room) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bsp *BSPDungeonLevel) Size() util.Size {
|
|
||||||
return bsp.level.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bsp *BSPDungeonLevel) SetTileAt(x int, y int, t Tile) {
|
|
||||||
bsp.level.SetTileAt(x, y, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bsp *BSPDungeonLevel) TileAt(x int, y int) Tile {
|
|
||||||
return bsp.level.TileAt(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bsp *BSPDungeonLevel) Tick() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bsp *BSPDungeonLevel) Rooms() []Room {
|
|
||||||
return bsp.rooms
|
|
||||||
}
|
|
15
game/world/generate_empty_map.go
Normal file
15
game/world/generate_empty_map.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package world
|
||||||
|
|
||||||
|
func CreateEmptyDungeonLevel(width, height int) *EmptyDungeonMap {
|
||||||
|
m := new(EmptyDungeonMap)
|
||||||
|
|
||||||
|
tiles := make([][]Tile, height)
|
||||||
|
|
||||||
|
for h := range height {
|
||||||
|
tiles[h] = make([]Tile, width)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.level = CreateBasicMap(tiles)
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package model
|
package world
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/util"
|
"mvvasilev/last_light/util"
|
||||||
|
@ -8,7 +8,17 @@ type Map interface {
|
||||||
Size() util.Size
|
Size() util.Size
|
||||||
SetTileAt(x, y int, t Tile)
|
SetTileAt(x, y int, t Tile)
|
||||||
TileAt(x, y int) Tile
|
TileAt(x, y int) Tile
|
||||||
Tick()
|
Tick(dt int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
type WithPlayerSpawnPoint interface {
|
||||||
|
PlayerSpawnPoint() util.Position
|
||||||
|
Map
|
||||||
|
}
|
||||||
|
|
||||||
|
type WithRooms interface {
|
||||||
|
Rooms() []util.Room
|
||||||
|
Map
|
||||||
}
|
}
|
||||||
|
|
||||||
type BasicMap struct {
|
type BasicMap struct {
|
|
@ -1,4 +1,4 @@
|
||||||
package model
|
package world
|
||||||
|
|
||||||
import "mvvasilev/last_light/util"
|
import "mvvasilev/last_light/util"
|
||||||
|
|
||||||
|
@ -102,8 +102,8 @@ func (mm *MultilevelMap) TileAtHeight(x, y, height int) Tile {
|
||||||
return mm.layers[height].TileAt(x, y)
|
return mm.layers[height].TileAt(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mm *MultilevelMap) Tick() {
|
func (mm *MultilevelMap) Tick(dt int64) {
|
||||||
for _, l := range mm.layers {
|
for _, l := range mm.layers {
|
||||||
l.Tick()
|
l.Tick(dt)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package model
|
package world
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"mvvasilev/last_light/game/model"
|
||||||
"mvvasilev/last_light/util"
|
"mvvasilev/last_light/util"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
|
@ -14,6 +15,8 @@ const (
|
||||||
MaterialWall
|
MaterialWall
|
||||||
MaterialGrass
|
MaterialGrass
|
||||||
MaterialVoid
|
MaterialVoid
|
||||||
|
MaterialClosedDoor
|
||||||
|
MaterialOpenDoor
|
||||||
)
|
)
|
||||||
|
|
||||||
type TileType struct {
|
type TileType struct {
|
||||||
|
@ -74,6 +77,26 @@ func TileTypeWall() TileType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TileTypeClosedDoor() TileType {
|
||||||
|
return TileType{
|
||||||
|
Material: MaterialClosedDoor,
|
||||||
|
Passable: false,
|
||||||
|
Transparent: false,
|
||||||
|
Presentation: '[',
|
||||||
|
Style: tcell.StyleDefault.Foreground(tcell.ColorLightSteelBlue).Background(tcell.ColorSaddleBrown),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TileTypeOpenDoor() TileType {
|
||||||
|
return TileType{
|
||||||
|
Material: MaterialClosedDoor,
|
||||||
|
Passable: false,
|
||||||
|
Transparent: false,
|
||||||
|
Presentation: '_',
|
||||||
|
Style: tcell.StyleDefault.Foreground(tcell.ColorLightSteelBlue),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Tile interface {
|
type Tile interface {
|
||||||
Position() util.Position
|
Position() util.Position
|
||||||
Presentation() (rune, tcell.Style)
|
Presentation() (rune, tcell.Style)
|
||||||
|
@ -117,11 +140,11 @@ func (st *StaticTile) Type() TileType {
|
||||||
|
|
||||||
type ItemTile struct {
|
type ItemTile struct {
|
||||||
position util.Position
|
position util.Position
|
||||||
itemType *ItemType
|
itemType *model.ItemType
|
||||||
quantity int
|
quantity int
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateItemTile(position util.Position, itemType *ItemType, quantity int) *ItemTile {
|
func CreateItemTile(position util.Position, itemType *model.ItemType, quantity int) *ItemTile {
|
||||||
it := new(ItemTile)
|
it := new(ItemTile)
|
||||||
|
|
||||||
it.position = position
|
it.position = position
|
||||||
|
@ -131,7 +154,7 @@ func CreateItemTile(position util.Position, itemType *ItemType, quantity int) *I
|
||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *ItemTile) Type() *ItemType {
|
func (it *ItemTile) Type() *model.ItemType {
|
||||||
return it.itemType
|
return it.itemType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +167,7 @@ func (it *ItemTile) Position() util.Position {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *ItemTile) Presentation() (rune, tcell.Style) {
|
func (it *ItemTile) Presentation() (rune, tcell.Style) {
|
||||||
return it.itemType.tileIcon, it.itemType.style
|
return it.itemType.TileIcon(), it.itemType.Style()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *ItemTile) Passable() bool {
|
func (it *ItemTile) Passable() bool {
|
||||||
|
@ -154,3 +177,43 @@ func (it *ItemTile) Passable() bool {
|
||||||
func (it *ItemTile) Transparent() bool {
|
func (it *ItemTile) Transparent() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EntityTile interface {
|
||||||
|
Entity() model.MovableEntity
|
||||||
|
Tile
|
||||||
|
}
|
||||||
|
|
||||||
|
type BasicEntityTile struct {
|
||||||
|
entity model.MovableEntity
|
||||||
|
|
||||||
|
presentation rune
|
||||||
|
style tcell.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateBasicEntityTile(entity model.MovableEntity, presentation rune, style tcell.Style) *BasicEntityTile {
|
||||||
|
return &BasicEntityTile{
|
||||||
|
entity: entity,
|
||||||
|
presentation: presentation,
|
||||||
|
style: style,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bet *BasicEntityTile) Entity() model.MovableEntity {
|
||||||
|
return bet.entity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bet *BasicEntityTile) Position() util.Position {
|
||||||
|
return bet.entity.Position()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bet *BasicEntityTile) Presentation() (rune, tcell.Style) {
|
||||||
|
return bet.presentation, bet.style
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bet *BasicEntityTile) Passable() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bet *BasicEntityTile) Transparent() bool {
|
||||||
|
return false
|
||||||
|
}
|
42
util/util.go
42
util/util.go
|
@ -2,6 +2,20 @@ package util
|
||||||
|
|
||||||
import "math/rand"
|
import "math/rand"
|
||||||
|
|
||||||
|
type Positioned struct {
|
||||||
|
pos Position
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithPosition(pos Position) Positioned {
|
||||||
|
return Positioned{
|
||||||
|
pos: pos,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wp *Positioned) Position() Position {
|
||||||
|
return wp.pos
|
||||||
|
}
|
||||||
|
|
||||||
type Position struct {
|
type Position struct {
|
||||||
x int
|
x int
|
||||||
y int
|
y int
|
||||||
|
@ -29,6 +43,25 @@ func (p Position) WithOffset(xOffset int, yOffset int) Position {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Sized struct {
|
||||||
|
size Size
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithSize(size Size) Sized {
|
||||||
|
return Sized{
|
||||||
|
size: size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if the provided coordinates fit within the sized struct, [0, N)
|
||||||
|
func (ws *Sized) FitsWithin(x, y int) bool {
|
||||||
|
return 0 <= x && x < ws.size.width && 0 <= y && y < ws.size.height
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *Sized) Size() Size {
|
||||||
|
return ws.size
|
||||||
|
}
|
||||||
|
|
||||||
type Size struct {
|
type Size struct {
|
||||||
width int
|
width int
|
||||||
height int
|
height int
|
||||||
|
@ -54,6 +87,10 @@ func (s Size) Area() int {
|
||||||
return s.width * s.height
|
return s.width * s.height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s Size) AsArrayIndex(x, y int) int {
|
||||||
|
return y*s.width + x
|
||||||
|
}
|
||||||
|
|
||||||
func LimitIncrement(i int, limit int) int {
|
func LimitIncrement(i int, limit int) int {
|
||||||
if (i + 1) > limit {
|
if (i + 1) > limit {
|
||||||
return i
|
return i
|
||||||
|
@ -73,3 +110,8 @@ func LimitDecrement(i int, limit int) int {
|
||||||
func RandInt(min, max int) int {
|
func RandInt(min, max int) int {
|
||||||
return min + rand.Intn(max-min)
|
return min + rand.Intn(max-min)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Room struct {
|
||||||
|
Positioned
|
||||||
|
Sized
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue