aboutsummaryrefslogtreecommitdiff
path: root/internal/game/act_burn.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_burn.go
parent2725e2927a1595c7b100d942d1f14146252adeb7 (diff)
downloadthehouseoficarus-abd612c15799f604e671e83dc7c410ed2b44185f.tar.gz
slop refactor
Diffstat (limited to 'internal/game/act_burn.go')
-rw-r--r--internal/game/act_burn.go532
1 files changed, 532 insertions, 0 deletions
diff --git a/internal/game/act_burn.go b/internal/game/act_burn.go
new file mode 100644
index 0000000..861ff93
--- /dev/null
+++ b/internal/game/act_burn.go
@@ -0,0 +1,532 @@
+package game
+
+import (
+ "fmt"
+ "math/rand"
+ "strings"
+
+ "thehouseoficarus/internal/behavior"
+ "thehouseoficarus/internal/engine"
+ "thehouseoficarus/internal/item"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+)
+
+func (g *Game) executeBurn(sess *net.Session, args []string, rawInput string) {
+ p := sess.Player
+ g.cancelAction(p)
+ g.doBurn(sess, p, strings.Join(args, " "))
+}
+
+func (g *Game) executeStoke(sess *net.Session, args []string, rawInput string) {
+ p := sess.Player
+ g.cancelAction(p)
+ g.doStoke(sess, p, strings.Join(args, " "))
+}
+
+func (g *Game) doBurn(sess *net.Session, p *player.Player, input string) {
+ if p.Action != nil {
+ g.cancelAction(p)
+ }
+ g.cancelBgAction(p)
+
+ itemID, fromGround, ambiguous := g.resolveBurnTarget(p, input)
+ if ambiguous {
+ sess.WriteLine("Burn what?")
+ return
+ }
+ if itemID == "" {
+ sess.WriteLine("Burn what?")
+ return
+ }
+
+ g.startBurn(sess, p, itemID, fromGround)
+}
+
+func (g *Game) doStoke(sess *net.Session, p *player.Player, input string) {
+ if p.Action != nil {
+ g.cancelAction(p)
+ }
+ g.cancelBgAction(p)
+
+ if !g.hasFireObject(p.RoomID) {
+ sess.WriteLine("There's no fire here to stoke.")
+ return
+ }
+
+ itemID, ambiguous := g.resolveStokeTarget(p, input)
+ if ambiguous {
+ sess.WriteLine("Stoke what?")
+ return
+ }
+ if itemID == "" {
+ sess.WriteLine("You have no logs to stoke with!")
+ return
+ }
+
+ g.startStoke(sess, p, itemID)
+}
+
+func (g *Game) startBurn(sess *net.Session, p *player.Player, itemID string, fromGround bool) {
+ if p.FirstFreeSlot() == -1 && !fromGround {
+ sess.WriteLine("Your inventory is too full!")
+ return
+ }
+
+ if g.hasFireObject(p.RoomID) {
+ sess.WriteLine("There is already a fire burning here.")
+ return
+ }
+
+ logDef, err := g.ItemStore.Load(itemID)
+ if err != nil {
+ sess.WriteLine("Something is wrong with that item.")
+ return
+ }
+
+ if logDef.BurnTicks <= 0 {
+ sess.WriteLine(fmt.Sprintf("You can't burn the %s.", g.itemColorize(sess, logDef, logDef.Name)))
+ return
+ }
+
+ skillLevel := p.Level(player.Firemaking)
+ if logDef.FireLevel > 0 && skillLevel < logDef.FireLevel {
+ sess.WriteLine(fmt.Sprintf("You need level %d firemaking to burn %s.", logDef.FireLevel, g.itemColorize(sess, logDef, logDef.Name)))
+ return
+ }
+
+ toolSpeed, ok := g.findFireTool(sess, p)
+ if !ok {
+ return
+ }
+
+ if !fromGround {
+ if !p.HasItem(itemID) {
+ sess.WriteLine(fmt.Sprintf("You don't have any %s.", g.itemColorize(sess, logDef, logDef.Name)))
+ return
+ }
+ } else {
+ ground := g.World.GroundItems(p.RoomID)
+ if ground[itemID] <= 0 {
+ sess.WriteLine(fmt.Sprintf("There are no %s on the ground here.", g.itemColorize(sess, logDef, logDef.Name)))
+ return
+ }
+ }
+
+ phase := 1
+ if !fromGround {
+ phase = 0
+ }
+
+ g.broadcastAction(sess, "\n%s starts building a fire.", p.Name)
+
+ p.Action = &behavior.Action{
+ Type: behavior.TypeBurn,
+ TargetID: itemID,
+ TargetName: logDef.Name,
+ WaitLeft: 0,
+ Data: &behavior.BurnData{
+ ItemID: itemID,
+ ToolSpeed: toolSpeed,
+ Level: logDef.FireLevel,
+ XP: logDef.FireXP,
+ BurnTicks: logDef.BurnTicks,
+ Phase: phase,
+ },
+ }
+}
+
+func (g *Game) startStoke(sess *net.Session, p *player.Player, itemID string) {
+ fireKey := g.findFireObjectKey(p.RoomID)
+ if fireKey == "" {
+ sess.WriteLine("There's no fire here to stoke.")
+ return
+ }
+
+ logDef, err := g.ItemStore.Load(itemID)
+ if err != nil || logDef.BurnTicks <= 0 {
+ sess.WriteLine(fmt.Sprintf("You can't stoke the fire with that."))
+ return
+ }
+
+ if !p.HasItem(itemID) {
+ sess.WriteLine(fmt.Sprintf("You don't have any %s.", g.itemColorize(sess, logDef, logDef.Name)))
+ return
+ }
+
+ sess.WriteLine("You begin to tend to the fire.")
+
+ g.broadcastAction(sess, "\n%s tends to the fire.", p.Name)
+
+ p.Action = &behavior.Action{
+ Type: behavior.TypeStoke,
+ TargetID: itemID,
+ TargetName: logDef.Name,
+ WaitLeft: engine.ToTicks(10),
+ Data: &behavior.BurnData{
+ ItemID: itemID,
+ BurnTicks: logDef.BurnTicks,
+ XP: logDef.FireXP,
+ },
+ }
+}
+
+func (g *Game) advanceBurn(sess *net.Session, p *player.Player) {
+ d := p.Action.Data.(*behavior.BurnData)
+ itemID := d.ItemID
+ toolSpeed := d.ToolSpeed
+ phase := d.Phase
+
+ logDef, err := g.ItemStore.Load(itemID)
+ if err != nil {
+ g.cancelAction(p)
+ return
+ }
+
+ if phase == 0 {
+ if !p.HasItem(itemID) {
+ sess.WriteLine(fmt.Sprintf("You're out of %s.", g.itemColorize(sess, logDef, logDef.Name)))
+ g.cancelAction(p)
+ return
+ }
+ p.RemoveItem(itemID, 1)
+ g.World.AddGroundItem(p.RoomID, itemID, 1)
+ g.AccountStore.SaveCharacter(p)
+
+ sess.WriteLine(g.colorize(sess, "fire", fmt.Sprintf("You drop the %s and begin to make a fire...", g.itemColorize(sess, logDef, logDef.Name))))
+ g.broadcastDrop(p, itemID, logDef.Name)
+
+ d.Phase = 1
+ p.Action.WaitLeft = engine.ToTicks(toolSpeed)
+ return
+ }
+
+ ground := g.World.GroundItems(p.RoomID)
+ if ground[itemID] <= 0 {
+ sess.WriteLine("The thing you were trying to burn is gone.")
+ g.cancelAction(p)
+ return
+ }
+
+ if phase == 1 {
+ sess.WriteLine(g.colorize(sess, "fire", fmt.Sprintf("You try burning the %s on the ground...", g.itemColorize(sess, logDef, logDef.Name))))
+ d.Phase = 2
+ p.Action.WaitLeft = engine.ToTicks(toolSpeed)
+ return
+ }
+
+ if phase == 2 {
+ skillLevel := p.Level(player.Firemaking)
+ chance := 0.5 + float64(skillLevel-logDef.FireLevel)*0.02
+ if chance > 0.95 {
+ chance = 0.95
+ }
+ if chance < 0.05 {
+ chance = 0.05
+ }
+
+ if rand.Float64() < chance {
+ g.World.RemoveGroundItem(p.RoomID, itemID, 1)
+ g.World.AddObjInstance(p.RoomID, "fire", logDef.BurnTicks)
+
+ xp := logDef.FireXP
+ if xp > 0 {
+ g.awardSkillXP(sess, p, player.Firemaking, xp)
+ }
+
+ g.AccountStore.SaveCharacter(p)
+ g.cancelAction(p)
+
+ msg := g.colorize(sess, "fire", "You manage to get a fire going!")
+ if xp > 0 && p.OptionBool("xp_drops") {
+ msg += g.formatXpDropSingle(sess, p, player.Firemaking, xp)
+ }
+ sess.WriteLine(msg)
+
+ g.broadcastAction(sess, "\n%s started a fire!", p.Name)
+ return
+ }
+
+ sess.WriteLine("You can't seem to get a fire going.")
+ d.Phase = 1
+ p.Action.WaitLeft = engine.ToTicks(toolSpeed)
+ }
+}
+
+func (g *Game) advanceStoke(sess *net.Session, p *player.Player) {
+ d := p.Action.Data.(*behavior.BurnData)
+ itemID := d.ItemID
+ fireKey := g.findFireObjectKey(p.RoomID)
+ burnTicks := d.BurnTicks
+ xp := d.XP
+
+ st := g.World.GetObjStateByKey(fireKey)
+ if st == nil || st.Quality <= 0 {
+ sess.WriteLine("The fire went out!")
+ g.cancelAction(p)
+ return
+ }
+
+ logDef, _ := g.ItemStore.Load(itemID)
+ name := itemID
+ if logDef != nil {
+ name = logDef.Name
+ }
+
+ if !p.HasItem(itemID) {
+ sess.WriteLine(g.colorize(sess, "fire", fmt.Sprintf("You're out of %s and the fire is roaring.", g.itemColorize(sess, logDef, name))))
+ g.cancelAction(p)
+ return
+ }
+
+ p.RemoveItem(itemID, 1)
+ st.Quality += burnTicks / 2
+
+ if xp > 0 {
+ g.awardSkillXP(sess, p, player.Firemaking, xp)
+ }
+
+ g.AccountStore.SaveCharacter(p)
+
+ msg := g.colorize(sess, "fire", fmt.Sprintf("You throw %s onto the fire.", g.itemColorize(sess, logDef, name)))
+ if xp > 0 && p.OptionBool("xp_drops") {
+ msg += g.formatXpDropSingle(sess, p, player.Firemaking, xp)
+ }
+ sess.WriteLine(msg)
+
+ if !p.HasItem(itemID) {
+ sess.WriteLine(g.colorize(sess, "fire", fmt.Sprintf("You're out of %s and the fire is roaring.", g.itemColorize(sess, logDef, name))))
+ g.cancelAction(p)
+ return
+ }
+
+ p.Action.WaitLeft = engine.ToTicks(10.0)
+}
+
+func (g *Game) findFireTool(sess *net.Session, p *player.Player) (toolSpeed float64, ok bool) {
+ toolSpeed = -1
+ var toolItemID string
+
+ if itemID, equipped := p.Equipment[item.SlotMainHand]; equipped {
+ def, err := g.ItemStore.Load(itemID)
+ if err == nil && def.ToolType == "fire" {
+ toolSpeed = def.ToolSpeed
+ toolItemID = itemID
+ }
+ }
+
+ if toolSpeed < 0 {
+ for _, slot := range p.Inventory {
+ if slot == nil || slot.Quantity <= 0 {
+ continue
+ }
+ def, err := g.ItemStore.Load(slot.ItemID)
+ if err != nil || def.ToolType != "fire" {
+ continue
+ }
+ toolSpeed = def.ToolSpeed
+ toolItemID = slot.ItemID
+ break
+ }
+ }
+
+ if toolSpeed < 0 {
+ sess.WriteLine("You need a fire starter to do that.")
+ return 0, false
+ }
+
+ if !g.consumeFireTool(sess, p, toolItemID) {
+ return 0, false
+ }
+
+ return toolSpeed, true
+}
+
+func (g *Game) consumeFireTool(sess *net.Session, p *player.Player, toolItemID string) bool {
+ def, _ := g.ItemStore.Load(toolItemID)
+
+ if def != nil && def.MaxQuality > 0 {
+ for _, slot := range p.Inventory {
+ if slot != nil && slot.ItemID == toolItemID && slot.MaxQuality > 0 {
+ if slot.Quality <= 0 {
+ sess.WriteLine("The lighter is out of butane!")
+ return false
+ }
+ slot.Quality--
+ g.AccountStore.SaveCharacter(p)
+ return true
+ }
+ }
+ if q, ok := p.EquipQuality[toolItemID]; ok {
+ if q <= 0 {
+ sess.WriteLine("The lighter is out of butane!")
+ return false
+ }
+ p.EquipQuality[toolItemID] = q - 1
+ g.AccountStore.SaveCharacter(p)
+ return true
+ }
+ }
+
+ if def != nil && def.Stackable {
+ if p.HasItem(toolItemID) {
+ p.RemoveItem(toolItemID, 1)
+ g.AccountStore.SaveCharacter(p)
+ return true
+ }
+ for slot, itemID := range p.Equipment {
+ if itemID == toolItemID {
+ delete(p.Equipment, slot)
+ g.AccountStore.SaveCharacter(p)
+ return true
+ }
+ }
+ sess.WriteLine("You need a fire starter to do that.")
+ return false
+ }
+
+ return true
+}
+
+func (g *Game) findFireObjectKey(roomID int) string {
+ objs := g.World.AllObjInstances(roomID)
+ for _, st := range objs {
+ if st.DefID == "fire" && st.Quality > 0 {
+ return g.World.ObjStateKey(st.RoomID, st.DefID, st.Index)
+ }
+ }
+ return ""
+}
+
+func (g *Game) hasFireObject(roomID int) bool {
+ return g.findFireObjectKey(roomID) != ""
+}
+
+func (g *Game) broadcastDrop(p *player.Player, itemID, itemName string) {
+ itemDef, _ := g.ItemStore.Load(itemID)
+ for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
+ op := other.Player
+ if op != nil && op.Name != p.Name {
+ colored := itemName
+ if itemDef != nil {
+ colored = g.itemColorize(other, itemDef, itemName)
+ }
+ other.WriteLine(fmt.Sprintf("%s drops a %s.", p.Name, colored))
+ }
+ }
+}
+
+func (g *Game) FireTick() {
+ for _, st := range g.World.AllFireStates() {
+ st.Quality--
+ if st.Quality <= 0 {
+ g.World.RemoveObjInstance(st.RoomID, st.DefID)
+ g.cancelStokers(st.RoomID)
+ if def, err := g.ObjectStore.Load(st.DefID); err == nil && def.RemovalItem != "" {
+ g.World.AddGroundItem(st.RoomID, def.RemovalItem, 1)
+ }
+ }
+ }
+}
+
+func (g *Game) cancelStokers(roomID int) {
+ for _, other := range g.Hub.PlayersInRoom(roomID) {
+ op := other.Player
+ if op != nil && op.Action != nil && op.Action.Type == behavior.TypeStoke {
+ other.WriteLine("The fire went out!")
+ g.cancelAction(op)
+ g.writePrompt(other)
+ }
+ }
+}
+
+func (g *Game) resolveBurnTarget(p *player.Player, input string) (itemID string, fromGround bool, ambiguous bool) {
+ if input != "" {
+ return g.namedBurnTarget(p, input)
+ }
+ return g.defaultBurnTarget(p)
+}
+
+func (g *Game) burnableGround(roomID int, name string) []string {
+ var out []string
+ for itemID := range g.World.GroundItems(roomID) {
+ def, err := g.ItemStore.Load(itemID)
+ if err != nil || def.BurnTicks <= 0 {
+ continue
+ }
+ if name == "" || def.MatchesName(name) {
+ out = append(out, itemID)
+ }
+ }
+ return out
+}
+
+func (g *Game) burnableInv(p *player.Player, name string) []string {
+ var out []string
+ seen := make(map[string]bool)
+ for _, slot := range p.Inventory {
+ if slot == nil || slot.Quantity <= 0 {
+ continue
+ }
+ def, err := g.ItemStore.Load(slot.ItemID)
+ if err != nil || def.BurnTicks <= 0 || seen[slot.ItemID] {
+ continue
+ }
+ if name == "" || def.MatchesName(name) {
+ seen[slot.ItemID] = true
+ out = append(out, slot.ItemID)
+ }
+ }
+ return out
+}
+
+func (g *Game) namedBurnTarget(p *player.Player, input string) (itemID string, fromGround bool, ambiguous bool) {
+ groundMatches := g.burnableGround(p.RoomID, input)
+ if len(groundMatches) > 1 {
+ return "", false, true
+ }
+ if len(groundMatches) == 1 {
+ return groundMatches[0], true, false
+ }
+
+ invMatches := g.burnableInv(p, input)
+ if len(invMatches) > 1 {
+ return "", false, true
+ }
+ if len(invMatches) == 1 {
+ return invMatches[0], false, false
+ }
+
+ return "", false, false
+}
+
+func (g *Game) defaultBurnTarget(p *player.Player) (itemID string, fromGround bool, ambiguous bool) {
+ groundBurnable := g.burnableGround(p.RoomID, "")
+ if len(groundBurnable) == 1 {
+ return groundBurnable[0], true, false
+ }
+ if len(groundBurnable) > 1 {
+ return "", false, true
+ }
+
+ invBurnable := g.burnableInv(p, "")
+ if len(invBurnable) == 1 {
+ return invBurnable[0], false, false
+ }
+ if len(invBurnable) > 1 {
+ return "", false, true
+ }
+
+ return "", false, false
+}
+
+func (g *Game) resolveStokeTarget(p *player.Player, input string) (string, bool) {
+ invBurnable := g.burnableInv(p, input)
+ if len(invBurnable) == 1 {
+ return invBurnable[0], false
+ }
+ if len(invBurnable) > 1 {
+ return "", true
+ }
+ return "", false
+}