package game import ( "fmt" "sort" "strings" "thehouseoficarus/internal/color" "thehouseoficarus/internal/net" "thehouseoficarus/internal/player" "thehouseoficarus/internal/world" ) 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("A7"), fmt.Sprint(m.HP)), m.MaxHP) } } var desc string if g.Combat.IsMobInCombat(m.InstanceID) { if len(m.CombatDescriptions) > 0 { target := g.Combat.MobTarget(m.InstanceID) pattern := m.CombatDescriptions[randInt(len(m.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" } var levelStr string if m.Protected { levelStr = "" } else { 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, isLocal, err := g.resolveObjectDef(p.RoomID, objID) if err != nil { continue } if def.Hidden && !p.GodMode { continue } if farmPatchDefIDs[objID] { continue } var godPrefix string if p.GodMode { var tags []string if isLocal { tags = append(tags, color.Render(g.colorMode(sess), color.ColorSpec{Fg: -1, Bold: true}, "[LOCAL]")) } else { tags = append(tags, color.Render(g.colorMode(sess), color.ColorSpec{Fg: -1, Bold: true}, "[GLOBAL]")) } if def.Hidden { tags = append(tags, color.Render(g.colorMode(sess), color.ColorSpec{Fg: -1, Bold: true}, "(HIDDEN)")) } godPrefix = strings.Join(tags, " ") + " " } 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%s", godPrefix, 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%s", godPrefix, 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%s (depleted)%s%s", godPrefix, 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) 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("%s is here", g.colorize(sess, "player_name", op.Name)) if cs := g.Combat.Get(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 desc := g.playerActionDisplay(op); desc != "" { line += ", " + desc } sess.WriteLine(line + ".") } } }