ETH Price: $2,113.63 (+2.66%)

Transaction Decoder

Block:
4784856 at Dec-23-2017 09:54:39 PM +UTC
Transaction Fee:
0.001753395 ETH $3.71
Gas Used:
350,679 Gas / 5 Gwei

Emitted Events:

57 EtheremonProcessor.EventCatchMonster( trainer=[Sender] 0x03b3b1b629907c6e171ea3d3301915a3a5e70470, result=0, objId=11117 )

Account State Difference:

  Address   Before After State Difference Code
0x03b3b1B6...3a5E70470
1.570701025 Eth
Nonce: 31
1.56894763 Eth
Nonce: 32
0.001753395
(Spark Pool)
1,990.48648793854617145 Eth1,990.48824133354617145 Eth0.001753395
0xABC1c404...81D18Eb3E
(Etheremon: Data)

Execution Trace

EtheremonProcessor.catchMonster( _classId=26, _name=Omnom ) => ( 0 )
File 1 of 2: EtheremonProcessor
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 removeElementOfArrayType(ArrayType _type, uint64 _id, 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 EtheremonProcessor is EtheremonEnum, BasicAccessControl, SafeMath {
    
    uint8 public STAT_COUNT = 6;
    uint8 public STAT_MAX = 32;
    
    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;
    }
    
    // data contract
    address public dataContract;
    
    function EtheremonProcessor(address _dataContract) public {
        dataContract = _dataContract;
    }

    // internal
    modifier requireDataContract {
        require(dataContract != 0x0);
        _;
    }
    

    // event
    event EventCatchMonster(address indexed trainer, ResultCode result, uint64 objId);
    event EventCashOut(address indexed trainer, ResultCode result, uint256 amount);
    event EventWithdrawEther(address indexed sendTo, ResultCode result, uint256 amount);
 
     // admin
    function withdrawEther(address _sendTo, uint _amount) onlyOwner public returns(ResultCode) {
        if (_amount > this.balance) {
            EventWithdrawEther(_sendTo, ResultCode.ERROR_INVALID_AMOUNT, 0);
            return ResultCode.ERROR_INVALID_AMOUNT;
        }
        
        _sendTo.transfer(_amount);
        EventWithdrawEther(_sendTo, ResultCode.SUCCESS, _amount);
        return ResultCode.SUCCESS;
    }
    
    function setDataContract(address _dataContract) onlyModerators public {
        dataContract = _dataContract;
    }
    
    function addMonsterClassBasic(uint32 _classId, uint8 _type, uint256 _price, uint256 _returnPrice,
        uint8 _ss1, uint8 _ss2, uint8 _ss3, uint8 _ss4, uint8 _ss5, uint8 _ss6) onlyModerators public {
            
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        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 public {
        
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        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);
        
    }
    
    // helper
    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 () payable public {
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        data.addExtraBalance(msg.sender, msg.value);
    }
    
    // public
    
    function catchMonster(uint32 _classId, string _name) requireDataContract public payable returns(ResultCode) {
        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) {
            EventCatchMonster(msg.sender, ResultCode.ERROR_CLASS_NOT_FOUND, 0);
            return ResultCode.ERROR_CLASS_NOT_FOUND;
        }
        
        uint256 totalBalance = safeAdd(msg.value, data.getExtraBalance(msg.sender));
        uint256 payPrice = class.price;
        if (payPrice > totalBalance) {
            data.addExtraBalance(msg.sender, msg.value);
            EventCatchMonster(msg.sender, ResultCode.ERROR_LOW_BALANCE, 0);
            return ResultCode.ERROR_LOW_BALANCE;
        }
        
        // deduct the balance
        data.setExtraBalance(msg.sender, safeSubtract(totalBalance, payPrice));
        
        // add monster
        uint64 objId = data.addMonsterObj(_classId, msg.sender, _name);
        // generate base stat
        for (uint i=0; i < STAT_COUNT; i+= 1) {
            uint8 value = getRandom(STAT_MAX, uint8(i)) + data.getElementInArrayType(ArrayType.STAT_START, uint64(_classId), i);
            data.addElementToArrayType(ArrayType.STAT_BASE, objId, value);
        }

        // calculate the price
        uint256 distributedPrice = safeMult(class.returnPrice, class.total + 2);
        if (payPrice < distributedPrice) {
            payPrice = distributedPrice;
            // update price
            data.setMonsterClass(_classId, distributedPrice, class.returnPrice, true);
        }
        
        EventCatchMonster(msg.sender, ResultCode.SUCCESS, objId);
        return ResultCode.SUCCESS;
    }
    
    function cashOut(uint256 _amount) requireDataContract public returns(ResultCode) {
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        
        uint256 totalAmount = data.collectAllReturnBalance(msg.sender);
        // default to cash out all
        if (_amount == 0) {
            _amount = totalAmount;
        }
        if (_amount > totalAmount) {
            EventCashOut(msg.sender, ResultCode.ERROR_LOW_BALANCE, 0);
            return ResultCode.ERROR_LOW_BALANCE;
        }
        
        // check contract has enough money
        if (this.balance < _amount) {
            EventCashOut(msg.sender, ResultCode.ERROR_NOT_ENOUGH_MONEY, 0);
            return ResultCode.ERROR_NOT_ENOUGH_MONEY;
        }
        
        if (_amount > 0) {
            data.deductExtraBalance(msg.sender, _amount);
            if (!msg.sender.send(_amount)) {
                data.addExtraBalance(msg.sender, _amount);
                EventCashOut(msg.sender, ResultCode.ERROR_SEND_FAIL, 0);
                return ResultCode.ERROR_SEND_FAIL;
            }
        }
        
        EventCashOut(msg.sender, ResultCode.SUCCESS, _amount);
        return ResultCode.SUCCESS;
    }
    
    function getTrainerBalance(address _trainer) constant public returns(uint256) {
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        return data.getExpectedBalance(_trainer);
    }
    
    function getMonsterClassBasic(uint32 _classId) constant public 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);
    }

    function getLevel(uint32 exp) pure internal returns (uint8) {
        uint8 level = 1;
        uint8 requirement = 100;
        while(level < 100 && exp > requirement) {
            exp -= requirement;
            level += 1;
            requirement = requirement * 12 / 10 + 5;
        }
        return level;
    }

    function getMonsterLevel(uint64 _objId) constant public returns(uint8) {
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        MonsterObjAcc memory obj;
        uint32 _ = 0;
        (obj.monsterId, obj.classId, obj.trainer, obj.exp, _, _, obj.createTime) = data.getMonsterObj(_objId);
     
        return getLevel(obj.exp);
    }
    
    function getMonsterCP(uint64 _objId) constant public returns(uint64) {
        EtheremonDataBase data = EtheremonDataBase(dataContract);
        MonsterObjAcc memory obj;
        uint32 _ = 0;
        (obj.monsterId, obj.classId, obj.trainer, obj.exp, _, _, obj.createTime) = data.getMonsterObj(_objId);
     
        uint baseSize = data.getSizeArrayType(ArrayType.STAT_BASE, obj.monsterId);
        if (baseSize == 0)
            return 0;
        
        uint256 total = 0;
        for(uint i=0; i < baseSize; i+=1) {
            total += data.getElementInArrayType(ArrayType.STAT_BASE, obj.monsterId, i);
            total += safeMult(data.getElementInArrayType(ArrayType.STAT_STEP, uint64(obj.classId), i), getLevel(obj.exp));
        }
        
        return uint64(total/baseSize);
    }
}

File 2 of 2: 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));
    }

}