aboutsummaryrefslogtreecommitdiff
path: root/internal/game/cmd_smith.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-17 21:13:07 -0400
committerhistoria <[not public]>2026-06-17 21:13:07 -0400
commitec2e94e3463654a6288464a4c070b48ef9bf7368 (patch)
tree3c5df0f9a6a5968fa4c725a7d331825883ae798c /internal/game/cmd_smith.go
parenteef5ddaa94f8cff6010d092c30fed53d2be01524 (diff)
downloadthehouseoficarus-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.go342
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, " ")
+}