package game import ( "fmt" "sort" "strconv" "strings" "thehouseoficarus/internal/color" "thehouseoficarus/internal/combat" "thehouseoficarus/internal/net" "thehouseoficarus/internal/object" "thehouseoficarus/internal/player" "thehouseoficarus/internal/world" ) func (g *Game) doLook(sess *net.Session) { p := sess.Player room, err := g.World.LoadRoom(p.RoomID) if err != nil { sess.WriteLine("You are in a void.") return } g.showRoomName(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) } func (g *Game) showRoomName(sess *net.Session, p *player.Player, room *world.Room) { sess.WriteLines( "", fmt.Sprintf("%s (%s)", g.colorize(sess, "room_name", room.Name), g.colorize(sess, "room_number", fmt.Sprintf("#%d", room.ID))), ) } 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 } rawLines := wrapText(g.roomDescription(sess, room), descWidth) mode := g.colorMode(sess) roomDescSpec := g.resolveColor(sess, "room_desc") lines := make([]string, len(rawLines)) for i, l := range rawLines { lines[i] = color.ExpandTagsDefault(mode, roomDescSpec, l) } return lines } // roomDescription resolves the room's effective description for this player, // picking the first conditional variant whose condition passes, else the base // Description. func (g *Game) roomDescription(sess *net.Session, room *world.Room) string { for _, d := range room.Descriptions { if d.Condition == nil || g.checkCondition(sess, d.Condition) { return d.Text } } return room.Description } // resolveObjDesc resolves the description an object shows to this player. For an // object with conditional Descriptions, the first variant whose condition // passes wins; if none pass the object is reported as not present (false), so // look falls through as if it were not there. Objects without Descriptions use // their plain Description and are always present. func (g *Game) resolveObjDesc(sess *net.Session, def *object.ObjectDef) (string, bool) { if len(def.Descriptions) == 0 { return def.Description, true } for _, d := range def.Descriptions { if d.Condition == nil || g.checkCondition(sess, d.Condition) { return d.Text, true } } return "", false } func (g *Game) showRoomMobs(sess *net.Session, p *player.Player, room *world.Room) []string { mobs := g.MobStore.MobsInRoom(p.RoomID) 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 { if m.IsTask() { pct := 0 if m.MaxHP > 0 { pct = (m.MaxHP - m.HP) * 100 / m.MaxHP } hp = fmt.Sprintf(" [%d%% complete]", pct) } else { 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) []string { objs := g.World.AllObjInstances(p.RoomID) 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 } if def.Hidden { continue } if farmPatchDefIDs[objID] { continue } 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) } } 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 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) } lines = append(lines, fmt.Sprintf("%s%s%s", line, suffix, qualityTimer)) } 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) } 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) } lines = append(lines, fmt.Sprintf("%s (depleted)%s%s", line, tag, timer)) } } lines = append(lines, g.showFarmPatches(sess, p)...) return lines } func (g *Game) showGroundItems(sess *net.Session, p *player.Player, room *world.Room) []string { ground := g.World.GroundItemsDetailed(p.RoomID) 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 groupKey struct { itemID string despawnTimer int } 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) 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 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 { 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) } } 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) }) var out []string out = append(out, "") out = append(out, "On the ground:") 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 _, 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) { if len(room.Exits) > 0 { sess.WriteLine("") if p.OptionBool("exits") { sess.WriteLine("Exits:") type exitLine struct { dir string targetName string } var lines []exitLine maxDirLen := 0 for _, dir := range world.ExitOrder { exitDef, ok := room.Exits[dir] if !ok { continue } coloredDir := g.colorize(sess, "exit_direction", string(dir)) targetRoom, err := g.World.LoadRoom(exitDef.Room) targetName := g.colorize(sess, "room_number", fmt.Sprintf("#%d", exitDef.Room)) if err == nil { targetName = g.colorize(sess, "exit_name", targetRoom.Name) } if exitDef.Condition != nil && !g.checkCondition(sess, exitDef.Condition) { targetName += " (blocked)" } lines = append(lines, exitLine{coloredDir, targetName}) if visibleLen(coloredDir) > maxDirLen { maxDirLen = visibleLen(coloredDir) } } for _, l := range lines { pad := maxDirLen + (len(l.dir) - visibleLen(l.dir)) sess.WriteLine(fmt.Sprintf(" %-*s - %s", pad, l.dir, l.targetName)) } } else { sess.Write("Exits: ") first := true for _, dir := range world.ExitOrder { if _, ok := room.Exits[dir]; ok { if !first { sess.Write(", ") } sess.Write(g.colorize(sess, "exit_direction", string(dir))) first = false } } sess.WriteLine("") } } } func (g *Game) showRoomPlayers(sess *net.Session, p *player.Player, room *world.Room) { others := g.Hub.PlayersInRoom(p.RoomID) for _, other := range others { if other != sess && other.Player != nil { op := other.Player line := fmt.Sprintf("\n%s is here", g.colorize(sess, "player_name", op.Name)) if cs := combat.GetCombat(op.Name); cs != nil { if mob := g.MobStore.GetInstance(cs.MobID); mob != nil && mob.HP > 0 { name := mobDisplayName(mob, false) if idx := mobInstanceIdx(mob, g.MobStore.MobsInRoom(p.RoomID)); idx > 0 { name += fmt.Sprintf(" [%d]", idx) } line += fmt.Sprintf(" (fighting %s)", name) } } else if as, ok := op.ActionState.(*ActionState); ok && as != nil { if desc := as.Description(); desc != "" { line += ", " + desc } } sess.WriteLine(line + ".") } } } func (g *Game) doLookTarget(sess *net.Session, input string) { p := sess.Player lower := strings.ToLower(input) if exitDir := g.World.ResolveExit(lower); exitDir != "" { room, err := g.World.LoadRoom(p.RoomID) if err != nil { sess.WriteLine("You can't see anything that way.") return } exitDef, ok := room.Exits[exitDir] if !ok { sess.WriteLine("You can't see anything that way.") return } if exitDef.Condition != nil && !g.checkCondition(sess, exitDef.Condition) { msg := exitDef.BlockedMessage if msg == "" { msg = fmt.Sprintf("The way %s is blocked.", exitDir) } sess.WriteLine(msg) return } g.World.SeedGroundItems(exitDef.Room) g.seedRoomMobs(exitDef.Room) origRoom := p.RoomID p.RoomID = exitDef.Room g.doLook(sess) p.RoomID = origRoom return } var best *world.MobInstance bestQ := world.MatchNone for _, m := range g.MobStore.MobsInRoom(p.RoomID) { q := m.MatchQuality(lower) if q > bestQ { bestQ = q best = m } } if best != nil { mobColor := "mob" if best.Protected { 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, mobColor, best.Name), levelStr), ) if best.IdleDescription != "" { sess.WriteLine(fmt.Sprintf(" %s", best.IdleDescription)) } sess.WriteLines( "", fmt.Sprintf(" Attack: %d", best.Attack), fmt.Sprintf(" Strength: %d", best.Strength), fmt.Sprintf(" Defense: %d", best.Defense), fmt.Sprintf(" HP: %d/%d", best.HP, best.MaxHP), ) if best.StabDefense != 0 || best.SlashDefense != 0 || best.CrushDefense != 0 || best.ScienceDefense != 0 || best.RangedDefense != 0 { sess.WriteLines( "", " Defense bonuses:", fmt.Sprintf(" Stab: %+d Slash: %+d Crush: %+d", best.StabDefense, best.SlashDefense, best.CrushDefense), fmt.Sprintf(" Science: %+d Ranged: %+d", best.ScienceDefense, best.RangedDefense), ) } if best.Weakness != "" { sess.WriteLine(fmt.Sprintf(" Weakness: %s", best.Weakness)) } return } rawInstances := g.World.FindObjInstances(p.RoomID, lower) // Group matched object instances by definition, keeping only those visible // to this player (an object whose conditional descriptions all fail is // absent). If more than one distinct object matches, disambiguate. var presentDefs []string byDef := map[string][]world.ObjState{} descByDef := map[string]string{} for _, ist := range rawInstances { def, err := g.ObjectStore.Load(ist.DefID) if err != nil { continue } text, present := g.resolveObjDesc(sess, def) if !present { continue } if _, seen := byDef[ist.DefID]; !seen { presentDefs = append(presentDefs, ist.DefID) descByDef[ist.DefID] = text } byDef[ist.DefID] = append(byDef[ist.DefID], ist) } if len(presentDefs) > 1 { sess.WriteLine("That's ambiguous, which one?") for _, defID := range presentDefs { if def, err := g.ObjectStore.Load(defID); err == nil { sess.WriteLine(fmt.Sprintf(" %s", def.Name)) } } return } if len(presentDefs) == 1 { instances := byDef[presentDefs[0]] objDescText := descByDef[presentDefs[0]] st := &instances[0] def, _ := g.ObjectStore.Load(st.DefID) if st.DefID == "estate_directory" { g.lookEstateDirectory(sess) return } sess.WriteLine("") if len(instances) > 1 { sess.WriteLine(fmt.Sprintf("%d %ss:", len(instances), def.Name)) } else { sess.WriteLine(def.Name) } if objDescText != "" && !strings.Contains(objDescText, "{quality}") { sess.WriteLine(fmt.Sprintf(" %s", objDescText)) } if def.Safespot != nil { realSt := g.World.GetObjState(p.RoomID, st.DefID, st.Index) if realSt != nil { levelStr := "intact" if realSt.SafespotLevel <= 0 { levelStr = fmt.Sprintf("intact (level %d/%d)", len(def.Safespot.Levels), len(def.Safespot.Levels)) } else if realSt.SafespotLevel < len(def.Safespot.Levels) { levelStr = fmt.Sprintf("degraded (level %d/%d)", realSt.SafespotLevel, len(def.Safespot.Levels)) } else { levelStr = fmt.Sprintf("intact (level %d/%d)", realSt.SafespotLevel, len(def.Safespot.Levels)) } blockInfo := "anything" if def.Safespot.MaxBlockSize != "" { blockInfo = fmt.Sprintf("up to %s mobs", def.Safespot.MaxBlockSize) } sess.WriteLine(fmt.Sprintf(" Safespot: %s — blocks %s", levelStr, blockInfo)) if len(realSt.SafespotOccupants) > 0 { sess.WriteLine(fmt.Sprintf(" Occupied by: %s", strings.Join(realSt.SafespotOccupants, ", "))) } } } if p.OptionBool("depletion") { for _, ist := range instances { if ist.Depleted { if len(instances) > 1 { sess.WriteLine(fmt.Sprintf(" %s %d: depleted, respawns in %d ticks.", def.Name, ist.Index+1, int(ist.DepleteTimer))) } else { sess.WriteLine(fmt.Sprintf(" Depleted, respawns in %d ticks.", int(ist.DepleteTimer))) } } else if ist.SharedMax > 0 && float64(ist.SharedTimer) < ist.SharedMax { if len(instances) > 1 { sess.WriteLine(fmt.Sprintf(" %s %d: despawn timer %d/%.0f.", def.Name, ist.Index+1, ist.SharedTimer, ist.SharedMax)) } else { sess.WriteLine(fmt.Sprintf(" Despawn timer: %d/%.0f ticks.", ist.SharedTimer, ist.SharedMax)) } } } } for _, ist := range instances { if ist.Quality > 0 && strings.Contains(def.Description, "{quality}") { desc := strings.ReplaceAll(def.Description, "{quality}", fmt.Sprintf("%.0f", ist.Quality)) sess.WriteLine(fmt.Sprintf(" %s", desc)) } } farmSuffix := g.farmObjSuffix(p, st.DefID, st.Index) if farmSuffix != "" { sess.WriteLine(farmSuffix) } return } ground := g.World.GroundItems(p.RoomID) for itemID := range ground { def, err := g.ItemStore.Load(itemID) if err != nil || !def.MatchesName(input) { continue } sess.WriteLines( "", g.itemColorize(sess, def, def.Name), fmt.Sprintf(" %s", def.Description), fmt.Sprintf(" Value: %d credits", def.Value), ) g.showItemStats(sess, def) return } for i := 0; i < 28; i++ { slot := p.InvSlot(i) if slot == nil { continue } def, err := g.ItemStore.Load(slot.ItemID) if err != nil || !def.MatchesName(input) { continue } lines := []string{ "", g.itemColorize(sess, def, def.Name), fmt.Sprintf(" %s", def.Description), fmt.Sprintf(" Value: %d credits", def.Value), } if slot.MaxQuality > 0 { lines = append(lines, fmt.Sprintf(" Has %d units of butane left.", slot.Quality)) } sess.WriteLines(lines...) g.showItemStats(sess, def) return } others := g.Hub.PlayersInRoom(p.RoomID) for _, other := range others { if other == sess || other.Player == nil { continue } op := other.Player if strings.ToLower(op.Name) != lower { continue } showPlayerInfo(g, sess, op) return } sess.WriteLine(fmt.Sprintf("There's no '%s' here.", input)) } func showPlayerInfo(g *Game, sess *net.Session, p *player.Player) { myP := sess.Player theirLevel := p.CombatLevel() levelStr := g.levelColorize(sess, myP.CombatLevel(), theirLevel, fmt.Sprint(theirLevel)) sess.WriteLines( "", p.Name, fmt.Sprintf(" Combat Level: %s", levelStr), fmt.Sprintf(" HP: %d/%d", p.HP, p.MaxHP()), "", ) for _, s := range player.AllSkills { level := p.Level(s) sess.WriteLine(fmt.Sprintf(" %-12s Level: %d", s, level)) } sess.WriteLine("") sess.WriteLine(" Equipment:") for _, slot := range EquipSlots { itemID, ok := p.Equipment[slot] if !ok { continue } name := itemID var itemDef *object.ItemDef if def, err := g.ItemStore.Load(itemID); err == nil { name = def.Name itemDef = def } sess.WriteLine(fmt.Sprintf(" %-12s %s", slot, g.itemColorize(sess, itemDef, name))) } if p.Description != "" { sess.WriteLine("") sess.WriteLine(fmt.Sprintf(" %s", p.Description)) } } func (g *Game) doExits(sess *net.Session) { p := sess.Player room, err := g.World.LoadRoom(p.RoomID) if err != nil || len(room.Exits) == 0 { sess.WriteLine("There are no exits here.") return } type exitLine struct { dir string name string } var lines []exitLine maxDirLen := 0 for _, dir := range world.ExitOrder { exitDef, ok := room.Exits[dir] if !ok { continue } coloredDir := g.colorize(sess, "exit_direction", string(dir)) targetRoom, err := g.World.LoadRoom(exitDef.Room) targetName := g.colorize(sess, "room_number", fmt.Sprintf("#%d", exitDef.Room)) if err == nil { targetName = g.colorize(sess, "exit_name", targetRoom.Name) } lines = append(lines, exitLine{coloredDir, targetName}) if visibleLen(coloredDir) > maxDirLen { maxDirLen = visibleLen(coloredDir) } } for _, l := range lines { pad := maxDirLen + (len(l.dir) - visibleLen(l.dir)) sess.WriteLine(fmt.Sprintf(" %-*s - %s", pad, l.dir, l.name)) } } func joinInts(nums []int) string { var parts []string for _, n := range nums { parts = append(parts, strconv.Itoa(n)) } return strings.Join(parts, ",") } func mobInstanceIdx(mob *world.MobInstance, roomMobs []*world.MobInstance) int { var same []*world.MobInstance for _, m := range roomMobs { if m.DefID == mob.DefID { same = append(same, m) } } if len(same) <= 1 { return 0 } sort.Slice(same, func(i, j int) bool { return same[i].InstanceID < same[j].InstanceID }) for i, m := range same { if m.InstanceID == mob.InstanceID { return i + 1 } } return 0 } func visibleLen(s string) int { return color.VisibleLen(s) } func wrapText(text string, width int) []string { if width <= 0 { return []string{text} } words := strings.Fields(text) if len(words) == 0 { return nil } var lines []string current := words[0] for _, word := range words[1:] { if visibleLen(current)+1+visibleLen(word) <= width { current += " " + word } else { lines = append(lines, current) current = word } } lines = append(lines, current) return lines } func (g *Game) writeLookSideBySide(sess *net.Session, p *player.Player, contentLines []string, mapLines []string) { total := len(contentLines) if len(mapLines) > total { total = len(mapLines) } leftMap := p.OptionString("tiny_map") == "left" mapWidth := p.OptionInt("room_desc_width") if mapWidth <= 0 { mapWidth = 70 } for i := 0; i < total; i++ { content := "" if i < len(contentLines) { content = contentLines[i] } if i < len(mapLines) { 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)) } } else { sess.WriteLine(content) } } } func (g *Game) executeLook(sess *net.Session, args []string, rawInput string) { if len(args) == 0 { g.doLook(sess) } else { g.doLookTarget(sess, strings.Join(args, " ")) } } func (g *Game) executeExits(sess *net.Session, args []string, rawInput string) { g.doExits(sess) } func (g *Game) showItemStats(sess *net.Session, def *object.ItemDef) { s := def.Stats hasAttack := s.StabAttack != 0 || s.SlashAttack != 0 || s.CrushAttack != 0 || s.ScienceAttack != 0 || s.RangedAttack != 0 hasDefense := s.StabDefense != 0 || s.SlashDefense != 0 || s.CrushDefense != 0 || s.ScienceDefense != 0 || s.RangedDefense != 0 hasOther := s.StrengthBonus != 0 || s.RangedStrength != 0 || s.ScienceDamage != 0 || s.TechnologyBonus != 0 if !hasAttack && !hasDefense && !hasOther { return } sess.WriteLine("") if hasAttack || hasDefense { sess.WriteLine(" Attack bonuses: Defense bonuses:") sess.WriteLine(fmt.Sprintf(" Stab: %+4d Stab: %+4d", s.StabAttack, s.StabDefense)) sess.WriteLine(fmt.Sprintf(" Slash: %+4d Slash: %+4d", s.SlashAttack, s.SlashDefense)) sess.WriteLine(fmt.Sprintf(" Crush: %+4d Crush: %+4d", s.CrushAttack, s.CrushDefense)) sess.WriteLine(fmt.Sprintf(" Science:%+4d Science:%+4d", s.ScienceAttack, s.ScienceDefense)) sess.WriteLine(fmt.Sprintf(" Ranged: %+4d Ranged: %+4d", s.RangedAttack, s.RangedDefense)) } if hasOther { sess.WriteLine("") sess.WriteLine(" Other bonuses:") if s.StrengthBonus != 0 { sess.WriteLine(fmt.Sprintf(" Melee strength: %+d", s.StrengthBonus)) } if s.RangedStrength != 0 { sess.WriteLine(fmt.Sprintf(" Ranged strength: %+d", s.RangedStrength)) } if s.ScienceDamage != 0 { sess.WriteLine(fmt.Sprintf(" Science damage: %+d", s.ScienceDamage)) } if s.TechnologyBonus != 0 { sess.WriteLine(fmt.Sprintf(" Technology: %+d", s.TechnologyBonus)) } } if def.AttackType != "" { sess.WriteLine(fmt.Sprintf(" Attack type: %s", def.AttackType)) } if def.Speed > 0 { sess.WriteLine(fmt.Sprintf(" Speed: %.0f", def.Speed)) } if len(def.Requirements) > 0 { sess.WriteLine("") sess.WriteLine(" Requirements:") for skill, level := range def.Requirements { sess.WriteLine(fmt.Sprintf(" %s: %d", skill, level)) } } }