diff --git a/assets/clouds/cloud_0.png b/assets/clouds/cloud_0.png
new file mode 100644
index 0000000..d1b757c
Binary files /dev/null and b/assets/clouds/cloud_0.png differ
diff --git a/assets/clouds/cloud_1.png b/assets/clouds/cloud_1.png
new file mode 100644
index 0000000..a636de2
Binary files /dev/null and b/assets/clouds/cloud_1.png differ
diff --git a/assets/clouds/cloud_2.png b/assets/clouds/cloud_2.png
new file mode 100644
index 0000000..b4535e4
Binary files /dev/null and b/assets/clouds/cloud_2.png differ
diff --git a/assets/clouds/cloud_3.png b/assets/clouds/cloud_3.png
new file mode 100644
index 0000000..614646e
Binary files /dev/null and b/assets/clouds/cloud_3.png differ
diff --git a/assets/clouds/cloud_4.png b/assets/clouds/cloud_4.png
new file mode 100644
index 0000000..2966af4
Binary files /dev/null and b/assets/clouds/cloud_4.png differ
diff --git a/assets/clouds/cloud_5.png b/assets/clouds/cloud_5.png
new file mode 100644
index 0000000..302998a
Binary files /dev/null and b/assets/clouds/cloud_5.png differ
diff --git a/assets/clouds/cloud_6.png b/assets/clouds/cloud_6.png
new file mode 100644
index 0000000..d35148e
Binary files /dev/null and b/assets/clouds/cloud_6.png differ
diff --git a/assets/clouds/cloud_7.png b/assets/clouds/cloud_7.png
new file mode 100644
index 0000000..25812af
Binary files /dev/null and b/assets/clouds/cloud_7.png differ
diff --git a/assets/clouds/cloud_8.png b/assets/clouds/cloud_8.png
new file mode 100644
index 0000000..a7c6993
Binary files /dev/null and b/assets/clouds/cloud_8.png differ
diff --git a/src/server/main.py b/src/server/main.py
index 74525cf..5673dac 100644
--- a/src/server/main.py
+++ b/src/server/main.py
@@ -556,7 +556,8 @@ def get_map():
"width": w,
"height": h,
"data": map_data,
- "regions": regions_data
+ "regions": regions_data,
+ "config": CONFIG.get("frontend", {})
}
diff --git a/static/config.yml b/static/config.yml
index d52b4ab..5198946 100644
--- a/static/config.yml
+++ b/static/config.yml
@@ -38,4 +38,8 @@ nickname:
minor_event_threshold: 25 # 获得绰号需要的短期事件数量
save:
- max_events_to_save: 1000
\ No newline at end of file
+ max_events_to_save: 1000
+
+frontend:
+ water_speed: none
+ cloud_freq: low
\ No newline at end of file
diff --git a/tools/img_gemini/clouds/cloud_0.png b/tools/img_gemini/clouds/cloud_0.png
new file mode 100644
index 0000000..d1b757c
Binary files /dev/null and b/tools/img_gemini/clouds/cloud_0.png differ
diff --git a/tools/img_gemini/clouds/cloud_1.png b/tools/img_gemini/clouds/cloud_1.png
new file mode 100644
index 0000000..a636de2
Binary files /dev/null and b/tools/img_gemini/clouds/cloud_1.png differ
diff --git a/tools/img_gemini/clouds/cloud_2.png b/tools/img_gemini/clouds/cloud_2.png
new file mode 100644
index 0000000..b4535e4
Binary files /dev/null and b/tools/img_gemini/clouds/cloud_2.png differ
diff --git a/tools/img_gemini/clouds/cloud_3.png b/tools/img_gemini/clouds/cloud_3.png
new file mode 100644
index 0000000..614646e
Binary files /dev/null and b/tools/img_gemini/clouds/cloud_3.png differ
diff --git a/tools/img_gemini/clouds/cloud_4.png b/tools/img_gemini/clouds/cloud_4.png
new file mode 100644
index 0000000..2966af4
Binary files /dev/null and b/tools/img_gemini/clouds/cloud_4.png differ
diff --git a/tools/img_gemini/clouds/cloud_5.png b/tools/img_gemini/clouds/cloud_5.png
new file mode 100644
index 0000000..302998a
Binary files /dev/null and b/tools/img_gemini/clouds/cloud_5.png differ
diff --git a/tools/img_gemini/clouds/cloud_6.png b/tools/img_gemini/clouds/cloud_6.png
new file mode 100644
index 0000000..d35148e
Binary files /dev/null and b/tools/img_gemini/clouds/cloud_6.png differ
diff --git a/tools/img_gemini/clouds/cloud_7.png b/tools/img_gemini/clouds/cloud_7.png
new file mode 100644
index 0000000..25812af
Binary files /dev/null and b/tools/img_gemini/clouds/cloud_7.png differ
diff --git a/tools/img_gemini/clouds/cloud_8.png b/tools/img_gemini/clouds/cloud_8.png
new file mode 100644
index 0000000..a7c6993
Binary files /dev/null and b/tools/img_gemini/clouds/cloud_8.png differ
diff --git a/tools/img_gemini/split_cloud.py b/tools/img_gemini/split_cloud.py
new file mode 100644
index 0000000..15559c5
--- /dev/null
+++ b/tools/img_gemini/split_cloud.py
@@ -0,0 +1,127 @@
+import os
+import numpy as np
+from PIL import Image, ImageFilter, ImageChops
+
+def split_cloud_smart():
+ input_path = os.path.join(os.path.dirname(__file__), 'origin', 'cloud.jpg')
+ output_dir = os.path.join(os.path.dirname(__file__), 'clouds')
+
+ if not os.path.exists(output_dir):
+ os.makedirs(output_dir)
+
+ print(f"Processing {input_path}...")
+
+ try:
+ img = Image.open(input_path).convert("RGBA")
+ except Exception as e:
+ print(f"Error opening image: {e}")
+ return
+
+ # 1. 智能采样背景色 (Smart Sampling)
+ # 取图像四周边缘的像素来计算背景特征,比只取一个角更稳健
+ width, height = img.size
+ # 提取上、下、左、右四条边的像素
+ top_edge = np.array(img.crop((0, 0, width, 1)))
+ bottom_edge = np.array(img.crop((0, height-1, width, height)))
+ left_edge = np.array(img.crop((0, 0, 1, height)))
+ right_edge = np.array(img.crop((width-1, 0, width, height)))
+
+ # 合并边缘像素
+ edges = np.concatenate([
+ top_edge.reshape(-1, 4),
+ bottom_edge.reshape(-1, 4),
+ left_edge.reshape(-1, 4),
+ right_edge.reshape(-1, 4)
+ ])
+
+ # 计算背景色的平均值和标准差,用于确定容差范围
+ bg_mean = np.mean(edges, axis=0)
+ print(f"Smart sampled background color (RGBA): {bg_mean}")
+
+ # 2. HSV 色彩空间分离 (HSV Separation)
+ # 将图片转为 HSV,利用饱和度(S)和亮度(V)来区分云(通常S低V高)和深色背景(通常S高V低)
+ hsv_img = img.convert("HSV")
+ hsv_data = np.array(hsv_img)
+ rgb_data = np.array(img)
+
+ # 提取通道
+ H, S, V = hsv_data[:,:,0], hsv_data[:,:,1], hsv_data[:,:,2]
+ R, G, B = rgb_data[:,:,0], rgb_data[:,:,1], rgb_data[:,:,2]
+
+ # 计算 RGB 欧氏距离 (针对平均背景色)
+ # 只比较 RGB 前三个通道
+ diff_r = R.astype(float) - bg_mean[0]
+ diff_g = G.astype(float) - bg_mean[1]
+ diff_b = B.astype(float) - bg_mean[2]
+ rgb_distance = np.sqrt(diff_r**2 + diff_g**2 + diff_b**2)
+
+ # 定义阈值
+ # RGB 容差:允许背景有一定的颜色波动
+ rgb_tolerance = 60.0
+
+ # HSV 辅助判断:
+ # 背景通常是深紫色:需要保护云朵(白色/灰色),云朵的特征是低饱和度(Low S)
+ # 如果一个像素离背景色有点远,但它饱和度很高且偏紫,那它可能还是背景(渐变区)
+ # 如果一个像素离背景色很近,但它饱和度极低(它是灰色的云边缘),那应该保留
+
+ # 创建 Alpha Mask (0 为完全透明/背景,255 为完全不透明/云)
+ # 初始 Mask:距离背景色越近,Alpha 越小
+ alpha_mask = np.zeros_like(H, dtype=np.float32)
+
+ # 核心逻辑:
+ # 1. 主要是背景:RGB 距离 < 容差
+ # 2. 渐变增强:对于边缘区域,使用 Sigmoid 函数做软过渡,而不是硬切
+
+ # 归一化距离,距离越小越接近背景
+ normalized_dist = np.clip(rgb_distance / rgb_tolerance, 0, 1)
+
+ # 简单的线性映射翻转:距离越小(背景),Alpha越小(透明)
+ # 使用平滑函数 (Smoothstep) 让过渡更自然: 3x^2 - 2x^3
+ alpha_mask = np.clip((normalized_dist - 0.2) / 0.6, 0, 1) # 0.2到0.8之间过渡
+ alpha_mask = alpha_mask * alpha_mask * (3 - 2 * alpha_mask)
+
+ # 3. 保护云朵核心 (Cloud Core Protection)
+ # 如果像素很亮(V高)且饱和度很低(S低),强制认为是云,设为不透明
+ # 假设云是白色的,背景是深色的
+ is_cloud_core = (V > 150) & (S < 60)
+ alpha_mask[is_cloud_core] = 1.0
+
+ # 4. 转换回 0-255 并应用羽化
+ final_alpha = (alpha_mask * 255).astype(np.uint8)
+
+ # 创建蒙版图像
+ mask_img = Image.fromarray(final_alpha, mode='L')
+
+ # 边缘羽化 (Matte Refinement)
+ # 对蒙版进行轻微模糊,消除锯齿
+ mask_img = mask_img.filter(ImageFilter.GaussianBlur(radius=1.5))
+
+ # 将处理好的 Alpha 通道应用回原图
+ r, g, b, a = img.split()
+ img_transparent = Image.merge('RGBA', (r, g, b, mask_img))
+
+ # 切割逻辑保持不变
+ width, height = img.size
+ cell_width = width // 3
+ cell_height = height // 3
+
+ count = 0
+ for r in range(3):
+ for c in range(3):
+ left = c * cell_width
+ top = r * cell_height
+ right = left + cell_width
+ bottom = top + cell_height
+
+ cell = img_transparent.crop((left, top, right, bottom))
+
+ output_filename = f"cloud_{count}.png"
+ output_path = os.path.join(output_dir, output_filename)
+ cell.save(output_path)
+ print(f"Saved {output_path}")
+ count += 1
+
+ print("Smart split done!")
+
+if __name__ == "__main__":
+ split_cloud_smart()
diff --git a/web/src/components/game/CloudLayer.vue b/web/src/components/game/CloudLayer.vue
new file mode 100644
index 0000000..95d2aa8
--- /dev/null
+++ b/web/src/components/game/CloudLayer.vue
@@ -0,0 +1,204 @@
+
+
+
+
+
+
diff --git a/web/src/components/game/GameCanvas.vue b/web/src/components/game/GameCanvas.vue
index 3169f2d..8885193 100644
--- a/web/src/components/game/GameCanvas.vue
+++ b/web/src/components/game/GameCanvas.vue
@@ -5,6 +5,7 @@ import { useElementSize } from '@vueuse/core'
import Viewport from './Viewport.vue'
import MapLayer from './MapLayer.vue'
import EntityLayer from './EntityLayer.vue'
+import CloudLayer from './CloudLayer.vue'
import { useTextures } from './composables/useTextures'
const container = ref()
@@ -71,6 +72,7 @@ onMounted(() => {
@regionSelected="handleRegionSelected"
/>
+
diff --git a/web/src/components/game/composables/useTextures.ts b/web/src/components/game/composables/useTextures.ts
index 9e028d7..e0c6b8a 100644
--- a/web/src/components/game/composables/useTextures.ts
+++ b/web/src/components/game/composables/useTextures.ts
@@ -71,6 +71,16 @@ export function useTextures() {
}
})
+ // Load Clouds
+ const cloudPromises: Promise[] = []
+ for (let i = 0; i <= 8; i++) {
+ cloudPromises.push(
+ Assets.load(`/assets/clouds/cloud_${i}.png`)
+ .then(tex => { textures.value[`cloud_${i}`] = tex })
+ .catch(e => console.warn(`Failed cloud_${i}`, e))
+ )
+ }
+
// Load Avatars based on available IDs
const avatarPromises: Promise[] = []
@@ -90,7 +100,7 @@ export function useTextures() {
)
}
- await Promise.all([...tilePromises, ...avatarPromises])
+ await Promise.all([...tilePromises, ...avatarPromises, ...cloudPromises])
isLoaded.value = true
console.log('Base textures loaded')
diff --git a/web/src/stores/world.ts b/web/src/stores/world.ts
index 66645a4..0991ed7 100644
--- a/web/src/stores/world.ts
+++ b/web/src/stores/world.ts
@@ -21,6 +21,7 @@ export const useWorldStore = defineStore('world', () => {
const regions = shallowRef