Skip to content
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
2 changes: 2 additions & 0 deletions CommonClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ def output(self, text: str):

def _cmd_exit(self) -> bool:
"""Close connections and client"""
if self.ctx.ui:
self.ctx.ui.stop()
self.ctx.exit_event.set()
return True

Expand Down
6 changes: 6 additions & 0 deletions WebHostLib/templates/playerOptions/macros.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
{{ OptionTitle(option_name, option) }}
<div class="named-range-container">
<select id="{{ option_name }}-select" name="{{ option_name }}" data-option-name="{{ option_name }}" {{ "disabled" if option.default == "random" }}>
{% if option.default not in option.special_range_names.values() %}
<option value="{{ option.default }}" selected>Default ({{ option.default }})</option>
{% endif %}
{% for key, val in option.special_range_names.items() %}
{% if option.default == val %}
<option value="{{ val }}" selected>{{ key|replace("_", " ")|title }} ({{ val }})</option>
Expand Down Expand Up @@ -94,6 +97,9 @@
<div class="text-choice-container">
<div class="text-choice-wrapper">
<select id="{{ option_name }}" name="{{ option_name }}" {{ "disabled" if option.default == "random" }}>
{% if option.default not in option.options.values() %}
<option value="{{ option.default }}" selected>Default ({{ option.default }})</option>
{% endif %}
{% for id, name in option.name_lookup.items()|sort %}
{% if name != "random" %}
{% if option.default == id %}
Expand Down
234 changes: 181 additions & 53 deletions worlds/stardew_valley/__init__.py

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions worlds/stardew_valley/archipelago.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"game": "Stardew Valley",
"authors": ["KaitoKid", "Jouramie", "Witchybun (Mod Support)", "Exempt-Medic (Proofreading)"],
"authors": ["Kaito Kid", "Jouramie", "Witchybun (Mod Support)", "Exempt-Medic (Proofreading)"],
"minimum_ap_version": "0.6.4",
"world_version": "6.0.0"
"world_version": "7.4.0"
}
208 changes: 202 additions & 6 deletions worlds/stardew_valley/bundles/bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
from random import Random
from typing import List, Tuple

from Options import DeathLink
from .bundle_item import BundleItem
from ..content import StardewContent
from ..options import BundlePrice, StardewValleyOptions, ExcludeGingerIsland, FestivalLocations
from ..strings.currency_names import Currency
from ..options import BundlePrice, StardewValleyOptions, ExcludeGingerIsland, FestivalLocations, TrapDifficulty, \
MultipleDaySleepEnabled, Gifting, EntranceRandomization
from ..strings.bundle_names import MemeBundleName
from ..strings.currency_names import Currency, MemeCurrency


@dataclass
Expand All @@ -19,6 +22,10 @@ class Bundle:
def __repr__(self):
return f"{self.name} -> {self.number_required} from {repr(self.items)}"

def special_behavior(self, world):
if self.name == MemeBundleName.clickbait:
world.options.exclude_locations.value.add(MemeBundleName.clickbait)


@dataclass
class BundleTemplate:
Expand All @@ -42,7 +49,42 @@ def extend_from(template, items: List[BundleItem]):
template.number_required_items)

def create_bundle(self, random: Random, content: StardewContent, options: StardewValleyOptions) -> Bundle:
number_required, price_multiplier = get_bundle_final_prices(options.bundle_price, self.number_required_items, False)
try:
number_required, price_multiplier = get_bundle_final_prices(options.bundle_price, self.number_required_items, False)
filtered_items = [item for item in self.items if item.can_appear(content, options)]
number_items = len(filtered_items)
number_chosen_items = self.number_possible_items
if number_chosen_items < number_required:
number_chosen_items = number_required

if number_chosen_items > number_items:
chosen_items = filtered_items + random.choices(filtered_items, k=number_chosen_items - number_items)
else:
chosen_items = random.sample(filtered_items, number_chosen_items)
chosen_items = [item.as_amount(min(999, max(1, math.floor(item.amount * price_multiplier)))) for item in chosen_items]
return Bundle(self.room, self.name, chosen_items, number_required)
except Exception as e:
raise Exception(f"Failed at creating bundle '{self.name}'. Error: {e}")


def can_appear(self, options: StardewValleyOptions) -> bool:
if self.name == MemeBundleName.trap and options.trap_items.value == TrapDifficulty.option_no_traps:
return False
if self.name == MemeBundleName.hibernation and options.multiple_day_sleep_enabled == MultipleDaySleepEnabled.option_false:
return False
if self.name == MemeBundleName.cooperation and options.gifting == Gifting.option_false:
return False
return True


class FixedMultiplierBundleTemplate(BundleTemplate):

def __init__(self, room: str, name: str, items: List[BundleItem], number_possible_items: int,
number_required_items: int):
super().__init__(room, name, items, number_possible_items, number_required_items)

