aboutsummaryrefslogtreecommitdiff
path: root/internal/game/core_production.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/core_production.go')
-rw-r--r--internal/game/core_production.go803
1 files changed, 803 insertions, 0 deletions
diff --git a/internal/game/core_production.go b/internal/game/core_production.go
new file mode 100644
index 0000000..227c4ad
--- /dev/null
+++ b/internal/game/core_production.go
@@ -0,0 +1,803 @@
+package game
+
+import (
+ "fmt"
+ "math/rand"
+ "sort"
+ "strconv"
+ "strings"
+
+ "thehouseoficarus/internal/action"
+ "thehouseoficarus/internal/color"
+ "thehouseoficarus/internal/engine"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
+ "thehouseoficarus/internal/player"
+
+)
+
+func (g *Game) startProduction(sess *net.Session, p *player.Player, recipe *action.RecipeDef, actionType, displayVerb, startMsg, endMsg string, count int) {
+ g.cancelAction(p)
+ g.cancelBackgroundAction(p)
+
+ 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, skill))
+ g.reprompt(sess)
+ return
+ }
+ }
+
+ if !recipe.HasAllItemsQty(p.CountItem) {
+ sess.WriteLine("You don't have the required materials.")
+ g.reprompt(sess)
+ return
+ }
+
+ outDef, _ := g.ItemStore.Load(recipe.Output)
+
+ wait := recipe.Wait
+ if wait <= 0 {
+ wait = 4
+ }
+
+ outputName := recipe.Output
+ if outDef != nil {
+ outputName = outDef.Name
+ }
+
+ p.Action = &action.Action{
+ Type: actionType,
+ TargetID: recipe.ID,
+ TargetName: outputName,
+ Data: map[string]any{
+ "recipe_id": recipe.ID,
+ "phase": 0,
+ "wait": wait,
+ "start_msg": startMsg,
+ "end_msg": endMsg,
+ "remaining": count,
+ },
+ WaitLeft: engine.ToTicks(1),
+ }
+
+ p.ActionState = &ActionState{Type: ActionProducing, TargetName: outputName, Verb: displayVerb}
+
+ if g.Hub != nil {
+ for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
+ if other != sess && other.Player != nil {
+ other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s starts %s.", p.Name, displayVerb)))
+ }
+ }
+ }
+}
+
+func (g *Game) startProductionFromRecipe(sess *net.Session, p *player.Player, recipe *action.RecipeDef, count int) {
+ _, stationName := g.findStation(p.RoomID, recipe.Station)
+ if stationName == "" {
+ stationName = "inventory"
+ }
+
+ firstItem := recipe.FirstItemName(func(id string) (string, bool) {
+ def, err := g.ItemStore.Load(id)
+ if err != nil {
+ return id, false
+ }
+ return def.Name, true
+ })
+
+ 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)
+}
+
+func (g *Game) loadProductionRecipe(recipeID string) *action.RecipeDef {
+ all, err := g.RecipeStore.LoadAll()
+ if err == nil {
+ for _, r := range all {
+ if r.ID == recipeID {
+ return &r
+ }
+ }
+ }
+ if strings.HasPrefix(recipeID, "combine_") {
+ itemID := strings.TrimPrefix(recipeID, "combine_")
+ if itemDef, err := g.ItemStore.Load(itemID); err == nil && len(itemDef.MadeFrom) > 0 {
+ return g.buildCombineRecipe(itemDef)
+ }
+ }
+ return nil
+}
+
+func (g *Game) advanceProduction(sess *net.Session, p *player.Player) bool {
+ recipeID := p.Action.Data["recipe_id"].(string)
+ phase := p.Action.Data["phase"].(int)
+ wait := p.Action.Data["wait"].(float64)
+ startMsg, _ := p.Action.Data["start_msg"].(string)
+ endMsg, _ := p.Action.Data["end_msg"].(string)
+ remaining, _ := p.Action.Data["remaining"].(int)
+
+ recipe := g.loadProductionRecipe(recipeID)
+ if recipe == nil {
+ g.cancelAction(p)
+ return false
+ }
+
+ if phase == 0 {
+ if startMsg != "" {
+ sess.WriteLine(fmt.Sprintf("\n%s", startMsg))
+ }
+ p.Action.Data["phase"] = 1
+ p.Action.WaitLeft = engine.ToTicks(wait)
+ return true
+ }
+
+ skill := recipe.EffectiveSkill()
+ skillLevel := p.Level(player.SkillName(skill))
+ chance := 1.0
+ if recipe.Success != nil {
+ chance = action.SuccessChance(*recipe.Success, skillLevel, recipe.Level)
+ }
+
+ if rand.Float64() < chance {
+ outputQty := recipe.OutputQty
+ if outputQty <= 0 {
+ outputQty = 1
+ }
+
+ byproducts := g.collectByproducts(p, recipe)
+
+ placed := false
+ outDef, _ := g.ItemStore.Load(recipe.Output)
+ if outDef != nil && outDef.Stackable {
+ for i := 0; i < 28; i++ {
+ slot := p.InvSlot(i)
+ if slot != nil && slot.ItemID == recipe.Output {
+ recipe.ConsumeAll(p.HasItem, p.RemoveItem)
+ slot.Quantity += outputQty
+ placed = true
+ break
+ }
+ }
+ }
+ if !placed {
+ recipe.ConsumeAll(p.HasItem, p.RemoveItem)
+ freeSlot := p.FirstFreeSlot()
+ if freeSlot == -1 {
+ sess.WriteLine("Your inventory is too full!")
+ g.cancelAction(p)
+ return false
+ }
+ p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: recipe.Output, Quantity: outputQty})
+ }
+
+ for _, bp := range byproducts {
+ if slot := p.FirstFreeSlot(); slot >= 0 {
+ p.SetInvSlot(slot, &player.InventorySlot{ItemID: bp, Quantity: 1})
+ }
+ }
+
+ if recipe.XP > 0 {
+ 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)
+
+ msg := recipe.Message
+ if msg == "" {
+ outputName := recipe.Output
+ if outDef != nil {
+ outputName = outDef.Name
+ }
+ msg = fmt.Sprintf("You produce %s.", outputName)
+ }
+ if p.OptionBool("xp_drops") && recipe.XP > 0 {
+ abbr := player.SkillAbbr[player.SkillName(skill)]
+ msg += g.colorize(sess, "xp", fmt.Sprintf(" (+%dxp %s)", recipe.XP, abbr))
+ }
+ sess.WriteLine(msg)
+ } else {
+ recipe.ConsumeAll(p.HasItem, p.RemoveItem)
+
+ if recipe.Fail != "" {
+ freeSlot := p.FirstFreeSlot()
+ if freeSlot >= 0 {
+ p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: recipe.Fail, Quantity: 1})
+ }
+ }
+ g.AccountStore.SaveCharacter(p)
+
+ msg := recipe.FailMessage
+ if msg == "" {
+ msg = "You fail and the materials are lost."
+ }
+ sess.WriteLine(g.colorize(sess, "damage", msg))
+ }
+
+ if remaining > 0 {
+ remaining--
+ p.Action.Data["remaining"] = remaining
+ if remaining <= 0 {
+ if endMsg != "" {
+ sess.WriteLine(fmt.Sprintf("\n%s", endMsg))
+ }
+ g.cancelAction(p)
+ return false
+ }
+ }
+
+ if g.canContinueProduction(p, recipe) {
+ p.Action.WaitLeft = engine.ToTicks(wait)
+ return true
+ }
+
+ if endMsg != "" {
+ sess.WriteLine(fmt.Sprintf("\n%s", endMsg))
+ }
+ g.cancelAction(p)
+ return false
+}
+
+func (g *Game) collectByproducts(p *player.Player, recipe *action.RecipeDef) []string {
+ var byproducts []string
+ for _, e := range recipe.Consume {
+ if len(e.Byproducts) == 0 {
+ continue
+ }
+ for i, id := range e.Items {
+ if p.HasItem(id) && i < len(e.Byproducts) && e.Byproducts[i] != "" {
+ byproducts = append(byproducts, e.Byproducts[i])
+ break
+ }
+ }
+ }
+ return byproducts
+}
+
+func (g *Game) canContinueProduction(p *player.Player, recipe *action.RecipeDef) bool {
+ if !recipe.HasAllItemsQty(p.CountItem) {
+ return false
+ }
+ outputQty := recipe.OutputQty
+ if outputQty <= 0 {
+ outputQty = 1
+ }
+ outDef, _ := g.ItemStore.Load(recipe.Output)
+ if outDef != nil && outDef.Stackable {
+ for i := 0; i < 28; i++ {
+ slot := p.InvSlot(i)
+ if slot != nil && slot.ItemID == recipe.Output {
+ return true
+ }
+ }
+ }
+ return p.FirstFreeSlot() >= 0
+}
+
+func (g *Game) handleRecipeChoice(sess *net.Session, input string) {
+ input = strings.TrimSpace(input)
+
+ menuData := sess.PendingMenu
+ if len(menuData) == 0 {
+ sess.State = net.StateGame
+ g.reprompt(sess)
+ return
+ }
+
+ if input == "" {
+ sess.PendingMenu = nil
+ sess.State = net.StateGame
+ sess.WriteLine("Never mind.")
+ g.reprompt(sess)
+ return
+ }
+
+ if choice, err := strconv.Atoi(input); err == nil {
+ sess.PendingMenu = nil
+ sess.State = net.StateGame
+ if choice <= 0 || choice > len(menuData) {
+ sess.WriteLine("Never mind.")
+ g.reprompt(sess)
+ return
+ }
+ g.dispatchMenuEntry(sess, menuData[choice-1])
+ return
+ }
+
+ lower := strings.ToLower(input)
+ matchIdx := -1
+ for i, entry := range menuData {
+ name := g.menuEntryName(entry)
+ if name == "" {
+ continue
+ }
+ if strings.ToLower(name) == lower || action.WordPrefixMatch(input, name) {
+ if matchIdx >= 0 {
+ sess.WriteLine("That's ambiguous.")
+ return
+ }
+ matchIdx = i
+ }
+ }
+ if matchIdx >= 0 {
+ sess.PendingMenu = nil
+ sess.State = net.StateGame
+ g.dispatchMenuEntry(sess, menuData[matchIdx])
+ return
+ }
+
+ sess.PendingMenu = nil
+ sess.State = net.StateGame
+ sess.WriteLine("Never mind.")
+ g.reprompt(sess)
+}
+
+func (g *Game) menuEntryName(entry map[string]string) string {
+ if rid, ok := entry["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 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
+ }
+ return barID
+ }
+ if cid, ok := entry["combine_item"]; ok {
+ if def, _ := g.ItemStore.Load(cid); def != nil {
+ return def.Name
+ }
+ return cid
+ }
+ return ""
+}
+
+func (g *Game) dispatchMenuEntry(sess *net.Session, entry map[string]string) {
+ if cid, ok := entry["combine_item"]; ok {
+ g.promptHowMany(sess, "combine_"+cid)
+ return
+ }
+ if barID, ok := entry["bar_id"]; ok {
+ p := sess.Player
+ allRecipes, _ := g.RecipeStore.LoadAll()
+ g.showSmithTable(sess, p, barID, allRecipes)
+ return
+ }
+ if rid, ok := entry["fletch_recipe_id"]; ok {
+ p := sess.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.Quantity > 1 {
+ parts = append(parts, fmt.Sprintf("%s x%d", itemName, e.Quantity))
+ } 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(recipeDefIDs(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
+ 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
+ }
+
+ p.EnsureFlags()
+ 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
+}
+type recipeEntry struct {
+ ItemName string
+ 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"},
+ "pharmacy": {"mix", "mixing"},
+ "construction": {"construct", "constructing"},
+}
+
+var productionActionTypes map[string]bool
+
+func init() {
+ productionActionTypes = make(map[string]bool)
+ for _, pt := range productionTypes {
+ productionActionTypes[pt.ActionType] = true
+ }
+}
+
+func recipeMenuData(recipeIDs []string) []map[string]string {
+ out := make([]map[string]string, len(recipeIDs))
+ for i, id := range recipeIDs {
+ out[i] = map[string]string{"recipe_id": id}
+ }
+ return out
+}
+
+func recipeDefIDs(defs []action.RecipeDef) []string {
+ ids := make([]string, len(defs))
+ for i, d := range defs {
+ ids[i] = d.ID
+ }
+ return ids
+}
+
+func entryMenuData(entries []recipeEntry) []map[string]string {
+ out := make([]map[string]string, len(entries))
+ for i, e := range entries {
+ out[i] = map[string]string{"recipe_id": e.Recipe.ID}
+ }
+ return out
+}
+
+func (g *Game) promptHowMany(sess *net.Session, recipeID string) {
+ sess.PendingRecipeID = recipeID
+ sess.State = net.StateHowMany
+ sess.Write("How many (return for all)?: ")
+}
+
+type recipeSelection struct {
+ Recipe *action.RecipeDef
+ Count int
+}
+
+func (g *Game) resolveRecipeByInput(input string, recipes []action.RecipeDef, lastRecipeID string) (sel recipeSelection, errMsg string) {
+ if input == "" {
+ if lastRecipeID == "" {
+ return sel, "never_mind"
+ }
+ for i := range recipes {
+ if recipes[i].ID == lastRecipeID {
+ sel.Recipe = &recipes[i]
+ return sel, ""
+ }
+ }
+ return sel, "never_mind"
+ }
+
+ qty, productName := parseQty(input)
+ productName = strings.TrimSpace(productName)
+
+ if idx, err := strconv.Atoi(productName); err == nil && idx > 0 && idx <= len(recipes) {
+ sel.Recipe = &recipes[idx-1]
+ sel.Count = qty
+ return sel, ""
+ }
+
+ matchCount := 0
+ for i := range recipes {
+ outDef, _ := g.ItemStore.Load(recipes[i].Output)
+ name := recipes[i].Output
+ if outDef != nil {
+ name = outDef.Name
+ }
+ if action.WordPrefixMatch(productName, name) {
+ if matchCount == 0 {
+ sel.Recipe = &recipes[i]
+ sel.Count = qty
+ }
+ matchCount++
+ }
+ }
+ if matchCount > 1 {
+ return sel, "ambiguous"
+ }
+ if sel.Recipe == nil {
+ return sel, "never_mind"
+ }
+ return sel, ""
+}
+
+func (g *Game) showMenuTable(sess *net.Session, title string, names []string) {
+ p := sess.Player
+ tbl := &Table{Title: title}
+ for i, name := range names {
+ tbl.Rows = append(tbl.Rows, []string{fmt.Sprintf("%d", i+1), name})
+ }
+ unicode := p.OptionBool("unicode")
+ sess.WriteLine("")
+ for _, line := range tbl.Render(unicode) {
+ sess.WriteLine(line)
+ }
+}
+
+func (g *Game) handleHowMany(sess *net.Session, input string) {
+ p := sess.Player
+ recipeID := sess.PendingRecipeID
+ sess.PendingRecipeID = ""
+ sess.State = net.StateGame
+
+ input = strings.TrimSpace(input)
+
+ var count int
+ if input == "" {
+ count = 0
+ } else if n, err := strconv.Atoi(input); err == nil && n > 0 {
+ count = n
+ } else {
+ sess.WriteLine("Never mind.")
+ g.reprompt(sess)
+ return
+ }
+
+ var recipe *action.RecipeDef
+ if strings.HasPrefix(recipeID, "combine_") {
+ itemID := strings.TrimPrefix(recipeID, "combine_")
+ itemDef, err := g.ItemStore.Load(itemID)
+ if err != nil || len(itemDef.MadeFrom) == 0 {
+ g.reprompt(sess)
+ return
+ }
+ recipe = g.buildCombineRecipe(itemDef)
+ } else {
+ all, err := g.RecipeStore.LoadAll()
+ if err != nil {
+ g.reprompt(sess)
+ return
+ }
+ for _, r := range all {
+ if r.ID == recipeID {
+ recipe = &r
+ break
+ }
+ }
+ }
+
+ if recipe == nil {
+ g.reprompt(sess)
+ return
+ }
+
+ g.startProductionFromRecipe(sess, p, recipe, count)
+}
+
+func (g *Game) buildCombineRecipe(def *object.ItemDef) *action.RecipeDef {
+ var consume []action.ConsumeEntry
+ for _, mf := range def.MadeFrom {
+ consume = append(consume, action.ConsumeEntry{
+ Items: mf.Items,
+ Quantity: mf.Quantity,
+ Byproducts: mf.Byproducts,
+ })
+ }
+ wait := def.Ticks
+ if wait <= 0 {
+ wait = 2
+ }
+ return &action.RecipeDef{
+ ID: "combine_" + def.ID,
+ Type: "combine",
+ Wait: wait,
+ Consume: consume,
+ Output: def.ID,
+ Message: fmt.Sprintf("You create %s.", def.Name),
+ }
+}