diff options
| author | historia <[not public]> | 2026-06-25 15:40:48 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-06-25 15:40:48 -0400 |
| commit | abd612c15799f604e671e83dc7c410ed2b44185f (patch) | |
| tree | 0597ca92350de73aac2a7cf26b2f2d6595dac0cc /internal/game/act_farm.go | |
| parent | 2725e2927a1595c7b100d942d1f14146252adeb7 (diff) | |
| download | thehouseoficarus-abd612c15799f604e671e83dc7c410ed2b44185f.tar.gz | |
slop refactor
Diffstat (limited to 'internal/game/act_farm.go')
| -rw-r--r-- | internal/game/act_farm.go | 644 |
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 +} |
