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/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(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), mw.GetLayer("base")) go vp.Listen(State) //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{}{ "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("devmenu", screens.NewDevmenuScreen( mw, controller, screenMgr, &State, types.NewCenteredRect(mw.Rect, 70, 25), true, ).SetBgColor("#ef6d559d"). SetFgColor("white"), ) screenMgr.SetScreenByName("title") //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) //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) 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.Layer(0) //return to base layer 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("menubg", 2, "white") mainwindow.AddLayer("menu", 3, "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.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 { //fixme testing only case "F10": State.Do(func() { blt.Set("window: size=100x47; font: ./assets/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 } } } } } }