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_burn.go | |
| parent | 2725e2927a1595c7b100d942d1f14146252adeb7 (diff) | |
| download | thehouseoficarus-abd612c15799f604e671e83dc7c410ed2b44185f.tar.gz | |
slop refactor
Diffstat (limited to 'internal/game/act_burn.go')
| -rw-r--r-- | internal/game/act_burn.go | 532 |
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 +} |
