package game import ( "fmt" "sort" "strings" "thehouseoficarus/internal/action" "thehouseoficarus/internal/color" "thehouseoficarus/internal/net" "thehouseoficarus/internal/object" "thehouseoficarus/internal/player" ) func (g *Game) doSmith(sess *net.Session, input string) { p := sess.Player.(*player.Player) g.CancelAction(p) stationDefID, _ := g.findStation(p.RoomID, []string{"anvil"}) if stationDefID == "" { sess.WriteLine("You need an anvil to smith.") return } if !g.hasToolType(p, "hammer") { sess.WriteLine("You need a hammer to smith.") return } allRecipes, err := g.RecipeStore.LoadAll() if err != nil { sess.WriteLine("Error loading recipes.") return } if input != "" { matches := g.findInventoryMatches(input, p) if len(matches) == 0 { sess.WriteLine(fmt.Sprintf("You don't have any '%s'.", input)) return } unique := uniqueItemNames(matches) if len(unique) > 1 { sess.WriteLine("Which one?") for _, name := range unique { sess.WriteLine(fmt.Sprintf(" - %s", name)) } return } barID := matches[0].ID if !hasSmithingRecipes(allRecipes, barID) { sess.WriteLine("You can't smith that.") return } g.showSmithTable(sess, p, barID, allRecipes) return } barTypes := findSmithableBars(allRecipes, p) if len(barTypes) == 0 { sess.WriteLine("You don't have any bars to smith.") return } if len(barTypes) == 1 { g.showSmithTable(sess, p, barTypes[0], allRecipes) return } sess.PendingMenu = barMenuData(barTypes) sess.State = net.StateRecipeChoice sess.WriteLine("\nWhich bars would you like to smith?") for i, barID := range barTypes { def, _ := g.ItemStore.Load(barID) name := barID if def != nil { name = g.itemColorize(sess, def, def.Name) } sess.WriteLine(fmt.Sprintf(" %d) %s", i+1, name)) } sess.WriteLine(fmt.Sprintf(" %d) Nothing", len(barTypes)+1)) } func (g *Game) showSmithTable(sess *net.Session, p *player.Player, barID string, allRecipes []action.RecipeDef) { barDef, _ := g.ItemStore.Load(barID) barName := barID if barDef != nil { barName = barDef.Name } var recipes []action.RecipeDef for _, r := range allRecipes { if r.Type == "smithing" && r.MatchesEntry(barID) { recipes = append(recipes, r) } } if len(recipes) == 0 { sess.WriteLine("There's nothing to smith with that.") return } sort.Slice(recipes, func(i, j int) bool { return recipes[i].Level < recipes[j].Level }) mode := g.colorMode(sess) skillLevel := p.Level(player.Smithing) barCount := p.CountItem(barID) dimSpec := color.Parse("240") tbl := &Table{ Title: titleCase(barName) + " Smithing", Columns: []string{"Product", "Inputs", "Level Req."}, } for _, r := range recipes { outDef, _ := g.ItemStore.Load(r.Output) productName := r.Output if outDef != nil { productName = outDef.Name } bars := barsRequired(r, barID) inputStr := "1 bar" if bars > 1 { inputStr = fmt.Sprintf("%d bars", bars) } levelStr := fmt.Sprint(r.Level) canMake := barCount >= bars && skillLevel >= r.Level if canMake { productName = g.itemColorize(sess, outDef, productName) } else { productName = color.Render(mode, dimSpec, productName) inputStr = color.Render(mode, dimSpec, inputStr) levelStr = color.Render(mode, dimSpec, levelStr) } tbl.Rows = append(tbl.Rows, []string{productName, inputStr, levelStr}) } unicode := p.OptionBool("unicode") sess.WriteLine("") for _, line := range tbl.Render(unicode) { sess.WriteLine(line) } lastFlag := fmt.Sprintf("last_smith_%s", barID) lastRecipeID, _ := p.Flags[lastFlag].(string) hint := "" if lastRecipeID != "" { for _, r := range recipes { if r.ID == lastRecipeID { if outDef, err := g.ItemStore.Load(r.Output); err == nil { hint = outDef.Name } break } } } if hint != "" { sess.Write(fmt.Sprintf("Produce what (enter for all %s): ", hint)) } else { sess.Write("Produce what: ") } sess.PendingRecipeID = barID sess.State = net.StateSmithProduct } func (g *Game) handleSmithProduct(sess *net.Session, input string) { p := sess.Player.(*player.Player) barID := sess.PendingRecipeID sess.PendingRecipeID = "" sess.State = net.StateGame input = strings.TrimSpace(input) allRecipes, err := g.RecipeStore.LoadAll() if err != nil { g.reprompt(sess) return } var recipes []action.RecipeDef for _, r := range allRecipes { if r.Type == "smithing" && r.MatchesEntry(barID) { recipes = append(recipes, r) } } var targetRecipe *action.RecipeDef var count int if input == "" { lastFlag := fmt.Sprintf("last_smith_%s", barID) lastRecipeID, _ := p.Flags[lastFlag].(string) if lastRecipeID == "" { sess.WriteLine("Never mind.") g.reprompt(sess) return } for i := range recipes { if recipes[i].ID == lastRecipeID { targetRecipe = &recipes[i] break } } if targetRecipe == nil { sess.WriteLine("Never mind.") g.reprompt(sess) return } count = 0 } else { qty, productName := parseQty(input) count = qty productName = strings.ToLower(strings.TrimSpace(productName)) for i := range recipes { outDef, _ := g.ItemStore.Load(recipes[i].Output) name := recipes[i].Output if outDef != nil { name = outDef.Name } lower := strings.ToLower(name) if lower == productName || strings.HasPrefix(lower, productName) { targetRecipe = &recipes[i] break } } if targetRecipe == nil { sess.WriteLine("Never mind.") g.reprompt(sess) return } } skillLevel := p.Level(player.Smithing) if skillLevel < targetRecipe.Level { sess.WriteLine(fmt.Sprintf("You need level %d smithing to make that.", targetRecipe.Level)) g.reprompt(sess) return } if !targetRecipe.HasAllItemsQty(p.CountItem) { sess.WriteLine("You don't have enough bars.") g.reprompt(sess) return } if p.Flags == nil { p.Flags = make(map[string]any) } p.Flags[fmt.Sprintf("last_smith_%s", barID)] = targetRecipe.ID g.AccountStore.SaveCharacter(p) g.startProductionFromRecipe(sess, p, targetRecipe, count) } func (g *Game) hasToolType(p *player.Player, toolType string) bool { if itemID, ok := p.Equipment[object.SlotMainHand]; ok { if def, err := g.ItemStore.Load(itemID); err == nil && def.ToolType == toolType { return true } } for i := 0; i < 28; i++ { slot := p.InvSlot(i) if slot == nil { continue } if def, err := g.ItemStore.Load(slot.ItemID); err == nil && def.ToolType == toolType { return true } } return false } func hasSmithingRecipes(allRecipes []action.RecipeDef, barID string) bool { for _, r := range allRecipes { if r.Type == "smithing" && r.MatchesEntry(barID) { return true } } return false } func findSmithableBars(allRecipes []action.RecipeDef, p *player.Player) []string { barSet := make(map[string]bool) for _, r := range allRecipes { if r.Type != "smithing" { continue } for _, e := range r.Consume { for _, id := range e.Items { if p.HasItem(id) { barSet[id] = true } } } } var bars []string for id := range barSet { bars = append(bars, id) } sort.Strings(bars) return bars } func barsRequired(r action.RecipeDef, barID string) int { for _, e := range r.Consume { for _, id := range e.Items { if id == barID { qty := e.Qty if qty <= 0 { qty = 1 } return qty } } } return 1 } func barMenuData(barIDs []string) []map[string]string { out := make([]map[string]string, len(barIDs)) for i, id := range barIDs { out[i] = map[string]string{"bar_id": id} } return out } func titleCase(s string) string { words := strings.Fields(s) for i, w := range words { if len(w) > 0 { words[i] = strings.ToUpper(w[:1]) + w[1:] } } return strings.Join(words, " ") }