Socket
Socket
Sign inDemoInstall

programming-game

Package Overview
Dependencies
13
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.0.1 to 0.0.2

build/recipes.d.ts

2

build/constants.d.ts
export declare const maxPartySize = 5;
export declare const globalCooldown = 0.5;
export declare const hungerRate = 0.1;
export declare const maxCarryWeight = 50000;
export declare const maxCarryWeight = 70000;
export declare const heavilyEncumberedWeight = 40000;
export declare const encumberedWeight = 25000;

@@ -7,4 +7,4 @@ "use strict";

exports.hungerRate = 0.1;
exports.maxCarryWeight = 50000; // 50 kg
exports.maxCarryWeight = 70000; // 100 kg or ~220 lbs
exports.heavilyEncumberedWeight = 40000; // 40 kg
exports.encumberedWeight = 25000; // 25 kg

@@ -1,22 +0,20 @@

import { AcceptPartyInviteIntent, AttackIntent, BuyIntent, CraftIntent, CurrentPlayer, EatIntent, HeartbeatFromServer, Intent, InviteToPartyIntent, LeavePartyIntent, MoveIntent, NPC_IDS, PlayerEquipment, ROLES, RespawnIntent, SeekPartyIntent, SellIntent, SetRoleIntent, SummonManaIntent, UseIntent, WeaponSkillIntent } from "./types";
import { AcceptPartyInviteIntent, AttackIntent, BuyIntent, ClientSidePlayer, ClientSideUnit, CraftIntent, EatIntent, Intent, InviteToPartyIntent, LeavePartyIntent, MoveIntent, NPC_IDS, PlayerEquipment, PlayersSeekingParty, Position, ROLES, RespawnIntent, SeekPartyIntent, SellIntent, SetRoleIntent, SummonManaIntent, UseIntent, WeaponSkillIntent } from "./types";
import { Spells } from "./spells";
import { WeaponSkill } from "./weapon-skills";
import { Items } from "./items";
import { Equipments, Items } from "./items";
import { EquipIntent } from "./types";
import { CastIntent } from "./spells";
export type OnTickCurrentPlayer = {} & Omit<CurrentPlayer, "units"> & typeof handlers;
import { recipes } from "./recipes";
/**
* Omit<Player, "partyInvites" | "party"> & {
partyInvites: Pick<Player, "name" | "role" | "id">[];
partyId?: string;
party: Party;
units: Record<string, Unit>;
};
*/
export type OnTickCurrentPlayer = ClientSidePlayer & typeof handlers;
declare const handlers: {
respawn: () => RespawnIntent;
seekParty: () => SeekPartyIntent;
acceptPartyInvite: (playerId: string) => AcceptPartyInviteIntent;
setRole: (role: ROLES) => SetRoleIntent;
buy: (item: Items, until: number, from: NPC_IDS) => BuyIntent;
sell: (item: Items, until: number, to: string) => SellIntent;
equip: (item: Items, slot: keyof PlayerEquipment) => EquipIntent;
craft: (item: Items) => CraftIntent;
leaveParty: () => LeavePartyIntent;
inviteToParty: (playerId: string) => InviteToPartyIntent;
use: (item: Items, until: number) => UseIntent;
cast: (spell: Spells, target?: string) => CastIntent;
eat: (item: Items, save: number) => EatIntent;
attack: (target: string) => AttackIntent;
move: (position: {

@@ -26,4 +24,15 @@ x: number;

}) => MoveIntent;
attack: (target: string) => AttackIntent;
respawn: () => RespawnIntent;
summonMana: () => SummonManaIntent;
eat: (item: Items, save: number) => EatIntent;
cast: (spell: Spells, target?: string) => CastIntent;
sell: (item: Items, until: number, to: string) => SellIntent;
buy: (item: Items, until: number, from: NPC_IDS) => BuyIntent;
use: (item: Items, until: number) => UseIntent;
seekParty: () => SeekPartyIntent;
inviteToParty: (playerId: string) => InviteToPartyIntent;
leaveParty: () => LeavePartyIntent;
acceptPartyInvite: (playerId: string) => AcceptPartyInviteIntent;
equip: (item: Equipments, slot: keyof PlayerEquipment) => EquipIntent;
craft: (item: keyof typeof recipes, from: Partial<Record<Items, number>>) => CraftIntent;
useWeaponSkill: (skill: WeaponSkill, target: string) => WeaponSkillIntent;

@@ -35,4 +44,4 @@ };

player?: OnTickCurrentPlayer;
playersSeekingParty: HeartbeatFromServer["playersSeekingParty"];
units: CurrentPlayer["units"];
playersSeekingParty: PlayersSeekingParty;
units: Record<string, ClientSideUnit>;
}

@@ -49,2 +58,3 @@ export type TickHeartbeat = OverworldHeartBeat | ArenaHeartBeat;

}
export declare const distance: (pos1: Position, pos2: Position) => number;
export type OnTick = (heartbeat: TickHeartbeat) => Intent | void;

@@ -58,3 +68,3 @@ type ConnectProps = {

};
export declare const connect: ({ credentials, onTick }: ConnectProps) => () => void;
export declare const connect: ({ credentials, onTick, }: ConnectProps) => (() => void);
export {};

