diff options
Diffstat (limited to 'src/LivingVillage.Kernel/Occupation.fs')
| -rw-r--r-- | src/LivingVillage.Kernel/Occupation.fs | 61 |
1 files changed, 55 insertions, 6 deletions
diff --git a/src/LivingVillage.Kernel/Occupation.fs b/src/LivingVillage.Kernel/Occupation.fs index 5c75c81..2168c4e 100644 --- a/src/LivingVillage.Kernel/Occupation.fs +++ b/src/LivingVillage.Kernel/Occupation.fs @@ -51,7 +51,8 @@ module Occupation = | Done | Failed - /// 首期任务池 = 农夫3(送粮/帮工/翻土)+ 渔夫3(夜捕/卖渔/询价),design §3 表。 + /// 首期任务池 = 农夫3(送粮/帮工/翻土)+ 渔夫3(夜捕/卖渔/询价) + /// + P15 补齐货郎2(收货/转卖)+ 书生2(观察记事/论理),design §3 表。 type TaskTemplateId = | DeliverGrain | HelpWork @@ -59,6 +60,10 @@ module Occupation = | NightCatch | SellFish | MarketInquiry + | BuyGoods + | Resell + | ObserveNotes + | ReasonDebate let taskNameOf (template: TaskTemplateId) : string = match template with @@ -68,14 +73,26 @@ module Occupation = | NightCatch -> "夜捕" | SellFish -> "卖渔" | MarketInquiry -> "询价" + | BuyGoods -> "收货" + | Resell -> "转卖" + | ObserveNotes -> "观察记事" + | ReasonDebate -> "论理" - /// 货郎/书生模板留后续单,当前为空池 → 不生成每日任务。 let taskPoolOf (kind: Kind) : TaskTemplateId list = match kind with | Farmer -> [ DeliverGrain; HelpWork; TillSoil ] | Fisher -> [ NightCatch; SellFish; MarketInquiry ] - | Peddler - | Scholar -> [] + | Peddler -> [ BuyGoods; Resell ] + | Scholar -> [ ObserveNotes; ReasonDebate ] + + /// 每职业任务流种子:与世界 seed 异或后驱动 taskHash,使四职业的每日序列互不相同。 + /// 纯常量(splitmix 同源),禁 System.Random。 + let occupationSeedOf (kind: Kind) : uint64 = + match kind with + | Farmer -> 0x1111111111111111UL + | Fisher -> 0x2222222222222222UL + | Peddler -> 0x3333333333333333UL + | Scholar -> 0x4444444444444444UL type DailyTask = { TemplateId: TaskTemplateId @@ -87,12 +104,15 @@ module Occupation = /// 完成判定信号(design §3:走既有事件通道)。 /// Dialogued 来自 DialogueEvent(chooseDialogue);Traded 来自 TradeEvent, - /// 字段序 = 卖方/物品/数量(玩家递出即 seller = playerId);ArrivedAt 为到达瓦片; + /// 字段序 = 卖方/物品/数量(玩家递出即 seller = playerId);Purchased = 玩家买入 + /// (TradeEvent 中 playerId 为 buyer);ArrivedAt 为到达瓦片;Observed 为观察面板; /// NightAtWater 由调用方把 isNight + 水边判定折叠后喂入。Sim.step 零感知。 type TaskSignal = | Dialogued of Sim.NpcId | Traded of Sim.NpcId * Sim.ItemKind * int + | Purchased of Sim.ItemKind * int | ArrivedAt of int * int + | Observed | NightAtWater /// TargetNpc/TargetTile 为 None 时视为通配(指定目标由后续 Desktop 接线单落地)。 @@ -110,6 +130,10 @@ module Occupation = | NightCatch, NightAtWater -> true | SellFish, Traded (seller, item, quantity) -> seller = Sim.playerId && item = Sim.Fish && quantity >= 1 | MarketInquiry, Dialogued _ -> true + | BuyGoods, Purchased (_, quantity) -> quantity >= 1 + | Resell, Traded (seller, _, quantity) -> seller = Sim.playerId && quantity >= 1 + | ObserveNotes, Observed -> true + | ReasonDebate, Dialogued _ -> true | _ -> false /// Offered → Active;其余状态原样返回(终态不可复活)。 @@ -145,7 +169,7 @@ module Occupation = state <- next out - /// 当日任务生成:池内确定性抽 1 条;空池(货郎/书生首期)→ None。 + /// 当日任务生成:池内确定性抽 1 条;空池 → None。 /// TargetNpc/TargetTile 首期留 None(通配),OfferedTick/DueTick 覆盖整日。 let dailyTaskOf (seed: uint64) (occupationSeed: uint64) (dayIndex: int64) (kind: Kind) : DailyTask option = match taskPoolOf kind with @@ -161,6 +185,13 @@ module Occupation = DueTick = offered + Sim.ticksPerDay - 1L State = Offered } + /// 当日任务刷新:已持任务的 OfferedTick 仍落在 nowTick 所属日时原样保留(含 + /// Done/Failed 终态,保证当日面板与存档稳定);跨日才按当日重新生成 Offered。 + let refreshToday (seed: uint64) (occupationSeed: uint64) (kind: Kind) (nowTick: int64) (today: DailyTask option) : DailyTask option = + match today with + | Some task when dayIndexOf task.OfferedTick = dayIndexOf nowTick -> Some task + | _ -> dailyTaskOf seed occupationSeed (dayIndexOf nowTick) kind + /// 挂在玩家侧的职业状态,随存档 v3 保存(design §2)。 /// Today = 当日任务(§3);TaskToken 保留为 v3 前缀占位扩展位(恒 0,暂未启用)。 type State = @@ -172,6 +203,24 @@ module Occupation = let stateOf (kind: Kind) : State = { Profile = profileOf kind; TaskToken = 0; Today = None; StoryStage = 0 } + // ---- 既有事件通道 → 任务信号桥(design §3:完成判定统一走 InteractionEvent)---- + + /// 仅玩家发起的对话/交易才产生任务信号;NPC 之间互动的副作用不派任务。 + /// TradeEvent 字段序 = buyer/seller/item/quantity/price,玩家为卖方 → Traded,为买方 → Purchased。 + let signalOfInteraction (event: Sim.InteractionEvent) : TaskSignal option = + match event.Kind with + | Sim.DialogueEvent outcome when outcome.Actor = Sim.playerId -> Some(Dialogued outcome.Target) + | Sim.TradeEvent (_, seller, item, quantity, _) when seller = Sim.playerId -> Some(Traded(seller, item, quantity)) + | Sim.TradeEvent (buyer, _, item, quantity, _) when buyer = Sim.playerId -> Some(Purchased(item, quantity)) + | _ -> None + + /// 用一条既有事件推进当日任务(Offered 须先 acceptTask;终态不可复活)。 + /// 无职业 / 无当日任务 / 非玩家事件时原样返回。 + let advanceTask (event: Sim.InteractionEvent) (state: State) : State = + match state.Today, signalOfInteraction event with + | Some task, Some signal -> { state with Today = Some(applySignal signal task) } + | _ -> state + // ---- 报价偏置(design §2/§3):Kernel 纯函数旁路,不改 Sim.fs 数值口径 ---- // 4 位定点整数基点(1/10000)表达偏置,避免浮点漂移;结果钳在 [0.95, 1.05]。 // 货郎:买卖双向有利(买压价/卖抬价 ±3.00%);书生:解读行情小幅加成 ±1.00%; |
