package game import ( "fmt" "strings" "thehouseoficarus/internal/item" "thehouseoficarus/internal/net" "thehouseoficarus/internal/player" "thehouseoficarus/internal/world" ) 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 } ed := g.exitDisplayState(sess, p.RoomID, exitDir, exitDef) if !ed.Visible { sess.WriteLine("You can't see anything that way.") return } if ed.Blocked { 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" } var levelStr string if best.Protected { levelStr = g.colorizeConstant(sess, "protected_label", "(protected)") } else { 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.Description != "" { sess.WriteLine(g.expandTags(sess, best.Description)) } if !best.Protected { 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.resolveObjectDef(p.RoomID, 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.resolveObjectDef(p.RoomID, 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.resolveObjectDef(p.RoomID, 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)) } if objDescText != "" && !strings.Contains(objDescText, "{quality}") { sess.WriteLine(g.expandTags(sess, objDescText)) } if len(def.OnLook) > 0 { g.runTrigger(sess, p, def.OnLook, p.RoomID, p.HasItem, false) } 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(objDescText, "{quality}") { desc := strings.ReplaceAll(objDescText, "{quality}", fmt.Sprintf("%.0f", ist.Quality)) sess.WriteLine(g.expandTags(sess, desc)) } } if farmPatchDefIDs[st.DefID] { prefix := farmFlagPrefix(st.DefID, st.Index) if prefix != "" { g.ensureFarmState(p, prefix) g.ShowFarmPatchDetail(sess, p, prefix, st.DefID, st.Index) } } else { 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), g.expandTags(sess, 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), g.expandTags(sess, 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 *item.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(p.Description) } } func (g *Game) showItemStats(sess *net.Session, def *item.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)) } } }