Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minecraft pre-1.17 changes #16

Merged
merged 19 commits into from
Jul 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
6e33181
Changed advancement_goal to a Range option
espeon65536 Jun 8, 2021
d7a46f0
added get_option_name to Range option for spoiler generation
espeon65536 Jun 8, 2021
6211760
Merge branch 'main' of https://github.com/ArchipelagoMW/Archipelago i…
espeon65536 Jun 15, 2021
059946d
Shifted Minecraft to the new AutoWorld system
espeon65536 Jun 15, 2021
e49d10a
Clean up imports
espeon65536 Jun 15, 2021
b29d0b8
Fixed some options in the Minecraft section of playerSettings
espeon65536 Jun 16, 2021
cd0306d
additional import cleanup
espeon65536 Jun 16, 2021
16ae77c
Plandoing structures causes them to output in the spoiler log
espeon65536 Jun 17, 2021
f778a26
Forbid villages from spawning in the Nether
espeon65536 Jun 25, 2021
6837cd2
Require the ability to respawn the dragon for all dragon-related adva…
espeon65536 Jun 25, 2021
fd811bf
fix minecraft tests
espeon65536 Jun 25, 2021
719f9d7
Monsters Hunted made a hard-postgame advancement, so both flags must …
espeon65536 Jun 25, 2021
44943f6
Merge branch 'main' of https://github.com/ArchipelagoMW/Archipelago i…
espeon65536 Jun 26, 2021
75891b2
fix tests again
espeon65536 Jun 26, 2021
57c761a
Made AdvancementGoal a Range again
espeon65536 Jun 26, 2021
f918d34
un-disabled villages spawning in nether
espeon65536 Jun 28, 2021
92c21de
Merge branch 'main' of https://github.com/ArchipelagoMW/Archipelago i…
espeon65536 Jun 28, 2021
e37ca97
add bee traps
espeon65536 Jul 2, 2021
1e90470
increment MC client version and network_data_package version
espeon65536 Jul 2, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions BaseClasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -894,13 +894,15 @@ def can_kill_wither(self, player: int):
return self.fortress_loot(player) and normal_kill

def 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('Ingot Crafting', player)
if self.combat_difficulty(player) == 'easy':
return self.has("Progressive Weapons", player, 3) and self.has("Progressive Armor", player, 2) and self.has('Archery', player) and \
self.can_brew_potions(player) and self.can_enchant(player)
return respawn_dragon and self.has("Progressive Weapons", player, 3) and self.has("Progressive Armor", player, 2) and \
self.has('Archery', player) and self.can_brew_potions(player) and self.can_enchant(player)
if self.combat_difficulty(player) == 'hard':
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)
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)


def collect(self, item: Item, event: bool = False, location: Location = None) -> bool:
Expand Down
12 changes: 1 addition & 11 deletions Main.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
from worlds.alttp.Shops import create_shops, ShopSlotFill, SHOP_ID_START, total_shop_slots, FillDisabledShopSlots
from worlds.alttp.ItemPool import generate_itempool, difficulties, fill_prizes
from Utils import output_path, parse_player_names, get_options, __version__, version_tuple
from worlds.minecraft import gen_minecraft, fill_minecraft_slot_data, generate_mc_data
from worlds.minecraft.Regions import minecraft_create_regions
from worlds.generic.Rules import locality_rules
from worlds import Games, lookup_any_item_name_to_id, AutoWorld
import Patch
Expand Down Expand Up @@ -196,9 +194,6 @@ def main(args, seed=None):

AutoWorld.call_all(world, "create_regions")

for player in world.minecraft_player_ids:
minecraft_create_regions(world, player)

for player in world.alttp_player_ids:
if world.open_pyramid[player] == 'goal':
world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt',
Expand Down Expand Up @@ -260,9 +255,6 @@ def main(args, seed=None):

AutoWorld.call_all(world, "generate_basic")

for player in world.minecraft_player_ids:
gen_minecraft(world, player)

logger.info("Running Item Plando")

