aboutsummaryrefslogtreecommitdiff
path: root/internal/game
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-25 21:23:34 -0400
committerhistoria <[not public]>2026-06-25 21:23:34 -0400
commite2d5b081f27141bb3dd89dbe595860b8a1345d55 (patch)
tree3090f8150580fe85a872caa04165ff70613bbaa8 /internal/game
parent84d74ca4351a97148ad8339676f9df7946bc21fb (diff)
downloadthehouseoficarus-e2d5b081f27141bb3dd89dbe595860b8a1345d55.tar.gz
feat: on_enter features ported over to triggers, cosmetic fixes with trigger messages
Diffstat (limited to 'internal/game')
-rw-r--r--internal/game/act_room.go104
-rw-r--r--internal/game/cmd_move.go4
-rw-r--r--internal/game/core_login_char.go12
-rw-r--r--internal/game/render_color.go2
-rw-r--r--internal/game/triggers.go31
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))
}
}
}