scrapeboard/NS.py

2383 lines
97 KiB
Python
Raw Normal View History

2017-12-25 05:30:10 -05:00
# -*- coding: utf-8 -*-
#
# [SCRAPEBOARD] is an arcade game in development by [@diskmem] and [@snakesandrews]
#
# It requires custom hardware to play but can be tested in keyboard mode without the hardware.
# For more information on setting up and running the game, see the README, or for the game in
# general, visit https://scrape.nugget.fun/
#
import argparse, pathlib
2020-10-28 15:28:12 -04:00
from random import randint, choice, random
2017-12-21 02:50:27 -05:00
from math import pi
2017-12-14 03:49:02 -05:00
from copy import copy
2018-01-02 17:24:45 -05:00
from glob import iglob
2018-08-03 02:27:18 -04:00
from os.path import basename, join
2018-06-27 21:21:51 -04:00
from threading import Thread
2018-09-23 01:01:36 -04:00
from time import sleep
2020-10-28 15:28:12 -04:00
from PIL import Image
2017-12-14 03:49:02 -05:00
2022-01-19 13:53:13 -05:00
import pygame
2018-11-21 01:19:29 -05:00
from pygame import Surface, Color, mixer
2018-01-02 17:24:45 -05:00
from pygame.event import clear
2017-12-14 03:49:02 -05:00
from pygame.mixer import Sound
2018-07-03 18:10:26 -04:00
from pygame.image import load, fromstring
2020-10-28 20:02:23 -04:00
from pygame.transform import rotate, flip, scale, smoothscale
2017-12-14 03:49:02 -05:00
from pygame.time import get_ticks
from pygame.font import Font
2018-07-09 22:38:07 -04:00
from pygame.draw import aalines, lines
2020-10-28 15:28:12 -04:00
from pygame.gfxdraw import aapolygon, arc, polygon, aaellipse, ellipse, filled_ellipse, filled_circle
2017-12-14 03:49:02 -05:00
from pygame.locals import *
from lib.pgfw.pgfw.Game import Game
from lib.pgfw.pgfw.GameChild import GameChild
from lib.pgfw.pgfw.Sprite import Sprite, RainbowSprite
from lib.pgfw.pgfw.Animation import Animation
2018-07-10 00:57:27 -04:00
from lib.pgfw.pgfw.extension import (
get_step, get_step_relative, get_delta, reflect_angle,
2018-09-20 04:57:06 -04:00
render_box, get_hsla_color, get_hue_shifted_surface,
2022-02-24 20:53:34 -05:00
get_color_swapped_surface, load_frames, fill_colorkey
2018-07-10 00:57:27 -04:00
)
2017-12-14 03:49:02 -05:00
from lib.pgfw.pgfw.gfx_extension import aa_filled_polygon
class NS(Game, Animation):
"""
The main game object. It initializes and updates the title screen, boss manager, platform, dialog manager, screen wipe manager,
main character, and more (see the objects initialized in __init__). It initializes and watches the Arduino serial port and
listens for and responds to keyboard input.
"""
2017-12-14 03:49:02 -05:00
# Class variables that can be used to represent each of the four game pads. The L stands for "light", and the directions
# indicate which pad is being identified.
2017-12-25 01:41:20 -05:00
LNW, LNE, LSE, LSW = range(4)
# Class variables that can be used to represent each of the six possible orientations of the board on the four pads: the
# four sides of the square and the two diagonals.
2018-01-02 17:24:45 -05:00
N, NE, E, NW, S, W = range(6)
2022-02-24 01:16:45 -05:00
FRONT_WIDTH = 156
BACK_WIDTH = 271
LENGTH = 94
FRONT = 330
2017-12-14 03:49:02 -05:00
STEP = .4
2018-01-02 20:26:30 -05:00
IDLE_TIMEOUT = 60000 * 5
2018-11-21 01:19:29 -05:00
CHANNEL_COUNT = 8
2019-01-04 01:53:35 -05:00
NO_RESET_TIMEOUT = 3000
2017-12-14 03:49:02 -05:00
def __init__(self):
# Specify possible arguments and parse the command line. If the -h flag is passed, the argparse library will
# print a help message and end the program.
parser = argparse.ArgumentParser()
parser.add_argument("--minimize-load-time", action="store_true")
parser.add_argument("--serial-port")
parser.add_argument("--audio-buffer-size", type=int, default=1024)
parser.add_argument("--list-serial-ports", action="store_true")
parser.add_argument("--no-serial", action="store_true")
parser.add_argument("--show-config", action="store_true")
arguments = parser.parse_known_args()[0]
# Pre-initialize the mixer to use the specified buffer size in bytes. The default is set to 1024 to prevent lagging
# on the Raspberry Pi.
2022-01-19 13:53:13 -05:00
pygame.mixer.pre_init(44100, -16, 2, 1024)
# Pygame will be loaded in here.
2017-12-14 03:49:02 -05:00
Game.__init__(self)
# Add type declarations for non-string config name/value pairs that aren't in the default PGFW config dict.
2018-09-20 04:57:06 -04:00
self.get_configuration().type_declarations.add_chart(
{
"time":
{
2020-10-29 13:40:05 -04:00
"int": ["timer-max-time", "timer-start-time", "timer-addition", "sword-delay",
"attract-gif-length", "attract-board-length", "attract-reset-countdown"]
2018-09-20 04:57:06 -04:00
},
"input":
{
"bool": "serial"
2020-10-29 13:40:05 -04:00
},
"display":
{
"float": "attract-gif-alpha",
"bool": "effects"
},
"system":
{
"bool": "minimize-load-time"
2022-02-24 01:16:45 -05:00
},
"pads":
{
"int-list": "center"
2018-09-20 04:57:06 -04:00
}
})
# If a serial port was passed on the command line, override the config file setting
if arguments.serial_port is not None:
self.get_configuration().set("input", "arduino-port", arguments.serial_port)
# Command line flag requesting minimal load time overrides config file setting
if arguments.minimize_load_time:
self.get_configuration().set("system", "minimize-load-time", True)
# Turn off effects if minimal load time is requested. Minimal load time setting overrides display effects setting.
if self.get_configuration("system", "minimize-load-time"):
self.get_configuration().set("display", "effects", False)
# Apply the no serial flag from the command line if requested
if arguments.no_serial:
self.get_configuration().set("input", "serial", False)
# Print the configuration if requested on the command line
if arguments.show_config:
print(self.get_configuration())
# Initialize the serial reader and launch a thread for reading from the serial port
if self.serial_enabled():
from serial import Serial, SerialException
from serial.tools import list_ports
# If a list of serial ports was requested, print detected ports and exit.
if arguments.list_serial_ports:
for port in list_ports.comports():
print(f"Detected serial port: {port.device}")
exit()
# Open the port specified by the configuration or command line if it is found. If the specified port is not found,
# open the first found serial port. If no serial ports are found, raise an exception.
requested_port = self.get_configuration("input", "arduino-port")
devices = [port.device for port in list_ports.comports()]
if requested_port in devices:
self.serial_reader = Serial(requested_port, timeout=.3)
elif devices:
self.serial_reader = Serial(devices[0], timeout=.3)
else:
raise SerialException("No serial port devices were detected. Use --no-serial for keyboard-only mode.")
self.serial_kill = False
self.serial_data = 0
self.reset_arduino()
self.serial_thread = Thread(target=self.read_serial)
self.serial_thread.start()
2017-12-14 03:49:02 -05:00
Animation.__init__(self, self)
self.subscribe(self.respond, KEYDOWN)
self.subscribe(self.respond, KEYUP)
self.subscribe(self.respond)
ds = self.get_display_surface()
self.background = Surface(ds.get_size())
self.background.fill((0, 0, 0))
self.level_select = LevelSelect(self)
2018-09-19 21:33:58 -04:00
self.platform = Platform(self)
2020-10-29 01:06:56 -04:00
self.tony = Tony(self)
self.logo = Logo(self)
2017-12-21 02:50:27 -05:00
self.title = Title(self)
2018-01-02 20:51:42 -05:00
self.ending = Ending(self)
2017-12-21 02:50:27 -05:00
self.wipe = Wipe(self)
2017-12-21 06:59:45 -05:00
self.dialogue = Dialogue(self)
2017-12-14 03:49:02 -05:00
self.chemtrails = Chemtrails(self)
self.boss = Boss(self)
self.last_press = get_ticks()
2018-01-02 22:56:53 -05:00
self.register(self.blink_score, interval=500)
self.play(self.blink_score)
2017-12-14 03:49:02 -05:00
self.reset()
2018-01-02 22:56:53 -05:00
self.most_recent_time = None
2018-01-02 17:24:45 -05:00
clear()
2018-06-27 21:21:51 -04:00
def serial_enabled(self):
return self.get_configuration("input", "serial")
2018-06-27 21:21:51 -04:00
def read_serial(self):
while not self.serial_kill:
2018-09-21 13:09:23 -04:00
name = self.get_configuration("input", "arduino-port")
2018-09-23 01:01:36 -04:00
try:
transmission = self.serial_reader.readline().strip()
2020-10-05 18:28:14 -04:00
print(transmission)
2018-09-23 01:01:36 -04:00
except SerialException:
2020-10-05 18:28:14 -04:00
print("Serial not ready... passing...")
2018-09-23 01:01:36 -04:00
transmission = ""
if len(transmission) == 4:
try:
self.serial_data = int(transmission, 2)
except ValueError:
2020-10-05 18:28:14 -04:00
print("Value error checking four digit serial transmission")
2018-09-23 01:01:36 -04:00
self.handle_garbage(transmission)
self.reset_arduino()
2019-01-04 01:53:35 -05:00
self.idle_elapsed = 0
2018-09-23 01:01:36 -04:00
elif len(transmission) > 0:
try:
int(transmission, 2)
except ValueError:
2020-10-05 18:28:14 -04:00
print("Received a non-four digit serial transmission")
2018-09-23 01:01:36 -04:00
self.handle_garbage(transmission)
2018-06-27 21:21:51 -04:00
else:
self.serial_data = 0
2018-09-23 01:01:36 -04:00
def handle_garbage(self, transmission):
self.serial_data = 0
2020-10-05 18:28:14 -04:00
print("Garbage detected: %s" % transmission)
2018-09-23 01:01:36 -04:00
self.serial_reader.reset_input_buffer()
def reset_arduino(self):
2019-01-04 01:53:35 -05:00
if self.serial_enabled():
self.serial_reader.dtr = False
self.serial_reader.reset_input_buffer()
self.serial_reader.dtr = True
2018-09-23 01:01:36 -04:00
2018-06-27 21:21:51 -04:00
def end(self, evt):
if evt.type == QUIT or self.delegate.compare(evt, "quit"):
self.serial_kill = True
Game.end(self, evt)
def apply_serial(self):
for ii, light in enumerate(self.platform.lights):
light.pressed = bool(self.serial_data & (2 ** ii))
# reset idle timer if a light is detected as pressed in serial data
if light.pressed:
self.idle_elapsed = 0
2018-06-27 21:21:51 -04:00
2017-12-28 08:01:58 -05:00
def reset(self, leave_wipe_running=False):
2018-01-02 22:56:53 -05:00
self.score_hidden = False
2018-01-02 20:26:30 -05:00
self.idle_elapsed = 0
2017-12-20 18:56:58 -05:00
self.suppressing_input = False
self.level_select.reset()
2017-12-21 02:50:27 -05:00
self.title.reset()
2017-12-28 08:01:58 -05:00
if not leave_wipe_running:
self.wipe.reset()
2018-01-02 20:51:42 -05:00
self.ending.reset()
2017-12-14 03:49:02 -05:00
self.boss.reset()
self.chemtrails.reset()
2017-12-20 18:56:58 -05:00
self.platform.reset()
2017-12-21 06:59:45 -05:00
self.dialogue.reset()
2019-01-04 01:53:35 -05:00
self.no_reset_elapsed = 0
2020-10-18 18:50:48 -04:00
self.title.activate()
2017-12-21 06:59:45 -05:00
2018-01-02 22:56:53 -05:00
def set_most_recent_time(self, score):
self.most_recent_time = score
def blink_score(self):
self.score_hidden = not self.score_hidden
2017-12-21 06:59:45 -05:00
def suppress_input(self):
self.suppressing_input = True
2017-12-28 08:01:58 -05:00
# self.platform.unpress()
2017-12-21 06:59:45 -05:00
def unsuppress_input(self):
self.suppressing_input = False
2017-12-14 03:49:02 -05:00
def respond(self, event):
"""
Respond to keyboard input.
___ ___
| O| P| These keyboard keys correspond to the floor pads.
|___|___| (O = top left pad, P = top right pad, L = bottom left pad, ; = bottom right pad)
| L| ;| Arrow keys can also be used.
|___|___| (UP = top left pad, RIGHT = top right pad, DOWN = bottom left pad, LEFT = bottom right pad)
The Z key is a shortcut for reset (F8 also resets).
The A key force resets the connected Arduino (or does nothing if no Arduino is connected).
"""
2017-12-20 18:56:58 -05:00
if not self.suppressing_input and event.type in (KEYDOWN, KEYUP):
2018-06-27 21:21:51 -04:00
if self.last_press <= get_ticks() - int(self.get_configuration("input", "buffer")):
2017-12-14 03:49:02 -05:00
pressed = True if event.type == KEYDOWN else False
2017-12-20 18:56:58 -05:00
lights = self.platform.lights
2018-01-02 20:26:30 -05:00
self.idle_elapsed = 0
2017-12-20 18:56:58 -05:00
if event.key in (K_UP, K_o):
2017-12-25 01:41:20 -05:00
lights[NS.LNW].pressed = pressed
2017-12-20 18:56:58 -05:00
elif event.key in (K_RIGHT, K_p):
2017-12-25 01:41:20 -05:00
lights[NS.LNE].pressed = pressed
2017-12-20 18:56:58 -05:00
elif event.key in (K_DOWN, K_SEMICOLON):
2017-12-25 01:41:20 -05:00
lights[NS.LSE].pressed = pressed
2017-12-20 18:56:58 -05:00
elif event.key in (K_LEFT, K_l):
2017-12-25 01:41:20 -05:00
lights[NS.LSW].pressed = pressed
2018-01-01 15:02:36 -05:00
elif event.key == K_z:
self.reset()
2018-09-23 01:01:36 -04:00
elif event.key == K_a:
self.reset_arduino()
2017-12-14 03:49:02 -05:00
self.last_press = get_ticks()
else:
if self.get_delegate().compare(event, "reset-game"):
self.reset()
def update(self):
Animation.update(self)
2019-01-04 01:53:35 -05:00
last_frame_duration = self.time_filter.get_last_frame_duration()
2020-10-11 19:14:07 -04:00
if self.serial_enabled():
self.apply_serial()
if self.title.active or self.ending.active or self.dialogue.active:
2019-01-04 01:53:35 -05:00
self.no_reset_elapsed += last_frame_duration
# if we received good input, reset the auto reset timer
if 0b11 <= self.serial_data <= 0b1100:
self.no_reset_elapsed = 0
2019-01-04 01:53:35 -05:00
if self.no_reset_elapsed >= self.NO_RESET_TIMEOUT:
2020-10-05 18:28:14 -04:00
print("auto arduino reset triggered")
2019-01-04 01:53:35 -05:00
self.reset_arduino()
self.no_reset_elapsed = 0
2020-10-28 20:02:23 -04:00
self.title.update()
self.level_select.update()
2018-01-02 20:51:42 -05:00
self.ending.update()
2017-12-14 03:49:02 -05:00
self.boss.update()
2020-10-28 20:02:23 -04:00
if not self.title.active:
self.platform.update()
self.chemtrails.update()
2017-12-25 01:41:20 -05:00
self.boss.update_dialogue()
2017-12-21 02:50:27 -05:00
self.wipe.update()
2018-01-02 20:26:30 -05:00
self.idle_elapsed += self.time_filter.get_last_frame_duration()
if self.idle_elapsed >= self.IDLE_TIMEOUT:
self.reset()
2017-12-14 03:49:02 -05:00
class LevelSelect(GameChild):
"""
Display the available levels. Initialize a platform for each level and display each platform beneath its level glowing
with a pair of pads to press to start that level. Wait for user input, then launch the level of the pair that gets
pressed by the user.
"""
def __init__(self, parent):
GameChild.__init__(self, parent)
self.subscribe(self.respond, KEYDOWN)
def activate(self):
self.active = True
def deactivate(self):
self.active = False
def reset(self):
self.deactivate()
def respond(self, event):
if self.active:
launch_level = None
if event.key == K_1:
launch_level = 0
elif event.key == K_2:
launch_level = 1
elif event.key == K_3:
launch_level = 2
if launch_level is not None:
self.deactivate()
self.get_game().boss.start_level(launch_level)
def update(self):
if self.active:
self.get_display_surface().fill((255, 255, 0))
2018-09-19 21:33:58 -04:00
class Button(Sprite):
MARGIN = 2
BLANK = (200, 200, 200)
def __init__(self, parent, edge, size, border):
Sprite.__init__(self, parent)
colors = self.get_game().platform.get_color_pair_from_edge(edge)
width = size * 2 + self.MARGIN + border * 4
step = width / 2 + self.MARGIN / 2
rect_width = width / 2 - self.MARGIN / 2
rects = Rect(0, 0, rect_width, rect_width), \
Rect(step, 0, rect_width, rect_width), \
Rect(step, step, rect_width, rect_width), \
Rect(0, step, rect_width, rect_width)
if edge == NS.N:
colored = rects[0], rects[1]
elif edge == NS.NE:
colored = rects[1], rects[3]
elif edge == NS.E:
colored = rects[1], rects[2]
elif edge == NS.NW:
colored = rects[0], rects[2]
elif edge == NS.S:
colored = rects[3], rects[2]
elif edge == NS.W:
colored = rects[0], rects[3]
for lightness in range(30, 90, 5):
2018-09-19 21:33:58 -04:00
frame = Surface((width, width), SRCALPHA)
for topleft in (0, 0), (step, 0), (step, step), (0, step):
rect = Rect(topleft, (rect_width, rect_width))
border_color = Color(*self.BLANK)
border_color.a = 179
frame.fill(border_color, rect)
frame.fill((0, 0, 0, 0), rect.inflate(-border * 2, -border * 2))
for ii in range(2):
2018-09-19 21:33:58 -04:00
original_color = Color(*colors[ii])
original_color.a = 255
edited_color = Color(0, 0, 0)
edited_color.hsla = int(original_color.hsla[0]), int(original_color.hsla[1]), \
lightness, 70
frame.fill(edited_color, colored[ii])
frame.fill(original_color, colored[ii].inflate(-border * 2, -border * 2))
self.add_frame(frame)
2018-09-20 04:57:06 -04:00
class Meter(GameChild):
SPACING = 12
def __init__(self, parent):
GameChild.__init__(self, parent)
2018-11-21 01:19:29 -05:00
def setup(self, background, rect, indent, color, units, path):
2018-09-20 04:57:06 -04:00
self.background = background
self.rect = rect
self.icons = []
x = rect.left + indent
base = get_color_swapped_surface(
2018-11-21 01:19:29 -05:00
load(self.get_resource(path)).convert_alpha(),
2018-09-20 04:57:06 -04:00
(0, 0, 0), color)
while x <= self.rect.right - base.get_width() - self.SPACING:
icon = Sprite(self)
icon.add_frame(base)
icon.location.midleft = x, self.rect.centery
self.icons.append(icon)
x += icon.location.w + self.SPACING
self.units = units
def reset(self):
self.amount = self.units
for icon in self.icons:
icon.unhide()
def change(self, delta):
self.amount += delta
cutoff = float(self.amount) / self.units * len(self.icons)
for ii, icon in enumerate(self.icons):
if ii < cutoff:
icon.unhide()
else:
icon.hide()
def update(self):
ds = self.get_display_surface()
ds.blit(self.background, self.rect)
for icon in self.icons:
icon.update()
class Tony(Sprite):
def __init__(self, parent):
2020-10-29 01:06:56 -04:00
Sprite.__init__(self, parent, 100, False)
self.board = Sprite(self, 100)
self.board.load_from_path(self.get_resource("newTony/TonyArms"), True)
self.effect = Sprite(self)
dsr = self.get_display_surface().get_rect()
if self.get_configuration("display", "effects"):
for offset in range(12):
w, h = dsr.w + 40, int(dsr.h * .65)
glow = Surface((w, h), SRCALPHA)
for ii, y in enumerate(range(h, 0, -8)):
hue = range(240, 200, -2)[(ii - offset) % 12]
alpha = min(100, int(round(y / float(h - 10) * 100)))
color = get_hsla_color(hue, 100, 50, alpha)
if ii == 0:
aaellipse(glow, w // 2, y, w // 2 - 4, h // 20, color)
ellipse(glow, w // 2, y, w // 2 - 4, h // 20, color)
filled_ellipse(glow, w // 2, y, w // 2 - 4, h // 20, color)
self.effect.add_frame(glow)
2020-10-29 01:06:56 -04:00
self.effect.location.topleft = -20, int(dsr.h * .35)
self.add_frame(load(self.get_resource("Big_Tony.png")).convert_alpha())
self.load_from_path(self.get_resource("newTony/TonyShirtHead"), True)
self.add_frameset([0], name="static")
self.add_frameset(range(1, len(self.frames)), name="board")
2020-10-30 15:29:35 -04:00
self.taunts = []
for sfx_name in self.get_audio().sfx:
if sfx_name.startswith("TonyTauntsBend_"):
self.taunts.append(sfx_name)
self.location.centerx = dsr.centerx
self.board.location.centerx = self.location.centerx
2020-10-29 01:06:56 -04:00
def set_frameset(self, name):
Sprite.set_frameset(self, name)
self.get_current_frameset().reset()
self.set_framerate(100)
if name == "board":
self.board.get_current_frameset().reset()
self.board.unhide()
self.board.set_framerate(100)
self.board.halt()
elif name == "static":
self.board.hide()
2020-10-30 03:57:46 -04:00
def shift_frame(self):
Sprite.shift_frame(self)
frameset = self.get_current_frameset()
if frameset.name == "board" and frameset.current_index == 1:
2020-10-30 15:29:35 -04:00
self.get_audio().play_sfx(choice(self.taunts))
2020-10-30 03:57:46 -04:00
2020-10-29 01:06:56 -04:00
def update(self):
save = self.get_display_surface()
intermediate_surface = Surface(self.location.size, SRCALPHA)
self.display_surface = intermediate_surface
location_save = self.location.copy()
self.location.topleft = 0, 0
2020-10-29 01:06:56 -04:00
Sprite.update(self)
self.display_surface = save
self.location = location_save
2020-10-29 01:06:56 -04:00
self.effect.display_surface = intermediate_surface
self.effect.update(flags=BLEND_RGBA_SUB)
self.get_display_surface().blit(intermediate_surface, self.location.topleft)
if self.get_game().title.active:
2022-02-24 01:16:45 -05:00
self.get_game().title.video.update()
2020-10-29 01:06:56 -04:00
self.get_game().platform.update()
self.get_game().chemtrails.update()
frameset = self.get_current_frameset()
if frameset.name == "board":
self.board.get_current_frameset().current_index = frameset.current_index
if frameset.current_index == len(frameset.order) - 1:
self.set_framerate(3000)
else:
self.set_framerate(100)
self.board.update()
2020-10-28 15:28:12 -04:00
class Video(Sprite):
2020-10-28 20:02:23 -04:00
def __init__(self, parent, diameter, next_video_chance=.01):
2020-10-28 15:28:12 -04:00
Sprite.__init__(self, parent, 100)
2020-10-28 20:02:23 -04:00
self.next_video_chance = next_video_chance
2020-10-28 15:28:12 -04:00
pattern = join(self.get_resource("gif"), "Boarding_*.gif")
self.gifs = []
for path in iglob(pattern):
self.gifs.append(Image.open(path))
print(self.gifs[-1].info)
self.gif = self.gifs[1]
2020-10-28 20:02:23 -04:00
self.mask = Surface([diameter] * 2, SRCALPHA)
2020-10-28 15:28:12 -04:00
rect = self.mask.get_rect()
2020-10-29 13:40:05 -04:00
alpha = int(self.get_configuration("display", "attract-gif-alpha") * 255)
filled_circle(self.mask, rect.centerx, rect.centery, rect.centerx, (0, 0, 0, alpha))
filled_circle(self.mask, rect.centerx, rect.centery, rect.centerx - 2, (255, 255, 255, alpha))
2020-10-28 20:02:23 -04:00
self.add_frame(self.mask)
if not self.get_configuration("system", "minimize-load-time"):
self.play()
2020-10-28 20:02:23 -04:00
def shift_frame(self):
Sprite.shift_frame(self)
if random() < self.next_video_chance:
2020-10-28 15:28:12 -04:00
while True:
selection = choice(self.gifs)
if selection != self.gif:
self.gif = selection
break
self.gif.seek((self.gif.tell() + 1) % self.gif.n_frames)
2020-10-28 20:02:23 -04:00
frame = smoothscale(
2020-10-28 15:28:12 -04:00
fromstring(self.gif.convert("RGBA").tobytes(), self.gif.size, "RGBA"),
(self.mask.get_width(), int(self.gif.width * self.gif.height / self.mask.get_width())))
copy = self.mask.copy()
rect = frame.get_rect()
rect.bottom = copy.get_rect().bottom
copy.blit(frame, rect, None, BLEND_RGBA_MIN)
self.clear_frames()
self.add_frame(copy)
2020-10-29 01:06:56 -04:00
class Logo(Sprite):
def __init__(self, parent):
Sprite.__init__(self, parent)
dsr = self.get_display_surface().get_rect()
self.load_from_path(self.get_resource("Title_tile.png"), True)
for y in range(0, dsr.h + self.location.h, self.location.h):
for x in range(0, dsr.w + self.location.w, self.location.w):
if x != 0 or y != 0:
self.add_location((x, y))
self.effect = Sprite(self, 100)
palette = (255, 255, 255), (255, 255, 128), (255, 255, 0)
thickness = 8
for offset in range(len(palette)):
frame = Surface(dsr.size)
for x in range(0, dsr.w, thickness):
frame.fill(palette[(offset + x) % len(palette)], (x, 0, thickness, dsr.h))
self.effect.add_frame(frame)
def update(self):
self.effect.update()
# tiled background
self.move(-2, 2)
if self.location.right < 0:
self.move(self.location.w)
if self.location.top > 0:
self.move(dy=-self.location.h)
Sprite.update(self, flags=BLEND_RGBA_MIN)
2020-10-28 15:28:12 -04:00
2020-10-28 20:02:23 -04:00
class Title(Animation):
"""
Handles displaying and drawing the title screen.
"""
2017-12-20 18:56:58 -05:00
2020-10-30 03:57:46 -04:00
UNLOCK_MOVES = NS.NW, NS.N, NS.NE, NS.NW
2020-10-18 18:50:48 -04:00
2017-12-20 18:56:58 -05:00
def __init__(self, parent):
2020-10-28 20:02:23 -04:00
Animation.__init__(self, parent)
2017-12-21 02:50:27 -05:00
self.plank = Sprite(self)
self.plank.load_from_path(self.get_resource("Title_plank.png"), True)
ds = self.get_display_surface()
dsr = ds.get_rect()
self.plank.location.center = dsr.center
2020-10-28 20:02:23 -04:00
self.angle = pi / 8
self.video = Video(self, 320)
self.video.location.center = 329, 182
self.register(self.show_video, self.hide_video)
self.show_video()
2017-12-20 18:56:58 -05:00
def reset(self):
2020-10-18 18:50:48 -04:00
self.unlock_index = 0
2020-10-29 01:06:56 -04:00
self.get_game().platform.set_glowing(self.get_game().platform.get_buttons_from_edges([self.UNLOCK_MOVES[0]]))
2020-10-30 03:57:46 -04:00
self.halt()
self.show_video()
2017-12-20 18:56:58 -05:00
def activate(self):
self.active = True
2020-10-18 18:50:48 -04:00
platform = self.get_game().platform
platform.activate()
platform.set_glowing(platform.get_buttons_from_edges([self.UNLOCK_MOVES[self.unlock_index]]))
self.get_game().chemtrails.activate()
2020-10-29 01:06:56 -04:00
self.get_game().tony.set_frameset("static")
self.get_audio().play_bgm("title")
2017-12-20 18:56:58 -05:00
def deactivate(self):
self.active = False
2020-10-29 12:48:45 -04:00
self.halt()
2017-12-20 18:56:58 -05:00
2020-10-18 18:50:48 -04:00
def start_game(self):
"""
Turn off the title screen and display the level select. Set the most recent time to None so the most
recent high score stops blinking.
"""
2017-12-21 02:50:27 -05:00
self.deactivate()
2018-01-02 22:56:53 -05:00
self.get_game().set_most_recent_time(None)
self.get_game().level_select.activate()
2018-01-02 22:56:53 -05:00
def draw_scores(self):
step = 75
ds = self.get_display_surface()
lines = map(int, open(self.get_resource("scores")).readlines())
2018-01-02 22:56:53 -05:00
entries = ["BEST"] + sorted(lines)[:9]
for ii, entry in enumerate(entries):
if ii == 0 or ii == 5:
y = 30
font = Font(self.get_resource(Dialogue.FONT_PATH), 18)
if ii > 0:
text = self.get_formatted_time(entry)
else:
text = entry
message = render_box(font, text, True, Color(255, 255, 255),
Color(128, 128, 128), Color(0, 0, 0), padding=2)
2020-10-18 18:50:48 -04:00
message.set_alpha(230)
2018-01-02 22:56:53 -05:00
rect = message.get_rect()
rect.top = y
if ii < 5:
rect.left = -1
else:
rect.right = ds.get_width() + 1
if not entry == self.get_game().most_recent_time or not self.get_game().score_hidden:
ds.blit(message, rect)
y += step
def get_formatted_time(self, entry):
2018-07-25 22:02:44 -04:00
if int(entry) == 5999999:
return "--:--.-"
else:
minutes, milliseconds = divmod(int(entry), 60000)
seconds, fraction = divmod(milliseconds, 1000)
return "%i:%02i.%i" % (minutes, seconds, fraction / 100)
2017-12-21 02:50:27 -05:00
2020-10-28 20:02:23 -04:00
def show_video(self):
self.video.unhide()
2020-10-29 13:40:05 -04:00
self.play(self.hide_video, delay=self.get_configuration("time", "attract-gif-length"), play_once=True)
2020-10-29 01:06:56 -04:00
self.get_game().tony.set_frameset("static")
2020-10-30 03:57:46 -04:00
self.unlock_index = 0
self.get_game().platform.set_glowing(self.get_game().platform.get_buttons_from_edges([self.UNLOCK_MOVES[0]]))
2020-10-28 20:02:23 -04:00
def hide_video(self):
self.video.hide()
2020-10-29 13:40:05 -04:00
self.play(self.show_video, delay=self.get_configuration("time", "attract-board-length"), play_once=True)
2020-10-29 01:06:56 -04:00
self.get_game().tony.set_frameset("board")
2020-10-28 20:02:23 -04:00
2017-12-20 18:56:58 -05:00
def update(self):
"""
Scroll the background, check for button presses for the unlock pattern, handle switching between attract mode
with the GIFs active and unlocking pattern mode, and draw the screen
"""
2020-10-28 20:02:23 -04:00
Animation.update(self)
2017-12-20 18:56:58 -05:00
if self.active:
2017-12-21 02:50:27 -05:00
ds = self.get_display_surface()
dsr = ds.get_rect()
2020-10-29 01:06:56 -04:00
self.get_game().logo.update()
# Advance through the unlock pattern
2020-10-18 18:50:48 -04:00
platform = self.get_game().platform
if not self.get_game().wipe.is_playing() and platform.get_edge_pressed() == self.UNLOCK_MOVES[self.unlock_index]:
if self.unlock_index == len(self.UNLOCK_MOVES) - 1:
platform.set_glowing([])
self.get_game().wipe.start(self.start_game)
self.get_audio().play_sfx("confirm")
else:
self.unlock_index += 1
2020-10-18 18:50:48 -04:00
platform.set_glowing(platform.get_buttons_from_edges([self.UNLOCK_MOVES[self.unlock_index]]))
self.get_audio().play_sfx("land_0")
2020-10-29 01:06:56 -04:00
self.get_game().tony.update()
# Bounce the GIF around the screen
2020-10-28 15:28:12 -04:00
if self.video.location.right > dsr.right or self.video.location.left < dsr.left:
self.angle = reflect_angle(self.angle, 0)
if self.video.location.right > dsr.right:
self.video.move(dsr.right - self.video.location.right)
else:
self.video.move(dsr.left - self.video.location.left)
if self.video.location.bottom > dsr.bottom or self.video.location.top < dsr.top:
self.angle = reflect_angle(self.angle, pi)
if self.video.location.bottom > dsr.bottom:
self.video.move(dy=dsr.bottom - self.video.location.bottom)
else:
self.video.move(dy=dsr.top - self.video.location.top)
dx, dy = get_delta(self.angle, 5, False)
self.video.move(dx, dy)
# Hide GIFs/attract mode (or keep them hidden) if input is detected. Set a countdown that will turn
# attract mode back on if no input is detected before the countdown expires. As long as input keeps
# being detected, this block will keep running and restarting the countdown.
2020-10-28 20:02:23 -04:00
if platform.get_pressed():
self.video.hide()
2020-10-29 01:06:56 -04:00
self.get_game().tony.set_frameset("static")
2020-10-29 12:48:45 -04:00
self.halt()
self.play(self.show_video, delay=self.get_configuration("time", "attract-reset-countdown"), play_once=True)
2022-02-24 01:16:45 -05:00
# self.video.update()
2018-01-02 22:56:53 -05:00
self.draw_scores()
2017-12-20 18:56:58 -05:00
2017-12-21 06:59:45 -05:00
class Dialogue(Animation):
2022-02-24 20:53:34 -05:00
"""
This class creates the graphics for displaying character dialog. It displays an avatar, a character name, and a box with the
dialog text in classic RPG format. It uses the Animation class to scroll the text onto the screen with a sound effect to mimic
talking.
"""
2017-12-21 06:59:45 -05:00
BACKGROUND = 255, 255, 255
BORDER = 0, 0, 0
TEXT_COLOR = 0, 0, 0
FONT_PATH = "rounded-mplus-1m-bold.ttf"
FONT_SIZE = 18
def __init__(self, parent):
2018-01-02 17:24:45 -05:00
Animation.__init__(self, parent)
2017-12-21 06:59:45 -05:00
ds = self.get_display_surface()
dsr = ds.get_rect()
frame = Surface((dsr.w, 72))
2017-12-21 06:59:45 -05:00
frame.fill(self.BORDER)
frame.fill(self.BACKGROUND, (1, 1, frame.get_width() - 2, frame.get_height() - 2))
self.text_box = Sprite(self)
self.text_box.add_frame(frame)
self.text_box.location.bottomleft = dsr.bottomleft
frame = Surface((66, 66))
frame.fill(self.BORDER)
frame.fill(self.BACKGROUND, (1, 1, frame.get_width() - 2, frame.get_height() - 2))
self.avatar_box = Sprite(self)
self.avatar_box.add_frame(frame)
self.avatar_box.location.bottomleft = self.text_box.location.topleft
frame = Surface((128, 24))
frame.fill(self.BORDER)
frame.fill(self.BACKGROUND, (1, 1, frame.get_width() - 2, frame.get_height() - 2))
self.name_box = Sprite(self)
self.name_box.add_frame(frame)
self.name_box.location.bottomleft = self.avatar_box.location.bottomright
2018-01-02 20:26:30 -05:00
self.speech_channel = None
2017-12-21 06:59:45 -05:00
def reset(self):
2018-01-02 20:26:30 -05:00
self.stop_speech()
2017-12-21 06:59:45 -05:00
self.halt()
self.deactivate()
self.first_pressed = False
self.first_press_elapsed = 0
2018-01-02 20:26:30 -05:00
def stop_speech(self):
if self.speech_channel is not None:
self.speech_channel.stop()
2020-10-18 18:50:48 -04:00
self.speech_channel = None
2018-01-02 20:26:30 -05:00
2017-12-21 06:59:45 -05:00
def deactivate(self):
2018-01-02 20:26:30 -05:00
self.stop_speech()
2017-12-21 06:59:45 -05:00
self.active = False
def activate(self):
self.active = True
def set_avatar(self, image):
self.avatar = Sprite(self)
self.avatar.add_frame(image)
self.avatar.location.center = self.avatar_box.location.center
def set_name(self, text):
font = Font(self.get_resource(self.FONT_PATH), self.FONT_SIZE)
self.name = Sprite(self)
self.name.add_frame(font.render(text, True, self.TEXT_COLOR).convert_alpha())
2017-12-21 06:59:45 -05:00
self.name.location.midleft = self.name_box.location.left + 5, self.name_box.location.centery
def show_text(self, text):
self.full_text = text
self.text_index = 0
self.speech_channel = self.get_audio().play_sfx("talk", -1)
2017-12-21 06:59:45 -05:00
self.play()
def build_frame(self):
2018-01-02 17:24:45 -05:00
self.text_index += 2
if self.text_index >= len(self.full_text):
self.show_all()
2017-12-21 06:59:45 -05:00
def show_all(self):
2018-01-02 20:26:30 -05:00
self.stop_speech()
2017-12-21 06:59:45 -05:00
self.text_index = len(self.full_text)
self.halt()
def update(self):
if self.active:
Animation.update(self)
self.avatar_box.update()
self.avatar.update()
self.name_box.update()
self.name.update()
self.text_box.update()
font = Font(self.get_resource(self.FONT_PATH), self.FONT_SIZE)
message = Sprite(self)
lines = self.full_text[:self.text_index].split("\n")
frame = Surface((self.text_box.location.w - 10, 30 * len(lines)), SRCALPHA)
for ii, line in enumerate(lines):
surface = font.render(line, True, self.TEXT_COLOR).convert_alpha()
2017-12-21 06:59:45 -05:00
frame.blit(surface, (0, 30 * ii))
2020-10-18 18:50:48 -04:00
message.add_frame(frame)
message.location.topleft = self.text_box.location.left + 9, self.text_box.location.top + 8
message.update()
2017-12-21 06:59:45 -05:00
2018-09-19 21:33:58 -04:00
class SkipPrompt(GameChild):
2017-12-25 01:41:20 -05:00
def __init__(self, parent, callback):
2018-09-19 21:33:58 -04:00
GameChild.__init__(self, parent)
2017-12-25 01:41:20 -05:00
self.callback = callback
2018-09-19 21:33:58 -04:00
self.buttons = []
self.pluses = []
top = 3
left = 3
for ii, edge in enumerate((NS.S, NS.NE, NS.W)):
2022-02-24 20:53:34 -05:00
self.buttons.append(Button(self, edge, AdvancePrompt.BUTTON_SIZE, AdvancePrompt.BUTTON_BORDER))
2018-09-19 21:33:58 -04:00
self.buttons[-1].location.topleft = left, top
if ii < 2:
self.pluses.append(Sprite(self))
self.pluses[-1].load_from_path(self.get_resource("Plus.png"), True)
self.pluses[-1].location.center = (
self.buttons[-1].location.right + AdvancePrompt.BUTTON_SPACING / 2,
self.buttons[-1].location.centery)
left += self.buttons[-1].location.width + AdvancePrompt.BUTTON_SPACING
self.text = Sprite(self)
font = Font(self.get_resource(Dialogue.FONT_PATH), 18)
self.text.add_frame(font.render("TO SKIP", True, (0, 0, 0)).convert_alpha())
2018-09-19 21:33:58 -04:00
self.text.location.midleft = (
self.buttons[2].location.right + 5,
self.buttons[2].location.centery)
self.button_sound = self.get_audio().sfx["button"]
2017-12-25 01:41:20 -05:00
def reset(self):
self.press_index = 0
self.press_elapsed = 0
2018-09-19 21:33:58 -04:00
for button in self.buttons:
button.unhide()
for plus in self.pluses:
plus.unhide()
2017-12-25 01:41:20 -05:00
def update(self):
platform = self.get_game().platform
if self.press_index == 0 and platform.get_edge_pressed() == NS.S:
self.press_index += 1
2018-01-02 17:24:45 -05:00
self.button_sound.play()
2018-09-19 21:33:58 -04:00
self.buttons[0].hide()
self.pluses[0].hide()
2017-12-25 01:41:20 -05:00
elif self.press_index == 1 and platform.get_edge_pressed() == NS.NE:
self.press_index += 1
2018-01-02 17:24:45 -05:00
self.button_sound.play()
2018-09-19 21:33:58 -04:00
self.buttons[1].hide()
self.pluses[1].hide()
2017-12-25 01:41:20 -05:00
elif self.press_index == 2 and platform.get_edge_pressed() == NS.W:
self.callback()
self.get_audio().play_sfx("confirm")
2017-12-25 01:41:20 -05:00
elif self.press_index > 0:
self.press_elapsed += self.get_game().time_filter.get_last_frame_duration()
if self.press_elapsed > 4000:
self.reset()
2018-09-19 21:33:58 -04:00
for button in self.buttons:
button.update()
for plus in self.pluses:
plus.update()
self.text.update()
2017-12-25 01:41:20 -05:00
2018-09-19 21:33:58 -04:00
class AdvancePrompt(GameChild):
2020-10-30 03:57:46 -04:00
BUTTON_SIZE = 30
BUTTON_BORDER = 3
BUTTON_SPACING = 64
2017-12-25 01:41:20 -05:00
def __init__(self, parent):
2018-09-19 21:33:58 -04:00
GameChild.__init__(self, parent)
2017-12-25 01:41:20 -05:00
dsr = self.get_display_surface().get_rect()
2018-09-19 21:33:58 -04:00
self.buttons = Button(self, NS.N, self.BUTTON_SIZE, self.BUTTON_BORDER), \
Button(self, NS.NW, self.BUTTON_SIZE, self.BUTTON_BORDER)
self.plus = Sprite(self)
self.plus.load_from_path(self.get_resource("Plus.png"), True)
2020-10-30 03:57:46 -04:00
dsr = self.get_display_surface().get_rect()
self.plus.location.center = dsr.centerx, dsr.centery + 70
self.buttons[1].location.center = self.plus.location.move(self.BUTTON_SPACING, 0).center
self.buttons[0].location.center = self.plus.location.move(-self.BUTTON_SPACING, 0).center
self.background_rect = Rect(
self.buttons[0].location.topleft,
(self.buttons[1].location.right - self.buttons[0].location.left, self.buttons[0].location.height))
self.background_rect.inflate_ip((10, 10))
2017-12-25 01:41:20 -05:00
def reset(self):
self.cancel_first_press()
2018-09-19 21:33:58 -04:00
for button in self.buttons:
button.unhide()
self.plus.unhide()
2017-12-25 01:41:20 -05:00
def cancel_first_press(self):
self.first_pressed = False
self.first_pressed_elapsed = 0
2018-09-19 21:33:58 -04:00
self.buttons[0].unhide()
self.plus.unhide()
2017-12-25 01:41:20 -05:00
def check_first_press(self):
return not self.first_pressed and self.get_game().platform.get_edge_pressed() == NS.N
def press_first(self):
self.first_pressed = True
2018-09-19 21:33:58 -04:00
self.buttons[0].hide()
self.plus.hide()
self.get_audio().play_sfx("button")
2017-12-25 01:41:20 -05:00
def check_second_press(self):
2018-01-02 17:24:45 -05:00
pressed = self.first_pressed and self.get_game().platform.get_edge_pressed() == NS.NW
if pressed:
self.get_audio().play_sfx("confirm")
2018-01-02 17:24:45 -05:00
return pressed
2017-12-25 01:41:20 -05:00
def update(self):
if self.first_pressed:
self.first_pressed_elapsed += self.get_game().time_filter.get_last_frame_duration()
2020-10-30 03:57:46 -04:00
self.get_display_surface().fill((255, 255, 255), self.background_rect)
2018-09-19 21:33:58 -04:00
for button in self.buttons:
button.update()
self.plus.update()
2017-12-20 18:56:58 -05:00
class Wipe(Animation):
"""
This class creates a blinds screen wipe effect that can be given a callback function to be called exactly when the screen is
filled with the wipe graphic. This allows the game to transition between states behind the wipe graphic to create a curtain
effect.
"""
2017-12-20 18:56:58 -05:00
2017-12-21 02:50:27 -05:00
BLIND_COUNT = 4
SPEED = 6
2017-12-20 18:56:58 -05:00
def __init__(self, parent):
"""
Initialize the wipe image and sound effect
@param parent PGFW game object that instantiated the wipe
"""
2017-12-20 18:56:58 -05:00
Animation.__init__(self, parent)
2017-12-21 02:50:27 -05:00
self.image = load(self.get_resource("Ink.png")).convert()
self.sound = self.get_audio().sfx["wipe"]
2017-12-20 18:56:58 -05:00
def reset(self):
"""
Deactivate and stop the animation
"""
2017-12-20 18:56:58 -05:00
self.deactivate()
2017-12-21 02:50:27 -05:00
self.halt()
2017-12-20 18:56:58 -05:00
def deactivate(self):
self.active = False
def activate(self):
self.active = True
2017-12-21 02:50:27 -05:00
def start(self, callback):
"""
Trigger the wipe animation to begin. The given callback function will be called when the screen is filled with the
wipe graphic.
@param callback function to be called when the wipe is covering the screen
"""
2017-12-21 02:50:27 -05:00
self.activate()
self.up = True
self.blind_height = self.get_display_surface().get_height() / self.BLIND_COUNT
self.callback = callback
self.play()
2018-01-02 17:24:45 -05:00
self.sound.play()
2017-12-21 02:50:27 -05:00
def build_frame(self):
"""
This grows and shrinks the height of the blinds that control how much of the wipe graphic is currently displayed. It
will be called automatically every frame as long as the wipe's update method is being called.
"""
2017-12-21 02:50:27 -05:00
if self.up:
self.blind_height -= self.SPEED
if self.blind_height <= 0:
self.up = False
self.callback()
else:
self.blind_height += self.SPEED
if self.blind_height >= self.get_display_surface().get_height() / self.BLIND_COUNT:
self.halt()
self.deactivate()
2017-12-21 06:59:45 -05:00
self.get_game().unsuppress_input()
2017-12-21 02:50:27 -05:00
2017-12-20 18:56:58 -05:00
def update(self):
"""
Use the blind height value and screen clipping to draw the screen wipe in the state indicated by the blind height. The
screen is clipped to rects based on the blind height, and only those rects will have the wipe graphic drawn. Other screen
areas will show what is being drawn behind the screen wipe.
"""
2017-12-20 18:56:58 -05:00
if self.active:
Animation.update(self)
2017-12-21 02:50:27 -05:00
ds = self.get_display_surface()
dsr = ds.get_rect()
for y in range(0, dsr.h, dsr.h // self.BLIND_COUNT):
2017-12-21 02:50:27 -05:00
if self.up:
ds.set_clip((0, y, dsr.w, dsr.h // self.BLIND_COUNT - self.blind_height))
2017-12-21 02:50:27 -05:00
else:
ds.set_clip((0, y + self.blind_height, dsr.w, dsr.h // self.BLIND_COUNT - self.blind_height))
ds.blit(self.image, (0, 0))
ds.set_clip(None)
2017-12-20 18:56:58 -05:00
class Platform(GameChild):
2022-02-24 01:16:45 -05:00
"""
This class contains methods for manipulating and getting information about the platform the player is standing on,
both the real one and on-screen representation. It initializes four Light objects, one for each pad on the platform.
It can set lights to glowing, return the states of individual lights or pairs of lights, reset lights, and draw the
on-screen representation.
"""
2017-12-20 18:56:58 -05:00
def __init__(self, parent):
2022-02-24 01:16:45 -05:00
"""
Initialize four lights, one for each pad on the platform. Initialize a Sprite for the pad graphics with one
frameset per six possible combinations of lights. Initialize masks for creating a glow effect on the pads.
"""
2017-12-20 18:56:58 -05:00
GameChild.__init__(self, parent)
self.lights = [
2022-01-27 15:10:02 -05:00
Light(self, self.get_configuration("pads", "nw_color"), NS.LNW),
Light(self, self.get_configuration("pads", "ne_color"), NS.LNE),
Light(self, self.get_configuration("pads", "se_color"), NS.LSE),
Light(self, self.get_configuration("pads", "sw_color"), NS.LSW)
2017-12-20 18:56:58 -05:00
]
2022-02-24 01:16:45 -05:00
self.view = Sprite(self)
self.view.load_from_path("pad", True)
self.view.add_frameset([0], name="neutral")
self.view.add_frameset([1], name=str(NS.N))
self.view.add_frameset([2], name=str(NS.E))
self.view.add_frameset([3], name=str(NS.NW))
self.view.add_frameset([4], name=str(NS.NE))
self.view.add_frameset([5], name=str(NS.W))
self.view.add_frameset([6], name=str(NS.S))
self.view.location.center = self.get_configuration("pads", "center")
self.glow_masks = []
base_images = load_frames(self.get_resource("pad_mask"), True)
for image in base_images:
self.glow_masks.append([image])
for mask in self.glow_masks:
intensity_resolution = 12
for intensity in range(1, intensity_resolution):
copy = mask[0].copy()
pixels = pygame.PixelArray(copy)
color = pygame.Color(0, 0, 0)
h, s, l, a = color.hsla
l = int(intensity / intensity_resolution * 100)
color.hsla = h, s, l, a
pixels.replace(pygame.Color(0, 0, 0), color)
del pixels
mask.append(copy)
2017-12-20 18:56:58 -05:00
def reset(self):
2022-02-24 01:16:45 -05:00
"""
Deactivate this object and reset each light
"""
2017-12-21 02:50:27 -05:00
self.deactivate()
2017-12-25 01:41:20 -05:00
self.reset_lights()
def reset_lights(self):
2017-12-20 18:56:58 -05:00
for light in self.lights:
light.reset()
2017-12-21 02:50:27 -05:00
def deactivate(self):
2022-02-24 01:16:45 -05:00
"""
This will stop the platform from being drawn and lights from updating
"""
2017-12-21 02:50:27 -05:00
self.active = False
def activate(self):
2022-02-24 01:16:45 -05:00
"""
This will cause the platform to get drawn and lights to update when this object's update method is called
"""
2017-12-21 02:50:27 -05:00
self.active = True
2017-12-21 06:59:45 -05:00
def unpress(self):
2022-02-24 01:16:45 -05:00
"""
Set the state of each light to unpressed
"""
2017-12-21 06:59:45 -05:00
for light in self.lights:
light.pressed = False
2017-12-20 18:56:58 -05:00
def get_pressed(self):
2022-02-24 01:16:45 -05:00
"""
Returns a list of light positions pressed (NS.LNW, NS.LNE, NS.LSE, NS.LSW)
"""
2017-12-20 18:56:58 -05:00
return [light.position for light in self.lights if light.pressed]
2017-12-20 19:31:25 -05:00
def get_edge_pressed(self):
2022-02-24 01:16:45 -05:00
"""
Gets the edge (2 light combination) currently pressed. This only returns one edge since there should only
be one able to be pressed at a time. If no edge is pressed, returns None.
@return NS.N | NS.NE | NS.E | NS.NW | NS.S | NS.W | None
"""
2017-12-20 19:31:25 -05:00
pressed = self.get_pressed()
2017-12-25 01:41:20 -05:00
if NS.LNW in pressed and NS.LNE in pressed:
2017-12-20 19:31:25 -05:00
return NS.N
2017-12-25 01:41:20 -05:00
elif NS.LNE in pressed and NS.LSW in pressed:
2017-12-20 19:31:25 -05:00
return NS.NE
2017-12-25 01:41:20 -05:00
elif NS.LNE in pressed and NS.LSE in pressed:
2017-12-20 19:31:25 -05:00
return NS.E
2017-12-25 01:41:20 -05:00
elif NS.LNW in pressed and NS.LSE in pressed:
2017-12-20 19:31:25 -05:00
return NS.NW
2017-12-25 01:41:20 -05:00
elif NS.LSE in pressed and NS.LSW in pressed:
2017-12-20 19:31:25 -05:00
return NS.S
2017-12-25 01:41:20 -05:00
elif NS.LSW in pressed and NS.LNW in pressed:
2017-12-20 19:31:25 -05:00
return NS.W
2022-02-24 01:16:45 -05:00
def get_glowing_edge(self):
"""
Return the edge currently glowing or None
@return NS.N | NS.NE | NS.E | NS.NW | NS.S | NS.W | None
"""
if self.lights[NS.LNW].glowing() and self.lights[NS.LNE].glowing():
return NS.N
elif self.lights[NS.LNE].glowing() and self.lights[NS.LSW].glowing():
return NS.NE
elif self.lights[NS.LNE].glowing() and self.lights[NS.LSE].glowing():
return NS.E
elif self.lights[NS.LNW].glowing() and self.lights[NS.LSE].glowing():
return NS.NW
elif self.lights[NS.LSE].glowing() and self.lights[NS.LSW].glowing():
return NS.S
elif self.lights[NS.LSW].glowing() and self.lights[NS.LNW].glowing():
return NS.W
2017-12-25 01:41:20 -05:00
def get_buttons_from_edges(self, edges):
2022-02-24 01:16:45 -05:00
"""
Get a list of light positions contained by a list of edges. For example, [NS.N, NS.E] would give [NS.LNW, NS.LNE, NS.LSE].
@param edges list of edges [NS.N | NS.NE | NS.E | NS.NW | NS.S | NS.W]
@return list of light positions [NS.LNW | NS.LNE | NS.LSE | NS.LSW]
"""
2017-12-25 01:41:20 -05:00
buttons = set()
for edge in edges:
if edge == NS.N:
buttons = buttons.union((NS.LNW, NS.LNE))
elif edge == NS.NE:
buttons = buttons.union((NS.LNE, NS.LSW))
elif edge == NS.E:
buttons = buttons.union((NS.LNE, NS.LSE))
elif edge == NS.NW:
buttons = buttons.union((NS.LNW, NS.LSE))
elif edge == NS.S:
buttons = buttons.union((NS.LSE, NS.LSW))
elif edge == NS.W:
buttons = buttons.union((NS.LSW, NS.LNW))
return list(buttons)
2017-12-28 08:01:58 -05:00
def get_steps_from_edge(self, edge):
2022-02-24 01:16:45 -05:00
"""
Get the edges that are one step away from a given edge. For example, NS.N would give (NS.NE, NS.NW) because those
are the edges that only require a pivot move of one step from NS.N.
@param edge one of NS.N, NS.NE, NS.E, NS.NW, NS.S, NS.W
@return pair of edges that are one step away
"""
2017-12-28 08:01:58 -05:00
if edge == NS.N:
return NS.NE, NS.NW
elif edge == NS.NE:
return NS.N, NS.E, NS.S, NS.W
elif edge == NS.E:
return NS.NE, NS.NW
elif edge == NS.NW:
return NS.N, NS.E, NS.S, NS.W
elif edge == NS.S:
return NS.NE, NS.NW
elif edge == NS.W:
return NS.NE, NS.NW
def get_right_angles_from_edge(self, edge):
2022-02-24 01:16:45 -05:00
"""
Get the pair of angles that are at a right angle to a given edge. For example, NS.N would return (NS.E, NW.W). For
diagonals, this returns None.
@param edge one of NS.N, NS.NE, NS.E, NS.NW, NS.S, NS.W
@return pair of edges that are at a right angle to given edge or None
"""
2017-12-28 08:01:58 -05:00
if edge == NS.N:
return NS.E, NS.W
elif edge == NS.NE:
return None
elif edge == NS.E:
return NS.N, NS.S
elif edge == NS.NW:
return None
elif edge == NS.S:
return NS.E, NS.W
elif edge == NS.W:
return NS.N, NS.S
def get_opposite_of_edge(self, edge):
2022-02-24 01:16:45 -05:00
"""
Get the edge opposite to a given edge. For example, NS.N would return NS.S. For diagonals, the opposite is the
reverse diagonal.
@param edge one of NS.N, NS.NE, NS.E, NS.NW, NS.S, NS.W
@return edge opposite to given edge, one of NS.N, NS.NE, NS.E, NS.NW, NS.S, NS.W
"""
2017-12-28 08:01:58 -05:00
if edge == NS.N:
return NS.S
elif edge == NS.NE:
return NS.NW
elif edge == NS.E:
return NS.W
elif edge == NS.NW:
return NS.NE
elif edge == NS.S:
return NS.N
elif edge == NS.W:
return NS.E
2018-06-21 15:17:22 -04:00
def get_color_pair_from_edge(self, edge):
2022-02-24 01:16:45 -05:00
"""
Return the pair of pygame color objects that make up a given edge
@param edge one of NS.N, NS.NE, NS.E, NS.NW, NS.S, NS.W
@return tuple of pygame color objects
"""
2018-06-21 15:17:22 -04:00
if edge == NS.N:
return self.lights[NS.LNW].color, self.lights[NS.LNE].color
elif edge == NS.NE:
return self.lights[NS.LNE].color, self.lights[NS.LSW].color
elif edge == NS.E:
return self.lights[NS.LNE].color, self.lights[NS.LSE].color
elif edge == NS.NW:
return self.lights[NS.LNW].color, self.lights[NS.LSE].color
elif edge == NS.S:
return self.lights[NS.LSW].color, self.lights[NS.LSE].color
elif edge == NS.W:
return self.lights[NS.LNW].color, self.lights[NS.LSW].color
2017-12-25 01:41:20 -05:00
def set_glowing(self, selected):
2022-02-24 01:16:45 -05:00
"""
Set the given light IDs to glowing and other indices to not glowing.
@param selected list of light IDs (NS.LNW, NS.LNE, NS.LSE, NS.LSW)
"""
2017-12-25 01:41:20 -05:00
for ii, light in enumerate(self.lights):
light.glow_index = 0
light.halt(light.glow)
if ii in selected:
light.play(light.glow)
2017-12-20 18:56:58 -05:00
def update(self):
2022-02-24 01:16:45 -05:00
"""
Update each light and draw the platform and glow effect
"""
2017-12-21 02:50:27 -05:00
if self.active:
for light in self.lights:
light.update()
2022-02-24 01:16:45 -05:00
# draw the pad based on which pads are glowing
glowing = self.get_glowing_edge()
if glowing is None:
self.view.set_frameset("neutral")
self.view.update()
else:
self.view.set_frameset(str(glowing))
self.view.update()
for light in self.lights:
if light.glowing():
self.get_display_surface().blit(self.glow_masks[light.position][light.glow_index], self.view.location, None, BLEND_RGBA_ADD)
# for light in self.lights:
# light.draw_glow()
2017-12-20 18:56:58 -05:00
2017-12-14 03:49:02 -05:00
class Light(Animation):
2022-02-24 01:16:45 -05:00
"""
This class represents a pad on the platform. Typically there are four instances for a platform, one for each corner of the
platform. Each light stores its color and position on the platform. This class contains methods for glowing the light and
getting its properties.
"""
2017-12-14 03:49:02 -05:00
2020-10-18 18:50:48 -04:00
TITLE_OFFSET = 0
2017-12-25 01:41:20 -05:00
2017-12-14 03:49:02 -05:00
def __init__(self, parent, color, position):
2022-02-24 01:16:45 -05:00
"""
Initialize a new Light object, providing color and position on the platform.
@param parent PGFW game object that instantiated this object
@param color pygame color object
@param position the light's position on the platform, one of NS.LNW, NS.LNE, NS.LSE, NS.LSW
"""
2017-12-14 03:49:02 -05:00
Animation.__init__(self, parent)
self.color = Color(color)
2017-12-25 01:41:20 -05:00
self.color.a = 225
2017-12-14 03:49:02 -05:00
self.position = position
self.pressed = False
ds = self.get_display_surface()
frontleft = ds.get_width() / 2 - NS.FRONT_WIDTH / 2, NS.FRONT
backleft = ds.get_width() / 2 - NS.BACK_WIDTH / 2, NS.FRONT + NS.LENGTH
left_step = get_step_relative(frontleft, backleft, NS.STEP)
midleft = frontleft[0] + left_step[0], frontleft[1] + left_step[1]
frontmid = ds.get_width() / 2, NS.FRONT
mid = ds.get_width() / 2, NS.FRONT + NS.LENGTH * NS.STEP
backmid = ds.get_width() / 2, NS.FRONT + NS.LENGTH
frontright = ds.get_width() / 2 + NS.FRONT_WIDTH / 2, NS.FRONT
backright = ds.get_width() / 2 + NS.BACK_WIDTH / 2, NS.FRONT + NS.LENGTH
right_step = get_step_relative(frontright, backright, NS.STEP)
midright = frontright[0] + right_step[0], frontright[1] + right_step[1]
2017-12-25 01:41:20 -05:00
if self.position == NS.LNW:
2017-12-14 03:49:02 -05:00
self.points = frontleft, frontmid, mid, midleft
2017-12-25 01:41:20 -05:00
elif self.position == NS.LNE:
2017-12-14 03:49:02 -05:00
self.points = frontmid, frontright, midright, mid
2017-12-25 01:41:20 -05:00
elif self.position == NS.LSE:
2017-12-14 03:49:02 -05:00
self.points = mid, midright, backright, backmid
2017-12-25 01:41:20 -05:00
elif self.position == NS.LSW:
2017-12-14 03:49:02 -05:00
self.points = midleft, mid, backmid, backleft
2017-12-25 01:41:20 -05:00
self.register(self.glow)
2017-12-14 03:49:02 -05:00
def reset(self):
2022-02-24 01:16:45 -05:00
"""
Unhide, halt glow animation
"""
2017-12-14 03:49:02 -05:00
self.hidden = False
2017-12-25 01:41:20 -05:00
self.halt(self.glow)
2017-12-20 18:56:58 -05:00
self.reset_timer()
2017-12-25 01:41:20 -05:00
self.glow_index = 0
2017-12-14 03:49:02 -05:00
2017-12-25 01:41:20 -05:00
def glow(self):
2022-02-24 01:16:45 -05:00
"""
Moves the glow animation forward a frame by incrementing an index
"""
2017-12-25 01:41:20 -05:00
self.glow_index += 1
2022-02-24 01:16:45 -05:00
if self.glow_index >= len(self.parent.glow_masks[0]):
2017-12-25 01:41:20 -05:00
self.glow_index = 0
2017-12-14 03:49:02 -05:00
def update(self):
2022-02-24 01:16:45 -05:00
"""
Checks the attack state to determine whether to start or stop glowing
"""
2017-12-14 03:49:02 -05:00
Animation.update(self)
2020-10-18 18:50:48 -04:00
if not self.get_game().title.active:
2017-12-25 01:41:20 -05:00
boss = self.get_game().boss
chemtrails = self.get_game().chemtrails
2022-02-24 01:16:45 -05:00
# checks the boss attack queue and chameleon queue index to see if the glow should be started now
2022-02-24 20:53:34 -05:00
if boss.queue and not self.is_playing(self.glow) \
2017-12-25 01:41:20 -05:00
and self.in_orientation(boss.queue[chemtrails.queue_index]):
2017-12-28 08:01:58 -05:00
self.play(self.glow)
2022-02-24 01:16:45 -05:00
# turns off the glow
elif self.is_playing(self.glow) and (not boss.queue or not self.in_orientation(boss.queue[chemtrails.queue_index])):
2017-12-25 01:41:20 -05:00
self.reset()
2018-07-09 22:38:07 -04:00
def get_points(self):
2020-10-18 18:50:48 -04:00
if self.get_game().title.active:
2017-12-25 01:41:20 -05:00
points = []
for point in self.points:
2020-10-18 18:50:48 -04:00
points.append((point[0], point[1] - self.TITLE_OFFSET))
2018-07-09 22:38:07 -04:00
return points
else:
return self.points
def draw_glow(self):
for ii, y in enumerate(range(0, self.glow_index, 3)):
2018-07-09 22:38:07 -04:00
shifted = []
for point in self.get_points():
2018-08-03 02:27:18 -04:00
shifted.append((point[0], point[1] - y))
if self.position == NS.LSW:
saturation = 0
else:
saturation = int((self.color.hsla[1] + 80) % 100)
if not ii % 2:
lightness = 0
else:
lightness = 40
lines(
self.get_display_surface(),
get_hsla_color(
int(self.color.hsla[0]), saturation, lightness
),
True, shifted, 3
)
2017-12-14 03:49:02 -05:00
def in_orientation(self, orientation):
2022-02-24 01:16:45 -05:00
"""
Returns True if this light is contained in the given edge
@param orientation edge to check, one of NS.N, NS.NW, NS.W, NS.NE, NS.E, NS.S
@return True | False
"""
2017-12-25 01:41:20 -05:00
if self.position == NS.LNW:
2017-12-20 18:56:58 -05:00
return orientation in (NS.N, NS.NW, NS.W)
2017-12-25 01:41:20 -05:00
elif self.position == NS.LNE:
2017-12-20 18:56:58 -05:00
return orientation in (NS.N, NS.NE, NS.E)
2017-12-25 01:41:20 -05:00
elif self.position == NS.LSE:
2017-12-20 18:56:58 -05:00
return orientation in (NS.NW, NS.E, NS.S)
2017-12-25 01:41:20 -05:00
elif self.position == NS.LSW:
2017-12-20 18:56:58 -05:00
return orientation in (NS.S, NS.NE, NS.W)
2017-12-14 03:49:02 -05:00
2022-02-24 01:16:45 -05:00
def glowing(self):
"""
Returns True if this light is glowing, False otherwise
@return True | False
"""
return self.is_playing(self.glow)
2017-12-14 03:49:02 -05:00
2018-01-02 17:24:45 -05:00
class Chemtrails(Sprite):
2022-02-25 00:11:53 -05:00
"""
This class stores the graphics and state of the player character. It contains sprite frames, health and life objects, and the
timer that counts down the amount of time left to perform a move.
"""
2017-12-14 03:49:02 -05:00
def __init__(self, parent):
2022-02-25 00:11:53 -05:00
"""
Load the sprite frames, one for each pad orientation. Initialize a health object, lives object, and timer.
@param parent PGFW game object that initialized this object
"""
2018-01-02 17:24:45 -05:00
Sprite.__init__(self, parent)
self.load_from_path(self.get_resource("littleSlimeGoop"), True)
2022-02-25 00:11:53 -05:00
# one frame for each pad orientation
2018-01-02 17:24:45 -05:00
for direction in (NS.N, NS.NE, NS.E, NS.NW, NS.S, NS.W):
self.add_frameset([direction], switch=(direction == NS.N))
2017-12-14 03:49:02 -05:00
self.life = Life(self)
2018-09-20 04:57:06 -04:00
self.boys = Boys(self)
2017-12-25 05:30:10 -05:00
self.timer = Timer(self)
2017-12-14 03:49:02 -05:00
def reset(self):
2022-02-25 00:11:53 -05:00
"""
Reset the health, lives, and timer objects and deactivate.
"""
2017-12-21 02:50:27 -05:00
self.deactivate()
2017-12-14 03:49:02 -05:00
self.life.reset()
2018-09-20 04:57:06 -04:00
self.boys.reset()
2017-12-25 05:30:10 -05:00
self.timer.reset()
2017-12-14 03:49:02 -05:00
2017-12-21 02:50:27 -05:00
def deactivate(self):
self.active = False
def activate(self):
self.active = True
2017-12-14 03:49:02 -05:00
def challenge(self):
2022-02-25 00:11:53 -05:00
"""
Start an attempt against a new queue of swords to be cleared.
"""
2017-12-25 05:30:10 -05:00
self.timer.reset()
2017-12-14 03:49:02 -05:00
self.queue_index = 0
def update(self):
2017-12-21 02:50:27 -05:00
if self.active:
self.orient()
2018-01-02 17:24:45 -05:00
Sprite.update(self)
2020-10-18 18:50:48 -04:00
if not self.get_game().title.active:
2017-12-28 08:01:58 -05:00
boss = self.get_game().boss
if boss.queue:
2017-12-25 05:30:10 -05:00
self.timer.tick()
2017-12-25 01:41:20 -05:00
self.attack()
2018-09-20 04:57:06 -04:00
if self.timer.amount < 0:
2017-12-25 01:41:20 -05:00
self.life.decrease()
if not boss.is_playing(boss.show_end_dialogue, include_delay=True):
2017-12-25 05:30:10 -05:00
self.timer.reset()
2017-12-28 08:01:58 -05:00
boss.combo()
if not boss.is_playing(boss.show_introduction_dialogue, include_delay=True):
2018-09-20 04:57:06 -04:00
self.timer.update()
self.life.update()
2018-11-21 01:19:29 -05:00
# self.boys.update()
2017-12-14 03:49:02 -05:00
def attack(self):
2022-02-25 00:11:53 -05:00
"""
Hit the boss if this is called while the boss attack queue is active and the player is in the correct orientation.
Add time to the timer, decrease the boss's health, and play a sound effect. If the queue is finished, reset the
timer completely and trigger another boss combo.
"""
2017-12-14 03:49:02 -05:00
boss = self.get_game().boss
queue = boss.queue
if self.orientation == queue[self.queue_index]:
2018-06-29 01:24:52 -04:00
self.timer.add_time(self.get_configuration("time", "timer-addition"))
2017-12-28 08:01:58 -05:00
if boss.level_index == 0:
boss.health.decrease(4)
elif boss.level_index == 1:
boss.health.decrease(4)
elif boss.level_index == 2:
boss.health.decrease(4)
2017-12-14 03:49:02 -05:00
self.queue_index += 1
2017-12-28 08:01:58 -05:00
boss.last_attack = self.orientation
2018-01-02 20:26:30 -05:00
boss.sword.block()
2017-12-14 03:49:02 -05:00
if self.queue_index == len(queue):
2017-12-25 05:30:10 -05:00
self.timer.reset()
if not boss.is_playing(boss.show_end_dialogue, include_delay=True):
2017-12-28 08:01:58 -05:00
boss.combo()
self.get_audio().play_sfx("complete_pattern_3")
2018-01-02 17:24:45 -05:00
else:
self.get_audio().play_sfx("land_0")
2017-12-25 01:41:20 -05:00
self.get_game().platform.reset_lights()
2017-12-14 03:49:02 -05:00
def orient(self):
2022-02-25 00:11:53 -05:00
"""
Place the sprite on screen based on which edge is being pressed by the player on the real mat.
"""
2017-12-14 03:49:02 -05:00
ds = self.get_display_surface()
2017-12-20 19:31:25 -05:00
edge = self.get_game().platform.get_edge_pressed()
2020-10-18 18:50:48 -04:00
dy = -Light.TITLE_OFFSET if self.get_game().title.active else 0
2018-01-02 17:24:45 -05:00
if edge is not None:
self.set_frameset(edge + 1)
self.unhide()
else:
self.hide()
2017-12-20 19:31:25 -05:00
if edge == NS.N:
2022-02-25 00:11:53 -05:00
self.location.center = ds.get_width() / 2, NS.FRONT + dy - 10
2017-12-20 18:56:58 -05:00
self.orientation = NS.N
2017-12-20 19:31:25 -05:00
elif edge == NS.E:
self.location.center = ds.get_width() / 2 + NS.FRONT_WIDTH / 2 - 85, \
2022-02-25 00:11:53 -05:00
NS.FRONT + NS.LENGTH * NS.STEP - 40 + dy
2017-12-20 18:56:58 -05:00
self.orientation = NS.E
2017-12-20 19:31:25 -05:00
elif edge == NS.S:
2018-01-02 17:24:45 -05:00
self.location.center = ds.get_width() / 2, \
2022-02-25 00:11:53 -05:00
NS.FRONT + NS.LENGTH - NS.LENGTH * NS.STEP - 65 + dy
2017-12-20 18:56:58 -05:00
self.orientation = NS.S
2017-12-20 19:31:25 -05:00
elif edge == NS.W:
2022-02-25 00:11:53 -05:00
self.location.center = ds.get_width() / 2 - NS.FRONT_WIDTH / 2 + 85, \
NS.FRONT + NS.LENGTH * NS.STEP - 40 + dy
2017-12-20 18:56:58 -05:00
self.orientation = NS.W
2017-12-20 19:31:25 -05:00
elif edge == NS.NW:
2022-02-25 00:11:53 -05:00
self.location.center = ds.get_width() / 2, \
NS.FRONT + NS.LENGTH * NS.STEP + dy - 45
2017-12-20 18:56:58 -05:00
self.orientation = NS.NW
2017-12-20 19:31:25 -05:00
elif edge == NS.NE:
2022-02-25 00:11:53 -05:00
self.location.center = ds.get_width() / 2 - 5, \
NS.FRONT + NS.LENGTH * NS.STEP - 45 + dy
2017-12-20 18:56:58 -05:00
self.orientation = NS.NE
2017-12-14 03:49:02 -05:00
else:
self.orientation = None
2018-09-20 04:57:06 -04:00
class Timer(Meter):
2017-12-25 05:30:10 -05:00
def __init__(self, parent):
2018-09-20 04:57:06 -04:00
Meter.__init__(self, parent)
2017-12-25 05:30:10 -05:00
dsr = self.get_display_surface().get_rect()
2018-09-20 04:57:06 -04:00
background = load(self.get_resource("HUD_timer.png")).convert()
rect = background.get_rect()
rect.bottomright = dsr.right - 6, dsr.bottom - 4
self.setup(background, rect, 53, (0, 0, 255),
2018-11-21 01:19:29 -05:00
self.get_configuration("time", "timer-start-time"), "scrapeIcons/scrapeIcons_07.png")
2017-12-25 05:30:10 -05:00
def add_time(self, amount):
2018-09-20 04:57:06 -04:00
self.change(amount)
2017-12-25 05:30:10 -05:00
def tick(self):
2018-09-20 04:57:06 -04:00
self.change(-self.get_game().time_filter.get_last_frame_duration())
2017-12-25 05:30:10 -05:00
2017-12-14 03:49:02 -05:00
2018-09-20 04:57:06 -04:00
class Life(Meter):
2017-12-14 03:49:02 -05:00
2018-09-20 04:57:06 -04:00
def __init__(self, parent):
Meter.__init__(self, parent)
dsr = self.get_display_surface().get_rect()
background = load(self.get_resource("HUD_health.png")).convert()
rect = background.get_rect()
2022-02-24 20:53:34 -05:00
rect.bottomleft = 4, dsr.bottom - 4
2018-11-21 01:19:29 -05:00
self.setup(background, rect, 70, (255, 0, 0), 3, "scrapeIcons/scrapeIcons_03.png")
2017-12-14 03:49:02 -05:00
def decrease(self):
self.get_audio().play_sfx("hurt")
2018-09-20 04:57:06 -04:00
self.change(-1)
if self.amount <= 0:
self.amount = 0
self.parent.boys.change(-1)
2017-12-28 08:01:58 -05:00
self.get_game().boss.finish_battle(False)
2017-12-14 03:49:02 -05:00
2018-09-20 04:57:06 -04:00
class Boys(Meter):
def __init__(self, parent):
Meter.__init__(self, parent)
dsr = self.get_display_surface().get_rect()
background = load(self.get_resource("HUD_lives.png")).convert()
rect = background.get_rect()
rect.bottomleft = 6, dsr.bottom - 4
2018-11-21 01:19:29 -05:00
self.setup(background, rect, 60, (0, 255, 0), 3, "scrapeIcons/scrapeIcons_01.png")
2017-12-14 03:49:02 -05:00
2017-12-28 08:01:58 -05:00
class Boss(Animation):
"""
The Boss object also serves as the level object, and it is expected that only one of these objects is initialized.
Its drawing, animations, timings, etc will be determined by the level_index member variable. For example, if
level_index is 0, the kool man sprite will be drawn, but if level_index is 2, the spoopy sprite will be drawn.
"""
2017-12-14 03:49:02 -05:00
def __init__(self, parent):
"""
Load graphics for boss sprites, avatars, and backgrounds. Initialize boss health and swords objects. Register
animations that control attacks, effects, and dialog.
"""
2017-12-28 08:01:58 -05:00
Animation.__init__(self, parent)
hue_shift = 30
if self.get_configuration("display", "effects"):
self.kool_man = RainbowSprite(self, load(self.get_resource("Kool_man_waah.png")).convert_alpha(), hue_shift)
self.spoopy = RainbowSprite(self, load(self.get_resource("Spoopy.png")).convert_alpha(), hue_shift)
else:
self.kool_man = Sprite(self)
self.kool_man.load_from_path("Kool_man_waah.png", True)
self.spoopy = Sprite(self)
self.spoopy.load_from_path("Spoopy.png", True)
self.visitor = Sprite(self, 42)
self.visitor.load_from_path("alienAnimations/alienBoil", True)
2018-08-03 02:27:18 -04:00
for sprite in self.kool_man, self.visitor, self.spoopy:
2022-02-24 20:53:34 -05:00
sprite.location.topleft = 207, 10
2017-12-14 03:49:02 -05:00
self.health = Health(self)
self.sword = Sword(self)
2017-12-28 08:01:58 -05:00
self.register(self.brandish, self.cancel_flash, self.show_introduction_dialogue,
2020-10-30 03:57:46 -04:00
self.show_end_dialogue, self.end_dialogue)
2017-12-28 08:01:58 -05:00
self.kool_man.add_frameset([0], name="normal", switch=True)
# Set alien's normal frameset to an idle animation
self.visitor.add_frameset(list(range(0, len(self.visitor.frames))), name="normal", switch=True)
2017-12-28 08:01:58 -05:00
self.spoopy.add_frameset([0], name="normal", switch=True)
self.kool_man_avatar = load(self.get_resource("Kool_man_avatar.png")).convert()
self.visitor_avatar = load(self.get_resource("Visitor_avatar.png")).convert()
self.spoopy_avatar = load(self.get_resource("Spoopy_avatar.png")).convert()
2017-12-25 01:41:20 -05:00
self.advance_prompt = AdvancePrompt(self)
2018-06-27 21:21:51 -04:00
self.backgrounds = [Sprite(self), Sprite(self), Sprite(self)]
2018-08-03 02:27:18 -04:00
self.backgrounds[0].load_from_path(self.get_resource("bg/bg001.png"))
self.backgrounds[1].load_from_path(self.get_resource("bg/bg002.png"))
self.backgrounds[2].load_from_path(self.get_resource("bg/bg003.png"))
2018-09-23 01:01:36 -04:00
self.countdown = Countdown(self)
# Set the alien's arm to its own sprite
self.alien_arm = Sprite(self, 42)
# Map the strings used to indicate direction in the animations directory to the IDs defined in the script
name_map = {
"U": NS.N,
"DR": NS.NE,
"R": NS.E,
"DL": NS.NW,
"D": NS.S,
"L": NS.W,
}
# Set static frames for alien arms, one for each of the 6 board orientations
root = pathlib.Path(self.get_resource("alienAnimations/alienArms/Moving"))
static_arm_frame_map = {
"UtoDR/*05.png": NS.N,
"UtoDR/*10.png": NS.NE,
"RtoDL/*05.png": NS.E,
"RtoDL/*10.png": NS.NW,
"DtoL/*05.png": NS.S,
"DtoL/*10.png": NS.W
}
orientation_frame_indices = {}
for path, orientation in static_arm_frame_map.items():
2022-02-24 20:53:34 -05:00
base = pygame.image.load(str(list(root.glob(path))[0]))
colorkeyed = fill_colorkey(base)
self.alien_arm.add_frame(colorkeyed)
frame_index = len(self.alien_arm.frames) - 1
self.alien_arm.add_frameset([frame_index], name=str(orientation))
orientation_frame_indices[orientation] = frame_index
# Add sword smear animations to the alien's arm, one for each of the 30 possible combinations of 6 board orientations
for directory in pathlib.Path(self.get_resource("alienAnimations/alienArms/Moving")).iterdir():
if directory.is_dir():
frame_paths = list(sorted(directory.iterdir()))
# Extract board orientation IDs from the directory name
orientation_1, orientation_2 = [name_map[orientation] for orientation in directory.name.split("to")]
# Alien arm sprite frame indices for each orientation
frame_index_orientation_1 = orientation_frame_indices[orientation_1]
frame_index_orientation_2 = orientation_frame_indices[orientation_2]
# Add orientation_1 -> orientation_2 animation
frame_order = [frame_index_orientation_1]
for path in frame_paths[5:9]:
self.alien_arm.load_from_path(path, True)
frame_order.append(len(self.alien_arm.frames) - 1)
frame_order.append(frame_index_orientation_2)
self.alien_arm.add_frameset(frame_order, name=f"{orientation_1}_{orientation_2}")
# Add orientation_2 -> orientation_1 animation
frame_order = [frame_index_orientation_2]
for path in frame_paths[0:4]:
self.alien_arm.load_from_path(path, True)
frame_order.append(len(self.alien_arm.frames) - 1)
frame_order.append(frame_index_orientation_1)
self.alien_arm.add_frameset(frame_order, name=f"{orientation_2}_{orientation_1}")
self.alien_arm.location.center = self.visitor.location.center
self.alien_arm.hide()
2017-12-14 03:49:02 -05:00
def cancel_flash(self):
2017-12-28 08:01:58 -05:00
if self.level_index == 0:
self.kool_man.set_frameset("normal")
elif self.level_index == 1:
self.visitor.set_frameset("normal")
elif self.level_index == 2:
self.spoopy.set_frameset("normal")
def start_level(self, index):
2018-09-20 04:57:06 -04:00
self.level_index = index
2017-12-28 08:01:58 -05:00
self.battle_finished = False
self.player_defeated = False
self.health.reset()
self.get_game().chemtrails.timer.reset()
self.get_game().chemtrails.life.reset()
2017-12-25 01:41:20 -05:00
self.activate()
dialogue = self.get_game().dialogue
dialogue.deactivate()
2017-12-28 08:01:58 -05:00
if index == 0:
dialogue.set_avatar(self.kool_man_avatar)
dialogue.set_name("Kool Man")
self.kool_man.unhide()
self.kool_man.set_frameset("normal")
elif index == 1:
dialogue.set_avatar(self.visitor_avatar)
dialogue.set_name("Visitor")
self.visitor.unhide()
self.visitor.set_frameset("normal")
elif index == 2:
dialogue.set_avatar(self.spoopy_avatar)
dialogue.set_name("Spoopy")
self.spoopy.unhide()
self.spoopy.set_frameset("normal")
self.get_audio().play_bgm(f"level_{index}")
2017-12-28 08:01:58 -05:00
self.play(self.show_introduction_dialogue, delay=3000, play_once=True)
2017-12-25 01:41:20 -05:00
self.get_game().platform.activate()
self.get_game().chemtrails.activate()
2017-12-28 08:01:58 -05:00
self.last_attack = NS.NW
2017-12-25 01:41:20 -05:00
2017-12-28 08:01:58 -05:00
def show_introduction_dialogue(self):
2017-12-25 01:41:20 -05:00
dialogue = self.get_game().dialogue
dialogue.activate()
2017-12-28 08:01:58 -05:00
if self.level_index == 0:
dialogue.show_text("You'll never be able to block my sword, you lizard slime!" +
2022-02-24 20:53:34 -05:00
" See if you can keep up\nwith these moves!")
2017-12-28 08:01:58 -05:00
elif self.level_index == 1:
dialogue.show_text("We're just warming up, slime breath! Prepare to get spun" +
2022-02-24 20:53:34 -05:00
" by these combos!")
2017-12-28 08:01:58 -05:00
elif self.level_index == 2:
dialogue.show_text("Lizard! My moves are so unpredictable you might as well" +
2022-02-24 20:53:34 -05:00
" give up now!")
2020-10-30 03:57:46 -04:00
self.play(self.end_dialogue, delay=5000, play_once=True)
2017-12-25 01:41:20 -05:00
2017-12-14 03:49:02 -05:00
def reset(self):
2017-12-28 08:01:58 -05:00
self.level_index = 0
2018-01-01 15:02:36 -05:00
self.kills = 0
2018-01-02 22:56:53 -05:00
self.time_elapsed = 0
2017-12-21 02:50:27 -05:00
self.deactivate()
2017-12-14 03:49:02 -05:00
self.cancel_flash()
self.halt(self.cancel_flash)
self.health.reset()
self.halt(self.brandish)
self.sword.reset()
2017-12-25 01:41:20 -05:00
self.advance_prompt.reset()
2017-12-14 03:49:02 -05:00
self.queue = None
self.brandish_complete = True
2018-09-23 01:01:36 -04:00
self.countdown.reset()
2020-10-30 03:57:46 -04:00
self.halt(self.end_dialogue)
2017-12-14 03:49:02 -05:00
2017-12-21 02:50:27 -05:00
def deactivate(self):
self.active = False
def activate(self):
self.active = True
2020-10-30 03:57:46 -04:00
def combo(self, delay=2500):
2017-12-14 03:49:02 -05:00
self.queue = None
2018-11-21 01:19:29 -05:00
if self.get_game().serial_enabled():
self.get_game().reset_arduino()
2020-10-30 03:57:46 -04:00
self.play(self.brandish, delay=delay, play_once=True)
2017-12-14 03:49:02 -05:00
def brandish(self):
self.queue = []
2017-12-28 08:01:58 -05:00
platform = self.get_game().platform
if self.level_index == 0:
if self.health.amount > 90:
first = choice(platform.get_steps_from_edge(self.last_attack))
self.queue = [first]
elif self.health.amount > 70:
first = choice(platform.get_steps_from_edge(self.last_attack))
self.queue = [first, choice(platform.get_steps_from_edge(first))]
elif self.health.amount > 30:
choices = [0]
if self.last_attack in (NS.NE, NS.NW):
choices.append(1)
else:
choices.extend((2, 3))
result = choice(choices)
if result == 0:
first = choice(platform.get_steps_from_edge(self.last_attack))
second = choice(platform.get_steps_from_edge(first))
self.queue = [first, second, first, second]
elif result == 1:
first = choice(platform.get_steps_from_edge(self.last_attack))
self.queue = [first, choice(platform.get_steps_from_edge(first)),
choice(platform.get_right_angles_from_edge(first))]
elif result == 2:
first = choice(platform.get_steps_from_edge(self.last_attack))
self.queue = [first, choice(platform.get_steps_from_edge(first)),
platform.get_opposite_of_edge(first)]
elif result == 3:
first = choice(platform.get_steps_from_edge(self.last_attack))
second = choice(platform.get_steps_from_edge(first))
self.queue = [first, second,
choice(platform.get_right_angles_from_edge(second))]
else:
choices = [0, 1]
if self.last_attack in (NS.NE, NS.NW):
choices.extend((2, 3, 4))
else:
choices.append(5)
result = choice(choices)
if result == 0 or result == 1:
first = choice(platform.get_steps_from_edge(self.last_attack))
second = choice(platform.get_steps_from_edge(first))
last = second if result else platform.get_opposite_of_edge(second)
self.queue = [first, second, platform.get_opposite_of_edge(first),
last]
elif result == 2:
first = choice(platform.get_steps_from_edge(self.last_attack))
self.queue = [first, choice(platform.get_right_angles_from_edge(first)),
platform.get_opposite_of_edge(first)]
elif result == 3:
first = choice(platform.get_steps_from_edge(self.last_attack))
self.queue = [first, choice(platform.get_steps_from_edge(first)),
choice(platform.get_right_angles_from_edge(first)),
platform.get_opposite_of_edge(first)]
elif result == 4:
first = choice(platform.get_steps_from_edge(self.last_attack))
second = choice(platform.get_steps_from_edge(first))
self.queue = [first, second,
choice(platform.get_right_angles_from_edge(first)),
platform.get_opposite_of_edge(second)]
elif result == 5:
first = choice(platform.get_steps_from_edge(self.last_attack))
second = choice(platform.get_steps_from_edge(first))
self.queue = [first, second, platform.get_opposite_of_edge(first),
choice(platform.get_right_angles_from_edge(second))]
elif self.level_index == 1:
if self.health.amount > 85:
if self.last_attack in (NS.NE, NS.NW):
choices = 1, 2
else:
choices = 0,
result = choice(choices)
if result == 0:
first = choice(platform.get_steps_from_edge(self.last_attack))
self.queue = [first, platform.get_opposite_of_edge(first)]
elif result == 1:
first = choice(platform.get_steps_from_edge(self.last_attack))
self.queue = [first, choice(platform.get_right_angles_from_edge(first)),
platform.get_opposite_of_edge(first)]
elif result == 2:
first = choice(platform.get_steps_from_edge(self.last_attack))
self.queue = [first, platform.get_opposite_of_edge(first)]
elif self.health.amount > 60:
if self.last_attack in (NS.NE, NS.NW):
choices = 2, 3
else:
choices = 0, 1
result = choice(choices)
first = choice(platform.get_steps_from_edge(self.last_attack))
if result == 0:
second = choice(platform.get_steps_from_edge(first))
self.queue = [first, second, platform.get_opposite_of_edge(second)]
elif result == 1:
second = choice(platform.get_steps_from_edge(first))
self.queue = [first, second,
choice(platform.get_right_angles_from_edge(second)),
platform.get_opposite_of_edge(first)]
elif result == 2:
second = platform.get_opposite_of_edge(first)
self.queue = [first, second,
choice(platform.get_right_angles_from_edge(second))]
elif result == 3:
second = choice(platform.get_right_angles_from_edge(first))
self.queue = [first, second, platform.get_opposite_of_edge(first),
platform.get_opposite_of_edge(second)]
elif self.health.amount > 30:
result = choice(range(3))
if result == 0:
first = self.choose_new_edge((NS.N, NS.E, NS.S, NS.W))
self.queue = [first, choice(platform.get_steps_from_edge(first)),
platform.get_opposite_of_edge(first), first]
elif result == 1:
first = self.choose_new_edge((NS.NE, NS.NW))
second = choice(platform.get_steps_from_edge(first))
self.queue = [first, second, platform.get_opposite_of_edge(second),
choice(platform.get_right_angles_from_edge(second))]
elif result == 2:
first = self.choose_new_edge((NS.NE, NS.NW))
second = choice(platform.get_steps_from_edge(first))
self.queue = [first, second,
choice(platform.get_right_angles_from_edge(second)),
platform.get_opposite_of_edge(second)]
else:
result = choice(range(4))
if result == 0:
first = self.choose_new_edge((NS.NE, NS.NW))
second = platform.get_opposite_of_edge(first)
self.queue = [first, second, first, second]
elif result == 1:
first = self.choose_new_edge((NS.N, NS.E, NS.S, NS.W))
self.queue = [first, platform.get_opposite_of_edge(first), first]
elif result == 2:
first = self.choose_new_edge((NS.N, NS.E, NS.S, NS.W))
self.queue = [first, choice(platform.get_steps_from_edge(first)),
choice(platform.get_right_angles_from_edge(first)),
platform.get_opposite_of_edge(first), first]
elif result == 3:
first = self.choose_new_edge((NS.N, NS.E, NS.S, NS.W))
second = platform.get_opposite_of_edge(first)
third = choice(platform.get_right_angles_from_edge(first))
self.queue = [first, second, third, platform.get_opposite_of_edge(second),
platform.get_opposite_of_edge(third)]
elif self.level_index == 2:
if self.health.amount > 90:
length = 3
elif self.health.amount > 70:
length = 4
elif self.health.amount > 40:
length = 5
else:
length = 6
while len(self.queue) < length:
while True:
orientation = randint(0, 5)
if (not self.queue and orientation != self.last_attack) or \
(len(self.queue) > 0 and orientation != self.queue[-1]):
self.queue.append(orientation)
break
2017-12-14 03:49:02 -05:00
self.unbrandished = copy(self.queue)
self.brandish_complete = False
2018-01-02 20:26:30 -05:00
self.sword.reset()
2017-12-14 03:49:02 -05:00
self.sword.play(self.sword.brandish, play_once=True)
self.get_game().chemtrails.challenge()
2017-12-28 08:01:58 -05:00
def choose_new_edge(self, edges):
while True:
edge = choice(edges)
if edge != self.last_attack:
return edge
def finish_battle(self, win):
self.battle_finished = True
self.halt(self.brandish)
self.halt(self.cancel_flash)
self.sword.reset()
self.queue = []
self.brandish_complete = True
if win:
if self.level_index == 0:
self.kool_man.set_frameset(0)
elif self.level_index == 1:
self.visitor.set_frameset(0)
elif self.level_index == 2:
self.spoopy.set_frameset(0)
2018-01-02 22:56:53 -05:00
self.add_score()
2017-12-28 08:01:58 -05:00
self.player_defeated = not win
2018-01-02 20:26:30 -05:00
self.kills += not win
2017-12-28 08:01:58 -05:00
self.play(self.show_end_dialogue, delay=3000, play_once=True)
2018-01-02 22:56:53 -05:00
def add_score(self):
self.get_game().set_most_recent_time(self.time_elapsed)
open(self.get_resource("scores"), "a").write(str(self.time_elapsed) + "\n")
2018-01-02 22:56:53 -05:00
2017-12-28 08:01:58 -05:00
def show_end_dialogue(self):
dialogue = self.get_game().dialogue
dialogue.activate()
if self.level_index == 0:
if self.player_defeated:
dialogue.show_text("Maybe next time!")
else:
dialogue.show_text("Hey! Wow! Lizard!")
elif self.level_index == 1:
if self.player_defeated:
dialogue.show_text("Wiped out!")
else:
dialogue.show_text("Well done! But it's not over yet!")
elif self.level_index == 2:
if self.player_defeated:
dialogue.show_text("Just like I thought!")
else:
dialogue.show_text("H-how? But you're only a lizard! How could you" +
2022-02-24 20:53:34 -05:00
" manage to defeat all of us?")
2018-09-23 01:01:36 -04:00
if self.player_defeated:
self.countdown.activate()
2020-10-30 03:57:46 -04:00
else:
self.play(self.end_dialogue, delay=5000, play_once=True)
2017-12-28 08:01:58 -05:00
def transition_to_battle(self):
index = self.level_index + (not self.player_defeated)
2018-01-02 20:51:42 -05:00
if self.kills >= 3:
self.get_game().reset(True)
elif index < 3:
2017-12-28 08:01:58 -05:00
self.start_level(index)
else:
2018-01-02 20:51:42 -05:00
game = self.get_game()
game.boss.reset()
game.chemtrails.reset()
game.platform.reset()
game.ending.activate()
2018-01-01 15:02:36 -05:00
def transition_to_title(self):
self.get_game().reset(True)
2017-12-28 08:01:58 -05:00
def damage(self):
if self.level_index == 0:
self.kool_man.set_frameset(0)
elif self.level_index == 1:
self.visitor.set_frameset(0)
2018-07-03 18:10:26 -04:00
elif self.level_index == 2:
self.spoopy.set_frameset(0)
2017-12-28 08:01:58 -05:00
2020-10-30 03:57:46 -04:00
def end_dialogue(self):
self.get_game().dialogue.deactivate()
if not self.battle_finished:
self.combo(delay=1300)
else:
self.get_game().wipe.start(self.transition_to_battle)
2017-12-14 03:49:02 -05:00
def update(self):
2017-12-21 02:50:27 -05:00
if self.active:
2018-06-27 21:21:51 -04:00
self.backgrounds[self.level_index].update()
2017-12-25 01:41:20 -05:00
dialogue = self.get_game().dialogue
2020-10-30 03:57:46 -04:00
if self.countdown.active and dialogue.active and self.get_game().chemtrails.boys.amount > 0:
2017-12-25 01:41:20 -05:00
if self.advance_prompt.check_first_press():
self.advance_prompt.press_first()
elif self.advance_prompt.check_second_press():
2018-09-23 01:01:36 -04:00
self.countdown.deactivate()
2017-12-25 01:41:20 -05:00
if dialogue.is_playing():
dialogue.show_all()
else:
self.get_game().dialogue.deactivate()
2017-12-28 08:01:58 -05:00
if not self.battle_finished:
self.combo()
else:
self.get_game().wipe.start(self.transition_to_battle)
2017-12-25 03:48:36 -05:00
self.advance_prompt.cancel_first_press()
2018-01-02 22:56:53 -05:00
else:
self.time_elapsed += self.get_game().time_filter.get_last_frame_duration()
2017-12-28 08:01:58 -05:00
Animation.update(self)
if self.level_index == 0:
self.kool_man.update()
elif self.level_index == 1:
self.visitor.update()
2022-02-24 20:53:34 -05:00
if self.brandish_complete:
if self.queue is not None:
self.alien_arm.unhide()
remaining_positions = list(reversed(self.queue[self.get_game().chemtrails.queue_index:]))
for ii, position in enumerate(remaining_positions):
alpha = int((ii + 1) / len(remaining_positions) * 255)
self.alien_arm.set_frameset(str(position))
self.alien_arm.get_current_frame().set_alpha(alpha)
self.alien_arm.update()
self.alien_arm.get_current_frame().set_alpha(255)
else:
self.alien_arm.update()
2017-12-28 08:01:58 -05:00
elif self.level_index == 2:
self.spoopy.update()
2017-12-21 02:50:27 -05:00
self.sword.update()
self.health.update()
2018-09-23 01:01:36 -04:00
self.countdown.update()
2017-12-14 03:49:02 -05:00
2017-12-25 01:41:20 -05:00
def update_dialogue(self):
if self.active:
dialogue = self.get_game().dialogue
if dialogue.active:
self.get_game().dialogue.update()
2020-10-30 03:57:46 -04:00
if self.countdown.active:
self.advance_prompt.update()
2017-12-25 01:41:20 -05:00
2017-12-14 03:49:02 -05:00
2018-09-23 01:01:36 -04:00
class Countdown(GameChild):
def __init__(self, parent):
GameChild.__init__(self, parent)
dsr = self.get_display_surface().get_rect()
font = Font(self.get_resource(Dialogue.FONT_PATH), 76)
self.heading = Sprite(self)
self.heading.add_frame(font.render("CONTINUE?", True, (0, 0, 0), (255, 255, 255)).convert_alpha())
2018-09-23 01:01:36 -04:00
self.heading.location.midtop = dsr.centerx, 50
self.game_over = Sprite(self)
self.game_over.add_frame(font.render("GAME OVER", True, (0, 0, 0), (255, 255, 255)).convert_alpha())
2018-09-23 01:01:36 -04:00
self.game_over.location.center = dsr.centerx, dsr.centery - 40
self.glyphs = []
for ii in range(10):
2018-09-23 01:01:36 -04:00
glyph = Sprite(self)
frame = Surface((140, 140))
frame.fill((255, 255, 255))
digits = font.render("%i" % ii, True, (0, 0, 0), (255, 255, 255)).convert_alpha()
2018-09-23 01:01:36 -04:00
rect = digits.get_rect()
rect.center = frame.get_rect().center
frame.blit(digits, rect)
glyph.add_frame(frame)
glyph.location.center = dsr.centerx, dsr.centery - 30
self.glyphs.append(glyph)
def reset(self):
self.deactivate()
def deactivate(self):
self.active = False
def activate(self):
self.remaining = 9999
self.active = True
def end_game(self):
self.get_game().reset(True)
def update(self):
if self.active:
if self.get_game().chemtrails.boys.amount > 0:
self.heading.update()
self.glyphs[int(self.remaining / 1000)].update()
else:
self.game_over.update()
if not self.get_game().wipe.is_playing():
if self.remaining <= 0:
self.get_game().wipe.start(self.end_game)
self.remaining = 0
else:
self.remaining -= self.get_game().time_filter.get_last_frame_duration()
2018-01-02 20:26:30 -05:00
class Sword(Animation):
2017-12-14 03:49:02 -05:00
2018-08-03 02:27:18 -04:00
SHIFT = 15
2018-07-03 18:10:26 -04:00
SPRITE_COUNT = 6
2018-06-21 16:07:11 -04:00
2017-12-14 03:49:02 -05:00
def __init__(self, parent):
2018-01-02 20:26:30 -05:00
Animation.__init__(self, parent)
2018-07-25 22:02:44 -04:00
swords = self.swords = []
2022-02-24 20:53:34 -05:00
#for root in "Sword_kool_man/", "Sword_visitor/", "Sword_spoopy/":
for root in "Sword_kool_man/", "local/test/", "Sword_spoopy/":
2018-08-03 02:27:18 -04:00
swords.append([[], [], [], [], [], []])
base_image_paths = sorted(iglob(join(self.get_resource(root), "*.png")))
if not self.get_configuration("display", "effects"):
base_image_paths = [base_image_paths[0]]
2022-02-24 20:53:34 -05:00
for frame_index, path in enumerate(base_image_paths):
2018-08-03 02:27:18 -04:00
base = load(self.get_resource(path)).convert_alpha()
for position in range(6):
2018-08-03 02:27:18 -04:00
if position == NS.N or position == NS.S:
rotated = rotate(base, 270)
elif position == NS.NW:
rotated = rotate(base, 45)
elif position == NS.NE:
rotated = rotate(base, 310)
else:
rotated = base
surface = rotated.copy()
colors = self.get_game().platform.get_color_pair_from_edge(position)
color_a = Color(colors[0].r, colors[0].g, colors[0].b, 255)
color_b = Color(colors[1].r, colors[1].g, colors[1].b, 255)
2022-02-24 20:53:34 -05:00
# edit lightness to create glowing effect
for color in (color_a, color_b):
h, s, l, a = color.hsla
l = 30 + int(abs((frame_index % 10) - 5) / 5 * 60)
color.hsla = h, s, l, a
2018-08-03 02:27:18 -04:00
rect = surface.get_rect()
if position == NS.N or position == NS.S:
surface.fill(color_a, (0, 0, rect.w / 2, rect.h), BLEND_RGBA_MIN)
surface.fill(color_b, (rect.centerx, 0, rect.w / 2, rect.h), BLEND_RGBA_MIN)
else:
surface.fill(color_a, (0, 0, rect.w, rect.h / 2), BLEND_RGBA_MIN)
surface.fill(color_b, (0, rect.centery, rect.w, rect.h / 2), BLEND_RGBA_MIN)
swords[-1][position].append(surface)
masks = self.masks = []
for alpha in range(16, 255, 16):
2018-08-03 02:27:18 -04:00
surface = Surface((300, 300), SRCALPHA)
surface.fill((255, 255, 255, alpha))
masks.append(surface)
self.register(self.brandish, self.lower, self.swab)
2017-12-14 03:49:02 -05:00
def reset(self):
self.halt(self.brandish)
self.halt(self.lower)
self.halt(self.swab)
2018-01-02 20:26:30 -05:00
self.next_index = 0
2018-08-03 02:27:18 -04:00
self.sprites = []
2017-12-14 03:49:02 -05:00
def brandish(self):
level_index = self.parent.level_index
2018-08-03 02:27:18 -04:00
position = self.parent.unbrandished.pop(0)
offset = -self.SHIFT
for ii, queued in enumerate(self.parent.queue):
offset += self.SHIFT * (queued == position)
if len(self.parent.unbrandished) == len(self.parent.queue) - ii - 1:
break
2017-12-14 03:49:02 -05:00
dsr = self.get_display_surface().get_rect()
2018-08-03 02:27:18 -04:00
sprite = Sprite(self)
for frame in self.swords[level_index][position]:
2018-08-03 02:27:18 -04:00
sprite.add_frame(frame)
2018-07-25 22:02:44 -04:00
if position in (NS.W, NS.E):
2022-02-24 20:53:34 -05:00
sprite.location.centery = dsr.centery - 80 + offset
2018-07-25 22:02:44 -04:00
if position == NS.W:
2018-08-03 02:27:18 -04:00
sprite.location.centerx = dsr.centerx - 100 - offset
2018-07-25 22:02:44 -04:00
else:
2018-08-03 02:27:18 -04:00
sprite.location.centerx = dsr.centerx + 100 - offset
2018-07-25 22:02:44 -04:00
elif position in (NS.N, NS.S):
2018-08-03 02:27:18 -04:00
sprite.location.centerx = dsr.centerx - offset
2018-07-25 22:02:44 -04:00
if position == NS.N:
2022-02-24 20:53:34 -05:00
sprite.location.centery = dsr.centery - 150 + offset
2018-07-25 22:02:44 -04:00
else:
2022-02-24 20:53:34 -05:00
sprite.location.centery = dsr.centery + 20 + offset
2018-07-25 22:02:44 -04:00
else:
2022-02-24 20:53:34 -05:00
sprite.location.center = dsr.centerx - offset, dsr.centery - 80
2018-08-03 02:27:18 -04:00
self.sprites.append(sprite)
self.get_audio().play_sfx("brandish")
2017-12-14 03:49:02 -05:00
self.play(self.lower, delay=400, play_once=True)
if len(self.parent.unbrandished) > 0:
self.play(self.brandish, delay=self.get_configuration("time", "sword-delay"), play_once=True)
if level_index == 1:
self.parent.alien_arm.unhide()
self.parent.alien_arm.set_frameset(str(position))
if len(self.parent.unbrandished) > 0:
self.play(self.swab, delay=self.get_configuration("time", "sword-delay") - 42 * 4, play_once=True, position=position)
def swab(self, position):
if self.parent.level_index == 1:
self.parent.alien_arm.set_frameset(f"{position}_{self.parent.unbrandished[0]}")
2017-12-14 03:49:02 -05:00
def lower(self):
if len(self.parent.unbrandished) == 0:
self.parent.brandish_complete = True
if self.parent.level_index == 1:
self.parent.alien_arm.hide()
2017-12-14 03:49:02 -05:00
2018-01-02 20:26:30 -05:00
def block(self):
2018-08-03 02:27:18 -04:00
if len(self.sprites):
self.sprites.pop(0)
2018-01-02 20:26:30 -05:00
2017-12-14 03:49:02 -05:00
def update(self):
2018-01-02 20:26:30 -05:00
Animation.update(self)
2022-02-24 20:53:34 -05:00
# only draw the current sword in the queue
if self.sprites:
self.sprites[0].update()
2017-12-14 03:49:02 -05:00
2018-09-20 04:57:06 -04:00
class Health(Meter):
2017-12-14 03:49:02 -05:00
2018-09-20 04:57:06 -04:00
OFFSET = 4
2017-12-14 03:49:02 -05:00
def __init__(self, parent):
2018-09-20 04:57:06 -04:00
Meter.__init__(self, parent)
2018-07-10 00:57:27 -04:00
dsr = self.get_display_surface().get_rect()
2018-11-21 01:19:29 -05:00
self.background = load(self.get_resource("HUD_boss.png")).convert()
self.rect = self.background.get_rect()
self.rect.midtop = dsr.centerx, self.OFFSET
def setup(self):
level_index = self.get_game().boss.level_index
if level_index == 0:
icon_index = 22
elif level_index == 1:
icon_index = 17
elif level_index == 2:
icon_index = 19
Meter.setup(self, self.background, self.rect, 52, (255, 0, 255), 100,
"scrapeIcons/scrapeIcons_%i.png" % icon_index)
def reset(self):
self.setup()
Meter.reset(self)
2017-12-14 03:49:02 -05:00
def decrease(self, damage):
2018-09-20 04:57:06 -04:00
self.change(-damage)
2017-12-28 08:01:58 -05:00
self.parent.damage()
2017-12-14 03:49:02 -05:00
if self.amount <= 0:
self.amount = 0
self.get_audio().play_sfx("complete_pattern_1")
self.get_audio().play_sfx("defeat")
2017-12-28 08:01:58 -05:00
self.get_game().boss.finish_battle(True)
2017-12-14 03:49:02 -05:00
else:
self.parent.play(self.parent.cancel_flash, delay=1000, play_once=True)
2018-01-02 20:51:42 -05:00
class Ending(Animation):
2022-02-24 20:53:34 -05:00
TEXT = "Wow! You vanquished all the goons and skated like a pro, slime bag." + \
" You made your\nfather proud today. I love you, child.",
2018-01-02 20:51:42 -05:00
def __init__(self, parent):
Animation.__init__(self, parent)
self.slime_bag = Sprite(self)
self.slime_bag.load_from_path(self.get_resource("Introduction_slime_bag.png"), True)
2018-07-25 22:02:44 -04:00
self.slime_bag.location.center = self.get_display_surface().get_rect().centerx, 300
2018-01-02 20:51:42 -05:00
self.tony_avatar = load(self.get_resource("Introduction_tony_avatar.png")).convert()
self.advance_prompt = AdvancePrompt(self)
2020-10-30 03:57:46 -04:00
self.register(self.start, self.start_wipe)
2018-01-02 20:51:42 -05:00
def reset(self):
self.deactivate()
self.slime_bag.unhide()
self.halt()
self.text_index = 0
self.advance_prompt.reset()
2018-09-20 02:02:27 -04:00
self.angle = choice((pi / 4, 3 * pi / 4, 5 * pi / 4, 7 * pi / 4))
2018-01-02 20:51:42 -05:00
def deactivate(self):
self.active = False
def activate(self):
self.active = True
self.play(self.start, delay=3000, play_once=True)
2018-07-25 22:02:44 -04:00
font = Font(self.get_resource("rounded-mplus-1m-bold.ttf"), 64)
time = self.get_game().title.get_formatted_time(self.get_game().most_recent_time)
2020-10-29 12:48:45 -04:00
foreground = font.render(time, False, (180, 150, 20), (255, 255, 255)).convert_alpha()
2018-07-25 22:02:44 -04:00
dsr = self.get_display_surface().get_rect()
2018-09-20 02:02:27 -04:00
self.text = RainbowSprite(self, foreground, 180, 200)
2018-07-25 22:02:44 -04:00
self.text.location.midtop = dsr.centerx, 80
2020-10-29 01:06:56 -04:00
self.get_game().tony.set_frameset("static")
2020-10-30 03:57:46 -04:00
dialogue = self.get_game().dialogue
dialogue.activate()
dialogue.set_avatar(self.tony_avatar)
dialogue.set_name("???")
dialogue.show_text("")
self.play(self.start_wipe, delay=20000, play_once=True)
self.get_audio().play_bgm("end")
2018-01-02 20:51:42 -05:00
def start(self):
self.advance_prompt.cancel_first_press()
dialogue = self.get_game().dialogue
dialogue.set_name("Tony")
dialogue.show_text(self.TEXT[0])
self.text_index = 0
def end_game(self):
self.deactivate()
self.get_game().reset(True)
def start_wipe(self):
self.get_game().wipe.start(self.end_game)
def update(self):
if self.active:
Animation.update(self)
dialogue = self.get_game().dialogue
wipe = self.get_game().wipe
2020-10-29 12:48:45 -04:00
self.get_game().logo.update()
self.get_game().tony.update()
2018-01-02 20:51:42 -05:00
self.slime_bag.update()
2018-09-20 02:02:27 -04:00
dsr = self.get_display_surface().get_rect()
if self.text.location.right > dsr.right or self.text.location.left < dsr.left:
self.angle = reflect_angle(self.angle, 0)
if self.text.location.right > dsr.right:
self.text.move(dsr.right - self.text.location.right)
else:
self.text.move(dsr.left - self.text.location.left)
if self.text.location.bottom > self.get_game().dialogue.avatar_box.location.top \
or self.text.location.top < dsr.top:
self.angle = reflect_angle(self.angle, pi)
if self.text.location.top < dsr.top:
self.text.move(dy=dsr.top - self.text.location.top)
else:
self.text.move(
dy=self.get_game().dialogue.avatar_box.location.top - \
self.text.location.bottom)
dx, dy = get_delta(self.angle, 5, False)
self.text.move(dx, dy)
2018-07-25 22:02:44 -04:00
self.text.update()
2018-01-02 20:51:42 -05:00
self.get_game().dialogue.update()