Preliminary implementation of new sprite screen

This commit is contained in:
Kevin Cathcart 2017-11-13 00:29:42 -05:00
parent df8a16c5ac
commit d31a7d6791
69 changed files with 217 additions and 23 deletions

133
Gui.py
View File

@ -1,11 +1,12 @@
from Main import main, __version__ as ESVersion from Main import main, __version__ as ESVersion
from Utils import is_bundled, local_path, output_path, open_file from Utils import is_bundled, local_path, output_path, open_file
from argparse import Namespace from argparse import Namespace
from Rom import Sprite
from glob import glob
import random import random
import subprocess
import os import os
import sys import shutil
from tkinter import Checkbutton, OptionMenu, Tk, LEFT, RIGHT, BOTTOM, TOP, StringVar, IntVar, Frame, Label, W, E, X, Entry, Spinbox, Button, filedialog, messagebox, PhotoImage from tkinter import Checkbutton, OptionMenu, Toplevel, LabelFrame, PhotoImage, Tk, LEFT, RIGHT, BOTTOM, TOP, StringVar, IntVar, Frame, Label, W, E, X, Y, Entry, Spinbox, Button, filedialog, messagebox
def guiMain(args=None): def guiMain(args=None):
@ -69,8 +70,9 @@ def guiMain(args=None):
spriteEntry = Entry(spriteDialogFrame, textvariable=spriteVar) spriteEntry = Entry(spriteDialogFrame, textvariable=spriteVar)
def SpriteSelect(): def SpriteSelect():
sprite = filedialog.askopenfilename() #sprite = filedialog.askopenfilename()
spriteVar.set(sprite) #spriteVar.set(sprite)
SpriteSelector(Toplevel(mainWindow), spriteVar.set)
spriteSelectButton = Button(spriteDialogFrame, text='Select Sprite', command=SpriteSelect) spriteSelectButton = Button(spriteDialogFrame, text='Select Sprite', command=SpriteSelect)
@ -280,5 +282,126 @@ def set_icon(window):
er48 = PhotoImage(file=local_path('data/ER32.gif')) er48 = PhotoImage(file=local_path('data/ER32.gif'))
window.tk.call('wm', 'iconphoto', window._w, er16, er32, er48) window.tk.call('wm', 'iconphoto', window._w, er16, er32, er48)
class SpriteSelector(object):
def __init__(self, parent, callback):
if is_bundled():
self.deploy_icons()
self.parent = parent
self.callback = callback
parent.wm_title("TAKE ANY ONE YOU WANT")
parent['padx']=5
parent['pady']=5
self.icon_section('Official Sprites', self.official_sprite_dir+'/*', 'Official Sprites not found. Click "Update Official Sprites" to download them.')
self.icon_section('Unofficial Sprites', self.unofficial_sprite_dir+'/*', 'Put sprites in the Sprites/Unofficial folder to have them appear here.')
frame = Frame(parent)
frame.pack(side=BOTTOM, fill=X, pady=5)
button = Button(frame, text="Browse for file...", command=self.browse_for_sprite)
button.pack(side=RIGHT, padx=(5,0))
# todo: Actually implement this. Requires a yet-to-be coded API from VT
button = Button(frame, text="Update Official Sprites")
button.pack(side=RIGHT, padx=(5,0))
button = Button(frame, text="Use Default Sprite", command=self.use_default_sprite)
button.pack(side=LEFT)
set_icon(parent)
def icon_section(self, frame_label, path, no_results_label):
frame = LabelFrame(self.parent, text=frame_label, padx=5, pady=5)
frame.pack(side=TOP, fill=X)
i=0
for file in glob(output_path(path)):
image = get_image_for_sprite(file)
if image is None: continue
button = Button(frame, image=image, command=lambda file=file:self.select_sprite(file))
button.image=image
button.grid(row=i//16, column=i%16)
i+=1
if i==0:
label = Label(frame, text="Put sprites in the Sprites/Unoffical folder to have them appear here.")
label.pack()
def browse_for_sprite(self):
sprite = filedialog.askopenfilename()
self.callback(sprite)
self.parent.destroy()
def use_default_sprite(self):
self.callback("")
self.parent.destroy()
def select_sprite(self, spritename):
self.callback(spritename)
self.parent.destroy()
def deploy_icons(self):
if not os.path.exists(self.unofficial_sprite_dir):
os.makedirs(self.unofficial_sprite_dir)
if not os.path.exists(self.official_sprite_dir):
shutil.copytree(self.local_official_sprite_dir, self.official_sprite_dir)
@property
def official_sprite_dir(self):
if is_bundled():
return output_path("sprites/official")
else:
return self.local_official_sprite_dir
@property
def local_official_sprite_dir(site):
return local_path("data/sprites/official")
@property
def unofficial_sprite_dir(self):
if is_bundled():
return output_path("sprites/unofficial")
else:
return self.local_unofficial_sprite_dir
@property
def local_unofficial_sprite_dir(site):
return local_path("data/sprites/unofficial")
def get_image_for_sprite(filename):
sprite = Sprite(filename)
image = PhotoImage(height=24, width=16, palette="256/256/256")
def color_to_hex(color):
return "#{0:02X}{1:02X}{2:02X}".format(*color)
def drawsprite(spr, pal_as_colors, offset):
for y in range(len(spr)):
for x in range(len(spr[y])):
pal_index=spr[y][x]
if pal_index:
color=pal_as_colors[pal_index-1]
image.put(color_to_hex(color),to=(x+offset[0],y+offset[1]))
shadow_palette = [(40,40,40)]
shadow = [
[0,0,0,1,1,1,1,1,1,0,0,0],
[0,1,1,1,1,1,1,1,1,1,1,0],
[1,1,1,1,1,1,1,1,1,1,1,1],
[1,1,1,1,1,1,1,1,1,1,1,1],
[0,1,1,1,1,1,1,1,1,1,1,0],
[0,0,0,1,1,1,1,1,1,0,0,0],
]
drawsprite(shadow, shadow_palette, (2,17))
palettes = sprite.decode_palette()
body = sprite.decode16(0x4C0)
drawsprite(body, palettes[0], (0,8))
head = sprite.decode16(0x40)
drawsprite(head, palettes[0], (0,0))
return image.zoom(2)
if __name__ == '__main__': if __name__ == '__main__':
guiMain() guiMain()

View File

@ -1,7 +1,7 @@
from BaseClasses import World, CollectionState, Item from BaseClasses import World, CollectionState, Item
from Regions import create_regions from Regions import create_regions
from EntranceShuffle import link_entrances from EntranceShuffle import link_entrances
from Rom import patch_rom, LocalRom, JsonRom from Rom import patch_rom, Sprite, LocalRom, JsonRom
from Rules import set_rules from Rules import set_rules
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
from Items import ItemFactory from Items import ItemFactory
@ -91,7 +91,7 @@ def main(args, seed=None):
logger.info('Patching ROM.') logger.info('Patching ROM.')
if args.sprite is not None: if args.sprite is not None:
sprite = bytearray(open(args.sprite, 'rb').read()) sprite = Sprite(args.sprite)
else: else:
sprite = None sprite = None

View File

@ -1,7 +1,7 @@
from BaseClasses import World from BaseClasses import World
from Regions import create_regions from Regions import create_regions
from EntranceShuffle import link_entrances, connect_entrance, connect_two_way, connect_exit from EntranceShuffle import link_entrances, connect_entrance, connect_two_way, connect_exit
from Rom import patch_rom, LocalRom, write_string_to_rom from Rom import patch_rom, LocalRom, Sprite, write_string_to_rom
from Rules import set_rules from Rules import set_rules
from Dungeons import create_dungeons from Dungeons import create_dungeons
from Items import ItemFactory from Items import ItemFactory
@ -74,7 +74,7 @@ def main(args, seed=None):
logger.info('Patching ROM.') logger.info('Patching ROM.')
if args.sprite is not None: if args.sprite is not None:
sprite = bytearray(open(args.sprite, 'rb').read()) sprite = Sprite(args.sprite)
else: else:
sprite = None sprite = None

97
Rom.py
View File

@ -84,6 +84,83 @@ class LocalRom(object):
inv = crc ^ 0xFFFF inv = crc ^ 0xFFFF
self.write_bytes(0x7FDC, [inv & 0xFF, (inv >> 8) & 0xFF, crc & 0xFF, (crc >> 8) & 0xFF]) self.write_bytes(0x7FDC, [inv & 0xFF, (inv >> 8) & 0xFF, crc & 0xFF, (crc >> 8) & 0xFF])
class Sprite(object):
default_palette = [255, 127, 126, 35, 183, 17, 158, 54, 165, 20, 255, 1, 120, 16, 157,
89, 71, 54, 104, 59, 74, 10, 239, 18, 92, 42, 113, 21, 24, 122,
255, 127, 126, 35, 183, 17, 158, 54, 165, 20, 255, 1, 120, 16, 157,
89, 128, 105, 145, 118, 184, 38, 127, 67, 92, 42, 153, 17, 24, 122,
255, 127, 126, 35, 183, 17, 158, 54, 165, 20, 255, 1, 120, 16, 157,
89, 87, 16, 126, 69, 243, 109, 185, 126, 92, 42, 39, 34, 24, 122,
255, 127, 126, 35, 218, 17, 158, 54, 165, 20, 255, 1, 120, 16, 151,
61, 71, 54, 104, 59, 74, 10, 239, 18, 126, 86, 114, 24, 24, 122]
default_glove_palette = [246, 82, 118, 3]
def __init__(self, filename):
filedata = bytearray(open(filename, 'rb').read())
self.valid=True
if len(filedata) == 0x7000:
# sprite file with graphics and without palette data
self.sprite = filedata[:0x7000]
self.palette = None
self.glove_palette = None
elif len(filedata) == 0x7078:
# sprite file with graphics and palette data
self.sprite = filedata[:0x7000]
self.palette = filedata[0x7000:]
self.glove_palette = filedata[0x7036:0x7038] + filedata[0x7054:0x7056]
elif len(filedata) in [0x100000, 0x200000]:
# full rom with patched sprite, extract it
self.sprite = filedata[0x80000:0x87000]
self.palette = filedata[0xDD308:0xDD380]
self.glove_palette = filedata[0xDEDF5:0xDEDF9]
else:
self.valid=False
def decode8(self, pos):
arr=[[0 for _ in range(8)] for _ in range(8)]
for y in range(8):
for x in range(8):
position = 1<<(7-x)
val=0;
if self.sprite[pos+2*y] & position: val += 1
if self.sprite[pos+2*y+1] & position: val += 2
if self.sprite[pos+2*y+16] & position: val += 4
if self.sprite[pos+2*y+17] & position: val += 8
arr[y][x]= val
return arr
def decode16(self, pos):
arr=[[0 for _ in range(16)] for _ in range(16)]
top_left = self.decode8(pos)
top_right = self.decode8(pos+0x20)
bottom_left = self.decode8(pos+0x200)
bottom_right = self.decode8(pos+0x220)
for x in range(8):
for y in range(8):
arr[y][x] = top_left[y][x]
arr[y][x+8] = top_right[y][x]
arr[y+8][x] = bottom_left[y][x]
arr[y+8][x+8] = bottom_right[y][x]
return arr
def decode_palette(self):
"Returns the palettes as an array of arrays of 15 colors"
def array_chunk(arr,size):
return list(zip(*[iter(arr)] * size))
def make_int16(pair):
return pair[1]<<8 | pair[0]
def expand_color(i):
return ( (i & 0x1F)*8, (i>>5 & 0x1F)*8, (i>>10 & 0x1F)*8)
raw_palette = self.palette
if raw_palette is None: raw_palette = Sprite.default_palette
# turn palette data into a list of RGB tuples with 8 bit values
palette_as_colors = [expand_color(make_int16(chnk)) for chnk in array_chunk(raw_palette, 2)]
# split into palettes of 15 colors
return array_chunk(palette_as_colors, 15)
def int16_as_bytes(value): def int16_as_bytes(value):
value = value & 0xFFFF value = value & 0xFFFF
return [value & 0xFF, (value >> 8) & 0xFF] return [value & 0xFF, (value >> 8) & 0xFF]
@ -603,20 +680,12 @@ def patch_rom(world, rom, hashtable, beep='normal', sprite=None):
def write_sprite(rom, sprite): def write_sprite(rom, sprite):
if len(sprite) == 0x7000: if not sprite.valid: return
# sprite file with graphics and without palette data rom.write_bytes(0x80000, sprite.sprite)
rom.write_bytes(0x80000, sprite[:0x7000]) if sprite.palette is not None:
elif len(sprite) == 0x7078: rom.write_bytes(0xDD308, sprite.palette)
# sprite file with graphics and palette data if sprite.glove_palette is not None:
rom.write_bytes(0x80000, sprite[:0x7000]) rom.write_bytes(0xDEDF5, sprite.glove_palette)
rom.write_bytes(0xDD308, sprite[0x7000:])
rom.write_bytes(0xDEDF5, sprite[0x7036:0x7038])
rom.write_bytes(0xDEDF7, sprite[0x7054:0x7056])
elif len(sprite) in [0x100000, 0x200000]:
# full rom with patched sprite, extract it
rom.write_bytes(0x80000, sprite[0x80000:0x87000])
rom.write_bytes(0xDD308, sprite[0xDD308:0xDD380])
rom.write_bytes(0xDEDF5, sprite[0xDEDF5:0xDEDF9])
def write_string_to_rom(rom, target, string): def write_string_to_rom(rom, target, string):

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

2
data/sprites/unofficial/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore