[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 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 typing import Dict
from random import Random from random import Random
def has_requirements_for_level_access(state: CollectionState, level_name: str, previous_level_completed_event_name: str, def has_requirements_for_level_access(state: CollectionState, level_name: str, previous_level_completed_event_name: str,
required_star_count: int, player: int) -> bool: required_star_count: int, allow_ramp_tricks: bool, 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]
for req in level_reqs: # Must have correct ramp buttons and pre-requisite levels, or tricks to sequence break
if not state.has(req + " Level Complete", player): overworld_region = overworld_region_by_level[level_name]
return False # This level needs another to be beaten first overworld_logic = overworld_region_logic[overworld_region]
visited = list()
for req in ramp_reqs: if not overworld_logic(state, player, allow_ramp_tricks, visited):
if not state.has(req + " Ramp", player): return False
return False # The player doesn't have the pre-requisite ramp button
# Kevin Levels Need to have the corresponding items # Kevin Levels Need to have the corresponding items
if level_name.startswith("K"): if level_name.startswith("K"):
@ -81,8 +76,9 @@ def is_item_progression(item_name, level_mapping, include_kevin):
if item_name.endswith("Emote"): if item_name.endswith("Emote"):
return False return False
if "Kevin" in item_name or "Ramp" in item_name: for item_identifier in ["Kevin", "Ramp", "Dash"]:
return True # always progression 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): def item_in_logic(shortname, _item_name):
for star in range(0, 3): 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 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) return True
#
# If empty, a ramp is required to access, but the ramp button is garunteed accessible def can_reach_yellow_island(state: CollectionState, player: int, allow_tricks: bool, visited: list) -> bool:
# if OverworldRegion.yellow_island in visited:
# If populated, ramp(s) are required to access and the button requires all levels in the return False
# list to be compelted before it can be pressed visited.append(OverworldRegion.yellow_island)
#
ramp_logic = { return state.has("Yellow Ramp", player)
"1-5": (["Yellow"], []),
"2-2": (["Green"], []), def can_reach_dark_green_mountain(state: CollectionState, player: int, allow_tricks: bool, visited: list) -> bool:
"3-1": (["Blue"], []), if OverworldRegion.dark_green_mountain in visited:
"5-2": (["Purple"], []), return False
"6-1": (["Pink"], []), visited.append(OverworldRegion.dark_green_mountain)
"6-2": (["Red", "Purple"], ["5-1"]), # 5-1 spawns blue button, blue button gets you to red button
"Kevin-1": (["Dark Green"], []), return state.has_all({"Dark Green Ramp", "Kevin-1"}, player)
"Kevin-7": (["Purple"], ["5-1"]), # 5-1 spawns blue button,
# press blue button, def can_reach_out_of_bounds(state: CollectionState, player: int, allow_tricks: bool, visited: list) -> bool:
# climb blue ramp, if OverworldRegion.out_of_bounds in visited:
# jump the gap, return False
# climb wood ramps visited.append(OverworldRegion.out_of_bounds)
"Kevin-8": (["Red", "Blue"], ["5-1", "6-2"]), # Same as above, but 6-2 spawns the ramp to K8
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 horde_logic = { # Additive

View File

@ -1,6 +1,6 @@
from enum import IntEnum from enum import IntEnum
from typing import TypedDict from typing import TypedDict
from Options import DefaultOnToggle, Range, Choice from Options import Toggle, DefaultOnToggle, Range, Choice
class LocationBalancingMode(IntEnum): class LocationBalancingMode(IntEnum):
@ -21,6 +21,12 @@ class OC2OnToggle(DefaultOnToggle):
return bool(self.value) return bool(self.value)
class OC2Toggle(Toggle):
@property
def result(self) -> bool:
return bool(self.value)
class LocationBalancing(Choice): 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. """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 option_full = LocationBalancingMode.full.value
default = LocationBalancingMode.compromise.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): class DeathLink(Choice):
"""DeathLink is an opt-in feature for Multiworlds where individual death events are propogated to all games with DeathLink enabled. """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" 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 """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.""" to view leaderboard scores for that number of players."""
display_name = "Display Leaderboard Scores" display_name = "Display Leaderboard Scores"
@ -153,6 +163,7 @@ class StarThresholdScale(Range):
overcooked_options = { overcooked_options = {
# generator options # generator options
"location_balancing": LocationBalancing, "location_balancing": LocationBalancing,
"ramp_tricks": RampTricks,
# deathlink # deathlink
"deathlink": DeathLink, "deathlink": DeathLink,

View File

@ -372,3 +372,62 @@ level_id_to_shortname = {
(Overcooked2Dlc.SEASONAL , 30 ): "Moon 1-4" , (Overcooked2Dlc.SEASONAL , 30 ): "Moon 1-4" ,
(Overcooked2Dlc.SEASONAL , 31 ): "Moon 1-5" , (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] = \ 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: \ 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) self.connect_regions("Overworld", level.level_name, level_access_rule)
# Level --> Overworld # 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. 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. 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 import unittest
from random import Random from random import Random
from worlds.AutoWorld import AutoWorldRegister
from test.general import setup_solo_multiworld
from worlds.overcooked2.Items import * 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.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, level_shuffle_factory from worlds.overcooked2.Logic import level_logic, overworld_region_logic, level_shuffle_factory
from worlds.overcooked2.Locations import oc2_location_name_to_id from worlds.overcooked2.Locations import oc2_location_name_to_id
@ -170,3 +172,43 @@ class Overcooked2Test(unittest.TestCase):
count += 1 count += 1
self.assertEqual(count, len(level_id_range), f"Number of levels in {dlc.name} has discrepancy between level_id range and directory") 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])