[OC2] Overworld Logic (#1530)

This commit is contained in:
toasterparty 2023-03-20 09:16:19 -07:00 committed by GitHub
parent 6671b21a86
commit d4b793902f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 260 additions and 42 deletions

View File

@ -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

View File

@ -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,

View File

@ -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,
}

View File

@ -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

View File

@ -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)

View File

@ -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])