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) {
|
public async set(collection: string, payload: any) {
|
||||||
try {
|
try {
|
||||||
const db = await Database.client.db();
|
const db = await Database.client.db();
|
||||||
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
@ -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;
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
@ -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);
|
||||||
}
|
}
|
@ -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,
|
||||||
|
@ -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", {
|
||||||
|
@ -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;
|
||||||
|
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