Archipelago/worlds/stardew_valley/items.py

529 lines
24 KiB
Python
Raw Normal View History

import csv
import enum
import logging
from dataclasses import dataclass, field
from pathlib import Path
from random import Random
2023-07-19 18:26:38 +00:00
from typing import Dict, List, Protocol, Union, Set, Optional
from BaseClasses import Item, ItemClassification
from . import options, data
from .data.villagers_data import all_villagers
2023-07-19 18:26:38 +00:00
from .mods.mod_data import ModNames
from .options import StardewOptions
2023-07-19 18:26:38 +00:00
from .strings.ap_names.buff_names import Buff
ITEM_CODE_OFFSET = 717000
logger = logging.getLogger(__name__)
world_folder = Path(__file__).parent
class Group(enum.Enum):
RESOURCE_PACK = enum.auto()
FRIENDSHIP_PACK = enum.auto()
COMMUNITY_REWARD = enum.auto()
TRASH = enum.auto()
MINES_FLOOR_10 = enum.auto()
MINES_FLOOR_20 = enum.auto()
MINES_FLOOR_50 = enum.auto()
MINES_FLOOR_60 = enum.auto()
MINES_FLOOR_80 = enum.auto()
MINES_FLOOR_90 = enum.auto()
MINES_FLOOR_110 = enum.auto()
FOOTWEAR = enum.auto()
HATS = enum.auto()
RING = enum.auto()
WEAPON = enum.auto()
PROGRESSIVE_TOOLS = enum.auto()
SKILL_LEVEL_UP = enum.auto()
ARCADE_MACHINE_BUFFS = enum.auto()
GALAXY_WEAPONS = enum.auto()
BASE_RESOURCE = enum.auto()
WARP_TOTEM = enum.auto()
GEODE = enum.auto()
ORE = enum.auto()
FERTILIZER = enum.auto()
SEED = enum.auto()
2023-07-19 18:26:38 +00:00
CROPSANITY = enum.auto()
FISHING_RESOURCE = enum.auto()
SEASON = enum.auto()
TRAVELING_MERCHANT_DAY = enum.auto()
MUSEUM = enum.auto()
FRIENDSANITY = enum.auto()
2023-07-19 18:26:38 +00:00
FESTIVAL = enum.auto()
RARECROW = enum.auto()
TRAP = enum.auto()
MAXIMUM_ONE = enum.auto()
EXACTLY_TWO = enum.auto()
2023-07-19 18:26:38 +00:00
DEPRECATED = enum.auto()
RESOURCE_PACK_USEFUL = enum.auto()
SPECIAL_ORDER_BOARD = enum.auto()
SPECIAL_ORDER_QI = enum.auto()
BABY = enum.auto()
GINGER_ISLAND = enum.auto()
WALNUT_PURCHASE = enum.auto()
TV_CHANNEL = enum.auto()
MAGIC_SPELL = enum.auto()
@dataclass(frozen=True)
class ItemData:
code_without_offset: Optional[int]
name: str
classification: ItemClassification
2023-07-19 18:26:38 +00:00
mod_name: Optional[str] = None
groups: Set[Group] = field(default_factory=frozenset)
def __post_init__(self):
if not isinstance(self.groups, frozenset):
super().__setattr__("groups", frozenset(self.groups))
@property
def code(self):
return ITEM_CODE_OFFSET + self.code_without_offset if self.code_without_offset is not None else None
def has_any_group(self, *group: Group) -> bool:
groups = set(group)
return bool(groups.intersection(self.groups))
class StardewItemFactory(Protocol):
def __call__(self, name: Union[str, ItemData]) -> Item:
raise NotImplementedError
def load_item_csv():
try:
from importlib.resources import files
except ImportError:
from importlib_resources import files # noqa
items = []
with files(data).joinpath("items.csv").open() as file:
item_reader = csv.DictReader(file)
for item in item_reader:
id = int(item["id"]) if item["id"] else None
classification = ItemClassification[item["classification"]]
groups = {Group[group] for group in item["groups"].split(",") if group}
2023-07-19 18:26:38 +00:00
mod_name = str(item["mod_name"]) if item["mod_name"] else None
items.append(ItemData(id, item["name"], classification, mod_name, groups))
return items
events = [
ItemData(None, "Victory", ItemClassification.progression),
ItemData(None, "Month End", ItemClassification.progression),
]
all_items: List[ItemData] = load_item_csv() + events
item_table: Dict[str, ItemData] = {}
items_by_group: Dict[Group, List[ItemData]] = {}
def initialize_groups():
for item in all_items:
for group in item.groups:
item_group = items_by_group.get(group, list())
item_group.append(item)
items_by_group[group] = item_group
def initialize_item_table():
item_table.update({item.name: item for item in all_items})
initialize_item_table()
initialize_groups()
2023-07-19 18:26:38 +00:00
def create_items(item_factory: StardewItemFactory, locations_count: int, items_to_exclude: List[Item],
world_options: StardewOptions,
random: Random) -> List[Item]:
2023-07-19 18:26:38 +00:00
items = []
unique_items = create_unique_items(item_factory, world_options, random)
for item in items_to_exclude:
2023-07-19 18:26:38 +00:00
if item in unique_items:
unique_items.remove(item)
assert len(unique_items) <= locations_count, f"There should be at least as many locations [{locations_count}] as there are mandatory items [{len(unique_items)}]"
items += unique_items
logger.debug(f"Created {len(unique_items)} unique items")
2023-07-19 18:26:38 +00:00
unique_filler_items = create_unique_filler_items(item_factory, world_options, random, locations_count - len(items))
items += unique_filler_items
logger.debug(f"Created {len(unique_filler_items)} unique filler items")
2023-07-19 18:26:38 +00:00
resource_pack_items = fill_with_resource_packs_and_traps(item_factory, world_options, random, items, locations_count)
items += resource_pack_items
logger.debug(f"Created {len(resource_pack_items)} resource packs")
return items
def create_unique_items(item_factory: StardewItemFactory, world_options: StardewOptions, random: Random) -> List[Item]:
items = []
items.extend(item_factory(item) for item in items_by_group[Group.COMMUNITY_REWARD])
create_backpack_items(item_factory, world_options, items)
create_mine_rewards(item_factory, items, random)
2023-07-19 18:26:38 +00:00
create_elevators(item_factory, world_options, items)
create_tools(item_factory, world_options, items)
create_skills(item_factory, world_options, items)
2023-07-19 18:26:38 +00:00
create_wizard_buildings(item_factory, world_options, items)
create_carpenter_buildings(item_factory, world_options, items)
items.append(item_factory("Beach Bridge"))
2023-07-19 18:26:38 +00:00
items.append(item_factory("Dark Talisman"))
create_tv_channels(item_factory, items)
create_special_quest_rewards(item_factory, items)
2023-07-19 18:26:38 +00:00
create_stardrops(item_factory, world_options, items)
create_museum_items(item_factory, world_options, items)
create_arcade_machine_items(item_factory, world_options, items)
items.append(item_factory(random.choice(items_by_group[Group.GALAXY_WEAPONS])))
create_player_buffs(item_factory, world_options, items)
2023-07-19 18:26:38 +00:00
create_traveling_merchant_items(item_factory, items)
items.append(item_factory("Return Scepter"))
2023-07-19 18:26:38 +00:00
create_seasons(item_factory, world_options, items)
create_seeds(item_factory, world_options, items)
create_friendsanity_items(item_factory, world_options, items)
2023-07-19 18:26:38 +00:00
create_festival_rewards(item_factory, world_options, items)
create_babies(item_factory, items, random)
create_special_order_board_rewards(item_factory, world_options, items)
create_special_order_qi_rewards(item_factory, world_options, items)
create_walnut_purchase_rewards(item_factory, world_options, items)
create_magic_mod_spells(item_factory, world_options, items)
return items
def create_backpack_items(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
if (world_options[options.BackpackProgression] == options.BackpackProgression.option_progressive or
world_options[options.BackpackProgression] == options.BackpackProgression.option_early_progressive):
items.extend(item_factory(item) for item in ["Progressive Backpack"] * 2)
2023-07-19 18:26:38 +00:00
if ModNames.big_backpack in world_options[options.Mods]:
items.append(item_factory("Progressive Backpack"))
def create_mine_rewards(item_factory: StardewItemFactory, items: List[Item], random: Random):
items.append(item_factory("Rusty Sword"))
items.append(item_factory(random.choice(items_by_group[Group.MINES_FLOOR_10])))
items.append(item_factory(random.choice(items_by_group[Group.MINES_FLOOR_20])))
items.append(item_factory("Slingshot"))
items.append(item_factory(random.choice(items_by_group[Group.MINES_FLOOR_50])))
items.append(item_factory(random.choice(items_by_group[Group.MINES_FLOOR_60])))
items.append(item_factory("Master Slingshot"))
items.append(item_factory(random.choice(items_by_group[Group.MINES_FLOOR_80])))
items.append(item_factory(random.choice(items_by_group[Group.MINES_FLOOR_90])))
items.append(item_factory(random.choice(items_by_group[Group.MINES_FLOOR_110])))
items.append(item_factory("Skull Key"))
2023-07-19 18:26:38 +00:00
def create_elevators(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
if world_options[options.ElevatorProgression] == options.ElevatorProgression.option_vanilla:
return
items.extend([item_factory(item) for item in ["Progressive Mine Elevator"] * 24])
if ModNames.deepwoods in world_options[options.Mods]:
items.extend([item_factory(item) for item in ["Progressive Woods Obelisk Sigils"] * 10])
2023-07-19 18:26:38 +00:00
if ModNames.skull_cavern_elevator in world_options[options.Mods]:
items.extend([item_factory(item) for item in ["Progressive Skull Cavern Elevator"] * 8])
def create_tools(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
if world_options[options.ToolProgression] == options.ToolProgression.option_progressive:
items.extend(item_factory(item) for item in items_by_group[Group.PROGRESSIVE_TOOLS] * 4)
items.append(item_factory("Golden Scythe"))
def create_skills(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
if world_options[options.SkillProgression] == options.SkillProgression.option_progressive:
2023-07-19 18:26:38 +00:00
for item in items_by_group[Group.SKILL_LEVEL_UP]:
if item.mod_name not in world_options[options.Mods] and item.mod_name is not None:
continue
items.extend(item_factory(item) for item in [item.name] * 10)
2023-07-19 18:26:38 +00:00
def create_wizard_buildings(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
items.append(item_factory("Earth Obelisk"))
items.append(item_factory("Water Obelisk"))
items.append(item_factory("Desert Obelisk"))
items.append(item_factory("Junimo Hut"))
items.append(item_factory("Gold Clock"))
2023-07-19 18:26:38 +00:00
if world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_false:
items.append(item_factory("Island Obelisk"))
if ModNames.deepwoods in world_options[options.Mods]:
items.append(item_factory("Woods Obelisk"))
2023-07-19 18:26:38 +00:00
def create_carpenter_buildings(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
if world_options[options.BuildingProgression] in {options.BuildingProgression.option_progressive,
options.BuildingProgression.option_progressive_early_shipping_bin}:
items.append(item_factory("Progressive Coop"))
items.append(item_factory("Progressive Coop"))
items.append(item_factory("Progressive Coop"))
items.append(item_factory("Progressive Barn"))
items.append(item_factory("Progressive Barn"))
items.append(item_factory("Progressive Barn"))
items.append(item_factory("Well"))
items.append(item_factory("Silo"))
items.append(item_factory("Mill"))
items.append(item_factory("Progressive Shed"))
items.append(item_factory("Progressive Shed"))
items.append(item_factory("Fish Pond"))
items.append(item_factory("Stable"))
items.append(item_factory("Slime Hutch"))
items.append(item_factory("Shipping Bin"))
items.append(item_factory("Progressive House"))
items.append(item_factory("Progressive House"))
items.append(item_factory("Progressive House"))
2023-07-19 18:26:38 +00:00
if ModNames.tractor in world_options[options.Mods]:
items.append(item_factory("Tractor Garage"))
def create_special_quest_rewards(item_factory: StardewItemFactory, items: List[Item]):
items.append(item_factory("Adventurer's Guild"))
items.append(item_factory("Club Card"))
items.append(item_factory("Magnifying Glass"))
items.append(item_factory("Bear's Knowledge"))
items.append(item_factory("Iridium Snake Milk"))
2023-07-19 18:26:38 +00:00
def create_stardrops(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
items.append(item_factory("Stardrop")) # The Mines level 100
items.append(item_factory("Stardrop")) # Old Master Cannoli
2023-07-19 18:26:38 +00:00
if world_options[options.Fishsanity] != options.Fishsanity.option_none:
items.append(item_factory("Stardrop")) #Master Angler Stardrop
if ModNames.deepwoods in world_options[options.Mods]:
items.append(item_factory("Stardrop")) # Petting the Unicorn
def create_museum_items(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
if world_options[options.Museumsanity] == options.Museumsanity.option_none:
return
items.extend(item_factory(item) for item in ["Magic Rock Candy"] * 5)
items.extend(item_factory(item) for item in ["Ancient Seeds"] * 5)
items.extend(item_factory(item) for item in ["Traveling Merchant Metal Detector"] * 4)
items.append(item_factory("Ancient Seeds Recipe"))
items.append(item_factory("Stardrop"))
items.append(item_factory("Rusty Key"))
items.append(item_factory("Dwarvish Translation Guide"))
def create_friendsanity_items(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
if world_options[options.Friendsanity] == options.Friendsanity.option_none:
return
exclude_non_bachelors = world_options[options.Friendsanity] == options.Friendsanity.option_bachelors
exclude_locked_villagers = world_options[options.Friendsanity] == options.Friendsanity.option_starting_npcs or \
world_options[options.Friendsanity] == options.Friendsanity.option_bachelors
2023-07-19 18:26:38 +00:00
include_post_marriage_hearts = world_options[options.Friendsanity] == options.Friendsanity.option_all_with_marriage
exclude_ginger_island = world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true
heart_size = world_options[options.FriendsanityHeartSize]
for villager in all_villagers:
2023-07-19 18:26:38 +00:00
if villager.mod_name not in world_options[options.Mods] and villager.mod_name is not None:
continue
if not villager.available and exclude_locked_villagers:
continue
if not villager.bachelor and exclude_non_bachelors:
continue
2023-07-19 18:26:38 +00:00
if villager.name == "Leo" and exclude_ginger_island:
continue
heart_cap = 8 if villager.bachelor else 10
if include_post_marriage_hearts and villager.bachelor:
heart_cap = 14
for heart in range(1, 15):
2023-07-19 18:26:38 +00:00
if heart > heart_cap:
break
if heart % heart_size == 0 or heart == heart_cap:
items.append(item_factory(f"{villager.name} <3"))
if not exclude_non_bachelors:
for heart in range(1, 6):
2023-07-19 18:26:38 +00:00
if heart % heart_size == 0 or heart == 5:
items.append(item_factory(f"Pet <3"))
2023-07-19 18:26:38 +00:00
def create_babies(item_factory: StardewItemFactory, items: List[Item], random: Random):
baby_items = [item for item in items_by_group[Group.BABY]]
for i in range(2):
chosen_baby = random.choice(baby_items)
items.append(item_factory(chosen_baby))
2023-07-19 18:26:38 +00:00
def create_arcade_machine_items(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
if world_options[options.ArcadeMachineLocations] == options.ArcadeMachineLocations.option_full_shuffling:
items.append(item_factory("JotPK: Progressive Boots"))
items.append(item_factory("JotPK: Progressive Boots"))
items.append(item_factory("JotPK: Progressive Gun"))
items.append(item_factory("JotPK: Progressive Gun"))
items.append(item_factory("JotPK: Progressive Gun"))
items.append(item_factory("JotPK: Progressive Gun"))
items.append(item_factory("JotPK: Progressive Ammo"))
items.append(item_factory("JotPK: Progressive Ammo"))
items.append(item_factory("JotPK: Progressive Ammo"))
items.append(item_factory("JotPK: Extra Life"))
items.append(item_factory("JotPK: Extra Life"))
items.append(item_factory("JotPK: Increased Drop Rate"))
items.extend(item_factory(item) for item in ["Junimo Kart: Extra Life"] * 8)
def create_player_buffs(item_factory: StardewItemFactory, world_options: options.StardewOptions, items: List[Item]):
2023-07-19 18:26:38 +00:00
number_of_movement_buffs: int = world_options[options.NumberOfMovementBuffs]
number_of_luck_buffs: int = world_options[options.NumberOfLuckBuffs]
items.extend(item_factory(item) for item in [Buff.movement] * number_of_movement_buffs)
items.extend(item_factory(item) for item in [Buff.luck] * number_of_luck_buffs)
2023-07-19 18:26:38 +00:00
def create_traveling_merchant_items(item_factory: StardewItemFactory, items: List[Item]):
items.extend([*(item_factory(item) for item in items_by_group[Group.TRAVELING_MERCHANT_DAY]),
*(item_factory(item) for item in ["Traveling Merchant Stock Size"] * 6),
*(item_factory(item) for item in ["Traveling Merchant Discount"] * 8)])
2023-07-19 18:26:38 +00:00
def create_seasons(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
if world_options[options.SeasonRandomization] == options.SeasonRandomization.option_disabled:
2023-07-19 18:26:38 +00:00
return
if world_options[options.SeasonRandomization] == options.SeasonRandomization.option_progressive:
2023-07-19 18:26:38 +00:00
items.extend([item_factory(item) for item in ["Progressive Season"] * 3])
return
items.extend([item_factory(item) for item in items_by_group[Group.SEASON]])
def create_seeds(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
if world_options[options.Cropsanity] == options.Cropsanity.option_disabled:
return
include_ginger_island = world_options[options.ExcludeGingerIsland] != options.ExcludeGingerIsland.option_true
seed_items = [item_factory(item) for item in items_by_group[Group.CROPSANITY] if include_ginger_island or Group.GINGER_ISLAND not in item.groups]
items.extend(seed_items)
def create_festival_rewards(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
if world_options[options.FestivalLocations] == options.FestivalLocations.option_disabled:
return
items.extend([*[item_factory(item) for item in items_by_group[Group.FESTIVAL] if item.classification != ItemClassification.filler],
item_factory("Stardrop")])
def create_walnut_purchase_rewards(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
if world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true:
return
items.extend([item_factory("Boat Repair"),
item_factory("Open Professor Snail Cave"),
item_factory("Ostrich Incubator Recipe"),
item_factory("Treehouse"),
2023-07-19 18:26:38 +00:00
*[item_factory(item) for item in items_by_group[Group.WALNUT_PURCHASE]]])
def create_special_order_board_rewards(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
if world_options[options.SpecialOrderLocations] == options.SpecialOrderLocations.option_disabled:
return
2023-07-19 18:26:38 +00:00
items.extend([item_factory(item) for item in items_by_group[Group.SPECIAL_ORDER_BOARD]])
2023-07-19 18:26:38 +00:00
def create_special_order_qi_rewards(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
if (world_options[options.SpecialOrderLocations] != options.SpecialOrderLocations.option_board_qi or
world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true):
return
qi_gem_rewards = ["100 Qi Gems", "10 Qi Gems", "40 Qi Gems", "25 Qi Gems", "25 Qi Gems",
"40 Qi Gems", "20 Qi Gems", "50 Qi Gems", "40 Qi Gems", "35 Qi Gems"]
qi_gem_items = [item_factory(reward) for reward in qi_gem_rewards]
items.extend(qi_gem_items)
def create_tv_channels(item_factory: StardewItemFactory, items: List[Item]):
items.extend([item_factory(item) for item in items_by_group[Group.TV_CHANNEL]])
def create_filler_festival_rewards(item_factory: StardewItemFactory, world_options: StardewOptions) -> List[Item]:
if world_options[options.FestivalLocations] == options.FestivalLocations.option_disabled:
return []
2023-07-19 18:26:38 +00:00
return [item_factory(item) for item in items_by_group[Group.FESTIVAL] if
item.classification == ItemClassification.filler]
2023-07-19 18:26:38 +00:00
def create_magic_mod_spells(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]):
if ModNames.magic not in world_options[options.Mods]:
return []
items.extend([item_factory(item) for item in items_by_group[Group.MAGIC_SPELL]])
2023-07-19 18:26:38 +00:00
def create_unique_filler_items(item_factory: StardewItemFactory, world_options: options.StardewOptions, random: Random,
available_item_slots: int) -> List[Item]:
items = []
2023-07-19 18:26:38 +00:00
items.extend(create_filler_festival_rewards(item_factory, world_options))
if len(items) > available_item_slots:
items = random.sample(items, available_item_slots)
return items
def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, world_options: options.StardewOptions, random: Random,
items_already_added: List[Item],
number_locations: int) -> List[Item]:
include_traps = world_options[options.TrapItems] != options.TrapItems.option_no_traps
all_filler_packs = [pack for pack in items_by_group[Group.RESOURCE_PACK]]
all_filler_packs.extend(items_by_group[Group.TRASH])
if include_traps:
all_filler_packs.extend(items_by_group[Group.TRAP])
items_already_added_names = [item.name for item in items_already_added]
useful_resource_packs = [pack for pack in items_by_group[Group.RESOURCE_PACK_USEFUL]
if pack.name not in items_already_added_names]
trap_items = [pack for pack in items_by_group[Group.TRAP]
if pack.name not in items_already_added_names and
(pack.mod_name is None or pack.mod_name in world_options[options.Mods])]
priority_filler_items = []
priority_filler_items.extend(useful_resource_packs)
if include_traps:
priority_filler_items.extend(trap_items)
all_filler_packs = remove_excluded_packs(all_filler_packs, world_options)
priority_filler_items = remove_excluded_packs(priority_filler_items, world_options)
number_priority_items = len(priority_filler_items)
required_resource_pack = number_locations - len(items_already_added)
if required_resource_pack < number_priority_items:
chosen_priority_items = [item_factory(resource_pack) for resource_pack in
random.sample(priority_filler_items, required_resource_pack)]
return chosen_priority_items
items = []
chosen_priority_items = [item_factory(resource_pack) for resource_pack in priority_filler_items]
items.extend(chosen_priority_items)
required_resource_pack -= number_priority_items
all_filler_packs = [filler_pack for filler_pack in all_filler_packs
if Group.MAXIMUM_ONE not in filler_pack.groups or
filler_pack.name not in [priority_item.name for priority_item in priority_filler_items]]
while required_resource_pack > 0:
resource_pack = random.choice(all_filler_packs)
exactly_2 = Group.EXACTLY_TWO in resource_pack.groups
while exactly_2 and required_resource_pack == 1:
resource_pack = random.choice(all_filler_packs)
exactly_2 = Group.EXACTLY_TWO in resource_pack.groups
items.append(item_factory(resource_pack))
required_resource_pack -= 1
if exactly_2:
items.append(item_factory(resource_pack))
required_resource_pack -= 1
if exactly_2 or Group.MAXIMUM_ONE in resource_pack.groups:
all_filler_packs.remove(resource_pack)
return items
2023-07-19 18:26:38 +00:00
def remove_excluded_packs(packs, world_options):
included_packs = [pack for pack in packs if Group.DEPRECATED not in pack.groups]
if world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true:
included_packs = [pack for pack in included_packs if Group.GINGER_ISLAND not in pack.groups]
return included_packs