aboutsummaryrefslogtreecommitdiff
path: root/internal/game/triggers.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-25 20:33:47 -0400
committerhistoria <[not public]>2026-06-25 20:33:47 -0400
commit84d74ca4351a97148ad8339676f9df7946bc21fb (patch)
treea9f50ac56e8bbcdf2b307ef6f7143b67bce46c61 /internal/game/triggers.go
parentc55d0e4150b23f2c5c9f96bbc3d4dc2fc6dbaa36 (diff)
downloadthehouseoficarus-84d74ca4351a97148ad8339676f9df7946bc21fb.tar.gz
feat: trigger system for scripted events added.
Diffstat (limited to 'internal/game/triggers.go')
-rw-r--r--internal/game/triggers.go291
1 files changed, 291 insertions, 0 deletions
diff --git a/internal/game/triggers.go b/internal/game/triggers.go
new file mode 100644
index 0000000..76ce9cd
--- /dev/null
+++ b/internal/game/triggers.go
@@ -0,0 +1,291 @@
+package game
+
+import (
+ "fmt"
+ "strings"
+
+ "thehouseoficarus/internal/engine"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+ "thehouseoficarus/internal/world"
+)
+
+func (g *Game) TriggerSeqTick() {
+ g.processPlayerTriggerSequences()
+ g.processWorldTriggerSequences()
+}
+
+func (g *Game) TransientMobTick() {
+ if g.Hub == nil {
+ return
+ }
+ for _, inst := range g.MobStore.AllInstances() {
+ if inst.Owner == "" || inst.HP <= 0 {
+ continue
+ }
+ if !inst.DespawnOnLeave {
+ continue
+ }
+
+ g.charsMu.Lock()
+ ownerSess := g.loggedInChars[inst.Owner]
+ g.charsMu.Unlock()
+ if ownerSess == nil || ownerSess.Player == nil {
+ continue
+ }
+
+ ownerRoom := ownerSess.Player.RoomID
+ allowed := inst.DespawnRooms
+ if len(allowed) == 0 {
+ allowed = []int{inst.HomeRoomID}
+ }
+ inAllowed := false
+ for _, r := range allowed {
+ if ownerRoom == r {
+ inAllowed = true
+ break
+ }
+ }
+ if inAllowed {
+ inst.DespawnCounter = inst.DespawnTickCount()
+ continue
+ }
+ inst.DecrementDespawnCounter()
+ if inst.DespawnCounter <= 0 {
+ 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))))
+ }
+ }
+ }
+}
+
+func (g *Game) processPlayerTriggerSequences() {
+ seqs := g.TriggerStore.SnapshotPlayerSeqs()
+ for _, seq := range seqs {
+ g.charsMu.Lock()
+ sess := g.loggedInChars[seq.PlayerName]
+ g.charsMu.Unlock()
+ if sess == nil || sess.Player == nil {
+ g.TriggerStore.RemovePlayerSeq(seq.PlayerName, seq.TriggerID)
+ continue
+ }
+ p := sess.Player
+ if p.RoomID != seq.RoomID {
+ continue
+ }
+
+ seq.Wait--
+ if seq.Wait > 0 {
+ continue
+ }
+
+ var jobs []world.TriggerStep
+ for len(seq.Steps) > 0 && seq.Wait <= 0 {
+ step := seq.Steps[0]
+ seq.Steps = seq.Steps[1:]
+ jobs = append(jobs, step)
+ if len(seq.Steps) > 0 {
+ seq.Wait = seq.Steps[0].Delay
+ }
+ }
+
+ for _, step := range jobs {
+ g.executeTriggerStep(sess, p, &step, seq.RoomID, seq.FlagValue)
+ }
+
+ if len(seq.Steps) == 0 {
+ g.TriggerStore.RemovePlayerSeq(seq.PlayerName, seq.TriggerID)
+ if p.RoomID == seq.RoomID && sess.State == net.StateGame {
+ g.writePrompt(sess)
+ }
+ }
+ }
+}
+
+func (g *Game) processWorldTriggerSequences() {
+ seqs := g.TriggerStore.SnapshotWorldSeqs()
+ for _, seq := range seqs {
+ seq.Wait--
+ if seq.Wait > 0 {
+ continue
+ }
+
+ var jobs []world.TriggerStep
+ for len(seq.Steps) > 0 && seq.Wait <= 0 {
+ step := seq.Steps[0]
+ seq.Steps = seq.Steps[1:]
+ jobs = append(jobs, step)
+ if len(seq.Steps) > 0 {
+ seq.Wait = seq.Steps[0].Delay
+ }
+ }
+
+ for _, step := range jobs {
+ g.executeWorldTriggerStep(&step, seq.RoomID, seq.FlagValue)
+ }
+
+ if len(seq.Steps) == 0 {
+ g.TriggerStore.RemoveWorldSeq(seq.TriggerID)
+ }
+ }
+}
+
+func (g *Game) executeTriggerStep(sess *net.Session, p *player.Player, step *world.TriggerStep, roomID int, flagValue any) {
+ if step.Message != "" {
+ msg := expandTemplate(step.Message, p.Name, flagValue)
+ 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))
+ }
+ }
+ 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))
+ }
+ }
+ }
+ if len(step.SetFlags) > 0 {
+ g.Flags.SetAll(step.SetFlags)
+ }
+ if len(step.SetPlayerFlags) > 0 {
+ for k, v := range step.SetPlayerFlags {
+ g.setPlayerFlag(p, k, v)
+ }
+ g.AccountStore.SaveCharacter(p)
+ }
+ if step.GiveItem != "" {
+ slot := p.FirstFreeSlot()
+ if slot == -1 {
+ 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)
+ sess.WriteLine(fmt.Sprintf("You regain %d hitpoints.", step.Heal))
+ }
+ if step.SpawnMob != nil {
+ g.spawnTriggerMob(sess, p, step.SpawnMob, roomID)
+ }
+ if step.DespawnMob != "" {
+ g.despawnTriggerMobs(step.DespawnMob, p.Name)
+ }
+ if step.Teleport > 0 {
+ g.teleportPlayer(sess, p, step.Teleport)
+ }
+}
+
+func (g *Game) executeWorldTriggerStep(step *world.TriggerStep, roomID int, flagValue any) {
+ if step.Broadcast != "" && g.Hub != nil {
+ msg := expandTemplate(step.Broadcast, "", flagValue)
+ for _, other := range g.Hub.PlayersInRoom(roomID) {
+ other.WriteLine(g.colorize(other, "broadcast", "\n"+msg))
+ }
+ }
+ if step.BroadcastGlobal != "" && g.Hub != nil {
+ msg := expandTemplate(step.BroadcastGlobal, "", flagValue)
+ for _, other := range g.Hub.AllSessions() {
+ if other.Player != nil {
+ other.WriteLine(g.colorize(other, "broadcast", "\n"+msg))
+ }
+ }
+ }
+ if len(step.SetFlags) > 0 {
+ g.Flags.SetAll(step.SetFlags)
+ }
+ if step.SpawnMob != nil {
+ g.spawnWorldTriggerMob(step.SpawnMob, roomID)
+ }
+ if step.DespawnMob != "" {
+ g.despawnTriggerMobs(step.DespawnMob, "")
+ }
+}
+
+func (g *Game) spawnTriggerMob(sess *net.Session, p *player.Player, cfg *world.SpawnMobConfig, roomID int) {
+ if cfg.ID == "" {
+ return
+ }
+ def, err := g.MobStore.LoadDef(cfg.ID)
+ if err != nil {
+ return
+ }
+ inst := g.MobStore.SpawnTransient(def, cfg, roomID, p.Name)
+ if inst == nil {
+ return
+ }
+ inst.ResetDespawnCounter(engine.ToTicks(cfg.DespawnTicks))
+ sess.WriteLine(g.colorize(sess, "broadcast", "\n"+mobDisplayName(inst, true)+" appears!"))
+}
+
+func (g *Game) spawnWorldTriggerMob(cfg *world.SpawnMobConfig, roomID int) {
+ if cfg.ID == "" {
+ return
+ }
+ def, err := g.MobStore.LoadDef(cfg.ID)
+ if err != nil {
+ return
+ }
+ inst := g.MobStore.SpawnTransient(def, cfg, roomID, "")
+ if inst == nil {
+ return
+ }
+ 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!"))
+ }
+ }
+}
+
+func (g *Game) despawnTriggerMobs(mobID string, owner string) {
+ for _, inst := range g.MobStore.AllInstances() {
+ if inst.DefID != mobID {
+ continue
+ }
+ if owner != "" && inst.Owner != owner {
+ continue
+ }
+ if inst.HP <= 0 {
+ g.MobStore.RemoveInstance(inst.InstanceID)
+ continue
+ }
+ 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))))
+ }
+ }
+ g.MobStore.RemoveInstance(inst.InstanceID)
+ }
+}
+
+func expandTemplate(tmpl string, playerName string, flagValue any) string {
+ s := tmpl
+ if playerName != "" {
+ s = strings.ReplaceAll(s, "%p", playerName)
+ }
+ if flagValue != nil {
+ s = strings.ReplaceAll(s, "%v", fmt.Sprint(flagValue))
+ }
+ return s
+}