feat: customizer options for prices in shops and money balance tuning
This commit is contained in:
@@ -153,6 +153,7 @@ class World(object):
|
||||
set_player_attr('trap_door_mode', 'optional')
|
||||
set_player_attr('key_logic_algorithm', 'partial')
|
||||
set_player_attr('aga_randomness', True)
|
||||
set_player_attr('money_balance', 100)
|
||||
|
||||
set_player_attr('shopsanity', False)
|
||||
set_player_attr('mixed_travel', 'prevent')
|
||||
|
||||
4
CLI.py
4
CLI.py
@@ -142,7 +142,8 @@ def parse_cli(argv, no_defaults=False):
|
||||
'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle',
|
||||
'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx',
|
||||
'msu_resume', 'collection_rate', 'colorizepots', 'decoupledoors', 'door_type_mode',
|
||||
'trap_door_mode', 'key_logic_algorithm', 'door_self_loops', 'any_enemy_logic', 'aga_randomness']:
|
||||
'trap_door_mode', 'key_logic_algorithm', 'door_self_loops', 'any_enemy_logic', 'aga_randomness',
|
||||
'money_balance']:
|
||||
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
||||
if player == 1:
|
||||
setattr(ret, name, {1: value})
|
||||
@@ -228,6 +229,7 @@ def parse_settings():
|
||||
'mixed_travel': 'prevent',
|
||||
'standardize_palettes': 'standardize',
|
||||
'aga_randomness': True,
|
||||
'money_balance': 100,
|
||||
|
||||
"triforce_pool": 0,
|
||||
"triforce_goal": 0,
|
||||
|
||||
11
Fill.py
11
Fill.py
@@ -1020,14 +1020,16 @@ def balance_money_progression(world):
|
||||
solvent = set()
|
||||
insolvent = set()
|
||||
for player in range(1, world.players+1):
|
||||
if wallet[player] >= sphere_costs[player] >= 0:
|
||||
modifier = world.money_balance[player]/100
|
||||
if wallet[player] >= sphere_costs[player] * modifier >= 0:
|
||||
solvent.add(player)
|
||||
if sphere_costs[player] > 0 and sphere_costs[player] > wallet[player]:
|
||||
if sphere_costs[player] > 0 and sphere_costs[player] * modifier > wallet[player]:
|
||||
insolvent.add(player)
|
||||
if len([p for p in solvent if len(locked_by_money[p]) > 0]) == 0:
|
||||
if len(insolvent) > 0:
|
||||
target_player = min(insolvent, key=lambda p: sphere_costs[p]-wallet[p])
|
||||
difference = sphere_costs[target_player]-wallet[target_player]
|
||||
target_modifier = world.money_balance[target_player]/100
|
||||
difference = sphere_costs[target_player] * target_modifier - wallet[target_player]
|
||||
logger.debug(f'Money balancing needed: Player {target_player} short {difference}')
|
||||
else:
|
||||
difference = 0
|
||||
@@ -1066,7 +1068,8 @@ def balance_money_progression(world):
|
||||
solvent.add(target_player)
|
||||
# apply solvency
|
||||
for player in solvent:
|
||||
wallet[player] -= sphere_costs[player]
|
||||
modifier = world.money_balance[player]/100
|
||||
wallet[player] -= sphere_costs[player] * modifier
|
||||
for location in locked_by_money[player]:
|
||||
if isinstance(location, str) and location == 'Kiki':
|
||||
kiki_paid[player] = True
|
||||
|
||||
27
ItemList.py
27
ItemList.py
@@ -704,7 +704,8 @@ def customize_shops(world, player):
|
||||
if retro_bow and item.name == 'Single Arrow':
|
||||
price = 80
|
||||
# randomize price
|
||||
shop.add_inventory(idx, item.name, randomize_price(price), max_repeat, player=item.player)
|
||||
price = final_price(loc, price, world, player)
|
||||
shop.add_inventory(idx, item.name, price, max_repeat, player=item.player)
|
||||
if item.name in cap_replacements and shop_name not in retro_shops and item.player == player:
|
||||
possible_replacements.append((shop, idx, location, item))
|
||||
# randomize shopkeeper
|
||||
@@ -721,8 +722,10 @@ def customize_shops(world, player):
|
||||
if len(choices) > 0:
|
||||
shop, idx, loc, item = random.choice(choices)
|
||||
upgrade = ItemFactory('Bomb Upgrade (+5)', player)
|
||||
shop.add_inventory(idx, upgrade.name, randomize_price(upgrade.price), 6,
|
||||
item.name, randomize_price(item.price), player=item.player)
|
||||
up_price = final_price(loc, upgrade.price, world, player)
|
||||
rep_price = final_price(loc, item.price, world, player)
|
||||
shop.add_inventory(idx, upgrade.name, up_price, 6,
|
||||
item.name, rep_price, player=item.player)
|
||||
loc.item = upgrade
|
||||
upgrade.location = loc
|
||||
if not found_arrow_upgrade and len(possible_replacements) > 0:
|
||||
@@ -733,8 +736,10 @@ def customize_shops(world, player):
|
||||
if len(choices) > 0:
|
||||
shop, idx, loc, item = random.choice(choices)
|
||||
upgrade = ItemFactory('Arrow Upgrade (+5)', player)
|
||||
shop.add_inventory(idx, upgrade.name, randomize_price(upgrade.price), 6,
|
||||
item.name, randomize_price(item.price), player=item.player)
|
||||
up_price = final_price(loc, upgrade.price, world, player)
|
||||
rep_price = final_price(loc, item.price, world, player)
|
||||
shop.add_inventory(idx, upgrade.name, up_price, 6,
|
||||
item.name, rep_price, player=item.player)
|
||||
loc.item = upgrade
|
||||
upgrade.location = loc
|
||||
change_shop_items_to_rupees(world, player, shops_to_customize)
|
||||
@@ -742,6 +747,15 @@ def customize_shops(world, player):
|
||||
check_hints(world, player)
|
||||
|
||||
|
||||
def final_price(location, price, world, player):
|
||||
if world.customizer and world.customizer.get_prices(player):
|
||||
custom_prices = world.customizer.get_prices(player)
|
||||
if location in custom_prices:
|
||||
# todo: validate valid price
|
||||
return custom_prices[location]
|
||||
return randomize_price(price)
|
||||
|
||||
|
||||
def randomize_price(price):
|
||||
half_price = price // 2
|
||||
max_price = price - half_price
|
||||
@@ -781,6 +795,9 @@ def balance_prices(world, player):
|
||||
shop_locations = []
|
||||
for shop, loc_list in shop_to_location_table.items():
|
||||
for loc in loc_list:
|
||||
if world.customizer and world.customizer.get_prices(player) and loc in world.customizer.get_prices(player):
|
||||
needed_money += world.customizer.get_prices(player)[loc]
|
||||
continue # considered a fixed price and shouldn't be altered
|
||||
loc = world.get_location(loc, player)
|
||||
shop_locations.append(loc)
|
||||
slot = shop_to_location_table[loc.parent_region.name].index(loc.name)
|
||||
|
||||
2
Main.py
2
Main.py
@@ -145,6 +145,7 @@ def main(args, seed=None, fish=None):
|
||||
world.collection_rate = args.collection_rate.copy()
|
||||
world.colorizepots = args.colorizepots.copy()
|
||||
world.aga_randomness = args.aga_randomness.copy()
|
||||
world.money_balance = args.money_balance.copy()
|
||||
|
||||
world.treasure_hunt_count = {}
|
||||
world.treasure_hunt_total = {}
|
||||
@@ -513,6 +514,7 @@ def copy_world(world):
|
||||
ret.trap_door_mode = world.trap_door_mode.copy()
|
||||
ret.key_logic_algorithm = world.key_logic_algorithm.copy()
|
||||
ret.aga_randomness = world.aga_randomness.copy()
|
||||
ret.money_balance = world.money_balance.copy()
|
||||
ret.experimental = world.experimental.copy()
|
||||
ret.shopsanity = world.shopsanity.copy()
|
||||
ret.dropshuffle = world.dropshuffle.copy()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
* 1.4.8
|
||||
- New option: Mirror Scroll - to add the item to the starting inventory in non-doors modes (Thanks Telethar!)
|
||||
- Customizer: Ability to customize shop prices and control money balancing. `money_balance` is a percentage betwen 0 and 100 that attempts to ensure you have that much percentage of money available for purchases. (100 is default, 0 essentially ignores money considerations)
|
||||
- Fixed a key logic bug with decoupled doors when a big key door leads to a small key door (the small key door was missing appropriate logic)
|
||||
- Fixed an ER bug where Bonk Fairy could be used for a mandatory connector in standard mode (boots could allow escape to be skipped)
|
||||
- Fixed an issue with flute activation in rain mode. (thanks Codemann!)
|
||||
|
||||
@@ -539,6 +539,7 @@
|
||||
"action": "store_false",
|
||||
"type": "bool"
|
||||
},
|
||||
"money_balance": {},
|
||||
"saveonexit": {
|
||||
"choices": [
|
||||
"ask",
|
||||
|
||||
@@ -19,11 +19,20 @@ class CustomSettings(object):
|
||||
self.relative_dir = None
|
||||
self.world_rep = {}
|
||||
self.player_range = None
|
||||
self.player_map = {} # player number to name
|
||||
|
||||
def load_yaml(self, file):
|
||||
self.file_source = load_yaml(file)
|
||||
head, filename = os.path.split(file)
|
||||
self.relative_dir = head
|
||||
if 'version' in self.file_source and self.file_source['version'].startswith('2'):
|
||||
player_number = 1
|
||||
for key in self.file_source.keys():
|
||||
if key in ['meta', 'version']:
|
||||
continue
|
||||
else:
|
||||
self.player_map[player_number] = key
|
||||
player_number += 1
|
||||
|
||||
def determine_seed(self, default_seed):
|
||||
if 'meta' in self.file_source:
|
||||
@@ -161,6 +170,7 @@ class CustomSettings(object):
|
||||
args.triforce_max_difference[p] = get_setting(settings['triforce_max_difference'], args.triforce_max_difference[p])
|
||||
args.beemizer[p] = get_setting(settings['beemizer'], args.beemizer[p])
|
||||
args.aga_randomness[p] = get_setting(settings['aga_randomness'], args.aga_randomness[p])
|
||||
args.money_balance[p] = get_setting(settings['money_balance'], args.money_balance[p])
|
||||
|
||||
# mystery usage
|
||||
args.usestartinventory[p] = get_setting(settings['usestartinventory'], args.usestartinventory[p])
|
||||
@@ -189,6 +199,9 @@ class CustomSettings(object):
|
||||
return self.file_source['placements']
|
||||
return None
|
||||
|
||||
def get_prices(self, player):
|
||||
return self.get_attribute_by_player_composite('prices', player)
|
||||
|
||||
def get_advanced_placements(self):
|
||||
if 'advanced_placements' in self.file_source:
|
||||
return self.file_source['advanced_placements']
|
||||
@@ -229,6 +242,34 @@ class CustomSettings(object):
|
||||
return self.file_source['enemies']
|
||||
return None
|
||||
|
||||
|
||||
def get_attribute_by_player_composite(self, attribute, player):
|
||||
attempt = self.get_attribute_by_player_new(attribute, player)
|
||||
if attempt is not None:
|
||||
return attempt
|
||||
attempt = self.get_attribute_by_player(attribute, player)
|
||||
return attempt
|
||||
|
||||
def get_attribute_by_player(self, attribute, player):
|
||||
if attribute in self.file_source:
|
||||
if player in self.file_source[attribute]:
|
||||
return self.file_source[attribute][player]
|
||||
return None
|
||||
|
||||
def get_attribute_by_player_new(self, attribute, player):
|
||||
player_id = self.get_player_id(player)
|
||||
if player_id is not None:
|
||||
if attribute in self.file_source[player_id]:
|
||||
return self.file_source[player_id][attribute]
|
||||
return None
|
||||
|
||||
def get_player_id(self, player):
|
||||
if player in self.file_source:
|
||||
return player
|
||||
if player in self.player_map and self.player_map[player] in self.file_source:
|
||||
return self.player_map[player]
|
||||
return None
|
||||
|
||||
def create_from_world(self, world, settings):
|
||||
self.player_range = range(1, world.players + 1)
|
||||
settings_dict, meta_dict = {}, {}
|
||||
@@ -293,6 +334,7 @@ class CustomSettings(object):
|
||||
settings_dict[p]['triforce_pool'] = world.treasure_hunt_total[p]
|
||||
settings_dict[p]['beemizer'] = world.beemizer[p]
|
||||
settings_dict[p]['aga_randomness'] = world.aga_randomness[p]
|
||||
settings_dict[p]['money_balance'] = world.money_balance[p]
|
||||
|
||||
# rom adjust stuff
|
||||
# settings_dict[p]['sprite'] = world.sprite[p]
|
||||
|
||||
Reference in New Issue
Block a user