ETH Price: $1,999.31 (+0.51%)

Transaction Decoder

Block:
4984477 at Jan-27-2018 11:00:12 PM +UTC
Transaction Fee:
0.000177604 ETH $0.36
Gas Used:
177,604 Gas / 1 Gwei

Account State Difference:

  Address   Before After State Difference Code
294.032234891102309655 Eth294.032412495102309655 Eth0.000177604
0x69aAE7a2...9d16360B3
0.058804287777 Eth
Nonce: 582
0.057626683777 Eth
Nonce: 583
0.001177604
0xABC1c404...81D18Eb3E
(Etheremon: Data)
0xcaEF67F7...5b7dE8De5
(Etheremon: Gym)
4.549 Eth4.55 Eth0.001

Execution Trace

ETH 0.001 EtheremonGym.startTraining( _objId=1556, _trainerLevel=4, _t1=2, _t2=4, _t3=6 )
startTraining[EtheremonGym (ln:598)]
File 1 of 3: EtheremonGym
pragma solidity ^0.4.16;

// copyright contact@Etheremon.com

contract SafeMath {

    /* function assert(bool assertion) internal { */
    /*   if (!assertion) { */
    /*     throw; */
    /*   } */
    /* }      // assert no longer needed once solidity is on 0.4.10 */

    function safeAdd(uint256 x, uint256 y) pure internal returns(uint256) {
      uint256 z = x + y;
      assert((z >= x) && (z >= y));
      return z;
    }

    function safeSubtract(uint256 x, uint256 y) pure internal returns(uint256) {
      assert(x >= y);
      uint256 z = x - y;
      return z;
    }

    function safeMult(uint256 x, uint256 y) pure internal returns(uint256) {
      uint256 z = x * y;
      assert((x == 0)||(z/x == y));
      return z;
    }

}

contract BasicAccessControl {
    address public owner;
    // address[] public moderators;
    uint16 public totalModerators = 0;
    mapping (address => bool) public moderators;
    bool public isMaintaining = true;

    function BasicAccessControl() public {
        owner = msg.sender;
    }

    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }

    modifier onlyModerators() {
        require(msg.sender == owner || moderators[msg.sender] == true);
        _;
    }

    modifier isActive {
        require(!isMaintaining);
        _;
    }

    function ChangeOwner(address _newOwner) onlyOwner public {
        if (_newOwner != address(0)) {
            owner = _newOwner;
        }
    }


    function AddModerator(address _newModerator) onlyOwner public {
        if (moderators[_newModerator] == false) {
            moderators[_newModerator] = true;
            totalModerators += 1;
        }
    }
    
    function RemoveModerator(address _oldModerator) onlyOwner public {
        if (moderators[_oldModerator] == true) {
            moderators[_oldModerator] = false;
            totalModerators -= 1;
        }
    }

    function UpdateMaintaining(bool _isMaintaining) onlyOwner public {
        isMaintaining = _isMaintaining;
    }
}

contract EtheremonEnum {

    enum ResultCode {
        SUCCESS,
        ERROR_CLASS_NOT_FOUND,
        ERROR_LOW_BALANCE,
        ERROR_SEND_FAIL,
        ERROR_NOT_TRAINER,
        ERROR_NOT_ENOUGH_MONEY,
        ERROR_INVALID_AMOUNT,
        ERROR_OBJ_NOT_FOUND,
        ERROR_OBJ_INVALID_OWNERSHIP
    }
    
    enum ArrayType {
        CLASS_TYPE,
        STAT_STEP,
        STAT_START,
        STAT_BASE,
        OBJ_SKILL
    }

    enum PropertyType {
        ANCESTOR,
        XFACTOR
    }
    
    enum BattleResult {
        CASTLE_WIN,
        CASTLE_LOSE,
        CASTLE_DESTROYED
    }
    
    enum CacheClassInfoType {
        CLASS_TYPE,
        CLASS_STEP,
        CLASS_ANCESTOR
    }
}

contract EtheremonDataBase is EtheremonEnum, BasicAccessControl, SafeMath {
    
    uint64 public totalMonster;
    uint32 public totalClass;
    
    // read
    function getSizeArrayType(ArrayType _type, uint64 _id) constant public returns(uint);
    function getElementInArrayType(ArrayType _type, uint64 _id, uint _index) constant public returns(uint8);
    function getMonsterClass(uint32 _classId) constant public returns(uint32 classId, uint256 price, uint256 returnPrice, uint32 total, bool catchable);
    function getMonsterObj(uint64 _objId) constant public returns(uint64 objId, uint32 classId, address trainer, uint32 exp, uint32 createIndex, uint32 lastClaimIndex, uint createTime);
    function getMonsterName(uint64 _objId) constant public returns(string name);
    function getExtraBalance(address _trainer) constant public returns(uint256);
    function getMonsterDexSize(address _trainer) constant public returns(uint);
    function getMonsterObjId(address _trainer, uint index) constant public returns(uint64);
    function getExpectedBalance(address _trainer) constant public returns(uint256);
    function getMonsterReturn(uint64 _objId) constant public returns(uint256 current, uint256 total);
}

contract EtheremonGateway is EtheremonEnum, BasicAccessControl {
    // using for battle contract later
    function increaseMonsterExp(uint64 _objId, uint32 amount) onlyModerators public;
    function decreaseMonsterExp(uint64 _objId, uint32 amount) onlyModerators public;
    
    // read 
    function isGason(uint64 _objId) constant external returns(bool);
    function getObjBattleInfo(uint64 _objId) constant external returns(uint32 classId, uint32 exp, bool isGason, 
        uint ancestorLength, uint xfactorsLength);
    function getClassPropertySize(uint32 _classId, PropertyType _type) constant external returns(uint);
    function getClassPropertyValue(uint32 _classId, PropertyType _type, uint index) constant external returns(uint32);
}

