Minecraft Randomizer
Squash merge, original Commits: * Minecraft locations, items, and generation without logic * added id lookup for minecraft * typing import fix in minecraft/Items.py * fix 2 * implementing Minecraft options and hard/postgame advancement exclusion * first logic pass (75/80) * logic pass 2 and proper completion conditions * added insane difficulty pool, modified method of excluding item pools for easier extension * bump network_data_package version * minecraft testing framework * switch Ancient Debris to Netherite Scrap to avoid advancement triggering on receiving that item * Testing now functions, split tests up by advancement pane, added some story tests * Newer testing framework: every advancement gets its own function, for ease of testing * fixed logic for The End... Again... * changed option names to "include_hard_advancements" etc. * village/pillager-related advancements now require can_adventure: weapon + food * a few minecraft tests * rename "Flint & Steel" to "Flint and Steel" for parity with in-game name * additional MC tests * more tests, mostly nether-related tests * more tests, removed anvil path for Two Birds One Arrow * include Minecraft slot data, and a world seed for each Minecraft player slot * Added new items: ender pearls, lapis, porkchops * All remaining Minecraft tests * formatting of Minecraft tests and logic for better readability * require Wither kill for Monsters Hunted * properly removed 8 Emeralds item from item pool * enchanting required for wither; fishing rod required for water breathing; water breathing required for elder guardian kill * Added 12 new advancements (ported from old achievement system) * renamed "On a Rail" for consistency with modern advancements * tests for the new advancements * moved slot_data generation for minecraft into worlds/minecraft/__init__.py, added logic_version to slot_data * output minecraft options in the spoiler log * modified advancement goal values for new advancements * make non-native Minecraft items appear as Shovel in ALttP, and unknown-game items as Power Stars * fixed glowstone block logic for Not Quite Nine Lives * setup for shuffling MC structures: building ER world and shuffling regions/entrances * ensured Nether Fortresses can't be placed in the End * finished logic for structure randomization * fixed nonnative items always showing up as Hammers in ALttP shops * output minecraft structure info in the spoiler * generate .apmc file for communication with MC client * fixed structure rando always using the same seed * move stuff to worlds/minecraft/Regions.py * make output apmc file have consistent name with other files * added minecraft bottle macro; fixed tests imports * generalizing MC region generation * restructured structure shuffling in preparation for structure plando * only output structure rando info in spoiler if they are shuffled * Force structure rando to always be off, for the stable release * added Minecraft options to player settings * formally added combat_difficulty as an option * Added Ender Dragon into playthrough, cleaned up goal map * Added new difficulties: Easy, Normal, Hard combat * moved .apmc generation time to prevent outputs on failed generation * updated tests for new combat logic * Fixed bug causing generation to fail; removed Nether Fortress event since it should no longer be needed with the fix * moved all MC-specific functions into gen_minecraft * renamed "logic_version" to "client_version" * bug fixes properly flagged event locations/items with id None moved generation back to Main.py to fix mysterious generation failures * moved link_minecraft_regions into minecraft init, left create_regions in Main for caching * added seed_name, player_name, client_version to apmc file * reenabled structure shuffle * added entrance tests for minecraft Co-authored-by: achuang <alexander.w.chuang@gmail.com>
This commit is contained in:
74
worlds/minecraft/Items.py
Normal file
74
worlds/minecraft/Items.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from BaseClasses import Region, Entrance, Location, MultiWorld, Item
|
||||
import typing
|
||||
|
||||
class ItemData(typing.NamedTuple):
|
||||
code: int
|
||||
progression: bool
|
||||
|
||||
class MinecraftItem(Item):
|
||||
game: str = "Minecraft"
|
||||
def __init__(self, name: str, progression: bool, code: int, player: int):
|
||||
super().__init__(name, progression, code if code else None, player)
|
||||
|
||||
item_table = {
|
||||
"Archery": ItemData(45000, True),
|
||||
"Ingot Crafting": ItemData(45001, True),
|
||||
"Resource Blocks": ItemData(45002, True),
|
||||
"Brewing": ItemData(45003, True),
|
||||
"Enchanting": ItemData(45004, True),
|
||||
"Bucket": ItemData(45005, True),
|
||||
"Flint and Steel": ItemData(45006, True),
|
||||
"Bed": ItemData(45007, True),
|
||||
"Bottles": ItemData(45008, True),
|
||||
"Shield": ItemData(45009, True),
|
||||
"Fishing Rod": ItemData(45010, True),
|
||||
"Campfire": ItemData(45011, True),
|
||||
"Progressive Weapons": ItemData(45012, True),
|
||||
"Progressive Tools": ItemData(45013, True),
|
||||
"Progressive Armor": ItemData(45014, True),
|
||||
"8 Netherite Scrap": ItemData(45015, True),
|
||||
"8 Emeralds": ItemData(45016, False),
|
||||
"4 Emeralds": ItemData(45017, False),
|
||||
"Channeling Book": ItemData(45018, True),
|
||||
"Silk Touch Book": ItemData(45019, True),
|
||||
"Sharpness III Book": ItemData(45020, False),
|
||||
"Piercing IV Book": ItemData(45021, True),
|
||||
"Looting III Book": ItemData(45022, False),
|
||||
"Infinity Book": ItemData(45023, False),
|
||||
"4 Diamond Ore": ItemData(45024, False),
|
||||
"16 Iron Ore": ItemData(45025, False),
|
||||
"500 XP": ItemData(45026, False),
|
||||
"100 XP": ItemData(45027, False),
|
||||
"50 XP": ItemData(45028, False),
|
||||
"3 Ender Pearls": ItemData(45029, True),
|
||||
"4 Lapis Lazuli": ItemData(45030, False),
|
||||
"16 Porkchops": ItemData(45031, False),
|
||||
"8 Gold Ore": ItemData(45032, False),
|
||||
"Rotten Flesh": ItemData(45033, False),
|
||||
"Single Arrow": ItemData(45034, False),
|
||||
|
||||
"Victory": ItemData(0, True)
|
||||
}
|
||||
|
||||
# If not listed here then has frequency 1
|
||||
item_frequencies = {
|
||||
"Progressive Weapons": 3,
|
||||
"Progressive Tools": 3,
|
||||
"Progressive Armor": 2,
|
||||
"8 Netherite Scrap": 2,
|
||||
"8 Emeralds": 0,
|
||||
"4 Emeralds": 8,
|
||||
"4 Diamond Ore": 4,
|
||||
"16 Iron Ore": 4,
|
||||
"500 XP": 4, # 2 after exclusions
|
||||
"100 XP": 10, # 4 after exclusions
|
||||
"50 XP": 12, # 4 after exclusions
|
||||
"3 Ender Pearls": 4,
|
||||
"4 Lapis Lazuli": 2,
|
||||
"16 Porkchops": 8,
|
||||
"8 Gold Ore": 4,
|
||||
"Rotten Flesh": 4,
|
||||
"Single Arrow": 0
|
||||
}
|
||||
|
||||
lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in item_table.items() if data.code}
|
||||
142
worlds/minecraft/Locations.py
Normal file
142
worlds/minecraft/Locations.py
Normal file
@@ -0,0 +1,142 @@
|
||||
from BaseClasses import Region, Entrance, Location, MultiWorld, Item
|
||||
import typing
|
||||
|
||||
class AdvData(typing.NamedTuple):
|
||||
id: int
|
||||
region: str
|
||||
|
||||
class MinecraftAdvancement(Location):
|
||||
game: str = "Minecraft"
|
||||
def __init__(self, player: int, name: str, address: int, parent):
|
||||
super().__init__(player, name, address if address else None, parent)
|
||||
self.event = True if address == 0 else False
|
||||
|
||||
advancement_table = {
|
||||
"Who is Cutting Onions?": AdvData(42000, 'Overworld'),
|
||||
"Oh Shiny": AdvData(42001, 'Overworld'),
|
||||
"Suit Up": AdvData(42002, 'Overworld'),
|
||||
"Very Very Frightening": AdvData(42003, 'Village'),
|
||||
"Hot Stuff": AdvData(42004, 'Overworld'),
|
||||
"Free the End": AdvData(42005, 'The End'),
|
||||
"A Furious Cocktail": AdvData(42006, 'Nether Fortress'),
|
||||
"Best Friends Forever": AdvData(42007, 'Overworld'),
|
||||
"Bring Home the Beacon": AdvData(42008, 'Nether Fortress'),
|
||||
"Not Today, Thank You": AdvData(42009, 'Overworld'),
|
||||
"Isn't It Iron Pick": AdvData(42010, 'Overworld'),
|
||||
"Local Brewery": AdvData(42011, 'Nether Fortress'),
|
||||
"The Next Generation": AdvData(42012, 'The End'),
|
||||
"Fishy Business": AdvData(42013, 'Overworld'),
|
||||
"Hot Tourist Destinations": AdvData(42014, 'The Nether'),
|
||||
"This Boat Has Legs": AdvData(42015, 'The Nether'),
|
||||
"Sniper Duel": AdvData(42016, 'Overworld'),
|
||||
"Nether": AdvData(42017, 'The Nether'),
|
||||
"Great View From Up Here": AdvData(42018, 'End City'),
|
||||
"How Did We Get Here?": AdvData(42019, 'Nether Fortress'),
|
||||
"Bullseye": AdvData(42020, 'Overworld'),
|
||||
"Spooky Scary Skeleton": AdvData(42021, 'Nether Fortress'),
|
||||
"Two by Two": AdvData(42022, 'The Nether'),
|
||||
"Stone Age": AdvData(42023, 'Overworld'),
|
||||
"Two Birds, One Arrow": AdvData(42024, 'Overworld'),
|
||||
"We Need to Go Deeper": AdvData(42025, 'The Nether'),
|
||||
"Who's the Pillager Now?": AdvData(42026, 'Pillager Outpost'),
|
||||
"Getting an Upgrade": AdvData(42027, 'Overworld'),
|
||||
"Tactical Fishing": AdvData(42028, 'Overworld'),
|
||||
"Zombie Doctor": AdvData(42029, 'Overworld'),
|
||||
"The City at the End of the Game": AdvData(42030, 'End City'),
|
||||
"Ice Bucket Challenge": AdvData(42031, 'Overworld'),
|
||||
"Remote Getaway": AdvData(42032, 'The End'),
|
||||
"Into Fire": AdvData(42033, 'Nether Fortress'),
|
||||
"War Pigs": AdvData(42034, 'Bastion Remnant'),
|
||||
"Take Aim": AdvData(42035, 'Overworld'),
|
||||
"Total Beelocation": AdvData(42036, 'Overworld'),
|
||||
"Arbalistic": AdvData(42037, 'Overworld'),
|
||||
"The End... Again...": AdvData(42038, 'The End'),
|
||||
"Acquire Hardware": AdvData(42039, 'Overworld'),
|
||||
"Not Quite \"Nine\" Lives": AdvData(42040, 'The Nether'),
|
||||
"Cover Me With Diamonds": AdvData(42041, 'Overworld'),
|
||||
"Sky's the Limit": AdvData(42042, 'End City'),
|
||||
"Hired Help": AdvData(42043, 'Overworld'),
|
||||
"Return to Sender": AdvData(42044, 'The Nether'),
|
||||
"Sweet Dreams": AdvData(42045, 'Overworld'),
|
||||
"You Need a Mint": AdvData(42046, 'The End'),
|
||||
"Adventure": AdvData(42047, 'Overworld'),
|
||||
"Monsters Hunted": AdvData(42048, 'Overworld'),
|
||||
"Enchanter": AdvData(42049, 'Overworld'),
|
||||
"Voluntary Exile": AdvData(42050, 'Pillager Outpost'),
|
||||
"Eye Spy": AdvData(42051, 'Overworld'),
|
||||
"The End": AdvData(42052, 'The End'),
|
||||
"Serious Dedication": AdvData(42053, 'The Nether'),
|
||||
"Postmortal": AdvData(42054, 'Village'),
|
||||
"Monster Hunter": AdvData(42055, 'Overworld'),
|
||||
"Adventuring Time": AdvData(42056, 'Overworld'),
|
||||
"A Seedy Place": AdvData(42057, 'Overworld'),
|
||||
"Those Were the Days": AdvData(42058, 'Bastion Remnant'),
|
||||
"Hero of the Village": AdvData(42059, 'Village'),
|
||||
"Hidden in the Depths": AdvData(42060, 'The Nether'),
|
||||
"Beaconator": AdvData(42061, 'Nether Fortress'),
|
||||
"Withering Heights": AdvData(42062, 'Nether Fortress'),
|
||||
"A Balanced Diet": AdvData(42063, 'Village'),
|
||||
"Subspace Bubble": AdvData(42064, 'The Nether'),
|
||||
"Husbandry": AdvData(42065, 'Overworld'),
|
||||
"Country Lode, Take Me Home": AdvData(42066, 'The Nether'),
|
||||
"Bee Our Guest": AdvData(42067, 'Overworld'),
|
||||
"What a Deal!": AdvData(42068, 'Village'),
|
||||
"Uneasy Alliance": AdvData(42069, 'The Nether'),
|
||||
"Diamonds!": AdvData(42070, 'Overworld'),
|
||||
"A Terrible Fortress": AdvData(42071, 'Nether Fortress'),
|
||||
"A Throwaway Joke": AdvData(42072, 'Overworld'),
|
||||
"Minecraft": AdvData(42073, 'Overworld'),
|
||||
"Sticky Situation": AdvData(42074, 'Overworld'),
|
||||
"Ol' Betsy": AdvData(42075, 'Overworld'),
|
||||
"Cover Me in Debris": AdvData(42076, 'The Nether'),
|
||||
"The End?": AdvData(42077, 'The End'),
|
||||
"The Parrots and the Bats": AdvData(42078, 'Overworld'),
|
||||
"A Complete Catalogue": AdvData(42079, 'Village'),
|
||||
"Getting Wood": AdvData(42080, 'Overworld'),
|
||||
"Time to Mine!": AdvData(42081, 'Overworld'),
|
||||
"Hot Topic": AdvData(42082, 'Overworld'),
|
||||
"Bake Bread": AdvData(42083, 'Overworld'),
|
||||
"The Lie": AdvData(42084, 'Overworld'),
|
||||
"On a Rail": AdvData(42085, 'Overworld'),
|
||||
"Time to Strike!": AdvData(42086, 'Overworld'),
|
||||
"Cow Tipper": AdvData(42087, 'Overworld'),
|
||||
"When Pigs Fly": AdvData(42088, 'Overworld'),
|
||||
"Overkill": AdvData(42089, 'Nether Fortress'),
|
||||
"Librarian": AdvData(42090, 'Overworld'),
|
||||
"Overpowered": AdvData(42091, 'Overworld'),
|
||||
|
||||
"Ender Dragon": AdvData(0, 'The End')
|
||||
}
|
||||
|
||||
exclusion_table = {
|
||||
"hard": {
|
||||
"Very Very Frightening": "50 XP",
|
||||
"Two by Two": "100 XP",
|
||||
"Two Birds, One Arrow": "50 XP",
|
||||
"Arbalistic": "100 XP",
|
||||
"Beaconator": "50 XP",
|
||||
"A Balanced Diet": "100 XP",
|
||||
"Uneasy Alliance": "100 XP",
|
||||
"Cover Me in Debris": "100 XP",
|
||||
"A Complete Catalogue": "50 XP",
|
||||
"Overpowered": "50 XP"
|
||||
},
|
||||
"insane": {
|
||||
"How Did We Get Here?": "500 XP",
|
||||
"Adventuring Time": "500 XP"
|
||||
},
|
||||
"postgame": {
|
||||
"The Next Generation": "50 XP",
|
||||
"The End... Again...": "50 XP",
|
||||
"You Need a Mint": "50 XP",
|
||||
"Monsters Hunted": "100 XP"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
events_table = {
|
||||
"Ender Dragon": "Victory"
|
||||
}
|
||||
|
||||
|
||||
lookup_id_to_name: typing.Dict[int, str] = {loc_data.id: loc_name for loc_name, loc_data in advancement_table.items() if loc_data.id}
|
||||
101
worlds/minecraft/Regions.py
Normal file
101
worlds/minecraft/Regions.py
Normal file
@@ -0,0 +1,101 @@
|
||||
from .Locations import MinecraftAdvancement, advancement_table
|
||||
|
||||
from BaseClasses import Region, Entrance, Location, MultiWorld, Item
|
||||
|
||||
def minecraft_create_regions(world: MultiWorld, player: int):
|
||||
|
||||
def MCRegion(region_name: str, exits=[]):
|
||||
ret = Region(region_name, None, region_name, player)
|
||||
ret.world = world
|
||||
ret.locations = [ MinecraftAdvancement(player, loc_name, loc_data.id, ret)
|
||||
for loc_name, loc_data in advancement_table.items()
|
||||
if loc_data.region == region_name ]
|
||||
for exit in exits:
|
||||
ret.exits.append(Entrance(player, exit, ret))
|
||||
return ret
|
||||
|
||||
world.regions += [MCRegion(*r) for r in mc_regions]
|
||||
|
||||
for (exit, region) in mandatory_connections:
|
||||
world.get_entrance(exit, player).connect(world.get_region(region, player))
|
||||
|
||||
def link_minecraft_structures(world: MultiWorld, player: int):
|
||||
|
||||
# Get all unpaired exits and all regions without entrances (except the Menu)
|
||||
# This function is destructive on these lists.
|
||||
exits = [exit.name for r in world.regions if r.player == player for exit in r.exits if exit.connected_region == None]
|
||||
structs = [r.name for r in world.regions if r.player == player and r.entrances == [] and r.name != 'Menu']
|
||||
try:
|
||||
assert len(exits) == len(structs)
|
||||
except AssertionError as e: # this should never happen
|
||||
raise Exception(f"Could not obtain equal numbers of Minecraft exits and structures for player {player}") from e
|
||||
num_regions = len(exits)
|
||||
pairs = {}
|
||||
|
||||
def check_valid_connection(exit, struct):
|
||||
if (exit in exits) and (struct in structs) and (exit not in pairs):
|
||||
return True
|
||||
return False
|
||||
|
||||
def set_pair(exit, struct):
|
||||
pairs[exit] = struct
|
||||
exits.remove(exit)
|
||||
structs.remove(struct)
|
||||
|
||||
# Plando stuff. Remove any utilized exits/structs from the lists.
|
||||
# Raise error if trying to put Nether Fortress in the End.
|
||||
|
||||
if world.shuffle_structures[player]:
|
||||
# Can't put Nether Fortress in the End
|
||||
if 'The End Structure' in exits and 'Nether Fortress' in structs:
|
||||
try:
|
||||
end_struct = world.random.choice([s for s in structs if s != 'Nether Fortress'])
|
||||
set_pair('The End Structure', end_struct)
|
||||
except IndexError as e:
|
||||
raise Exception(f"Plando forced Nether Fortress in the End for player {player}") from e
|
||||
world.random.shuffle(structs)
|
||||
for exit, struct in zip(exits[:], structs[:]):
|
||||
set_pair(exit, struct)
|
||||
else: # write remaining default connections
|
||||
for (exit, struct) in default_connections:
|
||||
if exit in exits:
|
||||
set_pair(exit, struct)
|
||||
|
||||
# Make sure we actually paired everything; might fail if plando
|
||||
try:
|
||||
assert len(exits) == len(structs) == 0
|
||||
except AssertionError as e:
|
||||
raise Exception(f"Failed to connect all Minecraft structures for player {player}; check plando settings in yaml") from e
|
||||
|
||||
for exit, struct in pairs.items():
|
||||
world.get_entrance(exit, player).connect(world.get_region(struct, player))
|
||||
if world.shuffle_structures[player]:
|
||||
world.spoiler.set_entrance(exit, struct, 'entrance', player)
|
||||
|
||||
# (Region name, list of exits)
|
||||
mc_regions = [
|
||||
('Menu', ['New World']),
|
||||
('Overworld', ['Nether Portal', 'End Portal', 'Overworld Structure 1', 'Overworld Structure 2']),
|
||||
('The Nether', ['Nether Structure 1', 'Nether Structure 2']),
|
||||
('The End', ['The End Structure']),
|
||||
('Village', []),
|
||||
('Pillager Outpost', []),
|
||||
('Nether Fortress', []),
|
||||
('Bastion Remnant', []),
|
||||
('End City', [])
|
||||
]
|
||||
|
||||
# (Entrance, region pointed to)
|
||||
mandatory_connections = [
|
||||
('New World', 'Overworld'),
|
||||
('Nether Portal', 'The Nether'),
|
||||
('End Portal', 'The End')
|
||||
]
|
||||
|
||||
default_connections = {
|
||||
('Overworld Structure 1', 'Village'),
|
||||
('Overworld Structure 2', 'Pillager Outpost'),
|
||||
('Nether Structure 1', 'Nether Fortress'),
|
||||
('Nether Structure 2', 'Bastion Remnant'),
|
||||
('The End Structure', 'End City')
|
||||
}
|
||||
143
worlds/minecraft/Rules.py
Normal file
143
worlds/minecraft/Rules.py
Normal file
@@ -0,0 +1,143 @@
|
||||
from ..generic.Rules import set_rule
|
||||
from .Locations import exclusion_table, events_table
|
||||
from BaseClasses import Region, Entrance, Location, MultiWorld, Item
|
||||
from Options import AdvancementGoal
|
||||
|
||||
def set_rules(world: MultiWorld, player: int):
|
||||
|
||||
def reachable_locations(state):
|
||||
postgame_advancements = set(exclusion_table['postgame'].keys())
|
||||
postgame_advancements.add('Free the End')
|
||||
for event in events_table.keys():
|
||||
postgame_advancements.add(event)
|
||||
return [location for location in world.get_locations() if
|
||||
(player is None or location.player == player) and
|
||||
(location.name not in postgame_advancements) and
|
||||
location.can_reach(state)]
|
||||
|
||||
# 92 total advancements, 16 are typically excluded, 1 is Free the End. Goal is to complete X advancements and then Free the End.
|
||||
goal_map = {
|
||||
'few': 30,
|
||||
'normal': 50,
|
||||
'many': 70
|
||||
}
|
||||
goal = goal_map[getattr(world, 'advancement_goal')[player].get_option_name()]
|
||||
can_complete = lambda state: len(reachable_locations(state)) >= goal and state.can_reach('The End', 'Region', player) and state.can_kill_ender_dragon(player)
|
||||
|
||||
if world.logic[player] != 'nologic':
|
||||
world.completion_condition[player] = lambda state: state.has('Victory', player)
|
||||
|
||||
set_rule(world.get_entrance("Nether Portal", player), lambda state: state.has('Flint and Steel', player) and
|
||||
(state.has('Bucket', player) or state.has('Progressive Tools', player, 3)) and
|
||||
state.has_iron_ingots(player))
|
||||
set_rule(world.get_entrance("End Portal", player), lambda state: state.enter_stronghold(player) and state.has('3 Ender Pearls', player, 4))
|
||||
set_rule(world.get_entrance("Overworld Structure 1", player), lambda state: state.can_adventure(player))
|
||||
set_rule(world.get_entrance("Overworld Structure 2", player), lambda state: state.can_adventure(player))
|
||||
set_rule(world.get_entrance("Nether Structure 1", player), lambda state: state.can_adventure(player))
|
||||
set_rule(world.get_entrance("Nether Structure 2", player), lambda state: state.can_adventure(player))
|
||||
set_rule(world.get_entrance("The End Structure", player), lambda state: state.can_adventure(player))
|
||||
|
||||
set_rule(world.get_location("Ender Dragon", player), lambda state: can_complete(state))
|
||||
|
||||
set_rule(world.get_location("Who is Cutting Onions?", player), lambda state: state.can_piglin_trade(player))
|
||||
set_rule(world.get_location("Oh Shiny", player), lambda state: state.can_piglin_trade(player))
|
||||
set_rule(world.get_location("Suit Up", player), lambda state: state.has("Progressive Armor", player) and state.has_iron_ingots(player))
|
||||
set_rule(world.get_location("Very Very Frightening", player), lambda state: state.has("Channeling Book", player) and state.can_use_anvil(player) and state.can_enchant(player))
|
||||
set_rule(world.get_location("Hot Stuff", player), lambda state: state.has("Bucket", player) and state.has_iron_ingots(player))
|
||||
set_rule(world.get_location("Free the End", player), lambda state: can_complete(state))
|
||||
set_rule(world.get_location("A Furious Cocktail", player), lambda state: state.can_brew_potions(player) and state.has("Fishing Rod", player) and state.can_reach('The Nether', 'Region', player))
|
||||
set_rule(world.get_location("Best Friends Forever", player), lambda state: True)
|
||||
set_rule(world.get_location("Bring Home the Beacon", player), lambda state: state.can_kill_wither(player) and state.has_diamond_pickaxe(player) and
|
||||
state.has("Ingot Crafting", player) and state.has("Resource Blocks", player))
|
||||
set_rule(world.get_location("Not Today, Thank You", player), lambda state: state.has("Shield", player) and state.has_iron_ingots(player))
|
||||
set_rule(world.get_location("Isn't It Iron Pick", player), lambda state: state.has("Progressive Tools", player, 2) and state.has_iron_ingots(player))
|
||||
set_rule(world.get_location("Local Brewery", player), lambda state: state.can_brew_potions(player))
|
||||
set_rule(world.get_location("The Next Generation", player), lambda state: can_complete(state))
|
||||
set_rule(world.get_location("Fishy Business", player), lambda state: state.has("Fishing Rod", player))
|
||||
set_rule(world.get_location("Hot Tourist Destinations", player), lambda state: state.fortress_loot(player) and state.has("Fishing Rod", player))
|
||||
set_rule(world.get_location("This Boat Has Legs", player), lambda state: state.fortress_loot(player) and state.has("Fishing Rod", player))
|
||||
set_rule(world.get_location("Sniper Duel", player), lambda state: state.has("Archery", player))
|
||||
set_rule(world.get_location("Nether", player), lambda state: True)
|
||||
set_rule(world.get_location("Great View From Up Here", player), lambda state: state.basic_combat(player))
|
||||
set_rule(world.get_location("How Did We Get Here?", player), lambda state: state.can_brew_potions(player) and state.has_gold_ingots(player) and # most effects; Absorption
|
||||
state.can_reach('End City', 'Region', player) and state.can_reach('The Nether', 'Region', player) and # Levitation; potion ingredients
|
||||
state.has("Fishing Rod", player) and state.has("Archery", player) and # Pufferfish, Nautilus Shells; spectral arrows
|
||||
state.can_reach("Bring Home the Beacon", "Location", player) and # Haste
|
||||
state.can_reach("Hero of the Village", "Location", player)) # Bad Omen, Hero of the Village
|
||||
set_rule(world.get_location("Bullseye", player), lambda state: state.has("Archery", player) and state.has("Progressive Tools", player, 2) and state.has_iron_ingots(player))
|
||||
set_rule(world.get_location("Spooky Scary Skeleton", player), lambda state: state.basic_combat(player))
|
||||
set_rule(world.get_location("Two by Two", player), lambda state: state.has_iron_ingots(player) and state.can_adventure(player)) # shears > seagrass > turtles; nether > striders; gold carrots > horses skips ingots
|
||||
set_rule(world.get_location("Stone Age", player), lambda state: True)
|
||||
set_rule(world.get_location("Two Birds, One Arrow", player), lambda state: state.craft_crossbow(player) and state.can_enchant(player))
|
||||
set_rule(world.get_location("We Need to Go Deeper", player), lambda state: True)
|
||||
set_rule(world.get_location("Who's the Pillager Now?", player), lambda state: state.craft_crossbow(player))
|
||||
set_rule(world.get_location("Getting an Upgrade", player), lambda state: state.has("Progressive Tools", player))
|
||||
set_rule(world.get_location("Tactical Fishing", player), lambda state: state.has("Bucket", player) and state.has_iron_ingots(player))
|
||||
set_rule(world.get_location("Zombie Doctor", player), lambda state: state.can_brew_potions(player) and state.has_gold_ingots(player))
|
||||
set_rule(world.get_location("The City at the End of the Game", player), lambda state: True)
|
||||
set_rule(world.get_location("Ice Bucket Challenge", player), lambda state: state.has_diamond_pickaxe(player))
|
||||
set_rule(world.get_location("Remote Getaway", player), lambda state: True)
|
||||
set_rule(world.get_location("Into Fire", player), lambda state: state.basic_combat(player))
|
||||
set_rule(world.get_location("War Pigs", player), lambda state: state.basic_combat(player))
|
||||
set_rule(world.get_location("Take Aim", player), lambda state: state.has("Archery", player))
|
||||
set_rule(world.get_location("Total Beelocation", player), lambda state: state.has("Silk Touch Book", player) and state.can_use_anvil(player) and state.can_enchant(player))
|
||||
set_rule(world.get_location("Arbalistic", player), lambda state: state.craft_crossbow(player) and state.has("Piercing IV Book", player) and
|
||||
state.can_use_anvil(player) and state.can_enchant(player))
|
||||
set_rule(world.get_location("The End... Again...", player), lambda state: can_complete(state) and state.has("Ingot Crafting", player) and state.can_reach('The Nether', 'Region', player)) # furnace for glass, nether for ghast tears
|
||||
set_rule(world.get_location("Acquire Hardware", player), lambda state: state.has_iron_ingots(player))
|
||||
set_rule(world.get_location("Not Quite \"Nine\" Lives", player), lambda state: state.can_piglin_trade(player) and state.has("Resource Blocks", player))
|
||||
set_rule(world.get_location("Cover Me With Diamonds", player), lambda state: state.has("Progressive Armor", player, 2) and state.can_reach("Diamonds!", "Location", player))
|
||||
set_rule(world.get_location("Sky's the Limit", player), lambda state: state.basic_combat(player))
|
||||
set_rule(world.get_location("Hired Help", player), lambda state: state.has("Resource Blocks", player) and state.has_iron_ingots(player))
|
||||
set_rule(world.get_location("Return to Sender", player), lambda state: True)
|
||||
set_rule(world.get_location("Sweet Dreams", player), lambda state: state.has("Bed", player) or state.can_reach('Village', 'Region', player))
|
||||
set_rule(world.get_location("You Need a Mint", player), lambda state: can_complete(state) and state.has_bottle_mc(player))
|
||||
set_rule(world.get_location("Adventure", player), lambda state: True)
|
||||
set_rule(world.get_location("Monsters Hunted", player), lambda state: can_complete(state) and state.can_kill_wither(player) and state.has("Fishing Rod", player)) # pufferfish for Water Breathing
|
||||
set_rule(world.get_location("Enchanter", player), lambda state: state.can_enchant(player))
|
||||
set_rule(world.get_location("Voluntary Exile", player), lambda state: state.basic_combat(player))
|
||||
set_rule(world.get_location("Eye Spy", player), lambda state: state.enter_stronghold(player))
|
||||
set_rule(world.get_location("The End", player), lambda state: True)
|
||||
set_rule(world.get_location("Serious Dedication", player), lambda state: state.can_reach("Hidden in the Depths", "Location", player) and state.has_gold_ingots(player))
|
||||
set_rule(world.get_location("Postmortal", player), lambda state: state.complete_raid(player))
|
||||
set_rule(world.get_location("Monster Hunter", player), lambda state: True)
|
||||
set_rule(world.get_location("Adventuring Time", player), lambda state: state.can_adventure(player))
|
||||
set_rule(world.get_location("A Seedy Place", player), lambda state: True)
|
||||
set_rule(world.get_location("Those Were the Days", player), lambda state: True)
|
||||
set_rule(world.get_location("Hero of the Village", player), lambda state: state.complete_raid(player))
|
||||
set_rule(world.get_location("Hidden in the Depths", player), lambda state: state.can_brew_potions(player) and state.has("Bed", player) and state.has_diamond_pickaxe(player)) # bed mining :)
|
||||
set_rule(world.get_location("Beaconator", player), lambda state: state.can_kill_wither(player) and state.has_diamond_pickaxe(player) and
|
||||
state.has("Ingot Crafting", player) and state.has("Resource Blocks", player))
|
||||
set_rule(world.get_location("Withering Heights", player), lambda state: state.can_kill_wither(player))
|
||||
set_rule(world.get_location("A Balanced Diet", player), lambda state: state.has_bottle_mc(player) and state.has_gold_ingots(player) and # honey bottle; gapple
|
||||
state.has("Resource Blocks", player) and state.can_reach('The End', 'Region', player)) # notch apple, chorus fruit
|
||||
set_rule(world.get_location("Subspace Bubble", player), lambda state: state.has_diamond_pickaxe(player))
|
||||
set_rule(world.get_location("Husbandry", player), lambda state: True)
|
||||
set_rule(world.get_location("Country Lode, Take Me Home", player), lambda state: state.can_reach("Hidden in the Depths", "Location", player) and state.has_gold_ingots(player))
|
||||
set_rule(world.get_location("Bee Our Guest", player), lambda state: state.has("Campfire", player) and state.has_bottle_mc(player))
|
||||
set_rule(world.get_location("What a Deal!", player), lambda state: True)
|
||||
set_rule(world.get_location("Uneasy Alliance", player), lambda state: state.has_diamond_pickaxe(player))
|
||||
set_rule(world.get_location("Diamonds!", player), lambda state: state.has("Progressive Tools", player, 2) and state.has_iron_ingots(player))
|
||||
set_rule(world.get_location("A Terrible Fortress", player), lambda state: True) # since you don't have to fight anything
|
||||
set_rule(world.get_location("A Throwaway Joke", player), lambda state: True) # kill drowned
|
||||
set_rule(world.get_location("Minecraft", player), lambda state: True)
|
||||
set_rule(world.get_location("Sticky Situation", player), lambda state: state.has_bottle_mc(player))
|
||||
set_rule(world.get_location("Ol' Betsy", player), lambda state: state.craft_crossbow(player))
|
||||
set_rule(world.get_location("Cover Me in Debris", player), lambda state: state.has("Progressive Armor", player, 2) and
|
||||
state.has("8 Netherite Scrap", player, 2) and state.has("Ingot Crafting", player) and
|
||||
state.can_reach("Diamonds!", "Location", player) and state.can_reach("Hidden in the Depths", "Location", player))
|
||||
set_rule(world.get_location("The End?", player), lambda state: True)
|
||||
set_rule(world.get_location("The Parrots and the Bats", player), lambda state: True)
|
||||
set_rule(world.get_location("A Complete Catalogue", player), lambda state: True) # kill fish for raw
|
||||
set_rule(world.get_location("Getting Wood", player), lambda state: True)
|
||||
set_rule(world.get_location("Time to Mine!", player), lambda state: True)
|
||||
set_rule(world.get_location("Hot Topic", player), lambda state: state.has("Ingot Crafting", player))
|
||||
set_rule(world.get_location("Bake Bread", player), lambda state: True)
|
||||
set_rule(world.get_location("The Lie", player), lambda state: state.has_iron_ingots(player) and state.has("Bucket", player))
|
||||
set_rule(world.get_location("On a Rail", player), lambda state: state.has_iron_ingots(player))
|
||||
set_rule(world.get_location("Time to Strike!", player), lambda state: True)
|
||||
set_rule(world.get_location("Cow Tipper", player), lambda state: True)
|
||||
set_rule(world.get_location("When Pigs Fly", player), lambda state: state.fortress_loot(player) and state.has("Fishing Rod", player) and state.can_adventure(player)) # saddles in fortress chests
|
||||
set_rule(world.get_location("Overkill", player), lambda state: state.can_brew_potions(player) and state.has("Progressive Weapons", player)) # strength 1, stone axe crit
|
||||
set_rule(world.get_location("Librarian", player), lambda state: state.has("Enchanting", player))
|
||||
set_rule(world.get_location("Overpowered", player), lambda state: state.has("Resource Blocks", player) and state.has_gold_ingots(player))
|
||||
@@ -0,0 +1,68 @@
|
||||
from random import Random
|
||||
from .Items import MinecraftItem, item_table, item_frequencies
|
||||
from .Locations import exclusion_table, events_table
|
||||
from .Regions import link_minecraft_structures
|
||||
from .Rules import set_rules
|
||||
|
||||
from BaseClasses import Region, Entrance, Location, MultiWorld, Item
|
||||
from Options import minecraft_options
|
||||
|
||||
client_version = (0, 3)
|
||||
|
||||
def generate_mc_data(world: MultiWorld, player: int, seedname: str):
|
||||
import base64, json
|
||||
from Utils import output_path
|
||||
|
||||
exits = ["Overworld Structure 1", "Overworld Structure 2", "Nether Structure 1", "Nether Structure 2", "The End Structure"]
|
||||
data = {
|
||||
'world_seed': Random(world.rom_seeds[player]).getrandbits(32), # consistent and doesn't interfere with other generation
|
||||
'seed_name': seedname,
|
||||
'player_name': world.get_player_names(player),
|
||||
'client_version': client_version,
|
||||
'structures': {exit: world.get_entrance(exit, player).connected_region.name for exit in exits}
|
||||
}
|
||||
|
||||
filename = f"AP_{seedname}_P{player}_{world.get_player_names(player)}.apmc"
|
||||
with open(output_path(filename), 'wb') as f:
|
||||
f.write(base64.b64encode(bytes(json.dumps(data), 'utf-8')))
|
||||
|
||||
def fill_minecraft_slot_data(world: MultiWorld, player: int):
|
||||
slot_data = {}
|
||||
for option_name in minecraft_options:
|
||||
option = getattr(world, option_name)[player]
|
||||
slot_data[option_name] = int(option.value)
|
||||
slot_data['client_version'] = client_version
|
||||
return slot_data
|
||||
|
||||
# Generates the item pool given the table and frequencies in Items.py.
|
||||
def minecraft_gen_item_pool(world: MultiWorld, player: int):
|
||||
|
||||
pool = []
|
||||
for item_name, item_data in item_table.items():
|
||||
for count in range(item_frequencies.get(item_name, 1)):
|
||||
pool.append(MinecraftItem(item_name, item_data.progression, item_data.code, player))
|
||||
|
||||
prefill_pool = {}
|
||||
prefill_pool.update(events_table)
|
||||
exclusion_pools = ['hard', 'insane', 'postgame']
|
||||
for key in exclusion_pools:
|
||||
if not getattr(world, f"include_{key}_advancements")[player]:
|
||||
prefill_pool.update(exclusion_table[key])
|
||||
|
||||
for loc_name, item_name in prefill_pool.items():
|
||||
item_data = item_table[item_name]
|
||||
location = world.get_location(loc_name, player)
|
||||
item = MinecraftItem(item_name, item_data.progression, item_data.code, player)
|
||||
world.push_item(location, item, collect=False)
|
||||
pool.remove(item)
|
||||
location.event = item_data.progression
|
||||
location.locked = True
|
||||
|
||||
world.itempool += pool
|
||||
|
||||
# Generate Minecraft world.
|
||||
def gen_minecraft(world: MultiWorld, player: int):
|
||||
link_minecraft_structures(world, player)
|
||||
minecraft_gen_item_pool(world, player)
|
||||
set_rules(world, player)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user