Home Reference Source Repository

app/ui/CharacterBuilder.js

import Classes from '../entities/creatures/classes/Classes.js';

import Abilities from '../abilities/Abilities.js';
import Armors from '../entities/armor/Armors.js';
import Consumables from '../entities/consumables/Consumables.js';
import Weapons from '../entities/weapons/Weapons.js';

import AbilityConsumable from '../entities/consumables/AbilityConsumable.js';

var dialogPolyfill = require('../../../../../node_modules/dialog-polyfill/dialog-polyfill.js');
require('../../../../../node_modules/dialog-polyfill/dialog-polyfill.css');

const angular = require('angular');

var Purchaseables = Object.assign({}, Abilities, Armors, Consumables, Weapons);

const promiseHandlers = {};

const CHARACTERS = {
    Fighter: 70,
    Miner: 60,
    Rogue: 60,
    Wizard: 55
};

const MELEE_WEAPONS = {
    Stick: 0,
    Dagger: 10,
    Shortsword: 20,
    FrostDagger: 25,
    Warhammer: 25,
    Longsword: 30,
    LightningRod: 45
};
const RANGED_WEAPONS = {
    Slingshot: 10,
    Shortbow: 30,
    Longbow: 60
};

const ARMOR = {
    LightArmor: 15,
    MediumArmor: 25,
    HeavyArmor: 40/*,

    LightRobe: 5,
    MediumRobe: 15,
    HeavyRobe: 25*/
};

const CONSUMABLES = {
    CherrySoda: 10,
    BlueberrySoda: 10,

    Fireball: 20,
    ForceDart: 10,
    LesserSnare: 10
};

const ABILITIES = {
    Fireball: 30,
    Firebolt: 35,
    LesserSnare: 15
};

function getStartingAbilities(className) {
    switch(className) {
    case 'Fighter': return ['DashAttack'];
    case 'Miner': return ['Tunnel'];
    case 'Wizard': return ['ForceDart'];
    case 'Rogue': return ['Leap'];
    }
}

