add new pngs
4
.gitignore
vendored
@@ -152,6 +152,4 @@ local_config.yml
|
||||
|
||||
台本/
|
||||
笔记/
|
||||
tmp/
|
||||
tmp2/
|
||||
tmp3/
|
||||
tmp*/
|
||||
@@ -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 []
|
||||
|
||||
|
||||
|
||||
BIN
tools/img_gemini/origin2/dessert.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
tools/img_gemini/origin2/forest.jpg
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
tools/img_gemini/origin2/glacier.jpg
Normal file
|
After Width: | Height: | Size: 300 KiB |
BIN
tools/img_gemini/origin2/grassland.jpg
Normal file
|
After Width: | Height: | Size: 228 KiB |
BIN
tools/img_gemini/origin2/mountain.jpg
Normal file
|
After Width: | Height: | Size: 446 KiB |
BIN
tools/img_gemini/origin2/snow_mountain.jpg
Normal file
|
After Width: | Height: | Size: 228 KiB |
210
tools/img_gemini/split2.py
Normal 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()
|
||||
|
||||
BIN
tools/img_gemini/split2/dessert_0.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
tools/img_gemini/split2/dessert_1.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
tools/img_gemini/split2/dessert_2.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
tools/img_gemini/split2/dessert_3.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
tools/img_gemini/split2/dessert_4.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
tools/img_gemini/split2/dessert_5.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
tools/img_gemini/split2/dessert_6.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
tools/img_gemini/split2/dessert_7.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
tools/img_gemini/split2/dessert_8.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
tools/img_gemini/split2/forest_0.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
tools/img_gemini/split2/forest_1.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
tools/img_gemini/split2/forest_2.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
tools/img_gemini/split2/forest_3.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
tools/img_gemini/split2/forest_4.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
tools/img_gemini/split2/forest_5.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
tools/img_gemini/split2/forest_6.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
tools/img_gemini/split2/forest_7.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
tools/img_gemini/split2/forest_8.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
tools/img_gemini/split2/glacier_0.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
tools/img_gemini/split2/glacier_1.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
tools/img_gemini/split2/glacier_2.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
tools/img_gemini/split2/glacier_3.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
tools/img_gemini/split2/glacier_4.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
tools/img_gemini/split2/glacier_5.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
tools/img_gemini/split2/glacier_6.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
tools/img_gemini/split2/glacier_7.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
tools/img_gemini/split2/glacier_8.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
tools/img_gemini/split2/grassland_0.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
tools/img_gemini/split2/grassland_1.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
tools/img_gemini/split2/grassland_2.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
tools/img_gemini/split2/grassland_3.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
tools/img_gemini/split2/grassland_4.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
tools/img_gemini/split2/grassland_5.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
tools/img_gemini/split2/grassland_6.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
tools/img_gemini/split2/grassland_7.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
tools/img_gemini/split2/grassland_8.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
tools/img_gemini/split2/mountain_0.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
tools/img_gemini/split2/mountain_1.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
tools/img_gemini/split2/mountain_2.png
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
tools/img_gemini/split2/mountain_3.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
tools/img_gemini/split2/mountain_4.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
tools/img_gemini/split2/mountain_5.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
tools/img_gemini/split2/mountain_6.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
tools/img_gemini/split2/mountain_7.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
tools/img_gemini/split2/mountain_8.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
tools/img_gemini/split2/snow_mountain_0.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
tools/img_gemini/split2/snow_mountain_1.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
tools/img_gemini/split2/snow_mountain_2.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
tools/img_gemini/split2/snow_mountain_3.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
tools/img_gemini/split2/snow_mountain_4.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
tools/img_gemini/split2/snow_mountain_5.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
tools/img_gemini/split2/snow_mountain_6.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
tools/img_gemini/split2/snow_mountain_7.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
tools/img_gemini/split2/snow_mountain_8.png
Normal file
|
After Width: | Height: | Size: 37 KiB |