diff options
Diffstat (limited to 'internal/game')
| -rw-r--r-- | internal/game/act_room.go | 104 | ||||
| -rw-r--r-- | internal/game/cmd_move.go | 4 | ||||
| -rw-r--r-- | internal/game/core_login_char.go | 12 | ||||
| -rw-r--r-- | internal/game/render_color.go | 2 | ||||
| -rw-r--r-- | internal/game/triggers.go | 31 |
5 files changed, 115 insertions, 38 deletions
diff --git a/internal/game/act_room.go b/internal/game/act_room.go index fec7304..ff7a780 100644 --- a/internal/game/act_room.go +++ b/internal/game/act_room.go @@ -4,13 +4,14 @@ import ( "fmt" "strings" + "thehouseoficarus/internal/color" "thehouseoficarus/internal/net" "thehouseoficarus/internal/player" "thehouseoficarus/internal/world" ) -// enterSeq is an in-flight on_enter cutscene for a single player. It is driven -// one step at a time by EnterSeqTick. +// enterSeq is an in-flight on_enter stepped sequence for a single player. It is +// driven one step at a time by EnterSeqTick. type enterSeq struct { sess *net.Session playerName string @@ -22,8 +23,8 @@ type enterSeq struct { // runEnterSteps evaluates a room's on_enter script for the player. Steps whose // condition fails are dropped. If none of the surviving steps are "timed" -// (no delay, no flag mutation) the messages are printed synchronously, exactly -// as before. Otherwise the surviving steps become a scheduled cutscene driven +// (no delay, no flag mutation) the messages are printed synchronously. +// Otherwise the surviving steps become a scheduled enter sequence driven // by EnterSeqTick. func (g *Game) runEnterSteps(sess *net.Session, roomID int) { room, err := g.World.LoadRoom(roomID) @@ -36,21 +37,21 @@ func (g *Game) runEnterSteps(sess *net.Session, roomID int) { } var steps []world.EnterStep - cutscene := false + sequenced := false for _, step := range room.OnEnter { if step.Condition != nil && !g.checkCondition(sess, step.Condition) { continue } steps = append(steps, step) if step.IsTimed() { - cutscene = true + sequenced = true } } if len(steps) == 0 { return } - if !cutscene { + if !sequenced { for _, step := range steps { if step.Message != "" { sess.WriteLine(step.Message) @@ -59,16 +60,16 @@ func (g *Game) runEnterSteps(sess *net.Session, roomID int) { return } - g.startEnterCutscene(sess, p, roomID, steps) + g.startEnterSeq(sess, p, roomID, steps) } -// startEnterCutscene registers a scheduled on_enter sequence and persists a -// marker on the player so the cutscene can be resumed on reconnect if it is -// interrupted (disconnect, server restart). -func (g *Game) startEnterCutscene(sess *net.Session, p *player.Player, roomID int, steps []world.EnterStep) { +// startEnterSeq registers a scheduled on_enter sequence and persists a marker +// on the player so the sequence can be resumed on reconnect if interrupted +// (disconnect, server restart). +func (g *Game) startEnterSeq(sess *net.Session, p *player.Player, roomID int, steps []world.EnterStep) { g.cancelEnterSeq(p.Name) - if p.CutsceneRoom != roomID { - p.CutsceneRoom = roomID + if p.EnterSeqRoom != roomID { + p.EnterSeqRoom = roomID g.AccountStore.SaveCharacter(p) } g.enterMu.Lock() @@ -82,9 +83,9 @@ func (g *Game) startEnterCutscene(sess *net.Session, p *player.Player, roomID in g.enterMu.Unlock() } -// EnterSeqTick advances every in-flight cutscene by one tick. Sequences for a -// player who is no longer connected are paused (left for resume); sequences are -// removed from the map on completion or cancellation. +// EnterSeqTick advances every in-flight enter sequence by one tick. Sequences +// for a player who is no longer connected are paused (left for resume); +// sequences are removed from the map on completion or cancellation. func (g *Game) EnterSeqTick() { g.enterMu.Lock() seqs := make([]*enterSeq, 0, len(g.enterSeqs)) @@ -137,20 +138,71 @@ func (g *Game) fireEnterStep(seq *enterSeq, step world.EnterStep) { if p == nil || p.RoomID != seq.roomID { return } + seqSpec := g.resolveColor(seq.sess, "sequence") + broadcastSpec := g.resolveColor(seq.sess, "broadcast") + mode := g.colorMode(seq.sess) if step.Message != "" { - // Break from the prompt on the first line only, so a multi-line - // cutscene reads as tight consecutive lines. + msg := expandTemplate(step.Message, p.Name, nil) + msg = color.ExpandTagsDefault(mode, seqSpec, msg) if !seq.started { - seq.sess.WriteLine("\n" + step.Message) + seq.sess.WriteLine("\n" + msg) seq.started = true } else { - seq.sess.WriteLine(step.Message) + 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)) + } + } + if step.BroadcastGlobal != "" && g.Hub != nil { + msg := expandTemplate(step.BroadcastGlobal, p.Name, nil) + msg = color.ExpandTagsDefault(mode, broadcastSpec, msg) + for _, other := range g.Hub.AllSessions() { + if other.Player != nil { + other.WriteLine(g.colorize(other, "broadcast", "\n"+msg)) + } } } if len(step.SetFlags) > 0 || len(step.SetPlayerFlags) > 0 { g.applyFlagMutations(p, step.SetFlags, step.SetPlayerFlags) g.AccountStore.SaveCharacter(p) } + if step.SpawnMob != nil { + g.spawnTriggerMob(seq.sess, p, step.SpawnMob, seq.roomID) + } + if step.DespawnMob != "" { + g.despawnTriggerMobs(step.DespawnMob, p.Name) + } + if step.GiveItem != "" { + slot := p.FirstFreeSlot() + if slot == -1 { + seq.sess.WriteLine("Your inventory is too full to receive that.") + } else { + p.SetInvSlot(slot, &player.InventorySlot{ItemID: step.GiveItem, Quantity: 1}) + g.AccountStore.SaveCharacter(p) + } + } + if step.TakeItem != "" { + if p.HasItem(step.TakeItem) { + p.RemoveItem(step.TakeItem, 1) + g.AccountStore.SaveCharacter(p) + } + } + if step.Heal > 0 { + p.HP += step.Heal + if maxHP := p.MaxHP(); p.HP > maxHP { + p.HP = maxHP + } + g.AccountStore.SaveCharacter(p) + seq.sess.WriteLine(fmt.Sprintf("You regain %d hitpoints.", step.Heal)) + } + if step.Teleport > 0 { + g.teleportPlayer(seq.sess, p, step.Teleport) + } } func (g *Game) completeEnterSeq(seq *enterSeq) { @@ -161,16 +213,16 @@ func (g *Game) completeEnterSeq(seq *enterSeq) { if p == nil { return } - if p.CutsceneRoom == seq.roomID { - p.CutsceneRoom = 0 + if p.EnterSeqRoom == seq.roomID { + p.EnterSeqRoom = 0 g.AccountStore.SaveCharacter(p) } g.writePrompt(seq.sess) } -// cancelEnterSeq drops any in-flight cutscene for the player. It does not touch -// the persisted CutsceneRoom marker, so a cutscene cancelled by disconnect can -// still be resumed on reconnect. +// cancelEnterSeq drops any in-flight enter sequence for the player. It does not +// touch the persisted EnterSeqRoom marker, so a sequence cancelled by +// disconnect can still be resumed on reconnect. func (g *Game) cancelEnterSeq(name string) { g.enterMu.Lock() delete(g.enterSeqs, name) diff --git a/internal/game/cmd_move.go b/internal/game/cmd_move.go index 63f9bcd..2939c34 100644 --- a/internal/game/cmd_move.go +++ b/internal/game/cmd_move.go @@ -149,8 +149,8 @@ func (g *Game) completeMove(sess *net.Session, p *player.Player) { p.Stats.RecordRoomVisit(targetID) g.cancelEnterSeq(p.Name) - if p.CutsceneRoom != 0 && p.CutsceneRoom == oldRoom { - p.CutsceneRoom = 0 + if p.EnterSeqRoom != 0 && p.EnterSeqRoom == oldRoom { + p.EnterSeqRoom = 0 } if len(pendingFlags) > 0 || len(pendingPlayerFlags) > 0 { g.applyFlagMutations(p, pendingFlags, pendingPlayerFlags) diff --git a/internal/game/core_login_char.go b/internal/game/core_login_char.go index dec6dae..5724c7b 100644 --- a/internal/game/core_login_char.go +++ b/internal/game/core_login_char.go @@ -57,6 +57,10 @@ func (g *Game) handleNewCharName(sess *net.Session, input string) { sess.Account.Characters = acc.Characters g.connectCharacter(sess, name) + + if sess.Player != nil { + g.runEnterSteps(sess, sess.Player.RoomID) + } } func (g *Game) connectCharacter(sess *net.Session, name string) { @@ -102,18 +106,18 @@ func (g *Game) connectCharacter(sess *net.Session, name string) { g.doLook(sess) g.checkAggro(sess) - // Resume an interrupted on_enter cutscene (e.g. disconnected mid-sequence). + // Resume an interrupted on_enter sequence (e.g. disconnected mid-sequence). // Only fires when a persisted marker matches the room the player is in, so // ordinary on_enter scripts never run on login. - if p.CutsceneRoom != 0 { - if p.CutsceneRoom == p.RoomID { + if p.EnterSeqRoom != 0 { + if p.EnterSeqRoom == p.RoomID { g.runEnterSteps(sess, p.RoomID) } g.enterMu.Lock() _, active := g.enterSeqs[p.Name] g.enterMu.Unlock() if !active { - p.CutsceneRoom = 0 + p.EnterSeqRoom = 0 g.AccountStore.SaveCharacter(p) } } diff --git a/internal/game/render_color.go b/internal/game/render_color.go index c6f154c..a5ab8fa 100644 --- a/internal/game/render_color.go +++ b/internal/game/render_color.go @@ -16,7 +16,7 @@ func (g *Game) colorMode(sess *net.Session) string { } func (g *Game) resolveColor(sess *net.Session, category string) color.ColorSpec { - if sess.Account != nil && sess.Account.Colors != nil { + if sess != nil && sess.Account != nil && sess.Account.Colors != nil { if val, ok := sess.Account.Colors[category]; ok { if val == "off" { return color.NoColor() diff --git a/internal/game/triggers.go b/internal/game/triggers.go index 76ce9cd..3b6f609 100644 --- a/internal/game/triggers.go +++ b/internal/game/triggers.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "thehouseoficarus/internal/color" "thehouseoficarus/internal/engine" "thehouseoficarus/internal/net" "thehouseoficarus/internal/player" @@ -91,8 +92,14 @@ func (g *Game) processPlayerTriggerSequences() { } } + firstMsg := true for _, step := range jobs { - g.executeTriggerStep(sess, p, &step, seq.RoomID, seq.FlagValue) + s := step + if firstMsg && s.Message != "" { + s.Message = "\n" + s.Message + firstMsg = false + } + g.executeTriggerStep(sess, p, &s, seq.RoomID, seq.FlagValue) } if len(seq.Steps) == 0 { @@ -133,21 +140,29 @@ func (g *Game) processWorldTriggerSequences() { } func (g *Game) executeTriggerStep(sess *net.Session, p *player.Player, step *world.TriggerStep, roomID int, flagValue any) { + seqSpec := g.resolveColor(sess, "sequence") + broadcastSpec := g.resolveColor(sess, "broadcast") + playerMode := g.colorMode(sess) if step.Message != "" { msg := expandTemplate(step.Message, p.Name, flagValue) + msg = color.ExpandTagsDefault(playerMode, seqSpec, msg) sess.WriteLine(msg) } if step.Broadcast != "" && g.Hub != nil { msg := expandTemplate(step.Broadcast, p.Name, flagValue) for _, other := range g.Hub.PlayersInRoom(roomID) { - other.WriteLine(g.colorize(other, "broadcast", "\n"+msg)) + otherMode := g.colorMode(other) + rendered := color.ExpandTagsDefault(otherMode, broadcastSpec, msg) + other.WriteLine(g.colorize(other, "broadcast", "\n"+rendered)) } } if step.BroadcastGlobal != "" && g.Hub != nil { msg := expandTemplate(step.BroadcastGlobal, p.Name, flagValue) for _, other := range g.Hub.AllSessions() { if other.Player != nil { - other.WriteLine(g.colorize(other, "broadcast", "\n"+msg)) + otherMode := g.colorMode(other) + rendered := color.ExpandTagsDefault(otherMode, broadcastSpec, msg) + other.WriteLine(g.colorize(other, "broadcast", "\n"+rendered)) } } } @@ -197,15 +212,21 @@ func (g *Game) executeTriggerStep(sess *net.Session, p *player.Player, step *wor func (g *Game) executeWorldTriggerStep(step *world.TriggerStep, roomID int, flagValue any) { if step.Broadcast != "" && g.Hub != nil { msg := expandTemplate(step.Broadcast, "", flagValue) + broadcastSpec := g.resolveColor(nil, "broadcast") for _, other := range g.Hub.PlayersInRoom(roomID) { - other.WriteLine(g.colorize(other, "broadcast", "\n"+msg)) + otherMode := g.colorMode(other) + rendered := color.ExpandTagsDefault(otherMode, broadcastSpec, msg) + other.WriteLine(g.colorize(other, "broadcast", "\n"+rendered)) } } if step.BroadcastGlobal != "" && g.Hub != nil { msg := expandTemplate(step.BroadcastGlobal, "", flagValue) + broadcastSpec := g.resolveColor(nil, "broadcast") for _, other := range g.Hub.AllSessions() { if other.Player != nil { - other.WriteLine(g.colorize(other, "broadcast", "\n"+msg)) + otherMode := g.colorMode(other) + rendered := color.ExpandTagsDefault(otherMode, broadcastSpec, msg) + other.WriteLine(g.colorize(other, "broadcast", "\n"+rendered)) } } } |