@@ -13,42 +13,7 @@ "use strict";

};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.connect = void 0;
exports.connect = exports.distance = void 0;
var socket_io_client_1 = require("socket.io-client");
var types_1 = require("./types");
var recipes_1 = require("./recipes");
var handlers = {

@@ -100,4 +65,4 @@ setRole: function (role) {

},
craft: function (item) {
return { type: types_1.IntentType.craft, item: item };
craft: function (item, from) {
return { type: types_1.IntentType.craft, item: item, from: from };
},

@@ -108,6 +73,20 @@ useWeaponSkill: function (skill, target) {

};
var distance = function (pos1, pos2) {
return Math.sqrt(Math.pow((pos1.x - pos2.x), 2) + Math.pow((pos1.y - pos2.y), 2));
};
exports.distance = distance;
var entries = function (obj) {
return Object.entries(obj);
};
var isPlayerUnit = function (unit) {
return !!unit && unit.type === "player";
};
var fps = function (fps) {
return 1000 / fps;
};
var lastIntent;
var connect = function (_a) {
var credentials = _a.credentials, onTick = _a.onTick;
var url = process.env.NODE_ENV === "development"
? "http://localhost:3000"
? "http://localhost:3001"
: "wss://programming-game.com";

@@ -120,140 +99,426 @@ var socket = (0, socket_io_client_1.io)(url, {

socket.on("connect", function () {
console.log("connected");
console.log("connected", socket.id);
// setInterval(() => {
// console.log("lastIntent", JSON.stringify(lastIntent));
// }, 3000);
});
var signalIntents = function (intents) {
var validIntents = !!intents.find(function (intentObj) {
return intentObj.arenaIntent || intentObj.overworldIntent;
var innerState = {
instances: {},
arenaDurations: {},
};
var initializeInstance = function (instance, charId) {
var _a, _b;
(_a = innerState.instances)[instance] || (_a[instance] = {
time: 0,
characters: {},
playersSeekingParty: new Map(),
});
if (validIntents) {
socket.emit("message", intents);
}
(_b = innerState.instances[instance].characters)[charId] || (_b[charId] = {
units: {},
});
return innerState.instances[instance];
};
var messageHandler = function (m) { return __awaiter(void 0, void 0, void 0, function () {
var heartBeat, players, intents;
return __generator(this, function (_a) {
heartBeat = JSON.parse(m);
players = Object.values(heartBeat.players);
intents = players.map(function (player) {
var normalPlayerTickHeartbeat = {
inArena: false,
player: __assign(__assign(__assign({}, player), { party: player.party }), handlers),
time: heartBeat.time,
playersSeekingParty: heartBeat.playersSeekingParty,
units: player.units,
var updateUnit = function (instance, charId, unitId, cb) {
var char = innerState.instances[instance].characters[charId];
if (!char)
return;
var unit = char.units[unitId];
cb(unit);
};
var socketEventHandlers = {
loot: function (instance, charId, event) {
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) {
innerState.arenaDurations[instance] = event.duration;
},
beganCasting: function (instance, charId, event) {
updateUnit(instance, charId, event.unitId, function (unit) {
if (!unit)
return;
unit.casting = event.spell;
unit.castStart = Date.now();
});
},
castSpell: function (instance, charId, event) {
updateUnit(instance, charId, event.unitId, function (unit) {
if (!unit)
return;
unit.casting = null;
unit.castStart = null;
});
},
stats: function (instance, charId, event) {
updateUnit(instance, charId, event.unitId, function (unit) {
if (!unit)
return;
unit.stats = event.stats;
});
},
lostStatus: function (instance, charId, event) {
updateUnit(instance, charId, event.unitId, function (unit) {
if (!unit)
return;
delete unit.statusEffects[event.effect];
});
},
gainedStatus: function (instance, charId, event) {
updateUnit(instance, charId, event.unitId, function (unit) {
if (!unit)
return;
unit.statusEffects[event.effect] = {
duration: event.duration,
source: event.source,
stacks: event.stacks,
};
var arenaHeartBeat = {
inArena: true,
arenaTimeRemaining: heartBeat.arena.timeRemaining,
units: heartBeat.arena.units,
playersSeekingParty: [],
time: heartBeat.time,
};
var arenaPlayer = heartBeat.arena.players[player.id];
// console.log("player", cred.id, heartBeat.arena.players);
if (arenaPlayer) {
var party_1 = {};
var targetParty = Object.values(heartBeat.arena.parties).find(function (innerParty) {
return !!innerParty.members[arenaPlayer.id];
});
},
mp: function (instance, charId, event) {
updateUnit(instance, charId, event.unitId, function (unit) {
if (!unit)
return;
unit.mp = event.mp;
});
},
usedWeaponSkill: function (instance, charId, event) {
updateUnit(instance, charId, event.unitId, function (unit) {
if (!unit)
return;
unit.tp = event.tp;
});
},
ate: function (instance, charId, event) {
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) {
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
un.equipment[event.slot] = event.item;
(_a = un.inventory)[_b = event.item] || (_a[_b] = 1);
un.inventory[event.item] -= 1;
}
});
},
unequipped: function (instance, charId, event) {
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] = 1);
un.inventory[event.item] += 1;
}
});
},
beganCrafting: function (instance, charId, event) {
updateUnit(instance, charId, event.unitId, function (unit) {
if (isPlayerUnit(unit)) {
unit.crafting = event.item;
unit.craftStart = Date.now();
}
});
},
finishedCrafting: function (instance, charId, event) {
updateUnit(instance, charId, event.unitId, function (unit) {
if (isPlayerUnit(unit)) {
unit.crafting = undefined;
unit.craftStart = undefined;
var output = recipes_1.recipes[event.item].output;
entries(output).forEach(function (_a) {
var _b;
var item = _a[0], amount = _a[1];
(_b = unit.inventory)[item] || (_b[item] = 0);
unit.inventory[item] += amount;
});
if (targetParty) {
Object.keys(targetParty.members).forEach(function (memberId) {
party_1[memberId] = heartBeat.arena.units[memberId];
});
entries(event.spent).forEach(function (_a) {
var _b;
var item = _a[0], amount = _a[1];
(_b = unit.inventory)[item] || (_b[item] = 0);
unit.inventory[item] -= amount;
});
unit.crafting = undefined;
}
});
},
traded: function (instance, charId, event) {
updateUnit(instance, charId, event.actingUnitId, function (unit) {
if (isPlayerUnit(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;
});
}
});
},
moved: function (instance, charId, event) {
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 = initializeInstance(instanceName, charId);
instance.characters[player.id] = {
units: event.units,
};
},
unitAppeared: function (instanceName, charId, event) {
var instance = initializeInstance(instanceName, charId);
instance.characters[charId].units[event.unit.id] = event.unit;
},
unitDisappeared: function (instanceName, charId, event) {
var instance = initializeInstance(instanceName, charId);
delete instance.characters[charId].units[event.unitId];
},
seekParty: function (instanceName, charId, event) {
var instance = initializeInstance(instanceName, charId);
instance.playersSeekingParty.set(event.playersSeeking.id, event.playersSeeking);
},
acceptedPartyInvite: function (instanceName, charId, event) {
var instance = initializeInstance(instanceName, charId);
instance.playersSeekingParty.delete(event.inviteeId);
},
updatedParty: function (instanceName, charId, event) {
updateUnit(instanceName, charId, charId, function (unit) {
if (!unit)
return;
unit.party = event.party;
});
},
updatedRole: function (instanceName, charId, event) {
updateUnit(instanceName, charId, event.unitId, function (unit) {
if (isPlayerUnit(unit)) {
unit.role = event.role;
}
});
},
setIntent: function (instanceName, charId, event) {
updateUnit(instanceName, charId, event.unitId, function (innerUnit) {
if (innerUnit) {
innerUnit.intent = event.intent;
}
});
},
attacked: function (instanceName, charId, event) {
updateUnit(instanceName, charId, event.attacked, function (attackedUnit) {
if (!attackedUnit)
return;
attackedUnit.hp = event.hp;
});
updateUnit(instanceName, charId, event.attacker, function (attackerUnit) {
if (!attackerUnit)
return;
attackerUnit.tp = event.attackerTp;
});
},
despawn: function (instanceName, charId, event) {
var instance = initializeInstance(instanceName, charId);
delete instance.characters[charId].units[event.unitId];
},
hp: function (instanceName, charId, event) {
updateUnit(instanceName, charId, event.unitId, function (unit) {
if (!unit)
return;
unit.hp = event.hp;
});
},
tp: function (instanceName, charId, event) {
updateUnit(instanceName, charId, event.unitId, function (unit) {
if (!unit)
return;
unit.tp = event.tp;
});
},
calories: function (instanceName, charId, event) {
updateUnit(instanceName, charId, event.unitId, function (unit) {
if (unit && "calories" in unit) {
unit.calories = event.calories;
}
});
},
died: function (instanceName, charId, event) {
updateUnit(instanceName, charId, event.unitId, function (unit) {
if (!unit)
return;
unit.hp = 0;
});
},
invited: function (instanceName, charId, event) {
var instance = initializeInstance(instanceName, charId);
var player = instance.characters[charId].units[charId];
if (player) {
player.partyInvites.push({
id: event.inviter.id,
name: event.inviter.name,
role: event.inviter.role,
});
}
},
};
var time = 0;
var lastOnTick = Date.now();
var runOnTick = function () {
var elapsed = Date.now() - lastOnTick;
lastOnTick = Date.now();
Object.entries(innerState.instances).forEach(function (_a) {
var instanceId = _a[0], instance = _a[1];
if (innerState.arenaDurations[instanceId]) {
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;
});
});
time++;
if (instanceId === "overworld") {
var char = charState.units[charId];
if (!char)
return;
var intent = onTick({
inArena: false,
player: __assign(__assign({}, char), handlers),
playersSeekingParty: Array.from(instance.playersSeekingParty.values()),
time: time,
units: charState.units,
});
if (intent) {
lastIntent = {
c: charId,
i: instanceId,
intent: intent,
unitId: charId,
};
socket.emit("setIntent", lastIntent);
}
arenaHeartBeat.player = __assign(__assign(__assign({}, arenaPlayer), { party: party_1, partyInvites: [] }), handlers);
else {
lastIntent = undefined;
}
}
return {
overworldIntent: onTick(normalPlayerTickHeartbeat),
arenaIntent: onTick(arenaHeartBeat),
};
else {
var char = charState.units[charId];
var intent = onTick({
inArena: true,
arenaTimeRemaining: innerState.arenaDurations[instanceId],
player: char
? __assign(__assign({}, char), handlers) : undefined,
playersSeekingParty: Array.from(instance.playersSeekingParty.values()),
time: time,
units: charState.units,
});
if (intent) {
lastIntent = {
c: charId,
i: instanceId,
intent: intent,
unitId: charId,
};
socket.emit("setIntent", lastIntent);
}
else {
lastIntent = undefined;
}
}
// need to emit for arena as well
});
signalIntents(intents);
return [2 /*return*/];
});
}); };
console.log('registering "message" handler');
socket.on("message", messageHandler);
};
var heartBeatInterval = setInterval(runOnTick, fps(60));
socket.on("events", 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];
events.forEach(function (_a) {
// console.log("eee...");
var eventName = _a[0], eventPayload = _a[1];
initializeInstance(instanceId, charId);
// if (eventPayload.instance !== "overworld") {
// console.log(
// "got ",
// eventPayload.instance,
// innerState.instances[eventPayload.instance]
// );
// }
var instance = innerState.instances[instanceId];
var handler = socketEventHandlers[eventName];
var isConnect = eventName === "connectionEvent";
// const hasChar = instance.characters[eventPayload.character];
// if (!isConnect && !hasChar) {
// return;
// }
if (handler) {
// @ts-expect-error
handler(instanceId, charId, eventPayload);
}
});
});
});
runOnTick();
});
return function () {
socket.off("message", messageHandler);
clearInterval(heartBeatInterval);
socket.disconnect();
socket.close();
innerState = {
instances: {},
arenaDurations: {},
};
};
// return entries(socketEventHandlers).reduce<() => void>(
// (acc, [key, handler]) => {
// socket.on(key, handler);
// runOnTick();
// return () => {
// socket.off(key, handler);
// acc();
// };
// },
// () => {
// }
// );
};
exports.connect = connect;
// class Client {
// private socket: Socket;
// constructor(credentials: Credential[]) {
// this.socket = io("");
// this.socket.on("connect", () => {
// console.log("connected");
// });
// }
// private signalIntent = (intent: Intent) => {
// this.socket.emit("message", intent);
// };
// public onTick: OnTick = (callback) => {
// const messageHandler = async (m: any) => {
// const intent = await callback(JSON.parse(m));
// if (intent) this.signalIntent(intent);
// };
// this.socket.on("message", messageHandler);
// return () => {
// this.socket.off("message", messageHandler);
// };
// };
// public setRole = (role: ROLES) => {
// this.signalIntent({ type: IntentType.setRole, role });
// };
// public attack = (target: string) => {
// this.signalIntent({ type: IntentType.attack, target });
// };
// public move = (position: { x: number; y: number }) => {
// this.signalIntent({ type: IntentType.move, position });
// };
// public respawn = () => {
// this.signalIntent({ type: IntentType.respawn });
// };
// public summonMana = () => {
// this.signalIntent({ type: IntentType.summonMana });
// };
// public eat = (item: Items, save: number) => {
// this.signalIntent({ type: IntentType.eat, item: item, save });
// };
// public cast = (spell: Spells, target?: string) => {
// this.signalIntent({ type: IntentType.cast, spell, target });
// };
// public sell = (item: Items, until: number, to: string) => {
// this.signalIntent({ type: IntentType.sell, item, until, to });
// };
// public buy = (item: Items, until: number, from: NPC_IDS) => {
// this.signalIntent({ type: IntentType.buy, item, until, from });
// };
// public use = (item: Items, until: number) => {
// this.signalIntent({ type: IntentType.use, item, until });
// };
// public seekParty = () => {
// this.signalIntent({ type: IntentType.seekParty });
// };
// public inviteToParty = (playerId: string) => {
// this.signalIntent({ type: IntentType.inviteToParty, playerId });
// };
// public leaveParty = () => {
// this.signalIntent({ type: IntentType.leaveParty });
// };
// public acceptPartyInvite = (playerId: string) => {
// this.signalIntent({ type: IntentType.acceptPartyInvite, playerId });
// };
// public equip = (item: Items, slot: keyof PlayerEquipment) => {
// this.signalIntent({ type: IntentType.equip, item, slot });
// };
// public craft = (item: Items) => {
// this.signalIntent({ type: IntentType.craft, item });
// };
// public useWeaponSkill = (skill: WeaponSkill, target: string) => {
// this.signalIntent({ type: IntentType.weaponSkill, skill, target });
// };
// public disconnect = () => {
// this.socket.disconnect();
// this.socket.close();
// };
// }

