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:
espeon65536
2021-05-08 07:38:57 -04:00
committed by GitHub
parent eb2a3009f4
commit 2f7e532f4f
15 changed files with 2005 additions and 15 deletions

74
worlds/minecraft/Items.py Normal file
View 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}

View 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
View 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
View 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))

View File

@@ -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)