diff --git a/src/open_dread_rando/dread_patcher.py b/src/open_dread_rando/dread_patcher.py index 9207f94fd..35f8382e0 100644 --- a/src/open_dread_rando/dread_patcher.py +++ b/src/open_dread_rando/dread_patcher.py @@ -21,6 +21,7 @@ from open_dread_rando.patcher_editor import PatcherEditor from open_dread_rando.pickups.lua_editor import LuaEditor from open_dread_rando.pickups.pickup import pickup_object_for +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.objective import apply_objective_patches @@ -56,6 +57,13 @@ def create_custom_init(editor: PatcherEditor, configuration: dict) -> str: shards = inventory.pop("ITEM_LIFE_SHARDS") max_life += shards * energy_per_part + inventory.update({ + # TODO: expose shuffling these + "ITEM_WEAPON_POWER_BEAM": 1, + "ITEM_WEAPON_MISSILE_LAUNCHER": 1, + }) + inventory = update_starting_inventory_split_pickups(inventory) + # Game doesn't like to start if some fields are missing, like ITEM_WEAPON_POWER_BOMB_MAX final_inventory = { "ITEM_MAX_LIFE": max_life, @@ -117,6 +125,8 @@ def create_collision_camera_table(editor: PatcherEditor, configuration: dict): editor.add_new_asset("system/scripts/cc_to_room_name.lc", file, ["packs/system/system.pkg"]) def patch_pickups(editor: PatcherEditor, lua_scripts: LuaEditor, pickups_config: list[dict], configuration: dict): + patch_split_pickups(editor) + # add to the TOC editor.add_new_asset("actors/items/randomizer_powerup/scripts/randomizer_powerup.lc", b'', []) diff --git a/src/open_dread_rando/files/custom_scenario.lua b/src/open_dread_rando/files/custom_scenario.lua index 72b859593..a37d19991 100644 --- a/src/open_dread_rando/files/custom_scenario.lua +++ b/src/open_dread_rando/files/custom_scenario.lua @@ -230,6 +230,32 @@ function Scenario.UpdateProgressiveItemModels() Game.AddSF(0.1, "Scenario._UpdateProgressiveItemModels", "") end +Scenario._BlastShieldTypes = { + doorshieldsupermissile = { + item = "ITEM_WEAPON_SUPER_MISSILE", + damage = {"SUPER_MISSILE", "ICE_MISSILE"}, + }, + door_shield_plasma = { + item = "ITEM_WEAPON_PLASMA_BEAM", + damage = {"PLASMA_BEAM"} + } +} +function Scenario._UpdateBlastShields() + for name, actordef in pairs(Game.GetEntities()) do + shield_type = Scenario._BlastShieldTypes[actordef] + if shield_type ~= nil and RandomizerPowerup.HasItem(shield_type.item) then + local shield = Game.GetActor(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 + local original_onload = Scenario.OnLoadScenarioFinished function Scenario.OnLoadScenarioFinished() original_onload() @@ -257,6 +283,7 @@ function Scenario.OnLoadScenarioFinished() if Scenario.VisitAllTeleportScenarios() then return end Scenario.UpdateProgressiveItemModels() + Scenario.UpdateBlastShields() Blackboard.SetProp("GAME_PROGRESS", "RandoMapSeen" .. CurrentScenarioID, "b", true) @@ -339,10 +366,12 @@ end function Scenario.InitFromBlackboard() RandomizerPowerup.ApplyTunableChanges() + RandomizerPowerup.UpdateWeapons() end function Scenario.OnSubAreaChange(old_subarea, old_actorgroup, new_subarea, new_actorgroup, disable_fade) Scenario.UpdateProgressiveItemModels() + Scenario.UpdateBlastShields() Scenario.UpdateRoomName(new_subarea) end diff --git a/src/open_dread_rando/files/randomizer_powerup.lua b/src/open_dread_rando/files/randomizer_powerup.lua index 21b3966b6..6cb3c9181 100644 --- a/src/open_dread_rando/files/randomizer_powerup.lua +++ b/src/open_dread_rando/files/randomizer_powerup.lua @@ -72,7 +72,9 @@ function RandomizerPowerup.OnPickedUp(actor, resources) end RandomizerPowerup.ApplyTunableChanges() + RandomizerPowerup.UpdateWeapons() Scenario.UpdateProgressiveItemModels() + Scenario.UpdateBlastShields() RandomizerPowerup.IncrementInventoryIndex() RL.UpdateRDVClient(false) return granted @@ -279,6 +281,101 @@ function RandomizerPowerup._ApplyTunableChanges() end end +function RandomizerPowerup.UpdateWeapons() + RandomizerPowerup.UpdateBeams() + RandomizerPowerup.UpdateMissiles() +end + +function RandomizerPowerup.BeamsState() + return { + power = RandomizerPowerup.HasItem("ITEM_WEAPON_POWER_BEAM"), + wide = RandomizerPowerup.HasItem("ITEM_WEAPON_WIDE_BEAM"), + plasma = RandomizerPowerup.HasItem("ITEM_WEAPON_PLASMA_BEAM"), + wave = RandomizerPowerup.HasItem("ITEM_WEAPON_WAVE_BEAM"), + } +end + +function RandomizerPowerup.MissileState() + return { + missile = RandomizerPowerup.HasItem("ITEM_WEAPON_MISSILE_LAUNCHER"), + super = RandomizerPowerup.HasItem("ITEM_WEAPON_SUPER_MISSILE"), + ice = RandomizerPowerup.HasItem("ITEM_WEAPON_ICE_MISSILE"), + } +end + +function RandomizerPowerup.UpdateBeams() + RandomizerPowerup._UpdateBeams(RandomizerPowerup.BeamsState()) +end + +function RandomizerPowerup.UpdateMissiles() + RandomizerPowerup._UpdateMissiles(RandomizerPowerup.MissileState()) +end + +function RandomizerPowerup._UpdateBeams(beams) + if not beams.power then return nil end + + local offset = 60 + local plasma_damage = 50 + local wave_damage = 80 + if not beams.wide then + offset = 0 + plasma_damage = plasma_damage / 3 + wave_damage = wave_damage / 3 + end + if not beams.plasma then + wave_damage = wave_damage / 2 + end + Scenario.SetTunableValue("CTunableWideBeam", "fPerpendicularOffsetSize", offset) + Scenario.SetTunableValue("CTunablePlasmaBeam", "fDamageAmount", plasma_damage) + Scenario.SetTunableValue("CTunableWaveBeam", "fDamageAmount", wave_damage) + + if beams.wide and beams.plasma and beams.wave then + return "ITEM_WEAPON_WIDE_PLASMA_WAVE_BEAM" + end + if beams.plasma and beams.wave then + return "ITEM_WEAPON_PLASMA_WAVE_BEAM" + end + if beams.wide and beams.wave then + return "ITEM_WEAPON_WIDE_WAVE_BEAM" + end + if beams.wide and beams.plasma then + return "ITEM_WEAPON_WIDE_PLASMA_BEAM" + end + if beams.wave then + return "ITEM_WEAPON_SOLO_WAVE_BEAM" + end + if beams.plasma then + return "ITEM_WEAPON_SOLO_PLASMA_BEAM" + end + if beams.wide then + return "ITEM_WEAPON_SOLO_WIDE_BEAM" + end + return "ITEM_WEAPON_POWER_BEAM" +end + +function RandomizerPowerup._UpdateMissiles(missiles) + -- don't give any missiles without launcher + if not missiles.missile then return nil end + + local ice_damage = 400 + if not missiles.super then + ice_damage = ice_damage / 3 + end + Scenario.SetTunableValue("CTunableIceMissile", "fDamageAmount", ice_damage) + + if missiles.super and missiles.ice then + return "ITEM_WEAPON_SUPER_ICE_MISSILE" + end + if missiles.ice then + return "ITEM_WEAPON_SOLO_ICE_MISSILE" + end + if missiles.super then + return "ITEM_WEAPON_SOLO_SUPER_MISSILE" + end + + return "ITEM_WEAPON_MISSILE_LAUNCHER" +end + -- Main PBs RandomizerPowerBomb = {} setmetatable(RandomizerPowerBomb, {__index = RandomizerPowerup}) @@ -345,6 +442,11 @@ end RandomizerStormMissile = {} setmetatable(RandomizerStormMissile, {__index = RandomizerPowerup}) function RandomizerStormMissile.OnPickedUp(actor, progression) + progression = progression or {{{item_id = "ITEM_MULTILOCKON", quantity = 1}}} + if RandomizerPowerup.HasItem("ITEM_WEAPON_MISSILE_LAUNCHER") then + table.insert(progression[1], 1, {item_id = "ITEM_WEAPON_STORM_MISSILE", quantity = 1}) + end + RandomizerPowerup.ToggleInputsOnPickedUp( actor, progression, "ITEM_MULTILOCKON" ) @@ -363,3 +465,102 @@ function RandomizerEnergyPart.OnPickedUp(actor, progression) end end +local function pick_up_beam(beam, actor, progression) + progression = progression or {{{item_id = "ITEM_WEAPON_" .. beam:upper() .. "_BEAM", quantity = 1}}} + local beams = RandomizerPowerup.BeamsState() + if #progression == 1 then + beams[beam] = true + local to_grant = RandomizerPowerup._UpdateBeams(beams) + if to_grant ~= nil then + table.insert(progression[1], 1, {item_id = to_grant, quantity = 1}) + end + else + -- progressive beams + progression = { + { + {item_id = "ITEM_WEAPON_SOLO_WIDE_BEAM", quantity = 1}, + {item_id = "ITEM_WEAPON_WIDE_BEAM", quantity = 1,} + }, + { + {item_id = "ITEM_WEAPON_WIDE_PLASMA_BEAM", quantity = 1}, + {item_id = "ITEM_WEAPON_PLASMA_BEAM", quantity = 1}, + }, + { + {item_id = "ITEM_WEAPON_WIDE_PLASMA_WAVE_BEAM", quantity = 1}, + {item_id = "ITEM_WEAPON_WAVE_BEAM", quantity = 1}, + } + } + Game.AddSF(0.1, "RandomizerPowerup.UpdateBeams", "") + end + + return RandomizerPowerup.OnPickedUp(actor, progression) +end + +RandomizerPowerBeam = {} +setmetatable(RandomizerPowerBeam, {__index = RandomizerPowerup}) +function RandomizerPowerBeam.OnPickedUp(actor, progression) + return pick_up_beam("power", actor, progression) +end + +RandomizerWideBeam = {} +setmetatable(RandomizerWideBeam, {__index = RandomizerPowerup}) +function RandomizerWideBeam.OnPickedUp(actor, progression) + return pick_up_beam("wide", actor, progression) +end + +RandomizerPlasmaBeam = {} +setmetatable(RandomizerPlasmaBeam, {__index = RandomizerPowerup}) +function RandomizerPlasmaBeam.OnPickedUp(actor, progression) + return pick_up_beam("plasma", actor, progression) +end + +RandomizerWaveBeam = {} +setmetatable(RandomizerWaveBeam, {__index = RandomizerPowerup}) +function RandomizerWaveBeam.OnPickedUp(actor, progression) + return pick_up_beam("wave", actor, progression) +end + +local function pick_up_missile(id, item, actor, progression) + progression = progression or {{{item_id = item, quantity = 1}}} + local missiles = RandomizerPowerup.MissileState() + if #progression == 1 then + missiles[id] = true + local to_grant = RandomizerPowerup._UpdateMissiles(missiles) + if to_grant ~= nil then + table.insert(progression[1], 1, {item_id = to_grant, quantity = 1}) + end + else + -- progressive missiles + progression = { + { + {item_id = "ITEM_WEAPON_SOLO_SUPER_MISSILE", quantity = 1}, + {item_id = "ITEM_WEAPON_SUPER_MISSILE", quantity = 1,} + }, + { + {item_id = "ITEM_WEAPON_SUPER_ICE_MISSILE", quantity = 1}, + {item_id = "ITEM_WEAPON_ICE_MISSILE", quantity = 1}, + }, + } + Game.AddSF(0.1, "RandomizerPowerup.UpdateMissiles", "") + end + + return RandomizerPowerup.OnPickedUp(actor, progression) +end + +RandomizerMissileLauncher = {} +setmetatable(RandomizerMissileLauncher, {__index = RandomizerPowerup}) +function RandomizerMissileLauncher.OnPickedUp(actor, progression) + return pick_up_missile("missile", "ITEM_WEAPON_MISSILE_LAUNCHER", actor, progression) +end + +RandomizerSuperMissile = {} +setmetatable(RandomizerSuperMissile, {__index = RandomizerPowerup}) +function RandomizerSuperMissile.OnPickedUp(actor, progression) + return pick_up_missile("super", "ITEM_WEAPON_SUPER_MISSILE", actor, progression) +end + +RandomizerIceMissile = {} +setmetatable(RandomizerIceMissile, {__index = RandomizerPowerup}) +function RandomizerIceMissile.OnPickedUp(actor, progression) + return pick_up_missile("ice", "ITEM_WEAPON_ICE_MISSILE", actor, progression) +end diff --git a/src/open_dread_rando/pickups/lua_editor.py b/src/open_dread_rando/pickups/lua_editor.py index 887c9c050..8448e842d 100644 --- a/src/open_dread_rando/pickups/lua_editor.py +++ b/src/open_dread_rando/pickups/lua_editor.py @@ -21,6 +21,13 @@ def _read_level_lua(level_id: str) -> str: "ITEM_MULTILOCKON": "RandomizerStormMissile", "ITEM_LIFE_SHARDS": "RandomizerEnergyPart", "ITEM_GHOST_AURA": "RandomizerFlashShift", + "ITEM_WEAPON_POWER_BEAM": "RandomizerPowerBeam", + "ITEM_WEAPON_WIDE_BEAM": "RandomizerWideBeam", + "ITEM_WEAPON_PLASMA_BEAM": "RandomizerPlasmaBeam", + "ITEM_WEAPON_WAVE_BEAM": "RandomizerWaveBeam", + "ITEM_WEAPON_MISSILE_LAUNCHER": "RandomizerMissileLauncher", + "ITEM_WEAPON_SUPER_MISSILE": "RandomizerSuperMissile", + "ITEM_WEAPON_ICE_MISSILE": "RandomizerIceMissile", } diff --git a/src/open_dread_rando/pickups/pickup.py b/src/open_dread_rando/pickups/pickup.py index aee97d7e3..3ec21b972 100644 --- a/src/open_dread_rando/pickups/pickup.py +++ b/src/open_dread_rando/pickups/pickup.py @@ -113,8 +113,14 @@ def patch_multi_item_pickup(self, bmsad: dict) -> dict: pickable: dict = bmsad["property"]["components"]["PICKABLE"] script: dict = bmsad["property"]["components"]["SCRIPT"] + item_id = "ITEM_NONE" + first_progression = self.pickup["resources"][0][0]["item_id"] + if first_progression in {"ITEM_WEAPON_WIDE_BEAM", "ITEM_WEAPON_SUPER_MISSILE"}: + # the gun doesn't appear to be selected properly on pickup unless we do this + item_id = first_progression + set_custom_params: dict = pickable["functions"][0]["params"] - set_custom_params["Param1"]["value"] = "ITEM_NONE" + set_custom_params["Param1"]["value"] = item_id script["functions"][0]["params"]["Param2"]["value"] = self.lua_editor.get_script_class(self.pickup, actordef_name=bmsad[ diff --git a/src/open_dread_rando/pickups/split_pickups.py b/src/open_dread_rando/pickups/split_pickups.py new file mode 100644 index 000000000..daba5b070 --- /dev/null +++ b/src/open_dread_rando/pickups/split_pickups.py @@ -0,0 +1,472 @@ +import copy +import dataclasses +import itertools +from enum import Enum +from typing import Any, Generic, Optional, TypeVar + +from construct import Container +from mercury_engine_data_structures.formats.bmsad import Bmsad + +from open_dread_rando.patcher_editor import PatcherEditor + + +@dataclasses.dataclass +class Color3f: + r: float + g: float + b: float + + +class BeamInput(Enum): + NONE = 0 + ZR = 1 + L = 2 + + +class MissileInput(Enum): + NONE = 0 + L = 1 + + +class CCFunc: + def __init__(self, raw: dict) -> None: + self.raw = raw + + def _param(self, i: int) -> Container: + return self.raw["params"][f"Param{i}"] + + def get_param(self, i: int) -> Any: + return self._param(i).value + + def set_param(self, i: int, value: Any): + self._param(i).value = value + + +T = TypeVar('T') +class Param(Generic[T]): + def __init__(self, index: int) -> None: + self.index = index + + def __get__(self, inst: CCFunc, objtype=None) -> T: + return inst.get_param(self.index) + + def __set__(self, inst: CCFunc, value: T): + inst.set_param(self.index, value) + + +class AddPrimaryGun(CCFunc): + name = Param[str](1) + class_name = Param[str](2) + subactor_name = Param[str](3) + autofire_delay = Param[float](4) + inventory_item = Param[str](5) + burst_fx = Param[str](6) + chargeburst_fx = Param[str](7) + charge_fx = Param[str](8) + on_fire = Param[str](9) + charge_sfx = Param[str](10) + priority = Param[int](12) + + @property + def input_type(self) -> BeamInput: + return BeamInput(self.get_param(11)) + + @input_type.setter + def input_type(self, value: BeamInput): + self.set_param(11, value.value) + + @property + def color(self) -> Color3f: + return Color3f( + self.get_param(13), + self.get_param(14), + self.get_param(15), + ) + + @color.setter + def color(self, value: Color3f): + self.set_param(13, value.r) + self.set_param(14, value.g) + self.set_param(15, value.b) + + +@dataclasses.dataclass(frozen=True) +class BillBoardGroupParams: + texture: str + unk1: int + unk2: int + unk3: int + unk4: float + + +class SetBillBoardGroupParams(CCFunc): + name = Param[str](1) + + def _get_group(self, i: int) -> BillBoardGroupParams: + i *= 5 + return BillBoardGroupParams( + self.get_param(2 + i), + self.get_param(3 + i), + self.get_param(4 + i), + self.get_param(5 + i), + self.get_param(6 + i), + ) + + def _set_group(self, i: int, value: BillBoardGroupParams): + i *= 5 + self.set_param(2 + i, value.texture) + self.set_param(3 + i, value.unk1) + self.set_param(4 + i, value.unk2) + self.set_param(5 + i, value.unk3) + self.set_param(6 + i, value.unk4) + + @property + def beam(self) -> BillBoardGroupParams: + return self._get_group(0) + + @beam.setter + def beam(self, value: BillBoardGroupParams): + self._set_group(0, value) + + @property + def charge(self) -> BillBoardGroupParams: + return self._get_group(1) + + @charge.setter + def charge(self, value: BillBoardGroupParams): + self._set_group(1, value) + + @property + def boost(self) -> BillBoardGroupParams: + return self._get_group(2) + + @boost.setter + def boost(self, value: BillBoardGroupParams): + self._set_group(2, value) + + +class SetGunChargeParams(CCFunc): + name = Param[str](1) + + +@dataclasses.dataclass +class PrimaryGunFuncs: + add_primary_gun: AddPrimaryGun + set_billboard_group_params: Optional[SetBillBoardGroupParams] + set_gun_charge_params: Optional[SetGunChargeParams] + + @classmethod + def from_raw(cls, + add_primary_gun: dict, + set_billboard_group_params: Optional[dict] = None, + set_gun_charge_params: Optional[dict] = None + ): + add_primary_gun = AddPrimaryGun(add_primary_gun) + if set_billboard_group_params is not None: + set_billboard_group_params = SetBillBoardGroupParams(set_billboard_group_params) + if set_gun_charge_params is not None: + set_gun_charge_params = SetGunChargeParams(set_gun_charge_params) + return cls( + add_primary_gun, + set_billboard_group_params, + set_gun_charge_params + ) + + @property + def as_list(self) -> list[dict]: + ret = [self.add_primary_gun.raw] + if self.set_billboard_group_params is not None: + ret.append(self.set_billboard_group_params.raw) + if self.set_gun_charge_params is not None: + ret.append(self.set_gun_charge_params.raw) + return ret + + @property + def name(self) -> str: + return self.add_primary_gun.params.name + + @name.setter + def name(self, value: str): + self.add_primary_gun.name = value + if self.set_billboard_group_params is not None: + self.set_billboard_group_params.name = value + if self.set_gun_charge_params is not None: + self.set_gun_charge_params.name = value + + +class AddSecondaryGun(CCFunc): + name = Param[str](1) + class_name = Param[str](2) + subactor_name = Param[str](3) + fire_delay = Param[float](4) + + main_inventory_item = Param[str](5) + ammo_inventory_item = Param[str](6) + capacity_inventory_item = Param[str](11) + + burst_fx = Param[str](7) + on_fire = Param[str](8) + priority = Param[str](10) + + @property + def input_type(self) -> MissileInput: + return MissileInput(self.get_param(9)) + + @input_type.setter + def input_type(self, value: MissileInput): + self.set_param(9, value.value) + + +def select_gun(param1: str, param2: bool) -> dict: + return { + "name": "SelectGun", + "unk": 1, + "params": { + "Param1": { + "type": "s", + "value": param1, + }, + "Param2": { + "type": "b", + "value": param2, + }, + }, + } + + +SAMUS_BMSAD_PATH = "actors/characters/samus/charclasses/samus.bmsad" + +# ruff: noqa: C901 +def update_starting_inventory_split_pickups(inventory: dict) -> dict: + inventory = copy.copy(inventory) + + power = "ITEM_WEAPON_POWER_BEAM" in inventory + wide = "ITEM_WEAPON_WIDE_BEAM" in inventory + plasma = "ITEM_WEAPON_PLASMA_BEAM" in inventory + wave = "ITEM_WEAPON_WAVE_BEAM" in inventory + + if power: + if wide: + inventory["ITEM_WEAPON_SOLO_WIDE_BEAM"] = 1 + if plasma: + inventory["ITEM_WEAPON_SOLO_PLASMA_BEAM"] = 1 + if wave: + inventory["ITEM_WEAPON_SOLO_WAVE_BEAM"] = 1 + if wide and plasma: + inventory["ITEM_WEAPON_WIDE_PLASMA_BEAM"] = 1 + if wide and wave: + inventory["ITEM_WEAPON_WIDE_WAVE_BEAM"] = 1 + if plasma and wave: + inventory["ITEM_WEAPON_PLASMA_WAVE_BEAM"] = 1 + if wide and plasma and wave: + inventory["ITEM_WEAPON_WIDE_PLASMA_WAVE_BEAM"] = 1 + + missile = "ITEM_WEAPON_MISSILE_LAUNCHER" in inventory + supers = "ITEM_WEAPON_SUPER_MISSILE" in inventory + ice = "ITEM_WEAPON_ICE_MISSILE" in inventory + storm = "ITEM_MULTILOCKON" in inventory + + if missile: + if supers: + inventory["ITEM_WEAPON_SOLO_SUPER_MISSILE"] = 1 + if ice: + inventory["ITEM_WEAPON_SOLO_ICE_MISSILE"] = 1 + if supers and ice: + inventory["ITEM_WEAPON_SUPER_ICE_MISSILE"] = 1 + if storm: + inventory["ITEM_WEAPON_STORM_MISSILE"] = 1 + + return inventory + + + +def patch_split_pickups(editor: PatcherEditor): + samus = editor.get_file(SAMUS_BMSAD_PATH, Bmsad) + + primaries = _patch_split_beams(editor) + secondaries = _patch_split_missiles(editor) + selections = [ + select_gun("PowerBeam", True), + select_gun("Missile", False), + ] + + samus.raw.property.components["GUN"].functions = primaries + secondaries + selections + + _patch_blast_shields(editor) + + +def _patch_split_beams(editor: PatcherEditor) -> list[dict]: + samus = editor.get_file(SAMUS_BMSAD_PATH, Bmsad) + gun_funcs = samus.raw.property.components["GUN"].functions + + power = PrimaryGunFuncs.from_raw( + gun_funcs[0], + gun_funcs[1], + gun_funcs[2], + ) + wide = PrimaryGunFuncs.from_raw( + gun_funcs[3], + gun_funcs[4], + gun_funcs[5], + ) + plasma = PrimaryGunFuncs.from_raw( + gun_funcs[6], + gun_funcs[7], + gun_funcs[8], + ) + wave = PrimaryGunFuncs.from_raw( + gun_funcs[9], + gun_funcs[10], + gun_funcs[11], + ) + hyper = PrimaryGunFuncs.from_raw( + gun_funcs[12], + gun_funcs[13], + ) + grapple = PrimaryGunFuncs.from_raw( + gun_funcs[14], + ) + spbgun = PrimaryGunFuncs.from_raw( + gun_funcs[15], + ) + + power.add_primary_gun.inventory_item = "ITEM_WEAPON_POWER_BEAM" + power.add_primary_gun.priority = 0 + + solo_wide = copy.deepcopy(wide) + solo_wide.name = "SoloWideBeam" + solo_wide.add_primary_gun.inventory_item = "ITEM_WEAPON_SOLO_WIDE_BEAM" + solo_wide.add_primary_gun.priority = 1 + + solo_plasma = copy.deepcopy(plasma) + solo_plasma.name = "SoloPlasmaBeam" + + solo_plasma.add_primary_gun.inventory_item = "ITEM_WEAPON_SOLO_PLASMA_BEAM" + solo_plasma.add_primary_gun.priority = 2 + solo_plasma.add_primary_gun.on_fire = "OnPowerBeamFire" + solo_plasma.add_primary_gun.charge_sfx = "weapons/chargedloop_beam.wav" + + solo_plasma.set_billboard_group_params.beam = power.set_billboard_group_params.beam + solo_plasma.set_billboard_group_params.charge = power.set_billboard_group_params.charge + solo_plasma.set_billboard_group_params.boost = power.set_billboard_group_params.boost + + solo_wave = copy.deepcopy(wave) + solo_wave.name = "SoloWaveBeam" + solo_wave.add_primary_gun.inventory_item = "ITEM_WEAPON_SOLO_WAVE_BEAM" + solo_wave.add_primary_gun.priority = 3 + solo_wave.add_primary_gun.on_fire = "OnPowerBeamFire" + solo_wave.add_primary_gun.charge_sfx = "weapons/chargedloop_beam.wav" + solo_wave.add_primary_gun.color = Color3f(0.8, 0.0, 70.0) + solo_wave.set_billboard_group_params.beam = power.set_billboard_group_params.beam + solo_wave.set_billboard_group_params.charge = power.set_billboard_group_params.charge + solo_wave.set_billboard_group_params.boost = power.set_billboard_group_params.boost + + wide_plasma = copy.deepcopy(plasma) + wide_plasma.name = "WidePlasmaBeam" + wide_plasma.add_primary_gun.inventory_item = "ITEM_WEAPON_WIDE_PLASMA_BEAM" + wide_plasma.add_primary_gun.priority = 4 + + wide_wave = copy.deepcopy(wave) + wide_wave.name = "WideWaveBeam" + wide_wave.add_primary_gun.inventory_item = "ITEM_WEAPON_WIDE_WAVE_BEAM" + wide_wave.add_primary_gun.priority = 5 + wide_wave.add_primary_gun.color = Color3f(0.8, 0.0, 70.0) + + plasma_wave = copy.deepcopy(wave) + plasma_wave.name = "PlasmaWaveBeam" + plasma_wave.add_primary_gun.inventory_item = "ITEM_WEAPON_PLASMA_WAVE_BEAM" + plasma_wave.add_primary_gun.priority = 6 + plasma_wave.add_primary_gun.on_fire = "OnPowerBeamFire" + plasma_wave.add_primary_gun.charge_sfx = "weapons/chargedloop_beam.wav" + plasma_wave.set_billboard_group_params.beam = power.set_billboard_group_params.beam + plasma_wave.set_billboard_group_params.charge = power.set_billboard_group_params.charge + plasma_wave.set_billboard_group_params.boost = power.set_billboard_group_params.boost + + full = copy.deepcopy(wave) + full.name = "AllBeams" + full.add_primary_gun.inventory_item = "ITEM_WEAPON_WIDE_PLASMA_WAVE_BEAM" + full.add_primary_gun.priority = 7 + + hyper.add_primary_gun.priority = 8 + + return list(itertools.chain.from_iterable(gun.as_list for gun in [ + power, + solo_wide, + solo_plasma, + solo_wave, + wide_plasma, + wide_wave, + plasma_wave, + full, + hyper, + grapple, + spbgun, + ])) + + +def _patch_split_missiles(editor: PatcherEditor) -> list[dict]: + samus = editor.get_file(SAMUS_BMSAD_PATH, Bmsad) + gun = samus.raw.property.components["GUN"] + + ice_bmsad = editor.get_file("actors/weapons/icemissile/charclasses/icemissile.bmsad", Bmsad) + solo_ice_bmsad = copy.deepcopy(ice_bmsad) + solo_ice_bmsad.raw.name = "soloicemissile" + movement = solo_ice_bmsad.raw.property.components["MOVEMENT"].fields.fields + movement.fInitialSpeed = 1250 + movement.fTimeInInitialSpeed = 0.2 + movement.fTimeToReachSpeed = 0.6 + editor.add_new_asset( + "actors/weapons/soloicemissile/charclasses/soloicemissile.bmsad", + solo_ice_bmsad, + editor.find_pkgs("actors/weapons/icemissile/charclasses/icemissile.bmsad"), + ) + + samus.raw.property.sub_actors.append("soloicemissile") + + missile = AddSecondaryGun(gun.functions[16]) + supers = AddSecondaryGun(gun.functions[17]) + ice = AddSecondaryGun(gun.functions[18]) + lockon = AddSecondaryGun(gun.functions[19]) + omega = AddSecondaryGun(gun.functions[20]) + + missile.main_inventory_item = "ITEM_WEAPON_MISSILE_LAUNCHER" + missile.priority = 0 + + solo_super = copy.deepcopy(supers) + solo_super.name = "SoloSuperMissile" + solo_super.main_inventory_item = "ITEM_WEAPON_SOLO_SUPER_MISSILE" + solo_super.priority = 1 + + solo_ice = copy.deepcopy(ice) + solo_ice.name = "SoloIceMissile" + solo_ice.main_inventory_item = "ITEM_WEAPON_SOLO_ICE_MISSILE" + solo_ice.subactor_name = "soloicemissile" + solo_ice.fire_delay = 0.27 + solo_ice.priority = 2 + + super_ice = copy.deepcopy(ice) + super_ice.name = "SuperIceMissile" + super_ice.main_inventory_item = "ITEM_WEAPON_SUPER_ICE_MISSILE" + super_ice.priority = 3 + + lockon.main_inventory_item = "ITEM_WEAPON_STORM_MISSILE" + + return [gun.raw for gun in [ + missile, + solo_super, + solo_ice, + super_ice, + lockon, + omega, + ]] + + +def _patch_blast_shields(editor: PatcherEditor): + supers = editor.get_file("actors/props/doorshieldsupermissile/charclasses/doorshieldsupermissile.bmsad", Bmsad) + super_life = supers.raw.property.components["LIFE"] + super_life.functions.pop(1) # AddDamageSource("SUPER_MISSILE") + super_life.functions.pop(1) # AddDamageSource("ICE_MISSILE") + + plasma = editor.get_file("actors/props/door_shield_plasma/charclasses/door_shield_plasma.bmsad", Bmsad) + plasma_life = plasma.raw.property.components["LIFE"] + plasma_life.functions.pop(1) # AddDamageSource("PLASMA_BEAM")