ETH Price: $1,997.52 (+3.13%)
Gas: 0.04 Gwei

Transaction Decoder

Block:
8051054 at Jun-29-2019 06:00:54 AM +UTC
Transaction Fee:
0.0004358868 ETH $0.87
Gas Used:
189,516 Gas / 2.3 Gwei

Emitted Events:

85 0x8a6014227138556a259e7b2bf1dce668f9bdfd06.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x00000000000000000000000061d9557d6d259428976486e45ed0d8f47593ef5c, 0000000000000000000000000000000000000000000000000000000000000001 )
86 0x028b1e152d9fc838e5c0d480ed2d39b3d11ea2f0.0x716e3e1889ffd1c72a748db32820852fe1fd5b77a6455afb5dcdef6d7b5cbd5c( 0x716e3e1889ffd1c72a748db32820852fe1fd5b77a6455afb5dcdef6d7b5cbd5c, 00000000000000000000000061d9557d6d259428976486e45ed0d8f47593ef5c, 000000000000000000000000f8a8012dcf95914d5e052722775e473a887ef0e4, 0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000000000000000000000000000000000000000000016, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000015, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000001 )

Account State Difference:

  Address   Before After State Difference Code
0x028b1E15...3d11EA2F0
(Nanopool)
5,218.078626250107602916 Eth5,218.079062136907602916 Eth0.0004358868
0x61d9557d...47593eF5c
0.0960029993 Eth
Nonce: 22
0.0955671125 Eth
Nonce: 23
0.0004358868
0x8a601422...8F9BdFd06
0x98278eB7...42Ce53f2b
0xf936AA9e...b8716BA5e

Execution Trace

