alchemyst/cmd/game/main.go

279 lines
7.8 KiB
Go

package main
import (
"context"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"lab.zaar.be/thefish/alchemyst-go/engine/ecs"
"lab.zaar.be/thefish/alchemyst-go/engine/gamemap"
"lab.zaar.be/thefish/alchemyst-go/engine/gamemap/mapgens"
"lab.zaar.be/thefish/alchemyst-go/engine/gamestate"
"lab.zaar.be/thefish/alchemyst-go/engine/items"
"lab.zaar.be/thefish/alchemyst-go/engine/mob"
"lab.zaar.be/thefish/alchemyst-go/engine/mob/movement"
"lab.zaar.be/thefish/alchemyst-go/engine/screens"
"lab.zaar.be/thefish/alchemyst-go/engine/types"
"lab.zaar.be/thefish/alchemyst-go/ui"
"lab.zaar.be/thefish/alchemyst-go/ui/mainwindow"
"lab.zaar.be/thefish/alchemyst-go/util"
"lab.zaar.be/thefish/alchemyst-go/util/appctx"
blt "lab.zaar.be/thefish/bearlibterminal"
"os"
"runtime"
"time"
)
var modifiers = []int{blt.TK_SHIFT, blt.TK_ALT, blt.TK_CONTROL}
// Рецепт чтобы убежать от [fatal] 'refresh' was not called from the main thread
// https://github.com/golang/go/wiki/LockOSThread
func init() {
runtime.LockOSThread()
}
//we can run logic in separate goroutines
//
// go doSometing(State,...)
//
//and there we go like this:
// func doSomething(State main.GameState, args...) {
// ...
// State.Do(func() {
// ...do stuff in main thread
// })
// ...
// }
var State = gamestate.GameState{
Mainfunc: make(chan func()),
Exit: make(chan struct{}, 1),
Input: make(chan string, 1),
RawInput: make(chan int, 1),
FovRecompute: make(chan struct{}, 1),
Redraw: make(chan struct{}, 1),
}
func main() {
config := util.LoadConfig()
var logLevels = map[string]zerolog.Level{"debug": zerolog.DebugLevel, "info": zerolog.InfoLevel, "warn": zerolog.WarnLevel}
var logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339}).Level(logLevels[config.Verbosity])
// set up context
mainCtx := appctx.NewClientContext(config, &logger)
//set up main window
mw := mainwindow.Init(mainCtx)
defer mw.Close()
setupLayers(mw)
//set up input decoder
go decodeInput(mainCtx, mw.GetLayer("base"))
//fixme set up (load / generate) level - move to game / enter or title / exit
//level, rooms := _default.DefaultGen(mainCtx, gamemap.NewLevel(mainCtx, "test", 1))
//level, rooms := mapgens.DelaunayMstGen(mainCtx, gamemap.NewLevel(mainCtx, "test", 1))
level, rooms := mapgens.DelaunayMstExtGen(mainCtx, gamemap.NewLevel(mainCtx, "test", 1))
//level, rooms := mapgens.DelaunayPureGen(mainCtx, gamemap.NewLevel(mainCtx, "test", 1))
State.Level = level
sidebarWidth := 0
//Set up viewport
vp := mainwindow.NewViewPort(sidebarWidth, 0, (mw.W - sidebarWidth), (mw.H - 0))
//set up controller
controller := ecs.NewController()
controller.MapComponentClass(ecs.CoordsComponent, types.Coords{})
controller.MapComponentClass(ecs.AppearanceComponent, types.Appearance{})
controller.MapComponentClass(ecs.MobComponent, mob.Mob{})
controller.MapComponentClass(ecs.MoveableComponent, movement.Moveable{})
controller.MapComponentClass(ecs.CarriedComponent, movement.Moveable{})
controller.MapComponentClass(ecs.UsableComponent, movement.Moveable{})
moveable := movement.Moveable{
Controller: controller,
Level: level,
}
//Set up Screen Manager
screenMgr := types.NewScreenManager(mainCtx)
screenMgr.AddScreen("title", screens.NewTitleScreen(mw, screenMgr))
screenMgr.AddScreen("game", screens.NewGameScreen(mw, &State, vp, controller, screenMgr))
screenMgr.AddScreen("help", screens.NewMenuScreen(
mw,
screenMgr,
"Help",
"Keybindings:",
//"[color=yellow]Note[/color]: Many of these are not implemented yet",
"[color=yellow]Note[/color]: Many of these are not implemented yet",
types.NewCenteredRect(mw.Rect, 50, 15),
true, ).
SetBgColor("#ef1d494f").
SetFgColor("white").
SetItems([]interface{}{
"hjklyubn, NumPad 12346789, arrow keys - move",
"s or . - pass turn",
"g or , - pick up item",
"i - inventory",
"? - this screen",
"Ctrl+q - exit",
"f or F - fire or throw weapon",
"z or Z - cast a spell",
"p - pray",
"Ctrl+p - message log",
}).MakeList(),
)
screenMgr.AddScreen("inventory", screens.NewMenuScreen(
mw,
screenMgr,
"Inventory",
"Items in your backpack:",
//"[color=yellow]Note[/color]: Many of these are not implemented yet",
"",
types.NewCenteredRect(mw.Rect, 70, 25),
true, ).
SetBgColor("#ef305c70").
SetFgColor("white").
SetItems([]interface{}{
`"Fish-eye" crafty shaded glasses`,
"Xecutor's glowing visor",
"Kitschy goggles of many pathways",
"Ring of inexistence",
"Orb of omniscience",
"Wand of amnesia",
}).MakeList(),
)
screenMgr.AddScreen("devmenu", screens.NewDevmenuScreen(
mw,
controller,
screenMgr,
&State,
types.NewCenteredRect(mw.Rect, 70, 25),
true,
).SetBgColor("#ef6d559d").
SetFgColor("white"),
)
screenMgr.SetScreenByName("title")
//fixme set up (load / generate) player - move to game / enter or title / exit
player := controller.CreateEntity([]ecs.Component{})
controller.AddComponent(player, types.Appearance{
Glyph: types.PlainGlyphHolder{"@"},
ColorSet: types.TileColorSet{
Fg: types.PlainColorHolder{255, 255, 255, 255},
},
})
controller.AddComponent(player, rooms[0].Center) //implicit Coords
controller.AddComponent(player, moveable)
potion := controller.CreateEntity([]ecs.Component{})
controller.AddComponent(potion, types.Appearance{
Glyph: types.PlainGlyphHolder{"!"},
ColorSet: types.TileColorSet{
Fg: types.PlainColorHolder{255, 55, 255, 222},
},
})
controller.AddComponent(potion, rooms[1].Center) //implicit Coords
controller.AddComponent(potion, items.Carried{})
controller.AddComponent(potion, items.Usable{})
controller.AddComponent(potion, items.Consumable{})
State.Player = player
State.Controller = controller
//but every call to bearlibterminal must be wrapped to closure and passed to mainfunc
var exit = false
for !exit {
select {
case State.RawInput <- ui.ReadKeyCode():
break
case pressed := <-State.Input:
screenMgr.CurrentScreen.HandleInput(pressed)
break
//case f := <-State.mainfunc:
// f()
// break
case <-State.Exit:
appctx.Logger(mainCtx).Warn().Msg("quitting NOW")
exit = true
break
// не оставляйте default в бесконечном select {} - сожрет всё CPU
default:
screenMgr.CurrentScreen.Render()
blt.Layer(0) //return to base layer
blt.Refresh()
}
}
appctx.Logger(mainCtx).Info().Msg("pre-shutdown sequence")
}
func setupLayers(mainwindow *mainwindow.MainWindow) {
mainwindow.AddLayer("base", 0, "white")
mainwindow.AddLayer("overlay", 1, "white")
mainwindow.AddLayer("menubg", 2, "white")
mainwindow.AddLayer("menu", 3, "white")
}
func decodeInput(ctx context.Context, baseLayer *mainwindow.Layer) {
var exit = false
var waitForWCspam = true
for !exit {
select {
case keycode := <-State.RawInput:
if keycode == blt.TK_NONE {
continue
}
if keycode == blt.TK_CLOSE && !waitForWCspam {
appctx.Logger(ctx).Warn().Msg("exiting on window close...")
State.Exit <- struct{}{}
appctx.Logger(ctx).Warn().Msg("...done")
return
}
var pressed = ""
var isModifier, _ = util.IntInSlice(keycode, modifiers)
if !isModifier {
pressed = ui.Scancodemap[keycode]
if blt.Check(blt.TK_SHIFT) != 0 {
pressed = "Shift+" + pressed
}
if blt.Check(blt.TK_ALT) != 0 {
pressed = "Alt+" + pressed
}
if blt.Check(blt.TK_CONTROL) != 0 {
pressed = "Ctrl+" + pressed
}
//global hotkeys
switch pressed {
case "Ctrl+q":
//fallthrough
//case "Escape":
appctx.Logger(ctx).Info().Msg("exiting on quit command...")
State.Exit <- struct{}{}
appctx.Logger(ctx).Info().Msg("...done")
exit = true
return
default:
if pressed != "" {
waitForWCspam = false;
State.Input <- pressed
}
}
}
}
}
}