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 BaseClasses import MultiWorld
 | 
			
		||||
from Options import Toggle, DefaultOnToggle, Option
 | 
			
		||||
from Options import Toggle, DefaultOnToggle, Option, Range
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# class HardMode(Toggle):
 | 
			
		||||
| 
						 | 
				
			
			@ -30,8 +30,8 @@ class ShuffleVaultBoxes(Toggle):
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
class ShuffleUncommonLocations(Toggle):
 | 
			
		||||
    """Adds the following checks to the pool:
 | 
			
		||||
    Mountaintop River Shape, Tutorial Patio Floor, Theater Videos"""
 | 
			
		||||
    """Adds some optional puzzles that are somewhat difficult or out of the way.
 | 
			
		||||
    Examples: Mountaintop River Shape, Tutorial Patio Floor, Theater Videos"""
 | 
			
		||||
    display_name = "Shuffle Uncommon Locations"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -46,6 +46,14 @@ class ChallengeVictoryCondition(Toggle):
 | 
			
		|||
    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] = {
 | 
			
		||||
    # "hard_mode": HardMode,
 | 
			
		||||
    # "unlock_symbols": UnlockSymbols,
 | 
			
		||||
| 
						 | 
				
			
			@ -54,7 +62,8 @@ the_witness_options: Dict[str, Option] = {
 | 
			
		|||
    "shuffle_vault_boxes": ShuffleVaultBoxes,
 | 
			
		||||
    "shuffle_uncommon": ShuffleUncommonLocations,
 | 
			
		||||
    "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:
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
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 .regions import WitnessRegions
 | 
			
		||||
from .Options import is_option_enabled, the_witness_options
 | 
			
		||||
from .utils import best_junk_to_add_based_on_weights
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class WitnessWebWorld(WebWorld):
 | 
			
		||||
| 
						 | 
				
			
			@ -50,6 +51,8 @@ class WitnessWorld(World):
 | 
			
		|||
        self.items = WitnessPlayerItems(self.locat, self.world, self.player, self.player_logic)
 | 
			
		||||
        self.regio = WitnessRegions(self.locat)
 | 
			
		||||
 | 
			
		||||
        self.junk_items_created = {key: 0 for key in self.items.JUNK_WEIGHTS.keys()}
 | 
			
		||||
 | 
			
		||||
    def generate_basic(self):
 | 
			
		||||
        # Generate item pool
 | 
			
		||||
        pool = []
 | 
			
		||||
| 
						 | 
				
			
			@ -69,13 +72,10 @@ class WitnessWorld(World):
 | 
			
		|||
        pool.remove(items_by_name[random_good_item])
 | 
			
		||||
 | 
			
		||||
        # Put in junk items to fill the rest
 | 
			
		||||
        junk_pool = self.items.JUNK_WEIGHTS.copy()
 | 
			
		||||
        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
 | 
			
		||||
        )
 | 
			
		||||
        junk_size = 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)
 | 
			
		||||
        for event_location in self.locat.EVENT_LOCATION_TABLE:
 | 
			
		||||
| 
						 | 
				
			
			@ -118,10 +118,12 @@ class WitnessWorld(World):
 | 
			
		|||
        new_item.trap = item.trap
 | 
			
		||||
        return new_item
 | 
			
		||||
 | 
			
		||||
    def get_filler_item_name(self) -> str:  # Used ny itemlinks
 | 
			
		||||
        junk_pool = self.items.JUNK_WEIGHTS.copy()
 | 
			
		||||
    def get_filler_item_name(self) -> str:  # Used by itemlinks
 | 
			
		||||
        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):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,8 @@ from typing import Dict, NamedTuple, Optional
 | 
			
		|||
 | 
			
		||||
from BaseClasses import Item, MultiWorld
 | 
			
		||||
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):
 | 
			
		||||
| 
						 | 
				
			
			@ -33,12 +34,19 @@ class StaticWitnessItems:
 | 
			
		|||
 | 
			
		||||
    ALL_ITEM_TABLE: Dict[str, ItemData] = {}
 | 
			
		||||
 | 
			
		||||
    JUNK_WEIGHTS = {
 | 
			
		||||
        "Speed Boost": 1,
 | 
			
		||||
        "Slowness": 0.8,
 | 
			
		||||
        "Power Surge": 0.2,
 | 
			
		||||
    # These should always add up to 1!!!
 | 
			
		||||
    BONUS_WEIGHTS = {
 | 
			
		||||
        "Speed Boost": Fraction(1, 1),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # 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):
 | 
			
		||||
        item_tab = dict()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -91,8 +99,28 @@ class WitnessPlayerItems:
 | 
			
		|||
            self.EVENT_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 = {
 | 
			
		||||
            key: value for (key, value)
 | 
			
		||||
            in StaticWitnessItems.JUNK_WEIGHTS.items()
 | 
			
		||||
            in self.JUNK_WEIGHTS.items()
 | 
			
		||||
            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
 | 
			
		||||
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):
 | 
			
		||||
| 
						 | 
				
			
			@ -55,4 +95,4 @@ def get_disable_unrandomized_list():
 | 
			
		|||
    path = os.path.join(os.path.dirname(__file__), adjustment_file)
 | 
			
		||||
 | 
			
		||||
    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