// license:BSD-3-Clause
// copyright-holders:BUT
/*
* Thunder Ceptor board
* emulate video hardware
*/
#include "emu.h"
#include "includes/tceptor.h"
#define TX_TILE_OFFSET_CENTER (32 * 2)
#define TX_TILE_OFFSET_RIGHT (32 * 0 + 2)
#define TX_TILE_OFFSET_LEFT (32 * 31 + 2)
#define SPR_TRANS_COLOR (0xff + 0x300)
#define SPR_MASK_COLOR (0xfe + 0x300)
/*******************************************************************/
PALETTE_INIT_MEMBER(tceptor_state, tceptor)
{
const uint8_t *color_prom = memregion("proms")->base();
int i;
/* create a lookup table for the palette */
for (i = 0; i < 0x400; i++)
{
int r = pal4bit(color_prom[i + 0x000]);
int g = pal4bit(color_prom[i + 0x400]);
int b = pal4bit(color_prom[i + 0x800]);
palette.set_indirect_color(i, rgb_t(r, g, b));
}
/* color_prom now points to the beginning of the lookup table */
color_prom += 0xc00;
/*
color lookup table:
0- +1024 ( 4 * 256) colors: text (use 0- 256 colors)
1024- +1024 (16 * 64) colors: sprite (use 768- 256 colors)
2048- +512 ( 8 * 64) colors: bg (use 0- 512 colors)
3840- +256 ( 4 * 64) colors: road (use 512- 256 colors)
*/
/* tiles lookup table (1024 colors) */
for (i = 0; i < 0x0400; i++)
{
int ctabentry = color_prom[i];
palette.set_pen_indirect(i, ctabentry);
}
/* sprites lookup table (1024 colors) */
for (i = 0x0400; i < 0x0800; i++)
{
int ctabentry = color_prom[i] | 0x300;
palette.set_pen_indirect(i, ctabentry);
}
/* background: no lookup PROM, use directly (512 colors) */
for (i = 0x0a00; i < 0x0c00; i++)
{
int ctabentry = i & 0x1ff;
palette.set_pen_indirect(i, ctabentry);
}
/* road lookup table (256 colors) */
for (i = 0x0f00; i < 0x1000; i++)
{
int ctabentry = color_prom[i - 0x700] | 0x200;
palette.set_pen_indirect(i, ctabentry);
}
/* setup sprite mask color map */
/* tceptor2: only 0x23 */
memset(m_is_mask_spr, 0, sizeof m_is_mask_spr);
for (i = 0; i < 0x400; i++)
if (palette.pen_indirect(i | 0x400) == SPR_MASK_COLOR)
m_is_mask_spr[i >> 4] = 1;
}
/*******************************************************************/
inline int tceptor_state::get_tile_addr(int tile_index)
{
int x = tile_index / 28;
int y = tile_index % 28;
switch (x)
{
case 0:
return TX_TILE_OFFSET_LEFT + y;
case 33:
return TX_TILE_OFFSET_RIGHT + y;
}
return TX_TILE_OFFSET_CENTER + (x - 1) + y * 32;
}
TILE_GET_INFO_MEMBER(tceptor_state::get_tx_tile_info)
{
int offset = get_tile_addr(tile_index);
int code = m_tile_ram[offset];
int color = m_tile_attr[offset];
tileinfo.group = color;
SET_TILE_INFO_MEMBER(0, code, color, 0);
}
void tceptor_state::tile_mark_dirty(int offset)
{
int x = -1;
int y = -1;
if (offset >= TX_TILE_OFFSET_LEFT && offset < TX_TILE_OFFSET_LEFT + 28)
{
x = 0;
y = offset - TX_TILE_OFFSET_LEFT;
}
else if (offset >= TX_TILE_OFFSET_RIGHT && offset < TX_TILE_OFFSET_RIGHT + 28)
{
x = 33;
y = offset - TX_TILE_OFFSET_RIGHT;
}
else if (offset >= TX_TILE_OFFSET_CENTER && offset < TX_TILE_OFFSET_CENTER + 32 * 28)
{
offset -= TX_TILE_OFFSET_CENTER;
x = (offset % 32) + 1;
y = offset / 32;
}
if (x >= 0)
m_tx_tilemap->mark_tile_dirty(x * 28 + y);
}
WRITE8_MEMBER(tceptor_state::tceptor_tile_ram_w)
{
if (m_tile_ram[offset] != data)
{
m_tile_ram[offset] = data;
tile_mark_dirty(offset);
}
}
WRITE8_MEMBER(tceptor_state::tceptor_tile_attr_w)
{
if (m_tile_attr[offset] != data)
{
m_tile_attr[offset] = data;
tile_mark_dirty(offset);
}
}
/*******************************************************************/
TILE_GET_INFO_MEMBER(tceptor_state::get_bg1_tile_info)
{
uint16_t data = m_bg_ram[tile_index * 2] | (m_bg_ram[tile_index * 2 + 1] << 8);
int code = (data & 0x3ff) | 0x000;
int color = (data & 0xfc00) >> 10;
SET_TILE_INFO_MEMBER(m_bg, code, color, 0);
}
TILE_GET_INFO_MEMBER(tceptor_state::get_bg2_tile_info)
{
uint16_t data = m_bg_ram[tile_index * 2 + 0x1000] | (m_bg_ram[tile_index * 2 + 1 + 0x1000] << 8);
int code = (data & 0x3ff) | 0x400;
int color = (data & 0xfc00) >> 10;
SET_TILE_INFO_MEMBER(m_bg, code, color, 0);
}
WRITE8_MEMBER(tceptor_state::tceptor_bg_ram_w)
{
m_bg_ram[offset] = data;
offset /= 2;
if (offset < 0x800)
m_bg1_tilemap->mark_tile_dirty(offset);
else
m_bg2_tilemap->mark_tile_dirty(offset - 0x800);
}
WRITE8_MEMBER(tceptor_state::tceptor_bg_scroll_w)
{
switch (offset)
{
case 0:
m_bg1_scroll_x &= 0xff;
m_bg1_scroll_x |= data << 8;
break;
case 1:
m_bg1_scroll_x &= 0xff00;
m_bg1_scroll_x |= data;
break;
case 2:
m_bg1_scroll_y = data;
break;
case 4:
m_bg2_scroll_x &= 0xff;
m_bg2_scroll_x |= data << 8;
break;
case 5:
m_bg2_scroll_x &= 0xff00;
m_bg2_scroll_x |= data;
break;
case 6:
m_bg2_scroll_y = data;
break;
}
}
/*******************************************************************/
void tceptor_state::decode_bg(const char * region)
{
static const gfx_layout bg_layout =
{
8, 8,
2048,
3,
{ 0x40000+4, 0, 4 },
{ 0, 1, 2, 3, 8, 9, 10, 11 },
{ 0, 16, 32, 48, 64, 80, 96, 112 },
128
};
int gfx_index = m_bg;
uint8_t *src = memregion(region)->base() + 0x8000;
int len = 0x8000;
int i;
std::vector<uint8_t> buffer(len);
/* expand rom tc2-19.10d */
for (i = 0; i < len / 2; i++)
{
buffer[i*2+1] = src[i] & 0x0f;
buffer[i*2] = (src[i] & 0xf0) >> 4;
}
memcpy(src, &buffer[0], len);
/* decode the graphics */
m_gfxdecode->set_gfx(gfx_index, std::make_unique<gfx_element>(m_palette, bg_layout, memregion(region)->base(), 0, 64, 0x0a00));
}
void tceptor_state::decode_sprite(int gfx_index, const gfx_layout *layout, const void *data)
{
/* decode the graphics */
m_gfxdecode->set_gfx(gfx_index, std::make_unique<gfx_element>(m_palette, *layout, (const uint8_t *)data, 0, 64, 1024));
}
// fix sprite order
void tceptor_state::decode_sprite16(const char * region)
{
static const gfx_layout spr16_layout =
{
16, 16,
512,
4,
{ 0x00000, 0x00004, 0x40000, 0x40004 },
{
0*8, 0*8+1, 0*8+2, 0*8+3, 1*8, 1*8+1, 1*8+2, 1*8+3,
2*8, 2*8+1, 2*8+2, 2*8+3, 3*8, 3*8+1, 3*8+2, 3*8+3
},
{
0*2*16, 1*2*16, 2*2*16, 3*2*16, 4*2*16, 5*2*16, 6*2*16, 7*2*16,
8*2*16, 9*2*16, 10*2*16, 11*2*16, 12*2*16, 13*2*16, 14*2*16, 15*2*16
},
2*16*16
};
uint8_t *src = memregion(region)->base();
int len = memregion(region)->bytes();
int i, y;
m_decoded_16 = std::make_unique<uint8_t[]>(len);
for (i = 0; i < len / (4*4*16); i++)
for (y = 0; y < 16; y++)
{
memcpy(&m_decoded_16[(i*4 + 0) * (2*16*16/8) + y * (2*16/8)],
&src[i * (2*32*32/8) + y * (2*32/8)],
4);
memcpy(&m_decoded_16[(i*4 + 1) * (2*16*16/8) + y * (2*16/8)],
&src[i * (2*32*32/8) + y * (2*32/8) + (4*8/8)],
4);
memcpy(&m_decoded_16[(i*4 + 2) * (2*16*16/8) + y * (2*16/8)],
&src[i * (2*32*32/8) + y * (2*32/8) + (16*2*32/8)],
4);
memcpy(&m_decoded_16[(i*4 + 3) * (2*16*16/8) + y * (2*16/8)],
&src[i * (2*32*32/8) + y * (2*32/8) + (4*8/8) + (16*2*32/8)],
4);
}
decode_sprite(m_sprite16, &spr16_layout, m_decoded_16.get());
}
// fix sprite order
void tceptor_state::decode_sprite32(const char * region)
{
static const gfx_layout spr32_layout =
{
32, 32,
1024,
4,
{ 0x000000, 0x000004, 0x200000, 0x200004 },
{
0*8, 0*8+1, 0*8+2, 0*8+3, 1*8, 1*8+1, 1*8+2, 1*8+3,
2*8, 2*8+1, 2*8+2, 2*8+3, 3*8, 3*8+1, 3*8+2, 3*8+3,
4*8, 4*8+1, 4*8+2, 4*8+3, 5*8, 5*8+1, 5*8+2, 5*8+3,
6*8, 6*8+1, 6*8+2, 6*8+3, 7*8, 7*8+1, 7*8+2, 7*8+3
},
{
0*2*32, 1*2*32, 2*2*32, 3*2*32, 4*2*32, 5*2*32, 6*2*32, 7*2*32,
8*2*32, 9*2*32, 10*2*32, 11*2*32, 12*2*32, 13*2*32, 14*2*32, 15*2*32,
16*2*32, 17*2*32, 18*2*32, 19*2*32, 20*2*32, 21*2*32, 22*2*32, 23*2*32,
24*2*32, 25*2*32, 26*2*32, 27*2*32, 28*2*32, 29*2*32, 30*2*32, 31*2*32
},
2*32*32
};
uint8_t *src = memregion(region)->base();
int len = memregion(region)->bytes();
int total = spr32_layout.total;
int size = spr32_layout.charincrement / 8;
int i;
m_decoded_32 = make_unique_clear<uint8_t[]>(len);
for (i = 0; i < total; i++)
{
int code;
code = (i & 0x07f) | ((i & 0x180) << 1) | 0x80;
code &= ~((i & 0x200) >> 2);
memcpy(&m_decoded_32[size * (i + 0)], &src[size * (code + 0)], size);
memcpy(&m_decoded_32[size * (i + total)], &src[size * (code + total)], size);
}
decode_sprite(m_sprite32, &spr32_layout, m_decoded_32.get());
}
void tceptor_state::video_start()
{
int gfx_index;
m_sprite_ram_buffered = make_unique_clear<uint16_t[]>(0x200/2);
/* find first empty slot to decode gfx */
for (gfx_index = 0; gfx_index < MAX_GFX_ELEMENTS; gfx_index++)
if (m_gfxdecode->gfx(gfx_index) == nullptr)
break;
assert(gfx_index + 4 <= MAX_GFX_ELEMENTS);
m_bg = gfx_index++;
decode_bg("gfx2");
m_sprite16 = gfx_index++;
decode_sprite16("gfx3");
m_sprite32 = gfx_index++;
decode_sprite32("gfx4");
/* allocate temp bitmaps */
m_screen->register_screen_bitmap(m_temp_bitmap);
m_c45_road->set_transparent_color(m_palette->pen_indirect(0xfff));
m_tx_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(FUNC(tceptor_state::get_tx_tile_info),this), TILEMAP_SCAN_COLS, 8, 8, 34, 28);
m_tx_tilemap->set_scrollx(0, -2*8);
m_tx_tilemap->set_scrolly(0, 0);
m_tx_tilemap->configure_groups(*m_gfxdecode->gfx(0), 7);
m_bg1_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(FUNC(tceptor_state::get_bg1_tile_info),this), TILEMAP_SCAN_ROWS, 8, 8, 64, 32);
m_bg2_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(FUNC(tceptor_state::get_bg2_tile_info),this), TILEMAP_SCAN_ROWS, 8, 8, 64, 32);
save_pointer(NAME(m_sprite_ram_buffered.get()), 0x200 / 2);
save_item(NAME(m_bg1_scroll_x));
save_item(NAME(m_bg1_scroll_y));
save_item(NAME(m_bg2_scroll_x));
save_item(NAME(m_bg2_scroll_y));
}
/*******************************************************************/
/*
Sprite data format
000: zzzzzzBB BTTTTTTT
002: ZZZZZZPP PPCCCCCC
100: fFL---YY YYYYYYYY
102: ------XX XXXXXXXX
B: bank
T: number
P: priority
C: color
X: x
Y: y
L: large sprite
F: flip x
f: flip y
Z: zoom x
z: zoom y
*/
void tceptor_state::draw_sprites(bitmap_ind16 &bitmap, const rectangle &cliprect, int sprite_priority)
{
uint16_t *mem1 = &m_sprite_ram_buffered[0x000/2];
uint16_t *mem2 = &m_sprite_ram_buffered[0x100/2];
int need_mask = 0;
int i;
for (i = 0; i < 0x100; i += 2)
{
int scalex = (mem1[1 + i] & 0xfc00) << 1;
int scaley = (mem1[0 + i] & 0xfc00) << 1;
int pri = 7 - ((mem1[1 + i] & 0x3c0) >> 6);
if (pri == sprite_priority && scalex && scaley)
{
int x = mem2[1 + i] & 0x3ff;
int y = 512 - (mem2[0 + i] & 0x3ff);
int flipx = mem2[0 + i] & 0x4000;
int flipy = mem2[0 + i] & 0x8000;
int color = mem1[1 + i] & 0x3f;
int gfx;
int code;
if (mem2[0 + i] & 0x2000)
{
gfx = m_sprite32;
code = mem1[0 + i] & 0x3ff;
}
else
{
gfx = m_sprite16;
code = mem1[0 + i] & 0x1ff;
scaley *= 2;
}
if (m_is_mask_spr[color])
{
if (!need_mask)
// backup previous bitmap
copybitmap(m_temp_bitmap, bitmap, 0, 0, 0, 0, cliprect);
need_mask = 1;
}
// round off
scalex += 0x800;
scaley += 0x800;
x -= 64;
y -= 78;
m_gfxdecode->gfx(gfx)->zoom_transmask(bitmap,
cliprect,
code,
color,
flipx, flipy,
x, y,
scalex,
scaley,
m_palette->transpen_mask(*m_gfxdecode->gfx(gfx), color, SPR_TRANS_COLOR));
}
}
/* if SPR_MASK_COLOR pen is used, restore pixels from previous bitmap */
if (need_mask)
{
int x, y;
for (x = cliprect.min_x; x <= cliprect.max_x; x++)
for (y = cliprect.min_y; y <= cliprect.max_y; y++)
if (m_palette->pen_indirect(bitmap.pix16(y, x)) == SPR_MASK_COLOR)
// restore pixel
bitmap.pix16(y, x) = m_temp_bitmap.pix16(y, x);
}
}
uint32_t tceptor_state::screen_update_tceptor(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
rectangle rect;
int pri;
int bg_center = 144 - ((((m_bg1_scroll_x + m_bg2_scroll_x ) & 0x1ff) - 288) / 2);
// left background
rect = cliprect;
rect.max_x = bg_center;
m_bg1_tilemap->set_scrollx(0, m_bg1_scroll_x + 12);
m_bg1_tilemap->set_scrolly(0, m_bg1_scroll_y + 20); //32?
m_bg1_tilemap->draw(screen, bitmap, rect, 0, 0);
// right background
rect.min_x = bg_center;
rect.max_x = cliprect.max_x;
m_bg2_tilemap->set_scrollx(0, m_bg2_scroll_x + 20);
m_bg2_tilemap->set_scrolly(0, m_bg2_scroll_y + 20); // 32?
m_bg2_tilemap->draw(screen, bitmap, rect, 0, 0);
for (pri = 0; pri < 8; pri++)
{
m_c45_road->draw(bitmap, cliprect, pri * 2);
m_c45_road->draw(bitmap, cliprect, pri * 2 + 1);
draw_sprites(bitmap, cliprect, pri);
}
m_tx_tilemap->draw(screen, bitmap, cliprect, 0, 0);
return 0;
}
WRITE_LINE_MEMBER(tceptor_state::screen_vblank_tceptor)
{
// rising edge
if (state)
{
memcpy(m_sprite_ram_buffered.get(), m_sprite_ram, 0x200);
if (m_m6809_irq_enable)
m_maincpu->set_input_line(0, HOLD_LINE);
else
m_m6809_irq_enable = 1;
if (m_m68k_irq_enable)
m_subcpu->set_input_line(1, HOLD_LINE);
if (m_mcu_irq_enable)
m_mcu->set_input_line(0, HOLD_LINE);
else
m_mcu_irq_enable = 1;
}
}
WRITE8_MEMBER(tceptor_state::tceptor2_shutter_w)
{
// 3D scope shutter control
m_shutter = BIT(data, 0);
}