diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/color/color.go | 3 | ||||
| -rw-r--r-- | internal/game/action_talk.go | 1 | ||||
| -rw-r--r-- | internal/game/cmd_attack.go | 2 | ||||
| -rw-r--r-- | internal/game/cmd_estate_directory.go | 1 | ||||
| -rw-r--r-- | internal/game/cmd_move.go | 1 | ||||
| -rw-r--r-- | internal/game/cmd_registry.go | 2 | ||||
| -rw-r--r-- | internal/game/cmd_score.go | 323 | ||||
| -rw-r--r-- | internal/game/cmd_skills.go | 40 | ||||
| -rw-r--r-- | internal/game/cmd_trigger_transport.go | 1 | ||||
| -rw-r--r-- | internal/game/core_login_char.go | 2 | ||||
| -rw-r--r-- | internal/game/ui_bar.go | 104 | ||||
| -rw-r--r-- | internal/game/ui_combat.go | 38 | ||||
| -rw-r--r-- | internal/player/player.go | 5 | ||||
| -rw-r--r-- | internal/player/stats.go | 27 |
14 files changed, 474 insertions, 76 deletions
diff --git a/internal/color/color.go b/internal/color/color.go index 58c040c..dcbb280 100644 --- a/internal/color/color.go +++ b/internal/color/color.go @@ -6,6 +6,7 @@ import ( "regexp" "strconv" "strings" + "unicode/utf8" ) const Reset = "\033[0m" @@ -176,7 +177,7 @@ func StyleCode(name string) string { } func VisibleLen(s string) int { - return len(ansiRe.ReplaceAllString(s, "")) + return utf8.RuneCountInString(ansiRe.ReplaceAllString(s, "")) } func ContrastFg(mode string, bgIndex int) string { diff --git a/internal/game/action_talk.go b/internal/game/action_talk.go index 787ce3e..c37134f 100644 --- a/internal/game/action_talk.go +++ b/internal/game/action_talk.go @@ -242,6 +242,7 @@ func (g *Game) applyNodeAction(sess *net.Session, na *action.NodeAction) { } if na.Teleport > 0 { p.RoomID = na.Teleport + p.Stats.RecordRoomVisit(na.Teleport) g.AccountStore.SaveCharacter(p) g.World.SeedGroundItems(p.RoomID) g.seedRoomMobs(p.RoomID) diff --git a/internal/game/cmd_attack.go b/internal/game/cmd_attack.go index 1ede21f..703b9ac 100644 --- a/internal/game/cmd_attack.go +++ b/internal/game/cmd_attack.go @@ -539,6 +539,7 @@ func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInst } } p.DeactivateAllTechs() + p.Stats.RecordDeath() sess.WriteLine(g.colorize(sess, "death", "\nOh dear, you are dead!")) p.ClearMoveState() g.dropItemsOnDeath(p) @@ -554,6 +555,7 @@ func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInst } if mob != nil && mob.HP <= 0 { + p.Stats.RecordMobKill(mob.DefID) sess.WriteLine(g.colorize(sess, "victory", fmt.Sprintf("\nYou have defeated %s!", mobDisplayName(mob, true)))) g.onAssassinKill(sess, p, mob) diff --git a/internal/game/cmd_estate_directory.go b/internal/game/cmd_estate_directory.go index dbc79f6..780bcce 100644 --- a/internal/game/cmd_estate_directory.go +++ b/internal/game/cmd_estate_directory.go @@ -201,6 +201,7 @@ func (g *Game) teleportPlayer(sess *net.Session, p *player.Player, roomID int) { } p.RoomID = roomID + p.Stats.RecordRoomVisit(roomID) if g.Hub != nil { g.Hub.LeaveRoom(sess) g.Hub.EnterRoom(sess, p.RoomID) diff --git a/internal/game/cmd_move.go b/internal/game/cmd_move.go index e93b14b..f921a81 100644 --- a/internal/game/cmd_move.go +++ b/internal/game/cmd_move.go @@ -119,6 +119,7 @@ func (g *Game) completeMove(sess *net.Session, p *player.Player) { oldRoom := p.RoomID p.RoomID = targetID + p.Stats.RecordRoomVisit(targetID) g.AccountStore.SaveCharacter(p) g.World.SeedGroundItems(p.RoomID) diff --git a/internal/game/cmd_registry.go b/internal/game/cmd_registry.go index ea09e94..4824684 100644 --- a/internal/game/cmd_registry.go +++ b/internal/game/cmd_registry.go @@ -40,6 +40,8 @@ var commandRegistry = map[string]commandDef{ "say": {(*Game).executeSay, ClassInstant}, "sc": {(*Game).executeScore, ClassInstant}, "score": {(*Game).executeScore, ClassInstant}, + "skills": {(*Game).executeSkills, ClassInstant}, + "sk": {(*Game).executeSkills, ClassInstant}, "task": {(*Game).executeTask, ClassInstant}, "stats": {(*Game).executeStats, ClassInstant}, "tech": {(*Game).executeTech, ClassInstant}, diff --git a/internal/game/cmd_score.go b/internal/game/cmd_score.go index 523dcb9..d0302c2 100644 --- a/internal/game/cmd_score.go +++ b/internal/game/cmd_score.go @@ -3,6 +3,7 @@ package game import ( "fmt" "strconv" + "strings" "thehouseoficarus/internal/color" "thehouseoficarus/internal/net" @@ -16,62 +17,300 @@ func (g *Game) executeScore(sess *net.Session, args []string, rawInput string) { func (g *Game) doScore(sess *net.Session) { p := sess.Player mode := g.colorMode(sess) - sess.WriteLines( - "", - 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", 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))), - ) - - t := &Table{Title: "Skills", Columns: []string{ - color.Render(mode, color.Parse("75"), "Skill"), - color.Render(mode, color.Parse("245"), "Abbr"), - color.Render(mode, color.Parse("230"), "Level"), - color.Render(mode, color.Parse("222"), "XP"), - color.Render(mode, color.Parse("179"), "XP to Next"), - }} + unicode := p.OptionBool("unicode") + + h, v, tl, tr, ml, mr, bl, br := boxGlyphs(unicode) + const boxW = 72 + const innerW = boxW - 4 + + var out []string + + border := func(l, r string) { + out = append(out, l+strings.Repeat(h, boxW-2)+r) + } + content := func(text string) { + out = append(out, v+" "+padLine(text, innerW)+" "+v) + } + sep := func() { border(ml, mr) } + gap := func() { content("") } + + padded := func(text string, width int) string { + pad := width - color.VisibleLen(text) + if pad < 0 { + pad = 0 + } + return text + strings.Repeat(" ", pad) + } + + skillDisplay := map[player.SkillName]string{ + player.Attack: "Attack", player.Strength: "Strength", player.Defense: "Defense", + player.Hitpoints: "Hitpoints", player.Ranged: "Ranged", player.Science: "Science", + player.Technology: "Technology", player.Assassin: "Assassin", + player.Fishing: "Fishing", player.Woodcutting: "Woodcutting", player.Mining: "Mining", + player.Hacking: "Hacking", player.Scavenging: "Scavenging", player.Farming: "Farming", + player.Thieving: "Thieving", + player.Cooking: "Cooking", player.Smithing: "Smithing", player.Crafting: "Crafting", + player.Fletching: "Fletching", player.Pharmacy: "Pharmacy", player.Construction: "Construction", + player.Firemaking: "Firemaking", + player.Agility: "Agility", + } + + type skillCat struct { + Name string + Skills []player.SkillName + } + categories := []skillCat{ + {"Combat", []player.SkillName{ + player.Attack, player.Strength, player.Defense, player.Hitpoints, + player.Ranged, player.Science, player.Technology, player.Assassin, + }}, + {"Gathering", []player.SkillName{ + player.Fishing, player.Woodcutting, player.Mining, player.Hacking, + player.Scavenging, player.Thieving, + }}, + {"Production", []player.SkillName{ + player.Cooking, player.Smithing, player.Crafting, player.Fletching, + player.Pharmacy, player.Construction, player.Farming, + }}, + {"Utility", []player.SkillName{ + player.Agility, player.Firemaking, + }}, + } + + // ── Top border + title ── + border(tl, tr) + + titleSep := strings.Repeat(h, 3) + title := titleSep + " D A T A P A D " + titleSep + titleVL := color.VisibleLen(title) + leftPad := (innerW - titleVL) / 2 + rightPad := innerW - titleVL - leftPad + content(strings.Repeat(" ", leftPad) + title + strings.Repeat(" ", rightPad)) + + // ── Vitals ── + sep() + + nameStr := g.colorize(sess, "player_name", p.Name) + clStr := color.Render(mode, color.Parse("230"), 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, "credits_pickup", formatInt(p.Credits)) + styleStr := color.Render(mode, color.Parse("75"), fmt.Sprintf("Style: %s", p.AttackStyle)) + invStr := fmt.Sprintf("Inv: %d/28 used", 28-p.FreeSlots()) + row2 := padded("Credits: "+creditsStr, 20) + padded(styleStr, 24) + invStr + content(row2) + + gap() + + hpStyle := hpBarStyle(p.HP, p.MaxHP()) + hpBarStr := RenderColoredBar(p.HP, p.MaxHP(), 26, unicode, hpStyle, mode) + hpFg := hpBarFg(p.HP, p.MaxHP()) + hpLine := fmt.Sprintf("HP [%s] %s / %s", + hpBarStr, + color.Render(mode, color.ColorSpec{Fg: hpFg}, fmt.Sprint(p.HP)), + color.Render(mode, color.Parse("230"), fmt.Sprint(p.MaxHP()))) + content(hpLine) + + batBarRaw := RenderBarF(p.Battery, p.MaxBattery(), 26, unicode) + batBarStr := color.Render(mode, color.ColorSpec{Fg: 45}, batBarRaw) + batLine := fmt.Sprintf("BAT [%s] %s / %s", + batBarStr, + color.Render(mode, color.ColorSpec{Fg: 45}, fmt.Sprintf("%.1f", p.Battery)), + color.Render(mode, color.Parse("230"), fmt.Sprintf("%.0f", p.MaxBattery()))) + content(batLine) + + gap() + + actionDesc := "Idle" + if as, ok := p.ActionState.(*ActionState); ok && as != nil { + if d := as.Description(); d != "" { + actionDesc = d + } + } + content("Action: " + actionDesc) + + // ── Skills ── + totalLevel := 0 for _, s := range player.AllSkills { + totalLevel += p.Level(s) + } + sep() + content(fmt.Sprintf("Skills %s Total Level %d", strings.Repeat(h, 2), totalLevel)) + gap() + + bullet := "\u25c6" + if !unicode { + bullet = "*" + } + + buildSkillCell := func(s player.SkillName) string { level := p.Level(s) - xp := p.Skills[s] - next := player.XPForNextLevel(xp) - t.Rows = append(t.Rows, []string{ - color.Render(mode, color.Parse("75"), string(s)), - color.Render(mode, color.Parse("245"), player.SkillAbbr[s]), - color.Render(mode, color.Parse("230"), strconv.Itoa(level)), - color.Render(mode, color.Parse("222"), strconv.Itoa(xp)), - color.Render(mode, color.Parse("179"), strconv.Itoa(next)), - }) - } - for _, line := range t.Render(p.OptionBool("unicode")) { - sess.WriteLine(line) + lvlColor := skillLevelColor(level) + name := padded(color.Render(mode, color.ColorSpec{Fg: 75}, 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) + } + + for _, cat := range categories { + content(" " + bullet + " " + cat.Name) + + skills := cat.Skills + for i := 0; i < len(skills); i += 2 { + left := buildSkillCell(skills[i]) + row := left + if i+1 < len(skills) { + right := buildSkillCell(skills[i+1]) + row += " " + right + } + content(" " + row) + } + gap() } + // ── Statistics ── + sep() + stats := p.Stats + statsLine := fmt.Sprintf("Rooms Explored: %d Kills: %d Deaths: %d", + len(stats.RoomsVisited), stats.TotalKills, stats.Deaths) + content(statsLine) + content(fmt.Sprintf("APS Nodes Found: %d", len(getKnownApsNodes(p)))) + + // ── Active Systems ── + if len(p.ActiveTechs) > 0 || len(p.ActiveBuffs) > 0 { + sep() + } if len(p.ActiveTechs) > 0 { - sess.WriteLine("") - sess.WriteLine("Active Tech:") + var techParts []string for _, id := range p.ActiveTechList() { def := GetTechDef(id) if def != nil { - sess.WriteLine(fmt.Sprintf(" %s %s", - color.Render(mode, color.Parse("82"), "[ON]"), - color.Render(mode, color.Parse("75"), def.Name), - )) + on := color.Render(mode, color.ColorSpec{Fg: 82}, "[ON]") + name := color.Render(mode, color.ColorSpec{Fg: 75}, def.Name) + techParts = append(techParts, on+" "+name) } } + content(strings.Join(techParts, " ")) } - if len(p.ActiveBuffs) > 0 { - sess.WriteLine("") - sess.WriteLine("Active Buffs:") for _, buff := range p.ActiveBuffs { - sess.WriteLine(fmt.Sprintf(" %s +%d%% %s (%d ticks)", - color.Render(mode, color.Parse("82"), "[BUFF]"), + buffLine := fmt.Sprintf("%s +%d%% %s (%d ticks remaining)", + color.Render(mode, color.ColorSpec{Fg: 82}, "[BUFF]"), buff.BonusPercent, - color.Render(mode, color.Parse("75"), buff.Stat), - buff.TicksLeft, - )) + color.Render(mode, color.ColorSpec{Fg: 75}, buff.Stat), + buff.TicksLeft) + content(buffLine) + } + } + + // ── Bottom border ── + border(bl, br) + + for _, line := range out { + sess.WriteLine(line) + } +} + +func boxGlyphs(unicode bool) (h, v, tl, tr, ml, mr, bl, br string) { + if unicode { + return "═", "║", "╔", "╗", "╠", "╣", "╚", "╝" + } + return "-", "|", "+", "+", "+", "+", "+", "+" +} + +func padLine(text string, width int) string { + pad := width - color.VisibleLen(text) + if pad < 0 { + pad = 0 + } + return text + strings.Repeat(" ", pad) +} + +func formatInt(n int) string { + if n < 1000 { + return strconv.Itoa(n) + } + s := strconv.Itoa(n) + var parts []string + for i := len(s); i > 0; i -= 3 { + start := i - 3 + if start < 0 { + start = 0 } + parts = append([]string{s[start:i]}, parts...) + } + return strings.Join(parts, ",") +} + +func hpBarStyle(current, max int) *BarStyle { + if max <= 0 { + max = 1 + } + pct := float64(current) / float64(max) + var fg int + switch { + case pct >= 0.7: + fg = 84 + case pct >= 0.4: + fg = 208 + default: + fg = 196 + } + return &BarStyle{FilledColor: fg, EmptyColor: 238, EmptyDim: true} +} + +func hpBarFg(current, max int) int { + if max <= 0 { + max = 1 + } + pct := float64(current) / float64(max) + switch { + case pct >= 0.7: + return 84 + case pct >= 0.4: + return 208 + default: + return 196 + } +} + +func skillLevelColor(level int) int { + switch { + case level >= 99: + return 196 + case level >= 80: + return 214 + case level >= 60: + return 208 + case level >= 40: + return 220 + default: + return 40 + } +} + +func xpProgressBar(xp int, width int, unicode bool) string { + currentLevel := player.LevelForXP(xp) + if currentLevel >= 99 { + fillRune, _ := barRunes(unicode) + return strings.Repeat(fillRune, width) + } + xpForLevel := player.XPForLevel(currentLevel) + xpForNext := player.XPForLevel(currentLevel + 1) + progress := xp - xpForLevel + needed := xpForNext - xpForLevel + if needed <= 0 { + needed = 1 + } + fillRune, emptyRune := barRunes(unicode) + filled := progress * width / needed + if filled > width { + filled = width + } + if filled < 0 { + filled = 0 } + return strings.Repeat(fillRune, filled) + strings.Repeat(emptyRune, width-filled) } diff --git a/internal/game/cmd_skills.go b/internal/game/cmd_skills.go new file mode 100644 index 0000000..9d2e247 --- /dev/null +++ b/internal/game/cmd_skills.go @@ -0,0 +1,40 @@ +package game + +import ( + "strconv" + + "thehouseoficarus/internal/color" + "thehouseoficarus/internal/net" + "thehouseoficarus/internal/player" +) + +func (g *Game) executeSkills(sess *net.Session, args []string, rawInput string) { + g.doSkills(sess) +} + +func (g *Game) doSkills(sess *net.Session) { + p := sess.Player + mode := g.colorMode(sess) + t := &Table{Title: "Skills", Columns: []string{ + color.Render(mode, color.Parse("75"), "Skill"), + color.Render(mode, color.Parse("245"), "Abbr"), + color.Render(mode, color.Parse("230"), "Level"), + color.Render(mode, color.Parse("222"), "XP"), + color.Render(mode, color.Parse("179"), "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("75"), string(s)), + color.Render(mode, color.Parse("245"), player.SkillAbbr[s]), + color.Render(mode, color.Parse("230"), strconv.Itoa(level)), + color.Render(mode, color.Parse("222"), strconv.Itoa(xp)), + color.Render(mode, color.Parse("179"), strconv.Itoa(next)), + }) + } + for _, line := range t.Render(p.OptionBool("unicode")) { + sess.WriteLine(line) + } +} diff --git a/internal/game/cmd_trigger_transport.go b/internal/game/cmd_trigger_transport.go index 2354c27..b745232 100644 --- a/internal/game/cmd_trigger_transport.go +++ b/internal/game/cmd_trigger_transport.go @@ -31,6 +31,7 @@ func (g *Game) triggerTransport(sess *net.Session, p *player.Player, mod *ModDef sess.WriteLine(fmt.Sprintf("\nYou activate %s...", mod.Name)) p.RoomID = mod.Destination + p.Stats.RecordRoomVisit(mod.Destination) if g.Hub != nil { g.Hub.LeaveRoom(sess) g.Hub.EnterRoom(sess, p.RoomID) diff --git a/internal/game/core_login_char.go b/internal/game/core_login_char.go index 84f146b..f0d6863 100644 --- a/internal/game/core_login_char.go +++ b/internal/game/core_login_char.go @@ -42,6 +42,7 @@ func (g *Game) handleNewCharName(sess *net.Session, input string) { p := player.New(name) p.RoomID = 1 + p.Stats.RecordRoomVisit(1) if err := g.AccountStore.SaveCharacter(p); err != nil { sess.WriteLine(fmt.Sprintf("Error creating character: %v", err)) @@ -89,6 +90,7 @@ func (g *Game) connectCharacter(sess *net.Session, name string) { } } + p.Stats.RecordRoomVisit(p.RoomID) g.World.SeedGroundItems(p.RoomID) g.seedRoomMobs(p.RoomID) g.seedRoomObjects(p.RoomID) diff --git a/internal/game/ui_bar.go b/internal/game/ui_bar.go new file mode 100644 index 0000000..ca0b721 --- /dev/null +++ b/internal/game/ui_bar.go @@ -0,0 +1,104 @@ +package game + +import ( + "math" + "strings" + + "thehouseoficarus/internal/color" +) + +type BarStyle struct { + FilledColor int + EmptyColor int + EmptyDim bool +} + +func RenderBar(current, max, width int, unicode bool) string { + if max <= 0 { + max = 1 + } + if current < 0 { + current = 0 + } + if current > max { + current = max + } + filled := int(math.Round(float64(current) * float64(width) / float64(max))) + if filled > width { + filled = width + } + if filled < 0 { + filled = 0 + } + + fillRune, emptyRune := barRunes(unicode) + return strings.Repeat(fillRune, filled) + strings.Repeat(emptyRune, width-filled) +} + +func RenderBarF(current, max float64, width int, unicode bool) string { + if max <= 0 { + max = 1 + } + ratio := current / max + if ratio > 1 { + ratio = 1 + } + if ratio < 0 { + ratio = 0 + } + filled := int(math.Round(ratio * float64(width))) + if filled > width { + filled = width + } + if filled < 0 { + filled = 0 + } + + fillRune, emptyRune := barRunes(unicode) + return strings.Repeat(fillRune, filled) + strings.Repeat(emptyRune, width-filled) +} + +func RenderColoredBar(current, max, width int, unicode bool, style *BarStyle, mode string) string { + if max <= 0 { + max = 1 + } + if current < 0 { + current = 0 + } + if current > max { + current = max + } + filled := int(math.Round(float64(current) * float64(width) / float64(max))) + if filled > width { + filled = width + } + if filled < 0 { + filled = 0 + } + + fillRune, emptyRune := barRunes(unicode) + fillStr := strings.Repeat(fillRune, filled) + emptyStr := strings.Repeat(emptyRune, width-filled) + + if style != nil && mode != "none" && mode != "" { + if style.FilledColor >= 0 { + fillStr = color.Render(mode, color.ColorSpec{Fg: style.FilledColor}, fillStr) + } + if style.EmptyColor >= 0 { + spec := color.ColorSpec{Fg: style.EmptyColor} + if style.EmptyDim { + spec.Dim = true + } + emptyStr = color.Render(mode, spec, emptyStr) + } + } + + return fillStr + emptyStr +} + +func barRunes(unicode bool) (fill, empty string) { + if unicode { + return "█", "░" + } + return "#", "." +} diff --git a/internal/game/ui_combat.go b/internal/game/ui_combat.go index 56de7e8..ef00fd5 100644 --- a/internal/game/ui_combat.go +++ b/internal/game/ui_combat.go @@ -1,12 +1,6 @@ package game -import ( - "math" - "strings" - - "thehouseoficarus/internal/color" - "thehouseoficarus/internal/net" -) +import "thehouseoficarus/internal/net" func (g *Game) hpBar(sess *net.Session, current, max int) string { if max <= 0 { @@ -20,16 +14,6 @@ func (g *Game) hpBar(sess *net.Session, current, max int) string { } 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: @@ -40,20 +24,8 @@ func (g *Game) hpBar(sess *net.Session, current, max int) string { 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() + unicode := sess.Player != nil && sess.Player.OptionBool("unicode") + style := &BarStyle{FilledColor: fgColor, EmptyColor: 238, EmptyDim: true} + bar := RenderColoredBar(current, max, 10, unicode, style, g.colorMode(sess)) + return "[" + bar + "]" } diff --git a/internal/player/player.go b/internal/player/player.go index 1ddb3ea..7953691 100644 --- a/internal/player/player.go +++ b/internal/player/player.go @@ -189,6 +189,7 @@ type Player struct { Sneaking bool `yaml:"-"` SneakNotified map[string]bool `yaml:"-"` ActiveBuffs []PotionBuff `yaml:"-"` + Stats PlayerStats `yaml:"stats"` } type PotionBuff struct { @@ -311,6 +312,10 @@ func New(name string) *Player { AttackStyle: Accurate, Prompt: "> ", RoomID: 0, + Stats: PlayerStats{ + RoomsVisited: make(map[int]bool), + MobKills: make(map[string]int), + }, } for _, s := range AllSkills { p.Skills[s] = 0 diff --git a/internal/player/stats.go b/internal/player/stats.go new file mode 100644 index 0000000..82b741c --- /dev/null +++ b/internal/player/stats.go @@ -0,0 +1,27 @@ +package player + +type PlayerStats struct { + RoomsVisited map[int]bool `yaml:"rooms_visited,omitempty"` + MobKills map[string]int `yaml:"mob_kills,omitempty"` + TotalKills int `yaml:"total_kills"` + Deaths int `yaml:"deaths"` +} + +func (s *PlayerStats) RecordRoomVisit(roomID int) { + if s.RoomsVisited == nil { + s.RoomsVisited = make(map[int]bool) + } + s.RoomsVisited[roomID] = true +} + +func (s *PlayerStats) RecordMobKill(mobDefID string) { + if s.MobKills == nil { + s.MobKills = make(map[string]int) + } + s.MobKills[mobDefID]++ + s.TotalKills++ +} + +func (s *PlayerStats) RecordDeath() { + s.Deaths++ +} |
