[common] update Game with GameEvent
This commit is contained in:
@@ -7,7 +7,8 @@ const dateformat = require('dateformat');
|
|||||||
|
|
||||||
|
|
||||||
// ToDo: update default in gulp-haxetool
|
// ToDo: update default in gulp-haxetool
|
||||||
FlashPlayer.VERSION = '31';
|
FlashPlayer.VERSION = '32';
|
||||||
|
//Haxe.VERSION = '4.0.0-rc.2';
|
||||||
if (Config.SdkDir) {
|
if (Config.SdkDir) {
|
||||||
Sdk.dir = Config.SdkDir;
|
Sdk.dir = Config.SdkDir;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ import promhx.Promise;
|
|||||||
import ru.m.animate.Animate;
|
import ru.m.animate.Animate;
|
||||||
import ru.m.animate.OnceAnimate;
|
import ru.m.animate.OnceAnimate;
|
||||||
import ru.m.geom.Point;
|
import ru.m.geom.Point;
|
||||||
import ru.m.tankz.core.EntityType;
|
|
||||||
import ru.m.tankz.engine.Engine;
|
import ru.m.tankz.engine.Engine;
|
||||||
|
import ru.m.tankz.game.Game.GameListener;
|
||||||
|
import ru.m.tankz.game.GameEvent;
|
||||||
import ru.m.tankz.render.RenderItem;
|
import ru.m.tankz.render.RenderItem;
|
||||||
import ru.m.tankz.Type;
|
|
||||||
|
|
||||||
class Render extends SpriteView implements EngineListener {
|
class Render extends SpriteView implements GameListener {
|
||||||
|
|
||||||
private var backgroundLayer:Sprite;
|
private var backgroundLayer:Sprite;
|
||||||
private var groundLayer:Sprite;
|
private var groundLayer:Sprite;
|
||||||
@@ -99,69 +99,58 @@ class Render extends SpriteView implements EngineListener {
|
|||||||
clearLayer(upperLayer);
|
clearLayer(upperLayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onSpawn(entity:EntityType):Void {
|
public function onGameEvent(event:GameEvent):Void {
|
||||||
switch entity {
|
switch event {
|
||||||
case TANK(tank):
|
case SPAWN(TANK(tank)):
|
||||||
var item = new TankItem(tank);
|
var item = new TankItem(tank);
|
||||||
items.set(tank.key, item);
|
items.set(tank.key, item);
|
||||||
entryLayer.addChild(item.view);
|
entryLayer.addChild(item.view);
|
||||||
item.update();
|
item.update();
|
||||||
playAnimate(tank.rect.center, AnimateBundle.tankSpawn());
|
playAnimate(tank.rect.center, AnimateBundle.tankSpawn());
|
||||||
case BULLET(bullet):
|
case SPAWN(BULLET(bullet)):
|
||||||
var item = new BulletItem(bullet);
|
var item = new BulletItem(bullet);
|
||||||
items.set(bullet.key, item);
|
items.set(bullet.key, item);
|
||||||
entryLayer.addChild(item.view);
|
entryLayer.addChild(item.view);
|
||||||
item.update();
|
item.update();
|
||||||
case EAGLE(eagle):
|
case SPAWN(EAGLE(eagle)):
|
||||||
var item = new EagleItem(eagle);
|
var item = new EagleItem(eagle);
|
||||||
items.set(eagle.key, item);
|
items.set(eagle.key, item);
|
||||||
entryLayer.addChild(item.view);
|
entryLayer.addChild(item.view);
|
||||||
item.update();
|
item.update();
|
||||||
case BONUS(bonus):
|
case SPAWN(BONUS(bonus)):
|
||||||
var item = new BonusItem(bonus);
|
var item = new BonusItem(bonus);
|
||||||
items.set(bonus.key, item);
|
items.set(bonus.key, item);
|
||||||
upperLayer.addChild(item.view);
|
upperLayer.addChild(item.view);
|
||||||
item.update();
|
item.update();
|
||||||
case _:
|
case DESTROY(TANK(tank, who, wherewith, score)):
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onCollision(entity:EntityType, with:EntityType):Void {
|
|
||||||
switch [entity, with] {
|
|
||||||
case [BULLET(_), EAGLE(eagle)]:
|
|
||||||
if (eagle.death) {
|
|
||||||
playAnimate(eagle.rect.center, AnimateBundle.tankBoom());
|
|
||||||
}
|
|
||||||
case _:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onDestroy(entity:EntityType):Void {
|
|
||||||
switch entity {
|
|
||||||
case TANK(tank):
|
|
||||||
if (items.exists(tank.key)) {
|
if (items.exists(tank.key)) {
|
||||||
entryLayer.removeChild(items.get(tank.key).view);
|
entryLayer.removeChild(items.get(tank.key).view);
|
||||||
items.remove(tank.key);
|
items.remove(tank.key);
|
||||||
playAnimate(tank.rect.center, AnimateBundle.tankBoom());
|
playAnimate(tank.rect.center, AnimateBundle.tankBoom());
|
||||||
if (tank.config.score > 0) {
|
if (score != 0) {
|
||||||
showScore(tank.rect.center, tank.config.score);
|
showScore(tank.rect.center, score);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case BULLET(bullet):
|
case DESTROY(BULLET(bullet)):
|
||||||
if (items.exists(bullet.key)) {
|
if (items.exists(bullet.key)) {
|
||||||
entryLayer.removeChild(items.get(bullet.key).view);
|
entryLayer.removeChild(items.get(bullet.key).view);
|
||||||
items.remove(bullet.key);
|
items.remove(bullet.key);
|
||||||
var point = bullet.rect.center.add(new Point(bullet.rect.width * bullet.rect.direction.x, bullet.rect.height * bullet.rect.direction.y));
|
var point = bullet.rect.center.add(new Point(bullet.rect.width * bullet.rect.direction.x, bullet.rect.height * bullet.rect.direction.y));
|
||||||
playAnimate(point, AnimateBundle.bulletBoom());
|
playAnimate(point, AnimateBundle.bulletBoom());
|
||||||
}
|
}
|
||||||
case BONUS(bonus):
|
case DESTROY(BONUS(bonus, who, score)):
|
||||||
if (items.exists(bonus.key)) {
|
if (items.exists(bonus.key)) {
|
||||||
upperLayer.removeChild(items.get(bonus.key).view);
|
upperLayer.removeChild(items.get(bonus.key).view);
|
||||||
items.remove(bonus.key);
|
items.remove(bonus.key);
|
||||||
if (bonus.config.score > 0) {
|
if (score != 0) {
|
||||||
showScore(bonus.rect.center, bonus.config.score);
|
showScore(bonus.rect.center, score);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case DESTROY(EAGLE(eagle, who, wherewith, score)):
|
||||||
|
playAnimate(eagle.rect.center, AnimateBundle.tankBoom());
|
||||||
|
if (score != 0) {
|
||||||
|
showScore(eagle.rect.center, score);
|
||||||
|
}
|
||||||
case _:
|
case _:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,7 +165,6 @@ class Render extends SpriteView implements EngineListener {
|
|||||||
upperLayer.removeChild(animate);
|
upperLayer.removeChild(animate);
|
||||||
}
|
}
|
||||||
animate.dispose();
|
animate.dispose();
|
||||||
return null;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,8 @@ import flash.media.Sound;
|
|||||||
import flash.media.SoundChannel;
|
import flash.media.SoundChannel;
|
||||||
import flash.media.SoundTransform;
|
import flash.media.SoundTransform;
|
||||||
import openfl.utils.Assets;
|
import openfl.utils.Assets;
|
||||||
import ru.m.tankz.control.HumanControl;
|
|
||||||
import ru.m.tankz.game.Game;
|
import ru.m.tankz.game.Game;
|
||||||
import ru.m.tankz.game.GameEvent;
|
import ru.m.tankz.game.GameEvent;
|
||||||
import ru.m.tankz.game.GameState;
|
|
||||||
|
|
||||||
class SoundManager implements GameListener {
|
class SoundManager implements GameListener {
|
||||||
private static var TAG(default, never):String = 'SoundManager';
|
private static var TAG(default, never):String = 'SoundManager';
|
||||||
@@ -76,44 +74,33 @@ class SoundManager implements GameListener {
|
|||||||
channels.remove(event.currentTarget);
|
channels.remove(event.currentTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onGameStart(state:GameState):Void {
|
|
||||||
play('start');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onGameChange(state:GameState):Void {
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onGameComplete(state:GameState):Void {
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onGameEvent(event:GameEvent):Void {
|
public function onGameEvent(event:GameEvent):Void {
|
||||||
// ToDo:
|
switch event {
|
||||||
/*switch event {
|
case START(state):
|
||||||
case SPAWN_BULLET(player):
|
play('start');
|
||||||
// ToDo:
|
case SPAWN(BULLET(bullet)):
|
||||||
if (Std.is(player.control, HumanControl)) {
|
if (false /* ToDo: human tank */) {
|
||||||
play('shot');
|
play('shot');
|
||||||
}
|
}
|
||||||
case HIT_TANK(player, target):
|
case SPAWN(BONUS(bonus)):
|
||||||
|
play('bonus_add');
|
||||||
|
case HIT(TANK(tank, who, wherewith)):
|
||||||
play('bullet_hit');
|
play('bullet_hit');
|
||||||
case DESTROY_TANK(player, target):
|
case DESTROY(TANK(tank, who, wherewith, score)):
|
||||||
// ToDo:
|
if (true /* ToDo: human tank */) {
|
||||||
if (Std.is(target.control, HumanControl)) {
|
|
||||||
play('boom_player');
|
play('boom_player');
|
||||||
} else {
|
} else {
|
||||||
play('boom_bot');
|
play('boom_bot');
|
||||||
}
|
}
|
||||||
case DESTROY_EAGLE(player, eagleTeamId):
|
case DESTROY(EAGLE(eagle, who, wherewith, score)):
|
||||||
play('boom_player');
|
play('boom_player');
|
||||||
case SPAWN_BONUS(bonus):
|
case DESTROY(BONUS(bonus, who, score)):
|
||||||
play('bonus');
|
|
||||||
case TAKE_BONUS(player, bonus):
|
|
||||||
if (bonus.type == 'life') {
|
if (bonus.type == 'life') {
|
||||||
play('live');
|
play('live');
|
||||||
} else {
|
} else {
|
||||||
play('bonus_get');
|
play('bonus_get');
|
||||||
}
|
}
|
||||||
case _:
|
case _:
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package ru.m.tankz.view.classic;
|
package ru.m.tankz.view.classic;
|
||||||
|
|
||||||
import ru.m.tankz.game.GameEvent;
|
|
||||||
import haxework.view.LabelView;
|
import haxework.view.LabelView;
|
||||||
import haxework.view.VGroupView;
|
import haxework.view.VGroupView;
|
||||||
|
import ru.m.tankz.game.GameEvent;
|
||||||
import ru.m.tankz.game.GameState;
|
import ru.m.tankz.game.GameState;
|
||||||
import ru.m.tankz.preset.ClassicGame;
|
import ru.m.tankz.preset.ClassicGame;
|
||||||
import ru.m.tankz.Type.PlayerId;
|
import ru.m.tankz.Type.PlayerId;
|
||||||
@@ -19,15 +19,12 @@ import ru.m.tankz.view.common.LifeView;
|
|||||||
private var player1Id:PlayerId = new PlayerId(ClassicGame.HUMAN, 0);
|
private var player1Id:PlayerId = new PlayerId(ClassicGame.HUMAN, 0);
|
||||||
private var player2Id:PlayerId = new PlayerId(ClassicGame.HUMAN, 1);
|
private var player2Id:PlayerId = new PlayerId(ClassicGame.HUMAN, 1);
|
||||||
|
|
||||||
public function onGameStart(state:GameState):Void {
|
public function refresh(state:GameState):Void {
|
||||||
level.text = 'Level ${state.levelId}';
|
level.text = 'Level ${state.levelId}';
|
||||||
}
|
|
||||||
|
|
||||||
public function onGameChange(state:GameState):Void {
|
|
||||||
bot.life = state.getTeamLife(ClassicGame.BOT);
|
bot.life = state.getTeamLife(ClassicGame.BOT);
|
||||||
player1.life = state.getPlayerLife(player1Id);
|
player1.life = state.getPlayerLife(player1Id);
|
||||||
player1.score = state.getPlayerScore(player1Id);
|
player1.score = state.getPlayerScore(player1Id);
|
||||||
if (true) {
|
if (state.getPlayerLife(player2Id) > 0) {
|
||||||
player2.visible = true;
|
player2.visible = true;
|
||||||
player2.life = state.getPlayerLife(player2Id);
|
player2.life = state.getPlayerLife(player2Id);
|
||||||
player2.score = state.getPlayerScore(player2Id);
|
player2.score = state.getPlayerScore(player2Id);
|
||||||
@@ -36,11 +33,27 @@ import ru.m.tankz.view.common.LifeView;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onGameComplete(state:GameState):Void {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onGameEvent(event:GameEvent):Void {
|
public function onGameEvent(event:GameEvent):Void {
|
||||||
|
switch event {
|
||||||
|
case START(state):
|
||||||
|
refresh(state);
|
||||||
|
case CHANGE(TEAM_LIFE(team, life)):
|
||||||
|
if (team.id == ClassicGame.BOT) {
|
||||||
|
bot.life = life;
|
||||||
|
}
|
||||||
|
case CHANGE(PLAYER_LIFE(player, life)):
|
||||||
|
if (player.id == player1Id) {
|
||||||
|
player1.life = life;
|
||||||
|
} else if (player.id == player2Id) {
|
||||||
|
player2.life = life;
|
||||||
|
}
|
||||||
|
case CHANGE(PLAYER_SCORE(player, score)):
|
||||||
|
if (player.id == player1Id) {
|
||||||
|
player1.score = score;
|
||||||
|
} else if (player.id == player2Id) {
|
||||||
|
player2.score = score;
|
||||||
|
}
|
||||||
|
case _:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class GameFrame extends GroupView implements GameListener {
|
|||||||
|
|
||||||
private function start(state:GameState):Void {
|
private function start(state:GameState):Void {
|
||||||
game = new Game(state.type);
|
game = new Game(state.type);
|
||||||
game.engine.connect(render);
|
game.connect(render);
|
||||||
game.connect(soundManager);
|
game.connect(soundManager);
|
||||||
game.connect(this);
|
game.connect(this);
|
||||||
if (panel != null) {
|
if (panel != null) {
|
||||||
@@ -71,11 +71,9 @@ class GameFrame extends GroupView implements GameListener {
|
|||||||
render.reset();
|
render.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onGameStart(state:GameState):Void {}
|
public function onGameEvent(event:GameEvent):Void {
|
||||||
|
switch event {
|
||||||
public function onGameChange(state:GameState):Void {}
|
case GameEvent.COMPLETE(state, winner):
|
||||||
|
|
||||||
public function onGameComplete(state:GameState):Void {
|
|
||||||
result = state;
|
result = state;
|
||||||
this.state = switch game.next() {
|
this.state = switch game.next() {
|
||||||
case Some(s):
|
case Some(s):
|
||||||
@@ -88,9 +86,8 @@ class GameFrame extends GroupView implements GameListener {
|
|||||||
}
|
}
|
||||||
stop();
|
stop();
|
||||||
switcher.change(ResultFrame.ID);
|
switcher.change(ResultFrame.ID);
|
||||||
|
case _:
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onGameEvent(event:GameEvent):Void {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onHide():Void {
|
public function onHide():Void {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import haxework.view.HGroupView;
|
|||||||
import haxework.view.ImageView;
|
import haxework.view.ImageView;
|
||||||
import haxework.view.LabelView;
|
import haxework.view.LabelView;
|
||||||
import openfl.Assets;
|
import openfl.Assets;
|
||||||
import ru.m.tankz.game.GameState.PlayerState;
|
|
||||||
import ru.m.tankz.game.GameState;
|
import ru.m.tankz.game.GameState;
|
||||||
|
|
||||||
@:template class LifeView extends HGroupView {
|
@:template class LifeView extends HGroupView {
|
||||||
@@ -51,7 +50,7 @@ import ru.m.tankz.game.GameState;
|
|||||||
|
|
||||||
override public function update():Void {
|
override public function update():Void {
|
||||||
super.update();
|
super.update();
|
||||||
if (state != null) {
|
if (state != null && currentState != null) {
|
||||||
var tankConfig = currentState.config.getTank(state.tank);
|
var tankConfig = currentState.config.getTank(state.tank);
|
||||||
tank = tankConfig == null ? 'ba' : tankConfig.skin;
|
tank = tankConfig == null ? 'ba' : tankConfig.skin;
|
||||||
color = currentState.config.getColor(state.id);
|
color = currentState.config.getColor(state.id);
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import haxework.view.DataView;
|
|||||||
import haxework.view.LabelView;
|
import haxework.view.LabelView;
|
||||||
import haxework.view.VGroupView;
|
import haxework.view.VGroupView;
|
||||||
import ru.m.tankz.game.GameEvent;
|
import ru.m.tankz.game.GameEvent;
|
||||||
import ru.m.tankz.game.GameState.PlayerState;
|
|
||||||
import ru.m.tankz.game.GameState;
|
import ru.m.tankz.game.GameState;
|
||||||
import ru.m.tankz.view.common.IGamePanel;
|
import ru.m.tankz.view.common.IGamePanel;
|
||||||
import ru.m.tankz.view.common.LifeView;
|
import ru.m.tankz.view.common.LifeView;
|
||||||
@@ -14,22 +13,15 @@ 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>;
|
||||||
|
|
||||||
public function onGameStart(state:GameState):Void {
|
public function onGameEvent(event:GameEvent):Void {
|
||||||
|
switch event {
|
||||||
|
case START(state):
|
||||||
level.text = 'Level ${state.levelId}';
|
level.text = 'Level ${state.levelId}';
|
||||||
players.data = Lambda.array(state.players);
|
players.data = Lambda.array(state.players);
|
||||||
}
|
case _:
|
||||||
|
|
||||||
public function onGameChange(state:GameState):Void {
|
|
||||||
for (view in players.views) {
|
for (view in players.views) {
|
||||||
view.toUpdate();
|
view.toUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onGameComplete(state:GameState):Void {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onGameEvent(event:GameEvent):Void {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import haxework.view.LabelView;
|
|||||||
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.DotaGame;
|
import ru.m.tankz.preset.DotaGame;
|
||||||
|
import ru.m.tankz.Type.TeamId;
|
||||||
import ru.m.tankz.view.common.IGamePanel;
|
import ru.m.tankz.view.common.IGamePanel;
|
||||||
import ru.m.tankz.view.common.LifeView;
|
import ru.m.tankz.view.common.LifeView;
|
||||||
|
|
||||||
@@ -14,22 +15,31 @@ import ru.m.tankz.view.common.LifeView;
|
|||||||
@:view var dire:LifeView;
|
@:view var dire:LifeView;
|
||||||
@:view var level:LabelView;
|
@:view var level:LabelView;
|
||||||
|
|
||||||
public function onGameStart(state:GameState):Void {
|
public function refresh(state:GameState):Void {
|
||||||
level.text = 'Level ${state.levelId}';
|
level.text = 'Level ${state.levelId}';
|
||||||
}
|
|
||||||
|
|
||||||
public function onGameChange(state:GameState):Void {
|
|
||||||
radiant.life = state.getTeamLife(DotaGame.RADIANT);
|
radiant.life = state.getTeamLife(DotaGame.RADIANT);
|
||||||
radiant.score = state.getTeamScore(DotaGame.RADIANT);
|
radiant.score = state.getTeamScore(DotaGame.RADIANT);
|
||||||
dire.life = state.getTeamLife(DotaGame.DIRE);
|
dire.life = state.getTeamLife(DotaGame.DIRE);
|
||||||
dire.score = state.getTeamScore(DotaGame.DIRE);
|
dire.score = state.getTeamScore(DotaGame.DIRE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onGameComplete(state:GameState):Void {
|
private inline function getLifeView(teamId:TeamId):LifeView {
|
||||||
|
return switch teamId {
|
||||||
|
case DotaGame.RADIANT: radiant;
|
||||||
|
case DotaGame.DIRE: dire;
|
||||||
|
case _: null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onGameEvent(event:GameEvent):Void {
|
public function onGameEvent(event:GameEvent):Void {
|
||||||
|
switch event {
|
||||||
|
case START(state):
|
||||||
|
refresh(state);
|
||||||
|
case CHANGE(TEAM_LIFE(team, life)):
|
||||||
|
getLifeView(team.id).life = life;
|
||||||
|
case CHANGE(TEAM_SCORE(team, score)):
|
||||||
|
getLifeView(team.id).score = score;
|
||||||
|
case _:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,7 @@ import ru.m.tankz.map.Grid;
|
|||||||
import ru.m.tankz.map.LevelMap;
|
import ru.m.tankz.map.LevelMap;
|
||||||
|
|
||||||
interface EngineListener {
|
interface EngineListener {
|
||||||
public function onSpawn(entity:EntityType):Void;
|
|
||||||
public function onCollision(entity:EntityType, with:EntityType):Void;
|
public function onCollision(entity:EntityType, with:EntityType):Void;
|
||||||
public function onDestroy(entity:EntityType):Void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@:yield @:dispatcher(EngineListener) class Engine {
|
@:yield @:dispatcher(EngineListener) class Engine {
|
||||||
@@ -35,14 +33,10 @@ interface EngineListener {
|
|||||||
|
|
||||||
public function spawn(entity:Entity):Void {
|
public function spawn(entity:Entity):Void {
|
||||||
entities.set(entity.id, entity);
|
entities.set(entity.id, entity);
|
||||||
var type = EntityTypeResolver.of(entity);
|
|
||||||
spawnSignal.emit(type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function destroy(entity:Entity):Void {
|
public function destroy(entity:Entity):Void {
|
||||||
if (entities.exists(entity.id)) {
|
if (entities.exists(entity.id)) {
|
||||||
var type = EntityTypeResolver.of(entity);
|
|
||||||
destroySignal.emit(type);
|
|
||||||
entities.remove(entity.id);
|
entities.remove(entity.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package ru.m.tankz.game;
|
package ru.m.tankz.game;
|
||||||
|
|
||||||
|
import ru.m.tankz.game.GameEvent.ChangeEvent;
|
||||||
import haxe.ds.Option;
|
import haxe.ds.Option;
|
||||||
import haxe.Timer;
|
import haxe.Timer;
|
||||||
import ru.m.geom.Point;
|
import ru.m.geom.Point;
|
||||||
@@ -19,13 +20,10 @@ import ru.m.tankz.game.Spawner;
|
|||||||
import ru.m.tankz.Type;
|
import ru.m.tankz.Type;
|
||||||
|
|
||||||
interface GameListener {
|
interface GameListener {
|
||||||
public function onGameStart(state:GameState):Void;
|
|
||||||
public function onGameChange(state:GameState):Void;
|
|
||||||
public function onGameComplete(state:GameState):Void;
|
|
||||||
public function onGameEvent(event:GameEvent):Void;
|
public function onGameEvent(event:GameEvent):Void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@:dispatcher(GameListener) class Game implements IGame implements EngineListener {
|
@:dispatcher(GameListener) class Game implements IGame implements EngineListener implements GameListener {
|
||||||
|
|
||||||
private static var TAG(default, never):String = "Game";
|
private static var TAG(default, never):String = "Game";
|
||||||
|
|
||||||
@@ -47,6 +45,7 @@ interface GameListener {
|
|||||||
this.config = configBundle.get(type);
|
this.config = configBundle.get(type);
|
||||||
this.engine = new Engine(config);
|
this.engine = new Engine(config);
|
||||||
engine.connect(this);
|
engine.connect(this);
|
||||||
|
connect(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function action(tankId:Int, action:TankAction):Void {
|
public function action(tankId:Int, action:TankAction):Void {
|
||||||
@@ -60,16 +59,16 @@ interface GameListener {
|
|||||||
case SHOT:
|
case SHOT:
|
||||||
var bullet = tank.shot();
|
var bullet = tank.shot();
|
||||||
if (bullet != null) {
|
if (bullet != null) {
|
||||||
engine.spawn(bullet);
|
gameEventSignal.emit(GameEvent.SPAWN(BULLET(bullet)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTeam(teamId:TeamId):Team {
|
public inline function getTeam(teamId:TeamId):Team {
|
||||||
return teams[teamId];
|
return teams[teamId];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPlayer(playerId:PlayerId):Player {
|
public inline function getPlayer(playerId:PlayerId):Player {
|
||||||
return teams[playerId.team].players[playerId.index];
|
return teams[playerId.team].players[playerId.index];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,11 +131,10 @@ interface GameListener {
|
|||||||
eagle.color = config.getColor(new PlayerId(eagle.team, -1));
|
eagle.color = config.getColor(new PlayerId(eagle.team, -1));
|
||||||
team.eagleId = eagle.id;
|
team.eagleId = eagle.id;
|
||||||
applyPoint(eagle, eaglePoint);
|
applyPoint(eagle, eaglePoint);
|
||||||
engine.spawn(eagle);
|
gameEventSignal.emit(GameEvent.SPAWN(EAGLE(eagle)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gameStartSignal.emit(state);
|
gameEventSignal.emit(GameEvent.START(state));
|
||||||
gameChangeSignal.emit(state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function spawn(task:SpawnTask):Void {
|
private function spawn(task:SpawnTask):Void {
|
||||||
@@ -147,11 +145,10 @@ interface GameListener {
|
|||||||
var tank = buildTank(task);
|
var tank = buildTank(task);
|
||||||
player.tankId = tank.id;
|
player.tankId = tank.id;
|
||||||
player.state.tank = tank.config.type;
|
player.state.tank = tank.config.type;
|
||||||
engine.spawn(tank);
|
gameEventSignal.emit(GameEvent.SPAWN(TANK(tank)));
|
||||||
gameChangeSignal.emit(state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function checkComplete() {
|
private function checkComplete():Void {
|
||||||
var actives:Array<TeamId> = [];
|
var actives:Array<TeamId> = [];
|
||||||
for (team in teams.iterator()) {
|
for (team in teams.iterator()) {
|
||||||
if (team.isAlive) {
|
if (team.isAlive) {
|
||||||
@@ -180,22 +177,10 @@ interface GameListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Timer.delay(function() {
|
Timer.delay(function() {
|
||||||
//gameChangeSignal.emit(state);
|
gameEventSignal.emit(GameEvent.COMPLETE(state, getTeam(winner)));
|
||||||
gameCompleteSignal.emit(state);
|
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onSpawn(entity:EntityType):Void {
|
|
||||||
switch entity {
|
|
||||||
case TANK(tank):
|
|
||||||
getPlayer(tank.playerId).control.start();
|
|
||||||
case BULLET(bullet):
|
|
||||||
getPlayer(bullet.playerId).state.shots++;
|
|
||||||
gameEventSignal.emit(GameEvent.SPAWN(BULLET(bullet)));
|
|
||||||
case _:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onCollision(entity:EntityType, with:EntityType):Void {
|
public function onCollision(entity:EntityType, with:EntityType):Void {
|
||||||
switch entity {
|
switch entity {
|
||||||
case EntityType.TANK(tank):
|
case EntityType.TANK(tank):
|
||||||
@@ -209,14 +194,12 @@ interface GameListener {
|
|||||||
case [TANK(tank), EAGLE(eagle)]:
|
case [TANK(tank), EAGLE(eagle)]:
|
||||||
tank.rect.lean(eagle.rect);
|
tank.rect.lean(eagle.rect);
|
||||||
case [BULLET(bullet), BULLET(other_bullet)]:
|
case [BULLET(bullet), BULLET(other_bullet)]:
|
||||||
engine.destroy(bullet);
|
gameEventSignal.emit(GameEvent.DESTROY(BULLET(bullet)));
|
||||||
engine.destroy(other_bullet);
|
gameEventSignal.emit(GameEvent.DESTROY(BULLET(other_bullet)));
|
||||||
case [BULLET(bullet), CELL(cell)]:
|
case [BULLET(bullet), CELL(cell)]:
|
||||||
engine.destroy(bullet);
|
|
||||||
gameEventSignal.emit(GameEvent.HIT(CELL(cell, bullet.tank, bullet)));
|
gameEventSignal.emit(GameEvent.HIT(CELL(cell, bullet.tank, bullet)));
|
||||||
|
gameEventSignal.emit(GameEvent.DESTROY(BULLET(bullet)));
|
||||||
case [TANK(tank), BONUS(bonus)]:
|
case [TANK(tank), BONUS(bonus)]:
|
||||||
applyBonus(tank, bonus);
|
|
||||||
engine.destroy(bonus);
|
|
||||||
gameEventSignal.emit(GameEvent.DESTROY(BONUS(bonus, tank, bonus.config.score)));
|
gameEventSignal.emit(GameEvent.DESTROY(BONUS(bonus, tank, bonus.config.score)));
|
||||||
case [BULLET(bullet), TANK(tank)]/* | [TANK(tank), BULLET(bullet)]*/:
|
case [BULLET(bullet), TANK(tank)]/* | [TANK(tank), BULLET(bullet)]*/:
|
||||||
if (bullet.tankId == tank.id || (!engine.config.game.friendlyFire && tank.playerId.team == bullet.playerId.team)) {
|
if (bullet.tankId == tank.id || (!engine.config.game.friendlyFire && tank.playerId.team == bullet.playerId.team)) {
|
||||||
@@ -234,54 +217,20 @@ interface GameListener {
|
|||||||
tank.config = engine.config.getTank(tank.config.downgrade);
|
tank.config = engine.config.getTank(tank.config.downgrade);
|
||||||
gameEventSignal.emit(GameEvent.HIT(TANK(tank, bullet.tank, bullet)));
|
gameEventSignal.emit(GameEvent.HIT(TANK(tank, bullet.tank, bullet)));
|
||||||
} else {
|
} else {
|
||||||
engine.destroy(tank);
|
var score = tank.config.score;
|
||||||
gameEventSignal.emit(GameEvent.DESTROY(TANK(tank, bullet.tank, bullet, tank.config.score)));
|
if (tank.playerId.team == bullet.playerId.team) {
|
||||||
|
score = Math.round(score * -0.5);
|
||||||
|
}
|
||||||
|
gameEventSignal.emit(GameEvent.DESTROY(TANK(tank, bullet.tank, bullet, score)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
engine.destroy(bullet);
|
gameEventSignal.emit(GameEvent.DESTROY(BULLET(bullet)));
|
||||||
}
|
}
|
||||||
case [BULLET(bullet), EAGLE(eagle)]:
|
case [BULLET(bullet), EAGLE(eagle)]:
|
||||||
engine.destroy(bullet);
|
|
||||||
if (!eagle.protect.active) {
|
if (!eagle.protect.active) {
|
||||||
eagle.death = true;
|
gameEventSignal.emit(GameEvent.DESTROY(EAGLE(eagle, bullet.tank, bullet, eagle.score)));
|
||||||
if (bullet.playerId.team != eagle.team) {
|
|
||||||
getPlayer(bullet.playerId).state.score += eagle.score;
|
|
||||||
}
|
}
|
||||||
checkComplete();
|
gameEventSignal.emit(GameEvent.DESTROY(BULLET(bullet)));
|
||||||
gameChangeSignal.emit(state);
|
|
||||||
gameEventSignal.emit(GameEvent.DESTROY(EAGLE(eagle, bullet.tank, bullet, eagle.config.score)));
|
|
||||||
}
|
|
||||||
case _:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onDestroy(entity:EntityType):Void {
|
|
||||||
switch (entity) {
|
|
||||||
case BULLET(bullet):
|
|
||||||
var tank:Tank = bullet.tank;
|
|
||||||
if (tank != null) tank.onDestroyBullet();
|
|
||||||
case TANK(tank):
|
|
||||||
var team = getTeam(tank.playerId.team);
|
|
||||||
var player = getPlayer(tank.playerId);
|
|
||||||
player.control.stop();
|
|
||||||
player.tankId = 0; //ToDo: ?
|
|
||||||
team.onDestroy(player.id);
|
|
||||||
var respawn:Bool = team.tryRespawn(player.id);
|
|
||||||
if (respawn) {
|
|
||||||
team.spawner.push(player.id);
|
|
||||||
}
|
|
||||||
if (!team.isAlive) {
|
|
||||||
checkComplete();
|
|
||||||
}
|
|
||||||
if (tank.bonus) {
|
|
||||||
spawnBonus();
|
|
||||||
}
|
|
||||||
// ToDo:
|
|
||||||
/*if (playerId != null) {
|
|
||||||
getPlayer(playerId).state.frags++;
|
|
||||||
getPlayer(playerId).state.score += tank.config.score * (tank.playerId.team == playerId.team ? 0 : 1);
|
|
||||||
}*/
|
|
||||||
gameChangeSignal.emit(state);
|
|
||||||
case _:
|
case _:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,9 +257,6 @@ interface GameListener {
|
|||||||
|
|
||||||
public function dispose():Void {
|
public function dispose():Void {
|
||||||
engine.dispose();
|
engine.dispose();
|
||||||
gameStartSignal.dispose();
|
|
||||||
gameChangeSignal.dispose();
|
|
||||||
gameCompleteSignal.dispose();
|
|
||||||
gameEventSignal.dispose();
|
gameEventSignal.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,23 +265,22 @@ interface GameListener {
|
|||||||
var bonus = new Bonus(bonusConfig);
|
var bonus = new Bonus(bonusConfig);
|
||||||
bonus.rect.x = Math.round(Math.random() * engine.map.width / engine.map.cellWidth) * engine.map.cellWidth;
|
bonus.rect.x = Math.round(Math.random() * engine.map.width / engine.map.cellWidth) * engine.map.cellWidth;
|
||||||
bonus.rect.y = Math.round(Math.random() * engine.map.height/ engine.map.cellHeight) * engine.map.cellHeight;
|
bonus.rect.y = Math.round(Math.random() * engine.map.height/ engine.map.cellHeight) * engine.map.cellHeight;
|
||||||
engine.spawn(bonus);
|
|
||||||
gameEventSignal.emit(GameEvent.SPAWN(BONUS(bonus)));
|
gameEventSignal.emit(GameEvent.SPAWN(BONUS(bonus)));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline private function alienTank(team:TeamId):Tank->Bool {
|
private inline function alienTank(team:TeamId):Tank->Bool {
|
||||||
return function(tank:Tank):Bool return team != tank.playerId.team;
|
return function(tank:Tank):Bool return team != tank.playerId.team;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function applyBonus(tank:Tank, bonus:Bonus):Void {
|
private function applyBonus(tank:Tank, bonus:Bonus):Void {
|
||||||
switch (bonus.config.type) {
|
switch (bonus.config.type) {
|
||||||
case "life":
|
case "life":
|
||||||
getPlayer(tank.playerId).state.life++;
|
changeLife(tank.playerId, 1);
|
||||||
case "star":
|
case "star":
|
||||||
upgradeTank(tank);
|
upgradeTank(tank);
|
||||||
case "grenade":
|
case "grenade":
|
||||||
for (t in engine.iterTanks(alienTank(tank.playerId.team))) {
|
for (t in engine.iterTanks(alienTank(tank.playerId.team))) {
|
||||||
engine.destroy(t);
|
gameEventSignal.emit(GameEvent.DESTROY(TANK(t, tank, null, 0)));
|
||||||
}
|
}
|
||||||
case "helmet":
|
case "helmet":
|
||||||
tank.protect.on(bonus.config.duration);
|
tank.protect.on(bonus.config.duration);
|
||||||
@@ -353,15 +298,11 @@ interface GameListener {
|
|||||||
case "gun":
|
case "gun":
|
||||||
upgradeTank(tank, 5);
|
upgradeTank(tank, 5);
|
||||||
case _:
|
case _:
|
||||||
engine.destroy(tank); // :-D
|
gameEventSignal.emit(GameEvent.DESTROY(TANK(tank, null, null, 0))); // :-D
|
||||||
}
|
}
|
||||||
if (bonus.config.score > 0) {
|
|
||||||
getPlayer(tank.playerId).state.score += bonus.config.score;
|
|
||||||
}
|
|
||||||
gameChangeSignal.emit(state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function upgradeTank(tank:Tank, level:Int = 1):Void {
|
private function upgradeTank(tank:Tank, level:Int = 1):Void {
|
||||||
if (tank.config.upgrade != null) {
|
if (tank.config.upgrade != null) {
|
||||||
while (level-- > 0 && tank.config.upgrade != null) {
|
while (level-- > 0 && tank.config.upgrade != null) {
|
||||||
tank.config = config.getTank(tank.config.upgrade);
|
tank.config = config.getTank(tank.config.upgrade);
|
||||||
@@ -371,7 +312,83 @@ interface GameListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function hitTank(tank:Tank):Void {
|
private function changeScore(playerId:PlayerId, score:Int):Void {
|
||||||
|
var player = getPlayer(playerId);
|
||||||
|
var team = getTeam(playerId.team);
|
||||||
|
player.state.score += score;
|
||||||
|
gameEventSignal.emit(GameEvent.CHANGE(PLAYER_SCORE(player, player.state.score)));
|
||||||
|
gameEventSignal.emit(GameEvent.CHANGE(TEAM_SCORE(team, state.getTeamScore(team.id))));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function changeLife(playerId:PlayerId, life:Int):Void {
|
||||||
|
var player = getPlayer(playerId);
|
||||||
|
player.state.life += life;
|
||||||
|
gameEventSignal.emit(GameEvent.CHANGE(PLAYER_LIFE(player, player.state.life)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function changeTeamLife(teamId:TeamId, life:Int):Void {
|
||||||
|
var team = getTeam(teamId);
|
||||||
|
team.state.life += life;
|
||||||
|
gameEventSignal.emit(GameEvent.CHANGE(TEAM_LIFE(team, team.state.life)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onGameEvent(event:GameEvent):Void {
|
||||||
|
switch event {
|
||||||
|
case GameEvent.SPAWN(EAGLE(eagle)):
|
||||||
|
engine.spawn(eagle);
|
||||||
|
case GameEvent.SPAWN(TANK(tank)):
|
||||||
|
engine.spawn(tank);
|
||||||
|
getPlayer(tank.playerId).control.start();
|
||||||
|
case GameEvent.SPAWN(BULLET(bullet)):
|
||||||
|
engine.spawn(bullet);
|
||||||
|
case GameEvent.SPAWN(BONUS(bonus)):
|
||||||
|
engine.spawn(bonus);
|
||||||
|
case GameEvent.DESTROY(EAGLE(eagle, who, wherewith, score)):
|
||||||
|
eagle.death = true;
|
||||||
|
if (score != 0) {
|
||||||
|
changeScore(who.playerId, score);
|
||||||
|
}
|
||||||
|
checkComplete();
|
||||||
|
case GameEvent.DESTROY(TANK(tank, who, wherewith, score)):
|
||||||
|
engine.destroy(tank);
|
||||||
|
var team = getTeam(tank.playerId.team);
|
||||||
|
var player = getPlayer(tank.playerId);
|
||||||
|
player.control.stop();
|
||||||
|
player.tankId = 0; //ToDo: ?
|
||||||
|
team.onDestroy(player.id);
|
||||||
|
if (player.state.life > 0) {
|
||||||
|
changeLife(player.id, -1);
|
||||||
|
} else if (team.state.life > 0) {
|
||||||
|
changeTeamLife(team.id, -1);
|
||||||
|
}
|
||||||
|
var respawn:Bool = team.tryRespawn(player.id);
|
||||||
|
if (respawn) {
|
||||||
|
team.spawner.push(player.id);
|
||||||
|
}
|
||||||
|
if (!team.isAlive) {
|
||||||
|
checkComplete();
|
||||||
|
}
|
||||||
|
if (tank.bonus && wherewith != null) {
|
||||||
|
spawnBonus();
|
||||||
|
}
|
||||||
|
if (score != 0) {
|
||||||
|
changeScore(who.playerId, score);
|
||||||
|
}
|
||||||
|
case GameEvent.DESTROY(CELL(cell, who, wherewith)):
|
||||||
|
|
||||||
|
case GameEvent.DESTROY(BONUS(bonus, who, score)):
|
||||||
|
engine.destroy(bonus);
|
||||||
|
applyBonus(who, bonus);
|
||||||
|
if (score != 0) {
|
||||||
|
changeScore(who.playerId, score);
|
||||||
|
}
|
||||||
|
case GameEvent.DESTROY(BULLET(bullet)):
|
||||||
|
engine.destroy(bullet);
|
||||||
|
var tank:Tank = bullet.tank;
|
||||||
|
if (tank != null) {
|
||||||
|
tank.onDestroyBullet();
|
||||||
|
}
|
||||||
|
case _:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,17 +23,21 @@ enum DestroyEvent {
|
|||||||
TANK(tank:Tank, who:Tank, wherewith:Bullet, score:Int);
|
TANK(tank:Tank, who:Tank, wherewith:Bullet, score:Int);
|
||||||
CELL(cell:GridCell, who:Tank, wherewith:Bullet);
|
CELL(cell:GridCell, who:Tank, wherewith:Bullet);
|
||||||
BONUS(bonus:Bonus, who:Tank, score:Int);
|
BONUS(bonus:Bonus, who:Tank, score:Int);
|
||||||
|
BULLET(bullet:Bullet);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ChangeEvent {
|
enum ChangeEvent {
|
||||||
SCORE(player:Player, value:Int);
|
PLAYER_SCORE(player:Player, value:Int);
|
||||||
PLAYER_LIFE(player:Player, value:Int);
|
PLAYER_LIFE(player:Player, value:Int);
|
||||||
|
TEAM_SCORE(team:Team, value:Int);
|
||||||
TEAM_LIFE(team:Team, value:Int);
|
TEAM_LIFE(team:Team, value:Int);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum GameEvent {
|
enum GameEvent {
|
||||||
|
START(state:GameState);
|
||||||
SPAWN(event:SpawnEvent);
|
SPAWN(event:SpawnEvent);
|
||||||
HIT(event:HitEvent);
|
HIT(event:HitEvent);
|
||||||
DESTROY(event:DestroyEvent);
|
DESTROY(event:DestroyEvent);
|
||||||
CHANGE(event:ChangeEvent);
|
CHANGE(event:ChangeEvent);
|
||||||
|
COMPLETE(state:GameState, winner:Team);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,24 @@
|
|||||||
package ru.m.tankz.game;
|
package ru.m.tankz.game;
|
||||||
|
|
||||||
|
import haxework.signal.Signal;
|
||||||
|
import ru.m.tankz.control.Control;
|
||||||
import ru.m.tankz.engine.Engine;
|
import ru.m.tankz.engine.Engine;
|
||||||
import ru.m.tankz.control.Control.TankAction;
|
import ru.m.tankz.game.Game;
|
||||||
|
import ru.m.tankz.Type;
|
||||||
|
|
||||||
interface IGame {
|
interface IGame {
|
||||||
public var engine(default, null):Engine;
|
public var engine(default, null):Engine;
|
||||||
|
public var gameEventSignal(default, null):Signal<GameEvent>;
|
||||||
|
|
||||||
|
public function connect(listener:GameListener):Void;
|
||||||
|
|
||||||
|
public function disconnect(listener:GameListener):Void;
|
||||||
|
|
||||||
|
public function start(state:GameState):Void;
|
||||||
|
|
||||||
public function action(tankId:Int, action:TankAction):Void;
|
public function action(tankId:Int, action:TankAction):Void;
|
||||||
|
|
||||||
|
public function getTeam(teamId:TeamId):Team;
|
||||||
|
|
||||||
|
public function getPlayer(playerId:PlayerId):Player;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,14 +37,8 @@ class Team {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onDestroy(playerId:PlayerId) {
|
public function onDestroy(playerId:PlayerId):Void {
|
||||||
active--;
|
active--;
|
||||||
var player:Player = players[playerId.index];
|
|
||||||
if (player.state.life > 0) {
|
|
||||||
player.state.life--;
|
|
||||||
} else {
|
|
||||||
state.life--;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToDo: eagle state?
|
// ToDo: eagle state?
|
||||||
|
|||||||
Reference in New Issue
Block a user