从零开始手写mmo游戏从框架到爆炸(十五)— 命令行客户端改造
导航:从零开始手写mmo游戏从框架到爆炸(零)—— 导航-CSDN博客
到现在,我们切实需要一个客户端来完整的进行英雄选择,选择地图,打怪等等功能。所以我们需要把之前极为简陋的客户端改造一下。
首先再common模块中增加打印颜色的工具类:ConsoleColors
package com.loveprogrammer.utils;/**** @version 1.0.0* @description:* @author: eric* @date: 2024-02-18 09:41**/
public class ConsoleColors {// Resetpublic static final String RESET = "\033[0m"; // Text Reset// Regular Colorspublic static final String BLACK = "\033[0;30m"; // BLACKpublic static final String RED = "\033[0;31m"; // REDpublic static final String GREEN = "\033[0;32m"; // GREENpublic static final String YELLOW = "\033[0;33m"; // YELLOWpublic static final String BLUE = "\033[0;34m"; // BLUEpublic static final String PURPLE = "\033[0;35m"; // PURPLEpublic static final String CYAN = "\033[0;36m"; // CYANpublic static final String WHITE = "\033[0;37m"; // WHITE// Boldpublic static final String BLACK_BOLD = "\033[1;30m"; // BLACKpublic static final String RED_BOLD = "\033[1;31m"; // REDpublic static final String GREEN_BOLD = "\033[1;32m"; // GREENpublic static final String YELLOW_BOLD = "\033[1;33m"; // YELLOWpublic static final String BLUE_BOLD = "\033[1;34m"; // BLUEpublic static final String PURPLE_BOLD = "\033[1;35m"; // PURPLEpublic static final String CYAN_BOLD = "\033[1;36m"; // CYANpublic static final String WHITE_BOLD = "\033[1;37m"; // WHITE// Underlinepublic static final String BLACK_UNDERLINED = "\033[4;30m"; // BLACKpublic static final String RED_UNDERLINED = "\033[4;31m"; // REDpublic static final String GREEN_UNDERLINED = "\033[4;32m"; // GREENpublic static final String YELLOW_UNDERLINED = "\033[4;33m"; // YELLOWpublic static final String BLUE_UNDERLINED = "\033[4;34m"; // BLUEpublic static final String PURPLE_UNDERLINED = "\033[4;35m"; // PURPLEpublic static final String CYAN_UNDERLINED = "\033[4;36m"; // CYANpublic static final String WHITE_UNDERLINED = "\033[4;37m"; // WHITE// Backgroundpublic static final String BLACK_BACKGROUND = "\033[40m"; // BLACKpublic static final String RED_BACKGROUND = "\033[41m"; // REDpublic static final String GREEN_BACKGROUND = "\033[42m"; // GREENpublic static final String YELLOW_BACKGROUND = "\033[43m"; // YELLOWpublic static final String BLUE_BACKGROUND = "\033[44m"; // BLUEpublic static final String PURPLE_BACKGROUND = "\033[45m"; // PURPLEpublic static final String CYAN_BACKGROUND = "\033[46m"; // CYANpublic static final String WHITE_BACKGROUND = "\033[47m"; // WHITE// High Intensitypublic static final String BLACK_BRIGHT = "\033[0;90m"; // BLACKpublic static final String RED_BRIGHT = "\033[0;91m"; // REDpublic static final String GREEN_BRIGHT = "\033[0;92m"; // GREENpublic static final String YELLOW_BRIGHT = "\033[0;93m"; // YELLOWpublic static final String BLUE_BRIGHT = "\033[0;94m"; // BLUEpublic static final String PURPLE_BRIGHT = "\033[0;95m"; // PURPLEpublic static final String CYAN_BRIGHT = "\033[0;96m"; // CYANpublic static final String WHITE_BRIGHT = "\033[0;97m"; // WHITE// Bold High Intensitypublic static final String BLACK_BOLD_BRIGHT = "\033[1;90m"; // BLACKpublic static final String RED_BOLD_BRIGHT = "\033[1;91m"; // REDpublic static final String GREEN_BOLD_BRIGHT = "\033[1;92m"; // GREENpublic static final String YELLOW_BOLD_BRIGHT = "\033[1;93m";// YELLOWpublic static final String BLUE_BOLD_BRIGHT = "\033[1;94m"; // BLUEpublic static final String PURPLE_BOLD_BRIGHT = "\033[1;95m";// PURPLEpublic static final String CYAN_BOLD_BRIGHT = "\033[1;96m"; // CYANpublic static final String WHITE_BOLD_BRIGHT = "\033[1;97m"; // WHITE// High Intensity backgroundspublic static final String BLACK_BACKGROUND_BRIGHT = "\033[0;100m";// BLACKpublic static final String RED_BACKGROUND_BRIGHT = "\033[0;101m";// REDpublic static final String GREEN_BACKGROUND_BRIGHT = "\033[0;102m";// GREENpublic static final String YELLOW_BACKGROUND_BRIGHT = "\033[0;103m";// YELLOWpublic static final String BLUE_BACKGROUND_BRIGHT = "\033[0;104m";// BLUEpublic static final String PURPLE_BACKGROUND_BRIGHT = "\033[0;105m"; // PURPLEpublic static final String CYAN_BACKGROUND_BRIGHT = "\033[0;106m"; // CYANpublic static final String WHITE_BACKGROUND_BRIGHT = "\033[0;107m"; // WHITEpublic static void main(String[] args) {System.out.println(ConsoleColors.RED_BOLD_BRIGHT + "肩甲");System.out.println(ConsoleColors.RED_BOLD + "肩甲");}
}
增加统一打印工具类:ConsolePrint
package com.loveprogrammer.console;import com.alibaba.fastjson2.util.DateUtils;import java.util.Date;/*** @version 1.0.0* @description: 输出类* @author: eric* @date: 2024-02-18 16:55**/
public class ConsolePrint {private static final String space = "\t\t\t\t\t\t\t\t";public static void publishMessage(String content,int position) {String format = DateUtils.format(new Date(),DateUtils.DateTimeFormatPattern.DATE_TIME_FORMAT_19_DASH.pattern);String threadName = Thread.currentThread().getName();if(position == 0) {System.out.print(content);}else if(position == 1) {System.out.println(content);}else {System.out.println(space + content);}}public static void publishMessage(String content) {System.out.println(content);}public static void publishMessagePrint(String content,String placeholder) {System.out.print(content + placeholder);}
}
修改command模块的结构,把tag根据不同的topic拆分到不同的类中,方便维护。

