Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Landstalker: Refactor for PR Review
Browse files Browse the repository at this point in the history
ThePhar committed Nov 23, 2023
1 parent d0fedd5 commit ecdef81
Showing 8 changed files with 247 additions and 229 deletions.
49 changes: 28 additions & 21 deletions worlds/landstalker/Hints.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import random
from typing import List
from BaseClasses import MultiWorld, Location
from . import LandstalkerItem
from typing import TYPE_CHECKING

from BaseClasses import Location
from .data.hint_source import HINT_SOURCES_JSON

if TYPE_CHECKING:
from random import Random
from . import LandstalkerWorld


def generate_blurry_location_hint(location: Location, random: random.Random):
cleaned_location_name = location.hint_text.lower().translate({ord(c): None for c in '(),:'})
cleaned_location_name.replace('-', ' ')
cleaned_location_name.replace('/', ' ')
cleaned_location_name.replace('.', ' ')
location_name_words = [w for w in cleaned_location_name.split(' ') if len(w) > 3]
def generate_blurry_location_hint(location: Location, random: "Random"):
cleaned_location_name = location.hint_text.lower().translate({ord(c): None for c in "(),:"})
cleaned_location_name.replace("-", " ")
cleaned_location_name.replace("/", " ")
cleaned_location_name.replace(".", " ")
location_name_words = [w for w in cleaned_location_name.split(" ") if len(w) > 3]

random_word_1 = "mysterious"
random_word_2 = "place"
@@ -22,32 +25,36 @@ def generate_blurry_location_hint(location: Location, random: random.Random):
return [random_word_1, random_word_2]


def generate_lithograph_hint(multiworld: MultiWorld, player: int, jewel_items: List[LandstalkerItem]):
def generate_lithograph_hint(world: "LandstalkerWorld"):
hint_text = "It's barely readable:\n"
jewel_items = world.jewel_items

for item in jewel_items:
# Jewel hints are composed of 4 'words' shuffled randomly:
# - the name of the player whose world contains said jewel (if not ours)
# - the color of the jewel (if relevant)
# - two random words from the location name
words = generate_blurry_location_hint(item.location, multiworld.per_slot_randoms[player])
words = generate_blurry_location_hint(item.location, world.random)
words[0] = words[0].upper()
words[1] = words[1].upper()
if len(jewel_items) < 6:
# Add jewel color if we are not using generic jewels because jewel count is 6 or more
words.append(item.name.split(' ')[0].upper())
if item.location.player != player:
words.append(item.name.split(" ")[0].upper())
if item.location.player != world.player:
# Add player name if it's not in our own world
words.append(multiworld.get_player_name(item.location.player).upper())
multiworld.per_slot_randoms[player].shuffle(words)
player_name = world.multiworld.get_player_name(world.player)
words.append(player_name.upper())
world.random.shuffle(words)
hint_text += " ".join(words) + "\n"
return hint_text.rstrip('\n')
return hint_text.rstrip("\n")


def generate_random_hints(multiworld: MultiWorld, this_player: int):
def generate_random_hints(world: "LandstalkerWorld"):
hints = {}
hint_texts = []
random = multiworld.per_slot_randoms[this_player]
random = world.random
multiworld = world.multiworld
this_player = world.player

# Exclude Life Stock from the hints as some of them are considered as progression for Fahl, but isn't really
# exciting when hinted
@@ -80,7 +87,7 @@ def generate_random_hints(multiworld: MultiWorld, this_player: int):
# Hint-type #3: Own progression item in remote location
for item in remote_own_progression_items:
other_player = multiworld.get_player_name(item.location.player)
if item.location.game == 'Landstalker':
if item.location.game == "Landstalker":
region_hint_name = item.location.parent_region.hint_text
hint_texts.append(f"If you need {item.name}, tell {other_player} to look {region_hint_name}.")
else:
@@ -121,7 +128,7 @@ def generate_random_hints(multiworld: MultiWorld, this_player: int):
hint_texts = list(set(hint_texts))
random.shuffle(hint_texts)

hint_count = multiworld.hint_count[this_player].value
hint_count = world.options.hint_count.value
del hint_texts[hint_count:]

