mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
DOOM 1993: implement new game (#1759)
* DOOM 1993: implement new game * DOOM 1993 - Phar's cleanup to __init__.py
This commit is contained in:
234
worlds/doom_1993/__init__.py
Normal file
234
worlds/doom_1993/__init__.py
Normal file
@@ -0,0 +1,234 @@
|
||||
import functools
|
||||
import logging
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from BaseClasses import CollectionState, Item, ItemClassification, Location, MultiWorld, Region, Tutorial
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from . import Events, Items, Locations, Options, Regions, Rules
|
||||
|
||||
logger = logging.getLogger("DOOM 1993")
|
||||
|
||||
|
||||
class DOOM1993Location(Location):
|
||||
game: str = "DOOM 1993"
|
||||
|
||||
|
||||
class DOOM1993Item(Item):
|
||||
game: str = "DOOM 1993"
|
||||
|
||||
|
||||
class DOOM1993Web(WebWorld):
|
||||
tutorials = [Tutorial(
|
||||
"Multiworld Setup Guide",
|
||||
"A guide to setting up the DOOM 1993 randomizer connected to an Archipelago Multiworld",
|
||||
"English",
|
||||
"setup_en.md",
|
||||
"setup/en",
|
||||
["Daivuk"]
|
||||
)]
|
||||
theme = "dirt"
|
||||
|
||||
|
||||
class DOOM1993World(World):
|
||||
"""
|
||||
Developed by id Software, and originally released in 1993, DOOM pioneered and popularized the first-person shooter,
|
||||
setting a standard for all FPS games.
|
||||
"""
|
||||
option_definitions = Options.options
|
||||
game = "DOOM 1993"
|
||||
web = DOOM1993Web()
|
||||
data_version = 1
|
||||
required_client_version = (0, 3, 9)
|
||||
|
||||
item_name_to_id = {data["name"]: item_id for item_id, data in Items.item_table.items()}
|
||||
item_name_groups = Items.item_name_groups
|
||||
|
||||
location_name_to_id = {data["name"]: loc_id for loc_id, data in Locations.location_table.items()}
|
||||
location_name_groups = Locations.location_name_groups
|
||||
|
||||
starting_level_for_episode: List[str] = [
|
||||
"Hangar (E1M1)",
|
||||
"Deimos Anomaly (E2M1)",
|
||||
"Hell Keep (E3M1)"
|
||||
]
|
||||
|
||||
# Item ratio that scales depending on episode count. These are the ratio for 3 episode.
|
||||
items_ratio: Dict[str, float] = {
|
||||
"Armor": 41,
|
||||
"Mega Armor": 25,
|
||||
"Berserk": 12,
|
||||
"Invulnerability": 10,
|
||||
"Partial invisibility": 18,
|
||||
"Supercharge": 28,
|
||||
"Medikit": 15,
|
||||
"Box of bullets": 13,
|
||||
"Box of rockets": 13,
|
||||
"Box of shotgun shells": 13,
|
||||
"Energy cell pack": 10
|
||||
}
|
||||
|
||||
def __init__(self, world: MultiWorld, player: int):
|
||||
self.included_episodes = [1, 1, 1]
|
||||
self.location_count = 0
|
||||
|
||||
super().__init__(world, player)
|
||||
|
||||
def get_episode_count(self):
|
||||
return functools.reduce(lambda count, episode: count + episode, self.included_episodes)
|
||||
|
||||
def generate_early(self):
|
||||
# Cache which episodes are included
|
||||
for i in range(3):
|
||||
self.included_episodes[i] = getattr(self.multiworld, f"episode{i + 1}")[self.player].value
|
||||
|
||||
# If no episodes selected, select Episode 1
|
||||
if self.get_episode_count() == 0:
|
||||
self.included_episodes[0] = 1
|
||||
|
||||
def create_regions(self):
|
||||
# Main regions
|
||||
menu_region = Region("Menu", self.player, self.multiworld)
|
||||
mars_region = Region("Mars", self.player, self.multiworld)
|
||||
self.multiworld.regions += [menu_region, mars_region]
|
||||
menu_region.add_exits(["Mars"])
|
||||
|
||||
# Create regions and locations
|
||||
for region_name in Regions.regions:
|
||||
region = Region(region_name, self.player, self.multiworld)
|
||||
region.add_locations({
|
||||
loc["name"]: (loc_id if loc["index"] != -1 else None)
|
||||
for loc_id, loc in Locations.location_table.items()
|
||||
if loc["region"] == region_name and self.included_episodes[loc["episode"] - 1]
|
||||
}, DOOM1993Location)
|
||||
|
||||
self.multiworld.regions.append(region)
|
||||
|
||||
# Link all regions to Mars
|
||||
mars_region.add_exits(Regions.regions)
|
||||
|
||||
# Sum locations for items creation
|
||||
self.location_count = len(self.multiworld.get_locations(self.player))
|
||||
|
||||
def completion_rule(self, state: CollectionState):
|
||||
for event in Events.events:
|
||||
if event not in self.location_name_to_id:
|
||||
continue
|
||||
loc = Locations.location_table[self.location_name_to_id[event]]
|
||||
if not self.included_episodes[loc["episode"] - 1]:
|
||||
continue
|
||||
if not state.has(event, self.player, 1):
|
||||
return False
|
||||
return True
|
||||
|
||||
def set_rules(self):
|
||||
Rules.set_rules(self)
|
||||
self.multiworld.completion_condition[self.player] = lambda state: self.completion_rule(state)
|
||||
|
||||
# Forbid progression items to locations that can be missed and can't be picked up. (e.g. One-time timed
|
||||
# platform) Unless the user allows for it.
|
||||
if getattr(self.multiworld, "allow_death_logic")[self.player]:
|
||||
self.multiworld.exclude_locations[self.player] += set(Locations.death_logic_locations)
|
||||
|
||||
def create_item(self, name: str) -> DOOM1993Item:
|
||||
item_id: int = self.item_name_to_id[name]
|
||||
return DOOM1993Item(name, Items.item_table[item_id]["classification"], item_id, self.player)
|
||||
|
||||
def create_event(self, name: str) -> DOOM1993Item:
|
||||
return DOOM1993Item(name, ItemClassification.progression, None, self.player)
|
||||
|
||||
def place_locked_item_in_locations(self, item_name, locations):
|
||||
location = self.multiworld.random.choice(locations)
|
||||
self.multiworld.get_location(location, self.player).place_locked_item(self.create_item(item_name))
|
||||
self.location_count -= 1
|
||||
|
||||
def create_items(self):
|
||||
is_only_first_episode: bool = self.get_episode_count() == 1 and self.included_episodes[0]
|
||||
itempool: List[DOOM1993Item] = []
|
||||
|
||||
# Items
|
||||
for item_id, item in Items.item_table.items():
|
||||
if item["episode"] != -1 and not self.included_episodes[item["episode"] - 1]:
|
||||
continue
|
||||
|
||||
if item["name"] in {"BFG9000", "Plasma Gun"} and is_only_first_episode:
|
||||
continue # Don't include those guns in first episode
|
||||
|
||||
if item["name"] in {"Warrens (E3M9) - Blue skull key", "Halls of the Damned (E2M6) - Yellow skull key"}:
|
||||
continue
|
||||
|
||||
count = item["count"] if item["name"] not in self.starting_level_for_episode else item["count"] - 1
|
||||
itempool += [self.create_item(item["name"]) for _ in range(count)]
|
||||
|
||||
# Place end level items in locked locations
|
||||
for event in Events.events:
|
||||
if event not in self.location_name_to_id:
|
||||
continue
|
||||
|
||||
loc = Locations.location_table[self.location_name_to_id[event]]
|
||||
if not self.included_episodes[loc["episode"] - 1]:
|
||||
continue
|
||||
|
||||
self.multiworld.get_location(event, self.player).place_locked_item(self.create_event(event))
|
||||
self.location_count -= 1
|
||||
|
||||
# Special case for E2M6 and E3M8, where you enter a normal door then get stuck behind with a key door.
|
||||
# We need to put the key in the locations available behind this door.
|
||||
if self.included_episodes[1]:
|
||||
self.place_locked_item_in_locations("Halls of the Damned (E2M6) - Yellow skull key", [
|
||||
"Halls of the Damned (E2M6) - Yellow skull key",
|
||||
"Halls of the Damned (E2M6) - Partial invisibility 2"
|
||||
])
|
||||
if self.included_episodes[2]:
|
||||
self.place_locked_item_in_locations("Warrens (E3M9) - Blue skull key", [
|
||||
"Warrens (E3M9) - Rocket launcher",
|
||||
"Warrens (E3M9) - Rocket launcher 2",
|
||||
"Warrens (E3M9) - Partial invisibility",
|
||||
"Warrens (E3M9) - Invulnerability",
|
||||
"Warrens (E3M9) - Supercharge",
|
||||
"Warrens (E3M9) - Berserk",
|
||||
"Warrens (E3M9) - Chaingun"
|
||||
])
|
||||
|
||||
# Give starting levels right away
|
||||
for i in range(len(self.included_episodes)):
|
||||
if self.included_episodes[i]:
|
||||
self.multiworld.push_precollected(self.create_item(self.starting_level_for_episode[i]))
|
||||
|
||||
# Fill the rest starting with weapons, powerups then fillers
|
||||
self.create_ratioed_items("Armor", itempool)
|
||||
self.create_ratioed_items("Mega Armor", itempool)
|
||||
self.create_ratioed_items("Berserk", itempool)
|
||||
self.create_ratioed_items("Invulnerability", itempool)
|
||||
self.create_ratioed_items("Partial invisibility", itempool)
|
||||
self.create_ratioed_items("Supercharge", itempool)
|
||||
|
||||
while len(itempool) < self.location_count:
|
||||
itempool.append(self.create_item(self.get_filler_item_name()))
|
||||
|
||||
# add itempool to multiworld
|
||||
self.multiworld.itempool += itempool
|
||||
|
||||
def get_filler_item_name(self):
|
||||
return self.multiworld.random.choice([
|
||||
"Medikit",
|
||||
"Box of bullets",
|
||||
"Box of rockets",
|
||||
"Box of shotgun shells",
|
||||
"Energy cell pack"
|
||||
])
|
||||
|
||||
def create_ratioed_items(self, item_name: str, itempool: List[DOOM1993Item]):
|
||||
remaining_loc = self.location_count - len(itempool)
|
||||
ep_count = self.get_episode_count()
|
||||
|
||||
# Was balanced for 3 episodes
|
||||
count = min(remaining_loc, max(1, int(round(self.items_ratio[item_name] * ep_count / 3))))
|
||||
if count == 0:
|
||||
logger.warning("Warning, no ", item_name, " will be placed.")
|
||||
return
|
||||
|
||||
for i in range(count):
|
||||
itempool.append(self.create_item(item_name))
|
||||
|
||||
def fill_slot_data(self) -> Dict[str, Any]:
|
||||
return {name: getattr(self.multiworld, name)[self.player].value for name in self.option_definitions}
|
||||
Reference in New Issue
Block a user