diff options
Diffstat (limited to 'internal/game')
93 files changed, 360 insertions, 366 deletions
diff --git a/internal/game/account_registration_regression_test.go b/internal/game/account_registration_regression_test.go index 5ecd254..42e7ad1 100644 --- a/internal/game/account_registration_regression_test.go +++ b/internal/game/account_registration_regression_test.go @@ -14,7 +14,7 @@ import ( func TestPendingRegistrationCannotOverwriteExistingAccount(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") a := &net.Session{Conn: newChanConn(), State: net.StateAccountName} diff --git a/internal/game/act_agility.go b/internal/game/act_agility.go index 68ecaab..fd16102 100644 --- a/internal/game/act_agility.go +++ b/internal/game/act_agility.go @@ -140,7 +140,7 @@ func (g *Game) completeObstacle(sess *net.Session, p *player.Player, d *behavior g.teleportPlayer(sess, p, nextRoom) } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } func (g *Game) obstacleFail(sess *net.Session, p *player.Player, d *behavior.ObstacleData) { @@ -162,7 +162,7 @@ func (g *Game) obstacleFail(sess *net.Session, p *player.Player, d *behavior.Obs sess.WriteLine(g.colorize(sess, "damage_taken", fmt.Sprintf("You take %s damage. HP: %d/%d", g.dmgDisplay(sess, damage), p.HP, p.MaxHP()))) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) g.cancelAction(p) g.teleportPlayer(sess, p, onFailRoom) } diff --git a/internal/game/act_burn.go b/internal/game/act_burn.go index b310145..2d03ac4 100644 --- a/internal/game/act_burn.go +++ b/internal/game/act_burn.go @@ -191,7 +191,7 @@ func (g *Game) advanceBurn(sess *net.Session, p *player.Player) { } p.RemoveItem(itemID, 1) g.World.AddGroundItem(p.RoomID, itemID, 1) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) sess.WriteLine(g.colorize(sess, "fire", fmt.Sprintf("You drop the %s and begin to make a fire...", g.itemColorize(sess, logDef, logDef.Name)))) g.broadcastDrop(p, itemID, logDef.Name) @@ -234,7 +234,7 @@ func (g *Game) advanceBurn(sess *net.Session, p *player.Player) { g.awardSkillXP(sess, p, player.Firemaking, xp) } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) g.cancelAction(p) msg := g.colorize(sess, "fire", "You manage to get a fire going!") @@ -286,7 +286,7 @@ func (g *Game) advanceStoke(sess *net.Session, p *player.Player) { g.awardSkillXP(sess, p, player.Firemaking, xp) } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) msg := g.colorize(sess, "fire", fmt.Sprintf("You throw %s onto the fire.", g.itemColorize(sess, logDef, name))) if xp > 0 && p.OptionBool("xp_drops") { @@ -353,7 +353,7 @@ func (g *Game) consumeFireTool(sess *net.Session, p *player.Player, toolItemID s return false } slot.Quality-- - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) return true } } @@ -363,7 +363,7 @@ func (g *Game) consumeFireTool(sess *net.Session, p *player.Player, toolItemID s return false } p.EquipQuality[toolItemID] = q - 1 - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) return true } } @@ -371,13 +371,13 @@ func (g *Game) consumeFireTool(sess *net.Session, p *player.Player, toolItemID s if def != nil && def.Stackable { if p.HasItem(toolItemID) { p.RemoveItem(toolItemID, 1) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) return true } for slot, itemID := range p.Equipment { if itemID == toolItemID { delete(p.Equipment, slot) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) return true } } diff --git a/internal/game/act_clean.go b/internal/game/act_clean.go index f5f86d6..f995ae1 100644 --- a/internal/game/act_clean.go +++ b/internal/game/act_clean.go @@ -69,7 +69,7 @@ func (g *Game) advanceClean(sess *net.Session, p *player.Player) { if craft.XP > 0 { g.awardSkillXP(sess, p, player.Pharmacy, craft.XP) } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) msg := g.resolveCraftMessage(sess, craft.SuccessMessage, "You clean the %i1.", craft, matchItem.ID, nil, p.HasItem) if p.OptionBool("xp_drops") && craft.XP > 0 { diff --git a/internal/game/act_combine.go b/internal/game/act_combine.go index 3d744db..26d85c6 100644 --- a/internal/game/act_combine.go +++ b/internal/game/act_combine.go @@ -129,7 +129,7 @@ func (g *Game) advanceCombine(sess *net.Session, p *player.Player) { p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: item.ID, Quantity: outputQty}) } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) if d.Remaining > 0 { d.Remaining-- diff --git a/internal/game/act_effects.go b/internal/game/act_effects.go index a49bd84..4438eb6 100644 --- a/internal/game/act_effects.go +++ b/internal/game/act_effects.go @@ -113,14 +113,14 @@ func (g *Game) applyStep(sess *net.Session, p *player.Player, step *behavior.Ste // 5. set_player_flags (and globals, folded in so the SetAll happens once). if len(step.SetGlobalFlags) > 0 || len(step.SetPlayerFlags) > 0 { g.applyFlagMutations(p, step.SetGlobalFlags, step.SetPlayerFlags) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } // 6. take_item if step.TakeItem != "" { if p.HasItem(step.TakeItem) { p.RemoveItem(step.TakeItem, 1) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } } @@ -133,7 +133,7 @@ func (g *Game) applyStep(sess *net.Session, p *player.Player, step *behavior.Ste } } else { p.SetInvSlot(slot, &player.InventorySlot{ItemID: step.GiveItem, Quantity: 1}) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } } @@ -153,7 +153,7 @@ func (g *Game) applyStep(sess *net.Session, p *player.Player, step *behavior.Ste if maxHP := p.MaxHP(); p.HP > maxHP { p.HP = maxHP } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) if sess != nil { sess.WriteLine(fmt.Sprintf("You regain %d hitpoints.", step.Heal)) } @@ -164,13 +164,13 @@ func (g *Game) applyStep(sess *net.Session, p *player.Player, step *behavior.Ste if step.Credits < 0 { if p.Credits >= -step.Credits { p.Credits += step.Credits - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } else if sess != nil { sess.WriteLine(fmt.Sprintf("You don't have enough credits. (Need %d, have %d)", -step.Credits, p.Credits)) } } else { p.Credits += step.Credits - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } } @@ -195,7 +195,7 @@ func (g *Game) applyStep(sess *net.Session, p *player.Player, step *behavior.Ste // 14. aps_node — dynamic room-id flag, kept as a dedicated effect. if step.ApsNode { g.setPlayerFlag(p, "aps_node_"+strconv.Itoa(p.RoomID), true) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } } @@ -230,7 +230,7 @@ func (g *Game) giveTriggerDrop(sess *net.Session, p *player.Player, drop *behavi if sess != nil { sess.WriteLine(fmt.Sprintf("You receive %d credits.", qty)) } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) return } @@ -249,7 +249,7 @@ func (g *Game) giveTriggerDrop(sess *net.Session, p *player.Player, drop *behavi p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: drop.ItemID, Quantity: qty}) sess.WriteLine(fmt.Sprintf("You receive %s.", g.itemColorize(sess, lootDef, name))) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } // applyTalkStep fires a talk node/option's Action (a single Step) diff --git a/internal/game/act_farm.go b/internal/game/act_farm.go index cc7ddc4..91384a0 100644 --- a/internal/game/act_farm.go +++ b/internal/game/act_farm.go @@ -115,7 +115,7 @@ func (g *Game) advanceFarmGrowth(sess *net.Session, p *player.Player) { g.setPlayerFlag(p, prefix+"_diseased", false) sess.WriteLine(g.colorize(sess, "farm_disease", fmt.Sprintf("Your %s has died from disease!", g.seedDisplayName(sess, seedID)))) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) continue } @@ -157,7 +157,7 @@ func (g *Game) advanceFarmGrowth(sess *net.Session, p *player.Player) { g.seedDisplayName(sess, seedID), stage, maxStages))) } } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } } @@ -194,7 +194,7 @@ func (g *Game) advancePlant(sess *net.Session, p *player.Player) { g.awardSkillXP(sess, p, player.Farming, intXP) } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) seedName := seedID if def, err := g.ItemStore.Load(seedID); err == nil { @@ -288,7 +288,7 @@ func (g *Game) advanceHarvest(sess *net.Session, p *player.Player) { g.setPlayerFlag(p, prefix+"_diseased", false) g.setPlayerFlag(p, prefix+"_dead", false) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) productName := product if productDef != nil { @@ -322,7 +322,7 @@ func (g *Game) advanceRake(sess *net.Session, p *player.Player) { g.setPlayerFlag(p, prefix+"_diseased", false) g.setPlayerFlag(p, prefix+"_watered", false) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) patchName := patchDisplayName(prefix) sess.WriteLine(fmt.Sprintf("You rake the %s clean.", patchName)) @@ -342,7 +342,7 @@ func (g *Game) advanceWater(sess *net.Session, p *player.Player) { g.setPlayerFlag(p, prefix+"_watered", true) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) patchName := patchDisplayName(prefix) sess.WriteLine(fmt.Sprintf("You water the %s.", patchName)) @@ -363,7 +363,7 @@ func (g *Game) advanceCure(sess *net.Session, p *player.Player) { p.RemoveItem("plant_cure", 1) g.setPlayerFlag(p, prefix+"_diseased", false) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) sess.WriteLine("You apply the plant cure. The patch looks healthy again.") diff --git a/internal/game/act_fletch.go b/internal/game/act_fletch.go index c4dcef0..96ba736 100644 --- a/internal/game/act_fletch.go +++ b/internal/game/act_fletch.go @@ -13,7 +13,7 @@ import ( func (g *Game) startFletchAction(sess *net.Session, p *player.Player, def *item.ItemDef, count int) { p.EnsureFlags() g.setPlayerFlag(p, "last_fletch", def.ID) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) if def.FirstCraft().TicksPerCycle <= 0 { g.doInstantFletch(sess, p, def, count) @@ -80,7 +80,7 @@ func (g *Game) doInstantFletch(sess *net.Session, p *player.Player, def *item.It return } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) outDef, _ := g.ItemStore.Load(def.ID) outputName := def.Name @@ -186,7 +186,7 @@ func (g *Game) advanceFletch(sess *net.Session, p *player.Player) { if c.XP > 0 { g.awardSkillXP(sess, p, skill, c.XP) } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) msg := g.resolveCraftMessage(sess, c.SuccessMessage, "You fletch a %n.", c, def.ID, nil, p.HasItem) if p.OptionBool("xp_drops") && c.XP > 0 { diff --git a/internal/game/act_gather.go b/internal/game/act_gather.go index 447e23a..a1c87b9 100644 --- a/internal/game/act_gather.go +++ b/internal/game/act_gather.go @@ -239,7 +239,7 @@ func (g *Game) advanceGather(sess *net.Session, p *player.Player) { if xp > 0 { g.awardSkillXP(sess, p, player.SkillName(cfg.Skill), xp) } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) msg := drop.SuccessMessage coloredName := g.itemColorize(sess, itemDef, itemName) @@ -452,6 +452,6 @@ func (g *Game) checkBirdNest(sess *net.Session, p *player.Player, cfg *behavior. } p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: birdsNestItemID, Quantity: 1}) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) sess.WriteLine(fmt.Sprintf("A %s falls out of the tree!", nestName)) } diff --git a/internal/game/act_identify.go b/internal/game/act_identify.go index b905494..6ff4d61 100644 --- a/internal/game/act_identify.go +++ b/internal/game/act_identify.go @@ -78,6 +78,6 @@ func (g *Game) advanceIdentify(sess *net.Session, p *player.Player) { } sess.WriteLine(msg) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) g.cancelAction(p) } diff --git a/internal/game/act_state_test.go b/internal/game/act_state_test.go index 0ec139f..4182c61 100644 --- a/internal/game/act_state_test.go +++ b/internal/game/act_state_test.go @@ -8,7 +8,7 @@ import ( func TestPlayerActionDisplayFightingInCombat(t *testing.T) { g := escapeTestGame() - inst := spawnTestMob(g, "skeleton", 100) + inst := spawnTestMob(g, 100) p := player.New("combatant") p.EscapeDir = "north" diff --git a/internal/game/act_steal.go b/internal/game/act_steal.go index 880acff..f9ca095 100644 --- a/internal/game/act_steal.go +++ b/internal/game/act_steal.go @@ -69,7 +69,7 @@ func (g *Game) doSteal(sess *net.Session, input string) { g.cancelAction(p) - targetType, mob, objDef, errMsg := g.resolveStealTarget(sess, p, input) + targetType, mob, objDef, errMsg := g.resolveStealTarget(p, input) if errMsg != "" { sess.WriteLine(errMsg) return @@ -78,7 +78,7 @@ func (g *Game) doSteal(sess *net.Session, input string) { g.startSteal(sess, p, targetType, mob, objDef) } -func (g *Game) resolveStealTarget(sess *net.Session, p *player.Player, input string) (targetType string, mob *world.MobInstance, objDef *object.ObjectDef, errMsg string) { +func (g *Game) resolveStealTarget(p *player.Player, input string) (targetType string, mob *world.MobInstance, objDef *object.ObjectDef, errMsg string) { input = strings.ToLower(strings.TrimSpace(input)) instanceIdx := -1 searchName := input @@ -377,7 +377,7 @@ func (g *Game) advanceSteal(sess *net.Session, p *player.Player) { g.awardSkillXP(sess, p, player.Thieving, stealXP) } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) if p.FirstFreeSlot() == -1 { sess.WriteLine("Your inventory is full.") diff --git a/internal/game/baseline_race_test.go b/internal/game/baseline_race_test.go index fc07c5e..4efc907 100644 --- a/internal/game/baseline_race_test.go +++ b/internal/game/baseline_race_test.go @@ -16,16 +16,16 @@ import ( // ticker goroutine, and hub disconnect callbacks. func TestBaselineRaceSessionInputTickDisconnect(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) - h.seedAccount("bob", "pw", false) + h.seedAccount("alice") + h.seedAccount("bob") h.seedCharacter("alice", "alice") h.seedCharacter("bob", "bob") aSess, _ := h.connect("alice") bSess, _ := h.connect("bob") - aLines := h.goInput(aSess, 1) - bLines := h.goInput(bSess, 1) + aLines := h.goInput(aSess) + bLines := h.goInput(bSess) var wg sync.WaitGroup wg.Add(2) @@ -61,10 +61,10 @@ func TestBaselineRaceSessionInputTickDisconnect(t *testing.T) { // admin entry point) while players keep moving and ticking. func TestBaselineRaceAdminRewriteWhilePlaying(t *testing.T) { h := newHarness(t) - h.seedAccount("carol", "pw", false) + h.seedAccount("carol") h.seedCharacter("carol", "carol") sess, _ := h.connect("carol") - lines := h.goInput(sess, 1) + lines := h.goInput(sess) var wg sync.WaitGroup wg.Add(1) @@ -90,7 +90,7 @@ func TestBaselineRaceAdminRewriteWhilePlaying(t *testing.T) { func TestBaselineRaceCasinoWallet(t *testing.T) { h := newHarness(t) for _, n := range []string{"dave", "erin", "frank"} { - h.seedAccount(n, "pw", false) + h.seedAccount(n) p := h.seedCharacter(n, n) p.Credits = 100000 } @@ -115,7 +115,7 @@ func TestBaselineRaceCasinoWallet(t *testing.T) { }() // Session input competes with the wallet operations. - lines := h.goInput(s, 1) + lines := h.goInput(s) wg.Add(1) go func() { defer wg.Done() diff --git a/internal/game/casino.go b/internal/game/casino.go index 5268d34..9af9156 100644 --- a/internal/game/casino.go +++ b/internal/game/casino.go @@ -189,7 +189,7 @@ func casinoAffordable(p *player.Player) int { return p.Credits + countChips(p) } // the player is now clear of all casino games; false means a departure is // blocked mid-round and the caller (quit) must abort. Nil-safe for test // scaffolding without a Casino manager. -func (g *Game) leaveCasinoGames(sess *net.Session, p *player.Player) bool { +func (g *Game) leaveCasinoGames(p *player.Player) bool { if g.Casino == nil { return true } diff --git a/internal/game/casino_forfeit_regression_test.go b/internal/game/casino_forfeit_regression_test.go index db5ba31..d29187d 100644 --- a/internal/game/casino_forfeit_regression_test.go +++ b/internal/game/casino_forfeit_regression_test.go @@ -15,7 +15,7 @@ import ( // it on the next startup. func TestDisconnectForfeitsMachineStake(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.Credits = 100 @@ -40,7 +40,7 @@ func TestDisconnectForfeitsMachineStake(t *testing.T) { // frees up and the stake is not refunded by recovery. func TestDisconnectForfeitsBlackjackStake(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") // A natural opening hand (player or dealer blackjack) settles the round // inside Bet() itself, before any disconnect can forfeit anything. Deal diff --git a/internal/game/cmd_aps.go b/internal/game/cmd_aps.go index 56820ad..88befa5 100644 --- a/internal/game/cmd_aps.go +++ b/internal/game/cmd_aps.go @@ -125,14 +125,14 @@ func displayApsNodes(g *Game, sess *net.Session, p *player.Player, known []int) }) mode := g.colorMode(sess) - hLabel := g.resolveConstant(sess, "table_header_label") - hDetail := g.resolveConstant(sess, "table_header_detail") - hValue := g.resolveConstant(sess, "table_header_value") + hLabel := g.resolveConstant("table_header_label") + hDetail := g.resolveConstant("table_header_detail") + hValue := g.resolveConstant("table_header_value") rows := make([][]string, len(infos)) for i, info := range infos { distStr := strconv.Itoa(info.Distance) if info.Distance == 0 { - distStr = color.Render(mode, g.resolveConstant(sess, "aps_here"), "here") + distStr = color.Render(mode, g.resolveConstant("aps_here"), "here") } rows[i] = []string{ color.Render(mode, hDetail, fmt.Sprintf("#%d", info.ID)), diff --git a/internal/game/cmd_bank.go b/internal/game/cmd_bank.go index 86057cc..8da6e89 100644 --- a/internal/game/cmd_bank.go +++ b/internal/game/cmd_bank.go @@ -163,7 +163,7 @@ func (g *Game) doBankDeposit(sess *net.Session, input string) { MaxQuality: slot.MaxQuality, }) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) displayName := itemID if def != nil { @@ -202,7 +202,7 @@ func (g *Game) doBankDepositAll(sess *net.Session) { return } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) sess.WriteLine(fmt.Sprintf("You deposit %d item%s.", deposited, plural(deposited))) } @@ -300,7 +300,7 @@ func (g *Game) withdrawBankItem(sess *net.Session, p *player.Player, bankSlotIdx slot.Quality == bs.Quality && slot.MaxQuality == bs.MaxQuality { slot.Quantity += bs.Quantity p.SetBankSlot(bankSlotIdx, nil) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) displayName := bs.ItemID if def != nil { displayName = def.Name @@ -327,7 +327,7 @@ func (g *Game) withdrawBankItem(sess *net.Session, p *player.Player, bankSlotIdx p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: itemID, Quantity: 1, Quality: bs.Quality, MaxQuality: bs.MaxQuality}) if qty > 1 { bs.Quantity-- - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) displayName := itemID if def != nil { displayName = def.Name @@ -339,7 +339,7 @@ func (g *Game) withdrawBankItem(sess *net.Session, p *player.Player, bankSlotIdx } p.SetBankSlot(bankSlotIdx, nil) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) displayName := itemID if def != nil { @@ -370,7 +370,7 @@ func (g *Game) doBankDepositCredits(sess *net.Session, parts []string) { } p.Credits -= amount p.BankedCredits += amount - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) sess.WriteLine(g.colorize(sess, "currency_pickup", fmt.Sprintf("You deposit %s credits. (Banked: %s)", formatInt(amount), formatInt(p.BankedCredits)))) g.showBankBrowse(sess) } @@ -392,7 +392,7 @@ func (g *Game) doBankWithdrawCredits(sess *net.Session, parts []string) { } p.BankedCredits -= amount p.Credits += amount - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) sess.WriteLine(g.colorize(sess, "currency_pickup", fmt.Sprintf("You withdraw %s credits.", formatInt(amount)))) g.showBankBrowse(sess) } diff --git a/internal/game/cmd_color.go b/internal/game/cmd_color.go index b659795..614583f 100644 --- a/internal/game/cmd_color.go +++ b/internal/game/cmd_color.go @@ -93,7 +93,7 @@ func (g *Game) saveAccountColor(sess *net.Session, target, value string) { } acc.Colors[target] = value } - g.AccountStore.SaveAccount(acc) + g.saveAccount(acc) } func (g *Game) getCurrentColor(sess *net.Session, category string) string { diff --git a/internal/game/cmd_consume.go b/internal/game/cmd_consume.go index aec33c5..a49d897 100644 --- a/internal/game/cmd_consume.go +++ b/internal/game/cmd_consume.go @@ -101,7 +101,7 @@ func (g *Game) doEatNow(sess *net.Session, p *player.Player, itemID string, def } p.ConsumeCooldown = 3 - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) sess.WriteLine(g.colorize(sess, "eat_food", def.EatMessage)) diff --git a/internal/game/cmd_craft.go b/internal/game/cmd_craft.go index 37038c0..7091e39 100644 --- a/internal/game/cmd_craft.go +++ b/internal/game/cmd_craft.go @@ -165,7 +165,7 @@ func (g *Game) availableItems(p *player.Player, allItems []*item.ItemDef) []*ite func (g *Game) startItem(sess *net.Session, p *player.Player, def *item.ItemDef, count int, flagKey string) { p.EnsureFlags() g.setPlayerFlag(p, flagKey, def.ID) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) g.startProductionFromItem(sess, p, def, count) } @@ -182,7 +182,7 @@ func (g *Game) hasGrimyHerbs(p *player.Player) bool { return false } -func (g *Game) findCraftItems(p *player.Player, itemAID, itemBID string) []*item.ItemDef { +func (g *Game) findCraftItems(itemAID, itemBID string) []*item.ItemDef { items := g.CraftIndex.ByType("crafting") toolTypeA := "" diff --git a/internal/game/cmd_description.go b/internal/game/cmd_description.go index 1fc24ae..c8a13d1 100644 --- a/internal/game/cmd_description.go +++ b/internal/game/cmd_description.go @@ -27,7 +27,7 @@ func (g *Game) handleDescChange(sess *net.Session, input string) { if input != "" { p := sess.Player p.Description = input - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) sess.WriteLine(fmt.Sprintf("Description set to: %s", input)) } else { sess.WriteLine("Description left unchanged.") diff --git a/internal/game/cmd_dig.go b/internal/game/cmd_dig.go index 3fda55e..1497c34 100644 --- a/internal/game/cmd_dig.go +++ b/internal/game/cmd_dig.go @@ -98,7 +98,7 @@ func (g *Game) executeDig(sess *net.Session, args []string, rawInput string) { p.RoomID = conflictID p.HazardTimer = 0 p.Stats.RecordRoomVisit(conflictID) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) g.World.SeedGroundItems(conflictID) g.seedRoomMobs(conflictID) @@ -194,7 +194,7 @@ func (g *Game) executeDig(sess *net.Session, args []string, rawInput string) { p.RoomID = newID p.HazardTimer = 0 p.Stats.RecordRoomVisit(newID) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) g.World.SeedGroundItems(newID) g.seedRoomMobs(newID) diff --git a/internal/game/cmd_drop.go b/internal/game/cmd_drop.go index 674f3da..072f54a 100644 --- a/internal/game/cmd_drop.go +++ b/internal/game/cmd_drop.go @@ -55,7 +55,7 @@ func (g *Game) doDropAllNamed(sess *net.Session, input string) { qty := slot.Quantity p.SetInvSlot(matches[0].Slot, nil) g.World.AddGroundItem(p.RoomID, itemID, qty) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) if qty == 1 { sess.WriteLine(fmt.Sprintf("You drop a %s.", coloredName)) } else { @@ -74,7 +74,7 @@ func (g *Game) doDropAllNamed(sess *net.Session, input string) { p.SetInvSlot(match.Slot, nil) g.World.AddGroundItem(p.RoomID, itemID, slot.Quantity) } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) if total == 1 { sess.WriteLine(fmt.Sprintf("You drop a %s.", coloredName)) } else { @@ -138,7 +138,7 @@ func (g *Game) doDrop(sess *net.Session, input string) { p.SetInvSlot(matches[0].Slot, nil) } g.World.AddGroundItem(p.RoomID, itemID, qty) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) if qty == 1 { sess.WriteLine(fmt.Sprintf("You drop a %s.", g.itemColorize(sess, def, name))) } else { @@ -167,7 +167,7 @@ func (g *Game) doDrop(sess *net.Session, input string) { } dropped := qty - remaining - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) if dropped == 0 { sess.WriteLine(fmt.Sprintf("You don't have any %s.", g.itemColorize(sess, def, name))) } else if dropped == 1 { @@ -223,7 +223,7 @@ func (g *Game) handleDropAll(sess *net.Session, input string) { p.SetInvSlot(i, nil) } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) sess.State = net.StateGame if len(dropped) == 0 { diff --git a/internal/game/cmd_estate_directory.go b/internal/game/cmd_estate_directory.go index 2441157..fc79e6c 100644 --- a/internal/game/cmd_estate_directory.go +++ b/internal/game/cmd_estate_directory.go @@ -206,7 +206,7 @@ func (g *Game) teleportPlayer(sess *net.Session, p *player.Player, roomID int) { g.World.SeedGroundItems(p.RoomID) g.seedRoomMobs(p.RoomID) g.seedRoomObjects(p.RoomID) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) g.doLook(sess) g.runEnterSteps(sess, p.RoomID) g.writePrompt(sess) diff --git a/internal/game/cmd_farm.go b/internal/game/cmd_farm.go index aaa3779..dec16c0 100644 --- a/internal/game/cmd_farm.go +++ b/internal/game/cmd_farm.go @@ -138,13 +138,13 @@ func (g *Game) doPlant(sess *net.Session, input string) { }, } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } func (g *Game) doHarvest(sess *net.Session, input string) { p := sess.Player - prefix, _, _ := g.findFarmPatch(sess, p, input, func(flags map[string]any, prefix string) bool { + prefix := g.findFarmPatch(p, input, func(flags map[string]any, prefix string) bool { ready, _ := flags[prefix+"_ready"].(bool) return ready }) @@ -223,7 +223,7 @@ func (g *Game) doHarvest(sess *net.Session, input string) { }, } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } func (g *Game) doRake(sess *net.Session, input string) { @@ -234,7 +234,7 @@ func (g *Game) doRake(sess *net.Session, input string) { return } - prefix, _, _ := g.findFarmPatch(sess, p, input, func(flags map[string]any, prefix string) bool { + prefix := g.findFarmPatch(p, input, func(flags map[string]any, prefix string) bool { weeds, _ := flags[prefix+"_weeds"].(bool) dead, _ := flags[prefix+"_dead"].(bool) return weeds || dead @@ -268,7 +268,7 @@ func (g *Game) doRake(sess *net.Session, input string) { }, } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } func (g *Game) doWater(sess *net.Session, input string) { @@ -279,7 +279,7 @@ func (g *Game) doWater(sess *net.Session, input string) { return } - prefix, _, _ := g.findFarmPatch(sess, p, input, func(flags map[string]any, prefix string) bool { + prefix := g.findFarmPatch(p, input, func(flags map[string]any, prefix string) bool { seed, _ := flags[prefix+"_seed"].(string) if seed == "" { return false @@ -327,7 +327,7 @@ func (g *Game) doWater(sess *net.Session, input string) { }, } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } func (g *Game) doCure(sess *net.Session, input string) { @@ -338,7 +338,7 @@ func (g *Game) doCure(sess *net.Session, input string) { return } - prefix, _, _ := g.findFarmPatch(sess, p, input, func(flags map[string]any, prefix string) bool { + prefix := g.findFarmPatch(p, input, func(flags map[string]any, prefix string) bool { diseased, _ := flags[prefix+"_diseased"].(bool) return diseased }) @@ -370,7 +370,7 @@ func (g *Game) doCure(sess *net.Session, input string) { }, } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } func (g *Game) ShowFarmPatchDetail(sess *net.Session, p *player.Player, prefix, defID string, index int) { @@ -436,10 +436,10 @@ func boolToYes(b bool) string { return "No" } -func (g *Game) findFarmPatch(sess *net.Session, p *player.Player, input string, matcher func(map[string]any, string) bool) (prefix string, defID string, index int) { +func (g *Game) findFarmPatch(p *player.Player, input string, matcher func(map[string]any, string) bool) (prefix string) { objInstances := g.World.AllObjInstances(p.RoomID) if len(objInstances) == 0 { - return "", "", -1 + return "" } type patchInfo struct { @@ -478,24 +478,24 @@ func (g *Game) findFarmPatch(sess *net.Session, p *player.Player, input string, } if len(patches) == 0 { - return "", "", -1 + return "" } if input == "" { - return patches[0].prefix, patches[0].defID, patches[0].index + return patches[0].prefix } lower := strings.ToLower(input) for _, pi := range patches { if strings.Contains(lower, strings.ToLower(pi.name)) { - return pi.prefix, pi.defID, pi.index + return pi.prefix } } for _, pi := range patches { if strings.Contains(lower, pi.prefix) { - return pi.prefix, pi.defID, pi.index + return pi.prefix } } - return "", "", -1 + return "" } diff --git a/internal/game/cmd_fletch.go b/internal/game/cmd_fletch.go index d638bc0..af27897 100644 --- a/internal/game/cmd_fletch.go +++ b/internal/game/cmd_fletch.go @@ -173,7 +173,7 @@ func (g *Game) availableFletchItems(p *player.Player, allItems []*item.ItemDef) return result } -func (g *Game) findFletchItems(p *player.Player, itemAID, itemBID string) []*item.ItemDef { +func (g *Game) findFletchItems(itemAID, itemBID string) []*item.ItemDef { items := g.CraftIndex.ByType("fletching") toolTypeA := "" diff --git a/internal/game/cmd_get.go b/internal/game/cmd_get.go index 7a5181f..f2776a3 100644 --- a/internal/game/cmd_get.go +++ b/internal/game/cmd_get.go @@ -83,7 +83,7 @@ func (g *Game) doGet(sess *net.Session, input string) { return } slot.Quantity += removed - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) sess.WriteLine(fmt.Sprintf("You pick up %d x %s. (now %d)", qty, g.itemColorize(sess, def, def.Name), slot.Quantity)) return } @@ -98,7 +98,7 @@ func (g *Game) doGet(sess *net.Session, input string) { return } p.SetInvSlot(freeSlot, g.newInventorySlot(itemID, qty)) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) if qty == 1 { sess.WriteLine(fmt.Sprintf("You pick up a %s.", g.itemColorize(sess, def, def.Name))) } else { @@ -120,7 +120,7 @@ func (g *Game) doGet(sess *net.Session, input string) { picked++ } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) if picked == 0 { sess.WriteLine("Your inventory is full.") } else if picked == 1 { @@ -201,7 +201,7 @@ func (g *Game) doGetAllNamed(sess *net.Session, input string) { return } slot.Quantity += removed - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) sess.WriteLine(fmt.Sprintf("You pick up %d x %s. (now %d)", removed, coloredName, slot.Quantity)) return } @@ -217,7 +217,7 @@ func (g *Game) doGetAllNamed(sess *net.Session, input string) { return } p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: itemID, Quantity: removed}) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) if removed == 1 { sess.WriteLine(fmt.Sprintf("You pick up a %s.", coloredName)) } else { @@ -240,7 +240,7 @@ func (g *Game) doGetAllNamed(sess *net.Session, input string) { picked++ } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) if picked == 0 { sess.WriteLine("Your inventory is full.") } else if picked == 1 { @@ -316,7 +316,7 @@ func (g *Game) doGetAll(sess *net.Session) { } else { sess.WriteLine("Your inventory is full!") } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) return } _, ok := g.World.RemoveReservedItem(p.RoomID, itemID, qty, p.Name) @@ -340,7 +340,7 @@ func (g *Game) doGetAll(sess *net.Session) { } else { sess.WriteLine("Your inventory is full!") } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) return } @@ -359,7 +359,7 @@ func (g *Game) doGetAll(sess *net.Session) { } } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) if len(picked) == 0 { sess.WriteLine("Your inventory is full.") @@ -379,7 +379,7 @@ func (g *Game) pickupCredits(sess *net.Session, p *player.Player, itemID string) return } p.Credits += qty - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) if qty == 1 { sess.WriteLine(g.colorize(sess, "currency_pickup", "You pick up 1 credit.")) } else { diff --git a/internal/game/cmd_god.go b/internal/game/cmd_god.go index 56311cb..b819fbd 100644 --- a/internal/game/cmd_god.go +++ b/internal/game/cmd_god.go @@ -61,7 +61,7 @@ func (g *Game) executeUnGod(sess *net.Session, args []string, rawInput string) { return } g.restoreGodPlayer(p) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) sess.WriteLine("God mode deactivated. Skills restored.") } diff --git a/internal/game/cmd_goto.go b/internal/game/cmd_goto.go index 1771f8d..96984b9 100644 --- a/internal/game/cmd_goto.go +++ b/internal/game/cmd_goto.go @@ -45,7 +45,7 @@ func (g *Game) executeGoto(sess *net.Session, args []string, rawInput string) { p.RoomID = roomID p.HazardTimer = 0 p.Stats.RecordRoomVisit(roomID) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) g.World.SeedGroundItems(roomID) g.seedRoomMobs(roomID) diff --git a/internal/game/cmd_hideallexits.go b/internal/game/cmd_hideallexits.go index d9747c9..3d0c5e9 100644 --- a/internal/game/cmd_hideallexits.go +++ b/internal/game/cmd_hideallexits.go @@ -33,6 +33,6 @@ func (g *Game) executeHideAllExits(sess *net.Session, args []string, rawInput st for _, k := range cleared { delete(p.Flags, k) } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) sess.WriteLine(fmt.Sprintf("Cleared %d discovered exit(s).", len(cleared))) } diff --git a/internal/game/cmd_id.go b/internal/game/cmd_id.go index 87adce7..bf64bbe 100644 --- a/internal/game/cmd_id.go +++ b/internal/game/cmd_id.go @@ -2,7 +2,6 @@ package game import ( "fmt" - "strings" "thehouseoficarus/internal/behavior" "thehouseoficarus/internal/net" @@ -83,10 +82,10 @@ func junkMultiplier(junkID string, level int) int { } func (g *Game) executeIdentify(sess *net.Session, args []string, rawInput string) { - g.doIdentify(sess, strings.Join(args, " ")) + g.doIdentify(sess) } -func (g *Game) doIdentify(sess *net.Session, input string) { +func (g *Game) doIdentify(sess *net.Session) { p := sess.Player g.cancelAction(p) diff --git a/internal/game/cmd_look.go b/internal/game/cmd_look.go index f36fd9e..3e0bbc4 100644 --- a/internal/game/cmd_look.go +++ b/internal/game/cmd_look.go @@ -43,7 +43,7 @@ func (g *Game) doLook(sess *net.Session) { } var content []string - content = append(content, g.showRoomDescription(sess, p, room, descWidth)...) + content = append(content, g.showRoomDescription(sess, room, descWidth)...) content = append(content, g.showRoomObjects(sess, p, room)...) content = append(content, g.showRoomMobs(sess, p, room)...) content = append(content, g.showGroundItems(sess, p, room)...) diff --git a/internal/game/cmd_mods.go b/internal/game/cmd_mods.go index 0126b5f..de49adc 100644 --- a/internal/game/cmd_mods.go +++ b/internal/game/cmd_mods.go @@ -55,10 +55,10 @@ func (g *Game) doMods(sess *net.Session, showAll bool) { t := &ui.Table{ Title: cat.Name, Columns: []string{ - color.Render(mode, g.resolveConstant(sess, "table_header_label"), "Module"), - color.Render(mode, g.resolveConstant(sess, "table_header_detail"), "Level"), - color.Render(mode, g.resolveConstant(sess, "table_header_xp"), "Cost"), - color.Render(mode, g.resolveConstant(sess, "table_header_xp"), "XP"), + color.Render(mode, g.resolveConstant("table_header_label"), "Module"), + color.Render(mode, g.resolveConstant("table_header_detail"), "Level"), + color.Render(mode, g.resolveConstant("table_header_xp"), "Cost"), + color.Render(mode, g.resolveConstant("table_header_xp"), "XP"), }, } @@ -77,10 +77,10 @@ func (g *Game) doMods(sess *net.Session, showAll bool) { }) } else if unlocked { t.Rows = append(t.Rows, []string{ - color.Render(mode, g.resolveConstant(sess, "table_header_label"), m.Name), - color.Render(mode, g.resolveConstant(sess, "table_header_detail"), levelStr), - color.Render(mode, g.resolveConstant(sess, "table_header_xp"), costStr), - color.Render(mode, g.resolveConstant(sess, "table_header_xp"), xpStr), + color.Render(mode, g.resolveConstant("table_header_label"), m.Name), + color.Render(mode, g.resolveConstant("table_header_detail"), levelStr), + color.Render(mode, g.resolveConstant("table_header_xp"), costStr), + color.Render(mode, g.resolveConstant("table_header_xp"), xpStr), }) } else { t.Rows = append(t.Rows, []string{ diff --git a/internal/game/cmd_move.go b/internal/game/cmd_move.go index 4772a2c..72c3236 100644 --- a/internal/game/cmd_move.go +++ b/internal/game/cmd_move.go @@ -75,7 +75,7 @@ func (g *Game) doMove(sess *net.Session, dir string, isWalk bool) { // fire in completeMove once the player arrives. ExitDef.Condition (above, // evaluated by exitDisplayState) gates whether the exit is passable at // all; each on_traverse entry has its own additional Condition gate. - p.MovePendingTrigger = g.matchExitTrigger(sess, p, exitDef) + p.MovePendingTrigger = g.matchExitTrigger(sess, exitDef) inCombat := g.Combat.Get(p.Name) != nil @@ -130,7 +130,7 @@ func (g *Game) doMove(sess *net.Session, dir string, isWalk bool) { // Condition (and item filter, n/a for traverse) passes for this player, or nil // if the exit has no matching on_traverse entry. The matched trigger's Steps // run as a sequence once the player arrives in the target room. -func (g *Game) matchExitTrigger(sess *net.Session, p *player.Player, exitDef world.ExitDef) *behavior.Trigger { +func (g *Game) matchExitTrigger(sess *net.Session, exitDef world.ExitDef) *behavior.Trigger { for i := range exitDef.OnTraverse { t := &exitDef.OnTraverse[i] if t.Condition != nil && !g.checkCondition(sess, t.Condition) { @@ -195,7 +195,7 @@ func (g *Game) completeMove(sess *net.Session, p *player.Player) { if pendingTrigger != nil { g.runTrigger(sess, p, []behavior.Trigger{*pendingTrigger}, p.RoomID, nil, false) } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) g.World.SeedGroundItems(p.RoomID) g.seedRoomMobs(p.RoomID) diff --git a/internal/game/cmd_option.go b/internal/game/cmd_option.go index d35ca12..294ed5e 100644 --- a/internal/game/cmd_option.go +++ b/internal/game/cmd_option.go @@ -16,10 +16,10 @@ func (g *Game) doOption(sess *net.Session, input string) { mode := g.colorMode(sess) if input == "" { - hLabel := g.resolveConstant(sess, "table_header_label") - hValue := g.resolveConstant(sess, "table_header_value") - hDetail := g.resolveConstant(sess, "table_header_detail") - hDesc := g.resolveConstant(sess, "table_header_desc") + hLabel := g.resolveConstant("table_header_label") + hValue := g.resolveConstant("table_header_value") + hDetail := g.resolveConstant("table_header_detail") + hDesc := g.resolveConstant("table_header_desc") table := &ui.Table{ Columns: []string{ color.Render(mode, hLabel, "Option"), @@ -102,7 +102,7 @@ func (g *Game) doOption(sess *net.Session, input string) { if def.Name == "unicode" && parsed == false { acc.Options["card_size"] = 5 } - g.AccountStore.SaveAccount(acc) + g.saveAccount(acc) } sess.WriteLine(fmt.Sprintf("%s set to %s.", def.Name, formatOptionValue(p, def))) } diff --git a/internal/game/cmd_prompt.go b/internal/game/cmd_prompt.go index 3596ebf..b613f44 100644 --- a/internal/game/cmd_prompt.go +++ b/internal/game/cmd_prompt.go @@ -25,19 +25,19 @@ func (g *Game) doPrompt(sess *net.Session, input string) { if input == "reset" { p.Prompt = "> " - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) sess.WriteLine("Prompt reset to default.") return } if input == "none" { p.Prompt = "" - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) sess.WriteLine("Prompt set to blank.") return } p.Prompt = input - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) sess.WriteLine(fmt.Sprintf("Prompt set to: %s", input)) } diff --git a/internal/game/cmd_quit.go b/internal/game/cmd_quit.go index 85e3510..e564c7e 100644 --- a/internal/game/cmd_quit.go +++ b/internal/game/cmd_quit.go @@ -29,7 +29,7 @@ func (g *Game) doQuit(sess *net.Session) { // to the round in flight refuses and the quit is aborted, so deliberate // exits never forfeit a stake or reveal cards mid-hand. Forfeit-on-leave // stays reserved for genuine disconnects and "quit now". - if !g.leaveCasinoGames(sess, p) { + if !g.leaveCasinoGames(p) { sess.WriteLine(g.colorize(sess, "warning", "Type \"quit now\" to log out anyway — your bet will be forfeited.")) return } @@ -82,7 +82,7 @@ func (g *Game) beginQuitCountdown(sess *net.Session) { // now — immediately before the disconnect. Settling games only // pay seated bettors, so this forfeits a mid-round stake. g.emitCasinoEvents(g.Casino.MarkDisconnected(p.Name)) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) g.charsMu.Lock() delete(g.loggedInChars, p.Name) g.charsMu.Unlock() diff --git a/internal/game/cmd_remove.go b/internal/game/cmd_remove.go index 38f17a3..349617d 100644 --- a/internal/game/cmd_remove.go +++ b/internal/game/cmd_remove.go @@ -80,7 +80,7 @@ func (g *Game) doRemove(sess *net.Session, input string) { delete(p.EquipQuality, foundItemID) } p.SetInvSlot(freeSlot, invSlot) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) name := foundItemID def, _ := g.ItemStore.Load(foundItemID) @@ -106,7 +106,7 @@ func (g *Game) removeAmmo(sess *net.Session, p *player.Player, qty int) { delete(p.Equipment, item.SlotAmmo) p.AmmoQty = 0 } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) def, _ := g.ItemStore.Load(ammoID) name := ammoID if def != nil { @@ -129,7 +129,7 @@ func (g *Game) removeAmmo(sess *net.Session, p *player.Player, qty int) { delete(p.Equipment, item.SlotAmmo) p.AmmoQty = 0 } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) def, _ := g.ItemStore.Load(ammoID) name := ammoID @@ -190,7 +190,7 @@ func (g *Game) doRemoveAll(sess *net.Session) { sess.WriteLine(fmt.Sprintf("You remove %s.", g.itemColorize(sess, def, name))) } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } func (g *Game) removeAllAmmo(sess *net.Session, p *player.Player, itemID string) { diff --git a/internal/game/cmd_room_insert.go b/internal/game/cmd_room_insert.go index 132469d..92ef38b 100644 --- a/internal/game/cmd_room_insert.go +++ b/internal/game/cmd_room_insert.go @@ -185,7 +185,7 @@ func (g *Game) roomInsert(sess *net.Session, args []string) { p.RoomID = newID p.HazardTimer = 0 p.Stats.RecordRoomVisit(newID) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) g.World.SeedGroundItems(newID) g.seedRoomMobs(newID) diff --git a/internal/game/cmd_score.go b/internal/game/cmd_score.go index 426dc08..6f00de0 100644 --- a/internal/game/cmd_score.go +++ b/internal/game/cmd_score.go @@ -99,16 +99,16 @@ func (g *Game) doScore(sess *net.Session) { creditsStr := g.colorize(sess, "currency_pickup", formatInt(p.Credits)) chipsStr := g.colorize(sess, "currency_pickup", formatInt(countChips(p))) - styleStr := color.Render(mode, g.resolveConstant(sess, "table_header_label"), fmt.Sprintf("Style: %s", p.AttackStyle)) + styleStr := color.Render(mode, g.resolveConstant("table_header_label"), fmt.Sprintf("Style: %s", p.AttackStyle)) invStr := fmt.Sprintf("Inv: %d/28", 28-p.FreeSlots()) row2 := padded("Credits: "+creditsStr, 19) + padded("Chips: "+chipsStr, 16) + padded(styleStr, 22) + invStr content(row2) gap() - hpStyle := g.hpBarStyle(sess, p.HP, p.MaxHP()) + hpStyle := g.hpBarStyle(p.HP, p.MaxHP()) hpBarStr := ui.RenderColoredBar(p.HP, p.MaxHP(), 26, unicode, hpStyle, mode) - hpFg := g.hpBarStyle(sess, p.HP, p.MaxHP()).FilledColor + hpFg := g.hpBarStyle(p.HP, p.MaxHP()).FilledColor hpLine := fmt.Sprintf("HP [%s] %s / %s", hpBarStr, color.Render(mode, color.ColorSpec{Fg: hpFg}, fmt.Sprint(p.HP)), @@ -116,16 +116,16 @@ func (g *Game) doScore(sess *net.Session) { content(hpLine) batBarRaw := ui.RenderBarF(p.Battery, p.MaxBattery(), 26, unicode) - batBarStr := color.Render(mode, g.resolveConstant(sess, "battery_bar"), batBarRaw) - batFg := g.resolveConstant(sess, "battery_bar").Fg + batBarStr := color.Render(mode, g.resolveConstant("battery_bar"), batBarRaw) + batFg := g.resolveConstant("battery_bar").Fg batLine := fmt.Sprintf("Battery [%s] %s / %s", batBarStr, color.Render(mode, color.ColorSpec{Fg: batFg}, fmt.Sprintf("%.1f", p.Battery)), g.colorizeConstant(sess, "score_max_value", fmt.Sprintf("%.0f", p.MaxBattery()))) content(batLine) - engSpec := g.resolveConstant(sess, "run_energy_bar") - engEmpty := g.resolveConstant(sess, "hp_bar_empty") + engSpec := g.resolveConstant("run_energy_bar") + engEmpty := g.resolveConstant("hp_bar_empty") engStyle := &ui.BarStyle{FilledColor: engSpec.Fg, EmptyColor: engEmpty.Fg, EmptyDim: engEmpty.Dim} engBarStr := ui.RenderColoredBar(p.RunEnergy, p.MaxRunEnergy(), 26, unicode, engStyle, mode) engLine := fmt.Sprintf("Run En. [%s] %s / %s", @@ -158,8 +158,8 @@ func (g *Game) doScore(sess *net.Session) { buildSkillCell := func(s player.SkillName) string { level := p.Level(s) - lvlColor := g.skillLevelColor(sess, level) - name := padded(color.Render(mode, g.resolveConstant(sess, "active_tech_stat"), skillDisplay[s]), 12) + lvlColor := g.skillLevelColor(level) + name := padded(color.Render(mode, g.resolveConstant("active_tech_stat"), skillDisplay[s]), 12) lvl := color.Render(mode, color.ColorSpec{Fg: lvlColor}, fmt.Sprintf("%2d", level)) xpBar := xpProgressBar(p.Skills[s], 12, unicode) return fmt.Sprintf("%s %s [%s]", name, lvl, xpBar) @@ -198,8 +198,8 @@ func (g *Game) doScore(sess *net.Session) { for _, id := range p.ActiveTechList() { def := GetTechDef(id) if def != nil { - on := color.Render(mode, g.resolveConstant(sess, "active_tech_on"), "[ON]") - name := color.Render(mode, g.resolveConstant(sess, "active_tech_stat"), def.Name) + on := color.Render(mode, g.resolveConstant("active_tech_on"), "[ON]") + name := color.Render(mode, g.resolveConstant("active_tech_stat"), def.Name) techParts = append(techParts, on+" "+name) } } @@ -208,9 +208,9 @@ func (g *Game) doScore(sess *net.Session) { if len(p.ActiveBuffs) > 0 { for _, buff := range p.ActiveBuffs { buffLine := fmt.Sprintf("%s +%d%% %s (%d ticks remaining)", - color.Render(mode, g.resolveConstant(sess, "active_tech_status"), "[BUFF]"), + color.Render(mode, g.resolveConstant("active_tech_status"), "[BUFF]"), buff.BonusPercent, - color.Render(mode, g.resolveConstant(sess, "active_tech_stat"), buff.Stat), + color.Render(mode, g.resolveConstant("active_tech_stat"), buff.Stat), buff.TicksLeft) content(buffLine) } @@ -257,17 +257,17 @@ func formatInt(n int) string { // skillLevelColor returns the xterm256 palette index for a skill level cell // on the datapad score panel, sourcing it from the constant_colors section. -func (g *Game) skillLevelColor(sess *net.Session, level int) int { - spec := g.resolveConstant(sess, "skill_lvl_base") +func (g *Game) skillLevelColor(level int) int { + spec := g.resolveConstant("skill_lvl_base") switch { case level >= 99: - spec = g.resolveConstant(sess, "skill_lvl_max") + spec = g.resolveConstant("skill_lvl_max") case level >= 80: - spec = g.resolveConstant(sess, "skill_lvl_high") + spec = g.resolveConstant("skill_lvl_high") case level >= 60: - spec = g.resolveConstant(sess, "skill_lvl_mid") + spec = g.resolveConstant("skill_lvl_mid") case level >= 40: - spec = g.resolveConstant(sess, "skill_lvl_low") + spec = g.resolveConstant("skill_lvl_low") } return spec.Fg } diff --git a/internal/game/cmd_setplayerflag.go b/internal/game/cmd_setplayerflag.go index 3d0bdbf..ec99cda 100644 --- a/internal/game/cmd_setplayerflag.go +++ b/internal/game/cmd_setplayerflag.go @@ -55,6 +55,6 @@ func (g *Game) executeSetPlayerFlag(sess *net.Session, args []string, rawInput s } g.setPlayerFlag(tp, flagName, value) - g.AccountStore.SaveCharacter(tp) + g.saveCharacter(tp) sess.WriteLine(fmt.Sprintf("Player flag '%s' set to %v for %s.", flagName, value, tp.Name)) } diff --git a/internal/game/cmd_shop.go b/internal/game/cmd_shop.go index e9f16b8..ca1bf53 100644 --- a/internal/game/cmd_shop.go +++ b/internal/game/cmd_shop.go @@ -289,7 +289,7 @@ func (g *Game) doShopBuy(sess *net.Session, mob *world.MobInstance, input string total := price * taken p.Credits -= total g.giveShopItems(p, c.itemID, taken, c.def) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) itemColor := g.itemColorize(sess, c.def, c.name) credits := g.colorize(sess, "currency_pickup", fmt.Sprintf("%d", total)) @@ -353,7 +353,7 @@ func (g *Game) doShopSell(sess *net.Session, mob *world.MobInstance, input strin p.RemoveItem(match.ID, actualQty) p.Credits += total g.MobStore.ShopAdd(mob.InstanceID, match.ID, actualQty) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) credits := g.colorize(sess, "currency_pickup", fmt.Sprintf("%d", total)) if actualQty == 1 { diff --git a/internal/game/cmd_skills.go b/internal/game/cmd_skills.go index 2f9f52e..725286a 100644 --- a/internal/game/cmd_skills.go +++ b/internal/game/cmd_skills.go @@ -16,10 +16,10 @@ func (g *Game) executeSkills(sess *net.Session, args []string, rawInput string) func (g *Game) doSkills(sess *net.Session) { p := sess.Player mode := g.colorMode(sess) - hLabel := g.resolveConstant(sess, "table_header_label") - hDetail := g.resolveConstant(sess, "table_header_detail") - hValue := g.resolveConstant(sess, "table_header_value") - hXP := g.resolveConstant(sess, "table_header_xp") + hLabel := g.resolveConstant("table_header_label") + hDetail := g.resolveConstant("table_header_detail") + hValue := g.resolveConstant("table_header_value") + hXP := g.resolveConstant("table_header_xp") t := &ui.Table{Title: "Skills", Columns: []string{ color.Render(mode, hLabel, "Skill"), color.Render(mode, hDetail, "Abbr"), diff --git a/internal/game/cmd_smelt.go b/internal/game/cmd_smelt.go index 5f7a7b3..97d9371 100644 --- a/internal/game/cmd_smelt.go +++ b/internal/game/cmd_smelt.go @@ -17,7 +17,7 @@ func (g *Game) doSmelt(sess *net.Session, input string) { p := sess.Player g.cancelAction(p) - stationDefID, stationName := g.findStation(p.RoomID, []string{"furnace"}) + stationDefID, _ := g.findStation(p.RoomID, []string{"furnace"}) if stationDefID == "" { sess.WriteLine("You need a furnace to smelt.") return @@ -26,7 +26,7 @@ func (g *Game) doSmelt(sess *net.Session, input string) { items := g.CraftIndex.BySubtype("smelting") if input == "" { - g.showSmeltMenu(sess, p, items, stationDefID, stationName) + g.showSmeltMenu(sess, p, items, stationDefID) return } @@ -59,7 +59,7 @@ func (g *Game) doSmelt(sess *net.Session, input string) { 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, items []*item.ItemDef, stationDefID, stationName string) { +func (g *Game) showSmeltMenu(sess *net.Session, p *player.Player, items []*item.ItemDef, stationDefID string) { seen := make(map[string]bool) var recipes []*item.ItemDef diff --git a/internal/game/cmd_stop.go b/internal/game/cmd_stop.go index b05e1fc..4b287ce 100644 --- a/internal/game/cmd_stop.go +++ b/internal/game/cmd_stop.go @@ -10,7 +10,7 @@ func (g *Game) executeStop(sess *net.Session, args []string, rawInput string) { // queue; cancel a pending quit countdown explicitly. g.cancelRest(p.Name) if _, playing := g.Casino.Participation(p.Name); playing { - g.leaveCasinoGames(sess, p) + g.leaveCasinoGames(p) return } ss, _ := g.safespot.Get(p.Name) diff --git a/internal/game/cmd_style.go b/internal/game/cmd_style.go index 043c65f..d475b4f 100644 --- a/internal/game/cmd_style.go +++ b/internal/game/cmd_style.go @@ -33,7 +33,7 @@ func (g *Game) doStyle(sess *net.Session, input string) { } if len(matches) == 1 { p.AttackStyle = player.AttackStyle(matches[0]) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) sess.WriteLine(fmt.Sprintf("Combat style set to %s.", matches[0])) return } diff --git a/internal/game/cmd_summon.go b/internal/game/cmd_summon.go index 9383e31..6477b11 100644 --- a/internal/game/cmd_summon.go +++ b/internal/game/cmd_summon.go @@ -60,7 +60,7 @@ func (g *Game) executeSummon(sess *net.Session, args []string, rawInput string) tp.RoomID = adminRoom tp.HazardTimer = 0 tp.Stats.RecordRoomVisit(adminRoom) - g.AccountStore.SaveCharacter(tp) + g.saveCharacter(tp) g.World.SeedGroundItems(adminRoom) g.seedRoomMobs(adminRoom) diff --git a/internal/game/cmd_tech.go b/internal/game/cmd_tech.go index 1431228..587df60 100644 --- a/internal/game/cmd_tech.go +++ b/internal/game/cmd_tech.go @@ -129,14 +129,14 @@ func (g *Game) doTechList(sess *net.Session) { var nameStr string if tech.Level <= techLevel { if p.HasActiveTech(tech.ID) { - status = color.Render(mode, g.resolveConstant(sess, "tech_status_on"), "ON") + status = color.Render(mode, g.resolveConstant("tech_status_on"), "ON") } else { - status = color.Render(mode, g.resolveConstant(sess, "tech_status_off"), "off") + status = color.Render(mode, g.resolveConstant("tech_status_off"), "off") } - nameStr = color.Render(mode, g.resolveConstant(sess, "tech_name_unlocked"), tech.Name) + nameStr = color.Render(mode, g.resolveConstant("tech_name_unlocked"), tech.Name) } else { - status = color.Render(mode, g.resolveConstant(sess, "tech_status_locked"), "locked") - nameStr = color.Render(mode, g.resolveConstant(sess, "tech_name_locked"), tech.Name) + status = color.Render(mode, g.resolveConstant("tech_status_locked"), "locked") + nameStr = color.Render(mode, g.resolveConstant("tech_name_locked"), tech.Name) } effect := techEffectString(tech) @@ -192,7 +192,7 @@ func (g *Game) doTechQuick(sess *net.Session, p *player.Player, input string) { } p.QuickTech = matches[0].ID - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) sess.WriteLine(fmt.Sprintf("Quick tech set to %s.", matches[0].Name)) } diff --git a/internal/game/cmd_trigger_combat.go b/internal/game/cmd_trigger_combat.go index 2f51057..f9a6bf1 100644 --- a/internal/game/cmd_trigger_combat.go +++ b/internal/game/cmd_trigger_combat.go @@ -18,14 +18,14 @@ import ( // level and the mob's Science stat (plus ScienceDefense) respectively — the // mob's Defense stat is not involved. Science has no attack styles, so no style // bonus applies. -func (g *Game) scienceAttack(sess *net.Session, p *player.Player, mob *world.MobInstance, mod *ModDef) bool { +func (g *Game) scienceAttack(sess *net.Session, p *player.Player, mob *world.MobInstance, mod *ModDef) { if p.Level(player.Science) < mod.Level { sess.WriteLine(fmt.Sprintf("You need level %d science to trigger %s.", mod.Level, mod.Name)) - return false + return } if g.processConsumeQueue(p, sess) { - return true + return } g.consumeJunkCost(p, mod) @@ -78,7 +78,7 @@ func (g *Game) scienceAttack(sess *net.Session, p *player.Player, mob *world.Mob g.awardSkillXP(sess, p, player.Hitpoints, hpXP) gains = append(gains, xpGain{string(player.Hitpoints), hpXP}) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) mobName := mobDisplayName(mob, true) prefix := fmt.Sprintf("%s hits %s for %s damage.", @@ -91,7 +91,6 @@ func (g *Game) scienceAttack(sess *net.Session, p *player.Player, mob *world.Mob } else { sess.WriteLine(g.colorize(sess, "miss", fmt.Sprintf("%s fails to connect.", mod.Name))) } - return true } func (g *Game) findInventoryItem(p *player.Player, input string) (int, *player.InventorySlot) { diff --git a/internal/game/cmd_trigger_transport.go b/internal/game/cmd_trigger_transport.go index dbb0d16..f6c9c77 100644 --- a/internal/game/cmd_trigger_transport.go +++ b/internal/game/cmd_trigger_transport.go @@ -59,6 +59,6 @@ func (g *Game) executeTransportEffect(sess *net.Session, p *player.Player, mod * destName = room.Name } sess.WriteLine(fmt.Sprintf("You arrive at %s.", destName)) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) g.doLook(sess) } diff --git a/internal/game/cmd_use.go b/internal/game/cmd_use.go index 12dbc73..b975469 100644 --- a/internal/game/cmd_use.go +++ b/internal/game/cmd_use.go @@ -82,7 +82,7 @@ func (g *Game) useRoomObject(sess *net.Session, p *player.Player, input string) } recipes := g.stationRecipes(p, objSt.DefID) if len(recipes) > 0 { - g.showRecipeMenu(sess, p, recipes, def.Name) + g.showRecipeMenu(sess, recipes, def.Name) return true } bt := def.BehaviorType() @@ -140,7 +140,7 @@ func craftLabel(c *item.CraftDef) string { return "combine" } -func (g *Game) showRecipeMenu(sess *net.Session, p *player.Player, items []*item.ItemDef, stationName string) { +func (g *Game) showRecipeMenu(sess *net.Session, items []*item.ItemDef, stationName string) { if len(items) == 0 { sess.WriteLine(fmt.Sprintf("You don't have anything you can make at the %s.", stationName)) return @@ -299,8 +299,8 @@ func (g *Game) doUseItemOnTarget(sess *net.Session, p *player.Player, itemAName, itemBID := matchesB[0].ID - craftItems := g.findCraftItems(p, itemAID, itemBID) - fletchItems := g.findFletchItems(p, itemAID, itemBID) + craftItems := g.findCraftItems(itemAID, itemBID) + fletchItems := g.findFletchItems(itemAID, itemBID) if len(craftItems) > 0 && len(fletchItems) > 0 { var menu []map[string]string diff --git a/internal/game/cmd_wear.go b/internal/game/cmd_wear.go index c52321c..cea4d67 100644 --- a/internal/game/cmd_wear.go +++ b/internal/game/cmd_wear.go @@ -117,7 +117,7 @@ func (g *Game) doWear(sess *net.Session, input string) { } p.Equipment[def.EquipSlot()] = slot.ItemID - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) verb := "wear" if def.EquipmentType() != "" { @@ -150,7 +150,7 @@ func (g *Game) equipAmmo(sess *net.Session, p *player.Player, invIdx int, slot * p.AmmoQty = transferQty } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) displayName := g.itemColorize(sess, def, def.Name) sess.WriteLine(fmt.Sprintf("You equip %s (x%d).", displayName, p.AmmoQty)) } @@ -284,7 +284,7 @@ func (g *Game) doWearAll(sess *net.Session) { if len(equipped) == 0 { sess.WriteLine("Nothing new to wear.") } else { - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) sess.Write("You wear: ") formatPickupList(sess, equipped) } diff --git a/internal/game/combat_attack.go b/internal/game/combat_attack.go index bd16999..80994e1 100644 --- a/internal/game/combat_attack.go +++ b/internal/game/combat_attack.go @@ -288,7 +288,7 @@ func (g *Game) playerAttack(sess *net.Session, p *player.Player, mob *world.MobI } if combat.HitCheck(attRoll, defRoll) { - g.applyPlayerHit(sess, p, mob, attackType, weaponType, maxHit) + g.applyPlayerHit(sess, p, mob, weaponType, maxHit) } else { g.applyPlayerMiss(sess, p, mob, weaponType) } @@ -354,10 +354,10 @@ func (g *Game) playerAttackUnarmed(sess *net.Session, p *player.Player, mob *wor return } - attackType, _, attRoll, defRoll, maxHit := g.calculateUnarmedAttackRoll(p, mob) + _, _, attRoll, defRoll, maxHit := g.calculateUnarmedAttackRoll(p, mob) if combat.HitCheck(attRoll, defRoll) { - g.applyPlayerHit(sess, p, mob, attackType, item.EquipMeleeWeapon, maxHit) + g.applyPlayerHit(sess, p, mob, item.EquipMeleeWeapon, maxHit) } else { g.applyPlayerMiss(sess, p, mob, item.EquipMeleeWeapon) } @@ -379,7 +379,7 @@ func (g *Game) calculateUnarmedAttackRoll(p *player.Player, mob *world.MobInstan return } -func (g *Game) applyPlayerHit(sess *net.Session, p *player.Player, mob *world.MobInstance, attackType string, weaponType item.EquipmentType, maxHit int) { +func (g *Game) applyPlayerHit(sess *net.Session, p *player.Player, mob *world.MobInstance, weaponType item.EquipmentType, maxHit int) { dmg := combat.RollDamage(maxHit) mob.HP -= dmg @@ -403,14 +403,14 @@ func (g *Game) applyPlayerHit(sess *net.Session, p *player.Player, mob *world.Mo gains := g.awardCombatXP(sess, p, dmg, isRanged) if isRanged { - g.consumeAmmo(sess, p) + g.consumeAmmo(p) } if mob.IsTask() { - g.writeTaskProgress(sess, p, mob, dmg, gains) + g.writeTaskProgress(sess, p, mob, gains) } else { mobName := mobDisplayName(mob, true) - attacker := mobDisplayNameCap(mob, true) + attacker := mobDisplayNameCap(mob) prefix := fmt.Sprintf("You hit %s for %s damage.", g.colorize(sess, "mob", mobName), g.colorize(sess, "damage_dealt", g.dmgDisplay(sess, dmg))) prefix = padCombatPrefix(prefix, fmt.Sprintf("You hit %s for > %d < damage.", mobName, 999), @@ -430,7 +430,7 @@ func (g *Game) applyPlayerMiss(sess *net.Session, p *player.Player, mob *world.M sess.WriteLine(g.colorize(sess, "miss", fmt.Sprintf("You miss %s.", mobDisplayName(mob, true)))) if weaponType == item.EquipRangedWeapon { - g.consumeAmmo(sess, p) + g.consumeAmmo(p) if p.AmmoQty <= 0 { sess.WriteLine("You've run out of ammo!") g.Combat.Leave(p.Name) @@ -438,7 +438,7 @@ func (g *Game) applyPlayerMiss(sess *net.Session, p *player.Player, mob *world.M } } -func (g *Game) consumeAmmo(sess *net.Session, p *player.Player) { +func (g *Game) consumeAmmo(p *player.Player) { ammoID := p.Equipment[item.SlotAmmo] p.AmmoQty-- @@ -455,5 +455,5 @@ func (g *Game) consumeAmmo(sess *net.Session, p *player.Player) { } } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } diff --git a/internal/game/combat_mob.go b/internal/game/combat_mob.go index 7d60a11..c53d847 100644 --- a/internal/game/combat_mob.go +++ b/internal/game/combat_mob.go @@ -139,7 +139,7 @@ func (g *Game) applyMobHit(sess *net.Session, p *player.Player, mob *world.MobIn if cs != nil && !cs.DamageWarningShown { cs.DamageWarningShown = true fbName := g.itemDisplayName(mob.DamageWithout) - attacker := mobDisplayNameCap(mob, true) + attacker := mobDisplayNameCap(mob) sess.WriteLine(g.colorize(sess, "warning", fmt.Sprintf(" %s's attack is extra effective! Equip %s for protection.", attacker, fbName))) @@ -152,9 +152,9 @@ func (g *Game) applyMobHit(sess *net.Session, p *player.Player, mob *world.MobIn p.HP = 0 } p.StartRegen() - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) - attacker := mobDisplayNameCap(mob, true) + attacker := mobDisplayNameCap(mob) mobName := mobDisplayName(mob, true) prefix := fmt.Sprintf("%s hits you for %s damage.", g.colorize(sess, "mob", attacker), g.colorize(sess, "damage_taken", g.dmgDisplay(sess, dmg))) prefix = padCombatPrefix(prefix, @@ -174,7 +174,7 @@ func (g *Game) applyMobHit(sess *net.Session, p *player.Player, mob *world.MobIn sess.WriteLine(g.colorize(sess, "miss", fmt.Sprintf("You power through %s's relentless attacks and just flee!", mobDisplayName(mob, false)))) - g.beginEscapeMove(sess, p) + g.beginEscapeMove(p) } else { p.ClearEscape() p.WalkSequence = nil @@ -205,11 +205,11 @@ func padCombatPrefix(prefix, plainSelf, plainOther string) string { } func (g *Game) applyMobMiss(sess *net.Session, p *player.Player, mob *world.MobInstance) { - attacker := mobDisplayNameCap(mob, true) + attacker := mobDisplayNameCap(mob) sess.WriteLine(g.colorize(sess, "miss", fmt.Sprintf("%s misses you.", attacker))) if p.EscapeDir != "" { - g.beginEscapeMove(sess, p) + g.beginEscapeMove(p) } } @@ -238,7 +238,7 @@ func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInst g.scheduleMobRespawn(mob) if p.EscapeDir != "" { - g.beginEscapeMove(sess, p) + g.beginEscapeMove(p) return } @@ -280,7 +280,7 @@ func (g *Game) announceKill(sess *net.Session, p *player.Player, mob *world.MobI func (g *Game) awardKillDrops(sess *net.Session, p *player.Player, mob *world.MobInstance, isTask bool) { dropLabel := "You receive:" if !isTask { - dropLabel = mobDisplayNameCap(mob, true) + " drops:" + dropLabel = mobDisplayNameCap(mob) + " drops:" } writeDrop := func(itemID string, qty int) { @@ -373,7 +373,7 @@ func (g *Game) killPlayer(sess *net.Session, p *player.Player, mob *world.MobIns p.HP = p.MaxHP() p.RunEnergy = p.MaxRunEnergy() p.RoomID = g.StartingRoom - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) if g.Hub != nil { g.Hub.EnterRoom(sess, p.RoomID) } @@ -412,7 +412,7 @@ func (g *Game) awardCombatXP(sess *net.Session, p *player.Player, dmg int, isRan for _, gain := range gains { g.awardSkillXP(sess, p, player.SkillName(gain.Skill), gain.XP) } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) return gains } @@ -432,7 +432,7 @@ func (g *Game) stopCombat(playerName string) { // escape, and that branch is itself gated on moveTicks returning > 0; the // Cape-of-Agility / GodMode 0-tick bypass never enters the stash path, so there // is no 0-tick branch to handle here. -func (g *Game) beginEscapeMove(sess *net.Session, p *player.Player) { +func (g *Game) beginEscapeMove(p *player.Player) { p.MoveDirection = p.EscapeDir p.MoveTarget = p.EscapeTarget p.MovePendingTrigger = p.EscapeTrigger diff --git a/internal/game/core_hacking.go b/internal/game/core_hacking.go index a537057..af7a211 100644 --- a/internal/game/core_hacking.go +++ b/internal/game/core_hacking.go @@ -83,7 +83,7 @@ func (g *Game) endHacking(sess *net.Session, p *player.Player, completed bool, w sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf("(+%dxp %s)", xp, player.SkillAbbr[player.Hacking]))) } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } delete(g.hackingStates, p.Name) diff --git a/internal/game/core_login_account.go b/internal/game/core_login_account.go index 4c05373..47421e9 100644 --- a/internal/game/core_login_account.go +++ b/internal/game/core_login_account.go @@ -194,7 +194,7 @@ func (g *Game) handleColorChoice(sess *net.Session, input string) { acc.Options = make(map[string]any) } acc.Options["color"] = "none" - g.AccountStore.SaveAccount(acc) + g.saveAccount(acc) } } g.showMenu(sess) diff --git a/internal/game/core_production.go b/internal/game/core_production.go index 192f8ec..34ad8a9 100644 --- a/internal/game/core_production.go +++ b/internal/game/core_production.go @@ -7,7 +7,6 @@ import ( ) func (g *Game) findTool(p *player.Player, toolTypes []string) (toolSpeed float64, toolName string, toolType string, found bool) { - toolSpeed = -1 if itemID, ok := p.Equipment[item.SlotMainHand]; ok { if def, err := g.ItemStore.Load(itemID); err == nil { for _, t := range toolTypes { diff --git a/internal/game/core_utils.go b/internal/game/core_utils.go index a07e789..de997cb 100644 --- a/internal/game/core_utils.go +++ b/internal/game/core_utils.go @@ -44,16 +44,13 @@ func mobDisplayName(m *world.MobInstance, definite bool) string { return "a " + m.Name } -// mobDisplayNameCap is like mobDisplayName but capitalizes the article for use -// at the start of a sentence (e.g. "The goblin hits you."). -func mobDisplayNameCap(m *world.MobInstance, definite bool) string { +// mobDisplayNameCap is like mobDisplayName but always uses the definite +// article, for use at the start of a sentence (e.g. "The goblin hits you."). +func mobDisplayNameCap(m *world.MobInstance) string { if m.Unique { return m.Name } - if definite { - return "The " + m.Name - } - return "A " + m.Name + return "The " + m.Name } // itemDisplayName returns an item's display name for the given id, falling back diff --git a/internal/game/credits_pickup_test.go b/internal/game/credits_pickup_test.go index 61509e4..721c550 100644 --- a/internal/game/credits_pickup_test.go +++ b/internal/game/credits_pickup_test.go @@ -13,7 +13,7 @@ import ( // "That's not yours!". func TestPickupCreditsSentinelQuantity(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.Credits = 100 @@ -30,7 +30,7 @@ func TestPickupCreditsSentinelQuantity(t *testing.T) { // the "get all" path. func TestGetAllPicksUpCreditsWithSentinelQuantity(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.Credits = 0 diff --git a/internal/game/economy_recovery_identity_test.go b/internal/game/economy_recovery_identity_test.go index eceeecc..c2d3d0a 100644 --- a/internal/game/economy_recovery_identity_test.go +++ b/internal/game/economy_recovery_identity_test.go @@ -21,7 +21,7 @@ import ( // inspection instead of being silently settled against B's balance. func TestRecoveryDoesNotPayReusedCharacterName(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.ID = "original-character" @@ -63,7 +63,7 @@ func TestRecoveryDoesNotPayReusedCharacterName(t *testing.T) { // gone and the rollback silently erases a payout that was already applied. func TestEconomyRecoveryRunsAfterLifecycleRollback(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.ID = "stable-character" diff --git a/internal/game/economy_recovery_regression_test.go b/internal/game/economy_recovery_regression_test.go index f818037..f9d3b37 100644 --- a/internal/game/economy_recovery_regression_test.go +++ b/internal/game/economy_recovery_regression_test.go @@ -17,7 +17,7 @@ import ( // startup recovery — the documented refund-on-crash policy. func TestRecoveryRefundsInFlightMachineWager(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.Credits = 100 @@ -46,7 +46,7 @@ func TestRecoveryRefundsInFlightMachineWager(t *testing.T) { // identical before and after a chip-only wager). func TestRecoveryRefundsChipOnlyWager(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.Credits = 100 @@ -80,7 +80,7 @@ func TestRecoveryRefundsChipOnlyWager(t *testing.T) { // settles the stake from that evidence — exactly once, no double refund. func TestRecoveryReappliesLostPayoutWithEvidence(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.Credits = 90 // post-debit balance: the 10-credit stake was charged @@ -113,7 +113,7 @@ func TestRecoveryReappliesLostPayoutWithEvidence(t *testing.T) { // records, newest processed first) both refund after a crash. func TestRecoveryRefundsSequentialStakes(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.Credits = 80 // bet 10 then doubled: 100 -> 90 -> 80 @@ -145,7 +145,7 @@ func TestRecoveryRefundsSequentialStakes(t *testing.T) { // the balance to `after` would silently drop 20 owed credits. func TestRecoveryReappliesIdenticalFailedPayouts(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.Credits = 90 // post-debit balance: both payouts failed to save @@ -177,7 +177,7 @@ func TestRecoveryReappliesIdenticalFailedPayouts(t *testing.T) { // player frees a slot, the same outstanding stake refunds successfully. func TestRefundOutstandingKeepsStakesOpenWithoutInventoryRoom(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.SetInvSlot(0, &player.InventorySlot{ItemID: "chips", Quantity: 10}) @@ -222,7 +222,7 @@ func TestRefundOutstandingKeepsStakesOpenWithoutInventoryRoom(t *testing.T) { // chips — the record stays open for operator inspection. func TestRecoveryKeepsWagerOpenWhenChipsDoNotFit(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { // Durable state at crash time: the 10-chip debit landed (10 -> 0 @@ -257,7 +257,7 @@ func TestRecoveryKeepsWagerOpenWhenChipsDoNotFit(t *testing.T) { // a liability is never silently dropped. func TestRecoveryPreservesAmbiguousPayout(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { // A payout owed at balance 100 failed to save; the player then @@ -298,7 +298,7 @@ func TestRecoveryPreservesAmbiguousPayout(t *testing.T) { // liability is never erased: its record stays open for operator inspection. func TestRecoveryKeepsUnpaidSiblingsOpenAtCreditCap(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.Credits = casino.MaxCredits - 30 @@ -332,7 +332,7 @@ func TestRecoveryKeepsUnpaidSiblingsOpenAtCreditCap(t *testing.T) { // may be settled only once nothing is owed. func TestRecoveryRestoresChipsFromMixedRefundCrash(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.Credits = 100 @@ -379,7 +379,7 @@ func TestRecoveryRestoresChipsFromMixedRefundCrash(t *testing.T) { // leaves the chip side outstanding. func TestRecoveryDoesNotReturnPaidChipStakeInKind(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.Credits = 100 @@ -414,7 +414,7 @@ func TestRecoveryDoesNotReturnPaidChipStakeInKind(t *testing.T) { // and silently erases the owed amount. func TestRecoveryUnpaidLiabilitySurvivesLaterRecoveries(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.Credits = casino.MaxCredits - 30 @@ -453,7 +453,7 @@ func TestRecoveryUnpaidLiabilitySurvivesLaterRecoveries(t *testing.T) { // applied — a reported win is never silently dropped. func TestCapBlockedPayoutIsJournaled(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.Credits = casino.MaxCredits @@ -499,7 +499,7 @@ func TestCapBlockedPayoutIsJournaled(t *testing.T) { // the open record is still owed. func TestRecoveryReappliesPartiallyPaidIdenticalPayouts(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.Credits = 90 diff --git a/internal/game/economy_shutdown_test.go b/internal/game/economy_shutdown_test.go index a3e6adb..efdd5fd 100644 --- a/internal/game/economy_shutdown_test.go +++ b/internal/game/economy_shutdown_test.go @@ -83,7 +83,7 @@ func writeEconomyJournal(t *testing.T, dir string, records []economyRecord) { func TestEconomyWagerDebitsDurallyAndSettles(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.Credits = 100 @@ -119,7 +119,7 @@ func TestEconomyWagerDebitsDurallyAndSettles(t *testing.T) { func TestEconomyFailedSaveRevertsAndVoids(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.Credits = 100 @@ -224,8 +224,8 @@ func TestEconomyRecoveryAppliesLostPayout(t *testing.T) { func TestShutdownSavesOnlineCharactersAndRefundsWagers(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) - h.seedAccount("bob", "pw", false) + h.seedAccount("alice") + h.seedAccount("bob") h.seedCharacter("alice", "alice") h.seedCharacter("bob", "bob") sA, _ := h.connect("alice") diff --git a/internal/game/exit_migrate.go b/internal/game/exit_migrate.go index c678591..1fceca9 100644 --- a/internal/game/exit_migrate.go +++ b/internal/game/exit_migrate.go @@ -128,7 +128,7 @@ func (g *Game) RewriteRoomIDs(idMap map[int]int) { continue } if rewritePlayerRoomIDs(sess.Player, idMap) { - g.AccountStore.SaveCharacter(sess.Player) + g.saveCharacter(sess.Player) } online[sess.Player.Name] = true } @@ -146,7 +146,7 @@ func (g *Game) RewriteRoomIDs(idMap map[int]int) { continue } if rewritePlayerRoomIDs(p, idMap) { - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } } } diff --git a/internal/game/game.go b/internal/game/game.go index ca38e27..096e070 100644 --- a/internal/game/game.go +++ b/internal/game/game.go @@ -1,3 +1,5 @@ +// Package game contains the MUD itself: commands, skilling actions, combat, +// triggers, rendering, economy, and persistence glue around the world. package game import ( @@ -173,7 +175,7 @@ func (g *Game) SetHub(hub *net.Hub) { if p := sess.Player; p != nil { g.restoreGodPlayer(p) g.persistLockedOnDisconnect(p) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) p.DeactivateAllTechs() g.detachSession(p.Name) delete(g.hackingStates, p.Name) diff --git a/internal/game/hacking/hacking.go b/internal/game/hacking/hacking.go index f022ee1..fe6089b 100644 --- a/internal/game/hacking/hacking.go +++ b/internal/game/hacking/hacking.go @@ -1,3 +1,5 @@ +// Package hacking implements the Hacking skill minigames (liar's dice, +// mastermind, …) played against terminals around Vesta. package hacking type Minigame interface { diff --git a/internal/game/hacking/liars_dice.go b/internal/game/hacking/liars_dice.go index 8e21b94..3c7ac31 100644 --- a/internal/game/hacking/liars_dice.go +++ b/internal/game/hacking/liars_dice.go @@ -250,11 +250,9 @@ func (l *LiarsDiceGame) doStatus() string { sb.WriteString(fmt.Sprintf("Your fragments: %s (You: %d, System: %d)\n", l.diceStr(l.playerDice), len(l.playerDice), len(l.aiDice))) if l.hasBid { - who := "System" + who := "you" if !l.playerTurn { who = "System" - } else { - who = "you" } sb.WriteString(fmt.Sprintf("Current bid: %d %s (by %s)\n", l.currentBid.Quantity, faceNamePlural(l.currentBid.Face), who)) } diff --git a/internal/game/harness_test.go b/internal/game/harness_test.go index a368634..c80f437 100644 --- a/internal/game/harness_test.go +++ b/internal/game/harness_test.go @@ -147,13 +147,14 @@ func seedWorld(t *testing.T, dir string) { } // seedAccount writes an account file directly (bypassing the login menu). -func (h *harness) seedAccount(name, password string, admin bool) { +func (h *harness) seedAccount(name string) { + const password = "pw" t := h.t hash, err := player.HashPassword(password) if err != nil { t.Fatalf("hash: %v", err) } - acc := &player.Account{Name: name, PasswordHash: hash, Admin: admin} + acc := &player.Account{Name: name, PasswordHash: hash} if err := h.g.AccountStore.SaveAccount(acc); err != nil { t.Fatalf("seed account %s: %v", name, err) } @@ -239,11 +240,9 @@ func (h *harness) input(sess *net.Session, lines <-chan string) { } } -// goInput starts input goroutine(s) for a session, returning the send channel. -func (h *harness) goInput(sess *net.Session, workers int) chan<- string { +// goInput starts the input goroutine for a session, returning the send channel. +func (h *harness) goInput(sess *net.Session) chan<- string { lines := make(chan string, 64) - for i := 0; i < workers; i++ { - go h.input(sess, lines) - } + go h.input(sess, lines) return lines } diff --git a/internal/game/inventory_pickup_test.go b/internal/game/inventory_pickup_test.go index a0d267e..2fc89b7 100644 --- a/internal/game/inventory_pickup_test.go +++ b/internal/game/inventory_pickup_test.go @@ -14,7 +14,7 @@ import ( func TestPickupMergesIntoFullInventory(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") writeSeedFile(t, filepath.Join(h.dir, "items", "chips.yaml"), []byte("id: chips\nname: chips\nstackable: true\n")) sess := &net.Session{Conn: newChanConn(), Player: p, State: net.StateGame} @@ -34,7 +34,7 @@ func TestPickupMergesIntoFullInventory(t *testing.T) { func TestPickupFullInventoryWithoutStackStillRefused(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") writeSeedFile(t, filepath.Join(h.dir, "items", "chips.yaml"), []byte("id: chips\nname: chips\nstackable: true\n")) sess := &net.Session{Conn: newChanConn(), Player: p, State: net.StateGame} diff --git a/internal/game/lifecycle_txn_test.go b/internal/game/lifecycle_txn_test.go index f575298..ea1cd65 100644 --- a/internal/game/lifecycle_txn_test.go +++ b/internal/game/lifecycle_txn_test.go @@ -17,7 +17,7 @@ import ( // sessions authenticated to it. func setupAcct(t *testing.T, h *harness) (*net.Session, *net.Session) { t.Helper() - h.seedAccount("acct", "pw", false) + h.seedAccount("acct") h.seedCharacter("alice", "acct") h.seedCharacter("bob", "acct") s1 := h.connectMenu("acct") @@ -110,7 +110,7 @@ func TestLifecycleStaleMenuAfterDelete(t *testing.T) { // through). func TestLifecycleRenameBeatsConnect(t *testing.T) { h := newHarness(t) - h.seedAccount("carl", "pw", false) + h.seedAccount("carl") h.seedCharacter("carl", "carl") sess, conn := h.connect("carl") diff --git a/internal/game/look_entities.go b/internal/game/look_entities.go index 0d08551..70077a3 100644 --- a/internal/game/look_entities.go +++ b/internal/game/look_entities.go @@ -13,7 +13,7 @@ import ( ) func (g *Game) showRoomMobs(sess *net.Session, p *player.Player, room *world.Room) []string { - mobs := g.MobStore.MobsInRoom(p.RoomID) + mobs := g.MobStore.MobsInRoom(room.ID) if len(mobs) == 0 { return nil } @@ -105,7 +105,7 @@ func (g *Game) showRoomObjects(sess *net.Session, p *player.Player, room *world. if p.GodMode { var tags []string tagLabel := func(t string) string { - return color.Render(g.colorMode(sess), g.resolveConstant(sess, "god_tag_label"), t) + return color.Render(g.colorMode(sess), g.resolveConstant("god_tag_label"), t) } if isLocal { tags = append(tags, tagLabel("[LOCAL]")) @@ -259,7 +259,7 @@ func casinoGameLabel(game casino.GameName) string { } func (g *Game) showGroundItems(sess *net.Session, p *player.Player, room *world.Room) []string { - ground := g.World.GroundItemsDetailed(p.RoomID) + ground := g.World.GroundItemsDetailed(room.ID) if len(ground) == 0 { return nil } @@ -381,7 +381,7 @@ func (g *Game) showGroundItems(sess *net.Session, p *player.Player, room *world. } func (g *Game) showRoomPlayers(sess *net.Session, p *player.Player, room *world.Room) { - others := g.Hub.PlayersInRoom(p.RoomID) + others := g.Hub.PlayersInRoom(room.ID) for _, other := range others { if other != sess && other.Player != nil { op := other.Player diff --git a/internal/game/look_room.go b/internal/game/look_room.go index 9d77ebe..3208f40 100644 --- a/internal/game/look_room.go +++ b/internal/game/look_room.go @@ -53,7 +53,7 @@ func (g *Game) roomSymbolBracket(sess *net.Session, p *player.Player, room *worl color.Render(mode, dimSpec, "]") } -func (g *Game) showRoomDescription(sess *net.Session, p *player.Player, room *world.Room, descWidth int) []string { +func (g *Game) showRoomDescription(sess *net.Session, room *world.Room, descWidth int) []string { if descWidth <= 0 { descWidth = 70 } diff --git a/internal/game/look_target.go b/internal/game/look_target.go index 8368946..5f3818d 100644 --- a/internal/game/look_target.go +++ b/internal/game/look_target.go @@ -160,7 +160,7 @@ func (g *Game) doLookTarget(sess *net.Session, input string) { if def.Safespot != nil { realSt := g.World.GetObjState(p.RoomID, st.DefID, st.Index) if realSt != nil { - levelStr := "intact" + var levelStr string if realSt.SafespotLevel <= 0 { levelStr = fmt.Sprintf("intact (level %d/%d)", len(def.Safespot.Levels), len(def.Safespot.Levels)) } else if realSt.SafespotLevel < len(def.Safespot.Levels) { diff --git a/internal/game/migrate_rooms_test.go b/internal/game/migrate_rooms_test.go index eb4a44c..57eb46a 100644 --- a/internal/game/migrate_rooms_test.go +++ b/internal/game/migrate_rooms_test.go @@ -15,10 +15,10 @@ import ( // in one owner task; nothing stays attached to room 2. func TestMigrateRoomIDsMovesEverythingTogether(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) - p := h.seedCharacter("alice", "alice") + h.seedAccount("alice") + h.seedCharacter("alice", "alice") sess, _ := h.connect("alice") - p = sess.Player + p := sess.Player h.g.RunOnOwner(func() { p.RoomID = 2 @@ -59,7 +59,7 @@ func TestMigrateRoomIDsMovesEverythingTogether(t *testing.T) { // TestMigrateRoomIDsRoundTrip preserves participation both directions. func TestMigrateRoomIDsRoundTrip(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") h.seedCharacter("alice", "alice") sess, _ := h.connect("alice") p := sess.Player @@ -89,7 +89,7 @@ func TestMigrateRoomIDsRoundTrip(t *testing.T) { // saved to disk (RoomID rewritten in the character file). func TestMigrateRoomIDsPersistsOnlinePlayer(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") h.seedCharacter("alice", "alice") sess, _ := h.connect("alice") p := sess.Player diff --git a/internal/game/owner_concurrency_test.go b/internal/game/owner_concurrency_test.go index 58e426b..72cf878 100644 --- a/internal/game/owner_concurrency_test.go +++ b/internal/game/owner_concurrency_test.go @@ -16,16 +16,16 @@ import ( // and hub disconnects concurrently. func TestOwnerSerializesEntryPoints(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) - h.seedAccount("bob", "pw", false) + h.seedAccount("alice") + h.seedAccount("bob") h.seedCharacter("alice", "alice") h.seedCharacter("bob", "bob") aSess, _ := h.connect("alice") bSess, _ := h.connect("bob") - aLines := h.goInput(aSess, 1) - bLines := h.goInput(bSess, 1) + aLines := h.goInput(aSess) + bLines := h.goInput(bSess) var wg sync.WaitGroup wg.Add(2) @@ -61,10 +61,10 @@ func TestOwnerSerializesEntryPoints(t *testing.T) { // concurrently with live movement and ticking. func TestOwnerAdminRewriteWhilePlaying(t *testing.T) { h := newHarness(t) - h.seedAccount("carol", "pw", false) + h.seedAccount("carol") h.seedCharacter("carol", "carol") sess, _ := h.connect("carol") - lines := h.goInput(sess, 1) + lines := h.goInput(sess) var wg sync.WaitGroup wg.Add(2) @@ -93,8 +93,8 @@ func TestOwnerAdminRewriteWhilePlaying(t *testing.T) { // players from receiving output and progressing. func TestSlowClientDoesNotStallTheWorld(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) - h.seedAccount("bob", "pw", false) + h.seedAccount("alice") + h.seedAccount("bob") h.seedCharacter("alice", "alice") h.seedCharacter("bob", "bob") @@ -105,7 +105,7 @@ func TestSlowClientDoesNotStallTheWorld(t *testing.T) { bSess, bConn := h.connect("bob") aConn.writeGate = make(chan struct{}) - bLines := h.goInput(bSess, 1) + bLines := h.goInput(bSess) for i := 0; i < 300; i++ { aSess.WriteLine("spam for the stalled client") @@ -127,11 +127,11 @@ func TestSlowClientDoesNotStallTheWorld(t *testing.T) { // (save + registry cleanup) serializes with session input on the owner. func TestOwnerDisconnectDuringInput(t *testing.T) { h := newHarness(t) - h.seedAccount("dave", "pw", false) + h.seedAccount("dave") h.seedCharacter("dave", "dave") sess, conn := h.connect("dave") - lines := h.goInput(sess, 1) + lines := h.goInput(sess) done := make(chan struct{}) go func() { defer close(done) diff --git a/internal/game/owner_disconnect_test.go b/internal/game/owner_disconnect_test.go index d19b194..9140a40 100644 --- a/internal/game/owner_disconnect_test.go +++ b/internal/game/owner_disconnect_test.go @@ -17,7 +17,7 @@ import ( // callback must not block on the owner from the owner. func TestDisconnectTickHardRemoveCompletesOnOwner(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") h.seedCharacter("alice", "alice") sess, _ := h.connect("alice") diff --git a/internal/game/production_engine.go b/internal/game/production_engine.go index 260cef0..65c01b9 100644 --- a/internal/game/production_engine.go +++ b/internal/game/production_engine.go @@ -258,7 +258,7 @@ func (g *Game) advanceProduction(sess *net.Session, p *player.Player) bool { sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d %s! ***", newLevel, skill))) } } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) var defaultSuccess string if info.SuccessMessage != "" { @@ -280,7 +280,7 @@ func (g *Game) advanceProduction(sess *net.Session, p *player.Player) bool { p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: craft.Fail, Quantity: 1}) } } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) msg := g.resolveCraftMessage(sess, craft.FailMessage, info.FailMessage, craft, item.ID, nil, p.HasItem) if msg != "" { @@ -340,10 +340,6 @@ func (g *Game) canContinueProduction(p *player.Player, item *item.ItemDef) bool if !craftHasAllItemsQty(craft, p.CountItem) { return false } - outputQty := craft.OutputQty - if outputQty <= 0 { - outputQty = 1 - } outDef, _ := g.ItemStore.Load(item.ID) if outDef != nil && outDef.Stackable { for i := 0; i < 28; i++ { diff --git a/internal/game/production_index.go b/internal/game/production_index.go index 672dcae..2646a43 100644 --- a/internal/game/production_index.go +++ b/internal/game/production_index.go @@ -220,9 +220,9 @@ func craftHasAllItemsQty(c *item.CraftDef, countItem func(string) int) bool { return true } -func consumeIngredients(c *item.CraftDef, hasItem func(string) bool, removeItem func(string, int) bool) bool { +func consumeIngredients(c *item.CraftDef, hasItem func(string) bool, removeItem func(string, int) bool) { if c == nil { - return false + return } for _, e := range c.Ingredients { found := false @@ -234,8 +234,7 @@ func consumeIngredients(c *item.CraftDef, hasItem func(string) bool, removeItem } } if !found { - return false + return } } - return true } diff --git a/internal/game/production_menu.go b/internal/game/production_menu.go index 10b7eb0..090214f 100644 --- a/internal/game/production_menu.go +++ b/internal/game/production_menu.go @@ -127,7 +127,7 @@ func (g *Game) showProductionTable(sess *net.Session, p *player.Player, items [] mode := g.colorMode(sess) skillLevel := p.Level(player.SkillName(skill)) - dimSpec := g.resolveConstant(sess, "production_locked") + dimSpec := g.resolveConstant("production_locked") tbl := &ui.Table{ Title: title, @@ -259,7 +259,7 @@ func (g *Game) handleProductChoice(sess *net.Session, input string) { p.EnsureFlags() g.setPlayerFlag(p, lastFlagKey, sel.Item.ID) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) if background { g.startFletchAction(sess, p, sel.Item, sel.Count) diff --git a/internal/game/quit_casino_test.go b/internal/game/quit_casino_test.go index f055e07..f905fca 100644 --- a/internal/game/quit_casino_test.go +++ b/internal/game/quit_casino_test.go @@ -37,8 +37,8 @@ func quitTestGame(t *testing.T) (*Game, *net.Session) { return g, sess } -func (g *Game) quitResting(name string) bool { - _, ok := g.restTimers[name] +func (g *Game) quitResting() bool { + _, ok := g.restTimers["alice"] return ok } @@ -63,7 +63,7 @@ func TestQuitBlockedMidRoundKeepsWager(t *testing.T) { if _, playing := g.Casino.Participation("alice"); !playing { t.Fatal("quit mid-round removed the player from the table") } - if g.quitResting("alice") { + if g.quitResting() { t.Fatal("blocked quit started the rest countdown") } // The stake is still committed to the round, not refunded. @@ -90,7 +90,7 @@ func TestQuitBlockedMidRoundBaccarat(t *testing.T) { if _, playing := g.Casino.Participation("alice"); !playing { t.Fatal("quit mid-round removed the player from the baccarat table") } - if g.quitResting("alice") { + if g.quitResting() { t.Fatal("blocked quit started the rest countdown") } } @@ -111,7 +111,7 @@ func TestQuitLeavesTableBetweenRounds(t *testing.T) { if _, playing := g.Casino.Participation("alice"); playing { t.Fatal("quit did not free the table seat") } - if !g.quitResting("alice") { + if !g.quitResting() { t.Fatal("quit did not start the rest countdown") } } @@ -129,7 +129,7 @@ func TestQuitLeavesMachine(t *testing.T) { if _, playing := g.Casino.Participation("alice"); playing { t.Fatal("quit did not free the machine") } - if !g.quitResting("alice") { + if !g.quitResting() { t.Fatal("quit did not start the rest countdown") } if strings.Contains(string(conn.data), "WARNING") { @@ -145,7 +145,7 @@ func TestStopCancelsQuit(t *testing.T) { g.executeStop(sess, nil, "stop") - if g.quitResting("alice") { + if g.quitResting() { t.Fatal("stop did not cancel the pending quit") } } @@ -185,7 +185,7 @@ func TestQuitNowForfeitsOnlyAtLogout(t *testing.T) { if _, playing := g.Casino.Participation("alice"); !playing { t.Fatal("quit now freed the seat before the logout tick") } - if !g.quitResting("alice") { + if !g.quitResting() { t.Fatal("quit now did not start the rest countdown") } @@ -210,7 +210,7 @@ func TestQuitNowForfeitsOnlyAtLogout(t *testing.T) { if _, stillLoggedIn := g.loggedInChars["alice"]; stillLoggedIn { t.Fatal("quit now did not remove the session from the logged-in map") } - if g.quitResting("alice") { + if g.quitResting() { t.Fatal("countdown timer was not cleared after logout") } } @@ -227,7 +227,7 @@ func TestQuitNowCancelledByStopMidRound(t *testing.T) { g.doQuitNow(sess) g.executeStop(sess, nil, "stop") - if g.quitResting("alice") { + if g.quitResting() { t.Fatal("stop did not cancel the pending quit-now countdown") } if _, playing := g.Casino.Participation("alice"); !playing { diff --git a/internal/game/readiness_regression_test.go b/internal/game/readiness_regression_test.go index 752b755..7fa2b2e 100644 --- a/internal/game/readiness_regression_test.go +++ b/internal/game/readiness_regression_test.go @@ -21,7 +21,7 @@ import ( // bool/int pair is a data race and can lose the disconnect entirely. func TestDisconnectRaceIsFreeAndNeverLost(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") h.seedCharacter("alice", "alice") sess, _ := h.connect("alice") @@ -49,7 +49,7 @@ func TestDisconnectRaceIsFreeAndNeverLost(t *testing.T) { // takeable stock — never half-remove the ground with nothing delivered. func TestReservedPickupDoesNotDestroyItems(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") writeSeedFile(t, filepath.Join(h.dir, "items", "chips.yaml"), []byte("id: chips\nname: chips\nstackable: true\n")) sess := &net.Session{Conn: newChanConn(), Player: p, State: net.StateGame} @@ -76,7 +76,7 @@ func TestReservedPickupDoesNotDestroyItems(t *testing.T) { // the crash and recovery still delivers the owed payout. func TestOwedPayoutSurvivesCharacterRename(t *testing.T) { h := newHarness(t) - h.seedAccount("acct", "pw", false) + h.seedAccount("acct") p := h.seedCharacter("alice", "acct") h.g.RunOnOwner(func() { p.ID = "stable-char-id" // production characters carry a stable ID @@ -132,7 +132,7 @@ func TestAccountRenamePreservesAccessAndOwnership(t *testing.T) { // round resolved, so recovery must not ALSO return the chip stake in kind. func TestSettledSurrenderDoesNotRefundChipStake(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.Credits = 100 @@ -166,7 +166,7 @@ func TestSettledSurrenderDoesNotRefundChipStake(t *testing.T) { // recovery (after the disk is fixed) re-applies the owed payout. func TestRecoverySaveFailureRetriesOnNextStartup(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") charDir := filepath.Join(h.dir, "players", "characters") t.Cleanup(func() { os.Chmod(charDir, 0755) }) @@ -208,7 +208,7 @@ func TestRecoverySaveFailureRetriesOnNextStartup(t *testing.T) { // marker). func TestLostResolutionMarkerSurfacesAndRefunds(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.Credits = 100 @@ -247,7 +247,7 @@ func TestLostResolutionMarkerSurfacesAndRefunds(t *testing.T) { // is not durable. func TestUnjournalablePayoutRefundsStake(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") p := h.seedCharacter("alice", "alice") h.g.RunOnOwner(func() { p.Credits = 100 diff --git a/internal/game/render_color.go b/internal/game/render_color.go index ab3692e..ccce05e 100644 --- a/internal/game/render_color.go +++ b/internal/game/render_color.go @@ -47,7 +47,7 @@ func (g *Game) colorize(sess *net.Session, category, text string) string { // per-player override map — constants are admin-tunable only — so players // have no way to change them. Falls back through the file's constant_colors // map, then to the built-in DefaultConstantColors. -func (g *Game) resolveConstant(sess *net.Session, category string) color.ColorSpec { +func (g *Game) resolveConstant(category string) color.ColorSpec { if g.ConstantColorConfig != nil { if val, ok := (*g.ConstantColorConfig)[category]; ok && val != "" { return color.Parse(val) @@ -62,14 +62,14 @@ func (g *Game) resolveConstant(sess *net.Session, category string) color.ColorSp // colorizeConstant paints text with a constant color spec, bypassing the // player override tier entirely. func (g *Game) colorizeConstant(sess *net.Session, category, text string) string { - spec := g.resolveConstant(sess, category) + spec := g.resolveConstant(category) return color.Render(g.colorMode(sess), spec, text) } // levelColorSpec returns the color spec for the level-difference band shown // next to mob/player levels (green/red/white by how far the target is from // the viewer). Uses admin-tunable constant_colors; falls back to built-ins. -func (g *Game) levelColorSpec(sess *net.Session, myLevel, theirLevel int) color.ColorSpec { +func (g *Game) levelColorSpec(myLevel, theirLevel int) color.ColorSpec { diff := theirLevel - myLevel var name string switch { @@ -84,11 +84,11 @@ func (g *Game) levelColorSpec(sess *net.Session, myLevel, theirLevel int) color. default: name = "level_diff_down_big" } - return g.resolveConstant(sess, name) + return g.resolveConstant(name) } func (g *Game) levelColorize(sess *net.Session, myLevel, theirLevel int, text string) string { - spec := g.levelColorSpec(sess, myLevel, theirLevel) + spec := g.levelColorSpec(myLevel, theirLevel) return color.Render(g.colorMode(sess), spec, text) } @@ -130,7 +130,7 @@ func (g *Game) namedColorSpecs(sess *net.Session) map[string]color.ColorSpec { } } for name := range config.DefaultConstantColors() { - if spec := g.resolveConstant(sess, name); !spec.Empty() { + if spec := g.resolveConstant(name); !spec.Empty() { names[name] = spec } } @@ -139,7 +139,7 @@ func (g *Game) namedColorSpecs(sess *net.Session) map[string]color.ColorSpec { if _, ok := names[name]; ok { continue } - if spec := g.resolveConstant(sess, name); !spec.Empty() { + if spec := g.resolveConstant(name); !spec.Empty() { names[name] = spec } } diff --git a/internal/game/render_combat.go b/internal/game/render_combat.go index 4055b4d..24e5d01 100644 --- a/internal/game/render_combat.go +++ b/internal/game/render_combat.go @@ -33,13 +33,13 @@ func (g *Game) renderBarGeneric(sess *net.Session, current, max int, prefix stri var spec color.ColorSpec switch { case pct >= 0.7: - spec = g.resolveConstant(sess, prefix+"_high") + spec = g.resolveConstant(prefix + "_high") case pct >= 0.4: - spec = g.resolveConstant(sess, prefix+"_med") + spec = g.resolveConstant(prefix + "_med") default: - spec = g.resolveConstant(sess, prefix+"_low") + spec = g.resolveConstant(prefix + "_low") } - empty := g.resolveConstant(sess, "hp_bar_empty") + empty := g.resolveConstant("hp_bar_empty") unicode := sess.Player != nil && sess.Player.OptionBool("unicode") style := &ui.BarStyle{FilledColor: spec.Fg, EmptyColor: empty.Fg, EmptyDim: empty.Dim} @@ -50,19 +50,19 @@ func (g *Game) renderBarGeneric(sess *net.Session, current, max int, prefix stri // hpBarStyle returns the per-tier fill/empty color spec for an HP bar at the // given ratio (high>=70%, med>=40%, low<40%). Colors come from the // constant_colors map (hp_bar_high/med/low/empty). -func (g *Game) hpBarStyle(sess *net.Session, current, max int) *ui.BarStyle { +func (g *Game) hpBarStyle(current, max int) *ui.BarStyle { if max <= 0 { max = 1 } pct := float64(current) / float64(max) - spec := g.resolveConstant(sess, "hp_bar_low") + spec := g.resolveConstant("hp_bar_low") switch { case pct >= 0.7: - spec = g.resolveConstant(sess, "hp_bar_high") + spec = g.resolveConstant("hp_bar_high") case pct >= 0.4: - spec = g.resolveConstant(sess, "hp_bar_med") + spec = g.resolveConstant("hp_bar_med") } - empty := g.resolveConstant(sess, "hp_bar_empty") + empty := g.resolveConstant("hp_bar_empty") return &ui.BarStyle{FilledColor: spec.Fg, EmptyColor: empty.Fg, EmptyDim: empty.Dim} } diff --git a/internal/game/save.go b/internal/game/save.go new file mode 100644 index 0000000..8e1af3d --- /dev/null +++ b/internal/game/save.go @@ -0,0 +1,26 @@ +package game + +import ( + "log" + + "thehouseoficarus/internal/player" +) + +// Best-effort persistence helpers. Gameplay code re-saves a character or +// account after most state changes; a save failure there is not recoverable +// by the caller, so these helpers log it once in one place instead of +// silently discarding it at ~100 call sites. Lifecycle transactions (create, +// delete, rename, migration) intentionally do NOT use these: they must +// propagate save errors to the caller. + +func (g *Game) saveCharacter(p *player.Player) { + if err := g.AccountStore.SaveCharacter(p); err != nil { + log.Printf("save character %s: %v", p.Name, err) + } +} + +func (g *Game) saveAccount(acc *player.Account) { + if err := g.AccountStore.SaveAccount(acc); err != nil { + log.Printf("save account %s: %v", acc.Name, err) + } +} diff --git a/internal/game/shutdown_admission_test.go b/internal/game/shutdown_admission_test.go index 6aa4be5..a7e7ee2 100644 --- a/internal/game/shutdown_admission_test.go +++ b/internal/game/shutdown_admission_test.go @@ -14,7 +14,7 @@ import ( func TestShutdownRejectsSessionInput(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") h.seedCharacter("alice", "alice") sess, _ := h.connect("alice") sess.State = net.StateGame @@ -37,7 +37,7 @@ func TestShutdownRejectsSessionInput(t *testing.T) { func TestShutdownRejectsInputDuringDrain(t *testing.T) { h := newHarness(t) - h.seedAccount("alice", "pw", false) + h.seedAccount("alice") h.seedCharacter("alice", "alice") sess, _ := h.connect("alice") sess.State = net.StateGame diff --git a/internal/game/sys_assassin.go b/internal/game/sys_assassin.go index fc814ec..0fd929e 100644 --- a/internal/game/sys_assassin.go +++ b/internal/game/sys_assassin.go @@ -51,7 +51,7 @@ func (g *Game) onAssassinKill(sess *net.Session, p *player.Player, mob *world.Mo } sess.WriteLine(g.colorize(sess, "assassin_task", fmt.Sprintf(" Assassin task: %d of %d %ss remaining.", remaining, total, name))) } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } func streakBonus(streak int) int { diff --git a/internal/game/sys_combat_test.go b/internal/game/sys_combat_test.go index 788e62c..dcd8e5e 100644 --- a/internal/game/sys_combat_test.go +++ b/internal/game/sys_combat_test.go @@ -8,7 +8,8 @@ import ( "thehouseoficarus/internal/world" ) -func spawnTestMob(g *Game, defID string, roomID int) *world.MobInstance { +func spawnTestMob(g *Game, roomID int) *world.MobInstance { + const defID = "skeleton" def, err := g.MobStore.LoadDef(defID) if err != nil { panic("spawnTestMob: " + err.Error()) @@ -27,7 +28,7 @@ func TestAggroInlineRollFiresOnArrival(t *testing.T) { defer patchAggroRand(0.0)() g := escapeTestGame() - spawnTestMob(g, "skeleton", 100) + spawnTestMob(g, 100) p := player.New("agro_fire") p.RoomID = 100 sess := escapeTestSession(p) @@ -44,7 +45,7 @@ func TestAggroPerTickRetrySubscriber(t *testing.T) { defer patchAggroRand(0.5)() g := escapeTestGame() - spawnTestMob(g, "skeleton", 200) + spawnTestMob(g, 200) p := player.New("agro_retry") p.RoomID = 200 sess := escapeTestSession(p) @@ -67,7 +68,7 @@ func TestAggroPerTickRetrySubscriber(t *testing.T) { defer patchAggroRand(0.5)() g := escapeTestGame() - spawnTestMob(g, "skeleton", 300) + spawnTestMob(g, 300) p := player.New("agro_leave") p.RoomID = 300 sess := escapeTestSession(p) diff --git a/internal/game/sys_escape_test.go b/internal/game/sys_escape_test.go index 0a64666..1fce4f4 100644 --- a/internal/game/sys_escape_test.go +++ b/internal/game/sys_escape_test.go @@ -89,8 +89,7 @@ func TestBeginEscapeMoveWithEnergy(t *testing.T) { p.EscapeTarget = 10 p.EscapeIsWalk = false - sess := escapeTestSession(p) - g.beginEscapeMove(sess, p) + g.beginEscapeMove(p) if p.EscapeDir != "" { t.Error("beginEscapeMove should clear EscapeDir") @@ -115,8 +114,7 @@ func TestBeginEscapeMoveWithoutEnergy(t *testing.T) { p.EscapeTarget = 5 p.EscapeIsWalk = false - sess := escapeTestSession(p) - g.beginEscapeMove(sess, p) + g.beginEscapeMove(p) if p.MoveTicks != 2 { t.Errorf("expected MoveTicks=2 without energy, got %d", p.MoveTicks) diff --git a/internal/game/sys_hazard.go b/internal/game/sys_hazard.go index 3d51b6d..3a8247b 100644 --- a/internal/game/sys_hazard.go +++ b/internal/game/sys_hazard.go @@ -123,7 +123,7 @@ func (g *Game) applyHazardHit(sess *net.Session, p *player.Player, hz *world.Haz p.HP = 0 } p.StartRegen() - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) msg := hz.HitMessage switch { diff --git a/internal/game/sys_labor.go b/internal/game/sys_labor.go index f0e1d01..5bd04f5 100644 --- a/internal/game/sys_labor.go +++ b/internal/game/sys_labor.go @@ -27,7 +27,7 @@ func (g *Game) progressBar(sess *net.Session, progress, max int) string { // writeTaskProgress prints the per-tick progress line for a task worksite, // mirroring the combat hit line but framed as labor instead of damage. -func (g *Game) writeTaskProgress(sess *net.Session, p *player.Player, mob *world.MobInstance, dmg int, gains []xpGain) { +func (g *Game) writeTaskProgress(sess *net.Session, p *player.Player, mob *world.MobInstance, gains []xpGain) { progress := mob.MaxHP - mob.HP if progress < 0 { progress = 0 diff --git a/internal/game/sys_science.go b/internal/game/sys_science.go index a616b42..58e2db0 100644 --- a/internal/game/sys_science.go +++ b/internal/game/sys_science.go @@ -66,25 +66,6 @@ func GetMod(id string) *ModDef { return modByID[id] } -func FindMod(input string) *ModDef { - if m, ok := modByID[input]; ok { - return m - } - lower := strings.ToLower(strings.ReplaceAll(input, " ", "_")) - for _, m := range AllMods { - if strings.HasPrefix(m.ID, lower) { - return m - } - } - lowerSpace := strings.ToLower(input) - for _, m := range AllMods { - if strings.HasPrefix(strings.ToLower(m.Name), lowerSpace) { - return m - } - } - return nil -} - func findAllModMatches(input string) []*ModDef { inputWords := strings.Fields(strings.ToLower(input)) if len(inputWords) == 0 { @@ -228,18 +209,17 @@ func (g *Game) hasJunkCost(p *player.Player, mod *ModDef) bool { return true } -func (g *Game) consumeJunkCost(p *player.Player, mod *ModDef) bool { +func (g *Game) consumeJunkCost(p *player.Player, mod *ModDef) { cost := g.effectiveJunkCost(p, mod) for itemID, qty := range cost { if !p.RemoveItem(itemID, qty) { - return false + return } } - g.AccountStore.SaveCharacter(p) - return true + g.saveCharacter(p) } -func (g *Game) triggerModReward(sess *net.Session, p *player.Player, mod *ModDef, multiplier float64, extraGains ...xpGain) int { +func (g *Game) triggerModReward(sess *net.Session, p *player.Player, mod *ModDef, multiplier float64, extraGains ...xpGain) { g.consumeJunkCost(p, mod) sciXP := int(float64(mod.BaseXP) * multiplier) @@ -249,7 +229,7 @@ func (g *Game) triggerModReward(sess *net.Session, p *player.Player, mod *ModDef g.awardSkillXP(sess, p, player.SkillName(gain.Skill), gain.XP) } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) if p.OptionBool("xp_drops") { var parts []string @@ -265,7 +245,6 @@ func (g *Game) triggerModReward(sess *net.Session, p *player.Player, mod *ModDef sess.WriteLine(g.colorize(sess, "xp", "("+strings.Join(parts, ", ")+")")) } } - return sciXP } func (g *Game) junkCostString(p *player.Player, mod *ModDef) string { diff --git a/internal/game/sys_technology.go b/internal/game/sys_technology.go index e285ba8..6f03276 100644 --- a/internal/game/sys_technology.go +++ b/internal/game/sys_technology.go @@ -189,7 +189,7 @@ func (g *Game) tryRecharge(sess *net.Session, p *player.Player, input string) bo sess.WriteLine("Your battery is already full.") } else { p.Battery = p.MaxBattery() - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) sess.WriteLine(g.colorize(sess, "battery", "You connect to the charging station. Your battery is fully recharged.")) } diff --git a/internal/game/sys_triggers.go b/internal/game/sys_triggers.go index cae774e..038d3fd 100644 --- a/internal/game/sys_triggers.go +++ b/internal/game/sys_triggers.go @@ -87,7 +87,7 @@ func (g *Game) startSequence(sess *net.Session, p *player.Player, steps []behavi Steps: append([]behavior.Step(nil), steps...), Wait: seq.wait, } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } } @@ -204,7 +204,7 @@ func (g *Game) completePlayerSequence(seq *sequence, p *player.Player) { g.removeSequence(seq) if p != nil && p.PendingSequence != nil && p.PendingSequence.Key == seq.key { p.PendingSequence = nil - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } if sess := seq.sess; sess != nil && sess.State == net.StateGame { g.writePrompt(sess) @@ -322,7 +322,7 @@ func (g *Game) persistLockedOnDisconnect(p *player.Player) { Steps: append([]behavior.Step(nil), locked.steps...), Wait: locked.wait, } - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) } } diff --git a/internal/game/tick.go b/internal/game/tick.go index 3f6a8b2..cd9b2fd 100644 --- a/internal/game/tick.go +++ b/internal/game/tick.go @@ -27,7 +27,7 @@ func (g *Game) DisconnectTick() { } if sess.TickDisconnectCountdown() { g.restoreGodPlayer(p) - g.AccountStore.SaveCharacter(p) + g.saveCharacter(p) g.Hub.HardRemove(sess) } } |
