import os import json import settings import typing from base64 import b64encode, b64decode from typing import Dict, Any from BaseClasses import Region, Entrance, Item, Tutorial, ItemClassification, Location from worlds.AutoWorld import World, WebWorld from . import Constants from .Options import minecraft_options from .Structures import shuffle_structures from .ItemPool import build_item_pool, get_junk_item_names from .Rules import set_rules client_version = 9 class MinecraftSettings(settings.Group): class ForgeDirectory(settings.OptionalUserFolderPath): pass class ReleaseChannel(str): """ release channel, currently "release", or "beta" any games played on the "beta" channel have a high likelihood of no longer working on the "release" channel. """ forge_directory: ForgeDirectory = ForgeDirectory("Minecraft Forge server") max_heap_size: str = "2G" release_channel: ReleaseChannel = ReleaseChannel("release") class MinecraftWebWorld(WebWorld): theme = "jungle" bug_report_page = "https://github.com/KonoTyran/Minecraft_AP_Randomizer/issues/new?assignees=&labels=bug&template=bug_report.yaml&title=%5BBug%5D%3A+Brief+Description+of+bug+here" setup = Tutorial( "Multiworld Setup Tutorial", "A guide to setting up the Archipelago Minecraft software on your computer. This guide covers" "single-player, multiworld, and related software.", "English", "minecraft_en.md", "minecraft/en", ["Kono Tyran"] ) setup_es = Tutorial( setup.tutorial_name, setup.description, "Español", "minecraft_es.md", "minecraft/es", ["Edos"] ) setup_sv = Tutorial( setup.tutorial_name, setup.description, "Swedish", "minecraft_sv.md", "minecraft/sv", ["Albinum"] ) setup_fr = Tutorial( setup.tutorial_name, setup.description, "Français", "minecraft_fr.md", "minecraft/fr", ["TheLynk"] ) tutorials = [setup, setup_es, setup_sv, setup_fr] class MinecraftWorld(World): """ Minecraft is a game about creativity. In a world made entirely of cubes, you explore, discover, mine, craft, and try not to explode. Delve deep into the earth and discover abandoned mines, ancient structures, and materials to create a portal to another world. Defeat the Ender Dragon, and claim victory! """ game: str = "Minecraft" option_definitions = minecraft_options settings: typing.ClassVar[MinecraftSettings] topology_present = True web = MinecraftWebWorld() item_name_to_id = Constants.item_name_to_id location_name_to_id = Constants.location_name_to_id data_version = 7 def _get_mc_data(self) -> Dict[str, Any]: exits = [connection[0] for connection in Constants.region_info["default_connections"]] return { 'world_seed': self.multiworld.per_slot_randoms[self.player].getrandbits(32), 'seed_name': self.multiworld.seed_name, 'player_name': self.multiworld.get_player_name(self.player), 'player_id': self.player, 'client_version': client_version, 'structures': {exit: self.multiworld.get_entrance(exit, self.player).connected_region.name for exit in exits}, 'advancement_goal': self.multiworld.advancement_goal[self.player].value, 'egg_shards_required': min(self.multiworld.egg_shards_required[self.player].value, self.multiworld.egg_shards_available[self.player].value), 'egg_shards_available': self.multiworld.egg_shards_available[self.player].value, 'required_bosses': self.multiworld.required_bosses[self.player].current_key, 'MC35': bool(self.multiworld.send_defeated_mobs[self.player].value), 'death_link': bool(self.multiworld.death_link[self.player].value), 'starting_items': str(self.multiworld.starting_items[self.player].value), 'race': self.multiworld.is_race, } def create_item(self, name: str) -> Item: item_class = ItemClassification.filler if name in Constants.item_info["progression_items"]: item_class = ItemClassification.progression elif name in Constants.item_info["useful_items"]: item_class = ItemClassification.useful elif name in Constants.item_info["trap_items"]: item_class = ItemClassification.trap return MinecraftItem(name, item_class, self.item_name_to_id.get(name, None), self.player) def create_event(self, region_name: str, event_name: str) -> None: region = self.multiworld.get_region(region_name, self.player) loc = MinecraftLocation(self.player, event_name, None, region) loc.place_locked_item(self.create_event_item(event_name)) region.locations.append(loc) def create_event_item(self, name: str) -> None: item = self.create_item(name) item.classification = ItemClassification.progression return item def create_regions(self) -> None: # Create regions for region_name, exits in Constants.region_info["regions"]: r = Region(region_name, self.player, self.multiworld) for exit_name in exits: r.exits.append(Entrance(self.player, exit_name, r)) self.multiworld.regions.append(r) # Bind mandatory connections for entr_name, region_name in Constants.region_info["mandatory_connections"]: e = self.multiworld.get_entrance(entr_name, self.player) r = self.multiworld.get_region(region_name, self.player) e.connect(r) # Add locations for region_name, locations in Constants.location_info["locations_by_region"].items(): region = self.multiworld.get_region(region_name, self.player) for loc_name in locations: loc = MinecraftLocation(self.player, loc_name, self.location_name_to_id.get(loc_name, None), region) region.locations.append(loc) # Add events self.create_event("Nether Fortress", "Blaze Rods") self.create_event("The End", "Ender Dragon") self.create_event("Nether Fortress", "Wither") # Shuffle the connections shuffle_structures(self) def create_items(self) -> None: self.multiworld.itempool += build_item_pool(self) set_rules = set_rules def generate_output(self, output_directory: str) -> None: data = self._get_mc_data() filename = f"AP_{self.multiworld.get_out_file_name_base(self.player)}.apmc" with open(os.path.join(output_directory, filename), 'wb') as f: f.write(b64encode(bytes(json.dumps(data), 'utf-8'))) def fill_slot_data(self) -> dict: slot_data = self._get_mc_data() for option_name in minecraft_options: option = getattr(self.multiworld, option_name)[self.player] 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 get_filler_item_name(self) -> str: return get_junk_item_names(self.multiworld.random, 1)[0] class MinecraftLocation(Location): game = "Minecraft" class MinecraftItem(Item): game = "Minecraft" def mc_update_output(raw_data, server, port): data = json.loads(b64decode(raw_data)) data['server'] = server data['port'] = port return b64encode(bytes(json.dumps(data), 'utf-8'))