This commit is contained in:
bridge
2025-12-14 14:59:25 +08:00
parent 864b03b460
commit 6b0bf25699
9 changed files with 113 additions and 30 deletions

View File

@@ -29,6 +29,26 @@ class Auxiliary:
# 特殊属性(用于存储实例特定数据)
special_data: dict = field(default_factory=dict)
def __deepcopy__(self, memo):
"""
自定义深拷贝:
Sect 对象必须保持单例引用,不能深拷贝,否则会复制整个宗门及其所有成员,
导致内存浪费和潜在的无限递归/哈希错误。
"""
import copy
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
for k, v in self.__dict__.items():
if k == 'sect':
# 浅拷贝引用
setattr(result, k, v)
else:
# 深拷贝其他属性
setattr(result, k, copy.deepcopy(v, memo))
return result
def get_info(self) -> str:
"""获取简略信息"""
return f"{self.name}"

View File

@@ -32,6 +32,26 @@ class Weapon:
# 特殊属性(如万魂幡的吞噬魂魄计数)
special_data: dict = field(default_factory=dict)
def __deepcopy__(self, memo):
"""
自定义深拷贝:
Sect 对象必须保持单例引用,不能深拷贝,否则会复制整个宗门及其所有成员,
导致内存浪费和潜在的无限递归/哈希错误。
"""
import copy
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
for k, v in self.__dict__.items():
if k == 'sect':
# 浅拷贝引用
setattr(result, k, v)
else:
# 深拷贝其他属性
setattr(result, k, copy.deepcopy(v, memo))
return result
def get_info(self) -> str:
"""获取简略信息"""
suffix = ""

View File

