mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-22 07:35:37 -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>
182 lines
7.2 KiB
Python
182 lines
7.2 KiB
Python
import pkgutil
|
|
from collections.abc import Buffer
|
|
from enum import Enum
|
|
from io import BytesIO
|
|
from typing import Literal, NamedTuple, cast
|
|
|
|
from bokeh.protocol import Protocol
|
|
from kivy.uix.image import CoreImage
|
|
|
|
from CommonClient import logger
|
|
|
|
from .. import game
|
|
from ..game.graphics import Graphic
|
|
|
|
|
|
# The import "from kivy.graphics.texture import Texture" does not work correctly.
|
|
# We never need the class directly, so we need to use a protocol.
|
|
class Texture(Protocol):
|
|
mag_filter: Literal["nearest"]
|
|
|
|
def get_region(self, x: int, y: int, w: int, h: int) -> "Texture": ...
|
|
|
|
|
|
class RelatedTexture(NamedTuple):
|
|
base_texture_file: str
|
|
x: int
|
|
y: int
|
|
width: int
|
|
height: int
|
|
|
|
|
|
IMAGE_GRAPHICS: dict[Graphic, str | RelatedTexture] = {
|
|
Graphic.WALL: RelatedTexture("inanimates.png", 16, 32, 16, 16),
|
|
Graphic.BREAKABLE_BLOCK: RelatedTexture("inanimates.png", 32, 32, 16, 16),
|
|
Graphic.CHEST: RelatedTexture("inanimates.png", 0, 16, 16, 16),
|
|
Graphic.BUSH: RelatedTexture("inanimates.png", 16, 16, 16, 16),
|
|
Graphic.KEY_DOOR: RelatedTexture("inanimates.png", 32, 16, 16, 16),
|
|
Graphic.BUTTON_NOT_ACTIVATED: RelatedTexture("inanimates.png", 0, 0, 16, 16),
|
|
Graphic.BUTTON_ACTIVATED: RelatedTexture("inanimates.png", 16, 0, 16, 16),
|
|
Graphic.BUTTON_DOOR: RelatedTexture("inanimates.png", 32, 0, 16, 16),
|
|
|
|
Graphic.NORMAL_ENEMY_1_HEALTH: RelatedTexture("normal_enemy.png", 0, 0, 16, 16),
|
|
Graphic.NORMAL_ENEMY_2_HEALTH: RelatedTexture("normal_enemy.png", 16, 0, 16, 16),
|
|
|
|
Graphic.BOSS_5_HEALTH: RelatedTexture("boss.png", 16, 16, 16, 16),
|
|
Graphic.BOSS_4_HEALTH: RelatedTexture("boss.png", 0, 16, 16, 16),
|
|
Graphic.BOSS_3_HEALTH: RelatedTexture("boss.png", 32, 32, 16, 16),
|
|
Graphic.BOSS_2_HEALTH: RelatedTexture("boss.png", 16, 32, 16, 16),
|
|
Graphic.BOSS_1_HEALTH: RelatedTexture("boss.png", 0, 32, 16, 16),
|
|
|
|
Graphic.EMPTY_HEART: RelatedTexture("hearts.png", 0, 0, 16, 16),
|
|
Graphic.HEART: RelatedTexture("hearts.png", 16, 0, 16, 16),
|
|
Graphic.HALF_HEART: RelatedTexture("hearts.png", 32, 0, 16, 16),
|
|
|
|
Graphic.REMOTE_ITEM: RelatedTexture("items.png", 0, 16, 16, 16),
|
|
Graphic.CONFETTI_CANNON: RelatedTexture("items.png", 16, 16, 16, 16),
|
|
Graphic.HAMMER: RelatedTexture("items.png", 32, 16, 16, 16),
|
|
Graphic.KEY: RelatedTexture("items.png", 0, 0, 16, 16),
|
|
Graphic.SHIELD: RelatedTexture("items.png", 16, 0, 16, 16),
|
|
Graphic.SWORD: RelatedTexture("items.png", 32, 0, 16, 16),
|
|
|
|
Graphic.ITEMS_TEXT: "items_text.png",
|
|
|
|
Graphic.ZERO: RelatedTexture("numbers.png", 0, 16, 16, 16),
|
|
Graphic.ONE: RelatedTexture("numbers.png", 16, 16, 16, 16),
|
|
Graphic.TWO: RelatedTexture("numbers.png", 32, 16, 16, 16),
|
|
Graphic.THREE: RelatedTexture("numbers.png", 48, 16, 16, 16),
|
|
Graphic.FOUR: RelatedTexture("numbers.png", 64, 16, 16, 16),
|
|
Graphic.FIVE: RelatedTexture("numbers.png", 0, 0, 16, 16),
|
|
Graphic.SIX: RelatedTexture("numbers.png", 16, 0, 16, 16),
|
|
Graphic.SEVEN: RelatedTexture("numbers.png", 32, 0, 16, 16),
|
|
Graphic.EIGHT: RelatedTexture("numbers.png", 48, 0, 16, 16),
|
|
Graphic.NINE: RelatedTexture("numbers.png", 64, 0, 16, 16),
|
|
|
|
Graphic.LETTER_A: RelatedTexture("letters.png", 0, 16, 16, 16),
|
|
Graphic.LETTER_E: RelatedTexture("letters.png", 16, 16, 16, 16),
|
|
Graphic.LETTER_H: RelatedTexture("letters.png", 32, 16, 16, 16),
|
|
Graphic.LETTER_I: RelatedTexture("letters.png", 0, 0, 16, 16),
|
|
Graphic.LETTER_M: RelatedTexture("letters.png", 16, 0, 16, 16),
|
|
Graphic.LETTER_T: RelatedTexture("letters.png", 32, 0, 16, 16),
|
|
|
|
Graphic.DIVIDE: RelatedTexture("symbols.png", 0, 16, 16, 16),
|
|
Graphic.EQUALS: RelatedTexture("symbols.png", 16, 16, 16, 16),
|
|
Graphic.MINUS: RelatedTexture("symbols.png", 32, 16, 16, 16),
|
|
Graphic.PLUS: RelatedTexture("symbols.png", 0, 0, 16, 16),
|
|
Graphic.TIMES: RelatedTexture("symbols.png", 16, 0, 16, 16),
|
|
Graphic.NO: RelatedTexture("symbols.png", 32, 0, 16, 16),
|
|
|
|
Graphic.UNKNOWN: RelatedTexture("symbols.png", 32, 0, 16, 16), # Same as "No"
|
|
}
|
|
|
|
BACKGROUND_TILE = RelatedTexture("inanimates.png", 0, 32, 16, 16)
|
|
|
|
|
|
class PlayerSprite(Enum):
|
|
HUMAN = 0
|
|
DUCK = 1
|
|
HORSE = 2
|
|
CAT = 3
|
|
UNKNOWN = -1
|
|
|
|
|
|
PLAYER_GRAPHICS = {
|
|
Graphic.PLAYER_DOWN: {
|
|
PlayerSprite.HUMAN: RelatedTexture("human.png", 0, 16, 16, 16),
|
|
PlayerSprite.DUCK: RelatedTexture("duck.png", 0, 16, 16, 16),
|
|
PlayerSprite.HORSE: RelatedTexture("horse.png", 0, 16, 16, 16),
|
|
PlayerSprite.CAT: RelatedTexture("cat.png", 0, 16, 16, 16),
|
|
},
|
|
Graphic.PLAYER_UP: {
|
|
PlayerSprite.HUMAN: RelatedTexture("human.png", 16, 0, 16, 16),
|
|
PlayerSprite.DUCK: RelatedTexture("duck.png", 16, 0, 16, 16),
|
|
PlayerSprite.HORSE: RelatedTexture("horse.png", 16, 0, 16, 16),
|
|
PlayerSprite.CAT: RelatedTexture("cat.png", 16, 0, 16, 16),
|
|
},
|
|
Graphic.PLAYER_LEFT: {
|
|
PlayerSprite.HUMAN: RelatedTexture("human.png", 16, 16, 16, 16),
|
|
PlayerSprite.DUCK: RelatedTexture("duck.png", 16, 16, 16, 16),
|
|
PlayerSprite.HORSE: RelatedTexture("horse.png", 16, 16, 16, 16),
|
|
PlayerSprite.CAT: RelatedTexture("cat.png", 16, 16, 16, 16),
|
|
},
|
|
Graphic.PLAYER_RIGHT: {
|
|
PlayerSprite.HUMAN: RelatedTexture("human.png", 0, 0, 16, 16),
|
|
PlayerSprite.DUCK: RelatedTexture("duck.png", 0, 0, 16, 16),
|
|
PlayerSprite.HORSE: RelatedTexture("horse.png", 0, 0, 16, 16),
|
|
PlayerSprite.CAT: RelatedTexture("cat.png", 0, 0, 16, 16),
|
|
},
|
|
}
|
|
|
|
ALL_GRAPHICS = [
|
|
BACKGROUND_TILE,
|
|
*IMAGE_GRAPHICS.values(),
|
|
*[graphic for sub_dict in PLAYER_GRAPHICS.values() for graphic in sub_dict.values()],
|
|
]
|
|
|
|
_textures: dict[str | RelatedTexture, Texture] = {}
|
|
|
|
|
|
def get_texture_by_identifier(texture_identifier: str | RelatedTexture) -> Texture:
|
|
if texture_identifier in _textures:
|
|
return _textures[texture_identifier]
|
|
|
|
if isinstance(texture_identifier, str):
|
|
image_data = pkgutil.get_data(game.__name__, f"graphics/{texture_identifier}")
|
|
if image_data is None:
|
|
raise RuntimeError(f'Could not find file "graphics/{texture_identifier}" for texture {texture_identifier}')
|
|
|
|
image_bytes = BytesIO(cast(Buffer, image_data))
|
|
texture = cast(Texture, CoreImage(image_bytes, ext="png").texture)
|
|
texture.mag_filter = "nearest"
|
|
_textures[texture_identifier] = texture
|
|
return texture
|
|
|
|
base_texture_filename, x, y, w, h = texture_identifier
|
|
|
|
base_texture = get_texture_by_identifier(base_texture_filename)
|
|
|
|
sub_texture = base_texture.get_region(x, y, w, h)
|
|
sub_texture.mag_filter = "nearest"
|
|
_textures[texture_identifier] = sub_texture
|
|
return sub_texture
|
|
|
|
|
|
def get_texture(graphic: Graphic | Literal["Grass"], player_sprite: PlayerSprite | None = None) -> Texture | None:
|
|
if graphic == Graphic.EMPTY:
|
|
return None
|
|
|
|
if graphic == "Grass":
|
|
return get_texture_by_identifier(BACKGROUND_TILE)
|
|
|
|
if graphic in IMAGE_GRAPHICS:
|
|
return get_texture_by_identifier(IMAGE_GRAPHICS[graphic])
|
|
|
|
if graphic in PLAYER_GRAPHICS:
|
|
if player_sprite is None:
|
|
raise ValueError("Tried to load a player graphic without specifying a player_sprite")
|
|
|
|
return get_texture_by_identifier(PLAYER_GRAPHICS[graphic][player_sprite])
|
|
|
|
logger.exception(f"Tried to load unknown graphic {graphic}.")
|
|
return get_texture(Graphic.UNKNOWN)
|