Stardew Valley: Fix a logic bug and a documentation typo (#1722)

Typo in the documentation
Logic error for help wanted quests
This commit is contained in:
agilbert1412 2023-04-16 05:22:33 -04:00 committed by GitHub
parent 50d9ab041a
commit ea03c90152
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 103 additions and 75 deletions

View File

@ -26,6 +26,16 @@ class BundleItem:
def as_quality(self, quality: int): def as_quality(self, quality: int):
return BundleItem.item_bundle(self.item.name, self.item.item_id, self.amount, quality) return BundleItem.item_bundle(self.item.name, self.item.item_id, self.amount, quality)
def as_gold_quality(self):
return self.as_quality(2)
def as_quality_crop(self):
amount = 5
difficult_crops = ["Sweet Gem Berry", "Ancient Fruit"]
if self.item.name in difficult_crops:
amount = 1
return self.as_gold_quality().as_amount(amount)
def __repr__(self): def __repr__(self):
return f"{self.amount} {quality_dict[self.quality]} {self.item.name}" return f"{self.amount} {quality_dict[self.quality]} {self.item.name}"
@ -281,7 +291,7 @@ summer_crops_items = [blueberry, corn, hops, hot_pepper, melon, poppy,
fall_crops_items = [corn, sunflower, wheat, amaranth, bok_choy, cranberries, fall_crops_items = [corn, sunflower, wheat, amaranth, bok_choy, cranberries,
eggplant, fairy_rose, grape, pumpkin, yam, sweet_gem_berry] eggplant, fairy_rose, grape, pumpkin, yam, sweet_gem_berry]
all_crops_items = sorted({*spring_crop_items, *summer_crops_items, *fall_crops_items}) all_crops_items = sorted({*spring_crop_items, *summer_crops_items, *fall_crops_items})
quality_crops_items = [item.as_quality(2).as_amount(5) for item in all_crops_items] quality_crops_items = [item.as_quality_crop() for item in all_crops_items]
# TODO void_egg, dinosaur_egg, ostrich_egg, golden_egg # TODO void_egg, dinosaur_egg, ostrich_egg, golden_egg
animal_product_items = [egg, large_egg, brown_egg, large_brown_egg, wool, milk, large_milk, animal_product_items = [egg, large_egg, brown_egg, large_brown_egg, wool, milk, large_milk,
goat_milk, large_goat_milk, truffle, duck_feather, duck_egg, rabbit_foot] goat_milk, large_goat_milk, truffle, duck_feather, duck_egg, rabbit_foot]

View File

@ -4,7 +4,7 @@
- Stardew Valley on PC (Recommended: [Steam version](https://store.steampowered.com/app/413150/Stardew_Valley/)) - Stardew Valley on PC (Recommended: [Steam version](https://store.steampowered.com/app/413150/Stardew_Valley/))
- SMAPI ([Mod loader for Stardew Valley](https://smapi.io/)) - SMAPI ([Mod loader for Stardew Valley](https://smapi.io/))
- [StardewArchipelago Mod Release 2.x.x](https://github.com/agilbert1412/StardewArchipelago/releases) - [StardewArchipelago Mod Release 3.x.x](https://github.com/agilbert1412/StardewArchipelago/releases)
- It is important to use a mod release of version 3.x.x to play seeds that have been generated here. Later releases can only be used with later releases of the world generator, that are not hosted on archipelago.gg yet. - It is important to use a mod release of version 3.x.x to play seeds that have been generated here. Later releases can only be used with later releases of the world generator, that are not hosted on archipelago.gg yet.
## Optional Software ## Optional Software

View File

@ -124,21 +124,22 @@ def set_rules(multi_world: MultiWorld, player: int, world_options: StardewOption
# Help Wanted Quests # Help Wanted Quests
desired_number_help_wanted: int = world_options[options.HelpWantedLocations] // 7 desired_number_help_wanted: int = world_options[options.HelpWantedLocations] // 7
for i in range(1, desired_number_help_wanted + 1): for i in range(0, desired_number_help_wanted):
prefix = "Help Wanted:" prefix = "Help Wanted:"
delivery = "Item Delivery" delivery = "Item Delivery"
rule = logic.received("Month End", i - 1) rule = logic.received("Month End", i)
fishing_rule = rule & logic.can_fish() fishing_rule = rule & logic.can_fish()
slay_rule = rule & logic.has_any_weapon() slay_rule = rule & logic.has_any_weapon()
for j in range(i, i + 4): item_delivery_index = (i * 4) + 1
MultiWorldRules.set_rule(multi_world.get_location(f"{prefix} {delivery} {j}", player), for j in range(item_delivery_index, item_delivery_index + 4):
rule.simplify()) location_name = f"{prefix} {delivery} {j}"
MultiWorldRules.set_rule(multi_world.get_location(location_name, player), rule.simplify())
MultiWorldRules.set_rule(multi_world.get_location(f"{prefix} Gathering {i}", player), MultiWorldRules.set_rule(multi_world.get_location(f"{prefix} Gathering {i+1}", player),
rule.simplify()) rule.simplify())
MultiWorldRules.set_rule(multi_world.get_location(f"{prefix} Fishing {i}", player), MultiWorldRules.set_rule(multi_world.get_location(f"{prefix} Fishing {i+1}", player),
fishing_rule.simplify()) fishing_rule.simplify())
MultiWorldRules.set_rule(multi_world.get_location(f"{prefix} Slay Monsters {i}", player), MultiWorldRules.set_rule(multi_world.get_location(f"{prefix} Slay Monsters {i+1}", player),
slay_rule.simplify()) slay_rule.simplify())
set_fishsanity_rules(all_location_names, logic, multi_world, player) set_fishsanity_rules(all_location_names, logic, multi_world, player)

View File

@ -1,16 +1,30 @@
import unittest import unittest
from ..data.bundle_data import all_bundle_items from ..data.bundle_data import all_bundle_items, quality_crops_items
class TestBundles(unittest.TestCase): class TestBundles(unittest.TestCase):
def test_all_bundle_items_have_3_parts(self): def test_all_bundle_items_have_3_parts(self):
for bundle_item in all_bundle_items: for bundle_item in all_bundle_items:
name = bundle_item.item.name with self.subTest(bundle_item.item.name):
assert len(name) > 0 self.assertGreater(len(bundle_item.item.name), 0)
id = bundle_item.item.item_id id = bundle_item.item.item_id
assert (id > 0 or id == -1) self.assertGreaterEqual(id, -1)
amount = bundle_item.amount self.assertNotEqual(id, 0)
assert amount > 0 self.assertGreater(bundle_item.amount, 0)
quality = bundle_item.quality self.assertGreaterEqual(bundle_item.quality, 0)
assert quality >= 0
def test_quality_crops_have_correct_amounts(self):
for bundle_item in quality_crops_items:
with self.subTest(bundle_item.item.name):
name = bundle_item.item.name
if name == "Sweet Gem Berry" or name == "Ancient Fruit":
self.assertEqual(bundle_item.amount, 1)
else:
self.assertEqual(bundle_item.amount, 5)
def test_quality_crops_have_correct_quality(self):
for bundle_item in quality_crops_items:
with self.subTest(bundle_item.item.name):
self.assertEqual(bundle_item.quality, 2)

View File

@ -9,12 +9,12 @@ class TestCsvIntegrity(unittest.TestCase):
items = load_item_csv() items = load_item_csv()
for item in items: for item in items:
assert item.code_without_offset is not None, \ self.assertIsNotNone(item.code_without_offset, "Some item do not have an id."
"Some item do not have an id. Run the script `update_data.py` to generate them." " Run the script `update_data.py` to generate them.")
def test_locations_integrity(self): def test_locations_integrity(self):
locations = load_location_csv() locations = load_location_csv()
for location in locations: for location in locations:
assert location.code_without_offset is not None, \ self.assertIsNotNone(location.code_without_offset, "Some location do not have an id."
"Some location do not have an id. Run the script `update_data.py` to generate them." " Run the script `update_data.py` to generate them.")

View File

@ -17,54 +17,57 @@ class TestBaseItemGeneration(SVTestBase):
if item.classification is classification} if item.classification is classification}
for item in all_classified_items: for item in all_classified_items:
assert item in self.multiworld.itempool self.assertIn(item, self.multiworld.itempool)
def test_creates_as_many_item_as_non_event_locations(self): def test_creates_as_many_item_as_non_event_locations(self):
non_event_locations = [location for location in self.multiworld.get_locations(self.player) if non_event_locations = [location for location in self.multiworld.get_locations(self.player) if
not location.event] not location.event]
assert len(non_event_locations), len(self.multiworld.itempool) self.assertEqual(len(non_event_locations), len(self.multiworld.itempool))
class TestGivenProgressiveBackpack(SVTestBase): class TestGivenProgressiveBackpack(SVTestBase):
options = {options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive} options = {options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive}
def test_when_generate_world_then_two_progressive_backpack_are_added(self): def test_when_generate_world_then_two_progressive_backpack_are_added(self):
assert self.multiworld.itempool.count(self.world.create_item("Progressive Backpack")) == 2 self.assertEqual(self.multiworld.itempool.count(self.world.create_item("Progressive Backpack")), 2)
def test_when_generate_world_then_backpack_locations_are_added(self): def test_when_generate_world_then_backpack_locations_are_added(self):
created_locations = {location.name for location in self.multiworld.get_locations(1)} created_locations = {location.name for location in self.multiworld.get_locations(1)}
assert all(location.name in created_locations for location in locations.locations_by_tag[LocationTags.BACKPACK]) backpacks_exist = [location.name in created_locations
for location in locations.locations_by_tag[LocationTags.BACKPACK]]
all_exist = all(backpacks_exist)
self.assertTrue(all_exist)
class TestRemixedMineRewards(SVTestBase): class TestRemixedMineRewards(SVTestBase):
def test_when_generate_world_then_one_reward_is_added_per_chest(self): def test_when_generate_world_then_one_reward_is_added_per_chest(self):
# assert self.world.create_item("Rusty Sword") in self.multiworld.itempool # assert self.world.create_item("Rusty Sword") in self.multiworld.itempool
assert any(self.world.create_item(item) in self.multiworld.itempool self.assertTrue(any(self.world.create_item(item) in self.multiworld.itempool
for item in items_by_group[Group.MINES_FLOOR_10]) for item in items_by_group[Group.MINES_FLOOR_10]))
assert any(self.world.create_item(item) in self.multiworld.itempool self.assertTrue(any(self.world.create_item(item) in self.multiworld.itempool
for item in items_by_group[Group.MINES_FLOOR_20]) for item in items_by_group[Group.MINES_FLOOR_20]))
assert self.world.create_item("Slingshot") in self.multiworld.itempool self.assertIn(self.world.create_item("Slingshot"), self.multiworld.itempool)
assert any(self.world.create_item(item) in self.multiworld.itempool self.assertTrue(any(self.world.create_item(item) in self.multiworld.itempool
for item in items_by_group[Group.MINES_FLOOR_50]) for item in items_by_group[Group.MINES_FLOOR_50]))
assert any(self.world.create_item(item) in self.multiworld.itempool self.assertTrue(any(self.world.create_item(item) in self.multiworld.itempool
for item in items_by_group[Group.MINES_FLOOR_60]) for item in items_by_group[Group.MINES_FLOOR_60]))
assert self.world.create_item("Master Slingshot") in self.multiworld.itempool self.assertIn(self.world.create_item("Master Slingshot"), self.multiworld.itempool)
assert any(self.world.create_item(item) in self.multiworld.itempool self.assertTrue(any(self.world.create_item(item) in self.multiworld.itempool
for item in items_by_group[Group.MINES_FLOOR_80]) for item in items_by_group[Group.MINES_FLOOR_80]))
assert any(self.world.create_item(item) in self.multiworld.itempool self.assertTrue(any(self.world.create_item(item) in self.multiworld.itempool
for item in items_by_group[Group.MINES_FLOOR_90]) for item in items_by_group[Group.MINES_FLOOR_90]))
assert self.world.create_item("Stardrop") in self.multiworld.itempool self.assertIn(self.world.create_item("Stardrop"), self.multiworld.itempool)
assert any(self.world.create_item(item) in self.multiworld.itempool self.assertTrue(any(self.world.create_item(item) in self.multiworld.itempool
for item in items_by_group[Group.MINES_FLOOR_110]) for item in items_by_group[Group.MINES_FLOOR_110]))
assert self.world.create_item("Skull Key") in self.multiworld.itempool self.assertIn(self.world.create_item("Skull Key"), self.multiworld.itempool)
# This test as 1 over 90,000 changes to fail... Sorry in advance # This test has a 1/90,000 chance to fail... Sorry in advance
def test_when_generate_world_then_rewards_are_not_all_vanilla(self): def test_when_generate_world_then_rewards_are_not_all_vanilla(self):
assert not all(self.world.create_item(item) in self.multiworld.itempool self.assertFalse(all(self.world.create_item(item) in self.multiworld.itempool
for item in for item in
["Leather Boots", "Steel Smallsword", "Tundra Boots", "Crystal Dagger", "Firewalker Boots", ["Leather Boots", "Steel Smallsword", "Tundra Boots", "Crystal Dagger", "Firewalker Boots",
"Obsidian Edge", "Space Boots"]) "Obsidian Edge", "Space Boots"]))
class TestProgressiveElevator(SVTestBase): class TestProgressiveElevator(SVTestBase):
@ -81,11 +84,11 @@ class TestProgressiveElevator(SVTestBase):
self.collect([self.get_item_by_name("Combat Level")] * 4) self.collect([self.get_item_by_name("Combat Level")] * 4)
self.collect(self.get_item_by_name("Adventurer's Guild")) self.collect(self.get_item_by_name("Adventurer's Guild"))
assert not self.multiworld.get_region("The Mines - Floor 120", self.player).can_reach(self.multiworld.state) self.assertFalse(self.multiworld.get_region("The Mines - Floor 120", self.player).can_reach(self.multiworld.state))
self.collect(self.get_item_by_name("Progressive Mine Elevator")) self.collect(self.get_item_by_name("Progressive Mine Elevator"))
assert self.multiworld.get_region("The Mines - Floor 120", self.player).can_reach(self.multiworld.state) self.assertTrue(self.multiworld.get_region("The Mines - Floor 120", self.player).can_reach(self.multiworld.state))
def test_given_access_to_floor_115_when_find_another_pickaxe_and_sword_then_has_access_to_floor_120(self): def test_given_access_to_floor_115_when_find_another_pickaxe_and_sword_then_has_access_to_floor_120(self):
self.collect([self.get_item_by_name("Progressive Pickaxe")] * 2) self.collect([self.get_item_by_name("Progressive Pickaxe")] * 2)
@ -94,14 +97,14 @@ class TestProgressiveElevator(SVTestBase):
self.collect([self.get_item_by_name("Combat Level")] * 4) self.collect([self.get_item_by_name("Combat Level")] * 4)
self.collect(self.get_item_by_name("Adventurer's Guild")) self.collect(self.get_item_by_name("Adventurer's Guild"))
assert not self.multiworld.get_region("The Mines - Floor 120", self.player).can_reach(self.multiworld.state) self.assertFalse(self.multiworld.get_region("The Mines - Floor 120", self.player).can_reach(self.multiworld.state))
self.collect(self.get_item_by_name("Progressive Pickaxe")) self.collect(self.get_item_by_name("Progressive Pickaxe"))
self.collect(self.multiworld.create_item("Steel Falchion", self.player)) self.collect(self.multiworld.create_item("Steel Falchion", self.player))
self.collect(self.get_item_by_name("Combat Level")) self.collect(self.get_item_by_name("Combat Level"))
self.collect(self.get_item_by_name("Combat Level")) self.collect(self.get_item_by_name("Combat Level"))
assert self.multiworld.get_region("The Mines - Floor 120", self.player).can_reach(self.multiworld.state) self.assertTrue(self.multiworld.get_region("The Mines - Floor 120", self.player).can_reach(self.multiworld.state))
class TestLocationGeneration(SVTestBase): class TestLocationGeneration(SVTestBase):
@ -109,7 +112,7 @@ class TestLocationGeneration(SVTestBase):
def test_all_location_created_are_in_location_table(self): def test_all_location_created_are_in_location_table(self):
for location in self.multiworld.get_locations(self.player): for location in self.multiworld.get_locations(self.player):
if not location.event: if not location.event:
assert location.name in location_table self.assertIn(location.name, location_table)
class TestLocationAndItemCount(SVTestBase): class TestLocationAndItemCount(SVTestBase):
@ -130,7 +133,7 @@ class TestLocationAndItemCount(SVTestBase):
} }
def test_minimal_location_maximal_items_still_valid(self): def test_minimal_location_maximal_items_still_valid(self):
assert len(self.multiworld.get_locations()) >= len(self.multiworld.get_items()) self.assertGreaterEqual(len(self.multiworld.get_locations()), len(self.multiworld.get_items()))
class TestFriendsanityNone(SVTestBase): class TestFriendsanityNone(SVTestBase):
@ -140,11 +143,11 @@ class TestFriendsanityNone(SVTestBase):
def test_no_friendsanity_items(self): def test_no_friendsanity_items(self):
for item in self.multiworld.get_items(): for item in self.multiworld.get_items():
assert not item.name.endswith(": 1 <3") self.assertFalse(item.name.endswith(": 1 <3"))
def test_no_friendsanity_locations(self): def test_no_friendsanity_locations(self):
for location in self.multiworld.get_locations(): for location in self.multiworld.get_locations():
assert not location.name.startswith("Friendsanity") self.assertFalse(location.name.startswith("Friendsanity"))
class TestFriendsanityBachelors(SVTestBase): class TestFriendsanityBachelors(SVTestBase):
@ -159,7 +162,7 @@ class TestFriendsanityBachelors(SVTestBase):
for item in self.multiworld.get_items(): for item in self.multiworld.get_items():
if item.name.endswith(suffix): if item.name.endswith(suffix):
villager_name = item.name[:item.name.index(suffix)] villager_name = item.name[:item.name.index(suffix)]
assert villager_name in self.bachelors self.assertIn(villager_name, self.bachelors)
def test_friendsanity_only_bachelor_locations(self): def test_friendsanity_only_bachelor_locations(self):
prefix = "Friendsanity: " prefix = "Friendsanity: "
@ -171,8 +174,8 @@ class TestFriendsanityBachelors(SVTestBase):
parts = name_trimmed.split(" ") parts = name_trimmed.split(" ")
name = parts[0] name = parts[0]
hearts = parts[1] hearts = parts[1]
assert name in self.bachelors self.assertIn(name, self.bachelors)
assert int(hearts) <= 8 self.assertLessEqual(int(hearts), 8)
class TestFriendsanityStartingNpcs(SVTestBase): class TestFriendsanityStartingNpcs(SVTestBase):
@ -186,7 +189,7 @@ class TestFriendsanityStartingNpcs(SVTestBase):
for item in self.multiworld.get_items(): for item in self.multiworld.get_items():
if item.name.endswith(suffix): if item.name.endswith(suffix):
villager_name = item.name[:item.name.index(suffix)] villager_name = item.name[:item.name.index(suffix)]
assert villager_name not in self.excluded_npcs self.assertNotIn(villager_name, self.excluded_npcs)
def test_friendsanity_only_starting_npcs_locations(self): def test_friendsanity_only_starting_npcs_locations(self):
prefix = "Friendsanity: " prefix = "Friendsanity: "
@ -198,14 +201,14 @@ class TestFriendsanityStartingNpcs(SVTestBase):
parts = name_trimmed.split(" ") parts = name_trimmed.split(" ")
name = parts[0] name = parts[0]
hearts = parts[1] hearts = parts[1]
assert name not in self.excluded_npcs self.assertNotIn(name, self.excluded_npcs)
assert name in all_villagers_by_name or name == "Pet" self.assertTrue(name in all_villagers_by_name or name == "Pet")
if name == "Pet": if name == "Pet":
assert int(hearts) <= 5 self.assertLessEqual(int(hearts), 5)
elif all_villagers_by_name[name].bachelor: elif all_villagers_by_name[name].bachelor:
assert int(hearts) <= 8 self.assertLessEqual(int(hearts), 8)
else: else:
assert int(hearts) <= 10 self.assertLessEqual(int(hearts), 10)
class TestFriendsanityAllNpcs(SVTestBase): class TestFriendsanityAllNpcs(SVTestBase):
@ -218,7 +221,7 @@ class TestFriendsanityAllNpcs(SVTestBase):
for item in self.multiworld.get_items(): for item in self.multiworld.get_items():
if item.name.endswith(suffix): if item.name.endswith(suffix):
villager_name = item.name[:item.name.index(suffix)] villager_name = item.name[:item.name.index(suffix)]
assert villager_name in all_villagers_by_name or villager_name == "Pet" self.assertTrue(villager_name in all_villagers_by_name or villager_name == "Pet")
def test_friendsanity_all_locations(self): def test_friendsanity_all_locations(self):
prefix = "Friendsanity: " prefix = "Friendsanity: "
@ -230,13 +233,13 @@ class TestFriendsanityAllNpcs(SVTestBase):
parts = name_trimmed.split(" ") parts = name_trimmed.split(" ")
name = parts[0] name = parts[0]
hearts = parts[1] hearts = parts[1]
assert name in all_villagers_by_name or name == "Pet" self.assertTrue(name in all_villagers_by_name or name == "Pet")
if name == "Pet": if name == "Pet":
assert int(hearts) <= 5 self.assertLessEqual(int(hearts), 5)
elif all_villagers_by_name[name].bachelor: elif all_villagers_by_name[name].bachelor:
assert int(hearts) <= 8 self.assertLessEqual(int(hearts), 8)
else: else:
assert int(hearts) <= 10 self.assertLessEqual(int(hearts), 10)
class TestFriendsanityAllNpcsWithMarriage(SVTestBase): class TestFriendsanityAllNpcsWithMarriage(SVTestBase):
@ -249,7 +252,7 @@ class TestFriendsanityAllNpcsWithMarriage(SVTestBase):
for item in self.multiworld.get_items(): for item in self.multiworld.get_items():
if item.name.endswith(suffix): if item.name.endswith(suffix):
villager_name = item.name[:item.name.index(suffix)] villager_name = item.name[:item.name.index(suffix)]
assert villager_name in all_villagers_by_name or villager_name == "Pet" self.assertTrue(villager_name in all_villagers_by_name or villager_name == "Pet")
def test_friendsanity_all_with_marriage_locations(self): def test_friendsanity_all_with_marriage_locations(self):
prefix = "Friendsanity: " prefix = "Friendsanity: "
@ -261,10 +264,10 @@ class TestFriendsanityAllNpcsWithMarriage(SVTestBase):
parts = name_trimmed.split(" ") parts = name_trimmed.split(" ")
name = parts[0] name = parts[0]
hearts = parts[1] hearts = parts[1]
assert name in all_villagers_by_name or name == "Pet" self.assertTrue(name in all_villagers_by_name or name == "Pet")
if name == "Pet": if name == "Pet":
assert int(hearts) <= 5 self.assertLessEqual(int(hearts), 5)
elif all_villagers_by_name[name].bachelor: elif all_villagers_by_name[name].bachelor:
assert int(hearts) <= 14 self.assertLessEqual(int(hearts), 14)
else: else:
assert int(hearts) <= 10 self.assertLessEqual(int(hearts), 10)