implement left jump/left drop tracing in trace-jump.sml, and verified that it works
This commit is contained in:
@@ -249,9 +249,8 @@ struct
|
|||||||
* So, if we check for jump first, we would always jump before dropping
|
* So, if we check for jump first, we would always jump before dropping
|
||||||
* even if jumping is not necessary. *)
|
* even if jumping is not necessary. *)
|
||||||
if TraceJump.traceRightDrop (enemy, #id nextPlatform, platformTree) then
|
if TraceJump.traceRightDrop (enemy, #id nextPlatform, platformTree) then
|
||||||
EnemyPatch.W_Y_AXIS DROP_BELOW_PLATFORM ::
|
EnemyPatch.W_Y_AXIS DROP_BELOW_PLATFORM :: EnemyPatch.W_X_AXIS MOVE_RIGHT
|
||||||
EnemyPatch.W_X_AXIS MOVE_RIGHT ::
|
:: acc
|
||||||
acc
|
|
||||||
else if TraceJump.traceRightJump (enemy, #id nextPlatform, platformTree) then
|
else if TraceJump.traceRightJump (enemy, #id nextPlatform, platformTree) then
|
||||||
if standingOnArea (enemy, platformTree) then
|
if standingOnArea (enemy, platformTree) then
|
||||||
EnemyPatch.W_Y_AXIS (JUMPING 0) :: EnemyPatch.W_X_AXIS MOVE_RIGHT :: acc
|
EnemyPatch.W_Y_AXIS (JUMPING 0) :: EnemyPatch.W_X_AXIS MOVE_RIGHT :: acc
|
||||||
@@ -261,79 +260,16 @@ struct
|
|||||||
EnemyPatch.W_X_AXIS MOVE_RIGHT :: acc
|
EnemyPatch.W_X_AXIS MOVE_RIGHT :: acc
|
||||||
|
|
||||||
fun getMoveLeftPatches (nextPlatform, enemy, platformTree, acc) =
|
fun getMoveLeftPatches (nextPlatform, enemy, platformTree, acc) =
|
||||||
let
|
if TraceJump.traceLeftDrop (enemy, #id nextPlatform, platformTree) then
|
||||||
val {x = platX, y = platY, width = platWidth, ...} = nextPlatform
|
EnemyPatch.W_Y_AXIS DROP_BELOW_PLATFORM :: EnemyPatch.W_X_AXIS MOVE_LEFT
|
||||||
val platFinishX = platX + platWidth
|
:: acc
|
||||||
|
else if TraceJump.traceLeftJump (enemy, #id nextPlatform, platformTree) then
|
||||||
val {x = ex, y = ey, yAxis = eyAxis, ...} = enemy
|
if standingOnArea (enemy, platformTree) then
|
||||||
|
EnemyPatch.W_Y_AXIS (JUMPING 0) :: EnemyPatch.W_X_AXIS MOVE_LEFT :: acc
|
||||||
val xDiff = ex - platX
|
|
||||||
in
|
|
||||||
if ey > platY then
|
|
||||||
(* enemy is lower than next platform so needs to jump *)
|
|
||||||
let
|
|
||||||
val jumpAmt =
|
|
||||||
case eyAxis of
|
|
||||||
JUMPING amt => amt
|
|
||||||
| _ => 0
|
|
||||||
val apexY = ey - (Constants.jumpLimit - jumpAmt)
|
|
||||||
|
|
||||||
val yDiff = platY - apexY
|
|
||||||
in
|
|
||||||
if yDiff <= xDiff then
|
|
||||||
(* enemy can reach platform by jumping *)
|
|
||||||
let
|
|
||||||
val acc =
|
|
||||||
if standingOnArea (enemy, platformTree) then
|
|
||||||
EnemyPatch.W_Y_AXIS (JUMPING 0) :: acc
|
|
||||||
else
|
|
||||||
acc
|
|
||||||
in
|
|
||||||
EnemyPatch.W_X_AXIS MOVE_LEFT :: acc
|
|
||||||
end
|
|
||||||
else
|
|
||||||
EnemyPatch.W_X_AXIS MOVE_LEFT :: acc
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
(* platform is below or at same y coordinat as enemy
|
EnemyPatch.W_X_AXIS MOVE_LEFT :: acc
|
||||||
* so might possibly require dropping below rather than jumping. *)
|
else
|
||||||
let
|
EnemyPatch.W_X_AXIS MOVE_LEFT :: acc
|
||||||
(* check if we can get to next platform without jumping.
|
|
||||||
* If we can, then simply move rightwards
|
|
||||||
* and possibly drop below platform.
|
|
||||||
* Else, jump and move rightwards *)
|
|
||||||
val yDiff = platY - ey
|
|
||||||
in
|
|
||||||
if yDiff >= xDiff then
|
|
||||||
(* can reach next platform by simply dropping and moving left *)
|
|
||||||
EnemyPatch.W_Y_AXIS DROP_BELOW_PLATFORM
|
|
||||||
:: EnemyPatch.W_X_AXIS MOVE_LEFT :: acc
|
|
||||||
else
|
|
||||||
let
|
|
||||||
val jumpAmt =
|
|
||||||
case eyAxis of
|
|
||||||
JUMPING amt => amt
|
|
||||||
| _ => 0
|
|
||||||
val apexY = ey - (Constants.jumpLimit - jumpAmt)
|
|
||||||
val yDiff = platY - apexY
|
|
||||||
in
|
|
||||||
if yDiff <= xDiff then
|
|
||||||
(* can reach if we jump and move left *)
|
|
||||||
let
|
|
||||||
val acc =
|
|
||||||
if standingOnArea (enemy, platformTree) then
|
|
||||||
EnemyPatch.W_Y_AXIS (JUMPING 0) :: acc
|
|
||||||
else
|
|
||||||
acc
|
|
||||||
in
|
|
||||||
EnemyPatch.W_X_AXIS MOVE_LEFT :: acc
|
|
||||||
end
|
|
||||||
else
|
|
||||||
(* cannot reach yet so move left until we can *)
|
|
||||||
EnemyPatch.W_X_AXIS MOVE_LEFT :: acc
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
(* get patches to help enemy move to nextPlatformID *)
|
(* get patches to help enemy move to nextPlatformID *)
|
||||||
fun getPathToNextPlatform
|
fun getPathToNextPlatform
|
||||||
@@ -368,27 +304,26 @@ struct
|
|||||||
* then move enemy left/right to make them fully overlap with platform *)
|
* then move enemy left/right to make them fully overlap with platform *)
|
||||||
fun getHorizontalLandingPatches (enemy, nextPlatform, acc) =
|
fun getHorizontalLandingPatches (enemy, nextPlatform, acc) =
|
||||||
case #xAxis enemy of
|
case #xAxis enemy of
|
||||||
STAY_STILL =>
|
STAY_STILL => acc
|
||||||
acc
|
|
||||||
| _ =>
|
| _ =>
|
||||||
let
|
let
|
||||||
val {x = px, width = pw, ...} = nextPlatform
|
val {x = px, width = pw, ...} = nextPlatform
|
||||||
val pfx = px + pw
|
val pfx = px + pw
|
||||||
|
|
||||||
val {x = ex, ...} = enemy
|
val {x = ex, ...} = enemy
|
||||||
val efx = ex + Constants.enemySize
|
val efx = ex + Constants.enemySize
|
||||||
in
|
in
|
||||||
if isBetween (px, ex, pfx) andalso isBetween (px, efx, pfx) then
|
if isBetween (px, ex, pfx) andalso isBetween (px, efx, pfx) then
|
||||||
acc
|
acc
|
||||||
else
|
else
|
||||||
let
|
let
|
||||||
val startDiff = abs (px - ex)
|
val startDiff = abs (px - ex)
|
||||||
val endDiff = abs (pfx - efx)
|
val endDiff = abs (pfx - efx)
|
||||||
in
|
in
|
||||||
if startDiff > endDiff then EnemyPatch.W_X_AXIS MOVE_LEFT :: acc
|
if startDiff > endDiff then EnemyPatch.W_X_AXIS MOVE_LEFT :: acc
|
||||||
else EnemyPatch.W_X_AXIS MOVE_RIGHT :: acc
|
else EnemyPatch.W_X_AXIS MOVE_RIGHT :: acc
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
fun getFallingPatches (enemy, newPlatformID, platforms, acc) =
|
fun getFallingPatches (enemy, newPlatformID, platforms, acc) =
|
||||||
let
|
let
|
||||||
@@ -426,16 +361,13 @@ struct
|
|||||||
case #xAxis enemy of
|
case #xAxis enemy of
|
||||||
STAY_STILL =>
|
STAY_STILL =>
|
||||||
let
|
let
|
||||||
val acc =
|
val acc =
|
||||||
if #x player <= #x enemy then
|
if #x player <= #x enemy then EnemyPatch.W_X_AXIS MOVE_RIGHT :: acc
|
||||||
EnemyPatch.W_X_AXIS MOVE_RIGHT :: acc
|
else EnemyPatch.W_X_AXIS MOVE_LEFT :: acc
|
||||||
else
|
|
||||||
EnemyPatch.W_X_AXIS MOVE_LEFT :: acc
|
|
||||||
in
|
in
|
||||||
acc
|
acc
|
||||||
end
|
end
|
||||||
| _ =>
|
| _ => getPatrollPatches (enemy, wallTree, platformTree, acc)
|
||||||
getPatrollPatches (enemy, wallTree, platformTree, acc)
|
|
||||||
|
|
||||||
fun getFollowPatches
|
fun getFollowPatches
|
||||||
(player: player, enemy, wallTree, platformTree, platforms, acc) =
|
(player: player, enemy, wallTree, platformTree, platforms, acc) =
|
||||||
@@ -466,8 +398,7 @@ struct
|
|||||||
nextPlatformID :: _ =>
|
nextPlatformID :: _ =>
|
||||||
let
|
let
|
||||||
val acc = EnemyPatch.W_NEXT_PLAT_ID nextPlatformID :: acc
|
val acc = EnemyPatch.W_NEXT_PLAT_ID nextPlatformID :: acc
|
||||||
val acc =
|
val acc = getPathToNextPlatform
|
||||||
getPathToNextPlatform
|
|
||||||
( nextPlatformID
|
( nextPlatformID
|
||||||
, platforms
|
, platforms
|
||||||
, platformTree
|
, platformTree
|
||||||
@@ -479,7 +410,8 @@ struct
|
|||||||
in
|
in
|
||||||
EnemyPatch.W_X_AXIS STAY_STILL :: acc
|
EnemyPatch.W_X_AXIS STAY_STILL :: acc
|
||||||
end
|
end
|
||||||
| [] => startPatrolPatches (player, enemy, wallTree, platformTree, acc)
|
| [] =>
|
||||||
|
startPatrolPatches (player, enemy, wallTree, platformTree, acc)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -168,13 +168,13 @@ struct
|
|||||||
val walls = Vector.fromList [wall1, wall2, wall3]
|
val walls = Vector.fromList [wall1, wall2, wall3]
|
||||||
val wallTree = Wall.generateTree walls
|
val wallTree = Wall.generateTree walls
|
||||||
|
|
||||||
val plat1 = {id = 1, x = 155, y = 911, width = 199}
|
val plat1 = {id = 1, x = 111, y = 711, width = 199}
|
||||||
val plat2 = {id = 2, x = 355, y = 759, width = 555}
|
val plat2 = {id = 2, x = 355, y = 759, width = 555}
|
||||||
val plat3 = {id = 3, x = 355, y = 659, width = 111}
|
val plat3 = {id = 3, x = 955, y = 659, width = 111}
|
||||||
val plat4 = {id = 4, x = 155, y = 855, width = 99}
|
val plat4 = {id = 4, x = 455, y = 855, width = 99}
|
||||||
val plat5 = {id = 5, x = 155, y = 811, width = 199}
|
val plat5 = {id = 5, x = 555, y = 811, width = 199}
|
||||||
val plat6 = {id = 6, x = 155, y = 710, width = 199}
|
val plat6 = {id = 6, x = 655, y = 710, width = 199}
|
||||||
val plat7 = {id = 7, x = 301, y = 855, width = 99}
|
val plat7 = {id = 7, x = 701, y = 855, width = 99}
|
||||||
val plat8 = {id = 8, x = 970, y = 815, width = 303}
|
val plat8 = {id = 8, x = 970, y = 815, width = 303}
|
||||||
val plat9 = {id = 9, x = 959, y = 705, width = 303}
|
val plat9 = {id = 9, x = 959, y = 705, width = 303}
|
||||||
val plat10 = {id = 10, x = 970, y = 759, width = 303}
|
val plat10 = {id = 10, x = 970, y = 759, width = 303}
|
||||||
@@ -184,9 +184,9 @@ struct
|
|||||||
val plat14 = {id = 14, x = 1000, y = 415, width = 303}
|
val plat14 = {id = 14, x = 1000, y = 415, width = 303}
|
||||||
val plat15 = {id = 15, x = 1000, y = 335, width = 303}
|
val plat15 = {id = 15, x = 1000, y = 335, width = 303}
|
||||||
val plat16 = {id = 16, x = 1000, y = 295, width = 303}
|
val plat16 = {id = 16, x = 1000, y = 295, width = 303}
|
||||||
val plat17 = {id = 17, x = 155, y = 599, width = 199}
|
val plat17 = {id = 17, x = 855, y = 599, width = 199}
|
||||||
val plat18 = {id = 18, x = 155, y = 499, width = 199}
|
val plat18 = {id = 18, x = 755, y = 499, width = 199}
|
||||||
val plat19 = {id = 19, x = 155, y = 399, width = 199}
|
val plat19 = {id = 19, x = 655, y = 399, width = 199}
|
||||||
val platforms = Vector.fromList
|
val platforms = Vector.fromList
|
||||||
[ plat1
|
[ plat1
|
||||||
, plat2
|
, plat2
|
||||||
@@ -212,8 +212,8 @@ struct
|
|||||||
|
|
||||||
val enemy1 =
|
val enemy1 =
|
||||||
{ id = 1
|
{ id = 1
|
||||||
, x = 251
|
, x = 751
|
||||||
, y = 855
|
, y = 555
|
||||||
, health = 1
|
, health = 1
|
||||||
, xAxis = STAY_STILL
|
, xAxis = STAY_STILL
|
||||||
, yAxis = FALLING
|
, yAxis = FALLING
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
structure TraceJump =
|
structure TraceJump =
|
||||||
struct
|
struct
|
||||||
structure Trace = MakeQuadTreeFold (struct
|
structure Trace =
|
||||||
type env = int
|
MakeQuadTreeFold
|
||||||
type state = bool
|
(struct
|
||||||
|
type env = int
|
||||||
|
type state = bool
|
||||||
|
|
||||||
fun fold (foldPlatID, nextPlatID, hasFoundNextPlatID) =
|
fun fold (foldPlatID, nextPlatID, hasFoundNextPlatID) =
|
||||||
hasFoundNextPlatID orelse foldPlatID = nextPlatID
|
hasFoundNextPlatID orelse foldPlatID = nextPlatID
|
||||||
end)
|
end)
|
||||||
|
|
||||||
fun traceRightJumpDescent (x, y, nextPlatID, platTree) =
|
fun traceRightDescent (x, y, nextPlatID, platTree) =
|
||||||
if x >= Constants.worldWidth orelse y >= Constants.worldHeight then
|
if x >= Constants.worldWidth orelse y >= Constants.worldHeight then
|
||||||
(* we hit bounds of screen and saw that there was
|
(* we hit bounds of screen and saw that there was
|
||||||
* no way to jump to next nextPlatID *)
|
* no way to jump to next nextPlatID *)
|
||||||
@@ -17,62 +19,120 @@ struct
|
|||||||
let
|
let
|
||||||
val width = Constants.moveEnemyBy
|
val width = Constants.moveEnemyBy
|
||||||
val height = Constants.worldHeight - y
|
val height = Constants.worldHeight - y
|
||||||
val shouldJumpRight =
|
val shouldJumpRight = Trace.foldRegion
|
||||||
Trace.foldRegion (x, y, width, height, nextPlatID, false, platTree)
|
(x, y, width, height, nextPlatID, false, platTree)
|
||||||
|
|
||||||
val nextX = x + Constants.moveEnemyBy
|
val nextX = x + Constants.moveEnemyBy
|
||||||
val nextY = y + Constants.moveEnemyBy
|
val nextY = y + Constants.moveEnemyBy
|
||||||
in
|
in
|
||||||
shouldJumpRight orelse
|
shouldJumpRight
|
||||||
traceRightJumpDescent (nextX, nextY, nextPlatID, platTree)
|
orelse traceRightDescent (nextX, nextY, nextPlatID, platTree)
|
||||||
end
|
end
|
||||||
|
|
||||||
fun traceRightDrop (enemy, nextPlatID, platTree) =
|
fun traceRightDrop (enemy: GameType.enemy, nextPlatID, platTree) =
|
||||||
let
|
let
|
||||||
open GameType
|
val {x, y, ...} = enemy
|
||||||
val {x, y, ...}: enemy = enemy
|
|
||||||
val x = x - Constants.enemySize
|
val x = x - Constants.enemySize
|
||||||
in
|
in
|
||||||
traceRightJumpDescent (x, y, nextPlatID, platTree)
|
traceRightDescent (x, y, nextPlatID, platTree)
|
||||||
end
|
end
|
||||||
|
|
||||||
fun traceRightJumpAscent (x, y, remainingJump, nextPlatID, platTree) =
|
fun traceRightJumpAscent (x, y, remainingJump, nextPlatID, platTree) =
|
||||||
if remainingJump >= Constants.jumpLimit then
|
if remainingJump >= Constants.jumpLimit then
|
||||||
traceRightJumpDescent (x, y, nextPlatID, platTree)
|
traceRightDescent (x, y, nextPlatID, platTree)
|
||||||
else
|
else
|
||||||
let
|
let
|
||||||
val width = Constants.moveEnemyBy
|
val width = Constants.moveEnemyBy
|
||||||
val height = Constants.worldHeight - y
|
val height = Constants.worldHeight - y
|
||||||
val shouldJumpRight =
|
val shouldJumpRight = Trace.foldRegion
|
||||||
Trace.foldRegion (x, y, width, height, nextPlatID, false, platTree)
|
(x, y, width, height, nextPlatID, false, platTree)
|
||||||
|
|
||||||
val nextX = x + Constants.moveEnemyBy
|
val nextX = x + Constants.moveEnemyBy
|
||||||
val nextY = y - Constants.moveEnemyBy
|
val nextY = y - Constants.moveEnemyBy
|
||||||
val nextJump = remainingJump + Constants.moveEnemyBy
|
val nextJump = remainingJump + Constants.moveEnemyBy
|
||||||
in
|
in
|
||||||
shouldJumpRight orelse
|
shouldJumpRight
|
||||||
|
orelse
|
||||||
traceRightJumpAscent (nextX, nextY, nextJump, nextPlatID, platTree)
|
traceRightJumpAscent (nextX, nextY, nextJump, nextPlatID, platTree)
|
||||||
end
|
end
|
||||||
|
|
||||||
fun traceRightJump (enemy, nextPlatID, platTree) =
|
fun traceRightJump (enemy: GameType.enemy, nextPlatID, platTree) =
|
||||||
let
|
let
|
||||||
open GameType
|
val {x, y, ...} = enemy
|
||||||
val {x, y, ...}: enemy = enemy
|
|
||||||
val x = x - Constants.enemySize
|
val x = x - Constants.enemySize
|
||||||
|
|
||||||
|
open GameType
|
||||||
in
|
in
|
||||||
if EnemyPhysics.standingOnArea (x, y, platTree) then
|
if EnemyPhysics.standingOnArea (x, y, platTree) then
|
||||||
traceRightJumpAscent (x, y, 0, nextPlatID, platTree)
|
traceRightJumpAscent (x, y, 0, nextPlatID, platTree)
|
||||||
else
|
else
|
||||||
case #yAxis enemy of
|
case #yAxis enemy of
|
||||||
JUMPING amt =>
|
JUMPING amt => traceRightJumpAscent (x, y, amt, nextPlatID, platTree)
|
||||||
traceRightJumpAscent (x, y, amt, nextPlatID, platTree)
|
| ON_GROUND => traceRightJumpAscent (x, y, 0, nextPlatID, platTree)
|
||||||
| ON_GROUND =>
|
| FALLING => traceRightDescent (x, y, nextPlatID, platTree)
|
||||||
traceRightJumpAscent (x, y, 0, nextPlatID, platTree)
|
| DROP_BELOW_PLATFORM => traceRightDescent (x, y, nextPlatID, platTree)
|
||||||
| FALLING =>
|
| FLOATING _ => traceRightDescent (x, y, nextPlatID, platTree)
|
||||||
traceRightJumpDescent (x, y, nextPlatID, platTree)
|
end
|
||||||
| DROP_BELOW_PLATFORM =>
|
|
||||||
traceRightJumpDescent (x, y, nextPlatID, platTree)
|
fun traceLeftDescent (x, y, nextPlatID, platTree) =
|
||||||
| FLOATING _ =>
|
if x <= 0 orelse y >= Constants.worldHeight then
|
||||||
traceRightJumpDescent (x, y, nextPlatID, platTree)
|
false
|
||||||
|
else
|
||||||
|
let
|
||||||
|
val width = Constants.moveEnemyBy
|
||||||
|
val height = Constants.worldHeight - y
|
||||||
|
val shouldJumpLeft = Trace.foldRegion
|
||||||
|
(x, y, width, height, nextPlatID, false, platTree)
|
||||||
|
|
||||||
|
val nextX = x - Constants.moveEnemyBy
|
||||||
|
val nextY = y + Constants.moveEnemyBy
|
||||||
|
in
|
||||||
|
shouldJumpLeft
|
||||||
|
orelse traceRightDescent (nextX, nextY, nextPlatID, platTree)
|
||||||
|
end
|
||||||
|
|
||||||
|
fun traceLeftDrop (enemy: GameType.enemy, nextPlatID, platTree) =
|
||||||
|
let
|
||||||
|
val {x, y, ...} = enemy
|
||||||
|
val x = x + Constants.enemySize
|
||||||
|
in
|
||||||
|
traceLeftDescent (x, y, nextPlatID, platTree)
|
||||||
|
end
|
||||||
|
|
||||||
|
fun traceLeftJumpAscent (x, y, remainingJump, nextPlatID, platTree) =
|
||||||
|
if remainingJump >= Constants.jumpLimit then
|
||||||
|
traceLeftDescent (x, y, nextPlatID, platTree)
|
||||||
|
else
|
||||||
|
let
|
||||||
|
val width = Constants.moveEnemyBy
|
||||||
|
val height = Constants.worldHeight - y
|
||||||
|
val shouldJumpLeft = Trace.foldRegion
|
||||||
|
(x, y, width, height, nextPlatID, false, platTree)
|
||||||
|
|
||||||
|
val nextX = x - Constants.moveEnemyBy
|
||||||
|
val nextY = y - Constants.moveEnemyBy
|
||||||
|
val nextJump = remainingJump + Constants.moveEnemyBy
|
||||||
|
in
|
||||||
|
shouldJumpLeft
|
||||||
|
orelse
|
||||||
|
traceLeftJumpAscent (nextX, nextY, nextJump, nextPlatID, platTree)
|
||||||
|
end
|
||||||
|
|
||||||
|
fun traceLeftJump (enemy: GameType.enemy, nextPlatID, platTree) =
|
||||||
|
let
|
||||||
|
val {x, y, ...} = enemy
|
||||||
|
val x = x + Constants.enemySize
|
||||||
|
|
||||||
|
open GameType
|
||||||
|
in
|
||||||
|
if EnemyPhysics.standingOnArea (x, y, platTree) then
|
||||||
|
traceLeftJumpAscent (x, y, 0, nextPlatID, platTree)
|
||||||
|
else
|
||||||
|
case #yAxis enemy of
|
||||||
|
JUMPING amt => traceLeftJumpAscent (x, y, amt, nextPlatID, platTree)
|
||||||
|
| ON_GROUND => traceLeftJumpAscent (x, y, 0, nextPlatID, platTree)
|
||||||
|
| FALLING => traceLeftDescent (x, y, nextPlatID, platTree)
|
||||||
|
| DROP_BELOW_PLATFORM => traceLeftDescent (x, y, nextPlatID, platTree)
|
||||||
|
| FLOATING _ => traceLeftDescent (x, y, nextPlatID, platTree)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user