Minecraft rewrite (#1493)
* Minecraft: rewrite to modern AP standards * Fix gitignore to not try to ignore the entire minecraft world * minecraft: clean up MC-specific tests * minecraft: use pkgutil instead of open * minecraft: ship as apworld * mc: update region to new api * Increase upper limit on advancement and egg shard goals * mc: reduce egg shard count by 5 for structure compasses * Minecraft: add more tests Ensures data loading works; tests beatability with various options at their max setting; new tests for 1.19 advancements * test improvements * mc: typing and imports cleanup * parens * mc: condense filler item code and override get_filler_item_name
This commit is contained in:
@@ -1,17 +1,16 @@
|
||||
import os
|
||||
import json
|
||||
from base64 import b64encode, b64decode
|
||||
from math import ceil
|
||||
from typing import Dict, Any
|
||||
|
||||
from .Items import MinecraftItem, item_table, required_items, junk_weights
|
||||
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_advancement_rules, set_completion_rules
|
||||
from worlds.generic.Rules import exclusion_rules
|
||||
from BaseClasses import Region, Entrance, Item, Tutorial, ItemClassification, Location
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
|
||||
from BaseClasses import Region, Entrance, Item, Tutorial, ItemClassification
|
||||
from . import Constants
|
||||
from .Options import minecraft_options
|
||||
from ..AutoWorld import World, WebWorld
|
||||
from .Structures import shuffle_structures
|
||||
from .ItemPool import build_item_pool, get_junk_item_names
|
||||
from .Rules import set_rules
|
||||
|
||||
client_version = 9
|
||||
|
||||
@@ -71,13 +70,13 @@ class MinecraftWorld(World):
|
||||
topology_present = True
|
||||
web = MinecraftWebWorld()
|
||||
|
||||
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()}
|
||||
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):
|
||||
exits = [connection[0] for connection in default_connections]
|
||||
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,
|
||||
@@ -96,74 +95,70 @@ class MinecraftWorld(World):
|
||||
'race': self.multiworld.is_race,
|
||||
}
|
||||
|
||||
def generate_basic(self):
|
||||
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
|
||||
|
||||
# Generate item pool
|
||||
itempool = []
|
||||
junk_pool = junk_weights.copy()
|
||||
# Add all required progression items
|
||||
for (name, num) in required_items.items():
|
||||
itempool += [name] * num
|
||||
# Add structure compasses if desired
|
||||
if self.multiworld.structure_compasses[self.player]:
|
||||
structures = [connection[1] for connection in default_connections]
|
||||
for struct_name in structures:
|
||||
itempool.append(f"Structure Compass ({struct_name})")
|
||||
# Add dragon egg shards
|
||||
if self.multiworld.egg_shards_required[self.player] > 0:
|
||||
itempool += ["Dragon Egg Shard"] * self.multiworld.egg_shards_available[self.player]
|
||||
# Add bee traps if desired
|
||||
bee_trap_quantity = ceil(self.multiworld.bee_traps[self.player] * (len(self.location_names) - len(itempool)) * 0.01)
|
||||
itempool += ["Bee Trap"] * bee_trap_quantity
|
||||
# Fill remaining items with randomly generated junk
|
||||
itempool += self.multiworld.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()), k=len(self.location_names) - len(itempool))
|
||||
# Convert itempool into real items
|
||||
itempool = [item for item in map(lambda name: self.create_item(name), itempool)]
|
||||
return MinecraftItem(name, item_class, self.item_name_to_id.get(name, None), self.player)
|
||||
|
||||
# Choose locations to automatically exclude based on settings
|
||||
exclusion_pool = set()
|
||||
exclusion_types = ['hard', 'unreasonable']
|
||||
for key in exclusion_types:
|
||||
if not getattr(self.multiworld, 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.multiworld.required_bosses[self.player].current_key))
|
||||
exclusion_rules(self.multiworld, self.player, exclusion_pool)
|
||||
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)
|
||||
|
||||
# Prefill event locations with their events
|
||||
self.multiworld.get_location("Blaze Spawner", self.player).place_locked_item(self.create_item("Blaze Rods"))
|
||||
self.multiworld.get_location("Ender Dragon", self.player).place_locked_item(self.create_item("Defeat Ender Dragon"))
|
||||
self.multiworld.get_location("Wither", self.player).place_locked_item(self.create_item("Defeat Wither"))
|
||||
def create_event_item(self, name: str) -> None:
|
||||
item = self.create_item(name)
|
||||
item.classification = ItemClassification.progression
|
||||
return item
|
||||
|
||||
self.multiworld.itempool += itempool
|
||||
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)
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.multiworld.random.choices(list(junk_weights.keys()), weights=list(junk_weights.values()))[0]
|
||||
# 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)
|
||||
|
||||
def set_rules(self):
|
||||
set_advancement_rules(self.multiworld, self.player)
|
||||
set_completion_rules(self.multiworld, self.player)
|
||||
# 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)
|
||||
|
||||
def create_regions(self):
|
||||
def MCRegion(region_name: str, exits=[]):
|
||||
ret = Region(region_name, self.player, self.multiworld)
|
||||
ret.locations = [MinecraftAdvancement(self.player, loc_name, loc_data.id, ret)
|
||||
for loc_name, loc_data in advancement_table.items()
|
||||
if loc_data.region == region_name]
|
||||
for exit in exits:
|
||||
ret.exits.append(Entrance(self.player, exit, ret))
|
||||
return ret
|
||||
# Add events
|
||||
self.create_event("Nether Fortress", "Blaze Rods")
|
||||
self.create_event("The End", "Ender Dragon")
|
||||
self.create_event("Nether Fortress", "Wither")
|
||||
|
||||
self.multiworld.regions += [MCRegion(*r) for r in mc_regions]
|
||||
link_minecraft_structures(self.multiworld, self.player)
|
||||
# Shuffle the connections
|
||||
shuffle_structures(self)
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
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):
|
||||
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]
|
||||
@@ -171,20 +166,16 @@ class MinecraftWorld(World):
|
||||
slot_data[option_name] = int(option.value)
|
||||
return slot_data
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
item_data = item_table[name]
|
||||
if name == "Bee Trap":
|
||||
classification = ItemClassification.trap
|
||||
# prevent books from going on excluded locations
|
||||
elif name in ("Sharpness III Book", "Infinity Book", "Looting III Book"):
|
||||
classification = ItemClassification.useful
|
||||
elif item_data.progression:
|
||||
classification = ItemClassification.progression
|
||||
else:
|
||||
classification = ItemClassification.filler
|
||||
item = MinecraftItem(name, classification, item_data.code, self.player)
|
||||
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"
|
||||
|
||||
return item
|
||||
|
||||
def mc_update_output(raw_data, server, port):
|
||||
data = json.loads(b64decode(raw_data))
|
||||
|
||||
Reference in New Issue
Block a user