Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JSON API for mass deleting actors #382

Merged
merged 11 commits into from
Dec 23, 2024
13 changes: 8 additions & 5 deletions src/open_dread_rando/door_locks/door_patcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,8 @@
continue

# get shield actor and cache its sName
shieldActor = self.editor.resolve_actor_reference(self.editor.reference_for_link(link, scenario))
reference = self.editor.reference_for_link(link, scenario)
shieldActor = self.editor.resolve_actor_reference(reference)

Check warning on line 404 in src/open_dread_rando/door_locks/door_patcher.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/door_locks/door_patcher.py#L403-L404

Added lines #L403 - L404 were not covered by tests
old_sName = shieldActor.sName

# skip hdoors (doors where the environment covers one side of the door)
Expand All @@ -411,16 +412,18 @@
# reclaim old shield id if this is a RandoShield
self.reclaim_old_shield_id(shieldActor.sName, scenario)

# grab the lowest open id and rename it
# grab the lowest open id
new_id = self.get_shield_id(scenario)
shieldActor.sName = new_id
life_comp[link_name] = self.editor.build_link(new_id)

# make new actor, copy its groups, delete it
brfld = self.editor.get_scenario(scenario)
brfld.actors_for_sublayer('default')[new_id] = shieldActor
self.editor.copy_actor_groups({ "actor": old_sName }, { "actor": new_id }, scenario)
brfld.actors_for_sublayer('default').pop(old_sName)
self.editor.remove_entity(reference, None)

Check warning on line 422 in src/open_dread_rando/door_locks/door_patcher.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/door_locks/door_patcher.py#L422

Added line #L422 was not covered by tests

# actually rename it
shieldActor.sName = new_id
life_comp[link_name] = self.editor.build_link(new_id)

Check warning on line 426 in src/open_dread_rando/door_locks/door_patcher.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/door_locks/door_patcher.py#L425-L426

Added lines #L425 - L426 were not covered by tests

# update the minimap entry as well
mapBlockages = self.editor.get_scenario_map(scenario).raw.Root.mapBlockages
Expand Down
4 changes: 4 additions & 0 deletions src/open_dread_rando/dread_patcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from open_dread_rando.pickups.split_pickups import patch_split_pickups, update_starting_inventory_split_pickups
from open_dread_rando.specific_patches import game_patches
from open_dread_rando.specific_patches.environmental_damage import apply_constant_damage
from open_dread_rando.specific_patches.mass_delete_actors import mass_delete_actors

Check warning on line 32 in src/open_dread_rando/dread_patcher.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/dread_patcher.py#L32

Added line #L32 was not covered by tests
from open_dread_rando.specific_patches.objective import apply_objective_patches
from open_dread_rando.specific_patches.static_fixes import apply_static_fixes
from open_dread_rando.validator_with_default import DefaultValidatingDraft7Validator
Expand Down Expand Up @@ -271,6 +272,9 @@
# Specific game patches
game_patches.apply_game_patches(editor, configuration.get("game_patches", {}))

# Mass delete actors
mass_delete_actors(editor, configuration["mass_delete_actors"])

Check warning on line 276 in src/open_dread_rando/dread_patcher.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/dread_patcher.py#L276

Added line #L276 was not covered by tests

# Actor patches
apply_actor_patches(editor, configuration.get("actor_patches"))

