Transaction Hash:
Block:
5654553 at May-22-2018 12:01:48 AM +UTC
Transaction Fee:
0.00133689303 ETH
$2.72
Gas Used:
166,903 Gas / 8.01 Gwei
Emitted Events:
| 76 |
Goo.PlayerAttacked( attacker=[Sender] 0x627fee75db7d30c5f14375d4140544d62822578d, target=0x9AfDa2dd...b0CF33F97, success=True, gooStolen=837769365000 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x57B116DA...29c1B2355 | |||||
| 0x627feE75...62822578D |
0.020174142432827596 Eth
Nonce: 2832
|
0.018837249402827596 Eth
Nonce: 2833
| 0.00133689303 | ||
|
0xEA674fdD...16B898ec8
Miner
| (Ethermine) | 550.387952537841183538 Eth | 550.389289430871183538 Eth | 0.00133689303 |
Execution Trace
Goo.attackPlayer( target=0x9AfDa2dd29775559f42833302fEB357b0CF33F97 )
-
0xff18a0e035b7d835d47189a000547f041a57f5d0.CALL( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.69632f56( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.6101a1f7( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.21446cfe( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.69632f56( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.6101a1f7( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.21446cfe( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.69632f56( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.6101a1f7( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.21446cfe( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.69632f56( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.6101a1f7( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.21446cfe( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.69632f56( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.6101a1f7( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.21446cfe( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.69632f56( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.6101a1f7( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.21446cfe( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.69632f56( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.6101a1f7( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.21446cfe( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.69632f56( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.6101a1f7( ) -
0xff18a0e035b7d835d47189a000547f041a57f5d0.21446cfe( )
attackPlayer[Goo (ln:831)]
getPlayersBattlePower[Goo (ln:839)]battleUnitIdRange[Goo (ln:878)]getUnitsAttack[Goo (ln:886)]unitAttack[Goo (ln:925)]
getUnitsStealingCapacity[Goo (ln:887)]unitStealingCapacity[Goo (ln:933)]
getUnitsDefense[Goo (ln:889)]unitDefense[Goo (ln:929)]
getWeakenedDefensePower[Goo (ln:842)]balanceOf[Goo (ln:847)]balanceOfUnclaimedGoo[Goo (ln:156)]getGooProduction[Goo (ln:162)]
balanceOfUnclaimedGoo[Goo (ln:849)]getGooProduction[Goo (ln:162)]
PlayerAttacked[Goo (ln:860)]PlayerAttacked[Goo (ln:862)]balanceOf[Goo (ln:862)]balanceOfUnclaimedGoo[Goo (ln:156)]getGooProduction[Goo (ln:162)]
balanceOf[Goo (ln:863)]balanceOfUnclaimedGoo[Goo (ln:156)]getGooProduction[Goo (ln:162)]
PlayerAttacked[Goo (ln:871)]
pragma solidity ^0.4.0;
interface ERC20 {
function totalSupply() public constant returns (uint);
function balanceOf(address tokenOwner) public constant returns (uint balance);
function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
function transfer(address to, uint tokens) public returns (bool success);
function approve(address spender, uint tokens) public returns (bool success);
function transferFrom(address from, address to, uint tokens) public returns (bool success);
event Transfer(address indexed from, address indexed to, uint tokens);
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}
// GOO - Crypto Idle Game
// https://ethergoo.io
contract Goo is ERC20 {
string public constant name = "IdleEth";
string public constant symbol = "Goo";
uint8 public constant decimals = 0;
uint256 private roughSupply;
uint256 public totalGooProduction;
address public owner; // Minor management of game
bool public gameStarted;
uint256 public researchDivPercent = 8;
uint256 public gooDepositDivPercent = 2;
uint256 public totalEtherGooResearchPool; // Eth dividends to be split between players' goo production
uint256[] private totalGooProductionSnapshots; // The total goo production for each prior day past
uint256[] private totalGooDepositSnapshots; // The total goo deposited for each prior day past
uint256[] private allocatedGooResearchSnapshots; // Div pot #1 (research eth allocated to each prior day past)
uint256[] private allocatedGooDepositSnapshots; // Div pot #2 (deposit eth allocated to each prior day past)
uint256 public nextSnapshotTime;
// Balances for each player
mapping(address => uint256) private ethBalance;
mapping(address => uint256) private gooBalance;
mapping(address => mapping(uint256 => uint256)) private gooProductionSnapshots; // Store player's goo production for given day (snapshot)
mapping(address => mapping(uint256 => uint256)) private gooDepositSnapshots; // Store player's goo deposited for given day (snapshot)
mapping(address => mapping(uint256 => bool)) private gooProductionZeroedSnapshots; // This isn't great but we need know difference between 0 production and an unused/inactive day.
mapping(address => uint256) private lastGooSaveTime; // Seconds (last time player claimed their produced goo)
mapping(address => uint256) public lastGooProductionUpdate; // Days (last snapshot player updated their production)
mapping(address => uint256) private lastGooResearchFundClaim; // Days (snapshot number)
mapping(address => uint256) private lastGooDepositFundClaim; // Days (snapshot number)
mapping(address => uint256) private battleCooldown; // If user attacks they cannot attack again for short time
// Stuff owned by each player
mapping(address => mapping(uint256 => uint256)) private unitsOwned;
mapping(address => mapping(uint256 => bool)) private upgradesOwned;
mapping(uint256 => address) private rareItemOwner;
mapping(uint256 => uint256) private rareItemPrice;
// Rares & Upgrades (Increase unit's production / attack etc.)
mapping(address => mapping(uint256 => uint256)) private unitGooProductionIncreases; // Adds to the goo per second
mapping(address => mapping(uint256 => uint256)) private unitGooProductionMultiplier; // Multiplies the goo per second
mapping(address => mapping(uint256 => uint256)) private unitAttackIncreases;
mapping(address => mapping(uint256 => uint256)) private unitAttackMultiplier;
mapping(address => mapping(uint256 => uint256)) private unitDefenseIncreases;
mapping(address => mapping(uint256 => uint256)) private unitDefenseMultiplier;
mapping(address => mapping(uint256 => uint256)) private unitGooStealingIncreases;
mapping(address => mapping(uint256 => uint256)) private unitGooStealingMultiplier;
mapping(address => mapping(uint256 => uint256)) private unitMaxCap;
// Mapping of approved ERC20 transfers (by player)
mapping(address => mapping(address => uint256)) private allowed;
mapping(address => bool) private protectedAddresses; // For npc exchanges (requires 0 goo production)
// Raffle structures
struct TicketPurchases {
TicketPurchase[] ticketsBought;
uint256 numPurchases; // Allows us to reset without clearing TicketPurchase[] (avoids potential for gas limit)
uint256 raffleId;
}
// Allows us to query winner without looping (avoiding potential for gas limit)
struct TicketPurchase {
uint256 startId;
uint256 endId;
}
// Raffle tickets
mapping(address => TicketPurchases) private rareItemTicketsBoughtByPlayer;
mapping(uint256 => address[]) private itemRafflePlayers;
// Duplicating for the two raffles is not ideal
mapping(address => TicketPurchases) private rareUnitTicketsBoughtByPlayer;
mapping(uint256 => address[]) private unitRafflePlayers;
// Item raffle info
uint256 private constant RAFFLE_TICKET_BASE_GOO_PRICE = 1000;
uint256 private itemRaffleEndTime;
uint256 private itemRaffleRareId;
uint256 private itemRaffleTicketsBought;
address private itemRaffleWinner; // Address of winner
bool private itemRaffleWinningTicketSelected;
uint256 private itemRaffleTicketThatWon;
// Unit raffle info
uint256 private unitRaffleEndTime;
uint256 private unitRaffleId; // Raffle Id
uint256 private unitRaffleRareId; // Unit Id
uint256 private unitRaffleTicketsBought;
address private unitRaffleWinner; // Address of winner
bool private unitRaffleWinningTicketSelected;
uint256 private unitRaffleTicketThatWon;
// Minor game events
event UnitBought(address player, uint256 unitId, uint256 amount);
event UnitSold(address player, uint256 unitId, uint256 amount);
event PlayerAttacked(address attacker, address target, bool success, uint256 gooStolen);
event ReferalGain(address player, address referal, uint256 amount);
event UpgradeMigration(address player, uint256 upgradeId, uint256 txProof);
GooGameConfig schema = GooGameConfig(0xf925a82b8c26520170c8d51b65a7def6364877b3);
// Constructor
function Goo() public payable {
owner = msg.sender;
}
function() payable {
// Fallback will donate to pot
totalEtherGooResearchPool += msg.value;
}
function beginGame(uint256 firstDivsTime) external payable {
require(msg.sender == owner);
require(!gameStarted);
gameStarted = true; // GO-OOOO!
nextSnapshotTime = firstDivsTime;
totalGooDepositSnapshots.push(0); // Add initial-zero snapshot
totalEtherGooResearchPool = msg.value; // Seed pot
}
// Incase community prefers goo deposit payments over production %, can be tweaked for balance
function tweakDailyDividends(uint256 newResearchPercent, uint256 newGooDepositPercent) external {
require(msg.sender == owner);
require(newResearchPercent > 0 && newResearchPercent <= 10);
require(newGooDepositPercent > 0 && newGooDepositPercent <= 10);
researchDivPercent = newResearchPercent;
gooDepositDivPercent = newGooDepositPercent;
}
function totalSupply() public constant returns(uint256) {
return roughSupply; // Stored goo (rough supply as it ignores earned/unclaimed goo)
}
function balanceOf(address player) public constant returns(uint256) {
return gooBalance[player] + balanceOfUnclaimedGoo(player);
}
function balanceOfUnclaimedGoo(address player) internal constant returns (uint256) {
uint256 lastSave = lastGooSaveTime[player];
if (lastSave > 0 && lastSave < block.timestamp) {
return (getGooProduction(player) * (block.timestamp - lastSave)) / 100;
}
return 0;
}
function etherBalanceOf(address player) public constant returns(uint256) {
return ethBalance[player];
}
function transfer(address recipient, uint256 amount) public returns (bool) {
updatePlayersGoo(msg.sender);
require(amount <= gooBalance[msg.sender]);
gooBalance[msg.sender] -= amount;
gooBalance[recipient] += amount;
emit Transfer(msg.sender, recipient, amount);
return true;
}
function transferFrom(address player, address recipient, uint256 amount) public returns (bool) {
updatePlayersGoo(player);
require(amount <= allowed[player][msg.sender] && amount <= gooBalance[player]);
gooBalance[player] -= amount;
gooBalance[recipient] += amount;
allowed[player][msg.sender] -= amount;
emit Transfer(player, recipient, amount);
return true;
}
function approve(address approvee, uint256 amount) public returns (bool){
allowed[msg.sender][approvee] = amount;
emit Approval(msg.sender, approvee, amount);
return true;
}
function allowance(address player, address approvee) public constant returns(uint256){
return allowed[player][approvee];
}
function getGooProduction(address player) public constant returns (uint256){
return gooProductionSnapshots[player][lastGooProductionUpdate[player]];
}
function updatePlayersGoo(address player) internal {
uint256 gooGain = balanceOfUnclaimedGoo(player);
lastGooSaveTime[player] = block.timestamp;
roughSupply += gooGain;
gooBalance[player] += gooGain;
}
function updatePlayersGooFromPurchase(address player, uint256 purchaseCost) internal {
uint256 unclaimedGoo = balanceOfUnclaimedGoo(player);
if (purchaseCost > unclaimedGoo) {
uint256 gooDecrease = purchaseCost - unclaimedGoo;
require(gooBalance[player] >= gooDecrease);
roughSupply -= gooDecrease;
gooBalance[player] -= gooDecrease;
} else {
uint256 gooGain = unclaimedGoo - purchaseCost;
roughSupply += gooGain;
gooBalance[player] += gooGain;
}
lastGooSaveTime[player] = block.timestamp;
}
function increasePlayersGooProduction(address player, uint256 increase) internal {
gooProductionSnapshots[player][allocatedGooResearchSnapshots.length] = getGooProduction(player) + increase;
lastGooProductionUpdate[player] = allocatedGooResearchSnapshots.length;
totalGooProduction += increase;
}
function reducePlayersGooProduction(address player, uint256 decrease) internal {
uint256 previousProduction = getGooProduction(player);
uint256 newProduction = SafeMath.sub(previousProduction, decrease);
if (newProduction == 0) { // Special case which tangles with "inactive day" snapshots (claiming divs)
gooProductionZeroedSnapshots[player][allocatedGooResearchSnapshots.length] = true;
delete gooProductionSnapshots[player][allocatedGooResearchSnapshots.length]; // 0
} else {
gooProductionSnapshots[player][allocatedGooResearchSnapshots.length] = newProduction;
}
lastGooProductionUpdate[player] = allocatedGooResearchSnapshots.length;
totalGooProduction -= decrease;
}
function buyBasicUnit(uint256 unitId, uint256 amount) external {
uint256 schemaUnitId;
uint256 gooProduction;
uint256 gooCost;
uint256 ethCost;
uint256 existing = unitsOwned[msg.sender][unitId];
(schemaUnitId, gooProduction, gooCost, ethCost) = schema.getUnitInfo(unitId, existing, amount);
require(gameStarted);
require(schemaUnitId > 0); // Valid unit
require(ethCost == 0); // Free unit
uint256 newTotal = SafeMath.add(existing, amount);
if (newTotal > 99) { // Default unit limit
require(newTotal <= unitMaxCap[msg.sender][unitId]); // Housing upgrades (allow more units)
}
// Update players goo
updatePlayersGooFromPurchase(msg.sender, gooCost);
if (gooProduction > 0) {
increasePlayersGooProduction(msg.sender, getUnitsProduction(msg.sender, unitId, amount));
}
unitsOwned[msg.sender][unitId] = newTotal;
emit UnitBought(msg.sender, unitId, amount);
}
function buyEthUnit(uint256 unitId, uint256 amount) external payable {
uint256 schemaUnitId;
uint256 gooProduction;
uint256 gooCost;
uint256 ethCost;
uint256 existing = unitsOwned[msg.sender][unitId];
(schemaUnitId, gooProduction, gooCost, ethCost) = schema.getUnitInfo(unitId, existing, amount);
require(gameStarted);
require(schemaUnitId > 0);
require(ethBalance[msg.sender] + msg.value >= ethCost);
if (ethCost > msg.value) {
ethBalance[msg.sender] -= (ethCost - msg.value);
}
uint256 devFund = ethCost / 50; // 2% fee on purchases (marketing, gameplay & maintenance)
uint256 dividends = (ethCost - devFund) / 4; // 25% goes to pool (75% retained for sale value)
totalEtherGooResearchPool += dividends;
ethBalance[owner] += devFund;
uint256 newTotal = SafeMath.add(existing, amount);
if (newTotal > 99) { // Default unit limit
require(newTotal <= unitMaxCap[msg.sender][unitId]); // Housing upgrades (allow more units)
}
// Update players goo
updatePlayersGooFromPurchase(msg.sender, gooCost);
if (gooProduction > 0) {
increasePlayersGooProduction(msg.sender, getUnitsProduction(msg.sender, unitId, amount));
}
unitsOwned[msg.sender][unitId] += amount;
emit UnitBought(msg.sender, unitId, amount);
}
function sellUnit(uint256 unitId, uint256 amount) external {
uint256 existing = unitsOwned[msg.sender][unitId];
require(existing >= amount && amount > 0);
existing -= amount;
unitsOwned[msg.sender][unitId] = existing;
uint256 schemaUnitId;
uint256 gooProduction;
uint256 gooCost;
uint256 ethCost;
(schemaUnitId, gooProduction, gooCost, ethCost) = schema.getUnitInfo(unitId, existing, amount);
require(schema.unitSellable(unitId));
uint256 gooChange = balanceOfUnclaimedGoo(msg.sender) + ((gooCost * 3) / 4); // Claim unsaved goo whilst here
lastGooSaveTime[msg.sender] = block.timestamp;
roughSupply += gooChange;
gooBalance[msg.sender] += gooChange;
if (gooProduction > 0) {
reducePlayersGooProduction(msg.sender, getUnitsProduction(msg.sender, unitId, amount));
}
if (ethCost > 0) { // Premium units sell for 75% of buy cost
ethBalance[msg.sender] += (ethCost * 3) / 4;
}
emit UnitSold(msg.sender, unitId, amount);
}
function buyUpgrade(uint256 upgradeId) external payable {
uint256 gooCost;
uint256 ethCost;
uint256 upgradeClass;
uint256 unitId;
uint256 upgradeValue;
uint256 prerequisiteUpgrade;
(gooCost, ethCost, upgradeClass, unitId, upgradeValue, prerequisiteUpgrade) = schema.getUpgradeInfo(upgradeId);
require(gameStarted);
require(unitId > 0); // Valid upgrade
require(!upgradesOwned[msg.sender][upgradeId]); // Haven't already purchased
if (prerequisiteUpgrade > 0) {
require(upgradesOwned[msg.sender][prerequisiteUpgrade]);
}
if (ethCost > 0) {
require(ethBalance[msg.sender] + msg.value >= ethCost);
if (ethCost > msg.value) { // They can use their balance instead
ethBalance[msg.sender] -= (ethCost - msg.value);
}
uint256 devFund = ethCost / 50; // 2% fee on purchases (marketing, gameplay & maintenance)
totalEtherGooResearchPool += (ethCost - devFund); // Rest goes to div pool (Can't sell upgrades)
ethBalance[owner] += devFund;
}
// Update players goo
updatePlayersGooFromPurchase(msg.sender, gooCost);
upgradeUnitMultipliers(msg.sender, upgradeClass, unitId, upgradeValue);
upgradesOwned[msg.sender][upgradeId] = true;
}
function upgradeUnitMultipliers(address player, uint256 upgradeClass, uint256 unitId, uint256 upgradeValue) internal {
uint256 productionGain;
if (upgradeClass == 0) {
unitGooProductionIncreases[player][unitId] += upgradeValue;
productionGain = unitsOwned[player][unitId] * upgradeValue * (10 + unitGooProductionMultiplier[player][unitId]);
increasePlayersGooProduction(player, productionGain);
} else if (upgradeClass == 1) {
unitGooProductionMultiplier[player][unitId] += upgradeValue;
productionGain = unitsOwned[player][unitId] * upgradeValue * (schema.unitGooProduction(unitId) + unitGooProductionIncreases[player][unitId]);
increasePlayersGooProduction(player, productionGain);
} else if (upgradeClass == 2) {
unitAttackIncreases[player][unitId] += upgradeValue;
} else if (upgradeClass == 3) {
unitAttackMultiplier[player][unitId] += upgradeValue;
} else if (upgradeClass == 4) {
unitDefenseIncreases[player][unitId] += upgradeValue;
} else if (upgradeClass == 5) {
unitDefenseMultiplier[player][unitId] += upgradeValue;
} else if (upgradeClass == 6) {
unitGooStealingIncreases[player][unitId] += upgradeValue;
} else if (upgradeClass == 7) {
unitGooStealingMultiplier[player][unitId] += upgradeValue;
} else if (upgradeClass == 8) {
unitMaxCap[player][unitId] = upgradeValue; // Housing upgrade (new capacity)
}
}
function removeUnitMultipliers(address player, uint256 upgradeClass, uint256 unitId, uint256 upgradeValue) internal {
uint256 productionLoss;
if (upgradeClass == 0) {
unitGooProductionIncreases[player][unitId] -= upgradeValue;
productionLoss = unitsOwned[player][unitId] * upgradeValue * (10 + unitGooProductionMultiplier[player][unitId]);
reducePlayersGooProduction(player, productionLoss);
} else if (upgradeClass == 1) {
unitGooProductionMultiplier[player][unitId] -= upgradeValue;
productionLoss = unitsOwned[player][unitId] * upgradeValue * (schema.unitGooProduction(unitId) + unitGooProductionIncreases[player][unitId]);
reducePlayersGooProduction(player, productionLoss);
} else if (upgradeClass == 2) {
unitAttackIncreases[player][unitId] -= upgradeValue;
} else if (upgradeClass == 3) {
unitAttackMultiplier[player][unitId] -= upgradeValue;
} else if (upgradeClass == 4) {
unitDefenseIncreases[player][unitId] -= upgradeValue;
} else if (upgradeClass == 5) {
unitDefenseMultiplier[player][unitId] -= upgradeValue;
} else if (upgradeClass == 6) {
unitGooStealingIncreases[player][unitId] -= upgradeValue;
} else if (upgradeClass == 7) {
unitGooStealingMultiplier[player][unitId] -= upgradeValue;
}
}
function buyRareItem(uint256 rareId) external payable {
uint256 upgradeClass;
uint256 unitId;
uint256 upgradeValue;
(upgradeClass, unitId, upgradeValue) = schema.getRareInfo(rareId);
address previousOwner = rareItemOwner[rareId];
require(previousOwner != 0);
require(unitId > 0);
// We have to claim buyer's goo before updating their production values
updatePlayersGoo(msg.sender);
upgradeUnitMultipliers(msg.sender, upgradeClass, unitId, upgradeValue);
// We have to claim seller's goo before reducing their production values
updatePlayersGoo(previousOwner);
removeUnitMultipliers(previousOwner, upgradeClass, unitId, upgradeValue);
uint256 ethCost = rareItemPrice[rareId];
require(ethBalance[msg.sender] + msg.value >= ethCost);
// Splitbid/Overbid
if (ethCost > msg.value) {
// Earlier require() said they can still afford it (so use their ingame balance)
ethBalance[msg.sender] -= (ethCost - msg.value);
} else if (msg.value > ethCost) {
// Store overbid in their balance
ethBalance[msg.sender] += msg.value - ethCost;
}
// Distribute ethCost
uint256 devFund = ethCost / 50; // 2% fee on purchases (marketing, gameplay & maintenance)
uint256 dividends = ethCost / 20; // 5% goes to pool (~93% goes to player)
totalEtherGooResearchPool += dividends;
ethBalance[owner] += devFund;
// Transfer / update rare item
rareItemOwner[rareId] = msg.sender;
rareItemPrice[rareId] = (ethCost * 5) / 4; // 25% price flip increase
ethBalance[previousOwner] += ethCost - (dividends + devFund);
}
function withdrawEther(uint256 amount) external {
require(amount <= ethBalance[msg.sender]);
ethBalance[msg.sender] -= amount;
msg.sender.transfer(amount);
}
function fundGooResearch(uint256 amount) external {
updatePlayersGooFromPurchase(msg.sender, amount);
gooDepositSnapshots[msg.sender][totalGooDepositSnapshots.length - 1] += amount;
totalGooDepositSnapshots[totalGooDepositSnapshots.length - 1] += amount;
}
function claimResearchDividends(address referer, uint256 startSnapshot, uint256 endSnapShot) external {
require(startSnapshot <= endSnapShot);
require(startSnapshot >= lastGooResearchFundClaim[msg.sender]);
require(endSnapShot < allocatedGooResearchSnapshots.length);
uint256 researchShare;
uint256 previousProduction = gooProductionSnapshots[msg.sender][lastGooResearchFundClaim[msg.sender] - 1]; // Underflow won't be a problem as gooProductionSnapshots[][0xffffffffff] = 0;
for (uint256 i = startSnapshot; i <= endSnapShot; i++) {
// Slightly complex things by accounting for days/snapshots when user made no tx's
uint256 productionDuringSnapshot = gooProductionSnapshots[msg.sender][i];
bool soldAllProduction = gooProductionZeroedSnapshots[msg.sender][i];
if (productionDuringSnapshot == 0 && !soldAllProduction) {
productionDuringSnapshot = previousProduction;
} else {
previousProduction = productionDuringSnapshot;
}
researchShare += (allocatedGooResearchSnapshots[i] * productionDuringSnapshot) / totalGooProductionSnapshots[i];
}
if (gooProductionSnapshots[msg.sender][endSnapShot] == 0 && !gooProductionZeroedSnapshots[msg.sender][endSnapShot] && previousProduction > 0) {
gooProductionSnapshots[msg.sender][endSnapShot] = previousProduction; // Checkpoint for next claim
}
lastGooResearchFundClaim[msg.sender] = endSnapShot + 1;
uint256 referalDivs;
if (referer != address(0) && referer != msg.sender) {
referalDivs = researchShare / 100; // 1%
ethBalance[referer] += referalDivs;
emit ReferalGain(referer, msg.sender, referalDivs);
}
ethBalance[msg.sender] += researchShare - referalDivs;
}
function claimGooDepositDividends(address referer, uint256 startSnapshot, uint256 endSnapShot) external {
require(startSnapshot <= endSnapShot);
require(startSnapshot >= lastGooDepositFundClaim[msg.sender]);
require(endSnapShot < allocatedGooDepositSnapshots.length);
uint256 depositShare;
for (uint256 i = startSnapshot; i <= endSnapShot; i++) {
depositShare += (allocatedGooDepositSnapshots[i] * gooDepositSnapshots[msg.sender][i]) / totalGooDepositSnapshots[i];
}
lastGooDepositFundClaim[msg.sender] = endSnapShot + 1;
uint256 referalDivs;
if (referer != address(0) && referer != msg.sender) {
referalDivs = depositShare / 100; // 1%
ethBalance[referer] += referalDivs;
emit ReferalGain(referer, msg.sender, referalDivs);
}
ethBalance[msg.sender] += depositShare - referalDivs;
}
// Allocate pot #1 divs for the day (00:00 cron job)
function snapshotDailyGooResearchFunding() external {
require(msg.sender == owner);
uint256 todaysGooResearchFund = (totalEtherGooResearchPool * researchDivPercent) / 100; // 8% of pool daily
totalEtherGooResearchPool -= todaysGooResearchFund;
totalGooProductionSnapshots.push(totalGooProduction);
allocatedGooResearchSnapshots.push(todaysGooResearchFund);
nextSnapshotTime = block.timestamp + 24 hours;
}
// Allocate pot #2 divs for the day (12:00 cron job)
function snapshotDailyGooDepositFunding() external {
require(msg.sender == owner);
uint256 todaysGooDepositFund = (totalEtherGooResearchPool * gooDepositDivPercent) / 100; // 2% of pool daily
totalEtherGooResearchPool -= todaysGooDepositFund;
totalGooDepositSnapshots.push(0); // Reset for to store next day's deposits
allocatedGooDepositSnapshots.push(todaysGooDepositFund); // Store to payout divs for previous day deposits
}
// Raffle for rare items
function buyItemRaffleTicket(uint256 amount) external {
require(itemRaffleEndTime >= block.timestamp);
require(amount > 0);
uint256 ticketsCost = SafeMath.mul(RAFFLE_TICKET_BASE_GOO_PRICE, amount);
require(balanceOf(msg.sender) >= ticketsCost);
// Update players goo
updatePlayersGooFromPurchase(msg.sender, ticketsCost);
// Handle new tickets
TicketPurchases storage purchases = rareItemTicketsBoughtByPlayer[msg.sender];
// If we need to reset tickets from a previous raffle
if (purchases.raffleId != itemRaffleRareId) {
purchases.numPurchases = 0;
purchases.raffleId = itemRaffleRareId;
itemRafflePlayers[itemRaffleRareId].push(msg.sender); // Add user to raffle
}
// Store new ticket purchase
if (purchases.numPurchases == purchases.ticketsBought.length) {
purchases.ticketsBought.length += 1;
}
purchases.ticketsBought[purchases.numPurchases++] = TicketPurchase(itemRaffleTicketsBought, itemRaffleTicketsBought + (amount - 1)); // (eg: buy 10, get id's 0-9)
// Finally update ticket total
itemRaffleTicketsBought += amount;
}
// Raffle for rare units
function buyUnitRaffleTicket(uint256 amount) external {
require(unitRaffleEndTime >= block.timestamp);
require(amount > 0);
uint256 ticketsCost = SafeMath.mul(RAFFLE_TICKET_BASE_GOO_PRICE, amount);
require(balanceOf(msg.sender) >= ticketsCost);
// Update players goo
updatePlayersGooFromPurchase(msg.sender, ticketsCost);
// Handle new tickets
TicketPurchases storage purchases = rareUnitTicketsBoughtByPlayer[msg.sender];
// If we need to reset tickets from a previous raffle
if (purchases.raffleId != unitRaffleId) {
purchases.numPurchases = 0;
purchases.raffleId = unitRaffleId;
unitRafflePlayers[unitRaffleId].push(msg.sender); // Add user to raffle
}
// Store new ticket purchase
if (purchases.numPurchases == purchases.ticketsBought.length) {
purchases.ticketsBought.length += 1;
}
purchases.ticketsBought[purchases.numPurchases++] = TicketPurchase(unitRaffleTicketsBought, unitRaffleTicketsBought + (amount - 1)); // (eg: buy 10, get id's 0-9)
// Finally update ticket total
unitRaffleTicketsBought += amount;
}
function startItemRaffle(uint256 endTime, uint256 rareId) external {
require(msg.sender == owner);
require(schema.validRareId(rareId));
require(rareItemOwner[rareId] == 0);
require(block.timestamp < endTime);
if (itemRaffleRareId != 0) { // Sanity to assure raffle has ended before next one starts
require(itemRaffleWinner != 0);
}
// Reset previous raffle info
itemRaffleWinningTicketSelected = false;
itemRaffleTicketThatWon = 0;
itemRaffleWinner = 0;
itemRaffleTicketsBought = 0;
// Set current raffle info
itemRaffleEndTime = endTime;
itemRaffleRareId = rareId;
}
function startUnitRaffle(uint256 endTime, uint256 unitId) external {
require(msg.sender == owner);
require(block.timestamp < endTime);
if (unitRaffleRareId != 0) { // Sanity to assure raffle has ended before next one starts
require(unitRaffleWinner != 0);
}
// Reset previous raffle info
unitRaffleWinningTicketSelected = false;
unitRaffleTicketThatWon = 0;
unitRaffleWinner = 0;
unitRaffleTicketsBought = 0;
// Set current raffle info
unitRaffleEndTime = endTime;
unitRaffleRareId = unitId;
unitRaffleId++; // Can't use unitRaffleRareId (as rare units are not unique)
}
function awardItemRafflePrize(address checkWinner, uint256 checkIndex) external {
require(itemRaffleEndTime < block.timestamp);
require(itemRaffleWinner == 0);
require(rareItemOwner[itemRaffleRareId] == 0);
if (!itemRaffleWinningTicketSelected) {
drawRandomItemWinner(); // Ideally do it in one call (gas limit cautious)
}
// Reduce gas by (optionally) offering an address to _check_ for winner
if (checkWinner != 0) {
TicketPurchases storage tickets = rareItemTicketsBoughtByPlayer[checkWinner];
if (tickets.numPurchases > 0 && checkIndex < tickets.numPurchases && tickets.raffleId == itemRaffleRareId) {
TicketPurchase storage checkTicket = tickets.ticketsBought[checkIndex];
if (itemRaffleTicketThatWon >= checkTicket.startId && itemRaffleTicketThatWon <= checkTicket.endId) {
assignItemRafflePrize(checkWinner); // WINNER!
return;
}
}
}
// Otherwise just naively try to find the winner (will work until mass amounts of players)
for (uint256 i = 0; i < itemRafflePlayers[itemRaffleRareId].length; i++) {
address player = itemRafflePlayers[itemRaffleRareId][i];
TicketPurchases storage playersTickets = rareItemTicketsBoughtByPlayer[player];
uint256 endIndex = playersTickets.numPurchases - 1;
// Minor optimization to avoid checking every single player
if (itemRaffleTicketThatWon >= playersTickets.ticketsBought[0].startId && itemRaffleTicketThatWon <= playersTickets.ticketsBought[endIndex].endId) {
for (uint256 j = 0; j < playersTickets.numPurchases; j++) {
TicketPurchase storage playerTicket = playersTickets.ticketsBought[j];
if (itemRaffleTicketThatWon >= playerTicket.startId && itemRaffleTicketThatWon <= playerTicket.endId) {
assignItemRafflePrize(player); // WINNER!
return;
}
}
}
}
}
function awardUnitRafflePrize(address checkWinner, uint256 checkIndex) external {
require(unitRaffleEndTime < block.timestamp);
require(unitRaffleWinner == 0);
if (!unitRaffleWinningTicketSelected) {
drawRandomUnitWinner(); // Ideally do it in one call (gas limit cautious)
}
// Reduce gas by (optionally) offering an address to _check_ for winner
if (checkWinner != 0) {
TicketPurchases storage tickets = rareUnitTicketsBoughtByPlayer[checkWinner];
if (tickets.numPurchases > 0 && checkIndex < tickets.numPurchases && tickets.raffleId == unitRaffleId) {
TicketPurchase storage checkTicket = tickets.ticketsBought[checkIndex];
if (unitRaffleTicketThatWon >= checkTicket.startId && unitRaffleTicketThatWon <= checkTicket.endId) {
assignUnitRafflePrize(checkWinner); // WINNER!
return;
}
}
}
// Otherwise just naively try to find the winner (will work until mass amounts of players)
for (uint256 i = 0; i < unitRafflePlayers[unitRaffleId].length; i++) {
address player = unitRafflePlayers[unitRaffleId][i];
TicketPurchases storage playersTickets = rareUnitTicketsBoughtByPlayer[player];
uint256 endIndex = playersTickets.numPurchases - 1;
// Minor optimization to avoid checking every single player
if (unitRaffleTicketThatWon >= playersTickets.ticketsBought[0].startId && unitRaffleTicketThatWon <= playersTickets.ticketsBought[endIndex].endId) {
for (uint256 j = 0; j < playersTickets.numPurchases; j++) {
TicketPurchase storage playerTicket = playersTickets.ticketsBought[j];
if (unitRaffleTicketThatWon >= playerTicket.startId && unitRaffleTicketThatWon <= playerTicket.endId) {
assignUnitRafflePrize(player); // WINNER!
return;
}
}
}
}
}
function assignItemRafflePrize(address winner) internal {
itemRaffleWinner = winner;
rareItemOwner[itemRaffleRareId] = winner;
rareItemPrice[itemRaffleRareId] = (schema.rareStartPrice(itemRaffleRareId) * 21) / 20; // Buy price slightly higher (Div pool cut)
updatePlayersGoo(winner);
uint256 upgradeClass;
uint256 unitId;
uint256 upgradeValue;
(upgradeClass, unitId, upgradeValue) = schema.getRareInfo(itemRaffleRareId);
upgradeUnitMultipliers(winner, upgradeClass, unitId, upgradeValue);
}
function assignUnitRafflePrize(address winner) internal {
unitRaffleWinner = winner;
updatePlayersGoo(winner);
increasePlayersGooProduction(winner, getUnitsProduction(winner, unitRaffleRareId, 1));
unitsOwned[winner][unitRaffleRareId] += 1;
}
// Random enough for small contests (Owner only to prevent trial & error execution)
function drawRandomItemWinner() public {
require(msg.sender == owner);
require(itemRaffleEndTime < block.timestamp);
require(!itemRaffleWinningTicketSelected);
uint256 seed = itemRaffleTicketsBought + block.timestamp;
itemRaffleTicketThatWon = addmod(uint256(block.blockhash(block.number-1)), seed, itemRaffleTicketsBought);
itemRaffleWinningTicketSelected = true;
}
function drawRandomUnitWinner() public {
require(msg.sender == owner);
require(unitRaffleEndTime < block.timestamp);
require(!unitRaffleWinningTicketSelected);
uint256 seed = unitRaffleTicketsBought + block.timestamp;
unitRaffleTicketThatWon = addmod(uint256(block.blockhash(block.number-1)), seed, unitRaffleTicketsBought);
unitRaffleWinningTicketSelected = true;
}
// Gives players the upgrades they 'previously paid for' (i.e. will be one of same unit/type/value of their v1 purchase)
// Tx of their (prior) purchase is provided so can be validated by anyone for 0 abuse
function migrateV1Upgrades(address[] playerToCredit, uint256[] upgradeIds, uint256[] txProof) external {
require(msg.sender == owner);
require(!gameStarted); // Pre-game migration
for (uint256 i = 0; i < txProof.length; i++) {
address player = playerToCredit[i];
uint256 upgradeId = upgradeIds[i];
uint256 unitId = schema.upgradeUnitId(upgradeId);
if (unitId > 0 && !upgradesOwned[player][upgradeId]) { // Upgrade valid (and haven't already migrated)
uint256 upgradeClass = schema.upgradeClass(upgradeId);
uint256 upgradeValue = schema.upgradeValue(upgradeId);
upgradeUnitMultipliers(player, upgradeClass, unitId, upgradeValue);
upgradesOwned[player][upgradeId] = true;
emit UpgradeMigration(player, upgradeId, txProof[i]);
}
}
}
function protectAddress(address exchange, bool shouldProtect) external {
require(msg.sender == owner);
if (shouldProtect) {
require(getGooProduction(exchange) == 0); // Can't protect actual players
}
protectedAddresses[exchange] = shouldProtect;
}
function attackPlayer(address target) external {
require(battleCooldown[msg.sender] < block.timestamp);
require(target != msg.sender);
require(!protectedAddresses[target]); // Target not whitelisted (i.e. exchange wallets)
uint256 attackingPower;
uint256 defendingPower;
uint256 stealingPower;
(attackingPower, defendingPower, stealingPower) = getPlayersBattlePower(msg.sender, target);
if (battleCooldown[target] > block.timestamp) { // When on battle cooldown you're vulnerable (starting value is 50% normal power)
defendingPower = schema.getWeakenedDefensePower(defendingPower);
}
if (attackingPower > defendingPower) {
battleCooldown[msg.sender] = block.timestamp + 30 minutes;
if (balanceOf(target) > stealingPower) {
// Save all their unclaimed goo, then steal attacker's max capacity (at same time)
uint256 unclaimedGoo = balanceOfUnclaimedGoo(target);
if (stealingPower > unclaimedGoo) {
uint256 gooDecrease = stealingPower - unclaimedGoo;
gooBalance[target] -= gooDecrease;
roughSupply -= gooDecrease;
} else {
uint256 gooGain = unclaimedGoo - stealingPower;
gooBalance[target] += gooGain;
roughSupply += gooGain;
}
gooBalance[msg.sender] += stealingPower;
emit PlayerAttacked(msg.sender, target, true, stealingPower);
} else {
emit PlayerAttacked(msg.sender, target, true, balanceOf(target));
gooBalance[msg.sender] += balanceOf(target);
gooBalance[target] = 0;
}
lastGooSaveTime[target] = block.timestamp;
// We don't need to claim/save msg.sender's goo (as production delta is unchanged)
} else {
battleCooldown[msg.sender] = block.timestamp + 10 minutes;
emit PlayerAttacked(msg.sender, target, false, 0);
}
}
function getPlayersBattlePower(address attacker, address defender) internal constant returns (uint256, uint256, uint256) {
uint256 startId;
uint256 endId;
(startId, endId) = schema.battleUnitIdRange();
uint256 attackingPower;
uint256 defendingPower;
uint256 stealingPower;
// Not ideal but will only be a small number of units (and saves gas when buying units)
while (startId <= endId) {
attackingPower += getUnitsAttack(attacker, startId, unitsOwned[attacker][startId]);
stealingPower += getUnitsStealingCapacity(attacker, startId, unitsOwned[attacker][startId]);
defendingPower += getUnitsDefense(defender, startId, unitsOwned[defender][startId]);
startId++;
}
return (attackingPower, defendingPower, stealingPower);
}
function getPlayersBattleStats(address player) external constant returns (uint256, uint256, uint256, uint256) {
uint256 startId;
uint256 endId;
(startId, endId) = schema.battleUnitIdRange();
uint256 attackingPower;
uint256 defendingPower;
uint256 stealingPower;
// Not ideal but will only be a small number of units (and saves gas when buying units)
while (startId <= endId) {
attackingPower += getUnitsAttack(player, startId, unitsOwned[player][startId]);
stealingPower += getUnitsStealingCapacity(player, startId, unitsOwned[player][startId]);
defendingPower += getUnitsDefense(player, startId, unitsOwned[player][startId]);
startId++;
}
if (battleCooldown[player] > block.timestamp) { // When on battle cooldown you're vulnerable (starting value is 50% normal power)
defendingPower = schema.getWeakenedDefensePower(defendingPower);
}
return (attackingPower, defendingPower, stealingPower, battleCooldown[player]);
}
function getUnitsProduction(address player, uint256 unitId, uint256 amount) internal constant returns (uint256) {
return (amount * (schema.unitGooProduction(unitId) + unitGooProductionIncreases[player][unitId]) * (10 + unitGooProductionMultiplier[player][unitId]));
}
function getUnitsAttack(address player, uint256 unitId, uint256 amount) internal constant returns (uint256) {
return (amount * (schema.unitAttack(unitId) + unitAttackIncreases[player][unitId]) * (10 + unitAttackMultiplier[player][unitId])) / 10;
}
function getUnitsDefense(address player, uint256 unitId, uint256 amount) internal constant returns (uint256) {
return (amount * (schema.unitDefense(unitId) + unitDefenseIncreases[player][unitId]) * (10 + unitDefenseMultiplier[player][unitId])) / 10;
}
function getUnitsStealingCapacity(address player, uint256 unitId, uint256 amount) internal constant returns (uint256) {
return (amount * (schema.unitStealingCapacity(unitId) + unitGooStealingIncreases[player][unitId]) * (10 + unitGooStealingMultiplier[player][unitId])) / 10;
}
// To display on website
function getGameInfo() external constant returns (uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256[], bool[]){
uint256[] memory units = new uint256[](schema.currentNumberOfUnits());
bool[] memory upgrades = new bool[](schema.currentNumberOfUpgrades());
uint256 startId;
uint256 endId;
(startId, endId) = schema.productionUnitIdRange();
uint256 i;
while (startId <= endId) {
units[i] = unitsOwned[msg.sender][startId];
i++;
startId++;
}
(startId, endId) = schema.battleUnitIdRange();
while (startId <= endId) {
units[i] = unitsOwned[msg.sender][startId];
i++;
startId++;
}
// Reset for upgrades
i = 0;
(startId, endId) = schema.upgradeIdRange();
while (startId <= endId) {
upgrades[i] = upgradesOwned[msg.sender][startId];
i++;
startId++;
}
return (block.timestamp, totalEtherGooResearchPool, totalGooProduction, totalGooDepositSnapshots[totalGooDepositSnapshots.length - 1], gooDepositSnapshots[msg.sender][totalGooDepositSnapshots.length - 1],
nextSnapshotTime, balanceOf(msg.sender), ethBalance[msg.sender], getGooProduction(msg.sender), units, upgrades);
}
// To display on website
function getRareItemInfo() external constant returns (address[], uint256[]) {
address[] memory itemOwners = new address[](schema.currentNumberOfRares());
uint256[] memory itemPrices = new uint256[](schema.currentNumberOfRares());
uint256 startId;
uint256 endId;
(startId, endId) = schema.rareIdRange();
uint256 i;
while (startId <= endId) {
itemOwners[i] = rareItemOwner[startId];
itemPrices[i] = rareItemPrice[startId];
i++;
startId++;
}
return (itemOwners, itemPrices);
}
// To display on website
function viewUnclaimedResearchDividends() external constant returns (uint256, uint256, uint256) {
uint256 startSnapshot = lastGooResearchFundClaim[msg.sender];
uint256 latestSnapshot = allocatedGooResearchSnapshots.length - 1; // No snapshots to begin with
uint256 researchShare;
uint256 previousProduction = gooProductionSnapshots[msg.sender][lastGooResearchFundClaim[msg.sender] - 1]; // Underflow won't be a problem as gooProductionSnapshots[][0xfffffffffffff] = 0;
for (uint256 i = startSnapshot; i <= latestSnapshot; i++) {
// Slightly complex things by accounting for days/snapshots when user made no tx's
uint256 productionDuringSnapshot = gooProductionSnapshots[msg.sender][i];
bool soldAllProduction = gooProductionZeroedSnapshots[msg.sender][i];
if (productionDuringSnapshot == 0 && !soldAllProduction) {
productionDuringSnapshot = previousProduction;
} else {
previousProduction = productionDuringSnapshot;
}
researchShare += (allocatedGooResearchSnapshots[i] * productionDuringSnapshot) / totalGooProductionSnapshots[i];
}
return (researchShare, startSnapshot, latestSnapshot);
}
// To display on website
function viewUnclaimedDepositDividends() external constant returns (uint256, uint256, uint256) {
uint256 startSnapshot = lastGooDepositFundClaim[msg.sender];
uint256 latestSnapshot = allocatedGooDepositSnapshots.length - 1; // No snapshots to begin with
uint256 depositShare;
for (uint256 i = startSnapshot; i <= latestSnapshot; i++) {
depositShare += (allocatedGooDepositSnapshots[i] * gooDepositSnapshots[msg.sender][i]) / totalGooDepositSnapshots[i];
}
return (depositShare, startSnapshot, latestSnapshot);
}
// To allow clients to verify contestants
function getItemRafflePlayers(uint256 raffleId) external constant returns (address[]) {
return (itemRafflePlayers[raffleId]);
}
// To allow clients to verify contestants
function getUnitRafflePlayers(uint256 raffleId) external constant returns (address[]) {
return (unitRafflePlayers[raffleId]);
}
// To allow clients to verify contestants
function getPlayersItemTickets(address player) external constant returns (uint256[], uint256[]) {
TicketPurchases storage playersTickets = rareItemTicketsBoughtByPlayer[player];
if (playersTickets.raffleId == itemRaffleRareId) {
uint256[] memory startIds = new uint256[](playersTickets.numPurchases);
uint256[] memory endIds = new uint256[](playersTickets.numPurchases);
for (uint256 i = 0; i < playersTickets.numPurchases; i++) {
startIds[i] = playersTickets.ticketsBought[i].startId;
endIds[i] = playersTickets.ticketsBought[i].endId;
}
}
return (startIds, endIds);
}
// To allow clients to verify contestants
function getPlayersUnitTickets(address player) external constant returns (uint256[], uint256[]) {
TicketPurchases storage playersTickets = rareUnitTicketsBoughtByPlayer[player];
if (playersTickets.raffleId == unitRaffleId) {
uint256[] memory startIds = new uint256[](playersTickets.numPurchases);
uint256[] memory endIds = new uint256[](playersTickets.numPurchases);
for (uint256 i = 0; i < playersTickets.numPurchases; i++) {
startIds[i] = playersTickets.ticketsBought[i].startId;
endIds[i] = playersTickets.ticketsBought[i].endId;
}
}
return (startIds, endIds);
}
// To display on website
function getLatestItemRaffleInfo() external constant returns (uint256, uint256, uint256, address, uint256) {
return (itemRaffleEndTime, itemRaffleRareId, itemRaffleTicketsBought, itemRaffleWinner, itemRaffleTicketThatWon);
}
// To display on website
function getLatestUnitRaffleInfo() external constant returns (uint256, uint256, uint256, address, uint256) {
return (unitRaffleEndTime, unitRaffleRareId, unitRaffleTicketsBought, unitRaffleWinner, unitRaffleTicketThatWon);
}
// New units may be added in future, but check it matches existing schema so no-one can abuse selling.
function updateGooConfig(address newSchemaAddress) external {
require(msg.sender == owner);
GooGameConfig newSchema = GooGameConfig(newSchemaAddress);
requireExistingUnitsSame(newSchema);
requireExistingUpgradesSame(newSchema);
// Finally update config
schema = GooGameConfig(newSchema);
}
function requireExistingUnitsSame(GooGameConfig newSchema) internal constant {
// Requires units eth costs match up or fail execution
uint256 startId;
uint256 endId;
(startId, endId) = schema.productionUnitIdRange();
while (startId <= endId) {
require(schema.unitEthCost(startId) == newSchema.unitEthCost(startId));
require(schema.unitGooProduction(startId) == newSchema.unitGooProduction(startId));
startId++;
}
(startId, endId) = schema.battleUnitIdRange();
while (startId <= endId) {
require(schema.unitEthCost(startId) == newSchema.unitEthCost(startId));
require(schema.unitAttack(startId) == newSchema.unitAttack(startId));
require(schema.unitDefense(startId) == newSchema.unitDefense(startId));
require(schema.unitStealingCapacity(startId) == newSchema.unitStealingCapacity(startId));
startId++;
}
}
function requireExistingUpgradesSame(GooGameConfig newSchema) internal constant {
uint256 startId;
uint256 endId;
// Requires ALL upgrade stats match up or fail execution
(startId, endId) = schema.upgradeIdRange();
while (startId <= endId) {
require(schema.upgradeGooCost(startId) == newSchema.upgradeGooCost(startId));
require(schema.upgradeEthCost(startId) == newSchema.upgradeEthCost(startId));
require(schema.upgradeClass(startId) == newSchema.upgradeClass(startId));
require(schema.upgradeUnitId(startId) == newSchema.upgradeUnitId(startId));
require(schema.upgradeValue(startId) == newSchema.upgradeValue(startId));
startId++;
}
// Requires ALL rare stats match up or fail execution
(startId, endId) = schema.rareIdRange();
while (startId <= endId) {
uint256 oldClass;
uint256 oldUnitId;
uint256 oldValue;
uint256 newClass;
uint256 newUnitId;
uint256 newValue;
(oldClass, oldUnitId, oldValue) = schema.getRareInfo(startId);
(newClass, newUnitId, newValue) = newSchema.getRareInfo(startId);
require(oldClass == newClass);
require(oldUnitId == newUnitId);
require(oldValue == newValue);
startId++;
}
}
}
contract GooGameConfig {
mapping(uint256 => Unit) private unitInfo;
mapping(uint256 => Upgrade) private upgradeInfo;
mapping(uint256 => Rare) private rareInfo;
uint256 public constant currentNumberOfUnits = 15;
uint256 public constant currentNumberOfUpgrades = 210;
uint256 public constant currentNumberOfRares = 2;
address public owner;
struct Unit {
uint256 unitId;
uint256 baseGooCost;
uint256 gooCostIncreaseHalf; // Halfed to make maths slightly less (cancels a 2 out)
uint256 ethCost;
uint256 baseGooProduction;
uint256 attackValue;
uint256 defenseValue;
uint256 gooStealingCapacity;
bool unitSellable; // Rare units (from raffle) not sellable
}
struct Upgrade {
uint256 upgradeId;
uint256 gooCost;
uint256 ethCost;
uint256 upgradeClass;
uint256 unitId;
uint256 upgradeValue;
uint256 prerequisiteUpgrade;
}
struct Rare {
uint256 rareId;
uint256 ethCost;
uint256 rareClass;
uint256 unitId;
uint256 rareValue;
}
function GooGameConfig() public {
owner = msg.sender;
rareInfo[1] = Rare(1, 0.5 ether, 1, 1, 40); // 40 = +400%
rareInfo[2] = Rare(2, 0.5 ether, 0, 2, 35); // +35
unitInfo[1] = Unit(1, 0, 10, 0, 2, 0, 0, 0, true);
unitInfo[2] = Unit(2, 100, 50, 0, 5, 0, 0, 0, true);
unitInfo[3] = Unit(3, 0, 0, 0.01 ether, 100, 0, 0, 0, true);
unitInfo[4] = Unit(4, 200, 100, 0, 10, 0, 0, 0, true);
unitInfo[5] = Unit(5, 500, 250, 0, 20, 0, 0, 0, true);
unitInfo[6] = Unit(6, 1000, 500, 0, 40, 0, 0, 0, true);
unitInfo[7] = Unit(7, 0, 1000, 0.05 ether, 500, 0, 0, 0, true);
unitInfo[8] = Unit(8, 1500, 750, 0, 60, 0, 0, 0, true);
unitInfo[9] = Unit(9, 0, 0, 10 ether, 6000, 0, 0, 0, false); // First secret rare unit from raffle (unsellable)
unitInfo[40] = Unit(40, 50, 25, 0, 0, 10, 10, 10000, true);
unitInfo[41] = Unit(41, 100, 50, 0, 0, 1, 25, 500, true);
unitInfo[42] = Unit(42, 0, 0, 0.01 ether, 0, 200, 10, 50000, true);
unitInfo[43] = Unit(43, 250, 125, 0, 0, 25, 1, 15000, true);
unitInfo[44] = Unit(44, 500, 250, 0, 0, 20, 40, 5000, true);
unitInfo[45] = Unit(45, 0, 2500, 0.02 ether, 0, 0, 0, 100000, true);
}
address allowedConfig;
function setConfigSetupContract(address schema) external {
require(msg.sender == owner);
allowedConfig = schema;
}
function addUpgrade(uint256 id, uint256 goo, uint256 eth, uint256 class, uint256 unit, uint256 value, uint256 prereq) external {
require(msg.sender == allowedConfig);
upgradeInfo[id] = Upgrade(id, goo, eth, class, unit, value, prereq);
}
function getGooCostForUnit(uint256 unitId, uint256 existing, uint256 amount) public constant returns (uint256) {
Unit storage unit = unitInfo[unitId];
if (amount == 1) { // 1
if (existing == 0) {
return unit.baseGooCost;
} else {
return unit.baseGooCost + (existing * unit.gooCostIncreaseHalf * 2);
}
} else if (amount > 1) {
uint256 existingCost;
if (existing > 0) { // Gated by unit limit
existingCost = (unit.baseGooCost * existing) + (existing * (existing - 1) * unit.gooCostIncreaseHalf);
}
existing = SafeMath.add(existing, amount);
return SafeMath.add(SafeMath.mul(unit.baseGooCost, existing), SafeMath.mul(SafeMath.mul(existing, (existing - 1)), unit.gooCostIncreaseHalf)) - existingCost;
}
}
function getWeakenedDefensePower(uint256 defendingPower) external constant returns (uint256) {
return defendingPower / 2;
}
function validRareId(uint256 rareId) external constant returns (bool) {
return (rareId > 0 && rareId < 3);
}
function unitSellable(uint256 unitId) external constant returns (bool) {
return unitInfo[unitId].unitSellable;
}
function unitEthCost(uint256 unitId) external constant returns (uint256) {
return unitInfo[unitId].ethCost;
}
function unitGooProduction(uint256 unitId) external constant returns (uint256) {
return unitInfo[unitId].baseGooProduction;
}
function unitAttack(uint256 unitId) external constant returns (uint256) {
return unitInfo[unitId].attackValue;
}
function unitDefense(uint256 unitId) external constant returns (uint256) {
return unitInfo[unitId].defenseValue;
}
function unitStealingCapacity(uint256 unitId) external constant returns (uint256) {
return unitInfo[unitId].gooStealingCapacity;
}
function rareStartPrice(uint256 rareId) external constant returns (uint256) {
return rareInfo[rareId].ethCost;
}
function upgradeGooCost(uint256 upgradeId) external constant returns (uint256) {
return upgradeInfo[upgradeId].gooCost;
}
function upgradeEthCost(uint256 upgradeId) external constant returns (uint256) {
return upgradeInfo[upgradeId].ethCost;
}
function upgradeClass(uint256 upgradeId) external constant returns (uint256) {
return upgradeInfo[upgradeId].upgradeClass;
}
function upgradeUnitId(uint256 upgradeId) external constant returns (uint256) {
return upgradeInfo[upgradeId].unitId;
}
function upgradeValue(uint256 upgradeId) external constant returns (uint256) {
return upgradeInfo[upgradeId].upgradeValue;
}
function productionUnitIdRange() external constant returns (uint256, uint256) {
return (1, 9);
}
function battleUnitIdRange() external constant returns (uint256, uint256) {
return (40, 45);
}
function upgradeIdRange() external constant returns (uint256, uint256) {
return (1, 210);
}
function rareIdRange() external constant returns (uint256, uint256) {
return (1, 2);
}
function getUpgradeInfo(uint256 upgradeId) external constant returns (uint256, uint256, uint256, uint256, uint256, uint256) {
return (upgradeInfo[upgradeId].gooCost, upgradeInfo[upgradeId].ethCost, upgradeInfo[upgradeId].upgradeClass,
upgradeInfo[upgradeId].unitId, upgradeInfo[upgradeId].upgradeValue, upgradeInfo[upgradeId].prerequisiteUpgrade);
}
function getRareInfo(uint256 rareId) external constant returns (uint256, uint256, uint256) {
return (rareInfo[rareId].rareClass, rareInfo[rareId].unitId, rareInfo[rareId].rareValue);
}
function getUnitInfo(uint256 unitId, uint256 existing, uint256 amount) external constant returns (uint256, uint256, uint256, uint256) {
return (unitInfo[unitId].unitId, unitInfo[unitId].baseGooProduction, getGooCostForUnit(unitId, existing, amount), SafeMath.mul(unitInfo[unitId].ethCost, amount));
}
}
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;
}
}