diff options
Diffstat (limited to 'internal/game')
45 files changed, 301 insertions, 671 deletions
diff --git a/internal/game/action.go b/internal/game/action.go index f25cabc..2f5dbaf 100644 --- a/internal/game/action.go +++ b/internal/game/action.go @@ -57,7 +57,7 @@ func (g *Game) startAction(sess *net.Session, verb, target string) { if p.Action != nil { g.cancelAction(p) } - g.cancelBackgroundAction(p) + g.cancelBgAction(p) lower := strings.ToLower(target) instanceIdx := -1 @@ -206,7 +206,7 @@ func (g *Game) cancelAction(p *player.Player) { p.ClearMoveState() } -func (g *Game) cancelBackgroundAction(p *player.Player) { +func (g *Game) cancelBgAction(p *player.Player) { p.BackgroundAction = nil p.BackgroundActionState = nil } diff --git a/internal/game/action_agility.go b/internal/game/action_agility.go index 37fbab9..43c9cfd 100644 --- a/internal/game/action_agility.go +++ b/internal/game/action_agility.go @@ -52,13 +52,7 @@ func (g *Game) startObstacle(sess *net.Session, p *player.Player, info *Obstacle TargetName: info.CourseName, } - if g.Hub != nil { - for _, other := range g.Hub.PlayersInRoom(p.RoomID) { - if other != sess && other.Player != nil { - other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s starts %s on the %s.", p.Name, gerund, info.CourseName))) - } - } - } + g.broadcastAction(sess, "\n%s starts %s on the %s.", p.Name, gerund, info.CourseName) } func (g *Game) advanceObstacle(sess *net.Session, p *player.Player) { diff --git a/internal/game/action_burn.go b/internal/game/action_burn.go index c31d7d5..71b5a35 100644 --- a/internal/game/action_burn.go +++ b/internal/game/action_burn.go @@ -28,7 +28,7 @@ func (g *Game) doBurn(sess *net.Session, p *player.Player, input string) { if p.Action != nil { g.cancelAction(p) } - g.cancelBackgroundAction(p) + g.cancelBgAction(p) itemID, fromGround, ambiguous := g.resolveBurnTarget(p, input) if ambiguous { @@ -47,7 +47,7 @@ func (g *Game) doStoke(sess *net.Session, p *player.Player, input string) { if p.Action != nil { g.cancelAction(p) } - g.cancelBackgroundAction(p) + g.cancelBgAction(p) if !g.hasFireObject(p.RoomID) { sess.WriteLine("There's no fire here to stoke.") @@ -120,13 +120,7 @@ func (g *Game) startBurn(sess *net.Session, p *player.Player, itemID string, fro p.ActionState = &ActionState{Type: ActionBurning} - if g.Hub != nil { - for _, other := range g.Hub.PlayersInRoom(p.RoomID) { - if other != sess && other.Player != nil { - other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s starts building a fire.", p.Name))) - } - } - } + g.broadcastAction(sess, "\n%s starts building a fire.", p.Name) p.Action = &action.Action{ Type: "burn", @@ -166,13 +160,7 @@ func (g *Game) startStoke(sess *net.Session, p *player.Player, itemID string) { p.ActionState = &ActionState{Type: ActionStoking} - if g.Hub != nil { - for _, other := range g.Hub.PlayersInRoom(p.RoomID) { - if other != sess && other.Player != nil { - other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s tends to the fire.", p.Name))) - } - } - } + g.broadcastAction(sess, "\n%s tends to the fire.", p.Name) p.Action = &action.Action{ Type: "stoke", @@ -260,11 +248,7 @@ func (g *Game) advanceBurn(sess *net.Session, p *player.Player) { } sess.WriteLine(msg) - for _, other := range g.Hub.PlayersInRoom(p.RoomID) { - if other != sess { - other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s started a fire!", p.Name))) - } - } + g.broadcastAction(sess, "\n%s started a fire!", p.Name) return } @@ -458,12 +442,12 @@ func (g *Game) cancelStokers(roomID int) { func (g *Game) resolveBurnTarget(p *player.Player, input string) (itemID string, fromGround bool, ambiguous bool) { if input != "" { - return g.resolveNamedBurnTarget(p, input) + return g.namedBurnTarget(p, input) } - return g.resolveDefaultBurnTarget(p) + return g.defaultBurnTarget(p) } -func (g *Game) collectBurnableGround(roomID int, name string) []string { +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) @@ -477,7 +461,7 @@ func (g *Game) collectBurnableGround(roomID int, name string) []string { return out } -func (g *Game) collectBurnableInv(p *player.Player, name string) []string { +func (g *Game) burnableInv(p *player.Player, name string) []string { var out []string seen := make(map[string]bool) for _, slot := range p.Inventory { @@ -496,8 +480,8 @@ func (g *Game) collectBurnableInv(p *player.Player, name string) []string { return out } -func (g *Game) resolveNamedBurnTarget(p *player.Player, input string) (itemID string, fromGround bool, ambiguous bool) { - groundMatches := g.collectBurnableGround(p.RoomID, input) +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 } @@ -505,7 +489,7 @@ func (g *Game) resolveNamedBurnTarget(p *player.Player, input string) (itemID st return groundMatches[0], true, false } - invMatches := g.collectBurnableInv(p, input) + invMatches := g.burnableInv(p, input) if len(invMatches) > 1 { return "", false, true } @@ -516,8 +500,8 @@ func (g *Game) resolveNamedBurnTarget(p *player.Player, input string) (itemID st return "", false, false } -func (g *Game) resolveDefaultBurnTarget(p *player.Player) (itemID string, fromGround bool, ambiguous bool) { - groundBurnable := g.collectBurnableGround(p.RoomID, "") +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 } @@ -525,7 +509,7 @@ func (g *Game) resolveDefaultBurnTarget(p *player.Player) (itemID string, fromGr return "", false, true } - invBurnable := g.collectBurnableInv(p, "") + invBurnable := g.burnableInv(p, "") if len(invBurnable) == 1 { return invBurnable[0], false, false } @@ -537,7 +521,7 @@ func (g *Game) resolveDefaultBurnTarget(p *player.Player) (itemID string, fromGr } func (g *Game) resolveStokeTarget(p *player.Player, input string) (string, bool) { - invBurnable := g.collectBurnableInv(p, input) + invBurnable := g.burnableInv(p, input) if len(invBurnable) == 1 { return invBurnable[0], false } diff --git a/internal/game/action_clean.go b/internal/game/action_clean.go index 104dcb0..7330883 100644 --- a/internal/game/action_clean.go +++ b/internal/game/action_clean.go @@ -21,7 +21,7 @@ func (g *Game) advanceClean(sess *net.Session, p *player.Player) { allRecipes, err := g.RecipeStore.LoadAll() if err != nil { - g.cancelBackgroundAction(p) + g.cancelBgAction(p) return } @@ -51,7 +51,7 @@ func (g *Game) advanceClean(sess *net.Session, p *player.Player) { if recipe == nil { sess.WriteLine("\nYou've finished cleaning herbs.") - g.cancelBackgroundAction(p) + g.cancelBgAction(p) return } @@ -105,7 +105,7 @@ func (g *Game) advanceClean(sess *net.Session, p *player.Player) { if !hasMore { sess.WriteLine("\nYou've finished cleaning herbs.") - g.cancelBackgroundAction(p) + g.cancelBgAction(p) return } diff --git a/internal/game/action_default_target.go b/internal/game/action_default_target.go index 2ca0bd3..a184f32 100644 --- a/internal/game/action_default_target.go +++ b/internal/game/action_default_target.go @@ -8,7 +8,7 @@ func verbToSkill(verb string) string { return verbSkill[verb] } -func (g *Game) resolveDefaultTarget(roomID int, verb string) string { +func (g *Game) defaultTarget(roomID int, verb string) string { skill := verbToSkill(verb) if skill == "" { return "" diff --git a/internal/game/action_farm.go b/internal/game/action_farm.go index d79150f..21604aa 100644 --- a/internal/game/action_farm.go +++ b/internal/game/action_farm.go @@ -51,7 +51,7 @@ func (g *Game) ensureFarmState(p *player.Player, prefix string) { } } -func (g *Game) findActiveFarmPrefixes(p *player.Player) []string { +func (g *Game) findFarmPrefixes(p *player.Player) []string { seen := make(map[string]bool) var prefixes []string for key := range p.Flags { @@ -94,7 +94,7 @@ func (g *Game) FarmTick() { } func (g *Game) advanceFarmGrowth(sess *net.Session, p *player.Player) { - prefixes := g.findActiveFarmPrefixes(p) + prefixes := g.findFarmPrefixes(p) for _, prefix := range prefixes { seedID, _ := p.Flags[prefix+"_seed"].(string) if seedID == "" { @@ -492,7 +492,7 @@ func (g *Game) farmMatchPrefix(p *player.Player, input string) (prefix string, d return "", "", -1 } -func (g *Game) farmLookSuffixForObj(p *player.Player, defID string, index int) string { +func (g *Game) farmObjSuffix(p *player.Player, defID string, index int) string { if defID == "tool_shed" { return g.toolShedLookSuffix(p) } @@ -538,7 +538,7 @@ func (g *Game) showFarmPatches(sess *net.Session, p *player.Player) { g.ensureFarmState(p, prefix) idxStr := "" - if obj.Index > 0 || g.countFarmPatchesOfType(objInstances, obj.DefID) > 1 { + if obj.Index > 0 || g.countPatchesByType(objInstances, obj.DefID) > 1 { idxStr = fmt.Sprintf(" %d", obj.Index+1) } @@ -618,7 +618,7 @@ func (g *Game) showFarmPatches(sess *net.Session, p *player.Player) { } } -func (g *Game) countFarmPatchesOfType(instances []world.ObjState, defID string) int { +func (g *Game) countPatchesByType(instances []world.ObjState, defID string) int { count := 0 for _, obj := range instances { if obj.DefID == defID && farmPatchDefIDs[obj.DefID] { diff --git a/internal/game/action_fletch.go b/internal/game/action_fletch.go index aa5dfc7..3d8c30f 100644 --- a/internal/game/action_fletch.go +++ b/internal/game/action_fletch.go @@ -115,13 +115,7 @@ func (g *Game) startTimedFletch(sess *net.Session, p *player.Player, recipe *act } p.BackgroundActionState = &ActionState{Type: ActionFletching, TargetName: outputName} - if g.Hub != nil { - for _, other := range g.Hub.PlayersInRoom(p.RoomID) { - if other != sess && other.Player != nil { - other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s starts fletching.", p.Name))) - } - } - } + g.broadcastAction(sess, "\n%s starts fletching.", p.Name) sess.WriteLine(fmt.Sprintf("\nYou begin fletching %s.", outputName)) } @@ -132,9 +126,9 @@ func (g *Game) advanceFletch(sess *net.Session, p *player.Player) { wait, _ := p.BackgroundAction.Data["wait"].(float64) remaining, _ := p.BackgroundAction.Data["remaining"].(int) - recipe := g.loadProductionRecipe(recipeID) + recipe := g.loadRecipe(recipeID) if recipe == nil { - g.cancelBackgroundAction(p) + g.cancelBgAction(p) return } @@ -146,7 +140,7 @@ func (g *Game) advanceFletch(sess *net.Session, p *player.Player) { if !g.canDoFletchRecipe(p, recipe) { sess.WriteLine("\nYou've run out of fletching materials.") - g.cancelBackgroundAction(p) + g.cancelBgAction(p) return } @@ -175,7 +169,7 @@ func (g *Game) advanceFletch(sess *net.Session, p *player.Player) { freeSlot := p.FirstFreeSlot() if freeSlot == -1 { sess.WriteLine("\nYour inventory is too full!") - g.cancelBackgroundAction(p) + g.cancelBgAction(p) return } p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: recipe.Output, Quantity: outputQty}) @@ -205,14 +199,14 @@ func (g *Game) advanceFletch(sess *net.Session, p *player.Player) { p.BackgroundAction.Data["remaining"] = remaining if remaining <= 0 { sess.WriteLine("\nYou've finished fletching.") - g.cancelBackgroundAction(p) + g.cancelBgAction(p) return } } if !g.canContinueProduction(p, recipe) { sess.WriteLine("\nYou've run out of fletching materials.") - g.cancelBackgroundAction(p) + g.cancelBgAction(p) return } diff --git a/internal/game/action_gather.go b/internal/game/action_gather.go index e169beb..9b47de2 100644 --- a/internal/game/action_gather.go +++ b/internal/game/action_gather.go @@ -53,42 +53,11 @@ func (g *Game) startGather(sess *net.Session, p *player.Player, obj *object.Obje } wait := cfg.BaseWait + var toolName string if len(cfg.Tools) > 0 { - var toolSpeed float64 = -1 - if itemID, ok := p.Equipment[object.SlotMainHand]; ok { - def, err := g.ItemStore.Load(itemID) - if err == nil { - for _, t := range cfg.Tools { - if def.ToolType == t { - toolSpeed = def.ToolSpeed - break - } - } - } - } - if toolSpeed < 0 { - for i := 0; i < 28; i++ { - slot := p.InvSlot(i) - if slot == nil { - continue - } - def, err := g.ItemStore.Load(slot.ItemID) - if err != nil { - continue - } - for _, t := range cfg.Tools { - if def.ToolType == t { - toolSpeed = def.ToolSpeed - break - } - } - if toolSpeed >= 0 { - break - } - } - } - if toolSpeed < 0 { + toolSpeed, name, found := g.findTool(p, cfg.Tools) + if !found { names := make([]string, len(cfg.Tools)) for i, t := range cfg.Tools { names[i] = strings.ReplaceAll(t, "_", " ") @@ -97,6 +66,7 @@ func (g *Game) startGather(sess *net.Session, p *player.Player, obj *object.Obje sess.WriteLine(fmt.Sprintf("You need a %s to do that.", toolList)) return } + toolName = name wait = cfg.BaseWait - toolSpeed if wait < 1 { wait = 1 @@ -118,38 +88,6 @@ func (g *Game) startGather(sess *net.Session, p *player.Player, obj *object.Obje sess.WriteLine("Your inventory is too full!") return } - - toolName := "" - if itemID, ok := p.Equipment[object.SlotMainHand]; ok { - if def, err := g.ItemStore.Load(itemID); err == nil { - for _, t := range cfg.Tools { - if def.ToolType == t { - toolName = def.Name - break - } - } - } - } - if toolName == "" { - for _, slot := range p.Inventory { - if slot == nil || slot.Quantity <= 0 { - continue - } - def, err := g.ItemStore.Load(slot.ItemID) - if err != nil { - continue - } - for _, t := range cfg.Tools { - if def.ToolType == t { - toolName = def.Name - break - } - } - if toolName != "" { - break - } - } - } verb := cfg.Skill switch cfg.Skill { case "woodcutting": @@ -157,13 +95,7 @@ func (g *Game) startGather(sess *net.Session, p *player.Player, obj *object.Obje } p.ActionState = &ActionState{Type: ActionGathering, TargetName: obj.Name, ToolName: toolName, Verb: verb} - if g.Hub != nil { - for _, other := range g.Hub.PlayersInRoom(p.RoomID) { - if other != sess && other.Player != nil { - other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s starts %s the %s.", p.Name, verb, obj.Name))) - } - } - } + g.broadcastAction(sess, "\n%s starts %s the %s.", p.Name, verb, obj.Name) data := map[string]any{ "obj_def_id": obj.ID, diff --git a/internal/game/action_search.go b/internal/game/action_search.go index d7dabe0..5cb8644 100644 --- a/internal/game/action_search.go +++ b/internal/game/action_search.go @@ -35,13 +35,7 @@ func (g *Game) startSearch(sess *net.Session, p *player.Player, itemID string, s p.ActionState = &ActionState{Type: ActionSearching, TargetName: def.Name} - if g.Hub != nil { - for _, other := range g.Hub.PlayersInRoom(p.RoomID) { - if other != sess && other.Player != nil { - other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s searches a %s.", p.Name, def.Name))) - } - } - } + g.broadcastAction(sess, "\n%s searches a %s.", p.Name, def.Name) } func (g *Game) advanceSearch(sess *net.Session, p *player.Player) { diff --git a/internal/game/action_steal.go b/internal/game/action_steal.go index 3510ce7..a95be25 100644 --- a/internal/game/action_steal.go +++ b/internal/game/action_steal.go @@ -259,13 +259,7 @@ func (g *Game) startSteal(sess *net.Session, p *player.Player, targetType string sess.WriteLine(fmt.Sprintf("You attempt to steal from the %s...", targetName)) p.ActionState = &ActionState{Type: ActionStealing, TargetName: targetName} - if g.Hub != nil { - for _, other := range g.Hub.PlayersInRoom(p.RoomID) { - if other != sess && other.Player != nil { - other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s glances around the room.", p.Name))) - } - } - } + g.broadcastAction(sess, "\n%s glances around the room.", p.Name) p.Action = &action.Action{ Type: "steal", diff --git a/internal/game/action_use.go b/internal/game/action_use.go index 368162b..a3858a9 100644 --- a/internal/game/action_use.go +++ b/internal/game/action_use.go @@ -36,13 +36,7 @@ func (g *Game) startUse(sess *net.Session, p *player.Player, obj *object.ObjectD p.ActionState = &ActionState{Type: ActionUsing, TargetName: obj.Name} - if g.Hub != nil { - for _, other := range g.Hub.PlayersInRoom(p.RoomID) { - if other != sess && other.Player != nil { - other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s uses the %s.", p.Name, obj.Name))) - } - } - } + g.broadcastAction(sess, "\n%s uses the %s.", p.Name, obj.Name) p.Action = &action.Action{ Type: "use", diff --git a/internal/game/cmd_agility.go b/internal/game/cmd_agility.go index 084c5cf..e347322 100644 --- a/internal/game/cmd_agility.go +++ b/internal/game/cmd_agility.go @@ -31,7 +31,7 @@ func (g *Game) doObstacle(sess *net.Session, verb string) { if p.Action != nil { g.cancelAction(p) } - g.cancelBackgroundAction(p) + g.cancelBgAction(p) g.startObstacle(sess, p, info) } diff --git a/internal/game/cmd_attack.go b/internal/game/cmd_attack.go index f9cfba2..89cde46 100644 --- a/internal/game/cmd_attack.go +++ b/internal/game/cmd_attack.go @@ -126,7 +126,7 @@ func (g *Game) findMob(sess *net.Session, input string, roomID int) *world.MobIn func (g *Game) startCombat(sess *net.Session, p *player.Player, mob *world.MobInstance) { g.cancelRest(p.Name) - g.cancelBackgroundAction(p) + g.cancelBgAction(p) combat.EnterCombat(p.Name, mob.InstanceID) p.AttackTimer = 0 @@ -162,13 +162,7 @@ func (g *Game) startCombat(sess *net.Session, p *player.Player, mob *world.MobIn } p.ActionState = &ActionState{Type: ActionCombating, TargetName: mob.Name} - if g.Hub != nil { - for _, other := range g.Hub.PlayersInRoom(p.RoomID) { - if other != sess && other.Player != nil { - other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s attacks %s!", p.Name, mobDisplayName(mob, true)))) - } - } - } + g.broadcastAction(sess, "\n%s attacks %s!", p.Name, mobDisplayName(mob, true)) g.Ticks.Subscribe(1, func() bool { cs := combat.GetCombat(p.Name) @@ -567,7 +561,7 @@ func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInst } if mob.Drops.Remains != "" { - g.World.AddReservedGroundItem(p.RoomID, mob.Drops.Remains, 1, p.Name) + g.World.AddReservedItem(p.RoomID, mob.Drops.Remains, 1, p.Name) def, _ := g.ItemStore.Load(mob.Drops.Remains) name := mob.Drops.Remains if def != nil { @@ -588,7 +582,7 @@ func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInst if qty <= 0 { qty = 1 } - g.World.AddReservedGroundItem(p.RoomID, entry.ItemID, qty, p.Name) + g.World.AddReservedItem(p.RoomID, entry.ItemID, qty, p.Name) def, _ := g.ItemStore.Load(entry.ItemID) name := entry.ItemID if def != nil { diff --git a/internal/game/cmd_clean.go b/internal/game/cmd_clean.go index 47896bb..951b41d 100644 --- a/internal/game/cmd_clean.go +++ b/internal/game/cmd_clean.go @@ -1,7 +1,6 @@ package game import ( - "fmt" "strings" "thehouseoficarus/internal/action" @@ -54,7 +53,7 @@ func (g *Game) doClean(sess *net.Session, input string) { return } - g.cancelBackgroundAction(p) + g.cancelBgAction(p) p.BackgroundAction = &action.Action{ Type: "clean", @@ -67,13 +66,7 @@ func (g *Game) doClean(sess *net.Session, input string) { } p.BackgroundActionState = &ActionState{Type: ActionCleaning} - if g.Hub != nil { - for _, other := range g.Hub.PlayersInRoom(p.RoomID) { - if other != sess && other.Player != nil { - other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s starts cleaning herbs.", p.Name))) - } - } - } + g.broadcastAction(sess, "\n%s starts cleaning herbs.", p.Name) sess.WriteLine("\nYou begin cleaning herbs.") } diff --git a/internal/game/cmd_consume.go b/internal/game/cmd_consume.go index 0a7d335..3782237 100644 --- a/internal/game/cmd_consume.go +++ b/internal/game/cmd_consume.go @@ -283,11 +283,5 @@ func (g *Game) applyPotion(sess *net.Session, p *player.Player, itemID string, d p.ConsumeCooldown = 3 p.ActionState = &ActionState{Type: ActionEating} - if g.Hub != nil { - for _, other := range g.Hub.PlayersInRoom(p.RoomID) { - if other != sess && other.Player != nil { - other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s drinks a %s.", p.Name, def.Name))) - } - } - } + g.broadcastAction(sess, "\n%s drinks a %s.", p.Name, def.Name) } diff --git a/internal/game/cmd_cook.go b/internal/game/cmd_cook.go index 288afb0..04c98bb 100644 --- a/internal/game/cmd_cook.go +++ b/internal/game/cmd_cook.go @@ -61,28 +61,13 @@ func (g *Game) doCook(sess *net.Session, input string) { } } - if len(recipes) == 0 { - sess.WriteLine("You can't cook that.") - return - } - - if len(recipes) == 1 { - g.promptHowMany(sess, recipes[0].ID) - return - } - - sess.PendingMenu = recipeMenuData(recipeDefIDs(recipes)) - sess.State = net.StateRecipeChoice - var names []string - for _, r := range recipes { - names = append(names, g.recipeName(sess, &r)) - } - g.showMenuTable(sess, fmt.Sprintf("What would you like to cook on the %s?", stationName), names) + title := fmt.Sprintf("What would you like to cook on the %s?", stationName) + g.showRecipeChoice(sess, p, recipes, "You can't cook that.", title, "") } func (g *Game) showCookMenu(sess *net.Session, p *player.Player, allRecipes []action.RecipeDef, stationDefID, stationName string) { seen := make(map[string]bool) - var entries []recipeEntry + var recipes []action.RecipeDef for _, r := range allRecipes { if r.Type != "cooking" { @@ -98,41 +83,9 @@ func (g *Game) showCookMenu(sess *net.Session, p *player.Player, allRecipes []ac continue } seen[r.ID] = true - entries = append(entries, recipeEntry{g.recipeName(sess, &r), r}) - } - - if len(entries) == 0 { - sess.WriteLine("You don't have anything you can cook.") - return + recipes = append(recipes, r) } - if len(entries) == 1 { - if p.OptionBool("cook_all") { - g.startProductionFromRecipe(sess, p, &entries[0].Recipe, 0) - return - } - g.promptHowMany(sess, entries[0].Recipe.ID) - return - } - - sess.State = net.StateRecipeChoice - var names []string - for _, e := range entries { - names = append(names, e.ItemName) - } - g.showMenuTable(sess, fmt.Sprintf("What would you like to cook on the %s?", stationName), names) - ids := make([]string, len(entries)) - for i, e := range entries { - ids[i] = e.Recipe.ID - } - sess.PendingMenu = recipeMenuData(ids) -} - -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() + title := fmt.Sprintf("What would you like to cook on the %s?", stationName) + g.showRecipeChoice(sess, p, recipes, "You don't have anything you can cook.", title, "cook_all") } diff --git a/internal/game/cmd_craft.go b/internal/game/cmd_craft.go index 447d980..d061a9c 100644 --- a/internal/game/cmd_craft.go +++ b/internal/game/cmd_craft.go @@ -17,14 +17,14 @@ func (g *Game) executeConstruct(sess *net.Session, args []string, rawInput strin } func (g *Game) doCraft(sess *net.Session, input string) { - g.doGenericStationSkill(sess, input, "crafting", player.Crafting, "last_craft", "Crafting", "craft", "Craft") + g.doStationSkill(sess, input, "crafting", player.Crafting, "last_craft", "Crafting", "craft", "Craft") } func (g *Game) doConstruct(sess *net.Session, input string) { - g.doGenericStationSkill(sess, input, "construction", player.Construction, "last_construct", "Construction", "construct", "Construct") + g.doStationSkill(sess, input, "construction", player.Construction, "last_construct", "Construction", "construct", "Construct") } -func (g *Game) doGenericStationSkill(sess *net.Session, input string, recipeType string, skill player.SkillName, flagKey, label, verbPast, verbCap string) { +func (g *Game) doStationSkill(sess *net.Session, input string, recipeType string, skill player.SkillName, flagKey, label, verbPast, verbCap string) { p := sess.Player g.cancelAction(p) @@ -39,14 +39,14 @@ func (g *Game) doGenericStationSkill(sess *net.Session, input string, recipeType if r.Type != recipeType { continue } - if !g.genericRecipeVisible(p, &r) { + if !g.recipeVisible(p, &r) { continue } recipes = append(recipes, r) } if input != "" { - g.doGenericWithInput(sess, p, recipes, input, skill, flagKey, label, verbCap) + g.doWithInput(sess, p, recipes, input, skill, flagKey, label, verbCap) return } @@ -55,8 +55,8 @@ func (g *Game) doGenericStationSkill(sess *net.Session, input string, recipeType for i := range recipes { if recipes[i].ID == lastRecipeID { r := &recipes[i] - if g.canDoGenericRecipe(p, r, skill) { - g.startGenericRecipe(sess, p, r, 0, flagKey) + if g.canDoRecipe(p, r, skill) { + g.startRecipe(sess, p, r, 0, flagKey) return } break @@ -64,7 +64,7 @@ func (g *Game) doGenericStationSkill(sess *net.Session, input string, recipeType } } - available := g.availableGenericRecipes(p, recipes) + available := g.availableRecipes(p, recipes) if len(available) == 0 { sess.WriteLine("You don't have any materials to " + verbPast + ".") return @@ -72,14 +72,14 @@ func (g *Game) doGenericStationSkill(sess *net.Session, input string, recipeType optionKey := verbPast + "_all" if p.OptionBool(optionKey) && len(available) == 1 { - g.startGenericRecipe(sess, p, &available[0], 0, flagKey) + g.startRecipe(sess, p, &available[0], 0, flagKey) return } g.showProductionTable(sess, p, available, label, recipeType, flagKey, verbCap, false) } -func (g *Game) doGenericWithInput(sess *net.Session, p *player.Player, recipes []action.RecipeDef, input string, skill player.SkillName, flagKey, label, verbCap string) { +func (g *Game) doWithInput(sess *net.Session, p *player.Player, recipes []action.RecipeDef, input string, skill player.SkillName, flagKey, label, verbCap string) { qty, productName := parseQty(input) productName = strings.TrimSpace(productName) @@ -102,7 +102,7 @@ func (g *Game) doGenericWithInput(sess *net.Session, p *player.Player, recipes [ var available []action.RecipeDef for _, r := range matched { - if g.canDoGenericRecipe(p, &r, skill) { + if g.canDoRecipe(p, &r, skill) { available = append(available, r) } } @@ -113,14 +113,14 @@ func (g *Game) doGenericWithInput(sess *net.Session, p *player.Player, recipes [ } if len(available) == 1 { - g.startGenericRecipe(sess, p, &available[0], qty, flagKey) + g.startRecipe(sess, p, &available[0], qty, flagKey) return } g.showProductionTable(sess, p, available, label, label, flagKey, verbCap, false) } -func (g *Game) genericRecipeVisible(p *player.Player, r *action.RecipeDef) bool { +func (g *Game) recipeVisible(p *player.Player, r *action.RecipeDef) bool { if len(r.Station) > 0 { stID, _ := g.findStation(p.RoomID, r.Station) if stID == "" { @@ -133,7 +133,7 @@ func (g *Game) genericRecipeVisible(p *player.Player, r *action.RecipeDef) bool return true } -func (g *Game) canDoGenericRecipe(p *player.Player, r *action.RecipeDef, skill player.SkillName) bool { +func (g *Game) canDoRecipe(p *player.Player, r *action.RecipeDef, skill player.SkillName) bool { if p.Level(skill) < r.Level { return false } @@ -152,7 +152,7 @@ func (g *Game) canDoGenericRecipe(p *player.Player, r *action.RecipeDef, skill p return true } -func (g *Game) availableGenericRecipes(p *player.Player, allRecipes []action.RecipeDef) []action.RecipeDef { +func (g *Game) availableRecipes(p *player.Player, allRecipes []action.RecipeDef) []action.RecipeDef { var result []action.RecipeDef for _, r := range allRecipes { if r.HasAllItemsQty(p.CountItem) { @@ -162,7 +162,7 @@ func (g *Game) availableGenericRecipes(p *player.Player, allRecipes []action.Rec return result } -func (g *Game) startGenericRecipe(sess *net.Session, p *player.Player, recipe *action.RecipeDef, count int, flagKey string) { +func (g *Game) startRecipe(sess *net.Session, p *player.Player, recipe *action.RecipeDef, count int, flagKey string) { p.EnsureFlags() p.Flags[flagKey] = recipe.ID g.AccountStore.SaveCharacter(p) @@ -170,7 +170,7 @@ func (g *Game) startGenericRecipe(sess *net.Session, p *player.Player, recipe *a g.startProductionFromRecipe(sess, p, recipe, count) } -func (g *Game) findCraftRecipesForItems(p *player.Player, itemAID, itemBID string) []action.RecipeDef { +func (g *Game) findCraftRecipes(p *player.Player, itemAID, itemBID string) []action.RecipeDef { allRecipes, err := g.RecipeStore.LoadAll() if err != nil { return nil diff --git a/internal/game/cmd_description.go b/internal/game/cmd_description.go index 5d0c5c1..1fc24ae 100644 --- a/internal/game/cmd_description.go +++ b/internal/game/cmd_description.go @@ -20,10 +20,10 @@ func (g *Game) doDescription(sess *net.Session) { } sess.WriteLine("") sess.Write("Enter a new description (or press enter to keep current): ") - sess.State = net.StateChangeDescription + sess.State = net.StateChangeDesc } -func (g *Game) handleDescriptionChange(sess *net.Session, input string) { +func (g *Game) handleDescChange(sess *net.Session, input string) { if input != "" { p := sess.Player p.Description = input diff --git a/internal/game/cmd_drop.go b/internal/game/cmd_drop.go index 551b014..ca3c244 100644 --- a/internal/game/cmd_drop.go +++ b/internal/game/cmd_drop.go @@ -194,7 +194,7 @@ func (g *Game) executeDrop(sess *net.Session, args []string, rawInput string) { } } -func (g *Game) handleDropAllConfirm(sess *net.Session, input string) { +func (g *Game) handleDropAll(sess *net.Session, input string) { p := sess.Player if p == nil { sess.State = net.StateGame diff --git a/internal/game/cmd_fletch.go b/internal/game/cmd_fletch.go index e218dca..5a4bc19 100644 --- a/internal/game/cmd_fletch.go +++ b/internal/game/cmd_fletch.go @@ -184,7 +184,7 @@ func (g *Game) availableFletchRecipes(p *player.Player, allFletch []action.Recip return result } -func (g *Game) findFletchRecipesForItems(p *player.Player, itemAID, itemBID string) []action.RecipeDef { +func (g *Game) findFletchRecipes(p *player.Player, itemAID, itemBID string) []action.RecipeDef { allRecipes, err := g.RecipeStore.LoadAll() if err != nil { return nil diff --git a/internal/game/cmd_get.go b/internal/game/cmd_get.go index f3485fe..30bc8c2 100644 --- a/internal/game/cmd_get.go +++ b/internal/game/cmd_get.go @@ -77,7 +77,7 @@ func (g *Game) doGet(sess *net.Session, input string) { for i := 0; i < 28; i++ { slot := p.InvSlot(i) if slot != nil && slot.ItemID == itemID { - if _, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, qty, p.Name); !ok { + if _, ok := g.World.RemoveReservedItem(p.RoomID, itemID, qty, p.Name); !ok { sess.WriteLine("That's not yours!") return } @@ -92,7 +92,7 @@ func (g *Game) doGet(sess *net.Session, input string) { sess.WriteLine("Your inventory is full.") return } - if _, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, qty, p.Name); !ok { + if _, ok := g.World.RemoveReservedItem(p.RoomID, itemID, qty, p.Name); !ok { sess.WriteLine("That's not yours!") return } @@ -112,7 +112,7 @@ func (g *Game) doGet(sess *net.Session, input string) { if fs == -1 { break } - if _, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, 1, p.Name); !ok { + if _, ok := g.World.RemoveReservedItem(p.RoomID, itemID, 1, p.Name); !ok { break } p.SetInvSlot(fs, &player.InventorySlot{ItemID: itemID, Quantity: 1}) @@ -193,7 +193,7 @@ func (g *Game) doGetAllNamed(sess *net.Session, input string) { for i := 0; i < 28; i++ { slot := p.InvSlot(i) if slot != nil && slot.ItemID == itemID { - removed, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, wanted, p.Name) + removed, ok := g.World.RemoveReservedItem(p.RoomID, itemID, wanted, p.Name) if !ok { sess.WriteLine("That's not yours!") return @@ -209,7 +209,7 @@ func (g *Game) doGetAllNamed(sess *net.Session, input string) { sess.WriteLine("Your inventory is full.") return } - removed, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, wanted, p.Name) + removed, ok := g.World.RemoveReservedItem(p.RoomID, itemID, wanted, p.Name) if !ok { sess.WriteLine("That's not yours!") return @@ -230,7 +230,7 @@ func (g *Game) doGetAllNamed(sess *net.Session, input string) { if fs == -1 { break } - _, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, 1, p.Name) + _, ok := g.World.RemoveReservedItem(p.RoomID, itemID, 1, p.Name) if !ok { break } @@ -266,7 +266,7 @@ func (g *Game) doGetAll(sess *net.Session) { continue } if itemID == "credits" { - taken, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, 999999, p.Name) + taken, ok := g.World.RemoveReservedItem(p.RoomID, itemID, 999999, p.Name) if !ok { continue } @@ -286,7 +286,7 @@ func (g *Game) doGetAll(sess *net.Session) { for i := 0; i < 28; i++ { slot := p.InvSlot(i) if slot != nil && slot.ItemID == itemID { - _, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, qty, p.Name) + _, ok := g.World.RemoveReservedItem(p.RoomID, itemID, qty, p.Name) if !ok { qty = 0 stacked = true @@ -313,7 +313,7 @@ func (g *Game) doGetAll(sess *net.Session) { g.AccountStore.SaveCharacter(p) return } - _, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, qty, p.Name) + _, ok := g.World.RemoveReservedItem(p.RoomID, itemID, qty, p.Name) if !ok { break } @@ -339,7 +339,7 @@ func (g *Game) doGetAll(sess *net.Session) { } take := 1 - _, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, take, p.Name) + _, ok := g.World.RemoveReservedItem(p.RoomID, itemID, take, p.Name) if !ok { break } @@ -364,7 +364,7 @@ func (g *Game) doGetAll(sess *net.Session) { } func (g *Game) pickupCredits(sess *net.Session, p *player.Player, itemID string) { - qty, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, 999999, p.Name) + qty, ok := g.World.RemoveReservedItem(p.RoomID, itemID, 999999, p.Name) if !ok { sess.WriteLine("That's not yours!") return diff --git a/internal/game/cmd_jack.go b/internal/game/cmd_jack.go index 16f8b2a..f33ea85 100644 --- a/internal/game/cmd_jack.go +++ b/internal/game/cmd_jack.go @@ -52,7 +52,7 @@ func (g *Game) doJack(sess *net.Session, target string) { } g.cancelAction(p) - g.cancelBackgroundAction(p) + g.cancelBgAction(p) minigame := tDef.Minigame() display := minigame.Init(hackLevel) @@ -68,13 +68,7 @@ func (g *Game) doJack(sess *net.Session, target string) { p.ActionState = &ActionState{Type: ActionHacking, TargetName: tDef.Name} sess.State = net.StateHacking - if g.Hub != nil { - for _, other := range g.Hub.PlayersInRoom(p.RoomID) { - if other != sess { - other.WriteLine(fmt.Sprintf("\n%s jacks into a terminal.", p.Name)) - } - } - } + g.broadcastAction(sess, "\n%s jacks into a terminal.", p.Name) sess.WriteLine(display) sess.Write("\nhack> ") diff --git a/internal/game/cmd_look.go b/internal/game/cmd_look.go index 6913333..505f7a6 100644 --- a/internal/game/cmd_look.go +++ b/internal/game/cmd_look.go @@ -48,7 +48,7 @@ func (g *Game) showRoomDescription(sess *net.Session, p *player.Player, room *wo roomDescSpec := g.resolveColor(sess, "room_desc") descLines := make([]string, len(rawLines)) for i, l := range rawLines { - descLines[i] = color.ExpandTagsWithDefault(mode, roomDescSpec, l) + descLines[i] = color.ExpandTagsDefault(mode, roomDescSpec, l) } wroteDesc := false if p.OptionString("tiny_map") != "off" { @@ -546,7 +546,7 @@ func (g *Game) doLookTarget(sess *net.Session, input string) { } } - farmSuffix := g.farmLookSuffixForObj(p, st.DefID, st.Index) + farmSuffix := g.farmObjSuffix(p, st.DefID, st.Index) if farmSuffix != "" { sess.WriteLine(farmSuffix) } diff --git a/internal/game/cmd_mix.go b/internal/game/cmd_mix.go index 7bf1f7b..337bb18 100644 --- a/internal/game/cmd_mix.go +++ b/internal/game/cmd_mix.go @@ -3,7 +3,6 @@ package game import ( "strings" - "thehouseoficarus/internal/action" "thehouseoficarus/internal/net" "thehouseoficarus/internal/player" ) @@ -13,110 +12,5 @@ func (g *Game) executeMix(sess *net.Session, args []string, rawInput string) { } func (g *Game) doMix(sess *net.Session, input string) { - p := sess.Player - g.cancelAction(p) - - allRecipes, err := g.RecipeStore.LoadAll() - if err != nil { - sess.WriteLine("Error loading recipes.") - return - } - - if input == "" { - g.showMixMenu(sess, p, allRecipes) - return - } - - qty, itemName := parseQty(input) - _ = qty - - var matched []action.RecipeDef - for _, r := range allRecipes { - if r.Type != "pharmacy" { - continue - } - outDef, _ := g.ItemStore.Load(r.Output) - name := r.Output - if outDef != nil { - name = outDef.Name - } - if action.WordPrefixMatch(itemName, name) { - matched = append(matched, r) - } - } - - if len(matched) == 0 { - sess.WriteLine("You can't mix that.") - return - } - - var available []action.RecipeDef - for _, r := range matched { - if r.HasAllItems(p.HasItem) && p.Level(player.Pharmacy) >= r.Level { - available = append(available, r) - } - } - - if len(available) == 0 { - sess.WriteLine("You don't have the materials for that.") - return - } - - if len(available) == 1 { - g.promptHowMany(sess, available[0].ID) - return - } - - sess.State = net.StateRecipeChoice - var names []string - for _, r := range available { - names = append(names, g.recipeName(sess, &r)) - } - g.showMenuTable(sess, "What would you like to mix?", names) - sess.PendingMenu = recipeMenuData(recipeDefIDs(available)) -} - -func (g *Game) showMixMenu(sess *net.Session, p *player.Player, allRecipes []action.RecipeDef) { - seen := make(map[string]bool) - var entries []recipeEntry - - for _, r := range allRecipes { - if r.Type != "pharmacy" { - continue - } - if seen[r.ID] { - continue - } - if !r.HasAllItems(p.HasItem) { - continue - } - seen[r.ID] = true - entries = append(entries, recipeEntry{g.recipeName(sess, &r), r}) - } - - if len(entries) == 0 { - sess.WriteLine("You don't have anything you can mix.") - return - } - - if len(entries) == 1 { - if p.OptionBool("mix_all") { - g.startProductionFromRecipe(sess, p, &entries[0].Recipe, 0) - return - } - g.promptHowMany(sess, entries[0].Recipe.ID) - return - } - - sess.State = net.StateRecipeChoice - var names []string - for _, e := range entries { - names = append(names, e.ItemName) - } - g.showMenuTable(sess, "What would you like to mix?", names) - ids := make([]string, len(entries)) - for i, e := range entries { - ids[i] = e.Recipe.ID - } - sess.PendingMenu = recipeMenuData(ids) + g.doStationSkill(sess, input, "pharmacy", player.Pharmacy, "last_mix", "Mixing", "mix", "Mix") } diff --git a/internal/game/cmd_quit.go b/internal/game/cmd_quit.go index 46403f4..187d766 100644 --- a/internal/game/cmd_quit.go +++ b/internal/game/cmd_quit.go @@ -1,8 +1,6 @@ package game import ( - "fmt" - "thehouseoficarus/internal/combat" "thehouseoficarus/internal/engine" "thehouseoficarus/internal/net" @@ -24,13 +22,7 @@ func (g *Game) doQuit(sess *net.Session) { p.ActionState = &ActionState{Type: ActionResting} g.cancelRest(p.Name) - if g.Hub != nil { - for _, other := range g.Hub.PlayersInRoom(p.RoomID) { - if other != sess && other.Player != nil { - other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s sits down to rest.", p.Name))) - } - } - } + g.broadcastAction(sess, "\n%s sits down to rest.", p.Name) ticksLeft := 10 id := g.Ticks.Subscribe(engine.ToTicks(1), func() bool { diff --git a/internal/game/cmd_registry.go b/internal/game/cmd_registry.go index c4f7700..ea741d9 100644 --- a/internal/game/cmd_registry.go +++ b/internal/game/cmd_registry.go @@ -149,7 +149,7 @@ func (g *Game) executeCommand(sess *net.Session, cmd string, args []string, rawI switch a { case "gather": if len(args) == 0 { - target := g.resolveDefaultTarget(p.RoomID, cmd) + target := g.defaultTarget(p.RoomID, cmd) if target == "" { sess.WriteLine(fmt.Sprintf("%s what?", cmd)) return @@ -176,7 +176,7 @@ func (g *Game) executeGather(sess *net.Session, args []string, rawInput string) p := sess.Player g.cancelAction(p) if len(args) == 0 { - target := g.resolveDefaultTarget(p.RoomID, verb) + target := g.defaultTarget(p.RoomID, verb) if target == "" { sess.WriteLine(fmt.Sprintf("%s what?", verb)) return diff --git a/internal/game/cmd_smelt.go b/internal/game/cmd_smelt.go index a72cf53..f1ff4e2 100644 --- a/internal/game/cmd_smelt.go +++ b/internal/game/cmd_smelt.go @@ -53,69 +53,29 @@ func (g *Game) doSmelt(sess *net.Session, input string) { if r.Type != "smelting" || !stationMatch(stationDefID, r.Station) || !r.MatchesEntry(itemID) { continue } - if !r.HasAllItemsQty(p.CountItem) || p.Level(player.SkillName(r.EffectiveSkill())) < r.Level { + if !r.HasAllItemsQty(p.CountItem) || p.Level(player.SkillName(r.EffectiveSkill())) < r.Level { continue } recipes = append(recipes, r) } - if len(recipes) == 0 { - sess.WriteLine("You can't smelt that.") - return - } - - if len(recipes) == 1 { - g.promptHowMany(sess, recipes[0].ID) - return - } - - sess.PendingMenu = recipeMenuData(recipeDefIDs(recipes)) - sess.State = net.StateRecipeChoice - var names []string - for _, r := range recipes { - names = append(names, g.recipeName(sess, &r)) - } - g.showMenuTable(sess, "What would you like to smelt?", names) + g.showRecipeChoice(sess, p, recipes, "You can't smelt that.", "What would you like to smelt?", "") } func (g *Game) showSmeltMenu(sess *net.Session, p *player.Player, allRecipes []action.RecipeDef, stationDefID, stationName string) { seen := make(map[string]bool) - var entries []recipeEntry + var recipes []action.RecipeDef for _, r := range allRecipes { if r.Type != "smelting" || !stationMatch(stationDefID, r.Station) { continue } - if seen[r.ID] || !r.HasAllItemsQty(p.CountItem) || p.Level(player.SkillName(r.EffectiveSkill())) < r.Level { + if seen[r.ID] || !r.HasAllItemsQty(p.CountItem) || p.Level(player.SkillName(r.EffectiveSkill())) < r.Level { continue } seen[r.ID] = true - entries = append(entries, recipeEntry{g.recipeName(sess, &r), r}) - } - - if len(entries) == 0 { - sess.WriteLine("You don't have anything you can smelt.") - return - } - - if len(entries) == 1 { - if p.OptionBool("smelt_all") { - g.startProductionFromRecipe(sess, p, &entries[0].Recipe, 0) - return - } - g.promptHowMany(sess, entries[0].Recipe.ID) - return + recipes = append(recipes, r) } - sess.State = net.StateRecipeChoice - var names []string - for _, e := range entries { - names = append(names, e.ItemName) - } - g.showMenuTable(sess, "What would you like to smelt?", names) - ids := make([]string, len(entries)) - for i, e := range entries { - ids[i] = e.Recipe.ID - } - sess.PendingMenu = recipeMenuData(ids) + g.showRecipeChoice(sess, p, recipes, "You don't have anything you can smelt.", "What would you like to smelt?", "smelt_all") } diff --git a/internal/game/cmd_smith.go b/internal/game/cmd_smith.go index a2eaad7..c7a32cd 100644 --- a/internal/game/cmd_smith.go +++ b/internal/game/cmd_smith.go @@ -63,7 +63,7 @@ func (g *Game) doSmith(sess *net.Session, input string) { if len(barTypes) == 1 { if p.OptionBool("smith_all") { - if recipe := g.findSingleSmithRecipe(p, barTypes[0], allRecipes); recipe != nil { + if recipe := g.findSmithRecipe(p, barTypes[0], allRecipes); recipe != nil { g.startProductionFromRecipe(sess, p, recipe, 0) return } @@ -148,7 +148,7 @@ func barMenuData(barIDs []string) []map[string]string { return out } -func (g *Game) findSingleSmithRecipe(p *player.Player, barID string, allRecipes []action.RecipeDef) *action.RecipeDef { +func (g *Game) findSmithRecipe(p *player.Player, barID string, allRecipes []action.RecipeDef) *action.RecipeDef { var makeable []*action.RecipeDef skillLevel := p.Level(player.Smithing) for i, r := range allRecipes { diff --git a/internal/game/cmd_trigger_combat.go b/internal/game/cmd_trigger_combat.go index 62c5083..444f80d 100644 --- a/internal/game/cmd_trigger_combat.go +++ b/internal/game/cmd_trigger_combat.go @@ -23,7 +23,7 @@ func (g *Game) scienceAttack(sess *net.Session, p *player.Player, mob *world.Mob g.consumeJunkCost(p, mod) - equipSciBonus := g.totalEquipScienceAttack(p) + equipSciBonus := g.totalScienceAttack(p) attRoll := (p.Level(player.Science) + g.buffLevelBonus(p, "science") + 8) * (equipSciBonus + 64) mobSciDef := mob.ScienceDefense diff --git a/internal/game/cmd_trigger_enchant.go b/internal/game/cmd_trigger_enchant.go index 535616b..a1a6950 100644 --- a/internal/game/cmd_trigger_enchant.go +++ b/internal/game/cmd_trigger_enchant.go @@ -41,13 +41,9 @@ func (g *Game) triggerEnchant(sess *net.Session, p *player.Player, mod *ModDef, g.cancelAction(p) } - g.consumeJunkCost(p, mod) - inv.ItemID = outputID - sciXP := mod.BaseXP - g.awardSkillXP(sess, p, player.Science, sciXP) - g.AccountStore.SaveCharacter(p) + g.triggerModReward(sess, p, mod, 1.0) outputDef, _ := g.ItemStore.Load(outputID) outputName := outputID @@ -61,9 +57,6 @@ func (g *Game) triggerEnchant(sess *net.Session, p *player.Player, mod *ModDef, } sess.WriteLine(fmt.Sprintf("You enchant the %s and it becomes a %s!", inputName, outputName)) - if p.OptionBool("xp_drops") { - sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf("+%dxp sci", sciXP))) - } } func (g *Game) triggerChipBolts(sess *net.Session, p *player.Player, mod *ModDef, chip chipEntry) { @@ -82,8 +75,6 @@ func (g *Game) triggerChipBolts(sess *net.Session, p *player.Player, mod *ModDef g.cancelAction(p) } - g.consumeJunkCost(p, mod) - p.RemoveItem(chip.Input, chip.Qty) placed := false for i := 0; i < 28; i++ { @@ -99,9 +90,7 @@ func (g *Game) triggerChipBolts(sess *net.Session, p *player.Player, mod *ModDef p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: chip.Output, Quantity: chip.Qty}) } - sciXP := mod.BaseXP - g.awardSkillXP(sess, p, player.Science, sciXP) - g.AccountStore.SaveCharacter(p) + g.triggerModReward(sess, p, mod, 1.0) inputDef, _ := g.ItemStore.Load(chip.Input) name := chip.Input @@ -110,7 +99,4 @@ func (g *Game) triggerChipBolts(sess *net.Session, p *player.Player, mod *ModDef } sess.WriteLine(fmt.Sprintf("You chip %d %s with arcane circuitry.", chip.Qty, name)) - if p.OptionBool("xp_drops") { - sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf("+%dxp sci", sciXP))) - } } diff --git a/internal/game/cmd_trigger_transport.go b/internal/game/cmd_trigger_transport.go index 2ca8db2..2354c27 100644 --- a/internal/game/cmd_trigger_transport.go +++ b/internal/game/cmd_trigger_transport.go @@ -18,14 +18,7 @@ func (g *Game) triggerTransport(sess *net.Session, p *player.Player, mod *ModDef g.cancelAction(p) } - g.consumeJunkCost(p, mod) - - sciXP := mod.BaseXP - g.awardSkillXP(sess, p, player.Science, sciXP) - if p.OptionBool("xp_drops") { - sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf("+%dxp sci", sciXP))) - } - g.AccountStore.SaveCharacter(p) + g.triggerModReward(sess, p, mod, 1.0) if g.Hub != nil { for _, other := range g.Hub.PlayersInRoom(p.RoomID) { diff --git a/internal/game/cmd_trigger_utility.go b/internal/game/cmd_trigger_utility.go index efb23e7..ce59975 100644 --- a/internal/game/cmd_trigger_utility.go +++ b/internal/game/cmd_trigger_utility.go @@ -14,7 +14,7 @@ func (g *Game) triggerUtility(sess *net.Session, p *player.Player, mod *ModDef, case "low_process", "high_process": g.triggerProcessing(sess, p, mod, targetArg) case "bones_to_nutrients": - g.triggerBonesToNutrients(sess, p, mod) + g.triggerBonesToNuts(sess, p, mod) case "em_grab": g.triggerEmGrab(sess, p, mod, targetArg) case "superheat": @@ -51,8 +51,6 @@ func (g *Game) triggerProcessing(sess *net.Session, p *player.Player, mod *ModDe g.cancelAction(p) } - g.consumeJunkCost(p, mod) - creditValue := itemDef.Value if mod.ID == "low_process" { creditValue = itemDef.Value / 2 @@ -69,19 +67,13 @@ func (g *Game) triggerProcessing(sess *net.Session, p *player.Player, mod *ModDe p.Credits += creditValue - sciXP := mod.BaseXP - g.awardSkillXP(sess, p, player.Science, sciXP) - g.AccountStore.SaveCharacter(p) + g.triggerModReward(sess, p, mod, 1.0) sess.WriteLine(fmt.Sprintf("You process the %s. You receive %s credits.", itemDef.Name, g.colorize(sess, "credits_pickup", fmt.Sprint(creditValue)))) - - if p.OptionBool("xp_drops") { - sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf("+%dxp sci", sciXP))) - } } -func (g *Game) triggerBonesToNutrients(sess *net.Session, p *player.Player, mod *ModDef) { +func (g *Game) triggerBonesToNuts(sess *net.Session, p *player.Player, mod *ModDef) { if combat.GetCombat(p.Name) != nil { sess.WriteLine("You can't do that during combat!") return @@ -97,8 +89,6 @@ func (g *Game) triggerBonesToNutrients(sess *net.Session, p *player.Player, mod g.cancelAction(p) } - g.consumeJunkCost(p, mod) - for i := 0; i < 28; i++ { slot := p.InvSlot(i) if slot != nil && slot.ItemID == "bones" { @@ -106,14 +96,9 @@ func (g *Game) triggerBonesToNutrients(sess *net.Session, p *player.Player, mod } } - sciXP := mod.BaseXP * boneCount - g.awardSkillXP(sess, p, player.Science, sciXP) - g.AccountStore.SaveCharacter(p) + g.triggerModReward(sess, p, mod, float64(boneCount)) sess.WriteLine(fmt.Sprintf("You convert %d bones into nutrient bars.", boneCount)) - if p.OptionBool("xp_drops") { - sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf("+%dxp sci", sciXP))) - } } func (g *Game) triggerEmGrab(sess *net.Session, p *player.Player, mod *ModDef, targetArg string) { @@ -150,8 +135,6 @@ func (g *Game) triggerEmGrab(sess *net.Session, p *player.Player, mod *ModDef, t g.cancelAction(p) } - g.consumeJunkCost(p, mod) - qty := groundItems[matchedID] g.World.RemoveGroundItem(p.RoomID, matchedID, qty) @@ -164,14 +147,9 @@ func (g *Game) triggerEmGrab(sess *net.Session, p *player.Player, mod *ModDef, t freeSlot := p.FirstFreeSlot() p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: matchedID, Quantity: qty}) - sciXP := mod.BaseXP - g.awardSkillXP(sess, p, player.Science, sciXP) - g.AccountStore.SaveCharacter(p) + g.triggerModReward(sess, p, mod, 1.0) sess.WriteLine(fmt.Sprintf("You magnetically pull the %s toward you.", name)) - if p.OptionBool("xp_drops") { - sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf("+%dxp sci", sciXP))) - } } func (g *Game) triggerSuperheat(sess *net.Session, p *player.Player, mod *ModDef, targetArg string) { @@ -224,8 +202,6 @@ func (g *Game) triggerSuperheat(sess *net.Session, p *player.Player, mod *ModDef g.cancelAction(p) } - g.consumeJunkCost(p, mod) - for _, e := range recipe.Consume { for _, itemID := range e.Items { qty := e.Quantity @@ -254,12 +230,11 @@ func (g *Game) triggerSuperheat(sess *net.Session, p *player.Player, mod *ModDef p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: recipe.Output, Quantity: outputQty}) } - sciXP := mod.BaseXP - g.awardSkillXP(sess, p, player.Science, sciXP) + var extraGains []xpGain if recipe.XP > 0 { - g.awardSkillXP(sess, p, player.SkillName(recipe.Skill), recipe.XP) + extraGains = []xpGain{{recipe.Skill, recipe.XP}} } - g.AccountStore.SaveCharacter(p) + g.triggerModReward(sess, p, mod, 1.0, extraGains...) outputDef, _ := g.ItemStore.Load(recipe.Output) outputName := recipe.Output @@ -273,13 +248,6 @@ func (g *Game) triggerSuperheat(sess *net.Session, p *player.Player, mod *ModDef } sess.WriteLine(fmt.Sprintf("You superheat the %s and produce a %s.", inputName, outputName)) - if p.OptionBool("xp_drops") { - parts := []string{fmt.Sprintf("+%dxp sci", sciXP)} - if recipe.XP > 0 { - parts = append(parts, fmt.Sprintf("+%dxp %s", recipe.XP, player.SkillAbbr[player.SkillName(recipe.Skill)])) - } - sess.WriteLine(g.colorize(sess, "xp", "("+strings.Join(parts, ", ")+")")) - } } func (g *Game) triggerPlankMake(sess *net.Session, p *player.Player, mod *ModDef, targetArg string) { @@ -320,8 +288,6 @@ func (g *Game) triggerPlankMake(sess *net.Session, p *player.Player, mod *ModDef g.cancelAction(p) } - g.consumeJunkCost(p, mod) - p.RemoveItem(inv.ItemID, 1) p.Credits -= info.cost @@ -332,16 +298,9 @@ func (g *Game) triggerPlankMake(sess *net.Session, p *player.Player, mod *ModDef } p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: info.plankID, Quantity: 1}) - sciXP := mod.BaseXP - g.awardSkillXP(sess, p, player.Science, sciXP) - g.awardSkillXP(sess, p, player.Construction, 10) - g.AccountStore.SaveCharacter(p) + g.triggerModReward(sess, p, mod, 1.0, xpGain{Skill: string(player.Construction), XP: 10}) sess.WriteLine(fmt.Sprintf("You convert the log into %s.", info.name)) - if p.OptionBool("xp_drops") { - parts := []string{fmt.Sprintf("+%dxp sci", sciXP), "+10xp con"} - sess.WriteLine(g.colorize(sess, "xp", "("+strings.Join(parts, ", ")+")")) - } return } @@ -360,8 +319,6 @@ func (g *Game) triggerPlankMake(sess *net.Session, p *player.Player, mod *ModDef g.cancelAction(p) } - g.consumeJunkCost(p, mod) - p.RemoveItem(logID, qty) p.Credits -= totalCost @@ -376,20 +333,12 @@ func (g *Game) triggerPlankMake(sess *net.Session, p *player.Player, mod *ModDef processed++ } - sciXP := mod.BaseXP * qty - g.awardSkillXP(sess, p, player.Science, sciXP) - conXP := 10 * qty - g.awardSkillXP(sess, p, player.Construction, conXP) - g.AccountStore.SaveCharacter(p) + g.triggerModReward(sess, p, mod, float64(qty), xpGain{Skill: string(player.Construction), XP: 10 * qty}) sess.WriteLine(fmt.Sprintf("You convert %d logs into %s for %d credits.", processed, info.name, totalCost)) if processed < qty { sess.WriteLine(fmt.Sprintf("Your inventory is full. The remaining %d planks fall to the ground.", qty-processed)) } - if p.OptionBool("xp_drops") { - parts := []string{fmt.Sprintf("+%dxp sci", sciXP), fmt.Sprintf("+%dxp con", conXP)} - sess.WriteLine(g.colorize(sess, "xp", "("+strings.Join(parts, ", ")+")")) - } return } diff --git a/internal/game/cmd_use.go b/internal/game/cmd_use.go index 4f830a8..e653c93 100644 --- a/internal/game/cmd_use.go +++ b/internal/game/cmd_use.go @@ -83,7 +83,7 @@ func (g *Game) useRoomObject(sess *net.Session, p *player.Player, input string) } recipes := g.stationRecipes(p, objSt.DefID) if len(recipes) > 0 { - g.showStationRecipeMenu(sess, p, recipes, def.Name) + g.showRecipeMenu(sess, p, recipes, def.Name) return true } bt := def.BehaviorType() @@ -105,13 +105,7 @@ func (g *Game) useRoomObject(sess *net.Session, p *player.Player, input string) g.applyNodeAction(sess, ui.Action) } p.ActionState = &ActionState{Type: ActionUsing, TargetName: def.Name} - if g.Hub != nil { - for _, other := range g.Hub.PlayersInRoom(p.RoomID) { - if other != sess && other.Player != nil { - other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s uses the %s.", p.Name, def.Name))) - } - } - } + g.broadcastAction(sess, "\n%s uses the %s.", p.Name, def.Name) return true } if len(def.UseInteractions) > 0 { @@ -160,7 +154,7 @@ func (g *Game) stationRecipes(p *player.Player, stationDefID string) []action.Re return results } -func (g *Game) showStationRecipeMenu(sess *net.Session, p *player.Player, recipes []action.RecipeDef, stationName string) { +func (g *Game) showRecipeMenu(sess *net.Session, p *player.Player, recipes []action.RecipeDef, stationName string) { if len(recipes) == 0 { sess.WriteLine(fmt.Sprintf("You don't have anything you can make at the %s.", stationName)) return @@ -320,8 +314,8 @@ func (g *Game) doUseItemOnTarget(sess *net.Session, p *player.Player, itemAName, itemBID := matchesB[0].ID - craftRecipes := g.findCraftRecipesForItems(p, itemAID, itemBID) - fletchRecipes := g.findFletchRecipesForItems(p, itemAID, itemBID) + craftRecipes := g.findCraftRecipes(p, itemAID, itemBID) + fletchRecipes := g.findFletchRecipes(p, itemAID, itemBID) if len(craftRecipes) > 0 && len(fletchRecipes) > 0 { var menu []map[string]string @@ -353,12 +347,12 @@ func (g *Game) doUseItemOnTarget(sess *net.Session, p *player.Player, itemAName, if len(craftRecipes) > 0 { var available []action.RecipeDef for _, r := range craftRecipes { - if g.canDoGenericRecipe(p, &r, player.Crafting) { + if g.canDoRecipe(p, &r, player.Crafting) { available = append(available, r) } } if len(available) == 1 { - g.startGenericRecipe(sess, p, &available[0], 0, "last_craft") + g.startRecipe(sess, p, &available[0], 0, "last_craft") return } if len(available) > 1 { @@ -429,7 +423,7 @@ func (g *Game) findCombineResults(sess *net.Session, p *player.Player, itemAID, } } } - if aEntry >= 0 && bEntry >= 0 && aEntry != bEntry && madeFromItemsAvailable(p, def.MadeFrom) { + if aEntry >= 0 && bEntry >= 0 && aEntry != bEntry && madeFromAvailable(p, def.MadeFrom) { matched = append(matched, def) } } @@ -444,7 +438,7 @@ func combineMenuData(defs []*object.ItemDef) []map[string]string { return out } -func madeFromItemsAvailable(p *player.Player, madeFrom []object.MadeFromEntry) bool { +func madeFromAvailable(p *player.Player, madeFrom []object.MadeFromEntry) bool { for _, entry := range madeFrom { found := false for _, id := range entry.Items { diff --git a/internal/game/core_login_account.go b/internal/game/core_login_account.go index 87f8c62..a2c1bd8 100644 --- a/internal/game/core_login_account.go +++ b/internal/game/core_login_account.go @@ -83,7 +83,7 @@ func (g *Game) handlePassword(sess *net.Session, input string) { g.showMenu(sess) } -func (g *Game) handleNewAccountPass(sess *net.Session, input string) { +func (g *Game) handleNewPass(sess *net.Session, input string) { input = strings.TrimSpace(input) if input == "" { sess.Conn.SetEcho(true) @@ -98,11 +98,11 @@ func (g *Game) handleNewAccountPass(sess *net.Session, input string) { return } sess.PendingPass = input - sess.State = net.StateNewAccountConfirm + sess.State = net.StateNewAccount sess.Write("Confirm password: ") } -func (g *Game) handleNewAccountConfirm(sess *net.Session, input string) { +func (g *Game) handleNewAccount(sess *net.Session, input string) { input = strings.TrimSpace(input) if input == "" { sess.Conn.SetEcho(true) diff --git a/internal/game/core_messages.go b/internal/game/core_messages.go deleted file mode 100644 index 1c9e85a..0000000 --- a/internal/game/core_messages.go +++ /dev/null @@ -1,13 +0,0 @@ -package game - -const ( - MsgErrLoadRecipes = "Error loading recipes." - MsgNoCombat = "You can't do that during combat!" - MsgNoDontHave = "You don't have any '%s'." - MsgNeverMind = "Never mind." - MsgSomethingWrongObject = "Something is wrong with this object." - MsgSomethingWrongThat = "Something is wrong with that." - MsgInventoryFull = "Your inventory is too full!" - MsgAmbiguous = "That's ambiguous, which one?" - MsgWhichOne = "Which one?" -) diff --git a/internal/game/core_production.go b/internal/game/core_production.go index 227c4ad..59517d5 100644 --- a/internal/game/core_production.go +++ b/internal/game/core_production.go @@ -18,7 +18,7 @@ import ( func (g *Game) startProduction(sess *net.Session, p *player.Player, recipe *action.RecipeDef, actionType, displayVerb, startMsg, endMsg string, count int) { g.cancelAction(p) - g.cancelBackgroundAction(p) + g.cancelBgAction(p) skill := recipe.EffectiveSkill() if skill != "" { @@ -65,13 +65,7 @@ func (g *Game) startProduction(sess *net.Session, p *player.Player, recipe *acti p.ActionState = &ActionState{Type: ActionProducing, TargetName: outputName, Verb: displayVerb} - if g.Hub != nil { - for _, other := range g.Hub.PlayersInRoom(p.RoomID) { - if other != sess && other.Player != nil { - other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s starts %s.", p.Name, displayVerb))) - } - } - } + g.broadcastAction(sess, "\n%s starts %s.", p.Name, displayVerb) } func (g *Game) startProductionFromRecipe(sess *net.Session, p *player.Player, recipe *action.RecipeDef, count int) { @@ -106,14 +100,9 @@ func (g *Game) startProductionFromRecipe(sess *net.Session, p *player.Player, re g.startProduction(sess, p, recipe, actionType, displayVerb, startMsg, endMsg, count) } -func (g *Game) loadProductionRecipe(recipeID string) *action.RecipeDef { - all, err := g.RecipeStore.LoadAll() - if err == nil { - for _, r := range all { - if r.ID == recipeID { - return &r - } - } +func (g *Game) loadRecipe(recipeID string) *action.RecipeDef { + if r := g.RecipeStore.GetByID(recipeID); r != nil { + return r } if strings.HasPrefix(recipeID, "combine_") { itemID := strings.TrimPrefix(recipeID, "combine_") @@ -132,7 +121,7 @@ func (g *Game) advanceProduction(sess *net.Session, p *player.Player) bool { endMsg, _ := p.Action.Data["end_msg"].(string) remaining, _ := p.Action.Data["remaining"].(int) - recipe := g.loadProductionRecipe(recipeID) + recipe := g.loadRecipe(recipeID) if recipe == nil { g.cancelAction(p) return false @@ -350,25 +339,19 @@ func (g *Game) handleRecipeChoice(sess *net.Session, input string) { func (g *Game) menuEntryName(entry map[string]string) string { if rid, ok := entry["recipe_id"]; ok { - all, _ := g.RecipeStore.LoadAll() - for _, r := range all { - if r.ID == rid { - if def, err := g.ItemStore.Load(r.Output); err == nil { - return def.Name - } - return r.Output + if r := g.RecipeStore.GetByID(rid); r != nil { + if def, err := g.ItemStore.Load(r.Output); err == nil { + return def.Name } + return r.Output } } if rid, ok := entry["fletch_recipe_id"]; ok { - all, _ := g.RecipeStore.LoadAll() - for _, r := range all { - if r.ID == rid { - if def, err := g.ItemStore.Load(r.Output); err == nil { - return def.Name - } - return r.Output + if r := g.RecipeStore.GetByID(rid); r != nil { + if def, err := g.ItemStore.Load(r.Output); err == nil { + return def.Name } + return r.Output } } if barID, ok := entry["bar_id"]; ok { @@ -399,12 +382,9 @@ func (g *Game) dispatchMenuEntry(sess *net.Session, entry map[string]string) { } if rid, ok := entry["fletch_recipe_id"]; ok { p := sess.Player - allRecipes, _ := g.RecipeStore.LoadAll() - for _, r := range allRecipes { - if r.ID == rid { - g.startFletchAction(sess, p, &r, 0) - return - } + if r := g.RecipeStore.GetByID(rid); r != nil { + g.startFletchAction(sess, p, r, 0) + return } g.reprompt(sess) return @@ -416,7 +396,7 @@ func (g *Game) dispatchMenuEntry(sess *net.Session, entry map[string]string) { g.reprompt(sess) } -func (g *Game) formatRecipeMaterials(r *action.RecipeDef) string { +func (g *Game) formatMaterials(r *action.RecipeDef) string { var parts []string for _, e := range r.Consume { itemName := e.Items[0] @@ -461,7 +441,7 @@ func (g *Game) showProductionTable(sess *net.Session, p *player.Player, recipes productName = fmt.Sprintf("%s x%d", productName, outputQty) } - matStr := g.formatRecipeMaterials(&r) + matStr := g.formatMaterials(&r) levelStr := fmt.Sprint(r.Level) numStr := fmt.Sprintf("%d", i+1) @@ -524,20 +504,11 @@ func (g *Game) handleProductChoice(sess *net.Session, input string) { input = strings.TrimSpace(input) - allRecipes, err := g.RecipeStore.LoadAll() - if err != nil { - g.reprompt(sess) - return - } - var recipes []action.RecipeDef for _, entry := range menuData { rid := entry["recipe_id"] - for _, r := range allRecipes { - if r.ID == rid { - recipes = append(recipes, r) - break - } + if r := g.RecipeStore.GetByID(rid); r != nil { + recipes = append(recipes, *r) } } @@ -587,10 +558,15 @@ func (g *Game) handleProductChoice(sess *net.Session, input string) { } } -func (g *Game) hasToolType(p *player.Player, toolType string) bool { +func (g *Game) findTool(p *player.Player, toolTypes []string) (toolSpeed float64, toolName string, found bool) { + toolSpeed = -1 if itemID, ok := p.Equipment[object.SlotMainHand]; ok { - if def, err := g.ItemStore.Load(itemID); err == nil && def.ToolType == toolType { - return true + if def, err := g.ItemStore.Load(itemID); err == nil { + for _, t := range toolTypes { + if def.ToolType == t { + return def.ToolSpeed, def.Name, true + } + } } } for i := 0; i < 28; i++ { @@ -598,11 +574,22 @@ func (g *Game) hasToolType(p *player.Player, toolType string) bool { if slot == nil { continue } - if def, err := g.ItemStore.Load(slot.ItemID); err == nil && def.ToolType == toolType { - return true + def, err := g.ItemStore.Load(slot.ItemID) + if err != nil { + continue + } + for _, t := range toolTypes { + if def.ToolType == t { + return def.ToolSpeed, def.Name, true + } } } - return false + return -1, "", false +} + +func (g *Game) hasToolType(p *player.Player, toolType string) bool { + _, _, found := g.findTool(p, []string{toolType}) + return found } type recipeEntry struct { ItemName string @@ -729,6 +716,37 @@ func (g *Game) showMenuTable(sess *net.Session, title string, names []string) { } } +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 (g *Game) showRecipeChoice(sess *net.Session, p *player.Player, recipes []action.RecipeDef, emptyMsg, title string, autoOption string) { + if len(recipes) == 0 { + sess.WriteLine(emptyMsg) + return + } + if len(recipes) == 1 { + if autoOption != "" && p.OptionBool(autoOption) { + g.startProductionFromRecipe(sess, p, &recipes[0], 0) + return + } + g.promptHowMany(sess, recipes[0].ID) + return + } + sess.State = net.StateRecipeChoice + sess.PendingMenu = recipeMenuData(recipeDefIDs(recipes)) + var names []string + for i := range recipes { + names = append(names, g.recipeName(sess, &recipes[i])) + } + g.showMenuTable(sess, title, names) +} + func (g *Game) handleHowMany(sess *net.Session, input string) { p := sess.Player recipeID := sess.PendingRecipeID @@ -758,17 +776,7 @@ func (g *Game) handleHowMany(sess *net.Session, input string) { } recipe = g.buildCombineRecipe(itemDef) } else { - all, err := g.RecipeStore.LoadAll() - if err != nil { - g.reprompt(sess) - return - } - for _, r := range all { - if r.ID == recipeID { - recipe = &r - break - } - } + recipe = g.RecipeStore.GetByID(recipeID) } if recipe == nil { diff --git a/internal/game/core_startup.go b/internal/game/core_startup.go index ab52762..ef33ff7 100644 --- a/internal/game/core_startup.go +++ b/internal/game/core_startup.go @@ -8,6 +8,7 @@ import ( "strings" "thehouseoficarus/internal/action" + "thehouseoficarus/internal/world" ) type StartupIssue struct { @@ -19,29 +20,29 @@ type StartupIssue struct { func (g *Game) ValidateStartup() []StartupIssue { var issues []StartupIssue - issues = append(issues, validateDuplicateIDs(g.DataDir, "items", "item")...) - issues = append(issues, validateDuplicateIDs(g.DataDir, "rooms", "room")...) - issues = append(issues, validateDuplicateIDs(g.DataDir, "mobs", "mob")...) - issues = append(issues, validateDuplicateIDs(g.DataDir, "objects", "object")...) - issues = append(issues, validateDuplicateIDs(g.DataDir, "drops", "drop table")...) - issues = append(issues, validateDuplicateIDs(g.DataDir, "recipes", "recipe")...) - issues = append(issues, validateDuplicateIDs(g.DataDir, "courses", "course")...) - issues = append(issues, validateDuplicateIDs(g.DataDir, "modules", "module")...) - issues = append(issues, validateDuplicateIDs(g.DataDir, "help", "help topic")...) - - issues = append(issues, validateRoomReferences(g)...) - issues = append(issues, validateMobReferences(g)...) - issues = append(issues, validateObjectReferences(g)...) - issues = append(issues, validateItemReferences(g)...) - issues = append(issues, validateRecipeReferences(g)...) - issues = append(issues, validateDropTableReferences(g)...) - issues = append(issues, validateCourseReferences(g)...) + issues = append(issues, validateIDs(g.DataDir, "items", "item")...) + issues = append(issues, validateIDs(g.DataDir, "rooms", "room")...) + issues = append(issues, validateIDs(g.DataDir, "mobs", "mob")...) + issues = append(issues, validateIDs(g.DataDir, "objects", "object")...) + issues = append(issues, validateIDs(g.DataDir, "drops", "drop table")...) + issues = append(issues, validateIDs(g.DataDir, "recipes", "recipe")...) + issues = append(issues, validateIDs(g.DataDir, "courses", "course")...) + issues = append(issues, validateIDs(g.DataDir, "modules", "module")...) + issues = append(issues, validateIDs(g.DataDir, "help", "help topic")...) + + issues = append(issues, validateRooms(g)...) + issues = append(issues, validateMobs(g)...) + issues = append(issues, validateObjects(g)...) + issues = append(issues, validateItems(g)...) + issues = append(issues, validateRecipes(g)...) + issues = append(issues, validateDropTables(g)...) + issues = append(issues, validateCourses(g)...) issues = append(issues, validateRoomWiring(g)...) return issues } -func validateDuplicateIDs(dataDir, subDir, label string) []StartupIssue { +func validateIDs(dataDir, subDir, label string) []StartupIssue { dir := filepath.Join(dataDir, subDir) dups := action.CheckDuplicateIDs(dir) var issues []StartupIssue @@ -60,7 +61,7 @@ func validateDuplicateIDs(dataDir, subDir, label string) []StartupIssue { return issues } -func validateRoomReferences(g *Game) []StartupIssue { +func validateRooms(g *Game) []StartupIssue { var issues []StartupIssue roomIndex := g.World.RoomIndex() itemIDs := g.ItemStore.IDSet() @@ -86,6 +87,16 @@ func validateRoomReferences(g *Game) []StartupIssue { } for dir, exit := range room.Exits { + switch dir { + case world.North, world.South, world.East, world.West, world.Up, world.Down: + default: + issues = append(issues, StartupIssue{ + Level: "ERROR", + Type: "reference", + Message: fmt.Sprintf("Room %d: invalid exit direction %q (only north/south/east/west/up/down supported)", id, dir), + }) + continue + } if exit.Room <= 0 { continue } @@ -159,7 +170,7 @@ func validateRoomReferences(g *Game) []StartupIssue { return issues } -func validateMobReferences(g *Game) []StartupIssue { +func validateMobs(g *Game) []StartupIssue { var issues []StartupIssue itemIDs := g.ItemStore.IDSet() dropIDs := dropTableIDSet(g.DataDir) @@ -233,7 +244,7 @@ func validateMobReferences(g *Game) []StartupIssue { return issues } -func validateObjectReferences(g *Game) []StartupIssue { +func validateObjects(g *Game) []StartupIssue { var issues []StartupIssue objIDs := g.ObjectStore.IDSet() itemIDs := g.ItemStore.IDSet() @@ -301,7 +312,7 @@ func validateObjectReferences(g *Game) []StartupIssue { } if obj.Gather != nil { - issues = append(issues, validateGatherConfig(fmt.Sprintf("Object %q: gather", id), obj.Gather, itemIDs, dropIDs)...) + issues = append(issues, validateGather(fmt.Sprintf("Object %q: gather", id), obj.Gather, itemIDs, dropIDs)...) } if obj.Talk != nil { @@ -316,7 +327,7 @@ func validateObjectReferences(g *Game) []StartupIssue { return issues } -func validateItemReferences(g *Game) []StartupIssue { +func validateItems(g *Game) []StartupIssue { var issues []StartupIssue itemIDs := g.ItemStore.IDSet() dropIDs := dropTableIDSet(g.DataDir) @@ -393,7 +404,7 @@ func validateItemReferences(g *Game) []StartupIssue { return issues } -func validateRecipeReferences(g *Game) []StartupIssue { +func validateRecipes(g *Game) []StartupIssue { var issues []StartupIssue itemIDs := g.ItemStore.IDSet() @@ -456,7 +467,7 @@ func validateRecipeReferences(g *Game) []StartupIssue { return issues } -func validateDropTableReferences(g *Game) []StartupIssue { +func validateDropTables(g *Game) []StartupIssue { var issues []StartupIssue dropIDs := dropTableIDSet(g.DataDir) itemIDs := g.ItemStore.IDSet() @@ -494,7 +505,7 @@ func validateDropTableReferences(g *Game) []StartupIssue { return issues } -func validateCourseReferences(g *Game) []StartupIssue { +func validateCourses(g *Game) []StartupIssue { var issues []StartupIssue roomIndex := g.World.RoomIndex() @@ -582,7 +593,7 @@ func validateRoomWiring(g *Game) []StartupIssue { return issues } -func validateGatherConfig(prefix string, cfg *action.GatherConfig, itemIDs map[string]bool, dropIDs map[string]bool) []StartupIssue { +func validateGather(prefix string, cfg *action.GatherConfig, itemIDs map[string]bool, dropIDs map[string]bool) []StartupIssue { var issues []StartupIssue if cfg.Bait != "" && !itemIDs[cfg.Bait] { diff --git a/internal/game/core_types.go b/internal/game/core_types.go index d41b18a..898078b 100644 --- a/internal/game/core_types.go +++ b/internal/game/core_types.go @@ -22,7 +22,7 @@ type xpGain struct { type deathDrop struct { itemID string quantity int - totalVal int + totalValue int isEquip bool equipSlot object.EquipSlot invSlot int diff --git a/internal/game/core_utils.go b/internal/game/core_utils.go index 409422e..c169580 100644 --- a/internal/game/core_utils.go +++ b/internal/game/core_utils.go @@ -104,6 +104,22 @@ func formatPickupList(sess *net.Session, picked []string) { } } +func (g *Game) broadcastAction(sess *net.Session, format string, args ...interface{}) { + if g.Hub == nil { + return + } + p := sess.Player + if p == nil { + return + } + msg := fmt.Sprintf(format, args...) + for _, other := range g.Hub.PlayersInRoom(p.RoomID) { + if other != sess && other.Player != nil { + other.WriteLine(g.colorize(other, "broadcast", msg)) + } + } +} + func (g *Game) newInventorySlot(itemID string, qty int) *player.InventorySlot { slot := &player.InventorySlot{ItemID: itemID, Quantity: qty} if def, err := g.ItemStore.Load(itemID); err == nil && def.MaxQuality > 0 { diff --git a/internal/game/game.go b/internal/game/game.go index 5d6e21e..ac8622e 100644 --- a/internal/game/game.go +++ b/internal/game/game.go @@ -47,7 +47,7 @@ type Game struct { WorldFlags map[string]any ColorConfig *config.ColorsConfig DataDir string - ValidationConfig config.StartupValidationConfig + ValidationConfig config.ValidationConfig restTimers map[string]uint64 charsMu sync.Mutex loggedInChars map[string]*net.Session @@ -60,7 +60,7 @@ type Game struct { hackingStates map[string]*hacking.Session } -func New(dataDir string, colorConfig *config.ColorsConfig, valConfig config.StartupValidationConfig) *Game { +func New(dataDir string, colorConfig *config.ColorsConfig, valConfig config.ValidationConfig) *Game { g := &Game{ World: world.New(dataDir), ObjectStore: object.NewObjectStore(dataDir), @@ -110,9 +110,9 @@ func (g *Game) HandleSession(sess *net.Session, input string) { case net.StatePassword: g.handlePassword(sess, input) case net.StateNewAccountPass: - g.handleNewAccountPass(sess, input) - case net.StateNewAccountConfirm: - g.handleNewAccountConfirm(sess, input) + g.handleNewPass(sess, input) + case net.StateNewAccount: + g.handleNewAccount(sess, input) case net.StateMenu: g.handleMenu(sess, input) case net.StateNewCharName: @@ -129,8 +129,8 @@ func (g *Game) HandleSession(sess *net.Session, input string) { g.handlePurgeAccount(sess, input) case net.StateGame: g.handleGameCommand(sess, input) - case net.StateChangeDescription: - g.handleDescriptionChange(sess, input) + case net.StateChangeDesc: + g.handleDescChange(sess, input) case net.StateTalk: g.handleTalkInput(sess, input) case net.StateShop: @@ -138,7 +138,7 @@ func (g *Game) HandleSession(sess *net.Session, input string) { case net.StateBank: g.handleBankInput(sess, input) case net.StateDropAllConfirm: - g.handleDropAllConfirm(sess, input) + g.handleDropAll(sess, input) case net.StateRecipeChoice: g.handleRecipeChoice(sess, input) case net.StateHowMany: diff --git a/internal/game/hacking/hacking_liars_dice.go b/internal/game/hacking/liars_dice.go index bcfbd40..bcfbd40 100644 --- a/internal/game/hacking/hacking_liars_dice.go +++ b/internal/game/hacking/liars_dice.go diff --git a/internal/game/hacking/hacking_mastermind.go b/internal/game/hacking/mastermind.go index 8d4eb1c..8d4eb1c 100644 --- a/internal/game/hacking/hacking_mastermind.go +++ b/internal/game/hacking/mastermind.go diff --git a/internal/game/hacking/hacking_wumpus.go b/internal/game/hacking/wumpus.go index 91b9243..91b9243 100644 --- a/internal/game/hacking/hacking_wumpus.go +++ b/internal/game/hacking/wumpus.go diff --git a/internal/game/sys_combat.go b/internal/game/sys_combat.go index 030bed1..702871c 100644 --- a/internal/game/sys_combat.go +++ b/internal/game/sys_combat.go @@ -31,7 +31,7 @@ func (g *Game) dropItemsOnDeath(p *player.Player) { items = append(items, deathDrop{ itemID: inv.ItemID, quantity: inv.Quantity, - totalVal: val, + totalValue: val, invSlot: slot, }) } @@ -44,7 +44,7 @@ func (g *Game) dropItemsOnDeath(p *player.Player) { items = append(items, deathDrop{ itemID: itemID, quantity: 1, - totalVal: val, + totalValue: val, isEquip: true, equipSlot: eqSlot, }) @@ -55,7 +55,7 @@ func (g *Game) dropItemsOnDeath(p *player.Player) { } sort.Slice(items, func(i, j int) bool { - return items[i].totalVal > items[j].totalVal + return items[i].totalValue > items[j].totalValue }) for i := 3; i < len(items); i++ { diff --git a/internal/game/sys_science.go b/internal/game/sys_science.go index e35105a..376d7c7 100644 --- a/internal/game/sys_science.go +++ b/internal/game/sys_science.go @@ -9,6 +9,7 @@ import ( "gopkg.in/yaml.v3" "thehouseoficarus/internal/action" + "thehouseoficarus/internal/net" "thehouseoficarus/internal/object" "thehouseoficarus/internal/player" ) @@ -182,7 +183,7 @@ func (g *Game) hasDeckEquipped(p *player.Player) bool { return def.WeaponType == object.WeaponScience } -func (g *Game) equippedProvidesJunk(p *player.Player) string { +func (g *Game) providesJunk(p *player.Player) string { itemID, ok := p.Equipment[object.SlotMainHand] if !ok { return "" @@ -206,7 +207,7 @@ func (g *Game) effectiveJunkCost(p *player.Player, mod *ModDef) map[string]int { delete(cost, "scrap_metal") } - providesJunk := g.equippedProvidesJunk(p) + providesJunk := g.providesJunk(p) if providesJunk != "" { delete(cost, providesJunk) } @@ -235,6 +236,31 @@ func (g *Game) consumeJunkCost(p *player.Player, mod *ModDef) bool { return true } +func (g *Game) triggerModReward(sess *net.Session, p *player.Player, mod *ModDef, multiplier float64, extraGains ...xpGain) int { + g.consumeJunkCost(p, mod) + + sciXP := int(float64(mod.BaseXP) * multiplier) + if sciXP < 1 { + sciXP = 1 + } + g.awardSkillXP(sess, p, player.Science, sciXP) + + for _, gain := range extraGains { + g.awardSkillXP(sess, p, player.SkillName(gain.Skill), gain.XP) + } + + g.AccountStore.SaveCharacter(p) + + if p.OptionBool("xp_drops") { + parts := []string{fmt.Sprintf("+%dxp %s", sciXP, player.SkillAbbr[player.Science])} + for _, gain := range extraGains { + parts = append(parts, fmt.Sprintf("+%dxp %s", gain.XP, player.SkillAbbr[player.SkillName(gain.Skill)])) + } + sess.WriteLine(g.colorize(sess, "xp", "("+strings.Join(parts, ", ")+")")) + } + return sciXP +} + func (g *Game) junkCostString(p *player.Player, mod *ModDef) string { cost := g.effectiveJunkCost(p, mod) delete(cost, "scrap_metal") @@ -259,7 +285,7 @@ func (g *Game) junkCostString(p *player.Player, mod *ModDef) string { return strings.Join(parts, ", ") } -func (g *Game) totalEquipScienceAttack(p *player.Player) int { +func (g *Game) totalScienceAttack(p *player.Player) int { total := 0 for _, itemID := range p.Equipment { def, err := g.ItemStore.Load(itemID) |
