/** *Submitted for verification at Etherscan.io on 2018-05-14 */ pragma solidity 0.4.23; /** * @title SafeMath * @dev Math operations with safety checks that throw on error */ library SafeMath { /** * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { assert(b <= a); return a - b; } /** * @dev Adds two numbers, throws on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; assert(c >= a); return c; } } /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control * functions, this simplifies the implementation of "user permissions". */ contract Ownable { address internal contractOwner; constructor () internal { if(contractOwner == address(0)){ contractOwner = msg.sender; } } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(msg.sender == contractOwner); _; } /** * @dev Allows the current owner to transfer control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ function transferOwnership(address newOwner) public onlyOwner { require(newOwner != address(0)); contractOwner = newOwner; } } /** * @title Pausable * @dev Base contract which allows children to implement an emergency stop mechanism. */ contract Pausable is Ownable { bool private paused = false; /** * @dev Modifier to allow actions only when the contract IS paused @dev If is paused msg.value is send back */ modifier whenNotPaused() { if(paused == true && msg.value > 0){ msg.sender.transfer(msg.value); } require(!paused); _; } /** * @dev Called by the owner to pause, triggers stopped state */ function triggerPause() onlyOwner external { paused = !paused; } } /// @title A contract for creating new champs and making withdrawals contract ChampFactory is Pausable{ event NewChamp(uint256 champID, address owner); using SafeMath for uint; //SafeMath for overflow prevention /* * Variables */ struct Champ { uint256 id; //same as position in Champ[] uint256 attackPower; uint256 defencePower; uint256 cooldownTime; //how long does it take to be ready attack again uint256 readyTime; //if is smaller than block.timestamp champ is ready to fight uint256 winCount; uint256 lossCount; uint256 position; //position in leaderboard. subtract 1 and you got position in leaderboard[] uint256 price; //selling price uint256 withdrawCooldown; //if you one of the 800 best champs and withdrawCooldown is less as block.timestamp then you get ETH reward uint256 eq_sword; uint256 eq_shield; uint256 eq_helmet; bool forSale; //is champ for sale? } struct AddressInfo { uint256 withdrawal; uint256 champsCount; uint256 itemsCount; string name; } //Item struct struct Item { uint8 itemType; // 1 - Sword | 2 - Shield | 3 - Helmet uint8 itemRarity; // 1 - Common | 2 - Uncommon | 3 - Rare | 4 - Epic | 5 - Legendery | 6 - Forged uint256 attackPower; uint256 defencePower; uint256 cooldownReduction; uint256 price; uint256 onChampId; //can not be used to decide if item is on champ, because champ's id can be 0, 'bool onChamp' solves it. bool onChamp; bool forSale; //is item for sale? } mapping (address => AddressInfo) public addressInfo; mapping (uint256 => address) public champToOwner; mapping (uint256 => address) public itemToOwner; mapping (uint256 => string) public champToName; Champ[] public champs; Item[] public items; uint256[] public leaderboard; uint256 internal createChampFee = 5 finney; uint256 internal lootboxFee = 5 finney; uint256 internal pendingWithdrawal = 0; uint256 private randNonce = 0; //being used in generating random numbers uint256 public champsForSaleCount; uint256 public itemsForSaleCount; /* * Modifiers */ /// @dev Checks if msg.sender is owner of champ modifier onlyOwnerOfChamp(uint256 _champId) { require(msg.sender == champToOwner[_champId]); _; } /// @dev Checks if msg.sender is NOT owner of champ modifier onlyNotOwnerOfChamp(uint256 _champId) { require(msg.sender != champToOwner[_champId]); _; } /// @notice Checks if amount was sent modifier isPaid(uint256 _price){ require(msg.value >= _price); _; } /// @notice People are allowed to withdraw only if min. balance (0.01 gwei) is reached modifier contractMinBalanceReached(){ require( (address(this).balance).sub(pendingWithdrawal) > 1000000 ); _; } /// @notice Checks if withdraw cooldown passed modifier isChampWithdrawReady(uint256 _id){ require(champs[_id].withdrawCooldown < block.timestamp); _; } /// @notice Distribute input funds between contract owner and players modifier distributeInput(address _affiliateAddress){ //contract owner uint256 contractOwnerWithdrawal = (msg.value / 100) * 50; // 50% addressInfo[contractOwner].withdrawal += contractOwnerWithdrawal; pendingWithdrawal += contractOwnerWithdrawal; //affiliate //checks if _affiliateAddress is set & if affiliate address is not buying player if(_affiliateAddress != address(0) && _affiliateAddress != msg.sender){ uint256 affiliateBonus = (msg.value / 100) * 25; //provision is 25% addressInfo[_affiliateAddress].withdrawal += affiliateBonus; pendingWithdrawal += affiliateBonus; } _; } /* * View */ /// @notice Gets champs by address /// @param _owner Owner address function getChampsByOwner(address _owner) external view returns(uint256[]) { uint256[] memory result = new uint256[](addressInfo[_owner].champsCount); uint256 counter = 0; for (uint256 i = 0; i < champs.length; i++) { if (champToOwner[i] == _owner) { result[counter] = i; counter++; } } return result; } /// @notice Gets total champs count function getChampsCount() external view returns(uint256){ return champs.length; } /// @notice Gets champ's reward in wei function getChampReward(uint256 _position) public view returns(uint256) { if(_position <= 800){ //percentageMultipier = 10,000 //maxReward = 2000 = .2% * percentageMultipier //subtractPerPosition = 2 = .0002% * percentageMultipier //2000 - (2 * (_position - 1)) uint256 rewardPercentage = uint256(2000).sub(2 * (_position - 1)); //available funds are all funds - already pending uint256 availableWithdrawal = address(this).balance.sub(pendingWithdrawal); //calculate reward for champ's position //1000000 = percentageMultipier * 100 return availableWithdrawal / 1000000 * rewardPercentage; }else{ return uint256(0); } } /* * Internal */ /// @notice Generates random modulus /// @param _modulus Max random modulus function randMod(uint256 _modulus) internal returns(uint256) { randNonce++; return uint256(keccak256(randNonce, blockhash(block.number - 1))) % _modulus; } /* * External */ /// @notice Creates new champ /// @param _affiliateAddress Affiliate address (optional) function createChamp(address _affiliateAddress) external payable whenNotPaused isPaid(createChampFee) distributeInput(_affiliateAddress) { /* Champ memory champ = Champ({ id: 0, attackPower: 2 + randMod(4), defencePower: 1 + randMod(4), cooldownTime: uint256(1 days) - uint256(randMod(9) * 1 hours), readyTime: 0, winCount: 0, lossCount: 0, position: leaderboard.length + 1, //Last place in leaderboard is new champ's position. Used in new champ struct bellow. +1 to avoid zero position. price: 0, withdrawCooldown: uint256(block.timestamp), eq_sword: 0, eq_shield: 0, eq_helmet: 0, forSale: false }); */ // This line bellow is about 30k gas cheaper than lines above. They are the same. Lines above are just more readable. uint256 id = champs.push(Champ(0, 2 + randMod(4), 1 + randMod(4), uint256(1 days) - uint256(randMod(9) * 1 hours), 0, 0, 0, leaderboard.length + 1, 0, uint256(block.timestamp), 0,0,0, false)) - 1; champs[id].id = id; //sets id in Champ struct leaderboard.push(id); //push champ on the last place in leaderboard champToOwner[id] = msg.sender; //sets owner of this champ - msg.sender addressInfo[msg.sender].champsCount++; emit NewChamp(id, msg.sender); } /// @notice Change "CreateChampFee". If ETH price will grow up it can expensive to create new champ. /// @param _fee New "CreateChampFee" /// @dev Only owner of contract can change "CreateChampFee" function setCreateChampFee(uint256 _fee) external onlyOwner { createChampFee = _fee; } /// @notice Change champ's name function changeChampsName(uint _champId, string _name) external onlyOwnerOfChamp(_champId){ champToName[_champId] = _name; } /// @notice Change players's name function changePlayersName(string _name) external { addressInfo[msg.sender].name = _name; } /// @notice Withdraw champ's reward /// @param _id Champ id /// @dev Move champ reward to pending withdrawal to his wallet. function withdrawChamp(uint _id) external onlyOwnerOfChamp(_id) contractMinBalanceReached isChampWithdrawReady(_id) whenNotPaused { Champ storage champ = champs[_id]; require(champ.position <= 800); champ.withdrawCooldown = block.timestamp + 1 days; //one withdrawal 1 per day uint256 withdrawal = getChampReward(champ.position); addressInfo[msg.sender].withdrawal += withdrawal; pendingWithdrawal += withdrawal; } /// @dev Send all pending funds of caller's address function withdrawToAddress(address _address) external whenNotPaused { address playerAddress = _address; if(playerAddress == address(0)){ playerAddress = msg.sender; } uint256 share = addressInfo[playerAddress].withdrawal; //gets pending funds require(share > 0); //is it more than 0? //first sets players withdrawal pending to 0 and subtract amount from playerWithdrawals then transfer funds to avoid reentrancy addressInfo[playerAddress].withdrawal = 0; //set player's withdrawal pendings to 0 pendingWithdrawal = pendingWithdrawal.sub(share); //subtract share from total pendings playerAddress.transfer(share); //transfer } } /// @title Moderates items and creates new ones contract Items is ChampFactory { event NewItem(uint256 itemID, address owner); constructor () internal { //item -> nothing items.push(Item(0, 0, 0, 0, 0, 0, 0, false, false)); } /* * Modifiers */ /// @notice Checks if sender is owner of item modifier onlyOwnerOfItem(uint256 _itemId) { require(_itemId != 0); require(msg.sender == itemToOwner[_itemId]); _; } /// @notice Checks if sender is NOT owner of item modifier onlyNotOwnerOfItem(uint256 _itemId) { require(msg.sender != itemToOwner[_itemId]); _; } /* * View */ ///@notice Check if champ has something on ///@param _type Sword, shield or helmet function hasChampSomethingOn(uint _champId, uint8 _type) internal view returns(bool){ Champ storage champ = champs[_champId]; if(_type == 1){ return (champ.eq_sword == 0) ? false : true; } if(_type == 2){ return (champ.eq_shield == 0) ? false : true; } if(_type == 3){ return (champ.eq_helmet == 0) ? false : true; } } /// @notice Gets items by address /// @param _owner Owner address function getItemsByOwner(address _owner) external view returns(uint256[]) { uint256[] memory result = new uint256[](addressInfo[_owner].itemsCount); uint256 counter = 0; for (uint256 i = 0; i < items.length; i++) { if (itemToOwner[i] == _owner) { result[counter] = i; counter++; } } return result; } /* * Public */ ///@notice Takes item off champ function takeOffItem(uint _champId, uint8 _type) public onlyOwnerOfChamp(_champId) { uint256 itemId; Champ storage champ = champs[_champId]; if(_type == 1){ itemId = champ.eq_sword; //Get item ID if (itemId > 0) { //0 = nothing champ.eq_sword = 0; //take off sword } } if(_type == 2){ itemId = champ.eq_shield; //Get item ID if(itemId > 0) {//0 = nothing champ.eq_shield = 0; //take off shield } } if(_type == 3){ itemId = champ.eq_helmet; //Get item ID if(itemId > 0) { //0 = nothing champ.eq_helmet = 0; //take off } } if(itemId > 0){ items[itemId].onChamp = false; //item is free to use, is not on champ } } /* * External */ ///@notice Puts item on champ function putOn(uint256 _champId, uint256 _itemId) external onlyOwnerOfChamp(_champId) onlyOwnerOfItem(_itemId) { Champ storage champ = champs[_champId]; Item storage item = items[_itemId]; //checks if items is on some other champ if(item.onChamp){ takeOffItem(item.onChampId, item.itemType); //take off from champ } item.onChamp = true; //item is on champ item.onChampId = _champId; //champ's id //put on if(item.itemType == 1){ //take off actual sword if(champ.eq_sword > 0){ takeOffItem(champ.id, 1); } champ.eq_sword = _itemId; //put on sword } if(item.itemType == 2){ //take off actual shield if(champ.eq_shield > 0){ takeOffItem(champ.id, 2); } champ.eq_shield = _itemId; //put on shield } if(item.itemType == 3){ //take off actual helmet if(champ.eq_helmet > 0){ takeOffItem(champ.id, 3); } champ.eq_helmet = _itemId; //put on helmet } } /// @notice Opens loot box and generates new item function openLootbox(address _affiliateAddress) external payable whenNotPaused isPaid(lootboxFee) distributeInput(_affiliateAddress) { uint256 pointToCooldownReduction; uint256 randNum = randMod(1001); //random number <= 1000 uint256 pointsToShare; //total points given uint256 itemID; //sets up item Item memory item = Item({ itemType: uint8(uint256(randMod(3) + 1)), //generates item type - max num is 2 -> 0 + 1 SWORD | 1 + 1 SHIELD | 2 + 1 HELMET; itemRarity: uint8(0), attackPower: 0, defencePower: 0, cooldownReduction: 0, price: 0, onChampId: 0, onChamp: false, forSale: false }); // Gets Rarity of item // 45% common // 27% uncommon // 19% rare // 7% epic // 2% legendary if(450 > randNum){ pointsToShare = 25 + randMod(9); //25 basic + random number max to 8 item.itemRarity = uint8(1); }else if(720 > randNum){ pointsToShare = 42 + randMod(17); //42 basic + random number max to 16 item.itemRarity = uint8(2); }else if(910 > randNum){ pointsToShare = 71 + randMod(25); //71 basic + random number max to 24 item.itemRarity = uint8(3); }else if(980 > randNum){ pointsToShare = 119 + randMod(33); //119 basic + random number max to 32 item.itemRarity = uint8(4); }else{ pointsToShare = 235 + randMod(41); //235 basic + random number max to 40 item.itemRarity = uint8(5); } //Gets type of item if(item.itemType == uint8(1)){ //ITEM IS SWORDS item.attackPower = pointsToShare / 10 * 7; //70% attackPower pointsToShare -= item.attackPower; //points left; item.defencePower = pointsToShare / 10 * randMod(6); //up to 15% defencePower pointsToShare -= item.defencePower; //points left; item.cooldownReduction = pointsToShare * uint256(1 minutes); //rest of points is cooldown reduction item.itemType = uint8(1); } if(item.itemType == uint8(2)){ //ITEM IS SHIELD item.defencePower = pointsToShare / 10 * 7; //70% defencePower pointsToShare -= item.defencePower; //points left; item.attackPower = pointsToShare / 10 * randMod(6); //up to 15% attackPowerPower pointsToShare -= item.attackPower; //points left; item.cooldownReduction = pointsToShare * uint256(1 minutes); //rest of points is cooldown reduction item.itemType = uint8(2); } if(item.itemType == uint8(3)){ //ITEM IS HELMET pointToCooldownReduction = pointsToShare / 10 * 7; //70% cooldown reduction item.cooldownReduction = pointToCooldownReduction * uint256(1 minutes); //points to time pointsToShare -= pointToCooldownReduction; //points left; item.attackPower = pointsToShare / 10 * randMod(6); //up to 15% attackPower pointsToShare -= item.attackPower; //points left; item.defencePower = pointsToShare; //rest of points is defencePower item.itemType = uint8(3); } itemID = items.push(item) - 1; itemToOwner[itemID] = msg.sender; //sets owner of this item - msg.sender addressInfo[msg.sender].itemsCount++; //every address has count of items emit NewItem(itemID, msg.sender); } /// @notice Change "lootboxFee". /// @param _fee New "lootboxFee" /// @dev Only owner of contract can change "lootboxFee" function setLootboxFee(uint _fee) external onlyOwner { lootboxFee = _fee; } } /// @title Moderates buying and selling items contract ItemMarket is Items { event TransferItem(address from, address to, uint256 itemID); /* * Modifiers */ ///@notice Checks if item is for sale modifier itemIsForSale(uint256 _id){ require(items[_id].forSale); _; } ///@notice Checks if item is NOT for sale modifier itemIsNotForSale(uint256 _id){ require(items[_id].forSale == false); _; } ///@notice If item is for sale then cancel sale modifier ifItemForSaleThenCancelSale(uint256 _itemID){ Item storage item = items[_itemID]; if(item.forSale){ _cancelItemSale(item); } _; } ///@notice Distribute sale eth input modifier distributeSaleInput(address _owner) { uint256 contractOwnerCommision; //1% uint256 playerShare; //99% if(msg.value > 100){ contractOwnerCommision = (msg.value / 100); playerShare = msg.value - contractOwnerCommision; }else{ contractOwnerCommision = 0; playerShare = msg.value; } addressInfo[_owner].withdrawal += playerShare; addressInfo[contractOwner].withdrawal += contractOwnerCommision; pendingWithdrawal += playerShare + contractOwnerCommision; _; } /* * View */ function getItemsForSale() view external returns(uint256[]){ uint256[] memory result = new uint256[](itemsForSaleCount); if(itemsForSaleCount > 0){ uint256 counter = 0; for (uint256 i = 0; i < items.length; i++) { if (items[i].forSale == true) { result[counter]=i; counter++; } } } return result; } /* * Private */ ///@notice Cancel sale. Should not be called without checking if item is really for sale. function _cancelItemSale(Item storage item) private { //No need to overwrite item's price item.forSale = false; itemsForSaleCount--; } /* * Internal */ /// @notice Transfer item function transferItem(address _from, address _to, uint256 _itemID) internal ifItemForSaleThenCancelSale(_itemID) { Item storage item = items[_itemID]; //take off if(item.onChamp && _to != champToOwner[item.onChampId]){ takeOffItem(item.onChampId, item.itemType); } addressInfo[_to].itemsCount++; addressInfo[_from].itemsCount--; itemToOwner[_itemID] = _to; emit TransferItem(_from, _to, _itemID); } /* * Public */ /// @notice Calls transfer item /// @notice Address _from is msg.sender. Cannot be used is market, bc msg.sender is buyer function giveItem(address _to, uint256 _itemID) public onlyOwnerOfItem(_itemID) { transferItem(msg.sender, _to, _itemID); } /// @notice Calcels item's sale function cancelItemSale(uint256 _id) public itemIsForSale(_id) onlyOwnerOfItem(_id){ Item storage item = items[_id]; _cancelItemSale(item); } /* * External */ /// @notice Sets item for sale function setItemForSale(uint256 _id, uint256 _price) external onlyOwnerOfItem(_id) itemIsNotForSale(_id) { Item storage item = items[_id]; item.forSale = true; item.price = _price; itemsForSaleCount++; } /// @notice Buys item function buyItem(uint256 _id) external payable whenNotPaused onlyNotOwnerOfItem(_id) itemIsForSale(_id) isPaid(items[_id].price) distributeSaleInput(itemToOwner[_id]) { transferItem(itemToOwner[_id], msg.sender, _id); } } /// @title Manages forging contract ItemForge is ItemMarket { event Forge(uint256 forgedItemID); ///@notice Forge items together function forgeItems(uint256 _parentItemID, uint256 _childItemID) external onlyOwnerOfItem(_parentItemID) onlyOwnerOfItem(_childItemID) ifItemForSaleThenCancelSale(_parentItemID) ifItemForSaleThenCancelSale(_childItemID) { //checks if items are not the same require(_parentItemID != _childItemID); Item storage parentItem = items[_parentItemID]; Item storage childItem = items[_childItemID]; //take child item off, because child item will be burned if(childItem.onChamp){ takeOffItem(childItem.onChampId, childItem.itemType); } //update parent item parentItem.attackPower = (parentItem.attackPower > childItem.attackPower) ? parentItem.attackPower : childItem.attackPower; parentItem.defencePower = (parentItem.defencePower > childItem.defencePower) ? parentItem.defencePower : childItem.defencePower; parentItem.cooldownReduction = (parentItem.cooldownReduction > childItem.cooldownReduction) ? parentItem.cooldownReduction : childItem.cooldownReduction; parentItem.itemRarity = uint8(6); //burn child item transferItem(msg.sender, address(0), _childItemID); emit Forge(_parentItemID); } } /// @title Manages attacks in game contract ChampAttack is ItemForge { event Attack(uint256 winnerChampID, uint256 defeatedChampID, bool didAttackerWin); /* * Modifiers */ /// @notice Is champ ready to fight again? modifier isChampReady(uint256 _champId) { require (champs[_champId].readyTime <= block.timestamp); _; } /// @notice Prevents from self-attack modifier notSelfAttack(uint256 _champId, uint256 _targetId) { require(_champId != _targetId); _; } /// @notice Checks if champ does exist modifier targetExists(uint256 _targetId){ require(champToOwner[_targetId] != address(0)); _; } /* * View */ /// @notice Gets champ's attack power, defence power and cooldown reduction with items on function getChampStats(uint256 _champId) public view returns(uint256,uint256,uint256){ Champ storage champ = champs[_champId]; Item storage sword = items[champ.eq_sword]; Item storage shield = items[champ.eq_shield]; Item storage helmet = items[champ.eq_helmet]; //AP uint256 totalAttackPower = champ.attackPower + sword.attackPower + shield.attackPower + helmet.attackPower; //Gets champs AP //DP uint256 totalDefencePower = champ.defencePower + sword.defencePower + shield.defencePower + helmet.defencePower; //Gets champs DP //CR uint256 totalCooldownReduction = sword.cooldownReduction + shield.cooldownReduction + helmet.cooldownReduction; //Gets CR return (totalAttackPower, totalDefencePower, totalCooldownReduction); } /* * Pure */ /// @notice Subtracts ability points. Helps to not cross minimal attack ability points -> 2 /// @param _playerAttackPoints Actual player's attack points /// @param _x Amount to subtract function subAttack(uint256 _playerAttackPoints, uint256 _x) internal pure returns (uint256) { return (_playerAttackPoints <= _x + 2) ? 2 : _playerAttackPoints - _x; } /// @notice Subtracts ability points. Helps to not cross minimal defence ability points -> 1 /// @param _playerDefencePoints Actual player's defence points /// @param _x Amount to subtract function subDefence(uint256 _playerDefencePoints, uint256 _x) internal pure returns (uint256) { return (_playerDefencePoints <= _x) ? 1 : _playerDefencePoints - _x; } /* * Private */ /// @dev Is called from from Attack function after the winner is already chosen /// @dev Updates abilities, champ's stats and swaps positions function _attackCompleted(Champ storage _winnerChamp, Champ storage _defeatedChamp, uint256 _pointsGiven, uint256 _pointsToAttackPower) private { /* * Updates abilities after fight */ //winner abilities update _winnerChamp.attackPower += _pointsToAttackPower; //increase attack power _winnerChamp.defencePower += _pointsGiven - _pointsToAttackPower; //max point that was given - already given to AP //defeated champ's abilities update //checks for not cross minimal AP & DP points _defeatedChamp.attackPower = subAttack(_defeatedChamp.attackPower, _pointsToAttackPower); //decrease attack power _defeatedChamp.defencePower = subDefence(_defeatedChamp.defencePower, _pointsGiven - _pointsToAttackPower); // decrease defence power /* * Update champs' wins and losses */ _winnerChamp.winCount++; _defeatedChamp.lossCount++; /* * Swap positions */ if(_winnerChamp.position > _defeatedChamp.position) { //require loser to has better (lower) postion than attacker uint256 winnerPosition = _winnerChamp.position; uint256 loserPosition = _defeatedChamp.position; _defeatedChamp.position = winnerPosition; _winnerChamp.position = loserPosition; //position in champ struct is always one point bigger than in leaderboard array leaderboard[winnerPosition - 1] = _defeatedChamp.id; leaderboard[loserPosition - 1] = _winnerChamp.id; } } /// @dev Gets pointsGiven and pointsToAttackPower function _getPoints(uint256 _pointsGiven) private returns (uint256 pointsGiven, uint256 pointsToAttackPower){ return (_pointsGiven, randMod(_pointsGiven+1)); } /* * External */ /// @notice Attack function /// @param _champId Attacker champ /// @param _targetId Target champ function attack(uint256 _champId, uint256 _targetId) external onlyOwnerOfChamp(_champId) isChampReady(_champId) notSelfAttack(_champId, _targetId) targetExists(_targetId) { Champ storage myChamp = champs[_champId]; Champ storage enemyChamp = champs[_targetId]; uint256 pointsGiven; //total points that will be divided between AP and DP uint256 pointsToAttackPower; //part of points that will be added to attack power, the rest of points go to defence power uint256 myChampAttackPower; uint256 enemyChampDefencePower; uint256 myChampCooldownReduction; (myChampAttackPower,,myChampCooldownReduction) = getChampStats(_champId); (,enemyChampDefencePower,) = getChampStats(_targetId); //if attacker's AP is more than target's DP then attacker wins if (myChampAttackPower > enemyChampDefencePower) { //this should demotivate players from farming on way weaker champs than they are //the bigger difference between AP & DP is, the reward is smaller if(myChampAttackPower - enemyChampDefencePower < 5){ //big experience - 3 ability points (pointsGiven, pointsToAttackPower) = _getPoints(3); }else if(myChampAttackPower - enemyChampDefencePower < 10){ //medium experience - 2 ability points (pointsGiven, pointsToAttackPower) = _getPoints(2); }else{ //small experience - 1 ability point to random ability (attack power or defence power) (pointsGiven, pointsToAttackPower) = _getPoints(1); } _attackCompleted(myChamp, enemyChamp, pointsGiven, pointsToAttackPower); emit Attack(myChamp.id, enemyChamp.id, true); } else { //1 ability point to random ability (attack power or defence power) (pointsGiven, pointsToAttackPower) = _getPoints(1); _attackCompleted(enemyChamp, myChamp, pointsGiven, pointsToAttackPower); emit Attack(enemyChamp.id, myChamp.id, false); } //Trigger cooldown for attacker myChamp.readyTime = uint256(block.timestamp + myChamp.cooldownTime - myChampCooldownReduction); } } /// @title Moderates buying and selling champs contract ChampMarket is ChampAttack { event TransferChamp(address from, address to, uint256 champID); /* * Modifiers */ ///@notice Require champ to be sale modifier champIsForSale(uint256 _id){ require(champs[_id].forSale); _; } ///@notice Require champ NOT to be for sale modifier champIsNotForSale(uint256 _id){ require(champs[_id].forSale == false); _; } ///@notice If champ is for sale then cancel sale modifier ifChampForSaleThenCancelSale(uint256 _champID){ Champ storage champ = champs[_champID]; if(champ.forSale){ _cancelChampSale(champ); } _; } /* * View */ ///@notice Gets all champs for sale function getChampsForSale() view external returns(uint256[]){ uint256[] memory result = new uint256[](champsForSaleCount); if(champsForSaleCount > 0){ uint256 counter = 0; for (uint256 i = 0; i < champs.length; i++) { if (champs[i].forSale == true) { result[counter]=i; counter++; } } } return result; } /* * Private */ ///@dev Cancel sale. Should not be called without checking if champ is really for sale. function _cancelChampSale(Champ storage champ) private { //cancel champ's sale //no need waste gas to overwrite his price. champ.forSale = false; champsForSaleCount--; } /* * Internal */ /// @notice Transfer champ function transferChamp(address _from, address _to, uint256 _champId) internal ifChampForSaleThenCancelSale(_champId){ Champ storage champ = champs[_champId]; //transfer champ addressInfo[_to].champsCount++; addressInfo[_from].champsCount--; champToOwner[_champId] = _to; //transfer items if(champ.eq_sword != 0) { transferItem(_from, _to, champ.eq_sword); } if(champ.eq_shield != 0) { transferItem(_from, _to, champ.eq_shield); } if(champ.eq_helmet != 0) { transferItem(_from, _to, champ.eq_helmet); } emit TransferChamp(_from, _to, _champId); } /* * Public */ /// @notice Champ is no more for sale function cancelChampSale(uint256 _id) public champIsForSale(_id) onlyOwnerOfChamp(_id) { Champ storage champ = champs[_id]; _cancelChampSale(champ); } /* * External */ /// @notice Gift champ /// @dev Address _from is msg.sender function giveChamp(address _to, uint256 _champId) external onlyOwnerOfChamp(_champId) { transferChamp(msg.sender, _to, _champId); } /// @notice Sets champ for sale function setChampForSale(uint256 _id, uint256 _price) external onlyOwnerOfChamp(_id) champIsNotForSale(_id) { Champ storage champ = champs[_id]; champ.forSale = true; champ.price = _price; champsForSaleCount++; } /// @notice Buys champ function buyChamp(uint256 _id) external payable whenNotPaused onlyNotOwnerOfChamp(_id) champIsForSale(_id) isPaid(champs[_id].price) distributeSaleInput(champToOwner[_id]) { transferChamp(champToOwner[_id], msg.sender, _id); } } /// @title Only used for deploying all contracts contract MyCryptoChampCore is ChampMarket { /* © Copyright 2018 - Patrik Mojzis Redistributing and modifying is prohibited. https://mycryptochamp.io/ What is MyCryptoChamp? - Blockchain game about upgrading champs by fighting, getting better items, trading them and the best 800 champs are daily rewarded by real Ether. Feel free to ask any questions hello@mycryptochamp.io */ }