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
 | 
