diff --git a/CHANGELOG.md b/CHANGELOG.md index 1acd121..d7eb0d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Replacing component relations from an inherited view now preserves values which depend on the destination. - Removing a relation tag or component now preserves query membership when the other relation still exists. - Queries collecting component rows now resolve inherited components instead of raising `KeyError` for prefab instances. - Relation components can now be copied between distinct keys in the same entity, instead of the assignment being silently ignored. diff --git a/tcod/ecs/entity.py b/tcod/ecs/entity.py index 0b206ff..c498c45 100644 --- a/tcod/ecs/entity.py +++ b/tcod/ecs/entity.py @@ -1071,9 +1071,10 @@ def __setitem__(self, __key: ComponentKey[T], __values: Mapping[Entity, T], /) - and __values.key == __key ): return + items = tuple(__values.items()) # The input may inherit values from the mapping being replaced. mapping: EntityComponentRelationMapping[T] = self[__key] mapping.clear() - for target, component in __values.items(): + for target, component in items: mapping[target] = component def __delitem__(self, key: ComponentKey[object]) -> None: diff --git a/tests/test_relations.py b/tests/test_relations.py index 451f5ee..364ca6a 100644 --- a/tests/test_relations.py +++ b/tests/test_relations.py @@ -359,3 +359,30 @@ def test_overlapping_relation_queries(key: ComponentKey[int], remove_first: str) else: actor.relation_tags_many[key].remove(faction) assert not any(queries) + + +@pytest.mark.parametrize("key", [int, ("affinity", int)]) +@pytest.mark.parametrize("depth", [1, 3]) +@pytest.mark.parametrize("override", [False, True]) +def test_component_relation_assignment_from_inherited_view( + key: ComponentKey[int], depth: int, *, override: bool +) -> None: + world = tcod.ecs.Registry() + prefab = world["prefab"] + first, second = world["first"], world["second"] + prefab.relation_components[key] = {first: 10, second: 20} + instance = prefab + for _ in range(depth): + instance = instance.instantiate() + if override: + instance.relation_components[key][first] = 30 + expected = {first: 30 if override else 10, second: 20} + query = world.Q.all_of(relations=[(key, second)]) + assert prefab in query + + prefab.relation_components[key] = instance.relation_components[key] + + assert dict(prefab.relation_components[key]) == expected + assert dict(instance.relation_components[key]) == expected + assert prefab in query + assert instance in query