* Option RangeWithSpecialMax * amendment to typing in web options * compare string with number * lots of work on zillion * fix zillion fill logic * fix a few more issues in zillion fill logic * can make zillion patch and use it * put multi items in zillion rom * work on ZillionClient * logging and auth in client * work on sending and receiving items * implement item_handling flag * fix locations ids to NuktiServer package * use rewrite of zri * cache logic rule data for performance * use new id maps * fix some problems with the big recent merge * ZillionClient: use new context manager for Memory class * fix ItemClassification for Zillion items and some debug statements for asserts, documentation on running scripts for manual testing type correction in CommonContext * fix some issues in client, start on docs, put rescue and item ram addresses in slot data * use new location name system fix item locations getting out of sync in progression balancing * zillion client can read slot name from game * zillion: new item names * remove extra unneeded import * newer options (room gen and starting cards) * update comment in zillion patch * zillion non static regions * change some logging, update some comments * allow ZillionClient to exit in certain situations * todo note to fix options doc strings * don't force auto forfeit * rework validation of floppy requirement and item counts and fix race condition in generate_output * reorganize Zillion component structure with System class * documentation updates for Zillion * attempt inno_setup.iss * remove todo comment for something done * update comment * rework item count zillion options and some small cleanups * fix location check count * data package version 1 * Zillion can pass unit tests without rom * fix freeze if closing ZillionClient while it's waiting for server login * specify commit hash for zilliandomizer package * some changes to options validation * Zillion doors saved on multiworld server * add missing function in inno_setup and name of vanilla continues in options * rework zillion sync task and context * Apply documentation suggestions from SoldierofOrder Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com> * update zillion package * workaround for asyncio udp bug There is a bug in Python in Windows https://github.com/python/cpython/issues/91227 that makes it so if I look for RetroArch before it's ready, it breaks the asyncio udp transport system. As a workaround, we don't look for RetroArch until the user asks for it with /sms * a few of the smaller suggestions from review * logic only looks at my locations instead of all the multiworld locations * some adjustments from pull request discussion and some unit tests * patch webhost changes from pull request discussion * zillion logic tests * better vblr test * test interaction of character rescue items with logic * move unit tests to new worlds folder * comment improvements * fix minor logic issue and add memory read timeout * capitalization in option display names Opa-Opa is a proper noun * redirect zz stdout to debug * fix option validation bug making unbeatable seeds * remove line that does nothing * attach logic cache to world Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com> Co-authored-by: Doug Hoskisson <doughoskisson@novuslabs.com>
		
			
				
	
	
		
			381 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			381 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
from collections import Counter
 | 
						|
# import logging
 | 
						|
from typing import TYPE_CHECKING, Any, Dict, Tuple, cast
 | 
						|
from Options import AssembleOptions, DefaultOnToggle, Range, SpecialRange, Toggle, Choice
 | 
						|
from zilliandomizer.options import \
 | 
						|
    Options as ZzOptions, char_to_gun, char_to_jump, ID, \
 | 
						|
    VBLR as ZzVBLR, chars, Chars, ItemCounts as ZzItemCounts
 | 
						|
from zilliandomizer.options.parsing import validate as zz_validate
 | 
						|
if TYPE_CHECKING:
 | 
						|
    from BaseClasses import MultiWorld
 | 
						|
 | 
						|
 | 
						|
