UNPKG

programming-game

Version:

The client for programming game, an mmorpg that you interact with entirely through code.

798 lines (797 loc) 35.3 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseClient = void 0; var items_1 = require("./items"); var types_1 = require("./types"); var isPlayerUnit = function (unit) { return !!unit && unit.type === "player"; }; var entries = function (obj) { return Object.entries(obj); }; var keys = function (obj) { return Object.keys(obj); }; var getHandlers = function (player) { return { setRole: function (role) { return { type: types_1.IntentType.setRole, role: role }; }, attack: function (target) { return { type: types_1.IntentType.attack, target: target.id }; }, move: function (position) { return { type: types_1.IntentType.move, position: position }; }, respawn: function () { return { type: types_1.IntentType.respawn }; }, summonMana: function () { return { type: types_1.IntentType.summonMana }; }, eat: function (item) { return { type: types_1.IntentType.eat, item: item, save: (player.inventory[item] || 0) - 1, }; }, cast: function (spell, target) { return { type: types_1.IntentType.cast, spell: spell, target: target === null || target === void 0 ? void 0 : target.id }; }, sell: function (opt) { return { type: types_1.IntentType.sellItems, items: entries(opt.items).reduce(function (acc, _a) { var item = _a[0], amount = _a[1]; var until = (player.inventory[item] || 0) - (amount || 0); acc[item] = until; return acc; }, {}), to: opt.to.id, }; }, buy: function (opts) { return { type: types_1.IntentType.buyItems, items: entries(opts.items).reduce(function (acc, _a) { var item = _a[0], amount = _a[1]; if (!amount) return acc; var until = amount + (player.inventory[item] || 0); if (amount) { acc[item] = until; } return acc; }, {}), from: opts.from.id, }; }, use: function (item, target) { return { type: types_1.IntentType.use, item: item, until: (player.inventory[item] || 0) - 1, target: target === null || target === void 0 ? void 0 : target.id, }; }, seekParty: function () { return { type: types_1.IntentType.seekParty }; }, inviteToParty: function (playerId) { return { type: types_1.IntentType.inviteToParty, playerId: playerId }; }, leaveParty: function () { return { type: types_1.IntentType.leaveParty }; }, acceptPartyInvite: function (playerId) { return { type: types_1.IntentType.acceptPartyInvite, playerId: playerId }; }, equip: function (item, slot) { return { type: types_1.IntentType.equip, item: item, slot: slot }; }, unequip: function (slot) { return { type: types_1.IntentType.unequip, slot: slot }; }, craft: function (item, from) { return { type: types_1.IntentType.craft, item: item, from: from }; }, useWeaponSkill: function (specifics) { if (specifics.skill === "misdirectingShot") { return { type: types_1.IntentType.weaponSkill, skill: specifics.skill, target: specifics.target.id, options: { to: specifics.target.id, }, }; } return { type: types_1.IntentType.weaponSkill, skill: specifics.skill, target: specifics.target.id, }; }, drop: function (_a) { var item = _a.item, amount = _a.amount; return { type: types_1.IntentType.drop, item: item, until: (player.inventory[item] || 0) - amount, }; }, setSpellStones: function (item, stones, name) { return { type: types_1.IntentType.setSpellStones, item: item, stones: stones, name: name, }; }, setTrade: function (trades) { return { type: types_1.IntentType.setTrade, trades: trades, }; }, acceptQuest: function (npc, questId) { return { type: types_1.IntentType.acceptQuest, npcId: npc.id, questId: questId }; }, abandonQuest: function (questId) { return { type: types_1.IntentType.abandonQuest, questId: questId }; }, turnInQuest: function (npc, questId) { return { type: types_1.IntentType.turnInQuest, npcId: npc.id, questId: questId }; }, withdraw: function (npc, items) { var until = {}; entries(items).forEach(function (_a) { var item = _a[0], _b = _a[1], amount = _b === void 0 ? 0 : _b; var charAmount = player.storage[item] || 0; until[item] = charAmount - amount; }); return { type: types_1.IntentType.withdraw, npcId: npc.id, until: until, }; }, deposit: function (npc, items) { var until = {}; entries(items).forEach(function (_a) { var item = _a[0], _b = _a[1], amount = _b === void 0 ? 0 : _b; var charAmount = player.inventory[item] || 0; until[item] = charAmount - amount; }); return { type: types_1.IntentType.deposit, npcId: npc.id, until: until, }; }, }; }; /** * Base class for the client. * This class cares about the events, but NOT the implementation of the events. * Helpful for testing the server/client integration without firing up websockets. */ var BaseClient = /** @class */ (function () { function BaseClient(_a) { var onTick = _a.onTick, onEvent = _a.onEvent, setIntent = _a.setIntent; var _this = this; this.time = 0; this.lastOnTick = Date.now(); this.innerState = { instances: {}, arenaDurations: {}, }; this.initializeInstance = function (instance, charId) { var _a, _b; (_a = _this.innerState.instances)[instance] || (_a[instance] = { time: 0, characters: {}, playersSeekingParty: new Map(), }); (_b = _this.innerState.instances[instance].characters)[charId] || (_b[charId] = { units: {}, uniqueItems: {}, gameObjects: {}, }); return _this.innerState.instances[instance]; }; this.clearState = function () { _this.innerState = { instances: {}, arenaDurations: {}, }; _this.heartBeatInterval && clearInterval(_this.heartBeatInterval); }; this.eventsHandler = function (events) { Object.keys(events).forEach(function (instanceId) { var eventCharMap = events[instanceId]; Object.entries(eventCharMap).forEach(function (_a) { var charId = _a[0], events = _a[1]; _this.initializeInstance(instanceId, charId); events.forEach(function (_a) { var _b; var eventName = _a[0], eventPayload = _a[1]; (_b = _this.onEvent) === null || _b === void 0 ? void 0 : _b.call(_this, instanceId, charId, eventName, eventPayload); var handler = _this.socketEventHandlers[eventName]; if (handler) { // @ts-expect-error handler(instanceId, charId, eventPayload); } }); }); }); _this.runOnTick(); }; this.runOnTick = function () { var elapsed = Date.now() - _this.lastOnTick; _this.lastOnTick = Date.now(); Object.entries(_this.innerState.instances).forEach(function (_a) { var instanceId = _a[0], instance = _a[1]; if (_this.innerState.arenaDurations[instanceId]) { _this.innerState.arenaDurations[instanceId] -= elapsed; } Object.keys(instance.characters).map(function (charId) { var charState = instance.characters[charId]; Object.values(charState.units).forEach(function (unit) { Object.values(unit.statusEffects).forEach(function (effect) { effect.duration -= elapsed; }); }); _this.time++; if (instanceId === "overworld" || instanceId.startsWith("instance-")) { var char = charState.units[charId]; if (!char) return; var intent = _this.onTick({ inArena: false, player: __assign(__assign({}, char), getHandlers(char)), boundary: instance.boundary, instanceId: instanceId, playersSeekingParty: Array.from(instance.playersSeekingParty.values()), items: __assign(__assign({}, charState.uniqueItems), items_1.items), gameObjects: __assign({}, charState.gameObjects), time: _this.time, units: charState.units, }); if (intent) { var updated = { c: charId, i: instanceId, intent: intent, unitId: charId, }; _this.setIntent(updated); } } else { // because we run on a timer, we may not have units in our local state // if we're not fighting in the arena, when this is the case, avoid running onTics if (!Object.keys(charState.units).length) return; var char = charState.units[charId]; var intent = _this.onTick({ inArena: true, instanceId: instanceId, arenaTimeRemaining: _this.innerState.arenaDurations[instanceId], player: char ? __assign(__assign({}, char), getHandlers(char)) : undefined, boundary: instance.boundary, items: __assign(__assign({}, charState.uniqueItems), items_1.items), gameObjects: __assign({}, charState.gameObjects), playersSeekingParty: Array.from(instance.playersSeekingParty.values()), time: _this.time, units: charState.units, }); if (intent) { var updated = { c: charId, i: instanceId, intent: intent, unitId: charId, }; _this.setIntent(updated); } } }); }); }; /** * Handles events coming from the server. */ this.socketEventHandlers = { boundary: function (instance, charId, event) { var instanceState = _this.initializeInstance(instance, charId); instanceState.boundary = event; }, focus: function (instance, charId, event) { _this.updateUnit(instance, charId, event.monsterId, function (unit) { if (!unit) return; if (unit.type !== "monster") return; unit.focus = event.focus; }); }, hazardDamaged: function (instance, charId, event) { _this.updateUnit(instance, charId, event.unitId, function (unit) { if (!unit) return; unit.hp = event.hp; }); }, used: function (instance, charId, event) { _this.updateUnit(instance, charId, event.unitId, function (unit) { var _a, _b; (_a = unit.inventory)[_b = event.item] || (_a[_b] = 1); unit.inventory[event.item] -= 1; }); }, dropped: function (instance, charId, event) { _this.updateUnit(instance, charId, event.unitId, function (unit) { var _a, _b; (_a = unit.inventory)[_b = event.item] || (_a[_b] = 0); unit.inventory[event.item] -= event.amount; }); }, loot: function (instance, charId, event) { _this.updateUnit(instance, charId, event.unitId, function (unit) { if (!unit) return; if (!("inventory" in unit)) return; entries(event.items).forEach(function (_a) { var _b; var item = _a[0], amount = _a[1]; if (!amount) return; (_b = unit.inventory)[item] || (_b[item] = 0); unit.inventory[item] += amount; }); }); }, arena: function (instance, charId, event) { _this.innerState.arenaDurations[instance] = event.duration; }, beganCasting: function (instance, charId, event) { _this.updateUnit(instance, charId, event.unitId, function (unit) { if (!unit) return; unit.action = "cast"; unit.actionStart = Date.now(); unit.actionDuration = event.duration; unit.actionTarget = event.target; }); }, castSpell: function (instance, charId, event) { _this.updateUnit(instance, charId, event.unitId, function (unit) { if (!unit) return; unit.action = undefined; unit.actionStart = undefined; unit.actionDuration = undefined; unit.actionTarget = undefined; }); }, stats: function (instance, charId, event) { _this.updateUnit(instance, charId, event.unitId, function (unit) { if (!unit) return; unit.stats = event.stats; }); }, lostStatus: function (instance, charId, event) { _this.updateUnit(instance, charId, event.unitId, function (unit) { if (!unit) return; delete unit.statusEffects[event.effect]; }); }, gainedStatus: function (instance, charId, event) { _this.updateUnit(instance, charId, event.unitId, function (unit) { if (!unit) return; unit.statusEffects[event.effect] = { duration: event.duration, source: event.source, stacks: event.stacks, effects: event.effects, }; }); }, mp: function (instance, charId, event) { _this.updateUnit(instance, charId, event.unitId, function (unit) { if (!unit) return; unit.mp = event.mp; }); }, usedWeaponSkill: function (instance, charId, event) { _this.updateUnit(instance, charId, event.unitId, function (unit) { if (!unit) return; unit.tp = event.tp; }); }, ate: function (instance, charId, event) { _this.updateUnit(instance, charId, event.unitId, function (unit) { if (!unit) return; if ("calories" in unit) { unit.calories = event.calories; } if ("inventory" in unit) { unit.inventory[event.item] = event.remaining; } }); }, equipped: function (instance, charId, event) { _this.updateUnit(instance, charId, event.unitId, function (unit) { var _a, _b; if (!unit) return; if (isPlayerUnit(unit)) { // @ts-ignore unit.equipment[event.slot] = event.item; } (_a = unit.inventory)[_b = event.item] || (_a[_b] = 1); unit.inventory[event.item] -= 1; }); }, unequipped: function (instance, charId, event) { _this.updateUnit(instance, charId, event.unitId, function (unit) { var _a, _b; if (!unit) return; if ((unit === null || unit === void 0 ? void 0 : unit.type) === "player") { var un = unit; // @ts-ignore unit.equipment[event.slot] = null; (_a = un.inventory)[_b = event.item] || (_a[_b] = 0); un.inventory[event.item] += 1; } }); }, beganCrafting: function (instance, charId, event) { _this.updateUnit(instance, charId, event.unitId, function (unit) { unit.action = "craft"; unit.actionDuration = event.duration; unit.actionStart = Date.now(); unit.actionTarget = event.item; }); }, finishedCrafting: function (instance, charId, event) { var charState = _this.innerState.instances[instance].characters[charId]; _this.updateUnit(instance, charId, event.unitId, function (unit) { entries(event.items).forEach(function (_a) { var _b; var item = _a[0], amount = _a[1]; (_b = unit.inventory)[item] || (_b[item] = 0); unit.inventory[item] += amount; }); entries(event.spent).forEach(function (_a) { var _b; var item = _a[0], amount = _a[1]; (_b = unit.inventory)[item] || (_b[item] = amount); unit.inventory[item] -= amount; }); if (event.uniqueItems) { entries(event.uniqueItems).forEach(function (_a) { var item = _a[0], def = _a[1]; if (!item || !def) return; charState.uniqueItems[item] = def; }); } unit.action = undefined; unit.actionDuration = undefined; unit.actionStart = undefined; unit.actionUsing = undefined; unit.actionTarget = undefined; }); }, traded: function (instance, charId, event) { _this.updateUnit(instance, charId, event.actingUnitId, function (unit) { entries(event.gave).forEach(function (_a) { var _b; var item = _a[0], amount = _a[1]; (_b = unit.inventory)[item] || (_b[item] = 0); unit.inventory[item] -= amount; }); entries(event.got).forEach(function (_a) { var _b; var item = _a[0], amount = _a[1]; (_b = unit.inventory)[item] || (_b[item] = 0); unit.inventory[item] += amount; delete unit.trades.wants[item]; }); }); _this.updateUnit(instance, charId, event.targetUnitId, function (unit) { entries(event.gave).forEach(function (_a) { var _b; var item = _a[0], amount = _a[1]; (_b = unit.inventory)[item] || (_b[item] = 0); unit.inventory[item] += amount; delete unit.trades.wants[item]; }); entries(event.got).forEach(function (_a) { var _b; var item = _a[0], amount = _a[1]; (_b = unit.inventory)[item] || (_b[item] = 0); unit.inventory[item] -= amount; }); }); }, moved: function (instance, charId, event) { _this.updateUnit(instance, charId, event.id, function (unit) { if (!unit) return; var x = event.x, y = event.y; unit.position = { x: x, y: y }; }); }, connectionEvent: function (instanceName, charId, event) { var player = event.player; var instance = _this.initializeInstance(instanceName, charId); instance.characters[player.id] = { units: event.units, uniqueItems: event.uniqueItems, gameObjects: event.gameObjects, }; }, unitAppeared: function (instanceName, charId, event) { var instance = _this.initializeInstance(instanceName, charId); var char = instance.characters[charId]; char.units[event.unit.id] = event.unit; keys(event.uniqueItems).forEach(function (uniqueItemId) { char.uniqueItems[uniqueItemId] = event.uniqueItems[uniqueItemId]; }); }, unitDisappeared: function (instanceName, charId, event) { var instance = _this.initializeInstance(instanceName, charId); delete instance.characters[charId].units[event.unitId]; }, seekParty: function (instanceName, charId, event) { var instance = _this.initializeInstance(instanceName, charId); instance.playersSeekingParty.set(event.playersSeeking.id, event.playersSeeking); }, acceptedPartyInvite: function (instanceName, charId, event) { var instance = _this.initializeInstance(instanceName, charId); instance.playersSeekingParty.delete(event.inviteeId); }, updatedParty: function (instanceName, charId, event) { _this.updateUnit(instanceName, charId, charId, function (unit) { if (!unit) return; unit.party = event.party; }); }, updatedRole: function (instanceName, charId, event) { _this.updateUnit(instanceName, charId, event.unitId, function (unit) { unit.role = event.role; }); }, setIntent: function (instanceName, charId, event) { _this.updateUnit(instanceName, charId, event.unitId, function (innerUnit) { if (innerUnit) { innerUnit.intent = event.intent; } }); }, attacked: function (instanceName, charId, event) { _this.updateUnit(instanceName, charId, event.attacked, function (attackedUnit) { if (!attackedUnit) return; attackedUnit.hp = event.hp; }); _this.updateUnit(instanceName, charId, event.attacker, function (attackerUnit) { if (!attackerUnit) return; attackerUnit.tp = event.attackerTp; }); }, despawn: function (instanceName, charId, event) { var instance = _this.initializeInstance(instanceName, charId); delete instance.characters[charId].units[event.unitId]; }, hp: function (instanceName, charId, event) { _this.updateUnit(instanceName, charId, event.unitId, function (unit) { if (!unit) return; unit.hp = event.hp; }); }, tp: function (instanceName, charId, event) { _this.updateUnit(instanceName, charId, event.unitId, function (unit) { if (!unit) return; unit.tp = event.tp; }); }, calories: function (instanceName, charId, event) { _this.updateUnit(instanceName, charId, event.unitId, function (unit) { if (unit && "calories" in unit) { unit.calories = event.calories; } }); }, died: function (instanceName, charId, event) { _this.updateUnit(instanceName, charId, event.unitId, function (unit) { if (!unit) return; unit.hp = 0; }); }, invited: function (instanceName, charId, event) { var instance = _this.initializeInstance(instanceName, charId); var player = instance.characters[charId].units[charId]; if (player && isPlayerUnit(player)) { player.partyInvites.push({ id: event.inviter.id, name: event.inviter.name, role: event.inviter.role, }); } }, updatedTrade: function (instanceName, charId, event) { _this.updateUnit(instanceName, charId, event.unitId, function (unit) { if (!unit) return; unit.trades = event.trades; }); }, acceptedQuest: function (instanceName, charId, event) { _this.updatePlayer(instanceName, charId, event.unitId, function (unit) { unit.quests[event.quest.id] = event.quest; }); if (charId === event.unitId) { _this.updateNpc(instanceName, charId, event.quest.start_npc, function (npc) { delete npc.availableQuests[event.quest.id]; }); } }, abandonedQuest: function (instanceName, charId, event) { _this.updatePlayer(instanceName, charId, event.unitId, function (unit) { delete unit.quests[event.questId]; }); }, completedQuest: function (instanceName, charId, event) { _this.updatePlayer(instanceName, charId, event.unitId, function (unit) { delete unit.quests[event.questId]; }); }, questAvailable: function (instanceName, charId, event) { _this.updateNpc(instanceName, charId, event.npcId, function (npc) { npc.availableQuests[event.quest.id] = event.quest; }); }, questUpdate: function (instanceName, charId, event) { _this.updatePlayer(instanceName, charId, event.unitId, function (unit) { if (!unit) return; unit.quests[event.quest.id] = event.quest; }); }, inventory: function (instanceName, charId, event) { _this.updateUnit(instanceName, charId, event.unitId, function (unit) { if (!unit) return; entries(event.inventory).forEach(function (_a) { var item = _a[0], amount = _a[1]; if ((amount || 0) <= 0) { delete unit.inventory[item]; } else { unit.inventory[item] = amount; } }); }); }, storageEmptied: function (instanceName, charId) { _this.updatePlayer(instanceName, charId, charId, function (player) { player.storage = {}; }); }, storageCharged: function (instanceName, charId, event) { _this.updatePlayer(instanceName, charId, charId, function (player) { if (event.coinsLeft) { player.storage.copperCoin = event.coinsLeft; } else { delete player.storage.copperCoin; } }); }, withdrew: function (instanceName, charId, event) { _this.updatePlayer(instanceName, charId, charId, function (player) { entries(event.items).forEach(function (_a) { var itemId = _a[0], _b = _a[1], amount = _b === void 0 ? 0 : _b; player.storage[itemId] = (player.storage[itemId] || 0) - amount; player.inventory[itemId] = (player.inventory[itemId] || 0) + amount; if (itemId in player.storage && (player.storage[itemId] || 0) <= 0) { delete player.storage[itemId]; } }); }); }, deposited: function (instanceName, charId, event) { _this.updatePlayer(instanceName, charId, charId, function (player) { entries(event.items).forEach(function (_a) { var itemId = _a[0], _b = _a[1], amount = _b === void 0 ? 0 : _b; player.storage[itemId] = (player.storage[itemId] || 0) + amount; player.inventory[itemId] = (player.inventory[itemId] || 0) - amount; if (itemId in player.inventory && (player.inventory[itemId] || 0) <= 0) { delete player.inventory[itemId]; } }); }); }, objectAppeared: function (instanceName, charId, event) { var instance = _this.initializeInstance(instanceName, charId); var char = instance.characters[charId]; char.gameObjects[event.object.id] = event.object; }, objectDisappeared: function (instanceName, charId, event) { var instance = _this.initializeInstance(instanceName, charId); var char = instance.characters[charId]; delete char.gameObjects[event.objectId]; }, objectUpdated: function (instanceName, charId, event) { var instance = _this.initializeInstance(instanceName, charId); var char = instance.characters[charId]; var gameObject = char.gameObjects[event.objectId]; if (gameObject) { // Update the game object with the new properties Object.assign(gameObject, event.properties); } }, }; this.updateUnit = function (instance, charId, unitId, cb) { var char = _this.innerState.instances[instance].characters[charId]; if (!char) return; var unit = char.units[unitId]; if (!unit) return; cb(unit); }; this.updateNpc = function (instance, charId, npcId, cb) { _this.updateUnit(instance, charId, npcId, function (possiblyNpc) { if (possiblyNpc.type === "npc") { cb(possiblyNpc); } }); }; this.updatePlayer = function (instance, charId, unitId, cb) { _this.updateUnit(instance, charId, unitId, function (unit) { if (!isPlayerUnit(unit)) return; cb(unit); }); }; this.onTick = onTick; this.onEvent = onEvent; this.setIntent = setIntent; this.heartBeatInterval = setInterval(this.runOnTick, 300); } return BaseClient; }()); exports.BaseClient = BaseClient;