diff options
Diffstat (limited to 'internal/game')
| -rw-r--r-- | internal/game/action.go | 2 | ||||
| -rw-r--r-- | internal/game/action_cook.go | 153 | ||||
| -rw-r--r-- | internal/game/action_state.go | 6 | ||||
| -rw-r--r-- | internal/game/cmd_attack.go | 5 | ||||
| -rw-r--r-- | internal/game/cmd_cook.go | 246 | ||||
| -rw-r--r-- | internal/game/cmd_eat.go | 132 | ||||
| -rw-r--r-- | internal/game/cmd_use.go | 200 | ||||
| -rw-r--r-- | internal/game/game.go | 26 | ||||
| -rw-r--r-- | internal/game/help.go | 2 | ||||
| -rw-r--r-- | internal/game/tick.go | 15 |
10 files changed, 783 insertions, 4 deletions
diff --git a/internal/game/action.go b/internal/game/action.go index 26484e0..3857030 100644 --- a/internal/game/action.go +++ b/internal/game/action.go @@ -224,6 +224,8 @@ func (g *Game) AdvanceActions() { g.advanceStoke(sess, p) case "search": g.advanceSearch(sess, p) + case "cook": + g.advanceCook(sess, p) } if p.Action == nil { g.writePrompt(sess) diff --git a/internal/game/action_cook.go b/internal/game/action_cook.go new file mode 100644 index 0000000..e4d2597 --- /dev/null +++ b/internal/game/action_cook.go @@ -0,0 +1,153 @@ +package game + +import ( + "fmt" + "math/rand" + + "thirdcollapse/internal/action" + "thirdcollapse/internal/engine" + "thirdcollapse/internal/net" + "thirdcollapse/internal/player" +) + +func (g *Game) startCook(sess *net.Session, p *player.Player, recipe *action.RecipeDef, stationName string) { + g.CancelAction(p) + + if recipe.Skill != "" { + skillLevel := p.Level(player.SkillName(recipe.Skill)) + if skillLevel < recipe.Level { + sess.WriteLine(fmt.Sprintf("You need level %d %s to cook that.", recipe.Level, recipe.Skill)) + return + } + } + + if !recipe.HasAllItems(p.HasItem) { + sess.WriteLine("You don't have the required ingredients.") + return + } + + wait := recipe.Wait + if wait <= 0 { + wait = 6 + } + + p.Action = &action.Action{ + Type: "cook", + TargetID: recipe.ID, + TargetName: recipe.DisplayName(), + Data: map[string]any{ + "recipe_id": recipe.ID, + "station_name": stationName, + "phase": 0, + "wait": wait, + }, + WaitLeft: engine.ToTicks(1), + } + + p.ActionState = &ActionState{Type: ActionCooking, TargetName: recipe.DisplayName()} +} + +func (g *Game) advanceCook(sess *net.Session, p *player.Player) bool { + recipeID := p.Action.Data["recipe_id"].(string) + stationName := p.Action.Data["station_name"].(string) + phase := p.Action.Data["phase"].(int) + wait := p.Action.Data["wait"].(float64) + + all, err := g.RecipeStore.LoadAll() + if err != nil { + g.CancelAction(p) + return false + } + var recipe *action.RecipeDef + for _, r := range all { + if r.ID == recipeID { + recipe = &r + break + } + } + if recipe == nil { + g.CancelAction(p) + return false + } + + if phase == 0 { + firstItem := recipe.FirstItemName(func(id string) (string, bool) { + def, err := g.ItemStore.Load(id) + if err != nil { + return id, false + } + return def.Name, true + }) + p.ActionState = &ActionState{Type: ActionCooking, TargetName: recipe.DisplayName()} + if stationName != "" && stationName != "inventory" && stationName != "ground" { + sess.WriteLine(fmt.Sprintf("\nYou start cooking %s on the %s.", firstItem, stationName)) + } else { + sess.WriteLine(fmt.Sprintf("\nYou start making %s.", recipe.DisplayName())) + } + p.Action.Data["phase"] = 1 + p.Action.WaitLeft = engine.ToTicks(wait) + return true + } + + skillLevel := p.Level(player.SkillName(recipe.Skill)) + chance := 1.0 + if recipe.Success != nil { + chance = action.SuccessChance(*recipe.Success, skillLevel, recipe.Level) + } + + if rand.Float64() < chance { + outputID := recipe.Output + + freeSlot := p.FirstFreeSlot() + if freeSlot == -1 { + sess.WriteLine("Your inventory is too full!") + g.CancelAction(p) + return false + } + + recipe.ConsumeAll(p.HasItem, p.RemoveItem) + p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: outputID, Quantity: 1}) + + if recipe.XP > 0 { + if newLevel := p.AddSkillXP(player.SkillName(recipe.Skill), recipe.XP); newLevel > 0 { + sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d %s! ***", newLevel, recipe.Skill))) + } + } + g.AccountStore.SaveCharacter(p) + + msg := recipe.Message + if msg == "" { + msg = fmt.Sprintf("Cooked to perfection. It looks great!") + } + sess.WriteLine(msg) + if p.OptionBool("xp_drops") && recipe.XP > 0 { + abbr := player.SkillAbbr[player.SkillName(recipe.Skill)] + sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf(" (+%dxp %s)", recipe.XP, abbr))) + } + } else { + recipe.ConsumeAll(p.HasItem, p.RemoveItem) + + if recipe.Fail != "" { + freeSlot := p.FirstFreeSlot() + if freeSlot >= 0 { + p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: recipe.Fail, Quantity: 1}) + } + } + g.AccountStore.SaveCharacter(p) + + msg := recipe.FailMessage + if msg == "" { + msg = "You badly overcook it." + } + sess.WriteLine(msg) + } + + if recipe.HasAllItems(p.HasItem) && p.FirstFreeSlot() >= 0 { + p.Action.Data["phase"] = 1 + p.Action.WaitLeft = engine.ToTicks(wait) + return true + } + + g.CancelAction(p) + return false +} diff --git a/internal/game/action_state.go b/internal/game/action_state.go index 280dd56..c5d086e 100644 --- a/internal/game/action_state.go +++ b/internal/game/action_state.go @@ -19,6 +19,8 @@ const ( ActionSearching ActionType = "searching" ActionResting ActionType = "resting" ActionWalking ActionType = "walking" + ActionCooking ActionType = "cooking" + ActionEating ActionType = "eating" ) type ActionState struct { @@ -64,6 +66,10 @@ func (a *ActionState) Description() string { return "resting" case ActionWalking: return "walking somewhere with a purpose!" + case ActionCooking: + return "cooking some " + a.TargetName + case ActionEating: + return "eating " + a.TargetName } return "" } diff --git a/internal/game/cmd_attack.go b/internal/game/cmd_attack.go index ba4a2e1..2e15419 100644 --- a/internal/game/cmd_attack.go +++ b/internal/game/cmd_attack.go @@ -171,6 +171,11 @@ func (g *Game) startCombat(sess *net.Session, p *player.Player, mob *world.MobIn } func (g *Game) playerAttack(sess *net.Session, p *player.Player, mob *world.MobInstance) { + if g.processConsumeQueue(p, sess) { + p.ActionState = &ActionState{Type: ActionEating, TargetName: "food"} + return + } + attBonus, strBonus, _ := combat.AttackStyleBonus(string(p.AttackStyle)) equipAtt := 0 diff --git a/internal/game/cmd_cook.go b/internal/game/cmd_cook.go new file mode 100644 index 0000000..be5fa3a --- /dev/null +++ b/internal/game/cmd_cook.go @@ -0,0 +1,246 @@ +package game + +import ( + "fmt" + "strconv" + "strings" + + "thirdcollapse/internal/action" + "thirdcollapse/internal/net" + "thirdcollapse/internal/player" +) + +type cookableEntry struct { + ItemName string + Recipe action.RecipeDef +} + +func (g *Game) doCook(sess *net.Session, input string) { + p := sess.Player.(*player.Player) + g.CancelAction(p) + + stationName := g.findCookingStation(p.RoomID) + if stationName == "" { + sess.WriteLine("You need a fire or cooking range to cook.") + return + } + + allRecipes, err := g.RecipeStore.LoadAll() + if err != nil { + sess.WriteLine("Error loading recipes.") + return + } + + if input == "" { + g.showCookMenu(sess, p, allRecipes, stationName) + return + } + + qty, itemName := parseQty(input) + _ = qty + + matches := g.findInventoryMatches(itemName, p) + if len(matches) == 0 { + sess.WriteLine(fmt.Sprintf("You don't have any '%s'.", itemName)) + return + } + + unique := uniqueItemNames(matches) + if len(unique) > 1 { + sess.WriteLine("Which one?") + for _, name := range unique { + sess.WriteLine(fmt.Sprintf(" - %s", name)) + } + return + } + + itemID := matches[0].ID + + var recipes []action.RecipeDef + for _, r := range allRecipes { + if r.Type != "cooking" { + continue + } + if stationOK(stationName, r.Station) && r.MatchesEntry(itemID) && r.HasAllItems(p.HasItem) { + recipes = append(recipes, r) + } + } + + if len(recipes) == 0 { + sess.WriteLine("You can't cook that here.") + return + } + + if len(recipes) == 1 { + g.startCook(sess, p, &recipes[0], stationName) + return + } + + sess.PendingRecipeItem = itemID + sess.PendingCookMenu = recipesToMenuData(recipes) + sess.State = net.StateCookRecipe + sess.WriteLine(fmt.Sprintf("\nWhat would you like to cook on the %s?", stationName)) + for i, r := range recipes { + sess.WriteLine(fmt.Sprintf(" %d) %s", i+1, g.recipeName(sess, &r))) + } + sess.WriteLine(fmt.Sprintf(" %d) Nothing", len(recipes)+1)) +} + +func (g *Game) showCookMenu(sess *net.Session, p *player.Player, allRecipes []action.RecipeDef, stationName string) { + seen := make(map[string]bool) + var cookables []cookableEntry + + for _, r := range allRecipes { + if r.Type != "cooking" { + continue + } + if !stationOK(stationName, r.Station) { + continue + } + if seen[r.ID] { + continue + } + if !r.HasAllItems(p.HasItem) { + continue + } + seen[r.ID] = true + cookables = append(cookables, cookableEntry{g.recipeName(sess, &r), r}) + } + + if len(cookables) == 0 { + sess.WriteLine("You don't have anything you can cook.") + return + } + + if len(cookables) == 1 { + g.startCook(sess, p, &cookables[0].Recipe, stationName) + return + } + + sess.PendingRecipeItem = "" + sess.State = net.StateCookRecipe + sess.WriteLine(fmt.Sprintf("\nWhat would you like to cook on the %s?", stationName)) + for i, c := range cookables { + sess.WriteLine(fmt.Sprintf(" %d) %s", i+1, c.ItemName)) + } + sess.WriteLine(fmt.Sprintf(" %d) Nothing", len(cookables)+1)) + + sess.PendingCookMenu = makeCookMenuData(cookables) +} + +func (g *Game) recipeName(sess *net.Session, r *action.RecipeDef) string { + if r.Output != "" { + if def, err := g.ItemStore.Load(r.Output); err == nil { + return g.itemColorize(sess, def, def.Name) + } + } + return r.DisplayName() +} + +func makeCookMenuData(cookables []cookableEntry) []map[string]string { + out := make([]map[string]string, len(cookables)) + for i, c := range cookables { + out[i] = map[string]string{"recipe_id": c.Recipe.ID} + } + return out +} + +func recipesToMenuData(recipes []action.RecipeDef) []map[string]string { + out := make([]map[string]string, len(recipes)) + for i, r := range recipes { + out[i] = map[string]string{"recipe_id": r.ID} + } + return out +} + +func stationOK(stationName string, recipeStations []string) bool { + if len(recipeStations) == 0 { + return false + } + for _, rs := range recipeStations { + if (rs == "fire" && stationName == "fire") || (rs == "cooking_range" && stationName == "cooking range") { + return true + } + } + return false +} + +func (g *Game) findCookingStation(roomID int) string { + for _, obj := range g.World.AllObjInstances(roomID) { + if obj.Depleted { + continue + } + if obj.DefID == "fire" { + return "fire" + } + if obj.DefID == "cooking_range" { + return "cooking range" + } + } + return "" +} + +func (g *Game) handleCookRecipe(sess *net.Session, input string) { + p := sess.Player.(*player.Player) + input = strings.TrimSpace(input) + if input == "" { + return + } + + choice, err := strconv.Atoi(input) + if err != nil { + sess.State = net.StateGame + sess.PendingRecipeItem = "" + sess.PendingCookMenu = nil + sess.WriteLine("You decide not to cook anything.") + g.reprompt(sess) + return + } + + if len(sess.PendingCookMenu) > 0 { + menuData := sess.PendingCookMenu + sess.PendingCookMenu = nil + if choice <= 0 || choice > len(menuData)+1 { + sess.WriteLine("Invalid choice.") + return + } + sess.State = net.StateGame + if choice == len(menuData)+1 { + sess.WriteLine("You decide not to cook anything.") + g.reprompt(sess) + return + } + entry := menuData[choice-1] + + if cid, ok := entry["combine_item"]; ok { + def, err := g.ItemStore.Load(cid) + if err == nil { + p := sess.Player.(*player.Player) + g.produceCombined(sess, p, def) + } + g.reprompt(sess) + return + } + + all, err := g.RecipeStore.LoadAll() + if err != nil { + g.reprompt(sess) + return + } + for _, r := range all { + if r.ID == entry["recipe_id"] { + stationName := "inventory" + if len(r.Station) > 0 { + stationName = g.findCookingStation(p.RoomID) + } + g.startCook(sess, p, &r, stationName) + return + } + } + g.reprompt(sess) + return + } + + sess.State = net.StateGame + g.reprompt(sess) +} diff --git a/internal/game/cmd_eat.go b/internal/game/cmd_eat.go new file mode 100644 index 0000000..5d6292b --- /dev/null +++ b/internal/game/cmd_eat.go @@ -0,0 +1,132 @@ +package game + +import ( + "fmt" + "time" + + "thirdcollapse/internal/net" + "thirdcollapse/internal/object" + "thirdcollapse/internal/player" +) + +func (g *Game) doEat(sess *net.Session, input string) { + p := sess.Player.(*player.Player) + + if input == "" { + sess.WriteLine("Eat what?") + return + } + + qty, itemName := parseQty(input) + _ = qty + + matches := g.findInventoryMatches(itemName, p) + if len(matches) == 0 { + sess.WriteLine(fmt.Sprintf("You don't have any '%s'.", itemName)) + return + } + + unique := uniqueItemNames(matches) + if len(unique) > 1 { + sess.WriteLine("Which one?") + for _, name := range unique { + sess.WriteLine(fmt.Sprintf(" - %s", name)) + } + return + } + + itemID := matches[0].ID + def, _ := g.ItemStore.Load(itemID) + + if def == nil || def.EatMessage == "" { + sess.WriteLine("You can't eat that.") + return + } + + if p.ConsumeCooldown > 0 { + sess.WriteLine("You're still digesting your last meal.") + return + } + + g.cancelRest(p.Name) + + if p.Action == nil && len(p.WalkSequence) == 0 { + g.doEatNow(sess, p, itemID, def) + return + } + + g.CancelAction(p) + + g.consumeQueue[p.Name] = &QueuedCommand{ + Session: sess, + Command: "eat", + Args: itemID, + Timestamp: time.Now(), + } + + if !p.OptionBool("queue_silently") { + sess.WriteLine(fmt.Sprintf("\nYou prepare to eat %s.", def.Name)) + } +} + +func (g *Game) doEatNow(sess *net.Session, p *player.Player, itemID string, def *object.ItemDef) { + if def == nil { + var err error + def, err = g.ItemStore.Load(itemID) + if err != nil || def.EatMessage == "" { + return + } + } + + p.RemoveItem(itemID, 1) + + p.HP += def.HealValue + maxHP := p.MaxHP() + if p.HP > maxHP { + p.HP = maxHP + } + if p.HP < 0 { + p.HP = 0 + } + + if def.HealValue != 0 { + p.StartRegen() + } + + p.ConsumeCooldown = 3 + g.AccountStore.SaveCharacter(p) + + p.ActionState = &ActionState{Type: ActionEating, TargetName: def.Name} + + sess.WriteLine(def.EatMessage) + + if p.HP <= 0 { + g.endCombat(sess, p, nil) + } +} + +func (g *Game) processConsumeQueue(p *player.Player, sess *net.Session) bool { + qc, ok := g.consumeQueue[p.Name] + if !ok { + return false + } + delete(g.consumeQueue, p.Name) + + itemID := qc.Args + def, err := g.ItemStore.Load(itemID) + if err != nil || def.EatMessage == "" { + return false + } + + if p.ConsumeCooldown > 0 { + return false + } + + if !p.HasItem(itemID) { + sess.WriteLine("You no longer have that to eat.") + return false + } + + g.doEatNow(sess, p, itemID, def) + return true +} diff --git a/internal/game/cmd_use.go b/internal/game/cmd_use.go new file mode 100644 index 0000000..ef36e12 --- /dev/null +++ b/internal/game/cmd_use.go @@ -0,0 +1,200 @@ +package game + +import ( + "fmt" + "strings" + + "thirdcollapse/internal/net" + "thirdcollapse/internal/object" + "thirdcollapse/internal/player" +) + +func (g *Game) doUse(sess *net.Session, input string) { + p := sess.Player.(*player.Player) + g.CancelAction(p) + + lower := strings.ToLower(input) + + sep := "" + if strings.Contains(lower, " on ") { + sep = " on " + } else if strings.Contains(lower, " with ") { + sep = " with " + } + + if sep != "" { + parts := strings.SplitN(lower, sep, 2) + itemA := strings.TrimSpace(parts[0]) + itemB := strings.TrimSpace(parts[1]) + if itemA == itemB { + sess.WriteLine("You can't use that with itself.") + return + } + g.doUseItemOnTarget(sess, p, itemA, itemB) + return + } + + if len(strings.Fields(input)) >= 2 { + g.doUseItemOnTarget(sess, p, strings.Fields(input)[0], strings.Join(strings.Fields(input)[1:], " ")) + return + } + + g.StartAction(sess, "use", input) +} + +func (g *Game) doUseItemOnTarget(sess *net.Session, p *player.Player, itemAName, itemBName string) { + matchesA := g.findInventoryMatches(itemAName, p) + if len(matchesA) == 0 { + sess.WriteLine(fmt.Sprintf("You don't have any '%s'.", itemAName)) + return + } + + uniqueA := uniqueItemNames(matchesA) + if len(uniqueA) > 1 { + sess.WriteLine("Which one?") + for _, name := range uniqueA { + sess.WriteLine(fmt.Sprintf(" - %s", name)) + } + return + } + + itemAID := matchesA[0].ID + + matchesB := g.findInventoryMatches(itemBName, p) + + objInstances := g.World.FindObjInstances(p.RoomID, itemBName) + if len(objInstances) > 0 { + objSt := &objInstances[0] + def, _ := g.ObjectStore.Load(objSt.DefID) + if def == nil { + g.StartAction(sess, "use", itemBName) + return + } + + stationIDs := []string{def.ID} + recipes, err := g.RecipeStore.FindByStation(itemAID, stationIDs, "") + if err == nil && len(recipes) > 0 { + if len(recipes) == 1 { + g.startCook(sess, p, &recipes[0], def.Name) + return + } + sess.PendingRecipeItem = itemAID + sess.PendingCookMenu = recipesToMenuData(recipes) + sess.State = net.StateCookRecipe + sess.WriteLine(fmt.Sprintf("\nWhat would you like to make with %s on the %s?", itemAName, def.Name)) + for i, r := range recipes { + sess.WriteLine(fmt.Sprintf(" %d) %s", i+1, g.recipeName(sess, &r))) + } + return + } + + g.StartAction(sess, "use", itemBName) + return + } + + matchesB = g.findInventoryMatches(itemBName, p) + if len(matchesB) > 0 { + uniqueB := uniqueItemNames(matchesB) + if len(uniqueB) > 1 { + sess.WriteLine("Which '" + itemBName + "'?") + for _, name := range uniqueB { + sess.WriteLine(fmt.Sprintf(" - %s", name)) + } + return + } + + itemBID := matchesB[0].ID + + matched := g.findCombineResults(sess, p, itemAID, itemBID) + if len(matched) == 1 { + g.produceCombined(sess, p, matched[0]) + return + } + if len(matched) > 1 { + sess.PendingCookMenu = combineMenuData(matched) + sess.State = net.StateCookRecipe + sess.WriteLine("\nWhat would you like to make?") + for i, def := range matched { + sess.WriteLine(fmt.Sprintf(" %d) %s", i+1, g.itemColorize(sess, def, def.Name))) + } + return + } + + sess.WriteLine("You can't combine those items.") + return + } + + sess.WriteLine(fmt.Sprintf("There's no '%s' here to use that on.", itemBName)) +} + +func (g *Game) findCombineResults(sess *net.Session, p *player.Player, itemAID, itemBID string) []*object.ItemDef { + items, err := g.ItemStore.LoadAll() + if err != nil { + return nil + } + var matched []*object.ItemDef + for _, def := range items { + if len(def.MadeFrom) == 0 { + continue + } + aEntry := -1 + bEntry := -1 + for ei, entry := range def.MadeFrom { + for _, id := range entry.Items { + if id == itemAID && aEntry < 0 { + aEntry = ei + } + if id == itemBID && bEntry < 0 { + bEntry = ei + } + } + } + if aEntry >= 0 && bEntry >= 0 && aEntry != bEntry && madeFromItemsAvailable(p, def.MadeFrom) { + matched = append(matched, def) + } + } + return matched +} + +func (g *Game) produceCombined(sess *net.Session, p *player.Player, def *object.ItemDef) { + freeSlot := p.FirstFreeSlot() + if freeSlot == -1 { + sess.WriteLine("Your inventory is too full.") + return + } + for _, entry := range def.MadeFrom { + for _, id := range entry.Items { + if p.HasItem(id) { + p.RemoveItem(id, entry.Qty) + break + } + } + } + p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: def.ID, Quantity: 1}) + g.AccountStore.SaveCharacter(p) + sess.WriteLine(fmt.Sprintf("You combine the ingredients and create %s.", g.itemColorize(sess, def, def.Name))) +} + +func combineMenuData(defs []*object.ItemDef) []map[string]string { + out := make([]map[string]string, len(defs)) + for i, d := range defs { + out[i] = map[string]string{"combine_item": d.ID} + } + return out +} + +func madeFromItemsAvailable(p *player.Player, madeFrom []object.MadeFromEntry) bool { + for _, entry := range madeFrom { + found := false + for _, id := range entry.Items { + if p.HasItem(id) { + found = true + break + } + } + if !found { + return false + } + } + return true +} diff --git a/internal/game/game.go b/internal/game/game.go index 456419f..33dd0d1 100644 --- a/internal/game/game.go +++ b/internal/game/game.go @@ -40,6 +40,7 @@ type Game struct { AccountStore *player.AccountStore MobStore *world.MobStore BehaviorStore *action.Store + RecipeStore *action.RecipeStore Hub *net.Hub Ticks *engine.Engine WorldFlags map[string]any @@ -51,6 +52,7 @@ type Game struct { combatPadWidth int freeQueue map[string][]QueuedCommand activeQueue map[string]*QueuedCommand + consumeQueue map[string]*QueuedCommand pendingDepletions []pendingDepletion } @@ -62,6 +64,7 @@ func New(dataDir string, colorConfig *config.ColorsConfig) *Game { AccountStore: player.NewAccountStore(dataDir), MobStore: world.NewMobStore(dataDir), BehaviorStore: action.NewStore(dataDir), + RecipeStore: action.NewRecipeStore(dataDir), Ticks: engine.New(), WorldFlags: make(map[string]any), ColorConfig: colorConfig, @@ -70,6 +73,7 @@ func New(dataDir string, colorConfig *config.ColorsConfig) *Game { loggedInChars: make(map[string]*net.Session), freeQueue: make(map[string][]QueuedCommand), activeQueue: make(map[string]*QueuedCommand), + consumeQueue: make(map[string]*QueuedCommand), pendingDepletions: nil, } } @@ -118,6 +122,8 @@ func (g *Game) HandleSession(sess *net.Session, input string) { g.handleTalkInput(sess, input) case net.StateDropAllConfirm: g.handleDropAllConfirm(sess, input) + case net.StateCookRecipe: + g.handleCookRecipe(sess, input) } } @@ -134,8 +140,10 @@ func classifyCommand(cmd string) CommandClass { "attack", "kill", "north", "n", "south", "s", "east", "e", "west", "w", "up", "u", "down", "d", - "quit", "use", "burn", "stoke", "search", "walk": + "quit", "use", "burn", "stoke", "search", "walk", "cook": return ClassActive + case "eat": + return ClassFree } if _, ok := verbAliases[cmd]; ok { return ClassActive @@ -314,7 +322,7 @@ func (g *Game) executeCommand(sess *net.Session, cmd string, args []string, rawI } else { g.doHelp(sess, strings.Join(args, " ")) } - case "mine", "chop", "fish", "cut", "use", "pull", "push": + case "mine", "chop", "fish", "cut", "pull", "push": g.CancelAction(p) if len(args) == 0 { target := g.resolveDefaultTarget(p.RoomID, cmd) @@ -328,6 +336,15 @@ func (g *Game) executeCommand(sess *net.Session, cmd string, args []string, rawI g.StartAction(sess, cmd, strings.Join(args, " ")) return } + case "use": + g.doUse(sess, strings.Join(args, " ")) + return + case "cook": + g.doCook(sess, strings.Join(args, " ")) + return + case "eat": + g.doEat(sess, strings.Join(args, " ")) + return case "talk", "speak", "ask": g.CancelAction(p) if len(args) == 0 { @@ -413,7 +430,7 @@ func (g *Game) ProcessQueuedCommands() { } switch as.Type { case ActionGathering, ActionCombating, ActionUsing, ActionTalking, - ActionToggling, ActionBurning, ActionStoking, ActionResting, ActionWalking: + ActionToggling, ActionBurning, ActionStoking, ActionResting, ActionWalking, ActionCooking: default: p.ActionState = nil } @@ -455,7 +472,8 @@ func (g *Game) ProcessQueuedCommands() { g.executeCommand(qc.Session, parts[0], parts[1:], qc.Command+" "+qc.Args) } isBusy := p.Action != nil || len(p.WalkSequence) > 0 || combat.GetCombat(p.Name) != nil - if wasBusy || !isBusy { + _, isResting := g.restTimers[p.Name] + if !isResting && (wasBusy || !isBusy) { g.writePrompt(qc.Session) } } diff --git a/internal/game/help.go b/internal/game/help.go index 4d716ad..d3f34c3 100644 --- a/internal/game/help.go +++ b/internal/game/help.go @@ -29,8 +29,10 @@ var commandList = []cmdEntry{ {"burn", "Active", "Start a fire"}, {"chop / cut", "Active", "Chop trees (Woodcutting)"}, {"color / colors", "Instant", "Customize display colors"}, + {"cook", "Active", "Cook raw food on a fire or range"}, {"description / desc", "Instant", "Set your character description"}, {"drop", "Active", "Drop items to the ground"}, + {"eat", "Free", "Eat food to restore hitpoints"}, {"equipment / eq", "Instant", "Show equipped items"}, {"exits", "Instant", "List available exits"}, {"fish", "Active", "Fish at fishing spots (Fishing)"}, diff --git a/internal/game/tick.go b/internal/game/tick.go index f326ad8..7567ca7 100644 --- a/internal/game/tick.go +++ b/internal/game/tick.go @@ -198,3 +198,18 @@ func (g *Game) legalMobExits(inst *world.MobInstance) []int { } return legal } + +func (g *Game) ConsumeTick() { + if g.Hub == nil { + return + } + for _, sess := range g.Hub.AllSessions() { + p, ok := sess.Player.(*player.Player) + if !ok || p == nil { + continue + } + if p.ConsumeCooldown > 0 { + p.ConsumeCooldown-- + } + } +} |
