[OC2] Overworld Logic (#1530)
This commit is contained in:
parent
6671b21a86
commit
d4b793902f
|
@ -1,22 +1,17 @@
|
|||
from BaseClasses import CollectionState
|
||||
from .Overcooked2Levels import Overcooked2GenericLevel, Overcooked2Dlc, Overcooked2Level
|
||||
from .Overcooked2Levels import Overcooked2GenericLevel, Overcooked2Dlc, Overcooked2Level, OverworldRegion, overworld_region_by_level
|
||||
from typing import Dict
|
||||
from random import Random
|
||||
|
||||
|
||||
def has_requirements_for_level_access(state: CollectionState, level_name: str, previous_level_completed_event_name: str,
|
||||
required_star_count: int, player: int) -> bool:
|
||||
# Check if the ramps in the overworld are set correctly
|
||||
if level_name in ramp_logic:
|
||||
(ramp_reqs, level_reqs) = ramp_logic[level_name]
|
||||
required_star_count: int, allow_ramp_tricks: bool, player: int) -> bool:
|
||||
|
||||
for req in level_reqs:
|
||||
if not state.has(req + " Level Complete", player):
|
||||
return False # This level needs another to be beaten first
|
||||
|
||||
for req in ramp_reqs:
|
||||
if not state.has(req + " Ramp", player):
|
||||
return False # The player doesn't have the pre-requisite ramp button
|
||||
# Must have correct ramp buttons and pre-requisite levels, or tricks to sequence break
|
||||
overworld_region = overworld_region_by_level[level_name]
|
||||
overworld_logic = overworld_region_logic[overworld_region]
|
||||
visited = list()
|
||||
if not overworld_logic(state, player, allow_ramp_tricks, visited):
|
||||
return False
|
||||
|
||||
# Kevin Levels Need to have the corresponding items
|
||||
if level_name.startswith("K"):
|
||||
|
@ -81,8 +76,9 @@ def is_item_progression(item_name, level_mapping, include_kevin):
|
|||
if item_name.endswith("Emote"):
|
||||
return False
|
||||
|
||||
if "Kevin" in item_name or "Ramp" in item_name:
|
||||
return True # always progression
|
||||
for item_identifier in ["Kevin", "Ramp", "Dash"]:
|
||||
if item_identifier in item_name:
|
||||
return True # These things are always progression because they can have overworld implications
|
||||
|
||||
def item_in_logic(shortname, _item_name):
|
||||
for star in range(0, 3):
|
||||
|
@ -214,28 +210,128 @@ def is_completable_no_items(level: Overcooked2GenericLevel) -> bool:
|
|||
|
||||
return len(exclusive) == 0 and len(additive) == 0
|
||||
|
||||
def can_reach_main(state: CollectionState, player: int, allow_tricks: bool, visited: list) -> bool:
|
||||
if OverworldRegion.main in visited:
|
||||
return False
|
||||
visited.append(OverworldRegion.main)
|
||||
|
||||
# If key missing, doesn't require a ramp to access (or the logic is handled by a preceeding level)
|
||||
#
|
||||
# If empty, a ramp is required to access, but the ramp button is garunteed accessible
|
||||
#
|
||||
# If populated, ramp(s) are required to access and the button requires all levels in the
|
||||
# list to be compelted before it can be pressed
|
||||
#
|
||||
ramp_logic = {
|
||||
"1-5": (["Yellow"], []),
|
||||
"2-2": (["Green"], []),
|
||||
"3-1": (["Blue"], []),
|
||||
"5-2": (["Purple"], []),
|
||||
"6-1": (["Pink"], []),
|
||||
"6-2": (["Red", "Purple"], ["5-1"]), # 5-1 spawns blue button, blue button gets you to red button
|
||||
"Kevin-1": (["Dark Green"], []),
|
||||
"Kevin-7": (["Purple"], ["5-1"]), # 5-1 spawns blue button,
|
||||
# press blue button,
|
||||
# climb blue ramp,
|
||||
# jump the gap,
|
||||
# climb wood ramps
|
||||
"Kevin-8": (["Red", "Blue"], ["5-1", "6-2"]), # Same as above, but 6-2 spawns the ramp to K8
|
||||
return True
|
||||
|
||||
def can_reach_yellow_island(state: CollectionState, player: int, allow_tricks: bool, visited: list) -> bool:
|
||||
if OverworldRegion.yellow_island in visited:
|
||||
return False
|
||||
visited.append(OverworldRegion.yellow_island)
|
||||
|
||||
return state.has("Yellow Ramp", player)
|
||||
|
||||
def can_reach_dark_green_mountain(state: CollectionState, player: int, allow_tricks: bool, visited: list) -> bool:
|
||||
if OverworldRegion.dark_green_mountain in visited:
|
||||
return False
|
||||
visited.append(OverworldRegion.dark_green_mountain)
|
||||
|
||||
return state.has_all({"Dark Green Ramp", "Kevin-1"}, player)
|
||||
|
||||
def can_reach_out_of_bounds(state: CollectionState, player: int, allow_tricks: bool, visited: list) -> bool:
|
||||
if OverworldRegion.out_of_bounds in visited:
|
||||
return False
|
||||
visited.append(OverworldRegion.out_of_bounds)
|
||||
|
||||
return allow_tricks and state.has("Progressive Dash", player) and can_reach_dark_green_mountain(state, player, allow_tricks, visited)
|
||||
|
||||
def can_reach_stonehenge_mountain(state: CollectionState, player: int, allow_tricks: bool, visited: list) -> bool:
|
||||
if OverworldRegion.stonehenge_mountain in visited:
|
||||
return False
|
||||
visited.append(OverworldRegion.stonehenge_mountain)
|
||||
|
||||
if state.has("Blue Ramp", player):
|
||||
return True
|
||||
|
||||
if can_reach_out_of_bounds(state, player, allow_tricks, visited):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def can_reach_sky_shelf(state: CollectionState, player: int, allow_tricks: bool, visited: list) -> bool:
|
||||
if OverworldRegion.sky_shelf in visited:
|
||||
return False
|
||||
visited.append(OverworldRegion.sky_shelf)
|
||||
|
||||
if state.has("Green Ramp", player):
|
||||
return True
|
||||
|
||||
if state.has_all({"5-1 Level Complete", "Purple Ramp"}, player):
|
||||
return True
|
||||
|
||||
if allow_tricks and can_reach_pink_island(state, player, allow_tricks, visited) and state.has("Progressive Dash", player):
|
||||
return True
|
||||
|
||||
if can_reach_tip_of_the_map(state, player, allow_tricks, visited):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def can_reach_pink_island(state: CollectionState, player: int, allow_tricks: bool, visited: list) -> bool:
|
||||
if OverworldRegion.pink_island in visited:
|
||||
return False
|
||||
visited.append(OverworldRegion.pink_island)
|
||||
|
||||
if state.has("Pink Ramp", player):
|
||||
return True
|
||||
|
||||
if allow_tricks and state.has("Progressive Dash", player) and can_reach_sky_shelf(state, player, allow_tricks, visited):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def can_reach_tip_of_the_map(state: CollectionState, player: int, allow_tricks: bool, visited: list) -> bool:
|
||||
if OverworldRegion.tip_of_the_map in visited:
|
||||
return False
|
||||
visited.append(OverworldRegion.tip_of_the_map)
|
||||
|
||||
if state.has_all({"5-1 Level Complete", "Purple Ramp"}, player):
|
||||
return True
|
||||
|
||||
if can_reach_out_of_bounds(state, player, allow_tricks, visited):
|
||||
return True
|
||||
|
||||
if allow_tricks and can_reach_sky_shelf(state, player, allow_tricks, visited):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def can_reach_mars_shelf(state: CollectionState, player: int, allow_tricks: bool, visited: list) -> bool:
|
||||
if OverworldRegion.mars_shelf in visited:
|
||||
return False
|
||||
visited.append(OverworldRegion.mars_shelf)
|
||||
|
||||
tip_of_the_map = can_reach_tip_of_the_map(state, player, allow_tricks, visited)
|
||||
|
||||
if tip_of_the_map and allow_tricks:
|
||||
return True
|
||||
|
||||
if tip_of_the_map and state.has_all({"6-1 Level Complete", "Red Ramp"}, player):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def can_reach_kevin_eight_island(state: CollectionState, player: int, allow_tricks: bool, visited: list) -> bool:
|
||||
if OverworldRegion.kevin_eight_island in visited:
|
||||
return False
|
||||
visited.append(OverworldRegion.kevin_eight_island)
|
||||
|
||||
return can_reach_mars_shelf(state, player, allow_tricks, visited)
|
||||
|
||||
|
||||
overworld_region_logic = {
|
||||
OverworldRegion.main : can_reach_main ,
|
||||
OverworldRegion.yellow_island : can_reach_yellow_island ,
|
||||
OverworldRegion.sky_shelf : can_reach_sky_shelf ,
|
||||
OverworldRegion.stonehenge_mountain: can_reach_stonehenge_mountain,
|
||||
OverworldRegion.tip_of_the_map : can_reach_tip_of_the_map ,
|
||||
OverworldRegion.pink_island : can_reach_pink_island ,
|
||||
OverworldRegion.mars_shelf : can_reach_mars_shelf ,
|
||||
OverworldRegion.dark_green_mountain: can_reach_dark_green_mountain,
|
||||
OverworldRegion.kevin_eight_island : can_reach_kevin_eight_island ,
|
||||
}
|
||||
|
||||
horde_logic = { # Additive
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from enum import IntEnum
|
||||
from typing import TypedDict
|
||||
from Options import DefaultOnToggle, Range, Choice
|
||||
from Options import Toggle, DefaultOnToggle, Range, Choice
|
||||
|
||||
|
||||
class LocationBalancingMode(IntEnum):
|
||||
|
@ -21,6 +21,12 @@ class OC2OnToggle(DefaultOnToggle):
|
|||
return bool(self.value)
|
||||
|
||||
|
||||
class OC2Toggle(Toggle):
|
||||
@property
|
||||
def result(self) -> bool:
|
||||
return bool(self.value)
|
||||
|
||||
|
||||
class LocationBalancing(Choice):
|
||||
"""Location balancing affects the density of progression items found in your world relative to other wordlds. This setting changes nothing for solo games.
|
||||
|
||||
|
@ -36,6 +42,10 @@ class LocationBalancing(Choice):
|
|||
option_full = LocationBalancingMode.full.value
|
||||
default = LocationBalancingMode.compromise.value
|
||||
|
||||
class RampTricks(OC2Toggle):
|
||||
"""If enabled, generated games may require sequence breaks on the overworld map. This includes crossing small gaps and escaping out of bounds."""
|
||||
display_name = "Overworld Tricks"
|
||||
|
||||
|
||||
class DeathLink(Choice):
|
||||
"""DeathLink is an opt-in feature for Multiworlds where individual death events are propogated to all games with DeathLink enabled.
|
||||
|
@ -66,7 +76,7 @@ class AlwaysPreserveCookingProgress(OC2OnToggle):
|
|||
display_name = "Preserve Cooking/Mixing Progress"
|
||||
|
||||
|
||||
class DisplayLeaderboardScores(OC2OnToggle):
|
||||
class DisplayLeaderboardScores(OC2Toggle):
|
||||
"""Modifies the Overworld map to fetch and display the current world records for each level. Press number keys 1-4
|
||||
to view leaderboard scores for that number of players."""
|
||||
display_name = "Display Leaderboard Scores"
|
||||
|
@ -153,6 +163,7 @@ class StarThresholdScale(Range):
|
|||
overcooked_options = {
|
||||
# generator options
|
||||
"location_balancing": LocationBalancing,
|
||||
"ramp_tricks": RampTricks,
|
||||
|
||||
# deathlink
|
||||
"deathlink": DeathLink,
|
||||
|
|
|
@ -372,3 +372,62 @@ level_id_to_shortname = {
|
|||
(Overcooked2Dlc.SEASONAL , 30 ): "Moon 1-4" ,
|
||||
(Overcooked2Dlc.SEASONAL , 31 ): "Moon 1-5" ,
|
||||
}
|
||||
|
||||
class OverworldRegion(IntEnum):
|
||||
main = 0
|
||||
yellow_island = 1
|
||||
sky_shelf = 2
|
||||
stonehenge_mountain = 3
|
||||
tip_of_the_map = 4
|
||||
pink_island = 5
|
||||
mars_shelf = 6
|
||||
dark_green_mountain = 7
|
||||
kevin_eight_island = 8
|
||||
out_of_bounds = 9
|
||||
|
||||
overworld_region_by_level = {
|
||||
"1-1": OverworldRegion.main,
|
||||
"1-2": OverworldRegion.main,
|
||||
"1-3": OverworldRegion.main,
|
||||
"1-4": OverworldRegion.main,
|
||||
"1-5": OverworldRegion.yellow_island,
|
||||
"1-6": OverworldRegion.yellow_island,
|
||||
"2-1": OverworldRegion.main,
|
||||
"2-2": OverworldRegion.sky_shelf,
|
||||
"2-3": OverworldRegion.sky_shelf,
|
||||
"2-4": OverworldRegion.main,
|
||||
"2-5": OverworldRegion.main,
|
||||
"2-6": OverworldRegion.main,
|
||||
"3-1": OverworldRegion.main,
|
||||
"3-2": OverworldRegion.main,
|
||||
"3-3": OverworldRegion.main,
|
||||
"3-4": OverworldRegion.main,
|
||||
"3-5": OverworldRegion.main,
|
||||
"3-6": OverworldRegion.main,
|
||||
"4-1": OverworldRegion.main,
|
||||
"4-2": OverworldRegion.main,
|
||||
"4-3": OverworldRegion.main,
|
||||
"4-4": OverworldRegion.main,
|
||||
"4-5": OverworldRegion.main,
|
||||
"4-6": OverworldRegion.main,
|
||||
"5-1": OverworldRegion.main,
|
||||
"5-2": OverworldRegion.sky_shelf,
|
||||
"5-3": OverworldRegion.main,
|
||||
"5-4": OverworldRegion.tip_of_the_map,
|
||||
"5-5": OverworldRegion.tip_of_the_map,
|
||||
"5-6": OverworldRegion.tip_of_the_map,
|
||||
"6-1": OverworldRegion.pink_island,
|
||||
"6-2": OverworldRegion.tip_of_the_map,
|
||||
"6-3": OverworldRegion.tip_of_the_map,
|
||||
"6-4": OverworldRegion.sky_shelf,
|
||||
"6-5": OverworldRegion.mars_shelf,
|
||||
"6-6": OverworldRegion.mars_shelf,
|
||||
"Kevin-1": OverworldRegion.dark_green_mountain,
|
||||
"Kevin-2": OverworldRegion.main,
|
||||
"Kevin-3": OverworldRegion.main,
|
||||
"Kevin-4": OverworldRegion.main,
|
||||
"Kevin-5": OverworldRegion.main,
|
||||
"Kevin-6": OverworldRegion.main,
|
||||
"Kevin-7": OverworldRegion.tip_of_the_map,
|
||||
"Kevin-8": OverworldRegion.kevin_eight_island,
|
||||
}
|
||||
|
|
|
@ -322,7 +322,7 @@ class Overcooked2World(World):
|
|||
|
||||
level_access_rule: Callable[[CollectionState], bool] = \
|
||||
lambda state, level_name=level.level_name, previous_level_completed_event_name=previous_level_completed_event_name, required_star_count=required_star_count: \
|
||||
has_requirements_for_level_access(state, level_name, previous_level_completed_event_name, required_star_count, self.player)
|
||||
has_requirements_for_level_access(state, level_name, previous_level_completed_event_name, required_star_count, self.options["RampTricks"], self.player)
|
||||
self.connect_regions("Overworld", level.level_name, level_access_rule)
|
||||
|
||||
# Level --> Overworld
|
||||
|
|
|
@ -82,3 +82,13 @@ To completely remove *OC2-Modding*, navigate to your game's installation folder
|
|||
Since the goal of randomizer isn't necessarily to achieve new personal high scores, players may find themselves waiting for a level timer to expire once they've met their objective. A new feature called *Auto-Complete* has been added to automatically complete levels once a target star count has been achieved.
|
||||
|
||||
To enable *Auto-Complete*, press the **Show** button near the top of your screen to expand the modding controls. Then, repeatedly press the **Auto-Complete** button until it shows the desired setting.
|
||||
|
||||
## Overworld Sequence Breaking
|
||||
|
||||
In the world's settings, there is an option called "Overworld Tricks" which allows the generator to make games which require doing tricks with the food truck to complete. This includes:
|
||||
|
||||
- Dashing across gaps
|
||||
|
||||
- "Wiggling" up ledges
|
||||
|
||||
- Going out of bounds [See Video](https://youtu.be/VdOGhi6XPu4)
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import unittest
|
||||
|
||||
from random import Random
|
||||
|
||||
from worlds.AutoWorld import AutoWorldRegister
|
||||
from test.general import setup_solo_multiworld
|
||||
|
||||
from worlds.overcooked2.Items import *
|
||||
from worlds.overcooked2.Overcooked2Levels import Overcooked2Dlc, Overcooked2Level, level_id_to_shortname, ITEMS_TO_EXCLUDE_IF_NO_DLC
|
||||
from worlds.overcooked2.Logic import level_logic, level_shuffle_factory
|
||||
from worlds.overcooked2.Overcooked2Levels import Overcooked2Dlc, Overcooked2Level, OverworldRegion, overworld_region_by_level, level_id_to_shortname, ITEMS_TO_EXCLUDE_IF_NO_DLC
|
||||
from worlds.overcooked2.Logic import level_logic, overworld_region_logic, level_shuffle_factory
|
||||
from worlds.overcooked2.Locations import oc2_location_name_to_id
|
||||
|
||||
|
||||
|
@ -170,3 +172,43 @@ class Overcooked2Test(unittest.TestCase):
|
|||
count += 1
|
||||
|
||||
self.assertEqual(count, len(level_id_range), f"Number of levels in {dlc.name} has discrepancy between level_id range and directory")
|
||||
|
||||
def testOverworldRegion(self):
|
||||
# OverworldRegion
|
||||
# overworld_region_by_level
|
||||
# overworld_region_logic
|
||||
|
||||
# Test for duplicates
|
||||
regions_list = [x for x in OverworldRegion]
|
||||
regions_set = set(regions_list)
|
||||
self.assertEqual(len(regions_list), len(regions_set), f"Duplicate values in OverworldRegion")
|
||||
|
||||
# Test all levels represented
|
||||
shortnames = [level.as_generic_level.shortname for level in Overcooked2Level()]
|
||||
for shortname in shortnames:
|
||||
if " " in shortname:
|
||||
shortname = shortname.split(" ")[1]
|
||||
shortname = shortname.replace("K-", "Kevin-")
|
||||
self.assertIn(shortname, overworld_region_by_level)
|
||||
|
||||
for region in overworld_region_by_level.values():
|
||||
# Test all regions valid
|
||||
self.assertIn(region, regions_list)
|
||||
|
||||
# Test Region Coverage
|
||||
self.assertIn(region, overworld_region_logic)
|
||||
|
||||
# Test all regions valid
|
||||
for region in overworld_region_logic:
|
||||
self.assertIn(region, regions_set)
|
||||
|
||||
self.assertIn("Overcooked! 2", AutoWorldRegister.world_types.keys())
|
||||
world_type = AutoWorldRegister.world_types["Overcooked! 2"]
|
||||
world = setup_solo_multiworld(world_type)
|
||||
state = world.get_all_state(False)
|
||||
|
||||
# Test region logic
|
||||
for logic in overworld_region_logic.values():
|
||||
for allow_tricks in [False, True]:
|
||||
result = logic(state, 1, allow_tricks, list())
|
||||
self.assertIn(result, [False, True])
|
||||
|
|
Loading…
Reference in New Issue