diff --git a/worlds/stardew_valley/logic.py b/worlds/stardew_valley/logic.py index 46580d549ac6..00b60696a91f 100644 --- a/worlds/stardew_valley/logic.py +++ b/worlds/stardew_valley/logic.py @@ -1043,11 +1043,12 @@ def can_reproduce(self, number_children: int = 1) -> StardewRule: def has_relationship(self, npc: str, hearts: int = 1) -> StardewRule: if hearts <= 0: return True_() - if self.options[options.Friendsanity] == options.Friendsanity.option_none: + friendsanity = self.options[options.Friendsanity] + if friendsanity == options.Friendsanity.option_none: return self.can_earn_relationship(npc, hearts) if npc not in all_villagers_by_name: if npc == NPC.pet: - if self.options[options.Friendsanity] == options.Friendsanity.option_bachelors: + if friendsanity == options.Friendsanity.option_bachelors: return self.can_befriend_pet(hearts) return self.received_hearts(NPC.pet, hearts) if npc == Generic.any or npc == Generic.bachelor: @@ -1077,12 +1078,12 @@ def has_relationship(self, npc: str, hearts: int = 1) -> StardewRule: if not self.npc_is_in_current_slot(npc): return True_() villager = all_villagers_by_name[npc] - if self.options[options.Friendsanity] == options.Friendsanity.option_bachelors and not villager.bachelor: + if friendsanity == options.Friendsanity.option_bachelors and not villager.bachelor: return self.can_earn_relationship(npc, hearts) - if self.options[options.Friendsanity] == options.Friendsanity.option_starting_npcs and not villager.available: + if friendsanity == options.Friendsanity.option_starting_npcs and not villager.available: return self.can_earn_relationship(npc, hearts) - if self.options[ - options.Friendsanity] != options.Friendsanity.option_all_with_marriage and villager.bachelor and hearts > 8: + is_capped_at_8 = villager.bachelor and friendsanity != options.Friendsanity.option_all_with_marriage + if is_capped_at_8 and hearts > 8: return self.received_hearts(villager, 8) & self.can_earn_relationship(npc, hearts) return self.received_hearts(villager, hearts) @@ -1136,11 +1137,22 @@ def can_earn_relationship(self, npc: str, hearts: int = 0) -> StardewRule: rule_if_birthday = self.has_season(villager.birthday) & self.has_any_universal_love() & self.has_lived_months(hearts // 2) rule_if_not_birthday = self.has_lived_months(hearts) earn_rule = self.can_meet(npc) & (rule_if_birthday | rule_if_not_birthday) + if villager.bachelor: + if hearts > 8: + earn_rule = earn_rule & self.can_date(npc) + if hearts > 10: + earn_rule = earn_rule & self.can_marry(npc) else: earn_rule = self.has_lived_months(min(hearts // 2, 8)) return previous_heart_rule & earn_rule + def can_date(self, npc: str) -> StardewRule: + return self.has_relationship(npc, 8) & self.has(Gift.bouquet) + + def can_marry(self, npc: str) -> StardewRule: + return self.has_relationship(npc, 10) & self.has(Gift.mermaid_pendant) + def can_befriend_pet(self, hearts: int): if hearts <= 0: return True_() diff --git a/worlds/stardew_valley/test/TestRules.py b/worlds/stardew_valley/test/TestRules.py index 8556dac1d89a..0847d8a63b95 100644 --- a/worlds/stardew_valley/test/TestRules.py +++ b/worlds/stardew_valley/test/TestRules.py @@ -444,3 +444,63 @@ def collect_all_except(multiworld, item_to_not_collect: str): for item in multiworld.get_items(): if item.name != item_to_not_collect: multiworld.state.collect(item) + + +class TestFriendsanityDatingRules(SVTestBase): + options = { + options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized_not_winter, + options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage, + options.FriendsanityHeartSize.internal_name: 3 + } + + def test_earning_dating_heart_requires_dating(self): + month_name = "Month End" + for i in range(12): + month_item = self.world.create_item(month_name) + self.multiworld.state.collect(month_item, event=True) + self.multiworld.state.collect(self.world.create_item("Beach Bridge"), event=False) + self.multiworld.state.collect(self.world.create_item("Progressive House"), event=False) + self.multiworld.state.collect(self.world.create_item("Adventurer's Guild"), event=False) + self.multiworld.state.collect(self.world.create_item("Galaxy Hammer"), event=False) + for i in range(3): + self.multiworld.state.collect(self.world.create_item("Progressive Pickaxe"), event=False) + self.multiworld.state.collect(self.world.create_item("Progressive Axe"), event=False) + self.multiworld.state.collect(self.world.create_item("Progressive Barn"), event=False) + for i in range(10): + self.multiworld.state.collect(self.world.create_item("Foraging Level"), event=False) + self.multiworld.state.collect(self.world.create_item("Farming Level"), event=False) + self.multiworld.state.collect(self.world.create_item("Mining Level"), event=False) + self.multiworld.state.collect(self.world.create_item("Combat Level"), event=False) + self.multiworld.state.collect(self.world.create_item("Progressive Mine Elevator"), event=False) + self.multiworld.state.collect(self.world.create_item("Progressive Mine Elevator"), event=False) + + npc = "Abigail" + heart_name = f"{npc} <3" + step = 3 + + self.assert_can_reach_heart_up_to(npc, 3, step) + self.multiworld.state.collect(self.world.create_item(heart_name), event=False) + self.assert_can_reach_heart_up_to(npc, 6, step) + self.multiworld.state.collect(self.world.create_item(heart_name), event=False) + self.assert_can_reach_heart_up_to(npc, 8, step) + self.multiworld.state.collect(self.world.create_item(heart_name), event=False) + self.assert_can_reach_heart_up_to(npc, 10, step) + self.multiworld.state.collect(self.world.create_item(heart_name), event=False) + self.assert_can_reach_heart_up_to(npc, 14, step) + + def assert_can_reach_heart_up_to(self, npc: str, max_reachable: int, step: int): + prefix = "Friendsanity: " + suffix = " <3" + for i in range(1, max_reachable + 1): + if i % step != 0 and i != 14: + continue + location = f"{prefix}{npc} {i}{suffix}" + can_reach = self.world.logic.can_reach_location(location)(self.multiworld.state) + self.assertTrue(can_reach, f"Should be able to earn relationship up to {i} hearts") + for i in range(max_reachable + 1, 14 + 1): + if i % step != 0 and i != 14: + continue + location = f"{prefix}{npc} {i}{suffix}" + can_reach = self.world.logic.can_reach_location(location)(self.multiworld.state) + self.assertFalse(can_reach, f"Should not be able to earn relationship up to {i} hearts") +