package game import ( "fmt" "strings" "thehouseoficarus/internal/behavior" "thehouseoficarus/internal/net" "thehouseoficarus/internal/player" ) func (g *Game) executePlant(sess *net.Session, args []string, rawInput string) { p := sess.Player g.cancelAction(p) if len(args) == 0 { sess.WriteLine("Plant what?") } else { g.doPlant(sess, strings.Join(args, " ")) } } func (g *Game) executeHarvest(sess *net.Session, args []string, rawInput string) { p := sess.Player g.cancelAction(p) if len(args) == 0 { g.doHarvest(sess, "") } else { g.doHarvest(sess, strings.Join(args, " ")) } } func (g *Game) executeRake(sess *net.Session, args []string, rawInput string) { p := sess.Player g.cancelAction(p) if len(args) == 0 { g.doRake(sess, "") } else { g.doRake(sess, strings.Join(args, " ")) } } func (g *Game) executeWater(sess *net.Session, args []string, rawInput string) { p := sess.Player g.cancelAction(p) if len(args) == 0 { g.doWater(sess, "") } else { g.doWater(sess, strings.Join(args, " ")) } } func (g *Game) executeCure(sess *net.Session, args []string, rawInput string) { p := sess.Player g.cancelAction(p) if len(args) == 0 { g.doCure(sess, "") } else { g.doCure(sess, strings.Join(args, " ")) } } func (g *Game) doPlant(sess *net.Session, input string) { p := sess.Player if input == "" { sess.WriteLine("Plant what?") return } matches := g.findInventoryMatches(input, p) if len(matches) == 0 { sess.WriteLine(fmt.Sprintf("You don't have any '%s'.", input)) return } unique := uniqueItemNames(matches) if len(unique) > 1 { g.showWhichOne(sess, matches) return } seedID := matches[0].ID seedDef, err := g.ItemStore.Load(seedID) if err != nil || seedDef.FarmPatchType == "" { sess.WriteLine("You can't plant that.") return } farmLevel := p.Level(player.Farming) if seedDef.FarmLevel > 0 && farmLevel < seedDef.FarmLevel { sess.WriteLine(fmt.Sprintf("You need level %d farming to plant that. Your farming level is %d.", seedDef.FarmLevel, farmLevel)) return } if !g.hasToolType(p, "spade") { sess.WriteLine("You need a spade to plant seeds.") return } patchDefID := seedDef.FarmPatchType + "_patch" objInstances := g.World.FindObjInstances(p.RoomID, patchDefID) if len(objInstances) == 0 { sess.WriteLine("There's no suitable patch here to plant that.") return } var prefix string for _, obj := range objInstances { pref := farmFlagPrefix(patchDefID, obj.Index) if pref == "" { continue } g.ensureFarmState(p, pref) weeds, _ := p.Flags[pref+"_weeds"].(bool) seedPlanted, _ := p.Flags[pref+"_seed"].(string) if !weeds && seedPlanted == "" { prefix = pref break } } if prefix == "" { sess.WriteLine("All patches here have something in them. Rake them first.") return } // The seed is consumed when the plant action COMPLETES (advancePlant), // not here: an interrupted action must not destroy the seed. p.Action = &behavior.Action{ Type: "plant", TargetID: seedID, TargetName: seedDef.Name, WaitLeft: 2, Data: &behavior.FarmData{ SeedID: seedID, Prefix: prefix, XP: float64(seedDef.FarmPlantXP), }, } g.saveCharacter(p) } func (g *Game) doHarvest(sess *net.Session, input string) { p := sess.Player prefix := g.findFarmPatch(p, input, func(flags map[string]any, prefix string) bool { ready, _ := flags[prefix+"_ready"].(bool) return ready }) if prefix == "" { if input != "" { sess.WriteLine("That patch isn't ready to harvest.") } else { sess.WriteLine("There's nothing ready to harvest here.") } return } g.ensureFarmState(p, prefix) ready, _ := p.Flags[prefix+"_ready"].(bool) if !ready { sess.WriteLine("There's nothing ready to harvest here.") return } if !g.hasToolType(p, "spade") { sess.WriteLine("You need a spade to harvest.") return } seedID, _ := p.Flags[prefix+"_seed"].(string) if seedID == "" { sess.WriteLine("There's nothing planted here.") return } seedDef, err := g.ItemStore.Load(seedID) if err != nil { sess.WriteLine("Error loading seed data.") return } product := seedDef.FarmProduct if product == "" { sess.WriteLine("This crop has no harvest product defined.") return } productDef, _ := g.ItemStore.Load(product) productName := product if productDef != nil { productName = productDef.Name } if p.FreeSlots() <= 0 && (productDef == nil || !productDef.Stackable || p.CountItem(product) == 0) { sess.WriteLine("Your inventory is full.") return } minYield := seedDef.FarmMinYield maxYield := seedDef.FarmMaxYield if minYield <= 0 { minYield = 3 } if maxYield <= 0 { maxYield = 10 } harvestXP := seedDef.FarmHarvestXP p.Action = &behavior.Action{ Type: "harvest", TargetID: prefix, TargetName: productName, WaitLeft: 3, Data: &behavior.FarmData{ Prefix: prefix, SeedID: seedID, Product: product, MinYield: float64(minYield), MaxYield: float64(maxYield), XP: float64(harvestXP), }, } g.saveCharacter(p) } func (g *Game) doRake(sess *net.Session, input string) { p := sess.Player if !g.hasToolType(p, "rake") { sess.WriteLine("You need a rake to do that.") return } prefix := g.findFarmPatch(p, input, func(flags map[string]any, prefix string) bool { weeds, _ := flags[prefix+"_weeds"].(bool) dead, _ := flags[prefix+"_dead"].(bool) return weeds || dead }) if prefix == "" { if input != "" { sess.WriteLine("That patch doesn't need raking.") } else { sess.WriteLine("There's nothing here that needs raking.") } return } g.ensureFarmState(p, prefix) weeds, _ := p.Flags[prefix+"_weeds"].(bool) dead, _ := p.Flags[prefix+"_dead"].(bool) if !weeds && !dead { sess.WriteLine("The patch doesn't need raking.") return } patchName := patchDisplayName(prefix) p.Action = &behavior.Action{ Type: "rake", TargetID: prefix, TargetName: patchName, WaitLeft: 4, Data: &behavior.FarmData{ Prefix: prefix, }, } g.saveCharacter(p) } func (g *Game) doWater(sess *net.Session, input string) { p := sess.Player if !g.hasToolType(p, "watering_can") { sess.WriteLine("You need a watering can to do that.") return } prefix := g.findFarmPatch(p, input, func(flags map[string]any, prefix string) bool { seed, _ := flags[prefix+"_seed"].(string) if seed == "" { return false } ready, _ := flags[prefix+"_ready"].(bool) dead, _ := flags[prefix+"_dead"].(bool) watered, _ := flags[prefix+"_watered"].(bool) return !dead && !ready && !watered }) if prefix == "" { if input != "" { sess.WriteLine("That patch doesn't need watering.") } else { sess.WriteLine("There's nothing here that needs watering.") } return } g.ensureFarmState(p, prefix) seedID, _ := p.Flags[prefix+"_seed"].(string) if seedID == "" { sess.WriteLine("There's nothing planted here to water.") return } ready, _ := p.Flags[prefix+"_ready"].(bool) if ready { sess.WriteLine("The crop is already fully grown.") return } watered, _ := p.Flags[prefix+"_watered"].(bool) if watered { sess.WriteLine("The patch is already watered.") return } patchName := patchDisplayName(prefix) p.Action = &behavior.Action{ Type: "water", TargetID: prefix, TargetName: patchName, WaitLeft: 2, Data: &behavior.FarmData{ Prefix: prefix, }, } g.saveCharacter(p) } func (g *Game) doCure(sess *net.Session, input string) { p := sess.Player if !p.HasItem("plant_cure") { sess.WriteLine("You don't have any plant cure.") return } prefix := g.findFarmPatch(p, input, func(flags map[string]any, prefix string) bool { diseased, _ := flags[prefix+"_diseased"].(bool) return diseased }) if prefix == "" { if input != "" { sess.WriteLine("That patch isn't diseased.") } else { sess.WriteLine("There's nothing here that needs curing.") } return } g.ensureFarmState(p, prefix) diseased, _ := p.Flags[prefix+"_diseased"].(bool) if !diseased { sess.WriteLine("The patch isn't diseased.") return } patchName := patchDisplayName(prefix) p.Action = &behavior.Action{ Type: "cure", TargetID: prefix, TargetName: patchName, WaitLeft: 2, Data: &behavior.FarmData{ Prefix: prefix, }, } g.saveCharacter(p) } func (g *Game) ShowFarmPatchDetail(sess *net.Session, p *player.Player, prefix, defID string, index int) { weeds, _ := p.Flags[prefix+"_weeds"].(bool) if weeds { sess.WriteLine(" Status: Weeds") sess.WriteLine(" (Rake to clear before planting)") return } seedID, _ := p.Flags[prefix+"_seed"].(string) if seedID == "" { sess.WriteLine(" Status: Empty") sess.WriteLine(" (Ready to plant)") return } seedName := seedID if def, err := g.ItemStore.Load(seedID); err == nil { seedName = def.Name } dead, _ := p.Flags[prefix+"_dead"].(bool) if dead { sess.WriteLine(" Status: Dead") sess.WriteLine(fmt.Sprintf(" Planted: %s", seedName)) sess.WriteLine(" (Rake to clear)") return } diseased, _ := p.Flags[prefix+"_diseased"].(bool) if diseased { sess.WriteLine(" Status: Diseased!") sess.WriteLine(fmt.Sprintf(" Planted: %s", seedName)) sess.WriteLine(" (Use plant cure to save it)") return } ready, _ := p.Flags[prefix+"_ready"].(bool) if ready { sess.WriteLine(" Status: Ready to harvest!") sess.WriteLine(fmt.Sprintf(" Planted: %s", seedName)) return } stage := intFromFlag(p.Flags, prefix+"_stage") maxStages := 4 if def, err := g.ItemStore.Load(seedID); err == nil && def.FarmStages > 0 { maxStages = def.FarmStages } watered, _ := p.Flags[prefix+"_watered"].(bool) sess.WriteLine(fmt.Sprintf(" Status: Growing (stage %d/%d)", stage, maxStages)) sess.WriteLine(fmt.Sprintf(" Planted: %s", seedName)) sess.WriteLine(fmt.Sprintf(" Watered: %s", boolToYes(watered))) sess.WriteLine(fmt.Sprintf(" Diseased: %s", boolToYes(diseased))) } func boolToYes(b bool) string { if b { return "Yes" } return "No" } func (g *Game) findFarmPatch(p *player.Player, input string, matcher func(map[string]any, string) bool) (prefix string) { objInstances := g.World.AllObjInstances(p.RoomID) if len(objInstances) == 0 { return "" } type patchInfo struct { prefix string defID string index int name string } var patches []patchInfo for _, obj := range objInstances { if !farmPatchDefIDs[obj.DefID] { continue } g.ensureFarmState(p, farmFlagPrefix(obj.DefID, obj.Index)) } for _, obj := range objInstances { if !farmPatchDefIDs[obj.DefID] { continue } pref := farmFlagPrefix(obj.DefID, obj.Index) if pref == "" { continue } if !matcher(p.Flags, pref) { continue } var name string if def, err := g.ObjectStore.Load(obj.DefID); err == nil { name = def.Name } else { name = obj.DefID } patches = append(patches, patchInfo{prefix: pref, defID: obj.DefID, index: obj.Index, name: name}) } if len(patches) == 0 { return "" } if input == "" { return patches[0].prefix } lower := strings.ToLower(input) for _, pi := range patches { if strings.Contains(lower, strings.ToLower(pi.name)) { return pi.prefix } } for _, pi := range patches { if strings.Contains(lower, pi.prefix) { return pi.prefix } } return "" }