暂存 角色正常读取
This commit is contained in:
parent
fd55de15b2
commit
b25486bb46
@ -704,7 +704,7 @@ namespace PSO2SERVER
|
||||
|
||||
var fakeChar = new Character
|
||||
{
|
||||
CharacterId = 12345678 + new Random().Next(),
|
||||
CharacterID = 12345678 + new Random().Next(),
|
||||
Player = fakePlayer,
|
||||
Name = playerName,
|
||||
Looks = client.Character.Looks,
|
||||
|
@ -261,7 +261,7 @@ namespace PSO2SERVER.Models
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public int CharacterId { get; set; }
|
||||
public int CharacterID { get; set; }
|
||||
public int player_id { get; set; }
|
||||
public uint unk1 { get; set; }
|
||||
public uint voice_type { get; set; }
|
||||
|
@ -34,8 +34,6 @@ namespace PSO2SERVER.Models
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct Items
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public PSO2ItemNone None;
|
||||
[FieldOffset(0)]
|
||||
public PSO2ItemWeapon Weapon;
|
||||
[FieldOffset(0)]
|
||||
@ -46,8 +44,10 @@ namespace PSO2SERVER.Models
|
||||
public PSO2ItemCamo Camo;
|
||||
[FieldOffset(0)]
|
||||
public PSO2ItemUnit Unit;
|
||||
[FieldOffset(0)]
|
||||
public byte[] Unknown;
|
||||
//[FieldOffset(0)]
|
||||
//public byte[] Unknown;
|
||||
//[FieldOffset(0)]
|
||||
//public PSO2ItemNone None;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
|
@ -1,25 +0,0 @@
|
||||
using PSO2SERVER.Packets.PSOPackets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PSO2SERVER.Packets.Handlers
|
||||
{
|
||||
[PacketHandlerAttr(0x03, 0x10)]
|
||||
public class DoItMaybe : PacketHandler
|
||||
{
|
||||
#region implemented abstract members of PacketHandler
|
||||
|
||||
public override void HandlePacket(Client context, byte flags, byte[] data, uint position, uint size)
|
||||
{
|
||||
if (context.User == null || context.Character == null)
|
||||
return;
|
||||
|
||||
context.SendPacket(new LoadingScreenRemovePacket());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
45
Server/Packets/Handlers/03-ServerHandler/03-10-MapLoaded.cs
Normal file
45
Server/Packets/Handlers/03-ServerHandler/03-10-MapLoaded.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using PSO2SERVER.Models;
|
||||
using PSO2SERVER.Packets.PSOPackets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PSO2SERVER.Packets.Handlers
|
||||
{
|
||||
[PacketHandlerAttr(0x03, 0x10)]
|
||||
public class MapLoaded : PacketHandler
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct MapLoadedPacket
|
||||
{
|
||||
/// Loaded zone object.
|
||||
public ObjectHeader MapObject;
|
||||
|
||||
/// Unknown data, 32 bytes.
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)]
|
||||
public byte[] Unk;
|
||||
|
||||
// 可选构造函数
|
||||
public MapLoadedPacket(ObjectHeader mapObject)
|
||||
{
|
||||
MapObject = mapObject;
|
||||
Unk = new byte[0x20];
|
||||
}
|
||||
}
|
||||
|
||||
#region implemented abstract members of PacketHandler
|
||||
|
||||
public override void HandlePacket(Client context, byte flags, byte[] data, uint position, uint size)
|
||||
{
|
||||
if (context.User == null || context.Character == null)
|
||||
return;
|
||||
|
||||
context.SendPacket(new LoadingScreenRemovePacket());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace PSO2SERVER.Packets.Handlers
|
||||
{
|
||||
[PacketHandlerAttr(0xB, 0x19)]
|
||||
[PacketHandlerAttr(0x0B, 0x19)]
|
||||
class QuestDifficultyRequestHandler : PacketHandler
|
||||
{
|
||||
public override void HandlePacket(Client context, byte flags, byte[] data, uint position, uint size)
|
||||
|
@ -4,7 +4,7 @@ using PSO2SERVER.Packets.PSOPackets;
|
||||
|
||||
namespace PSO2SERVER.Packets.Handlers
|
||||
{
|
||||
[PacketHandlerAttr(0xE, 0xC)]
|
||||
[PacketHandlerAttr(0x0E, 0x0C)]
|
||||
class QuestDifficultyStartHandler : PacketHandler
|
||||
{
|
||||
// Go go maximum code duplication (for now)
|
||||
|
@ -22,19 +22,19 @@ namespace PSO2SERVER.Packets.Handlers
|
||||
|
||||
public override void HandlePacket(Client context, byte flags, byte[] data, uint position, uint size)
|
||||
{
|
||||
if (context.User == null)
|
||||
return;
|
||||
|
||||
var reader = new PacketReader(data, position, size);
|
||||
var pkt = reader.ReadStruct<CharacterSelectedPacket>();
|
||||
|
||||
//Logger.Write("id {0}", charId);
|
||||
|
||||
if (context.User == null)
|
||||
return;
|
||||
|
||||
if (context.Character == null) // On character create, this is already set.
|
||||
{
|
||||
using (var db = new ServerEf())
|
||||
{
|
||||
var character = db.Characters.Where(c => c.CharacterId == pkt.CharId).First();
|
||||
var character = db.Characters.Where(c => c.CharacterID == pkt.CharId).First();
|
||||
|
||||
if (character == null || character.Player.PlayerId != context.User.PlayerId)
|
||||
{
|
||||
|
@ -4,8 +4,6 @@ using PSO2SERVER.Models;
|
||||
using PSO2SERVER.Packets.PSOPackets;
|
||||
using PSO2SERVER.Database;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace PSO2SERVER.Packets.Handlers
|
||||
{
|
||||
@ -20,30 +18,27 @@ namespace PSO2SERVER.Packets.Handlers
|
||||
return;
|
||||
|
||||
|
||||
PacketWriter w = new PacketWriter();
|
||||
|
||||
var reader = new PacketReader(data, position, size);
|
||||
var info = string.Format("[<--] 接收到的数据 (hex): ");
|
||||
Logger.WriteHex(info, data);
|
||||
var setting = reader.ReadStruct<Character.CharParam>();
|
||||
var name = reader.ReadFixedLengthUtf16(16);//玩家名称 宽字符
|
||||
var looks = reader.ReadStruct<Character.LooksParam>();
|
||||
var unk3 = reader.ReadUInt32();
|
||||
var jobs = reader.ReadStruct<Character.JobParam>();
|
||||
w.WriteStruct(jobs);
|
||||
Logger.WriteHex(info, w.ToArray());
|
||||
|
||||
//Logger.WriteInternal("[CHR] {0} 创建了名为 {1} 的新角色.", context.User.Username, name);
|
||||
reader.ReadBytes(12); // 12 unknown bytes
|
||||
reader.ReadByte(); // VoiceType
|
||||
reader.ReadBytes(5); // 5 unknown bytes
|
||||
reader.ReadUInt16(); // VoiceData
|
||||
var name = reader.ReadFixedLengthUtf16(16);
|
||||
|
||||
reader.BaseStream.Seek(0x4, SeekOrigin.Current); // Padding
|
||||
var looks = reader.ReadStruct<Character.LooksParam>();
|
||||
var jobs = reader.ReadStruct<Character.JobParam>();
|
||||
|
||||
Logger.WriteInternal("[CHR] {0} 创建了名为 {1} 的新角色.", context.User.Username, name);
|
||||
var newCharacter = new Character
|
||||
{
|
||||
unk1 = setting.unk1,
|
||||
voice_type = setting.voice_type,
|
||||
unk2 = setting.unk2,
|
||||
voice_pitch = setting.voice_pitch,
|
||||
Name = name,
|
||||
Looks = looks,
|
||||
unk3 = unk3,
|
||||
Jobs = jobs,
|
||||
Looks = looks,
|
||||
Player = context.User
|
||||
};
|
||||
|
||||
@ -55,16 +50,14 @@ namespace PSO2SERVER.Packets.Handlers
|
||||
if (existingCharacters.Count > 0)
|
||||
{
|
||||
// Increment ID if characters already exist
|
||||
newCharacter.CharacterId = existingCharacters.Max(c => c.CharacterId) + 1;
|
||||
newCharacter.CharacterID = existingCharacters.Max(c => c.CharacterID) + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Start with ID 1 if no characters exist
|
||||
newCharacter.CharacterId = 1;
|
||||
newCharacter.CharacterID = 1;
|
||||
}
|
||||
|
||||
newCharacter.player_id = context.User.PlayerId;
|
||||
|
||||
//Logger.Write("newCharacter.CharacterId {0} {1}", newCharacter.CharacterId, context.User.PlayerId);
|
||||
|
||||
db.Characters.Add(newCharacter);
|
||||
|
@ -22,7 +22,7 @@ namespace PSO2SERVER.Packets.Handlers
|
||||
{
|
||||
|
||||
foreach (var character in db.Characters)
|
||||
if (character.CharacterId == id)
|
||||
if (character.CharacterID == id)
|
||||
{
|
||||
db.Characters.Remove(character);
|
||||
db.ChangeTracker.DetectChanges();
|
||||
|
@ -22,11 +22,11 @@ namespace PSO2SERVER.Packets.Handlers
|
||||
|
||||
foreach (var client in ServerApp.Instance.Server.Clients)
|
||||
{
|
||||
if (client.Character.CharacterId == id)
|
||||
if (client.Character.CharacterID == id)
|
||||
{
|
||||
var infoPacket = new GuildInfoPacket(context.Character);
|
||||
context.SendPacket(infoPacket);
|
||||
Logger.Write("[NFO] Sent guild info to " + client.Character.CharacterId);
|
||||
Logger.Write("[NFO] Sent guild info to " + client.Character.CharacterID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -27,35 +27,60 @@ namespace PSO2SERVER.Packets.Handlers
|
||||
|
||||
public static void LoadPacketHandlers()
|
||||
{
|
||||
var handlers = (from t in Assembly.GetExecutingAssembly().GetTypes()
|
||||
where t.IsClass && t.Namespace == "PSO2SERVER.Packets.Handlers" &&
|
||||
t.IsSubclassOf(typeof(PacketHandler))
|
||||
let attrs = (PacketHandlerAttr[])t.GetCustomAttributes(typeof(PacketHandlerAttr), false)
|
||||
where attrs.Length > 0
|
||||
select new
|
||||
{
|
||||
attrs[0].Type,
|
||||
attrs[0].Subtype,
|
||||
HandlerType = t
|
||||
}).ToList();
|
||||
|
||||
// Sort handlers by Type and Subtype
|
||||
handlers = handlers.OrderBy(h => h.Type).ThenBy(h => h.Subtype).ToList();
|
||||
|
||||
foreach (var handler in handlers)
|
||||
try
|
||||
{
|
||||
Logger.WriteInternal("[数据] 数据包 0x{0:X2} - 0x{1:X2} 处理已载入 {2} ."
|
||||
, handler.Type
|
||||
, handler.Subtype
|
||||
, handler.HandlerType.Name
|
||||
var handlers = (from t in Assembly.GetExecutingAssembly().GetTypes()
|
||||
where t.IsClass && t.Namespace == "PSO2SERVER.Packets.Handlers" &&
|
||||
t.IsSubclassOf(typeof(PacketHandler))
|
||||
let attrs = (PacketHandlerAttr[])t.GetCustomAttributes(typeof(PacketHandlerAttr), false)
|
||||
where attrs.Length > 0
|
||||
select new
|
||||
{
|
||||
attrs[0].Type,
|
||||
attrs[0].Subtype,
|
||||
HandlerType = t
|
||||
}).ToList();
|
||||
|
||||
// Sort handlers by Type and Subtype
|
||||
handlers = handlers.OrderBy(h => h.Type).ThenBy(h => h.Subtype).ToList();
|
||||
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
Logger.WriteInternal("[数据] 数据包 0x{0:X2} - 0x{1:X2} 处理已载入 {2}.",
|
||||
handler.Type,
|
||||
handler.Subtype,
|
||||
handler.HandlerType.Name
|
||||
);
|
||||
|
||||
ushort packetTypeUShort = Helper.PacketTypeToUShort(handler.Type, handler.Subtype);
|
||||
if (!Handlers.ContainsKey(packetTypeUShort))
|
||||
{
|
||||
Handlers.Add(packetTypeUShort, (PacketHandler)Activator.CreateInstance(handler.HandlerType));
|
||||
ushort packetTypeUShort = Helper.PacketTypeToUShort(handler.Type, handler.Subtype);
|
||||
if (!Handlers.ContainsKey(packetTypeUShort))
|
||||
{
|
||||
// Create instance and add to dictionary
|
||||
var handlerInstance = (PacketHandler)Activator.CreateInstance(handler.HandlerType);
|
||||
Handlers.Add(packetTypeUShort, handlerInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.WriteInternal("[警告] 数据包 0x{0:X2} - 0x{1:X2} 已存在处理器 {2}.",
|
||||
handler.Type,
|
||||
handler.Subtype,
|
||||
handler.HandlerType.Name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ReflectionTypeLoadException ex)
|
||||
{
|
||||
// Log the exception details
|
||||
foreach (var loaderException in ex.LoaderExceptions)
|
||||
{
|
||||
Logger.WriteInternal("[错误] 加载类型时出现异常: {0}", loaderException.Message);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.WriteInternal("[错误] 发生异常: {0}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -38,43 +38,36 @@ namespace PSO2SERVER.Packets.PSOPackets
|
||||
var writer = new PacketWriter();
|
||||
|
||||
// Player header
|
||||
writer.WritePlayerHeader((uint) _character.Player.PlayerId);
|
||||
writer.WritePlayerHeader((uint)_character.Player.PlayerId);
|
||||
|
||||
// Spawn position
|
||||
writer.Write(Position);
|
||||
|
||||
writer.Write((ushort) 0); // padding?
|
||||
writer.Write((ushort)0); // padding?
|
||||
writer.WriteFixedLengthASCII("Character", 32);
|
||||
writer.Write((ushort) 1); // 0x44
|
||||
writer.Write((ushort) 0); // 0x46
|
||||
writer.Write((uint) 602); // 0x48
|
||||
writer.Write((uint) 1); // 0x4C
|
||||
writer.Write((uint) 53); // 0x50
|
||||
writer.Write((uint) 0); // 0x54
|
||||
|
||||
writer.Write((uint) (IsItMe ? 0x2F : 0x27)); // 0x58
|
||||
writer.Write((ushort) 0); // 0x5C
|
||||
writer.Write((ushort) 0); // 0x5E
|
||||
|
||||
writer.Write((uint)_character.CharacterId);
|
||||
writer.Write((uint)_character.player_id);
|
||||
writer.Write(_character.voice_type);
|
||||
writer.Write((uint)_character.voice_pitch);
|
||||
//writer.Write((uint)_character.CharacterId); // player ID copy
|
||||
//writer.Write((uint)0); // "char array ugggghhhhh" according to PolarisLegacy
|
||||
//writer.Write((uint)0); // "voiceParam_unknown4"
|
||||
//writer.Write((uint)0); // "voiceParam_unknown8"
|
||||
writer.Write((ushort)1); // 0x44
|
||||
writer.Write((ushort)0); // 0x46
|
||||
writer.Write((uint)602); // 0x48
|
||||
writer.Write((uint)1); // 0x4C
|
||||
writer.Write((uint)53); // 0x50
|
||||
writer.Write((uint)0); // 0x54
|
||||
writer.Write((uint)(IsItMe ? 47 : 39)); // 0x58
|
||||
writer.Write((ushort)559); // 0x5C
|
||||
writer.Write((ushort)306); // 0x5E
|
||||
writer.Write((uint)_character.Player.PlayerId); // player ID copy
|
||||
writer.Write((uint)0); // "char array ugggghhhhh" according to PolarisLegacy
|
||||
writer.Write((uint)0); // "voiceParam_unknown4"
|
||||
writer.Write((uint)0); // "voiceParam_unknown8"
|
||||
writer.WriteFixedLengthUtf16(_character.Name, 16);
|
||||
writer.Write((uint)0); // 0x90
|
||||
writer.WriteStruct(_character.Looks);
|
||||
writer.WriteStruct(_character.Jobs);
|
||||
writer.WriteFixedLengthUtf16("", 32); // title?
|
||||
|
||||
writer.Write((uint) 0); // 0x204
|
||||
writer.Write((uint) 0); // gmflag?
|
||||
writer.Write((uint)0); // 0x204
|
||||
writer.Write((uint)0); // gmflag?
|
||||
writer.WriteFixedLengthUtf16(_character.Player.Nickname, 16); // nickname, maybe not 16 chars?
|
||||
for (var i = 0; i < 64; i++)
|
||||
writer.Write((byte) 0);
|
||||
writer.Write((byte)0);
|
||||
|
||||
return writer.ToArray();
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ using PSO2SERVER.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Remoting.Contexts;
|
||||
using System.Text;
|
||||
|
||||
@ -26,43 +27,6 @@ namespace PSO2SERVER.Packets.PSOPackets
|
||||
|
||||
private int _PlayerId;
|
||||
|
||||
/// <summary>
|
||||
/// Available characters.
|
||||
/// </summary>
|
||||
public Character[] Characters { get; set; }
|
||||
|
||||
//public Item[][] EquippedItems { get; set; } = new Item[10][];
|
||||
|
||||
/// <summary>
|
||||
/// Character play times.
|
||||
/// </summary>
|
||||
public uint[] PlayTimes { get; set; } = new uint[30];
|
||||
|
||||
/// <summary>
|
||||
/// Character deletion flags (flag, deletion timestamp).
|
||||
/// </summary>
|
||||
public (uint Flag, uint Timestamp)[] DeletionFlags { get; set; } = new (uint, uint)[30];
|
||||
|
||||
/// <summary>
|
||||
/// Character ship transfer flags.
|
||||
/// </summary>
|
||||
public (uint Flag, uint Transfer)[] TransferFlags { get; set; } = new (uint, uint)[30];
|
||||
|
||||
/// <summary>
|
||||
/// Account accessory flag (?).
|
||||
/// </summary>
|
||||
public ushort AccountAccessory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Login survey flag.
|
||||
/// </summary>
|
||||
public uint LoginSurvey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ad flag (on global 12 star unit ad).
|
||||
/// </summary>
|
||||
public uint Ad { get; set; }
|
||||
|
||||
public CharacterListPacket(int PlayerId)
|
||||
{
|
||||
_PlayerId = PlayerId;
|
||||
@ -78,7 +42,7 @@ namespace PSO2SERVER.Packets.PSOPackets
|
||||
{
|
||||
var chars = db.Characters
|
||||
.Where(w => w.Player.PlayerId == _PlayerId)
|
||||
.OrderBy(o => o.CharacterId) // TODO: Order by last played
|
||||
.OrderBy(o => o.CharacterID) // TODO: Order by last played
|
||||
.Select(s => s);
|
||||
|
||||
writer.Write((uint)chars.Count()); // Number of characters
|
||||
@ -88,19 +52,20 @@ namespace PSO2SERVER.Packets.PSOPackets
|
||||
|
||||
foreach (var ch in chars)
|
||||
{
|
||||
writer.Write(ch.CharacterId);
|
||||
writer.Write(ch.player_id);
|
||||
writer.Write(ch.unk1);
|
||||
writer.Write(ch.voice_type);
|
||||
writer.Write(ch.unk2);
|
||||
writer.Write(ch.voice_pitch);
|
||||
writer.Write((uint)ch.CharacterID);
|
||||
writer.Write((uint)_PlayerId);
|
||||
|
||||
for (var i = 0; i < 0x10; i++)
|
||||
writer.Write((byte)0);
|
||||
|
||||
writer.WriteFixedLengthUtf16(ch.Name, 16);
|
||||
writer.Write((uint)0);
|
||||
writer.WriteStruct(ch.Looks);
|
||||
|
||||
writer.WriteStruct(ch.Looks); // Note: Pre-Episode 4 created looks doesn't seem to work anymore
|
||||
writer.WriteStruct(ch.Jobs);
|
||||
|
||||
for (var i = 0; i < 0x90; i++)
|
||||
writer.Write((byte)0);
|
||||
for (var i = 0; i < 0xFC; i++)
|
||||
writer.Write((byte)3);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ namespace PSO2SERVER.Packets.PSOPackets
|
||||
writer.Write((byte) 0);
|
||||
|
||||
// Character ID
|
||||
writer.Write((uint) _character.CharacterId);
|
||||
writer.Write((uint) _character.CharacterID);
|
||||
|
||||
// Padding?
|
||||
for (var i = 0; i < 4; i++)
|
||||
|
@ -162,5 +162,10 @@ namespace PSO2SERVER.Packets
|
||||
Write(b);
|
||||
}
|
||||
}
|
||||
|
||||
internal void WriteFixedLengthUtf16(int i, int v)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -172,7 +172,7 @@
|
||||
<Compile Include="Object\ObjectManager.cs" />
|
||||
<Compile Include="Packets\Handlers\03-ServerHandler\03-03-InitialLoad.cs" />
|
||||
<Compile Include="Packets\Handlers\03-ServerHandler\03-0C-PingResponse.cs" />
|
||||
<Compile Include="Packets\Handlers\03-ServerHandler\03-10-DoItMaybe.cs" />
|
||||
<Compile Include="Packets\Handlers\03-ServerHandler\03-10-MapLoaded.cs" />
|
||||
<Compile Include="Packets\Handlers\03-ServerHandler\03-16-CampshipTeleportDown.cs" />
|
||||
<Compile Include="Packets\Handlers\03-ServerHandler\03-3B-TeleportCafeToLobby.cs" />
|
||||
<Compile Include="Packets\Handlers\03-ServerHandler\03-3C-TeleportLobbyToCafe.cs" />
|
||||
|
BIN
数据对比/角色/extra.bin
Normal file
BIN
数据对比/角色/extra.bin
Normal file
Binary file not shown.
Binary file not shown.
BIN
数据对比/角色/job-2-人类-猎人.bin
Normal file
BIN
数据对比/角色/job-2-人类-猎人.bin
Normal file
Binary file not shown.
BIN
数据对比/角色/look-2-人类-猎人.bin
Normal file
BIN
数据对比/角色/look-2-人类-猎人.bin
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user