contract DungeonTransportation is DungeonBase {

    /**
     * @dev The PlayerTransported event is fired when user transported to another dungeon.
     */
    event PlayerTransported(uint timestamp, address indexed playerAddress, uint indexed originDungeonId, uint indexed destinationDungeonId);


    /* ======== GAME SETTINGS ======== */

    /**
     * @notice The actual fee contribution required to call transport() is calculated by this feeMultiplier,
     *  times the dungeon difficulty of destination dungeon. The payment is accumulated to the rewards of the origin dungeon,
     *  and a large proportion will be claimed by whoever successfully challenged the floor.
     *  1000 szabo = 0.001 ether
     */
    uint public transportationFeeMultiplier = 500 szabo;


    /* ======== STORAGE ======== */


    /**
     * @dev A mapping from token IDs to the address that owns them.
     */
    mapping(address => uint) public playerToDungeonID;

    /**
     * @dev A mapping from owner address to count of tokens that address owns.
     */
    mapping(uint => uint) public dungeonPlayerCount;

    /**
     * @dev The main external function to call when a player transport to another dungeon.
     *  Will generate a PlayerTransported event.
     */
    function transport(uint _destinationDungeonId) whenNotPaused dungeonCanTransport(_destinationDungeonId) external payable {
        uint originDungeonId = playerToDungeonID[msg.sender];

        // Disallow transport to the same dungeon.
        require(_destinationDungeonId != originDungeonId);

        // Get the dungeon details from the token contract.
        uint difficulty;
        uint capacity;
        (,, difficulty, capacity,,,,,) = dungeonTokenContract.dungeons(_destinationDungeonId);

        // Disallow weaker user to transport to "difficult" dungeon.
        uint top5HeroesPower = _getTop5HeroesPower(msg.sender, _destinationDungeonId);
        require(top5HeroesPower >= difficulty * 12);

        // Checks for payment, any exceeding funds will be transferred back to the player.
        uint baseFee = difficulty * transportationFeeMultiplier;
        uint additionalFee = top5HeroesPower / 48 * transportationFeeMultiplier;
        uint requiredFee = baseFee + additionalFee;
        require(msg.value >= requiredFee);

        // ** STORAGE UPDATE **
        // Increment the accumulated rewards for the dungeon.
        dungeonTokenContract.addDungeonRewards(originDungeonId, requiredFee);

        // Calculate any excess funds and make it available to be withdrawed by the player.
        asyncSend(msg.sender, msg.value - requiredFee);

        _transport(originDungeonId, _destinationDungeonId);
    }

    /**
     * Private function to assigns location of a player
     */
    function _transport(uint _originDungeonId, uint _destinationDungeonId) private {
        // If a player do not have any hero, claim first hero.
        if (heroTokenContract.balanceOf(msg.sender) == 0) {
            claimHero();
        }

        // ** STORAGE UPDATE **
        // Update the ownershipTokenCount.
        dungeonPlayerCount[_originDungeonId]--;
        dungeonPlayerCount[_destinationDungeonId]++;

        // ** STORAGE UPDATE **
        // Update player location.
        playerToDungeonID[msg.sender] = _destinationDungeonId;

        // Emit the DungeonChallenged event.
        PlayerTransported(now, msg.sender, _originDungeonId, _destinationDungeonId);
    }


    /* ======== OWNERSHIP FUNCTIONS ======== */

    /**
     * @notice Used in transport, challenge and train, to get the genes of a specific hero,
     *  a claim a hero if didn't have any.
     */
    function _getHeroGenesOrClaimFirstHero(uint _heroId) internal returns (uint heroId, uint heroGenes) {
        heroId = _heroId;

        // If a player do not have any hero, claim first hero first.
        if (heroTokenContract.balanceOf(msg.sender) == 0) {
            heroId = claimHero();
        }

        (,,,heroGenes) = heroTokenContract.heroes(heroId);
    }

    /**
     * @dev Claim a new hero with empty genes.
     */
    function claimHero() public returns (uint) {
        // If a player do not tranport to any dungeon yet, and it is the first time claiming the hero,
        // set the dungeon location, increment the #0 Holyland player count by 1.
        if (playerToDungeonID[msg.sender] == 0 && heroTokenContract.balanceOf(msg.sender) == 0) {
            dungeonPlayerCount[0]++;
        }

        return heroTokenContract.createHero(0, msg.sender);
    }


    /* ======== SETTER FUNCTIONS ======== */

    /**
     * @dev Updates the fee contribution multiplier required for calling transport().
     */
    function setTransportationFeeMultiplier(uint _newTransportationFeeMultiplier) onlyOwner external {
        transportationFeeMultiplier = _newTransportationFeeMultiplier;
    }


    /* ======== MODIFIERS ======== */

    /**
     * @dev Throws if dungeon status do not allow transportation, also check for dungeon existence.
     *  Also check if the capacity of the destination dungeon is reached.
     */
    modifier dungeonCanTransport(uint _destinationDungeonId) {
        require(_destinationDungeonId < dungeonTokenContract.totalSupply());
        uint status;
        uint capacity;
        (,status,,capacity,,,,,) = dungeonTokenContract.dungeons(_destinationDungeonId);
        require(status == 0 || status == 1);

        // Check if the capacity of the destination dungeon is reached.
        // Capacity 0 = Infinity
        require(capacity == 0 || dungeonPlayerCount[_destinationDungeonId] < capacity);
        _;
    }

}
function transport(uint _destinationDungeonId) whenNotPaused dungeonCanTransport(_destinationDungeonId) external payable {
    uint originDungeonId = playerToDungeonID[msg.sender];

    // Disallow transport to the same dungeon.
    require(_destinationDungeonId != originDungeonId);

    // Get the dungeon details from the token contract.
    uint difficulty;
    uint capacity;
    (,, difficulty, capacity,,,,,) = dungeonTokenContract.dungeons(_destinationDungeonId);

    // Disallow weaker user to transport to "difficult" dungeon.
    uint top5HeroesPower = _getTop5HeroesPower(msg.sender, _destinationDungeonId);
    require(top5HeroesPower >= difficulty * 12);

    // Checks for payment, any exceeding funds will be transferred back to the player.
    uint baseFee = difficulty * transportationFeeMultiplier;
    uint additionalFee = top5HeroesPower / 48 * transportationFeeMultiplier;
    uint requiredFee = baseFee + additionalFee;
    require(msg.value >= requiredFee);

    // ** STORAGE UPDATE **
    // Increment the accumulated rewards for the dungeon.
    dungeonTokenContract.addDungeonRewards(originDungeonId, requiredFee);

    // Calculate any excess funds and make it available to be withdrawed by the player.
    asyncSend(msg.sender, msg.value - requiredFee);

    _transport(originDungeonId, _destinationDungeonId);
}

/**
 * @dev The main external function to call when a player transport to another dungeon.
 *  Will generate a PlayerTransported event.
 */