class ZillionContinues(SpecialRange):
 | 
						|
    """
 | 
						|
    number of continues before game over
 | 
						|
 | 
						|
    game over teleports you to your ship, keeping items and open doors
 | 
						|
    """
 | 
						|
    default = 3
 | 
						|
    range_start = 0
 | 
						|
    range_end = 21
 | 
						|
    display_name = "continues"
 | 
						|
    special_range_names = {
 | 
						|
        "vanilla": 3,
 | 
						|
        "infinity": 21
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
class ZillionEarlyScope(Toggle):
 | 
						|
    """ whether to make sure there is a scope available early """
 | 
						|
    display_name = "early scope"
 | 
						|
 | 
						|
 | 
						|
class ZillionFloppyReq(Range):
 | 
						|
    """ how many floppy disks are required """
 | 
						|
    range_start = 0
 | 
						|
    range_end = 8
 | 
						|
    default = 5
 | 
						|
    display_name = "floppies required"
 | 
						|
 | 
						|
 | 
						|
class VBLR(Choice):
 | 
						|
    option_vanilla = 0
 | 
						|
    option_balanced = 1
 | 
						|
    option_low = 2
 | 
						|
    option_restrictive = 3
 | 
						|
    default = 1
 | 
						|
 | 
						|
 | 
						|
class ZillionGunLevels(VBLR):
 | 
						|
    """
 | 
						|
    Zillion gun power for the number of Zillion power ups you pick up
 | 
						|
 | 
						|
    For "restrictive", Champ is the only one that can get Zillion gun power level 3.
 | 
						|
    """
 | 
						|
    display_name = "gun levels"
 | 
						|
 | 
						|
 | 
						|
class ZillionJumpLevels(VBLR):
 | 
						|
    """
 | 
						|
    jump levels for each character level
 | 
						|
 | 
						|
    For "restrictive", Apple is the only one that can get jump level 3.
 | 
						|
    """
 | 
						|
    display_name = "jump levels"
 | 
						|
 | 
						|
 | 
						|
class ZillionRandomizeAlarms(DefaultOnToggle):
 | 
						|
    """ whether to randomize the locations of alarm sensors """
 | 
						|
    display_name = "randomize alarms"
 | 
						|
 | 
						|
 | 
						|
class ZillionMaxLevel(Range):
 | 
						|
    """ the highest level you can get """
 | 
						|
    range_start = 3
 | 
						|
    range_end = 8
 | 
						|
    default = 8
 | 
						|
    display_name = "max level"
 | 
						|
 | 
						|
 | 
						|
class ZillionOpasPerLevel(Range):
 | 
						|
    """
 | 
						|
    how many Opa-Opas are required to level up
 | 
						|
 | 
						|
    Lower makes you level up faster.
 | 
						|
    """
 | 
						|
    range_start = 1
 | 
						|
    range_end = 5
 | 
						|
    default = 2
 | 
						|
    display_name = "Opa-Opas per level"
 | 
						|
 | 
						|
 | 
						|
class ZillionStartChar(Choice):
 | 
						|
    """ which character you start with """
 | 
						|
    option_jj = 0
 | 
						|
    option_apple = 1
 | 
						|
    option_champ = 2
 | 
						|
    display_name = "start character"
 | 
						|
    default = "random"
 | 
						|
 | 
						|
 | 
						|
class ZillionIDCardCount(Range):
 | 
						|
    """
 | 
						|
    how many ID Cards are in the game
 | 
						|
 | 
						|
    Vanilla is 63
 | 
						|
 | 
						|
    maximum total for all items is 144
 | 
						|
    """
 | 
						|
    range_start = 0
 | 
						|
    range_end = 126
 | 
						|
    default = 42
 | 
						|
    display_name = "ID Card count"
 | 
						|
 | 
						|
 | 
						|
class ZillionBreadCount(Range):
 | 
						|
    """
 | 
						|
    how many Breads are in the game
 | 
						|
 | 
						|
    Vanilla is 33
 | 
						|
 | 
						|
    maximum total for all items is 144
 | 
						|
    """
 | 
						|
    range_start = 0
 | 
						|
    range_end = 126
 | 
						|
    default = 50
 | 
						|
    display_name = "Bread count"
 | 
						|
 | 
						|
 | 
						|
class ZillionOpaOpaCount(Range):
 | 
						|
    """
 | 
						|
    how many Opa-Opas are in the game
 | 
						|
 | 
						|
    Vanilla is 26
 | 
						|
 | 
						|
    maximum total for all items is 144
 | 
						|
    """
 | 
						|
    range_start = 0
 | 
						|
    range_end = 126
 | 
						|
    default = 26
 | 
						|
    display_name = "Opa-Opa count"
 | 
						|
 | 
						|
 | 
						|
class ZillionZillionCount(Range):
 | 
						|
    """
 | 
						|
    how many Zillion gun power ups are in the game
 | 
						|
 | 
						|
    Vanilla is 6
 | 
						|
 | 
						|
    maximum total for all items is 144
 | 
						|
    """
 | 
						|
    range_start = 0
 | 
						|
    range_end = 126
 | 
						|
    default = 8
 | 
						|
    display_name = "Zillion power up count"
 | 
						|
 | 
						|
 | 
						|
class ZillionFloppyDiskCount(Range):
 | 
						|
    """
 | 
						|
    how many Floppy Disks are in the game
 | 
						|
 | 
						|
    Vanilla is 5
 | 
						|
 | 
						|
    maximum total for all items is 144
 | 
						|
    """
 | 
						|
    range_start = 0
 | 
						|
    range_end = 126
 | 
						|
    default = 7
 | 
						|
    display_name = "Floppy Disk count"
 | 
						|
 | 
						|
 | 
						|
class ZillionScopeCount(Range):
 | 
						|
    """
 | 
						|
    how many Scopes are in the game
 | 
						|
 | 
						|
    Vanilla is 4
 | 
						|
 | 
						|
    maximum total for all items is 144
 | 
						|
    """
 | 
						|
    range_start = 0
 | 
						|
    range_end = 126
 | 
						|
    default = 4
 | 
						|
    display_name = "Scope count"
 | 
						|
 | 
						|
 | 
						|
class ZillionRedIDCardCount(Range):
 | 
						|
    """
 | 
						|
    how many Red ID Cards are in the game
 | 
						|
 | 
						|
    Vanilla is 1
 | 
						|
 | 
						|
    maximum total for all items is 144
 | 
						|
    """
 | 
						|
    range_start = 0
 | 
						|
    range_end = 126
 | 
						|
    default = 2
 | 
						|
    display_name = "Red ID Card count"
 | 
						|
 | 
						|
 | 
						|
class ZillionSkill(Range):
 | 
						|
    """ the difficulty level of the game """
 | 
						|
    range_start = 0
 | 
						|
    range_end = 5
 | 
						|
    default = 2
 | 
						|
 | 
						|
 | 
						|
class ZillionStartingCards(SpecialRange):
 | 
						|
    """
 | 
						|
    how many ID Cards to start the game with
 | 
						|
 | 
						|
    Refilling at the ship also ensures you have at least this many cards.
 | 
						|
    0 gives vanilla behavior.
 | 
						|
    """
 | 
						|
    default = 2
 | 
						|
    range_start = 0
 | 
						|
    range_end = 10
 | 
						|
    display_name = "starting cards"
 | 
						|
    special_range_names = {
 | 
						|
        "vanilla": 0
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
class ZillionRoomGen(Toggle):
 | 
						|
    """ whether to generate rooms with random terrain """
 | 
						|
    display_name = "room generation"
 | 
						|
 | 
						|
 | 
						|
zillion_options: Dict[str, AssembleOptions] = {
 | 
						|
    "continues": ZillionContinues,
 | 
						|
    # "early_scope": ZillionEarlyScope,  # TODO: implement
 | 
						|
    "floppy_req": ZillionFloppyReq,
 | 
						|
    "gun_levels": ZillionGunLevels,
 | 
						|
    "jump_levels": ZillionJumpLevels,
 | 
						|
    "randomize_alarms": ZillionRandomizeAlarms,
 | 
						|
    "max_level": ZillionMaxLevel,
 | 
						|
    "start_char": ZillionStartChar,
 | 
						|
    "opas_per_level": ZillionOpasPerLevel,
 | 
						|
    "id_card_count": ZillionIDCardCount,
 | 
						|
    "bread_count": ZillionBreadCount,
 | 
						|
    "opa_opa_count": ZillionOpaOpaCount,
 | 
						|
    "zillion_count": ZillionZillionCount,
 | 
						|
    "floppy_disk_count": ZillionFloppyDiskCount,
 | 
						|
    "scope_count": ZillionScopeCount,
 | 
						|
    "red_id_card_count": ZillionRedIDCardCount,
 | 
						|
    "skill": ZillionSkill,
 | 
						|
    "starting_cards": ZillionStartingCards,
 | 
						|
    "room_gen": ZillionRoomGen,
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
def convert_item_counts(ic: "Counter[str]") -> ZzItemCounts:
 | 
						|
    tr: ZzItemCounts = {
 | 
						|
        ID.card: ic["ID Card"],
 | 
						|
        ID.red: ic["Red ID Card"],
 | 
						|
        ID.floppy: ic["Floppy Disk"],
 | 
						|
        ID.bread: ic["Bread"],
 | 
						|
        ID.gun: ic["Zillion"],
 | 
						|
        ID.opa: ic["Opa-Opa"],
 | 
						|
        ID.scope: ic["Scope"],
 | 
						|
        ID.empty: ic["Empty"],
 | 
						|
    }
 | 
						|
    return tr
 | 
						|
 | 
						|
 | 
						|
def validate(world: "MultiWorld", p: int) -> "Tuple[ZzOptions, Counter[str]]":
 | 
						|
    """
 | 
						|
    adjusts options to make game completion possible
 | 
						|
 | 
						|
    `world` parameter is MultiWorld object that has my options on it
 | 
						|
    `p` is my player id
 | 
						|
    """
 | 
						|
    for option_name in zillion_options:
 | 
						|
        assert hasattr(world, option_name), f"Zillion option {option_name} didn't get put in world object"
 | 
						|
    wo = cast(Any, world)  # so I don't need getattr on all the options
 | 
						|
 | 
						|
    skill = wo.skill[p].value
 | 
						|
 | 
						|
    jump_levels = cast(ZillionJumpLevels, wo.jump_levels[p])
 | 
						|
    jump_option = jump_levels.get_current_option_name().lower()
 | 
						|
    required_level = char_to_jump["Apple"][cast(ZzVBLR, jump_option)].index(3) + 1
 | 
						|
    if skill == 0:
 | 
						|
        # because of hp logic on final boss
 | 
						|
        required_level = 8
 | 
						|
 | 
						|
    gun_levels = cast(ZillionGunLevels, wo.gun_levels[p])
 | 
						|
    gun_option = gun_levels.get_current_option_name().lower()
 | 
						|
    guns_required = char_to_gun["Champ"][cast(ZzVBLR, gun_option)].index(3)
 | 
						|
 | 
						|
    floppy_req = cast(ZillionFloppyReq, wo.floppy_req[p])
 | 
						|
 | 
						|
    card = cast(ZillionIDCardCount, wo.id_card_count[p])
 | 
						|
    bread = cast(ZillionBreadCount, wo.bread_count[p])
 | 
						|
    opa = cast(ZillionOpaOpaCount, wo.opa_opa_count[p])
 | 
						|
    gun = cast(ZillionZillionCount, wo.zillion_count[p])
 | 
						|
    floppy = cast(ZillionFloppyDiskCount, wo.floppy_disk_count[p])
 | 
						|
    scope = cast(ZillionScopeCount, wo.scope_count[p])
 | 
						|
    red = cast(ZillionRedIDCardCount, wo.red_id_card_count[p])
 | 
						|
    item_counts = Counter({
 | 
						|
        "ID Card": card,
 | 
						|
        "Bread": bread,
 | 
						|
        "Opa-Opa": opa,
 | 
						|
        "Zillion": gun,
 | 
						|
        "Floppy Disk": floppy,
 | 
						|
        "Scope": scope,
 | 
						|
        "Red ID Card": red
 | 
						|
    })
 | 
						|
    minimums = Counter({
 | 
						|
        "ID Card": 0,
 | 
						|
        "Bread": 0,
 | 
						|
        "Opa-Opa": required_level - 1,
 | 
						|
        "Zillion": guns_required,
 | 
						|
        "Floppy Disk": floppy_req.value,
 | 
						|
        "Scope": 0,
 | 
						|
        "Red ID Card": 1
 | 
						|
    })
 | 
						|
    for key in minimums:
 | 
						|
        item_counts[key] = max(minimums[key], item_counts[key])
 | 
						|
    max_movables = 144 - sum(minimums.values())
 | 
						|
    movables = item_counts - minimums
 | 
						|
    while sum(movables.values()) > max_movables:
 | 
						|
        # logging.warning("zillion options validate: player options item counts too high")
 | 
						|
        total = sum(movables.values())
 | 
						|
        scaler = max_movables / total
 | 
						|
        for key in movables:
 | 
						|
            movables[key] = int(movables[key] * scaler)
 | 
						|
    item_counts = movables + minimums
 | 
						|
 | 
						|
    # now have required items, and <= 144
 | 
						|
 | 
						|
    # now fill remaining with empty
 | 
						|
    total = sum(item_counts.values())
 | 
						|
    diff = 144 - total
 | 
						|
    if "Empty" not in item_counts:
 | 
						|
        item_counts["Empty"] = 0
 | 
						|
    item_counts["Empty"] += diff
 | 
						|
    assert sum(item_counts.values()) == 144
 | 
						|
 | 
						|
    max_level = cast(ZillionMaxLevel, wo.max_level[p])
 | 
						|
    max_level.value = max(required_level, max_level.value)
 | 
						|
 | 
						|
    opas_per_level = cast(ZillionOpasPerLevel, wo.opas_per_level[p])
 | 
						|
    while (opas_per_level.value > 1) and (1 + item_counts["Opa-Opa"] // opas_per_level.value < max_level.value):
 | 
						|
        # logging.warning(
 | 
						|
        #     "zillion options validate: option opas_per_level incompatible with options max_level and opa_opa_count"
 | 
						|
        # )
 | 
						|
        opas_per_level.value -= 1
 | 
						|
 | 
						|
    # that should be all of the level requirements met
 | 
						|
 | 
						|
    start_char = cast(ZillionStartChar, wo.start_char[p])
 | 
						|
    start_char_name = start_char.get_current_option_name()
 | 
						|
    if start_char_name == "Jj":
 | 
						|
        start_char_name = "JJ"
 | 
						|
    assert start_char_name in chars
 | 
						|
    start_char_name = cast(Chars, start_char_name)
 | 
						|
 | 
						|
    starting_cards = cast(ZillionStartingCards, wo.starting_cards[p])
 | 
						|
 | 
						|
    room_gen = cast(ZillionRoomGen, wo.room_gen[p])
 | 
						|
 | 
						|
    zz_item_counts = convert_item_counts(item_counts)
 | 
						|
    zz_op = ZzOptions(
 | 
						|
        zz_item_counts,
 | 
						|
        cast(ZzVBLR, jump_option),
 | 
						|
        cast(ZzVBLR, gun_option),
 | 
						|
        opas_per_level.value,
 | 
						|
        max_level.value,
 | 
						|
        False,  # tutorial
 | 
						|
        skill,
 | 
						|
        start_char_name,
 | 
						|
        floppy_req.value,
 | 
						|
        wo.continues[p].value,
 | 
						|
        wo.randomize_alarms[p].value,
 | 
						|
        False,  # wo.early_scope[p].value,
 | 
						|
        True,  # balance defense
 | 
						|
        starting_cards.value,
 | 
						|
        bool(room_gen.value)
 | 
						|
    )
 | 
						|
    zz_validate(zz_op)
 | 
						|
    return zz_op, item_counts
 |