summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop
diff options
context:
space:
mode:
Diffstat (limited to 'src/LivingVillage.Desktop')
-rw-r--r--src/LivingVillage.Desktop/Assets/cjk-glyph-atlas.pngbin52827 -> 60594 bytes
-rw-r--r--src/LivingVillage.Desktop/Assets/cjk-glyphs.txt31
-rw-r--r--src/LivingVillage.Desktop/ChineseText.fs25
-rw-r--r--src/LivingVillage.Desktop/CjkGlyphAtlas.fs2
-rw-r--r--src/LivingVillage.Desktop/Game.fs38
-rw-r--r--src/LivingVillage.Desktop/MenuState.fs29
-rw-r--r--src/LivingVillage.Desktop/WorldBootstrap.fs17
7 files changed, 135 insertions, 7 deletions
diff --git a/src/LivingVillage.Desktop/Assets/cjk-glyph-atlas.png b/src/LivingVillage.Desktop/Assets/cjk-glyph-atlas.png
index 9678aeb..598c2bc 100644
--- a/src/LivingVillage.Desktop/Assets/cjk-glyph-atlas.png
+++ b/src/LivingVillage.Desktop/Assets/cjk-glyph-atlas.png
Binary files differ
diff --git a/src/LivingVillage.Desktop/Assets/cjk-glyphs.txt b/src/LivingVillage.Desktop/Assets/cjk-glyphs.txt
index 331e2ec..cdfc550 100644
--- a/src/LivingVillage.Desktop/Assets/cjk-glyphs.txt
+++ b/src/LivingVillage.Desktop/Assets/cjk-glyphs.txt
@@ -1,16 +1,22 @@
×
一
不
+业
+中
主
乐
乡
+书
买
+事
于
互
交
人
+今
从
价
+任
传
体
作
@@ -21,12 +27,14 @@
入
关
内
+农
准
出
到
前
力
功
+务
动
助
单
@@ -35,18 +43,21 @@
原
友
取
+受
可
同
和
回
围
圃
+土
在
墙
备
夜
大
天
+夫
失
好
始
@@ -59,7 +70,9 @@
层
居
屋
+工
已
+帮
年
庄
库
@@ -69,6 +82,7 @@
式
当
录
+待
得
心
必
@@ -86,16 +100,20 @@
拒
择
挑
+捕
据
+接
提
播
操
+收
效
敌
数
新
方
无
+日
时
明
易
@@ -119,10 +137,13 @@
江
没
法
+渔
游
物
状
+理
瓦
+生
用
留
白
@@ -134,6 +155,7 @@
移
竹
笑
+粮
精
系
紧
@@ -143,37 +165,46 @@
绪
续
置
+翻
聊
+职
能
范
获
菜
衅
+行
观
认
讨
议
记
讲
+论
设
话
+询
说
请
读
谅
谈
败
+货
资
足
+转
近
返
还
进
迫
退
+送
选
速
道
+郎
量
金
鉴
diff --git a/src/LivingVillage.Desktop/ChineseText.fs b/src/LivingVillage.Desktop/ChineseText.fs
index d516432..82418e6 100644
--- a/src/LivingVillage.Desktop/ChineseText.fs
+++ b/src/LivingVillage.Desktop/ChineseText.fs
@@ -184,7 +184,30 @@ module ChineseText =
"关闭"
"天数"
"金钱"
- "精力" ]
+ "精力"
+ "选择职业"
+ "暂不选择"
+ "农夫"
+ "渔夫"
+ "货郎"
+ "书生"
+ "任务"
+ "今日暂无任务"
+ "无职业"
+ "待接受"
+ "进行中"
+ "已完成"
+ "已失败"
+ "送粮"
+ "帮工"
+ "翻土"
+ "夜捕"
+ "卖渔"
+ "询价"
+ "收货"
+ "转卖"
+ "观察记事"
+ "论理" ]
let layoutText (maxWidth: int) (supported: Set<char>) (text: string) : TextLayout =
let width = max 1 maxWidth
diff --git a/src/LivingVillage.Desktop/CjkGlyphAtlas.fs b/src/LivingVillage.Desktop/CjkGlyphAtlas.fs
index fc19828..07895c8 100644
--- a/src/LivingVillage.Desktop/CjkGlyphAtlas.fs
+++ b/src/LivingVillage.Desktop/CjkGlyphAtlas.fs
@@ -11,7 +11,7 @@ module CjkGlyphAtlas =
// Keep this table sorted and stable so the generated PNG has a reproducible layout.
let private characterTable =
- "×一不主乐乡买于互交人从价传体作使保倍停入关内准出到前力功动助单卖南原友取可同和回围圃在墙备夜大天失好始存完家察对就层居屋已年庄库应度开式当录得心必态怒总恼意戏成或打找报拒择挑据提播操效敌数新方无时明易是晚景暂更最有村板林标档桥歉民水求江没法游物状瓦用留白目石确社离移竹笑精系紧给绝继绪续置聊能范获菜衅观认讨议记讲设话说请读谅谈败资足近返还进迫退选速道量金鉴钱门闭闲间闻附零需面项须食饥饿黛,:"
+ "×一不业中主乐乡书买事于互交人今从价任传体作使保倍停入关内农准出到前力功务动助单卖南原友取受可同和回围圃土在墙备夜大天夫失好始存完家察对就层居屋工已帮年庄库应度开式当录待得心必态怒总恼意戏成或打找报拒择挑捕据接提播操收效敌数新方无日时明易是晚景暂更最有村板林标档桥歉民水求江没法渔游物状理瓦生用留白目石确社离移竹笑粮精系紧给绝继绪续置翻聊职能范获菜衅行观认讨议记讲论设话询说请读谅谈败货资足转近返还进迫退送选速道郎量金鉴钱门闭闲间闻附零需面项须食饥饿黛,:"
let characters = characterTable.ToCharArray()
let rowCount = (characters.Length + columns - 1) / columns
diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs
index 6918e00..1b9508b 100644
--- a/src/LivingVillage.Desktop/Game.fs
+++ b/src/LivingVillage.Desktop/Game.fs
@@ -204,11 +204,18 @@ type LivingVillageGame() as this =
m5View <- M5Interaction.refreshPrompt world M5Interaction.initial
this.CenterCamera()
- member private this.StartNewGame() =
- world <- createInitialWorld ()
+ member private this.StartNewGame (occupation: Occupation.Kind option) =
+ let state =
+ occupation
+ |> Option.map (fun kind ->
+ let startTick = if daylightAutoplay then 6L * Sim.ticksPerDay / 24L else 0L
+ WorldBootstrap.occupationStateFor 42UL kind startTick)
+ world <- WorldBootstrap.initialWorldWithOccupation daylightAutoplay 42UL Sim.npcCount state
this.ResetWorldView()
+ m5View <- { m5View with Task = state }
menu <- menu |> MenuState.setCurrentWorld true |> MenuState.enterGame
- printfn $"new-game=ok seed=42 npcs={Sim.npcCount}"
+ let occupationName = occupation |> Option.map Occupation.nameOf |> Option.defaultValue "none"
+ printfn $"new-game=ok seed=42 npcs={Sim.npcCount} occupation={occupationName}"
member private this.SaveWorld (prefix: string) : bool =
try
@@ -247,7 +254,10 @@ type LivingVillageGame() as this =
| ContinueGame ->
menu <- menu |> MenuState.setCurrentWorld true |> MenuState.enterGame
printfn $"continue=ok tick={world.Tick}"
- | StartNewGame -> this.StartNewGame()
+ | StartNewGame ->
+ menu <- MenuState.openOccupationSelect menu
+ printfn "menu=occupation-select"
+ | StartNewGameWith occupation -> this.StartNewGame occupation
| LoadGame ->
match this.LoadWorld "load" with
| Ok _ ->
@@ -278,7 +288,9 @@ type LivingVillageGame() as this =
else None
match input with
| Some nextInput -> this.DispatchMenuInput nextInput
- | None when (autoplay || sampleMode) && not autoplayFlow && menu.Page = MainMenu && not menu.HasCurrentWorld ->
+ | None when (autoplay || sampleMode) && not autoplayFlow
+ && (menu.Page = MainMenu || menu.Page = OccupationSelect)
+ && not menu.HasCurrentWorld ->
autoplayFrames <- autoplayFrames + 1
if autoplayFrames >= 15 then
this.DispatchMenuInput Confirm
@@ -435,6 +447,12 @@ type LivingVillageGame() as this =
else
avatarMoving <- false
this.CenterCamera()
+ match m5View.Task with
+ | Some state ->
+ let refreshed = WorldBootstrap.refreshOccupationToday 42UL state world.Tick
+ if refreshed <> state then
+ m5View <- { m5View with Task = Some refreshed }
+ | None -> ()
m5View <- M5Interaction.refreshPrompt world m5View
member private this.RunAutoplayFlow() =
@@ -455,6 +473,9 @@ type LivingVillageGame() as this =
| 50 ->
printfn "flow=new-game"
flowStep <- 1
+ | 1 when menu.Page = OccupationSelect ->
+ this.DispatchMenuInput Confirm
+ printfn "flow=occupation=none"
| 1 when menu.Page = Playing ->
this.OpenMainMenu()
printfn "flow=return-menu"
@@ -619,6 +640,7 @@ type LivingVillageGame() as this =
let contentRows =
match menu.Page with
| MainMenu -> MenuState.items menu |> List.length
+ | OccupationSelect -> MenuState.occupationOptions.Length
| Settings -> MenuState.settings.Length
| Controls -> MenuState.controlRows.Length
| LoadError -> 4
@@ -696,6 +718,12 @@ type LivingVillageGame() as this =
MenuState.items menu
|> List.iteri (fun index item -> drawSelection index (MenuState.itemLabel item) (contentY + index * lineHeight))
drawText innerX (panelY + panelHeight - 40) mutedColor "UP DOWN 选择 ENTER 确认"
+ | OccupationSelect ->
+ drawText innerX headingY mutedColor "选择职业"
+ MenuState.occupationOptions
+ |> List.iteri (fun index occupation ->
+ drawSelection index (MenuState.occupationLabel occupation) (contentY + index * lineHeight))
+ drawText innerX (panelY + panelHeight - 40) mutedColor "UP DOWN 选择 ENTER 确认 ESC 返回"
| Settings ->
drawText innerX headingY mutedColor (ChineseText.menuHeading SettingsHeading)
MenuState.settings
diff --git a/src/LivingVillage.Desktop/MenuState.fs b/src/LivingVillage.Desktop/MenuState.fs
index 2848e22..619ac58 100644
--- a/src/LivingVillage.Desktop/MenuState.fs
+++ b/src/LivingVillage.Desktop/MenuState.fs
@@ -4,6 +4,7 @@ open LivingVillage.Kernel
type MenuPage =
| MainMenu
+ | OccupationSelect
| Settings
| Controls
| Playing
@@ -33,6 +34,7 @@ type MenuCommand =
| NoCommand
| ContinueGame
| StartNewGame
+ | StartNewGameWith of Occupation.Kind option
| LoadGame
| ApplySettings of SimulationControl
| ExitGame
@@ -100,9 +102,26 @@ module MenuState =
| ControlsItem -> "操作说明"
| ExitItem -> "退出"
+ /// 开局四选一 + 「暂不选择」(None = 旧行为,无职业)。
+ let occupationOptions : Occupation.Kind option list =
+ [ Some Occupation.Farmer
+ Some Occupation.Fisher
+ Some Occupation.Peddler
+ Some Occupation.Scholar
+ None ]
+
+ let occupationLabel (occupation: Occupation.Kind option) : string =
+ match occupation with
+ | Some kind -> Occupation.nameOf kind
+ | None -> "暂不选择"
+
+ let selectedOccupation (state: MenuState) : Occupation.Kind option =
+ occupationOptions |> List.item state.Selected
+
let private optionsFor (state: MenuState) : int =
match state.Page with
| MainMenu -> items state |> List.length
+ | OccupationSelect -> occupationOptions.Length
| Settings -> settings.Length
| _ -> 0
@@ -150,6 +169,14 @@ module MenuState =
let openSettings (state: MenuState) : MenuState =
{ state with Page = Settings; Selected = 0; ReturnPage = state.Page; Error = None }
+ /// 新档确认后进入职业选择页;默认选中「暂不选择」= 不写职业的旧行为。
+ let openOccupationSelect (state: MenuState) : MenuState =
+ { state with
+ Page = OccupationSelect
+ Selected = occupationOptions.Length - 1
+ ReturnPage = state.Page
+ Error = None }
+
let openControls (state: MenuState) : MenuState =
{ state with Page = Controls; Selected = 0; ReturnPage = state.Page; Error = None }
@@ -187,6 +214,7 @@ module MenuState =
| Back ->
match state.Page with
| MainMenu -> state, ExitGame
+ | OccupationSelect -> openMain state, NoCommand
| Settings
| Controls -> returnFromSubpage state, NoCommand
| LoadError -> openMain state, NoCommand
@@ -194,6 +222,7 @@ module MenuState =
| Confirm ->
match state.Page with
| MainMenu -> confirmMain state
+ | OccupationSelect -> state, StartNewGameWith (selectedOccupation state)
| Settings ->
let nextSettings = selectedControl state
{ state with Settings = nextSettings }, ApplySettings nextSettings
diff --git a/src/LivingVillage.Desktop/WorldBootstrap.fs b/src/LivingVillage.Desktop/WorldBootstrap.fs
index 0e8ea30..f8ba775 100644
--- a/src/LivingVillage.Desktop/WorldBootstrap.fs
+++ b/src/LivingVillage.Desktop/WorldBootstrap.fs
@@ -18,6 +18,23 @@ module WorldBootstrap =
let private daylightStartTick = 6L * Sim.ticksPerDay / 24L
+ /// 新档职业状态:按世界 seed 与职业种子生成 nowTick 所属日的 Offered 任务(design §3)。
+ let occupationStateFor (seed: uint64) (kind: Occupation.Kind) (nowTick: int64) : Occupation.State =
+ let baseState = Occupation.stateOf kind
+ { baseState with
+ Today = Occupation.dailyTaskOf seed (Occupation.occupationSeedOf kind) (Occupation.dayIndexOf nowTick) kind }
+
+ /// 跨日刷新当日任务;同日(含 Done/Failed 终态)原样保留。
+ let refreshOccupationToday (seed: uint64) (state: Occupation.State) (nowTick: int64) : Occupation.State =
+ { state with
+ Today =
+ Occupation.refreshToday
+ seed
+ (Occupation.occupationSeedOf state.Profile.Kind)
+ state.Profile.Kind
+ nowTick
+ state.Today }
+
let initialWorldWithOccupation (startAtDaylight: bool) (seed: uint64) (count: int) (occupation: Occupation.State option) : World =
if count < 0 || count > npcSpawnTiles.Length then
invalidArg (nameof count) "Desktop bootstrap supports at most 30 NPCs."