hint_source_names = [source["description"] for source in HINT_SOURCES_JSON if
11 changes: 4 additions & 7 deletions worlds/landstalker/Items.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Dict, NamedTuple, List
from typing import Dict, List, NamedTuple

from BaseClasses import Item, ItemClassification

BASE_ITEM_ID = 4000
@@ -96,13 +97,9 @@ def get_weighted_filler_item_names():
weighted_item_names: List[str] = []
for name, data in item_table.items():
if data.classification == ItemClassification.filler:
weighted_item_names += [name for _ in range(0, data.quantity)]
weighted_item_names += [name for _ in range(data.quantity)]
return weighted_item_names


def build_item_name_to_id_table():
item_name_to_id_table = {}
for name, data in item_table.items():
item_name_to_id_table[name] = data.id + BASE_ITEM_ID
return item_name_to_id_table

return {name: data.id + BASE_ITEM_ID for name, data in item_table.items()}
12 changes: 7 additions & 5 deletions worlds/landstalker/Locations.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from typing import Dict, Optional
from BaseClasses import Location, Region

from BaseClasses import Location
from .Regions import LandstalkerRegion
from .data.item_source import ITEM_SOURCES_JSON

BASE_LOCATION_ID = 4000
@@ -13,12 +15,12 @@ class LandstalkerLocation(Location):
type_string: str
price: int = 0

def __init__(self, player: int, name: str, location_id: Optional[int], region: Region, type_string: str):
def __init__(self, player: int, name: str, location_id: Optional[int], region: LandstalkerRegion, type_string: str):
super().__init__(player, name, location_id, region)
self.type_string = type_string


def create_locations(player: int, regions_table: Dict[str, Region], name_to_id_table: Dict[str, int]):
def create_locations(player: int, regions_table: Dict[str, LandstalkerRegion], name_to_id_table: Dict[str, int]):
# Create real locations from the data inside the corresponding JSON file
for data in ITEM_SOURCES_JSON:
region_id = data["nodeId"]
@@ -27,8 +29,8 @@ def create_locations(player: int, regions_table: Dict[str, Region], name_to_id_t
region.locations.append(new_location)

# Create a specific end location that will contain a fake win-condition item
end_location = LandstalkerLocation(player, "End", None, regions_table['end'], "reward")
regions_table['end'].locations.append(end_location)
end_location = LandstalkerLocation(player, "End", None, regions_table["end"], "reward")
regions_table["end"].locations.append(end_location)


def build_location_name_to_id_table():
105 changes: 53 additions & 52 deletions worlds/landstalker/Options.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from typing import Dict
from dataclasses import dataclass

from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle
from Options import Choice, DeathLink, DefaultOnToggle, PerGameCommonOptions, Range, Toggle


class LandstalkerGoal(Choice):
"""
The goal to accomplish in order to complete the seed.
- Beat Gola: beat the usual final boss (same as vanilla)
- Reach Kazalt: find the jewels and take the teleporter to Kazalt
- Beat Dark Nole: the door to King Nole's fight brings you into a final dungeon with an absurdly hard boss you have to beat to win the game
- Beat Dark Nole: the door to King Nole's fight brings you into a final dungeon with an absurdly hard boss you have
to beat to win the game
"""
display_name = "Goal"

@@ -21,9 +22,9 @@ class LandstalkerGoal(Choice):

class JewelCount(Range):
"""
Determines the number of jewels to find in order to be able to reach Kazalt
Determines the number of jewels to find to be able to reach Kazalt.
"""
display_name = "Jewel count"
display_name = "Jewel Count"
range_start = 0
range_end = 9
default = 5
@@ -34,7 +35,7 @@ class ProgressiveArmors(DefaultOnToggle):
When obtaining an armor, you get the next armor tier instead of getting the specific armor tier that was
placed here by randomization. Enabling this provides a smoother progression.
"""
display_name = "Progressive armors"
display_name = "Progressive Armors"


class UseRecordBook(DefaultOnToggle):
@@ -58,15 +59,15 @@ class EnsureEkeEkeInShops(DefaultOnToggle):
Ensures an EkeEke will always be for sale in one shop per region in the game.
Disabling this can lead to frustrating situations where you cannot refill your health items and might get locked.
"""
display_name = "Ensure EkeEke in shops"
display_name = "Ensure EkeEke in Shops"


class RemoveGumiBoulder(Toggle):
"""
Removes the boulder between Gumi and Ryuma which is usually a one-way path.
This makes the vanilla early game (Massan, Gumi...) more easily accessible when starting outside of it.
Removes the boulder between Gumi and Ryuma, which is usually a one-way path.
This makes the vanilla early game (Massan, Gumi...) more easily accessible when starting outside it.
"""
display_name = "Remove boulder after Gumi"
display_name = "Remove Boulder After Gumi"


class EnemyJumpingInLogic(Toggle):
@@ -75,23 +76,23 @@ class EnemyJumpingInLogic(Toggle):
This gives access to Mountainous Area from Lake Shrine sector and to the cliff chest behind a magic tree near Mir Tower.
These tricks not being easy, you should leave this disabled until practiced.
"""
display_name = "Enemy jumping in logic"
display_name = "Enemy Jumping in Logic"


class TreeCuttingGlitchInLogic(Toggle):
"""
Adds tree-cutting glitch as a logical rule, enabling access to both chests behind magic trees in Mir Tower Sector
without having Axe Magic.
"""
display_name = "Tree-cutting glitch in logic"
display_name = "Tree-cutting Glitch in Logic"


class DamageBoostingInLogic(Toggle):
"""
Adds damage boosting as a logical rule, removing any requirements involving Iron Boots or Fireproof Boots.
Who doesn't like walking on spikes and lava?
"""
display_name = "Damage boosting in logic"
display_name = "Damage Boosting in Logic"


class WhistleUsageBehindTrees(DefaultOnToggle):
@@ -100,7 +101,7 @@ class WhistleUsageBehindTrees(DefaultOnToggle):
Enabling this allows using Einstein Whistle from both sides of the magic trees.
This is only useful in seeds starting in the "waterfall" spawn region or where teleportation trees are made open from the start.
"""
display_name = "Allow using Einstein Whistle behind trees"
display_name = "Allow Using Einstein Whistle Behind Trees"


class SpawnRegion(Choice):
@@ -109,19 +110,19 @@ class SpawnRegion(Choice):
It is advised to keep Massan as your spawn location for your first few seeds.
Picking a late-game location can make the seed significantly harder, both for logic and combat.
"""
display_name = "Starting region"
display_name = "Starting Region"

option_massan = "massan"
option_gumi = "gumi"
option_kado = "kado"
option_waterfall = "waterfall"
option_ryuma = "ryuma"
option_mercator = "mercator"
option_verla = "verla"
option_greenmaze = "greenmaze"
option_destel = "destel"
option_massan = 0
option_gumi = 1
option_kado = 2
option_waterfall = 3
option_ryuma = 4
option_mercator = 5
option_verla = 6
option_greenmaze = 7
option_destel = 8

default = "massan"
default = 0


class TeleportTreeRequirements(Choice):
@@ -132,7 +133,7 @@ class TeleportTreeRequirements(Choice):
- Visit Trees: Both trees from a tree pair need to be visited to teleport between them
Vanilla behavior is "Clear Tibor And Visit Trees"
"""
display_name = "Teleportation trees requirements"
display_name = "Teleportation Trees Requirements"

option_none = 0
option_clear_tibor = 1
@@ -146,7 +147,7 @@ class ShuffleTrees(Toggle):
"""
If enabled, all teleportation trees will be shuffled into new pairs.
"""
display_name = "Shuffle teleportation trees"
display_name = "Shuffle Teleportation Trees"


class ReviveUsingEkeeke(DefaultOnToggle):
@@ -155,15 +156,15 @@ class ReviveUsingEkeeke(DefaultOnToggle):
This setting allows disabling this feature, making the game extremely harder.
USE WITH CAUTION!
"""
display_name = "Revive using EkeEke"
display_name = "Revive Using EkeEke"


class ShopPricesFactor(Range):
"""
Applies a percentage factor on all prices in shops. Having higher prices can lead to a bit of gold farming, which
can make seeds longer but also sometimes more frustrating.
"""
display_name = "Shop prices factor (%)"
display_name = "Shop Prices Factor (%)"
range_start = 50
range_end = 200
default = 100
@@ -178,7 +179,7 @@ class CombatDifficulty(Choice):
- Hard: 140% HP & damage
- Insane: 200% HP & damage
"""
display_name = "Combat difficulty"
display_name = "Combat Difficulty"

option_peaceful = 0
option_easy = 1
@@ -193,35 +194,35 @@ class HintCount(Range):
"""
Determines the number of Foxy NPCs that will be scattered across the world, giving various types of hints
"""
display_name = "Hint count"
display_name = "Hint Count"
range_start = 0
range_end = 25
default = 12


ls_options: Dict[str, type(Option)] = {
"goal": LandstalkerGoal,
"spawn_region": SpawnRegion,
"jewel_count": JewelCount,
"progressive_armors": ProgressiveArmors,
"use_record_book": UseRecordBook,
"use_spell_book": UseSpellBook,
@dataclass
class LandstalkerOptions(PerGameCommonOptions):
goal: LandstalkerGoal
spawn_region: SpawnRegion
jewel_count: JewelCount
progressive_armors: ProgressiveArmors
use_record_book: UseRecordBook
use_spell_book: UseSpellBook

"shop_prices_factor": ShopPricesFactor,
"combat_difficulty": CombatDifficulty,
shop_prices_factor: ShopPricesFactor
combat_difficulty: CombatDifficulty

"teleport_tree_requirements": TeleportTreeRequirements,
"shuffle_trees": ShuffleTrees,
teleport_tree_requirements: TeleportTreeRequirements
shuffle_trees: ShuffleTrees

"ensure_ekeeke_in_shops": EnsureEkeEkeInShops,
"remove_gumi_boulder": RemoveGumiBoulder,
"allow_whistle_usage_behind_trees": WhistleUsageBehindTrees,
"handle_damage_boosting_in_logic": DamageBoostingInLogic,
"handle_enemy_jumping_in_logic": EnemyJumpingInLogic,
"handle_tree_cutting_glitch_in_logic": TreeCuttingGlitchInLogic,
ensure_ekeeke_in_shops: EnsureEkeEkeInShops
remove_gumi_boulder: RemoveGumiBoulder
allow_whistle_usage_behind_trees: WhistleUsageBehindTrees
handle_damage_boosting_in_logic: DamageBoostingInLogic
handle_enemy_jumping_in_logic: EnemyJumpingInLogic
handle_tree_cutting_glitch_in_logic: TreeCuttingGlitchInLogic

"hint_count": HintCount,
hint_count: HintCount

"revive_using_ekeeke": ReviveUsingEkeeke,
"death_link": DeathLink,
}
revive_using_ekeeke: ReviveUsingEkeeke
death_link: DeathLink
Loading

0 comments on commit ecdef81

Please sign in to comment.