[editor] update Frames
This commit is contained in:
@@ -5,14 +5,15 @@ import haxework.view.ImageView;
|
|||||||
import haxework.view.LabelView;
|
import haxework.view.LabelView;
|
||||||
import openfl.Assets;
|
import openfl.Assets;
|
||||||
import ru.m.tankz.game.GameState;
|
import ru.m.tankz.game.GameState;
|
||||||
|
import ru.m.tankz.Type;
|
||||||
|
|
||||||
@:template class LifeView extends HGroupView {
|
@:template class LifeView extends HGroupView {
|
||||||
@:provide static var currentState:GameState;
|
|
||||||
|
|
||||||
@:view("tank") public var tankImage:ImageView;
|
@:view("tank") public var tankImage:ImageView;
|
||||||
@:view("life") public var lifeLabel:LabelView;
|
@:view("life") public var lifeLabel:LabelView;
|
||||||
@:view("score") public var scoreLabel:LabelView;
|
@:view("score") public var scoreLabel:LabelView;
|
||||||
|
|
||||||
|
public var playerId(default, default):PlayerId;
|
||||||
public var state(null, set):PlayerState;
|
public var state(null, set):PlayerState;
|
||||||
public var tank(null, set):String;
|
public var tank(null, set):String;
|
||||||
public var color(null, set):Int;
|
public var color(null, set):Int;
|
||||||
@@ -20,8 +21,11 @@ import ru.m.tankz.game.GameState;
|
|||||||
public var score(null, set):Int;
|
public var score(null, set):Int;
|
||||||
|
|
||||||
private inline function set_state(value:PlayerState):PlayerState {
|
private inline function set_state(value:PlayerState):PlayerState {
|
||||||
state = value;
|
playerId = value.id;
|
||||||
toUpdate();
|
tank = value.tank;
|
||||||
|
color = value.color;
|
||||||
|
life = value.life;
|
||||||
|
score = value.score;
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,17 +52,6 @@ import ru.m.tankz.game.GameState;
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
override public function update():Void {
|
|
||||||
super.update();
|
|
||||||
if (state != null && currentState != null) {
|
|
||||||
var tankConfig = currentState.config.getTank(state.tank);
|
|
||||||
tank = tankConfig == null ? 'ba' : tankConfig.skin;
|
|
||||||
color = currentState.config.getColor(state.id);
|
|
||||||
life = state.life;
|
|
||||||
score = state.score;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static inline function factory(index:Int, data:PlayerState):LifeView {
|
public static inline function factory(index:Int, data:PlayerState):LifeView {
|
||||||
var result = new LifeView();
|
var result = new LifeView();
|
||||||
result.state = data;
|
result.state = data;
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
package ru.m.tankz.view.common;
|
|
||||||
|
|
||||||
import haxework.resources.IResources;
|
|
||||||
import haxework.color.ColorUtil;
|
|
||||||
import haxework.view.DataView;
|
|
||||||
import haxework.view.HGroupView;
|
|
||||||
import haxework.view.LabelView;
|
|
||||||
import haxework.view.skin.ISkin;
|
|
||||||
import haxework.view.ToggleButtonView;
|
|
||||||
import ru.m.tankz.control.Control;
|
|
||||||
import ru.m.tankz.game.GameState;
|
|
||||||
import ru.m.tankz.Type.TeamId;
|
|
||||||
|
|
||||||
class TeamButton extends ToggleButtonView {
|
|
||||||
public var team(default, set):TeamId;
|
|
||||||
|
|
||||||
private function set_team(value:TeamId):TeamId {
|
|
||||||
if (team != value) {
|
|
||||||
team = value;
|
|
||||||
text = team.substr(0, 1).toUpperCase() + team.substr(1);
|
|
||||||
}
|
|
||||||
return team;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TeamSkin implements ISkin<TeamButton> {
|
|
||||||
|
|
||||||
@:provide private static var resources:IResources;
|
|
||||||
|
|
||||||
public var color(default, default):Int;
|
|
||||||
|
|
||||||
public function new(color:Int) {
|
|
||||||
this.color = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function draw(view:TeamButton):Void {
|
|
||||||
view.fontColor = view.on ? 0xffffff : resources.color.get("dark");
|
|
||||||
var graphics = view.content.graphics;
|
|
||||||
graphics.beginFill(view.on ? color : resources.color.get("light"));
|
|
||||||
graphics.lineStyle(2, color, 0.5);
|
|
||||||
graphics.drawRoundRect(0, 0, view.width, view.height, 5, 5);
|
|
||||||
graphics.endFill();
|
|
||||||
graphics.lineStyle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@:template class PlayerView extends HGroupView {
|
|
||||||
private static inline var TEAM_NONE:TeamId = "none";
|
|
||||||
|
|
||||||
public var item_index(default, set):Int;
|
|
||||||
public var data(default, set):Array<PlayerState>;
|
|
||||||
public var colorize:Bool;
|
|
||||||
|
|
||||||
@:view var label(default, null):LabelView;
|
|
||||||
@:view var teams(default, null):DataView<TeamId, ToggleButtonView>;
|
|
||||||
|
|
||||||
@:provide var state:GameState;
|
|
||||||
|
|
||||||
private var player:PlayerState;
|
|
||||||
|
|
||||||
private function teamViewFactory(index:Int, team:TeamId) {
|
|
||||||
var view = new TeamButton();
|
|
||||||
var color = getTeamColor(team);
|
|
||||||
view.skin = [Style.text(color), new TeamSkin(color)];
|
|
||||||
view.geometry.padding = [10, 5];
|
|
||||||
view.team = team;
|
|
||||||
view.on = team == TEAM_NONE;
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function set_data(value:Array<PlayerState>):Array<PlayerState> {
|
|
||||||
data = value;
|
|
||||||
teams.data = [TEAM_NONE].concat([for (team in state.preset.teams) team.id]);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getTeamColor(teamId:TeamId):Int {
|
|
||||||
var color = 0xaaaaaa;
|
|
||||||
for (team in state.preset.teams) {
|
|
||||||
if (team.id == teamId) {
|
|
||||||
if (!team.color.zero) color = team.color;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function set_item_index(value:Int):Int {
|
|
||||||
item_index = value;
|
|
||||||
label.text = 'Player ${item_index+1}';
|
|
||||||
return item_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function onTeamSelect(team:TeamId) {
|
|
||||||
if (player != null) {
|
|
||||||
player.controller = NONE;
|
|
||||||
player.color = 0;
|
|
||||||
player = null;
|
|
||||||
}
|
|
||||||
for (p in data) {
|
|
||||||
if (p.id.team == team) {
|
|
||||||
switch (p.controller) {
|
|
||||||
case NONE:
|
|
||||||
player = p;
|
|
||||||
player.controller = HUMAN(item_index);
|
|
||||||
if (colorize) {
|
|
||||||
player.color = ColorUtil.multiply(state.config.getTeam(team).color, 1.7);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case _:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (view in teams.views) {
|
|
||||||
var button = cast(view, TeamButton);
|
|
||||||
button.on = team == button.team;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,8 +4,10 @@ import haxework.view.DataView;
|
|||||||
import haxework.view.LabelView;
|
import haxework.view.LabelView;
|
||||||
import haxework.view.VGroupView;
|
import haxework.view.VGroupView;
|
||||||
import ru.m.geom.Direction;
|
import ru.m.geom.Direction;
|
||||||
|
import ru.m.tankz.bundle.IConfigBundle;
|
||||||
import ru.m.tankz.game.GameEvent;
|
import ru.m.tankz.game.GameEvent;
|
||||||
import ru.m.tankz.game.GameState;
|
import ru.m.tankz.game.GameState;
|
||||||
|
import ru.m.tankz.preset.DeathGame;
|
||||||
import ru.m.tankz.view.common.LifeView;
|
import ru.m.tankz.view.common.LifeView;
|
||||||
|
|
||||||
@:template class DeathGamePanel extends VGroupView implements IGamePanel {
|
@:template class DeathGamePanel extends VGroupView implements IGamePanel {
|
||||||
@@ -14,15 +16,31 @@ import ru.m.tankz.view.common.LifeView;
|
|||||||
@:view var level:LabelView;
|
@:view var level:LabelView;
|
||||||
@:view var players:DataView<PlayerState, LifeView>;
|
@:view var players:DataView<PlayerState, LifeView>;
|
||||||
|
|
||||||
|
@:provide var configBundle:IConfigBundle;
|
||||||
|
|
||||||
|
private function getView(playerId):LifeView {
|
||||||
|
for (view in players.dataViews) {
|
||||||
|
if (view.playerId == playerId) {
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public function onGameEvent(event:GameEvent):Void {
|
public function onGameEvent(event:GameEvent):Void {
|
||||||
switch event {
|
switch event {
|
||||||
case START(start):
|
case START(start):
|
||||||
this.level.text = 'Level ${start.level.id}';
|
this.level.text = 'Level ${start.level.id}';
|
||||||
players.data = Lambda.array(start.state.players);
|
players.data = Lambda.array(start.state.players);
|
||||||
|
case SPAWN(TANK(_, _, playerId, info)):
|
||||||
|
var skin = configBundle.get(DeathGame.TYPE).getTank(info.type).skin;
|
||||||
|
getView(playerId).tank = skin;
|
||||||
|
getView(playerId).color = info.color;
|
||||||
|
case CHANGE(PLAYER_LIFE(playerId, life)):
|
||||||
|
getView(playerId).life = life;
|
||||||
|
case CHANGE(PLAYER_SCORE(playerId, score)):
|
||||||
|
getView(playerId).score = score;
|
||||||
case _:
|
case _:
|
||||||
for (view in players.views) {
|
|
||||||
view.toUpdate();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,13 +53,6 @@ import ru.m.tankz.Type;
|
|||||||
|
|
||||||
private function changePause(value:Bool):Void {}
|
private function changePause(value:Bool):Void {}
|
||||||
|
|
||||||
private function applyPosition(entity:Entity, position:Position):Void {
|
|
||||||
entity.rect.center = new Point(position.x, position.y);
|
|
||||||
if (position.direction != null) {
|
|
||||||
entity.rect.direction = position.direction;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public inline function getTeam(teamId:TeamId):Team {
|
public inline function getTeam(teamId:TeamId):Team {
|
||||||
return teams[teamId];
|
return teams[teamId];
|
||||||
}
|
}
|
||||||
@@ -75,9 +68,6 @@ import ru.m.tankz.Type;
|
|||||||
case COMPLETE(result):
|
case COMPLETE(result):
|
||||||
this.state = result.state;
|
this.state = result.state;
|
||||||
this.winner = result.winner;
|
this.winner = result.winner;
|
||||||
case SPAWN(EAGLE(id, rect, teamId)):
|
|
||||||
var team = getTeam(teamId);
|
|
||||||
team.eagleId = id;
|
|
||||||
case SPAWN(TANK(id, rect, playerId, info)):
|
case SPAWN(TANK(id, rect, playerId, info)):
|
||||||
if (controls.exists(playerId)) {
|
if (controls.exists(playerId)) {
|
||||||
var control = controls[playerId];
|
var control = controls[playerId];
|
||||||
|
|||||||
@@ -383,6 +383,9 @@ class GameRunner extends Game implements EngineListener {
|
|||||||
case ACTION(tankId, STOP):
|
case ACTION(tankId, STOP):
|
||||||
gameEventSignal.emit(STOP(TANK(tankId)));
|
gameEventSignal.emit(STOP(TANK(tankId)));
|
||||||
engine.stop(tankId);
|
engine.stop(tankId);
|
||||||
|
case SPAWN(EAGLE(id, rect, teamId)):
|
||||||
|
var team = getTeam(teamId);
|
||||||
|
team.eagleId = id;
|
||||||
case SPAWN(BULLET(_, _, playerId, _)):
|
case SPAWN(BULLET(_, _, playerId, _)):
|
||||||
getPlayer(playerId).bullets++;
|
getPlayer(playerId).bullets++;
|
||||||
case DESTROY(EAGLE(id, shot)):
|
case DESTROY(EAGLE(id, shot)):
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
package ru.m.tankz.map;
|
package ru.m.tankz.map;
|
||||||
|
|
||||||
import ru.m.tankz.config.Config.GridSize;
|
import haxe.ds.HashMap;
|
||||||
import ru.m.tankz.config.Config.CellSize;
|
|
||||||
import ru.m.geom.Line;
|
import ru.m.geom.Line;
|
||||||
import ru.m.geom.Point;
|
import ru.m.geom.Point;
|
||||||
import ru.m.geom.Rectangle;
|
import ru.m.geom.Rectangle;
|
||||||
import haxe.ds.HashMap;
|
|
||||||
|
|
||||||
|
|
||||||
class GridCell {
|
class GridCell {
|
||||||
public var cellX(default, null):Int;
|
public var cellX(default, null):Int;
|
||||||
@@ -42,7 +39,6 @@ class GridCell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Grid {
|
class Grid {
|
||||||
public var cellWidth(default, null):Int;
|
public var cellWidth(default, null):Int;
|
||||||
public var cellHeight(default, null):Int;
|
public var cellHeight(default, null):Int;
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
package ru.m.tankz.map;
|
package ru.m.tankz.map;
|
||||||
|
|
||||||
import ru.m.tankz.Type.BrickType;
|
|
||||||
import haxe.ds.HashMap;
|
import haxe.ds.HashMap;
|
||||||
import ru.m.tankz.map.Grid;
|
|
||||||
import ru.m.geom.Point;
|
import ru.m.geom.Point;
|
||||||
import ru.m.tankz.config.Config;
|
import ru.m.tankz.config.Config;
|
||||||
|
import ru.m.tankz.map.Grid;
|
||||||
|
|
||||||
class LevelMap {
|
class LevelMap {
|
||||||
|
|
||||||
@@ -30,7 +28,7 @@ class LevelMap {
|
|||||||
cellWidth = config.cell.width;
|
cellWidth = config.cell.width;
|
||||||
cellHeight = config.cell.width;
|
cellHeight = config.cell.width;
|
||||||
gridWidth = size != null ? size.width : config.grid.width;
|
gridWidth = size != null ? size.width : config.grid.width;
|
||||||
gridHeight = size != null ? size.height : config.grid.width;
|
gridHeight = size != null ? size.height : config.grid.height;
|
||||||
bricksMap = new HashMap();
|
bricksMap = new HashMap();
|
||||||
bricks = [];
|
bricks = [];
|
||||||
grid = new Grid(
|
grid = new Grid(
|
||||||
|
|||||||
@@ -1,22 +1,9 @@
|
|||||||
package ru.m.tankz.editor.view;
|
package ru.m.tankz.editor.view;
|
||||||
|
|
||||||
import haxework.view.ButtonView;
|
|
||||||
import haxework.view.frame.FrameSwitcher;
|
import haxework.view.frame.FrameSwitcher;
|
||||||
import haxework.view.IGroupView;
|
|
||||||
import haxework.view.IView;
|
|
||||||
import haxework.view.ToggleButtonView;
|
|
||||||
import haxework.view.VGroupView;
|
import haxework.view.VGroupView;
|
||||||
|
|
||||||
@:template class EditorView extends VGroupView {
|
@:template class EditorView extends VGroupView {
|
||||||
@:view public var switcher(default, null):FrameSwitcher;
|
@:view public var switcher(default, null):FrameSwitcher;
|
||||||
@:view public var tabs(default, null):IGroupView;
|
|
||||||
|
|
||||||
public function onPress(v:ButtonView):Void {
|
|
||||||
switcher.change(v.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function onFrameSwitch(frame:IView<Dynamic>):Void {
|
|
||||||
for (view in tabs.views) cast(view, ToggleButtonView).on = view.id == frame.id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,31 +6,25 @@ layout.hAlign: center
|
|||||||
views:
|
views:
|
||||||
# Tabs
|
# Tabs
|
||||||
- id: tabs
|
- id: tabs
|
||||||
$type: haxework.view.HGroupView
|
$type: haxework.view.ButtonGroup<String>
|
||||||
geometry.margin.bottom: -2
|
geometry.margin.bottom: -2
|
||||||
views:
|
buttonSkinId: button.tab
|
||||||
- id: map
|
layout:
|
||||||
$type: haxework.view.ToggleButtonView
|
$type: haxework.view.layout.HorizontalLayout
|
||||||
skinId: button.tab
|
+onDataSelect: $code:function(id) switcher.change(id)
|
||||||
text: Map
|
data:
|
||||||
+onPress: $this:onPress
|
- "map"
|
||||||
- id: tank
|
- "tank"
|
||||||
$type: haxework.view.ToggleButtonView
|
selected: "map"
|
||||||
skinId: button.tab
|
|
||||||
text: Tank
|
|
||||||
+onPress: $this:onPress
|
|
||||||
# Switcher
|
# Switcher
|
||||||
- id: switcher
|
- id: switcher
|
||||||
$type: haxework.view.frame.FrameSwitcher
|
$type: haxework.view.frame.FrameSwitcher
|
||||||
skinId: border
|
skinId: border
|
||||||
+onSwitch: $this:onFrameSwitch
|
|
||||||
geometry.size.stretch: true
|
geometry.size.stretch: true
|
||||||
geometry.padding: 5
|
geometry.padding: 5
|
||||||
views:
|
factory:
|
||||||
- id: map
|
_map_: {$class: ru.m.tankz.editor.view.MapFrame}
|
||||||
$type: ru.m.tankz.editor.view.MapFrame
|
_tank_: {$class: ru.m.tankz.editor.view.TankFrame}
|
||||||
- id: tank
|
|
||||||
$type: ru.m.tankz.editor.view.TankFrame
|
|
||||||
# Version
|
# Version
|
||||||
- $type: haxework.view.LabelView
|
- $type: haxework.view.LabelView
|
||||||
skinId: text
|
skinId: text
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package ru.m.tankz.editor.view;
|
package ru.m.tankz.editor.view;
|
||||||
|
|
||||||
import haxework.view.DataView;
|
import haxework.view.DataView;
|
||||||
|
import haxework.view.frame.FrameView;
|
||||||
import haxework.view.InputView;
|
import haxework.view.InputView;
|
||||||
import haxework.view.VGroupView;
|
|
||||||
import ru.m.tankz.bundle.IConfigBundle;
|
import ru.m.tankz.bundle.IConfigBundle;
|
||||||
import ru.m.tankz.config.Config;
|
import ru.m.tankz.config.Config;
|
||||||
import ru.m.tankz.editor.FileUtil;
|
import ru.m.tankz.editor.FileUtil;
|
||||||
@@ -13,7 +13,7 @@ import ru.m.tankz.preset.ClassicGame;
|
|||||||
import ru.m.tankz.Type;
|
import ru.m.tankz.Type;
|
||||||
import ru.m.tankz.util.LevelUtil;
|
import ru.m.tankz.util.LevelUtil;
|
||||||
|
|
||||||
@:template class MapFrame extends VGroupView {
|
@:template class MapFrame extends FrameView<Dynamic> {
|
||||||
public static inline var ID = 'map';
|
public static inline var ID = 'map';
|
||||||
public static inline var TAG = 'map';
|
public static inline var TAG = 'map';
|
||||||
|
|
||||||
@@ -30,6 +30,10 @@ import ru.m.tankz.util.LevelUtil;
|
|||||||
@:provide var configBundle:IConfigBundle;
|
@:provide var configBundle:IConfigBundle;
|
||||||
@:provide var config:Config;
|
@:provide var config:Config;
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
super(ID);
|
||||||
|
}
|
||||||
|
|
||||||
public function init():Void {
|
public function init():Void {
|
||||||
fileName = "level000.txt";
|
fileName = "level000.txt";
|
||||||
type = ClassicGame.TYPE;
|
type = ClassicGame.TYPE;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
---
|
---
|
||||||
$type: haxework.view.VGroupView
|
|
||||||
geometry.size.stretch: true
|
geometry.size.stretch: true
|
||||||
layout.margin: 2
|
layout:
|
||||||
layout.vAlign: top
|
$type: haxework.view.layout.VerticalLayout
|
||||||
layout.hAlign: center
|
margin: 2
|
||||||
|
vAlign: top
|
||||||
|
hAlign: center
|
||||||
views:
|
views:
|
||||||
- $type: haxework.view.HGroupView
|
- $type: haxework.view.HGroupView
|
||||||
layout.margin: 5
|
layout.margin: 5
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
package ru.m.tankz.editor.view;
|
package ru.m.tankz.editor.view;
|
||||||
|
|
||||||
import ru.m.tankz.editor.view.tank.TankView;
|
|
||||||
import haxework.view.IGroupView;
|
|
||||||
import haxework.color.Color;
|
import haxework.color.Color;
|
||||||
|
import haxework.view.frame.FrameView;
|
||||||
|
import haxework.view.IGroupView;
|
||||||
import haxework.view.InputView;
|
import haxework.view.InputView;
|
||||||
import haxework.view.VGroupView;
|
import ru.m.tankz.editor.view.tank.TankView;
|
||||||
|
|
||||||
@:template class TankFrame extends VGroupView {
|
@:template class TankFrame extends FrameView<Dynamic> {
|
||||||
public static inline var ID = 'tank';
|
public static inline var ID = 'tank';
|
||||||
|
|
||||||
@:view var tanks(default, null):IGroupView;
|
@:view var tanks(default, null):IGroupView;
|
||||||
@:view("color") var colorLabel(default, null):InputView;
|
@:view("color") var colorLabel(default, null):InputView;
|
||||||
private var color(default, set):Color;
|
private var color(default, set):Color;
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
super(ID);
|
||||||
|
}
|
||||||
|
|
||||||
public function init():Void {
|
public function init():Void {
|
||||||
color = 0x00ff00;
|
color = 0x00ff00;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
---
|
---
|
||||||
geometry.size.stretch: true
|
geometry.size.stretch: true
|
||||||
geometry.padding: 10
|
geometry.padding: 10
|
||||||
layout.hAlign: center
|
layout:
|
||||||
layout.margin: 10
|
$type: haxework.view.layout.VerticalLayout
|
||||||
|
hAlign: center
|
||||||
|
margin: 10
|
||||||
views:
|
views:
|
||||||
- id: tanks
|
- id: tanks
|
||||||
$type: haxework.view.GroupView
|
$type: haxework.view.GroupView
|
||||||
|
|||||||
Reference in New Issue
Block a user