mirror of
https://github.com/mvvasilev/last_light.git
synced 2025-04-19 12:49:52 +03:00
Turn system, bunch of other things
This commit is contained in:
parent
b30dc8dec3
commit
0f093dd7f9
39 changed files with 1339 additions and 420 deletions
50
engine/event_logger.go
Normal file
50
engine/event_logger.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package engine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GameEvent struct {
|
||||||
|
time time.Time
|
||||||
|
contents string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ge *GameEvent) Time() time.Time {
|
||||||
|
return ge.time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ge *GameEvent) Contents() string {
|
||||||
|
return ge.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
type GameEventLog struct {
|
||||||
|
logs []*GameEvent
|
||||||
|
|
||||||
|
maxSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateGameEventLog(maxSize int) *GameEventLog {
|
||||||
|
return &GameEventLog{
|
||||||
|
logs: make([]*GameEvent, 0, 10),
|
||||||
|
maxSize: maxSize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (log *GameEventLog) Log(contents string) {
|
||||||
|
log.logs = append(log.logs, &GameEvent{
|
||||||
|
time: time.Now(),
|
||||||
|
contents: contents,
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(log.logs) > log.maxSize {
|
||||||
|
log.logs = log.logs[1:len(log.logs)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (log *GameEventLog) Tail(n int) []*GameEvent {
|
||||||
|
if n > len(log.logs) {
|
||||||
|
return log.logs
|
||||||
|
}
|
||||||
|
|
||||||
|
return log.logs[len(log.logs)-n : len(log.logs)]
|
||||||
|
}
|
73
engine/priority_queue.go
Normal file
73
engine/priority_queue.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package engine
|
||||||
|
|
||||||
|
import "slices"
|
||||||
|
|
||||||
|
// [ a = 1, b = 2, c = 3, d = 4 ]
|
||||||
|
// a = 1 <- [ b = 2, c = 3, d = 4 ]
|
||||||
|
// do a action
|
||||||
|
// sub a priority from queue: [ b = 1, c = 2, d = 3 ]
|
||||||
|
// [ b = 1, a = 1, c = 2, d = 3 ] <- a = 1
|
||||||
|
// b = 1 <- [ a = 1, c = 2, d = 3 ]
|
||||||
|
|
||||||
|
type priorityQueueItem[T interface{}] struct {
|
||||||
|
priority int
|
||||||
|
value T
|
||||||
|
}
|
||||||
|
|
||||||
|
type PriorityQueue[T interface{}] struct {
|
||||||
|
queue []*priorityQueueItem[T]
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreatePriorityQueue[T interface{}]() *PriorityQueue[T] {
|
||||||
|
return &PriorityQueue[T]{
|
||||||
|
queue: make([]*priorityQueueItem[T], 0, 10),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pq *PriorityQueue[T]) Enqueue(prio int, value T) {
|
||||||
|
pq.queue = append(pq.queue, &priorityQueueItem[T]{priority: prio, value: value})
|
||||||
|
slices.SortFunc(pq.queue, func(e1, e2 *priorityQueueItem[T]) int { return e1.priority - e2.priority })
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pq *PriorityQueue[T]) AdjustPriorities(amount int) {
|
||||||
|
for _, e := range pq.queue {
|
||||||
|
e.priority += amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop element w/ lowest priority
|
||||||
|
func (pq *PriorityQueue[T]) DequeueValue() (value T) {
|
||||||
|
if len(pq.queue) < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
value, pq.queue = pq.queue[0].value, pq.queue[1:len(pq.queue)]
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek element w/ lowest priority
|
||||||
|
func (pq *PriorityQueue[T]) Peek() (priority int, value T) {
|
||||||
|
if len(pq.queue) < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
priority, value = pq.queue[0].priority, pq.queue[0].value
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop element w/ lowest priority, returning the priority as well as the value
|
||||||
|
func (pq *PriorityQueue[T]) Dequeue() (priority int, value T) {
|
||||||
|
if len(pq.queue) < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
priority, value, pq.queue = pq.queue[0].priority, pq.queue[0].value, pq.queue[1:len(pq.queue)]
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pq *PriorityQueue[T]) Clear() {
|
||||||
|
pq.queue = make([]*priorityQueueItem[T], 0, 10)
|
||||||
|
}
|
53
engine/raycasting.go
Normal file
53
engine/raycasting.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package engine
|
||||||
|
|
||||||
|
func CastRay(pos1, pos2 Position) (points []Position) {
|
||||||
|
x1, y1 := pos1.XY()
|
||||||
|
x2, y2 := pos2.XY()
|
||||||
|
|
||||||
|
isSteep := AbsInt(y2-y1) > AbsInt(x2-x1)
|
||||||
|
|
||||||
|
if isSteep {
|
||||||
|
x1, y1 = y1, x1
|
||||||
|
x2, y2 = y2, x2
|
||||||
|
}
|
||||||
|
|
||||||
|
reversed := false
|
||||||
|
if x1 > x2 {
|
||||||
|
x1, x2 = x2, x1
|
||||||
|
y1, y2 = y2, y1
|
||||||
|
reversed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
deltaX := x2 - x1
|
||||||
|
deltaY := AbsInt(y2 - y1)
|
||||||
|
err := deltaX / 2
|
||||||
|
y := y1
|
||||||
|
var ystep int
|
||||||
|
|
||||||
|
if y1 < y2 {
|
||||||
|
ystep = 1
|
||||||
|
} else {
|
||||||
|
ystep = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
for x := x1; x < x2+1; x++ {
|
||||||
|
if isSteep {
|
||||||
|
points = append(points, Position{y, x})
|
||||||
|
} else {
|
||||||
|
points = append(points, Position{x, y})
|
||||||
|
}
|
||||||
|
err -= deltaY
|
||||||
|
if err < 0 {
|
||||||
|
y += ystep
|
||||||
|
err += deltaX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if reversed {
|
||||||
|
for i, j := 0, len(points)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
points[i], points[j] = points[j], points[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -126,6 +126,10 @@ func (rect Rectangle) Position() Position {
|
||||||
return rect.position
|
return rect.position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rect Rectangle) Size() Size {
|
||||||
|
return rect.size
|
||||||
|
}
|
||||||
|
|
||||||
func (rect Rectangle) drawBorders(v views.View) {
|
func (rect Rectangle) drawBorders(v views.View) {
|
||||||
width := rect.size.Width()
|
width := rect.size.Width()
|
||||||
height := rect.size.Height()
|
height := rect.size.Height()
|
||||||
|
@ -170,3 +174,43 @@ func (rect Rectangle) Draw(v views.View) {
|
||||||
rect.drawFill(v)
|
rect.drawFill(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DrawRectangle(
|
||||||
|
x int,
|
||||||
|
y int,
|
||||||
|
width int,
|
||||||
|
height int,
|
||||||
|
nwCorner, northBorder, neCorner,
|
||||||
|
westBorder, fillRune, eastBorder,
|
||||||
|
swCorner, southBorder, seCorner rune,
|
||||||
|
isBorderless, isFilled bool,
|
||||||
|
style tcell.Style, v views.View,
|
||||||
|
) {
|
||||||
|
|
||||||
|
v.SetContent(x, y, nwCorner, nil, style)
|
||||||
|
v.SetContent(x+width-1, y, neCorner, nil, style)
|
||||||
|
v.SetContent(x, y+height-1, swCorner, nil, style)
|
||||||
|
v.SetContent(x+width-1, y+height-1, seCorner, nil, style)
|
||||||
|
|
||||||
|
if !isBorderless {
|
||||||
|
for w := 1; w < width-1; w++ {
|
||||||
|
v.SetContent(x+w, y, northBorder, nil, style)
|
||||||
|
v.SetContent(x+w, y+height-1, southBorder, nil, style)
|
||||||
|
}
|
||||||
|
|
||||||
|
for h := 1; h < height-1; h++ {
|
||||||
|
v.SetContent(x, y+h, westBorder, nil, style)
|
||||||
|
v.SetContent(x+width-1, y+h, eastBorder, nil, style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isFilled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for w := 1; w < width-1; w++ {
|
||||||
|
for h := 1; h < height-1; h++ {
|
||||||
|
v.SetContent(x+w, y+h, fillRune, nil, style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -109,3 +109,15 @@ func (t *Text) Draw(s views.View) {
|
||||||
currentHPos += runeCount + 1 // add +1 to account for space after word
|
currentHPos += runeCount + 1 // add +1 to account for space after word
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DrawText(x, y int, content string, style tcell.Style, s views.View) {
|
||||||
|
currentHPos := 0
|
||||||
|
currentVPos := 0
|
||||||
|
|
||||||
|
lastPos := 0
|
||||||
|
|
||||||
|
for _, r := range content {
|
||||||
|
s.SetContent(x+currentHPos+lastPos, y+currentVPos, r, nil, style)
|
||||||
|
lastPos++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -143,3 +143,13 @@ func MapSlice[S ~[]E, E any, R any](slice S, mappingFunc func(e E) R) []R {
|
||||||
|
|
||||||
return newSlice
|
return newSlice
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AbsInt(val int) int {
|
||||||
|
switch {
|
||||||
|
case val < 0:
|
||||||
|
return -val
|
||||||
|
case val == 0:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
23
game/game.go
23
game/game.go
|
@ -2,12 +2,17 @@ package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
|
"mvvasilev/last_light/game/input"
|
||||||
"mvvasilev/last_light/game/state"
|
"mvvasilev/last_light/game/state"
|
||||||
|
"mvvasilev/last_light/game/turns"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Game struct {
|
type Game struct {
|
||||||
|
turnSystem *turns.TurnSystem
|
||||||
|
inputSystem *input.InputSystem
|
||||||
|
|
||||||
state state.GameState
|
state state.GameState
|
||||||
|
|
||||||
quitGame bool
|
quitGame bool
|
||||||
|
@ -16,7 +21,11 @@ type Game struct {
|
||||||
func CreateGame() *Game {
|
func CreateGame() *Game {
|
||||||
game := new(Game)
|
game := new(Game)
|
||||||
|
|
||||||
game.state = state.NewMainMenuState()
|
game.turnSystem = turns.CreateTurnSystem()
|
||||||
|
|
||||||
|
game.inputSystem = input.CreateInputSystemWithDefaultBindings()
|
||||||
|
|
||||||
|
game.state = state.CreateMainMenuState(game.turnSystem, game.inputSystem)
|
||||||
|
|
||||||
return game
|
return game
|
||||||
}
|
}
|
||||||
|
@ -26,24 +35,22 @@ func (g *Game) Input(ev *tcell.EventKey) {
|
||||||
g.quitGame = true
|
g.quitGame = true
|
||||||
}
|
}
|
||||||
|
|
||||||
g.state.OnInput(ev)
|
g.inputSystem.Input(g.state.InputContext(), ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) Tick(dt int64) bool {
|
func (g *Game) Tick(dt int64) (continueGame bool) {
|
||||||
if g.quitGame {
|
continueGame = !g.quitGame
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
s := g.state.OnTick(dt)
|
s := g.state.OnTick(dt)
|
||||||
|
|
||||||
switch s.(type) {
|
switch s.(type) {
|
||||||
case *state.QuitState:
|
case *state.QuitState:
|
||||||
return false
|
g.quitGame = true
|
||||||
}
|
}
|
||||||
|
|
||||||
g.state = s
|
g.state = s
|
||||||
|
|
||||||
return true
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) CollectDrawables() []engine.Drawable {
|
func (g *Game) CollectDrawables() []engine.Drawable {
|
||||||
|
|
111
game/input/input_system.go
Normal file
111
game/input/input_system.go
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
package input
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Context string
|
||||||
|
|
||||||
|
const (
|
||||||
|
InputContext_Play = "play"
|
||||||
|
InputContext_Menu = "menu"
|
||||||
|
InputContext_Inventory = "inventory"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InputKey string
|
||||||
|
|
||||||
|
func InputKeyOf(context Context, mod tcell.ModMask, key tcell.Key, r rune) InputKey {
|
||||||
|
return InputKey(fmt.Sprintf("%v-%v-%v-%v", context, mod, key, r))
|
||||||
|
}
|
||||||
|
|
||||||
|
type InputAction int
|
||||||
|
|
||||||
|
const (
|
||||||
|
InputAction_None InputAction = iota
|
||||||
|
|
||||||
|
InputAction_MovePlayer_North
|
||||||
|
InputAction_MovePlayer_South
|
||||||
|
InputAction_MovePlayer_East
|
||||||
|
InputAction_MovePlayer_West
|
||||||
|
|
||||||
|
InputAction_Interact
|
||||||
|
InputAction_OpenInventory
|
||||||
|
InputAction_PickUpItem
|
||||||
|
InputAction_OpenLogs
|
||||||
|
InputAction_DropItem
|
||||||
|
InputAction_EquipItem
|
||||||
|
InputAction_UnequipItem
|
||||||
|
|
||||||
|
InputAction_PauseGame
|
||||||
|
|
||||||
|
InputAction_Menu_HighlightDown
|
||||||
|
InputAction_Menu_HighlightUp
|
||||||
|
InputAction_Menu_HighlightLeft
|
||||||
|
InputAction_Menu_HighlightRight
|
||||||
|
|
||||||
|
InputAction_Menu_Select
|
||||||
|
|
||||||
|
InputAction_Menu_Exit
|
||||||
|
)
|
||||||
|
|
||||||
|
type InputSystem struct {
|
||||||
|
keyBindings map[InputKey]InputAction
|
||||||
|
|
||||||
|
nextAction InputAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateInputSystemWithDefaultBindings() *InputSystem {
|
||||||
|
return &InputSystem{
|
||||||
|
keyBindings: map[InputKey]InputAction{
|
||||||
|
InputKeyOf(InputContext_Play, 0, tcell.KeyUp, 0): InputAction_MovePlayer_North,
|
||||||
|
InputKeyOf(InputContext_Play, 0, tcell.KeyDown, 0): InputAction_MovePlayer_South,
|
||||||
|
InputKeyOf(InputContext_Play, 0, tcell.KeyLeft, 0): InputAction_MovePlayer_West,
|
||||||
|
InputKeyOf(InputContext_Play, 0, tcell.KeyRight, 0): InputAction_MovePlayer_East,
|
||||||
|
InputKeyOf(InputContext_Play, 0, tcell.KeyEsc, 0): InputAction_PauseGame,
|
||||||
|
InputKeyOf(InputContext_Play, 0, tcell.KeyRune, 'i'): InputAction_OpenInventory,
|
||||||
|
InputKeyOf(InputContext_Play, 0, tcell.KeyRune, 'l'): InputAction_OpenLogs,
|
||||||
|
InputKeyOf(InputContext_Play, 0, tcell.KeyRune, 'e'): InputAction_Interact,
|
||||||
|
InputKeyOf(InputContext_Play, 0, tcell.KeyRune, 'p'): InputAction_PickUpItem,
|
||||||
|
InputKeyOf(InputContext_Menu, 0, tcell.KeyESC, 0): InputAction_Menu_Exit,
|
||||||
|
InputKeyOf(InputContext_Menu, 0, tcell.KeyLeft, 0): InputAction_Menu_HighlightLeft,
|
||||||
|
InputKeyOf(InputContext_Menu, 0, tcell.KeyRight, 0): InputAction_Menu_HighlightRight,
|
||||||
|
InputKeyOf(InputContext_Menu, 0, tcell.KeyUp, 0): InputAction_Menu_HighlightUp,
|
||||||
|
InputKeyOf(InputContext_Menu, 0, tcell.KeyDown, 0): InputAction_Menu_HighlightDown,
|
||||||
|
InputKeyOf(InputContext_Menu, 0, tcell.KeyCR, 13): InputAction_Menu_Select,
|
||||||
|
InputKeyOf(InputContext_Inventory, 0, tcell.KeyESC, 0): InputAction_Menu_Exit,
|
||||||
|
InputKeyOf(InputContext_Inventory, 0, tcell.KeyRune, 'i'): InputAction_Menu_Exit,
|
||||||
|
InputKeyOf(InputContext_Inventory, 0, tcell.KeyRune, 'e'): InputAction_EquipItem,
|
||||||
|
InputKeyOf(InputContext_Inventory, 0, tcell.KeyRune, 'd'): InputAction_DropItem,
|
||||||
|
InputKeyOf(InputContext_Inventory, 0, tcell.KeyLeft, 0): InputAction_Menu_HighlightLeft,
|
||||||
|
InputKeyOf(InputContext_Inventory, 0, tcell.KeyRight, 0): InputAction_Menu_HighlightRight,
|
||||||
|
InputKeyOf(InputContext_Inventory, 0, tcell.KeyUp, 0): InputAction_Menu_HighlightUp,
|
||||||
|
InputKeyOf(InputContext_Inventory, 0, tcell.KeyDown, 0): InputAction_Menu_HighlightDown,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kb *InputSystem) ImportBindings(imports map[InputKey]InputAction) {
|
||||||
|
kb.keyBindings = imports
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kb *InputSystem) ExportBindings() map[InputKey]InputAction {
|
||||||
|
return kb.keyBindings
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kb *InputSystem) Bind(key InputKey, action InputAction) {
|
||||||
|
kb.keyBindings[key] = action
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kb *InputSystem) Input(context Context, ev *tcell.EventKey) {
|
||||||
|
kb.nextAction = kb.keyBindings[InputKeyOf(context, ev.Modifiers(), ev.Key(), ev.Rune())]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kb *InputSystem) NextAction() (nextAction InputAction) {
|
||||||
|
nextAction = kb.nextAction
|
||||||
|
|
||||||
|
kb.nextAction = InputAction_None
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -43,7 +43,7 @@ func (inv *BasicInventory) Push(i Item) (success bool) {
|
||||||
|
|
||||||
// Try to first find a matching item with capacity
|
// Try to first find a matching item with capacity
|
||||||
for index, existingItem := range inv.contents {
|
for index, existingItem := range inv.contents {
|
||||||
if existingItem != nil && existingItem.Type() == itemType {
|
if existingItem != nil && existingItem.Type().Id() == itemType.Id() {
|
||||||
if existingItem.Quantity()+1 > existingItem.Type().MaxStack() {
|
if existingItem.Quantity()+1 > existingItem.Type().MaxStack() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type ItemType interface {
|
type ItemType interface {
|
||||||
|
Id() int
|
||||||
Name() string
|
Name() string
|
||||||
Description() string
|
Description() string
|
||||||
TileIcon() rune
|
TileIcon() rune
|
||||||
|
@ -15,6 +16,7 @@ type ItemType interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type BasicItemType struct {
|
type BasicItemType struct {
|
||||||
|
id int
|
||||||
name string
|
name string
|
||||||
description string
|
description string
|
||||||
tileIcon rune
|
tileIcon rune
|
||||||
|
@ -26,6 +28,7 @@ type BasicItemType struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateBasicItemType(
|
func CreateBasicItemType(
|
||||||
|
id int,
|
||||||
name, description string,
|
name, description string,
|
||||||
tileIcon rune,
|
tileIcon rune,
|
||||||
icon string,
|
icon string,
|
||||||
|
@ -34,6 +37,7 @@ func CreateBasicItemType(
|
||||||
style tcell.Style,
|
style tcell.Style,
|
||||||
) *BasicItemType {
|
) *BasicItemType {
|
||||||
return &BasicItemType{
|
return &BasicItemType{
|
||||||
|
id: id,
|
||||||
name: name,
|
name: name,
|
||||||
description: description,
|
description: description,
|
||||||
tileIcon: tileIcon,
|
tileIcon: tileIcon,
|
||||||
|
@ -44,6 +48,10 @@ func CreateBasicItemType(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (it *BasicItemType) Id() int {
|
||||||
|
return it.id
|
||||||
|
}
|
||||||
|
|
||||||
func (it *BasicItemType) Name() string {
|
func (it *BasicItemType) Name() string {
|
||||||
return it.name
|
return it.name
|
||||||
}
|
}
|
||||||
|
@ -74,6 +82,7 @@ func (it *BasicItemType) EquippableSlot() EquippedSlot {
|
||||||
|
|
||||||
func ItemTypeFish() ItemType {
|
func ItemTypeFish() ItemType {
|
||||||
return &BasicItemType{
|
return &BasicItemType{
|
||||||
|
id: 0,
|
||||||
name: "Fish",
|
name: "Fish",
|
||||||
description: "What's a fish doing down here?",
|
description: "What's a fish doing down here?",
|
||||||
tileIcon: '>',
|
tileIcon: '>',
|
||||||
|
@ -86,6 +95,7 @@ func ItemTypeFish() ItemType {
|
||||||
|
|
||||||
func ItemTypeGold() ItemType {
|
func ItemTypeGold() ItemType {
|
||||||
return &BasicItemType{
|
return &BasicItemType{
|
||||||
|
id: 1,
|
||||||
name: "Gold",
|
name: "Gold",
|
||||||
description: "Not all those who wander are lost",
|
description: "Not all those who wander are lost",
|
||||||
tileIcon: '¤',
|
tileIcon: '¤',
|
||||||
|
@ -98,6 +108,7 @@ func ItemTypeGold() ItemType {
|
||||||
|
|
||||||
func ItemTypeArrow() ItemType {
|
func ItemTypeArrow() ItemType {
|
||||||
return &BasicItemType{
|
return &BasicItemType{
|
||||||
|
id: 2,
|
||||||
name: "Arrow",
|
name: "Arrow",
|
||||||
description: "Ammunition for a bow",
|
description: "Ammunition for a bow",
|
||||||
tileIcon: '-',
|
tileIcon: '-',
|
||||||
|
@ -110,6 +121,7 @@ func ItemTypeArrow() ItemType {
|
||||||
|
|
||||||
func ItemTypeKey() ItemType {
|
func ItemTypeKey() ItemType {
|
||||||
return &BasicItemType{
|
return &BasicItemType{
|
||||||
|
id: 3,
|
||||||
name: "Key",
|
name: "Key",
|
||||||
description: "Indispensable for unlocking things",
|
description: "Indispensable for unlocking things",
|
||||||
tileIcon: '¬',
|
tileIcon: '¬',
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package model
|
package npc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
|
@ -11,21 +11,36 @@ type Direction int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DirectionNone Direction = iota
|
DirectionNone Direction = iota
|
||||||
DirectionUp
|
North
|
||||||
DirectionDown
|
South
|
||||||
DirectionLeft
|
West
|
||||||
DirectionRight
|
East
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func DirectionName(dir Direction) string {
|
||||||
|
switch dir {
|
||||||
|
case North:
|
||||||
|
return "North"
|
||||||
|
case South:
|
||||||
|
return "South"
|
||||||
|
case West:
|
||||||
|
return "West"
|
||||||
|
case East:
|
||||||
|
return "East"
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func MovementDirectionOffset(dir Direction) (int, int) {
|
func MovementDirectionOffset(dir Direction) (int, int) {
|
||||||
switch dir {
|
switch dir {
|
||||||
case DirectionUp:
|
case North:
|
||||||
return 0, -1
|
return 0, -1
|
||||||
case DirectionDown:
|
case South:
|
||||||
return 0, 1
|
return 0, 1
|
||||||
case DirectionLeft:
|
case West:
|
||||||
return -1, 0
|
return -1, 0
|
||||||
case DirectionRight:
|
case East:
|
||||||
return 1, 0
|
return 1, 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package model
|
package npc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
|
@ -24,7 +24,15 @@ func CreatePlayer(x, y int) *Player {
|
||||||
p.id = uuid.New()
|
p.id = uuid.New()
|
||||||
p.position = engine.PositionAt(x, y)
|
p.position = engine.PositionAt(x, y)
|
||||||
p.inventory = item.CreateEquippedInventory()
|
p.inventory = item.CreateEquippedInventory()
|
||||||
p.BasicRPGEntity = rpg.CreateBasicRPGEntity()
|
p.BasicRPGEntity = rpg.CreateBasicRPGEntity(
|
||||||
|
map[rpg.Stat]int{
|
||||||
|
rpg.Stat_Attributes_Constitution: 10,
|
||||||
|
rpg.Stat_Attributes_Dexterity: 10,
|
||||||
|
rpg.Stat_Attributes_Strength: 10,
|
||||||
|
rpg.Stat_Attributes_Intelligence: 10,
|
||||||
|
},
|
||||||
|
map[rpg.Stat][]rpg.StatModifier{},
|
||||||
|
)
|
||||||
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
16
game/player/player_logic.go
Normal file
16
game/player/player_logic.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package player
|
||||||
|
|
||||||
|
import "mvvasilev/last_light/game/input"
|
||||||
|
|
||||||
|
func PlayerTurn(inputSystem *input.InputSystem) (complete, requeue bool) {
|
||||||
|
requeue = true
|
||||||
|
complete = false
|
||||||
|
|
||||||
|
nextAction := inputSystem.NextAction()
|
||||||
|
|
||||||
|
if nextAction == input.InputAction_None {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -4,11 +4,18 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/item"
|
"mvvasilev/last_light/game/item"
|
||||||
|
"slices"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: figure out event logging. Need to inform the player of the things that are happening...
|
||||||
|
|
||||||
|
const MaxNumberOfModifiers = 6
|
||||||
|
|
||||||
type ItemSupplier func() item.Item
|
type ItemSupplier func() item.Item
|
||||||
|
|
||||||
type LootTable struct {
|
type LootTable struct {
|
||||||
|
@ -58,34 +65,89 @@ func pointPerRarity(rarity ItemRarity) int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func generateUniqueItemName() string {
|
||||||
|
starts := []string{
|
||||||
|
"du", "nol", "ma", "re",
|
||||||
|
"ka", "gro", "hru", "lo",
|
||||||
|
"ara", "ke", "ko", "uro",
|
||||||
|
"ne", "pe", "pa", "pho",
|
||||||
|
}
|
||||||
|
|
||||||
|
middles := []string{
|
||||||
|
"kora", "duru", "kolku", "dila",
|
||||||
|
"luio", "ghro", "kelma", "riga",
|
||||||
|
"fela", "fiya", "numa", "ruta",
|
||||||
|
}
|
||||||
|
|
||||||
|
end := []string{
|
||||||
|
"dum", "dor", "dar", "thar",
|
||||||
|
"thor", "thum", "hor", "hum",
|
||||||
|
"her", "kom", "kur", "kyr",
|
||||||
|
"mor", "mar", "man", "kum",
|
||||||
|
"tum",
|
||||||
|
}
|
||||||
|
|
||||||
|
name := starts[rand.Intn(len(starts))] + middles[rand.Intn(len(middles))] + end[rand.Intn(len(end))]
|
||||||
|
|
||||||
|
r, size := utf8.DecodeRuneInString(name)
|
||||||
|
|
||||||
|
return string(unicode.ToUpper(r)) + name[size:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomAdjective() string {
|
||||||
|
adjectives := []string{
|
||||||
|
"shiny", "gruesome", "sharp", "tattered",
|
||||||
|
"mediocre", "unusual", "bright", "rusty",
|
||||||
|
"dreadful", "exceptional", "old", "bent",
|
||||||
|
"ancient", "crude", "dented", "cool",
|
||||||
|
}
|
||||||
|
|
||||||
|
adj := adjectives[rand.Intn(len(adjectives))]
|
||||||
|
|
||||||
|
r, size := utf8.DecodeRuneInString(adj)
|
||||||
|
|
||||||
|
return string(unicode.ToUpper(r)) + adj[size:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomSuffix() string {
|
||||||
|
suffixes := []string{
|
||||||
|
"of the Monkey", "of the Tiger", "of the Elephant", "of the Slug",
|
||||||
|
"of Elven Make",
|
||||||
|
}
|
||||||
|
|
||||||
|
return suffixes[rand.Intn(len(suffixes))]
|
||||||
|
}
|
||||||
|
|
||||||
func generateItemName(itemType RPGItemType, rarity ItemRarity) (string, tcell.Style) {
|
func generateItemName(itemType RPGItemType, rarity ItemRarity) (string, tcell.Style) {
|
||||||
switch rarity {
|
switch rarity {
|
||||||
case ItemRarity_Common:
|
case ItemRarity_Common:
|
||||||
return itemType.Name(), tcell.StyleDefault
|
return itemType.Name(), tcell.StyleDefault
|
||||||
case ItemRarity_Uncommon:
|
case ItemRarity_Uncommon:
|
||||||
return itemType.Name(), tcell.StyleDefault.Foreground(tcell.ColorLime)
|
return randomAdjective() + " " + itemType.Name(), tcell.StyleDefault.Foreground(tcell.ColorLime)
|
||||||
case ItemRarity_Rare:
|
case ItemRarity_Rare:
|
||||||
return itemType.Name(), tcell.StyleDefault.Foreground(tcell.ColorBlue)
|
return itemType.Name() + " " + randomSuffix(), tcell.StyleDefault.Foreground(tcell.ColorBlue)
|
||||||
case ItemRarity_Epic:
|
case ItemRarity_Epic:
|
||||||
return itemType.Name(), tcell.StyleDefault.Foreground(tcell.ColorPurple)
|
return randomAdjective() + " " + itemType.Name() + " " + randomSuffix(), tcell.StyleDefault.Foreground(tcell.ColorPurple)
|
||||||
case ItemRarity_Legendary:
|
case ItemRarity_Legendary:
|
||||||
return itemType.Name(), tcell.StyleDefault.Foreground(tcell.ColorOrange).Attributes(tcell.AttrBold)
|
return generateUniqueItemName() + ", Legendary " + itemType.Name(), tcell.StyleDefault.Foreground(tcell.ColorOrange).Attributes(tcell.AttrBold)
|
||||||
default:
|
default:
|
||||||
return itemType.Name(), tcell.StyleDefault
|
return itemType.Name(), tcell.StyleDefault
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func randomStat() Stat {
|
func randomStat(metaItemTypes []RPGItemMetaType) Stat {
|
||||||
stats := []Stat{
|
stats := make(map[RPGItemMetaType][]Stat, 0)
|
||||||
|
|
||||||
|
stats[MetaItemType_Weapon] = []Stat{
|
||||||
Stat_Attributes_Strength,
|
Stat_Attributes_Strength,
|
||||||
Stat_Attributes_Dexterity,
|
Stat_Attributes_Dexterity,
|
||||||
Stat_Attributes_Intelligence,
|
Stat_Attributes_Intelligence,
|
||||||
Stat_Attributes_Constitution,
|
Stat_Attributes_Constitution,
|
||||||
Stat_PhysicalPrecisionBonus,
|
|
||||||
Stat_EvasionBonus,
|
|
||||||
Stat_MagicPrecisionBonus,
|
|
||||||
Stat_TotalPrecisionBonus,
|
Stat_TotalPrecisionBonus,
|
||||||
Stat_DamageBonus_Physical_Unarmed,
|
}
|
||||||
|
|
||||||
|
stats[MetaItemType_Physical_Weapon] = []Stat{
|
||||||
|
Stat_PhysicalPrecisionBonus,
|
||||||
Stat_DamageBonus_Physical_Slashing,
|
Stat_DamageBonus_Physical_Slashing,
|
||||||
Stat_DamageBonus_Physical_Piercing,
|
Stat_DamageBonus_Physical_Piercing,
|
||||||
Stat_DamageBonus_Physical_Bludgeoning,
|
Stat_DamageBonus_Physical_Bludgeoning,
|
||||||
|
@ -95,37 +157,102 @@ func randomStat() Stat {
|
||||||
Stat_DamageBonus_Magic_Thunder,
|
Stat_DamageBonus_Magic_Thunder,
|
||||||
Stat_DamageBonus_Magic_Acid,
|
Stat_DamageBonus_Magic_Acid,
|
||||||
Stat_DamageBonus_Magic_Poison,
|
Stat_DamageBonus_Magic_Poison,
|
||||||
|
}
|
||||||
|
|
||||||
|
stats[MetaItemType_Magic_Weapon] = []Stat{
|
||||||
|
Stat_MagicPrecisionBonus,
|
||||||
|
Stat_DamageBonus_Magic_Fire,
|
||||||
|
Stat_DamageBonus_Magic_Cold,
|
||||||
|
Stat_DamageBonus_Magic_Necrotic,
|
||||||
|
Stat_DamageBonus_Magic_Thunder,
|
||||||
|
Stat_DamageBonus_Magic_Acid,
|
||||||
|
Stat_DamageBonus_Magic_Poison,
|
||||||
|
}
|
||||||
|
|
||||||
|
stats[MetaItemType_Armour] = []Stat{
|
||||||
|
Stat_EvasionBonus,
|
||||||
|
Stat_DamageBonus_Physical_Unarmed,
|
||||||
Stat_MaxHealthBonus,
|
Stat_MaxHealthBonus,
|
||||||
}
|
}
|
||||||
|
|
||||||
return stats[rand.Intn(len(stats))]
|
stats[MetaItemType_Magic_Armour] = []Stat{
|
||||||
|
Stat_MagicPrecisionBonus,
|
||||||
|
Stat_DamageBonus_Magic_Fire,
|
||||||
|
Stat_DamageBonus_Magic_Cold,
|
||||||
|
Stat_DamageBonus_Magic_Necrotic,
|
||||||
|
Stat_DamageBonus_Magic_Thunder,
|
||||||
|
Stat_DamageBonus_Magic_Acid,
|
||||||
|
Stat_DamageBonus_Magic_Poison,
|
||||||
|
}
|
||||||
|
|
||||||
|
stats[MetaItemType_Physical_Armour] = []Stat{
|
||||||
|
Stat_PhysicalPrecisionBonus,
|
||||||
|
Stat_DamageBonus_Physical_Slashing,
|
||||||
|
Stat_DamageBonus_Physical_Piercing,
|
||||||
|
Stat_DamageBonus_Physical_Bludgeoning,
|
||||||
|
}
|
||||||
|
|
||||||
|
possibleStats := make([]Stat, 0, 10)
|
||||||
|
|
||||||
|
for _, mt := range metaItemTypes {
|
||||||
|
possibleStats = append(possibleStats, stats[mt]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return slices.Compact(possibleStats)[rand.Intn(len(stats))]
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateItemStatModifiers(rarity ItemRarity) []StatModifier {
|
func generateItemStatModifiers(itemType RPGItemType, rarity ItemRarity) []StatModifier {
|
||||||
points := pointPerRarity(rarity)
|
points := pointPerRarity(rarity)
|
||||||
modifiers := []StatModifier{}
|
modifiers := make(map[Stat]*StatModifier, 0)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if points <= 0 {
|
// If no points remain, or if the number of modifiers on the item reaches the maximum
|
||||||
|
if points <= 0 || len(modifiers) == MaxNumberOfModifiers {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
modAmount := engine.RandInt(-points/2, points)
|
// Random chance to increase or decrease a stat
|
||||||
|
modAmount := engine.RandInt(-points, points)
|
||||||
|
|
||||||
if modAmount == 0 {
|
if modAmount == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
modifiers = append(modifiers, StatModifier{
|
stat := randomStat(itemType.MetaTypes())
|
||||||
Id: StatModifierId(uuid.New().String()),
|
|
||||||
Stat: randomStat(),
|
|
||||||
Bonus: modAmount,
|
|
||||||
})
|
|
||||||
|
|
||||||
points -= modAmount
|
existingForStat := modifiers[stat]
|
||||||
|
|
||||||
|
// If this stat modifier already exists on the item, add the new modification amount to the old
|
||||||
|
if existingForStat != nil {
|
||||||
|
existingForStat.Bonus += modAmount
|
||||||
|
|
||||||
|
// If the added amount is 0, remove the modifier
|
||||||
|
if existingForStat.Bonus == 0 {
|
||||||
|
delete(modifiers, stat)
|
||||||
|
} else {
|
||||||
|
modifiers[stat] = existingForStat
|
||||||
}
|
}
|
||||||
|
|
||||||
return modifiers
|
} else {
|
||||||
|
// Otherwise, append a new stat modifier
|
||||||
|
modifiers[stat] = &StatModifier{
|
||||||
|
Id: StatModifierId(uuid.New().String()),
|
||||||
|
Stat: stat,
|
||||||
|
Bonus: modAmount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrease amount of points left by absolute value
|
||||||
|
points -= engine.AbsInt(modAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
vals := make([]StatModifier, 0, len(modifiers))
|
||||||
|
|
||||||
|
for _, v := range modifiers {
|
||||||
|
vals = append(vals, *v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return vals
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each rarity gets an amount of generation points, the higher the rarity, the more points
|
// Each rarity gets an amount of generation points, the higher the rarity, the more points
|
||||||
|
@ -138,6 +265,6 @@ func GenerateItemOfTypeAndRarity(itemType RPGItemType, rarity ItemRarity) RPGIte
|
||||||
name,
|
name,
|
||||||
style,
|
style,
|
||||||
itemType,
|
itemType,
|
||||||
generateItemStatModifiers(rarity),
|
generateItemStatModifiers(itemType, rarity),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,10 @@ type BasicRPGEntity struct {
|
||||||
currentHealth int
|
currentHealth int
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateBasicRPGEntity() *BasicRPGEntity {
|
func CreateBasicRPGEntity(baseStats map[Stat]int, statModifiers map[Stat][]StatModifier) *BasicRPGEntity {
|
||||||
return &BasicRPGEntity{
|
return &BasicRPGEntity{
|
||||||
stats: make(map[Stat]int, 0),
|
stats: baseStats,
|
||||||
statModifiers: make(map[Stat][]StatModifier, 0),
|
statModifiers: statModifiers,
|
||||||
currentHealth: 0,
|
currentHealth: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ func (brpg *BasicRPGEntity) AddStatModifier(modifier StatModifier) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (brpg *BasicRPGEntity) RemoveStatModifier(id StatModifierId) {
|
func (brpg *BasicRPGEntity) RemoveStatModifier(id StatModifierId) {
|
||||||
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
func (brpg *BasicRPGEntity) CurrentHealth() int {
|
func (brpg *BasicRPGEntity) CurrentHealth() int {
|
||||||
|
|
|
@ -6,8 +6,20 @@ import (
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type RPGItemMetaType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
MetaItemType_Physical_Weapon RPGItemMetaType = iota
|
||||||
|
MetaItemType_Magic_Weapon
|
||||||
|
MetaItemType_Weapon
|
||||||
|
MetaItemType_Physical_Armour
|
||||||
|
MetaItemType_Magic_Armour
|
||||||
|
MetaItemType_Armour
|
||||||
|
)
|
||||||
|
|
||||||
type RPGItemType interface {
|
type RPGItemType interface {
|
||||||
RollDamage() func(victim, attacker RPGEntity) (damage int, dmgType DamageType)
|
RollDamage() func(victim, attacker RPGEntity) (damage int, dmgType DamageType)
|
||||||
|
MetaTypes() []RPGItemMetaType
|
||||||
|
|
||||||
item.ItemType
|
item.ItemType
|
||||||
}
|
}
|
||||||
|
@ -21,6 +33,8 @@ type RPGItem interface {
|
||||||
type BasicRPGItemType struct {
|
type BasicRPGItemType struct {
|
||||||
damageRollFunc func(victim, attacker RPGEntity) (damage int, dmgType DamageType)
|
damageRollFunc func(victim, attacker RPGEntity) (damage int, dmgType DamageType)
|
||||||
|
|
||||||
|
metaTypes []RPGItemMetaType
|
||||||
|
|
||||||
*item.BasicItemType
|
*item.BasicItemType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,13 +42,19 @@ func (it *BasicRPGItemType) RollDamage() func(victim, attacker RPGEntity) (damag
|
||||||
return it.damageRollFunc
|
return it.damageRollFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (it *BasicRPGItemType) MetaTypes() []RPGItemMetaType {
|
||||||
|
return it.metaTypes
|
||||||
|
}
|
||||||
|
|
||||||
func ItemTypeBow() RPGItemType {
|
func ItemTypeBow() RPGItemType {
|
||||||
return &BasicRPGItemType{
|
return &BasicRPGItemType{
|
||||||
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
||||||
// TODO: Ranged
|
// TODO: Ranged
|
||||||
return RollD8(1), DamageType_Physical_Piercing
|
return RollD8(1), DamageType_Physical_Piercing
|
||||||
},
|
},
|
||||||
|
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
||||||
BasicItemType: item.CreateBasicItemType(
|
BasicItemType: item.CreateBasicItemType(
|
||||||
|
1000,
|
||||||
"Bow",
|
"Bow",
|
||||||
"To shoot arrows with",
|
"To shoot arrows with",
|
||||||
')',
|
')',
|
||||||
|
@ -51,7 +71,9 @@ func ItemTypeLongsword() RPGItemType {
|
||||||
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
||||||
return RollD8(1), DamageType_Physical_Slashing
|
return RollD8(1), DamageType_Physical_Slashing
|
||||||
},
|
},
|
||||||
|
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
||||||
BasicItemType: item.CreateBasicItemType(
|
BasicItemType: item.CreateBasicItemType(
|
||||||
|
1001,
|
||||||
"Longsword",
|
"Longsword",
|
||||||
"You know nothing.",
|
"You know nothing.",
|
||||||
'/',
|
'/',
|
||||||
|
@ -68,7 +90,9 @@ func ItemTypeClub() RPGItemType {
|
||||||
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
||||||
return RollD8(1), DamageType_Physical_Bludgeoning
|
return RollD8(1), DamageType_Physical_Bludgeoning
|
||||||
},
|
},
|
||||||
|
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
||||||
BasicItemType: item.CreateBasicItemType(
|
BasicItemType: item.CreateBasicItemType(
|
||||||
|
1002,
|
||||||
"Club",
|
"Club",
|
||||||
"Bonk",
|
"Bonk",
|
||||||
'!',
|
'!',
|
||||||
|
@ -85,7 +109,9 @@ func ItemTypeDagger() RPGItemType {
|
||||||
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
||||||
return RollD6(1), DamageType_Physical_Piercing
|
return RollD6(1), DamageType_Physical_Piercing
|
||||||
},
|
},
|
||||||
|
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
||||||
BasicItemType: item.CreateBasicItemType(
|
BasicItemType: item.CreateBasicItemType(
|
||||||
|
1003,
|
||||||
"Dagger",
|
"Dagger",
|
||||||
"Stabby, stabby",
|
"Stabby, stabby",
|
||||||
'-',
|
'-',
|
||||||
|
@ -102,7 +128,9 @@ func ItemTypeHandaxe() RPGItemType {
|
||||||
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
||||||
return RollD6(1), DamageType_Physical_Slashing
|
return RollD6(1), DamageType_Physical_Slashing
|
||||||
},
|
},
|
||||||
|
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
||||||
BasicItemType: item.CreateBasicItemType(
|
BasicItemType: item.CreateBasicItemType(
|
||||||
|
1004,
|
||||||
"Handaxe",
|
"Handaxe",
|
||||||
"Choppy, choppy",
|
"Choppy, choppy",
|
||||||
'¶',
|
'¶',
|
||||||
|
@ -120,7 +148,9 @@ func ItemTypeJavelin() RPGItemType {
|
||||||
// TODO: Ranged
|
// TODO: Ranged
|
||||||
return RollD6(1), DamageType_Physical_Piercing
|
return RollD6(1), DamageType_Physical_Piercing
|
||||||
},
|
},
|
||||||
|
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
||||||
BasicItemType: item.CreateBasicItemType(
|
BasicItemType: item.CreateBasicItemType(
|
||||||
|
1005,
|
||||||
"Javelin",
|
"Javelin",
|
||||||
"Ranged pokey, pokey",
|
"Ranged pokey, pokey",
|
||||||
'Î',
|
'Î',
|
||||||
|
@ -137,7 +167,9 @@ func ItemTypeLightHammer() RPGItemType {
|
||||||
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
||||||
return RollD6(1), DamageType_Physical_Bludgeoning
|
return RollD6(1), DamageType_Physical_Bludgeoning
|
||||||
},
|
},
|
||||||
|
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
||||||
BasicItemType: item.CreateBasicItemType(
|
BasicItemType: item.CreateBasicItemType(
|
||||||
|
1006,
|
||||||
"Handaxe",
|
"Handaxe",
|
||||||
"Choppy, choppy",
|
"Choppy, choppy",
|
||||||
'¶',
|
'¶',
|
||||||
|
@ -154,7 +186,9 @@ func ItemTypeMace() RPGItemType {
|
||||||
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
||||||
return RollD6(1), DamageType_Physical_Bludgeoning
|
return RollD6(1), DamageType_Physical_Bludgeoning
|
||||||
},
|
},
|
||||||
|
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
||||||
BasicItemType: item.CreateBasicItemType(
|
BasicItemType: item.CreateBasicItemType(
|
||||||
|
1007,
|
||||||
"Mace",
|
"Mace",
|
||||||
"Smashey, smashey",
|
"Smashey, smashey",
|
||||||
'i',
|
'i',
|
||||||
|
@ -172,7 +206,9 @@ func ItemTypeQuarterstaff() RPGItemType {
|
||||||
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
||||||
return RollD6(1), DamageType_Physical_Bludgeoning
|
return RollD6(1), DamageType_Physical_Bludgeoning
|
||||||
},
|
},
|
||||||
|
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
||||||
BasicItemType: item.CreateBasicItemType(
|
BasicItemType: item.CreateBasicItemType(
|
||||||
|
1008,
|
||||||
"Quarterstaff",
|
"Quarterstaff",
|
||||||
"Whacky, whacky",
|
"Whacky, whacky",
|
||||||
'|',
|
'|',
|
||||||
|
@ -189,7 +225,9 @@ func ItemTypeSickle() RPGItemType {
|
||||||
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
||||||
return RollD6(1), DamageType_Physical_Slashing
|
return RollD6(1), DamageType_Physical_Slashing
|
||||||
},
|
},
|
||||||
|
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
||||||
BasicItemType: item.CreateBasicItemType(
|
BasicItemType: item.CreateBasicItemType(
|
||||||
|
1009,
|
||||||
"Sickle",
|
"Sickle",
|
||||||
"Slicey, slicey?",
|
"Slicey, slicey?",
|
||||||
'?',
|
'?',
|
||||||
|
@ -206,7 +244,9 @@ func ItemTypeSpear() RPGItemType {
|
||||||
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
||||||
return RollD8(1), DamageType_Physical_Piercing
|
return RollD8(1), DamageType_Physical_Piercing
|
||||||
},
|
},
|
||||||
|
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
||||||
BasicItemType: item.CreateBasicItemType(
|
BasicItemType: item.CreateBasicItemType(
|
||||||
|
1010,
|
||||||
"Spear",
|
"Spear",
|
||||||
"Pokey, pokey",
|
"Pokey, pokey",
|
||||||
'Î',
|
'Î',
|
||||||
|
|
|
@ -160,9 +160,9 @@ func StatValue(entity RPGEntity, stat Stat) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Base Max Health is determined from constitution:
|
// Base Max Health is determined from constitution:
|
||||||
// Constitution + Max Health Bonus + 10
|
// 5*Constitution + Max Health Bonus
|
||||||
func BaseMaxHealth(entity RPGEntity) int {
|
func BaseMaxHealth(entity RPGEntity) int {
|
||||||
return StatValue(entity, Stat_Attributes_Constitution) + StatValue(entity, Stat_MaxHealthBonus) + 10
|
return 5*StatValue(entity, Stat_Attributes_Constitution) + StatValue(entity, Stat_MaxHealthBonus)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dexterity + Evasion bonus + luck roll
|
// Dexterity + Evasion bonus + luck roll
|
||||||
|
@ -191,18 +191,6 @@ func PhysicalHitRoll(attacker RPGEntity, victim RPGEntity) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func hitRoll(evasionRoll, precisionRoll int) bool {
|
func hitRoll(evasionRoll, precisionRoll int) bool {
|
||||||
if evasionRoll == 20 && precisionRoll == 20 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if evasionRoll == 20 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if precisionRoll == 20 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return evasionRoll < precisionRoll
|
return evasionRoll < precisionRoll
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,40 +2,38 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
|
"mvvasilev/last_light/game/input"
|
||||||
|
"mvvasilev/last_light/game/turns"
|
||||||
"mvvasilev/last_light/game/ui"
|
"mvvasilev/last_light/game/ui"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DialogState struct {
|
type DialogState struct {
|
||||||
|
inputSystem *input.InputSystem
|
||||||
|
turnSystem *turns.TurnSystem
|
||||||
|
|
||||||
prevState GameState
|
prevState GameState
|
||||||
|
|
||||||
dialog *ui.UIDialog
|
dialog *ui.UIDialog
|
||||||
|
|
||||||
selectDialog bool
|
|
||||||
returnToPreviousState bool
|
returnToPreviousState bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateDialogState(dialog *ui.UIDialog, prevState GameState) *DialogState {
|
func CreateDialogState(inputSystem *input.InputSystem, turnSystem *turns.TurnSystem, dialog *ui.UIDialog, prevState GameState) *DialogState {
|
||||||
return &DialogState{
|
return &DialogState{
|
||||||
|
inputSystem: inputSystem,
|
||||||
|
turnSystem: turnSystem,
|
||||||
prevState: prevState,
|
prevState: prevState,
|
||||||
dialog: dialog,
|
dialog: dialog,
|
||||||
returnToPreviousState: false,
|
returnToPreviousState: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ds *DialogState) OnInput(e *tcell.EventKey) {
|
func (s *DialogState) InputContext() input.Context {
|
||||||
if e.Key() == tcell.KeyEnter {
|
return input.InputContext_Menu
|
||||||
ds.selectDialog = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ds.dialog.Input(e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ds *DialogState) OnTick(dt int64) GameState {
|
func (ds *DialogState) OnTick(dt int64) GameState {
|
||||||
if ds.selectDialog {
|
if ds.inputSystem.NextAction() == input.InputAction_Menu_Select {
|
||||||
ds.selectDialog = false
|
|
||||||
ds.returnToPreviousState = true
|
ds.returnToPreviousState = true
|
||||||
ds.dialog.Select()
|
ds.dialog.Select()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,20 +2,11 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
|
"mvvasilev/last_light/game/input"
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type GameState interface {
|
type GameState interface {
|
||||||
OnInput(e *tcell.EventKey)
|
InputContext() input.Context
|
||||||
OnTick(dt int64) GameState
|
OnTick(dt int64) GameState
|
||||||
CollectDrawables() []engine.Drawable
|
CollectDrawables() []engine.Drawable
|
||||||
}
|
}
|
||||||
|
|
||||||
type PausableState interface {
|
|
||||||
Pause()
|
|
||||||
Unpause()
|
|
||||||
SetPaused(paused bool)
|
|
||||||
|
|
||||||
GameState
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,29 +2,32 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/model"
|
"mvvasilev/last_light/game/input"
|
||||||
"mvvasilev/last_light/game/player"
|
"mvvasilev/last_light/game/player"
|
||||||
|
"mvvasilev/last_light/game/turns"
|
||||||
"mvvasilev/last_light/game/ui/menu"
|
"mvvasilev/last_light/game/ui/menu"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InventoryScreenState struct {
|
type InventoryScreenState struct {
|
||||||
prevState PausableState
|
inputSystem *input.InputSystem
|
||||||
|
turnSystem *turns.TurnSystem
|
||||||
|
|
||||||
|
prevState GameState
|
||||||
exitMenu bool
|
exitMenu bool
|
||||||
|
|
||||||
inventoryMenu *menu.PlayerInventoryMenu
|
inventoryMenu *menu.PlayerInventoryMenu
|
||||||
selectedInventorySlot engine.Position
|
selectedInventorySlot engine.Position
|
||||||
|
|
||||||
player *player.Player
|
player *player.Player
|
||||||
|
|
||||||
moveInventorySlotDirection model.Direction
|
|
||||||
dropSelectedInventorySlot bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateInventoryScreenState(player *player.Player, prevState PausableState) *InventoryScreenState {
|
func CreateInventoryScreenState(inputSystem *input.InputSystem, turnSystem *turns.TurnSystem, player *player.Player, prevState GameState) *InventoryScreenState {
|
||||||
iss := new(InventoryScreenState)
|
iss := new(InventoryScreenState)
|
||||||
|
|
||||||
|
iss.inputSystem = inputSystem
|
||||||
|
iss.turnSystem = turnSystem
|
||||||
iss.prevState = prevState
|
iss.prevState = prevState
|
||||||
iss.player = player
|
iss.player = player
|
||||||
iss.selectedInventorySlot = engine.PositionAt(0, 0)
|
iss.selectedInventorySlot = engine.PositionAt(0, 0)
|
||||||
|
@ -34,76 +37,50 @@ func CreateInventoryScreenState(player *player.Player, prevState PausableState)
|
||||||
return iss
|
return iss
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iss *InventoryScreenState) OnInput(e *tcell.EventKey) {
|
func (s *InventoryScreenState) InputContext() input.Context {
|
||||||
if e.Key() == tcell.KeyEsc || (e.Key() == tcell.KeyRune && e.Rune() == 'i') {
|
return input.InputContext_Inventory
|
||||||
iss.exitMenu = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Key() == tcell.KeyRune && e.Rune() == 'x' {
|
|
||||||
iss.dropSelectedInventorySlot = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Key() != tcell.KeyRune {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch e.Rune() {
|
|
||||||
case 'k':
|
|
||||||
iss.moveInventorySlotDirection = model.DirectionUp
|
|
||||||
case 'j':
|
|
||||||
iss.moveInventorySlotDirection = model.DirectionDown
|
|
||||||
case 'h':
|
|
||||||
iss.moveInventorySlotDirection = model.DirectionLeft
|
|
||||||
case 'l':
|
|
||||||
iss.moveInventorySlotDirection = model.DirectionRight
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iss *InventoryScreenState) OnTick(dt int64) GameState {
|
func (iss *InventoryScreenState) OnTick(dt int64) (nextState GameState) {
|
||||||
if iss.exitMenu {
|
nextAction := iss.inputSystem.NextAction()
|
||||||
iss.prevState.Unpause()
|
nextState = iss
|
||||||
return iss.prevState
|
|
||||||
}
|
|
||||||
|
|
||||||
if iss.dropSelectedInventorySlot {
|
switch nextAction {
|
||||||
|
case input.InputAction_Menu_Exit:
|
||||||
|
nextState = iss.prevState
|
||||||
|
case input.InputAction_DropItem:
|
||||||
iss.player.Inventory().Drop(iss.selectedInventorySlot.XY())
|
iss.player.Inventory().Drop(iss.selectedInventorySlot.XY())
|
||||||
iss.dropSelectedInventorySlot = false
|
case input.InputAction_Menu_HighlightUp:
|
||||||
}
|
|
||||||
|
|
||||||
if iss.moveInventorySlotDirection != model.DirectionNone {
|
|
||||||
|
|
||||||
switch iss.moveInventorySlotDirection {
|
|
||||||
case model.DirectionUp:
|
|
||||||
if iss.selectedInventorySlot.Y() == 0 {
|
if iss.selectedInventorySlot.Y() == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(0, -1)
|
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(0, -1)
|
||||||
case model.DirectionDown:
|
iss.inventoryMenu.SelectSlot(iss.selectedInventorySlot.XY())
|
||||||
|
case input.InputAction_Menu_HighlightDown:
|
||||||
if iss.selectedInventorySlot.Y() == iss.player.Inventory().Shape().Height()-1 {
|
if iss.selectedInventorySlot.Y() == iss.player.Inventory().Shape().Height()-1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(0, +1)
|
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(0, +1)
|
||||||
case model.DirectionLeft:
|
iss.inventoryMenu.SelectSlot(iss.selectedInventorySlot.XY())
|
||||||
|
case input.InputAction_Menu_HighlightLeft:
|
||||||
if iss.selectedInventorySlot.X() == 0 {
|
if iss.selectedInventorySlot.X() == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(-1, 0)
|
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(-1, 0)
|
||||||
case model.DirectionRight:
|
iss.inventoryMenu.SelectSlot(iss.selectedInventorySlot.XY())
|
||||||
|
case input.InputAction_Menu_HighlightRight:
|
||||||
if iss.selectedInventorySlot.X() == iss.player.Inventory().Shape().Width()-1 {
|
if iss.selectedInventorySlot.X() == iss.player.Inventory().Shape().Width()-1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(+1, 0)
|
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(+1, 0)
|
||||||
}
|
|
||||||
|
|
||||||
iss.inventoryMenu.SelectSlot(iss.selectedInventorySlot.XY())
|
iss.inventoryMenu.SelectSlot(iss.selectedInventorySlot.XY())
|
||||||
iss.moveInventorySlotDirection = model.DirectionNone
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return iss
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iss *InventoryScreenState) CollectDrawables() []engine.Drawable {
|
func (iss *InventoryScreenState) CollectDrawables() []engine.Drawable {
|
||||||
|
|
|
@ -2,12 +2,17 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
|
"mvvasilev/last_light/game/input"
|
||||||
|
"mvvasilev/last_light/game/turns"
|
||||||
"mvvasilev/last_light/game/ui"
|
"mvvasilev/last_light/game/ui"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MainMenuState struct {
|
type MainMenuState struct {
|
||||||
|
turnSystem *turns.TurnSystem
|
||||||
|
inputSystem *input.InputSystem
|
||||||
|
|
||||||
menuTitle *engine.Raw
|
menuTitle *engine.Raw
|
||||||
buttons []*ui.UISimpleButton
|
buttons []*ui.UISimpleButton
|
||||||
currButtonSelected int
|
currButtonSelected int
|
||||||
|
@ -16,11 +21,16 @@ type MainMenuState struct {
|
||||||
startNewGame bool
|
startNewGame bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMainMenuState() *MainMenuState {
|
func CreateMainMenuState(turnSystem *turns.TurnSystem, inputSystem *input.InputSystem) *MainMenuState {
|
||||||
|
turnSystem.Clear()
|
||||||
|
|
||||||
state := new(MainMenuState)
|
state := new(MainMenuState)
|
||||||
|
|
||||||
highlightStyle := tcell.StyleDefault.Attributes(tcell.AttrBold)
|
highlightStyle := tcell.StyleDefault.Attributes(tcell.AttrBold)
|
||||||
|
|
||||||
|
state.turnSystem = turnSystem
|
||||||
|
state.inputSystem = inputSystem
|
||||||
|
|
||||||
state.menuTitle = engine.CreateRawDrawable(
|
state.menuTitle = engine.CreateRawDrawable(
|
||||||
11, 1, tcell.StyleDefault.Attributes(tcell.AttrBold).Foreground(tcell.ColorYellow),
|
11, 1, tcell.StyleDefault.Attributes(tcell.AttrBold).Foreground(tcell.ColorYellow),
|
||||||
" | | | _) | | ",
|
" | | | _) | | ",
|
||||||
|
@ -46,31 +56,35 @@ func NewMainMenuState() *MainMenuState {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mms *MainMenuState) OnInput(e *tcell.EventKey) {
|
func (s *MainMenuState) InputContext() input.Context {
|
||||||
if e.Key() == tcell.KeyDown {
|
return input.InputContext_Menu
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mms *MainMenuState) OnTick(dt int64) GameState {
|
||||||
|
nextAction := mms.inputSystem.NextAction()
|
||||||
|
|
||||||
|
if nextAction == input.InputAction_Menu_HighlightDown {
|
||||||
mms.buttons[mms.currButtonSelected].Unhighlight()
|
mms.buttons[mms.currButtonSelected].Unhighlight()
|
||||||
mms.currButtonSelected = engine.LimitIncrement(mms.currButtonSelected, 2)
|
mms.currButtonSelected = engine.LimitIncrement(mms.currButtonSelected, 2)
|
||||||
mms.buttons[mms.currButtonSelected].Highlight()
|
mms.buttons[mms.currButtonSelected].Highlight()
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Key() == tcell.KeyUp {
|
if nextAction == input.InputAction_Menu_HighlightUp {
|
||||||
mms.buttons[mms.currButtonSelected].Unhighlight()
|
mms.buttons[mms.currButtonSelected].Unhighlight()
|
||||||
mms.currButtonSelected = engine.LimitDecrement(mms.currButtonSelected, 0)
|
mms.currButtonSelected = engine.LimitDecrement(mms.currButtonSelected, 0)
|
||||||
mms.buttons[mms.currButtonSelected].Highlight()
|
mms.buttons[mms.currButtonSelected].Highlight()
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Key() == tcell.KeyEnter {
|
if nextAction == input.InputAction_Menu_Select {
|
||||||
mms.buttons[mms.currButtonSelected].Select()
|
mms.buttons[mms.currButtonSelected].Select()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (mms *MainMenuState) OnTick(dt int64) GameState {
|
|
||||||
if mms.quitGame {
|
if mms.quitGame {
|
||||||
return &QuitState{}
|
return &QuitState{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if mms.startNewGame {
|
if mms.startNewGame {
|
||||||
return BeginPlayingState()
|
return CreatePlayingState(mms.turnSystem, mms.inputSystem)
|
||||||
}
|
}
|
||||||
|
|
||||||
return mms
|
return mms
|
||||||
|
|
|
@ -2,13 +2,18 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
|
"mvvasilev/last_light/game/input"
|
||||||
|
"mvvasilev/last_light/game/turns"
|
||||||
"mvvasilev/last_light/game/ui"
|
"mvvasilev/last_light/game/ui"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PauseGameState struct {
|
type PauseGameState struct {
|
||||||
prevState PausableState
|
turnSystem *turns.TurnSystem
|
||||||
|
inputSystem *input.InputSystem
|
||||||
|
|
||||||
|
prevState GameState
|
||||||
|
|
||||||
unpauseGame bool
|
unpauseGame bool
|
||||||
returnToMainMenu bool
|
returnToMainMenu bool
|
||||||
|
@ -18,9 +23,11 @@ type PauseGameState struct {
|
||||||
currButtonSelected int
|
currButtonSelected int
|
||||||
}
|
}
|
||||||
|
|
||||||
func PauseGame(prevState PausableState) *PauseGameState {
|
func PauseGame(prevState GameState, turnSystem *turns.TurnSystem, inputSystem *input.InputSystem) *PauseGameState {
|
||||||
s := new(PauseGameState)
|
s := new(PauseGameState)
|
||||||
|
|
||||||
|
s.turnSystem = turnSystem
|
||||||
|
s.inputSystem = inputSystem
|
||||||
s.prevState = prevState
|
s.prevState = prevState
|
||||||
|
|
||||||
highlightStyle := tcell.StyleDefault.Attributes(tcell.AttrBold)
|
highlightStyle := tcell.StyleDefault.Attributes(tcell.AttrBold)
|
||||||
|
@ -60,36 +67,32 @@ func PauseGame(prevState PausableState) *PauseGameState {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pg *PauseGameState) OnInput(e *tcell.EventKey) {
|
func (s *PauseGameState) InputContext() input.Context {
|
||||||
if e.Key() == tcell.KeyEsc {
|
return input.InputContext_Menu
|
||||||
pg.unpauseGame = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Key() == tcell.KeyDown {
|
|
||||||
pg.buttons[pg.currButtonSelected].Unhighlight()
|
|
||||||
pg.currButtonSelected = engine.LimitIncrement(pg.currButtonSelected, 1)
|
|
||||||
pg.buttons[pg.currButtonSelected].Highlight()
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Key() == tcell.KeyUp {
|
|
||||||
pg.buttons[pg.currButtonSelected].Unhighlight()
|
|
||||||
pg.currButtonSelected = engine.LimitDecrement(pg.currButtonSelected, 0)
|
|
||||||
pg.buttons[pg.currButtonSelected].Highlight()
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Key() == tcell.KeyEnter {
|
|
||||||
pg.buttons[pg.currButtonSelected].Select()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pg *PauseGameState) OnTick(dt int64) GameState {
|
func (pg *PauseGameState) OnTick(dt int64) GameState {
|
||||||
|
switch pg.inputSystem.NextAction() {
|
||||||
|
case input.InputAction_Menu_Exit:
|
||||||
|
pg.unpauseGame = true
|
||||||
|
case input.InputAction_Menu_HighlightDown:
|
||||||
|
pg.buttons[pg.currButtonSelected].Unhighlight()
|
||||||
|
pg.currButtonSelected = engine.LimitIncrement(pg.currButtonSelected, 1)
|
||||||
|
pg.buttons[pg.currButtonSelected].Highlight()
|
||||||
|
case input.InputAction_Menu_HighlightUp:
|
||||||
|
pg.buttons[pg.currButtonSelected].Unhighlight()
|
||||||
|
pg.currButtonSelected = engine.LimitDecrement(pg.currButtonSelected, 0)
|
||||||
|
pg.buttons[pg.currButtonSelected].Highlight()
|
||||||
|
case input.InputAction_Menu_Select:
|
||||||
|
pg.buttons[pg.currButtonSelected].Select()
|
||||||
|
}
|
||||||
|
|
||||||
if pg.unpauseGame {
|
if pg.unpauseGame {
|
||||||
pg.prevState.Unpause()
|
|
||||||
return pg.prevState
|
return pg.prevState
|
||||||
}
|
}
|
||||||
|
|
||||||
if pg.returnToMainMenu {
|
if pg.returnToMainMenu {
|
||||||
return NewMainMenuState()
|
return CreateMainMenuState(pg.turnSystem, pg.inputSystem)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pg
|
return pg
|
||||||
|
|
|
@ -2,8 +2,11 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/model"
|
"mvvasilev/last_light/game/input"
|
||||||
|
"mvvasilev/last_light/game/npc"
|
||||||
"mvvasilev/last_light/game/player"
|
"mvvasilev/last_light/game/player"
|
||||||
|
"mvvasilev/last_light/game/rpg"
|
||||||
|
"mvvasilev/last_light/game/turns"
|
||||||
"mvvasilev/last_light/game/ui"
|
"mvvasilev/last_light/game/ui"
|
||||||
"mvvasilev/last_light/game/world"
|
"mvvasilev/last_light/game/world"
|
||||||
|
|
||||||
|
@ -12,33 +15,88 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type PlayingState struct {
|
type PlayingState struct {
|
||||||
|
turnSystem *turns.TurnSystem
|
||||||
|
inputSystem *input.InputSystem
|
||||||
|
|
||||||
player *player.Player
|
player *player.Player
|
||||||
someNPC *model.BasicNPC
|
someNPC *npc.BasicNPC
|
||||||
|
|
||||||
|
eventLog *engine.GameEventLog
|
||||||
|
uiEventLog *ui.UIEventLog
|
||||||
|
|
||||||
|
healthBar *ui.UIHealthBar
|
||||||
|
|
||||||
dungeon *world.Dungeon
|
dungeon *world.Dungeon
|
||||||
|
|
||||||
viewport *engine.Viewport
|
viewport *engine.Viewport
|
||||||
|
|
||||||
movePlayerDirection model.Direction
|
viewShortLogs bool
|
||||||
pauseGame bool
|
|
||||||
openInventory bool
|
|
||||||
pickUpUnderPlayer bool
|
|
||||||
interact bool
|
|
||||||
moveEntities bool
|
|
||||||
|
|
||||||
nextGameState GameState
|
nextGameState GameState
|
||||||
}
|
}
|
||||||
|
|
||||||
func BeginPlayingState() *PlayingState {
|
func CreatePlayingState(turnSystem *turns.TurnSystem, inputSystem *input.InputSystem) *PlayingState {
|
||||||
|
turnSystem.Clear()
|
||||||
|
|
||||||
s := new(PlayingState)
|
s := new(PlayingState)
|
||||||
|
|
||||||
|
s.turnSystem = turnSystem
|
||||||
|
s.inputSystem = inputSystem
|
||||||
|
|
||||||
mapSize := engine.SizeOf(128, 128)
|
mapSize := engine.SizeOf(128, 128)
|
||||||
|
|
||||||
s.dungeon = world.CreateDungeon(mapSize.Width(), mapSize.Height(), 1)
|
s.dungeon = world.CreateDungeon(mapSize.Width(), mapSize.Height(), 1)
|
||||||
|
|
||||||
s.player = player.CreatePlayer(s.dungeon.CurrentLevel().PlayerSpawnPoint().XY())
|
s.player = player.CreatePlayer(s.dungeon.CurrentLevel().PlayerSpawnPoint().XY())
|
||||||
|
s.player.Heal(rpg.BaseMaxHealth(s.player))
|
||||||
|
|
||||||
s.someNPC = model.CreateNPC(s.dungeon.CurrentLevel().NextLevelStaircase())
|
s.turnSystem.Schedule(10, func() (complete bool, requeue bool) {
|
||||||
|
requeue = true
|
||||||
|
complete = false
|
||||||
|
|
||||||
|
switch inputSystem.NextAction() {
|
||||||
|
case input.InputAction_PauseGame:
|
||||||
|
s.nextGameState = PauseGame(s, s.turnSystem, s.inputSystem)
|
||||||
|
case input.InputAction_OpenInventory:
|
||||||
|
s.nextGameState = CreateInventoryScreenState(s.inputSystem, s.turnSystem, s.player, s)
|
||||||
|
case input.InputAction_PickUpItem:
|
||||||
|
s.PickUpItemUnderPlayer()
|
||||||
|
complete = true
|
||||||
|
case input.InputAction_Interact:
|
||||||
|
s.InteractBelowPlayer()
|
||||||
|
complete = true
|
||||||
|
case input.InputAction_OpenLogs:
|
||||||
|
s.viewShortLogs = !s.viewShortLogs
|
||||||
|
case input.InputAction_MovePlayer_East:
|
||||||
|
s.MovePlayer(npc.East)
|
||||||
|
complete = true
|
||||||
|
case input.InputAction_MovePlayer_West:
|
||||||
|
s.MovePlayer(npc.West)
|
||||||
|
complete = true
|
||||||
|
case input.InputAction_MovePlayer_North:
|
||||||
|
s.MovePlayer(npc.North)
|
||||||
|
complete = true
|
||||||
|
case input.InputAction_MovePlayer_South:
|
||||||
|
s.MovePlayer(npc.South)
|
||||||
|
complete = true
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
})
|
||||||
|
|
||||||
|
s.someNPC = npc.CreateNPC(s.dungeon.CurrentLevel().NextLevelStaircase())
|
||||||
|
|
||||||
|
s.turnSystem.Schedule(20, func() (complete bool, requeue bool) {
|
||||||
|
s.CalcPathToPlayerAndMove()
|
||||||
|
|
||||||
|
return true, true
|
||||||
|
})
|
||||||
|
|
||||||
|
s.eventLog = engine.CreateGameEventLog(100)
|
||||||
|
|
||||||
|
s.uiEventLog = ui.CreateUIEventLog(0, 17, 80, 7, s.eventLog, tcell.StyleDefault)
|
||||||
|
s.healthBar = ui.CreateHealthBar(68, 0, 12, 3, s.player.CurrentHealth(), rpg.BaseMaxHealth(s.player), tcell.StyleDefault)
|
||||||
|
|
||||||
s.dungeon.CurrentLevel().AddEntity(s.player, '@', tcell.StyleDefault)
|
s.dungeon.CurrentLevel().AddEntity(s.player, '@', tcell.StyleDefault)
|
||||||
s.dungeon.CurrentLevel().AddEntity(s.someNPC, 'N', tcell.StyleDefault)
|
s.dungeon.CurrentLevel().AddEntity(s.someNPC, 'N', tcell.StyleDefault)
|
||||||
|
@ -55,32 +113,24 @@ func BeginPlayingState() *PlayingState {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *PlayingState) Pause() {
|
func (s *PlayingState) InputContext() input.Context {
|
||||||
ps.pauseGame = true
|
return input.InputContext_Play
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *PlayingState) Unpause() {
|
func (ps *PlayingState) MovePlayer(direction npc.Direction) {
|
||||||
ps.pauseGame = false
|
if direction == npc.DirectionNone {
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *PlayingState) SetPaused(paused bool) {
|
|
||||||
ps.pauseGame = paused
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *PlayingState) MovePlayer() {
|
|
||||||
if ps.movePlayerDirection == model.DirectionNone {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newPlayerPos := ps.player.Position().WithOffset(model.MovementDirectionOffset(ps.movePlayerDirection))
|
newPlayerPos := ps.player.Position().WithOffset(npc.MovementDirectionOffset(direction))
|
||||||
|
|
||||||
if ps.dungeon.CurrentLevel().IsTilePassable(newPlayerPos.XY()) {
|
if ps.dungeon.CurrentLevel().IsTilePassable(newPlayerPos.XY()) {
|
||||||
dx, dy := model.MovementDirectionOffset(ps.movePlayerDirection)
|
dx, dy := npc.MovementDirectionOffset(direction)
|
||||||
ps.dungeon.CurrentLevel().MoveEntity(ps.player.UniqueId(), dx, dy)
|
ps.dungeon.CurrentLevel().MoveEntity(ps.player.UniqueId(), dx, dy)
|
||||||
ps.viewport.SetCenter(ps.player.Position())
|
ps.viewport.SetCenter(ps.player.Position())
|
||||||
}
|
}
|
||||||
|
|
||||||
ps.movePlayerDirection = model.DirectionNone
|
ps.eventLog.Log("You moved " + npc.DirectionName(direction))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *PlayingState) InteractBelowPlayer() {
|
func (ps *PlayingState) InteractBelowPlayer() {
|
||||||
|
@ -100,6 +150,8 @@ func (ps *PlayingState) InteractBelowPlayer() {
|
||||||
func (ps *PlayingState) SwitchToNextLevel() {
|
func (ps *PlayingState) SwitchToNextLevel() {
|
||||||
if !ps.dungeon.HasNextLevel() {
|
if !ps.dungeon.HasNextLevel() {
|
||||||
ps.nextGameState = CreateDialogState(
|
ps.nextGameState = CreateDialogState(
|
||||||
|
ps.inputSystem,
|
||||||
|
ps.turnSystem,
|
||||||
ui.CreateOkDialog(
|
ui.CreateOkDialog(
|
||||||
"The Unknown Depths",
|
"The Unknown Depths",
|
||||||
"The staircases descent down to the lower levels is seemingly blocked by multiple large boulders. They appear immovable.",
|
"The staircases descent down to the lower levels is seemingly blocked by multiple large boulders. They appear immovable.",
|
||||||
|
@ -134,6 +186,8 @@ func (ps *PlayingState) SwitchToNextLevel() {
|
||||||
func (ps *PlayingState) SwitchToPreviousLevel() {
|
func (ps *PlayingState) SwitchToPreviousLevel() {
|
||||||
if !ps.dungeon.HasPreviousLevel() {
|
if !ps.dungeon.HasPreviousLevel() {
|
||||||
ps.nextGameState = CreateDialogState(
|
ps.nextGameState = CreateDialogState(
|
||||||
|
ps.inputSystem,
|
||||||
|
ps.turnSystem,
|
||||||
ui.CreateOkDialog(
|
ui.CreateOkDialog(
|
||||||
"The Surface",
|
"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.",
|
"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.",
|
||||||
|
@ -177,13 +231,59 @@ func (ps *PlayingState) PickUpItemUnderPlayer() {
|
||||||
|
|
||||||
if !success {
|
if !success {
|
||||||
ps.dungeon.CurrentLevel().SetItemAt(pos.X(), pos.Y(), item)
|
ps.dungeon.CurrentLevel().SetItemAt(pos.X(), pos.Y(), item)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
itemName, _ := item.Name()
|
||||||
|
|
||||||
|
ps.eventLog.Log("You picked up " + itemName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *PlayingState) HasLineOfSight(start, end engine.Position) bool {
|
||||||
|
positions := engine.CastRay(start, end)
|
||||||
|
|
||||||
|
for _, p := range positions {
|
||||||
|
if ps.dungeon.CurrentLevel().IsGroundTileOpaque(p.XY()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *PlayingState) CalcPathToPlayerAndMove() {
|
func (ps *PlayingState) CalcPathToPlayerAndMove() {
|
||||||
distanceToPlayer := ps.someNPC.Position().Distance(ps.player.Position())
|
playerVisibleAndInRange := false
|
||||||
|
|
||||||
|
if ps.someNPC.Position().Distance(ps.player.Position()) < 20 && ps.HasLineOfSight(ps.someNPC.Position(), ps.player.Position()) {
|
||||||
|
playerVisibleAndInRange = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !playerVisibleAndInRange {
|
||||||
|
randomMove := npc.Direction(engine.RandInt(int(npc.DirectionNone), int(npc.East)))
|
||||||
|
|
||||||
|
nextPos := ps.someNPC.Position()
|
||||||
|
|
||||||
|
switch randomMove {
|
||||||
|
case npc.North:
|
||||||
|
nextPos = nextPos.WithOffset(0, -1)
|
||||||
|
case npc.South:
|
||||||
|
nextPos = nextPos.WithOffset(0, +1)
|
||||||
|
case npc.West:
|
||||||
|
nextPos = nextPos.WithOffset(-1, 0)
|
||||||
|
case npc.East:
|
||||||
|
nextPos = nextPos.WithOffset(+1, 0)
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ps.dungeon.CurrentLevel().IsTilePassable(nextPos.XY()) {
|
||||||
|
ps.dungeon.CurrentLevel().MoveEntityTo(
|
||||||
|
ps.someNPC.UniqueId(),
|
||||||
|
nextPos.X(),
|
||||||
|
nextPos.Y(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if distanceToPlayer > 20 {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,81 +312,16 @@ func (ps *PlayingState) CalcPathToPlayerAndMove() {
|
||||||
ps.dungeon.CurrentLevel().MoveEntityTo(ps.someNPC.UniqueId(), nextPos.X(), nextPos.Y())
|
ps.dungeon.CurrentLevel().MoveEntityTo(ps.someNPC.UniqueId(), nextPos.X(), nextPos.Y())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *PlayingState) OnInput(e *tcell.EventKey) {
|
func (ps *PlayingState) OnTick(dt int64) (nextState GameState) {
|
||||||
ps.player.Input(e)
|
ps.nextGameState = ps
|
||||||
|
|
||||||
if e.Key() == tcell.KeyEsc {
|
ps.turnSystem.NextTurn()
|
||||||
ps.pauseGame = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Key() == tcell.KeyRune && e.Rune() == 'i' {
|
|
||||||
ps.openInventory = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Key() == tcell.KeyRune && e.Rune() == 'p' {
|
|
||||||
ps.pickUpUnderPlayer = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Key() == tcell.KeyRune && e.Rune() == 'e' {
|
|
||||||
ps.interact = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch e.Key() {
|
|
||||||
case tcell.KeyUp:
|
|
||||||
ps.movePlayerDirection = model.DirectionUp
|
|
||||||
ps.moveEntities = true
|
|
||||||
case tcell.KeyDown:
|
|
||||||
ps.movePlayerDirection = model.DirectionDown
|
|
||||||
ps.moveEntities = true
|
|
||||||
case tcell.KeyLeft:
|
|
||||||
ps.movePlayerDirection = model.DirectionLeft
|
|
||||||
ps.moveEntities = true
|
|
||||||
case tcell.KeyRight:
|
|
||||||
ps.movePlayerDirection = model.DirectionRight
|
|
||||||
ps.moveEntities = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *PlayingState) OnTick(dt int64) GameState {
|
|
||||||
ps.player.Tick(dt)
|
|
||||||
|
|
||||||
if ps.pauseGame {
|
|
||||||
return PauseGame(ps)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ps.openInventory {
|
|
||||||
ps.openInventory = false
|
|
||||||
return CreateInventoryScreenState(ps.player, ps)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ps.movePlayerDirection != model.DirectionNone {
|
|
||||||
ps.MovePlayer()
|
|
||||||
}
|
|
||||||
|
|
||||||
if ps.pickUpUnderPlayer {
|
|
||||||
ps.pickUpUnderPlayer = false
|
|
||||||
ps.PickUpItemUnderPlayer()
|
|
||||||
}
|
|
||||||
|
|
||||||
if ps.interact {
|
|
||||||
ps.interact = false
|
|
||||||
ps.InteractBelowPlayer()
|
|
||||||
}
|
|
||||||
|
|
||||||
if ps.moveEntities {
|
|
||||||
ps.moveEntities = false
|
|
||||||
ps.CalcPathToPlayerAndMove()
|
|
||||||
}
|
|
||||||
|
|
||||||
return ps.nextGameState
|
return ps.nextGameState
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *PlayingState) CollectDrawables() []engine.Drawable {
|
func (ps *PlayingState) CollectDrawables() []engine.Drawable {
|
||||||
return engine.Multidraw(engine.CreateDrawingInstructions(func(v views.View) {
|
mainCameraDrawingInstructions := engine.CreateDrawingInstructions(func(v views.View) {
|
||||||
visibilityMap := engine.ComputeFOV(
|
visibilityMap := engine.ComputeFOV(
|
||||||
func(x, y int) world.Tile {
|
func(x, y int) world.Tile {
|
||||||
ps.dungeon.CurrentLevel().Flatten().MarkExplored(x, y)
|
ps.dungeon.CurrentLevel().Flatten().MarkExplored(x, y)
|
||||||
|
@ -314,5 +349,17 @@ func (ps *PlayingState) CollectDrawables() []engine.Drawable {
|
||||||
|
|
||||||
return ' ', tcell.StyleDefault
|
return ' ', tcell.StyleDefault
|
||||||
})
|
})
|
||||||
}))
|
})
|
||||||
|
|
||||||
|
drawables := []engine.Drawable{}
|
||||||
|
|
||||||
|
drawables = append(drawables, mainCameraDrawingInstructions)
|
||||||
|
|
||||||
|
if ps.viewShortLogs {
|
||||||
|
drawables = append(drawables, ps.uiEventLog)
|
||||||
|
}
|
||||||
|
|
||||||
|
drawables = append(drawables, ps.healthBar)
|
||||||
|
|
||||||
|
return drawables
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,14 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
|
"mvvasilev/last_light/game/input"
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type QuitState struct {
|
type QuitState struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *QuitState) OnInput(e *tcell.EventKey) {
|
func (s *QuitState) InputContext() input.Context {
|
||||||
|
return input.InputContext_Menu
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *QuitState) OnTick(dt int64) GameState {
|
func (q *QuitState) OnTick(dt int64) GameState {
|
||||||
|
|
59
game/turns/turn_system.go
Normal file
59
game/turns/turn_system.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package turns
|
||||||
|
|
||||||
|
import "mvvasilev/last_light/engine"
|
||||||
|
|
||||||
|
type turn struct {
|
||||||
|
cost int
|
||||||
|
action func() (complete, requeue bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TurnSystem struct {
|
||||||
|
turnQueue *engine.PriorityQueue[*turn]
|
||||||
|
|
||||||
|
paused bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateTurnSystem() *TurnSystem {
|
||||||
|
return &TurnSystem{
|
||||||
|
turnQueue: engine.CreatePriorityQueue[*turn](),
|
||||||
|
paused: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TurnSystem) NextTurn() {
|
||||||
|
if ts.paused {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
turnCost, turn := ts.turnQueue.Peek()
|
||||||
|
|
||||||
|
if turn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
complete, requeue := turn.action()
|
||||||
|
|
||||||
|
// If the action isn't complete, we'll re-do it again next time
|
||||||
|
if !complete {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ts.turnQueue.Dequeue()
|
||||||
|
|
||||||
|
ts.turnQueue.AdjustPriorities(-turnCost)
|
||||||
|
|
||||||
|
if requeue {
|
||||||
|
ts.turnQueue.Enqueue(turn.cost, turn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TurnSystem) Schedule(cost int, action func() (complete, requeue bool)) {
|
||||||
|
ts.turnQueue.Enqueue(cost, &turn{
|
||||||
|
cost: cost,
|
||||||
|
action: action,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TurnSystem) Clear() {
|
||||||
|
ts.turnQueue.Clear()
|
||||||
|
}
|
|
@ -1,82 +0,0 @@
|
||||||
package ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"mvvasilev/last_light/engine"
|
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
"github.com/gdamore/tcell/v2/views"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UIContainerLayout int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// These change the provided ui positions
|
|
||||||
UpperLeft UIContainerLayout = iota
|
|
||||||
MiddleLeft
|
|
||||||
LowerLeft
|
|
||||||
UpperRight
|
|
||||||
MiddleRight
|
|
||||||
LowerRight
|
|
||||||
UpperCenter
|
|
||||||
MiddleCenter
|
|
||||||
LowerCenter
|
|
||||||
// This uses the positions as provided in the ui elements
|
|
||||||
Manual
|
|
||||||
)
|
|
||||||
|
|
||||||
type UIContainer struct {
|
|
||||||
id uuid.UUID
|
|
||||||
layout UIContainerLayout
|
|
||||||
position engine.Position
|
|
||||||
size engine.Size
|
|
||||||
elements []UIElement
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateUIContainer(x, y int, width, height int, layout UIContainerLayout) *UIContainer {
|
|
||||||
container := new(UIContainer)
|
|
||||||
|
|
||||||
container.id = uuid.New()
|
|
||||||
container.layout = layout
|
|
||||||
container.position = engine.PositionAt(x, y)
|
|
||||||
container.size = engine.SizeOf(width, height)
|
|
||||||
container.elements = make([]UIElement, 0)
|
|
||||||
|
|
||||||
return container
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uic *UIContainer) Push(element UIElement) {
|
|
||||||
uic.elements = append(uic.elements, element)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uic *UIContainer) Clear() {
|
|
||||||
uic.elements = make([]UIElement, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uic *UIContainer) UniqueId() uuid.UUID {
|
|
||||||
return uic.id
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uic *UIContainer) MoveTo(x, y int) {
|
|
||||||
uic.position = engine.PositionAt(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uic *UIContainer) Position() engine.Position {
|
|
||||||
return uic.position
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uic *UIContainer) Size() engine.Size {
|
|
||||||
return uic.size
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uic *UIContainer) Draw(v views.View) {
|
|
||||||
for _, e := range uic.elements {
|
|
||||||
e.Draw(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uic *UIContainer) Input(ev *tcell.EventKey) {
|
|
||||||
for _, e := range uic.elements {
|
|
||||||
e.Input(ev)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,6 +2,7 @@ package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
|
"mvvasilev/last_light/game/input"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/gdamore/tcell/v2/views"
|
"github.com/gdamore/tcell/v2/views"
|
||||||
|
@ -90,13 +91,13 @@ func (d *UIDialog) Size() engine.Size {
|
||||||
return d.window.Size()
|
return d.window.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *UIDialog) Input(e *tcell.EventKey) {
|
func (d *UIDialog) Input(inputAction input.InputAction) {
|
||||||
if e.Key() == tcell.KeyLeft {
|
if inputAction == input.InputAction_Menu_HighlightLeft {
|
||||||
if !d.yesBtn.IsHighlighted() {
|
if !d.yesBtn.IsHighlighted() {
|
||||||
d.noBtn.Unhighlight()
|
d.noBtn.Unhighlight()
|
||||||
d.yesBtn.Highlight()
|
d.yesBtn.Highlight()
|
||||||
}
|
}
|
||||||
} else if e.Key() == tcell.KeyRight {
|
} else if inputAction == input.InputAction_Menu_HighlightRight {
|
||||||
if d.noBtn == nil {
|
if d.noBtn == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
62
game/ui/event_logger.go
Normal file
62
game/ui/event_logger.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mvvasilev/last_light/engine"
|
||||||
|
"mvvasilev/last_light/game/input"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
"github.com/gdamore/tcell/v2/views"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UIEventLog struct {
|
||||||
|
id uuid.UUID
|
||||||
|
|
||||||
|
eventLogger *engine.GameEventLog
|
||||||
|
|
||||||
|
window *UIWindow
|
||||||
|
|
||||||
|
style tcell.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateUIEventLog(x, y, width, height int, eventLogger *engine.GameEventLog, style tcell.Style) *UIEventLog {
|
||||||
|
return &UIEventLog{
|
||||||
|
id: uuid.New(),
|
||||||
|
eventLogger: eventLogger,
|
||||||
|
window: CreateWindow(x, y, width, height, "", style),
|
||||||
|
style: style,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uie *UIEventLog) MoveTo(x int, y int) {
|
||||||
|
uie.window.MoveTo(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uie *UIEventLog) Position() engine.Position {
|
||||||
|
return uie.window.Position()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uie *UIEventLog) Size() engine.Size {
|
||||||
|
return uie.window.Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uie *UIEventLog) Input(inputAction input.InputAction) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uie *UIEventLog) UniqueId() uuid.UUID {
|
||||||
|
return uie.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uie *UIEventLog) Draw(v views.View) {
|
||||||
|
uie.window.Draw(v)
|
||||||
|
|
||||||
|
x, y := uie.Position().XY()
|
||||||
|
height := uie.Size().Height()
|
||||||
|
|
||||||
|
textHeight := height - 2
|
||||||
|
|
||||||
|
for i, ge := range uie.eventLogger.Tail(textHeight) {
|
||||||
|
engine.DrawText(x+1, y+i+1, ge.Contents(), uie.style, v)
|
||||||
|
}
|
||||||
|
}
|
104
game/ui/health_bar.go
Normal file
104
game/ui/health_bar.go
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"mvvasilev/last_light/engine"
|
||||||
|
"mvvasilev/last_light/game/input"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
"github.com/gdamore/tcell/v2/views"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UIHealthBar struct {
|
||||||
|
id uuid.UUID
|
||||||
|
health int
|
||||||
|
maxHealth int
|
||||||
|
|
||||||
|
window *UIWindow
|
||||||
|
|
||||||
|
style tcell.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: style for health bar fill
|
||||||
|
// TODO: 'HP' title
|
||||||
|
// TODO: test different percentages
|
||||||
|
func CreateHealthBar(x, y, w, h, health, maxHealth int, style tcell.Style) *UIHealthBar {
|
||||||
|
return &UIHealthBar{
|
||||||
|
window: CreateWindow(x, y, w, h, "HP", style),
|
||||||
|
health: health,
|
||||||
|
maxHealth: maxHealth,
|
||||||
|
style: style,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uihp *UIHealthBar) SetHealth(health int) {
|
||||||
|
uihp.health = health
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uihp *UIHealthBar) SetMaxHealth(maxHealth int) {
|
||||||
|
uihp.maxHealth = maxHealth
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uihp *UIHealthBar) MoveTo(x int, y int) {
|
||||||
|
uihp.window.MoveTo(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uihp *UIHealthBar) Position() engine.Position {
|
||||||
|
return uihp.window.Position()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uihp *UIHealthBar) Size() engine.Size {
|
||||||
|
return uihp.window.Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uihp *UIHealthBar) Input(inputAction input.InputAction) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uihp *UIHealthBar) UniqueId() uuid.UUID {
|
||||||
|
return uihp.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uihp *UIHealthBar) Draw(v views.View) {
|
||||||
|
x, y, w, h := uihp.Position().X(), uihp.Position().Y(), uihp.Size().Width(), uihp.Size().Height()
|
||||||
|
|
||||||
|
uihp.window.Draw(v)
|
||||||
|
|
||||||
|
stages := []rune{'█', '▓', '▒', '░'} // 0 = 1.0, 1 = 0.75, 2 = 0.5, 3 = 0.25
|
||||||
|
|
||||||
|
percentage := (float64(w) - 2.0) * (float64(uihp.health) / float64(uihp.maxHealth))
|
||||||
|
|
||||||
|
whole := math.Trunc(percentage)
|
||||||
|
last := percentage - whole
|
||||||
|
|
||||||
|
hpStyle := tcell.StyleDefault.Foreground(tcell.ColorIndianRed)
|
||||||
|
|
||||||
|
for i := range int(whole) {
|
||||||
|
v.SetContent(x+1+i, y+1, stages[0], nil, hpStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
if last > 0.0 {
|
||||||
|
if last <= 0.25 {
|
||||||
|
v.SetContent(x+1+int(whole), y+1, stages[3], nil, hpStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
if last <= 0.50 {
|
||||||
|
v.SetContent(x+1+int(whole), y+1, stages[2], nil, hpStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
if last <= 0.75 {
|
||||||
|
v.SetContent(x+1+int(whole), y+1, stages[1], nil, hpStyle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hpText := fmt.Sprintf("%v/%v", uihp.health, uihp.maxHealth)
|
||||||
|
|
||||||
|
engine.DrawText(
|
||||||
|
x+w/2-len(hpText)/2,
|
||||||
|
y+h-1,
|
||||||
|
hpText,
|
||||||
|
hpStyle,
|
||||||
|
v,
|
||||||
|
)
|
||||||
|
}
|
166
game/ui/item.go
Normal file
166
game/ui/item.go
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"mvvasilev/last_light/engine"
|
||||||
|
"mvvasilev/last_light/game/input"
|
||||||
|
"mvvasilev/last_light/game/item"
|
||||||
|
"mvvasilev/last_light/game/rpg"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
"github.com/gdamore/tcell/v2/views"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UIBasicItem struct {
|
||||||
|
id uuid.UUID
|
||||||
|
|
||||||
|
item item.Item
|
||||||
|
|
||||||
|
window UIWindow
|
||||||
|
itemName UILabel
|
||||||
|
|
||||||
|
engine.Positioned
|
||||||
|
engine.Sized
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateUIBasicItem(x, y int, item item.Item, style tcell.Style) *UIBasicItem {
|
||||||
|
|
||||||
|
name, nameStyle := item.Name()
|
||||||
|
|
||||||
|
return &UIBasicItem{
|
||||||
|
id: uuid.New(),
|
||||||
|
item: item,
|
||||||
|
window: *CreateWindow(x, y, 33, 8, "Item", style),
|
||||||
|
itemName: *CreateSingleLineUILabel(x+1, y+1, name, nameStyle),
|
||||||
|
Positioned: engine.WithPosition(engine.PositionAt(x, y)),
|
||||||
|
Sized: engine.WithSize(engine.SizeOf(33, 8)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uibi *UIBasicItem) Input(e *tcell.EventKey) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uibi *UIBasicItem) UniqueId() uuid.UUID {
|
||||||
|
return uibi.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uibi *UIBasicItem) Draw(v views.View) {
|
||||||
|
uibi.window.Draw(v)
|
||||||
|
uibi.itemName.Draw(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UIRPGItem struct {
|
||||||
|
id uuid.UUID
|
||||||
|
|
||||||
|
item rpg.RPGItem
|
||||||
|
|
||||||
|
window UIWindow
|
||||||
|
itemName UILabel
|
||||||
|
|
||||||
|
engine.Positioned
|
||||||
|
engine.Sized
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateUIRPGItem(x, y int, item rpg.RPGItem, style tcell.Style) *UIRPGItem {
|
||||||
|
|
||||||
|
name, nameStyle := item.Name()
|
||||||
|
|
||||||
|
return &UIRPGItem{
|
||||||
|
id: uuid.New(),
|
||||||
|
item: item,
|
||||||
|
window: *CreateWindow(x, y, 33, 8, "Item", style),
|
||||||
|
itemName: *CreateSingleLineUILabel(x+1, y+1, name, nameStyle),
|
||||||
|
Positioned: engine.WithPosition(engine.PositionAt(x, y)),
|
||||||
|
Sized: engine.WithSize(engine.SizeOf(33, 8)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uiri *UIRPGItem) Input(inputAction input.InputAction) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uiri *UIRPGItem) UniqueId() uuid.UUID {
|
||||||
|
return uiri.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uiri *UIRPGItem) Draw(v views.View) {
|
||||||
|
uiri.window.Draw(v)
|
||||||
|
uiri.itemName.Draw(v)
|
||||||
|
|
||||||
|
statModifiers := uiri.item.Modifiers()
|
||||||
|
|
||||||
|
x, y := uiri.itemName.Position().XY()
|
||||||
|
y++
|
||||||
|
|
||||||
|
for i, sm := range statModifiers {
|
||||||
|
|
||||||
|
drawRPGItemStatModifier(x, y, tcell.StyleDefault, v, &sm)
|
||||||
|
|
||||||
|
x += 9 + 2 // each stat is 9 characters long, with 2 characters separating the stats
|
||||||
|
|
||||||
|
// Only 3 stats per line
|
||||||
|
if i > 0 && (i+1)%3 == 0 {
|
||||||
|
x = uiri.itemName.Position().X()
|
||||||
|
y++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawRPGItemStatModifier(x, y int, style tcell.Style, view views.View, sm *rpg.StatModifier) {
|
||||||
|
|
||||||
|
// 5 characters per stat name
|
||||||
|
// 1 separating character
|
||||||
|
// 3 characters for bonus ( including sign, modifiers are limited to -99 and +99)
|
||||||
|
|
||||||
|
const SEPARATING_CHARACTER rune = ':'
|
||||||
|
|
||||||
|
switch sm.Stat {
|
||||||
|
case rpg.Stat_Attributes_Strength:
|
||||||
|
engine.DrawText(x, y, "STR", style, view)
|
||||||
|
case rpg.Stat_Attributes_Dexterity:
|
||||||
|
engine.DrawText(x, y, "DEX", style, view)
|
||||||
|
case rpg.Stat_Attributes_Intelligence:
|
||||||
|
engine.DrawText(x, y, "INT", style, view)
|
||||||
|
case rpg.Stat_Attributes_Constitution:
|
||||||
|
engine.DrawText(x, y, "CON", style, view)
|
||||||
|
case rpg.Stat_PhysicalPrecisionBonus:
|
||||||
|
engine.DrawText(x, y, "pPrcs", style, view)
|
||||||
|
case rpg.Stat_EvasionBonus:
|
||||||
|
engine.DrawText(x, y, "Evasn", style, view)
|
||||||
|
case rpg.Stat_MagicPrecisionBonus:
|
||||||
|
engine.DrawText(x, y, "mPrcs", style, view)
|
||||||
|
case rpg.Stat_TotalPrecisionBonus:
|
||||||
|
engine.DrawText(x, y, "tPrcs", style, view)
|
||||||
|
case rpg.Stat_DamageBonus_Physical_Unarmed:
|
||||||
|
engine.DrawText(x, y, "Unrmd", style, view)
|
||||||
|
case rpg.Stat_DamageBonus_Physical_Slashing:
|
||||||
|
engine.DrawText(x, y, "Slshn", style, view)
|
||||||
|
case rpg.Stat_DamageBonus_Physical_Piercing:
|
||||||
|
engine.DrawText(x, y, "Prcng", style, view)
|
||||||
|
case rpg.Stat_DamageBonus_Physical_Bludgeoning:
|
||||||
|
engine.DrawText(x, y, "Bldgn", style, view)
|
||||||
|
case rpg.Stat_DamageBonus_Magic_Fire:
|
||||||
|
engine.DrawText(x, y, "Fire", style, view)
|
||||||
|
case rpg.Stat_DamageBonus_Magic_Cold:
|
||||||
|
engine.DrawText(x, y, "Cold", style, view)
|
||||||
|
case rpg.Stat_DamageBonus_Magic_Necrotic:
|
||||||
|
engine.DrawText(x, y, "Ncrtc", style, view)
|
||||||
|
case rpg.Stat_DamageBonus_Magic_Thunder:
|
||||||
|
engine.DrawText(x, y, "Thndr", style, view)
|
||||||
|
case rpg.Stat_DamageBonus_Magic_Acid:
|
||||||
|
engine.DrawText(x, y, "Acid", style, view)
|
||||||
|
case rpg.Stat_DamageBonus_Magic_Poison:
|
||||||
|
engine.DrawText(x, y, "Poisn", style, view)
|
||||||
|
case rpg.Stat_MaxHealthBonus:
|
||||||
|
engine.DrawText(x, y, "maxHP", style, view)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
view.SetContent(x+5, y, SEPARATING_CHARACTER, nil, style)
|
||||||
|
|
||||||
|
if sm.Bonus < 0 {
|
||||||
|
engine.DrawText(x+6, y, fmt.Sprintf("-%02d", -sm.Bonus), tcell.StyleDefault.Foreground(tcell.ColorIndianRed), view)
|
||||||
|
} else {
|
||||||
|
engine.DrawText(x+6, y, fmt.Sprintf("+%02d", sm.Bonus), tcell.StyleDefault.Foreground(tcell.ColorLime), view)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
|
"mvvasilev/last_light/game/input"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
|
@ -52,4 +53,4 @@ func (t *UILabel) Draw(v views.View) {
|
||||||
t.text.Draw(v)
|
t.text.Draw(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *UILabel) Input(e *tcell.EventKey) {}
|
func (t *UILabel) Input(inputAction input.InputAction) {}
|
||||||
|
|
|
@ -3,7 +3,9 @@ package menu
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
|
"mvvasilev/last_light/game/input"
|
||||||
"mvvasilev/last_light/game/item"
|
"mvvasilev/last_light/game/item"
|
||||||
|
"mvvasilev/last_light/game/rpg"
|
||||||
"mvvasilev/last_light/game/ui"
|
"mvvasilev/last_light/game/ui"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
|
@ -116,25 +118,18 @@ func CreatePlayerInventoryMenu(x, y int, playerInventory *item.EquippedInventory
|
||||||
})
|
})
|
||||||
|
|
||||||
menu.selectedItem = engine.CreateDrawingInstructions(func(v views.View) {
|
menu.selectedItem = engine.CreateDrawingInstructions(func(v views.View) {
|
||||||
ui.CreateWindow(x+2, y+14, 33, 8, "ITEM", style).Draw(v)
|
|
||||||
|
|
||||||
item := playerInventory.ItemAt(menu.selectedInventorySlot.XY())
|
item := playerInventory.ItemAt(menu.selectedInventorySlot.XY())
|
||||||
|
|
||||||
if item == nil {
|
if item == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
name, nameStyle := item.Name()
|
switch it := item.(type) {
|
||||||
|
case rpg.RPGItem:
|
||||||
ui.CreateSingleLineUILabel(x+3, y+15, name, nameStyle).Draw(v)
|
ui.CreateUIRPGItem(x+2, y+14, it, style).Draw(v)
|
||||||
|
default:
|
||||||
// |Stt:+00|Stt:+00|Stt:+00|Stt:+00|
|
ui.CreateUIBasicItem(x+2, y+14, it, style).Draw(v)
|
||||||
// switch it := item.(type) {
|
}
|
||||||
// case rpg.RPGItem:
|
|
||||||
// //statModifiers := it.Modifiers()
|
|
||||||
|
|
||||||
// default:
|
|
||||||
// }
|
|
||||||
})
|
})
|
||||||
|
|
||||||
menu.help = ui.CreateSingleLineUILabel(x+2, y+22, "hjkl - move, x - drop, e - equip", style)
|
menu.help = ui.CreateSingleLineUILabel(x+2, y+22, "hjkl - move, x - drop, e - equip", style)
|
||||||
|
@ -154,7 +149,7 @@ func (pim *PlayerInventoryMenu) Size() engine.Size {
|
||||||
return pim.inventoryMenu.Size()
|
return pim.inventoryMenu.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pim *PlayerInventoryMenu) Input(e *tcell.EventKey) {
|
func (pim *PlayerInventoryMenu) Input(inputAction input.InputAction) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,6 +158,10 @@ func (pim *PlayerInventoryMenu) UniqueId() uuid.UUID {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pim *PlayerInventoryMenu) SelectSlot(x, y int) {
|
func (pim *PlayerInventoryMenu) SelectSlot(x, y int) {
|
||||||
|
if pim.selectedInventorySlot.X() == x && pim.selectedInventorySlot.Y() == y {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
pim.inventoryGrid.Unhighlight()
|
pim.inventoryGrid.Unhighlight()
|
||||||
pim.selectedInventorySlot = engine.PositionAt(x, y)
|
pim.selectedInventorySlot = engine.PositionAt(x, y)
|
||||||
pim.inventoryGrid.Highlight(pim.selectedInventorySlot)
|
pim.inventoryGrid.Highlight(pim.selectedInventorySlot)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
|
"mvvasilev/last_light/game/input"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
@ -96,6 +97,6 @@ func (sb *UISimpleButton) Draw(v views.View) {
|
||||||
sb.text.Draw(v)
|
sb.text.Draw(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sb *UISimpleButton) Input(e *tcell.EventKey) {
|
func (sb *UISimpleButton) Input(inputAction input.InputAction) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,14 @@ package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
|
"mvvasilev/last_light/game/input"
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type UIElement interface {
|
type UIElement interface {
|
||||||
MoveTo(x, y int)
|
MoveTo(x, y int)
|
||||||
Position() engine.Position
|
Position() engine.Position
|
||||||
Size() engine.Size
|
Size() engine.Size
|
||||||
Input(e *tcell.EventKey)
|
Input(inputAction input.InputAction)
|
||||||
|
|
||||||
engine.Drawable
|
engine.Drawable
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
|
"mvvasilev/last_light/game/input"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
|
@ -23,7 +24,9 @@ func CreateWindow(x, y, width, height int, title string, style tcell.Style) *UIW
|
||||||
|
|
||||||
titlePos := (width / 2) - int(titleLen/2)
|
titlePos := (width / 2) - int(titleLen/2)
|
||||||
|
|
||||||
|
if title != "" {
|
||||||
w.title = engine.CreateText(x+titlePos, y, int(titleLen), 1, title, style)
|
w.title = engine.CreateText(x+titlePos, y, int(titleLen), 1, title, style)
|
||||||
|
}
|
||||||
|
|
||||||
w.box = engine.CreateRectangle(
|
w.box = engine.CreateRectangle(
|
||||||
x, y, width, height,
|
x, y, width, height,
|
||||||
|
@ -49,13 +52,16 @@ func (w *UIWindow) Position() engine.Position {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *UIWindow) Size() engine.Size {
|
func (w *UIWindow) Size() engine.Size {
|
||||||
return engine.SizeOf(0, 0)
|
return w.box.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *UIWindow) Draw(v views.View) {
|
func (w *UIWindow) Draw(v views.View) {
|
||||||
w.box.Draw(v)
|
w.box.Draw(v)
|
||||||
|
|
||||||
|
if w.title != nil {
|
||||||
w.title.Draw(v)
|
w.title.Draw(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *UIWindow) Input(e *tcell.EventKey) {
|
func (w *UIWindow) Input(inputAction input.InputAction) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/item"
|
"mvvasilev/last_light/game/item"
|
||||||
"mvvasilev/last_light/game/model"
|
"mvvasilev/last_light/game/npc"
|
||||||
"mvvasilev/last_light/game/rpg"
|
"mvvasilev/last_light/game/rpg"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
|
@ -113,7 +113,6 @@ func CreateDungeonLevel(width, height int, dungeonType DungeonType) *DungeonLeve
|
||||||
return item.CreateBasicItem(item.ItemTypeFish(), 1)
|
return item.CreateBasicItem(item.ItemTypeFish(), 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
genTable.Add(1, func() item.Item {
|
|
||||||
itemTypes := []rpg.RPGItemType{
|
itemTypes := []rpg.RPGItemType{
|
||||||
rpg.ItemTypeBow(),
|
rpg.ItemTypeBow(),
|
||||||
rpg.ItemTypeLongsword(),
|
rpg.ItemTypeLongsword(),
|
||||||
|
@ -128,6 +127,7 @@ func CreateDungeonLevel(width, height int, dungeonType DungeonType) *DungeonLeve
|
||||||
rpg.ItemTypeSpear(),
|
rpg.ItemTypeSpear(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
genTable.Add(1, func() item.Item {
|
||||||
itemType := itemTypes[rand.Intn(len(itemTypes))]
|
itemType := itemTypes[rand.Intn(len(itemTypes))]
|
||||||
|
|
||||||
rarities := []rpg.ItemRarity{
|
rarities := []rpg.ItemRarity{
|
||||||
|
@ -156,7 +156,7 @@ func CreateDungeonLevel(width, height int, dungeonType DungeonType) *DungeonLeve
|
||||||
groundLevel = CreateBSPDungeonMap(width, height, 4)
|
groundLevel = CreateBSPDungeonMap(width, height, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
items := SpawnItems(groundLevel.Rooms(), 0.02, genTable, []engine.Position{
|
items := SpawnItems(groundLevel.Rooms(), 0.1, genTable, []engine.Position{
|
||||||
groundLevel.NextLevelStaircasePosition(),
|
groundLevel.NextLevelStaircasePosition(),
|
||||||
groundLevel.PlayerSpawnPoint(),
|
groundLevel.PlayerSpawnPoint(),
|
||||||
groundLevel.PreviousLevelStaircasePosition(),
|
groundLevel.PreviousLevelStaircasePosition(),
|
||||||
|
@ -202,9 +202,9 @@ func SpawnItems(spawnableAreas []engine.BoundingBox, maxItemRatio float32, genTa
|
||||||
numItems := rand.Intn(maxItems)
|
numItems := rand.Intn(maxItems)
|
||||||
|
|
||||||
for range numItems {
|
for range numItems {
|
||||||
itemType := genTable.Generate()
|
item := genTable.Generate()
|
||||||
|
|
||||||
if itemType == nil {
|
if item == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +217,7 @@ func SpawnItems(spawnableAreas []engine.BoundingBox, maxItemRatio float32, genTa
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
itemTiles = append(itemTiles, CreateItemTile(pos, itemType))
|
itemTiles = append(itemTiles, CreateItemTile(pos, item))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,7 +240,7 @@ func (d *DungeonLevel) DropEntity(uuid uuid.UUID) {
|
||||||
d.entityLevel.DropEntity(uuid)
|
d.entityLevel.DropEntity(uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DungeonLevel) AddEntity(entity model.MovableEntity, presentation rune, style tcell.Style) {
|
func (d *DungeonLevel) AddEntity(entity npc.MovableEntity, presentation rune, style tcell.Style) {
|
||||||
d.entityLevel.AddEntity(entity, presentation, style)
|
d.entityLevel.AddEntity(entity, presentation, style)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,6 +291,14 @@ func (d *DungeonLevel) IsTilePassable(x, y int) bool {
|
||||||
return d.TileAt(x, y).Passable()
|
return d.TileAt(x, y).Passable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DungeonLevel) IsGroundTileOpaque(x, y int) bool {
|
||||||
|
if !d.groundLevel.Size().Contains(x, y) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.TileAt(x, y).Opaque()
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DungeonLevel) Flatten() Map {
|
func (d *DungeonLevel) Flatten() Map {
|
||||||
return d.multilevel
|
return d.multilevel
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package world
|
||||||
import (
|
import (
|
||||||
"maps"
|
"maps"
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/model"
|
"mvvasilev/last_light/game/npc"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
@ -43,7 +43,7 @@ func (em *EntityMap) FindEntityByUuid(uuid uuid.UUID) (key int, entity EntityTil
|
||||||
return -1, nil
|
return -1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (em *EntityMap) AddEntity(entity model.MovableEntity, presentation rune, style tcell.Style) {
|
func (em *EntityMap) AddEntity(entity npc.MovableEntity, presentation rune, style tcell.Style) {
|
||||||
if !em.FitsWithin(entity.Position().XY()) {
|
if !em.FitsWithin(entity.Position().XY()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package world
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/item"
|
"mvvasilev/last_light/game/item"
|
||||||
"mvvasilev/last_light/game/model"
|
"mvvasilev/last_light/game/npc"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
)
|
)
|
||||||
|
@ -231,18 +231,18 @@ func (it *ItemTile) Type() TileType {
|
||||||
}
|
}
|
||||||
|
|
||||||
type EntityTile interface {
|
type EntityTile interface {
|
||||||
Entity() model.MovableEntity
|
Entity() npc.MovableEntity
|
||||||
Tile
|
Tile
|
||||||
}
|
}
|
||||||
|
|
||||||
type BasicEntityTile struct {
|
type BasicEntityTile struct {
|
||||||
entity model.MovableEntity
|
entity npc.MovableEntity
|
||||||
|
|
||||||
presentation rune
|
presentation rune
|
||||||
style tcell.Style
|
style tcell.Style
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateBasicEntityTile(entity model.MovableEntity, presentation rune, style tcell.Style) *BasicEntityTile {
|
func CreateBasicEntityTile(entity npc.MovableEntity, presentation rune, style tcell.Style) *BasicEntityTile {
|
||||||
return &BasicEntityTile{
|
return &BasicEntityTile{
|
||||||
entity: entity,
|
entity: entity,
|
||||||
presentation: presentation,
|
presentation: presentation,
|
||||||
|
@ -250,7 +250,7 @@ func CreateBasicEntityTile(entity model.MovableEntity, presentation rune, style
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bet *BasicEntityTile) Entity() model.MovableEntity {
|
func (bet *BasicEntityTile) Entity() npc.MovableEntity {
|
||||||
return bet.entity
|
return bet.entity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue