aboutsummaryrefslogtreecommitdiff
path: root/internal/game/act_farm.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-25 15:40:48 -0400
committerhistoria <[not public]>2026-06-25 15:40:48 -0400
commitabd612c15799f604e671e83dc7c410ed2b44185f (patch)
tree0597ca92350de73aac2a7cf26b2f2d6595dac0cc /internal/game/act_farm.go
parent2725e2927a1595c7b100d942d1f14146252adeb7 (diff)
downloadthehouseoficarus-abd612c15799f604e671e83dc7c410ed2b44185f.tar.gz
slop refactor
Diffstat (limited to 'internal/game/act_farm.go')
-rw-r--r--internal/game/act_farm.go644
1 files changed, 644 insertions, 0 deletions
diff --git a/internal/game/act_farm.go b/internal/game/act_farm.go
new file mode 100644
index 0000000..62e08fd
--- /dev/null
+++ b/internal/game/act_farm.go
@@ -0,0 +1,644 @@
+package game
+
+import (
+ "fmt"
+ "math/rand"
+ "sort"
+ "strings"
+
+ "thehouseoficarus/internal/behavior"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+ "thehouseoficarus/internal/world"
+)
+
+const FarmTickInterval = 500
+
+var farmPatchDefIDs = map[string]bool{
+ "herb_patch": true,
+ "allotment_patch": true,
+ "flower_patch": true,
+ "bush_patch": true,
+ "tree_patch": true,
+}
+
+func farmFlagPrefix(defID string, index int) string {
+ switch defID {
+ case "herb_patch":
+ return fmt.Sprintf("farm_herb_%d", index+1)
+ case "allotment_patch":
+ return fmt.Sprintf("farm_allot_%d", index+1)
+ case "flower_patch":
+ return fmt.Sprintf("farm_flower_%d", index+1)
+ case "bush_patch":
+ return fmt.Sprintf("farm_bush_%d", index+1)
+ case "tree_patch":
+ return fmt.Sprintf("farm_tree_%d", index+1)
+ }
+ return ""
+}
+
+func (g *Game) ensureFarmState(p *player.Player, prefix string) {
+ p.EnsureFlags()
+ if _, exists := p.Flags[prefix+"_seed"]; !exists {
+ p.Flags[prefix+"_weeds"] = true
+ p.Flags[prefix+"_seed"] = ""
+ p.Flags[prefix+"_stage"] = 0
+ p.Flags[prefix+"_watered"] = false
+ p.Flags[prefix+"_diseased"] = false
+ p.Flags[prefix+"_dead"] = false
+ p.Flags[prefix+"_ready"] = false
+ }
+}
+
+func (g *Game) findFarmPrefixes(p *player.Player) []string {
+ seen := make(map[string]bool)
+ var prefixes []string
+ for key := range p.Flags {
+ if !strings.HasPrefix(key, "farm_") {
+ continue
+ }
+ if !strings.HasSuffix(key, "_seed") {
+ continue
+ }
+ prefix := strings.TrimSuffix(key, "_seed")
+ if !seen[prefix] {
+ seen[prefix] = true
+ if seedID, ok := p.Flags[key].(string); ok && seedID != "" {
+ prefixes = append(prefixes, prefix)
+ }
+ }
+ }
+ sort.Strings(prefixes)
+ return prefixes
+}
+
+func (g *Game) FarmTick() {
+ g.farmTickCounter++
+ if g.farmTickCounter < FarmTickInterval {
+ return
+ }
+ g.farmTickCounter = 0
+
+ if g.Hub == nil {
+ return
+ }
+
+ for _, sess := range g.Hub.AllSessions() {
+ p := sess.Player
+ if p == nil || p.Flags == nil {
+ continue
+ }
+ g.advanceFarmGrowth(sess, p)
+ }
+}
+
+func (g *Game) advanceFarmGrowth(sess *net.Session, p *player.Player) {
+ prefixes := g.findFarmPrefixes(p)
+ for _, prefix := range prefixes {
+ seedID, _ := p.Flags[prefix+"_seed"].(string)
+ if seedID == "" {
+ continue
+ }
+ dead, _ := p.Flags[prefix+"_dead"].(bool)
+ if dead {
+ continue
+ }
+ diseased, _ := p.Flags[prefix+"_diseased"].(bool)
+ ready, _ := p.Flags[prefix+"_ready"].(bool)
+ if ready {
+ continue
+ }
+
+ if diseased {
+ p.Flags[prefix+"_dead"] = true
+ p.Flags[prefix+"_diseased"] = false
+ sess.WriteLine(g.colorize(sess, "farm_disease",
+ fmt.Sprintf("\nYour %s has died from disease!", g.seedDisplayName(sess, seedID))))
+ g.AccountStore.SaveCharacter(p)
+ continue
+ }
+
+ stage := intFromFlag(p.Flags, prefix+"_stage")
+ watered, _ := p.Flags[prefix+"_watered"].(bool)
+
+ seedDef, err := g.ItemStore.Load(seedID)
+ if err != nil {
+ continue
+ }
+
+ maxStages := seedDef.FarmStages
+ if maxStages <= 0 {
+ maxStages = 4
+ }
+
+ stage++
+
+ if stage >= maxStages {
+ p.Flags[prefix+"_stage"] = stage
+ p.Flags[prefix+"_ready"] = true
+ p.Flags[prefix+"_watered"] = false
+ sess.WriteLine(g.colorize(sess, "farm_grow",
+ fmt.Sprintf("\nYour %s is fully grown and ready to harvest!",
+ g.seedDisplayName(sess, seedID))))
+ } else {
+ if !watered && rand.Float64() < 0.10 {
+ p.Flags[prefix+"_stage"] = stage
+ p.Flags[prefix+"_diseased"] = true
+ p.Flags[prefix+"_watered"] = false
+ sess.WriteLine(g.colorize(sess, "farm_disease",
+ fmt.Sprintf("\nYour %s has become diseased!",
+ g.seedDisplayName(sess, seedID))))
+ } else {
+ p.Flags[prefix+"_stage"] = stage
+ p.Flags[prefix+"_watered"] = false
+ sess.WriteLine(g.colorize(sess, "farm_grow",
+ fmt.Sprintf("\nYour %s has grown to stage %d/%d.",
+ g.seedDisplayName(sess, seedID), stage, maxStages)))
+ }
+ }
+ g.AccountStore.SaveCharacter(p)
+ }
+}
+
+func (g *Game) seedDisplayName(sess *net.Session, seedID string) string {
+ if def, err := g.ItemStore.Load(seedID); err == nil {
+ return g.itemColorize(sess, def, def.Name)
+ }
+ return seedID
+}
+
+func (g *Game) advancePlant(sess *net.Session, p *player.Player) {
+ if p.Action == nil || p.Action.Data == nil {
+ return
+ }
+ data, ok := p.Action.Data.(*behavior.FarmData)
+ if !ok {
+ return
+ }
+ prefix := data.Prefix
+ seedID := data.SeedID
+ xp := data.XP
+
+ g.ensureFarmState(p, prefix)
+
+ p.Flags[prefix+"_weeds"] = false
+ p.Flags[prefix+"_seed"] = seedID
+ p.Flags[prefix+"_stage"] = 0
+ p.Flags[prefix+"_watered"] = false
+ p.Flags[prefix+"_diseased"] = false
+ p.Flags[prefix+"_dead"] = false
+ p.Flags[prefix+"_ready"] = false
+
+ if intXP := int(xp); intXP > 0 {
+ g.awardSkillXP(sess, p, player.Farming, intXP)
+ }
+
+ g.AccountStore.SaveCharacter(p)
+
+ seedName := seedID
+ if def, err := g.ItemStore.Load(seedID); err == nil {
+ seedName = g.itemColorize(sess, def, def.Name)
+ }
+ patchName := patchDisplayName(prefix)
+ msg := fmt.Sprintf("You plant a %s in the %s.", seedName, patchName)
+ if int(xp) > 0 && p.OptionBool("xp_drops") {
+ msg += g.formatXpDropSingle(sess, p, player.Farming, int(xp))
+ }
+ sess.WriteLine(msg)
+
+ p.Action = nil
+}
+
+func (g *Game) advanceHarvest(sess *net.Session, p *player.Player) {
+ if p.Action == nil || p.Action.Data == nil {
+ return
+ }
+ data, ok := p.Action.Data.(*behavior.FarmData)
+ if !ok {
+ return
+ }
+ prefix := data.Prefix
+ product := data.Product
+ minYield := data.MinYield
+ maxYield := data.MaxYield
+ xp := data.XP
+
+ ready, _ := p.Flags[prefix+"_ready"].(bool)
+ if !ready {
+ sess.WriteLine("There's nothing ready to harvest here.")
+ p.Action = nil
+ return
+ }
+
+ yield := int(minYield) + rand.Intn(int(maxYield)-int(minYield)+1)
+ bonus := p.Level(player.Farming) / 20
+ yield += bonus
+
+ freeSlots := p.FreeSlots()
+ if yield > freeSlots {
+ productDef, _ := g.ItemStore.Load(product)
+ if productDef == nil || !productDef.Stackable {
+ yield = freeSlots
+ }
+ }
+ if yield <= 0 {
+ sess.WriteLine("Your inventory is too full!")
+ p.Action = nil
+ return
+ }
+
+ productDef, _ := g.ItemStore.Load(product)
+ if productDef != nil && productDef.Stackable {
+ added := 0
+ for i := 0; i < 28 && added < yield; i++ {
+ slot := p.InvSlot(i)
+ if slot != nil && slot.ItemID == product {
+ slot.Quantity += yield
+ added = yield
+ break
+ }
+ }
+ if added == 0 {
+ freeSlot := p.FirstFreeSlot()
+ if freeSlot >= 0 {
+ p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: product, Quantity: yield})
+ }
+ }
+ } else {
+ for i := 0; i < yield; i++ {
+ freeSlot := p.FirstFreeSlot()
+ if freeSlot < 0 {
+ break
+ }
+ p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: product, Quantity: 1})
+ }
+ }
+
+ if intXP := int(xp); intXP > 0 {
+ g.awardSkillXP(sess, p, player.Farming, intXP)
+ }
+
+ g.ensureFarmState(p, prefix)
+ p.Flags[prefix+"_seed"] = ""
+ p.Flags[prefix+"_stage"] = 0
+ p.Flags[prefix+"_ready"] = false
+ p.Flags[prefix+"_watered"] = false
+ p.Flags[prefix+"_weeds"] = true
+ p.Flags[prefix+"_diseased"] = false
+ p.Flags[prefix+"_dead"] = false
+
+ g.AccountStore.SaveCharacter(p)
+
+ productName := product
+ if productDef != nil {
+ productName = g.itemColorize(sess, productDef, productDef.Name)
+ }
+ patchName := patchDisplayName(prefix)
+ msg := fmt.Sprintf("You harvest %d %s from the %s.", yield, productName, patchName)
+ if int(xp) > 0 && p.OptionBool("xp_drops") {
+ msg += g.formatXpDropSingle(sess, p, player.Farming, int(xp))
+ }
+ sess.WriteLine(msg)
+
+ p.Action = nil
+}
+
+func (g *Game) advanceRake(sess *net.Session, p *player.Player) {
+ if p.Action == nil || p.Action.Data == nil {
+ return
+ }
+ data, ok := p.Action.Data.(*behavior.FarmData)
+ if !ok {
+ return
+ }
+ prefix := data.Prefix
+
+ p.Flags[prefix+"_weeds"] = false
+ p.Flags[prefix+"_dead"] = false
+ p.Flags[prefix+"_seed"] = ""
+ p.Flags[prefix+"_stage"] = 0
+ p.Flags[prefix+"_ready"] = false
+ p.Flags[prefix+"_diseased"] = false
+ p.Flags[prefix+"_watered"] = false
+
+ g.AccountStore.SaveCharacter(p)
+
+ patchName := patchDisplayName(prefix)
+ sess.WriteLine(fmt.Sprintf("You rake the %s clean.", patchName))
+
+ p.Action = nil
+}
+
+func (g *Game) advanceWater(sess *net.Session, p *player.Player) {
+ if p.Action == nil || p.Action.Data == nil {
+ return
+ }
+ data, ok := p.Action.Data.(*behavior.FarmData)
+ if !ok {
+ return
+ }
+ prefix := data.Prefix
+
+ p.Flags[prefix+"_watered"] = true
+
+ g.AccountStore.SaveCharacter(p)
+
+ patchName := patchDisplayName(prefix)
+ sess.WriteLine(fmt.Sprintf("You water the %s.", patchName))
+
+ p.Action = nil
+}
+
+func (g *Game) advanceCure(sess *net.Session, p *player.Player) {
+ if p.Action == nil || p.Action.Data == nil {
+ return
+ }
+ data, ok := p.Action.Data.(*behavior.FarmData)
+ if !ok {
+ return
+ }
+ prefix := data.Prefix
+
+ p.RemoveItem("plant_cure", 1)
+ p.Flags[prefix+"_diseased"] = false
+
+ g.AccountStore.SaveCharacter(p)
+
+ sess.WriteLine("You apply the plant cure. The patch looks healthy again.")
+
+ p.Action = nil
+}
+
+func patchDisplayName(prefix string) string {
+ switch {
+ case strings.Contains(prefix, "herb"):
+ return "herb patch"
+ case strings.Contains(prefix, "allot"):
+ return "allotment patch"
+ case strings.Contains(prefix, "flower"):
+ return "flower patch"
+ case strings.Contains(prefix, "bush"):
+ return "bush patch"
+ case strings.Contains(prefix, "tree"):
+ return "tree patch"
+ }
+ return "patch"
+}
+
+func (g *Game) farmPatchLookSuffix(p *player.Player, defID string, index int) string {
+ prefix := farmFlagPrefix(defID, index)
+ if prefix == "" {
+ return ""
+ }
+ g.ensureFarmState(p, prefix)
+
+ weeds, _ := p.Flags[prefix+"_weeds"].(bool)
+ if weeds {
+ return "\nIt is overgrown with weeds."
+ }
+
+ seedID, _ := p.Flags[prefix+"_seed"].(string)
+ if seedID == "" {
+ return "\nThe patch is empty and ready for planting."
+ }
+
+ dead, _ := p.Flags[prefix+"_dead"].(bool)
+ if dead {
+ return "\nThe plant has died. You need to rake it clean."
+ }
+
+ diseased, _ := p.Flags[prefix+"_diseased"].(bool)
+ if diseased {
+ return "\nThe plant looks diseased! Use plant cure to save it."
+ }
+
+ ready, _ := p.Flags[prefix+"_ready"].(bool)
+ if ready {
+ name := seedID
+ if def, err := g.ItemStore.Load(seedID); err == nil {
+ name = def.Name
+ }
+ return fmt.Sprintf("\nA fully grown %s is ready to harvest!", name)
+ }
+
+ stage := intFromFlag(p.Flags, prefix+"_stage")
+ maxStages := 4
+ if def, err := g.ItemStore.Load(seedID); err == nil && def.FarmStages > 0 {
+ maxStages = def.FarmStages
+ }
+ name := seedID
+ if def, err := g.ItemStore.Load(seedID); err == nil {
+ name = def.Name
+ }
+ watered, _ := p.Flags[prefix+"_watered"].(bool)
+ suffix := fmt.Sprintf("\nA %s is growing (stage %d/%d).", name, stage, maxStages)
+ if watered {
+ suffix += " It has been watered."
+ }
+ return suffix
+}
+
+func (g *Game) toolShedLookSuffix(p *player.Player) string {
+ var stored []string
+ if v, _ := p.Flags["tool_shed_rake"].(bool); v {
+ stored = append(stored, "a rake")
+ }
+ if v, _ := p.Flags["tool_shed_spade"].(bool); v {
+ stored = append(stored, "a spade")
+ }
+ if v, _ := p.Flags["tool_shed_watering_can"].(bool); v {
+ stored = append(stored, "a watering can")
+ }
+ if len(stored) == 0 {
+ return "\nThe shed is empty."
+ }
+ return fmt.Sprintf("\nInside: %s.", strings.Join(stored, ", "))
+}
+
+func (g *Game) farmMatchPrefix(p *player.Player, input string) (prefix string, defID string, index int) {
+ objInstances := g.World.AllObjInstances(p.RoomID)
+ if len(objInstances) == 0 {
+ return "", "", -1
+ }
+
+ type indexedFarm struct {
+ prefix string
+ defID string
+ index int
+ name string
+ }
+ var farms []indexedFarm
+
+ for _, obj := range objInstances {
+ if !farmPatchDefIDs[obj.DefID] {
+ continue
+ }
+ pref := farmFlagPrefix(obj.DefID, obj.Index)
+ if pref == "" {
+ continue
+ }
+ var name string
+ if def, err := g.ObjectStore.Load(obj.DefID); err == nil {
+ name = def.Name
+ } else {
+ name = obj.DefID
+ }
+ farms = append(farms, indexedFarm{prefix: pref, defID: obj.DefID, index: obj.Index, name: name})
+ }
+
+ if len(farms) == 0 {
+ return "", "", -1
+ }
+
+ if input == "" {
+ return farms[0].prefix, farms[0].defID, farms[0].index
+ }
+
+ lower := strings.ToLower(input)
+ for _, f := range farms {
+ if strings.Contains(lower, strings.ToLower(f.name)) || behavior.WordPrefixMatch(lower, f.name) {
+ return f.prefix, f.defID, f.index
+ }
+ }
+
+ return "", "", -1
+}
+
+func (g *Game) farmObjSuffix(p *player.Player, defID string, index int) string {
+ if defID == "tool_shed" {
+ return g.toolShedLookSuffix(p)
+ }
+ if farmPatchDefIDs[defID] {
+ return g.farmPatchLookSuffix(p, defID, index)
+ }
+ return ""
+}
+
+func (g *Game) showFarmPatches(sess *net.Session, p *player.Player) []string {
+ objInstances := g.World.AllObjInstances(p.RoomID)
+ hasFarmPatches := false
+ for _, obj := range objInstances {
+ if farmPatchDefIDs[obj.DefID] {
+ hasFarmPatches = true
+ break
+ }
+ }
+ if !hasFarmPatches {
+ return nil
+ }
+
+ type farmDisplay struct {
+ name string
+ line string
+ }
+ var displays []farmDisplay
+
+ for _, obj := range objInstances {
+ if !farmPatchDefIDs[obj.DefID] {
+ continue
+ }
+ def, _ := g.ObjectStore.Load(obj.DefID)
+ patchName := obj.DefID
+ if def != nil {
+ patchName = def.Name
+ }
+
+ prefix := farmFlagPrefix(obj.DefID, obj.Index)
+ if prefix == "" {
+ continue
+ }
+ g.ensureFarmState(p, prefix)
+
+ idxStr := ""
+ if obj.Index > 0 || g.countPatchesByType(objInstances, obj.DefID) > 1 {
+ idxStr = fmt.Sprintf(" %d", obj.Index+1)
+ }
+
+ weeds, _ := p.Flags[prefix+"_weeds"].(bool)
+ if weeds {
+ displays = append(displays, farmDisplay{
+ name: fmt.Sprintf("%s%s", patchName, idxStr),
+ line: fmt.Sprintf("overgrown with weeds"),
+ })
+ continue
+ }
+
+ seedID, _ := p.Flags[prefix+"_seed"].(string)
+ if seedID == "" {
+ displays = append(displays, farmDisplay{
+ name: fmt.Sprintf("%s%s", patchName, idxStr),
+ line: fmt.Sprintf("empty and ready for planting"),
+ })
+ continue
+ }
+
+ seedName := seedID
+ if sd, err := g.ItemStore.Load(seedID); err == nil {
+ seedName = g.itemColorize(sess, sd, sd.Name)
+ }
+
+ dead, _ := p.Flags[prefix+"_dead"].(bool)
+ if dead {
+ displays = append(displays, farmDisplay{
+ name: fmt.Sprintf("%s%s", patchName, idxStr),
+ line: fmt.Sprintf("contains a dead plant"),
+ })
+ continue
+ }
+
+ diseased, _ := p.Flags[prefix+"_diseased"].(bool)
+ if diseased {
+ displays = append(displays, farmDisplay{
+ name: fmt.Sprintf("%s%s", patchName, idxStr),
+ line: fmt.Sprintf("has a sickly-looking %s plant", seedName),
+ })
+ continue
+ }
+
+ ready, _ := p.Flags[prefix+"_ready"].(bool)
+ if ready {
+ displays = append(displays, farmDisplay{
+ name: fmt.Sprintf("%s%s", patchName, idxStr),
+ line: fmt.Sprintf("has a fully grown %s {ready to harvest}", seedName),
+ })
+ continue
+ }
+
+ stage := intFromFlag(p.Flags, prefix+"_stage")
+ maxStages := 4
+ if sd, err := g.ItemStore.Load(seedID); err == nil && sd.FarmStages > 0 {
+ maxStages = sd.FarmStages
+ }
+ watered, _ := p.Flags[prefix+"_watered"].(bool)
+ waterStr := ""
+ if watered {
+ waterStr = ", watered"
+ }
+ displays = append(displays, farmDisplay{
+ name: fmt.Sprintf("%s%s", patchName, idxStr),
+ line: fmt.Sprintf("has a growing %s (stage %d/%d%s)", seedName, stage, maxStages, waterStr),
+ })
+ }
+
+ if len(displays) == 0 {
+ return nil
+ }
+
+ var lines []string
+ lines = append(lines, "")
+ for _, d := range displays {
+ lines = append(lines, fmt.Sprintf("A %s: %s.", d.name, d.line))
+ }
+ return lines
+}
+
+func (g *Game) countPatchesByType(instances []world.ObjState, defID string) int {
+ count := 0
+ for _, obj := range instances {
+ if obj.DefID == defID && farmPatchDefIDs[obj.DefID] {
+ count++
+ }
+ }
+ return count
+}