summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop/VillageArt.fs
diff options
context:
space:
mode:
Diffstat (limited to 'src/LivingVillage.Desktop/VillageArt.fs')
-rw-r--r--src/LivingVillage.Desktop/VillageArt.fs70
1 files changed, 67 insertions, 3 deletions
diff --git a/src/LivingVillage.Desktop/VillageArt.fs b/src/LivingVillage.Desktop/VillageArt.fs
index e08cde4..0427dea 100644
--- a/src/LivingVillage.Desktop/VillageArt.fs
+++ b/src/LivingVillage.Desktop/VillageArt.fs
@@ -556,7 +556,7 @@ module VillageArt =
// P34 structure slice: indices into cc0-tiles.png. Kenney Tiny Farm ships no
// water / roof / wall / railing / door art, so indices 4-11 are self-drawn
// (provenance in docs/素材来源.md), laid out by this fixed contract.
- let cc0TileCount = 19
+ let cc0TileCount = 21
let cc0TileIndexWaterA = 4
let cc0TileIndexWaterB = 5
let cc0TileIndexRoofRidge = 6
@@ -574,6 +574,10 @@ module VillageArt =
let cc0TileIndexStoneLantern = 16
let cc0TileIndexStall = 17
let cc0TileIndexReeds = 18
+ // P40 appended elements (indices 19-20). P40 also redrew the boat (14/15) and
+ // sharpened the stone lantern (16); indices 0-13 and 17-18 stay byte-identical.
+ let cc0TileIndexPaving = 19
+ let cc0TileIndexRedLantern = 20
let cc0WaterFrameCount = 2
let cc0TileAtlasWidth = Sim.tilePixels * cc0TileCount
let cc0TileAtlasHeight = Sim.tilePixels
@@ -704,6 +708,47 @@ module VillageArt =
&& (tileIsWater map x (y - 1) || tileIsWater map x (y + 1)) then
yield (x, y) ]
+ /// P40 石板路衔接:返回「与通行网络相连」的石板格索引集合。判据为纯几何:
+ /// 该格是石板(code 2),且它本身或四邻属于 `map.Paths`(主路/桥引道/门口)或
+ /// `map.Bridges`(桥面)。稀疏的 `DecorativeStones` 散布石板因远离路网被排除,
+ /// 于是 cc0 模式下石板只沿路径/门口/桥形成连贯带(孤立石板改画草地)。
+ /// 纯函数,只依赖 map(seed 内确定),不写瓦片、不改可达性。
+ let cc0PavingTiles (map: MapGen.Result) : Set<int> =
+ if map.Width <= 0 then
+ Set.empty
+ else
+ let indexOf x y = y * map.Width + x
+ let inMap x y = x >= 0 && x < map.Width && y >= 0 && y < map.Height
+ let isStone x y =
+ inMap x y && map.Tiles.[indexOf x y] = int MapGen.GroundTile.Stone
+ let anchors =
+ (map.Paths @ map.Bridges)
+ |> List.map (fun (x, y) -> indexOf x y)
+ |> Set.ofList
+ let isAnchor x y = inMap x y && Set.contains (indexOf x y) anchors
+ let connected x y =
+ isAnchor x y
+ || isAnchor (x - 1) y
+ || isAnchor (x + 1) y
+ || isAnchor x (y - 1)
+ || isAnchor x (y + 1)
+ [ for y in 0 .. map.Height - 1 do
+ for x in 0 .. map.Width - 1 do
+ if isStone x y && connected x y then yield indexOf x y ]
+ |> Set.ofList
+
+ /// P40 红灯笼:在每户民居门口确定性挂一盏红纸灯笼(门口 y,取门格)。
+ /// 与石灯笼形态区分:红纸圆身 + 金穗底座;位置为 (map) 纯函数,同 seed 不变。
+ /// 仅 cc0 模式绘制,且并入 `nightLightTilesWith` 的既有 P14 预乘 alpha 光晕路径。
+ let cc0RedLanternTiles (map: MapGen.Result) : (int * int) list =
+ if map.Width <= 0 then
+ []
+ else
+ (map.Buildings @ map.Farmhouses)
+ |> List.map (fun building -> (building.DoorX, building.DoorY))
+ |> List.filter (fun (x, y) -> x >= 0 && x < map.Width && y >= 0 && y < map.Height)
+ |> List.distinct
+
/// Ground atlas selection: `Some cc0-tiles.png` when the pack is on, else `None`
/// so the renderer keeps its procedural Jiangnan ground as the fallback.
let selectedGroundAtlasRelativePath () : string option =
@@ -883,9 +928,12 @@ module VillageArt =
/// P30:散落农舍的门窗暖光一并计入。
/// P39:`includeRoadLanterns` 为真时把主路石灯笼(`cc0LanternTiles`)计入,使夜帧灯笼
/// 光晕复用同一光晕路径;仅 cc0 模式为真,故 fallback(江南程序图集)不受影响。
+ /// P40:同一开关下把门口红灯笼(`cc0RedLanternTiles`)也计入;红灯笼与石灯笼复用
+ /// 同一条 P14 预乘 alpha 径向光晕路径,不新增光照系统。
let nightLightTilesWith (includeRoadLanterns: bool) (map: MapGen.Result) : (int * int) list =
let lanterns = bridgeLanternTiles map
let roadLanterns = if includeRoadLanterns then cc0LanternTiles map 12 else []
+ let redLanterns = if includeRoadLanterns then cc0RedLanternTiles map else []
let houseLights =
(map.Buildings @ map.Farmhouses)
|> List.collect (fun building ->
@@ -896,7 +944,7 @@ module VillageArt =
else
[ (building.DoorX, building.DoorY) ]
(building.DoorX, building.DoorY) :: windowTiles)
- lanterns @ roadLanterns @ houseLights |> List.distinct
+ lanterns @ roadLanterns @ redLanterns @ houseLights |> List.distinct
/// 既有夜景灯光源(不含 P39 主路石灯笼),保持既有调用与断言语义不变。
let nightLightTiles (map: MapGen.Result) : (int * int) list =
@@ -983,13 +1031,26 @@ module VillageArt =
| Some index -> drawCc0At position index
| None -> draw position sprite
+ // P40 石板路衔接:cc0 模式下只在「与路网/门口/桥相连」的石板格铺连贯石板带,
+ // 稀疏孤立装饰石板改画草地,于是场地上不再出现游离石板。fallback(无 cc0 图集)
+ // 与无 mapGen 的大世界保持原样,故既有输出逐字节不变。
+ let cc0PavingSet =
+ match mapGen, textures.Cc0TileAtlas with
+ | Some map, Some _ -> Some (map.Width, cc0PavingTiles map)
+ | _ -> None
+
// Procedural terrain for the 512x384 large world: water/pebble/peat codes map
// onto the jiangnan atlas sprites with deterministic ripple continuity.
// 生成器世界:1 水 / 2 石板路 / 3 泥炭 / 0 草地。泥炭按草地渲染,路走石板贴图。
let drawTerrainTile (position: TilePosition) (code: int) =
match code with
| 1 -> drawWater position
- | 2 -> draw position StonePathSprite
+ | 2 ->
+ match cc0PavingSet with
+ | Some (mapWidth, paving) when Set.contains (position.Y * mapWidth + position.X) paving ->
+ drawCc0At position cc0TileIndexPaving
+ | Some _ -> draw position GrassSprite
+ | None -> draw position StonePathSprite
// P30 乡村空区:水田(4)走水波动画、菜畦(5)走菜地素材;均为成熟 tile。
// P34:水田/水网在 CC0 包下同样走自绘 2 相位水面。
| 4 -> drawWater position
@@ -1053,6 +1114,9 @@ module VillageArt =
drawCc0Map stallX stallY cc0TileIndexStall
for (reedX, reedY) in cc0ReedTiles map 4 do
drawCc0Map reedX reedY cc0TileIndexReeds
+ // P40 红灯笼:门口挂一盏,与主路石灯笼形态区分;夜光晕走同一 P14 路径。
+ for (lanternRedX, lanternRedY) in cc0RedLanternTiles map do
+ drawCc0Map lanternRedX lanternRedY cc0TileIndexRedLantern
| None -> ()
| None -> ()