package game import ( "fmt" "strings" "thehouseoficarus/internal/behavior" "thehouseoficarus/internal/color" "thehouseoficarus/internal/net" "thehouseoficarus/internal/object" "thehouseoficarus/internal/player" "thehouseoficarus/internal/world" ) func (g *Game) startMobTalk(sess *net.Session, p *player.Player, mob *world.MobInstance) { if mob.OwnerOnly && mob.Owner != "" && mob.Owner != p.Name { sess.WriteLine("They ignore you.") return } cfg := mob.TalkConfig if cfg == nil { sess.WriteLine("This person has nothing to say.") return } if node, ok := cfg.Nodes["start"]; ok { p.Action = &behavior.Action{ Type: behavior.TypeTalk, TargetID: mob.DefID, TargetName: mob.Name, Data: &behavior.TalkData{Node: "start", Cfg: cfg}, } g.showTalkNode(sess, node) } else { sess.WriteLine("This person has nothing to say.") } } func (g *Game) startTalk(sess *net.Session, p *player.Player, obj *object.ObjectDef) { cfg := obj.Talk if cfg == nil { sess.WriteLine("This person has nothing to say.") return } if node, ok := cfg.Nodes["start"]; ok { p.Action = &behavior.Action{ Type: behavior.TypeTalk, TargetID: obj.ID, TargetName: obj.Name, Data: &behavior.TalkData{Node: "start", Cfg: cfg}, } g.showTalkNode(sess, node) } else { sess.WriteLine("This person has nothing to say.") } } func (g *Game) handleNodeAction(sess *net.Session, na *behavior.Step) (intercepted bool) { g.applyTalkStep(sess, na) if v, ok := g.GlobalFlags.Get("guard_hostile"); ok && v != nil { g.GlobalFlags.Delete("guard_hostile") p := sess.Player if p != nil && p.Action != nil { if td, ok := p.Action.Data.(*behavior.TalkData); ok && td.StealGuard { guardMob := g.findGuardInRoom(p.RoomID, "guard") if guardMob != nil { sess.State = net.StateGame g.cancelAction(p) guardMob.Protected = false g.startCombat(sess, p, guardMob) } return true } } } return false } func (g *Game) showTalkNode(sess *net.Session, node behavior.TalkNode) { p := sess.Player td, _ := p.Action.Data.(*behavior.TalkData) td.CancelTalk = false td.PendingSeq = false td.PendingChoice = "" td.PendingAction = nil td.PendingChosen = false dialogSpec := g.resolveColor(sess, "dialog") cfg := td.Cfg for { if node.Condition == nil || g.checkCondition(sess, node.Condition) { if len(node.Messages) > 0 { if node.Action != nil { if g.handleNodeAction(sess, node.Action) { return } } td.SeqIndex = 0 msg := node.Messages[0] if msg != "" { sess.WriteLine(color.ExpandDialogTags(g.colorMode(sess), dialogSpec, msg)) } td.SeqIndex = 1 if td.SeqIndex < len(node.Messages) { sess.State = net.StateTalkSequence sess.Write(g.colorize(sess, "dialog_options", "[enter to continue]\n")) return } } else { if node.Action != nil { if g.handleNodeAction(sess, node.Action) { return } } } visible := g.visibleOptions(sess, node.Options) if len(visible) > 0 { for i, opt := range visible { sess.WriteLine(fmt.Sprintf(" %d. %s", i+1, color.ExpandDialogTags(g.colorMode(sess), g.resolveColor(sess, "dialog_options"), opt.Text))) } sess.State = net.StateTalk g.reprompt(sess) return } } if node.Goto != "" && cfg != nil { next, ok := cfg.Nodes[node.Goto] if !ok { break } if td != nil { td.Node = node.Goto } node = next } else { break } } sess.State = net.StateGame g.writePrompt(sess) } func (g *Game) visibleOptions(sess *net.Session, options []behavior.TalkOption) []behavior.TalkOption { var visible []behavior.TalkOption for _, opt := range options { if opt.Condition != nil && !g.checkCondition(sess, opt.Condition) { continue } visible = append(visible, opt) } return visible } func (g *Game) finishSequence(sess *net.Session, node behavior.TalkNode) { visible := g.visibleOptions(sess, node.Options) if len(visible) > 0 { for i, opt := range visible { sess.WriteLine(fmt.Sprintf(" %d. %s", i+1, color.ExpandDialogTags(g.colorMode(sess), g.resolveColor(sess, "dialog_options"), opt.Text))) } sess.State = net.StateTalk g.reprompt(sess) return } td, _ := sess.Player.Action.Data.(*behavior.TalkData) if node.Goto != "" && td != nil && td.Cfg != nil { nextNode, ok := td.Cfg.Nodes[node.Goto] if ok { td.Node = node.Goto g.showTalkNode(sess, nextNode) return } } g.cancelAction(sess.Player) sess.State = net.StateGame } func (g *Game) handleTalkInput(sess *net.Session, input string) { p := sess.Player if p == nil || p.Action == nil || p.Action.Type != behavior.TypeTalk { sess.State = net.StateGame g.writePrompt(sess) return } if p.Action.WaitLeft > 0 { return } td, ok := p.Action.Data.(*behavior.TalkData) if !ok || td.Cfg == nil { g.cancelAction(p) sess.State = net.StateGame g.writePrompt(sess) return } input = strings.TrimSpace(input) if input == "" && sess.State != net.StateTalkSequence { input = "1" } if sess.State == net.StateTalkSequence { _, ok := td.Cfg.Nodes[td.Node] if !ok { td.CancelTalk = true p.Action.WaitLeft = 1 return } if input == "" { td.PendingSeq = true } else { td.CancelTalk = true } p.Action.WaitLeft = 1 return } if td.EstateDirectory { g.handleEstateDirectoryTalk(sess, input) return } cfg := td.Cfg nodeKey := td.Node node, ok := cfg.Nodes[nodeKey] if !ok { td.CancelTalk = true p.Action.WaitLeft = 1 return } idx, err := parseChoiceIndex(input) if err != nil || idx <= 0 { td.CancelTalk = true p.Action.WaitLeft = 1 return } validIdx := 0 var chosen *behavior.TalkOption for i := range node.Options { opt := &node.Options[i] if opt.Condition != nil && !g.checkCondition(sess, opt.Condition) { continue } validIdx++ if validIdx == idx { chosen = opt break } } if chosen == nil { td.CancelTalk = true p.Action.WaitLeft = 1 return } td.PendingChoice = chosen.Goto td.PendingAction = chosen.Action td.PendingChosen = true p.Action.WaitLeft = 1 } func (g *Game) advanceTalk(sess *net.Session, p *player.Player) { td, ok := p.Action.Data.(*behavior.TalkData) if !ok || td.Cfg == nil { g.cancelAction(p) sess.State = net.StateGame return } if td.CancelTalk { sess.WriteLine("You leave the conversation.") g.cancelAction(p) sess.State = net.StateGame return } if td.PendingSeq { td.PendingSeq = false node, ok := td.Cfg.Nodes[td.Node] if !ok { g.cancelAction(p) sess.State = net.StateGame return } dialogSpec := g.resolveColor(sess, "dialog") if td.SeqIndex < len(node.Messages) { msg := node.Messages[td.SeqIndex] if msg != "" { sess.WriteLine(color.ExpandDialogTags(g.colorMode(sess), dialogSpec, msg)) } td.SeqIndex++ } if td.SeqIndex >= len(node.Messages) { g.finishSequence(sess, node) return } sess.State = net.StateTalkSequence sess.Write("[enter to continue]\n") return } if td.PendingChosen { td.PendingChosen = false choice := td.PendingChoice action := td.PendingAction td.PendingChoice = "" td.PendingAction = nil if action != nil { if g.handleNodeAction(sess, action) { return } } if choice != "" { if nextNode, ok := td.Cfg.Nodes[choice]; ok { td.Node = choice g.showTalkNode(sess, nextNode) return } } g.cancelAction(p) sess.State = net.StateGame return } }