diff --git a/CHANGELOG.md b/CHANGELOG.md index 360dc26..442fc3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +0.17.0 +------ +* Improved `ResultFrame` + 0.16.0 ------ * Added `GamepadView` for sensor control diff --git a/WORK.md b/WORK.md index d6a82ea..0ff95a3 100644 --- a/WORK.md +++ b/WORK.md @@ -1,11 +1,8 @@ * **shovel** bonus with armor bricks * bonuses in dota/death mod * tanks and bullets speed balancing -* result frame update (next game select, only human player info) * network game series * map packs (create in editor, import in game, save imported in local storage) * update bots * improve bonuses system -* screen gamepad on mobiles -* resize render on mobiles * save human state in classic game diff --git a/src/common/haxe/ru/m/tankz/bonus/BaseBonus.hx b/src/common/haxe/ru/m/tankz/bonus/BaseBonus.hx new file mode 100644 index 0000000..b4a8526 --- /dev/null +++ b/src/common/haxe/ru/m/tankz/bonus/BaseBonus.hx @@ -0,0 +1,24 @@ +package ru.m.tankz.bonus; + +import ru.m.tankz.config.Config; +import ru.m.tankz.engine.IEngine; +import ru.m.tankz.game.IGame; +import ru.m.tankz.Type; + +class BaseBonus implements IBonus { + public var type(get, null):BonusType; + + private var config:BonusConfig; + + public function new(config:BonusConfig) { + this.config = config; + } + + private inline function get_type():BonusType { + return config != null ? config.type : ""; + } + + public function apply(playerId:PlayerId, game:IGame, engine:IEngine):Void { + throw "Not Implemented"; + } +} diff --git a/src/common/haxe/ru/m/tankz/bonus/DestroyTeamBonus.hx b/src/common/haxe/ru/m/tankz/bonus/DestroyTeamBonus.hx new file mode 100644 index 0000000..6a3b7c6 --- /dev/null +++ b/src/common/haxe/ru/m/tankz/bonus/DestroyTeamBonus.hx @@ -0,0 +1,21 @@ +package ru.m.tankz.bonus; + +import ru.m.tankz.core.Tank; +import ru.m.tankz.engine.IEngine; +import ru.m.tankz.game.GameEvent; +import ru.m.tankz.game.IGame; +import ru.m.tankz.Type; + +using ru.m.tankz.game.GameUtil; + +class DestroyTeamBonus extends BaseBonus { + + override public function apply(playerId:PlayerId, game:IGame, engine:IEngine):Void { + var tank:Tank = engine.getEntity(game.getPlayer(playerId).tankId); + if (tank != null) { + for (t in engine.iterTanks(tank.playerId.team.alienTank())) { + game.gameEventSignal.emit(DESTROY(TANK(t.id, {tankId: tank.id}))); + } + } + } +} diff --git a/src/common/haxe/ru/m/tankz/bonus/FreezeTeamBonus.hx b/src/common/haxe/ru/m/tankz/bonus/FreezeTeamBonus.hx new file mode 100644 index 0000000..afecac0 --- /dev/null +++ b/src/common/haxe/ru/m/tankz/bonus/FreezeTeamBonus.hx @@ -0,0 +1,18 @@ +package ru.m.tankz.bonus; + +import ru.m.tankz.engine.IEngine; +import ru.m.tankz.game.IGame; +import ru.m.tankz.Type; + +using ru.m.tankz.game.GameUtil; + +class FreezeTeamBonus extends BaseBonus { + + override public function apply(playerId:PlayerId, game:IGame, engine:IEngine):Void { + for (team in game.teams) { + if (team.id != playerId.team) { + game.freezeTeam(engine, team.id, config.duration); + } + } + } +} diff --git a/src/common/haxe/ru/m/tankz/bonus/IBonus.hx b/src/common/haxe/ru/m/tankz/bonus/IBonus.hx new file mode 100644 index 0000000..51ca089 --- /dev/null +++ b/src/common/haxe/ru/m/tankz/bonus/IBonus.hx @@ -0,0 +1,10 @@ +package ru.m.tankz.bonus; + +import ru.m.tankz.engine.IEngine; +import ru.m.tankz.game.IGame; +import ru.m.tankz.Type; + +interface IBonus { + public var type(get, null):BonusType; + public function apply(playerId:PlayerId, game:IGame, engine:IEngine):Void; +} diff --git a/src/common/haxe/ru/m/tankz/bonus/LifeBonus.hx b/src/common/haxe/ru/m/tankz/bonus/LifeBonus.hx new file mode 100644 index 0000000..4b0d7e4 --- /dev/null +++ b/src/common/haxe/ru/m/tankz/bonus/LifeBonus.hx @@ -0,0 +1,14 @@ +package ru.m.tankz.bonus; + +import ru.m.tankz.engine.IEngine; +import ru.m.tankz.game.IGame; +import ru.m.tankz.Type; + +using ru.m.tankz.game.GameUtil; + +class LifeBonus extends BaseBonus { + + override public function apply(playerId:PlayerId, game:IGame, engine:IEngine):Void { + game.changeLife(playerId, 1); + } +} diff --git a/src/common/haxe/ru/m/tankz/bonus/ProtectEagleBonus.hx b/src/common/haxe/ru/m/tankz/bonus/ProtectEagleBonus.hx new file mode 100644 index 0000000..07c7c43 --- /dev/null +++ b/src/common/haxe/ru/m/tankz/bonus/ProtectEagleBonus.hx @@ -0,0 +1,20 @@ +package ru.m.tankz.bonus; + +import ru.m.tankz.core.Eagle; +import ru.m.tankz.engine.IEngine; +import ru.m.tankz.game.IGame; +import ru.m.tankz.game.Team; +import ru.m.tankz.Type; + +using ru.m.tankz.game.GameUtil; + +class ProtectEagleBonus extends BaseBonus { + + override public function apply(playerId:PlayerId, game:IGame, engine:IEngine):Void { + var team:Team = game.getTeam(playerId.team); + if (team.eagleId > 0) { + var eagle:Eagle = cast(engine.entities[team.eagleId], Eagle); + game.protectEagle(eagle, config.duration); + } + } +} diff --git a/src/common/haxe/ru/m/tankz/bonus/ProtectTankBonus.hx b/src/common/haxe/ru/m/tankz/bonus/ProtectTankBonus.hx new file mode 100644 index 0000000..b265735 --- /dev/null +++ b/src/common/haxe/ru/m/tankz/bonus/ProtectTankBonus.hx @@ -0,0 +1,18 @@ +package ru.m.tankz.bonus; + +import ru.m.tankz.core.Tank; +import ru.m.tankz.engine.IEngine; +import ru.m.tankz.game.IGame; +import ru.m.tankz.Type; + +using ru.m.tankz.game.GameUtil; + +class ProtectTankBonus extends BaseBonus { + + override public function apply(playerId:PlayerId, game:IGame, engine:IEngine):Void { + var tank:Tank = engine.getEntity(game.getPlayer(playerId).tankId); + if (tank != null) { + game.protectTank(tank, config.duration); + } + } +} diff --git a/src/common/haxe/ru/m/tankz/bonus/UpgradeTankBonus.hx b/src/common/haxe/ru/m/tankz/bonus/UpgradeTankBonus.hx new file mode 100644 index 0000000..f71a494 --- /dev/null +++ b/src/common/haxe/ru/m/tankz/bonus/UpgradeTankBonus.hx @@ -0,0 +1,18 @@ +package ru.m.tankz.bonus; + +import ru.m.tankz.core.Tank; +import ru.m.tankz.engine.IEngine; +import ru.m.tankz.game.IGame; +import ru.m.tankz.Type; + +using ru.m.tankz.game.GameUtil; + +class UpgradeTankBonus extends BaseBonus { + + override public function apply(playerId:PlayerId, game:IGame, engine:IEngine):Void { + var tank:Tank = engine.getEntity(game.getPlayer(playerId).tankId); + if (tank != null) { + game.upgradeTank(tank, config.value); + } + } +} diff --git a/src/common/haxe/ru/m/tankz/config/Config.hx b/src/common/haxe/ru/m/tankz/config/Config.hx index c1efd87..6f75d6d 100644 --- a/src/common/haxe/ru/m/tankz/config/Config.hx +++ b/src/common/haxe/ru/m/tankz/config/Config.hx @@ -64,6 +64,7 @@ typedef TankConfig = { typedef BonusConfig = { var type:BonusType; @:optional var duration:Null; + @:optional var value:Null; @:optinal var score:Null; } diff --git a/src/common/haxe/ru/m/tankz/game/Game.hx b/src/common/haxe/ru/m/tankz/game/Game.hx index ea76049..3cf10c5 100644 --- a/src/common/haxe/ru/m/tankz/game/Game.hx +++ b/src/common/haxe/ru/m/tankz/game/Game.hx @@ -9,6 +9,7 @@ import ru.m.tankz.control.NoneControlFactory; import ru.m.tankz.control.PlayerControl; import ru.m.tankz.core.EntityType; import ru.m.tankz.engine.IEngine; +import ru.m.tankz.engine.ITicker; import ru.m.tankz.game.GameEvent; import ru.m.tankz.game.GameState; import ru.m.tankz.game.IGame; @@ -28,6 +29,7 @@ import ru.m.tankz.Type; public var controlFactory(default, null):IControlFactory; public var pause(default, set):Bool; public var controls(default, null):Map; + public var ticker(default, null):ITicker; @:provide var configBundle:IConfigBundle; diff --git a/src/common/haxe/ru/m/tankz/game/GameRunner.hx b/src/common/haxe/ru/m/tankz/game/GameRunner.hx index a7a0233..6a5f037 100644 --- a/src/common/haxe/ru/m/tankz/game/GameRunner.hx +++ b/src/common/haxe/ru/m/tankz/game/GameRunner.hx @@ -2,6 +2,13 @@ package ru.m.tankz.game; import ru.m.geom.Line; import ru.m.geom.Point; +import ru.m.tankz.bonus.DestroyTeamBonus; +import ru.m.tankz.bonus.FreezeTeamBonus; +import ru.m.tankz.bonus.IBonus; +import ru.m.tankz.bonus.LifeBonus; +import ru.m.tankz.bonus.ProtectEagleBonus; +import ru.m.tankz.bonus.ProtectTankBonus; +import ru.m.tankz.bonus.UpgradeTankBonus; import ru.m.tankz.control.Control; import ru.m.tankz.core.Bonus; import ru.m.tankz.core.Bullet; @@ -16,17 +23,37 @@ import ru.m.tankz.game.Spawner; import ru.m.tankz.Type; import ru.m.Timer; +using ru.m.tankz.game.GameUtil; + class GameRunner extends Game implements EngineListener { private var timer:Timer; private var builder:EntityBuilder; + private var bonuses:Map; public function new(start:Start) { super(start.state.type); this.level = start.level; this.state = start.state; - this.builder = new EntityBuilder(config); - this.engine = new Engine(config, level.size); - this.engine.connect(this); + builder = new EntityBuilder(config); + bonuses = new Map(); + for (bonus in buildBonuses()) { + bonuses[bonus.type] = bonus; + } + engine = new Engine(config, level.size); + ticker = engine.ticker; + engine.connect(this); + } + + private function buildBonuses():Array { + return [ + new ProtectEagleBonus(config.getBonus("shovel")), + new ProtectTankBonus(config.getBonus("helmet")), + new LifeBonus(config.getBonus("life")), + new UpgradeTankBonus(config.getBonus("star")), + new UpgradeTankBonus(config.getBonus("gun")), + new DestroyTeamBonus(config.getBonus("grenade")), + new FreezeTeamBonus(config.getBonus("clock")), + ]; } override function changePause(value:Bool):Void { @@ -92,52 +119,6 @@ class GameRunner extends Game implements EngineListener { } } - private function protectEagle(eagle:Eagle, duration:Float):Void { - eagle.protect = true; - gameEventSignal.emit(CHANGE(EAGLE_PROTECT(eagle.id, eagle.protect))); - engine.ticker.emit(function() { - eagle.protect = false; - gameEventSignal.emit(CHANGE(EAGLE_PROTECT(eagle.id, eagle.protect))); - }, Std.int(duration * 1000)); - } - - private function protectTank(tank:Tank, duration:Float):Void { - tank.protect = true; - gameEventSignal.emit(CHANGE(TANK_PROTECT(tank.id, tank.protect))); - engine.ticker.emit(function() { - tank.protect = false; - gameEventSignal.emit(CHANGE(TANK_PROTECT(tank.id, tank.protect))); - }, Std.int(duration * 1000)); - } - - private function freezeTank(tank:Tank, duration:Float):Void { - tank.freezing = true; - tank.stop(); - gameEventSignal.emit(STOP(TANK(tank.id))); - gameEventSignal.emit(CHANGE(TANK_FREEZE(tank.id, tank.freezing))); - engine.ticker.emit(function() { - tank.freezing = false; - gameEventSignal.emit(CHANGE(TANK_FREEZE(tank.id, tank.freezing))); - }, Std.int(duration * 1000)); - } - - private function freezeTeam(teamId:TeamId, duration:Float):Void { - getTeam(teamId).freezing = true; - for (tank in engine.iterTanks(teamTank(teamId))) { - tank.freezing = true; - tank.stop(); - gameEventSignal.emit(STOP(TANK(tank.id))); - gameEventSignal.emit(CHANGE(TANK_FREEZE(tank.id, tank.freezing))); - } - engine.ticker.emit(function() { - getTeam(teamId).freezing = false; - for (tank in engine.iterTanks(teamTank(teamId))) { - tank.freezing = false; - gameEventSignal.emit(CHANGE(TANK_FREEZE(tank.id, tank.freezing))); - } - }, Std.int(duration * 1000)); - } - private function checkComplete():Void { var actives:Array = []; for (team in teams.iterator()) { @@ -176,14 +157,6 @@ class GameRunner extends Game implements EngineListener { } } - private inline function emitTankMove(tank:Tank):Void { - gameEventSignal.emit(MOVE(TANK(tank.id, {x:tank.rect.x, y:tank.rect.y, direction:tank.rect.direction}))); - } - - private inline function emitTankChange(tank:Tank):Void { - gameEventSignal.emit(CHANGE(TANK(tank.id, tank.config.type, tank.hits, tank.bonus))); - } - public function onCollision(entity:EntityType, with:EntityType):Void { switch entity { case EntityType.TANK(tank): @@ -280,78 +253,12 @@ class GameRunner extends Game implements EngineListener { gameEventSignal.emit(EventUtil.buildBonusSpawn(bonus)); } - private inline function teamTank(team:TeamId):Tank->Bool { - return function(tank:Tank):Bool return team == tank.playerId.team; - } - - private inline function alienTank(team:TeamId):Tank->Bool { - return function(tank:Tank):Bool return team != tank.playerId.team; - } - private function applyBonus(tank:Tank, bonus:Bonus):Void { - switch (bonus.config.type) { - case "life": - changeLife(tank.playerId, 1); - case "star": - upgradeTank(tank); - case "grenade": - for (t in engine.iterTanks(alienTank(tank.playerId.team))) { - gameEventSignal.emit(DESTROY(TANK(t.id, {tankId: tank.id}))); - } - case "helmet": - protectTank(tank, bonus.config.duration); - case "clock": - for (team in teams) { - if (team.id != tank.playerId.team) { - freezeTeam(team.id, bonus.config.duration); - } - } - /*for (t in engine.iterTanks(alienTank(tank.playerId.team))) { - freezeTank(t, bonus.config.duration); - }*/ - case "shovel": - // ToDo: protect eagle/area - var team:Team = teams[tank.playerId.team]; - if (team.eagleId > 0) { - var eagle:Eagle = cast(engine.entities[team.eagleId], Eagle); - protectEagle(eagle, bonus.config.duration); - } - case "gun": - upgradeTank(tank, 5); - case _: - gameEventSignal.emit(DESTROY(TANK(tank.id, {tankId: tank.id}))); // :-D - } - } - - private function upgradeTank(tank:Tank, level:Int = 1):Void { - if (tank.config.upgrade != null) { - while (level-- > 0 && tank.config.upgrade != null) { - tank.config = config.getTank(tank.config.upgrade); - } + if (bonuses.exists(bonus.config.type)) { + bonuses[bonus.config.type].apply(tank.playerId, this, engine); } else { - tank.hits++; + gameEventSignal.emit(DESTROY(TANK(tank.id, {tankId: tank.id}))); // :-D } - emitTankChange(tank); - } - - private function changeScore(playerId:PlayerId, score:Int):Void { - var player = getPlayer(playerId); - var team = getTeam(playerId.team); - player.state.score += score; - gameEventSignal.emit(CHANGE(PLAYER_SCORE(playerId, player.state.score))); - gameEventSignal.emit(CHANGE(TEAM_SCORE(playerId.team, state.getTeamScore(team.id)))); - } - - private function changeLife(playerId:PlayerId, life:Int):Void { - var player = getPlayer(playerId); - player.state.life += life; - gameEventSignal.emit(CHANGE(PLAYER_LIFE(playerId, player.state.life))); - } - - private function changeTeamLife(teamId:TeamId, life:Int):Void { - var team = getTeam(teamId); - team.state.life += life; - gameEventSignal.emit(CHANGE(TEAM_LIFE(teamId, team.state.life))); } override public function onGameEvent(event:GameEvent):Void { @@ -394,7 +301,9 @@ class GameRunner extends Game implements EngineListener { var team = getTeam(teamId); team.eagleId = id; case SPAWN(TANK(id, rect, playerId, info)): - getPlayer(playerId).state.tank = info; + var player = getPlayer(playerId); + player.tankId = id; + player.state.tank = info; case SPAWN(BULLET(_, _, playerId, _)): getPlayer(playerId).bullets++; case DESTROY(EAGLE(id, shot)): diff --git a/src/common/haxe/ru/m/tankz/game/GameUtil.hx b/src/common/haxe/ru/m/tankz/game/GameUtil.hx new file mode 100644 index 0000000..4ceca65 --- /dev/null +++ b/src/common/haxe/ru/m/tankz/game/GameUtil.hx @@ -0,0 +1,106 @@ +package ru.m.tankz.game; + +import ru.m.tankz.engine.IEngine; +import ru.m.tankz.core.Eagle; +import ru.m.tankz.core.Tank; +import ru.m.tankz.game.GameEvent; +import ru.m.tankz.game.IGame; +import ru.m.tankz.Type; + +using ru.m.tankz.game.GameUtil; + +class GameUtil { + + private static inline function teamTank(team:TeamId):Tank->Bool { + return function(tank:Tank):Bool return team == tank.playerId.team; + } + + public static inline function alienTank(team:TeamId):Tank->Bool { + return function(tank:Tank):Bool return team != tank.playerId.team; + } + + public static inline function emitTankMove(game:IGame, tank:Tank):Void { + game.gameEventSignal.emit(MOVE(TANK(tank.id, {x:tank.rect.x, y:tank.rect.y, direction:tank.rect.direction}))); + } + + public static inline function emitTankChange(game:IGame, tank:Tank):Void { + game.gameEventSignal.emit(CHANGE(TANK(tank.id, tank.config.type, tank.hits, tank.bonus))); + } + + public static function protectTank(game:IGame, tank:Tank, duration:Float):Void { + tank.protect = true; + game.gameEventSignal.emit(CHANGE(TANK_PROTECT(tank.id, tank.protect))); + game.ticker.emit(function() { + tank.protect = false; + game.gameEventSignal.emit(CHANGE(TANK_PROTECT(tank.id, tank.protect))); + }, Std.int(duration * 1000)); + } + + public static function protectEagle(game:IGame, eagle:Eagle, duration:Float):Void { + eagle.protect = true; + game.gameEventSignal.emit(CHANGE(EAGLE_PROTECT(eagle.id, eagle.protect))); + game.ticker.emit(function() { + eagle.protect = false; + game.gameEventSignal.emit(CHANGE(EAGLE_PROTECT(eagle.id, eagle.protect))); + }, Std.int(duration * 1000)); + } + + public static function freezeTank(game:IGame, tank:Tank, duration:Float):Void { + tank.freezing = true; + tank.stop(); + game.gameEventSignal.emit(STOP(TANK(tank.id))); + game.gameEventSignal.emit(CHANGE(TANK_FREEZE(tank.id, tank.freezing))); + game.ticker.emit(function() { + tank.freezing = false; + game.gameEventSignal.emit(CHANGE(TANK_FREEZE(tank.id, tank.freezing))); + }, Std.int(duration * 1000)); + } + + public static function freezeTeam(game:IGame, engine:IEngine, teamId:TeamId, duration:Float):Void { + game.getTeam(teamId).freezing = true; + for (tank in engine.iterTanks(teamId.teamTank())) { + tank.freezing = true; + tank.stop(); + game.gameEventSignal.emit(STOP(TANK(tank.id))); + game.gameEventSignal.emit(CHANGE(TANK_FREEZE(tank.id, tank.freezing))); + } + game.ticker.emit(function() { + game.getTeam(teamId).freezing = false; + for (tank in engine.iterTanks(teamId.teamTank())) { + tank.freezing = false; + game.gameEventSignal.emit(CHANGE(TANK_FREEZE(tank.id, tank.freezing))); + } + }, Std.int(duration * 1000)); + } + + public static function changeLife(game:IGame, playerId:PlayerId, life:Int):Void { + var player = game.getPlayer(playerId); + player.state.life += life; + game.gameEventSignal.emit(CHANGE(PLAYER_LIFE(playerId, player.state.life))); + } + + public static function changeTeamLife(game:IGame, teamId:TeamId, life:Int):Void { + var team = game.getTeam(teamId); + team.state.life += life; + game.gameEventSignal.emit(CHANGE(TEAM_LIFE(teamId, team.state.life))); + } + + public static function changeScore(game:IGame, playerId:PlayerId, score:Int):Void { + var player = game.getPlayer(playerId); + var team = game.getTeam(playerId.team); + player.state.score += score; + game.gameEventSignal.emit(CHANGE(PLAYER_SCORE(playerId, player.state.score))); + game.gameEventSignal.emit(CHANGE(TEAM_SCORE(playerId.team, game.state.getTeamScore(team.id)))); + } + + public static function upgradeTank(game:IGame, tank:Tank, level:Int = 1):Void { + if (tank.config.upgrade != null) { + while (level-- > 0 && tank.config.upgrade != null) { + tank.config = game.config.getTank(tank.config.upgrade); + } + } else { + tank.hits++; + } + game.emitTankChange(tank); + } +} diff --git a/src/common/haxe/ru/m/tankz/game/IGame.hx b/src/common/haxe/ru/m/tankz/game/IGame.hx index 6d5ee0b..5708757 100644 --- a/src/common/haxe/ru/m/tankz/game/IGame.hx +++ b/src/common/haxe/ru/m/tankz/game/IGame.hx @@ -1,5 +1,6 @@ package ru.m.tankz.game; +import ru.m.tankz.engine.ITicker; import haxework.signal.Signal; import ru.m.tankz.config.Config; import ru.m.tankz.control.Control; @@ -16,6 +17,7 @@ interface IGame extends GameListener { public var controlFactory(default, null):IControlFactory; public var pause(default, set):Bool; public var controls(default, null):Map; + public var ticker(default, null):ITicker; public var gameEventSignal(default, null):Signal; diff --git a/src/common/haxe/ru/m/tankz/game/record/GamePlayer.hx b/src/common/haxe/ru/m/tankz/game/record/GamePlayer.hx index 31fb51c..1c9336b 100644 --- a/src/common/haxe/ru/m/tankz/game/record/GamePlayer.hx +++ b/src/common/haxe/ru/m/tankz/game/record/GamePlayer.hx @@ -9,7 +9,6 @@ class GamePlayer extends Game { private var record:GameRecord; private var data:Array; - private var ticker:Ticker; public function new(record:GameRecord) { super(record.state.type); diff --git a/src/common/resources/config/classic.yaml b/src/common/resources/config/classic.yaml index 8a77ec6..4f1bf6d 100644 --- a/src/common/resources/config/classic.yaml +++ b/src/common/resources/config/classic.yaml @@ -152,8 +152,8 @@ bonuses: - {score: 500, type: helmet, duration: 20} - {score: 500, type: life} - {score: 500, type: shovel, duration: 10} - - {score: 500, type: star} - - {score: 500, type: gun} + - {score: 500, type: star, value: 1} + - {score: 500, type: gun, value: 5} presets: - id: 0