@@ -125,12 +125,3 @@ import { UniqueItemId, UnitStats } from "./types";

type ArmorType = "helm" | "chest" | "legs" | "feet" | "hands";
type ItemCountMap = Partial<Record<Items, number>>;
type Recipe = {
duration: number;
input: ItemCountMap | ItemCountMap[];
output: ItemCountMap;
required: (Items | Spells)[][];
};
type Recipes = Partial<Record<Items, Recipe>>;
export declare const recipes: Recipes;
export declare const items: Record<Items, ItemDefinition>;
export {};

@@ -14,3 +14,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.items = exports.recipes = void 0;
exports.items = void 0;
var spells_1 = require("./spells");

@@ -30,336 +30,2 @@ var generateArmor = function (id, name, type, defense) {

};
exports.recipes = {
woodenArrow: {
duration: 1000,
input: {
feather: 1,
arrowShaft: 1,
},
output: {
woodenArrow: 1,
},
required: [],
},
arrowShaft: {
duration: 1000,
input: {
bitOfWood: 1,
},
output: {
arrowShaft: 1,
},
required: [],
},
bitOfWood: {
duration: 1000,
input: {
woodLog: 1,
},
output: {
bitOfWood: 10,
},
required: [],
},
copperIngot: {
duration: 30000,
input: {
chunkOfCopper: 3,
},
output: {
copperIngot: 1,
},
required: [["furnace"]],
},
copperDagger: {
duration: 30000,
input: {
copperIngot: 3,
woodPommel: 1,
},
output: {
copperDagger: 1,
},
required: [["anvil"]],
},
woodPommel: {
duration: 30000,
input: {
bitOfWood: 3,
},
output: {
woodPommel: 1,
},
required: [],
},
leatherStrips: {
duration: 30000,
input: {
lightLeather: 1,
},
output: {
leatherStrips: 3,
},
required: [],
},
lightLeather: {
duration: 30000,
input: {
ratPelt: 3,
},
output: {
lightLeather: 1,
},
required: [],
},
snakeSkinLeather: {
duration: 30000,
input: {
snakeSkin: 3,
},
output: {
snakeSkinLeather: 1,
},
required: [],
},
// cloth ingredients
linenCloth: {
duration: 30000,
input: {
tatteredLinenCloth: 3,
},
output: {
linenCloth: 1,
},
required: [],
},
woolCloth: {
duration: 30000,
input: {
tatteredWoolCloth: 5,
},
output: {
woolCloth: 1,
},
required: [],
},
// cloth armor
clothHelm: {
duration: 30000,
input: {
linenCloth: 3,
coarseThread: 10,
},
output: {
clothHelm: 1,
},
required: [["copperNeedle"]],
},
clothChest: {
duration: 30000,
input: {
linenCloth: 5,
coarseThread: 10,
},
output: {
clothChest: 1,
},
required: [["copperNeedle"]],
},
clothGloves: {
duration: 30000,
input: {
linenCloth: 2,
coarseThread: 10,
},
output: {
clothGloves: 1,
},
required: [["copperNeedle"]],
},
clothLegs: {
duration: 30000,
input: {
linenCloth: 4,
coarseThread: 10,
},
output: {
clothLegs: 1,
},
required: [["copperNeedle"]],
},
clothBoots: {
duration: 30000,
input: {
linenCloth: 2,
coarseThread: 10,
},
output: {
clothBoots: 1,
},
required: [["copperNeedle"]],
},
// leather armor
lightLeatherHelm: {
duration: 30000,
input: {
lightLeather: 3,
leatherStrips: 10,
},
output: {
lightLeatherHelm: 1,
},
required: [["copperNeedle"]],
},
lightLeatherChest: {
duration: 30000,
input: {
lightLeather: 5,
leatherStrips: 10,
},
output: {
lightLeatherChest: 1,
},
required: [["copperNeedle"]],
},
lightLeatherGloves: {
duration: 30000,
input: {
lightLeather: 2,
leatherStrips: 10,
},
output: {
lightLeatherGloves: 1,
},
required: [["copperNeedle"]],
},
lightLeatherLegs: {
duration: 30000,
input: {
lightLeather: 4,
leatherStrips: 10,
},
output: {
lightLeatherLegs: 1,
},
required: [["copperNeedle"]],
},
lightLeatherBoots: {
duration: 30000,
input: {
lightLeather: 2,
leatherStrips: 10,
},
output: {
lightLeatherBoots: 1,
},
required: [["copperNeedle"]],
},
// snake skin armor
snakeSkinHelm: {
duration: 30000,
input: {
snakeSkinLeather: 3,
leatherStrips: 10,
},
output: {
snakeSkinHelm: 1,
},
required: [["copperNeedle"]],
},
snakeSkinChest: {
duration: 30000,
input: {
snakeSkinLeather: 5,
leatherStrips: 10,
},
output: {
snakeSkinChest: 1,
},
required: [["copperNeedle"]],
},
snakeSkinGloves: {
duration: 30000,
input: {
snakeSkinLeather: 2,
leatherStrips: 10,
},
output: {
snakeSkinGloves: 1,
},
required: [["copperNeedle"]],
},
snakeSkinLegs: {
duration: 30000,
input: {
snakeSkinLeather: 4,
leatherStrips: 10,
},
output: {
snakeSkinLegs: 1,
},
required: [["copperNeedle"]],
},
snakeSkinBoots: {
duration: 30000,
input: {
snakeSkinLeather: 2,
leatherStrips: 10,
},
output: {
snakeSkinBoots: 1,
},
required: [["copperNeedle"]],
},
// copper mail armor
copperMailHelm: {
duration: 30000,
input: {
copperIngot: 3,
},
output: {
copperMailHelm: 1,
},
required: [["anvil"]],
},
copperMailChest: {
duration: 30000,
input: {
copperIngot: 5,
},
output: {
copperMailChest: 1,
},
required: [["anvil"]],
},
copperMailGloves: {
duration: 30000,
input: {
copperIngot: 2,
},
output: {
copperMailGloves: 1,
},
required: [["anvil"]],
},
copperMailLegs: {
duration: 30000,
input: {
copperIngot: 4,
},
output: {
copperMailLegs: 1,
},
required: [["anvil"]],
},
copperMailBoots: {
duration: 30000,
input: {
copperIngot: 2,
},
output: {
copperMailBoots: 1,
},
required: [["anvil"]],
},
};
var createCraftingIngredient = function (id, name, weight) {

@@ -366,0 +32,0 @@ return {

@@ -1,2 +0,3 @@

import { Amulets, Boots, Chests, Gloves, Items, Legs, OffhandWeapons, OneHandedWeapons, Rings, Weapons } from "./items";
import { Amulets, Boots, Chests, Equipments, Gloves, Helms, Items, Legs, OffhandWeapons, OneHandedWeapons, Rings, Weapons } from "./items";
import { recipes } from "./recipes";
import { CastIntent, Spells } from "./spells";

@@ -26,8 +27,2 @@ import { WeaponSkill } from "./weapon-skills";

}
export type CurrentPlayer = Omit<Player, "partyInvites" | "party"> & {
partyInvites: Pick<Player, "name" | "role" | "id">[];
partyId?: string;
party: Party;
units: Record<string, Unit>;
};
export type Parties = Record<string, {

@@ -37,13 +32,175 @@ lootIndex: number;

}>;
export type HeartbeatFromServer = {
players: Record<string, CurrentPlayer>;
time: number;
playersSeekingParty: Pick<Player, "name" | "role" | "id">[];
export type PlayersSeekingParty = Pick<Player, "name" | "role" | "id">[];
export type ClientSidePartyInvites = Pick<Player, "name" | "role" | "id">[];
export type InstanceName = "overworld" | "1v1Arena";
export type ClientSidePlayer = Omit<Player, "partyInvites" | "party"> & {
partyInvites: ClientSidePartyInvites;
party: Record<string, true>;
};
export type ClientSideUnit = Omit<Unit, "partyInvites" | "party"> & {
partyInvites: ClientSidePartyInvites;
party: Record<string, true>;
};
export type ClientSideNPC = Omit<NPC, "partyInvites" | "party"> & {
partyInvites: ClientSidePartyInvites;
party: Record<string, true>;
};
export type RawEvents = {
acceptedPartyInvite: {
inviteeId: string;
inviterId: string;
};
connectionEvent: {
player: Player;
party: Record<string, true>;
units: Record<string, ClientSideUnit>;
playersSeekingParty: PlayersSeekingParty;
time: number;
partyInvites: ClientSidePartyInvites;
};
updatedRole: {
unitId: string;
role: ROLES;
};
updatedParty: {
party: Record<string, true>;
};
invited: {
inviter: {
id: string;
role: ROLES;
name: string;
};
invitee: string;
};
seekParty: {
playersSeeking: PlayersSeekingParty[0];
};
unitAppeared: {
unit: ClientSideUnit;
};
unitDisappeared: {
unitId: string;
};
moved: {
id: string;
} & Position;
attacked: {
attacker: string;
attacked: string;
damage: number;
hp: number;
attackerTp: number;
};
loot: {
unitId: string;
items: Partial<Record<Items, number>>;
};
beganCasting: {
unitId: string;
spell: Spells;
target?: string;
};
castSpell: {
unitId: string;
spell: Spells;
};
gainedStatus: {
unitId: string;
effect: StatusEffect;
duration: number;
source: string;
stacks: number;
shield: number;
};
arena: {
timeRemaining: number;
units: Record<string, Unit>;
players: Record<string, Player>;
parties: Parties;
duration: number;
};
lostStatus: {
unitId: string;
effect: StatusEffect;
};
hp: {
unitId: string;
hp: number;
};
mp: {
unitId: string;
mp: number;
};
tp: {
unitId: string;
tp: number;
};
calories: {
unitId: string;
calories: number;
};
ate: {
unitId: string;
item: Items;
calories: number;
remaining: number;
};
equipped: {
unitId: string;
item: Equipments;
slot: keyof PlayerEquipment;
};
usedWeaponSkill: {
unitId: string;
targetId: string;
skill: WeaponSkill;
tp: number;
};
unequipped: {
unitId: string;
item: Equipments;
slot: keyof PlayerEquipment;
};
beganCrafting: {
unitId: string;
item: keyof typeof recipes;
};
finishedCrafting: {
unitId: string;
item: keyof typeof recipes;
spent: Partial<Record<Items, number>>;
};
setIntent: {
unitId: string;
intent: Intent;
};
despawn: {
unitId: string;
};
died: {
unitId: string;
};
traded: {
actingUnitId: string;
targetUnitId: string;
gave: Partial<Record<Items, number>>;
got: Partial<Record<Items, number>>;
};
stats: {
unitId: string;
stats: UnitStats;
};
};
type CreateEventsEvents = <T extends keyof RawEvents>() => EventGroup<T>;
export type EventGroup<T extends keyof RawEvents> = [T, RawEvents[T]][];
export type BatchedEvents = {
[instance: string]: {
[character: string]: ReturnType<CreateEventsEvents>;
};
};
export type EventObjMap<T extends {}> = {
c: string;
i: InstanceName;
} & T;
type CreateIntentMap = <T extends keyof RawEvents, K>(obj: K) => {
[key in T]: (instance: string, charId: string, event: RawEvents[key]) => void;
};
export type EventMap = ReturnType<CreateIntentMap>;
/**

@@ -187,2 +344,3 @@ * Spell Groups?

export type BaseUnit = {
unitsInSight: Set<string>;
id: string;

@@ -253,3 +411,3 @@ hp: number;

threat: Record<string, true>;
crafting?: Items;
crafting?: keyof typeof recipes;
craftStart?: number;

@@ -259,14 +417,14 @@ inventory: Inventory;

export type PlayerEquipment = {
helm: Items | null;
chest: Chests | null;
legs: Legs | null;
feet: Boots | null;
hands: Gloves | null;
weapon: Weapons | null;
offhand: OffhandWeapons | OneHandedWeapons | null;
amulet: Amulets | null;
ring1: Rings | null;
ring2: Rings | null;
earring1: Earrings | null;
earring2: Earrings | null;
helm: Helms | UniqueItemId | null;
chest: Chests | UniqueItemId | null;
legs: Legs | UniqueItemId | null;
feet: Boots | UniqueItemId | null;
hands: Gloves | UniqueItemId | null;
weapon: Weapons | UniqueItemId | null;
offhand: OffhandWeapons | OneHandedWeapons | UniqueItemId | null;
amulet: Amulets | UniqueItemId | null;
ring1: Rings | UniqueItemId | null;
ring2: Rings | UniqueItemId | null;
earring1: Earrings | UniqueItemId | null;
earring2: Earrings | UniqueItemId | null;
};

@@ -307,7 +465,8 @@ type Earrings = never;

type: IntentType.craft;
item: Items;
item: keyof typeof recipes;
from: Partial<Inventory>;
}
export interface EquipIntent extends BaseIntent {
type: IntentType.equip;
item: Items;
item: Equipments;
slot: keyof PlayerEquipment;

@@ -314,0 +473,0 @@ }

{
"name": "programming-game",
"version": "0.0.1",
"version": "0.0.2",
"description": "",

@@ -21,3 +21,4 @@ "keywords": [],

"./types": "./build/types.js",
"./weapon-skills": "./build/weapon-skills.js"
"./weapon-skills": "./build/weapon-skills.js",
"./recipes": "./build/recipes.js"
},

@@ -24,0 +25,0 @@ "scripts": {

export const maxPartySize = 5;
export const globalCooldown = 0.5;
export const hungerRate = 0.1;
export const maxCarryWeight = 50_000; // 50 kg
export const maxCarryWeight = 70_000; // 100 kg or ~220 lbs
export const heavilyEncumberedWeight = 40_000; // 40 kg
export const encumberedWeight = 25_000; // 25 kg

@@ -1,10 +0,15 @@

import { io } from "socket.io-client";
import { Socket, io } from "socket.io-client";
import {
AcceptPartyInviteIntent,
AttackIntent,
BatchedEvents,
BuyIntent,
ClientSidePartyInvites,
ClientSidePlayer,
ClientSideUnit,
CraftIntent,
CurrentPlayer,
EatIntent,
HeartbeatFromServer,
EventMap,
EventObjMap,
InstanceName,
Intent,

@@ -16,5 +21,8 @@ IntentType,

NPC_IDS,
Party,
Player,
PlayerEquipment,
PlayersSeekingParty,
Position,
ROLES,
RawEvents,
RespawnIntent,

@@ -25,83 +33,80 @@ SeekPartyIntent,

SummonManaIntent,
Unit,
UseIntent,
WeaponSkillIntent,
sightRange,
} from "./types";
import { Spells } from "./spells";
import { WeaponSkill } from "./weapon-skills";
import { Items } from "./items";
import { Equipments, Items } from "./items";
import { EquipIntent } from "./types";
import { CastIntent } from "./spells";
import { recipes } from "./recipes";
import { channel } from "diagnostics_channel";
// this will be different than the actual server heartbeat
// this adds runtime sugar to the game objects.
export type OnTickCurrentPlayer = {} & Omit<CurrentPlayer, "units"> &
typeof handlers;
const handlers: {
respawn: () => RespawnIntent;
seekParty: () => SeekPartyIntent;
acceptPartyInvite: (playerId: string) => AcceptPartyInviteIntent;
setRole: (role: ROLES) => SetRoleIntent;
buy: (item: Items, until: number, from: NPC_IDS) => BuyIntent;
sell: (item: Items, until: number, to: string) => SellIntent;
equip: (item: Items, slot: keyof PlayerEquipment) => EquipIntent;
craft: (item: Items) => CraftIntent;
leaveParty: () => LeavePartyIntent;
inviteToParty: (playerId: string) => InviteToPartyIntent;
use: (item: Items, until: number) => UseIntent;
cast: (spell: Spells, target?: string) => CastIntent;
eat: (item: Items, save: number) => EatIntent;
move: (position: { x: number; y: number }) => MoveIntent;
attack: (target: string) => AttackIntent;
summonMana: () => SummonManaIntent;
useWeaponSkill: (skill: WeaponSkill, target: string) => WeaponSkillIntent;
} = {
setRole: (role: ROLES) => {
/**
* Omit<Player, "partyInvites" | "party"> & {
partyInvites: Pick<Player, "name" | "role" | "id">[];
partyId?: string;
party: Party;
units: Record<string, Unit>;
};
*/
export type OnTickCurrentPlayer = ClientSidePlayer & typeof handlers;
const handlers = {
setRole: (role: ROLES): SetRoleIntent => {
return { type: IntentType.setRole, role };
},
attack: (target: string) => {
attack: (target: string): AttackIntent => {
return { type: IntentType.attack, target };
},
move: (position: { x: number; y: number }) => {
move: (position: { x: number; y: number }): MoveIntent => {
return { type: IntentType.move, position };
},
respawn: () => {
respawn: (): RespawnIntent => {
return { type: IntentType.respawn } as RespawnIntent;
},
summonMana: () => {
summonMana: (): SummonManaIntent => {
return { type: IntentType.summonMana };
},
eat: (item: Items, save: number) => {
eat: (item: Items, save: number): EatIntent => {
return { type: IntentType.eat, item: item, save };
},
cast: (spell: Spells, target?: string) => {
cast: (spell: Spells, target?: string): CastIntent => {
return { type: IntentType.cast, spell, target };
},
sell: (item: Items, until: number, to: string) => {
sell: (item: Items, until: number, to: string): SellIntent => {
return { type: IntentType.sell, item, until, to };
},
buy: (item: Items, until: number, from: NPC_IDS) => {
buy: (item: Items, until: number, from: NPC_IDS): BuyIntent => {
return { type: IntentType.buy, item, until, from };
},
use: (item: Items, until: number) => {
use: (item: Items, until: number): UseIntent => {
return { type: IntentType.use, item, until };
},
seekParty: () => {
seekParty: (): SeekPartyIntent => {
return { type: IntentType.seekParty };
},
inviteToParty: (playerId: string) => {
inviteToParty: (playerId: string): InviteToPartyIntent => {
return { type: IntentType.inviteToParty, playerId };
},
leaveParty: () => {
leaveParty: (): LeavePartyIntent => {
return { type: IntentType.leaveParty };
},
acceptPartyInvite: (playerId: string) => {
acceptPartyInvite: (playerId: string): AcceptPartyInviteIntent => {
return { type: IntentType.acceptPartyInvite, playerId };
},
equip: (item: Items, slot: keyof PlayerEquipment) => {
equip: (item: Equipments, slot: keyof PlayerEquipment): EquipIntent => {
return { type: IntentType.equip, item, slot };
},
craft: (item: Items) => {
return { type: IntentType.craft, item };
craft: (
item: keyof typeof recipes,
from: Partial<Record<Items, number>>
): CraftIntent => {
return { type: IntentType.craft, item, from };
},
useWeaponSkill: (skill: WeaponSkill, target: string) => {
useWeaponSkill: (skill: WeaponSkill, target: string): WeaponSkillIntent => {
return { type: IntentType.weaponSkill, skill, target };

@@ -114,6 +119,8 @@ },

player?: OnTickCurrentPlayer;
playersSeekingParty: HeartbeatFromServer["playersSeekingParty"];
units: CurrentPlayer["units"];
playersSeekingParty: PlayersSeekingParty;
units: Record<string, ClientSideUnit>;
}
export type TickHeartbeat = OverworldHeartBeat | ArenaHeartBeat;
interface OverworldHeartBeat extends BasicHeartbeat {

@@ -123,2 +130,3 @@ inArena: false;

}
interface ArenaHeartBeat extends BasicHeartbeat {

@@ -130,3 +138,12 @@ inArena: true;

export const distance = (pos1: Position, pos2: Position) => {
return Math.sqrt((pos1.x - pos2.x) ** 2 + (pos1.y - pos2.y) ** 2);
};
export type OnTick = (heartbeat: TickHeartbeat) => Intent | void;
const entries = <T extends Record<string, unknown>>(
obj: T
): [keyof T, T[keyof T]][] => {
return Object.entries(obj) as [keyof T, T[keyof T]][];
};
type ConnectProps = {

@@ -139,8 +156,21 @@ credentials: {

};
export const connect = ({ credentials, onTick }: ConnectProps) => {
const isPlayerUnit = (unit?: ClientSideUnit): unit is ClientSidePlayer => {
return !!unit && unit.type === "player";
};
const fps = (fps: number) => {
return 1000 / fps;
};
let lastIntent: EventObjMap<RawEvents["setIntent"]> | undefined;
export const connect = ({
credentials,
onTick,
}: ConnectProps): (() => void) => {
const url =
process.env.NODE_ENV === "development"
? "http://localhost:3000"
? "http://localhost:3001"
: `wss://programming-game.com`;
const socket = io(url, {
const socket: Socket<{
events: (events: BatchedEvents) => void;
setIntent: (eventVal: EventObjMap<RawEvents["setIntent"]>) => void;
}> = io(url, {
auth: {

@@ -151,151 +181,420 @@ credentials,

socket.on("connect", () => {
console.log("connected");
console.log("connected", socket.id);
// setInterval(() => {
// console.log("lastIntent", JSON.stringify(lastIntent));
// }, 3000);
});
const signalIntents = (
intents: {
overworldIntent: Intent | void;
arenaIntent: Intent | void;
}[]
let innerState: {
instances: {
[key: string]: {
time: number;
characters: Record<
string,
{
units: Record<string, ClientSideUnit>;
}
>;
playersSeekingParty: Map<string, PlayersSeekingParty[0]>;
};
};
arenaDurations: Record<string, number>;
} = {
instances: {},
arenaDurations: {},
};
const initializeInstance = (instance: string, charId: string) => {
innerState.instances[instance] ||= {
time: 0,
characters: {},
playersSeekingParty: new Map(),
};
innerState.instances[instance].characters[charId] ||= {
units: {},
};
return innerState.instances[instance];
};
const updateUnit = <T extends keyof RawEvents>(
instance: string,
charId: string,
unitId: string,
cb: (unit?: ClientSideUnit | ClientSidePlayer) => void
) => {
const validIntents = !!intents.find((intentObj) => {
return intentObj.arenaIntent || intentObj.overworldIntent;
});
if (validIntents) {
socket.emit("message", intents);
}
const char = innerState.instances[instance].characters[charId];
if (!char) return;
const unit = char.units[unitId];
cb(unit);
};
const messageHandler = async (m: string) => {
const heartBeat = JSON.parse(m) as HeartbeatFromServer;
const players = Object.values(heartBeat.players);
const intents = players.map((player) => {
const normalPlayerTickHeartbeat = {
inArena: false,
player: {
...player,
party: player.party,
...handlers,
},
time: heartBeat.time,
playersSeekingParty: heartBeat.playersSeekingParty,
units: player.units,
} satisfies TickHeartbeat;
const arenaHeartBeat: TickHeartbeat = {
inArena: true,
arenaTimeRemaining: heartBeat.arena.timeRemaining,
units: heartBeat.arena.units,
playersSeekingParty: [],
time: heartBeat.time,
const socketEventHandlers: EventMap = {
loot: (instance, charId, event) => {
updateUnit(instance, charId, event.unitId, (unit) => {
if (!unit) return;
if (!("inventory" in unit)) return;
entries(event.items).forEach(([item, amount]) => {
if (!amount) return;
unit.inventory[item] ||= 0;
unit.inventory[item]! += amount;
});
});
},
arena: (instance, charId, event) => {
innerState.arenaDurations[instance] = event.duration;
},
beganCasting: (instance, charId, event) => {
updateUnit(instance, charId, event.unitId, (unit) => {
if (!unit) return;
unit.casting = event.spell;
unit.castStart = Date.now();
});
},
castSpell: (instance, charId, event) => {
updateUnit(instance, charId, event.unitId, (unit) => {
if (!unit) return;
unit.casting = null;
unit.castStart = null;
});
},
stats: (instance, charId, event) => {
updateUnit(instance, charId, event.unitId, (unit) => {
if (!unit) return;
unit.stats = event.stats;
});
},
lostStatus: (instance, charId, event) => {
updateUnit(instance, charId, event.unitId, (unit) => {
if (!unit) return;
delete unit.statusEffects[event.effect];
});
},
gainedStatus: (instance, charId, event) => {
updateUnit(instance, charId, event.unitId, (unit) => {
if (!unit) return;
unit.statusEffects[event.effect] = {
duration: event.duration,
source: event.source,
stacks: event.stacks,
};
});
},
mp: (instance, charId, event) => {
updateUnit(instance, charId, event.unitId, (unit) => {
if (!unit) return;
unit.mp = event.mp;
});
},
usedWeaponSkill: (instance, charId, event) => {
updateUnit(instance, charId, event.unitId, (unit) => {
if (!unit) return;
unit.tp = event.tp;
});
},
ate: (instance, charId, event) => {
updateUnit(instance, charId, event.unitId, (unit) => {
if (!unit) return;
if ("calories" in unit) {
unit.calories = event.calories;
}
if ("inventory" in unit) {
unit.inventory[event.item] = event.remaining;
}
});
},
equipped: (instance, charId, event) => {
updateUnit(instance, charId, event.unitId, (unit) => {
if (!unit) return;
if (unit?.type === "player") {
const un = unit as ClientSidePlayer;
//@ts-ignore
un.equipment[event.slot] = event.item;
un.inventory[event.item] ||= 1;
un.inventory[event.item]! -= 1;
}
});
},
unequipped: (instance, charId, event) => {
updateUnit(instance, charId, event.unitId, (unit) => {
if (!unit) return;
if (unit?.type === "player") {
const un = unit as ClientSidePlayer;
// @ts-ignore
unit.equipment[event.slot] = null;
un.inventory[event.item] ||= 1;
un.inventory[event.item]! += 1;
}
});
},
beganCrafting: (instance, charId, event) => {
updateUnit(instance, charId, event.unitId, (unit) => {
if (isPlayerUnit(unit)) {
unit.crafting = event.item;
unit.craftStart = Date.now();
}
});
},
finishedCrafting: (instance, charId, event) => {
updateUnit(instance, charId, event.unitId, (unit) => {
if (isPlayerUnit(unit)) {
unit.crafting = undefined;
unit.craftStart = undefined;
const output = recipes[event.item].output;
entries(output).forEach(([item, amount]) => {
unit.inventory[item] ||= 0;
unit.inventory[item]! += amount!;
});
entries(event.spent).forEach(([item, amount]) => {
unit.inventory[item] ||= 0;
unit.inventory[item]! -= amount!;
});
unit.crafting = undefined;
}
});
},
traded: (instance, charId, event) => {
updateUnit(instance, charId, event.actingUnitId, (unit): void => {
if (isPlayerUnit(unit)) {
entries(event.gave).forEach(([item, amount]) => {
unit.inventory[item] ||= 0;
unit.inventory[item]! -= amount!;
});
entries(event.got).forEach(([item, amount]) => {
unit.inventory[item] ||= 0;
unit.inventory[item]! += amount!;
});
}
});
},
moved: (instance, charId, event) => {
updateUnit(instance, charId, event.id, (unit) => {
if (!unit) return;
const { x, y } = event;
unit.position = { x, y };
});
},
connectionEvent: (instanceName, charId, event) => {
const player = event.player;
const instance = initializeInstance(instanceName, charId);
instance.characters[player.id] = {
units: event.units,
};
const arenaPlayer = heartBeat.arena.players[player.id];
// console.log("player", cred.id, heartBeat.arena.players);
if (arenaPlayer) {
const party: Party = {};
const targetParty = Object.values(heartBeat.arena.parties).find(
(innerParty) => {
return !!innerParty.members[arenaPlayer.id];
},
unitAppeared: (instanceName, charId, event) => {
const instance = initializeInstance(instanceName, charId);
instance.characters[charId].units[event.unit.id] = event.unit;
},
unitDisappeared: (instanceName, charId, event) => {
const instance = initializeInstance(instanceName, charId);
delete instance.characters[charId].units[event.unitId];
},
seekParty: (instanceName, charId, event) => {
const instance = initializeInstance(instanceName, charId);
instance.playersSeekingParty.set(
event.playersSeeking.id,
event.playersSeeking
);
},
acceptedPartyInvite: (instanceName, charId, event) => {
const instance = initializeInstance(instanceName, charId);
instance.playersSeekingParty.delete(event.inviteeId);
},
updatedParty: (instanceName, charId, event) => {
updateUnit(instanceName, charId, charId, (unit) => {
if (!unit) return;
unit.party = event.party;
});
},
updatedRole: (instanceName, charId, event) => {
updateUnit(instanceName, charId, event.unitId, (unit) => {
if (isPlayerUnit(unit)) {
unit.role = event.role;
}
});
},
setIntent: (instanceName, charId, event) => {
updateUnit(instanceName, charId, event.unitId, (innerUnit) => {
if (innerUnit) {
innerUnit.intent = event.intent;
}
});
},
attacked: (instanceName, charId, event) => {
updateUnit(instanceName, charId, event.attacked, (attackedUnit) => {
if (!attackedUnit) return;
attackedUnit.hp = event.hp;
});
updateUnit(instanceName, charId, event.attacker, (attackerUnit) => {
if (!attackerUnit) return;
attackerUnit.tp = event.attackerTp;
});
},
despawn: (instanceName, charId, event) => {
const instance = initializeInstance(instanceName, charId);
delete instance.characters[charId].units[event.unitId];
},
hp: (instanceName, charId, event) => {
updateUnit(instanceName, charId, event.unitId, (unit) => {
if (!unit) return;
unit.hp = event.hp;
});
},
tp: (instanceName, charId, event) => {
updateUnit(instanceName, charId, event.unitId, (unit) => {
if (!unit) return;
unit.tp = event.tp;
});
},
calories: (instanceName, charId, event) => {
updateUnit(instanceName, charId, event.unitId, (unit) => {
if (unit && "calories" in unit) {
unit.calories = event.calories;
}
});
},
died: (instanceName, charId, event) => {
updateUnit(instanceName, charId, event.unitId, (unit) => {
if (!unit) return;
unit.hp = 0;
});
},
invited: (instanceName, charId, event) => {
const instance = initializeInstance(instanceName, charId);
const player = instance.characters[charId].units[charId];
if (player) {
player.partyInvites.push({
id: event.inviter.id,
name: event.inviter.name,
role: event.inviter.role,
});
}
},
};
let time = 0;
let lastOnTick = Date.now();
const runOnTick = () => {
const elapsed = Date.now() - lastOnTick;
lastOnTick = Date.now();
Object.entries(innerState.instances).forEach(([instanceId, instance]) => {
if (innerState.arenaDurations[instanceId]) {
innerState.arenaDurations[instanceId] -= elapsed;
}
Object.keys(instance.characters).map((charId) => {
const charState = instance.characters[charId];
Object.values(charState.units).forEach((unit) => {
Object.values(unit.statusEffects).forEach((effect) => {
effect.duration -= elapsed;
});
});
time++;
if (instanceId === "overworld") {
const char = charState.units[charId] as ClientSidePlayer;
if (!char) return;
const intent = onTick({
inArena: false,
player: {
...char,
...handlers,
},
playersSeekingParty: Array.from(
instance.playersSeekingParty.values()
),
time,
units: charState.units,
});
if (intent) {
lastIntent = {
c: charId,
i: instanceId as InstanceName,
intent: intent,
unitId: charId,
};
socket.emit("setIntent", lastIntent);
} else {
lastIntent = undefined;
}
);
if (targetParty) {
Object.keys(targetParty.members).forEach((memberId) => {
party[memberId] = heartBeat.arena.units[memberId];
} else {
const char = charState.units[charId] as ClientSidePlayer;
const intent = onTick({
inArena: true,
arenaTimeRemaining: innerState.arenaDurations[instanceId],
player: char
? {
...char,
...handlers,
}
: undefined,
playersSeekingParty: Array.from(
instance.playersSeekingParty.values()
),
time,
units: charState.units,
});
if (intent) {
lastIntent = {
c: charId,
i: instanceId as InstanceName,
intent: intent,
unitId: charId,
};
socket.emit("setIntent", lastIntent);
} else {
lastIntent = undefined;
}
}
arenaHeartBeat.player = {
...arenaPlayer,
party,
partyInvites: [],
...handlers,
};
}
return {
overworldIntent: onTick(normalPlayerTickHeartbeat),
arenaIntent: onTick(arenaHeartBeat),
};
// need to emit for arena as well
});
});
signalIntents(intents);
};
console.log('registering "message" handler');
socket.on("message", messageHandler);
const heartBeatInterval = setInterval(runOnTick, fps(60));
socket.on("events", (events): void => {
Object.keys(events).forEach((instanceId) => {
const eventCharMap = events[instanceId];
Object.entries(eventCharMap).forEach(([charId, events]) => {
events.forEach(([eventName, eventPayload]) => {
// console.log("eee...");
initializeInstance(instanceId, charId);
// if (eventPayload.instance !== "overworld") {
// console.log(
// "got ",
// eventPayload.instance,
// innerState.instances[eventPayload.instance]
// );
// }
const instance = innerState.instances[instanceId];
const handler = socketEventHandlers[eventName];
const isConnect = eventName === "connectionEvent";
// const hasChar = instance.characters[eventPayload.character];
// if (!isConnect && !hasChar) {
// return;
// }
if (handler) {
// @ts-expect-error
handler(instanceId, charId, eventPayload);
}
});
});
});
runOnTick();
});
return () => {
socket.off("message", messageHandler);
clearInterval(heartBeatInterval);
socket.disconnect();
socket.close();
innerState = {
instances: {},
arenaDurations: {},
};
};
// return entries(socketEventHandlers).reduce<() => void>(
// (acc, [key, handler]) => {
// socket.on(key, handler);
// runOnTick();
// return () => {
// socket.off(key, handler);
// acc();
// };
// },
// () => {
// }
// );
};
// class Client {
// private socket: Socket;
// constructor(credentials: Credential[]) {
// this.socket = io("");
// this.socket.on("connect", () => {
// console.log("connected");
// });
// }
// private signalIntent = (intent: Intent) => {
// this.socket.emit("message", intent);
// };
// public onTick: OnTick = (callback) => {
// const messageHandler = async (m: any) => {
// const intent = await callback(JSON.parse(m));
// if (intent) this.signalIntent(intent);
// };
// this.socket.on("message", messageHandler);
// return () => {
// this.socket.off("message", messageHandler);
// };
// };
// public setRole = (role: ROLES) => {
// this.signalIntent({ type: IntentType.setRole, role });
// };
// public attack = (target: string) => {
// this.signalIntent({ type: IntentType.attack, target });
// };
// public move = (position: { x: number; y: number }) => {
// this.signalIntent({ type: IntentType.move, position });
// };
// public respawn = () => {
// this.signalIntent({ type: IntentType.respawn });
// };
// public summonMana = () => {
// this.signalIntent({ type: IntentType.summonMana });
// };
// public eat = (item: Items, save: number) => {
// this.signalIntent({ type: IntentType.eat, item: item, save });
// };
// public cast = (spell: Spells, target?: string) => {
// this.signalIntent({ type: IntentType.cast, spell, target });
// };
// public sell = (item: Items, until: number, to: string) => {
// this.signalIntent({ type: IntentType.sell, item, until, to });
// };
// public buy = (item: Items, until: number, from: NPC_IDS) => {
// this.signalIntent({ type: IntentType.buy, item, until, from });
// };
// public use = (item: Items, until: number) => {
// this.signalIntent({ type: IntentType.use, item, until });
// };
// public seekParty = () => {
// this.signalIntent({ type: IntentType.seekParty });
// };
// public inviteToParty = (playerId: string) => {
// this.signalIntent({ type: IntentType.inviteToParty, playerId });
// };
// public leaveParty = () => {
// this.signalIntent({ type: IntentType.leaveParty });
// };
// public acceptPartyInvite = (playerId: string) => {
// this.signalIntent({ type: IntentType.acceptPartyInvite, playerId });
// };
// public equip = (item: Items, slot: keyof PlayerEquipment) => {
// this.signalIntent({ type: IntentType.equip, item, slot });
// };
// public craft = (item: Items) => {
// this.signalIntent({ type: IntentType.craft, item });
// };
// public useWeaponSkill = (skill: WeaponSkill, target: string) => {
// this.signalIntent({ type: IntentType.weaponSkill, skill, target });
// };
// public disconnect = () => {
// this.socket.disconnect();
// this.socket.close();
// };
// }

@@ -285,345 +285,2 @@ import { UniqueItemId, UnitStats } from "./types";

};
type ItemCountMap = Partial<Record<Items, number>>;
type Recipe = {
duration: number;
input: ItemCountMap | ItemCountMap[];
output: ItemCountMap;
required: (Items | Spells)[][];
};
type Recipes = Partial<Record<Items, Recipe>>;
export const recipes: Recipes = {
woodenArrow: {
duration: 1000,
input: {
feather: 1,
arrowShaft: 1,
},
output: {
woodenArrow: 1,
},
required: [],
},
arrowShaft: {
duration: 1000,
input: {
bitOfWood: 1,
},
output: {
arrowShaft: 1,
},
required: [],
},
bitOfWood: {
duration: 1000,
input: {
woodLog: 1,
},
output: {
bitOfWood: 10,
},
required: [],
},
copperIngot: {
duration: 30000,
input: {
chunkOfCopper: 3,
},
output: {
copperIngot: 1,
},
required: [["furnace"]],
},
copperDagger: {
duration: 30000,
input: {
copperIngot: 3,
woodPommel: 1,
},
output: {
copperDagger: 1,
},
required: [["anvil"]],
},
woodPommel: {
duration: 30000,
input: {
bitOfWood: 3,
},
output: {
woodPommel: 1,
},
required: [],
},
leatherStrips: {
duration: 30000,
input: {
lightLeather: 1,
},
output: {
leatherStrips: 3,
},
required: [],
},
lightLeather: {
duration: 30000,
input: {
ratPelt: 3,
},
output: {
lightLeather: 1,
},
required: [],
},
snakeSkinLeather: {
duration: 30000,
input: {
snakeSkin: 3,
},
output: {
snakeSkinLeather: 1,
},
required: [],
},
// cloth ingredients
linenCloth: {
duration: 30_000,
input: {
tatteredLinenCloth: 3,
},
output: {
linenCloth: 1,
},
required: [],
},
woolCloth: {
duration: 30_000,
input: {
tatteredWoolCloth: 5,
},
output: {
woolCloth: 1,
},
required: [],
},
// cloth armor
clothHelm: {
duration: 30_000,
input: {
linenCloth: 3,
coarseThread: 10,
},
output: {
clothHelm: 1,
},
required: [["copperNeedle"]],
},
clothChest: {
duration: 30_000,
input: {
linenCloth: 5,
coarseThread: 10,
},
output: {
clothChest: 1,
},
required: [["copperNeedle"]],
},
clothGloves: {
duration: 30_000,
input: {
linenCloth: 2,
coarseThread: 10,
},
output: {
clothGloves: 1,
},
required: [["copperNeedle"]],
},
clothLegs: {
duration: 30_000,
input: {
linenCloth: 4,
coarseThread: 10,
},
output: {
clothLegs: 1,
},
required: [["copperNeedle"]],
},
clothBoots: {
duration: 30_000,
input: {
linenCloth: 2,
coarseThread: 10,
},
output: {
clothBoots: 1,
},
required: [["copperNeedle"]],
},
// leather armor
lightLeatherHelm: {
duration: 30_000,
input: {
lightLeather: 3,
leatherStrips: 10,
},
output: {
lightLeatherHelm: 1,
},
required: [["copperNeedle"]],
},
lightLeatherChest: {
duration: 30_000,
input: {
lightLeather: 5,
leatherStrips: 10,
},
output: {
lightLeatherChest: 1,
},
required: [["copperNeedle"]],
},
lightLeatherGloves: {
duration: 30_000,
input: {
lightLeather: 2,
leatherStrips: 10,
},
output: {
lightLeatherGloves: 1,
},
required: [["copperNeedle"]],
},
lightLeatherLegs: {
duration: 30_000,
input: {
lightLeather: 4,
leatherStrips: 10,
},
output: {
lightLeatherLegs: 1,
},
required: [["copperNeedle"]],
},
lightLeatherBoots: {
duration: 30_000,
input: {
lightLeather: 2,
leatherStrips: 10,
},
output: {
lightLeatherBoots: 1,
},
required: [["copperNeedle"]],
},
// snake skin armor
snakeSkinHelm: {
duration: 30_000,
input: {
snakeSkinLeather: 3,
leatherStrips: 10,
},
output: {
snakeSkinHelm: 1,
},
required: [["copperNeedle"]],
},
snakeSkinChest: {
duration: 30_000,
input: {
snakeSkinLeather: 5,
leatherStrips: 10,
},
output: {
snakeSkinChest: 1,
},
required: [["copperNeedle"]],
},
snakeSkinGloves: {
duration: 30_000,
input: {
snakeSkinLeather: 2,
leatherStrips: 10,
},
output: {
snakeSkinGloves: 1,
},
required: [["copperNeedle"]],
},
snakeSkinLegs: {
duration: 30_000,
input: {
snakeSkinLeather: 4,
leatherStrips: 10,
},
output: {
snakeSkinLegs: 1,
},
required: [["copperNeedle"]],
},
snakeSkinBoots: {
duration: 30_000,
input: {
snakeSkinLeather: 2,
leatherStrips: 10,
},
output: {
snakeSkinBoots: 1,
},
required: [["copperNeedle"]],
},
// copper mail armor
copperMailHelm: {
duration: 30_000,
input: {
copperIngot: 3,
},
output: {
copperMailHelm: 1,
},
required: [["anvil"]],
},
copperMailChest: {
duration: 30_000,
input: {
copperIngot: 5,
},
output: {
copperMailChest: 1,
},
required: [["anvil"]],
},
copperMailGloves: {
duration: 30_000,
input: {
copperIngot: 2,
},
output: {
copperMailGloves: 1,
},
required: [["anvil"]],
},
copperMailLegs: {
duration: 30_000,
input: {
copperIngot: 4,
},
output: {
copperMailLegs: 1,
},
required: [["anvil"]],
},
copperMailBoots: {
duration: 30_000,
input: {
copperIngot: 2,
},
output: {
copperMailBoots: 1,
},
required: [["anvil"]],
},
};
const createCraftingIngredient = (

@@ -630,0 +287,0 @@ id: Trash,

@@ -5,3 +5,5 @@ import {

Chests,
Equipments,
Gloves,
Helms,
Items,

@@ -14,2 +16,3 @@ Legs,

} from "./items";
import { recipes } from "./recipes";
import { CastIntent, Spells } from "./spells";

@@ -41,8 +44,2 @@ import { WeaponSkill } from "./weapon-skills";

}
export type CurrentPlayer = Omit<Player, "partyInvites" | "party"> & {
partyInvites: Pick<Player, "name" | "role" | "id">[];
partyId?: string;
party: Party;
units: Record<string, Unit>;
};
export type Parties = Record<

@@ -56,14 +53,183 @@ string,

>;
export type HeartbeatFromServer = {
players: Record<string, CurrentPlayer>;
time: number;
playersSeekingParty: Pick<Player, "name" | "role" | "id">[];
export type PlayersSeekingParty = Pick<Player, "name" | "role" | "id">[];
export type ClientSidePartyInvites = Pick<Player, "name" | "role" | "id">[];
export type InstanceName = "overworld" | "1v1Arena";
export type ClientSidePlayer = Omit<Player, "partyInvites" | "party"> & {
partyInvites: ClientSidePartyInvites;
party: Record<string, true>;
};
export type ClientSideUnit = Omit<Unit, "partyInvites" | "party"> & {
partyInvites: ClientSidePartyInvites;
party: Record<string, true>;
};
export type ClientSideNPC = Omit<NPC, "partyInvites" | "party"> & {
partyInvites: ClientSidePartyInvites;
party: Record<string, true>;
};
export type RawEvents = {
// use "& BaseEventMap" to validate the structure of the events
acceptedPartyInvite: {
inviteeId: string;
inviterId: string;
};
connectionEvent: {
player: Player;
party: Record<string, true>;
units: Record<string, ClientSideUnit>;
playersSeekingParty: PlayersSeekingParty;
time: number;
partyInvites: ClientSidePartyInvites;
};
updatedRole: {
unitId: string;
role: ROLES;
};
updatedParty: {
party: Record<string, true>;
};
invited: {
inviter: {
id: string;
role: ROLES;
name: string;
};
invitee: string;
};
seekParty: {
playersSeeking: PlayersSeekingParty[0];
};
unitAppeared: {
unit: ClientSideUnit;
};
unitDisappeared: {
unitId: string;
};
moved: {
id: string;
} & Position;
attacked: {
attacker: string;
attacked: string;
damage: number;
// attacked hp
hp: number;
attackerTp: number;
};
loot: {
unitId: string;
items: Partial<Record<Items, number>>;
};
beganCasting: {
unitId: string;
spell: Spells;
target?: string;
};
castSpell: {
unitId: string;
spell: Spells;
};
gainedStatus: {
unitId: string;
effect: StatusEffect;
duration: number;
source: string;
stacks: number;
shield: number;
};
arena: {
timeRemaining: number;
units: Record<string, Unit>;
players: Record<string, Player>;
parties: Parties;
duration: number;
};
lostStatus: {
unitId: string;
effect: StatusEffect;
};
hp: {
unitId: string;
hp: number;
};
mp: {
unitId: string;
mp: number;
};
tp: {
unitId: string;
tp: number;
};
calories: {
unitId: string;
calories: number;
};
ate: {
unitId: string;
item: Items;
calories: number;
remaining: number;
};
equipped: {
unitId: string;
item: Equipments;
slot: keyof PlayerEquipment;
};
usedWeaponSkill: {
unitId: string;
targetId: string;
skill: WeaponSkill;
tp: number;
};
unequipped: {
unitId: string;
item: Equipments;
slot: keyof PlayerEquipment;
};
beganCrafting: {
unitId: string;
item: keyof typeof recipes;
};
finishedCrafting: {
unitId: string;
item: keyof typeof recipes;
spent: Partial<Record<Items, number>>;
};
setIntent: {
unitId: string;
intent: Intent;
};
despawn: {
unitId: string;
};
died: {
unitId: string;
};
traded: {
actingUnitId: string;
targetUnitId: string;
gave: Partial<Record<Items, number>>;
got: Partial<Record<Items, number>>;
};
stats: {
unitId: string;
stats: UnitStats;
};
};
type CreateEventsEvents = <T extends keyof RawEvents>() => EventGroup<T>;
export type EventGroup<T extends keyof RawEvents> = [T, RawEvents[T]][];
export type BatchedEvents = {
[instance: string]: {
[character: string]: ReturnType<CreateEventsEvents>;
};
};
export type EventObjMap<T extends {}> = {
c: string;
i: InstanceName;
} & T;
type CreateIntentMap = <T extends keyof RawEvents, K>(
obj: K
) => {
[key in T]: (instance: string, charId: string, event: RawEvents[key]) => void;
};
export type EventMap = ReturnType<CreateIntentMap>;
/**

@@ -214,2 +380,3 @@ * Spell Groups?

export type BaseUnit = {
unitsInSight: Set<string>;
id: string;

@@ -294,3 +461,3 @@ hp: number;

threat: Record<string, true>;
crafting?: Items;
crafting?: keyof typeof recipes;
craftStart?: number;

@@ -301,14 +468,14 @@ inventory: Inventory; // item id: quantity

export type PlayerEquipment = {
helm: Items | null;
chest: Chests | null;
legs: Legs | null;
feet: Boots | null;
hands: Gloves | null;
weapon: Weapons | null;
offhand: OffhandWeapons | OneHandedWeapons | null;
amulet: Amulets | null;
ring1: Rings | null;
ring2: Rings | null;
earring1: Earrings | null;
earring2: Earrings | null;
helm: Helms | UniqueItemId | null;
chest: Chests | UniqueItemId | null;
legs: Legs | UniqueItemId | null;
feet: Boots | UniqueItemId | null;
hands: Gloves | UniqueItemId | null;
weapon: Weapons | UniqueItemId | null;
offhand: OffhandWeapons | OneHandedWeapons | UniqueItemId | null;
amulet: Amulets | UniqueItemId | null;
ring1: Rings | UniqueItemId | null;
ring2: Rings | UniqueItemId | null;
earring1: Earrings | UniqueItemId | null;
earring2: Earrings | UniqueItemId | null;
};

@@ -371,7 +538,8 @@

type: IntentType.craft;
item: Items;
item: keyof typeof recipes;
from: Partial<Inventory>;
}
export interface EquipIntent extends BaseIntent {
type: IntentType.equip;
item: Items;
item: Equipments;
slot: keyof PlayerEquipment;

@@ -378,0 +546,0 @@ }

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc