diff --git a/.gitignore b/.gitignore index 1d7b3795..26885103 100644 --- a/.gitignore +++ b/.gitignore @@ -152,3 +152,7 @@ dmypy.json cython_debug/ Archipelago.zip + +#minecraft server stuff +jdk*/ +minecraft*/ \ No newline at end of file diff --git a/MinecraftClient.py b/MinecraftClient.py index 509c70b0..e178c00b 100644 --- a/MinecraftClient.py +++ b/MinecraftClient.py @@ -15,6 +15,7 @@ atexit.register(input, "Press enter to exit.") # 1 or more digits followed by m or g, then optional b max_heap_re = re.compile(r"^\d+[mMgG][bB]?$") +forge_version = "1.17.1-37.0.109" def prompt_yes_no(prompt): @@ -30,15 +31,6 @@ def prompt_yes_no(prompt): print('Please respond with "y" or "n".') -# Find Forge jar file; raise error if not found -def find_forge_jar(forge_dir): - for entry in os.scandir(forge_dir): - if ".jar" in entry.name and "forge" in entry.name: - logging.info(f"Found forge .jar: {entry.name}") - return entry.name - raise FileNotFoundError(f"Could not find forge .jar in {forge_dir}.") - - # Create mods folder if needed; find AP randomizer jar; return None if not found. def find_ap_randomizer_jar(forge_dir): mods_dir = os.path.join(forge_dir, 'mods') @@ -77,37 +69,57 @@ def replace_apmc_files(forge_dir, apmc_file): logging.info(f"Copied {os.path.basename(apmc_file)} to {apdata_dir}") +def read_apmc_file(apmc_file): + from base64 import b64decode + import json + + with open(apmc_file, 'r') as f: + data = json.loads(b64decode(f.read())) + return data + + # Check mod version, download new mod from GitHub releases page if needed. -def update_mod(forge_dir): +def update_mod(forge_dir, apmc_file, get_prereleases=False): ap_randomizer = find_ap_randomizer_jar(forge_dir) + if apmc_file is not None: + data = read_apmc_file(apmc_file) + minecraft_version = data.get('minecraft_version', '') + client_releases_endpoint = "https://api.github.com/repos/KonoTyran/Minecraft_AP_Randomizer/releases" resp = requests.get(client_releases_endpoint) if resp.status_code == 200: # OK - latest_release = resp.json()[0] - if ap_randomizer != latest_release['assets'][0]['name']: - logging.info(f"A new release of the Minecraft AP randomizer mod was found: " - f"{latest_release['assets'][0]['name']}") - if ap_randomizer is not None: - logging.info(f"Your current mod is {ap_randomizer}.") - else: - logging.info(f"You do not have the AP randomizer mod installed.") - if prompt_yes_no("Would you like to update?"): - old_ap_mod = os.path.join(forge_dir, 'mods', ap_randomizer) if ap_randomizer is not None else None - new_ap_mod = os.path.join(forge_dir, 'mods', latest_release['assets'][0]['name']) - logging.info("Downloading AP randomizer mod. This may take a moment...") - apmod_resp = requests.get(latest_release['assets'][0]['browser_download_url']) - if apmod_resp.status_code == 200: - with open(new_ap_mod, 'wb') as f: - f.write(apmod_resp.content) - logging.info(f"Wrote new mod file to {new_ap_mod}") - if old_ap_mod is not None: - os.remove(old_ap_mod) - logging.info(f"Removed old mod file from {old_ap_mod}") + try: + latest_release = next(filter(lambda release: (not release['prerelease'] or get_prereleases) and + (apmc_file is None or minecraft_version in release['assets'][0]['name']), + resp.json())) + if ap_randomizer != latest_release['assets'][0]['name']: + logging.info(f"A new release of the Minecraft AP randomizer mod was found: " + f"{latest_release['assets'][0]['name']}") + if ap_randomizer is not None: + logging.info(f"Your current mod is {ap_randomizer}.") else: - logging.error(f"Error retrieving the randomizer mod (status code {apmod_resp.status_code}).") - logging.error(f"Please report this issue on the Archipelago Discord server.") - sys.exit(1) + logging.info(f"You do not have the AP randomizer mod installed.") + if prompt_yes_no("Would you like to update?"): + old_ap_mod = os.path.join(forge_dir, 'mods', ap_randomizer) if ap_randomizer is not None else None + new_ap_mod = os.path.join(forge_dir, 'mods', latest_release['assets'][0]['name']) + logging.info("Downloading AP randomizer mod. This may take a moment...") + apmod_resp = requests.get(latest_release['assets'][0]['browser_download_url']) + if apmod_resp.status_code == 200: + with open(new_ap_mod, 'wb') as f: + f.write(apmod_resp.content) + logging.info(f"Wrote new mod file to {new_ap_mod}") + if old_ap_mod is not None: + os.remove(old_ap_mod) + logging.info(f"Removed old mod file from {old_ap_mod}") + else: + logging.error(f"Error retrieving the randomizer mod (status code {apmod_resp.status_code}).") + logging.error(f"Please report this issue on the Archipelago Discord server.") + sys.exit(1) + except StopIteration: + logging.warning(f"No compatible mod version found for {minecraft_version}.") + if not prompt_yes_no("Run server anyway?"): + sys.exit(0) else: logging.error(f"Error checking for randomizer mod updates (status code {resp.status_code}).") logging.error(f"If this was not expected, please report this issue on the Archipelago Discord server.") @@ -139,11 +151,69 @@ def check_eula(forge_dir): sys.exit(0) -# Run the Forge server. Return process object -def run_forge_server(forge_dir, heap_arg): - forge_server = find_forge_jar(forge_dir) +# get the current JDK16 +def find_jdk_dir() -> str: + for entry in os.listdir(): + if os.path.isdir(entry) and entry.startswith("jdk16"): + return os.path.abspath(entry) - java_exe = os.path.abspath(os.path.join('jre8', 'bin', 'java.exe')) + +# get the java exe location +def find_jdk() -> str: + jdk = find_jdk_dir() + jdk_exe = os.path.join(jdk, "bin", "java.exe") + if os.path.isfile(jdk_exe): + return jdk_exe + + +# Download Corretto 16 (Amazon JDK) +def download_java(): + jdk = find_jdk_dir() + if jdk is not None: + print(f"Removing old JDK...") + from shutil import rmtree + rmtree(jdk) + + print(f"Downloading Java...") + jdk_url = "https://corretto.aws/downloads/latest/amazon-corretto-16-x64-windows-jdk.zip" + resp = requests.get(jdk_url) + if resp.status_code == 200: # OK + print(f"Extracting...") + import zipfile + from io import BytesIO + with zipfile.ZipFile(BytesIO(resp.content)) as zf: + zf.extractall() + else: + print(f"Error downloading Java (status code {resp.status_code}).") + print(f"If this was not expected, please report this issue on the Archipelago Discord server.") + if not prompt_yes_no("Continue anyways?"): + sys.exit(0) + + +# download and install forge +def install_forge(directory: str): + jdk = find_jdk() + if jdk is not None: + print(f"Downloading Forge {forge_version}...") + forge_url = f"https://maven.minecraftforge.net/net/minecraftforge/forge/{forge_version}/forge-{forge_version}-installer.jar" + resp = requests.get(forge_url) + if resp.status_code == 200: # OK + forge_install_jar = os.path.join(directory, "forge_install.jar") + if not os.path.exists(directory): + os.mkdir(directory) + with open(forge_install_jar, 'wb') as f: + f.write(resp.content) + print(f"Installing Forge...") + argstring = ' '.join([jdk, "-jar", "\"" + forge_install_jar+ "\"", "--installServer", "\"" + directory + "\""]) + install_process = Popen(argstring) + install_process.wait() + os.remove(forge_install_jar) + + +# Run the Forge server. Return process object +def run_forge_server(forge_dir: str, heap_arg): + + java_exe = find_jdk() if not os.path.isfile(java_exe): java_exe = "java" # try to fall back on java in the PATH @@ -152,7 +222,13 @@ def run_forge_server(forge_dir, heap_arg): heap_arg = heap_arg[:-1] heap_arg = "-Xmx" + heap_arg - argstring = ' '.join([java_exe, heap_arg, "-jar", forge_server, "-nogui"]) + args_file = os.path.join(forge_dir, "libraries", "net", "minecraftforge", "forge", forge_version, "win_args.txt") + win_args = [] + with open(args_file) as argfile: + for line in argfile: + win_args.append(line.strip()) + + argstring = ' '.join([java_exe, heap_arg] + win_args + ["-nogui"]) logging.info(f"Running Forge server: {argstring}") os.chdir(forge_dir) return Popen(argstring) @@ -162,6 +238,10 @@ if __name__ == '__main__': Utils.init_logging("MinecraftClient") parser = argparse.ArgumentParser() parser.add_argument("apmc_file", default=None, nargs='?', help="Path to an Archipelago Minecraft data file (.apmc)") + parser.add_argument('--install', '-i', dest='install', default=False, action='store_true', + help="Download and install Java and the Forge server. Does not launch the client afterwards.") + parser.add_argument('--prerelease', default=False, action='store_true', + help="Auto-update prerelease versions.") args = parser.parse_args() apmc_file = os.path.abspath(args.apmc_file) if args.apmc_file else None @@ -173,6 +253,12 @@ if __name__ == '__main__': forge_dir = options["minecraft_options"]["forge_directory"] max_heap = options["minecraft_options"]["max_heap_size"] + if args.install: + print("Installing Java and Minecraft Forge") + download_java() + install_forge(forge_dir) + sys.exit(0) + if apmc_file is not None and not os.path.isfile(apmc_file): raise FileNotFoundError(f"Path {apmc_file} does not exist or could not be accessed.") if not os.path.isdir(forge_dir): @@ -180,7 +266,7 @@ if __name__ == '__main__': if not max_heap_re.match(max_heap): raise Exception(f"Max heap size {max_heap} in incorrect format. Use a number followed by M or G, e.g. 512M or 2G.") - update_mod(forge_dir) + update_mod(forge_dir, apmc_file, args.prerelease) replace_apmc_files(forge_dir, apmc_file) check_eula(forge_dir) server_process = run_forge_server(forge_dir, max_heap) diff --git a/Options.py b/Options.py index 9fc2da5a..03fb0c84 100644 --- a/Options.py +++ b/Options.py @@ -277,8 +277,8 @@ class OptionList(Option): supports_weighting = False value: list - def __init__(self, value: typing.List[str, typing.Any]): - self.value = value + def __init__(self, value: typing.List[typing.Any]): + self.value = value or [] super(OptionList, self).__init__() @classmethod diff --git a/WebHostLib/static/assets/tutorial/minecraft/minecraft_en.md b/WebHostLib/static/assets/tutorial/minecraft/minecraft_en.md index 5cb7ef82..44eeed6f 100644 --- a/WebHostLib/static/assets/tutorial/minecraft/minecraft_en.md +++ b/WebHostLib/static/assets/tutorial/minecraft/minecraft_en.md @@ -1,11 +1,9 @@ # Minecraft Randomizer Setup Guide -#Automatic Hosting Install -- download and install [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) and choose the `Minecraft Client` module - ## Required Software -- [Minecraft Java Edition](https://www.minecraft.net/en-us/store/minecraft-java-edition) +- [Minecraft Java Edition](https://www.minecraft.net/en-us/store/minecraft-java-edition) (update 1.17.1) +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) (select `Minecraft Client` during installation.) ## Configuring your YAML file @@ -16,73 +14,7 @@ each player to enjoy an experience customized for their taste, and different pla can all have different options. ### Where do I get a YAML file? -A basic minecraft yaml will look like this. -```yaml -description: Basic Minecraft Yaml -# Your name in-game. Spaces will be replaced with underscores and -# there is a 16 character limit -name: YourName -game: Minecraft - -# Shared Options supported by all games: -accessibility: locations -progression_balancing: on -# Minecraft Specific Options - -Minecraft: - # Number of advancements required (87 max) to spawn the Ender Dragon and complete the game. - advancement_goal: 50 - - # Number of dragon egg shards to collect (30 max) before the Ender Dragon will spawn. - egg_shards_required: 10 - - # Number of egg shards available in the pool (30 max). - egg_shards_available: 15 - - # Modifies the level of items logically required for - # exploring dangerous areas and fighting bosses. - combat_difficulty: - easy: 0 - normal: 1 - hard: 0 - - # Junk-fills certain RNG-reliant or tedious advancements. - include_hard_advancements: - on: 0 - off: 1 - - # Junk-fills extremely difficult advancements; - # this is only How Did We Get Here? and Adventuring Time. - include_insane_advancements: - on: 0 - off: 1 - - # Some advancements require defeating the Ender Dragon; - # this will junk-fill them, so you won't have to finish them to send some items. - include_postgame_advancements: - on: 0 - off: 1 - - # Enables shuffling of villages, outposts, fortresses, bastions, and end cities. - shuffle_structures: - on: 0 - off: 1 - - # Adds structure compasses to the item pool, - # which point to the nearest indicated structure. - structure_compasses: - on: 0 - off: 1 - - # Replaces a percentage of junk items with bee traps - # which spawn multiple angered bees around every player when received. - bee_traps: - 0: 1 - 25: 0 - 50: 0 - 75: 0 - 100: 0 -``` +you can customize your settings by visiting the [minecraft player settings](/games/Minecraft/player-settings) ## Joining a MultiWorld Game @@ -93,38 +25,34 @@ When you join a multiworld game, you will be asked to provide your YAML file to is done, the host will provide you with either a link to download your data file, or with a zip file containing everyone's data files. Your data file should have a `.apmc` extension. -double click on your `.apmc` file to have the minecraft client auto-launch the installed forge server. +double-click on your `.apmc` file to have the minecraft client auto-launch the installed forge server. +make sure to leave this window open as this is your server console. ### Connect to the MultiServer -After having placed your data file in the `APData` folder, start the Forge server and make sure you have OP -status by typing `/op YourMinecraftUsername` in the forge server console then connecting in your Minecraft client. +Using minecraft 1.17.1 connect to the server `localhost`. Once in game type `/connect (Port) (Password)` where `` is the address of the Archipelago server. `(Port)` is only required if the Archipelago server is not using the default port of 38281. `(Password)` is only required if the Archipleago server you are using has a password set. ### Play the game -When the console tells you that you have joined the room, you're ready to begin playing. Congratulations +When the console tells you that you have joined the room, you're all set. Congratulations on successfully joining a multiworld game! At this point any additional minecraft players may connect to your -forge server. +forge server. to star the game once everyone is ready type `/start`. +### Useful commands +- `!help` displays a list all server commands +- `!hint` will display how many hint points you have, along with any hints that have been given that are related to your game. +- `!hint (item)` will ask the server to tell you where (item) is +- `!hint_location (location)` will ask the server to tell you what item is on (location) -## Manual Installation Procedures -this is only required if you wish to set up a forge install yourself, its recommended to just use the Archipelago Installer. -###Required Software -- [Minecraft Forge](https://files.minecraftforge.net/net/minecraftforge/forge/index_1.16.5.html) +## Manual Installation +it is highly recommended to ues the Archipelago installer to handle the installation of the forge server for you. +support will not be given for those wishing to manually install forge. but for those of you who know how, and wish to +do so the following links are the versions of the software we use. +### Manual install Software links +- [Minecraft Forge](https://files.minecraftforge.net/net/minecraftforge/forge/index_1.17.1.html) - [Minecraft Archipelago Randomizer Mod](https://github.com/KonoTyran/Minecraft_AP_Randomizer/releases) **DO NOT INSTALL THIS ON YOUR CLIENT** -### Dedicated Server Setup -Only one person has to do this setup and host a dedicated server for everyone else playing to connect to. -1. Download the 1.16.5 **Minecraft Forge** installer from the link above, making sure to download the most recent recommended version. +- [Java 16](https://docs.aws.amazon.com/corretto/latest/corretto-16-ug/downloads-list.html) -2. Run the `forge-1.16.5-xx.x.x-installer.jar` file and choose **install server**. - - On this page you will also choose where to install the server to remember this directory it's important in the next step. - -3. Navigate to where you installed the server and open `forge-1.16.5-xx.x.x.jar` - - Upon first launch of the server it will close and ask you to accept Minecraft's EULA. There will be a new file called `eula.txt` that contains a link to Minecraft's EULA, and a line that you need to change to `eula=true` to accept Minecraft's EULA. - - This will create the appropriate directories for you to place the files in the following step. - -4. Place the `aprandomizer-x.x.x.jar` from the link above file into the `mods` folder of the above installation of your forge server. - - Once again run the server, it will load up and generate the required directory `APData` for when you are ready to play a game! diff --git a/WebHostLib/templates/minecraftTracker.html b/WebHostLib/templates/minecraftTracker.html index 5389b025..c1d6fa97 100644 --- a/WebHostLib/templates/minecraftTracker.html +++ b/WebHostLib/templates/minecraftTracker.html @@ -42,6 +42,7 @@ + diff --git a/WebHostLib/tracker.py b/WebHostLib/tracker.py index 567257bf..59102707 100644 --- a/WebHostLib/tracker.py +++ b/WebHostLib/tracker.py @@ -423,19 +423,21 @@ def __renderMinecraftTracker(multisave: Dict[str, Any], room: Room, locations: D "Fishing Rod": "https://static.wikia.nocookie.net/minecraft_gamepedia/images/7/7f/Fishing_Rod_JE2_BE2.png", "Campfire": "https://static.wikia.nocookie.net/minecraft_gamepedia/images/9/91/Campfire_JE2_BE2.gif", "Water Bottle": "https://static.wikia.nocookie.net/minecraft_gamepedia/images/7/75/Water_Bottle_JE2_BE2.png", + "Spyglass": "https://static.wikia.nocookie.net/minecraft_gamepedia/images/c/c1/Spyglass_JE2_BE1.png", "Dragon Head": "https://static.wikia.nocookie.net/minecraft_gamepedia/images/b/b6/Dragon_Head.png", } minecraft_location_ids = { - "Story": [42073, 42080, 42081, 42023, 42082, 42027, 42039, 42085, 42002, 42009, 42010, - 42070, 42041, 42049, 42090, 42004, 42031, 42025, 42029, 42051, 42077, 42089], + "Story": [42073, 42023, 42027, 42039, 42002, 42009, 42010, 42070, + 42041, 42049, 42004, 42031, 42025, 42029, 42051, 42077], "Nether": [42017, 42044, 42069, 42058, 42034, 42060, 42066, 42076, 42064, 42071, 42021, - 42062, 42008, 42061, 42033, 42011, 42006, 42019, 42000, 42040, 42001, 42015, 42014], + 42062, 42008, 42061, 42033, 42011, 42006, 42019, 42000, 42040, 42001, 42015, 42014], "The End": [42052, 42005, 42012, 42032, 42030, 42042, 42018, 42038, 42046], - "Adventure": [42047, 42086, 42087, 42050, 42059, 42055, 42072, 42003, 42035, 42016, 42020, - 42048, 42054, 42068, 42043, 42074, 42075, 42024, 42026, 42037, 42045, 42056, 42088], - "Husbandry": [42065, 42067, 42078, 42022, 42007, 42079, 42013, 42028, - 42036, 42057, 42063, 42053, 42083, 42084, 42091] + "Adventure": [42047, 42050, 42096, 42097, 42098, 42059, 42055, 42072, 42003, 42035, 42016, 42020, + 42048, 42054, 42068, 42043, 42074, 42075, 42024, 42026, 42037, 42045, 42056, 42099, 42100], + "Husbandry": [42065, 42067, 42078, 42022, 42007, 42079, 42013, 42028, 42036, + 42057, 42063, 42053, 42102, 42101, 42092, 42093, 42094, 42095], + "Archipelago": [42080, 42081, 42082, 42083, 42084, 42085, 42086, 42087, 42088, 42089, 42090, 42091], } display_data = {} diff --git a/inno_setup_310.iss b/inno_setup_310.iss index 09125537..13b0ac4c 100644 --- a/inno_setup_310.iss +++ b/inno_setup_310.iss @@ -86,9 +86,6 @@ Source: "{#sourcepath}\ArchipelagoMinecraftClient.exe"; DestDir: "{app}"; Flags: Source: "{#sourcepath}\ArchipelagoOoTAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/oot Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall -;minecraft temp files -Source: "{tmp}\forge-installer.jar"; DestDir: "{app}"; Flags: skipifsourcedoesntexist external deleteafterinstall; Components: client/minecraft - [Icons] Name: "{group}\{#MyAppName} Folder"; Filename: "{app}"; Name: "{group}\{#MyAppName} Server"; Filename: "{app}\{#MyAppExeName}"; Components: server @@ -105,7 +102,7 @@ Name: "{commondesktop}\{#MyAppName} Factorio Client"; Filename: "{app}\Archipela Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..." Filename: "{app}\ArchipelagoLttPAdjuster"; Parameters: "--update_sprites"; StatusMsg: "Updating Sprite Library..."; Components: client/sni/lttp or generator/lttp -Filename: "{app}\jre8\bin\java.exe"; Parameters: "-jar ""{app}\forge-installer.jar"" --installServer ""{app}\Minecraft Forge server"""; Flags: runhidden; Check: IsForgeNeeded(); StatusMsg: "Installing Forge Server..."; Components: client/minecraft +Filename: "{app}\ArchipelagoMinecraftClient.exe"; Parameters: "--install"; StatusMsg: "Installing Forge Server..."; Components: client/minecraft [UninstallDelete] Type: dirifempty; Name: "{app}" @@ -145,7 +142,6 @@ Root: HKCR; Subkey: "{#MyAppName}multidata\shell\open\command"; ValueData: """{ const SHCONTCH_NOPROGRESSBOX = 4; SHCONTCH_RESPONDYESTOALL = 16; - FORGE_VERSION = '1.16.5-36.2.0'; // See: https://stackoverflow.com/a/51614652/2287576 function IsVCRedist64BitNeeded(): boolean; @@ -167,48 +163,6 @@ begin end; end; -function IsForgeNeeded(): boolean; -begin - Result := True; - if (FileExists(ExpandConstant('{app}')+'\Minecraft Forge Server\forge-'+FORGE_VERSION+'.jar')) then - Result := False; -end; - -function IsJavaNeeded(): boolean; -begin - Result := True; - if (FileExists(ExpandConstant('{app}')+'\jre8\bin\java.exe')) then - Result := False; -end; - -function OnDownloadMinecraftProgress(const Url, FileName: String; const Progress, ProgressMax: Int64): Boolean; -begin - if Progress = ProgressMax then - Log(Format('Successfully downloaded Minecraft additional files to {tmp}: %s', [FileName])); - Result := True; -end; - -procedure UnZip(ZipPath, TargetPath: string); -var - Shell: Variant; - ZipFile: Variant; - TargetFolder: Variant; -begin - Shell := CreateOleObject('Shell.Application'); - - ZipFile := Shell.NameSpace(ZipPath); - if VarIsClear(ZipFile) then - RaiseException( - Format('ZIP file "%s" does not exist or cannot be opened', [ZipPath])); - - TargetFolder := Shell.NameSpace(TargetPath); - if VarIsClear(TargetFolder) then - RaiseException(Format('Target path "%s" does not exist', [TargetPath])); - - TargetFolder.CopyHere( - ZipFile.Items, SHCONTCH_NOPROGRESSBOX or SHCONTCH_RESPONDYESTOALL); -end; - var R : longint; var lttprom: string; @@ -223,8 +177,6 @@ var SoERomFilePage: TInputFileWizardPage; var ootrom: string; var OoTROMFilePage: TInputFileWizardPage; -var MinecraftDownloadPage: TDownloadWizardPage; - function GetSNESMD5OfFile(const rom: string): string; var data: AnsiString; begin @@ -272,11 +224,6 @@ begin '.sfc'); end; -procedure AddMinecraftDownloads(); -begin - MinecraftDownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), @OnDownloadMinecraftProgress); -end; - procedure AddOoTRomPage(); begin ootrom := FileSearch('The Legend of Zelda - Ocarina of Time.z64', WizardDirValue()); @@ -309,33 +256,7 @@ end; function NextButtonClick(CurPageID: Integer): Boolean; begin - if (CurPageID = wpReady) and (WizardIsComponentSelected('client/minecraft')) then begin - MinecraftDownloadPage.Clear; - if(IsForgeNeeded()) then - MinecraftDownloadPage.Add('https://maven.minecraftforge.net/net/minecraftforge/forge/'+FORGE_VERSION+'/forge-'+FORGE_VERSION+'-installer.jar','forge-installer.jar',''); - if(IsJavaNeedeD()) then - MinecraftDownloadPage.Add('https://corretto.aws/downloads/latest/amazon-corretto-8-x64-windows-jre.zip','java.zip',''); - MinecraftDownloadPage.Show; - try - try - MinecraftDownloadPage.Download; - Result := True; - except - if MinecraftDownloadPage.AbortedByUser then - Log('Aborted by user.') - else - SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbCriticalError, MB_OK, IDOK); - Result := False; - end; - finally - if( isJavaNeeded() ) then - if(ForceDirectories(ExpandConstant('{app}'))) then - UnZip(ExpandConstant('{tmp}')+'\java.zip',ExpandConstant('{app}')); - MinecraftDownloadPage.Hide; - end; - Result := True; - end - else if (assigned(LttPROMFilePage)) and (CurPageID = LttPROMFilePage.ID) then + if (assigned(LttPROMFilePage)) and (CurPageID = LttPROMFilePage.ID) then Result := not (LttPROMFilePage.Values[0] = '') else if (assigned(SMROMFilePage)) and (CurPageID = SMROMFilePage.ID) then Result := not (SMROMFilePage.Values[0] = '') @@ -426,8 +347,6 @@ begin soerom := CheckRom('Secret of Evermore (USA).sfc', '6e9c94511d04fac6e0a1e582c170be3a'); if Length(soerom) = 0 then SoEROMFilePage:= AddRomPage('Secret of Evermore (USA).sfc'); - - AddMinecraftDownloads(); end; @@ -442,4 +361,4 @@ begin Result := not (WizardIsComponentSelected('generator/soe')); if (assigned(OoTROMFilePage)) and (PageID = OoTROMFilePage.ID) then Result := not (WizardIsComponentSelected('generator/oot')); -end; +end; \ No newline at end of file diff --git a/inno_setup_38.iss b/inno_setup_38.iss index c6f9ed75..35c8c414 100644 --- a/inno_setup_38.iss +++ b/inno_setup_38.iss @@ -86,9 +86,6 @@ Source: "{#sourcepath}\ArchipelagoMinecraftClient.exe"; DestDir: "{app}"; Flags: Source: "{#sourcepath}\ArchipelagoOoTAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/oot Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall -;minecraft temp files -Source: "{tmp}\forge-installer.jar"; DestDir: "{app}"; Flags: skipifsourcedoesntexist external deleteafterinstall; Components: client/minecraft - [Icons] Name: "{group}\{#MyAppName} Folder"; Filename: "{app}"; Name: "{group}\{#MyAppName} Server"; Filename: "{app}\{#MyAppExeName}"; Components: server @@ -105,7 +102,7 @@ Name: "{commondesktop}\{#MyAppName} Factorio Client"; Filename: "{app}\Archipela Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..." Filename: "{app}\ArchipelagoLttPAdjuster"; Parameters: "--update_sprites"; StatusMsg: "Updating Sprite Library..."; Components: client/sni/lttp or generator/lttp -Filename: "{app}\jre8\bin\java.exe"; Parameters: "-jar ""{app}\forge-installer.jar"" --installServer ""{app}\Minecraft Forge server"""; Flags: runhidden; Check: IsForgeNeeded(); StatusMsg: "Installing Forge Server..."; Components: client/minecraft +Filename: "{app}\ArchipelagoMinecraftClient.exe"; Parameters: "--install"; StatusMsg: "Installing Forge Server..."; Components: client/minecraft [UninstallDelete] Type: dirifempty; Name: "{app}" @@ -145,7 +142,6 @@ Root: HKCR; Subkey: "{#MyAppName}multidata\shell\open\command"; ValueData: """{ const SHCONTCH_NOPROGRESSBOX = 4; SHCONTCH_RESPONDYESTOALL = 16; - FORGE_VERSION = '1.16.5-36.2.0'; // See: https://stackoverflow.com/a/51614652/2287576 function IsVCRedist64BitNeeded(): boolean; @@ -167,48 +163,6 @@ begin end; end; -function IsForgeNeeded(): boolean; -begin - Result := True; - if (FileExists(ExpandConstant('{app}')+'\Minecraft Forge Server\forge-'+FORGE_VERSION+'.jar')) then - Result := False; -end; - -function IsJavaNeeded(): boolean; -begin - Result := True; - if (FileExists(ExpandConstant('{app}')+'\jre8\bin\java.exe')) then - Result := False; -end; - -function OnDownloadMinecraftProgress(const Url, FileName: String; const Progress, ProgressMax: Int64): Boolean; -begin - if Progress = ProgressMax then - Log(Format('Successfully downloaded Minecraft additional files to {tmp}: %s', [FileName])); - Result := True; -end; - -procedure UnZip(ZipPath, TargetPath: string); -var - Shell: Variant; - ZipFile: Variant; - TargetFolder: Variant; -begin - Shell := CreateOleObject('Shell.Application'); - - ZipFile := Shell.NameSpace(ZipPath); - if VarIsClear(ZipFile) then - RaiseException( - Format('ZIP file "%s" does not exist or cannot be opened', [ZipPath])); - - TargetFolder := Shell.NameSpace(TargetPath); - if VarIsClear(TargetFolder) then - RaiseException(Format('Target path "%s" does not exist', [TargetPath])); - - TargetFolder.CopyHere( - ZipFile.Items, SHCONTCH_NOPROGRESSBOX or SHCONTCH_RESPONDYESTOALL); -end; - var R : longint; var lttprom: string; @@ -223,8 +177,6 @@ var SoERomFilePage: TInputFileWizardPage; var ootrom: string; var OoTROMFilePage: TInputFileWizardPage; -var MinecraftDownloadPage: TDownloadWizardPage; - function GetSNESMD5OfFile(const rom: string): string; var data: AnsiString; begin @@ -272,11 +224,6 @@ begin '.sfc'); end; -procedure AddMinecraftDownloads(); -begin - MinecraftDownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), @OnDownloadMinecraftProgress); -end; - procedure AddOoTRomPage(); begin ootrom := FileSearch('The Legend of Zelda - Ocarina of Time.z64', WizardDirValue()); @@ -309,33 +256,7 @@ end; function NextButtonClick(CurPageID: Integer): Boolean; begin - if (CurPageID = wpReady) and (WizardIsComponentSelected('client/minecraft')) then begin - MinecraftDownloadPage.Clear; - if(IsForgeNeeded()) then - MinecraftDownloadPage.Add('https://maven.minecraftforge.net/net/minecraftforge/forge/'+FORGE_VERSION+'/forge-'+FORGE_VERSION+'-installer.jar','forge-installer.jar',''); - if(IsJavaNeedeD()) then - MinecraftDownloadPage.Add('https://corretto.aws/downloads/latest/amazon-corretto-8-x64-windows-jre.zip','java.zip',''); - MinecraftDownloadPage.Show; - try - try - MinecraftDownloadPage.Download; - Result := True; - except - if MinecraftDownloadPage.AbortedByUser then - Log('Aborted by user.') - else - SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbCriticalError, MB_OK, IDOK); - Result := False; - end; - finally - if( isJavaNeeded() ) then - if(ForceDirectories(ExpandConstant('{app}'))) then - UnZip(ExpandConstant('{tmp}')+'\java.zip',ExpandConstant('{app}')); - MinecraftDownloadPage.Hide; - end; - Result := True; - end - else if (assigned(LttPROMFilePage)) and (CurPageID = LttPROMFilePage.ID) then + if (assigned(LttPROMFilePage)) and (CurPageID = LttPROMFilePage.ID) then Result := not (LttPROMFilePage.Values[0] = '') else if (assigned(SMROMFilePage)) and (CurPageID = SMROMFilePage.ID) then Result := not (SMROMFilePage.Values[0] = '') @@ -356,7 +277,7 @@ begin R := CompareStr(GetSNESMD5OfFile(LttPROMFilePage.Values[0]), '03a63945398191337e896e5771f77173') if R <> 0 then MsgBox('ALttP ROM validation failed. Very likely wrong file.', mbInformation, MB_OK); - + Result := LttPROMFilePage.Values[0] end else @@ -404,7 +325,7 @@ begin R := CompareStr(GetMD5OfFile(OoTROMFilePage.Values[0]), '5bd1fe107bf8106b2ab6650abecd54d6') * CompareStr(GetMD5OfFile(OoTROMFilePage.Values[0]), '6697768a7a7df2dd27a692a2638ea90b') * CompareStr(GetMD5OfFile(OoTROMFilePage.Values[0]), '05f0f3ebacbc8df9243b6148ffe4792f'); if R <> 0 then MsgBox('OoT ROM validation failed. Very likely wrong file.', mbInformation, MB_OK); - + Result := OoTROMFilePage.Values[0] end else @@ -412,7 +333,7 @@ begin end; procedure InitializeWizard(); -begin +begin AddOoTRomPage(); lttprom := CheckRom('Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', '03a63945398191337e896e5771f77173'); @@ -426,8 +347,6 @@ begin soerom := CheckRom('Secret of Evermore (USA).sfc', '6e9c94511d04fac6e0a1e582c170be3a'); if Length(soerom) = 0 then SoEROMFilePage:= AddRomPage('Secret of Evermore (USA).sfc'); - - AddMinecraftDownloads(); end; diff --git a/test/minecraft/TestAdvancements.py b/test/minecraft/TestAdvancements.py index a3578973..6ddebcbf 100644 --- a/test/minecraft/TestAdvancements.py +++ b/test/minecraft/TestAdvancements.py @@ -613,19 +613,24 @@ class TestAdvancements(TestMinecraft): ["You Need a Mint", False, [], ['Progressive Resource Crafting']], ["You Need a Mint", False, [], ['Flint and Steel']], ["You Need a Mint", False, [], ['Progressive Tools']], - ["You Need a Mint", False, ['Progressive Weapons'], ['Progressive Weapons', 'Progressive Weapons']], - ["You Need a Mint", False, [], ['Progressive Armor']], + ["You Need a Mint", False, [], ['Progressive Weapons']], + ["You Need a Mint", False, [], ['Progressive Armor', 'Shield']], ["You Need a Mint", False, [], ['Brewing']], + ["You Need a Mint", False, [], ['Bottles']], ["You Need a Mint", False, ['Progressive Tools', 'Progressive Tools'], ['Bucket', 'Progressive Tools']], ["You Need a Mint", False, ['3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls'], ['3 Ender Pearls']], - ["You Need a Mint", False, [], ['Archery']], - ["You Need a Mint", False, [], ['Bottles']], - ["You Need a Mint", True, ['Progressive Resource Crafting', 'Progressive Tools', 'Flint and Steel', 'Bucket', - 'Progressive Weapons', 'Progressive Weapons', 'Archery', 'Progressive Armor', - 'Brewing', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', 'Bottles']], + ["You Need a Mint", True, ['Progressive Resource Crafting', 'Progressive Tools', 'Flint and Steel', 'Bucket', + 'Progressive Weapons', 'Progressive Armor', 'Brewing', + '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', 'Bottles']], ["You Need a Mint", True, ['Progressive Resource Crafting', 'Progressive Tools', 'Flint and Steel', 'Progressive Tools', 'Progressive Tools', - 'Progressive Weapons', 'Progressive Weapons', 'Archery', 'Progressive Armor', - 'Brewing', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', 'Bottles']], + 'Progressive Weapons', 'Progressive Armor', 'Brewing', + '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', 'Bottles']], + ["You Need a Mint", True, ['Progressive Resource Crafting', 'Progressive Tools', 'Flint and Steel', 'Bucket', + 'Progressive Weapons', 'Shield', 'Brewing', + '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', 'Bottles']], + ["You Need a Mint", True, ['Progressive Resource Crafting', 'Progressive Tools', 'Flint and Steel', 'Progressive Tools', 'Progressive Tools', + 'Progressive Weapons', 'Shield', 'Brewing', + '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', 'Bottles']], ]) def test_42047(self): @@ -954,7 +959,11 @@ class TestAdvancements(TestMinecraft): def test_42072(self): self.run_location_tests([ - ["A Throwaway Joke", True, []], + ["A Throwaway Joke", False, []], + ["A Throwaway Joke", False, [], ['Progressive Weapons']], + ["A Throwaway Joke", False, [], ['Campfire', 'Progressive Resource Crafting']], + ["A Throwaway Joke", True, ['Progressive Weapons', 'Campfire']], + ["A Throwaway Joke", True, ['Progressive Weapons', 'Progressive Resource Crafting']], ]) def test_42073(self): @@ -1143,3 +1152,127 @@ class TestAdvancements(TestMinecraft): ["Overpowered", True, ['Progressive Resource Crafting', 'Progressive Tools', 'Progressive Tools', 'Flint and Steel', 'Bucket', 'Progressive Weapons', 'Shield']], ]) + def test_42092(self): + self.run_location_tests([ + ["Wax On", False, []], + ["Wax On", False, [], ["Progressive Tools"]], + ["Wax On", False, [], ["Campfire"]], + ["Wax On", False, ["Progressive Resource Crafting"], ["Progressive Resource Crafting"]], + ["Wax On", True, ["Progressive Tools", "Progressive Resource Crafting", "Progressive Resource Crafting", "Campfire"]], + ]) + + def test_42093(self): + self.run_location_tests([ + ["Wax Off", False, []], + ["Wax Off", False, [], ["Progressive Tools"]], + ["Wax Off", False, [], ["Campfire"]], + ["Wax Off", False, ["Progressive Resource Crafting"], ["Progressive Resource Crafting"]], + ["Wax Off", True, ["Progressive Tools", "Progressive Resource Crafting", "Progressive Resource Crafting", "Campfire"]], + ]) + + def test_42094(self): + self.run_location_tests([ + ["The Cutest Predator", False, []], + ["The Cutest Predator", False, [], ["Progressive Tools"]], + ["The Cutest Predator", False, [], ["Progressive Resource Crafting"]], + ["The Cutest Predator", False, [], ["Bucket"]], + ["The Cutest Predator", True, ["Progressive Tools", "Progressive Resource Crafting", "Bucket"]], + ]) + + def test_42095(self): + self.run_location_tests([ + ["The Healing Power of Friendship", False, []], + ["The Healing Power of Friendship", False, [], ["Progressive Tools"]], + ["The Healing Power of Friendship", False, [], ["Progressive Resource Crafting"]], + ["The Healing Power of Friendship", False, [], ["Bucket"]], + ["The Healing Power of Friendship", True, ["Progressive Tools", "Progressive Resource Crafting", "Bucket"]], + ]) + + def test_42096(self): + self.run_location_tests([ + ["Is It a Bird?", False, []], + ["Is It a Bird?", False, [], ["Progressive Weapons"]], + ["Is It a Bird?", False, [], ["Progressive Tools"]], + ["Is It a Bird?", False, [], ["Progressive Resource Crafting"]], + ["Is It a Bird?", False, [], ["Spyglass"]], + ["Is It a Bird?", True, ["Progressive Weapons", "Progressive Tools", "Progressive Resource Crafting", "Spyglass"]], + ]) + + def test_42097(self): + self.run_location_tests([ + ["Is It a Balloon?", False, []], + ["Is It a Balloon?", False, [], ['Progressive Resource Crafting']], + ["Is It a Balloon?", False, [], ['Flint and Steel']], + ["Is It a Balloon?", False, [], ['Progressive Tools']], + ["Is It a Balloon?", False, [], ['Progressive Weapons']], + ["Is It a Balloon?", False, [], ['Spyglass']], + ["Is It a Balloon?", False, ['Progressive Tools', 'Progressive Tools'], ['Bucket', 'Progressive Tools']], + ["Is It a Balloon?", True, ['Progressive Resource Crafting', 'Progressive Tools', 'Flint and Steel', 'Bucket', 'Progressive Weapons', 'Spyglass']], + ["Is It a Balloon?", True, ['Progressive Resource Crafting', 'Progressive Tools', 'Flint and Steel', 'Progressive Tools', 'Progressive Tools', 'Progressive Weapons', 'Spyglass']], + ]) + + def test_42098(self): + self.run_location_tests([ + ["Is It a Plane?", False, []], + ["Is It a Plane?", False, [], ['Progressive Resource Crafting']], + ["Is It a Plane?", False, [], ['Flint and Steel']], + ["Is It a Plane?", False, [], ['Progressive Tools']], + ["Is It a Plane?", False, [], ['Progressive Weapons']], + ["Is It a Plane?", False, [], ['Progressive Armor', 'Shield']], + ["Is It a Plane?", False, [], ['Brewing']], + ["Is It a Plane?", False, ['Progressive Tools', 'Progressive Tools'], ['Bucket', 'Progressive Tools']], + ["Is It a Plane?", False, ['3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls'], ['3 Ender Pearls']], + ["Is It a Plane?", False, [], ['Spyglass']], + ["Is It a Plane?", True, ['Progressive Resource Crafting', 'Progressive Tools', 'Flint and Steel', 'Bucket', + 'Progressive Weapons', 'Progressive Armor', 'Brewing', + '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', 'Spyglass']], + ["Is It a Plane?", True, ['Progressive Resource Crafting', 'Progressive Tools', 'Flint and Steel', 'Progressive Tools', 'Progressive Tools', + 'Progressive Weapons', 'Progressive Armor', 'Brewing', + '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', 'Spyglass']], + ["Is It a Plane?", True, ['Progressive Resource Crafting', 'Progressive Tools', 'Flint and Steel', 'Bucket', + 'Progressive Weapons', 'Shield', 'Brewing', + '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', 'Spyglass']], + ["Is It a Plane?", True, ['Progressive Resource Crafting', 'Progressive Tools', 'Flint and Steel', 'Progressive Tools', 'Progressive Tools', + 'Progressive Weapons', 'Shield', 'Brewing', + '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', '3 Ender Pearls', 'Spyglass']], + ]) + + def test_42099(self): + self.run_location_tests([ + ["Surge Protector", False, []], + ["Surge Protector", False, [], ['Channeling Book']], + ["Surge Protector", False, ['Progressive Resource Crafting'], ['Progressive Resource Crafting']], + ["Surge Protector", False, [], ['Enchanting']], + ["Surge Protector", False, [], ['Progressive Tools']], + ["Surge Protector", False, [], ['Progressive Weapons']], + ["Surge Protector", True, ['Progressive Weapons', 'Progressive Tools', 'Progressive Tools', 'Progressive Tools', + 'Enchanting', 'Progressive Resource Crafting', 'Progressive Resource Crafting', 'Channeling Book']], + ]) + + def test_42100(self): + self.run_location_tests([ + ["Light as a Rabbit", False, []], + ["Light as a Rabbit", False, [], ["Progressive Weapons"]], + ["Light as a Rabbit", False, [], ["Progressive Tools"]], + ["Light as a Rabbit", False, [], ["Progressive Resource Crafting"]], + ["Light as a Rabbit", False, [], ["Bucket"]], + ["Light as a Rabbit", True, ["Progressive Weapons", "Progressive Tools", "Progressive Resource Crafting", "Bucket"]], + ]) + + def test_42101(self): + self.run_location_tests([ + ["Glow and Behold!", False, []], + ["Glow and Behold!", False, [], ["Progressive Weapons"]], + ["Glow and Behold!", False, [], ["Progressive Resource Crafting", "Campfire"]], + ["Glow and Behold!", True, ["Progressive Weapons", "Progressive Resource Crafting"]], + ["Glow and Behold!", True, ["Progressive Weapons", "Campfire"]], + ]) + + def test_42102(self): + self.run_location_tests([ + ["Whatever Floats Your Goat!", False, []], + ["Whatever Floats Your Goat!", False, [], ["Progressive Weapons"]], + ["Whatever Floats Your Goat!", False, [], ["Progressive Resource Crafting", "Campfire"]], + ["Whatever Floats Your Goat!", True, ["Progressive Weapons", "Progressive Resource Crafting"]], + ["Whatever Floats Your Goat!", True, ["Progressive Weapons", "Campfire"]], + ]) diff --git a/test/minecraft/TestMinecraft.py b/test/minecraft/TestMinecraft.py index fd2c0c1c..2e558421 100644 --- a/test/minecraft/TestMinecraft.py +++ b/test/minecraft/TestMinecraft.py @@ -4,7 +4,7 @@ from BaseClasses import MultiWorld from worlds import AutoWorld from worlds.minecraft import MinecraftWorld from worlds.minecraft.Items import MinecraftItem, item_table -from worlds.minecraft.Options import AdvancementGoal, CombatDifficulty, BeeTraps +from worlds.minecraft.Options import * from Options import Toggle, Range # Converts the name of an item into an item object @@ -30,16 +30,17 @@ class TestMinecraft(TestBase): self.world = MultiWorld(1) self.world.game[1] = "Minecraft" self.world.worlds[1] = MinecraftWorld(self.world, 1) - exclusion_pools = ['hard', 'insane', 'postgame'] + exclusion_pools = ['hard', 'unreasonable', 'postgame'] for pool in exclusion_pools: - setattr(self.world, f"include_{pool}_advancements", [False, False]) + setattr(self.world, f"include_{pool}_advancements", {1: False}) setattr(self.world, "advancement_goal", {1: AdvancementGoal(30)}) - setattr(self.world, "shuffle_structures", {1: Toggle(False)}) - setattr(self.world, "combat_difficulty", {1: CombatDifficulty(1)}) # normal + setattr(self.world, "egg_shards_required", {1: EggShardsRequired(0)}) + setattr(self.world, "egg_shards_available", {1: EggShardsAvailable(0)}) + setattr(self.world, "required_bosses", {1: BossGoal(1)}) # ender dragon + setattr(self.world, "shuffle_structures", {1: ShuffleStructures(False)}) setattr(self.world, "bee_traps", {1: BeeTraps(0)}) + setattr(self.world, "combat_difficulty", {1: CombatDifficulty(1)}) # normal setattr(self.world, "structure_compasses", {1: Toggle(False)}) - setattr(self.world, "egg_shards_required", {1: Range(0)}) - setattr(self.world, "egg_shards_available", {1: Range(0)}) AutoWorld.call_single(self.world, "create_regions", 1) AutoWorld.call_single(self.world, "generate_basic", 1) AutoWorld.call_single(self.world, "set_rules", 1) diff --git a/worlds/minecraft/Items.py b/worlds/minecraft/Items.py index b2806ab5..a176c4f7 100644 --- a/worlds/minecraft/Items.py +++ b/worlds/minecraft/Items.py @@ -56,10 +56,12 @@ item_table = { "Structure Compass (End City)": ItemData(45041, True), "Shulker Box": ItemData(45042, False), "Dragon Egg Shard": ItemData(45043, True), + "Spyglass": ItemData(45044, True), "Bee Trap (Minecraft)": ItemData(45100, False), "Blaze Rods": ItemData(None, True), - "Victory": ItemData(None, True) + "Defeat Ender Dragon": ItemData(None, True), + "Defeat Wither": ItemData(None, True), } # 33 required items @@ -87,6 +89,7 @@ required_items = { "Infinity Book": 1, "3 Ender Pearls": 4, "Saddle": 1, + "Spyglass": 1, } junk_weights = { diff --git a/worlds/minecraft/Locations.py b/worlds/minecraft/Locations.py index d1134e15..7dbf85de 100644 --- a/worlds/minecraft/Locations.py +++ b/worlds/minecraft/Locations.py @@ -108,9 +108,21 @@ advancement_table = { "Overkill": AdvData(42089, 'Nether Fortress'), "Librarian": AdvData(42090, 'Overworld'), "Overpowered": AdvData(42091, 'Bastion Remnant'), + "Wax On": AdvData(42092, 'Overworld'), + "Wax Off": AdvData(42093, 'Overworld'), + "The Cutest Predator": AdvData(42094, 'Overworld'), + "The Healing Power of Friendship": AdvData(42095, 'Overworld'), + "Is It a Bird?": AdvData(42096, 'Overworld'), + "Is It a Balloon?": AdvData(42097, 'The Nether'), + "Is It a Plane?": AdvData(42098, 'The End'), + "Surge Protector": AdvData(42099, 'Overworld'), + "Light as a Rabbit": AdvData(42100, 'Overworld'), + "Glow and Behold!": AdvData(42101, 'Overworld'), + "Whatever Floats Your Goat!": AdvData(42102, 'Overworld'), "Blaze Spawner": AdvData(None, 'Nether Fortress'), - "Ender Dragon": AdvData(None, 'The End') + "Ender Dragon": AdvData(None, 'The End'), + "Wither": AdvData(None, 'Nether Fortress'), } exclusion_table = { @@ -126,23 +138,39 @@ exclusion_table = { "Uneasy Alliance", "Cover Me in Debris", "A Complete Catalogue", + "Surge Protector", + "Light as a Rabbit", # will be normal in 1.18 }, - "insane": { + "unreasonable": { "How Did We Get Here?", "Adventuring Time", }, - "postgame": { - "Free the End", - "The Next Generation", - "The End... Again...", - "You Need a Mint", - "Monsters Hunted", +} + +def get_postgame_advancements(required_bosses): + + postgame_advancements = { + "ender_dragon": { + "Free the End", + "The Next Generation", + "The End... Again...", + "You Need a Mint", + "Monsters Hunted", + "Is It a Plane?", + }, + "wither": { + "Withering Heights", + "Bring Home the Beacon", + "Beaconator", + "A Furious Cocktail", + "How Did We Get Here?", + "Monsters Hunted", + } } -} -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} + advancements = set() + if required_bosses in {"ender_dragon", "both"}: + advancements.update(postgame_advancements["ender_dragon"]) + if required_bosses in {"wither", "both"}: + advancements.update(postgame_advancements["wither"]) + return advancements diff --git a/worlds/minecraft/Options.py b/worlds/minecraft/Options.py index 49929faf..04e87c06 100644 --- a/worlds/minecraft/Options.py +++ b/worlds/minecraft/Options.py @@ -1,37 +1,51 @@ import typing -from Options import Choice, Option, Toggle, Range +from Options import Choice, Option, Toggle, Range, OptionList, DeathLink class AdvancementGoal(Range): - """Number of advancements required to spawn the Ender Dragon.""" + """Number of advancements required to spawn bosses.""" displayname = "Advancement Goal" range_start = 0 - range_end = 87 - default = 50 + range_end = 92 + default = 40 class EggShardsRequired(Range): - """Number of dragon egg shards to collect before the Ender Dragon will spawn.""" + """Number of dragon egg shards to collect to spawn bosses.""" displayname = "Egg Shards Required" range_start = 0 - range_end = 30 + range_end = 40 + default = 0 class EggShardsAvailable(Range): """Number of dragon egg shards available to collect.""" displayname = "Egg Shards Available" range_start = 0 - range_end = 30 + range_end = 40 + default = 0 + + +class BossGoal(Choice): + """Bosses which must be defeated to finish the game.""" + displayname = "Required Bosses" + option_none = 0 + option_ender_dragon = 1 + option_wither = 2 + option_both = 3 + default = 1 class ShuffleStructures(Toggle): """Enables shuffling of villages, outposts, fortresses, bastions, and end cities.""" displayname = "Shuffle Structures" + default = 1 class StructureCompasses(Toggle): """Adds structure compasses to the item pool, which point to the nearest indicated structure.""" displayname = "Structure Compasses" + default = 1 class BeeTraps(Range): @@ -39,6 +53,7 @@ class BeeTraps(Range): displayname = "Bee Trap Percentage" range_start = 0 range_end = 100 + default = 0 class CombatDifficulty(Choice): @@ -53,33 +68,46 @@ class CombatDifficulty(Choice): class HardAdvancements(Toggle): """Enables certain RNG-reliant or tedious advancements.""" displayname = "Include Hard Advancements" + default = 0 -class InsaneAdvancements(Toggle): +class UnreasonableAdvancements(Toggle): """Enables the extremely difficult advancements "How Did We Get Here?" and "Adventuring Time.\"""" - displayname = "Include Insane Advancements" + displayname = "Include Unreasonable Advancements" + default = 0 class PostgameAdvancements(Toggle): - """Enables advancements that require spawning and defeating the Ender Dragon.""" + """Enables advancements that require spawning and defeating the required bosses.""" displayname = "Include Postgame Advancements" + default = 0 class SendDefeatedMobs(Toggle): """Send killed mobs to other Minecraft worlds which have this option enabled.""" displayname = "Send Defeated Mobs" + default = 0 + + +class StartingItems(OptionList): + """Start with these items. Each entry should be of this format: {item: "item_name", amount: #, nbt: "nbt_string"}""" + displayname = "Starting Items" + default = 0 minecraft_options: typing.Dict[str, type(Option)] = { - "advancement_goal": AdvancementGoal, - "egg_shards_required": EggShardsRequired, - "egg_shards_available": EggShardsAvailable, - "shuffle_structures": ShuffleStructures, - "structure_compasses": StructureCompasses, - "bee_traps": BeeTraps, - "combat_difficulty": CombatDifficulty, - "include_hard_advancements": HardAdvancements, - "include_insane_advancements": InsaneAdvancements, - "include_postgame_advancements": PostgameAdvancements, - "send_defeated_mobs": SendDefeatedMobs, + "advancement_goal": AdvancementGoal, + "egg_shards_required": EggShardsRequired, + "egg_shards_available": EggShardsAvailable, + "required_bosses": BossGoal, + "shuffle_structures": ShuffleStructures, + "structure_compasses": StructureCompasses, + "bee_traps": BeeTraps, + "combat_difficulty": CombatDifficulty, + "include_hard_advancements": HardAdvancements, + "include_unreasonable_advancements": UnreasonableAdvancements, + "include_postgame_advancements": PostgameAdvancements, + "send_defeated_mobs": SendDefeatedMobs, + "starting_items": StartingItems, + "death_link": DeathLink, } diff --git a/worlds/minecraft/Rules.py b/worlds/minecraft/Rules.py index d3c0873f..e3adfc9d 100644 --- a/worlds/minecraft/Rules.py +++ b/worlds/minecraft/Rules.py @@ -1,5 +1,5 @@ -from ..generic.Rules import set_rule -from .Locations import exclusion_table, events_table +from ..generic.Rules import set_rule, add_rule +from .Locations import exclusion_table, get_postgame_advancements from BaseClasses import MultiWorld from ..AutoWorld import LogicMixin @@ -9,6 +9,9 @@ class MinecraftLogic(LogicMixin): def _mc_has_iron_ingots(self, player: int): return self.has('Progressive Tools', player) and self.has('Progressive Resource Crafting', player) + def _mc_has_copper_ingots(self, player: int): + return self.has('Progressive Tools', player) and self.has('Progressive Resource Crafting', player) + def _mc_has_gold_ingots(self, player: int): return self.has('Progressive Resource Crafting', player) and (self.has('Progressive Tools', player, 2) or self.can_reach('The Nether', 'Region', player)) @@ -21,6 +24,9 @@ class MinecraftLogic(LogicMixin): def _mc_has_bottle(self, player: int): return self.has('Bottles', player) and self.has('Progressive Resource Crafting', player) + def _mc_has_spyglass(self, player: int): + return self._mc_has_copper_ingots(player) and self.has('Spyglass', player) and self._mc_can_adventure(player) + def _mc_can_enchant(self, player: int): return self.has('Enchanting', player) and self._mc_has_diamond_pickaxe(player) # mine obsidian and lapis @@ -81,48 +87,32 @@ class MinecraftLogic(LogicMixin): return self._mc_fortress_loot(player) and (normal_kill or self.can_reach('The Nether', 'Region', player) or self.can_reach('The End', 'Region', player)) return self._mc_fortress_loot(player) and normal_kill + def _mc_can_respawn_ender_dragon(self, player: int): + return self.can_reach('The Nether', 'Region', player) and self.can_reach('The End', 'Region', player) and \ + self.has('Progressive Resource Crafting', player) # smelt sand into glass + def _mc_can_kill_ender_dragon(self, player: int): - # Since it is possible to kill the dragon without getting any of the advancements related to it, we need to require that it can be respawned. - respawn_dragon = self.can_reach('The Nether', 'Region', player) and self.has('Progressive Resource Crafting', player) if self._mc_combat_difficulty(player) == 'easy': - return respawn_dragon and self.has("Progressive Weapons", player, 3) and self.has("Progressive Armor", player, 2) and \ + return self.has("Progressive Weapons", player, 3) and self.has("Progressive Armor", player, 2) and \ self.has('Archery', player) and self._mc_can_brew_potions(player) and self._mc_can_enchant(player) if self._mc_combat_difficulty(player) == 'hard': - return respawn_dragon and ((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 respawn_dragon and self.has('Progressive Weapons', player, 2) and self.has('Progressive Armor', player) and self.has('Archery', player) + 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 _mc_has_structure_compass(self, entrance_name: str, player: int): if not self.world.structure_compasses[player]: return True return self.has(f"Structure Compass ({self.world.get_entrance(entrance_name, player).connected_region.name})", player) - -def set_rules(world: MultiWorld, player: int): - def reachable_locations(state): - postgame_advancements = exclusion_table['postgame'].copy() - for event in events_table.keys(): - postgame_advancements.add(event) - return [location for location in world.get_locations() if - location.player == player and - location.name not in postgame_advancements and - location.can_reach(state)] +# Sets rules on entrances and advancements that are always applied +def set_advancement_rules(world: MultiWorld, player: int): # Retrieves the appropriate structure compass for the given entrance def get_struct_compass(entrance_name): struct = world.get_entrance(entrance_name, player).connected_region.name return f"Structure Compass ({struct})" - # 92 total advancements. Goal is to complete X advancements and then Free the End. - # There are 5 advancements which cannot be included for dragon spawning (4 postgame, Free the End) - # Hence the true maximum is (92 - 5) = 87 - goal = world.advancement_goal[player] - egg_shards = min(world.egg_shards_required[player], world.egg_shards_available[player]) - can_complete = lambda state: len(reachable_locations(state)) >= goal and state.has("Dragon Egg Shard", player, egg_shards) and state.can_reach('The End', 'Region', player) and state._mc_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._mc_has_iron_ingots(player)) @@ -133,7 +123,8 @@ def set_rules(world: MultiWorld, player: int): set_rule(world.get_entrance("Nether Structure 2", player), lambda state: state._mc_can_adventure(player) and state._mc_has_structure_compass("Nether Structure 2", player)) set_rule(world.get_entrance("The End Structure", player), lambda state: state._mc_can_adventure(player) and state._mc_has_structure_compass("The End Structure", player)) - set_rule(world.get_location("Ender Dragon", player), lambda state: can_complete(state)) + set_rule(world.get_location("Ender Dragon", player), lambda state: state._mc_can_kill_ender_dragon(player)) + set_rule(world.get_location("Wither", player), lambda state: state._mc_can_kill_wither(player)) set_rule(world.get_location("Blaze Spawner", player), lambda state: state._mc_fortress_loot(player)) set_rule(world.get_location("Who is Cutting Onions?", player), lambda state: state._mc_can_piglin_trade(player)) @@ -142,7 +133,7 @@ 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._mc_can_use_anvil(player) and state._mc_can_enchant(player) and \ ((world.get_region('Village', player).entrances[0].parent_region.name != 'The End' and state.can_reach('Village', 'Region', player)) or state.can_reach('Zombie Doctor', 'Location', player))) # need villager into the overworld for lightning strike set_rule(world.get_location("Hot Stuff", player), lambda state: state.has("Bucket", player) and state._mc_has_iron_ingots(player)) - set_rule(world.get_location("Free the End", player), lambda state: can_complete(state)) + set_rule(world.get_location("Free the End", player), lambda state: state._mc_can_respawn_ender_dragon(player) and state._mc_can_kill_ender_dragon(player)) set_rule(world.get_location("A Furious Cocktail", player), lambda state: state._mc_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 @@ -154,7 +145,7 @@ def set_rules(world: MultiWorld, player: int): set_rule(world.get_location("Not Today, Thank You", player), lambda state: state.has("Shield", player) and state._mc_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._mc_has_iron_ingots(player)) set_rule(world.get_location("Local Brewery", player), lambda state: state._mc_can_brew_potions(player)) - set_rule(world.get_location("The Next Generation", player), lambda state: can_complete(state)) + set_rule(world.get_location("The Next Generation", player), lambda state: state._mc_can_kill_ender_dragon(player)) 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: True) set_rule(world.get_location("This Boat Has Legs", player), lambda state: (state._mc_fortress_loot(player) or state._mc_complete_raid(player)) and @@ -188,7 +179,7 @@ def set_rules(world: MultiWorld, player: int): set_rule(world.get_location("Total Beelocation", player), lambda state: state.has("Silk Touch Book", player) and state._mc_can_use_anvil(player) and state._mc_can_enchant(player)) set_rule(world.get_location("Arbalistic", player), lambda state: state._mc_craft_crossbow(player) and state.has("Piercing IV Book", player) and state._mc_can_use_anvil(player) and state._mc_can_enchant(player)) - set_rule(world.get_location("The End... Again...", player), lambda state: can_complete(state)) + set_rule(world.get_location("The End... Again...", player), lambda state: state._mc_can_respawn_ender_dragon(player) and state._mc_can_kill_ender_dragon(player)) set_rule(world.get_location("Acquire Hardware", player), lambda state: state._mc_has_iron_ingots(player)) set_rule(world.get_location("Not Quite \"Nine\" Lives", player), lambda state: state._mc_can_piglin_trade(player) and state.has("Progressive Resource Crafting", player, 2)) 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)) @@ -196,9 +187,10 @@ def set_rules(world: MultiWorld, player: int): set_rule(world.get_location("Hired Help", player), lambda state: state.has("Progressive Resource Crafting", player, 2) and state._mc_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._mc_has_bottle(player)) + set_rule(world.get_location("You Need a Mint", player), lambda state: state._mc_can_respawn_ender_dragon(player) and state._mc_has_bottle(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._mc_can_kill_wither(player) and state.has("Fishing Rod", player)) # pufferfish for Water Breathing + set_rule(world.get_location("Monsters Hunted", player), lambda state: state._mc_can_respawn_ender_dragon(player) and state._mc_can_kill_ender_dragon(player) and + state._mc_can_kill_wither(player) and state.has("Fishing Rod", player)) # pufferfish for Water Breathing set_rule(world.get_location("Enchanter", player), lambda state: state._mc_can_enchant(player)) set_rule(world.get_location("Voluntary Exile", player), lambda state: state._mc_basic_combat(player)) set_rule(world.get_location("Eye Spy", player), lambda state: state._mc_enter_stronghold(player)) @@ -224,7 +216,7 @@ def set_rules(world: MultiWorld, player: int): set_rule(world.get_location("Uneasy Alliance", player), lambda state: state._mc_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._mc_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("A Throwaway Joke", player), lambda state: state._mc_can_adventure(player)) # kill drowned set_rule(world.get_location("Minecraft", player), lambda state: True) set_rule(world.get_location("Sticky Situation", player), lambda state: state.has("Campfire", player) and state._mc_has_bottle(player)) set_rule(world.get_location("Ol' Betsy", player), lambda state: state._mc_craft_crossbow(player)) @@ -249,3 +241,42 @@ def set_rules(world: MultiWorld, player: int): set_rule(world.get_location("Librarian", player), lambda state: state.has("Enchanting", player)) set_rule(world.get_location("Overpowered", player), lambda state: state._mc_has_iron_ingots(player) and state.has('Progressive Tools', player, 2) and state._mc_basic_combat(player)) # mine gold blocks w/ iron pick + set_rule(world.get_location("Wax On", player), lambda state: state._mc_has_copper_ingots(player) and state.has('Campfire', player) and + state.has('Progressive Resource Crafting', player, 2)) + set_rule(world.get_location("Wax Off", player), lambda state: state._mc_has_copper_ingots(player) and state.has('Campfire', player) and + state.has('Progressive Resource Crafting', player, 2)) + set_rule(world.get_location("The Cutest Predator", player), lambda state: state._mc_has_iron_ingots(player) and state.has('Bucket', player)) + set_rule(world.get_location("The Healing Power of Friendship", player), lambda state: state._mc_has_iron_ingots(player) and state.has('Bucket', player)) + set_rule(world.get_location("Is It a Bird?", player), lambda state: state._mc_has_spyglass(player) and state._mc_can_adventure(player)) + set_rule(world.get_location("Is It a Balloon?", player), lambda state: state._mc_has_spyglass(player)) + set_rule(world.get_location("Is It a Plane?", player), lambda state: state._mc_has_spyglass(player) and state._mc_can_respawn_ender_dragon(player)) + set_rule(world.get_location("Surge Protector", player), lambda state: state.has("Channeling Book", player) and state._mc_can_use_anvil(player) and state._mc_can_enchant(player) and \ + ((world.get_region('Village', player).entrances[0].parent_region.name != 'The End' and state.can_reach('Village', 'Region', player)) or state.can_reach('Zombie Doctor', 'Location', player))) + set_rule(world.get_location("Light as a Rabbit", player), lambda state: state._mc_can_adventure(player) and state._mc_has_iron_ingots(player) and state.has('Bucket', player)) + set_rule(world.get_location("Glow and Behold!", player), lambda state: state._mc_can_adventure(player)) + set_rule(world.get_location("Whatever Floats Your Goat!", player), lambda state: state._mc_can_adventure(player)) + +# Sets rules on completion condition and postgame advancements +def set_completion_rules(world: MultiWorld, player: int): + def reachable_locations(state): + postgame_advancements = get_postgame_advancements(world.required_bosses[player].current_key) + return [location for location in world.get_locations() if + location.player == player and + location.name not in postgame_advancements and + location.address != None and + location.can_reach(state)] + + def defeated_required_bosses(state): + return (world.required_bosses[player].current_key not in {"ender_dragon", "both"} or state.has("Defeat Ender Dragon", player)) and \ + (world.required_bosses[player].current_key not in {"wither", "both"} or state.has("Defeat Wither", player)) + + # 103 total advancements. Goal is to complete X advancements and then defeat the dragon. + # There are 11 possible postgame advancements; 5 for dragon, 5 for wither, 1 shared between them + # Hence the max for completion is 92 + egg_shards = min(world.egg_shards_required[player], world.egg_shards_available[player]) + completion_requirements = lambda state: len(reachable_locations(state)) >= world.advancement_goal[player] and \ + state.has("Dragon Egg Shard", player, egg_shards) + world.completion_condition[player] = lambda state: completion_requirements(state) and defeated_required_bosses(state) + # Set rules on postgame advancements + for adv_name in get_postgame_advancements(world.required_bosses[player].current_key): + add_rule(world.get_location(adv_name, player), completion_requirements) diff --git a/worlds/minecraft/__init__.py b/worlds/minecraft/__init__.py index a8a78fb9..45f7860c 100644 --- a/worlds/minecraft/__init__.py +++ b/worlds/minecraft/__init__.py @@ -4,16 +4,17 @@ from base64 import b64encode, b64decode from math import ceil from .Items import MinecraftItem, item_table, required_items, junk_weights -from .Locations import MinecraftAdvancement, advancement_table, exclusion_table, events_table +from .Locations import MinecraftAdvancement, advancement_table, exclusion_table, get_postgame_advancements from .Regions import mc_regions, link_minecraft_structures, default_connections -from .Rules import set_rules +from .Rules import set_advancement_rules, set_completion_rules from worlds.generic.Rules import exclusion_rules from BaseClasses import Region, Entrance, Item from .Options import minecraft_options from ..AutoWorld import World -client_version = 6 +client_version = 7 +minecraft_version = "1.17.1" class MinecraftWorld(World): """ @@ -29,7 +30,7 @@ class MinecraftWorld(World): item_name_to_id = {name: data.code for name, data in item_table.items()} location_name_to_id = {name: data.id for name, data in advancement_table.items()} - data_version = 3 + data_version = 4 def _get_mc_data(self): exits = [connection[0] for connection in default_connections] @@ -39,12 +40,16 @@ class MinecraftWorld(World): 'player_name': self.world.get_player_name(self.player), 'player_id': self.player, 'client_version': client_version, + 'minecraft_version': minecraft_version, 'structures': {exit: self.world.get_entrance(exit, self.player).connected_region.name for exit in exits}, 'advancement_goal': self.world.advancement_goal[self.player], 'egg_shards_required': min(self.world.egg_shards_required[self.player], self.world.egg_shards_available[self.player]), 'egg_shards_available': self.world.egg_shards_available[self.player], + 'required_bosses': self.world.required_bosses[self.player].current_key, 'MC35': bool(self.world.send_defeated_mobs[self.player]), - 'race': self.world.is_race + 'death_link': bool(self.world.death_link[self.player]), + 'starting_items': str(self.world.starting_items[self.player].value), + 'race': self.world.is_race, } def generate_basic(self): @@ -72,20 +77,24 @@ class MinecraftWorld(World): # Choose locations to automatically exclude based on settings exclusion_pool = set() - exclusion_types = ['hard', 'insane', 'postgame'] + exclusion_types = ['hard', 'unreasonable'] for key in exclusion_types: if not getattr(self.world, f"include_{key}_advancements")[self.player]: exclusion_pool.update(exclusion_table[key]) + # For postgame advancements, check with the boss goal + exclusion_pool.update(get_postgame_advancements(self.world.required_bosses[self.player].current_key)) exclusion_rules(self.world, self.player, exclusion_pool) # Prefill event locations with their events self.world.get_location("Blaze Spawner", self.player).place_locked_item(self.create_item("Blaze Rods")) - self.world.get_location("Ender Dragon", self.player).place_locked_item(self.create_item("Victory")) + self.world.get_location("Ender Dragon", self.player).place_locked_item(self.create_item("Defeat Ender Dragon")) + self.world.get_location("Wither", self.player).place_locked_item(self.create_item("Defeat Wither")) self.world.itempool += itempool def set_rules(self): - set_rules(self.world, self.player) + set_advancement_rules(self.world, self.player) + set_completion_rules(self.world, self.player) def create_regions(self): def MCRegion(region_name: str, exits=[]): @@ -110,7 +119,8 @@ class MinecraftWorld(World): slot_data = self._get_mc_data() for option_name in minecraft_options: option = getattr(self.world, option_name)[self.player] - slot_data[option_name] = int(option.value) + if slot_data.get(option_name, None) is None and type(option.value) in {str, int}: + slot_data[option_name] = int(option.value) return slot_data def create_item(self, name: str) -> Item: