diff --git a/src/open_samus_returns_rando/exefs_patches.py b/src/open_samus_returns_rando/exefs_patches.py new file mode 100644 index 00000000..4d9466bf --- /dev/null +++ b/src/open_samus_returns_rando/exefs_patches.py @@ -0,0 +1,36 @@ +from pathlib import Path + +from open_samus_returns_rando.files import files_path + + +def create_exefs_patches( + out_code: Path, out_exheader: Path, input_code: bytes | None, input_exheader: bytes + ) -> None: + if input_code is None: + raise ValueError("Could not get decompressed + decrypted code binary") + + import ips # type: ignore + + # code.bin patching + code_ips_path = files_path().joinpath("exefs_patches", "code.ips") + out_code.parent.mkdir(parents=True, exist_ok=True) + with ( + Path.open(code_ips_path, "rb") as code_ips, + Path.open(out_code, "wb") as result + ): + content = code_ips.read() + patch = ips.Patch.load(content) + patch.apply(input_code, result) + + # exheader.bin patching + # Citra and Luma don't support patching the exheader. User needs to provide it as input and + # here the patch is just applied + exheader_ips_path = files_path().joinpath("exefs_patches", "exheader.ips") + out_exheader.parent.mkdir(parents=True, exist_ok=True) + with ( + Path.open(exheader_ips_path, "rb") as exheader_ips, + Path.open(out_exheader, "wb") as result + ): + content = exheader_ips.read() + patch = ips.Patch.load(content) + patch.apply(input_exheader, result) diff --git a/src/open_samus_returns_rando/files/custom/randoapi.lua b/src/open_samus_returns_rando/files/custom/randoapi.lua new file mode 100644 index 00000000..4e54f82b --- /dev/null +++ b/src/open_samus_returns_rando/files/custom/randoapi.lua @@ -0,0 +1,37 @@ +Game.ImportLibrary("actors/items/randomizerpowerup/scripts/randomizerpowerup.lc", false) + +RandoApi = RandoApi or { + ChangeSuitValues = function(hasVaria, hasGravity) end, + ChangeBeams = function(hasWave, hasSpazer, hasPlasma, dmgSpazer, dmgPlasma, dmgPlasmaWave, dmgPlasmaSpazer) end +} + +function RandoApi.main() +end + +function RandoApi.CheckSuits() + local hasVaria = RandomizerPowerup.GetItemAmount("ITEM_VARIA_SUIT") > 0 + local hasGravity = RandomizerPowerup.GetItemAmount("ITEM_GRAVITY_SUIT") > 0 + + -- Update damage reductions based on the suits + RandoApi.ChangeSuitValues(hasVaria, hasGravity) + + -- Model and damage_alarm updates + if hasGravity then + Game.GetEntity("Samus").MODELUPDATER.sModelAlias = "Gravity" + if hasVaria then + Game.GetPlayer():StopEntityLoopWithFade("actors/samus/damage_alarm.wav", 0.6) + end + elseif hasVaria then + Game.GetEntity("Samus").MODELUPDATER.sModelAlias = "Varia" + Game.GetPlayer():StopEntityLoopWithFade("actors/samus/damage_alarm.wav", 0.6) + end +end + +function RandoApi.CheckBeams() + local hasWave = RandomizerPowerup.GetItemAmount("ITEM_WEAPON_WAVE_BEAM") > 0 + local hasSpazer = RandomizerPowerup.GetItemAmount("ITEM_WEAPON_SPAZER_BEAM") > 0 + local hasPlasma = RandomizerPowerup.GetItemAmount("ITEM_WEAPON_PLASMA_BEAM") > 0 + + -- Damage values are Solo Spazer, Solo Plasma, Plasma + Wave, and Plasma + Spazer, respctively + RandoApi.ChangeBeams(hasWave, hasSpazer, hasPlasma, 40, 75, 90, 82.5) +end \ No newline at end of file diff --git a/src/open_samus_returns_rando/files/custom/scenario.lua b/src/open_samus_returns_rando/files/custom/scenario.lua index eaaa385f..db034a88 100644 --- a/src/open_samus_returns_rando/files/custom/scenario.lua +++ b/src/open_samus_returns_rando/files/custom/scenario.lua @@ -2,6 +2,7 @@ Game.ImportLibrary("system/scripts/scenario_original.lua") Game.ImportLibrary("system/scripts/guilib.lua", false) Game.ImportLibrary("system/scripts/queue.lua", false) Game.ImportLibrary("system/scripts/cosmetics.lua", false) +Game.ImportLibrary("system/scripts/randoapi.lua", false) Game.DoFile("system/scripts/room_names.lua") Game.DoFile("system/scripts/elevators.lua") @@ -50,6 +51,7 @@ end function Scenario.OnSubAreaChange(old_subarea, old_actorgroup, new_subarea, new_actorgroup, disable_fade) Scenario.UpdateProgressiveItemModels() + Scenario.UpdateBlastShields() Scenario.UpdateRoomName(new_subarea) end @@ -114,8 +116,12 @@ function Scenario.InitScenario(_ARG_0_, _ARG_1_, _ARG_2_, _ARG_3_) Game.HUDIdleScreenLeave() Scenario.ShowText() end - + + RandoApi.CheckSuits() + RandoApi.CheckBeams() Scenario.UpdateProgressiveItemModels() + Scenario.UpdateBlastShields() + if Scenario.showNextSFID ~= nil then Game.DelSFByID(Scenario.showNextSFID) Scenario.showNextSFID = nil @@ -225,10 +231,36 @@ function Scenario.OnPostCreditsEnd() Game.GoToMainMenu() end +Scenario._BlastShieldTypes = { + doorwave = { + item = "ITEM_WEAPON_WAVE_BEAM", + damage = {"WAVE_BEAM"}, + }, + doorspazerbeam = { + item = "ITEM_WEAPON_SPAZER_BEAM", + damage = {"POWER_BEAM"}, + }, +} + +function Scenario._UpdateBlastShields() + for name, actordef in pairs(Game.GetEntities()) do + shield_type = Scenario._BlastShieldTypes[actordef] + if shield_type ~= nil and Game.GetItemAmount(Game.GetPlayerName(), shield_type.item) > 0 then + local shield = Game.GetEntity(name) + for _, damage in ipairs(shield_type.damage) do + shield.LIFE:AddDamageSource(damage) + end + end + end +end + +function Scenario.UpdateBlastShields() + Game.AddSF(0.1, "Scenario._UpdateBlastShields", "") +end + Scenario.QueuedPopups = Scenario.QueuedPopups or Queue() function Scenario.ShowIfNotActive() - if Scenario.hideSFID == nil and Scenario.showNextSFID == nil then Scenario.ShowNextAsyncPopup() end diff --git a/src/open_samus_returns_rando/files/exefs_patches/code.ips b/src/open_samus_returns_rando/files/exefs_patches/code.ips index f3178a9c..6d324115 100644 Binary files a/src/open_samus_returns_rando/files/exefs_patches/code.ips and b/src/open_samus_returns_rando/files/exefs_patches/code.ips differ diff --git a/src/open_samus_returns_rando/files/pickups/randomizersuit.lua b/src/open_samus_returns_rando/files/pickups/randomizersuit.lua index 80ce1fb1..5ac1d32e 100644 --- a/src/open_samus_returns_rando/files/pickups/randomizersuit.lua +++ b/src/open_samus_returns_rando/files/pickups/randomizersuit.lua @@ -1,8 +1,16 @@ Game.ImportLibrary("actors/items/randomizerpowerup/scripts/randomizerpowerup.lc", false) + RandomizerSuit = RandomizerSuit or {} function RandomizerSuit.main() end +function RandomizerSuit.CheckLiquidState(liquid) + if liquid ~= nil and liquid.TRIGGER:IsPlayerInside() == true then + liquid.TRIGGER:DisableTrigger() + liquid.TRIGGER:EnableTrigger() + end +end + function RandomizerSuit.ResetLiquidState() -- When collecting an item in a liquid, the liquid must be disabled if the item is Gravity, -- otherwise Samus' movement is permanently Gravityless, even after exiting the liquid. @@ -15,10 +23,7 @@ function RandomizerSuit.ResetLiquidState() for i = 1, 2 do for j = 1, 19 do local water = Game.GetEntity(waterPrefixes[i] .. string.format("%03d", j)) - if water ~= nil and water.TRIGGER:IsPlayerInside() == true then - water.TRIGGER:DisableTrigger() - water.TRIGGER:EnableTrigger() - end + RandomizerSuit.CheckLiquidState(water) end end end @@ -26,11 +31,8 @@ function RandomizerSuit.ResetLiquidState() local lavaScenarios = {"s020_area2", "s033_area3b", "s036_area3c", "s040_area4", "s050_area5"} if scenario == lavaScenarios[i] then for j = 1, 5 do - lava = Game.GetEntity("TG_Lava_" .. string.format("%03d", j)) - if lava ~= nil and lava.TRIGGER:IsPlayerInside() == true then - lava.TRIGGER:DisableTrigger() - lava.TRIGGER:EnableTrigger() - end + local lava = Game.GetEntity("TG_Lava_" .. string.format("%03d", j)) + RandomizerSuit.CheckLiquidState(lava) end end end @@ -53,10 +55,7 @@ function RandomizerSuit.ResetLiquidState() end for i = 1, #liquids do local liquid = Game.GetEntity(liquids[i]) - if liquid.TRIGGER:IsPlayerInside() == true then - Game.GetEntity(liquid[i]).TRIGGER:DisableTrigger() - Game.GetEntity(liquid[i]).TRIGGER:EnableTrigger() - end + RandomizerSuit.CheckLiquidState(liquid) end end end @@ -64,10 +63,5 @@ end function RandomizerSuit.OnPickedUp(progression, actorOrName) RandomizerSuit.ResetLiquidState() RandomizerPowerup.OnPickedUp(progression, actorOrName) - if Game.GetItemAmount(Game.GetPlayerName(), "ITEM_GRAVITY_SUIT") > 0 then - Game.GetEntity("Samus").MODELUPDATER.sModelAlias = "Gravity" - else - Game.GetEntity("Samus").MODELUPDATER.sModelAlias = "Varia" - end - Game.GetPlayer():StopEntityLoopWithFade("actors/samus/damage_alarm.wav", 0.6) + RandoApi.CheckSuits() end \ No newline at end of file diff --git a/src/open_samus_returns_rando/files/templates/randomizerpowerup.lua b/src/open_samus_returns_rando/files/templates/randomizerpowerup.lua index a35bea6e..4f3042ab 100644 --- a/src/open_samus_returns_rando/files/templates/randomizerpowerup.lua +++ b/src/open_samus_returns_rando/files/templates/randomizerpowerup.lua @@ -53,9 +53,12 @@ function RandomizerPowerup.OnPickedUp(resources, actorOrName, regionName) for _, resource in ipairs(granted) do RandomizerPowerup.IncreaseAmmo(resource) + RandoApi.CheckBeams() end Scenario.UpdateProgressiveItemModels() + Scenario.UpdateBlastShields() + if actorOrName ~= nil then RandomizerPowerup.PostCollectionAdjustments(actorOrName) end diff --git a/src/open_samus_returns_rando/lua_editor.py b/src/open_samus_returns_rando/lua_editor.py index bc23d490..4cbba6b7 100644 --- a/src/open_samus_returns_rando/lua_editor.py +++ b/src/open_samus_returns_rando/lua_editor.py @@ -324,30 +324,18 @@ def save_modifications(self, editor: PatcherEditor, configuration: dict) -> None self._add_replacement_files(editor, configuration) # add new system scripts - editor.add_new_asset( - "system/scripts/guilib.lc", - Lua(Container(lua_text=files_path().joinpath("custom", "guilib.lua").read_text()), editor.target_game), - [] - ) - - editor.add_new_asset( - "system/scripts/queue.lc", - Lua(Container(lua_text=files_path().joinpath("custom", "queue.lua").read_text()), editor.target_game), - [] - ) - - cosmetics_script = cosmetic_patches.lua_cosmetics(configuration["cosmetic_patches"]) - editor.add_new_asset( - "system/scripts/cosmetics.lc", - Lua(Container(lua_text=cosmetics_script), editor.target_game), - [] - ) - - editor.add_new_asset( - "system/scripts/room_names.lc", - Lua(Container(lua_text=files_path().joinpath("custom", "room_names.lua").read_text()), editor.target_game), - [] - ) + SYSTEM_SCRIPTS = ["cosmetics", "guilib", "queue", "randoapi", "room_names"] + for system_script in SYSTEM_SCRIPTS: + lua_text_replacement = ( + files_path().joinpath("custom", f"{system_script}.lua").read_text() + if system_script != "cosmetics" + else cosmetic_patches.lua_cosmetics(configuration["cosmetic_patches"]) + ) + editor.add_new_asset( + f"system/scripts/{system_script}.lc", + Lua(Container(lua_text=lua_text_replacement), editor.target_game), + [], + ) # replace ensured scripts with the final code final_metroid_script = lua_util.replace_lua_template("metroid_template.lua", {"mapping": self._metroid_dict}) diff --git a/src/open_samus_returns_rando/multiworld_integration.py b/src/open_samus_returns_rando/pickups/multiworld_integration.py similarity index 50% rename from src/open_samus_returns_rando/multiworld_integration.py rename to src/open_samus_returns_rando/pickups/multiworld_integration.py index 8339e6fe..e742c486 100644 --- a/src/open_samus_returns_rando/multiworld_integration.py +++ b/src/open_samus_returns_rando/pickups/multiworld_integration.py @@ -1,4 +1,3 @@ -from pathlib import Path from open_samus_returns_rando.files import files_path from open_samus_returns_rando.lua_editor import get_parent_for @@ -30,37 +29,3 @@ def get_lua_for_item(progression: list[list[dict[str, str | int]]], region_name: return (f'{parent_content}\nMultiworldPickup.OnPickedUp({progression_as_lua},nil,{region_name})' .replace("\n", "\\\n").replace("'", "\\'") ) - - -def create_exefs_patches( - out_code: Path, out_exheader: Path, input_code: bytes | None, input_exheader: bytes, enabled: bool - ) -> None: - if not enabled: - return - - if input_code is None: - raise ValueError("Could not get decompressed + decrypted code binary") - - import ips # type: ignore - - # Citra and Luma don't support patching the exheader. User needs to provide it as input and - # here the patch is just applied - exheader_ips_path = files_path().joinpath("exefs_patches", "exheader.ips") - out_exheader.parent.mkdir(parents=True, exist_ok=True) - with ( - Path.open(exheader_ips_path, "rb") as exheader_ips, - Path.open(out_exheader, "wb") as result - ): - content = exheader_ips.read() - patch = ips.Patch.load(content) - patch.apply(input_exheader, result) - - code_ips_path = files_path().joinpath("exefs_patches", "code.ips") - out_code.parent.mkdir(parents=True, exist_ok=True) - with ( - Path.open(code_ips_path, "rb") as code_ips, - Path.open(out_code, "wb") as result - ): - content = code_ips.read() - patch = ips.Patch.load(content) - patch.apply(input_code, result) diff --git a/src/open_samus_returns_rando/samus_returns_patcher.py b/src/open_samus_returns_rando/samus_returns_patcher.py index 6dbdac1a..9cdd5670 100644 --- a/src/open_samus_returns_rando/samus_returns_patcher.py +++ b/src/open_samus_returns_rando/samus_returns_patcher.py @@ -6,6 +6,7 @@ from mercury_engine_data_structures.file_tree_editor import OutputFormat from open_samus_returns_rando.debug import debug_custom_pickups, debug_spawn_points +from open_samus_returns_rando.exefs_patches import create_exefs_patches from open_samus_returns_rando.files import files_path from open_samus_returns_rando.logger import LOG from open_samus_returns_rando.lua_editor import LuaEditor @@ -17,7 +18,6 @@ from open_samus_returns_rando.misc_patches.final_boss import patch_final_boss from open_samus_returns_rando.misc_patches.spawn_points import patch_custom_spawn_points from open_samus_returns_rando.misc_patches.text_patches import add_spiderboost_status, apply_text_patches -from open_samus_returns_rando.multiworld_integration import create_exefs_patches from open_samus_returns_rando.patcher_editor import PatcherEditor from open_samus_returns_rando.pickups.custom_pickups import patch_custom_pickups from open_samus_returns_rando.pickups.pickup import patch_pickups @@ -153,13 +153,12 @@ def patch_extracted(input_path: Path, output_path: Path, configuration: dict) -> out_code = output_path.joinpath("code.bin") out_exheader = output_path.joinpath("exheader.bin") - # Create Exefs patches for multiworld + # Create Exefs patches for multiworld and other binary modifications LOG.info("Creating exefs patches") create_exefs_patches( out_code, out_exheader, parsed_rom.get_code_binary(), parsed_rom.exheader(), - configuration["enable_remote_lua"] ) LOG.info("Saving modified lua scripts") diff --git a/src/open_samus_returns_rando/specific_patches/door_patches.py b/src/open_samus_returns_rando/specific_patches/door_patches.py index 99fe0ebb..d4f6b87d 100644 --- a/src/open_samus_returns_rando/specific_patches/door_patches.py +++ b/src/open_samus_returns_rando/specific_patches/door_patches.py @@ -220,6 +220,12 @@ def _patch_tiles(editor: PatcherEditor) -> None: door_tile["icon"] = "doorpowerright" +def _patch_beam_door_weaknesses(editor: PatcherEditor) -> None: + # Prevent stronger beams from opening doors for weaker beams with split beams + for door in ["doorwave", "doorspazerbeam"]: + beam_door = editor.get_file(f"actors/props/{door}/charclasses/{door}.bmsad", Bmsad) + beam_door.raw.components.LIFE.functions[0].params.Param1.value = "" + def _static_door_patches(editor: PatcherEditor) -> None: _patch_one_way_doors(editor) @@ -228,6 +234,7 @@ def _static_door_patches(editor: PatcherEditor) -> None: _patch_beam_covers(editor) _patch_charge_doors(editor) _patch_tiles(editor) + _patch_beam_door_weaknesses(editor) class ActorData(Enum): diff --git a/src/open_samus_returns_rando/specific_patches/game_patches.py b/src/open_samus_returns_rando/specific_patches/game_patches.py index a31273f5..95b7e590 100644 --- a/src/open_samus_returns_rando/specific_patches/game_patches.py +++ b/src/open_samus_returns_rando/specific_patches/game_patches.py @@ -21,9 +21,7 @@ def _remove_pb_weaknesses(editor: PatcherEditor, configuration: dict) -> None: # Charge Door if configuration["charge_door_buff"]: for door in ["doorchargecharge", "doorclosedcharge"]: - charge_door = editor.get_file( - f"actors/props/{door}/charclasses/{door}.bmsad", Bmsad - ) + charge_door = editor.get_file(f"actors/props/{door}/charclasses/{door}.bmsad", Bmsad) func = charge_door.raw.components.LIFE.functions[0] if func.params.Param1.value: func.params.Param1.value = "CHARGE_BEAM" @@ -33,18 +31,14 @@ def _remove_pb_weaknesses(editor: PatcherEditor, configuration: dict) -> None: # Beam Doors if configuration["beam_door_buff"]: for door in ["doorwave", "doorspazerbeam", "doorcreature"]: - beam_door = editor.get_file( - f"actors/props/{door}/charclasses/{door}.bmsad", Bmsad - ) - func_wp = beam_door.raw.components.LIFE.functions[1] - func_s = beam_door.raw.components.LIFE.functions[2] - if func_wp.params.Param1.value: - if door == "doorwave": - func_wp.params.Param1.value = "WAVE_BEAM" - else: - func_wp.params.Param1.value = "PLASMA_BEAM" - if func_s.params.Param1.value: - func_s.params.Param1.value = "SPAZER_BEAM" + beam_door = editor.get_file(f"actors/props/{door}/charclasses/{door}.bmsad", Bmsad) + func = beam_door.raw.components.LIFE.functions + if door == "doorwave": + func[1].params.Param1.value = "" + elif door == "doorcreature": + func[1].params.Param1.value = "PLASMA_BEAM" + else: + func[2].params.Param1.value = "" # Blobthrowers/Steel Orbs if configuration["beam_burst_buff"]: @@ -54,9 +48,9 @@ def _remove_pb_weaknesses(editor: PatcherEditor, configuration: dict) -> None: ] for plants in PLANT_FILES: plant = editor.get_file(plants, Bmsad) - plant.raw["components"]["LIFE"]["fields"][ - "bShouldDieWithPowerBomb" - ] = Container({"type": "bool", "value": False}) + plant.raw["components"]["LIFE"]["fields"]["bShouldDieWithPowerBomb"] = Container( + {"type": "bool", "value": False} + ) def _remove_grapple_blocks(editor: PatcherEditor, configuration: dict) -> None: