UNPKG

@aaronjewell/w3-build-order

Version:

Library for configuring a build order in Warcraft 3

1,872 lines (1,659 loc) 83.3 kB
import clonedeep from 'lodash.clonedeep'; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread2(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _possibleConstructorReturn(self, call) { if (call && (typeof call === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); } function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _createForOfIteratorHelper(o, allowArrayLike) { var it; if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function () {}; return { s: F, n: function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function (e) { throw e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function () { it = o[Symbol.iterator](); }, n: function () { var step = it.next(); normalCompletion = step.done; return step; }, e: function (e) { didErr = true; err = e; }, f: function () { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; } var buildings = [{ id: "altarOfStorms", name: "Altar of Storms", type: "building", gold: 180, lumber: 50, buildTime: 60, supply: 0, dependsOn: [], order: 6, units: ["bladeMaster", "farSeer", "shadowHunter", "taurenChieftain"], upgrades: [], items: [], image: "orc/altarofstorms.png" }, { id: "barracks", name: "Barracks", type: "building", gold: 180, lumber: 50, buildTime: 60, supply: 0, dependsOn: [], order: 2, units: ["grunt"], upgrades: ["trollRegeneration", "berserkerUpgrade", "burningOil"], items: [], image: "orc/barracks.png" }, { id: "beastiary", name: "Beastiary", type: "building", gold: 145, lumber: 140, buildTime: 60, supply: 0, dependsOn: ["stronghold"], order: 8, units: [], upgrades: ["ensnare", "envenomedSpears", "liquidFire"], items: [], image: "orc/beastiary.png" }, { id: "greatHall", name: "Great Hall", type: "building", gold: 385, lumber: 185, buildTime: 150, supply: 11, tier: 1, dependsOn: [], order: 1, units: ["peon"], upgrades: ["pillage", "unitInventory", "stronghold", "fortress"], items: [], image: "orc/greathall.png" }, { id: "orcBurrow", name: "Orc Burrow", type: "building", gold: 160, lumber: 40, buildTime: 50, supply: 10, dependsOn: [], order: 5, units: [], upgrades: [], items: [], image: "orc/orcburrow.png" }, { id: "spiritLodge", name: "Spirit Lodge", type: "building", gold: 150, lumber: 135, buildTime: 70, supply: 0, dependsOn: ["stronghold"], order: 7, units: [], upgrades: ["shamanAdeptTraining", "shamanMasterTraining", "witchDoctorAdeptTraining", "witchDoctorMasterTraining"], items: [], image: "orc/spiritlodge.png" }, { id: "taurenTotem", name: "Tauren Totem", type: "building", gold: 135, lumber: 155, buildTime: 70, supply: 0, dependsOn: ["warMill", "fortress"], order: 9, units: [], upgrades: ["spiritWalkeAdeptTraining", "spiritWalkerMasterTraining", "pulverizeDamageIncrease"], items: [], image: "orc/taurentotem.png" }, { id: "voodooLounge", name: "Voodoo Lounge", type: "building", gold: 130, lumber: 30, buildTime: 60, supply: 0, dependsOn: [], order: 10, units: [], upgrades: [], items: ["healingSalve", "lesserClarityPotion", "orbOfLightning", "potionOfHealing", "potionOfMana", "scrollOfSpeed", "scrollOfTownPortal", "tinyGreatHall"], image: "orc/voodoolounge.png" }, { id: "warMill", name: "War Mill", type: "building", gold: 205, lumber: 0, buildTime: 70, supply: 0, dependsOn: [], order: 3, units: [], upgrades: ["spikedBarricades", "improvedSpikedBarricades", "reinforcedDefenses", "steelMeleeWeapons", "thoriumMeleeWeapons", "arcaniteMeleeWeapons", "steelRangedWeapons", "thoriumRangedWeapons", "arcaniteRangedWeapons"], items: [], image: "orc/warmill.png" }, { id: "watchTower", name: "Watch Tower", type: "building", gold: 110, lumber: 80, buildTime: 60, supply: 0, dependsOn: ["warMill"], order: 4, units: [], upgrades: [], items: [], image: "orc/watchtower.png" }]; var units = [{ id: "peon", name: "Peon", type: "unit", gold: 75, lumber: 0, supply: 1, buildTime: 15, canHarvest: true, canMine: true, canBuild: true, order: 1, image: "orc/peon.png" }, { id: "grunt", name: "Grunt", type: "unit", gold: 200, lumber: 0, supply: 3, buildTime: 30, order: 1, image: "orc/grunt.png" }, { id: "headhunter", name: "Headhunter", type: "unit", gold: 135, lumber: 20, supply: 2, buildTime: 20, order: 2, image: "orc/headhunter.png" }, { id: "demolisher", name: "Demolisher", type: "unit", gold: 220, lumber: 50, supply: 4, buildTime: 40, order: 3, image: "orc/demolisher.png" }, { id: "shaman", name: "Shaman", type: "unit", gold: 130, lumber: 20, supply: 2, buildTime: 30, order: 1, image: "orc/shaman.png" }, { id: "witchDoctor", name: "Witch Doctor", type: "unit", gold: 145, lumber: 25, supply: 2, buildTime: 30, order: 2, image: "orc/witchdoctor.png" }, { id: "raider", name: "Raider", type: "unit", gold: 180, lumber: 40, supply: 3, buildTime: 28, order: 1, image: "orc/wolfrider.png" }, { id: "kodoBeast", name: "Kodo Beast", type: "unit", gold: 255, lumber: 60, supply: 4, buildTime: 30, order: 3, image: "orc/kodobeast.png" }, { id: "windRider", name: "Wind Rider", type: "unit", gold: 265, lumber: 40, supply: 4, buildTime: 35, order: 2, image: "orc/wyvernrider.png" }, { id: "trollBatrider", name: "Troll Batrider", type: "unit", gold: 160, lumber: 40, supply: 2, buildTime: 28, orer: 4, image: "orc/batrider.png" }, { id: "tauren", name: "Tauren", type: "unit", gold: 280, lumber: 80, supply: 5, buildTime: 44, order: 2, image: "orc/tauren.png" }, { id: "bladeMaster", name: "Blade Master", type: "unit", gold: 425, lumber: 100, supply: 5, buildTime: 55, limit: 1, isHero: true, order: 9, image: "orc/blademaster.png" }, { id: "farSeer", name: "Far Seer", type: "unit", gold: 425, lumber: 100, supply: 5, buildTime: 55, limit: 1, isHero: true, order: 10, image: "orc/farseer.png" }, { id: "taurenChieftain", name: "Tuaren Chieftain", type: "unit", gold: 425, lumber: 100, supply: 5, buildTime: 55, limit: 1, isHero: true, order: 11, image: "orc/chieftain.png" }, { id: "shadowHunter", name: "Shadow Hunter", type: "unit", gold: 425, lumber: 100, supply: 5, buildTime: 55, limit: 1, isHero: true, order: 5, image: "orc/shadowhunter.png" }]; var Action = /*#__PURE__*/function () { function Action(_ref) { var type = _ref.type, start = _ref.start, valid = _ref.valid, duration = _ref.duration, meta = _ref.meta; _classCallCheck(this, Action); this._id = Action._id++; this.type = type; this.start = start; this.valid = valid; this.duration = duration; this.meta = meta; } _createClass(Action, null, [{ key: "build", value: function build(start, buildingData, worker) { return new Action({ type: "build", start: start, duration: buildingData.buildTime, valid: true, meta: { worker: worker, building: buildingData } }); } }, { key: "buy", value: function buy(start, itemData, building) { return new Action({ type: "buy", start: start, duration: 0, valid: true, meta: { building: building, item: itemData } }); } }, { key: "train", value: function train(start, unitData, building) { return new Action({ type: "train", start: start, duration: unitData.buildTime || 0, valid: true, meta: { unit: unitData, building: building } }); } }, { key: "upgrade", value: function upgrade(start, upgradeData, building) { return new Action({ type: "upgrade", start: start, duration: upgradeData.buildTime, valid: true, meta: { building: building, upgrade: upgradeData } }); } }, { key: "assignToGold", value: function assignToGold(start, worker) { return new Action({ type: "assignToGold", start: start, duration: 0, valid: true, meta: { worker: worker } }); } }, { key: "removeFromGold", value: function removeFromGold(start, worker) { return new Action({ type: "removeFromGold", start: start, duration: 0, valid: true, meta: { worker: worker } }); } }, { key: "assignToLumber", value: function assignToLumber(start, worker) { return new Action({ type: "assignToLumber", start: start, duration: 0, valid: true, meta: { worker: worker } }); } }, { key: "removeFromLumber", value: function removeFromLumber(start, worker) { return new Action({ type: "removeFromLumber", start: start, duration: 0, valid: true, meta: { worker: worker } }); } }]); return Action; }(); _defineProperty(Action, "_id", 1); var Building = /*#__PURE__*/function () { function Building(_ref) { var id = _ref.id, name = _ref.name, gold = _ref.gold, lumber = _ref.lumber, buildTime = _ref.buildTime, dependsOn = _ref.dependsOn, supply = _ref.supply, type = _ref.type, units = _ref.units, upgrades = _ref.upgrades, items = _ref.items, image = _ref.image; _classCallCheck(this, Building); this._id = Building._id++; this.id = id; this.name = name; this.gold = gold; this.lumber = lumber; this.buildTime = buildTime; this.dependsOn = dependsOn; this.units = units; this.upgrades = upgrades; this.items = items; this.supply = supply; this.image = image; this.type = type; } _createClass(Building, null, [{ key: "resetIds", value: function resetIds() { Building._id = 1; } }]); return Building; }(); _defineProperty(Building, "_id", 1); var Item = /*#__PURE__*/function () { function Item(_ref) { var id = _ref.id, name = _ref.name, gold = _ref.gold, lumber = _ref.lumber, dependsOn = _ref.dependsOn, order = _ref.order, type = _ref.type, image = _ref.image; _classCallCheck(this, Item); this._id = Item._id++; this.id = id; this.name = name; this.gold = gold; this.order = order, this.lumber = lumber; this.dependsOn = dependsOn; this.image = image; this.type = type; } _createClass(Item, null, [{ key: "resetIds", value: function resetIds() { Item._id = 1; } }]); return Item; }(); _defineProperty(Item, "_id", 1); var ResourceList = /*#__PURE__*/function () { function ResourceList() { var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; _classCallCheck(this, ResourceList); this.value = value; } _createClass(ResourceList, [{ key: "addValues", value: function addValues(resources) { var resourceList = resources; if (!(resourceList instanceof ResourceList)) { resourceList = new ResourceList(resourceList); } for (var _i = 0, _Object$entries = Object.entries(resourceList.value); _i < _Object$entries.length; _i++) { var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2), key = _Object$entries$_i[0], value = _Object$entries$_i[1]; if (key in this.value) { if (Array.isArray(this.value[key])) { if (Array.isArray(value)) { this.value[key] = this.value[key].concat(value); } else { this.value[key].push(value); } } else { this.value[key] += value; } } else { this.value[key] = value; } } } }, { key: "removeValues", value: function removeValues(resources) { var _this = this; var resourceList = resources; if (!(resourceList instanceof ResourceList)) { resourceList = new ResourceList(resourceList); } var _loop = function _loop() { var _Object$entries2$_i = _slicedToArray(_Object$entries2[_i2], 2), key = _Object$entries2$_i[0], value = _Object$entries2$_i[1]; if (key in _this.value) { if (Array.isArray(_this.value[key])) { if (Array.isArray(value)) { var _iterator = _createForOfIteratorHelper(value), _step; try { var _loop2 = function _loop2() { var v = _step.value; var index = _this.value[key].findIndex(function (i) { return i._id === v._id; }); _this.value[key].splice(index, 1); }; for (_iterator.s(); !(_step = _iterator.n()).done;) { _loop2(); } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } } else { var index = _this.value[key].findIndex(function (i) { return i._id === value._id; }); _this.value[key].splice(index, 1); } } else { _this.value[key] -= value; } } else { throw new Error("Unable to lose resource, none accumulated."); } }; for (var _i2 = 0, _Object$entries2 = Object.entries(resourceList.value); _i2 < _Object$entries2.length; _i2++) { _loop(); } } }]); return ResourceList; }(); var ResourceEvent = /*#__PURE__*/function () { function ResourceEvent(tick, actionId) { _classCallCheck(this, ResourceEvent); _defineProperty(this, "resourceLoss", new ResourceList()); _defineProperty(this, "resourceGain", new ResourceList()); this.actionId = actionId; this.tick = tick; } _createClass(ResourceEvent, [{ key: "loseResources", value: function loseResources(resourceList) { var resources = resourceList; if (!(resourceList instanceof ResourceList)) { resources = new ResourceList(resources); } this.resourceLoss.addValues(resourceList); } }, { key: "gainResources", value: function gainResources(resourceList) { var resources = resourceList; if (!(resourceList instanceof ResourceList)) { resources = new ResourceList(resources); } this.resourceGain.addValues(resourceList); } }]); return ResourceEvent; }(); var Unit = /*#__PURE__*/function () { function Unit(_ref) { var id = _ref.id, buildTime = _ref.buildTime, _ref$canBuild = _ref.canBuild, canBuild = _ref$canBuild === void 0 ? false : _ref$canBuild, _ref$canHarvest = _ref.canHarvest, canHarvest = _ref$canHarvest === void 0 ? false : _ref$canHarvest, _ref$canMine = _ref.canMine, canMine = _ref$canMine === void 0 ? false : _ref$canMine, image = _ref.image, _ref$isHero = _ref.isHero, isHero = _ref$isHero === void 0 ? false : _ref$isHero, order = _ref.order, gold = _ref.gold, lumber = _ref.lumber, name = _ref.name, supply = _ref.supply, type = _ref.type; _classCallCheck(this, Unit); this._id = Unit._id++; this.id = id; this.buildTime = buildTime; this.canBuild = canBuild; this.canHarvest = canHarvest; this.canMine = canMine; this.gold = gold; this.isHero = isHero; this.order = order; this.image = image; this.lumber = lumber; this.name = name; this.supply = supply; this.type = type; } _createClass(Unit, null, [{ key: "resetIds", value: function resetIds() { Unit._id = 1; } }]); return Unit; }(); _defineProperty(Unit, "_id", 1); var Upgrade = function Upgrade(_ref) { var id = _ref.id, name = _ref.name, gold = _ref.gold, lumber = _ref.lumber, buildTime = _ref.buildTime, tier = _ref.tier, replaces = _ref.replaces, dependsOn = _ref.dependsOn, image = _ref.image; _classCallCheck(this, Upgrade); this.id = id; this.name = name; this.gold = gold; this.lumber = lumber; this.buildTime = buildTime; this.tier = tier; this.replaces = replaces; this.dependsOn = dependsOn; this.image = image; }; var EventsProcessor = /*#__PURE__*/function () { function EventsProcessor() { _classCallCheck(this, EventsProcessor); } _createClass(EventsProcessor, [{ key: "validateActions", value: function validateActions(actions, resourceList) { var accumulatedActions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; var processedActions = []; var _iterator = _createForOfIteratorHelper(actions), _step; try { var _loop = function _loop() { var action = _step.value; action.valid = true; action.error = null; switch (action.type) { case "build": // Check for sufficient resources if (resourceList.value.gold < action.meta.building.gold || resourceList.value.lumber < action.meta.building.lumber) { action.valid = false; action.error = "not enough resources"; } // Check if builder still exists if (!resourceList.value.units.find(function (u) { return u._id === action.meta.worker._id; })) { action.valid = false; action.error = "worker no longer exists"; } break; case "buy": // Check for sufficient resources if (resourceList.value.gold < action.meta.item.gold || resourceList.value.lumber < action.meta.item.lumber) { action.valid = false; action.error = "not enough resources"; } // Check if shop still exists if (!resourceList.value.buildings.find(function (b) { return b._id === action.meta.building._id; })) { action.valid = false; action.error = "shop no longer exists"; } // Check that we have the dependencies if (typeof action.meta.item.dependsOn !== "undefined" && !action.meta.item.dependsOn.every(function (dependency) { return resourceList.value.upgrades.some(function (completed) { return completed.id === dependency; }); })) { action.valid = false; action.error = "missing required upgrade"; } // Check that we haven't exceeded replenishment if ([].concat(_toConsumableArray(accumulatedActions), processedActions).filter(function (a) { return a.type === "buy" && a.meta.item.id === action.meta.item.id && action.start - a.start < action.meta.item.replenish; }).length >= action.meta.item.max) { action.valid = false; action.error = "exceeded item replenishment"; } break; case "train": if (resourceList.value.gold < action.meta.unit.gold || resourceList.value.lumber < action.meta.unit.lumber || resourceList.value.supply < action.meta.unit.supply) { action.valid = false; action.error = "not enough resources"; } if (action.meta.unit.type !== "neutralUnit" && !resourceList.value.buildings.find(function (b) { return b._id === action.meta.building._id && b.id === action.meta.building.id; })) { action.valid = false; action.error = "building no longer available for training"; } if (action.meta.unit.type !== "neutralUnit" && processedActions.filter(function (a) { return a.type === "train" && a.meta.building._id === action.meta.building._id && a.start + a.duration >= action.start && a.start + a.duration <= action.start + action.duration; }).length) { action.valid = false; action.error = "building is busy training"; } if (action.meta.unit.limit && action.meta.unit.limit <= processedActions.filter(function (a) { return a.type === "train" && a.meta.unit.id === action.meta.unit.id; }).length) { action.valid = false; action.error = "too many of this unit produced"; } // Check that we haven't exceeded replenishment if ([].concat(_toConsumableArray(accumulatedActions), processedActions).filter(function (a) { return a.type === "train" && a.meta.unit.id === action.meta.unit.id && action.start - a.start < action.meta.unit.replenish; }).length >= action.meta.unit.max) { action.valid = false; action.error = "exceeded unit replenishment"; } if (action.meta.unit.availability && action.meta.unit.limit < a.start) { action.valid = false; action.error = "unit being produced before available"; } break; case "assignToGold": if (!resourceList.value.units.find(function (u) { return u._id === action.meta.worker._id && u.id === action.meta.worker.id; })) { action.valid = false; action.error = "worker no longer available for mining"; } break; case "removeFromGold": if (!resourceList.value.miningWorkers.find(function (u) { return u._id === action.meta.worker._id && u.id === action.meta.worker.id; })) { action.valid = false; action.error = "worker no longer mining at this time"; } break; case "assignToLumber": if (!resourceList.value.units.find(function (u) { return u._id === action.meta.worker._id && u.id === action.meta.worker.id; })) { action.valid = false; action.error = "worker no longer available for harvesting"; } break; case "removeFromLumber": if (!resourceList.value.harvestingWorkers.find(function (u) { return u._id === action.meta.worker._id && u.id === action.meta.worker.id; })) { action.valid = false; action.error = "worker no longer harvesting at this time"; } break; case "upgrade": if (resourceList.value.gold < action.meta.upgrade.gold || resourceList.value.lumber < action.meta.upgrade.lumber) { action.valid = false; action.error = "not enough resources for upgrade"; } if (!action.meta.upgrade.dependsOn.buildings.every(function (dependency) { return resourceList.value.buildings.some(function (completed) { return completed.id === dependency; }); }) || !action.meta.upgrade.dependsOn.upgrades.every(function (dependency) { return resourceList.value.upgrades.some(function (completed) { return completed.id === dependency; }); })) { action.valid = false; action.error = "missing required building or upgrade"; } if (!!resourceList.value.upgrades.find(function (u) { return u.id === action.meta.upgrade.id; })) { action.valid = false; action.error = "upgrade already researched"; } if (action.meta.upgrade.replaces && !resourceList.value.upgrades.find(function (u) { return u.id === action.meta.upgrade.replaces; })) { action.valid = false; action.error = "missing earlier version of upgrade"; } } processedActions.push(action); }; for (_iterator.s(); !(_step = _iterator.n()).done;) { _loop(); } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } } }, { key: "processAction", value: function processAction(action) { var _immediateLoss$loseRe; var resourceEvents = []; switch (action.type) { case "build": var immediateLoss = new ResourceEvent(action.start, action._id); immediateLoss.loseResources({ gold: action.meta.building.gold, lumber: action.meta.building.lumber, units: [action.meta.worker] }); var futureGain = new ResourceEvent(action.start + action.duration, action._id); futureGain.gainResources({ supply: action.meta.building.supply, buildings: [new Building(action.meta.building)], units: [action.meta.worker] }); resourceEvents.push(immediateLoss, futureGain); break; case "buy": var immediateLoss = new ResourceEvent(action.start, action._id); immediateLoss.loseResources({ gold: action.meta.item.gold, lumber: action.meta.item.lumber || 0 // Neutral items do not cost lumber }); var immediateGain = new ResourceEvent(action.start + action.duration, action._id); immediateGain.gainResources({ items: [new Item(action.meta.item)] }); resourceEvents.push(immediateLoss, immediateGain); break; case "train": var immediateLoss = new ResourceEvent(action.start, action._id); immediateLoss.loseResources((_immediateLoss$loseRe = { supply: action.meta.unit.supply, gold: action.meta.unit.gold, lumber: action.meta.unit.lumber }, _defineProperty(_immediateLoss$loseRe, "supply", action.meta.unit.supply), _defineProperty(_immediateLoss$loseRe, "buildings", [action.meta.building]), _immediateLoss$loseRe)); var futureGain = new ResourceEvent(action.start + action.duration, action._id); futureGain.gainResources({ units: [new Unit(action.meta.unit)], buildings: [action.meta.building] }); resourceEvents.push(immediateLoss, futureGain); break; case "assignToGold": var immediateLoss = new ResourceEvent(action.start, action._id); immediateLoss.loseResources({ units: [action.meta.worker] }); var immediateGain = new ResourceEvent(action.start, action._id); immediateGain.gainResources({ miningWorkers: [action.meta.worker] }); resourceEvents.push(immediateGain, immediateLoss); break; case "removeFromGold": var immediateLoss = new ResourceEvent(action.start, action._id); immediateLoss.loseResources({ miningWorkers: [action.meta.worker] }); var immediateGain = new ResourceEvent(action.start, action._id); immediateGain.gainResources({ units: [action.meta.worker] }); resourceEvents.push(immediateGain, immediateLoss); break; case "assignToLumber": var immediateLoss = new ResourceEvent(action.start, action._id); immediateLoss.loseResources({ units: [action.meta.worker] }); var immediateGain = new ResourceEvent(action.start, action._id); immediateGain.gainResources({ harvestingWorkers: [action.meta.worker] }); resourceEvents.push(immediateGain, immediateLoss); break; case "removeFromLumber": var immediateLoss = new ResourceEvent(action.start, action._id); immediateLoss.loseResources({ harvestingWorkers: [action.meta.worker] }); var immediateGain = new ResourceEvent(action.start, action._id); immediateGain.gainResources({ units: [action.meta.worker] }); resourceEvents.push(immediateGain, immediateLoss); break; case "upgrade": var immediateLoss = new ResourceEvent(action.start, action._id); immediateLoss.loseResources({ buildings: [action.meta.building], gold: action.meta.upgrade.gold, lumber: action.meta.upgrade.lumber }); var futureGain = new ResourceEvent(action.start + action.duration, action._id); futureGain.gainResources({ upgrades: [new Upgrade(action.meta.upgrade)], buildings: [action.meta.building] }); resourceEvents.push(immediateLoss, futureGain); break; } return resourceEvents; } }]); return EventsProcessor; }(); var TickEvents = /*#__PURE__*/function () { function TickEvents(tick) { _classCallCheck(this, TickEvents); _defineProperty(this, "actions", []); this.tick = tick; } _createClass(TickEvents, [{ key: "addAction", value: function addAction(action) { this.actions.push(action); } }, { key: "removeAction", value: function removeAction(id) { var index = this.actions.findIndex(function (a) { return a._id === id; }); if (index >= 0) { this.actions.splice(index, 1); } } }]); return TickEvents; }(); var EventRegister = /*#__PURE__*/function () { function EventRegister(startingResources) { _classCallCheck(this, EventRegister); _defineProperty(this, "eventsProcessor", new EventsProcessor()); _defineProperty(this, "eventsIndices", []); _defineProperty(this, "events", {}); this.startingResources = startingResources; this.resetEvents(); } _createClass(EventRegister, [{ key: "eventsAt", value: function eventsAt(tick) { var indexLocation = this.eventsIndices.indexOf(tick); if (indexLocation < 0) { this.events[tick] = new TickEvents(tick); this.eventsIndices.push(tick); this.eventsIndices.sort(function (a, b) { return a - b; }); } return this.events[tick]; } }, { key: "resourceEventsAt", value: function resourceEventsAt(tick) { var indexLocation = this.resourceEventsIndices.indexOf(tick); if (indexLocation < 0) { this.resourceEvents[tick] = []; this.resourceEventsIndices.push(tick); this.resourceEventsIndices.sort(function (a, b) { return a - b; }); // Make an event so we stop for resource calculation this.eventsAt(tick); } return this.resourceEvents[tick]; } }, { key: "resetEvents", value: function resetEvents() { this.resourceEventsIndices = []; this.resourceEvents = {}; var toRemove = []; var _iterator = _createForOfIteratorHelper(this.eventsIndices), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var index = _step.value; if (!this.events[index].actions.length) { toRemove.push(index); } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } this.eventsIndices = this.eventsIndices.filter(function (ei) { return toRemove.indexOf(ei) === -1; }); for (var _i = 0, _toRemove = toRemove; _i < _toRemove.length; _i++) { var removeIndex = _toRemove[_i]; delete this.resourceEvents[removeIndex]; } var initialResourceEvent = new ResourceEvent(0, null); var startingGain = clonedeep(this.startingResources.value); Building.resetIds(); Item.resetIds(); Unit.resetIds(); startingGain.units.map(function (u) { return new Unit(u); }); startingGain.buildings.map(function (u) { return new Building(u); }); initialResourceEvent.gainResources(startingGain); initialResourceEvent.loseResources({ supply: 5 }); this.addResourceEvent(initialResourceEvent); } }, { key: "forEachEvent", value: function forEachEvent(cb) { for (var i = 0; i < this.eventsIndices.length; i++) { var index = this.eventsIndices[i]; cb(this.events[index], index); } } }, { key: "addAction", value: function addAction(action) { this.eventsAt(action.start).addAction(action); } }, { key: "removeAction", value: function removeAction(tick, id) { this.eventsAt(tick).removeAction(id); } }, { key: "addResourceEvent", value: function addResourceEvent(resourceEvent) { this.resourceEventsAt(resourceEvent.tick).push(resourceEvent); } }]); return EventRegister; }(); var ActionManager = /*#__PURE__*/function () { function ActionManager(race, resourcesManager) { _classCallCheck(this, ActionManager); _defineProperty(this, "eventsProcessor", new EventsProcessor()); this.eventsRegister = new EventRegister(race.startingResources); this.resourcesManager = resourcesManager; this.processEvents(); } _createClass(ActionManager, [{ key: "addAction", value: function addAction(action) { // Add the action event to the registry this.eventsRegister.addAction(action); // Reset our accumulated resources this.resourcesManager.reset(); // Reset previous resource events this.eventsRegister.resetEvents(); this.processEvents(); } }, { key: "processEvents", value: function processEvents() { var _this = this; var accumulatedActions = []; this.eventsRegister.forEachEvent(function (event, index) { // Sum up resource events added by previous actions. var gains = _this.eventsRegister.resourceEventsAt(event.tick).flatMap(function (re) { return re.resourceGain; }); var losses = _this.eventsRegister.resourceEventsAt(event.tick).flatMap(function (re) { return re.resourceLoss; }); // Get starting resources var resourcesForTick = new ResourceList(_this.resourcesManager.resourcesAt(event.tick)); // Add in all the new gains var _iterator = _createForOfIteratorHelper(gains), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var gain = _step.value; resourcesForTick.addValues(gain); } // Subtract any losses } catch (err) { _iterator.e(err); } finally { _iterator.f(); } var _iterator2 = _createForOfIteratorHelper(losses), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var loss = _step2.value; resourcesForTick.removeValues(loss); } // Set validity of existing actions } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } _this.eventsProcessor.validateActions(event.actions, resourcesForTick, accumulatedActions); // Process actions to get new resource events. var _iterator3 = _createForOfIteratorHelper(event.actions), _step3; try { for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { var eventAction = _step3.value; if (eventAction.valid) { var resourceEvents = _this.eventsProcessor.processAction(eventAction); var _iterator4 = _createForOfIteratorHelper(resourceEvents), _step4; try { for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) { var re = _step4.value; if (re.tick === event.tick) { resourcesForTick.addValues(re.resourceGain); resourcesForTick.removeValues(re.resourceLoss); } else { _this.eventsRegister.addResourceEvent(re); } } } catch (err) { _iterator4.e(err); } finally { _iterator4.f(); } } } // Save the resources value for this tick } catch (err) { _iterator3.e(err); } finally { _iterator3.f(); } _this.resourcesManager.setResources(event.tick, resourcesForTick); // Accumulate past actions for next event accumulatedActions = accumulatedActions.concat(event.actions); }); } }, { key: "removeAction", value: function removeAction(action) { // Remove the action from the registery this.eventsRegister.removeAction(action.start, action._id); // Reset our accumulated resources this.resourcesManager.reset(); // Reset previous resource events this.eventsRegister.resetEvents(); this.processEvents(); } }, { key: "build", value: function build(tick, buildingData, worker) { this.addAction(Action.build(tick, buildingData, worker)); } }, { key: "buy", value: function buy(tick, itemData, building) { this.addAction(Action.buy(tick, itemData, building)); } }, { key: "train", value: function train(tick, unitData, building) { this.addAction(Action.train(tick, unitData, building)); } }, { key: "upgrade", value: function upgrade(tick, upgradeData, building) { this.addAction(Action.upgrade(tick, upgradeData, building)); } }, { key: "assignToGold", value: function assignToGold(tick, worker) { this.addAction(Action.assignToGold(tick, worker)); } }, { key: "removeFromGold", value: function removeFromGold(tick, miner) { this.addAction(Action.removeFromGold(tick, miner)); } }, { key: "assignToLumber", value: function assignToLumber(tick, worker) { this.addAction(Action.assignToLumber(tick, worker)); } }, { key: "removeFromLumber", value: function removeFromLumber(tick, harvester) { this.addAction(Action.removeFromLumber(tick, harvester)); } }, { key: "getActions", value: function getActions() { var actions = []; this.eventsRegister.forEachEvent(function (e) { return actions.push.apply(actions, _toConsumableArray(e.actions)); }); return actions; } }]); return ActionManager; }(); var neutralBuildings = [{ id: "goblinMerchant", name: "Goblin Merchant", type: "building", units: [], items: ["circletOfNobility", "periaptOfVitality", "bootsOfSpeed", "dustOfAppearance", "scrollOfHealing", "scrollOfProtection", "scrollOfTownPortalNeutral", "potionOfInvisibility", "tomeOfRetraining", "staffOfTeleportation", "potionOfLesserInvulnerability"], image: "neutral/goblinmerchant.png" }, { id: "goblinLaboratory", name: "Goblin Laboratory", type: "building", units: ["goblinSapper", "goblinZeppelin", "goblinShredder"], items: [], image: "neutral/goblinlaboratory.png" }, { id: "tavern", name: "Tavern", type: "building", units: ["nagaSeaWitch", "darkRanger", "pandarenBrewmaster", "firelord", "beastmaster", "pitLord", "goblinTinker", "goblinAlchemist"], items: [], image: "neutral/tavern.png" }, { id: "mercenaryCamp", name: "Mercenary Camp", type: "building", units: ["forestTrollShadowPriest", "forestTrollBerserker", "mudGolem", "ogreMauler"], items: [], image: "neutral/mercenarycamp.png" }]; var neutralUnits = [{ id: "goblinSapper", name: "Goblin Sapper", type: "neutralUnit", isHero: false, gold: 215, lumber: 100, supply: 2, order: 1, max: 3, replenish: 45, availability: 440, image: "neutral/goblinsapper.gif" }, { id: "goblinZeppelin", name: "Goblin Zeppelin", type: "neutralUnit", isHero: false, gold: 240, lumber: 60, supply: 0, order: 2, max: 1, replenish: 30, availability: 440, image: "neutral/goblinzeppelin.gif" }, { id: "goblinShredder", name: "Goblin Shredder", type: "neutralUnit", isHero: false, gold: 375, lumber: 100, supply: 4, order: 3, max: 3, replenish: 80, availability: 300, image: "neutral/goblinshredder.gif" }, { id: "nagaSeaWitch", name: "Naga Sea Witch", type: "neutralUnit", isHero: true, gold: 375, lumber: 135, supply: 5, order: 1, max: 1, replenish: 0, availability: 135, image: "neutral/nagaseawitch.gif" }, { id: "darkRanger", name: "Dark Ranger", type: "neutralUnit", isHero: true, gold: 425, lumber: 135, supply: 5, order: 2, max: 1, replenish: 0, availability: 135, image: "neutral/darkranger.gif" }, { id: "pandarenBrewmaster", name: "Pandaren Brewmaster", type: "neutralUnit", isHero: true, gold: 425, lumber: 135, supply: 5, order: 3, max: 1, replenish: 0, availability: 135, image: "neutral/pandarenbrewmaster.gif" }, { id: "firelord", name: "Firelord", type: "neutralUnit", isHero: true, gold: 425, lumber: 135, supply: 5, order: 4, max: 1, availability: 135, image: "neutral/firelord.jpg" }, { id: "beastmaster", name: "Beastmaster", type: "neutralUnit", isHero: true, gold: 425, lumber: 135, supply: 5, order: 6, max: 1, availability: 135, image: "neutral/beastmaster.gif" }, { id: "pitLord", name: "Pit Lord", type: "neutralUnit", isHero: true, gold: 425, lumber: 135, supply: 5, order: 5, max: 1, availability: 135, image: "neutral/pitlord.gif" }, { id: "goblinTinker", name: "Goblin Tinker", type: "neutralUnit", isHero: true, gold: 425, lumber: 135, supply: 5, order: 7, max: 1, availability: 135, image: "neutral/goblintinker.gif" }, { id: "goblinAlchemist", name: "Goblin Alchemist", type: "neutralUnit", isHero: true, gold: 425, lumber: 135, supply: 5, order: 8, max: 1, availability: 135, image: "neutral/goblinalchemist.gif" }, { id: "forestTrollShadowPriest", name: "Forest Troll Shadow Priest", type: "neutralUnit", isHero: false, gold: 195, lumber: 10, supply: 2, order: 1, replenish: 110, max: 1, availability: 120, image: "neutral/foresttrollshadowpriest.gif" }, { id: "forestTrollBerserker", name: "Forest Troll Berserker", type: "neutralUnit", isHero: false, gold: 245, lumber: 30, supply: 3, order: 2, replenish: 210, max: 1, availability: 220, image: "neutral/foresttrol