389 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			389 lines
		
	
	
		
			10 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 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 ZillionEarlyScope(Toggle):
 | 
						|
    """ make sure Scope is available early """
 | 
						|
    display_name = "early scope"
 | 
						|
 | 
						|
 | 
						|
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,
 | 
						|
    "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,
 | 
						|
    "early_scope": ZillionEarlyScope,
 | 
						|
    "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.current_key
 | 
						|
    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.current_key
 | 
						|
    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
 | 
						|
 | 
						|
    name_capitalization = {
 | 
						|
        "jj": "JJ",
 | 
						|
        "apple": "Apple",
 | 
						|
        "champ": "Champ",
 | 
						|
    }
 | 
						|
 | 
						|
    start_char = cast(ZillionStartChar, wo.start_char[p])
 | 
						|
    start_char_name = name_capitalization[start_char.current_key]
 | 
						|
    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])
 | 
						|
 | 
						|
    early_scope = cast(ZillionEarlyScope, wo.early_scope[p])
 | 
						|
    if early_scope:
 | 
						|
        world.early_items[p]["Scope"] = 1
 | 
						|
 | 
						|
    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,  # early scope is done with AP early_items API
 | 
						|
        True,  # balance defense
 | 
						|
        starting_cards.value,
 | 
						|
        bool(room_gen.value)
 | 
						|
    )
 | 
						|
    zz_validate(zz_op)
 | 
						|
    return zz_op, item_counts
 |