blob: e0ac037fd5c047721f8f22fe2fb223f363f614fe (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
namespace LivingVillage.Desktop
open LivingVillage.Kernel
open LivingVillage.Kernel.Sim
module WorldBootstrap =
let private tileCenter (x: int) (y: int) : Vec2 =
{ X = float32 (x * Sim.tilePixels + Sim.tilePixels / 2)
Y = float32 (y * Sim.tilePixels + Sim.tilePixels / 2) }
let private npcSpawnTiles : (int * int) array =
[| for y in [ 14; 18; 28; 32; 36; 40 ] do
for x in [ 24; 28; 32; 36; 40 ] do
yield x, y |]
let private avatarSpawnPosition = tileCenter 31 25
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 终态)原样保留。
/// P55:经 Kernel `refreshState`,跨日时按上一日是否已完成保留/清零连续天数。
let refreshOccupationToday (seed: uint64) (state: Occupation.State) (nowTick: int64) : Occupation.State =
Occupation.refreshState seed (Occupation.occupationSeedOf state.Profile.Kind) state.Profile.Kind nowTick state
// ---- P66 任务目标具体化(design §2.4-3)----
// 可达候选集合完全由世界/样板场景派生;Kernel 只做纯 hash 定点。
/// 可达 NPC:世界中实际存在的村民 id(顺序稳定)。
/// 可达瓦片:样板场景的菜地(VegetableGarden)世界瓦片 —— 设计口径「自家地块」,
/// 跨 legacy/riverscape 一致且必在陆地上。
let reachableTargets (world: World) : Occupation.TaskTargets =
let npcs = world.Npcs |> Array.map (fun npc -> npc.Id) |> Array.toList
let tiles =
VillagePresentation.scene.Elements
|> List.choose (fun element ->
match element.Prop with
| VillagePresentation.VegetableGarden ->
let tile = VillagePresentation.worldTileForSceneTile element.Position
Some(tile.X, tile.Y)
| _ -> None)
{ Npcs = npcs; Tiles = tiles }
/// 新档职业状态(P66):按真实世界的可达候选集合定点当日任务。
let occupationStateForWorld (seed: uint64) (kind: Occupation.Kind) (nowTick: int64) (world: World) : Occupation.State =
let baseState = Occupation.stateOf kind
{ baseState with
Today =
Occupation.dailyTaskOfWith
(reachableTargets world)
seed
(Occupation.occupationSeedOf kind)
(Occupation.dayIndexOf nowTick)
kind }
/// 跨日刷新(P66):按真实世界的可达候选集合重算跨日当天任务;同日原样保留。
let refreshOccupationTodayWorld (seed: uint64) (state: Occupation.State) (nowTick: int64) (world: World) : Occupation.State =
Occupation.refreshStateWith
(reachableTargets world)
seed
(Occupation.occupationSeedOf state.Profile.Kind)
state.Profile.Kind
nowTick
state
/// 开局身份年鉴(design §3):带职业建世界后写入一条 `StoryAnnal`「以…为生」,
/// 形成个人传记线。Game 与测试共用同一口径。
let appendIdentityAnnal (state: Occupation.State) (world: World) : World =
Sim.appendAnnal
{ Tick = world.Tick
Kind = Sim.StoryAnnal
Summary = Occupation.Story.identitySummaryOf state.Profile.Kind }
world
/// 以显式站位建世界:avatarTile 为玩家出生瓦片,npcTiles 为 NPC 出生瓦片序列。
/// 64x48 旧档与世界走 `avatarSpawnPosition` + `npcSpawnTiles` 的等价调用,字节口径不变。
let initialWorldWithPlacement
(startAtDaylight: bool)
(seed: uint64)
(count: int)
(avatarTile: int * int)
(npcTiles: (int * int) list)
(occupation: Occupation.State option) : World =
if count < 0 || count > npcTiles.Length then
invalidArg (nameof count) "Desktop bootstrap supports at most 30 NPCs."
let avatarPosition = tileCenter (fst avatarTile) (snd avatarTile)
let startTick = if startAtDaylight then daylightStartTick else 0L
let baseWorld = Sim.initialWorldN seed count
let avatar =
let baseAvatar =
{ baseWorld.Avatar with
Pos = avatarPosition
Mind = { baseWorld.Avatar.Mind with Target = avatarPosition } }
match occupation with
| None -> baseAvatar
| Some state ->
let needs = baseAvatar.Mind.Needs
{ baseAvatar with
Mind =
{ baseAvatar.Mind with
Needs =
{ needs with
Money = float32 state.Profile.InitialMoney
Energy = float32 state.Profile.InitialEnergy } } }
let npcs =
baseWorld.Npcs
|> Array.mapi (fun index npc ->
let x, y = List.item index npcTiles
{ npc with Pos = tileCenter x y })
{ baseWorld with
Tick = startTick
Time = float startTick * Sim.dtSeconds
Avatar = avatar
Npcs = npcs }
let initialWorldWithOccupation (startAtDaylight: bool) (seed: uint64) (count: int) (occupation: Occupation.State option) : World =
initialWorldWithPlacement
startAtDaylight
seed
count
(31, 25)
(List.ofArray npcSpawnTiles)
occupation
/// P20 第三步:默认可玩世界。生成器的核心可达区作为站位,30 个 spawn 全部落核心可达域。
let initialWorldInMap (startAtDaylight: bool) (seed: uint64) (count: int) (map: MapGen.Result) : World =
let avatarTile = ((map.Core.MinX + map.Core.MaxX) / 2, (map.Core.MinY + map.Core.MaxY) / 2)
initialWorldWithPlacement startAtDaylight seed count avatarTile map.Spawns None
let initialWorldForDaylight (startAtDaylight: bool) (seed: uint64) (count: int) : World =
initialWorldWithOccupation startAtDaylight seed count None
let initialWorld (seed: uint64) (count: int) : World =
initialWorldWithOccupation false seed count None
|