summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-22 09:24:28 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-22 09:24:28 +0800
commitdbce686019230c7a1105f5cc4b7b258698201128 (patch)
tree95eb229fad32d862d1093f1a569054fe8cf5e571 /src/LivingVillage.Desktop
parent590f3d8d5cc031c0909241b329ad7f7f18035991 (diff)
downloadliving-village-dbce686019230c7a1105f5cc4b7b258698201128.tar.gz
feat(mapgen): P26 512 大图河岸装饰(护岸石垒/芦苇/垂柳)仅视觉层,不挡通行
Diffstat (limited to 'src/LivingVillage.Desktop')
-rw-r--r--src/LivingVillage.Desktop/Assets/jiangnan-world.pngbin4147 -> 4468 bytes
-rw-r--r--src/LivingVillage.Desktop/MapGen.fs91
-rw-r--r--src/LivingVillage.Desktop/VillageArt.fs16
3 files changed, 101 insertions, 6 deletions
diff --git a/src/LivingVillage.Desktop/Assets/jiangnan-world.png b/src/LivingVillage.Desktop/Assets/jiangnan-world.png
index ea2866d..11145a8 100644
--- a/src/LivingVillage.Desktop/Assets/jiangnan-world.png
+++ b/src/LivingVillage.Desktop/Assets/jiangnan-world.png
Binary files differ
diff --git a/src/LivingVillage.Desktop/MapGen.fs b/src/LivingVillage.Desktop/MapGen.fs
index 63b14bc..ce9fb3a 100644
--- a/src/LivingVillage.Desktop/MapGen.fs
+++ b/src/LivingVillage.Desktop/MapGen.fs
@@ -34,7 +34,9 @@ module MapGen =
/// P24:大图额外沿河民居数量(沿现有路网泊松散布)。默认 0,64x48/256x192 不受影响。
ExtraRiversideHouses: int
/// P25:是否放置孤立装饰石块。默认 true;大图关闭,避免草地上出现游离石板 tile。
- DecorativeStones: bool }
+ DecorativeStones: bool
+ /// P26:是否沿海河岸放置河岸装饰(护岸石垒/芦苇/垂柳)。默认 false;仅大图开启。
+ RiverDecorations: bool }
/// P20 第二步:一条贯穿全图的东西向河道(每列恰好一段,宽度下限 RiverWidth)。
type River =
@@ -50,6 +52,18 @@ module MapGen =
DoorX: int
DoorY: int }
+ /// P26 河岸装饰种类:护岸石垒 / 芦苇 / 垂柳(垂柳占上下两格)。
+ type RiverDecorationKind =
+ | RevetmentStone
+ | Reeds
+ | Willow
+
+ /// P26 河岸装饰:纯视觉,不写入瓦片、不影响通行;仅大图沿海河岸生成。
+ type RiverDecoration =
+ { Kind: RiverDecorationKind
+ X: int
+ Y: int }
+
type Result =
{ Width: int
Height: int
@@ -61,6 +75,7 @@ module MapGen =
Bridges: (int * int) list
Paths: (int * int) list
Buildings: Building list
+ Decorations: RiverDecoration list
ReachableTiles: int
ReachabilityOk: bool
BridgeCrossingsOk: bool }
@@ -76,7 +91,8 @@ module MapGen =
SpawnColumns = 5
SpawnRows = 6
ExtraRiversideHouses = 0
- DecorativeStones = true }
+ DecorativeStones = true
+ RiverDecorations = false }
/// P23 尺寸自适应参数:河道数量随高度增长(约每 96 行一条),其余沿用默认。
/// 64x48 / 256x192 仍得到 2 条河道,与既有输出逐字节一致;512x384 得到 4 条。
@@ -89,7 +105,8 @@ module MapGen =
Height = height
RiverCount = max base_.RiverCount (max 1 (height / 96))
ExtraRiversideHouses = (if large then 10 else 0)
- DecorativeStones = not large }
+ DecorativeStones = not large
+ RiverDecorations = large }
// ---- splitmix64 与 value noise(无外部依赖、无时钟) ----
@@ -330,6 +347,63 @@ module MapGen =
addPath building.DoorX building.DoorY
extraPlaced <- extraPlaced + 1
+ // 5c) P26:河岸装饰(护岸石垒/芦苇/垂柳),仅大图启用。纯视觉层,不写入任何瓦片,
+ // 故 64x48/256x192 的 tiles checksum 与默认世界完全不受影响;装饰只落在紧邻水面的
+ // 岸格,且避开路网/桥面/民居/核心区,保证不遮挡通行。
+ let mutable decorations: RiverDecoration list = []
+ if p.RiverDecorations then
+ let mutable decorRng = p.Seed ^^^ 0xD3C0DEUL
+ let nextDecor () =
+ let nextState, raw = splitmix decorRng
+ decorRng <- nextState
+ raw
+ let bridgeLookup = System.Collections.Generic.HashSet<int>(bridges |> List.map (fun (x, y) -> idx x y))
+ let inBuildingFootprint x y =
+ buildings
+ |> List.exists (fun b ->
+ x >= b.Left - 1 && x <= b.Left + b.Width && y >= b.Top && y <= b.DoorY + 1)
+ let blocked x y =
+ not (inMap x y)
+ || tiles.[idx x y] = int GroundTile.Water
+ || pathSet.Contains(idx x y)
+ || bridgeLookup.Contains(idx x y)
+ || inCore x y
+ || inBuildingFootprint x y
+ let occupies (kind: RiverDecorationKind) (y: int) =
+ if kind = Willow then [ y; y + 1 ] else [ y ]
+ let tooClose (kind: RiverDecorationKind) x y =
+ decorations
+ |> List.exists (fun d ->
+ let dyList = occupies d.Kind d.Y
+ occupies kind y
+ |> List.exists (fun cy ->
+ dyList
+ |> List.exists (fun oy ->
+ let dx = d.X - x
+ let dy = oy - cy
+ dx * dx + dy * dy < 9)))
+ let tryPlace (kind: RiverDecorationKind) x y =
+ let tilesFree =
+ if kind = Willow then not (blocked x y) && not (blocked x (y + 1))
+ else not (blocked x y)
+ if tilesFree && not (tooClose kind x y) then
+ decorations <- { Kind = kind; X = x; Y = y } :: decorations
+ let kindOf (roll: uint64) =
+ match int (roll % 3UL) with
+ | 1 -> Reeds
+ | 2 -> Willow
+ | _ -> RevetmentStone
+ for river in riverBands do
+ for x in 2 .. w - 3 do
+ let lowerRoll = nextDecor ()
+ if lowerRoll % 3UL <> 0UL then
+ tryPlace (kindOf lowerRoll) x (river.CenterY + river.Width)
+ if nextDecor () % 4UL = 0UL then
+ let upperRoll = nextDecor ()
+ let kind = kindOf upperRoll
+ let anchorY = if kind = Willow then river.CenterY - 2 else river.CenterY - 1
+ tryPlace kind x anchorY
+
// 6) 30 个 spawn:核心区内的规则网格。
let sc = max 2 p.SpawnColumns
let sr = max 2 p.SpawnRows
@@ -385,6 +459,7 @@ module MapGen =
Bridges = bridges
Paths = paths
Buildings = buildings
+ Decorations = decorations
ReachableTiles = reachableTiles
ReachabilityOk = spawnsOk && doorsOk && bridgesOk
BridgeCrossingsOk = crossingsOk }
@@ -414,6 +489,16 @@ module MapGen =
.Append(building.Width).Append(':').Append(building.Height).Append(':')
.Append(building.DoorX).Append(':').Append(building.DoorY).Append(';')
|> ignore
+ builder.Append('|') |> ignore
+ let decorCode = function
+ | RevetmentStone -> 0
+ | Reeds -> 1
+ | Willow -> 2
+ for decoration in map.Decorations do
+ builder
+ .Append(decorCode decoration.Kind).Append(':')
+ .Append(decoration.X).Append(':').Append(decoration.Y).Append(';')
+ |> ignore
builder.ToString()
/// 可行走判定:非水,或位于桥面上(桥面仍标记为水但可通行)。
diff --git a/src/LivingVillage.Desktop/VillageArt.fs b/src/LivingVillage.Desktop/VillageArt.fs
index f303620..d2fcc1a 100644
--- a/src/LivingVillage.Desktop/VillageArt.fs
+++ b/src/LivingVillage.Desktop/VillageArt.fs
@@ -157,6 +157,7 @@ module VillageArt =
| RoofEaveOverhangSprite
| WallLatticeSprite
| CourtyardDoorSprite
+ | RevetmentStoneSprite
type private XnaColor = Microsoft.Xna.Framework.Color
type private XnaRectangle = Microsoft.Xna.Framework.Rectangle
@@ -499,7 +500,7 @@ module VillageArt =
let characterAtlasRelativePath = "Assets/jiangnan-characters.png"
let interiorAtlasRelativePath = "Assets/jiangnan-interior.png"
- let worldTileCount = 31
+ let worldTileCount = 32
let worldAtlasWidth = Sim.tilePixels * worldTileCount
let worldAtlasHeight = Sim.tilePixels
let characterAtlasWidth = 32 * 5 * 4 * 6
@@ -576,6 +577,7 @@ module VillageArt =
| RoofEaveOverhangSprite -> 28
| WallLatticeSprite -> 29
| CourtyardDoorSprite -> 30
+ | RevetmentStoneSprite -> 31
XnaRectangle(index * Sim.tilePixels, 0, Sim.tilePixels, Sim.tilePixels)
let private tilePosition x y : TilePosition =
@@ -626,13 +628,13 @@ module VillageArt =
(cell: int)
(map: MapGen.Result) =
let bridgeSource = tileSourceRectangle BridgeSprite
+ let drawCell sprite x y =
+ spriteBatch.Draw(textures.TileAtlas, XnaRectangle(tileX x, tileY y, cell, cell), tileSourceRectangle sprite, XnaColor.White)
for (x, y) in map.Bridges do
spriteBatch.Draw(textures.TileAtlas, XnaRectangle(tileX x, tileY y, cell, cell), bridgeSource, XnaColor.White)
for building in map.Buildings do
let left = building.Left
let right = building.Left + building.Width - 1
- let drawCell sprite x y =
- spriteBatch.Draw(textures.TileAtlas, XnaRectangle(tileX x, tileY y, cell, cell), tileSourceRectangle sprite, XnaColor.White)
match houseFacadeVariant building with
| 0 ->
// 变体 A(P24):黛瓦(脊/檐)+ 木墙/窗 + 门。
@@ -656,6 +658,14 @@ module VillageArt =
elif (x - left) % 2 = 0 then WallLatticeSprite
else WallWoodSprite
drawCell wallSprite x building.DoorY
+ // P26 河岸装饰:护岸石垒 / 芦苇 / 垂柳(垂柳占上下两格),只在大图生成。
+ for decoration in map.Decorations do
+ match decoration.Kind with
+ | MapGen.RiverDecorationKind.RevetmentStone -> drawCell RevetmentStoneSprite decoration.X decoration.Y
+ | MapGen.RiverDecorationKind.Reeds -> drawCell ReedSprite decoration.X decoration.Y
+ | MapGen.RiverDecorationKind.Willow ->
+ drawCell WillowTopSprite decoration.X decoration.Y
+ drawCell WillowBaseSprite decoration.X (decoration.Y + 1)
/// 桥头灯笼瓦片:每座桥每列的最上/最下一格(桥两端),用于挂灯笼与夜间暖光。
let bridgeLanternTiles (map: MapGen.Result) : (int * int) list =