mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +00:00
Long expeditions D5-b: loadout preset prompt at launch
`!expedition start <zone>` (and `!resume`) with no pack arg now DMs a Lean / Balanced / Heavy menu sized per zone tier. Player replies with the preset name (short forms l/b/h also work). Raw `Ns Md` syntax stays as the advanced override. Heavy always equals supplyPackCaps (the D5-a harsh×3 ceiling); Lean covers intended days at raw burn; Balanced sits between.
This commit is contained in:
@@ -118,8 +118,9 @@ Each successful background walk now logs a `walk` entry (`appendExpeditionLog(..
|
|||||||
|
|
||||||
> **Note:** the original "Empirical (sim-driven)" path was blocked: under D2-b event-anchored expeditions, `SimRunner.TickDay` calls `deliverBriefing` → `deliverBriefingEventAnchored`, which reads `time.Now().UTC()` for its safety-net check, so synthetic ticks never advance `CurrentDay` and `DaysAtEnd` stays at 0. Re-baselining off real day-counts requires teaching the sim to drive the event-anchored rollover (D7).
|
> **Note:** the original "Empirical (sim-driven)" path was blocked: under D2-b event-anchored expeditions, `SimRunner.TickDay` calls `deliverBriefing` → `deliverBriefingEventAnchored`, which reads `time.Now().UTC()` for its safety-net check, so synthetic ticks never advance `CurrentDay` and `DaysAtEnd` stays at 0. Re-baselining off real day-counts requires teaching the sim to drive the event-anchored rollover (D7).
|
||||||
|
|
||||||
**Remaining work (D5-b+):**
|
**D5-b (shipped 2026-05-27):** "Pick your loadout" preset prompt at launch. `!expedition start <zone>` with no pack arg now DMs a Lean / Balanced / Heavy menu (sized per zone tier via new `loadoutPurchase(tier, SupplyLoadout)` in `dnd_expedition_supplies.go`); player replies with `!expedition start <zone> lean|balanced|heavy` (short forms `l`/`b`/`h` also work). `!resume` got the same treatment. Raw `Ns Md` syntax remains the advanced override — `resolveLoadoutOrParse` in `dnd_expedition_cmd.go` tries a single-token preset first, falls back to `parseSupplyArgs`. Heavy always equals `supplyPackCaps` (the D5-a harsh×3 ceiling); Lean covers intended days at raw burn; Balanced sits between. `parseSupplyArgs("")` still returns 1 standard for any tier — it's no longer reachable from `!expedition start` (the empty path is intercepted to prompt), but the `1s` default is preserved so future callers don't get surprise zero-pack purchases. Help text updated; two existing scenario tests now pass `lean` explicitly, three new tests cover preset resolution + the prompt-vs-start guard.
|
||||||
- "Pick your loadout" preset prompt at launch (Lean / Balanced / Heavy) — raw `Ns Md` syntax becomes the advanced override.
|
|
||||||
|
**Remaining work (D5-c+):**
|
||||||
- Forage and Ranger forage re-baseline against the new caps.
|
- Forage and Ranger forage re-baseline against the new caps.
|
||||||
- DailyBurn / `phase5BDailyBurnRatePct` revisit once D7 sim measures real elapsed-day counts.
|
- DailyBurn / `phase5BDailyBurnRatePct` revisit once D7 sim measures real elapsed-day counts.
|
||||||
|
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ func TestAdv2Scenario_HarvestForestShadows(t *testing.T) {
|
|||||||
p := &AdventurePlugin{euro: euro}
|
p := &AdventurePlugin{euro: euro}
|
||||||
|
|
||||||
// Forage-friendly expedition.
|
// Forage-friendly expedition.
|
||||||
if err := p.handleDnDExpeditionCmd(MessageContext{Sender: uid}, "start forest_shadows"); err != nil {
|
if err := p.handleDnDExpeditionCmd(MessageContext{Sender: uid}, "start forest_shadows lean"); err != nil {
|
||||||
t.Fatalf("expedition start: %v", err)
|
t.Fatalf("expedition start: %v", err)
|
||||||
}
|
}
|
||||||
exp, _ := getActiveExpedition(uid)
|
exp, _ := getActiveExpedition(uid)
|
||||||
|
|||||||
@@ -92,16 +92,16 @@ func expeditionHelpText() string {
|
|||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
b.WriteString("**!expedition** — multi-day dungeon expeditions.\n\n")
|
b.WriteString("**!expedition** — multi-day dungeon expeditions.\n\n")
|
||||||
b.WriteString("`!expedition list` — show zones available at your level\n")
|
b.WriteString("`!expedition list` — show zones available at your level\n")
|
||||||
b.WriteString("`!expedition start <zone> [Ns] [Md]` — outfit & begin\n")
|
b.WriteString("`!expedition start <zone> [loadout]` — outfit & begin\n")
|
||||||
b.WriteString(" `Ns` = N standard packs (10 SU, 50 coins; cap scales by zone tier)\n")
|
b.WriteString(" loadout: `lean` / `balanced` / `heavy` (scales by zone tier)\n")
|
||||||
b.WriteString(" `Md` = M deluxe packs (20 SU, 90 coins; cap scales by zone tier)\n")
|
b.WriteString(" advanced: raw pack counts — `Ns Md` (e.g. `2s 1d`)\n")
|
||||||
b.WriteString(" default: `1s`\n")
|
b.WriteString(" no loadout? you'll be prompted to pick one\n")
|
||||||
b.WriteString("`!expedition run` — autopilot: walk rooms until something needs you (alias `!explore`)\n")
|
b.WriteString("`!expedition run` — autopilot: walk rooms until something needs you (alias `!explore`)\n")
|
||||||
b.WriteString("`!expedition status` — current expedition snapshot\n")
|
b.WriteString("`!expedition status` — current expedition snapshot\n")
|
||||||
b.WriteString("`!expedition log` — last 5 log entries\n")
|
b.WriteString("`!expedition log` — last 5 log entries\n")
|
||||||
b.WriteString("`!expedition abandon` — end the expedition (no rewards)\n")
|
b.WriteString("`!expedition abandon` — end the expedition (no rewards)\n")
|
||||||
b.WriteString("`!extract` — voluntary extraction (1 day, resumable for 7 days)\n")
|
b.WriteString("`!extract` — voluntary extraction (1 day, resumable for 7 days)\n")
|
||||||
b.WriteString("`!resume [Ns] [Md]` — resume an extracted expedition\n")
|
b.WriteString("`!resume [loadout]` — resume an extracted expedition (same `lean`/`balanced`/`heavy` choices)\n")
|
||||||
b.WriteString("`!map` — region/room ASCII map")
|
b.WriteString("`!map` — region/room ASCII map")
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
@@ -178,6 +178,66 @@ func parseSupplyArgs(rest string) (SupplyPurchase, error) {
|
|||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolveLoadoutOrParse first tries a single-token preset (lean/balanced/
|
||||||
|
// heavy and short forms); on miss it falls back to raw `Ns Md` parsing.
|
||||||
|
// Tier is needed because preset purchase counts scale by zone tier.
|
||||||
|
func resolveLoadoutOrParse(tok string, tier ZoneTier) (SupplyPurchase, error) {
|
||||||
|
trimmed := strings.TrimSpace(tok)
|
||||||
|
if !strings.ContainsAny(trimmed, " \t") {
|
||||||
|
if l, ok := parseLoadoutToken(trimmed); ok {
|
||||||
|
return loadoutPurchase(tier, l), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parseSupplyArgs(tok)
|
||||||
|
}
|
||||||
|
|
||||||
|
// renderLoadoutPrompt formats the "pick your loadout" DM. The resume
|
||||||
|
// command and start command share it; cmdHint tells the player which
|
||||||
|
// command to type back with the chosen preset.
|
||||||
|
func renderLoadoutPrompt(zone ZoneDefinition, cmdHint string) string {
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString(fmt.Sprintf("🎒 **Pick a loadout — %s** _(T%d)_\n\n", zone.Display, int(zone.Tier)))
|
||||||
|
for _, l := range []SupplyLoadout{LoadoutLean, LoadoutBalanced, LoadoutHeavy} {
|
||||||
|
pp := loadoutPurchase(zone.Tier, l)
|
||||||
|
b.WriteString(fmt.Sprintf(" `%s` — %s — %.0f SU, %d coins — %s\n",
|
||||||
|
loadoutName(l), packBreakdown(pp), pp.Total(), pp.Cost(), loadoutBlurb(l)))
|
||||||
|
}
|
||||||
|
b.WriteString(fmt.Sprintf("\nType `!%s %s` (or `lean` / `heavy`).\n", cmdHint, loadoutName(LoadoutBalanced)))
|
||||||
|
b.WriteString("Advanced: raw pack counts like `2s 1d`.")
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadoutName(l SupplyLoadout) string {
|
||||||
|
switch l {
|
||||||
|
case LoadoutLean:
|
||||||
|
return "lean"
|
||||||
|
case LoadoutHeavy:
|
||||||
|
return "heavy"
|
||||||
|
}
|
||||||
|
return "balanced"
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadoutBlurb(l SupplyLoadout) string {
|
||||||
|
switch l {
|
||||||
|
case LoadoutLean:
|
||||||
|
return "covers the intended run at calm burn; thin if things go sideways"
|
||||||
|
case LoadoutHeavy:
|
||||||
|
return "max cap; rides out harsh stretches and overruns"
|
||||||
|
}
|
||||||
|
return "recommended; absorbs a harsh patch or two"
|
||||||
|
}
|
||||||
|
|
||||||
|
func packBreakdown(p SupplyPurchase) string {
|
||||||
|
switch {
|
||||||
|
case p.StandardPacks > 0 && p.DeluxePacks > 0:
|
||||||
|
return fmt.Sprintf("%d standard + %d deluxe", p.StandardPacks, p.DeluxePacks)
|
||||||
|
case p.DeluxePacks > 0:
|
||||||
|
return fmt.Sprintf("%d deluxe", p.DeluxePacks)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%d standard", p.StandardPacks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *AdventurePlugin) expeditionCmdStart(ctx MessageContext, c *DnDCharacter, rest string) error {
|
func (p *AdventurePlugin) expeditionCmdStart(ctx MessageContext, c *DnDCharacter, rest string) error {
|
||||||
if rest == "" {
|
if rest == "" {
|
||||||
return p.SendDM(ctx.Sender,
|
return p.SendDM(ctx.Sender,
|
||||||
@@ -195,11 +255,16 @@ func (p *AdventurePlugin) expeditionCmdStart(ctx MessageContext, c *DnDCharacter
|
|||||||
return p.SendDM(ctx.Sender,
|
return p.SendDM(ctx.Sender,
|
||||||
"Unknown zone for your level. Try `!expedition list`.")
|
"Unknown zone for your level. Try `!expedition list`.")
|
||||||
}
|
}
|
||||||
purchase, err := parseSupplyArgs(packTok)
|
zoneForCaps, _ := getZone(zoneID)
|
||||||
|
// D5-b: prompt for a preset loadout on empty args. Raw `Ns Md` syntax
|
||||||
|
// still works as the advanced override.
|
||||||
|
if strings.TrimSpace(packTok) == "" {
|
||||||
|
return p.SendDM(ctx.Sender, renderLoadoutPrompt(zoneForCaps, "expedition start "+string(zoneID)))
|
||||||
|
}
|
||||||
|
purchase, err := resolveLoadoutOrParse(packTok, zoneForCaps.Tier)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return p.SendDM(ctx.Sender, "Couldn't parse supply packs: "+err.Error())
|
return p.SendDM(ctx.Sender, "Couldn't parse supply packs: "+err.Error())
|
||||||
}
|
}
|
||||||
zoneForCaps, _ := getZone(zoneID)
|
|
||||||
if err := purchase.Validate(zoneForCaps.Tier); err != nil {
|
if err := purchase.Validate(zoneForCaps.Tier); err != nil {
|
||||||
return p.SendDM(ctx.Sender, "Invalid pack selection: "+err.Error())
|
return p.SendDM(ctx.Sender, "Invalid pack selection: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,76 @@ func TestParseSupplyArgs_Combinations(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResolveLoadout_PresetTokens(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
tok string
|
||||||
|
tier ZoneTier
|
||||||
|
std, dlx int
|
||||||
|
}{
|
||||||
|
{"lean", ZoneTierBeginner, 1, 0},
|
||||||
|
{"balanced", ZoneTierJourneyman, 3, 0},
|
||||||
|
{"heavy", ZoneTierLegendary, 7, 2},
|
||||||
|
{"h", ZoneTierVeteran, 5, 1},
|
||||||
|
{"L", ZoneTierLegendary, 5, 0},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
got, err := resolveLoadoutOrParse(c.tok, c.tier)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%q@T%d: %v", c.tok, c.tier, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got.StandardPacks != c.std || got.DeluxePacks != c.dlx {
|
||||||
|
t.Errorf("%q@T%d: got %+v, want std=%d dlx=%d", c.tok, c.tier, got, c.std, c.dlx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveLoadout_FallsBackToRawParse(t *testing.T) {
|
||||||
|
got, err := resolveLoadoutOrParse("2s 1d", ZoneTierJourneyman)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got.StandardPacks != 2 || got.DeluxePacks != 1 {
|
||||||
|
t.Errorf("raw parse: got %+v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadoutPurchase_HeavyMatchesCaps(t *testing.T) {
|
||||||
|
for _, tier := range []ZoneTier{ZoneTierBeginner, ZoneTierApprentice, ZoneTierJourneyman, ZoneTierVeteran, ZoneTierLegendary} {
|
||||||
|
p := loadoutPurchase(tier, LoadoutHeavy)
|
||||||
|
std, dlx := supplyPackCaps(tier)
|
||||||
|
if p.StandardPacks != std || p.DeluxePacks != dlx {
|
||||||
|
t.Errorf("T%d heavy %+v, want std=%d dlx=%d", tier, p, std, dlx)
|
||||||
|
}
|
||||||
|
if err := p.Validate(tier); err != nil {
|
||||||
|
t.Errorf("T%d heavy fails own validation: %v", tier, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExpeditionCmd_StartNoArgsPromptsLoadout(t *testing.T) {
|
||||||
|
setupAuditTestDB(t)
|
||||||
|
uid := id.UserID("@exp-cmd-prompt:example")
|
||||||
|
expeditionCmdTestCharacter(t, uid, 1)
|
||||||
|
defer cleanupExpeditions(uid)
|
||||||
|
|
||||||
|
euro := &EuroPlugin{}
|
||||||
|
euro.ensureBalance(uid)
|
||||||
|
euro.Credit(uid, 500, "test")
|
||||||
|
p := &AdventurePlugin{euro: euro}
|
||||||
|
|
||||||
|
// Empty pack args: should prompt, NOT start.
|
||||||
|
if err := p.handleDnDExpeditionCmd(MessageContext{Sender: uid}, "start goblin_warrens"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if exp, _ := getActiveExpedition(uid); exp != nil {
|
||||||
|
t.Error("expedition started on empty-arg prompt; should have waited for preset choice")
|
||||||
|
}
|
||||||
|
if bal := euro.GetBalance(uid); bal != 500 {
|
||||||
|
t.Errorf("coins moved on prompt: %v", bal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestThreatThresholdLabel_Bands(t *testing.T) {
|
func TestThreatThresholdLabel_Bands(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
level int
|
level int
|
||||||
@@ -119,8 +189,8 @@ func TestExpeditionCmd_StartAbandonRoundtrip(t *testing.T) {
|
|||||||
euro.Credit(uid, 200, "test setup")
|
euro.Credit(uid, 200, "test setup")
|
||||||
p := &AdventurePlugin{euro: euro}
|
p := &AdventurePlugin{euro: euro}
|
||||||
|
|
||||||
// Default 1 standard pack = 50 coins. Should land an active expedition.
|
// Lean preset = 1 standard pack at T1 = 50 coins. Should land an active expedition.
|
||||||
if err := p.handleDnDExpeditionCmd(MessageContext{Sender: uid}, "start goblin_warrens"); err != nil {
|
if err := p.handleDnDExpeditionCmd(MessageContext{Sender: uid}, "start goblin_warrens lean"); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
exp, err := getActiveExpedition(uid)
|
exp, err := getActiveExpedition(uid)
|
||||||
@@ -219,7 +289,7 @@ func TestExpeditionCmd_RunWalksRooms(t *testing.T) {
|
|||||||
euro.Credit(uid, 200, "test setup")
|
euro.Credit(uid, 200, "test setup")
|
||||||
p := &AdventurePlugin{euro: euro}
|
p := &AdventurePlugin{euro: euro}
|
||||||
|
|
||||||
if err := p.handleDnDExpeditionCmd(MessageContext{Sender: uid}, "start goblin_warrens"); err != nil {
|
if err := p.handleDnDExpeditionCmd(MessageContext{Sender: uid}, "start goblin_warrens lean"); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
exp, _ := getActiveExpedition(uid)
|
exp, _ := getActiveExpedition(uid)
|
||||||
|
|||||||
@@ -308,11 +308,15 @@ func (p *AdventurePlugin) handleResumeCmd(ctx MessageContext, args string) error
|
|||||||
"That extraction is past its 7-day resume window — the dungeon has reshaped without you. Start a new expedition.")
|
"That extraction is past its 7-day resume window — the dungeon has reshaped without you. Start a new expedition.")
|
||||||
}
|
}
|
||||||
|
|
||||||
purchase, err := parseSupplyArgs(strings.TrimSpace(args))
|
resumeZone, _ := getZone(exp.ZoneID)
|
||||||
|
// D5-b: prompt for a preset loadout on empty args.
|
||||||
|
if strings.TrimSpace(args) == "" {
|
||||||
|
return p.SendDM(ctx.Sender, renderLoadoutPrompt(resumeZone, "resume"))
|
||||||
|
}
|
||||||
|
purchase, err := resolveLoadoutOrParse(strings.TrimSpace(args), resumeZone.Tier)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return p.SendDM(ctx.Sender, "Couldn't parse supply packs: "+err.Error())
|
return p.SendDM(ctx.Sender, "Couldn't parse supply packs: "+err.Error())
|
||||||
}
|
}
|
||||||
resumeZone, _ := getZone(exp.ZoneID)
|
|
||||||
if err := purchase.Validate(resumeZone.Tier); err != nil {
|
if err := purchase.Validate(resumeZone.Tier); err != nil {
|
||||||
return p.SendDM(ctx.Sender, "Invalid pack selection: "+err.Error())
|
return p.SendDM(ctx.Sender, "Invalid pack selection: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package plugin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Phase 12 E1b — Supply procurement, daily burn, and depletion effects.
|
// Phase 12 E1b — Supply procurement, daily burn, and depletion effects.
|
||||||
@@ -39,6 +40,68 @@ func supplyPackCaps(tier ZoneTier) (standard, deluxe int) {
|
|||||||
return 3, 1
|
return 3, 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SupplyLoadout names a tier-scaled preset purchase. D5-b: empty-arg
|
||||||
|
// `!expedition start <zone>` now prompts the player to pick one of these
|
||||||
|
// rather than silently defaulting to 1 standard pack. Raw `Ns Md` syntax
|
||||||
|
// remains the advanced override.
|
||||||
|
type SupplyLoadout int
|
||||||
|
|
||||||
|
const (
|
||||||
|
LoadoutLean SupplyLoadout = iota
|
||||||
|
LoadoutBalanced
|
||||||
|
LoadoutHeavy
|
||||||
|
)
|
||||||
|
|
||||||
|
// loadoutPurchase returns the SupplyPurchase for a preset at a tier.
|
||||||
|
// Lean: covers intended days at raw daily burn (cheap, no harsh buffer).
|
||||||
|
// Balanced: ~harsh-ready for an intended-length run (recommended).
|
||||||
|
// Heavy: equals supplyPackCaps — the harsh×3 ceiling D5-a sized for.
|
||||||
|
func loadoutPurchase(tier ZoneTier, l SupplyLoadout) SupplyPurchase {
|
||||||
|
switch l {
|
||||||
|
case LoadoutHeavy:
|
||||||
|
std, dlx := supplyPackCaps(tier)
|
||||||
|
return SupplyPurchase{StandardPacks: std, DeluxePacks: dlx}
|
||||||
|
case LoadoutBalanced:
|
||||||
|
switch tier {
|
||||||
|
case ZoneTierBeginner, ZoneTierApprentice:
|
||||||
|
return SupplyPurchase{StandardPacks: 2}
|
||||||
|
case ZoneTierJourneyman:
|
||||||
|
return SupplyPurchase{StandardPacks: 3}
|
||||||
|
case ZoneTierVeteran:
|
||||||
|
return SupplyPurchase{StandardPacks: 5}
|
||||||
|
case ZoneTierLegendary:
|
||||||
|
return SupplyPurchase{StandardPacks: 5, DeluxePacks: 1}
|
||||||
|
}
|
||||||
|
return SupplyPurchase{StandardPacks: 2}
|
||||||
|
default: // LoadoutLean
|
||||||
|
switch tier {
|
||||||
|
case ZoneTierBeginner, ZoneTierApprentice:
|
||||||
|
return SupplyPurchase{StandardPacks: 1}
|
||||||
|
case ZoneTierJourneyman:
|
||||||
|
return SupplyPurchase{StandardPacks: 2}
|
||||||
|
case ZoneTierVeteran:
|
||||||
|
return SupplyPurchase{StandardPacks: 3}
|
||||||
|
case ZoneTierLegendary:
|
||||||
|
return SupplyPurchase{StandardPacks: 5}
|
||||||
|
}
|
||||||
|
return SupplyPurchase{StandardPacks: 1}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseLoadoutToken returns the preset selected by tok, if any. Accepts
|
||||||
|
// short forms (l/b/h) and a couple of synonyms. Empty/unknown → false.
|
||||||
|
func parseLoadoutToken(tok string) (SupplyLoadout, bool) {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(tok)) {
|
||||||
|
case "lean", "l", "light":
|
||||||
|
return LoadoutLean, true
|
||||||
|
case "balanced", "b", "bal", "standard":
|
||||||
|
return LoadoutBalanced, true
|
||||||
|
case "heavy", "h", "max", "full":
|
||||||
|
return LoadoutHeavy, true
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
// supplyDailyBurn returns the base SU/day for a zone tier (§4.1).
|
// supplyDailyBurn returns the base SU/day for a zone tier (§4.1).
|
||||||
// Tier 1: 1, Tier 2: 1.5, Tier 3: 2, Tier 4: 3, Tier 5: 4.
|
// Tier 1: 1, Tier 2: 1.5, Tier 3: 2, Tier 4: 3, Tier 5: 4.
|
||||||
func supplyDailyBurn(tier ZoneTier) float32 {
|
func supplyDailyBurn(tier ZoneTier) float32 {
|
||||||
|
|||||||
Reference in New Issue
Block a user