aboutsummaryrefslogtreecommitdiff
path: root/internal/game
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game')
-rw-r--r--internal/game/action_burn.go4
-rw-r--r--internal/game/action_gather.go9
-rw-r--r--internal/game/action_state.go7
-rw-r--r--internal/game/action_use.go2
-rw-r--r--internal/game/cmd_attack.go4
-rw-r--r--internal/game/cmd_look.go27
-rw-r--r--internal/game/cmd_map.go2
-rw-r--r--internal/game/cmd_option.go9
-rw-r--r--internal/game/game.go8
-rw-r--r--internal/game/map.go76
-rw-r--r--internal/game/map_test.go8
-rw-r--r--internal/game/table.go100
-rw-r--r--internal/game/tick.go4
13 files changed, 203 insertions, 57 deletions
diff --git a/internal/game/action_burn.go b/internal/game/action_burn.go
index 917a6fe..b16ba42 100644
--- a/internal/game/action_burn.go
+++ b/internal/game/action_burn.go
@@ -225,7 +225,7 @@ func (g *Game) advanceBurn(sess *net.Session, p *player.Player) {
g.CancelAction(p)
msg := fmt.Sprintf("You manage to get a fire going!")
- if xp > 0 && p.OptionBool("xpdrops") {
+ if xp > 0 && p.OptionBool("xp_drops") {
msg += fmt.Sprintf(" (+%dxp %s)", xp, player.SkillAbbr[player.Firemaking])
}
sess.WriteLine(msg)
@@ -282,7 +282,7 @@ func (g *Game) advanceStoke(sess *net.Session, p *player.Player) {
g.AccountStore.SaveCharacter(p)
msg := fmt.Sprintf("You throw %s onto the fire.", name)
- if xp > 0 && p.OptionBool("xpdrops") {
+ if xp > 0 && p.OptionBool("xp_drops") {
msg += fmt.Sprintf(" (+%dxp %s)", xp, player.SkillAbbr[player.Firemaking])
}
sess.WriteLine(msg)
diff --git a/internal/game/action_gather.go b/internal/game/action_gather.go
index 512be81..4e1e609 100644
--- a/internal/game/action_gather.go
+++ b/internal/game/action_gather.go
@@ -138,7 +138,12 @@ func (g *Game) startGather(sess *net.Session, p *player.Player, obj *object.Obje
}
}
}
- p.ActionState = &ActionState{Type: ActionGathering, TargetName: obj.Name, ToolName: toolName}
+ verb := cfg.Skill
+ switch cfg.Skill {
+ case "woodcutting":
+ verb = "chopping"
+ }
+ p.ActionState = &ActionState{Type: ActionGathering, TargetName: obj.Name, ToolName: toolName, Verb: verb}
data := map[string]any{
"behavior_id": obj.BehaviorID,
@@ -257,7 +262,7 @@ func (g *Game) advanceGather(sess *net.Session, p *player.Player) {
if msg == "" {
msg = fmt.Sprintf("You manage to get some %s.", itemName)
}
- if xp > 0 && p.OptionBool("xpdrops") {
+ if xp > 0 && p.OptionBool("xp_drops") {
msg += fmt.Sprintf(" (+%dxp %s)", xp, player.SkillAbbr[player.SkillName(cfg.Skill)])
}
sess.WriteLine(msg)
diff --git a/internal/game/action_state.go b/internal/game/action_state.go
index fba5319..8db4a7b 100644
--- a/internal/game/action_state.go
+++ b/internal/game/action_state.go
@@ -25,6 +25,7 @@ type ActionState struct {
TargetName string
ToolName string
Direction string
+ Verb string
}
func (a *ActionState) Description() string {
@@ -33,7 +34,11 @@ func (a *ActionState) Description() string {
}
switch a.Type {
case ActionGathering:
- return "mining a " + a.TargetName
+ desc := a.Verb + " a " + a.TargetName
+ if a.ToolName != "" {
+ desc += " with a " + a.ToolName
+ }
+ return desc
case ActionCombating:
return "fighting a " + a.TargetName
case ActionMoving:
diff --git a/internal/game/action_use.go b/internal/game/action_use.go
index 8987ee0..ec63723 100644
--- a/internal/game/action_use.go
+++ b/internal/game/action_use.go
@@ -108,7 +108,7 @@ func (g *Game) advanceUse(sess *net.Session, p *player.Player) {
itemName = def.Name
}
line := fmt.Sprintf("You make a %s.", itemName)
- if cfg.XP > 0 && p.OptionBool("xpdrops") {
+ if cfg.XP > 0 && p.OptionBool("xp_drops") {
line += fmt.Sprintf(" (+%dxp %s)", cfg.XP, player.SkillAbbr[player.SkillName(cfg.Skill)])
}
sess.WriteLine(line)
diff --git a/internal/game/cmd_attack.go b/internal/game/cmd_attack.go
index ef80189..ca5bc4c 100644
--- a/internal/game/cmd_attack.go
+++ b/internal/game/cmd_attack.go
@@ -215,7 +215,7 @@ func (g *Game) playerAttack(sess *net.Session, p *player.Player, mob *world.MobI
prefix := fmt.Sprintf(" You hit %s for %d damage.", mobName, dmg)
hpPart := fmt.Sprintf("[%d/%dhp]", mob.HP, mob.MaxHP)
line := fmt.Sprintf("%-*s %s", g.combatPadWidth, prefix, hpPart)
- if p.OptionBool("xpdrops") && len(gains) > 0 {
+ if p.OptionBool("xp_drops") && len(gains) > 0 {
var parts []string
for _, gain := range gains {
parts = append(parts, fmt.Sprintf("+%dxp %s", gain.XP, player.SkillAbbr[player.SkillName(gain.Skill)]))
@@ -462,7 +462,7 @@ func (g *Game) respawnMob(instanceID string) {
if g.Hub != nil {
for _, sess := range g.Hub.PlayersInRoom(homeRoom) {
- if p, ok := sess.Player.(*player.Player); ok && p.OptionBool("mobspawn") {
+ if p, ok := sess.Player.(*player.Player); ok && p.OptionBool("mob_spawn") {
sess.WriteLine(fmt.Sprintf("\n%s (level %d) spawns in the area.", mobDisplayName(inst, false), mobCombatLevel(inst)))
}
}
diff --git a/internal/game/cmd_look.go b/internal/game/cmd_look.go
index 3b81f80..7efb65a 100644
--- a/internal/game/cmd_look.go
+++ b/internal/game/cmd_look.go
@@ -25,20 +25,23 @@ func (g *Game) doLook(sess *net.Session) {
room.Name,
)
- mapWidth := p.OptionInt("max_room_description_width")
- if mapWidth <= 0 {
- mapWidth = 70
+ descWidth := p.OptionInt("room_desc_width")
+ if descWidth <= 0 {
+ descWidth = 70
}
- if p.OptionBool("tinymap") && mapWidth > 0 {
- descLines := wrapText(room.Description, mapWidth)
- mapLines := buildTinyMap(g, p.RoomID)
+ descLines := wrapText(room.Description, descWidth)
+ wroteDesc := false
+ if p.OptionString("tiny_map") != "off" {
+ mapLines := buildTinyMap(g, p.RoomID, mapGlyphsForPlayer(p.OptionBool("unicode")))
if len(mapLines) == 7 {
g.writeLookSideBySide(sess, p, descLines, mapLines)
- } else {
- sess.WriteLine(room.Description)
+ wroteDesc = true
+ }
+ }
+ if !wroteDesc {
+ for _, line := range descLines {
+ sess.WriteLine(line)
}
- } else {
- sess.WriteLine(room.Description)
}
mobs := g.MobStore.MobsInRoom(p.RoomID)
@@ -574,8 +577,8 @@ func (g *Game) writeLookSideBySide(sess *net.Session, p *player.Player, descLine
if len(mapLines) > total {
total = len(mapLines)
}
- leftMap := p.OptionBool("left_tinymap")
- mapWidth := p.OptionInt("max_room_description_width")
+ leftMap := p.OptionString("tiny_map") == "left"
+ mapWidth := p.OptionInt("room_desc_width")
if mapWidth <= 0 {
mapWidth = 70
}
diff --git a/internal/game/cmd_map.go b/internal/game/cmd_map.go
index e0413d3..f4ec11c 100644
--- a/internal/game/cmd_map.go
+++ b/internal/game/cmd_map.go
@@ -16,7 +16,7 @@ func (g *Game) doMap(sess *net.Session) {
mapHeight = 5
}
- lines := buildFullMap(g, p.RoomID, mapWidth, mapHeight)
+ lines := buildFullMap(g, p.RoomID, mapWidth, mapHeight, mapGlyphsForPlayer(p.OptionBool("unicode")))
mode := p.OptionString("mappadding")
if mode == "none" || mode == "x" {
diff --git a/internal/game/cmd_option.go b/internal/game/cmd_option.go
index 49085db..9302baa 100644
--- a/internal/game/cmd_option.go
+++ b/internal/game/cmd_option.go
@@ -14,11 +14,16 @@ func (g *Game) doOption(sess *net.Session, input string) {
if input == "" {
sess.WriteLine("")
- sess.WriteLine(fmt.Sprintf(" %-14s %-10s %-22s %s", "Option", "Value", "Valid", "Description"))
+ table := &Table{
+ Columns: []string{"Option", "Value", "Valid", "Description"},
+ }
for _, def := range player.OptionDefs {
val := formatOptionValue(p, &def)
valid := formatValidValues(&def)
- sess.WriteLine(fmt.Sprintf(" %-14s %-10s %-22s %s", def.Name, val, valid, def.Description))
+ table.Rows = append(table.Rows, []string{def.Name, val, valid, def.Description})
+ }
+ for _, line := range table.Render(p.OptionBool("unicode")) {
+ sess.WriteLine(line)
}
return
}
diff --git a/internal/game/game.go b/internal/game/game.go
index 37a1f3f..7447215 100644
--- a/internal/game/game.go
+++ b/internal/game/game.go
@@ -177,10 +177,9 @@ func (g *Game) handleGameCommand(sess *net.Session, input string) {
Args: strings.Join(parts[1:], " "),
Timestamp: time.Now(),
})
- if !p.OptionBool("queue_actions_silently") {
+ if !p.OptionBool("queue_silently") {
sess.WriteLine(fmt.Sprintf("\nYou prepare to %s.", cmd))
}
- sess.Write("\r\n> ")
return
}
@@ -191,10 +190,9 @@ func (g *Game) handleGameCommand(sess *net.Session, input string) {
Timestamp: time.Now(),
}
g.cancelRest(p.Name)
- if !p.OptionBool("queue_actions_silently") {
+ if !p.OptionBool("queue_silently") {
sess.WriteLine(fmt.Sprintf("\nYou prepare to %s.", cmd))
}
- sess.Write("\r\n> ")
}
func (g *Game) executeCommand(sess *net.Session, cmd string, args []string, rawInput string) {
@@ -401,6 +399,7 @@ func (g *Game) ProcessQueuedCommands() {
if len(parts) > 0 {
g.executeCommand(qc.Session, parts[0], parts[1:], qc.Command+" "+qc.Args)
}
+ qc.Session.Write("\r\n> ")
}
delete(g.freeQueue, p.Name)
}
@@ -418,6 +417,7 @@ func (g *Game) ProcessQueuedCommands() {
if len(parts) > 0 {
g.executeCommand(qc.Session, parts[0], parts[1:], qc.Command+" "+qc.Args)
}
+ qc.Session.Write("\r\n> ")
}
g.activeQueue = make(map[string]*QueuedCommand)
diff --git a/internal/game/map.go b/internal/game/map.go
index e7738bd..8b14e25 100644
--- a/internal/game/map.go
+++ b/internal/game/map.go
@@ -6,6 +6,30 @@ import (
"thirdcollapse/internal/world"
)
+type mapGlyphs struct {
+ topLeft, topRight rune
+ bottomLeft, bottomRight rune
+ side rune
+ topFill rune
+ connectorH, connectorV rune
+ upArrow, downArrow rune
+}
+
+func mapGlyphsForPlayer(unicode bool) mapGlyphs {
+ if unicode {
+ return mapGlyphs{
+ topLeft: '╔', topRight: '╗', bottomLeft: '╚', bottomRight: '╝',
+ side: '║', topFill: '═', connectorH: '─', connectorV: '│',
+ upArrow: '↑', downArrow: '↓',
+ }
+ }
+ return mapGlyphs{
+ topLeft: '.', topRight: '.', bottomLeft: ':', bottomRight: ':',
+ side: ':', topFill: '.', connectorH: '-', connectorV: '|',
+ upArrow: '^', downArrow: 'v',
+ }
+}
+
type mapGraph struct {
posToRoom map[[2]int]int
roomToPos map[int][2]int
@@ -62,8 +86,8 @@ func buildGraph(g *Game, startRoomID int) *mapGraph {
return mg
}
-func buildTinyMap(g *Game, roomID int) []string {
- mg := buildGraph(g, roomID)
+func buildTinyMap(g *Game, roomID int, mg mapGlyphs) []string {
+ bg := buildGraph(g, roomID)
grid := make([][]rune, 5)
for i := range grid {
@@ -76,7 +100,7 @@ func buildTinyMap(g *Game, roomID int) []string {
for y := -1; y <= 1; y++ {
for x := -1; x <= 1; x++ {
pos := [2]int{x, y}
- rid, ok := mg.posToRoom[pos]
+ rid, ok := bg.posToRoom[pos]
if !ok {
continue
}
@@ -94,13 +118,13 @@ func buildTinyMap(g *Game, roomID int) []string {
for x := -1; x <= 0; x++ {
leftPos := [2]int{x, y}
rightPos := [2]int{x + 1, y}
- leftRoom, leftOK := mg.posToRoom[leftPos]
- rightRoom, rightOK := mg.posToRoom[rightPos]
+ leftRoom, leftOK := bg.posToRoom[leftPos]
+ rightRoom, rightOK := bg.posToRoom[rightPos]
if !leftOK || !rightOK {
continue
}
if exitsConnect(g, leftRoom, rightRoom, world.East, world.West) {
- grid[(y+1)*2][(x+1)*2+1] = '─'
+ grid[(y+1)*2][(x+1)*2+1] = mg.connectorH
}
}
}
@@ -109,13 +133,13 @@ func buildTinyMap(g *Game, roomID int) []string {
for x := -1; x <= 1; x++ {
topPos := [2]int{x, y}
bottomPos := [2]int{x, y + 1}
- topRoom, topOK := mg.posToRoom[topPos]
- bottomRoom, bottomOK := mg.posToRoom[bottomPos]
+ topRoom, topOK := bg.posToRoom[topPos]
+ bottomRoom, bottomOK := bg.posToRoom[bottomPos]
if !topOK || !bottomOK {
continue
}
if exitsConnect(g, topRoom, bottomRoom, world.South, world.North) {
- grid[(y+1)*2+1][(x+1)*2] = '│'
+ grid[(y+1)*2+1][(x+1)*2] = mg.connectorV
}
}
}
@@ -123,19 +147,21 @@ func buildTinyMap(g *Game, roomID int) []string {
cur, _ := loadRoom(g, roomID)
if cur != nil {
if _, ok := exitTarget(cur, world.Up); ok {
- grid[1][3] = '↑'
+ grid[1][3] = mg.upArrow
}
if _, ok := exitTarget(cur, world.Down); ok {
- grid[3][1] = '↓'
+ grid[3][1] = mg.downArrow
}
}
+ topFill := strings.Repeat(string(mg.topFill), 5)
lines := make([]string, 7)
- lines[0] = "╔═════╗"
+ lines[0] = string(mg.topLeft) + topFill + string(mg.topRight)
for row := 0; row < 5; row++ {
- lines[row+1] = "║" + string(grid[row]) + "║"
+ lines[row+1] = string(mg.side) + string(grid[row]) + string(mg.side)
}
- lines[6] = "╚═════╝"
+ botFill := strings.Repeat(string(mg.topFill), 5)
+ lines[6] = string(mg.bottomLeft) + botFill + string(mg.bottomRight)
return lines
}
@@ -152,10 +178,8 @@ func exitsConnect(g *Game, room1, room2 int, dir12, dir21 world.ExitDir) bool {
if !ok {
return false
}
- if id, ok := exitTarget(r2, dir21); ok && id == room1 {
- return true
- }
- return false
+ id, ok := exitTarget(r2, dir21)
+ return ok && id == room1
}
func exitTarget(room *world.Room, dir world.ExitDir) (int, bool) {
@@ -234,8 +258,8 @@ func leftTrimCommon(lines []string) []string {
return result
}
-func buildFullMap(g *Game, roomID, mapWidth, mapHeight int) []string {
- mg := buildGraph(g, roomID)
+func buildFullMap(g *Game, roomID, mapWidth, mapHeight int, mg mapGlyphs) []string {
+ bg := buildGraph(g, roomID)
grid := make([][]rune, mapHeight)
for i := range grid {
@@ -248,7 +272,7 @@ func buildFullMap(g *Game, roomID, mapWidth, mapHeight int) []string {
cx := mapWidth / 2
cy := mapHeight / 2
- for pos, rid := range mg.posToRoom {
+ for pos, rid := range bg.posToRoom {
gr := cy + pos[1]*2
gc := cx + pos[0]*2
if gr < 0 || gr >= mapHeight || gc < 0 || gc >= mapWidth {
@@ -261,25 +285,25 @@ func buildFullMap(g *Game, roomID, mapWidth, mapHeight int) []string {
}
}
- for pos, rid := range mg.posToRoom {
+ for pos, rid := range bg.posToRoom {
x, y := pos[0], pos[1]
- if rightID, ok := mg.posToRoom[[2]int{x + 1, y}]; ok {
+ if rightID, ok := bg.posToRoom[[2]int{x + 1, y}]; ok {
if exitsConnect(g, rid, rightID, world.East, world.West) {
gr := cy + y*2
gc := cx + x*2 + 1
if gr >= 0 && gr < mapHeight && gc >= 0 && gc < mapWidth {
- grid[gr][gc] = '─'
+ grid[gr][gc] = mg.connectorH
}
}
}
- if bottomID, ok := mg.posToRoom[[2]int{x, y + 1}]; ok {
+ if bottomID, ok := bg.posToRoom[[2]int{x, y + 1}]; ok {
if exitsConnect(g, rid, bottomID, world.South, world.North) {
gr := cy + y*2 + 1
gc := cx + x*2
if gr >= 0 && gr < mapHeight && gc >= 0 && gc < mapWidth {
- grid[gr][gc] = '│'
+ grid[gr][gc] = mg.connectorV
}
}
}
diff --git a/internal/game/map_test.go b/internal/game/map_test.go
index 3453117..8c82f4a 100644
--- a/internal/game/map_test.go
+++ b/internal/game/map_test.go
@@ -12,6 +12,8 @@ func TestBuildTinyMap(t *testing.T) {
World: world.New("../../data"),
}
+ mg := mapGlyphsForPlayer(true)
+
tests := []struct {
name string
roomID int
@@ -49,7 +51,7 @@ func TestBuildTinyMap(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- lines := buildTinyMap(g, tt.roomID)
+ lines := buildTinyMap(g, tt.roomID, mg)
if len(lines) != 7 {
t.Fatalf("expected 7 lines, got %d", len(lines))
}
@@ -97,6 +99,8 @@ func TestBuildFullMap(t *testing.T) {
World: world.New("../../data"),
}
+ mg := mapGlyphsForPlayer(true)
+
tests := []struct {
name string
roomID int
@@ -111,7 +115,7 @@ func TestBuildFullMap(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- lines := buildFullMap(g, tt.roomID, tt.width, tt.height)
+ lines := buildFullMap(g, tt.roomID, tt.width, tt.height, mg)
if len(lines) != tt.height {
t.Errorf("expected %d lines, got %d", tt.height, len(lines))
}
diff --git a/internal/game/table.go b/internal/game/table.go
new file mode 100644
index 0000000..e3dad97
--- /dev/null
+++ b/internal/game/table.go
@@ -0,0 +1,100 @@
+package game
+
+import (
+ "fmt"
+ "strings"
+)
+
+type tableGlyphs struct {
+ topLeft, topSep, topRight rune
+ side, colSep rune
+ sepLeft, sepCross, sepRight rune
+ botLeft, botSep, botRight rune
+ fillH, fillHSep rune
+}
+
+func tableGlyphSet(unicode bool) tableGlyphs {
+ if unicode {
+ return tableGlyphs{
+ topLeft: '╔', topSep: '╤', topRight: '╗', side: '║', colSep: '│',
+ sepLeft: '╟', sepCross: '┼', sepRight: '╢',
+ botLeft: '╚', botSep: '╧', botRight: '╝', fillH: '═', fillHSep: '─',
+ }
+ }
+ return tableGlyphs{
+ topLeft: '+', topSep: '+', topRight: '+', side: '|', colSep: '|',
+ sepLeft: '+', sepCross: '+', sepRight: '+',
+ botLeft: '+', botSep: '+', botRight: '+', fillH: '-', fillHSep: '-',
+ }
+}
+
+type Table struct {
+ Columns []string
+ Rows [][]string
+}
+
+func (t *Table) Render(unicode bool) []string {
+ g := tableGlyphSet(unicode)
+
+ nCols := len(t.Columns)
+ if nCols == 0 {
+ return nil
+ }
+
+ colWidths := make([]int, nCols)
+ for i, h := range t.Columns {
+ colWidths[i] = len(h)
+ }
+ for _, row := range t.Rows {
+ for i, cell := range row {
+ if i >= nCols {
+ break
+ }
+ if len(cell) > colWidths[i] {
+ colWidths[i] = len(cell)
+ }
+ }
+ }
+
+ makeSep := func(left, cross, right rune, fill rune) string {
+ var b strings.Builder
+ b.WriteRune(left)
+ for i := 0; i < nCols; i++ {
+ if i > 0 {
+ b.WriteRune(cross)
+ }
+ b.WriteString(strings.Repeat(string(fill), colWidths[i]+2))
+ }
+ b.WriteRune(right)
+ return b.String()
+ }
+
+ makeRow := func(cells []string) string {
+ var b strings.Builder
+ b.WriteRune(g.side)
+ for i := 0; i < nCols; i++ {
+ if i > 0 {
+ b.WriteRune(g.colSep)
+ }
+ cell := ""
+ if i < len(cells) {
+ cell = cells[i]
+ }
+ b.WriteString(fmt.Sprintf(" %-*s ", colWidths[i], cell))
+ }
+ b.WriteRune(g.side)
+ return b.String()
+ }
+
+ var out []string
+
+ out = append(out, makeSep(g.topLeft, g.topSep, g.topRight, g.fillH))
+ out = append(out, makeRow(t.Columns))
+ out = append(out, makeSep(g.sepLeft, g.sepCross, g.sepRight, g.fillHSep))
+ for _, row := range t.Rows {
+ out = append(out, makeRow(row))
+ }
+ out = append(out, makeSep(g.botLeft, g.botSep, g.botRight, g.fillH))
+
+ return out
+}
diff --git a/internal/game/tick.go b/internal/game/tick.go
index 4d037ec..767109e 100644
--- a/internal/game/tick.go
+++ b/internal/game/tick.go
@@ -126,14 +126,14 @@ func (g *Game) WanderTick() {
if !ok {
continue
}
- if p.RoomID == m.fromRoom && p.OptionBool("mobleave") {
+ if p.RoomID == m.fromRoom && p.OptionBool("mob_leave") {
if m.level > 0 {
sess.WriteLine(fmt.Sprintf("\n%s (level %d) leaves.", m.name, m.level))
} else {
sess.WriteLine(fmt.Sprintf("\n%s moves away.", m.name))
}
}
- if p.RoomID == m.toRoom && p.OptionBool("mobenter") {
+ if p.RoomID == m.toRoom && p.OptionBool("mob_enter") {
if m.level > 0 {
sess.WriteLine(fmt.Sprintf("\n%s (level %d) enters.", m.name, m.level))
} else {