package game import ( "fmt" "strconv" "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.NodeAction) (intercepted bool) { if na.Shop != nil { g.applyNodeAction(sess, na) g.enterShop(sess, na.Shop) return true } g.applyNodeAction(sess, na) if v, ok := g.Flags.Get("guard_hostile"); ok && v != nil { g.Flags.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) dialogSpec := g.resolveColor(sess, "dialog") msg := node.Message.Pick() if msg != "" { sess.WriteLine(color.ExpandTagsDefault(g.colorMode(sess), dialogSpec, msg)) } if node.Action != nil { if g.handleNodeAction(sess, node.Action) { return } } cfg := td.Cfg for { visible := g.visibleOptions(sess, node.Options) if len(visible) > 0 { for i, opt := range visible { sess.WriteLine(fmt.Sprintf(" %d. %s", i+1, opt.Text)) } sess.State = net.StateTalk sess.Write("\nChoice: ") return } if node.Next != "" && cfg != nil { next, ok := cfg.Nodes[node.Next] if !ok { break } if td != nil { td.Node = node.Next } node = next msg := node.Message.Pick() if msg != "" { sess.WriteLine(color.ExpandTagsDefault(g.colorMode(sess), dialogSpec, msg)) } if node.Action != nil { if g.handleNodeAction(sess, node.Action) { return } } } 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) 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 } input = strings.TrimSpace(input) if input == "" { input = "1" } lower := strings.ToLower(input) if lower == "bye" || lower == "exit" || lower == "end" || lower == "quit" { g.cancelAction(p) sess.State = net.StateGame g.writePrompt(sess) return } if td, ok := p.Action.Data.(*behavior.TalkData); ok && td.EstateDirectory { g.handleEstateDirectoryTalk(sess, input) return } td, ok := p.Action.Data.(*behavior.TalkData) if !ok || td.Cfg == nil { g.cancelAction(p) sess.State = net.StateGame g.writePrompt(sess) return } cfg := td.Cfg nodeKey := td.Node node, ok := cfg.Nodes[nodeKey] if !ok { g.cancelAction(p) sess.State = net.StateGame g.writePrompt(sess) return } idx, err := parseChoiceIndex(input) if err != nil || idx <= 0 { g.cancelAction(p) sess.State = net.StateGame g.writePrompt(sess) 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 { g.cancelAction(p) sess.State = net.StateGame g.writePrompt(sess) return } if chosen.Action != nil { if g.handleNodeAction(sess, chosen.Action) { return } } if chosen.Goto != "" { if nextNode, ok := cfg.Nodes[chosen.Goto]; ok { td.Node = chosen.Goto g.showTalkNode(sess, nextNode) return } } g.cancelAction(p) sess.State = net.StateGame g.writePrompt(sess) } func (g *Game) enterShop(sess *net.Session, cfg *behavior.ShopConfig) { sess.Shop = cfg sess.State = net.StateShop g.showShopBrowse(sess) g.writeShopPrompt(sess) } func (g *Game) applyNodeAction(sess *net.Session, na *behavior.NodeAction) { p := sess.Player if p == nil { return } if na.AssignTask { g.assignAssassinTask(sess, p) } if na.SkipTask { g.skipAssassinTask(sess, p) } if na.ExtendTask { g.extendAssassinTask(sess, p) } if na.ReputationCost > 0 { rep := getPlayerFlagInt(p, "assassin_reputation") if rep < na.ReputationCost { sess.WriteLine(fmt.Sprintf("You don't have enough Reputation. (Need %d, have %d)", na.ReputationCost, rep)) return } g.setPlayerFlag(p, "assassin_reputation", rep-na.ReputationCost) g.AccountStore.SaveCharacter(p) } for k, v := range na.SetFlags { g.Flags.Set(k, v) } for k, v := range na.SetPlayerFlags { g.setPlayerFlag(p, k, v) } if na.TakeItem != "" { if p.HasItem(na.TakeItem) { p.RemoveItem(na.TakeItem, 1) } } if na.GiveItem != "" { slot := p.FirstFreeSlot() if slot == -1 { sess.WriteLine("Your inventory is too full to receive that.") } else { p.SetInvSlot(slot, &player.InventorySlot{ItemID: na.GiveItem, Quantity: 1}) g.AccountStore.SaveCharacter(p) } } if na.Teleport > 0 { p.RoomID = na.Teleport p.Stats.RecordRoomVisit(na.Teleport) g.AccountStore.SaveCharacter(p) g.World.SeedGroundItems(p.RoomID) g.seedRoomMobs(p.RoomID) g.seedRoomObjects(p.RoomID) if g.Hub != nil { g.Hub.EnterRoom(sess, p.RoomID) } g.doLook(sess) g.runEnterSteps(sess, p.RoomID) } if na.Heal > 0 { p.HP += na.Heal if maxHP := p.MaxHP(); p.HP > maxHP { p.HP = maxHP } g.AccountStore.SaveCharacter(p) sess.WriteLine(fmt.Sprintf("You regain %d hitpoints.", na.Heal)) } if na.Cost > 0 { if p.Credits >= na.Cost { p.Credits -= na.Cost g.AccountStore.SaveCharacter(p) } } if na.Sawmill { g.processSawmill(sess, p) } if na.ApsNode { g.setPlayerFlag(p, "aps_node_"+strconv.Itoa(p.RoomID), true) } }