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('trap_door_mode', 'optional')
|
||||||
set_player_attr('key_logic_algorithm', 'partial')
|
set_player_attr('key_logic_algorithm', 'partial')
|
||||||
set_player_attr('aga_randomness', True)
|
set_player_attr('aga_randomness', True)
|
||||||
|
set_player_attr('money_balance', 100)
|
||||||
|
|
||||||
set_player_attr('shopsanity', False)
|
set_player_attr('shopsanity', False)
|
||||||
set_player_attr('mixed_travel', 'prevent')
|
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',
|
'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle',
|
||||||
'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx',
|
'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx',
|
||||||
'msu_resume', 'collection_rate', 'colorizepots', 'decoupledoors', 'door_type_mode',
|
'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)
|
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
||||||
if player == 1:
|
if player == 1:
|
||||||
setattr(ret, name, {1: value})
|
setattr(ret, name, {1: value})
|
||||||
@@ -228,6 +229,7 @@ def parse_settings():
|
|||||||
'mixed_travel': 'prevent',
|
'mixed_travel': 'prevent',
|
||||||
'standardize_palettes': 'standardize',
|
'standardize_palettes': 'standardize',
|
||||||
'aga_randomness': True,
|
'aga_randomness': True,
|
||||||
|
'money_balance': 100,
|
||||||
|
|
||||||
"triforce_pool": 0,
|
"triforce_pool": 0,
|
||||||
"triforce_goal": 0,
|
"triforce_goal": 0,
|
||||||
|
|||||||
11
Fill.py
11
Fill.py
@@ -1020,14 +1020,16 @@ def balance_money_progression(world):
|
|||||||
solvent = set()
|
solvent = set()
|
||||||
insolvent = set()
|
insolvent = set()
|
||||||
for player in range(1, world.players+1):
|
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)
|
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)
|
insolvent.add(player)
|
||||||
if len([p for p in solvent if len(locked_by_money[p]) > 0]) == 0:
|
if len([p for p in solvent if len(locked_by_money[p]) > 0]) == 0:
|
||||||
if len(insolvent) > 0:
|
if len(insolvent) > 0:
|
||||||
target_player = min(insolvent, key=lambda p: sphere_costs[p]-wallet[p])
|
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}')
|
logger.debug(f'Money balancing needed: Player {target_player} short {difference}')
|
||||||
else:
|
else:
|
||||||
difference = 0
|
difference = 0
|
||||||
@@ -1066,7 +1068,8 @@ def balance_money_progression(world):
|
|||||||
solvent.add(target_player)
|
solvent.add(target_player)
|
||||||
# apply solvency
|
# apply solvency
|
||||||
for player in solvent:
|
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]:
|
for location in locked_by_money[player]:
|
||||||
if isinstance(location, str) and location == 'Kiki':
|
if isinstance(location, str) and location == 'Kiki':
|
||||||
kiki_paid[player] = True
|
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':
|
if retro_bow and item.name == 'Single Arrow':
|
||||||
price = 80
|
price = 80
|
||||||
# randomize price
|
# 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:
|
if item.name in cap_replacements and shop_name not in retro_shops and item.player == player:
|
||||||
possible_replacements.append((shop, idx, location, item))
|
possible_replacements.append((shop, idx, location, item))
|
||||||
# randomize shopkeeper
|
# randomize shopkeeper
|
||||||
@@ -721,8 +722,10 @@ def customize_shops(world, player):
|
|||||||
if len(choices) > 0:
|
if len(choices) > 0:
|
||||||
shop, idx, loc, item = random.choice(choices)
|
shop, idx, loc, item = random.choice(choices)
|
||||||
upgrade = ItemFactory('Bomb Upgrade (+5)', player)
|
upgrade = ItemFactory('Bomb Upgrade (+5)', player)
|
||||||
shop.add_inventory(idx, upgrade.name, randomize_price(upgrade.price), 6,
|
up_price = final_price(loc, upgrade.price, world, player)
|
||||||
item.name, randomize_price(item.price), player=item.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
|
loc.item = upgrade
|
||||||
upgrade.location = loc
|
upgrade.location = loc
|
||||||
if not found_arrow_upgrade and len(possible_replacements) > 0:
|
if not found_arrow_upgrade and len(possible_replacements) > 0:
|
||||||
@@ -733,8 +736,10 @@ def customize_shops(world, player):
|
|||||||
if len(choices) > 0:
|
if len(choices) > 0:
|
||||||
shop, idx, loc, item = random.choice(choices)
|
shop, idx, loc, item = random.choice(choices)
|
||||||
upgrade = ItemFactory('Arrow Upgrade (+5)', player)
|
upgrade = ItemFactory('Arrow Upgrade (+5)', player)
|
||||||
shop.add_inventory(idx, upgrade.name, randomize_price(upgrade.price), 6,
|
up_price = final_price(loc, upgrade.price, world, player)
|
||||||
item.name, randomize_price(item.price), player=item.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
|
loc.item = upgrade
|
||||||
upgrade.location = loc
|
upgrade.location = loc
|
||||||
change_shop_items_to_rupees(world, player, shops_to_customize)
|
change_shop_items_to_rupees(world, player, shops_to_customize)
|
||||||
@@ -742,6 +747,15 @@ def customize_shops(world, player):
|
|||||||
check_hints(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):
|
def randomize_price(price):
|
||||||
half_price = price // 2
|
half_price = price // 2
|
||||||
max_price = price - half_price
|
max_price = price - half_price
|
||||||
@@ -781,6 +795,9 @@ def balance_prices(world, player):
|
|||||||
shop_locations = []
|
shop_locations = []
|
||||||
for shop, loc_list in shop_to_location_table.items():
|
for shop, loc_list in shop_to_location_table.items():
|
||||||
for loc in loc_list:
|
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)
|
loc = world.get_location(loc, player)
|
||||||
shop_locations.append(loc)
|
shop_locations.append(loc)
|
||||||
slot = shop_to_location_table[loc.parent_region.name].index(loc.name)
|
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.collection_rate = args.collection_rate.copy()
|
||||||
world.colorizepots = args.colorizepots.copy()
|
world.colorizepots = args.colorizepots.copy()
|
||||||
world.aga_randomness = args.aga_randomness.copy()
|
world.aga_randomness = args.aga_randomness.copy()
|
||||||
|
world.money_balance = args.money_balance.copy()
|
||||||
|
|
||||||
world.treasure_hunt_count = {}
|
world.treasure_hunt_count = {}
|
||||||
world.treasure_hunt_total = {}
|
world.treasure_hunt_total = {}
|
||||||
@@ -513,6 +514,7 @@ def copy_world(world):
|
|||||||
ret.trap_door_mode = world.trap_door_mode.copy()
|
ret.trap_door_mode = world.trap_door_mode.copy()
|
||||||
ret.key_logic_algorithm = world.key_logic_algorithm.copy()
|
ret.key_logic_algorithm = world.key_logic_algorithm.copy()
|
||||||
ret.aga_randomness = world.aga_randomness.copy()
|
ret.aga_randomness = world.aga_randomness.copy()
|
||||||
|
ret.money_balance = world.money_balance.copy()
|
||||||
ret.experimental = world.experimental.copy()
|
ret.experimental = world.experimental.copy()
|
||||||
ret.shopsanity = world.shopsanity.copy()
|
ret.shopsanity = world.shopsanity.copy()
|
||||||
ret.dropshuffle = world.dropshuffle.copy()
|
ret.dropshuffle = world.dropshuffle.copy()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
* 1.4.8
|
* 1.4.8
|
||||||
- New option: Mirror Scroll - to add the item to the starting inventory in non-doors modes (Thanks Telethar!)
|
- 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 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 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!)
|
- Fixed an issue with flute activation in rain mode. (thanks Codemann!)
|
||||||
|
|||||||
@@ -539,6 +539,7 @@
|
|||||||
"action": "store_false",
|
"action": "store_false",
|
||||||
"type": "bool"
|
"type": "bool"
|
||||||
},
|
},
|
||||||
|
"money_balance": {},
|
||||||
"saveonexit": {
|
"saveonexit": {
|
||||||
"choices": [
|
"choices": [
|
||||||
"ask",
|
"ask",
|
||||||
|
|||||||
@@ -19,11 +19,20 @@ class CustomSettings(object):
|
|||||||
self.relative_dir = None
|
self.relative_dir = None
|
||||||
self.world_rep = {}
|
self.world_rep = {}
|
||||||
self.player_range = None
|
self.player_range = None
|
||||||
|
self.player_map = {} # player number to name
|
||||||
|
|
||||||
def load_yaml(self, file):
|
def load_yaml(self, file):
|
||||||
self.file_source = load_yaml(file)
|
self.file_source = load_yaml(file)
|
||||||
head, filename = os.path.split(file)
|
head, filename = os.path.split(file)
|
||||||
self.relative_dir = head
|
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):
|
def determine_seed(self, default_seed):
|
||||||
if 'meta' in self.file_source:
|
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.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.beemizer[p] = get_setting(settings['beemizer'], args.beemizer[p])
|
||||||
args.aga_randomness[p] = get_setting(settings['aga_randomness'], args.aga_randomness[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
|
# mystery usage
|
||||||
args.usestartinventory[p] = get_setting(settings['usestartinventory'], args.usestartinventory[p])
|
args.usestartinventory[p] = get_setting(settings['usestartinventory'], args.usestartinventory[p])
|
||||||
@@ -189,6 +199,9 @@ class CustomSettings(object):
|
|||||||
return self.file_source['placements']
|
return self.file_source['placements']
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_prices(self, player):
|
||||||
|
return self.get_attribute_by_player_composite('prices', player)
|
||||||
|
|
||||||
def get_advanced_placements(self):
|
def get_advanced_placements(self):
|
||||||
if 'advanced_placements' in self.file_source:
|
if 'advanced_placements' in self.file_source:
|
||||||
return self.file_source['advanced_placements']
|
return self.file_source['advanced_placements']
|
||||||
@@ -229,6 +242,34 @@ class CustomSettings(object):
|
|||||||
return self.file_source['enemies']
|
return self.file_source['enemies']
|
||||||
return None
|
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):
|
def create_from_world(self, world, settings):
|
||||||
self.player_range = range(1, world.players + 1)
|
self.player_range = range(1, world.players + 1)
|
||||||
settings_dict, meta_dict = {}, {}
|
settings_dict, meta_dict = {}, {}
|
||||||
@@ -293,6 +334,7 @@ class CustomSettings(object):
|
|||||||
settings_dict[p]['triforce_pool'] = world.treasure_hunt_total[p]
|
settings_dict[p]['triforce_pool'] = world.treasure_hunt_total[p]
|
||||||
settings_dict[p]['beemizer'] = world.beemizer[p]
|
settings_dict[p]['beemizer'] = world.beemizer[p]
|
||||||
settings_dict[p]['aga_randomness'] = world.aga_randomness[p]
|
settings_dict[p]['aga_randomness'] = world.aga_randomness[p]
|
||||||
|
settings_dict[p]['money_balance'] = world.money_balance[p]
|
||||||
|
|
||||||
# rom adjust stuff
|
# rom adjust stuff
|
||||||
# settings_dict[p]['sprite'] = world.sprite[p]
|
# settings_dict[p]['sprite'] = world.sprite[p]
|
||||||
|
|||||||
Reference in New Issue
Block a user