diff options
Diffstat (limited to 'src/LivingVillage.Desktop/VillageArt.fs')
| -rw-r--r-- | src/LivingVillage.Desktop/VillageArt.fs | 70 |
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 -> () |
