From 3102525d97aa6f1ad152c74b2ccfbd4b3a9b83f5 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Fri, 26 Jun 2026 02:52:31 -0400 Subject: feat: simplified conversation trees and output won't overwrite prompts now --- internal/behavior/behavior.go | 10 +- internal/behavior/types.go | 6 + internal/game/act.go | 2 + internal/game/act_agility.go | 2 +- internal/game/act_burn.go | 6 +- internal/game/act_farm.go | 38 +++--- internal/game/act_gather.go | 8 +- internal/game/act_room.go | 14 +- internal/game/act_search.go | 2 +- internal/game/act_steal.go | 2 +- internal/game/act_talk.go | 228 +++++++++++++++++++++++---------- internal/game/act_use.go | 2 +- internal/game/cmd_attack.go | 2 +- internal/game/cmd_bank.go | 4 +- internal/game/cmd_color.go | 2 +- internal/game/cmd_consume.go | 2 +- internal/game/cmd_estate_directory.go | 2 +- internal/game/cmd_farm.go | 2 +- internal/game/cmd_global.go | 2 +- internal/game/cmd_inventory.go | 1 - internal/game/cmd_jack.go | 2 +- internal/game/cmd_move.go | 4 +- internal/game/cmd_option.go | 2 +- internal/game/cmd_queued.go | 2 +- internal/game/cmd_quit.go | 2 +- internal/game/cmd_say.go | 2 +- internal/game/cmd_shop.go | 10 +- internal/game/cmd_tech.go | 2 +- internal/game/cmd_trigger_transport.go | 2 +- internal/game/cmd_use.go | 2 +- internal/game/cmd_who.go | 2 +- internal/game/combat_attack.go | 12 +- internal/game/combat_mob.go | 16 +-- internal/game/core_login_char.go | 2 +- internal/game/game.go | 6 +- internal/game/look_entities.go | 2 +- internal/game/render_help.go | 2 +- internal/game/sys_assassin.go | 2 +- internal/game/sys_buff.go | 2 +- internal/game/sys_combat.go | 2 +- internal/game/sys_hazard.go | 4 +- internal/game/tick.go | 12 +- internal/game/triggers.go | 25 ++-- internal/net/server.go | 37 +++++- internal/validate/checks.go | 6 +- 45 files changed, 315 insertions(+), 184 deletions(-) (limited to 'internal') diff --git a/internal/behavior/behavior.go b/internal/behavior/behavior.go index f81c73c..e01d8a7 100644 --- a/internal/behavior/behavior.go +++ b/internal/behavior/behavior.go @@ -30,10 +30,12 @@ type TalkConfig struct { } type TalkNode struct { - Message MessageList `yaml:"message"` - Options []TalkOption `yaml:"options"` - Action *NodeAction `yaml:"action"` - Next string `yaml:"next,omitempty"` + Message MessageList `yaml:"message"` + Sequence []string `yaml:"sequence,omitempty"` + Condition *Condition `yaml:"condition,omitempty"` + Options []TalkOption `yaml:"options"` + Action *NodeAction `yaml:"action"` + Goto string `yaml:"goto,omitempty"` } type TalkOption struct { diff --git a/internal/behavior/types.go b/internal/behavior/types.go index f8e65f7..3d55d79 100644 --- a/internal/behavior/types.go +++ b/internal/behavior/types.go @@ -110,8 +110,14 @@ type StealData struct { type TalkData struct { Node string Cfg *TalkConfig + SeqIndex int EstateDirectory bool StealGuard bool + PendingChoice string + PendingAction *NodeAction + CancelTalk bool + PendingSeq bool + PendingChosen bool } type UseData struct { diff --git a/internal/game/act.go b/internal/game/act.go index 8920d0c..31d8a33 100644 --- a/internal/game/act.go +++ b/internal/game/act.go @@ -230,6 +230,8 @@ func (g *Game) AdvanceActions() { g.advanceCure(sess, p) case "obstacle": g.advanceObstacle(sess, p) + case behavior.TypeTalk: + g.advanceTalk(sess, p) default: if productionActionTypes[string(p.Action.Type)] { g.advanceProduction(sess, p) diff --git a/internal/game/act_agility.go b/internal/game/act_agility.go index 4a68580..d66b9c5 100644 --- a/internal/game/act_agility.go +++ b/internal/game/act_agility.go @@ -46,7 +46,7 @@ func (g *Game) startObstacle(sess *net.Session, p *player.Player, info *Obstacle }, } - g.broadcastAction(sess, "\n%s starts %s on the %s.", p.Name, gerund, info.CourseName) + g.broadcastAction(sess, "%s starts %s on the %s.", p.Name, gerund, info.CourseName) } func (g *Game) advanceObstacle(sess *net.Session, p *player.Player) { diff --git a/internal/game/act_burn.go b/internal/game/act_burn.go index 861ff93..7420dff 100644 --- a/internal/game/act_burn.go +++ b/internal/game/act_burn.go @@ -118,7 +118,7 @@ func (g *Game) startBurn(sess *net.Session, p *player.Player, itemID string, fro phase = 0 } - g.broadcastAction(sess, "\n%s starts building a fire.", p.Name) + g.broadcastAction(sess, "%s starts building a fire.", p.Name) p.Action = &behavior.Action{ Type: behavior.TypeBurn, @@ -156,7 +156,7 @@ func (g *Game) startStoke(sess *net.Session, p *player.Player, itemID string) { sess.WriteLine("You begin to tend to the fire.") - g.broadcastAction(sess, "\n%s tends to the fire.", p.Name) + g.broadcastAction(sess, "%s tends to the fire.", p.Name) p.Action = &behavior.Action{ Type: behavior.TypeStoke, @@ -243,7 +243,7 @@ func (g *Game) advanceBurn(sess *net.Session, p *player.Player) { } sess.WriteLine(msg) - g.broadcastAction(sess, "\n%s started a fire!", p.Name) + g.broadcastAction(sess, "%s started a fire!", p.Name) return } diff --git a/internal/game/act_farm.go b/internal/game/act_farm.go index a4d96b4..6060dca 100644 --- a/internal/game/act_farm.go +++ b/internal/game/act_farm.go @@ -113,8 +113,8 @@ func (g *Game) advanceFarmGrowth(sess *net.Session, p *player.Player) { if diseased { g.setPlayerFlag(p, prefix+"_dead", true) g.setPlayerFlag(p, prefix+"_diseased", false) - sess.WriteLine(g.colorize(sess, "farm_disease", - fmt.Sprintf("\nYour %s has died from disease!", g.seedDisplayName(sess, seedID)))) + sess.WriteLine(g.colorize(sess, "farm_disease", + fmt.Sprintf("Your %s has died from disease!", g.seedDisplayName(sess, seedID)))) g.AccountStore.SaveCharacter(p) continue } @@ -138,23 +138,23 @@ func (g *Game) advanceFarmGrowth(sess *net.Session, p *player.Player) { g.setPlayerFlag(p, prefix+"_stage", stage) g.setPlayerFlag(p, prefix+"_ready", true) g.setPlayerFlag(p, prefix+"_watered", false) - sess.WriteLine(g.colorize(sess, "farm_grow", - fmt.Sprintf("\nYour %s is fully grown and ready to harvest!", - g.seedDisplayName(sess, seedID)))) + sess.WriteLine(g.colorize(sess, "farm_grow", + fmt.Sprintf("Your %s is fully grown and ready to harvest!", + g.seedDisplayName(sess, seedID)))) } else { if !watered && rand.Float64() < 0.10 { g.setPlayerFlag(p, prefix+"_stage", stage) g.setPlayerFlag(p, prefix+"_diseased", true) g.setPlayerFlag(p, prefix+"_watered", false) - sess.WriteLine(g.colorize(sess, "farm_disease", - fmt.Sprintf("\nYour %s has become diseased!", - g.seedDisplayName(sess, seedID)))) + sess.WriteLine(g.colorize(sess, "farm_disease", + fmt.Sprintf("Your %s has become diseased!", + g.seedDisplayName(sess, seedID)))) } else { g.setPlayerFlag(p, prefix+"_stage", stage) g.setPlayerFlag(p, prefix+"_watered", false) - sess.WriteLine(g.colorize(sess, "farm_grow", - fmt.Sprintf("\nYour %s has grown to stage %d/%d.", - g.seedDisplayName(sess, seedID), stage, maxStages))) + sess.WriteLine(g.colorize(sess, "farm_grow", + fmt.Sprintf("Your %s has grown to stage %d/%d.", + g.seedDisplayName(sess, seedID), stage, maxStages))) } } g.AccountStore.SaveCharacter(p) @@ -395,22 +395,22 @@ func (g *Game) farmPatchLookSuffix(p *player.Player, defID string, index int) st weeds, _ := p.Flags[prefix+"_weeds"].(bool) if weeds { - return "\nIt is overgrown with weeds." + return "It is overgrown with weeds." } seedID, _ := p.Flags[prefix+"_seed"].(string) if seedID == "" { - return "\nThe patch is empty and ready for planting." + return "The patch is empty and ready for planting." } dead, _ := p.Flags[prefix+"_dead"].(bool) if dead { - return "\nThe plant has died. You need to rake it clean." + return "The plant has died. You need to rake it clean." } diseased, _ := p.Flags[prefix+"_diseased"].(bool) if diseased { - return "\nThe plant looks diseased! Use plant cure to save it." + return "The plant looks diseased! Use plant cure to save it." } ready, _ := p.Flags[prefix+"_ready"].(bool) @@ -419,7 +419,7 @@ func (g *Game) farmPatchLookSuffix(p *player.Player, defID string, index int) st if def, err := g.ItemStore.Load(seedID); err == nil { name = def.Name } - return fmt.Sprintf("\nA fully grown %s is ready to harvest!", name) + return fmt.Sprintf("A fully grown %s is ready to harvest!", name) } stage := intFromFlag(p.Flags, prefix+"_stage") @@ -432,7 +432,7 @@ func (g *Game) farmPatchLookSuffix(p *player.Player, defID string, index int) st name = def.Name } watered, _ := p.Flags[prefix+"_watered"].(bool) - suffix := fmt.Sprintf("\nA %s is growing (stage %d/%d).", name, stage, maxStages) + suffix := fmt.Sprintf("A %s is growing (stage %d/%d).", name, stage, maxStages) if watered { suffix += " It has been watered." } @@ -451,9 +451,9 @@ func (g *Game) toolShedLookSuffix(p *player.Player) string { stored = append(stored, "a watering can") } if len(stored) == 0 { - return "\nThe shed is empty." + return "The shed is empty." } - return fmt.Sprintf("\nInside: %s.", strings.Join(stored, ", ")) + return fmt.Sprintf("Inside: %s.", strings.Join(stored, ", ")) } func (g *Game) farmMatchPrefix(p *player.Player, input string) (prefix string, defID string, index int) { diff --git a/internal/game/act_gather.go b/internal/game/act_gather.go index 0c743e7..826b901 100644 --- a/internal/game/act_gather.go +++ b/internal/game/act_gather.go @@ -94,7 +94,7 @@ func (g *Game) startGather(sess *net.Session, p *player.Player, obj *object.Obje verb = "chopping" } - g.broadcastAction(sess, "\n%s starts %s the %s.", p.Name, verb, obj.Name) + g.broadcastAction(sess, "%s starts %s the %s.", p.Name, verb, obj.Name) d := &behavior.GatherData{ ObjDefID: obj.ID, @@ -259,7 +259,7 @@ func (g *Game) advanceGather(sess *net.Session, p *player.Player) { continue } if gd, ok := op.Action.Data.(*behavior.GatherData); ok && gd.InstanceKey == instanceKey { - other.WriteLine(fmt.Sprintf("\nThe %s was depleted by %s!", g.colorize(sess, "item", p.Action.TargetName), g.colorize(sess, "player_name", p.Name))) + other.WriteLine(fmt.Sprintf("The %s was depleted by %s!", g.colorize(sess, "item", p.Action.TargetName), g.colorize(sess, "player_name", p.Name))) g.cancelAction(op) g.writePrompt(other) } @@ -330,10 +330,10 @@ func (g *Game) depleteSharedTree(st *world.ObjState, cfg *behavior.GatherConfig, } } if isDepleter { - sess.WriteLine(fmt.Sprintf("\n%s", msg)) + sess.WriteLine(msg) continue } - sess.WriteLine(fmt.Sprintf("\n%s", msg)) + sess.WriteLine(msg) g.cancelAction(op) g.writePrompt(sess) } diff --git a/internal/game/act_room.go b/internal/game/act_room.go index ff7a780..ddca261 100644 --- a/internal/game/act_room.go +++ b/internal/game/act_room.go @@ -18,7 +18,6 @@ type enterSeq struct { roomID int steps []world.EnterStep wait int - started bool } // runEnterSteps evaluates a room's on_enter script for the player. Steps whose @@ -144,18 +143,13 @@ func (g *Game) fireEnterStep(seq *enterSeq, step world.EnterStep) { if step.Message != "" { msg := expandTemplate(step.Message, p.Name, nil) msg = color.ExpandTagsDefault(mode, seqSpec, msg) - if !seq.started { - seq.sess.WriteLine("\n" + msg) - seq.started = true - } else { - seq.sess.WriteLine(msg) - } + seq.sess.WriteLine(msg) } if step.Broadcast != "" && g.Hub != nil { msg := expandTemplate(step.Broadcast, p.Name, nil) msg = color.ExpandTagsDefault(mode, broadcastSpec, msg) for _, other := range g.Hub.PlayersInRoom(seq.roomID) { - other.WriteLine(g.colorize(other, "broadcast", "\n"+msg)) + other.WriteLine(g.colorize(other, "broadcast", msg)) } } if step.BroadcastGlobal != "" && g.Hub != nil { @@ -163,7 +157,7 @@ func (g *Game) fireEnterStep(seq *enterSeq, step world.EnterStep) { msg = color.ExpandTagsDefault(mode, broadcastSpec, msg) for _, other := range g.Hub.AllSessions() { if other.Player != nil { - other.WriteLine(g.colorize(other, "broadcast", "\n"+msg)) + other.WriteLine(g.colorize(other, "broadcast", msg)) } } } @@ -264,7 +258,7 @@ func (g *Game) BroadcastRespawns() { msg := strings.ReplaceAll(cfg.RespawnBroadcast, "{name}", name) if g.Hub != nil { for _, sess := range g.Hub.PlayersInRoom(st.RoomID) { - sess.WriteLine(g.colorize(sess, "broadcast", fmt.Sprintf("\n%s", msg))) + sess.WriteLine(g.colorize(sess, "broadcast", msg)) } } } diff --git a/internal/game/act_search.go b/internal/game/act_search.go index 6af15ca..6f5fa65 100644 --- a/internal/game/act_search.go +++ b/internal/game/act_search.go @@ -33,7 +33,7 @@ func (g *Game) startSearch(sess *net.Session, p *player.Player, itemID string, s }, } - g.broadcastAction(sess, "\n%s searches a %s.", p.Name, def.Name) + g.broadcastAction(sess, "%s searches a %s.", p.Name, def.Name) } func (g *Game) advanceSearch(sess *net.Session, p *player.Player) { diff --git a/internal/game/act_steal.go b/internal/game/act_steal.go index 39df9e2..1c76474 100644 --- a/internal/game/act_steal.go +++ b/internal/game/act_steal.go @@ -261,7 +261,7 @@ func (g *Game) startSteal(sess *net.Session, p *player.Player, targetType string sess.WriteLine(fmt.Sprintf("You attempt to steal from the %s...", targetName)) - g.broadcastAction(sess, "\n%s glances around the room.", p.Name) + g.broadcastAction(sess, "%s glances around the room.", p.Name) p.Action = &behavior.Action{ Type: behavior.TypeSteal, diff --git a/internal/game/act_talk.go b/internal/game/act_talk.go index 14361f8..c87e3c5 100644 --- a/internal/game/act_talk.go +++ b/internal/game/act_talk.go @@ -87,51 +87,63 @@ func (g *Game) handleNodeAction(sess *net.Session, na *behavior.NodeAction) (int 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") - 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)) + if node.Condition == nil || g.checkCondition(sess, node.Condition) { + if len(node.Sequence) > 0 { + if node.Action != nil { + if g.handleNodeAction(sess, node.Action) { + return + } + } + td.SeqIndex = 0 + msg := node.Sequence[0] + if msg != "" { + sess.WriteLine(color.ExpandTagsDefault(g.colorMode(sess), dialogSpec, msg)) + } + td.SeqIndex = 1 + if td.SeqIndex < len(node.Sequence) { + sess.State = net.StateTalkSequence + sess.Write("[enter to continue]\n") + return + } + } else { + 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 + } + } + } + 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 + g.reprompt(sess) + return } - sess.State = net.StateTalk - sess.Write("\nChoice: ") - return } - - if node.Next != "" && cfg != nil { - next, ok := cfg.Nodes[node.Next] + if node.Goto != "" && cfg != nil { + next, ok := cfg.Nodes[node.Goto] if !ok { break } if td != nil { - td.Node = node.Next + td.Node = node.Goto } 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 } @@ -152,6 +164,31 @@ func (g *Game) visibleOptions(sess *net.Session, options []behavior.TalkOption) 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, 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 { @@ -160,46 +197,57 @@ func (g *Game) handleTalkInput(sess *net.Session, input string) { return } - input = strings.TrimSpace(input) - if input == "" { - input = "1" + if p.Action.WaitLeft > 0 { + return } - lower := strings.ToLower(input) - if lower == "bye" || lower == "exit" || lower == "end" || lower == "quit" { + + td, ok := p.Action.Data.(*behavior.TalkData) + if !ok || td.Cfg == nil { 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) + 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 } - td, ok := p.Action.Data.(*behavior.TalkData) - if !ok || td.Cfg == nil { - g.cancelAction(p) - sess.State = net.StateGame - g.writePrompt(sess) + if td.EstateDirectory { + g.handleEstateDirectoryTalk(sess, input) return } - cfg := td.Cfg + cfg := td.Cfg nodeKey := td.Node node, ok := cfg.Nodes[nodeKey] if !ok { - g.cancelAction(p) - sess.State = net.StateGame - g.writePrompt(sess) + td.CancelTalk = true + p.Action.WaitLeft = 1 return } idx, err := parseChoiceIndex(input) if err != nil || idx <= 0 { - g.cancelAction(p) - sess.State = net.StateGame - g.writePrompt(sess) + td.CancelTalk = true + p.Action.WaitLeft = 1 return } @@ -218,29 +266,79 @@ func (g *Game) handleTalkInput(sess *net.Session, input string) { } 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 - g.writePrompt(sess) return } - if chosen.Action != nil { - if g.handleNodeAction(sess, chosen.Action) { - return - } + if td.CancelTalk { + sess.WriteLine("You leave the conversation.") + g.cancelAction(p) + sess.State = net.StateGame + return } - if chosen.Goto != "" { - if nextNode, ok := cfg.Nodes[chosen.Goto]; ok { - td.Node = chosen.Goto - g.showTalkNode(sess, nextNode) + 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.Sequence) { + msg := node.Sequence[td.SeqIndex] + if msg != "" { + sess.WriteLine(color.ExpandTagsDefault(g.colorMode(sess), dialogSpec, msg)) + } + td.SeqIndex++ + } + if td.SeqIndex >= len(node.Sequence) { + g.finishSequence(sess, node) return } + sess.State = net.StateTalkSequence + sess.Write("[enter to continue]\n") + return } - g.cancelAction(p) - sess.State = net.StateGame - g.writePrompt(sess) + 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 + } } func (g *Game) enterShop(sess *net.Session, cfg *behavior.ShopConfig) { diff --git a/internal/game/act_use.go b/internal/game/act_use.go index 34f28c3..0d8582d 100644 --- a/internal/game/act_use.go +++ b/internal/game/act_use.go @@ -34,7 +34,7 @@ func (g *Game) startUse(sess *net.Session, p *player.Player, obj *object.ObjectD return } - g.broadcastAction(sess, "\n%s uses the %s.", p.Name, obj.Name) + g.broadcastAction(sess, "%s uses the %s.", p.Name, obj.Name) p.Action = &behavior.Action{ Type: "use", diff --git a/internal/game/cmd_attack.go b/internal/game/cmd_attack.go index 5635fd0..d60bb05 100644 --- a/internal/game/cmd_attack.go +++ b/internal/game/cmd_attack.go @@ -52,7 +52,7 @@ func (g *Game) respawnMob(instanceID string) { if p := sess.Player; p != nil && p.OptionBool("mob_spawn") { mobLvl := mobCombatLevel(inst) levelStr := g.levelColorize(sess, p.CombatLevel(), mobLvl, fmt.Sprintf("(level %d)", mobLvl)) - sess.WriteLine(fmt.Sprintf("\n%s %s spawns in the area.", g.colorize(sess, "mob", mobDisplayName(inst, false)), levelStr)) + sess.WriteLine(fmt.Sprintf("%s %s spawns in the area.", g.colorize(sess, "mob", mobDisplayName(inst, false)), levelStr)) } } } diff --git a/internal/game/cmd_bank.go b/internal/game/cmd_bank.go index 8d96c5f..c489ad8 100644 --- a/internal/game/cmd_bank.go +++ b/internal/game/cmd_bank.go @@ -58,7 +58,7 @@ func (g *Game) handleBankInput(sess *net.Session, input string) { } func (g *Game) writeBankPrompt(sess *net.Session) { - sess.Write(fmt.Sprintf("\n%s", g.colorize(sess, "dialog", "Bank> "))) + sess.WritePrompt(g.colorize(sess, "dialog", "Bank> ")) } func (g *Game) showBankBrowse(sess *net.Session) { @@ -93,7 +93,7 @@ func (g *Game) showBankBrowse(sess *net.Session) { } if len(entries) == 0 { - sess.WriteLine("\nYour bank is empty.") + sess.WriteLine("Your bank is empty.") return } diff --git a/internal/game/cmd_color.go b/internal/game/cmd_color.go index 666a622..33b406d 100644 --- a/internal/game/cmd_color.go +++ b/internal/game/cmd_color.go @@ -80,7 +80,7 @@ func (g *Game) saveAccountColor(sess *net.Session, target, value string) { acc, err := g.AccountStore.LoadAccount(sess.Account.Name) if err != nil { - sess.WriteLine("\nError loading account.") + sess.WriteLine("Error loading account.") return } if value == "" { diff --git a/internal/game/cmd_consume.go b/internal/game/cmd_consume.go index 133bf57..423cddb 100644 --- a/internal/game/cmd_consume.go +++ b/internal/game/cmd_consume.go @@ -279,5 +279,5 @@ func (g *Game) applyPotion(sess *net.Session, p *player.Player, itemID string, d p.ConsumeCooldown = 3 - g.broadcastAction(sess, "\n%s drinks a %s.", p.Name, def.Name) + g.broadcastAction(sess, "%s drinks a %s.", p.Name, def.Name) } diff --git a/internal/game/cmd_estate_directory.go b/internal/game/cmd_estate_directory.go index 5a4423b..b28e634 100644 --- a/internal/game/cmd_estate_directory.go +++ b/internal/game/cmd_estate_directory.go @@ -191,7 +191,7 @@ func (g *Game) teleportPlayer(sess *net.Session, p *player.Player, roomID int) { if g.Hub != nil { for _, other := range g.Hub.PlayersInRoom(p.RoomID) { if other != sess { - other.WriteLine(fmt.Sprintf("\n%s disappears in a shimmer of light.", p.Name)) + other.WriteLine(fmt.Sprintf("%s disappears in a shimmer of light.", p.Name)) } } } diff --git a/internal/game/cmd_farm.go b/internal/game/cmd_farm.go index b601e49..87d3d81 100644 --- a/internal/game/cmd_farm.go +++ b/internal/game/cmd_farm.go @@ -446,7 +446,7 @@ func (g *Game) doInspect(sess *net.Session, input string) { indexStr = fmt.Sprintf(" %d", fp.index+1) } - sess.WriteLine(fmt.Sprintf("\n=== %s%s ===", patchName, indexStr)) + sess.WriteLine(fmt.Sprintf("=== %s%s ===", patchName, indexStr)) weeds, _ := p.Flags[fp.prefix+"_weeds"].(bool) if weeds { diff --git a/internal/game/cmd_global.go b/internal/game/cmd_global.go index d06a233..32577c3 100644 --- a/internal/game/cmd_global.go +++ b/internal/game/cmd_global.go @@ -28,7 +28,7 @@ func (g *Game) doGlobal(sess *net.Session, msg string) { if other.Player == nil { continue } - other.WriteLine(fmt.Sprintf("\n%s%s: %s", + other.WriteLine(fmt.Sprintf("%s%s: %s", g.colorize(other, "global", "[Global] "), g.colorize(other, "player_name", p.Name), g.colorize(other, "global", msg))) diff --git a/internal/game/cmd_inventory.go b/internal/game/cmd_inventory.go index 5024642..b7b52ed 100644 --- a/internal/game/cmd_inventory.go +++ b/internal/game/cmd_inventory.go @@ -12,7 +12,6 @@ func (g *Game) executeInventory(sess *net.Session, args []string, rawInput strin func (g *Game) doInventory(sess *net.Session) { p := sess.Player - sess.WriteLine("") sess.WriteLine(fmt.Sprintf("Inventory (%d free):", p.FreeSlots())) num := 0 diff --git a/internal/game/cmd_jack.go b/internal/game/cmd_jack.go index a0f102e..efdbba5 100644 --- a/internal/game/cmd_jack.go +++ b/internal/game/cmd_jack.go @@ -66,7 +66,7 @@ func (g *Game) doJack(sess *net.Session, target string) { sess.State = net.StateHacking - g.broadcastAction(sess, "\n%s jacks into a terminal.", p.Name) + g.broadcastAction(sess, "%s jacks into a terminal.", p.Name) sess.WriteLine(display) sess.Write("\nhack> ") diff --git a/internal/game/cmd_move.go b/internal/game/cmd_move.go index 2939c34..a1b492e 100644 --- a/internal/game/cmd_move.go +++ b/internal/game/cmd_move.go @@ -164,13 +164,13 @@ func (g *Game) completeMove(sess *net.Session, p *player.Player) { if g.Hub != nil { for _, other := range g.Hub.PlayersInRoom(oldRoom) { if other != sess { - other.WriteLine(fmt.Sprintf("\n%s leaves to the %s.", p.Name, exitDir)) + other.WriteLine(fmt.Sprintf("%s leaves to the %s.", p.Name, exitDir)) } } g.Hub.EnterRoom(sess, targetID) for _, other := range g.Hub.PlayersInRoom(targetID) { if other != sess { - other.WriteLine(fmt.Sprintf("\n%s arrives.", p.Name)) + other.WriteLine(fmt.Sprintf("%s arrives.", p.Name)) } } } diff --git a/internal/game/cmd_option.go b/internal/game/cmd_option.go index 05a6ece..d8ef0a4 100644 --- a/internal/game/cmd_option.go +++ b/internal/game/cmd_option.go @@ -84,7 +84,7 @@ func (g *Game) doOption(sess *net.Session, input string) { acc.Options[def.Name] = parsed g.AccountStore.SaveAccount(acc) } - sess.WriteLine(fmt.Sprintf("\n%s set to %s.", def.Name, formatOptionValue(p, def))) + sess.WriteLine(fmt.Sprintf("%s set to %s.", def.Name, formatOptionValue(p, def))) } func (g *Game) executeOption(sess *net.Session, args []string, rawInput string) { diff --git a/internal/game/cmd_queued.go b/internal/game/cmd_queued.go index dcda825..549c5fb 100644 --- a/internal/game/cmd_queued.go +++ b/internal/game/cmd_queued.go @@ -61,6 +61,6 @@ func (g *Game) doQueued(sess *net.Session) { } } if pos > 0 && len(names) > 1 { - sess.WriteLine(fmt.Sprintf("\n Your active action will resolve as queue position #%d of %d.", pos, len(names))) + sess.WriteLine(fmt.Sprintf(" Your active action will resolve as queue position #%d of %d.", pos, len(names))) } } diff --git a/internal/game/cmd_quit.go b/internal/game/cmd_quit.go index 2740275..60b1495 100644 --- a/internal/game/cmd_quit.go +++ b/internal/game/cmd_quit.go @@ -20,7 +20,7 @@ func (g *Game) doQuit(sess *net.Session) { g.cancelAction(p) g.cancelRest(p.Name) - g.broadcastAction(sess, "\n%s sits down to rest.", p.Name) + g.broadcastAction(sess, "%s sits down to rest.", p.Name) ticksLeft := 10 id := g.Ticks.Subscribe(engine.ToTicks(1), func() bool { diff --git a/internal/game/cmd_say.go b/internal/game/cmd_say.go index f21ed88..b7d3982 100644 --- a/internal/game/cmd_say.go +++ b/internal/game/cmd_say.go @@ -28,7 +28,7 @@ func (g *Game) doSay(sess *net.Session, msg string) { if other == sess { other.WriteLine(fmt.Sprintf("You say: %s", g.colorize(sess, "say", msg))) } else { - other.WriteLine(fmt.Sprintf("\n%s says: %s", g.colorize(other, "player_name", p.Name), g.colorize(other, "say", msg))) + other.WriteLine(fmt.Sprintf("%s says: %s", g.colorize(other, "player_name", p.Name), g.colorize(other, "say", msg))) } } } diff --git a/internal/game/cmd_shop.go b/internal/game/cmd_shop.go index d1b0e9c..91a11c6 100644 --- a/internal/game/cmd_shop.go +++ b/internal/game/cmd_shop.go @@ -88,7 +88,7 @@ func (g *Game) cmdShopSell(sess *net.Session, args []string) { } func (g *Game) writeShopPrompt(sess *net.Session) { - sess.Write(fmt.Sprintf("\n%s", g.colorize(sess, "dialog", "> "))) + sess.WritePrompt(g.colorize(sess, "dialog", "> ")) } func (g *Game) shopConfig(sess *net.Session) *behavior.ShopConfig { @@ -417,16 +417,16 @@ func (g *Game) leaveShop(sess *net.Session) { sess.WriteLine(fmt.Sprintf(" %d. %s", i+1, opt.Text)) } sess.State = net.StateTalk - sess.Write("\nChoice: ") + g.reprompt(sess) return } - if node.Next != "" { - next, ok := td.Cfg.Nodes[node.Next] + if node.Goto != "" { + next, ok := td.Cfg.Nodes[node.Goto] if !ok { break } - td.Node = node.Next + td.Node = node.Goto node = next } else { break diff --git a/internal/game/cmd_tech.go b/internal/game/cmd_tech.go index cb4c9f7..19b5425 100644 --- a/internal/game/cmd_tech.go +++ b/internal/game/cmd_tech.go @@ -157,7 +157,7 @@ func (g *Game) doTechList(sess *net.Session) { if p.QuickTech != "" { qDef := GetTechDef(p.QuickTech) if qDef != nil { - sess.WriteLine(fmt.Sprintf("\nQuick tech: %s", qDef.Name)) + sess.WriteLine(fmt.Sprintf("Quick tech: %s", qDef.Name)) } } } diff --git a/internal/game/cmd_trigger_transport.go b/internal/game/cmd_trigger_transport.go index 4272a07..ba86ebe 100644 --- a/internal/game/cmd_trigger_transport.go +++ b/internal/game/cmd_trigger_transport.go @@ -22,7 +22,7 @@ func (g *Game) triggerTransport(sess *net.Session, p *player.Player, mod *ModDef if g.Hub != nil { for _, other := range g.Hub.PlayersInRoom(p.RoomID) { if other != sess { - other.WriteLine(fmt.Sprintf("\n%s teleports away.", p.Name)) + other.WriteLine(fmt.Sprintf("%s teleports away.", p.Name)) } } } diff --git a/internal/game/cmd_use.go b/internal/game/cmd_use.go index 64f35c0..d399e8f 100644 --- a/internal/game/cmd_use.go +++ b/internal/game/cmd_use.go @@ -103,7 +103,7 @@ func (g *Game) useRoomObject(sess *net.Session, p *player.Player, input string) if ui.Action != nil { g.applyNodeAction(sess, ui.Action) } - g.broadcastAction(sess, "\n%s uses the %s.", p.Name, def.Name) + g.broadcastAction(sess, "%s uses the %s.", p.Name, def.Name) return true } if len(def.UseInteractions) > 0 { diff --git a/internal/game/cmd_who.go b/internal/game/cmd_who.go index 87c8a9c..f3c7092 100644 --- a/internal/game/cmd_who.go +++ b/internal/game/cmd_who.go @@ -23,7 +23,7 @@ func (g *Game) executeWho(sess *net.Session, args []string, rawInput string) { return strings.ToLower(players[i].Name) < strings.ToLower(players[j].Name) }) - sess.WriteLine(fmt.Sprintf("\nPlayers online (%d):", len(players))) + sess.WriteLine(fmt.Sprintf("Players online (%d):", len(players))) for _, p := range players { lvl := p.CombatLevel() levelStr := g.levelColorize(sess, myP.CombatLevel(), lvl, fmt.Sprintf("(level %d)", lvl)) diff --git a/internal/game/combat_attack.go b/internal/game/combat_attack.go index 61eb9c1..36a496b 100644 --- a/internal/game/combat_attack.go +++ b/internal/game/combat_attack.go @@ -182,9 +182,9 @@ func (g *Game) startCombat(sess *net.Session, p *player.Player, mob *world.MobIn } } if isTask { - g.broadcastAction(sess, "\n%s begins working on %s.", p.Name, mobDisplayName(mob, true)) + g.broadcastAction(sess, "%s begins working on %s.", p.Name, mobDisplayName(mob, true)) } else { - g.broadcastAction(sess, "\n%s attacks %s!", p.Name, mobDisplayName(mob, true)) + g.broadcastAction(sess, "%s attacks %s!", p.Name, mobDisplayName(mob, true)) } g.Ticks.Subscribe(1, func() bool { @@ -208,13 +208,13 @@ func (g *Game) startCombat(sess *net.Session, p *player.Player, mob *world.MobIn if mod != nil && g.hasJunkCost(p, mod) { g.scienceAttack(sess, p, currentMob, mod) } else if mod != nil { - sess.WriteLine(g.colorize(sess, "error", - fmt.Sprintf("\nYou're out of junk for %s!! You flail with your fists in desperation!", mod.Name))) + sess.WriteLine(g.colorize(sess, "error", + fmt.Sprintf("You're out of junk for %s!! You flail with your fists in desperation!", mod.Name))) g.playerAttackUnarmed(sess, p, currentMob) } else { p.AutotriggerMod = "" - sess.WriteLine(g.colorize(sess, "error", - "\nNo autotrigger set! Set a module with the 'autotrigger' command.")) + sess.WriteLine(g.colorize(sess, "error", + "No autotrigger set! Set a module with the 'autotrigger' command.")) g.playerAttackUnarmed(sess, p, currentMob) } } else { diff --git a/internal/game/combat_mob.go b/internal/game/combat_mob.go index 5558578..2abaf81 100644 --- a/internal/game/combat_mob.go +++ b/internal/game/combat_mob.go @@ -142,9 +142,9 @@ func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInst if complete == "" { complete = fmt.Sprintf("You finish your work on %s!", mobDisplayName(mob, true)) } - sess.WriteLine(g.colorize(sess, "victory", "\n"+complete)) - } else { - sess.WriteLine(g.colorize(sess, "victory", fmt.Sprintf("\nYou have defeated %s!", mobDisplayName(mob, true)))) + sess.WriteLine(g.colorize(sess, "victory", complete)) + } else { + sess.WriteLine(g.colorize(sess, "victory", fmt.Sprintf("You have defeated %s!", mobDisplayName(mob, true)))) g.onAssassinKill(sess, p, mob) } @@ -152,12 +152,12 @@ func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInst for _, other := range g.Hub.PlayersInRoom(p.RoomID) { if other != sess && other.Player != nil { if isTask { - other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s finishes working on %s.", p.Name, mobDisplayName(mob, false)))) + other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("%s finishes working on %s.", p.Name, mobDisplayName(mob, false)))) } else { op := other.Player mobLvl := mobCombatLevel(mob) levelStr := g.levelColorize(other, op.CombatLevel(), mobLvl, fmt.Sprintf("(level %d)", mobLvl)) - other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s has slain %s %s!", p.Name, mobDisplayName(mob, false), levelStr))) + other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("%s has slain %s %s!", p.Name, mobDisplayName(mob, false), levelStr))) } } } @@ -242,14 +242,14 @@ func (g *Game) killPlayer(sess *net.Session, p *player.Player, mob *world.MobIns if mob.HP < 0 { mob.HP = 0 } - sess.WriteLine(fmt.Sprintf("\nDead Man's Switch activates! %s takes %d damage!", - mobDisplayName(mob, true), retDmg)) + sess.WriteLine(fmt.Sprintf("Dead Man's Switch activates! %s takes %d damage!", + mobDisplayName(mob, true), retDmg)) } } } p.DeactivateAllTechs() p.Stats.RecordDeath() - sess.WriteLine(g.colorize(sess, "death", "\nOh dear, you are dead!")) + sess.WriteLine(g.colorize(sess, "death", "Oh dear, you are dead!")) p.ClearMoveState() p.HazardTimer = 0 if ss, ok := g.safespot.Get(p.Name); ok { diff --git a/internal/game/core_login_char.go b/internal/game/core_login_char.go index 5724c7b..d705684 100644 --- a/internal/game/core_login_char.go +++ b/internal/game/core_login_char.go @@ -122,7 +122,7 @@ func (g *Game) connectCharacter(sess *net.Session, name string) { } } - sess.Write("\r\n> ") + sess.WritePrompt("> ") } func (g *Game) handleRenameChar(sess *net.Session, input string) { diff --git a/internal/game/game.go b/internal/game/game.go index acd2924..3cfd811 100644 --- a/internal/game/game.go +++ b/internal/game/game.go @@ -153,7 +153,7 @@ func (g *Game) HandleSession(sess *net.Session, input string) { g.handleGameCommand(sess, input) case net.StateChangeDesc: g.handleDescChange(sess, input) - case net.StateTalk: + case net.StateTalk, net.StateTalkSequence: g.handleTalkInput(sess, input) case net.StateShop: g.handleShopInput(sess, input) @@ -177,11 +177,11 @@ func (g *Game) HandleSession(sess *net.Session, input string) { } func (g *Game) writePrompt(sess *net.Session) { - sess.Write("\r\n" + g.promptStr(sess)) + sess.WritePrompt(g.promptStr(sess)) } func (g *Game) reprompt(sess *net.Session) { - sess.Write(g.promptStr(sess)) + sess.Reprompt(g.promptStr(sess)) } func (g *Game) handleGameCommand(sess *net.Session, input string) { diff --git a/internal/game/look_entities.go b/internal/game/look_entities.go index 915317f..36cff07 100644 --- a/internal/game/look_entities.go +++ b/internal/game/look_entities.go @@ -334,7 +334,7 @@ func (g *Game) showRoomPlayers(sess *net.Session, p *player.Player, room *world. for _, other := range others { if other != sess && other.Player != nil { op := other.Player - line := fmt.Sprintf("\n%s is here", g.colorize(sess, "player_name", op.Name)) + line := fmt.Sprintf("%s is here", g.colorize(sess, "player_name", op.Name)) if cs := g.Combat.Get(op.Name); cs != nil { if mob := g.MobStore.GetInstance(cs.MobID); mob != nil && mob.HP > 0 { name := mobDisplayName(mob, false) diff --git a/internal/game/render_help.go b/internal/game/render_help.go index 1727153..869d306 100644 --- a/internal/game/render_help.go +++ b/internal/game/render_help.go @@ -133,7 +133,7 @@ func (g *Game) doHelp(sess *net.Session, topic string) { topic = strings.ToLower(topic) for _, h := range helps { if strings.ToLower(h.Name) == topic { - sess.WriteLine(fmt.Sprintf("\n %s", h.Content)) + sess.WriteLine(fmt.Sprintf(" %s", h.Content)) return } } diff --git a/internal/game/sys_assassin.go b/internal/game/sys_assassin.go index 720529c..65ddb7c 100644 --- a/internal/game/sys_assassin.go +++ b/internal/game/sys_assassin.go @@ -93,7 +93,7 @@ func (g *Game) onAssassinKill(sess *net.Session, p *player.Player, mob *world.Mo rep += bonus currentRep := getPlayerFlagInt(p, "assassin_reputation") g.setPlayerFlag(p, "assassin_reputation", currentRep+rep) - sess.WriteLine(g.colorize(sess, "assassin_task", fmt.Sprintf("\n*** Assassin task complete! ***"))) + sess.WriteLine(g.colorize(sess, "assassin_task", fmt.Sprintf("*** Assassin task complete! ***"))) if bonus > 0 { sess.WriteLine(g.colorize(sess, "assassin_task", fmt.Sprintf(" Streak bonus! %d tasks in a row. +%d bonus reputation.", streak, bonus))) } diff --git a/internal/game/sys_buff.go b/internal/game/sys_buff.go index 9449731..3b8555c 100644 --- a/internal/game/sys_buff.go +++ b/internal/game/sys_buff.go @@ -28,7 +28,7 @@ func (g *Game) BuffTick() { } if dirty { p.ActiveBuffs = remaining - sess.WriteLine(g.colorize(sess, "broadcast", "\nYour potion buff has worn off.")) + sess.WriteLine(g.colorize(sess, "broadcast", "Your potion buff has worn off.")) } } } diff --git a/internal/game/sys_combat.go b/internal/game/sys_combat.go index e020ccf..19dbb98 100644 --- a/internal/game/sys_combat.go +++ b/internal/game/sys_combat.go @@ -107,7 +107,7 @@ func (g *Game) checkAggro(sess *net.Session) { if !aggMob.Unique { attacker = "The " + aggMob.Name } - sess.WriteLine(fmt.Sprintf("\n%s attacks you!", g.colorize(sess, "mob", attacker))) + sess.WriteLine(fmt.Sprintf("%s attacks you!", g.colorize(sess, "mob", attacker))) g.startCombat(sess, p, aggMob) return false }) diff --git a/internal/game/sys_hazard.go b/internal/game/sys_hazard.go index 29bf65e..c3b52b7 100644 --- a/internal/game/sys_hazard.go +++ b/internal/game/sys_hazard.go @@ -92,7 +92,7 @@ func (g *Game) rollHazard(sess *net.Session, p *player.Player, hz *world.HazardD if miss == "" { miss = fmt.Sprintf("You weather the %s.", hz.Name) } - sess.WriteLine("\n" + g.colorize(sess, "miss", miss)) + sess.WriteLine(g.colorize(sess, "miss", miss)) g.writePrompt(sess) } } @@ -123,7 +123,7 @@ func (g *Game) applyHazardHit(sess *net.Session, p *player.Player, hz *world.Haz msg = fmt.Sprintf(msg, dmg) } hpSuffix := fmt.Sprintf(" %s [%d/%d]", g.hpBar(sess, p.HP, p.MaxHP()), p.HP, p.MaxHP()) - sess.WriteLine("\n" + g.colorize(sess, "damage_taken", msg) + hpSuffix) + sess.WriteLine(g.colorize(sess, "damage_taken", msg) + hpSuffix) // NB: a hazard never clears MoveState, so it cannot interrupt a walk the way // a mob hit ("Can't escape!") does. diff --git a/internal/game/tick.go b/internal/game/tick.go index ee9ecf2..f8d6f04 100644 --- a/internal/game/tick.go +++ b/internal/game/tick.go @@ -142,17 +142,17 @@ func (g *Game) WanderTick() { if p.RoomID == m.fromRoom && p.OptionBool("mob_leave") { if m.level > 0 { levelStr := g.levelColorize(sess, p.CombatLevel(), m.level, fmt.Sprintf("(level %d)", m.level)) - sess.WriteLine(fmt.Sprintf("\n%s %s leaves.", g.colorize(sess, "mob", m.name), levelStr)) + sess.WriteLine(fmt.Sprintf("%s %s leaves.", g.colorize(sess, "mob", m.name), levelStr)) } else { - sess.WriteLine(fmt.Sprintf("\n%s moves away.", g.colorize(sess, "mob", m.name))) + sess.WriteLine(fmt.Sprintf("%s moves away.", g.colorize(sess, "mob", m.name))) } } if p.RoomID == m.toRoom && p.OptionBool("mob_enter") { if m.level > 0 { levelStr := g.levelColorize(sess, p.CombatLevel(), m.level, fmt.Sprintf("(level %d)", m.level)) - sess.WriteLine(fmt.Sprintf("\n%s %s enters.", g.colorize(sess, "mob", m.name), levelStr)) + sess.WriteLine(fmt.Sprintf("%s %s enters.", g.colorize(sess, "mob", m.name), levelStr)) } else { - sess.WriteLine(fmt.Sprintf("\n%s drifts in.", g.colorize(sess, "mob", m.name))) + sess.WriteLine(fmt.Sprintf("%s drifts in.", g.colorize(sess, "mob", m.name))) } } } @@ -269,8 +269,8 @@ func (g *Game) TechTick() { if p.Battery <= 0 { p.Battery = 0 p.DeactivateAllTechs() - sess.WriteLine(g.colorize(sess, "tech_depleted", - "\nYour battery is depleted! All tech has been disabled.")) + sess.WriteLine(g.colorize(sess, "tech_depleted", + "Your battery is depleted! All tech has been disabled.")) g.writePrompt(sess) } } diff --git a/internal/game/triggers.go b/internal/game/triggers.go index 3b6f609..cd1bbd3 100644 --- a/internal/game/triggers.go +++ b/internal/game/triggers.go @@ -56,7 +56,7 @@ func (g *Game) TransientMobTick() { g.MobStore.RemoveInstance(inst.InstanceID) for _, sess := range g.Hub.PlayersInRoom(inst.RoomID) { sess.WriteLine(g.colorize(sess, "broadcast", - fmt.Sprintf("\n%s fades away.", mobDisplayName(inst, true)))) + fmt.Sprintf("%s fades away.", mobDisplayName(inst, true)))) } } } @@ -92,13 +92,8 @@ func (g *Game) processPlayerTriggerSequences() { } } - firstMsg := true for _, step := range jobs { s := step - if firstMsg && s.Message != "" { - s.Message = "\n" + s.Message - firstMsg = false - } g.executeTriggerStep(sess, p, &s, seq.RoomID, seq.FlagValue) } @@ -153,7 +148,7 @@ func (g *Game) executeTriggerStep(sess *net.Session, p *player.Player, step *wor for _, other := range g.Hub.PlayersInRoom(roomID) { otherMode := g.colorMode(other) rendered := color.ExpandTagsDefault(otherMode, broadcastSpec, msg) - other.WriteLine(g.colorize(other, "broadcast", "\n"+rendered)) + other.WriteLine(g.colorize(other, "broadcast", rendered)) } } if step.BroadcastGlobal != "" && g.Hub != nil { @@ -162,7 +157,7 @@ func (g *Game) executeTriggerStep(sess *net.Session, p *player.Player, step *wor if other.Player != nil { otherMode := g.colorMode(other) rendered := color.ExpandTagsDefault(otherMode, broadcastSpec, msg) - other.WriteLine(g.colorize(other, "broadcast", "\n"+rendered)) + other.WriteLine(g.colorize(other, "broadcast", rendered)) } } } @@ -216,7 +211,7 @@ func (g *Game) executeWorldTriggerStep(step *world.TriggerStep, roomID int, flag for _, other := range g.Hub.PlayersInRoom(roomID) { otherMode := g.colorMode(other) rendered := color.ExpandTagsDefault(otherMode, broadcastSpec, msg) - other.WriteLine(g.colorize(other, "broadcast", "\n"+rendered)) + other.WriteLine(g.colorize(other, "broadcast", rendered)) } } if step.BroadcastGlobal != "" && g.Hub != nil { @@ -226,7 +221,7 @@ func (g *Game) executeWorldTriggerStep(step *world.TriggerStep, roomID int, flag if other.Player != nil { otherMode := g.colorMode(other) rendered := color.ExpandTagsDefault(otherMode, broadcastSpec, msg) - other.WriteLine(g.colorize(other, "broadcast", "\n"+rendered)) + other.WriteLine(g.colorize(other, "broadcast", rendered)) } } } @@ -254,7 +249,7 @@ func (g *Game) spawnTriggerMob(sess *net.Session, p *player.Player, cfg *world.S return } inst.ResetDespawnCounter(engine.ToTicks(cfg.DespawnTicks)) - sess.WriteLine(g.colorize(sess, "broadcast", "\n"+mobDisplayName(inst, true)+" appears!")) + sess.WriteLine(g.colorize(sess, "broadcast", mobDisplayName(inst, true)+" appears!")) } func (g *Game) spawnWorldTriggerMob(cfg *world.SpawnMobConfig, roomID int) { @@ -272,8 +267,8 @@ func (g *Game) spawnWorldTriggerMob(cfg *world.SpawnMobConfig, roomID int) { inst.ResetDespawnCounter(engine.ToTicks(cfg.DespawnTicks)) if g.Hub != nil { for _, sess := range g.Hub.PlayersInRoom(roomID) { - sess.WriteLine(g.colorize(sess, "broadcast", - "\n"+mobDisplayName(inst, true)+" appears!")) + sess.WriteLine(g.colorize(sess, "broadcast", + mobDisplayName(inst, true)+" appears!")) } } } @@ -292,8 +287,8 @@ func (g *Game) despawnTriggerMobs(mobID string, owner string) { } if g.Hub != nil { for _, sess := range g.Hub.PlayersInRoom(inst.RoomID) { - sess.WriteLine(g.colorize(sess, "broadcast", - fmt.Sprintf("\n%s vanishes.", mobDisplayName(inst, true)))) + sess.WriteLine(g.colorize(sess, "broadcast", + fmt.Sprintf("%s vanishes.", mobDisplayName(inst, true)))) } } g.MobStore.RemoveInstance(inst.InstanceID) diff --git a/internal/net/server.go b/internal/net/server.go index 9ae8532..731beef 100644 --- a/internal/net/server.go +++ b/internal/net/server.go @@ -32,6 +32,7 @@ const ( StateGame StateChangeDesc StateTalk + StateTalkSequence StateDropAllConfirm StateRecipeChoice StateHowMany @@ -61,6 +62,7 @@ type Session struct { Disconnecting bool DisconnectTicks int Shop *behavior.ShopConfig + promptVisible bool writeMu sync.Mutex } @@ -346,6 +348,10 @@ func (sess *Session) wrap(msg string) string { func (sess *Session) Write(msg string) { sess.writeMu.Lock() defer sess.writeMu.Unlock() + if sess.promptVisible { + sess.Conn.Write([]byte("\r\n")) + sess.promptVisible = false + } sess.Conn.Write([]byte(msg)) } @@ -353,13 +359,21 @@ func (sess *Session) WriteLine(msg string) { msg = sess.wrap(msg) sess.writeMu.Lock() defer sess.writeMu.Unlock() + if sess.promptVisible { + sess.Conn.Write([]byte("\r\n")) + sess.promptVisible = false + } sess.Conn.Write([]byte(msg + "\r\n")) } func (sess *Session) WriteLines(lines ...string) { sess.writeMu.Lock() defer sess.writeMu.Unlock() - for _, l := range lines { + for i, l := range lines { + if i == 0 && sess.promptVisible { + sess.Conn.Write([]byte("\r\n")) + sess.promptVisible = false + } sess.Conn.Write([]byte(sess.wrap(l) + "\r\n")) } } @@ -368,9 +382,30 @@ func (sess *Session) Writef(format string, args ...interface{}) { msg := sess.wrap(fmt.Sprintf(format, args...)) sess.writeMu.Lock() defer sess.writeMu.Unlock() + if sess.promptVisible { + sess.Conn.Write([]byte("\r\n")) + sess.promptVisible = false + } sess.Conn.Write([]byte(msg)) } +func (sess *Session) WritePrompt(prompt string) { + sess.writeMu.Lock() + defer sess.writeMu.Unlock() + if sess.promptVisible { + return + } + sess.Conn.Write([]byte(prompt)) + sess.promptVisible = true +} + +func (sess *Session) Reprompt(prompt string) { + sess.writeMu.Lock() + defer sess.writeMu.Unlock() + sess.Conn.Write([]byte(prompt)) + sess.promptVisible = true +} + func (sess *Session) Close() error { sess.writeMu.Lock() defer sess.writeMu.Unlock() diff --git a/internal/validate/checks.go b/internal/validate/checks.go index 9cff717..f1d6b5e 100644 --- a/internal/validate/checks.go +++ b/internal/validate/checks.go @@ -806,12 +806,12 @@ func validateTalkConfig(prefix string, cfg *behavior.TalkConfig, itemIDs map[str if node.Action != nil { issues = append(issues, validateNodeAction(nodePrefix, node.Action, itemIDs, roomIndex)...) } - if node.Next != "" { - if _, ok := cfg.Nodes[node.Next]; !ok { + if node.Goto != "" { + if _, ok := cfg.Nodes[node.Goto]; !ok { issues = append(issues, Issue{ Level: "ERROR", Type: "reference", - Message: fmt.Sprintf("%s: next points to nonexistent node %q", nodePrefix, node.Next), + Message: fmt.Sprintf("%s: goto points to nonexistent node %q", nodePrefix, node.Goto), }) } } -- cgit v1.2.3