Last active
August 29, 2015 14:07
-
-
Save totty90/024533cb974ba7d194ba to your computer and use it in GitHub Desktop.
Planets Multiplayer: Source code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
- Game: http://54.77.31.219/ | |
- Last version video: https://www.youtube.com/watch?v=5H3Pu4WFZD4 | |
- Community: https://plus.google.com/u/1/communities/105437932515232469714 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define(function(require, exports, module){ | |
var _ = require('underscore'); | |
var utils = require('client/utils'); | |
var Phaser = require('phaser'); | |
var Block = require('../actors/Block'); | |
var PlanetGenerator = require('client/PlanetGenerator') | |
var constants = require('client/constants'); | |
var EventEmitter = require('eventEmitter'); | |
var BlockStore = function(options){ | |
_.extend(this, { | |
api : null, | |
stores : {}, | |
},options); | |
this.__shipsById = {}; | |
this.__planetsById = {}; | |
this.__planetsByBlockId = []; | |
this.__blocksIdByPlanetId = {}; | |
this.__blocksById = []; | |
this.__blocksByPos = {}; | |
this.__selectedBlocksHistory = []; | |
this.__onPlanetChangeBinded = this.__onPlanetChange.bind(this); | |
this.onBlockUpdated = new Phaser.Signal(); | |
this.invalidateInBoundsBlocks(); | |
this.generateEmptyBlocks(); | |
var appDispatcher = options.appDispatcher; | |
this.dispatchToken = appDispatcher.register(this.onAppDispatcherAction.bind(this)) | |
} | |
BlockStore.getBoundingBoxStepped = function(view, step){ | |
// Adjusting so we don't care about negative values | |
var adjustX = 0; | |
if(view.x < 0){ | |
adjustX = step * Math.ceil(-view.x/step); | |
} | |
view.x += adjustX; | |
var adjustY = 0; | |
if(view.y < 0){ | |
adjustY = step * Math.ceil(-view.y/step); | |
} | |
view.y += adjustY; | |
var leftBoundingBox = view.x - view.x % step - adjustX; | |
var topBoundingBox = view.y - view.y % step - adjustY; | |
var horizontalSteps = Math.ceil((view.width + view.x % step) / step); | |
var verticalSteps = Math.ceil((view.height + view.y % step) / step); | |
var width = step * horizontalSteps; | |
var height = step * verticalSteps; | |
return { | |
x : leftBoundingBox, | |
y : topBoundingBox, | |
width : width, | |
height : height, | |
horizontalSteps : horizontalSteps, | |
verticalSteps : verticalSteps, | |
leftStep : leftBoundingBox / step, | |
topStep : topBoundingBox / step, | |
} | |
} | |
_.extend(BlockStore.prototype, EventEmitter.prototype, { | |
onAppDispatcherAction: function(payload){ | |
var action = payload.action; | |
switch(action.type){ | |
case 'players.unlockBlock': | |
// @todo. | |
break; | |
default: | |
// do nothing | |
} | |
}, | |
selectBlock: function(block){ | |
this.selectedBlock = block; | |
this.__selectedBlocksHistory.push(block); | |
}, | |
getLastXSelectedBlocks: function(howMany){ | |
return _.last(this.__selectedBlocksHistory, howMany) | |
}, | |
getVisibleBlocks: function(){ | |
var last2SelectedBlocks = this.getLastXSelectedBlocks(2); | |
console.log(last2SelectedBlocks.length) | |
return last2SelectedBlocks | |
}, | |
updateAuthorizedBlocks: function(){ | |
var unlockedBlocksIds = this.stores.authStore.currentPlayer.unlockedBlocksIds; | |
unlockedBlocksIds.forEach(function(blockId){ | |
var block = this.getBlockById(blockId); | |
block.setAuthorized(true); | |
}.bind(this)) | |
}, | |
invalidateInBoundsBlocks: function(){ | |
this.__inBoundsBlocksAreDirty = true; | |
}, | |
updateInBoundsBlocks: function(){ | |
if(!this.__inBoundsBlocksAreDirty) return; | |
// Use camera | |
var cameraVisibleRect = this.stores.cameraStore.getVisibleArea(); | |
// Don't consider scale. | |
// The origin of the blocks is (0, 0) | |
var blockSize = constants.BLOCK_SIZE; | |
var boundingBoxStepped = BlockStore.getBoundingBoxStepped(cameraVisibleRect, blockSize); | |
var inBoundsBlocksIds = []; | |
var inBoundsBlocksById = {}; | |
var inBoundsBlocks = []; | |
var block; | |
var currentPlayer = this.stores.authStore.currentPlayer; | |
for(var x = boundingBoxStepped.leftStep; x < boundingBoxStepped.leftStep + boundingBoxStepped.horizontalSteps; x++){ | |
for(var y = boundingBoxStepped.topStep; y < boundingBoxStepped.topStep + boundingBoxStepped.verticalSteps; y++){ | |
block = this.getBlockByXY(x, y); | |
// if(currentPlayer.unlockedBlocksIds.indexOf(block._id) === -1) continue; | |
inBoundsBlocksIds.push(block._id); | |
inBoundsBlocks.push(block); | |
inBoundsBlocksById[block._id] = block; | |
} | |
} | |
this.__inBoundsBlocksIds = inBoundsBlocksIds; | |
this.__inBoundsBlocksById = inBoundsBlocksById; | |
this.__inBoundsBlocks = inBoundsBlocks; | |
this.__inBoundsBlocksAreDirty = false; | |
}, | |
getInBoundsBlocks: function(){ | |
this.updateInBoundsBlocks(); | |
return this.__inBoundsBlocks; | |
}, | |
getInBoundsBlocksIds: function(){ | |
this.updateInBoundsBlocks(); | |
return this.__inBoundsBlocksIds; | |
}, | |
getInBoundsBlocksById: function(){ | |
this.updateInBoundsBlocks(); | |
return this.__inBoundsBlocksById; | |
}, | |
getRenderedBlocks: function(){ | |
return this.__blocksById.filter(function(block){ | |
return block.isRendered(); | |
}) | |
}, | |
getOutOfBoundsRenderedBlocks: function(){ | |
var renderedBlocks = this.getRenderedBlocks(); | |
var authorizedAndInBoundsBlocks = this.getAuthorizedAndInBoundsBlocks(); | |
var outOfBoundsRenderedBlocks = _.difference(renderedBlocks, authorizedAndInBoundsBlocks); | |
return outOfBoundsRenderedBlocks; | |
}, | |
getAuthorizedAndInBoundsBlocks: function(){ | |
return this.getInBoundsBlocks().filter(function(block){ | |
return this.blockIdIsAuthorized(block._id); | |
}.bind(this)) | |
}, | |
blockIdIsAuthorized: function(blockId){ | |
return this.stores.authStore.currentPlayer.unlockedBlocksIds.indexOf(blockId) !== -1; | |
}, | |
generateEmptyBlocks: function(){ | |
var i = 1000; | |
while(i--){ | |
var blockPos = PlanetGenerator.getXYFromUlamSpiralIndex(i); | |
this.addBlock(new Block({ | |
_id : i, | |
pos : blockPos, | |
game : this.game, | |
})) | |
} | |
}, | |
getBlockByWorldPosXY: function(x, y){ | |
var blockX = Math.floor(x / constants.BLOCK_SIZE); | |
var blockY = Math.floor(y / constants.BLOCK_SIZE); | |
return this.getBlockByXY(blockX, blockY); | |
}, | |
getAllShips: function(){ | |
var activeShips = []; | |
_.forEach(this.__shipsById, function(ship){ | |
if(ship.destinationReached) return; | |
activeShips.push(ship); | |
}) | |
return activeShips; | |
}, | |
replaceShipId: function(oldShipId, newShipId){ | |
var ship = this.__shipsById[oldShipId]; | |
delete this.__shipsById[oldShipId]; | |
this.__shipsById[newShipId] = ship; | |
ship._id = newShipId; | |
}, | |
addShip: function(ship){ | |
// Can happen when the ship travel between blocks because we | |
// get the ships by a certain block, therefore no need to render | |
// it again, as it's the exact same ship. | |
if(this.hasShip(ship)) return; | |
this.__shipsById[ship._id] = ship; | |
this.onBlockUpdated.dispatch(ship.from.block); | |
}, | |
hasShip: function(ship){ | |
return !!this.__shipsById[ship._id]; | |
}, | |
removeShipById: function(shipId){ | |
var ship = this.__shipsById[shipId]; | |
delete this.__shipsById[shipId]; | |
this.onBlockUpdated.dispatch(ship.to.block); | |
}, | |
__addPlanet: function(planet){ | |
if(!planet) throw new Error('no planet') | |
var planetId = planet._id; | |
if(planetId === void 0) throw new Error('no planet id') | |
if(this.__planetsById[planetId]) throw new Error('planet already added') | |
this.__planetsById[planetId] = planet; | |
if(!this.__planetsByBlockId[planet.block.id]){ | |
this.__planetsByBlockId[planet.block.id] = []; | |
} | |
this.__planetsByBlockId[planet.block.id].push(planet); | |
planet.onChange = this.__onPlanetChangeBinded; | |
}, | |
__onPlanetChange: function(planet, what){ | |
this.onBlockUpdated.dispatch(planet.block.id, what, planet); | |
}, | |
setBlockPlanets: function(blockId, planets){ | |
var _this = this; | |
planets.forEach(function(planet){ | |
_this.__addPlanet(planet) | |
}) | |
this.onBlockUpdated.dispatch(blockId); | |
}, | |
getPlanetsInBlockId: function(blockId){ | |
var planets = this.__planetsByBlockId[blockId]; | |
return planets ? planets.slice() : false; | |
}, | |
getPlanetById: function(planetId){ | |
if(planetId === void 0) throw new Error('add a planet id') | |
return this.__planetsById[planetId]; | |
}, | |
getAllPlanets: function(){ | |
return _.extend({}, this.__planetsById); | |
}, | |
addBlock: function(block){ | |
this.__blocksById[block._id] = block; | |
this.__blocksByPos[block.getPosAsString()] = block; | |
}, | |
getBlockByXY: function(x, y){ | |
return this.__blocksByPos[Block.getPosXYAsString(x, y)]; | |
}, | |
getBlockByPosString: function(posString){ | |
return this.__blocksByPos[posString]; | |
}, | |
getBlockByPosObject: function(posObject){ | |
return this.getBlockByPosString(Block.getPosObjectAsString(posObject)); | |
}, | |
getBlockById: function(id){ | |
return this.__blocksById[id]; | |
}, | |
}) | |
return BlockStore; | |
}); | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define(function(require, exports, module){ | |
var _ = require('underscore'); | |
var helpers = require('./helpers'); | |
var constants = require('../constants'); | |
var utils = require('../utils'); | |
var BuildingLogic = require('./BuildingLogic'); | |
var errors = require('errors'); | |
var TaskLogic = require('./TaskLogic'); | |
var GeneticsLogic = require('./GeneticsLogic'); | |
var PropertyLogic = require('./PropertyLogic'); | |
var PlanetLogic = function(data){ | |
// toObject only exists in mongoose on the server side. | |
// On the client side we want to add the stuff. On the serverside | |
// we don't need because the data is already added on this object. | |
if(!data.toObject){ | |
_.extend(this, data); | |
} | |
// We pass an object because on the server side when is saved, it should return | |
// this task. Would be nicer to find a better solution because this is not natural | |
// and not expected therefore can lead to serious bugs like not saving the task. | |
this.__taskLogic = new TaskLogic(this, this.task); | |
this.__genetics = new GeneticsLogic(this); | |
if(this.upgrades){ | |
this.upgrades = this.upgrades.map(function(value){ | |
return value ? value : 0; | |
}) | |
}else{ | |
this.upgrades = []; | |
} | |
} | |
PlanetLogic.create = function(data){ | |
if(_.isString(data)){ | |
data = PlanetLogic.decode(data); | |
} | |
return new PlanetLogic(data); | |
} | |
// Encode replace | |
PlanetLogic.encode = function(planetData){ | |
var planetEncoded = []; | |
planetEncoded[0] = planetData._id; | |
planetEncoded[1] = planetData.units; | |
planetEncoded[2] = planetData.gold; | |
if(planetData.owner){ | |
planetEncoded[3] = planetData.owner.username; | |
planetEncoded[4] = planetData.owner.color; | |
planetEncoded[5] = planetData.owner.colorB; | |
planetEncoded[6] = planetData.owner.colorC; | |
} | |
planetEncoded[7] = planetData.capacity; | |
if(planetData.task){ | |
planetEncoded[8] = planetData.task.typeId !== void 0 ? planetData.task.typeId : void 0; | |
planetEncoded[9] = planetData.task.dateStarted !== void 0 ? +planetData.task.dateStarted : void 0; | |
planetEncoded[10] = planetData.task.payload !== void 0 ? JSON.stringify(planetData.task.payload) : void 0; | |
} | |
// We can join everything without a separator because upgrades go from 0-9 therefore | |
// each position is an upgrade level. | |
planetEncoded[11] = planetData.upgrades.join(''); | |
planetEncoded[12] = planetData.block.id; | |
planetEncoded[13] = planetData.goldMined; | |
planetEncoded[14] = planetData.geneticCode; | |
planetEncoded[15] = +planetData.lastUpdated; | |
if(planetData._id === '1608,2095') console.log(planetEncoded) | |
return planetEncoded.join('|'); | |
} | |
PlanetLogic.decode = function(planetEncoded){ | |
planetEncoded = planetEncoded.split('|'); | |
// if(planetEncoded[0] === '12789,13179') debugger | |
planetEncoded = planetEncoded.map(function(item){ | |
if(item === ''){ | |
return void 0; | |
} | |
return item; | |
}) | |
var _id = planetEncoded[0]; | |
var upgrades = planetEncoded[11]; | |
if(upgrades){ | |
upgrades = upgrades.split(''); | |
}else{ | |
upgrades = []; | |
} | |
upgrades = upgrades.map(function(u){ | |
return Number(u); | |
}) | |
// if(_id === "2850,3047") debugger | |
var planetData = { | |
_id : _id, | |
units : Number(planetEncoded[1]), | |
gold : Number(planetEncoded[2]), | |
owner: { | |
username : planetEncoded[3], | |
color : planetEncoded[4], | |
colorB : planetEncoded[5], | |
colorC : planetEncoded[6], | |
}, | |
capacity : Number(planetEncoded[7]), | |
task: { | |
typeId : planetEncoded[8] === void 0 ? void 0 : Number(planetEncoded[8]), | |
dateStarted : planetEncoded[9] === void 0 ? void 0 : new Date(Number(planetEncoded[9])), | |
payload : planetEncoded[10] === void 0 ? void 0 : JSON.parse(planetEncoded[10]), | |
}, | |
pos: { | |
x: Number(_id.split(',')[0]), | |
y: Number(_id.split(',')[1]), | |
}, | |
upgrades : upgrades, | |
goldMined : Number(planetEncoded[13]), | |
block : { | |
id: Number(planetEncoded[12]) | |
}, | |
geneticCode: planetEncoded[14], | |
lastUpdated: planetEncoded[15] === void 0 ? void 0 : new Date(Number(planetEncoded[15])), | |
} | |
return planetData; | |
} | |
PlanetLogic.properties = { | |
} | |
_.extend(PlanetLogic.prototype, { | |
getUpgrade : helpers.getUpgrade, | |
getProperty : PropertyLogic.getProperty, | |
getGenetics: function(){ | |
return this.__genetics; | |
}, | |
upgradeExists: function(nameOrId){ | |
return !!this.getUpgrade(nameOrId); | |
}, | |
getPercentUnitsFilled: function(){ | |
return this.units / this.capacity; | |
}, | |
ownedByBot: function(){ | |
return this.hasOwner() && this.owner.isBot; | |
}, | |
getNearestPlanetFromArray: function(planets, minCapacity){ | |
minCapacity = minCapacity || 0; | |
var closestPlanetL = Infinity, distance; | |
var getDistance = helpers.getDistance; | |
var currentPlanet = this; | |
var closestPlanet; | |
planets.forEach(function(planet){ | |
if(!planet) return; | |
distance = getDistance(planet, currentPlanet) | |
if(distance < closestPlanetL && planet.capacity >= minCapacity){ | |
closestPlanetL = distance; | |
closestPlanet = planet; | |
} | |
}) | |
return { | |
planet : closestPlanet, | |
distance : distance | |
}; | |
}, | |
getRange: function(){ | |
return this.getUpgrade('range', 'value'); | |
}, | |
increaseUnits: function(units){ | |
this.setUnits(this.units + units) | |
}, | |
removeOwner: function(){ | |
this.setOwner(void 0); | |
}, | |
isUnowned : function(){ | |
return !(this.owner && this.owner.username !== void 0) | |
}, | |
isOwned: function(){ | |
return !this.isUnowned(); | |
}, | |
isOwnedBy: function(username){ | |
if(this.isUnowned()) return false; | |
return this.owner.username === username; | |
}, | |
getOwnerUsername: function(){ | |
if(!this.hasOwner()) return false; | |
return this.owner.username; | |
}, | |
// Needed because the color of the owner must be updated if no color | |
// has been provided such in the unowned planets. Also the color of the | |
// player has to be updated to the format required by the game. | |
setOwner: function(owner){ | |
this.owner = owner; | |
if(!this.owner || this.owner.username === void 0){ | |
this.owner = {}; | |
this.owner.color = constants.PLANET_WITOUT_OWNER_COLOR; | |
this.owner.colorB = constants.PLANET_WITOUT_OWNER_COLOR_B; | |
this.owner.colorC = constants.PLANET_WITOUT_OWNER_COLOR_C; | |
}else{ | |
this.owner.color = parseInt('0x' + owner.color); | |
this.owner.colorB = parseInt('0x' + owner.colorB); | |
this.owner.colorC = parseInt('0x' + owner.colorC); | |
} | |
}, | |
subtractUnits: function(units){ | |
this.setUnits(this.units - units) | |
}, | |
fillWithMaxUnits: function(){ | |
this.setUnits(this.capacity); | |
}, | |
setUnits: function(units){ | |
units = Math.round(units); | |
if(units > this.capacity){ | |
units = this.capacity; | |
}else if(units < 0){ | |
units = 0; | |
} | |
this.units = units; | |
}, | |
getGold: function(formatted){ | |
var value = this.gold | |
if(formatted){ | |
return utils.autoformatNumber(value) | |
} | |
return value | |
}, | |
getFormattedPos: function(){ | |
return this.pos.x + ',' + this.pos.y; | |
}, | |
hasOwner: function(){ | |
return this.owner && this.owner.username !== void 0 | |
}, | |
getNextLevelUpgrade: function(nameOrId, dataType){ | |
var currentLevel = this.getUpgrade(nameOrId, 'level'); | |
var newArgs = [nameOrId, dataType, currentLevel + 1]; | |
return this.getUpgrade.apply(this, newArgs) | |
}, | |
getUnitBuildQuantity: function(formatted){ | |
var value = this.getUpgrade('unitBuildQuantity', 'value'); | |
if(formatted){ | |
return utils.autoformatNumber(value) + ' ' + this.getUpgrade('unitBuildQuantity', 'unitType') | |
} | |
return value | |
}, | |
getGoldMineQuantity: function(formatted){ | |
var value = this.getUpgrade('goldMineQuantity', 'value'); | |
if(formatted){ | |
return utils.autoformatNumber(value) + ' ' + this.getUpgrade('goldMineQuantity', 'unitType') | |
} | |
return value | |
}, | |
getRadius: function(){ | |
return this.capacity * constants.UNITS_RADIUS_FACTOR; | |
}, | |
getTask: function(){ | |
return this.__taskLogic; | |
}, | |
}) | |
return PlanetLogic; | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define(function(require, exports, module){ | |
var React = require('react'); | |
var utils = require('client/utils'); | |
var constants = require('client/constants'); | |
var PlanetTask = require('./generic/PlanetTask') | |
var tinycolor = require('tinycolor'); | |
var moment = require('moment'); | |
var PIXI = require('pixi'); | |
var Phaser = require('phaser') | |
var ReactComponent = React.createClass({ | |
getInitialState: function(){ | |
return { | |
} | |
}, | |
__onBuildUnitsClick: function(){ | |
this.__genericTaskClick(constants.TASK_TYPES.BUILD_UNITS); | |
}, | |
__onMineGoldClick: function(){ | |
this.__genericTaskClick(constants.TASK_TYPES.GOLD_MINING); | |
}, | |
__onUpgradeClick: function(){ | |
this.__genericTaskClick(constants.TASK_TYPES.UPGRADE); | |
}, | |
__onDisinfectClick: function(){ | |
this.__genericTaskClick(constants.TASK_TYPES.DISINFECT); | |
}, | |
__onRemoveOwnershipClick: function(){ | |
this.__genericTaskClick(constants.TASK_TYPES.REMOVE_OWNERSHIP); | |
}, | |
__genericTaskClick: function(taskId){ | |
var planet = this.props.data.currentActionStore.selectedPlanet; | |
if(planet.getTask().taskIsDone(taskId)){ | |
this.props.data.capi('ui.completeTask', { | |
planet : planet, | |
}) | |
}else if(planet.getTask().taskIsRunning(taskId)){ | |
this.props.data.capi('ui.cancelTask', { | |
planet : planet, | |
}) | |
}else{ | |
if(taskId === constants.TASK_TYPES.UPGRADE){ | |
this.props.data.rightSidePanelStore.setPanel('UpgradePanel'); | |
}else{ | |
this.props.data.capi('ui.startTask', { | |
taskId : taskId, | |
planet : planet, | |
}) | |
} | |
} | |
}, | |
__currentPlayerIsOwner: function(){ | |
var selectedPlanet = this.props.data.currentActionStore.selectedPlanet; | |
return selectedPlanet ? selectedPlanet.isOwnedBy(data.authStore.currentPlayer.username) : false; | |
}, | |
componentDidMount: function(){ | |
}, | |
componentWillUpdate: function(){ | |
}, | |
__showTooltip: function(text, e){ | |
// Set it directly on the state without setState because we don't | |
// need to update the UI here. The TooltipContainer will update it's UI. | |
this.state.tooltipId = this.props.data.tooltipStore.add({ | |
target : e.target, | |
avoidOverlap : this.refs.root.getDOMNode().parentNode, | |
// Hackish way, is faster for now, but fix it later. | |
text : text, | |
direction : 'left' | |
}); | |
}, | |
__hideTooltip: function(){ | |
// Set it directly on the state without setState because we don't | |
// need to update the UI here. The TooltipContainer will update it's UI. | |
this.props.data.tooltipStore.remove(this.state.tooltipId); | |
}, | |
render: function(){ | |
var planet = this.props.data.currentActionStore.selectedPlanet; | |
if(!planet) return null; | |
var ownerTooltip = 'Tells who owns this planet. '; | |
if(this.__currentPlayerIsOwner()){ | |
ownerTooltip += 'You are the owner of this planet.'; | |
}else{ | |
if(planet.owner.username === void 0){ | |
ownerTooltip += 'This planet is not owned by any player. Is easier to attack.' | |
}else{ | |
ownerTooltip += 'You are not the owner of this planet. Another player owns this planet.' | |
} | |
} | |
return React.DOM.section({ | |
className : 'planet-panel', | |
ref : 'root' | |
}, | |
this.__currentPlayerIsOwner() ? React.DOM.section({ | |
className: 'task-section ' | |
}, | |
React.DOM.h2({}, 'planet tasks'), | |
React.DOM.ul({ | |
className: 'task-list' | |
}, | |
new PlanetTask({ | |
key : 'mineGold', | |
taskId : constants.TASK_TYPES.GOLD_MINING, | |
planet : planet, | |
currentPlayerIsOwner : this.__currentPlayerIsOwner(), | |
onClick : this.__onMineGoldClick, | |
tooltipStore : this.props.data.tooltipStore, | |
}), | |
new PlanetTask({ | |
key : 'buildUnits', | |
taskId : constants.TASK_TYPES.BUILD_UNITS, | |
planet : planet, | |
currentPlayerIsOwner : this.__currentPlayerIsOwner(), | |
onClick : this.__onBuildUnitsClick, | |
tooltipStore : this.props.data.tooltipStore, | |
}), | |
new PlanetTask({ | |
key : 'disinfect', | |
taskId : constants.TASK_TYPES.DISINFECT, | |
planet : planet, | |
currentPlayerIsOwner : this.__currentPlayerIsOwner(), | |
onClick : this.__onDisinfectClick, | |
tooltipStore : this.props.data.tooltipStore, | |
}), | |
new PlanetTask({ | |
key : 'upgrade', | |
taskId : constants.TASK_TYPES.UPGRADE, | |
planet : planet, | |
currentPlayerIsOwner : this.__currentPlayerIsOwner(), | |
onClick : this.__onUpgradeClick, | |
tooltipStore : this.props.data.tooltipStore, | |
}), | |
new PlanetTask({ | |
key : 'remove ownership', | |
taskId : constants.TASK_TYPES.REMOVE_OWNERSHIP, | |
planet : planet, | |
currentPlayerIsOwner : this.__currentPlayerIsOwner(), | |
onClick : this.__onRemoveOwnershipClick, | |
tooltipStore : this.props.data.tooltipStore, | |
}) | |
) | |
) : null, | |
React.DOM.section({ | |
className: 'info' | |
}, | |
React.DOM.h2({}, 'planet info'), | |
// Not used now, but might come back. | |
// React.DOM.div({ | |
// className: 'planet-preview small', | |
// style: { | |
// backgroundColor : '#'+utils.decimalToHex(planet.owner.color), | |
// borderColor : '#'+utils.decimalToHex(planet.owner.colorB), | |
// } | |
// }), | |
React.DOM.ul({ | |
className: 'info-items' | |
}, | |
// Not used now, but might come back. | |
// React.DOM.li({}, | |
// React.DOM.span({className: 'value'}, planet.getFormattedPos()), | |
// React.DOM.span({className: 'key'}, 'pos x, y') | |
// ), | |
React.DOM.li({ | |
onMouseEnter : this.__showTooltip.bind(this, ownerTooltip), | |
onMouseLeave : this.__hideTooltip, | |
}, | |
React.DOM.span({className: 'value'}, (planet.owner.username || '?') + (this.__currentPlayerIsOwner() ? ' (you)' : '')), | |
React.DOM.span({className: 'key'}, 'owner') | |
), | |
React.DOM.li({ | |
onMouseEnter : this.__showTooltip.bind(this, 'Shows how many units you have on the planet and the maximum capacity. 30/60 means that you have 30 units and the maximum number of units that you can have on this planet is 60.'), | |
onMouseLeave : this.__hideTooltip, | |
}, | |
React.DOM.span({className: 'value'}, utils.autoformatNumber(planet.units) + '/' + planet.capacity + ' u'), | |
React.DOM.span({className: 'key'}, 'units/capacity') | |
), | |
React.DOM.li({ | |
onMouseEnter : this.__showTooltip.bind(this, 'Gold remaining for mining on this planet. Gold is not infinite therefore every time you gold mine you get the gold from the ground. Some day it will end.'), | |
onMouseLeave : this.__hideTooltip, | |
}, | |
React.DOM.span({className: 'value'}, planet.getGold(true) + ' g'), | |
React.DOM.span({className: 'key'}, 'gold on planet') | |
), | |
React.DOM.li({ | |
onMouseEnter : this.__showTooltip.bind(this, 'The more zombie you have the less efficient is your planet (If you have less than 10% then no problem). Lowers the ship attack and speed and planet defense. Be careful that if you send a ship from a planet with zombies you might infect another planet. In order to disinfect use the disinfect task on the planet.'), | |
onMouseLeave : this.__hideTooltip, | |
}, | |
React.DOM.span({className: 'value'}, utils.autoformatNumber(planet.getGenetics().getGene('zombie', 'percent') * 100) + ' %z'), | |
React.DOM.span({className: 'key'}, 'zombie') | |
), | |
React.DOM.li({ | |
onMouseEnter : this.__showTooltip.bind(this, 'The attack of your planet. The more you have, the more units you will kill when you attack another planet.'), | |
onMouseLeave : this.__hideTooltip, | |
}, | |
React.DOM.span({className: 'value'}, utils.autoformatNumber(planet.getProperty('shipAttack')) + ' a'), | |
React.DOM.span({className: 'key'}, 'ship attack') | |
), | |
React.DOM.li({ | |
onMouseEnter : this.__showTooltip.bind(this, 'The defense of your planet. The more you have the harder is to for other players to kill your units on this planet.'), | |
onMouseLeave : this.__hideTooltip, | |
}, | |
React.DOM.span({className: 'value'}, utils.autoformatNumber(planet.getProperty('planetDefense')) + ' d'), | |
React.DOM.span({className: 'key'}, 'planet defense') | |
) | |
// Not used now, but might come back. | |
// React.DOM.li({}, | |
// React.DOM.span({className: 'value'}, utils.autoformatNumber(planet.getProperty('shipSpeed')) + ' px/m'), | |
// React.DOM.span({className: 'key'}, 'ship speed') | |
// ), | |
// React.DOM.li({}, | |
// React.DOM.span({className: 'value'}, moment(planet.lastUpdated).startOf('seconds').fromNow()), | |
// React.DOM.span({className: 'key'}, 'last updated') | |
// ), | |
// React.DOM.li({}, | |
// React.DOM.span({className: 'value'}, planet.getTask().getLastType() !== void 0 ? constants.getTaskBy('id', planet.getTask().getLastType()).text : 'no tasks done yet'), | |
// React.DOM.span({className: 'key'}, 'last task') | |
// ) | |
) | |
// Not used now, but might come back. | |
// , | |
// React.DOM.ul({ | |
// className : 'genetic-code', | |
// title : 'Planet DNA/Genetic code.' | |
// }, | |
// planet.geneticCode.split('').map(function(geneticCodeChar, i){ | |
// var bgColor = tinycolor('#'+planet.getGenetics().getColorAtIndex(i)).setAlpha(0.5); | |
// var textColor = tinycolor.mostReadable(bgColor, ["#FFF", "#000"]).toHexString(); | |
// return React.DOM.li({ | |
// style: { | |
// color : textColor, | |
// backgroundColor : tinycolor('#'+planet.getGenetics().getColorAtIndex(i)).setAlpha(1), | |
// } | |
// }, geneticCodeChar) | |
// }) | |
// ) | |
) | |
) | |
} | |
}); | |
return ReactComponent; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define(function(require, exports, module){ | |
var React = require('react'); | |
var utils = require('client/utils'); | |
var constants = require('client/constants'); | |
var ReactComponent = React.createClass({ | |
getInitialState: function(){ | |
return {} | |
}, | |
componentDidMount: function(){ | |
this.__updateTimeMissing(); | |
this.__interval = setInterval(this.__updateTimeMissing.bind(this), 1000/60); | |
}, | |
componentWillReceiveProps: function(newProps){ | |
this.__updateTimeMissing(newProps); | |
}, | |
componentWillUnmount: function(){ | |
clearInterval(this.__interval); | |
}, | |
__updateTimeMissing: function(props){ | |
props = props || this.props; | |
var remainingTime = void 0; | |
if(this.__currentTaskIsActive()){ | |
remainingTime = props.planet.getTask().taskEndsInMs(); | |
} | |
this.setState({ | |
remainingTime : remainingTime, | |
percentDone : props.planet.getTask().getCurrentTaskPercentDone() | |
}) | |
}, | |
__currentTaskIsActive: function(){ | |
return this.props.planet.getTask().getCurrentTaskId() === this.props.taskId; | |
}, | |
__getTaskTime: function(){ | |
var taskId = this.props.taskId; | |
var planet = this.props.planet; | |
if(taskId === constants.TASK_TYPES.GOLD_MINING){ | |
return planet.getTask().getMineGoldTaskTime(); | |
} | |
else if(taskId === constants.TASK_TYPES.BUILD_UNITS){ | |
return planet.getTask().getBuildUnitsTaskTime(); | |
} | |
else if(taskId === constants.TASK_TYPES.UPGRADE){ | |
return planet.getTask().getUpgradeTaskTime(); | |
} | |
else if(taskId === constants.TASK_TYPES.DISINFECT){ | |
return planet.getTask().getTaskTime(constants.TASK_TYPES.DISINFECT); | |
} | |
}, | |
__getTaskTitle: function(){ | |
var taskId = this.props.taskId; | |
return constants.getTaskBy('id', taskId).text; | |
}, | |
__getTaskState: function(){ | |
var planet = this.props.planet; | |
// The state of the task on the planet is one thing but the | |
// state of this task is another even if they are similar. | |
// This is relative to the current view task. | |
if(this.__currentTaskIsActive()){ | |
return planet.getTask().getCurrentTaskState(); | |
}else if(planet.getTask().isActive()){ | |
return 'disabled'; | |
}else{ | |
return 'idle'; | |
} | |
}, | |
__getTaskData: function(){ | |
var taskId = this.props.taskId; | |
var planet = this.props.planet; | |
var taskData = { | |
title: this.__getTaskTitle() | |
}; | |
if(taskId === constants.TASK_TYPES.GOLD_MINING){ | |
taskData.value = '+' + utils.autoformatNumber(planet.getProperty('goldMineQuantity')) + ' g' | |
taskData.needs = [ | |
{ | |
title : 'time', | |
value : planet.getTask().getMineGoldTaskTime(true) | |
} | |
] | |
} | |
else if(taskId === constants.TASK_TYPES.BUILD_UNITS){ | |
taskData.value = '+' + utils.autoformatNumber(planet.getProperty('unitBuildQuantity')) + ' u' | |
taskData.needs = [ | |
{ | |
title : 'time', | |
value : planet.getTask().getBuildUnitsTaskTime(true) | |
}, | |
{ | |
title : 'gold', | |
value : utils.autoformatNumber(planet.getTask().getTaskGold(constants.TASK_TYPES.BUILD_UNITS)) | |
} | |
] | |
} | |
else if(taskId === constants.TASK_TYPES.UPGRADE){ | |
} | |
else if(taskId === constants.TASK_TYPES.DISINFECT){ | |
taskData.value = '-' + utils.autoformatNumber(25) + ' %' | |
taskData.needs = [ | |
{ | |
title : 'time', | |
value : utils.autoformatNumber(planet.getTask().getTaskTime(constants.TASK_TYPES.DISINFECT, void 0, true)/1000) + ' s' | |
}, | |
{ | |
title : 'gold', | |
value : utils.autoformatNumber(planet.getTask().getTaskGold(constants.TASK_TYPES.DISINFECT)) | |
} | |
] | |
} | |
else if(taskId === constants.TASK_TYPES.REMOVE_OWNERSHIP){ | |
taskData.needs = [ | |
{ | |
title : 'time', | |
value : utils.autoformatNumber(planet.getTask().getTaskTime(constants.TASK_TYPES.REMOVE_OWNERSHIP, void 0, true)/1000) + ' s' | |
}, | |
{ | |
title : 'gold', | |
value : utils.autoformatNumber(planet.getTask().getTaskGold(constants.TASK_TYPES.REMOVE_OWNERSHIP)) | |
} | |
] | |
} | |
taskData.state = this.__getTaskState(); | |
taskData.stateDetails = this.__getClickInfo(); | |
return taskData; | |
}, | |
__getClickInfo: function(){ | |
var state = this.__getTaskState(); | |
var taskId = this.props.taskId; | |
if(state === 'running'){ | |
return this.props.currentPlayerIsOwner ? 'cancel this task' : ''; | |
}else if(state === 'done'){ | |
var currentTask = constants.getTaskBy('id', this.props.taskId); | |
return this.props.currentPlayerIsOwner ? currentTask.onDone : ''; | |
}else if(state === 'idle'){ | |
if(taskId === constants.TASK_TYPES.UPGRADE){ | |
return this.props.currentPlayerIsOwner ? 'view upgrades' : ''; | |
}else{ | |
return this.props.currentPlayerIsOwner ? 'start' : ''; | |
} | |
}else if(state === 'disabled'){ | |
return this.props.currentPlayerIsOwner ? 'wait for other tasks to end' : 'you don\'t own this planet'; | |
} | |
}, | |
getStateSection: function(taskData){ | |
var remainingTimeString = ''; | |
if(this.state.remainingTime){ | |
remainingTimeString = utils.autoformatNumber(this.state.remainingTime/1000) + | |
's remaining (' + Math.round(this.state.percentDone) + '%)'; | |
} | |
return React.DOM.span({ | |
className: 'state ' + taskData.state}, | |
'state: ' + taskData.state + ' ' + Math.round(this.state.percentDone) + '%') | |
}, | |
getProgressBarSection: function(){ | |
if(!this.__currentTaskIsActive()) return null; | |
return React.DOM.span({className: 'percent-done', | |
style: { | |
width: this.state.percentDone + '%' | |
} | |
}) | |
}, | |
getRequirementsSection: function(taskData){ | |
if(!taskData.needs) return null; | |
var needs = taskData.needs.map(function(need){ | |
return React.DOM.li({ | |
className: need.title | |
}, need.title + ' ' + need.value); | |
}) | |
return React.DOM.ul({className: 'more'}, needs); | |
}, | |
__showTooltip: function(e){ | |
// Set it directly on the state without setState because we don't | |
// need to update the UI here. The TooltipContainer will update it's UI. | |
this.state.tooltipId = this.props.tooltipStore.add({ | |
target : this.refs.root.getDOMNode(), | |
avoidOverlap : this.refs.root.getDOMNode().parentNode.parentNode, | |
text : this.getTooltipContents(), | |
direction : 'left' | |
}); | |
}, | |
__hideTooltip: function(){ | |
// Set it directly on the state without setState because we don't | |
// need to update the UI here. The TooltipContainer will update it's UI. | |
this.props.tooltipStore.remove(this.state.tooltipId); | |
}, | |
getTooltipContents: function(){ | |
var taskData = this.__getTaskData(); | |
var planet = this.props.planet; | |
var currentTask = constants.getTaskBy('id', this.props.taskId); | |
var children = []; | |
children.push(taskData.title + ': ' + currentTask.details); | |
if(taskData.value !== void 0){ | |
children.push(React.DOM.li({}, 'You will get: ')); | |
children.push(React.DOM.li({}, taskData.value)) | |
} | |
if(this.__currentTaskIsActive()){ | |
children.push(React.DOM.li({}, this.getStateSection(taskData))) | |
children.push(React.DOM.li({}, taskData.stateDetails)) | |
}else{ | |
children.push(React.DOM.li({}, 'You need: ')); | |
children.push(React.DOM.li({}, this.getRequirementsSection(taskData))); | |
} | |
if(this.__currentTaskIsActive()){ | |
children.push(React.DOM.li({}, this.getProgressBarSection())); | |
} | |
return React.DOM.ul({}, children); | |
}, | |
render: function(){ | |
var title; | |
var state = this.__getTaskState(); | |
if(state === 'done'){ | |
title = this.__getClickInfo(); | |
}else{ | |
title = this.__getTaskTitle(); | |
} | |
return React.DOM.li({ | |
className : 'item ' + state, | |
ref : 'root' | |
}, | |
React.DOM.div({ | |
onClick : this.props.onClick, | |
className : 'main-action' | |
}, | |
this.getProgressBarSection(), | |
React.DOM.h3({className: 'title'}, title) | |
), | |
React.DOM.div({ | |
className : 'help', | |
onMouseEnter : this.__showTooltip, | |
onMouseLeave : this.__hideTooltip, | |
}, '?') | |
); | |
} | |
}); | |
return ReactComponent; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define(function(require, exports, module){ | |
var _ = require('underscore'); | |
var constants = require('constants'); | |
var utils = require('utils'); | |
// This is a 2 way fn because we need to know the level | |
// based on xp and reverse (xp required per level). | |
// We use the `Math.floor` because will not create errors | |
// when the xp approach the limit of the levels. | |
var levelToXp = function(level){ | |
return Math.floor(Math.pow(level, 3)) | |
} | |
var xpToLevel = function(xp){ | |
return Math.floor(Math.pow(xp, 1/3)); | |
} | |
var levelToPlanets = function(level){ | |
return constants.PLANETS_GIVEN_TO_NEW_PLAYERS + Math.floor(Math.pow(level, 1/0.75)) | |
} | |
var planetsToLevel = function(planets){ | |
return Math.floor(Math.pow(planets, 0.75)) - constants.PLANETS_GIVEN_TO_NEW_PLAYERS; | |
} | |
// Test the player level fn. | |
// var i = 1000; | |
// while(i){ | |
// i -= 1; | |
// var level = xpToLevel(i); | |
// var xp = levelToXp(level); | |
// console.log(i, xp, level) | |
// } | |
var PlayerLogic = function(playerData){ | |
_.extend(this, playerData); | |
// This is necessary because the player on the planet's or ships owner are not full players. | |
this.fullPlayer = true; | |
} | |
PlayerLogic.STATES = { | |
OFFLINE : 0, | |
ONLINE : 1, | |
NOT_FOCUSSED : 2 | |
} | |
_.extend(PlayerLogic.prototype, { | |
usernameIsMissing: function(){ | |
// This is not 100% bulletproof but will work 99.9999% | |
// because no user would have his username the exact same as | |
// his internal id. | |
return this._id === this.username; | |
}, | |
getLevel: function(){ | |
return xpToLevel(this.XP); | |
}, | |
// This will get the seconds played at any time. | |
computeSecondsPlayed: function(){ | |
var currentIntervalSeconds = Math.round((+new Date - this.stats.lastTimeOnline)/1000); | |
return this.stats.secondsPlayed + currentIntervalSeconds; | |
}, | |
// Will return the number of XP needed for this level. | |
getThisLevelXP: function(){ | |
var level = this.getLevel(); | |
return levelToXp(level); | |
}, | |
getNextLevelXP: function(){ | |
var level = this.getLevel(); | |
return levelToXp(level + 1); | |
}, | |
getMaxPlanets: function(){ | |
return levelToPlanets(this.getLevel()); | |
}, | |
getNumOfPlanetsOwned: function(){ | |
return this.stats.planetsOwnedN; | |
}, | |
increaseNumOfPlanetsOwned: function(value){ | |
if(!value) return; | |
this.stats.planetsOwnedN += value; | |
if(this.stats.planetsOwnedN < 0) this.stats.planetsOwnedN = 0; | |
if(this.stats.planetsOwnedN > this.getMaxPlanets()) throw new Error('You should not increase the # of planets because the limits are off.') | |
}, | |
canHaveMorePlanets: function(howMany){ | |
howMany = howMany === void 0 ? 1 : howMany; | |
return this.stats.planetsOwnedN + howMany <= this.getMaxPlanets() | |
}, | |
getTotalGoldMined: function(){ | |
return this.stats.totalGoldMined; | |
}, | |
setState: function(newState){ | |
var stateChanged = this.state !== newState; | |
prevState = this.state; | |
this.state = newState; | |
var STATES = PlayerLogic.STATES | |
if(newState === STATES.ONLINE){ | |
this.stats.lastTimeOnline = new Date(); | |
} | |
this.lastPing = new Date(); | |
if([STATES.OFFLINE, STATES.NOT_FOCUSSED].indexOf(newState) !== -1 && prevState === STATES.ONLINE){ | |
this.stats.secondsPlayed = this.computeSecondsPlayed(); | |
} | |
} | |
}) | |
return PlayerLogic; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define(function(require, exports, module){ | |
var _ = require('underscore'); | |
var utils = require('utils'); | |
var constants = require('client/constants'); | |
var PlanetUpgrades = require('client/upgrades/PlanetUpgrades'); | |
var helpers = { | |
getNearestPlanet: function(planet, availablePlanets, notAvailablePlanets){ | |
var closest = Infinity; | |
var choosenPlanetB; | |
var i = availablePlanets.length; | |
while(i--){ | |
var planetB = availablePlanets[i]; | |
if(notAvailablePlanets.indexOf(planetB) !== -1) continue; | |
var dist = utils.distanceBetween2Points(planet.pos, planetB.pos); | |
if(dist < closest){ | |
closest = dist; | |
choosenPlanetB = planetB; | |
} | |
} | |
return choosenPlanetB; | |
}, | |
getNearestPlanets: function(availablePlanets, howMany){ | |
var nearestPlanets = []; | |
var i = howMany; | |
var planet; | |
while(i--){ | |
planet = helpers.getNearestPlanet(availablePlanets[0], availablePlanets, nearestPlanets); | |
if(planet){ | |
nearestPlanets.push(planet) | |
} | |
} | |
return nearestPlanets; | |
}, | |
// You give this fn the planets you want to be reachable (targetPlanets) | |
// and the planets you have available (availablePlanets). Then it returns | |
// the first reachable planet. Then you specify the limit which means | |
// that the algorithm will stop after finding X planets in range. | |
findInRangePlanets: function(targetPlanets, availablePlanets, limit){ | |
var limit = limit || 1; | |
var l = availablePlanets.length; | |
var availablePlanet; | |
var nearestPlanet; | |
var minCapacity = 50; | |
var targetPlanet; | |
var k = targetPlanets.length; | |
var results = []; | |
for(var i = 0; i < l; i++){ | |
if(results.length >= limit) break | |
availablePlanet = availablePlanets[i]; | |
var range = availablePlanet.getRange(); | |
for(var j = 0; j < k; j++){ | |
targetPlanet = targetPlanets[j]; | |
var distance = helpers.getDistance(targetPlanet, availablePlanet); | |
if(distance < range){ | |
// You don't need to check if this already exists in the results | |
// because the loop don't run twice for the same combination. | |
results.push({ | |
to : targetPlanet, | |
from : availablePlanet, | |
distance : distance | |
}) | |
} | |
} | |
} | |
function shuffle(o){ //v1.0 | |
for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x); | |
return o; | |
}; | |
return shuffle(results); | |
}, | |
// name: of the upgrade | |
// dataType: [time, gold, value, level, unitType] | |
// atLevel: undefined will return the values at the planet level, if specified will return at that level. | |
getUpgrade : function(nameOrId, dataType, atLevel){ | |
var upgrade; | |
if(_.isNumber(nameOrId)){ | |
upgrade = PlanetUpgrades[nameOrId]; | |
}else{ | |
upgrade = PlanetUpgrades.getByName(nameOrId); | |
} | |
if(!upgrade){ | |
return false; console.error(new Error('No upgrade nameOrId: "' + nameOrId + '" found.')); | |
} | |
var upgradeId = upgrade.id; | |
if(atLevel === void 0){ | |
atLevel = this.upgrades[upgradeId] || 0 | |
} | |
if(dataType === 'level'){ | |
return atLevel; | |
}else if(!dataType){ | |
return upgrade; | |
}else{ | |
var map = { | |
time : 'getTime', | |
gold : 'getPrice', | |
price : 'getPrice', | |
value : 'getValue', | |
level : null, | |
unitType : 'unitType' | |
} | |
var upgradeMethod = map[dataType]; | |
var fn = upgrade[upgradeMethod]; | |
if(_.isFunction(fn)){ | |
return fn(atLevel) | |
}else{ | |
var value = fn; | |
return value; | |
} | |
} | |
}, | |
getDistance : function(fromPlanet, toPlanet){ | |
var distance = utils.distanceBetween2Points({x: fromPlanet.pos.x, y: fromPlanet.pos.y}, | |
{x: toPlanet.pos.x , y: toPlanet.pos.y }); | |
var radiusFix = fromPlanet.getRadius() + toPlanet.getRadius(); | |
return distance - radiusFix; | |
}, | |
planetHasSameOwnerAsShip: function(planet, ship){ | |
return planet.owner && planet.owner.username === ship.owner.username | |
}, | |
// Options: | |
// check the send ship | |
// | |
// Returns: | |
// err : a string if can't send ship | |
// data : true if can send ship | |
canSendShip: function(options){ | |
var fromPlanet = options.fromPlanet; | |
var toPlanet = options.toPlanet; | |
var player = options.player; | |
if(options.units < 0) return {err: 'negative units'}; | |
if(!fromPlanet.owner) return {err: 'no owner on from planet'}; | |
if(!player) return {err: 'no player provided'}; | |
if(!options.isBot && fromPlanet.owner.username !== player.username){ | |
return {err: 'current player is not the owner of the from planet'}; | |
} | |
if(fromPlanet._id === toPlanet._id) return {err: 'same planet'}; | |
if(fromPlanet.units === 0) return {err: 'units === 0'}; | |
if(fromPlanet.units < options.units) return {err: 'units < than requested'}; | |
var distance = helpers.getDistance(fromPlanet, toPlanet); | |
var maxDistance = fromPlanet.getRange(); | |
if(distance > maxDistance) return {err: 'distance: '+ distance + ' > ' + maxDistance}; | |
if(player.isGhost){ | |
if(toPlanet.hasOwner() && toPlanet.owner.username !== player.username){ | |
return {err: 'isGhost'}; | |
} | |
} | |
return {data: true}; | |
}, | |
// Options: | |
// check the send ship | |
// | |
// Returns: | |
// shipData | |
createShip: function(options){ | |
var fromPlanet = options.fromPlanet; | |
var toPlanet = options.toPlanet; | |
var player = options.player; | |
var shipData = { | |
owner: { | |
username : player.username, | |
color : player.color, | |
colorB : player.colorB, | |
colorC : player.colorC, | |
isBot : player.bot.isBot | |
}, | |
from: { | |
x : fromPlanet.pos.x, | |
y : fromPlanet.pos.y, | |
block : fromPlanet.block.id | |
}, | |
to: { | |
x : toPlanet.pos.x, | |
y : toPlanet.pos.y, | |
block : toPlanet.block.id | |
}, | |
// Enforce positive and absolute numbers. | |
units : Math.round(Math.abs(options.units)), | |
// Ensure is a copy so no unexpected changes will be reflected in the planet. | |
upgrades : fromPlanet.upgrades.slice(0), | |
geneticCode : fromPlanet.geneticCode.slice(0), | |
} | |
if(options.isBot) shipData.owner.isBot = true; | |
return shipData; | |
}, | |
// This will actually change the inputs you provided. | |
// | |
// Options: | |
// fromPlanet | |
// toPlanet | |
// player | |
// isBot | |
// units | |
// | |
// Returns: | |
// err | |
// data | |
// ship | |
// fromPlanet | |
// apiArgs: this can used to send the request without anymore data processing | |
sendShip: function(options){ | |
var fromPlanet = options.fromPlanet; | |
var toPlanet = options.toPlanet; | |
var canSendShip = helpers.canSendShip(options) | |
if(canSendShip.err) return canSendShip; | |
if(!canSendShip.data) return {err: 'unknown error'}; | |
var ship = helpers.createShip(options); | |
if(fromPlanet.units < ship.units) return {err: 'planet.units is negative'}; | |
fromPlanet.subtractUnits(ship.units); | |
return { | |
data: ship, | |
apiArgs: { | |
fromPlanetId : fromPlanet._id, | |
toPlanetId : toPlanet._id, | |
units : ship.units | |
} | |
}; | |
}, | |
onShipDestinationReached: function(ship, planet, shipOwner, planetOwner){ | |
if(!shipOwner) return {err: 'no ship owner'} | |
var friendlyPlanet = planet.owner && ship.owner && ship.owner.username === planet.owner.username; | |
if(planet && !planetOwner){ | |
// On the client side we don't need the planetOwner but on the server side | |
// we need it in order to update XP and planets owned. We could load the | |
// planetPlayer on the client side if needed later, but for now will make | |
// stuff more complex and requires another request. | |
} | |
var warn; | |
var changeOwner = false; | |
if(friendlyPlanet){ | |
planet.increaseUnits(ship.units); | |
}else{ | |
var shipAttack = ship.getProperty('shipAttack'); | |
var planetDefense = planet.getProperty('planetDefense'); | |
// So it's a lot easier to conquer an unOwned planet | |
var temp = planetDefense + shipAttack | |
var virtualUnits = planet.units * planetDefense / temp - ship.units * shipAttack / temp; | |
var newUnits = Math.round(virtualUnits); | |
if(virtualUnits < 0){ | |
newUnits = Math.min(-virtualUnits, ship.units); | |
changeOwner = true; | |
} | |
if(newUnits < 0){ | |
newUnits = 0; | |
} | |
var destroyedUnits = 0; | |
if(changeOwner){ | |
destroyedUnits = planet.units; | |
}else{ | |
destroyedUnits = planet.units - newUnits; | |
} | |
var multiplier = planet.isUnowned() ? constants.XP.UNOWNED_MULTIPLIER : 1; | |
// On the client side you don't care about increasing enemies XP | |
if(!shipOwner.XP) shipOwner.XP = 0; | |
shipOwner.XP += destroyedUnits * constants.XP.PER_UNIT_DESTROYED * planetDefense * multiplier; | |
// This will lead to client side inconsistencies because this will never happen when the ship | |
// owner is not the current player. This is fixed when the block is updated. | |
if(changeOwner && shipOwner.fullPlayer){ | |
if(!shipOwner.canHaveMorePlanets(1)){ | |
changeOwner = false; | |
// We set units to 0 because the ship has destroyed all the units but can't own | |
// this new planet therefore the planet will remain empty. | |
newUnits = 0; | |
warn = "You can't own more planets. You need a higher level." | |
} | |
} | |
planet.setUnits(newUnits); | |
if(changeOwner){ | |
// Ship owner always exists. But: | |
// - On the client will only be full player if is the current player; | |
// - On the server will always be full player. | |
if(shipOwner.fullPlayer){ | |
shipOwner.increaseNumOfPlanetsOwned(1); | |
shipOwner.XP += constants.XP.PER_PLANET_OWNED * planetDefense * multiplier; | |
} | |
// We will have planet owner: | |
// - On the client only when is the planet owner is the current player; | |
// - On the server side all the time; | |
if(planetOwner && planetOwner.fullPlayer){ | |
planetOwner.increaseNumOfPlanetsOwned(-1) | |
} | |
// On the client side the shipOwner doesn't have the ownsBlocksIds property because | |
// is only the cached object located as ship.owner. | |
if(shipOwner.ownsBlocksIds && shipOwner.ownsBlocksIds.indexOf(planet.block.id) === -1){ | |
shipOwner.ownsBlocksIds.push(planet.block.id); | |
} | |
} | |
shipOwner.XP = Math.round(shipOwner.XP*100)/100; | |
} | |
planet.getGenetics().mutateByShip(ship); | |
var planetUnits = planet.units; | |
if(changeOwner){ | |
// This only sets the owner data stored in the ship to the new | |
// planet. The ship.owner contains less data than shipOwner. | |
planet.owner = ship.owner; | |
}else if(planetUnits === 0){ | |
// Lucky owner, this planet is still his. | |
} | |
ship.destinationReached = true; | |
return { | |
data: { | |
friendly : friendlyPlanet, | |
destroyedUnits : destroyedUnits, | |
ownerChanged : changeOwner, | |
warn : warn | |
} | |
}; | |
}, | |
} | |
return helpers; | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define(function(require, exports, module){ | |
var utils = require('client/utils'); | |
var constants = require('client/constants'); | |
var React = require('react'); | |
var ReactComponent = React.createClass({ | |
getInitialState: function(){ | |
return { | |
tooltipBB: { | |
top : 0, | |
right : 0, | |
bottom : 0, | |
left : 0, | |
width : 0, | |
height : 0 | |
}, | |
blink: true | |
} | |
}, | |
componentDidMount: function(){ | |
// We need to make another update after the tooltip is first rendered | |
// to know it's width and height. This is needed in order to make the | |
// tooltip to center itself around some point. | |
// Commented at this moment because the tooltip will need to update | |
// on each frame because when scrolling will not keep the correct | |
// position relative to the target object. | |
// this.setState({ | |
// tooltipBB : this.refs.root.getDOMNode().getBoundingClientRect() | |
// }); | |
}, | |
render: function(){ | |
var targetBB = this.props.tooltip.target.getBoundingClientRect(); | |
var avoidOverlapBB = this.props.tooltip.avoidOverlap.getBoundingClientRect(); | |
var tooltipBB; | |
if(this.refs.root){ | |
tooltipBB = this.refs.root.getDOMNode().getBoundingClientRect(); | |
}else{ | |
tooltipBB = { | |
top : 0, | |
right : 0, | |
bottom : 0, | |
left : 0, | |
width : 0, | |
height : 0 | |
} | |
} | |
var viewportSize = { | |
height: window.innerHeight | |
} | |
var direction = this.props.tooltip.direction; | |
var offsetX = 10; | |
var lineHeight = 2; | |
var tooltipStyle = { | |
top : targetBB.top + targetBB.height / 2 - tooltipBB.height / 2, | |
}; | |
// Hide the tooltip on the first frame, as it will not be | |
// in the correct position and will create some bad animations. | |
if(tooltipBB.width === 0){ | |
tooltipStyle.opacity = 0; | |
} | |
// Keep the tooltip in the view (top and bottom correction). | |
if(tooltipStyle.top + tooltipBB.height > viewportSize.height){ | |
tooltipStyle.top -= tooltipStyle.top + tooltipBB.height - viewportSize.height | |
}else if(tooltipStyle.top < 0){ | |
tooltipStyle.top = 0; | |
} | |
var lineStyle = { | |
top : targetBB.top + targetBB.height / 2 - 1 | |
} | |
// Makes the line to don't be hidden and to have an offset from the edges. | |
var lineOffsetEdges = 5; | |
if(lineStyle.top + lineOffsetEdges + lineHeight > viewportSize.height){ | |
lineStyle.top -= lineStyle.top + lineOffsetEdges + lineHeight - viewportSize.height | |
} | |
if(lineStyle.top < 0){ | |
lineStyle.top = lineOffsetEdges; | |
} | |
if(direction === 'right'){ | |
tooltipStyle.left = avoidOverlapBB.left + avoidOverlapBB.width + offsetX; | |
lineStyle.left = targetBB.left + targetBB.width; | |
lineStyle.width = avoidOverlapBB.left + avoidOverlapBB.width + offsetX - (targetBB.left + targetBB.width) | |
}else if(direction === 'left'){ | |
tooltipStyle.left = avoidOverlapBB.left - tooltipBB.width - offsetX; | |
lineStyle.left = tooltipStyle.left + tooltipBB.width; | |
lineStyle.width = targetBB.left - lineStyle.left; | |
lineStyle.width = lineStyle.width < 0 ? -lineStyle.width : lineStyle.width; | |
} | |
return React.DOM.div({className: 'tooltip-wrapper'}, | |
React.DOM.div({ | |
className : 'tooltip' + (this.state.blink ? ' blink' : ''), | |
ref : 'root', | |
style : tooltipStyle | |
}, this.props.tooltip.text), | |
React.DOM.div({ | |
className: 'line', | |
style: lineStyle | |
}) | |
) | |
} | |
}); | |
return ReactComponent; | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment