func removeEnt(gp *GamePanel) lua.GoFunction { return func(L *lua.State) int { if !LuaCheckParamsOk(L, "RemoveEnt", LuaEntity) { return 0 } ent := LuaToEntity(L, gp.game, -1) if ent == nil { base.Warn().Printf("Tried to RemoveEnt on an entity that doesn't exist.") return 0 } removed := false for i := range gp.game.Ents { if gp.game.Ents[i] == ent { gp.game.Ents[i] = gp.game.Ents[len(gp.game.Ents)-1] gp.game.Ents = gp.game.Ents[0 : len(gp.game.Ents)-1] gp.game.viewer.RemoveDrawable(ent) removed = true break } } if !removed { base.Warn().Printf("Tried to RemoveEnt an entity that wasn't in the game.") } return 0 } }
// Returns a map from player name to the path of that player's file. func GetAllPlayers() map[string]string { root := filepath.Join(base.GetDataDir(), "players") players := make(map[string]string) filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if info.IsDir() { return nil } f, err := os.Open(path) if err != nil { base.Warn().Printf("Unable to open player file: %s.", path) return nil } defer f.Close() dec := gob.NewDecoder(f) var name string err = dec.Decode(&name) if err != nil { base.Warn().Printf("Unable to read player file: %s.", path) return nil } players[name] = path return nil }) return players }
func setCondition(gp *GamePanel) lua.GoFunction { return func(L *lua.State) int { if !LuaCheckParamsOk(L, "SetCondition", LuaEntity, LuaString, LuaBoolean) { return 0 } gp.script.syncStart() defer gp.script.syncEnd() ent := LuaToEntity(L, gp.game, -3) if ent == nil { base.Warn().Printf("Tried to SetCondition on an entity that doesn't exist.") return 0 } if ent.Stats == nil { base.Warn().Printf("Tried to SetCondition on an entity that doesn't have stats.") return 0 } name := L.ToString(-2) if L.ToBoolean(-1) { ent.Stats.ApplyCondition(status.MakeCondition(name)) } else { ent.Stats.RemoveCondition(name) } return 0 } }
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 ai_struct.setupLuaState() go ai_struct.masterRoutine() *dst_iface = ai_struct }
func (g *Game) TeamLos(side Side, x, y, dx, dy int) bool { var team_los [][]byte if side == SideExplorers { team_los = g.los.intruders.tex.Pix() } else if side == SideHaunt { team_los = g.los.denizens.tex.Pix() } else { base.Warn().Printf("Can only ask for TeamLos for the intruders and denizens.") return false } if team_los == nil { return false } for i := x; i < x+dx; i++ { for j := y; j < y+dy; j++ { if i < 0 || j < 0 || i >= len(team_los) || j >= len(team_los[0]) { continue } if team_los[i][j] >= house.LosVisibilityThreshold { return true } } } return false }
func (b *Button) RenderAt(x, y int) { gl.Color4ub(255, 255, 255, byte(b.shade*255)) if b.Texture.Path != "" { b.Texture.Data().RenderNatural(b.X+x, b.Y+y) b.bounds.x = b.X + x b.bounds.y = b.Y + y b.bounds.dx = b.Texture.Data().Dx() b.bounds.dy = b.Texture.Data().Dy() } else { d := base.GetDictionary(b.Text.Size) b.bounds.x = b.X + x b.bounds.y = b.Y + y b.bounds.dx = int(d.StringWidth(b.Text.String)) b.bounds.dy = int(d.MaxHeight()) base.Log().Printf("Button '%s' @ %d %d %d %d", b.Text.String, b.bounds.x, b.bounds.y, b.bounds.dx, b.bounds.dy) var just gui.Justification switch b.Text.Justification { case "center": just = gui.Center b.bounds.x -= b.bounds.dx / 2 case "left": just = gui.Left case "right": just = gui.Right b.bounds.x -= b.bounds.dx default: just = gui.Center b.bounds.x -= b.bounds.dx / 2 b.Text.Justification = "center" base.Warn().Printf("Failed to indicate valid aligmnent, '%s' is not valid.", b.Text.Justification) } d.RenderString(b.Text.String, float64(b.X+x), float64(b.Y+y), 0, d.MaxHeight(), just) } }
func UpdatePlayer(p *Player, L *lua.State) { buffer := bytes.NewBuffer(nil) L.GetGlobal("store") err := LuaEncodeTable(buffer, L, -1) if err != nil { base.Warn().Printf("Error encoding lua state: %v", err) } L.Pop(1) p.Lua_store = buffer.Bytes() }
func setAp(gp *GamePanel) lua.GoFunction { return func(L *lua.State) int { if !LuaCheckParamsOk(L, "SetAp", LuaEntity, LuaInteger) { return 0 } gp.script.syncStart() defer gp.script.syncEnd() ent := LuaToEntity(L, gp.game, -2) if ent == nil { base.Warn().Printf("Tried to SetAp on an entity that doesn't exist.") return 0 } if ent.Stats == nil { base.Warn().Printf("Tried to SetAp on an entity that doesn't have stats.") return 0 } ent.Stats.SetAp(L.ToInteger(-1)) return 0 } }
// Returns true iff the action was set // This function will return false if there is no selected entity, if the // action cannot be selected (because it is invalid or the entity has // insufficient Ap), or if there is an action currently executing. func (g *Game) SetCurrentAction(action Action) bool { if g.Action_state != noAction && g.Action_state != preppingAction { return false } // the action should be one that belongs to the current entity, if not then // we need to bail out immediately if g.selected_ent == nil { base.Warn().Printf("Tried to SetCurrentAction() without a selected entity.") return action == nil } if action != nil { valid := false for _, a := range g.selected_ent.Actions { if a == action { valid = true break } } if !valid { base.Warn().Printf("Tried to SetCurrentAction() with an action that did not belong to the selected entity.") return action == nil } } if g.current_action != nil { g.current_action.Cancel() } if action == nil { g.Action_state = noAction } else { g.Action_state = preppingAction } g.viewer.RemoveFloorDrawable(g.current_action) g.current_action = action if g.current_action != nil { g.viewer.AddFloorDrawable(g.current_action) } return true }
func (g *Game) SpawnEntity(spawn *Entity, x, y int) bool { for i := range g.Ents { cx, cy := g.Ents[i].Pos() if cx == x && cy == y { base.Warn().Printf("Can't spawn entity at (%d, %d) - already occupied by '%s'.", x, y, g.Ents[i].Name) return false } } spawn.X = float64(x) spawn.Y = float64(y) spawn.Info.RoomsExplored[spawn.CurrentRoom()] = true g.Ents = append(g.Ents, spawn) return true }
// Distributes the ents among the spawn points. Since this is done randomly // it might not work, so there is a very small chance that not all spawns will // have an ent given to them, even if it is possible to distrbiute them // properly. Regardless, at least some will be spawned. func spawnEnts(g *Game, ents []*Entity, spawns []*house.SpawnPoint) { sort.Sort(orderSpawnsSmallToBig(spawns)) sanity := 100 var places []entSpawnPair for sanity > 0 { sanity-- places = places[0:0] sort.Sort(orderEntsBigToSmall(ents)) //slightly shuffle the ents for i := range ents { j := i + rand.Intn(5) - 2 if j >= 0 && j < len(ents) { ents[i], ents[j] = ents[j], ents[i] } } // Go through each ent and try to place it in an unused spawn point used_spawns := make(map[*house.SpawnPoint]bool) for _, ent := range ents { for _, spawn := range spawns { if used_spawns[spawn] { continue } if spawn.Dx < ent.Dx || spawn.Dy < ent.Dy { continue } used_spawns[spawn] = true places = append(places, entSpawnPair{ent, spawn}) break } } if len(places) == len(spawns) { break } } if sanity > 0 { base.Log().Printf("Placed all objects with %d sanity remaining", sanity) } else { base.Warn().Printf("Only able to place %d out of %d objects", len(places), len(spawns)) } for _, place := range places { place.ent.X = float64(place.spawn.X + rand.Intn(place.spawn.Dx-place.ent.Dx+1)) place.ent.Y = float64(place.spawn.Y + rand.Intn(place.spawn.Dy-place.ent.Dy+1)) g.viewer.AddDrawable(place.ent) g.Ents = append(g.Ents, place.ent) base.Log().Printf("Using object '%s' at (%.0f, %.0f)", place.ent.Name, place.ent.X, place.ent.Y) } }
func setPosition(gp *GamePanel) lua.GoFunction { return func(L *lua.State) int { if !LuaCheckParamsOk(L, "SetPosition", LuaEntity, LuaPoint) { return 0 } gp.script.syncStart() defer gp.script.syncEnd() ent := LuaToEntity(L, gp.game, -2) if ent == nil { base.Warn().Printf("Tried to SetPosition on an entity that doesn't exist.") return 0 } x, y := LuaToPoint(L, -1) ent.X = float64(x) ent.Y = float64(y) return 0 } }
func (t *TextArea) RenderString(s string) { var just gui.Justification switch t.Justification { case "center": just = gui.Center case "left": just = gui.Left case "right": just = gui.Right default: base.Warn().Printf("Unknown justification '%s' in main gui bar.", t.Justification) t.Justification = "center" } px := float64(t.X) py := float64(t.Y) d := base.GetDictionary(t.Size) d.RenderString(s, px, py, 0, d.MaxHeight(), just) }
func saveStore(gp *GamePanel, player *Player) lua.GoFunction { return func(L *lua.State) int { if !LuaCheckParamsOk(L, "SaveStore") { return 0 } gp.script.syncStart() defer gp.script.syncEnd() UpdatePlayer(player, gp.script.L) str, err := base.ToGobToBase64(gp.game) if err != nil { base.Error().Printf("Error gobbing game state: %v", err) return 0 } player.Game_state = str player.Name = "autosave" err = SavePlayer(player) if err != nil { base.Warn().Printf("Unable to save player: %v", err) } return 0 } }
func playAnimations(gp *GamePanel) lua.GoFunction { return func(L *lua.State) int { if !LuaCheckParamsOk(L, "PlayAnimations", LuaEntity, LuaArray) { return 0 } gp.script.syncStart() ent := LuaToEntity(L, gp.game, -2) if ent == nil { base.Warn().Printf("Tried to PlayAnimation on an entity that doesn't exist.") return 0 } gp.script.syncEnd() ent.Sprite().Wait([]string{"ready", "killed"}) if ent.Sprite().AnimState() == "ready" { L.PushNil() for L.Next(-2) != 0 { ent.Sprite().Command(L.ToString(-1)) L.Pop(1) } ent.Sprite().Wait([]string{"ready", "killed"}) } return 0 } }
func (m *MainBar) Draw(region gui.Region) { m.region = region gl.Enable(gl.TEXTURE_2D) m.layout.Background.Data().Bind() gl.Color4d(1, 1, 1, 1) gl.Begin(gl.QUADS) gl.TexCoord2d(0, 0) gl.Vertex2i(region.X, region.Y) gl.TexCoord2d(0, -1) gl.Vertex2i(region.X, region.Y+region.Dy) gl.TexCoord2d(1, -1) gl.Vertex2i(region.X+region.Dx, region.Y+region.Dy) gl.TexCoord2d(1, 0) gl.Vertex2i(region.X+region.Dx, region.Y) gl.End() buttons := m.no_actions_buttons if m.ent != nil && len(m.ent.Actions) > m.layout.Actions.Count { buttons = m.all_buttons } for _, button := range buttons { button.RenderAt(region.X, region.Y) } ent := m.game.HoveredEnt() if ent == nil { ent = m.ent } if ent != nil && ent.Stats != nil { gl.Color4d(1, 1, 1, 1) ent.Still.Data().Bind() tdx := ent.Still.Data().Dx() tdy := ent.Still.Data().Dy() cx := region.X + m.layout.CenterStillFrame.X cy := region.Y + m.layout.CenterStillFrame.Y gl.Begin(gl.QUADS) gl.TexCoord2d(0, 0) gl.Vertex2i(cx-tdx/2, cy-tdy/2) gl.TexCoord2d(0, -1) gl.Vertex2i(cx-tdx/2, cy+tdy/2) gl.TexCoord2d(1, -1) gl.Vertex2i(cx+tdx/2, cy+tdy/2) gl.TexCoord2d(1, 0) gl.Vertex2i(cx+tdx/2, cy-tdy/2) gl.End() m.layout.Name.RenderString(ent.Name) m.layout.Ap.RenderString(fmt.Sprintf("Ap:%d", ent.Stats.ApCur())) m.layout.Hp.RenderString(fmt.Sprintf("Hp:%d", ent.Stats.HpCur())) m.layout.Corpus.RenderString(fmt.Sprintf("Corpus:%d", ent.Stats.Corpus())) m.layout.Ego.RenderString(fmt.Sprintf("Ego:%d", ent.Stats.Ego())) gl.Color4d(1, 1, 1, 1) m.layout.Divider.Data().Bind() tdx = m.layout.Divider.Data().Dx() tdy = m.layout.Divider.Data().Dy() cx = region.X + m.layout.Name.X cy = region.Y + m.layout.Name.Y - 5 gl.Begin(gl.QUADS) gl.TexCoord2d(0, 0) gl.Vertex2i(cx-tdx/2, cy-tdy/2) gl.TexCoord2d(0, -1) gl.Vertex2i(cx-tdx/2, cy+(tdy+1)/2) gl.TexCoord2d(1, -1) gl.Vertex2i(cx+(tdx+1)/2, cy+(tdy+1)/2) gl.TexCoord2d(1, 0) gl.Vertex2i(cx+(tdx+1)/2, cy-tdy/2) gl.End() } if m.ent != nil && m.ent.Stats != nil { // Actions { spacing := m.layout.Actions.Icon_size * float64(m.layout.Actions.Count) spacing = m.layout.Actions.Width - spacing spacing /= float64(m.layout.Actions.Count - 1) m.state.Actions.space = spacing s := m.layout.Actions.Icon_size num_actions := len(m.ent.Actions) xpos := m.layout.Actions.X if num_actions > m.layout.Actions.Count { xpos -= m.state.Actions.scroll_pos * (s + spacing) } d := base.GetDictionary(10) var r gui.Region r.X = int(m.layout.Actions.X) r.Y = int(m.layout.Actions.Y - d.MaxHeight()) r.Dx = int(m.layout.Actions.Width) r.Dy = int(m.layout.Actions.Icon_size + d.MaxHeight()) r.PushClipPlanes() gl.Color4d(1, 1, 1, 1) for i, action := range m.ent.Actions { // Highlight the selected action if action == m.game.current_action { gl.Disable(gl.TEXTURE_2D) gl.Color4d(1, 0, 0, 1) gl.Begin(gl.QUADS) gl.Vertex3d(xpos-2, m.layout.Actions.Y-2, 0) gl.Vertex3d(xpos-2, m.layout.Actions.Y+s+2, 0) gl.Vertex3d(xpos+s+2, m.layout.Actions.Y+s+2, 0) gl.Vertex3d(xpos+s+2, m.layout.Actions.Y-2, 0) gl.End() } gl.Enable(gl.TEXTURE_2D) action.Icon().Data().Bind() if action.Preppable(m.ent, m.game) { gl.Color4d(1, 1, 1, 1) } else { gl.Color4d(0.5, 0.5, 0.5, 1) } gl.Begin(gl.QUADS) gl.TexCoord2d(0, 0) gl.Vertex3d(xpos, m.layout.Actions.Y, 0) gl.TexCoord2d(0, -1) gl.Vertex3d(xpos, m.layout.Actions.Y+s, 0) gl.TexCoord2d(1, -1) gl.Vertex3d(xpos+s, m.layout.Actions.Y+s, 0) gl.TexCoord2d(1, 0) gl.Vertex3d(xpos+s, m.layout.Actions.Y, 0) gl.End() gl.Disable(gl.TEXTURE_2D) ypos := m.layout.Actions.Y - d.MaxHeight() - 2 d.RenderString(fmt.Sprintf("%d", i+1), xpos+s/2, ypos, 0, d.MaxHeight(), gui.Center) xpos += spacing + m.layout.Actions.Icon_size } r.PopClipPlanes() // Now, if there is a selected action, position it between the arrows if m.state.Actions.selected != nil { // a := m.state.Actions.selected d := base.GetDictionary(15) x := m.layout.Actions.X + m.layout.Actions.Width/2 y := float64(m.layout.ActionLeft.Y) str := fmt.Sprintf("%s:%dAP", m.state.Actions.selected.String(), m.state.Actions.selected.AP()) gl.Color4d(1, 1, 1, 1) d.RenderString(str, x, y, 0, d.MaxHeight(), gui.Center) } } // Conditions { gl.Color4d(1, 1, 1, 1) c := m.layout.Conditions d := base.GetDictionary(int(c.Size)) ypos := c.Y + c.Height - d.MaxHeight() + m.state.Conditions.scroll_pos var r gui.Region r.X = int(c.X) r.Y = int(c.Y) r.Dx = int(c.Width) r.Dy = int(c.Height) r.PushClipPlanes() for _, s := range m.ent.Stats.ConditionNames() { d.RenderString(s, c.X+c.Width/2, ypos, 0, d.MaxHeight(), gui.Center) ypos -= float64(d.MaxHeight()) } r.PopClipPlanes() } // Gear if m.ent.ExplorerEnt != nil && m.ent.ExplorerEnt.Gear != nil { gear := m.ent.ExplorerEnt.Gear layout := m.layout.Gear icon := gear.Small_icon.Data() icon.RenderNatural(int(layout.X), int(layout.Y)) d := base.GetDictionary(10) d.RenderString("Gear", layout.X+float64(icon.Dx())/2, layout.Y-d.MaxHeight(), 0, d.MaxHeight(), gui.Center) } } // Mouseover text if m.state.MouseOver.active { var x int switch m.state.MouseOver.location { case mouseOverActions: x = int(m.layout.Actions.X + m.layout.Actions.Width/2) case mouseOverConditions: x = int(m.layout.Conditions.X + m.layout.Conditions.Width/2) case mouseOverGear: default: base.Warn().Printf("Got an unknown mouseover location: %d", m.state.MouseOver.location) m.state.MouseOver.active = false } y := m.layout.Background.Data().Dy() - 40 d := base.GetDictionary(15) d.RenderString(m.state.MouseOver.text, float64(x), float64(y), 0, d.MaxHeight(), gui.Center) } }
// Returns an array of all entities of a specified type that are in this // entity's los. The entities in the array will be sorted in ascending order // of distance from this entity. // Format // ents = nearestNEntites(max, kind) // // Input: // max - integer - Maximum number of entities to return // kind - string - One of "intruder" "denizen" "minion" "servitor" // "master" "non-minion" "non-servitor" "non-master" and // "all". The "non-*" parameters indicate denizens only // (i.e. will *not* include intruders) that are not of the // type specified. // // Output: // ents - array[integer] - Array of entity ids. func NearestNEntitiesFunc(me *game.Entity) lua.GoFunction { valid_kinds := map[string]bool{ "intruder": true, "denizen": true, "minion": true, "servitor": true, "master": true, "object": true, } return func(L *lua.State) int { if !game.LuaCheckParamsOk(L, "NearestNEntities", game.LuaInteger, game.LuaString) { return 0 } g := me.Game() max := L.ToInteger(-2) kind := L.ToString(-1) if !valid_kinds[kind] { err_str := fmt.Sprintf("NearestNEntities expects kind in the set ['intruder' 'denizen' 'servitor' 'master' 'minion'], got %s.", kind) base.Warn().Printf(err_str) L.PushString(err_str) L.Error() return 0 } var eds entityDistSlice for _, ent := range g.Ents { if ent.Stats != nil && ent.Stats.HpCur() <= 0 { continue } switch kind { case "intruder": if ent.Side() != game.SideExplorers { continue } case "denizen": if ent.Side() != game.SideHaunt { continue } case "minion": if ent.HauntEnt == nil || ent.HauntEnt.Level != game.LevelMinion { continue } case "servitor": if ent.HauntEnt == nil || ent.HauntEnt.Level != game.LevelServitor { continue } case "master": if ent.HauntEnt == nil || ent.HauntEnt.Level != game.LevelMaster { continue } case "object": if ent.ObjectEnt == nil { continue } } x, y := ent.Pos() dx, dy := ent.Dims() if !me.HasTeamLos(x, y, dx, dy) { continue } eds = append(eds, entityDist{rangedDistBetween(me, ent), ent}) } // TODO: ONLY GUYS THAT EXIST sort.Sort(eds) if max > len(eds) { max = len(eds) } if max < 0 { max = 0 } eds = eds[0:max] // eds contains the results, in order. Now we make a lua table and // populate it with the entity ids of the results. L.NewTable() for i := range eds { L.PushInteger(i + 1) game.LuaPushEntity(L, eds[i].ent) L.SetTable(-3) } return 1 } }
func (g *Game) Think(dt int64) { for _, ent := range g.Ents { if !g.all_ents_in_game[ent] { g.all_ents_in_game[ent] = true g.all_ents_in_memory[ent] = true } } var mark []*Entity for ent := range g.all_ents_in_memory { if !g.all_ents_in_game[ent] && ent != g.new_ent { mark = append(mark, ent) } } for _, ent := range mark { delete(g.all_ents_in_game, ent) delete(g.all_ents_in_memory, ent) ent.Release() } // Figure out if there are any entities that might be occluded be any // furniture, if so we'll want to make that furniture a little transparent. for _, floor := range g.House.Floors { for _, room := range floor.Rooms { for _, furn := range room.Furniture { if !furn.Blocks_los { continue } rx, ry := room.Pos() x, y2 := furn.Pos() x += rx y2 += ry dx, dy := furn.Dims() x2 := x + dx y := y2 + dy tex := furn.Orientations[furn.Rotation].Texture.Data() tex_dy := 2 * (tex.Dy() * ((x2 - y2) - (x - y))) / tex.Dx() v1 := y - x v2 := y2 - x2 hit := false for _, ent := range g.Ents { ex, ey2 := ent.Pos() edx, edy := ent.Dims() ex2 := ex + edx ey := ey2 + edy if ex+ey2 < x+y2 || ex+ey2 > x+y2+tex_dy { continue } if ent.Side() != g.Side && !g.TeamLos(g.Side, ex, ey2, edx, edy) { continue } ev1 := ey - ex ev2 := ey2 - ex2 if ev2 >= v1 || ev1 <= v2 { continue } hit = true break } alpha := furn.Alpha() if hit { furn.SetAlpha(doApproach(alpha, 0.3, dt)) } else { furn.SetAlpha(doApproach(alpha, 1.0, dt)) } } } } switch g.Turn_state { case turnStateInit: select { case <-g.comm.script_to_game: base.Log().Printf("ScriptComm: change to turnStateStart") g.Turn_state = turnStateStart g.script.OnRound(g) // g.OnRound() default: } case turnStateStart: select { case <-g.comm.script_to_game: base.Log().Printf("ScriptComm: change to turnStateAiAction") g.Turn_state = turnStateAiAction default: } case turnStateScriptOnAction: select { case exec := <-g.comm.script_to_game: if g.current_exec != nil { base.Error().Printf("Got an exec from the script when we already had one pending.") } else { if exec != nil { g.current_exec = exec.(ActionExec) } else { base.Log().Printf("ScriptComm: change to turnStateAiAction") g.Turn_state = turnStateAiAction } } default: } case turnStateMainPhaseOver: select { case exec := <-g.comm.script_to_game: if exec != nil { base.Log().Printf("ScriptComm: Got an exec: %v", exec) g.current_exec = exec.(ActionExec) g.Action_state = doingAction ent := g.EntityById(g.current_exec.EntityId()) g.viewer.RemoveFloorDrawable(g.current_action) g.current_action = ent.Actions[g.current_exec.ActionIndex()] g.viewer.AddFloorDrawable(g.current_action) ent.current_action = g.current_action } else { g.Turn_state = turnStateEnd base.Log().Printf("ScriptComm: change to turnStateEnd for realzes") } default: base.Log().Printf("ScriptComm: turnStateMainPhaseOver default") } case turnStateEnd: select { case <-g.comm.script_to_game: g.Turn_state = turnStateStart base.Log().Printf("ScriptComm: change to turnStateStart") g.OnRound() default: } } if g.current_exec != nil && g.Action_state != verifyingAction && g.Turn_state != turnStateMainPhaseOver { ent := g.EntityById(g.current_exec.EntityId()) g.viewer.RemoveFloorDrawable(g.current_action) g.current_action = ent.Actions[g.current_exec.ActionIndex()] g.viewer.AddFloorDrawable(g.current_action) ent.current_action = g.current_action g.Action_state = verifyingAction base.Log().Printf("ScriptComm: request exec verification") g.comm.game_to_script <- g.current_exec } if g.Action_state == verifyingAction { select { case <-g.comm.script_to_game: g.Action_state = doingAction default: } } // If there is an action that is currently executing we need to advance that // action. if g.Action_state == doingAction { res := g.current_action.Maintain(dt, g, g.current_exec) if g.current_exec != nil { base.Log().Printf("ScriptComm: sent action") g.current_exec = nil } switch res { case Complete: g.current_action.Cancel() g.viewer.RemoveFloorDrawable(g.current_action) g.current_action = nil g.Action_state = noAction if g.Turn_state != turnStateMainPhaseOver { g.Turn_state = turnStateScriptOnAction } base.Log().Printf("ScriptComm: Action complete") g.comm.game_to_script <- nil g.checkWinConditions() case InProgress: case CheckForInterrupts: } } for _, ent := range g.Ents { ent.Think(dt) s := ent.Sprite() if s.AnimState() == "ready" && s.Idle() && g.current_action == nil && ent.current_action != nil { g.viewer.RemoveFloorDrawable(g.current_action) ent.current_action = nil } } if g.new_ent != nil { g.new_ent.Think(dt) } for i := range g.Ents { g.UpdateEntLos(g.Ents[i], false) } if g.los.denizens.mode == LosModeEntities { g.mergeLos(SideHaunt) } if g.los.intruders.mode == LosModeEntities { g.mergeLos(SideExplorers) } // Do spawn points los stuff for _, los := range []*spawnLos{&g.Los_spawns.Denizens, &g.Los_spawns.Intruders} { if los.r == nil || los.r.String() != los.Pattern { if los.Pattern == "" { los.r = nil } else { var err error los.r, err = regexp.Compile("^" + los.Pattern + "$") if err != nil { base.Warn().Printf("Unable to compile regexp: `%s`", los.Pattern) los.Pattern = "" } } } } for i := 0; i < 2; i++ { var los *spawnLos var pix [][]byte if i == 0 { if g.los.denizens.mode == LosModeBlind { continue } los = &g.Los_spawns.Denizens pix = g.los.denizens.tex.Pix() } else { if g.los.intruders.mode == LosModeBlind { continue } los = &g.Los_spawns.Intruders pix = g.los.intruders.tex.Pix() } if los.r == nil { continue } for _, spawn := range g.House.Floors[0].Spawns { if !los.r.MatchString(spawn.Name) { continue } sx, sy := spawn.Pos() dx, dy := spawn.Dims() for x := sx; x < sx+dx; x++ { for y := sy; y < sy+dy; y++ { if pix[x][y] < house.LosVisibilityThreshold { pix[x][y] = house.LosVisibilityThreshold } } } } } for _, tex := range []*house.LosTexture{g.los.denizens.tex, g.los.intruders.tex} { pix := tex.Pix() amt := dt/6 + 1 mod := false for i := range pix { for j := range pix[i] { v := int64(pix[i][j]) if v < house.LosVisibilityThreshold { v -= amt } else { v += amt } if v < house.LosMinVisibility { v = house.LosMinVisibility } if v < 0 { v = 0 } if v > 255 { v = 255 } mod = mod || (byte(v) != pix[i][j]) pix[i][j] = byte(v) } } if mod { tex.Remap() } } // Don't do any ai stuff if there is a pending action if g.current_action != nil { return } // Also don't do an ai stuff if this isn't the appropriate state if g.Turn_state != turnStateAiAction { return } // If any entities are not either ready or dead let's wait until they are // before we do any of the ai stuff for _, ent := range g.Ents { if ent.Side() != SideHaunt && ent.Side() != SideExplorers { // Relics and cleanse points and whatnot matter here, and they might not // be in a 'ready' state. continue } state := ent.sprite.Sprite().AnimState() if state != "ready" && state != "killed" { return } if !ent.sprite.Sprite().Idle() { return } } // Do Ai - if there is any to do if g.Side == SideHaunt { if g.Ai.minions.Active() { g.active_ai = g.Ai.minions g.Action_state = waitingAction } else { if g.Ai.denizens.Active() { g.active_ai = g.Ai.denizens g.Action_state = waitingAction } } } else { if g.Ai.intruders.Active() { g.active_ai = g.Ai.intruders g.Action_state = waitingAction } } if g.Action_state == waitingAction { select { case exec := <-g.active_ai.ActionExecs(): if exec != nil { g.current_exec = exec } else { g.Action_state = noAction // TODO: indicate that the master ai can go now } default: } } if g.player_inactive && g.Action_state == noAction && !g.Ai.intruders.Active() && !g.Ai.denizens.Active() && !g.Ai.minions.Active() { g.Turn_state = turnStateMainPhaseOver base.Log().Printf("ScriptComm: change to turnStateMainPhaseOver") g.comm.game_to_script <- nil base.Log().Printf("ScriptComm: sent nil") } }
func startGameScript(gp *GamePanel, path string, player *Player, data map[string]string) { // Clear out the panel, now the script can do whatever it wants player.Script_path = path gp.AnchorBox = gui.MakeAnchorBox(gui.Dims{1024, 768}) base.Log().Printf("startGameScript") if !filepath.IsAbs(path) { path = filepath.Join(base.GetDataDir(), "scripts", filepath.FromSlash(path)) } // The game script runs in a separate go routine and functions that need to // communicate with the game will do so via channels - DUH why did i even // write this comment? prog, err := ioutil.ReadFile(path) if err != nil { base.Error().Printf("Unable to load game script file %s: %v", path, err) return } gp.script = &gameScript{} base.Log().Printf("script = %p", gp.script) gp.script.L = lua.NewState() gp.script.L.OpenLibs() gp.script.L.SetExecutionLimit(25000) gp.script.L.NewTable() LuaPushSmartFunctionTable(gp.script.L, FunctionTable{ "ChooserFromFile": func() { gp.script.L.PushGoFunctionAsCFunction(chooserFromFile(gp)) }, "StartScript": func() { gp.script.L.PushGoFunctionAsCFunction(startScript(gp, player)) }, "SaveGameState": func() { gp.script.L.PushGoFunctionAsCFunction(saveGameState(gp)) }, "LoadGameState": func() { gp.script.L.PushGoFunctionAsCFunction(loadGameState(gp)) }, "DoExec": func() { gp.script.L.PushGoFunctionAsCFunction(doExec(gp)) }, "SelectEnt": func() { gp.script.L.PushGoFunctionAsCFunction(selectEnt(gp)) }, "FocusPos": func() { gp.script.L.PushGoFunctionAsCFunction(focusPos(gp)) }, "SelectHouse": func() { gp.script.L.PushGoFunctionAsCFunction(selectHouse(gp)) }, "LoadHouse": func() { gp.script.L.PushGoFunctionAsCFunction(loadHouse(gp)) }, "SaveStore": func() { gp.script.L.PushGoFunctionAsCFunction(saveStore(gp, player)) }, "ShowMainBar": func() { gp.script.L.PushGoFunctionAsCFunction(showMainBar(gp, player)) }, "SpawnEntityAtPosition": func() { gp.script.L.PushGoFunctionAsCFunction(spawnEntityAtPosition(gp)) }, "GetSpawnPointsMatching": func() { gp.script.L.PushGoFunctionAsCFunction(getSpawnPointsMatching(gp)) }, "SpawnEntitySomewhereInSpawnPoints": func() { gp.script.L.PushGoFunctionAsCFunction(spawnEntitySomewhereInSpawnPoints(gp)) }, "IsSpawnPointInLos": func() { gp.script.L.PushGoFunctionAsCFunction(isSpawnPointInLos(gp)) }, "PlaceEntities": func() { gp.script.L.PushGoFunctionAsCFunction(placeEntities(gp)) }, "RoomAtPos": func() { gp.script.L.PushGoFunctionAsCFunction(roomAtPos(gp)) }, "SetLosMode": func() { gp.script.L.PushGoFunctionAsCFunction(setLosMode(gp)) }, "GetAllEnts": func() { gp.script.L.PushGoFunctionAsCFunction(getAllEnts(gp)) }, "DialogBox": func() { gp.script.L.PushGoFunctionAsCFunction(dialogBox(gp)) }, "PickFromN": func() { gp.script.L.PushGoFunctionAsCFunction(pickFromN(gp)) }, "SetGear": func() { gp.script.L.PushGoFunctionAsCFunction(setGear(gp)) }, "BindAi": func() { gp.script.L.PushGoFunctionAsCFunction(bindAi(gp)) }, "SetVisibility": func() { gp.script.L.PushGoFunctionAsCFunction(setVisibility(gp)) }, "EndPlayerInteraction": func() { gp.script.L.PushGoFunctionAsCFunction(endPlayerInteraction(gp)) }, "GetLos": func() { gp.script.L.PushGoFunctionAsCFunction(getLos(gp)) }, "SetVisibleSpawnPoints": func() { gp.script.L.PushGoFunctionAsCFunction(setVisibleSpawnPoints(gp)) }, "SetCondition": func() { gp.script.L.PushGoFunctionAsCFunction(setCondition(gp)) }, "SetPosition": func() { gp.script.L.PushGoFunctionAsCFunction(setPosition(gp)) }, "SetHp": func() { gp.script.L.PushGoFunctionAsCFunction(setHp(gp)) }, "SetAp": func() { gp.script.L.PushGoFunctionAsCFunction(setAp(gp)) }, "RemoveEnt": func() { gp.script.L.PushGoFunctionAsCFunction(removeEnt(gp)) }, "PlayAnimations": func() { gp.script.L.PushGoFunctionAsCFunction(playAnimations(gp)) }, "PlayMusic": func() { gp.script.L.PushGoFunctionAsCFunction(playMusic(gp)) }, "StopMusic": func() { gp.script.L.PushGoFunctionAsCFunction(stopMusic(gp)) }, "SetMusicParam": func() { gp.script.L.PushGoFunctionAsCFunction(setMusicParam(gp)) }, "PlaySound": func() { gp.script.L.PushGoFunctionAsCFunction(playSound(gp)) }, "SetWaypoint": func() { gp.script.L.PushGoFunctionAsCFunction(setWaypoint(gp)) }, "RemoveWaypoint": func() { gp.script.L.PushGoFunctionAsCFunction(removeWaypoint(gp)) }, "Rand": func() { gp.script.L.PushGoFunctionAsCFunction(randFunc(gp)) }, "Sleep": func() { gp.script.L.PushGoFunctionAsCFunction(sleepFunc(gp)) }, }) gp.script.L.SetMetaTable(-2) gp.script.L.SetGlobal("Script") registerUtilityFunctions(gp.script.L) if player.Lua_store != nil { loadGameStateRaw(gp, player.Game_state) err := LuaDecodeTable(bytes.NewBuffer(player.Lua_store), gp.script.L, gp.game) if err != nil { base.Warn().Printf("Error decoding lua state: %v", err) } gp.script.L.SetGlobal("store") } else { gp.script.L.NewTable() gp.script.L.SetGlobal("store") } gp.script.sync = make(chan struct{}) base.Log().Printf("Sync: %p", gp.script.sync) res := gp.script.L.DoString(string(prog)) if !res { base.Error().Printf("There was an error running script %s:\n%s", path, prog) } else { base.Log().Printf("No_init: %v\n", player.No_init) go func() { gp.script.L.NewTable() for k, v := range data { gp.script.L.PushString(k) gp.script.L.PushString(v) gp.script.L.SetTable(-3) } gp.script.L.SetGlobal("__data") gp.script.L.SetExecutionLimit(250000) if player.No_init { gp.script.syncStart() loadGameStateRaw(gp, player.Game_state) gp.game.script = gp.script gp.script.syncEnd() } else { gp.script.L.DoString("Init(__data)") if gp.game.Side == SideHaunt { gp.game.Ai.minions.Activate() gp.game.Ai.denizens.Activate() gp.game.player_inactive = gp.game.Ai.denizens.Active() } else { gp.game.Ai.intruders.Activate() gp.game.player_inactive = gp.game.Ai.intruders.Active() } } if gp.game == nil { base.Error().Printf("Script failed to load a house during Init().") } else { gp.game.comm.script_to_game <- nil } }() } }