Basic avatar system (#9)

This commit is contained in:
memetrollsXD 2022-08-02 02:08:12 +02:00 committed by GitHub
parent 87d98b041f
commit 01e60ab390
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 252 additions and 52 deletions

32
src/commands/avatar.ts Normal file
View File

@ -0,0 +1,32 @@
import Avatar from "../db/Avatar";
import Logger from "../util/Logger";
import Interface, { Command } from "./Interface";
const c = new Logger("/avatar", "blue");
export default async function handle(command: Command) {
if (!Interface.target) {
c.log("No target specified");
return;
}
const actionType = command.args[0];
const avatarId = Number(command.args[1]);
const uid = Interface.target.player.db._id;
switch (actionType) {
default:
c.log(`Usage: /avatar <add|remove> <avatarId>`);
break;
case "add":
if (!avatarId) return c.log("No avatarId specified");
// Check if it already exists
const avatar = await Avatar.fromUID(uid, avatarId);
if (avatar) return c.log(`Avatar ${avatarId} already exists`);
Avatar.create(uid, avatarId).then(a => c.log(`Avatar ${avatarId} added to ${a.ownerUid}`));
break;
case "remove":
if (!avatarId) return c.log("No avatarId specified");
Avatar.remove(uid, avatarId).then(() => c.log(`Avatar ${avatarId} removed from ${uid}`));
break;
}
}

42
src/db/Avatar.ts Normal file
View File

@ -0,0 +1,42 @@
import { Avatar as AvatarI } from '../data/proto/StarRail';
import Database from './Database';
type UID = number | string;
export default class Avatar {
private constructor(public ownerUid: UID, public data: AvatarI) {
}
public static async create(uid: UID, baseAvatarId: number = 1001): Promise<Avatar> {
const db = Database.getInstance();
// Check if already exists
const existing = await Avatar.fromUID(uid, baseAvatarId);
if (existing.length > 0) return existing[0];
const avatar = new Avatar(uid, {
baseAvatarId,
equipmentUniqueId: 20003, // TODO: Placeholder while we work on inventory system
equipRelicList: [],
exp: 0,
level: 1,
promotion: 1,
rank: 1,
skilltreeList: [],
});
db.set("avatars", avatar);
return avatar;
}
public static async fromUID(ownerUid: UID, baseAvatarId?: number): Promise<Avatar[]> {
const query = { ownerUid } as { ownerUid: UID, baseAvatarId?: number };
if (baseAvatarId) query.baseAvatarId = baseAvatarId;
const db = Database.getInstance();
return await db.getAll("avatars", query) as unknown as Avatar[];
}
public static async remove(ownerUid: UID, baseAvatarId: number): Promise<void> {
const db = Database.getInstance();
await db.delete("avatars", { ownerUid, baseAvatarId });
}
}

View File

@ -38,6 +38,22 @@ export default class Database {
} }
} }
public async getAll(collection: string, query?: object) {
try {
const db = await Database.client.db();
const _collection = db.collection(collection);
if (!(await db.listCollections({ name: collection }).toArray()).length) {
c.warn(`Collection ${collection} does not exist. Creating...`);
await _collection.createIndexes([{ key: { id: 1 }, unique: true }]);
}
const result = query ? await _collection.find(query).toArray() : await _collection.find().toArray();
return result;
} catch (e) {
c.error(e as Error);
return null;
}
}
public async set(collection: string, payload: any) { public async set(collection: string, payload: any) {
try { try {
const db = await Database.client.db(); const db = await Database.client.db();

View File

@ -1,3 +1,4 @@
import { LineupInfo, Vector } from "../data/proto/StarRail";
import Logger from "../util/Logger"; import Logger from "../util/Logger";
import Account from "./Account"; import Account from "./Account";
import Database from "./Database"; import Database from "./Database";
@ -18,6 +19,15 @@ interface PlayerI {
scoin: number; scoin: number;
worldLevel: number; worldLevel: number;
} }
lineup: {
curIndex: number;
lineups: LineupInfo[];
}
posData: {
floorID: number;
planeID: number;
pos?: Vector;
}
} }
export default class Player { export default class Player {

View File

@ -45,8 +45,6 @@ export default class Session {
recv = this.kcpobj.recv(); recv = this.kcpobj.recv();
if (!recv) break; if (!recv) break;
this.c.debug(`recv ${recv.toString("hex")}`);
if (Packet.isValid(recv)) { if (Packet.isValid(recv)) {
this.handlePacket(new Packet(recv)); this.handlePacket(new Packet(recv));
} }
@ -58,6 +56,7 @@ export default class Session {
public async handlePacket(packet: Packet) { public async handlePacket(packet: Packet) {
if (Logger.VERBOSE_LEVEL >= VerboseLevel.WARNS) this.c.log(packet.protoName) if (Logger.VERBOSE_LEVEL >= VerboseLevel.WARNS) this.c.log(packet.protoName)
this.c.debug(packet.body);
import(`../packets/${packet.protoName}`).then(mod => { import(`../packets/${packet.protoName}`).then(mod => {
mod.default(this, packet); mod.default(this, packet);
@ -70,10 +69,10 @@ export default class Session {
} }
public send(name: PacketName, body: {}) { public send(name: PacketName, body: {}) {
this.c.debug(body);
const packet = Packet.encode(name, body); const packet = Packet.encode(name, body);
if (!packet) return; if (!packet) return;
if (Logger.VERBOSE_LEVEL >= VerboseLevel.WARNS) this.c.log(packet.protoName); if (Logger.VERBOSE_LEVEL >= VerboseLevel.WARNS) this.c.log(packet.protoName);
this.c.debug(`send ${packet.rawData.toString('hex')}`);
this.kcpobj.send(packet.rawData); this.kcpobj.send(packet.rawData);
} }

View File

@ -3,8 +3,10 @@ import Packet from "../kcp/Packet";
import Session from "../kcp/Session"; import Session from "../kcp/Session";
export default async function handle(session: Session, packet: Packet) { export default async function handle(session: Session, packet: Packet) {
const lineup = session.player.db.lineup;
session.send("GetAllLineupDataScRsp", { session.send("GetAllLineupDataScRsp", {
retcode: 0, retcode: 0,
lineupList: [] curIndex: lineup.curIndex,
} as unknown as GetAllLineupDataScRsp); lineupList: lineup.lineups,
} as GetAllLineupDataScRsp);
} }

View File

@ -2,22 +2,16 @@ import { GetAvatarDataCsReq, GetAvatarDataScRsp } from "../../data/proto/StarRai
import AvatarExcelTable from "../../data/excel/AvatarExcelTable.json"; import AvatarExcelTable from "../../data/excel/AvatarExcelTable.json";
import Packet from "../kcp/Packet"; import Packet from "../kcp/Packet";
import Session from "../kcp/Session"; import Session from "../kcp/Session";
import Avatar from "../../db/Avatar";
export default async function handle(session: Session, packet: Packet) { export default async function handle(session: Session, packet: Packet) {
const body = packet.body as GetAvatarDataCsReq; const body = packet.body as GetAvatarDataCsReq;
const avatar = await Avatar.fromUID(session.player.db._id);
const dataObj = { const dataObj = {
retcode: 0, retcode: 0,
avatarList: [{ avatarList: avatar.map(av => av.data),
baseAvatarId: 1001,
equipmentUniqueId: 13501,
equipRelicList: [],
exp: 0,
level: 1,
promotion: 1,
rank: 1,
skilltreeList: [],
}],
isAll: body.isGetAll isAll: body.isGetAll
} as GetAvatarDataScRsp; } as GetAvatarDataScRsp;

View File

@ -1,24 +1,36 @@
import { AvatarType, BattleEndStatus, GetCurBattleInfoScRsp } from "../../data/proto/StarRail"; import { AvatarType, BattleAvatar, BattleEndStatus, GetCurBattleInfoScRsp } from "../../data/proto/StarRail";
import Packet from "../kcp/Packet"; import Packet from "../kcp/Packet";
import Session from "../kcp/Session"; import Session from "../kcp/Session";
export default async function handle(session: Session, packet: Packet) { export default async function handle(session: Session, packet: Packet) {
const _lineup = session.player.db.lineup;
const lineup = _lineup.lineups[_lineup.curIndex];
session.send("GetCurBattleInfoScRsp", { session.send("GetCurBattleInfoScRsp", {
retcode: 0, retcode: 0,
avatarList: [{ avatarList: lineup.avatarList.map(list => {
avatarType: AvatarType.AVATAR_FORMAL_TYPE, return {
id: 1001, avatarType: list.avatarType,
equipmentList: [{
id: 20003,
level: 1, level: 1,
rank: 1,
index: 1,
hp: 100,
sp: 100,
promotion: 1, promotion: 1,
rank: 1
}], }],
stageId: 10000, hp: list.hp,
id: list.id,
index: list.slot,
level: 1,
promotion: 1,
rank: 1,
relicList: [],
skilltreeList: []
} as unknown as BattleAvatar;
}),
stageId: 1,
logicRandomSeed: 2503, logicRandomSeed: 2503,
battleInfo: {}, battleInfo: {},
lastEndStatus: BattleEndStatus.BATTLE_END_WIN, lastEndStatus: BattleEndStatus.BATTLE_END_WIN,
lastEventId: 0 lastEventId: 1
} as GetCurBattleInfoScRsp); } as GetCurBattleInfoScRsp);
} }

View File

@ -3,23 +3,10 @@ import Packet from "../kcp/Packet";
import Session from "../kcp/Session"; import Session from "../kcp/Session";
export default async function handle(session: Session, packet: Packet) { export default async function handle(session: Session, packet: Packet) {
const _lineup = session.player.db.lineup;
const lineup = _lineup.lineups[_lineup.curIndex];
session.send("GetCurLineupDataScRsp", { session.send("GetCurLineupDataScRsp", {
retcode: 0, retcode: 0,
lineup: { lineup
avatarList: [{
slot: 1,
avatarType: AvatarType.AVATAR_FORMAL_TYPE,
id: 1001,
hp: 100,
sp: 100,
satiety: 100
}],
index: 1,
isVirtual: false,
mp: 100,
name: "lineuprspname",
planeId: 10000,
leaderSlot: 1
}
} as GetCurLineupDataScRsp); } as GetCurLineupDataScRsp);
} }

View File

@ -3,11 +3,12 @@ import Packet from "../kcp/Packet";
import Session from "../kcp/Session"; import Session from "../kcp/Session";
export default async function handle(session: Session, packet: Packet) { export default async function handle(session: Session, packet: Packet) {
const posData = session.player.db.posData;
session.send("GetCurSceneInfoScRsp", { session.send("GetCurSceneInfoScRsp", {
retcode: 0, retcode: 0,
scene: { scene: {
planeId: 10000, planeId: posData.planeID,
floorId: 10000000, floorId: posData.floorID,
entityList: [], entityList: [],
entityBuffList: [], entityBuffList: [],
entryId: 10001, entryId: 10001,

View File

@ -1,4 +1,5 @@
import { PlayerBasicInfo, PlayerLoginCsReq, PlayerLoginScRsp } from "../../data/proto/StarRail"; import { AvatarType, ExtraLineupType, PlayerBasicInfo, PlayerLoginCsReq, PlayerLoginScRsp } from "../../data/proto/StarRail";
import Avatar from "../../db/Avatar";
import Player from "../../db/Player"; import Player from "../../db/Player";
import Packet from "../kcp/Packet"; import Packet from "../kcp/Packet";
import Session from "../kcp/Session"; import Session from "../kcp/Session";
@ -21,9 +22,11 @@ import Session from "../kcp/Session";
export default async function handle(session: Session, packet: Packet) { export default async function handle(session: Session, packet: Packet) {
const body = packet.body as PlayerLoginCsReq; const body = packet.body as PlayerLoginCsReq;
const plr = await Player.fromUID(session.player.db._id)!; const plr = await Player.fromUID(session.player.db._id);
if (!plr!.db.basicInfo) { if (!plr) return;
plr!.db.basicInfo = {
if (!plr.db.basicInfo) {
plr.db.basicInfo = {
exp: 0, exp: 0,
level: 1, level: 1,
hcoin: 0, hcoin: 0,
@ -33,7 +36,40 @@ export default async function handle(session: Session, packet: Packet) {
stamina: 100, stamina: 100,
worldLevel: 1, worldLevel: 1,
} }
plr!.save(); plr.save();
}
if (!plr.db.lineup) {
Avatar.create(plr.db._id);
plr.db.lineup = {
curIndex: 0,
lineups: [{
avatarList: [{
avatarType: AvatarType.AVATAR_FORMAL_TYPE,
hp: 10000,
sp: 10000,
satiety: 100,
slot: 0,
id: 1001
}],
planeId: 10001,
isVirtual: false,
name: "Default Party",
index: 0,
leaderSlot: 0,
mp: 100,
extraLineupType: ExtraLineupType.LINEUP_NONE
}]
}
plr.save();
}
if (!plr.db.posData) {
plr.db.posData = {
floorID: 10001001,
planeID: 10001
}
plr.save();
} }
session.send("PlayerLoginScRsp", { session.send("PlayerLoginScRsp", {

View File

@ -56,7 +56,7 @@ export default class ProtoFactory {
} }
} }
c.debug(`Initialized with " ${messageTypeMap.size} types`); c.debug(`Initialized with ${messageTypeMap.size} types`);
//c.log(this.getName(types.PlayerLoginScRsp)) //c.log(this.getName(types.PlayerLoginScRsp))
return; return;

View File

@ -0,0 +1,69 @@
import _AvatarExcelTable from "../../data/excel/AvatarExcelTable.json";
const AvatarExcelTable = _AvatarExcelTable as { [key: string]: AvatarExcelTableEntry };
export default class AvatarExcel {
private constructor() { }
public static fromId(id: number): AvatarExcelTableEntry {
return AvatarExcelTable[id];
}
public static fromIds(ids: number[]): AvatarExcelTableEntry[] {
return ids.map(id => AvatarExcel.fromId(id));
}
}
interface Reward {
ItemID: number,
ItemNum: number,
}
interface TextMap {
hash: number;
}
export interface AvatarExcelTableEntry {
AvatarID: number;
AvatarName: TextMap;
AvatarFullName: TextMap;
AdventurePlayerID: number;
AvatarVOTag: string;
Rarity: string;
JsonPath: string;
NatureID: number;
DamageType: string;
SPNeed: {
RawValue: number;
};
ExpGroup: number;
MaxPromotion: number;
MaxRank: number;
RankIDList: number[];
RewardList: Reward[];
RewardListMax: Reward[];
SkillList: number[];
AvatarBaseType: string;
DefaultAvatarModelPath: string;
DefaultAvatarHeadIconPath: string;
AvatarSideIconPath: string;
ActionAvatarHeadIconPath: string;
AvatarBaseTypeIconPath: string;
AvatarDialogHalfImagePath: string;
UltraSkillCutInPrefabPath: string;
UIAvatarModelPath: string;
ManikinJsonPath: string;
AvatarDesc: TextMap;
AIPath: string;
SkilltreePrefabPath: string;
DamageTypeResistance: never[];
Release: boolean;
SideAvatarHeadIconPath: string;
WaitingAvatarHeadIconPath: string;
AvatarCutinImgPath: string;
AvatarCutinBgImgPath: string;
AvatarCutinFrontImgPath: string;
AvatarCutinIntroText: TextMap;
GachaResultOffset: number[];
AvatarDropOffset: number[];
AvatarTrialOffset: number[];
}