for item in world.itempool:
Expand Down Expand Up @@ -511,7 +503,7 @@ def write_multidata(roms, outputs):
for slot in world.hk_player_ids:
slot_data[slot] = AutoWorld.call_single(world, "fill_slot_data", slot)
for slot in world.minecraft_player_ids:
slot_data[slot] = fill_minecraft_slot_data(world, slot)
slot_data[slot] = AutoWorld.call_single(world, "fill_slot_data", slot)

locations_data: Dict[int, Dict[int, Tuple[int, int]]] = {player: {} for player in world.player_ids}
for location in world.get_filled_locations():
Expand Down Expand Up @@ -563,8 +555,6 @@ def write_multidata(roms, outputs):
if multidata_task:
multidata_task.result() # retrieve exception if one exists
pool.shutdown() # wait for all queued tasks to complete
for player in world.minecraft_player_ids: # Doing this after shutdown prevents the .apmc from being generated if there's an error
generate_mc_data(world, player)
if not args.skip_playthrough:
logger.info('Calculating playthrough.')
create_playthrough(world)
Expand Down
3 changes: 3 additions & 0 deletions Options.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ def from_any(cls, data: typing.Any) -> Range:
return cls(data)
return cls.from_text(str(data))

def get_option_name(self):
return str(self.value)

def __str__(self):
return str(self.value)

Expand Down
7 changes: 2 additions & 5 deletions playerSettings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,7 @@ Factorio:
burner-mining-drill: 19
stone-furnace: 19
Minecraft:
advancement_goal: # Number of advancements required (out of 92 total) to spawn the Ender Dragon and complete the game.
few: 0 # 30 advancements
normal: 1 # 50
many: 0 # 70
advancement_goal: 50 # Number of advancements required (out of 92 total) to spawn the Ender Dragon and complete the game.
combat_difficulty: # Modifies the level of items logically required for exploring dangerous areas and fighting bosses.
easy: 0
normal: 1
Expand All @@ -119,7 +116,7 @@ Minecraft:
include_postgame_advancements: # Some advancements require defeating the Ender Dragon; this will junk-fill them so you won't have to finish to send some items.
on: 0
off: 1
shuffle_structures: # CURRENTLY DISABLED; enables shuffling of villages, outposts, fortresses, bastions, and end cities.
shuffle_structures: # Enables shuffling of villages, outposts, fortresses, bastions, and end cities.
on: 0
off: 1
A Link to the Past:
Expand Down
21 changes: 10 additions & 11 deletions test/minecraft/TestMinecraft.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import worlds.minecraft.Options
from test.TestBase import TestBase
from BaseClasses import MultiWorld
from worlds.minecraft import minecraft_gen_item_pool
from worlds.minecraft.Regions import minecraft_create_regions, link_minecraft_structures
from worlds.minecraft.Rules import set_rules
from worlds import AutoWorld
from worlds.minecraft import MinecraftWorld
from worlds.minecraft.Items import MinecraftItem, item_table
import Options
from worlds.minecraft.Options import AdvancementGoal, CombatDifficulty

# Converts the name of an item into an item object
def MCItemFactory(items, player: int):
Expand All @@ -29,16 +28,16 @@ class TestMinecraft(TestBase):
def setUp(self):
self.world = MultiWorld(1)
self.world.game[1] = "Minecraft"
self.world.worlds[1] = MinecraftWorld(self.world, 1)
exclusion_pools = ['hard', 'insane', 'postgame']
for pool in exclusion_pools:
setattr(self.world, f"include_{pool}_advancements", [False, False])
setattr(self.world, "advancement_goal", [0, worlds.minecraft.Options.AdvancementGoal(value=0)])
setattr(self.world, "shuffle_structures", [False, False])
setattr(self.world, "combat_difficulty", [0, worlds.minecraft.Options.CombatDifficulty(value=1)])
minecraft_create_regions(self.world, 1)
link_minecraft_structures(self.world, 1)
minecraft_gen_item_pool(self.world, 1)
set_rules(self.world, 1)
setattr(self.world, "advancement_goal", {1: AdvancementGoal(30)})
setattr(self.world, "shuffle_structures", {1: False})
setattr(self.world, "combat_difficulty", {1: CombatDifficulty(1)}) # normal
AutoWorld.call_single(self.world, "create_regions", 1)
AutoWorld.call_single(self.world, "generate_basic", 1)
AutoWorld.call_single(self.world, "set_rules", 1)

