aboutsummaryrefslogtreecommitdiff
path: root/internal/game
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game')
-rw-r--r--internal/game/cmd_aps.go15
-rw-r--r--internal/game/cmd_color.go63
-rw-r--r--internal/game/cmd_mods.go16
-rw-r--r--internal/game/cmd_option.go20
-rw-r--r--internal/game/cmd_score.go85
-rw-r--r--internal/game/cmd_skills.go24
-rw-r--r--internal/game/cmd_tech.go12
-rw-r--r--internal/game/game.go30
-rw-r--r--internal/game/look_entities.go11
-rw-r--r--internal/game/look_target.go2
-rw-r--r--internal/game/production_menu.go2
-rw-r--r--internal/game/render_color.go43
-rw-r--r--internal/game/render_combat.go44
-rw-r--r--internal/game/sys_labor.go2
14 files changed, 225 insertions, 144 deletions
diff --git a/internal/game/cmd_aps.go b/internal/game/cmd_aps.go
index 727bf8b..56820ad 100644
--- a/internal/game/cmd_aps.go
+++ b/internal/game/cmd_aps.go
@@ -125,15 +125,18 @@ func displayApsNodes(g *Game, sess *net.Session, p *player.Player, known []int)
})
mode := g.colorMode(sess)
+ hLabel := g.resolveConstant(sess, "table_header_label")
+ hDetail := g.resolveConstant(sess, "table_header_detail")
+ hValue := g.resolveConstant(sess, "table_header_value")
rows := make([][]string, len(infos))
for i, info := range infos {
distStr := strconv.Itoa(info.Distance)
if info.Distance == 0 {
- distStr = color.Render(mode, color.Parse("6C"), "here")
+ distStr = color.Render(mode, g.resolveConstant(sess, "aps_here"), "here")
}
rows[i] = []string{
- color.Render(mode, color.Parse("F5"), fmt.Sprintf("#%d", info.ID)),
- color.Render(mode, color.Parse("49"), info.Name),
+ color.Render(mode, hDetail, fmt.Sprintf("#%d", info.ID)),
+ color.Render(mode, hLabel, info.Name),
distStr,
}
}
@@ -141,9 +144,9 @@ func displayApsNodes(g *Game, sess *net.Session, p *player.Player, known []int)
t := &ui.Table{
Title: "APS Datapad",
Columns: []string{
- color.Render(mode, color.Parse("F5"), "Room"),
- color.Render(mode, color.Parse("49"), "Name"),
- color.Render(mode, color.Parse("FA"), "Distance"),
+ color.Render(mode, hDetail, "Room"),
+ color.Render(mode, hLabel, "Name"),
+ color.Render(mode, hValue, "Distance"),
},
Rows: rows,
}
diff --git a/internal/game/cmd_color.go b/internal/game/cmd_color.go
index 01e2b9a..01912a7 100644
--- a/internal/game/cmd_color.go
+++ b/internal/game/cmd_color.go
@@ -134,39 +134,78 @@ func (g *Game) executeColor(sess *net.Session, args []string, rawInput string) {
}
var colorCategoryOrder = []string{
+ // Room / look
"room_name",
"room_number",
"room_desc",
- "direction",
"exit_direction",
"exit_name",
- "protected_mob",
+ "dim",
"mob",
+ "protected_mob",
+ "player_name",
+ "item",
+
+ // Movement
+ "direction",
+
+ // Combat
"damage_dealt",
"damage_taken",
-
- "xp",
- "level_up",
- "item",
- "player_name",
+ "miss",
"death",
"victory",
- "miss",
+ "warning",
"error",
+ "science_mod",
+
+ // XP / level
+ "xp",
+ "level_up",
+
+ // Pickups / economy
+ "currency_pickup",
+
+ // Chat / dialog
"say",
"global",
+ "broadcast",
"dialog",
"dialog_options",
- "broadcast",
- "fire",
- "eat_food",
+
+ // Drops / consume / fire
"drop_message",
- "currency_pickup",
+ "eat_food",
+ "fire",
+
+ // Tick / status
+ "visual_tick",
+ "visual_first_tick",
+ // Tech / farm
+ "battery",
+ "tech_depleted",
+ "farm_grow",
+ "farm_disease",
+
+ // Assassin / map / safespot
+ "assassin_task",
"map_at",
"map_blocked",
"map_agility",
"safespot_alert",
+
+ // System
+ "sequence",
+}
+
+// PlayerColorCategoryOrder is the canonical ordered list of player-overridable
+// color categories, shared with the admin GUI so its left-side editor list
+// mirrors the in-game `colors` command output exactly.
+func PlayerColorCategoryOrder() []string {
+ out := make([]string, len(colorCategoryOrder))
+ copy(out, colorCategoryOrder)
+ return out
}
func showColorTable(g *Game, sess *net.Session) {
diff --git a/internal/game/cmd_mods.go b/internal/game/cmd_mods.go
index 26b0e95..0126b5f 100644
--- a/internal/game/cmd_mods.go
+++ b/internal/game/cmd_mods.go
@@ -55,10 +55,10 @@ func (g *Game) doMods(sess *net.Session, showAll bool) {
t := &ui.Table{
Title: cat.Name,
Columns: []string{
- color.Render(mode, color.Parse("49"), "Module"),
- color.Render(mode, color.Parse("F5"), "Level"),
- color.Render(mode, color.Parse("B3"), "Cost"),
- color.Render(mode, color.Parse("B3"), "XP"),
+ color.Render(mode, g.resolveConstant(sess, "table_header_label"), "Module"),
+ color.Render(mode, g.resolveConstant(sess, "table_header_detail"), "Level"),
+ color.Render(mode, g.resolveConstant(sess, "table_header_xp"), "Cost"),
+ color.Render(mode, g.resolveConstant(sess, "table_header_xp"), "XP"),
},
}
@@ -77,10 +77,10 @@ func (g *Game) doMods(sess *net.Session, showAll bool) {
})
} else if unlocked {
t.Rows = append(t.Rows, []string{
- color.Render(mode, color.Parse("49"), m.Name),
- color.Render(mode, color.Parse("F5"), levelStr),
- color.Render(mode, color.Parse("B3"), costStr),
- color.Render(mode, color.Parse("B3"), xpStr),
+ color.Render(mode, g.resolveConstant(sess, "table_header_label"), m.Name),
+ color.Render(mode, g.resolveConstant(sess, "table_header_detail"), levelStr),
+ color.Render(mode, g.resolveConstant(sess, "table_header_xp"), costStr),
+ color.Render(mode, g.resolveConstant(sess, "table_header_xp"), xpStr),
})
} else {
t.Rows = append(t.Rows, []string{
diff --git a/internal/game/cmd_option.go b/internal/game/cmd_option.go
index 2d6f3f0..55a04de 100644
--- a/internal/game/cmd_option.go
+++ b/internal/game/cmd_option.go
@@ -16,12 +16,16 @@ func (g *Game) doOption(sess *net.Session, input string) {
mode := g.colorMode(sess)
if input == "" {
+ hLabel := g.resolveConstant(sess, "table_header_label")
+ hValue := g.resolveConstant(sess, "table_header_value")
+ hDetail := g.resolveConstant(sess, "table_header_detail")
+ hDesc := g.resolveConstant(sess, "table_header_desc")
table := &ui.Table{
Columns: []string{
- color.Render(mode, color.Parse("49"), "Option"),
- color.Render(mode, color.Parse("FA"), "Value"),
- color.Render(mode, color.Parse("F5"), "Valid"),
- color.Render(mode, color.Parse("FF"), "Description"),
+ color.Render(mode, hLabel, "Option"),
+ color.Render(mode, hValue, "Value"),
+ color.Render(mode, hDetail, "Valid"),
+ color.Render(mode, hDesc, "Description"),
},
}
for _, def := range player.OptionDefs {
@@ -29,10 +33,10 @@ func (g *Game) doOption(sess *net.Session, input string) {
val = truncateForTable(val, p.OptionBool("unicode"))
valid := formatValidValues(&def)
table.Rows = append(table.Rows, []string{
- color.Render(mode, color.Parse("49"), def.Name),
- color.Render(mode, color.Parse("FA"), val),
- color.Render(mode, color.Parse("F5"), valid),
- color.Render(mode, color.Parse("FF"), def.Description),
+ color.Render(mode, hLabel, def.Name),
+ color.Render(mode, hValue, val),
+ color.Render(mode, hDetail, valid),
+ color.Render(mode, hDesc, def.Description),
})
}
for _, line := range table.Render(p.OptionBool("unicode"), p.OptionInt("wrap_width")) {
diff --git a/internal/game/cmd_score.go b/internal/game/cmd_score.go
index 31caf1e..3949486 100644
--- a/internal/game/cmd_score.go
+++ b/internal/game/cmd_score.go
@@ -92,43 +92,46 @@ func (g *Game) doScore(sess *net.Session) {
sep()
nameStr := g.colorize(sess, "player_name", p.Name)
- clStr := color.Render(mode, color.Parse("FA"), fmt.Sprintf("Combat Lvl %d", p.CombatLevel()))
+ clStr := g.colorizeConstant(sess, "score_max_value", fmt.Sprintf("Combat Lvl %d", p.CombatLevel()))
roomStr := g.colorize(sess, "room_number", fmt.Sprintf("Room #%d", p.RoomID))
row1 := padded(nameStr, 20) + padded(clStr, 24) + roomStr
content(row1)
creditsStr := g.colorize(sess, "currency_pickup", formatInt(p.Credits))
chipsStr := g.colorize(sess, "currency_pickup", formatInt(countChips(p)))
- styleStr := color.Render(mode, color.Parse("49"), fmt.Sprintf("Style: %s", p.AttackStyle))
+ styleStr := color.Render(mode, g.resolveConstant(sess, "table_header_label"), fmt.Sprintf("Style: %s", p.AttackStyle))
invStr := fmt.Sprintf("Inv: %d/28", 28-p.FreeSlots())
row2 := padded("Credits: "+creditsStr, 19) + padded("Chips: "+chipsStr, 16) + padded(styleStr, 22) + invStr
content(row2)
gap()
- hpStyle := hpBarStyle(p.HP, p.MaxHP())
+ hpStyle := g.hpBarStyle(sess, p.HP, p.MaxHP())
hpBarStr := ui.RenderColoredBar(p.HP, p.MaxHP(), 26, unicode, hpStyle, mode)
- hpFg := hpBarFg(p.HP, p.MaxHP())
+ hpFg := g.hpBarStyle(sess, p.HP, p.MaxHP()).FilledColor
hpLine := fmt.Sprintf("HP [%s] %s / %s",
hpBarStr,
color.Render(mode, color.ColorSpec{Fg: hpFg}, fmt.Sprint(p.HP)),
- color.Render(mode, color.Parse("FA"), fmt.Sprint(p.MaxHP())))
+ g.colorizeConstant(sess, "score_max_value", fmt.Sprint(p.MaxHP())))
content(hpLine)
batBarRaw := ui.RenderBarF(p.Battery, p.MaxBattery(), 26, unicode)
- batBarStr := color.Render(mode, color.ColorSpec{Fg: 110}, batBarRaw)
+ batBarStr := color.Render(mode, g.resolveConstant(sess, "battery_bar"), batBarRaw)
+ batFg := g.resolveConstant(sess, "battery_bar").Fg
batLine := fmt.Sprintf("BAT [%s] %s / %s",
batBarStr,
- color.Render(mode, color.ColorSpec{Fg: 110}, fmt.Sprintf("%.1f", p.Battery)),
- color.Render(mode, color.Parse("FA"), fmt.Sprintf("%.0f", p.MaxBattery())))
+ color.Render(mode, color.ColorSpec{Fg: batFg}, fmt.Sprintf("%.1f", p.Battery)),
+ g.colorizeConstant(sess, "score_max_value", fmt.Sprintf("%.0f", p.MaxBattery())))
content(batLine)
- engStyle := &ui.BarStyle{FilledColor: 108, EmptyColor: 237, EmptyDim: true}
+ engSpec := g.resolveConstant(sess, "run_energy_bar")
+ engEmpty := g.resolveConstant(sess, "hp_bar_empty")
+ engStyle := &ui.BarStyle{FilledColor: engSpec.Fg, EmptyColor: engEmpty.Fg, EmptyDim: engEmpty.Dim}
engBarStr := ui.RenderColoredBar(p.RunEnergy, p.MaxRunEnergy(), 26, unicode, engStyle, mode)
engLine := fmt.Sprintf("ENG [%s] %s / %s",
engBarStr,
- color.Render(mode, color.ColorSpec{Fg: 108}, fmt.Sprint(p.RunEnergy)),
- color.Render(mode, color.Parse("FA"), fmt.Sprint(p.MaxRunEnergy())))
+ color.Render(mode, color.ColorSpec{Fg: engSpec.Fg}, fmt.Sprint(p.RunEnergy)),
+ g.colorizeConstant(sess, "score_max_value", fmt.Sprint(p.MaxRunEnergy())))
content(engLine)
gap()
@@ -155,8 +158,8 @@ func (g *Game) doScore(sess *net.Session) {
buildSkillCell := func(s player.SkillName) string {
level := p.Level(s)
- lvlColor := skillLevelColor(level)
- name := padded(color.Render(mode, color.ColorSpec{Fg: 110}, skillDisplay[s]), 12)
+ lvlColor := g.skillLevelColor(sess, level)
+ name := padded(color.Render(mode, g.resolveConstant(sess, "active_tech_stat"), skillDisplay[s]), 12)
lvl := color.Render(mode, color.ColorSpec{Fg: lvlColor}, fmt.Sprintf("%2d", level))
xpBar := xpProgressBar(p.Skills[s], 12, unicode)
return fmt.Sprintf("%s %s [%s]", name, lvl, xpBar)
@@ -195,8 +198,8 @@ func (g *Game) doScore(sess *net.Session) {
for _, id := range p.ActiveTechList() {
def := GetTechDef(id)
if def != nil {
- on := color.Render(mode, color.ColorSpec{Fg: 108}, "[ON]")
- name := color.Render(mode, color.ColorSpec{Fg: 110}, def.Name)
+ on := color.Render(mode, g.resolveConstant(sess, "active_tech_on"), "[ON]")
+ name := color.Render(mode, g.resolveConstant(sess, "active_tech_stat"), def.Name)
techParts = append(techParts, on+" "+name)
}
}
@@ -205,9 +208,9 @@ func (g *Game) doScore(sess *net.Session) {
if len(p.ActiveBuffs) > 0 {
for _, buff := range p.ActiveBuffs {
buffLine := fmt.Sprintf("%s +%d%% %s (%d ticks remaining)",
- color.Render(mode, color.ColorSpec{Fg: 108}, "[BUFF]"),
+ color.Render(mode, g.resolveConstant(sess, "active_tech_status"), "[BUFF]"),
buff.BonusPercent,
- color.Render(mode, color.ColorSpec{Fg: 110}, buff.Stat),
+ color.Render(mode, g.resolveConstant(sess, "active_tech_stat"), buff.Stat),
buff.TicksLeft)
content(buffLine)
}
@@ -252,51 +255,21 @@ func formatInt(n int) string {
return strings.Join(parts, ",")
}
-func hpBarStyle(current, max int) *ui.BarStyle {
- if max <= 0 {
- max = 1
- }
- pct := float64(current) / float64(max)
- var fg int
- switch {
- case pct >= 0.7:
- fg = 108
- case pct >= 0.4:
- fg = 179
- default:
- fg = 167
- }
- return &ui.BarStyle{FilledColor: fg, EmptyColor: 237, EmptyDim: true}
-}
-
-func hpBarFg(current, max int) int {
- if max <= 0 {
- max = 1
- }
- pct := float64(current) / float64(max)
- switch {
- case pct >= 0.7:
- return 108
- case pct >= 0.4:
- return 179
- default:
- return 167
- }
-}
-
-func skillLevelColor(level int) int {
+// skillLevelColor returns the xterm256 palette index for a skill level cell
+// on the datapad score panel, sourcing it from the constant_colors section.
+func (g *Game) skillLevelColor(sess *net.Session, level int) int {
+ spec := g.resolveConstant(sess, "skill_lvl_base")
switch {
case level >= 99:
- return 180
+ spec = g.resolveConstant(sess, "skill_lvl_max")
case level >= 80:
- return 179
+ spec = g.resolveConstant(sess, "skill_lvl_high")
case level >= 60:
- return 116
+ spec = g.resolveConstant(sess, "skill_lvl_mid")
case level >= 40:
- return 73
- default:
- return 66
+ spec = g.resolveConstant(sess, "skill_lvl_low")
}
+ return spec.Fg
}
func xpProgressBar(xp int, width int, unicode bool) string {
diff --git a/internal/game/cmd_skills.go b/internal/game/cmd_skills.go
index 2eeb149..2f9f52e 100644
--- a/internal/game/cmd_skills.go
+++ b/internal/game/cmd_skills.go
@@ -16,23 +16,27 @@ func (g *Game) executeSkills(sess *net.Session, args []string, rawInput string)
func (g *Game) doSkills(sess *net.Session) {
p := sess.Player
mode := g.colorMode(sess)
+ hLabel := g.resolveConstant(sess, "table_header_label")
+ hDetail := g.resolveConstant(sess, "table_header_detail")
+ hValue := g.resolveConstant(sess, "table_header_value")
+ hXP := g.resolveConstant(sess, "table_header_xp")
t := &ui.Table{Title: "Skills", Columns: []string{
- color.Render(mode, color.Parse("49"), "Skill"),
- color.Render(mode, color.Parse("F5"), "Abbr"),
- color.Render(mode, color.Parse("FA"), "Level"),
- color.Render(mode, color.Parse("B3"), "XP"),
- color.Render(mode, color.Parse("B3"), "XP to Next"),
+ color.Render(mode, hLabel, "Skill"),
+ color.Render(mode, hDetail, "Abbr"),
+ color.Render(mode, hValue, "Level"),
+ color.Render(mode, hXP, "XP"),
+ color.Render(mode, hXP, "XP to Next"),
}}
for _, s := range player.AllSkills {
level := p.Level(s)
xp := p.Skills[s]
next := player.XPForNextLevel(xp)
t.Rows = append(t.Rows, []string{
- color.Render(mode, color.Parse("49"), string(s)),
- color.Render(mode, color.Parse("F5"), player.SkillAbbr[s]),
- color.Render(mode, color.Parse("FA"), strconv.Itoa(level)),
- color.Render(mode, color.Parse("B3"), strconv.Itoa(xp)),
- color.Render(mode, color.Parse("B3"), strconv.Itoa(next)),
+ color.Render(mode, hLabel, string(s)),
+ color.Render(mode, hDetail, player.SkillAbbr[s]),
+ color.Render(mode, hValue, strconv.Itoa(level)),
+ color.Render(mode, hXP, strconv.Itoa(xp)),
+ color.Render(mode, hXP, strconv.Itoa(next)),
})
}
for _, line := range t.Render(p.OptionBool("unicode"), p.OptionInt("wrap_width")) {
diff --git a/internal/game/cmd_tech.go b/internal/game/cmd_tech.go
index 4ca1382..0a5580c 100644
--- a/internal/game/cmd_tech.go
+++ b/internal/game/cmd_tech.go
@@ -118,7 +118,7 @@ func (g *Game) doTechList(sess *net.Session) {
"",
fmt.Sprintf("Battery: %s/%s",
g.colorize(sess, "battery", fmt.Sprintf("%.1f", p.Battery)),
- color.Render(mode, color.Parse("FA"), fmt.Sprintf("%.0f", p.MaxBattery())),
+ g.colorizeConstant(sess, "score_max_value", fmt.Sprintf("%.0f", p.MaxBattery())),
),
)
@@ -129,14 +129,14 @@ func (g *Game) doTechList(sess *net.Session) {
nameStr := tech.Name
if tech.Level <= techLevel {
if p.HasActiveTech(tech.ID) {
- status = color.Render(mode, color.Parse("6C"), "ON")
+ status = color.Render(mode, g.resolveConstant(sess, "tech_status_on"), "ON")
} else {
- status = color.Render(mode, color.Parse("F0"), "off")
+ status = color.Render(mode, g.resolveConstant(sess, "tech_status_off"), "off")
}
- nameStr = color.Render(mode, color.Parse("49"), tech.Name)
+ nameStr = color.Render(mode, g.resolveConstant(sess, "tech_name_unlocked"), tech.Name)
} else {
- status = color.Render(mode, color.Parse("A7"), "locked")
- nameStr = color.Render(mode, color.Parse("F0"), tech.Name)
+ status = color.Render(mode, g.resolveConstant(sess, "tech_status_locked"), "locked")
+ nameStr = color.Render(mode, g.resolveConstant(sess, "tech_name_locked"), tech.Name)
}
effect := techEffectString(tech)
diff --git a/internal/game/game.go b/internal/game/game.go
index eed61f5..700877b 100644
--- a/internal/game/game.go
+++ b/internal/game/game.go
@@ -38,9 +38,10 @@ type Deps struct {
MobStore *world.MobStore
CraftIndex *CraftIndex
CourseStore *CourseStore
- Ticks *engine.Engine
- ColorConfig *config.ColorsConfig
- DataDir string
+ Ticks *engine.Engine
+ ColorConfig *config.ColorsConfig
+ ConstantColorConfig *config.ColorsConfig
+ DataDir string
}
// Game is the central orchestrator: it owns the data stores (via the embedded
@@ -75,19 +76,20 @@ type Game struct {
shutdownMu sync.Mutex
}
-func New(dataDir string, colorConfig *config.ColorsConfig, valConfig config.ValidationConfig, startingRoom int) *Game {
+func New(dataDir string, colorConfig *config.ColorsConfig, constantColorConfig *config.ColorsConfig, valConfig config.ValidationConfig, startingRoom int) *Game {
g := &Game{
Deps: Deps{
- World: world.New(dataDir),
- ObjectStore: object.NewObjectStore(dataDir),
- ItemStore: item.NewItemStore(dataDir),
- AccountStore: player.NewAccountStore(dataDir),
- MobStore: world.NewMobStore(dataDir),
- CraftIndex: NewCraftIndex(),
- CourseStore: NewCourseStore(dataDir),
- Ticks: engine.New(),
- ColorConfig: colorConfig,
- DataDir: dataDir,
+ World: world.New(dataDir),
+ ObjectStore: object.NewObjectStore(dataDir),
+ ItemStore: item.NewItemStore(dataDir),
+ AccountStore: player.NewAccountStore(dataDir),
+ MobStore: world.NewMobStore(dataDir),
+ CraftIndex: NewCraftIndex(),
+ CourseStore: NewCourseStore(dataDir),
+ Ticks: engine.New(),
+ ColorConfig: colorConfig,
+ ConstantColorConfig: constantColorConfig,
+ DataDir: dataDir,
},
GlobalFlags: NewGlobalFlagStore(),
Combat: combat.NewTracker(),
diff --git a/internal/game/look_entities.go b/internal/game/look_entities.go
index c1befe3..dd2cdf0 100644
--- a/internal/game/look_entities.go
+++ b/internal/game/look_entities.go
@@ -37,7 +37,7 @@ func (g *Game) showRoomMobs(sess *net.Session, p *player.Player, room *world.Roo
}
hp = fmt.Sprintf(" [%d%% complete]", pct)
} else {
- hp = fmt.Sprintf(" [%s/%dhp]", color.Render(g.colorMode(sess), color.Parse("A7"), fmt.Sprint(m.HP)), m.MaxHP)
+ hp = fmt.Sprintf(" [%s/%dhp]", g.colorizeConstant(sess, "mob_hp_inline", fmt.Sprint(m.HP)), m.MaxHP)
}
}
var desc string
@@ -103,13 +103,16 @@ func (g *Game) showRoomObjects(sess *net.Session, p *player.Player, room *world.
var godPrefix string
if p.GodMode {
var tags []string
+ tagLabel := func(t string) string {
+ return color.Render(g.colorMode(sess), g.resolveConstant(sess, "god_tag_label"), t)
+ }
if isLocal {
- tags = append(tags, color.Render(g.colorMode(sess), color.ColorSpec{Fg: -1, Bold: true}, "[LOCAL]"))
+ tags = append(tags, tagLabel("[LOCAL]"))
} else {
- tags = append(tags, color.Render(g.colorMode(sess), color.ColorSpec{Fg: -1, Bold: true}, "[GLOBAL]"))
+ tags = append(tags, tagLabel("[GLOBAL]"))
}
if def.Hidden {
- tags = append(tags, color.Render(g.colorMode(sess), color.ColorSpec{Fg: -1, Bold: true}, "(HIDDEN)"))
+ tags = append(tags, tagLabel("(HIDDEN)"))
}
godPrefix = strings.Join(tags, " ") + " "
}
diff --git a/internal/game/look_target.go b/internal/game/look_target.go
index bb9b5da..9257940 100644
--- a/internal/game/look_target.go
+++ b/internal/game/look_target.go
@@ -64,7 +64,7 @@ func (g *Game) doLookTarget(sess *net.Session, input string) {
}
var levelStr string
if best.Protected {
- levelStr = color.Render(g.colorMode(sess), color.Parse("FA"), "(protected)")
+ levelStr = g.colorizeConstant(sess, "protected_label", "(protected)")
} else {
mobLevel := mobCombatLevel(best)
levelStr = g.levelColorize(sess, p.CombatLevel(), mobLevel, fmt.Sprintf("(level %d)", mobLevel))
diff --git a/internal/game/production_menu.go b/internal/game/production_menu.go
index ff9fc51..10b7eb0 100644
--- a/internal/game/production_menu.go
+++ b/internal/game/production_menu.go
@@ -127,7 +127,7 @@ func (g *Game) showProductionTable(sess *net.Session, p *player.Player, items []
mode := g.colorMode(sess)
skillLevel := p.Level(player.SkillName(skill))
- dimSpec := color.Parse("F0")
+ dimSpec := g.resolveConstant(sess, "production_locked")
tbl := &ui.Table{
Title: title,
diff --git a/internal/game/render_color.go b/internal/game/render_color.go
index 3464f26..958b671 100644
--- a/internal/game/render_color.go
+++ b/internal/game/render_color.go
@@ -42,24 +42,53 @@ func (g *Game) colorize(sess *net.Session, category, text string) string {
return color.Render(g.colorMode(sess), spec, text)
}
-func levelColorSpec(myLevel, theirLevel int) color.ColorSpec {
+// resolveConstant returns the live spec for a constant (player-invisible)
+// color category. Unlike resolveColor, it never consults the account's
+// per-player override map — constants are admin-tunable only — so players
+// have no way to change them. Falls back through the file's constant_colors
+// map, then to the built-in DefaultConstantColors.
+func (g *Game) resolveConstant(sess *net.Session, category string) color.ColorSpec {
+ if g.ConstantColorConfig != nil {
+ if val, ok := (*g.ConstantColorConfig)[category]; ok && val != "" {
+ return color.Parse(val)
+ }
+ }
+ if val, ok := config.DefaultConstantColors()[category]; ok && val != "" {
+ return color.Parse(val)
+ }
+ return color.NoColor()
+}
+
+// colorizeConstant paints text with a constant color spec, bypassing the
+// player override tier entirely.
+func (g *Game) colorizeConstant(sess *net.Session, category, text string) string {
+ spec := g.resolveConstant(sess, category)
+ return color.Render(g.colorMode(sess), spec, text)
+}
+
+// levelColorSpec returns the color spec for the level-difference band shown
+// next to mob/player levels (green/red/white by how far the target is from
+// the viewer). Uses admin-tunable constant_colors; falls back to built-ins.
+func (g *Game) levelColorSpec(sess *net.Session, myLevel, theirLevel int) color.ColorSpec {
diff := theirLevel - myLevel
+ var name string
switch {
case diff == 0:
- return color.Parse("FA")
+ name = "level_diff_equal"
case diff > 0 && diff < 5:
- return color.Parse("B3")
+ name = "level_diff_up_small"
case diff >= 5:
- return color.Parse("A7")
+ name = "level_diff_up_big"
case diff < 0 && diff > -5:
- return color.Parse("6E")
+ name = "level_diff_down_small"
default:
- return color.Parse("41")
+ name = "level_diff_down_big"
}
+ return g.resolveConstant(sess, name)
}
func (g *Game) levelColorize(sess *net.Session, myLevel, theirLevel int, text string) string {
- spec := levelColorSpec(myLevel, theirLevel)
+ spec := g.levelColorSpec(sess, myLevel, theirLevel)
return color.Render(g.colorMode(sess), spec, text)
}
diff --git a/internal/game/render_combat.go b/internal/game/render_combat.go
index e4a0d55..4055b4d 100644
--- a/internal/game/render_combat.go
+++ b/internal/game/render_combat.go
@@ -3,6 +3,7 @@ package game
import (
"fmt"
+ "thehouseoficarus/internal/color"
"thehouseoficarus/internal/net"
"thehouseoficarus/internal/ui"
)
@@ -14,7 +15,10 @@ func (g *Game) dmgDisplay(sess *net.Session, dmg int) string {
return fmt.Sprintf("> %d <", dmg)
}
-func (g *Game) renderBar(sess *net.Session, current, max, lowColor int) string {
+// renderBarGeneric renders a high/med/low tiered bar at width 10 using the
+// named constant-color family (prefix: "hp_bar" or "task_bar"). The empty
+// cells are painted with hp_bar_empty.
+func (g *Game) renderBarGeneric(sess *net.Session, current, max int, prefix string) string {
if max <= 0 {
max = 1
}
@@ -25,23 +29,43 @@ func (g *Game) renderBar(sess *net.Session, current, max, lowColor int) string {
current = max
}
- pct := float64(current) / float64(max) * 100.0
- var fgColor int
+ pct := float64(current) / float64(max)
+ var spec color.ColorSpec
switch {
- case pct >= 70:
- fgColor = 108
- case pct >= 40:
- fgColor = 179
+ case pct >= 0.7:
+ spec = g.resolveConstant(sess, prefix+"_high")
+ case pct >= 0.4:
+ spec = g.resolveConstant(sess, prefix+"_med")
default:
- fgColor = lowColor
+ spec = g.resolveConstant(sess, prefix+"_low")
}
+ empty := g.resolveConstant(sess, "hp_bar_empty")
unicode := sess.Player != nil && sess.Player.OptionBool("unicode")
- style := &ui.BarStyle{FilledColor: fgColor, EmptyColor: 237, EmptyDim: true}
+ style := &ui.BarStyle{FilledColor: spec.Fg, EmptyColor: empty.Fg, EmptyDim: empty.Dim}
bar := ui.RenderColoredBar(current, max, 10, unicode, style, g.colorMode(sess))
return "[" + bar + "]"
}
+// hpBarStyle returns the per-tier fill/empty color spec for an HP bar at the
+// given ratio (high>=70%, med>=40%, low<40%). Colors come from the
+// constant_colors map (hp_bar_high/med/low/empty).
+func (g *Game) hpBarStyle(sess *net.Session, current, max int) *ui.BarStyle {
+ if max <= 0 {
+ max = 1
+ }
+ pct := float64(current) / float64(max)
+ spec := g.resolveConstant(sess, "hp_bar_low")
+ switch {
+ case pct >= 0.7:
+ spec = g.resolveConstant(sess, "hp_bar_high")
+ case pct >= 0.4:
+ spec = g.resolveConstant(sess, "hp_bar_med")
+ }
+ empty := g.resolveConstant(sess, "hp_bar_empty")
+ return &ui.BarStyle{FilledColor: spec.Fg, EmptyColor: empty.Fg, EmptyDim: empty.Dim}
+}
+
func (g *Game) hpBar(sess *net.Session, current, max int) string {
- return g.renderBar(sess, current, max, 167)
+ return g.renderBarGeneric(sess, current, max, "hp_bar")
}
diff --git a/internal/game/sys_labor.go b/internal/game/sys_labor.go
index 6004b75..f0e1d01 100644
--- a/internal/game/sys_labor.go
+++ b/internal/game/sys_labor.go
@@ -22,7 +22,7 @@ func taskWorkVerb(mob *world.MobInstance) string {
// inverse of hpBar. It is used for task worksites whose internal HP drains to 0
// to complete the work (progress = MaxHP - HP).
func (g *Game) progressBar(sess *net.Session, progress, max int) string {
- return g.renderBar(sess, progress, max, 67)
+ return g.renderBarGeneric(sess, progress, max, "task_bar")
}
// writeTaskProgress prints the per-tick progress line for a task worksite,