I don't want to talk about it. (#35)

This commit is contained in:
GanyusLeftHorn 2022-08-04 02:41:34 +02:00 committed by GitHub
parent fa7a364f88
commit 4738e7c44a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 237 additions and 142 deletions

View File

@ -13,6 +13,7 @@ export default async function handle(command: Command) {
const actionType = command.args[0];
const avatarId = Number(command.args[1]);
const uid = Interface.target.player.db._id;
const player = Interface.target.player;
switch (actionType) {
default: {
@ -20,21 +21,32 @@ export default async function handle(command: Command) {
break;
}
case "add": {
if (!avatarId) return c.log("No avatarId specified");
if (!avatarId) {
return c.log("No avatarId specified");
}
// Check if it already exists
const avatar = await Avatar.fromUID(uid, avatarId);
if (avatar.length > 0) return c.log(`Avatar ${avatarId} already exists`);
Avatar.create(uid, avatarId).then(a => c.log(`Avatar ${avatarId} added to ${a.ownerUid}`));
if (await Avatar.hasAvatar(player, avatarId)) {
return c.log(`Avatar ${avatarId} already exists`);
}
await Avatar.addAvatarToPlayer(player, avatarId).then(a => c.log(`Avatar ${avatarId} added to ${a.db.ownerUid}`));
break;
}
case "remove": {
if (!avatarId) return c.log("No avatarId specified");
Avatar.remove(uid, avatarId).then(() => c.log(`Avatar ${avatarId} removed from ${uid}`));
await Avatar.removeAvatarFromPlayer(player, avatarId).then(() => c.log(`Avatar ${avatarId} removed from ${uid}`));
break;
}
case "giveall": {
for (const id in AvatarExcelTable) {
await Avatar.create(uid, parseInt(id));
const avatarId = Number(id);
// Let's not brick our account.
if (avatarId>= 8000) {
continue;
}
await Avatar.addAvatarToPlayer(player, avatarId);
}
c.log(`All avatars added to ${uid}`);
break;
@ -42,7 +54,7 @@ export default async function handle(command: Command) {
case "removeall": {
for (const id in AvatarExcelTable) {
if (Number(id) !== 1001) {
await Avatar.remove(uid, parseInt(id));
await Avatar.removeAvatarFromPlayer(player, parseInt(id));
}
}
c.log(`All avatars removed from ${uid}`);

View File

@ -1,68 +1,162 @@
import { Avatar as AvatarI, AvatarType, LineupAvatar } from '../data/proto/StarRail';
// import { Avatar as AvatarI, AvatarType, LineupAvatar } from '../data/proto/StarRail';
import { AvatarSkillTree, AvatarType, EquipRelic, Avatar as AvatarProto } from '../data/proto/StarRail';
import Logger from '../util/Logger';
import Database from './Database';
import Player, { LineupI } from './Player';
const c = new Logger("Avatar");
type UID = number | string;
export default class Avatar {
private constructor(public ownerUid: UID, public data: AvatarI, public lineup: LineupAvatar) {
interface AvatarI {
ownerUid: number,
baseAvatarId: number,
avatarType: AvatarType,
level: number,
exp: number,
promotion: number,
rank: number,
equipmentUniqueId: number,
equipRelicList: EquipRelic[],
skilltreeList: AvatarSkillTree[],
fightProps: {
hp: number,
sp: number,
satiety: number
}
}
export default class Avatar {
public readonly player: Player;
public readonly db: AvatarI;
private constructor(player: Player, db: AvatarI) {
this.player = player;
this.db = db;
}
public static async create(uid: UID, baseAvatarId: number = 1001, slot: number = -1): Promise<Avatar> {
/********************************************************************************
Create and fetch avatars from the database.
********************************************************************************/
public static async loadAvatarsForPlayer(player: Player) : Promise<Avatar[]> {
// Read avatars for this player from the database.
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,
const avatars = await db.getAll("avatars", { ownerUid: player.uid }) as unknown as AvatarI[];
// If this player doesn't have any avatars yet, add a default.
if (avatars.length < 1) {
avatars.push({
ownerUid: player.uid,
baseAvatarId: 1001,
avatarType: AvatarType.AVATAR_FORMAL_TYPE,
level: 1,
exp: 1,
promotion: 1,
rank: 1,
equipmentUniqueId: 20003,
equipRelicList: [],
skilltreeList: [],
}, {
avatarType: AvatarType.AVATAR_FORMAL_TYPE,
fightProps: {
hp: 10000,
id: baseAvatarId,
satiety: 100,
slot: slot,
sp: 10000
});
db.set("avatars", avatar);
return avatar;
sp: 10000,
satiety: 100
}
} as AvatarI);
}
public static async fromUID(ownerUid: UID, baseAvatarId?: number): Promise<Avatar[]> {
const query = { ownerUid } as { ownerUid: UID, "data.baseAvatarId"?: number };
if (baseAvatarId) query['data.baseAvatarId'] = baseAvatarId;
// Construct Avatar instances.
const res: Avatar[] = []
for (const avatar of avatars) {
res.push(new Avatar(player, avatar));
}
// Done.
return res;
}
public static async loadAvatarForPlayer(player: Player, baseAvatarId: number) : Promise<Avatar> {
// Fetch the given avatar from the database.
const db = Database.getInstance();
return await db.getAll("avatars", query) as unknown as Avatar[];
const avatar = await db.get("avatars", { ownerUid: player.uid, baseAvatarId: baseAvatarId }) as unknown as AvatarI;
// Sanity check.
if (!avatar) {
throw new Error(`Avatar ${baseAvatarId} does not exist for player ${player.uid}. This should never happen. Check your logic at the callsite.`);
}
public static async fromLineup(uid: UID, lineup: LineupI): Promise<Avatar[]> {
try {
const avatarList: Array<Avatar> = [];
for (let i = 0; i < lineup.avatarList.length; i++) {
const avatarId = lineup.avatarList[i];
const avatar = await Avatar.fromUID(uid, avatarId);
avatarList.push(avatar[0]);
// Done.
return new Avatar(player, avatar);
}
return await Promise.all(avatarList);
} catch (e) {
c.error(e as Error);
return [];
}
}
public static async remove(ownerUid: UID, baseAvatarId: number): Promise<void> {
public static async hasAvatar(player: Player, baseAvatarId: number) : Promise<boolean> {
// Fetch the given avatar from the database.
const db = Database.getInstance();
await db.delete("avatars", { ownerUid, "data.baseAvatarId": baseAvatarId });
const avatar = await db.get("avatars", { ownerUid: player.uid, baseAvatarId: baseAvatarId }) as unknown as AvatarI;
// Return.
return avatar ? true : false;
}
public static async addAvatarToPlayer(player: Player, baseAvatarId: number) : Promise<Avatar> {
const db = Database.getInstance();
// Make sure the player doesn't already have that avatar.
const existingAvatar = await db.get("avatars", { ownerUid: player.uid, baseAvatarId: baseAvatarId }) as unknown as AvatarI;
if (existingAvatar) {
return new Avatar(player, existingAvatar);
}
// Insert.
const data : AvatarI = {
ownerUid: player.uid,
baseAvatarId: baseAvatarId,
avatarType: AvatarType.AVATAR_FORMAL_TYPE,
level: 1,
exp: 1,
promotion: 1,
rank: 1,
equipmentUniqueId: 20003,
equipRelicList: [],
skilltreeList: [],
fightProps: {
hp: 10000,
sp: 10000,
satiety: 100
}
};
await db.set("avatars", data);
return new Avatar(player, data);
}
public static async removeAvatarFromPlayer(player: Player, baseAvatarId: number) {
const db = Database.getInstance();
await db.delete("avatars", { ownerUid: player.uid, baseAvatarId: baseAvatarId });
}
public static async getAvatarsForLineup(player: Player, lineup: LineupI) : Promise<Avatar[]> {
const res: Avatar[] = [];
// Load all avatars in this lineup.
for (const avatarId of lineup.avatarList) {
res.push(await Avatar.loadAvatarForPlayer(player, avatarId));
}
// Done.
return res;
}
/********************************************************************************
Get avatar info.
********************************************************************************/
public asAvatarProto() : AvatarProto {
return {
baseAvatarId: this.db.baseAvatarId,
exp: this.db.exp,
level: this.db.level,
promotion: this.db.promotion,
rank: this.db.rank,
skilltreeList: this.db.skilltreeList,
equipmentUniqueId: this.db.equipmentUniqueId,
equipRelicList: this.db.equipRelicList
};
}
}

View File

@ -28,7 +28,7 @@ export default class Database {
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 }]);
await _collection.createIndexes([{ key: { _id: 1 } }]);
}
const result = query ? await _collection.findOne(query) : await _collection.findOne();
return result;
@ -44,7 +44,7 @@ export default class Database {
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 }]);
await _collection.createIndexes([{ key: { _id: 1 } }]);
}
const result = query ? await _collection.find(query).toArray() : await _collection.find().toArray();
return result;
@ -60,10 +60,12 @@ export default class Database {
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 }]);
await _collection.createIndexes([{ key: { _id: 1 } }]);
}
return await _collection.insertOne(payload);
} catch (e) {
c.error("In set.")
c.error(payload)
c.error(e as Error);
}
}
@ -74,7 +76,7 @@ export default class Database {
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 }]);
await _collection.createIndexes([{ key: { _id: 1 } }]);
}
return await _collection.deleteOne(query);
} catch (e) {
@ -88,7 +90,7 @@ export default class Database {
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 }]);
await _collection.createIndexes([{ key: { _id: 1 } }]);
}
return await _collection.updateOne(query, { $set: payload }, { upsert: true });
} catch (e) {

View File

@ -3,7 +3,6 @@ import Logger from "../util/Logger";
import Database from "./Database";
import Player from "./Player";
import ItemExcel from "../util/excel/ItemExcel";
import { Timestamp } from "mongodb";
const c = new Logger("Inventory");
@ -165,10 +164,6 @@ export default class Inventory {
if (!itemData || itemData.ItemType != "Material") {
return;
}
const t = itemData.ItemType;
if (t != "Material") {
return;
}
// Get current item count for this ID and calculate new count.
const currentCount = this.db.materials[id] ?? 0;
@ -189,6 +184,11 @@ export default class Inventory {
public async addEquipment(equipment: number | Equipment) {
// If the parameter is a number, add a new equipment with this item ID as base.
if (typeof(equipment) == "number") {
// Sanity check.
if (ItemExcel.fromId(equipment)?.ItemType != "Equipment") {
return;
}
const equip : Equipment = {
tid: equipment,
uniqueId: this.db.nextItemUid++,
@ -292,6 +292,9 @@ export default class Inventory {
// Find index to delete.
const toDelete: number = (typeof(equipment) == "number") ? equipment : equipment.uniqueId;
const index = this.db.equipments.findIndex(i => i.uniqueId == toDelete);
if (index == -1) {
return;
}
// Delete and save.
this.db.equipments.splice(index, 1);
@ -309,6 +312,9 @@ export default class Inventory {
// Find index to delete.
const toDelete: number = (typeof(relic) == "number") ? relic : relic.uniqueId;
const index = this.db.relics.findIndex(i => i.uniqueId == toDelete);
if (index == -1) {
return;
}
// Delete and save.
this.db.relics.splice(index, 1);

View File

@ -1,5 +1,5 @@
import Session from "../server/kcp/Session";
import { AvatarType, ExtraLineupType, HeroBasicType, LineupInfo, Vector } from "../data/proto/StarRail";
import { AvatarType, ExtraLineupType, HeroBasicType, LineupAvatar, LineupInfo, Vector } from "../data/proto/StarRail";
import Logger from "../util/Logger";
import Account from "./Account";
import Avatar from "./Avatar";
@ -67,34 +67,35 @@ export default class Player {
public static async fromToken(session: Session, token: string): Promise<Player | undefined> {
const db = Database.getInstance();
const plr = await db.get("players", { token }) as unknown as PlayerI;
if (!plr) return Player.fromUID(session, (await Account.fromToken(token))?.uid || Math.round(Math.random() * 50000));
const plr = await db.get("players", { token: token }) as unknown as PlayerI;
if (!plr) return await Player.fromUID(session, (await Account.fromToken(token))?.uid || Math.round(Math.random() * 50000));
return new Player(session, plr);
}
public async getLineup(lineupIndex?: number): Promise<LineupInfo> {
const curIndex = this.db.lineup.curIndex;
const lineup = this.db.lineup.lineups[lineupIndex || curIndex];
const avatars = await Avatar.fromLineup(this.uid, lineup);
let slot = 0;
avatars.forEach(avatar => {
// Fallback lineup
if (!avatar) return; // Matsuko.
if (!avatar.lineup) avatar.lineup = {
avatarType: AvatarType.AVATAR_FORMAL_TYPE,
hp: 10000,
id: 1001,
satiety: 100,
// Get avatar data.
const index = lineupIndex ?? this.db.lineup.curIndex;
const lineup = this.db.lineup.lineups[index];
const avatars = await Avatar.getAvatarsForLineup(this, lineup);
// Construct LineupInfo.
const lineupAvatars : LineupAvatar[] = [];
for (let slot = 0; slot < avatars.length; slot++) {
lineupAvatars.push({
slot: slot,
sp: 10000
}
avatar.lineup.slot = slot++;
avatarType: avatars[slot].db.avatarType,
id: avatars[slot].db.baseAvatarId,
hp: avatars[slot].db.fightProps.hp,
sp: avatars[slot].db.fightProps.sp,
satiety: avatars[slot].db.fightProps.satiety
});
}
return {
...lineup,
index: 0,
avatarList: avatars.map(x => x.lineup)
avatarList: lineupAvatars
}
}
@ -166,23 +167,26 @@ export default class Player {
name: "",
planeId: 10001
}
const LINEUPS = 6;
dataObj.lineup = {
curIndex: 0,
lineups: {}
}
for (let i = 0; i <= LINEUPS; i++) {
const copy = baseLineup;
copy.index = 0;
copy.name = `Team ${i}`;
for (let i = 0; i < LINEUPS; i++) {
const copy = {
...baseLineup,
index: 0,
name: `Team ${i}`
};
dataObj.lineup.lineups[i] = copy;
}
await Avatar.create(uid, 1001, 0);
const player = new Player(session, dataObj);
await Avatar.addAvatarToPlayer(player, 1001);
// await Avatar.create(uid, 1001, 0);
// Save to database and return.
await db.set("players", dataObj);
return new Player(session, dataObj);
return player;
}
public async save() {

View File

@ -55,7 +55,7 @@ export default class Session {
if (!recv) break;
if (Packet.isValid(recv)) {
this.handlePacket(new Packet(recv));
await this.handlePacket(new Packet(recv));
}
} while (recv)
@ -68,8 +68,8 @@ export default class Session {
this.c.verbL(packet.body);
this.c.verbH(packet.rawData);
import(`../packets/${packet.protoName}`).then(mod => {
mod.default(this, packet);
await import(`../packets/${packet.protoName}`).then(async mod => {
await mod.default(this, packet);
}).catch(e => {
if (e.code === 'MODULE_NOT_FOUND') this.c.warn(`Unhandled packet: ${packet.protoName}`);
else this.c.error(e);
@ -79,12 +79,13 @@ export default class Session {
}
public async sync() {
const avatars = await Avatar.fromUID(this.player.db._id);
const avatars = await Avatar.loadAvatarsForPlayer(this.player);
//const avatars = await Avatar.fromUID(this.player.db._id);
const inventory = await this.player.getInventory();
this.send(PlayerSyncScNotify, PlayerSyncScNotify.fromPartial({
avatarSync: {
avatarList: avatars.map(x => x.data)
avatarList: avatars.map(x => x.asAvatarProto())
},
materialList: inventory.getMaterialList(),
equipmentList: inventory.getEquipmentList(),

View File

@ -1,26 +1,19 @@
import { GetAvatarDataCsReq, GetAvatarDataScRsp } from "../../data/proto/StarRail";
import AvatarExcelTable from "../../data/excel/AvatarExcelTable.json";
import Packet from "../kcp/Packet";
import Session from "../kcp/Session";
import AvatarDb from "../../db/Avatar";
import { Avatar } from "../../data/proto/StarRail";
export default async function handle(session: Session, packet: Packet) {
const body = packet.body as GetAvatarDataCsReq;
const avatars = await AvatarDb.loadAvatarsForPlayer(session.player);
const avatar = await AvatarDb.fromUID(session.player.db._id);
console.log(avatar.length)
const dataObj = {
retcode: 0,
avatarList: avatar.map(av => Avatar.fromPartial(av.data)),
avatarList: avatars.map(av => av.asAvatarProto()),
isAll: body.isGetAll
};
Object.values(AvatarExcelTable).forEach(avatar => {
// dataObj.avatarList.push()
});
session.send(GetAvatarDataScRsp, dataObj);
// Make sure we wait for this to send.
// GetAvatarDataScRsp HAS to be sent immediately after the Req.
await session.send(GetAvatarDataScRsp, dataObj);
}

View File

@ -1,39 +1,12 @@
import { AvatarType, ExtraLineupType, GetCurLineupDataScRsp } from "../../data/proto/StarRail";
import Avatar from "../../db/Avatar";
import Packet from "../kcp/Packet";
import Session from "../kcp/Session";
export default async function handle(session: Session, packet: Packet) {
let lineup = await session.player.getLineup();
// This is a HORRIBLE solution, but tbh I just can't reproduce the bug so:
if (!lineup) {
session.c.error("Error! lineup is undefined. Falling back to default lineup.", false);
lineup = {
avatarList: [{
avatarType: AvatarType.AVATAR_FORMAL_TYPE,
hp: 10000,
id: 1001,
sp: 10000,
satiety: 100,
slot: 0,
}],
extraLineupType: ExtraLineupType.LINEUP_NONE,
index: 0,
isVirtual: false,
leaderSlot: 0,
mp: 100,
planeId: 10001,
name: "Fallback"
}
session.player.setLineup(lineup, 0, 0);
session.player.save();
}
session.send(GetCurLineupDataScRsp, {
retcode: 0,
lineup: {
...lineup,
}
lineup: lineup
} as GetCurLineupDataScRsp);
}

View File

@ -8,9 +8,16 @@ export default async function handle(session: Session, packet: Packet) {
const body = packet.body as JoinLineupCsReq;
session.send(JoinLineupScRsp, { retcode: 0 });
// Replace avatar in the player's lineup.
const slot = body.slot ?? 0;
session.player.db.lineup.lineups[session.player.db.lineup.curIndex].avatarList[slot] = body.baseAvatarId;
/*
let lineup = await session.player.getLineup();
const slot = body.slot || 0;
const avatarList = [];
// What in the fuck is the purpose of this loop supposed to be?!
for (const avatarId in lineup) {
const avatar = await Avatar.fromUID(session.player.db._id, Number(avatarId));
if (avatar.length === 0) return session.c.warn(`Avatar ${body.baseAvatarId} not found`);
@ -27,10 +34,12 @@ export default async function handle(session: Session, packet: Packet) {
};
if (body.extraLineupType) lineup.extraLineupType = body.extraLineupType;
session.player.setLineup(lineup);
*/
session.player.save();
session.send(SyncLineupNotify, {
lineup: lineup,
lineup: await session.player.getLineup(),
reasonList: [SyncLineupReason.SYNC_REASON_NONE]
} as SyncLineupNotify);
}

View File

@ -45,7 +45,8 @@ export default async function handle(session: Session, packet: Packet) {
}
if (!plr.db.lineup) {
await Avatar.create(plr.db._id, 1001, 0);
await Avatar.addAvatarToPlayer(plr, 1001);
//await Avatar.create(plr.db._id, 1001, 0);
const baseLineup = {
avatarList: [1001],
extraLineupType: ExtraLineupType.LINEUP_NONE,