contract EtheremonGym is EtheremonEnum, BasicAccessControl, SafeMath {
    uint8 constant public STAT_COUNT = 6;
    
    struct MonsterObjAcc {
        uint64 monsterId;
        uint32 classId;
        address trainer;
        string name;
        uint32 exp;
        uint32 createIndex;
        uint32 lastClaimIndex;
        uint createTime;
    }
    
    struct AttackData {
        uint32 objClassId;
        address trainee;
        uint8 objLevel;
        uint8 winCount;
        uint32 winExp;
        uint32 loseExp;
    }
    
    struct HpData {
        uint16 aHpAttack;
        uint16 aHpAttackCritical;
        uint16 bHpAttack;
        uint16 bHpAttackCritical;        
    }
    
    struct GymTrainer {
        uint32 classId;
        uint8[6] statBases;
    }
    
    struct TrainingLog {
        uint8[3] trainers;
        uint8 trainerLevel;
        uint64 objId;
        uint8 objLevel;
        uint8 ran;
    }
    
    struct CacheClassInfo {
        uint8[] types;
        uint8[] steps;
        uint32[] ancestors;
    }
    
    mapping(uint8 => GymTrainer) public gymTrainers;
    mapping(address => TrainingLog) public trainees;
    mapping(uint8 => uint8) typeAdvantages;
    mapping(uint32 => CacheClassInfo) cacheClasses;
    mapping(uint8 => uint32) levelExps;
    mapping(uint8 => uint32) levelExpGains;
    uint256 public gymFee = 0.001 ether;
    uint8 public maxTrainerLevel = 5;
    uint8 public totalTrainer = 0;
    uint8 public maxRandomRound = 4;
    uint8 public typeBuffPercentage = 20;
    uint8 public minHpDeducted = 10;
    uint8 public expPercentage = 70;
    
    // contract
    address public worldContract;
    address public dataContract;

   // modifier
    modifier requireDataContract {
        require(dataContract != address(0));
        _;
    }
    
    modifier requireWorldContract {
        require(worldContract != address(0));
        _;
    }
    
    // constructor
    function EtheremonGym(address _dataContract, address _worldContract) public {
        dataContract = _dataContract;
        worldContract = _worldContract;
    }
    
    
     // admin & moderators
    function setTypeAdvantages() onlyModerators external {
        typeAdvantages[1] = 14;
        typeAdvantages[2] = 16;
        typeAdvantages[3] = 8;
        typeAdvantages[4] = 9;
        typeAdvantages[5] = 2;
        typeAdvantages[6] = 11;
        typeAdvantages[7] = 3;
        typeAdvantages[8] = 5;
        typeAdvantages[9] = 15;
        typeAdvantages[11] = 18;
        // skipp 10
        typeAdvantages[12] = 7;
        typeAdvantages[13] = 6;
        typeAdvantages[14] = 17;
        typeAdvantages[15] = 13;
        typeAdvantages[16] = 12;
        typeAdvantages[17] = 1;
        typeAdvantages[18] = 4;
    }
    
    function setTypeAdvantage(uint8 _type1, uint8 _type2) onlyModerators external {
        typeAdvantages[_type1] = _type2;
    }
    
    function setCacheClassInfo(uint32 _classId) onlyModerators requireDataContract requireWorldContract public {
        EtheremonDataBase data = EtheremonDataBase(dataContract);
         EtheremonGateway gateway = EtheremonGateway(worldContract);
        uint i = 0;
        CacheClassInfo storage classInfo = cacheClasses[_classId];

        // add type
        i = data.getSizeArrayType(ArrayType.CLASS_TYPE, uint64(_classId));
        uint8[] memory aTypes = new uint8[](i);
        for(; i > 0 ; i--) {
            aTypes[i-1] = data.getElementInArrayType(ArrayType.CLASS_TYPE, uint64(_classId), i-1);
        }
        classInfo.types = aTypes;

        // add steps
        i = data.getSizeArrayType(ArrayType.STAT_STEP, uint64(_classId));
        uint8[] memory steps = new uint8[](i);
        for(; i > 0 ; i--) {
            steps[i-1] = data.getElementInArrayType(ArrayType.STAT_STEP, uint64(_classId), i-1);
        }
        classInfo.steps = steps;
        
        // add ancestor
        i = gateway.getClassPropertySize(_classId, PropertyType.ANCESTOR);
        uint32[] memory ancestors = new uint32[](i);
        for(; i > 0 ; i--) {
            ancestors[i-1] = gateway.getClassPropertyValue(_classId, PropertyType.ANCESTOR, i-1);
        }
        classInfo.ancestors = ancestors;
    }
    
    function fastSetCacheClassInfo(uint32 _classId1, uint32 _classId2, uint32 _classId3, uint32 _classId4) onlyModerators requireDataContract requireWorldContract external {
        setCacheClassInfo(_classId1);
        setCacheClassInfo(_classId2);
        setCacheClassInfo(_classId3);
        setCacheClassInfo(_classId4);
    }
    
    function presetGymTrainer() onlyModerators external {
        GymTrainer storage trainer1 = gymTrainers[1];
        trainer1.classId = 12;
        trainer1.statBases[0] = 85;
        trainer1.statBases[1] = 95;
        trainer1.statBases[2] = 65;
        trainer1.statBases[3] = 50;
        trainer1.statBases[4] = 50;
        trainer1.statBases[5] = 50;
        GymTrainer storage trainer2 = gymTrainers[2];
        trainer2.classId = 15;
        trainer2.statBases[0] = 50;
        trainer2.statBases[1] = 55;
        trainer2.statBases[2] = 85;
        trainer2.statBases[3] = 85;
        trainer2.statBases[4] = 40;
        trainer2.statBases[5] = 75;
        GymTrainer storage trainer3 = gymTrainers[3];
        trainer3.classId = 8;
        trainer3.statBases[0] = 110;
        trainer3.statBases[1] = 60;
        trainer3.statBases[2] = 40;
        trainer3.statBases[3] = 60;
        trainer3.statBases[4] = 40;
        trainer3.statBases[5] = 40;
        GymTrainer storage trainer4 = gymTrainers[4];
        trainer4.classId = 4;
        trainer4.statBases[0] = 54;
        trainer4.statBases[1] = 69;
        trainer4.statBases[2] = 58;
        trainer4.statBases[3] = 75;
        trainer4.statBases[4] = 75;
        trainer4.statBases[5] = 70;
        GymTrainer storage trainer5 = gymTrainers[5];
        trainer5.classId = 6;
        trainer5.statBases[0] = 50;
        trainer5.statBases[1] = 50;
        trainer5.statBases[2] = 50;
        trainer5.statBases[3] = 105;
        trainer5.statBases[4] = 55;
        trainer5.statBases[5] = 95;
        GymTrainer storage trainer6 = gymTrainers[6];
        trainer6.classId = 13;
        trainer6.statBases[0] = 55;
        trainer6.statBases[1] = 90;
        trainer6.statBases[2] = 95;
        trainer6.statBases[3] = 45;
        trainer6.statBases[4] = 35;
        trainer6.statBases[5] = 35;
        GymTrainer storage trainer7 = gymTrainers[7];
        trainer7.classId = 7;
        trainer7.statBases[0] = 85;
        trainer7.statBases[1] = 60;
        trainer7.statBases[2] = 73;
        trainer7.statBases[3] = 75;
        trainer7.statBases[4] = 80;
        trainer7.statBases[5] = 50;
        GymTrainer storage trainer8 = gymTrainers[8];
        trainer8.classId = 24;
        trainer8.statBases[0] = 140;
        trainer8.statBases[1] = 135;
        trainer8.statBases[2] = 70;
        trainer8.statBases[3] = 77;
        trainer8.statBases[4] = 90;
        trainer8.statBases[5] = 50;
        GymTrainer storage trainer9 = gymTrainers[9];
        trainer9.classId = 16;
        trainer9.statBases[0] = 70;
        trainer9.statBases[1] = 105;
        trainer9.statBases[2] = 80;
        trainer9.statBases[3] = 60;
        trainer9.statBases[4] = 80;
        trainer9.statBases[5] = 90;
        totalTrainer = 9;
    }
    
    function setGymTrainer(uint8 _trainerId, uint32 _classId, uint8 _s0, uint8 _s1, uint8 _s2, uint8 _s3, uint8 _s4, uint8 _s5) onlyModerators external {
        GymTrainer storage trainer = gymTrainers[_trainerId];
        if (trainer.classId == 0)
            totalTrainer += 1;
        trainer.classId = _classId;
        trainer.statBases[0] = _s0;
        trainer.statBases[1] = _s1;
        trainer.statBases[2] = _s2;
        trainer.statBases[3] = _s3;
        trainer.statBases[4] = _s4;
        trainer.statBases[5] = _s5;
    }
    
    function setContract(address _dataContract, address _worldContract) onlyModerators external {
        dataContract = _dataContract;
        worldContract = _worldContract;
    }
    
    function setConfig(uint256 _gymFee, uint8 _maxTrainerLevel, uint8 _maxRandomRound, uint8 _typeBuffPercentage, 
        uint8 _minHpDeducted, uint8 _expPercentage) onlyModerators external {
        gymFee = _gymFee;
        maxTrainerLevel = _maxTrainerLevel;
        maxRandomRound = _maxRandomRound;
        typeBuffPercentage = _typeBuffPercentage;
        minHpDeducted = _minHpDeducted;
        expPercentage = _expPercentage;
    }
    
    function genLevelExp() onlyModerators external {
        uint8 level = 1;
        uint32 requirement = 100;
        uint32 sum = requirement;
        while(level <= 100) {
            levelExps[level] = sum;
            level += 1;
            requirement = (requirement * 11) / 10 + 5;
            sum += requirement;
        }
    }
    
    function genLevelExpGain() onlyModerators external {
        levelExpGains[1] = 31;
        levelExpGains[2] = 33;
        levelExpGains[3] = 34;
        levelExpGains[4] = 36;
        levelExpGains[5] = 38;
        levelExpGains[6] = 40;
        levelExpGains[7] = 42;
        levelExpGains[8] = 44;
        levelExpGains[9] = 46;
        levelExpGains[10] = 48;
    }
    
    function setLevelExpGain(uint8 _level, uint32 _exp) onlyModerators external {
        levelExpGains[_level] = _exp;
    }
    
    function withdrawEther(address _sendTo, uint _amount) onlyModerators external {
        if (_amount > this.balance) {
            revert();
        }
        _sendTo.transfer(_amount);
    }
    
    // public
    function getCacheClassSize(uint32 _classId) constant public returns(uint, uint, uint) {
        CacheClassInfo storage classInfo = cacheClasses[_classId];
        return (classInfo.types.length, classInfo.steps.length, classInfo.ancestors.length);
    }
    
    function getTrainerInfo(uint8 _trainerId) constant external returns(uint32, uint8, uint8, uint8, uint8, uint8, uint8) {
        GymTrainer memory trainer = gymTrainers[_trainerId];
        return (trainer.classId, trainer.statBases[0], trainer.statBases[1], trainer.statBases[2], trainer.statBases[3],
            trainer.statBases[4], trainer.statBases[5]);
    }
    
    function getRandom(uint8 maxRan, uint8 index) constant public returns(uint8) {
        uint256 genNum = uint256(block.blockhash(block.number-1));
        for (uint8 i = 0; i < index && i < 6; i ++) {
            genNum /= 256;
        }
        return uint8(genNum % maxRan);
    }
    
    function getLevel(uint32 exp) view public returns (uint8) {
        uint8 minIndex = 1;
        uint8 maxIndex = 100;
        uint8 currentIndex;
     
        while (minIndex < maxIndex) {
            currentIndex = (minIndex + maxIndex) / 2;
            if (exp < levelExps[currentIndex])
                maxIndex = currentIndex;
            else
                minIndex = currentIndex + 1;
        }
        return minIndex;
    }
    
    function getGainExp(uint8 xLevel, uint8 yLevel) constant public returns(uint32 winExp, uint32 loseExp){
        winExp = levelExpGains[yLevel] * expPercentage / 100;
        if (xLevel > yLevel) {
            if (xLevel > yLevel + 10) {
                winExp = 5;
            } else {
                winExp /= uint32(3) ** (xLevel - yLevel) / uint32(2) ** (xLevel - yLevel);
                if (winExp < 5)
                    winExp = 5;
            }
        }
        loseExp = winExp / 3;
    }
    
    function safeDeduct(uint16 a, uint16 b) pure private returns(uint16){
        if (a > b) {
            return a - b;
        }
        return 0;
    }
    
    function getTypeSupport(uint32 _aClassId, uint32 _bClassId) constant private returns (bool aHasAdvantage, bool bHasAdvantage) {
        // check types 
        for (uint i = 0; i < cacheClasses[_aClassId].types.length; i++) {
            for (uint j = 0; j < cacheClasses[_bClassId].types.length; j++) {
                if (typeAdvantages[cacheClasses[_aClassId].types[i]] == cacheClasses[_bClassId].types[j]) {
                    aHasAdvantage = true;
                }
                if (typeAdvantages[cacheClasses[_bClassId].types[j]] == cacheClasses[_aClassId].types[i]) {
                    bHasAdvantage = true;
                }
            }
        }
    }
    
    function calHpDeducted(uint16 _attack, uint16 _specialAttack, uint16 _defense, uint16 _specialDefense, bool _lucky) view public returns(uint16){
        if (_lucky) {
            _attack = _attack * 13 / 10;
            _specialAttack = _specialAttack * 13 / 10;
        }
        uint16 hpDeducted = safeDeduct(_attack, _defense * 3 /4);
        uint16 hpSpecialDeducted = safeDeduct(_specialAttack, _specialDefense* 3 / 4);
        if (hpDeducted < minHpDeducted && hpSpecialDeducted < minHpDeducted)
            return minHpDeducted;
        if (hpDeducted > hpSpecialDeducted)
            return hpDeducted;
        return hpSpecialDeducted;
    }
    
    function attack(uint8 _index, uint8 _ran, uint16[6] _aStats, uint16[6] _bStats) constant public returns(bool win) {
        if (_ran < _index * maxRandomRound)
            _ran = maxRandomRound;
        else
            _ran = _ran - _index * maxRandomRound;
            
        uint16 round = 0;
        uint16 aHp = _aStats[0];
        uint16 bHp = _bStats[0];
        if (_aStats[5] > _bStats[5]) {
            while (round < maxRandomRound && aHp > 0 && bHp > 0) {
                if (round % 2 == 0) {
                    // a attack 
                    bHp = safeDeduct(bHp, calHpDeducted(_aStats[1], _aStats[3], _bStats[2], _bStats[4], round==_ran));
                } else {
                    aHp = safeDeduct(aHp, calHpDeducted(_bStats[1], _bStats[3], _aStats[2], _aStats[4], round==_ran));
                }
                round++;
            }
        } else {
            while (round < maxRandomRound && aHp > 0 && bHp > 0) {
                if (round % 2 != 0) {
                    bHp = safeDeduct(bHp, calHpDeducted(_aStats[1], _aStats[3], _bStats[2], _bStats[4], round==_ran));
                } else {
                    aHp = safeDeduct(aHp, calHpDeducted(_bStats[1], _bStats[3], _aStats[2], _aStats[4], round==_ran));
                }
                round++;
            }
        }
        
        win = aHp >= bHp;
    }
    
    function attackTrainer(uint8 _index, uint8 _ran, uint8 _trainerId, uint8 _trainerLevel, uint32 _objClassId, uint16[6] _objStats) constant public returns(bool result) {
        GymTrainer memory trainer = gymTrainers[_trainerId];
        uint16[6] memory trainerStats;
        uint i = 0;
        for (i=0; i < STAT_COUNT; i+=1) {
            trainerStats[i] = trainer.statBases[i];
        }
        for (i=0; i < cacheClasses[trainer.classId].steps.length; i++) {
            trainerStats[i] += uint16(safeMult(cacheClasses[trainer.classId].steps[i], _trainerLevel*3));
        }
        
        bool objHasAdvantage;
        bool trainerHasAdvantage;
        (objHasAdvantage, trainerHasAdvantage) = getTypeSupport(_objClassId, trainer.classId);
        uint16 originAttack = _objStats[1];
        uint16 originAttackSpecial = _objStats[3];
        if (objHasAdvantage) {
            _objStats[1] += _objStats[1] * typeBuffPercentage / 100;
            _objStats[3] += _objStats[3] * typeBuffPercentage / 100;
        }
        if (trainerHasAdvantage) {
            trainerStats[1] += trainerStats[1] * typeBuffPercentage / 100;
            trainerStats[3] += trainerStats[3] * typeBuffPercentage / 100;
        }
        result = attack(_index, _ran, _objStats, trainerStats);
        _objStats[1] = originAttack;
        _objStats[3] = originAttackSpecial;
    }
    
    function getObjInfo(uint64 _objId) constant public returns(uint32 classId, address trainee, uint8 level) {
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        MonsterObjAcc memory obj;
        (obj.monsterId, classId, trainee, obj.exp, obj.createIndex, obj.lastClaimIndex, obj.createTime) = data.getMonsterObj(_objId);
        level = getLevel(obj.exp);
    }
    
    function startTraining(uint64 _objId, uint8 _trainerLevel, uint8 _t1, uint8 _t2, uint8 _t3) isActive requireDataContract requireWorldContract payable external {
        if (_trainerLevel > maxTrainerLevel)
            revert();
        if (msg.value != gymFee)
            revert();
        if (_t1 == _t2 || _t1 == _t3 || _t2 == _t3)
            revert();
        if (_t1 == 0 || _t2 == 0 || _t3 == 0 || _t1 > totalTrainer || _t2 > totalTrainer || _t3 > totalTrainer)
            revert();

        AttackData memory att;
        (att.objClassId, att.trainee, att.objLevel) = getObjInfo(_objId);
        if (msg.sender != att.trainee)
            revert();

        uint i = 0;
        uint16[6] memory objStats;
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        for (i=0; i < STAT_COUNT; i+=1) {
            objStats[i] = data.getElementInArrayType(ArrayType.STAT_BASE, _objId, i);
        }
        for (i=0; i < cacheClasses[att.objClassId].steps.length; i++) {
            objStats[i] += uint16(safeMult(cacheClasses[att.objClassId].steps[i], att.objLevel*3));
        }
        
        att.winCount = 0;
        uint8 ran = getRandom(maxRandomRound*3, 0);
        if (attackTrainer(0, ran, _t1, _trainerLevel, att.objClassId, objStats))
            att.winCount += 1;
        if (attackTrainer(1, ran, _t2, _trainerLevel, att.objClassId, objStats))
            att.winCount += 1;
        if (attackTrainer(2, ran, _t3, _trainerLevel, att.objClassId, objStats))
            att.winCount += 1;

        (att.winExp, att.loseExp) = getGainExp(att.objLevel, _trainerLevel);
        EtheremonGateway gateway = EtheremonGateway(worldContract);
        gateway.increaseMonsterExp(_objId, att.winCount * att.winExp + (3 - att.winCount) * att.loseExp);
        
        TrainingLog storage trainingLog = trainees[msg.sender];
        trainingLog.trainers[0] = _t1;
        trainingLog.trainers[1] = _t2;
        trainingLog.trainers[2] = _t3;
        trainingLog.trainerLevel = _trainerLevel;
        trainingLog.objId = _objId;
        trainingLog.objLevel = att.objLevel;
        trainingLog.ran = ran;
    }
    
    function getTrainingLog(address _trainee) constant external returns(uint8, uint8, uint8, uint64, uint8, uint8, uint8) {
        TrainingLog memory trainingLog = trainees[_trainee];
        return (trainingLog.trainers[0], trainingLog.trainers[1], trainingLog.trainers[2], 
            trainingLog.objId, trainingLog.trainerLevel, trainingLog.objLevel, trainingLog.ran);
    }
}

