diff --git a/src/cli/commands/avatarCommands/index.ts b/src/cli/commands/avatarCommands/index.ts index 4d7ef860..50819727 100644 --- a/src/cli/commands/avatarCommands/index.ts +++ b/src/cli/commands/avatarCommands/index.ts @@ -3,6 +3,8 @@ import equipCommand from './equipCommand' import godCommand from './godCommand' import guidCommand from './guidCommand' import healCommand from './healCommand' +import levelCommand from './levelCommand' +import promoteCommand from './promoteCommand' import rechargeCommand from './rechargeCommand' import setcsCommand from './setcsCommand' import talentCommand from './talentCommand' @@ -11,10 +13,12 @@ import fpCommand from './fpCommand' const avatarCommands: CommandDefinition[] = [ godCommand, healCommand, + levelCommand, + promoteCommand, rechargeCommand, guidCommand, equipCommand, - fpCommand, // setfp getfp listfp,3 in 1 + fpCommand, // setfp getfp listfp, 3 in 1 setcsCommand, talentCommand ] diff --git a/src/cli/commands/avatarCommands/levelCommand.ts b/src/cli/commands/avatarCommands/levelCommand.ts new file mode 100644 index 00000000..86e9c8d6 --- /dev/null +++ b/src/cli/commands/avatarCommands/levelCommand.ts @@ -0,0 +1,42 @@ +import translate from '@/translate' +import { CommandDefinition } from '..' + +const levelCommand: CommandDefinition = { + name: 'level', + usage: 2, + args: [ + { name: 'lv', type: 'num' }, + { name: 'uidInput', type: 'str', optional: true } + ], + allowPlayer: true, + exec: async (cmdInfo) => { + const { args, sender, cli, kcpServer } = cmdInfo + const { print, printError } = cli + const [lv, uidInput] = args + + let uid; + if (uidInput === '@s' || uidInput === undefined) { + uid = sender?.uid; + } else if (!isNaN(parseInt(uidInput))) { + uid = parseInt(uidInput); + } else { + return printError(translate('generic.invalidTarget')); + } + + const player = kcpServer.game.getPlayerByUid(uid || sender?.uid) + if (!player) return printError(translate('generic.playerNotFound')) + + const { currentAvatar } = player + if (!currentAvatar) return printError(translate('generic.playerNoCurAvatar')) + + const targetLv = parseInt(lv) + if (targetLv >= 1 && targetLv <= 90) { + currentAvatar.level = targetLv + print(translate('cli.commands.level.info.level', currentAvatar.avatarId, currentAvatar.level)) + } else { + printError(translate('cli.commands.level.error.invalidLv', currentAvatar.level)) + } + } +} + +export default levelCommand \ No newline at end of file diff --git a/src/cli/commands/avatarCommands/promoteCommand.ts b/src/cli/commands/avatarCommands/promoteCommand.ts new file mode 100644 index 00000000..f36a5a9b --- /dev/null +++ b/src/cli/commands/avatarCommands/promoteCommand.ts @@ -0,0 +1,42 @@ +import translate from '@/translate' +import { CommandDefinition } from '..' + +const promoteCommand: CommandDefinition = { + name: 'promote', + usage: 2, + args: [ + { name: 'promotion', type: 'num' }, + { name: 'uidInput', type: 'str', optional: true } + ], + allowPlayer: true, + exec: async (cmdInfo) => { + const { args, sender, cli, kcpServer } = cmdInfo + const { print, printError } = cli + const [promotion, uidInput] = args + + let uid; + if (uidInput === '@s' || uidInput === undefined) { + uid = sender?.uid; + } else if (!isNaN(parseInt(uidInput))) { + uid = parseInt(uidInput); + } else { + return printError(translate('generic.invalidTarget')); + } + + const player = kcpServer.game.getPlayerByUid(uid || sender?.uid) + if (!player) return printError(translate('generic.playerNotFound')) + + const { currentAvatar } = player + if (!currentAvatar) return printError(translate('generic.playerNoCurAvatar')) + + const targetPromotion = parseInt(promotion) + if (targetPromotion >= 0 && targetPromotion <= 6) { + currentAvatar.promoteLevel = targetPromotion + print(translate('cli.commands.promote.info.promote', currentAvatar.avatarId, currentAvatar.promoteLevel)) + } else { + printError(translate('cli.commands.promote.error.invalidPromotion', currentAvatar.promoteLevel)) + } + } +} + +export default promoteCommand \ No newline at end of file diff --git a/src/kcpServer/game/entity/index.ts b/src/kcpServer/game/entity/index.ts index 4674023c..21b364b0 100644 --- a/src/kcpServer/game/entity/index.ts +++ b/src/kcpServer/game/entity/index.ts @@ -15,6 +15,7 @@ import { getStringHash } from '@/utils/hash' import EntityProps from './entityProps' import FightProp, { FightPropChangeReason } from './fightProps' import Motion from './motion' +import SceneEntityAppear from '#/packets/SceneEntityAppear' export default class Entity extends BaseClass { manager?: EntityManager @@ -140,8 +141,28 @@ export default class Entity extends BaseClass { get level() { return this.props.get(PlayerPropEnum.PROP_LEVEL) } + set level(v: number) { this.props.set(PlayerPropEnum.PROP_LEVEL, v) + // promotion + if (v >= 1 && v <= 20) { + this.promoteLevel = 0 + } else if (v > 20 && v <= 40) { + this.promoteLevel = 1 + } else if (v > 40 && v <= 50) { + this.promoteLevel = 2 + } else if (v > 50 && v <= 60) { + this.promoteLevel = 3 + } else if (v > 60 && v <= 70) { + this.promoteLevel = 4 + } else if (v > 70 && v <= 80) { + this.promoteLevel = 5 + } else if (v > 80 && v <= 90) { + this.promoteLevel = 6 + } else { + // do nothing + } + SceneEntityAppear.sendNotify } get exp() { diff --git a/src/kcpServer/game/index.ts b/src/kcpServer/game/index.ts index cae7bd20..65e9ed82 100644 --- a/src/kcpServer/game/index.ts +++ b/src/kcpServer/game/index.ts @@ -34,6 +34,8 @@ import ActivityManager from './manager/activityManager' import { ChatManager } from './manager/chatManager' import ShopManager from './manager/shopManager' +const logger = new Logger('GAME') + export default class Game { server: KcpServer @@ -99,7 +101,7 @@ export default class Game { // login await this.playerLogin(player.context) } catch (err) { - console.log('Failed to create server player:', err) + logger.error('Failed to create server player:', err) } } diff --git a/src/kcpServer/packets/GmTalk.ts b/src/kcpServer/packets/GmTalk.ts index 9f9f2a6f..e44a2bed 100644 --- a/src/kcpServer/packets/GmTalk.ts +++ b/src/kcpServer/packets/GmTalk.ts @@ -30,11 +30,17 @@ class GmTalkPacket extends Packet implements PacketInterface { private async gmtHp(context: PacketContext, amount: number) { const { player, seqId } = context const { currentAvatar } = player - if (amount > 0) await currentAvatar.heal(amount, true, ChangeHpReasonEnum.CHANGE_HP_ADD_GM, seqId) - else await currentAvatar.takeDamage(0, -amount, true, ChangeHpReasonEnum.CHANGE_HP_SUB_GM, seqId) + if (amount > 0) { + logger.info(`[${player.uid}]Player's current avatar(id: ${currentAvatar.avatarId}) hp add ${amount}.`) + await currentAvatar.heal(amount, true, ChangeHpReasonEnum.CHANGE_HP_ADD_GM, seqId) + } + else { + logger.info(`[${player.uid}]Player's current avatar(id: ${currentAvatar.avatarId}) hp sub ${amount}.`) + await currentAvatar.takeDamage(0, -amount, true, ChangeHpReasonEnum.CHANGE_HP_SUB_GM, seqId) + } } - private async gmtMonster(context: PacketContext, id: number, count: number, lvl: number, x?: number, y?: number, z?: number) { + private async gmtMonster(context: PacketContext, id: number, count: number, lvl?: number, pose?: number, x?: number, y?: number, z?: number) { const { player } = context const { currentScene, pos: playerPos } = player const { entityManager } = currentScene @@ -42,12 +48,13 @@ class GmTalkPacket extends Packet implements PacketInterface { const pos = (x == null || y == null || z == null) ? playerPos.clone() : new Vector(x, y, z) for (let i = 0; i < count; i++) { + logger.info(`[${player.uid}]Summon monster(id: ${id}, lvl: ${lvl}) in [${x}, ${y}, ${z}].`) const entity = new Monster(id, player) entity.motion.pos.copy(pos) entity.bornPos.copy(pos) - await entity.initNew(lvl) + await entity.initNew(lvl || 1) await entityManager.add(entity) } } @@ -61,6 +68,7 @@ class GmTalkPacket extends Packet implements PacketInterface { switch (type) { // NOSONAR case 'MONSTER': entityType = ProtEntityTypeEnum.PROT_ENTITY_MONSTER + logger.info(`[${player.uid}]Killing all monsters.`) break default: return @@ -85,23 +93,27 @@ class GmTalkPacket extends Packet implements PacketInterface { private async gmtGod(context: PacketContext, enable: boolean, type?: string) { const { player } = context if (type == null) player.godMode = enable + logger.info(`[${player.uid}]GodMode set to ${enable}.`) } - // new private async gmtTp(context: PacketContext, x: number, y: number, z: number) { const { player } = context const { currentScene } = player + logger.info(`[${player.uid}]Teleporting to [${x}, ${y}, ${z}].`) currentScene.join(context, new Vector(x, y, z), new Vector(), SceneEnterTypeEnum.ENTER_GOTO, SceneEnterReasonEnum.TRANS_POINT) } private async gmtScoin(context: PacketContext, amount: number) { const { player } = context player.addMora(amount, true) + logger.info(`[${player.uid}]Add scoin(amount: ${amount}).`) } private async gmtGadget(context: PacketContext, id: number, lv: number) { const { player } = context const gadget = new Gadget(id) + logger.info(`[${player.uid}]Summon gadget(id: ${id}, lvl: ${lv}) in [${player.pos.x}, ${player.pos.y}, ${player.pos.z}].`) + gadget.motion.pos.copy(player.pos) gadget.bornPos.copy(player.pos) @@ -127,6 +139,7 @@ class GmTalkPacket extends Packet implements PacketInterface { const scene = await player.currentWorld.getScene(id) const { pos, rot } = await this.getSceneData(id) + logger.info(`[${player.uid}]Teleporting to [${pos}] in scene ${id}.`) scene.join(context, pos, rot, SceneEnterTypeEnum.ENTER_JUMP, SceneEnterReasonEnum.TRANS_POINT) } @@ -137,9 +150,23 @@ class GmTalkPacket extends Packet implements PacketInterface { const scene = await player.currentWorld.getScene(sceneId) const { pos, rot } = await this.getSceneData(sceneId) + logger.info(`[${player.uid}]Teleporting to [${pos}] in dungeon ${id}(scene ${sceneId}).`) + scene.join(context, pos, rot, SceneEnterTypeEnum.ENTER_DUNGEON, SceneEnterReasonEnum.DUNGEON_ENTER) } + private async gmtLevel(context: PacketContext, level: number) { + const { player } = context + player.currentAvatar.level = level + logger.info(`[${player.uid}]Current avatar level set to ${level}.`) + } + + private async gmtBreak(context: PacketContext, promoteLevel: number) { + const { player } = context + player.currentAvatar.promoteLevel = promoteLevel + logger.info(`[${player.uid}]Current avatar promote level set to ${promoteLevel}.`) + } + async request(context: PacketContext, data: GmTalkReq): Promise { const { msg } = data const cmd = msg?.split(' ')?.[0]?.toLowerCase() @@ -175,8 +202,14 @@ class GmTalkPacket extends Packet implements PacketInterface { case 'dungeon': await this.gmtDungeon(context, Number(args[0])) break + case 'level': + await this.gmtLevel(context, Number(args[0])) + break + case 'break': + await this.gmtBreak(context, Number(args[0])) + break default: - logger.warn(`Unsupported GM command: ${msg}`) + logger.warn(`[${context.player.uid}]Unsupported GM command: ${msg}`) await this.response(context, { retcode: RetcodeEnum.RET_UNKNOWN_ERROR }) return } diff --git a/src/translate/data/en-us.json b/src/translate/data/en-us.json index ac68eef4..d1e6691d 100644 --- a/src/translate/data/en-us.json +++ b/src/translate/data/en-us.json @@ -683,6 +683,32 @@ "heal": "Healed all avatar." } }, + "level": { + "desc": "Set current avatar level", + "usage": { + "0": "level - Set player's current avatar level", + "1": "level - (In game) Set current avatar level" + }, + "info": { + "level": "Avatar(id: %0) level set to %1." + }, + "error": { + "invalidLv": "Invalid level: %0, it should be 1~90." + } + }, + "promote": { + "desc": "Set current avatar promote level", + "usage": { + "0": "promote - Set player's current avatar promote level", + "1": "promote - (In game) Set current avatar promote level" + }, + "info": { + "promote": "Avatar(id: %0) promote level set to %1." + }, + "error": { + "invalidpromotion": "Invalid promote level: %0, it should be 0~6." + } + }, "recharge": { "desc": "Recharge all avatar in current team", "usage": { diff --git a/src/translate/data/zh-cn.json b/src/translate/data/zh-cn.json index 89bb2129..e1b05262 100644 --- a/src/translate/data/zh-cn.json +++ b/src/translate/data/zh-cn.json @@ -345,7 +345,7 @@ "debug": { "ability": "成功缓存能力数据", "avatar": "成功缓存头像数据", - "dungeon": "成功缓存地下城数据", + "dungeon": "成功缓存秘境数据", "gadget": "成功缓存小工具数据", "growCurve": "成功缓存成长曲线数据", "mapArea": "成功缓存地图区域数据", @@ -682,6 +682,32 @@ "heal": "已治疗所有角色." } }, + "level": { + "desc": "设置当前角色的等级", + "usage": { + "0": "level - 设置玩家当前角色的等级", + "1": "level - (游戏中)设置当前角色的等级" + }, + "info": { + "level": "角色(id: %0) 等级设置为 %1." + }, + "error": { + "invalidLv": "无效的等级: %0, 应当为1~90的任意整数." + } + }, + "promote": { + "desc": "设置当前角色的突破等级", + "usage": { + "0": "promote - 设置玩家当前角色的突破等级", + "1": "promote - (游戏中)设置当前角色的突破等级" + }, + "info": { + "promote": "角色(id: %0) 突破等级设置为 %1." + }, + "error": { + "invalidpromotion": "无效的突破等级: %0, 应当为0~6的任意整数." + } + }, "recharge": { "desc": "为当前团队中的所有角色充能", "usage": { diff --git a/src/translate/data/zh-tw.json b/src/translate/data/zh-tw.json index 97dd05a1..ba32fd4a 100644 --- a/src/translate/data/zh-tw.json +++ b/src/translate/data/zh-tw.json @@ -345,7 +345,7 @@ "debug": { "ability": "成功緩存能力數據", "avatar": "成功緩存頭像數據", - "dungeon": "成功緩存地下城數據", + "dungeon": "成功緩存秘境數據", "gadget": "成功緩存小工具數據", "growCurve": "成功緩存成長曲線數據", "mapArea": "成功緩存地圖區域數據", @@ -682,6 +682,32 @@ "heal": "已治療所有角色." } }, + "level": { + "desc": "設置當前角色的等級", + "usage": { + "0": "level - 設置玩家當前角色的等級", + "1": "level - (遊戲中)設置當前角色的等級" + }, + "info": { + "level": "角色(id: %0) 等級設置為 %1." + }, + "error": { + "invalidLv": "無效的等級: %0, 應當為1~90的任意整數." + } + }, + "promote": { + "desc": "設置當前角色的突破等級", + "usage": { + "0": "promote - 设置玩家当前角色的突破等级", + "1": "promote - (遊戲中)設置當前角色的突破等級" + }, + "info": { + "promote": "角色(id: %0) 突破等級設置為 %1." + }, + "error": { + "invalidpromotion": "無效的突破等級: %0, 應當為0~6的任意整數." + } + }, "recharge": { "desc": "為當前團隊中的所有角色充能", "usage": {