diff options
| author | historia <[not public]> | 2026-06-18 18:50:54 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-06-18 18:50:54 -0400 |
| commit | 17e7725f252c7f5d3be2e9c37d62751c631f196f (patch) | |
| tree | 40698c5c95cb21302adbb8cd021bd8d049b6fce1 /internal/game/cmd_fletch.go | |
| parent | fae979b75e37b6ad57e57062a1b637a37ec87174 (diff) | |
| download | thehouseoficarus-17e7725f252c7f5d3be2e9c37d62751c631f196f.tar.gz | |
feat: fletching added including zero-time bolt feathering, which probably will break lots of things
Diffstat (limited to 'internal/game/cmd_fletch.go')
| -rw-r--r-- | internal/game/cmd_fletch.go | 383 |
1 files changed, 383 insertions, 0 deletions
diff --git a/internal/game/cmd_fletch.go b/internal/game/cmd_fletch.go new file mode 100644 index 0000000..fe8c0b3 --- /dev/null +++ b/internal/game/cmd_fletch.go @@ -0,0 +1,383 @@ +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 +} |
