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 @@ def rename_shields(self, door: Container, scenario: str):
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)
old_sName = shieldActor.sName

# skip hdoors (doors where the environment covers one side of the door)
Expand All @@ -411,16 +412,18 @@ def rename_shields(self, door: Container, scenario: str):
# 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)

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

# 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
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 @@ def apply_patches(editor: PatcherEditor, lua_editor: LuaEditor, configuration: d
# Specific game patches
game_patches.apply_game_patches(editor, configuration.get("game_patches", {}))

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

# 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 provided scenario or actor group that",
MayberryZoom marked this conversation as resolved.
Show resolved Hide resolved
"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
66 changes: 66 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,66 @@
from collections import namedtuple

from open_dread_rando.patcher_editor import PatcherEditor

ActorReferenceTuple = namedtuple("ActorReferenceTuple", ["scenario", "actor_layer", "sublayer", "actor"])
MayberryZoom marked this conversation as resolved.
Show resolved Hide resolved

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

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))

def _remove_actors_from_groups(editor: PatcherEditor, scenario_name: str, actor_layer: str,
actor_groups: list[str], to_remove: set) -> None:
scenario = editor.get_scenario(scenario_name)

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)))

def _remove_actors_not_in_groups(editor: PatcherEditor, scenario_name: str, actor_layer: str,
actor_groups: list[str], to_remove: set, to_keep) -> None:
scenario = editor.get_scenario(scenario_name)

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)))
else:
for actor_link in scenario.get_actor_group(group, actor_layer):
to_keep.add(ActorReferenceTuple(**editor.reference_for_link(actor_link, scenario_name)))

def mass_delete_actors(editor: PatcherEditor, configuration: dict) -> None:
# Sets of tuples will be used to ensure no duplicate entries
to_remove = set()
to_keep = set()

for reference in configuration["to_keep"]:
to_keep.add(ActorReferenceTuple(
reference["scenario"],
MayberryZoom marked this conversation as resolved.
Show resolved Hide resolved
reference["actor_layer"],
reference.get("sublayer", reference["layer"]),
reference["actor"]
))

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

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

elif method == "remove_from_groups":
_remove_actors_from_groups(editor, scenario_name, actor_layer, scenario_config["actor_groups"], to_remove)

elif method == "keep_from_groups":
_remove_actors_not_in_groups(editor, scenario_name, actor_layer,
scenario_config["actor_groups"], to_remove, to_keep)

for reference in to_keep:
to_remove.discard(reference)

for actor in to_remove:
editor.remove_entity(actor._asdict(), None)
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_01"
}
]
},
"layout_uuid": "00000000-0000-1111-0000-000000000000",
"mod_compatibility": "ryujinx",
"mod_category": "romfs"
Expand Down
Loading