From 6b0bf25699ec504a93efd215c1ca5f033d4e6cef Mon Sep 17 00:00:00 2001 From: bridge Date: Sun, 14 Dec 2025 14:59:25 +0800 Subject: [PATCH] fix pngs --- src/classes/auxiliary.py | 20 +++++++ src/classes/weapon.py | 20 +++++++ src/sim/load/load_game.py | 7 +-- static/config.yml | 2 +- tests/conftest.py | 9 --- tests/test_deepcopy_fix.py | 55 +++++++++++++++++++ tests/test_save_load.py | 0 tests/test_sect_ranks.py | 16 +++--- .../game/composables/useTextures.ts | 14 ++--- 9 files changed, 113 insertions(+), 30 deletions(-) delete mode 100644 tests/conftest.py create mode 100644 tests/test_deepcopy_fix.py create mode 100644 tests/test_save_load.py diff --git a/src/classes/auxiliary.py b/src/classes/auxiliary.py index 1c686f7..67510cb 100644 --- a/src/classes/auxiliary.py +++ b/src/classes/auxiliary.py @@ -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}" diff --git a/src/classes/weapon.py b/src/classes/weapon.py index ee47308..b2078f1 100644 --- a/src/classes/weapon.py +++ b/src/classes/weapon.py @@ -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 = "" diff --git a/src/sim/load/load_game.py b/src/sim/load/load_game.py index 469384f..eb134fd 100644 --- a/src/sim/load/load_game.py +++ b/src/sim/load/load_game.py @@ -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 = {} diff --git a/static/config.yml b/static/config.yml index bb31fa0..840e93e 100644 --- a/static/config.yml +++ b/static/config.yml @@ -31,7 +31,7 @@ avatar: # all 引入所有主角不引入随机角色 # random,正常随机角色,但是有一定概率出主角 # none, 不引入主角(默认选项) - protagonist: "all" + protagonist: "random" social: major_event_context_num: 10 # 大事(长期记忆)展示数量 diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index d6f4fee..0000000 --- a/tests/conftest.py +++ /dev/null @@ -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) - - diff --git a/tests/test_deepcopy_fix.py b/tests/test_deepcopy_fix.py new file mode 100644 index 0000000..8813a76 --- /dev/null +++ b/tests/test_deepcopy_fix.py @@ -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"] + diff --git a/tests/test_save_load.py b/tests/test_save_load.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_sect_ranks.py b/tests/test_sect_ranks.py index b1e1e93..4f88ea2 100644 --- a/tests/test_sect_ranks.py +++ b/tests/test_sect_ranks.py @@ -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), diff --git a/web/src/components/game/composables/useTextures.ts b/web/src/components/game/composables/useTextures.ts index 2d95ba7..a08cf51 100644 --- a/web/src/components/game/composables/useTextures.ts +++ b/web/src/components/game/composables/useTextures.ts @@ -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') }