package game import ( "fmt" "strconv" "strings" "thehouseoficarus/internal/action" "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) { cfg := mob.TalkConfig if cfg == nil { sess.WriteLine("This person has nothing to say.") return } if node, ok := cfg.Nodes["start"]; ok { p.ActionState = &ActionState{Type: ActionTalking, TargetName: mob.Name} p.Action = &action.Action{ Type: "talk", TargetID: mob.DefID, TargetName: mob.Name, Data: map[string]any{"node": "start", "talk_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.ActionState = &ActionState{Type: ActionTalking, TargetName: obj.Name} p.Action = &action.Action{ Type: "talk", TargetID: obj.ID, TargetName: obj.Name, Data: map[string]any{"node": "start", "talk_cfg": cfg}, } g.showTalkNode(sess, node) } else { sess.WriteLine("This person has nothing to say.") } } func (g *Game) showTalkNode(sess *net.Session, node action.TalkNode) { sess.WriteLine(fmt.Sprintf("%s", g.colorize(sess, "dialog", node.Message))) if node.Action != nil { if node.Action.Shop != nil { g.applyNodeAction(sess, node.Action) g.enterShop(sess, node.Action.Shop) return } g.applyNodeAction(sess, node.Action) if g.WorldFlags["guard_hostile"] != nil { delete(g.WorldFlags, "guard_hostile") p := sess.Player if p != nil && p.Action != nil && p.Action.Data["steal_guard"] == true { 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 } } } if len(node.Options) == 0 { sess.State = net.StateGame g.writePrompt(sess) return } visible := 0 for _, opt := range node.Options { if opt.Condition != nil && !g.checkCondition(sess, opt.Condition) { continue } visible++ sess.WriteLine(fmt.Sprintf(" %d. %s", visible, opt.Text)) } sess.State = net.StateTalk sess.Write("\nChoice: ") } func (g *Game) handleTalkInput(sess *net.Session, input string) { p := sess.Player if p == nil || p.Action == nil || p.Action.Type != "talk" { sess.State = net.StateGame g.writePrompt(sess) return } input = strings.TrimSpace(input) 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 _, ok := p.Action.Data["estate_directory"].(bool); ok { g.handleEstateDirectoryTalk(sess, input) return } cfg, ok := p.Action.Data["talk_cfg"].(*action.TalkConfig) if !ok || cfg == nil { g.cancelAction(p) sess.State = net.StateGame g.writePrompt(sess) return } nodeKey := p.Action.Data["node"].(string) 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 *action.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.End { g.cancelAction(p) sess.State = net.StateGame g.writePrompt(sess) return } if chosen.Goto != "" { if nextNode, ok := cfg.Nodes[chosen.Goto]; ok { p.Action.Data["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 *action.ShopConfig) { sess.Shop = cfg sess.State = net.StateShop g.showShopBrowse(sess) g.writeShopPrompt(sess) } func (g *Game) applyNodeAction(sess *net.Session, na *action.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 } setPlayerFlag(p, "assassin_reputation", rep-na.ReputationCost) g.AccountStore.SaveCharacter(p) } for k, v := range na.SetFlags { g.WorldFlags[k] = v } for k, v := range na.SetPlayerFlags { p.EnsureFlags() p.Flags[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 { setPlayerFlag(p, "aps_node_"+strconv.Itoa(p.RoomID), true) } }