def _get_items(self, item_pool, all_except):
if all_except and len(all_except) > 0:
Expand Down
2 changes: 1 addition & 1 deletion worlds/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

network_data_package = {"lookup_any_location_id_to_name": lookup_any_location_id_to_name,
"lookup_any_item_id_to_name": lookup_any_item_id_to_name,
"version": 6}
"version": 7}


@enum.unique
Expand Down
8 changes: 5 additions & 3 deletions worlds/minecraft/Items.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from BaseClasses import Region, Entrance, Location, MultiWorld, Item
from BaseClasses import Item
import typing

class ItemData(typing.NamedTuple):
Expand Down Expand Up @@ -46,6 +46,7 @@ def __init__(self, name: str, progression: bool, code: int, player: int):
"8 Gold Ore": ItemData(45032, False),
"Rotten Flesh": ItemData(45033, False),
"Single Arrow": ItemData(45034, False),
"Bee Trap (Minecraft)": ItemData(45100, False),

"Victory": ItemData(0, True)
}
Expand All @@ -67,8 +68,9 @@ def __init__(self, name: str, progression: bool, code: int, player: int):
"4 Lapis Lazuli": 2,
"16 Porkchops": 8,
"8 Gold Ore": 4,
"Rotten Flesh": 4,
"Single Arrow": 0
"Rotten Flesh": 2,
"Single Arrow": 0,
"Bee Trap (Minecraft)": 2
}

lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in item_table.items() if data.code}
3 changes: 2 additions & 1 deletion worlds/minecraft/Locations.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from BaseClasses import Region, Entrance, Location, MultiWorld, Item
from BaseClasses import Location
import typing

class AdvData(typing.NamedTuple):
Expand Down Expand Up @@ -114,6 +114,7 @@ def __init__(self, player: int, name: str, address: int, parent):
"Two by Two": "100 XP",
"Two Birds, One Arrow": "50 XP",
"Arbalistic": "100 XP",
"Monsters Hunted": "100 XP",
"Beaconator": "50 XP",
"A Balanced Diet": "100 XP",
"Uneasy Alliance": "100 XP",
Expand Down
12 changes: 5 additions & 7 deletions worlds/minecraft/Options.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import typing
from Options import Choice, Option, Toggle, Range

from Options import Choice, Option, Toggle


class AdvancementGoal(Choice):
option_few = 0
option_normal = 1
option_many = 2
default = 1
class AdvancementGoal(Range):
range_start = 0
range_end = 87
default = 50


class CombatDifficulty(Choice):
Expand Down
91 changes: 35 additions & 56 deletions worlds/minecraft/Regions.py
Original file line number Diff line number Diff line change
@@ -1,73 +1,44 @@
from .Locations import MinecraftAdvancement, advancement_table

from BaseClasses import Region, Entrance, Location, MultiWorld, Item

def minecraft_create_regions(world: MultiWorld, player: int):

