Witness: Bugfixes in response to beta tests (#1473)

* Make all Keep Pressure Plates logically required for the Laser Panel

* Added more Tutorial checks

* Added the remaining two Shipwreck Boat EPs to the exclude list for normal

* Improved itempool filling system, added warning if usefuls had to be eaten

* Moved creation of said warning string to utils

* Fixed logic bug causing broken seeds on Mountain Floor 2

* Hints system change

* Expert Logic Fix

* Fixed typo

* Better wording

* Added missing games to junk hints

* Made sure Entrance names are unique

* Fixed missing Obelisk Side

* Disable Non Randomized + EP Shuffle fix

* Fixed disable_non_randomized precompleted EPs being 'disabled' instead of 'precompleted'

* Fixed if/elif error

* Tutorial Gate Open local symbol item becomes local_early_item in expert instead

* Bump required client version. There is a beta client that sends 0.3.9.

* Removed print statement, oops

* Fixed itempool manipulation in pre_fill

* Replaced string concats with fstrings

* Improved make_warning_string function signature

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Improved performance on removing multiple items from multiworld itempool

* Comment

* Fixed errors with the code

* Made removal from itempool not fail unit test for multiple references

* Moved all item creation to create_items, got rid of itempool modifying system

* Colored Squares is no longer a good item, that's outdated

* Removed double if

* React to from_pool: false by removing a junk item

* Fixed warning if only Fnc Brain was removed

* Make use of string truthiness instead

* Made reading of plandoed items safer

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
This commit is contained in:
NewSoupVi
2023-03-03 00:08:24 +01:00
committed by GitHub
parent 0cf8206660
commit 805f33c39e
12 changed files with 292 additions and 183 deletions

View File

@@ -14,7 +14,7 @@ from .items import WitnessItem, StaticWitnessItems, WitnessPlayerItems
from .rules import set_rules
from .regions import WitnessRegions
from .Options import is_option_enabled, the_witness_options, get_option_value
from .utils import best_junk_to_add_based_on_weights, get_audio_logs
from .utils import best_junk_to_add_based_on_weights, get_audio_logs, make_warning_string
from logging import warning
@@ -38,7 +38,7 @@ class WitnessWorld(World):
"""
game = "The Witness"
topology_present = False
data_version = 12
data_version = 13
static_logic = StaticWitnessLogic()
static_locat = StaticWitnessLocations()
@@ -52,7 +52,7 @@ class WitnessWorld(World):
location_name_to_id = StaticWitnessLocations.ALL_LOCATIONS_TO_ID
item_name_groups = StaticWitnessItems.ITEM_NAME_GROUPS
required_client_version = (0, 3, 8)
required_client_version = (0, 3, 9)
def _get_slot_data(self):
return {
@@ -93,13 +93,13 @@ class WitnessWorld(World):
self.items = WitnessPlayerItems(self.locat, self.multiworld, self.player, self.player_logic)
self.regio = WitnessRegions(self.locat)
def create_regions(self):
self.regio.create_regions(self.multiworld, self.player, self.player_logic)
def generate_basic(self):
self.log_ids_to_hints = dict()
self.junk_items_created = {key: 0 for key in self.items.JUNK_WEIGHTS.keys()}
def create_regions(self):
self.regio.create_regions(self.multiworld, self.player, self.player_logic)
def create_items(self):
# Generate item pool
pool = []
for item in self.items.ITEM_TABLE:
@@ -109,22 +109,12 @@ class WitnessWorld(World):
pool.append(witness_item)
self.items_by_name[item] = witness_item
less_junk = 0
dog_check = self.multiworld.get_location(
"Town Pet the Dog", self.player
)
dog_check.place_locked_item(self.create_item("Puzzle Skip"))
less_junk += 1
for precol_item in self.multiworld.precollected_items[self.player]:
if precol_item.name in self.items_by_name: # if item is in the pool, remove 1 instance.
item_obj = self.items_by_name[precol_item.name]
if item_obj in pool:
pool.remove(item_obj) # remove one instance of this pre-collected item if it exists
pool.remove(item_obj) # remove one instance of this pre-collected item if it exists
for item in self.player_logic.STARTING_INVENTORY:
self.multiworld.push_precollected(self.items_by_name[item])
@@ -132,15 +122,8 @@ class WitnessWorld(World):
for item in self.items.EXTRA_AMOUNTS:
for i in range(0, self.items.EXTRA_AMOUNTS[item]):
if len(pool) < len(self.locat.CHECK_LOCATION_TABLE) - len(self.locat.EVENT_LOCATION_TABLE) - less_junk:
witness_item = self.create_item(item)
pool.append(witness_item)
# Put in junk items to fill the rest
junk_size = len(self.locat.CHECK_LOCATION_TABLE) - len(pool) - len(self.locat.EVENT_LOCATION_TABLE) - less_junk
for i in range(0, junk_size):
pool.append(self.create_item(self.get_filler_item_name()))
witness_item = self.create_item(item)
pool.append(witness_item)
# Tie Event Items to Event Locations (e.g. Laser Activations)
for event_location in self.locat.EVENT_LOCATION_TABLE:
@@ -150,27 +133,108 @@ class WitnessWorld(World):
location_obj = self.multiworld.get_location(event_location, self.player)
location_obj.place_locked_item(item_obj)
self.multiworld.itempool += pool
# Find out how much empty space there is for junk items. -1 for the "Town Pet the Dog" check
itempool_difference = len(self.locat.CHECK_LOCATION_TABLE) - len(self.locat.EVENT_LOCATION_TABLE) - 1
itempool_difference -= len(pool)
def pre_fill(self):
# Put good item on first check if there are any of the designated "good items" in the pool
# Place two locked items: Good symbol on Tutorial Gate Open, and a Puzzle Skip on "Town Pet the Dog"
good_items_in_the_game = []
plandoed_items = set()
for v in self.multiworld.plando_items[self.player]:
if v.get("from_pool", True):
plandoed_items.update({self.items_by_name[i] for i in v.get("items", dict()).keys()
if i in self.items_by_name})
if "item" in v and v["item"] in self.items_by_name:
plandoed_items.add(self.items_by_name[v["item"]])
for symbol in self.items.GOOD_ITEMS:
item = self.items_by_name[symbol]
if item in self.multiworld.itempool: # Only do this if the item is still in item pool (e.g. after plando)
if item in pool and item not in plandoed_items:
# for now, any item that is mentioned in any plando option, even if it's a list of items, is ineligible.
# Hopefully, in the future, plando gets resolved before create_items.
# I could also partially resolve lists myself, but this could introduce errors if not done carefully.
good_items_in_the_game.append(symbol)
if good_items_in_the_game:
random_good_item = self.multiworld.random.choice(good_items_in_the_game)
first_check = self.multiworld.get_location(
"Tutorial Gate Open", self.player
)
item = self.items_by_name[random_good_item]
first_check.place_locked_item(item)
self.multiworld.itempool.remove(item)
if get_option_value(self.multiworld, self.player, "puzzle_randomization") == 1:
self.multiworld.local_early_items[self.player][random_good_item] = 1
else:
first_check = self.multiworld.get_location(
"Tutorial Gate Open", self.player
)
first_check.place_locked_item(item)
pool.remove(item)
dog_check = self.multiworld.get_location(
"Town Pet the Dog", self.player
)
dog_check.place_locked_item(self.create_item("Puzzle Skip"))
# Fill rest of item pool with junk if there is room
if itempool_difference > 0:
for i in range(0, itempool_difference):
self.multiworld.itempool.append(self.create_item(self.get_filler_item_name()))
# Remove junk, Functioning Brain, useful items (non-door), useful door items in that order until there is room
if itempool_difference < 0:
junk = [
item for item in pool
if item.classification in {ItemClassification.filler, ItemClassification.trap}
and item.name != "Functioning Brain"
]
f_brain = [item for item in pool if item.name == "Functioning Brain"]
usefuls = [
item for item in pool
if item.classification == ItemClassification.useful
and item.name not in StaticWitnessLogic.ALL_DOOR_ITEMS_AS_DICT
]
removable_doors = [
item for item in pool
if item.classification == ItemClassification.useful
and item.name in StaticWitnessLogic.ALL_DOOR_ITEMS_AS_DICT
]
self.multiworld.per_slot_randoms[self.player].shuffle(junk)
self.multiworld.per_slot_randoms[self.player].shuffle(usefuls)
self.multiworld.per_slot_randoms[self.player].shuffle(removable_doors)
removed_junk = False
removed_usefuls = False
removed_doors = False
for i in range(itempool_difference, 0):
if junk:
pool.remove(junk.pop())
removed_junk = True
elif f_brain:
pool.remove(f_brain.pop())
elif usefuls:
pool.remove(usefuls.pop())
removed_usefuls = True
elif removable_doors:
pool.remove(removable_doors.pop())
removed_doors = True
warn = make_warning_string(
removed_junk, removed_usefuls, removed_doors, not junk, not usefuls, not removable_doors
)
if warn:
warning(f"This Witness world has too few locations to place all its items."
f" In order to make space, {warn} had to be removed.")
# Finally, add the generated pool to the overall itempool
self.multiworld.itempool += pool
def set_rules(self):
set_rules(self.multiworld, self.player, self.player_logic, self.locat)