angular.module('vog', [])
.filter('vogName', function() {
    return (name) => name.replace(/([^A-Z])([A-Z])/g, '$1 $2');
})
.component('characterBuild', {
    bindings: {
        build: '='
    },
    controller: function() {
        this.getSelectedAbilityNames = function() {
            const { build: { abilities } } = this;
            return Object.keys(ABILITIES).filter((ability)=>abilities[ability]);
        };
        this.getAbilityNames = function() {
            const { build: {character, abilities } } = this;
            return getStartingAbilities(character).concat(this.getSelectedAbilityNames());
        };
    },
    templateUrl: 'character-build.html'
})
.controller('character-builder', ['$scope', 'promiseHandlers', function($scope, promiseHandlers) {
    const { resolve, reject } = promiseHandlers;

    $scope.CHARACTERS = CHARACTERS;
    $scope.MELEE_WEAPONS = MELEE_WEAPONS;
    $scope.RANGED_WEAPONS = RANGED_WEAPONS;
    $scope.ARMOR = ARMOR;
    $scope.CONSUMABLES = CONSUMABLES;
    $scope.ABILITIES = ABILITIES;

    try {
        $scope.lastBuild = JSON.parse(localStorage.lastBuild);
    } catch(e) {
        console.error(e);
    }

    $scope.selections = {
        character: 'Fighter',
        melee: 'Stick',
        ranged: null,
        armor: null,
        backpack: [],
        abilities: {}
    };

    $scope.prebuilts = [{
        character: 'Fighter',
        melee: 'Warhammer',
        ranged: null,
        armor: 'MediumArmor',
        backpack: ['CherrySoda', 'ForceDart'],
        abilities: {}
    }, {
        character: 'Rogue',
        melee: 'FrostDagger',
        ranged: null,
        armor: 'LightArmor',
        backpack: ['CherrySoda'],
        abilities: {}
    }, {
        character: 'Wizard',
        melee: 'Stick',
        ranged: null,
        armor: 'LightArmor',
        backpack: ['BlueberrySoda'],
        abilities: {Fireball: true}
    }];

    $scope.selectLastBuild = function(index) {
        $scope.selections = $scope.lastBuild;
    }

    $scope.selectPrebuilt = function(index) {
        $scope.selections = $scope.prebuilts[index];
    }

    let isBuilderVisible = false;
    $scope.isBuilderVisible = () => isBuilderVisible;
    $scope.showBuilder = () => isBuilderVisible = true;

    $scope.getSelectedAbilityNames = function() {
        return Object.keys(ABILITIES).filter((ability)=>$scope.selections.abilities[ability]);
    }

    $scope.getConsumableName = function(consumable) {
        return consumable in Abilities ? `${consumable}_Consumable` : consumable;
    }

    $scope.getPurchaseableAbilities = function() {
        const character = new Classes[$scope.selections.character]();
        const mana = character.getBaseMana();
        return Object.keys(ABILITIES)
            .filter((name)=>new Abilities[name]().getManaCost() <= mana)
            .reduce(function(obj, name) {
                obj[name] = ABILITIES[name];
                return obj;
            }, {});
    };

    $scope.fixAbilities = function() {
        const { selections: { character, abilities } } = $scope;
        const mana = new Classes[character]().getBaseMana();
        Object.keys(ABILITIES).forEach(function(abilityName) {
            if(new Abilities[abilityName]().getManaCost() > mana) {
                abilities[abilityName] = false;
            }
        });
    };

    $scope.getBackpackSize = function() {
        return new Classes[$scope.selections.character]().getInventory().getBackpack().length;
    };

    $scope.addToBackpack = function(item) {
        const { selections: { backpack } } = $scope;
        backpack.push(item);
        if(backpack.length > $scope.getBackpackSize()) {
            backpack.shift();
        }
    };

    $scope.getCost = function() {
        const { selections } = $scope;
        return MELEE_WEAPONS[selections.melee] +
            (RANGED_WEAPONS[selections.ranged] || 0) +
            (ARMOR[selections.armor] || 0) +
            selections.backpack.map((item)=>CONSUMABLES[item]).reduce((a, b)=>a+b, 0) +
            $scope.getSelectedAbilityNames().map((ability)=>ABILITIES[ability]).reduce((a, b)=>a+b, 0);
    };

    $scope.getMoney = function() {
        return CHARACTERS[$scope.selections.character];
    }

    $scope.isBuildLegal = function() {
        return $scope.getCost() <= $scope.getMoney();
    };

    $scope.submit = function() {
        const { selections } = $scope;
        const character = new Classes[selections.character]();
        getStartingAbilities(selections.character).concat($scope.getSelectedAbilityNames()).forEach(function(abilityName) {
            character.addAbility(new Abilities[abilityName]());
        });

        [
            Purchaseables[selections.melee],
            Purchaseables[selections.ranged],
            Purchaseables[selections.armor]
        ].filter(Boolean).forEach(function(Class) {
            character.addItem(new Class());
        });

        selections.backpack.forEach(function(itemName) {
            if(Consumables[itemName]) {
                character.addItem(new Consumables[itemName]());
            } else {
                character.addItem(new AbilityConsumable(new Abilities[itemName]));
            }
        });

        localStorage.lastBuild = JSON.stringify(selections);

        resolve(character);
    };
}]).constant('promiseHandlers', promiseHandlers).run(['$templateCache', function($templateCache) {
    $templateCache.put('character-build.html',
        `<div class="character-build">
            <h3>{{$ctrl.build.character}}</h3>
            <div class="melee-weapon">{{$ctrl.build.melee | vogName}}</div>
            <div class="ranged-weapon" ng-if="$ctrl.build.ranged">{{$ctrl.build.ranged | vogName}}</div>
            <div class="armor" ng-if="$ctrl.build.armor">{{$ctrl.build.armor | vogName}}</div>
            <div class="abilities">
                <span ng-repeat="name in $ctrl.getAbilityNames()"><span ng-if="!$first">, </span>{{name | vogName}}</span>
            </div>
            <div class="consumables">
                <span ng-repeat="name in $ctrl.build.backpack"><span ng-if="!$first">, </span>{{name | vogName}}</span>
            </div>
        </div>`);
    $templateCache.put('character-builder.html',
        `<form method="dialog" class="gitrecht" ng-controller="character-builder" ng-submit="submit()">
            <h2>Select Character</h2>
            <div class="prebuilts">
                <div class="group" ng-if="lastBuild">
                    <h3>Most Recent</h3>
                    <button ng-click="selectLastBuild()">
                        <character-build build="lastBuild"></character-build>
                    </button>
                </div>
                <div class="group">
                    <button ng-repeat="build in prebuilts" ng-click="selectPrebuilt($index)">
                        <character-build build="build"></character-build>
                    </button>
                </div>
            </div>
            <button type="button" ng-if="!isBuilderVisible()" ng-click="showBuilder()">Make a Build</button>
            <div class="builder" ng-if="isBuilderVisible()">
                <div class="col">
                    <div class="items">
                        <h3>Character</h3>
                        <label ng-repeat="(character, money) in CHARACTERS" class="icon" data-class-name="{{character}}" title="{{character | vogName}}" ng-class="{selected: selections.character === character}">
                            <input type="radio" name="melee" value="{{character}}" ng-model="selections.character" ng-click="fixAbilities()">
                        </label>
                    </div>
                </div>
                <div class="col">
                    <div class="items">
                        <h3>Melee Weapon</h3>
                        <label ng-repeat="(weapon, cost) in MELEE_WEAPONS"
                               class="icon" data-item-name="{{weapon}}"
                               title="{{weapon | vogName}}"
                               ng-class="{selected: selections.melee === weapon}"
                               data-cost="{{cost}}">
                            <input type="radio" name="melee" ng-value="weapon" ng-model="selections.melee">
                        </label>
                    </div>
                </div>
                <div class="col">
                    <div class="items">
                        <h3>Ranged Weapon</h3>
                        <label class="icon" data-item-name="None" ng-class="{selected: selections.ranged === null}">
                            <input type="radio" name="ranged" ng-value="null" title="None" ng-model="selections.ranged">
                        </label>
                        <label ng-repeat="(weapon, cost) in RANGED_WEAPONS"
                               class="icon" data-item-name="{{weapon}}"
                               title="{{weapon | vogName}}"
                               ng-class="{selected: selections.ranged === weapon}"
                               data-cost="{{cost}}">
                            <input type="radio" name="ranged" ng-value="weapon" ng-model="selections.ranged">
                        </label>
                    </div>
                </div>
                <div class="col">
                    <div class="items">
                        <h3>Armor</h3>
                        <label class="icon" data-item-name="None" title="None" ng-class="{selected: selections.armor === null}">
                            <input type="radio" name="armor" ng-value="null" ng-model="selections.armor">
                        </label>
                        <label ng-repeat="(armor, cost) in ARMOR"
                               class="icon" data-item-name="{{armor}}"
                               title="{{armor | vogName}}"
                               ng-class="{selected: selections.armor === armor}"
                               data-cost="{{cost}}">
                            <input type="radio" name="armor" ng-value="armor" ng-model="selections.armor">
                        </label>
                    </div>
                </div>
                <div class="col">
                    <div class="items">
                        <h3>Abilities</h3>
                        <label ng-repeat="(ability, cost) in getPurchaseableAbilities()"
                            class="icon" data-ability-name="{{ability}}"
                            title="{{ability | vogName}}"
                            ng-class="{selected: selections.abilities[ability]}"
                            data-cost="{{cost}}">
                            <input type="checkbox" class="icon" ng-model="selections.abilities[ability]"></input>
                        </label>
                    </div>
                </div>
                <div class="col">
                    <div class="items">
                        <h3>Consumables</h3>
                        <button ng-repeat="(consumable, cost) in CONSUMABLES"
                                type="button" class="icon"
                                data-item-name="{{getConsumableName(consumable)}}"
                                title="{{consumable | vogName}}"
                                ng-click="addToBackpack(consumable)"
                                data-cost="{{cost}}"></button>
                    </div>
                </div>
                <div class="preview">
                    <character-build build="selections"></character-build>
                    <span ng-if="getCost() <= getMoney()" class="amount-left">
                        <span class="value">{{getMoney() - getCost()}}</span>
                        <span class="remaining">left</span>
                    </span>
                    <span ng-if="getCost() > getMoney()" class="amount-overspent">
                        <span class="value">{{getCost() - getMoney()}}</span>
                        <span class="remaining">too much</span>
                    </span>
                    <div><input type="submit" ng-disabled="!isBuildLegal()"/></div>
                </div>
            </div>
        </form>`);
}]);

export default class CharacterBuilder {
    constructor() {
        this._promise = new Promise((resolve, reject) => {
            var $dialog = $(`
                <dialog class='character-builder'>
                    <ng-include src="'character-builder.html'"></ng-include>
                </dialog>`).appendTo('body');

            const dialog = $dialog[0];

            if(!dialog.open) {
                dialogPolyfill.registerDialog(dialog);
            }

            promiseHandlers.resolve = resolve;
            promiseHandlers.reject = reject;

            angular.bootstrap(dialog, ['vog']);

            dialog.showModal();
        });
    }

    getCharacter() {
        return this._promise;
    }
}