summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop/Game.fs
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-28 23:06:20 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-28 23:06:20 +0800
commit07f2e072a14cc74133b187bb8715758790cdfe67 (patch)
tree715efbba87d3be156e927ed5c3ed857a274ea1ae /src/LivingVillage.Desktop/Game.fs
parenta26ba18e9710dcdac57d942d2d7ac1ce1e47ca1a (diff)
downloadliving-village-07f2e072a14cc74133b187bb8715758790cdfe67.tar.gz
p52: 玩家交易通道——买/卖经 Sim.quotePrice→Occupation.biasedQuote,背包侧车/钱财增减 + v3 round-trip + HUD 短反馈(design §2.4-2)
Diffstat (limited to 'src/LivingVillage.Desktop/Game.fs')
-rw-r--r--src/LivingVillage.Desktop/Game.fs103
1 files changed, 102 insertions, 1 deletions
diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs
index db39566..0dce3a9 100644
--- a/src/LivingVillage.Desktop/Game.fs
+++ b/src/LivingVillage.Desktop/Game.fs
@@ -336,6 +336,20 @@ type LivingVillageGame() as this =
let mutable p47Step = 0
let mutable p47PendingName = ""
let mutable p47Pending = false
+ // P52 证据钩子:LV_P52_SHOT=1 时选职业开局 → 走到 NPC 旁用真实 Interact 开对话 →
+ // 真实 BuyFromTarget / SellToTarget 各一次,拍「对话入口 / 买入后 / 卖出后」三帧;
+ // 每笔交易打印钱与背包数量前后值,供 analyze 脚本算 delta。
+ // LV_P52_OCCUPATION=farmer|fisher|peddler|scholar(默认 peddler)。
+ let p52ShotMode = Environment.GetEnvironmentVariable("LV_P52_SHOT") = "1"
+ let p52Occupation =
+ match Environment.GetEnvironmentVariable("LV_P52_OCCUPATION") with
+ | "farmer" -> Occupation.Farmer
+ | "fisher" -> Occupation.Fisher
+ | "scholar" -> Occupation.Scholar
+ | _ -> Occupation.Peddler
+ let mutable p52Step = 0
+ let mutable p52PendingName = ""
+ let mutable p52Pending = false
let mutable autoplayFrames = 0
let mutable flowStep = 0
let mutable flowHold = 0
@@ -1106,6 +1120,77 @@ type LivingVillageGame() as this =
p47PendingName world.Tick world.Avatar.Pos.X world.Avatar.Pos.Y camera.X camera.Y
p47Step <- p47Step + 1
+ /// P52 证据:选职业开局 → 真实 Interact 开对话 → 真实 BuyFromTarget / SellToTarget 各一次。
+ member private this.PrepareP52Shot () =
+ let setHour (hour: float) =
+ let tick = M6Presentation.resolveStartTick (Some hour) false
+ world <- { world with Tick = tick; Time = float tick * Sim.dtSeconds }
+ let bagText () =
+ match m5View.Task with
+ | None -> "-"
+ | Some state ->
+ state.Backpack
+ |> List.map (fun (item, quantity) -> sprintf "%A=%d" item quantity)
+ |> String.concat ";"
+ if not p52Pending then
+ match p52Step with
+ | 0 ->
+ setHour 12.0
+ let npc = world.Npcs.[0]
+ world <- { world with Avatar = { world.Avatar with Pos = npc.Pos } }
+ let nextWorld, nextView = M5Interaction.apply M5Command.Interact world m5View
+ world <- nextWorld
+ m5View <- nextView
+ this.CenterCamera()
+ p52PendingName <- "p52-dialog"
+ p52Pending <- true
+ | 1 ->
+ let moneyBefore = world.Avatar.Mind.Needs.Money
+ let bagBefore = bagText ()
+ let nextWorld, nextView = M5Interaction.apply M5Command.BuyFromTarget world m5View
+ world <- nextWorld
+ m5View <- nextView
+ printfn "p52-trade buy npc=%A money_before=%.2f money_after=%.2f bag_before=[%s] bag_after=[%s] status=%s"
+ world.Npcs.[0].Id moneyBefore world.Avatar.Mind.Needs.Money bagBefore (bagText ()) m5View.Status
+ p52PendingName <- "p52-buy"
+ p52Pending <- true
+ | 2 ->
+ let reopenedWorld, reopenedView = M5Interaction.apply M5Command.Interact world m5View
+ world <- reopenedWorld
+ m5View <- reopenedView
+ let moneyBefore = world.Avatar.Mind.Needs.Money
+ let bagBefore = bagText ()
+ let nextWorld, nextView = M5Interaction.apply M5Command.SellToTarget world m5View
+ world <- nextWorld
+ m5View <- nextView
+ printfn "p52-trade sell npc=%A money_before=%.2f money_after=%.2f bag_before=[%s] bag_after=[%s] status=%s"
+ world.Npcs.[0].Id moneyBefore world.Avatar.Mind.Needs.Money bagBefore (bagText ()) m5View.Status
+ p52PendingName <- "p52-sell"
+ p52Pending <- true
+ | _ ->
+ printfn "p52-shot=done"
+ this.Exit()
+
+ /// P52 证据:在 `Draw` 末尾落盘每帧,并打印面板/反馈/背包快照。
+ member private this.CaptureP52Shot () =
+ if p52Pending then
+ p52Pending <- false
+ System.IO.Directory.CreateDirectory recordDirectory |> ignore
+ this.SaveBackBuffer(sprintf "%s/%s.png" recordDirectory p52PendingName)
+ let bag =
+ match m5View.Task with
+ | None -> "-"
+ | Some state ->
+ state.Backpack
+ |> List.map (fun (item, quantity) -> sprintf "%A=%d" item quantity)
+ |> String.concat ";"
+ printfn "p52-shot=%s panel=%s status=%s bag=[%s]"
+ p52PendingName
+ (M5Interaction.panelName m5View)
+ m5View.Status
+ bag
+ p52Step <- p52Step + 1
+
/// P46 证据:菜单阶段按帧表把 menuEntranceFrame 钉到灯笼呼吸的波峰/波谷,拍两相位。
member private this.PrepareP46Shot () =
if p46MenuADone && p46MenuBDone then
@@ -1249,13 +1334,15 @@ type LivingVillageGame() as this =
| None -> ()
let m5Command =
if overlayOpen then
- // P44:弹层开启只允许对话选项 1-6;E/Q/Tab/C/T 等世界交互键全部屏蔽,不穿透。
+ // P44:弹层开启只允许对话选项 1-6 + P52 买卖键;E/Q/Tab/C/T 等世界交互键全部屏蔽,不穿透。
if m5View.Panel = DialoguePanel && pressedAny [ Keys.D1; Keys.NumPad1 ] then Some Intent1
elif m5View.Panel = DialoguePanel && pressedAny [ Keys.D2; Keys.NumPad2 ] then Some Intent2
elif m5View.Panel = DialoguePanel && pressedAny [ Keys.D3; Keys.NumPad3 ] then Some Intent3
elif m5View.Panel = DialoguePanel && pressedAny [ Keys.D4; Keys.NumPad4 ] then Some Intent4
elif m5View.Panel = DialoguePanel && pressedAny [ Keys.D5; Keys.NumPad5 ] then Some Intent5
elif m5View.Panel = DialoguePanel && pressedAny [ Keys.D6; Keys.NumPad6 ] then Some Intent6
+ elif m5View.Panel = DialoguePanel && pressed Keys.B then Some BuyFromTarget
+ elif m5View.Panel = DialoguePanel && pressed Keys.V then Some SellToTarget
else None
else
if M5Interaction.worldInputAllowed m5View && pressed Keys.E then Some Interact
@@ -1265,6 +1352,8 @@ type LivingVillageGame() as this =
elif m5View.Panel = DialoguePanel && pressedAny [ Keys.D4; Keys.NumPad4 ] then Some Intent4
elif m5View.Panel = DialoguePanel && pressedAny [ Keys.D5; Keys.NumPad5 ] then Some Intent5
elif m5View.Panel = DialoguePanel && pressedAny [ Keys.D6; Keys.NumPad6 ] then Some Intent6
+ elif m5View.Panel = DialoguePanel && pressed Keys.B then Some BuyFromTarget
+ elif m5View.Panel = DialoguePanel && pressed Keys.V then Some SellToTarget
elif pressed Keys.Q then Some Observe
elif pressed Keys.Tab then Some ToggleNeeds
elif pressed Keys.C then Some ShowChronicle
@@ -1513,6 +1602,16 @@ type LivingVillageGame() as this =
else this.DispatchMenuInput Confirm
elif menu.Page = Playing then
this.PrepareP47Shot()
+ elif p52ShotMode then
+ if menu.Page = MainMenu then
+ this.DispatchMenuInput Confirm
+ elif menu.Page = OccupationSelect then
+ let targetIndex =
+ MenuState.occupationOptions |> List.findIndex (fun option -> option = Some p52Occupation)
+ if menu.Selected <> targetIndex then this.DispatchMenuInput Down
+ else this.DispatchMenuInput Confirm
+ elif menu.Page = Playing then
+ this.PrepareP52Shot()
elif menu.Page = Playing then
this.UpdatePlaying gameTime kb pressed pressedAny
elif menuShotMode then
@@ -1926,6 +2025,8 @@ type LivingVillageGame() as this =
this.CaptureP46Shot()
if p47ShotMode && menu.Page = Playing then
this.CaptureP47Shot()
+ if p52ShotMode && menu.Page = Playing then
+ this.CaptureP52Shot()
/// 启动画面:分层天色 + 屋脊/水面剪影 + 标题淡入上浮 + 跳过提示,全部为帧计数纯函数。
member private this.DrawSplash() =