Implementing entities. (#15)
* player: allowing player to send packet outside of packet handlers entity: simple entity implementation, spawning doesnt work currently * fix: conflict * fix: faulty request data * fix: oopsie * again... * requested: file name & fixes for latest commit * fix: memetrolls on drugs * [CodeFactor] Apply fixes [ci skip] [skip ci] * Minor changes * Formatting * Rename actor.ts to Actor.ts * Rename entity.ts to Entity.ts * Rename monster.ts to Monster.ts * Rename npc.ts to NPC.ts * Rename prop.ts to Prop.ts * Rename scene.ts to Scene.ts Co-authored-by: labalityowo <56186498+labalityowo@users.noreply.github.com> Co-authored-by: codefactor-io <support@codefactor.io> Co-authored-by: memetrollsXD <memetrollsxd@gmail.com>
This commit is contained in:
parent
0098da757c
commit
83d81487df
@ -10,7 +10,7 @@ export default class Avatar {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async create(uid: UID, baseAvatarId: number = 1001): Promise<Avatar> {
|
public static async create(uid: UID, baseAvatarId: number = 1001, slot: number = -1): Promise<Avatar> {
|
||||||
const db = Database.getInstance();
|
const db = Database.getInstance();
|
||||||
// Check if already exists
|
// Check if already exists
|
||||||
const existing = await Avatar.fromUID(uid, baseAvatarId);
|
const existing = await Avatar.fromUID(uid, baseAvatarId);
|
||||||
@ -29,7 +29,7 @@ export default class Avatar {
|
|||||||
hp: 10000,
|
hp: 10000,
|
||||||
id: baseAvatarId,
|
id: baseAvatarId,
|
||||||
satiety: 100,
|
satiety: 100,
|
||||||
slot: -1,
|
slot: slot,
|
||||||
sp: 10000
|
sp: 10000
|
||||||
});
|
});
|
||||||
db.set("avatars", avatar);
|
db.set("avatars", avatar);
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
import Session from "../server/kcp/Session";
|
||||||
import { ExtraLineupType, HeroBasicType, LineupInfo, Vector } from "../data/proto/StarRail";
|
import { ExtraLineupType, HeroBasicType, 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 Avatar from "./Avatar";
|
import Avatar from "./Avatar";
|
||||||
import Database from "./Database";
|
import Database from "./Database";
|
||||||
|
import { Scene } from "../game/Scene";
|
||||||
const c = new Logger("Player");
|
const c = new Logger("Player");
|
||||||
|
|
||||||
export interface LineupI {
|
export interface LineupI {
|
||||||
@ -40,30 +42,33 @@ interface PlayerI {
|
|||||||
posData: {
|
posData: {
|
||||||
floorID: number;
|
floorID: number;
|
||||||
planeID: number;
|
planeID: number;
|
||||||
pos?: Vector;
|
pos: Vector;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Player {
|
export default class Player {
|
||||||
public readonly uid: number;
|
public readonly uid: number
|
||||||
private constructor(public db: PlayerI) {
|
public readonly scene: Scene;
|
||||||
|
|
||||||
|
private constructor(readonly session: Session, public db: PlayerI) {
|
||||||
this.uid = db._id;
|
this.uid = db._id;
|
||||||
|
this.scene = new Scene(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async fromUID(uid: number | string): Promise<Player | undefined> {
|
public static async fromUID(session: Session, uid: number | string): Promise<Player | undefined> {
|
||||||
if (typeof uid == "string") uid = Number(uid);
|
if (typeof uid == "string") uid = Number(uid);
|
||||||
const db = Database.getInstance();
|
const db = Database.getInstance();
|
||||||
const player = await db.get("players", { _id: uid }) as unknown as PlayerI;
|
const player = await db.get("players", { _id: uid }) as unknown as PlayerI;
|
||||||
if (!player) return Player.create(uid);
|
if (!player) return Player.create(session, uid);
|
||||||
return new Player(player);
|
return new Player(session, player);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async fromToken(token: string): Promise<Player | undefined> {
|
public static async fromToken(session: Session, token: string): Promise<Player | undefined> {
|
||||||
const db = Database.getInstance();
|
const db = Database.getInstance();
|
||||||
const plr = await db.get("players", { token }) as unknown as PlayerI;
|
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<LineupInfo> {
|
public async getLineup(lineupIndex?: number): Promise<LineupInfo> {
|
||||||
@ -89,7 +94,7 @@ export default class Player {
|
|||||||
this.db.lineup.curIndex = curIndex;
|
this.db.lineup.curIndex = curIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async create(uid: number | string): Promise<Player | undefined> {
|
public static async create(session: Session, uid: number | string): Promise<Player | undefined> {
|
||||||
if (typeof uid == "string") uid = Number(uid);
|
if (typeof uid == "string") uid = Number(uid);
|
||||||
const acc = await Account.fromUID(uid);
|
const acc = await Account.fromUID(uid);
|
||||||
if (!acc) {
|
if (!acc) {
|
||||||
@ -119,7 +124,12 @@ export default class Player {
|
|||||||
},
|
},
|
||||||
posData: {
|
posData: {
|
||||||
floorID: 10001001,
|
floorID: 10001001,
|
||||||
planeID: 10001
|
planeID: 10001,
|
||||||
|
pos: {
|
||||||
|
x: 0,
|
||||||
|
y: 439,
|
||||||
|
z: -45507
|
||||||
|
}
|
||||||
},
|
},
|
||||||
banned: false
|
banned: false
|
||||||
} as PlayerI
|
} as PlayerI
|
||||||
@ -141,13 +151,16 @@ export default class Player {
|
|||||||
lineups: {}
|
lineups: {}
|
||||||
}
|
}
|
||||||
for (let i = 0; i <= LINEUPS; i++) {
|
for (let i = 0; i <= LINEUPS; i++) {
|
||||||
let copy = baseLineup;
|
const copy = baseLineup;
|
||||||
copy.index = slot++;
|
copy.index = slot++;
|
||||||
dataObj.lineup.lineups[i] = copy;
|
dataObj.lineup.lineups[i] = copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await Avatar.create(uid, 1001, 0);
|
||||||
|
|
||||||
|
|
||||||
await db.set("players", dataObj);
|
await db.set("players", dataObj);
|
||||||
return new Player(dataObj);
|
return new Player(session, dataObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async save() {
|
public async save() {
|
||||||
|
30
src/game/Scene.ts
Normal file
30
src/game/Scene.ts
Normal file
@ -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<number, Entity> = new Map<number, Entity>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
src/game/entities/Actor.ts
Normal file
28
src/game/entities/Actor.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
33
src/game/entities/Entity.ts
Normal file
33
src/game/entities/Entity.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
29
src/game/entities/Monster.ts
Normal file
29
src/game/entities/Monster.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
26
src/game/entities/NPC.ts
Normal file
26
src/game/entities/NPC.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
29
src/game/entities/Prop.ts
Normal file
29
src/game/entities/Prop.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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 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;
|
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", {
|
session.send("GetCurSceneInfoScRsp", {
|
||||||
retcode: 0,
|
retcode: 0,
|
||||||
scene: {
|
scene: {
|
||||||
planeId: posData.planeID,
|
planeId: posData.planeID,
|
||||||
floorId: posData.floorID,
|
floorId: posData.floorID,
|
||||||
entityList: [],
|
entityList: [
|
||||||
|
curAvatarEntity
|
||||||
|
],
|
||||||
entityBuffList: [],
|
entityBuffList: [],
|
||||||
entryId: 10001,
|
entryId: 10001,
|
||||||
envBuffList: [],
|
envBuffList: [],
|
||||||
@ -17,4 +23,6 @@ export default async function handle(session: Session, packet: Packet) {
|
|||||||
lightenSectionList: []
|
lightenSectionList: []
|
||||||
},
|
},
|
||||||
} as unknown as GetCurSceneInfoScRsp);
|
} as unknown as GetCurSceneInfoScRsp);
|
||||||
|
session.player.scene.spawnEntity(curAvatarEntity, true);
|
||||||
|
session.player.scene.entryId = 10001;
|
||||||
}
|
}
|
@ -27,7 +27,7 @@ export default async function handle(session: Session, packet: Packet) {
|
|||||||
const account = await Account.fromToken(body.token || "");
|
const account = await Account.fromToken(body.token || "");
|
||||||
if (!account) retWarn(`Account not found with token ${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) retWarn(`Player not found with accountToken ${account?.token}`);
|
||||||
if (!player || !account) {
|
if (!player || !account) {
|
||||||
dataObj.retcode = 6;
|
dataObj.retcode = 6;
|
||||||
|
@ -22,7 +22,7 @@ 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, session.player.db._id);
|
||||||
if (!plr) return;
|
if (!plr) return;
|
||||||
|
|
||||||
if (!plr.db.heroBasicType) {
|
if (!plr.db.heroBasicType) {
|
||||||
@ -45,7 +45,7 @@ export default async function handle(session: Session, packet: Packet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!plr.db.lineup) {
|
if (!plr.db.lineup) {
|
||||||
Avatar.create(plr.db._id);
|
await Avatar.create(plr.db._id, 1001, 0);
|
||||||
const baseLineup = {
|
const baseLineup = {
|
||||||
avatarList: [1001],
|
avatarList: [1001],
|
||||||
extraLineupType: ExtraLineupType.LINEUP_NONE,
|
extraLineupType: ExtraLineupType.LINEUP_NONE,
|
||||||
@ -63,7 +63,7 @@ export default async function handle(session: Session, packet: Packet) {
|
|||||||
lineups: {}
|
lineups: {}
|
||||||
}
|
}
|
||||||
for (let i = 0; i <= LINEUPS; i++) {
|
for (let i = 0; i <= LINEUPS; i++) {
|
||||||
let copy = baseLineup;
|
const copy = baseLineup;
|
||||||
copy.index = slot++;
|
copy.index = slot++;
|
||||||
plr.db.lineup.lineups[i] = copy;
|
plr.db.lineup.lineups[i] = copy;
|
||||||
}
|
}
|
||||||
@ -73,7 +73,12 @@ export default async function handle(session: Session, packet: Packet) {
|
|||||||
if (!plr.db.posData) {
|
if (!plr.db.posData) {
|
||||||
plr.db.posData = {
|
plr.db.posData = {
|
||||||
floorID: 10001001,
|
floorID: 10001001,
|
||||||
planeID: 10001
|
planeID: 10001,
|
||||||
|
pos: {
|
||||||
|
x: 0,
|
||||||
|
y: 439,
|
||||||
|
z: -45507
|
||||||
|
}
|
||||||
}
|
}
|
||||||
plr.save();
|
plr.save();
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,38 @@
|
|||||||
import { SceneEntityMoveCsReq, SceneEntityMoveScRsp } from "../../data/proto/StarRail";
|
import { SceneEntityMoveCsReq, SceneEntityMoveScRsp } from "../../data/proto/StarRail";
|
||||||
|
import { ActorEntity } from "../../game/entities/Actor";
|
||||||
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 body = packet.body as SceneEntityMoveCsReq;
|
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", {
|
session.send("SceneEntityMoveScRsp", {
|
||||||
retcode: 0,
|
retcode: 0,
|
||||||
|
Loading…
Reference in New Issue
Block a user