impulses & forces
This commit is contained in:
parent
4b10be1b17
commit
0ed2a403dc
@ -17,6 +17,7 @@ READONLY struct entity _g_entity_nil = {
|
|||||||
.cached_global_xform = XFORM_IDENT_NOCAST,
|
.cached_global_xform = XFORM_IDENT_NOCAST,
|
||||||
.cached_global_xform_dirty = false,
|
.cached_global_xform_dirty = false,
|
||||||
.verlet_xform = XFORM_IDENT_NOCAST,
|
.verlet_xform = XFORM_IDENT_NOCAST,
|
||||||
|
.density = 1,
|
||||||
.sprite_local_xform = XFORM_IDENT_NOCAST,
|
.sprite_local_xform = XFORM_IDENT_NOCAST,
|
||||||
.sprite_tint = COLOR_WHITE
|
.sprite_tint = COLOR_WHITE
|
||||||
};
|
};
|
||||||
@ -27,6 +28,7 @@ GLOBAL READONLY struct entity g_entity_default = {
|
|||||||
.cached_global_xform = XFORM_IDENT_NOCAST,
|
.cached_global_xform = XFORM_IDENT_NOCAST,
|
||||||
.cached_global_xform_dirty = true,
|
.cached_global_xform_dirty = true,
|
||||||
.verlet_xform = XFORM_IDENT_NOCAST,
|
.verlet_xform = XFORM_IDENT_NOCAST,
|
||||||
|
.density = 1,
|
||||||
.sprite_local_xform = XFORM_IDENT_NOCAST,
|
.sprite_local_xform = XFORM_IDENT_NOCAST,
|
||||||
.sprite_tint = COLOR_WHITE
|
.sprite_tint = COLOR_WHITE
|
||||||
};
|
};
|
||||||
|
|||||||
22
src/entity.h
22
src/entity.h
@ -9,9 +9,11 @@ enum entity_prop {
|
|||||||
|
|
||||||
ENTITY_PROP_ACTIVE,
|
ENTITY_PROP_ACTIVE,
|
||||||
|
|
||||||
ENTITY_PROP_RELEASE_NEXT_TICK,
|
ENTITY_PROP_RELEASE,
|
||||||
|
|
||||||
ENTITY_PROP_PHYSICAL,
|
ENTITY_PROP_PHYSICAL,
|
||||||
|
ENTITY_PROP_IMPULSE,
|
||||||
|
ENTITY_PROP_FORCE,
|
||||||
|
|
||||||
ENTITY_PROP_PLAYER_CONTROLLED,
|
ENTITY_PROP_PLAYER_CONTROLLED,
|
||||||
ENTITY_PROP_CAMERA,
|
ENTITY_PROP_CAMERA,
|
||||||
@ -105,6 +107,8 @@ struct entity {
|
|||||||
|
|
||||||
/* ENTITY_PROP_PHYSICAL */
|
/* ENTITY_PROP_PHYSICAL */
|
||||||
|
|
||||||
|
f32 density;
|
||||||
|
|
||||||
/* Xform of the entity from the previous frame (used to calculate velocity) */
|
/* Xform of the entity from the previous frame (used to calculate velocity) */
|
||||||
struct xform verlet_xform;
|
struct xform verlet_xform;
|
||||||
|
|
||||||
@ -112,6 +116,22 @@ struct entity {
|
|||||||
struct v2 final_velocity;
|
struct v2 final_velocity;
|
||||||
struct v2 final_acceleration;
|
struct v2 final_acceleration;
|
||||||
|
|
||||||
|
/* ====================================================================== */
|
||||||
|
/* Impulse */
|
||||||
|
|
||||||
|
/* ENTITY_PROP_IMPULSE */
|
||||||
|
|
||||||
|
/* Applies impulse velocity to parent entity */
|
||||||
|
struct v2 impulse;
|
||||||
|
|
||||||
|
/* ====================================================================== */
|
||||||
|
/* Impulse */
|
||||||
|
|
||||||
|
/* ENTITY_PROP_FORCE */
|
||||||
|
|
||||||
|
/* Applies force to parent entity */
|
||||||
|
struct v2 force;
|
||||||
|
|
||||||
/* ====================================================================== */
|
/* ====================================================================== */
|
||||||
/* Sprite */
|
/* Sprite */
|
||||||
|
|
||||||
|
|||||||
258
src/game.c
258
src/game.c
@ -170,6 +170,18 @@ INTERNAL void spawn_test_entities(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========================== *
|
||||||
|
* Activate
|
||||||
|
* ========================== */
|
||||||
|
|
||||||
|
INTERNAL void activate_now(struct entity *ent)
|
||||||
|
{
|
||||||
|
entity_enable_prop(ent, ENTITY_PROP_ACTIVE);
|
||||||
|
ent->activation_tick = G.tick.tick_id;
|
||||||
|
ent->verlet_xform = entity_get_xform(ent);
|
||||||
|
++ent->continuity_gen;
|
||||||
|
}
|
||||||
|
|
||||||
/* ========================== *
|
/* ========================== *
|
||||||
* Update
|
* Update
|
||||||
* ========================== */
|
* ========================== */
|
||||||
@ -244,35 +256,6 @@ INTERNAL void game_update(struct game_cmd_array game_cmds)
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========================== *
|
|
||||||
* Release entities
|
|
||||||
* ========================== */
|
|
||||||
|
|
||||||
/* TODO: Breadth first iteration to only release parent entities (since
|
|
||||||
* child entities will be released along with parent anyway) */
|
|
||||||
|
|
||||||
{
|
|
||||||
struct temp_arena temp = arena_temp_begin(scratch.arena);
|
|
||||||
|
|
||||||
struct entity **ents_to_release = arena_dry_push(temp.arena, struct entity *);
|
|
||||||
u64 ents_to_release_count = 0;
|
|
||||||
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
|
|
||||||
struct entity *ent = &store->entities[entity_index];
|
|
||||||
if (!ent->valid) continue;
|
|
||||||
|
|
||||||
if (entity_has_prop(ent, ENTITY_PROP_RELEASE_NEXT_TICK)) {
|
|
||||||
*arena_push(temp.arena, struct entity *) = ent;
|
|
||||||
++ents_to_release_count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (u64 i = 0; i < ents_to_release_count; ++i) {
|
|
||||||
entity_release(store, ents_to_release[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
arena_temp_end(temp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ========================== *
|
/* ========================== *
|
||||||
* Activate entities
|
* Activate entities
|
||||||
* ========================== */
|
* ========================== */
|
||||||
@ -284,10 +267,7 @@ INTERNAL void game_update(struct game_cmd_array game_cmds)
|
|||||||
if (!entity_has_prop(ent, ENTITY_PROP_ACTIVE)) {
|
if (!entity_has_prop(ent, ENTITY_PROP_ACTIVE)) {
|
||||||
u64 atick = ent->activation_tick;
|
u64 atick = ent->activation_tick;
|
||||||
if (atick != 0 || G.tick.tick_id >= atick) {
|
if (atick != 0 || G.tick.tick_id >= atick) {
|
||||||
entity_enable_prop(ent, ENTITY_PROP_ACTIVE);
|
activate_now(ent);
|
||||||
ent->activation_tick = G.tick.tick_id;
|
|
||||||
ent->verlet_xform = entity_get_xform(ent);
|
|
||||||
++ent->continuity_gen;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -539,6 +519,107 @@ INTERNAL void game_update(struct game_cmd_array game_cmds)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========================== *
|
||||||
|
* Create forces from control
|
||||||
|
* ========================== */
|
||||||
|
|
||||||
|
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
|
||||||
|
struct entity *ent = &store->entities[entity_index];
|
||||||
|
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
|
||||||
|
|
||||||
|
struct v2 move = ent->control.move;
|
||||||
|
if (!v2_eq(move, V2(0, 0))) {
|
||||||
|
struct xform xf = entity_get_xform(ent);
|
||||||
|
|
||||||
|
struct v2 tick_velocity = v2_sub(xf.og, ent->verlet_xform.og);
|
||||||
|
|
||||||
|
/* Player movement */
|
||||||
|
if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) {
|
||||||
|
f32 max_speed = ent->player_max_speed;
|
||||||
|
f32 acceleration_rate = ent->player_acceleration;
|
||||||
|
acceleration_rate = clamp_f32(acceleration_rate, 0, GAME_FPS); /* Can't integrate acceleration rate higher than FPS */
|
||||||
|
struct v2 target_velocity = v2_mul(ent->control.move, max_speed);
|
||||||
|
struct v2 target_acceleration = v2_sub(target_velocity, v2_div(tick_velocity, dt));
|
||||||
|
struct v2 acceleration = v2_mul(target_acceleration, acceleration_rate);
|
||||||
|
|
||||||
|
/* Create force */
|
||||||
|
struct entity *f = entity_alloc(ent);
|
||||||
|
entity_enable_prop(f, ENTITY_PROP_FORCE);
|
||||||
|
f->force = acceleration;
|
||||||
|
activate_now(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================== *
|
||||||
|
* Create constant forces
|
||||||
|
* ========================== */
|
||||||
|
|
||||||
|
/* TODO: Just do this in simulation processing */
|
||||||
|
|
||||||
|
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
|
||||||
|
struct entity *ent = &store->entities[entity_index];
|
||||||
|
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
|
||||||
|
if (!entity_has_prop(ent, ENTITY_PROP_PHYSICAL)) continue;
|
||||||
|
|
||||||
|
struct xform xf = entity_get_xform(ent);
|
||||||
|
struct v2 velocity = v2_div(v2_sub(xf.og, ent->verlet_xform.og), dt);
|
||||||
|
|
||||||
|
/* Ground friction */
|
||||||
|
{
|
||||||
|
struct entity *f = entity_alloc(ent);
|
||||||
|
|
||||||
|
f32 force_len = -v2_len(velocity) * 8;
|
||||||
|
|
||||||
|
entity_enable_prop(f, ENTITY_PROP_FORCE);
|
||||||
|
f->force = v2_mul(v2_norm(velocity), force_len);
|
||||||
|
|
||||||
|
activate_now(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================== *
|
||||||
|
* Apply impulses to verlet xform
|
||||||
|
* ========================== */
|
||||||
|
|
||||||
|
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
|
||||||
|
struct entity *ent = &store->entities[entity_index];
|
||||||
|
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
|
||||||
|
|
||||||
|
/* FIXME: Why does marking all ents with ENTITY_PROP_RELEASE here raise exception */
|
||||||
|
|
||||||
|
if (entity_has_prop(ent, ENTITY_PROP_IMPULSE)) {
|
||||||
|
struct entity *parent = entity_from_handle(store, ent->parent);
|
||||||
|
if (entity_has_prop(parent, ENTITY_PROP_ACTIVE)) {
|
||||||
|
struct v2 tick_velocity = v2_mul(ent->impulse, dt);
|
||||||
|
parent->verlet_xform.og = v2_sub(parent->verlet_xform.og, tick_velocity);
|
||||||
|
entity_enable_prop(ent, ENTITY_PROP_RELEASE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================== *
|
||||||
|
* Apply forces to verlet xform
|
||||||
|
* ========================== */
|
||||||
|
|
||||||
|
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
|
||||||
|
struct entity *ent = &store->entities[entity_index];
|
||||||
|
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
|
||||||
|
|
||||||
|
f32 density = ent->density;
|
||||||
|
f32 mass = density; /* TODO: Real mass calculation */
|
||||||
|
|
||||||
|
if (entity_has_prop(ent, ENTITY_PROP_FORCE)) {
|
||||||
|
struct entity *parent = entity_from_handle(store, ent->parent);
|
||||||
|
if (entity_has_prop(parent, ENTITY_PROP_ACTIVE)) {
|
||||||
|
struct v2 acceleration = v2_div(ent->force, mass);
|
||||||
|
struct v2 tick_acceleration = v2_mul(acceleration, dt * dt);
|
||||||
|
parent->verlet_xform.og = v2_sub(parent->verlet_xform.og, tick_acceleration);
|
||||||
|
entity_enable_prop(ent, ENTITY_PROP_RELEASE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ========================== *
|
/* ========================== *
|
||||||
* Simulate entity physics
|
* Simulate entity physics
|
||||||
* ========================== */
|
* ========================== */
|
||||||
@ -547,21 +628,19 @@ INTERNAL void game_update(struct game_cmd_array game_cmds)
|
|||||||
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
|
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
|
||||||
struct entity *ent = &store->entities[entity_index];
|
struct entity *ent = &store->entities[entity_index];
|
||||||
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
|
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
|
||||||
if (!entity_has_prop(ent, entity_has_prop(ent, ENTITY_PROP_PHYSICAL))) continue;
|
if (!entity_has_prop(ent, ENTITY_PROP_PHYSICAL)) continue;
|
||||||
|
|
||||||
|
|
||||||
struct xform xf = entity_get_xform(ent);
|
struct xform xf = entity_get_xform(ent);
|
||||||
|
|
||||||
/* Calculate velocity from old position */
|
/* Calculate velocity from old position */
|
||||||
|
struct v2 tick_velocity = V2(0, 0);
|
||||||
struct v2 velocity = V2(0, 0);
|
|
||||||
{
|
{
|
||||||
velocity = v2_sub(xf.og, ent->verlet_xform.og);
|
tick_velocity = v2_sub(xf.og, ent->verlet_xform.og);
|
||||||
velocity = v2_div(velocity, dt);
|
|
||||||
ent->verlet_xform = xf;
|
ent->verlet_xform = xf;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Player angle */
|
/* Player angle */
|
||||||
|
|
||||||
if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) {
|
if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) {
|
||||||
/* Solve for final angle using law of sines */
|
/* Solve for final angle using law of sines */
|
||||||
f32 final_xf_angle;
|
f32 final_xf_angle;
|
||||||
@ -616,27 +695,11 @@ INTERNAL void game_update(struct game_cmd_array game_cmds)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: Accumulate forces to calculate acceleration */
|
/* Apply velocity */
|
||||||
struct v2 acceleration = V2(0, 0);
|
xf.og = v2_add(xf.og, tick_velocity);
|
||||||
{
|
|
||||||
/* Player movement */
|
|
||||||
if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) {
|
|
||||||
f32 max_speed = ent->player_max_speed;
|
|
||||||
f32 acceleration_rate = ent->player_acceleration;
|
|
||||||
acceleration_rate = clamp_f32(acceleration_rate, 0, GAME_FPS); /* Can't integrate acceleration rate higher than FPS */
|
|
||||||
struct v2 target_velocity = v2_mul(ent->control.move, max_speed);
|
|
||||||
struct v2 target_acceleration = v2_sub(target_velocity, velocity);
|
|
||||||
acceleration = v2_mul(target_acceleration, acceleration_rate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Integrate acceleration & velocity */
|
ent->final_velocity = v2_div(tick_velocity, dt);
|
||||||
struct v2 acceleration_tick = v2_mul(acceleration, dt * dt);
|
ent->final_acceleration = v2_div(v2_sub(v2_sub(xf.og, ent->verlet_xform.og), tick_velocity), dt);
|
||||||
struct v2 velocity_tick = v2_add(v2_mul(velocity, dt), acceleration_tick);
|
|
||||||
xf.og = v2_add(xf.og, velocity_tick);
|
|
||||||
|
|
||||||
ent->final_velocity = velocity;
|
|
||||||
ent->final_acceleration = acceleration;
|
|
||||||
entity_set_xform(ent, xf);
|
entity_set_xform(ent, xf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -644,6 +707,31 @@ INTERNAL void game_update(struct game_cmd_array game_cmds)
|
|||||||
* Initialize bullet kinematics from sources
|
* Initialize bullet kinematics from sources
|
||||||
* ========================== */
|
* ========================== */
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
|
||||||
|
struct entity *ent = &store->entities[entity_index];
|
||||||
|
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
|
||||||
|
|
||||||
|
/* FIXME: Apply src entity velocity to bullet velocity */
|
||||||
|
/* TODO: Use impulse */
|
||||||
|
|
||||||
|
if (entity_has_prop(ent, ENTITY_PROP_BULLET) && !entity_has_prop(ent, ENTITY_PROP_PHYSICAL)) {
|
||||||
|
struct entity *src = entity_from_handle(store, ent->bullet_src);
|
||||||
|
struct xform src_xf = entity_get_xform(src);
|
||||||
|
|
||||||
|
struct v2 pos = xform_mul_v2(src_xf, ent->bullet_src_pos);
|
||||||
|
struct v2 vec = xform_basis_mul_v2(src_xf, ent->bullet_src_dir);
|
||||||
|
vec = v2_mul(v2_norm(vec), ent->bullet_impulse);
|
||||||
|
|
||||||
|
struct xform xf = XFORM_TRS(.t = pos, .r = v2_angle(vec) + PI / 2);
|
||||||
|
entity_set_xform(ent, xf);
|
||||||
|
|
||||||
|
ent->verlet_xform = XFORM_POS(v2_sub(pos, v2_mul(vec, dt)));
|
||||||
|
|
||||||
|
entity_enable_prop(ent, ENTITY_PROP_PHYSICAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
|
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
|
||||||
struct entity *ent = &store->entities[entity_index];
|
struct entity *ent = &store->entities[entity_index];
|
||||||
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
|
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
|
||||||
@ -655,25 +743,22 @@ INTERNAL void game_update(struct game_cmd_array game_cmds)
|
|||||||
struct xform src_xf = entity_get_xform(src);
|
struct xform src_xf = entity_get_xform(src);
|
||||||
|
|
||||||
struct v2 pos = xform_mul_v2(src_xf, ent->bullet_src_pos);
|
struct v2 pos = xform_mul_v2(src_xf, ent->bullet_src_pos);
|
||||||
struct v2 vec = xform_basis_mul_v2(src_xf, ent->bullet_src_dir);
|
struct v2 velocity = xform_basis_mul_v2(src_xf, ent->bullet_src_dir);
|
||||||
vec = v2_mul(v2_norm(vec), ent->bullet_impulse);
|
velocity = v2_mul(v2_norm(velocity), ent->bullet_impulse);
|
||||||
|
|
||||||
#if 0
|
struct xform xf = XFORM_TRS(.t = pos, .r = v2_angle(velocity) + PI / 2);
|
||||||
{
|
|
||||||
struct v2 src_velocity = v2_sub(src_xf.og, src->verlet_xform.og);
|
|
||||||
src_velocity = v2_div(src_velocity, dt);
|
|
||||||
vec = v2_add(vec, src_velocity);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct xform xf = XFORM_TRS(.t = pos, .r = v2_angle(vec) + PI / 2);
|
|
||||||
entity_set_xform(ent, xf);
|
entity_set_xform(ent, xf);
|
||||||
|
ent->verlet_xform = xf;
|
||||||
ent->verlet_xform = XFORM_POS(v2_sub(pos, v2_mul(vec, dt)));
|
|
||||||
|
|
||||||
entity_enable_prop(ent, ENTITY_PROP_PHYSICAL);
|
entity_enable_prop(ent, ENTITY_PROP_PHYSICAL);
|
||||||
|
|
||||||
|
/* Create impulse */
|
||||||
|
struct entity *impulse = entity_alloc(ent);
|
||||||
|
impulse->impulse = velocity;
|
||||||
|
entity_enable_prop(impulse, ENTITY_PROP_IMPULSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* ========================== *
|
/* ========================== *
|
||||||
* Update camera position
|
* Update camera position
|
||||||
@ -749,6 +834,35 @@ INTERNAL void game_update(struct game_cmd_array game_cmds)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========================== *
|
||||||
|
* Release entities
|
||||||
|
* ========================== */
|
||||||
|
|
||||||
|
/* TODO: Breadth first iteration to only release parent entities (since
|
||||||
|
* child entities will be released along with parent anyway) */
|
||||||
|
|
||||||
|
{
|
||||||
|
struct temp_arena temp = arena_temp_begin(scratch.arena);
|
||||||
|
|
||||||
|
struct entity **ents_to_release = arena_dry_push(temp.arena, struct entity *);
|
||||||
|
u64 ents_to_release_count = 0;
|
||||||
|
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
|
||||||
|
struct entity *ent = &store->entities[entity_index];
|
||||||
|
if (!ent->valid) continue;
|
||||||
|
|
||||||
|
if (entity_has_prop(ent, ENTITY_PROP_RELEASE)) {
|
||||||
|
*arena_push(temp.arena, struct entity *) = ent;
|
||||||
|
++ents_to_release_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (u64 i = 0; i < ents_to_release_count; ++i) {
|
||||||
|
entity_release(store, ents_to_release[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
arena_temp_end(temp);
|
||||||
|
}
|
||||||
|
|
||||||
/* ========================== *
|
/* ========================== *
|
||||||
* Publish tick
|
* Publish tick
|
||||||
* ========================== */
|
* ========================== */
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user