diff options
Diffstat (limited to 'internal/game/action_burn.go')
| -rw-r--r-- | internal/game/action_burn.go | 515 |
1 files changed, 515 insertions, 0 deletions
diff --git a/internal/game/action_burn.go b/internal/game/action_burn.go new file mode 100644 index 0000000..d57307a --- /dev/null +++ b/internal/game/action_burn.go @@ -0,0 +1,515 @@ +package game + +import ( + "fmt" + "math/rand" + + "thirdcollapse/internal/action" + "thirdcollapse/internal/net" + "thirdcollapse/internal/object" + "thirdcollapse/internal/player" +) + +func (g *Game) doBurn(sess *net.Session, p *player.Player, input string) { + if p.Action != nil { + g.CancelAction(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) + } + + 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.", 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, 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.", 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.", logDef.Name)) + return + } + } + + phase := 1 + if !fromGround { + phase = 0 + } + + p.Action = &action.Action{ + Type: "burn", + TargetID: itemID, + TargetName: logDef.Name, + WaitLeft: 0, + Data: map[string]any{ + "item_id": itemID, + "tool_speed": toolSpeed, + "level": logDef.FireLevel, + "xp": logDef.FireXP, + "burn_ticks": 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.", logDef.Name)) + return + } + + sess.WriteLine("You begin to tend to the fire.") + + p.Action = &action.Action{ + Type: "stoke", + TargetID: itemID, + TargetName: logDef.Name, + WaitLeft: 10, + Data: map[string]any{ + "item_id": itemID, + "fire_key": fireKey, + "burn_ticks": logDef.BurnTicks, + "xp": logDef.FireXP, + }, + } +} + +func (g *Game) advanceBurn(sess *net.Session, p *player.Player) { + data := p.Action.Data + itemID := data["item_id"].(string) + toolSpeed := data["tool_speed"].(int) + phase := data["phase"].(int) + + 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.", logDef.Name)) + g.CancelAction(p) + return + } + p.RemoveItem(itemID, 1) + g.World.AddGroundItem(p.RoomID, itemID, 1) + g.AccountStore.SaveCharacter(p) + + sess.WriteLine(fmt.Sprintf("You drop the %s and begin to make a fire...", logDef.Name)) + g.broadcastDrop(p, logDef.Name) + + data["phase"] = 1 + p.Action.WaitLeft = 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(fmt.Sprintf("You try burning the %s on the ground...", logDef.Name)) + data["phase"] = 2 + p.Action.WaitLeft = 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 { + if newLevel := p.AddSkillXP(player.Firemaking, xp); newLevel > 0 { + sess.WriteLine(fmt.Sprintf("*** You are now level %d firemaking! ***", newLevel)) + } + } + + g.AccountStore.SaveCharacter(p) + g.CancelAction(p) + + msg := fmt.Sprintf("You manage to get a fire going!") + if xp > 0 && p.OptionBool("xpdrops") { + msg += fmt.Sprintf(" (+%dxp %s)", xp, player.SkillAbbr[player.Firemaking]) + } + sess.WriteLine(msg) + + for _, other := range g.Hub.PlayersInRoom(p.RoomID) { + if other != sess { + other.WriteLine(fmt.Sprintf("\n%s started a fire!", p.Name)) + } + } + return + } + + sess.WriteLine("You can't seem to get a fire going.") + data["phase"] = 1 + p.Action.WaitLeft = toolSpeed + } +} + +func (g *Game) advanceStoke(sess *net.Session, p *player.Player) { + data := p.Action.Data + itemID := data["item_id"].(string) + fireKey := data["fire_key"].(string) + burnTicks := data["burn_ticks"].(int) + xp := data["xp"].(int) + + 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(fmt.Sprintf("You're out of %s and the fire is roaring.", name)) + g.CancelAction(p) + return + } + + p.RemoveItem(itemID, 1) + st.Quality += burnTicks / 2 + + if xp > 0 { + if newLevel := p.AddSkillXP(player.Firemaking, xp); newLevel > 0 { + sess.WriteLine(fmt.Sprintf("*** You are now level %d firemaking! ***", newLevel)) + } + } + + g.AccountStore.SaveCharacter(p) + + msg := fmt.Sprintf("You throw %s onto the fire.", name) + if xp > 0 && p.OptionBool("xpdrops") { + msg += fmt.Sprintf(" (+%dxp %s)", xp, player.SkillAbbr[player.Firemaking]) + } + sess.WriteLine(msg) + + if !p.HasItem(itemID) { + sess.WriteLine(fmt.Sprintf("You're out of %s and the fire is roaring.", name)) + g.CancelAction(p) + return + } + + p.Action.WaitLeft = 10 +} + +func (g *Game) findFireTool(sess *net.Session, p *player.Player) (toolSpeed int, ok bool) { + toolSpeed = -1 + var toolItemID string + + if itemID, equipped := p.Equipment[object.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, itemName string) { + for _, other := range g.Hub.PlayersInRoom(p.RoomID) { + op, ok := other.Player.(*player.Player) + if ok && op.Name != p.Name { + other.WriteLine(fmt.Sprintf("\n%s drops a %s.", p.Name, itemName)) + } + } +} + +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, ok := other.Player.(*player.Player) + if ok && op.Action != nil && op.Action.Type == "stoke" { + other.WriteLine("The fire went out!") + g.CancelAction(op) + } + } +} + +func (g *Game) resolveBurnTarget(p *player.Player, input string) (itemID string, fromGround bool, ambiguous bool) { + if input != "" { + return g.resolveNamedBurnTarget(p, input) + } + return g.resolveDefaultBurnTarget(p) +} + +func (g *Game) collectBurnableGround(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) collectBurnableInv(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) resolveNamedBurnTarget(p *player.Player, input string) (itemID string, fromGround bool, ambiguous bool) { + groundMatches := g.collectBurnableGround(p.RoomID, input) + if len(groundMatches) > 1 { + return "", false, true + } + if len(groundMatches) == 1 { + return groundMatches[0], true, false + } + + invMatches := g.collectBurnableInv(p, input) + if len(invMatches) > 1 { + return "", false, true + } + if len(invMatches) == 1 { + return invMatches[0], false, false + } + + return "", false, false +} + +func (g *Game) resolveDefaultBurnTarget(p *player.Player) (itemID string, fromGround bool, ambiguous bool) { + groundBurnable := g.collectBurnableGround(p.RoomID, "") + if len(groundBurnable) == 1 { + return groundBurnable[0], true, false + } + if len(groundBurnable) > 1 { + return "", false, true + } + + invBurnable := g.collectBurnableInv(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.collectBurnableInv(p, input) + if len(invBurnable) == 1 { + return invBurnable[0], false + } + if len(invBurnable) > 1 { + return "", true + } + return "", false +} |
