From 60047bb12cfa4030b06c8951a1df2937d5774def Mon Sep 17 00:00:00 2001 From: Leo Martel Date: Sun, 8 Sep 2013 01:45:32 -0700 Subject: [PATCH] highlights for unit status --- client.js | 85 +++++++++++++++++++-------------------- init.js | 5 ++- lib/external/hex.money.js | 39 ++++++++++++++---- lib/helpers/util.js | 59 +++++++++++++++++++++++---- lib/unit.js | 13 +++++- server.js | 45 +++++++++++++-------- 6 files changed, 168 insertions(+), 78 deletions(-) diff --git a/client.js b/client.js index 456bc7b..d9c1b1a 100644 --- a/client.js +++ b/client.js @@ -34,23 +34,7 @@ if (Meteor.isClient) { var msg = getMessage(); // msg = "a really long status message just so I can test exactly how this should be rendered in dat dere sidebar"; if(!msg) return undefined; - msg = msg.split(" "); - var formatted = ""; - var chars = 0; - for(var i = 0; i < msg.length; i++){ - var word = msg[i]; - if(chars + word.length > MESSAGE_CHAR_WRAP){ - formatted += "\n"; - chars = 0; - } else { - formatted += " "; - } - formatted += word; - - // +1 for space - chars += word.length + 1; - } - return ">> " + formatted.trim().split("\n").join(" <<
>> ") + " <<"; + return "=====
" + msg + "
====="; }; /** @@ -118,7 +102,8 @@ if (Meteor.isClient) { var replayData = {}; var count = 0; Actions.find({ gameId: getGame()._id}, {sort: { timestamp: 1 } }).forEach(function(action){ - if(action.round > maxRound) maxRound = action.round; + var baseRound = Math.floor(action.round); + if(baseRound > maxRound) maxRound = baseRound; // Setup round is not set on a timeout, to avoid flickering of pieces if(action.round === 0){ @@ -130,7 +115,7 @@ if (Meteor.isClient) { replayData[count] = action._id; replayData[action._id] = setTimeout(function(){ actionReplayDone(action); - message("Instant replay: ROUND " + action.round); + message("Instant replay: ROUND " + baseRound); renderAction(action, board); //TODO: might be a bug around here with the path callback. Call draw() after a timeout? }, action.round * ROUND_MILLISECONDS); @@ -327,8 +312,7 @@ if (Meteor.isClient) { var start = board.get(unit.location); var path = start.getPathTo(end); - // TODO: query database for max unit speed, use that as the divisor. Set as constant 5, override using startup() - var duration = Math.min(MOVE_MILLISECONDS * path.length, ROUND_MILLISECONDS); + var duration = Math.min(MOVE_MILLISECONDS * path.length, MAX_MOVE); var move = board.action().get(unit.location).movePayloadAlongPath( board.action().get(unit.location).getPathTo( board.action().get(end.getLocation()) @@ -361,13 +345,18 @@ if (Meteor.isClient) { var enemy = Units.findOne(end.getPayloadData()); setDefender(enemy); - Meteor.call("attack", getGame()._id, unit, enemy, function(err, hits){ - if(hits > 0){ - message("Attack successful!"); - } else if(hits === 0) { + Meteor.call("attack", getGame()._id, unit, enemy, function(err, results){ + if(results.hits > 0){ + var str = "Attack successful!\n"; + str += getCard(enemy).name + " was "; + if(results.status === UnitStatus.DAMAGED) str += UnitStatus.DISRUPTED + " and "; + str += results.status + "."; + // TODO render rolls + message(str); + } else if(results.hits === 0) { message("Attack failed."); } - // Units.update(unit._id, {$set: {used: true } }); + toggleUnitSelection(unit); }); @@ -431,36 +420,46 @@ if (Meteor.isClient) { function unusedUnits(){ var board = getBoard(); if(board && isReplayOver()){ - var unused = 0; - Units.find({_id: {$in: this.unitIds}}).forEach(function(unit){ - if(unit.used){ - board.get(unit.location).setHighlight(UNIT_USED, true).draw(); - } else { - unused++; - } - }); - return unused; + return Units.find({_id: {$in: this.unitIds}, used: false}).count(); } - return undefined; } Template.movement.unitStatus = function(){ - unitStatus(false); + highlights(false); }; - function unitStatus(canAttack){ + function highlights(canAttack){ var board = getBoard(); if(!board) return; - var bgs = [UNIT_SELECTED, CAN_MOVE_TO, ENEMY_SELECTED]; - if(notYourTurn()){ - bgs.push(UNIT_USED); - } + var bgs = [UNIT_SELECTED, CAN_MOVE_TO, ENEMY_SELECTED, UNIT_USED]; if(canAttack){ bgs = bgs.concat( [CAN_SEE, CAN_ATTACK, CAN_MOVE_TO_AND_SEE] ); //TODO: can't see? } clearHighlights(bgs); + + Units.find({_id: {$in: getArmy().unitIds}, used: true }).forEach(function(unit){ + board.get(unit.location).setHighlight(UNIT_USED, true).draw(); + }); + + // Highlight all statuses. Pending takes precedence over active. + Armies.find({gameId: getGame()._id}).forEach(function(army){ + Units.find({_id: {$in: army.unitIds} }).forEach(function(unit){ + var hex = board.get(unit.location); + var used = hex.getHighlightColor() === UNIT_USED; + var hl; + if(unit.pendingStatus){ + hl = getHighlightArgsForStatus(unit.pendingStatus, false); + } else if(unit.status){ + hl = getHighlightArgsForStatus(unit.status, true); + } + if(!hl) return; + if(used) hl[1] += 0.3; + hex.setHighlight(hl[0], hl[1], hl[2]).draw(); + }); + }); + var active = getUnit(); if(!active){ setCanMoveTo(undefined); @@ -527,7 +526,7 @@ if (Meteor.isClient) { }; Template.assault.unitStatus = function(){ - unitStatus(true); + highlights(true); }; Template.unitCard.events({ diff --git a/init.js b/init.js index 8a46039..7fe0eba 100644 --- a/init.js +++ b/init.js @@ -12,9 +12,10 @@ SELECT = "." + KLASS; CONTENT_WIDTH = 0.75; CONTENT_MARGIN = 0.10; MESSAGE_CHAR_WRAP = 20; -ROUND_MILLISECONDS = 2000; +ROUND_MILLISECONDS = 3000; TICK_MILLISECONDS = ROUND_MILLISECONDS / 60; -MOVE_MILLISECONDS = TICK_MILLISECONDS * 12; +MOVE_MILLISECONDS = TICK_MILLISECONDS * 8; +MAX_MOVE = ROUND_MILLISECONDS / 2; DEPLOYMENT_ZONE_WIDTH = 3; diff --git a/lib/external/hex.money.js b/lib/external/hex.money.js index 75e4507..532f0bc 100644 --- a/lib/external/hex.money.js +++ b/lib/external/hex.money.js @@ -1097,12 +1097,30 @@ H$ = {}; function HexGrid_destroyDetachedAsset(asset, selectString){ selectString = selectString || "." + asset.klass; delete asset.animating; - d3.select(selectString).remove(); - var i = this.detachedAssets.indexOf(asset); - this.detachedAssets.splice(i, 1); + d3.selectAll(selectString).remove(); + var assets = this.detachedAssets; + var keep = []; + for(var i = 0; i < assets.length; i++){ + if(assets[i].klass !== asset.klass) keep.push(assets[i]); + } + this.detachedAssets = keep; return this; } +// H$.HexGrid.prototype.interruptAnimations = HexGrid_interruptAnimations; +// function HexGrid_interruptAnimations(){ +// +// // slice to copy array to avoid concurrent modification issues +// var rogue = this.detachedAssets.slice(0); +// for(var i = 0; i < rogue.length; i++){ +// console.log(rogue[i]) +// if(rogue[i].animating) this.destroyDetachedAsset(rogue[i]); +// } +// this.detachedAssets = rogue; +// return this; +// } + + /** * The move animations are designed to be as interruptable as possible; the payload * and asset are loaded into the destination at the beginning of the animation, @@ -1117,7 +1135,7 @@ H$ = {}; // slice to copy array to avoid concurrent modification issues var rogue = this.detachedAssets.slice(0); for(var i = 0; i < rogue.length; i++){ - if(rogue[i].animating) this.destroyDetachedAsset(rogue[i]); + if(rogue[i].animating || true) this.destroyDetachedAsset(rogue[i]); } return this; } @@ -1185,7 +1203,7 @@ H$ = {}; if(this.highlight && !this.highlightOver){ renderHex(this, highlightClass, this.highlight, this.highlight) - .style("fill-opacity", 0.5); + .style("fill-opacity", this.highlightOpacity); } if(Array.isArray(this.payload)){ @@ -1197,7 +1215,7 @@ H$ = {}; if(this.highlight && this.highlightOver){ renderHex(this, highlightClass, this.highlight, this.highlight) - .style("fill-opacity", 0.5); + .style("fill-opacity", this.highlightOpacity); } function renderHex(context, klass, stroke, fill){ @@ -1366,7 +1384,14 @@ H$ = {}; .attr("x1", startWillTravel.x()).attr("y1", startWillTravel.y()) .attr("x2", target.x()).attr("y2", target.y()) .each("end", function(){ - if(destroy) grid.destroyDetachedAsset(asset, "#" + uniqueId); + + // is flag has been disabled, stop + if(!asset.animating) return; + if(destroy){ + grid.destroyDetachedAsset(asset, "#" + uniqueId); + } else { + asset.animating = false; + } if(iterations > 1){ options.iterations = iterations - 1; startHex.drawLineTo(targetHex, options); diff --git a/lib/helpers/util.js b/lib/helpers/util.js index 345152c..09fbe39 100644 --- a/lib/helpers/util.js +++ b/lib/helpers/util.js @@ -102,9 +102,9 @@ getAttacks = function(attacker, defender){ }; hasStatus = function(unit, status){ - var stat = unit.statuses; - if(status) return stat.indexOf(status) !== -1; - return stat.indexOf(UnitStatus.DISRUPTED) !== -1 || stat.indexOf(UnitStatus.DAMAGED) !== -1 + var stat = unit.status; + if(status) return stat === status; + return status !== null; }; countHits = function(attacks, defender){ @@ -129,7 +129,7 @@ countHits = function(attacks, defender){ } }; -hasCover = function(gameId, unit){ +rollCover = function(gameId, unit){ var loc = unit.location; var layout = Maps.findOne(Games.findOne(gameId).mapId); var bg; @@ -141,19 +141,64 @@ hasCover = function(gameId, unit){ } } + var type = getCard(unit).type; var terrain = Terrain[bg]; + var canRoll; switch(terrain){ case Terrain.HOLES: case Terrain.MARSH: - return getCard(unit).type === UnitType.SOLDIER; + canRoll = (type === UnitType.SOLDIER); break; case Terrain.FOREST: case Terrain.HILL: case Terrain.TOWN: - return true; + canRoll = true; break; default: - return false; + canRoll = false; break; } + if(canRoll){ + var roll = Math.floor((Math.random() * 6) + 1); + if(type === UnitType.SOLDIER){ + return roll >= 4; + } else { + return roll >= 5; + } + } + return false; +}; + +isDisrupted = function(unit){ + return unit.status === UnitStatus.DISRUPTED || unit.status === UnitStatus.DISRUPTED_AND_DAMAGED; +}; + +isDamaged = function(unit){ + return unit.status === UnitStatus.DAMAGED || unit.status === UnitStatus.DISRUPTED_AND_DAMAGED; +}; + +getHighlightArgsForStatus = function(status, isActive){ + var color; + var highlightOver = false; + var opacity = 0.1; + switch(status){ + case UnitStatus.DISRUPTED: + color = "yellow"; + break; + case UnitStatus.DAMAGED: + color = "darkred"; + break; + case UnitStatus.DISRUPTED_AND_DAMAGED: + color = "red"; + break; + case UnitStatus.DESTROYED: + color = "black"; + opacity = 0.3; + break; + } + if(isActive){ + opacity = 0.5; + highlightOver = true; + } + return [color, opacity, highlightOver]; }; \ No newline at end of file diff --git a/lib/unit.js b/lib/unit.js index 8b335e3..3f6074d 100644 --- a/lib/unit.js +++ b/lib/unit.js @@ -53,8 +53,8 @@ function _Unit(unitCard){ this.cardId = unitCard._id; this.location = null; this.used = false; - this.statuses = []; - this.pendingStatuses = []; + this.status = null; + this.pendingStatus = null; } UnitCard = _UnitCard; @@ -99,6 +99,14 @@ function _UnitCard(options){ } else throw "unit creation: options hash missing required field: vehicleAttacks (format: [short, med, long] )"; } +CombatResults = _CombatResults; +function _CombatResults(rolls, hits, cover, status){ + this.rolls = rolls; + this.hits = hits; + this.cover = cover; + this.status = status; +} + Faction = null; (function(){ var faction = { @@ -123,6 +131,7 @@ UnitStatus = null; (function(){ var unitStatus = { DISRUPTED: "Disrupted", + DISRUPTED_AND_DAMAGED: "Disrupted and Damaged", DAMAGED: "Damaged", DESTROYED: "Destroyed" }; diff --git a/server.js b/server.js index 56139e5..6ca1b03 100644 --- a/server.js +++ b/server.js @@ -79,26 +79,34 @@ if (Meteor.isServer) { Units.update({_id: {$in: army.unitIds}}, {$set: {used: false } }, {multi: true}); }, attack: function(gameId, attacker, defender){ + var game = Games.findOne(gameId); var count = getAttacks(attacker, defender); var attacks = []; for(var i = 0; i < count; i++){ - attacks.push(roll(attacker)); + attacks.push(attackRoll(attacker)); } var hits = countHits(attacks, defender); - var cover = hasCover(gameId, defender); - + var cover = rollCover(gameId, defender); for(var i = 0; i < hits; i++){ incrementStatus(defender, cover); } - // Units.update(defender._id, {$set: {pendingStatuses: defender.pendingStatuses } }); + Units.update(attacker._id, {$set: {used: true } }); + Units.update(defender._id, {$set: {pendingStatus: defender.pendingStatus } }); fireProjectiles(attacker, count, defender, hits === 0); - return hits; - function roll(attacking){ + if(defender.pendingStatus){ + var hl = getHighlightArgsForStatus(defender.pendingStatus, false); + var highlight = (new H$.Action()).get(defender.location).setHighlight(hl[0], hl[1], hl[2]).draw(); + var foo = Actions.insert({gameId: gameId, duration: 0, round: game.round + 0.5, timestamp: Date.now(), document: highlight.$serialize()}); + } + + return new CombatResults(attacks, hits, cover, defender.pendingStatus); + + function attackRoll(attacking){ var base = Math.floor((Math.random() * 6) + 1); if(hasStatus(attacking)){ base--; @@ -107,19 +115,23 @@ if (Meteor.isServer) { } function incrementStatus(unit, cover){ - switch(unit.pendingStatuses.length){ - case 0: - unit.pendingStatuses.push(UnitStatus.DISRUPTED); + switch(unit.pendingStatus){ + case null: + unit.pendingStatus = UnitStatus.DISRUPTED; break; - case 1: - if(getCard(unit).type === UnitType.VEHICLE){ - if(cover) break; - unit.pendingStatuses.push(UnitStatus.DAMAGED); + case UnitStatus.DISRUPTED: + if(cover) break; + + // Vehicles get a damage counter if they don't already have one + if(getCard(unit).type === UnitType.VEHICLE && !isDamaged(unit)){ + unit.pendingStatus = UnitStatus.DISRUPTED_AND_DAMAGED; break; } // else fall through: + case UnitStatus.DISRUPTED_AND_DAMAGED: + unit.pendingStatus = UnitStatus.DESTROYED; + case UnitStatus.DESTROYED: default: - if(cover) break; - unit.pendingStatuses.push(UnitStatus.DESTROYED); + break; } } @@ -164,8 +176,7 @@ if (Meteor.isServer) { } ); - Actions.insert({gameId: gameId, duration: duration, round: Games.findOne(gameId).round, timestamp: Date.now(), document: fire.$serialize()}); - + Actions.insert({gameId: gameId, duration: duration, round: game.round + 0.5, timestamp: Date.now(), document: fire.$serialize()}); } } });