2024-04-27 22:32:05 +03:00
package state
import (
2024-05-06 20:43:35 +03:00
"mvvasilev/last_light/engine"
2024-04-27 22:32:05 +03:00
"mvvasilev/last_light/game/model"
2024-05-12 23:22:39 +03:00
"mvvasilev/last_light/game/ui"
2024-05-06 18:59:14 +03:00
"mvvasilev/last_light/game/world"
2024-04-27 22:32:05 +03:00
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/views"
)
type PlayingState struct {
2024-05-12 23:22:39 +03:00
player * model . Player
someNPC * model . NPC
dungeon * world . Dungeon
2024-04-27 22:32:05 +03:00
2024-05-06 20:43:35 +03:00
viewport * engine . Viewport
2024-04-27 22:32:05 +03:00
movePlayerDirection model . Direction
pauseGame bool
openInventory bool
2024-05-03 13:46:32 +03:00
pickUpUnderPlayer bool
2024-05-12 23:22:39 +03:00
interact bool
moveEntities bool
nextGameState GameState
2024-04-27 22:32:05 +03:00
}
func BeginPlayingState ( ) * PlayingState {
s := new ( PlayingState )
2024-05-06 21:47:20 +03:00
mapSize := engine . SizeOf ( 128 , 128 )
2024-04-27 22:32:05 +03:00
2024-05-12 23:22:39 +03:00
s . dungeon = world . CreateDungeon ( mapSize . Width ( ) , mapSize . Height ( ) , 1 )
2024-04-27 22:32:05 +03:00
2024-05-12 23:22:39 +03:00
s . player = model . CreatePlayer ( s . dungeon . CurrentLevel ( ) . PlayerSpawnPoint ( ) . XY ( ) )
2024-05-06 18:59:14 +03:00
2024-05-12 23:22:39 +03:00
s . someNPC = model . CreateNPC ( s . dungeon . CurrentLevel ( ) . NextLevelStaircase ( ) )
2024-04-27 22:32:05 +03:00
2024-05-12 23:22:39 +03:00
s . dungeon . CurrentLevel ( ) . AddEntity ( s . player , '@' , tcell . StyleDefault )
s . dungeon . CurrentLevel ( ) . AddEntity ( s . someNPC , 'N' , tcell . StyleDefault )
2024-04-27 22:32:05 +03:00
2024-05-06 20:43:35 +03:00
s . viewport = engine . CreateViewport (
2024-05-06 21:47:20 +03:00
engine . PositionAt ( 0 , 0 ) ,
2024-05-12 23:22:39 +03:00
s . dungeon . CurrentLevel ( ) . PlayerSpawnPoint ( ) ,
2024-05-06 21:47:20 +03:00
engine . SizeOf ( 80 , 24 ) ,
2024-04-27 22:32:05 +03:00
tcell . StyleDefault ,
)
2024-05-12 23:22:39 +03:00
s . nextGameState = s
2024-04-27 22:32:05 +03:00
return s
}
func ( ps * PlayingState ) Pause ( ) {
ps . pauseGame = true
}
func ( ps * PlayingState ) Unpause ( ) {
ps . pauseGame = false
}
func ( ps * PlayingState ) SetPaused ( paused bool ) {
ps . pauseGame = paused
}
func ( ps * PlayingState ) MovePlayer ( ) {
if ps . movePlayerDirection == model . DirectionNone {
return
}
newPlayerPos := ps . player . Position ( ) . WithOffset ( model . MovementDirectionOffset ( ps . movePlayerDirection ) )
2024-05-12 23:22:39 +03:00
if ps . dungeon . CurrentLevel ( ) . IsTilePassable ( newPlayerPos . XY ( ) ) {
2024-05-06 18:59:14 +03:00
dx , dy := model . MovementDirectionOffset ( ps . movePlayerDirection )
2024-05-12 23:22:39 +03:00
ps . dungeon . CurrentLevel ( ) . MoveEntity ( ps . player . UniqueId ( ) , dx , dy )
2024-04-27 22:32:05 +03:00
ps . viewport . SetCenter ( ps . player . Position ( ) )
}
ps . movePlayerDirection = model . DirectionNone
}
2024-05-12 23:22:39 +03:00
func ( ps * PlayingState ) InteractBelowPlayer ( ) {
playerPos := ps . player . Position ( )
if playerPos == ps . dungeon . CurrentLevel ( ) . NextLevelStaircase ( ) {
ps . SwitchToNextLevel ( )
return
}
if playerPos == ps . dungeon . CurrentLevel ( ) . PreviousLevelStaircase ( ) {
ps . SwitchToPreviousLevel ( )
return
}
}
func ( ps * PlayingState ) SwitchToNextLevel ( ) {
if ! ps . dungeon . HasNextLevel ( ) {
ps . nextGameState = CreateDialogState (
ui . CreateOkDialog (
"The Unknown Depths" ,
"The staircases descent down to the lower levels is seemingly blocked by multiple large boulders. They appear immovable." ,
"Continue" ,
40 ,
func ( ) {
ps . nextGameState = ps
} ,
) ,
ps ,
)
return
}
ps . dungeon . CurrentLevel ( ) . DropEntity ( ps . player . UniqueId ( ) )
ps . dungeon . MoveToNextLevel ( )
ps . player . MoveTo ( ps . dungeon . CurrentLevel ( ) . PlayerSpawnPoint ( ) )
ps . viewport = engine . CreateViewport (
engine . PositionAt ( 0 , 0 ) ,
ps . dungeon . CurrentLevel ( ) . PlayerSpawnPoint ( ) ,
engine . SizeOf ( 80 , 24 ) ,
tcell . StyleDefault ,
)
ps . dungeon . CurrentLevel ( ) . AddEntity ( ps . player , '@' , tcell . StyleDefault )
}
func ( ps * PlayingState ) SwitchToPreviousLevel ( ) {
if ! ps . dungeon . HasPreviousLevel ( ) {
ps . nextGameState = CreateDialogState (
ui . CreateOkDialog (
"The Surface" ,
"You feel the gentle, yet chilling breeze of the surface make its way through the weaving cavern tunnels, the very same you had to make your way through to get where you are. There is nothing above that you need. Find the last light, or die trying." ,
"Continue" ,
40 ,
func ( ) {
ps . nextGameState = ps
} ,
) ,
ps ,
)
return
}
ps . dungeon . CurrentLevel ( ) . DropEntity ( ps . player . UniqueId ( ) )
ps . dungeon . MoveToPreviousLevel ( )
ps . player . MoveTo ( ps . dungeon . CurrentLevel ( ) . NextLevelStaircase ( ) )
ps . viewport = engine . CreateViewport (
engine . PositionAt ( 0 , 0 ) ,
ps . dungeon . CurrentLevel ( ) . NextLevelStaircase ( ) ,
engine . SizeOf ( 80 , 24 ) ,
tcell . StyleDefault ,
)
ps . dungeon . CurrentLevel ( ) . AddEntity ( ps . player , '@' , tcell . StyleDefault )
}
2024-05-03 13:46:32 +03:00
func ( ps * PlayingState ) PickUpItemUnderPlayer ( ) {
pos := ps . player . Position ( )
2024-05-12 23:22:39 +03:00
item := ps . dungeon . CurrentLevel ( ) . RemoveItemAt ( pos . XY ( ) )
if item == nil {
return
}
success := ps . player . Inventory ( ) . Push ( * item )
2024-05-03 13:46:32 +03:00
2024-05-12 23:22:39 +03:00
if ! success {
ps . dungeon . CurrentLevel ( ) . SetItemAt ( pos . X ( ) , pos . Y ( ) , * item )
}
}
2024-05-03 13:46:32 +03:00
2024-05-12 23:22:39 +03:00
func ( ps * PlayingState ) CalcPathToPlayerAndMove ( ) {
distanceToPlayer := ps . someNPC . Position ( ) . Distance ( ps . player . Position ( ) )
2024-05-13 01:01:39 +03:00
if distanceToPlayer > 20 {
2024-05-03 13:46:32 +03:00
return
}
2024-05-12 23:22:39 +03:00
pathToPlayer := engine . FindPath (
ps . someNPC . Position ( ) ,
ps . player . Position ( ) ,
func ( x , y int ) bool {
if x == ps . player . Position ( ) . X ( ) && y == ps . player . Position ( ) . Y ( ) {
return true
}
2024-05-03 13:46:32 +03:00
2024-05-12 23:22:39 +03:00
return ps . dungeon . CurrentLevel ( ) . IsTilePassable ( x , y )
} ,
)
2024-05-03 13:46:32 +03:00
2024-05-12 23:22:39 +03:00
nextPos , hasNext := pathToPlayer . Next ( )
if ! hasNext {
2024-05-03 13:46:32 +03:00
return
}
2024-05-12 23:22:39 +03:00
if nextPos . Equals ( ps . player . Position ( ) ) {
return
}
ps . dungeon . CurrentLevel ( ) . MoveEntityTo ( ps . someNPC . UniqueId ( ) , nextPos . X ( ) , nextPos . Y ( ) )
2024-05-03 13:46:32 +03:00
}
2024-04-27 22:32:05 +03:00
func ( ps * PlayingState ) OnInput ( e * tcell . EventKey ) {
ps . player . Input ( e )
if e . Key ( ) == tcell . KeyEsc {
ps . pauseGame = true
return
}
if e . Key ( ) == tcell . KeyRune && e . Rune ( ) == 'i' {
ps . openInventory = true
return
}
2024-05-03 13:46:32 +03:00
if e . Key ( ) == tcell . KeyRune && e . Rune ( ) == 'p' {
ps . pickUpUnderPlayer = true
return
}
2024-05-12 23:22:39 +03:00
if e . Key ( ) == tcell . KeyRune && e . Rune ( ) == 'e' {
ps . interact = true
return
}
2024-04-27 22:32:05 +03:00
switch e . Key ( ) {
case tcell . KeyUp :
ps . movePlayerDirection = model . DirectionUp
2024-05-12 23:22:39 +03:00
ps . moveEntities = true
2024-04-27 22:32:05 +03:00
case tcell . KeyDown :
ps . movePlayerDirection = model . DirectionDown
2024-05-12 23:22:39 +03:00
ps . moveEntities = true
2024-04-27 22:32:05 +03:00
case tcell . KeyLeft :
ps . movePlayerDirection = model . DirectionLeft
2024-05-12 23:22:39 +03:00
ps . moveEntities = true
2024-04-27 22:32:05 +03:00
case tcell . KeyRight :
ps . movePlayerDirection = model . DirectionRight
2024-05-12 23:22:39 +03:00
ps . moveEntities = true
2024-04-27 22:32:05 +03:00
}
}
func ( ps * PlayingState ) OnTick ( dt int64 ) GameState {
ps . player . Tick ( dt )
if ps . pauseGame {
return PauseGame ( ps )
}
if ps . openInventory {
2024-05-03 13:46:32 +03:00
ps . openInventory = false
2024-04-27 22:32:05 +03:00
return CreateInventoryScreenState ( ps . player , ps )
}
if ps . movePlayerDirection != model . DirectionNone {
ps . MovePlayer ( )
}
2024-05-03 13:46:32 +03:00
if ps . pickUpUnderPlayer {
ps . pickUpUnderPlayer = false
ps . PickUpItemUnderPlayer ( )
}
2024-05-12 23:22:39 +03:00
if ps . interact {
ps . interact = false
ps . InteractBelowPlayer ( )
}
if ps . moveEntities {
ps . moveEntities = false
ps . CalcPathToPlayerAndMove ( )
}
return ps . nextGameState
2024-04-27 22:32:05 +03:00
}
2024-05-06 20:43:35 +03:00
func ( ps * PlayingState ) CollectDrawables ( ) [ ] engine . Drawable {
return engine . Multidraw ( engine . CreateDrawingInstructions ( func ( v views . View ) {
2024-05-03 13:46:32 +03:00
ps . viewport . DrawFromProvider ( v , func ( x , y int ) ( rune , tcell . Style ) {
2024-05-12 23:22:39 +03:00
tile := ps . dungeon . CurrentLevel ( ) . TileAt ( x , y )
2024-04-27 22:32:05 +03:00
if tile != nil {
return tile . Presentation ( )
}
2024-05-03 13:46:32 +03:00
return ' ' , tcell . StyleDefault
2024-04-27 22:32:05 +03:00
} )
} ) )
}