aboutsummaryrefslogtreecommitdiff
path: root/internal/game
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game')
-rw-r--r--internal/game/action_clean.go60
-rw-r--r--internal/game/action_farm.go4
-rw-r--r--internal/game/action_fletch.go110
-rw-r--r--internal/game/action_steal.go2
-rw-r--r--internal/game/cmd_bank.go4
-rw-r--r--internal/game/cmd_clean.go42
-rw-r--r--internal/game/cmd_cook.go42
-rw-r--r--internal/game/cmd_craft.go185
-rw-r--r--internal/game/cmd_fletch.go181
-rw-r--r--internal/game/cmd_shop.go2
-rw-r--r--internal/game/cmd_smelt.go36
-rw-r--r--internal/game/cmd_smith.go59
-rw-r--r--internal/game/cmd_trigger_utility.go44
-rw-r--r--internal/game/cmd_use.go264
-rw-r--r--internal/game/cmd_verbs.go4
-rw-r--r--internal/game/core_production.go406
-rw-r--r--internal/game/core_startup.go95
-rw-r--r--internal/game/craft_index.go234
-rw-r--r--internal/game/game.go14
19 files changed, 946 insertions, 842 deletions
diff --git a/internal/game/action_clean.go b/internal/game/action_clean.go
index 7330883..b85a3e0 100644
--- a/internal/game/action_clean.go
+++ b/internal/game/action_clean.go
@@ -19,37 +19,31 @@ func (g *Game) advanceClean(sess *net.Session, p *player.Player) {
return
}
- allRecipes, err := g.RecipeStore.LoadAll()
- if err != nil {
- g.cancelBgAction(p)
- return
- }
+ items := g.CraftIndex.ByType("clean")
- var recipe *cleanMatch
- for _, r := range allRecipes {
- if r.Type != "clean" {
- continue
- }
+ var match *cleanMatch
+ for _, item := range items {
+ c := item.FirstCraft()
if filter != "" {
- if len(r.Consume) == 0 || len(r.Consume[0].Items) == 0 {
+ if len(c.Consume) == 0 || len(c.Consume[0].Items) == 0 {
continue
}
- if !strings.Contains(r.Consume[0].Items[0], filter) &&
- !strings.Contains(r.Output, filter) {
+ if !strings.Contains(c.Consume[0].Items[0], filter) &&
+ !strings.Contains(item.ID, filter) {
continue
}
}
- if p.Level(player.Pharmacy) < r.Level {
+ if p.Level(player.Pharmacy) < c.Level {
continue
}
- if !r.HasAllItems(p.HasItem) {
+ if !craftHasAllItems(c, p.HasItem) {
continue
}
- recipe = &cleanMatch{r.Consume[0].Items[0], r.Output, r.XP, r.Message}
+ match = &cleanMatch{c.Consume[0].Items[0], item.ID, c.XP, c.Message}
break
}
- if recipe == nil {
+ if match == nil {
sess.WriteLine("\nYou've finished cleaning herbs.")
g.cancelBgAction(p)
return
@@ -57,47 +51,45 @@ func (g *Game) advanceClean(sess *net.Session, p *player.Player) {
for i := 0; i < 28; i++ {
slot := p.InvSlot(i)
- if slot != nil && slot.ItemID == recipe.consumeID {
- slot.ItemID = recipe.outputID
+ if slot != nil && slot.ItemID == match.consumeID {
+ slot.ItemID = match.outputID
break
}
}
- if recipe.xp > 0 {
- g.awardSkillXP(sess, p, player.Pharmacy, recipe.xp)
+ if match.xp > 0 {
+ g.awardSkillXP(sess, p, player.Pharmacy, match.xp)
}
g.AccountStore.SaveCharacter(p)
- msg := recipe.message
+ msg := match.message
if msg == "" {
- outDef, _ := g.ItemStore.Load(recipe.outputID)
- outputName := recipe.outputID
+ outDef, _ := g.ItemStore.Load(match.outputID)
+ outputName := match.outputID
if outDef != nil {
outputName = outDef.Name
}
msg = fmt.Sprintf("You clean a %s.", outputName)
}
- if p.OptionBool("xp_drops") && recipe.xp > 0 {
+ if p.OptionBool("xp_drops") && match.xp > 0 {
abbr := player.SkillAbbr[player.Pharmacy]
- msg += g.colorize(sess, "xp", fmt.Sprintf(" (+%dxp %s)", recipe.xp, abbr))
+ msg += g.colorize(sess, "xp", fmt.Sprintf(" (+%dxp %s)", match.xp, abbr))
}
sess.WriteLine(msg)
hasMore := false
- for _, r := range allRecipes {
- if r.Type != "clean" {
- continue
- }
+ for _, item := range items {
+ c := item.FirstCraft()
if filter != "" {
- if len(r.Consume) == 0 || len(r.Consume[0].Items) == 0 {
+ if len(c.Consume) == 0 || len(c.Consume[0].Items) == 0 {
continue
}
- if !strings.Contains(r.Consume[0].Items[0], filter) &&
- !strings.Contains(r.Output, filter) {
+ if !strings.Contains(c.Consume[0].Items[0], filter) &&
+ !strings.Contains(item.ID, filter) {
continue
}
}
- if p.Level(player.Pharmacy) >= r.Level && r.HasAllItems(p.HasItem) {
+ if p.Level(player.Pharmacy) >= c.Level && craftHasAllItems(c, p.HasItem) {
hasMore = true
break
}
diff --git a/internal/game/action_farm.go b/internal/game/action_farm.go
index 21604aa..679af83 100644
--- a/internal/game/action_farm.go
+++ b/internal/game/action_farm.go
@@ -6,8 +6,8 @@ import (
"sort"
"strings"
- "thehouseoficarus/internal/action"
"thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
"thehouseoficarus/internal/player"
"thehouseoficarus/internal/world"
)
@@ -484,7 +484,7 @@ func (g *Game) farmMatchPrefix(p *player.Player, input string) (prefix string, d
lower := strings.ToLower(input)
for _, f := range farms {
- if strings.Contains(lower, strings.ToLower(f.name)) || action.WordPrefixMatch(lower, f.name) {
+ if strings.Contains(lower, strings.ToLower(f.name)) || object.WordPrefixMatch(lower, f.name) {
return f.prefix, f.defID, f.index
}
}
diff --git a/internal/game/action_fletch.go b/internal/game/action_fletch.go
index 3d8c30f..52de86b 100644
--- a/internal/game/action_fletch.go
+++ b/internal/game/action_fletch.go
@@ -6,43 +6,45 @@ import (
"thehouseoficarus/internal/action"
"thehouseoficarus/internal/engine"
"thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
"thehouseoficarus/internal/player"
)
-func (g *Game) startFletchAction(sess *net.Session, p *player.Player, recipe *action.RecipeDef, count int) {
+func (g *Game) startFletchAction(sess *net.Session, p *player.Player, item *object.ItemDef, count int) {
p.EnsureFlags()
- p.Flags["last_fletch"] = recipe.ID
+ p.Flags["last_fletch"] = item.ID
g.AccountStore.SaveCharacter(p)
- if recipe.Wait <= 0 {
- g.doInstantFletch(sess, p, recipe, count)
+ if item.FirstCraft().Wait <= 0 {
+ g.doInstantFletch(sess, p, item, count)
return
}
- g.startTimedFletch(sess, p, recipe, count)
+ g.startTimedFletch(sess, p, item, count)
}
-func (g *Game) doInstantFletch(sess *net.Session, p *player.Player, recipe *action.RecipeDef, count int) {
- outDef, _ := g.ItemStore.Load(recipe.Output)
- outputQty := recipe.OutputQty
+func (g *Game) doInstantFletch(sess *net.Session, p *player.Player, item *object.ItemDef, count int) {
+ c := item.FirstCraft()
+ outputQty := c.OutputQty
if outputQty <= 0 {
outputQty = 1
}
batches := 0
totalOutput := 0
- skill := player.SkillName(recipe.EffectiveSkill())
+ skill := player.SkillName(c.EffectiveSkill())
+ tmpItem := &object.ItemDef{Craft: object.CraftList{*c}}
for {
- if !g.canDoFletchRecipe(p, recipe) {
+ if !g.canDoFletchItem(p, tmpItem) {
break
}
placed := false
- if outDef != nil && outDef.Stackable {
+ if item.Stackable {
for i := 0; i < 28; i++ {
slot := p.InvSlot(i)
- if slot != nil && slot.ItemID == recipe.Output {
- recipe.ConsumeAll(p.HasItem, p.RemoveItem)
+ if slot != nil && slot.ItemID == item.ID {
+ craftConsumeAll(c, p.HasItem, p.RemoveItem)
slot.Quantity += outputQty
placed = true
break
@@ -50,16 +52,16 @@ func (g *Game) doInstantFletch(sess *net.Session, p *player.Player, recipe *acti
}
}
if !placed {
- recipe.ConsumeAll(p.HasItem, p.RemoveItem)
+ craftConsumeAll(c, p.HasItem, p.RemoveItem)
freeSlot := p.FirstFreeSlot()
if freeSlot == -1 {
break
}
- p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: recipe.Output, Quantity: outputQty})
+ p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: item.ID, Quantity: outputQty})
}
- if recipe.XP > 0 {
- g.awardSkillXP(sess, p, skill, recipe.XP)
+ if c.XP > 0 {
+ g.awardSkillXP(sess, p, skill, c.XP)
}
batches++
@@ -80,57 +82,47 @@ func (g *Game) doInstantFletch(sess *net.Session, p *player.Player, recipe *acti
g.AccountStore.SaveCharacter(p)
- outputName := recipe.Output
- if outDef != nil {
- outputName = outDef.Name
- }
-
- msg := fmt.Sprintf("You fletch %d %s.", totalOutput, outputName)
- if p.OptionBool("xp_drops") && recipe.XP > 0 {
- totalXP := recipe.XP * batches
+ msg := fmt.Sprintf("You fletch %d %s.", totalOutput, item.Name)
+ if p.OptionBool("xp_drops") && c.XP > 0 {
+ totalXP := c.XP * batches
abbr := player.SkillAbbr[skill]
msg += g.colorize(sess, "xp", fmt.Sprintf(" (+%dxp %s)", totalXP, abbr))
}
sess.WriteLine(msg)
}
-func (g *Game) startTimedFletch(sess *net.Session, p *player.Player, recipe *action.RecipeDef, count int) {
- outDef, _ := g.ItemStore.Load(recipe.Output)
- outputName := recipe.Output
- if outDef != nil {
- outputName = outDef.Name
- }
-
+func (g *Game) startTimedFletch(sess *net.Session, p *player.Player, item *object.ItemDef, count int) {
p.BackgroundAction = &action.Action{
Type: "fletch",
- TargetID: recipe.ID,
- TargetName: outputName,
+ TargetID: item.ID,
+ TargetName: item.Name,
Data: map[string]any{
- "recipe_id": recipe.ID,
+ "item_id": item.ID,
"phase": 0,
- "wait": recipe.Wait,
+ "wait": item.FirstCraft().Wait,
"remaining": count,
},
WaitLeft: engine.ToTicks(1),
}
- p.BackgroundActionState = &ActionState{Type: ActionFletching, TargetName: outputName}
+ p.BackgroundActionState = &ActionState{Type: ActionFletching, TargetName: item.Name}
g.broadcastAction(sess, "\n%s starts fletching.", p.Name)
- sess.WriteLine(fmt.Sprintf("\nYou begin fletching %s.", outputName))
+ sess.WriteLine(fmt.Sprintf("\nYou begin fletching %s.", item.Name))
}
func (g *Game) advanceFletch(sess *net.Session, p *player.Player) {
- recipeID, _ := p.BackgroundAction.Data["recipe_id"].(string)
+ itemID, _ := p.BackgroundAction.Data["item_id"].(string)
phase, _ := p.BackgroundAction.Data["phase"].(int)
wait, _ := p.BackgroundAction.Data["wait"].(float64)
remaining, _ := p.BackgroundAction.Data["remaining"].(int)
- recipe := g.loadRecipe(recipeID)
- if recipe == nil {
+ item := g.loadCraftItem(itemID)
+ if item == nil {
g.cancelBgAction(p)
return
}
+ c := item.FirstCraft()
if phase == 0 {
p.BackgroundAction.Data["phase"] = 1
@@ -138,26 +130,26 @@ func (g *Game) advanceFletch(sess *net.Session, p *player.Player) {
return
}
- if !g.canDoFletchRecipe(p, recipe) {
+ tmpItem := &object.ItemDef{Craft: object.CraftList{*c}}
+ if !g.canDoFletchItem(p, tmpItem) {
sess.WriteLine("\nYou've run out of fletching materials.")
g.cancelBgAction(p)
return
}
- outDef, _ := g.ItemStore.Load(recipe.Output)
- outputQty := recipe.OutputQty
+ outputQty := c.OutputQty
if outputQty <= 0 {
outputQty = 1
}
- skill := player.SkillName(recipe.EffectiveSkill())
+ skill := player.SkillName(c.EffectiveSkill())
placed := false
- if outDef != nil && outDef.Stackable {
+ if item.Stackable {
for i := 0; i < 28; i++ {
slot := p.InvSlot(i)
- if slot != nil && slot.ItemID == recipe.Output {
- recipe.ConsumeAll(p.HasItem, p.RemoveItem)
+ if slot != nil && slot.ItemID == item.ID {
+ craftConsumeAll(c, p.HasItem, p.RemoveItem)
slot.Quantity += outputQty
placed = true
break
@@ -165,32 +157,28 @@ func (g *Game) advanceFletch(sess *net.Session, p *player.Player) {
}
}
if !placed {
- recipe.ConsumeAll(p.HasItem, p.RemoveItem)
+ craftConsumeAll(c, p.HasItem, p.RemoveItem)
freeSlot := p.FirstFreeSlot()
if freeSlot == -1 {
sess.WriteLine("\nYour inventory is too full!")
g.cancelBgAction(p)
return
}
- p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: recipe.Output, Quantity: outputQty})
+ p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: item.ID, Quantity: outputQty})
}
- if recipe.XP > 0 {
- g.awardSkillXP(sess, p, skill, recipe.XP)
+ if c.XP > 0 {
+ g.awardSkillXP(sess, p, skill, c.XP)
}
g.AccountStore.SaveCharacter(p)
- outputName := recipe.Output
- if outDef != nil {
- outputName = outDef.Name
- }
- msg := recipe.Message
+ msg := c.Message
if msg == "" {
- msg = fmt.Sprintf("You fletch %s.", outputName)
+ msg = fmt.Sprintf("You fletch %s.", item.Name)
}
- if p.OptionBool("xp_drops") && recipe.XP > 0 {
+ if p.OptionBool("xp_drops") && c.XP > 0 {
abbr := player.SkillAbbr[skill]
- msg += g.colorize(sess, "xp", fmt.Sprintf(" (+%dxp %s)", recipe.XP, abbr))
+ msg += g.colorize(sess, "xp", fmt.Sprintf(" (+%dxp %s)", c.XP, abbr))
}
sess.WriteLine(msg)
@@ -204,7 +192,7 @@ func (g *Game) advanceFletch(sess *net.Session, p *player.Player) {
}
}
- if !g.canContinueProduction(p, recipe) {
+ if !g.canContinueProduction(p, item) {
sess.WriteLine("\nYou've run out of fletching materials.")
g.cancelBgAction(p)
return
diff --git a/internal/game/action_steal.go b/internal/game/action_steal.go
index a95be25..259c914 100644
--- a/internal/game/action_steal.go
+++ b/internal/game/action_steal.go
@@ -169,7 +169,7 @@ func (g *Game) resolveStealTarget(sess *net.Session, p *player.Player, input str
var matchingObjs []*object.ObjectDef
for _, o := range stealObjs {
- if action.WordPrefixMatch(searchName, o.Name) {
+ if object.WordPrefixMatch(searchName, o.Name) {
matchingObjs = append(matchingObjs, o)
}
}
diff --git a/internal/game/cmd_bank.go b/internal/game/cmd_bank.go
index d91e115..5f9067d 100644
--- a/internal/game/cmd_bank.go
+++ b/internal/game/cmd_bank.go
@@ -5,8 +5,8 @@ import (
"sort"
"strings"
- "thehouseoficarus/internal/action"
"thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
"thehouseoficarus/internal/player"
)
@@ -239,7 +239,7 @@ func (g *Game) doBankWithdraw(sess *net.Session, input string) {
}
lower := strings.ToLower(input)
for _, e := range entries {
- if action.WordPrefixMatch(e.name, lower) {
+ if object.WordPrefixMatch(e.name, lower) {
matches = append(matches, e)
}
}
diff --git a/internal/game/cmd_clean.go b/internal/game/cmd_clean.go
index 951b41d..28758b8 100644
--- a/internal/game/cmd_clean.go
+++ b/internal/game/cmd_clean.go
@@ -7,6 +7,7 @@ import (
"thehouseoficarus/internal/combat"
"thehouseoficarus/internal/engine"
"thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
"thehouseoficarus/internal/player"
)
@@ -22,26 +23,19 @@ func (g *Game) doClean(sess *net.Session, input string) {
return
}
- allRecipes, err := g.RecipeStore.LoadAll()
- if err != nil {
- sess.WriteLine("Error loading recipes.")
- return
- }
+ items := g.CraftIndex.ByType("clean")
filter := strings.TrimSpace(input)
found := false
- for _, r := range allRecipes {
- if r.Type != "clean" {
- continue
- }
- if filter != "" && !matchesCleanFilter(r, filter) {
+ for _, item := range items {
+ if filter != "" && !matchesCleanFilter(item, filter) {
continue
}
- if p.Level(player.Pharmacy) < r.Level {
+ if p.Level(player.Pharmacy) < item.FirstCraft().Level {
continue
}
- if !r.HasAllItems(p.HasItem) {
+ if !craftHasAllItems(item.FirstCraft(), p.HasItem) {
continue
}
found = true
@@ -49,7 +43,21 @@ func (g *Game) doClean(sess *net.Session, input string) {
}
if !found {
- sess.WriteLine("You don't have any herbs to clean.")
+ hasHerbs := false
+ for _, item := range items {
+ if filter != "" && !matchesCleanFilter(item, filter) {
+ continue
+ }
+ if craftHasAllItems(item.FirstCraft(), p.HasItem) {
+ hasHerbs = true
+ break
+ }
+ }
+ if hasHerbs {
+ sess.WriteLine("You don't have the Pharmacy level to clean any of your herbs.")
+ } else {
+ sess.WriteLine("You don't have any herbs to clean.")
+ }
return
}
@@ -71,13 +79,13 @@ func (g *Game) doClean(sess *net.Session, input string) {
sess.WriteLine("\nYou begin cleaning herbs.")
}
-func matchesCleanFilter(r action.RecipeDef, filter string) bool {
- if len(r.Consume) > 0 && len(r.Consume[0].Items) > 0 {
- if strings.Contains(r.Consume[0].Items[0], filter) {
+func matchesCleanFilter(item *object.ItemDef, filter string) bool {
+ if len(item.Craft) > 0 && len(item.FirstCraft().Consume) > 0 && len(item.FirstCraft().Consume[0].Items) > 0 {
+ if strings.Contains(item.FirstCraft().Consume[0].Items[0], filter) {
return true
}
}
- if strings.Contains(r.Output, filter) {
+ if strings.Contains(item.ID, filter) {
return true
}
return false
diff --git a/internal/game/cmd_cook.go b/internal/game/cmd_cook.go
index 04c98bb..6dfbcce 100644
--- a/internal/game/cmd_cook.go
+++ b/internal/game/cmd_cook.go
@@ -4,8 +4,8 @@ import (
"fmt"
"strings"
- "thehouseoficarus/internal/action"
"thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
"thehouseoficarus/internal/player"
)
@@ -23,14 +23,10 @@ func (g *Game) doCook(sess *net.Session, input string) {
return
}
- allRecipes, err := g.RecipeStore.LoadAll()
- if err != nil {
- sess.WriteLine("Error loading recipes.")
- return
- }
+ items := g.CraftIndex.ByType("cooking")
if input == "" {
- g.showCookMenu(sess, p, allRecipes, stationDefID, stationName)
+ g.showCookMenu(sess, p, items, stationDefID, stationName)
return
}
@@ -51,13 +47,11 @@ func (g *Game) doCook(sess *net.Session, input string) {
itemID := matches[0].ID
- var recipes []action.RecipeDef
- for _, r := range allRecipes {
- if r.Type != "cooking" {
- continue
- }
- if stationMatch(stationDefID, r.Station) && r.MatchesEntry(itemID) && r.HasAllItems(p.HasItem) {
- recipes = append(recipes, r)
+ var recipes []*object.ItemDef
+ for _, item := range items {
+ c := item.FirstCraft()
+ if stationMatch(stationDefID, c.Station) && craftMatchesEntry(c, itemID) && craftHasAllItems(c, p.HasItem) {
+ recipes = append(recipes, item)
}
}
@@ -65,25 +59,23 @@ func (g *Game) doCook(sess *net.Session, input string) {
g.showRecipeChoice(sess, p, recipes, "You can't cook that.", title, "")
}
-func (g *Game) showCookMenu(sess *net.Session, p *player.Player, allRecipes []action.RecipeDef, stationDefID, stationName string) {
+func (g *Game) showCookMenu(sess *net.Session, p *player.Player, items []*object.ItemDef, stationDefID, stationName string) {
seen := make(map[string]bool)
- var recipes []action.RecipeDef
+ var recipes []*object.ItemDef
- for _, r := range allRecipes {
- if r.Type != "cooking" {
- continue
- }
- if !stationMatch(stationDefID, r.Station) {
+ for _, item := range items {
+ c := item.FirstCraft()
+ if !stationMatch(stationDefID, c.Station) {
continue
}
- if seen[r.ID] {
+ if seen[item.ID] {
continue
}
- if !r.HasAllItems(p.HasItem) {
+ if !craftHasAllItems(c, p.HasItem) {
continue
}
- seen[r.ID] = true
- recipes = append(recipes, r)
+ seen[item.ID] = true
+ recipes = append(recipes, item)
}
title := fmt.Sprintf("What would you like to cook on the %s?", stationName)
diff --git a/internal/game/cmd_craft.go b/internal/game/cmd_craft.go
index d061a9c..3290daa 100644
--- a/internal/game/cmd_craft.go
+++ b/internal/game/cmd_craft.go
@@ -3,8 +3,8 @@ package game
import (
"strings"
- "thehouseoficarus/internal/action"
"thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
"thehouseoficarus/internal/player"
)
@@ -28,35 +28,27 @@ func (g *Game) doStationSkill(sess *net.Session, input string, recipeType string
p := sess.Player
g.cancelAction(p)
- allRecipes, err := g.RecipeStore.LoadAll()
- if err != nil {
- sess.WriteLine("Error loading recipes.")
- return
- }
+ items := g.CraftIndex.ByType(recipeType)
- var recipes []action.RecipeDef
- for _, r := range allRecipes {
- if r.Type != recipeType {
- continue
- }
- if !g.recipeVisible(p, &r) {
+ var filtered []*object.ItemDef
+ for _, item := range items {
+ if !g.itemVisible(p, item) {
continue
}
- recipes = append(recipes, r)
+ filtered = append(filtered, item)
}
if input != "" {
- g.doWithInput(sess, p, recipes, input, skill, flagKey, label, verbCap)
+ g.doWithInput(sess, p, filtered, input, skill, flagKey, label, verbCap)
return
}
- lastRecipeID, _ := p.Flags[flagKey].(string)
- if lastRecipeID != "" {
- for i := range recipes {
- if recipes[i].ID == lastRecipeID {
- r := &recipes[i]
- if g.canDoRecipe(p, r, skill) {
- g.startRecipe(sess, p, r, 0, flagKey)
+ lastItemID, _ := p.Flags[flagKey].(string)
+ if lastItemID != "" {
+ for _, item := range filtered {
+ if item.ID == lastItemID {
+ if g.canDoItem(p, item, skill) {
+ g.startItem(sess, p, item, 0, flagKey)
return
}
break
@@ -64,34 +56,33 @@ func (g *Game) doStationSkill(sess *net.Session, input string, recipeType string
}
}
- available := g.availableRecipes(p, recipes)
+ available := g.availableItems(p, filtered)
if len(available) == 0 {
+ if recipeType == "pharmacy" && g.hasGrimyHerbs(p) {
+ sess.WriteLine("Mix what? Grimy herbs? Clean those first!")
+ return
+ }
sess.WriteLine("You don't have any materials to " + verbPast + ".")
return
}
optionKey := verbPast + "_all"
if p.OptionBool(optionKey) && len(available) == 1 {
- g.startRecipe(sess, p, &available[0], 0, flagKey)
+ g.startItem(sess, p, available[0], 0, flagKey)
return
}
g.showProductionTable(sess, p, available, label, recipeType, flagKey, verbCap, false)
}
-func (g *Game) doWithInput(sess *net.Session, p *player.Player, recipes []action.RecipeDef, input string, skill player.SkillName, flagKey, label, verbCap string) {
+func (g *Game) doWithInput(sess *net.Session, p *player.Player, items []*object.ItemDef, input string, skill player.SkillName, flagKey, label, verbCap string) {
qty, productName := parseQty(input)
productName = strings.TrimSpace(productName)
- var matched []action.RecipeDef
- for _, r := range recipes {
- outDef, _ := g.ItemStore.Load(r.Output)
- name := r.Output
- if outDef != nil {
- name = outDef.Name
- }
- if action.WordPrefixMatch(productName, name) {
- matched = append(matched, r)
+ var matched []*object.ItemDef
+ for _, item := range items {
+ if object.WordPrefixMatch(productName, item.Name) {
+ matched = append(matched, item)
}
}
@@ -100,10 +91,10 @@ func (g *Game) doWithInput(sess *net.Session, p *player.Player, recipes []action
return
}
- var available []action.RecipeDef
- for _, r := range matched {
- if g.canDoRecipe(p, &r, skill) {
- available = append(available, r)
+ var available []*object.ItemDef
+ for _, item := range matched {
+ if g.canDoItem(p, item, skill) {
+ available = append(available, item)
}
}
@@ -113,68 +104,85 @@ func (g *Game) doWithInput(sess *net.Session, p *player.Player, recipes []action
}
if len(available) == 1 {
- g.startRecipe(sess, p, &available[0], qty, flagKey)
+ g.startItem(sess, p, available[0], qty, flagKey)
return
}
g.showProductionTable(sess, p, available, label, label, flagKey, verbCap, false)
}
-func (g *Game) recipeVisible(p *player.Player, r *action.RecipeDef) bool {
- if len(r.Station) > 0 {
- stID, _ := g.findStation(p.RoomID, r.Station)
- if stID == "" {
+func (g *Game) itemVisible(p *player.Player, item *object.ItemDef) bool {
+ return item.FindCraft(func(c object.CraftDef) bool {
+ if len(c.Station) > 0 {
+ stID, _ := g.findStation(p.RoomID, c.Station)
+ if stID == "" {
+ return false
+ }
+ }
+ if c.Tool != "" && !g.hasToolType(p, c.Tool) {
return false
}
- }
- if r.Tool != "" && !g.hasToolType(p, r.Tool) {
- return false
- }
- return true
+ return true
+ }) != nil
}
-func (g *Game) canDoRecipe(p *player.Player, r *action.RecipeDef, skill player.SkillName) bool {
- if p.Level(skill) < r.Level {
- return false
- }
- if !r.HasAllItemsQty(p.CountItem) {
- return false
- }
- if len(r.Station) > 0 {
- stID, _ := g.findStation(p.RoomID, r.Station)
- if stID == "" {
+func (g *Game) canDoItem(p *player.Player, item *object.ItemDef, skill player.SkillName) bool {
+ m := item.FindCraft(func(c object.CraftDef) bool {
+ if p.Level(skill) < c.Level {
return false
}
- }
- if r.Tool != "" && !g.hasToolType(p, r.Tool) {
- return false
- }
- return true
+ if !craftHasAllItemsQty(&c, p.CountItem) {
+ return false
+ }
+ if len(c.Station) > 0 {
+ stID, _ := g.findStation(p.RoomID, c.Station)
+ if stID == "" {
+ return false
+ }
+ }
+ if c.Tool != "" && !g.hasToolType(p, c.Tool) {
+ return false
+ }
+ return true
+ })
+ return m != nil
}
-func (g *Game) availableRecipes(p *player.Player, allRecipes []action.RecipeDef) []action.RecipeDef {
- var result []action.RecipeDef
- for _, r := range allRecipes {
- if r.HasAllItemsQty(p.CountItem) {
- result = append(result, r)
+func (g *Game) availableItems(p *player.Player, allItems []*object.ItemDef) []*object.ItemDef {
+ var result []*object.ItemDef
+ for _, item := range allItems {
+ m := item.FindCraft(func(c object.CraftDef) bool {
+ return craftHasAllItemsQty(&c, p.CountItem)
+ })
+ if m != nil {
+ result = append(result, item)
}
}
return result
}
-func (g *Game) startRecipe(sess *net.Session, p *player.Player, recipe *action.RecipeDef, count int, flagKey string) {
+func (g *Game) startItem(sess *net.Session, p *player.Player, item *object.ItemDef, count int, flagKey string) {
p.EnsureFlags()
- p.Flags[flagKey] = recipe.ID
+ p.Flags[flagKey] = item.ID
g.AccountStore.SaveCharacter(p)
- g.startProductionFromRecipe(sess, p, recipe, count)
+ g.startProductionFromItem(sess, p, item, count)
}
-func (g *Game) findCraftRecipes(p *player.Player, itemAID, itemBID string) []action.RecipeDef {
- allRecipes, err := g.RecipeStore.LoadAll()
- if err != nil {
- return nil
+func (g *Game) hasGrimyHerbs(p *player.Player) bool {
+ items := g.CraftIndex.ByType("clean")
+ for _, item := range items {
+ if item.FindCraft(func(c object.CraftDef) bool {
+ return craftHasAllItems(&c, p.HasItem)
+ }) != nil {
+ return true
+ }
}
+ return false
+}
+
+func (g *Game) findCraftItems(p *player.Player, itemAID, itemBID string) []*object.ItemDef {
+ items := g.CraftIndex.ByType("crafting")
toolTypeA := ""
toolTypeB := ""
@@ -185,19 +193,22 @@ func (g *Game) findCraftRecipes(p *player.Player, itemAID, itemBID string) []act
toolTypeB = defB.ToolType
}
- var matched []action.RecipeDef
- for _, r := range allRecipes {
- if r.Type != "crafting" {
- continue
- }
- if r.Tool == "" {
- continue
- }
-
- if r.Tool == toolTypeA && r.MatchesEntry(itemBID) {
- matched = append(matched, r)
- } else if r.Tool == toolTypeB && r.MatchesEntry(itemAID) {
- matched = append(matched, r)
+ var matched []*object.ItemDef
+ for _, item := range items {
+ match := item.FindCraft(func(c object.CraftDef) bool {
+ if c.Tool == "" {
+ return false
+ }
+ if c.Tool == toolTypeA && craftMatchesEntry(&c, itemBID) {
+ return true
+ }
+ if c.Tool == toolTypeB && craftMatchesEntry(&c, itemAID) {
+ return true
+ }
+ return false
+ })
+ if match != nil {
+ matched = append(matched, item)
}
}
return matched
diff --git a/internal/game/cmd_fletch.go b/internal/game/cmd_fletch.go
index 5a4bc19..088ce63 100644
--- a/internal/game/cmd_fletch.go
+++ b/internal/game/cmd_fletch.go
@@ -3,9 +3,9 @@ package game
import (
"strings"
- "thehouseoficarus/internal/action"
"thehouseoficarus/internal/combat"
"thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
"thehouseoficarus/internal/player"
)
@@ -23,34 +23,38 @@ var fletchGemItems = map[string]bool{
"uncut_ruby": true, "uncut_diamond": true,
}
-func fletchNeedsKnife(r *action.RecipeDef) bool {
- if r.Tool == "knife" {
- return true
- }
- 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
+func fletchNeedsKnife(item *object.ItemDef) bool {
+ return item.FindCraft(func(c object.CraftDef) bool {
+ if c.Tool == "knife" {
+ return true
+ }
+ for _, e := range c.Consume {
+ for _, id := range e.Items {
+ if fletchLogItems[id] {
+ if strings.Contains(item.ID, "shaft") || strings.Contains(item.ID, "bow") {
+ return true
+ }
}
}
}
- }
- return false
+ return false
+ }) != nil
}
-func fletchNeedsChisel(r *action.RecipeDef) bool {
- if r.Tool == "chisel" {
- return true
- }
- for _, e := range r.Consume {
- for _, id := range e.Items {
- if fletchGemItems[id] {
- return true
+func fletchNeedsChisel(item *object.ItemDef) bool {
+ return item.FindCraft(func(c object.CraftDef) bool {
+ if c.Tool == "chisel" {
+ return true
+ }
+ for _, e := range c.Consume {
+ for _, id := range e.Items {
+ if fletchGemItems[id] {
+ return true
+ }
}
}
- }
- return false
+ return false
+ }) != nil
}
func (g *Game) doFletch(sess *net.Session, input string) {
@@ -66,32 +70,19 @@ func (g *Game) doFletch(sess *net.Session, input string) {
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)
- }
+ items := g.CraftIndex.ByType("fletching")
if input != "" {
- g.doFletchWithInput(sess, p, fletchRecipes, input)
+ g.doFletchWithInput(sess, p, items, 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)
+ lastItemID, _ := p.Flags["last_fletch"].(string)
+ if lastItemID != "" {
+ for _, item := range items {
+ if item.ID == lastItemID {
+ if g.canDoFletchItem(p, item) {
+ g.startFletchAction(sess, p, item, 0)
return
}
break
@@ -99,33 +90,28 @@ func (g *Game) doFletch(sess *net.Session, input string) {
}
}
- available := g.availableFletchRecipes(p, fletchRecipes)
+ available := g.availableFletchItems(p, items)
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)
+ g.startFletchAction(sess, p, available[0], 0)
return
}
g.showProductionTable(sess, p, available, "Fletching", "fletching", "last_fletch", "Fletch", true)
}
-func (g *Game) doFletchWithInput(sess *net.Session, p *player.Player, fletchRecipes []action.RecipeDef, input string) {
+func (g *Game) doFletchWithInput(sess *net.Session, p *player.Player, items []*object.ItemDef, 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 action.WordPrefixMatch(productName, name) {
- matched = append(matched, r)
+ var matched []*object.ItemDef
+ for _, item := range items {
+ if object.WordPrefixMatch(productName, item.Name) {
+ matched = append(matched, item)
}
}
@@ -134,10 +120,10 @@ func (g *Game) doFletchWithInput(sess *net.Session, p *player.Player, fletchReci
return
}
- var available []action.RecipeDef
- for _, r := range matched {
- if g.canDoFletchRecipe(p, &r) {
- available = append(available, r)
+ var available []*object.ItemDef
+ for _, item := range matched {
+ if g.canDoFletchItem(p, item) {
+ available = append(available, item)
}
}
@@ -147,48 +133,49 @@ func (g *Game) doFletchWithInput(sess *net.Session, p *player.Player, fletchReci
}
if len(available) == 1 {
- count := qty
- g.startFletchAction(sess, p, &available[0], count)
+ g.startFletchAction(sess, p, available[0], qty)
return
}
g.showProductionTable(sess, p, available, "Fletching", "fletching", "last_fletch", "Fletch", true)
}
-func (g *Game) hasFletchTools(p *player.Player, r *action.RecipeDef) bool {
- if r.Tool != "" {
- return g.hasToolType(p, r.Tool)
+func (g *Game) hasFletchTools(p *player.Player, item *object.ItemDef) bool {
+ c := item.FirstCraft()
+ if c.Tool != "" {
+ return g.hasToolType(p, c.Tool)
}
- if fletchNeedsKnife(r) && !g.hasToolType(p, "knife") {
+ if fletchNeedsKnife(item) && !g.hasToolType(p, "knife") {
return false
}
- if fletchNeedsChisel(r) && !g.hasToolType(p, "chisel") {
+ if fletchNeedsChisel(item) && !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) canDoFletchItem(p *player.Player, item *object.ItemDef) bool {
+ return item.FindCraft(func(c object.CraftDef) bool {
+ return p.Level(player.Fletching) >= c.Level &&
+ craftHasAllItemsQty(&c, p.CountItem)
+ }) != nil && g.hasFletchTools(p, item)
}
-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)
+func (g *Game) availableFletchItems(p *player.Player, allItems []*object.ItemDef) []*object.ItemDef {
+ var result []*object.ItemDef
+ for _, item := range allItems {
+ match := item.FindCraft(func(c object.CraftDef) bool {
+ return craftHasAllItemsQty(&c, p.CountItem)
+ })
+ if match != nil && g.hasFletchTools(p, item) {
+ result = append(result, item)
}
}
return result
}
-func (g *Game) findFletchRecipes(p *player.Player, itemAID, itemBID string) []action.RecipeDef {
- allRecipes, err := g.RecipeStore.LoadAll()
- if err != nil {
- return nil
- }
+func (g *Game) findFletchItems(p *player.Player, itemAID, itemBID string) []*object.ItemDef {
+ items := g.CraftIndex.ByType("fletching")
toolTypeA := ""
toolTypeB := ""
@@ -201,26 +188,24 @@ func (g *Game) findFletchRecipes(p *player.Player, itemAID, itemBID string) []ac
isToolA := toolTypeA == "knife" || toolTypeA == "chisel"
isToolB := toolTypeB == "knife" || toolTypeB == "chisel"
- var matched []action.RecipeDef
- for _, r := range allRecipes {
- if r.Type != "fletching" {
- continue
- }
+ var matched []*object.ItemDef
+ for _, item := range items {
+ c := item.FirstCraft()
- if r.Tool != "" {
- if r.Tool == toolTypeA && r.MatchesEntry(itemBID) {
- matched = append(matched, r)
+ if c.Tool != "" {
+ if c.Tool == toolTypeA && craftMatchesEntry(c, itemBID) {
+ matched = append(matched, item)
continue
}
- if r.Tool == toolTypeB && r.MatchesEntry(itemAID) {
- matched = append(matched, r)
+ if c.Tool == toolTypeB && craftMatchesEntry(c, itemAID) {
+ matched = append(matched, item)
continue
}
}
aMatch := false
bMatch := false
- for _, e := range r.Consume {
+ for _, e := range c.Consume {
for _, id := range e.Items {
if id == itemAID {
aMatch = true
@@ -232,21 +217,21 @@ func (g *Game) findFletchRecipes(p *player.Player, itemAID, itemBID string) []ac
}
if aMatch && bMatch {
- matched = append(matched, r)
+ matched = append(matched, item)
continue
}
if isToolA && bMatch {
- if (fletchNeedsKnife(&r) && toolTypeA == "knife") ||
- (fletchNeedsChisel(&r) && toolTypeA == "chisel") {
- matched = append(matched, r)
+ if (fletchNeedsKnife(item) && toolTypeA == "knife") ||
+ (fletchNeedsChisel(item) && toolTypeA == "chisel") {
+ matched = append(matched, item)
continue
}
}
if isToolB && aMatch {
- if (fletchNeedsKnife(&r) && toolTypeB == "knife") ||
- (fletchNeedsChisel(&r) && toolTypeB == "chisel") {
- matched = append(matched, r)
+ if (fletchNeedsKnife(item) && toolTypeB == "knife") ||
+ (fletchNeedsChisel(item) && toolTypeB == "chisel") {
+ matched = append(matched, item)
continue
}
}
diff --git a/internal/game/cmd_shop.go b/internal/game/cmd_shop.go
index 2742151..c6b7831 100644
--- a/internal/game/cmd_shop.go
+++ b/internal/game/cmd_shop.go
@@ -158,7 +158,7 @@ func (g *Game) findShopMatches(input string, cfg *action.ShopConfig) []shopMatch
if def != nil {
itemName = def.Name
}
- if action.WordPrefixMatch(input, itemName) {
+ if object.WordPrefixMatch(input, itemName) {
matches = append(matches, shopMatch{si: si, name: itemName, def: def})
}
}
diff --git a/internal/game/cmd_smelt.go b/internal/game/cmd_smelt.go
index f1ff4e2..8ad2391 100644
--- a/internal/game/cmd_smelt.go
+++ b/internal/game/cmd_smelt.go
@@ -4,8 +4,8 @@ import (
"fmt"
"strings"
- "thehouseoficarus/internal/action"
"thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
"thehouseoficarus/internal/player"
)
@@ -23,14 +23,10 @@ func (g *Game) doSmelt(sess *net.Session, input string) {
return
}
- allRecipes, err := g.RecipeStore.LoadAll()
- if err != nil {
- sess.WriteLine("Error loading recipes.")
- return
- }
+ items := g.CraftIndex.ByType("smelting")
if input == "" {
- g.showSmeltMenu(sess, p, allRecipes, stationDefID, stationName)
+ g.showSmeltMenu(sess, p, items, stationDefID, stationName)
return
}
@@ -48,33 +44,35 @@ func (g *Game) doSmelt(sess *net.Session, input string) {
itemID := matches[0].ID
- var recipes []action.RecipeDef
- for _, r := range allRecipes {
- if r.Type != "smelting" || !stationMatch(stationDefID, r.Station) || !r.MatchesEntry(itemID) {
+ var recipes []*object.ItemDef
+ for _, item := range items {
+ c := item.FirstCraft()
+ if !stationMatch(stationDefID, c.Station) || !craftMatchesEntry(c, itemID) {
continue
}
- if !r.HasAllItemsQty(p.CountItem) || p.Level(player.SkillName(r.EffectiveSkill())) < r.Level {
+ if !craftHasAllItemsQty(c, p.CountItem) || p.Level(player.SkillName(c.EffectiveSkill())) < c.Level {
continue
}
- recipes = append(recipes, r)
+ recipes = append(recipes, item)
}
g.showRecipeChoice(sess, p, recipes, "You can't smelt that.", "What would you like to smelt?", "")
}
-func (g *Game) showSmeltMenu(sess *net.Session, p *player.Player, allRecipes []action.RecipeDef, stationDefID, stationName string) {
+func (g *Game) showSmeltMenu(sess *net.Session, p *player.Player, items []*object.ItemDef, stationDefID, stationName string) {
seen := make(map[string]bool)
- var recipes []action.RecipeDef
+ var recipes []*object.ItemDef
- for _, r := range allRecipes {
- if r.Type != "smelting" || !stationMatch(stationDefID, r.Station) {
+ for _, item := range items {
+ c := item.FirstCraft()
+ if !stationMatch(stationDefID, c.Station) {
continue
}
- if seen[r.ID] || !r.HasAllItemsQty(p.CountItem) || p.Level(player.SkillName(r.EffectiveSkill())) < r.Level {
+ if seen[item.ID] || !craftHasAllItemsQty(c, p.CountItem) || p.Level(player.SkillName(c.EffectiveSkill())) < c.Level {
continue
}
- seen[r.ID] = true
- recipes = append(recipes, r)
+ seen[item.ID] = true
+ recipes = append(recipes, item)
}
g.showRecipeChoice(sess, p, recipes, "You don't have anything you can smelt.", "What would you like to smelt?", "smelt_all")
diff --git a/internal/game/cmd_smith.go b/internal/game/cmd_smith.go
index c7a32cd..712c03c 100644
--- a/internal/game/cmd_smith.go
+++ b/internal/game/cmd_smith.go
@@ -5,8 +5,8 @@ import (
"sort"
"strings"
- "thehouseoficarus/internal/action"
"thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
"thehouseoficarus/internal/player"
)
@@ -29,11 +29,7 @@ func (g *Game) doSmith(sess *net.Session, input string) {
return
}
- allRecipes, err := g.RecipeStore.LoadAll()
- if err != nil {
- sess.WriteLine("Error loading recipes.")
- return
- }
+ items := g.CraftIndex.ByType("smithing")
if input != "" {
matches := g.findInventoryMatches(input, p)
@@ -47,15 +43,15 @@ func (g *Game) doSmith(sess *net.Session, input string) {
return
}
barID := matches[0].ID
- if !hasSmithingRecipes(allRecipes, barID) {
+ if !hasSmithingItems(items, barID) {
sess.WriteLine("You can't smith that.")
return
}
- g.showSmithTable(sess, p, barID, allRecipes)
+ g.showSmithTable(sess, p, barID)
return
}
- barTypes := findSmithableBars(allRecipes, p)
+ barTypes := findSmithableBars(items, p)
if len(barTypes) == 0 {
sess.WriteLine("You don't have any bars to smith.")
return
@@ -63,12 +59,12 @@ func (g *Game) doSmith(sess *net.Session, input string) {
if len(barTypes) == 1 {
if p.OptionBool("smith_all") {
- if recipe := g.findSmithRecipe(p, barTypes[0], allRecipes); recipe != nil {
- g.startProductionFromRecipe(sess, p, recipe, 0)
+ if item := g.findSmithItem(p, barTypes[0], items); item != nil {
+ g.startProductionFromItem(sess, p, item, 0)
return
}
}
- g.showSmithTable(sess, p, barTypes[0], allRecipes)
+ g.showSmithTable(sess, p, barTypes[0])
return
}
@@ -86,17 +82,18 @@ func (g *Game) doSmith(sess *net.Session, input string) {
g.showMenuTable(sess, "Which bars would you like to smith?", names)
}
-func (g *Game) showSmithTable(sess *net.Session, p *player.Player, barID string, allRecipes []action.RecipeDef) {
+func (g *Game) showSmithTable(sess *net.Session, p *player.Player, barID string) {
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)
+ allItems := g.CraftIndex.ByType("smithing")
+ var recipes []*object.ItemDef
+ for _, item := range allItems {
+ if craftMatchesEntry(item.FirstCraft(), barID) {
+ recipes = append(recipes, item)
}
}
@@ -109,22 +106,19 @@ func (g *Game) showSmithTable(sess *net.Session, p *player.Player, barID string,
g.showProductionTable(sess, p, recipes, titleCase(barName)+" Smithing", "smithing", lastFlag, "Produce", false)
}
-func hasSmithingRecipes(allRecipes []action.RecipeDef, barID string) bool {
- for _, r := range allRecipes {
- if r.Type == "smithing" && r.MatchesEntry(barID) {
+func hasSmithingItems(items []*object.ItemDef, barID string) bool {
+ for _, item := range items {
+ if craftMatchesEntry(item.FirstCraft(), barID) {
return true
}
}
return false
}
-func findSmithableBars(allRecipes []action.RecipeDef, p *player.Player) []string {
+func findSmithableBars(items []*object.ItemDef, p *player.Player) []string {
barSet := make(map[string]bool)
- for _, r := range allRecipes {
- if r.Type != "smithing" {
- continue
- }
- for _, e := range r.Consume {
+ for _, item := range items {
+ for _, e := range item.FirstCraft().Consume {
for _, id := range e.Items {
if p.HasItem(id) {
barSet[id] = true
@@ -148,15 +142,16 @@ func barMenuData(barIDs []string) []map[string]string {
return out
}
-func (g *Game) findSmithRecipe(p *player.Player, barID string, allRecipes []action.RecipeDef) *action.RecipeDef {
- var makeable []*action.RecipeDef
+func (g *Game) findSmithItem(p *player.Player, barID string, items []*object.ItemDef) *object.ItemDef {
+ var makeable []*object.ItemDef
skillLevel := p.Level(player.Smithing)
- for i, r := range allRecipes {
- if r.Type != "smithing" || !r.MatchesEntry(barID) {
+ for _, item := range items {
+ c := item.FirstCraft()
+ if !craftMatchesEntry(c, barID) {
continue
}
- if skillLevel >= r.Level && r.HasAllItemsQty(p.CountItem) {
- makeable = append(makeable, &allRecipes[i])
+ if skillLevel >= c.Level && craftHasAllItemsQty(c, p.CountItem) {
+ makeable = append(makeable, item)
}
}
if len(makeable) == 1 {
diff --git a/internal/game/cmd_trigger_utility.go b/internal/game/cmd_trigger_utility.go
index ce59975..69abc34 100644
--- a/internal/game/cmd_trigger_utility.go
+++ b/internal/game/cmd_trigger_utility.go
@@ -6,6 +6,7 @@ import (
"thehouseoficarus/internal/combat"
"thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
"thehouseoficarus/internal/player"
)
@@ -169,18 +170,25 @@ func (g *Game) triggerSuperheat(sess *net.Session, p *player.Player, mod *ModDef
return
}
- recipe := g.RecipeStore.FindByInput("smelt", inv.ItemID)
- if recipe == nil {
+ var match *object.ItemDef
+ for _, item := range g.CraftIndex.ByType("smelting") {
+ if craftMatchesEntry(item.FirstCraft(), inv.ItemID) {
+ match = item
+ break
+ }
+ }
+ if match == nil {
sess.WriteLine("You can't superheat that.")
return
}
+ c := match.FirstCraft()
- if recipe.Level > 0 && p.Level(player.SkillName(recipe.Skill)) < recipe.Level {
- sess.WriteLine(fmt.Sprintf("You need level %d %s to smelt that.", recipe.Level, recipe.Skill))
+ if c.Level > 0 && p.Level(player.SkillName(c.Skill)) < c.Level {
+ sess.WriteLine(fmt.Sprintf("You need level %d %s to smelt that.", c.Level, c.Skill))
return
}
- for _, e := range recipe.Consume {
+ for _, e := range c.Consume {
for _, itemID := range e.Items {
qty := e.Quantity
if qty <= 0 {
@@ -202,24 +210,16 @@ func (g *Game) triggerSuperheat(sess *net.Session, p *player.Player, mod *ModDef
g.cancelAction(p)
}
- for _, e := range recipe.Consume {
- for _, itemID := range e.Items {
- qty := e.Quantity
- if qty <= 0 {
- qty = 1
- }
- p.RemoveItem(itemID, qty)
- }
- }
+ craftConsumeAll(c, p.HasItem, p.RemoveItem)
- outputQty := recipe.OutputQty
+ outputQty := c.OutputQty
if outputQty <= 0 {
outputQty = 1
}
placed := false
for i := 0; i < 28; i++ {
slot := p.InvSlot(i)
- if slot != nil && slot.ItemID == recipe.Output {
+ if slot != nil && slot.ItemID == match.ID {
slot.Quantity += outputQty
placed = true
break
@@ -227,20 +227,16 @@ func (g *Game) triggerSuperheat(sess *net.Session, p *player.Player, mod *ModDef
}
if !placed {
freeSlot := p.FirstFreeSlot()
- p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: recipe.Output, Quantity: outputQty})
+ p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: match.ID, Quantity: outputQty})
}
var extraGains []xpGain
- if recipe.XP > 0 {
- extraGains = []xpGain{{recipe.Skill, recipe.XP}}
+ if c.XP > 0 {
+ extraGains = []xpGain{{c.EffectiveSkill(), c.XP}}
}
g.triggerModReward(sess, p, mod, 1.0, extraGains...)
- outputDef, _ := g.ItemStore.Load(recipe.Output)
- outputName := recipe.Output
- if outputDef != nil {
- outputName = outputDef.Name
- }
+ outputName := match.Name
inputDef, _ := g.ItemStore.Load(inv.ItemID)
inputName := inv.ItemID
if inputDef != nil {
diff --git a/internal/game/cmd_use.go b/internal/game/cmd_use.go
index e653c93..640e28b 100644
--- a/internal/game/cmd_use.go
+++ b/internal/game/cmd_use.go
@@ -4,7 +4,6 @@ import (
"fmt"
"strings"
- "thehouseoficarus/internal/action"
"thehouseoficarus/internal/net"
"thehouseoficarus/internal/object"
"thehouseoficarus/internal/player"
@@ -116,57 +115,42 @@ func (g *Game) useRoomObject(sess *net.Session, p *player.Player, input string)
return true
}
-func (g *Game) stationRecipes(p *player.Player, stationDefID string) []action.RecipeDef {
- allRecipes, err := g.RecipeStore.LoadAll()
- if err != nil {
- return nil
- }
- var results []action.RecipeDef
- for _, r := range allRecipes {
- if len(r.Station) == 0 {
- continue
- }
- stationMatch := false
- for _, sid := range r.Station {
- if sid == stationDefID {
- stationMatch = true
- break
- }
- }
- if !stationMatch {
+func (g *Game) stationRecipes(p *player.Player, stationDefID string) []*object.ItemDef {
+ items := g.CraftIndex.ByStation(stationDefID)
+ var results []*object.ItemDef
+ for _, item := range items {
+ c := item.FirstCraft()
+ if !craftHasAllItemsQty(c, p.CountItem) {
continue
}
- if !r.HasAllItemsQty(p.CountItem) {
+ skillName := player.SkillName(c.EffectiveSkill())
+ if p.Level(skillName) < c.Level {
continue
}
- skillName := player.SkillName(r.EffectiveSkill())
- if p.Level(skillName) < r.Level {
- continue
- }
- if r.Tool != "" && !g.hasToolType(p, r.Tool) {
+ if c.Tool != "" && !g.hasToolType(p, c.Tool) {
continue
}
if stationDefID == "anvil" && !g.hasToolType(p, "hammer") {
continue
}
- results = append(results, r)
+ results = append(results, item)
}
return results
}
-func (g *Game) showRecipeMenu(sess *net.Session, p *player.Player, recipes []action.RecipeDef, stationName string) {
- if len(recipes) == 0 {
+func (g *Game) showRecipeMenu(sess *net.Session, p *player.Player, items []*object.ItemDef, stationName string) {
+ if len(items) == 0 {
sess.WriteLine(fmt.Sprintf("You don't have anything you can make at the %s.", stationName))
return
}
- if len(recipes) == 1 {
- g.promptHowMany(sess, recipes[0].ID)
+ if len(items) == 1 {
+ g.promptHowMany(sess, items[0].ID)
return
}
needTypes := false
- for i := range recipes {
- for j := i + 1; j < len(recipes); j++ {
- if recipes[i].Type != recipes[j].Type {
+ for i := range items {
+ for j := i + 1; j < len(items); j++ {
+ if len(items[i].Craft) > 0 && len(items[j].Craft) > 0 && items[i].FirstCraft().Type != items[j].FirstCraft().Type {
needTypes = true
break
}
@@ -175,17 +159,23 @@ func (g *Game) showRecipeMenu(sess *net.Session, p *player.Player, recipes []act
break
}
}
- menu := recipeMenuData(recipeDefIDs(recipes))
+ menu := itemMenuData(itemIDs(items))
sess.PendingMenu = menu
sess.State = net.StateRecipeChoice
var names []string
- for _, r := range recipes {
- name := g.recipeName(sess, &r)
+ for _, item := range items {
+ cType := ""
+ if len(item.Craft) > 0 {
+ cType = item.FirstCraft().Type
+ }
+ if cType == "" {
+ cType = "combine"
+ }
+ typeLabel := strings.ToUpper(cType[:1]) + cType[1:]
if needTypes {
- typeLabel := strings.ToUpper(r.Type[:1]) + r.Type[1:]
- names = append(names, fmt.Sprintf("%s (%s)", name, typeLabel))
+ names = append(names, fmt.Sprintf("%s (%s)", g.itemName(sess, item), typeLabel))
} else {
- names = append(names, name)
+ names = append(names, g.itemName(sess, item))
}
}
g.showMenuTable(sess, fmt.Sprintf("What would you like to make at the %s?", stationName), names)
@@ -217,23 +207,28 @@ func (g *Game) doUseItemOnTarget(sess *net.Session, p *player.Player, itemAName,
return
}
- stationIDs := []string{def.ID}
- recipes, err := g.RecipeStore.FindByStation(itemAID, stationIDs, "")
- if err == nil && len(recipes) > 0 {
- if recipes[0].Type == "smelting" {
- filtered := recipes[:0:0]
- for _, r := range recipes {
- if r.Type != "smelting" {
+ recipes := g.CraftIndex.FindByStationInput(def.ID, itemAID)
+
+ if len(recipes) > 0 {
+ cType := ""
+ if len(recipes[0].Craft) > 0 {
+ cType = recipes[0].FirstCraft().Type
+ }
+ if cType == "smelting" {
+ var filtered []*object.ItemDef
+ for _, item := range recipes {
+ c := item.FirstCraft()
+ if c.Type != "smelting" {
continue
}
- if !r.HasAllItemsQty(p.CountItem) {
+ if !craftHasAllItemsQty(c, p.CountItem) {
continue
}
- skillLevel := p.Level(player.SkillName(r.EffectiveSkill()))
- if skillLevel < r.Level {
+ skillLevel := p.Level(player.SkillName(c.EffectiveSkill()))
+ if skillLevel < c.Level {
continue
}
- filtered = append(filtered, r)
+ filtered = append(filtered, item)
}
if len(filtered) == 0 {
sess.WriteLine("You can't smelt that here.")
@@ -243,36 +238,35 @@ func (g *Game) doUseItemOnTarget(sess *net.Session, p *player.Player, itemAName,
g.promptHowMany(sess, filtered[0].ID)
return
}
- sess.PendingMenu = recipeMenuData(recipeDefIDs(filtered))
- sess.State = net.StateRecipeChoice
- var names []string
- for _, r := range filtered {
- names = append(names, g.recipeName(sess, &r))
- }
- g.showMenuTable(sess, "What would you like to smelt?", names)
- return
+ sess.PendingMenu = itemMenuData(itemIDs(filtered))
+ sess.State = net.StateRecipeChoice
+ var names []string
+ for _, item := range filtered {
+ names = append(names, g.itemName(sess, item))
+ }
+ g.showMenuTable(sess, "What would you like to smelt?", names)
+ return
}
- if recipes[0].Type == "smithing" {
+ if cType == "smithing" {
if !g.hasToolType(p, "hammer") {
sess.WriteLine("You need a hammer to smith.")
return
}
- allRecipes, _ := g.RecipeStore.LoadAll()
- g.showSmithTable(sess, p, itemAID, allRecipes)
+ g.showSmithTable(sess, p, itemAID)
return
}
if len(recipes) == 1 {
g.promptHowMany(sess, recipes[0].ID)
return
}
- sess.PendingMenu = recipeMenuData(recipeDefIDs(recipes))
- sess.State = net.StateRecipeChoice
- var names []string
- for _, r := range recipes {
- names = append(names, g.recipeName(sess, &r))
- }
- g.showMenuTable(sess, fmt.Sprintf("What would you like to make with %s on the %s?", itemAName, def.Name), names)
- return
+ sess.PendingMenu = itemMenuData(itemIDs(recipes))
+ sess.State = net.StateRecipeChoice
+ var names []string
+ for _, item := range recipes {
+ names = append(names, g.itemName(sess, item))
+ }
+ g.showMenuTable(sess, fmt.Sprintf("What would you like to make with %s on the %s?", itemAName, def.Name), names)
+ return
}
for _, ui := range def.UseInteractions {
@@ -314,29 +308,19 @@ func (g *Game) doUseItemOnTarget(sess *net.Session, p *player.Player, itemAName,
itemBID := matchesB[0].ID
- craftRecipes := g.findCraftRecipes(p, itemAID, itemBID)
- fletchRecipes := g.findFletchRecipes(p, itemAID, itemBID)
+ craftItems := g.findCraftItems(p, itemAID, itemBID)
+ fletchItems := g.findFletchItems(p, itemAID, itemBID)
- if len(craftRecipes) > 0 && len(fletchRecipes) > 0 {
+ if len(craftItems) > 0 && len(fletchItems) > 0 {
var menu []map[string]string
var names []string
- for _, r := range craftRecipes {
- menu = append(menu, map[string]string{"recipe_id": r.ID})
- outDef, _ := g.ItemStore.Load(r.Output)
- name := r.Output
- if outDef != nil {
- name = outDef.Name
- }
- names = append(names, fmt.Sprintf("%s (Crafting)", name))
+ for _, item := range craftItems {
+ menu = append(menu, map[string]string{"item_id": item.ID})
+ names = append(names, fmt.Sprintf("%s (Crafting)", item.Name))
}
- for _, r := range fletchRecipes {
- menu = append(menu, map[string]string{"fletch_recipe_id": r.ID})
- outDef, _ := g.ItemStore.Load(r.Output)
- name := r.Output
- if outDef != nil {
- name = outDef.Name
- }
- names = append(names, fmt.Sprintf("%s (Fletching)", name))
+ for _, item := range fletchItems {
+ menu = append(menu, map[string]string{"item_id": item.ID})
+ names = append(names, fmt.Sprintf("%s (Fletching)", item.Name))
}
sess.PendingMenu = menu
sess.State = net.StateRecipeChoice
@@ -344,15 +328,15 @@ func (g *Game) doUseItemOnTarget(sess *net.Session, p *player.Player, itemAName,
return
}
- if len(craftRecipes) > 0 {
- var available []action.RecipeDef
- for _, r := range craftRecipes {
- if g.canDoRecipe(p, &r, player.Crafting) {
- available = append(available, r)
+ if len(craftItems) > 0 {
+ var available []*object.ItemDef
+ for _, item := range craftItems {
+ if g.canDoItem(p, item, player.Crafting) {
+ available = append(available, item)
}
}
if len(available) == 1 {
- g.startRecipe(sess, p, &available[0], 0, "last_craft")
+ g.startItem(sess, p, available[0], 0, "last_craft")
return
}
if len(available) > 1 {
@@ -361,15 +345,15 @@ func (g *Game) doUseItemOnTarget(sess *net.Session, p *player.Player, itemAName,
}
}
- if len(fletchRecipes) > 0 {
- var available []action.RecipeDef
- for _, r := range fletchRecipes {
- if g.canDoFletchRecipe(p, &r) {
- available = append(available, r)
+ if len(fletchItems) > 0 {
+ var available []*object.ItemDef
+ for _, item := range fletchItems {
+ if g.canDoFletchItem(p, item) {
+ available = append(available, item)
}
}
if len(available) == 1 {
- g.startFletchAction(sess, p, &available[0], 0)
+ g.startFletchAction(sess, p, available[0], 0)
return
}
if len(available) > 1 {
@@ -378,22 +362,33 @@ func (g *Game) doUseItemOnTarget(sess *net.Session, p *player.Player, itemAName,
}
}
- matched := g.findCombineResults(sess, p, itemAID, itemBID)
- if len(matched) == 1 {
- g.promptHowMany(sess, "combine_"+matched[0].ID)
+ matched := g.CraftIndex.FindByTwoInputs(itemAID, itemBID)
+ var matchable []*object.ItemDef
+ for _, item := range matched {
+ if craftHasAllItemsQty(item.FirstCraft(), p.CountItem) {
+ matchable = append(matchable, item)
+ }
+ }
+ if len(matchable) == 1 {
+ g.promptHowMany(sess, matchable[0].ID)
return
}
- if len(matched) > 1 {
- sess.PendingMenu = combineMenuData(matched)
+ if len(matchable) > 1 {
+ sess.PendingMenu = itemMenuData(itemIDs(matchable))
sess.State = net.StateRecipeChoice
var names []string
- for _, def := range matched {
- names = append(names, g.itemColorize(sess, def, def.Name))
+ for _, item := range matchable {
+ names = append(names, g.itemName(sess, item))
}
g.showMenuTable(sess, "What would you like to make?", names)
return
}
+ if g.isGrimyHerb(itemAID) && itemBID == "vial_of_water" {
+ sess.WriteLine("Don't put a grimy herb in there! Clean it first!")
+ return
+ }
+
sess.WriteLine("You can't combine those items.")
return
}
@@ -401,55 +396,16 @@ func (g *Game) doUseItemOnTarget(sess *net.Session, p *player.Player, itemAName,
sess.WriteLine(fmt.Sprintf("There's no '%s' here to use that on.", itemBName))
}
-func (g *Game) findCombineResults(sess *net.Session, p *player.Player, itemAID, itemBID string) []*object.ItemDef {
- items, err := g.ItemStore.LoadAll()
- if err != nil {
- return nil
- }
- var matched []*object.ItemDef
- for _, def := range items {
- if len(def.MadeFrom) == 0 {
- continue
- }
- aEntry := -1
- bEntry := -1
- for ei, entry := range def.MadeFrom {
- for _, id := range entry.Items {
- if id == itemAID && aEntry < 0 {
- aEntry = ei
- }
- if id == itemBID && bEntry < 0 {
- bEntry = ei
+func (g *Game) isGrimyHerb(itemID string) bool {
+ items := g.CraftIndex.ByType("clean")
+ for _, item := range items {
+ for _, e := range item.FirstCraft().Consume {
+ for _, id := range e.Items {
+ if id == itemID {
+ return true
}
}
}
- if aEntry >= 0 && bEntry >= 0 && aEntry != bEntry && madeFromAvailable(p, def.MadeFrom) {
- matched = append(matched, def)
- }
}
- return matched
-}
-
-func combineMenuData(defs []*object.ItemDef) []map[string]string {
- out := make([]map[string]string, len(defs))
- for i, d := range defs {
- out[i] = map[string]string{"combine_item": d.ID}
- }
- return out
-}
-
-func madeFromAvailable(p *player.Player, madeFrom []object.MadeFromEntry) bool {
- for _, entry := range madeFrom {
- found := false
- for _, id := range entry.Items {
- if p.HasItem(id) {
- found = true
- break
- }
- }
- if !found {
- return false
- }
- }
- return true
+ return false
}
diff --git a/internal/game/cmd_verbs.go b/internal/game/cmd_verbs.go
index 7801903..f66dc74 100644
--- a/internal/game/cmd_verbs.go
+++ b/internal/game/cmd_verbs.go
@@ -88,12 +88,12 @@ func (g *Game) executeVerbs(sess *net.Session, args []string, rawInput string) {
if len(recipes) > 0 {
addUnique(&sectionObjs, &seen, "use "+name)
for _, r := range recipes {
- if info, ok := productionTypes[r.Type]; ok {
+ if info, ok := productionTypes[r.FirstCraft().Type]; ok {
if info.ActionType != "combine" && info.ActionType != "fletch" && info.ActionType != "mix" {
addUnique(&sectionObjs, &seen, info.ActionType)
}
}
- for _, c := range r.Consume {
+ for _, c := range r.FirstCraft().Consume {
for _, itemID := range c.Items {
if itemDef, err := g.ItemStore.Load(itemID); err == nil {
addUnique(&sectionObjs, &seen, "use "+itemDef.Name+" on "+name)
diff --git a/internal/game/core_production.go b/internal/game/core_production.go
index 59517d5..4aecc54 100644
--- a/internal/game/core_production.go
+++ b/internal/game/core_production.go
@@ -13,47 +13,61 @@ import (
"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) {
+func craftFirstItemName(c *object.CraftDef, load func(string) (string, bool)) string {
+ if c == nil {
+ return ""
+ }
+ for _, e := range c.Consume {
+ for _, id := range e.Items {
+ if name, ok := load(id); ok {
+ return name
+ }
+ return id
+ }
+ }
+ return ""
+}
+
+func (g *Game) startProduction(sess *net.Session, p *player.Player, item *object.ItemDef, craft *object.CraftDef, actionType, displayVerb, startMsg, endMsg string, count int) {
g.cancelAction(p)
g.cancelBgAction(p)
- skill := recipe.EffectiveSkill()
+ skill := craft.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))
+ if skillLevel < craft.Level {
+ sess.WriteLine(fmt.Sprintf("You need level %d %s to do that.", craft.Level, skill))
g.reprompt(sess)
return
}
}
- if !recipe.HasAllItemsQty(p.CountItem) {
+ if !craftHasAllItemsQty(craft, p.CountItem) {
sess.WriteLine("You don't have the required materials.")
g.reprompt(sess)
return
}
- outDef, _ := g.ItemStore.Load(recipe.Output)
+ outDef, _ := g.ItemStore.Load(item.ID)
- wait := recipe.Wait
+ wait := craft.Wait
if wait <= 0 {
wait = 4
}
- outputName := recipe.Output
+ outputName := item.ID
if outDef != nil {
outputName = outDef.Name
}
p.Action = &action.Action{
Type: actionType,
- TargetID: recipe.ID,
+ TargetID: item.ID,
TargetName: outputName,
Data: map[string]any{
- "recipe_id": recipe.ID,
+ "item_id": item.ID,
"phase": 0,
"wait": wait,
"start_msg": startMsg,
@@ -68,13 +82,18 @@ func (g *Game) startProduction(sess *net.Session, p *player.Player, recipe *acti
g.broadcastAction(sess, "\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)
+func (g *Game) startProductionFromItem(sess *net.Session, p *player.Player, item *object.ItemDef, count int) {
+ craft := item.FirstCraft()
+ if craft == nil {
+ return
+ }
+
+ _, stationName := g.findStation(p.RoomID, craft.Station)
if stationName == "" {
stationName = "inventory"
}
- firstItem := recipe.FirstItemName(func(id string) (string, bool) {
+ firstItem := craftFirstItemName(craft, func(id string) (string, bool) {
def, err := g.ItemStore.Load(id)
if err != nil {
return id, false
@@ -82,9 +101,9 @@ func (g *Game) startProductionFromRecipe(sess *net.Session, p *player.Player, re
return def.Name, true
})
- info, ok := productionTypes[recipe.Type]
+ info, ok := productionTypes[craft.Type]
if !ok {
- info = productionTypeInfo{recipe.Type, recipe.Type}
+ info = productionTypeInfo{craft.Type, craft.Type}
}
actionType := info.ActionType
displayVerb := info.DisplayVerb
@@ -97,67 +116,79 @@ func (g *Game) startProductionFromRecipe(sess *net.Session, p *player.Player, re
}
endMsg = fmt.Sprintf("You've finished %s.", displayVerb)
- g.startProduction(sess, p, recipe, actionType, displayVerb, startMsg, endMsg, count)
+ g.startProduction(sess, p, item, craft, actionType, displayVerb, startMsg, endMsg, count)
}
-func (g *Game) loadRecipe(recipeID string) *action.RecipeDef {
- if r := g.RecipeStore.GetByID(recipeID); r != nil {
- return r
+func (g *Game) loadCraftItem(itemID string) *object.ItemDef {
+ item, err := g.ItemStore.Load(itemID)
+ if err != nil || len(item.Craft) == 0 {
+ return nil
}
- 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
+ return item
}
func (g *Game) advanceProduction(sess *net.Session, p *player.Player) bool {
- recipeID := p.Action.Data["recipe_id"].(string)
+ itemID := p.Action.Data["item_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.loadRecipe(recipeID)
- if recipe == nil {
+ item := g.loadCraftItem(itemID)
+ if item == nil {
g.cancelAction(p)
return false
}
+ craft := item.FirstCraft()
if phase == 0 {
if startMsg != "" {
sess.WriteLine(fmt.Sprintf("\n%s", startMsg))
}
+ if len(craft.Steps) > 0 {
+ p.Action.Data["next_step_index"] = 0
+ p.Action.Data["steps_accumulated_ticks"] = float64(0)
+ }
p.Action.Data["phase"] = 1
p.Action.WaitLeft = engine.ToTicks(wait)
return true
}
- skill := recipe.EffectiveSkill()
+ if stepsIdx, ok := p.Action.Data["next_step_index"].(int); ok && stepsIdx < len(craft.Steps) {
+ accTicks, _ := p.Action.Data["steps_accumulated_ticks"].(float64)
+ accTicks += wait
+ p.Action.Data["steps_accumulated_ticks"] = accTicks
+ for i := stepsIdx; i < len(craft.Steps); i++ {
+ if accTicks >= craft.Steps[i].Tick {
+ sess.WriteLine(craft.Steps[i].Message)
+ p.Action.Data["next_step_index"] = i + 1
+ }
+ }
+ }
+
+ skill := craft.EffectiveSkill()
skillLevel := p.Level(player.SkillName(skill))
chance := 1.0
- if recipe.Success != nil {
- chance = action.SuccessChance(*recipe.Success, skillLevel, recipe.Level)
+ if craft.Success != nil {
+ chance = action.SuccessChance(action.SuccessFormula(*craft.Success), skillLevel, craft.Level)
}
if rand.Float64() < chance {
- outputQty := recipe.OutputQty
+ outputQty := craft.OutputQty
if outputQty <= 0 {
outputQty = 1
}
- byproducts := g.collectByproducts(p, recipe)
+ byproducts := g.collectByproducts(p, item)
placed := false
- outDef, _ := g.ItemStore.Load(recipe.Output)
+ outDef, _ := g.ItemStore.Load(item.ID)
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)
+ if slot != nil && slot.ItemID == item.ID {
+ craftConsumeAll(craft, p.HasItem, p.RemoveItem)
slot.Quantity += outputQty
placed = true
break
@@ -165,14 +196,14 @@ func (g *Game) advanceProduction(sess *net.Session, p *player.Player) bool {
}
}
if !placed {
- recipe.ConsumeAll(p.HasItem, p.RemoveItem)
+ craftConsumeAll(craft, 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})
+ p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: item.ID, Quantity: outputQty})
}
for _, bp := range byproducts {
@@ -181,38 +212,38 @@ func (g *Game) advanceProduction(sess *net.Session, p *player.Player) bool {
}
}
- if recipe.XP > 0 {
- if newLevel := p.AddSkillXP(player.SkillName(skill), recipe.XP); newLevel > 0 {
+ if craft.XP > 0 {
+ if newLevel := p.AddSkillXP(player.SkillName(skill), craft.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
+ msg := craft.Message
if msg == "" {
- outputName := recipe.Output
+ outputName := item.ID
if outDef != nil {
outputName = outDef.Name
}
msg = fmt.Sprintf("You produce %s.", outputName)
}
- if p.OptionBool("xp_drops") && recipe.XP > 0 {
+ if p.OptionBool("xp_drops") && craft.XP > 0 {
abbr := player.SkillAbbr[player.SkillName(skill)]
- msg += g.colorize(sess, "xp", fmt.Sprintf(" (+%dxp %s)", recipe.XP, abbr))
+ msg += g.colorize(sess, "xp", fmt.Sprintf(" (+%dxp %s)", craft.XP, abbr))
}
sess.WriteLine(msg)
} else {
- recipe.ConsumeAll(p.HasItem, p.RemoveItem)
+ craftConsumeAll(craft, p.HasItem, p.RemoveItem)
- if recipe.Fail != "" {
+ if craft.Fail != "" {
freeSlot := p.FirstFreeSlot()
if freeSlot >= 0 {
- p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: recipe.Fail, Quantity: 1})
+ p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: craft.Fail, Quantity: 1})
}
}
g.AccountStore.SaveCharacter(p)
- msg := recipe.FailMessage
+ msg := craft.FailMessage
if msg == "" {
msg = "You fail and the materials are lost."
}
@@ -231,7 +262,7 @@ func (g *Game) advanceProduction(sess *net.Session, p *player.Player) bool {
}
}
- if g.canContinueProduction(p, recipe) {
+ if g.canContinueProduction(p, item) {
p.Action.WaitLeft = engine.ToTicks(wait)
return true
}
@@ -243,9 +274,13 @@ func (g *Game) advanceProduction(sess *net.Session, p *player.Player) bool {
return false
}
-func (g *Game) collectByproducts(p *player.Player, recipe *action.RecipeDef) []string {
+func (g *Game) collectByproducts(p *player.Player, item *object.ItemDef) []string {
+ craft := item.FirstCraft()
+ if craft == nil {
+ return nil
+ }
var byproducts []string
- for _, e := range recipe.Consume {
+ for _, e := range craft.Consume {
if len(e.Byproducts) == 0 {
continue
}
@@ -259,19 +294,23 @@ func (g *Game) collectByproducts(p *player.Player, recipe *action.RecipeDef) []s
return byproducts
}
-func (g *Game) canContinueProduction(p *player.Player, recipe *action.RecipeDef) bool {
- if !recipe.HasAllItemsQty(p.CountItem) {
+func (g *Game) canContinueProduction(p *player.Player, item *object.ItemDef) bool {
+ craft := item.FirstCraft()
+ if craft == nil {
return false
}
- outputQty := recipe.OutputQty
+ if !craftHasAllItemsQty(craft, p.CountItem) {
+ return false
+ }
+ outputQty := craft.OutputQty
if outputQty <= 0 {
outputQty = 1
}
- outDef, _ := g.ItemStore.Load(recipe.Output)
+ outDef, _ := g.ItemStore.Load(item.ID)
if outDef != nil && outDef.Stackable {
for i := 0; i < 28; i++ {
slot := p.InvSlot(i)
- if slot != nil && slot.ItemID == recipe.Output {
+ if slot != nil && slot.ItemID == item.ID {
return true
}
}
@@ -316,7 +355,7 @@ func (g *Game) handleRecipeChoice(sess *net.Session, input string) {
if name == "" {
continue
}
- if strings.ToLower(name) == lower || action.WordPrefixMatch(input, name) {
+ if strings.ToLower(name) == lower || object.WordPrefixMatch(input, name) {
if matchIdx >= 0 {
sess.WriteLine("That's ambiguous.")
return
@@ -338,21 +377,11 @@ func (g *Game) handleRecipeChoice(sess *net.Session, input string) {
}
func (g *Game) menuEntryName(entry map[string]string) string {
- if rid, ok := entry["recipe_id"]; ok {
- if r := g.RecipeStore.GetByID(rid); r != nil {
- if def, err := g.ItemStore.Load(r.Output); err == nil {
- return def.Name
- }
- return r.Output
- }
- }
- if rid, ok := entry["fletch_recipe_id"]; ok {
- if r := g.RecipeStore.GetByID(rid); r != nil {
- if def, err := g.ItemStore.Load(r.Output); err == nil {
- return def.Name
- }
- return r.Output
+ if iid, ok := entry["item_id"]; ok {
+ if def, _ := g.ItemStore.Load(iid); def != nil {
+ return def.Name
}
+ return iid
}
if barID, ok := entry["bar_id"]; ok {
if def, _ := g.ItemStore.Load(barID); def != nil {
@@ -360,51 +389,34 @@ func (g *Game) menuEntryName(entry map[string]string) string {
}
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)
+ if iid, ok := entry["item_id"]; ok {
+ g.promptHowMany(sess, iid)
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
- if r := g.RecipeStore.GetByID(rid); r != nil {
- g.startFletchAction(sess, p, r, 0)
- return
- }
- g.reprompt(sess)
- return
- }
- if rid, ok := entry["recipe_id"]; ok {
- g.promptHowMany(sess, rid)
+ g.showSmithTable(sess, sess.Player, barID)
return
}
g.reprompt(sess)
}
-func (g *Game) formatMaterials(r *action.RecipeDef) string {
+func (g *Game) formatMaterials(item *object.ItemDef) string {
+ craft := item.FirstCraft()
+ if craft == nil {
+ return ""
+ }
var parts []string
- for _, e := range r.Consume {
+ for _, e := range craft.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))
+ if e.Quantity > 1 {
+ parts = append(parts, fmt.Sprintf("%s x%d", itemName, e.Quantity))
} else {
parts = append(parts, itemName)
}
@@ -412,9 +424,9 @@ func (g *Game) formatMaterials(r *action.RecipeDef) string {
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
+func (g *Game) showProductionTable(sess *net.Session, p *player.Player, items []*object.ItemDef, title, skill, lastFlagKey, promptVerb string, background bool) {
+ sort.Slice(items, func(i, j int) bool {
+ return items[i].FirstCraft().Level < items[j].FirstCraft().Level
})
mode := g.colorMode(sess)
@@ -426,14 +438,15 @@ func (g *Game) showProductionTable(sess *net.Session, p *player.Player, recipes
Columns: []string{"#", "Product", "Materials", "Level"},
}
- for i, r := range recipes {
- outDef, _ := g.ItemStore.Load(r.Output)
- productName := r.Output
+ for i, item := range items {
+ craft := item.FirstCraft()
+ outDef, _ := g.ItemStore.Load(item.ID)
+ productName := item.ID
if outDef != nil {
productName = outDef.Name
}
- outputQty := r.OutputQty
+ outputQty := craft.OutputQty
if outputQty <= 0 {
outputQty = 1
}
@@ -441,11 +454,11 @@ func (g *Game) showProductionTable(sess *net.Session, p *player.Player, recipes
productName = fmt.Sprintf("%s x%d", productName, outputQty)
}
- matStr := g.formatMaterials(&r)
- levelStr := fmt.Sprint(r.Level)
+ matStr := g.formatMaterials(item)
+ levelStr := fmt.Sprint(craft.Level)
numStr := fmt.Sprintf("%d", i+1)
- canMake := skillLevel >= r.Level && r.HasAllItemsQty(p.CountItem)
+ canMake := skillLevel >= craft.Level && craftHasAllItemsQty(craft, p.CountItem)
if canMake {
productName = g.itemColorize(sess, outDef, productName)
} else {
@@ -464,12 +477,12 @@ func (g *Game) showProductionTable(sess *net.Session, p *player.Player, recipes
sess.WriteLine(line)
}
- lastRecipeID, _ := p.Flags[lastFlagKey].(string)
+ lastItemID, _ := 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 {
+ if lastItemID != "" {
+ for _, item := range items {
+ if item.ID == lastItemID {
+ if outDef, err := g.ItemStore.Load(item.ID); err == nil {
hint = outDef.Name
}
break
@@ -483,7 +496,7 @@ func (g *Game) showProductionTable(sess *net.Session, p *player.Player, recipes
sess.Write(fmt.Sprintf("%s what: ", promptVerb))
}
- sess.PendingMenu = recipeMenuData(recipeDefIDs(recipes))
+ sess.PendingMenu = itemMenuData(itemIDs(items))
sess.PendingSkill = skill
sess.PendingLastFlag = lastFlagKey
sess.PendingBackground = background
@@ -504,26 +517,26 @@ func (g *Game) handleProductChoice(sess *net.Session, input string) {
input = strings.TrimSpace(input)
- var recipes []action.RecipeDef
+ var items []*object.ItemDef
for _, entry := range menuData {
- rid := entry["recipe_id"]
- if r := g.RecipeStore.GetByID(rid); r != nil {
- recipes = append(recipes, *r)
+ iid := entry["item_id"]
+ if item := g.loadCraftItem(iid); item != nil {
+ items = append(items, item)
}
}
- if len(recipes) == 0 {
+ if len(items) == 0 {
g.reprompt(sess)
return
}
- sort.Slice(recipes, func(i, j int) bool {
- return recipes[i].Level < recipes[j].Level
+ sort.Slice(items, func(i, j int) bool {
+ return items[i].FirstCraft().Level < items[j].FirstCraft().Level
})
- lastRecipeID, _ := p.Flags[lastFlagKey].(string)
+ lastItemID, _ := p.Flags[lastFlagKey].(string)
- sel, errMsg := g.resolveRecipeByInput(input, recipes, lastRecipeID)
+ sel, errMsg := g.resolveCraftByInput(input, items, lastItemID)
switch errMsg {
case "ambiguous":
sess.WriteLine("That's ambiguous.")
@@ -535,26 +548,27 @@ func (g *Game) handleProductChoice(sess *net.Session, input string) {
return
}
+ craft := sel.Item.FirstCraft()
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))
+ if skillLevel < craft.Level {
+ sess.WriteLine(fmt.Sprintf("You need level %d %s to make that.", craft.Level, skill))
g.reprompt(sess)
return
}
- if !sel.Recipe.HasAllItemsQty(p.CountItem) {
+ if !craftHasAllItemsQty(craft, p.CountItem) {
sess.WriteLine("You don't have the materials for that.")
g.reprompt(sess)
return
}
p.EnsureFlags()
- p.Flags[lastFlagKey] = sel.Recipe.ID
+ p.Flags[lastFlagKey] = sel.Item.ID
g.AccountStore.SaveCharacter(p)
if background {
- g.startFletchAction(sess, p, sel.Recipe, sel.Count)
+ g.startFletchAction(sess, p, sel.Item, sel.Count)
} else {
- g.startProductionFromRecipe(sess, p, sel.Recipe, sel.Count)
+ g.startProductionFromItem(sess, p, sel.Item, sel.Count)
}
}
@@ -591,10 +605,6 @@ func (g *Game) hasToolType(p *player.Player, toolType string) bool {
_, _, found := g.findTool(p, []string{toolType})
return found
}
-type recipeEntry struct {
- ItemName string
- Recipe action.RecipeDef
-}
type productionTypeInfo struct {
ActionType string
@@ -621,15 +631,15 @@ func init() {
}
}
-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}
+func itemMenuData(itemIDs []string) []map[string]string {
+ out := make([]map[string]string, len(itemIDs))
+ for i, id := range itemIDs {
+ out[i] = map[string]string{"item_id": id}
}
return out
}
-func recipeDefIDs(defs []action.RecipeDef) []string {
+func itemIDs(defs []*object.ItemDef) []string {
ids := make([]string, len(defs))
for i, d := range defs {
ids[i] = d.ID
@@ -637,33 +647,25 @@ func recipeDefIDs(defs []action.RecipeDef) []string {
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
+func (g *Game) promptHowMany(sess *net.Session, itemID string) {
+ sess.PendingItemID = itemID
sess.State = net.StateHowMany
sess.Write("How many (return for all)?: ")
}
-type recipeSelection struct {
- Recipe *action.RecipeDef
- Count int
+type craftSelection struct {
+ Item *object.ItemDef
+ Count int
}
-func (g *Game) resolveRecipeByInput(input string, recipes []action.RecipeDef, lastRecipeID string) (sel recipeSelection, errMsg string) {
+func (g *Game) resolveCraftByInput(input string, items []*object.ItemDef, lastItemID string) (sel craftSelection, errMsg string) {
if input == "" {
- if lastRecipeID == "" {
+ if lastItemID == "" {
return sel, "never_mind"
}
- for i := range recipes {
- if recipes[i].ID == lastRecipeID {
- sel.Recipe = &recipes[i]
+ for i := range items {
+ if items[i].ID == lastItemID {
+ sel.Item = items[i]
return sel, ""
}
}
@@ -673,22 +675,22 @@ func (g *Game) resolveRecipeByInput(input string, recipes []action.RecipeDef, la
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]
+ if idx, err := strconv.Atoi(productName); err == nil && idx > 0 && idx <= len(items) {
+ sel.Item = items[idx-1]
sel.Count = qty
return sel, ""
}
matchCount := 0
- for i := range recipes {
- outDef, _ := g.ItemStore.Load(recipes[i].Output)
- name := recipes[i].Output
+ for i := range items {
+ outDef, _ := g.ItemStore.Load(items[i].ID)
+ name := items[i].ID
if outDef != nil {
name = outDef.Name
}
- if action.WordPrefixMatch(productName, name) {
+ if object.WordPrefixMatch(productName, name) {
if matchCount == 0 {
- sel.Recipe = &recipes[i]
+ sel.Item = items[i]
sel.Count = qty
}
matchCount++
@@ -697,7 +699,7 @@ func (g *Game) resolveRecipeByInput(input string, recipes []action.RecipeDef, la
if matchCount > 1 {
return sel, "ambiguous"
}
- if sel.Recipe == nil {
+ if sel.Item == nil {
return sel, "never_mind"
}
return sel, ""
@@ -716,41 +718,42 @@ func (g *Game) showMenuTable(sess *net.Session, title string, names []string) {
}
}
-func (g *Game) recipeName(sess *net.Session, r *action.RecipeDef) string {
- if r.Output != "" {
- if def, err := g.ItemStore.Load(r.Output); err == nil {
+func (g *Game) itemName(sess *net.Session, item *object.ItemDef) string {
+ if item != nil {
+ if def, err := g.ItemStore.Load(item.ID); err == nil {
return g.itemColorize(sess, def, def.Name)
}
+ return item.ID
}
- return r.DisplayName()
+ return ""
}
-func (g *Game) showRecipeChoice(sess *net.Session, p *player.Player, recipes []action.RecipeDef, emptyMsg, title string, autoOption string) {
- if len(recipes) == 0 {
+func (g *Game) showRecipeChoice(sess *net.Session, p *player.Player, items []*object.ItemDef, emptyMsg, title string, autoOption string) {
+ if len(items) == 0 {
sess.WriteLine(emptyMsg)
return
}
- if len(recipes) == 1 {
+ if len(items) == 1 {
if autoOption != "" && p.OptionBool(autoOption) {
- g.startProductionFromRecipe(sess, p, &recipes[0], 0)
+ g.startProductionFromItem(sess, p, items[0], 0)
return
}
- g.promptHowMany(sess, recipes[0].ID)
+ g.promptHowMany(sess, items[0].ID)
return
}
sess.State = net.StateRecipeChoice
- sess.PendingMenu = recipeMenuData(recipeDefIDs(recipes))
+ sess.PendingMenu = itemMenuData(itemIDs(items))
var names []string
- for i := range recipes {
- names = append(names, g.recipeName(sess, &recipes[i]))
+ for _, item := range items {
+ names = append(names, g.itemName(sess, item))
}
g.showMenuTable(sess, title, names)
}
func (g *Game) handleHowMany(sess *net.Session, input string) {
p := sess.Player
- recipeID := sess.PendingRecipeID
- sess.PendingRecipeID = ""
+ itemID := sess.PendingItemID
+ sess.PendingItemID = ""
sess.State = net.StateGame
input = strings.TrimSpace(input)
@@ -766,46 +769,11 @@ func (g *Game) handleHowMany(sess *net.Session, input string) {
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 {
- recipe = g.RecipeStore.GetByID(recipeID)
- }
-
- if recipe == nil {
+ item := g.loadCraftItem(itemID)
+ if item == 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),
- }
+ g.startProductionFromItem(sess, p, item, count)
}
diff --git a/internal/game/core_startup.go b/internal/game/core_startup.go
index ef33ff7..4bdb50d 100644
--- a/internal/game/core_startup.go
+++ b/internal/game/core_startup.go
@@ -25,7 +25,6 @@ func (g *Game) ValidateStartup() []StartupIssue {
issues = append(issues, validateIDs(g.DataDir, "mobs", "mob")...)
issues = append(issues, validateIDs(g.DataDir, "objects", "object")...)
issues = append(issues, validateIDs(g.DataDir, "drops", "drop table")...)
- issues = append(issues, validateIDs(g.DataDir, "recipes", "recipe")...)
issues = append(issues, validateIDs(g.DataDir, "courses", "course")...)
issues = append(issues, validateIDs(g.DataDir, "modules", "module")...)
issues = append(issues, validateIDs(g.DataDir, "help", "help topic")...)
@@ -34,7 +33,7 @@ func (g *Game) ValidateStartup() []StartupIssue {
issues = append(issues, validateMobs(g)...)
issues = append(issues, validateObjects(g)...)
issues = append(issues, validateItems(g)...)
- issues = append(issues, validateRecipes(g)...)
+ issues = append(issues, validateCraftBlocks(g)...)
issues = append(issues, validateDropTables(g)...)
issues = append(issues, validateCourses(g)...)
issues = append(issues, validateRoomWiring(g)...)
@@ -368,29 +367,6 @@ func validateItems(g *Game) []StartupIssue {
})
}
- for _, mf := range def.MadeFrom {
- for _, item := range mf.Items {
- if item != "" && !itemIDs[item] {
- issues = append(issues, StartupIssue{
- Level: "ERROR",
- Type: "reference",
- Message: fmt.Sprintf("Item %q: made_from item %q does not exist",
- id, item),
- })
- }
- }
- for _, bp := range mf.Byproducts {
- if bp != "" && !itemIDs[bp] {
- issues = append(issues, StartupIssue{
- Level: "ERROR",
- Type: "reference",
- Message: fmt.Sprintf("Item %q: made_from byproduct %q does not exist",
- id, bp),
- })
- }
- }
- }
-
if def.FarmProduct != "" && !itemIDs[def.FarmProduct] {
issues = append(issues, StartupIssue{
Level: "ERROR",
@@ -404,64 +380,61 @@ func validateItems(g *Game) []StartupIssue {
return issues
}
-func validateRecipes(g *Game) []StartupIssue {
+func validateCraftBlocks(g *Game) []StartupIssue {
var issues []StartupIssue
itemIDs := g.ItemStore.IDSet()
+ objIDs := g.ObjectStore.IDSet()
- allRecipes, err := g.RecipeStore.LoadAll()
+ allItems, err := g.ItemStore.LoadAll()
if err != nil {
issues = append(issues, StartupIssue{
Level: "ERROR",
Type: "reference",
- Message: fmt.Sprintf("Failed to load recipes: %v", err),
+ Message: fmt.Sprintf("Failed to load items: %v", err),
})
return issues
}
- recipeIDs := make(map[string]bool)
- for _, r := range allRecipes {
- if r.ID != "" {
- if recipeIDs[r.ID] {
+ for _, item := range allItems {
+ if len(item.Craft) == 0 {
+ continue
+ }
+ for ci := range item.Craft {
+ c := &item.Craft[ci]
+
+ for _, e := range c.Consume {
+ for _, consumeItem := range e.Items {
+ if consumeItem != "" && !itemIDs[consumeItem] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Item %q (craft: %s): consumes nonexistent item %q",
+ item.ID, c.Type, consumeItem),
+ })
+ }
+ }
+ }
+
+ if c.Fail != "" && !itemIDs[c.Fail] {
issues = append(issues, StartupIssue{
Level: "ERROR",
- Type: "duplicate",
- Message: fmt.Sprintf("Recipe %q: duplicate ID field (two recipe files with same ID value)",
- r.ID),
+ Type: "reference",
+ Message: fmt.Sprintf("Item %q (craft: %s): fail product %q does not exist",
+ item.ID, c.Type, c.Fail),
})
}
- recipeIDs[r.ID] = true
- }
- for _, e := range r.Consume {
- for _, item := range e.Items {
- if item != "" && !itemIDs[item] {
+ for _, sid := range c.Station {
+ if sid != "" && !objIDs[sid] {
issues = append(issues, StartupIssue{
Level: "ERROR",
Type: "reference",
- Message: fmt.Sprintf("Recipe %q (%s): consumes nonexistent item %q",
- r.ID, r.Type, item),
+ Message: fmt.Sprintf("Item %q (craft: %s): station object %q does not exist",
+ item.ID, c.Type, sid),
})
}
}
}
-
- if r.Output != "" && !itemIDs[r.Output] {
- issues = append(issues, StartupIssue{
- Level: "ERROR",
- Type: "reference",
- Message: fmt.Sprintf("Recipe %q (%s): output item %q does not exist",
- r.ID, r.Type, r.Output),
- })
- }
-
- if r.Fail != "" && !itemIDs[r.Fail] {
- issues = append(issues, StartupIssue{
- Level: "ERROR",
- Type: "reference",
- Message: fmt.Sprintf("Recipe %q (%s): fail product %q does not exist",
- r.ID, r.Type, r.Fail),
- })
- }
}
return issues
@@ -778,4 +751,4 @@ func LogStartupIssues(issues []StartupIssue) {
if errors > 0 {
fmt.Fprintf(os.Stderr, "*** %d startup errors detected. The server may behave unexpectedly. ***\n", errors)
}
-} \ No newline at end of file
+}
diff --git a/internal/game/craft_index.go b/internal/game/craft_index.go
new file mode 100644
index 0000000..078ec3c
--- /dev/null
+++ b/internal/game/craft_index.go
@@ -0,0 +1,234 @@
+package game
+
+import (
+ "sync"
+
+ "thehouseoficarus/internal/object"
+)
+
+type CraftIndex struct {
+ mu sync.RWMutex
+ byType map[string][]*object.ItemDef
+ byInput map[string][]*object.ItemDef
+ byStation map[string][]*object.ItemDef
+}
+
+func NewCraftIndex() *CraftIndex {
+ return &CraftIndex{
+ byType: make(map[string][]*object.ItemDef),
+ byInput: make(map[string][]*object.ItemDef),
+ byStation: make(map[string][]*object.ItemDef),
+ }
+}
+
+func (idx *CraftIndex) Build(items []*object.ItemDef) {
+ idx.mu.Lock()
+ defer idx.mu.Unlock()
+
+ idx.byType = make(map[string][]*object.ItemDef)
+ idx.byInput = make(map[string][]*object.ItemDef)
+ idx.byStation = make(map[string][]*object.ItemDef)
+
+ for _, def := range items {
+ if len(def.Craft) == 0 {
+ continue
+ }
+ for ci := range def.Craft {
+ craft := &def.Craft[ci]
+ t := craft.Type
+ if t == "" {
+ t = "combine"
+ }
+ idx.byType[t] = appendUnique(idx.byType[t], def)
+
+ for _, e := range craft.Consume {
+ for _, id := range e.Items {
+ idx.byInput[id] = appendUnique(idx.byInput[id], def)
+ }
+ }
+
+ for _, sid := range craft.Station {
+ idx.byStation[sid] = appendUnique(idx.byStation[sid], def)
+ }
+ }
+ }
+}
+
+func appendUnique(items []*object.ItemDef, item *object.ItemDef) []*object.ItemDef {
+ for _, existing := range items {
+ if existing == item {
+ return items
+ }
+ }
+ return append(items, item)
+}
+
+func (idx *CraftIndex) ByType(craftType string) []*object.ItemDef {
+ idx.mu.RLock()
+ defer idx.mu.RUnlock()
+ items := idx.byType[craftType]
+ out := make([]*object.ItemDef, len(items))
+ copy(out, items)
+ return out
+}
+
+func (idx *CraftIndex) ByStation(stationDefID string) []*object.ItemDef {
+ idx.mu.RLock()
+ defer idx.mu.RUnlock()
+ items := idx.byStation[stationDefID]
+ out := make([]*object.ItemDef, len(items))
+ copy(out, items)
+ return out
+}
+
+func (idx *CraftIndex) ByInput(itemID string) []*object.ItemDef {
+ idx.mu.RLock()
+ defer idx.mu.RUnlock()
+ items := idx.byInput[itemID]
+ out := make([]*object.ItemDef, len(items))
+ copy(out, items)
+ return out
+}
+
+func (idx *CraftIndex) FindByTwoInputs(itemA, itemB string) []*object.ItemDef {
+ idx.mu.RLock()
+ defer idx.mu.RUnlock()
+
+ candidatesA := idx.byInput[itemA]
+ if len(candidatesA) == 0 {
+ return nil
+ }
+
+ candidateSetB := make(map[string]bool)
+ for _, def := range idx.byInput[itemB] {
+ candidateSetB[def.ID] = true
+ }
+
+ var results []*object.ItemDef
+ for _, def := range candidatesA {
+ if !candidateSetB[def.ID] {
+ continue
+ }
+ for _, craft := range def.Craft {
+ entryA := craftEntry(craft, itemA)
+ entryB := craftEntry(craft, itemB)
+ if entryA >= 0 && entryB >= 0 && entryA != entryB {
+ results = append(results, def)
+ break
+ }
+ }
+ }
+ return results
+}
+
+func (idx *CraftIndex) FindByStationInput(stationDefID, itemID string) []*object.ItemDef {
+ idx.mu.RLock()
+ defer idx.mu.RUnlock()
+
+ var results []*object.ItemDef
+ for _, def := range idx.byStation[stationDefID] {
+ for _, craft := range def.Craft {
+ if craftMatchesEntry(&craft, itemID) {
+ results = append(results, def)
+ break
+ }
+ }
+ }
+ return results
+}
+
+func craftEntryFor(def *object.ItemDef, itemID string) int {
+ for _, craft := range def.Craft {
+ if ei := craftEntry(craft, itemID); ei >= 0 {
+ return ei
+ }
+ }
+ return -1
+}
+
+func craftEntry(craft object.CraftDef, itemID string) int {
+ for ei, e := range craft.Consume {
+ for _, id := range e.Items {
+ if id == itemID {
+ return ei
+ }
+ }
+ }
+ return -1
+}
+
+func craftMatchesEntry(c *object.CraftDef, itemID string) bool {
+ if c == nil {
+ return false
+ }
+ for _, e := range c.Consume {
+ for _, id := range e.Items {
+ if id == itemID {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+func craftHasAllItems(c *object.CraftDef, hasItem func(string) bool) bool {
+ if c == nil {
+ return false
+ }
+ for _, e := range c.Consume {
+ found := false
+ for _, id := range e.Items {
+ if hasItem(id) {
+ found = true
+ break
+ }
+ }
+ if !found {
+ return false
+ }
+ }
+ return true
+}
+
+func craftHasAllItemsQty(c *object.CraftDef, countItem func(string) int) bool {
+ if c == nil {
+ return false
+ }
+ for _, e := range c.Consume {
+ found := false
+ for _, id := range e.Items {
+ qty := e.Quantity
+ if qty <= 0 {
+ qty = 1
+ }
+ if countItem(id) >= qty {
+ found = true
+ break
+ }
+ }
+ if !found {
+ return false
+ }
+ }
+ return true
+}
+
+func craftConsumeAll(c *object.CraftDef, hasItem func(string) bool, removeItem func(string, int) bool) bool {
+ if c == nil {
+ return false
+ }
+ for _, e := range c.Consume {
+ found := false
+ for _, id := range e.Items {
+ if hasItem(id) {
+ removeItem(id, e.Quantity)
+ found = true
+ break
+ }
+ }
+ if !found {
+ return false
+ }
+ }
+ return true
+}
diff --git a/internal/game/game.go b/internal/game/game.go
index ac8622e..0dd5bdc 100644
--- a/internal/game/game.go
+++ b/internal/game/game.go
@@ -7,7 +7,6 @@ import (
"sync"
"time"
- "thehouseoficarus/internal/action"
"thehouseoficarus/internal/combat"
"thehouseoficarus/internal/config"
"thehouseoficarus/internal/engine"
@@ -40,7 +39,7 @@ type Game struct {
ItemStore *object.ItemStore
AccountStore *player.AccountStore
MobStore *world.MobStore
- RecipeStore *action.RecipeStore
+ CraftIndex *CraftIndex
CourseStore *CourseStore
Hub *net.Hub
Ticks *engine.Engine
@@ -67,7 +66,7 @@ func New(dataDir string, colorConfig *config.ColorsConfig, valConfig config.Vali
ItemStore: object.NewItemStore(dataDir),
AccountStore: player.NewAccountStore(dataDir),
MobStore: world.NewMobStore(dataDir),
- RecipeStore: action.NewRecipeStore(dataDir),
+ CraftIndex: NewCraftIndex(),
CourseStore: NewCourseStore(dataDir),
Ticks: engine.New(),
WorldFlags: make(map[string]any),
@@ -85,6 +84,7 @@ func New(dataDir string, colorConfig *config.ColorsConfig, valConfig config.Vali
}
g.CourseStore.LoadAll()
g.LoadMods()
+ g.buildCraftIndex()
g.ValidateAndLog()
return g
}
@@ -331,6 +331,14 @@ func (g *Game) ProcessQueuedCommands() {
g.flushPendingDepletions()
}
+func (g *Game) buildCraftIndex() {
+ items, err := g.ItemStore.LoadAll()
+ if err != nil {
+ return
+ }
+ g.CraftIndex.Build(items)
+}
+
type pendingDepletion struct {
instanceKey string
objDefID string