Skip to content

Commit

Permalink
Merge pull request Ziktofel#323 from Salzkorn/cmo-recursion
Browse files Browse the repository at this point in the history
Fix entry rules in client sometimes not resolving correctly due to recursion error
  • Loading branch information
Ziktofel authored Oct 11, 2024
2 parents 2122910 + 4be7d07 commit ff95fc6
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 26 deletions.
6 changes: 3 additions & 3 deletions worlds/sc2/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1426,16 +1426,16 @@ def calc_available_nodes(ctx: SC2Context) -> typing.Tuple[typing.List[int], typi
accessible_rules: typing.Set[int] = set()
for campaign_idx, campaign in enumerate(ctx.custom_mission_order):
available_layouts[campaign_idx] = []
if campaign.entry_rule.is_accessible(beaten_missions, received_items, ctx.mission_id_to_entry_rules, accessible_rules, set()):
if campaign.entry_rule.is_accessible(beaten_missions, received_items, ctx.mission_id_to_entry_rules, accessible_rules, []):
available_campaigns.append(campaign_idx)
for layout_idx, layout in enumerate(campaign.layouts):
if layout.entry_rule.is_accessible(beaten_missions, received_items, ctx.mission_id_to_entry_rules, accessible_rules, set()):
if layout.entry_rule.is_accessible(beaten_missions, received_items, ctx.mission_id_to_entry_rules, accessible_rules, []):
available_layouts[campaign_idx].append(layout_idx)
for column in layout.missions:
for mission in column:
if mission.mission_id == -1:
continue
if mission.entry_rule.is_accessible(beaten_missions, received_items, ctx.mission_id_to_entry_rules, accessible_rules, set()):
if mission.entry_rule.is_accessible(beaten_missions, received_items, ctx.mission_id_to_entry_rules, accessible_rules, []):
available_missions.append(mission.mission_id)

return available_missions, available_layouts, available_campaigns
Expand Down
55 changes: 32 additions & 23 deletions worlds/sc2/mission_order/entry_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def shows_single_rule(self) -> bool:
def is_accessible(
self, beaten_missions: Set[int], received_items: Dict[int, int],
mission_id_to_entry_rules: Dict[int, MissionEntryRules],
accessible_rules: Set[int], seen_rules: Set[int]
accessible_rules: Set[int], seen_rules: List[int]
) -> bool:
return False

Expand Down Expand Up @@ -121,7 +121,7 @@ def shows_single_rule(self) -> bool:
def is_accessible(
self, beaten_missions: Set[int], received_items: Dict[int, int],
mission_id_to_entry_rules: Dict[int, MissionEntryRules],
accessible_rules: Set[int], seen_rules: Set[int]
accessible_rules: Set[int], seen_rules: List[int]
) -> bool:
# Beat rules are accessible if all their missions are beaten and accessible
if not beaten_missions.issuperset(self.mission_ids):
Expand Down Expand Up @@ -182,7 +182,9 @@ def tooltip(self, indents: int, missions: Dict[int, SC2Mission]) -> str:
req = self.visual_reqs[0]
req_str = missions[req].mission_name if type(req) == int else req
if self.amount == 1:
return f"Beat {req_str}"
if type(req) == int:
return f"Beat {req_str}"
return f"Beat any mission from {req_str}"
return f"Beat {amount} missions from {req_str}"
if self.amount == 1:
tooltip = f"Beat any mission from:\n{indent}- "
Expand All @@ -198,18 +200,16 @@ def shows_single_rule(self) -> bool:
def is_accessible(
self, beaten_missions: Set[int], received_items: Dict[int, int],
mission_id_to_entry_rules: Dict[int, MissionEntryRules],
accessible_rules: Set[int], seen_rules: Set[int]
accessible_rules: Set[int], seen_rules: List[int]
) -> bool:
# Count rules are accessible if enough of their missions are beaten and accessible
# return self.amount <= sum(
# all(
# rule.is_accessible(beaten_missions, received_items, mission_id_to_entry_rules, accessible_rules, seen_rules)
# for rule in mission_id_to_entry_rules[mission_id]
# )
# for mission_id in beaten_missions.intersection(self.mission_ids)
# )
# Temp hotfix as the missions that have this rule themselves aren't counted in the client against this rule
return self.amount <= len(beaten_missions.intersection(self.mission_ids))
return self.amount <= sum(
all(
rule.is_accessible(beaten_missions, received_items, mission_id_to_entry_rules, accessible_rules, seen_rules)
for rule in mission_id_to_entry_rules[mission_id]
)
for mission_id in beaten_missions.intersection(self.mission_ids)
)

class SubRuleEntryRule(EntryRule):
rule_id: int
Expand Down Expand Up @@ -281,11 +281,16 @@ def parse_from_dict(data: Dict[str, Any]) -> SubRuleRuleData:
**{field: value for field, value in rule_data.items()}
)
sub_rules.append(rule)
return SubRuleRuleData(
rule = SubRuleRuleData(
rule_id,
sub_rules,
amount
)
# Add an accessibility buffer for top level rules
# This is an optimization to make recalculations of large mission orders feel smoother
if rule.rule_id >= 0:
rule.buffer_accessible = False
return rule

@staticmethod
def empty() -> SubRuleRuleData:
Expand All @@ -311,26 +316,30 @@ def shows_single_rule(self) -> bool:
def is_accessible(
self, beaten_missions: Set[int], received_items: Dict[int, int],
mission_id_to_entry_rules: Dict[int, MissionEntryRules],
accessible_rules: Set[int], seen_rules: Set[int]
accessible_rules: Set[int], seen_rules: List[int]
) -> bool:
# Early exit check for top-level entry rules
if self.rule_id >= 0:
if self.rule_id in accessible_rules:
if self.buffer_accessible or self.rule_id in accessible_rules:
return True
# Never consider rules discovered via recursion to be accessible
# (unless they succeeded, in which case they will be in accessible_rules)
if self.rule_id in seen_rules:
return False
seen_rules.add(self.rule_id)
seen_rules.append(self.rule_id)
# Sub-rule rules are accessible if enough of their child rules are accessible
if self.amount <= sum(
success = self.amount <= sum(
rule.is_accessible(beaten_missions, received_items, mission_id_to_entry_rules, accessible_rules, seen_rules)
for rule in self.sub_rules
):
if self.rule_id >= 0:
)
if self.rule_id >= 0:
if success:
accessible_rules.add(self.rule_id)
return True
return False
self.buffer_accessible = True
# Rules that failed in the middle of calculating another rule
# should be allowed to try again at a later point
seen_rules.remove(self.rule_id)
return success

class ItemEntryRule(EntryRule):
items_to_check: Dict[str, int]
Expand Down Expand Up @@ -377,7 +386,7 @@ def shows_single_rule(self) -> bool:
def is_accessible(
self, beaten_missions: Set[int], received_items: Dict[int, int],
mission_id_to_entry_rules: Dict[int, MissionEntryRules],
accessible_rules: Set[int], seen_rules: Set[int]
accessible_rules: Set[int], seen_rules: List[int]
) -> bool:
return all(
item in received_items and received_items[item] >= amount
Expand Down

0 comments on commit ff95fc6

Please sign in to comment.