diff --git a/res/css/main.css b/res/css/main.css index f71bab5..d94738e 100644 --- a/res/css/main.css +++ b/res/css/main.css @@ -94,28 +94,63 @@ div.menu { /* Battle view markup (TEMP) * ---------------------------------------------------------------------------------------------------------------------- */ -div.toolbox { - float: right; +div.game_over { + position: fixed; + width: 100%; + height: 40%; + margin: 30% 0px 30% 0px; + + opacity: 0.6; + background: #444444; + border-top: 1px solid #000000; + border-bottom: 1px solid #000000; + + font-size: 18px; + font-weight: bold; } -div.toolbox div.timers { - clear: right; +div.toolbox { float: right; - margin: 4px 10px 4px 10px; - padding: 4px; - border: 1px solid #444444; } - div.toolbox div.timers div.timer { - color: #990000; + + div.toolbox div.player { + clear: right; + float: right; + margin: 4px 10px 4px 10px; + padding: 4px; + border: 1px solid #444444; + + font-size: 13px; } -div.toolbox div.selected { - clear: right; - float: right; - margin: 4px 10px 4px 10px; - padding: 4px; - border: 1px solid #444444; -} + div.toolbox div.timers { + clear: right; + float: right; + margin: 4px 10px 4px 10px; + padding: 4px; + border: 1px solid #444444; + } + div.toolbox div.timers div.timer { + color: #990000; + } + div.toolbox div.timers div.action { + margin: 4px; + color: #0000ff; + font-size: 11px; + cursor: pointer; + } + div.toolbox div.timers div.action:hover { + font-weight: bold; + text-decoration: underline; + } + + div.toolbox div.selected { + clear: right; + float: right; + margin: 4px 10px 4px 10px; + padding: 4px; + border: 1px solid #444444; + } div.grid { @@ -149,28 +184,47 @@ div.grid { width: 44px; height: 44px; } + div.grid div.grid_row div.tile_selected { background: url('../graphics/battle/tile_selected.png') top left no-repeat; } - div.grid div.grid_row div.tile_move_near { - background: url('../graphics/battle/tile_move_near.png') top left no-repeat; + div.grid div.grid_row div.tile_move { + background: url('../graphics/battle/tile_move.png') top left no-repeat; } - div.grid div.grid_row div.tile_move_far { - background: url('../graphics/battle/tile_move_far.png') top left no-repeat; + div.grid div.grid_row div.tile_range { + background: url('../graphics/battle/tile_range.png') top left no-repeat; } div.grid div.grid_row div.tile_attack { background: url('../graphics/battle/tile_attack.png') top left no-repeat; } div.grid div.grid_row div.tile div.unit { - float: left; - width: 32px; - height: 38px; + position: relative; + width: 40px; + height: 40px; + margin: 0px -40px -40px 4px; - margin-left: 6px; - margin-top: -8px; + background-position: center center; + background-repeat: no-repeat; + } + div.grid div.grid_row div.tile div.unit_player { + background-image: url('../graphics/battle/unit_player.gif'); + } + div.grid div.grid_row div.tile div.unit_enemy { + background-image: url('../graphics/battle/unit_enemy.gif'); + } - background: url('../graphics/battle/ofzza.gif') top left no-repeat; + div.grid div.grid_row div.tile div.distance { + position: relative; + width: 44px; + margin: 32px 0px 0px 0px; + + text-align: center; + font-size: 9px; + color: #ffffff; + } + div.grid div.grid_row div.tile:hover div.distance { + color: #000000; } div.grid div.grid_row div.tile div.info { diff --git a/res/graphics/battle/ofzza.gif b/res/graphics/battle/ofzza.gif deleted file mode 100644 index 33b07ce..0000000 Binary files a/res/graphics/battle/ofzza.gif and /dev/null differ diff --git a/res/graphics/battle/sprite.png b/res/graphics/battle/sprite.png deleted file mode 100644 index 7d01c38..0000000 Binary files a/res/graphics/battle/sprite.png and /dev/null differ diff --git a/res/graphics/battle/tile.psd b/res/graphics/battle/tile.psd deleted file mode 100644 index 048aa31..0000000 Binary files a/res/graphics/battle/tile.psd and /dev/null differ diff --git a/res/graphics/battle/tile_move_near.png b/res/graphics/battle/tile_move.png similarity index 100% rename from res/graphics/battle/tile_move_near.png rename to res/graphics/battle/tile_move.png diff --git a/res/graphics/battle/tile_move_far.png b/res/graphics/battle/tile_range.png similarity index 100% rename from res/graphics/battle/tile_move_far.png rename to res/graphics/battle/tile_range.png diff --git a/res/graphics/battle/unit_enemy.gif b/res/graphics/battle/unit_enemy.gif new file mode 100644 index 0000000..8d79c6c Binary files /dev/null and b/res/graphics/battle/unit_enemy.gif differ diff --git a/res/graphics/battle/unit_player.gif b/res/graphics/battle/unit_player.gif new file mode 100644 index 0000000..f31a2be Binary files /dev/null and b/res/graphics/battle/unit_player.gif differ diff --git a/res/js/bt_config.js b/res/js/bt_config.js index 391d4f7..2189960 100644 --- a/res/js/bt_config.js +++ b/res/js/bt_config.js @@ -9,23 +9,55 @@ // Configuration namespace // --------------------------------------------------------------------------------------------------------------------- -// Holds root path of URL where application is deployed -bt.config.urls = { - clientUrl : '/battle/static/btjs2/', - servicesUrl : '/battle/' -} + // Configure root path of URL where application is deployed + // ----------------------------------------------------------------------------------------------------------------- + bt.config.urls = { + // Set client-side URL path + clientUrl : '/battle/static/btjs2/', + // Set server-side URL path + servicesUrl : '/battle/' + } -// Set poling -bt.config.poling.minimalIntervalBetweenPolls = 2000; + // Set poling + // ----------------------------------------------------------------------------------------------------------------- -// Set debugging options -bt.debugging.events.publishToConsole = false; -bt.debugging.model.verifyModelConstructors = true; + // Set value for minimal time interval between polling same service in [ms] + bt.config.poling.minimalIntervalBetweenPolls = 2000; + + // Set debugging options + // ----------------------------------------------------------------------------------------------------------------- + + // Set if events are pushed to console + bt.debugging.events.publishToConsole = true; + // Set if model constructors will test received properties + bt.debugging.model.verifyModelConstructors = true; // BattleField configuration namespace // --------------------------------------------------------------------------------------------------------------------- -bt.config.game.battle.styles.selected = 'tile_selected'; -bt.config.game.battle.styles.move_near = 'tile_move_near'; -bt.config.game.battle.styles.move_far = 'tile_move_far'; -bt.config.game.battle.styles.attack = 'tile_attack'; + // Set CSS Styles + // ----------------------------------------------------------------------------------------------------------------- + + // Selected tile CSS class name + bt.config.game.battle.styles.selected = 'tile_selected'; + // Selected tile CSS class name + bt.config.game.battle.styles.move = 'tile_move'; + // Selected tile CSS class name + bt.config.game.battle.styles.range = 'tile_range'; + // Selected tile CSS class name + bt.config.game.battle.styles.attack = 'tile_attack'; + + // Player's unit CSS class name + bt.config.game.battle.styles.player = 'unit_player'; + // Enemy unit CSS class name + bt.config.game.battle.styles.enemy = 'unit_enemy'; + + // Configure actions + // ----------------------------------------------------------------------------------------------------------------- + + // Set unit's move radius + bt.config.game.battle.actions.moveRadius = 2; + // Set if actions and movement can pass through other units + bt.config.game.battle.actions.jumpUnits = true; + // Set if player can attack his own units + bt.config.game.battle.actions.friendlyFire = true; \ No newline at end of file diff --git a/res/js/bt_game_battle.js b/res/js/bt_game_battle.js index 8bcfc1b..53e9050 100644 --- a/res/js/bt_game_battle.js +++ b/res/js/bt_game_battle.js @@ -58,17 +58,23 @@ bt.services.battleService = app.factory('BattleService', function ($jsonRpc, $in // Map service monitoring methods battleService.monitoring = { + // Starts continuous monitoring of service monitorTimeLeft : function() { $interval.set('battleService.timers.update', function() { bt.game.battle.timers.update(battleService); }, 1000); $interval.start('battleService.timers.update'); + $interval.set('battleService.states.lastState', function() { bt.game.battle.battleField.update(battleService); }, 4000); + $interval.start('battleService.states.lastState'); }, + // Stops continuous monitoring of service stopMonitoring : function() { $interval.clear('battleService.timers.update'); + $interval.clear('battleService.states.lastState'); } }; // Map service initialization methods battleService.initialization = { + // Gets initial state from service initialize : function() { battleService.calls.initialState( // Parameters @@ -81,6 +87,8 @@ bt.services.battleService = app.factory('BattleService', function ($jsonRpc, $in bt.services.battleService.BattleFieldUpdated.dispatch({ message: 'Response from "BattleService.init_state().', data : data }); // Initialize view-model's battleField from response bt.game.battle.model.battleField = new bt.model.definitions.battle.battleField(data); + // Get last state + bt.game.battle.battleField.update(battleService); }, // On error callback function(data) { @@ -89,11 +97,86 @@ bt.services.battleService = app.factory('BattleService', function ($jsonRpc, $in } ); }, + // Destroys model gotten from service destroy : function() { delete bt.game.battle.model.battleField; } }; + // Map service unit actions methods + battleService.actions = { + // Passes turn to other player + pass : function() { + battleService.calls.processAction( + // Parameters + [[ 0, 'pass', [] ]], + // On successfull load callback + function(data) { + // Fire events + bt.services.battleService.Called.dispatch({ message: 'Response from "BattleService.process_action().', data : data }); + bt.services.battleService.BattleFieldAction_Pass.dispatch({ message: 'Response from "BattleService.process_action().', data : data }); + // Refresh timers + bt.game.battle.timers.query(battleService); + }, + // On error callback + function(data) { + // Fire events + bt.services.battleService.Error.dispatch({ message: 'Error calling "BattleService.process_action()"!', data : data }); + } + ) + }, + // Moves unit ( unit object | unit id) to new location ( {x, y} ) + move : function(unit, targetLocation) { + var unitId = (angular.isNumber(unit) ? unit : unit.id); + battleService.calls.processAction( + // Parameters + [[ unitId, 'move', [targetLocation.x, targetLocation.y] ]], + // On successfull load callback + function(data) { + // Fire events + bt.services.battleService.Called.dispatch({ message: 'Response from "BattleService.process_action().', data : data }); + bt.services.battleService.BattleFieldAction_Move.dispatch({ message: 'Response from "BattleService.process_action().', data : data }); + // Refresh timers + bt.game.battle.timers.query(battleService); + // Process move response + bt.game.battle.battleField.actions._processMove(data); + }, + // On error callback + function(data) { + // Fire events + bt.services.battleService.Error.dispatch({ message: 'Error calling "BattleService.process_action()"!', data : data }); + // TODO: Remove alert! + alert(data); + } + ) + }, + // Attacks unit ( unit object | unit id) at location ( {x, y} ) + attack : function(unit, targetLocation) { + var unitId = (angular.isNumber(unit) ? unit : unit.id); + battleService.calls.processAction( + // Parameters + [[ unitId, 'attack', [targetLocation.x, targetLocation.y] ]], + // On successfull load callback + function(data) { + // Fire events + bt.services.battleService.Called.dispatch({ message: 'Response from "BattleService.process_action().', data : data }); + bt.services.battleService.BattleFieldAction_Attack.dispatch({ message: 'Response from "BattleService.process_action().', data : data }); + // Refresh timers + bt.game.battle.timers.query(battleService); + // Process move response + bt.game.battle.battleField.actions._processAttack(data); + }, + // On error callback + function(data) { + // Fire events + bt.services.battleService.Error.dispatch({ message: 'Error calling "BattleService.process_action()"!', data : data }); + // TODO: Remove alert! + alert(data); + } + ) + } + } + // Return service mapping return battleService; }); @@ -111,10 +194,19 @@ bt.events.define(bt.services.battleService, 'Error'); // "Service call successfull" event bt.events.define(bt.services.battleService, 'Updated'); +// "Battle field new turn" event +bt.events.define(bt.services.battleService, 'BattleField_NewTurn'); + // "Battle field initialized" event bt.events.define(bt.services.battleService, 'BattleFieldInitialized'); // "Battle field updated" event bt.events.define(bt.services.battleService, 'BattleFieldUpdated'); +// "Battle field pass action" event +bt.events.define(bt.services.battleService, 'BattleFieldAction_Pass'); +// "Battle field move action" event +bt.events.define(bt.services.battleService, 'BattleFieldAction_Move'); +// "Battle field attack action" event +bt.events.define(bt.services.battleService, 'BattleFieldAction_Attack'); // Initialize 'battle view' game functionality // --------------------------------------------------------------------------------------------------------------------- @@ -133,6 +225,9 @@ bt.game.battle = { $scope.services = { battle : BattleService }; + // Set battle field actions + $scope.processAction = function(tile) { bt.game.battle.model.battleField.grid.processTileClick (BattleService, tile); }; + $scope.passTurn = function() { BattleService.actions.pass(); }; }).controller }, @@ -175,10 +270,12 @@ bt.game.battle = { _playTime : null, // Holds remaining battle time + battleTimeFormated : null, battleTime : null, // Holds remaining play (turn) time + playTimeFormated : null, playTime : null, - + // Parses server timer format to milliseconds _parseServerTime : function(time) { var parsed = time.split(':'); @@ -195,13 +292,18 @@ bt.game.battle = { // Recalculate values var diff = (new Date()) - bt.game.battle.timers._lastQueryTime; var battleTime = (bt.game.battle.timers._battleTime - diff) / 1000; - bt.game.battle.timers.battleTime = Math.floor(battleTime / 3600) + ' : ' + Math.floor((battleTime % 3600) / 60) + ' : ' + Math.floor(battleTime % 60); + if (battleTime < 0) battleTime = 0; + bt.game.battle.timers.battleTime = battleTime; + bt.game.battle.timers.battleTimeFormated = Math.floor(battleTime / 3600) + ' : ' + Math.floor((battleTime % 3600) / 60) + ' : ' + Math.floor(battleTime % 60); var playTime = (bt.game.battle.timers._playTime - diff) / 1000; - bt.game.battle.timers.playTime = Math.floor(playTime / 3600) + ' : ' + Math.floor((playTime % 3600) / 60) + ' : ' + Math.floor(playTime % 60); - // Fire event - bt.services.battleService.Updated.dispatch({ - message: 'Updated "BattleService" timers' - }); + if (playTime < 0) playTime = 0; + bt.game.battle.timers.playTime = playTime; + bt.game.battle.timers.playTimeFormated = Math.floor(playTime / 3600) + ' : ' + Math.floor((playTime % 3600) / 60) + ' : ' + Math.floor(playTime % 60); + // Check values + if ((battleTime <= 0) || (playTime <= 0)) { + // Query values + bt.game.battle.timers.query(battleService); + } } }, @@ -210,31 +312,115 @@ bt.game.battle = { // Check time since last query if ((new Date() - bt.game.battle.timers._lastQueryTime) > bt.config.poling.minimalIntervalBetweenPolls) { // Query timers from service - battleService.calls.timeLeft( // Parameters - [], - // Success callback - function(data) { - // Set data - if ((data) && (data.battle) && (data.ply)) { - bt.game.battle.timers._battleTime = bt.game.battle.timers._parseServerTime(data.battle); - bt.game.battle.timers._playTime = bt.game.battle.timers._parseServerTime(data.ply); - bt.game.battle.timers._lastQueryTime = new Date(); - // Fire event - bt.services.battleService.Called.dispatch({ message: 'Response from "BattleService.timeLeft()".', data : data }); - // Update timers - bt.game.battle.timers.update(); - } else { + battleService.calls.timeLeft( // Parameters + [], + // Success callback + function(data) { + // Set data + if ((data) && (data.battle) && (data.ply)) { + bt.game.battle.timers._battleTime = bt.game.battle.timers._parseServerTime(data.battle); + if ((bt.game.battle.timers._parseServerTime(data.ply) > bt.game.battle.timers._playTime) && (bt.game.battle.timers._playTime !== null)) { + bt.services.battleService.BattleField_NewTurn.dispatch({ message: 'New turn!', data : data }); + } + bt.game.battle.timers._playTime = bt.game.battle.timers._parseServerTime(data.ply); + bt.game.battle.timers._lastQueryTime = new Date(); + // Fire events + bt.services.battleService.Called.dispatch({ message: 'Response from "BattleService.timeLeft()".', data : data }); + bt.services.battleService.Updated.dispatch({ message: 'Updated "BattleService" timers' }); + // Update timers + bt.game.battle.timers.update(); + } else { + // Fire event + bt.services.battleService.Error.dispatch({ message: 'Error calling "BattleService.timeLeft()"!', data : data }); + } + }, + // Fail callback + function(data) { + // Fire event + bt.services.battleService.Error.dispatch({ message: 'Error calling "BattleService.timeLeft()"!', data : data }); + } + ); + } + } + + }, + + // Battle field namespace + battleField : { + + // Actions namespace + actions : { + // Performs pass action + pass : function(battleService) { + battleService.actions.pass(); + }, + + // Performs move action + move : function(battleService, unit, tile) { + battleService.actions.move(unit, tile.location); + }, + // Processes successfull move action's response + _processMove : function(data) { + var units = data.response.result; + for (var i in units) { + var unit = bt.game.battle.model.battleField.units.unitsById[units[i][0]]; + var location = units[i][1]; + if (unit) bt.game.battle.model.battleField.grid.moveContent(unit, { x : location[0], y : location[1] }); + } + }, + + // Performs attack action + attack : function(battleService, unit, tile) { + battleService.actions.attack(unit, tile.location); + }, + // Processes successfull attack action's response + _processAttack : function(data) { + var units = data.response.result; + for (var i in units) { + var unit = bt.game.battle.model.battleField.units.unitsById[units[i][0]]; + var hp = unit.hp - units[i][1]; + if (unit) unit.updateHp(hp); + } + } + }, + + // Updates battle field state + update : function(battleService) { + // Call 'BattleField.last_state()' + battleService.calls.getLastState( // Parameters + [], + // Success callback + function(data) { + if ((data) && (bt.game.battle.model.battleField)) { + // Process update response + bt.game.battle.battleField._processUpdate(data); + // Fire events + bt.services.battleService.Called.dispatch({ message: 'Response from "BattleService.last_state()".', data : data }); + bt.services.battleService.Updated.dispatch({ message: 'Updated "BattleService" state', data : data }); + } + }, + // Fail callback + function(data) { // Fire event - bt.services.battleService.Error.dispatch({ message: 'Error calling "BattleService.timeLeft()"!', data : data }); + bt.services.battleService.Error.dispatch({ message: 'Error calling "BattleService.last_state()"!', data : data }); } - }, - // Fail callback - function(data) { - // Fire event - bt.services.battleService.Error.dispatch({ message: 'Error calling "BattleService.timeLeft()"!', data : data }); - } - ); + ); + }, + _processUpdate : function(data) { + // Update HP + if (data.HPs) for (var id in data.HPs) { + var unit = bt.game.battle.model.battleField.units.unitsById[id]; + if (unit) unit.updateHp(data.HPs[id]); + } + // Update location + if (data.locs) for (var id in data.locs) { + var unit = bt.game.battle.model.battleField.units.unitsById[id]; + if (unit) bt.game.battle.model.battleField.grid.moveContent(unit, { x : data.locs[id][0], y : data.locs[id][1] }); } + // Update game status + if (data.game_over) bt.game.battle.model.battleField.gameOver = data.game_over; + if (data.whose_action) bt.game.battle.model.battleField.activePlayer = data.whose_action; + } } @@ -248,4 +434,9 @@ bt.game.battle.model = { // Holds reference to battle view's battleField model battleField : null -} \ No newline at end of file +} + +// TODO: Debugging: Hooked events +// --------------------------------------------------------------------------------------------------------------------- +bt.services.battleService.BattleField_NewTurn.subscribe( function(event) { alert("New turn!"); } ); +bt.services.battleService.BattleFieldAction_Attack.subscribe( function(event) { for (var i in event.data.response.result) alert('Attack does ' + event.data.response.result[i][1] + ' damage!'); } ); diff --git a/res/js/bt_init.js b/res/js/bt_init.js index 2651d6a..0c0083f 100644 --- a/res/js/bt_init.js +++ b/res/js/bt_init.js @@ -57,6 +57,16 @@ var bt = { // Configuration namespace config : { + + // Web URLs namespace + urls : { + + // Holds client-side URL path + clientUrl : '/', + // Holds server-side URL path + servicesUrl : '/' + + }, // Views configuration namespace views : { @@ -110,12 +120,31 @@ var bt = { // Battle view namespace battle : { - // Styling namespace + // CSS Styles configuration namespace styles : { + // Selected tile CSS class name selected : 'tile_selected', - move_near : 'tile_move_near', - move_far : 'tile_move_far', - attack : 'tile_attack' + // Selected tile CSS class name + move : 'tile_move', + // Selected tile CSS class name + range : 'tile_range', + // Selected tile CSS class name + attack : 'tile_attack', + + // Player's unit CSS class name + player : 'player', + // Enemy unit CSS class name + enemy : 'enemy' + }, + + // Actions configuration namespace + actions : { + // Holds unit's move radius + moveRadius : 1, + // Toggles if actions and movement can pass through other units + jumpUnits : true, + // Toggles if player can attack his own units + friendlyFire : true } } diff --git a/res/js/libs/angular_interval.js b/res/js/libs/angular_interval.js index 617c08f..e87ff24 100644 --- a/res/js/libs/angular_interval.js +++ b/res/js/libs/angular_interval.js @@ -23,7 +23,10 @@ var interval = angular.module('angular-interval', []).factory("$interval", funct // Starts an interval action this.start = function(name, starting) { if (intervalBase._intervals[name]) { - if (!starting) intervalBase._intervals[name].run = true; + if (!starting) { + intervalBase._intervals[name].run = true; + intervalBase._intervals[name].fn(); + } if (intervalBase._intervals[name].run) { $timeout(intervalBase._intervals[name].fn, intervalBase._intervals[name].interval) .then( function() { intervalBase.start(name, true); } ); diff --git a/res/js/model/bt_model.js b/res/js/model/bt_model.js index 080f558..1e0b720 100644 --- a/res/js/model/bt_model.js +++ b/res/js/model/bt_model.js @@ -17,6 +17,11 @@ bt.model.common = { F : 'Fire', I : 'Ice', W : 'Wind' + }, + // Gets element definition from name + getDefinition : function(element) { + for (var i in bt.model.common.elements.definitions) if (element == bt.model.common.elements.definitions[i]) return bt.model.common.elements.definitions[i]; + return null; } }, @@ -24,12 +29,54 @@ bt.model.common = { // Basic weapons namespace weapons : { - // Holds basic weapons definitions / types + // Holds basic weapons definitions definitions : { - sword : 'sword', - bow : 'bow', - wand : 'wand', - glove : 'glove' + bow : 'bow', + glove : 'glove', + wand : 'wand', + sword : 'sword', + + magma : 'magma', + firestorm : 'firestorm', + forestfire : 'forestfire', + pyrocumulus : 'pyrocumulus', + icestorm : 'icestorm', + blizzard : 'blizzard', + avalanche : 'avalanche', + permafrost : 'permafrost' + }, + // Gets weapon definition from name + getDefinition : function(weapon) { + for (var i in bt.model.common.weapons.definitions) if (weapon == bt.model.common.weapons.definitions[i]) return bt.model.common.weapons.definitions[i]; + return null; + }, + + // Holds basic weapons by types + types : { + ranged : [ 'bow', 'magma', 'firestorm', 'forestfire', 'pyrocumulus' ], + dot : [ 'glove', 'firestorm', 'icestorm', 'blizzard', 'pyrocumulus' ], + aoe : [ 'wand', 'avalanche', 'icestorm', 'blizzard', 'permafrost' ], + full : [ 'sword', 'magma', 'avalanche', 'forestfire', 'permafrost' ] + }, + // Gets weapon type from name + getType : function(weapon) { + for (var i in bt.model.common.weapons.types) { + for (var j in bt.model.common.weapons.types[i]) if (weapon == bt.model.common.weapons.types[i][j]) return i; + } + return null; + }, + + ranges : { + ranged : { min : 4, max : 8 }, + dot : { min : 1, max : 1 }, + aoe : { min : 1, max : Number.MAX_VALUE }, + full : { min : 1, max : 1 } + }, + // Gets weapon range from name + getRange : function(weapon) { + var type = bt.model.common.weapons.getType(weapon); + if (bt.model.common.weapons.ranges[type]) return bt.model.common.weapons.ranges[type]; + return null; } }, @@ -40,7 +87,13 @@ bt.model.common = { // Holds basic unit definitions / types definitions : { scient : 'scient', - nescient : 'nescient' + nescient : 'nescient', + part : 'part' + }, + // Gets unit definition from name + getDefinition : function(unit) { + for (var i in bt.model.common.units.definitions) if (unit == bt.model.common.units.definitions[i]) return bt.model.common.units.definitions[i]; + return null; } } diff --git a/res/js/model/bt_model_battle.js b/res/js/model/bt_model_battle.js index e119528..f37ecc6 100644 --- a/res/js/model/bt_model_battle.js +++ b/res/js/model/bt_model_battle.js @@ -59,6 +59,17 @@ bt.model.definitions.battle = { if (bt.debugging.model.verifyModelConstructors) { if ((!angular.isDefined(this.location)) || (!angular.isDefined(this.location.x)) || (!angular.isDefined(this.location.y))) console.error(obj, 'Localized object definition incomplete: Missing location!'); } + + // Update functionality + this.updateLocation = function(location) { + if ((angular.isArray(location)) && (location.length == 2)) { + this.location.x = location[0]; + this.location.y = location[1]; + } else if (angular.isObject(location)) { + this.location.x = location.x; + this.location.y = location.y; + } + } }, // Weapon definition @@ -111,6 +122,11 @@ bt.model.definitions.battle = { this.hp = (4 * (this.physical.defense + this.magic.defense + this.value)); } this.updateStats(); + + // Update functionality + this.updateHp = function(hp) { + this.hp = hp; + } }, // Grid definition @@ -125,8 +141,7 @@ bt.model.definitions.battle = { this.addUnit = function(unit) { this.units.push(unit); if (unit.id) { - if (!angular.isDefined(this.unitsById[unit.id])) this.unitsById[unit.id] = [ ]; - this.unitsById[unit.id].push(unit); + this.unitsById[unit.id] = unit; } if (unit.owner) { if (!angular.isDefined(this.unitsByOwner[unit.owner])) this.unitsByOwner[unit.owner] = [ ]; @@ -153,6 +168,7 @@ bt.model.definitions.battle = { } // Initialize children this._type = bt.model.common.units.definitions.scient; + this.style = (this.owner == bt.game.authentication.username ? bt.config.game.battle.styles.player : bt.config.game.battle.styles.enemy); this.weapon = new bt.model.definitions.battle.weapon(this.weapon); if (angular.isDefined(this.weaponBonus)) { this.weaponBonus = new bt.model.definitions.battle.stone(this.weaponBonus); @@ -169,23 +185,36 @@ bt.model.definitions.battle = { if (obj) bt.model.extend(this, [new bt.model.definitions.battle.localized(obj), new bt.model.definitions.battle.cStone(obj)]); // Initialize children this.contents = [ ]; + this.units = { } + for (var type in bt.model.common.units.definitions) { + this.units[bt.model.common.units.definitions[type]] = [ ]; + } // Adds content to tile this.addContent = function(content) { this.contents.push(content); + var type = bt.model.common.units.getDefinition(content._type); + if (type) this.units[type].push(content); }; // Removes content from tile - this.removedContent = function(content) { - for (var i in this.contents) if (this.contents[i] == content) this.contents[i] = null; + this.removeContent = function(content) { + for (var i in this.contents) if (this.contents[i] == content) this.contents.splice(i, 1); + var type = bt.model.common.units.getDefinition(content._type); + if (type) for (var i in this.units[type]) if (this.units[type][i] == content) this.units[type].splice(i, 1); }; // Clears all title's content this.clearContent = function(content) { this.contents = [ ]; + var type = bt.model.common.units.getDefinition(content._type); + if (type) this.units[type] = [ ]; }; this.isOwnedByPlayer = function() { - return (this.contents.length == 0 ? false : (this.contents[0].owner == bt.game.authentication.username)); - } + for (var i in this.contents) { + if (this.contents[i].owner == bt.game.authentication.username) return true; + } + return false; + } }, // Grid definition @@ -218,7 +247,7 @@ bt.model.definitions.battle = { this.tilesByY[tile.location.y][tile.location.x] = tile; } - // Tiles selection + // Tiles manipulation // --------------------------------------------------------- // Gets all neighbouring tiles with distances from source less than radius and with no units in the way @@ -244,13 +273,30 @@ bt.model.definitions.battle = { if ((base.tilesByX[neighbouringTile.x]) && (base.tilesByX[neighbouringTile.x][neighbouringTile.y]) && ((!result[neighbouringTileId]) || (result[neighbouringTileId].radius > (sourceRadius + 1)))) { // Add tile to result var resultTile = base.tilesByX[neighbouringTile.x][neighbouringTile.y]; - result[neighbouringTileId] = { radius : sourceRadius + 1, tile : resultTile }; + resultTile.distance = sourceRadius + 1; + result[neighbouringTileId] = { radius : resultTile.distance, tile : resultTile }; // Process further neighbours - if ((radius > 1) && (resultTile.contents.length == 0)) base.getNeighourTiles(resultTile, (radius - 1), result); + if ((radius > 1) && ((bt.config.game.battle.actions.jumpUnits) || (resultTile.contents.length == 0))) base.getNeighourTiles(resultTile, (radius - 1), result); } } // Return result return result; + }, + + // Moves content to new tile + this.moveContent = function(content, targetLocation) { + // Get content location + var sourceLocation = content.location; + // Get source and target tiles + var sourceTile = this.tilesByX[sourceLocation.x][sourceLocation.y]; + var targetTile = this.tilesByX[targetLocation.x][targetLocation.y]; + // Move content + if (sourceTile != targetTile) { + sourceTile.removeContent(content); + targetTile.addContent(content); + if (this.selectedTile == sourceTile) this._selectTile(targetTile); + content.updateLocation(targetLocation); + } } // UI interpretation @@ -258,14 +304,19 @@ bt.model.definitions.battle = { // Holds reference to selected tile this.selectedTile = null; - this.processTileClick = function(tile) { + this.processTileClick = function(battleService, tile) { // Check if tile is selectable - if (tile.isOwnedByPlayer()) { - // Select tile - this._selectTile(tile); - } else if (tile.avaliableAction) { + if (tile.avaliableAction) { // Execute tile action - this._executeActionOnTile(tile); + this._executeActionOnTile(battleService, tile); + } else if (tile.isOwnedByPlayer()) { + if (tile != this.selectedTile) { + // Select tile + this._selectTile(tile); + } else { + // Deselect tile + this._selectTile(null); + } } else { // Deselect tile this._selectTile(null); @@ -273,37 +324,57 @@ bt.model.definitions.battle = { } // Sets tile as selected this._selectTile = function(tile) { - // Set selected tile - this.selectedTile = tile; // Clear tiles' styles and actions for (var i in this.tiles) { this.tiles[i].style = null; this.tiles[i].avaliableAction = null; + this.tiles[i].distance = null; } + // Set selected tile + this.selectedTile = tile; if (tile) { - // Set tiles' styles and actions - var nearTiles = base.getNeighourTiles(this.selectedTile, 4); - for (var i in nearTiles) { - if ((nearTiles[i].tile.contents.length > 0) && (!nearTiles[i].tile.isOwnedByPlayer())) { - nearTiles[i].tile.style = bt.config.game.battle.styles.attack; - nearTiles[i].tile.avaliableAction = 'attack'; - } else if (nearTiles[i].tile.contents.length == 0) { - if (nearTiles[i].radius <= 1) { - nearTiles[i].tile.style = bt.config.game.battle.styles.move_near; - nearTiles[i].tile.avaliableAction = 'move'; - } else if (nearTiles[i] != this.selectedTile) { - nearTiles[i].tile.style = bt.config.game.battle.styles.move_far; - nearTiles[i].tile.avaliableAction = 'move'; + // Calculate weapon attack radius + var units = this.selectedTile.units[bt.model.common.units.definitions.scient]; + if (units.length > 0) { + var unit = units[0]; + var weapon = unit.weapon; + if (weapon) { + var attackRadius = bt.model.common.weapons.getRange(weapon._type); + if (attackRadius) { + var minAttackRadius = attackRadius.min, maxAttackRadius = attackRadius.max; + + // Set tiles' styles and actions + var nearTiles = base.getNeighourTiles(this.selectedTile, (maxAttackRadius > bt.config.game.battle.actions.moveRadius ? maxAttackRadius : bt.config.game.battle.actions.moveRadius)); + for (var i in nearTiles) { + if ((nearTiles[i].tile != this.selectedTile) + &&((nearTiles[i].tile.units[bt.model.common.units.definitions.scient].length > 0) + && ((bt.config.game.battle.actions.friendlyFire) || (!nearTiles[i].tile.isOwnedByPlayer()))) + && ((nearTiles[i].radius >= minAttackRadius) && (nearTiles[i].radius <= maxAttackRadius))) { + nearTiles[i].tile.style = bt.config.game.battle.styles.attack; + nearTiles[i].tile.avaliableAction = 'attack'; + } else if (nearTiles[i].tile.units[bt.model.common.units.definitions.scient].length == 0) { + if (nearTiles[i].radius <= bt.config.game.battle.actions.moveRadius) { + nearTiles[i].tile.style = bt.config.game.battle.styles.move; + nearTiles[i].tile.avaliableAction = 'move'; + } else if ((nearTiles[i] != this.selectedTile) && (nearTiles[i].radius >= minAttackRadius) && ((nearTiles[i].radius <= maxAttackRadius))) { + nearTiles[i].tile.style = bt.config.game.battle.styles.range; + } + } + } + this.selectedTile.style = bt.config.game.battle.styles.selected; } } } - this.selectedTile.style = bt.config.game.battle.styles.selected; } }; // Sets tile as selected - this._executeActionOnTile = function(tile) { + this._executeActionOnTile = function(battleService, tile) { if (tile.avaliableAction) { - alert('Processing action "' + tile.avaliableAction + '"!'); + if (tile.avaliableAction == 'move') { + bt.game.battle.battleField.actions.move(battleService, this.selectedTile.units[bt.model.common.units.definitions.scient][0], tile); + } else if (tile.avaliableAction == 'attack') { + bt.game.battle.battleField.actions.attack(battleService, this.selectedTile.units[bt.model.common.units.definitions.scient][0], tile); + } } }; diff --git a/res/partials/views/battle.html b/res/partials/views/battle.html index 7f0b0c8..78cbda0 100644 --- a/res/partials/views/battle.html +++ b/res/partials/views/battle.html @@ -4,10 +4,15 @@