@@ -67,7 +67,7 @@ def load_game(save_path: Optional[Path] = None) -> Tuple["World", "Simulator", L
from src.classes.avatar import Avatar
from src.classes.sect import sects_by_id
from src.sim.simulator import Simulator
from src.run.create_map import create_cultivation_world_map, add_sect_headquarters
from src.run.load_map import load_cultivation_world_map
# 读取存档文件
with open(save_path, "r", encoding="utf-8") as f:
@@ -79,7 +79,7 @@ def load_game(save_path: Optional[Path] = None) -> Tuple["World", "Simulator", L
f"游戏时间: {meta.get('game_time', 'unknown')})")
# 重建地图(地图本身不变,只需重建宗门总部位置)
game_map = create_cultivation_world_map()
game_map = load_cultivation_world_map()
# 读取世界数据
world_data = save_data.get("world", {})
@@ -99,9 +99,6 @@ def load_game(save_path: Optional[Path] = None) -> Tuple["World", "Simulator", L
existed_sect_ids = world_data.get("existed_sect_ids", [])
existed_sects = [sects_by_id[sid] for sid in existed_sect_ids if sid in sects_by_id]
# 在地图上添加宗门总部
add_sect_headquarters(game_map, existed_sects)
# 第一阶段重建所有Avatar不含relations
avatars_data = save_data.get("avatars", [])
all_avatars = {}

View File

@@ -31,7 +31,7 @@ avatar:
# all 引入所有主角不引入随机角色
# random正常随机角色但是有一定概率出主角
# none, 不引入主角(默认选项)
protagonist: "all"
protagonist: "random"
social:
major_event_context_num: 10 # 大事(长期记忆)展示数量

View File

@@ -1,9 +0,0 @@
import os
import sys
# 将项目根目录加入 Python 路径,确保可以导入 `src` 包
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT)

View File

@@ -0,0 +1,55 @@
import pytest
import copy
from src.classes.weapon import Weapon, WeaponType
from src.classes.equipment_grade import EquipmentGrade
from src.classes.sect import Sect, SectHeadQuarter
from src.classes.alignment import Alignment
from pathlib import Path
def test_weapon_deepcopy_does_not_copy_sect():
# 1. 创建模拟的 Sect
hq = SectHeadQuarter("HQ", "Desc", Path("img.png"))
sect = Sect(
id=1, name="TestSect", desc="Desc", member_act_style="Style",
alignment=Alignment.Righteous, headquarter=hq, technique_names=[]
)
# 向 Sect 中添加一些可能导致问题的成员(虽然这里只是简单测试引用)
# 在真实场景中Sect.members 可能包含复杂的 Avatar 对象
sect.members["dummy"] = "DummyAvatar"
# 2. 创建 Weapon 并关联 Sect
weapon = Weapon(
id=101, name="TestWeapon", weapon_type=WeaponType.SWORD,
grade=EquipmentGrade.COMMON, sect_id=1, desc="Desc", sect=sect
)
# 3. 深拷贝 Weapon
weapon_copy = copy.deepcopy(weapon)
# 4. 验证 Weapon 被复制了
assert weapon_copy is not weapon
assert weapon_copy.id == weapon.id
# 5. 关键验证Sect 应该是同一个对象(浅拷贝)
assert weapon_copy.sect is sect
assert weapon_copy.sect is weapon.sect
# 验证 Sect 的成员没有被复制
assert weapon_copy.sect.members is sect.members
def test_weapon_special_data_is_copied():
# 验证 special_data 是否被正确深拷贝
weapon = Weapon(
id=101, name="TestWeapon", weapon_type=WeaponType.SWORD,
grade=EquipmentGrade.COMMON, sect_id=None, desc="Desc"
)
weapon.special_data = {"souls": 10, "nested": {"a": 1}}
weapon_copy = copy.deepcopy(weapon)
assert weapon_copy.special_data == weapon.special_data
assert weapon_copy.special_data is not weapon.special_data
assert weapon_copy.special_data["nested"] is not weapon.special_data["nested"]

0
tests/test_save_load.py Normal file
View File

View File

@@ -50,8 +50,8 @@ def test_auto_promote():
def test_avatar_sect_rank_assignment():
"""测试avatar创建时宗门职位分配"""
from src.run.create_map import create_cultivation_world_map
game_map = create_cultivation_world_map()
from src.run.load_map import load_cultivation_world_map
game_map = load_cultivation_world_map()
world = World(
map=game_map,
month_stamp=MonthStamp(100 * 12),
@@ -77,8 +77,8 @@ def test_avatar_sect_rank_assignment():
def test_patriarch_uniqueness():
"""测试每个宗门只有一个掌门"""
from src.run.create_map import create_cultivation_world_map
game_map = create_cultivation_world_map()
from src.run.load_map import load_cultivation_world_map
game_map = load_cultivation_world_map()
world = World(
map=game_map,
month_stamp=MonthStamp(100 * 12),
@@ -104,8 +104,8 @@ def test_patriarch_uniqueness():
def test_sect_str_display():
"""测试宗门信息显示"""
from src.run.create_map import create_cultivation_world_map
game_map = create_cultivation_world_map()
from src.run.load_map import load_cultivation_world_map
game_map = load_cultivation_world_map()
world = World(
map=game_map,
month_stamp=MonthStamp(100 * 12),
@@ -129,8 +129,8 @@ def test_sect_str_display():
def test_cultivation_breakthrough_promotion():
"""测试突破境界后自动晋升"""
from src.run.create_map import create_cultivation_world_map
game_map = create_cultivation_world_map()
from src.run.load_map import load_cultivation_world_map
game_map = load_cultivation_world_map()
world = World(
map=game_map,
month_stamp=MonthStamp(100 * 12),

View File

@@ -53,15 +53,8 @@ export function useTextures() {
'SEA': '/assets/tiles/sea.png',
'WATER_FULL': '/assets/tiles/water_full.jpg',
'SEA_FULL': '/assets/tiles/sea_full.jpg',
'MOUNTAIN': '/assets/tiles/mountain.png',
'FOREST': '/assets/tiles/forest.png',
'CITY': '/assets/tiles/city.png',
'DESERT': '/assets/tiles/desert.png',
'RAINFOREST': '/assets/tiles/rainforest.png',
'GLACIER': '/assets/tiles/glacier.png',
'SNOW_MOUNTAIN': '/assets/tiles/snow_mountain.png',
'VOLCANO': '/assets/tiles/volcano.png',
'GRASSLAND': '/assets/tiles/grassland.png',
'SWAMP': '/assets/tiles/swamp.png',
'FARM': '/assets/tiles/farm.png',
'ISLAND': '/assets/tiles/island.png',
@@ -134,6 +127,13 @@ export function useTextures() {
await Promise.all([...tilePromises, ...variantPromises, ...avatarPromises, ...cloudPromises])
// 为没有基础纹理的变体类型设置默认纹理使用第0个变体作为默认值
Object.keys(TILE_VARIANTS).forEach(key => {
if (!textures.value[key] && textures.value[`${key}_0`]) {
textures.value[key] = textures.value[`${key}_0`]
}
})
isLoaded.value = true
console.log('Base textures loaded')
}