Archipelago/worlds/zillion/options.py

375 lines
9.7 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 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,
"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, # early scope can be done with AP early_items
True, # balance defense
starting_cards.value,
bool(room_gen.value)
)
zz_validate(zz_op)
return zz_op, item_counts