add new pngs

This commit is contained in:
bridge
2025-12-11 22:22:12 +08:00
parent 231450bd33
commit 1f45d13214
63 changed files with 255 additions and 38 deletions

4
.gitignore vendored
View File

@@ -152,6 +152,4 @@ local_config.yml
台本/
笔记/
tmp/
tmp2/
tmp3/
tmp*/

View File

@@ -1,6 +1,5 @@
from __future__ import annotations
import random
from src.classes.action import TimedAction
from src.classes.event import Event
from src.classes.root import get_essence_types_for_root
@@ -12,71 +11,81 @@ class Cultivate(TimedAction):
修炼动作,可以增加修仙进度。
"""
COMMENT = "修炼,增进修为"
DOABLES_REQUIREMENTS = "在修炼区域中,修炼区域的灵气为角色的灵根之一,且角色未到瓶颈。"
COMMENT = "修炼,增进修为。在修炼区域(洞府)且灵气匹配时效果最佳,否则效果很差。"
DOABLES_REQUIREMENTS = "角色未到瓶颈;若在洞府区域,则该洞府需无主或归自己所有"
PARAMS = {}
duration_months = 10
# 经验常量
BASE_EXP_PER_DENSITY = 100 # 修炼区域每点灵气密度的基础经验
BASE_EXP_LOW_EFFICIENCY = 50 # 无匹配灵气或非修炼区域的基础经验
def _execute(self) -> None:
"""
修炼
获得的exp增加取决于essence的对应灵根的大小。
获得的exp取决于区域类型和灵气匹配情况:
- 修炼区域 + 匹配灵气exp = BASE_EXP_PER_DENSITY * density
- 修炼区域 + 无匹配灵气 或 非修炼区域exp = BASE_EXP_LOW_EFFICIENCY
"""
root = self.avatar.root
essence = self.avatar.tile.region.essence
# 多元素:取与角色灵根任一匹配元素的最大密度
essence_types = get_essence_types_for_root(root)
essence_density = max((essence.get_density(et) for et in essence_types), default=0)
exp = self.get_exp(essence_density)
# 结算额外修炼经验(来自功法/宗门/灵根等已合并)
if self.avatar.cultivation_progress.is_in_bottleneck():
return
exp = self._calculate_base_exp()
# 结算额外修炼经验(来自功法/宗门/灵根等)
extra_exp = int(self.avatar.effects.get("extra_cultivate_exp", 0) or 0)
if extra_exp:
exp += extra_exp
self.avatar.cultivation_progress.add_exp(exp)
def get_exp(self, essence_density: int) -> int:
def _get_matched_essence_density(self) -> int:
"""
根据essence的密度计算获得的exp
公式为base * essence_density
获取当前区域与角色灵根匹配的灵气密度
若不在修炼区域或无匹配灵气,返回 0。
"""
if self.avatar.cultivation_progress.is_in_bottleneck():
region = self.avatar.tile.region
if not isinstance(region, CultivateRegion):
return 0
base = 100
return base * essence_density
essence_types = get_essence_types_for_root(self.avatar.root)
return max((region.essence.get_density(et) for et in essence_types), default=0)
def _calculate_base_exp(self) -> int:
"""
根据区域类型和灵气匹配情况计算基础经验
"""
density = self._get_matched_essence_density()
if density > 0:
return self.BASE_EXP_PER_DENSITY * density
return self.BASE_EXP_LOW_EFFICIENCY
def can_start(self) -> tuple[bool, str]:
root = self.avatar.root
region = self.avatar.tile.region
essence_types = get_essence_types_for_root(root)
# 瓶颈检查
if not self.avatar.cultivation_progress.can_cultivate():
return False, "修为已达瓶颈,无法继续修炼"
if not isinstance(region, CultivateRegion):
return False, "当前不在修炼区域"
# 检查洞府所有权
if region.host_avatar is not None and region.host_avatar != self.avatar:
return False, f"该洞府已被 {region.host_avatar.name} 占据,无法修炼"
if all(region.essence.get_density(et) == 0 for et in essence_types):
return False, "当前区域无与灵根相符的灵气"
region = self.avatar.tile.region
# 如果在修炼区域,检查洞府所有权
if isinstance(region, CultivateRegion):
if region.host_avatar is not None and region.host_avatar != self.avatar:
return False, f"该洞府已被 {region.host_avatar.name} 占据,无法修炼"
return True, ""
def start(self) -> Event:
# 计算修炼时长缩减
reduction = float(self.avatar.effects.get("cultivate_duration_reduction", 0.0))
reduction = max(0.0, min(0.9, reduction)) # 限制在 [0, 0.9] 范围内
reduction = max(0.0, min(0.9, reduction))
# 动态设置此次修炼的实际duration(四舍五入确保为整数月份)
# 动态设置此次修炼的实际duration
base_duration = self.__class__.duration_months
actual_duration = max(1, round(base_duration * (1.0 - reduction)))
self.duration_months = actual_duration
return Event(self.world.month_stamp, f"{self.avatar.name}{self.avatar.tile.region.name} 开始修炼", related_avatars=[self.avatar.id])
# TimedAction 已统一 step 逻辑
efficiency = "进境颇佳" if self._get_matched_essence_density() > 0 else "进境缓慢"
return Event(self.world.month_stamp, f"{self.avatar.name}{self.avatar.tile.region.name} 开始修炼,{efficiency}", related_avatars=[self.avatar.id])
async def finish(self) -> list[Event]:
return []

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

210
tools/img_gemini/split2.py Normal file
View File

@@ -0,0 +1,210 @@
"""
将3x3网格图片切分为9张独立图片
基于行/列像素方差检测分隔线位置
"""
import os
from pathlib import Path
import numpy as np
from PIL import Image
def find_split_line(variance: np.ndarray, start: int, end: int) -> tuple[int, int]:
"""
在指定区间内找到方差最小的连续区域(分隔线位置)
返回 (line_start, line_end)
"""
# 在区间内找最小值位置
region = variance[start:end]
min_idx = start + np.argmin(region)
min_val = variance[min_idx]
# 向两侧扩展直到方差明显上升超过最小值的3倍或绝对阈值
threshold = max(min_val * 3, 50)
line_start = min_idx
while line_start > 0 and variance[line_start - 1] < threshold:
line_start -= 1
line_end = min_idx
while line_end < len(variance) - 1 and variance[line_end + 1] < threshold:
line_end += 1
return line_start, line_end + 1 # 返回左闭右开区间
def find_split_line_by_gradient(gray: np.ndarray, axis: int, start: int, end: int, is_bright_line: bool) -> tuple[int, int]:
"""
基于梯度突变检测分隔线边界
axis: 0=检测水平线, 1=检测垂直线
is_bright_line: True=亮色分隔线(如白色), False=暗色分隔线(如黑色)
"""
if axis == 0:
means = np.mean(gray, axis=1) # 每行的平均亮度
else:
means = np.mean(gray, axis=0) # 每列的平均亮度
# 计算相邻行/列的亮度差异(梯度)
gradient = np.diff(means)
# 找分隔线中心:在区间内找亮度极值
region = means[start:end]
if is_bright_line:
center_idx = start + np.argmax(region) # 亮色线找最亮点
else:
center_idx = start + np.argmin(region) # 暗色线找最暗点
# 从中心向左找边界:寻找梯度符号变化点(亮度突变)
line_start = center_idx
for i in range(center_idx - 1, max(0, center_idx - 30), -1):
# 对于亮色线:边界处 gradient > 0从暗到亮
# 对于暗色线:边界处 gradient < 0从亮到暗
if is_bright_line and gradient[i] > 3:
line_start = i + 1
break
elif not is_bright_line and gradient[i] < -3:
line_start = i + 1
break
line_start = i
# 从中心向右找边界
line_end = center_idx
for i in range(center_idx, min(len(gradient), center_idx + 30)):
if is_bright_line and gradient[i] < -3:
line_end = i + 1
break
elif not is_bright_line and gradient[i] > 3:
line_end = i + 1
break
line_end = i + 1
return line_start, line_end
def detect_split_lines_forest(image: Image.Image) -> tuple[list, list]:
"""专门处理 forest 的白色宽分隔线"""
gray = np.array(image.convert('L'), dtype=np.float32)
h, w = gray.shape
h_line1 = find_split_line_by_gradient(gray, 0, h // 4, h // 2 + h // 8, is_bright_line=True)
h_line2 = find_split_line_by_gradient(gray, 0, h // 2 + h // 8, 3 * h // 4, is_bright_line=True)
v_line1 = find_split_line_by_gradient(gray, 1, w // 4, w // 2 + w // 8, is_bright_line=True)
v_line2 = find_split_line_by_gradient(gray, 1, w // 2 + w // 8, 3 * w // 4, is_bright_line=True)
return [h_line1, h_line2], [v_line1, v_line2]
def detect_split_lines_snow_mountain(image: Image.Image) -> tuple[list, list]:
"""专门处理 snow_mountain 的黑色细分隔线"""
gray = np.array(image.convert('L'), dtype=np.float32)
h, w = gray.shape
h_line1 = find_split_line_by_gradient(gray, 0, h // 4, h // 2 + h // 8, is_bright_line=False)
h_line2 = find_split_line_by_gradient(gray, 0, h // 2 + h // 8, 3 * h // 4, is_bright_line=False)
v_line1 = find_split_line_by_gradient(gray, 1, w // 4, w // 2 + w // 8, is_bright_line=False)
v_line2 = find_split_line_by_gradient(gray, 1, w // 2 + w // 8, 3 * w // 4, is_bright_line=False)
return [h_line1, h_line2], [v_line1, v_line2]
def detect_split_lines(image: Image.Image) -> tuple[list, list]:
"""
检测水平和垂直分隔线的位置
返回 (h_lines, v_lines),每个是 [(start1, end1), (start2, end2)]
"""
gray = np.array(image.convert('L'), dtype=np.float32)
h, w = gray.shape
# 计算每行的方差
row_variance = np.var(gray, axis=1)
# 计算每列的方差
col_variance = np.var(gray, axis=0)
# 平滑处理,减少噪点影响
kernel_size = 3
row_variance = np.convolve(row_variance, np.ones(kernel_size)/kernel_size, mode='same')
col_variance = np.convolve(col_variance, np.ones(kernel_size)/kernel_size, mode='same')
# 在约 1/4~1/2 区间找第一条分隔线1/2~3/4 区间找第二条
h_line1 = find_split_line(row_variance, h // 4, h // 2 + h // 8)
h_line2 = find_split_line(row_variance, h // 2 + h // 8, 3 * h // 4)
v_line1 = find_split_line(col_variance, w // 4, w // 2 + w // 8)
v_line2 = find_split_line(col_variance, w // 2 + w // 8, 3 * w // 4)
return [h_line1, h_line2], [v_line1, v_line2]
def split_image(image: Image.Image, h_lines: list, v_lines: list) -> list[Image.Image]:
"""
根据分隔线位置切分图片为9块
"""
w, h = image.size
# 计算切分边界:[0, line1_start, line1_end, line2_start, line2_end, total]
y_bounds = [0, h_lines[0][0], h_lines[0][1], h_lines[1][0], h_lines[1][1], h]
x_bounds = [0, v_lines[0][0], v_lines[0][1], v_lines[1][0], v_lines[1][1], w]
tiles = []
# 取索引 0, 2, 4 对应的区域(跳过分隔线区域 1, 3
for row_idx in [0, 2, 4]:
for col_idx in [0, 2, 4]:
left = x_bounds[col_idx]
right = x_bounds[col_idx + 1]
top = y_bounds[row_idx]
bottom = y_bounds[row_idx + 1]
tile = image.crop((left, top, right, bottom))
tiles.append(tile)
return tiles
def process_image(input_path: Path, output_dir: Path):
"""处理单张图片"""
image = Image.open(input_path)
name = input_path.stem
# 特殊图像使用专门的检测方法
if name == 'forest':
h_lines, v_lines = detect_split_lines_forest(image)
elif name == 'snow_mountain':
h_lines, v_lines = detect_split_lines_snow_mountain(image)
else:
h_lines, v_lines = detect_split_lines(image)
print(f"{name}: 水平分隔线 {h_lines}, 垂直分隔线 {v_lines}")
# 切分
tiles = split_image(image, h_lines, v_lines)
# 保存
for i, tile in enumerate(tiles):
output_path = output_dir / f"{name}_{i}.png"
tile.save(output_path)
print(f" 保存: {output_path.name} ({tile.size[0]}x{tile.size[1]})")
def main():
script_dir = Path(__file__).parent
input_dir = script_dir / "origin2"
output_dir = script_dir / "split2"
output_dir.mkdir(exist_ok=True)
# 支持的图片格式
extensions = {'.jpg', '.jpeg', '.png'}
for path in sorted(input_dir.iterdir()):
if path.suffix.lower() in extensions:
process_image(path, output_dir)
print(f"\n完成!输出目录: {output_dir}")
if __name__ == "__main__":
main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB