summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop/WorldBootstrap.fs
blob: bf0ef65780bdfb1b62a74fb861298470ef3b8199 (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
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 终态)原样保留。
    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 }

    /// 以显式站位建世界: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