aboutsummaryrefslogtreecommitdiff
path: root/internal/game
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-25 04:46:40 -0400
committerhistoria <[not public]>2026-06-25 04:46:40 -0400
commit2725e2927a1595c7b100d942d1f14146252adeb7 (patch)
treee8bc2292634a2d686c751981ddb3097517cccc6a /internal/game
parent94a06fbbe682701ca4d4bd2a70cd74d8df29c932 (diff)
downloadthehouseoficarus-2725e2927a1595c7b100d942d1f14146252adeb7.tar.gz
feat: who, global chat, removed redundant YAML IDs
Diffstat (limited to 'internal/game')
-rw-r--r--internal/game/cmd_color.go1
-rw-r--r--internal/game/cmd_global.go36
-rw-r--r--internal/game/cmd_look.go88
-rw-r--r--internal/game/cmd_registry.go2
-rw-r--r--internal/game/cmd_verbs.go2
-rw-r--r--internal/game/cmd_who.go32
-rw-r--r--internal/game/core_course.go1
-rw-r--r--internal/game/sys_science.go1
-rw-r--r--internal/game/sys_technology.go1
-rw-r--r--internal/game/ui_help.go2
10 files changed, 121 insertions, 45 deletions
diff --git a/internal/game/cmd_color.go b/internal/game/cmd_color.go
index 51f8158..7bf073d 100644
--- a/internal/game/cmd_color.go
+++ b/internal/game/cmd_color.go
@@ -154,6 +154,7 @@ var colorCategoryOrder = []string{
"miss",
"error",
"say",
+ "global",
"dialog",
"broadcast",
"fire",
diff --git a/internal/game/cmd_global.go b/internal/game/cmd_global.go
new file mode 100644
index 0000000..d06a233
--- /dev/null
+++ b/internal/game/cmd_global.go
@@ -0,0 +1,36 @@
+package game
+
+import (
+ "fmt"
+ "strings"
+
+ "thehouseoficarus/internal/net"
+)
+
+func (g *Game) executeGlobal(sess *net.Session, args []string, rawInput string) {
+ if len(args) == 0 {
+ sess.WriteLine("Usage: global <message> — sends a message to everyone on the world.")
+ return
+ }
+
+ msgStart := strings.Index(strings.ToLower(rawInput), "global ") + 7
+ if msgStart >= 7 && msgStart < len(rawInput) {
+ g.doGlobal(sess, rawInput[msgStart:])
+ } else {
+ g.doGlobal(sess, strings.Join(args, " "))
+ }
+}
+
+func (g *Game) doGlobal(sess *net.Session, msg string) {
+ p := sess.Player
+
+ for _, other := range g.Hub.AllSessions() {
+ if other.Player == nil {
+ continue
+ }
+ other.WriteLine(fmt.Sprintf("\n%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_look.go b/internal/game/cmd_look.go
index 3f31a15..5cdc15d 100644
--- a/internal/game/cmd_look.go
+++ b/internal/game/cmd_look.go
@@ -540,26 +540,26 @@ func (g *Game) doLookTarget(sess *net.Session, input string) {
fmt.Sprintf("%s %s", g.colorize(sess, mobColor, best.Name), levelStr),
)
if best.IdleDescription != "" {
- sess.WriteLine(fmt.Sprintf(" %s", best.IdleDescription))
+ sess.WriteLine(best.IdleDescription)
}
sess.WriteLines(
"",
- fmt.Sprintf(" Attack: %d", best.Attack),
- fmt.Sprintf(" Strength: %d", best.Strength),
- fmt.Sprintf(" Defense: %d", best.Defense),
- fmt.Sprintf(" HP: %d/%d", best.HP, best.MaxHP),
+ fmt.Sprintf("Attack: %d", best.Attack),
+ fmt.Sprintf("Strength: %d", best.Strength),
+ fmt.Sprintf("Defense: %d", best.Defense),
+ fmt.Sprintf("HP: %d/%d", best.HP, best.MaxHP),
)
if best.StabDefense != 0 || best.SlashDefense != 0 || best.CrushDefense != 0 ||
best.ScienceDefense != 0 || best.RangedDefense != 0 {
sess.WriteLines(
"",
- " Defense bonuses:",
- fmt.Sprintf(" Stab: %+d Slash: %+d Crush: %+d", best.StabDefense, best.SlashDefense, best.CrushDefense),
- fmt.Sprintf(" Science: %+d Ranged: %+d", best.ScienceDefense, best.RangedDefense),
+ "Defense bonuses:",
+ fmt.Sprintf(" Stab: %+d Slash: %+d Crush: %+d", best.StabDefense, best.SlashDefense, best.CrushDefense),
+ fmt.Sprintf(" Science: %+d Ranged: %+d", best.ScienceDefense, best.RangedDefense),
)
}
if best.Weakness != "" {
- sess.WriteLine(fmt.Sprintf(" Weakness: %s", best.Weakness))
+ sess.WriteLine(fmt.Sprintf("Weakness: %s", best.Weakness))
}
return
}
@@ -612,12 +612,10 @@ func (g *Game) doLookTarget(sess *net.Session, input string) {
sess.WriteLine("")
if len(instances) > 1 {
sess.WriteLine(fmt.Sprintf("%d %ss:", len(instances), def.Name))
- } else {
- sess.WriteLine(def.Name)
}
if objDescText != "" && !strings.Contains(objDescText, "{quality}") {
- sess.WriteLine(fmt.Sprintf(" %s", objDescText))
+ sess.WriteLine(color.ExpandTags(g.colorMode(sess), objDescText))
}
if def.Safespot != nil {
@@ -635,9 +633,9 @@ func (g *Game) doLookTarget(sess *net.Session, input string) {
if def.Safespot.MaxBlockSize != "" {
blockInfo = fmt.Sprintf("up to %s mobs", def.Safespot.MaxBlockSize)
}
- sess.WriteLine(fmt.Sprintf(" Safespot: %s — blocks %s", levelStr, blockInfo))
+ sess.WriteLine(fmt.Sprintf("Safespot: %s — blocks %s", levelStr, blockInfo))
if len(realSt.SafespotOccupants) > 0 {
- sess.WriteLine(fmt.Sprintf(" Occupied by: %s", strings.Join(realSt.SafespotOccupants, ", ")))
+ sess.WriteLine(fmt.Sprintf("Occupied by: %s", strings.Join(realSt.SafespotOccupants, ", ")))
}
}
}
@@ -646,15 +644,15 @@ func (g *Game) doLookTarget(sess *net.Session, input string) {
for _, ist := range instances {
if ist.Depleted {
if len(instances) > 1 {
- sess.WriteLine(fmt.Sprintf(" %s %d: depleted, respawns in %d ticks.", def.Name, ist.Index+1, int(ist.DepleteTimer)))
+ sess.WriteLine(fmt.Sprintf("%s %d: depleted, respawns in %d ticks.", def.Name, ist.Index+1, int(ist.DepleteTimer)))
} else {
- sess.WriteLine(fmt.Sprintf(" Depleted, respawns in %d ticks.", int(ist.DepleteTimer)))
+ sess.WriteLine(fmt.Sprintf("Depleted, respawns in %d ticks.", int(ist.DepleteTimer)))
}
} else if ist.SharedMax > 0 && float64(ist.SharedTimer) < ist.SharedMax {
if len(instances) > 1 {
- sess.WriteLine(fmt.Sprintf(" %s %d: despawn timer %d/%.0f.", def.Name, ist.Index+1, ist.SharedTimer, ist.SharedMax))
+ sess.WriteLine(fmt.Sprintf("%s %d: despawn timer %d/%.0f.", def.Name, ist.Index+1, ist.SharedTimer, ist.SharedMax))
} else {
- sess.WriteLine(fmt.Sprintf(" Despawn timer: %d/%.0f ticks.", ist.SharedTimer, ist.SharedMax))
+ sess.WriteLine(fmt.Sprintf("Despawn timer: %d/%.0f ticks.", ist.SharedTimer, ist.SharedMax))
}
}
}
@@ -662,7 +660,7 @@ func (g *Game) doLookTarget(sess *net.Session, input string) {
for _, ist := range instances {
if ist.Quality > 0 && strings.Contains(def.Description, "{quality}") {
desc := strings.ReplaceAll(def.Description, "{quality}", fmt.Sprintf("%.0f", ist.Quality))
- sess.WriteLine(fmt.Sprintf(" %s", desc))
+ sess.WriteLine(color.ExpandTags(g.colorMode(sess), desc))
}
}
@@ -682,8 +680,8 @@ func (g *Game) doLookTarget(sess *net.Session, input string) {
sess.WriteLines(
"",
g.itemColorize(sess, def, def.Name),
- fmt.Sprintf(" %s", def.Description),
- fmt.Sprintf(" Value: %d credits", def.Value),
+ color.ExpandTags(g.colorMode(sess), def.Description),
+ fmt.Sprintf("Value: %d credits", def.Value),
)
g.showItemStats(sess, def)
return
@@ -701,11 +699,11 @@ func (g *Game) doLookTarget(sess *net.Session, input string) {
lines := []string{
"",
g.itemColorize(sess, def, def.Name),
- fmt.Sprintf(" %s", def.Description),
- fmt.Sprintf(" Value: %d credits", def.Value),
+ color.ExpandTags(g.colorMode(sess), def.Description),
+ fmt.Sprintf("Value: %d credits", def.Value),
}
if slot.MaxQuality > 0 {
- lines = append(lines, fmt.Sprintf(" Has %d units of butane left.", slot.Quality))
+ lines = append(lines, fmt.Sprintf("Has %d units of butane left.", slot.Quality))
}
sess.WriteLines(lines...)
g.showItemStats(sess, def)
@@ -735,18 +733,18 @@ func showPlayerInfo(g *Game, sess *net.Session, p *player.Player) {
sess.WriteLines(
"",
p.Name,
- fmt.Sprintf(" Combat Level: %s", levelStr),
- fmt.Sprintf(" HP: %d/%d", p.HP, p.MaxHP()),
+ fmt.Sprintf("Combat Level: %s", levelStr),
+ fmt.Sprintf("HP: %d/%d", p.HP, p.MaxHP()),
"",
)
for _, s := range player.AllSkills {
level := p.Level(s)
- sess.WriteLine(fmt.Sprintf(" %-12s Level: %d", s, level))
+ sess.WriteLine(fmt.Sprintf("%-12s Level: %d", s, level))
}
sess.WriteLine("")
- sess.WriteLine(" Equipment:")
+ sess.WriteLine("Equipment:")
for _, slot := range EquipSlots {
itemID, ok := p.Equipment[slot]
if !ok {
@@ -758,12 +756,12 @@ func showPlayerInfo(g *Game, sess *net.Session, p *player.Player) {
name = def.Name
itemDef = def
}
- sess.WriteLine(fmt.Sprintf(" %-12s %s", slot, g.itemColorize(sess, itemDef, name)))
+ sess.WriteLine(fmt.Sprintf(" %-12s %s", slot, g.itemColorize(sess, itemDef, name)))
}
if p.Description != "" {
sess.WriteLine("")
- sess.WriteLine(fmt.Sprintf(" %s", p.Description))
+ sess.WriteLine(p.Description)
}
}
@@ -916,40 +914,40 @@ func (g *Game) showItemStats(sess *net.Session, def *object.ItemDef) {
sess.WriteLine("")
if hasAttack || hasDefense {
- sess.WriteLine(" Attack bonuses: Defense bonuses:")
- sess.WriteLine(fmt.Sprintf(" Stab: %+4d Stab: %+4d", s.StabAttack, s.StabDefense))
- sess.WriteLine(fmt.Sprintf(" Slash: %+4d Slash: %+4d", s.SlashAttack, s.SlashDefense))
- sess.WriteLine(fmt.Sprintf(" Crush: %+4d Crush: %+4d", s.CrushAttack, s.CrushDefense))
- sess.WriteLine(fmt.Sprintf(" Science:%+4d Science:%+4d", s.ScienceAttack, s.ScienceDefense))
- sess.WriteLine(fmt.Sprintf(" Ranged: %+4d Ranged: %+4d", s.RangedAttack, s.RangedDefense))
+ sess.WriteLine("Attack bonuses: Defense bonuses:")
+ sess.WriteLine(fmt.Sprintf(" Stab: %+4d Stab: %+4d", s.StabAttack, s.StabDefense))
+ sess.WriteLine(fmt.Sprintf(" Slash: %+4d Slash: %+4d", s.SlashAttack, s.SlashDefense))
+ sess.WriteLine(fmt.Sprintf(" Crush: %+4d Crush: %+4d", s.CrushAttack, s.CrushDefense))
+ sess.WriteLine(fmt.Sprintf(" Science:%+4d Science:%+4d", s.ScienceAttack, s.ScienceDefense))
+ sess.WriteLine(fmt.Sprintf(" Ranged: %+4d Ranged: %+4d", s.RangedAttack, s.RangedDefense))
}
if hasOther {
sess.WriteLine("")
- sess.WriteLine(" Other bonuses:")
+ sess.WriteLine("Other bonuses:")
if s.StrengthBonus != 0 {
- sess.WriteLine(fmt.Sprintf(" Melee strength: %+d", s.StrengthBonus))
+ sess.WriteLine(fmt.Sprintf(" Melee strength: %+d", s.StrengthBonus))
}
if s.RangedStrength != 0 {
- sess.WriteLine(fmt.Sprintf(" Ranged strength: %+d", s.RangedStrength))
+ sess.WriteLine(fmt.Sprintf(" Ranged strength: %+d", s.RangedStrength))
}
if s.ScienceDamage != 0 {
- sess.WriteLine(fmt.Sprintf(" Science damage: %+d", s.ScienceDamage))
+ sess.WriteLine(fmt.Sprintf(" Science damage: %+d", s.ScienceDamage))
}
if s.TechnologyBonus != 0 {
- sess.WriteLine(fmt.Sprintf(" Technology: %+d", s.TechnologyBonus))
+ sess.WriteLine(fmt.Sprintf(" Technology: %+d", s.TechnologyBonus))
}
}
if def.AttackType != "" {
- sess.WriteLine(fmt.Sprintf(" Attack type: %s", def.AttackType))
+ sess.WriteLine(fmt.Sprintf("Attack type: %s", def.AttackType))
}
if def.Speed > 0 {
- sess.WriteLine(fmt.Sprintf(" Speed: %.0f", def.Speed))
+ sess.WriteLine(fmt.Sprintf("Speed: %.0f", def.Speed))
}
if len(def.Requirements) > 0 {
sess.WriteLine("")
- sess.WriteLine(" Requirements:")
+ sess.WriteLine("Requirements:")
for skill, level := range def.Requirements {
- sess.WriteLine(fmt.Sprintf(" %s: %d", skill, level))
+ sess.WriteLine(fmt.Sprintf(" %s: %d", skill, level))
}
}
}
diff --git a/internal/game/cmd_registry.go b/internal/game/cmd_registry.go
index be1c276..bc910ed 100644
--- a/internal/game/cmd_registry.go
+++ b/internal/game/cmd_registry.go
@@ -39,6 +39,8 @@ var commandRegistry = map[string]commandDef{
"down": {(*Game).executeMove, ClassActive},
"d": {(*Game).executeMove, ClassActive},
"say": {(*Game).executeSay, ClassInstant},
+ "global": {(*Game).executeGlobal, ClassInstant},
+ "who": {(*Game).executeWho, ClassInstant},
"sc": {(*Game).executeScore, ClassInstant},
"score": {(*Game).executeScore, ClassInstant},
"skills": {(*Game).executeSkills, ClassInstant},
diff --git a/internal/game/cmd_verbs.go b/internal/game/cmd_verbs.go
index 9ee1607..f76ee6c 100644
--- a/internal/game/cmd_verbs.go
+++ b/internal/game/cmd_verbs.go
@@ -11,6 +11,8 @@ import (
var alwaysAvailable = []string{
"look", "l",
"say",
+ "global",
+ "who",
"exits",
"map",
"walk",
diff --git a/internal/game/cmd_who.go b/internal/game/cmd_who.go
new file mode 100644
index 0000000..87c8a9c
--- /dev/null
+++ b/internal/game/cmd_who.go
@@ -0,0 +1,32 @@
+package game
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+)
+
+func (g *Game) executeWho(sess *net.Session, args []string, rawInput string) {
+ myP := sess.Player
+
+ var players []*player.Player
+ for _, other := range g.Hub.AllSessions() {
+ if other.Player == nil {
+ continue
+ }
+ players = append(players, other.Player)
+ }
+ sort.Slice(players, func(i, j int) bool {
+ return strings.ToLower(players[i].Name) < strings.ToLower(players[j].Name)
+ })
+
+ sess.WriteLine(fmt.Sprintf("\nPlayers online (%d):", len(players)))
+ for _, p := range players {
+ lvl := p.CombatLevel()
+ levelStr := g.levelColorize(sess, myP.CombatLevel(), lvl, fmt.Sprintf("(level %d)", lvl))
+ sess.WriteLine(fmt.Sprintf(" %s %s", p.Name, levelStr))
+ }
+}
diff --git a/internal/game/core_course.go b/internal/game/core_course.go
index 908466d..7d7fe08 100644
--- a/internal/game/core_course.go
+++ b/internal/game/core_course.go
@@ -96,6 +96,7 @@ func (cs *CourseStore) loadAllLocked() {
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil
}
+ cfg.ID = id
cs.courses[cfg.ID] = &cfg
totalObstacles := len(cfg.Obstacles)
diff --git a/internal/game/sys_science.go b/internal/game/sys_science.go
index 1129fee..517e051 100644
--- a/internal/game/sys_science.go
+++ b/internal/game/sys_science.go
@@ -49,6 +49,7 @@ func (g *Game) LoadMods() error {
if err := yaml.Unmarshal(data, &m); err != nil {
return nil
}
+ m.ID = id
AllMods = append(AllMods, &m)
return nil
})
diff --git a/internal/game/sys_technology.go b/internal/game/sys_technology.go
index 6afc757..fa0301f 100644
--- a/internal/game/sys_technology.go
+++ b/internal/game/sys_technology.go
@@ -50,6 +50,7 @@ func (g *Game) LoadTechs() error {
if err := yaml.Unmarshal(data, &t); err != nil {
return nil
}
+ t.ID = id
AllTechs = append(AllTechs, &t)
return nil
})
diff --git a/internal/game/ui_help.go b/internal/game/ui_help.go
index d482fce..28f4371 100644
--- a/internal/game/ui_help.go
+++ b/internal/game/ui_help.go
@@ -45,6 +45,7 @@ var commandList = []cmdEntry{
{"fish", "Active", "Fish at fishing spots (Fishing)"},
{"fletch", "Free", "Fletch logs into bows (Fletching)"},
{"get / take / pick", "Active", "Pick up items from the ground"},
+ {"global", "Instant", "Chat with everyone on the server"},
{"help", "Instant", "Show help topics"},
{"id / identify", "Active", "Identify herbs (Pharmacy)"},
{"inventory / i / inv", "Instant", "Show your inventory"},
@@ -78,6 +79,7 @@ var commandList = []cmdEntry{
{"use", "Active", "Use an object (crafting)"},
{"walk", "Active", "Pathfind to a room or multi-step walk"},
{"wear / wield", "Free", "Equip items"},
+ {"who", "Instant", "List everyone online and their combat level"},
}
func LoadHelp(dataDir string) ([]HelpDef, error) {