Basic avatar system (#9)
This commit is contained in:
parent
87d98b041f
commit
01e60ab390
32
src/commands/avatar.ts
Normal file
32
src/commands/avatar.ts
Normal 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
42
src/db/Avatar.ts
Normal 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 });
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
try {
|
||||
const db = await Database.client.db();
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { LineupInfo, Vector } from "../data/proto/StarRail";
|
||||
import Logger from "../util/Logger";
|
||||
import Account from "./Account";
|
||||
import Database from "./Database";
|
||||
@ -18,6 +19,15 @@ interface PlayerI {
|
||||
scoin: number;
|
||||
worldLevel: number;
|
||||
}
|
||||
lineup: {
|
||||
curIndex: number;
|
||||
lineups: LineupInfo[];
|
||||
}
|
||||
posData: {
|
||||
floorID: number;
|
||||
planeID: number;
|
||||
pos?: Vector;
|
||||
}
|
||||
}
|
||||
|
||||
export default class Player {
|
||||
|
@ -45,8 +45,6 @@ export default class Session {
|
||||
recv = this.kcpobj.recv();
|
||||
if (!recv) break;
|
||||
|
||||
this.c.debug(`recv ${recv.toString("hex")}`);
|
||||
|
||||
if (Packet.isValid(recv)) {
|
||||
this.handlePacket(new Packet(recv));
|
||||
}
|
||||
@ -58,6 +56,7 @@ export default class Session {
|
||||
|
||||
public async handlePacket(packet: Packet) {
|
||||
if (Logger.VERBOSE_LEVEL >= VerboseLevel.WARNS) this.c.log(packet.protoName)
|
||||
this.c.debug(packet.body);
|
||||
|
||||
import(`../packets/${packet.protoName}`).then(mod => {
|
||||
mod.default(this, packet);
|
||||
@ -70,10 +69,10 @@ export default class Session {
|
||||
}
|
||||
|
||||
public send(name: PacketName, body: {}) {
|
||||
this.c.debug(body);
|
||||
const packet = Packet.encode(name, body);
|
||||
if (!packet) return;
|
||||
if (Logger.VERBOSE_LEVEL >= VerboseLevel.WARNS) this.c.log(packet.protoName);
|
||||
this.c.debug(`send ${packet.rawData.toString('hex')}`);
|
||||
this.kcpobj.send(packet.rawData);
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,10 @@ import Packet from "../kcp/Packet";
|
||||
import Session from "../kcp/Session";
|
||||
|
||||
export default async function handle(session: Session, packet: Packet) {
|
||||
const lineup = session.player.db.lineup;
|
||||
session.send("GetAllLineupDataScRsp", {
|
||||
retcode: 0,
|
||||
lineupList: []
|
||||
} as unknown as GetAllLineupDataScRsp);
|
||||
curIndex: lineup.curIndex,
|
||||
lineupList: lineup.lineups,
|
||||
} as GetAllLineupDataScRsp);
|
||||
}
|
@ -2,22 +2,16 @@ import { GetAvatarDataCsReq, GetAvatarDataScRsp } from "../../data/proto/StarRai
|
||||
import AvatarExcelTable from "../../data/excel/AvatarExcelTable.json";
|
||||
import Packet from "../kcp/Packet";
|
||||
import Session from "../kcp/Session";
|
||||
import Avatar from "../../db/Avatar";
|
||||
|
||||
export default async function handle(session: Session, packet: Packet) {
|
||||
const body = packet.body as GetAvatarDataCsReq;
|
||||
|
||||
const avatar = await Avatar.fromUID(session.player.db._id);
|
||||
|
||||
const dataObj = {
|
||||
retcode: 0,
|
||||
avatarList: [{
|
||||
baseAvatarId: 1001,
|
||||
equipmentUniqueId: 13501,
|
||||
equipRelicList: [],
|
||||
exp: 0,
|
||||
level: 1,
|
||||
promotion: 1,
|
||||
rank: 1,
|
||||
skilltreeList: [],
|
||||
}],
|
||||
avatarList: avatar.map(av => av.data),
|
||||
isAll: body.isGetAll
|
||||
} as GetAvatarDataScRsp;
|
||||
|
||||
|
@ -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 Session from "../kcp/Session";
|
||||
|
||||
export default async function handle(session: Session, packet: Packet) {
|
||||
const _lineup = session.player.db.lineup;
|
||||
const lineup = _lineup.lineups[_lineup.curIndex];
|
||||
|
||||
session.send("GetCurBattleInfoScRsp", {
|
||||
retcode: 0,
|
||||
avatarList: [{
|
||||
avatarType: AvatarType.AVATAR_FORMAL_TYPE,
|
||||
id: 1001,
|
||||
avatarList: lineup.avatarList.map(list => {
|
||||
return {
|
||||
avatarType: list.avatarType,
|
||||
equipmentList: [{
|
||||
id: 20003,
|
||||
level: 1,
|
||||
rank: 1,
|
||||
index: 1,
|
||||
hp: 100,
|
||||
sp: 100,
|
||||
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,
|
||||
battleInfo: {},
|
||||
lastEndStatus: BattleEndStatus.BATTLE_END_WIN,
|
||||
lastEventId: 0
|
||||
lastEventId: 1
|
||||
} as GetCurBattleInfoScRsp);
|
||||
}
|
@ -3,23 +3,10 @@ import Packet from "../kcp/Packet";
|
||||
import Session from "../kcp/Session";
|
||||
|
||||
export default async function handle(session: Session, packet: Packet) {
|
||||
const _lineup = session.player.db.lineup;
|
||||
const lineup = _lineup.lineups[_lineup.curIndex];
|
||||
session.send("GetCurLineupDataScRsp", {
|
||||
retcode: 0,
|
||||
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
|
||||
}
|
||||
lineup
|
||||
} as GetCurLineupDataScRsp);
|
||||
}
|
@ -3,11 +3,12 @@ import Packet from "../kcp/Packet";
|
||||
import Session from "../kcp/Session";
|
||||
|
||||
export default async function handle(session: Session, packet: Packet) {
|
||||
const posData = session.player.db.posData;
|
||||
session.send("GetCurSceneInfoScRsp", {
|
||||
retcode: 0,
|
||||
scene: {
|
||||
planeId: 10000,
|
||||
floorId: 10000000,
|
||||
planeId: posData.planeID,
|
||||
floorId: posData.floorID,
|
||||
entityList: [],
|
||||
entityBuffList: [],
|
||||
entryId: 10001,
|
||||
|
@ -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 Packet from "../kcp/Packet";
|
||||
import Session from "../kcp/Session";
|
||||
@ -21,9 +22,11 @@ import Session from "../kcp/Session";
|
||||
export default async function handle(session: Session, packet: Packet) {
|
||||
const body = packet.body as PlayerLoginCsReq;
|
||||
|
||||
const plr = await Player.fromUID(session.player.db._id)!;
|
||||
if (!plr!.db.basicInfo) {
|
||||
plr!.db.basicInfo = {
|
||||
const plr = await Player.fromUID(session.player.db._id);
|
||||
if (!plr) return;
|
||||
|
||||
if (!plr.db.basicInfo) {
|
||||
plr.db.basicInfo = {
|
||||
exp: 0,
|
||||
level: 1,
|
||||
hcoin: 0,
|
||||
@ -33,7 +36,40 @@ export default async function handle(session: Session, packet: Packet) {
|
||||
stamina: 100,
|
||||
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", {
|
||||
|
@ -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))
|
||||
return;
|
||||
|
69
src/util/excel/AvatarExcel.ts
Normal file
69
src/util/excel/AvatarExcel.ts
Normal 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[];
|
||||
}
|
Loading…
Reference in New Issue
Block a user