之前的客户端就是简单的nettyclient,但是现在客户端也要解析topic和tag,所以我们根据server来改造客户端。大致结构如下:

客户端的监听类- NetworkClientListener
package com.loveprogrammer.network;import com.alibaba.fastjson2.JSON;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.loveprogrammer.command.anotation.TagListener;
import com.loveprogrammer.command.client.ClientMenuTag;
import com.loveprogrammer.command.client.ClientTopic;
import com.loveprogrammer.handler.HandlerFactory;
import com.loveprogrammer.listener.INetworkEventListener;
import com.loveprogrammer.pojo.StringMessage;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.lang.reflect.Method;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class NetworkClientListener implements INetworkEventListener {protected static final Logger logger = LoggerFactory.getLogger(NetworkClientListener.class);private NetworkClientListener(){}private static final NetworkClientListener instance = new NetworkClientListener();public static NetworkClientListener getInstance(){return instance;}private final ThreadPoolExecutor executor = new ThreadPoolExecutor(2,2,0L,TimeUnit.SECONDS,new LinkedBlockingQueue<>(1024),new ThreadFactoryBuilder().setNameFormat("worker-pool-%d").build(),new ThreadPoolExecutor.CallerRunsPolicy());/**** 同客户端转发* @param ctx* @param topic* @param tag* @param msg*/public void forward(ChannelHandlerContext ctx, int topic, int tag, String msg) {StringMessage data = new StringMessage();data.setTopicId(topic);data.setTagId(tag);data.setBody(msg);channelRead(ctx,data);}@Overridepublic void onConnected(ChannelHandlerContext ctx) {}@Overridepublic void onDisconnected(ChannelHandlerContext ctx) {}@Overridepublic void onExceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {}@Overridepublic void channelRead(ChannelHandlerContext ctx, StringMessage msg) {int topicId = msg.getTopicId();int tagId = msg.getTagId();Object handler = HandlerFactory.handlerMap.get(topicId);if (handler == null) {logger.warn("未获取到指定的消息监听对象,topicId {}", topicId);return;}String bodyValue = msg.getBody();executor.execute(() -> {try {Class<?> handlerClass = handler.getClass();// 找到tag 遍历methodsMethod[] methods = handlerClass.getMethods();for (Method method : methods) {TagListener mqListener = method.getAnnotation(TagListener.class);if (tagId == mqListener.tag()) {Class<?> aClass = mqListener.messageClass();String name = aClass.getName();// 先处理基本类型if ("java.lang.String".equals(name)) {method.invoke(handler, ctx, bodyValue);} else if ("java.lang.Long".equals(name)) {Long object = Long.parseLong(bodyValue);method.invoke(handler, ctx, object);} else if ("java.lang.Integer".equals(name)) {Integer object = Integer.parseInt(bodyValue);method.invoke(handler, ctx, object);} else if ("java.lang.Short".equals(name)) {Short object = Short.parseShort(bodyValue);method.invoke(handler, ctx, object);} else if ("java.lang.Byte".equals(name)) {Byte object = Byte.parseByte(bodyValue);method.invoke(handler, ctx, object);} else if ("java.lang.Double".equals(name)) {Double object = Double.parseDouble(bodyValue);method.invoke(handler, ctx, object);} else if ("java.lang.Float".equals(name)) {Float object = Float.parseFloat(bodyValue);method.invoke(handler, ctx, object);}// 转对象类型else {Object object = JSON.parseObject(bodyValue, aClass);method.invoke(handler, ctx, object);}break;}}} catch (Exception e) {logger.error("发生异常", e);// 转发到首页forward(ctx, ClientTopic.TOPIC_MENU, ClientMenuTag.TAG_MENU_PORTAL, msg.getBody());}});}
}
客户端菜单监听- MenuHandler
package com.loveprogrammer.handler.support;import com.loveprogrammer.command.IHandler;
import com.loveprogrammer.command.anotation.TagListener;
import com.loveprogrammer.command.anotation.TopicListener;
import com.loveprogrammer.command.client.ClientTopic;
import com.loveprogrammer.command.client.ClientMenuTag;
import com.loveprogrammer.console.ConsolePrint;
import com.loveprogrammer.network.NetworkClientListener;
import com.loveprogrammer.utils.ScannerInput;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** @ClassName MenuHandler* @Description TODO* @Author admin* @Date 2024/2/18 17:37* @Version 1.0*/
@TopicListener(topic = ClientTopic.TOPIC_MENU)
public class MenuHandler implements IHandler {public static final Logger log = LoggerFactory.getLogger(MenuHandler.class);@TagListener(tag = ClientMenuTag.TAG_MENU_PORTAL,messageClass = String.class)public void portalMenu(ChannelHandlerContext ctx, String msg){// 展示首页数据ConsolePrint.publishMessage("请选择您要进行的操作");ConsolePrint.publishMessage("【1.打怪】 【2.装备】 【3.战兽】");ConsolePrint.publishMessage("【4.冒险者工会】 【5.副本】 【6.工会】 ");ConsolePrint.publishMessage("【8.配置】 【9.退出】");ConsolePrint.publishMessage("请选择:");int choose = ScannerInput.inputInt(1, 9, 9);while (choose != 9) {switch (choose) {case 1:case 2:case 3:case 4:case 5:case 6:case 7:case 8:default:ConsolePrint.publishMessage("暂未开放,敬请期待", 1);break;}ConsolePrint.publishMessage("请选择您要进行的操作");ConsolePrint.publishMessage("【1.打怪】 【2.装备】 【3.战兽】");ConsolePrint.publishMessage("【4.冒险者工会】 【5.副本】 【6.工会】 ");ConsolePrint.publishMessage("【8.配置】 【9.退出】");ConsolePrint.publishMessage("请选择:");choose = ScannerInput.inputInt(1, 9, 9);}// 这里不退出,而是返回首页,做一个重定向NetworkClientListener.getInstance().forward(ctx,ClientTopic.TOPIC_MENU, ClientMenuTag.TAG_MENU_PORTAL,msg);}
}
剩余的改动这里就不一一赘述了,大家可以根据代码来看下调整的地方。本章对结构调整的有点大,会单独新增一个tag方便大家对比。
客户端运行后效果如下:
10:06:15.602 [nioEventLoopGroup-2-1] [INFO ] com.loveprogrammer.handler.HandlerFactory:32 --- 初始化消息监听器成功 com.loveprogrammer.handler.support.MenuHandler
请选择您要进行的操作
【1.打怪】 【2.装备】 【3.战兽】
【4.冒险者工会】 【5.副本】 【6.工会】
【8.配置】 【9.退出】
请选择:
1
暂未开放,敬请期待
请选择您要进行的操作
【1.打怪】 【2.装备】 【3.战兽】
【4.冒险者工会】 【5.副本】 【6.工会】
【8.配置】 【9.退出】
请选择:
全部源码详见:
gitee : eternity-online: 多人在线mmo游戏 - Gitee.com
分支:step-09
请各位帅哥靓女帮忙去gitee上点个星星,谢谢!
相关文章:
从零开始手写mmo游戏从框架到爆炸(十五)— 命令行客户端改造
导航:从零开始手写mmo游戏从框架到爆炸(零)—— 导航-CSDN博客 到现在,我们切实需要一个客户端来完整的进行英雄选择,选择地图,打怪等等功能。所以我们需要把之前极为简陋的客户端改造一下。 首先…...
Elasticsearch:什么是 kNN?
kNN - K-nearest neighbor 定义 kNN(即 k 最近邻算法)是一种机器学习算法,它使用邻近度将一个数据点与其训练并记忆的一组数据进行比较以进行预测。 这种基于实例的学习为 kNN 提供了 “惰性学习(lazy learning)” 名…...
掌握网络未来:深入解析RSVP协议及其在确保服务质量中的关键作用
第一部分:RSVP简介 资源预留协议(RSVP)是一种网络协议,用于在网络中的各个节点之间预留资源,以支持数据流的服务质量(QoS)要求。RSVP特别适用于需要固定带宽和处理延迟的应用,如视频…...
【Linux】一站式教会:Ubuntu(无UI界面)使用apache-jmeter进行压测
🏡浩泽学编程:个人主页 🔥 推荐专栏:《深入浅出SpringBoot》《java对AI的调用开发》 《RabbitMQ》《Spring》《SpringMVC》 🛸学无止境,不骄不躁,知行合一 文章目录 前言一、Java…...
Howler.js:音频处理的轻量级解决方案
文章目录 Howler.js:音频处理的轻量级解决方案引言一、Howler.js简介1.1 特性概览 二、Howler.js基本使用使用详解2.1 创建一个Howl对象2.2 控制音频播放2.3 监听音频事件 三、进阶功能3.1 音频Sprites3.2 3D音频定位 四、微前端场景下的Howler.js Howler.js&#x…...
【讨论】Web端测试和App端测试的不同,如何说得更有新意?
Web 端测试和 App 端测试是针对不同平台的上的应用进行测试,Web应用和App端的应用实现方式不同,测试时的侧重点也不一样。 Web端应用和App端应用的区别: 平台兼容性 安装方式 功能和性能 用户体验 更新和维护 测试侧重点有何不同 平台…...
运维SRE-18 自动化批量管理-ansible4
12.2handles handles触发器(条件),满足条件后再做什么事情应用场景:想表示:配置文件变化,再重启服务 配置handlers之前,每次运行剧本都会重启nfs,无论配置文件是否变化。 [rootm01 /server/ans/playbook]…...
编程笔记 Golang基础 008 基本语法规则
编程笔记 Golang基础 008 基本语法规则 Go语言的基本语法规则. Go语言的基本语法规则包括但不限于以下要点: 标识符: 标识符用于命名变量、常量、类型、函数、包等。标识符由字母(a-z,A-Z)、数字(0-9&#…...
android input命令支持多指触摸成果展示-千里马framework实战开发
hi input命令扩展提示部分 generic_x86_64:/ # input -h Error: Unknown command: -h Usage: input [<source>] <command> [<arg>...]The source…...
Stable Diffusion 模型分享:Indigo Furry mix(人类与野兽的混合)
本文收录于《AI绘画从入门到精通》专栏,专栏总目录:点这里。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八案例九案例十...
OpenAI Sora引领AI跳舞视频新浪潮:字节跳动发布创新舞蹈视频生成框架
OpenAI的Sora已经引起广泛关注,预计今年AI跳舞视频将在抖音平台上大放异彩。下面将为您详细介绍一款字节跳动发布的AI视频动画框架。 技术定位:这款框架采用先进的diffusion技术,专注于生成人类舞蹈视频。它不仅能够实现人体动作和表情的迁移…...
[深度学习] 卷积神经网络“卷“在哪里?
🌈 博客个人主页:Chris在Coding 🎥 本文所属专栏:[深度学习] ❤️ 热门学习专栏:[Linux学习] ⏰ 我们仍在旅途 目录 1.卷积的定义 2.卷积的"卷"在哪里 3.什么又是卷积神…...
企业网络安全自查:总结报告与改进指南
按照网络和数据安全监督检查工作的要求, 现将网络信息安全自查阶段有关情况总结如下: 一、自查工作的组织开展情况 我单位始终高度重视网络与信息安全自查工作, 成立专项管理组织机构,深入学习贯彻相关文件精神,严格…...
怎么理解ping?这是我听过最好的回答
晚上好,我是老杨。 Ping这几个字母,已经深入网工人的骨髓了吧? 把Ping用到工作里,肯定不少人在用,但对Ping的了解和理解是不是足够深,取决了你能在工作里用到什么程度,能让它帮你到什么地步。…...
用户请求到响应可能存在的五级缓存
用户请求到响应可能存在的五级缓存 当用户在浏览器中输入URL进行访问时,请求并不是直接达到服务器,而是会经历多级缓存,以提高网络效率。本文将详细介绍用户请求到响应可能会经历的五个缓存级别:浏览器缓存,代理缓存&…...
云图极速版限时免费活动
产品介绍 云图极速版是针对拥有攻击面管理需求的用户打造的 SaaS 应用,致力于协助用户发现并管理互联网资产攻击面。 实战数据 (2023.11.6 - 2024.2.23) 云图极速版上线 3 个月以来,接入用户 3,563 家,扫描主体 19,961 个,累计发…...
vue3 vuex
目录 Vuex 是什么 什么是“状态管理模式”? 什么情况下我应该使用 Vuex? 使用方法: 提交载荷(Payload) 对象风格的提交方式 使用常量替代 Mutation 事件类型 Mutation 必须是同步函数 在组件中提交 Mutation …...
Java架构师之路三、网络通信:TCP/IP协议、HTTP协议、RESTful API、WebSocket、RPC等。
目录 TCP/IP协议: HTTP协议: RESTful API: WebSocket: RPC: UDP: HTTPS: 上篇:Java架构师之路二、数据库:SQL语言、关系型数据库、非关系型数据库、数据一致性、事…...
【C++】笔试训练(九)
目录 一、选择题二、编程题1、另类加法2、走方格的方案数 一、选择题 1、某函数申明如下 void Func(int& nVal1);有int a,下面使用正确的为() A Func(a) B Func(&a) C Func(*a) D Func(&(*a)) 答案:A 2、C语言中,类…...
模板注入 [BJDCTF2020]Cookie is so stable1
打开题目 有flag页面,有Hint页面 这里题目有提示,突破口是在cookie上面 经过测试发现有ssti注入: 抓包 判断模板注入类型的办法 输入 {{7*‘7’}} 回显49 输入{{7*‘7’}},返回49表示是 Twig 模块 输入{{7*‘7’}}࿰…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...
《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
HBuilderX安装(uni-app和小程序开发)
下载HBuilderX 访问官方网站:https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本: Windows版(推荐下载标准版) Windows系统安装步骤 运行安装程序: 双击下载的.exe安装文件 如果出现安全提示&…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...
自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
