aboutsummaryrefslogtreecommitdiff
path: root/internal/game/cmd_farm.go
diff options
context:
space:
mode:
authorworkhorse <workhorse@localhost.localdomain>2026-06-19 11:51:17 -0400
committerworkhorse <workhorse@localhost.localdomain>2026-06-19 11:51:17 -0400
commit575a71dcfed3b9fa44af244836f638a23df05a9a (patch)
treeeb5eff16eab86247a3dd93ff4d7fd3781f291b64 /internal/game/cmd_farm.go
parent52a3ce6a4b4a254dc5d3067979a09e93c060fa20 (diff)
downloadthehouseoficarus-575a71dcfed3b9fa44af244836f638a23df05a9a.tar.gz
feat: farming roughly implemented
Diffstat (limited to 'internal/game/cmd_farm.go')
-rw-r--r--internal/game/cmd_farm.go523
1 files changed, 523 insertions, 0 deletions
diff --git a/internal/game/cmd_farm.go b/internal/game/cmd_farm.go
new file mode 100644
index 0000000..0a374f0
--- /dev/null
+++ b/internal/game/cmd_farm.go
@@ -0,0 +1,523 @@
+package game
+
+import (
+ "fmt"
+ "strings"
+
+ "thehouseoficarus/internal/action"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+)
+
+func (g *Game) doPlant(sess *net.Session, input string) {
+ p := sess.Player.(*player.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
+ }
+
+ p.RemoveItem(seedID, 1)
+
+ p.Action = &action.Action{
+ Type: "plant",
+ TargetID: seedID,
+ TargetName: seedDef.Name,
+ WaitLeft: 2,
+ Data: map[string]any{
+ "seed_id": seedID,
+ "prefix": prefix,
+ "xp": float64(seedDef.FarmPlantXP),
+ },
+ }
+ p.ActionState = &ActionState{Type: ActionPlanting, TargetName: seedDef.Name}
+
+ g.AccountStore.SaveCharacter(p)
+}
+
+func (g *Game) doHarvest(sess *net.Session, input string) {
+ p := sess.Player.(*player.Player)
+
+ prefix, _, _ := g.findFarmPatch(sess, 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 = &action.Action{
+ Type: "harvest",
+ TargetID: prefix,
+ TargetName: productName,
+ WaitLeft: 3,
+ Data: map[string]any{
+ "prefix": prefix,
+ "seed_id": seedID,
+ "product": product,
+ "min_yield": float64(minYield),
+ "max_yield": float64(maxYield),
+ "xp": float64(harvestXP),
+ },
+ }
+ p.ActionState = &ActionState{Type: ActionHarvesting, TargetName: productName}
+
+ g.AccountStore.SaveCharacter(p)
+}
+
+func (g *Game) doRake(sess *net.Session, input string) {
+ p := sess.Player.(*player.Player)
+
+ if !g.hasToolType(p, "rake") {
+ sess.WriteLine("You need a rake to do that.")
+ return
+ }
+
+ prefix, _, _ := g.findFarmPatch(sess, 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 = &action.Action{
+ Type: "rake",
+ TargetID: prefix,
+ TargetName: patchName,
+ WaitLeft: 4,
+ Data: map[string]any{
+ "prefix": prefix,
+ },
+ }
+ p.ActionState = &ActionState{Type: ActionRaking, TargetName: patchName}
+
+ g.AccountStore.SaveCharacter(p)
+}
+
+func (g *Game) doWater(sess *net.Session, input string) {
+ p := sess.Player.(*player.Player)
+
+ if !g.hasToolType(p, "watering_can") {
+ sess.WriteLine("You need a watering can to do that.")
+ return
+ }
+
+ prefix, _, _ := g.findFarmPatch(sess, 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 = &action.Action{
+ Type: "water",
+ TargetID: prefix,
+ TargetName: patchName,
+ WaitLeft: 2,
+ Data: map[string]any{
+ "prefix": prefix,
+ },
+ }
+ p.ActionState = &ActionState{Type: ActionWatering, TargetName: patchName}
+
+ g.AccountStore.SaveCharacter(p)
+}
+
+func (g *Game) doCure(sess *net.Session, input string) {
+ p := sess.Player.(*player.Player)
+
+ if !p.HasItem("plant_cure") {
+ sess.WriteLine("You don't have any plant cure.")
+ return
+ }
+
+ prefix, _, _ := g.findFarmPatch(sess, 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 = &action.Action{
+ Type: "cure",
+ TargetID: prefix,
+ TargetName: patchName,
+ WaitLeft: 2,
+ Data: map[string]any{
+ "prefix": prefix,
+ },
+ }
+ p.ActionState = &ActionState{Type: ActionCuring, TargetName: patchName}
+
+ g.AccountStore.SaveCharacter(p)
+}
+
+func (g *Game) doInspect(sess *net.Session, input string) {
+ p := sess.Player.(*player.Player)
+
+ objInstances := g.World.AllObjInstances(p.RoomID)
+ var farmPatches []struct {
+ prefix string
+ defID string
+ index int
+ }
+
+ for _, obj := range objInstances {
+ if !farmPatchDefIDs[obj.DefID] {
+ continue
+ }
+ pref := farmFlagPrefix(obj.DefID, obj.Index)
+ if pref == "" {
+ continue
+ }
+ farmPatches = append(farmPatches, struct {
+ prefix string
+ defID string
+ index int
+ }{pref, obj.DefID, obj.Index})
+ }
+
+ if len(farmPatches) == 0 {
+ sess.WriteLine("There are no farming patches here.")
+ return
+ }
+
+ if input != "" {
+ lower := strings.ToLower(input)
+ var filtered []struct {
+ prefix string
+ defID string
+ index int
+ }
+ for _, fp := range farmPatches {
+ def, _ := g.ObjectStore.Load(fp.defID)
+ if def != nil && strings.Contains(strings.ToLower(def.Name), lower) {
+ filtered = append(filtered, fp)
+ }
+ }
+ if len(filtered) == 0 {
+ for _, fp := range farmPatches {
+ if strings.Contains(fp.prefix, lower) || strings.Contains(strings.ToLower(fp.defID), lower) {
+ filtered = append(filtered, fp)
+ }
+ }
+ }
+ farmPatches = filtered
+ }
+
+ for _, fp := range farmPatches {
+ g.ensureFarmState(p, fp.prefix)
+ def, _ := g.ObjectStore.Load(fp.defID)
+ patchName := fp.defID
+ if def != nil {
+ patchName = def.Name
+ }
+ indexStr := ""
+ if len(farmPatches) > 1 || fp.index > 0 {
+ indexStr = fmt.Sprintf(" %d", fp.index+1)
+ }
+
+ sess.WriteLine(fmt.Sprintf("\n=== %s%s ===", patchName, indexStr))
+
+ weeds, _ := p.Flags[fp.prefix+"_weeds"].(bool)
+ if weeds {
+ sess.WriteLine(" Status: Weeds")
+ sess.WriteLine(" (Rake to clear before planting)")
+ continue
+ }
+
+ seedID, _ := p.Flags[fp.prefix+"_seed"].(string)
+ if seedID == "" {
+ sess.WriteLine(" Status: Empty")
+ sess.WriteLine(" (Ready to plant)")
+ continue
+ }
+
+ seedName := seedID
+ if def, err := g.ItemStore.Load(seedID); err == nil {
+ seedName = def.Name
+ }
+
+ dead, _ := p.Flags[fp.prefix+"_dead"].(bool)
+ if dead {
+ sess.WriteLine(fmt.Sprintf(" Status: Dead"))
+ sess.WriteLine(fmt.Sprintf(" Planted: %s", seedName))
+ sess.WriteLine(" (Rake to clear)")
+ continue
+ }
+
+ diseased, _ := p.Flags[fp.prefix+"_diseased"].(bool)
+ if diseased {
+ sess.WriteLine(fmt.Sprintf(" Status: Diseased!"))
+ sess.WriteLine(fmt.Sprintf(" Planted: %s", seedName))
+ sess.WriteLine(" (Use plant cure to save it)")
+ continue
+ }
+
+ ready, _ := p.Flags[fp.prefix+"_ready"].(bool)
+ if ready {
+ sess.WriteLine(fmt.Sprintf(" Status: Ready to harvest!"))
+ sess.WriteLine(fmt.Sprintf(" Planted: %s", seedName))
+ continue
+ }
+
+ stage := intFlag(p.Flags, fp.prefix+"_stage")
+ maxStages := 4
+ if def, err := g.ItemStore.Load(seedID); err == nil && def.FarmStages > 0 {
+ maxStages = def.FarmStages
+ }
+ watered, _ := p.Flags[fp.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(sess *net.Session, p *player.Player, input string, matcher func(map[string]any, string) bool) (prefix string, defID string, index int) {
+ objInstances := g.World.AllObjInstances(p.RoomID)
+ if len(objInstances) == 0 {
+ return "", "", -1
+ }
+
+ 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 "", "", -1
+ }
+
+ if input == "" {
+ return patches[0].prefix, patches[0].defID, patches[0].index
+ }
+
+ lower := strings.ToLower(input)
+ for _, pi := range patches {
+ if strings.Contains(lower, strings.ToLower(pi.name)) {
+ return pi.prefix, pi.defID, pi.index
+ }
+ }
+ for _, pi := range patches {
+ if strings.Contains(lower, pi.prefix) {
+ return pi.prefix, pi.defID, pi.index
+ }
+ }
+
+ return "", "", -1
+}