File 2 of 3: EtheremonData
pragma solidity ^0.4.16;

// copyright contact@Etheremon.com

contract SafeMath {

    /* function assert(bool assertion) internal { */
    /*   if (!assertion) { */
    /*     throw; */
    /*   } */
    /* }      // assert no longer needed once solidity is on 0.4.10 */

    function safeAdd(uint256 x, uint256 y) pure internal returns(uint256) {
      uint256 z = x + y;
      assert((z >= x) && (z >= y));
      return z;
    }

    function safeSubtract(uint256 x, uint256 y) pure internal returns(uint256) {
      assert(x >= y);
      uint256 z = x - y;
      return z;
    }

    function safeMult(uint256 x, uint256 y) pure internal returns(uint256) {
      uint256 z = x * y;
      assert((x == 0)||(z/x == y));
      return z;
    }

}

contract BasicAccessControl {
    address public owner;
    address[] public moderators;

    function BasicAccessControl() public {
        owner = msg.sender;
    }

    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }

    modifier onlyModerators() {
        if (msg.sender != owner) {
            bool found = false;
            for (uint index = 0; index < moderators.length; index++) {
                if (moderators[index] == msg.sender) {
                    found = true;
                    break;
                }
            }
            require(found);
        }
        _;
    }

    function ChangeOwner(address _newOwner) onlyOwner public {
        if (_newOwner != address(0)) {
            owner = _newOwner;
        }
    }

    function Kill() onlyOwner public {
        selfdestruct(owner);
    }

    function AddModerator(address _newModerator) onlyOwner public {
        if (_newModerator != address(0)) {
            for (uint index = 0; index < moderators.length; index++) {
                if (moderators[index] == _newModerator) {
                    return;
                }
            }
            moderators.push(_newModerator);
        }
    }
    
    function RemoveModerator(address _oldModerator) onlyOwner public {
        uint foundIndex = 0;
        for (; foundIndex < moderators.length; foundIndex++) {
            if (moderators[foundIndex] == _oldModerator) {
                break;
            }
        }
        if (foundIndex < moderators.length) {
            moderators[foundIndex] = moderators[moderators.length-1];
            delete moderators[moderators.length-1];
            moderators.length--;
        }
    }
}


