aboutsummaryrefslogtreecommitdiff
path: root/internal/game/look_target.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/look_target.go')
-rw-r--r--internal/game/look_target.go343
1 files changed, 343 insertions, 0 deletions
diff --git a/internal/game/look_target.go b/internal/game/look_target.go
new file mode 100644
index 0000000..4819411
--- /dev/null
+++ b/internal/game/look_target.go
@@ -0,0 +1,343 @@
+package game
+
+import (
+ "fmt"
+ "strings"
+
+ "thehouseoficarus/internal/color"
+ "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
+ }
+ 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(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))
+ }
+
+ if objDescText != "" && !strings.Contains(objDescText, "{quality}") {
+ sess.WriteLine(color.ExpandTags(g.colorMode(sess), 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(objDescText, "{quality}") {
+ desc := strings.ReplaceAll(objDescText, "{quality}", fmt.Sprintf("%.0f", ist.Quality))
+ sess.WriteLine(color.ExpandTags(g.colorMode(sess), 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),
+ color.ExpandTags(g.colorMode(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),
+ color.ExpandTags(g.colorMode(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))
+ }
+ }
+}