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:
parent
eb2a3009f4
commit
2f7e532f4f
|
@ -169,6 +169,11 @@ class MultiWorld():
|
|||
def factorio_player_ids(self):
|
||||
yield from (player for player in range(1, self.players + 1) if self.game[player] == "Factorio")
|
||||
|
||||
@property
|
||||
def minecraft_player_ids(self):
|
||||
yield from (player for player in range(1, self.players + 1) if self.game[player] == "Minecraft")
|
||||
|
||||
|
||||
def get_name_string_for_object(self, obj) -> str:
|
||||
return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_names(obj.player)})'
|
||||
|
||||
|
@ -234,6 +239,7 @@ class MultiWorld():
|
|||
|
||||
def soft_collect(item):
|
||||
if item.game == "A Link to the Past" and item.name.startswith('Progressive '):
|
||||
# ALttP items
|
||||
if 'Sword' in item.name:
|
||||
if ret.has('Golden Sword', item.player):
|
||||
pass
|
||||
|
@ -806,6 +812,91 @@ class CollectionState(object):
|
|||
rules.append(self.has('Moon Pearl', player))
|
||||
return all(rules)
|
||||
|
||||
# Minecraft logic functions
|
||||
def has_iron_ingots(self, player: int):
|
||||
return self.has('Progressive Tools', player) and self.has('Ingot Crafting', player)
|
||||
|
||||
def has_gold_ingots(self, player: int):
|
||||
return self.has('Ingot Crafting', player) and (self.has('Progressive Tools', player, 2) or self.can_reach('The Nether', 'Region', player))
|
||||
|
||||
def has_diamond_pickaxe(self, player: int):
|
||||
return self.has('Progressive Tools', player, 3) and self.has_iron_ingots(player)
|
||||
|
||||
def craft_crossbow(self, player: int):
|
||||
return self.has('Archery', player) and self.has_iron_ingots(player)
|
||||
|
||||
def has_bottle_mc(self, player: int):
|
||||
return self.has('Bottles', player) and self.has('Ingot Crafting', player)
|
||||
|
||||
def can_enchant(self, player: int):
|
||||
return self.has('Enchanting', player) and self.has_diamond_pickaxe(player) # mine obsidian and lapis
|
||||
|
||||
def can_use_anvil(self, player: int):
|
||||
return self.has('Enchanting', player) and self.has('Resource Blocks', player) and self.has_iron_ingots(player)
|
||||
|
||||
def fortress_loot(self, player: int): # saddles, blaze rods, wither skulls
|
||||
return self.can_reach('Nether Fortress', 'Region', player) and self.basic_combat(player)
|
||||
|
||||
def can_brew_potions(self, player: int):
|
||||
return self.fortress_loot(player) and self.has('Brewing', player) and self.has_bottle_mc(player)
|
||||
|
||||
def can_piglin_trade(self, player: int):
|
||||
return self.has_gold_ingots(player) and (self.can_reach('The Nether', 'Region', player) or self.can_reach('Bastion Remnant', 'Region', player))
|
||||
|
||||
def enter_stronghold(self, player: int):
|
||||
return self.fortress_loot(player) and self.has('Brewing', player) and self.has('3 Ender Pearls', player)
|
||||
|
||||
# Difficulty-dependent functions
|
||||
def combat_difficulty(self, player: int):
|
||||
return self.world.combat_difficulty[player].get_option_name()
|
||||
|
||||
def can_adventure(self, player: int):
|
||||
if self.combat_difficulty(player) == 'easy':
|
||||
return self.has('Progressive Weapons', player, 2) and self.has_iron_ingots(player)
|
||||
elif self.combat_difficulty(player) == 'hard':
|
||||
return True
|
||||
return self.has('Progressive Weapons', player) and (self.has('Ingot Crafting', player) or self.has('Campfire', player))
|
||||
|
||||
def basic_combat(self, player: int):
|
||||
if self.combat_difficulty(player) == 'easy':
|
||||
return self.has('Progressive Weapons', player, 2) and self.has('Progressive Armor', player) and \
|
||||
self.has('Shield', player) and self.has_iron_ingots(player)
|
||||
elif self.combat_difficulty(player) == 'hard':
|
||||
return True
|
||||
return self.has('Progressive Weapons', player) and (self.has('Progressive Armor', player) or self.has('Shield', player)) and self.has_iron_ingots(player)
|
||||
|
||||
def complete_raid(self, player: int):
|
||||
reach_regions = self.can_reach('Village', 'Region', player) and self.can_reach('Pillager Outpost', 'Region', player)
|
||||
if self.combat_difficulty(player) == 'easy':
|
||||
return reach_regions and \
|
||||
self.has('Progressive Weapons', player, 3) and self.has('Progressive Armor', player, 2) and \
|
||||
self.has('Shield', player) and self.has('Archery', player) and \
|
||||
self.has('Progressive Tools', player, 2) and self.has_iron_ingots(player)
|
||||
elif self.combat_difficulty(player) == 'hard': # might be too hard?
|
||||
return reach_regions and self.has('Progressive Weapons', player, 2) and self.has_iron_ingots(player) and \
|
||||
(self.has('Progressive Armor', player) or self.has('Shield', player))
|
||||
return reach_regions and self.has('Progressive Weapons', player, 2) and self.has_iron_ingots(player) and \
|
||||
self.has('Progressive Armor', player) and self.has('Shield', player)
|
||||
|
||||
def can_kill_wither(self, player: int):
|
||||
build_wither = self.fortress_loot(player) and (self.can_reach('The Nether', 'Region', player) or self.can_piglin_trade(player))
|
||||
normal_kill = self.has("Progressive Weapons", player, 3) and self.has("Progressive Armor", player, 2) and self.can_brew_potions(player) and self.can_enchant(player)
|
||||
if self.combat_difficulty(player) == 'easy':
|
||||
return build_wither and normal_kill and self.has('Archery', player)
|
||||
elif self.combat_difficulty(player) == 'hard': # cheese kill using bedrock ceilings
|
||||
return build_wither and (normal_kill or self.can_reach('The Nether', 'Region', player) or self.can_reach('The End', 'Region', player))
|
||||
return build_wither and normal_kill
|
||||
|
||||
def can_kill_ender_dragon(self, player: int):
|
||||
if self.combat_difficulty(player) == 'easy':
|
||||
return self.has("Progressive Weapons", player, 3) and self.has("Progressive Armor", player, 2) and self.has('Archery', player) and \
|
||||
self.can_brew_potions(player) and self.can_enchant(player)
|
||||
if self.combat_difficulty(player) == 'hard':
|
||||
return (self.has('Progressive Weapons', player, 2) and self.has('Progressive Armor', player)) or \
|
||||
(self.has('Progressive Weapons', player, 1) and self.has('Bed', player))
|
||||
return self.has('Progressive Weapons', player, 2) and self.has('Progressive Armor', player) and self.has('Archery', player)
|
||||
|
||||
|
||||
def collect(self, item: Item, event: bool = False, location: Location = None) -> bool:
|
||||
if location:
|
||||
self.locations_checked.add(location)
|
||||
|
@ -1403,6 +1494,11 @@ class Spoiler(object):
|
|||
for hk_option in Options.hollow_knight_options:
|
||||
res = getattr(self.world, hk_option)[player]
|
||||
outfile.write(f'{hk_option+":":33}{res}\n')
|
||||
if player in self.world.minecraft_player_ids:
|
||||
import Options
|
||||
for mc_option in Options.minecraft_options:
|
||||
res = getattr(self.world, mc_option)[player]
|
||||
outfile.write(f'{mc_option+":":33}{bool_to_text(res) if type(res) == Options.Toggle else res.get_option_name()}\n')
|
||||
if player in self.world.alttp_player_ids:
|
||||
for team in range(self.world.teams):
|
||||
outfile.write('%s%s\n' % (
|
||||
|
@ -1418,7 +1514,7 @@ class Spoiler(object):
|
|||
outfile.write('Mode: %s\n' % self.metadata['mode'][player])
|
||||
outfile.write('Retro: %s\n' %
|
||||
('Yes' if self.metadata['retro'][player] else 'No'))
|
||||
outfile.write('Swordless: %s\n' % ('Yes' if self.metadata['swordless'][player] else 'No'))
|
||||
outfile.write('Swordless: %s\n' % ('Yes' if self.metadata['swordless'][player] else 'No'))
|
||||
outfile.write('Goal: %s\n' % self.metadata['goal'][player])
|
||||
if "triforce" in self.metadata["goal"][player]: # triforce hunt
|
||||
outfile.write("Pieces available for Triforce: %s\n" %
|
||||
|
|
18
Main.py
18
Main.py
|
@ -26,6 +26,8 @@ from worlds.hk import gen_hollow
|
|||
from worlds.hk import create_regions as hk_create_regions
|
||||
from worlds.factorio import gen_factorio, factorio_create_regions
|
||||
from worlds.factorio.Mod import generate_mod
|
||||
from worlds.minecraft import gen_minecraft, fill_minecraft_slot_data, generate_mc_data
|
||||
from worlds.minecraft.Regions import minecraft_create_regions
|
||||
from worlds.generic.Rules import locality_rules
|
||||
from worlds import Games
|
||||
import Patch
|
||||
|
@ -136,6 +138,8 @@ def main(args, seed=None):
|
|||
setattr(world, hk_option, getattr(args, hk_option, {}))
|
||||
for factorio_option in Options.factorio_options:
|
||||
setattr(world, factorio_option, getattr(args, factorio_option, {}))
|
||||
for minecraft_option in Options.minecraft_options:
|
||||
setattr(world, minecraft_option, getattr(args, minecraft_option, {}))
|
||||
world.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player option.
|
||||
|
||||
world.rom_seeds = {player: random.Random(world.random.randint(0, 999999999)) for player in range(1, world.players + 1)}
|
||||
|
@ -207,6 +211,9 @@ def main(args, seed=None):
|
|||
for player in world.factorio_player_ids:
|
||||
factorio_create_regions(world, player)
|
||||
|
||||
for player in world.minecraft_player_ids:
|
||||
minecraft_create_regions(world, player)
|
||||
|
||||
for player in world.alttp_player_ids:
|
||||
if world.open_pyramid[player] == 'goal':
|
||||
world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt', 'localganontriforcehunt', 'ganonpedestal'}
|
||||
|
@ -266,6 +273,9 @@ def main(args, seed=None):
|
|||
for player in world.factorio_player_ids:
|
||||
gen_factorio(world, player)
|
||||
|
||||
for player in world.minecraft_player_ids:
|
||||
gen_minecraft(world, player)
|
||||
|
||||
logger.info("Running Item Plando")
|
||||
|
||||
for item in world.itempool:
|
||||
|
@ -305,9 +315,7 @@ def main(args, seed=None):
|
|||
balance_multiworld_progression(world)
|
||||
|
||||
logger.info('Generating output files.')
|
||||
|
||||
outfilebase = 'AP_%s' % (args.outputname if args.outputname else world.seed)
|
||||
|
||||
rom_names = []
|
||||
|
||||
def _gen_rom(team: int, player: int):
|
||||
|
@ -511,6 +519,8 @@ def main(args, seed=None):
|
|||
for option_name in Options.hollow_knight_options:
|
||||
option = getattr(world, option_name)[slot]
|
||||
slots_data[option_name] = int(option.value)
|
||||
for slot in world.minecraft_player_ids:
|
||||
slot_data[slot] = fill_minecraft_slot_data(world, slot)
|
||||
multidata = zlib.compress(pickle.dumps({
|
||||
"slot_data" : slot_data,
|
||||
"games": games,
|
||||
|
@ -549,9 +559,11 @@ def main(args, seed=None):
|
|||
if multidata_task:
|
||||
multidata_task.result() # retrieve exception if one exists
|
||||
pool.shutdown() # wait for all queued tasks to complete
|
||||
for player in world.minecraft_player_ids: # Doing this after shutdown prevents the .apmc from being generated if there's an error
|
||||
generate_mc_data(world, player, str(args.outputname if args.outputname else world.seed))
|
||||
if not args.skip_playthrough:
|
||||
logger.info('Calculating playthrough.')
|
||||
create_playthrough(world)
|
||||
create_playthrough(world)
|
||||
if args.create_spoiler: # needs spoiler.hashes to be filled, that depend on rom_futures being done
|
||||
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
|
||||
|
||||
|
|
21
Options.py
21
Options.py
|
@ -296,7 +296,26 @@ factorio_options: typing.Dict[str, type(Option)] = {"max_science_pack": MaxScien
|
|||
"visibility": Visibility,
|
||||
"random_tech_ingredients": Toggle}
|
||||
|
||||
minecraft_options: typing.Dict[str, type(Option)] = {}
|
||||
class AdvancementGoal(Choice):
|
||||
option_few = 0
|
||||
option_normal = 1
|
||||
option_many = 2
|
||||
default = 1
|
||||
|
||||
class CombatDifficulty(Choice):
|
||||
option_easy = 0
|
||||
option_normal = 1
|
||||
option_hard = 2
|
||||
default = 1
|
||||
|
||||
minecraft_options: typing.Dict[str, type(Option)] = {
|
||||
"advancement_goal": AdvancementGoal,
|
||||
"combat_difficulty": CombatDifficulty,
|
||||
"include_hard_advancements": Toggle,
|
||||
"include_insane_advancements": Toggle,
|
||||
"include_postgame_advancements": Toggle,
|
||||
"shuffle_structures": Toggle
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
|
|
@ -70,6 +70,27 @@ visibility:
|
|||
random_tech_ingredients:
|
||||
on: 1
|
||||
off: 0
|
||||
# Minecraft options:
|
||||
advancement_goal: # Number of advancements required (out of 92 total) to spawn the Ender Dragon and complete the game.
|
||||
few: 0 # 30 advancements
|
||||
normal: 1 # 50
|
||||
many: 0 # 70
|
||||
combat_difficulty: # Modifies the level of items logically required for exploring dangerous areas and fighting bosses.
|
||||
easy: 0
|
||||
normal: 1
|
||||
hard: 0
|
||||
include_hard_advancements: # Junk-fills certain RNG-reliant or tedious advancements with XP rewards.
|
||||
on: 0
|
||||
off: 1
|
||||
include_insane_advancements: # Junk-fills extremely difficult advancements; this is only How Did We Get Here? and Adventuring Time.
|
||||
on: 0
|
||||
off: 1
|
||||
include_postgame_advancements: # Some advancements require defeating the Ender Dragon; this will junk-fill them so you won't have to finish to send some items.
|
||||
on: 0
|
||||
off: 1
|
||||
shuffle_structures: # CURRENTLY DISABLED; enables shuffling of villages, outposts, fortresses, bastions, and end cities.
|
||||
on: 0
|
||||
off: 1
|
||||
# A Link to the Past options:
|
||||
### Logic Section ###
|
||||
# Warning: overworld_glitches is not available and minor_glitches is only partially implemented on the door-rando version
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,92 @@
|
|||
from test.minecraft.TestMinecraft import TestMinecraft
|
||||
|
||||
class TestEntrances(TestMinecraft):
|
||||
|
||||
def testPortals(self):
|
||||
self.run_entrance_tests([
|
||||
['Nether Portal', False, []],
|
||||
['Nether Portal', False, [], ['Flint and Steel']],
|
||||
['Nether Portal', False, [], ['Ingot Crafting']],
|
||||
['Nether Portal', False, [], ['Progressive Tools']],
|
||||
['Nether Portal', False, ['Progressive Tools', 'Progressive Tools'], ['Bucket', 'Progressive Tools']],
|
||||
['Nether Portal', True, ['Flint and Steel', 'Ingot Crafting', 'Progressive Tools', 'Bucket']],
|
||||
['Nether Portal', True, ['Flint and Steel', 'Ingot Crafting', 'Progressive Tools', 'Progressive Tools', 'Progressive Tools']],
|
||||
|
||||
['End Portal', False, []],
|
||||
['End Portal', False, [], ['Brewing']],
|
||||
['End Portal', False, ['3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls'], ['3 Ender Pearls']],
|
||||
['End Portal', False, [], ['Flint and Steel']],
|
||||
['End Portal', False, [], ['Ingot Crafting']],
|
||||
['End Portal', False, [], ['Progressive Tools']],
|
||||
['End Portal', False, ['Progressive Tools', 'Progressive Tools'], ['Bucket', 'Progressive Tools']],
|
||||
['End Portal', False, [], ['Progressive Weapons']],
|
||||
['End Portal', False, [], ['Progressive Armor', 'Shield']],
|
||||
['End Portal', True, ['Flint and Steel', 'Ingot Crafting', 'Progressive Tools', 'Bucket',
|
||||
'Progressive Weapons', 'Progressive Armor',
|
||||
'Brewing', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls']],
|
||||
['End Portal', True, ['Flint and Steel', 'Ingot Crafting', 'Progressive Tools', 'Bucket',
|
||||
'Progressive Weapons', 'Shield',
|
||||
'Brewing', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls']],
|
||||
['End Portal', True, ['Flint and Steel', 'Ingot Crafting', 'Progressive Tools', 'Progressive Tools', 'Progressive Tools',
|
||||
'Progressive Weapons', 'Progressive Armor',
|
||||
'Brewing', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls']],
|
||||
['End Portal', True, ['Flint and Steel', 'Ingot Crafting', 'Progressive Tools', 'Progressive Tools', 'Progressive Tools',
|
||||
'Progressive Weapons', 'Shield',
|
||||
'Brewing', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls']],
|
||||
])
|
||||
|
||||
def testStructures(self):
|
||||
self.run_entrance_tests([ # Structures 1 and 2 should be logically equivalent
|
||||
['Overworld Structure 1', False, []],
|
||||
['Overworld Structure 1', False, [], ['Progressive Weapons']],
|
||||
['Overworld Structure 1', False, [], ['Ingot Crafting', 'Campfire']],
|
||||
['Overworld Structure 1', True, ['Progressive Weapons', 'Ingot Crafting']],
|
||||
['Overworld Structure 1', True, ['Progressive Weapons', 'Campfire']],
|
||||
|
||||
['Overworld Structure 2', False, []],
|
||||
['Overworld Structure 2', False, [], ['Progressive Weapons']],
|
||||
['Overworld Structure 2', False, [], ['Ingot Crafting', 'Campfire']],
|
||||
['Overworld Structure 2', True, ['Progressive Weapons', 'Ingot Crafting']],
|
||||
['Overworld Structure 2', True, ['Progressive Weapons', 'Campfire']],
|
||||
|
||||
['Nether Structure 1', False, []],
|
||||
['Nether Structure 1', False, [], ['Flint and Steel']],
|
||||
['Nether Structure 1', False, [], ['Ingot Crafting']],
|
||||
['Nether Structure 1', False, [], ['Progressive Tools']],
|
||||
['Nether Structure 1', False, ['Progressive Tools', 'Progressive Tools'], ['Bucket', 'Progressive Tools']],
|
||||
['Nether Structure 1', False, [], ['Progressive Weapons']],
|
||||
['Nether Structure 1', True, ['Flint and Steel', 'Ingot Crafting', 'Progressive Tools', 'Bucket', 'Progressive Weapons']],
|
||||
['Nether Structure 1', True, ['Flint and Steel', 'Ingot Crafting', 'Progressive Tools', 'Progressive Tools', 'Progressive Tools', 'Progressive Weapons']],
|
||||
|
||||
['Nether Structure 2', False, []],
|
||||
['Nether Structure 2', False, [], ['Flint and Steel']],
|
||||
['Nether Structure 2', False, [], ['Ingot Crafting']],
|
||||
['Nether Structure 2', False, [], ['Progressive Tools']],
|
||||
['Nether Structure 2', False, ['Progressive Tools', 'Progressive Tools'], ['Bucket', 'Progressive Tools']],
|
||||
['Nether Structure 2', False, [], ['Progressive Weapons']],
|
||||
['Nether Structure 2', True, ['Flint and Steel', 'Ingot Crafting', 'Progressive Tools', 'Bucket', 'Progressive Weapons']],
|
||||
['Nether Structure 2', True, ['Flint and Steel', 'Ingot Crafting', 'Progressive Tools', 'Progressive Tools', 'Progressive Tools', 'Progressive Weapons']],
|
||||
|
||||
['The End Structure', False, []],
|
||||
['The End Structure', False, [], ['Brewing']],
|
||||
['The End Structure', False, ['3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls'], ['3 Ender Pearls']],
|
||||
['The End Structure', False, [], ['Flint and Steel']],
|
||||
['The End Structure', False, [], ['Ingot Crafting']],
|
||||
['The End Structure', False, [], ['Progressive Tools']],
|
||||
['The End Structure', False, ['Progressive Tools', 'Progressive Tools'], ['Bucket', 'Progressive Tools']],
|
||||
['The End Structure', False, [], ['Progressive Weapons']],
|
||||
['The End Structure', False, [], ['Progressive Armor', 'Shield']],
|
||||
['The End Structure', True, ['Flint and Steel', 'Ingot Crafting', 'Progressive Tools', 'Bucket',
|
||||
'Progressive Weapons', 'Progressive Armor',
|
||||
'Brewing', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls']],
|
||||
['The End Structure', True, ['Flint and Steel', 'Ingot Crafting', 'Progressive Tools', 'Bucket',
|
||||
'Progressive Weapons', 'Shield',
|
||||
'Brewing', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls']],
|
||||
['The End Structure', True, ['Flint and Steel', 'Ingot Crafting', 'Progressive Tools', 'Progressive Tools', 'Progressive Tools',
|
||||
'Progressive Weapons', 'Progressive Armor',
|
||||
'Brewing', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls']],
|
||||
['The End Structure', True, ['Flint and Steel', 'Ingot Crafting', 'Progressive Tools', 'Progressive Tools', 'Progressive Tools',
|
||||
'Progressive Weapons', 'Shield',
|
||||
'Brewing', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls']],
|
||||
|
||||
])
|
|
@ -0,0 +1,57 @@
|
|||
from test.TestBase import TestBase
|
||||
from BaseClasses import MultiWorld
|
||||
from worlds.minecraft import minecraft_gen_item_pool
|
||||
from worlds.minecraft.Regions import minecraft_create_regions, link_minecraft_structures
|
||||
from worlds.minecraft.Rules import set_rules
|
||||
from worlds.minecraft.Items import MinecraftItem, item_table
|
||||
import Options
|
||||
|
||||
# Converts the name of an item into an item object
|
||||
def MCItemFactory(items, player: int):
|
||||
ret = []
|
||||
singleton = False
|
||||
if isinstance(items, str):
|
||||
items = [items]
|
||||
singleton = True
|
||||
for item in items:
|
||||
if item in item_table:
|
||||
ret.append(MinecraftItem(item, item_table[item].progression, item_table[item].code, player))
|
||||
else:
|
||||
raise Exception(f"Unknown item {item}")
|
||||
|
||||
if singleton:
|
||||
return ret[0]
|
||||
return ret
|
||||
|
||||
class TestMinecraft(TestBase):
|
||||
|
||||
def setUp(self):
|
||||
self.world = MultiWorld(1)
|
||||
self.world.game[1] = "Minecraft"
|
||||
exclusion_pools = ['hard', 'insane', 'postgame']
|
||||
for pool in exclusion_pools:
|
||||
setattr(self.world, f"include_{pool}_advancements", [False, False])
|
||||
setattr(self.world, "advancement_goal", [0, Options.AdvancementGoal(value=0)])
|
||||
setattr(self.world, "shuffle_structures", [False, False])
|
||||
setattr(self.world, "combat_difficulty", [0, Options.CombatDifficulty(value=1)])
|
||||
minecraft_create_regions(self.world, 1)
|
||||
link_minecraft_structures(self.world, 1)
|
||||
minecraft_gen_item_pool(self.world, 1)
|
||||
set_rules(self.world, 1)
|
||||
|
||||
def _get_items(self, item_pool, all_except):
|
||||
if all_except and len(all_except) > 0:
|
||||
items = self.world.itempool[:]
|
||||
items = [item for item in items if
|
||||
item.name not in all_except and not ("Bottle" in item.name and "AnyBottle" in all_except)]
|
||||
items.extend(MCItemFactory(item_pool[0], 1))
|
||||
else:
|
||||
items = MCItemFactory(item_pool[0], 1)
|
||||
return self.get_state(items)
|
||||
|
||||
def _get_items_partial(self, item_pool, missing_item):
|
||||
new_items = item_pool[0].copy()
|
||||
new_items.remove(missing_item)
|
||||
items = MCItemFactory(new_items, 1)
|
||||
return self.get_state(items)
|
||||
|
|
@ -8,23 +8,26 @@ __all__ = {"lookup_any_item_id_to_name",
|
|||
from .alttp.Items import lookup_id_to_name as alttp
|
||||
from .hk.Items import lookup_id_to_name as hk
|
||||
from .factorio import Technologies
|
||||
from .minecraft.Items import lookup_id_to_name as mc
|
||||
|
||||
lookup_any_item_id_to_name = {**alttp, **hk, **Technologies.lookup_id_to_name}
|
||||
assert len(alttp) + len(hk) + len(Technologies.lookup_id_to_name) == len(lookup_any_item_id_to_name)
|
||||
lookup_any_item_id_to_name = {**alttp, **hk, **Technologies.lookup_id_to_name, **mc}
|
||||
assert len(alttp) + len(hk) + len(Technologies.lookup_id_to_name) + len(mc) == len(lookup_any_item_id_to_name)
|
||||
lookup_any_item_name_to_id = {name: id for id, name in lookup_any_item_id_to_name.items()}
|
||||
|
||||
from .alttp import Regions
|
||||
from .hk import Locations
|
||||
from .minecraft import Locations as Advancements
|
||||
|
||||
lookup_any_location_id_to_name = {**Regions.lookup_id_to_name, **Locations.lookup_id_to_name,
|
||||
**Technologies.lookup_id_to_name}
|
||||
assert len(Regions.lookup_id_to_name) + len(Locations.lookup_id_to_name) + len(Technologies.lookup_id_to_name) == \
|
||||
**Technologies.lookup_id_to_name, **Advancements.lookup_id_to_name}
|
||||
assert len(Regions.lookup_id_to_name) + len(Locations.lookup_id_to_name) + \
|
||||
len(Technologies.lookup_id_to_name) + len(Advancements.lookup_id_to_name) == \
|
||||
len(lookup_any_location_id_to_name)
|
||||
lookup_any_location_name_to_id = {name: id for id, name in lookup_any_location_id_to_name.items()}
|
||||
|
||||
network_data_package = {"lookup_any_location_id_to_name": lookup_any_location_id_to_name,
|
||||
"lookup_any_item_id_to_name": lookup_any_item_id_to_name,
|
||||
"version": 4}
|
||||
"version": 5}
|
||||
|
||||
|
||||
@enum.unique
|
||||
|
|
|
@ -751,6 +751,13 @@ bonk_addresses = [0x4CF6C, 0x4CFBA, 0x4CFE0, 0x4CFFB, 0x4D018, 0x4D01B, 0x4D028,
|
|||
0x4D3F8, 0x4D416, 0x4D420, 0x4D423, 0x4D42D, 0x4D449, 0x4D48C, 0x4D4D9, 0x4D4DC, 0x4D4E3,
|
||||
0x4D504, 0x4D507, 0x4D55E, 0x4D56A]
|
||||
|
||||
def get_nonnative_item_sprite(game):
|
||||
game_to_id = {
|
||||
"Factorio": 0x09, # Hammer
|
||||
"Hollow Knight": 0x21, # Bug Catching Net
|
||||
"Minecraft": 0x13, # Shovel
|
||||
}
|
||||
return game_to_id.get(game, 0x6B) # default to Power Star
|
||||
|
||||
def patch_rom(world, rom, player, team, enemized):
|
||||
local_random = world.rom_seeds[player]
|
||||
|
@ -774,10 +781,7 @@ def patch_rom(world, rom, player, team, enemized):
|
|||
|
||||
if location.item is not None:
|
||||
if location.item.game != "A Link to the Past":
|
||||
if location.item.game == "Factorio":
|
||||
itemid = 0x09 # Hammer Sprite
|
||||
else:
|
||||
itemid = 0x21 # Bug Catching Net
|
||||
itemid = get_nonnative_item_sprite(location.item.game)
|
||||
# Keys in their native dungeon should use the orignal item code for keys
|
||||
elif location.parent_region.dungeon:
|
||||
if location.parent_region.dungeon.is_dungeon_item(location.item):
|
||||
|
@ -1715,7 +1719,20 @@ def write_custom_shops(rom, world, player):
|
|||
if item is None:
|
||||
break
|
||||
if not item['item'] in item_table: # item not native to ALTTP
|
||||
item_code = 0x09 # Hammer
|
||||
# This is a terrible way to do this, please fix later
|
||||
from worlds.hk.Items import lookup_id_to_name as hk_lookup
|
||||
from worlds.factorio.Technologies import lookup_id_to_name as factorio_lookup
|
||||
from worlds.minecraft.Items import lookup_id_to_name as mc_lookup
|
||||
item_name = item['item']
|
||||
if item_name in hk_lookup.values():
|
||||
item_game = 'Hollow Knight'
|
||||
elif item_name in factorio_lookup.values():
|
||||
item_game = 'Factorio'
|
||||
elif item_name in mc_lookup.values():
|
||||
item_game = 'Minecraft'
|
||||
else:
|
||||
item_game = 'Generic'
|
||||
item_code = get_nonnative_item_sprite(item_game)
|
||||
else:
|
||||
item_code = ItemFactory(item['item'], player).code
|
||||
if item['item'] == 'Single Arrow' and item['player'] == 0 and world.retro[player]:
|
||||
|
|
|
@ -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}
|
|
@ -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}
|
|
@ -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')
|
||||
}
|
|
@ -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)
|
||||
|
Loading…
Reference in New Issue