diff options
| author | historia <[not public]> | 2026-06-17 21:13:07 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-06-17 21:13:07 -0400 |
| commit | ec2e94e3463654a6288464a4c070b48ef9bf7368 (patch) | |
| tree | 3c5df0f9a6a5968fa4c725a7d331825883ae798c /internal/game/cmd_smith.go | |
| parent | eef5ddaa94f8cff6010d092c30fed53d2be01524 (diff) | |
| download | thehouseoficarus-ec2e94e3463654a6288464a4c070b48ef9bf7368.tar.gz | |
feat: smithing added, item/object interaction reworked. recipes and menus refactored.
Diffstat (limited to 'internal/game/cmd_smith.go')
| -rw-r--r-- | internal/game/cmd_smith.go | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/internal/game/cmd_smith.go b/internal/game/cmd_smith.go new file mode 100644 index 0000000..30dcddc --- /dev/null +++ b/internal/game/cmd_smith.go @@ -0,0 +1,342 @@ +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, " ") +} |
