aboutsummaryrefslogtreecommitdiff
path: root/internal/game/cmd_look.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-22 04:24:11 -0400
committerhistoria <[not public]>2026-06-22 04:24:11 -0400
commit5b9a75a1520a198c9c6b1f1f55e05c9f4aab5629 (patch)
tree18f500ca3492ba206a3f66e2f01146b05a1675b2 /internal/game/cmd_look.go
parent7f07c7236578e1a7bcef0282ff9d7f5bf8c7c045 (diff)
downloadthehouseoficarus-5b9a75a1520a198c9c6b1f1f55e05c9f4aab5629.tar.gz
feat: map now only reveals with exploration. players can set custom map symbols.
Diffstat (limited to 'internal/game/cmd_look.go')
-rw-r--r--internal/game/cmd_look.go579
1 files changed, 298 insertions, 281 deletions
diff --git a/internal/game/cmd_look.go b/internal/game/cmd_look.go
index 903b32d..98d48fd 100644
--- a/internal/game/cmd_look.go
+++ b/internal/game/cmd_look.go
@@ -23,10 +23,29 @@ func (g *Game) doLook(sess *net.Session) {
}
g.showRoomName(sess, p, room)
- g.showRoomDescription(sess, p, room)
- g.showRoomObjects(sess, p, room)
- g.showRoomMobs(sess, p, room)
- g.showGroundItems(sess, p, room)
+
+ var mapLines []string
+ if p.OptionString("tiny_map") != "off" {
+ mapLines = buildTinyMap(g, sess, p.RoomID, mapGlyphsForPlayer(p.OptionBool("unicode")))
+ if len(mapLines) != 7 {
+ mapLines = nil
+ }
+ }
+
+ var content []string
+ content = append(content, g.showRoomDescription(sess, p, room)...)
+ content = append(content, g.showRoomObjects(sess, p, room)...)
+ content = append(content, g.showRoomMobs(sess, p, room)...)
+ content = append(content, g.showGroundItems(sess, p, room)...)
+
+ if len(mapLines) == 7 {
+ g.writeLookSideBySide(sess, p, content, mapLines)
+ } else {
+ for _, line := range content {
+ sess.WriteLine(line)
+ }
+ }
+
g.showRoomExits(sess, p, room)
g.showRoomPlayers(sess, p, room)
}
@@ -38,7 +57,7 @@ func (g *Game) showRoomName(sess *net.Session, p *player.Player, room *world.Roo
)
}
-func (g *Game) showRoomDescription(sess *net.Session, p *player.Player, room *world.Room) {
+func (g *Game) showRoomDescription(sess *net.Session, p *player.Player, room *world.Room) []string {
descWidth := p.OptionInt("room_desc_width")
if descWidth <= 0 {
descWidth = 70
@@ -46,319 +65,316 @@ func (g *Game) showRoomDescription(sess *net.Session, p *player.Player, room *wo
rawLines := wrapText(room.Description, descWidth)
mode := g.colorMode(sess)
roomDescSpec := g.resolveColor(sess, "room_desc")
- descLines := make([]string, len(rawLines))
+ lines := make([]string, len(rawLines))
for i, l := range rawLines {
- descLines[i] = color.ExpandTagsDefault(mode, roomDescSpec, l)
- }
- 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)
- wroteDesc = true
- }
- }
- if !wroteDesc {
- for _, line := range descLines {
- sess.WriteLine(line)
- }
+ lines[i] = color.ExpandTagsDefault(mode, roomDescSpec, l)
}
+ return lines
}
-func (g *Game) showRoomMobs(sess *net.Session, p *player.Player, room *world.Room) {
+func (g *Game) showRoomMobs(sess *net.Session, p *player.Player, room *world.Room) []string {
mobs := g.MobStore.MobsInRoom(p.RoomID)
- if len(mobs) > 0 {
- sort.Slice(mobs, func(i, j int) bool {
- iDamaged := mobs[i].HP < mobs[i].MaxHP
- jDamaged := mobs[j].HP < mobs[j].MaxHP
- if iDamaged != jDamaged {
- return iDamaged
- }
- return mobs[i].InstanceID < mobs[j].InstanceID
- })
- sess.WriteLine("")
- playerLevel := p.CombatLevel()
- for _, m := range mobs {
- hp := ""
- if 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) {
- def, err := g.MobStore.LoadDef(m.DefID)
- if err == nil && len(def.CombatDescriptions) > 0 {
- target := combat.GetMobTarget(m.InstanceID)
- pattern := def.CombatDescriptions[randInt(len(def.CombatDescriptions))]
- desc = " " + fmt.Sprintf(pattern, target)
- }
- } else if m.IdleDescription != "" {
- desc = fmt.Sprintf(" %s", m.IdleDescription)
- }
- displayName := m.Name
- if !m.Unique {
- displayName = "A " + m.Name
- }
- mobColor := "mob"
- if m.Protected {
- 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, mobColor, displayName), levelStr, hp, desc))
+ if len(mobs) == 0 {
+ return nil
+ }
+ sort.Slice(mobs, func(i, j int) bool {
+ iDamaged := mobs[i].HP < mobs[i].MaxHP
+ jDamaged := mobs[j].HP < mobs[j].MaxHP
+ if iDamaged != jDamaged {
+ return iDamaged
+ }
+ return mobs[i].InstanceID < mobs[j].InstanceID
+ })
+ var lines []string
+ lines = append(lines, "")
+ playerLevel := p.CombatLevel()
+ for _, m := range mobs {
+ hp := ""
+ if 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) {
+ def, err := g.MobStore.LoadDef(m.DefID)
+ if err == nil && len(def.CombatDescriptions) > 0 {
+ target := combat.GetMobTarget(m.InstanceID)
+ pattern := def.CombatDescriptions[randInt(len(def.CombatDescriptions))]
+ desc = " " + fmt.Sprintf(pattern, target)
+ }
+ } else if m.IdleDescription != "" {
+ desc = fmt.Sprintf(" %s", m.IdleDescription)
+ }
+ displayName := m.Name
+ if !m.Unique {
+ displayName = "A " + m.Name
+ }
+ mobColor := "mob"
+ if m.Protected {
+ mobColor = "protected_mob"
}
+ mobLevel := mobCombatLevel(m)
+ levelStr := g.levelColorize(sess, playerLevel, mobLevel, fmt.Sprintf("(level %d)", mobLevel))
+ lines = append(lines, fmt.Sprintf("%s %s%s%s", g.colorize(sess, mobColor, displayName), levelStr, hp, desc))
}
+ return lines
}
-func (g *Game) showRoomObjects(sess *net.Session, p *player.Player, room *world.Room) {
+func (g *Game) showRoomObjects(sess *net.Session, p *player.Player, room *world.Room) []string {
objs := g.World.AllObjInstances(p.RoomID)
- if len(objs) > 0 {
- sess.WriteLine("")
- grouped := make(map[string]int)
- var order []string
- for _, o := range objs {
- if _, ok := grouped[o.DefID]; !ok {
- order = append(order, o.DefID)
- }
- grouped[o.DefID]++
+ if len(objs) == 0 {
+ return g.showFarmPatches(sess, p)
+ }
+ var lines []string
+ lines = append(lines, "")
+ grouped := make(map[string]int)
+ var order []string
+ for _, o := range objs {
+ if _, ok := grouped[o.DefID]; !ok {
+ order = append(order, o.DefID)
+ }
+ grouped[o.DefID]++
+ }
+ for _, objID := range order {
+ count := grouped[objID]
+ def, err := g.ObjectStore.Load(objID)
+ if err != nil {
+ continue
}
- for _, objID := range order {
- count := grouped[objID]
- def, err := g.ObjectStore.Load(objID)
- if err != nil {
- continue
- }
- if def.Hidden {
- continue
- }
+ if def.Hidden {
+ continue
+ }
+
+ if farmPatchDefIDs[objID] {
+ continue
+ }
- if farmPatchDefIDs[objID] {
+ type instInfo struct {
+ idx int
+ depleted bool
+ sharedMax int
+ sharedCur int
+ respawnIn int
+ quality int
+ }
+ var instances []instInfo
+ for i := 0; i < count; i++ {
+ st := g.World.GetObjState(p.RoomID, objID, i)
+ if st == nil {
continue
}
+ instances = append(instances, instInfo{
+ idx: i + 1,
+ depleted: st.Depleted,
+ sharedMax: int(st.SharedMax),
+ sharedCur: st.SharedTimer,
+ respawnIn: int(st.DepleteTimer),
+ quality: int(st.Quality),
+ })
+ }
+ multi := len(instances) > 1
+ showTimers := p.OptionBool("depletion")
+
+ var freshIdxs []int
+ var timed, depleted []instInfo
+ for _, ins := range instances {
+ if ins.depleted {
+ depleted = append(depleted, ins)
+ } else if ins.sharedMax > 0 && ins.sharedCur < ins.sharedMax {
+ timed = append(timed, ins)
+ } else {
+ freshIdxs = append(freshIdxs, ins.idx)
+ }
+ }
- type instInfo struct {
- idx int
- depleted bool
- sharedMax int
- sharedCur int
- respawnIn int
- quality int
+ roomDesc := def.InRoomDescription
+ if roomDesc != "" {
+ roomDesc = color.ExpandTags(g.colorMode(sess), roomDesc)
+ }
+ coloredName := g.objColorize(sess, def, def.Name)
+ coloredPlural := g.objColorize(sess, def, def.Name+"s")
+
+ if len(freshIdxs) > 0 {
+ var line string
+ if roomDesc != "" {
+ line = roomDesc
+ } else if len(freshIdxs) == 1 {
+ line = fmt.Sprintf("A %s is here.", coloredName)
+ } else {
+ line = fmt.Sprintf("%d %s are here.", len(freshIdxs), coloredPlural)
}
- var instances []instInfo
- for i := 0; i < count; i++ {
- st := g.World.GetObjState(p.RoomID, objID, i)
- if st == nil {
- continue
- }
- instances = append(instances, instInfo{
- idx: i + 1,
- depleted: st.Depleted,
- sharedMax: int(st.SharedMax),
- sharedCur: st.SharedTimer,
- respawnIn: int(st.DepleteTimer),
- quality: int(st.Quality),
- })
+ var suffix string
+ if multi && (len(timed) > 0 || len(depleted) > 0) {
+ suffix = fmt.Sprintf(" [%s]", joinInts(freshIdxs))
}
- multi := len(instances) > 1
- showTimers := p.OptionBool("depletion")
-
- var freshIdxs []int
- var timed, depleted []instInfo
- for _, ins := range instances {
- if ins.depleted {
- depleted = append(depleted, ins)
- } else if ins.sharedMax > 0 && ins.sharedCur < ins.sharedMax {
- timed = append(timed, ins)
- } else {
- freshIdxs = append(freshIdxs, ins.idx)
- }
+ qualityTimer := ""
+ if showTimers && len(instances) > 0 && instances[0].quality > 0 {
+ qualityTimer = fmt.Sprintf(" (Burning for %d more ticks)", instances[0].quality)
}
+ lines = append(lines, fmt.Sprintf("%s%s%s", line, suffix, qualityTimer))
+ }
- roomDesc := def.InRoomDescription
+ for _, ins := range timed {
+ var line string
if roomDesc != "" {
- roomDesc = color.ExpandTags(g.colorMode(sess), roomDesc)
+ line = roomDesc
+ } else {
+ line = fmt.Sprintf("A %s is here.", coloredName)
}
- coloredName := g.objColorize(sess, def, def.Name)
- coloredPlural := g.objColorize(sess, def, def.Name+"s")
-
- if len(freshIdxs) > 0 {
- var line string
- if roomDesc != "" {
- line = roomDesc
- } else if len(freshIdxs) == 1 {
- line = fmt.Sprintf("A %s is here.", coloredName)
- } else {
- line = fmt.Sprintf("%d %s are here.", len(freshIdxs), coloredPlural)
- }
- var suffix string
- if multi && (len(timed) > 0 || len(depleted) > 0) {
- suffix = fmt.Sprintf(" [%s]", joinInts(freshIdxs))
- }
- qualityTimer := ""
- if showTimers && len(instances) > 0 && instances[0].quality > 0 {
- qualityTimer = fmt.Sprintf(" (Burning for %d more ticks)", instances[0].quality)
- }
- sess.WriteLine(fmt.Sprintf("%s%s%s", line, suffix, qualityTimer))
+ tag := ""
+ if multi {
+ tag = fmt.Sprintf(" [%d]", ins.idx)
}
-
- for _, ins := range timed {
- var line string
- if roomDesc != "" {
- line = roomDesc
- } else {
- line = fmt.Sprintf("A %s is here.", coloredName)
- }
- tag := ""
- if multi {
- tag = fmt.Sprintf(" [%d]", ins.idx)
- }
- timer := ""
- if showTimers {
- timer = fmt.Sprintf(" (despawn: %d/%d)", ins.sharedCur, ins.sharedMax)
- }
- sess.WriteLine(fmt.Sprintf("%s%s%s", line, tag, timer))
+ timer := ""
+ if showTimers {
+ timer = fmt.Sprintf(" (despawn: %d/%d)", ins.sharedCur, ins.sharedMax)
}
+ lines = append(lines, fmt.Sprintf("%s%s%s", line, tag, timer))
+ }
- for _, ins := range depleted {
- var line string
- if roomDesc != "" {
- line = roomDesc
- } else {
- line = fmt.Sprintf("A %s is here.", coloredName)
- }
- tag := ""
- if multi {
- tag = fmt.Sprintf(" [%d]", ins.idx)
- }
- timer := ""
- if showTimers {
- timer = fmt.Sprintf(" (respawns in %d ticks)", ins.respawnIn)
- }
- sess.WriteLine(fmt.Sprintf("%s (depleted)%s%s", line, tag, timer))
+ for _, ins := range depleted {
+ var line string
+ if roomDesc != "" {
+ line = roomDesc
+ } else {
+ line = fmt.Sprintf("A %s is here.", coloredName)
+ }
+ tag := ""
+ if multi {
+ tag = fmt.Sprintf(" [%d]", ins.idx)
+ }
+ timer := ""
+ if showTimers {
+ timer = fmt.Sprintf(" (respawns in %d ticks)", ins.respawnIn)
}
+ lines = append(lines, fmt.Sprintf("%s (depleted)%s%s", line, tag, timer))
}
}
- g.showFarmPatches(sess, p)
+ lines = append(lines, g.showFarmPatches(sess, p)...)
+ return lines
}
-func (g *Game) showGroundItems(sess *net.Session, p *player.Player, room *world.Room) {
+func (g *Game) showGroundItems(sess *net.Session, p *player.Player, room *world.Room) []string {
ground := g.World.GroundItemsDetailed(p.RoomID)
- if len(ground) > 0 {
- showDespawn := p.OptionBool("despawn")
- showReserve := p.OptionBool("reserve")
+ if len(ground) == 0 {
+ return nil
+ }
+ showDespawn := p.OptionBool("despawn")
+ showReserve := p.OptionBool("reserve")
- type displayLine struct {
- name string
- quantity int
- colorName string
- annotation string
- }
+ type displayLine struct {
+ name string
+ quantity int
+ colorName string
+ annotation string
+ }
- type groupKey struct {
- itemID string
- despawnTimer int
- }
+ type groupKey struct {
+ itemID string
+ despawnTimer int
+ }
- var lines []displayLine
- groups := make(map[groupKey]*displayLine)
- var groupOrder []groupKey
+ var lines []displayLine
+ groups := make(map[groupKey]*displayLine)
+ var groupOrder []groupKey
- for _, info := range ground {
- def, err := g.ItemStore.Load(info.ItemID)
- name := info.ItemID
- if err == nil {
- name = def.Name
- }
- coloredName := g.itemColorize(sess, def, name)
+ for _, info := range ground {
+ def, err := g.ItemStore.Load(info.ItemID)
+ name := info.ItemID
+ if err == nil {
+ name = def.Name
+ }
+ coloredName := g.itemColorize(sess, def, name)
- if info.ReservedFor != "" {
- var parts []string
- if showReserve {
- parts = append(parts, fmt.Sprintf("reserved for %s %dt", info.ReservedFor, info.ReserveTimer))
- } else {
- parts = append(parts, "reserved")
- }
- if showDespawn && !info.IsSpawn {
- parts = append(parts, fmt.Sprintf("despawns %dt", info.DespawnTimer))
- }
- annotation := ""
- if len(parts) > 0 {
- annotation = fmt.Sprintf(" (%s)", strings.Join(parts, ", "))
- }
- lines = append(lines, displayLine{
- name: name,
- quantity: info.Quantity,
- colorName: coloredName,
- annotation: annotation,
- })
- continue
+ if info.ReservedFor != "" {
+ var parts []string
+ if showReserve {
+ parts = append(parts, fmt.Sprintf("reserved for %s %dt", info.ReservedFor, info.ReserveTimer))
+ } else {
+ parts = append(parts, "reserved")
}
+ if showDespawn && !info.IsSpawn {
+ parts = append(parts, fmt.Sprintf("despawns %dt", info.DespawnTimer))
+ }
+ annotation := ""
+ if len(parts) > 0 {
+ annotation = fmt.Sprintf(" (%s)", strings.Join(parts, ", "))
+ }
+ lines = append(lines, displayLine{
+ name: name,
+ quantity: info.Quantity,
+ colorName: coloredName,
+ annotation: annotation,
+ })
+ continue
+ }
- timerKey := -1
+ timerKey := -1
+ if showDespawn && !info.IsSpawn {
+ timerKey = info.DespawnTimer
+ }
+ key := groupKey{itemID: info.ItemID, despawnTimer: timerKey}
+
+ if existing, ok := groups[key]; ok {
+ existing.quantity += info.Quantity
+ } else {
+ annotation := ""
if showDespawn && !info.IsSpawn {
- timerKey = info.DespawnTimer
+ annotation = fmt.Sprintf(" (despawns %dt)", info.DespawnTimer)
}
- key := groupKey{itemID: info.ItemID, despawnTimer: timerKey}
-
- if existing, ok := groups[key]; ok {
- existing.quantity += info.Quantity
- } else {
- annotation := ""
- if showDespawn && !info.IsSpawn {
- annotation = fmt.Sprintf(" (despawns %dt)", info.DespawnTimer)
- }
- dl := &displayLine{
- name: name,
- quantity: info.Quantity,
- colorName: coloredName,
- annotation: annotation,
- }
- groups[key] = dl
- groupOrder = append(groupOrder, key)
+ dl := &displayLine{
+ name: name,
+ quantity: info.Quantity,
+ colorName: coloredName,
+ annotation: annotation,
}
+ groups[key] = dl
+ groupOrder = append(groupOrder, key)
}
+ }
- for _, key := range groupOrder {
- lines = append(lines, *groups[key])
- }
+ for _, key := range groupOrder {
+ lines = append(lines, *groups[key])
+ }
- sort.Slice(lines, func(i, j int) bool {
- return strings.ToLower(lines[i].name) < strings.ToLower(lines[j].name)
- })
+ sort.Slice(lines, func(i, j int) bool {
+ return strings.ToLower(lines[i].name) < strings.ToLower(lines[j].name)
+ })
- sess.WriteLine("")
- sess.WriteLine("On the ground:")
+ var out []string
+ out = append(out, "")
+ out = append(out, "On the ground:")
- type fmtLine struct {
- prefix string
- annotation string
- }
- var fmtLines []fmtLine
- maxPrefix := 0
+ type fmtLine struct {
+ prefix string
+ annotation string
+ }
+ var fmtLines []fmtLine
+ maxPrefix := 0
- for _, dl := range lines {
- prefix := ""
- if dl.quantity > 1 {
- prefix = fmt.Sprintf(" %d x %s", dl.quantity, dl.colorName)
- } else {
- prefix = fmt.Sprintf(" %s", dl.colorName)
- }
- if visibleLen(prefix) > maxPrefix {
- maxPrefix = visibleLen(prefix)
- }
- fmtLines = append(fmtLines, fmtLine{prefix, dl.annotation})
+ for _, dl := range lines {
+ prefix := ""
+ if dl.quantity > 1 {
+ prefix = fmt.Sprintf(" %d x %s", dl.quantity, dl.colorName)
+ } else {
+ prefix = fmt.Sprintf(" %s", dl.colorName)
}
+ if visibleLen(prefix) > maxPrefix {
+ maxPrefix = visibleLen(prefix)
+ }
+ fmtLines = append(fmtLines, fmtLine{prefix, dl.annotation})
+ }
- for _, l := range fmtLines {
- if l.annotation != "" {
- pad := maxPrefix + 1 + (len(l.prefix) - visibleLen(l.prefix))
- sess.WriteLine(fmt.Sprintf("%-*s%s", pad, l.prefix, l.annotation))
- } else {
- sess.WriteLine(l.prefix)
- }
+ for _, l := range fmtLines {
+ if l.annotation != "" {
+ pad := maxPrefix + 1 + (len(l.prefix) - visibleLen(l.prefix))
+ out = append(out, fmt.Sprintf("%-*s%s", pad, l.prefix, l.annotation))
+ } else {
+ out = append(out, l.prefix)
}
}
+ return out
}
func (g *Game) showRoomExits(sess *net.Session, p *player.Player, room *world.Room) {
@@ -763,8 +779,8 @@ func wrapText(text string, width int) []string {
return lines
}
-func (g *Game) writeLookSideBySide(sess *net.Session, p *player.Player, descLines []string, mapLines []string) {
- total := len(descLines)
+func (g *Game) writeLookSideBySide(sess *net.Session, p *player.Player, contentLines []string, mapLines []string) {
+ total := len(contentLines)
if len(mapLines) > total {
total = len(mapLines)
}
@@ -774,22 +790,23 @@ func (g *Game) writeLookSideBySide(sess *net.Session, p *player.Player, descLine
mapWidth = 70
}
for i := 0; i < total; i++ {
- desc := ""
- if i < len(descLines) {
- desc = descLines[i]
+ content := ""
+ if i < len(contentLines) {
+ content = contentLines[i]
}
- mapLine := ""
if i < len(mapLines) {
- mapLine = mapLines[i]
- }
- if leftMap {
- sess.WriteLine(fmt.Sprintf("%s %s", mapLine, desc))
- } else {
- pad := mapWidth + (len(desc) - visibleLen(desc))
- if pad < 0 {
- pad = 0
+ mapLine := mapLines[i]
+ if leftMap {
+ sess.WriteLine(fmt.Sprintf("%s %s", mapLine, content))
+ } else {
+ pad := mapWidth + (len(content) - visibleLen(content))
+ if pad < 0 {
+ pad = 0
+ }
+ sess.WriteLine(fmt.Sprintf("%-*s %s", pad, content, mapLine))
}
- sess.WriteLine(fmt.Sprintf("%-*s %s", pad, desc, mapLine))
+ } else {
+ sess.WriteLine(content)
}
}
}