contract EtheremonEnum {

    enum ResultCode {
        SUCCESS,
        ERROR_CLASS_NOT_FOUND,
        ERROR_LOW_BALANCE,
        ERROR_SEND_FAIL,
        ERROR_NOT_TRAINER,
        ERROR_NOT_ENOUGH_MONEY,
        ERROR_INVALID_AMOUNT
    }
    
    enum ArrayType {
        CLASS_TYPE,
        STAT_STEP,
        STAT_START,
        STAT_BASE,
        OBJ_SKILL
    }
}

contract EtheremonDataBase is EtheremonEnum, BasicAccessControl, SafeMath {
    
    uint64 public totalMonster;
    uint32 public totalClass;
    
    // write
    function addElementToArrayType(ArrayType _type, uint64 _id, uint8 _value) onlyModerators public returns(uint);
    function updateIndexOfArrayType(ArrayType _type, uint64 _id, uint _index, uint8 _value) onlyModerators public returns(uint);
    function setMonsterClass(uint32 _classId, uint256 _price, uint256 _returnPrice, bool _catchable) onlyModerators public returns(uint32);
    function addMonsterObj(uint32 _classId, address _trainer, string _name) onlyModerators public returns(uint64);
    function setMonsterObj(uint64 _objId, string _name, uint32 _exp, uint32 _createIndex, uint32 _lastClaimIndex) onlyModerators public;
    function increaseMonsterExp(uint64 _objId, uint32 amount) onlyModerators public;
    function decreaseMonsterExp(uint64 _objId, uint32 amount) onlyModerators public;
    function removeMonsterIdMapping(address _trainer, uint64 _monsterId) onlyModerators public;
    function addMonsterIdMapping(address _trainer, uint64 _monsterId) onlyModerators public;
    function clearMonsterReturnBalance(uint64 _monsterId) onlyModerators public returns(uint256 amount);
    function collectAllReturnBalance(address _trainer) onlyModerators public returns(uint256 amount);
    function transferMonster(address _from, address _to, uint64 _monsterId) onlyModerators public returns(ResultCode);
    function addExtraBalance(address _trainer, uint256 _amount) onlyModerators public returns(uint256);
    function deductExtraBalance(address _trainer, uint256 _amount) onlyModerators public returns(uint256);
    function setExtraBalance(address _trainer, uint256 _amount) onlyModerators public;
    
    // read
    function getSizeArrayType(ArrayType _type, uint64 _id) constant public returns(uint);
    function getElementInArrayType(ArrayType _type, uint64 _id, uint _index) constant public returns(uint8);
    function getMonsterClass(uint32 _classId) constant public returns(uint32 classId, uint256 price, uint256 returnPrice, uint32 total, bool catchable);
    function getMonsterObj(uint64 _objId) constant public returns(uint64 objId, uint32 classId, address trainer, uint32 exp, uint32 createIndex, uint32 lastClaimIndex, uint createTime);
    function getMonsterName(uint64 _objId) constant public returns(string name);
    function getExtraBalance(address _trainer) constant public returns(uint256);
    function getMonsterDexSize(address _trainer) constant public returns(uint);
    function getMonsterObjId(address _trainer, uint index) constant public returns(uint64);
    function getExpectedBalance(address _trainer) constant public returns(uint256);
    function getMonsterReturn(uint64 _objId) constant public returns(uint256 current, uint256 total);
}

