mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-25 11:53:23 -07:00
* 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>
118 lines
6.9 KiB
Python
118 lines
6.9 KiB
Python
from .bases import APQuestTestBase
|
|
|
|
|
|
class TestHardMode(APQuestTestBase):
|
|
options = {
|
|
"hard_mode": True,
|
|
}
|
|
|
|
def test_hard_mode_access(self) -> None:
|
|
# For the sake of brevity, we won't repeat anything we tested in easy mode.
|
|
# Instead, let's use this opportunity to talk a bit more about assertAccessDependency.
|
|
|
|
# Let's take the Enemy Drop location.
|
|
# In hard mode, the Enemy has two health. One swipe of the Sword does not kill it.
|
|
# This means that the Enemy has a chance to attack you back.
|
|
# If you only have the Sword, this attack kills you. After respawning, the Enemy has full health again.
|
|
# However, if you have a Shield, you can block the attack (resulting in half damage).
|
|
# Alternatively, if you have found a Health Upgrade, you can tank an extra hit.
|
|
|
|
# Why is this important?
|
|
# If we called assertAccessDependency with ["Right Room Enemy Drop"] and [["Shield"]], it would actually *fail*.
|
|
# This is because "Right Room Enemy Drop" is beatable without "Shield" - You can use "Health Upgrade" instead.
|
|
# However, we can call assertAccessDependency with *both* items like this:
|
|
|
|
with self.subTest("Test that you need either Shield or Health Upgrade to beat the Right Room Enemy"):
|
|
self.assertAccessDependency(
|
|
["Right Room Enemy Drop"],
|
|
[["Shield"], ["Health Upgrade"]],
|
|
only_check_listed=True,
|
|
)
|
|
|
|
# This tests that:
|
|
# 1. No Shield & No Health Upgrades -> Right Room Enemy Drop is not reachable.
|
|
# 2. Shield & No Health Upgrades -> Right Room Enemy Drop is reachable.
|
|
# 3. No Shield & All Health Upgrades -> Right Room Enemy Drop is reachable.
|
|
|
|
# Note: Every other item that isn't the Shield nor a Health Upgrade is collected into state.
|
|
# This even includes pre-placed items, which notably includes any event location/item pairs you created.
|
|
# In our case, it means we don't have to mention the Sword. By omitting it, it's assumed that we have it.
|
|
|
|
# This explains why the possible_items parameter is a list, but not why it's a list of lists.
|
|
# Let's look at the Final Boss Location. This location requires Sword, Shield, and both Health Upgrades.
|
|
# We could implement it like this:
|
|
with self.subTest("Test that the final boss isn't beatable without Sword, Shield, and both Health Upgrades"):
|
|
self.assertAccessDependency(
|
|
["Final Boss Defeated"],
|
|
[["Sword", "Shield", "Health Upgrade"]],
|
|
only_check_listed=True,
|
|
)
|
|
|
|
# This would now test the following:
|
|
# 1. Without Sword, nor Shield, nor any Health Upgrades, the final boss is not beatable.
|
|
# 2. With Sword, Shield, and all Health Upgrades, the final boss is beatable.
|
|
|
|
# But, it's not really advisable to do this.
|
|
# Think about it: If we implemented our logic incorrectly and forgot to add the Shield requirement,
|
|
# this call would still pass. We'd rather make sure that each item individually is required:
|
|
for item in ["Sword", "Shield", "Health Upgrade"]:
|
|
with self.subTest(f"Test that the final boss requires {item}"):
|
|
self.assertAccessDependency(
|
|
["Final Boss Defeated"],
|
|
[[item]],
|
|
only_check_listed=True,
|
|
)
|
|
|
|
# This now tests that:
|
|
# 1. Without Sword, you can't beat the Final Boss
|
|
# 2. With Sword, you can beat the Final Boss (if you have everything else)
|
|
# 3. Without Shield, you can't beat the Final Boss
|
|
# 4. With Shield, you can beat the Final Boss (if you have everything else)
|
|
# 5. Without Health Upgrades, you can't beat the Final Boss
|
|
# 6. With all Health Upgrades, you can beat the Final Boss (if you have everything else)
|
|
|
|
# 2., 4., and 6. are the exact same check, so it is a bit redundant.
|
|
# But crucially, we are ensuring that all three items are actually required.
|
|
|
|
# So that's not really why the inner elements are lists.
|
|
# So we ask again: Why are they lists? When is it ever useful?
|
|
# Fair warning: This is probably where you should stop reading this and skip to test_hard_mode_health_upgrades.
|
|
# But if you really want to know why:
|
|
|
|
# Having multiple elements in the inner lists is something that only comes up in more complex scenarios.
|
|
# APQuest doesn't have any of these scenarios, but let's imagine one for completeness' sake.
|
|
# Currently, the Enemy can be beaten with these item combinations:
|
|
# 1. Sword and Shield
|
|
# 2. Sword and Health Upgrade
|
|
# Let's say there was also a "Shield Bash". When using the Shield Bash, you cannot use the Shield to defend.
|
|
# This would mean there is a third valid combination:
|
|
# 3. Shield + Health Upgrade
|
|
# We have set up a scenario where none of the three items are enough on their own,
|
|
# but any combination of two items works.
|
|
# The best way to test this would be to call assertAccessDependency with:
|
|
# [["Sword", "Shield"], ["Sword", "Health Upgrade"], ["Shield", "Health Upgrade"]]
|
|
# If we omitted any item from any of the three sub-lists, the check would fail.
|
|
# This is because the item is still *mentioned* in one of the other two conditions,
|
|
# meaning it is not collected into state.
|
|
# Thus, this term cannot be simplified any further without testing something different to what we want to test.
|
|
|
|
# You can kinda think of assertAccessDependency as an OR(AND(item_list_1), AND(item_list_2), ...).
|
|
# Except this "AND" is a special "AND" which allows reducing each list to a single representative item.
|
|
# And also, the "OR" is special as well in that has to be exhaustive,
|
|
# where the set of completely unmentioned items must *not* be able to reach the location collectively.
|
|
# And *also*, each "AND" must be enough to access the location *out of the mentioned items*.
|
|
# ... I'm not sure this explanation helps anyone, but most of the time, you really don't have to think about it.
|
|
|
|
def test_hard_mode_health_upgrades(self) -> None:
|
|
# We'll also repeat our Health Upgrade test from the Easy Mode test case, but modified for Hard Mode.
|
|
# This will not be explained again here.
|
|
|
|
health_upgrades = self.get_items_by_name("Health Upgrade")
|
|
|
|
with self.subTest("Test that there are two Health Upgrades in the pool"):
|
|
self.assertEqual(len(health_upgrades), 2)
|
|
|
|
with self.subTest("Test that the Health Upgrades in the pool are progression, but not useful."):
|
|
self.assertFalse(any(health_upgrade.useful for health_upgrade in health_upgrades))
|
|
self.assertTrue(all(health_upgrade.advancement for health_upgrade in health_upgrades))
|