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 doSomething(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 appctx.NewClientContext(config, &logger) mainCtx := appctx.ClientState //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 := mapgens.DefaultGen(gamemap.NewLevel("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{}) controller.MapComponentClass(ecs.BackpackComponent, items.Backpack{}) moveable := movement.Moveable{ Controller: controller, Level: level, } items.Init(controller) bp := items.Backpack{MaxMass:100, MaxBulk:100} //Set up Screen Manager screenMgr := types.NewScreenManager() 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 weapon or throw item", "z or Z - cast a spell", "p - pray", "Ctrl+p - message log", }).MakeList(), ) inv := screens.InventoryScreen{ MenuScreen: 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"), } screenMgr.AddScreen("devmenu", screens.NewDevmenuScreen( mainCtx, 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) controller.AddComponent(player, bp) //fixme adding items 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[0].Center) //implicit Coords controller.AddComponent(potion, items.Carried{Mass:5, Bulk:3}) //fixme generate from blueprint! controller.AddComponent(potion, items.Usable{}) controller.AddComponent(potion, items.Consumable{}) controller.AddComponent(potion, ecs.Named{Name:"first potion"}) potion2 := controller.CreateEntity([]ecs.Component{}) controller.AddComponent(potion2, types.Appearance{ Glyph: types.PlainGlyphHolder{Glyph: "!"}, ColorSet: types.TileColorSet{ Fg: types.PlainColorHolder{A: 255, R: 222, G: 255, B: 55}, }, }) controller.AddComponent(potion2, rooms[1].Center) //implicit Coords controller.AddComponent(potion2, items.Carried{Mass:5, Bulk:3}) controller.AddComponent(potion2, items.Usable{}) controller.AddComponent(potion2, items.Consumable{}) controller.AddComponent(potion2, ecs.Named{Name:"second potion"}) //fixme end setting up items State.Player = player State.Controller = controller screenMgr.AddScreen("inventory", inv.MakeInverntory(player)) //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().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().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().Warn().Msg("exiting on window close...") State.Exit <- struct{}{} appctx.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 { case "Ctrl+q": //fallthrough //case "Escape": appctx.Logger().Info().Msg("exiting on quit command...") State.Exit <- struct{}{} appctx.Logger().Info().Msg("...done") exit = true return default: if pressed != "" { waitForWCspam = false; State.Input <- pressed } } } } } }