Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- Relation components can now be copied between distinct keys in the same entity, instead of the assignment being silently ignored.
https://github.com/HexDecimal/python-tcod-ecs/pull/59

## [5.5.1] - 2026-10-13

Special thanks to Anton Soloviev for critical bugfixes.
Expand Down
6 changes: 5 additions & 1 deletion tcod/ecs/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -1058,7 +1058,11 @@ def __setitem__(self, __key: ComponentKey[T], __values: Mapping[Entity, T], /) -

..versionadded:: 4.2.0
"""
if isinstance(__values, EntityComponentRelationMapping) and __values.entity is self.entity:
if (
isinstance(__values, EntityComponentRelationMapping)
and __values.entity is self.entity
and __values.key == __key
):
return
mapping: EntityComponentRelationMapping[T] = self[__key]
mapping.clear()
Expand Down
29 changes: 29 additions & 0 deletions tests/test_relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,35 @@ def test_relation_components() -> None:
assert not set(world.Q.all_of(relations=[(int, world[1])]))


@pytest.mark.parametrize("source_key", [int, ("fire", int)])
def test_component_relation_copy_between_keys(source_key: type[int] | tuple[str, type[int]]) -> None:
world = tcod.ecs.Registry()
mage, target, old_target = world["mage"], world["target"], world["old_target"]
destination_key = ("poison", int)
source_value = 5
mage.relation_components[source_key][target] = source_value
mage.relation_components[destination_key][old_target] = 9

mage.relation_components[destination_key] = mage.relation_components[source_key]

assert dict(mage.relation_components[destination_key]) == {target: source_value}
assert dict(mage.relation_components[source_key]) == {target: source_value}
assert set(world.Q.all_of(relations=[(destination_key, target)])) == {mage}
assert set(world.Q.all_of(relations=[(destination_key, old_target)])) == set()


def test_component_relation_copy_from_another_entity() -> None:
world = tcod.ecs.Registry()
first, second, target = world["first"], world["second"], world["target"]
value = 5
first.relation_components[int][target] = value

second.relation_components[int] = first.relation_components[int]

assert dict(second.relation_components[int]) == {target: value}
assert dict(first.relation_components[int]) == {target: value}


def test_conditional_relations() -> None:
world = tcod.ecs.Registry()
world["A"].relation_tag[ChildOf] = world["B"]
Expand Down
Loading