forked from mirror/Archipelago
* APQuest * Add confetti cannon * ID change on enemy drop * nevermind * Write the apworld * Actually implement hard mode * split everything into multiple files * Push out webworld into a file * Comment * Enemy health graphics * more ruff rules * graphics :) * heal player when receiving health upgrade * the dumbest client of all time * Fix typo * You can kinda play it now! Now we just need to render the game... :))) * fix kvui imports again * It's playable. Kind of * oops * Sounds and stuff * exceptions for audio * player sprite stuff * Not attack without sword * Make sure it plays correctly * Collect behavior * ruff * don't need to clear checked_locations, but do need to still clear finished_game * Connect calls disconnect, so this is not necessary * more seemless reconnection * Ok now I think it's correct * Bgm * Bgm * minor adjustment * More refactoring of graphics and sound * add graphics * Item column * Fix enemies not regaining their health * oops * oops * oops * 6 health final boss on hard mode * boss_6.png * Display APQuest items correctly * auto switch tabs * some mypy stuff * Intro song * Confetti Cannon * a bit more confetti work * launcher component * Graphics change * graphics and cleanup * fix apworld * comment out horse and cat for now * add docs * copypasta * ruff made my comment look unhinged * Move that comment * Fix typing and don't import kvui in nogui * lmao that already exists I don't need to do it myself * Must've just copied this from somewhere * order change * Add unit tests * Notes about the client * oops * another intro song case * Write WebWorld and setup guides * Yes description provided * thing * how to play * Music and Volume * Add cat and horse player sprites * updates * Add hammer and breakable wall * TODO * replace wav with ogg * Codeowners and readme * finish unit tests * lint * Todid * Update worlds/apquest/client/ap_quest_client.py Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Update worlds/apquest/client/custom_views.py Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Filler pattern * __future__ annotations * twebhost * Allow wasd and arrow keys * correct wording * oops * just say the website * append instead of += * qwint is onto my favoritism * kitty alias * Add a comment about preplaced items for assertAccessDependency * Use classvar_matrix instead of MultiworldTestBase * actually remove multiworld stuff from those tests * missed one more * Refactor a bit more * Fix getting of the user path * Actually explain components * Meh * Be a bit clearer about what's what * oops * More comments in the regions.py file * Nevermind * clarify regions further * I use too many brackets * Ok I'm done fr * simplify wording * missing . * Add precollected example * add note about precollected advancements * missing s * APQuest sound rework * Volume slider * I forgot I made this * a * fix volume of jingles * Add math trap to game (only works in play_in_console mode so far) * Math trap in apworld and client side * Fix background during math trap * fix leading 0 * Sound and further ui improvements for Math Trap * fix music bug * rename apquest subfolder to game * Move comment to where it belongs * Clear up language around components (hopefully) * Clear up what CommonClient is * Reword some more * Mention Archipelago (the program) explicitly * Update worlds/apquest/docs/en_APQuest.md Co-authored-by: Ixrec <ericrhitchcock@gmail.com> * Explain a bit more why you would use classvar matrix * reword the assert raises stuff * the volume slider thing is no longer true * german game page * Be more clear about why we're overriding Item and Location * default item classification * logically considered -> relevant to logic () * Update worlds/apquest/items.py Co-authored-by: Ixrec <ericrhitchcock@gmail.com> * a word on the ambiguity of the word 'filler' * more rewording * amount -> number * stress the necessity of appending to the multiworld itempool * Update worlds/apquest/locations.py Co-authored-by: Ixrec <ericrhitchcock@gmail.com> * get_location_names_with_ids * slight rewording of the new helper method * add some words about creating known location+item pairs * Add some more words to worlds/apqeust/options.py * more words in options.py * 120 chars (thanks Ixrec >:((( LOL) * Less confusing wording about rules, hopefully? * victory -> completion * remove the immediate creation of the hammer rule on the option region entrance * access rule performance * Make all imports module-level in world.py * formatting * get rid of noqa RUF012 (and also disable the rule in my local ruff.toml * move comment for docstring closer to docstring in another place * advancement???? * Missing function type annotations * pass mypy again (I don't love this one but all the alternatives are equally bad) * subclass instead of override * I forgor to remove these * Get rid of classvar_matrix and instead talk about some other stuff * protect people a bit from the assertAccessDependency nonsense * reword a bit more * word * More accessdependency text * More accessdependency text * More accessdependency text * More accessdependency text * oops * this is supposed to be absolute * Add some links to docs * that's called game now * Add an archipelago.json and explain what it means * new line who dis * reorganize a bit * ignore instead of skip * Update archipelago.json * She new on my line till I * Update archipelago.json * add controls tab * new ruff rule? idk * WHOOPS * Pack graphics into fewer files * annoying ruff format thing * Cleanup + mypy * relative import * Update worlds/apquest/client/custom_views.py Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com> * Update generate_math_problem.py * Update worlds/apquest/game/player.py Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com> --------- Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> Co-authored-by: Ixrec <ericrhitchcock@gmail.com> Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
137 lines
7.6 KiB
Python
137 lines
7.6 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
from BaseClasses import ItemClassification, Location
|
|
|
|
from . import items
|
|
|
|
if TYPE_CHECKING:
|
|
from .world import APQuestWorld
|
|
|
|
# Every location must have a unique integer ID associated with it.
|
|
# We will have a lookup from location name to ID here that, in world.py, we will import and bind to the world class.
|
|
# Even if a location doesn't exist on specific options, it must be present in this lookup.
|
|
LOCATION_NAME_TO_ID = {
|
|
"Top Left Room Chest": 1,
|
|
"Top Middle Chest": 2,
|
|
"Bottom Left Chest": 3,
|
|
"Bottom Left Extra Chest": 4,
|
|
"Bottom Right Room Left Chest": 5,
|
|
"Bottom Right Room Right Chest": 6,
|
|
# Location IDs don't need to be sequential, as long as they're unique and greater than 0.
|
|
"Right Room Enemy Drop": 10,
|
|
}
|
|
|
|
|
|
# Each Location instance must correctly report the "game" it belongs to.
|
|
# To make this simple, it is common practice to subclass the basic Location class and override the "game" field.
|
|
class APQuestLocation(Location):
|
|
game = "APQuest"
|
|
|
|
|
|
# Let's make one more helper method before we begin actually creating locations.
|
|
# Later on in the code, we'll want specific subsections of LOCATION_NAME_TO_ID.
|
|
# To reduce the chance of copy-paste errors writing something like {"Chest": LOCATION_NAME_TO_ID["Chest"]},
|
|
# let's make a helper method that takes a list of location names and returns them as a dict with their IDs.
|
|
# Note: There is a minor typing quirk here. Some functions want location addresses to be an "int | None",
|
|
# so while our function here only ever returns dict[str, int], we annotate it as dict[str, int | None].
|
|
def get_location_names_with_ids(location_names: list[str]) -> dict[str, int | None]:
|
|
return {location_name: LOCATION_NAME_TO_ID[location_name] for location_name in location_names}
|
|
|
|
|
|
def create_all_locations(world: APQuestWorld) -> None:
|
|
create_regular_locations(world)
|
|
create_events(world)
|
|
|
|
|
|
def create_regular_locations(world: APQuestWorld) -> None:
|
|
# Finally, we need to put the Locations ("checks") into their regions.
|
|
# Once again, before we do anything, we can grab our regions we created by using world.get_region()
|
|
overworld = world.get_region("Overworld")
|
|
top_left_room = world.get_region("Top Left Room")
|
|
bottom_right_room = world.get_region("Bottom Right Room")
|
|
right_room = world.get_region("Right Room")
|
|
|
|
# One way to create locations is by just creating them directly via their constructor.
|
|
bottom_left_chest = APQuestLocation(
|
|
world.player, "Bottom Left Chest", world.location_name_to_id["Bottom Left Chest"], overworld
|
|
)
|
|
|
|
# You can then add them to the region.
|
|
overworld.locations.append(bottom_left_chest)
|
|
|
|
# A simpler way to do this is by using the region.add_locations helper.
|
|
# For this, you need to have a dict of location names to their IDs (i.e. a subset of location_name_to_id)
|
|
# Aha! So that's why we made that "get_location_names_with_ids" helper method earlier.
|
|
# You also need to pass your overridden Location class.
|
|
bottom_right_room_locations = get_location_names_with_ids(
|
|
["Bottom Right Room Left Chest", "Bottom Right Room Right Chest"]
|
|
)
|
|
bottom_right_room.add_locations(bottom_right_room_locations, APQuestLocation)
|
|
|
|
top_left_room_locations = get_location_names_with_ids(["Top Left Room Chest"])
|
|
top_left_room.add_locations(top_left_room_locations, APQuestLocation)
|
|
|
|
right_room_locations = get_location_names_with_ids(["Right Room Enemy Drop"])
|
|
right_room.add_locations(right_room_locations, APQuestLocation)
|
|
|
|
# Locations may be in different regions depending on the player's options.
|
|
# In our case, the hammer option puts the Top Middle Chest into its own room called Top Middle Room.
|
|
top_middle_room_locations = get_location_names_with_ids(["Top Middle Chest"])
|
|
if world.options.hammer:
|
|
top_middle_room = world.get_region("Top Middle Room")
|
|
top_middle_room.add_locations(top_middle_room_locations, APQuestLocation)
|
|
else:
|
|
overworld.add_locations(top_middle_room_locations, APQuestLocation)
|
|
|
|
# Locations may exist only if the player enables certain options.
|
|
# In our case, the extra_starting_chest option adds the Bottom Left Extra Chest location.
|
|
if world.options.extra_starting_chest:
|
|
# Once again, it is important to stress that even though the Bottom Left Extra Chest location doesn't always
|
|
# exist, it must still always be present in the world's location_name_to_id.
|
|
# Whether the location actually exists in the seed is purely determined by whether we create and add it here.
|
|
bottom_left_extra_chest = get_location_names_with_ids(["Bottom Left Extra Chest"])
|
|
overworld.add_locations(bottom_left_extra_chest, APQuestLocation)
|
|
|
|
|
|
def create_events(world: APQuestWorld) -> None:
|
|
# Sometimes, the player may perform in-game actions that allow them to progress which are not related to Items.
|
|
# In our case, the player must press a button in the top left room to open the final boss door.
|
|
# AP has something for this purpose: "Event locations" and "Event items".
|
|
# An event location is no different than a regular location, except it has the address "None".
|
|
# It is treated during generation like any other location, but then it is discarded.
|
|
# This location cannot be "sent" and its item cannot be "received", but the item can be used in logic rules.
|
|
# Since we are creating more locations and adding them to regions, we need to grab those regions again first.
|
|
top_left_room = world.get_region("Top Left Room")
|
|
final_boss_room = world.get_region("Final Boss Room")
|
|
|
|
# One way to create an event is simply to use one of the normal methods of creating a location.
|
|
button_in_top_left_room = APQuestLocation(world.player, "Top Left Room Button", None, top_left_room)
|
|
top_left_room.locations.append(button_in_top_left_room)
|
|
|
|
# We then need to put an event item onto the location.
|
|
# An event item is an item whose code is "None" (same as the event location's address),
|
|
# and whose classification is "progression". Item creation will be discussed more in items.py.
|
|
# Note: Usually, items are created in world.create_items(), which for us happens in items.py.
|
|
# However, when the location of an item is known ahead of time (as is the case with an event location/item pair),
|
|
# it is common practice to create the item when creating the location.
|
|
# Since locations also have to be finalized after world.create_regions(), which runs before world.create_items(),
|
|
# we'll create both the event location and the event item in our locations.py code.
|
|
button_item = items.APQuestItem("Top Left Room Button Pressed", ItemClassification.progression, None, world.player)
|
|
button_in_top_left_room.place_locked_item(button_item)
|
|
|
|
# A way simpler way to do create an event location/item pair is by using the region.create_event helper.
|
|
# Luckily, we have another event we want to create: The Victory event.
|
|
# We will use this event to track whether the player can win the game.
|
|
# The Victory event is a completely optional abstraction - This will be discussed more in set_rules().
|
|
final_boss_room.add_event(
|
|
"Final Boss Defeated", "Victory", location_type=APQuestLocation, item_type=items.APQuestItem
|
|
)
|
|
|
|
# If you create all your regions and locations line-by-line like this,
|
|
# the length of your create_regions might get out of hand.
|
|
# Many worlds use more data-driven approaches using dataclasses or NamedTuples.
|
|
# However, it is worth understanding how the actual creation of regions and locations works,
|
|
# That way, we're not just mindlessly copy-pasting! :)
|