contract EtheremonData is EtheremonDataBase {

    struct MonsterClass {
        uint32 classId;
        uint8[] types;
        uint8[] statSteps;
        uint8[] statStarts;
        uint256 price;
        uint256 returnPrice;
        uint32 total;
        bool catchable;
    }
    
    struct MonsterObj {
        uint64 monsterId;
        uint32 classId;
        address trainer;
        string name;
        uint32 exp;
        uint8[] statBases;
        uint8[] skills;
        uint32 createIndex;
        uint32 lastClaimIndex;
        uint createTime;
    }

    mapping(uint32 => MonsterClass) public monsterClass;
    mapping(uint64 => MonsterObj) public monsterWorld;
    mapping(address => uint64[]) public trainerDex;
    mapping(address => uint256) public trainerExtraBalance;
    
    
    // write access
    function withdrawEther(address _sendTo, uint _amount) onlyOwner public returns(ResultCode) {
        if (_amount > this.balance) {
            return ResultCode.ERROR_INVALID_AMOUNT;
        }
        
        _sendTo.transfer(_amount);
        return ResultCode.SUCCESS;
    }
    
    function addElementToArrayType(ArrayType _type, uint64 _id, uint8 _value) onlyModerators public returns(uint) {
        uint8[] storage array = monsterWorld[_id].statBases;
        if (_type == ArrayType.CLASS_TYPE) {
            array = monsterClass[uint32(_id)].types;
        } else if (_type == ArrayType.STAT_STEP) {
            array = monsterClass[uint32(_id)].statSteps;
        } else if (_type == ArrayType.STAT_START) {
            array = monsterClass[uint32(_id)].statStarts;
        } else if (_type == ArrayType.OBJ_SKILL) {
            array = monsterWorld[_id].skills;
        }
        array.push(_value);
        return array.length;
    }
    
    function updateIndexOfArrayType(ArrayType _type, uint64 _id, uint _index, uint8 _value) onlyModerators public returns(uint) {
        uint8[] storage array = monsterWorld[_id].statBases;
        if (_type == ArrayType.CLASS_TYPE) {
            array = monsterClass[uint32(_id)].types;
        } else if (_type == ArrayType.STAT_STEP) {
            array = monsterClass[uint32(_id)].statSteps;
        } else if (_type == ArrayType.STAT_START) {
            array = monsterClass[uint32(_id)].statStarts;
        } else if (_type == ArrayType.OBJ_SKILL) {
            array = monsterWorld[_id].skills;
        }
        if (_index < array.length) {
            if (_value == 255) {
                // consider as delete
                for(uint i = _index; i < array.length - 1; i++) {
                    array[i] = array[i+1];
                }
                delete array[array.length-1];
                array.length--;
            } else {
                array[_index] = _value;
            }
        }
    }
    
    function setMonsterClass(uint32 _classId, uint256 _price, uint256 _returnPrice, bool _catchable) onlyModerators public returns(uint32) {
        MonsterClass storage class = monsterClass[_classId];
        if (class.classId == 0) {
            totalClass += 1;
        }
        class.classId = _classId;
        class.price = _price;
        class.returnPrice = _returnPrice;
        class.catchable = _catchable;
        return totalClass;
    }
    
    function addMonsterObj(uint32 _classId, address _trainer, string _name) onlyModerators public returns(uint64) {
        MonsterClass storage class = monsterClass[_classId];
        if (class.classId == 0)
            return 0;
                
        // construct new monster
        totalMonster += 1;
        class.total += 1;

        MonsterObj storage obj = monsterWorld[totalMonster];
        obj.monsterId = totalMonster;
        obj.classId = _classId;
        obj.trainer = _trainer;
        obj.name = _name;
        obj.exp = 1;
        obj.createIndex = class.total;
        obj.lastClaimIndex = class.total;
        obj.createTime = now;

        // add to monsterdex
        addMonsterIdMapping(_trainer, obj.monsterId);
        return obj.monsterId;
    }
    
    function setMonsterObj(uint64 _objId, string _name, uint32 _exp, uint32 _createIndex, uint32 _lastClaimIndex) onlyModerators public {
        MonsterObj storage obj = monsterWorld[_objId];
        if (obj.monsterId == _objId) {
            obj.name = _name;
            obj.exp = _exp;
            obj.createIndex = _createIndex;
            obj.lastClaimIndex = _lastClaimIndex;
        }
    }

    function increaseMonsterExp(uint64 _objId, uint32 amount) onlyModerators public {
        MonsterObj storage obj = monsterWorld[_objId];
        if (obj.monsterId == _objId) {
            obj.exp = uint32(safeAdd(obj.exp, amount));
        }
    }

    function decreaseMonsterExp(uint64 _objId, uint32 amount) onlyModerators public {
        MonsterObj storage obj = monsterWorld[_objId];
        if (obj.monsterId == _objId) {
            obj.exp = uint32(safeSubtract(obj.exp, amount));
        }
    }

    function removeMonsterIdMapping(address _trainer, uint64 _monsterId) onlyModerators public {
        uint foundIndex = 0;
        uint64[] storage objIdList = trainerDex[_trainer];
        for (; foundIndex < objIdList.length; foundIndex++) {
            if (objIdList[foundIndex] == _monsterId) {
                break;
            }
        }
        if (foundIndex < objIdList.length) {
            objIdList[foundIndex] = objIdList[objIdList.length-1];
            delete objIdList[objIdList.length-1];
            objIdList.length--;
            MonsterObj storage monster = monsterWorld[_monsterId];
            monster.trainer = 0;
        }
    }
    
    function addMonsterIdMapping(address _trainer, uint64 _monsterId) onlyModerators public {
        if (_trainer != address(0) && _monsterId > 0) {
            uint64[] storage objIdList = trainerDex[_trainer];
            for (uint i = 0; i < objIdList.length; i++) {
                if (objIdList[i] == _monsterId) {
                    return;
                }
            }
            objIdList.push(_monsterId);
            MonsterObj storage monster = monsterWorld[_monsterId];
            monster.trainer = _trainer;
        }
    }
    
    function clearMonsterReturnBalance(uint64 _monsterId) onlyModerators public returns(uint256) {
        MonsterObj storage monster = monsterWorld[_monsterId];
        MonsterClass storage class = monsterClass[monster.classId];
        if (monster.monsterId == 0 || class.classId == 0)
            return 0;
        uint256 amount = 0;
        uint32 gap = uint32(safeSubtract(class.total, monster.lastClaimIndex));
        if (gap > 0) {
            monster.lastClaimIndex = class.total;
            amount = safeMult(gap, class.returnPrice);
            trainerExtraBalance[monster.trainer] = safeAdd(trainerExtraBalance[monster.trainer], amount);
        }
        return amount;
    }
    
    function collectAllReturnBalance(address _trainer) onlyModerators public returns(uint256 amount) {
        uint64[] storage objIdList = trainerDex[_trainer];
        for (uint i = 0; i < objIdList.length; i++) {
            clearMonsterReturnBalance(objIdList[i]);
        }
        return trainerExtraBalance[_trainer];
    }
    
    function transferMonster(address _from, address _to, uint64 _monsterId) onlyModerators public returns(ResultCode) {
        MonsterObj storage monster = monsterWorld[_monsterId];
        if (monster.trainer != _from) {
            return ResultCode.ERROR_NOT_TRAINER;
        }
        
        clearMonsterReturnBalance(_monsterId);
        
        removeMonsterIdMapping(_from, _monsterId);
        addMonsterIdMapping(_to, _monsterId);
        return ResultCode.SUCCESS;
    }
    
    function addExtraBalance(address _trainer, uint256 _amount) onlyModerators public returns(uint256) {
        trainerExtraBalance[_trainer] = safeAdd(trainerExtraBalance[_trainer], _amount);
        return trainerExtraBalance[_trainer];
    }
    
    function deductExtraBalance(address _trainer, uint256 _amount) onlyModerators public returns(uint256) {
        trainerExtraBalance[_trainer] = safeSubtract(trainerExtraBalance[_trainer], _amount);
        return trainerExtraBalance[_trainer];
    }
    
    function setExtraBalance(address _trainer, uint256 _amount) onlyModerators public {
        trainerExtraBalance[_trainer] = _amount;
    }
    
    
    // public
    function () payable public {
        addExtraBalance(msg.sender, msg.value);
    }

    // read access
    function getSizeArrayType(ArrayType _type, uint64 _id) constant public returns(uint) {
        uint8[] storage array = monsterWorld[_id].statBases;
        if (_type == ArrayType.CLASS_TYPE) {
            array = monsterClass[uint32(_id)].types;
        } else if (_type == ArrayType.STAT_STEP) {
            array = monsterClass[uint32(_id)].statSteps;
        } else if (_type == ArrayType.STAT_START) {
            array = monsterClass[uint32(_id)].statStarts;
        } else if (_type == ArrayType.OBJ_SKILL) {
            array = monsterWorld[_id].skills;
        }
        return array.length;
    }
    
    function getElementInArrayType(ArrayType _type, uint64 _id, uint _index) constant public returns(uint8) {
        uint8[] storage array = monsterWorld[_id].statBases;
        if (_type == ArrayType.CLASS_TYPE) {
            array = monsterClass[uint32(_id)].types;
        } else if (_type == ArrayType.STAT_STEP) {
            array = monsterClass[uint32(_id)].statSteps;
        } else if (_type == ArrayType.STAT_START) {
            array = monsterClass[uint32(_id)].statStarts;
        } else if (_type == ArrayType.OBJ_SKILL) {
            array = monsterWorld[_id].skills;
        }
        if (_index >= array.length)
            return 0;
        return array[_index];
    }
    
    
    function getMonsterClass(uint32 _classId) constant public returns(uint32 classId, uint256 price, uint256 returnPrice, uint32 total, bool catchable) {
        MonsterClass storage class = monsterClass[_classId];
        classId = class.classId;
        price = class.price;
        returnPrice = class.returnPrice;
        total = class.total;
        catchable = class.catchable;
    }
    
    function getMonsterObj(uint64 _objId) constant public returns(uint64 objId, uint32 classId, address trainer, uint32 exp, uint32 createIndex, uint32 lastClaimIndex, uint createTime) {
        MonsterObj storage monster = monsterWorld[_objId];
        objId = monster.monsterId;
        classId = monster.classId;
        trainer = monster.trainer;
        exp = monster.exp;
        createIndex = monster.createIndex;
        lastClaimIndex = monster.lastClaimIndex;
        createTime = monster.createTime;
    }
    
    function getMonsterName(uint64 _objId) constant public returns(string name) {
        return monsterWorld[_objId].name;
    }

    function getExtraBalance(address _trainer) constant public returns(uint256) {
        return trainerExtraBalance[_trainer];
    }
    
    function getMonsterDexSize(address _trainer) constant public returns(uint) {
        return trainerDex[_trainer].length;
    }
    
    function getMonsterObjId(address _trainer, uint index) constant public returns(uint64) {
        if (index >= trainerDex[_trainer].length)
            return 0;
        return trainerDex[_trainer][index];
    }
    
    function getExpectedBalance(address _trainer) constant public returns(uint256) {
        uint64[] storage objIdList = trainerDex[_trainer];
        uint256 monsterBalance = 0;
        for (uint i = 0; i < objIdList.length; i++) {
            MonsterObj memory monster = monsterWorld[objIdList[i]];
            MonsterClass storage class = monsterClass[monster.classId];
            uint32 gap = uint32(safeSubtract(class.total, monster.lastClaimIndex));
            monsterBalance += safeMult(gap, class.returnPrice);
        }
        return monsterBalance;
    }
    
    function getMonsterReturn(uint64 _objId) constant public returns(uint256 current, uint256 total) {
        MonsterObj memory monster = monsterWorld[_objId];
        MonsterClass storage class = monsterClass[monster.classId];
        uint32 totalGap = uint32(safeSubtract(class.total, monster.createIndex));
        uint32 currentGap = uint32(safeSubtract(class.total, monster.lastClaimIndex));
        return (safeMult(currentGap, class.returnPrice), safeMult(totalGap, class.returnPrice));
    }

}

