aboutsummaryrefslogtreecommitdiff
path: root/internal/game/production_core.go
diff options
context:
space:
mode:
authorworkhorse <workhorse@localhost.localdomain>2026-06-19 20:24:52 -0400
committerworkhorse <workhorse@localhost.localdomain>2026-06-19 20:24:52 -0400
commit8ee8982b8759bcae116647cc993867f4c8b0a6ce (patch)
treeee01f7b1d745cbc94d9981108a1209527487b3e5 /internal/game/production_core.go
parent2bec24859af8f03c80a0a58e3240519942450167 (diff)
downloadthehouseoficarus-8ee8982b8759bcae116647cc993867f4c8b0a6ce.tar.gz
reorganized items, objects, and rooms in directories. oranized module names. added startup checks.
Diffstat (limited to 'internal/game/production_core.go')
-rw-r--r--internal/game/production_core.go606
1 files changed, 0 insertions, 606 deletions
diff --git a/internal/game/production_core.go b/internal/game/production_core.go
deleted file mode 100644
index 42f64e1..0000000
--- a/internal/game/production_core.go
+++ /dev/null
@@ -1,606 +0,0 @@
-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.Qty > 1 {
- parts = append(parts, fmt.Sprintf("%s x%d", itemName, e.Qty))
- } else {
- parts = append(parts, itemName)
- }
- }
- return strings.Join(parts, ", ")
-}
-
-func (g *Game) showProductionTable(sess *net.Session, p *player.Player, recipes []action.RecipeDef, title, skill, lastFlagKey, promptVerb string, background bool) {
- sort.Slice(recipes, func(i, j int) bool {
- return recipes[i].Level < recipes[j].Level
- })
-
- mode := g.colorMode(sess)
- skillLevel := p.Level(player.SkillName(skill))
- dimSpec := color.Parse("240")
-
- tbl := &Table{
- Title: title,
- Columns: []string{"#", "Product", "Materials", "Level"},
- }
-
- for i, r := range recipes {
- outDef, _ := g.ItemStore.Load(r.Output)
- productName := r.Output
- if outDef != nil {
- productName = outDef.Name
- }
-
- outputQty := r.OutputQty
- if outputQty <= 0 {
- outputQty = 1
- }
- if outDef != nil && outDef.Stackable && outputQty > 1 {
- productName = fmt.Sprintf("%s x%d", productName, outputQty)
- }
-
- matStr := g.formatRecipeMaterials(&r)
- levelStr := fmt.Sprint(r.Level)
- numStr := fmt.Sprintf("%d", i+1)
-
- canMake := skillLevel >= r.Level && r.HasAllItemsQty(p.CountItem)
- if canMake {
- productName = g.itemColorize(sess, outDef, productName)
- } else {
- productName = color.Render(mode, dimSpec, productName)
- matStr = color.Render(mode, dimSpec, matStr)
- levelStr = color.Render(mode, dimSpec, levelStr)
- numStr = color.Render(mode, dimSpec, numStr)
- }
-
- tbl.Rows = append(tbl.Rows, []string{numStr, productName, matStr, levelStr})
- }
-
- unicode := p.OptionBool("unicode")
- sess.WriteLine("")
- for _, line := range tbl.Render(unicode) {
- sess.WriteLine(line)
- }
-
- lastRecipeID, _ := p.Flags[lastFlagKey].(string)
- hint := ""
- if lastRecipeID != "" {
- for _, r := range recipes {
- if r.ID == lastRecipeID {
- if outDef, err := g.ItemStore.Load(r.Output); err == nil {
- hint = outDef.Name
- }
- break
- }
- }
- }
-
- if hint != "" {
- sess.Write(fmt.Sprintf("%s what (enter for all %s): ", promptVerb, hint))
- } else {
- sess.Write(fmt.Sprintf("%s what: ", promptVerb))
- }
-
- sess.PendingMenu = recipeMenuData(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
-}