0x028b1e152d9fc838e5c0d480ed2d39b3d11ea2f0.fe074e5e( )
  • Army.getArmiesPower( player=0x61d9557d6D259428976486E45eD0d8F47593eF5c, target=0xf8A8012DcF95914D5e052722775e473A887EF0e4 ) => ( playersAttack=6300, playersLooting=24000, targetsDefense=5600 )
  • Clans.getClanDetailsForAttack( player=0x61d9557d6D259428976486E45eD0d8F47593eF5c, target=0xf8A8012DcF95914D5e052722775e473A887EF0e4 ) => ( clanId=5, targetClanId=9, playerLootingBonus=40 )
  • 0x8a6014227138556a259e7b2bf1dce668f9bdfd06.b131da81( )
  • Units.grantArmyExp( player=0x61d9557d6D259428976486E45eD0d8F47593eF5c, unitId=21, amount=100 ) => ( True )
  • Units.unitsOwned( 0x61d9557d6D259428976486E45eD0d8F47593eF5c, 21 ) => ( units=0, factoryBuiltFlag=0 )
  • Units.unitsOwned( 0xf8A8012DcF95914D5e052722775e473A887EF0e4, 22 ) => ( units=0, factoryBuiltFlag=0 )
    File 1 of 3: Army
    pragma solidity ^0.4.25;
    
    /**
     * 
     * World War Goo - Competitive Idle Game
     * 
     * https://ethergoo.io
     * 
     */
    
    contract Army {
    
        GooToken constant goo = GooToken(0xdf0960778c6e6597f197ed9a25f12f5d971da86c);
        Clans clans = Clans(0x0);
    
        uint224 public totalArmyPower; // Global power of players (attack + defence)
        uint224 public gooBankroll; // Goo dividends to be split over time between clans/players' army power
        uint256 public nextSnapshotTime;
        address public owner; // Minor management of game
    
        mapping(address => mapping(uint256 => ArmyPower)) public armyPowerSnapshots; // Store player's army power for given day (snapshot)
        mapping(address => mapping(uint256 => bool)) public armyPowerZeroedSnapshots; // Edgecase to determine difference between 0 army and an unused/inactive day.
        mapping(address => uint256) public lastWarFundClaim; // Days (snapshot number)
        mapping(address => uint256) public lastArmyPowerUpdate; // Days (last snapshot) player's army was updated
        mapping(address => bool) operator;
    
        uint224[] public totalArmyPowerSnapshots; // The total player army power for each prior day past
        uint224[] public allocatedWarFundSnapshots; // Div pot (goo allocated to each prior day past)
        
        uint224 public playerDivPercent = 2;
        uint224 public clanDivPercent = 2;
    
        struct ArmyPower {
            uint80 attack;
            uint80 defense;
            uint80 looting;
        }
    
        constructor(uint256 firstSnapshotTime) public {
            nextSnapshotTime = firstSnapshotTime;
            owner = msg.sender;
        }
    
        function setClans(address clansContract) external {
            require(msg.sender == owner);
            clans = Clans(clansContract);
        }
    
        function setOperator(address gameContract, bool isOperator) external {
            require(msg.sender == owner);
            operator[gameContract] = isOperator;
        }
        
        function updateDailyDivPercents(uint224 newPlayersPercent, uint224 newClansPercent) external {
            require(msg.sender == owner);
            require(newPlayersPercent > 0 && newPlayersPercent <= 10); // 1-10% daily
            require(newClansPercent > 0 && newClansPercent <= 10); // 1-10% daily
            playerDivPercent = newPlayersPercent;
            clanDivPercent = newClansPercent;
        }
    
        function depositSpentGoo(uint224 gooSpent) external {
            require(operator[msg.sender]);
            gooBankroll += gooSpent;
        }
    
        function getArmyPower(address player) external view returns (uint80, uint80, uint80) {
            ArmyPower memory armyPower = armyPowerSnapshots[player][lastArmyPowerUpdate[player]];
            return (armyPower.attack, armyPower.defense, armyPower.looting);
        }
        
        // Convenience function 
        function getArmiesPower(address player, address target) external view returns (uint80 playersAttack, uint80 playersLooting, uint80 targetsDefense) {
            ArmyPower memory armyPower = armyPowerSnapshots[player][lastArmyPowerUpdate[player]];
            playersAttack = armyPower.attack;
            playersLooting = armyPower.looting;
            targetsDefense = armyPowerSnapshots[target][lastArmyPowerUpdate[target]].defense;
        }
    
        function increasePlayersArmyPowerTrio(address player, uint80 attackGain, uint80 defenseGain, uint80 lootingGain) public {
            require(operator[msg.sender]);
    
            ArmyPower memory existingArmyPower = armyPowerSnapshots[player][lastArmyPowerUpdate[player]];
            uint256 snapshotDay = allocatedWarFundSnapshots.length;
    
            // Adjust army power (reusing struct)
            existingArmyPower.attack += attackGain;
            existingArmyPower.defense += defenseGain;
            existingArmyPower.looting += lootingGain;
            armyPowerSnapshots[player][snapshotDay] = existingArmyPower;
    
            if (lastArmyPowerUpdate[player] != snapshotDay) {
                lastArmyPowerUpdate[player] = snapshotDay;
            }
            
            totalArmyPower += (attackGain + defenseGain);
            clans.increaseClanPower(player, attackGain + defenseGain);
        }
    
        function decreasePlayersArmyPowerTrio(address player, uint80 attackLoss, uint80 defenseLoss, uint80 lootingLoss) public {
            require(operator[msg.sender]);
    
            ArmyPower memory existingArmyPower = armyPowerSnapshots[player][lastArmyPowerUpdate[player]];
            uint256 snapshotDay = allocatedWarFundSnapshots.length;
    
            // Adjust army power (reusing struct)
            existingArmyPower.attack -= attackLoss;
            existingArmyPower.defense -= defenseLoss;
            existingArmyPower.looting -= lootingLoss;
    
            if (existingArmyPower.attack == 0 && existingArmyPower.defense == 0) { // Special case which tangles with "inactive day" snapshots (claiming divs)
                armyPowerZeroedSnapshots[player][snapshotDay] = true;
                delete armyPowerSnapshots[player][snapshotDay]; // 0
            } else {
                armyPowerSnapshots[player][snapshotDay] = existingArmyPower;
            }
            
            if (lastArmyPowerUpdate[player] != snapshotDay) {
                lastArmyPowerUpdate[player] = snapshotDay;
            }
    
            totalArmyPower -= (attackLoss + defenseLoss);
            clans.decreaseClanPower(player, attackLoss + defenseLoss);
        }
    
        function changePlayersArmyPowerTrio(address player, int attackChange, int defenseChange, int lootingChange) public {
            require(operator[msg.sender]);
    
            ArmyPower memory existingArmyPower = armyPowerSnapshots[player][lastArmyPowerUpdate[player]];
            uint256 snapshotDay = allocatedWarFundSnapshots.length;
    
            // Allow change to be positive or negative
            existingArmyPower.attack = uint80(int(existingArmyPower.attack) + attackChange);
            existingArmyPower.defense = uint80(int(existingArmyPower.defense) + defenseChange);
            existingArmyPower.looting = uint80(int(existingArmyPower.looting) + lootingChange);
    
            if (existingArmyPower.attack == 0 && existingArmyPower.defense == 0) { // Special case which tangles with "inactive day" snapshots (claiming divs)
                armyPowerZeroedSnapshots[player][snapshotDay] = true;
                delete armyPowerSnapshots[player][snapshotDay]; // 0
            } else {
                armyPowerSnapshots[player][snapshotDay] = existingArmyPower;
            }
    
            if (lastArmyPowerUpdate[player] != snapshotDay) {
                lastArmyPowerUpdate[player] = snapshotDay;
            }
            changeTotalArmyPower(player, attackChange, defenseChange);
        }
    
        function changeTotalArmyPower(address player, int attackChange, int defenseChange) internal {
            uint224 newTotal = uint224(int(totalArmyPower) + attackChange + defenseChange);
    
            if (newTotal > totalArmyPower) {
                clans.increaseClanPower(player, newTotal - totalArmyPower);
            } else if (newTotal < totalArmyPower) {
                clans.decreaseClanPower(player, totalArmyPower - newTotal);
            }
            totalArmyPower = newTotal;
        }
    
        // Allocate army power divs for the day (00:00 cron job)
        function snapshotDailyWarFunding() external {
            require(msg.sender == owner);
            require(now + 6 hours > nextSnapshotTime);
    
            totalArmyPowerSnapshots.push(totalArmyPower);
            allocatedWarFundSnapshots.push((gooBankroll * playerDivPercent) / 100);
            uint256 allocatedClanWarFund = (gooBankroll * clanDivPercent) / 100; // No daily snapshots needed for Clans (as below will also claim between the handful of clans)
            gooBankroll -= (gooBankroll * (playerDivPercent + clanDivPercent)) / 100;  // % of pool daily
    
            uint256 numClans = clans.totalSupply();
            uint256[] memory clanArmyPower = new uint256[](numClans);
    
            // Get total power from all clans
            uint256 todaysTotalClanPower;
            for (uint256 i = 1; i <= numClans; i++) {
                clanArmyPower[i-1] = clans.clanTotalArmyPower(i);
                todaysTotalClanPower += clanArmyPower[i-1];
            }
    
            // Distribute goo divs to clans based on their relative power
            for (i = 1; i <= numClans; i++) {
                clans.depositGoo((allocatedClanWarFund * clanArmyPower[i-1]) / todaysTotalClanPower, i);
            }
    
            nextSnapshotTime = now + 24 hours;
        }
    
        function claimWarFundDividends(uint256 startSnapshot, uint256 endSnapShot) external {
            require(startSnapshot <= endSnapShot);
            require(startSnapshot >= lastWarFundClaim[msg.sender]);
            require(endSnapShot < allocatedWarFundSnapshots.length);
    
            uint224 gooShare;
            ArmyPower memory previousArmyPower = armyPowerSnapshots[msg.sender][lastWarFundClaim[msg.sender] - 1]; // Underflow won't be a problem as armyPowerSnapshots[][0xffffffff] = 0;
            for (uint256 i = startSnapshot; i <= endSnapShot; i++) {
    
                // Slightly complex things by accounting for days/snapshots when user made no tx's
                ArmyPower memory armyPowerDuringSnapshot = armyPowerSnapshots[msg.sender][i];
                bool soldAllArmy = armyPowerZeroedSnapshots[msg.sender][i];
                if (!soldAllArmy && armyPowerDuringSnapshot.attack == 0 && armyPowerDuringSnapshot.defense == 0) {
                    armyPowerDuringSnapshot = previousArmyPower;
                } else {
                   previousArmyPower = armyPowerDuringSnapshot;
                }
    
                gooShare += (allocatedWarFundSnapshots[i] * (armyPowerDuringSnapshot.attack + armyPowerDuringSnapshot.defense)) / totalArmyPowerSnapshots[i];
            }
    
    
            ArmyPower memory endSnapshotArmyPower = armyPowerSnapshots[msg.sender][endSnapShot];
            if (endSnapshotArmyPower.attack == 0 && endSnapshotArmyPower.defense == 0 && !armyPowerZeroedSnapshots[msg.sender][endSnapShot] && (previousArmyPower.attack + previousArmyPower.defense) > 0) {
                armyPowerSnapshots[msg.sender][endSnapShot] = previousArmyPower; // Checkpoint for next claim
            }
    
            lastWarFundClaim[msg.sender] = endSnapShot + 1;
    
            (uint224 clanFee, uint224 leaderFee, address leader, uint224 referalFee, address referer) = clans.getPlayerFees(msg.sender);
            if (clanFee > 0) {
                clanFee = (gooShare * clanFee) / 100; // Convert from percent to goo
                leaderFee = (gooShare * leaderFee) / 100; // Convert from percent to goo
                clans.mintGoo(msg.sender, clanFee);
                goo.mintGoo(leaderFee, leader);
            }
            if (referer == address(0)) {
                referalFee = 0;
            } else if (referalFee > 0) {
                referalFee = (gooShare * referalFee) / 100; // Convert from percent to goo
                goo.mintGoo(referalFee, referer);
            }
            
            goo.mintGoo(gooShare - (clanFee + leaderFee + referalFee), msg.sender);
        }
    
        function getSnapshotDay() external view returns (uint256 snapshot) {
            snapshot = allocatedWarFundSnapshots.length;
        }
    
    }
    
    
    contract GooToken {
        function transfer(address to, uint256 tokens) external returns (bool);
        function increasePlayersGooProduction(address player, uint256 increase) external;
        function decreasePlayersGooProduction(address player, uint256 decrease) external;
        function updatePlayersGooFromPurchase(address player, uint224 purchaseCost) external;
        function updatePlayersGoo(address player) external;
        function mintGoo(uint224 amount, address player) external;
    }
    
    contract Clans {
        mapping(uint256 => uint256) public clanTotalArmyPower;
        function totalSupply() external view returns (uint256);
        function depositGoo(uint256 amount, uint256 clanId) external;
        function getPlayerFees(address player) external view returns (uint224 clansFee, uint224 leadersFee, address leader, uint224 referalsFee, address referer);
        function getPlayersClanUpgrade(address player, uint256 upgradeClass) external view returns (uint224 upgradeGain);
        function mintGoo(address player, uint256 amount) external;
        function increaseClanPower(address player, uint256 amount) external;
        function decreaseClanPower(address player, uint256 amount) external;
    }
    
    contract Factories {
        uint256 public constant MAX_SIZE = 40;
        function getFactories(address player) external returns (uint256[]);
        function addFactory(address player, uint8 position, uint256 unitId) external;
    }
    
    
    library SafeMath {
    
      /**
      * @dev Multiplies two numbers, throws on overflow.
      */
      function mul(uint224 a, uint224 b) internal pure returns (uint224) {
        if (a == 0) {
          return 0;
        }
        uint224 c = a * b;
        assert(c / a == b);
        return c;
      }
    
      /**
      * @dev Integer division of two numbers, truncating the quotient.
      */
      function div(uint256 a, uint256 b) internal pure returns (uint256) {
        // assert(b > 0); // Solidity automatically throws when dividing by 0
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
        return c;
      }
    
      /**
      * @dev Substracts 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;
      }
    }
    
    
    library SafeMath224 {
    
      /**
      * @dev Multiplies two numbers, throws on overflow.
      */
      function mul(uint224 a, uint224 b) internal pure returns (uint224) {
        if (a == 0) {
          return 0;
        }
        uint224 c = a * b;
        assert(c / a == b);
        return c;
      }
    
      /**
      * @dev Integer division of two numbers, truncating the quotient.
      */
      function div(uint224 a, uint224 b) internal pure returns (uint224) {
        // assert(b > 0); // Solidity automatically throws when dividing by 0
        uint224 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
        return c;
      }
    
      /**
      * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
      */
      function sub(uint224 a, uint224 b) internal pure returns (uint224) {
        assert(b <= a);
        return a - b;
      }
    
      /**
      * @dev Adds two numbers, throws on overflow.
      */
      function add(uint224 a, uint224 b) internal pure returns (uint224) {
        uint224 c = a + b;
        assert(c >= a);
        return c;
      }
    }

    File 2 of 3: Clans
    pragma solidity ^0.4.25;
    
    /**
     * 
     * World War Goo - Competitive Idle Game
     * 
     * https://ethergoo.io
     * 
     */
     
     
    interface ERC721 {
        function totalSupply() external view returns (uint256 tokens);
        function balanceOf(address owner) external view returns (uint256 balance);
        function ownerOf(uint256 tokenId) external view returns (address owner);
        function exists(uint256 tokenId) external view returns (bool tokenExists);
        function approve(address to, uint256 tokenId) external;
        function getApproved(uint256 tokenId) external view returns (address approvee);
    
        function transferFrom(address from, address to, uint256 tokenId) external;
        function tokensOf(address owner) external view returns (uint256[] tokens);
        //function tokenByIndex(uint256 index) external view returns (uint256 token);
    
        // Events
        event Transfer(address from, address to, uint256 tokenId);
        event Approval(address owner, address approved, uint256 tokenId);
    }
    
    interface ApproveAndCallFallBack {
        function receiveApproval(address from, uint256 tokens, address token, bytes data) external;
    }
    
    
    contract Clans is ERC721, ApproveAndCallFallBack {
        using SafeMath for uint256;
    
        GooToken constant goo = GooToken(0xdf0960778c6e6597f197ed9a25f12f5d971da86c);
        Army constant army = Army(0x98278eb74b388efd4d6fc81dd3f95b642ce53f2b);
        WWGClanCoupons constant clanCoupons = WWGClanCoupons(0xe9fe4e530ebae235877289bd978f207ae0c8bb25); // For minting clans to initial owners (prelaunch buyers)
    
        string public constant name = "Goo Clan";
        string public constant symbol = "GOOCLAN";
        uint224 numClans;
        address owner; // Minor management
    
        // ERC721 stuff
        mapping (uint256 => address) public tokenOwner;
        mapping (uint256 => address) public tokenApprovals;
        mapping (address => uint256[]) public ownedTokens;
        mapping(uint256 => uint256) public ownedTokensIndex;
    
        mapping(address => UserClan) public userClan;
        mapping(uint256 => uint224) public clanFee;
        mapping(uint256 => uint224) public leaderFee;
        mapping(uint256 => uint256) public clanMembers;
        mapping(uint256 => mapping(uint256 => uint224)) public clanUpgradesOwned;
        mapping(uint256 => uint256) public clanGoo;
        mapping(uint256 => address) public clanToken; // i.e. BNB
        mapping(uint256 => uint256) public baseTokenDenomination; // base value for token gains i.e. 0.000001 BNB
        mapping(uint256 => uint256) public clanTotalArmyPower;
    
        mapping(uint256 => uint224) public referalFee; // If invited to a clan how much % of player's divs go to referer
        mapping(address => mapping(uint256 => address)) public clanReferer; // Address of who invited player to each clan
    
        mapping(uint256 => Upgrade) public upgradeList;
        mapping(address => bool) operator;
    
        struct UserClan {
            uint224 clanId;
            uint32 clanJoinTime;
        }
    
        struct Upgrade {
            uint256 upgradeId;
            uint224 gooCost;
            uint224 upgradeGain;
            uint256 upgradeClass;
            uint256 prerequisiteUpgrade;
        }
    
        // Events
        event JoinedClan(uint256 clanId, address player, address referer);
        event LeftClan(uint256 clanId, address player);
    
        constructor() public {
            owner = msg.sender;
        }
    
        function setOperator(address gameContract, bool isOperator) external {
            require(msg.sender == owner);
            operator[gameContract] = isOperator;
        }
    
        function totalSupply() external view returns (uint256) {
            return numClans;
        }
    
        function balanceOf(address player) public view returns (uint256) {
            return ownedTokens[player].length;
        }
    
        function ownerOf(uint256 clanId) external view returns (address) {
            return tokenOwner[clanId];
        }
    
        function exists(uint256 clanId) public view returns (bool) {
            return tokenOwner[clanId] != address(0);
        }
    
        function approve(address to, uint256 clanId) external {
            require(tokenOwner[clanId] == msg.sender);
            tokenApprovals[clanId] = to;
            emit Approval(msg.sender, to, clanId);
        }
    
        function getApproved(uint256 clanId) external view returns (address) {
            return tokenApprovals[clanId];
        }
    
        function tokensOf(address player) external view returns (uint256[] tokens) {
             return ownedTokens[player];
        }
    
        function transferFrom(address from, address to, uint256 tokenId) public {
            require(tokenApprovals[tokenId] == msg.sender || tokenOwner[tokenId] == msg.sender);
    
            joinClanPlayer(to, uint224(tokenId), 0); // uint224 won't overflow due to tokenOwner check in removeTokenFrom()
            removeTokenFrom(from, tokenId);
            addTokenTo(to, tokenId);
    
            delete tokenApprovals[tokenId]; // Clear approval
            emit Transfer(from, to, tokenId);
        }
    
        function safeTransferFrom(address from, address to, uint256 tokenId) public {
            safeTransferFrom(from, to, tokenId, "");
        }
    
        function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public {
            transferFrom(from, to, tokenId);
            checkERC721Recieved(from, to, tokenId, data);
        }
    
        function checkERC721Recieved(address from, address to, uint256 tokenId, bytes memory data) internal {
            uint256 size;
            assembly { size := extcodesize(to) }
            if (size > 0) { // Recipient is contract so must confirm recipt
                bytes4 successfullyRecieved = ERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, data);
                require(successfullyRecieved == bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")));
            }
        }
    
        function removeTokenFrom(address from, uint256 tokenId) internal {
            require(tokenOwner[tokenId] == from);
            tokenOwner[tokenId] = address(0);
    
            uint256 tokenIndex = ownedTokensIndex[tokenId];
            uint256 lastTokenIndex = ownedTokens[from].length.sub(1);
            uint256 lastToken = ownedTokens[from][lastTokenIndex];
    
            ownedTokens[from][tokenIndex] = lastToken;
            ownedTokens[from][lastTokenIndex] = 0;
    
            ownedTokens[from].length--;
            ownedTokensIndex[tokenId] = 0;
            ownedTokensIndex[lastToken] = tokenIndex;
        }
    
        function addTokenTo(address to, uint256 tokenId) internal {
            require(ownedTokens[to].length == 0); // Can't own multiple clans
            tokenOwner[tokenId] = to;
            ownedTokensIndex[tokenId] = ownedTokens[to].length;
            ownedTokens[to].push(tokenId);
        }
    
        function updateClanFees(uint224 newClanFee, uint224 newLeaderFee, uint224 newReferalFee, uint256 clanId) external {
            require(msg.sender == tokenOwner[clanId]);
            require(newClanFee <= 25); // 25% max fee
            require(newReferalFee <= 10); // 10% max refs
            require(newLeaderFee <= newClanFee); // Clan gets fair cut
            clanFee[clanId] = newClanFee;
            leaderFee[clanId] = newLeaderFee;
            referalFee[clanId] = newReferalFee;
        }
    
        function getPlayerFees(address player) external view returns (uint224 clansFee, uint224 leadersFee, address leader, uint224 referalsFee, address referer) {
            uint256 usersClan = userClan[player].clanId;
            clansFee = clanFee[usersClan];
            leadersFee = leaderFee[usersClan];
            leader = tokenOwner[usersClan];
            referalsFee = referalFee[usersClan];
            referer = clanReferer[player][usersClan];
        }
    
        function getPlayersClanUpgrade(address player, uint256 upgradeClass) external view returns (uint224 upgradeGain) {
            upgradeGain = upgradeList[clanUpgradesOwned[userClan[player].clanId][upgradeClass]].upgradeGain;
        }
    
        function getClanUpgrade(uint256 clanId, uint256 upgradeClass) external view returns (uint224 upgradeGain) {
            upgradeGain = upgradeList[clanUpgradesOwned[clanId][upgradeClass]].upgradeGain;
        }
    
        // Convienence function
        function getClanDetailsForAttack(address player, address target) external view returns (uint256 clanId, uint256 targetClanId, uint224 playerLootingBonus) {
            clanId = userClan[player].clanId;
            targetClanId = userClan[target].clanId;
            playerLootingBonus = upgradeList[clanUpgradesOwned[clanId][3]].upgradeGain; // class 3 = looting bonus
        }
    
        function joinClan(uint224 clanId, address referer) external {
            require(exists(clanId));
            joinClanPlayer(msg.sender, clanId, referer);
        }
    
        // Allows smarter invites/referals in future
        function joinClanFromInvite(address player, uint224 clanId, address referer) external {
            require(operator[msg.sender]);
            joinClanPlayer(player, clanId, referer);
        }
    
        function joinClanPlayer(address player, uint224 clanId, address referer) internal {
            require(ownedTokens[player].length == 0); // Owners can't join
    
            (uint80 attack, uint80 defense,) = army.getArmyPower(player);
    
            // Leave old clan
            UserClan memory existingClan = userClan[player];
            if (existingClan.clanId > 0) {
                clanMembers[existingClan.clanId]--;
                clanTotalArmyPower[existingClan.clanId] -= (attack + defense);
                emit LeftClan(existingClan.clanId, player);
            }
    
            if (referer != address(0) && referer != player) {
                require(userClan[referer].clanId == clanId);
                clanReferer[player][clanId] = referer;
            }
    
            existingClan.clanId = clanId;
            existingClan.clanJoinTime = uint32(now);
    
            clanMembers[clanId]++;
            clanTotalArmyPower[clanId] += (attack + defense);
            userClan[player] = existingClan;
            emit JoinedClan(clanId, player, referer);
        }
    
        function leaveClan() external {
            require(ownedTokens[msg.sender].length == 0); // Owners can't leave
    
            UserClan memory usersClan = userClan[msg.sender];
            require(usersClan.clanId > 0);
    
            (uint80 attack, uint80 defense,) = army.getArmyPower(msg.sender);
            clanTotalArmyPower[usersClan.clanId] -= (attack + defense);
    
            clanMembers[usersClan.clanId]--;
            delete userClan[msg.sender];
            emit LeftClan(usersClan.clanId, msg.sender);
    
            // Cannot leave if player has unclaimed divs (edge case for clan fee abuse)
            require(attack + defense == 0 || army.lastWarFundClaim(msg.sender) == army.getSnapshotDay());
            require(usersClan.clanJoinTime + 24 hours < now);
        }
    
        function mintClan(address recipient, uint224 referalPercent, address clanTokenAddress, uint256 baseTokenReward) external {
            require(operator[msg.sender]);
            require(ERC20(clanTokenAddress).totalSupply() > 0);
    
            numClans++;
            uint224 clanId = numClans; // Starts from clanId 1
    
            // Add recipient to clan
            joinClanPlayer(recipient, clanId, 0);
    
            require(tokenOwner[clanId] == address(0));
            addTokenTo(recipient, clanId);
            emit Transfer(address(0), recipient, clanId);
    
            // Store clan token
            clanToken[clanId] = clanTokenAddress;
            baseTokenDenomination[clanId] = baseTokenReward;
            referalFee[clanId] = referalPercent;
    
            // Burn clan coupons from owner (prelaunch event)
            if (clanCoupons.totalSupply() > 0) {
                clanCoupons.burnCoupon(recipient, clanId);
            }
        }
    
        function addUpgrade(uint256 id, uint224 gooCost, uint224 upgradeGain, uint256 upgradeClass, uint256 prereq) external {
            require(operator[msg.sender]);
            upgradeList[id] = Upgrade(id, gooCost, upgradeGain, upgradeClass, prereq);
        }
    
        // Incase an existing token becomes invalid (i.e. migrates away)
        function updateClanToken(uint256 clanId, address newClanToken, bool shouldRetrieveOldTokens) external {
            require(msg.sender == owner);
            require(ERC20(newClanToken).totalSupply() > 0);
    
            if (shouldRetrieveOldTokens) {
                ERC20(clanToken[clanId]).transferFrom(this, owner, ERC20(clanToken[clanId]).balanceOf(this));
            }
    
            clanToken[clanId] = newClanToken;
        }
    
        // Incase need to tweak/balance attacking rewards (i.e. token moons so not viable to restock at current level)
        function updateClanTokenGain(uint256 clanId, uint256 baseTokenReward) external {
            require(msg.sender == owner);
            baseTokenDenomination[clanId] = baseTokenReward;
        }
    
    
        // Clan member goo deposits
        function receiveApproval(address player, uint256 amount, address, bytes) external {
            uint256 clanId = userClan[player].clanId;
            require(exists(clanId));
            require(msg.sender == address(goo));
    
            ERC20(msg.sender).transferFrom(player, address(0), amount);
            clanGoo[clanId] += amount;
        }
    
        function buyUpgrade(uint224 upgradeId) external {
            uint256 clanId = userClan[msg.sender].clanId;
            require(msg.sender == tokenOwner[clanId]);
    
            Upgrade memory upgrade = upgradeList[upgradeId];
            require (upgrade.upgradeId > 0); // Valid upgrade
    
            uint256 upgradeClass = upgrade.upgradeClass;
            uint256 latestOwned = clanUpgradesOwned[clanId][upgradeClass];
            require(latestOwned < upgradeId); // Haven't already purchased
            require(latestOwned >= upgrade.prerequisiteUpgrade); // Own prequisite
    
            // Clan discount
            uint224 upgradeDiscount = clanUpgradesOwned[clanId][0]; // class 0 = upgrade discount
            uint224 reducedUpgradeCost = upgrade.gooCost - ((upgrade.gooCost * upgradeDiscount) / 100);
    
            clanGoo[clanId] = clanGoo[clanId].sub(reducedUpgradeCost);
            army.depositSpentGoo(reducedUpgradeCost); // Transfer to goo bankroll
    
            clanUpgradesOwned[clanId][upgradeClass] = upgradeId;
        }
    
        // Goo from divs etc.
        function depositGoo(uint256 amount, uint256 clanId) external {
            require(operator[msg.sender]);
            require(exists(clanId));
            clanGoo[clanId] += amount;
        }
    
    
        function increaseClanPower(address player, uint256 amount) external {
            require(operator[msg.sender]);
    
            uint256 clanId = userClan[player].clanId;
            if (clanId > 0) {
                clanTotalArmyPower[clanId] += amount;
            }
        }
    
        function decreaseClanPower(address player, uint256 amount) external {
            require(operator[msg.sender]);
    
            uint256 clanId = userClan[player].clanId;
            if (clanId > 0) {
                clanTotalArmyPower[clanId] -= amount;
            }
        }
    
    
        function stealGoo(address attacker, uint256 playerClanId, uint256 enemyClanId, uint80 lootingPower) external returns(uint256) {
            require(operator[msg.sender]);
    
            uint224 enemyGoo = uint224(clanGoo[enemyClanId]);
            uint224 enemyGooStolen = (lootingPower > enemyGoo) ? enemyGoo : lootingPower;
    
            clanGoo[enemyClanId] = clanGoo[enemyClanId].sub(enemyGooStolen);
    
            uint224 clansShare = (enemyGooStolen * clanFee[playerClanId]) / 100;
            uint224 referersFee = referalFee[playerClanId];
            address referer = clanReferer[attacker][playerClanId];
    
            if (clansShare > 0 || (referersFee > 0 && referer != address(0))) {
                uint224 leaderShare = (enemyGooStolen * leaderFee[playerClanId]) / 100;
    
                uint224 refsShare;
                if (referer != address(0)) {
                    refsShare = (enemyGooStolen * referersFee) / 100;
                    goo.mintGoo(refsShare, referer);
                }
    
                clanGoo[playerClanId] += clansShare;
                goo.mintGoo(leaderShare, tokenOwner[playerClanId]);
                goo.mintGoo(enemyGooStolen - (clansShare + leaderShare + refsShare), attacker);
            } else {
                goo.mintGoo(enemyGooStolen, attacker);
            }
            return enemyGooStolen;
        }
    
    
        function rewardTokens(address attacker, uint256 playerClanId, uint80 lootingPower) external returns(uint256) {
            require(operator[msg.sender]);
    
            uint256 amount = baseTokenDenomination[playerClanId] * lootingPower;
            ERC20(clanToken[playerClanId]).transfer(attacker, amount);
            return amount;
    
        }
    
        // Daily clan dividends
        function mintGoo(address player, uint256 amount) external {
            require(operator[msg.sender]);
            clanGoo[userClan[player].clanId] += amount;
        }
    
    }
    
    contract ERC20 {
        function transferFrom(address from, address to, uint tokens) external returns (bool success);
        function transfer(address to, uint tokens) external returns (bool success);
        function totalSupply() external constant returns (uint);
        function balanceOf(address tokenOwner) external constant returns (uint balance);
    }
    
    contract GooToken {
        function mintGoo(uint224 amount, address player) external;
        function updatePlayersGooFromPurchase(address player, uint224 purchaseCost) external;
    }
    
    contract Army {
        mapping(address => uint256) public lastWarFundClaim; // Days (snapshot number)
        function depositSpentGoo(uint224 amount) external;
        function getArmyPower(address player) external view returns (uint80, uint80, uint80);
        function getSnapshotDay() external view returns (uint256 snapshot);
    }
    
    contract WWGClanCoupons {
        function totalSupply() external view returns (uint256);
        function burnCoupon(address clanOwner, uint256 tokenId) external;
    }
    
    contract ERC721Receiver {
        function onERC721Received(address operator, address from, uint256 tokenId, bytes data) external returns(bytes4);
    }
    
    
    
    
    
    
    
    
    
    library SafeMath {
    
      /**
      * @dev Multiplies two numbers, throws on overflow.
      */
      function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
          return 0;
        }
        uint256 c = a * b;
        assert(c / a == b);
        return c;
      }
    
      /**
      * @dev Integer division of two numbers, truncating the quotient.
      */
      function div(uint256 a, uint256 b) internal pure returns (uint256) {
        // assert(b > 0); // Solidity automatically throws when dividing by 0
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
        return c;
      }
    
      /**
      * @dev Substracts 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;
      }
    }

    File 3 of 3: Units
    pragma solidity ^0.4.25;
    
    /**
     * 
     * World War Goo - Competitive Idle Game
     * 
     * https://ethergoo.io
     * 
     */
    
    
    contract Units {
    
        GooToken constant goo = GooToken(0xdf0960778c6e6597f197ed9a25f12f5d971da86c);
        Army army = Army(0x0);
        Clans clans = Clans(0x0);
        Factories constant factories = Factories(0xc81068cd335889736fc485592e4d73a82403d44b);
    
        mapping(address => mapping(uint256 => UnitsOwned)) public unitsOwned;
        mapping(address => mapping(uint256 => UnitExperience)) public unitExp;
        mapping(address => mapping(uint256 => uint256)) private unitMaxCap;
    
        mapping(address => mapping(uint256 => UnitUpgrades)) private unitUpgrades;
        mapping(address => mapping(uint256 => UpgradesOwned)) public upgradesOwned; // For each unitId, which upgrades owned (3 columns of uint64)
    
        mapping(uint256 => Unit) public unitList;
        mapping(uint256 => Upgrade) public upgradeList;
        mapping(address => bool) operator;
    
        address owner;
    
        constructor() public {
            owner = msg.sender;
        }
    
        struct UnitsOwned {
            uint80 units;
            uint8 factoryBuiltFlag; // Incase user sells units, we still want to keep factory
        }
    
        struct UnitExperience {
            uint224 experience;
            uint32 level;
        }
    
        struct UnitUpgrades {
            uint32 prodIncrease;
            uint32 prodMultiplier;
    
            uint32 attackIncrease;
            uint32 attackMultiplier;
            uint32 defenseIncrease;
            uint32 defenseMultiplier;
            uint32 lootingIncrease;
            uint32 lootingMultiplier;
        }
    
        struct UpgradesOwned {
            uint64 column0;
            uint64 column1;
            uint64 column2;
        }
    
    
        // Unit & Upgrade data:
        
        struct Unit {
            uint256 unitId;
            uint224 gooCost;
            uint256 baseProduction;
            uint80 attack;
            uint80 defense;
            uint80 looting;
        }
    
        struct Upgrade {
            uint256 upgradeId;
            uint224 gooCost;
            uint256 unitId;
            uint256 column; // Columns of upgrades (1st & 2nd are unit specific, then 3rd is capacity)
            uint256 prerequisiteUpgrade;
    
            uint256 unitMaxCapacityGain;
            uint32 prodIncrease;
            uint32 prodMultiplier;
            uint32 attackIncrease;
            uint32 attackMultiplier;
            uint32 defenseIncrease;
            uint32 defenseMultiplier;
            uint32 lootingIncrease;
            uint32 lootingMultiplier;
        }
    
        function setArmy(address armyContract) external {
            require(msg.sender == owner);
            army = Army(armyContract);
        }
    
        function setClans(address clansContract) external {
            require(msg.sender == owner);
            clans = Clans(clansContract);
        }
    
        function setOperator(address gameContract, bool isOperator) external {
            require(msg.sender == owner);
            operator[gameContract] = isOperator;
        }
    
        function mintUnitExternal(uint256 unit, uint80 amount, address player, uint8 chosenPosition) external {
            require(operator[msg.sender]);
            mintUnit(unit, amount, player, chosenPosition);
        }
    
        function mintUnit(uint256 unit, uint80 amount, address player, uint8 chosenPosition) internal {
            UnitsOwned storage existingUnits = unitsOwned[player][unit];
            if (existingUnits.factoryBuiltFlag == 0) {
                // Edge case to create factory for player (on empty tile) where it is their first unit
                uint256[] memory existingFactories = factories.getFactories(player);
                uint256 length = existingFactories.length;
    
                // Provided position is not valid so find valid factory position
                if (chosenPosition >= factories.MAX_SIZE() || (chosenPosition < length && existingFactories[chosenPosition] > 0)) {
                    chosenPosition = 0;
                    while (chosenPosition < length && existingFactories[chosenPosition] > 0) {
                        chosenPosition++;
                    }
                }
    
                factories.addFactory(player, chosenPosition, unit);
                unitsOwned[player][unit] = UnitsOwned(amount, 1); // 1 = Flag to say factory exists
            } else {
                existingUnits.units += amount;
            }
    
            (uint80 attackStats, uint80 defenseStats, uint80 lootingStats) = getUnitsCurrentBattleStats(player, unit);
            if (attackStats > 0 || defenseStats > 0 || lootingStats > 0) {
                army.increasePlayersArmyPowerTrio(player, attackStats * amount, defenseStats * amount, lootingStats * amount);
            } else {
                uint256 prodIncrease = getUnitsCurrentProduction(player, unit) * amount;
                goo.increasePlayersGooProduction(player, prodIncrease / 100);
            }
        }
    
    
        function deleteUnitExternal(uint80 amount, uint256 unit, address player) external {
            require(operator[msg.sender]);
            deleteUnit(amount, unit, player);
        }
    
        function deleteUnit(uint80 amount, uint256 unit, address player) internal {
            (uint80 attackStats, uint80 defenseStats, uint80 lootingStats) = getUnitsCurrentBattleStats(player, unit);
            if (attackStats > 0 || defenseStats > 0 || lootingStats > 0) {
                army.decreasePlayersArmyPowerTrio(player, attackStats * amount, defenseStats * amount, lootingStats * amount);
            } else {
                uint256 prodDecrease = getUnitsCurrentProduction(player, unit) * amount;
                goo.decreasePlayersGooProduction(player, prodDecrease / 100);
            }
            unitsOwned[player][unit].units -= amount;
        }
    
    
        function getUnitsCurrentBattleStats(address player, uint256 unitId) internal view returns (uint80 attack, uint80 defense, uint80 looting) {
            Unit memory unit = unitList[unitId];
            UnitUpgrades memory existingUpgrades = unitUpgrades[player][unitId];
            attack = (unit.attack + existingUpgrades.attackIncrease) * (100 + existingUpgrades.attackMultiplier);
            defense = (unit.defense + existingUpgrades.defenseIncrease) * (100 + existingUpgrades.defenseMultiplier);
            looting = (unit.looting + existingUpgrades.lootingIncrease) * (100 + existingUpgrades.lootingMultiplier);
        }
        
        function getUnitsCurrentProduction(address player, uint256 unitId) public view returns (uint256) {
            UnitUpgrades memory existingUpgrades = unitUpgrades[player][unitId];
            return (unitList[unitId].baseProduction + existingUpgrades.prodIncrease) * (100 + existingUpgrades.prodMultiplier);
        }
    
    
        function buyUnit(uint256 unitId, uint80 amount, uint8 position) external {
            uint224 gooCost = SafeMath224.mul(unitList[unitId].gooCost, amount);
            require(gooCost > 0); // Valid unit
    
            uint80 newTotal = unitsOwned[msg.sender][unitId].units + amount;
            if (newTotal > 99) {
                require(newTotal < 99 + unitMaxCap[msg.sender][unitId]);
            }
    
            // Clan discount
            uint224 unitDiscount = clans.getPlayersClanUpgrade(msg.sender, 1); // class 1 = unit discount
            uint224 reducedGooCost = gooCost - ((gooCost * unitDiscount) / 100);
            uint224 seventyFivePercentRefund = (gooCost * 3) / 4;
    
            // Update players goo
            goo.updatePlayersGooFromPurchase(msg.sender, reducedGooCost);
            goo.mintGoo(seventyFivePercentRefund, this); // 75% refund is stored (in this contract) for when player sells unit
            army.depositSpentGoo(reducedGooCost - seventyFivePercentRefund); // Upto 25% Goo spent goes to divs (Remaining is discount + 75% player gets back when selling unit)
            mintUnit(unitId, amount, msg.sender, position);
        }
    
    
        function sellUnit(uint256 unitId, uint80 amount) external {
            require(unitsOwned[msg.sender][unitId].units >= amount && amount > 0);
    
            uint224 gooCost = unitList[unitId].gooCost;
            require(gooCost > 0);
    
            goo.updatePlayersGoo(msg.sender);
            deleteUnit(amount, unitId, msg.sender);
            goo.transfer(msg.sender, (gooCost * amount * 3) / 4); // Refund 75%
        }
    
    
        function grantArmyExp(address player, uint256 unitId, uint224 amount) external returns(bool) {
            require(operator[msg.sender]);
    
            UnitExperience memory existingExp = unitExp[player][unitId];
            uint224 expRequirement = (existingExp.level + 1) * 80; // Lvl 1: 80; Lvl 2: 160, Lvl 3: 240 (480 in total) etc.
    
            if (existingExp.experience + amount >= expRequirement) {
                existingExp.experience = (existingExp.experience + amount) - expRequirement;
                existingExp.level++;
                unitExp[player][unitId] = existingExp;
    
                // Grant buff to unit (5% additive multiplier)
                UnitUpgrades memory existingUpgrades = unitUpgrades[player][unitId];
                existingUpgrades.attackMultiplier += 5;
                existingUpgrades.defenseMultiplier += 5;
                existingUpgrades.lootingMultiplier += 5;
                unitUpgrades[player][unitId] = existingUpgrades;
    
                // Increase player's army power
                uint80 multiplierGain = unitsOwned[player][unitId].units * 5;
    
                Unit memory unit = unitList[unitId];
                uint80 attackGain = multiplierGain * (unit.attack + existingUpgrades.attackIncrease);
                uint80 defenseGain = multiplierGain * (unit.defense + existingUpgrades.defenseIncrease);
                uint80 lootingGain = multiplierGain * (unit.looting + existingUpgrades.lootingIncrease);
                army.increasePlayersArmyPowerTrio(player, attackGain, defenseGain, lootingGain);
                return true;
            } else {
                unitExp[player][unitId].experience += amount;
                return false;
            }
        }
    
        function increaseUnitCapacity(address player, uint256 upgradeGain, uint256 unitId) external {
            require(operator[msg.sender]);
            unitMaxCap[player][unitId] += upgradeGain;
        }
    
        function decreaseUnitCapacity(address player, uint256 upgradeGain, uint256 unitId) external {
            require(operator[msg.sender]);
            unitMaxCap[player][unitId] -= upgradeGain;
        }
    
    
        function increaseUpgradesExternal(address player, uint256 unitId, uint32 prodIncrease, uint32 prodMultiplier, uint32 attackIncrease, uint32 attackMultiplier, uint32 defenseIncrease, uint32 defenseMultiplier, uint32 lootingIncrease, uint32 lootingMultiplier) external {
            require(operator[msg.sender]);
            Upgrade memory upgrade = Upgrade(0,0,0,0,0,0, prodIncrease, prodMultiplier, attackIncrease, attackMultiplier, defenseIncrease, defenseMultiplier, lootingIncrease, lootingMultiplier);
            increaseUpgrades(player, upgrade, unitId);
        }
    
    
        function increaseUpgrades(address player, Upgrade upgrade, uint256 unitId) internal {
            uint80 units = unitsOwned[player][unitId].units;
            UnitUpgrades memory existingUpgrades = unitUpgrades[player][unitId];
    
            Unit memory unit = unitList[unitId];
            if (unit.baseProduction > 0) {
                // Increase goo production
                uint256 prodGain = units * upgrade.prodMultiplier * (unit.baseProduction + existingUpgrades.prodIncrease); // Multiplier gains
                prodGain += units * upgrade.prodIncrease * (100 + existingUpgrades.prodMultiplier); // Base prod gains
    
                goo.updatePlayersGoo(player);
                goo.increasePlayersGooProduction(player, prodGain / 100);
            } else {
                // Increase army power
                uint80 attackGain = units * upgrade.attackMultiplier * (unit.attack + existingUpgrades.attackIncrease); // Multiplier gains
                uint80 defenseGain = units * upgrade.defenseMultiplier * (unit.defense + existingUpgrades.defenseIncrease); // Multiplier gains
                uint80 lootingGain = units * upgrade.lootingMultiplier * (unit.looting + existingUpgrades.lootingIncrease); // Multiplier gains
    
                attackGain += units * upgrade.attackIncrease * (100 + existingUpgrades.attackMultiplier); // + Base gains
                defenseGain += units * upgrade.defenseIncrease * (100 + existingUpgrades.defenseMultiplier); // + Base gains
                lootingGain += units * upgrade.lootingIncrease * (100 + existingUpgrades.lootingMultiplier); // + Base gains
    
                army.increasePlayersArmyPowerTrio(player, attackGain, defenseGain, lootingGain);
            }
    
            existingUpgrades.prodIncrease += upgrade.prodIncrease;
            existingUpgrades.prodMultiplier += upgrade.prodMultiplier;
            existingUpgrades.attackIncrease += upgrade.attackIncrease;
            existingUpgrades.attackMultiplier += upgrade.attackMultiplier;
            existingUpgrades.defenseIncrease += upgrade.defenseIncrease;
            existingUpgrades.defenseMultiplier += upgrade.defenseMultiplier;
            existingUpgrades.lootingIncrease += upgrade.lootingIncrease;
            existingUpgrades.lootingMultiplier += upgrade.lootingMultiplier;
            unitUpgrades[player][unitId] = existingUpgrades;
        }
    
    
        function decreaseUpgradesExternal(address player, uint256 unitId, uint32 prodIncrease, uint32 prodMultiplier, uint32 attackIncrease, uint32 attackMultiplier, uint32 defenseIncrease, uint32 defenseMultiplier, uint32 lootingIncrease, uint32 lootingMultiplier) external {
            require(operator[msg.sender]);
            Upgrade memory upgrade = Upgrade(0,0,0,0,0,0, prodIncrease, prodMultiplier, attackIncrease, attackMultiplier, defenseIncrease, defenseMultiplier, lootingIncrease, lootingMultiplier);
            decreaseUpgrades(player, upgrade, unitId);
        }
    
    
        function decreaseUpgrades(address player, Upgrade upgrade, uint256 unitId) internal {
            uint80 units = unitsOwned[player][unitId].units;
            UnitUpgrades memory existingUpgrades = unitUpgrades[player][unitId];
    
            Unit memory unit = unitList[unitId];
            if (unit.baseProduction > 0) {
                // Decrease goo production
                uint256 prodLoss = units * upgrade.prodMultiplier * (unit.baseProduction + existingUpgrades.prodIncrease); // Multiplier losses
                prodLoss += units * upgrade.prodIncrease * (100 + existingUpgrades.prodMultiplier); // Base prod losses
    
                goo.updatePlayersGoo(player);
                goo.decreasePlayersGooProduction(player, prodLoss / 100);
            } else {
                // Decrease army power
                uint80 attackLoss = units * upgrade.attackMultiplier * (unit.attack + existingUpgrades.attackIncrease); // Multiplier losses
                uint80 defenseLoss = units * upgrade.defenseMultiplier * (unit.defense + existingUpgrades.defenseIncrease); // Multiplier losses
                uint80 lootingLoss = units * upgrade.lootingMultiplier * (unit.looting + existingUpgrades.lootingIncrease); // Multiplier losses
    
                attackLoss += units * upgrade.attackIncrease * (100 + existingUpgrades.attackMultiplier); // + Base losses
                defenseLoss += units * upgrade.defenseIncrease * (100 + existingUpgrades.defenseMultiplier); // + Base losses
                lootingLoss += units * upgrade.lootingIncrease * (100 + existingUpgrades.lootingMultiplier); // + Base losses
                army.decreasePlayersArmyPowerTrio(player, attackLoss, defenseLoss, lootingLoss);
            }
    
            existingUpgrades.prodIncrease -= upgrade.prodIncrease;
            existingUpgrades.prodMultiplier -= upgrade.prodMultiplier;
            existingUpgrades.attackIncrease -= upgrade.attackIncrease;
            existingUpgrades.attackMultiplier -= upgrade.attackMultiplier;
            existingUpgrades.defenseIncrease -= upgrade.defenseIncrease;
            existingUpgrades.defenseMultiplier -= upgrade.defenseMultiplier;
            existingUpgrades.lootingIncrease -= upgrade.lootingIncrease;
            existingUpgrades.lootingMultiplier -= upgrade.lootingMultiplier;
            unitUpgrades[player][unitId] = existingUpgrades;
        }
    
        function swapUpgradesExternal(address player, uint256 unitId, uint32[8] upgradeGains, uint32[8] upgradeLosses) external {
            require(operator[msg.sender]);
    
            UnitUpgrades memory existingUpgrades = unitUpgrades[player][unitId];
            Unit memory unit = unitList[unitId];
    
            if (unit.baseProduction > 0) {
                // Change goo production
                gooProductionChange(player, unitId, existingUpgrades, unit.baseProduction, upgradeGains, upgradeLosses);
            } else {
                // Change army power
                armyPowerChange(player, existingUpgrades, unit, upgradeGains, upgradeLosses);
            }
        }
        
        function armyPowerChange(address player, UnitUpgrades existingUpgrades, Unit unit, uint32[8] upgradeGains, uint32[8] upgradeLosses) internal {
            int256 existingAttack = int256((unit.attack + existingUpgrades.attackIncrease) * (100 + existingUpgrades.attackMultiplier));
            int256 existingDefense = int256((unit.defense + existingUpgrades.defenseIncrease) * (100 + existingUpgrades.defenseMultiplier));
            int256 existingLooting = int256((unit.looting + existingUpgrades.lootingIncrease) * (100 + existingUpgrades.lootingMultiplier));
        
            existingUpgrades.attackIncrease = uint32(int(existingUpgrades.attackIncrease) + (int32(upgradeGains[2]) - int32(upgradeLosses[2])));
            existingUpgrades.attackMultiplier = uint32(int(existingUpgrades.attackMultiplier) + (int32(upgradeGains[3]) - int32(upgradeLosses[3])));
            existingUpgrades.defenseIncrease = uint32(int(existingUpgrades.defenseIncrease) + (int32(upgradeGains[4]) - int32(upgradeLosses[4])));
            existingUpgrades.defenseMultiplier = uint32(int(existingUpgrades.defenseMultiplier) + (int32(upgradeGains[5]) - int32(upgradeLosses[5])));
            existingUpgrades.lootingIncrease = uint32(int(existingUpgrades.lootingIncrease) + (int32(upgradeGains[6]) - int32(upgradeLosses[6])));
            existingUpgrades.lootingMultiplier = uint32(int(existingUpgrades.lootingMultiplier) + (int32(upgradeGains[7]) - int32(upgradeLosses[7])));
            
            int256 attackChange = ((int256(unit.attack) + existingUpgrades.attackIncrease) * (100 + existingUpgrades.attackMultiplier)) - existingAttack;
            int256 defenseChange = ((int256(unit.defense) + existingUpgrades.defenseIncrease) * (100 + existingUpgrades.defenseMultiplier)) - existingDefense;
            int256 lootingChange = ((int256(unit.looting) + existingUpgrades.lootingIncrease) * (100 + existingUpgrades.lootingMultiplier)) - existingLooting;
            
            uint256 unitId = unit.unitId;
            int256 units = int256(unitsOwned[player][unitId].units);
            
            army.changePlayersArmyPowerTrio(player, units * attackChange, units * defenseChange, units * lootingChange);
            unitUpgrades[player][unitId] = existingUpgrades;
        }
        
        function gooProductionChange(address player, uint256 unitId, UnitUpgrades existingUpgrades, uint256 baseProduction, uint32[8] upgradeGains, uint32[8] upgradeLosses) internal {
            goo.updatePlayersGoo(player);
            
            int256 existingProd = int256((baseProduction + existingUpgrades.prodIncrease) * (100 + existingUpgrades.prodMultiplier));
            existingUpgrades.prodIncrease = uint32(int(existingUpgrades.prodIncrease) + (int32(upgradeGains[0]) - int32(upgradeLosses[0])));
            existingUpgrades.prodMultiplier = uint32(int(existingUpgrades.prodMultiplier) + (int32(upgradeGains[1]) - int32(upgradeLosses[1])));            
            
            int256 prodChange = ((int256(baseProduction) + existingUpgrades.prodIncrease) * (100 + existingUpgrades.prodMultiplier)) - existingProd;
            if (prodChange > 0) {
                goo.increasePlayersGooProduction(player, (unitsOwned[player][unitId].units * uint256(prodChange)) / 100);
            } else {
                goo.decreasePlayersGooProduction(player, (unitsOwned[player][unitId].units * uint256(-prodChange)) / 100);
            }
            
            unitUpgrades[player][unitId] = existingUpgrades;
        }
    
        function addUnit(uint256 id, uint224 baseGooCost, uint256 baseGooProduction, uint80 baseAttack, uint80 baseDefense, uint80 baseLooting) external {
            require(operator[msg.sender]);
            unitList[id] = Unit(id, baseGooCost, baseGooProduction, baseAttack, baseDefense, baseLooting);
        }
    
    
        function addUpgrade(uint256 id, uint224 gooCost, uint256 unit, uint256 column, uint256 prereq, uint256 unitMaxCapacityGain, uint32[8] upgradeGains) external {
            require(operator[msg.sender]);
            upgradeList[id] = Upgrade(id, gooCost, unit, column, prereq, unitMaxCapacityGain, upgradeGains[0], upgradeGains[1], upgradeGains[2], upgradeGains[3], upgradeGains[4], upgradeGains[5], upgradeGains[6], upgradeGains[7]);
        }
    
        function buyUpgrade(uint64 upgradeId) external {
            Upgrade memory upgrade = upgradeList[upgradeId];
            uint256 unitId = upgrade.unitId;
            UpgradesOwned memory ownedUpgrades = upgradesOwned[msg.sender][unitId];
    
            uint64 latestUpgradeOwnedForColumn;
            if (upgrade.column == 0) {
                latestUpgradeOwnedForColumn = ownedUpgrades.column0;
                ownedUpgrades.column0 = upgradeId;  // Update upgradesOwned
            } else if (upgrade.column == 1) {
                latestUpgradeOwnedForColumn = ownedUpgrades.column1;
                ownedUpgrades.column1 = upgradeId;  // Update upgradesOwned
            } else if (upgrade.column == 2) {
                latestUpgradeOwnedForColumn = ownedUpgrades.column2;
                ownedUpgrades.column2 = upgradeId;  // Update upgradesOwned
            }
            upgradesOwned[msg.sender][unitId] = ownedUpgrades;
    
            require(unitId > 0); // Valid upgrade
            require(latestUpgradeOwnedForColumn < upgradeId); // Haven't already purchased
            require(latestUpgradeOwnedForColumn >= upgrade.prerequisiteUpgrade); // Own prequisite
    
            // Clan discount
            uint224 upgradeDiscount = clans.getPlayersClanUpgrade(msg.sender, 0); // class 0 = upgrade discount
            uint224 reducedUpgradeCost = upgrade.gooCost - ((upgrade.gooCost * upgradeDiscount) / 100);
    
            // Update players goo
            goo.updatePlayersGooFromPurchase(msg.sender, reducedUpgradeCost);
            army.depositSpentGoo(reducedUpgradeCost); // Transfer to goo bankroll
    
            // Update stats for upgrade
            if (upgrade.column == 2) {
                unitMaxCap[msg.sender][unitId] += upgrade.unitMaxCapacityGain;
            } else if (upgrade.column == 1) {
                increaseUpgrades(msg.sender, upgrade, unitId);
            } else if (upgrade.column == 0) {
                increaseUpgrades(msg.sender, upgrade, unitId);
            }
        }
    
    }
    
    
    
    
    contract GooToken {
        function transfer(address to, uint256 tokens) external returns (bool);
        function increasePlayersGooProduction(address player, uint256 increase) external;
        function decreasePlayersGooProduction(address player, uint256 decrease) external;
        function updatePlayersGooFromPurchase(address player, uint224 purchaseCost) external;
        function updatePlayersGoo(address player) external;
        function mintGoo(uint224 amount, address player) external;
    }
    
    contract Army {
        function depositSpentGoo(uint224 gooSpent) external;
        function increasePlayersArmyPowerTrio(address player, uint80 attackGain, uint80 defenseGain, uint80 lootingGain) public;
        function decreasePlayersArmyPowerTrio(address player, uint80 attackLoss, uint80 defenseLoss, uint80 lootingLoss) public;
        function changePlayersArmyPowerTrio(address player, int attackChange, int defenseChange, int lootingChange) public;
    
    }
    
    contract Clans {
        mapping(uint256 => uint256) public clanTotalArmyPower;
        function totalSupply() external view returns (uint256);
        function depositGoo(uint256 amount, uint256 clanId) external;
        function getPlayerFees(address player) external view returns (uint224 clansFee, uint224 leadersFee, address leader, uint224 referalsFee, address referer);
        function getPlayersClanUpgrade(address player, uint256 upgradeClass) external view returns (uint224 upgradeGain);
        function mintGoo(address player, uint256 amount) external;
        function increaseClanPower(address player, uint256 amount) external;
        function decreaseClanPower(address player, uint256 amount) external;
    }
    
    contract Factories {
        uint256 public constant MAX_SIZE = 40;
        function getFactories(address player) external returns (uint256[]);
        function addFactory(address player, uint8 position, uint256 unitId) external;
    }
    
    
    library SafeMath {
    
      /**
      * @dev Multiplies two numbers, throws on overflow.
      */
      function mul(uint224 a, uint224 b) internal pure returns (uint224) {
        if (a == 0) {
          return 0;
        }
        uint224 c = a * b;
        assert(c / a == b);
        return c;
      }
    
      /**
      * @dev Integer division of two numbers, truncating the quotient.
      */
      function div(uint256 a, uint256 b) internal pure returns (uint256) {
        // assert(b > 0); // Solidity automatically throws when dividing by 0
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
        return c;
      }
    
      /**
      * @dev Substracts 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;
      }
    }
    
    
    library SafeMath224 {
    
      /**
      * @dev Multiplies two numbers, throws on overflow.
      */
      function mul(uint224 a, uint224 b) internal pure returns (uint224) {
        if (a == 0) {
          return 0;
        }
        uint224 c = a * b;
        assert(c / a == b);
        return c;
      }
    
      /**
      * @dev Integer division of two numbers, truncating the quotient.
      */
      function div(uint224 a, uint224 b) internal pure returns (uint224) {
        // assert(b > 0); // Solidity automatically throws when dividing by 0
        uint224 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
        return c;
      }
    
      /**
      * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
      */
      function sub(uint224 a, uint224 b) internal pure returns (uint224) {
        assert(b <= a);
        return a - b;
      }
    
      /**
      * @dev Adds two numbers, throws on overflow.
      */
      function add(uint224 a, uint224 b) internal pure returns (uint224) {
        uint224 c = a + b;
        assert(c >= a);
        return c;
      }
    }