func (exec *moveExec) measureCost(ent *game.Entity, g *game.Game) int { if len(exec.Path) == 0 { base.Error().Printf("Zero length path") return -1 } if g.ToVertex(ent.Pos()) != exec.Path[0] { base.Error().Printf("Path doesn't begin at ent's position, %d != %d", g.ToVertex(ent.Pos()), exec.Path[0]) return -1 } graph := g.Graph(ent.Side(), true, nil) v := g.ToVertex(ent.Pos()) cost := 0 for _, step := range exec.Path[1:] { dsts, costs := graph.Adjacent(v) ok := false prev := v base.Log().Printf("Adj(%d):", v) for j := range dsts { base.Log().Printf("Node %d", dsts[j]) if dsts[j] == step { cost += int(costs[j]) v = dsts[j] ok = true break } } base.Log().Printf("%d -> %d: %t", prev, v, ok) if !ok { return -1 } } return cost }
func InsertVersusMenu(ui gui.WidgetParent, replace func(gui.WidgetParent) error) error { // return doChooserMenu(ui, makeChooseVersusMetaMenu, replace, inserter(insertGoalMenu)) chooser, done, err := makeChooseVersusMetaMenu() if err != nil { return err } ui.AddChild(chooser) go func() { m := <-done ui.RemoveChild(chooser) if m != nil && len(m) == 1 { base.Log().Printf("Chose: %v", m) switch m[0] { case "Select House": ui.AddChild(MakeGamePanel("versus/basic.lua", nil, map[string]string{"map": "select"}, "")) case "Random House": ui.AddChild(MakeGamePanel("versus/basic.lua", nil, map[string]string{"map": "random"}, "")) case "Continue": ui.AddChild(MakeGamePanel("versus/basic.lua", nil, map[string]string{"map": "continue"}, "")) default: base.Error().Printf("Unknown meta choice '%s'", m[0]) return } } else { err := replace(ui) if err != nil { base.Error().Printf("Error replacing menu: %v", err) } } }() return nil }
func (a *AoeAttack) Maintain(dt int64, g *game.Game, ae game.ActionExec) game.MaintenanceStatus { if ae != nil { a.exec = ae.(*aoeExec) a.targets = a.getTargetsAt(g, a.exec.X, a.exec.Y) if a.Current_ammo > 0 { a.Current_ammo-- } a.ent = g.EntityById(ae.EntityId()) if !a.ent.HasLos(a.exec.X, a.exec.Y, 1, 1) { base.Error().Printf("Entity %d tried to target position (%d, %d) with an aoe but doesn't have los to it: %v", a.ent.Id, a.exec.X, a.exec.Y, a.exec) return game.Complete } if a.Ap > a.ent.Stats.ApCur() { base.Error().Printf("Got an aoe attack that required more ap than available: %v", a.exec) return game.Complete } a.ent.Stats.ApplyDamage(-a.Ap, 0, status.Unspecified) // Track this information for the ais - the attacking ent will only // remember one ent that it hit, but that's ok for _, target := range a.targets { if target.Side() != a.ent.Side() { target.Info.LastEntThatAttackedMe = a.ent.Id a.ent.Info.LastEntThatIAttacked = target.Id } } } if a.ent.Sprite().State() != "ready" { return game.InProgress } for _, target := range a.targets { if target.Stats.HpCur() > 0 && target.Sprite().State() != "ready" { return game.InProgress } } a.ent.TurnToFace(a.exec.X, a.exec.Y) for _, target := range a.targets { target.TurnToFace(a.ent.Pos()) } a.ent.Sprite().Command(a.Animation) for _, target := range a.targets { if g.DoAttack(a.ent, target, a.Strength, a.Kind) { for _, name := range a.Conditions { target.Stats.ApplyCondition(status.MakeCondition(name)) } target.Stats.ApplyDamage(0, -a.Damage, a.Kind) if target.Stats.HpCur() <= 0 { target.Sprite().CommandN([]string{"defend", "killed"}) } else { target.Sprite().CommandN([]string{"defend", "damaged"}) } } else { target.Sprite().CommandN([]string{"defend", "undamaged"}) } } return game.Complete }
func (a *BasicAttack) Maintain(dt int64, g *game.Game, ae game.ActionExec) game.MaintenanceStatus { if ae != nil { a.exec = ae.(*basicAttackExec) a.ent = g.EntityById(ae.EntityId()) a.target = a.ent.Game().EntityById(a.exec.Target) // Track this information for the ais if a.ent.Side() != a.target.Side() { a.ent.Info.LastEntThatIAttacked = a.target.Id a.target.Info.LastEntThatAttackedMe = a.ent.Id } if a.Ap > a.ent.Stats.ApCur() { base.Error().Printf("Got a basic attack that required more ap than available: %v", a.exec) base.Error().Printf("Ent: %s, Ap: %d", a.ent.Name, a.ent.Stats.ApCur()) return game.Complete } if !a.validTarget(a.ent, a.target) { base.Error().Printf("Got a basic attack that was invalid for some reason: %v", a.exec) return game.Complete } } if a.ent.Sprite().State() == "ready" && a.target.Sprite().State() == "ready" { a.target.TurnToFace(a.ent.Pos()) a.ent.TurnToFace(a.target.Pos()) if a.Current_ammo > 0 { a.Current_ammo-- } a.ent.Stats.ApplyDamage(-a.Ap, 0, status.Unspecified) var defender_cmds []string if g.DoAttack(a.ent, a.target, a.Strength, a.Kind) { for _, name := range a.Conditions { a.target.Stats.ApplyCondition(status.MakeCondition(name)) } a.target.Stats.ApplyDamage(0, -a.Damage, a.Kind) if a.target.Stats.HpCur() <= 0 { defender_cmds = []string{"defend", "killed"} } else { defender_cmds = []string{"defend", "damaged"} } results[a.exec.id] = BasicAttackResult{Hit: true} } else { defender_cmds = []string{"defend", "undamaged"} results[a.exec.id] = BasicAttackResult{Hit: false} } sprites := []*sprite.Sprite{a.ent.Sprite(), a.target.Sprite()} sprite.CommandSync(sprites, [][]string{[]string{a.Animation}, defender_cmds}, "hit") return game.Complete } return game.InProgress }
func (mdb *MediumDialogBox) Draw(region gui.Region) { mdb.region = region if mdb.done { return } gl.Enable(gl.TEXTURE_2D) gl.Color4ub(255, 255, 255, 255) mdb.layout.Background.Data().RenderNatural(region.X, region.Y) for _, button := range mdb.buttons { button.RenderAt(region.X, region.Y) } for i := range mdb.format.Sections { section := mdb.format.Sections[i] data := mdb.data.Pages[mdb.data.cur_page].Sections[i] p := section.Paragraph d := base.GetDictionary(p.Size) var just gui.Justification switch p.Halign { case "left": just = gui.Left case "right": just = gui.Right case "center": just = gui.Center default: base.Error().Printf("Unknown justification '%s'", p.Halign) p.Halign = "left" } var valign gui.Justification switch p.Valign { case "top": valign = gui.Top case "bottom": valign = gui.Bottom case "center": valign = gui.Center default: base.Error().Printf("Unknown justification '%s'", p.Valign) p.Valign = "top" } gl.Color4ub(255, 255, 255, 255) d.RenderParagraph(data.Text, float64(p.X+region.X), float64(p.Y+region.Y)-d.MaxHeight()/2, 0, float64(p.Dx), d.MaxHeight(), just, valign) gl.Color4ub(255, 255, 255, byte(data.shading*255)) tex := data.Image.Data() tex.RenderNatural(region.X+section.X-tex.Dx()/2, region.Y+section.Y-tex.Dy()/2) } }
func (a *SummonAction) Maintain(dt int64, g *game.Game, ae game.ActionExec) game.MaintenanceStatus { if ae != nil { exec := ae.(*summonExec) ent := g.EntityById(exec.Ent) if ent == nil { base.Error().Printf("Got a summon action without a valid entity.") return game.Complete } a.ent = ent _, a.cx, a.cy = a.ent.Game().FromVertex(exec.Pos) a.ent.Stats.ApplyDamage(-a.Ap, 0, status.Unspecified) a.spawn = game.MakeEntity(a.Ent_name, a.ent.Game()) if a.Current_ammo > 0 { a.Current_ammo-- } } if a.ent.Sprite().State() == "ready" { a.ent.TurnToFace(a.cx, a.cy) a.ent.Sprite().Command(a.Animation) a.spawn.Stats.OnBegin() a.ent.Game().SpawnEntity(a.spawn, a.cx, a.cy) return game.Complete } return game.InProgress }
func PopSpawnRegexp() { if len(spawn_regex) == 0 { base.Error().Printf("Tried to pop an empty stack.") return } spawn_regex = spawn_regex[0 : len(spawn_regex)-1] }
func LuaPushSmartFunctionTable(L *lua.State, ft FunctionTable) { // Copy it just in case - I can't imagine someone changing it after passing // it to this function, but I don't want to take any chances. myft := make(FunctionTable) for n, f := range ft { myft[n] = f } names := make([]string, len(myft))[0:0] for name := range myft { names = append(names, name) } sort.Strings(names) valid_selectors := "[" for i, name := range names { if i > 0 { valid_selectors += ", " } valid_selectors += fmt.Sprintf("'%s'", name) } valid_selectors += "]." L.NewTable() L.PushString("__index") L.PushGoFunction(func(L *lua.State) int { name := L.ToString(-1) if f, ok := myft[name]; ok { f() } else { base.Error().Printf("'%s' is not a valid selector, valid seletors are %s", name, valid_selectors) L.PushNil() } return 1 }) L.SetTable(-3) }
func MakeAction(name string) Action { f, ok := action_map[name] if !ok { base.Error().Printf("Unable to find an Action named '%s'", name) } return f() }
func makeAi(path string, g *game.Game, ent *game.Entity, dst_iface *game.Ai, kind game.AiKind) { ai_struct := new(Ai) ai_struct.path = path var err error ai_struct.watcher, err = fsnotify.NewWatcher() if err != nil { base.Warn().Printf("Unable to create a filewatcher - '%s' will not reload ai files dynamically: %v", path, err) ai_struct.watcher = nil } ai_struct.ent = ent ai_struct.game = g ai_struct.active_set = make(chan bool) ai_struct.active_query = make(chan bool) ai_struct.exec_query = make(chan struct{}) ai_struct.pause = make(chan struct{}) ai_struct.terminate = make(chan struct{}) ai_struct.execs = make(chan game.ActionExec) ai_struct.kind = kind err = ai_struct.setupLuaState() if err != nil { base.Error().Printf("Unable to make ai: %v", err) if ai_struct.watcher != nil { ai_struct.watcher.Close() } dst_iface = nil return } go ai_struct.masterRoutine() *dst_iface = ai_struct }
func InsertStartMenu(ui gui.WidgetParent) error { var sm StartMenu datadir := base.GetDataDir() err := base.LoadAndProcessObject(filepath.Join(datadir, "ui", "start", "layout.json"), "json", &sm.layout) if err != nil { return err } sm.buttons = []ButtonLike{ &sm.layout.Menu.Credits, &sm.layout.Menu.Versus, &sm.layout.Menu.Online, &sm.layout.Menu.Settings, } sm.layout.Menu.Credits.f = func(interface{}) { ui.RemoveChild(&sm) err := InsertCreditsMenu(ui) if err != nil { base.Error().Printf("Unable to make Credits Menu: %v", err) return } } sm.layout.Menu.Versus.f = func(interface{}) { ui.RemoveChild(&sm) err := InsertMapChooser( ui, func(name string) { ui.AddChild(MakeGamePanel(name, nil, nil, "")) }, InsertStartMenu, ) if err != nil { base.Error().Printf("Unable to make Map Chooser: %v", err) return } } sm.layout.Menu.Settings.f = func(interface{}) {} sm.layout.Menu.Online.f = func(interface{}) { ui.RemoveChild(&sm) err := InsertOnlineMenu(ui) if err != nil { base.Error().Printf("Unable to make Online Menu: %v", err) return } } ui.AddChild(&sm) return nil }
func MakeCondition(name string) Condition { maker, ok := condition_makers[name] if !ok { base.Error().Printf("Unable to find a Condition named '%s'", name) return condition_makers["Error"]() } return maker() }
func (ei *entityDef) Dims() (int, int) { if ei.Dx <= 0 || ei.Dy <= 0 { base.Error().Printf("Entity '%s' didn't have its Dims set properly", ei.Name) ei.Dx = 1 ei.Dy = 1 } return ei.Dx, ei.Dy }
func encodeActionExec(ae ActionExec) []byte { b := bytes.NewBuffer(nil) enc := gob.NewEncoder(b) err := enc.Encode(ae) if err != nil { base.Error().Printf("Failed to gob an ActionExec: %v", err) return nil } return b.Bytes() }
func decodeActionExec(b []byte) ActionExec { var ae ActionExec dec := gob.NewDecoder(bytes.NewReader(b)) err := dec.Decode(&ae) if err != nil { base.Error().Printf("Failed to ungob an ActionExec: %v", err) return nil } return ae }
func (g *Game) SetVisibility(side Side) { switch side { case SideHaunt: g.viewer.Los_tex = g.los.denizens.tex case SideExplorers: g.viewer.Los_tex = g.los.intruders.tex default: base.Error().Printf("Unable to SetVisibility for side == %d.", side) return } }
func PushSpawnRegexp(pattern string) { re, err := regexp.Compile(pattern) if err != nil { base.Error().Printf("Unable to compile regexp: '%s': %v", pattern, err) // Just duplicate the top one, since this will probably come with an // associated pop. spawn_regex = append(spawn_regex, topSpawnRegexp()) return } spawn_regex = append(spawn_regex, re) }
func PlaySound(name string, volume float64) { if system == nil { return } sound, err := system.GetEvent(name, fmod.MODE_DEFAULT) if err != nil { base.Error().Printf("Unable to get event '%s': %v", name, err) return } sound.SetVolume(volume) sound.Start() }
func (bae *BasicActionExec) SetBasicData(ent *Entity, action Action) { bae.Ent = ent.Id bae.Index = -1 for i := range ent.Actions { if ent.Actions[i] == action { bae.Index = i } } if bae.Index == -1 { base.Error().Printf("Action '%v' was unable to find itself in Entity %v's Actions: %v", action, ent, ent.Actions) } }
func (e *Entity) SetGear(gear_name string) bool { if e.ExplorerEnt == nil { base.Error().Printf("Tried to set gear on a non-explorer entity.") return false } if e.ExplorerEnt.Gear != nil && gear_name != "" { base.Error().Printf("Tried to set gear on an explorer that already had gear.") return false } if e.ExplorerEnt.Gear == nil && gear_name == "" { base.Error().Printf("Tried to remove gear from an explorer with no gear.") return false } if gear_name == "" { algorithm.Choose(&e.Actions, func(a Action) bool { return a.String() != e.ExplorerEnt.Gear.Action }) if e.ExplorerEnt.Gear.Condition != "" { e.Stats.RemoveCondition(e.ExplorerEnt.Gear.Condition) } e.ExplorerEnt.Gear = nil return true } var g Gear g.Defname = gear_name base.GetObject("gear", &g) if g.Name == "" { base.Error().Printf("Tried to load gear '%s' that doesn't exist.", gear_name) return false } e.ExplorerEnt.Gear = &g if g.Action != "" { e.Actions = append(e.Actions, MakeAction(g.Action)) } if g.Condition != "" { e.Stats.ApplyCondition(status.MakeCondition(g.Condition)) } return true }
func (ei *entityDef) Side() Side { types := 0 if ei.ExplorerEnt != nil { types++ } if ei.HauntEnt != nil { types++ } if ei.ObjectEnt != nil { types++ } if types > 1 { base.Error().Printf("Entity '%s' must specify exactly zero or one ent type.", ei.Name) return SideNone } switch { case ei.ExplorerEnt != nil: return SideExplorers case ei.HauntEnt != nil: switch ei.HauntEnt.Level { case LevelMinion: case LevelMaster: case LevelServitor: default: base.Error().Printf("Entity '%s' speciied unknown level '%s'.", ei.Name, ei.HauntEnt.Level) } return SideHaunt case ei.ObjectEnt != nil: return SideObject default: return SideNpc } return SideNone }
func (a *Move) Maintain(dt int64, g *game.Game, ae game.ActionExec) game.MaintenanceStatus { if ae != nil { exec := ae.(*moveExec) a.ent = g.EntityById(ae.EntityId()) if len(exec.Path) == 0 { base.Error().Printf("Got a move exec with a path length of 0: %v", exec) return game.Complete } a.cost = exec.measureCost(a.ent, g) if a.cost > a.ent.Stats.ApCur() { base.Error().Printf("Got a move that required more ap than available: %v", exec) base.Error().Printf("Path: %v", exec.Path) return game.Complete } if a.cost == -1 { base.Error().Printf("Got a move that followed an invalid path: %v", exec) base.Error().Printf("Path: %v", exec.Path) if a.ent == nil { base.Error().Printf("ENT was Nil!") } else { x, y := a.ent.Pos() v := g.ToVertex(x, y) base.Error().Printf("Ent pos: (%d, %d) -> (%d)", x, y, v) } return game.Complete } algorithm.Map2(exec.Path, &a.path, func(v int) [2]int { _, x, y := g.FromVertex(v) return [2]int{x, y} }) base.Log().Printf("Path Validated: %v", exec) a.ent.Stats.ApplyDamage(-a.cost, 0, status.Unspecified) src := g.ToVertex(a.ent.Pos()) graph := g.Graph(a.ent.Side(), true, nil) a.drawPath(a.ent, g, graph, src) } // Do stuff factor := float32(math.Pow(2, a.ent.Walking_speed)) dist := a.ent.DoAdvance(factor*float32(dt)/200, a.path[0][0], a.path[0][1]) for dist > 0 { if len(a.path) == 1 { a.ent.DoAdvance(0, 0, 0) a.ent.Info.RoomsExplored[a.ent.CurrentRoom()] = true a.ent = nil return game.Complete } a.path = a.path[1:] a.ent.Info.RoomsExplored[a.ent.CurrentRoom()] = true dist = a.ent.DoAdvance(dist, a.path[0][0], a.path[0][1]) } return game.InProgress }
func (g *Game) mergeLos(side Side) { var pix [][]byte switch side { case SideHaunt: pix = g.los.denizens.tex.Pix() case SideExplorers: pix = g.los.intruders.tex.Pix() default: base.Error().Printf("Unable to mergeLos on side %d.", side) return } for i := range g.los.full_merger { g.los.full_merger[i] = false } for _, ent := range g.Ents { if ent.Side() != side && !ent.Enemy_los { continue } if ent.los == nil { continue } for i := ent.los.minx; i <= ent.los.maxx; i++ { for j := ent.los.miny; j <= ent.los.maxy; j++ { if ent.los.grid[i][j] { g.los.merger[i][j] = true } } } } for i := 0; i < len(pix); i++ { for j := 0; j < len(pix); j++ { if g.los.merger[i][j] { continue } if pix[i][j] >= house.LosVisibilityThreshold { pix[i][j] = house.LosVisibilityThreshold - 1 } } } for i := range g.los.merger { for j := range g.los.merger[i] { if g.los.merger[i][j] { if pix[i][j] < house.LosVisibilityThreshold { pix[i][j] = house.LosVisibilityThreshold } } } } }
func doChooserMenu(ui gui.WidgetParent, cm chooserMaker, r replacer, i inserter) error { chooser, done, err := cm() if err != nil { return err } ui.AddChild(chooser) go func() { m := <-done ui.RemoveChild(chooser) if m != nil { base.Log().Printf("Chose: %v", m) err = i(ui, r) if err != nil { base.Error().Printf("Error making menu: %v", err) } } else { err := r(ui) if err != nil { base.Error().Printf("Error replacing menu: %v", err) } } }() return nil }
func insertGoalMenu(ui gui.WidgetParent, replace replacer) error { chooser, done, err := makeChooseGoalMenu() if err != nil { return err } ui.AddChild(chooser) go func() { m := <-done ui.RemoveChild(chooser) if m != nil { base.Log().Printf("Chose: %v", m) err = insertGoalMenu(ui, replace) if err != nil { base.Error().Printf("Error making goal menu: %v", err) } } else { err := replace(ui) if err != nil { base.Error().Printf("Error replacing menu: %v", err) } } }() return nil }
func (bae BasicActionExec) Push(L *lua.State, g *Game) { ent := g.EntityById(bae.Ent) if bae.Index < 0 || bae.Index >= len(ent.Actions) { base.Error().Printf("Tried to push an exec for an invalid action index: '%s' %d.", ent.Name) L.PushNil() return } L.NewTable() L.PushString("Action") ent.Actions[bae.Index].Push(L) L.SetTable(-3) L.PushString("Ent") LuaPushEntity(L, ent) L.SetTable(-3) }
func Init() { var err error system, err = fmod.EventSystemCreate() if err != nil { base.Error().Printf("Unable to create sound system: %v", err) return } err = system.Init(32, fmod.INIT_NORMAL, nil, fmod.EVENT_INIT_NORMAL) if err != nil { base.Error().Printf("Unable to initialize sound system: %v", err) return } version, _ := system.GetVersion() base.Log().Printf("Fmod version %x", version) err = system.SetMediaPath(filepath.Join(base.GetDataDir(), "sound") + "/") if err != nil { base.Error().Printf("Unable to set media path: %v\n", err) return } err = system.LoadPath("Haunts.fev", nil) if err != nil { base.Error().Printf("Unable to load fev: %v\n", err) return } freq = time.Millisecond * 3 approach = 0.01 music_volume = make(chan float64, 1) music_start = make(chan string, 1) param_control = make(chan paramRequest, 1) music_stop = make(chan bool, 1) go musicControl() }
// Tries to place new_ent in the game at its current position. Returns true // on success, false otherwise. // pattern is a regexp that matches only the names of all valid spawn points. func (g *Game) placeEntity(pattern string) bool { if g.new_ent == nil { base.Log().Printf("No new ent") return false } re, err := regexp.Compile(pattern) if err != nil { base.Error().Printf("Failed to compile regexp: '%s': %v", pattern, err) return false } g.new_ent.Info.RoomsExplored[g.new_ent.CurrentRoom()] = true ix, iy := int(g.new_ent.X), int(g.new_ent.Y) idx, idy := g.new_ent.Dims() r, f, _ := g.House.Floors[0].RoomFurnSpawnAtPos(ix, iy) if r == nil || f != nil { return false } for _, e := range g.Ents { x, y := e.Pos() dx, dy := e.Dims() r1 := image.Rect(x, y, x+dx, y+dy) r2 := image.Rect(ix, iy, ix+idx, iy+idy) if r1.Overlaps(r2) { return false } } // Check for spawn points for _, spawn := range g.House.Floors[0].Spawns { if !re.MatchString(spawn.Name) { continue } x, y := spawn.Pos() dx, dy := spawn.Dims() if ix < x || ix+idx > x+dx { continue } if iy < y || iy+idy > y+dy { continue } g.Ents = append(g.Ents, g.new_ent) g.new_ent = nil return true } return false }
func MakeSystemMenu(gp *GamePanel, player *Player) (gui.Widget, error) { var sm SystemMenu datadir := base.GetDataDir() err := base.LoadAndProcessObject(filepath.Join(datadir, "ui", "system", "layout.json"), "json", &sm.layout) if err != nil { return nil, err } sm.layout.Main.f = func(interface{}) {} sm.buttons = []ButtonLike{ &sm.layout.Sub.Return, &sm.layout.Sub.Save, } sm.layout.Sub.Return.f = func(_ui interface{}) { ui := _ui.(*gui.Gui) gp.game.Ents = nil gp.game.Think(1) // This should clean things up ui.DropFocus() Restart() } sm.layout.Sub.Save.Entry.text = player.Name sm.layout.Sub.Save.Button.f = func(interface{}) { UpdatePlayer(player, gp.script.L) str, err := base.ToGobToBase64(gp.game) if err != nil { base.Error().Printf("Error gobbing game state: %v", err) return } player.Game_state = str player.Name = sm.layout.Sub.Save.Text() player.No_init = true base.Log().Printf("Saving player: %v", player) err = SavePlayer(player) if err != nil { base.Warn().Printf("Unable to save player: %v", err) return } sm.saved_time = time.Now() sm.saved_alpha = 1.0 } return &sm, nil }
func MakeRosterChooser(options []Option, selector Selector, on_complete func(map[int]bool), on_undo func()) *RosterChooser { var rc RosterChooser rc.options = options err := base.LoadAndProcessObject(filepath.Join(base.GetDataDir(), "ui", "widgets", "roster_chooser.json"), "json", &rc.layout) if err != nil { base.Error().Printf("Failed to create RosterChooser: %v", err) return nil } rc.Request_dims = gui.Dims{ rc.layout.Down.Data().Dx() + rc.layout.Option.Dx, rc.layout.Num_options*rc.layout.Option.Dy + 2*int(base.GetDictionary(15).MaxHeight()), } rc.selected = make(map[int]bool) rc.selector = selector rc.on_complete = on_complete rc.on_undo = on_undo rc.render.options = make([]gui.Region, len(rc.options)) return &rc }