forked from ArchipelagoMW/Archipelago
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
DLCQuest : implement new game (ArchipelagoMW#1628)
adding DLC Quest as a new game
- Loading branch information
Showing
12 changed files
with
1,305 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import csv | ||
import enum | ||
import math | ||
from typing import Protocol, Union, Dict, List | ||
from BaseClasses import Item, ItemClassification | ||
from . import Options, data | ||
from dataclasses import dataclass, field | ||
from random import Random | ||
|
||
|
||
class DLCQuestItem(Item): | ||
game: str = "DLCQuest" | ||
|
||
|
||
offset = 120_000 | ||
|
||
|
||
class Group(enum.Enum): | ||
DLC = enum.auto() | ||
DLCQuest = enum.auto() | ||
Freemium = enum.auto() | ||
Item = enum.auto() | ||
Coin = enum.auto() | ||
Trap = enum.auto() | ||
|
||
|
||
@dataclass(frozen=True) | ||
class ItemData: | ||
code_without_offset: offset | ||
name: str | ||
classification: ItemClassification | ||
groups: set[Group] = field(default_factory=frozenset) | ||
|
||
def __post_init__(self): | ||
if not isinstance(self.groups, frozenset): | ||
super().__setattr__("groups", frozenset(self.groups)) | ||
|
||
@property | ||
def code(self): | ||
return offset + self.code_without_offset if self.code_without_offset is not None else None | ||
|
||
def has_any_group(self, *group: Group) -> bool: | ||
groups = set(group) | ||
return bool(groups.intersection(self.groups)) | ||
|
||
|
||
def load_item_csv(): | ||
try: | ||
from importlib.resources import files | ||
except ImportError: | ||
from importlib_resources import files # noqa | ||
|
||
items = [] | ||
with files(data).joinpath("items.csv").open() as file: | ||
item_reader = csv.DictReader(file) | ||
for item in item_reader: | ||
id = int(item["id"]) if item["id"] else None | ||
classification = ItemClassification[item["classification"]] | ||
groups = {Group[group] for group in item["groups"].split(",") if group} | ||
items.append(ItemData(id, item["name"], classification, groups)) | ||
return items | ||
|
||
|
||
all_items: List[ItemData] = load_item_csv() | ||
item_table: Dict[str, ItemData] = {} | ||
items_by_group: Dict[Group, List[ItemData]] = {} | ||
|
||
|
||
def initialize_item_table(): | ||
item_table.update({item.name: item for item in all_items}) | ||
|
||
|
||
def initialize_groups(): | ||
for item in all_items: | ||
for group in item.groups: | ||
item_group = items_by_group.get(group, list()) | ||
item_group.append(item) | ||
items_by_group[group] = item_group | ||
|
||
|
||
initialize_item_table() | ||
initialize_groups() | ||
|
||
|
||
def create_trap_items(world, World_Options: Options.DLCQuestOptions, trap_needed: int, random: Random) -> List[Item]: | ||
traps = [] | ||
for i in range(trap_needed): | ||
trap = random.choice(items_by_group[Group.Trap]) | ||
traps.append(world.create_item(trap)) | ||
|
||
return traps | ||
|
||
|
||
def create_items(world, World_Options: Options.DLCQuestOptions, locations_count: int, random: Random): | ||
created_items = [] | ||
if World_Options[Options.Campaign] == Options.Campaign.option_basic or World_Options[ | ||
Options.Campaign] == Options.Campaign.option_both: | ||
for item in items_by_group[Group.DLCQuest]: | ||
if item.has_any_group(Group.DLC): | ||
created_items.append(world.create_item(item)) | ||
if item.has_any_group(Group.Item) and World_Options[ | ||
Options.ItemShuffle] == Options.ItemShuffle.option_shuffled: | ||
created_items.append(world.create_item(item)) | ||
if World_Options[Options.CoinSanity] == Options.CoinSanity.option_coin: | ||
coin_bundle_needed = math.floor(825 / World_Options[Options.CoinSanityRange]) | ||
for item in items_by_group[Group.DLCQuest]: | ||
if item.has_any_group(Group.Coin): | ||
for i in range(coin_bundle_needed): | ||
created_items.append(world.create_item(item)) | ||
if 825 % World_Options[Options.CoinSanityRange] != 0: | ||
created_items.append(world.create_item(item)) | ||
|
||
if World_Options[Options.Campaign] == Options.Campaign.option_live_freemium_or_die or World_Options[ | ||
Options.Campaign] == Options.Campaign.option_both: | ||
for item in items_by_group[Group.Freemium]: | ||
if item.has_any_group(Group.DLC): | ||
created_items.append(world.create_item(item)) | ||
if item.has_any_group(Group.Item) and World_Options[ | ||
Options.ItemShuffle] == Options.ItemShuffle.option_shuffled: | ||
created_items.append(world.create_item(item)) | ||
if World_Options[Options.CoinSanity] == Options.CoinSanity.option_coin: | ||
coin_bundle_needed = math.floor(889 / World_Options[Options.CoinSanityRange]) | ||
for item in items_by_group[Group.Freemium]: | ||
if item.has_any_group(Group.Coin): | ||
for i in range(coin_bundle_needed): | ||
created_items.append(world.create_item(item)) | ||
if 889 % World_Options[Options.CoinSanityRange] != 0: | ||
created_items.append(world.create_item(item)) | ||
|
||
trap_items = create_trap_items(world, World_Options, locations_count - len(created_items), random) | ||
created_items += trap_items | ||
|
||
return created_items |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
from BaseClasses import Location, MultiWorld | ||
from . import Options | ||
|
||
|
||
class DLCQuestLocation(Location): | ||
game: str = "DLCQuest" | ||
|
||
|
||
offset = 120_000 | ||
|
||
location_table = { | ||
"Movement Pack": offset + 0, | ||
"Animation Pack": offset + 1, | ||
"Audio Pack": offset + 2, | ||
"Pause Menu Pack": offset + 3, | ||
"Time is Money Pack": offset + 4, | ||
"Double Jump Pack": offset + 5, | ||
"Pet Pack": offset + 6, | ||
"Sexy Outfits Pack": offset + 7, | ||
"Top Hat Pack": offset + 8, | ||
"Map Pack": offset + 9, | ||
"Gun Pack": offset + 10, | ||
"The Zombie Pack": offset + 11, | ||
"Night Map Pack": offset + 12, | ||
"Psychological Warfare Pack": offset + 13, | ||
"Armor for your Horse Pack": offset + 14, | ||
"Finish the Fight Pack": offset + 15, | ||
"Particles Pack": offset + 16, | ||
"Day One Patch Pack": offset + 17, | ||
"Checkpoint Pack": offset + 18, | ||
"Incredibly Important Pack": offset + 19, | ||
"Wall Jump Pack": offset + 20, | ||
"Health Bar Pack": offset + 21, | ||
"Parallax Pack": offset + 22, | ||
"Harmless Plants Pack": offset + 23, | ||
"Death of Comedy Pack": offset + 24, | ||
"Canadian Dialog Pack": offset + 25, | ||
"DLC NPC Pack": offset + 26, | ||
"Cut Content Pack": offset + 27, | ||
"Name Change Pack": offset + 28, | ||
"Season Pass": offset + 29, | ||
"High Definition Next Gen Pack": offset + 30, | ||
"Increased HP Pack": offset + 31, | ||
"Remove Ads Pack": offset + 32, | ||
"Big Sword Pack": offset + 33, | ||
"Really Big Sword Pack": offset + 34, | ||
"Unfathomable Sword Pack": offset + 35, | ||
"Pickaxe": offset + 36, | ||
"Gun": offset + 37, | ||
"Sword": offset + 38, | ||
"Wooden Sword": offset + 39, | ||
"Box of Various Supplies": offset + 40, | ||
"Humble Indie Bindle": offset + 41, | ||
"Double Jump Alcove Sheep": offset + 42, | ||
"Double Jump Floating Sheep": offset + 43, | ||
"Sexy Outfits Sheep": offset + 44, | ||
"Forest High Sheep": offset + 45, | ||
"Forest Low Sheep": offset + 46, | ||
"Between Trees Sheep": offset + 47, | ||
"Hole in the Wall Sheep": offset + 48, | ||
"Shepherd Sheep": offset + 49, | ||
"Top Hat Sheep": offset + 50, | ||
"North West Ceiling Sheep": offset + 51, | ||
"North West Alcove Sheep": offset + 52, | ||
"West Cave Sheep": offset + 53, | ||
"Cutscene Sheep": offset + 54, | ||
"Not Exactly Noble": offset + 55, | ||
"Story is Important": offset + 56, | ||
"Nice Try": offset + 57, | ||
"I Get That Reference!": offset + 58, | ||
} | ||
|
||
for i in range(1, 826): | ||
item_coin = f"DLC Quest: {i} Coin" | ||
location_table[item_coin] = offset + 58 + i | ||
|
||
for i in range(1, 890): | ||
item_coin_freemium = f"Live Freemium or Die: {i} Coin" | ||
location_table[item_coin_freemium] = offset + 825 + 58 + i |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
from typing import Union, Dict, runtime_checkable, Protocol | ||
from Options import Option, DeathLink, Choice, Toggle, SpecialRange | ||
from dataclasses import dataclass | ||
|
||
|
||
@runtime_checkable | ||
class DLCQuestOption(Protocol): | ||
internal_name: str | ||
|
||
|
||
@dataclass | ||
class DLCQuestOptions: | ||
options: Dict[str, Union[bool, int]] | ||
|
||
def __getitem__(self, item: Union[str, DLCQuestOption]) -> Union[bool, int]: | ||
if isinstance(item, DLCQuestOption): | ||
item = item.internal_name | ||
|
||
return self.options.get(item, None) | ||
|
||
|
||
class FalseDoubleJump(Choice): | ||
"""If you can do a double jump without the pack for it (glitch).""" | ||
internal_name = "double_jump_glitch" | ||
display_name = "Double Jump glitch" | ||
option_none = 0 | ||
option_simple = 1 | ||
option_all = 2 | ||
default = 0 | ||
|
||
|
||
class TimeIsMoney(Choice): | ||
"""Is your time worth the money, are you ready to grind your sword by hand?""" | ||
internal_name = "time_is_money" | ||
display_name = "Time Is Money" | ||
option_required = 0 | ||
option_optional = 1 | ||
default = 0 | ||
|
||
|
||
class CoinSanity(Choice): | ||
"""This is for the insane it can be 825 check, it is coin sanity""" | ||
internal_name = "coinsanity" | ||
display_name = "CoinSanity" | ||
option_none = 0 | ||
option_coin = 1 | ||
default = 0 | ||
|
||
|
||
class CoinSanityRange(SpecialRange): | ||
"""This is the amount of coin in a coin bundle""" | ||
internal_name = "coinbundlequantity" | ||
display_name = "Coin Bundle Quantity" | ||
range_start = 1 | ||
range_end = 100 | ||
default = 20 | ||
|
||
|
||
class EndingChoice(Choice): | ||
"""This is for the ending type of the basic game""" | ||
internal_name = "ending_choice" | ||
display_name = "Ending Choice" | ||
option_any = 0 | ||
option_true = 1 | ||
default = 1 | ||
|
||
|
||
class Campaign(Choice): | ||
"""Whitch game you wana play to end""" | ||
internal_name = "campaign" | ||
display_name = "Campaign" | ||
option_basic = 0 | ||
option_live_freemium_or_die = 1 | ||
option_both = 2 | ||
default = 0 | ||
|
||
|
||
class ItemShuffle(Choice): | ||
"""Should Inventory Items be separate from their DLCs and shuffled in the item pool""" | ||
internal_name = "item_shuffle" | ||
display_name = "Item Shuffle" | ||
option_disabled = 0 | ||
option_shuffled = 1 | ||
default = 0 | ||
|
||
|
||
DLCQuest_options: Dict[str, type(Option)] = { | ||
option.internal_name: option | ||
for option in [ | ||
FalseDoubleJump, | ||
CoinSanity, | ||
CoinSanityRange, | ||
TimeIsMoney, | ||
EndingChoice, | ||
Campaign, | ||
ItemShuffle, | ||
] | ||
} | ||
default_options = {option.internal_name: option.default for option in DLCQuest_options.values()} | ||
DLCQuest_options["death_link"] = DeathLink | ||
|
||
|
||
def fetch_options(world, player: int) -> DLCQuestOptions: | ||
return DLCQuestOptions({option: get_option_value(world, player, option) for option in DLCQuest_options}) | ||
|
||
|
||
def get_option_value(world, player: int, name: str) -> Union[bool, int]: | ||
assert name in DLCQuest_options, f"{name} is not a valid option for DLC Quest." | ||
|
||
value = getattr(world, name) | ||
|
||
if issubclass(DLCQuest_options[name], Toggle): | ||
return bool(value[player].value) | ||
return value[player].value |
Oops, something went wrong.