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>
167 lines
9.8 KiB
Python
167 lines
9.8 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
from BaseClasses import Item, ItemClassification
|
|
|
|
if TYPE_CHECKING:
|
|
from .world import APQuestWorld
|
|
|
|
# Every item must have a unique integer ID associated with it.
|
|
# We will have a lookup from item name to ID here that, in world.py, we will import and bind to the world class.
|
|
# Even if an item doesn't exist on specific options, it must be present in this lookup.
|
|
ITEM_NAME_TO_ID = {
|
|
"Key": 1,
|
|
"Sword": 2,
|
|
"Shield": 3,
|
|
"Hammer": 4,
|
|
"Health Upgrade": 5,
|
|
"Confetti Cannon": 6,
|
|
"Math Trap": 7,
|
|
}
|
|
|
|
# Items should have a defined default classification.
|
|
# In our case, we will make a dictionary from item name to classification.
|
|
DEFAULT_ITEM_CLASSIFICATIONS = {
|
|
"Key": ItemClassification.progression,
|
|
"Sword": ItemClassification.progression | ItemClassification.useful, # Items can have multiple classifications.
|
|
"Shield": ItemClassification.progression,
|
|
"Hammer": ItemClassification.progression,
|
|
"Health Upgrade": ItemClassification.useful,
|
|
"Confetti Cannon": ItemClassification.filler,
|
|
"Math Trap": ItemClassification.trap,
|
|
}
|
|
|
|
|
|
# Each Item instance must correctly report the "game" it belongs to.
|
|
# To make this simple, it is common practice to subclass the basic Item class and override the "game" field.
|
|
class APQuestItem(Item):
|
|
game = "APQuest"
|
|
|
|
|
|
# Ontop of our regular itempool, our world must be able to create arbitrary amounts of filler as requested by core.
|
|
# To do this, it must define a function called world.get_filler_item_name(), which we will define in world.py later.
|
|
# For now, let's make a function that returns the name of a random filler item here in items.py.
|
|
def get_random_filler_item_name(world: APQuestWorld) -> str:
|
|
# APQuest has an option called "trap_chance".
|
|
# This is the percentage chance that each filler item is a Math Trap instead of a Confetti Cannon.
|
|
# For this purpose, we need to use a random generator.
|
|
|
|
# IMPORTANT: Whenever you need to use a random generator, you must use world.random.
|
|
# This ensures that generating with the same generator seed twice yields the same output.
|
|
# DO NOT use a bare random object from Python's built-in random module.
|
|
if world.random.randint(0, 99) < world.options.trap_chance:
|
|
return "Math Trap"
|
|
return "Confetti Cannon"
|
|
|
|
|
|
def create_item_with_correct_classification(world: APQuestWorld, name: str) -> APQuestItem:
|
|
# Our world class must have a create_item() function that can create any of our items by name at any time.
|
|
# So, we make this helper function that creates the item by name with the correct classification.
|
|
# Note: This function's content could just be the contents of world.create_item in world.py directly,
|
|
# but it seemed nicer to have it in its own function over here in items.py.
|
|
classification = DEFAULT_ITEM_CLASSIFICATIONS[name]
|
|
|
|
# It is perfectly normal and valid for an item's classification to differ based on the player's options.
|
|
# In our case, Health Upgrades are only relevant to logic (and thus labeled as "progression") in hard mode.
|
|
if name == "Health Upgrade" and world.options.hard_mode:
|
|
classification = ItemClassification.progression
|
|
|
|
return APQuestItem(name, classification, ITEM_NAME_TO_ID[name], world.player)
|
|
|
|
|
|
# With those two helper functions defined, let's now get to actually creating and submitting our itempool.
|
|
def create_all_items(world: APQuestWorld) -> None:
|
|
# This is the function in which we will create all the items that this world submits to the multiworld item pool.
|
|
# There must be exactly as many items as there are locations.
|
|
# In our case, there are either six or seven locations.
|
|
# We must make sure that when there are six locations, there are six items,
|
|
# and when there are seven locations, there are seven items.
|
|
|
|
# Creating items should generally be done via the world's create_item method.
|
|
# First, we create a list containing all the items that always exist.
|
|
|
|
itempool: list[Item] = [
|
|
world.create_item("Key"),
|
|
world.create_item("Sword"),
|
|
world.create_item("Shield"),
|
|
world.create_item("Health Upgrade"),
|
|
world.create_item("Health Upgrade"),
|
|
]
|
|
|
|
# Some items may only exist if the player enables certain options.
|
|
# In our case, If the hammer option is enabled, the sixth item is the Hammer.
|
|
# Otherwise, we add a filler Confetti Cannon.
|
|
if world.options.hammer:
|
|
# Once again, it is important to stress that even though the Hammer doesn't always exist,
|
|
# it must be present in the worlds item_name_to_id.
|
|
# Whether it is actually in the itempool is determined purely by whether we create and add the item here.
|
|
itempool.append(world.create_item("Hammer"))
|
|
|
|
# Archipelago requires that each world submits as many locations as it submits items.
|
|
# This is where we can use our filler and trap items.
|
|
# APQuest has two of these: The Confetti Cannon and the Math Trap.
|
|
# (Unfortunately, Archipelago is a bit ambiguous about its terminology here:
|
|
# "filler" is an ItemClassification separate from "trap", but in a lot of its functions,
|
|
# Archipelago will use "filler" to just mean "an additional item created to fill out the itempool".
|
|
# "Filler" in this sense can technically have any ItemClassification,
|
|
# but most commonly ItemClassification.filler or ItemClassification.trap.
|
|
# Starting here, the word "filler" will be used to collectively refer to APQuest's Confetti Cannon and Math Trap,
|
|
# which are ItemClassification.filler and ItemClassification.trap respectively.)
|
|
# Creating filler items works the same as any other item. But there is a question:
|
|
# How many filler items do we actually need to create?
|
|
# In regions.py, we created either six or seven locations depending on the "extra_starting_chest" option.
|
|
# In this function, we have created five or six items depending on whether the "hammer" option is enabled.
|
|
# We *could* have a really complicated if-else tree checking the options again, but there is a better way.
|
|
# We can compare the size of our itempool so far to the number of locations in our world.
|
|
|
|
# The length of our itempool is easy to determine, since we have it as a list.
|
|
number_of_items = len(itempool)
|
|
|
|
# The number of locations is also easy to determine, but we have to be careful.
|
|
# Just calling len(world.get_locations()) would report an incorrect number, because of our *event locations*.
|
|
# What we actually want is the number of *unfilled* locations. Luckily, there is a helper method for this:
|
|
number_of_unfilled_locations = len(world.multiworld.get_unfilled_locations(world.player))
|
|
|
|
# Now, we just subtract the number of items from the number of locations to get the number of empty item slots.
|
|
needed_number_of_filler_items = number_of_unfilled_locations - number_of_items
|
|
|
|
# Finally, we create that many filler items and add them to the itempool.
|
|
# To create our filler, we could just use world.create_item("Confetti Cannon").
|
|
# But there is an alternative that works even better for most worlds, including APQuest.
|
|
# As discussed above, our world must have a get_filler_item_name() function defined,
|
|
# which must return the name of an infinitely repeatable filler item.
|
|
# Defining this function enables the use of a helper function called world.create_filler().
|
|
# You can just use this function directly to create as many filler items as you need to complete your itempool.
|
|
itempool += [world.create_filler() for _ in range(needed_number_of_filler_items)]
|
|
|
|
# But... is that the right option for your game? Let's explore that.
|
|
# For some games, the concepts of "regular itempool filler" and "additionally created filler" are different.
|
|
# These games might want / require specific amounts of specific filler items in their regular pool.
|
|
# To achieve this, they will have to intentionally create the correct quantities using world.create_item().
|
|
# They may still use world.create_filler() to fill up the rest of their itempool with "repeatable filler",
|
|
# after creating their "specific quantity" filler and still having room left over.
|
|
|
|
# But there are many other games which *only* have infinitely repeatable filler items.
|
|
# They don't care about specific amounts of specific filler items, instead only caring about the proportions.
|
|
# In this case, world.create_filler() can just be used for the entire filler itempool.
|
|
# APQuest is one of these games:
|
|
# Regardless of whether it's filler for the regular itempool or additional filler for item links / etc.,
|
|
# we always just want a Confetti Cannon or a Math Trap depending on the "trap_chance" option.
|
|
# We defined this behavior in our get_random_filler_item_name() function, which in world.py,
|
|
# we'll bind to world.get_filler_item_name(). So, we can just use world.create_filler() for all of our filler.
|
|
|
|
# Anyway. With our world's itempool finalized, we now need to submit it to the multiworld itempool.
|
|
# This is how the generator actually knows about the existence of our items.
|
|
world.multiworld.itempool += itempool
|
|
|
|
# Sometimes, you might want the player to start with certain items already in their inventory.
|
|
# These items are called "precollected items".
|
|
# They will be sent as soon as they connect for the first time (depending on your client's item handling flag).
|
|
# Players can add precollected items themselves via the generic "start_inventory" option.
|
|
# If you want to add your own precollected items, you can do so via world.push_precollected().
|
|
if world.options.start_with_one_confetti_cannon:
|
|
# We're adding a filler item, but you can also add progression items to the player's precollected inventory.
|
|
starting_confetti_cannon = world.create_item("Confetti Cannon")
|
|
world.push_precollected(starting_confetti_cannon)
|