pragma solidity ^0.4.25;

/**
 * 
 * World War Goo - Competitive Idle Game
 * 
 * https:/ethergoo.io
 * 
 */

interface ERC20 {
    function totalSupply() external constant returns (uint);
    function balanceOf(address tokenOwner) external constant returns (uint balance);
    function allowance(address tokenOwner, address spender) external constant returns (uint remaining);
    function transfer(address to, uint tokens) external returns (bool success);
    function approve(address spender, uint tokens) external returns (bool success);
    function approveAndCall(address spender, uint tokens, bytes data) external returns (bool success);
    function transferFrom(address from, address to, uint tokens) external returns (bool success);

    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}

interface ApproveAndCallFallBack {
    function receiveApproval(address from, uint256 tokens, address token, bytes data) external;
}

contract GooToken is ERC20 {
    using SafeMath for uint;
    using SafeMath224 for uint224;
    
    string public constant name  = "Vials of Goo";
    string public constant symbol = "GOO";
    uint8 public constant decimals = 12;
    uint224 public constant MAX_SUPPLY = 21000000 * (10 ** 12); // 21 million (to 12 szabo decimals)
    
    mapping(address => UserBalance) balances;
    mapping(address => mapping(address => uint256)) allowed;
    
    mapping(address => uint256) public gooProduction; // Store player's current goo production
    mapping(address => bool) operator;
    
    uint224 private totalGoo;
    uint256 public teamAllocation; // 10% reserve allocation towards exchange-listing negotiations, game costs, and ongoing community contests/aidrops
    address public owner; // Minor management of game
    bool public supplyCapHit; // No more production once we hit MAX_SUPPLY
    
    struct UserBalance {
        uint224 goo;
        uint32 lastGooSaveTime;
    }
    
    constructor() public {
        teamAllocation = MAX_SUPPLY / 10;
        owner = msg.sender;
    }
    
    function totalSupply() external view returns(uint) {
        return totalGoo;
    }
    
    function transfer(address to, uint256 tokens) external returns (bool) {
        updatePlayersGooInternal(msg.sender);
        
        require(tokens <= MAX_SUPPLY); // Prevent uint224 overflow
        uint224 amount = uint224(tokens); // Goo is uint224 but must comply to ERC20's uint256

        balances[msg.sender].goo = balances[msg.sender].goo.sub(amount);
        emit Transfer(msg.sender, to, amount);
        
        if (to == address(0)) { // Burn
            totalGoo -= amount;
        } else {
            balances[to].goo = balances[to].goo.add(amount);
        }
        return true;
    }
    
    function transferFrom(address from, address to, uint256 tokens) external returns (bool) {
        updatePlayersGooInternal(from);
        
        require(tokens <= MAX_SUPPLY); // Prevent uint224 overflow
        uint224 amount = uint224(tokens); // Goo is uint224 but must comply to ERC20's uint256
        
        allowed[from][msg.sender] = allowed[from][msg.sender].sub(amount);
        balances[from].goo = balances[from].goo.sub(amount);
        emit Transfer(from, to, amount);
        
        if (to == address(0)) { // Burn
            totalGoo -= amount;
        } else {
            balances[to].goo = balances[to].goo.add(amount);
        }
        return true;
    }
    
    function unlockAllocation(uint224 amount, address recipient) external {
        require(msg.sender == owner);
        teamAllocation = teamAllocation.sub(amount); // Hard limit
        
        totalGoo += amount;
        balances[recipient].goo = balances[recipient].goo.add(amount);
        emit Transfer(address(0), recipient, amount);
    }
    
    function setOperator(address gameContract, bool isOperator) external {
        require(msg.sender == owner);
        operator[gameContract] = isOperator;
    }
    
    function approve(address spender, uint256 tokens) external returns (bool) {
        allowed[msg.sender][spender] = tokens;
        emit Approval(msg.sender, spender, tokens);
        return true;
    }
    
    function approveAndCall(address spender, uint256 tokens, bytes data) external returns (bool) {
        allowed[msg.sender][spender] = tokens;
        emit Approval(msg.sender, spender, tokens);
        ApproveAndCallFallBack(spender).receiveApproval(msg.sender, tokens, this, data);
        return true;
    }
    
    function allowance(address tokenOwner, address spender) external view returns (uint256) {
        return allowed[tokenOwner][spender];
    }

    function recoverAccidentalTokens(address tokenAddress, uint256 tokens) external {
        require(msg.sender == owner);
        require(tokenAddress != address(this)); // Not Goo
        ERC20(tokenAddress).transfer(owner, tokens);
    }
    
    function balanceOf(address player) public constant returns(uint256) {
        return balances[player].goo + balanceOfUnclaimedGoo(player);
    }
    
    function balanceOfUnclaimedGoo(address player) internal constant returns (uint224 gooGain) {
        if (supplyCapHit) return;
        
        uint32 lastSave = balances[player].lastGooSaveTime;
        if (lastSave > 0 && lastSave < block.timestamp) {
            gooGain = uint224(gooProduction[player] * (block.timestamp - lastSave));
        }
        
        if (totalGoo + gooGain >= MAX_SUPPLY) {
            gooGain = MAX_SUPPLY - totalGoo;
        }
    }
    
    function mintGoo(uint224 amount, address player) external {
        if (supplyCapHit) return;
        require(operator[msg.sender]);
        
        uint224 minted = amount;
        if (totalGoo.add(amount) >= MAX_SUPPLY) {
            supplyCapHit = true;
            minted = MAX_SUPPLY - totalGoo;
        }

        balances[player].goo += minted;
        totalGoo += minted;
        emit Transfer(address(0), player, minted);
    }
    
    function updatePlayersGoo(address player) external {
        require(operator[msg.sender]);
        updatePlayersGooInternal(player);
    }
    
    function updatePlayersGooInternal(address player) internal {
        uint224 gooGain = balanceOfUnclaimedGoo(player);
        
        UserBalance memory balance = balances[player];
        if (gooGain > 0) {
            totalGoo += gooGain;
            if (!supplyCapHit && totalGoo == MAX_SUPPLY) {
                supplyCapHit = true;
            }
            
            balance.goo += gooGain;
            emit Transfer(address(0), player, gooGain);
        }
        
        if (balance.lastGooSaveTime < block.timestamp) {
            balance.lastGooSaveTime = uint32(block.timestamp); 
            balances[player] = balance;
        }
    }
    
    function updatePlayersGooFromPurchase(address player, uint224 purchaseCost) external {
        require(operator[msg.sender]);
        uint224 unclaimedGoo = balanceOfUnclaimedGoo(player);
        
        UserBalance memory balance = balances[player];
        balance.lastGooSaveTime = uint32(block.timestamp); 
        
        if (purchaseCost > unclaimedGoo) {
            uint224 gooDecrease = purchaseCost - unclaimedGoo;
            totalGoo -= gooDecrease;
            balance.goo = balance.goo.sub(gooDecrease);
            emit Transfer(player, address(0), gooDecrease);
        } else {
            uint224 gooGain = unclaimedGoo - purchaseCost;
            totalGoo += gooGain;
            balance.goo += gooGain;
            if (!supplyCapHit && totalGoo == MAX_SUPPLY) {
                supplyCapHit = true;
            }
            emit Transfer(address(0), player, gooGain);
        }
        balances[player] = balance;
    }
    
    function increasePlayersGooProduction(address player, uint256 increase) external {
        require(operator[msg.sender]);
        gooProduction[player] += increase;
    }
    
    function decreasePlayersGooProduction(address player, uint256 decrease) external {
        require(operator[msg.sender]);
        gooProduction[player] -= decrease;
    }

}



















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;
  }
}




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;
  }
}