forked from ArchipelagoMW/Archipelago
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path__init__.py
234 lines (186 loc) · 9.08 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
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(Regions.regions)
# 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}