mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-23 07: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>
257 lines
8.5 KiB
Python
257 lines
8.5 KiB
Python
from collections.abc import Callable
|
|
from dataclasses import dataclass
|
|
from math import sqrt
|
|
from random import choice, random
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from kivy.core.window import Keyboard, Window
|
|
from kivy.graphics import Color, Triangle
|
|
from kivy.graphics.instructions import Canvas
|
|
from kivy.input import MotionEvent
|
|
from kivy.uix.boxlayout import BoxLayout
|
|
from kivy.uix.gridlayout import GridLayout
|
|
from kivymd.uix.recycleview import MDRecycleView
|
|
|
|
from CommonClient import logger
|
|
|
|
from ..game.inputs import Input
|
|
|
|
|
|
INPUT_MAP = {
|
|
"up": Input.UP,
|
|
"w": Input.UP,
|
|
"down": Input.DOWN,
|
|
"s": Input.DOWN,
|
|
"right": Input.RIGHT,
|
|
"d": Input.RIGHT,
|
|
"left": Input.LEFT,
|
|
"a": Input.LEFT,
|
|
"spacebar": Input.ACTION,
|
|
"c": Input.CONFETTI,
|
|
"0": Input.ZERO,
|
|
"1": Input.ONE,
|
|
"2": Input.TWO,
|
|
"3": Input.THREE,
|
|
"4": Input.FOUR,
|
|
"5": Input.FIVE,
|
|
"6": Input.SIX,
|
|
"7": Input.SEVEN,
|
|
"8": Input.EIGHT,
|
|
"9": Input.NINE,
|
|
"backspace": Input.BACKSPACE,
|
|
}
|
|
|
|
|
|
class APQuestGameView(MDRecycleView):
|
|
_keyboard: Keyboard | None = None
|
|
input_function: Callable[[Input], None]
|
|
|
|
def __init__(self, input_function: Callable[[Input], None], **kwargs: Any) -> None:
|
|
super().__init__(**kwargs)
|
|
self.input_function = input_function
|
|
self.bind_keyboard()
|
|
|
|
def on_touch_down(self, touch: MotionEvent) -> None:
|
|
self.bind_keyboard()
|
|
|
|
def bind_keyboard(self) -> None:
|
|
if self._keyboard is not None:
|
|
return
|
|
self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
|
|
self._keyboard.bind(on_key_down=self._on_keyboard_down)
|
|
|
|
def _keyboard_closed(self) -> None:
|
|
if self._keyboard is None:
|
|
return
|
|
self._keyboard.unbind(on_key_down=self._on_keyboard_down)
|
|
self._keyboard = None
|
|
|
|
def _on_keyboard_down(self, _: Any, keycode: tuple[int, str], _1: Any, _2: Any) -> bool:
|
|
if keycode[1] in INPUT_MAP:
|
|
self.input_function(INPUT_MAP[keycode[1]])
|
|
return True
|
|
|
|
|
|
class APQuestGrid(GridLayout):
|
|
def check_resize(self, _: int, _1: int) -> None:
|
|
parent_width, parent_height = self.parent.size
|
|
|
|
self_width_according_to_parent_height = parent_height * 12 / 11
|
|
self_height_according_to_parent_width = parent_height * 11 / 12
|
|
|
|
if self_width_according_to_parent_height > parent_width:
|
|
self.size = parent_width, self_height_according_to_parent_width
|
|
else:
|
|
self.size = self_width_according_to_parent_height, parent_height
|
|
|
|
|
|
CONFETTI_COLORS = [
|
|
(220 / 255, 0, 212 / 255), # PINK
|
|
(0, 0, 252 / 255), # BLUE
|
|
(252 / 255, 220 / 255, 0), # YELLOW
|
|
(0, 184 / 255, 0), # GREEN
|
|
(252 / 255, 56 / 255, 0), # ORANGE
|
|
]
|
|
|
|
|
|
@dataclass
|
|
class Confetti:
|
|
x_pos: float
|
|
y_pos: float
|
|
x_speed: float
|
|
y_speed: float
|
|
color: tuple[float, float, float]
|
|
life: float = 3
|
|
|
|
triangle1: Triangle | None = None
|
|
triangle2: Triangle | None = None
|
|
color_instruction: Color | None = None
|
|
|
|
def update_speed(self, dt: float) -> None:
|
|
if self.x_speed > 0:
|
|
self.x_speed -= 2.7 * dt
|
|
if self.x_speed < 0:
|
|
self.x_speed = 0
|
|
else:
|
|
self.x_speed += 2.7 * dt
|
|
if self.x_speed > 0:
|
|
self.x_speed = 0
|
|
|
|
if self.y_speed > -0.03:
|
|
self.y_speed -= 2.7 * dt
|
|
if self.y_speed < -0.03:
|
|
self.y_speed = -0.03
|
|
else:
|
|
self.y_speed += 2.7 * dt
|
|
if self.y_speed > -0.03:
|
|
self.y_speed = -0.03
|
|
|
|
def move(self, dt: float) -> None:
|
|
self.update_speed(dt)
|
|
|
|
if self.y_pos > 1:
|
|
self.y_pos = 1
|
|
self.y_speed = 0
|
|
if self.x_pos < 0.01:
|
|
self.x_pos = 0.01
|
|
self.x_speed = 0
|
|
if self.x_pos > 0.99:
|
|
self.x_pos = 0.99
|
|
self.x_speed = 0
|
|
|
|
self.x_pos += self.x_speed * dt
|
|
self.y_pos += self.y_speed * dt
|
|
|
|
def render(self, offset_x: float, offset_y: float, max_x: int, max_y: int) -> None:
|
|
if self.x_speed == 0 and self.y_speed == 0:
|
|
x_normalized, y_normalized = 0.0, 1.0
|
|
else:
|
|
speed_magnitude = sqrt(self.x_speed**2 + self.y_speed**2)
|
|
x_normalized, y_normalized = self.x_speed / speed_magnitude, self.y_speed / speed_magnitude
|
|
|
|
half_top_to_bottom = 0.006
|
|
half_left_to_right = 0.018
|
|
|
|
upwards_delta_x = x_normalized * half_top_to_bottom
|
|
upwards_delta_y = y_normalized * half_top_to_bottom
|
|
sideways_delta_x = y_normalized * half_left_to_right
|
|
sideways_delta_y = x_normalized * half_left_to_right
|
|
|
|
top_left_x, top_left_y = upwards_delta_x - sideways_delta_x, upwards_delta_y + sideways_delta_y
|
|
bottom_left_x, bottom_left_y = -upwards_delta_x - sideways_delta_x, -upwards_delta_y + sideways_delta_y
|
|
top_right_x, top_right_y = -bottom_left_x, -bottom_left_y
|
|
bottom_right_x, bottom_right_y = -top_left_x, -top_left_y
|
|
|
|
top_left_x, top_left_y = top_left_x + self.x_pos, top_left_y + self.y_pos
|
|
bottom_left_x, bottom_left_y = bottom_left_x + self.x_pos, bottom_left_y + self.y_pos
|
|
top_right_x, top_right_y = top_right_x + self.x_pos, top_right_y + self.y_pos
|
|
bottom_right_x, bottom_right_y = bottom_right_x + self.x_pos, bottom_right_y + self.y_pos
|
|
|
|
top_left_x, top_left_y = top_left_x * max_x + offset_x, top_left_y * max_y + offset_y
|
|
bottom_left_x, bottom_left_y = bottom_left_x * max_x + offset_x, bottom_left_y * max_y + offset_y
|
|
top_right_x, top_right_y = top_right_x * max_x + offset_x, top_right_y * max_y + offset_y
|
|
bottom_right_x, bottom_right_y = bottom_right_x * max_x + offset_x, bottom_right_y * max_y + offset_y
|
|
|
|
points1 = (top_left_x, top_left_y, top_right_x, top_right_y, bottom_left_x, bottom_left_y)
|
|
points2 = (bottom_right_x, bottom_right_y, top_right_x, top_right_y, bottom_left_x, bottom_left_y)
|
|
|
|
if self.color_instruction is None:
|
|
self.color_instruction = Color(*self.color)
|
|
|
|
if self.triangle1 is None:
|
|
self.triangle1 = Triangle(points=points1)
|
|
else:
|
|
self.triangle1.points = points1
|
|
|
|
if self.triangle2 is None:
|
|
self.triangle2 = Triangle(points=points2)
|
|
else:
|
|
self.triangle2.points = points2
|
|
|
|
def reduce_life(self, dt: float, canvas: Canvas) -> bool:
|
|
self.life -= dt
|
|
|
|
if self.life <= 0:
|
|
if self.color_instruction is not None:
|
|
canvas.remove(self.color_instruction)
|
|
if self.triangle1 is not None:
|
|
canvas.remove(self.triangle1)
|
|
if self.triangle2 is not None:
|
|
canvas.remove(self.triangle2)
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
class ConfettiView(MDRecycleView):
|
|
confetti: list[Confetti]
|
|
|
|
def __init__(self, **kwargs: Any) -> None:
|
|
super().__init__(**kwargs)
|
|
self.confetti = []
|
|
|
|
def check_resize(self, _: int, _1: int) -> None:
|
|
parent_width, parent_height = self.parent.size
|
|
|
|
self_width_according_to_parent_height = parent_height * 12 / 11
|
|
self_height_according_to_parent_width = parent_height * 11 / 12
|
|
|
|
if self_width_according_to_parent_height > parent_width:
|
|
self.size = parent_width, self_height_according_to_parent_width
|
|
else:
|
|
self.size = self_width_according_to_parent_height, parent_height
|
|
|
|
def redraw_confetti(self, dt: float) -> None:
|
|
try:
|
|
with self.canvas:
|
|
for confetti in self.confetti:
|
|
confetti.move(dt)
|
|
|
|
self.confetti = [confetti for confetti in self.confetti if confetti.reduce_life(dt, self.canvas)]
|
|
|
|
for confetti in self.confetti:
|
|
confetti.render(self.pos[0], self.pos[1], self.size[0], self.size[1])
|
|
except Exception as e:
|
|
logger.exception(e)
|
|
|
|
def add_confetti(self, initial_position: tuple[float, float], amount: int) -> None:
|
|
for i in range(amount):
|
|
self.confetti.append(
|
|
Confetti(
|
|
initial_position[0],
|
|
initial_position[1],
|
|
random() * 3.2 - 1.6 - (initial_position[0] - 0.5) * 1.2,
|
|
random() * 3.2 - 1.3 - (initial_position[1] - 0.5) * 1.2,
|
|
choice(CONFETTI_COLORS),
|
|
3 + i * 0.05,
|
|
)
|
|
)
|
|
|
|
|
|
class VolumeSliderView(BoxLayout):
|
|
pass
|
|
|
|
|
|
class APQuestControlsView(BoxLayout):
|
|
pass
|