def MCRegion(region_name: str, exits=[]):
ret = Region(region_name, None, region_name, player)
ret.world = world
ret.locations = [ MinecraftAdvancement(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(player, exit, ret))
return ret

world.regions += [MCRegion(*r) for r in mc_regions]
def link_minecraft_structures(world, player):

# Link mandatory connections first
for (exit, region) in mandatory_connections:
world.get_entrance(exit, player).connect(world.get_region(region, player))

def link_minecraft_structures(world: MultiWorld, player: int):

# Get all unpaired exits and all regions without entrances (except the Menu)
# This function is destructive on these lists.
exits = [exit.name for r in world.regions if r.player == player for exit in r.exits if exit.connected_region == None]
structs = [r.name for r in world.regions if r.player == player and r.entrances == [] and r.name != 'Menu']
exits_spoiler = exits[:] # copy the original order for the spoiler log
try:
assert len(exits) == len(structs)
except AssertionError as e: # this should never happen
raise Exception(f"Could not obtain equal numbers of Minecraft exits and structures for player {player}") from e
raise Exception(f"Could not obtain equal numbers of Minecraft exits and structures for player {player} ({world.player_names[player]})")
num_regions = len(exits)
pairs = {}

def check_valid_connection(exit, struct):
if (exit in exits) and (struct in structs) and (exit not in pairs):
return True
return False

def set_pair(exit, struct):
try:
assert exit in exits
assert struct in structs
except AssertionError as e:
raise Exception(f"Invalid connection: {exit} => {struct} for player {player}")
pairs[exit] = struct
exits.remove(exit)
structs.remove(struct)

# Plando stuff. Remove any utilized exits/structs from the lists.
# Raise error if trying to put Nether Fortress in the End.
if (exit in exits) and (struct in structs) and (exit not in illegal_connections.get(struct, [])):
pairs[exit] = struct
exits.remove(exit)
structs.remove(struct)
else:
raise Exception(f"Invalid connection: {exit} => {struct} for player {player} ({world.player_names[player]})")

# Connect plando structures first
if world.plando_connections[player]:
for connection in world.plando_connections[player]:
try:
if connection.entrance == 'The End Structure' and connection.exit == 'Nether Fortress':
raise Exception(f"Cannot place Nether Fortress in the End for player {player}")
set_pair(connection.entrance, connection.exit)
except Exception as e:
raise Exception(f"Could not connect using {connection}") from e
for conn in world.plando_connections[player]:
set_pair(conn.entrance, conn.exit)

# The algorithm tries to place the most restrictive structures first. This algorithm always works on the
# relatively small set of restrictions here, but does not work on all possible inputs with valid configurations.
if world.shuffle_structures[player]:
# Can't put Nether Fortress in the End
if 'The End Structure' in exits and 'Nether Fortress' in structs:
structs.sort(reverse=True, key=lambda s: len(illegal_connections.get(s, [])))
for struct in structs[:]:
try:
end_struct = world.random.choice([s for s in structs if s != 'Nether Fortress'])
set_pair('The End Structure', end_struct)
except IndexError as e: # should only happen if structs is emptied by plando
raise Exception(f"Plando forced Nether Fortress in the End for player {player}") from e
world.random.shuffle(structs)
for exit, struct in zip(exits[:], structs[:]):
exit = world.random.choice([e for e in exits if e not in illegal_connections.get(struct, [])])
except IndexError:
raise Exception(f"No valid structure placements remaining for player {player} ({world.player_names[player]})")
set_pair(exit, struct)
else: # write remaining default connections
for (exit, struct) in default_connections:
Expand All @@ -77,13 +48,15 @@ def set_pair(exit, struct):
# Make sure we actually paired everything; might fail if plando
try:
assert len(exits) == len(structs) == 0
except AssertionError as e:
raise Exception(f"Failed to connect all Minecraft structures for player {player}; check plando settings in yaml") from e
except AssertionError:
raise Exception(f"Failed to connect all Minecraft structures for player {player} ({world.player_names[player]})")

for exit in exits_spoiler:
world.get_entrance(exit, player).connect(world.get_region(pairs[exit], player))
if world.shuffle_structures[player] or world.plando_connections[player]:
world.spoiler.set_entrance(exit, pairs[exit], 'entrance', player)


for exit, struct in pairs.items():
world.get_entrance(exit, player).connect(world.get_region(struct, player))
if world.shuffle_structures[player]:
world.spoiler.set_entrance(exit, struct, 'entrance', player)

# (Region name, list of exits)
mc_regions = [
Expand Down Expand Up @@ -112,3 +85,9 @@ def set_pair(exit, struct):
('Nether Structure 2', 'Bastion Remnant'),
('The End Structure', 'End City')
}

# Structure: illegal locations
illegal_connections = {
'Nether Fortress': ['The End Structure']
}

Loading