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 death link #3

Merged
merged 3 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions MetroidPrimeClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,20 @@ class MetroidPrimeCommandProcessor(ClientCommandProcessor):
def __init__(self, ctx: CommonContext):
super().__init__(ctx)

def _cmd_deathlink(self):
"""Toggle deathlink from client. Overrides default setting."""
if isinstance(self.ctx, MetroidPrimeContext):
new_value = True
if (self.tags["DeathLink"]):
new_value = False
Utils.async_start(self.ctx.update_death_link(
new_value), name="Update Deathlink")


class MetroidPrimeContext(CommonContext):
current_level_id = 0
previous_level_id = 0
is_pending_death_link_reset = False
command_processor = MetroidPrimeCommandProcessor
game_interface: MetroidPrimeInterface
game = "Metroid Prime"
Expand All @@ -30,14 +40,20 @@ def __init__(self, server_address, password):

def on_deathlink(self, data: Utils.Dict[str, Utils.Any]) -> None:
super().on_deathlink(data)
logger.debug("Death link not implemented")
self.game_interface.set_alive(False)

async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super(MetroidPrimeContext, self).server_auth(password_requested)
await self.get_username()
await self.send_connect()

def on_package(self, cmd: str, args: dict):
if cmd == "Connected":
if "death_link" in args["slot_data"]:
Utils.async_start(self.update_death_link(
bool(args["slot_data"]["death_link"])))


async def dolphin_sync_task(ctx: MetroidPrimeContext):
logger.info("Starting Dolphin connector")
Expand Down Expand Up @@ -158,6 +174,15 @@ async def handle_check_goal_complete(ctx: MetroidPrimeContext):
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])


async def handle_check_deathlink(ctx: MetroidPrimeContext):
health = ctx.game_interface.get_current_health()
if health <= 0 and ctx.is_pending_death_link_reset == False:
await ctx.send_death(ctx.player_names[ctx.slot] + " ran out of energy.")
ctx.is_pending_death_link_reset
elif health > 0 and ctx.is_pending_death_link_reset == True:
ctx.is_pending_death_link_reset = False


async def _handle_game_ready(ctx: MetroidPrimeContext):
if ctx.server:
if not ctx.slot:
Expand All @@ -169,7 +194,7 @@ async def _handle_game_ready(ctx: MetroidPrimeContext):
await handle_check_goal_complete(ctx)

if "DeathLink" in ctx.tags:
logger.debug("DeathLink not implemented")
await handle_check_deathlink(ctx)
await asyncio.sleep(0.5)
else:
logger.info("Waiting for player to connect to server")
Expand Down
4 changes: 2 additions & 2 deletions worlds/metroidprime/DolphinClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def read_pointer(self, pointer, offset, byte_count):
try:
address = self.dolphin.follow_pointers(pointer, [0])
except RuntimeError:
self.logger.error(f"Could not read pointer at {pointer:x}")
self.logger.debug(f"Could not read pointer at {pointer:x}")
return None

if not self.dolphin.is_hooked():
Expand All @@ -80,7 +80,7 @@ def write_pointer(self, pointer, offset, data):
try:
address = self.dolphin.follow_pointers(pointer, [0])
except RuntimeError:
self.logger.error(f"Could not read pointer at {pointer:x}")
self.logger.debug(f"Could not read pointer at {pointer:x}")
return None

if not self.dolphin.is_hooked():
Expand Down
2 changes: 2 additions & 0 deletions worlds/metroidprime/MetroidPrimeInterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ def __is_player_table_ready(self) -> bool:
"""Check if the player table is ready to be read from memory, indicating the game is in a playable state"""
player_table_bytes = self.dolphin_client.read_pointer(
cstate_manager_global + 0x84C, 0, 4)
if(player_table_bytes is None):
return False
player_table = struct.unpack(">I", player_table_bytes)[0]
if player_table == cplayer_vtable:
return True
Expand Down
3 changes: 2 additions & 1 deletion worlds/metroidprime/PrimeOptions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

from Options import Toggle, Range, ItemDict, StartInventoryPool, Choice, PerGameCommonOptions
from Options import DeathLink, Toggle, Range, ItemDict, StartInventoryPool, Choice, PerGameCommonOptions
from dataclasses import dataclass


Expand Down Expand Up @@ -42,4 +42,5 @@ class MetroidPrimeOptions(PerGameCommonOptions):
required_artifacts: RequiredArtifacts
exclude_items: ExcludeItems
final_bosses: FinalBosses
death_link: DeathLink

25 changes: 21 additions & 4 deletions worlds/metroidprime/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from typing import Any, Dict, List
from BaseClasses import Item, Tutorial, ItemClassification
from .Items import MetroidPrimeItem, suit_upgrade_table, artifact_table, item_table, custom_suit_upgrade_table
from .PrimeOptions import MetroidPrimeOptions
Expand Down Expand Up @@ -66,22 +67,26 @@ def create_items(self) -> None:
continue
elif i == "Missile Expansion":
for j in range(0, 8):
self.multiworld.itempool += [self.create_item('Missile Expansion', True)]
self.multiworld.itempool += [
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatter automatically changed these 🤷Let me know if style-wise you want me to configure it so it goes back to how it was. I'm just running the default PEP8 one.

self.create_item('Missile Expansion', True)]
items_added += 8
elif i == "Spring Ball":
continue
elif i == "Energy Tank":
for j in range(0, 8):
self.multiworld.itempool += [self.create_item("Energy Tank", True)]
self.multiworld.itempool += [
self.create_item("Energy Tank", True)]
for j in range(0, 6):
self.multiworld.itempool += [self.create_item("Energy Tank")]
self.multiworld.itempool += [
self.create_item("Energy Tank")]
items_added += 14
continue
elif i == "Ice Trap":
continue
elif i == "Power Bomb Expansion":
for j in range(0, 4):
self.multiworld.itempool += [self.create_item("Power Bomb Expansion")]
self.multiworld.itempool += [
self.create_item("Power Bomb Expansion")]
items_added += 4
else:
self.multiworld.itempool += [self.create_item(i)]
Expand All @@ -95,3 +100,15 @@ def set_rules(self) -> None:
set_rules(self.multiworld, self.player, every_location)
self.multiworld.completion_condition[self.player] = lambda state: (
state.can_reach("Mission Complete", "Region", self.player))

def fill_slot_data(self) -> Dict[str, Any]:

slot_data: Dict[str, Any] = {
"spring_ball": self.options.spring_ball.value,
"death_link": self.options.death_link.value,
"required_artifacts": self.options.required_artifacts.value,
"exclude_items": self.options.exclude_items.value,
"final_bosses": self.options.final_bosses.value,
}

return slot_data
Loading