Expand Down
98 changes: 98 additions & 0 deletions src/open_dread_rando/files/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,104 @@
"additionalProperties": false,
"default": {}
},
"mass_delete_actors": {
"description": "Deletes actors en masse",
"type": "object",
"properties": {
"to_remove": {
"type": "array",
"items": {
"examples": [
{
"scenario": "s010_cave",
"actor_layer": "rLightsLayer",
"method": "all"
},
{
"scenario": "s010_cave",
"method": "remove_from_groups",
"actor_groups": [
"eg_collision_camera_067_Default"
]
},
{
"scenario": "s030_baselab",
"actor_layer": "rLightsLayer",
"method": "keep_from_groups",
"actor_groups": [
"lg_collision_camera_011_Default"
]
}
],
"type": "object",
"properties": {
"scenario": {
"description": "The scenario to remove actors from",
"$ref": "#/$defs/scenario_name"
},
"actor_layer": {
"description": "The actor layer to remove actors from",
"type": "string",
"enum": [
"rEntitiesLayer",
"rSoundsLayer",
"rLightsLayer"
],
"default": "rEntitiesLayer"
},
"method": {
"description": "The method for removing actors. all removes all in the scenario, remove_from_groups will remove all actors in the provided actor groups, and keep_from_groups will remove from all actor groups not provided",
"type": "string",
"enum": [
"all",
"remove_from_groups",
"keep_from_groups"
],
"default": "all"
},
"actor_groups": {
"description": "The actor group the actors are in. Required if method is not all",
"type": "array",
"items": {
"type": "string"
}
}
},
"required": ["scenario"],
"if": {
"anyOf": [
{
"properties": {
"method": { "const": "remove_from_groups" }
},
"required": ["method"]
},
{
"properties": {
"method": { "const": "keep_from_groups" }
},
"required": ["method"]
}
]
},
"then": {
"required": ["actor_groups"]
}
},
"default": []
},
"to_keep": {
"description": "A list of actors not to remove. Use this to keep specific actors from a scenario or actor group that has been removed",
"type": "array",
"items": {
"$ref": "#/$defs/actor_reference_with_layer"
},
"default": []
}
},
"required": ["to_remove"],
"default": {}
MayberryZoom marked this conversation as resolved.
Show resolved Hide resolved
},
"show_shields_on_minimap": {
"type": "boolean",
"description": "Deprecated. Used to remove shields from the minimaps in Door Lock Rando.",
Expand Down
85 changes: 85 additions & 0 deletions src/open_dread_rando/specific_patches/mass_delete_actors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from __future__ import annotations

Check warning on line 1 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L1

Added line #L1 was not covered by tests

import typing

Check warning on line 3 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L3

Added line #L3 was not covered by tests

from open_dread_rando.patcher_editor import PatcherEditor

Check warning on line 5 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L5

Added line #L5 was not covered by tests


class ActorReferenceTuple(typing.NamedTuple):
scenario: str
actor_layer: str
sublayer: str
actor: str

Check warning on line 12 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L8-L12

Added lines #L8 - L12 were not covered by tests

def from_dict(reference: dict[str, str]) -> ActorReferenceTuple:
return ActorReferenceTuple(

Check warning on line 15 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L14-L15

Added lines #L14 - L15 were not covered by tests
reference["scenario"],
reference["actor_layer"],
reference.get("sublayer", reference.get("layer")),
reference["actor"]
)

def _remove_all_actors(editor: PatcherEditor, scenario_name: str, actor_layer: str) -> set[ActorReferenceTuple]:
to_remove = set()
scenario = editor.get_scenario(scenario_name)

Check warning on line 24 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L22-L24

Added lines #L22 - L24 were not covered by tests

for sublayer_name, actor_name, actor in scenario.all_actors_in_actor_layer(actor_layer):
to_remove.add(ActorReferenceTuple(scenario_name, actor_layer, sublayer_name, actor_name))

Check warning on line 27 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L26-L27

Added lines #L26 - L27 were not covered by tests

return to_remove

Check warning on line 29 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L29

Added line #L29 was not covered by tests

def _remove_actors_from_groups(editor: PatcherEditor, scenario_name: str, actor_layer: str,

Check warning on line 31 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L31

Added line #L31 was not covered by tests
actor_groups: list[str]) -> set[ActorReferenceTuple]:
to_remove = set()
scenario = editor.get_scenario(scenario_name)

Check warning on line 34 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L33-L34

Added lines #L33 - L34 were not covered by tests

for group in actor_groups:
for actor_link in scenario.get_actor_group(group, actor_layer):
to_remove.add(ActorReferenceTuple(**editor.reference_for_link(actor_link, scenario_name)))

Check warning on line 38 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L36-L38

Added lines #L36 - L38 were not covered by tests

return to_remove

Check warning on line 40 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L40

Added line #L40 was not covered by tests

def _remove_actors_not_in_groups(editor: PatcherEditor, scenario_name: str, actor_layer: str,

Check warning on line 42 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L42

Added line #L42 was not covered by tests
actor_groups: list[str]) -> tuple[set[ActorReferenceTuple], set[ActorReferenceTuple]]:
to_remove = set()
to_keep = set()
scenario = editor.get_scenario(scenario_name)

Check warning on line 46 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L44-L46

Added lines #L44 - L46 were not covered by tests

for group in scenario.actor_groups_for_actor_layer(actor_layer):
if group not in actor_groups:
for actor_link in scenario.get_actor_group(group, actor_layer):
to_remove.add(ActorReferenceTuple(**editor.reference_for_link(actor_link, scenario_name)))

Check warning on line 51 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L48-L51

Added lines #L48 - L51 were not covered by tests
else:
for actor_link in scenario.get_actor_group(group, actor_layer):
to_keep.add(ActorReferenceTuple(**editor.reference_for_link(actor_link, scenario_name)))

Check warning on line 54 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L53-L54

Added lines #L53 - L54 were not covered by tests

return to_remove, to_keep

Check warning on line 56 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L56

Added line #L56 was not covered by tests

def mass_delete_actors(editor: PatcherEditor, configuration: dict) -> None:

Check warning on line 58 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L58

Added line #L58 was not covered by tests
# Sets of tuples will be used to ensure no duplicate entries
to_remove = set()
to_keep = { ActorReferenceTuple.from_dict(reference) for reference in configuration["to_keep"] }

Check warning on line 61 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L60-L61

Added lines #L60 - L61 were not covered by tests

for scenario_config in configuration["to_remove"]:
scenario_name = scenario_config["scenario"]
actor_layer = scenario_config["actor_layer"]
method = scenario_config["method"]

Check warning on line 66 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L63-L66

Added lines #L63 - L66 were not covered by tests

if method == "all":
to_remove.update(_remove_all_actors(editor, scenario_name, actor_layer))

Check warning on line 69 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L68-L69

Added lines #L68 - L69 were not covered by tests

elif method == "remove_from_groups":
to_remove.update(_remove_actors_from_groups(editor, scenario_name, actor_layer,

Check warning on line 72 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L71-L72

Added lines #L71 - L72 were not covered by tests
scenario_config["actor_groups"]))

elif method == "keep_from_groups":
new_remove, new_keep = _remove_actors_not_in_groups(editor, scenario_name, actor_layer,

Check warning on line 76 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L75-L76

Added lines #L75 - L76 were not covered by tests
scenario_config["actor_groups"])

to_remove.update(new_remove)
to_keep.update(new_keep)

Check warning on line 80 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L79-L80

Added lines #L79 - L80 were not covered by tests

to_remove.difference_update(to_keep)

Check warning on line 82 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L82

Added line #L82 was not covered by tests

for actor in to_remove:
editor.remove_entity(actor._asdict(), None)

Check warning on line 85 in src/open_dread_rando/specific_patches/mass_delete_actors.py

View check run for this annotation

Codecov / codecov/patch

src/open_dread_rando/specific_patches/mass_delete_actors.py#L84-L85

Added lines #L84 - L85 were not covered by tests
7 changes: 7 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import lupa
import pytest

from open_dread_rando.patcher_editor import PatcherEditor

_FAIL_INSTEAD_OF_SKIP = True


Expand Down Expand Up @@ -56,6 +58,11 @@ def lua_runtime():
return runtime


@pytest.fixture()
def patcher_editor(dread_path):
return PatcherEditor(dread_path)


def pytest_addoption(parser):
parser.addoption('--skip-if-missing', action='store_false', dest="fail_if_missing",
default=True, help="Skip tests instead of missing, in case any asset is missing")
Expand Down
59 changes: 59 additions & 0 deletions tests/test_dread_patcher.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from unittest.mock import MagicMock

from mercury_engine_data_structures.formats.brfld import ActorLayer

from open_dread_rando import dread_patcher
from open_dread_rando.specific_patches.mass_delete_actors import mass_delete_actors


def test_cosmetic_options(lua_runtime):
Expand Down Expand Up @@ -66,3 +69,59 @@ def test_cosmetic_options(lua_runtime):

assert lua_runtime.eval("Init.fEnergyPerTank") == 75
assert lua_runtime.eval("Init.sLayoutUUID") == layoutUUID

def test_mass_delete_actors(patcher_editor):
configuration = {
"to_remove": [
{
"scenario": "s020_magma",
"actor_layer": "rEntitiesLayer",
"method": "all"
},
{
"scenario": "s010_cave",
"actor_layer": "rLightsLayer",
"method": "remove_from_groups",
"actor_groups": [
"lg_collision_camera_001"
]
},
{
"scenario": "s030_baselab",
"actor_layer": "rLightsLayer",
"method": "keep_from_groups",
"actor_groups": [
"lg_collision_camera_011_Default"
]
}
],
"to_keep": [
{
"scenario": "s010_cave",
"actor_layer": "rLightsLayer",
"sublayer": "cave_001_light",
"actor": "spot_001_1"
}
]
}

mass_delete_actors(patcher_editor, configuration)

s010_cave = patcher_editor.get_scenario("s010_cave")
cave_light_001 = s010_cave.get_actor_group("lg_collision_camera_001", "rLightsLayer")
cave_spot_001_1_link = patcher_editor.build_link("spot_001_1", "cave_001_light", ActorLayer.LIGHTS)

assert cave_light_001 == [cave_spot_001_1_link]

s020_magma = patcher_editor.get_scenario("s020_magma")
magma_entities = [actor_name for (sublayer_name, actor_name, actor) in s020_magma.all_actors_in_actor_layer()]

assert len(magma_entities) == 0

s030_baselab = patcher_editor.get_scenario("s030_baselab")
lab_light_001 = s030_baselab.get_actor_group("lg_collision_camera_001_Default", "rLightsLayer")
lab_light_010 = s030_baselab.get_actor_group("lg_collision_camera_010_Default", "rLightsLayer")
lab_light_011 = s030_baselab.get_actor_group("lg_collision_camera_011_Default", "rLightsLayer")
cubemap_010_link = patcher_editor.build_link("cubemap_010", "base_010_light", ActorLayer.LIGHTS)

assert len(lab_light_011) == 3 and len(lab_light_001) == 0 and cubemap_010_link in lab_light_010
32 changes: 32 additions & 0 deletions tests/test_files/patcher_files/april_fools_patcher.json
Original file line number Diff line number Diff line change
Expand Up @@ -7448,6 +7448,38 @@
"{c1}Metroid DNA 4{c0} is guarded by {c2}E.M.M.I.-07PB{c0}."
]
},
"mass_delete_actors": {
"to_remove": [
{
"scenario": "s020_magma",
"method": "all"
},
{
"scenario": "s010_cave",
"actor_layer": "rLightsLayer",
"method": "remove_from_groups",
"actor_groups": [
"lg_collision_camera_001"
]
},
{
"scenario": "s030_baselab",
"actor_layer": "rLightsLayer",
"method": "keep_from_groups",
"actor_groups": [
"lg_collision_camera_011_Default"
]
}
],
"to_keep": [
{
"scenario": "s010_cave",
"actor_layer": "rLightsLayer",
"sublayer": "cave_001_light",
"actor": "spot_001_1"
}
]
},
"layout_uuid": "00000000-0000-1111-0000-000000000000",
"mod_compatibility": "ryujinx",
"mod_category": "romfs"
Expand Down
Loading