File 3 of 3: EtheremonWorld
pragma solidity ^0.4.16;

// copyright contact@Etheremon.com

contract SafeMath {

    /* function assert(bool assertion) internal { */
    /*   if (!assertion) { */
    /*     throw; */
    /*   } */
    /* }      // assert no longer needed once solidity is on 0.4.10 */

    function safeAdd(uint256 x, uint256 y) pure internal returns(uint256) {
      uint256 z = x + y;
      assert((z >= x) && (z >= y));
      return z;
    }

    function safeSubtract(uint256 x, uint256 y) pure internal returns(uint256) {
      assert(x >= y);
      uint256 z = x - y;
      return z;
    }

    function safeMult(uint256 x, uint256 y) pure internal returns(uint256) {
      uint256 z = x * y;
      assert((x == 0)||(z/x == y));
      return z;
    }

}

contract BasicAccessControl {
    address public owner;
    // address[] public moderators;
    uint16 public totalModerators = 0;
    mapping (address => bool) public moderators;
    bool public isMaintaining = true;

    function BasicAccessControl() public {
        owner = msg.sender;
    }

    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }

    modifier onlyModerators() {
        require(moderators[msg.sender] == true);
        _;
    }

    modifier isActive {
        require(!isMaintaining);
        _;
    }

    function ChangeOwner(address _newOwner) onlyOwner public {
        if (_newOwner != address(0)) {
            owner = _newOwner;
        }
    }

    function AddModerator(address _newModerator) onlyOwner public {
        if (moderators[_newModerator] == false) {
            moderators[_newModerator] = true;
            totalModerators += 1;
        }
    }
    
    function RemoveModerator(address _oldModerator) onlyOwner public {
        if (moderators[_oldModerator] == true) {
            moderators[_oldModerator] = false;
            totalModerators -= 1;
        }
    }
    
    function UpdateMaintaining(bool _isMaintaining) onlyOwner public {
        isMaintaining = _isMaintaining;
    }
}

contract EtheremonEnum {

    enum ResultCode {
        SUCCESS,
        ERROR_CLASS_NOT_FOUND,
        ERROR_LOW_BALANCE,
        ERROR_SEND_FAIL,
        ERROR_NOT_TRAINER,
        ERROR_NOT_ENOUGH_MONEY,
        ERROR_INVALID_AMOUNT
    }
    
    enum ArrayType {
        CLASS_TYPE,
        STAT_STEP,
        STAT_START,
        STAT_BASE,
        OBJ_SKILL
    }
    
    enum PropertyType {
        ANCESTOR,
        XFACTOR
    }
}

contract EtheremonDataBase is EtheremonEnum, BasicAccessControl, SafeMath {
    
    uint64 public totalMonster;
    uint32 public totalClass;
    
    // write
    function withdrawEther(address _sendTo, uint _amount) onlyOwner public returns(ResultCode);
    function addElementToArrayType(ArrayType _type, uint64 _id, uint8 _value) onlyModerators public returns(uint);
    function updateIndexOfArrayType(ArrayType _type, uint64 _id, uint _index, uint8 _value) onlyModerators public returns(uint);
    function setMonsterClass(uint32 _classId, uint256 _price, uint256 _returnPrice, bool _catchable) onlyModerators public returns(uint32);
    function addMonsterObj(uint32 _classId, address _trainer, string _name) onlyModerators public returns(uint64);
    function setMonsterObj(uint64 _objId, string _name, uint32 _exp, uint32 _createIndex, uint32 _lastClaimIndex) onlyModerators public;
    function increaseMonsterExp(uint64 _objId, uint32 amount) onlyModerators public;
    function decreaseMonsterExp(uint64 _objId, uint32 amount) onlyModerators public;
    function removeMonsterIdMapping(address _trainer, uint64 _monsterId) onlyModerators public;
    function addMonsterIdMapping(address _trainer, uint64 _monsterId) onlyModerators public;
    function clearMonsterReturnBalance(uint64 _monsterId) onlyModerators public returns(uint256 amount);
    function collectAllReturnBalance(address _trainer) onlyModerators public returns(uint256 amount);
    function transferMonster(address _from, address _to, uint64 _monsterId) onlyModerators public returns(ResultCode);
    function addExtraBalance(address _trainer, uint256 _amount) onlyModerators public returns(uint256);
    function deductExtraBalance(address _trainer, uint256 _amount) onlyModerators public returns(uint256);
    function setExtraBalance(address _trainer, uint256 _amount) onlyModerators public;
    
    // read
    function getSizeArrayType(ArrayType _type, uint64 _id) constant public returns(uint);
    function getElementInArrayType(ArrayType _type, uint64 _id, uint _index) constant public returns(uint8);
    function getMonsterClass(uint32 _classId) constant public returns(uint32 classId, uint256 price, uint256 returnPrice, uint32 total, bool catchable);
    function getMonsterObj(uint64 _objId) constant public returns(uint64 objId, uint32 classId, address trainer, uint32 exp, uint32 createIndex, uint32 lastClaimIndex, uint createTime);
    function getMonsterName(uint64 _objId) constant public returns(string name);
    function getExtraBalance(address _trainer) constant public returns(uint256);
    function getMonsterDexSize(address _trainer) constant public returns(uint);
    function getMonsterObjId(address _trainer, uint index) constant public returns(uint64);
    function getExpectedBalance(address _trainer) constant public returns(uint256);
    function getMonsterReturn(uint64 _objId) constant public returns(uint256 current, uint256 total);
}

contract EtheremonGateway is EtheremonEnum, BasicAccessControl {
    // using for battle contract later
    function increaseMonsterExp(uint64 _objId, uint32 amount) onlyModerators public;
    function decreaseMonsterExp(uint64 _objId, uint32 amount) onlyModerators public;
    
    // read 
    function isGason(uint64 _objId) constant external returns(bool);
    function getObjBattleInfo(uint64 _objId) constant external returns(uint32 classId, uint32 exp, bool isGason, 
        uint ancestorLength, uint xfactorsLength);
    function getClassPropertySize(uint32 _classId, PropertyType _type) constant external returns(uint);
    function getClassPropertyValue(uint32 _classId, PropertyType _type, uint index) constant external returns(uint32);
}

