From 244458cc5c2590028c11769165897310e4171911 Mon Sep 17 00:00:00 2001 From: tabreturn Date: Sun, 15 Dec 2024 18:40:15 +1100 Subject: [PATCH 01/13] - add port files --- ports/megaball/Megaball.sh | 69 +++ .../megaball/gamedata/assets/img0.png | Bin 0 -> 3451 bytes .../gamedata/assets/my_resource.pyxres | Bin 0 -> 7184 bytes ports/megaball/megaball/gamedata/audio.py | 57 +++ ports/megaball/megaball/gamedata/circle.py | 29 ++ ports/megaball/megaball/gamedata/constants.py | 73 +++ ports/megaball/megaball/gamedata/game.py | 103 ++++ ports/megaball/megaball/gamedata/globals.py | 66 +++ ports/megaball/megaball/gamedata/hud.py | 31 ++ ports/megaball/megaball/gamedata/icon.ico | Bin 0 -> 1406 bytes ports/megaball/megaball/gamedata/input.py | 85 ++++ ports/megaball/megaball/gamedata/light.py | 41 ++ ports/megaball/megaball/gamedata/main.py | 36 ++ ports/megaball/megaball/gamedata/mainmenu.py | 118 +++++ ports/megaball/megaball/gamedata/palette.py | 135 +++++ ports/megaball/megaball/gamedata/player.py | 256 ++++++++++ ports/megaball/megaball/gamedata/readme.txt | 47 ++ ports/megaball/megaball/gamedata/rect.py | 32 ++ .../megaball/megaball/gamedata/screenshake.py | 38 ++ ports/megaball/megaball/gamedata/spinner.py | 156 ++++++ ports/megaball/megaball/gamedata/stage.py | 467 ++++++++++++++++++ ports/megaball/megaball/gamedata/stagedata.py | 69 +++ ports/megaball/megaball/gamedata/utils.py | 87 ++++ ports/megaball/megaball/gamedata/weapon.py | 81 +++ .../megaball/licenses/LICENSE.megaball.txt | 21 + 25 files changed, 2097 insertions(+) create mode 100755 ports/megaball/Megaball.sh create mode 100644 ports/megaball/megaball/gamedata/assets/img0.png create mode 100644 ports/megaball/megaball/gamedata/assets/my_resource.pyxres create mode 100644 ports/megaball/megaball/gamedata/audio.py create mode 100644 ports/megaball/megaball/gamedata/circle.py create mode 100644 ports/megaball/megaball/gamedata/constants.py create mode 100644 ports/megaball/megaball/gamedata/game.py create mode 100644 ports/megaball/megaball/gamedata/globals.py create mode 100644 ports/megaball/megaball/gamedata/hud.py create mode 100644 ports/megaball/megaball/gamedata/icon.ico create mode 100644 ports/megaball/megaball/gamedata/input.py create mode 100644 ports/megaball/megaball/gamedata/light.py create mode 100644 ports/megaball/megaball/gamedata/main.py create mode 100644 ports/megaball/megaball/gamedata/mainmenu.py create mode 100644 ports/megaball/megaball/gamedata/palette.py create mode 100644 ports/megaball/megaball/gamedata/player.py create mode 100644 ports/megaball/megaball/gamedata/readme.txt create mode 100644 ports/megaball/megaball/gamedata/rect.py create mode 100644 ports/megaball/megaball/gamedata/screenshake.py create mode 100644 ports/megaball/megaball/gamedata/spinner.py create mode 100644 ports/megaball/megaball/gamedata/stage.py create mode 100644 ports/megaball/megaball/gamedata/stagedata.py create mode 100644 ports/megaball/megaball/gamedata/utils.py create mode 100644 ports/megaball/megaball/gamedata/weapon.py create mode 100644 ports/megaball/megaball/licenses/LICENSE.megaball.txt diff --git a/ports/megaball/Megaball.sh b/ports/megaball/Megaball.sh new file mode 100755 index 0000000000..cd633ac895 --- /dev/null +++ b/ports/megaball/Megaball.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share} + +if [ -d "/opt/system/Tools/PortMaster/" ]; then + controlfolder="/opt/system/Tools/PortMaster" +elif [ -d "/opt/tools/PortMaster/" ]; then + controlfolder="/opt/tools/PortMaster" +elif [ -d "$XDG_DATA_HOME/PortMaster/" ]; then + controlfolder="$XDG_DATA_HOME/PortMaster" +else + controlfolder="/roms/ports/PortMaster" +fi + +source $controlfolder/control.txt + +[ -f "${controlfolder}/mod_${CFW_NAME}.txt" ] && source "${controlfolder}/mod_${CFW_NAME}.txt" + +GAMEDIR="/$directory/ports/megaball" +CONFDIR="$GAMEDIR/conf" +PYXEL_PKG="main.py" + +cd "${GAMEDIR}" + +> "${GAMEDIR}/log.txt" && exec > >(tee "${GAMEDIR}/log.txt") 2>&1 + +mkdir -p "$GAMEDIR/conf" +bind_directories "$HOME/.config/.pyxel/megaball" "$CONFDIR" + +# Load Pyxel runtime +runtime="pyxel_2.2.8_python_3.11" +export pyxel_dir="$HOME/pyxel" +mkdir -p "${pyxel_dir}" + +if [ ! -f "$controlfolder/libs/${runtime}.squashfs" ]; then + # Check for runtime if not downloaded via PM + if [ ! -f "$controlfolder/harbourmaster" ]; then + pm_message "This port requires the latest PortMaster to run, please go to https://portmaster.games/ for more info." + sleep 5 + exit 1 + fi + + $ESUDO $controlfolder/harbourmaster --quiet --no-check runtime_check "${runtime}.squashfs" +fi + +if [[ "$PM_CAN_MOUNT" != "N" ]]; then + $ESUDO umount "${pyxel_dir}" +fi + +$ESUDO mount "$controlfolder/libs/${runtime}.squashfs" "${pyxel_dir}" + +export SDL_GAMECONTROLLERCONFIG="$sdl_controllerconfig" + +$GPTOKEYB "pyxel" & + +pm_platform_helper "${pyxel_dir}/bin/pyxel" + +# Enable Pyxel virtual env +source "${pyxel_dir}/bin/activate" +export PYTHONHOME="${pyxel_dir}" +export PYTHONPYCACHEPREFIX="${GAMEDIR}/${runtime}.cache" + +"${pyxel_dir}/bin/pyxel" run "${GAMEDIR}/gamedata/${PYXEL_PKG}" + +if [[ "$PM_CAN_MOUNT" != "N" ]]; then + $ESUDO umount "${pyxel_dir}" +fi + +pm_finish diff --git a/ports/megaball/megaball/gamedata/assets/img0.png b/ports/megaball/megaball/gamedata/assets/img0.png new file mode 100644 index 0000000000000000000000000000000000000000..e0db8309e9132e6bef822d65fc3db0559d810aa5 GIT binary patch literal 3451 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 LBfU}``>6i`lV%Lj literal 0 HcmV?d00001 diff --git a/ports/megaball/megaball/gamedata/assets/my_resource.pyxres b/ports/megaball/megaball/gamedata/assets/my_resource.pyxres new file mode 100644 index 0000000000000000000000000000000000000000..369b2d2778771b47fa9527d790fa44770efdb1e0 GIT binary patch literal 7184 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 literal 0 HcmV?d00001 diff --git a/ports/megaball/megaball/gamedata/audio.py b/ports/megaball/megaball/gamedata/audio.py new file mode 100644 index 0000000000..1229365b44 --- /dev/null +++ b/ports/megaball/megaball/gamedata/audio.py @@ -0,0 +1,57 @@ + +import pyxel + +import globals + +MUS_IN_GAME = 0 +MUS_TITLE = 1 +MUS_START = 2 +MUS_STAGE_COMPLETE = 3 +MUS_DEATH = 4 +MUS_GAME_OVER = 5 + +MUSIC = [ + MUS_IN_GAME, + MUS_TITLE, + MUS_START, + MUS_STAGE_COMPLETE, + MUS_DEATH, + #MUS_GAME_OVER +] + +SND_MENU_MOVE = 16 +SND_MENU_SELECT = 17 +SND_HIT_WALL = 18 +SND_HIT_TARGET = 19 +SND_USED_WEAPON = 20 + +SOUNDS = [ + SND_MENU_MOVE, + SND_MENU_SELECT, + SND_HIT_WALL, + SND_HIT_TARGET, + SND_USED_WEAPON, +] + +def play_sound(snd, looping=False): + if globals.g_sound_on == False: + return + + if snd not in SOUNDS: + return + + if pyxel.play_pos(3) != -1: + return + + pyxel.play(3, snd, loop=looping) + +def play_music(msc, looping=False): + if globals.g_music_on == False: + return + + if msc not in MUSIC: + return + + pyxel.stop() + + pyxel.playm(msc, loop=looping) diff --git a/ports/megaball/megaball/gamedata/circle.py b/ports/megaball/megaball/gamedata/circle.py new file mode 100644 index 0000000000..4bbb726826 --- /dev/null +++ b/ports/megaball/megaball/gamedata/circle.py @@ -0,0 +1,29 @@ + +def overlap(x1, y1, r1, x2, y2, r2): + dx = x1 - x2 + dy = y1 - y2 + dist = dx * dx + dy * dy + radiusSum = r1 + r2 + return dist < radiusSum * radiusSum + +def contains_other(x1, y1, r1, x2, y2, r2): + radiusDiff = r1 - r2 + if radiusDiff < 0: + return False + + dx = x1 - x2 + dy = y1 - y2 + dist = dx * dx + dy * dy + radiusSum = r1 + r2 + return (not(radiusDiff * radiusDiff < dist) and (dist < radiusSum * radiusSum)) + +def contains_point(x, y, radius, px, py): + dx = x - px + dy = y - py + return dx * dx + dy * dy <= radius * radius + +class Circle: + def __init__(self, x, y, radius): + self.x = x + self.y = y + self.radius = radius diff --git a/ports/megaball/megaball/gamedata/constants.py b/ports/megaball/megaball/gamedata/constants.py new file mode 100644 index 0000000000..8d0e753fc6 --- /dev/null +++ b/ports/megaball/megaball/gamedata/constants.py @@ -0,0 +1,73 @@ + +GAME_TITLE = "MEGABALL" +GAME_WIDTH = 160 +GAME_HEIGHT = 144 +GAME_FPS = 60 +GAME_SCALE = 4 + +IMAGE_BANK_0_FILE = "assets/img0.png" +RESOURCE_FILE = "assets/my_resource.pyxres" + +COLLIDE_TOP_LEFT = [ + [1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0, 0], + [1, 1, 1, 1, 0, 0, 0, 0], + [1, 1, 1, 0, 0, 0, 0, 0], + [1, 1, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0], +] + +COLLIDE_TOP_RIGHT = [ + [1, 1, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 1], +] + +COLLIDE_BOTTOM_RIGHT = [ + [0, 0, 0, 0, 0, 0, 0, 1], + [0, 0, 0, 0, 0, 0, 1, 1], + [0, 0, 0, 0, 0, 1, 1, 1], + [0, 0, 0, 0, 1, 1, 1, 1], + [0, 0, 0, 1, 1, 1, 1, 1], + [0, 0, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1], +] + +COLLIDE_BOTTOM_LEFT = [ + [1, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1], +] + +COLLIDE_MATRIX_ALL = [ + COLLIDE_TOP_LEFT, + COLLIDE_TOP_RIGHT, + COLLIDE_BOTTOM_RIGHT, + COLLIDE_BOTTOM_LEFT +] + +def is_colliding_matrix(x, y, matrix): + if matrix not in COLLIDE_MATRIX_ALL: + return False + + if x < 0 or x > 7 or y < 0 or y > 7: + return False + + return matrix[y][x] + + + + \ No newline at end of file diff --git a/ports/megaball/megaball/gamedata/game.py b/ports/megaball/megaball/gamedata/game.py new file mode 100644 index 0000000000..30d10fdf98 --- /dev/null +++ b/ports/megaball/megaball/gamedata/game.py @@ -0,0 +1,103 @@ + +import pyxel + +import constants +import palette +import hud +import input +import stage +import screenshake +import mainmenu +import globals +import audio + +class Game: + def __init__(self): + self.pal_control = palette.PaletteControl() + + self.screen_shake = screenshake.ScreenShake(self) + + self.main_menu = mainmenu.MainMenu(self) + self.stage = stage.Stage(self, 0) + self.hud = hud.Hud(self) + + self.pal_index = 0 + + audio.play_music(audio.MUS_TITLE, True) + + def restart_music(self): + if self.main_menu.is_visible: + audio.play_music(audio.MUS_TITLE) + else: + self.stage.restart_music() + + def quit_to_main_menu(self): + del self.stage + self.stage = stage.Stage(self, 0) + globals.set_high_score() + globals.reset() + self.main_menu.reset() + self.add_fade(palette.FADE_STEP_TICKS_DEFAULT, palette.FADE_LEVEL_3) + + def go_to_next_stage(self): + globals.g_stage_num += 1 + del self.stage + self.stage = stage.Stage(self, globals.g_stage_num) + self.add_fade(palette.FADE_STEP_TICKS_DEFAULT, palette.FADE_LEVEL_3) + + def go_to_game_complete_stage(self): + del self.stage + self.stage = stage.Stage(self, stage.MAX_STAGE_NUM + 1) + self.add_fade(palette.FADE_STEP_TICKS_DEFAULT, palette.FADE_LEVEL_3) + + def restart_stage(self): + del self.stage + self.stage = stage.Stage(self, globals.g_stage_num) + self.add_fade(palette.FADE_STEP_TICKS_DEFAULT, palette.FADE_LEVEL_3) + + def start_game(self): + self.main_menu.hide() + del self.stage + self.stage = stage.Stage(self, globals.g_stage_num) + self.add_fade(palette.FADE_STEP_TICKS_DEFAULT, palette.FADE_LEVEL_3) + + def add_screen_shake(self, ticks, magnitude, queue=False): + self.screen_shake.add_event(ticks, magnitude, queue) + + def cycle_palette(self): + self.pal_index += 1 + if self.pal_index == len(palette.ALL): + self.pal_index = 0 + self.pal_control.add_palette_event(1, palette.ALL[self.pal_index]) + self.add_fade(palette.FADE_STEP_TICKS_DEFAULT, palette.FADE_LEVEL_3) + + def add_fade(self, ticks_per_level, target_level, callback=None): + self.pal_control.add_fade_event(ticks_per_level, target_level, callback) + + def update(self, last_inputs): + if pyxel.btnp(pyxel.KEY_F1): + globals.toggle_sound() + + if pyxel.btnp(pyxel.KEY_F2): + globals.toggle_music(self) + + self.main_menu.update(last_inputs) + + self.stage.update(last_inputs) + + self.pal_control.update() + self.screen_shake.update() + + def draw(self): + for c in range(palette.NUM_COLOURS): + pyxel.pal(palette.DEFAULT[c], self.pal_control.get_col(c)) + + pyxel.cls(self.pal_control.get_col(0)) + + self.stage.draw(self.screen_shake.x, self.screen_shake.y) + self.hud.draw(self.screen_shake.x, self.screen_shake.y) + + self.main_menu.draw(self.screen_shake.x, self.screen_shake.y) + + pyxel.pal() + \ No newline at end of file diff --git a/ports/megaball/megaball/gamedata/globals.py b/ports/megaball/megaball/gamedata/globals.py new file mode 100644 index 0000000000..bc0bb198be --- /dev/null +++ b/ports/megaball/megaball/gamedata/globals.py @@ -0,0 +1,66 @@ + +import pyxel + +import constants +import game + +STARTING_LIVES = 2 +MAX_SCORE = 999999 +MAX_LIVES = 99 + +SCORE_HIT_LIGHT = 200 +SCORE_STAGE_COMPLETE = 1000 +SCORE_USE_WEAPON = 4000 +SCORE_KILLED_SPINNER = 200 +SCORE_KILLED_ALL_SPINNERS = 10000 + +g_lives = STARTING_LIVES +g_score = 0 +g_highscore = 0 +g_stage_num = 1 +g_sound_on = True +g_music_on = True + +def reset(): + global g_lives + global g_score + global g_stage_num + + g_lives = STARTING_LIVES + g_score = 0 + g_stage_num = 1 + +def toggle_sound(): + global g_sound_on + + g_sound_on = not g_sound_on + + if g_sound_on == False: + pyxel.stop() + +def toggle_music(game_obj): + global g_music_on + + g_music_on = not g_music_on + + if g_music_on == False: + pyxel.stop() + else: + game_obj.restart_music() + +def set_high_score(): + global g_score + global g_highscore + + g_highscore = max(g_score, g_highscore) + +def add_lives(amt): + global g_lives + + g_lives = max(0, min(g_lives + amt, MAX_LIVES)) + +def add_score(amt): + global g_score + + g_score = max(0, min(g_score + amt, MAX_SCORE)) + \ No newline at end of file diff --git a/ports/megaball/megaball/gamedata/hud.py b/ports/megaball/megaball/gamedata/hud.py new file mode 100644 index 0000000000..51885d69a6 --- /dev/null +++ b/ports/megaball/megaball/gamedata/hud.py @@ -0,0 +1,31 @@ + +import pyxel + +import game +import constants +import globals +import utils +import stage + +class Hud: + def __init__(self, game): + self.game = game + + def update(self): + pass + + def draw(self, shake_x, shake_y): + # top bar + pyxel.blt(shake_x + 0, shake_y + 0, 0, 0, 0, constants.GAME_WIDTH, 16) + # bottom bar + pyxel.blt(shake_x + 0, shake_y + 136, 0, 0, 16, constants.GAME_WIDTH, 8) + # left bar + pyxel.blt(shake_x + 0, shake_y + 16, 0, 0, 24, 8, 120) + # right bar + pyxel.blt(shake_x + 152, shake_y + 16, 0, 8, 24, 8, 120) + + utils.draw_number_shadowed(shake_x + 31, shake_y + 5, globals.g_lives, zeropad=2) + utils.draw_number_shadowed(shake_x + 57, shake_y + 5, globals.g_score, zeropad=6) + utils.draw_number_shadowed(shake_x + 113, shake_y + 5, globals.g_stage_num, zeropad=2) + utils.draw_number_shadowed(shake_x + 137, shake_y + 5, stage.MAX_STAGE_NUM, zeropad=2) + \ No newline at end of file diff --git a/ports/megaball/megaball/gamedata/icon.ico b/ports/megaball/megaball/gamedata/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..defd61b9d06cee45d9f82f57becc50703fc9b86d GIT binary patch literal 1406 zcmZQzU<5(|0R|w+!H~hqz#zuJz@P!dKp_SNAO?wp07woSh&R?|C{n0n$dS0h5aiL# zFeAp2Va`-Z2FI4m4ELs$F)UuZm|@2B{|qaS6)^lixRN3I@Jogb$KNu%d-o2Taie0R zAut*OLnH*a$pUOZfTWQT2#^H8T!a9K3zTPs=>V}&_-y>k^lez literal 0 HcmV?d00001 diff --git a/ports/megaball/megaball/gamedata/input.py b/ports/megaball/megaball/gamedata/input.py new file mode 100644 index 0000000000..fa13def5c2 --- /dev/null +++ b/ports/megaball/megaball/gamedata/input.py @@ -0,0 +1,85 @@ + +import pyxel + +UP = 0 +DOWN = 1 +LEFT = 2 +RIGHT = 3 +BUTTON_A = 4 +BUTTON_B = 5 +BUTTON_START = 6 +BUTTON_SELECT = 7 + +class Input: + + def __init__(self): + self.pressing = [] + self.pressed = [] + + def get(self): + self.pressing.clear() + self.pressed.clear() + + # pressing + if pyxel.btn(pyxel.KEY_UP) or pyxel.btn(pyxel.KEY_W) or \ + pyxel.btn(pyxel.GAMEPAD1_BUTTON_DPAD_UP): + self.pressing.append(UP) + elif pyxel.btn(pyxel.KEY_DOWN) or pyxel.btn(pyxel.KEY_S) or \ + pyxel.btn(pyxel.GAMEPAD1_BUTTON_DPAD_DOWN): + self.pressing.append(DOWN) + + if pyxel.btn(pyxel.KEY_LEFT) or pyxel.btn(pyxel.KEY_A) or \ + pyxel.btn(pyxel.GAMEPAD1_BUTTON_DPAD_LEFT): + self.pressing.append(LEFT) + elif pyxel.btn(pyxel.KEY_RIGHT) or pyxel.btn(pyxel.KEY_D) or \ + pyxel.btn(pyxel.GAMEPAD1_BUTTON_DPAD_RIGHT): + self.pressing.append(RIGHT) + + if pyxel.btn(pyxel.KEY_Z) or pyxel.btn(pyxel.KEY_K) or \ + pyxel.btn(pyxel.GAMEPAD1_BUTTON_A): + self.pressing.append(BUTTON_A) + + if pyxel.btn(pyxel.KEY_X) or pyxel.btn(pyxel.KEY_L) or \ + pyxel.btn(pyxel.GAMEPAD1_BUTTON_B): + self.pressing.append(BUTTON_B) + + if pyxel.btn(pyxel.KEY_RETURN) or \ + pyxel.btn(pyxel.GAMEPAD1_BUTTON_START): + self.pressing.append(BUTTON_START) + + if pyxel.btn(pyxel.KEY_SPACE) or \ + pyxel.btn(pyxel.GAMEPAD1_BUTTON_BACK): + self.pressing.append(BUTTON_SELECT) + + # pressed + if pyxel.btnp(pyxel.KEY_UP) or pyxel.btnp(pyxel.KEY_W) or \ + pyxel.btnp(pyxel.GAMEPAD1_BUTTON_DPAD_UP): + self.pressed.append(UP) + elif pyxel.btnp(pyxel.KEY_DOWN) or pyxel.btnp(pyxel.KEY_S) or \ + pyxel.btnp(pyxel.GAMEPAD1_BUTTON_DPAD_DOWN): + self.pressed.append(DOWN) + + if pyxel.btnp(pyxel.KEY_LEFT) or pyxel.btnp(pyxel.KEY_A) or \ + pyxel.btnp(pyxel.GAMEPAD1_BUTTON_DPAD_LEFT): + self.pressed.append(LEFT) + elif pyxel.btnp(pyxel.KEY_RIGHT) or pyxel.btnp(pyxel.KEY_D) or \ + pyxel.btnp(pyxel.GAMEPAD1_BUTTON_DPAD_RIGHT): + self.pressed.append(RIGHT) + + if pyxel.btnp(pyxel.KEY_Z) or pyxel.btnp(pyxel.KEY_K) or \ + pyxel.btnp(pyxel.GAMEPAD1_BUTTON_A): + self.pressed.append(BUTTON_A) + + if pyxel.btnp(pyxel.KEY_X) or pyxel.btnp(pyxel.KEY_L) or \ + pyxel.btnp(pyxel.GAMEPAD1_BUTTON_B): + self.pressed.append(BUTTON_B) + + if pyxel.btnp(pyxel.KEY_RETURN) or \ + pyxel.btnp(pyxel.GAMEPAD1_BUTTON_START): + self.pressed.append(BUTTON_START) + + if pyxel.btnp(pyxel.KEY_SPACE) or \ + pyxel.btnp(pyxel.GAMEPAD1_BUTTON_BACK): + self.pressed.append(BUTTON_SELECT) + + diff --git a/ports/megaball/megaball/gamedata/light.py b/ports/megaball/megaball/gamedata/light.py new file mode 100644 index 0000000000..b48f659e46 --- /dev/null +++ b/ports/megaball/megaball/gamedata/light.py @@ -0,0 +1,41 @@ + +import pyxel + +import globals + +TICKS_PER_FRAME = 10 +MAX_FRAMES = 5 + +class Light: + def __init__(self, x, y): + self.x = x + self.y = y + + self.frame = 0 + self.frame_ticks = 0 + self.anim_dir = 1 + + self.is_hit = False + + def got_hit(self): + if self.is_hit == False: + self.frame = 4 + self.is_hit = True + globals.add_score(globals.SCORE_HIT_LIGHT) + return True + return False + + def update(self, stage): + if not self.is_hit: + self.frame_ticks += 1 + + if self.frame_ticks == TICKS_PER_FRAME: + self.frame_ticks = 0 + self.frame += self.anim_dir + + if self.frame == 0 or self.frame == MAX_FRAMES - 1: + self.anim_dir *= -1 + + def draw(self, shake_x, shake_y): + pyxel.blt(shake_x + self.x, shake_y + self.y, 0, 160 + self.frame*8, 0, 8, 8) + \ No newline at end of file diff --git a/ports/megaball/megaball/gamedata/main.py b/ports/megaball/megaball/gamedata/main.py new file mode 100644 index 0000000000..ec94e62291 --- /dev/null +++ b/ports/megaball/megaball/gamedata/main.py @@ -0,0 +1,36 @@ + +import pyxel + +import constants +import input +import game + +class App: + def __init__(self): + pyxel.init( + width=constants.GAME_WIDTH, + height=constants.GAME_HEIGHT, + fps=constants.GAME_FPS + ) + pyxel.scale=constants.GAME_SCALE + pyxel.caption = constants.GAME_TITLE + + pyxel.load(constants.RESOURCE_FILE) + pyxel.images[0].load(0, 0, constants.IMAGE_BANK_0_FILE) + + self.input = input.Input() + self.game = game.Game() + pyxel.mouse(False) + #pyxel.mouse(True) + + pyxel.run(self.update, self.draw) + + def update(self): + self.input.get() + self.game.update(self.input) + + def draw(self): + self.game.draw() + +App() + diff --git a/ports/megaball/megaball/gamedata/mainmenu.py b/ports/megaball/megaball/gamedata/mainmenu.py new file mode 100644 index 0000000000..496a502aac --- /dev/null +++ b/ports/megaball/megaball/gamedata/mainmenu.py @@ -0,0 +1,118 @@ + +import pyxel + +import utils +import globals +import game +import input +import palette +import audio + +SEL_START_GAME = 0 +SEL_PALETTE = 1 +SEL_EXIT_GAME = 2 + +SELECTIONS = { + SEL_START_GAME : [40,87,80,8], # [x, y, w, h] + SEL_PALETTE : [52,103,56,8], + SEL_EXIT_GAME : [44,119,72,8] +} + +class MainMenu: + def __init__(self, game): + self.game = game + + self.is_visible = True + + self.show_press_start = True + self.press_start_flash_ticks = 0 + self.sel_index = 0 + + def hide(self): + self.is_visible = False + + def reset(self): + self.is_visible = True + self.show_press_start = True + self.press_start_flash_ticks = 0 + self.sel_index = 0 + audio.play_music(audio.MUS_TITLE, True) + + def _pressed_select(self): + audio.play_sound(audio.SND_MENU_SELECT) + if self.sel_index == SEL_START_GAME: + self.game.add_fade(palette.FADE_STEP_TICKS_DEFAULT, + palette.FADE_LEVEL_6, self.game.start_game) + elif self.sel_index == SEL_PALETTE: + self.game.add_fade(palette.FADE_STEP_TICKS_DEFAULT, + palette.FADE_LEVEL_6, self.game.cycle_palette) + elif self.sel_index == SEL_EXIT_GAME: + self.game.add_fade(palette.FADE_STEP_TICKS_DEFAULT, + palette.FADE_LEVEL_0, pyxel.quit) + + def _change_selection(self, dir): + audio.play_sound(audio.SND_MENU_MOVE) + self.sel_index += dir + if self.sel_index < 0: + self.sel_index = len(SELECTIONS) - 1 + elif self.sel_index >= len(SELECTIONS): + self.sel_index = 0 + + def update(self, last_inputs): + if not self.is_visible: + return + + if self.show_press_start: + self.press_start_flash_ticks += 1 + if self.press_start_flash_ticks == 50: + self.press_start_flash_ticks = 0 + if input.BUTTON_START in last_inputs.pressed: + self.show_press_start = False + self.sel_index = 0 + else: + if input.BUTTON_A in last_inputs.pressed: + self._pressed_select() + elif input.UP in last_inputs.pressed: + self._change_selection(-1) + elif input.DOWN in last_inputs.pressed: + self._change_selection(1) + + def draw(self, shake_x, shake_y): + if not self.is_visible: + return + + if self.show_press_start: + if self.press_start_flash_ticks < 30: + pyxel.blt(shake_x + 36, shake_y + 104, 0, 16, 72, 40, 8, 8) # press + pyxel.blt(shake_x + 84, shake_y + 104, 0, 56, 72, 40, 8, 8) # start + else: + pyxel.blt(shake_x + 24, shake_y + 84, 0, 0, 144, 116, 52, 8) # panel bg + + pyxel.blt(shake_x + 40, shake_y + 88, 0, 56, 72, 40, 8, 8) # start + pyxel.blt(shake_x + 88, shake_y + 88, 0, 40, 80, 32, 8, 8) # game + + pyxel.blt(shake_x + 52, shake_y + 104, 0, 104, 80, 56, 8, 8) # palette + + pyxel.blt(shake_x + 44, shake_y + 120, 0, 96, 72, 32, 8, 8) # exit + pyxel.blt(shake_x + 84, shake_y + 120, 0, 40, 80, 32, 8, 8) # game + + pyxel.blt( + shake_x + SELECTIONS[self.sel_index][0]-12, + shake_y + SELECTIONS[self.sel_index][1], + 0, + 16, 33, 9, 9, 8 + ) # selection ball left + pyxel.blt( + shake_x + SELECTIONS[self.sel_index][0] + SELECTIONS[self.sel_index][2] + 2, + shake_y + SELECTIONS[self.sel_index][1], + 0, + 16, 33, 9, 9, 8 + ) # selection ball right + + pyxel.blt(shake_x + 44, shake_y + 20, 0, 16, 80, 24, 8, 8) # hi- + utils.draw_number_shadowed(shake_x + 68, shake_y + 20, + globals.g_highscore, zeropad=6) # highscore number + pyxel.blt(shake_x + 13, shake_y + 36, 0, 16, 88, 135, 44, 8) # logo + + + \ No newline at end of file diff --git a/ports/megaball/megaball/gamedata/palette.py b/ports/megaball/megaball/gamedata/palette.py new file mode 100644 index 0000000000..5684833d93 --- /dev/null +++ b/ports/megaball/megaball/gamedata/palette.py @@ -0,0 +1,135 @@ + +import pyxel + +NUM_COLOURS = 4 + +DEFAULT = [ pyxel.COLOR_NAVY, pyxel.COLOR_GREEN, pyxel.COLOR_LIME, pyxel.COLOR_WHITE ] +RED = [ pyxel.COLOR_PURPLE, pyxel.COLOR_RED, pyxel.COLOR_PINK, pyxel.COLOR_WHITE ] +BLUE = [ pyxel.COLOR_NAVY, pyxel.COLOR_DARK_BLUE, pyxel.COLOR_CYAN, pyxel.COLOR_WHITE ] +BROWN = [ pyxel.COLOR_BROWN, pyxel.COLOR_ORANGE, pyxel.COLOR_PEACH, pyxel.COLOR_WHITE ] +GREY = [ pyxel.COLOR_BLACK, pyxel.COLOR_DARK_BLUE, pyxel.COLOR_GRAY, pyxel.COLOR_WHITE ] + +ALL = [ + DEFAULT, + RED, + BLUE, + BROWN, + GREY +] + +FADE_LEVEL_0 = -3 # all colours to darkest colour. +FADE_LEVEL_1 = -2 # all but brightest to darkest colour. +FADE_LEVEL_2 = -1 # all but two brightest to darkest colour. +FADE_LEVEL_3 = 0 # no modification +FADE_LEVEL_4 = 1 # all but two darkest to brightest colour. +FADE_LEVEL_5 = 2 # all but darkest to brightest colour. +FADE_LEVEL_6 = 3 # all colours to brightest colour. + +FADE_LEVELS = [ + FADE_LEVEL_0, + FADE_LEVEL_1, + FADE_LEVEL_2, + FADE_LEVEL_3, + FADE_LEVEL_4, + FADE_LEVEL_5, + FADE_LEVEL_6 +] + +FADE_STEP_TICKS_DEFAULT = 5 +FADE_STEP_TICKS_SLOW = 10 + +class FadeEvent: + def __init__(self, ticks_per_level, new_level, callback=None): + self.ticks_per_level = ticks_per_level + self.ticks = 0 + self.new_level = new_level + self.callback = callback + +class FadeControl: + def __init__(self): + self.current_level = FADE_LEVEL_3 + + self.events = [] + + def add_event(self, ticks_per_level, new_level, callback=None): + if ticks_per_level <= 0 or new_level not in FADE_LEVELS: + return + + self.events.append(FadeEvent(ticks_per_level, new_level, callback)) + + def get_level(self): + return self.current_level + + def update(self): + if len(self.events) > 0: + e = self.events[0] + e.ticks += 1 + + if e.ticks == e.ticks_per_level: + e.ticks = 0 + if self.current_level < e.new_level: + self.current_level += 1 + elif self.current_level > e.new_level: + self.current_level -= 1 + + if self.current_level == e.new_level: + if e.callback is not None: + e.callback() + self.events.pop(0) + +class PaletteEvent: + def __init__(self, ticks, new_pal, callback=None): + self.ticks = ticks + self.new_pal = DEFAULT + self.callback = callback + if new_pal in ALL: + self.new_pal = new_pal + +class PaletteControl: + def __init__(self): + self.current_palette = DEFAULT + + self.events = [] + + self.fade_control = FadeControl() + + def add_fade_event(self, ticks_per_level, new_level, callback=None): + self.fade_control.add_event(ticks_per_level, new_level, callback) + + def add_palette_event(self, ticks, new_pal, callback=None): + if ticks <= 0 or new_pal not in ALL: + return + + #print("added palette event") + self.events.append(PaletteEvent(ticks, new_pal, callback)) + + def update(self): + self.fade_control.update() + + if len(self.events) > 0: + e = self.events[0] + e.ticks -= 1 + if e.ticks == 0: + self.current_palette = e.new_pal + if e.callback is not None: + e.callback() + self.events.pop(0) + + #print("Removed pal event, queue size now: " + str(len(self.events))) + + def set_pal(self, pal): + if pal in ALL: + self.current_palette = pal + + def get_pal(self): + return self.current_palette + + def get_col(self, index): + if index < 0 or index >= NUM_COLOURS: + return self.current_palette[0] + + index = max(0, min(NUM_COLOURS-1, index + self.fade_control.get_level())) + + return self.current_palette[index] + + diff --git a/ports/megaball/megaball/gamedata/player.py b/ports/megaball/megaball/gamedata/player.py new file mode 100644 index 0000000000..a7d95d50e2 --- /dev/null +++ b/ports/megaball/megaball/gamedata/player.py @@ -0,0 +1,256 @@ + +import math + +import pyxel + +import utils +import input +import rect +import circle +import light +import weapon +import globals +import audio + +MAX_SPEED = 1.2 +DECEL = 0.01 +ACCEL = 0.06 +SLOPE_ACCEL = 0.10 + +HIT_SOLID_DAMP = 0.7 + +INTRO_TICKS_PER_FRAME = 10 +DEAD_TICKS_PER_FRAME = 10 + +STATE_INTRO = 0 +STATE_PLAY = 1 +STATE_DEAD = 2 +STATE_STAGE_COMPLETE = 3 +STATE_GAME_COMPLETE = 4 +STATE_WEAPON = 5 + +class Player: + def __init__(self, x, y): + self.x = x + self.y = y + + self.vx = 0 + self.vy = 0 + + self.radius = 4 + + self.state = STATE_INTRO + + self.intro_frame = 4 + self.dead_frame = 0 + + self.anim_ticks = 0 + + self.weapon = weapon.Weapon() + + def _do_solid_collisions(self, stage): + new_x = self.x + self.vx + + for b in stage.solid_rects: + if utils.circle_rect_overlap(new_x, self.y, self.radius, + b[0], b[1], b[2], b[3]): + if self.x > b[0] + b[2]: # was prev to right of border. + new_x = b[0] + b[2] + self.radius + elif self.x < b[0]: # was prev to left of border. + new_x = b[0] - self.radius + + self.vx *= -HIT_SOLID_DAMP + stage.player_hit_solid() + break + + new_y = self.y + self.vy + + for b in stage.solid_rects: + if utils.circle_rect_overlap(self.x, new_y, self.radius, + b[0], b[1], b[2], b[3]): + if self.y > b[1] + b[3]: # was prev below border. + new_y = b[1] + b[3] + self.radius + elif self.y < b[1]: # was prev above border. + new_y = b[1] - self.radius + + self.vy *= -HIT_SOLID_DAMP + stage.player_hit_solid() + break + + self.x = new_x + self.y = new_y + + def _get_input_angle(self, last_inputs): + press_angle = None + if input.LEFT in last_inputs.pressing: + press_angle = 180 + elif input.RIGHT in last_inputs.pressing: + press_angle = 0 + + if input.UP in last_inputs.pressing: + if press_angle == 0: + press_angle = 315 + elif press_angle == 180: + press_angle = 225 + else: + press_angle = 270 + elif input.DOWN in last_inputs.pressing: + if press_angle == 0: + press_angle = 45 + elif press_angle == 180: + press_angle = 135 + else: + press_angle = 90 + + return press_angle + + # a "force" is a list of lists: [[speed, angle]... etc]. + def _apply_forces(self, forces): + for f in forces: + self.vx = max(-MAX_SPEED, + min(MAX_SPEED, + self.vx + f[0] * math.cos(math.radians(f[1])))) + self.vy = max(-MAX_SPEED, + min(MAX_SPEED, + self.vy + f[0] * math.sin(math.radians(f[1])))) + #print("py after: " + str(self.y)) + + def _get_tile_force(self, stage, forces): + angle = stage.get_tile_angle(self.x, self.y) + if angle is not None: + forces.append([SLOPE_ACCEL, angle]) + #print("Got tile force: spd:{a}, accl:{b}".format(a=SLOPE_ACCEL, b=angle)) + + def _is_stuck_in_pocket(self, stage): + if abs(self.vx) > 0.01 or abs(self.vy) > 0.01: + return False + + for p in stage.pockets: + if rect.contains_point(p[0], p[1], p[2], p[3], self.x, self.y): + return True + + def _do_enemy_collisions(self, stage): + for s in stage.spinners: + if s.is_dead: + continue + if circle.overlap(s.x, s.y, s.radius, self.x, self.y, self.radius): + self.state = STATE_DEAD + stage.player_hit() + return + + def _do_light_collisions(self, stage): + for s in stage.lights: + if rect.contains_point(s.x, s.y, 8, 8, self.x, self.y): + if s.got_hit() == True: + audio.play_sound(audio.SND_HIT_TARGET) + if stage.is_complete(): + self.state = STATE_STAGE_COMPLETE + globals.add_score(globals.SCORE_STAGE_COMPLETE) + return + + def fire_weapon(self, stage): + self.weapon.fire(self.x, self.y) + self.state = STATE_WEAPON + globals.g_lives -= 1 + stage.player_used_weapon() + globals.add_score(globals.SCORE_USE_WEAPON) + + def weapon_done(self): + self.state = STATE_INTRO + self.intro_frame = 4 + self.anim_ticks = 0 + self.vx = 0 + self.vy = 0 + + def update(self, stage, last_inputs): + if self.state == STATE_INTRO: + self.anim_ticks += 1 + if self.anim_ticks == INTRO_TICKS_PER_FRAME: + self.anim_ticks = 0 + if self.intro_frame > -1: + self.intro_frame -= 1 + + if self.intro_frame == -1: + self.intro_frame = 4 + self.state = STATE_PLAY + stage.player_intro_done() + return + elif self.state == STATE_DEAD: + self.anim_ticks += 1 + if self.anim_ticks == DEAD_TICKS_PER_FRAME: + self.anim_ticks = 0 + + if self.dead_frame < 11: + self.dead_frame += 1 + + if self.dead_frame == 11: + stage.player_death_anim_done() + return + elif self.state == STATE_STAGE_COMPLETE: + return + elif self.state == STATE_WEAPON: + self.weapon.update(self, stage) + return + + forces = [] + + #print("py after: " + str(self.y)) + + input_angle = self._get_input_angle(last_inputs) + if input_angle is not None: + forces.append([ACCEL, input_angle]) + + if not self._is_stuck_in_pocket(stage): + self._get_tile_force(stage, forces) + + self._apply_forces(forces) + + if self.vx > 0: + self.vx = max(0, self.vx - DECEL) + elif self.vx < 0: + self.vx = min(0, self.vx + DECEL) + + if self.vy > 0: + self.vy = max(0, self.vy - DECEL) + elif self.vy < 0: + self.vy = min(0, self.vy + DECEL) + + #print("vx,vy: {a},{b}".format(a=self.vx, b=self.vy)) + + self._do_solid_collisions(stage) + + self._do_enemy_collisions(stage) + + if self.state != STATE_DEAD and self.state != STATE_GAME_COMPLETE: + self._do_light_collisions(stage) + + if input.BUTTON_A in last_inputs.pressed and \ + self.state != STATE_WEAPON and \ + globals.g_lives > 0: + self.fire_weapon(stage) + + #print("py after: " + str(self.y)) + + #if pyxel.mouse_x >= 8 and pyxel.mouse_x < 152 and \ + # pyxel.mouse_y >= 16 and pyxel.mouse_y < 136: + # ang = stage.get_tile_angle(pyxel.mouse_x, pyxel.mouse_y) + #if ang is not None: + # print("Hit slope angle {a},{b}: ".format(a=pyxel.mouse_x,b=pyxel.mouse_y)\ + # + str(ang) + ", " + str(pyxel.frame_count)) + + + def draw(self, shake_x, shake_y): + if self.state == STATE_INTRO: + pyxel.blt(shake_x + self.x-10, shake_y + self.y-10, 0, + self.intro_frame*21, 231, 21, 21, 8) + elif self.state == STATE_DEAD: + pyxel.blt(shake_x + self.x-10, shake_y + self.y-10, 0, + self.dead_frame*21, 231, 21, 21, 8) + elif self.state == STATE_WEAPON: + self.weapon.draw(shake_x, shake_y) + else: + pyxel.blt(shake_x + self.x-self.radius, shake_y + self.y-self.radius, 0, + 16, 33, 9, 9, 8) + + #pyxel.circb(self.x, self.y, self.radius, 8) + \ No newline at end of file diff --git a/ports/megaball/megaball/gamedata/readme.txt b/ports/megaball/megaball/gamedata/readme.txt new file mode 100644 index 0000000000..4fa60734dc --- /dev/null +++ b/ports/megaball/megaball/gamedata/readme.txt @@ -0,0 +1,47 @@ +Megaball was made in a week for GBJam 8. + +The source code is also available here: https://github.com/helpcomputer/megaball + + +Gameplay + +The goal of the game is to roll the ball over every flashing panel to complete the stage. + +There are 15 stages that become increasingly more difficult, presenting you with tougher terrain and more enemies to avoid. + +For each stage you complete you gain one extra life. + +Your main aim should be to avoid enemies, but if you need to you can use your special weapon which will cause you to self-destruct and shatter into 10 pieces, killing any enemy which collides with these pieces. You will immediately re-spawn, and the enemy will reappear in 5 seconds. + + +Controls + +WASD, Arrow keys, or gamepad D-pad to move. + +Z key, K key, or gamepad Button A to fire weapon, or confirm in menu. + +Enter key or gamepad Start button to Start or Pause. + +F1 key to toggle sound. +F2 key to toggle music. + + +Credits + +Design & Art: + +https://helpcomputer.itch.io/ + +https://twitter.com/helpcomputer0 + +Sound and Music: + +https://mikerichmond.itch.io/ + +https://twitter.com/richmondmike + +Font by Damien Guard: + +https://damieng.com/typography/zx-origins/ + +https://twitter.com/damienguard \ No newline at end of file diff --git a/ports/megaball/megaball/gamedata/rect.py b/ports/megaball/megaball/gamedata/rect.py new file mode 100644 index 0000000000..e6c854e983 --- /dev/null +++ b/ports/megaball/megaball/gamedata/rect.py @@ -0,0 +1,32 @@ + +def overlap(x1, y1, w1, h1, x2, y2, w2, h2): + return x1 < x2 + w2 and \ + x1 + w1 > x2 and \ + y1 < y2 + h2 and \ + y1 + h1 > y2 + +def contains_point(x, y, w, h, px, py): + return x <= px and \ + x + w >= px and \ + y <= py and \ + y + h >= py + +class Rect: + def __init__(self, x, y, w, h): + self.x = x + self.y = y + self.w = w + self.h = h + + def is_overlapping(self, x, y, w, h): + return self.x < x + w and \ + self.x + self.w > x and \ + self.y < y + h and \ + self.y + self.h > y + + def is_overlapping_other(self, other): + return self.x < other.x + other.w and \ + self.x + self.w > other.x and \ + self.y < other.y + other.h and \ + self.y + self.h > other.y + \ No newline at end of file diff --git a/ports/megaball/megaball/gamedata/screenshake.py b/ports/megaball/megaball/gamedata/screenshake.py new file mode 100644 index 0000000000..c741761b60 --- /dev/null +++ b/ports/megaball/megaball/gamedata/screenshake.py @@ -0,0 +1,38 @@ + +import random + +class Event: + def __init__(self, ticks, mag): + self.ticks = ticks + self.magnitude = mag + +class ScreenShake: + def __init__(self, game): + self.game = game + + self.x = 0 + self.y = 0 + + self.events = [] + + def add_event(self, ticks, magnitude, queue=False): + if ticks < 0 or magnitude <= 0: + return + + if len(self.events) > 0 and not queue: + return + + self.events.append(Event(ticks, magnitude)) + + def update(self): + if len(self.events) > 0: + e = self.events[0] + e.ticks -= 1 + if e.ticks == 0: + self.events.pop(0) + self.x = 0 + self.y = 0 + else: + self.x = random.randint(-e.magnitude, e.magnitude) + self.y = random.randint(-e.magnitude, e.magnitude) + \ No newline at end of file diff --git a/ports/megaball/megaball/gamedata/spinner.py b/ports/megaball/megaball/gamedata/spinner.py new file mode 100644 index 0000000000..da7aa14ca9 --- /dev/null +++ b/ports/megaball/megaball/gamedata/spinner.py @@ -0,0 +1,156 @@ + +import random + +import pyxel + +import utils +import stage + +TYPE_AGGRESSIVE = 0 +TYPE_MILD = 1 +TYPE_RANDOM_SLOW = 2 +TYPE_RANDOM_FAST = 3 + +TYPES = [ + TYPE_AGGRESSIVE, + TYPE_MILD, + TYPE_RANDOM_SLOW, + TYPE_RANDOM_FAST +] + +TICKS_PER_FRAME = 10 +MAX_FRAME = 4 + +MAX_SPEED = 0.4 +MAX_RESPAWN_TICKS = 300 # 5 secs + +class Spinner: + def __init__(self, x, y, type): + self.x = x + self.y = y + self.type = 2 + if type in TYPES: + self.type = type + + self.vx = random.choice([-MAX_SPEED, MAX_SPEED]) + self.vy = random.choice([-MAX_SPEED, MAX_SPEED]) + + self.radius = 4 + + self.frame = 0 + self.frame_ticks = 0 + + self.is_dead = False + + self.respawn_ticks = MAX_RESPAWN_TICKS + + def _set_new_position(self, stageObj): + px = stageObj.player.x + py = stageObj.player.y + loc = None + loclist = [ + stage.SPAWN_SECTOR_TOPLEFT, + stage.SPAWN_SECTOR_BOTTOMLEFT, + stage.SPAWN_SECTOR_TOPRIGHT, + stage.SPAWN_SECTOR_BOTTOMRIGHT + ] + if px < 80: + if py < 75: + loclist.remove(stage.SPAWN_SECTOR_TOPLEFT) + else: + loclist.remove(stage.SPAWN_SECTOR_BOTTOMLEFT) + else: + if py < 75: + loclist.remove(stage.SPAWN_SECTOR_TOPRIGHT) + else: + loclist.remove(stage.SPAWN_SECTOR_BOTTOMRIGHT) + + loc = stageObj.get_random_spawn_loc(random.choice(loclist)) + self.x = loc[0] + self.y = loc[1] + + def kill(self): + self.is_dead = True + self.respawn_ticks = MAX_RESPAWN_TICKS + + def _do_collisions(self, stage): + new_x = self.x + self.vx + + for b in stage.solid_rects: + if utils.circle_rect_overlap(new_x, self.y, self.radius, + b[0], b[1], b[2], b[3]): + if self.x > b[0] + b[2]: # was prev to right of border. + new_x = b[0] + b[2] + self.radius + elif self.x < b[0]: # was prev to left of border. + new_x = b[0] - self.radius + + self.vx *= -1 + break + + new_y = self.y + self.vy + + for b in stage.solid_rects: + if utils.circle_rect_overlap(self.x, new_y, self.radius, + b[0], b[1], b[2], b[3]): + if self.y > b[1] + b[3]: # was prev below border. + new_y = b[1] + b[3] + self.radius + elif self.y < b[1]: # was prev above border. + new_y = b[1] - self.radius + + self.vy *= -1 + break + + self.x = new_x + self.y = new_y + + def respawn(self): + self.is_dead = False + + def update(self, stage): + if self.is_dead: + self.respawn_ticks -= 1 + if self.respawn_ticks == 0: + self.respawn() + elif self.respawn_ticks == 30: + self._set_new_position(stage) + else: + self._do_collisions(stage) + + self.frame_ticks += 1 + if self.frame_ticks == TICKS_PER_FRAME: + self.frame_ticks = 0 + self.frame += 1 + if self.frame == MAX_FRAME: + self.frame = 0 + + def draw(self, shake_x, shake_y): + if self.is_dead: + framex = None + if self.respawn_ticks < 10: + framex = 42 + elif self.respawn_ticks < 20: + framex = 63 + elif self.respawn_ticks < 30: + framex = 84 + if framex is not None: + pyxel.blt( + self.x + shake_x - 10, + self.y + shake_y - 10, + 0, + framex, + 231, + 21, 21, + 8 + ) + else: + pyxel.blt( + self.x + shake_x - 4, + self.y + shake_y - 4, + 0, + 160 + self.frame*9, + 8, + 9, 9, + 8 + ) + + \ No newline at end of file diff --git a/ports/megaball/megaball/gamedata/stage.py b/ports/megaball/megaball/gamedata/stage.py new file mode 100644 index 0000000000..cac3d4992f --- /dev/null +++ b/ports/megaball/megaball/gamedata/stage.py @@ -0,0 +1,467 @@ + +import math +import random + +import pyxel + +import player +import utils +import constants +import stage +import light +import input +import game +import palette +import spinner +import globals +import stagedata +import audio + + + + + + + + + +def custom_get_tile(self, x, y): + """ + Recreates the `.get()` method using Pyxel's official API. + + Args: + x (int): The x-coordinate in pixel space. + y (int): The y-coordinate in pixel space. + + Returns: + int: The tile index at the given coordinates, or None if out of bounds. + """ + TILE_SIZE = 8 # Size of each tile in pixels + + # Convert pixel-based coordinates to tile indices + tile_x = x // 8 + tile_y = y // 8 + + # Ensure the tile indices are within bounds + if 0 <= tile_x < self.width and 0 <= tile_y < self.height: + # Use .pget() to fetch the tile index + return self.pget(tile_x, tile_y) + return None # Return None for out-of-bounds coordinates + + + + + +pyxel.Tilemap.get = custom_get_tile + + + + + + + + + + +MAX_STAGE_NUM = 15 + +WIDTH_TILES = 18 +HEIGHT_TILES = 15 + +POST_TILE = utils.get_tile_index(40, 32) + +# tile_index : [angle, collision matrix if triangle] +SLOPE_TILES = { + utils.get_tile_index(56,32): [225, constants.COLLIDE_BOTTOM_RIGHT], # top-left + utils.get_tile_index(64,32): [270, None], # top + utils.get_tile_index(72,32): [315, constants.COLLIDE_BOTTOM_LEFT], # top-right + utils.get_tile_index(56,40): [180, None], # left + utils.get_tile_index(72,40): [0, None], # right + utils.get_tile_index(56,48): [135, constants.COLLIDE_TOP_RIGHT], # bottom-left + utils.get_tile_index(64,48): [90, None], # bottom + utils.get_tile_index(72,48): [45, constants.COLLIDE_TOP_LEFT], # bottom-right + utils.get_tile_index(80,32): [225, constants.COLLIDE_TOP_LEFT], # top-left 2 + utils.get_tile_index(88,32): [135, constants.COLLIDE_BOTTOM_LEFT], # bottom-left 2 + utils.get_tile_index(80,40): [45, constants.COLLIDE_BOTTOM_RIGHT], # bottom-right 2 + utils.get_tile_index(88,40): [315, constants.COLLIDE_TOP_RIGHT] # top-right 2 + #utils.get_tile_index(), +} + +POCKET_TILE_NW = utils.get_tile_index(80,40) +POCKET_TILE_NE = utils.get_tile_index(88,32) +POCKET_TILE_SE = utils.get_tile_index(80,32) +POCKET_TILE_SW = utils.get_tile_index(88,40) + +LIGHT_TILE = utils.get_tile_index(160,0) +BLANK_TILE = utils.get_tile_index(32,24) + +class PauseMenu: + + SEL_RESUME = 0 + SEL_PALETTE = 1 + SEL_QUIT = 2 + + SELECTIONS = { + SEL_RESUME : [56,55,48,8], + SEL_PALETTE : [52,71,56,8], + SEL_QUIT : [64,87,32,8] + } + + def __init__(self, stage): + self.stage = stage + + self.is_visible = False + + self.sel_index = 0 + + self.quitting = False + + def _pressed_select(self): + if self.sel_index == self.SEL_RESUME: + self.is_visible = False + elif self.sel_index == self.SEL_PALETTE: + self.stage.game.add_fade(5, palette.FADE_LEVEL_6, self.stage.game.cycle_palette) + elif self.sel_index == self.SEL_QUIT: + self.quitting = True + self.stage.quit() + + def _change_selection(self, dir): + self.sel_index += dir + if self.sel_index < 0: + self.sel_index = len(self.SELECTIONS) - 1 + elif self.sel_index >= len(self.SELECTIONS): + self.sel_index = 0 + + def update(self, last_inputs): + if not self.is_visible or self.quitting: + return + + if input.BUTTON_START in last_inputs.pressed: + self.is_visible = False + self.sel_index = 0 + elif input.BUTTON_A in last_inputs.pressed: + self._pressed_select() + elif input.UP in last_inputs.pressed: + self._change_selection(-1) + elif input.DOWN in last_inputs.pressed: + self._change_selection(1) + + def draw(self, shake_x, shake_y): + if not self.is_visible: + return + + pyxel.blt(shake_x + 24, shake_y + 52, 0, 0, 144, 116, 52, 8) # panel bg + + pyxel.blt(shake_x + 56, shake_y + 56, 0, 128, 72, 48, 8, 8) # resume + pyxel.blt(shake_x + 52, shake_y + 72, 0, 104, 80, 56, 8, 8) # palette + pyxel.blt(shake_x + 64, shake_y + 88, 0, 96, 64, 32, 8, 8) # quit + + pyxel.blt( + shake_x + self.SELECTIONS[self.sel_index][0]-12, + shake_y + self.SELECTIONS[self.sel_index][1], + 0, + 16, 33, 9, 9, 8 + ) # selection ball left + pyxel.blt( + shake_x + self.SELECTIONS[self.sel_index][0] + self.SELECTIONS[self.sel_index][2] + 2, + shake_y + self.SELECTIONS[self.sel_index][1], + 0, + 16, 33, 9, 9, 8 + ) # selection ball right + +STATE_INTRO = 0 +STATE_PLAY = 1 +STATE_DIED = 2 +STATE_DEMO = 3 +STATE_GAME_OVER = 4 +STATE_STAGE_COMPLETE = 5 +STATE_GAME_COMPLETE = 6 +STATE_PLAYER_WEAPON = 7 + +MAX_SHOW_GAME_OVER_TICKS = 300 # 5 secs +MAX_SHOW_GAME_COMPLETE_TICKS = 300 # 5 secs + +SPAWN_SECTOR_TOPLEFT = 0 +SPAWN_SECTOR_TOPRIGHT = 1 +SPAWN_SECTOR_BOTTOMLEFT = 2 +SPAWN_SECTOR_BOTTOMRIGHT = 3 + +class Stage: + def __init__(self, game, num): + self.game = game + self.num = num + self.tm = 0 + self.tmu = 0 + self.tmv = num * 16 + + self.state = STATE_INTRO + if self.num <= 0: + self.state = STATE_DEMO + elif self.num == MAX_STAGE_NUM + 1: + self.tm = 1 + self.tmu = 0 + self.tmv = 0 + self.state = STATE_GAME_COMPLETE + + self.solid_rects = [ + [0, 0, 160, 16], # [x, y, w, h] + [0, 16, 8, 128], + [152, 16, 8, 128], + [0, 136, 160, 8] + ] + + self.slopes = [] # [x, y] + self.pockets = [] # [x, y, w, h] + self.lights = [] # Light objects + self.spinners = [] # Spinner objects + + self.en_spawn_locs_topleft = [] # [[x,y],[x,y],[x,y]...] + self.en_spawn_locs_topright = [] # [[x,y],[x,y],[x,y]...] + self.en_spawn_locs_bottomleft = [] # [[x,y],[x,y],[x,y]...] + self.en_spawn_locs_bottomright = [] # [[x,y],[x,y],[x,y]...] + + if self.state != STATE_GAME_COMPLETE: + for yc in range(HEIGHT_TILES): + y = self.tmv + yc + for xc in range(WIDTH_TILES): + x = self.tmu + xc + tile = pyxel.tilemaps[self.tm].get(x, y) + if tile == POST_TILE: + self.solid_rects.append([xc*8 + 8, yc*8 + 16, 8, 8]) + elif tile in SLOPE_TILES: + self.slopes.append([xc*8 + 8, yc*8 + 16]) + elif tile == LIGHT_TILE: + self.lights.append(light.Light(xc*8 + 8, yc*8 + 16)) + + if tile == POCKET_TILE_NW: + if x < self.tmu + WIDTH_TILES-1 and y < self.tmv + HEIGHT_TILES-1: + if pyxel.tilemaps[self.tm].get(x+1, y) == POCKET_TILE_NE and\ + pyxel.tilemaps[self.tm].get(x+1, y+1) == POCKET_TILE_SE and\ + pyxel.tilemaps[self.tm].get(x, y+1) == POCKET_TILE_SW: + self.pockets.append([xc*8 + 8, yc*8 + 16, 16, 16]) + + if tile != POST_TILE and \ + xc > 0 and \ + xc < WIDTH_TILES-1 and \ + yc > 0 and \ + yc < HEIGHT_TILES-1 and \ + (xc < 5 or xc > WIDTH_TILES-6) and \ + (yc < 5 or yc > HEIGHT_TILES-6): + + loc = [xc*8 + 8 + 4, yc*8 + 16 + 4] + + if xc < 9: + if yc < 7: + self.en_spawn_locs_topleft.append(loc) + else: + self.en_spawn_locs_bottomleft.append(loc) + else: + if yc < 7: + self.en_spawn_locs_topright.append(loc) + else: + self.en_spawn_locs_bottomright.append(loc) + + #print(self.pockets) + num_spinners = 0 + stage_diff_name = stagedata.STAGE_DIFFICULTY[self.num] + for i in range(len(spinner.TYPES)): + en_qty = stagedata.ENEMIES[stage_diff_name][stagedata.SPINNER_KEY][i] + for sq in range(en_qty): + loc = self.get_random_spawn_loc(-1) + self.spinners.append(spinner.Spinner(loc[0], loc[1], i)) + + self.player = player.Player(75,75)#(12, 20) + if self.state == STATE_GAME_COMPLETE: + self.player.state = player.STATE_GAME_COMPLETE + audio.play_music(audio.MUS_IN_GAME, True) + else: + if self.state != STATE_DEMO: + audio.play_music(audio.MUS_START, False) + + self.pause_menu = PauseMenu(self) + + self.stage_over_ticks = 0 + + self.next_stage_flash_num = 0 + + def restart_music(self): + if self.state == STATE_PLAY: + audio.play_music(audio.MUS_IN_GAME) + + def get_random_spawn_loc(self, sector): + if sector == SPAWN_SECTOR_TOPLEFT: + return random.choice(self.en_spawn_locs_topleft) + elif sector == SPAWN_SECTOR_TOPRIGHT: + return random.choice(self.en_spawn_locs_topright) + elif sector == SPAWN_SECTOR_BOTTOMLEFT: + return random.choice(self.en_spawn_locs_bottomleft) + elif sector == SPAWN_SECTOR_BOTTOMRIGHT: + return random.choice(self.en_spawn_locs_bottomright) + else: + ranlist = random.choice([ + self.en_spawn_locs_topleft, + self.en_spawn_locs_topright, + self.en_spawn_locs_bottomleft, + self.en_spawn_locs_bottomright + ]) + return random.choice(ranlist) + + def player_used_weapon(self): + audio.play_sound(audio.SND_USED_WEAPON) + self.state = STATE_PLAYER_WEAPON + + def player_intro_done(self): + if self.state != STATE_PLAYER_WEAPON: + audio.play_music(audio.MUS_IN_GAME, True) + + self.state = STATE_PLAY + + def player_hit(self): + self.state = STATE_DIED + audio.play_music(audio.MUS_DEATH, False) + + def is_complete(self): + for i in self.lights: + if i.is_hit == False: + return False + + audio.play_music(audio.MUS_STAGE_COMPLETE, False) + self._check_next_stage() + + return True + + def player_death_anim_done(self): + if globals.g_lives >= 1: + globals.g_lives -= 1 + self.game.add_fade(palette.FADE_STEP_TICKS_DEFAULT, + palette.FADE_LEVEL_6, self.game.restart_stage) + else: + self.state = STATE_GAME_OVER + audio.play_music(audio.MUS_GAME_OVER, False) + + def _check_next_stage(self): + #if self.num < MAX_STAGE_NUM: + self.state = STATE_STAGE_COMPLETE + self.game.add_fade(palette.FADE_STEP_TICKS_DEFAULT, + palette.FADE_LEVEL_0, self.go_to_next_stage) + #else: + # self.state = STATE_GAME_COMPLETE + + def go_to_next_stage(self): + if self.next_stage_flash_num == 0: + self.game.add_fade(palette.FADE_STEP_TICKS_SLOW, + palette.FADE_LEVEL_3, self.go_to_next_stage) + elif self.next_stage_flash_num == 1: + self.game.add_fade(palette.FADE_STEP_TICKS_SLOW, + palette.FADE_LEVEL_0, self.go_to_next_stage) + #elif self.next_stage_flash_num == 2: + # self.game.add_fade(palette.FADE_STEP_TICKS_SLOW, + # palette.FADE_LEVEL_3, self.go_to_next_stage) + #elif self.next_stage_flash_num == 3: + # self.game.add_fade(palette.FADE_STEP_TICKS_SLOW, + # palette.FADE_LEVEL_0, self.go_to_next_stage) + else: + if self.num == stage.MAX_STAGE_NUM: + self.game.add_fade(palette.FADE_STEP_TICKS_SLOW, + palette.FADE_LEVEL_6, self.game.go_to_game_complete_stage) + else: + globals.add_lives(1) + self.game.add_fade(palette.FADE_STEP_TICKS_SLOW, + palette.FADE_LEVEL_6, self.game.go_to_next_stage) + + self.next_stage_flash_num += 1 + + def quit(self): + self.game.add_fade(palette.FADE_STEP_TICKS_DEFAULT, + palette.FADE_LEVEL_6, self.game.quit_to_main_menu) + + def player_hit_solid(self): + audio.play_sound(audio.SND_HIT_WALL) + self.game.add_screen_shake(5, 1, queue=False) + + # returns None or angle + def get_tile_angle(self, x, y): # x,y is screen pixels. + tile = pyxel.tilemaps[self.tm].get( + self.tmu + math.floor((x-8)/8), + self.tmv + math.floor((y-16)/8) + ) + + if tile in SLOPE_TILES: + # check if triangle matrix collision check needed. + if SLOPE_TILES[tile][1] is not None: + t = SLOPE_TILES[tile] + + tx = math.floor(abs(x - math.floor(x/8)*8)) + ty = math.floor(abs(y - math.floor(y/8)*8)) + + #print("Checking matrix x,y: {a},{b} ...".format(a=tx, b=ty)) + + if constants.is_colliding_matrix(tx, ty, t[1]): + #print("{a}, {b} hit triangle".format(a=x, b=y)) + #print("... collides.") + return t[0] + else: + #print("... no collision.") + return None + else: + return SLOPE_TILES[tile][0] + else: + return None + + def update(self, last_inputs): + if self.num > 0: # dont allow inputs on demo/main menu stage 0. + if self.pause_menu.is_visible: + self.pause_menu.update(last_inputs) + else: + if input.BUTTON_START in last_inputs.pressed: + if self.state == STATE_PLAY or \ + self.state == STATE_PLAYER_WEAPON: + self.pause_menu.is_visible = True + else: + self.player.update(self, last_inputs) + + if self.state == STATE_PLAY or\ + self.state == STATE_DEMO: + for s in self.spinners: + s.update(self) + + if self.state == STATE_GAME_OVER: + self.stage_over_ticks += 1 + if self.stage_over_ticks == MAX_SHOW_GAME_OVER_TICKS: + self.quit() + elif self.state == STATE_GAME_COMPLETE: + self.stage_over_ticks += 1 + if self.stage_over_ticks >= MAX_SHOW_GAME_COMPLETE_TICKS: + if input.BUTTON_A in last_inputs.pressed: + self.quit() + + if self.state == STATE_PLAY or\ + self.state == STATE_DEMO: + for i in self.lights: + i.update(self) + + def draw(self, shake_x, shake_y): + pyxel.bltm(shake_x + 8, shake_y + 16, self.tm, self.tmu, self.tmv, + WIDTH_TILES, HEIGHT_TILES, 8) + + for i in self.lights: + i.draw(shake_x, shake_y) + + if self.state == STATE_GAME_COMPLETE: + pyxel.blt(24 + shake_x, 32 + shake_y, 0, 136, 136, 112, 88) + + if self.num > 0: + self.player.draw(shake_x, shake_y) + + for s in self.spinners: + s.draw(shake_x, shake_y) + + if self.num > 0: + self.pause_menu.draw(shake_x, shake_y) + + if self.state == STATE_GAME_OVER and self.stage_over_ticks > 30: + pyxel.blt(32 + shake_x, 66 + shake_y, 0, 0, 196, 100, 26, 8) # game over bg + pyxel.blt(44 + shake_x, 72 + shake_y, 0, 40, 80, 32, 8, 8) # "game" + pyxel.blt(84 + shake_x, 72 + shake_y, 0, 72, 80, 32, 8, 8) # "over" + + diff --git a/ports/megaball/megaball/gamedata/stagedata.py b/ports/megaball/megaball/gamedata/stagedata.py new file mode 100644 index 0000000000..c93848b95a --- /dev/null +++ b/ports/megaball/megaball/gamedata/stagedata.py @@ -0,0 +1,69 @@ + +''' +Each difficulty has a dictionary: +easy : { + ... +} + +With that dictionary is another dictionary of quantity of each object type: + +"spinners" : [personalityA, personalityB, ... etc] +''' + +SPINNER_KEY = "spinners" +DIFF_NONE_KEY = "none" +DIFF_VERY_EASY_KEY = "very easy" +DIFF_EASY_KEY = "easy" +DIFF_MEDIUM_KEY = "medium" +DIFF_HARD_KEY = "hard" +DIFF_VERY_HARD_KEY = "very hard" + +#[aggressive, mildly aggressive, slow random, fast random] + +ENEMIES = { + + DIFF_NONE_KEY : { + SPINNER_KEY : [0,0,0,0] + }, + + DIFF_VERY_EASY_KEY : { + SPINNER_KEY : [2,1,1,0]#[1,1,1,0] + }, + + DIFF_EASY_KEY : { + SPINNER_KEY : [2,1,2,0]#[1,1,2,0] + }, + + DIFF_MEDIUM_KEY : { + SPINNER_KEY : [3,1,1,1]#[2,1,1,1] + }, + + DIFF_HARD_KEY : { + SPINNER_KEY : [3,1,1,2]#[2,1,1,2] + }, + + DIFF_VERY_HARD_KEY : { + SPINNER_KEY : [3,2,1,1]#[2,2,1,1] + } + +} + +STAGE_DIFFICULTY = [ + DIFF_NONE_KEY, # 0 + DIFF_VERY_EASY_KEY, # 1 + DIFF_EASY_KEY, # 2 + DIFF_EASY_KEY, # 3 + DIFF_EASY_KEY, # 4 + DIFF_EASY_KEY, # 5 + DIFF_MEDIUM_KEY, # 6 + DIFF_EASY_KEY, # 7 + DIFF_MEDIUM_KEY, # 8 + DIFF_VERY_EASY_KEY, # 9 + DIFF_VERY_HARD_KEY, # 10 + DIFF_HARD_KEY, # 11 + DIFF_VERY_HARD_KEY, # 12 + DIFF_EASY_KEY, # 13 + DIFF_VERY_HARD_KEY, # 14 + DIFF_VERY_HARD_KEY # 15 +] + diff --git a/ports/megaball/megaball/gamedata/utils.py b/ports/megaball/megaball/gamedata/utils.py new file mode 100644 index 0000000000..aa91ba6727 --- /dev/null +++ b/ports/megaball/megaball/gamedata/utils.py @@ -0,0 +1,87 @@ + +import math + +import pyxel + +def angle_reflect(incidenceAngle, surfaceAngle): + a = surfaceAngle * 2 - incidenceAngle + return (a + 360) % 360 + +def sign_triangle(p1, p2, p3): + return (p1[0] - p3[0]) * (p2[1] - p3[1]) - (p2[0] - p3[0]) * (p1[1] - p3[1]) + +def is_point_in_triangle(px, py, ax, ay, bx, by, cx, cy): + #print("Checking point [{a},{b}] in tri [{c}][{d}], [{e}][{f}], [{g}][{h}] ({i})".format( + # a=px, b=py, c=ax, d=ay, e=bx, f=by, g=cx, h=cy, i=pyxel.frame_count + #)) + d1 = sign_triangle([px, py], [ax,ay], [bx,by]) + d2 = sign_triangle([px, py], [bx,by], [cx,cy]) + d3 = sign_triangle([px, py], [cx,cy], [ax,ay]) + + has_neg = (d1 < 0) or (d2 < 0) or (d3 < 0) + has_pos = (d1 > 0) or (d2 > 0) or (d3 > 0) + + #print("return: " + str(not(has_neg and has_pos))) + + return not(has_neg and has_pos) + +def circle_rect_overlap(cx, cy, cr, rx, ry, rw, rh): + closestX = cx + closestY = cy + + if cx < rx: + closestX = rx + elif cx > rx + rw: + closestX = rx + rw + + if cy < ry: + closestY = ry + elif cy > ry + rh: + closestY = ry + rh + + closestX = closestX - cx + closestX *= closestX + closestY = closestY - cy + closestY *= closestY + + return closestX + closestY < cr * cr + +def get_angle_deg(x1, y1, x2, y2): + degs = math.degrees(math.atan2(y2 - y1, x2 - x1)) + return (degs + 360) % 360 + +def get_tile_x(index): + return math.floor(index % 32) * 8 + +def get_tile_y(index): + return math.floor(index / 32) * 8 + +def get_tile_index(x, y): + return x/8 + (y / 8) * 32 + +def lerp(v, d): + #print("delta: " + str(d) + ", v: " + str(v[0]) + "," + str(v[1])) + #print() + return (v[0] * (1.0 - d)) + (v[1] * d) + +def ease_out_expo(x): + if x == 1: + return 1 + + return 1 - math.pow(2, -10 * x) + +def ease_out_cubic(x): + return 1 - math.pow(1 - x, 3) + +def draw_number_shadowed(x, y, num, zeropad=0): + strnum = str(num) + if zeropad > 0: + strnum = strnum.zfill(zeropad) + + for i in range(len(strnum)): + pyxel.blt(x + i*8, y, 0, 16 + int(strnum[i])*8, 56, 8, 8, 8) + + + + + \ No newline at end of file diff --git a/ports/megaball/megaball/gamedata/weapon.py b/ports/megaball/megaball/gamedata/weapon.py new file mode 100644 index 0000000000..df2f43b360 --- /dev/null +++ b/ports/megaball/megaball/gamedata/weapon.py @@ -0,0 +1,81 @@ + +import math + +import pyxel + +import rect +import constants +import player +import stage +import spinner +import circle +import globals + +MAX_SHOTS = 10 +SHOT_RADIUS = 3 +SHOT_SPEED = 1.5 + +VEL = [] +for i in range(MAX_SHOTS): + VEL.append( + [ + SHOT_SPEED * math.cos(math.radians(i*36)), + SHOT_SPEED * math.sin(math.radians(i*36)), + ] + ) + +class Weapon: + def __init__(self): + self.active = False + + self.shots = [] + for i in range(MAX_SHOTS): + self.shots.append([0,0]) + + def fire(self, from_x, from_y): + self.active = True + for s in self.shots: + s[0] = from_x + s[1] = from_y + + def update(self, player, stage): + if not self.active: + return + + done = True + + for i, s in enumerate(self.shots): + s[0] += VEL[i][0] + s[1] += VEL[i][1] + + if done != False and\ + rect.contains_point(0, 0, + constants.GAME_WIDTH, constants.GAME_HEIGHT, + s[0], s[1]): + done = False + + for spin in stage.spinners: + if not spin.is_dead: + if circle.overlap( + s[0], s[1], SHOT_RADIUS, + spin.x, spin.y, spin.radius): + globals.add_score(globals.SCORE_KILLED_SPINNER) + spin.kill() + + if done: + spinners_killed = sum(s.is_dead == True for s in stage.spinners) + if spinners_killed == len(stage.spinners): + globals.add_score(globals.SCORE_KILLED_ALL_SPINNERS) + self.active = False + player.weapon_done() + + def draw(self, shake_x, shake_y): + if not self.active: + return + + for s in self.shots: + pyxel.blt(shake_x + s[0] - 10, + shake_y + s[1] - 10, + 0, 21, 231, 21, 21, 8) + + \ No newline at end of file diff --git a/ports/megaball/megaball/licenses/LICENSE.megaball.txt b/ports/megaball/megaball/licenses/LICENSE.megaball.txt new file mode 100644 index 0000000000..683638cecb --- /dev/null +++ b/ports/megaball/megaball/licenses/LICENSE.megaball.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 helpcomputer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From e7408a939472dc860de204b440d06c51ab180316 Mon Sep 17 00:00:00 2001 From: tabreturn Date: Sun, 15 Dec 2024 18:50:01 +1100 Subject: [PATCH 02/13] - ignore pycache --- ports/megaball/megaball/gamedata/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 ports/megaball/megaball/gamedata/.gitignore diff --git a/ports/megaball/megaball/gamedata/.gitignore b/ports/megaball/megaball/gamedata/.gitignore new file mode 100644 index 0000000000..c18dd8d83c --- /dev/null +++ b/ports/megaball/megaball/gamedata/.gitignore @@ -0,0 +1 @@ +__pycache__/ From 2c4458ce32577155933791ef277073b0312ef363 Mon Sep 17 00:00:00 2001 From: tabreturn Date: Sun, 15 Dec 2024 20:06:37 +1100 Subject: [PATCH 03/13] - getting nowhere --- ports/megaball/megaball/gamedata/stage.py | 114 ++++++++++++++-------- 1 file changed, 74 insertions(+), 40 deletions(-) diff --git a/ports/megaball/megaball/gamedata/stage.py b/ports/megaball/megaball/gamedata/stage.py index cac3d4992f..37e92f2436 100644 --- a/ports/megaball/megaball/gamedata/stage.py +++ b/ports/megaball/megaball/gamedata/stage.py @@ -21,37 +21,14 @@ - - - - +TILEMAP_SCALE = 8 +# Mokeypatch in get() from old API def custom_get_tile(self, x, y): - """ - Recreates the `.get()` method using Pyxel's official API. - - Args: - x (int): The x-coordinate in pixel space. - y (int): The y-coordinate in pixel space. - - Returns: - int: The tile index at the given coordinates, or None if out of bounds. - """ - TILE_SIZE = 8 # Size of each tile in pixels - - # Convert pixel-based coordinates to tile indices - tile_x = x // 8 - tile_y = y // 8 - - # Ensure the tile indices are within bounds + tile_x = x // TILEMAP_SCALE + tile_y = y // TILEMAP_SCALE if 0 <= tile_x < self.width and 0 <= tile_y < self.height: - # Use .pget() to fetch the tile index return self.pget(tile_x, tile_y) - return None # Return None for out-of-bounds coordinates - - - - - + return None pyxel.Tilemap.get = custom_get_tile @@ -192,7 +169,7 @@ def __init__(self, game, num): self.num = num self.tm = 0 self.tmu = 0 - self.tmv = num * 16 + self.tmv = num * 16 *TILEMAP_SCALE self.state = STATE_INTRO if self.num <= 0: @@ -221,18 +198,33 @@ def __init__(self, game, num): self.en_spawn_locs_bottomright = [] # [[x,y],[x,y],[x,y]...] if self.state != STATE_GAME_COMPLETE: - for yc in range(HEIGHT_TILES): + for yc in range(0, HEIGHT_TILES*TILEMAP_SCALE): y = self.tmv + yc - for xc in range(WIDTH_TILES): + for xc in range(WIDTH_TILES*TILEMAP_SCALE): x = self.tmu + xc tile = pyxel.tilemaps[self.tm].get(x, y) - if tile == POST_TILE: - self.solid_rects.append([xc*8 + 8, yc*8 + 16, 8, 8]) - elif tile in SLOPE_TILES: - self.slopes.append([xc*8 + 8, yc*8 + 16]) - elif tile == LIGHT_TILE: - self.lights.append(light.Light(xc*8 + 8, yc*8 + 16)) - + + + #print(f"Tile at ({x}, {y}): {tile}") + + tile_index = tile[1] * TILEMAP_SCALE + tile[0] + #print(f"Tile index at ({x}, {y}): {tile_index}") + + + if tile_index == POST_TILE: #if tile == POST_TILE: + #self.solid_rects.append([xc*8 + 8, yc*8 + 16, 8, 8]) + self.solid_rects.append([xc*8//TILEMAP_SCALE + 8, yc*8//TILEMAP_SCALE + 16, 8, 8]) + elif tile_index in SLOPE_TILES: #if tile == SLOPE_TILES: + #self.slopes.append([xc*8 + 8, yc*8 + 16]) + self.slopes.append([xc*8//TILEMAP_SCALE + 8, yc*8//TILEMAP_SCALE + 16]) + elif tile_index == LIGHT_TILE: #if tile == LIGHT_TILE: + #self.lights.append(light.Light(xc*8 + 8, yc*8 + 16)) + self.lights.append(light.Light(xc*8//TILEMAP_SCALE + 8, yc*8//TILEMAP_SCALE + 16)) + + + + + if tile == POCKET_TILE_NW: if x < self.tmu + WIDTH_TILES-1 and y < self.tmv + HEIGHT_TILES-1: if pyxel.tilemaps[self.tm].get(x+1, y) == POCKET_TILE_NE and\ @@ -442,7 +434,7 @@ def update(self, last_inputs): def draw(self, shake_x, shake_y): pyxel.bltm(shake_x + 8, shake_y + 16, self.tm, self.tmu, self.tmv, - WIDTH_TILES, HEIGHT_TILES, 8) + WIDTH_TILES *TILEMAP_SCALE, HEIGHT_TILES *TILEMAP_SCALE, 8) for i in self.lights: i.draw(shake_x, shake_y) @@ -464,4 +456,46 @@ def draw(self, shake_x, shake_y): pyxel.blt(44 + shake_x, 72 + shake_y, 0, 40, 80, 32, 8, 8) # "game" pyxel.blt(84 + shake_x, 72 + shake_y, 0, 72, 80, 32, 8, 8) # "over" - + + + + + + pyxel.rectb(24, 32, 8, 8, pyxel.COLOR_RED) + #print(self.lights) + + #print(f"Lights: {self.solid_rects}") + #print(f"Slopes: {self.slopes}") + #print(f"Pockets: {self.lights}") + + for slope in self.slopes: + #continue + x = slope.x # Use the correct attribute names + y = slope.y + #w = getattr(light, "width", 8) # Default width if attribute not found + #h = getattr(light, "height", 8) # Default height if attribute not found + # Draw rectangle outline with lines + pyxel.pset(shake_x + x, shake_y + y, pyxel.COLOR_GREEN) + + for solid_rect in self.solid_rects: + #continue + x, y, w, h = solid_rect # Extract values from the list + #w = getattr(light, "width", 8) # Default width if attribute not found + #h = getattr(light, "height", 8) # Default height if attribute not found + # Draw rectangle outline with lines + pyxel.pset(shake_x + x, shake_y + y, pyxel.COLOR_YELLOW) + + for light in self.lights: + x = light.x # Use the correct attribute names + y = light.y + #w = getattr(light, "width", 8) # Default width if attribute not found + #h = getattr(light, "height", 8) # Default height if attribute not found + # Draw rectangle outline with lines + pyxel.pset(shake_x + x, shake_y + y, pyxel.COLOR_RED) + + + + + + + From 3a44483d25f4e40e7290a00b447280b8a096821d Mon Sep 17 00:00:00 2001 From: tabreturn Date: Sun, 15 Dec 2024 20:48:54 +1100 Subject: [PATCH 04/13] - making some progress --- ports/megaball/megaball/gamedata/stage.py | 73 ++++++++++++++--------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/ports/megaball/megaball/gamedata/stage.py b/ports/megaball/megaball/gamedata/stage.py index 37e92f2436..6e04baa72c 100644 --- a/ports/megaball/megaball/gamedata/stage.py +++ b/ports/megaball/megaball/gamedata/stage.py @@ -192,15 +192,42 @@ def __init__(self, game, num): self.lights = [] # Light objects self.spinners = [] # Spinner objects - self.en_spawn_locs_topleft = [] # [[x,y],[x,y],[x,y]...] - self.en_spawn_locs_topright = [] # [[x,y],[x,y],[x,y]...] - self.en_spawn_locs_bottomleft = [] # [[x,y],[x,y],[x,y]...] - self.en_spawn_locs_bottomright = [] # [[x,y],[x,y],[x,y]...] + #self.en_spawn_locs_topleft = [] # [[x,y],[x,y],[x,y]...] + #self.en_spawn_locs_topright = [] # [[x,y],[x,y],[x,y]...] + #self.en_spawn_locs_bottomleft = [] # [[x,y],[x,y],[x,y]...] + #self.en_spawn_locs_bottomright = [] # [[x,y],[x,y],[x,y]...] + self.en_spawn_locs_topleft = [ + [12, 20], [20, 20], [28, 20], [36, 20], [44, 20], + [12, 28], [20, 28], [28, 28], [36, 28], [44, 28], + [12, 36], [20, 36], [28, 36], [36, 36], [44, 36], + [12, 44], [20, 44], [28, 44], [36, 44], [44, 44] + ] + self.en_spawn_locs_topright = [ + [92, 20], [100, 20], [108, 20], [116, 20], [124, 20], + [92, 28], [100, 28], [108, 28], [116, 28], [124, 28], + [92, 36], [100, 36], [108, 36], [116, 36], [124, 36], + [92, 44], [100, 44], [108, 44], [116, 44], [124, 44] + ] + self.en_spawn_locs_bottomleft = [ + [12, 76], [20, 76], [28, 76], [36, 76], [44, 76], + [12, 84], [20, 84], [28, 84], [36, 84], [44, 84], + [12, 92], [20, 92], [28, 92], [36, 92], [44, 92], + [12, 100], [20, 100], [28, 100], [36, 100], [44, 100] + ] + self.en_spawn_locs_bottomright = [ + [92, 76], [100, 76], [108, 76], [116, 76], [124, 76], + [92, 84], [100, 84], [108, 84], [116, 84], [124, 84], + [92, 92], [100, 92], [108, 92], [116, 92], [124, 92], + [92, 100], [100, 100], [108, 100], [116, 100], [124, 100] + ] + + + if self.state != STATE_GAME_COMPLETE: - for yc in range(0, HEIGHT_TILES*TILEMAP_SCALE): + for yc in range(0, HEIGHT_TILES *TILEMAP_SCALE, TILEMAP_SCALE): y = self.tmv + yc - for xc in range(WIDTH_TILES*TILEMAP_SCALE): + for xc in range(0, WIDTH_TILES *TILEMAP_SCALE, TILEMAP_SCALE): x = self.tmu + xc tile = pyxel.tilemaps[self.tm].get(x, y) @@ -461,37 +488,25 @@ def draw(self, shake_x, shake_y): - pyxel.rectb(24, 32, 8, 8, pyxel.COLOR_RED) - #print(self.lights) - - #print(f"Lights: {self.solid_rects}") - #print(f"Slopes: {self.slopes}") - #print(f"Pockets: {self.lights}") + #pyxel.rectb(24, 32, 8, 8, pyxel.COLOR_RED) + print(self.lights) + print(f"Solidrects: {self.solid_rects}") + print(f"Slopes: {self.slopes}") + print(f"Lights: {self.lights}") for slope in self.slopes: - #continue - x = slope.x # Use the correct attribute names + x = slope.x y = slope.y - #w = getattr(light, "width", 8) # Default width if attribute not found - #h = getattr(light, "height", 8) # Default height if attribute not found - # Draw rectangle outline with lines - pyxel.pset(shake_x + x, shake_y + y, pyxel.COLOR_GREEN) + pyxel.rectb(shake_x + x, shake_y + y, TILEMAP_SCALE, TILEMAP_SCALE, pyxel.COLOR_GREEN) for solid_rect in self.solid_rects: - #continue - x, y, w, h = solid_rect # Extract values from the list - #w = getattr(light, "width", 8) # Default width if attribute not found - #h = getattr(light, "height", 8) # Default height if attribute not found - # Draw rectangle outline with lines - pyxel.pset(shake_x + x, shake_y + y, pyxel.COLOR_YELLOW) + x, y, w, h = solid_rect + pyxel.rectb(shake_x + x, shake_y + y, TILEMAP_SCALE, TILEMAP_SCALE, pyxel.COLOR_YELLOW) for light in self.lights: - x = light.x # Use the correct attribute names + x = light.x y = light.y - #w = getattr(light, "width", 8) # Default width if attribute not found - #h = getattr(light, "height", 8) # Default height if attribute not found - # Draw rectangle outline with lines - pyxel.pset(shake_x + x, shake_y + y, pyxel.COLOR_RED) + pyxel.rectb(shake_x + x, shake_y + y, TILEMAP_SCALE, TILEMAP_SCALE, pyxel.COLOR_RED) From 58479ff2c30a7dd4dba116ac665ee44265641bea Mon Sep 17 00:00:00 2001 From: tabreturn Date: Mon, 16 Dec 2024 20:51:02 +1100 Subject: [PATCH 05/13] - breakthrough! found post tiles index --- ports/megaball/megaball/gamedata/stage.py | 50 ++++++++--------------- 1 file changed, 16 insertions(+), 34 deletions(-) diff --git a/ports/megaball/megaball/gamedata/stage.py b/ports/megaball/megaball/gamedata/stage.py index 6e04baa72c..ac5fecab15 100644 --- a/ports/megaball/megaball/gamedata/stage.py +++ b/ports/megaball/megaball/gamedata/stage.py @@ -17,12 +17,8 @@ import stagedata import audio - - - - TILEMAP_SCALE = 8 -# Mokeypatch in get() from old API +# Mokeypatch in get() to simulate old tilemap API def custom_get_tile(self, x, y): tile_x = x // TILEMAP_SCALE tile_y = y // TILEMAP_SCALE @@ -31,21 +27,12 @@ def custom_get_tile(self, x, y): return None pyxel.Tilemap.get = custom_get_tile - - - - - - - - - MAX_STAGE_NUM = 15 WIDTH_TILES = 18 HEIGHT_TILES = 15 -POST_TILE = utils.get_tile_index(40, 32) +POST_TILE = 37 #utils.get_tile_index(40, 32) # It's 37 now (not 133), no idea why ?! # tile_index : [angle, collision matrix if triangle] SLOPE_TILES = { @@ -196,6 +183,8 @@ def __init__(self, game, num): #self.en_spawn_locs_topright = [] # [[x,y],[x,y],[x,y]...] #self.en_spawn_locs_bottomleft = [] # [[x,y],[x,y],[x,y]...] #self.en_spawn_locs_bottomright = [] # [[x,y],[x,y],[x,y]...] + + # Hardcode spawn locations (becuase they stopped working) self.en_spawn_locs_topleft = [ [12, 20], [20, 20], [28, 20], [36, 20], [44, 20], [12, 28], [20, 28], [28, 28], [36, 28], [44, 28], @@ -221,9 +210,6 @@ def __init__(self, game, num): [92, 100], [100, 100], [108, 100], [116, 100], [124, 100] ] - - - if self.state != STATE_GAME_COMPLETE: for yc in range(0, HEIGHT_TILES *TILEMAP_SCALE, TILEMAP_SCALE): y = self.tmv + yc @@ -234,8 +220,13 @@ def __init__(self, game, num): #print(f"Tile at ({x}, {y}): {tile}") + + tile_index = tile[1] * TILEMAP_SCALE + tile[0] #print(f"Tile index at ({x}, {y}): {tile_index}") + print(f"POST_TILE index: {POST_TILE}") + print(f"SLOPE_TILES indices: {list(SLOPE_TILES.keys())}") + print(f"LIGHT_TILE index: {LIGHT_TILE}") if tile_index == POST_TILE: #if tile == POST_TILE: @@ -483,26 +474,17 @@ def draw(self, shake_x, shake_y): pyxel.blt(44 + shake_x, 72 + shake_y, 0, 40, 80, 32, 8, 8) # "game" pyxel.blt(84 + shake_x, 72 + shake_y, 0, 72, 80, 32, 8, 8) # "over" - - - - - - #pyxel.rectb(24, 32, 8, 8, pyxel.COLOR_RED) - print(self.lights) - print(f"Solidrects: {self.solid_rects}") - print(f"Slopes: {self.slopes}") - print(f"Lights: {self.lights}") - + # DEBUGGING + #print(f"Solidrects: {self.solid_rects}") + #print(f"Slopes: {self.slopes}") + #print(f"Lights: {self.lights}") for slope in self.slopes: - x = slope.x - y = slope.y - pyxel.rectb(shake_x + x, shake_y + y, TILEMAP_SCALE, TILEMAP_SCALE, pyxel.COLOR_GREEN) - + x = slope[0] + y = slope[1] + pyxel.rectb(shake_x + x, shake_y + y, TILEMAP_SCALE, TILEMAP_SCALE, pyxel.COLOR_BLACK) for solid_rect in self.solid_rects: x, y, w, h = solid_rect pyxel.rectb(shake_x + x, shake_y + y, TILEMAP_SCALE, TILEMAP_SCALE, pyxel.COLOR_YELLOW) - for light in self.lights: x = light.x y = light.y From 6ae3c711117dec7b9eddcde4822186aedb4fa88e Mon Sep 17 00:00:00 2001 From: tabreturn Date: Mon, 16 Dec 2024 21:28:34 +1100 Subject: [PATCH 06/13] - reindex slope tiles --- ports/megaball/megaball/gamedata/stage.py | 42 ++++++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/ports/megaball/megaball/gamedata/stage.py b/ports/megaball/megaball/gamedata/stage.py index ac5fecab15..37afbb22b5 100644 --- a/ports/megaball/megaball/gamedata/stage.py +++ b/ports/megaball/megaball/gamedata/stage.py @@ -32,9 +32,10 @@ def custom_get_tile(self, x, y): WIDTH_TILES = 18 HEIGHT_TILES = 15 -POST_TILE = 37 #utils.get_tile_index(40, 32) # It's 37 now (not 133), no idea why ?! +POST_TILE = 37 # utils.get_tile_index(40, 32) # tile_index : [angle, collision matrix if triangle] +''' SLOPE_TILES = { utils.get_tile_index(56,32): [225, constants.COLLIDE_BOTTOM_RIGHT], # top-left utils.get_tile_index(64,32): [270, None], # top @@ -50,6 +51,22 @@ def custom_get_tile(self, x, y): utils.get_tile_index(88,40): [315, constants.COLLIDE_TOP_RIGHT] # top-right 2 #utils.get_tile_index(), } +''' +SLOPE_TILES = { + 39: [225, constants.COLLIDE_BOTTOM_RIGHT], # top-left + 40: [270, None], # top + 41: [315, constants.COLLIDE_BOTTOM_LEFT], # top-right + 47: [180, None], # left + 49: [0, None], # right + 55: [135, constants.COLLIDE_TOP_RIGHT], # bottom-left + 56: [90, None], # bottom + 57: [45, constants.COLLIDE_TOP_LEFT], # bottom-right + 42: [225, constants.COLLIDE_TOP_LEFT], # top-left 2 + 43: [135, constants.COLLIDE_BOTTOM_LEFT], # bottom-left 2 + 50: [45, constants.COLLIDE_BOTTOM_RIGHT], # bottom-right 2 + 51: [315, constants.COLLIDE_TOP_RIGHT] # top-right 2 + #utils.get_tile_index(), +} POCKET_TILE_NW = utils.get_tile_index(80,40) POCKET_TILE_NE = utils.get_tile_index(88,32) @@ -239,8 +256,23 @@ def __init__(self, game, num): #self.lights.append(light.Light(xc*8 + 8, yc*8 + 16)) self.lights.append(light.Light(xc*8//TILEMAP_SCALE + 8, yc*8//TILEMAP_SCALE + 16)) - - + ''' + SLOPE_TILES = { + 39: [225, constants.COLLIDE_BOTTOM_RIGHT], # top-left + 40: [270, None], # top + 41: [315, constants.COLLIDE_BOTTOM_LEFT], # top-right + 47: [180, None], # left + 49: [0, None], # right + 55: [135, constants.COLLIDE_TOP_RIGHT], # bottom-left + 56: [90, None], # bottom + 57: [45, constants.COLLIDE_TOP_LEFT], # bottom-right + 42: [225, constants.COLLIDE_TOP_LEFT], # top-left 2 + 43: [135, constants.COLLIDE_BOTTOM_LEFT], # bottom-left 2 + 50: [45, constants.COLLIDE_BOTTOM_RIGHT], # bottom-right 2 + 51: [315, constants.COLLIDE_TOP_RIGHT] # top-right 2 + #utils.get_tile_index(), + } + ''' if tile == POCKET_TILE_NW: @@ -481,10 +513,10 @@ def draw(self, shake_x, shake_y): for slope in self.slopes: x = slope[0] y = slope[1] - pyxel.rectb(shake_x + x, shake_y + y, TILEMAP_SCALE, TILEMAP_SCALE, pyxel.COLOR_BLACK) + pyxel.rectb(shake_x + x, shake_y + y, TILEMAP_SCALE, TILEMAP_SCALE, pyxel.COLOR_YELLOW) for solid_rect in self.solid_rects: x, y, w, h = solid_rect - pyxel.rectb(shake_x + x, shake_y + y, TILEMAP_SCALE, TILEMAP_SCALE, pyxel.COLOR_YELLOW) + pyxel.rectb(shake_x + x, shake_y + y, TILEMAP_SCALE, TILEMAP_SCALE, pyxel.COLOR_BLACK) for light in self.lights: x = light.x y = light.y From ed85ecfcd0798d6d61ddbb37ef702817d5027849 Mon Sep 17 00:00:00 2001 From: tabreturn Date: Mon, 16 Dec 2024 21:45:37 +1100 Subject: [PATCH 07/13] - wip (slopes physics) --- ports/megaball/megaball/gamedata/stage.py | 60 +++++------------------ 1 file changed, 13 insertions(+), 47 deletions(-) diff --git a/ports/megaball/megaball/gamedata/stage.py b/ports/megaball/megaball/gamedata/stage.py index 37afbb22b5..7c8aa3455f 100644 --- a/ports/megaball/megaball/gamedata/stage.py +++ b/ports/megaball/megaball/gamedata/stage.py @@ -233,18 +233,7 @@ def __init__(self, game, num): for xc in range(0, WIDTH_TILES *TILEMAP_SCALE, TILEMAP_SCALE): x = self.tmu + xc tile = pyxel.tilemaps[self.tm].get(x, y) - - - #print(f"Tile at ({x}, {y}): {tile}") - - - tile_index = tile[1] * TILEMAP_SCALE + tile[0] - #print(f"Tile index at ({x}, {y}): {tile_index}") - print(f"POST_TILE index: {POST_TILE}") - print(f"SLOPE_TILES indices: {list(SLOPE_TILES.keys())}") - print(f"LIGHT_TILE index: {LIGHT_TILE}") - if tile_index == POST_TILE: #if tile == POST_TILE: #self.solid_rects.append([xc*8 + 8, yc*8 + 16, 8, 8]) @@ -256,25 +245,6 @@ def __init__(self, game, num): #self.lights.append(light.Light(xc*8 + 8, yc*8 + 16)) self.lights.append(light.Light(xc*8//TILEMAP_SCALE + 8, yc*8//TILEMAP_SCALE + 16)) - ''' - SLOPE_TILES = { - 39: [225, constants.COLLIDE_BOTTOM_RIGHT], # top-left - 40: [270, None], # top - 41: [315, constants.COLLIDE_BOTTOM_LEFT], # top-right - 47: [180, None], # left - 49: [0, None], # right - 55: [135, constants.COLLIDE_TOP_RIGHT], # bottom-left - 56: [90, None], # bottom - 57: [45, constants.COLLIDE_TOP_LEFT], # bottom-right - 42: [225, constants.COLLIDE_TOP_LEFT], # top-left 2 - 43: [135, constants.COLLIDE_BOTTOM_LEFT], # bottom-left 2 - 50: [45, constants.COLLIDE_BOTTOM_RIGHT], # bottom-right 2 - 51: [315, constants.COLLIDE_TOP_RIGHT] # top-right 2 - #utils.get_tile_index(), - } - ''' - - if tile == POCKET_TILE_NW: if x < self.tmu + WIDTH_TILES-1 and y < self.tmv + HEIGHT_TILES-1: if pyxel.tilemaps[self.tm].get(x+1, y) == POCKET_TILE_NE and\ @@ -423,7 +393,7 @@ def player_hit_solid(self): # returns None or angle def get_tile_angle(self, x, y): # x,y is screen pixels. - tile = pyxel.tilemaps[self.tm].get( + tile = pyxel.tilemap(self.tm).get( self.tmu + math.floor((x-8)/8), self.tmv + math.floor((y-16)/8) ) @@ -436,18 +406,20 @@ def get_tile_angle(self, x, y): # x,y is screen pixels. tx = math.floor(abs(x - math.floor(x/8)*8)) ty = math.floor(abs(y - math.floor(y/8)*8)) - #print("Checking matrix x,y: {a},{b} ...".format(a=tx, b=ty)) + print("Checking matrix x,y: {a},{b} ...".format(a=tx, b=ty)) if constants.is_colliding_matrix(tx, ty, t[1]): - #print("{a}, {b} hit triangle".format(a=x, b=y)) - #print("... collides.") + print("{a}, {b} hit triangle".format(a=x, b=y)) + print("... collides.") return t[0] else: - #print("... no collision.") + print("... no collision.") return None else: + print(SLOPE_TILES[tile][0]) return SLOPE_TILES[tile][0] else: + print('None') return None def update(self, last_inputs): @@ -507,24 +479,18 @@ def draw(self, shake_x, shake_y): pyxel.blt(84 + shake_x, 72 + shake_y, 0, 72, 80, 32, 8, 8) # "over" # DEBUGGING - #print(f"Solidrects: {self.solid_rects}") - #print(f"Slopes: {self.slopes}") - #print(f"Lights: {self.lights}") + + for solid_rect in self.solid_rects: + x, y, w, h = solid_rect + pyxel.rectb(shake_x + x, shake_y + y, TILEMAP_SCALE, TILEMAP_SCALE, pyxel.COLOR_BLACK) + for slope in self.slopes: x = slope[0] y = slope[1] pyxel.rectb(shake_x + x, shake_y + y, TILEMAP_SCALE, TILEMAP_SCALE, pyxel.COLOR_YELLOW) - for solid_rect in self.solid_rects: - x, y, w, h = solid_rect - pyxel.rectb(shake_x + x, shake_y + y, TILEMAP_SCALE, TILEMAP_SCALE, pyxel.COLOR_BLACK) + for light in self.lights: x = light.x y = light.y pyxel.rectb(shake_x + x, shake_y + y, TILEMAP_SCALE, TILEMAP_SCALE, pyxel.COLOR_RED) - - - - - - From b3460bb8f5cfbfb7ea37767a75f7202cdefa7e28 Mon Sep 17 00:00:00 2001 From: tabreturn Date: Tue, 17 Dec 2024 16:09:09 +1100 Subject: [PATCH 08/13] - add get controls --- ports/megaball/Megaball.sh | 2 +- ports/megaball/megaball/gamedata/stage.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ports/megaball/Megaball.sh b/ports/megaball/Megaball.sh index cd633ac895..8057f547e4 100755 --- a/ports/megaball/Megaball.sh +++ b/ports/megaball/Megaball.sh @@ -13,8 +13,8 @@ else fi source $controlfolder/control.txt - [ -f "${controlfolder}/mod_${CFW_NAME}.txt" ] && source "${controlfolder}/mod_${CFW_NAME}.txt" +get_controls GAMEDIR="/$directory/ports/megaball" CONFDIR="$GAMEDIR/conf" diff --git a/ports/megaball/megaball/gamedata/stage.py b/ports/megaball/megaball/gamedata/stage.py index 7c8aa3455f..181bdbd726 100644 --- a/ports/megaball/megaball/gamedata/stage.py +++ b/ports/megaball/megaball/gamedata/stage.py @@ -68,11 +68,15 @@ def custom_get_tile(self, x, y): #utils.get_tile_index(), } +######################################### TODO: ASSIGN POCKET TILE INDECES + POCKET_TILE_NW = utils.get_tile_index(80,40) POCKET_TILE_NE = utils.get_tile_index(88,32) POCKET_TILE_SE = utils.get_tile_index(80,32) POCKET_TILE_SW = utils.get_tile_index(88,40) +######################################### TODO: CONFIRM THESE TILE INDECES + LIGHT_TILE = utils.get_tile_index(160,0) BLANK_TILE = utils.get_tile_index(32,24) From 29926fbbbf798cecfec0dec05753fb9f301d57cb Mon Sep 17 00:00:00 2001 From: tabreturn Date: Tue, 17 Dec 2024 20:38:55 +1100 Subject: [PATCH 09/13] - hardcode indices for light and blank tiles --- ports/megaball/megaball/gamedata/stage.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/ports/megaball/megaball/gamedata/stage.py b/ports/megaball/megaball/gamedata/stage.py index 181bdbd726..716d4de12d 100644 --- a/ports/megaball/megaball/gamedata/stage.py +++ b/ports/megaball/megaball/gamedata/stage.py @@ -68,17 +68,13 @@ def custom_get_tile(self, x, y): #utils.get_tile_index(), } -######################################### TODO: ASSIGN POCKET TILE INDECES +POCKET_TILE_NW = 50 #utils.get_tile_index(80,40) +POCKET_TILE_NE = 43 #utils.get_tile_index(88,32) +POCKET_TILE_SE = 42 #utils.get_tile_index(80,32) +POCKET_TILE_SW = 51 #utils.get_tile_index(88,40) -POCKET_TILE_NW = utils.get_tile_index(80,40) -POCKET_TILE_NE = utils.get_tile_index(88,32) -POCKET_TILE_SE = utils.get_tile_index(80,32) -POCKET_TILE_SW = utils.get_tile_index(88,40) - -######################################### TODO: CONFIRM THESE TILE INDECES - -LIGHT_TILE = utils.get_tile_index(160,0) -BLANK_TILE = utils.get_tile_index(32,24) +LIGHT_TILE = 20 #utils.get_tile_index(160,0) +BLANK_TILE = 100 #utils.get_tile_index(32,24) class PauseMenu: From 859a2ba3274bd7257a65be2721e10ca79b3c533b Mon Sep 17 00:00:00 2001 From: tabreturn Date: Tue, 17 Dec 2024 21:05:46 +1100 Subject: [PATCH 10/13] - complete adaption --- ports/megaball/megaball/gamedata/stage.py | 49 ++++++++++++----------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/ports/megaball/megaball/gamedata/stage.py b/ports/megaball/megaball/gamedata/stage.py index 716d4de12d..660c2c0337 100644 --- a/ports/megaball/megaball/gamedata/stage.py +++ b/ports/megaball/megaball/gamedata/stage.py @@ -392,36 +392,37 @@ def player_hit_solid(self): self.game.add_screen_shake(5, 1, queue=False) # returns None or angle - def get_tile_angle(self, x, y): # x,y is screen pixels. - tile = pyxel.tilemap(self.tm).get( - self.tmu + math.floor((x-8)/8), - self.tmv + math.floor((y-16)/8) + def get_tile_angle(self, x, y): # x, y is screen pixels + tile = pyxel.tilemaps[self.tm].get( + self.tmu + math.floor((x - 8) / 8 * TILEMAP_SCALE), + self.tmv + math.floor((y - 16) / 8 * TILEMAP_SCALE) ) - - if tile in SLOPE_TILES: - # check if triangle matrix collision check needed. - if SLOPE_TILES[tile][1] is not None: - t = SLOPE_TILES[tile] - - tx = math.floor(abs(x - math.floor(x/8)*8)) - ty = math.floor(abs(y - math.floor(y/8)*8)) - - print("Checking matrix x,y: {a},{b} ...".format(a=tx, b=ty)) - + tile_index = tile[1] * TILEMAP_SCALE + tile[0] + #print(tile_index) + + if tile_index in SLOPE_TILES: + # Check if triangle matrix collision is needed + if SLOPE_TILES[tile_index][1] is not None: + t = SLOPE_TILES[tile_index] + + tx = math.floor(abs(x - math.floor(x / 8) * 8 * TILEMAP_SCALE)) + ty = math.floor(abs(y - math.floor(y / 8) * 8 * TILEMAP_SCALE)) + + #print(f"Checking matrix x,y: {tx},{ty} ...") + if constants.is_colliding_matrix(tx, ty, t[1]): - print("{a}, {b} hit triangle".format(a=x, b=y)) - print("... collides.") + #print(f"{x}, {y} hit triangle") + #print("... collides.") return t[0] else: - print("... no collision.") + #print("... no collision.") return None else: - print(SLOPE_TILES[tile][0]) - return SLOPE_TILES[tile][0] + #print(SLOPE_TILES[tile_index][0]) + return SLOPE_TILES[tile_index][0] else: - print('None') return None - + def update(self, last_inputs): if self.num > 0: # dont allow inputs on demo/main menu stage 0. if self.pause_menu.is_visible: @@ -479,7 +480,7 @@ def draw(self, shake_x, shake_y): pyxel.blt(84 + shake_x, 72 + shake_y, 0, 72, 80, 32, 8, 8) # "over" # DEBUGGING - + ''' for solid_rect in self.solid_rects: x, y, w, h = solid_rect pyxel.rectb(shake_x + x, shake_y + y, TILEMAP_SCALE, TILEMAP_SCALE, pyxel.COLOR_BLACK) @@ -493,4 +494,4 @@ def draw(self, shake_x, shake_y): x = light.x y = light.y pyxel.rectb(shake_x + x, shake_y + y, TILEMAP_SCALE, TILEMAP_SCALE, pyxel.COLOR_RED) - + ''' From aa27b42661873546f32f30695ef369f3fd83298a Mon Sep 17 00:00:00 2001 From: tabreturn Date: Tue, 17 Dec 2024 23:13:16 +1100 Subject: [PATCH 11/13] - add metadata --- ports/megaball/README.md | 29 +++++++++++++++++++++++++++++ ports/megaball/gameinfo.xml | 13 +++++++++++++ ports/megaball/port.json | 28 ++++++++++++++++++++++++++++ ports/megaball/screenshot.png | Bin 0 -> 3589 bytes 4 files changed, 70 insertions(+) create mode 100644 ports/megaball/README.md create mode 100644 ports/megaball/gameinfo.xml create mode 100644 ports/megaball/port.json create mode 100644 ports/megaball/screenshot.png diff --git a/ports/megaball/README.md b/ports/megaball/README.md new file mode 100644 index 0000000000..968b272bcf --- /dev/null +++ b/ports/megaball/README.md @@ -0,0 +1,29 @@ +## Notes + +Thanks [helpcomputer](https://helpcomputer.itch.io) (Adam) for creating this fantastic game and releasing it under an MIT license. + + +## Controls + +| Button | Action | +| D-PAD | Movement | +| START | Menu | +| A | Self-destruct | + + +## Compile + +```shell +apt update +apt install wget git python3-venv # python >=3.8 is required + +# Setup pyxel virtual env +python3 -m venv pyxel-venv +source pyxel-venv/bin/activate +pip install pyxel + +# Test the game +git clone https://github.com/tabreturn/PortMaster-New.git +cd ports/megaball +pyxel run megaball/gamedata/main.py +``` diff --git a/ports/megaball/gameinfo.xml b/ports/megaball/gameinfo.xml new file mode 100644 index 0000000000..62d80b4d59 --- /dev/null +++ b/ports/megaball/gameinfo.xml @@ -0,0 +1,13 @@ + + + + ./Megaball.sh + Megaball + The goal is to roll the ball over every flashing panel to complete the stage. For each stage you complete, you gain one extra life. If you need to, you can self-destruct (and lose a life) and shatter into ten pieces, killing any enemy which collides with these pieces; you will immediately re-spawn, and the enemy will reappear in 5 seconds. + 20200906T000000 + helpcomputer (Adam) + helpcomputer + Arcade + ./megaball/screenshot.png + + diff --git a/ports/megaball/port.json b/ports/megaball/port.json new file mode 100644 index 0000000000..d64c96bb82 --- /dev/null +++ b/ports/megaball/port.json @@ -0,0 +1,28 @@ +{ + "version": 3, + "name": "megaball.zip", + "items": [ + "Megaball.sh", + "megaball" + ], + "items_opt": null, + "attr": { + "title": "Megaball", + "porter": [ + "tabreturn" + ], + "desc": "The goal is to roll the ball over every flashing panel to complete the stage. For each stage you complete, you gain one extra life. If you need to, you can self-destruct (and lose a life) and shatter into ten pieces, killing any enemy which collides with these pieces; you will immediately re-spawn, and the enemy will reappear in 5 seconds.", + "desc_md": null, + "inst": "Ready to run! Thanks helpcomputer (https://helpcomputer.itch.io) for releasing this game under an MIT license.", + "inst_md": "Ready to run! Thanks [helpcomputer](https://helpcomputer.itch.io) for releasing this game under an MIT license.", + "genres": [ + "arcade" + ], + "image": {}, + "rtr": true, + "exp": false, + "runtime": "pyxel_2.2.8_python_3.11", + "reqs": [], + "arch": [] + } +} diff --git a/ports/megaball/screenshot.png b/ports/megaball/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..ef0175ddabd656077e3aea988925633f5f8a3495 GIT binary patch literal 3589 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 YwEmbR0a3pNzN`Veo%T9b@1&mlADK@pHvj+t literal 0 HcmV?d00001 From ec68d20ce9b144ad4da52f3ce2cf4c710bcc5d09 Mon Sep 17 00:00:00 2001 From: tabreturn Date: Tue, 17 Dec 2024 23:26:32 +1100 Subject: [PATCH 12/13] - refine metadata --- ports/megaball/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/megaball/README.md b/ports/megaball/README.md index 968b272bcf..731294e3ec 100644 --- a/ports/megaball/README.md +++ b/ports/megaball/README.md @@ -6,6 +6,7 @@ Thanks [helpcomputer](https://helpcomputer.itch.io) (Adam) for creating this fan ## Controls | Button | Action | +| -------| ------------- | | D-PAD | Movement | | START | Menu | | A | Self-destruct | @@ -23,7 +24,7 @@ source pyxel-venv/bin/activate pip install pyxel # Test the game -git clone https://github.com/tabreturn/PortMaster-New.git +git clone https://github.com/PortsMaster/PortMaster-New.git cd ports/megaball pyxel run megaball/gamedata/main.py ``` From 974f3e01c327741e94dff79cf8d8d2c279b27414 Mon Sep 17 00:00:00 2001 From: tabreturn Date: Wed, 18 Dec 2024 00:08:43 +1100 Subject: [PATCH 13/13] Update ports/megaball/port.json Co-authored-by: Jacob --- ports/megaball/port.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/megaball/port.json b/ports/megaball/port.json index d64c96bb82..4e0f42cd09 100644 --- a/ports/megaball/port.json +++ b/ports/megaball/port.json @@ -21,7 +21,7 @@ "image": {}, "rtr": true, "exp": false, - "runtime": "pyxel_2.2.8_python_3.11", + "runtime": "pyxel_2.2.8_python_3.11.squashfs", "reqs": [], "arch": [] }