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 support for changing the final boss #454

Merged
merged 16 commits into from
Aug 10, 2024
6 changes: 5 additions & 1 deletion src/open_samus_returns_rando/files/custom/arachnus.lua
Original file line number Diff line number Diff line change
@@ -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")
Expand Down
33 changes: 33 additions & 0 deletions src/open_samus_returns_rando/files/custom/scenario.lua
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,39 @@ 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()
if CurrentScenario.bFirstTimeCompleted then
Game.StopEnvironmentSound()
Game.AddGUISF(2.5, GUI.MainMenuGoToState, "i", 28)
else
dyceron marked this conversation as resolved.
Show resolved Hide resolved
Game.GoToMainMenu()
end
end

Scenario.QueuedPopups = Scenario.QueuedPopups or Queue()

function Scenario.ShowIfNotActive()
Expand Down
5 changes: 5 additions & 0 deletions src/open_samus_returns_rando/files/levels/s000_surface.lua
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ function s000_surface.InitFromBlackboard()
Game.SetPlayerInputEnabled(false, false)
s000_surface.LaunchPlanetArrival()
end
if not Blackboard.GetProp("DEFEATED_ENEMIES", "Ridley") and Game.GetEntity("LE_Item_Ridley") ~= nil then
Game.DisableEntity("LE_Item_Ridley")
else
Game.EnableEntity("LE_Item_Ridley")
end
dyceron marked this conversation as resolved.
Show resolved Hide resolved
dyceron marked this conversation as resolved.
Show resolved Hide resolved
end
function s000_surface.OnReloaded()
end
Expand Down
10 changes: 10 additions & 0 deletions src/open_samus_returns_rando/files/levels/s020_area2.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 16 additions & 7 deletions src/open_samus_returns_rando/files/levels/s070_area7.lua
Original file line number Diff line number Diff line change
Expand Up @@ -800,20 +800,26 @@ 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
if not Scenario.ReadFromBlackboard("ManicMinerBotStealOrbPlayed", false) then
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
Expand Down Expand Up @@ -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")
Expand Down
19 changes: 17 additions & 2 deletions src/open_samus_returns_rando/files/levels/s100_area10.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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_)
Expand Down Expand Up @@ -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)
Expand Down
22 changes: 19 additions & 3 deletions src/open_samus_returns_rando/files/levels/s110_surfaceb.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
dyceron marked this conversation as resolved.
Show resolved Hide resolved
Game.GetEntity("Baby Hatchling"):SetVisible(true)
Scenario.LoadNewScenario("s000_surface", "StartPoint0")
end
end
function s110_surfaceb.OnStartRidleyDead()
Game.AddEntityToUpdateInCutscene("LE_RidleyStorm")
Expand Down Expand Up @@ -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
Expand Down
33 changes: 28 additions & 5 deletions src/open_samus_returns_rando/files/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 5 additions & 3 deletions src/open_samus_returns_rando/lua_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
84 changes: 84 additions & 0 deletions src/open_samus_returns_rando/misc_patches/final_boss.py
Original file line number Diff line number Diff line change
@@ -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})
dyceron marked this conversation as resolved.
Show resolved Hide resolved

'''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"})
Loading