Skip to content

Commit

Permalink
WIP: entities
Browse files Browse the repository at this point in the history
  • Loading branch information
Ian321 committed Dec 8, 2024
1 parent 51a9685 commit 9fd8b4c
Show file tree
Hide file tree
Showing 4 changed files with 253 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ jobs:
- run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- run: |
python -m ss14_tiled.test
- run: |
TMP=$(mktemp -d)
git clone https://github.com/space-wizards/space-station-14 $TMP
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
autopep8
deepdiff
opencv-python
pylint
pyyaml
135 changes: 132 additions & 3 deletions ss14_tiled/__main__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""Main module. To be split up on a later day."""
import copy
import shutil
import sys
import xml.etree.ElementTree as ET
from pathlib import Path
Expand All @@ -14,7 +16,7 @@ def create_tiles(root: Path, out: Path):

resources_dir = root / "Resources"
yml_dir = resources_dir / "Prototypes/Tiles"
files = [x for x in yml_dir.glob("**/*") if x.is_file()]
files = [x for x in yml_dir.glob("**/*.yml") if x.is_file()]

root_element = ET.Element("tileset", name="Tiles")
tile_id = 0
Expand Down Expand Up @@ -54,7 +56,7 @@ def create_decals(root: Path, out: Path, name: str = "", color: str = "#FFF"):

resources_dir = root / "Resources"
yml_dir = resources_dir / "Prototypes/Decals"
files = [x for x in yml_dir.glob("**/*") if x.is_file()]
files = [x for x in yml_dir.glob("**/*.yml") if x.is_file()]

root_element = ET.Element("tileset", name="Decals")
if name:
Expand Down Expand Up @@ -96,6 +98,130 @@ def create_decals(root: Path, out: Path, name: str = "", color: str = "#FFF"):
encoding="UTF-8", xml_declaration=True)


def merge_entity(child: dict, parent: dict) -> dict:
"""Merge entities."""
out = copy.deepcopy(parent)
for (key, value) in child.items():
if key == "components":
continue
out[key] = value

if "abstract" in child and child["abstract"]:
out["abstract"] = True
elif "abstract" in out:
del out["abstract"]

if "components" in child:
if not "components" in out:
out["components"] = child["components"]
else:
for child_comp in child["components"]:
found = False
for i, out_comp in enumerate(out["components"]):
if child_comp["type"] != out_comp["type"]:
continue
found = True
for (key, value) in child_comp.items():
out_comp[key] = value
out["components"][i] = out_comp
if not found:
out["components"].append(child_comp)

return out


def find_entities(root: Path) -> list[dict]:
"""Find and return all entities."""

# Some bases are outside the "Entities" directory,
# so we have to go over everything.
yml_dir = root / "Resources/Prototypes"
files = [x for x in yml_dir.glob("**/*.yml") if x.is_file()]

children = []
adults = {}
for file in files:
for entity in yaml.load(file.read_text("UTF-8"), Loader=SafeLoadIgnoreUnknown) or []:
if entity["type"] != "entity":
continue # alias?
if "parent" in entity:
children.append(entity)
else:
adults[entity["id"]] = entity

while len(children) > 0:
still_children = []
for child in children:
parents = child["parent"]
if isinstance(parents, str):
parents = [parents]

if all(parent in adults for parent in parents):
merged = adults[parents[0]]
for parent in parents[1:]:
merged = merge_entity(adults[parent], merged)
adults[child["id"]] = merge_entity(child, merged)
else:
still_children.append(child)

children = still_children

return adults


def filter_entities(entities: dict) -> dict:
"""Filter out some of the entities."""
entities = {k: v for k, v in entities.items()
if "abstract" not in v}
entities = {k: v for k, v in entities.items()
if "Sprite" in [x["type"] for x in v["components"]]}
entities = {k: v for k, v in entities.items()
if "TimedDespawn" not in [x["type"] for x in v["components"]]}
entities = {k: v for k, v in entities.items()
if "suffix" not in v or "DEBUG" not in str(v["suffix"])}
entities = {k: v for k, v in entities.items()
if "suffix" not in v or "Admeme" not in str(v["suffix"])}
entities = {k: v for k, v in entities.items()
if "suffix" not in v or "DO NOT MAP" not in str(v["suffix"])}
entities = {k: v for k, v in entities.items()
if "categories" not in v or "HideSpawnMenu" not in v["categories"]}
entities = {k: v for k, v in entities.items()
if "Input" not in [x["type"] for x in v["components"]]}
entities = {k: v for k, v in entities.items()
if "RandomHumanoidSpawner" not in [x["type"] for x in v["components"]]}

return entities


def group_entities(entities: dict) -> list[tuple[str, dict]]:
"""Split entities into groups."""
# TODO: implement
return [("All", entities)]


def create_entities(root: Path, out: Path):
"""Create the "entities"-tiles."""
tiles_out = out / ".images" / "entities"
tiles_out.mkdir(parents=True, exist_ok=True)

entities = find_entities(root)
entities = filter_entities(entities)
groups = group_entities(entities)
# TODO: Continue


class SafeLoadIgnoreUnknown(yaml.SafeLoader):
"""YAML-Loader that ignores unknown constructors."""

def ignore_unknown(self, _node):
"""Returns None no matter the node."""
return None


SafeLoadIgnoreUnknown.add_constructor(
None, SafeLoadIgnoreUnknown.ignore_unknown)


def parse_hex(color: str):
"""Parse a hex string to RGBA uint8."""
if len(color) == 4:
Expand Down Expand Up @@ -162,9 +288,12 @@ def get_colors(root: Path) -> list[(str, str)]:
def setup(root: Path):
"""Create tile-sets for Tiled."""
out = Path("dist")
out.mkdir(exist_ok=True)
if out.exists():
shutil.rmtree(out)
out.mkdir()

create_tiles(root, out)
create_entities(root, out)
create_decals(root, out)
for (name, color) in get_colors(root):
create_decals(root, out, name, color)
Expand Down
118 changes: 118 additions & 0 deletions ss14_tiled/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
"""Some tests."""
import unittest

from deepdiff import DeepDiff

from . import __main__


class TestMergeEntity(unittest.TestCase):
"""Tests to see if merging entities works."""
def test_basalt(self):
"""From /Resources/Prototypes/Entities/Tiles/basalt.yml"""
child = {
"type": "entity",
"id": "BasaltTwo",
"parent": "BasaltOne",
"placement": {
"mode": "SnapgridCenter"
},
"components": [{
"type": "Sprite",
"layers": [{
"state": "basalt2",
"shader": "unshaded"
}]
}]
}
parent = {
"type": "entity",
"id": "BasaltOne",
"description": "Rock.",
"placement": {
"mode": "SnapgridCenter"
},
"components": [{
"type": "Clickable",
}, {
"type": "Sprite",
"sprite": "/Textures/Tiles/Planet/basalt.rsi",
"layers": [{
"state": "basalt1",
"shader": "unshaded"
}]
}, {
"type": "SyncSprite",
}, {
"type": "RequiresTile",
}, {
"type": "Transform",
"anchored": True
}, {
"type": "Tag",
"tags": ["HideContextMenu"]
}]
}
expected = {
"type": "entity",
"id": "BasaltTwo",
"parent": "BasaltOne",
"description": "Rock.",
"placement": {
"mode": "SnapgridCenter"
},
"components": [{
"type": "Clickable",
}, {
"type": "Sprite",
"sprite": "/Textures/Tiles/Planet/basalt.rsi",
"layers": [{
"state": "basalt2",
"shader": "unshaded"
}]
}, {
"type": "SyncSprite",
}, {
"type": "RequiresTile",
}, {
"type": "Transform",
"anchored": True
}, {
"type": "Tag",
"tags": ["HideContextMenu"]
}]
}
actual = __main__.merge_entity(child, parent)
diff = DeepDiff(actual, expected, ignore_order=True)
assert not diff

def test_new_component(self):
"""If the child has a new component."""
child = {
"id": "B",
"parent": "A",
"components": [{
"type": "test_2"
}]
}
parent = {
"id": "A",
"components": [{
"type": "test_1"
}]
}
expected = {
"id": "B",
"parent": "A",
"components": [{
"type": "test_1"
}, {
"type": "test_2"
}]
}
actual = __main__.merge_entity(child, parent)
diff = DeepDiff(actual, expected, ignore_order=True)
assert not diff

if __name__ == "__main__":
unittest.main()

0 comments on commit 9fd8b4c

Please sign in to comment.