diff --git a/src/open_samus_returns_rando/files/custom/arachnus.lua b/src/open_samus_returns_rando/files/custom/arachnus.lua index 710174b..9125305 100644 --- a/src/open_samus_returns_rando/files/custom/arachnus.lua +++ b/src/open_samus_returns_rando/files/custom/arachnus.lua @@ -1,6 +1,10 @@ Game.ImportLibrary("actors/characters/arachnus/scripts/arachnus_original.lc") function Arachnus.main() - Game.AddSF(0.0, "Arachnus.RotatePickup", "") + if Init.sFinalBoss == "Arachnus" then + Game.DeleteEntity("LE_PowerUp_Springball") + else + Game.AddSF(0.0, "Arachnus.RotatePickup", "") + end end function Arachnus.RotatePickup() local springball = Game.GetEntity("LE_PowerUp_Springball") diff --git a/src/open_samus_returns_rando/files/custom/scenario.lua b/src/open_samus_returns_rando/files/custom/scenario.lua index 58ad832..0179b26 100644 --- a/src/open_samus_returns_rando/files/custom/scenario.lua +++ b/src/open_samus_returns_rando/files/custom/scenario.lua @@ -201,6 +201,34 @@ function Scenario.RandoOnElevatorUse(from_actor, _ARG_1_, _ARG_2_) Elevator.Use("c10_samus", destination.scenario, destination.actor, _ARG_2_) end +function Scenario.ShowFinalBossMessage() + if Init.sFinalBoss == "Ridley" then return end + local boss = Init.sFinalBoss + local startpoint = boss + if boss == "Diggernaut" then + startpoint = "ManicMiner" + elseif boss == "Queen" then + boss = "the Queen" + end + GUI.LaunchMessage("Not enough Metroid DNA!\nCollect more DNA to fight " .. boss .. "!", "RandomizerPowerup.Dummy", "") + if Init.sFinalBoss ~= "Queen" then + Game.AddSF(0, "Scenario.FinalBossReload", "s", startpoint) + end +end + +function Scenario.FinalBossReload(startpoint) + Scenario.LoadNewScenario(Scenario.CurrentScenarioID, "ST_SG_" .. startpoint) +end + +function Scenario.LaunchCredits() + Game.ShowEndGameCredits(true) +end + +function Scenario.OnPostCreditsEnd() + Game.SaveGameComplete() + Game.GoToMainMenu() +end + Scenario.QueuedPopups = Scenario.QueuedPopups or Queue() function Scenario.ShowIfNotActive() diff --git a/src/open_samus_returns_rando/files/levels/s000_surface.lua b/src/open_samus_returns_rando/files/levels/s000_surface.lua index fd33b28..3fa2101 100644 --- a/src/open_samus_returns_rando/files/levels/s000_surface.lua +++ b/src/open_samus_returns_rando/files/levels/s000_surface.lua @@ -93,6 +93,13 @@ function s000_surface.InitFromBlackboard() Game.SetPlayerInputEnabled(false, false) s000_surface.LaunchPlanetArrival() end + if Game.GetEntity("LE_Item_Ridley") ~= nil then + if not Blackboard.GetProp("DEFEATED_ENEMIES", "Ridley") then + Game.DisableEntity("LE_Item_Ridley") + else + Game.EnableEntity("LE_Item_Ridley") + end + end end function s000_surface.OnReloaded() end diff --git a/src/open_samus_returns_rando/files/levels/s020_area2.lua b/src/open_samus_returns_rando/files/levels/s020_area2.lua index 3a32c08..4c3e76c 100644 --- a/src/open_samus_returns_rando/files/levels/s020_area2.lua +++ b/src/open_samus_returns_rando/files/levels/s020_area2.lua @@ -64,7 +64,17 @@ end function s020_area2.OnExit() end function s020_area2.OnSubAreaChange(_ARG_0_, _ARG_1_, _ARG_2_, _ARG_3_, _ARG_4_) + if _ARG_2_ == "collision_camera_005" and Init.sFinalBoss == "Arachnus" and 0 < Scenario.ReadFromBlackboard("entity_SG_Arachnus_001_deaths", 0) then + if _ARG_0_ == "" then + Scenario.OnPostCreditsEnd() + else + Game.AddSF(0, "Scenario.LaunchCredits", "") + end + end if _ARG_2_ == "collision_camera_006" then + if Init.sFinalBoss == "Arachnus" and not Blackboard.GetProp("GAME", "OBJECTIVE_COMPLETE") then + Game.AddSF(0.0, "Scenario.ShowFinalBossMessage", "") + end Game.DelSF("s020_area2.CloseDoor004") Game.AddSF(0.1, "s020_area2.CloseDoor004", "") end diff --git a/src/open_samus_returns_rando/files/levels/s070_area7.lua b/src/open_samus_returns_rando/files/levels/s070_area7.lua index c84c77b..bc8493d 100644 --- a/src/open_samus_returns_rando/files/levels/s070_area7.lua +++ b/src/open_samus_returns_rando/files/levels/s070_area7.lua @@ -800,12 +800,16 @@ function s070_area7.OnFansInit() end end function s070_area7.OnSubAreaChange(_ARG_0_, _ARG_1_, _ARG_2_, _ARG_3_, _ARG_4_) - if _ARG_0_ == "" and _ARG_2_ == "collision_camera_043" and _ARG_3_ == "ManicMiner_Dead" and Game.GetEntity("SG_ManicMinerBot") ~= nil then - Game.GetEntity("SG_ManicMinerBot").SPAWNGROUP:EnableSpawnGroup() - if Game.GetEntityFromSpawnPoint("SP_ManicMinerBot") ~= nil then - Game.GetEntityFromSpawnPoint("SP_ManicMinerBot").ANIMATION.sDefaultSpawnAction = "deathloop" - Game.GetEntityFromSpawnPoint("SP_ManicMinerBot").ANIMATION:SetAction("deathloop", true) - Game.GetEntityFromSpawnPoint("SP_ManicMinerBot").AI:OnDeadCutsceneEnd() + if _ARG_0_ == "" and _ARG_2_ == "collision_camera_043" and _ARG_3_ == "ManicMiner_Dead" then + if Init.sFinalBoss == "Diggernaut" then + Scenario.OnPostCreditsEnd() + elseif Game.GetEntity("SG_ManicMinerBot") ~= nil then + Game.GetEntity("SG_ManicMinerBot").SPAWNGROUP:EnableSpawnGroup() + if Game.GetEntityFromSpawnPoint("SP_ManicMinerBot") ~= nil then + Game.GetEntityFromSpawnPoint("SP_ManicMinerBot").ANIMATION.sDefaultSpawnAction = "deathloop" + Game.GetEntityFromSpawnPoint("SP_ManicMinerBot").ANIMATION:SetAction("deathloop", true) + Game.GetEntityFromSpawnPoint("SP_ManicMinerBot").AI:OnDeadCutsceneEnd() + end end end if _ARG_0_ == "collision_camera_041" and _ARG_2_ == "collision_camera_038" then @@ -813,7 +817,9 @@ function s070_area7.OnSubAreaChange(_ARG_0_, _ARG_1_, _ARG_2_, _ARG_3_, _ARG_4_) s070_area7.LaunchManicMinerBotStealOrb() end elseif _ARG_0_ == "collision_camera_034" and _ARG_2_ == "collision_camera_043" then - if not Scenario.ReadFromBlackboard("ManicMinerBotIntroCutscenePlayed", false) then + if Init.sFinalBoss == "Diggernaut" and not Blackboard.GetProp("GAME", "OBJECTIVE_COMPLETE") then + Game.AddSF(0, "Scenario.ShowFinalBossMessage", "") + elseif not Scenario.ReadFromBlackboard("ManicMinerBotIntroCutscenePlayed", false) then Game.SetCameraEnemy("manicminerbot") s070_area7.LaunchManicMinerBotIntroCutscene() end @@ -908,6 +914,9 @@ function s070_area7.OnStartManicMinerBotDeathCutscene() end end function s070_area7.OnEndManicMinerBotDeathCutscene() + if Init.sFinalBoss == "Diggernaut" then + Scenario.LaunchCredits() + end s070_area7.UpdateMinimapItemsphereIcon() Game.RemoveEntityToUpdateInCutscene("LE_ManicMinerWall") Game.RemoveEntityToUpdateInCutscene("LE_PowerUp_Powerbomb") diff --git a/src/open_samus_returns_rando/files/levels/s100_area10.lua b/src/open_samus_returns_rando/files/levels/s100_area10.lua index 2811742..4bfdc1e 100644 --- a/src/open_samus_returns_rando/files/levels/s100_area10.lua +++ b/src/open_samus_returns_rando/files/levels/s100_area10.lua @@ -76,7 +76,9 @@ function s100_area10.InitFromBlackboard() Scenario.WriteToBlackboard("firstTimeMetroidHatchlingIntroPlayed", "b", true) Game.DisableEntity("LE_Baby_Hatchling") Game.DisableTrigger("TG_MetroidRadar") - Game.GetEntity("LE_RandoDNA").USABLE:Activate(false) + if Init.sFinalBoss ~= "Queen" then + Game.GetEntity("LE_RandoDNA").USABLE:Activate(false) + end s100_area10.SetLowModelsVisibility(false) if Game.GetEntity("LE_ValveQueen") ~= nil then if Blackboard.GetProp("DEFEATED_ENEMIES", "Metroid") ~= nil and s100_area10.iNumMetroids == Blackboard.GetProp("DEFEATED_ENEMIES", "Metroid") then @@ -109,6 +111,11 @@ end function s100_area10.OnEnter_ActivationTeleport_10_02() Game.OnTeleportApproached("LE_Teleporter_10_02") end +function s100_area10.OnEnter_Queen_Access() + if Init.sFinalBoss == "Queen" and not Blackboard.GetProp("GAME", "OBJECTIVE_COMPLETE") and Blackboard.GetProp("DEFEATED_ENEMIES", "Metroid") ~= nil and s100_area10.iNumMetroids == Blackboard.GetProp("DEFEATED_ENEMIES", "Metroid") then + Game.AddSF(0, "Scenario.ShowFinalBossMessage", "") + end +end function s100_area10.OnLarva_001_Generated(_ARG_0_, _ARG_1_) if _ARG_1_ ~= nil and _ARG_1_.AI ~= nil then _ARG_1_.AI.bPlaceholder = false @@ -291,10 +298,15 @@ function s100_area10.OnSubAreaChange(_ARG_0_, _ARG_1_, _ARG_2_, _ARG_3_, _ARG_4_ elseif _ARG_0_ == "collision_camera_020" then Game.SetSafeFarPlaneFactor(s100_area10.fSafeFarPlaneFactor) end + if _ARG_0_ == "" and _ARG_2_ == "collision_camera_020" and _ARG_3_ == "PostMetroids_001" then + Scenario.OnPostCreditsEnd() + end if _ARG_0_ == "collision_camera_020" and _ARG_2_ == "collision_camera_022" then s100_area10.OnBabyCreationCutsceneTrigger() elseif _ARG_0_ == "collision_camera_019" and _ARG_2_ == "collision_camera_020" and Blackboard.GetProp("DEFEATED_ENEMIES", "Metroid") ~= nil and s100_area10.iNumMetroids == Blackboard.GetProp("DEFEATED_ENEMIES", "Metroid") then - s100_area10.LaunchQueenIntro() + if Init.sFinalBoss ~= "Queen" or Blackboard.GetProp("GAME", "OBJECTIVE_COMPLETE") then + s100_area10.LaunchQueenIntro() + end end Game.BossCheckPointManagerForceUnlockDoors() Scenario.OnSubAreaChange(_ARG_0_, _ARG_1_, _ARG_2_, _ARG_3_, _ARG_4_) @@ -386,6 +398,9 @@ function s100_area10.OnEndQueenDeathCutscene() if Game.GetPlayer() ~= nil then Game.GetPlayer().LIFE:SetInvulnerableWithReaction(false) end + if Init.sFinalBoss == "Queen" then + Scenario.LaunchCredits() + end Game.EnableTrigger("TG_BabyCreation") Game.SetSceneGroupEnabledByName("sg_vignette_20", true) Game.SetSceneGroupEnabledByName("sg_vignette_09", true) diff --git a/src/open_samus_returns_rando/files/levels/s110_surfaceb.lua b/src/open_samus_returns_rando/files/levels/s110_surfaceb.lua index 7e4f59f..8ed809a 100644 --- a/src/open_samus_returns_rando/files/levels/s110_surfaceb.lua +++ b/src/open_samus_returns_rando/files/levels/s110_surfaceb.lua @@ -63,7 +63,10 @@ function s110_surfaceb.InitFromBlackboard() s110_surfaceb.SetLowModelsVisibility(false) if Game.GetItemAmount(Game.GetPlayerName(), "ITEM_ADN") < 39 and Game.GetItemAmount(Game.GetPlayerName(), "ITEM_BABY_HATCHLING") < 1 then Game.PlayMusicStream(0, "streams/music/t_m2_surface_arr1.wav", -1, -1, -1, 2, 2, 1) -end + end + if Blackboard.GetProp("DEFEATED_ENEMIES", "Ridley") and Game.GetEntity("TG_Ridley_Access") ~= nil then + Game.DeleteEntity("TG_Ridley_Access") + end end function s110_surfaceb.OnReloaded() end @@ -99,6 +102,12 @@ end function s110_surfaceb.OnEnter_ActivationTeleport_00b_01() Game.OnTeleportApproached("LE_Teleporter_00b_01") end +function s110_surfaceb.LoadSurface() + Scenario.LoadNewScenario("s000_surface", "ST_Surface_Connector") +end +function s110_surfaceb.OnEnter_Ridley_Access() + GUI.LaunchMessage("Proteus Ridley can be fought at any time.\nAre you prepared to fight?", "s110_surfaceb.OnReloaded", "s110_surfaceb.LoadSurface") +end function s110_surfaceb.OnRidleyStartPoint() Game.HUDIdleScreenLeave() end @@ -299,6 +308,11 @@ function s110_surfaceb.LaunchRidleyDeadCutscene() GUI.ForceIdleCinematicControlledOn() Game.LaunchCutscene("cutscenes/ridley4/takes/10/ridley410.bmscu") s110_surfaceb.SetLowModelsVisibility(false) + if Init.sFinalBoss ~= "Ridley" then + Blackboard.SetProp("DEFEATED_ENEMIES", "Ridley", "b", true) + Game.GetEntity("Baby Hatchling"):SetVisible(true) + Scenario.LoadNewScenario("s000_surface", "StartPoint0") + end end function s110_surfaceb.OnStartRidleyDead() Game.AddEntityToUpdateInCutscene("LE_RidleyStorm") @@ -560,8 +574,10 @@ function s110_surfaceb.OnSubAreaChange(_ARG_0_, _ARG_1_, _ARG_2_, _ARG_3_, _ARG_ Game.SetScenarioItemEnabledByName("ray01", false) Game.SetSceneGroupEnabledByName("sg_debris02", false) Game.SetSceneGroupEnabledByName("sg_debris03", false) - if _ARG_2_ == "collision_camera_000" and not Blackboard.GetProp("GAME", "OBJECTIVE_COMPLETE") then - Scenario.LoadNewScenario("s000_surface", "ST_Surface_Connector") + if _ARG_2_ == "collision_camera_000" then + if (Init.sFinalBoss == "Ridley" and not Blackboard.GetProp("GAME", "OBJECTIVE_COMPLETE")) or Blackboard.GetProp("DEFEATED_ENEMIES", "Ridley") then + s110_surfaceb.LoadSurface() + end end if _ARG_2_ == "collision_camera_021" and _ARG_0_ == "collision_camera_021" and _ARG_3_ == "RidleyCombat" then if Game.GetEntity("SG_Ridley") ~= nil then diff --git a/src/open_samus_returns_rando/files/schema.json b/src/open_samus_returns_rando/files/schema.json index 7297b04..bf87e3d 100644 --- a/src/open_samus_returns_rando/files/schema.json +++ b/src/open_samus_returns_rando/files/schema.json @@ -255,16 +255,39 @@ }, "default": {} }, - "required_dna": { - "description": "The amount of Metroid DNA required to access Proteus Ridley.", - "type": "integer", - "minimum": 0 - }, "reveal_map_on_start": { "type": "boolean", "default": false, "description": "When true, the game will start with the whole map revealed" }, + "objective": { + "description": "Requirements for completing a seed.", + "type": "object", + "properties": { + "required_dna": { + "description": "The amount of Metroid DNA required to complete the objective.", + "type": "integer", + "minimum": 0 + }, + "final_boss": { + "description": "The required boss to defeat to complete the objective.", + "type": "string", + "enum": [ + "Arachnus", + "Diggernaut", + "Queen", + "Ridley" + ], + "default": "Ridley" + } + }, + "required": [ + "final_boss" + ], + "default": { + "final_boss": "Ridley" + } + }, "game_patches": { "type": "object", "properties": { diff --git a/src/open_samus_returns_rando/files/templates/custom_init.lua b/src/open_samus_returns_rando/files/templates/custom_init.lua index cfe4067..e671ab9 100644 --- a/src/open_samus_returns_rando/files/templates/custom_init.lua +++ b/src/open_samus_returns_rando/files/templates/custom_init.lua @@ -35,6 +35,7 @@ Init.bEnableRoomIds = TEMPLATE("enable_room_ids") Init.sBabyMetroidHint = TEMPLATE("baby_metroid_hint") Init.bTanksRefillAmmo = TEMPLATE("tanks_refill_ammo") Init.iRequiredDNA = TEMPLATE("required_dna") +Init.sFinalBoss = TEMPLATE("final_boss") local orig_log = Game.LogWarn if TEMPLATE("enable_remote_lua") then @@ -60,7 +61,16 @@ function Init.InitGameBlackboard() end end end - Blackboard.SetProp("PLAYER_INVENTORY", "ITEM_ADN", "f", 39 - Init.iRequiredDNA) + Blackboard.SetProp("GAME", "OBJECTIVE_COMPLETE", "b", false) + if Init.iRequiredDNA == 0 then + -- If no DNA is required, then the path to Ridley should always be open + Blackboard.SetProp("GAME", "OBJECTIVE_COMPLETE", "b", true) + Blackboard.SetProp("PLAYER_INVENTORY", "ITEM_ADN", "f", 39) + elseif Init.iRequiredDNA <= 39 then + Blackboard.SetProp("PLAYER_INVENTORY", "ITEM_ADN", "f", 39 - Init.iRequiredDNA) + else + Blackboard.SetProp("PLAYER_INVENTORY", "ITEM_ADN", "f", 0) + end Blackboard.SetProp("PLAYER_INVENTORY", "ITEM_METROID_COUNT", "f", 0) Blackboard.SetProp("PLAYER_INVENTORY", "ITEM_CURRENT_LIFE", "f", Init.tNewGameInventory.ITEM_MAX_LIFE) Blackboard.SetProp("PLAYER_INVENTORY", "ITEM_WEAPON_MISSILE_CURRENT", "f", Init.tNewGameInventory.ITEM_WEAPON_MISSILE_MAX) @@ -73,12 +83,6 @@ function Init.InitGameBlackboard() Blackboard.SetProp("GAME", "Version", "i", SaveGame.Version) Blackboard.SetProp("GAME", "HUD", "b", true) Blackboard.SetProp("GAME", "Player", "s", "samus") - -- If no DNA is required, then the path to Ridley should always be open - if Init.iRequiredDNA == 0 then - Blackboard.SetProp("GAME", "OBJECTIVE_COMPLETE", "b", true) - else - Blackboard.SetProp("GAME", "OBJECTIVE_COMPLETE", "b", false) - end Blackboard.SetProp(Game.GetPlayerBlackboardSectionName(), "LevelID", "s", "c10_samus") Blackboard.SetProp(Game.GetPlayerBlackboardSectionName(), "ScenarioID", "s", TEMPLATE("starting_scenario")) Blackboard.SetProp(Game.GetPlayerBlackboardSectionName(), "StartPoint", "s", TEMPLATE("starting_actor")) diff --git a/src/open_samus_returns_rando/files/templates/randomizerpowerup.lua b/src/open_samus_returns_rando/files/templates/randomizerpowerup.lua index eecd9e5..a43504a 100644 --- a/src/open_samus_returns_rando/files/templates/randomizerpowerup.lua +++ b/src/open_samus_returns_rando/files/templates/randomizerpowerup.lua @@ -195,20 +195,48 @@ function RandomizerPowerup.IncreaseAmmo(resource) end function RandomizerPowerup.ObjectiveComplete() - if RandomizerPowerup.GetItemAmount("ITEM_ADN") == 39 then + local required_dna = 39 + -- Default required_dna is 39, so change the requirement if set higher in the config + if Init.iRequiredDNA > 39 then + required_dna = Init.iRequiredDNA + end + + if RandomizerPowerup.GetItemAmount("ITEM_ADN") == required_dna then Blackboard.SetProp("GAME", "OBJECTIVE_COMPLETE", "b", true) Game.HUDIdleScreenLeave() - local baby = RandomizerPowerup.GetItemAmount("ITEM_BABY_HATCHLING") RandomizerPowerup.UpdateDNACounter() Game.AddGUISF(3, "RandomizerPowerup.DisableBlink", "") + + -- Assign boss names and areas to each boss + local boss = Init.sFinalBoss + local boss_name = boss + local boss_area = "" + if boss == "Arachnus" then + boss_area = "Area 2 Dam Exterior" + elseif boss == "Diggernaut" then + boss_area = "Area 6" + elseif boss == "Queen" then + boss_name = "the Queen" + boss_area = "Area 8" + elseif boss == "Ridley" then + boss_name = "Proteus Ridley" + boss_area = "Surface West" + end + + -- Handle the message depending the boss/other factors + local baby = RandomizerPowerup.GetItemAmount("ITEM_BABY_HATCHLING") + local message = "Enough Metroid DNA has been collected!\nThe path to " .. boss_name .. " has been opened in " .. boss_area .. "!" if baby > 0 then - GUI.LaunchMessage("Enough Metroid DNA has been collected!\nThe path to Proteus Ridley has been opened in Surface West!", - "RandomizerPowerup.Dummy", "") - if Scenario.CurrentScenarioID == "s110_surfaceb" then + GUI.LaunchMessage(message, "RandomizerPowerup.Dummy", "") + if Scenario.CurrentScenarioID == "s110_surfaceb" and boss == "Ridley" then Game.PlayMusicStream(0, "streams/music/k_crateria99.wav", -1, -1, -1, 2, 2, 1) end elseif baby == 0 then - GUI.LaunchMessage("Enough Metroid DNA has been collected!\n" .. Init.sBabyMetroidHint, "RandomizerPowerup.Dummy", "") + if boss == "Ridley" then + GUI.LaunchMessage("Enough Metroid DNA has been collected!\n" .. Init.sBabyMetroidHint, "RandomizerPowerup.Dummy", "") + else + GUI.LaunchMessage(message .. "\n" .. Init.sBabyMetroidHint, "RandomizerPowerup.Dummy", "") + end end end end diff --git a/src/open_samus_returns_rando/lua_editor.py b/src/open_samus_returns_rando/lua_editor.py index 6e45e51..341fe3a 100644 --- a/src/open_samus_returns_rando/lua_editor.py +++ b/src/open_samus_returns_rando/lua_editor.py @@ -200,6 +200,7 @@ def _create_custom_init(self, editor: PatcherEditor, configuration: dict) -> str inventory: dict[str, int] = configuration["starting_items"] starting_location: dict = configuration["starting_location"] starting_text: list[str] = configuration.get("starting_text", []) + objective: dict = configuration["objective"] game_patches: dict = configuration["game_patches"] cosmetic_options: dict = configuration["cosmetic_patches"] configuration_identifier: str = configuration["configuration_identifier"] @@ -261,8 +262,8 @@ def chunks(array: list[str], n: int) -> Iterable[list[str]]: if "baby_metroid_hint" in configuration: baby_metroid_hint = lua_util.wrap_string(configuration["baby_metroid_hint"]) - if "required_dna" in configuration: - required_dna = configuration["required_dna"] + if "required_dna" in objective: + required_dna = objective["required_dna"] else: starting_dna = [item for item in inventory if item.startswith("ITEM_RANDO_DNA")] required_dna = 39 - len(starting_dna) @@ -282,7 +283,8 @@ def chunks(array: list[str], n: int) -> Iterable[list[str]]: "enable_remote_lua": enable_remote_lua, "baby_metroid_hint": baby_metroid_hint, "tanks_refill_ammo": game_patches["tanks_refill_ammo"], - "required_dna": required_dna + "required_dna": required_dna, + "final_boss": lua_util.wrap_string(objective["final_boss"]) } return lua_util.replace_lua_template("custom_init.lua", replacement) diff --git a/src/open_samus_returns_rando/misc_patches/final_boss.py b/src/open_samus_returns_rando/misc_patches/final_boss.py new file mode 100644 index 0000000..707bb40 --- /dev/null +++ b/src/open_samus_returns_rando/misc_patches/final_boss.py @@ -0,0 +1,84 @@ +import typing + +from construct import Container # type: ignore[import-untyped] +from mercury_engine_data_structures.formats import Bmsad, Bmtun +from open_samus_returns_rando.patcher_editor import PatcherEditor + + +class NewTrigger(typing.NamedTuple): + scenario: str + name: str + position: list[float] + size: list[float] + entity_groups: list[str] + + +new_triggers = [ + NewTrigger("s100_area10", "TG_Queen_Access", [1000.0, 12050.0, 0.0], [500, 300, 300], ["collision_camera_019"]), + NewTrigger("s110_surfaceb", "TG_Ridley_Access", [-22800.0, 4400.0, 0.0], [150, 800, 800], ["collision_camera_017"]), +] + + +def add_boss_triggers(editor: PatcherEditor, new_trigger: NewTrigger) -> None: + template_tg = editor.get_scenario("s110_surfaceb").raw.actors[0]["TG_Activation_Teleport_00b_01"] + + scenario_name = new_trigger.scenario + scenario_file = editor.get_scenario(scenario_name) + + editor.copy_actor(scenario_name, new_trigger.position, template_tg, new_trigger.name, 0) + + arguments = scenario_file.raw.actors[0][new_trigger.name]["components"][0]["arguments"] + arguments[3]["value"] = "CurrentScenario.OnEnter_" + new_trigger.name[3:] + # Set the size of the trigger to fill the opening + arguments[16]["value"] = new_trigger.size[0] + arguments[17]["value"] = new_trigger.size[1] + arguments[18]["value"] = new_trigger.size[2] + + for entity_group in new_trigger.entity_groups: + scenario_file.add_actor_to_entity_groups(entity_group, new_trigger.name, True) + + +def patch_final_boss(editor: PatcherEditor, configuration: dict) -> None: + final_boss = configuration["objective"]["final_boss"] + game_patches = configuration["game_patches"] + if final_boss != "Ridley": + # Add new boss triggers + for new_trigger in new_triggers: + add_boss_triggers(editor, new_trigger) + if final_boss == "Arachnus": + # Buff Arachnus so the fight isn't trivial + arachnus = editor.get_file("actors/characters/arachnus/charclasses/arachnus.bmsad", Bmsad) + bmsad_life = arachnus.raw["components"]["LIFE"]["fields"] + # Disable Power Bombs because of the instant kill + bmsad_life["bShouldDieWithPowerBomb"] = Container({"type": "bool", "value": False}) + + '''All beams (except Ice) will use the same factor of 0.33 for balancing with the new health + Power Beam: 25 -> 8.25 + Ice: 10 + Wave: 50 -> 16.5 + Spazer: 210 -> 69.3 + Plasma: 300 -> 99 + ''' + bmsad_life["fPowerBeamFactor"]["value"] = 0.33 + # 1000 -> 500 + bmsad_life["fScrewAttackFactor"]["value"] = 0.5 + + tunables = editor.get_file("system/tunables/tunables.bmtun", Bmtun) + damage = tunables.raw["classes"]["Damage|CTunableCharClassAttackComponent"]["tunables"] + # 50 -> 150 + damage["fDamageArachnusDefault"]["value"] *= 3 + damage["fDamageArachnusEnergyWave"]["value"] *= 3 + damage["fDamageArachnusFireSplash"]["value"] *= 3 + + tunables_life = tunables.raw["classes"]["Life|CTunableCharClassAIComponent"]["tunables"] + # Original health is 3000 + tunables_life["fLifeArachnus"]["value"] = 10000 + elif final_boss == "Diggernaut": + # Move the grapple block by the elevator regardless if the grapple config is set + if not game_patches["remove_elevator_grapple_blocks"]: + scenario = editor.get_scenario("s070_area7") + scenario.raw.actors[9]["LE_GrappleMov_001"]["position"][0] = -15250.0 + elif final_boss == "Queen": + # Remove the Queen wall regardless if the config is set + if not game_patches["reverse_area8"]: + editor.remove_entity({"scenario": "s100_area10", "layer": 9, "actor": "LE_ValveQueen"}) diff --git a/src/open_samus_returns_rando/pickups/custom_pickups.py b/src/open_samus_returns_rando/pickups/custom_pickups.py index a28e35b..6bf71fa 100644 --- a/src/open_samus_returns_rando/pickups/custom_pickups.py +++ b/src/open_samus_returns_rando/pickups/custom_pickups.py @@ -14,9 +14,12 @@ class NewPickups(typing.NamedTuple): new_pickups = [ + NewPickups( + "s000_surface", "LE_Item_Ridley", [-8000.0, 1900.0, 0.0], 168, ["collision_camera_000"] + ), NewPickups( "s100_area10", "LE_Baby_Hatchling", [-3400.0, 11225.0, 0.0], 252, ["collision_camera_022"] - ) + ), ] @@ -43,6 +46,8 @@ def add_pickups(editor: PatcherEditor, new_pickup: NewPickups) -> None: scenario_file.add_actor_to_entity_groups(entity_group, new_pickup.name, True) -def patch_custom_pickups(editor: PatcherEditor) -> None: +def patch_custom_pickups(editor: PatcherEditor, configuration: dict) -> None: for new_pickup in new_pickups: + if configuration["objective"]["final_boss"] == "Ridley" and new_pickup.name == "LE_Item_Ridley": + continue add_pickups(editor, new_pickup) diff --git a/src/open_samus_returns_rando/pickups/pickup.py b/src/open_samus_returns_rando/pickups/pickup.py index 5b8dfc7..68fc5f5 100644 --- a/src/open_samus_returns_rando/pickups/pickup.py +++ b/src/open_samus_returns_rando/pickups/pickup.py @@ -322,7 +322,7 @@ def patch_minimap(self, editor: PatcherEditor, scenario_name: str, actor_name: s # Custom blocks are no longer attached to the pickup, so make all hidden pickups consistent and generic if pickup_tile_icon.icon_priority == "HIDDEN_ITEM" or ( # Boss pickups are now always visible on the map without fighting them, so make icons generic - actor_name in {"LE_PowerUp_Springball", "LE_PowerUp_Powerbomb", "LE_Baby_Hatchling"} + actor_name in {"LE_PowerUp_Springball", "LE_PowerUp_Powerbomb", "LE_Baby_Hatchling", "LE_Item_Ridley"} ): if tile_for_item[0]["tile_type"] == TileType.HEAT: pickup_tile_icon.icon = "itemenabledheat" diff --git a/src/open_samus_returns_rando/samus_returns_patcher.py b/src/open_samus_returns_rando/samus_returns_patcher.py index 63b1b6d..09a7d00 100644 --- a/src/open_samus_returns_rando/samus_returns_patcher.py +++ b/src/open_samus_returns_rando/samus_returns_patcher.py @@ -13,6 +13,7 @@ from open_samus_returns_rando.misc_patches.collision_camera_table import create_collision_camera_table from open_samus_returns_rando.misc_patches.credits import patch_credits from open_samus_returns_rando.misc_patches.elevators import patch_elevators +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 @@ -83,7 +84,7 @@ def patch_extracted(input_path: Path, input_exheader: Path | None, output_path: apply_static_fixes(editor) # Custom pickups - patch_custom_pickups(editor) + patch_custom_pickups(editor, configuration) debug_custom_pickups(editor, configuration["debug_custom_pickups"]) # Patch all pickups @@ -93,6 +94,9 @@ def patch_extracted(input_path: Path, input_exheader: Path | None, output_path: patch_custom_spawn_points(editor) debug_spawn_points(editor, configuration["debug_spawn_points"]) + # Custom final boss + patch_final_boss(editor, configuration) + # Fix unheated heat rooms patch_heat_rooms(editor)