summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop
diff options
context:
space:
mode:
Diffstat (limited to 'src/LivingVillage.Desktop')
-rw-r--r--src/LivingVillage.Desktop/Game.fs91
-rw-r--r--src/LivingVillage.Desktop/Interaction.fs14
2 files changed, 102 insertions, 3 deletions
diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs
index 0dce3a9..0cf996c 100644
--- a/src/LivingVillage.Desktop/Game.fs
+++ b/src/LivingVillage.Desktop/Game.fs
@@ -350,6 +350,13 @@ type LivingVillageGame() as this =
let mutable p52Step = 0
let mutable p52PendingName = ""
let mutable p52Pending = false
+ // P53 证据钩子:LV_P53_REC=1 时在真实世界里录制「远处 → 靠近提示出现 → 互动开对话 →
+ // 关闭+离开 → 提示消失」帧序列,逐帧打印面板/提示/目标/提示矩形,供 analyze 脚本 verdict。
+ // 提示层为瞬时叠加,不新开面板。
+ let p53RecordMode = Environment.GetEnvironmentVariable("LV_P53_REC") = "1"
+ let mutable p53Step = 0
+ let mutable p53PendingName = ""
+ let mutable p53Pending = false
let mutable autoplayFrames = 0
let mutable flowStep = 0
let mutable flowHold = 0
@@ -1191,6 +1198,80 @@ type LivingVillageGame() as this =
bag
p52Step <- p52Step + 1
+ /// P53 证据:真实录制提示出现/消失序列(远处 → 靠近 → 互动 → 关闭+离开)。
+ member private this.PrepareP53Recording () =
+ let setHour (hour: float) =
+ let tick = M6Presentation.resolveStartTick (Some hour) false
+ world <- { world with Tick = tick; Time = float tick * Sim.dtSeconds }
+ let placeAwayFromNpc () =
+ let npc = world.Npcs.[0]
+ world <- { world with Avatar = { world.Avatar with Pos = { X = npc.Pos.X + 240.0f; Y = npc.Pos.Y } } }
+ if not p53Pending then
+ match p53Step with
+ | 0 ->
+ setHour 12.0
+ // 只保留 0 号村民作候选,其余移远,隔离出唯一的提示对象(真实 resolver 不改)。
+ let npcs = Array.copy world.Npcs
+ for index in 1 .. npcs.Length - 1 do
+ npcs.[index] <- { npcs.[index] with Pos = { X = 4000.0f; Y = 4000.0f } }
+ world <- { world with Npcs = npcs }
+ placeAwayFromNpc ()
+ m5View <- M5Interaction.refreshPrompt world m5View
+ this.CenterCamera()
+ p53PendingName <- "p53-far"
+ p53Pending <- true
+ | 1 ->
+ let npc = world.Npcs.[0]
+ world <- { world with Avatar = { world.Avatar with Pos = npc.Pos } }
+ m5View <- M5Interaction.refreshPrompt world m5View
+ this.CenterCamera()
+ p53PendingName <- "p53-prompt"
+ p53Pending <- true
+ | 2 ->
+ let nextWorld, nextView = M5Interaction.apply M5Command.Interact world m5View
+ world <- nextWorld
+ m5View <- nextView
+ p53PendingName <- "p53-dialog"
+ p53Pending <- true
+ | 3 ->
+ let closedWorld, closedView = M5Interaction.apply M5Command.ClosePanel world m5View
+ world <- closedWorld
+ m5View <- closedView
+ placeAwayFromNpc ()
+ m5View <- M5Interaction.refreshPrompt world m5View
+ this.CenterCamera()
+ p53PendingName <- "p53-gone"
+ p53Pending <- true
+ | _ ->
+ printfn "p53-rec=done"
+ this.Exit()
+
+ /// P53 证据:在 `Draw` 末尾落盘每帧,并打印面板/提示/目标/提示矩形。
+ member private this.CaptureP53Recording () =
+ if p53Pending then
+ p53Pending <- false
+ System.IO.Directory.CreateDirectory recordDirectory |> ignore
+ this.SaveBackBuffer(sprintf "%s/%s.png" recordDirectory p53PendingName)
+ let targetText =
+ match InteractionResolver.resolve world m5View.HomeMode with
+ | Some target -> sprintf "%A" target.Action
+ | None -> "none"
+ let rectText =
+ match m5View.Prompt, m5View.PromptTargetPos with
+ | Some text, Some position ->
+ let viewport = this.GraphicsDevice.Viewport
+ let rect =
+ FloatingPrompt.layout viewport.Width viewport.Height position.X position.Y camera.X camera.Y 2 text
+ sprintf "%d,%d,%d,%d" rect.X rect.Y rect.Width rect.Height
+ | _ -> "none"
+ printfn "p53-rec=%s panel=%s prompt=%s target=%s rect=%s"
+ p53PendingName
+ (M5Interaction.panelName m5View)
+ (m5View.Prompt |> Option.defaultValue "none")
+ targetText
+ rectText
+ p53Step <- p53Step + 1
+
/// P46 证据:菜单阶段按帧表把 menuEntranceFrame 钉到灯笼呼吸的波峰/波谷,拍两相位。
member private this.PrepareP46Shot () =
if p46MenuADone && p46MenuBDone then
@@ -1612,6 +1693,14 @@ type LivingVillageGame() as this =
else this.DispatchMenuInput Confirm
elif menu.Page = Playing then
this.PrepareP52Shot()
+ elif p53RecordMode then
+ if menu.Page = MainMenu then
+ this.DispatchMenuInput Confirm
+ elif menu.Page = OccupationSelect then
+ if menu.Selected <> 0 then this.DispatchMenuInput Down
+ else this.DispatchMenuInput Confirm
+ elif menu.Page = Playing then
+ this.PrepareP53Recording()
elif menu.Page = Playing then
this.UpdatePlaying gameTime kb pressed pressedAny
elif menuShotMode then
@@ -2027,6 +2116,8 @@ type LivingVillageGame() as this =
this.CaptureP47Shot()
if p52ShotMode && menu.Page = Playing then
this.CaptureP52Shot()
+ if p53RecordMode && menu.Page = Playing then
+ this.CaptureP53Recording()
/// 启动画面:分层天色 + 屋脊/水面剪影 + 标题淡入上浮 + 跳过提示,全部为帧计数纯函数。
member private this.DrawSplash() =
diff --git a/src/LivingVillage.Desktop/Interaction.fs b/src/LivingVillage.Desktop/Interaction.fs
index 6a34b40..48bc3e7 100644
--- a/src/LivingVillage.Desktop/Interaction.fs
+++ b/src/LivingVillage.Desktop/Interaction.fs
@@ -111,6 +111,14 @@ module InteractionResolver =
distanceSquared avatarPosition position <= Sim.chatRangeSq
&& hasClearPath avatarPosition position
+ /// P53:交互动作 → 情境提示文案的唯一映射。提示层与按 E 的实际作用对象共用同一
+ /// 选择规则,文案只由动作决定,保证「提示写 E 交谈 → 按 E 作用于同一目标」。
+ let actionText (action: InteractionAction) : string =
+ match action with
+ | TalkTo _ -> "E 交谈"
+ | EnterHome _ -> "E 进屋"
+ | ExitHome _ -> "E 出屋"
+
let resolve (world: World) (homeMode: HomeMode) : InteractionTarget option =
let avatarPosition = world.Avatar.Pos
let candidates =
@@ -123,7 +131,7 @@ module InteractionResolver =
let avatarTile =
sceneTileFromPixel { X = avatarPosition.X; Y = avatarPosition.Y }
if isNearHomeDoor avatarTile home && targetIsReachable avatarPosition position then
- Some(target (ExitHome home.Id) position "E 出屋" avatarPosition)
+ Some(target (ExitHome home.Id) position (actionText (ExitHome home.Id)) avatarPosition)
else None
else None)
| Outside ->
@@ -134,13 +142,13 @@ module InteractionResolver =
let avatarTile =
sceneTileFromPixel { X = avatarPosition.X; Y = avatarPosition.Y }
if isNearHomeDoor avatarTile home && targetIsReachable avatarPosition position then
- Some(target (EnterHome home.Id) position "E 进屋" avatarPosition)
+ Some(target (EnterHome home.Id) position (actionText (EnterHome home.Id)) avatarPosition)
else None)
let npcs =
world.Npcs
|> Array.choose (fun npc ->
if dialogueAvailable npc && targetIsReachable avatarPosition npc.Pos then
- Some(target (TalkTo npc.Id) npc.Pos "E 交谈" avatarPosition)
+ Some(target (TalkTo npc.Id) npc.Pos (actionText (TalkTo npc.Id)) avatarPosition)
else None)
|> Array.toList
homes @ npcs