Preliminary implementation of new sprite screen
133
Gui.py
|
@ -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()
|
||||||
|
|
4
Main.py
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
@ -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):
|
||||||
|
|
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|