From bdbef7552fcc5917991fe0a168734fe7c8674952 Mon Sep 17 00:00:00 2001 From: Gabriel Eirea Date: Tue, 08 Feb 2011 23:28:57 +0000 Subject: Initial import --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f3d74a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyc +*~ diff --git a/TODO b/TODO new file mode 100644 index 0000000..fb27655 --- /dev/null +++ b/TODO @@ -0,0 +1,6 @@ +- add check for existence of event +- generalized event calling +- layers +- add events +- add objects +- editor? diff --git a/bicho.png b/bicho.png new file mode 100755 index 0000000..7dbee09 --- /dev/null +++ b/bicho.png Binary files differ diff --git a/derechos-plaza-fondo.jpg b/derechos-plaza-fondo.jpg new file mode 100644 index 0000000..2571eef --- /dev/null +++ b/derechos-plaza-fondo.jpg Binary files differ diff --git a/example.py b/example.py new file mode 100644 index 0000000..41c794e --- /dev/null +++ b/example.py @@ -0,0 +1,155 @@ +#! /usr/bin/env python +# Rayito example +# Copyright (C) 2011 Gabriel Eirea +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Contact information: +# Gabriel Eirea geirea@gmail.com +# Ceibal Jam http://ceibaljam.org + + +import pygame +import rayito + +if __name__ == "__main__": + + pygame.init() + screen = pygame.display.set_mode((800,600)) + clock = pygame.time.Clock() + + bicho_image = pygame.image.load('bicho.png').convert_alpha() + bicho = rayito.RtoImage(bicho_image, (50, 50)) + bicho2 = rayito.RtoImage(bicho_image, (100, 100)) + + font24 = pygame.font.Font(None, 24) + txts = ["Hola, yo soy Pancho", + "No creo que consigas\nnada siguiendome", + "Pero si te sirve de consuelo,\nsomos apenas unos sprites\natrapados en una grilla"] + dia = rayito.RtoDialog(txts, font24, (200, 200, 0), (0, 0, 200), (170,130)) + txts = ["Nada que ver", + "Yo estoy muy\ncontento aca"] + dia2 = rayito.RtoDialog(txts, font24, (255, 255, 0), (0, 0, 100), (450,130)) + + b1 = rayito.RtoButton("Apretame", font24, (255, 200, 0), (0, 100, 150), (70,60)) + + images = [] + master_image = pygame.image.load('xoxi-ninios-sprite-01.png').convert_alpha() + master_width, master_height = master_image.get_size() + w = int(master_width/4) + h = int(master_height/4) + for i in xrange(4): + images.append(master_image.subsurface((i*w, 0, w, h))) + for i in xrange(4): + images.append(master_image.subsurface((i*w, h, w, h))) + for i in xrange(4): + images.append(master_image.subsurface((i*w, 2*h, w, h))) + for i in xrange(4): + images.append(master_image.subsurface((i*w, 3*h, w, h))) + fms = [0, 1, 0, 2, 0, 1, 0, 3, # up + 4, 5, 4, 6, 4, 5, 4, 7, # down + 8, 9, 8, 10, 8, 9, 8, 11, # left + 15, 13, 15, 14, 15, 13, 15, 12, # right + 0, 4, 8, 15] # stopped in 4 directions + nf = [1, 2, 3, 4, 5, 6, 7, 0, + 9, 10, 11, 12, 13, 14, 15, 8, + 17, 18, 19, 20, 21, 22, 23, 16, + 25, 26, 27, 28, 29, 30, 31, 24, + 32, 33, 34, 35] + dxs = [0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + -8, -8, -8, -8, -8, -8, -8, -8, + 8, 8, 8, 8, 8, 8, 8, 8, + 0, 0, 0, 0] + dys = [-6, -6, -6, -6, -6, -6, -6, -6, + 6, 6, 6, 6, 6, 6, 6, 6, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0] + sp = rayito.RtoSprite(images, fms, nf, dxs, dys, (50, 200)) + sp2 = rayito.RtoSprite(images, fms, nf, dxs, dys, (420, 200)) + + sound = pygame.mixer.Sound('junggle_btn045.wav') + snd = rayito.RtoSound(sound, 1) + + tl = rayito.RtoTimeLine() + tl.add_event(0, sp, 'update_frame', 35) + tl.add_event(0, sp2, 'update_frame', 34) + tl.add_event(0, dia, 'restart') + tl.add_event(0, dia2, 'restart') + tl.add_event(0, sp, 'show') + tl.add_event(0, sp2, 'show') + tl.add_event(1000, sp, 'update_frame', 24) + tl.add_event(3000, sp, 'update_frame', 35) + tl.add_event(3500, snd, 'play') + tl.add_event(3500, dia, 'show') + tl.add_event(5500, dia, 'next_text') + tl.add_event(7500, dia, 'next_text') + tl.add_event(9500, dia, 'next_text') + tl.add_event(10000, dia2, 'show') + tl.add_event(10000, snd, 'play') + tl.add_event(12000, dia2, 'next_text') + tl.add_event(14000, dia2, 'next_text') + tl.add_event(15000, sp, 'update_frame', 16) + tl.add_event(17000, sp, 'update_frame', 34) + tl.add_event(19000, sp, 'hide') + tl.add_event(19000, b1, 'show') + + tl2 = rayito.RtoTimeLine() + tl2.add_event(0, bicho, 'show') + tl2.add_event(2000, bicho, 'move_start', (.06, .06)) + tl2.add_event(6000, bicho, 'hide') + + bg = pygame.image.load('derechos-plaza-fondo.jpg').convert_alpha() + + pl = rayito.RtoPlayer((600, 500), (50, 50), tl, loop = False, bg_image = bg) + pl.play() + + playing = True + global_time = 0 + scene = 0 + + while playing: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + playing = False + elif event.type == pygame.MOUSEBUTTONDOWN: + pressed = pl.pressed(event.pos) + if pressed and pl.is_finished(): + pl.new_timeline(tl2) + pl.play() + scene = 1 + global_time = 0 + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_SPACE: + if pl.is_paused(): + pl.play() + else: + pl.pause() + elif event.key == pygame.K_ESCAPE: + playing = False + + time_passed = clock.tick(20) + global_time += time_passed + +# screen.fill((0, 0, 0)) + + pl.update(global_time) + pl.render(screen) + + pygame.display.flip() + + if pl.is_finished(): + if scene == 1: + playing = False diff --git a/junggle_btn045.wav b/junggle_btn045.wav new file mode 100755 index 0000000..8d5d7fd --- /dev/null +++ b/junggle_btn045.wav Binary files differ diff --git a/rayito/RtoObject.py b/rayito/RtoObject.py new file mode 100755 index 0000000..5963f4c --- /dev/null +++ b/rayito/RtoObject.py @@ -0,0 +1,312 @@ +#! /usr/bin/env python +# Rayito objects +# Copyright (C) 2011 Gabriel Eirea +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Contact information: +# Gabriel Eirea geirea@gmail.com +# Ceibal Jam http://ceibaljam.org + +import pygame + +class RtoObject(): + """ Class for rayito objects. + + position: (x, y) of upper-left corner in pixels + layer: layer number, high is up, low is down + """ + + def __init__(self, position, layer = 0): + self._position = [position[0], position[1]] + self._layer = layer + self._visible = False + self._moving = False + self._velocity = (0, 0) + self._last_update = 0 + + def hide(self): + self._visible = False + + def show(self): + self._visible = True + + def move_to(self, newpos): + self._position = [newpos[0], newpos[1]] + + def move_by(self, relpos): + self._position[0] += relpos[0] + self._position[1] += relpos[1] + + def change_layer(self, new_layer): + self._layer = new_layer + + def move_start(self, vel): + # vel should be in pixels per millisecond + self._moving = True + self._velocity = vel + + def move_stop(self): + self._moving = False + + def update(self, time): + pixels_x = int(self._velocity[0]*(time - self._last_update)) + pixels_y = int(self._velocity[1]*(time - self._last_update)) + if self._moving: + self._position[0] += pixels_x + self._position[1] += pixels_y + self._last_update = time + self.update_specific(time) + + def update_specific(self, time): + pass + + def render(self, screen): + pass + + def is_pressed(self, pos): + return False + + +class RtoImage(RtoObject): + """ Class for rayito images. + + image: pygame surface with the image + """ + + def __init__(self, image, position, layer = 0): + RtoObject.__init__(self, position, layer) + self._image = image + + def scale_to(self, target_size): + w = self._image.get_width() + h = self._image.get_height() + center = (self._position[0]+int(w/2), self._position[1]+int(h/2)) + tmp = pygame.transform.scale(self._image, target_size) + wtmp = tmp.get_width() + htmp = tmp.get_height() + self._position = [center[0]-int(wtmp/2), center[1]-int(htmp/2)] + self._image = tmp + del tmp + + def scale_by(self, ratio): + w = self._image.get_width() + h = self._image.get_height() + center = (self._position[0]+int(w/2), self._position[1]+int(h/2)) + tmp = pygame.transform.scale(self._image, + (int(self._image.get_width()*ratio), + int(self._image.get_height()*ratio))) + wtmp = tmp.get_width() + htmp = tmp.get_height() + self._position = [center[0]-int(wtmp/2), center[1]-int(htmp/2)] + self._image = tmp + del tmp + + def flip_hor(self): + tmp = pygame.transform.flip(self._image, True, False) + self._image = tmp + del tmp + + def flip_ver(self): + tmp = pygame.transform.scale(self._image, False, True) + self._image = tmp + del tmp + + def rotate(self, angle): + w = self._image.get_width() + h = self._image.get_height() + center = (self._position[0]+int(w/2), self._position[1]+int(h/2)) + tmp = pygame.transform.rotate(self._image, angle) + wtmp = tmp.get_width() + htmp = tmp.get_height() + self._position = [center[0]-int(wtmp/2), center[1]-int(htmp/2)] + self._image = tmp + del tmp + + def render(self, screen): + if self._visible: + screen.blit(self._image,self._position) + + +class RtoDialog(RtoObject): + """Class for rayito dialogs + + text: list with text to display; each element can have \n to separate lines + font: pygame font to use + bg_color: background color (R, G, B) + fg_color: text color (R, G, B) + """ + + def __init__(self, text, font, bg_color, fg_color, position, layer = 0): + RtoObject.__init__(self, position, layer) + self._font = font + self._text = text + self._ntexts = len(text) + self._current_text = 0 + self._bg_color = bg_color + self._fg_color = fg_color + # compute size + self._width = 0 + self._height = 0 + for t in self._text: + lines = t.split("\n") + lh = 0 + for l in lines: + (w, h) = self._font.size(l) + if w > self._width: + self._width = w + lh += h + if lh > self._height: + self._height = lh + self._width += 10 + self._height += 10 + # create empty background + self._bg = pygame.Surface((self._width, self._height)) + self._bg.fill(self._bg_color) + # render first line of text + self.next_text() + + def next_text(self): + if self._current_text == self._ntexts: + self.hide() + return False + self._image = self._bg.copy() + yline = 5 + for l in self._text[self._current_text].split("\n"): + text_img = self._font.render(l, 1, self._fg_color) + textrect = text_img.get_rect() + textrect.left = 5 + textrect.top = yline + self._image.blit(text_img, textrect) + yline += self._font.get_height() + self._current_text += 1 + return True + + def render(self, screen): + if self._visible: + screen.blit(self._image, self._position) + + def restart(self): + self._current_text = 0 + self.next_text() + + +class RtoButton(RtoObject): + """Class for rayito buttons + + text: text to display; can have \n to separate lines + font: pygame font to use + bg_color: background color (R, G, B) + fg_color: text color (R, G, B) + """ + + def __init__(self, text, font, bg_color, fg_color, position, layer = 0): + RtoObject.__init__(self, position, layer) + self._font = font + self._text = text + self._bg_color = bg_color + self._fg_color = fg_color + # compute size + self._width = 0 + self._height = 0 + lines = self._text.split("\n") + for l in lines: + (w, h) = self._font.size(l) + if w > self._width: + self._width = w + self._height += h + self._width += 10 + self._height += 10 + # create empty background + self._image = pygame.Surface((self._width, self._height)) + self._image.fill(self._bg_color) + yline = 5 + for l in self._text.split("\n"): + text_img = self._font.render(l, 1, self._fg_color) + textrect = text_img.get_rect() + textrect.left = 5 + textrect.top = yline + self._image.blit(text_img, textrect) + yline += h + + def render(self, screen): + if self._visible: + screen.blit(self._image, self._position) + + def is_pressed(self, pos): + if pos[0] > self._position[0] \ + and pos[0] < self._position[0] + self._width \ + and pos[1] > self._position[1] \ + and pos[1] < self._position[1] + self._height: + return True + else: + return False + + +class RtoSprite(RtoObject): + """Class for rayito sprites. + + images: list with sprite images (each one a pygame surface) + frames: list with image# for every frame + next_frames: list with next frame from current one + dxs: list with dx for current frame (in pixels) + dys: list with dy for current frame (in pixels) + """ + + def __init__(self, images, frames, next_frames, dxs, dys, + position, layer = 0): + RtoObject.__init__(self, position, layer) + self._images = images + self._frames = frames + self._next_frames = next_frames + self._dxs = dxs + self._dys = dys + self._frame = 0 + self._image = self._images[self._frames[self._frame]] + + def update_specific(self, time): + self._position[0] += self._dxs[self._frame] + self._position[1] += self._dys[self._frame] + self._frame = self._next_frames[self._frame] + self._image = self._images[self._frames[self._frame]] + self._last_update = time + + def render(self, screen): + screen.blit(self._image, self._position) + + def update_frame(self, fr): + self._frame = fr + + +class RtoSound(): + """Class for rayito sounds. + + sound: pygame.mixer.Sound object + volume: volume (between 0 and 1) + """ + + def __init__(self, sound, volume = 1): + self._sound = sound + self._volume = volume + + def play(self): + self._sound.play() + + def set_volume(self, vol): + if vol >= 0 and vol <= 1: + self._sound.set_volume(vol) + + def update(self, time): + pass + diff --git a/rayito/RtoPlayer.py b/rayito/RtoPlayer.py new file mode 100755 index 0000000..d9f5e6e --- /dev/null +++ b/rayito/RtoPlayer.py @@ -0,0 +1,179 @@ +#! /usr/bin/env python +# Rayito player +# Copyright (C) 2011 Gabriel Eirea +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Contact information: +# Gabriel Eirea geirea@gmail.com +# Ceibal Jam http://ceibaljam.org + + +import pygame +import RtoObject +import RtoTimeLine + + +class RtoPlayer(): + """ Class for rayito player. + + RtoPlayer(size, position, timeline, loop = False) + where: + size = canvas size + position = upper-left coordinates in the screen + timeline = RtoTimeLine object with objects and events + loop = boolean that indicates whether the player loops or not + bg_image = pygame surface with background image + bg_color = background color to use if image not present + """ + + def __init__(self, size, position, timeline, loop = False, + bg_image = None, bg_color = (0, 0, 0)): + self._size = size + self._position = position + self._timeline = timeline + self._finished = False + self._canvas = pygame.Surface(size) + self._last_update = 0 + self._active_objects = set() + self._tl_index = 0 + self._tl_length = self._timeline.get_length() + self._loop = loop + self._loop_time = 0 + self._bg_image = bg_image + self._bg_color = bg_color + self._paused = True + self._pause_time = 0 + self._start_pause = pygame.time.get_ticks() + + def pause(self): + self._start_pause = pygame.time.get_ticks() + self._paused = True + + def play(self): + self._pause_time += pygame.time.get_ticks() - self._start_pause + self._paused = False + + def is_finished(self): + return self._finished + + def is_paused(self): + return self._paused + + def update(self, time): + """ Update the player at one time step. + + Reads all events between last call and current call, executes + the actions, updates the objects, and renders all active objects on + the canvas. + """ + if self._paused or self._finished: + return + # read events between last_update and time + # TODO: must be generalized + while self._timeline.get_time(self._tl_index) <= \ + time - self._loop_time - self._pause_time: + if self._timeline.get_event(self._tl_index) == 'show': + self._active_objects.add(self._timeline.get_object(self._tl_index)) + self._timeline.get_object(self._tl_index).show() + elif self._timeline.get_event(self._tl_index) == 'hide': + self._active_objects.remove(self._timeline.get_object(self._tl_index)) + self._timeline.get_object(self._tl_index).hide() + elif self._timeline.get_event(self._tl_index) == 'move_by': + self._timeline.get_object(self._tl_index).move_by( + self._timeline.get_params(self._tl_index)) + elif self._timeline.get_event(self._tl_index) == 'move_to': + self._timeline.get_object(self._tl_index).move_to( + self._timeline.get_params(self._tl_index)) + elif self._timeline.get_event(self._tl_index) == 'scale_by': + self._timeline.get_object(self._tl_index).scale_by( + self._timeline.get_params(self._tl_index)) + elif self._timeline.get_event(self._tl_index) == 'scale_to': + self._timeline.get_object(self._tl_index).scale_to( + self._timeline.get_params(self._tl_index)) + elif self._timeline.get_event(self._tl_index) == 'flip_hor': + self._timeline.get_object(self._tl_index).flip_hor() + elif self._timeline.get_event(self._tl_index) == 'flip_ver': + self._timeline.get_object(self._tl_index).flip_ver() + elif self._timeline.get_event(self._tl_index) == 'rotate': + self._timeline.get_object(self._tl_index).rotate( + self._timeline.get_params(self._tl_index)) + elif self._timeline.get_event(self._tl_index) == 'move_start': + self._timeline.get_object(self._tl_index).move_start( + self._timeline.get_params(self._tl_index)) + elif self._timeline.get_event(self._tl_index) == 'move_stop': + self._timeline.get_object(self._tl_index).move_stop() + elif self._timeline.get_event(self._tl_index) == 'next_text': + self._timeline.get_object(self._tl_index).next_text() + elif self._timeline.get_event(self._tl_index) == 'restart': + self._timeline.get_object(self._tl_index).restart() + elif self._timeline.get_event(self._tl_index) == 'update_frame': + self._timeline.get_object(self._tl_index).update_frame( + self._timeline.get_params(self._tl_index)) + elif self._timeline.get_event(self._tl_index) == 'pause': + self.pause() + elif self._timeline.get_event(self._tl_index) == 'play': + self._timeline.get_object(self._tl_index).play() + # update index and check boundary + self._tl_index += 1 + if self._tl_index == self._tl_length: + if self._loop: + self._tl_index = 0 + self._loop_time = time + else: + self._finished = True + break + # update all active objects + for obj in self._active_objects: + obj.update(time - self._loop_time - self._pause_time) + # update time and return + self._last_update = time + + def render(self, screen): + # erase canvas + # TODO: can be optimized with dirty rectangles + if self._bg_image: + self._canvas.blit(self._bg_image, (0,0)) + else: + self._canvas.fill(self._bg_color) + # TODO: order active objects by layer + # render active objects on canvas + for obj in self._active_objects: + obj.render(self._canvas) + # render canvas on screen + screen.blit(self._canvas, self._position) + + def new_timeline(self, timeline, loop = False): + self._timeline = timeline + self._finished = False + self._active_objects = set() + self._tl_index = 0 + self._tl_length = self._timeline.get_length() + self._loop = loop + self._loop_time = 0 + self._paused = True + self._pause_time = 0 + self._start_pause = pygame.time.get_ticks() + + def pressed(self, pos): + pos_in_canvas = (pos[0]-self._position[0], pos[1]-self._position[1]) + for obj in self._active_objects: + if obj.is_pressed(pos_in_canvas): + return obj # assuming only one object at pos + return None + + +if __name__ == "__main__": + pass + diff --git a/rayito/RtoTimeLine.py b/rayito/RtoTimeLine.py new file mode 100755 index 0000000..0892b5d --- /dev/null +++ b/rayito/RtoTimeLine.py @@ -0,0 +1,53 @@ +#! /usr/bin/env python +# Rayito +# Copyright (C) 2011 Gabriel Eirea +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Contact information: +# Gabriel Eirea geirea@gmail.com +# Ceibal Jam http://ceibaljam.org + + +class RtoTimeLine(): + """ Class for a time line that builds the script. + + """ + + def __init__(self): + self._time_line = [] + + def add_event(self, time, obj, event, params = None): + # TBD: check that obj has event + self._time_line.append([time, obj, event, params]) + self._time_line.sort() + + def get_time(self, index): + return self._time_line[index][0] + + def get_object(self, index): + return self._time_line[index][1] + + def get_event(self, index): + return self._time_line[index][2] + + def get_params(self, index): + return self._time_line[index][3] + + def get_length(self): + return len(self._time_line) + + def check_correctness(self): + # TBD + pass diff --git a/rayito/__init__.py b/rayito/__init__.py new file mode 100755 index 0000000..32d86df --- /dev/null +++ b/rayito/__init__.py @@ -0,0 +1,43 @@ +#! /usr/bin/env python +# Rayito - a timeline-based event player for pygame +# Copyright (C) 2011 Gabriel Eirea +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Contact information: +# Gabriel Eirea geirea@gmail.com +# Ceibal Jam http://ceibaljam.org + +"""Rayito - a timeline-based event player library for pygame + +Usage: + +0. Import pygame, create display, create clock, etc. +1. Import rayito +2. Load pygame images, sounds, fonts, etc. +3. Create rayito objects +4. Create a timeline and add events as (object, time, event, parameters) +5. Create a player with canvas size, position and timeline +6. In the main pygame loop, use player.update() and player.render(); +use the clock to set fps. + +""" + +from RtoObject import RtoImage +from RtoObject import RtoDialog +from RtoObject import RtoSprite +from RtoObject import RtoButton +from RtoObject import RtoSound +from RtoTimeLine import RtoTimeLine +from RtoPlayer import RtoPlayer diff --git a/xoxi-ninios-sprite-01.png b/xoxi-ninios-sprite-01.png new file mode 100644 index 0000000..fb53af5 --- /dev/null +++ b/xoxi-ninios-sprite-01.png Binary files differ -- cgit v0.9.1