programming-game
Advanced tools
Comparing version 0.0.1 to 0.0.2
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 |
669
src/index.ts
@@ -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(); | ||
// }; | ||
// } |
343
src/items.ts
@@ -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, |
228
src/types.ts
@@ -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
186633
26
6006