package main import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" "lab.zaar.be/thefish/alchemyst-go/engine/ecs" "lab.zaar.be/thefish/alchemyst-go/engine/ecs/systems" "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/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" 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 := util.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 level, rooms := mapgens.DefaultGen(gamemap.NewLevel(mainCtx, "test", 1)) State.Level = level //Set up viewport vp := mainwindow.NewViewPort(30, 0, 70, 47, mw.GetLayer("base")) go vp.Listen(State) //Set up Screen Manager screenMgr := types.NewScreenManager(mainCtx) screenMgr.AddScreen("title", &screens.TitleScreen{}) screenMgr.AddScreen("game", screens.NewGameScreen(mw, &State, vp)) screenMgr.SetScreenByName("game") //fixme //player := &mob.Player{ // Mob: mob.Mob{ // Appearance: &types.Appearance{ // Glyph: &types.PlainGlyphHolder{"@"}, // ColorSet: &types.TileColorSet{ // Fg: &types.PlainColorHolder{255, 255, 255, 255}, // }, // }, // Coords: rooms[0].Center, // BlocksPass: true, // }, //} //State.Player = player //vp.PlayerCoords = player.Coords //vp.Render(&State) //set up controller controller := ecs.NewController() controller.MapComponentClass("coords", types.Coords{}) controller.MapComponentClass("appearance", types.Appearance{}) controller.MapComponentClass("mob", mob.Mob{}) controller.MapComponentClass("moveable", movement.Moveable{}) moveable := movement.Moveable{ Controller: controller, Level: level, } //fixme set up (load / generate) player 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) render := systems.MobRenderSystem{EntityController: controller} controller.AddSystem(render, 1) 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: mainCtx.Logger().Warn().Msg("quitting NOW") exit = true break // не оставляйте default в бесконесчном select {} - сожрет всё CPU default: screenMgr.CurrentScreen.Render() blt.Refresh() } } mainCtx.Logger().Info().Msg("pre-shutdown sequence") } func setupLayers(mainwindow *mainwindow.MainWindow) { mainwindow.AddLayer("base", 0, "white") mainwindow.AddLayer("overlay", 1, "white") mainwindow.AddLayer("menu", 2, "white") } func decodeInput(ctx util.ClientCtx, 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 { ctx.Logger().Warn().Msg("exiting on window close...") State.Exit <- struct{}{} ctx.Logger().Warn().Msg("...done") return } var pressed = "" var isModifier, _ = util.InArray(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 { //fixme testing only case "F10": State.Do(func() { blt.Set("window: size=100x47; font: ./resources/fonts-ttf/UbuntuMono-R.ttf, size=11;") }) case "Ctrl+q": fallthrough case "Escape": ctx.Logger().Info().Msg("exiting on quit command...") State.Exit <- struct{}{} ctx.Logger().Info().Msg("...done") exit = true return default: if pressed != "" { waitForWCspam = false; State.Input <- pressed } } } } } }