package game import ( "fmt" "sort" "strings" "thehouseoficarus/internal/action" "thehouseoficarus/internal/color" "thehouseoficarus/internal/combat" "thehouseoficarus/internal/net" "thehouseoficarus/internal/player" "thehouseoficarus/internal/world" ) var fletchLogItems = map[string]bool{ "logs": true, "oak_logs": true, "willow_logs": true, "maple_logs": true, "yew_logs": true, "magic_logs": true, } var fletchGemItems = map[string]bool{ "uncut_sapphire": true, "uncut_emerald": true, "uncut_ruby": true, "uncut_diamond": true, } func fletchNeedsKnife(r *action.RecipeDef) bool { for _, e := range r.Consume { for _, id := range e.Items { if fletchLogItems[id] { if strings.Contains(r.Output, "shaft") || strings.Contains(r.Output, "bow") { return true } } } } return false } func fletchNeedsChisel(r *action.RecipeDef) bool { for _, e := range r.Consume { for _, id := range e.Items { if fletchGemItems[id] { return true } } } return false } func (g *Game) doFletch(sess *net.Session, input string) { p := sess.Player.(*player.Player) if combat.GetCombat(p.Name) != nil { sess.WriteLine("You can't do that during combat!") return } if p.Action != nil { p.Action = nil p.ActionState = nil } allRecipes, err := g.RecipeStore.LoadAll() if err != nil { sess.WriteLine("Error loading recipes.") return } var fletchRecipes []action.RecipeDef for _, r := range allRecipes { if r.Type != "fletching" { continue } fletchRecipes = append(fletchRecipes, r) } if input != "" { g.doFletchWithInput(sess, p, fletchRecipes, input) return } lastRecipeID, _ := p.Flags["last_fletch"].(string) if lastRecipeID != "" { for i := range fletchRecipes { if fletchRecipes[i].ID == lastRecipeID { r := &fletchRecipes[i] if g.canDoFletchRecipe(p, r) { g.startFletchAction(sess, p, r, 0) return } break } } } available := g.availableFletchRecipes(p, fletchRecipes) if len(available) == 0 { sess.WriteLine("You don't have any materials to fletch.") return } if p.OptionBool("fletch_all") && len(available) == 1 { g.startFletchAction(sess, p, &available[0], 0) return } g.showFletchTable(sess, p, available) } func (g *Game) doFletchWithInput(sess *net.Session, p *player.Player, fletchRecipes []action.RecipeDef, input string) { qty, productName := parseQty(input) productName = strings.TrimSpace(productName) var matched []action.RecipeDef for _, r := range fletchRecipes { outDef, _ := g.ItemStore.Load(r.Output) name := r.Output if outDef != nil { name = outDef.Name } if world.WordPrefixMatch(productName, name) { matched = append(matched, r) } } if len(matched) == 0 { sess.WriteLine("You can't fletch that.") return } var available []action.RecipeDef for _, r := range matched { if g.canDoFletchRecipe(p, &r) { available = append(available, r) } } if len(available) == 0 { sess.WriteLine("You don't have the materials for that.") return } if len(available) == 1 { count := qty g.startFletchAction(sess, p, &available[0], count) return } g.showFletchTable(sess, p, available) } func (g *Game) hasFletchTools(p *player.Player, r *action.RecipeDef) bool { if fletchNeedsKnife(r) && !g.hasToolType(p, "knife") { return false } if fletchNeedsChisel(r) && !g.hasToolType(p, "chisel") { return false } return true } func (g *Game) canDoFletchRecipe(p *player.Player, r *action.RecipeDef) bool { return p.Level(player.Fletching) >= r.Level && r.HasAllItemsQty(p.CountItem) && g.hasFletchTools(p, r) } func (g *Game) availableFletchRecipes(p *player.Player, allFletch []action.RecipeDef) []action.RecipeDef { var result []action.RecipeDef for _, r := range allFletch { if r.HasAllItemsQty(p.CountItem) && g.hasFletchTools(p, &r) { result = append(result, r) } } return result } func (g *Game) showFletchTable(sess *net.Session, p *player.Player, available []action.RecipeDef) { sort.Slice(available, func(i, j int) bool { return available[i].Level < available[j].Level }) mode := g.colorMode(sess) skillLevel := p.Level(player.Fletching) dimSpec := color.Parse("240") tbl := &Table{ Title: "Fletching Actions", Columns: []string{"#", "Product", "Materials", "Level"}, } for i, r := range available { outDef, _ := g.ItemStore.Load(r.Output) productName := r.Output if outDef != nil { productName = outDef.Name } outputQty := r.OutputQty if outputQty <= 0 { outputQty = 1 } if outDef != nil && outDef.Stackable && outputQty > 1 { productName = fmt.Sprintf("%s x%d", productName, outputQty) } var matParts []string for _, e := range r.Consume { itemName := e.Items[0] if def, err := g.ItemStore.Load(e.Items[0]); err == nil { itemName = def.Name } if e.Qty > 1 { matParts = append(matParts, fmt.Sprintf("%s x%d", itemName, e.Qty)) } else { matParts = append(matParts, itemName) } } matStr := strings.Join(matParts, ", ") levelStr := fmt.Sprint(r.Level) numStr := fmt.Sprintf("%d", i+1) canMake := skillLevel >= r.Level if canMake { productName = g.itemColorize(sess, outDef, productName) } else { productName = color.Render(mode, dimSpec, productName) matStr = color.Render(mode, dimSpec, matStr) levelStr = color.Render(mode, dimSpec, levelStr) numStr = color.Render(mode, dimSpec, numStr) } tbl.Rows = append(tbl.Rows, []string{numStr, productName, matStr, levelStr}) } unicode := p.OptionBool("unicode") sess.WriteLine("") for _, line := range tbl.Render(unicode) { sess.WriteLine(line) } lastRecipeID, _ := p.Flags["last_fletch"].(string) hint := "" if lastRecipeID != "" { for _, r := range available { if r.ID == lastRecipeID { if outDef, err := g.ItemStore.Load(r.Output); err == nil { hint = outDef.Name } break } } } if hint != "" { sess.Write(fmt.Sprintf("Fletch what (enter for all %s): ", hint)) } else { sess.Write("Fletch what: ") } sess.PendingMenu = recipeMenuData(available) sess.State = net.StateFletchProduct } func (g *Game) handleFletchProduct(sess *net.Session, input string) { p := sess.Player.(*player.Player) menuData := sess.PendingMenu sess.PendingMenu = nil 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 _, entry := range menuData { rid := entry["recipe_id"] for _, r := range allRecipes { if r.ID == rid { recipes = append(recipes, r) break } } } if len(recipes) == 0 { g.reprompt(sess) return } sort.Slice(recipes, func(i, j int) bool { return recipes[i].Level < recipes[j].Level }) lastRecipeID, _ := p.Flags["last_fletch"].(string) sel, errMsg := g.resolveRecipeByInput(input, recipes, lastRecipeID) switch errMsg { case "ambiguous": sess.WriteLine("That's ambiguous.") g.reprompt(sess) return case "never_mind": sess.WriteLine("Never mind.") g.reprompt(sess) return } if !g.canDoFletchRecipe(p, sel.Recipe) { if p.Level(player.Fletching) < sel.Recipe.Level { sess.WriteLine(fmt.Sprintf("You need level %d fletching to make that.", sel.Recipe.Level)) } else { sess.WriteLine("You don't have the materials for that.") } g.reprompt(sess) return } g.startFletchAction(sess, p, sel.Recipe, sel.Count) } func (g *Game) findFletchRecipesForItems(p *player.Player, itemAID, itemBID string) []action.RecipeDef { allRecipes, err := g.RecipeStore.LoadAll() if err != nil { return nil } toolTypeA := "" toolTypeB := "" if defA, _ := g.ItemStore.Load(itemAID); defA != nil { toolTypeA = defA.ToolType } if defB, _ := g.ItemStore.Load(itemBID); defB != nil { toolTypeB = defB.ToolType } isToolA := toolTypeA == "knife" || toolTypeA == "chisel" isToolB := toolTypeB == "knife" || toolTypeB == "chisel" var matched []action.RecipeDef for _, r := range allRecipes { if r.Type != "fletching" { continue } aMatch := false bMatch := false for _, e := range r.Consume { for _, id := range e.Items { if id == itemAID { aMatch = true } if id == itemBID { bMatch = true } } } if aMatch && bMatch { matched = append(matched, r) continue } if isToolA && bMatch { if (fletchNeedsKnife(&r) && toolTypeA == "knife") || (fletchNeedsChisel(&r) && toolTypeA == "chisel") { matched = append(matched, r) continue } } if isToolB && aMatch { if (fletchNeedsKnife(&r) && toolTypeB == "knife") || (fletchNeedsChisel(&r) && toolTypeB == "chisel") { matched = append(matched, r) continue } } } return matched }