sprites: initial implementation
This commit is contained in:
parent
3b9cbf82aa
commit
1b71c6cfb6
8
Makefile
8
Makefile
@ -6,6 +6,7 @@ LIB = ./saturn
|
|||||||
SRC = main.o input.o
|
SRC = main.o input.o
|
||||||
SRC += gen/maps.o
|
SRC += gen/maps.o
|
||||||
SRC += gen/map_objects.o
|
SRC += gen/map_objects.o
|
||||||
|
SRC += gen/sprites.o
|
||||||
DEP = $(patsubst %.o,%.d,$(SRC))
|
DEP = $(patsubst %.o,%.d,$(SRC))
|
||||||
|
|
||||||
res = $(subst pokered/,res/,$(patsubst %.$(1),%.$(1).o,$(wildcard $(2)*.$(1))))
|
res = $(subst pokered/,res/,$(patsubst %.$(1),%.$(1).o,$(wildcard $(2)*.$(1))))
|
||||||
@ -13,9 +14,10 @@ res_png = $(subst pokered/,res/,$(patsubst %.png,%.$(1).o,$(wildcard $(2)*.png))
|
|||||||
|
|
||||||
GFX_TILESETS = $(call res_png,2bpp,pokered/gfx/tilesets/)
|
GFX_TILESETS = $(call res_png,2bpp,pokered/gfx/tilesets/)
|
||||||
GFX_BLOCKSETS = $(call res,bst,pokered/gfx/blocksets/)
|
GFX_BLOCKSETS = $(call res,bst,pokered/gfx/blocksets/)
|
||||||
|
GFX_SPRITES = $(call res_png,2bpp,pokered/gfx/sprites/)
|
||||||
MAPS_BLOCKS = $(call res,blk,pokered/maps/)
|
MAPS_BLOCKS = $(call res,blk,pokered/maps/)
|
||||||
|
|
||||||
GENERATED = $(GFX_TILESETS) $(GFX_BLOCKSETS) $(MAPS_BLOCKS)
|
GENERATED = $(GFX_TILESETS) $(GFX_BLOCKSETS) $(GFX_SPRITES) $(MAPS_BLOCKS)
|
||||||
OBJ = $(SRC) $(GENERATED)
|
OBJ = $(SRC) $(GENERATED)
|
||||||
|
|
||||||
all: main.cue
|
all: main.cue
|
||||||
@ -47,6 +49,10 @@ res/%.2bpp: pokered/%.png
|
|||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
python tools/png_to_nbpp.py $< 2 $@
|
python tools/png_to_nbpp.py $< 2 $@
|
||||||
|
|
||||||
|
res/gfx/sprites/%.2bpp: pokered/gfx/sprites/%.png
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
python tools/png_to_nbpp_sprite.py $< 2 $@
|
||||||
|
|
||||||
%.2bpp.h:
|
%.2bpp.h:
|
||||||
$(BUILD_BINARY_H)
|
$(BUILD_BINARY_H)
|
||||||
|
|
||||||
|
113
main.cpp
113
main.cpp
@ -1,6 +1,7 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
#include "vdp2.h"
|
#include "vdp2.h"
|
||||||
|
#include "vdp1.h"
|
||||||
#include "scu.h"
|
#include "scu.h"
|
||||||
#include "smpc.h"
|
#include "smpc.h"
|
||||||
#include "sh2.h"
|
#include "sh2.h"
|
||||||
@ -12,6 +13,7 @@
|
|||||||
#include "input.hpp"
|
#include "input.hpp"
|
||||||
|
|
||||||
#include "gen/maps.hpp"
|
#include "gen/maps.hpp"
|
||||||
|
#include "gen/sprites.hpp"
|
||||||
#include "map_objects.hpp"
|
#include "map_objects.hpp"
|
||||||
|
|
||||||
constexpr inline uint16_t rgb15(int32_t r, int32_t g, int32_t b)
|
constexpr inline uint16_t rgb15(int32_t r, int32_t g, int32_t b)
|
||||||
@ -21,19 +23,14 @@ constexpr inline uint16_t rgb15(int32_t r, int32_t g, int32_t b)
|
|||||||
|
|
||||||
void palette_data()
|
void palette_data()
|
||||||
{
|
{
|
||||||
vdp2.cram.u16[0] = rgb15( 0, 0, 0);
|
vdp2.cram.u16[3] = rgb15( 0, 0, 0);
|
||||||
vdp2.cram.u16[1] = rgb15(10, 10, 10);
|
vdp2.cram.u16[2] = rgb15(10, 10, 10);
|
||||||
vdp2.cram.u16[2] = rgb15(21, 21, 21);
|
vdp2.cram.u16[1] = rgb15(21, 21, 21);
|
||||||
vdp2.cram.u16[3] = rgb15(31, 31, 31);
|
vdp2.cram.u16[0] = rgb15(31, 31, 31);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t cell_data(const start_size_t& buf, const uint32_t top)
|
static inline void _2bpp_4bpp_vram_copy(uint32_t * vram, const start_size_t& buf)
|
||||||
{
|
{
|
||||||
// round to nearest multiple of 32
|
|
||||||
const uint32_t table_size = ((buf.size * 2) + 0x20 - 1) & (-0x20);
|
|
||||||
const uint32_t base_address = top - table_size; // in bytes
|
|
||||||
|
|
||||||
uint32_t * vram = &vdp2.vram.u32[(base_address / 4)];
|
|
||||||
for (uint32_t ix = 0; ix < buf.size / 4; ix += 1) {
|
for (uint32_t ix = 0; ix < buf.size / 4; ix += 1) {
|
||||||
const uint32_t pixels = reinterpret_cast<uint32_t const * const>(buf.start)[ix];
|
const uint32_t pixels = reinterpret_cast<uint32_t const * const>(buf.start)[ix];
|
||||||
const uint32_t px0 = pixels >> 16 & 0xffff;
|
const uint32_t px0 = pixels >> 16 & 0xffff;
|
||||||
@ -52,6 +49,28 @@ uint32_t cell_data(const start_size_t& buf, const uint32_t top)
|
|||||||
#undef lshift
|
#undef lshift
|
||||||
#undef rshift
|
#undef rshift
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t character_pattern_table(const start_size_t& buf, const uint32_t top)
|
||||||
|
{
|
||||||
|
// round to nearest multiple of 32
|
||||||
|
const uint32_t table_size = ((buf.size * 2) + 0x20 - 1) & (-0x20);
|
||||||
|
const uint32_t base_address = top - table_size;
|
||||||
|
|
||||||
|
uint32_t * vram = &vdp1.vram.u32[(base_address / 4)];
|
||||||
|
_2bpp_4bpp_vram_copy(vram, buf);
|
||||||
|
|
||||||
|
return base_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t cell_data(const start_size_t& buf, const uint32_t top)
|
||||||
|
{
|
||||||
|
// round to nearest multiple of 32
|
||||||
|
const uint32_t table_size = ((buf.size * 2) + 0x20 - 1) & (-0x20);
|
||||||
|
const uint32_t base_address = top - table_size; // in bytes
|
||||||
|
|
||||||
|
uint32_t * vram = &vdp2.vram.u32[(base_address / 4)];
|
||||||
|
_2bpp_4bpp_vram_copy(vram, buf);
|
||||||
|
|
||||||
return base_address;
|
return base_address;
|
||||||
}
|
}
|
||||||
@ -187,10 +206,74 @@ void smpc_int(void)
|
|||||||
intback::fsm(digital_callback, nullptr);
|
intback::fsm(digital_callback, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sprite()
|
||||||
|
{
|
||||||
|
uint32_t top = (sizeof (union vdp1_vram));
|
||||||
|
const spritesheet_t& spritesheet = spritesheets[spritesheet_t::oak];
|
||||||
|
uint32_t character_address = top = character_pattern_table(spritesheet.spritesheet, top);
|
||||||
|
uint32_t color_address = 0;
|
||||||
|
|
||||||
|
/* TVM settings must be performed from the second H-blank IN interrupt after the
|
||||||
|
V-blank IN interrupt to the H-blank IN interrupt immediately after the V-blank
|
||||||
|
OUT interrupt. */
|
||||||
|
// "normal" display resolution, 16 bits per pixel, 512x256 framebuffer
|
||||||
|
vdp1.reg.TVMR = TVMR__TVM__NORMAL;
|
||||||
|
|
||||||
|
// swap framebuffers every 1 cycle; non-interlace
|
||||||
|
vdp1.reg.FBCR = 0;
|
||||||
|
|
||||||
|
// during a framebuffer erase cycle, write the color "black" to each pixel
|
||||||
|
constexpr uint16_t black = 0x0000;
|
||||||
|
vdp1.reg.EWDR = black;
|
||||||
|
|
||||||
|
// the EWLR/EWRR macros use somewhat nontrivial math for the X coordinates
|
||||||
|
// erase upper-left coordinate
|
||||||
|
vdp1.reg.EWLR = EWLR__16BPP_X1(0) | EWLR__Y1(0);
|
||||||
|
|
||||||
|
// erase lower-right coordinate
|
||||||
|
vdp1.reg.EWRR = EWRR__16BPP_X3(319) | EWRR__Y3(239);
|
||||||
|
|
||||||
|
vdp1.vram.cmd[0].CTRL = CTRL__JP__JUMP_NEXT | CTRL__COMM__SYSTEM_CLIP_COORDINATES;
|
||||||
|
vdp1.vram.cmd[0].LINK = 0;
|
||||||
|
vdp1.vram.cmd[0].XC = 319;
|
||||||
|
vdp1.vram.cmd[0].YC = 239;
|
||||||
|
|
||||||
|
vdp1.vram.cmd[1].CTRL = CTRL__JP__JUMP_NEXT | CTRL__COMM__LOCAL_COORDINATE;
|
||||||
|
vdp1.vram.cmd[1].LINK = 0;
|
||||||
|
vdp1.vram.cmd[1].XA = 0;
|
||||||
|
vdp1.vram.cmd[1].YA = 0;
|
||||||
|
|
||||||
|
vdp1.vram.cmd[2].CTRL = CTRL__JP__JUMP_NEXT | CTRL__COMM__NORMAL_SPRITE;
|
||||||
|
vdp1.vram.cmd[2].LINK = 0;
|
||||||
|
// The "end code" is 0xf, which is being used in the mai sprite palette. If
|
||||||
|
// both transparency and end codes are enabled, it seems there are only 14
|
||||||
|
// usable colors in the 4-bit color mode.
|
||||||
|
vdp1.vram.cmd[2].PMOD = PMOD__ECD | PMOD__COLOR_MODE__COLOR_BANK_16;
|
||||||
|
// It appears Kronos does not correctly calculate the color address in the
|
||||||
|
// VDP1 debugger. Kronos will report FFFC when the actual color table address
|
||||||
|
// in this example is 7FFE0.
|
||||||
|
vdp1.vram.cmd[2].COLR = color_address; // non-palettized (rgb15) color data
|
||||||
|
vdp1.vram.cmd[2].SRCA = character_address >> 3;
|
||||||
|
vdp1.vram.cmd[2].SIZE = SIZE__X(16) | SIZE__Y(16);
|
||||||
|
vdp1.vram.cmd[2].XA = 5 * 16;
|
||||||
|
vdp1.vram.cmd[2].YA = 5 * 16;
|
||||||
|
|
||||||
|
vdp1.vram.cmd[3].CTRL = CTRL__END;
|
||||||
|
|
||||||
|
// start drawing (execute the command list) on every frame
|
||||||
|
vdp1.reg.PTMR = PTMR__PTM__FRAME_CHANGE;
|
||||||
|
}
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
|
state.map_ix = map_t::celadon_city;
|
||||||
|
|
||||||
v_blank_in();
|
v_blank_in();
|
||||||
|
|
||||||
|
sprite();
|
||||||
|
|
||||||
|
vdp2.reg.PRISA = PRISA__S0PRIN(7); // Sprite register 0 PRIority Number
|
||||||
|
|
||||||
// DISP: Please make sure to change this bit from 0 to 1 during V blank.
|
// DISP: Please make sure to change this bit from 0 to 1 during V blank.
|
||||||
vdp2.reg.TVMD = ( TVMD__DISP | TVMD__LSMD__NON_INTERLACE
|
vdp2.reg.TVMD = ( TVMD__DISP | TVMD__LSMD__NON_INTERLACE
|
||||||
| TVMD__VRESO__240 | TVMD__HRESO__NORMAL_320);
|
| TVMD__VRESO__240 | TVMD__HRESO__NORMAL_320);
|
||||||
@ -199,7 +282,7 @@ void main()
|
|||||||
vdp2.reg.RAMCTL = RAMCTL__CRKTE | RAMCTL__CRMD__RGB_5BIT_1024 | RAMCTL__VRAMD | RAMCTL__VRBMD;
|
vdp2.reg.RAMCTL = RAMCTL__CRKTE | RAMCTL__CRMD__RGB_5BIT_1024 | RAMCTL__VRAMD | RAMCTL__VRBMD;
|
||||||
|
|
||||||
/* enable display of NBG0 */
|
/* enable display of NBG0 */
|
||||||
vdp2.reg.BGON = BGON__N0ON;
|
vdp2.reg.BGON = BGON__N0ON | BGON__N0TPON;
|
||||||
|
|
||||||
/* set character format for NBG0 to palettized 16 color
|
/* set character format for NBG0 to palettized 16 color
|
||||||
set enable "cell format" for NBG0
|
set enable "cell format" for NBG0
|
||||||
@ -216,10 +299,10 @@ void main()
|
|||||||
2-word: value of bit 5-0 * 0x4000
|
2-word: value of bit 5-0 * 0x4000
|
||||||
*/
|
*/
|
||||||
constexpr int plane_a = 0;
|
constexpr int plane_a = 0;
|
||||||
constexpr int plane_a_offset = plane_a * 0x2000;
|
//constexpr int plane_a_offset = plane_a * 0x2000;
|
||||||
|
|
||||||
constexpr int page_size = 64 * 64 * 2; // N0PNB__1WORD (16-bit)
|
//constexpr int page_size = 64 * 64 * 2; // N0PNB__1WORD (16-bit)
|
||||||
constexpr int plane_size = page_size * 1;
|
//constexpr int plane_size = page_size * 1;
|
||||||
|
|
||||||
vdp2.reg.MPOFN = MPOFN__N0MP(0); // bits 8~6
|
vdp2.reg.MPOFN = MPOFN__N0MP(0); // bits 8~6
|
||||||
vdp2.reg.MPABN0 = MPABN0__N0MPB(0) | MPABN0__N0MPA(plane_a); // bits 5~0
|
vdp2.reg.MPABN0 = MPABN0__N0MPB(0) | MPABN0__N0MPA(plane_a); // bits 5~0
|
||||||
@ -247,6 +330,4 @@ void main()
|
|||||||
|
|
||||||
scu.reg.IST = 0;
|
scu.reg.IST = 0;
|
||||||
scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN);
|
scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN);
|
||||||
|
|
||||||
state.map_ix = map_t::celadon_city;
|
|
||||||
}
|
}
|
||||||
|
6
start_size.hpp
Normal file
6
start_size.hpp
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct start_size_t {
|
||||||
|
uint8_t const * const start;
|
||||||
|
uint32_t size;
|
||||||
|
};
|
67
tileset.hpp
67
tileset.hpp
@ -1,67 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "res/gfx/tilesets/cavern.4bpp.h"
|
|
||||||
#include "res/gfx/tilesets/cemetery.4bpp.h"
|
|
||||||
#include "res/gfx/tilesets/club.4bpp.h"
|
|
||||||
#include "res/gfx/tilesets/facility.4bpp.h"
|
|
||||||
#include "res/gfx/tilesets/forest.4bpp.h"
|
|
||||||
#include "res/gfx/tilesets/gate.4bpp.h"
|
|
||||||
#include "res/gfx/tilesets/gym.4bpp.h"
|
|
||||||
#include "res/gfx/tilesets/house.4bpp.h"
|
|
||||||
#include "res/gfx/tilesets/interior.4bpp.h"
|
|
||||||
#include "res/gfx/tilesets/lab.4bpp.h"
|
|
||||||
#include "res/gfx/tilesets/lobby.4bpp.h"
|
|
||||||
#include "res/gfx/tilesets/mansion.4bpp.h"
|
|
||||||
#include "res/gfx/tilesets/overworld.4bpp.h"
|
|
||||||
#include "res/gfx/tilesets/plateau.4bpp.h"
|
|
||||||
#include "res/gfx/tilesets/pokecenter.4bpp.h"
|
|
||||||
#include "res/gfx/tilesets/reds_house.4bpp.h"
|
|
||||||
#include "res/gfx/tilesets/ship.4bpp.h"
|
|
||||||
#include "res/gfx/tilesets/ship_port.4bpp.h"
|
|
||||||
#include "res/gfx/tilesets/underground.4bpp.h"
|
|
||||||
|
|
||||||
struct tileset {
|
|
||||||
enum {
|
|
||||||
cavern,
|
|
||||||
cemetery,
|
|
||||||
club,
|
|
||||||
facility,
|
|
||||||
forest,
|
|
||||||
gate,
|
|
||||||
gym,
|
|
||||||
house,
|
|
||||||
interior,
|
|
||||||
lab,
|
|
||||||
lobby,
|
|
||||||
mansion,
|
|
||||||
overworld,
|
|
||||||
plateau,
|
|
||||||
pokecenter,
|
|
||||||
reds_house,
|
|
||||||
ship,
|
|
||||||
ship_port,
|
|
||||||
underground,
|
|
||||||
};
|
|
||||||
|
|
||||||
static uint32_t pattern_name(uint32_t graphics_ix);
|
|
||||||
static uint32_t load(uint32_t top, uint32_t i);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct buf_t {
|
|
||||||
uint32_t * buf;
|
|
||||||
uint32_t size;
|
|
||||||
uint32_t base_address;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern uint32_t cell_data(const buf_t& buf, const uint32_t top);
|
|
||||||
|
|
||||||
uint32_t tileset::pattern_name(uint32_t graphics_ix)
|
|
||||||
{
|
|
||||||
return tilesets[graphics_ix].base_address / 32;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t tileset::load(uint32_t top, uint32_t i)
|
|
||||||
{
|
|
||||||
tilesets[i].base_address = top = cell_data(tilesets[i], top);
|
|
||||||
return top;
|
|
||||||
}
|
|
@ -4,14 +4,17 @@ import sys
|
|||||||
|
|
||||||
from generate import maps
|
from generate import maps
|
||||||
from generate import map_objects
|
from generate import map_objects
|
||||||
|
from generate import sprites
|
||||||
|
|
||||||
def generate(base_path):
|
files = [
|
||||||
files = [
|
|
||||||
(maps.generate_maps_header, "maps.hpp"),
|
(maps.generate_maps_header, "maps.hpp"),
|
||||||
(maps.generate_maps_source, "maps.cpp"),
|
(maps.generate_maps_source, "maps.cpp"),
|
||||||
(map_objects.generate_map_objects_source, "map_objects.cpp")
|
(map_objects.generate_map_objects_source, "map_objects.cpp"),
|
||||||
]
|
(sprites.generate_sprites_header, "sprites.hpp"),
|
||||||
|
(sprites.generate_sprites_source, "sprites.cpp"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def generate(base_path):
|
||||||
for func, filename in files:
|
for func, filename in files:
|
||||||
path = base_path / filename
|
path = base_path / filename
|
||||||
with open(path, 'w') as f:
|
with open(path, 'w') as f:
|
||||||
|
18
tools/generate/binary.py
Normal file
18
tools/generate/binary.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
def binary_res(path, suffix):
|
||||||
|
# _binary_res_gfx_blocksets_overworld_bst_start
|
||||||
|
name = path.replace('/', '_').replace('.', '_')
|
||||||
|
return f"&_binary_res_{name}_{suffix}"
|
||||||
|
|
||||||
|
def start_size_value(path):
|
||||||
|
if path is None:
|
||||||
|
return [
|
||||||
|
"0,",
|
||||||
|
"0,",
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
start = binary_res(path, "start")
|
||||||
|
size = binary_res(path, "size")
|
||||||
|
return [
|
||||||
|
f"reinterpret_cast<uint8_t*>({start}),",
|
||||||
|
f"reinterpret_cast<uint32_t>({size}),",
|
||||||
|
]
|
@ -1,19 +1,51 @@
|
|||||||
|
"""
|
||||||
|
map_headers[0].tileset == 'OVERWORLD'
|
||||||
|
map_headers[0].name1 = 'PalletTown'
|
||||||
|
map_headers[0].name2 = 'PALLET_TOWN'
|
||||||
|
map_headers[0].blocks() == 'PalletTown_Blocks'
|
||||||
|
map_constants_list['PALLET_TOWN'] == MapConstant(10, 19)
|
||||||
|
maps_blocks_list['PalletTown_Blocks'] == 'maps/PalletTown.blk'
|
||||||
|
tileset_constants_list['OVERWORLD'] == 0
|
||||||
|
tileset_headers_list[0].name == 'Overworld'
|
||||||
|
tileset_headers_list[0].blockset() == 'Overworld_Block'
|
||||||
|
tileset_headers_list[0].gfx() == 'Overworld_GFX'
|
||||||
|
gfx_tilesets_list['Overworld_Block'] == 'gfx/blocksets/overworld.bst'
|
||||||
|
gfx_tilesets_list['Overworld_GFX'] == 'gfx/tilesets/overworld.2bpp'
|
||||||
|
"""
|
||||||
|
|
||||||
from parse import parse
|
from parse import parse
|
||||||
|
|
||||||
|
from generate.sort import default_sort
|
||||||
|
from generate.binary import binary_res, start_size_value
|
||||||
from generate.generate import renderer
|
from generate.generate import renderer
|
||||||
|
|
||||||
|
def sorted_map_constants_list():
|
||||||
|
return sorted(parse.map_constants_list.items(), key=default_sort)
|
||||||
|
|
||||||
def sorted_map_headers():
|
def sorted_map_headers():
|
||||||
|
map_constants_list = sorted_map_constants_list()
|
||||||
|
map_headers_dict = dict((map_header.name2, map_header) for map_header in parse.map_headers)
|
||||||
# hack to remove unused/duplicate underground_path_route_7
|
# hack to remove unused/duplicate underground_path_route_7
|
||||||
map_headers = sorted(parse.map_headers, key=lambda m: m.name2)
|
#map_headers = sorted(parse.map_headers, key=lambda m: m.name2)
|
||||||
return filter(lambda m: m.name1 != "UndergroundPathRoute7Copy", map_headers)
|
#return filter(lambda m: m.name1 != "UndergroundPathRoute7Copy", map_headers)
|
||||||
|
return (
|
||||||
|
map_headers_dict[map_name2] for map_name2, _ in map_constants_list
|
||||||
|
if map_name2 in map_headers_dict
|
||||||
|
# e.g CERULEAN_TRASHED_HOUSE_COPY has no map header
|
||||||
|
)
|
||||||
|
|
||||||
|
def sorted_tilesets_constants_list():
|
||||||
|
return sorted(parse.tileset_constants_list.items(), key=default_sort)
|
||||||
|
|
||||||
def includes_header():
|
def includes_header():
|
||||||
yield "#pragma once"
|
yield "#pragma once"
|
||||||
yield ""
|
yield ""
|
||||||
|
yield '#include "../start_size.hpp"'
|
||||||
|
yield ""
|
||||||
for map_header in sorted_map_headers():
|
for map_header in sorted_map_headers():
|
||||||
block_path = parse.maps_blocks_list[map_header.blocks()]
|
block_path = parse.maps_blocks_list[map_header.blocks()]
|
||||||
yield f'#include "../res/{block_path}.h"'
|
yield f'#include "../res/{block_path}.h"'
|
||||||
for tileset_name in sorted(parse.tileset_constants_list):
|
for tileset_name, _ in sorted_tilesets_constants_list():
|
||||||
tileset_index = parse.tileset_constants_list[tileset_name]
|
tileset_index = parse.tileset_constants_list[tileset_name]
|
||||||
tileset_header = parse.tileset_headers_list[tileset_index]
|
tileset_header = parse.tileset_headers_list[tileset_index]
|
||||||
|
|
||||||
@ -24,18 +56,10 @@ def includes_header():
|
|||||||
yield f'#include "../res/{gfx_path}.h"'
|
yield f'#include "../res/{gfx_path}.h"'
|
||||||
yield ""
|
yield ""
|
||||||
|
|
||||||
def struct_start_size_t():
|
|
||||||
return [
|
|
||||||
"struct start_size_t {",
|
|
||||||
"uint8_t const * const start;",
|
|
||||||
"uint32_t size;",
|
|
||||||
"};",
|
|
||||||
]
|
|
||||||
|
|
||||||
def struct_tileset_t():
|
def struct_tileset_t():
|
||||||
tileset_names = (
|
tileset_names = (
|
||||||
f"{name.lower()},"
|
f"{name.lower()},"
|
||||||
for name in sorted(parse.tileset_constants_list)
|
for name, _ in sorted_tilesets_constants_list()
|
||||||
)
|
)
|
||||||
return [
|
return [
|
||||||
"struct tileset_t {",
|
"struct tileset_t {",
|
||||||
@ -68,20 +92,6 @@ def struct_map_t():
|
|||||||
"};",
|
"};",
|
||||||
]
|
]
|
||||||
|
|
||||||
def binary_res(path, suffix):
|
|
||||||
# _binary_res_gfx_blocksets_overworld_bst_start
|
|
||||||
name = path.replace('/', '_').replace('.', '_')
|
|
||||||
return f"_binary_res_{name}_{suffix}"
|
|
||||||
|
|
||||||
def start_size_value(path):
|
|
||||||
start = binary_res(path, "start")
|
|
||||||
size = binary_res(path, "size")
|
|
||||||
|
|
||||||
return [
|
|
||||||
f"reinterpret_cast<uint8_t*>(&{start}),",
|
|
||||||
f"reinterpret_cast<uint32_t>(&{size}),",
|
|
||||||
]
|
|
||||||
|
|
||||||
def blockset_tileset(name: str):
|
def blockset_tileset(name: str):
|
||||||
tileset_index = parse.tileset_constants_list[name]
|
tileset_index = parse.tileset_constants_list[name]
|
||||||
tileset_header = parse.tileset_headers_list[tileset_index]
|
tileset_header = parse.tileset_headers_list[tileset_index]
|
||||||
@ -105,8 +115,8 @@ def tilesets_header():
|
|||||||
|
|
||||||
def tilesets():
|
def tilesets():
|
||||||
yield "const tileset_t tilesets[] = {"
|
yield "const tileset_t tilesets[] = {"
|
||||||
for tileset in sorted(parse.tileset_constants_list):
|
for name, _ in sorted_tilesets_constants_list():
|
||||||
yield from blockset_tileset(tileset)
|
yield from blockset_tileset(name)
|
||||||
yield "};"
|
yield "};"
|
||||||
|
|
||||||
def map(map_header):
|
def map(map_header):
|
||||||
@ -135,7 +145,6 @@ def maps():
|
|||||||
def generate_maps_header():
|
def generate_maps_header():
|
||||||
render, out = renderer()
|
render, out = renderer()
|
||||||
render(includes_header())
|
render(includes_header())
|
||||||
render(struct_start_size_t())
|
|
||||||
render(struct_tileset_t())
|
render(struct_tileset_t())
|
||||||
render(struct_map_t())
|
render(struct_map_t())
|
||||||
render(tilesets_header());
|
render(tilesets_header());
|
||||||
@ -154,19 +163,3 @@ def generate_maps_source():
|
|||||||
render(tilesets())
|
render(tilesets())
|
||||||
render(maps())
|
render(maps())
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
map_headers[0].tileset == 'OVERWORLD'
|
|
||||||
map_headers[0].name1 = 'PalletTown'
|
|
||||||
map_headers[0].name2 = 'PALLET_TOWN'
|
|
||||||
map_headers[0].blocks() == 'PalletTown_Blocks'
|
|
||||||
map_constants_list['PALLET_TOWN'] == MapConstant(10, 19)
|
|
||||||
maps_blocks_list['PalletTown_Blocks'] == 'maps/PalletTown.blk'
|
|
||||||
tileset_constants_list['OVERWORLD'] == 0
|
|
||||||
tileset_headers_list[0].name == 'Overworld'
|
|
||||||
tileset_headers_list[0].blockset() == 'Overworld_Block'
|
|
||||||
tileset_headers_list[0].gfx() == 'Overworld_GFX'
|
|
||||||
gfx_tilesets_list['Overworld_Block'] == 'gfx/blocksets/overworld.bst'
|
|
||||||
gfx_tilesets_list['Overworld_GFX'] == 'gfx/tilesets/overworld.2bpp'
|
|
||||||
"""
|
|
||||||
|
5
tools/generate/sort.py
Normal file
5
tools/generate/sort.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from operator import itemgetter
|
||||||
|
|
||||||
|
by_name = itemgetter(0)
|
||||||
|
by_index = itemgetter(1)
|
||||||
|
default_sort = by_name
|
98
tools/generate/sprites.py
Normal file
98
tools/generate/sprites.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
"""
|
||||||
|
spritesheets_list[0] == Spritesheet('RedSprite', 3)
|
||||||
|
sprite_constants_list[0] == 'SPRITE_NONE'
|
||||||
|
gfx_sprites_list['RedSprite'] == 'gfx/sprites/red.2bpp'
|
||||||
|
"""
|
||||||
|
# insert a empty sprite for SPRITE_NONE
|
||||||
|
# remove SPRITE_ prefix
|
||||||
|
|
||||||
|
|
||||||
|
from parse import parse
|
||||||
|
|
||||||
|
from generate.sort import default_sort
|
||||||
|
from generate.binary import binary_res, start_size_value
|
||||||
|
from generate.generate import renderer
|
||||||
|
|
||||||
|
def sorted_sprite_constants_list():
|
||||||
|
return sorted(parse.sprite_constants_list.items(), key=default_sort)
|
||||||
|
|
||||||
|
def includes_header():
|
||||||
|
yield '#pragma once'
|
||||||
|
yield ''
|
||||||
|
yield '#include "../start_size.hpp"'
|
||||||
|
yield ''
|
||||||
|
for name, index in sorted_sprite_constants_list():
|
||||||
|
if name == 'SPRITE_NONE':
|
||||||
|
continue
|
||||||
|
assert index != 0, index
|
||||||
|
spritesheet = parse.spritesheets_list[index - 1]
|
||||||
|
sprite_path = parse.gfx_sprites_list[spritesheet.name]
|
||||||
|
yield f'#include "../res/{sprite_path}.h"'
|
||||||
|
|
||||||
|
def includes_source():
|
||||||
|
yield '#include <cstdint>'
|
||||||
|
yield ''
|
||||||
|
yield '#include "sprites.hpp"'
|
||||||
|
yield ''
|
||||||
|
|
||||||
|
def sprite_name(name):
|
||||||
|
assert name.startswith('SPRITE_'), name
|
||||||
|
return name.removeprefix('SPRITE_').lower()
|
||||||
|
|
||||||
|
def struct_spritesheet_t():
|
||||||
|
sprite_names = (
|
||||||
|
f"{sprite_name(name)},"
|
||||||
|
for name, _ in sorted_sprite_constants_list()
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
"struct spritesheet_t {",
|
||||||
|
"start_size_t spritesheet;",
|
||||||
|
"uint8_t sprite_count;",
|
||||||
|
"",
|
||||||
|
"enum sprite {",
|
||||||
|
*sprite_names,
|
||||||
|
"};",
|
||||||
|
"};",
|
||||||
|
]
|
||||||
|
|
||||||
|
def sprites_header():
|
||||||
|
yield "extern const spritesheet_t spritesheets[];"
|
||||||
|
|
||||||
|
def generate_sprites_header():
|
||||||
|
render, out = renderer()
|
||||||
|
render(includes_header())
|
||||||
|
render(struct_spritesheet_t())
|
||||||
|
render(sprites_header())
|
||||||
|
return out
|
||||||
|
|
||||||
|
def sprite(name, index):
|
||||||
|
if name == 'SPRITE_NONE':
|
||||||
|
# special null sprite
|
||||||
|
sprite_path = None
|
||||||
|
sprite_count = 0
|
||||||
|
else:
|
||||||
|
# spritesheets_list does not include SPRITE_NULL at index 0
|
||||||
|
assert index != 0, index
|
||||||
|
spritesheet = parse.spritesheets_list[index - 1]
|
||||||
|
sprite_path = parse.gfx_sprites_list[spritesheet.name]
|
||||||
|
sprite_count = spritesheet.sprite_count
|
||||||
|
return [
|
||||||
|
f"[spritesheet_t::{sprite_name(name)}] = {{",
|
||||||
|
".spritesheet = {",
|
||||||
|
*start_size_value(sprite_path),
|
||||||
|
"},",
|
||||||
|
f".sprite_count = {sprite_count}",
|
||||||
|
"},",
|
||||||
|
]
|
||||||
|
|
||||||
|
def sprites():
|
||||||
|
yield "const spritesheet_t spritesheets[] = {"
|
||||||
|
for name, index in sorted_sprite_constants_list():
|
||||||
|
yield from sprite(name, index)
|
||||||
|
yield "};"
|
||||||
|
|
||||||
|
def generate_sprites_source():
|
||||||
|
render, out = renderer()
|
||||||
|
render(includes_source());
|
||||||
|
render(sprites())
|
||||||
|
return out
|
4
tools/palette.py
Normal file
4
tools/palette.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
def intensity_to_index(px):
|
||||||
|
indices = {0x00: 3, 0x55: 2, 0xaa: 1, 0xff: 0}
|
||||||
|
assert px in indices, px
|
||||||
|
return indices[px]
|
@ -1,5 +1,5 @@
|
|||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
from parse import parse
|
from parse import parse
|
||||||
|
|
||||||
for i in parse.map_objects_list.items():
|
for i in parse.sprite_constants_list:
|
||||||
pprint(i)
|
pprint(i)
|
||||||
|
@ -2,15 +2,16 @@ from parse.maps_blocks import tokenize_block, flatten
|
|||||||
|
|
||||||
def tokenize_lines(lines):
|
def tokenize_lines(lines):
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if '_GFX:' in line or '_Block:' in line:
|
if '::' in line:
|
||||||
yield tokenize_block(line, delim='::')
|
yield tokenize_block(line, delim='::')
|
||||||
|
|
||||||
def parse(prefix):
|
def parse(prefix):
|
||||||
path = prefix / 'gfx/tilesets.asm'
|
path = prefix / 'gfx/sprites.asm'
|
||||||
with open(path) as f:
|
with open(path) as f:
|
||||||
tokens = tokenize_lines(f.read().split('\n'))
|
tokens = tokenize_lines(f.read().split('\n'))
|
||||||
return list(
|
l = list(flatten(tokens,
|
||||||
flatten(tokens,
|
endings=['Sprite'],
|
||||||
endings=['_GFX', '_Block'],
|
base_path='gfx/'))
|
||||||
base_path='gfx/')
|
d = dict(l)
|
||||||
)
|
assert len(l) == len(d)
|
||||||
|
return d
|
@ -12,6 +12,10 @@ from parse import map_objects
|
|||||||
from parse import hidden_objects
|
from parse import hidden_objects
|
||||||
from parse import map_constants
|
from parse import map_constants
|
||||||
|
|
||||||
|
from parse import gfx_sprites
|
||||||
|
from parse import spritesheets
|
||||||
|
from parse import sprite_constants
|
||||||
|
|
||||||
prefix = Path(sys.argv[1])
|
prefix = Path(sys.argv[1])
|
||||||
|
|
||||||
map_headers = map_header.parse_all(prefix)
|
map_headers = map_header.parse_all(prefix)
|
||||||
@ -30,5 +34,9 @@ map_constants_list = map_constants.parse(prefix)
|
|||||||
#ledge_tiles.asm
|
#ledge_tiles.asm
|
||||||
#cut_tree_blocks.asm
|
#cut_tree_blocks.asm
|
||||||
|
|
||||||
|
|
||||||
# home/vcopy: animations
|
# home/vcopy: animations
|
||||||
|
|
||||||
|
# sprites
|
||||||
|
gfx_sprites_list = gfx_sprites.parse(prefix)
|
||||||
|
spritesheets_list = spritesheets.parse(prefix)
|
||||||
|
sprite_constants_list = sprite_constants.parse(prefix)
|
||||||
|
6
tools/parse/sprite_constants.py
Normal file
6
tools/parse/sprite_constants.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from parse.tileset_constants import flatten, tokenize_lines
|
||||||
|
|
||||||
|
def parse(prefix):
|
||||||
|
path = prefix / 'constants/sprite_constants.asm'
|
||||||
|
with open(path) as f:
|
||||||
|
return dict(flatten(tokenize_lines(f.read().split('\n'))))
|
39
tools/parse/spritesheets.py
Normal file
39
tools/parse/spritesheets.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from parse.map_header import tokenize_line
|
||||||
|
from parse import number
|
||||||
|
|
||||||
|
def tokenize_lines(lines):
|
||||||
|
for line in lines:
|
||||||
|
if 'overworld_sprite ' in line:
|
||||||
|
yield tokenize_line(line)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Spritesheet:
|
||||||
|
name: str
|
||||||
|
sprite_count: int
|
||||||
|
|
||||||
|
def translate_tile_count(tile_count):
|
||||||
|
# this is the number of non-animation sprites in the spritesheet
|
||||||
|
# the animation sprite is always at tile_index * 2
|
||||||
|
# all animated sprites have 2 animation frames
|
||||||
|
return int(tile_count) // 4
|
||||||
|
|
||||||
|
def flatten(tokens):
|
||||||
|
for ts in tokens:
|
||||||
|
if ts[0] != 'overworld_sprite':
|
||||||
|
continue
|
||||||
|
_, (name, tile_count) = ts
|
||||||
|
|
||||||
|
sprite_count = translate_tile_count(tile_count)
|
||||||
|
|
||||||
|
yield Spritesheet(
|
||||||
|
name=name,
|
||||||
|
sprite_count=sprite_count,
|
||||||
|
)
|
||||||
|
|
||||||
|
def parse(prefix):
|
||||||
|
path = prefix / 'data/sprites/sprites.asm'
|
||||||
|
with open(path) as f:
|
||||||
|
tokens = tokenize_lines(f.read().split('\n'))
|
||||||
|
return list(flatten(tokens))
|
@ -3,10 +3,7 @@ import sys
|
|||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
def intensity_to_index(px):
|
from palette import intensity_to_index
|
||||||
indices = {0x00: 0, 0x55: 1, 0xaa: 2, 0xff: 3}
|
|
||||||
assert px in indices, px
|
|
||||||
return indices[px]
|
|
||||||
|
|
||||||
def convert(image, bpp):
|
def convert(image, bpp):
|
||||||
assert bpp in {8, 4, 2}, bpp
|
assert bpp in {8, 4, 2}, bpp
|
||||||
|
68
tools/png_to_nbpp_sprite.py
Normal file
68
tools/png_to_nbpp_sprite.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from palette import intensity_to_index
|
||||||
|
|
||||||
|
cell_width = 16
|
||||||
|
cell_height = 16
|
||||||
|
|
||||||
|
def convert(image, bpp):
|
||||||
|
assert bpp in {8, 4, 2}, bpp
|
||||||
|
bits_per_byte = 8
|
||||||
|
px_per_byte = bits_per_byte // bpp
|
||||||
|
px_per_row = cell_width
|
||||||
|
bytes_per_row = (px_per_row // px_per_byte)
|
||||||
|
|
||||||
|
assert image.mode == 'L', image.mode
|
||||||
|
width, height = image.size
|
||||||
|
|
||||||
|
buf = bytearray(width * height // px_per_byte)
|
||||||
|
|
||||||
|
for cell_y in range(height//cell_height):
|
||||||
|
cell_y_start = cell_y * bytes_per_row * cell_height
|
||||||
|
|
||||||
|
for cell_x in range(width//cell_width):
|
||||||
|
cell_x_start = cell_y_start + bytes_per_row * cell_x
|
||||||
|
|
||||||
|
for y in range(cell_height):
|
||||||
|
for x in range(cell_width):
|
||||||
|
px = im.getpixel((cell_x * cell_width + x, cell_y * cell_height + y))
|
||||||
|
index = intensity_to_index(px)
|
||||||
|
buf_ix = cell_x_start + x//px_per_byte + y * bytes_per_row
|
||||||
|
|
||||||
|
buf[buf_ix] |= (index << bpp * ((px_per_byte - 1) - (x % px_per_byte)))
|
||||||
|
return buf
|
||||||
|
|
||||||
|
# (pixels/row)
|
||||||
|
# ------------ *
|
||||||
|
# (pixels/byte)
|
||||||
|
|
||||||
|
def debug(buf, bpp):
|
||||||
|
assert bpp in {8, 4, 2}, bpp
|
||||||
|
bits_per_byte = 8
|
||||||
|
px_per_byte = bits_per_byte // bpp
|
||||||
|
px_per_row = cell_width
|
||||||
|
bytes_per_row = (px_per_row // px_per_byte)
|
||||||
|
bit_mask = (2 ** bpp) - 1
|
||||||
|
|
||||||
|
for row in range(len(buf) // bytes_per_row):
|
||||||
|
for x in range(bytes_per_row):
|
||||||
|
px = buf[row * bytes_per_row + x]
|
||||||
|
for shift in reversed(range(px_per_byte)):
|
||||||
|
print((px >> (shift * bpp)) & bit_mask, end='')
|
||||||
|
print()
|
||||||
|
if (row % cell_height == (cell_height - 1)):
|
||||||
|
print()
|
||||||
|
|
||||||
|
in_path = sys.argv[1]
|
||||||
|
bpp = int(sys.argv[2])
|
||||||
|
out_path = sys.argv[3]
|
||||||
|
|
||||||
|
im = Image.open(in_path)
|
||||||
|
buf = convert(im, bpp)
|
||||||
|
if 'NBPP_DEBUG' in os.environ:
|
||||||
|
debug(buf, bpp)
|
||||||
|
with open(out_path, 'wb') as f:
|
||||||
|
f.write(buf)
|
Loading…
x
Reference in New Issue
Block a user