diff options
| author | historia <[not public]> | 2026-06-18 20:26:03 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-06-18 20:26:03 -0400 |
| commit | 97a1a908b9e6e6d3516628c139c9b64262b4fe08 (patch) | |
| tree | d82953974edbfb4051dff1a48f1b264b9c264d28 /internal/game/action_production.go | |
| parent | 17e7725f252c7f5d3be2e9c37d62751c631f196f (diff) | |
| download | thehouseoficarus-97a1a908b9e6e6d3516628c139c9b64262b4fe08.tar.gz | |
feat: crafting roughly implemented (leather, gems, gold, clay, spinning).
Diffstat (limited to 'internal/game/action_production.go')
| -rw-r--r-- | internal/game/action_production.go | 299 |
1 files changed, 261 insertions, 38 deletions
diff --git a/internal/game/action_production.go b/internal/game/action_production.go index d4e2274..df23198 100644 --- a/internal/game/action_production.go +++ b/internal/game/action_production.go @@ -3,10 +3,12 @@ package game import ( "fmt" "math/rand" + "sort" "strconv" "strings" "thehouseoficarus/internal/action" + "thehouseoficarus/internal/color" "thehouseoficarus/internal/engine" "thehouseoficarus/internal/net" "thehouseoficarus/internal/object" @@ -19,6 +21,29 @@ type recipeEntry struct { Recipe action.RecipeDef } +type productionTypeInfo struct { + ActionType string + DisplayVerb string +} + +var productionTypes = map[string]productionTypeInfo{ + "cooking": {"cook", "cooking"}, + "smelting": {"smelt", "smelting"}, + "smithing": {"smith", "smithing"}, + "crafting": {"craft", "crafting"}, + "combine": {"combine", "combining"}, + "fletching": {"fletch", "fletching"}, +} + +var productionActionTypes map[string]bool + +func init() { + productionActionTypes = make(map[string]bool) + for _, pt := range productionTypes { + productionActionTypes[pt.ActionType] = true + } +} + func recipeMenuData(recipes []action.RecipeDef) []map[string]string { out := make([]map[string]string, len(recipes)) for i, r := range recipes { @@ -183,10 +208,11 @@ func (g *Game) startProduction(sess *net.Session, p *player.Player, recipe *acti g.CancelAction(p) g.CancelBackgroundAction(p) - if recipe.Skill != "" { - skillLevel := p.Level(player.SkillName(recipe.Skill)) + skill := recipe.EffectiveSkill() + if skill != "" { + skillLevel := p.Level(player.SkillName(skill)) if skillLevel < recipe.Level { - sess.WriteLine(fmt.Sprintf("You need level %d %s to do that.", recipe.Level, recipe.Skill)) + sess.WriteLine(fmt.Sprintf("You need level %d %s to do that.", recipe.Level, skill)) g.reprompt(sess) return } @@ -242,38 +268,20 @@ func (g *Game) startProductionFromRecipe(sess *net.Session, p *player.Player, re return def.Name, true }) - var actionType, displayVerb, startMsg, endMsg string - switch recipe.Type { - case "cooking": - actionType = "cook" - displayVerb = "cooking" - if stationName != "inventory" { - startMsg = fmt.Sprintf("You start cooking %s on the %s.", firstItem, stationName) - } else { - startMsg = fmt.Sprintf("You start making %s.", firstItem) - } - endMsg = "You've cooked everything you can." - case "smelting": - actionType = "smelt" - displayVerb = "smelting" - startMsg = fmt.Sprintf("You begin to process %s in the furnace.", firstItem) - endMsg = "You've processed all the ore in your inventory." - case "smithing": - actionType = "smith" - displayVerb = "smithing" - startMsg = fmt.Sprintf("You hammer the %s on the anvil...", firstItem) - endMsg = "You've used all the bars you can." - case "combine": - actionType = "combine" - displayVerb = "combining" - startMsg = fmt.Sprintf("You start combining %s.", firstItem) - endMsg = "You've run out of materials." - default: - actionType = "craft" - displayVerb = "crafting" - startMsg = fmt.Sprintf("You start crafting with %s.", firstItem) - endMsg = "You've run out of materials." + info, ok := productionTypes[recipe.Type] + if !ok { + info = productionTypeInfo{recipe.Type, recipe.Type} + } + actionType := info.ActionType + displayVerb := info.DisplayVerb + + var startMsg, endMsg string + if stationName != "inventory" { + startMsg = fmt.Sprintf("You start %s %s on the %s.", displayVerb, firstItem, stationName) + } else { + startMsg = fmt.Sprintf("You start %s %s.", displayVerb, firstItem) } + endMsg = fmt.Sprintf("You've finished %s.", displayVerb) g.startProduction(sess, p, recipe, actionType, displayVerb, startMsg, endMsg, count) } @@ -319,7 +327,8 @@ func (g *Game) advanceProduction(sess *net.Session, p *player.Player) bool { return true } - skillLevel := p.Level(player.SkillName(recipe.Skill)) + skill := recipe.EffectiveSkill() + skillLevel := p.Level(player.SkillName(skill)) chance := 1.0 if recipe.Success != nil { chance = action.SuccessChance(*recipe.Success, skillLevel, recipe.Level) @@ -364,8 +373,8 @@ func (g *Game) advanceProduction(sess *net.Session, p *player.Player) bool { } if recipe.XP > 0 { - if newLevel := p.AddSkillXP(player.SkillName(recipe.Skill), recipe.XP); newLevel > 0 { - sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d %s! ***", newLevel, recipe.Skill))) + if newLevel := p.AddSkillXP(player.SkillName(skill), recipe.XP); newLevel > 0 { + sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d %s! ***", newLevel, skill))) } } g.AccountStore.SaveCharacter(p) @@ -379,7 +388,7 @@ func (g *Game) advanceProduction(sess *net.Session, p *player.Player) bool { msg = fmt.Sprintf("You produce %s.", outputName) } if p.OptionBool("xp_drops") && recipe.XP > 0 { - abbr := player.SkillAbbr[player.SkillName(recipe.Skill)] + abbr := player.SkillAbbr[player.SkillName(skill)] msg += g.colorize(sess, "xp", fmt.Sprintf(" (+%dxp %s)", recipe.XP, abbr)) } sess.WriteLine(msg) @@ -531,6 +540,17 @@ func (g *Game) menuEntryName(entry map[string]string) string { } } } + if rid, ok := entry["fletch_recipe_id"]; ok { + all, _ := g.RecipeStore.LoadAll() + for _, r := range all { + if r.ID == rid { + if def, err := g.ItemStore.Load(r.Output); err == nil { + return def.Name + } + return r.Output + } + } + } if barID, ok := entry["bar_id"]; ok { if def, _ := g.ItemStore.Load(barID); def != nil { return def.Name @@ -557,9 +577,212 @@ func (g *Game) dispatchMenuEntry(sess *net.Session, entry map[string]string) { g.showSmithTable(sess, p, barID, allRecipes) return } + if rid, ok := entry["fletch_recipe_id"]; ok { + p := sess.Player.(*player.Player) + allRecipes, _ := g.RecipeStore.LoadAll() + for _, r := range allRecipes { + if r.ID == rid { + g.startFletchAction(sess, p, &r, 0) + return + } + } + g.reprompt(sess) + return + } if rid, ok := entry["recipe_id"]; ok { g.promptHowMany(sess, rid) return } g.reprompt(sess) } + +func (g *Game) formatRecipeMaterials(r *action.RecipeDef) string { + var parts []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 { + parts = append(parts, fmt.Sprintf("%s x%d", itemName, e.Qty)) + } else { + parts = append(parts, itemName) + } + } + return strings.Join(parts, ", ") +} + +func (g *Game) showProductionTable(sess *net.Session, p *player.Player, recipes []action.RecipeDef, title, skill, lastFlagKey, promptVerb string, background bool) { + sort.Slice(recipes, func(i, j int) bool { + return recipes[i].Level < recipes[j].Level + }) + + mode := g.colorMode(sess) + skillLevel := p.Level(player.SkillName(skill)) + dimSpec := color.Parse("240") + + tbl := &Table{ + Title: title, + Columns: []string{"#", "Product", "Materials", "Level"}, + } + + for i, r := range recipes { + 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) + } + + matStr := g.formatRecipeMaterials(&r) + levelStr := fmt.Sprint(r.Level) + numStr := fmt.Sprintf("%d", i+1) + + canMake := skillLevel >= r.Level && r.HasAllItemsQty(p.CountItem) + 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[lastFlagKey].(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("%s what (enter for all %s): ", promptVerb, hint)) + } else { + sess.Write(fmt.Sprintf("%s what: ", promptVerb)) + } + + sess.PendingMenu = recipeMenuData(recipes) + sess.PendingSkill = skill + sess.PendingLastFlag = lastFlagKey + sess.PendingBackground = background + sess.State = net.StateProductChoice +} + +func (g *Game) handleProductChoice(sess *net.Session, input string) { + p := sess.Player.(*player.Player) + menuData := sess.PendingMenu + skill := sess.PendingSkill + lastFlagKey := sess.PendingLastFlag + background := sess.PendingBackground + sess.PendingMenu = nil + sess.PendingSkill = "" + sess.PendingLastFlag = "" + sess.PendingBackground = false + 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[lastFlagKey].(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 + } + + skillLevel := p.Level(player.SkillName(skill)) + if skillLevel < sel.Recipe.Level { + sess.WriteLine(fmt.Sprintf("You need level %d %s to make that.", sel.Recipe.Level, skill)) + g.reprompt(sess) + return + } + if !sel.Recipe.HasAllItemsQty(p.CountItem) { + sess.WriteLine("You don't have the materials for that.") + g.reprompt(sess) + return + } + + if p.Flags == nil { + p.Flags = make(map[string]any) + } + p.Flags[lastFlagKey] = sel.Recipe.ID + g.AccountStore.SaveCharacter(p) + + if background { + g.startFletchAction(sess, p, sel.Recipe, sel.Count) + } else { + g.startProductionFromRecipe(sess, p, sel.Recipe, sel.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 +} |
