mirror of
https://github.com/holub/mame
synced 2025-04-23 08:49:55 +03:00
Improved the graphic rendering a bit in blackt96, almost where it should be. Sound still missing and other possible issues related to PIC that is not hooked up - any help there is appreciated. From Haze (nw)
This commit is contained in:
parent
e2905e68ee
commit
88577a96d4
@ -57,16 +57,29 @@ This would suggest the hardware is capable of drawing 31x32 (992) sprites,
|
||||
each made of 1x32 tile strips, however the game only ever appears to use 3
|
||||
banks of tile strips (96 sprites)
|
||||
|
||||
Priority isn't understood (other attributes, sort by y pos?)
|
||||
In practice it seems like the hardware can probably only draw these 3 banks
|
||||
as separate layers with seemingly hardcoded priority levels, despite the odd
|
||||
design.
|
||||
|
||||
Furthermore there are two sets of graphics, background tiles and 'sprite'
|
||||
tiles which are of different bitdepths, but are addressed in the same way.
|
||||
|
||||
Tilemap hookup is just to make viewing easier, it's not used for rendering
|
||||
|
||||
|
||||
There is also an additional 8x8 text layer..
|
||||
|
||||
|
||||
Bugs:
|
||||
|
||||
Sometimes if you attack an enemy when you're at the top of the screen they'll
|
||||
end up landing in an even higher position, and appear over the backgrounds!
|
||||
I think this is just a game bug..
|
||||
|
||||
The timer doesn't work (PIC?, RAM Mirror?)
|
||||
|
||||
There are some unmapped writes past the end of text ram too
|
||||
|
||||
|
||||
*/
|
||||
|
||||
#include "emu.h"
|
||||
@ -110,6 +123,11 @@ public:
|
||||
DECLARE_READ8_MEMBER(blackt96_soundio_port02_r);
|
||||
DECLARE_WRITE8_MEMBER(blackt96_soundio_port02_w);
|
||||
|
||||
DECLARE_READ16_MEMBER( random_r )
|
||||
{
|
||||
return machine().rand();
|
||||
}
|
||||
|
||||
DECLARE_WRITE16_MEMBER(bg_videoram0_w);
|
||||
DECLARE_WRITE16_MEMBER(bg_videoram1_w);
|
||||
DECLARE_WRITE16_MEMBER(bg_videoram2_w);
|
||||
@ -121,6 +139,7 @@ public:
|
||||
|
||||
UINT16* m_spriteram[8];
|
||||
tilemap_t *m_bg_tilemap[8];
|
||||
UINT8 m_txt_bank;
|
||||
|
||||
TILE_GET_INFO_MEMBER(get_bg0_tile_info);
|
||||
TILE_GET_INFO_MEMBER(get_bg1_tile_info);
|
||||
@ -175,15 +194,15 @@ void blackt96_state::video_start()
|
||||
|
||||
m_spriteram[0] = m_spriteram0;
|
||||
m_spriteram[1] = m_spriteram1;
|
||||
m_spriteram[2] = m_spriteram2;
|
||||
m_spriteram[2] = m_spriteram2;
|
||||
m_spriteram[3] = m_spriteram3;
|
||||
m_spriteram[4] = m_spriteram4;
|
||||
m_spriteram[5] = m_spriteram5;
|
||||
m_spriteram[6] = m_spriteram6;
|
||||
m_spriteram[6] = m_spriteram6;
|
||||
m_spriteram[7] = m_spriteram7;
|
||||
}
|
||||
|
||||
static void draw_strip(running_machine &machine, bitmap_ind16 &bitmap, const rectangle &cliprect, int page, int column, int bg)
|
||||
static void draw_strip(running_machine &machine, bitmap_ind16 &bitmap, const rectangle &cliprect, int page, int column)
|
||||
{
|
||||
blackt96_state *state = machine.driver_data<blackt96_state>();
|
||||
/* the very first 'page' in the spriteram contains the x/y positions for each tile strip */
|
||||
@ -203,7 +222,7 @@ static void draw_strip(running_machine &machine, bitmap_ind16 &bitmap, const rec
|
||||
yy = 0x1ff-yy;
|
||||
if (yy&0x100) yy-=0x200;
|
||||
|
||||
yy -= 32;
|
||||
yy -= 15;
|
||||
|
||||
UINT16* base2 = state->m_spriteram[page]+column * (0x80/2);
|
||||
|
||||
@ -218,52 +237,43 @@ static void draw_strip(running_machine &machine, bitmap_ind16 &bitmap, const rec
|
||||
|
||||
if (tile&0x2000)
|
||||
{
|
||||
if (bg) drawgfx_transpen(bitmap,cliprect,gfxbg,tile&0x1fff,colour>>4,flipx,0,xx,yy+y*16,0);
|
||||
drawgfx_transpen(bitmap,cliprect,gfxbg,tile&0x1fff,colour>>4,flipx,0,xx,yy+y*16,0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!bg) drawgfx_transpen(bitmap,cliprect,gfxspr,tile&0x1fff,colour,flipx,0,xx,yy+y*16,0);
|
||||
drawgfx_transpen(bitmap,cliprect,gfxspr,tile&0x1fff,colour,flipx,0,xx,yy+y*16,0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_page(running_machine &machine, bitmap_ind16 &bitmap, const rectangle &cliprect, int page)
|
||||
{
|
||||
for (int strip=0;strip<32;strip++)
|
||||
{
|
||||
draw_strip(machine, bitmap, cliprect, page, strip);
|
||||
}
|
||||
}
|
||||
|
||||
UINT32 blackt96_state::screen_update_blackt96(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
|
||||
{
|
||||
int count;
|
||||
int x,y;
|
||||
gfx_element *gfx = machine().gfx[2];
|
||||
|
||||
bitmap.fill(get_black_pen(machine()), cliprect);
|
||||
|
||||
int strip;
|
||||
int page;
|
||||
|
||||
for (strip=0;strip<32;strip++)
|
||||
{
|
||||
for (page = 7;page>0;page--)
|
||||
{
|
||||
draw_strip(machine(), bitmap, cliprect, page, strip, 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (strip=0;strip<32;strip++)
|
||||
{
|
||||
for (page = 7;page>0;page--)
|
||||
{
|
||||
draw_strip(machine(), bitmap, cliprect, page, strip, 0);
|
||||
}
|
||||
}
|
||||
draw_page(machine(), bitmap, cliprect, 2); // bg
|
||||
draw_page(machine(), bitmap, cliprect, 3); // lower pri sprites
|
||||
draw_page(machine(), bitmap, cliprect, 1); // higher pri sprites
|
||||
|
||||
|
||||
/* Text Layer */
|
||||
count = 0;
|
||||
int count = 0;
|
||||
int x,y;
|
||||
gfx_element *gfx = machine().gfx[2];
|
||||
|
||||
for (x=0;x<64;x++)
|
||||
{
|
||||
for (y=0;y<32;y++)
|
||||
{
|
||||
UINT16 tile = (m_tilemapram[count*2]&0x7ff)+0x800; // +0xc00 for korean text
|
||||
UINT16 tile = (m_tilemapram[count*2]&0xff);
|
||||
tile += m_txt_bank * 0x100;
|
||||
drawgfx_transpen(bitmap,cliprect,gfx,tile,0,0,0,x*8,-16+y*8,0);
|
||||
count++;
|
||||
}
|
||||
@ -273,15 +283,24 @@ UINT32 blackt96_state::screen_update_blackt96(screen_device &screen, bitmap_ind1
|
||||
}
|
||||
|
||||
|
||||
WRITE16_MEMBER(blackt96_state::blackt96_c0000_w)
|
||||
{
|
||||
printf("blackt96_c0000_w %04x %04x\n",data,mem_mask);
|
||||
}
|
||||
|
||||
WRITE16_MEMBER(blackt96_state::blackt96_80000_w)
|
||||
{
|
||||
// TO sound MCU?
|
||||
printf("blackt96_80000_w %04x %04x\n",data,mem_mask);
|
||||
//printf("blackt96_80000_w %04x %04x\n",data,mem_mask);
|
||||
}
|
||||
|
||||
|
||||
WRITE16_MEMBER(blackt96_state::blackt96_c0000_w)
|
||||
{
|
||||
// unknown, also sound mcu?
|
||||
// -bbb --21
|
||||
// 1 - coin counter 1
|
||||
// 2 - coin counter 2
|
||||
// b = text tile bank?
|
||||
|
||||
m_txt_bank = (data & 0xf0)>>4;
|
||||
|
||||
printf("blackt96_c0000_w %04x %04x\n",data & 0xfc,mem_mask);
|
||||
}
|
||||
|
||||
|
||||
@ -289,16 +308,18 @@ static ADDRESS_MAP_START( blackt96_map, AS_PROGRAM, 16, blackt96_state )
|
||||
AM_RANGE(0x000000, 0x07ffff) AM_ROM
|
||||
AM_RANGE(0x080000, 0x080001) AM_READ_PORT("P1_P2") AM_WRITE(blackt96_80000_w)
|
||||
AM_RANGE(0x0c0000, 0x0c0001) AM_READ_PORT("IN1") AM_WRITE(blackt96_c0000_w) // COIN INPUT
|
||||
AM_RANGE(0x0e0000, 0x0e0001) AM_READ_PORT("IN2")
|
||||
AM_RANGE(0x0e8000, 0x0e8001) AM_READ_PORT("IN3")
|
||||
AM_RANGE(0x0e0000, 0x0e0001) AM_READ( random_r ) // AM_READ_PORT("IN2") // unk, from sound?
|
||||
AM_RANGE(0x0e8000, 0x0e8001) AM_READ( random_r ) // AM_READ_PORT("IN3") // unk, from sound?
|
||||
AM_RANGE(0x0f0000, 0x0f0001) AM_READ_PORT("DSW1")
|
||||
AM_RANGE(0x0f0008, 0x0f0009) AM_READ_PORT("DSW2")
|
||||
|
||||
AM_RANGE(0x100000, 0x100fff) AM_RAM AM_SHARE("tilemapram") // text tilemap
|
||||
AM_RANGE(0x200000, 0x200fff) AM_RAM_WRITE(bg_videoram0_w) AM_SHARE("spriteram0")
|
||||
AM_RANGE(0x201000, 0x201fff) AM_RAM_WRITE(bg_videoram1_w) AM_SHARE("spriteram1")
|
||||
AM_RANGE(0x202000, 0x202fff) AM_RAM_WRITE(bg_videoram2_w) AM_SHARE("spriteram2")
|
||||
AM_RANGE(0x203000, 0x203fff) AM_RAM_WRITE(bg_videoram3_w) AM_SHARE("spriteram3")
|
||||
AM_RANGE(0x200000, 0x200fff) AM_RAM_WRITE(bg_videoram0_w) AM_SHARE("spriteram0") // this is the 'list'
|
||||
AM_RANGE(0x201000, 0x201fff) AM_RAM_WRITE(bg_videoram1_w) AM_SHARE("spriteram1") // sprites layer 0
|
||||
AM_RANGE(0x202000, 0x202fff) AM_RAM_WRITE(bg_videoram2_w) AM_SHARE("spriteram2") // bg layer?
|
||||
AM_RANGE(0x203000, 0x203fff) AM_RAM_WRITE(bg_videoram3_w) AM_SHARE("spriteram3") // sprites layer 1
|
||||
// the following potentially exist (the ram is cleared, there is room for entries in the 'spriteram0' region
|
||||
// but they never get used..)
|
||||
AM_RANGE(0x204000, 0x204fff) AM_RAM_WRITE(bg_videoram4_w) AM_SHARE("spriteram4")
|
||||
AM_RANGE(0x205000, 0x205fff) AM_RAM_WRITE(bg_videoram5_w) AM_SHARE("spriteram5")
|
||||
AM_RANGE(0x206000, 0x206fff) AM_RAM_WRITE(bg_videoram6_w) AM_SHARE("spriteram6")
|
||||
@ -318,65 +339,31 @@ static INPUT_PORTS_START( blackt96 )
|
||||
PORT_BIT( 0x0002, IP_ACTIVE_LOW, IPT_JOYSTICK_DOWN ) PORT_PLAYER(1)
|
||||
PORT_BIT( 0x0004, IP_ACTIVE_LOW, IPT_JOYSTICK_LEFT ) PORT_PLAYER(1)
|
||||
PORT_BIT( 0x0008, IP_ACTIVE_LOW, IPT_JOYSTICK_RIGHT ) PORT_PLAYER(1)
|
||||
PORT_BIT( 0x0010, IP_ACTIVE_LOW, IPT_BUTTON1 ) PORT_PLAYER(1)
|
||||
PORT_BIT( 0x0020, IP_ACTIVE_LOW, IPT_BUTTON2 ) PORT_PLAYER(1)
|
||||
PORT_BIT( 0x0040, IP_ACTIVE_LOW, IPT_BUTTON3 ) PORT_PLAYER(1)
|
||||
PORT_BIT( 0x0010, IP_ACTIVE_LOW, IPT_BUTTON3 ) PORT_PLAYER(1) // kick
|
||||
PORT_BIT( 0x0020, IP_ACTIVE_LOW, IPT_BUTTON2 ) PORT_PLAYER(1) // jump
|
||||
PORT_BIT( 0x0040, IP_ACTIVE_LOW, IPT_BUTTON1 ) PORT_PLAYER(1) // punch / pick up
|
||||
PORT_BIT( 0x0080, IP_ACTIVE_LOW, IPT_START1 )
|
||||
PORT_BIT( 0x0100, IP_ACTIVE_LOW, IPT_JOYSTICK_UP ) PORT_PLAYER(2)
|
||||
PORT_BIT( 0x0200, IP_ACTIVE_LOW, IPT_JOYSTICK_DOWN ) PORT_PLAYER(2)
|
||||
PORT_BIT( 0x0400, IP_ACTIVE_LOW, IPT_JOYSTICK_LEFT ) PORT_PLAYER(2)
|
||||
PORT_BIT( 0x0800, IP_ACTIVE_LOW, IPT_JOYSTICK_RIGHT ) PORT_PLAYER(2)
|
||||
PORT_BIT( 0x1000, IP_ACTIVE_LOW, IPT_BUTTON1 ) PORT_PLAYER(2)
|
||||
PORT_BIT( 0x2000, IP_ACTIVE_LOW, IPT_BUTTON2 ) PORT_PLAYER(2)
|
||||
PORT_BIT( 0x4000, IP_ACTIVE_LOW, IPT_BUTTON3 ) PORT_PLAYER(2)
|
||||
PORT_BIT( 0x1000, IP_ACTIVE_LOW, IPT_BUTTON3 ) PORT_PLAYER(2) // kick
|
||||
PORT_BIT( 0x2000, IP_ACTIVE_LOW, IPT_BUTTON2 ) PORT_PLAYER(2) // jump
|
||||
PORT_BIT( 0x4000, IP_ACTIVE_LOW, IPT_BUTTON1 ) PORT_PLAYER(2) // punch / pick up
|
||||
PORT_BIT( 0x8000, IP_ACTIVE_LOW, IPT_START2 )
|
||||
|
||||
PORT_START("IN1")
|
||||
PORT_BIT( 0x0001, IP_ACTIVE_LOW, IPT_COIN1 ) // Test mode lists this as Service 1, but it appears to be Coin 1 (uses Coin 1 coinage etc.)
|
||||
PORT_DIPNAME( 0x0002, 0x0002, "xx")
|
||||
PORT_DIPSETTING( 0x0002, DEF_STR( Off ) )
|
||||
PORT_DIPSETTING( 0x0000, DEF_STR( On ) )
|
||||
PORT_DIPNAME( 0x0004, 0x0004, DEF_STR( Unknown ) )
|
||||
PORT_DIPSETTING( 0x0004, DEF_STR( Off ) )
|
||||
PORT_DIPSETTING( 0x0000, DEF_STR( On ) )
|
||||
PORT_DIPNAME( 0x0008, 0x0008, DEF_STR( Unknown ) )
|
||||
PORT_DIPSETTING( 0x0008, DEF_STR( Off ) )
|
||||
PORT_DIPSETTING( 0x0000, DEF_STR( On ) )
|
||||
PORT_DIPNAME( 0x0010, 0x0010, DEF_STR( Unknown ) ) // Test mode lists this as Coin 1, but it doesn't work
|
||||
PORT_DIPSETTING( 0x0010, DEF_STR( Off ) )
|
||||
PORT_DIPSETTING( 0x0000, DEF_STR( On ) )
|
||||
PORT_BIT( 0x0002, IP_ACTIVE_LOW, IPT_SERVICE1 ) // acts as a serive mode mirror
|
||||
PORT_BIT( 0x0004, IP_ACTIVE_HIGH, IPT_UNKNOWN )
|
||||
PORT_BIT( 0x0008, IP_ACTIVE_HIGH, IPT_UNKNOWN )
|
||||
PORT_BIT( 0x0010, IP_ACTIVE_HIGH, IPT_UNKNOWN ) // Test mode lists this as Coin 1, but it doesn't work
|
||||
PORT_BIT( 0x0020, IP_ACTIVE_LOW, IPT_COIN2 )
|
||||
PORT_DIPNAME( 0x0040, 0x0040, DEF_STR( Unknown ) )
|
||||
PORT_DIPSETTING( 0x0040, DEF_STR( Off ) )
|
||||
PORT_DIPSETTING( 0x0000, DEF_STR( On ) )
|
||||
PORT_DIPNAME( 0x0080, 0x0080, DEF_STR( Unknown ) )
|
||||
PORT_DIPSETTING( 0x0080, DEF_STR( Off ) )
|
||||
PORT_DIPSETTING( 0x0000, DEF_STR( On ) )
|
||||
PORT_DIPNAME( 0x0100, 0x0100, DEF_STR( Unknown ) )
|
||||
PORT_DIPSETTING( 0x0100, DEF_STR( Off ) )
|
||||
PORT_DIPSETTING( 0x0000, DEF_STR( On ) )
|
||||
PORT_DIPNAME( 0x0200, 0x0200, DEF_STR( Unknown ) )
|
||||
PORT_DIPSETTING( 0x0200, DEF_STR( Off ) )
|
||||
PORT_DIPSETTING( 0x0000, DEF_STR( On ) )
|
||||
PORT_DIPNAME( 0x0400, 0x0400, DEF_STR( Unknown ) )
|
||||
PORT_DIPSETTING( 0x0400, DEF_STR( Off ) )
|
||||
PORT_DIPSETTING( 0x0000, DEF_STR( On ) )
|
||||
PORT_DIPNAME( 0x0800, 0x0800, DEF_STR( Unknown ) )
|
||||
PORT_DIPSETTING( 0x0800, DEF_STR( Off ) )
|
||||
PORT_DIPSETTING( 0x0000, DEF_STR( On ) )
|
||||
PORT_DIPNAME( 0x1000, 0x1000, DEF_STR( Unknown ) )
|
||||
PORT_DIPSETTING( 0x1000, DEF_STR( Off ) )
|
||||
PORT_DIPSETTING( 0x0000, DEF_STR( On ) )
|
||||
PORT_DIPNAME( 0x2000, 0x2000, DEF_STR( Unknown ) )
|
||||
PORT_DIPSETTING( 0x2000, DEF_STR( Off ) )
|
||||
PORT_DIPSETTING( 0x0000, DEF_STR( On ) )
|
||||
PORT_DIPNAME( 0x4000, 0x4000, DEF_STR( Unknown ) )
|
||||
PORT_DIPSETTING( 0x4000, DEF_STR( Off ) )
|
||||
PORT_DIPSETTING( 0x0000, DEF_STR( On ) )
|
||||
PORT_DIPNAME( 0x8000, 0x8000, DEF_STR( Unknown ) )
|
||||
PORT_DIPSETTING( 0x8000, DEF_STR( Off ) )
|
||||
PORT_DIPSETTING( 0x0000, DEF_STR( On ) )
|
||||
|
||||
PORT_BIT( 0x0040, IP_ACTIVE_HIGH, IPT_UNKNOWN )
|
||||
PORT_BIT( 0x0080, IP_ACTIVE_HIGH, IPT_UNKNOWN )
|
||||
PORT_BIT( 0xff00, IP_ACTIVE_HIGH, IPT_UNKNOWN )
|
||||
|
||||
#if 0
|
||||
PORT_START("IN2")
|
||||
PORT_DIPNAME( 0x0001, 0x0001, "2" )
|
||||
PORT_DIPSETTING( 0x0001, DEF_STR( Off ) )
|
||||
@ -476,6 +463,7 @@ static INPUT_PORTS_START( blackt96 )
|
||||
PORT_DIPNAME( 0x8000, 0x8000, DEF_STR( Unknown ) )
|
||||
PORT_DIPSETTING( 0x8000, DEF_STR( Off ) )
|
||||
PORT_DIPSETTING( 0x0000, DEF_STR( On ) )
|
||||
#endif
|
||||
|
||||
/* Dipswitch Port A */
|
||||
PORT_START("DSW1")
|
||||
@ -505,7 +493,7 @@ static INPUT_PORTS_START( blackt96 )
|
||||
/* Dipswitch Port B */
|
||||
PORT_START("DSW2")
|
||||
PORT_SERVICE( 0x0100, IP_ACTIVE_HIGH ) PORT_DIPLOCATION("SW2:!8")
|
||||
PORT_DIPNAME( 0x0200, 0x0200, "Continue" ) PORT_DIPLOCATION("SW2:!7")
|
||||
PORT_DIPNAME( 0x0200, 0x0000, "Continue" ) PORT_DIPLOCATION("SW2:!7")
|
||||
PORT_DIPSETTING( 0x0200, DEF_STR( Off ) )
|
||||
PORT_DIPSETTING( 0x0000, DEF_STR( On ) )
|
||||
PORT_DIPNAME( 0x0c00, 0x0400, DEF_STR( Bonus_Life ) ) PORT_DIPLOCATION("SW2:!5,!6")
|
||||
@ -620,7 +608,7 @@ static MACHINE_CONFIG_START( blackt96, blackt96_state )
|
||||
MCFG_SCREEN_VBLANK_TIME(ATTOSECONDS_IN_USEC(0))
|
||||
MCFG_SCREEN_SIZE(256, 256)
|
||||
// MCFG_SCREEN_VISIBLE_AREA(0*8, 16*32-1, 0*8, 16*32-1)
|
||||
MCFG_SCREEN_VISIBLE_AREA(0*8, 256-1, 0*8, 208-1)
|
||||
MCFG_SCREEN_VISIBLE_AREA(0*8, 256-1, 0*8, 224-1)
|
||||
MCFG_SCREEN_UPDATE_DRIVER(blackt96_state, screen_update_blackt96)
|
||||
|
||||
MCFG_PALETTE_LENGTH(0x800)
|
||||
@ -664,9 +652,11 @@ ROM_START( blackt96 )
|
||||
ROM_LOAD32_BYTE( "13", 0x00002, 0x40000, CRC(0acceb9d) SHA1(e8a85c7eab45d84613ac37a9b7ffbc45b44eb2e5) )
|
||||
ROM_LOAD32_BYTE( "14", 0x00003, 0x40000, CRC(b5e3de25) SHA1(33ac5602ab6bcadc8b0d1aa805a3bdce0b67c215) )
|
||||
|
||||
ROM_REGION( 0x20000, "gfx3", 0 ) // txt tiles
|
||||
ROM_LOAD16_BYTE( "9", 0x00000, 0x10000, CRC(81a4cf4c) SHA1(94b2bbcbc8327d9babbc3b222bd88954c7e7b80e) )
|
||||
ROM_LOAD16_BYTE( "10", 0x00001, 0x10000, CRC(b78232a2) SHA1(36a4f01011faf64e46b73f0082ab04843ac8b0e2) )
|
||||
ROM_REGION( 0x10000, "gfx3", 0 ) // txt tiles
|
||||
ROM_LOAD16_BYTE( "9", 0x00000, 0x08000, CRC(81a4cf4c) SHA1(94b2bbcbc8327d9babbc3b222bd88954c7e7b80e) )
|
||||
ROM_CONTINUE( 0x00000, 0x08000 ) // first half is empty
|
||||
ROM_LOAD16_BYTE( "10", 0x00001, 0x08000, CRC(b78232a2) SHA1(36a4f01011faf64e46b73f0082ab04843ac8b0e2) )
|
||||
ROM_CONTINUE( 0x00001, 0x08000 ) // first half is empty
|
||||
ROM_END
|
||||
|
||||
GAME( 1996, blackt96, 0, blackt96, blackt96, driver_device, 0, ROT0, "D.G.R.M.", "Black Touch '96", GAME_NOT_WORKING | GAME_NO_SOUND )
|
||||
|
Loading…
Reference in New Issue
Block a user