def create_bundle(self, random: Random, content: StardewContent, options: StardewValleyOptions) -> Bundle:
number_required = get_number_required_items(options.bundle_price, self.number_required_items)
filtered_items = [item for item in self.items if item.can_appear(content, options)]
number_items = len(filtered_items)
number_chosen_items = self.number_possible_items
Expand All @@ -53,11 +95,53 @@ def create_bundle(self, random: Random, content: StardewContent, options: Starde
chosen_items = filtered_items + random.choices(filtered_items, k=number_chosen_items - number_items)
else:
chosen_items = random.sample(filtered_items, number_chosen_items)
chosen_items = [item.as_amount(max(1, math.floor(item.amount * price_multiplier))) for item in chosen_items]
chosen_items = [item.as_amount(min(999, max(1, item.amount))) for item in chosen_items]
return Bundle(self.room, self.name, chosen_items, number_required)

def can_appear(self, options: StardewValleyOptions) -> bool:
return True

class FixedPriceBundleTemplate(BundleTemplate):

def __init__(self, room: str, name: str, items: List[BundleItem], number_possible_items: int,
number_required_items: int):
super().__init__(room, name, items, number_possible_items, number_required_items)

def create_bundle(self, random: Random, content: StardewContent, options: StardewValleyOptions) -> Bundle:
filtered_items = [item for item in self.items if item.can_appear(content, options)]
number_items = len(filtered_items)
number_chosen_items = self.number_possible_items
if number_chosen_items < self.number_required_items:
number_chosen_items = self.number_required_items

if number_chosen_items > number_items:
chosen_items = filtered_items + random.choices(filtered_items, k=number_chosen_items - number_items)
else:
chosen_items = random.sample(filtered_items, number_chosen_items)
chosen_items = [item.as_amount(max(1, math.floor(item.amount))) for item in chosen_items]
return Bundle(self.room, self.name, chosen_items, self.number_required_items)


# This type of bundle will always match the number of slots and the number of items
class FixedSlotsBundleTemplate(BundleTemplate):

def __init__(self, room: str, name: str, items: List[BundleItem], number_possible_items: int,
number_required_items: int):
super().__init__(room, name, items, number_possible_items, number_required_items)

def create_bundle(self, random: Random, content: StardewContent, options: StardewValleyOptions) -> Bundle:
try:
number_required, price_multiplier = get_bundle_final_prices(options.bundle_price, self.number_required_items, False)
filtered_items = [item for item in self.items if item.can_appear(content, options)]
number_items = len(filtered_items)
number_chosen_items = number_required
if number_chosen_items > number_items:
chosen_items = filtered_items + random.choices(filtered_items, k=number_chosen_items - number_items)
else:
chosen_items = random.sample(filtered_items, number_chosen_items)
chosen_items = [item.as_amount(min(999, max(1, math.floor(item.amount * price_multiplier)))) for item in chosen_items]
return Bundle(self.room, self.name, chosen_items, number_required)
except Exception as e:
raise Exception(f"Failed at creating bundle '{self.name}'. Error: {e}")



class CurrencyBundleTemplate(BundleTemplate):
Expand All @@ -77,15 +161,33 @@ def get_currency_amount(self, bundle_price_option: BundlePrice):
return currency_amount

def can_appear(self, options: StardewValleyOptions) -> bool:
if not super().can_appear(options):
return False
if options.exclude_ginger_island == ExcludeGingerIsland.option_true:
if self.item.item_name == Currency.qi_gem or self.item.item_name == Currency.golden_walnut or self.item.item_name == Currency.cinder_shard:
return False
if options.festival_locations == FestivalLocations.option_disabled:
if self.item.item_name == Currency.star_token:
return False
if options.entrance_randomization != EntranceRandomization.option_disabled:
if self.item.item_name == MemeCurrency.time_elapsed:
return False
if options.death_link != DeathLink.option_true:
if self.item.item_name == MemeCurrency.deathlinks:
return False
return True


class FixedPriceCurrencyBundleTemplate(CurrencyBundleTemplate):

def __init__(self, room: str, name: str, item: BundleItem):
super().__init__(room, name, item)

def create_bundle(self, random: Random, content: StardewContent, options: StardewValleyOptions) -> Bundle:
currency_amount = self.item.amount
return Bundle(self.room, self.name, [BundleItem(self.item.item_name, currency_amount)], 1)


class MoneyBundleTemplate(CurrencyBundleTemplate):

def __init__(self, room: str, default_name: str, item: BundleItem):
Expand All @@ -111,11 +213,15 @@ def get_currency_amount(self, bundle_price_option: BundlePrice):

class IslandBundleTemplate(BundleTemplate):
def can_appear(self, options: StardewValleyOptions) -> bool:
if not super().can_appear(options):
return False
return options.exclude_ginger_island == ExcludeGingerIsland.option_false


class FestivalBundleTemplate(BundleTemplate):
def can_appear(self, options: StardewValleyOptions) -> bool:
if not super().can_appear(options):
return False
return options.festival_locations != FestivalLocations.option_disabled


Expand Down Expand Up @@ -149,6 +255,96 @@ def create_bundle(self, random: Random, content: StardewContent, options: Starde
return Bundle(self.room, self.name, chosen_items, number_required)


class FixedPriceDeepBundleTemplate(DeepBundleTemplate):

def __init__(self, room: str, name: str, categories: List[List[BundleItem]], number_possible_items: int,
number_required_items: int):
super().__init__(room, name, categories, number_possible_items, number_required_items)

def create_bundle(self, random: Random, content: StardewContent, options: StardewValleyOptions) -> Bundle:
number_required = self.number_required_items
number_categories = len(self.categories)
number_chosen_categories = self.number_possible_items
if number_chosen_categories < number_required:
number_chosen_categories = number_required

if number_chosen_categories > number_categories:
chosen_categories = self.categories + random.choices(self.categories,
k=number_chosen_categories - number_categories)
else:
chosen_categories = random.sample(self.categories, number_chosen_categories)

chosen_items = []
for category in chosen_categories:
filtered_items = [item for item in category if item.can_appear(content, options)]
chosen_items.append(random.choice(filtered_items))

chosen_items = [item.as_amount(max(1, math.floor(item.amount))) for item in chosen_items]
return Bundle(self.room, self.name, chosen_items, number_required)


@dataclass
class BureaucracyBundleTemplate(BundleTemplate):

def __init__(self, room: str, name: str, items: List[BundleItem], number_possible_items: int,
number_required_items: int):
super(BureaucracyBundleTemplate, self).__init__(room, name, items, number_possible_items, number_required_items)

def create_bundle(self, random: Random, content: StardewContent, options: StardewValleyOptions) -> Bundle:
number_required = self.number_required_items + options.bundle_price.value
number_required = min(12, max(4, number_required))
if options.bundle_price == BundlePrice.option_minimum:
number_required = 4
if options.bundle_price == BundlePrice.option_maximum:
number_required = 12
price_multiplier = 1

filtered_items = [item for item in self.items if item.can_appear(content, options)]
number_items = len(filtered_items)
number_chosen_items = self.number_possible_items
if number_chosen_items < number_required:
number_chosen_items = number_required

if number_chosen_items > number_items:
chosen_items = filtered_items + random.choices(filtered_items, k=number_chosen_items - number_items)
else:
chosen_items = random.sample(filtered_items, number_chosen_items)
chosen_items = [item.as_amount(max(1, math.floor(item.amount * price_multiplier))) for item in chosen_items]
return Bundle(self.room, self.name, chosen_items, number_required)


@dataclass
class RecursiveBundleTemplate(BundleTemplate):
number_sub_bundles: int

def __init__(self, room: str, name: str, items: List[BundleItem], number_possible_items: int,
number_required_items: int, number_sub_bundles: int):
super(RecursiveBundleTemplate, self).__init__(room, name, items, number_possible_items, number_required_items)
self.number_sub_bundles = number_sub_bundles

def create_bundle(self, random: Random, content: StardewContent, options: StardewValleyOptions) -> Bundle:
number_required = self.number_required_items + (options.bundle_price.value * self.number_sub_bundles)
if options.bundle_price == BundlePrice.option_minimum:
number_required = self.number_sub_bundles
if options.bundle_price == BundlePrice.option_maximum:
number_required = self.number_sub_bundles * 8
number_required = min(self.number_sub_bundles * 8, max(self.number_sub_bundles, number_required))
price_multiplier = get_price_multiplier(options.bundle_price, False)

filtered_items = [item for item in self.items if item.can_appear(content, options)]
number_items = len(filtered_items)
number_chosen_items = self.number_possible_items
if number_chosen_items < number_required:
number_chosen_items = number_required

if number_chosen_items > number_items:
chosen_items = filtered_items + random.choices(filtered_items, k=number_chosen_items - number_items)
else:
chosen_items = random.sample(filtered_items, number_chosen_items)
chosen_items = [item.as_amount(max(1, math.floor(item.amount * price_multiplier))) for item in chosen_items]
return Bundle(self.room, self.name, chosen_items, number_required)


def get_bundle_final_prices(bundle_price_option: BundlePrice, default_required_items: int, is_currency: bool) -> Tuple[int, float]:
number_required_items = get_number_required_items(bundle_price_option, default_required_items)
price_multiplier = get_price_multiplier(bundle_price_option, is_currency)
Expand Down
6 changes: 6 additions & 0 deletions worlds/stardew_valley/bundles/bundle_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ def can_appear(self, content: StardewContent, options: StardewValleyOptions) ->
return content.features.skill_progression.are_masteries_shuffled


class QiBoardItemSource(BundleItemSource):
def can_appear(self, content: StardewContent, options: StardewValleyOptions) -> bool:
return content_packs.qi_board_content_pack.name in content.registered_packs


class ContentItemSource(BundleItemSource):
"""This is meant to be used for items that are managed by the content packs."""

Expand All @@ -51,6 +56,7 @@ class Sources:
island = IslandItemSource()
festival = FestivalItemSource()
masteries = MasteryItemSource()
qi_board = QiBoardItemSource()
content = ContentItemSource()

item_name: str
Expand Down
Loading
Loading