Witness: Changes in response to Beta run 1 (#494)
Co-authored-by: metzner <unconfigured@null.spigotmc.org>
This commit is contained in:
parent
1e592b4681
commit
9ab7c8d9e5
|
@ -1,6 +1,6 @@
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from BaseClasses import MultiWorld
|
from BaseClasses import MultiWorld
|
||||||
from Options import Toggle, DefaultOnToggle, Option
|
from Options import Toggle, DefaultOnToggle, Option, Range
|
||||||
|
|
||||||
|
|
||||||
# class HardMode(Toggle):
|
# class HardMode(Toggle):
|
||||||
|
@ -30,8 +30,8 @@ class ShuffleVaultBoxes(Toggle):
|
||||||
|
|
||||||
|
|
||||||
class ShuffleUncommonLocations(Toggle):
|
class ShuffleUncommonLocations(Toggle):
|
||||||
"""Adds the following checks to the pool:
|
"""Adds some optional puzzles that are somewhat difficult or out of the way.
|
||||||
Mountaintop River Shape, Tutorial Patio Floor, Theater Videos"""
|
Examples: Mountaintop River Shape, Tutorial Patio Floor, Theater Videos"""
|
||||||
display_name = "Shuffle Uncommon Locations"
|
display_name = "Shuffle Uncommon Locations"
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,6 +46,14 @@ class ChallengeVictoryCondition(Toggle):
|
||||||
display_name = "Victory on beating the Challenge"
|
display_name = "Victory on beating the Challenge"
|
||||||
|
|
||||||
|
|
||||||
|
class TrapPercentage(Range):
|
||||||
|
"""Replaces junk items with traps, at the specified rate."""
|
||||||
|
display_name = "Trap Percentage"
|
||||||
|
range_start = 0
|
||||||
|
range_end = 100
|
||||||
|
default = 20
|
||||||
|
|
||||||
|
|
||||||
the_witness_options: Dict[str, Option] = {
|
the_witness_options: Dict[str, Option] = {
|
||||||
# "hard_mode": HardMode,
|
# "hard_mode": HardMode,
|
||||||
# "unlock_symbols": UnlockSymbols,
|
# "unlock_symbols": UnlockSymbols,
|
||||||
|
@ -54,7 +62,8 @@ the_witness_options: Dict[str, Option] = {
|
||||||
"shuffle_vault_boxes": ShuffleVaultBoxes,
|
"shuffle_vault_boxes": ShuffleVaultBoxes,
|
||||||
"shuffle_uncommon": ShuffleUncommonLocations,
|
"shuffle_uncommon": ShuffleUncommonLocations,
|
||||||
"shuffle_hard": ShuffleHardLocations,
|
"shuffle_hard": ShuffleHardLocations,
|
||||||
"challenge_victory": ChallengeVictoryCondition
|
"challenge_victory": ChallengeVictoryCondition,
|
||||||
|
"trap_percentage": TrapPercentage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -370,7 +370,7 @@ Jungle (Jungle) - Main Island - True:
|
||||||
|
|
||||||
Outside Jungle River (River) - Main Island - True - Jungle - 0x337FA:
|
Outside Jungle River (River) - Main Island - True - Jungle - 0x337FA:
|
||||||
0x17CAA (Rhombic Avoid to Monastery Garden) - True - Environment
|
0x17CAA (Rhombic Avoid to Monastery Garden) - True - Environment
|
||||||
0x15ADD (Rhombic Avoid Vault) - True - Environment
|
0x15ADD (Vault) - True - Environment & Black/White Squares & Dots
|
||||||
0x03702 (Vault Box) - 0x15ADD - True
|
0x03702 (Vault Box) - 0x15ADD - True
|
||||||
|
|
||||||
Outside Bunker (Bunker) - Main Island - True - Inside Bunker - 0x0A079:
|
Outside Bunker (Bunker) - Main Island - True - Inside Bunker - 0x0A079:
|
||||||
|
|
|
@ -12,6 +12,7 @@ from .items import WitnessItem, StaticWitnessItems, WitnessPlayerItems
|
||||||
from .rules import set_rules
|
from .rules import set_rules
|
||||||
from .regions import WitnessRegions
|
from .regions import WitnessRegions
|
||||||
from .Options import is_option_enabled, the_witness_options
|
from .Options import is_option_enabled, the_witness_options
|
||||||
|
from .utils import best_junk_to_add_based_on_weights
|
||||||
|
|
||||||
|
|
||||||
class WitnessWebWorld(WebWorld):
|
class WitnessWebWorld(WebWorld):
|
||||||
|
@ -50,6 +51,8 @@ class WitnessWorld(World):
|
||||||
self.items = WitnessPlayerItems(self.locat, self.world, self.player, self.player_logic)
|
self.items = WitnessPlayerItems(self.locat, self.world, self.player, self.player_logic)
|
||||||
self.regio = WitnessRegions(self.locat)
|
self.regio = WitnessRegions(self.locat)
|
||||||
|
|
||||||
|
self.junk_items_created = {key: 0 for key in self.items.JUNK_WEIGHTS.keys()}
|
||||||
|
|
||||||
def generate_basic(self):
|
def generate_basic(self):
|
||||||
# Generate item pool
|
# Generate item pool
|
||||||
pool = []
|
pool = []
|
||||||
|
@ -69,13 +72,10 @@ class WitnessWorld(World):
|
||||||
pool.remove(items_by_name[random_good_item])
|
pool.remove(items_by_name[random_good_item])
|
||||||
|
|
||||||
# Put in junk items to fill the rest
|
# Put in junk items to fill the rest
|
||||||
junk_pool = self.items.JUNK_WEIGHTS.copy()
|
junk_size = len(self.locat.CHECK_LOCATION_TABLE) - len(pool) - len(self.locat.EVENT_LOCATION_TABLE) - 1
|
||||||
junk_pool = self.world.random.choices(
|
|
||||||
list(junk_pool.keys()), weights=list(junk_pool.values()),
|
|
||||||
k=len(self.locat.CHECK_LOCATION_TABLE) - len(pool) - len(self.locat.EVENT_LOCATION_TABLE) - 1
|
|
||||||
)
|
|
||||||
|
|
||||||
pool += [self.create_item(junk) for junk in junk_pool]
|
for i in range(0, junk_size):
|
||||||
|
pool.append(self.create_item(self.get_filler_item_name()))
|
||||||
|
|
||||||
# Tie Event Items to Event Locations (e.g. Laser Activations)
|
# Tie Event Items to Event Locations (e.g. Laser Activations)
|
||||||
for event_location in self.locat.EVENT_LOCATION_TABLE:
|
for event_location in self.locat.EVENT_LOCATION_TABLE:
|
||||||
|
@ -118,10 +118,12 @@ class WitnessWorld(World):
|
||||||
new_item.trap = item.trap
|
new_item.trap = item.trap
|
||||||
return new_item
|
return new_item
|
||||||
|
|
||||||
def get_filler_item_name(self) -> str: # Used ny itemlinks
|
def get_filler_item_name(self) -> str: # Used by itemlinks
|
||||||
junk_pool = self.items.JUNK_WEIGHTS.copy()
|
item = best_junk_to_add_based_on_weights(self.items.JUNK_WEIGHTS, self.junk_items_created)
|
||||||
|
|
||||||
return self.world.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()))[0]
|
self.junk_items_created[item] += 1
|
||||||
|
|
||||||
|
return item
|
||||||
|
|
||||||
|
|
||||||
class WitnessLocation(Location):
|
class WitnessLocation(Location):
|
||||||
|
|
|
@ -6,7 +6,8 @@ from typing import Dict, NamedTuple, Optional
|
||||||
|
|
||||||
from BaseClasses import Item, MultiWorld
|
from BaseClasses import Item, MultiWorld
|
||||||
from . import StaticWitnessLogic, WitnessPlayerLocations, WitnessPlayerLogic
|
from . import StaticWitnessLogic, WitnessPlayerLocations, WitnessPlayerLogic
|
||||||
from .Options import is_option_enabled
|
from .Options import get_option_value, is_option_enabled
|
||||||
|
from fractions import Fraction
|
||||||
|
|
||||||
|
|
||||||
class ItemData(NamedTuple):
|
class ItemData(NamedTuple):
|
||||||
|
@ -33,12 +34,19 @@ class StaticWitnessItems:
|
||||||
|
|
||||||
ALL_ITEM_TABLE: Dict[str, ItemData] = {}
|
ALL_ITEM_TABLE: Dict[str, ItemData] = {}
|
||||||
|
|
||||||
JUNK_WEIGHTS = {
|
# These should always add up to 1!!!
|
||||||
"Speed Boost": 1,
|
BONUS_WEIGHTS = {
|
||||||
"Slowness": 0.8,
|
"Speed Boost": Fraction(1, 1),
|
||||||
"Power Surge": 0.2,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# These should always add up to 1!!!
|
||||||
|
TRAP_WEIGHTS = {
|
||||||
|
"Slowness": Fraction(8, 10),
|
||||||
|
"Power Surge": Fraction(2, 10),
|
||||||
|
}
|
||||||
|
|
||||||
|
ALL_JUNK_ITEMS = set(BONUS_WEIGHTS.keys()) | set(TRAP_WEIGHTS.keys())
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
item_tab = dict()
|
item_tab = dict()
|
||||||
|
|
||||||
|
@ -91,8 +99,28 @@ class WitnessPlayerItems:
|
||||||
self.EVENT_ITEM_TABLE[location] = ItemData(None, True, True)
|
self.EVENT_ITEM_TABLE[location] = ItemData(None, True, True)
|
||||||
self.ITEM_TABLE[location] = ItemData(None, True, True)
|
self.ITEM_TABLE[location] = ItemData(None, True, True)
|
||||||
|
|
||||||
|
trap_percentage = get_option_value(world, player, "trap_percentage")
|
||||||
|
|
||||||
|
self.JUNK_WEIGHTS = dict()
|
||||||
|
|
||||||
|
if trap_percentage != 0:
|
||||||
|
# I'm sure there must be some super "pythonic" way of doing this :D
|
||||||
|
|
||||||
|
for trap_name, trap_weight in StaticWitnessItems.TRAP_WEIGHTS.items():
|
||||||
|
self.JUNK_WEIGHTS[trap_name] = (trap_weight * trap_percentage) / 100
|
||||||
|
|
||||||
|
if trap_percentage != 100:
|
||||||
|
for bonus_name, bonus_weight in StaticWitnessItems.BONUS_WEIGHTS.items():
|
||||||
|
self.JUNK_WEIGHTS[bonus_name] = (bonus_weight * (100 - trap_percentage)) / 100
|
||||||
|
|
||||||
self.JUNK_WEIGHTS = {
|
self.JUNK_WEIGHTS = {
|
||||||
key: value for (key, value)
|
key: value for (key, value)
|
||||||
in StaticWitnessItems.JUNK_WEIGHTS.items()
|
in self.JUNK_WEIGHTS.items()
|
||||||
if key in self.ITEM_TABLE.keys()
|
if key in self.ITEM_TABLE.keys()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# JUNK_WEIGHTS will add up to 1 if the boosts weights and the trap weights each add up to 1 respectively.
|
||||||
|
|
||||||
|
for junk_item in StaticWitnessItems.ALL_JUNK_ITEMS:
|
||||||
|
if junk_item not in self.JUNK_WEIGHTS.keys():
|
||||||
|
del self.ITEM_TABLE[junk_item]
|
||||||
|
|
|
@ -1,5 +1,45 @@
|
||||||
import os
|
import os
|
||||||
from Utils import cache_argsless
|
from Utils import cache_argsless
|
||||||
|
from itertools import accumulate
|
||||||
|
from typing import *
|
||||||
|
from fractions import Fraction
|
||||||
|
|
||||||
|
|
||||||
|
def best_junk_to_add_based_on_weights(weights: Dict[Any, Fraction], created_junk: Dict[Any, int]):
|
||||||
|
min_error = ("", 2)
|
||||||
|
|
||||||
|
for junk_name, instances in created_junk.items():
|
||||||
|
new_dist = created_junk.copy()
|
||||||
|
new_dist[junk_name] += 1
|
||||||
|
new_dist_length = sum(new_dist.values())
|
||||||
|
new_dist = {key: Fraction(value/1)/new_dist_length for key, value in new_dist.items()}
|
||||||
|
|
||||||
|
errors = {key: abs(new_dist[key] - weights[key]) for key in created_junk.keys()}
|
||||||
|
|
||||||
|
new_min_error = max(errors.values())
|
||||||
|
|
||||||
|
if min_error[1] > new_min_error:
|
||||||
|
min_error = (junk_name, new_min_error)
|
||||||
|
|
||||||
|
return min_error[0]
|
||||||
|
|
||||||
|
|
||||||
|
def weighted_list(weights: Dict[Any, Fraction], length):
|
||||||
|
"""
|
||||||
|
Example:
|
||||||
|
weights = {A: 0.3, B: 0.3, C: 0.4}
|
||||||
|
length = 10
|
||||||
|
|
||||||
|
returns: [A, A, A, B, B, B, C, C, C, C]
|
||||||
|
|
||||||
|
Makes sure to match length *exactly*, might approximate as a result
|
||||||
|
"""
|
||||||
|
vals = accumulate(map(lambda x: x * length, weights.values()), lambda x, y: x + y)
|
||||||
|
output_list = []
|
||||||
|
for k, v in zip(weights.keys(), vals):
|
||||||
|
while len(output_list) < v:
|
||||||
|
output_list.append(k)
|
||||||
|
return output_list
|
||||||
|
|
||||||
|
|
||||||
def define_new_region(region_string):
|
def define_new_region(region_string):
|
||||||
|
@ -55,4 +95,4 @@ def get_disable_unrandomized_list():
|
||||||
path = os.path.join(os.path.dirname(__file__), adjustment_file)
|
path = os.path.join(os.path.dirname(__file__), adjustment_file)
|
||||||
|
|
||||||
with open(path) as f:
|
with open(path) as f:
|
||||||
return [line.strip() for line in f.readlines()]
|
return [line.strip() for line in f.readlines()]
|
||||||
|
|
Loading…
Reference in New Issue