From 085e728d22a3369bd110b77409914cfbe9ebe611 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Tue, 16 Jun 2026 04:17:43 -0400 Subject: feat: recipe system, combining items, cooking skill --- internal/game/cmd_use.go | 200 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 internal/game/cmd_use.go (limited to 'internal/game/cmd_use.go') diff --git a/internal/game/cmd_use.go b/internal/game/cmd_use.go new file mode 100644 index 0000000..ef36e12 --- /dev/null +++ b/internal/game/cmd_use.go @@ -0,0 +1,200 @@ +package game + +import ( + "fmt" + "strings" + + "thirdcollapse/internal/net" + "thirdcollapse/internal/object" + "thirdcollapse/internal/player" +) + +func (g *Game) doUse(sess *net.Session, input string) { + p := sess.Player.(*player.Player) + g.CancelAction(p) + + lower := strings.ToLower(input) + + sep := "" + if strings.Contains(lower, " on ") { + sep = " on " + } else if strings.Contains(lower, " with ") { + sep = " with " + } + + if sep != "" { + parts := strings.SplitN(lower, sep, 2) + itemA := strings.TrimSpace(parts[0]) + itemB := strings.TrimSpace(parts[1]) + if itemA == itemB { + sess.WriteLine("You can't use that with itself.") + return + } + g.doUseItemOnTarget(sess, p, itemA, itemB) + return + } + + if len(strings.Fields(input)) >= 2 { + g.doUseItemOnTarget(sess, p, strings.Fields(input)[0], strings.Join(strings.Fields(input)[1:], " ")) + return + } + + g.StartAction(sess, "use", input) +} + +func (g *Game) doUseItemOnTarget(sess *net.Session, p *player.Player, itemAName, itemBName string) { + matchesA := g.findInventoryMatches(itemAName, p) + if len(matchesA) == 0 { + sess.WriteLine(fmt.Sprintf("You don't have any '%s'.", itemAName)) + return + } + + uniqueA := uniqueItemNames(matchesA) + if len(uniqueA) > 1 { + sess.WriteLine("Which one?") + for _, name := range uniqueA { + sess.WriteLine(fmt.Sprintf(" - %s", name)) + } + return + } + + itemAID := matchesA[0].ID + + matchesB := g.findInventoryMatches(itemBName, p) + + objInstances := g.World.FindObjInstances(p.RoomID, itemBName) + if len(objInstances) > 0 { + objSt := &objInstances[0] + def, _ := g.ObjectStore.Load(objSt.DefID) + if def == nil { + g.StartAction(sess, "use", itemBName) + return + } + + stationIDs := []string{def.ID} + recipes, err := g.RecipeStore.FindByStation(itemAID, stationIDs, "") + if err == nil && len(recipes) > 0 { + if len(recipes) == 1 { + g.startCook(sess, p, &recipes[0], def.Name) + return + } + sess.PendingRecipeItem = itemAID + sess.PendingCookMenu = recipesToMenuData(recipes) + sess.State = net.StateCookRecipe + sess.WriteLine(fmt.Sprintf("\nWhat would you like to make with %s on the %s?", itemAName, def.Name)) + for i, r := range recipes { + sess.WriteLine(fmt.Sprintf(" %d) %s", i+1, g.recipeName(sess, &r))) + } + return + } + + g.StartAction(sess, "use", itemBName) + return + } + + matchesB = g.findInventoryMatches(itemBName, p) + if len(matchesB) > 0 { + uniqueB := uniqueItemNames(matchesB) + if len(uniqueB) > 1 { + sess.WriteLine("Which '" + itemBName + "'?") + for _, name := range uniqueB { + sess.WriteLine(fmt.Sprintf(" - %s", name)) + } + return + } + + itemBID := matchesB[0].ID + + matched := g.findCombineResults(sess, p, itemAID, itemBID) + if len(matched) == 1 { + g.produceCombined(sess, p, matched[0]) + return + } + if len(matched) > 1 { + sess.PendingCookMenu = combineMenuData(matched) + sess.State = net.StateCookRecipe + sess.WriteLine("\nWhat would you like to make?") + for i, def := range matched { + sess.WriteLine(fmt.Sprintf(" %d) %s", i+1, g.itemColorize(sess, def, def.Name))) + } + return + } + + sess.WriteLine("You can't combine those items.") + return + } + + 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 + } + } + } + if aEntry >= 0 && bEntry >= 0 && aEntry != bEntry && madeFromItemsAvailable(p, def.MadeFrom) { + matched = append(matched, def) + } + } + return matched +} + +func (g *Game) produceCombined(sess *net.Session, p *player.Player, def *object.ItemDef) { + freeSlot := p.FirstFreeSlot() + if freeSlot == -1 { + sess.WriteLine("Your inventory is too full.") + return + } + for _, entry := range def.MadeFrom { + for _, id := range entry.Items { + if p.HasItem(id) { + p.RemoveItem(id, entry.Qty) + break + } + } + } + p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: def.ID, Quantity: 1}) + g.AccountStore.SaveCharacter(p) + sess.WriteLine(fmt.Sprintf("You combine the ingredients and create %s.", g.itemColorize(sess, def, def.Name))) +} + +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 madeFromItemsAvailable(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 +} -- cgit v1.2.3