MC: 1.17 support (#120)

* MC: add death_link option

* Minecraft: 1.17 advancements and logic support

* Update Minecraft tracker to 1.17

* Minecraft: add tests for new advancements

* removed jdk/forge download install out of iss and into MinecraftClient.py using flag --install

* Add required_bosses option
choices are none, ender_dragon, wither, both
postgame advancements are set according to the required boss for completion

* fix docstring for PostgameAdvancements

* Minecraft: add starting_items
List of dicts: item, amount, nbt

* Update descriptions for AdvancementGoal and EggShardsRequired

* Minecraft: fix tests for required_bosses attribute

* Minecraft: updated logic for various dragon-related advancements
Split the logic into can_respawn and can_kill dragon
Free the End, Monsters Hunted, The End Again still require both respawn and kill, since the player needs to kill and be credited with the kill
You Need a Mint and Is It a Plane now require only respawn, since the dragon need only be alive; if killed out of logic, it's ok
The Next Generation only requires kill, since the egg spawns regardless of whether the player was credited with the kill or not

* Minecraft client: ignore prereleases unless --prerelease flag is on

* explicitly state all defaults
change structure shuffle and structure compass defaults to true
update install tutorial to point to player-settings page, as well as removing instructions for manual install

* Minecraft client: add Minecraft version check
Adds a minecraft_version field in the apmc, and downloads only mods which contain that version in the name of the .jar file.
This ensures that the client remains compatible even if new mods are released for later versions, since they won't download a mod for a later version than the apmc says.

Co-authored-by: Kono Tyran <Kono.Tyran@gmail.com>
This commit is contained in:
espeon65536 2021-11-30 20:37:11 -05:00 committed by GitHub
parent d7509972e4
commit 3fa253bac5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 501 additions and 408 deletions

4
.gitignore vendored
View File

@ -152,3 +152,7 @@ dmypy.json
cython_debug/
Archipelago.zip
#minecraft server stuff
jdk*/
minecraft*/

View File

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

View File

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

View File

@ -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 <AP-Address> (Port) (Password)` where `<AP-Address>` 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!

View File

@ -42,6 +42,7 @@
<td><img src="{{ icons['Enchanting Table'] }}" class="{{ 'acquired' if 'Enchanting' in acquired_items }}" title="Enchanting" /></td>
<td><img src="{{ icons['Fishing Rod'] }}" class="{{ 'acquired' if 'Fishing Rod' in acquired_items }}" title="Fishing Rod" /></td>
<td><img src="{{ icons['Campfire'] }}" class="{{ 'acquired' if 'Campfire' in acquired_items }}" title="Campfire" /></td>
<td><img src="{{ icons['Spyglass'] }}" class="{{ 'acquired' if 'Spyglass' in acquired_items }}" title="Spyglass" /></td>
<td><img src="{{ icons['Dragon Head'] }}" class="{{ 'acquired' if game_finished }}" title="Ender Dragon" /></td>
</tr>
</table>

View File

@ -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 = {}

View File

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

View File

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

View File

@ -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"]],
])

View File

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

View File

@ -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 = {

View File

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

View File

@ -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,
}

View File

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

View File

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