diff --git a/src/db/Avatar.ts b/src/db/Avatar.ts index ea307df..9c656d7 100644 --- a/src/db/Avatar.ts +++ b/src/db/Avatar.ts @@ -10,7 +10,7 @@ export default class Avatar { } - public static async create(uid: UID, baseAvatarId: number = 1001): Promise { + public static async create(uid: UID, baseAvatarId: number = 1001, slot: number = -1): Promise { const db = Database.getInstance(); // Check if already exists const existing = await Avatar.fromUID(uid, baseAvatarId); @@ -29,7 +29,7 @@ export default class Avatar { hp: 10000, id: baseAvatarId, satiety: 100, - slot: -1, + slot: slot, sp: 10000 }); db.set("avatars", avatar); diff --git a/src/db/Player.ts b/src/db/Player.ts index a20320b..9720941 100644 --- a/src/db/Player.ts +++ b/src/db/Player.ts @@ -1,8 +1,10 @@ +import Session from "../server/kcp/Session"; import { ExtraLineupType, HeroBasicType, LineupInfo, Vector } from "../data/proto/StarRail"; import Logger from "../util/Logger"; import Account from "./Account"; import Avatar from "./Avatar"; import Database from "./Database"; +import { Scene } from "../game/Scene"; const c = new Logger("Player"); export interface LineupI { @@ -40,30 +42,33 @@ interface PlayerI { posData: { floorID: number; planeID: number; - pos?: Vector; + pos: Vector; } } export default class Player { - public readonly uid: number; - private constructor(public db: PlayerI) { + public readonly uid: number + public readonly scene: Scene; + + private constructor(readonly session: Session, public db: PlayerI) { this.uid = db._id; + this.scene = new Scene(this); } - public static async fromUID(uid: number | string): Promise { + public static async fromUID(session: Session, uid: number | string): Promise { if (typeof uid == "string") uid = Number(uid); const db = Database.getInstance(); const player = await db.get("players", { _id: uid }) as unknown as PlayerI; - if (!player) return Player.create(uid); - return new Player(player); + if (!player) return Player.create(session, uid); + return new Player(session, player); } - public static async fromToken(token: string): Promise { + public static async fromToken(session: Session, token: string): Promise { const db = Database.getInstance(); const plr = await db.get("players", { token }) as unknown as PlayerI; - if (!plr) return Player.fromUID((await Account.fromToken(token))?.uid || Math.round(Math.random() * 50000)); + if (!plr) return Player.fromUID(session, (await Account.fromToken(token))?.uid || Math.round(Math.random() * 50000)); - return new Player(plr); + return new Player(session, plr); } public async getLineup(lineupIndex?: number): Promise { @@ -89,7 +94,7 @@ export default class Player { this.db.lineup.curIndex = curIndex; } - public static async create(uid: number | string): Promise { + public static async create(session: Session, uid: number | string): Promise { if (typeof uid == "string") uid = Number(uid); const acc = await Account.fromUID(uid); if (!acc) { @@ -119,7 +124,12 @@ export default class Player { }, posData: { floorID: 10001001, - planeID: 10001 + planeID: 10001, + pos: { + x: 0, + y: 439, + z: -45507 + } }, banned: false } as PlayerI @@ -141,13 +151,16 @@ export default class Player { lineups: {} } for (let i = 0; i <= LINEUPS; i++) { - let copy = baseLineup; + const copy = baseLineup; copy.index = slot++; dataObj.lineup.lineups[i] = copy; } + await Avatar.create(uid, 1001, 0); + + await db.set("players", dataObj); - return new Player(dataObj); + return new Player(session, dataObj); } public async save() { diff --git a/src/game/Scene.ts b/src/game/Scene.ts new file mode 100644 index 0000000..857d9f4 --- /dev/null +++ b/src/game/Scene.ts @@ -0,0 +1,30 @@ +import { SceneEntityDisappearScNotify, SceneEntityUpdateScNotify } from "../data/proto/StarRail"; +import Player from "../db/Player"; +import { Entity } from "./entities/Entity"; + +export class Scene { + public entryId!: number; + public entities: Map = new Map(); + + constructor(public readonly player: Player) { + + } + + public spawnEntity(entity: Entity, silent: boolean = false) { + this.entities.set(entity.entityId, entity); + if (!silent) { + this.player.session.send("SceneEntityUpdateScNotify", { + entityList: [entity.getSceneEntityInfo()] + } as SceneEntityUpdateScNotify); + } + } + + public despawnEntity(entityId: number, silent: boolean = false) { + this.entities.delete(entityId); + if (!silent) { + this.player.session.send("SceneEntityDisappearScNotify", { + entityIdList: [entityId] + } as SceneEntityDisappearScNotify); + } + } +} diff --git a/src/game/entities/Actor.ts b/src/game/entities/Actor.ts new file mode 100644 index 0000000..ccd37e5 --- /dev/null +++ b/src/game/entities/Actor.ts @@ -0,0 +1,28 @@ +import { AvatarType, EntityType, MotionInfo, SceneActorInfo, SceneEntityInfo, Vector } from "../../data/proto/StarRail"; +import { Scene } from "../Scene"; +import { Entity } from "./Entity"; + +export class ActorEntity extends Entity +{ + public mapLayer: number = 0; + + constructor(readonly scene: Scene, public readonly avatarId: number, public pos: Vector, public rot?: Vector){ + super(scene, pos, rot); + } + + public getSceneEntityInfo(): SceneEntityInfo { + const sceneEntityInfo = super.getSceneEntityInfo(); + sceneEntityInfo.actor = { + avatarType: AvatarType.AVATAR_FORMAL_TYPE, + baseAvatarId: this.avatarId, + uid: this.scene.player.db._id, + mapLayer: 0 //? + }; + return sceneEntityInfo; + } + + public getEntityType(): EntityType { + return EntityType.ENTITY_AVATAR; + } + +} diff --git a/src/game/entities/Entity.ts b/src/game/entities/Entity.ts new file mode 100644 index 0000000..18bd58e --- /dev/null +++ b/src/game/entities/Entity.ts @@ -0,0 +1,33 @@ +import { EntityType, MotionInfo, SceneEntityInfo, Vector } from "../../data/proto/StarRail"; +import { Scene } from "../Scene"; + +export abstract class Entity{ + static nextEntityId: number = 0; + readonly entityId: number; + + constructor(readonly scene: Scene, public pos: Vector, public rot?: Vector){ + this.entityId = Entity.nextEntityId++; + } + + public abstract getEntityType(): EntityType; + + public getSceneEntityInfo(): SceneEntityInfo{ + return { + entityId: this.entityId, + groupId: 1, + instId: 1, + motion: { + pos: this.pos, + rot: this.rot ?? { + x: 0, + y: 0, + z: 0, + }, + } as MotionInfo, + actor: {}, + npc: {}, + npcMonster: {}, + prop: {} + } as SceneEntityInfo; + } +} diff --git a/src/game/entities/Monster.ts b/src/game/entities/Monster.ts new file mode 100644 index 0000000..d897f54 --- /dev/null +++ b/src/game/entities/Monster.ts @@ -0,0 +1,29 @@ +import { AvatarType, EntityType, MotionInfo, SceneActorInfo, SceneEntityInfo, SceneNpcMonsterInfo, Vector } from "../../data/proto/StarRail"; +import { Scene } from "../Scene"; +import { Entity } from "./Entity"; + +export class MonsterEntity extends Entity +{ + public mapLayer: number = 0; + + constructor(readonly scene: Scene, public readonly monsterId: number, public pos: Vector, public rot?: Vector){ + super(scene, pos, rot); + } + + public getSceneEntityInfo(): SceneEntityInfo { + const sceneEntityInfo = super.getSceneEntityInfo(); + sceneEntityInfo.npcMonster = { + eventId: 1, + isGenMonster: true, + isSetWorldLevel: false, + worldLevel: 1,//hardcoded + monsterId: this.monsterId, + } as SceneNpcMonsterInfo; + return sceneEntityInfo; + } + + public getEntityType(): EntityType { + return EntityType.ENTITY_MONSTER; + } + +} diff --git a/src/game/entities/NPC.ts b/src/game/entities/NPC.ts new file mode 100644 index 0000000..e4416ae --- /dev/null +++ b/src/game/entities/NPC.ts @@ -0,0 +1,26 @@ +import { EntityType, NpcExtraInfo, PropExtraInfo, SceneEntityInfo, SceneNpcInfo, SceneNpcMonsterInfo, ScenePropInfo, Vector } from "../../data/proto/StarRail"; +import { Scene } from "../Scene"; +import { Entity } from "./Entity"; + +export class NpcEntity extends Entity +{ + public mapLayer: number = 0; + + constructor(readonly scene: Scene, public readonly npcId: number, public pos: Vector, public rot?: Vector){ + super(scene, pos, rot); + } + + public getSceneEntityInfo(): SceneEntityInfo { + const sceneEntityInfo = super.getSceneEntityInfo(); + sceneEntityInfo.npc = { + npcId: this.npcId, + extraInfo: {} as NpcExtraInfo + } as SceneNpcInfo; + return sceneEntityInfo; + } + + public getEntityType(): EntityType { + return EntityType.ENTITY_PROP; + } + +} diff --git a/src/game/entities/Prop.ts b/src/game/entities/Prop.ts new file mode 100644 index 0000000..4f1a813 --- /dev/null +++ b/src/game/entities/Prop.ts @@ -0,0 +1,29 @@ +import { EntityType, PropExtraInfo, SceneEntityInfo, SceneNpcMonsterInfo, ScenePropInfo, Vector } from "../../data/proto/StarRail"; +import { Scene } from "../Scene"; +import { Entity } from "./Entity"; + +export class PropEntity extends Entity +{ + public mapLayer: number = 0; + + constructor(readonly scene: Scene, public readonly propId: number, public pos: Vector, public rot?: Vector){ + super(scene, pos, rot); + } + + public getSceneEntityInfo(): SceneEntityInfo { + const sceneEntityInfo = super.getSceneEntityInfo(); + sceneEntityInfo.prop = { + propId: this.propId, + createTimeMs: Date.now(), + extraInfo: {} as PropExtraInfo, + lifeTimeMs: Date.now() + 1000 * 60 * 60, + propState: 1, + } as ScenePropInfo; + return sceneEntityInfo; + } + + public getEntityType(): EntityType { + return EntityType.ENTITY_PROP; + } + +} diff --git a/src/server/packets/GetCurSceneInfoCsReq.ts b/src/server/packets/GetCurSceneInfoCsReq.ts index a9fc761..175f3fe 100644 --- a/src/server/packets/GetCurSceneInfoCsReq.ts +++ b/src/server/packets/GetCurSceneInfoCsReq.ts @@ -1,15 +1,21 @@ -import { GetCurSceneInfoScRsp } from "../../data/proto/StarRail"; +import { GetCurSceneInfoScRsp, Vector } from "../../data/proto/StarRail"; +import { ActorEntity } from "../../game/entities/Actor"; 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; + const _lineup = session.player.db.lineup; + const lineup = _lineup.lineups[_lineup.curIndex]; + const curAvatarEntity = new ActorEntity(session.player.scene, lineup.avatarList[0], posData.pos); session.send("GetCurSceneInfoScRsp", { retcode: 0, scene: { planeId: posData.planeID, floorId: posData.floorID, - entityList: [], + entityList: [ + curAvatarEntity + ], entityBuffList: [], entryId: 10001, envBuffList: [], @@ -17,4 +23,6 @@ export default async function handle(session: Session, packet: Packet) { lightenSectionList: [] }, } as unknown as GetCurSceneInfoScRsp); + session.player.scene.spawnEntity(curAvatarEntity, true); + session.player.scene.entryId = 10001; } \ No newline at end of file diff --git a/src/server/packets/PlayerGetTokenCsReq.ts b/src/server/packets/PlayerGetTokenCsReq.ts index 6aec7c9..0c32f5a 100644 --- a/src/server/packets/PlayerGetTokenCsReq.ts +++ b/src/server/packets/PlayerGetTokenCsReq.ts @@ -27,7 +27,7 @@ export default async function handle(session: Session, packet: Packet) { const account = await Account.fromToken(body.token || ""); if (!account) retWarn(`Account not found with token ${body.token}`); - const player = await Player.fromToken(account?.token || ""); + const player = await Player.fromToken(session, account?.token || ""); if (!player) retWarn(`Player not found with accountToken ${account?.token}`); if (!player || !account) { dataObj.retcode = 6; diff --git a/src/server/packets/PlayerLoginCsReq.ts b/src/server/packets/PlayerLoginCsReq.ts index ce92e72..9b4e98e 100644 --- a/src/server/packets/PlayerLoginCsReq.ts +++ b/src/server/packets/PlayerLoginCsReq.ts @@ -22,7 +22,7 @@ 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); + const plr = await Player.fromUID(session, session.player.db._id); if (!plr) return; if (!plr.db.heroBasicType) { @@ -45,7 +45,7 @@ export default async function handle(session: Session, packet: Packet) { } if (!plr.db.lineup) { - Avatar.create(plr.db._id); + await Avatar.create(plr.db._id, 1001, 0); const baseLineup = { avatarList: [1001], extraLineupType: ExtraLineupType.LINEUP_NONE, @@ -63,7 +63,7 @@ export default async function handle(session: Session, packet: Packet) { lineups: {} } for (let i = 0; i <= LINEUPS; i++) { - let copy = baseLineup; + const copy = baseLineup; copy.index = slot++; plr.db.lineup.lineups[i] = copy; } @@ -73,7 +73,12 @@ export default async function handle(session: Session, packet: Packet) { if (!plr.db.posData) { plr.db.posData = { floorID: 10001001, - planeID: 10001 + planeID: 10001, + pos: { + x: 0, + y: 439, + z: -45507 + } } plr.save(); } diff --git a/src/server/packets/SceneEntityMoveCsReq.ts b/src/server/packets/SceneEntityMoveCsReq.ts index 7dbbedb..0e55457 100644 --- a/src/server/packets/SceneEntityMoveCsReq.ts +++ b/src/server/packets/SceneEntityMoveCsReq.ts @@ -1,9 +1,38 @@ import { SceneEntityMoveCsReq, SceneEntityMoveScRsp } from "../../data/proto/StarRail"; +import { ActorEntity } from "../../game/entities/Actor"; import Packet from "../kcp/Packet"; import Session from "../kcp/Session"; export default async function handle(session: Session, packet: Packet) { const body = packet.body as SceneEntityMoveCsReq; + if (session.player.scene.entryId !== body.entryId) { + return; + } + for (const entityMotion of body.entityMotionList) { + const entity = session.player.scene.entities.get(entityMotion.entityId); + if (!entity) { //what?? + session.player.scene.despawnEntity(entityMotion.entityId); + continue; + } + const motion = entityMotion.motion; + if (!motion) { + continue; + } + + if (motion.pos && (Object.keys(motion.pos!).length > 0)) { //preventing motion sending empty pos causing the pos to be reset + entity.pos = motion.pos; + if (entity instanceof ActorEntity) { + entity.mapLayer = entityMotion.mapLayer; + session.player.db.posData.pos = motion.pos!; + } + } + + entity.rot = (Object.keys(motion.rot!).length > 0) ? motion.rot : { + x: 0, + y: 0, + z: 0, + } + } session.send("SceneEntityMoveScRsp", { retcode: 0,