diff --git a/.npmignore b/.npmignore index 93c947c2e..f3fbb83a1 100644 --- a/.npmignore +++ b/.npmignore @@ -4,4 +4,6 @@ !LICENSE.txt !src/* !index.js +!index.d.ts +!types/* !package* diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 000000000..7add8e635 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,104 @@ +export { Account, AccountConfig } from './types/Account'; +export { AccountManager } from './types/AccountManager'; +export { Area } from './types/Area'; +export { AreaAudience } from './types/AreaAudience'; +export { AreaFactory } from './types/AreaFactory'; +export { AreaFloor } from './types/AreaFloor'; +export { AreaManager } from './types/AreaManager'; +export { AreaOfEffectDamage } from './types/AreaOfEffectDamage'; +export { AreaOfEffectHeal } from './types/AreaOfEffectHeal'; +export { Attribute, AttributeFormula } from './types/Attribute'; +export { AttributeFactory } from './types/AttributeFactory'; +export { Attributes } from './types/Attributes'; +export { BehaviorManager } from './types/BehaviorManager'; +export { Broadcast, Broadcastable } from './types/Broadcast'; +export { BundleManager } from './types/BundleManager'; +export { + ChannelConfig, + Channel, + NoMessageError, + NoPartyError, + NoRecipientError, +} from './types/Channel'; +export { AudienceOptions, ChannelAudience } from './types/ChannelAudience'; +export { ChannelManager } from './types/ChannelManager'; +export { Character, CharacterConfig } from './types/Character'; +export { CommandManager } from './types/CommandManager'; +export { CommandExecutable, CommandQueue } from './types/CommandQueue'; +export { CommandType } from './types/CommandType'; +export { Config } from './types/Config'; +export { Command } from './types/Command'; +export { Damage } from './types/Damage'; +export { Data } from './types/Data'; +export { DataSourceRegistry } from './types/DataSourceRegistry'; +export { DataSource } from './types/DataSource'; +export { Effect, EffectConfig, EffectModifiers } from './types/Effect'; +export { EffectableEntity } from './types/EffectableEntity'; +export { + EffectConfig as EffectFactoryType, + EffectFactory, +} from './types/EffectFactory'; +export { EffectFlag } from './types/EffectFlag'; +export { EffectList } from './types/EffectList'; +export { EntityFactory } from './types/EntityFactory'; +export { EntityLoader } from './types/EntityLoader'; +export { EntityLoaderRegistry } from './types/EntityLoaderRegistry'; +export { EntityReference } from './types/EntityReference'; +export { EventManager } from './types/EventManager'; +export { EventUtil } from './types/EventUtil'; +export { + EquipAlreadyEquippedError, + EquipSlotTakenError, +} from './types/EquipErrors'; +export { GameEntity } from './types/GameEntity'; +export { GameServer } from './types/GameServer'; +export { GameState } from './types/GameState'; +export { Heal } from './types/Heal'; +export { Helpfile, HelpfileOptions } from './types/Helpfile'; +export { HelpManager } from './types/HelpManager'; +export { Item } from './types/Item'; +export { ItemManager } from './types/ItemManager'; +export { Inventory, InventoryFullError } from './types/Inventory'; +export { ItemFactory } from './types/ItemFactory'; +export { Logger } from './types/Logger'; +export { Metadatable, MetadatableClass } from './types/Metadatable'; +export { MobFactory } from './types/MobFactory'; +export { MobManager } from './types/MobManager'; +export { Npc } from './types/Npc'; +export { Party } from './types/Party'; +export { PartyManager } from './types/PartyManager'; +export { PartyAudience } from './types/PartyAudience'; +export { Player } from './types/Player'; +export { PlayerManager } from './types/PlayerManager'; +export { PlayerRoles } from './types/PlayerRoles'; +export { PrivateAudience } from './types/PrivateAudience'; +export { Quest, QuestConfig } from './types/Quest'; +export { QuestFactory } from './types/QuestFactory'; +export { + QuestGoal, + QuestGoalConfig, + QuestGoalProgress, + QuestGoalSerialized, +} from './types/QuestGoal'; +export { QuestGoalManager } from './types/QuestGoalManager'; +export { QuestReward, QuestRewardConfig } from './types/QuestReward'; +export { QuestRewardManager } from './types/QuestRewardManager'; +export { QuestTracker, SerializedQuestTracker } from './types/QuestTracker'; +export { RoleAudience} from './types/RoleAudience'; +export { Room, Door, Exit } from './types/Room'; +export { RoomAudience } from './types/RoomAudience'; +export { RoomFactory } from './types/RoomFactory'; +export { RoomManager } from './types/RoomManager'; +export { Scriptable, ScriptableClass } from './types/Scriptable'; +export { Skill } from './types/Skill'; +export { + CooldownError, + NotEnoughResourcesError, + PassiveError, +} from './types/SkillErrors'; +export { SkillFlag } from './types/SkillFlag'; +export { SkillManager } from './types/SkillManager'; +export { SkillType } from './types/SkillType'; +export { TransportStream } from './types/TransportStream'; +export { Util } from './types/Util'; +export { WorldAudience } from './types/WorldAudience'; diff --git a/package-lock.json b/package-lock.json index 52e6729ad..3140ff81c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -136,6 +136,12 @@ "to-fast-properties": "^2.0.0" } }, + "@types/node": { + "version": "10.17.48", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.48.tgz", + "integrity": "sha512-Agl6xbYP6FOMDeAsr3QVZ+g7Yzg0uhPHWx0j5g4LFdUBHVtqtU+gH660k/lCEe506jJLOGbEzsnqPDTZGJQLag==", + "dev": true + }, "ajv": { "version": "6.6.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz", @@ -878,6 +884,7 @@ "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -1247,7 +1254,8 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "dev": true, + "optional": true }, "is-builtin-module": { "version": "1.0.0", @@ -1343,6 +1351,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -1395,7 +1404,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true + "dev": true, + "optional": true }, "lru-cache": { "version": "4.1.3", @@ -1698,7 +1708,8 @@ "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true + "dev": true, + "optional": true }, "require-directory": { "version": "2.1.1", @@ -2381,6 +2392,12 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "dev": true }, + "typescript": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.2.tgz", + "integrity": "sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ==", + "dev": true + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", diff --git a/package.json b/package.json index 9279bbf70..26d31e7ed 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "coverage": "nyc report --reporter=text-lcov | coveralls" }, "main": "index.js", + "types": "index.d.ts", "dependencies": { "bcryptjs": "^2.4.0", "js-yaml": "^3.12.0", @@ -29,6 +30,8 @@ "devDependencies": { "coveralls": "^3.0.2", "mocha": "^5.2.0", - "nyc": "^13.1.0" + "nyc": "^13.1.0", + "@types/node": "^10.12", + "typescript": "4.1.2" } } diff --git a/src/Area.js b/src/Area.js index c8334653d..3fe68c243 100644 --- a/src/Area.js +++ b/src/Area.js @@ -146,7 +146,7 @@ class Area extends GameEntity { * This method is automatically called every N milliseconds where N is defined in the * `setInterval` call to `GameState.AreaMAnager.tickAll` in the `ranvier` executable. It, in turn, * will fire the `updateTick` event on all its rooms and npcs - * + * * @param {GameState} state * @fires Room#updateTick * @fires Npc#updateTick diff --git a/src/Attribute.js b/src/Attribute.js index 192a72286..78b442017 100644 --- a/src/Attribute.js +++ b/src/Attribute.js @@ -22,8 +22,8 @@ class Attribute { * @param {object} metadata={} */ constructor(name, base, delta = 0, formula = null, metadata = {}) { - if (isNaN(base)) { - throw new TypeError(`Base attribute must be a number, got ${base}.`); + if (isNaN(base)) { + throw new TypeError(`Base attribute must be a number, got ${base}.`); } if (isNaN(delta)) { throw new TypeError(`Attribute delta must be a number, got ${delta}.`); @@ -66,7 +66,7 @@ class Attribute { /** * Bypass raise/lower, directly setting the delta - * @param {amount} + * @param {number} amount */ setDelta(amount) { this.delta = Math.min(amount, 0); diff --git a/src/AttributeFactory.js b/src/AttributeFactory.js index 3ab54f5f3..012790b74 100644 --- a/src/AttributeFactory.js +++ b/src/AttributeFactory.js @@ -46,6 +46,7 @@ class AttributeFactory { /** * @param {string} name + * @param {number} base * @param {number} delta * @return {Attribute} */ diff --git a/src/BehaviorManager.js b/src/BehaviorManager.js index e34d80363..667a18804 100644 --- a/src/BehaviorManager.js +++ b/src/BehaviorManager.js @@ -31,6 +31,7 @@ class BehaviorManager { /** * @param {string} behaviorName + * @param {string} event * @param {Function} listener */ addListener(behaviorName, event, listener) { diff --git a/src/BundleManager.js b/src/BundleManager.js index 042520b5e..61d9c94ea 100644 --- a/src/BundleManager.js +++ b/src/BundleManager.js @@ -26,6 +26,7 @@ const srcPath = __dirname + '/'; */ class BundleManager { /** + * @param {string} path * @param {GameState} state */ constructor(path, state) { @@ -41,6 +42,7 @@ class BundleManager { /** * Load in all bundles + * @param {boolean} distribute */ async loadBundles(distribute = true) { Logger.verbose('LOAD: BUNDLES'); @@ -317,7 +319,7 @@ class BundleManager { * @param {string} areaName * @param {string} type * @param {EntityFactory} factory - * @return {Array} + * @return {Array} */ async loadEntities(bundle, areaName, type, factory) { const loader = this.loaderRegistry.get(type); @@ -355,7 +357,7 @@ class BundleManager { /** * @param {EntityFactory} factory Instance of EntityFactory that the item/npc will be loaded into - * @param {EntityReference} entityRef + * @param {string} entityRef * @param {string} scriptPath */ loadEntityScript(factory, entityRef, scriptPath) { @@ -369,9 +371,9 @@ class BundleManager { } /** + * @param {string} bundle * @param {string} areaName - * @param {string} questsFile - * @return {Promise>} + * @return {Promise>} */ async loadQuests(bundle, areaName) { const loader = this.loaderRegistry.get('quests'); @@ -455,7 +457,6 @@ class BundleManager { /** * @param {string} bundle - * @param {string} helpDir */ async loadHelp(bundle) { Logger.verbose(`\tLOAD: Help...`); diff --git a/src/Channel.js b/src/Channel.js index b0b7ed8c7..0be0d224a 100644 --- a/src/Channel.js +++ b/src/Channel.js @@ -122,6 +122,7 @@ class Channel { * How to render the message the player just sent to the channel * E.g., you may want "chat" to say "You chat, 'message here'" * @param {Player} sender + * @param {Player} target * @param {string} message * @param {Function} colorify * @return {string} diff --git a/src/CommandQueue.js b/src/CommandQueue.js index 3a8210025..80683002f 100644 --- a/src/CommandQueue.js +++ b/src/CommandQueue.js @@ -26,6 +26,7 @@ class CommandQueue { /** * @param {CommandExecutable} executable Thing to run with an execute and a queue label * @param {number} lag Amount of lag to apply to the queue after the command is run + * @returns {number} */ enqueue(executable, lag) { let newIndex = this.commands.push(Object.assign(executable, { lag })) - 1; @@ -100,7 +101,6 @@ class CommandQueue { /** * For a given command index find how many seconds until it will run * @param {number} commandIndex - * @param {boolean} milliseconds * @return {number} */ getTimeTilRun(commandIndex) { diff --git a/src/Effect.js b/src/Effect.js index 67cad18c8..c23ba113b 100644 --- a/src/Effect.js +++ b/src/Effect.js @@ -109,7 +109,7 @@ class Effect extends EventEmitter { * Elapsed time in milliseconds since event was activated * @type {number} */ - get elapsed () { + get elapsed() { if (!this.startedAt) { return null; } diff --git a/src/Item.js b/src/Item.js index 9dcaec246..283a656bb 100644 --- a/src/Item.js +++ b/src/Item.js @@ -5,8 +5,7 @@ const uuid = require('uuid/v4'); const GameEntity = require('./GameEntity'); const ItemType = require('./ItemType'); const Logger = require('./Logger'); -const Metadatable = require('./Metadatable'); -const Player = require('./Player'); + const { Inventory, InventoryFullError } = require('./Inventory'); /** diff --git a/src/PlayerManager.js b/src/PlayerManager.js index 21a99cdbf..9fc51d555 100644 --- a/src/PlayerManager.js +++ b/src/PlayerManager.js @@ -83,11 +83,11 @@ class PlayerManager extends EventEmitter { } /** - * @param {Function} fn Filter function - * @return {array} + * @param {Function} predicate Filter function + * @return {array}, */ - filter(fn) { - return this.getPlayersAsArray().filter(fn); + filter(predicate) { + return this.getPlayersAsArray().filter(predicate); } /** diff --git a/test/unit/Account.js b/test/unit/Account.js new file mode 100644 index 000000000..cd096064a --- /dev/null +++ b/test/unit/Account.js @@ -0,0 +1,137 @@ +const assert = require('assert'); +const Account = require('../../src/Account'); + +describe('Account', function () { + /** @param {AccountDef} account */ + let account = null; + /** @param {AccountConfig} mockedAccountData */ + const mockedAccountData = { + username: 'testusername', + characters: [], + password: null, + }; + const testChar = { + deleted: false, + username: 'testchar', + }; + const testPass = 'testpass'; + + const originalDateNow = Date.now; + + beforeEach(function (done) { + account = new Account(mockedAccountData); + done(); + }); + + afterEach(function (done) { + Date.now = originalDateNow; + done(); + }); + + describe('#base methods', function () { + it('should be an instance of account', function (done) { + assert.equal('Account', account.constructor.name); + done(); + }); + }); + + describe('#properties', function () { + it('should get username', function (done) { + assert.equal(mockedAccountData.username, account.getUsername()); + done(); + }); + + it('should set a password', function (done) { + /** + * setPassword function also try to save file on disk, + * but it don't find it and raise an exception + */ + assert.throws(() => account.setPassword(testPass)); + done(); + }); + + it('should check a password', function (done) { + assert.throws(() => account.setPassword(testPass)); + assert.equal(true, account.checkPassword(testPass)); + done(); + }); + + it('should save data', function (done) { + /** + * save function also try to save file on disk, + * but it don't find it and raise an exception + */ + assert.throws(() => account.save(() => {})); + done(); + }); + + it('should mark account as banned', function (done) { + /** + * ban function also try to save file on disk, + * but it don't find it and raise an exception + */ + assert.throws(() => account.ban(() => {})); + assert.equal(true, account.banned); + done(); + }); + + it('should delete accont and its characters', function (done) { + /** + * save function also try to save file on disk, + * but it don't find it and raise an exception + */ + assert.throws(() => account.deleteAccount(() => {})); + assert.equal(true, account.deleted); + assert.deepEqual([], account.characters); + done(); + }); + + it('should serialize and return accont data', function (done) { + assert.deepEqual( + { ...mockedAccountData, metadata: {} }, + account.serialize() + ); + done(); + }); + }); + + describe('#characters', function () { + it('should add a character to account', function (done) { + assert.equal(undefined, account.hasCharacter(testChar.username)); + account.addCharacter(testChar.username); + assert.deepEqual(testChar, account.hasCharacter(testChar.username)); + done(); + }); + + it('should delete a character from account', function (done) { + assert.deepEqual(testChar, account.hasCharacter(testChar.username)); + /** + * deleteCaracter function also try to save file on disk, + * but it don't find it and raise an exception + */ + assert.throws(() => account.deleteCharacter(testChar.username)); + assert.deepEqual( + { ...testChar, deleted: true }, + account.hasCharacter(testChar.username) + ); + done(); + }); + + it('should undelete a character from account', function (done) { + assert.deepEqual( + { ...testChar, deleted: true }, + account.hasCharacter(testChar.username) + ); + /** + * undeleteCaracter function also try to save file on disk, + * but it don't find it and raise an exception + */ + assert.throws(() => account.undeleteCharacter(testChar.username)); + assert.deepEqual( + { ...testChar, deleted: false }, + account.hasCharacter(testChar.username) + ); + done(); + }); + }); +}); diff --git a/types/Account.d.ts b/types/Account.d.ts new file mode 100644 index 000000000..7dcf6fd2c --- /dev/null +++ b/types/Account.d.ts @@ -0,0 +1,99 @@ +export declare interface AccountConfig { + /** @property {string} username */ + username: string; + /** @property {Array} characters List of character names in this account */ + characters?: string[]; + /** @property {string} password Hashed password */ + password: string; + /** @property {boolean} banned Whether this account is banned or not */ + banned?: boolean; + /** @property {boolean} deleted Whether this account is deleted or not */ + deleted?: boolean; + // Arbitrary data bundles are free to shove whatever they want in + // WARNING: values must be JSON.stringify-able + metadata?: Record; +} + +export declare interface SerializedAccount { + username: string; + characters: string[]; + password: string; + metadata: Record; +} + +export declare class Account { + /** @property {string} username */ + username: string; + /** @property {Array} characters List of character names in this account */ + characters: string[]; + /** @property {string} password Hashed password */ + password: string; + /** @property {boolean} banned Whether this account is banned or not */ + banned: boolean; + + /** + * @param {AccountConfig} data Account save data + */ + constructor(data: AccountConfig); + + /** + * @return {string} + */ + getUsername(): string; + + /** + * @param {string} username + */ + addCharacter(username: string): void; + + /** + * @param {string} name + * @return {boolean} + */ + hasCharacter(name: string): boolean; + + /** + * @param {string} name Delete one of the chars + */ + deleteCharacter(name: string): void; + + /** + * @param {string} name Removes the deletion of one of the chars + */ + undeleteCharacter(name: string): void; + + /** + * @param {string} password Unhashed password. Is hashed inside this function + */ + setPassword(pass: string): void; + + /** + * @param {string} pass Unhashed password to check against account's password + * @return {boolean} + */ + checkPassword(pass: string): boolean; + + /** + * @param {?function} callback after-save callback + */ + save(callback?: () => void): void; + + /** + * Set this account to banned + There is no unban because this can just be done by manually editing the account file + */ + ban(): void; + + /** + * Set this account to deleted + There is no undelete because this can just be done by manually editing the account file + */ + deleteAccount(): void; + + /** + * Gather data from account object that will be persisted to disk + * + * @return {Object} + */ + serialize(): SerializedAccount; +} diff --git a/types/AccountManager.d.ts b/types/AccountManager.d.ts new file mode 100644 index 000000000..d719ea484 --- /dev/null +++ b/types/AccountManager.d.ts @@ -0,0 +1,34 @@ +import { Account } from './Account'; +import { EntityLoader} from './EntityLoader'; + +export declare class AccountManager { + /** @property {Map} accounts */ + accounts: Map; + /** @property {EntityLoader} loader */ + loader: EntityLoader; + + constructor(); + + /** + * Set the entity loader from which accounts are loaded + * @param {EntityLoader} + */ + setLoader(loader: EntityLoader): void; + + /** + * @param {Account} acc + */ + addAccount(acc: Account): void; + + /** + * @param {string} username + * @return {Account|undefined} + */ + getAccount(username: string): Account|undefined; + + /** + * @param {string} username + * @param {boolean} force Force reload data from disk + */ + loadAccount(username: string, force: boolean): Promise; +} \ No newline at end of file diff --git a/types/Area.d.ts b/types/Area.d.ts new file mode 100644 index 000000000..6072daa63 --- /dev/null +++ b/types/Area.d.ts @@ -0,0 +1,105 @@ +import { AreaFloor } from './AreaFloor'; +import { Broadcastable } from './Broadcast'; +import { GameEntity } from './GameEntity'; +import { GameState } from './GameState'; +import { Npc } from './Npc'; +import { Room } from './Room'; + +export declare class Area extends GameEntity { + /** Bundle this area comes from */ + bundle: string; + /** @property {string} name */ + name: string + /** @property {string} title */ + title: string; + /** @property {string} script A custom script for this item */ + script: string; + /** @property {Map} map a Map object keyed by the floor z-index, each floor is an array with [x][y] indexes for coordinates. */ + map: Map; + /** Map of room id to Room */ + rooms: Map; + /** Active NPCs that originate from this area. Note: this is NPCs that */ + npcs: Set; + /** Area configuration */ + info: Object + /** milliseconds since last respawn tick. See {@link Area#update} */ + lastRespawnTick: number; + + constructor(bundle, name, manifest); + + /** + * Get ranvier-root-relative path to this area + * @return {string} + */ + get areaPath(): string; + + /** + * Get an ordered list of floors in this area's map + * @return {Array} + */ + get floors(): Array; + + /** + * @param {string} id Room id + * @return {Room|undefined} + */ + getRoomById(id: string): Room|undefined; + + /** + * @param {Room} room + * @fires Area#roomAdded + */ + addRoom(room: Room): void; + + /** + * @param {Room} room + * @fires Area#roomRemoved + */ + removeRoom(room: Room): void + + /** + * @param {Room} room + * @throws Error + */ + addRoomToMap(room: Room): void; + + /** + * find a room at the given coordinates for this area + * @param {number} x + * @param {number} y + * @param {number} z + * @return {Room|boolean} + */ + getRoomAtCoordinates(x: number, y: number, z: number): Room|boolean; + + /** + * @param {Npc} npc + */ + addNpc(npc: Npc): void; + + /** + * Removes an NPC from the area. NOTE: This must manually remove the NPC from its room as well + * @param {Npc} npc + */ + removeNpc(npc: Npc): void; + + /** + * This method is automatically called every N milliseconds where N is defined in the + * `setInterval` call to `GameState.AreaMAnager.tickAll` in the `ranvier` executable. It, in turn, + * will fire the `updateTick` event on all its rooms and npcs + * + * @param {GameState} state + * @fires Room#updateTick + * @fires Npc#updateTick + */ + update(state: GameState): void; + + hydrate(state): void; + + /** + * Get all possible broadcast targets within an area. This includes all npcs, + * players, rooms, and the area itself + * @return {Array} + */ + getBroadcastTargets(): Array; +} \ No newline at end of file diff --git a/types/AreaAudience.d.ts b/types/AreaAudience.d.ts new file mode 100644 index 000000000..3b29a4e26 --- /dev/null +++ b/types/AreaAudience.d.ts @@ -0,0 +1,11 @@ +import { ChannelAudience } from './ChannelAudience'; +import { Player } from './Player'; + +/** + * Audience class representing characters in the same area as the sender + * @memberof ChannelAudience + * @extends ChannelAudience + */ +export declare class AreaAudience extends ChannelAudience { + getBroadcastTargets(): Array; +} \ No newline at end of file diff --git a/types/AreaFactory.d.ts b/types/AreaFactory.d.ts new file mode 100644 index 000000000..f016ba0e1 --- /dev/null +++ b/types/AreaFactory.d.ts @@ -0,0 +1,19 @@ +import { Area } from './Area'; +import { EntityFactory } from './EntityFactory'; + +export declare class AreaFactory extends EntityFactory { + /** + * Create a new instance of an area by name. Resulting area will not have + * any of its contained entities (items, npcs, rooms) hydrated. You will + * need to call `area.hydrate(state)` + * + * @param {string} entityRef Area name + * @return {Area} + */ + create(entityRef: string): Area; + + /** + * @see AreaFactory#create + */ + clone(area: Area): Area; +} \ No newline at end of file diff --git a/types/AreaFloor.d.ts b/types/AreaFloor.d.ts new file mode 100644 index 000000000..9f4241460 --- /dev/null +++ b/types/AreaFloor.d.ts @@ -0,0 +1,25 @@ +import { Room } from './Room'; + +export declare class AreaFloor { + /** @property {number} lowX The lowest x value */ + lowX: number; + /** @property {number} highX The highest x value */ + highX: number; + /** @property {number} lowY The lowest y value */ + lowY: number; + /** @property {number} highY The highest y value */ + highY: number; + /** @property {number} z This floor's z index */ + z: number; + + constructor(z: number); + + addRoom(x: number, y: number, room: Room): void; + + /** + * @return {Room|boolean} + */ + getRoom(x: number, y: number): Room; + + removeRoom(x: number, y: number): void; +} \ No newline at end of file diff --git a/types/AreaManager.d.ts b/types/AreaManager.d.ts new file mode 100644 index 000000000..76e036a65 --- /dev/null +++ b/types/AreaManager.d.ts @@ -0,0 +1,46 @@ +import { Area } from "./Area"; +import { GameState } from "./GameState"; + +export declare class AreaManager { + /** @property {Map} areas */ + areas: Map; + + constructor(); + + /** + * @param {string} name + * @return Area + */ + getArea(name: string): Area; + + /** + * @param {string} entityRef + * @return Area + */ + getAreaByReference(entityRef: string): Area; + + /** + * @param {Area} area + */ + addArea(area: Area): void; + + /** + * @param {Area} area + */ + removeArea(area: Area): void; + + /** + * Apply `updateTick` to all areas in the game + * @param {GameState} state + * @fires Area#updateTick + */ + tickAll(state: GameState): void; + + /** + * Get the placeholder area used to house players who were loaded into + * an invalid room + * + * @return {Area} + */ + getPlaceholderArea(): Area; +} \ No newline at end of file diff --git a/types/AreaOfEffectDamage.d.ts b/types/AreaOfEffectDamage.d.ts new file mode 100644 index 000000000..aeca53f28 --- /dev/null +++ b/types/AreaOfEffectDamage.d.ts @@ -0,0 +1,20 @@ +import { Character } from './Character'; +import { Damage } from './Damage'; +import { Room } from './Room'; + +export declare class AreaOfEffectDamage extends Damage { + /** + * @param {Room|Character} target + * @throws RangeError + * @fires Room#areaDamage + */ + commit(room: Room|Character): void; + + /** + * Override this method to customize valid targets such as + * only targeting hostile npcs, or only targeting players, etc. + * @param {Room} room + * @return {Array} + */ + getValidTargets(room: Room): Array; +} \ No newline at end of file diff --git a/types/AreaOfEffectHeal.d.ts b/types/AreaOfEffectHeal.d.ts new file mode 100644 index 000000000..5c460d7f2 --- /dev/null +++ b/types/AreaOfEffectHeal.d.ts @@ -0,0 +1,20 @@ +import { Character } from './Character'; +import { Heal } from './Heal'; +import { Room } from './Room'; + +export declare class AreaOfEffectHeal extends Heal { + /** + * @param {Room|Character} target + * @throws RangeError + * @fires Room#areaDamage + */ + commit(room: Room|Character): void; + + /** + * Override this method to customize valid targets such as + * only targeting hostile npcs, or only targeting players, etc. + * @param {Room} room + * @return {Array} + */ + getValidTargets(room: Room): Array; +} \ No newline at end of file diff --git a/types/Attribute.d.ts b/types/Attribute.d.ts new file mode 100644 index 000000000..d339d123f --- /dev/null +++ b/types/Attribute.d.ts @@ -0,0 +1,51 @@ +export declare class Attribute { + /** @property {string} name */ + name: string; + /** @property {number} base */ + base: number; + /** @property {number} delta Current difference from the base */ + delta: number; + /** @property {AttributeFormula} formula */ + formula: AttributeFormula; + /** @property {object} metadata any custom info for this attribute */ + metadata: object; + + constructor(name: string, base: number, delta: number, formula: AttributeFormula, metadata: object); + + /** + * Lower current value + * @param {number} amount + */ + lower(amount: number): void; + + /** + * Raise current value + * @param {number} amount + */ + raise(amount: number): void; + + /** + * Change the base value + * @param {number} amount + */ + setBase(amount: number): void; + + /** + * Bypass raise/lower, directly setting the delta + * @param {number} amount + */ + setDelta(amount: number): void; + + serialize(): object; +} + +export declare class AttributeFormula { + /** @property {Array} requires Array of attributes required for this formula to run */ + requires: Array; + /** @property {function (...number) : number} formula */ + formula: Function; + + constructor(requires: Array, fn: Function); + + evaluate(attribute, ...args): any; +} \ No newline at end of file diff --git a/types/AttributeFactory.d.ts b/types/AttributeFactory.d.ts new file mode 100644 index 000000000..79dce0b2a --- /dev/null +++ b/types/AttributeFactory.d.ts @@ -0,0 +1,39 @@ +import { Attribute, AttributeFormula } from './Attribute'; + +export declare class AttributeFactory { + /** @property {Map} attributes */ + attributes: Map; + constructor(); + + /** + * @param {string} name + * @param {number} base + * @param {AttributeFormula} formula + */ + add(name: string, base: number, formula: AttributeFormula, metadata: object): void + + /** + * @see Map#has + */ + has(name: string): boolean; + + /** + * Get a attribute definition. Use `create` if you want an instance of a attribute + * @param {string} name + * @return {object} + */ + get(name: string): object; + + /** + * @param {string} name + * @param {number} delta + * @return {Attribute} + */ + create(name: string, base: number, delta: number): Attribute; + + /** + * Make sure there are no circular dependencies between attributes + * @throws Error + */ + validateAttributes(): Map; +} \ No newline at end of file diff --git a/types/Attributes.d.ts b/types/Attributes.d.ts new file mode 100644 index 000000000..7225023bf --- /dev/null +++ b/types/Attributes.d.ts @@ -0,0 +1,24 @@ +import { Attribute } from "./Attribute"; + +export declare class Attributes extends Map { + /** + * @param {Attribute} attribute + */ + add(attribute: Attribute): void; + + /** + * @return {Iterator} see {@link Map#entries} + */ + getAttributes(): Iterator; + + /** + * Clear all deltas for all attributes in the list + */ + clearDeltas(): void; + + /** + * Gather data that will be persisted + * @return {Object} + */ + serialize(): Object; +} \ No newline at end of file diff --git a/types/BehaviorManager.d.ts b/types/BehaviorManager.d.ts new file mode 100644 index 000000000..a4d102a1d --- /dev/null +++ b/types/BehaviorManager.d.ts @@ -0,0 +1,26 @@ +import { EventManager } from './EventManager'; + +export declare class BehaviorManager { + constructor(); + + /** + * Get EventManager for a given behavior + * @param {string} name + * @return {EventManager} + */ + get(name: string): EventManager; + + /** + * Check to see if a behavior exists + * @param {string} name + * @return {boolean} + */ + has(name: string): boolean; + + /** + * @param {string} behaviorName + * @param {string} event + * @param {Function} listener + */ + addListener(behaviorName: string, event: string, listener: Function) +} \ No newline at end of file diff --git a/types/Broadcast.d.ts b/types/Broadcast.d.ts new file mode 100644 index 000000000..d0251f822 --- /dev/null +++ b/types/Broadcast.d.ts @@ -0,0 +1,115 @@ +import { Player } from './Player'; + +/** @typedef {{getBroadcastTargets: function(): Array}} */ +export declare type Broadcastable = { getBroadcastTargets: Array } + +export declare class Broadcast { + /** + * @param {Broadcastable} source Target to send the broadcast to + * @param {string} message + * @param {number|boolean} wrapWidth=false width to wrap the message to or don't wrap at all + * @param {boolean} useColor Whether to parse color tags in the message + * @param {?function(target, message): string} formatter=null Function to call to format the + * message to each target + */ + static at(source: Broadcastable, message: string, wrapWidth: boolean, useColor: boolean, formatter: Function): void; + + /** + * Broadcast.at for all except given list of players + * @see {@link Broadcast#at} + * @param {Broadcastable} source + * @param {string} message + * @param {Array} excludes + * @param {number|boolean} wrapWidth + * @param {boolean} useColor + * @param {function} formatter + */ + static atExcept(source: Broadcastable, message: string, excludes: Array, wrapWidth: number|boolean, useColor: boolean, formatter: Function): void; + + /** + * Helper wrapper around Broadcast.at to be used when you're using a formatter + * @see {@link Broadcast#at} + * @param {Broadcastable} source + * @param {string} message + * @param {function} formatter + * @param {number|boolean} wrapWidth + * @param {boolean} useColor + */ + static atFormatted(source: Broadcastable, message: string, formatter: Function, wrapWidth: number|boolean, useColor: boolean): void; + + /** + * `Broadcast.at` with a newline + * @see {@link Broadcast#at} + */ + static sayAt(source: Broadcastable, message: string, wrapWidth: number|boolean, useColor: boolean, formatter: Function): void; + + /** + * `Broadcast.atExcept` with a newline + * @see {@link Broadcast#atExcept} + */ + static sayAtExcept(source: Broadcastable, message: string, excludes, wrapWidth: number|boolean, useColor: boolean, formatter: Function): void; + + /** + * `Broadcast.atFormatted` with a newline + * @see {@link Broadcast#atFormatted} + */ + static sayAtFormatted(source: Broadcastable, message: string, formatter: Function, wrapWidth: number|boolean, useColor: boolean): void; + + /** + * Render the player's prompt including any extra prompts + * @param {Player} player + * @param {object} extra extra data to avail to the prompt string interpolator + * @param {number} wrapWidth + * @param {boolean} useColor + */ + static prompt(player: Player, extra: Object, wrapWidth: number, useColor: boolean): void; + + /** + * Generate an ASCII art progress bar + * @param {number} width Max width + * @param {number} percent Current percent + * @param {string} color + * @param {string} barChar Character to use for the current progress + * @param {string} fillChar Character to use for the rest + * @param {string} delimiters Characters to wrap the bar in + * @return {string} + */ + static progress(width: number, percent: number, color: string, barChar: string, fillChar: string, delimiters: string): string; + + /** + * Center a string in the middle of a given width + * @param {number} width + * @param {string} message + * @param {string} color + * @param {?string} fillChar Character to pad with, defaults to ' ' + * @return {string} + */ + static center(width: number, message: string, color: string, fillChar: string): string; + + /** + * Render a line of a specific width/color + * @param {number} width + * @param {string} fillChar + * @param {?string} color + * @return {string} + */ + static line(width: number, fillChar: string, color: string): string; + + /** + * Wrap a message to a given width. Note: Evaluates color tags + * @param {string} message + * @param {?number} width Defaults to 80 + * @return {string} + */ + static wrap(message: string, width: number): string; + + /** + * Indent all lines of a given string by a given amount + * @param {string} message + * @param {number} indent + * @return {string} + */ + static indent(message: string, indent: number): string; + + static isBroadcastable(source: Broadcastable): boolean; +} \ No newline at end of file diff --git a/types/BundleManager.d.ts b/types/BundleManager.d.ts new file mode 100644 index 000000000..d31997517 --- /dev/null +++ b/types/BundleManager.d.ts @@ -0,0 +1,130 @@ +import { Command } from './Command'; +import { EntityFactory } from './EntityFactory'; +import { GameState } from './GameState'; + +export declare class BundleManager { + /** + * @param {GameState} state + */ + constructor(path: string, state: GameState); + + /** + * Load in all bundles + */ + loadBundles(distribute?: boolean): Promise; + + /** + * @param {string} bundle Bundle name + * @param {string} bundlePath Path to bundle directory + */ + loadBundle(bundle: string, bundlePath: string): Promise; + + loadQuestGoals(bundle: string, goalsDir: string): void; + + loadQuestRewards(bundle: string, rewardsDir: string): void; + + /** + * Load attribute definitions + * @param {string} bundle + * @param {string} attributesFile + */ + loadAttributes(bundle: string, attributesFile: string): void; + + /** + * Load/initialize player. See the {@link http://ranviermud.com/extending/input_events/|Player Event guide} + * @param {string} bundle + * @param {string} eventsFile event js file to load + */ + loadPlayerEvents(bundle: string, eventsFile: string): void; + + /** + * @param {string} bundle + */ + loadAreas(bundle: string): Promise; + + /** + * @param {string} bundle + * @param {string} areaName + * @param {string} areaPath + */ + loadArea(bundle: string, areaName: string, manifest: string): Promise; + + /** + * Load an entity (item/npc/room) from file + * @param {string} bundle + * @param {string} areaName + * @param {string} type + * @param {EntityFactory} factory + * @return {Array} + */ + loadEntities(bundle: string, areaName: string, type: string, factory: EntityFactory): Promise; + + /** + * @param {EntityFactory} factory Instance of EntityFactory that the item/npc will be loaded into + * @param {string} entityRef + * @param {string} scriptPath + */ + loadEntityScript(factory: EntityFactory, entityRef: string, scriptPath: string): void; + + /** + * @param {string} bundle + * @param {string} areaName + * @return {Promise>} + */ + loadQuests(bundle: string, areaName: string): Promise + + /** + * @param {string} bundle + * @param {string} commandsDir + */ + loadCommands(bundle: string, commandsDir: string): void; + + /** + * @param {string} commandPath + * @param {string} commandName + * @param {string} bundle + * @return {Command} + */ + createCommand(commandPath: string, commandName: string, bundle: string): Command; + + /** + * @param {string} bundle + * @param {string} channelsFile + */ + loadChannels(bundle: string, channelsFile: string): void; + + /** + * @param {string} bundle + */ + loadHelp(bundle: string): Promise; + + /** + * @param {string} bundle + * @param {string} inputEventsDir + */ + loadInputEvents(bundle: string, inputEventsDir: string): void; + + /** + * @param {string} bundle + * @param {string} behaviorsDir + */ + loadBehaviors(bundle: string, behaviorsDir: string): void; + + /** + * @param {string} bundle + * @param {string} effectsDir + */ + loadEffects(bundle: string, effectsDir: string): void; + + /** + * @param {string} bundle + * @param {string} skillsDir + */ + loadSkills(bundle: string, skillsDir: string): void; + + /** + * @param {string} bundle + * @param {string} serverEventsDir + */ + loadServerEvents(bundle: string, serverEventsDir: string): void; +} \ No newline at end of file diff --git a/types/Channel.d.ts b/types/Channel.d.ts new file mode 100644 index 000000000..ee92b4735 --- /dev/null +++ b/types/Channel.d.ts @@ -0,0 +1,76 @@ +import { ChannelAudience } from './ChannelAudience'; +import { PlayerRoles } from './PlayerRoles'; +import { GameState } from './GameState'; +import { Player } from './Player'; + +export declare interface ChannelConfig { + /** @property {string} name Name of the channel */ + name: string; + /** @property {ChannelAudience} audience */ + audience: ChannelAudience; + /** @property {string} [description] */ + description: string; + /** @property {PlayerRoles} [minRequiredRole] */ + minRequiredRole: PlayerRoles; + /** @property {string} [color] */ + color: string; + /** @property {{sender: function, target: function}} [formatter] */ + formatter: Function; +} + +export declare class Channel { + /** @property {ChannelAudience} audience People who receive messages from this channel */ + audience: ChannelAudience; + /** @property {string} name Actual name of the channel the user will type */ + name: string; + /** @property {string} color Default color. This is purely a helper if you're using default format methods */ + color: string; + /** @property {PlayerRoles} minRequiredRole If set only players with the given role or greater can use the channel */ + minRequiredRole: PlayerRoles; + /** @property {string} description */ + description: string; + /** @property {{sender: function, target: function}} [formatter] */ + formatter: Function; + + constructor(config: ChannelConfig); + + /** + * @param {GameState} state + * @param {Player} sender + * @param {string} message + * @fires GameEntity#channelReceive + */ + send(state: GameState, sender: Player, message: string): void; + + describeSelf(sender: Player): void; + + getUsage(): string; + + /** + * How to render the message the player just sent to the channel + * E.g., you may want "chat" to say "You chat, 'message here'" + * @param {Player} sender + * @param {Player} target + * @param {string} message + * @param {Function} colorify + * @return {string} + */ + formatToSender(sender: Player, target: Player, message: string, colorify: Function): string; + + /** + * How to render the message to everyone else + * E.g., you may want "chat" to say "Playername chats, 'message here'" + * @param {Player} sender + * @param {Player} target + * @param {string} message + * @param {Function} colorify + * @return {string} + */ + formatToReceipient(sender: Player, target: Player, message: string, colorify: Function): string; + + colorify(message: string): string; +} + +export declare class NoPartyError extends Error {} +export declare class NoRecipientError extends Error {} +export declare class NoMessageError extends Error {} \ No newline at end of file diff --git a/types/ChannelAudience.d.ts b/types/ChannelAudience.d.ts new file mode 100644 index 000000000..4aca667ae --- /dev/null +++ b/types/ChannelAudience.d.ts @@ -0,0 +1,32 @@ +import { GameState } from "./GameState" +import { Player } from "./Player"; + +export declare interface AudienceOptions { + /** @param {GameState} state */ + state: GameState; + /** @param {Player} sender */ + sender: Player; + /** @param {string} message */ + message: string; +} + +export declare class ChannelAudience { + /** + * Configure the current state for the audience. Called by {@link Channel#send} + * @param {AudienceOptions} options + */ + configure(options: AudienceOptions): void; + + /** + * Find targets for this audience + * @return {Array} + */ + getBroadcastTargets(): Array; + + /** + * Modify the message to be sent + * @param {string} message + * @return {string} + */ + alterMessage(message: string): string; +} \ No newline at end of file diff --git a/types/ChannelManager.d.ts b/types/ChannelManager.d.ts new file mode 100644 index 000000000..e99d240ee --- /dev/null +++ b/types/ChannelManager.d.ts @@ -0,0 +1,31 @@ +import { Command } from "./Command"; + +export declare class ChannelManager { + constructor(); + + /** + * Get command by name + * @param {string} + * @return {Command} + */ + get(command: string): Command; + + /** + * Add the command and set up aliases + * @param {Command} + */ + add(command: Command): void; + + /** + * @param {Command} + */ + remove(command: Command): void; + + /** + * Find a command from a partial name + * @param {string} search + * @param {boolean} returnAlias true to also return which alias of the command was used + * @return {Command} + */ + find(search: string, returnAlias: boolean): Command; +} \ No newline at end of file diff --git a/types/Character.d.ts b/types/Character.d.ts new file mode 100644 index 000000000..0442abc04 --- /dev/null +++ b/types/Character.d.ts @@ -0,0 +1,160 @@ +import { EffectableEntity } from './EffectableEntity'; +import { EffectList } from './EffectList'; +import { EntityReference } from './EntityReference'; +import { Inventory } from './Inventory'; +import { Item } from './Item'; +import { Metadatable } from './Metadatable'; +import { Room } from './Room'; + +export declare interface CharacterConfig { + /** @property {string} name Name shown on look/who/login */ + name: string; + /** @property {Inventory} inventory */ + inventory: Inventory; + equipment: Map; + /** @property {number} level */ + level: number; + /** @property {Room} room Room the character is currently in */ + room: Room; + metadata: object; +} + +export declare class Character extends Metadatable(EffectableEntity) { + /** @property {string} name Name shown on look/who/login */ + name: string; + /** @property {Inventory} inventory */ + inventory: Inventory; + /** @property {Set} combatants Enemies this character is currently in combat with */ + combatants: Set; + /** @property {number} level */ + level: number; + /** @property {EffectList} effects List of current effects applied to the character */ + effects: EffectList; + /** @property {Room} room Room the character is currently in */ + room: Room; + + constructor(data: CharacterConfig); + + /** + * Start combat with a given target. + * @param {Character} target + * @param {?number} lag Optional milliseconds of lag to apply before the first attack + * @fires Character#combatStart + */ + initiateCombat(target: Character, lag: number): void; + + /** + * Check to see if this character is currently in combat or if they are + * currently in combat with a specific character + * @param {?Character} target + * @return boolean + */ + isInCombat(target: Character): boolean; + + /** + * @param {Character} target + * @fires Character#combatantAdded + */ + addCombatant(target: Character): void; + + /** + * @param {Character} target + * @fires Character#combatantRemoved + * @fires Character#combatEnd + */ + removeCombatant(target: Character): void; + + /** + * Fully remove this character from combat + */ + removeFromCombat(): void; + + /** + * @param {Item} item + * @param {string} slot Slot to equip the item in + * + * @throws EquipSlotTakenError + * @throws EquipAlreadyEquippedError + * @fires Character#equip + * @fires Item#equip + */ + equip(item: Item, slot: string): void; + + /** + * Remove equipment in a given slot and move it to the character's inventory + * @param {string} slot + * + * @throws InventoryFullError + * @fires Item#unequip + * @fires Character#unequip + */ + unequip(slot: string): void; + + /** + * Move an item to the character's inventory + * @param {Item} item + */ + addItem(item: Item): void; + + /** + * Remove an item from the character's inventory. Warning: This does not automatically place the + * item in any particular place. You will need to manually add it to the room or another + * character's inventory + * @param {Item} item + */ + removeItem(item: Item): void; + + /** + * Check to see if this character has a particular item by EntityReference + * @param {EntityReference} itemReference + * @return {Item|boolean} + */ + hasItem(itemReference: EntityReference): Item | boolean; + + /** + * @return {boolean} + */ + isInventoryFull(): boolean; + + /** + * Begin following another character. If the character follows itself they stop following. + * @param {Character} target + */ + follow(target: Character): void; + + /** + * Stop following whoever the character was following + * @fires Character#unfollowed + */ + unfollow(): void; + + /** + * @param {Character} follower + * @fires Character#gainedFollower + */ + addFollower(follower: Character): void; + + /** + * @param {Character} follower + * @fires Character#lostFollower + */ + removeFollower(follower: Character): void; + + /** + * @param {Character} target + * @return {boolean} + */ + isFollowing(target: Character): boolean; + + /** + * @param {Character} target + * @return {boolean} + */ + hasFollower(target: Character): boolean; + + /** + * Gather data to be persisted + * @return {Object} + */ + serialize(): Object; +} diff --git a/types/Command.d.ts b/types/Command.d.ts new file mode 100644 index 000000000..75fa1dca6 --- /dev/null +++ b/types/Command.d.ts @@ -0,0 +1,38 @@ +import { CommandType } from './CommandType'; +import { Player } from './Player'; +import { PlayerRoles } from './PlayerRoles'; + +export interface CommandDef { + name: string; + func: Function; + type?: CommandType; + aliases?: string[]; + usage?: string; + requiredRole?: PlayerRoles; + metadata?: Record; +} + +export declare class Command { + /** @property {string} bundle Bundle this command came from */ + bundle: string; + /** @property {CommandType} type */ + type: CommandType; + /** @property {string} name */ + name: string; + /** @property {Function} func Actual function that gets run when the command is executed */ + func: Function; + /** @property {string[]} aliases */ + aliases?: string[]; + /** @property {string} usage */ + usage: string; + /** @property {PlayerRoles} requiredRole */ + requiredRole: PlayerRoles; + /** @property {string} file File the command comes from */ + file: string; + /** @property {Record} metadata General use configuration object */ + metadata: Record; + + constructor(bundle: string, name: string, def: CommandDef, file: string); + + execute(args: string, player: Player, arg0?: string): any; +} diff --git a/types/CommandManager.d.ts b/types/CommandManager.d.ts new file mode 100644 index 000000000..3aacd863f --- /dev/null +++ b/types/CommandManager.d.ts @@ -0,0 +1,27 @@ +import { Command } from "./Command"; + +export declare class CommandManager { + commands: Map; + + /** + * Get command by name + */ + get(command: string): Command | null; + + /** + * Add the command and set up aliases + */ + add(command: Command): void; + + remove(command: Command): void; + + /** + * Find a command from a partial name + */ + find(search: string): Command | null; + + /** + * Find a command from a partial name + */ + find(search: string, returnAlias: boolean): Command | { command: Command, alias: string } | null; +} diff --git a/types/CommandQueue.d.ts b/types/CommandQueue.d.ts new file mode 100644 index 000000000..0c2037239 --- /dev/null +++ b/types/CommandQueue.d.ts @@ -0,0 +1,79 @@ +/** @typedef {{ execute: function (), label: string, lag: number= }} */ +export declare type CommandExecutable = { + execute: Function; + label: string; + lag: number; +} + +export declare class CommandQueue { + constructor(); + + /** + * Safely add lag to the current queue. This method will not let you add a + * negative amount as a safety measure. If you want to subtract lag you can + * directly manipulate the `lag` property. + * @param {number} amount milliseconds of lag + */ + addLag(amount: number): void; + + /** + * @param {CommandExecutable} executable Thing to run with an execute and a queue label + * @param {number} lag Amount of lag to apply to the queue after the command is run + * @returns {number} + */ + enqueue(executable: CommandExecutable, lag: number): number; + + hasPending(): boolean; + + /** + * Execute the currently pending command if it's ready + * @return {boolean} whether the command was executed + */ + execute(): boolean; + + /** + * @type {Array} + */ + queue(): Array; + + /** + * Flush all pending commands. Does _not_ reset lastRun/lag. Meaning that if + * the queue is flushed after a command was just run its lag will still have + * to expire before another command can be run. To fully reset the queue use + * the reset() method. + */ + flush(): void; + + /** + * Completely reset the queue and any lag. This is fairly dangerous as if the + * player could reliably reset the queue they could negate any command lag. To + * clear commands without altering lag use flush() + */ + reset(): void; + + /** + * Seconds until the next command can be executed + * @type {number} + */ + lagRemaining(): number; + + /** + * Milliseconds til the next command can be executed + * @type {number} + */ + msTilNextRun(): number; + + /** + * For a given command index find how many seconds until it will run + * @param {number} commandIndex + * @return {number} + */ + getTimeTilRun(commandIndex: number): number; + + /** + * Milliseconds until the command at the given index can be run + * @param {number} commandIndex + * @return {number} + */ + getMsTilRun(commandIndex: number): number; +} \ No newline at end of file diff --git a/types/CommandType.d.ts b/types/CommandType.d.ts new file mode 100644 index 000000000..2b820c988 --- /dev/null +++ b/types/CommandType.d.ts @@ -0,0 +1,6 @@ +export declare enum CommandType { + COMMAND = 'COMMAND', + SKILL = 'SKILL', + CHANNEL = 'CHANNEL', + MOVEMENT = 'MOVEMENT', +} \ No newline at end of file diff --git a/types/Config.d.ts b/types/Config.d.ts new file mode 100644 index 000000000..59db12255 --- /dev/null +++ b/types/Config.d.ts @@ -0,0 +1,12 @@ +export declare class Config { + /** + * @param {string} key + * @param {*} fallback fallback value + */ + static get(key: string, fallback?: any): any; + + /** + * Load `ranvier.json` from disk + */ + static load(data: any): void; +} diff --git a/types/Damage.d.ts b/types/Damage.d.ts new file mode 100644 index 000000000..33d53eec5 --- /dev/null +++ b/types/Damage.d.ts @@ -0,0 +1,34 @@ +import { Attribute } from "./Attribute"; +import { Character } from "./Character"; + +export declare class Damage { + attribute: Attribute; + amount: number; + attacker?: Character; + source?: any; + metadata?: object; + + /** + * @param {string} attribute Attribute the damage is going to apply to + * @param {number} amount + * @param {Character} [attacker=null] Character causing the damage + * @param {*} [source=null] Where the damage came from: skill, item, room, etc. + * @property {Object} metadata Extra info about the damage: type, hidden, critical, etc. + */ + constructor(attribute: string, amount: number, attacker?: Character, source?: any, metadata?: object); + + /** + * Evaluate actual damage taking attacker/target's effects into account + * @param {Character} target + * @return {number} Final damage amount + */ + evaluate(target: Character): number; + + /** + * Actually lower the attribute + * @param {Character} target + * @fires Character#hit + * @fires Character#damaged + */ + commit(target: Character): void; +} \ No newline at end of file diff --git a/types/Data.d.ts b/types/Data.d.ts new file mode 100644 index 000000000..13658fb70 --- /dev/null +++ b/types/Data.d.ts @@ -0,0 +1,65 @@ +export declare class Data { + static setDataPath(path: string): void; + + /** + * Read in and parse a file. Current supports yaml and json + * @param {string} filepath + * @return {*} parsed contents of file + */ + static parseFile(filepath: string): any; + + /** + * Write data to a file + * @param {string} filepath + * @param {*} data + * @param {function} callback + */ + static saveFile(filepath: string, data: any, callback: Function): void; + + /** + * load/parse a data file (player/account) + * @param {string} type + * @param {string} id + * @return {*} + */ + static load(type: string, id: string): any; + + /** + * Save data file (player/account) data to disk + * @param {string} type + * @param {string} id + * @param {*} data + * @param {function} callback + */ + static save(type: string, id: string, data: any, callback: Function): void; + + /** + * Check if a data file exists + * @param {string} type + * @param {string} id + * @return {boolean} + */ + static exists(type: string, id: string): boolean; + + /** + * get the file path for a given data file by type (player/account) + * @param {string} type + * @param {string} id + * @return {string} + */ + static getDataFilePath(type: string, id: string): string; + + /** + * Determine whether or not a path leads to a legitimate JS file or not. + * @param {string} path + * @param {string} [file] + * @return {boolean} + */ + static isScriptFile(path: string, file: string): boolean; + + /** + * load the MOTD for the intro screen + * @return string + */ + static loadMotd(): string; +} diff --git a/types/DataSource.d.ts b/types/DataSource.d.ts new file mode 100644 index 000000000..f2ef30950 --- /dev/null +++ b/types/DataSource.d.ts @@ -0,0 +1,13 @@ +import { EntityLoaderConfig } from './EntityLoader'; + +export declare interface DataSource { + hasData(config: EntityLoaderConfig): Promise; + + fetchAll(config: EntityLoaderConfig): Promise; + + fetch(config: EntityLoaderConfig, id: string|number): any; + + replace(config: EntityLoaderConfig, data: any): void; + + update(config: EntityLoaderConfig, id: string|number, data: any): void; +} diff --git a/types/DataSourceRegistry.d.ts b/types/DataSourceRegistry.d.ts new file mode 100644 index 000000000..a6397712d --- /dev/null +++ b/types/DataSourceRegistry.d.ts @@ -0,0 +1,14 @@ +import { DataSource } from './DataSource'; + +/** + * Holds instances of configured DataSources + * @type {Map} + */ +export declare class DataSourceRegistry extends Map { + /** + * @param {Function} requireFn used to require() the loader + * @param {string} rootPath project root + * @param {object} config configuration to load + */ + load(requireFn: Function, rootPath: string, config: object): void; +} diff --git a/types/Effect.d.ts b/types/Effect.d.ts new file mode 100644 index 000000000..a300dc922 --- /dev/null +++ b/types/Effect.d.ts @@ -0,0 +1,151 @@ +import { Character } from './Character'; +import { Damage } from './Damage'; +import { EventEmitter } from 'events'; +import { GameState } from './GameState'; + +/** @typedef EffectModifiers {{attributes: !Object}} */ +export declare type EffectModifiers = { + attributes: { [key: string]: Function } +}; + +export declare interface EffectConfig { + /** @property {boolean} autoActivate If this effect immediately activates itself when added to the target */ + autoActivate: boolean; + /** @property {boolean} hidden If this effect is shown in the character's effect list */ + hidden: boolean; + /** @property {boolean} refreshes If an effect with the same type is applied it will trigger an effectRefresh event instead of applying the additional effect. */ + refreshes: boolean; + /** @property {boolean} unique If multiple effects with the same `config.type` can be applied at once */ + unique: boolean; + /** @property {number} maxStacks When adding an effect of the same type it adds a stack to the current */ + /** effect up to maxStacks instead of adding the effect. Implies `config.unique` */ + maxStacks: number; + /** @property {boolean} persists If false the effect will not save to the player */ + persists: boolean; + /** @property {string} type The effect category, mainly used when disallowing stacking */ + type: string; + /** @property {boolean|number} tickInterval Number of seconds between calls to the `updateTick` listener */ + tickInterval: boolean | number; +} + +export declare class Effect extends EventEmitter { + /** @property {EffectConfig} config Effect configuration (name/desc/duration/etc.) */ + config: EffectConfig; + /** @property {string} id filename minus .js */ + id: string; + /** @property {EffectModifiers} modifiers Attribute modifier functions */ + modifier: EffectModifiers; + /** @property {number} startedAt Date.now() time this effect became active */ + startedAt: number; + /** @property {object} state Configuration of this _type_ of effect (magnitude, element, stat, etc.) */ + state: object; + /** @property {Character} target Character this effect is... effecting */ + target: Character; + + constructor(id: string, def: object); + + /** + * @type {string} + */ + get name(): string; + + /** + * @type {string} + */ + get description(): string; + + /** + * Total duration of effect in milliseconds + * @property {number} + */ + get duration(): number; + + set duration(dur: number); + + /** + * Elapsed time in milliseconds since event was activated + * @property {number} + */ + get elapsed(): number; + + /** + * Remaining time in seconds + * @property {number} + */ + get remaining(): number; + + isCurrent(): boolean; + + /** + * Set this effect active + * @fires Effect#effectActivated + */ + activate(): void; + + /** + * Set this effect active + * @fires Effect#effectDeactivated + */ + deactivate(): void; + + /** + * Remove this effect from its target + * @fires Effect#remove + */ + remove(): void; + + /** + * Stop this effect from having any effect temporarily + */ + pause(): void; + + /** + * Resume a paused effect + */ + resume(): void; + + /** + * Apply effect attribute modifiers to a given value + * + * @param {string} attrName + * @param {number} currentValue + * @return {number} attribute value modified by effect + */ + modifyAttribute(attrName: string, currentValue: number): number; + + /** + * Apply effect property modifiers to a given value + * + * @param {string} propertyName + * @param {*} currentValue + * @return {*} property value modified by effect + */ + modifyProperty(propertyName: string, currentValue: any): any; + + /** + * @param {Damage} damage + * @param {number} currentAmount + * @return {Damage} + */ + modifyIncomingDamage(damage: Damage, currentAmount: number): Damage; + + /** + * @param {Damage} damage + * @param {number} currentAmount + * @return {Damage} + */ + modifyOutgoingDamage(damage: Damage, currentAmount: number): Damage; + + /** + * Gather data to persist + * @return {Object} + */ + serialize(): Object; + + /** + * Reinitialize from persisted data + * @param {GameState} + * @param {Object} data + */ + hydrate(state: GameState, data: Object): void; +} diff --git a/types/EffectFactory.d.ts b/types/EffectFactory.d.ts new file mode 100644 index 000000000..6b6f6d6b9 --- /dev/null +++ b/types/EffectFactory.d.ts @@ -0,0 +1,38 @@ +import { Effect } from "./Effect"; +import { GameState } from "./GameState"; + +export declare type EffectConfig = { + config: { [key: string]: any } + listeners: { [key: string]: Function } +} + +export declare class EffectFactory { + constructor(); + + /** + * @param {string} id + * @param {EffectConfig} config + * @param {GameState} state + */ + add(id: string, config: EffectConfig, state: GameState): void; + + /** + * @param {string} id + */ + has(id: string): boolean; + + /** + * Get a effect definition. Use `create` if you want an instance of a effect + * @param {string} id + * @return {object} + */ + get(id: string): Effect; + + /** + * @param {string} id effect id + * @param {?object} config Effect.config override + * @param {?object} state Effect.state override + * @return {Effect} + */ + create(id: string, config?: EffectConfig, state?: object): Effect +} diff --git a/types/EffectFlag.d.ts b/types/EffectFlag.d.ts new file mode 100644 index 000000000..3b5faef2d --- /dev/null +++ b/types/EffectFlag.d.ts @@ -0,0 +1,4 @@ +export declare enum EffectFlag { + BUFF = 'BUFF', + DEBUFF = 'DEBUFF', +} \ No newline at end of file diff --git a/types/EffectList.d.ts b/types/EffectList.d.ts new file mode 100644 index 000000000..4ba8be1da --- /dev/null +++ b/types/EffectList.d.ts @@ -0,0 +1,107 @@ +import { Attribute } from "./Attribute"; +import { Character } from "./Character"; +import { Damage } from "./Damage"; +import { Effect } from "./Effect"; +import { GameState } from "./GameState"; + +export declare class EffectList { + effects: Set; + target: Character; + + /** + * @param {Character} target + * @param {Array} effects array of serialized effects (Object) or actual Effect instances + */ + constructor(target: Character, effects: Array); + + /** + * @type {number} + */ + get size(): number; + + /** + * Get current list of effects as an array + * @return {Array} + */ + entries(): Array; + + /** + * @param {string} type + * @return {boolean} + */ + hasEffectType(type: string): boolean; + + /** + * @param {string} type + * @return {Effect} + */ + getByType(type: string): Effect; + + /** + * Proxy an event to all effects + * @param {string} event + * @param {...*} args + */ + emit(event: string, ...args: Array): void; + + /** + * @param {Effect} effect + * @fires Effect#effectAdded + * @fires Effect#effectStackAdded + * @fires Effect#effectRefreshed + * @fires Character#effectAdded + */ + add(effect: Effect): void; + + /** + * Deactivate and remove an effect + * @param {Effect} effect + * @throws ReferenceError + * @fires Character#effectRemoved + */ + remove(effect: Effect): void; + + /** + * Remove all effects, bypassing all deactivate and remove events + */ + clear(): void; + + /** + * Ensure effects are still current and if not remove them + */ + validateEffects(): void; + + /** + * Gets the effective "max" value of an attribute (before subtracting delta). + * Does the work of actaully applying attribute modification + * @param {Atrribute} attr + * @return {number} + */ + evaluateAttribute(attr: Attribute): number; + + /** + * Gets the effective value of property doing all effect modifications. + * @param {string} propertyName + * @param {number} propertyValue + * @return {number} + */ + evaluateProperty(propertyName: string, propertyValue: number): number; + + /** + * @param {Damage} damage + * @param {number} currentAmount + * @return {number} + */ + evaluateIncomingDamage(damage: Damage, currentAmount: number): number; + + /** + * @param {Damage} damage + * @param {number} currentAmount + * @return {number} + */ + evaluateOutgoingDamage(damage: Damage, currentAmount: number): number; + + serialize(): Array; + + hydrate(state: GameState): void; +} \ No newline at end of file diff --git a/types/EffectableEntity.d.ts b/types/EffectableEntity.d.ts new file mode 100644 index 000000000..8081b7cc6 --- /dev/null +++ b/types/EffectableEntity.d.ts @@ -0,0 +1,136 @@ +import { EventEmitter } from 'events'; +import { Damage } from './Damage'; +import { Effect } from './Effect'; +import { GameState } from './GameState'; + +export declare class EffectableEntity extends EventEmitter { + constructor(data); + + /** + * Proxy all events on the entity to effects + * @param {string} event + * @param {...*} args + */ + emit(event: string | symbol, ...args: any[]): boolean; + + /** + * @param {string} attr Attribute name + * @return {boolean} + */ + hasAttribute(attr: string): boolean; + + /** + * Get current maximum value of attribute (as modified by effects.) + * @param {string} attr + * @return {number} + */ + getMaxAttribute(attr: string): number; + + /** + * @see {@link Attributes#add} + */ + addAttribute(attribute): void; + + /** + * Get the current value of an attribute (base modified by delta) + * @param {string} attr + * @return {number} + */ + getAttribute(attr: string): number; + + /** + * Get the effected value of a given property + * @param {string} propertyName + * @return {*} + */ + getProperty(propertyName: string): any; + + /** + * Get the base value for a given attribute + * @param {string} attrName Attribute name + * @return {number} + */ + getBaseAttribute(attrName: string): number; + + /** + * Clears any changes to the attribute, setting it to its base value. + * @param {string} attr + * @fires EffectableEntity#attributeUpdate + */ + setAttributeToMax(attr: string): void; + + /** + * Raise an attribute by name + * @param {string} attr + * @param {number} amount + * @see {@link Attributes#raise} + * @fires EffectableEntity#attributeUpdate + */ + raiseAttribute(attr: string, amount: number): void; + + /** + * Lower an attribute by name + * @param {string} attr + * @param {number} amount + * @see {@link Attributes#lower} + * @fires EffectableEntity#attributeUpdate + */ + lowerAttribute(attr: string, amount: number): void; + + /** + * Update an attribute's base value. + * + * NOTE: You _probably_ don't want to use this the way you think you do. You should not use this + * for any temporary modifications to an attribute, instead you should use an Effect modifier. + * + * This will _permanently_ update the base value for an attribute to be used for things like a + * player purchasing a permanent upgrade or increasing a stat on level up + * + * @param {string} attr Attribute name + * @param {number} newBase New base value + * @fires EffectableEntity#attributeUpdate + */ + setAttributeBase(attr: string, newBase: number): void; + + /** + * @param {string} type + * @return {boolean} + * @see {@link Effect} + */ + hasEffectType(type: string): boolean; + + /** + * @param {Effect} effect + * @see {@link Effect#remove} + */ + removeEffect(effect: Effect): void; + + /** + * @see EffectList.evaluateIncomingDamage + * @param {Damage} damage + * @param {number} currentAmount + * @return {number} + */ + evaluateIncomingDamage(damage: Damage, currentAmount: number): number; + + /** + * @see EffectList.evaluateOutgoingDamage + * @param {Damage} damage + * @param {number} currentAmount + * @return {number} + */ + evaluateOutgoingDamage(damage: Damage, currentAmount: number): number; + + /** + * Initialize the entity from storage + * @param {GameState} state + * @param {object} serialized + */ + hydrate(state: GameState, serialized: object): void; + + /** + * Gather data to be persisted + * @return {EffectableEntity} + */ + serialize(): EffectableEntity; +} \ No newline at end of file diff --git a/types/EntityFactory.d.ts b/types/EntityFactory.d.ts new file mode 100644 index 000000000..60f2db813 --- /dev/null +++ b/types/EntityFactory.d.ts @@ -0,0 +1,59 @@ +import { Area } from "./Area"; +import { Item } from "./Item"; +import { Npc } from "./Npc"; +import { Room } from "./Room"; + +export declare class EntityFactory { + constructor(); + + /** + * Create the key used by the entities and scripts maps + * @param {string} area Area name + * @param {number} id + * @return {string} + */ + createEntityRef(area: string, id: number): string; + + /** + * @param {string} entityRef + * @return {Object} + */ + getDefinition(entityRef: string): object; + + /** + * @param {string} entityRef + * @param {Object} def + */ + setDefinition(entityRef: string, def: object): void; + + /** + * Add an event listener from a script to a specific item + * @see BehaviorManager::addListener + * @param {string} entityRef + * @param {string} event + * @param {Function} listener + */ + addScriptListener(entityRef: string, event: string, listener: Function): void; + + /** + * Create a new instance of a given npc definition. Resulting npc will not be held or equipped + * and will _not_ have its default contents. If you want it to also populate its default contents + * you must manually call `npc.hydrate(state)` + * + * @param {Area} area + * @param {string} entityRef + * @param {Item|Npc|Room|Area} Type Type of entity to instantiate + * @return {type} + */ + createByType(area: Area, entityRef: string, Type: Item|Npc|Room|Area): Item|Npc|Room|Area; + + /** Method overloaded on sublasses */ + //create(); + + /** + * Clone an existing entity. + * @param {Item|Npc|Room|Area} entity + * @return {Item|Npc|Room|Area} + */ + clone(entity: Item|Npc|Room|Area): Item|Npc|Room|Area;; +} \ No newline at end of file diff --git a/types/EntityLoader.d.ts b/types/EntityLoader.d.ts new file mode 100644 index 000000000..c27b7cd23 --- /dev/null +++ b/types/EntityLoader.d.ts @@ -0,0 +1,34 @@ +import { DataSource } from './DataSource'; + +export declare interface EntityLoaderConfig { + area?: string; + bundle?: string; +} + +/** + * Used to CRUD an entity from a configured DataSource + */ +export declare class EntityLoader { + dataSource: DataSource; + config: EntityLoaderConfig; + + /** + * @param {DataSource} dataSource A class that implements DataSource interface + * @param {object} config + */ + constructor(dataSource: DataSource, config: EntityLoaderConfig); + + setArea(name: string): void; + + setBundle(name: string): void; + + hasData(): Promise; + + fetchAll(): Promise; + + fetch(id: string|number): any; + + replace(data: any): void; + + update(id: string|number, data: any): void; +} \ No newline at end of file diff --git a/types/EntityLoaderRegistry.d.ts b/types/EntityLoaderRegistry.d.ts new file mode 100644 index 000000000..5b8763026 --- /dev/null +++ b/types/EntityLoaderRegistry.d.ts @@ -0,0 +1,12 @@ +import EntityLoader from '../src/EntityLoader'; + +/** + * Holds instances of configured EntityLoaders + * @type {Map} + */ +export declare class EntityLoaderRegistry extends Map { + load( + sourceRegistry: EntityLoader, + config: { name: string; settings: object } + ): void; +} diff --git a/types/EntityReference.d.ts b/types/EntityReference.d.ts new file mode 100644 index 000000000..3a5ab9685 --- /dev/null +++ b/types/EntityReference.d.ts @@ -0,0 +1 @@ +export declare type EntityReference = string; diff --git a/types/EquipErrors.d.ts b/types/EquipErrors.d.ts new file mode 100644 index 000000000..69ded5b43 --- /dev/null +++ b/types/EquipErrors.d.ts @@ -0,0 +1,2 @@ +export declare class EquipSlotTakenError extends Error {} +export declare class EquipAlreadyEquippedError extends Error {} diff --git a/types/EventManager.d.ts b/types/EventManager.d.ts new file mode 100644 index 000000000..2fad7cbaf --- /dev/null +++ b/types/EventManager.d.ts @@ -0,0 +1,38 @@ +import EventEmitter from 'events'; + +export declare class EventManager { + constructor(); + + /** + * Fetch all listeners for a given event + * @param {string} name + * @return {Set} + */ + get(name: string): Set; + + /** + * @param {string} eventName + * @param {Function} listener + */ + add(eventName: string, listener: Function): void; + + /** + * Attach all currently added events to the given emitter + * @param {EventEmitter} emitter + * @param {Object} config + */ + attach(emitter: typeof EventEmitter, config?: Record): void; + + /** + * Remove all listeners for a given emitter or only those for the given events + * If no events are given it will remove all listeners from all events defined + * in this manager. + * + * Warning: This will remove _all_ listeners for a given event list, this includes + * listeners not in this manager but attached to the same event + * + * @param {EventEmitter} emitter + * @param {?string|iterable} events Optional name or list of event names to remove listeners from + */ + detach(emitter: typeof EventEmitter, events?: string | string[]): void; +} diff --git a/types/EventUtil.d.ts b/types/EventUtil.d.ts new file mode 100644 index 000000000..091b9a9ce --- /dev/null +++ b/types/EventUtil.d.ts @@ -0,0 +1,17 @@ +import { Socket } from "net"; + +export declare class EventUtil { + /** + * Generate a function for writing colored output to a socket + * @param {net.Socket} socket + * @return {function (string)} + */ + static genWrite(socket: Socket): Function; + + /** + * Generate a function for writing colored output to a socket with a newline + * @param {net.Socket} socket + * @return {function (string)} + */ + static genSay(socket: Socket): Function; +} diff --git a/types/GameEntity.d.ts b/types/GameEntity.d.ts new file mode 100644 index 000000000..3eaf6580d --- /dev/null +++ b/types/GameEntity.d.ts @@ -0,0 +1,7 @@ +import { EffectableEntity } from './EffectableEntity'; +import { Metadatable } from './Metadatable'; +import { Scriptable } from './Scriptable'; + +export declare class GameEntity extends Scriptable(Metadatable(EffectableEntity)) { + +} \ No newline at end of file diff --git a/types/GameServer.d.ts b/types/GameServer.d.ts new file mode 100644 index 000000000..30e889b2b --- /dev/null +++ b/types/GameServer.d.ts @@ -0,0 +1,14 @@ +import { EventEmitter } from 'events'; + +export declare class GameServer extends EventEmitter { + /** + * @param {commander} commander + * @fires GameServer#startup + */ + startup(commander: object): void; + + /** + * @fires GameServer#shutdown + */ + shutdown(): void; +} diff --git a/types/GameState.d.ts b/types/GameState.d.ts new file mode 100644 index 000000000..eaa252f32 --- /dev/null +++ b/types/GameState.d.ts @@ -0,0 +1,3 @@ +export declare class GameState { + [key: string]: any; +} \ No newline at end of file diff --git a/types/Heal.d.ts b/types/Heal.d.ts new file mode 100644 index 000000000..871f2313e --- /dev/null +++ b/types/Heal.d.ts @@ -0,0 +1,11 @@ +import { Character } from './Character'; +import { Damage } from './Damage'; +export declare class Heal extends Damage { + /** + * Raise a given attribute + * @param {Character} target + * @fires Character#heal + * @fires Character#healed + */ + commit(target: Character): void; +} diff --git a/types/HelpManager.d.ts b/types/HelpManager.d.ts new file mode 100644 index 000000000..35de3e2f2 --- /dev/null +++ b/types/HelpManager.d.ts @@ -0,0 +1,28 @@ +import { Helpfile } from "./Helpfile"; + +export declare class HelpManager { + constructor(); + + /** + * @param {string} help Helpfile name + */ + get(help: string): Helpfile | undefined; + + /** + * @param {Helpfile} help + */ + add(help: Helpfile): void; + + /** + * @param {string} search + * @return {Help} + */ + find(search: string): Helpfile; + + /** + * Returns first help matching keywords + * @param {string} search + * @return {?string} + */ + getFirst(help: string): Helpfile | null; +} \ No newline at end of file diff --git a/types/Helpfile.d.ts b/types/Helpfile.d.ts new file mode 100644 index 000000000..a9f3d2db7 --- /dev/null +++ b/types/Helpfile.d.ts @@ -0,0 +1,16 @@ +export declare interface HelpfileOptions { + keywords: string[]; + command: string; + channel: string; + related: string[]; + body: string; +} + +export declare class Helpfile { + /** + * @param {string} bundle Bundle the helpfile comes from + * @param {string} name + * @param {HelpfileOptions} options + */ + constructor(bundle: string, name: string, options: HelpfileOptions); +} diff --git a/types/Inventory.d.ts b/types/Inventory.d.ts new file mode 100644 index 000000000..677be47f7 --- /dev/null +++ b/types/Inventory.d.ts @@ -0,0 +1,32 @@ +import { Item, ItemDef } from './Item'; + +export declare class InventoryFullError extends Error { } + +export declare interface InventoryDef { + items?: [string, ItemDef | Item][]; + max?: number; +} + +export declare interface SerializedInventory { + items?: [string, ItemDef][]; + max?: number; +} + + +export declare class Inventory extends Map { + maxSize: number; + readonly isFull: boolean; + + constructor(init: InventoryDef); + + setMax(size: number): void; + + getMax(): number; + + // Can throw InventoryFullError; + addItem(item: Item): void | never; + + removeItem(item: Item): void; + + serialize(): SerializedInventory; +} diff --git a/types/Item.d.ts b/types/Item.d.ts new file mode 100644 index 000000000..53174f162 --- /dev/null +++ b/types/Item.d.ts @@ -0,0 +1,88 @@ +import { Area } from './Area'; +import { Character } from './Character'; +import { GameEntity } from './GameEntity'; +import { GameState } from './GameState'; +import { ItemType } from './ItemType'; +import { Room } from './Room'; +import { SerializedInventory } from './Inventory'; + +export declare interface ItemDef { + name: string; + id: string; + + description?: string; + inventory?: any; + metadata?: Record; + behaviors?: Record; + items?: ItemDef[]; + maxItems?: number; + isEquipped?: boolean; + entityReference: string; + room?: string | Room | null; + roomDesc?: string; + script?: string; + type?: ItemType | string; + uuid?: string; + closeable?: boolean; + closed?: boolean; + locked?: boolean; + lockedBy?: string | null; + + keywords: string[]; +} + +export declare class Item extends GameEntity { + name: string; + id: string; + + description: string; + metadata: Record; + behaviors: Map; + defaultItems: Item[] | ItemDef[]; + entityReference: string; + maxItems: number; + isEquipped: boolean; + room: string | Room | null; + roomDesc: string; + script: string | null; + type: ItemType | string; + uuid: string; + closeable: boolean; + closed: boolean; + locked: boolean; + lockedBy: string | null; + + carriedBy: string | null; + equippedBy: string | null; + + keywords: string[]; + + + constructor(area: Area, item: ItemDef); + + initializeInventory(inventory: SerializedInventory): void; + + hasKeyword(keyword: string): boolean; + + addItem(item: Item): void; + + removeItem(item: Item): void; + + isInventoryFull(): boolean; + + _setupInventory(): void; + + findCarrier(): Character | Item | null; + + open(): void; + + close(): void; + + lock(): void; + + unlock(): void; + + hydrate(state: GameState, serialized?: ItemDef): void; + + serialize(): ItemDef; +} diff --git a/types/ItemFactory.d.ts b/types/ItemFactory.d.ts new file mode 100644 index 000000000..497e82434 --- /dev/null +++ b/types/ItemFactory.d.ts @@ -0,0 +1,17 @@ +import { Area } from './Area'; +import { EntityFactory } from './EntityFactory'; +import { Item } from './Item'; + +export declare class ItemFactory extends EntityFactory { + /** + * Create a new instance of an item by EntityReference. Resulting item will + * not be held or equipped and will _not_ have its default contents. If you + * want it to also populate its default contents you must manually call + * `item.hydrate(state)` + * + * @param {Area} area + * @param {string} entityRef + * @return {Item} + */ + create(area: Area, entityRef: string): Item; +} diff --git a/types/ItemManager.d.ts b/types/ItemManager.d.ts new file mode 100644 index 000000000..db2bf5307 --- /dev/null +++ b/types/ItemManager.d.ts @@ -0,0 +1,14 @@ +import { Item } from './Item'; + +export declare class ItemManager { + constructor(); + + add(item: Item): void; + + remove(item: Item): void; + + /** + * @fires Item#updateTick + */ + tickAll(): void; +} diff --git a/types/ItemType.d.ts b/types/ItemType.d.ts new file mode 100644 index 000000000..3425ca826 --- /dev/null +++ b/types/ItemType.d.ts @@ -0,0 +1,8 @@ +export declare enum ItemType { + OBJECT = 1, + CONTAINER = 2, + ARMOR = 3, + WEAPON = 4, + POTION = 5, + RESOURCE = 6 +} diff --git a/types/Logger.d.ts b/types/Logger.d.ts new file mode 100644 index 000000000..0a2b16b43 --- /dev/null +++ b/types/Logger.d.ts @@ -0,0 +1,25 @@ +export declare class Logger { + static getLevel(): string; + static setLevel(level): void; + /* + Medium priority logging, default. + */ + static log(...messages): void; + /* + Appends red "ERROR" to the start of logs. + Highest priority logging. + */ + static error(...messages): void; + /* + Less high priority than error, still higher visibility than default. + */ + static warn(...messages): void; + /* + Lower priority logging. + Only logs if the environment variable is set to VERBOSE. + */ + static verbose(...messages): void; + static setFileLogging(path): void; + static deactivateFileLogging(): void; + static enablePrettyErrors(): void; +} diff --git a/types/Metadatable.d.ts b/types/Metadatable.d.ts new file mode 100644 index 000000000..f10dd4e5d --- /dev/null +++ b/types/Metadatable.d.ts @@ -0,0 +1,23 @@ +export declare class MetadatableClass { + /** + * Set a metadata value. + * Warning: Does _not_ autovivify, you will need to create the parent objects if they don't exist + * @param {string} key Key to set. Supports dot notation e.g., `"foo.bar"` + * @param {*} value Value must be JSON.stringify-able + * @throws Error + * @throws RangeError + * @fires Metadatable#metadataUpdate + */ + setMeta(key: string, value: any): void; + + /** + * Get metadata by dot notation + * Warning: This method is _very_ permissive and will not error on a non-existent key. Rather, it will return false. + * @param {string} key Key to fetch. Supports dot notation e.g., `"foo.bar"` + * @return {*} + * @throws Error + */ + getMeta(key: string): any; +} + +export declare function Metadatable(parentClass: any): any & MetadatableClass; \ No newline at end of file diff --git a/types/MobFactory.d.ts b/types/MobFactory.d.ts new file mode 100644 index 000000000..2fa52fd72 --- /dev/null +++ b/types/MobFactory.d.ts @@ -0,0 +1,16 @@ +import { Area } from './Area'; +import { EntityFactory } from './EntityFactory'; +import { Npc } from './Npc'; + +export declare class MobFactory extends EntityFactory { + /** + * Create a new instance of a given npc definition. Resulting npc will not + * have its default inventory. If you want to also populate its default + * contents you must manually call `npc.hydrate(state)` + * + * @param {Area} area + * @param {string} entityRef + * @return {Npc} + */ + create(area: Area, entityRef: string): Npc; +} diff --git a/types/MobManager.d.ts b/types/MobManager.d.ts new file mode 100644 index 000000000..9485eaaa0 --- /dev/null +++ b/types/MobManager.d.ts @@ -0,0 +1,16 @@ +import { Npc } from './Npc'; + +export declare class MobManager { + constructor(); + + /** + * @param {Npc} mob + */ + addMob(mob: Npc): void; + + /** + * Completely obliterate a mob from the game, nuclear option + * @param {Npc} mob + */ + removeMob(mob: Npc): void; +} diff --git a/types/Npc.d.ts b/types/Npc.d.ts new file mode 100644 index 000000000..6a4da53b7 --- /dev/null +++ b/types/Npc.d.ts @@ -0,0 +1,30 @@ +import { Area } from './Area'; +import { Character, CharacterConfig } from './Character'; +import { GameState } from './GameState'; +import { Room } from './Room'; +import { Scriptable } from './Scriptable'; + +/** + * @property {number} id Area-relative id (vnum) + * @property {Area} area Area npc belongs to (not necessarily the area they're currently in) + * @property {Map} behaviors + * @extends Character + * @mixes Scriptable + */ +export declare class Npc extends Scriptable(Character) { + constructor(area: Area, data: CharacterConfig); + + /** + * Move the npc to the given room, emitting events appropriately + * @param {Room} nextRoom + * @param {function} onMoved Function to run after the npc is moved to the next room but before enter events are fired + * @fires Room#npcLeave + * @fires Room#npcEnter + * @fires Npc#enterRoom + */ + moveTo(nextRoom: Room, onMoved: Function): void; + + hydrate(state: GameState): void; + + get isNpc(): boolean; +} diff --git a/types/Party.d.ts b/types/Party.d.ts new file mode 100644 index 000000000..c3f7e117b --- /dev/null +++ b/types/Party.d.ts @@ -0,0 +1,19 @@ +import { Player } from "./Player"; + +export declare class Party extends Set { + constructor(leader: Player); + + delete(member: Player): boolean; + + add(member: Player): this; + + disband(): void; + + invite(target: Player): void; + + isInvited(target: Player): boolean; + + removeInvite(target: Player): void; + + getBroadcastTargets(): Array; +} \ No newline at end of file diff --git a/types/PartyAudience.d.ts b/types/PartyAudience.d.ts new file mode 100644 index 000000000..7048f978d --- /dev/null +++ b/types/PartyAudience.d.ts @@ -0,0 +1,6 @@ +import { ChannelAudience } from './ChannelAudience'; +import { Player } from './Player'; + +export declare class PartyAudience extends ChannelAudience { + getBroadcastTargets(): Array; +} \ No newline at end of file diff --git a/types/PartyManager.d.ts b/types/PartyManager.d.ts new file mode 100644 index 000000000..54ed3351b --- /dev/null +++ b/types/PartyManager.d.ts @@ -0,0 +1,19 @@ +import { Party } from './Party'; +import { Player } from './Player'; + +/** + * Keeps track of active in game parties and is used to create new parties + * @extends Set + */ +export declare class PartyManager extends Set { + /** + * Create a new party from with a given leader + * @param {Player} leader + */ + create(leader: Player): void; + + /** + * @param {Party} party + */ + disband(party: Party): void; +} diff --git a/types/Player.d.ts b/types/Player.d.ts new file mode 100644 index 000000000..dec614538 --- /dev/null +++ b/types/Player.d.ts @@ -0,0 +1,62 @@ +import { Character } from './Character'; +import { Room } from './Room'; + +export declare class Player extends Character { + constructor(data); + + /** + * @see CommandQueue::enqueue + */ + queueCommand(executable, lag); + + /** + * Proxy all events on the player to the quest tracker + * @param {string} event + * @param {...*} args + */ + emit(event, ...args); + + /** + * Convert prompt tokens into actual data + * @param {string} promptStr + * @param {object} extraData Any extra data to give the prompt access to + */ + interpolatePrompt(promptStr: string, extraData: object); + + /** + * Add a line of text to be displayed immediately after the prompt when the prompt is displayed + * @param {string} id Unique prompt id + * @param {()} renderer Function to call to render the prompt string + * @param {?boolean} removeOnRender When true prompt will remove itself once rendered + * otherwise prompt will continue to be rendered until removed. + */ + addPrompt(id: string, renderer: Function, removeOnRender: boolean); + + /** + * @param {string} id + */ + removePrompt(id: string); + + /** + * @param {string} id + * @return {boolean} + */ + hasPrompt(id: string): boolean; + + /** + * Move the player to the given room, emitting events appropriately + * @param {Room} nextRoom + * @param {function} onMoved Function to run after the player is moved to the next room but before enter events are fired + * @fires Room#playerLeave + * @fires Room#playerEnter + * @fires Player#enterRoom + */ + moveTo(nextRoom: Room, onMoved: Function); + + /** + * @param {function} callback + */ + save(callback: Function): void; + hydrate(state: Object); + serialize(): Object; +} diff --git a/types/PlayerManager.d.ts b/types/PlayerManager.d.ts new file mode 100644 index 000000000..155954542 --- /dev/null +++ b/types/PlayerManager.d.ts @@ -0,0 +1,116 @@ +import { EventEmitter } from 'events'; + +import { Account } from './Account'; +import { EntityLoader } from './EntityLoader'; +import { EventManager } from './EventManager'; +import { GameState } from './GameState'; +import { Player } from './Player'; + +/** + * Keeps track of all active players in game + * @extends EventEmitter + * @property {Map} players + * @property {EventManager} events Player events + * @property {EntityLoader} loader + * @listens PlayerManager#save + * @listens PlayerManager#updateTick + */ +export declare class PlayerManager extends EventEmitter { + players: Map; + events: EventManager; + loader: EntityLoader | null; + + constructor(); + + /** + * Set the entity loader from which players are loaded + * @param {EntityLoader} + */ + setLoader(loader: EntityLoader): void; + + /** + * @param {string} name + * @return {Player} + */ + getPlayer(name: string): Player; + + /** + * @param {Player} player + */ + addPlayer(player: Player): void; + + /** + * Remove the player from the game. WARNING: You must manually save the player first + * as this will modify serializable properties + */ + removePlayer(player: Player, killSocket?: boolean): void; + + /** + * @return {array} + */ + getPlayersAsArray(): Player[]; + + /** + * @param {string} behaviorName + * @param {Function} listener + */ + addListener(event: string | symbol, listener: (...args: any[]) => void): this; + + /** + * @param {Function} predicate Filter function + * @return {Player[]}, + */ + filter( + fn: (current: Player, index: string | number, array: Player[]) => boolean + ): Player[]; + + /** + * Load a player for an account + * @param {GameState} state + * @param {Account} account + * @param {string} username + * @param {boolean} force true to force reload from storage + * @return {Player} + */ + loadPlayer( + state: GameState, + account: Account, + username: string, + force?: boolean + ): Promise; + + /** + * Turn player into a key used by this class's map + * @param {Player} player + * @return {string} + */ + keyify(player: Player): string; + + /** + * @param {string} name + * @return {boolean} + */ + exists(name: string): boolean; + + /** + * Save a player + * @fires Player#save + */ + save(player: Player): Promise; + + /** + * Save all players + * @fires Player#save + */ + saveAll(): Promise; + + /** + * @fires Player#updateTick + */ + tickAll(): void; + + /** + * Used by Broadcaster + */ + getBroadcastTargets(): Player; +} diff --git a/types/PlayerRoles.d.ts b/types/PlayerRoles.d.ts new file mode 100644 index 000000000..57a71cafa --- /dev/null +++ b/types/PlayerRoles.d.ts @@ -0,0 +1,5 @@ +export declare enum PlayerRoles { + ADMIN = 2, + BUILDER = 1, + PLAYER = 0, +} \ No newline at end of file diff --git a/types/PrivateAudience.d.ts b/types/PrivateAudience.d.ts new file mode 100644 index 000000000..2b19434f8 --- /dev/null +++ b/types/PrivateAudience.d.ts @@ -0,0 +1,14 @@ +import { ChannelAudience } from './ChannelAudience'; +import { Player } from './Player'; + +/** + * Audience class representing a specific targeted player. + * Example: `tell` command or `whisper` command. + * @memberof ChannelAudience + * @extends ChannelAudience + */ +export declare class PrivateAudience extends ChannelAudience { + getBroadcastTargets(): Player[]; + + alterMessage(message: string): string; +} \ No newline at end of file diff --git a/types/Quest.d.ts b/types/Quest.d.ts new file mode 100644 index 000000000..1a83c205f --- /dev/null +++ b/types/Quest.d.ts @@ -0,0 +1,61 @@ +import { EventEmitter } from 'events'; +import { GameState } from './GameState'; +import { Player } from './Player'; +import { QuestGoal } from './QuestGoal'; +import { QuestReward } from './QuestReward'; + +export declare type QuestConfig = { + title: string; + description: string, + completionMessage: string | null, + requires: string[], + level: number, + autoComplete: boolean, + repeatable: boolean, + rewards: QuestReward[], + goals: QuestGoal[] + +} + +/** + * @property {object} config Default config for this quest, see individual quest types for details + * @property {Player} player + * @property {object} state Current completion state + * @extends EventEmitter + */ +export declare class Quest extends EventEmitter { + constructor(GameState: GameState, id: string, config: QuestConfig, player: Player); + + /** + * Proxy all events to all the goals + * @param {string} event + * @param {...*} args + */ + emit(event, ...args); + + addGoal(goal); + + /** + * @fires Quest#turn-in-ready + * @fires Quest#progress + */ + onProgressUpdated(); + + /** + * @return {{ percent: number, display: string }} + */ + getProgress(); + + /** + * Save the current state of the quest on player save + * @return {object} + */ + serialize(); + + hydrate(); + + /** + * @fires Quest#complete + */ + complete(); +} diff --git a/types/QuestFactory.d.ts b/types/QuestFactory.d.ts new file mode 100644 index 000000000..f2d422278 --- /dev/null +++ b/types/QuestFactory.d.ts @@ -0,0 +1,47 @@ +import { GameState } from './GameState'; +import { Player } from './Player'; +import { Quest, QuestConfig } from './Quest'; + +export declare class QuestFactory { + constructor(); + + add(areaName: string, id: string, config: QuestConfig); + + set(qid: string, val: QuestConfig); + + /** + * Get a quest definition. Use `create` if you want an instance of a quest + * @param {string} qid + * @return {object} + */ + get(qid: string): Quest; + + /** + * Check to see if a player can start a given quest based on the quest's + * prerequisite quests + * @param {entityReference} questRef + * @return {boolean} + */ + canStart(player: Player, questRef: string); + + /** + * @param {GameState} GameState + * @param {entityReference} qid + * @param {Player} player + * @param {Array} state current quest state + * @return {Quest} + */ + create( + GameState: GameState, + qid: string, + player: Player, + state: any[] + ): Quest; + + /** + * @param {string} areaName + * @param {number} id + * @return {string} + */ + makeQuestKey(area: string, id: number): string; +} diff --git a/types/QuestGoal.d.ts b/types/QuestGoal.d.ts new file mode 100644 index 000000000..96d5fb835 --- /dev/null +++ b/types/QuestGoal.d.ts @@ -0,0 +1,36 @@ +import { EventEmitter } from 'events'; +import { GameState } from './GameState'; +import { Player } from './Player'; +import { Quest } from './Quest'; + +export declare type QuestGoalConfig = {}; +export declare type QuestGoalProgress = { + percent: number; + display: string; +}; +export declare type QuestGoalSerialized = { + state: GameState; + progress: QuestGoalProgress; + config: QuestGoalConfig; +}; + +export declare class QuestGoal extends EventEmitter { + /** + * @param {Quest} quest Quest this goal is for + * @param {object} config + * @param {Player} player + */ + constructor(quest: Quest, config: QuestGoalConfig, player: Player); + + /** + * @return {{ percent: number, display: string}} + */ + getProgress(): QuestGoalProgress; + + /** + * Put any cleanup activities after the quest is finished here + */ + complete(): void; + + serialize(): QuestGoalSerialized; +} diff --git a/types/QuestGoalManager.d.ts b/types/QuestGoalManager.d.ts new file mode 100644 index 000000000..e790a2065 --- /dev/null +++ b/types/QuestGoalManager.d.ts @@ -0,0 +1,4 @@ +/** + * Simple map of quest goal name => class definition + */ +export declare class QuestGoalManager extends Map {} diff --git a/types/QuestReward.d.ts b/types/QuestReward.d.ts new file mode 100644 index 000000000..e4fcb32d0 --- /dev/null +++ b/types/QuestReward.d.ts @@ -0,0 +1,37 @@ +import { GameState } from './GameState'; +import { Player } from './Player'; +import { Quest } from './Quest'; + +export declare type QuestRewardConfig = {}; + +/** + * Representation of a quest reward + * The {@link http://ranviermud.com/extending/areas/quests/|Quest guide} has instructions on to + * create new reward type for quests + */ +export declare class QuestReward { + /** + * Assign the reward to the player + * @param {GameState} GameState + * @param {Quest} quest quest this reward is being given from + * @param {object} config + * @param {Player} player + */ + static reward( + GameState: GameState, + quest: Quest, + config: QuestRewardConfig, + player: Player + ): void; + + /** + * Render the reward + * @return string + */ + static display( + GameState: GameState, + quest: Quest, + config: QuestRewardConfig, + player: Player + ): string; +} diff --git a/types/QuestRewardManager.d.ts b/types/QuestRewardManager.d.ts new file mode 100644 index 000000000..73833b171 --- /dev/null +++ b/types/QuestRewardManager.d.ts @@ -0,0 +1,4 @@ +/** + * Simple map of quest reward name => class instance + */ +export declare class QuestRewardManager extends Map {} diff --git a/types/QuestTracker.d.ts b/types/QuestTracker.d.ts new file mode 100644 index 000000000..dbc679d27 --- /dev/null +++ b/types/QuestTracker.d.ts @@ -0,0 +1,58 @@ +import { GameState } from './GameState'; +import { Player } from './Player'; +import { Quest } from './Quest'; + +export declare type SerializedQuestTracker = { + completed: Quest[]; + active: Quest[]; +}; + +export declare class QuestTracker { + /** + * @param {Player} player + * @param {Array} active + * @param {Array} completed + */ + constructor(player: Player, active: Quest[], completed: Quest[]); + + /** + * Proxy events to all active quests + * @param {string} event + * @param {...*} args + */ + emit(event: string, ...args: any[]): void; + + /** + * @param {EntityReference} qid + * @return {boolean} + */ + isActive(qid: string): boolean; + + /** + * @param {EntityReference} qid + * @return {boolean} + */ + isComplete(qid: string): boolean; + + get(qid: string): Quest; + + /** + * @param {EntityReference} qid + */ + complete(qid: string); + + /** + * @param {Quest} quest + */ + start(quest: Quest): void; + + /** + * @param {GameState} state + */ + hydrate(state: GameState): void; + + /** + * @return {object} + */ + serialize(): SerializedQuestTracker; +} diff --git a/types/RoleAudience.d.ts b/types/RoleAudience.d.ts new file mode 100644 index 000000000..5d2fd9e3f --- /dev/null +++ b/types/RoleAudience.d.ts @@ -0,0 +1,8 @@ +import { AudienceOptions, ChannelAudience } from "./ChannelAudience"; +import { Player } from "./Player"; + +export declare class RoleAudience extends ChannelAudience { + constructor(options: AudienceOptions); + + getBroadcastTargets(): Player[]; +} \ No newline at end of file diff --git a/types/Room.d.ts b/types/Room.d.ts new file mode 100644 index 000000000..fca85219d --- /dev/null +++ b/types/Room.d.ts @@ -0,0 +1,165 @@ +import { Area } from './Area'; +import { Character } from './Character'; +import { GameEntity } from './GameEntity'; +import { EntityReference } from './EntityReference'; +import { GameState } from './GameState'; +import { Item } from './Item'; +import { Npc } from './Npc'; +import { Player } from './Player'; + +export interface Door { + lockedBy?: EntityReference; + locked?: boolean; + closed?: boolean; +} + +export interface Exit { + roomId: string; + direction: string; + inferred?: boolean, +} + +export interface RoomDef { + title: string; + description: string; + id: string; + defaultItems?: Item[]; + defaultNpcs?: Npc[]; + script?: string; + behaviors?: Record; + coordinates?: [number, number, number]; + doors?: Record; +} + +export declare class Room extends GameEntity { + /** + * @property Area room is in + */ + area: Area; + + constructor(area: Area, def: RoomDef); + + /** + * Emits event on self and proxies certain events to other entities in the room. + * @param {string} eventName + * @param {...*} args + * @return {void} + */ + emit(eventName: string, ...args): void; + + /** + * @param {Player} player + */ + addPlayer(player: Player): void; + + /** + * @param {Player} player + */ + removePlayer(player: Player): void; + + /** + * @param {Npc} npc + */ + addNpc(npc: Npc): void; + + /** + * @param {Npc} npc + * @param {boolean} removeSpawn + */ + removeNpc(npc: Npc, removeSpawn: boolean): void; + + /** + * @param {Item} item + */ + addItem(item: Item): void; + + /** + * @param {Item} item + */ + removeItem(item: Item): void; + + /** + * Get exits for a room. Both inferred from coordinates and defined in the + * 'exits' property. + * + * @return {Array<{ id: string, direction: string, inferred: boolean, room: Room= }>} + */ + getExits(): Exit[]; + + /** + * Get the exit definition of a room's exit by searching the exit name + * @param {string} exitName exit name search + * @return {false|Object} + */ + findExit(exitName: string): false | Exit; + + /** + * Get the exit definition of a room's exit to a given room + * @param {Room} nextRoom + * @return {false|Object} + */ + getExitToRoom(nextRoom: Room): false | Exit; + + /** + * Check to see if this room has a door preventing movement from `fromRoom` to here + * @param {Room} fromRoom + * @return {boolean} + */ + hasDoor(fromRoom: Room): boolean; + + /** + * @param {Room} fromRoom + * @return {{lockedBy: EntityReference, locked: boolean, closed: boolean}} + */ + getDoor(fromRoom: Room): Door; + + /** + * Check to see of the door for `fromRoom` is locked + * @param {Room} fromRoom + * @return {boolean} + */ + isDoorLocked(fromRoom: Room): boolean; + + /** + * @param {Room} fromRoom + */ + openDoor(fromRoom: Room): void; + + /** + * @param {Room} fromRoom + */ + closeDoor(fromRoom: Room): void; + + /** + * @param {Room} fromRoom + */ + unlockDoor(fromRoom: Room): void; + + /** + * @param {Room} fromRoom + */ + lockDoor(fromRoom: Room): void; + + /** + * @param {GameState} state + * @param {string} entityRef + * @return {Item} The newly created item + */ + spawnItem(state: GameState, entityRef: string): Item; + + /** + * @param {GameState} state + * @param {string} entityRef + * @fires Npc#spawn + * @return {Npc} + */ + spawnNpc(state: GameState, entityRef: string): Npc; + + hydrate(state: GameState): void; + + /** + * Used by Broadcaster + * @return {Array} + */ + getBroadcastTargets(): Character[] +} diff --git a/types/RoomAudience.d.ts b/types/RoomAudience.d.ts new file mode 100644 index 000000000..0fe2c8829 --- /dev/null +++ b/types/RoomAudience.d.ts @@ -0,0 +1,6 @@ +import { ChannelAudience } from "./ChannelAudience"; +import { Player } from "./Player"; + +export declare class RoomAudience extends ChannelAudience { + getBroadcastTargets(): Player[]; +} \ No newline at end of file diff --git a/types/RoomFactory.d.ts b/types/RoomFactory.d.ts new file mode 100644 index 000000000..9ca1a8c8f --- /dev/null +++ b/types/RoomFactory.d.ts @@ -0,0 +1,18 @@ +import { Area } from './Area'; +import { EntityFactory } from './EntityFactory'; +import { Room } from './Room'; + +/** + * Stores definitions of rooms to allow for easy creation/cloning + * @extends EntityFactory + */ +export declare class RoomFactory extends EntityFactory { + /** + * Create a new instance of a given room. Room will not be hydrated + * + * @param {Area} area + * @param {string} entityRef + * @return {Room} + */ + create(area: Area, entityRef: string): Room; +} diff --git a/types/RoomManager.d.ts b/types/RoomManager.d.ts new file mode 100644 index 000000000..aaddf63de --- /dev/null +++ b/types/RoomManager.d.ts @@ -0,0 +1,24 @@ +import { Room } from "./Room"; + +/** + * Keeps track of all the individual rooms in the game + */ +export declare class RoomManager { + constructor(); + + /** + * @param {string} entityRef + * @return {Room} + */ + getRoom(entityRef: string): Room; + + /** + * @param {Room} room + */ + addRoom(room: Room): void; + + /** + * @param {Room} room + */ + removeRoom(room: Room): void; +} diff --git a/types/Scriptable.d.ts b/types/Scriptable.d.ts new file mode 100644 index 000000000..cf7816ebd --- /dev/null +++ b/types/Scriptable.d.ts @@ -0,0 +1,26 @@ +import { BehaviorManager } from './BehaviorManager'; + +export declare class ScriptableClass { + emit(name: string, ...args: any[]): void; + + /** + * @param {string} name + * @return {boolean} + */ + hasBehavior(name: string): boolean; + + /** + * @param {string} name + * @return {*} + */ + getBehavior(name: string): any; + + /** + * Attach this entity's behaviors from the manager + * @param {BehaviorManager} manager + */ + setupBehaviors(manager: BehaviorManager): void; +} + +export declare function Scriptable(parentClass: any): any & ScriptableClass; + diff --git a/types/Skill.d.ts b/types/Skill.d.ts new file mode 100644 index 000000000..8d334aeb9 --- /dev/null +++ b/types/Skill.d.ts @@ -0,0 +1,123 @@ +import { Attribute } from './Attribute'; +import { Character } from './Character'; +import { Effect } from './Effect'; +import { GameState } from './GameState'; +import { Player } from './Player'; +import { SkillType } from './SkillType'; + +export declare type SkillResource = { + attribute: string; + cost: number; +}; + +export declare type SkillConfig = { + configureEffect: Function; + cooldown: + | number + | { + group: string; + length: number; + }; + effect: Effect; + flags: string[]; + info: Function; + initiatesCombat?: boolean; + name: string; + requiresTarget?: boolean; + resource: null | SkillResource; + run: Function; + targetSelf?: boolean; + type: SkillType; + options?: object; +}; + +export declare type CooldownConfig = { + config: { + name: string; + description: string; + unique: boolean; + type: string; + }; + state: { + cooldownId: null | string; + }; + listeners: { + effectDeactivated: Function; + }; +}; + +/** + * @property {function (Effect)} configureEffect modify the skill's effect before adding to player + * @property {null|number} cooldownLength When a number > 0 apply a cooldown effect to disallow usage + * until the cooldown has ended + * @property {string} effect Id of the passive effect for this skill + * @property {Array} flags + * @property {function ()} info Function to run to display extra info about this skill + * @property {function ()} run Function to run when skill is executed/activated + * @property {GameState} state + * @property {SkillType} type + */ +export declare class Skill { + /** + * @param {string} id + * @param {object} config + * @param {GameState} state + */ + constructor(id: string, config: SkillConfig, state: GameState); + + /** + * perform an active skill + * @param {string} args + * @param {Player} player + * @param {Character} target + */ + execute(args: string, player: Player, target: Character): boolean; + + /** + * @param {Player} player + * @return {boolean} If the player has paid the resource cost(s). + */ + payResourceCosts(player: Player): boolean; + + // Helper to pay a single resource cost. + payResourceCost(player: Player, resource: SkillResource): Boolean; + + activate(player: Player): void; + + /** + * @param {Character} character + * @return {boolean|Effect} If on cooldown returns the cooldown effect + */ + onCooldown(character: Character): boolean | Effect; + + /** + * Put this skill on cooldown + * @param {Character} character + */ + cooldown(character: Character): void; + + getCooldownId(): string; + + /** + * Create an instance of the cooldown effect for use by cooldown() + * + * @private + * @return {Effect} + */ + createCooldownEffect(): Effect; + + getDefaultCooldownConfig(): CooldownConfig; + + /** + * @param {Character} character + * @return {boolean} + */ + hasEnoughResources(character: Character): boolean; + + /** + * @param {Character} character + * @param {{ attribute: string, cost: number}} resource + * @return {boolean} + */ + hasEnoughResource(character: Character, resource: SkillResource): boolean; +} diff --git a/types/SkillErrors.d.ts b/types/SkillErrors.d.ts new file mode 100644 index 000000000..b50075ff8 --- /dev/null +++ b/types/SkillErrors.d.ts @@ -0,0 +1,24 @@ +import { Effect } from './Effect'; + +/** + * Error used when trying to execute a skill and the player doesn't have enough resources + * @extends Error + */ +export declare class NotEnoughResourcesError extends Error { } + +/** + * Error used when trying to execute a passive skill + * @extends Error + */ +export declare class PassiveError extends Error { } + + +export declare class CooldownError extends Error { + /** @property {Effect} effect */ + effect: Effect; + + /** + * @param {Effect} effect Cooldown effect that triggered this error + */ + constructor(effect: Effect); +} \ No newline at end of file diff --git a/types/SkillFlag.d.ts b/types/SkillFlag.d.ts new file mode 100644 index 000000000..41b69d375 --- /dev/null +++ b/types/SkillFlag.d.ts @@ -0,0 +1,4 @@ +export declare enum SkillFlag { + PASSIVE = 'PASSIVE', + ACTIVE = 'ACTIVE' +} \ No newline at end of file diff --git a/types/SkillManager.d.ts b/types/SkillManager.d.ts new file mode 100644 index 000000000..ad72bc53a --- /dev/null +++ b/types/SkillManager.d.ts @@ -0,0 +1,29 @@ +import { Skill } from './Skill'; + +export declare class SkillManager { + constructor(); + + /** + * @param {string} skill Skill name + * @return {Skill|undefined} + */ + get(skill: Skill): Skill | undefined; + + /** + * @param {Skill} skill + */ + add(skill: Skill): void; + + /** + * @param {Skill} skill + */ + remove(skill: Skill): void; + + /** + * Find executable skills + * @param {string} search + * @param {boolean} includePassive + * @return {Skill} + */ + find(search: string, includePassive?: boolean): Skill | undefined; +} diff --git a/types/SkillType.d.ts b/types/SkillType.d.ts new file mode 100644 index 000000000..f252e40a0 --- /dev/null +++ b/types/SkillType.d.ts @@ -0,0 +1,4 @@ +export declare enum SkillType { + SKILL = 'SKILL', + SPELL = 'SPELL', +} \ No newline at end of file diff --git a/types/TransportStream.d.ts b/types/TransportStream.d.ts new file mode 100644 index 000000000..b88abd763 --- /dev/null +++ b/types/TransportStream.d.ts @@ -0,0 +1,37 @@ +import { EventEmitter } from 'events'; +import { Socket } from 'net'; + +export declare class TransportStream extends EventEmitter { + get readable(): boolean; + + get writable(): boolean; + + write(): void; + + /** + * A subtype-safe way to execute commands on a specific type of stream that invalid types will ignore. For given input + * for command (example, `"someCommand"` ill look for a method called `executeSomeCommand` on the `TransportStream` + * @param {string} command + * @param {...*} args + * @return {*} + */ + command(command: string, ...args: any[]); + + address(): undefined; + + end(): void; + + setEncoding(): void; + + pause(): void; + + resume(): void; + + destroy(): void; + + /** + * Attach a socket to this stream + * @param {*} socket + */ + attach(socket: Socket): void; +} diff --git a/types/Util.d.ts b/types/Util.d.ts new file mode 100644 index 000000000..51505c13d --- /dev/null +++ b/types/Util.d.ts @@ -0,0 +1,8 @@ +export declare type Util = { + /** + * Check to see if a given object is iterable + * @param {Object} obj + * @return {boolean} + */ + isIterable(obj: object): boolean; +}; diff --git a/types/WorldAudience.d.ts b/types/WorldAudience.d.ts new file mode 100644 index 000000000..83509ea4c --- /dev/null +++ b/types/WorldAudience.d.ts @@ -0,0 +1,11 @@ +import { ChannelAudience } from './ChannelAudience'; +import { Player } from './Player'; + +/** + * Audience class representing everyone in the game, except sender. + * @memberof ChannelAudience + * @extends ChannelAudience + */ +export declare class WorldAudience extends ChannelAudience { + getBroadcastTargets(): Player[]; +}