<commit_before>'use strict';

var $ = require('jquery');
var _ = require('lodash');

var pilotsWithAbilities = require('../models/pilots').pilotsWithAbilities;
var pilots = _.uniqBy(pilotsWithAbilities, function (pilot) {
    return pilot.text;
});
var upgrades = require('../models/upgrades').keyed;
var modalController = require('../controllers/modals');

module.exports = {
    renderUpgradesList: function (build) {
        // Get a list of the slots allowed for this build (determined by ship and PS) and the number of each upgrade per slot
        var upgradesAllowedInBuild = module.exports.numberOfUsableUpgrades(build.pilotSkill, build.currentShip, build.upgrades);
        var upgrades = module.exports.getAllowedUpgrades(build, upgradesAllowedInBuild);

        var $freeList = $('#free-upgrade-list');
        $freeList.empty();
        if (build.currentShip.startingUpgrades.length > 0) {
            $('.free-upgrades').show();
            // Add starting upgrades to the list
            _.forEach(build.currentShip.startingUpgrades, function (upgrade) {
                var $upgradeItem = module.exports.renderUpgradeItem(upgrade);
                $freeList.append($upgradeItem);
            });
        } else {
            $('.free-upgrades').hide();
        }

        var $allowedList = $('#allowed-upgrade-list');
        $allowedList.empty();
        if (upgrades.allowed.length > 0 || build.pilotAbilities.length > 0) {
            $('.purchased-upgrades .no-upgrades').hide();
            // Add purchased upgrades to the list
            _.forEach(upgrades.allowed, function (upgrade) {
                var $upgradeItem = module.exports.renderUpgradeItem(upgrade);
                $allowedList.append($upgradeItem);
            });
            // Add pilot abilities to the list
            _.forEach(build.pilotAbilities, function (pilotAbility) {
                var $upgradeItem = module.exports.renderPilotUpgradeItem(pilotAbility);
                $allowedList.append($upgradeItem);
            });
        } else {
            $('.purchased-upgrades .no-upgrades').show();
        }

        var $disallowedList = $('#disabled-upgrade-list');
        $disallowedList.empty();
        if (upgrades.disallowed.length > 0) {
            // there's some disabled upgrades here
            $('.disabled-upgrades').show();
            _.forEach(upgrades.disallowed, function (upgrade) {
                var $upgradeItem = module.exports.renderUpgradeItem(upgrade);
                $disallowedList.append($upgradeItem);
            });
        } else {
            $('.disabled-upgrades').hide();
        }

        var $buyNewList = $('#buy-upgrades-list');
        $buyNewList.empty();
        _.forEach(upgradesAllowedInBuild, function (slotInfo, slotType) {
            var $button = module.exports.renderAddUpgradeButton(slotType, build.currentShip, build.upgrades, build.pilotSkill);
            $buyNewList.append($button);
        });

    },
    getAllowedUpgrades: function (build, upgradesAllowedInBuild) {
        var purchasedUpgradesByType = _.clone(build.upgrades, true);

        var allowedUpgrades = [];
        var disallowedUpgrades = [];

        // Populate allowed and disallowed slot lists with the purchased upgrades
        _.forEach(purchasedUpgradesByType, function (upgradesList, slotType) {
            // Find the keys of any slots in the build that match these upgrades
            if (upgradesAllowedInBuild[slotType]) {
                // Allowed in build
                allowedUpgrades = allowedUpgrades.concat(upgradesList);
            } else {
                // This slot not allowed in this build (restricted by ship?)
                disallowedUpgrades = disallowedUpgrades.concat(upgradesList);
            }
        });

        return {
            allowed: allowedUpgrades,
            disallowed: disallowedUpgrades
        };
    },
    renderUpgradeItem: function (upgrade) {
        var imageUrl = '/components/xwing-data/images/' + upgrade.image;
        var $item = $('
' + module.exports.getIconString(upgrade.slot) + '' + upgrade.name + 'remove_red_eye
');
        return $item;
    },
    renderPilotUpgradeItem: function (pilot) {
        var escapedText = pilot.text.replace(/"/g, '"');
        var $item = $('
' + module.exports.getIconString('Elite') + 'Ability: ' + pilot.name + 'remove_red_eye
');

        return $item;
    },
    getIconString: function (upgradeSlotType) {
        var iconId = upgradeSlotType.replace(' ', '').replace('-', '');
        iconId = iconId.toLowerCase();
        var iconString = '';
        return iconString;
    },
    renderAddUpgradeButton: function (upgradeType, currentShip, existingUpgrades, pilotSkill) {
        var $li = $('
');
        var $button = $('<button class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--accent">' + module.exports.getIconString(upgradeType) + '' + upgradeType + '</button>');
        var filteredUpgrades = module.exports.getFilteredUpgrades(upgradeType, existingUpgrades, currentShip);
        // Disable button if nothing to show in modal
        if (_.size(filteredUpgrades) === 0) {
            $button.attr('disabled', 'disabled');
        }

        $button.on('click', function () {
            var $modalContent = module.exports.renderUpgradeModalContent(pilotSkill, filteredUpgrades);
            modalController.openOptionSelectModal($modalContent, 'Buy upgrade');
        });
        $li.append($button);

        return $li;
    },
    getFilteredUpgrades: function (upgradeTypeString, existingUpgrades, currentShip) {
        var upgradeTypes = upgradeTypeString.split(',');

        var filteredUpgradesByType = {};

        _.each(upgradeTypes, function (upgradeType) {
            var upgradesOfType = upgrades[upgradeType];
            var existingUpgradesOfType = existingUpgrades[upgradeType];
            var filteredUpgrades = module.exports.getUpgradesToShow(upgradesOfType, currentShip, existingUpgradesOfType);
            if (filteredUpgrades.length > 0) {
                filteredUpgradesByType[upgradeType] = filteredUpgrades;
            }
        });

        return filteredUpgradesByType;
    },
    renderUpgradeModalContent: function (pilotSkill, filteredUpgradesByType) {
        var tabs = [];
        _.each(filteredUpgradesByType, function (filteredUpgrades, upgradeType) {
            var $tab = module.exports.renderCardListModalContent(upgradeType, filteredUpgrades);
            tabs.push({
                name: upgradeType,
                $content: $tab
            });

            if (upgradeType === 'Elite') {
                var $abilityTab = module.exports.renderPilotAbilityModalContent(pilotSkill);
                tabs.push({
                    name: 'Elite pilot talent cards',
                    $content: $abilityTab
                });
            }
        });

        return module.exports.renderTabs(tabs);
    },
    renderTabs: function (tabsObject) {
        var $modalContent = $('
');

        if (tabsObject.length > 1) {
            // tab link elements
            var $tabsBar = $('
');
            _.each(tabsObject, function (tab) {
                var tabId = tab.$content.attr('id');
                var $tabLink = $('' + tab.name + '');
                $tabsBar.append($tabLink);
            });
            $tabsBar.find('a').first().addClass('is-active');

            // create DOM structure
            $modalContent.addClass('mdl-tabs mdl-js-tabs mdl-js-ripple-effect');
            $modalContent.prepend($tabsBar);
            tabsObject[0].$content.addClass('is-active');
            _.each(tabsObject, function (tab) {
                tab.$content.addClass('mdl-tabs__panel');
            });
        }

        _.each(tabsObject, function (tab) {
            $modalContent.append(tab.$content);
        });

        return $modalContent;
    },
    renderCardListModalContent: function (upgradeType, filteredUpgrades) {
        var $modalContent = $('
');
        var $upgradeList = $('
');

        _.forEach(filteredUpgrades, function (item) {
            var $upgrade = $('

');
            $upgrade.on('click', function () {
                // deselect other list options
                $(this).closest('ul').find('li').removeClass('selected');
                $(this).addClass('selected');
                var $text = $('' + item.name + ': ' + item.hotacPoints + 'XP');
                var $summaryElement = $('.featherlight .summary');
                $summaryElement.html($text);
                $(this).trigger('select', {
                    selectedUpgradeEvent: 'view.upgrades.buy',
                    selectedUpgradeId: item.id
                });
                $(this).closest('.featherlight').find('.modal-footer button').removeAttr('disabled');
            });

            $upgradeList.append($upgrade);
        });

        $modalContent.append($upgradeList);

        return $modalContent;
    },
    getUpgradesToShow: function (upgrades, currentShip, existingUpgrades) {
        var filteredUpgrades = _.filter(upgrades, function (upgrade) {
            // Remove any upgrades for different ships
            if (upgrade.ship && upgrade.ship.indexOf(currentShip.shipData.name) < 0) {
                return false;
            }

            // Remove any upgrades for different ship sizes
            if (upgrade.size && upgrade.size.indexOf(currentShip.shipData.size) < 0) {
                return false;
            }

            // Don't show anything which is a starting upgrade for the ship
            if (currentShip.startingUpgrades) {
                var found = _.find(currentShip.startingUpgrades, function (startingUpgrade) {
                    return startingUpgrade.xws === upgrade.xws;
                });
                if (found) {
                    return false;
                }
            }

            // Remove any upgrades the build already has
            var upgradeExists = _.find(existingUpgrades, function (existingUpgrade) {
                return existingUpgrade.id === upgrade.id;
            });

            if (upgradeExists) {
                var upgradeIsAllowed = false;
                // filter out any upgrades the player already has
                // except
                // * secondary weapons & bombs
                if (upgrade.slot === 'Bomb' || upgrade.slot === 'Torpedo' || upgrade.slot === 'Cannon' || upgrade.slot === 'Turret' || upgrade.slot === 'Missile') {
                    upgradeIsAllowed = true;
                // * hull upgrade and shield upgrade
                } else if (upgrade.xws === 'hullupgrade' || upgrade.xws === 'shieldupgrade') {
                    upgradeIsAllowed = true;
                }
                if (!upgradeIsAllowed) {
                    return false;
                }
            }

            return true;
        });

        return filteredUpgrades;
    },
    renderPilotAbilityModalContent: function (pilotSkill) {
        // Only show pilots of current PS or lower
        var availablePilots = _.filter(pilots, function (pilot) {
            return pilot.skill <= pilotSkill;
        });

        var $modalContent = $('
');
        var $upgradeList = $('
');

        _.forEach(availablePilots, function (pilotCard) {
            var $upgrade = $('

' + pilotCard.name + ' (PS ' + pilotCard.skill + ')

' + pilotCard.text + '

');
            $upgrade.on('click', function () {
                // deselect other list options
                $(this).closest('ul').find('li').removeClass('selected');
                $(this).addClass('selected');
                var $text = $('' + pilotCard.name + ': ' + pilotCard.skill + 'XP');
                var $summaryElement = $('.featherlight .summary');
                $summaryElement.html($text);
                $(this).trigger('select', {
                    selectedUpgradeEvent: 'view.pilotAbilities.buy',
                    selectedUpgradeId: pilotCard.id
                });

                $(this).closest('.featherlight').find('.modal-footer button').removeAttr('disabled');
            });

            $upgradeList.append($upgrade);
        });

        $modalContent.append($upgradeList);

        return $modalContent;
    },
    numberOfUsableUpgrades: function (pilotSkill, currentShip, upgradesByType) {

        // elite slots are dependent on pilot level
        var eliteSlots = 0;
        if (pilotSkill >= 9) {
            eliteSlots = 4;
        } else if (pilotSkill >= 7) {
            eliteSlots = 3;
        } else if (pilotSkill >= 5) {
            eliteSlots = 2;
        } else if (pilotSkill >= 3) {
            eliteSlots = 1;
        }

        // mod slots are dependent on pilot level
        var modSlots = 1;
        if (pilotSkill >= 8) {
            modSlots = 4;
        } else if (pilotSkill >= 6) {
            modSlots = 3;
        } else if (pilotSkill >= 4) {
            modSlots = 2;
        }

        var usableUpgrades = {
            Modification: {
                max: 3,
                allowed: modSlots
            },
            Title: {
                max: 1,
                allowed: 1
            }
        };
        if (eliteSlots > 0) {
            usableUpgrades.Elite = {
                max: 4,
                allowed: eliteSlots
            };
        }

        var addToSlot = function (slotKey, max, allowed) {
            if (!usableUpgrades[slotKey]) {
                usableUpgrades[slotKey] = {
                    max: max,
                    allowed: allowed
                };
            } else {
                usableUpgrades[slotKey].max += max;
                usableUpgrades[slotKey].allowed += allowed;
            }
        };

        // array to track which upgrades we've processed grants for, and prevent infinite loop
        var processedGrantForIds = [];
        var processGrants = function (upgrade) {
            // Is the type of this upgrade allowed on this ship?
            var foundGrant = false;
            if (processedGrantForIds.indexOf(upgrade.id) === -1) {
                // Only process this if we haven't already done so
                var slotType = upgrade.slot;
                if (usableUpgrades[slotType]) {
                    // slot is allowed on ship, so lets process any addiitonal slots the upgrade grants
                    if (upgrade.grants) {
                        _.each(upgrade.grants, function (grant) {
                            if (grant.type === 'slot') {
                                foundGrant = true;
                                addToSlot(grant.name, 1, 1);
                            }
                        });
                    }
                }
            }
            return foundGrant;
        };

        var processGrantsList = function (upgradeList) {
            var found = false;
            // keep looping while we find results. If we find a grant, we have to process the
            // whole list again, in case it affects another upgrade
            do {
                found = _.find(upgradeList, processGrants);
                if (found && found.id) {
                    processedGrantForIds.push(found.id);
                }
            } while (found);
        };

        // Add slots for the ship type
        var upgradeSlots = currentShip.upgradeSlots;
        _.each(upgradeSlots, function (slotType) {
            addToSlot(slotType, 1, 1);
        });

        // Do any starting upgrade grants before the purchased ones
        processGrantsList(currentShip.startingUpgrades);

        // Add slots given by upgrades/starting upgrades
        _.each(upgradesByType, function (upgradesList) {
            processGrantsList(upgradesList);
        });

        return usableUpgrades;
    }
};
<commit_msg>Fix error when ship doesnt have starting upgrades<commit_after>'use strict';

var $ = require('jquery');
var _ = require('lodash');

var pilotsWithAbilities = require('../models/pilots').pilotsWithAbilities;
var pilots = _.uniqBy(pilotsWithAbilities, function (pilot) {
    return pilot.text;
});
var upgrades = require('../models/upgrades').keyed;
var modalController = require('../controllers/modals');

module.exports = {
    renderUpgradesList: function (build) {
        // Get a list of the slots allowed for this build (determined by ship and PS) and the number of each upgrade per slot
        var upgradesAllowedInBuild = module.exports.numberOfUsableUpgrades(build.pilotSkill, build.currentShip, build.upgrades);
        var upgrades = module.exports.getAllowedUpgrades(build, upgradesAllowedInBuild);

        var $freeList = $('#free-upgrade-list');
        $freeList.empty();
        if (build.currentShip.startingUpgrades && build.currentShip.startingUpgrades.length > 0) {
            $('.free-upgrades').show();
            // Add starting upgrades to the list
            _.forEach(build.currentShip.startingUpgrades, function (upgrade) {
                var $upgradeItem = module.exports.renderUpgradeItem(upgrade);
                $freeList.append($upgradeItem);
            });
        } else {
            $('.free-upgrades').hide();
        }

        var $allowedList = $('#allowed-upgrade-list');
        $allowedList.empty();
        if (upgrades.allowed.length > 0 || build.pilotAbilities.length > 0) {
            $('.purchased-upgrades .no-upgrades').hide();
            // Add purchased upgrades to the list
            _.forEach(upgrades.allowed, function (upgrade) {
                var $upgradeItem = module.exports.renderUpgradeItem(upgrade);
                $allowedList.append($upgradeItem);
            });
            // Add pilot abilities to the list
            _.forEach(build.pilotAbilities, function (pilotAbility) {
                var $upgradeItem = module.exports.renderPilotUpgradeItem(pilotAbility);
                $allowedList.append($upgradeItem);
            });
        } else {
            $('.purchased-upgrades .no-upgrades').show();
        }

        var $disallowedList = $('#disabled-upgrade-list');
        $disallowedList.empty();
        if (upgrades.disallowed.length > 0) {
            // there's some disabled upgrades here
            $('.disabled-upgrades').show();
            _.forEach(upgrades.disallowed, function (upgrade) {
                var $upgradeItem = module.exports.renderUpgradeItem(upgrade);
                $disallowedList.append($upgradeItem);
            });
        } else {
            $('.disabled-upgrades').hide();
        }

        var $buyNewList = $('#buy-upgrades-list');
        $buyNewList.empty();
        _.forEach(upgradesAllowedInBuild, function (slotInfo, slotType) {
            var $button = module.exports.renderAddUpgradeButton(slotType, build.currentShip, build.upgrades, build.pilotSkill);
            $buyNewList.append($button);
        });

    },
    getAllowedUpgrades: function (build, upgradesAllowedInBuild) {
        var purchasedUpgradesByType = _.clone(build.upgrades, true);

        var allowedUpgrades = [];
        var disallowedUpgrades = [];

        // Populate allowed and disallowed slot lists with the purchased upgrades
        _.forEach(purchasedUpgradesByType, function (upgradesList, slotType) {
            // Find the keys of any slots in the build that match these upgrades
            if (upgradesAllowedInBuild[slotType]) {
                // Allowed in build
                allowedUpgrades = allowedUpgrades.concat(upgradesList);
            } else {
                // This slot not allowed in this build (restricted by ship?)
                disallowedUpgrades = disallowedUpgrades.concat(upgradesList);
            }
        });

        return {
            allowed: allowedUpgrades,
            disallowed: disallowedUpgrades
        };
    },
    renderUpgradeItem: function (upgrade) {
        var imageUrl = '/components/xwing-data/images/' + upgrade.image;
        var $item = $('
' + module.exports.getIconString(upgrade.slot) + '' + upgrade.name + 'remove_red_eye
');
        return $item;
    },
    renderPilotUpgradeItem: function (pilot) {
        var escapedText = pilot.text.replace(/"/g, '"');
        var $item = $('
' + module.exports.getIconString('Elite') + 'Ability: ' + pilot.name + 'remove_red_eye
');

        return $item;
    },
    getIconString: function (upgradeSlotType) {
        var iconId = upgradeSlotType.replace(' ', '').replace('-', '');
        iconId = iconId.toLowerCase();
        var iconString = '';
        return iconString;
    },
    renderAddUpgradeButton: function (upgradeType, currentShip, existingUpgrades, pilotSkill) {
        var $li = $('
');
        var $button = $('<button class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--accent">' + module.exports.getIconString(upgradeType) + '' + upgradeType + '</button>');
        var filteredUpgrades = module.exports.getFilteredUpgrades(upgradeType, existingUpgrades, currentShip);
        // Disable button if nothing to show in modal
        if (_.size(filteredUpgrades) === 0) {
            $button.attr('disabled', 'disabled');
        }

        $button.on('click', function () {
            var $modalContent = module.exports.renderUpgradeModalContent(pilotSkill, filteredUpgrades);
            modalController.openOptionSelectModal($modalContent, 'Buy upgrade');
        });
        $li.append($button);

        return $li;
    },
    getFilteredUpgrades: function (upgradeTypeString, existingUpgrades, currentShip) {
        var upgradeTypes = upgradeTypeString.split(',');

        var filteredUpgradesByType = {};

        _.each(upgradeTypes, function (upgradeType) {
            var upgradesOfType = upgrades[upgradeType];
            var existingUpgradesOfType = existingUpgrades[upgradeType];
            var filteredUpgrades = module.exports.getUpgradesToShow(upgradesOfType, currentShip, existingUpgradesOfType);
            if (filteredUpgrades.length > 0) {
                filteredUpgradesByType[upgradeType] = filteredUpgrades;
            }
        });

        return filteredUpgradesByType;
    },
    renderUpgradeModalContent: function (pilotSkill, filteredUpgradesByType) {
        var tabs = [];
        _.each(filteredUpgradesByType, function (filteredUpgrades, upgradeType) {
            var $tab = module.exports.renderCardListModalContent(upgradeType, filteredUpgrades);
            tabs.push({
                name: upgradeType,
                $content: $tab
            });

            if (upgradeType === 'Elite') {
                var $abilityTab = module.exports.renderPilotAbilityModalContent(pilotSkill);
                tabs.push({
                    name: 'Elite pilot talent cards',
                    $content: $abilityTab
                });
            }
        });

        return module.exports.renderTabs(tabs);
    },
    renderTabs: function (tabsObject) {
        var $modalContent = $('
');

        if (tabsObject.length > 1) {
            // tab link elements
            var $tabsBar = $('
');
            _.each(tabsObject, function (tab) {
                var tabId = tab.$content.attr('id');
                var $tabLink = $('' + tab.name + '');
                $tabsBar.append($tabLink);
            });
            $tabsBar.find('a').first().addClass('is-active');

            // create DOM structure
            $modalContent.addClass('mdl-tabs mdl-js-tabs mdl-js-ripple-effect');
            $modalContent.prepend($tabsBar);
            tabsObject[0].$content.addClass('is-active');
            _.each(tabsObject, function (tab) {
                tab.$content.addClass('mdl-tabs__panel');
            });
        }

        _.each(tabsObject, function (tab) {
            $modalContent.append(tab.$content);
        });

        return $modalContent;
    },
    renderCardListModalContent: function (upgradeType, filteredUpgrades) {
        var $modalContent = $('
');
        var $upgradeList = $('
');

        _.forEach(filteredUpgrades, function (item) {
            var $upgrade = $('

');
            $upgrade.on('click', function () {
                // deselect other list options
                $(this).closest('ul').find('li').removeClass('selected');
                $(this).addClass('selected');
                var $text = $('' + item.name + ': ' + item.hotacPoints + 'XP');
                var $summaryElement = $('.featherlight .summary');
                $summaryElement.html($text);
                $(this).trigger('select', {
                    selectedUpgradeEvent: 'view.upgrades.buy',
                    selectedUpgradeId: item.id
                });
                $(this).closest('.featherlight').find('.modal-footer button').removeAttr('disabled');
            });

            $upgradeList.append($upgrade);
        });

        $modalContent.append($upgradeList);

        return $modalContent;
    },
    getUpgradesToShow: function (upgrades, currentShip, existingUpgrades) {
        var filteredUpgrades = _.filter(upgrades, function (upgrade) {
            // Remove any upgrades for different ships
            if (upgrade.ship && upgrade.ship.indexOf(currentShip.shipData.name) < 0) {
                return false;
            }

            // Remove any upgrades for different ship sizes
            if (upgrade.size && upgrade.size.indexOf(currentShip.shipData.size) < 0) {
                return false;
            }

            // Don't show anything which is a starting upgrade for the ship
            if (currentShip.startingUpgrades) {
                var found = _.find(currentShip.startingUpgrades, function (startingUpgrade) {
                    return startingUpgrade.xws === upgrade.xws;
                });
                if (found) {
                    return false;
                }
            }

            // Remove any upgrades the build already has
            var upgradeExists = _.find(existingUpgrades, function (existingUpgrade) {
                return existingUpgrade.id === upgrade.id;
            });

            if (upgradeExists) {
                var upgradeIsAllowed = false;
                // filter out any upgrades the player already has
                // except
                // * secondary weapons & bombs
                if (upgrade.slot === 'Bomb' || upgrade.slot === 'Torpedo' || upgrade.slot === 'Cannon' || upgrade.slot === 'Turret' || upgrade.slot === 'Missile') {
                    upgradeIsAllowed = true;
                // * hull upgrade and shield upgrade
                } else if (upgrade.xws === 'hullupgrade' || upgrade.xws === 'shieldupgrade') {
                    upgradeIsAllowed = true;
                }
                if (!upgradeIsAllowed) {
                    return false;
                }
            }

            return true;
        });

        return filteredUpgrades;
    },
    renderPilotAbilityModalContent: function (pilotSkill) {
        // Only show pilots of current PS or lower
        var availablePilots = _.filter(pilots, function (pilot) {
            return pilot.skill <= pilotSkill;
        });

        var $modalContent = $('
');
        var $upgradeList = $('
');

        _.forEach(availablePilots, function (pilotCard) {
            var $upgrade = $('

' + pilotCard.name + ' (PS ' + pilotCard.skill + ')

' + pilotCard.text + '

');
            $upgrade.on('click', function () {
                // deselect other list options
                $(this).closest('ul').find('li').removeClass('selected');
                $(this).addClass('selected');
                var $text = $('' + pilotCard.name + ': ' + pilotCard.skill + 'XP');
                var $summaryElement = $('.featherlight .summary');
                $summaryElement.html($text);
                $(this).trigger('select', {
                    selectedUpgradeEvent: 'view.pilotAbilities.buy',
                    selectedUpgradeId: pilotCard.id
                });

                $(this).closest('.featherlight').find('.modal-footer button').removeAttr('disabled');
            });

            $upgradeList.append($upgrade);
        });

        $modalContent.append($upgradeList);

        return $modalContent;
    },
    numberOfUsableUpgrades: function (pilotSkill, currentShip, upgradesByType) {

        // elite slots are dependent on pilot level
        var eliteSlots = 0;
        if (pilotSkill >= 9) {
            eliteSlots = 4;
        } else if (pilotSkill >= 7) {
            eliteSlots = 3;
        } else if (pilotSkill >= 5) {
            eliteSlots = 2;
        } else if (pilotSkill >= 3) {
            eliteSlots = 1;
        }

        // mod slots are dependent on pilot level
        var modSlots = 1;
        if (pilotSkill >= 8) {
            modSlots = 4;
        } else if (pilotSkill >= 6) {
            modSlots = 3;
        } else if (pilotSkill >= 4) {
            modSlots = 2;
        }

        var usableUpgrades = {
            Modification: {
                max: 3,
                allowed: modSlots
            },
            Title: {
                max: 1,
                allowed: 1
            }
        };
        if (eliteSlots > 0) {
            usableUpgrades.Elite = {
                max: 4,
                allowed: eliteSlots
            };
        }

        var addToSlot = function (slotKey, max, allowed) {
            if (!usableUpgrades[slotKey]) {
                usableUpgrades[slotKey] = {
                    max: max,
                    allowed: allowed
                };
            } else {
                usableUpgrades[slotKey].max += max;
                usableUpgrades[slotKey].allowed += allowed;
            }
        };

        // array to track which upgrades we've processed grants for, and prevent infinite loop
        var processedGrantForIds = [];
        var processGrants = function (upgrade) {
            // Is the type of this upgrade allowed on this ship?
            var foundGrant = false;
            if (processedGrantForIds.indexOf(upgrade.id) === -1) {
                // Only process this if we haven't already done so
                var slotType = upgrade.slot;
                if (usableUpgrades[slotType]) {
                    // slot is allowed on ship, so lets process any addiitonal slots the upgrade grants
                    if (upgrade.grants) {
                        _.each(upgrade.grants, function (grant) {
                            if (grant.type === 'slot') {
                                foundGrant = true;
                                addToSlot(grant.name, 1, 1);
                            }
                        });
                    }
                }
            }
            return foundGrant;
        };

        var processGrantsList = function (upgradeList) {
            var found = false;
            // keep looping while we find results. If we find a grant, we have to process the
            // whole list again, in case it affects another upgrade
            do {
                found = _.find(upgradeList, processGrants);
                if (found && found.id) {
                    processedGrantForIds.push(found.id);
                }
            } while (found);
        };

        // Add slots for the ship type
        var upgradeSlots = currentShip.upgradeSlots;
        _.each(upgradeSlots, function (slotType) {
            addToSlot(slotType, 1, 1);
        });

        // Do any starting upgrade grants before the purchased ones
        processGrantsList(currentShip.startingUpgrades);

        // Add slots given by upgrades/starting upgrades
        _.each(upgradesByType, function (upgradesList) {
            processGrantsList(upgradesList);
        });

        return usableUpgrades;
    }
};
<|endoftext|>


<commit_before>/*jslint vars: true, nomen: true, plusplus: true, white: true */
/*global Pocketry */
(function() {

'use strict';

Pocketry.prototype.draggable = function(container, tileSelector) { // TODO: refactor excessive use of closures
	var tiles = container.querySelectorAll(tileSelector);
	var slotSize = 100; // FIXME: hard-coded
	var self = this;

	Array.prototype.forEach.call(tiles, function(tile) {
		if(tile.getAttribute('data-draggable')) {
			return;
		}
		var draggie = new Draggabilly(tile, { containment: container });
		tile.setAttribute('data-draggable', 'true');
		draggie.on('dragEnd', onDrop);
	});

	function onDrop(draggie, ev, pointer) {
		var p0 = draggie.startPosition;
		var p1 = draggie.position;
		var t0 = determineTile(p0, self.matrix, slotSize);
		var t1 = determineTile(p1, self.matrix, slotSize);
		if(t0 === t1) {
			return;
		}

		// replace with placeholder
		var i0 = self.stack.indexOf(t0);
		var placeholder = Object.create(Pocketry.placeholder);
		placeholder.size = t0.size.slice();
		placeholder.placeholder = 2;
		self.stack.splice(i0, 1, placeholder);
		// re-insert
		if(t1) {
			if(t1.placeholder === 1) {
				// find next non-implicit tile as stack reference
				var startPos = determineSlot(p1, slotSize);
				self.walk(startPos, function(rowIndex, colIndex, span) {
					var topRow = startPos.y - (startPos.y % this.rowSpan); // XXX: breaks encapsulation?
					if(colIndex === startPos.x && rowIndex === topRow) {
						return true;
					}
					var tile = this.matrix[rowIndex + span][colIndex];
					if(tile.placeholder !== 1) {
						t1 = tile;
						return false;
					}
				});
			}
			var i1 = self.stack.indexOf(t1);
			self.stack.splice(i1, t1.placeholder === 2 ? 1 : 0, t0);
		} else { // dropped somewhere after tail
			self.stack.push(t0);
		}

		// re-render
		var stack = self.stack;
		self.init();
		stack.forEach(function(tile) {
			delete tile._processed; // XXX: breaks encapsulation
			self.add(tile, tile.size[0], tile.size[1]);
		});
		self.render(container, slotSize, true);
		draggie.element.parentNode.appendChild(draggie.element); // z-index hack
		self.draggable(container, tileSelector); // XXX: awkward!
	}
};

function determineTile(coords, matrix, slotSize) {
	var slot = determineSlot(coords, slotSize);
	return matrix[slot.y][slot.x];
}

function determineSlot(coords, slotSize) {
	return {
		x: Math.round(coords.x / slotSize),
		y: Math.round(coords.y / slotSize)
	};
}

}());
<commit_msg>ensured dragged element snaps back into place when dropped onto itself<commit_after>/*jslint vars: true, nomen: true, plusplus: true, white: true */
/*global Pocketry */
(function() {

'use strict';

Pocketry.prototype.draggable = function(container, tileSelector) { // TODO: refactor excessive use of closures
	var tiles = container.querySelectorAll(tileSelector);
	var slotSize = 100; // FIXME: hard-coded
	var self = this;

	Array.prototype.forEach.call(tiles, function(tile) {
		if(tile.getAttribute('data-draggable')) {
			return;
		}
		var draggie = new Draggabilly(tile, { containment: container });
		tile.setAttribute('data-draggable', 'true');
		draggie.on('dragEnd', onDrop);
	});

	function onDrop(draggie, ev, pointer) {
		var p0 = draggie.startPosition;
		var p1 = draggie.position;
		var t0 = determineTile(p0, self.matrix, slotSize);
		var t1 = determineTile(p1, self.matrix, slotSize);
		if(t0 === t1) {
			rerender(self, container, slotSize, draggie.element, tileSelector);
			return;
		}

		// replace with placeholder
		var i0 = self.stack.indexOf(t0);
		var placeholder = Object.create(Pocketry.placeholder);
		placeholder.size = t0.size.slice();
		placeholder.placeholder = 2;
		self.stack.splice(i0, 1, placeholder);
		// re-insert
		if(t1) {
			if(t1.placeholder === 1) {
				// find next non-implicit tile as stack reference
				var startPos = determineSlot(p1, slotSize);
				self.walk(startPos, function(rowIndex, colIndex, span) {
					var topRow = startPos.y - (startPos.y % this.rowSpan); // XXX: breaks encapsulation?
					if(colIndex === startPos.x && rowIndex === topRow) {
						return true;
					}
					var tile = this.matrix[rowIndex + span][colIndex];
					if(tile.placeholder !== 1) {
						t1 = tile;
						return false;
					}
				});
			}
			var i1 = self.stack.indexOf(t1);
			self.stack.splice(i1, t1.placeholder === 2 ? 1 : 0, t0);
		} else { // dropped somewhere after tail
			self.stack.push(t0);
		}

		rerender(self, container, slotSize, draggie.element, tileSelector);
	}
};

function rerender(instance, container, slotSize, topNode, tileSelector) { // XXX: terrible signature
	var stack = instance.stack;
	instance.init();
	stack.forEach(function(tile) {
		delete tile._processed; // XXX: breaks encapsulation
		instance.add(tile, tile.size[0], tile.size[1]);
	});

	instance.render(container, slotSize, true);
	topNode.parentNode.appendChild(topNode); // z-index hack
	instance.draggable(container, tileSelector); // XXX: awkward!
}

function determineTile(coords, matrix, slotSize) {
	var slot = determineSlot(coords, slotSize);
	return matrix[slot.y][slot.x];
}

function determineSlot(coords, slotSize) {
	return {
		x: Math.round(coords.x / slotSize),
		y: Math.round(coords.y / slotSize)
	};
}

}());
<|endoftext|>


<commit_before>'use strict';

var $ = require('jquery');
var _ = require('lodash');

var pilotsWithAbilities = require('../models/pilots').pilotsWithAbilities;
var pilots = _.uniqBy(pilotsWithAbilities, function (pilot) {
    return pilot.text;
});
var modalController = require('../controllers/modals');
var events = require('../controllers/events');
var upgradesModel = require('../models/shipBuild/upgrades');
var upgradeSlotsModel = require('../models/shipBuild/upgradeSlots');

module.exports = {
    renderShipSlotsList: function (build) {
        // Process and create list for ship chassis slots
        var $freeShipSlots = $('#ship-slots-free');
        $freeShipSlots.empty();

        // Process and create list for ship chassis slots
        var $shipSlots = $('#ship-slots-default');
        $shipSlots.empty();

        var $ul = $('
');
        _.each(build.currentShip.startingUpgrades, function (upgrade) {
            var upgradeSlot = {
                type: upgrade.slot
            };
            var $li = module.exports.renderShipSlot(upgradeSlot, build, [upgrade]);
            $ul.append($li);
        });
        $freeShipSlots.append($ul);

        var upgradeSlots = upgradeSlotsModel.getShipSlots(build);

        var equippedUpgrades = $.extend(true, [], build.equippedUpgrades.upgrades);
        // var equippedAbilities = $.extend(true, [], build.equippedUpgrades.pilotAbilities);

        // Output allowed slots
        $ul = $('
');
        _.each(upgradeSlots.enabled, function (upgradeSlot) {
            var $li = module.exports.renderShipSlot(upgradeSlot, build, equippedUpgrades);
            $ul.append($li);
        });

        // Output disabled slots
        _.each(upgradeSlots.disabled, function (upgradeSlot) {
            if (build.pilotSkill < upgradeSlot.pilotSkill) {
                var $li = module.exports.renderShipSlot(upgradeSlot, build, equippedUpgrades);
                $ul.append($li);
            }
        });

        $shipSlots.append($ul);

        var $shipSlotsFromUpgrades = $('#ship-slots-upgrades');
        $shipSlotsFromUpgrades.empty();

        if (upgradeSlots.fromUpgrades.length > 0) {
            $ul = $('
');

            _.each(upgradeSlots.fromUpgrades, function (upgradeType) {
                var upgradeSlot = {
                    type: upgradeType
                };
                var $li = module.exports.renderShipSlot(upgradeSlot, build, equippedUpgrades);
                $ul.append($li);
            });

            $shipSlotsFromUpgrades.append($ul);
            $('#ship-slots-upgrades-wrapper').show();
        } else {
            $('#ship-slots-upgrades-wrapper').hide();
        }
    },
    renderShipSlot: function (upgradeSlot, build, equippedUpgrades) {

        var filteredUpgradesByType = upgradesModel.getFilteredUpgrades(upgradeSlot.type, build.upgrades, build.currentShip);

        // Don't show this slot if there are no available upgrades for it (e.g. a title slot for a ship with no titles)
        if (!filteredUpgradesByType || filteredUpgradesByType.length < 1) {
            return;
        }

        var slotHtml = '' + upgradeSlot.type + '';
        if (upgradeSlot.pilotSkill) {
            slotHtml += ' (PS ' + upgradeSlot.pilotSkill + ')';
        }
        var equipped = false;

        var $li = $('

');
        var $slot = $('

');
        $slot.append(module.exports.getIconString(upgradeSlot.type));
        $li.append($slot);

        if (build.pilotSkill < upgradeSlot.pilotSkill) {
            // Disabled
            $slot.addClass('disabled');
            $slot.append(slotHtml);
        } else {
            // Not disabled

            // Is there an equipped upgrade for this slot
            var matchingUpgrade = _.find(equippedUpgrades, function (item) {
                return item.slot === upgradeSlot.type;
            });

            if (matchingUpgrade) {
                equipped = true;
                slotHtml = '' + matchingUpgrade.name + '';
                _.remove(equippedUpgrades, function (item) {
                    return item.id === matchingUpgrade.id;
                });
            }

            if (upgradeSlot.type === 'Elite') {
                // check for abilities too
            }

            $slot.append(slotHtml);

            if (equipped) {
                var imageUrl = '/components/xwing-data/images/' + matchingUpgrade.image;
                $slot.append('remove_red_eye');
                $slot.attr('data-featherlight', imageUrl);
                // var $item = $('
' + upgrade.name + 'remove_red_eye
');

                var $icon = $('remove_circle_outline');
                $li.append($icon);
                $slot.addClass('equipped');
                $icon.on('click', function () {
                    module.exports.removeEquipSlot(matchingUpgrade.id, build);
                });
            } else {
                $slot.on('click', function () {
                    module.exports.clickEquipSlot(upgradeSlot.type, build.upgrades[upgradeSlot.type], filteredUpgradesByType, build);
                });
            }
        }

        return $li;
    },
    renderUpgradesList: function (build) {
        // Get a list of the slots allowed for this build (determined by ship and PS) and the number of each upgrade per slot
        var upgrades = upgradesModel.getAllowedUpgrades(build);

        var $allowedList = $('#allowed-upgrade-list');
        $allowedList.empty();
        if (upgrades.allowed.length > 0 || build.pilotAbilities.length > 0) {
            $('.purchased-upgrades .no-upgrades').hide();
            // Add purchased upgrades to the list
            _.forEach(upgrades.allowed, function (upgrade) {
                var $upgradeItem = module.exports.renderUpgradeItem(upgrade);
                $allowedList.append($upgradeItem);
            });
            // Add pilot abilities to the list
            _.forEach(build.pilotAbilities, function (pilotAbility) {
                var $upgradeItem = module.exports.renderPilotUpgradeItem(pilotAbility);
                $allowedList.append($upgradeItem);
            });
        } else {
            $('.purchased-upgrades .no-upgrades').show();
        }

        var $disallowedList = $('#disabled-upgrade-list');
        $disallowedList.empty();
        if (upgrades.disallowed.length > 0) {
            // there's some disabled upgrades here
            $('.disabled-upgrades').show();
            _.forEach(upgrades.disallowed, function (upgrade) {
                var $upgradeItem = module.exports.renderUpgradeItem(upgrade);
                $disallowedList.append($upgradeItem);
            });
        } else {
            $('.disabled-upgrades').hide();
        }
    },
    renderUpgradeItem: function (upgrade) {
        var imageUrl = '/components/xwing-data/images/' + upgrade.image;
        var $item = $('
' + module.exports.getIconString(upgrade.slot) + '' + upgrade.name + 'remove_red_eye
');
        return $item;
    },
    renderPilotUpgradeItem: function (pilot) {
        var escapedText = pilot.text.replace(/"/g, '"');
        var $item = $('
' + module.exports.getIconString('Elite') + 'Ability: ' + pilot.name + 'remove_red_eye
');

        return $item;
    },
    getIconString: function (upgradeSlotType) {
        var iconId = upgradeSlotType.replace(' ', '').replace('-', '');
        iconId = iconId.toLowerCase();
        var iconString = '';
        return iconString;
    },
    clickEquipSlot: function (upgradeType, equippedUpgradesByType, filteredUpgrades, build) {
        // open modal to choose upgrade to equip
        var $modalContent = module.exports.renderUpgradeModalContent(build, upgradeType, filteredUpgrades, equippedUpgradesByType);
        modalController.openOptionSelectModal($modalContent, 'Buy upgrade');
    },
    removeEquipSlot: function (upgradeId) {
        events.trigger('view.unequip.upgrade', upgradeId);
    },
    renderUpgradeModalContent: function (build, upgradeType, filteredUpgradesByType, equippedUpgrades) {
        var tabs = [];

        var $equippedUpgradesTab = module.exports.renderEquippedCardListModalContent(upgradeType, build, equippedUpgrades);
        tabs.push({
            name: 'Existing ' + upgradeType,
            $content: $equippedUpgradesTab
        });

        var $tab = module.exports.renderCardListModalContent(upgradeType, build, filteredUpgradesByType);
        var tabName = 'Buy new ' + upgradeType;
        if (upgradeType === 'Elite') {
            tabName = 'Elite cards';
        }
        tabs.push({
            name: tabName,
            $content: $tab
        });

        if (upgradeType === 'Elite') {
            var $abilityTab = module.exports.renderPilotAbilityModalContent(build);
            tabs.push({
                name: 'Pilot abilities',
                $content: $abilityTab
            });
        }

        return module.exports.renderTabs(tabs);
    },
    renderTabs: function (tabsObject) {
        var $modalContent = $('
');

        if (tabsObject.length > 1) {
            // tab link elements
            var $tabsBar = $('
');
            _.each(tabsObject, function (tab) {
                var tabId = tab.$content.attr('id');
                var $tabLink = $('' + tab.name + '');
                $tabsBar.append($tabLink);
            });
            $tabsBar.find('a').first().addClass('is-active');

            // create DOM structure
            $modalContent.addClass('mdl-tabs mdl-js-tabs mdl-js-ripple-effect');
            $modalContent.prepend($tabsBar);
            tabsObject[0].$content.addClass('is-active');
            _.each(tabsObject, function (tab) {
                tab.$content.addClass('mdl-tabs__panel');
            });
        }

        _.each(tabsObject, function (tab) {
            $modalContent.append(tab.$content);
        });

        return $modalContent;
    },
    renderCardListModalContent: function (upgradeType, build, filteredUpgrades) {
        var $modalContent = $('
');
        var $upgradeList = $('
');

        _.forEach(filteredUpgrades, function (item) {
            var $upgrade = $('

');
            if (build.currentXp >= item.points) {
                // We have enough XP to buy this item
                $upgrade.on('click', function () {
                    $(this).trigger('select', {
                        selectedUpgradeEvent: 'view.upgrades.buy',
                        selectedUpgradeId: item.id,
                        text: item.name + ': ' + item.hotacPoints + 'XP'
                    });
                });
            } else {
                // not enough XP
                $upgrade.addClass('cannot-afford');
            }

            $upgradeList.append($upgrade);
        });

        $modalContent.append($upgradeList);

        return $modalContent;
    },
    renderEquippedCardListModalContent: function (upgradeType, build, equippedUpgrades) {
        var $modalContent = $('
');
        var $upgradeList = $('
');

        _.forEach(equippedUpgrades, function (item) {
            var $upgrade = $('

');

            // We have enough XP to buy this item
            $upgrade.on('click', function () {
                $(this).trigger('select', {
                    selectedUpgradeEvent: 'view.equip.upgrade',
                    selectedUpgradeId: item.id,
                    text: item.name
                });
            });

            $upgradeList.append($upgrade);
        });

        $modalContent.append($upgradeList);

        return $modalContent;
    },
    renderPilotAbilityModalContent: function (build) {
        var $modalContent = $('
');
        var $upgradeList = $('
');

        _.forEach(pilots, function (pilotCard) {
            var upgradeCost = pilotCard.skill;
            var $upgrade = $('

' + pilotCard.name + ' (' + upgradeCost + 'XP)

' + pilotCard.text + '

');
            if (build.currentXp >= upgradeCost) {

                if (build.pilotSkill >= pilotCard.skill) {
                    // We have enough XP to buy this item
                    $upgrade.on('click', function () {
                        $(this).trigger('select', {
                            selectedUpgradeEvent: 'view.pilotAbilities.buy',
                            selectedUpgradeId: pilotCard.id,
                            text: pilotCard.name + ': ' + pilotCard.skill + 'XP'
                        });
                    });
                } else {
                    // not high enough PS level yet
                    $upgrade.addClass('disabled');
                }
            } else {
                // not enough XP
                $upgrade.addClass('cannot-afford');
            }

            $upgradeList.append($upgrade);
        });

        $modalContent.append($upgradeList);

        return $modalContent;
    }
};
<commit_msg>Allow equipping/unequipping free upgardes<commit_after>'use strict';

var $ = require('jquery');
var _ = require('lodash');

var pilotsWithAbilities = require('../models/pilots').pilotsWithAbilities;
var pilots = _.uniqBy(pilotsWithAbilities, function (pilot) {
    return pilot.text;
});
var modalController = require('../controllers/modals');
var events = require('../controllers/events');
var upgradesModel = require('../models/shipBuild/upgrades');
var upgradeSlotsModel = require('../models/shipBuild/upgradeSlots');

module.exports = {
    renderShipSlotsList: function (build) {
        var equippedUpgrades = $.extend(true, [], build.equippedUpgrades.upgrades);
        // var equippedAbilities = $.extend(true, [], build.equippedUpgrades.pilotAbilities);

        // Process and create list for ship chassis slots
        var $freeShipSlots = $('#ship-slots-free');
        $freeShipSlots.empty();

        var $ul = $('
');
        _.each(build.currentShip.startingUpgrades, function (upgrade) {
            var $li = module.exports.renderFreeShipSlot(upgrade, build, equippedUpgrades);
            $ul.append($li);
        });
        $freeShipSlots.append($ul);

        // Process and create list for ship chassis slots
        var $shipSlots = $('#ship-slots-default');
        $shipSlots.empty();

        var upgradeSlots = upgradeSlotsModel.getShipSlots(build);
        // Output allowed slots
        $ul = $('
');
        _.each(upgradeSlots.enabled, function (upgradeSlot) {
            var $li = module.exports.renderShipSlot(upgradeSlot, build, equippedUpgrades);
            $ul.append($li);
        });

        // Output disabled slots
        _.each(upgradeSlots.disabled, function (upgradeSlot) {
            if (build.pilotSkill < upgradeSlot.pilotSkill) {
                var $li = module.exports.renderShipSlot(upgradeSlot, build, equippedUpgrades);
                $ul.append($li);
            }
        });

        $shipSlots.append($ul);

        var $shipSlotsFromUpgrades = $('#ship-slots-upgrades');
        $shipSlotsFromUpgrades.empty();

        if (upgradeSlots.fromUpgrades.length > 0) {
            $ul = $('
');

            _.each(upgradeSlots.fromUpgrades, function (upgradeType) {
                var upgradeSlot = {
                    type: upgradeType
                };
                var $li = module.exports.renderShipSlot(upgradeSlot, build, equippedUpgrades);
                $ul.append($li);
            });

            $shipSlotsFromUpgrades.append($ul);
            $('#ship-slots-upgrades-wrapper').show();
        } else {
            $('#ship-slots-upgrades-wrapper').hide();
        }
    },
    renderShipSlot: function (upgradeSlot, build, equippedUpgrades) {

        var filteredUpgradesByType = upgradesModel.getFilteredUpgrades(upgradeSlot.type, build.upgrades, build.currentShip);

        // Don't show this slot if there are no available upgrades for it (e.g. a title slot for a ship with no titles)
        if (!filteredUpgradesByType || filteredUpgradesByType.length < 1) {
            return;
        }

        var slotHtml = '' + upgradeSlot.type + '';
        if (upgradeSlot.pilotSkill) {
            slotHtml += ' (PS ' + upgradeSlot.pilotSkill + ')';
        }
        var equipped = false;

        var $li = $('

');
        var $slot = $('

');
        $slot.append(module.exports.getIconString(upgradeSlot.type));
        $li.append($slot);

        if (build.pilotSkill < upgradeSlot.pilotSkill) {
            // Disabled
            $slot.addClass('disabled');
            $slot.append(slotHtml);
        } else {
            // Not disabled

            // Is there an equipped upgrade for this slot
            var matchingUpgrade = _.find(equippedUpgrades, function (item) {
                return item.slot === upgradeSlot.type;
            });

            if (matchingUpgrade) {
                equipped = true;
                slotHtml = '' + matchingUpgrade.name + '';
                _.remove(equippedUpgrades, function (item) {
                    return item.id === matchingUpgrade.id;
                });
            }

            if (upgradeSlot.type === 'Elite') {
                // check for abilities too
            }

            $slot.append(slotHtml);

            if (equipped) {
                var imageUrl = '/components/xwing-data/images/' + matchingUpgrade.image;
                $slot.append('remove_red_eye');
                $slot.attr('data-featherlight', imageUrl);
                var $icon = $('remove_circle_outline');
                $li.append($icon);
                $slot.addClass('equipped');
                $icon.on('click', function () {
                    module.exports.removeEquipSlot(matchingUpgrade.id, build);
                });
            } else {
                $slot.on('click', function () {
                    module.exports.clickEquipSlot(upgradeSlot.type, build.upgrades[upgradeSlot.type], filteredUpgradesByType, build);
                });
            }
        }

        return $li;
    },
    renderFreeShipSlot: function (upgrade, build, equippedUpgrades) {

        var slotHtml = '' + upgrade.name + '';
        var equipped = false;

        var $li = $('

');
        var $slot = $('

');
        $slot.append(module.exports.getIconString(upgrade.slot));
        $li.append($slot);

        // Is there an equipped upgrade that matches this free one?
        var matchingUpgrade = _.find(equippedUpgrades, function (item) {
            return item.id === upgrade.id;
        });

        if (matchingUpgrade) {
            equipped = true;
            _.remove(equippedUpgrades, function (item) {
                return item.id === matchingUpgrade.id;
            });
        }

        $slot.append(slotHtml);
        var $icon = $('remove_circle_outline');

        if (equipped) {
            var imageUrl = '/components/xwing-data/images/' + matchingUpgrade.image;
            $slot.append('remove_red_eye');
            $slot.attr('data-featherlight', imageUrl);
            $li.append($icon);
            $slot.addClass('equipped');
            $icon.on('click', function () {
                events.trigger('view.unequip.upgrade', upgrade.id);
            });
        } else {
            $icon = $('add_circle_outline');
            $li.append($icon);
            $icon.on('click', function () {
                events.trigger('view.equip.upgrade', upgrade.id);
            });
        }

        return $li;
    },
    renderUpgradesList: function (build) {
        // Get a list of the slots allowed for this build (determined by ship and PS) and the number of each upgrade per slot
        var upgrades = upgradesModel.getAllowedUpgrades(build);

        var $allowedList = $('#allowed-upgrade-list');
        $allowedList.empty();
        if (upgrades.allowed.length > 0 || build.pilotAbilities.length > 0) {
            $('.purchased-upgrades .no-upgrades').hide();
            // Add purchased upgrades to the list
            _.forEach(upgrades.allowed, function (upgrade) {
                var $upgradeItem = module.exports.renderUpgradeItem(upgrade);
                $allowedList.append($upgradeItem);
            });
            // Add pilot abilities to the list
            _.forEach(build.pilotAbilities, function (pilotAbility) {
                var $upgradeItem = module.exports.renderPilotUpgradeItem(pilotAbility);
                $allowedList.append($upgradeItem);
            });
        } else {
            $('.purchased-upgrades .no-upgrades').show();
        }

        var $disallowedList = $('#disabled-upgrade-list');
        $disallowedList.empty();
        if (upgrades.disallowed.length > 0) {
            // there's some disabled upgrades here
            $('.disabled-upgrades').show();
            _.forEach(upgrades.disallowed, function (upgrade) {
                var $upgradeItem = module.exports.renderUpgradeItem(upgrade);
                $disallowedList.append($upgradeItem);
            });
        } else {
            $('.disabled-upgrades').hide();
        }
    },
    renderUpgradeItem: function (upgrade) {
        var imageUrl = '/components/xwing-data/images/' + upgrade.image;
        var $item = $('
' + module.exports.getIconString(upgrade.slot) + '' + upgrade.name + 'remove_red_eye
');
        return $item;
    },
    renderPilotUpgradeItem: function (pilot) {
        var escapedText = pilot.text.replace(/"/g, '"');
        var $item = $('
' + module.exports.getIconString('Elite') + 'Ability: ' + pilot.name + 'remove_red_eye
');

        return $item;
    },
    getIconString: function (upgradeSlotType) {
        var iconId = upgradeSlotType.replace(' ', '').replace('-', '');
        iconId = iconId.toLowerCase();
        var iconString = '';
        return iconString;
    },
    clickEquipSlot: function (upgradeType, equippedUpgradesByType, filteredUpgrades, build) {
        // open modal to choose upgrade to equip
        var $modalContent = module.exports.renderUpgradeModalContent(build, upgradeType, filteredUpgrades, equippedUpgradesByType);
        modalController.openOptionSelectModal($modalContent, 'Buy upgrade');
    },
    removeEquipSlot: function (upgradeId) {
        events.trigger('view.unequip.upgrade', upgradeId);
    },
    renderUpgradeModalContent: function (build, upgradeType, filteredUpgradesByType, equippedUpgrades) {
        var tabs = [];

        var $equippedUpgradesTab = module.exports.renderEquippedCardListModalContent(upgradeType, build, equippedUpgrades);
        tabs.push({
            name: 'Existing ' + upgradeType,
            $content: $equippedUpgradesTab
        });

        var $tab = module.exports.renderCardListModalContent(upgradeType, build, filteredUpgradesByType);
        var tabName = 'Buy new ' + upgradeType;
        if (upgradeType === 'Elite') {
            tabName = 'Elite cards';
        }
        tabs.push({
            name: tabName,
            $content: $tab
        });

        if (upgradeType === 'Elite') {
            var $abilityTab = module.exports.renderPilotAbilityModalContent(build);
            tabs.push({
                name: 'Pilot abilities',
                $content: $abilityTab
            });
        }

        return module.exports.renderTabs(tabs);
    },
    renderTabs: function (tabsObject) {
        var $modalContent = $('
');

        if (tabsObject.length > 1) {
            // tab link elements
            var $tabsBar = $('
');
            _.each(tabsObject, function (tab) {
                var tabId = tab.$content.attr('id');
                var $tabLink = $('' + tab.name + '');
                $tabsBar.append($tabLink);
            });
            $tabsBar.find('a').first().addClass('is-active');

            // create DOM structure
            $modalContent.addClass('mdl-tabs mdl-js-tabs mdl-js-ripple-effect');
            $modalContent.prepend($tabsBar);
            tabsObject[0].$content.addClass('is-active');
            _.each(tabsObject, function (tab) {
                tab.$content.addClass('mdl-tabs__panel');
            });
        }

        _.each(tabsObject, function (tab) {
            $modalContent.append(tab.$content);
        });

        return $modalContent;
    },
    renderCardListModalContent: function (upgradeType, build, filteredUpgrades) {
        var $modalContent = $('
');
        var $upgradeList = $('
');

        _.forEach(filteredUpgrades, function (item) {
            var $upgrade = $('

');
            if (build.currentXp >= item.points) {
                // We have enough XP to buy this item
                $upgrade.on('click', function () {
                    $(this).trigger('select', {
                        selectedUpgradeEvent: 'view.upgrades.buy',
                        selectedUpgradeId: item.id,
                        text: item.name + ': ' + item.hotacPoints + 'XP'
                    });
                });
            } else {
                // not enough XP
                $upgrade.addClass('cannot-afford');
            }

            $upgradeList.append($upgrade);
        });

        $modalContent.append($upgradeList);

        return $modalContent;
    },
    renderEquippedCardListModalContent: function (upgradeType, build, equippedUpgrades) {
        var $modalContent = $('
');
        var $upgradeList = $('
');

        _.forEach(equippedUpgrades, function (item) {
            var $upgrade = $('

');

            // We have enough XP to buy this item
            $upgrade.on('click', function () {
                $(this).trigger('select', {
                    selectedUpgradeEvent: 'view.equip.upgrade',
                    selectedUpgradeId: item.id,
                    text: item.name
                });
            });

            $upgradeList.append($upgrade);
        });

        $modalContent.append($upgradeList);

        return $modalContent;
    },
    renderPilotAbilityModalContent: function (build) {
        var $modalContent = $('
');
        var $upgradeList = $('
');

        _.forEach(pilots, function (pilotCard) {
            var upgradeCost = pilotCard.skill;
            var $upgrade = $('

' + pilotCard.name + ' (' + upgradeCost + 'XP)

' + pilotCard.text + '

');
            if (build.currentXp >= upgradeCost) {

                if (build.pilotSkill >= pilotCard.skill) {
                    // We have enough XP to buy this item
                    $upgrade.on('click', function () {
                        $(this).trigger('select', {
                            selectedUpgradeEvent: 'view.pilotAbilities.buy',
                            selectedUpgradeId: pilotCard.id,
                            text: pilotCard.name + ': ' + pilotCard.skill + 'XP'
                        });
                    });
                } else {
                    // not high enough PS level yet
                    $upgrade.addClass('disabled');
                }
            } else {
                // not enough XP
                $upgrade.addClass('cannot-afford');
            }

            $upgradeList.append($upgrade);
        });

        $modalContent.append($upgradeList);

        return $modalContent;
    }
};
<|endoftext|>


<commit_before>// @flow
import React, {Component} from 'react';
import {findDOMNode} from 'react-dom';
import cssModules from 'react-css-modules';
import type {WorkType, UserType} from '../actions/type';
import Infinite from './infinite';
import ImageBox from './image-box';
import styles from './list.css';

type Props = {
	works: Array<WorkType>,
	users: Array<UserType>,
	title: string,
	onClick: (id: string) => void,
	onNextPage: () => void
};

class List extends Component {
	props: Props;
	target: Component<*, *, *>

	handleScrollTop = (e: Event) => {
		e.preventDefault();
		const node = findDOMNode(this.target);
		node.scrollTop = 0;
	};

	render() {
		const List = this.props.works.map(work => {
			const user = this.props.users[work.user];
			return (
				<ImageBox
					key={work.id}
					work={work}
					user={user}
					onClick={this.props.onClick}
					/>
			);
		});

		return (

						{this.props.title}

					<Infinite
						ref={c => {
							this.target = c;
						}}
						onIntersect={() => this.props.onNextPage()}
						>
						{List}
					</Infinite>

		);
	}
}

export default cssModules(List, styles);
<commit_msg>show close button in list<commit_after>// @flow
import React, {Component} from 'react';
import {findDOMNode} from 'react-dom';
import cssModules from 'react-css-modules';
import type {WorkType, UserType} from '../actions/type';
import Infinite from './infinite';
import ImageBox from './image-box';
import CloseButton from './close-button';
import styles from './list.css';

type Props = {
	works: Array<WorkType>,
	users: Array<UserType>,
	title: string,
	onClick: (id: string) => void,
	onNextPage: () => void
};

class List extends Component {
	props: Props;
	target: Component<*, *, *>

	handleScrollTop = (e: Event) => {
		e.preventDefault();
		const node = findDOMNode(this.target);
		node.scrollTop = 0;
	};

	handleClose = () => {
	}

	render() {
		const List = this.props.works.map(work => {
			const user = this.props.users[work.user];
			return (
				<ImageBox
					key={work.id}
					work={work}
					user={user}
					onClick={this.props.onClick}
					/>
			);
		});

		return (

						{this.props.title}

					<CloseButton onClose={this.handleClose}/>

					<Infinite
						ref={c => {
							this.target = c;
						}}
						onIntersect={() => this.props.onNextPage()}
						>
						{List}
					</Infinite>

		);
	}
}

export default cssModules(List, styles);
<|endoftext|>


<commit_before>const C3 = require('./c3')
const idify = require('./idify')
const { parseNoun } = require('./parse')

// TODO: this file needs some refactoring

const NO_MATCH = 0
const EXACT_MATCH = 1
const PARTIAL_MATCH = 2

function linearize (object, linearization = new C3(object)) {
  object.traits.forEach((trait, index) => {
    if (trait !== undefined) {
      linearization.add(object, trait)
      linearize(trait, linearization)
    } else {
      // Attempt at gracefully handle a broken trait chain
      // e.g. the parent object was destroyed
      object.traits.splice(index, 1)
    }
  })
  return linearization.run()
}

function match (name, search) {
  if (search === undefined) {
    return EXACT_MATCH
  }

  const xl = name.toLowerCase()
  const yl = search.toLowerCase()
  if (xl === yl) {
    return EXACT_MATCH
  }
  if (xl.indexOf(yl) === 0) {
    return PARTIAL_MATCH
  }
  return NO_MATCH
}

function getMatchesWithDeterminer (foundMatches, determiner, ambiguous, fail) {
  const length = foundMatches.length
  if (determiner === undefined) { // one, definite
    if (length === 1) {
      return foundMatches[0][1]
    }
    return ambiguous
  } else if (determiner === 'any') { // one, indefinite => random
    return foundMatches[Math.floor(Math.random() * length)][1]
  } else if (determiner === 'all') { // all
    return foundMatches.map(object => object[1])
  }
  // one, by ordinal rank
  const n = Number(determiner) - 1
  if (!isNaN(n) && n < length) {
    return foundMatches[n][1]
  }
  return fail
}

function matchPattern (pattern, str) {
  let patternParts
  let rest

  if (pattern === '*') {
    return true
  }
  if (pattern.indexOf('*') !== -1) {
    patternParts = pattern.split('*')
    if (str === patternParts[0]) {
      return true
    }
    if (str.indexOf(patternParts[0]) === 0) {
      if (patternParts[1] === '') {
        return true
      }
      rest = str.slice(patternParts[0].length, str.length + 1)
      if (patternParts[1].indexOf(rest) === 0) {
        return true
      }
    }
  } else if (pattern === str) {
    return true
  }
  return false
}

function matcheVerbPattern (pattern, str) {
  return pattern.split(' ').reduce((last, p) =>
    last || matchPattern(p, str)
  , false)
}

function verbMatchesCommand (verb, command, objects, self) {
  if (!matcheVerbPattern(verb.pattern, command.verb)) {
    return false
  }
  switch (verb.dobjarg) {
    case 'none':
      if (typeof command.dobjstr !== 'undefined') {
        return false
      }
      break
    case 'this':
      if (objects.dobj !== self) {
        return false
      }
      break
    default:
      break
  }
  switch (verb.iobjarg) {
    case 'none':
      if (typeof command.iobjstr !== 'undefined') {
        return false
      }
      break
    case 'this':
      if (objects.iobj !== self) {
        return false
      }
      break
    default:
      break
  }
  switch (verb.preparg) {
    case 'none':
      if (typeof command.prepstr !== 'undefined') {
        return false
      }
      break
    case 'any':
      return true
    default:
      if (verb.preparg.split('/').indexOf(command.prepstr) < 0) {
        return false
      }
  }
  return true
}

class WorldObjectClassBuilder {
  constructor (db, world, controllerMap) {
    this.db = db
    this.world = world
    this.controllerMap = controllerMap
  }

  buildClass () {
    const db = this.db
    const world = this.world
    const controllerMap = this.controllerMap

    return class WorldObject {
      constructor (properties) {
        for (const key in properties) {
          this[key] = properties[key]
        }
      }

      get online () {
        return !!controllerMap.get(this.id)
      }

      toString () {
        return `[object ${this.id}]`
      }

      send (msg) {
        const controller = controllerMap.get(this.id)
        if (controller) {
          controller.emit('output', msg)
          return true
        }
        return false
      }

      setPrompt (str) {
        const controller = controllerMap.get(this.id)
        if (controller) {
          controller.emit('set-prompt', str)
          return true
        }
        return false
      }

      addAlias (...args) {
        const aliases = this.aliases
        const ret = aliases.push(...args)
        this.aliases = aliases
        return ret
      }

      rmAlias (...args) {
        this.aliases = this.aliases.filter(a => args.indexOf(a) === -1)
        return this.aliases.length
      }

      addTrait (...args) {
        const traits = this.traits
        const ret = traits.push(...args)
        this.traits = traits
        return ret
      }

      rmTrait (...args) {
        this.traits = this.traits.filter(a => args.indexOf(a) === -1)
        return this.traits.length
      }

      linearize () {
        return linearize(this)
      }

      instanceOf (object) {
        return this.linearize().indexOf(object) !== -1
      }

      keys () {
        const objects = this.linearize()
        const propertySet = new Set()
        objects.forEach(object => {
          Reflect.ownKeys(object).forEach(key => { propertySet.add(key) })
        })
        return [...propertySet.values()]
      }

      values () {
        return this.keys().map(k => this[k])
      }

      new (idraw, props = {}) {
        // Sanitize the id, in case it wasn't previously checked with nextId()
        const id = idify(idraw)

        if (world.get(id) || id in world.context) {
          throw new TypeError(`Identifier '${id}' has already been declared`)
        }

        const newObj = {
          id,
          name: id,
          aliases: [],
          traitIds: [this.id],
          locationId: null,
          properties: {}
        }

        db.insert(newObj)
        world.insert(newObj)
        const object = world.get(id)

        for (const key in props) {
          if ({}.hasOwnProperty.call(props, key)) {
            object[key] = props[key]
          }
        }

        return object
      }

      destroy () {
        if (this.player && this.online) {
          throw new Error(`${this.id} is a player and is online, therefore cannot be destroyed.`)
        }

        db.removeById(this.id)
        world.removeById(this.id)

        return true
      }

      matchObjects (command) {
        return {
          dobj: command.dobjstr ? this.findObject(command.dobjstr) : world.get('nothing'),
          iobj: command.iobjstr ? this.findObject(command.iobjstr) : world.get('nothing')
        }
      }

      findObject (search) {
        if (search === 'me' || search === 'myself') {
          return this
        } else if (search === 'here') {
          return this.location
        }
        return this.findNearby(search)
      }

      findNearby (search) {
        if (search === '') {
          return world.get('fail')
        }

        let searchItems = []

        // Stuff inside this object.
        searchItems = searchItems.concat(this.contents)

        if (this.location) {
          // Stuff in the location this object is in.
          searchItems = searchItems.concat(this.location.contents)

          // Maybe add extra match objects from the location this object is in.
          let extraMatchObjects = []

          if (Array.isArray(this.location.extraMatchObjects)) {
            extraMatchObjects = this.location.extraMatchObjects
          }

          if (typeof this.location.extraMatchObjects === 'function') {
            const potentialExtraMatchObjects = this.location.extraMatchObjects()
            if (Array.isArray(potentialExtraMatchObjects)) {
              extraMatchObjects = potentialExtraMatchObjects
            }
          }

          searchItems = searchItems.concat(extraMatchObjects)
        }

        searchItems = searchItems.filter(object => object !== this)

        const [determiner, noun] = parseNoun(search)

        const potentialMatches = searchItems.map(object => [object.matches(noun), object])
        const exactMatches = potentialMatches.filter(m => m[0] === EXACT_MATCH)
        const partialMatches = potentialMatches.filter(m => m[0] === PARTIAL_MATCH)

        return WorldObject.findMatch(exactMatches, partialMatches, determiner)
      }

      findInside (search) {
        if (search === '') {
          return world.get('fail')
        }

        const [determiner, noun] = parseNoun(search)
        const potentialMatches = this.contents.map(object => [object.matches(noun), object])
        const exactMatches = potentialMatches.filter(m => m[0] === EXACT_MATCH)
        const partialMatches = potentialMatches.filter(m => m[0] === PARTIAL_MATCH)

        return WorldObject.findMatch(exactMatches, partialMatches, determiner)
      }

      static findMatch (partialMatches, exactMatches, determiner) {
        if (exactMatches.length > 0) {
          const result = getMatchesWithDeterminer(exactMatches, determiner,
            world.get('ambiguous'), world.get('fail'))
          // For now, treat collections as ambiguous
          return Array.isArray(result) ? world.get('ambiguous') : result
        }

        if (partialMatches.length > 0) {
          const result = getMatchesWithDeterminer(partialMatches, determiner,
            world.get('ambiguous'), world.get('fail'))
          // For now, treat collections as ambiguous
          return Array.isArray(result) ? world.get('ambiguous') : result
        }

        return world.get('fail')
      }

      matches (search) {
        const matches = this.aliases.concat([this.name]).map(name => match(name, search))

        if (matches.indexOf(EXACT_MATCH) >= 0) {
          return EXACT_MATCH
        }
        if (matches.indexOf(PARTIAL_MATCH) >= 0) {
          return PARTIAL_MATCH
        }
        return NO_MATCH
      }

      matchVerb (command, objects) {
        let verb

        verb = this.findVerb(command, objects)
        if (verb) {
          return { verb, this: this }
        }

        if (this.location) {
          verb = this.location.findVerb(command, objects)
          if (verb) {
            return { verb, this: this.location }
          }
        }

        if ((objects.dobj) && !Array.isArray(objects.dobj)) {
          verb = objects.dobj.findVerb(command, objects)
          if (verb) {
            return { verb, this: objects.dobj }
          }
        }

        if ((objects.iobj) && !Array.isArray(objects.iobj)) {
          verb = objects.iobj.findVerb(command, objects)
          if (verb) {
            return { verb, this: objects.iobj }
          }
        }

        return null
      }

      findVerb (command, objects, self = this) {
        for (const key of this.keys()) {
          const prop = this[key]
          if (prop && prop.verb && verbMatchesCommand(prop, command, objects, self)) {
            return key
          }
        }
      }
    }
  }
}

module.exports = WorldObjectClassBuilder
<commit_msg>Support right prompt; closes #157<commit_after>const C3 = require('./c3')
const idify = require('./idify')
const { parseNoun } = require('./parse')

// TODO: this file needs some refactoring

const NO_MATCH = 0
const EXACT_MATCH = 1
const PARTIAL_MATCH = 2

function linearize (object, linearization = new C3(object)) {
  object.traits.forEach((trait, index) => {
    if (trait !== undefined) {
      linearization.add(object, trait)
      linearize(trait, linearization)
    } else {
      // Attempt at gracefully handle a broken trait chain
      // e.g. the parent object was destroyed
      object.traits.splice(index, 1)
    }
  })
  return linearization.run()
}

function match (name, search) {
  if (search === undefined) {
    return EXACT_MATCH
  }

  const xl = name.toLowerCase()
  const yl = search.toLowerCase()
  if (xl === yl) {
    return EXACT_MATCH
  }
  if (xl.indexOf(yl) === 0) {
    return PARTIAL_MATCH
  }
  return NO_MATCH
}

function getMatchesWithDeterminer (foundMatches, determiner, ambiguous, fail) {
  const length = foundMatches.length
  if (determiner === undefined) { // one, definite
    if (length === 1) {
      return foundMatches[0][1]
    }
    return ambiguous
  } else if (determiner === 'any') { // one, indefinite => random
    return foundMatches[Math.floor(Math.random() * length)][1]
  } else if (determiner === 'all') { // all
    return foundMatches.map(object => object[1])
  }
  // one, by ordinal rank
  const n = Number(determiner) - 1
  if (!isNaN(n) && n < length) {
    return foundMatches[n][1]
  }
  return fail
}

function matchPattern (pattern, str) {
  let patternParts
  let rest

  if (pattern === '*') {
    return true
  }
  if (pattern.indexOf('*') !== -1) {
    patternParts = pattern.split('*')
    if (str === patternParts[0]) {
      return true
    }
    if (str.indexOf(patternParts[0]) === 0) {
      if (patternParts[1] === '') {
        return true
      }
      rest = str.slice(patternParts[0].length, str.length + 1)
      if (patternParts[1].indexOf(rest) === 0) {
        return true
      }
    }
  } else if (pattern === str) {
    return true
  }
  return false
}

function matcheVerbPattern (pattern, str) {
  return pattern.split(' ').reduce((last, p) =>
    last || matchPattern(p, str)
  , false)
}

function verbMatchesCommand (verb, command, objects, self) {
  if (!matcheVerbPattern(verb.pattern, command.verb)) {
    return false
  }
  switch (verb.dobjarg) {
    case 'none':
      if (typeof command.dobjstr !== 'undefined') {
        return false
      }
      break
    case 'this':
      if (objects.dobj !== self) {
        return false
      }
      break
    default:
      break
  }
  switch (verb.iobjarg) {
    case 'none':
      if (typeof command.iobjstr !== 'undefined') {
        return false
      }
      break
    case 'this':
      if (objects.iobj !== self) {
        return false
      }
      break
    default:
      break
  }
  switch (verb.preparg) {
    case 'none':
      if (typeof command.prepstr !== 'undefined') {
        return false
      }
      break
    case 'any':
      return true
    default:
      if (verb.preparg.split('/').indexOf(command.prepstr) < 0) {
        return false
      }
  }
  return true
}

class WorldObjectClassBuilder {
  constructor (db, world, controllerMap) {
    this.db = db
    this.world = world
    this.controllerMap = controllerMap
  }

  buildClass () {
    const db = this.db
    const world = this.world
    const controllerMap = this.controllerMap

    return class WorldObject {
      constructor (properties) {
        for (const key in properties) {
          this[key] = properties[key]
        }
      }

      get online () {
        return !!controllerMap.get(this.id)
      }

      toString () {
        return `[object ${this.id}]`
      }

      send (msg) {
        const controller = controllerMap.get(this.id)
        if (controller) {
          controller.emit('output', msg)
          return true
        }
        return false
      }

      setPrompt (str) {
        const controller = controllerMap.get(this.id)
        if (controller) {
          controller.emit('set-prompt', str)
          return true
        }
        return false
      }

      setRightPrompt (str) {
        const controller = controllerMap.get(this.id)
        if (controller) {
          controller.emit('set-right-prompt', str)
          return true
        }
        return false
      }

      addAlias (...args) {
        const aliases = this.aliases
        const ret = aliases.push(...args)
        this.aliases = aliases
        return ret
      }

      rmAlias (...args) {
        this.aliases = this.aliases.filter(a => args.indexOf(a) === -1)
        return this.aliases.length
      }

      addTrait (...args) {
        const traits = this.traits
        const ret = traits.push(...args)
        this.traits = traits
        return ret
      }

      rmTrait (...args) {
        this.traits = this.traits.filter(a => args.indexOf(a) === -1)
        return this.traits.length
      }

      linearize () {
        return linearize(this)
      }

      instanceOf (object) {
        return this.linearize().indexOf(object) !== -1
      }

      keys () {
        const objects = this.linearize()
        const propertySet = new Set()
        objects.forEach(object => {
          Reflect.ownKeys(object).forEach(key => { propertySet.add(key) })
        })
        return [...propertySet.values()]
      }

      values () {
        return this.keys().map(k => this[k])
      }

      new (idraw, props = {}) {
        // Sanitize the id, in case it wasn't previously checked with nextId()
        const id = idify(idraw)

        if (world.get(id) || id in world.context) {
          throw new TypeError(`Identifier '${id}' has already been declared`)
        }

        const newObj = {
          id,
          name: id,
          aliases: [],
          traitIds: [this.id],
          locationId: null,
          properties: {}
        }

        db.insert(newObj)
        world.insert(newObj)
        const object = world.get(id)

        for (const key in props) {
          if ({}.hasOwnProperty.call(props, key)) {
            object[key] = props[key]
          }
        }

        return object
      }

      destroy () {
        if (this.player && this.online) {
          throw new Error(`${this.id} is a player and is online, therefore cannot be destroyed.`)
        }

        db.removeById(this.id)
        world.removeById(this.id)

        return true
      }

      matchObjects (command) {
        return {
          dobj: command.dobjstr ? this.findObject(command.dobjstr) : world.get('nothing'),
          iobj: command.iobjstr ? this.findObject(command.iobjstr) : world.get('nothing')
        }
      }

      findObject (search) {
        if (search === 'me' || search === 'myself') {
          return this
        } else if (search === 'here') {
          return this.location
        }
        return this.findNearby(search)
      }

      findNearby (search) {
        if (search === '') {
          return world.get('fail')
        }

        let searchItems = []

        // Stuff inside this object.
        searchItems = searchItems.concat(this.contents)

        if (this.location) {
          // Stuff in the location this object is in.
          searchItems = searchItems.concat(this.location.contents)

          // Maybe add extra match objects from the location this object is in.
          let extraMatchObjects = []

          if (Array.isArray(this.location.extraMatchObjects)) {
            extraMatchObjects = this.location.extraMatchObjects
          }

          if (typeof this.location.extraMatchObjects === 'function') {
            const potentialExtraMatchObjects = this.location.extraMatchObjects()
            if (Array.isArray(potentialExtraMatchObjects)) {
              extraMatchObjects = potentialExtraMatchObjects
            }
          }

          searchItems = searchItems.concat(extraMatchObjects)
        }

        searchItems = searchItems.filter(object => object !== this)

        const [determiner, noun] = parseNoun(search)

        const potentialMatches = searchItems.map(object => [object.matches(noun), object])
        const exactMatches = potentialMatches.filter(m => m[0] === EXACT_MATCH)
        const partialMatches = potentialMatches.filter(m => m[0] === PARTIAL_MATCH)

        return WorldObject.findMatch(exactMatches, partialMatches, determiner)
      }

      findInside (search) {
        if (search === '') {
          return world.get('fail')
        }

        const [determiner, noun] = parseNoun(search)
        const potentialMatches = this.contents.map(object => [object.matches(noun), object])
        const exactMatches = potentialMatches.filter(m => m[0] === EXACT_MATCH)
        const partialMatches = potentialMatches.filter(m => m[0] === PARTIAL_MATCH)

        return WorldObject.findMatch(exactMatches, partialMatches, determiner)
      }

      static findMatch (partialMatches, exactMatches, determiner) {
        if (exactMatches.length > 0) {
          const result = getMatchesWithDeterminer(exactMatches, determiner,
            world.get('ambiguous'), world.get('fail'))
          // For now, treat collections as ambiguous
          return Array.isArray(result) ? world.get('ambiguous') : result
        }

        if (partialMatches.length > 0) {
          const result = getMatchesWithDeterminer(partialMatches, determiner,
            world.get('ambiguous'), world.get('fail'))
          // For now, treat collections as ambiguous
          return Array.isArray(result) ? world.get('ambiguous') : result
        }

        return world.get('fail')
      }

      matches (search) {
        const matches = this.aliases.concat([this.name]).map(name => match(name, search))

        if (matches.indexOf(EXACT_MATCH) >= 0) {
          return EXACT_MATCH
        }
        if (matches.indexOf(PARTIAL_MATCH) >= 0) {
          return PARTIAL_MATCH
        }
        return NO_MATCH
      }

      matchVerb (command, objects) {
        let verb

        verb = this.findVerb(command, objects)
        if (verb) {
          return { verb, this: this }
        }

        if (this.location) {
          verb = this.location.findVerb(command, objects)
          if (verb) {
            return { verb, this: this.location }
          }
        }

        if ((objects.dobj) && !Array.isArray(objects.dobj)) {
          verb = objects.dobj.findVerb(command, objects)
          if (verb) {
            return { verb, this: objects.dobj }
          }
        }

        if ((objects.iobj) && !Array.isArray(objects.iobj)) {
          verb = objects.iobj.findVerb(command, objects)
          if (verb) {
            return { verb, this: objects.iobj }
          }
        }

        return null
      }

      findVerb (command, objects, self = this) {
        for (const key of this.keys()) {
          const prop = this[key]
          if (prop && prop.verb && verbMatchesCommand(prop, command, objects, self)) {
            return key
          }
        }
      }
    }
  }
}

module.exports = WorldObjectClassBuilder
<|endoftext|>


<commit_before>// TODO: Can we do some inversion of control here?
// TODO: Should we be explicit in export options? -- I think so -- it is good observer formatting
var path = require('path'),
    spawn = require('child_process').spawn,
    Tempfile = require('temporary/lib/file'),
    which = require('which'),
    phantomLocation = which.sync('phantomjs'),
    ndarray = require('ndarray'),
    savePixels = require('save-pixels'),
    exporters = {};

// Function to add new exporters
function addExporter(name, exporter) {
  exporters[name] = exporter;
}

// Helper to create exporters (could be a class for better abstraction)
function getPhantomjsExporter(ext) {
  /**
   * Generic exporter
   * @param {Object} options Options to export with
   * @param {Number} [options.quality] Quality of the exported item
   * @param {Function} cb Error-first callback to return binary image string to
   */
  return function phantomjsExporterFn (options, cb) {
    var that = this;

    // Convert over all image paths to url paths
    var images = that.images;
    images.forEach(function getUrlPath (img) {
      img = img.img;
      img._urlpath = path.relative(__dirname + '/scripts', img._filepath);
    });

    // Collect our parameters
    var params = that.params;
    params.images = images;
    params.options = options;

    // console.log(options);

    // Stringify our argument for phantomjs
    var arg = JSON.stringify(params),
        encodedArg = encodeURIComponent(arg);

    // Write out argument to temporary file -- streams weren't cutting it
    var tmp = new Tempfile(),
        filepath = tmp.path;
    tmp.writeFileSync(encodedArg, 'utf8');

    // Create a child process for phantomjs
    var phantomjs = spawn(phantomLocation, [__dirname + '/scripts/compose.js', filepath]);

    // When there is data, save it
    // DEV: encodedPixels is an array of rgba values
    var encodedPixels = '';
    phantomjs.stdout.on('data', function (buffer) {
      encodedPixels += buffer.toString();
    });

    // When there is an error, concatenate it
    var err = '';
    phantomjs.stderr.on('data', function (buffer) {
      // Ignore PhantomJS 1.9.2 OSX errors
      // https://github.com/Ensighten/grunt-spritesmith/issues/33
      var bufferStr = buffer + '',
          isNot192OSXError = bufferStr.indexOf('WARNING: Method userSpaceScaleFactor') === -1,
          isNotPerformanceNote = bufferStr.indexOf('CoreText performance note:') === -1;
      if (isNot192OSXError && isNotPerformanceNote) {
        err += bufferStr;
      }
    });

    // When we are done
    phantomjs.on('close', function () {
      // Destroy the temporary file
      try { tmp.unlinkSync(); } catch (e) {}

      // If there was an error in phantom, callback with it
      if (err) {
        cb(new Error(err));
      }

      // Otherwise, decode the pixel values
      // DEV: This used to be thinner and not need padding but Windows was messing up the image
      var decodedPixels;
      try {
        decodedPixels = JSON.parse(encodedPixels);
      } catch (e) {
        cb(new Error('Error while parsing JSON "' + encodedPixels + '".\n' + e.message));
      }

      // Convert the pixels into an ndarray
      // Taken from https://github.com/mikolalysenko/get-pixels/blob/2ac98645119244d6e52afcef5fe52cc9300fb27b/dom-pixels.js#L14
      var pngNdarray = ndarray(decodedPixels, [params.height, params.width, 4],
                         [4 * params.width, 4, 1], 0),
          png = savePixels(pngNdarray, 'PNG');

      // Pump the stream from png to a binary string (expected by spritesmith)
      // TODO: We should be calling back with the stream =(
      var retVal = '';
      png.on('data', function (buffer) {
        retVal += buffer.toString('binary');
      });
      png.on('end', function () {
        cb(null, retVal);
      });
    });
  };
}

// Generate the png exporter
var phantomjsPngExporter = getPhantomjsExporter('png');
addExporter('png', phantomjsPngExporter);
addExporter('image/png', phantomjsPngExporter);

// TODO: It seems we can export jpg and webp images
// https://developer.mozilla.org/en-US/docs/DOM/HTMLCanvasElement#Methods

// Export our exporters
module.exports = {
  exporters: exporters,
  addExporter: addExporter
};<commit_msg>Added return to early callbacks<commit_after>// TODO: Can we do some inversion of control here?
// TODO: Should we be explicit in export options? -- I think so -- it is good observer formatting
var path = require('path'),
    spawn = require('child_process').spawn,
    Tempfile = require('temporary/lib/file'),
    which = require('which'),
    phantomLocation = which.sync('phantomjs'),
    ndarray = require('ndarray'),
    savePixels = require('save-pixels'),
    exporters = {};

// Function to add new exporters
function addExporter(name, exporter) {
  exporters[name] = exporter;
}

// Helper to create exporters (could be a class for better abstraction)
function getPhantomjsExporter(ext) {
  /**
   * Generic exporter
   * @param {Object} options Options to export with
   * @param {Number} [options.quality] Quality of the exported item
   * @param {Function} cb Error-first callback to return binary image string to
   */
  return function phantomjsExporterFn (options, cb) {
    var that = this;

    // Convert over all image paths to url paths
    var images = that.images;
    images.forEach(function getUrlPath (img) {
      img = img.img;
      img._urlpath = path.relative(__dirname + '/scripts', img._filepath);
    });

    // Collect our parameters
    var params = that.params;
    params.images = images;
    params.options = options;

    // console.log(options);

    // Stringify our argument for phantomjs
    var arg = JSON.stringify(params),
        encodedArg = encodeURIComponent(arg);

    // Write out argument to temporary file -- streams weren't cutting it
    var tmp = new Tempfile(),
        filepath = tmp.path;
    tmp.writeFileSync(encodedArg, 'utf8');

    // Create a child process for phantomjs
    var phantomjs = spawn(phantomLocation, [__dirname + '/scripts/compose.js', filepath]);

    // When there is data, save it
    // DEV: encodedPixels is an array of rgba values
    var encodedPixels = '';
    phantomjs.stdout.on('data', function (buffer) {
      encodedPixels += buffer.toString();
    });

    // When there is an error, concatenate it
    var err = '';
    phantomjs.stderr.on('data', function (buffer) {
      // Ignore PhantomJS 1.9.2 OSX errors
      // https://github.com/Ensighten/grunt-spritesmith/issues/33
      var bufferStr = buffer + '',
          isNot192OSXError = bufferStr.indexOf('WARNING: Method userSpaceScaleFactor') === -1,
          isNotPerformanceNote = bufferStr.indexOf('CoreText performance note:') === -1;
      if (isNot192OSXError && isNotPerformanceNote) {
        err += bufferStr;
      }
    });

    // When we are done
    phantomjs.on('close', function () {
      // Destroy the temporary file
      try { tmp.unlinkSync(); } catch (e) {}

      // If there was an error in phantom, callback with it
      if (err) {
        return cb(new Error(err));
      }

      // Otherwise, decode the pixel values
      // DEV: This used to be thinner and not need padding but Windows was messing up the image
      var decodedPixels;
      try {
        decodedPixels = JSON.parse(encodedPixels);
      } catch (e) {
        return cb(new Error('Error while parsing JSON "' + encodedPixels + '".\n' + e.message));
      }

      // Convert the pixels into an ndarray
      // Taken from https://github.com/mikolalysenko/get-pixels/blob/2ac98645119244d6e52afcef5fe52cc9300fb27b/dom-pixels.js#L14
      var pngNdarray = ndarray(decodedPixels, [params.height, params.width, 4],
                         [4 * params.width, 4, 1], 0),
          png = savePixels(pngNdarray, 'PNG');

      // Pump the stream from png to a binary string (expected by spritesmith)
      // TODO: We should be calling back with the stream =(
      var retVal = '';
      png.on('data', function (buffer) {
        retVal += buffer.toString('binary');
      });
      png.on('end', function () {
        cb(null, retVal);
      });
    });
  };
}

// Generate the png exporter
var phantomjsPngExporter = getPhantomjsExporter('png');
addExporter('png', phantomjsPngExporter);
addExporter('image/png', phantomjsPngExporter);

// TODO: It seems we can export jpg and webp images
// https://developer.mozilla.org/en-US/docs/DOM/HTMLCanvasElement#Methods

// Export our exporters
module.exports = {
  exporters: exporters,
  addExporter: addExporter
};<|endoftext|>


<commit_before>// Copyright (c) 2010 Romain Vallet
// Licensed under the MIT license, read license.txt

// The dynamic loading of images in Picasa Web Albums makes it difficult to handle with this extension.
// The function that "prepares" a link has been isolated (prepareImgLink) and is called whenever the
// user move the mouse over a link.

var hoverZoomPlugins = hoverZoomPlugins || [];
hoverZoomPlugins.push({
	name: 'Picasa Web Albums',
	version: '0.5',
	prepareImgLinks: function(callback) {

		function prepareImgLink() {
			var _this = $(this), data = _this.data();
			if (data.hoverZoomSrc) { return; }
			var src = _this.find('img')[0].src;
			src = src.replace(/\/s\d+(-c)?\//, options.showHighRes ? '/' : '/s800/');
			data.hoverZoomSrc = [src];
			_this.addClass('hoverZoomLink');

			var tooltip = _this.parent().find('.goog-icon-list-icon-meta:eq(0)');
			if (tooltip.length) {
				data.hoverZoomCaption = tooltip.text();
			}
		}

		var links = $("a.goog-icon-list-icon-link,div.gphoto-grid-cell a");
		links.live('mouseover', prepareImgLink);
		callback(links);
	}
});<commit_msg>Fixed Picasa plugin (fixes issue 142).<commit_after>// Copyright (c) 2010 Romain Vallet
// Licensed under the MIT license, read license.txt

// The dynamic loading of images in Picasa Web Albums makes it difficult to handle with this extension.
// The function that "prepares" a link has been isolated (prepareImgLink) and is called whenever the
// user move the mouse over a link.

var hoverZoomPlugins = hoverZoomPlugins || [];
hoverZoomPlugins.push({
	name: 'Picasa Web Albums',
	version: '0.5',
	prepareImgLinks: function(callback) {

		function prepareImgLink() {
			var _this = $(this), data = _this.data();
			if (data.hoverZoomSrc) { return; }
			var src = _this.find('img')[0].src;
			src = src.replace(/\/s\d+(-c)?\//, options.showHighRes ? '/' : '/s800/');
			data.hoverZoomSrc = [src];
			_this.addClass('hoverZoomLink');

			var tooltip = _this.parent().find('.goog-icon-list-icon-meta:eq(0)');
			if (tooltip.length) {
				data.hoverZoomCaption = tooltip.text();
			}
		}

		function bindLinks() {
			$('a.goog-icon-list-icon-link,div.gphoto-grid-cell a').mouseover(prepareImgLink);
			bindLinksTimeout = null;
		}

		var bindLinksTimeout = setTimeout(bindLinks, 500);

		function windowOnDOMNodeInserted(event) {
			if (!bindLinksTimeout && event.srcElement && event.srcElement.nodeName == 'A') {
				bindLinksTimeout = setTimeout(bindLinks, 500);
			}
		}

		$(window).bind('DOMNodeInserted', windowOnDOMNodeInserted)
	}
});<|endoftext|>