Minecraft updates (#13)
* 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 * Minecraft logic updates Wither kill now considers nether fortresses as a valid source of soul sand A Furious Cocktail now requires beacons for resistance and village access for carrots Uneasy Alliance now requires fishing rod to pull the ghast through the portal On a Rail now requires iron pickaxe to make powered rails Overkill now may require strength II without stone axe, which needs nether access * embed all apmc info into slot_data * updated MC tests for logic changes * put apmc into zipfile Co-authored-by: achuang <alexander.w.chuang@gmail.com>
This commit is contained in:
parent
40751f267b
commit
685de847c4
|
@ -880,13 +880,12 @@ class CollectionState(object):
|
|||
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)
|
||||
return self.fortress_loot(player) 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
|
||||
return self.fortress_loot(player) and (normal_kill or self.can_reach('The Nether', 'Region', player) or self.can_reach('The End', 'Region', player))
|
||||
return self.fortress_loot(player) and normal_kill
|
||||
|
||||
def can_kill_ender_dragon(self, player: int):
|
||||
if self.combat_difficulty(player) == 'easy':
|
||||
|
|
|
@ -40,6 +40,7 @@ if __name__ == "__main__":
|
|||
create_spoiler = multi_mystery_options["create_spoiler"]
|
||||
zip_roms = multi_mystery_options["zip_roms"]
|
||||
zip_diffs = multi_mystery_options["zip_diffs"]
|
||||
zip_apmcs = multi_mystery_options["zip_apmcs"]
|
||||
zip_spoiler = multi_mystery_options["zip_spoiler"]
|
||||
zip_multidata = multi_mystery_options["zip_multidata"]
|
||||
zip_format = multi_mystery_options["zip_format"]
|
||||
|
@ -132,7 +133,7 @@ if __name__ == "__main__":
|
|||
asyncio.run(MultiClient.run_game(os.path.join(output_path, file)))
|
||||
break
|
||||
|
||||
if any((zip_roms, zip_multidata, zip_spoiler, zip_diffs)):
|
||||
if any((zip_roms, zip_multidata, zip_spoiler, zip_diffs, zip_apmcs)):
|
||||
import zipfile
|
||||
|
||||
compression = {1: zipfile.ZIP_DEFLATED,
|
||||
|
@ -177,6 +178,13 @@ if __name__ == "__main__":
|
|||
remove_zipped_file(file)
|
||||
|
||||
|
||||
def _handle_apmc_file(file: str):
|
||||
if zip_apmcs:
|
||||
pack_file(file)
|
||||
if zip_apmcs == 2:
|
||||
remove_zipped_file(file)
|
||||
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor() as pool:
|
||||
futures = []
|
||||
with zipfile.ZipFile(zipname, "w", compression=compression, compresslevel=9) as zf:
|
||||
|
@ -186,6 +194,8 @@ if __name__ == "__main__":
|
|||
futures.append(pool.submit(_handle_sfc_file, file))
|
||||
elif file.endswith(".apbp"):
|
||||
futures.append(pool.submit(_handle_diff_file, file))
|
||||
elif file.endswith(".apmc"):
|
||||
futures.append(pool.submit(_handle_apmc_file, file))
|
||||
|
||||
if zip_multidata and os.path.exists(os.path.join(output_path, multidataname)):
|
||||
pack_file(multidataname)
|
||||
|
|
|
@ -81,6 +81,11 @@ multi_mystery_options:
|
|||
# -1 -> Create them without zipping
|
||||
# 2 -> Delete the non-zipped one.
|
||||
zip_diffs: 2
|
||||
# Zip apmc files for Minecraft
|
||||
# 0 -> Don't zip
|
||||
# 1 -> Create a zip
|
||||
# 2 -> Create a zip and delete apmc files inside of it
|
||||
zip_apmcs: 1
|
||||
# Zip spoiler log
|
||||
# 1 -> Include the spoiler log in the zip
|
||||
# 2 -> Delete the non-zipped one
|
||||
|
|
|
@ -89,15 +89,19 @@ class TestAdvancements(TestMinecraft):
|
|||
["A Furious Cocktail", False, [], ['Brewing']],
|
||||
["A Furious Cocktail", False, [], ['Bottles']],
|
||||
["A Furious Cocktail", False, [], ['Fishing Rod']],
|
||||
["A Furious Cocktail", False, ['Progressive Tools', 'Progressive Tools'], ['Bucket', 'Progressive Tools']],
|
||||
["A Furious Cocktail", True, ['Ingot Crafting', 'Progressive Tools', 'Flint and Steel', 'Bucket',
|
||||
'Progressive Weapons', 'Progressive Armor', 'Brewing', 'Bottles', 'Fishing Rod']],
|
||||
["A Furious Cocktail", False, ['Progressive Tools', 'Progressive Tools'], ['Progressive Tools']],
|
||||
["A Furious Cocktail", True, ['Ingot Crafting', 'Progressive Tools', 'Flint and Steel', 'Progressive Tools', 'Progressive Tools',
|
||||
'Progressive Weapons', 'Progressive Armor', 'Brewing', 'Bottles', 'Fishing Rod']],
|
||||
["A Furious Cocktail", True, ['Ingot Crafting', 'Progressive Tools', 'Flint and Steel', 'Bucket',
|
||||
'Progressive Weapons', 'Shield', 'Brewing', 'Bottles', 'Fishing Rod']],
|
||||
["A Furious Cocktail", True, ['Ingot Crafting', 'Progressive Tools', 'Flint and Steel', 'Progressive Tools', 'Progressive Tools',
|
||||
'Progressive Weapons', 'Shield', 'Brewing', 'Bottles', 'Fishing Rod']],
|
||||
'Progressive Weapons', 'Progressive Weapons', 'Progressive Weapons',
|
||||
'Progressive Armor', 'Progressive Armor',
|
||||
'Enchanting', 'Brewing', 'Bottles', 'Resource Blocks', 'Fishing Rod']],
|
||||
# ["A Furious Cocktail", True, ['Ingot Crafting', 'Progressive Tools', 'Flint and Steel', 'Bucket',
|
||||
# 'Progressive Weapons', 'Progressive Armor', 'Brewing', 'Bottles', 'Fishing Rod']],
|
||||
# ["A Furious Cocktail", True, ['Ingot Crafting', 'Progressive Tools', 'Flint and Steel', 'Progressive Tools', 'Progressive Tools',
|
||||
# 'Progressive Weapons', 'Progressive Armor', 'Brewing', 'Bottles', 'Fishing Rod']],
|
||||
# ["A Furious Cocktail", True, ['Ingot Crafting', 'Progressive Tools', 'Flint and Steel', 'Bucket',
|
||||
# 'Progressive Weapons', 'Shield', 'Brewing', 'Bottles', 'Fishing Rod']],
|
||||
# ["A Furious Cocktail", True, ['Ingot Crafting', 'Progressive Tools', 'Flint and Steel', 'Progressive Tools', 'Progressive Tools',
|
||||
# 'Progressive Weapons', 'Shield', 'Brewing', 'Bottles', 'Fishing Rod']],
|
||||
])
|
||||
|
||||
def test_42007(self):
|
||||
|
@ -934,7 +938,8 @@ class TestAdvancements(TestMinecraft):
|
|||
["Uneasy Alliance", False, [], ['Ingot Crafting']],
|
||||
["Uneasy Alliance", False, [], ['Flint and Steel']],
|
||||
["Uneasy Alliance", False, [], ['Progressive Tools', 'Progressive Tools'], ['Progressive Tools']],
|
||||
["Uneasy Alliance", True, ['Progressive Tools', 'Progressive Tools', 'Progressive Tools', 'Flint and Steel', 'Ingot Crafting']],
|
||||
["Uneasy Alliance", False, [], ['Fishing Rod']],
|
||||
["Uneasy Alliance", True, ['Progressive Tools', 'Progressive Tools', 'Progressive Tools', 'Flint and Steel', 'Ingot Crafting', 'Fishing Rod']],
|
||||
])
|
||||
|
||||
def test_42070(self):
|
||||
|
@ -1076,8 +1081,8 @@ class TestAdvancements(TestMinecraft):
|
|||
self.run_location_tests([
|
||||
["On a Rail", False, []],
|
||||
["On a Rail", False, [], ['Ingot Crafting']],
|
||||
["On a Rail", False, [], ['Progressive Tools']],
|
||||
["On a Rail", True, ['Ingot Crafting', 'Progressive Tools']],
|
||||
["On a Rail", False, ['Progressive Tools'], ['Progressive Tools', 'Progressive Tools']],
|
||||
["On a Rail", True, ['Ingot Crafting', 'Progressive Tools', 'Progressive Tools']],
|
||||
])
|
||||
|
||||
def test_42086(self):
|
||||
|
|
|
@ -45,7 +45,11 @@ def set_rules(world: MultiWorld, player: int):
|
|||
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("A Furious Cocktail", player), lambda state: state.can_brew_potions(player) and
|
||||
state.has("Fishing Rod", player) and # Water Breathing
|
||||
state.can_reach('The Nether', 'Region', player) and # Regeneration, Fire Resistance, gold nuggets
|
||||
state.can_reach('Village', 'Region', player) and # Night Vision, Invisibility
|
||||
state.can_reach('Bring Home the Beacon', 'Location', player)) # Resistance
|
||||
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))
|
||||
|
@ -116,7 +120,7 @@ def set_rules(world: MultiWorld, player: int):
|
|||
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("Uneasy Alliance", player), lambda state: state.has_diamond_pickaxe(player) and state.has('Fishing Rod', 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
|
||||
|
@ -134,10 +138,10 @@ def set_rules(world: MultiWorld, player: int):
|
|||
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("On a Rail", player), lambda state: state.has_iron_ingots(player) and state.has('Progressive Tools', player, 2)) # powered rails
|
||||
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("Overkill", player), lambda state: state.can_brew_potions(player) and (state.has("Progressive Weapons", player) or state.can_reach('The Nether', 'Region', player))) # strength 1 + stone axe crit OR strength 2 + wood 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))
|
||||
|
|
|
@ -9,12 +9,9 @@ from Options import minecraft_options
|
|||
|
||||
client_version = (0, 3)
|
||||
|
||||
def generate_mc_data(world: MultiWorld, player: int):
|
||||
import base64, json
|
||||
from Utils import output_path
|
||||
|
||||
def get_mc_data(world: MultiWorld, player: int):
|
||||
exits = ["Overworld Structure 1", "Overworld Structure 2", "Nether Structure 1", "Nether Structure 2", "The End Structure"]
|
||||
data = {
|
||||
return {
|
||||
'world_seed': Random(world.rom_seeds[player]).getrandbits(32), # consistent and doesn't interfere with other generation
|
||||
'seed_name': world.seed_name,
|
||||
'player_name': world.get_player_names(player),
|
||||
|
@ -23,16 +20,20 @@ def generate_mc_data(world: MultiWorld, player: int):
|
|||
'structures': {exit: world.get_entrance(exit, player).connected_region.name for exit in exits}
|
||||
}
|
||||
|
||||
def generate_mc_data(world: MultiWorld, player: int):
|
||||
import base64, json
|
||||
from Utils import output_path
|
||||
|
||||
data = get_mc_data(world, player)
|
||||
filename = f"AP_{world.seed_name}_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 = {}
|
||||
slot_data = get_mc_data(world, player)
|
||||
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.
|
||||
|
|
Loading…
Reference in New Issue