aboutsummaryrefslogtreecommitdiff
path: root/internal/game
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-27 02:29:55 -0400
committerhistoria <[not public]>2026-06-27 02:29:55 -0400
commit6f3d21d6e7f01155ce0c461857a053b43d329392 (patch)
tree51e96b62deb5f99a3d70c3a15ab94c520a1ee8e8 /internal/game
parent3102525d97aa6f1ad152c74b2ccfbd4b3a9b83f5 (diff)
downloadthehouseoficarus-6f3d21d6e7f01155ce0c461857a053b43d329392.tar.gz
feat: recall/home cmd, line break option
Diffstat (limited to 'internal/game')
-rw-r--r--internal/game/act.go2
-rw-r--r--internal/game/act_state.go2
-rw-r--r--internal/game/act_trigger_module.go61
-rw-r--r--internal/game/cmd_home.go7
-rw-r--r--internal/game/cmd_mods.go11
-rw-r--r--internal/game/cmd_option.go1
-rw-r--r--internal/game/cmd_registry.go2
-rw-r--r--internal/game/cmd_trigger.go22
-rw-r--r--internal/game/cmd_trigger_combat.go3
-rw-r--r--internal/game/cmd_trigger_transport.go9
-rw-r--r--internal/game/combat_attack.go5
-rw-r--r--internal/game/combat_mob.go7
-rw-r--r--internal/game/game.go4
-rw-r--r--internal/game/sys_science.go35
-rw-r--r--internal/game/tick.go3
15 files changed, 150 insertions, 24 deletions
diff --git a/internal/game/act.go b/internal/game/act.go
index 31d8a33..78f298d 100644
--- a/internal/game/act.go
+++ b/internal/game/act.go
@@ -232,6 +232,8 @@ func (g *Game) AdvanceActions() {
g.advanceObstacle(sess, p)
case behavior.TypeTalk:
g.advanceTalk(sess, p)
+ case behavior.TypeTriggerModule:
+ g.advanceTriggerModule(sess, p)
default:
if productionActionTypes[string(p.Action.Type)] {
g.advanceProduction(sess, p)
diff --git a/internal/game/act_state.go b/internal/game/act_state.go
index a219875..051390f 100644
--- a/internal/game/act_state.go
+++ b/internal/game/act_state.go
@@ -81,6 +81,8 @@ func (g *Game) playerActionDisplay(p *player.Player) string {
return "mixing some " + a.TargetName
case behavior.TypeConstruct:
return "constructing some " + a.TargetName
+ case behavior.TypeTriggerModule:
+ return "triggering " + a.TargetName
}
}
diff --git a/internal/game/act_trigger_module.go b/internal/game/act_trigger_module.go
new file mode 100644
index 0000000..467cb83
--- /dev/null
+++ b/internal/game/act_trigger_module.go
@@ -0,0 +1,61 @@
+package game
+
+import (
+ "fmt"
+
+ "thehouseoficarus/internal/behavior"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+)
+
+func (g *Game) startTriggerModule(sess *net.Session, p *player.Player, mod *ModDef) {
+ if p.Action != nil {
+ g.cancelAction(p)
+ }
+ p.Action = &behavior.Action{
+ Type: behavior.TypeTriggerModule,
+ TargetID: mod.ID,
+ TargetName: mod.Name,
+ WaitLeft: 0,
+ Data: &behavior.TriggerModuleData{
+ ModID: mod.ID,
+ Steps: mod.Sequence,
+ StepIndex: 0,
+ },
+ }
+ g.writePrompt(sess)
+}
+
+func (g *Game) advanceTriggerModule(sess *net.Session, p *player.Player) {
+ data := p.Action.Data.(*behavior.TriggerModuleData)
+
+ mod := GetMod(data.ModID)
+ if mod == nil {
+ g.cancelAction(p)
+ sess.WriteLine("The module has vanished.")
+ return
+ }
+
+ if data.StepIndex < len(data.Steps) {
+ step := data.Steps[data.StepIndex]
+ sess.WriteLine(step.Message)
+ data.StepIndex++
+ p.Action.WaitLeft = step.Delay
+ } else {
+ g.triggerModReward(sess, p, mod, 1.0)
+ if g.Combat.Get(p.Name) != nil {
+ g.Combat.Leave(p.Name)
+ }
+ switch mod.Category {
+ case ModTransport:
+ g.executeTransportEffect(sess, p, mod)
+ if mod.ID == "transport_home" {
+ p.HomeTransportCooldown = 3000
+ }
+ default:
+ sess.WriteLine(fmt.Sprintf("%s completes.", mod.Name))
+ }
+ g.cancelAction(p)
+ g.writePrompt(sess)
+ }
+}
diff --git a/internal/game/cmd_home.go b/internal/game/cmd_home.go
new file mode 100644
index 0000000..bc432c6
--- /dev/null
+++ b/internal/game/cmd_home.go
@@ -0,0 +1,7 @@
+package game
+
+import "thehouseoficarus/internal/net"
+
+func (g *Game) executeHome(sess *net.Session, args []string, rawInput string) {
+ g.doTrigger(sess, "transport_home")
+}
diff --git a/internal/game/cmd_mods.go b/internal/game/cmd_mods.go
index 54c8067..2fe9ae5 100644
--- a/internal/game/cmd_mods.go
+++ b/internal/game/cmd_mods.go
@@ -67,8 +67,15 @@ func (g *Game) doMods(sess *net.Session, showAll bool) {
levelStr := fmt.Sprintf("Lv %d", m.Level)
costStr := g.junkCostString(p, m)
xpStr := fmt.Sprintf("%d XP", m.BaseXP)
-
- if unlocked {
+ if m.ID == "transport_home" && p.HomeTransportCooldown > 0 {
+ nameStr := fmt.Sprintf("%s (%d ticks)", m.Name, p.HomeTransportCooldown)
+ t.Rows = append(t.Rows, []string{
+ g.colorize(sess, "dim", nameStr),
+ g.colorize(sess, "dim", levelStr),
+ g.colorize(sess, "dim", costStr),
+ g.colorize(sess, "dim", xpStr),
+ })
+ } else if unlocked {
t.Rows = append(t.Rows, []string{
color.Render(mode, color.Parse("75"), m.Name),
color.Render(mode, color.Parse("245"), levelStr),
diff --git a/internal/game/cmd_option.go b/internal/game/cmd_option.go
index d8ef0a4..836a150 100644
--- a/internal/game/cmd_option.go
+++ b/internal/game/cmd_option.go
@@ -16,7 +16,6 @@ func (g *Game) doOption(sess *net.Session, input string) {
mode := g.colorMode(sess)
if input == "" {
- sess.WriteLine("")
table := &ui.Table{
Columns: []string{
color.Render(mode, color.Parse("75"), "Option"),
diff --git a/internal/game/cmd_registry.go b/internal/game/cmd_registry.go
index 7ac9d0b..2cf0de1 100644
--- a/internal/game/cmd_registry.go
+++ b/internal/game/cmd_registry.go
@@ -93,6 +93,8 @@ var commandRegistry = map[string]commandDef{
"module": {(*Game).executeMods, ClassInstant},
"sneak": {(*Game).executeSneak, ClassInstant},
"trigger": {(*Game).executeTrigger, ClassActive},
+ "home": {(*Game).executeHome, ClassInstant},
+ "recall": {(*Game).executeHome, ClassInstant},
"talk": {(*Game).executeTalk, ClassActive},
"speak": {(*Game).executeTalk, ClassActive},
"ask": {(*Game).executeTalk, ClassActive},
diff --git a/internal/game/cmd_trigger.go b/internal/game/cmd_trigger.go
index 34e438a..f74efec 100644
--- a/internal/game/cmd_trigger.go
+++ b/internal/game/cmd_trigger.go
@@ -4,6 +4,7 @@ import (
"fmt"
"strings"
+ "thehouseoficarus/internal/behavior"
"thehouseoficarus/internal/net"
"thehouseoficarus/internal/player"
)
@@ -37,6 +38,24 @@ func (g *Game) doTrigger(sess *net.Session, input string) {
return
}
+ if mod.Category == ModTransport {
+ room, err := g.World.LoadRoom(p.RoomID)
+ if err == nil && room.BlockTransport {
+ sess.WriteLine("You can't imagine transport would pick you up here")
+ return
+ }
+ }
+
+ if mod.ID == "transport_home" && p.HomeTransportCooldown > 0 {
+ sess.WriteLine(fmt.Sprintf("The transport service won't shuttle you that frequently! Wait %d ticks!", p.HomeTransportCooldown))
+ return
+ }
+
+ if len(mod.Sequence) > 0 {
+ g.startTriggerModule(sess, p, mod)
+ return
+ }
+
switch mod.Category {
case ModCombat:
g.triggerCombatMod(sess, p, mod, targetArg)
@@ -65,6 +84,9 @@ func (g *Game) parseTriggerArgs(input string, sciLevel int) (*ModDef, string) {
func (g *Game) triggerCombatMod(sess *net.Session, p *player.Player, mod *ModDef, targetArg string) {
if cs := g.Combat.Get(p.Name); cs != nil {
+ if p.Action != nil && p.Action.Type == behavior.TypeTriggerModule {
+ g.cancelAction(p)
+ }
p.QueuedTrigger = mod.ID
sess.WriteLine(fmt.Sprintf("You queue %s for your next attack.", mod.Name))
return
diff --git a/internal/game/cmd_trigger_combat.go b/internal/game/cmd_trigger_combat.go
index 990ba86..cce9781 100644
--- a/internal/game/cmd_trigger_combat.go
+++ b/internal/game/cmd_trigger_combat.go
@@ -48,9 +48,6 @@ func (g *Game) scienceAttack(sess *net.Session, p *player.Player, mob *world.Mob
sciXP := mod.BaseXP
hpXP := mod.BaseXP / 3
- if hpXP < 1 {
- hpXP = 1
- }
var gains []xpGain
g.awardSkillXP(sess, p, player.Science, sciXP)
diff --git a/internal/game/cmd_trigger_transport.go b/internal/game/cmd_trigger_transport.go
index ba86ebe..e2053b0 100644
--- a/internal/game/cmd_trigger_transport.go
+++ b/internal/game/cmd_trigger_transport.go
@@ -9,7 +9,7 @@ import (
func (g *Game) triggerTransport(sess *net.Session, p *player.Player, mod *ModDef) {
if g.Combat.Get(p.Name) != nil {
- sess.WriteLine("You can't teleport during combat!")
+ sess.WriteLine("You can't transport during combat!")
return
}
@@ -18,11 +18,14 @@ func (g *Game) triggerTransport(sess *net.Session, p *player.Player, mod *ModDef
}
g.triggerModReward(sess, p, mod, 1.0)
+ g.executeTransportEffect(sess, p, mod)
+}
+func (g *Game) executeTransportEffect(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("%s teleports away.", p.Name))
+ other.WriteLine(fmt.Sprintf("%s requests a transport shuttle and departs.", p.Name))
}
}
}
@@ -41,7 +44,7 @@ func (g *Game) triggerTransport(sess *net.Session, p *player.Player, mod *ModDef
if room != nil {
destName = room.Name
}
- sess.WriteLine(fmt.Sprintf("You materialize at %s.", destName))
+ sess.WriteLine(fmt.Sprintf("You arrive at %s.", destName))
g.AccountStore.SaveCharacter(p)
g.doLook(sess)
}
diff --git a/internal/game/combat_attack.go b/internal/game/combat_attack.go
index 36a496b..bae210b 100644
--- a/internal/game/combat_attack.go
+++ b/internal/game/combat_attack.go
@@ -7,6 +7,7 @@ import (
"strconv"
"strings"
+ "thehouseoficarus/internal/behavior"
"thehouseoficarus/internal/color"
"thehouseoficarus/internal/combat"
"thehouseoficarus/internal/engine"
@@ -198,6 +199,10 @@ func (g *Game) startCombat(sess *net.Session, p *player.Player, mob *world.MobIn
return false
}
+ if p.Action != nil && p.Action.Type == behavior.TypeTriggerModule {
+ return true
+ }
+
p.AttackTimer++
if float64(p.AttackTimer) >= g.playerWeaponSpeed(p) {
p.AttackTimer = 0
diff --git a/internal/game/combat_mob.go b/internal/game/combat_mob.go
index 2abaf81..7096b66 100644
--- a/internal/game/combat_mob.go
+++ b/internal/game/combat_mob.go
@@ -115,6 +115,12 @@ func (g *Game) applyMobHit(sess *net.Session, p *player.Player, mob *world.MobIn
p.ClearMoveState()
sess.WriteLine("Can't escape!")
}
+
+ if p.Action != nil && p.Action.Type == behavior.TypeTriggerModule {
+ g.cancelAction(p)
+ p.AttackTimer = 0
+ sess.WriteLine("Your concentration is broken!")
+ }
}
func (g *Game) applyMobMiss(sess *net.Session, mob *world.MobInstance) {
@@ -250,6 +256,7 @@ func (g *Game) killPlayer(sess *net.Session, p *player.Player, mob *world.MobIns
p.DeactivateAllTechs()
p.Stats.RecordDeath()
sess.WriteLine(g.colorize(sess, "death", "Oh dear, you are dead!"))
+ p.Action = nil
p.ClearMoveState()
p.HazardTimer = 0
if ss, ok := g.safespot.Get(p.Name); ok {
diff --git a/internal/game/game.go b/internal/game/game.go
index 3cfd811..22dd74d 100644
--- a/internal/game/game.go
+++ b/internal/game/game.go
@@ -190,6 +190,10 @@ func (g *Game) handleGameCommand(sess *net.Session, input string) {
return
}
+ if sess.Player.OptionString("prompt_break") == "off" {
+ sess.ClearPrompt()
+ }
+
if sess.Account != nil && sess.Account.Aliases != nil {
firstSpace := strings.Index(input, " ")
var firstWord, rest string
diff --git a/internal/game/sys_science.go b/internal/game/sys_science.go
index 0ee446b..b04072f 100644
--- a/internal/game/sys_science.go
+++ b/internal/game/sys_science.go
@@ -25,15 +25,16 @@ const (
)
type ModDef struct {
- ID string `yaml:"id"`
- Name string `yaml:"name"`
- Level int `yaml:"level"`
- MaxHit int `yaml:"max_hit"`
- BaseXP int `yaml:"base_xp"`
- JunkCost map[string]int `yaml:"junk_cost"`
- Category ModCategory `yaml:"category"`
- Element string `yaml:"element"`
- Destination int `yaml:"destination"`
+ ID string `yaml:"id"`
+ Name string `yaml:"name"`
+ Level int `yaml:"level"`
+ MaxHit int `yaml:"max_hit"`
+ BaseXP int `yaml:"base_xp"`
+ JunkCost map[string]int `yaml:"junk_cost"`
+ Category ModCategory `yaml:"category"`
+ Element string `yaml:"element"`
+ Destination int `yaml:"destination"`
+ Sequence []behavior.ModTriggerStep `yaml:"sequence"`
}
var AllMods []*ModDef
@@ -241,9 +242,6 @@ func (g *Game) triggerModReward(sess *net.Session, p *player.Player, mod *ModDef
g.consumeJunkCost(p, mod)
sciXP := int(float64(mod.BaseXP) * multiplier)
- if sciXP < 1 {
- sciXP = 1
- }
g.awardSkillXP(sess, p, player.Science, sciXP)
for _, gain := range extraGains {
@@ -253,11 +251,18 @@ func (g *Game) triggerModReward(sess *net.Session, p *player.Player, mod *ModDef
g.AccountStore.SaveCharacter(p)
if p.OptionBool("xp_drops") {
- parts := []string{fmt.Sprintf("+%dxp %s", sciXP, player.SkillAbbr[player.Science])}
+ var parts []string
+ if sciXP > 0 {
+ parts = append(parts, fmt.Sprintf("+%dxp %s", sciXP, player.SkillAbbr[player.Science]))
+ }
for _, gain := range extraGains {
- parts = append(parts, fmt.Sprintf("+%dxp %s", gain.XP, player.SkillAbbr[player.SkillName(gain.Skill)]))
+ if gain.XP > 0 {
+ parts = append(parts, fmt.Sprintf("+%dxp %s", gain.XP, player.SkillAbbr[player.SkillName(gain.Skill)]))
+ }
+ }
+ if len(parts) > 0 {
+ sess.WriteLine(g.colorize(sess, "xp", "("+strings.Join(parts, ", ")+")"))
}
- sess.WriteLine(g.colorize(sess, "xp", "("+strings.Join(parts, ", ")+")"))
}
return sciXP
}
diff --git a/internal/game/tick.go b/internal/game/tick.go
index f8d6f04..7f593a4 100644
--- a/internal/game/tick.go
+++ b/internal/game/tick.go
@@ -288,6 +288,9 @@ func (g *Game) ConsumeTick() {
if p.ConsumeCooldown > 0 {
p.ConsumeCooldown--
}
+ if p.HomeTransportCooldown > 0 {
+ p.HomeTransportCooldown--
+ }
}
}