contract EtheremonWorld is EtheremonGateway, SafeMath {
    // old processor
    address constant public ETHEREMON_PROCESSOR = address(0x8a60806F05876f4d6dB00c877B0558DbCAD30682);
    uint8 constant public STAT_COUNT = 6;
    uint8 constant public STAT_MAX = 32;
    uint8 constant public GEN0_NO = 24;
    
    struct MonsterClassAcc {
        uint32 classId;
        uint256 price;
        uint256 returnPrice;
        uint32 total;
        bool catchable;
    }

    struct MonsterObjAcc {
        uint64 monsterId;
        uint32 classId;
        address trainer;
        string name;
        uint32 exp;
        uint32 createIndex;
        uint32 lastClaimIndex;
        uint createTime;
    }
    
    // Gen0 has return price & no longer can be caught when this contract is deployed
    struct Gen0Config {
        uint32 classId;
        uint256 originalPrice;
        uint256 returnPrice;
        uint32 total; // total caught (not count those from eggs)
    }
    
    struct GenXProperty {
        uint32 classId;
        bool isGason;
        uint32[] ancestors;
        uint32[] xfactors;
    }
    
    mapping(uint32 => Gen0Config) public gen0Config;
    mapping(uint32 => GenXProperty) public genxProperty;
    uint256 public totalCashout = 0; // for admin
    uint256 public totalEarn = 0; // exclude gen 0
    uint16 public priceIncreasingRatio = 1000;
    uint public maxDexSize = 500;
    
    address private lastHunter = address(0x0);

    // data contract
    address public dataContract;
    
    // event
    event EventCatchMonster(address indexed trainer, uint64 objId);
    event EventCashOut(address indexed trainer, ResultCode result, uint256 amount);
    event EventWithdrawEther(address indexed sendTo, ResultCode result, uint256 amount);
    
    function EtheremonWorld(address _dataContract) public {
        dataContract = _dataContract;
    }
    
     // admin & moderators
    function setMaxDexSize(uint _value) onlyModerators external {
        maxDexSize = _value;
    }
    
    function setOriginalPriceGen0() onlyModerators external {
        gen0Config[1] = Gen0Config(1, 0.3 ether, 0.003 ether, 374);
        gen0Config[2] = Gen0Config(2, 0.3 ether, 0.003 ether, 408);
        gen0Config[3] = Gen0Config(3, 0.3 ether, 0.003 ether, 373);
        gen0Config[4] = Gen0Config(4, 0.2 ether, 0.002 ether, 437);
        gen0Config[5] = Gen0Config(5, 0.1 ether, 0.001 ether, 497);
        gen0Config[6] = Gen0Config(6, 0.3 ether, 0.003 ether, 380); 
        gen0Config[7] = Gen0Config(7, 0.2 ether, 0.002 ether, 345);
        gen0Config[8] = Gen0Config(8, 0.1 ether, 0.001 ether, 518); 
        gen0Config[9] = Gen0Config(9, 0.1 ether, 0.001 ether, 447);
        gen0Config[10] = Gen0Config(10, 0.2 ether, 0.002 ether, 380); 
        gen0Config[11] = Gen0Config(11, 0.2 ether, 0.002 ether, 354);
        gen0Config[12] = Gen0Config(12, 0.2 ether, 0.002 ether, 346);
        gen0Config[13] = Gen0Config(13, 0.2 ether, 0.002 ether, 351); 
        gen0Config[14] = Gen0Config(14, 0.2 ether, 0.002 ether, 338);
        gen0Config[15] = Gen0Config(15, 0.2 ether, 0.002 ether, 341);
        gen0Config[16] = Gen0Config(16, 0.35 ether, 0.0035 ether, 384);
        gen0Config[17] = Gen0Config(17, 0.1 ether, 0.001 ether, 305); 
        gen0Config[18] = Gen0Config(18, 0.1 ether, 0.001 ether, 427);
        gen0Config[19] = Gen0Config(19, 0.1 ether, 0.001 ether, 304);
        gen0Config[20] = Gen0Config(20, 0.4 ether, 0.005 ether, 82);
        gen0Config[21] = Gen0Config(21, 1, 1, 123);
        gen0Config[22] = Gen0Config(22, 0.2 ether, 0.001 ether, 468);
        gen0Config[23] = Gen0Config(23, 0.5 ether, 0.0025 ether, 302);
        gen0Config[24] = Gen0Config(24, 1 ether, 0.005 ether, 195);
    }

    function getEarningAmount() constant public returns(uint256) {
        // calculate value for gen0
        uint256 totalValidAmount = 0;
        for (uint32 classId=1; classId <= GEN0_NO; classId++) {
            // make sure there is a class
            Gen0Config storage gen0 = gen0Config[classId];
            if (gen0.total >0 && gen0.classId == classId && gen0.originalPrice > 0 && gen0.returnPrice > 0) {
                uint256 rate = gen0.originalPrice/gen0.returnPrice;
                if (rate < gen0.total) {
                    totalValidAmount += (gen0.originalPrice + gen0.returnPrice) * rate / 2;
                    totalValidAmount += (gen0.total - rate) * gen0.returnPrice;
                } else {
                    totalValidAmount += (gen0.originalPrice + gen0.returnPrice * (rate - gen0.total + 1)) / 2 * gen0.total;
                }
            }
        }
        
        // add in earn from genx
        totalValidAmount = safeAdd(totalValidAmount, totalEarn);
        // deduct amount of cashing out 
        totalValidAmount = safeSubtract(totalValidAmount, totalCashout);
        
        return totalValidAmount;
    }
    
    function withdrawEther(address _sendTo, uint _amount) onlyModerators external returns(ResultCode) {
        if (_amount > this.balance) {
            EventWithdrawEther(_sendTo, ResultCode.ERROR_INVALID_AMOUNT, 0);
            return ResultCode.ERROR_INVALID_AMOUNT;
        }
        
        uint256 totalValidAmount = getEarningAmount();
        if (_amount > totalValidAmount) {
            EventWithdrawEther(_sendTo, ResultCode.ERROR_INVALID_AMOUNT, 0);
            return ResultCode.ERROR_INVALID_AMOUNT;
        }
        
        _sendTo.transfer(_amount);
        totalCashout += _amount;
        EventWithdrawEther(_sendTo, ResultCode.SUCCESS, _amount);
        return ResultCode.SUCCESS;
    }

    // convenient tool to add monster
    function addMonsterClassBasic(uint32 _classId, uint8 _type, uint256 _price, uint256 _returnPrice,
        uint8 _ss1, uint8 _ss2, uint8 _ss3, uint8 _ss4, uint8 _ss5, uint8 _ss6) onlyModerators external {
        
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        MonsterClassAcc memory class;
        (class.classId, class.price, class.returnPrice, class.total, class.catchable) = data.getMonsterClass(_classId);
        // can add only one time
        if (_classId == 0 || class.classId == _classId)
            revert();

        data.setMonsterClass(_classId, _price, _returnPrice, true);
        data.addElementToArrayType(ArrayType.CLASS_TYPE, uint64(_classId), _type);
        
        // add stat step
        data.addElementToArrayType(ArrayType.STAT_START, uint64(_classId), _ss1);
        data.addElementToArrayType(ArrayType.STAT_START, uint64(_classId), _ss2);
        data.addElementToArrayType(ArrayType.STAT_START, uint64(_classId), _ss3);
        data.addElementToArrayType(ArrayType.STAT_START, uint64(_classId), _ss4);
        data.addElementToArrayType(ArrayType.STAT_START, uint64(_classId), _ss5);
        data.addElementToArrayType(ArrayType.STAT_START, uint64(_classId), _ss6);
        
    }
    
    function addMonsterClassExtend(uint32 _classId, uint8 _type2, uint8 _type3, 
        uint8 _st1, uint8 _st2, uint8 _st3, uint8 _st4, uint8 _st5, uint8 _st6 ) onlyModerators external {

        EtheremonDataBase data = EtheremonDataBase(dataContract);
        if (_classId == 0 || data.getSizeArrayType(ArrayType.STAT_STEP, uint64(_classId)) > 0)
            revert();

        if (_type2 > 0) {
            data.addElementToArrayType(ArrayType.CLASS_TYPE, uint64(_classId), _type2);
        }
        if (_type3 > 0) {
            data.addElementToArrayType(ArrayType.CLASS_TYPE, uint64(_classId), _type3);
        }
        
        // add stat base
        data.addElementToArrayType(ArrayType.STAT_STEP, uint64(_classId), _st1);
        data.addElementToArrayType(ArrayType.STAT_STEP, uint64(_classId), _st2);
        data.addElementToArrayType(ArrayType.STAT_STEP, uint64(_classId), _st3);
        data.addElementToArrayType(ArrayType.STAT_STEP, uint64(_classId), _st4);
        data.addElementToArrayType(ArrayType.STAT_STEP, uint64(_classId), _st5);
        data.addElementToArrayType(ArrayType.STAT_STEP, uint64(_classId), _st6);
    }
    
    function setCatchable(uint32 _classId, bool catchable) onlyModerators external {
        // can not edit gen 0 - can not catch forever
        Gen0Config storage gen0 = gen0Config[_classId];
        if (gen0.classId == _classId)
            revert();
        
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        MonsterClassAcc memory class;
        (class.classId, class.price, class.returnPrice, class.total, class.catchable) = data.getMonsterClass(_classId);
        data.setMonsterClass(class.classId, class.price, class.returnPrice, catchable);
    }
    
    function setPriceIncreasingRatio(uint16 _ratio) onlyModerators external {
        priceIncreasingRatio = _ratio;
    }
    
    function setGason(uint32 _classId, bool _isGason) onlyModerators external {
        GenXProperty storage pro = genxProperty[_classId];
        pro.isGason = _isGason;
    }
    
    function addClassProperty(uint32 _classId, PropertyType _type, uint32 value) onlyModerators external {
        GenXProperty storage pro = genxProperty[_classId];
        pro.classId = _classId;
        if (_type == PropertyType.ANCESTOR) {
            pro.ancestors.push(value);
        } else {
            pro.xfactors.push(value);
        }
    }
    
    // gate way 
    function increaseMonsterExp(uint64 _objId, uint32 amount) onlyModerators public {
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        data.increaseMonsterExp(_objId, amount);
    }
    
    function decreaseMonsterExp(uint64 _objId, uint32 amount) onlyModerators public {
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        data.decreaseMonsterExp(_objId, amount);
    }
    
    // helper
    function getRandom(uint8 maxRan, uint8 index, address priAddress) constant public returns(uint8) {
        uint256 genNum = uint256(block.blockhash(block.number-1)) + uint256(priAddress);
        for (uint8 i = 0; i < index && i < 6; i ++) {
            genNum /= 256;
        }
        return uint8(genNum % maxRan);
    }
    
    function () payable public {
        if (msg.sender != ETHEREMON_PROCESSOR)
            revert();
    }
    
    // public
    
    function isGason(uint64 _objId) constant external returns(bool) {
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        MonsterObjAcc memory obj;
        (obj.monsterId, obj.classId, obj.trainer, obj.exp, obj.createIndex, obj.lastClaimIndex, obj.createTime) = data.getMonsterObj(_objId);
        GenXProperty storage pro = genxProperty[obj.classId];
        return pro.isGason;
    }
    
    function getObjIndex(uint64 _objId) constant public returns(uint32 classId, uint32 createIndex, uint32 lastClaimIndex) {
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        MonsterObjAcc memory obj;
        (obj.monsterId, obj.classId, obj.trainer, obj.exp, obj.createIndex, obj.lastClaimIndex, obj.createTime) = data.getMonsterObj(_objId);
        return (obj.classId, obj.createIndex, obj.lastClaimIndex);
    }
    
    function getObjBattleInfo(uint64 _objId) constant external returns(uint32 classId, uint32 exp, bool isGason, 
        uint ancestorLength, uint xfactorsLength) {
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        MonsterObjAcc memory obj;
        (obj.monsterId, obj.classId, obj.trainer, obj.exp, obj.createIndex, obj.lastClaimIndex, obj.createTime) = data.getMonsterObj(_objId);
        GenXProperty storage pro = genxProperty[obj.classId];
        return (obj.classId, obj.exp, pro.isGason, pro.ancestors.length, pro.xfactors.length);
    }
    
    function getClassPropertySize(uint32 _classId, PropertyType _type) constant external returns(uint) {
        if (_type == PropertyType.ANCESTOR) 
            return genxProperty[_classId].ancestors.length;
        else
            return genxProperty[_classId].xfactors.length;
    }
    
    function getClassPropertyValue(uint32 _classId, PropertyType _type, uint index) constant external returns(uint32) {
        if (_type == PropertyType.ANCESTOR)
            return genxProperty[_classId].ancestors[index];
        else
            return genxProperty[_classId].xfactors[index];
    }
    
    // only gen 0
    function getGen0COnfig(uint32 _classId) constant public returns(uint32, uint256, uint32) {
        Gen0Config storage gen0 = gen0Config[_classId];
        return (gen0.classId, gen0.originalPrice, gen0.total);
    }
    
    // only gen 0
    function getReturnFromMonster(uint64 _objId) constant public returns(uint256 current, uint256 total) {
        /*
        1. Gen 0 can not be caught anymore.
        2. Egg will not give return.
        */
        
        uint32 classId = 0;
        uint32 createIndex = 0;
        uint32 lastClaimIndex = 0;
        (classId, createIndex, lastClaimIndex) = getObjIndex(_objId);
        Gen0Config storage gen0 = gen0Config[classId];
        if (gen0.classId != classId) {
            return (0, 0);
        }
        
        uint32 currentGap = 0;
        uint32 totalGap = 0;
        if (lastClaimIndex < gen0.total)
            currentGap = gen0.total - lastClaimIndex;
        if (createIndex < gen0.total)
            totalGap = gen0.total - createIndex;
        return (safeMult(currentGap, gen0.returnPrice), safeMult(totalGap, gen0.returnPrice));
    }
    
    // write access
    
    function moveDataContractBalanceToWorld() external {
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        data.withdrawEther(address(this), data.balance);
    }
    
    function renameMonster(uint64 _objId, string name) isActive external {
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        MonsterObjAcc memory obj;
        (obj.monsterId, obj.classId, obj.trainer, obj.exp, obj.createIndex, obj.lastClaimIndex, obj.createTime) = data.getMonsterObj(_objId);
        if (obj.monsterId != _objId || obj.trainer != msg.sender) {
            revert();
        }
        data.setMonsterObj(_objId, name, obj.exp, obj.createIndex, obj.lastClaimIndex);
    }
    
    function catchMonster(uint32 _classId, string _name) isActive external payable {
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        MonsterClassAcc memory class;
        (class.classId, class.price, class.returnPrice, class.total, class.catchable) = data.getMonsterClass(_classId);
        
        if (class.classId == 0 || class.catchable == false) {
            revert();
        }
        
        // can not keep too much etheremon 
        if (data.getMonsterDexSize(msg.sender) > maxDexSize)
            revert();
        
        uint256 totalBalance = safeAdd(msg.value, data.getExtraBalance(msg.sender));
        uint256 payPrice = class.price;
        // increase price for each etheremon created
        if (class.total > 0)
            payPrice += class.price*(class.total-1)/priceIncreasingRatio;
        if (payPrice > totalBalance) {
            revert();
        }
        totalEarn += payPrice;
        
        // deduct the balance
        data.setExtraBalance(msg.sender, safeSubtract(totalBalance, payPrice));
        
        // add monster
        uint64 objId = data.addMonsterObj(_classId, msg.sender, _name);
        // generate base stat for the previous one
        for (uint i=0; i < STAT_COUNT; i+= 1) {
            uint8 value = getRandom(STAT_MAX, uint8(i), lastHunter) + data.getElementInArrayType(ArrayType.STAT_START, uint64(_classId), i);
            data.addElementToArrayType(ArrayType.STAT_BASE, objId, value);
        }
        
        lastHunter = msg.sender;
        EventCatchMonster(msg.sender, objId);
    }


    function cashOut(uint256 _amount) public returns(ResultCode) {
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        
        uint256 totalAmount = data.getExtraBalance(msg.sender);
        uint64 objId = 0;

        // collect gen 0 return price 
        uint dexSize = data.getMonsterDexSize(msg.sender);
        for (uint i = 0; i < dexSize; i++) {
            objId = data.getMonsterObjId(msg.sender, i);
            if (objId > 0) {
                MonsterObjAcc memory obj;
                (obj.monsterId, obj.classId, obj.trainer, obj.exp, obj.createIndex, obj.lastClaimIndex, obj.createTime) = data.getMonsterObj(objId);
                Gen0Config storage gen0 = gen0Config[obj.classId];
                if (gen0.classId == obj.classId) {
                    if (obj.lastClaimIndex < gen0.total) {
                        uint32 gap = uint32(safeSubtract(gen0.total, obj.lastClaimIndex));
                        if (gap > 0) {
                            totalAmount += safeMult(gap, gen0.returnPrice);
                            // reset total (except name is cleared :( )
                            data.setMonsterObj(obj.monsterId, " name me ", obj.exp, obj.createIndex, gen0.total);
                        }
                    }
                }
            }
        }
        
        // default to cash out all
        if (_amount == 0) {
            _amount = totalAmount;
        }
        if (_amount > totalAmount) {
            revert();
        }
        
        // check contract has enough money
        if (this.balance + data.balance < _amount){
            revert();
        } else if (this.balance < _amount) {
            data.withdrawEther(address(this), data.balance);
        }
        
        if (_amount > 0) {
            data.setExtraBalance(msg.sender, totalAmount - _amount);
            if (!msg.sender.send(_amount)) {
                data.setExtraBalance(msg.sender, totalAmount);
                EventCashOut(msg.sender, ResultCode.ERROR_SEND_FAIL, 0);
                return ResultCode.ERROR_SEND_FAIL;
            }
        }
        
        EventCashOut(msg.sender, ResultCode.SUCCESS, _amount);
        return ResultCode.SUCCESS;
    }
    
    // read access
    
    function getTrainerEarn(address _trainer) constant public returns(uint256) {
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        uint256 returnFromMonster = 0;
        // collect gen 0 return price 
        uint256 gen0current = 0;
        uint256 gen0total = 0;
        uint64 objId = 0;
        uint dexSize = data.getMonsterDexSize(_trainer);
        for (uint i = 0; i < dexSize; i++) {
            objId = data.getMonsterObjId(_trainer, i);
            if (objId > 0) {
                (gen0current, gen0total) = getReturnFromMonster(objId);
                returnFromMonster += gen0current;
            }
        }
        return returnFromMonster;
    }
    
    function getTrainerBalance(address _trainer) constant external returns(uint256) {
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        
        uint256 userExtraBalance = data.getExtraBalance(_trainer);
        uint256 returnFromMonster = getTrainerEarn(_trainer);

        return (userExtraBalance + returnFromMonster);
    }
    
    function getMonsterClassBasic(uint32 _classId) constant external returns(uint256, uint256, uint256, bool) {
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        MonsterClassAcc memory class;
        (class.classId, class.price, class.returnPrice, class.total, class.catchable) = data.getMonsterClass(_classId);
        return (class.price, class.returnPrice, class.total, class.catchable);
    }

}