aboutsummaryrefslogtreecommitdiff
path: root/internal/game
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-21 02:26:57 -0400
committerhistoria <[not public]>2026-06-21 02:26:57 -0400
commite4bc2de6e6aa507044a6a75b4c1954974539a857 (patch)
tree608f22548bf2b6a1a4b02bedad05ec7f3a3aa3f2 /internal/game
parentc36c1dc0c65e65b4d6ee53df6bbcc3a860eb6e4b (diff)
downloadthehouseoficarus-e4bc2de6e6aa507044a6a75b4c1954974539a857.tar.gz
feat: combat health bar. color improvements.
Diffstat (limited to 'internal/game')
-rw-r--r--internal/game/action_agility.go4
-rw-r--r--internal/game/action_finishing_blow.go2
-rw-r--r--internal/game/cmd_attack.go47
-rw-r--r--internal/game/cmd_color.go10
-rw-r--r--internal/game/cmd_look.go14
-rw-r--r--internal/game/cmd_score.go2
-rw-r--r--internal/game/cmd_trigger_combat.go12
-rw-r--r--internal/game/core_production.go2
-rw-r--r--internal/game/sys_combat.go2
-rw-r--r--internal/game/tick.go8
-rw-r--r--internal/game/ui_combat.go59
-rw-r--r--internal/game/ui_help.go2
12 files changed, 116 insertions, 48 deletions
diff --git a/internal/game/action_agility.go b/internal/game/action_agility.go
index 43c9cfd..f916a8d 100644
--- a/internal/game/action_agility.go
+++ b/internal/game/action_agility.go
@@ -140,13 +140,13 @@ func (g *Game) obstacleFail(sess *net.Session, p *player.Player, data map[string
damage = failMin + rand.Intn(failMax-failMin+1)
}
- sess.WriteLine(g.colorize(sess, "damage", "You slip and fall!"))
+ sess.WriteLine(g.colorize(sess, "damage_taken", "You slip and fall!"))
p.HP -= damage
if p.HP < 1 {
p.HP = 1
}
- sess.WriteLine(g.colorize(sess, "damage",
+ sess.WriteLine(g.colorize(sess, "damage_taken",
fmt.Sprintf("You take %d damage. HP: %d/%d", damage, p.HP, p.MaxHP())))
g.AccountStore.SaveCharacter(p)
diff --git a/internal/game/action_finishing_blow.go b/internal/game/action_finishing_blow.go
index 674c2ef..070559d 100644
--- a/internal/game/action_finishing_blow.go
+++ b/internal/game/action_finishing_blow.go
@@ -72,7 +72,7 @@ func (g *Game) doFinishingBlow(sess *net.Session, p *player.Player, mob *world.M
if consumed {
p.RemoveItem(mob.FinishingBlow, 1)
}
- sess.WriteLine(fmt.Sprintf("\nYou use the %s on %s!", g.itemColorize(sess, fbDef, fbDef.Name), g.colorize(sess, "mob_name", mobDisplayName(mob, true))))
+ sess.WriteLine(fmt.Sprintf("\nYou use the %s on %s!", g.itemColorize(sess, fbDef, fbDef.Name), g.colorize(sess, "mob", mobDisplayName(mob, true))))
mob.HP = 0
g.endCombat(sess, p, mob)
}
diff --git a/internal/game/cmd_attack.go b/internal/game/cmd_attack.go
index 89cde46..1ede21f 100644
--- a/internal/game/cmd_attack.go
+++ b/internal/game/cmd_attack.go
@@ -7,6 +7,7 @@ import (
"strings"
"thehouseoficarus/internal/action"
+ "thehouseoficarus/internal/color"
"thehouseoficarus/internal/combat"
"thehouseoficarus/internal/engine"
"thehouseoficarus/internal/net"
@@ -149,7 +150,7 @@ func (g *Game) startCombat(sess *net.Session, p *player.Player, mob *world.MobIn
if len(styleParts) > 0 {
styleStr = " Style: " + string(p.AttackStyle) + " (" + strings.Join(styleParts, ", ") + ")"
}
- sess.WriteLine(fmt.Sprintf("\nYou attack %s!%s", g.colorize(sess, "mob_name", mobDisplayName(mob, true)), styleStr))
+ sess.WriteLine(fmt.Sprintf("\nYou attack %s!%s", g.colorize(sess, "mob", mobDisplayName(mob, true)), styleStr))
} else {
mod := GetMod(p.AutotriggerMod)
modName := p.AutotriggerMod
@@ -157,7 +158,7 @@ func (g *Game) startCombat(sess *net.Session, p *player.Player, mob *world.MobIn
modName = mod.Name
}
sess.WriteLine(fmt.Sprintf("\nYou attack %s with %s!",
- g.colorize(sess, "mob_name", mobDisplayName(mob, true)),
+ g.colorize(sess, "mob", mobDisplayName(mob, true)),
g.colorize(sess, "science_mod", modName)))
}
p.ActionState = &ActionState{Type: ActionCombating, TargetName: mob.Name}
@@ -352,7 +353,7 @@ func (g *Game) applyPlayerHit(sess *net.Session, p *player.Player, mob *world.Mo
fbName = fbDef.Name
}
sess.WriteLine(g.colorize(sess, "warning",
- fmt.Sprintf(" %s resists death! Use %s on it to finish it off.",
+ fmt.Sprintf("%s resists death! Use %s on it to finish it off.",
mobDisplayName(mob, false), fbName)))
}
@@ -373,13 +374,17 @@ func (g *Game) applyPlayerHit(sess *net.Session, p *player.Player, mob *world.Mo
if !mob.Unique {
attacker = "The " + mob.Name
}
- w := len(fmt.Sprintf(" You hit %s for %d damage.", mobName, dmg))
- if w2 := len(fmt.Sprintf(" %s hits you for %d damage.", attacker, dmg)); w2 > w {
+ w := len(fmt.Sprintf("You hit %s for %d damage.", mobName, 999))
+ if w2 := len(fmt.Sprintf("%s hits you for %d damage.", attacker, 999)); w2 > w {
w = w2
}
- prefix := fmt.Sprintf(" You hit %s for %s damage.", g.colorize(sess, "mob_name", mobName), g.colorize(sess, "damage", fmt.Sprint(dmg)))
- hpPart := fmt.Sprintf("[%s/%dhp]", g.colorize(sess, "enemy_hp", fmt.Sprint(mob.HP)), mob.MaxHP)
- line := fmt.Sprintf("%-*s %s", w, prefix, hpPart)
+ prefix := fmt.Sprintf("You hit %s for %s damage.", g.colorize(sess, "mob", mobName), g.colorize(sess, "damage_dealt", fmt.Sprint(dmg)))
+ visLen := color.VisibleLen(prefix)
+ if visLen < w {
+ prefix += strings.Repeat(" ", w-visLen+1)
+ }
+ hpSuffix := fmt.Sprintf("%s [%2d/%d]", g.hpBar(sess, mob.HP, mob.MaxHP), mob.HP, mob.MaxHP)
+ line := prefix + hpSuffix
if p.OptionBool("xp_drops") && len(gains) > 0 {
var parts []string
for _, gain := range gains {
@@ -396,7 +401,7 @@ func (g *Game) applyPlayerHit(sess *net.Session, p *player.Player, mob *world.Mo
}
func (g *Game) applyPlayerMiss(sess *net.Session, p *player.Player, mob *world.MobInstance, weaponType object.WeaponType) {
- sess.WriteLine(g.colorize(sess, "miss", fmt.Sprintf(" You miss %s.", mobDisplayName(mob, true))))
+ sess.WriteLine(g.colorize(sess, "miss", fmt.Sprintf("You miss %s.", mobDisplayName(mob, true))))
if weaponType == object.WeaponRanged {
p.AmmoQty--
@@ -487,13 +492,17 @@ func (g *Game) applyMobHit(sess *net.Session, p *player.Player, mob *world.MobIn
attacker = "The " + mob.Name
}
mobName := mobDisplayName(mob, true)
- w := len(fmt.Sprintf(" %s hits you for %d damage.", attacker, dmg))
- if w2 := len(fmt.Sprintf(" You hit %s for %d damage.", mobName, dmg)); w2 > w {
+ w := len(fmt.Sprintf("%s hits you for %d damage.", attacker, 999))
+ if w2 := len(fmt.Sprintf("You hit %s for %d damage.", mobName, 999)); w2 > w {
w = w2
}
- prefix := fmt.Sprintf(" %s hits you for %s damage.", g.colorize(sess, "mob_name", attacker), g.colorize(sess, "damage", fmt.Sprint(dmg)))
- hpPart := fmt.Sprintf("[%s/%dhp]", g.colorize(sess, "character_hp", fmt.Sprint(p.HP)), p.MaxHP())
- sess.WriteLine(fmt.Sprintf("%-*s %s", w, prefix, hpPart))
+ prefix := fmt.Sprintf("%s hits you for %s damage.", g.colorize(sess, "mob", attacker), g.colorize(sess, "damage_taken", fmt.Sprint(dmg)))
+ visLen := color.VisibleLen(prefix)
+ if visLen < w {
+ prefix += strings.Repeat(" ", w-visLen+1)
+ }
+ hpSuffix := fmt.Sprintf("%s [%2d/%d]", g.hpBar(sess, p.HP, p.MaxHP()), p.HP, p.MaxHP())
+ sess.WriteLine(prefix + hpSuffix)
if p.MoveTicks > 0 {
p.ClearMoveState()
@@ -507,7 +516,7 @@ func (g *Game) applyMobMiss(sess *net.Session, mob *world.MobInstance) {
if !mob.Unique {
attacker = "The " + mob.Name
}
- sess.WriteLine(g.colorize(sess, "miss", fmt.Sprintf(" %s misses you.", attacker)))
+ sess.WriteLine(g.colorize(sess, "miss", fmt.Sprintf("%s misses you.", attacker)))
}
func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInstance) {
@@ -572,7 +581,7 @@ func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInst
dropper = "The " + mob.Name
}
coloredName := g.itemColorize(sess, def, name)
- sess.WriteLine(fmt.Sprintf(" %s %s", g.colorize(sess, "drop_message", dropper+" drops:"), coloredName))
+ sess.WriteLine(fmt.Sprintf("%s %s", g.colorize(sess, "drop_message", dropper+" drops:"), coloredName))
}
if len(mob.Drops.Loot) > 0 {
@@ -594,9 +603,9 @@ func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInst
}
coloredName := g.itemColorize(sess, def, name)
if qty > 1 {
- sess.WriteLine(fmt.Sprintf(" %s %d x %s", g.colorize(sess, "drop_message", dropper+" drops:"), qty, coloredName))
+ sess.WriteLine(fmt.Sprintf("%s %d x %s", g.colorize(sess, "drop_message", dropper+" drops:"), qty, coloredName))
} else {
- sess.WriteLine(fmt.Sprintf(" %s %s", g.colorize(sess, "drop_message", dropper+" drops:"), coloredName))
+ sess.WriteLine(fmt.Sprintf("%s %s", g.colorize(sess, "drop_message", dropper+" drops:"), coloredName))
}
}
}
@@ -684,7 +693,7 @@ func (g *Game) respawnMob(instanceID string) {
if p := sess.Player; p != nil && p.OptionBool("mob_spawn") {
mobLvl := mobCombatLevel(inst)
levelStr := g.levelColorize(sess, p.CombatLevel(), mobLvl, fmt.Sprintf("(level %d)", mobLvl))
- sess.WriteLine(fmt.Sprintf("\n%s %s spawns in the area.", g.colorize(sess, "mob_name", mobDisplayName(inst, false)), levelStr))
+ sess.WriteLine(fmt.Sprintf("\n%s %s spawns in the area.", g.colorize(sess, "mob", mobDisplayName(inst, false)), levelStr))
}
}
}
diff --git a/internal/game/cmd_color.go b/internal/game/cmd_color.go
index 257c965..fe2fe62 100644
--- a/internal/game/cmd_color.go
+++ b/internal/game/cmd_color.go
@@ -139,12 +139,12 @@ var colorCategoryOrder = []string{
"direction",
"exit_direction",
"exit_name",
- "mob_name",
- "friendly_npc",
- "hostile_npc",
+ "protected_mob",
+ "mob",
+ "damage_dealt",
+ "damage_taken",
"damage",
- "enemy_hp",
- "character_hp",
+
"xp",
"level_up",
"item",
diff --git a/internal/game/cmd_look.go b/internal/game/cmd_look.go
index 505f7a6..936a6b0 100644
--- a/internal/game/cmd_look.go
+++ b/internal/game/cmd_look.go
@@ -81,7 +81,7 @@ func (g *Game) showRoomMobs(sess *net.Session, p *player.Player, room *world.Roo
for _, m := range mobs {
hp := ""
if m.HP < m.MaxHP {
- hp = fmt.Sprintf(" [%s/%dhp]", g.colorize(sess, "enemy_hp", fmt.Sprint(m.HP)), m.MaxHP)
+ hp = fmt.Sprintf(" [%s/%dhp]", color.Render(g.colorMode(sess), color.Parse("167"), fmt.Sprint(m.HP)), m.MaxHP)
}
var desc string
if combat.IsMobInCombat(m.InstanceID) {
@@ -98,13 +98,13 @@ func (g *Game) showRoomMobs(sess *net.Session, p *player.Player, room *world.Roo
if !m.Unique {
displayName = "A " + m.Name
}
- npcColor := "hostile_npc"
+ mobColor := "mob"
if m.Protected {
- npcColor = "friendly_npc"
+ mobColor = "protected_mob"
}
mobLevel := mobCombatLevel(m)
levelStr := g.levelColorize(sess, playerLevel, mobLevel, fmt.Sprintf("(level %d)", mobLevel))
- sess.WriteLine(fmt.Sprintf(" %s %s%s%s", g.colorize(sess, npcColor, displayName), levelStr, hp, desc))
+ sess.WriteLine(fmt.Sprintf(" %s %s%s%s", g.colorize(sess, mobColor, displayName), levelStr, hp, desc))
}
}
}
@@ -466,15 +466,15 @@ func (g *Game) doLookTarget(sess *net.Session, input string) {
}
}
if best != nil {
- npcColor := "hostile_npc"
+ mobColor := "mob"
if best.Protected {
- npcColor = "friendly_npc"
+ mobColor = "protected_mob"
}
mobLevel := mobCombatLevel(best)
levelStr := g.levelColorize(sess, p.CombatLevel(), mobLevel, fmt.Sprintf("(level %d)", mobLevel))
sess.WriteLines(
"",
- fmt.Sprintf("%s %s", g.colorize(sess, npcColor, best.Name), levelStr),
+ fmt.Sprintf("%s %s", g.colorize(sess, mobColor, best.Name), levelStr),
)
if best.IdleDescription != "" {
sess.WriteLine(fmt.Sprintf(" %s", best.IdleDescription))
diff --git a/internal/game/cmd_score.go b/internal/game/cmd_score.go
index b49fd7f..523dcb9 100644
--- a/internal/game/cmd_score.go
+++ b/internal/game/cmd_score.go
@@ -20,7 +20,7 @@ func (g *Game) doScore(sess *net.Session) {
"",
fmt.Sprintf("Name: %s", g.colorize(sess, "player_name", p.Name)),
fmt.Sprintf("Combat Level: %s", color.Render(mode, color.Parse("230"), fmt.Sprint(p.CombatLevel()))),
- fmt.Sprintf("HP: %s/%s", g.colorize(sess, "character_hp", fmt.Sprint(p.HP)), color.Render(mode, color.Parse("230"), fmt.Sprint(p.MaxHP()))),
+ fmt.Sprintf("HP: %s/%s", color.Render(mode, color.Parse("84"), fmt.Sprint(p.HP)), color.Render(mode, color.Parse("230"), fmt.Sprint(p.MaxHP()))),
fmt.Sprintf("Battery: %s/%s", g.colorize(sess, "battery", fmt.Sprintf("%.1f", p.Battery)), color.Render(mode, color.Parse("230"), fmt.Sprintf("%.0f", p.MaxBattery()))),
fmt.Sprintf("Credits: %s", g.colorize(sess, "credits_pickup", fmt.Sprint(p.Credits))),
)
diff --git a/internal/game/cmd_trigger_combat.go b/internal/game/cmd_trigger_combat.go
index 444f80d..2086235 100644
--- a/internal/game/cmd_trigger_combat.go
+++ b/internal/game/cmd_trigger_combat.go
@@ -62,12 +62,12 @@ func (g *Game) scienceAttack(sess *net.Session, p *player.Player, mob *world.Mob
g.AccountStore.SaveCharacter(p)
mobName := mobDisplayName(mob, true)
- prefix := fmt.Sprintf(" %s hits %s for %s damage.",
+ prefix := fmt.Sprintf("%s hits %s for %s damage.",
g.colorize(sess, "science_mod", mod.Name),
- g.colorize(sess, "mob_name", mobName),
- g.colorize(sess, "damage", fmt.Sprint(dmg)))
- hpPart := fmt.Sprintf("[%s/%dhp]", g.colorize(sess, "enemy_hp", fmt.Sprint(mob.HP)), mob.MaxHP)
- line := prefix + " " + hpPart
+ g.colorize(sess, "mob", mobName),
+ g.colorize(sess, "damage_dealt", fmt.Sprint(dmg)))
+ hpSuffix := fmt.Sprintf("%s [%2d/%d]", g.hpBar(sess, mob.HP, mob.MaxHP), mob.HP, mob.MaxHP)
+ line := prefix + " " + hpSuffix
if p.OptionBool("xp_drops") && len(gains) > 0 {
var parts []string
for _, gain := range gains {
@@ -77,7 +77,7 @@ func (g *Game) scienceAttack(sess *net.Session, p *player.Player, mob *world.Mob
}
sess.WriteLine(line)
} else {
- sess.WriteLine(g.colorize(sess, "miss", fmt.Sprintf(" %s fails to connect.", mod.Name)))
+ sess.WriteLine(g.colorize(sess, "miss", fmt.Sprintf("%s fails to connect.", mod.Name)))
}
return true
}
diff --git a/internal/game/core_production.go b/internal/game/core_production.go
index 47a164c..b4101a9 100644
--- a/internal/game/core_production.go
+++ b/internal/game/core_production.go
@@ -286,7 +286,7 @@ func (g *Game) advanceProduction(sess *net.Session, p *player.Player) bool {
msg := g.resolveCraftMessage(sess, craft.FailMessage, info.FailMessage, craft, item.ID, nil, p.HasItem)
if msg != "" {
- sess.WriteLine(g.colorize(sess, "damage", msg))
+ sess.WriteLine(g.colorize(sess, "damage_taken", msg))
}
}
diff --git a/internal/game/sys_combat.go b/internal/game/sys_combat.go
index 702871c..81b2c83 100644
--- a/internal/game/sys_combat.go
+++ b/internal/game/sys_combat.go
@@ -105,7 +105,7 @@ func (g *Game) checkAggro(sess *net.Session) {
if !aggMob.Unique {
attacker = "The " + aggMob.Name
}
- sess.WriteLine(fmt.Sprintf("\n%s attacks you!", g.colorize(sess, "mob_name", attacker)))
+ sess.WriteLine(fmt.Sprintf("\n%s attacks you!", g.colorize(sess, "mob", attacker)))
g.startCombat(sess, p, aggMob)
return false
})
diff --git a/internal/game/tick.go b/internal/game/tick.go
index ec7eda9..1402b06 100644
--- a/internal/game/tick.go
+++ b/internal/game/tick.go
@@ -142,17 +142,17 @@ func (g *Game) WanderTick() {
if p.RoomID == m.fromRoom && p.OptionBool("mob_leave") {
if m.level > 0 {
levelStr := g.levelColorize(sess, p.CombatLevel(), m.level, fmt.Sprintf("(level %d)", m.level))
- sess.WriteLine(fmt.Sprintf("\n%s %s leaves.", g.colorize(sess, "mob_name", m.name), levelStr))
+ sess.WriteLine(fmt.Sprintf("\n%s %s leaves.", g.colorize(sess, "mob", m.name), levelStr))
} else {
- sess.WriteLine(fmt.Sprintf("\n%s moves away.", g.colorize(sess, "mob_name", m.name)))
+ sess.WriteLine(fmt.Sprintf("\n%s moves away.", g.colorize(sess, "mob", m.name)))
}
}
if p.RoomID == m.toRoom && p.OptionBool("mob_enter") {
if m.level > 0 {
levelStr := g.levelColorize(sess, p.CombatLevel(), m.level, fmt.Sprintf("(level %d)", m.level))
- sess.WriteLine(fmt.Sprintf("\n%s %s enters.", g.colorize(sess, "mob_name", m.name), levelStr))
+ sess.WriteLine(fmt.Sprintf("\n%s %s enters.", g.colorize(sess, "mob", m.name), levelStr))
} else {
- sess.WriteLine(fmt.Sprintf("\n%s drifts in.", g.colorize(sess, "mob_name", m.name)))
+ sess.WriteLine(fmt.Sprintf("\n%s drifts in.", g.colorize(sess, "mob", m.name)))
}
}
}
diff --git a/internal/game/ui_combat.go b/internal/game/ui_combat.go
new file mode 100644
index 0000000..56de7e8
--- /dev/null
+++ b/internal/game/ui_combat.go
@@ -0,0 +1,59 @@
+package game
+
+import (
+ "math"
+ "strings"
+
+ "thehouseoficarus/internal/color"
+ "thehouseoficarus/internal/net"
+)
+
+func (g *Game) hpBar(sess *net.Session, current, max int) string {
+ if max <= 0 {
+ max = 1
+ }
+ if current < 0 {
+ current = 0
+ }
+ if current > max {
+ current = max
+ }
+
+ pct := float64(current) / float64(max) * 100.0
+ filled := int(math.Round(pct / 10.0))
+ if filled < 0 {
+ filled = 0
+ }
+ if filled > 10 {
+ filled = 10
+ }
+
+ mode := g.colorMode(sess)
+
+ var fgColor int
+ switch {
+ case pct >= 70:
+ fgColor = 82
+ case pct >= 40:
+ fgColor = 220
+ default:
+ fgColor = 160
+ }
+
+ fillSpec := color.ColorSpec{Fg: fgColor}
+ emptySpec := color.ColorSpec{Fg: 238, Dim: true}
+
+ fillChar := "█"
+ emptyChar := "░"
+ if sess.Player != nil && !sess.Player.OptionBool("unicode") {
+ fillChar = "#"
+ emptyChar = "."
+ }
+
+ var sb strings.Builder
+ sb.WriteString("[")
+ sb.WriteString(color.Render(mode, fillSpec, strings.Repeat(fillChar, filled)))
+ sb.WriteString(color.Render(mode, emptySpec, strings.Repeat(emptyChar, 10-filled)))
+ sb.WriteString("]")
+ return sb.String()
+}
diff --git a/internal/game/ui_help.go b/internal/game/ui_help.go
index 151d1ba..ee8ad97 100644
--- a/internal/game/ui_help.go
+++ b/internal/game/ui_help.go
@@ -70,7 +70,7 @@ var commandList = []cmdEntry{
{"steal", "Active", "Steal from mobs (Thieving)"},
{"stoke", "Active", "Add logs to a fire"},
{"style", "Instant", "Change combat style"},
- {"talk / speak / ask", "Active", "Talk to NPCs"},
+ {"talk / speak / ask", "Active", "Talk to mobs"},
{"tech", "Instant", "Toggle technology abilities"},
{"trigger", "Active", "Trigger a science module"},
{"unalias", "Instant", "Remove command shortcuts"},