Skip to content

Commit

Permalink
Merge pull request #510 from randovania/rando-api
Browse files Browse the repository at this point in the history
Implement RandoAPI
  • Loading branch information
ThanatosGit authored Jan 24, 2025
2 parents a803ed5 + eb76d64 commit 26aa527
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 101 deletions.
36 changes: 36 additions & 0 deletions src/open_samus_returns_rando/exefs_patches.py
Original file line number Diff line number Diff line change
@@ -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)
37 changes: 37 additions & 0 deletions src/open_samus_returns_rando/files/custom/randoapi.lua
Original file line number Diff line number Diff line change
@@ -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
36 changes: 34 additions & 2 deletions src/open_samus_returns_rando/files/custom/scenario.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Binary file modified src/open_samus_returns_rando/files/exefs_patches/code.ips
Binary file not shown.
32 changes: 13 additions & 19 deletions src/open_samus_returns_rando/files/pickups/randomizersuit.lua
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -15,22 +23,16 @@ 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
for i = 1, 5 do
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
Expand All @@ -53,21 +55,13 @@ 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

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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
36 changes: 12 additions & 24 deletions src/open_samus_returns_rando/lua_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
5 changes: 2 additions & 3 deletions src/open_samus_returns_rando/samus_returns_patcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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")
Expand Down
7 changes: 7 additions & 0 deletions src/open_samus_returns_rando/specific_patches/door_patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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):
Expand Down
Loading

0 comments on commit 26aa527

Please sign in to comment.