UNPKG

programming-game

Version:

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

767 lines (766 loc) 35.5 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 constants_1 = require("./constants"); var get_handlers_1 = require("./get-handlers"); var items_1 = require("./items"); var recipes_1 = require("./recipes"); var utils_1 = require("./utils"); var localItems = items_1.items; var localConstants = constants_1.constants; var localRecipes = recipes_1.recipes; var isPlayerUnit = function (unit) { return !!unit && unit.type === "player"; }; var isEmptyObject = function (obj) { for (var _key in obj) { if (obj.hasOwnProperty(_key)) { return false; } } return true; }; /** * 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, tickInterval = _a.tickInterval; var _this = this; this.lastOnTick = Date.now(); this.innerState = { gameTime: 0, instances: {}, arenaDurations: {}, }; this.initializeInstance = function (instance, charId) { var _a, _b; (_a = _this.innerState.instances)[instance] || (_a[instance] = { time: 0, characters: {}, playersSeekingParty: [], }); (_b = _this.innerState.instances[instance].characters)[charId] || (_b[charId] = { units: {}, gameObjects: {}, }); return _this.innerState.instances[instance]; }; this.clearState = function () { _this.innerState = { gameTime: 0, instances: {}, arenaDurations: {}, }; _this.heartBeatInterval && clearInterval(_this.heartBeatInterval); }; this.eventsHandler = function (events) { var _loop_1 = function (instanceId) { var eventCharMap = events[instanceId]; var _loop_2 = function (charId) { var events_1 = eventCharMap[charId]; _this.initializeInstance(instanceId, charId); events_1.forEach(function (_a) { var _b; var eventName = _a[0], eventPayload = _a[1]; // it's important that this happens before the event is processed so that logging works correctly. (_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); } }); }; for (var charId in eventCharMap) { _loop_2(charId); } }; for (var instanceId in events) { _loop_1(instanceId); } _this.runOnTick(); }; this.runOnTick = function () { var elapsed = Date.now() - _this.lastOnTick; _this.lastOnTick = Date.now(); _this.innerState.gameTime += elapsed; for (var instanceId in _this.innerState.instances) { var instance = _this.innerState.instances[instanceId]; if (_this.innerState.arenaDurations[instanceId]) { _this.innerState.arenaDurations[instanceId] -= elapsed; } for (var charId in instance.characters) { var charState = instance.characters[charId]; for (var unitId in charState.units) { var unit = charState.units[unitId]; for (var effectId in unit.statusEffects) { var effect = unit.statusEffects[effectId]; effect.duration -= elapsed; } } if (instanceId === "overworld" || instanceId.startsWith("instance-")) { var char = charState.units[charId]; if (!char) continue; var intent = _this.onTick({ inArena: false, player: Object.assign((0, get_handlers_1.getHandlers)(char), char), boundary: instance.boundary, instanceId: instanceId, playersSeekingParty: instance.playersSeekingParty, items: localItems, gameObjects: charState.gameObjects, constants: localConstants, recipes: localRecipes, units: charState.units, gameTime: _this.innerState.gameTime, }); 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 onTicks if (isEmptyObject(charState.units)) continue; var char = charState.units[charId]; var intent = _this.onTick({ inArena: true, instanceId: instanceId, arenaTimeRemaining: _this.innerState.arenaDurations[instanceId], player: char ? Object.assign((0, get_handlers_1.getHandlers)(char), char) : undefined, boundary: instance.boundary, items: localItems, gameObjects: charState.gameObjects, constants: localConstants, recipes: localRecipes, playersSeekingParty: instance.playersSeekingParty, units: charState.units, gameTime: _this.innerState.gameTime, }); 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; }); }, combatSkillIncreased: function (instance, charId, event) { _this.updatePlayer(instance, charId, event.unitId, function (unit) { unit.combatSkills[event.skill] += event.amount; }); }, 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; (0, utils_1.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 = event.gameTime; _this.innerState.gameTime = event.gameTime; unit.actionUsing = event.spell; unit.actionDuration = event.duration; unit.actionTarget = event.target; }); }, beganEquippingSpell: function (instance, charId, event) { _this.updateUnit(instance, charId, event.unitId, function (unit) { unit.action = "equipSpell"; unit.actionStart = event.gameTime; unit.actionDuration = event.duration; unit.actionTarget = event.spell; _this.innerState.gameTime = event.gameTime; }); }, beganHarvesting: function (instance, charId, event) { _this.updateUnit(instance, charId, event.unitId, function (unit) { unit.action = "harvest"; unit.actionStart = event.gameTime; unit.actionDuration = event.duration; unit.actionTarget = event.objectId; _this.innerState.gameTime = event.gameTime; }); }, harvested: function (instanceName, charId, event) { // we don't clear the object, the server should // send a second event telling us the object is gone. _this.updateUnit(instanceName, charId, event.unitId, function (unit) { _this.clearUnitActions(unit); }); }, 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; if (unit.inventory[event.item] === 0) { delete unit.inventory[event.item]; } }); }, 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 = event.gameTime; unit.actionTarget = event.item; _this.innerState.gameTime = event.gameTime; }); }, finishedCrafting: function (instance, charId, event) { var charState = _this.innerState.instances[instance].characters[charId]; _this.updateUnit(instance, charId, event.unitId, function (unit) { (0, utils_1.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; }); (0, utils_1.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; }); _this.clearUnitActions(unit); }); }, traded: function (instance, charId, event) { _this.updateUnit(instance, charId, event.actingUnitId, function (unit) { (0, utils_1.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; }); (0, utils_1.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) { (0, utils_1.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]; }); (0, utils_1.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); // This will only come through if our local version doesn't match the server version if (event.items) { localItems = __assign(__assign({}, localItems), event.items); } if (event.constants) { localConstants = __assign(__assign({}, localConstants), event.constants); } localRecipes = __assign(__assign({}, localRecipes), event.recipes); instance.characters[player.id] = { units: event.units, 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; }, 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); var mapped = false; instance.playersSeekingParty = instance.playersSeekingParty.map(function (val) { if (val.id === event.playersSeeking.id) { mapped = true; return event.playersSeeking; } return val; }); if (!mapped) { instance.playersSeekingParty.push(event.playersSeeking); } }, acceptedPartyInvite: function (instanceName, charId, event) { var instance = _this.initializeInstance(instanceName, charId); _this.updatePlayer(instanceName, charId, event.inviteeId, function (player) { player.partyInvites = []; }); instance.playersSeekingParty = instance.playersSeekingParty.filter(function (val) { return val.id !== event.inviteeId; }); }, updatedPartyInvites: function (instanceName, charId, event) { _this.updatePlayer(instanceName, charId, event.unitId, function (unit) { if (!unit) return; if (!isPlayerUnit(unit)) return; unit.partyInvites = event.invites; }); }, updatedParty: function (instanceName, charId, event) { Object.keys(event.party).forEach(function (memberId) { _this.updateUnit(instanceName, charId, memberId, function (unit) { if (!unit) return; unit.party = event.party; }); }); }, updatedPartyV2: function (instanceName, charId, event) { Object.keys(event.party).forEach(function (memberId) { _this.updateUnit(instanceName, charId, memberId, function (unit) { if (!unit) return; unit.party = event.party; }); }); _this.updateUnit(instanceName, charId, event.playerId, 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; // clear out any pending actions if (event.intent === null) { _this.clearUnitActions(innerUnit); } } }); }, 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)) { if (!player.partyInvites.find(function (invite) { return invite.id === event.inviter.id; })) { 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; (0, utils_1.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) { (0, utils_1.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) { (0, utils_1.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); } }, unequippedSpell: function (instanceName, charId, event) { _this.updateUnit(instanceName, charId, event.unitId, function (unit) { var _a; var spell = unit.spellbook[0]; if (!spell) return; unit.spellbook.splice(0, 1); if (unit.type === "player") { (_a = unit.inventory)[spell] || (_a[spell] = 0); unit.inventory[spell] += 1; } }); }, equippedSpell: function (instanceName, charId, event) { _this.updateUnit(instanceName, charId, event.unitId, function (unit) { var _a; var spell = event.spell; (_a = unit.inventory)[spell] || (_a[spell] = 1); unit.inventory[spell] -= 1; if (unit.inventory[spell] <= 0) { delete unit.inventory[spell]; } unit.spellbook.push(spell); _this.clearUnitActions(unit); }); }, }; 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); }; /** * Called when the unit completes a cast, or craft, or other action that takes time. */ this.clearUnitActions = function (unit) { unit.action = undefined; unit.actionDuration = undefined; unit.actionStart = undefined; unit.actionUsing = undefined; unit.actionTarget = undefined; }; 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, tickInterval); } return BaseClient; }()); exports.BaseClient = BaseClient;