From 07f2e072a14cc74133b187bb8715758790cdfe67 Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Mon, 28 Sep 2026 23:06:20 +0800 Subject: p52: 玩家交易通道——买/卖经 Sim.quotePrice→Occupation.biasedQuote,背包侧车/钱财增减 + v3 round-trip + HUD 短反馈(design §2.4-2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/LivingVillage.Desktop/Game.fs | 103 +++++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) (limited to 'src/LivingVillage.Desktop/Game.fs') 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() = -- cgit v1.2.3