同步官网文档8m_25d
46
mconline/30-网络服插件教程/1-准备知识/0-插件的概念.md
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
front:
|
||||
hard: 入门
|
||||
time: 5分钟
|
||||
selection: true
|
||||
---
|
||||
|
||||
# 插件的概念
|
||||
|
||||
插件的本质其实和中国版Addon非常相似。
|
||||
|
||||
但是又和Addon略有不同。
|
||||
|
||||
例如下方就是一个插件的目录结构,可以自行和Addon进行对比。该结构在后面的章节会详细介绍,本处仅作了解。
|
||||
|
||||
```
|
||||
tutorialApolloMod
|
||||
behavior_packs
|
||||
tutorialBehavior
|
||||
developer_mods
|
||||
tutorialDev
|
||||
tutorialScripts
|
||||
resource_packs
|
||||
tutorialResource
|
||||
worlds
|
||||
level
|
||||
world_behavior_packs.json
|
||||
world_resource_packs.json
|
||||
```
|
||||
|
||||
插件会将developer_mods中的代码在服务器上运行,将behavior_packs中的内容发送到客户端去,并于服务器进行双向通信。
|
||||
|
||||
这样做可以防止代码遭到逆向工程爆破,从而保护了服务端插件的代码。
|
||||
|
||||
并且插件也可以相互调用,互相依赖,将服务器玩法进行模块化拆分。
|
||||
|
||||
## 为什么采用插件
|
||||
|
||||
这么做的目的是——**效率**。
|
||||
|
||||
将服务器需要用到的一个个小功能做成一个个插件。例如将领地保护功能、权限管理功能、箱子锁功能······纷纷做成一个个的小插件。这些功能虽然不能为服务器带来核心玩法,但是却是一个我的世界网络服的基石。编写插件,就可以让这些基础功能在每一个服务器上都能够安装,管理,减少重复开发。
|
||||
|
||||
同时一个个的小插件,正对应着模块化设计的理念。
|
||||
|
||||
哪个模块出现问题,需要改进,就可以轻松定位,轻松修复。大大提升了开发效率。
|
||||
|
||||
15
mconline/30-网络服插件教程/1-准备知识/1-必备知识.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# 必备知识
|
||||
如果掌握了下方任意一种开发技能,那么掌握Apollo插件开发将会事半功倍。
|
||||
|
||||
## Java服插件开发基础
|
||||
|
||||
Java服插件开发,即使用Java或Kotlin等jvm语言,使用Spigot等类似服务端,进行玩法开发。
|
||||
|
||||
虽然中国版Apollo插件的开发是使用Python进行开发,但是相信在掌握了jvm语言的前提下,会更容易上手,并且更容易理解Apollo插件的开发逻辑。
|
||||
|
||||
## 中国版Mod开发
|
||||
|
||||
如果已经掌握中国版Mod开发,那么学习Apollo插件的开发,将会更加轻松。因为Apollo插件的开发就是在ModSDK的基础上,新增了一系列服务器类型的概念,和专用的接口,并且提供了高性能的数据库后端,供数据存取。关于Apollo框架的结构,将会在后面的章节讲到。
|
||||
|
||||
对于已经会编写中国版Mod的开发者来说,学习Apollo插件编写,需要将更多的精力放在了解服务器插件的工作流程上。
|
||||
|
||||
42
mconline/30-网络服插件教程/1-准备知识/2-网络服的概念.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# 网络服的概念
|
||||
|
||||
## 简介
|
||||
|
||||
网络服,即运行在远程服务器上的一个游戏服务器,通过网络供玩家连接。
|
||||
|
||||
每个网络游戏都需要一个专用的服务器来进行游戏内部数据的计算,并将运算结果发送给客户端,呈现给玩家。
|
||||
|
||||
下面介绍的是一些经典的网络服软件,供读者了解。
|
||||
|
||||
### Minecraft Server
|
||||
|
||||
Mojang官方编写的我的世界Java版服务器软件。
|
||||
|
||||
### Spigot
|
||||
|
||||
一个使用Java编写的我的世界Java版服务器软件。
|
||||
|
||||
### Bedrock Server
|
||||
|
||||
Mojang官方编写的我的世界基岩版服务器软件。
|
||||
|
||||
### Nukkit
|
||||
|
||||
一个由国人使用Java编写的我的世界基岩版服务器软件。
|
||||
|
||||
## 网络服和Mod的区别
|
||||
|
||||
### 架构差异
|
||||
|
||||
我的世界的联机功能和单人游戏功能,其实都是在房主的终端下新建了一个内部服务器,再使用客户端部分进行游戏画面的渲染。Mod的服务器运算部分,就是运行在房主的终端上的。
|
||||
|
||||
而网络服软件,专门将游戏的服务器运算部分,从客户端中分离出来,并将其运行在一个相对于玩家终端来说更加高性能的服务器上,来实现更高的玩家承载量和稳定性。
|
||||
|
||||
换而言之,在网络服上游玩的玩家的数据,全部会交给专业服务器进行处理。而Mod联机的情况下,所有玩家的数据,都会在房主的设备上进行处理,如果设备的算力不够,就会非常影响游戏体验。
|
||||
|
||||
### 数据存储
|
||||
|
||||
在Mod联机的情况下,产生的数据大多都会存储到地图文件中,并由读取地图时一并加载到游戏中。
|
||||
|
||||
而在网络服中,可以使用高性能的数据库(例如MySQL,MongoDB,Redis)来存储数据,并快速筛选出需要的数据进行缓存。数据库软件各有各的优势,开发者可以根据需要自行决定数据的存储位置。
|
||||
|
||||
90
mconline/30-网络服插件教程/1-准备知识/3-数据库的概念.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# 数据库的概念
|
||||
|
||||
在服务器插件的开发中,大多数数据都会被存储到数据库。
|
||||
|
||||
## 简介
|
||||
|
||||
数据库在很多服务器软件的开发中,它起到了非常重要的数据存储和筛选的作用,在Apollo插件的开发中也不例外。
|
||||
|
||||
Apollo目前支持3种数据库,MySQL、Redis和MongoDB它们各有各的特点,下面将进行依次介绍。
|
||||
|
||||
### MySQL
|
||||
|
||||
MySQL是一种关系型数据库,它的数据存储格式和我们常用的excel非常相似。
|
||||
|
||||
每个MySQL的数据表中,都有提前设定好的列,用来代表这一列的数据的类型和名称。各个列的数据组合,叫做行。位于同一行的数据可以一同被选择。
|
||||
|
||||
为了更好的理解,下面举一个Excel和MySQL对比的例子。
|
||||
|
||||

|
||||
|
||||
如上图所示,这个是一个Excel表格,其中需要记录4种数据,分别是**玩家UID,玩家名,上次登录时间,注册时间**。这些就是这个数据表的列。
|
||||
|
||||
我们需要将每一个玩家都记录到这个表中,那么一个玩家的所有数据就占一行。
|
||||
|
||||
表现在MySQL数据表中,就如下图所示。
|
||||
|
||||
<img src="./images/mysql-2.png" style="zoom:150%;" />
|
||||
|
||||
在实际应用中,我们就可以通过uid查找到玩家的注册时间(第一次登陆时间),上次登录时间和玩家名。
|
||||
|
||||
### Redis
|
||||
|
||||
Redis是一款既可以基于内存,也可以持久化的Key-Value型数据库。
|
||||
|
||||
此数据库在Apollo插件开发中,主要用来存储一些经常需要改变的值。
|
||||
|
||||
操作过程非常像Python中的dict,此处不再举例说明。
|
||||
|
||||
例如玩家的在线统计——一个玩家进入服务器时,就需要操作数据库,修改总在线+1。同理,退出时,需要操作数据库,修改总在线-1。
|
||||
|
||||
### MongoDB
|
||||
|
||||
MongoDB是一款介于关系数据库和非关系数据库之间的数据库。
|
||||
|
||||
它可以轻松地将Python中常用的dict或json对象存储到数据库中,并很方便地对其中某个参数进行筛选和查询。
|
||||
|
||||
还是以上方的玩家信息为例,假如一个玩家的json对象是```{"uid":1,"name":"玩家1","reg_date":123,"last_login":123}```
|
||||
|
||||
那么将其插入到数据库中,即可通过```{"uid":1}```这样的条件,来查询到玩家的整条json信息。
|
||||
|
||||
|
||||
|
||||
开发者可以根据自己的习惯和实际用途,来选择使用不同的数据库。
|
||||
|
||||
|
||||
|
||||
## 前端工具
|
||||
|
||||
这里将以Navicat为例,给大家演示如何使用连接到服务器的MySQL数据库。其他数据库连接同理。
|
||||
|
||||
Navicat官网链接: https://www.navicat.com.cn/
|
||||
|
||||
### 视频教程
|
||||
|
||||
<iframe src="https://cc.163.com/act/m/daily/iframeplayer/?id=6181002648e2749089200cbe" height="600" width="800" allow="fullscreen" />
|
||||
|
||||
### 准备内容
|
||||
|
||||
- 支持MySQL的Navicat数据库管理软件
|
||||
- 用于机器连接的RSA密钥
|
||||
- 确保当前IP在服务器白名单内
|
||||
|
||||
### 操作步骤
|
||||
|
||||
1. 首先,打开Navicat软件,点击左上角的连接按钮,选择MySQL。
|
||||
|
||||

|
||||
|
||||
2. 在“常规”标签页下,输入连接名(任意,用于区分连接),用户名 *minecraft*,密码 *minecraft* (以上均为默认密码)。
|
||||
|
||||

|
||||
|
||||
3. 在“SSH”标签页下,勾选使用SSH隧道,填写主机,端口*32200*,用户名*fuzhu*,验证方法*公钥*,选择私钥路径。通行短语输入你创建RSA密钥时输入的密码。勾选保存通行短语并保存。
|
||||
|
||||

|
||||
|
||||
4. 随后可在左边区域看到你新建的连接,双击即可进行连接。
|
||||
|
||||

|
||||
|
||||
111
mconline/30-网络服插件教程/1-准备知识/4-小小云链接.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# 小小云连接
|
||||
|
||||
在阅读此教程前,首先需要安装XShell和XFtp。
|
||||
|
||||
XShell和XFtp均有免费的个人版,可在官网下载,链接: https://www.netsarang.com/zh/free-for-home-school/
|
||||
|
||||
## 小小云
|
||||
|
||||
小小云是一台云服务器,在这台云服务器上,预装了Debian操作系统和Apollo的运行环境。
|
||||
|
||||
Debian操作系统和常见的Windows操作系统不同。Debian是一种Linux系统,这种系统是为了服务器设计的,具有较高的稳定性,并且一般不安装图形操作界面,需要用户自行输入命令行命令来操作该系统。
|
||||
|
||||
下面将讲解如何申请和使用小小云。
|
||||
|
||||
## 创建密钥
|
||||
|
||||
### 视频教程
|
||||
|
||||
<iframe src="https://cc.163.com/act/m/daily/iframeplayer/?id=6180ffe7b8a81f8fa07ed443" height="600" width="800" allow="fullscreen" />
|
||||
|
||||
### 文字教程
|
||||
|
||||
在申请服务器前,你需要首先准备用于连接服务器的RSA密钥,并为其设置密码,操作步骤如下。
|
||||
|
||||
1. 打开XShell,找到菜单栏。依次点击**工具->新建用户密钥生成向导**
|
||||
|
||||
<img src="./images/rsa-1.png" style="zoom:150%;" />
|
||||
|
||||
2. 密钥类型选择RSA,长度默认2048位
|
||||
|
||||

|
||||
|
||||
3. 输入一个方便区分的密钥名称,并为密钥设置密码,此密码需要牢记,丢失后只能重新创建密钥,**无法找回**!
|
||||
|
||||

|
||||
|
||||
4. 点击完成,密钥创建完成。
|
||||
|
||||
5. 选中刚刚创建的私钥,点击导出,并在对话框中输入密码。将私钥文件存储好,方便后面Studio的配置。
|
||||
|
||||
<img src="./images/rsa-6.png" style="zoom:200%;" />
|
||||
|
||||
6. 接下来的步骤,是导出公钥并上传到开发者平台。选中刚刚创建的密钥,点击属性。
|
||||
|
||||
<img src="./images/rsa-4.png" style="zoom:200%;" />
|
||||
|
||||
7. 切换到公钥选型卡,点击保存为文件,并上传到开发者平台即可。
|
||||
|
||||
<img src="./images/rsa-5.png" style="zoom:200%;" />
|
||||
## 获取白名单网站
|
||||
|
||||
在访问开发机前,需要先获取白名单。
|
||||
|
||||
打开浏览器,输入 http://temp-white-list.mc.netease.com:9999/?machine=开发机IP地址
|
||||
|
||||
例如,如果你的开发机IP是42.186.1.1,就在浏览器中访问网址 http://temp-white-list.mc.netease.com:9999/?machine=42.186.1.1
|
||||
|
||||
访问后效果如下,稍候即可连接服务器。
|
||||
|
||||

|
||||
|
||||
## 私钥使用
|
||||
|
||||
### 视频教程
|
||||
|
||||
<iframe src="https://cc.163.com/act/m/daily/iframeplayer/?id=6180ffa948e2749089200cba" height="600" width="800" allow="fullscreen" />
|
||||
|
||||
### SSH
|
||||
|
||||
SSH连接是Linux服务器远程连接并进行管理的主要途径之一。下面将介绍使用XShell的基本使用方法,来连接到小小云。
|
||||
|
||||
1. 在XShell中点击左上角的新建按钮。
|
||||
|
||||
<img src="./images/ssh-1.png" style="zoom:150%;" />
|
||||
|
||||
2. 在弹出的界面中输入开发机地址,端口。名称仅用作区分,可任意输入。
|
||||
|
||||
<img src="./images/ssh-2.png" style="zoom:150%;" />
|
||||
|
||||
3. 在左侧的树形菜单中,找到**连接->用户身份验证**。填写用户名,方法中取消勾选**Password**,勾选并选中**Public Key**。
|
||||
|
||||
<img src="./images/ssh-3.png" style="zoom:150%;" />
|
||||
|
||||
4. 点击设置,选择之前创建的用户密钥,并输入密码。
|
||||
|
||||
<img src="./images/ssh-4.png" style="zoom:150%;" />
|
||||
|
||||
5. 点击确定,双击左侧列表中刚刚创建的连接,即可通过SSH登录到服务器。如果是第一次登陆,需要接受主机密钥,点击接受并保存即可。
|
||||
|
||||
<img src="./images/ssh-5.png" style="zoom:150%;" />
|
||||
|
||||
上方仅为XShell的操作,XFtp同理。不同的是,XShell访问的是服务器命令行控制台,XFtp访问的是服务器文件目录。
|
||||
|
||||
下图为XFtp连接后的示意。
|
||||
|
||||

|
||||
|
||||
### Studio
|
||||
|
||||
下面将介绍如何在Studio中配置私钥。
|
||||
|
||||
1. 登录Studio,在**新建**中,选择**基岩版网络服**。创建一个任意类型的服务器,本教程使用空白网络服作为示例。
|
||||
|
||||
2. 选择之前在XShell中导出的私钥文件,填写密码。
|
||||
|
||||
<img src="./images/studio-1.png" style="zoom:200%;" />
|
||||
|
||||
3. 翻到底部的**服务器设置**,在机器列表的文本框中填写开发机IP地址。如果配置正确且有白名单,**完成**按钮就会亮起,点击完成即配置完成。
|
||||
|
||||
<img src="./images/studio-2.png" style="zoom:200%;" />
|
||||
|
||||
BIN
mconline/30-网络服插件教程/1-准备知识/images/mysql-1.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
mconline/30-网络服插件教程/1-准备知识/images/mysql-2.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
mconline/30-网络服插件教程/1-准备知识/images/navicat-1.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
mconline/30-网络服插件教程/1-准备知识/images/navicat-2.png
Normal file
|
After Width: | Height: | Size: 221 KiB |
BIN
mconline/30-网络服插件教程/1-准备知识/images/navicat-3.png
Normal file
|
After Width: | Height: | Size: 283 KiB |
BIN
mconline/30-网络服插件教程/1-准备知识/images/navicat-4.png
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
mconline/30-网络服插件教程/1-准备知识/images/rsa-1.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
mconline/30-网络服插件教程/1-准备知识/images/rsa-2.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
mconline/30-网络服插件教程/1-准备知识/images/rsa-3.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
mconline/30-网络服插件教程/1-准备知识/images/rsa-4.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
mconline/30-网络服插件教程/1-准备知识/images/rsa-5.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
mconline/30-网络服插件教程/1-准备知识/images/rsa-6.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
mconline/30-网络服插件教程/1-准备知识/images/ssh-1.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
mconline/30-网络服插件教程/1-准备知识/images/ssh-2.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
mconline/30-网络服插件教程/1-准备知识/images/ssh-3.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
mconline/30-网络服插件教程/1-准备知识/images/ssh-4.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
mconline/30-网络服插件教程/1-准备知识/images/ssh-5.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
mconline/30-网络服插件教程/1-准备知识/images/ssh-6.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
mconline/30-网络服插件教程/1-准备知识/images/studio-1.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
mconline/30-网络服插件教程/1-准备知识/images/studio-2.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
mconline/30-网络服插件教程/1-准备知识/images/white-1.png
Normal file
|
After Width: | Height: | Size: 113 KiB |
37
mconline/30-网络服插件教程/2-初识插件/1-插件下载.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# 插件下载
|
||||
|
||||
本节将展示官方插件,第三方插件的使用流程,供读者了解。
|
||||
|
||||
## 下载入口
|
||||
|
||||
在基岩版网络服的插件标签页下,右上角可以看到官方插件和更多插件按钮。
|
||||
|
||||

|
||||
|
||||
目前官方插件和第三方插件涵盖了大部分的类型。包括但不限于:
|
||||
|
||||
- 聊天
|
||||
- 工具
|
||||
- 世界管理
|
||||
- 经济
|
||||
- 机制
|
||||
|
||||
未来官方会继续上传更多官方插件,并扶持、培养更多第三方开发者,扩充插件库,降低Apollo的开服门槛。
|
||||
|
||||
## 插件展示
|
||||
|
||||
### neteaseDaily
|
||||
|
||||
该插件给服务器带来了每日登录奖励的功能。
|
||||
|
||||
效果如图。
|
||||
|
||||

|
||||
|
||||
### neteaseMenus
|
||||
|
||||
该插件为服务器提供了快捷菜单功能。
|
||||
|
||||
效果如图。
|
||||
|
||||

|
||||
52
mconline/30-网络服插件教程/2-初识插件/2-应用插件.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# 应用插件
|
||||
|
||||
本节会以官方插件每日登陆奖励为例,介绍如何应用插件在Apollo框架中。
|
||||
|
||||
## 视频教程
|
||||
|
||||
<iframe src="https://cc.163.com/act/m/daily/iframeplayer/?id=618100dc75882ab49553e983" height="600" width="800" allow="fullscreen" />
|
||||
|
||||
## 文字教程
|
||||
|
||||
1. 打开Studio基岩版网络服标签。
|
||||
|
||||
2. 点击官方插件
|
||||
|
||||

|
||||
|
||||
3. 点击官方插件,找到每日登陆奖励,点击全部下载,点击后效果如图。
|
||||
|
||||
<img src="./images/deploy-2.png" style="zoom:150%;" />
|
||||
|
||||
4. 关闭官方插件界面,找到neteaseDailyService,点击更多,打开目录,复制mod.sql文件。
|
||||
|
||||

|
||||
|
||||
5. 在Navicat中连接到开发机,数据库minecraft。
|
||||
|
||||
6. 对minecraft数据库右键,点击运行sql文件。
|
||||
|
||||
<img src="./images/deploy-4.png" style="zoom:200%;" />
|
||||
|
||||
7. 选择文件,粘贴mod.sql文件,点击开始。
|
||||
|
||||
<img src="./images/deploy-5.png" style="zoom:200%;" />
|
||||
|
||||
8. 关闭界面,回到Studio。网络服开发 中,找到你创建的网络服。右键点击,选择服务器配置。
|
||||
|
||||

|
||||
|
||||
9. 游戏配置->大厅服->Mod 勾选neteaseDaily;控制服->Mod勾选neteaseDailyMaster;如果按照教程创建的空白模板,没有功能服,则新增一个功能服,Mod勾选neteaseDailyService。点击完成。
|
||||
|
||||
<img src="./images/deploy-7.png" style="zoom:150%;" />
|
||||
|
||||
10. 鼠标移到网络服上,点击部署。
|
||||
|
||||
<img src="./images/deploy-8.png" style="zoom:200%;" />
|
||||
|
||||
11. 部署完成后,点击开发测试。即可进入游戏。
|
||||
|
||||
|
||||
|
||||
体现本mod的功能需要调用接口,会放在后面讲解。也可以跟着[视频](#视频教程)操作进行尝试。效果图见[插件下载](./1-插件下载.html)。
|
||||
|
||||
30
mconline/30-网络服插件教程/2-初识插件/3-OP权限.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# OP权限
|
||||
|
||||
本节将介绍如何在Apollo中给指定玩家设置OP权限,以方便后续测试。
|
||||
|
||||
1. 对网络服右键,选择控制台调试。
|
||||
|
||||
<img src="./images/op-1.png" style="zoom: 150%;" />
|
||||
|
||||
2. 在脚本测试下,选择服务器(这里以lobby为例),输入以下代码,点击发送。
|
||||
|
||||
```python
|
||||
import mod.server.extraServerApi as serverApi
|
||||
comp = serverApi.GetEngineCompFactory().CreateGame(serverApi.GetLevelId())
|
||||
comp.SetGameRulesInfoServer({'cheat_info':{'enable': True}})
|
||||
```
|
||||
|
||||
**这么做就可以将Apollo的子服开启允许作弊,OP才能使用作弊命令。否则就算拥有OP权限,执行OP命令也会被服务器拦截。**
|
||||
|
||||

|
||||
|
||||
3. 在原生指令下,选择服务器(这里以lobby为例),输入```op 玩家名```。
|
||||
|
||||
<img src="./images/op-3.png" style="zoom:200%;" />
|
||||
|
||||
完成以上操作,即可在游戏中使用作弊命令。
|
||||
|
||||
## 注意事项
|
||||
|
||||
但是上方操作重新部署后会失效,后期可以自行制作插件,在服务器启动时执行上方代码,即可保持开启作弊模式。
|
||||
|
||||
BIN
mconline/30-网络服插件教程/2-初识插件/images/deploy-1.png
Normal file
|
After Width: | Height: | Size: 181 KiB |
BIN
mconline/30-网络服插件教程/2-初识插件/images/deploy-2.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
mconline/30-网络服插件教程/2-初识插件/images/deploy-3.png
Normal file
|
After Width: | Height: | Size: 178 KiB |
BIN
mconline/30-网络服插件教程/2-初识插件/images/deploy-4.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
mconline/30-网络服插件教程/2-初识插件/images/deploy-5.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
mconline/30-网络服插件教程/2-初识插件/images/deploy-6.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
mconline/30-网络服插件教程/2-初识插件/images/deploy-7.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
mconline/30-网络服插件教程/2-初识插件/images/deploy-8.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
mconline/30-网络服插件教程/2-初识插件/images/download-1.png
Normal file
|
After Width: | Height: | Size: 178 KiB |
BIN
mconline/30-网络服插件教程/2-初识插件/images/intro-1.png
Normal file
|
After Width: | Height: | Size: 145 KiB |
BIN
mconline/30-网络服插件教程/2-初识插件/images/intro-2.png
Normal file
|
After Width: | Height: | Size: 333 KiB |
BIN
mconline/30-网络服插件教程/2-初识插件/images/op-1.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
mconline/30-网络服插件教程/2-初识插件/images/op-2.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
mconline/30-网络服插件教程/2-初识插件/images/op-3.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
69
mconline/30-网络服插件教程/3-插件知识进阶/1-Apollo网络服架构.md
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
front: https://nie.res.netease.com/r/pic/20220408/a5a602e2-e859-4a41-bf9b-3ada874fc9b7.png
|
||||
hard: 入门
|
||||
time: 5分钟
|
||||
selection: true
|
||||
---
|
||||
|
||||
# Apollo网络服架构
|
||||
|
||||
## 比较
|
||||
|
||||
### Java服
|
||||
|
||||
传统的Java服一般指使用Spigot等类似服务端的服务器,玩家使用任意客户端都可以加入游戏。这种服务器纯靠服务器进行驱动,如果不通过一些特殊手段,无法强制客户端安装一些mod来增强游戏体验。
|
||||
|
||||
### Apollo
|
||||
|
||||
Apollo服务器不仅可以类似Java服那样使用服务器进行驱动,还会同步发送相关与服务器匹配的Mod到客户端,并且使用这些Mod和服务器进行双向通信,来实现在触发某个事件后,打开界面,播放动画等效果。
|
||||
|
||||
需要注意的是,在Java服中经常使用ProtocolLib或者net.minecraft.server(简称NMS)来绕过Spigot API来实现各种功能,例如计分板,title等等。而在Apollo的开发中,并没有这些类似的协议API可供使用,但是可以使用ModSDK来实现更强大的客户端表现功能。例如计分板功能可以使用ModSDK制作一个Hud来实现。这里需要Java服插件开发者们提前了解并转变思想。
|
||||
|
||||
## 架构
|
||||
|
||||

|
||||
|
||||
如上图所示,Apollo网络服,拥有5种不同类型的服务器,客户端(Client)和数据库(DB)
|
||||
|
||||
### 服务器
|
||||
|
||||
#### Proxy
|
||||
|
||||
Proxy即代理服务器,所有玩家的连接都会通过代理服务器,然后再分发到各个游戏服或大厅服中。功能和Java服的BungeeCord较为相似。只不过Proxy服务器无法像BungeeCord一样安装插件。
|
||||
|
||||
#### Game
|
||||
|
||||
Game即游戏服,为玩家提供某个特定的玩法,是玩家的主要载体。一个在线玩家只能存在于一个Game或Lobby中。
|
||||
|
||||
#### Lobby
|
||||
|
||||
Lobby即大厅服,主要作为各个玩法间的中转站。玩家可以在大厅服中选择自己想要玩的玩法,并提供跨服跳转的功能。其中Lobby服的接口和Game服基本一致。
|
||||
|
||||
#### Service
|
||||
|
||||
Service即功能服,主要用作各个Game/Lobby服的后端服务器。可以使用Service服来集中处理一些其他服务器的数据。Service服可以主动/被动向各个服务器发送数据,其他服务器也可以监听Service服的消息,并调用回调完成一系列应答。
|
||||
|
||||
Service和Game的配合相对来说比较重要,下面将举一个例子来介绍。
|
||||
|
||||
- gameA服务器制作了起床战争玩法。gameA服务器启动完成后,就会通知Service服务器可以让玩家进入本服务器。
|
||||
|
||||
- 在gameA服务器上凑够了足够开局的玩家后,gameA就会开始游戏逻辑,并通知Service,不要再匹配新的玩家进入该服务器。
|
||||
|
||||
- 在游戏结束后,gameA将玩家重新送回大厅,然后重置地图。
|
||||
|
||||
- 待重置地图完成后,gameA再次通知Service,本服务器已经可以让玩家加入了。
|
||||
|
||||
更多通信相关的内容将会在下一节进行说明。
|
||||
|
||||
#### Master
|
||||
|
||||
Master即控制服,可以使用Master服来调用管理员指令,完成一系列的运维操作。
|
||||
|
||||
### 客户端
|
||||
|
||||
客户端是玩家的终端,每个玩家都可以看作一个Client。
|
||||
|
||||
### 数据库
|
||||
|
||||
数据库即之前所提到的数据的载体,具体详见[数据库的概念](../1-准备知识/3-数据库的概念.html)。
|
||||
|
||||
246
mconline/30-网络服插件教程/3-插件知识进阶/2-服务器通信.md
Normal file
@@ -0,0 +1,246 @@
|
||||
# 服务器通信
|
||||
|
||||
服务器之间的信息交换是开发服务器非常重要的一个环节,本节将对部分通信相关的接口进行说明。
|
||||
|
||||
本节所使用的所有接口都可以在开发者文档上找到具体说明。
|
||||
|
||||
地址:http://mc.163.com/dev/mcmanual/mc-dev/mcdocs/2-Apollo/4-SDK/8-%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%80%9A%E4%BF%A1.html
|
||||
|
||||
### 举例说明
|
||||
|
||||
#### 需求
|
||||
|
||||
制作一个每日签到系统
|
||||
|
||||
#### 通信流程
|
||||
|
||||
- 玩家登陆后,Game/Lobby向Service发送玩家的uid,Service收到请求后,查询玩家今天是否登陆过,并返回数据给Game/Lobby。
|
||||
- Game/Lobby收到数据后,向Client发送某个打开UI的事件,数据作为参数一同发送。
|
||||
- Client收到打开UI的事件,解析参数,并调用ScreenNode创建UI,显示到玩家客户端上。
|
||||
- 玩家点击UI上的签到按钮,Client向Game/Lobby发送签到事件。
|
||||
- Game/Lobby收到签到事件,向Service发送玩家签到事件。
|
||||
- Service收到请求,确认玩家当天没签到过,并更新签到状态,并回应Game/Lobby签到是否成功。
|
||||
- Game/Lobby收到会用后,调用回调,根据参数判断签到是否成功,成功的话,给玩家发送签到奖励。
|
||||
|
||||
## 客户端UI
|
||||
|
||||
客户端UI相关的示例代码将会截取neteaseDaily插件的部分代码进行举例,可以自行下载并在插件中找到对应代码进行学习。
|
||||
|
||||
客户端首先需要在初始化时使用下方代码监听Game/Lobby发来的打开界面请求。
|
||||
|
||||
然后将事件参数提供给ScreenNode做出相关解析。
|
||||
|
||||
dailyClientSystem.py
|
||||
|
||||
```python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import neteaseDailyScript.dailyConst as dailyConst
|
||||
import client.extraClientApi as clientApi
|
||||
|
||||
ClientSystem = clientApi.GetClientSystemCls()
|
||||
|
||||
|
||||
class DailyClientSystem(ClientSystem):
|
||||
"""
|
||||
该mod的客户端类
|
||||
与服务端类通信
|
||||
显示每日登录奖励的状态
|
||||
"""
|
||||
|
||||
def __init__(self, namespace, systemName):
|
||||
ClientSystem.__init__(self, namespace, systemName)
|
||||
self.mPlayerId = clientApi.GetLocalPlayerId()
|
||||
self.mDailyUINode = None
|
||||
|
||||
self.ListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), dailyConst.UiInitFinishedEvent, self, self.OnUiInitFinished)
|
||||
self.ListenForEvent(dailyConst.ModName, dailyConst.ServerSystemName, dailyConst.DisplayDailyRewardEvent, self, self.OnDisplayDailyReward) # 服务端调用接口打开每日登录奖励界面
|
||||
self.ListenForEvent(dailyConst.ModName, dailyConst.ServerSystemName, dailyConst.PlayerRecvEvent, self, self.OnPlayerRecv) # 玩家正常领取奖励后更新每日登录奖励界面
|
||||
|
||||
def Destroy(self):
|
||||
self.mDailyUINode = None
|
||||
self.UnListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), dailyConst.UiInitFinishedEvent, self, self.OnUiInitFinished)
|
||||
self.UnListenForEvent(dailyConst.ModName, dailyConst.ServerSystemName, dailyConst.DisplayDailyRewardEvent, self, self.OnDisplayDailyReward)
|
||||
self.UnListenForEvent(dailyConst.ModName, dailyConst.ServerSystemName, dailyConst.PlayerRecvEvent, self, self.OnPlayerRecv)
|
||||
|
||||
def OnUiInitFinished(self, *args):
|
||||
# 注册UI 详细解释参照《UI API》
|
||||
clientApi.RegisterUI(dailyConst.ModName, dailyConst.dailyUIName, dailyConst.dailyUIClsPath, dailyConst.dailyUIScreenDef)
|
||||
# 创建UI 详细解释参照《UI API》
|
||||
clientApi.CreateUI(dailyConst.ModName, dailyConst.dailyUIName, {"isHud": 1})
|
||||
self.mDailyUINode = clientApi.GetUI(dailyConst.ModName, dailyConst.dailyUIName)
|
||||
if self.mDailyUINode:
|
||||
self.mDailyUINode.InitScreen()
|
||||
else:
|
||||
print '==== %s ====' % 'create UI: %s failed' % dailyConst.dailyUIScreenDef
|
||||
|
||||
def OnDisplayDailyReward(self, data):
|
||||
print 'OnDisplayDailyReward', data
|
||||
if self.mDailyUINode:
|
||||
self.mDailyUINode.open(data)
|
||||
#从服务器收到打开客户端UI的请求,调用ScreenNode并传输数据data,打开界面
|
||||
|
||||
def OnPlayerRecv(self, data):
|
||||
print 'OnPlayerRecv', data
|
||||
if self.mDailyUINode:
|
||||
self.mDailyUINode.refresh(data)
|
||||
|
||||
```
|
||||
|
||||
在UI逻辑代码中,编写点击领取按钮的相关逻辑代码。下方的代码就是确定领取的最关键的代码,将请求发送到服务器。
|
||||
|
||||
可以看到,其实最主要还是使用了ClientSystem#NotifyToServer函数,来传输请求到服务器。
|
||||
|
||||
dailyClientUI.py
|
||||
|
||||
```python
|
||||
def recv(self):
|
||||
print 'recv', self.m_valid_date
|
||||
clientApi.GetSystem(dailyConst.ModName, dailyConst.ClientSystemName).NotifyToServer(
|
||||
dailyConst.PlayerRecvEvent,
|
||||
{'playerId': clientApi.GetLocalPlayerId(), 'valid': self.m_valid_date} # 传递服务端发送下来的日期过去验证,表示领取的是同一天的每日登录奖励
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 客户端和Game/Lobby通信
|
||||
|
||||
这个通信接口和开发Mod所使用的接口是同一个,读者应该都对这个接口不陌生。
|
||||
|
||||
使用这个接口,客户端和服务器之间可以双向发送消息,具体使用方式如下。
|
||||
|
||||
```python
|
||||
#服务端给客户端发送消息的示例
|
||||
#client mod
|
||||
class testClient(ClientSystem):
|
||||
def __init__(self,namespace,systemName):
|
||||
ClientSystem.__init__(self, namespace, systemName)
|
||||
self.ListenForEvent('serverNamespace', 'serverSystem', 'PlayerJoinOKEvent', self, self.OnPlayerJoinOK)
|
||||
def OnPlayerJoinOK(self, args):
|
||||
#args的结果为:{'uid':123, 'name':'nickname'}
|
||||
print 'OnPlayerJoinOK', args
|
||||
#game/lobby mod
|
||||
class testServer(ServerSystem):
|
||||
def __init__(self, namespace, systemName):
|
||||
ServerSystem.__init__(self, namespace, systemName)
|
||||
def testNotifyClient(self):
|
||||
player = {}
|
||||
player['uid'] = 123
|
||||
player['name'] = 'nickname'
|
||||
playerId = '456'
|
||||
self.NotifyToClient(playerId, "PlayerJoinOKEvent", player)
|
||||
```
|
||||
|
||||
```python
|
||||
#客户端给服务端发送消息的示例
|
||||
#client mod
|
||||
class testClient(ClientSystem):
|
||||
def __init__(self,namespace,systemName):
|
||||
ClientSystem.__init__(self, namespace, systemName)
|
||||
def testNotifyServer(self):
|
||||
player = {}
|
||||
player['uid'] = 123
|
||||
player['name'] = 'nickname'
|
||||
self.NotifyToServer("PlayerJoinEvent", data)
|
||||
#game/lobby mod
|
||||
class testServer(ServerSystem):
|
||||
def __init__(self, namespace, systemName):
|
||||
ServerSystem.__init__(self, namespace, systemName)
|
||||
self.ListenForEvent('clientNamespace', 'clientSystem', 'PlayerJoinEvent', self, self.OnPlayerJoin)
|
||||
def OnPlayerJoin(self, args):
|
||||
#args结果为:{'uid':123, 'name':'nickname'}
|
||||
print 'OnPlayerJoin', args
|
||||
```
|
||||
|
||||
## Service和Service/Master通信
|
||||
|
||||
此类通信通常用于不同服务器节点之间的数据传输,具体使用方法如下。
|
||||
|
||||
```python
|
||||
#service同master通信示例.service同service通信与此类似,这里不重复了
|
||||
#service mod
|
||||
class testService(ServiceSystem):
|
||||
def __init__(self,namespace,systemName):
|
||||
ServiceSystem.__init__(self, namespace, systemName)
|
||||
#注册service方法,注意一个事件只能注册一次,否则后面监听函数会覆盖前面监听函数
|
||||
self.RegisterRpcMethodForMod('PlayerJoinOKEvent', self.OnPlayerJoinOK)
|
||||
def OnPlayerJoinOK(self, serverId, callbackId, args):
|
||||
#args的结果为:{'uid':123, 'name':'nickname'}
|
||||
print 'OnPlayerJoinOK', args
|
||||
response = {}
|
||||
response['result'] = 1
|
||||
self.ResponseToServer(serverId, callbackId, response)
|
||||
#master mod
|
||||
class masterServer(MasterSystem):
|
||||
def __init__(self, namespace, systemName):
|
||||
MasterSystem.__init__(self, namespace, systemName)
|
||||
def OnCallback(self, suc, args):
|
||||
#若成功:suc=True,args= {'result' : 1}
|
||||
#若超时,则suc为False
|
||||
if not suc:
|
||||
print 'OnCallback timeout'
|
||||
return
|
||||
print 'OnCallback success', args
|
||||
def testNotifyService(self):
|
||||
player = {}
|
||||
player['uid'] = 123
|
||||
player['name'] = 'nickname'
|
||||
self.RequestToServiceMod("idv_service", "PlayerJoinOKEvent", data, self.OnCallback, 2)
|
||||
```
|
||||
|
||||
在上方的示例代码中,调用了3个相关的接口,下面将解释这3个接口的具体使用方法。
|
||||
|
||||
### RegisterRpcMethodForMod
|
||||
|
||||
service接口,用来监听master发来的请求,并将请求参数带入回调函数。
|
||||
|
||||
如上方代码所示,service在收到**PlayerJoinOKEvent**后,将会触发**OnPlayerJoinOK**函数。
|
||||
|
||||
### ResponseToServer
|
||||
|
||||
service接口,用来回应master的通信请求。
|
||||
|
||||
在上方代码中,service服务器收到了请求进行处理后,会将操作成功的结果再次发送回master服务器,同时调用master的回调函数**OnCallback**。
|
||||
|
||||
如果请求没有在2秒钟内完成,则会判定超时。
|
||||
|
||||
超时秒数可以在master的函数中进行配置,可根据自己的需要自行调整。
|
||||
|
||||
### RequestToServiceMod
|
||||
|
||||
master接口,用来发送数据到指定的service服务器。
|
||||
|
||||
其中第一个参数为module,需要提前在service的mod.json中配置好module_names。
|
||||
|
||||
其他参数均没有什么需要特别注意的地方,参考官方文档,即可查询相关参数的具体含义。链接在本页顶部。
|
||||
|
||||
## Service和Lobby/Game通信
|
||||
|
||||
**Service和Lobby/Game**之间的通信方式,基本与**Service和Service/Master通信**一致。都是使用的```RegisterRpcMethodForMod```、```ResponseToServer```、```RequestToServiceMod```上方提到的这3个接口。demo也基本一致,下方代码供参考。
|
||||
|
||||
```python
|
||||
#service同game/lobby通信示例
|
||||
#service mod
|
||||
class testService(ServiceSystem):
|
||||
def __init__(self,namespace,systemName):
|
||||
ServiceSystem.__init__(self, namespace, systemName)
|
||||
self.RegisterRpcMethodForMod("idv_service", 'PlayerJoinOKEvent', self.OnPlayerJoinOK)
|
||||
def OnPlayerJoinOK(self, serverId, callbackId, args):
|
||||
#args的结果为:{'uid':123, 'name':'nickname'}
|
||||
print 'OnPlayerJoinOK', args
|
||||
#lobby mod
|
||||
class lobbyServer(ServerSystem):
|
||||
def __init__(self, namespace, systemName):
|
||||
ServerSystem.__init__(self, namespace, systemName)
|
||||
def testNotifyService(self):
|
||||
player = {}
|
||||
player['uid'] = 123
|
||||
player['name'] = 'nickname'
|
||||
self.RequestToServiceMod("idv_service", "PlayerJoinOKEvent", args)
|
||||
```
|
||||
|
||||
除此之外,Service和Lobby/Game之间,还有更多接口可供调用。
|
||||
|
||||
具体可以点击<a href="../../../mcdocs/2-Apollo/4-SDK/8-服务器通信.html#service和game/lobby通信" rel="noopenner"> 这里 </a>查看。
|
||||
|
||||
106
mconline/30-网络服插件教程/3-插件知识进阶/3-插件文件夹结构.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# 插件文件夹结构
|
||||
|
||||
基于之前的了解,插件一共有4种类型,分别是lobbyMod、gameMod、serviceMod、masterMod。
|
||||
|
||||
它们的文件夹结构不完全相同,本节将会就文件夹的内容和功能进行介绍。
|
||||
|
||||
## lobbyMod/gameMod
|
||||
|
||||
```
|
||||
xxxMod
|
||||
developer_mods ——服务器mod,不会被传输到客户端
|
||||
xxxModDev ——服务器mod根目录
|
||||
mod.json ——插件的配置文件和基本信息
|
||||
netease_require.json ——插件所用的依赖(可缺省)
|
||||
scripts ——服务端脚本
|
||||
behavior_packs
|
||||
xxxModBeh ——客户端mod根目录
|
||||
scripts ——客户端脚本
|
||||
manifest.json ——资源版本信息
|
||||
resource_packs
|
||||
xxxModRes ——资源根目录
|
||||
manifest.json ——资源版本信息
|
||||
ui —— ui文件
|
||||
... ——其他资源文件
|
||||
worlds
|
||||
level
|
||||
db
|
||||
level.dat ——地图全局数据
|
||||
levelname.txt ——地图名
|
||||
world_behavior_packs.json ——配置客户端需要下载的behavior mods
|
||||
world_resource_packs.json ——配置客户端需要下载的resource mods
|
||||
mod.sql ——数据表创建的SQL语句(可缺省)
|
||||
readme.txt ——插件介绍
|
||||
```
|
||||
|
||||
developer_mods和behavior_packs区别:
|
||||
|
||||
- developer_mods控制服务端行为,behavior_packs控制客户端行为。
|
||||
- behavior_packs会下载给客户端,developer_mods不会。
|
||||
- developer_mods可以使用Apollo SDK全部接口和MOD SDK中服务端相关接口,behavior_packs使用MOD SDK中客户端相关接口。
|
||||
- behavior_packs必须包含manifest.json文件,且需要在地图目录下world_behavior_packs.json文件中配置pack id和version。
|
||||
|
||||
world_behavior_packs.json和world_resource_packs.json文件可以不需要自己配置,使用MCS部署插件时,如果文件不合法,将会自动进行修复。
|
||||
|
||||
### neteaseDaily举例
|
||||
|
||||
```
|
||||
neteaseDaily
|
||||
developer_mods ——服务器mod,不会被传输到客户端
|
||||
neteaseDailyDev ——服务器mod根目录
|
||||
mod.json ——插件的配置文件和基本信息
|
||||
neteaseDailyScript ——服务端脚本根目录
|
||||
behavior_packs
|
||||
neteaseDailyBehavior ——客户端mod根目录
|
||||
neteaseDailyScript ——客户端脚本根目录
|
||||
resource_packs
|
||||
neteaseDailyRes
|
||||
manifest.json ——资源版本信息
|
||||
ui ——ui文件
|
||||
textures ——材质文件
|
||||
worlds
|
||||
level
|
||||
world_behavior_packs.json ——配置客户端需要下载的behavior mods
|
||||
world_resource_packs.json ——配置客户端需要下载的resource mods
|
||||
readme.txt ——插件介绍
|
||||
```
|
||||
|
||||
|
||||
|
||||
## serviceMod/masterMod
|
||||
|
||||
```
|
||||
xxxMod
|
||||
developer_mods ——服务器mod
|
||||
xxxModDev ——服务器mod根目录
|
||||
mod.json ——插件的配置文件和基本信息
|
||||
netease_require.json ——插件所用的依赖(可缺省)
|
||||
scripts ——服务端脚本
|
||||
mod.sql ——数据表创建的SQL语句(可缺省)
|
||||
readme.txt ——插件介绍
|
||||
```
|
||||
|
||||
serviceMod/masterMod和lobbyMod/gameMod相比,少了behavior_packs,resource_packs和worlds。该插件只能被运行在服务端上。
|
||||
|
||||
### neteaseDailyService举例
|
||||
|
||||
```
|
||||
neteaseDailyService
|
||||
developer_mods ——服务器mod,不会被传输到客户端
|
||||
neteaseDailyDev ——服务器mod根目录
|
||||
mod.json ——插件的配置文件和基本信息
|
||||
neteaseDailyScript ——服务端脚本
|
||||
mod.sql ——数据表创建的SQL语句
|
||||
readme.txt ——插件介绍
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 插件目录生成工具
|
||||
|
||||
按照实际需求,插件由若干个服务器mod组成,可包含lobby/game(大厅/游戏)服mod、service(功能)服mod以及master(控制)服mod。
|
||||
|
||||
各类服务器mod的目录结构,已在<a href="../../../mcguide/27-手机网络游戏/课程2:Apollo基础知识/第3节:服务器Mod.html" rel="noopenner"> 第3节:服务器Mod </a>中说明。
|
||||
|
||||
开发者可下载[生成插件模板工具](https://g79.gdl.netease.com/template.zip),在实际应用中快捷生成插件目录。
|
||||
|
||||
BIN
mconline/30-网络服插件教程/3-插件知识进阶/images/structure-1.png
Normal file
|
After Width: | Height: | Size: 190 KiB |
195
mconline/30-网络服插件教程/4-插件制作/1-规范.md
Normal file
@@ -0,0 +1,195 @@
|
||||
---
|
||||
front: https://nie.res.netease.com/r/pic/20220408/e24733db-2cc1-49ee-bacb-c1913b2e6c82.png
|
||||
hard: 入门
|
||||
time: 10分钟
|
||||
selection: true
|
||||
---
|
||||
|
||||
# 规范
|
||||
|
||||
制作符合规范的插件,不仅可以增加可读性,还可以减少插件间的冲突问题,并且更加便于使用,方便二次开发。
|
||||
|
||||
本节将介绍插件开发的相关规范。
|
||||
|
||||
## 设计文档规范
|
||||
|
||||
在设计插件的时候,用文档将设计方案记录。一方面便于将插件功能转化成开发者所需的说明文档,另一方面便于日后二次开发。
|
||||
|
||||
文档需要包含一下几个方面:
|
||||
|
||||
- 插件功能描述(若功能流程较为复杂,最好能包含流程图)
|
||||
- UI示意图
|
||||
- 配置说明
|
||||
- API
|
||||
- 事件以及运营指令
|
||||
- 功能描述
|
||||
|
||||
[点我](./设计文档示例.html)查看随身仓库插件的设计文档示例。
|
||||
|
||||
## 插件规范
|
||||
|
||||
插件目录及命名的规范可以参考[这里](https://g.126.fm/02YRPPK)
|
||||
|
||||
## 介绍文档规范
|
||||
|
||||
介绍文档,即插件目录下的readme.txt,为了使他人更好地理解并使用你的插件,需要根据规范编写readme.txt。
|
||||
|
||||
具体规范可参考[这里](https://g.126.fm/02AskEc)
|
||||
|
||||
例如下方的neteaseMenus的readme.txt
|
||||
|
||||
```
|
||||
插件介绍:
|
||||
该服务器Mod隶属于“主菜单”插件。
|
||||
“主菜单”插件实现主菜单功能:
|
||||
- 主菜单:定义并在游戏中生效主菜单(配置于lobby/game下的mod.json)
|
||||
|
||||
插件构成:
|
||||
目前“主菜单”插件包含以下Mod:
|
||||
- neteaseMenus:部署于大厅服或游戏服
|
||||
|
||||
使用步骤:
|
||||
(1)在部署配置中,将neteaseMenus添加至需要的大厅服或者游戏服的mods列表中
|
||||
|
||||
插件api:
|
||||
(1)设置主菜单按钮状态
|
||||
适用范围:客户端
|
||||
函数:UpdateMenus(data)
|
||||
参数:
|
||||
data: dict, 设置按钮状态的字典,格式参照下面例子
|
||||
返回:
|
||||
bool, 是否设置成功,注意设置失败也有可能已经改变了菜单状态
|
||||
示例:
|
||||
import client.extraClientApi as clientApi
|
||||
data = { # key对应按钮序号,从0开始,value为(0, 1)中的一个数字,【0】代表禁用该按钮,【1】代表开启该按钮
|
||||
1: 0,
|
||||
4: 1,
|
||||
16: -1,
|
||||
}
|
||||
menusSystem = clientApi.GetSystem("neteaseMenus", "neteaseMenusBeh")
|
||||
flag = menusSystem.UpdateMenus(data)
|
||||
|
||||
(2)获取主菜单所有按钮配置
|
||||
适用范围:客户端
|
||||
函数:GetMenus()
|
||||
参数:
|
||||
无
|
||||
返回:
|
||||
menus:dict 主菜单按钮配置,结构对应mod.json中的配置,详见mod.json
|
||||
示例:
|
||||
import client.extraClientApi as clientApi
|
||||
menusSystem = clientApi.GetSystem("neteaseMenus", "neteaseMenusBeh")
|
||||
menus = menusSystem.GetMenus() # 结构对应mod.json中的配置,详见mod.json
|
||||
|
||||
插件event:
|
||||
(1)MenusNavigateEvent
|
||||
适用范围:客户端
|
||||
命名空间:namespace = 'neteaseMenus', systemname = 'neteaseMenusBeh'
|
||||
描述:玩家点击了某按钮事件
|
||||
参数:
|
||||
playerId: str, 点击按钮玩家的playerId
|
||||
order: int, 被点击按钮的序号,从0开始
|
||||
示例:
|
||||
def __init__(self, namespace, systemName):
|
||||
self.ListenForEvent('neteaseMenus', 'neteaseMenusBeh', 'MenusNavigateEvent', self, self.OnMenusNavigate)
|
||||
|
||||
def OnMenusNavigate(self, data):
|
||||
print 'OnMenusNavigate', data
|
||||
playerId = data["playerId"]
|
||||
order = data["order"]
|
||||
print '玩家 {} 点击了主菜单中的 {} 号按钮'.format(playerId, order)
|
||||
|
||||
更新列表:
|
||||
1.0.0版本:
|
||||
初始版本
|
||||
1.0.1版本:
|
||||
调整了UI资源,增加了代码注释
|
||||
1.0.2版本:
|
||||
按照新规范调整UI和代码
|
||||
1.0.3版本:
|
||||
迭代新UI和代码
|
||||
1.0.4版本:
|
||||
重构了UI和代码,配置方式也发生了变化
|
||||
1.0.5版本:
|
||||
优化插件readme文档描述
|
||||
```
|
||||
|
||||
包含了以下内容:
|
||||
|
||||
- 插件介绍
|
||||
- 插件构成
|
||||
- 使用步骤
|
||||
- 插件api
|
||||
- 插件event
|
||||
- 更新列表
|
||||
|
||||
在上方例子的插件中,没有包含**运营指令**和**前置插件**的相关介绍。如果在你编写的插件中有运营指令和前置插件的需求,也需要将它们都写上。
|
||||
|
||||
例如下方的是经济插件的运营指令的介绍。
|
||||
|
||||
```
|
||||
支持的运营指令:
|
||||
运营指令:
|
||||
(1)查询一个玩家身上的货币剩余数量(该玩家需在线)
|
||||
post url: http:masterip:masterport/query-player-doughs
|
||||
post body:{
|
||||
"uid": 996 # 玩家的uid
|
||||
}
|
||||
response:
|
||||
{
|
||||
"code": 1, # 返回码,【1】代表成功,其他代表失败
|
||||
"message": "请求成功", # 失败原因的具体描述
|
||||
"entity": { # 返回该玩家身上货币剩余数量信息,查询失败则为空字典
|
||||
"RMB": 996,
|
||||
"USD": -996
|
||||
}
|
||||
}
|
||||
(2)为一个玩家添加/减少货币(参数:玩家id、货币类型、货币数量,货币数量可为负数)
|
||||
post url: http:masterip:masterport/update-player-doughs
|
||||
post body:{
|
||||
"uid": 996, # 玩家的uid
|
||||
"mod": { # 修改货币数据的字典
|
||||
"RMB": 996,
|
||||
"USD": -996
|
||||
}
|
||||
}
|
||||
response:
|
||||
{
|
||||
"code": 1, # 返回码,【1】代表成功,其他代表失败
|
||||
"message": "请求成功", # 失败原因的具体描述
|
||||
"entity": { # 返回该玩家身上货币剩余数量信息,操作失败则为空字典
|
||||
"RMB": 1992,
|
||||
"USD": -1992
|
||||
}
|
||||
}
|
||||
(3)查询当前总共有多少个出售摊位(2.0.0新增)
|
||||
post url: http:masterip:masterport/count-stalls
|
||||
post body:{}
|
||||
response:
|
||||
{
|
||||
"code": 1, # 返回码,【1】代表成功,其他代表失败
|
||||
"message": "请求成功", # 失败原因的具体描述
|
||||
"entity": {
|
||||
"stalls": {
|
||||
"996": {
|
||||
"duration": 12, # 摆摊总时长
|
||||
"deadline": 1590462263, # 摆摊自动收摊时间戳
|
||||
"ver": 0, # 无用
|
||||
"label": "好货不便宜", # 摊位名称
|
||||
"deals": [...] # 该摊位的出售记录
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
前置要求参考下方面对面交易插件
|
||||
|
||||
```
|
||||
插件介绍:
|
||||
该服务器Mod隶属于“面对面交易”插件。
|
||||
“交易”插件实现2个主要功能,1个是交易货币,1个是交易物品:
|
||||
- 交易货币:弱依赖经济插件,需要配置对应的货币类型和货币贴图。也可以自行进行管理,只需要在transactionServerSystem.py中搜索“经济插件”,并把对应的获取货币和更新货币的地方换成自己的接口管理即可
|
||||
- 交易物品:玩家可以通过选择背包中的物品及数量进行交易
|
||||
```
|
||||
|
||||
98
mconline/30-网络服插件教程/4-插件制作/2-知识点拆解.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# 知识点拆解
|
||||
|
||||
本节将通过官方插件的分析,加强概念的理解。
|
||||
|
||||
## 文件结构
|
||||
|
||||
按照下列流程,找到之前举例安装的neteaseDaily的文件夹。
|
||||
|
||||
1. 基岩版服务器
|
||||
2. 插件
|
||||
3. neteaseDaily
|
||||
4. 右键,打开目录
|
||||
|
||||
在弹出的文件夹中,可以看到插件目录下,有4个文件夹。
|
||||
|
||||

|
||||
|
||||
那么behavior_packs中所包含的,就是发送到玩家设备上运行的客户端mod;developer_mods中所包含的,是运行在服务器上的mod;resource_packs中所包含的,是客户端的资源文件,也会被发送到玩家设备;worlds是mod的地图信息。
|
||||
|
||||
如果对这部分有所遗忘,可以点击[这里](../3-插件知识进阶/3-插件文件夹结构.html)进行回顾。
|
||||
|
||||
## 回调
|
||||
|
||||
回调是编程中非常常用的一个技术,在ModSDK和Apollo插件的开发中,也经常用到它。
|
||||
|
||||
比如在监听事件时,用到的ListenForEvent中,就用到了它。
|
||||
|
||||
例如下方的语句中,self.OnServerChat就是一个回调函数,在ListenForEvent函数调用后,引擎会将其保存,并在ServerChatEvent触发时,调用这个函数。
|
||||
|
||||
```python
|
||||
self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), 'ServerChatEvent', self, self.OnServerChat)
|
||||
```
|
||||
|
||||
在Apollo开发中,也同样经常用到回调函数,就比如在neteaseDaily中。
|
||||
|
||||
打开```developer_mods/neteaseDailyDev/neteaseDailyScript/dailyServerSystem.py```,在这个文件中,可以看到RequestToService之后,调用了DailyServerRender函数作为回调函数。
|
||||
|
||||
```python
|
||||
def OpenDailyReward(self, uid):
|
||||
"""
|
||||
使一个玩家打开每日登录奖励的界面
|
||||
"""
|
||||
stamp = time.time()
|
||||
print 'OpenDailyReward', uid, stamp
|
||||
playerId = netgameApi.GetPlayerIdByUid(uid)
|
||||
if not playerId:
|
||||
print 'can not get playerId by uid: %s' % uid
|
||||
return
|
||||
if self.mRewards is None:
|
||||
logout.warning('因无可用奖励配置无法打开每日登录奖励界面')
|
||||
return
|
||||
if stamp < self.mRewards[0][0]:
|
||||
print 'not yet', self.mRewards[0][0]
|
||||
return
|
||||
self.RequestToService(
|
||||
dailyConst.ModName,
|
||||
dailyConst.DisplayDailyRewardEvent,
|
||||
{'uid': uid, 'stamp': stamp},
|
||||
lambda rtn, data: rtn and self.DailyServerRender(playerId, dailyConst.DisplayDailyRewardEvent, data)
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 功能服
|
||||
|
||||
功能服连接所有服务器和数据库,能够获取所有服务器的信息,可以处理一些全服操作。
|
||||
|
||||
比如在聊天插件中,如果在本地频道发消息,不需要经过功能服转发,但如果要在世界频道发消息,就需要在功能服中进行转发,把消息发到所有大厅服和游戏服。
|
||||
|
||||
具体代码可以下载官方聊天插件 neteaseChatService 查看。
|
||||
|
||||
## 数据库
|
||||
|
||||
在官方插件云端玩家信息插件,neteaseCloud中,找到mod.sql文件。
|
||||
|
||||
这个文件便是插件的MySQL数据表结构。文件如下:
|
||||
|
||||
```sql
|
||||
-- ###########################version1.0.0####################
|
||||
-- 云端背包
|
||||
CREATE TABLE IF NOT EXISTS `neteaseCloudItems` (
|
||||
`_id` INT UNSIGNED NOT NULL auto_increment COMMENT '唯一ID,自增',
|
||||
`uid` BIGINT UNSIGNED NOT NULL COMMENT '玩家uid',
|
||||
`cloud_items` varchar(10000) NOT NULL DEFAULT '' COMMENT '记录云背包内容',
|
||||
PRIMARY KEY (_id) COMMENT '主键',
|
||||
INDEX `uid_idx` (`uid`) COMMENT '索引'
|
||||
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
-- ###########################version2.0.0####################
|
||||
alter table neteaseCloudItems add column apply_tag varchar(20) not null default "";
|
||||
ALTER TABLE neteaseCloudItems ADD INDEX apply_tag_index (`apply_tag`, `uid`);
|
||||
```
|
||||
|
||||
sql文件使用SQL语句编写,需要掌握一定的SQL知识,可以访问下方链接进行了解。
|
||||
|
||||
https://www.w3school.com.cn/sql/index.asp
|
||||
|
||||
而这个数据表,则存储了玩家的背包信息和uid,服务器可以很方便地使用uid查找到对应的背包信息。
|
||||
36
mconline/30-网络服插件教程/4-插件制作/3-插件编写——需求篇.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# 插件编写——需求篇
|
||||
|
||||
在开始编写插件之前,我们需要首先明确插件需要什么功能,具体细节是什么样的以及各种用于二次开发的API需求和事件需求。
|
||||
|
||||
在接下来的篇幅中,将以抽奖插件为例,按照流程制作一个完整的插件。
|
||||
|
||||
## 插件需求
|
||||
|
||||
每日进行1期抽奖,每位玩家可随机领取3个不同的号码,号码取值是1~10000,领完为止。每日20:00开奖,通过邮件(官方neteaseAnnounced插件)告知中奖玩家并发放奖励。
|
||||
|
||||
## 细节需求
|
||||
|
||||
- 玩家可在game服或lobby服,输入cp1领取号码,成功领取后,用官方neteaseAlert插件提示“本次领取的号码是%s”
|
||||
- 奖励号码每日开奖后重置,玩家可重新领取。
|
||||
- 若超过当日可领取的上限:3,输入cp1领取时,用官方neteaseAlert插件提示“今日领取的号码已超过上限”
|
||||
- 每日开奖时间之前(上一天的20:01~当天20:00),输入cp2可查询已领取的号码。
|
||||
- 官方neteaseAlert插件提示已领取的号码情况“今日已领取%s(数量)个号码:%s、%s、%s”
|
||||
- 从1~10000中随机抽取5个号码作为本期中奖号码。
|
||||
- 中奖的邮件提示:本期中奖号码:%s、%s、%s、%s、%s。恭喜你,在本期抽奖活动中中奖,请再接再厉。邮件附件内容:黑曜石*5
|
||||
- 没中奖的邮件提示:本期中奖号码:%s、%s、%s、%s、%s。很遗憾,在本期抽奖活动中未能中奖,请继续努力。邮件附件内容:铁锭*3
|
||||
- 当天没有领取任何号码的玩家无需发放邮件。
|
||||
|
||||
## API需求
|
||||
|
||||
- 给指定玩家随机获得1个号码(必须是本期活动没有获得过的,如果没有号码则返回-1)
|
||||
- 返回玩家已获取的号码数量
|
||||
- 返回玩家已获取的号码列表
|
||||
- 重置某个玩家的号码
|
||||
- 重置所有号码
|
||||
- 执行发奖,返回获奖的号码列表以及获奖玩家列表
|
||||
|
||||
## 事件需求
|
||||
|
||||
- 发奖时刻之前触发——返回中奖号码,可修改中奖号码
|
||||
- 发奖时刻之后触发——返回中奖号码与中奖玩家
|
||||
|
||||
87
mconline/30-网络服插件教程/4-插件制作/4-插件编写——制作篇(上).md
Normal file
@@ -0,0 +1,87 @@
|
||||
# 插件编写——制作篇(上)
|
||||
|
||||
## 需求分析
|
||||
|
||||
之前提到过,Apollo插件拥有3种不同的类型,我们在开始编写前,首先需要思考哪些需求可以运行在哪个类型的服务器上。
|
||||
|
||||
结构图如下
|
||||
|
||||

|
||||
|
||||
### Game/Lobby
|
||||
|
||||
- 输入cp1领取号码,成功领取后,用官方neteaseAlert插件提示“本次领取的号码是%s”
|
||||
- 若超过当日可领取的上限:3,输入cp1领取时,用官方neteaseAlert插件提示“今日领取的号码已超过上限”
|
||||
- 每日开奖时间之前(上一天的20:01~当天20:00),输入cp2可查询已领取的号码。
|
||||
- 官方neteaseAlert插件提示已领取的号码情况“今日已领取%s(数量)个号码:%s、%s、%s”
|
||||
|
||||
> 为什么聊天输入相关的监听需要做在Game/Lobby服务器
|
||||
|
||||
因为玩家只会在Game/Lobby上在线,只有在这两种服务器上才能监听玩家的聊天信息。
|
||||
|
||||
### Service
|
||||
|
||||
- 每日20:00开奖,通过邮件(官方neteaseAnnounce插件)告知中奖玩家并发放奖励。
|
||||
- 中奖的邮件提示:本期中奖号码:%s、%s、%s、%s、%s。恭喜你,在本期抽奖活动中中奖,请再接再厉。邮件附件内容:黑曜石*5
|
||||
- 没中奖的邮件提示:本期中奖号码:%s、%s、%s、%s、%s。很遗憾,在本期抽奖活动中未能中奖,请继续努力。邮件附件内容:铁锭*3
|
||||
- 服务器API——给指定玩家随机获得1个号码(必须是本期活动没有获得过的,如果没有号码则返回-1)
|
||||
- 服务器API——返回玩家已获取的号码数量
|
||||
- 服务器API——返回玩家已获取的号码列表
|
||||
- 服务器API——重置某个玩家的号码
|
||||
- 服务器API——重置所有号码
|
||||
- 服务器API——执行发奖,返回获奖的号码列表以及获奖玩家列表
|
||||
- 发奖时刻之前触发——返回中奖号码,可修改中奖号码
|
||||
- 发奖时刻之后触发——返回中奖号码与中奖玩家
|
||||
|
||||
> 为什么“每日20:00开奖,通过邮件(官方neteaseAnnounce插件)告知中奖玩家并发放奖励。”需要做在Service服务器
|
||||
|
||||
因为发奖需要统一操作,而不可能所有玩家都在同一个Game/Lobby中,数据处理放在Service上则可以统一处理,更方便。
|
||||
|
||||
### Master
|
||||
|
||||
无
|
||||
|
||||
|
||||
|
||||
# 开发查询和参考链接
|
||||
|
||||
### Mod SDK
|
||||
|
||||
对游戏相关SDK不熟悉,可以在这里查询。
|
||||
|
||||
- 接口: <a href="../../../mcdocs/1-ModAPI/接口/通用/索引.html" rel="noopenner"> 链接 </a>
|
||||
- 事件:<a href="../../../mcdocs/1-ModAPI/事件/世界.html" rel="noopenner"> 链接 </a>
|
||||
|
||||
### Apollo
|
||||
|
||||
对Apollo的专用接口有疑问,可以在这里查询。
|
||||
|
||||
- <a href="../../../mcdocs/2-Apollo/4-SDK/1-大厅与游戏服事件.html" rel="noopenner"> 链接 </a>
|
||||
|
||||
### Apollo 插件地址
|
||||
|
||||
在这里可以查看已经制作好的插件,并了解它们各自的功能。
|
||||
|
||||
- 官方插件:<a href="../../../mcdocs/2-Apollo/5-官方插件简介.html" rel="noopenner"> 链接</a>
|
||||
- 第三方插件:<a href="../../../mcdocs/2-Apollo/6-第三方插件简介.html" rel="noopenner"> 链接</a>
|
||||
|
||||
# 代码编写
|
||||
|
||||
首先点击新建空白Addon,作品名称填写Lottery为例
|
||||
|
||||

|
||||
|
||||
点击启动编辑,会打开编辑器。但是我们创建项目的目的是为了快速生成一个Apollo插件模板,所以在弹出编辑器窗口后关闭即可。
|
||||
|
||||
随后在基岩版组件中,找到我们刚刚创建的名为Lottery的AddOn,右键选择,点击转换为服务器Mod。
|
||||
|
||||
<img src="./images/code-2.png" style="zoom:150%;" />
|
||||
|
||||
弹出的窗口中,选项不需要做特别修改,直接点击转换。
|
||||
|
||||
随后在基岩版服务器插件中,找到刚刚创建的Lottery,右键打开目录。因为我们之前已经将需求归类,总结出需要Game/Lobby服和Service服的插件,所以我们将文件夹复制,并重命名成符合规范的。这里我们复制一份Lottery并重命名成soldierLottery,再将原来的文件夹重命名成soldierLotteryService。
|
||||
|
||||
操作完成后文件夹的样子应该如截图所示。
|
||||
|
||||

|
||||
|
||||
271
mconline/30-网络服插件教程/4-插件制作/5-插件编写——制作篇(中).md
Normal file
@@ -0,0 +1,271 @@
|
||||
# 插件编写——制作篇(中)
|
||||
|
||||
## Service编写
|
||||
|
||||
根据之前对Service的了解,Service中是只应该有developer_mods文件夹的,而我们现在的soldierLotteryService文件夹,因为是从Game/Lobby复制而来的,所以需要手动将没有用的文件夹删除。只留下developer_mods文件夹,删除下图中选择的文件夹。
|
||||
|
||||

|
||||
|
||||
然后我们根据官方插件规范,将soldierLotteryService/developer_mods的Lottery文件夹,改名为soldierLotteryDev。
|
||||
|
||||
这里使用IntelliJ IDEA (PyCharm同理)为例,介绍插件代码的编写。
|
||||
|
||||
进入IDEA后,打开soldierLotteryService的项目,找到mod.json,按照官方规范进行修改,修改后的mod.json供参考。需要注意的是,support_server_type需要更改成service,否则无法在Service服部署。
|
||||
|
||||
```json
|
||||
{
|
||||
"netgame_mod_name": "soldierLotteryDev",
|
||||
"netgame_mod_version": "1.0.0",
|
||||
"min_app_version": "1.23.0.release20210722",
|
||||
"author": "Soldier",
|
||||
"module_names": "soldierLottery",
|
||||
"support_server_type": [
|
||||
"service"
|
||||
],
|
||||
"group": "soldierLottery"
|
||||
}
|
||||
```
|
||||
|
||||
随后我们就可以开始编写代码了,Service服的入口modMain和一般Mod的结构非常相似,创建一个consts.py用来存放一些常量后,就可以参照下方代码编写了。
|
||||
|
||||
modMain.py
|
||||
|
||||
```python
|
||||
# coding=utf-8
|
||||
from mod.common.mod import Mod
|
||||
import mod.server.extraServiceApi as serviceApi
|
||||
|
||||
from soldierLotteryScripts.consts import ModName, ModVersion, ServiceSystemName, ServiceSystemClsPath
|
||||
|
||||
|
||||
@Mod.Binding(name=ModName, version=ModVersion)
|
||||
class SoldierLotteryService(object):
|
||||
@Mod.InitService()
|
||||
def InitService(self):
|
||||
serviceApi.RegisterSystem(ModName, ServiceSystemName, ServiceSystemClsPath)
|
||||
print "SoldierLotteryService启动"
|
||||
|
||||
@Mod.DestroyService()
|
||||
def DestroyService(self):
|
||||
print "SoldierLotteryService卸载"
|
||||
|
||||
```
|
||||
|
||||
lotteryServiveSystem.py
|
||||
|
||||
```python
|
||||
# coding=utf-8
|
||||
from mod.server.system.serviceSystem import ServiceSystem
|
||||
|
||||
from soldierLotteryScripts.mysqlManager import MysqlManager
|
||||
|
||||
|
||||
class LotteryServiceSystem(ServiceSystem):
|
||||
|
||||
def __init__(self, namespace, systemName):
|
||||
ServiceSystem.__init__(self, namespace, systemName)
|
||||
```
|
||||
|
||||
随后我们可以开始编写MySQL数据库相关的代码。
|
||||
|
||||
在编写数据库的代码之前,我们首先需要设计好数据表结构。分析需求,可以得出该插件需要两张数据表,分别用来存储抽奖信息和玩家信息。
|
||||
|
||||
### 抽奖信息表
|
||||
|
||||
主要需要记录 抽奖id,是否开奖
|
||||
|
||||

|
||||
|
||||
因为这张表我们只需要通过id来设置是否已经开奖,而id又是主键,自带索引。所以无需创建索引,直接设计好后将SQL复制到mod.sql中即可。
|
||||
|
||||
```sql
|
||||
CREATE TABLE `soldierLotteryState` (
|
||||
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID既抽奖ID',
|
||||
`finish` int(1) UNSIGNED NOT NULL COMMENT '是否已经开奖',
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
```
|
||||
|
||||
### 玩家信息表
|
||||
|
||||
主要需要记录 玩家uid,玩家持有的抽奖id,对应抽奖的号码
|
||||
|
||||
则可以使用Navicat设计出数据表如下图所示。其中需要注意的是,玩家uid的类型必须为**无符号 int**,在sql命令中表达为 ```UNSIGNED INT```,不能是int。
|
||||
|
||||

|
||||
|
||||
由于功能上需要查询指定抽奖id的玩家拥有的号码,同时需要检索player和lottery两列数据,所以我们需要给它们加上索引来提高后续的搜索速度。
|
||||
|
||||
索引添加如下图。(索引涉及到的知识要求相对较高,如果不理解,可以在MySQL相关教程处学习,这里我们简单理解成需要检索哪一列就给哪一列添加索引)
|
||||
|
||||

|
||||
|
||||
随后在SQL预览处将SQL复制出来,自行修改表名,粘贴到mod.sql中。
|
||||
|
||||
```sql
|
||||
CREATE TABLE `soldierLotteryPlayer` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
|
||||
`player` int UNSIGNED NOT NULL COMMENT '玩家UID',
|
||||
`lottery` int(10) NOT NULL COMMENT '抽奖ID',
|
||||
`number` int(10) NOT NULL COMMENT '玩家持有的抽奖号码',
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `player_lottery`(`player`, `lottery`) USING BTREE COMMENT '玩家uid和抽奖id的索引'
|
||||
);
|
||||
```
|
||||
|
||||
### 数据接口
|
||||
|
||||
设计完数据表,就可以根据需求,编写可能会用到的数据相关接口。
|
||||
|
||||
这里我们对照接口一项一项编写,下面展示```mysqlManager.py```的一部分代码。
|
||||
|
||||
```python
|
||||
# coding=utf-8
|
||||
import random
|
||||
from collections import OrderedDict
|
||||
|
||||
from apolloCommon import mysqlPool
|
||||
|
||||
from soldierLotteryScripts import consts
|
||||
|
||||
|
||||
class MysqlManager(object):
|
||||
"""
|
||||
MysqlManager主要用来管理数据库数据
|
||||
提供函数快捷获取玩家数据
|
||||
"""
|
||||
|
||||
def __init__(self, serviceSystem):
|
||||
self.mServiceSystem = serviceSystem
|
||||
print "初始化MySQL数据库连接"
|
||||
mysqlPool.InitDB(10)
|
||||
self.mPlayerCache = {} # 玩家数据缓存
|
||||
self.mStateCache = OrderedDict() # 抽奖状态缓存
|
||||
self.mLotteryNumbers = {} # 抽奖号码缓存
|
||||
self.CacheLotteryStates()
|
||||
self.CacheAllNumbers()
|
||||
|
||||
def Destroy(self):
|
||||
mysqlPool.Finish()
|
||||
print "关闭MySQL连接"
|
||||
|
||||
def GetPlayerCache(self, uid):
|
||||
"""
|
||||
获取玩家缓存,没有缓存则从数据库获取,并返回None
|
||||
:param uid:
|
||||
:return:
|
||||
"""
|
||||
cache = self.mPlayerCache.get(uid)
|
||||
if cache is not None:
|
||||
return cache
|
||||
|
||||
def callback(uid, result):
|
||||
data = {}
|
||||
if result:
|
||||
for line in result:
|
||||
tmp = data.get(line[0], [])
|
||||
tmp.append(line[1])
|
||||
data[line[0]] = tmp
|
||||
print "缓存玩家{}的数据为{}".format(uid, data)
|
||||
self.mPlayerCache[uid] = data
|
||||
|
||||
sql = "SELECT `lottery`,`number` FROM `{}` WHERE `player` = %s;".format(consts.LotteryPlayerTable)
|
||||
mysqlPool.AsyncQueryWithOrderKey("cache_player_{}".format(uid), sql, (uid,),
|
||||
lambda result: callback(uid, result))
|
||||
return None
|
||||
|
||||
def RemovePlayerCache(self, uid):
|
||||
"""
|
||||
移除玩家缓存,退出时触发
|
||||
:param uid: 玩家UID
|
||||
:return:None
|
||||
"""
|
||||
cache = self.mPlayerCache.get(uid)
|
||||
if cache is None:
|
||||
# 因为正常获取过缓存数据的玩家,都存储过cache为{},不可能是None。所以此处是None的话不处理
|
||||
return
|
||||
del self.mPlayerCache[uid]
|
||||
```
|
||||
|
||||
需要从这串代码中注意的是,所有数据都采用了缓存的方式来处理。即玩家加入游戏,从MySQL获取数据并缓存;玩家退出游戏,如果玩家的数据发生了变化,则存到数据库并删除缓存,如果没变化则直接删除缓存。
|
||||
|
||||
并且,在执行SQL命令时,需要注意尽量让每个操作的**OrderKey**唯一,这样就可以使SQL命令并发执行,提高效率。
|
||||
|
||||
### 发送邮件
|
||||
|
||||
在需求中,有一条是使用官方邮箱插件发送奖励。所以我们首先需要下载官方的邮箱插件,并查看他的简介。
|
||||
|
||||
简介可以在官方插件页面下载后,在插件的右键菜单中找到。
|
||||
|
||||
在简介中可以找到发送邮件的接口
|
||||
|
||||
> (7)向一组指定uid的玩家发送私人邮件(功能服用)
|
||||
> 函数:OutSendMailToMany(touids, title, content, itemList=[], expire=None, srcName="")
|
||||
> 参数:
|
||||
> touids:list(int), 玩家唯一ID的列表
|
||||
> title:str, 邮件标题
|
||||
> content:str, 邮件正文
|
||||
> itemList:list(dict), 附件物品列表,格式与通用的【物品信息字典】相同(参照ModSDk中往背包中塞物品的字典),额外支持【durability】关键字定义耐久度。另外,如果同经济插件一起使用,还支持货币,格式为:
|
||||
> itemType为"currency"表示货币类型,itemName对应经济插件mod.json中dough_id配置,icon对应济插件mod.json中dough_icon配置,count表示货币数量。
|
||||
> expire:int, 邮件有效期,单位秒
|
||||
> srcName:str, 邮件发送者名字
|
||||
> 示例:
|
||||
> import server.extraServiceApi as serviceApi
|
||||
> itemDict = {
|
||||
> 'itemName': 'minecraft:bow',
|
||||
> 'count': 1,
|
||||
> 'enchantData': [(19, 1),],
|
||||
> 'auxValue': 0,
|
||||
> 'customTips':'§c new item §r',
|
||||
> 'extraId': 'abc'
|
||||
> }
|
||||
> currencyDict = {
|
||||
> 'itemName' : 'RMB',
|
||||
> 'itemType' : 'currency',
|
||||
> 'icon' : 'textures/ui/netease_trade/icon03@3x.png',
|
||||
> 'count' : 3
|
||||
> }
|
||||
> mailSystem = serviceApi.GetSystem("neteaseAnnounce", "neteaseAnnounceService")
|
||||
> mailSystem.OutSendMailToMany([123,234], "欢迎新人", "欢迎首次登录,开发组送上弓一把", [itemDict, currencyDict], 86400, "开发组")
|
||||
|
||||
我们可以参考介绍编写发送奖励邮件的代码。
|
||||
|
||||
### 通信
|
||||
|
||||
我们在Service服上编写的接口如果需要被Game/Lobby调用,就需要用到服务器之间的通信。下方的例子就实现了接口请求。对应代码文件```lotteryServiceSystem.py```。
|
||||
|
||||
```python
|
||||
class LotteryServiceSystem(ServiceSystem):
|
||||
|
||||
def __init__(self, namespace, systemName):
|
||||
ServiceSystem.__init__(self, namespace, systemName)
|
||||
self.mMysqlManager = MysqlManager(self)
|
||||
self.RegisterRpcMethodForMod(consts.GetPlayerRandomNumber, self.OnGetPlayerRandomNumber)
|
||||
|
||||
def OnGetPlayerRandomNumber(self, serverId, callbackId, args):
|
||||
player = args["player"]
|
||||
lottery = self.mMysqlManager.GetUnfinishedLottery()
|
||||
if lottery == -1:
|
||||
eventData = {
|
||||
"code": 1,
|
||||
"msg": -1
|
||||
}
|
||||
else:
|
||||
eventData = {
|
||||
"code": 0,
|
||||
"msg": self.mMysqlManager.GetPlayerRandomNumber(player, lottery)
|
||||
}
|
||||
|
||||
self.ResponseToServer(serverId, callbackId, eventData)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 代码下载
|
||||
|
||||
教程中仅展示了部分代码,全部代码可以在这里下载。
|
||||
|
||||
[抽奖插件——service部分](https://g79.gdl.netease.com/pluginguide04-06.zip)
|
||||
|
||||
随后我们就可以部署并测试啦!
|
||||
|
||||
122
mconline/30-网络服插件教程/4-插件制作/6-插件编写——制作篇(下).md
Normal file
@@ -0,0 +1,122 @@
|
||||
# 插件编写——制作篇(下)
|
||||
|
||||
### Game/Lobby编写
|
||||
|
||||
接下来开始制作Game/Lobby服的插件,我们按照官方要求的规范更改文件夹soldierLottery的命名。并创建脚本文件夹开始编写脚本。
|
||||
|
||||
同时由于我们这个插件不需要行为包和资源包,所以将对应文件夹删除。
|
||||
|
||||
更改命名后文件夹名字如下
|
||||
|
||||
<img src="./images/code-8.png" style="zoom:200%;" />
|
||||
|
||||
然后开始编写Game/Lobby和Service通信的部分,基础部分将不再过多讲解。
|
||||
|
||||
**下方编写的都是```lotteryServerSystem.py```中的代码**
|
||||
|
||||
```python
|
||||
def GetPlayerRandomNumber(self, uid, callback):
|
||||
"""
|
||||
为玩家获取一个随机彩票号码
|
||||
:param uid: 玩家UID
|
||||
:param callback:int 参数唯一,随机彩票号码,请求失败值为-1
|
||||
:return:
|
||||
"""
|
||||
self.RequestToServiceMod(ServiceServerType, GetPlayerRandomNumber, {"player": uid},
|
||||
lambda suc, args: callback(args["msg"]) if suc else callback(-1))
|
||||
```
|
||||
|
||||
例如这个接口,调用后就可以为玩家申请一个随机彩票号码,然后将号码带入回调函数的参数中并调用。Service端的对应相关代码,可以在上一节的最后找到。
|
||||
|
||||
同时我们还需要监听玩家加入和退出,并根据是否转服判断是否需要通知Service服务器对缓存进行处理。
|
||||
|
||||
```python
|
||||
def OnJoin(self, args):
|
||||
playerId = args["id"]
|
||||
uid = args["uid"]
|
||||
self.idUidMap[playerId] = uid
|
||||
if not args["isTransfer"]:
|
||||
print "玩家{}加入,进行缓存".format(uid)
|
||||
self.CachePlayer(uid)
|
||||
|
||||
def OnLeave(self, args):
|
||||
playerId = args["id"]
|
||||
uid = args["uid"]
|
||||
del self.idUidMap[playerId]
|
||||
if not args["isTransfer"]:
|
||||
print "玩家{}退出,删除缓存".format(uid)
|
||||
self.SavePlayer(uid)
|
||||
|
||||
def CachePlayer(self, uid):
|
||||
def callback(suc, args):
|
||||
print "缓存玩家: suc:{} ,args:{}".format(suc, args)
|
||||
|
||||
self.RequestToServiceMod(ServiceServerType, PlayerJoin, {"player": uid}, callback)
|
||||
|
||||
def SavePlayer(self, uid):
|
||||
def callback(suc, args):
|
||||
print "保存玩家: suc:{} ,args:{}".format(suc, args)
|
||||
|
||||
self.RequestToServiceMod(ServiceServerType, PlayerLeave, {"player": uid}, callback)
|
||||
```
|
||||
|
||||
然后再监听聊天事件,判断玩家是否输入cp1,cp2,并编写相应逻辑。
|
||||
|
||||
```python
|
||||
def OnServerChat(self, args):
|
||||
playerId = args["playerId"]
|
||||
message = args["message"]
|
||||
uid = self.idUidMap[playerId]
|
||||
if message == "cp1":
|
||||
args["cancel"] = True
|
||||
|
||||
def callback(number):
|
||||
if number != -1:
|
||||
self.alertSystem.Alert(playerId, '§f本次领取的号码是 §a{}'.format(number), 3)
|
||||
else:
|
||||
self.alertSystem.Alert(playerId, '§f今日领取的号码已超过上限', 3)
|
||||
|
||||
self.GetPlayerRandomNumber(uid, callback)
|
||||
|
||||
elif message == "cp2":
|
||||
args["cancel"] = True
|
||||
|
||||
def callback(numbers):
|
||||
if numbers is None:
|
||||
self.alertSystem.Alert(playerId, '§c获取失败,请稍后再试', 3)
|
||||
return
|
||||
size = len(numbers)
|
||||
if size == 0:
|
||||
self.alertSystem.Alert(playerId, '§c你还没有领取任何号码', 3)
|
||||
else:
|
||||
self.alertSystem.Alert(playerId, '§c今日已领取{}个号码: {}'.format(size, self.FormatNumbers(numbers)), 3)
|
||||
|
||||
self.GetPlayerNumbers(uid, callback)
|
||||
```
|
||||
|
||||
这里使用调用了neteaseAlert插件来发送提示,我们可以到官方插件库中下载neteaseAlert并查看readme.txt,找到我们需要的接口文档。
|
||||
|
||||
> (1)服务端向某一个玩家弹出一个提示,内容最多显示五行
|
||||
> 函数:Alert(playerId, text, seconds, xratio, yratio, priority)
|
||||
> 参数:
|
||||
> playerId: 玩家的playerId
|
||||
> text: 需要提示的内容
|
||||
> seconds: 内容显示停留的秒数,不传则用mod.json的default_show_time设置
|
||||
> xratio: 提示背景框中心与主屏横轴的位置比,0-1之间的小数,不传则用mod.json的default_xratio设置
|
||||
> yratio: 提示背景框中心与主屏纵轴的位置比,0-1之间的小数,不传则用mod.json的default_yratio设置
|
||||
> priority: 消息显示优先级,数值越大优先级越大。优先级大于0时消息按照优先级排序,顺序显示;优先级小于0时,覆盖显示当前消息,并清空之前保存的消息。默认优先级是-1
|
||||
> 示例:
|
||||
> import server.extraServerApi as serverApi
|
||||
> alertSystem = serverApi.GetSystem("neteaseAlert", "neteaseAlertDev")
|
||||
> alertSystem.Alert(playerId, '§c摊位方块只能放置于规定的摆摊区域。', 2, 0.5, 0.8, 50) # 提示框中点位于(横屏水平方向大小*0.5, 横屏竖直方向大小*0.8)处
|
||||
|
||||
|
||||
|
||||
### 代码下载
|
||||
|
||||
教程中仅展示了部分代码,全部代码可以在这里下载。
|
||||
|
||||
[彩票插件——lobby/game部分](https://g79.gdl.netease.com/pluginguide04-05.zip)
|
||||
|
||||
随后我们就可以部署并测试啦!
|
||||
|
||||
105
mconline/30-网络服插件教程/4-插件制作/7-插件编写——测试篇.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# 插件编写——测试篇
|
||||
|
||||
编写完插件,就可以部署并测试了。
|
||||
|
||||
利用[插件编写——制作篇(中)](./5-插件编写——制作篇(中).html#代码下载)和[插件编写——制作篇(下)](./6-插件编写——制作篇(下).html#代码下载)下载的插件代码,可跟着教程一同部署并测试。
|
||||
|
||||
|
||||
|
||||
## 部署过程
|
||||
|
||||
1. 执行mod.sql
|
||||
2. 配置项中勾选相关插件
|
||||
3. 执行部署
|
||||
4. 启动游戏,查看效果
|
||||
|
||||
部署过程在之前已经讲过,忘记了的可以[点我](../2-初识插件/2-应用插件.html)进行回顾。
|
||||
|
||||
在勾选相关插件的步骤里,因为我们使用到了多个官方插件,所以勾选时请注意将它们全部勾选上。
|
||||
|
||||
下方列出了不同类型的服务器需要勾选的插件。
|
||||
|
||||
### Game/Lobby服
|
||||
|
||||
- neteaseAlert
|
||||
- neteaseAnnounce
|
||||
- soldierLottery
|
||||
|
||||
### Master服
|
||||
|
||||
- neteaseAnnounceMaster
|
||||
|
||||
### Service服
|
||||
|
||||
- neteaseAnnounceService
|
||||
- soldierLotteryService
|
||||
|
||||
> 需要注意的是,由于示例插件中,请求到的service服务器类型固定是"service",所以在配置Service服务器时,在类型处请填写"service"。
|
||||
>
|
||||
> 如果需要修改,可以在consts.py中修改ServiceServerType。
|
||||
|
||||
设置完成后,点击部署即可进行部署。
|
||||
|
||||
随后便可以进入游戏测试效果。
|
||||
|
||||
### 效果测试
|
||||
|
||||
聊天框输入cp1 领取号码
|
||||
|
||||

|
||||
|
||||
聊天框输入cp2 查看已领取的号码
|
||||
|
||||

|
||||
|
||||
## 调试工具与功能展示
|
||||
|
||||
### 服务器日志
|
||||
|
||||
在开发者工具中,对服务器右键,可以看到查看服务器日志选项。
|
||||
|
||||
点击即可查看所有服务器的日志。
|
||||
|
||||
在服务器脚本开发中,使用```print "msg"```语句,即可将日志信息打印到其中。
|
||||
|
||||

|
||||
|
||||
### 脚本日志
|
||||
|
||||
脚本日志即客户端日志。在客户端启动后会随之出现。界面如图。
|
||||
|
||||
在客户端脚本开发中,使用```print "msg"```语句,即可将日志信息打印到其中。
|
||||
|
||||

|
||||
|
||||
如果关闭后需要重新打开,可以对服务器右键,点击“查看脚本测试日志”重新打开。
|
||||
|
||||
### 控制台调试
|
||||
|
||||
控制台调试中分为脚本,原生指令,POST指令三个标签。
|
||||
|
||||
#### 脚本
|
||||
|
||||
在指定服务器执行python脚本。
|
||||
|
||||
例如在本示例插件中,在Service服务器中执行下方代码,即可强制抽奖进行开奖。
|
||||
|
||||
```python
|
||||
import mod.server.extraServiceApi as serviceApi
|
||||
system = serviceApi.GetSystem("soldierLottery","soldierLotteryService")
|
||||
system.FinishLottery()
|
||||
```
|
||||
|
||||

|
||||
|
||||
#### 原生指令
|
||||
|
||||
原生指令即Minecraft自带的指令,可以执行op,gamemode等类似指令。
|
||||
|
||||

|
||||
|
||||
#### Post指令
|
||||
|
||||
Post指令即Master服务器提供的HTTP指令。可以在面板中选择或输入你需要执行的指令,例如查看在线玩家列表。
|
||||
|
||||

|
||||
67
mconline/30-网络服插件教程/4-插件制作/8-插件二次开发.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# 插件二次开发
|
||||
|
||||
在本节,我们将会修改之前写好的插件,对其进行二次开发。
|
||||
|
||||
## 需求
|
||||
|
||||
使用之前的抽奖插件,改造一个挖宝插件。
|
||||
|
||||
> 前一天20:01~当天20:00是挖宝时间。玩家破坏泥土方块有10%概率获得1个号码,每日每名玩家最多获得3个号码。每日20:00开奖,通过邮件(官方neteaseAnnounce插件中包含)告知中奖玩家并发放奖励。
|
||||
|
||||
- 废弃原插件输入cp1领取号码的功能
|
||||
- 玩家可在game服破坏泥土方块,有10%概率获得号码,若成功获得,用官方neteaseAlert插件提示“本次领取的号码是%s”
|
||||
- 奖励号码每日开奖后重置,玩家可重新通过破坏泥土方块获得号码。
|
||||
- 若超过当日可领取的上限:3,不能再通过挖掘泥土方块获得号码。
|
||||
- 每日开奖时间之前(上一天的20:01~当天20:00),输入cp2可查询已领取的号码。
|
||||
- 官方neteaseAlert插件提示已领取的号码情况“今日已领取%s(数量)个号码:%s、%s、%s”
|
||||
- 若玩家获得号码6666,可在发奖时领到特别大奖。
|
||||
- 从1~10000中随机抽取5个号码作为本期中奖号码。(抽取的号码不含6666)
|
||||
- 获得特别大奖者的邮件提示:恭喜你获得本期特别大奖!邮件附件内容:绿宝石*100
|
||||
- 本期没有玩家获得特别大奖——本期中奖号码:%s、%s、%s、%s、%s。恭喜你,在本期抽奖活动中中奖,请再接再厉。邮件附件内容:黑曜石*5
|
||||
- 本期有玩家获得特别大奖——本期特别大奖由%s获得。另外本期中奖号码:%s、%s、%s、%s、%s。恭喜你,在本期抽奖活动中中奖,请再接再厉。邮件附件内容:黑曜石*5
|
||||
- 本期没有玩家获得特别大奖——本期中奖号码:%s、%s、%s、%s、%s。很遗憾,在本期抽奖活动中您未能中奖,请继续努力。邮件附件内容:铁锭*3
|
||||
- 本期有玩家获得特别大奖——本期特别大奖由%s获得。另外本期中奖号码:%s、%s、%s、%s、%s。很遗憾,在本期抽奖活动中您未能中奖,请继续努力。邮件附件内容:铁锭*3
|
||||
- 当天没有领取任何号码的玩家无需发放邮件。
|
||||
|
||||
## 制作
|
||||
|
||||
### 修改
|
||||
|
||||
根据需求,我们首先需要删除cp1的相关监听。
|
||||
|
||||
然后监听破坏泥土方块事件,完成获取号码的相关逻辑。
|
||||
|
||||
并修改开奖的逻辑,判断是否有玩家获得超级大奖。
|
||||
|
||||
### 注意事项
|
||||
|
||||
由于我们需要同时筛选数据表中的lottery和number列,来查找是否有玩家获取了超级大奖。
|
||||
|
||||
之前的索引并未覆盖到这两列,所以我们需要更新一下mod.sql,增加一个索引。
|
||||
|
||||
SQL语句如下
|
||||
|
||||
```sql
|
||||
# 增加索引
|
||||
ALTER TABLE `soldierLotteryPlayer`
|
||||
ADD UNIQUE INDEX `lottery_number`(`lottery`, `number`) USING BTREE COMMENT '彩票id和彩票号码的索引';
|
||||
```
|
||||
|
||||
### 下载
|
||||
|
||||
修改了相关代码后的插件下载地址:
|
||||
|
||||
[二次开发的彩票插件](https://g79.gdl.netease.com/pluginguide04-08.zip)(含service与lobby/game部分)。
|
||||
|
||||
|
||||
|
||||
## 测试
|
||||
|
||||
在game服中挖掘泥土,可以看到有概率成功领取到号码。
|
||||
|
||||

|
||||
|
||||
测试开奖,与预期一致
|
||||
|
||||

|
||||
|
||||
BIN
mconline/30-网络服插件教程/4-插件制作/images/1.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
mconline/30-网络服插件教程/4-插件制作/images/code-1.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
mconline/30-网络服插件教程/4-插件制作/images/code-10.png
Normal file
|
After Width: | Height: | Size: 930 KiB |
BIN
mconline/30-网络服插件教程/4-插件制作/images/code-11.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
mconline/30-网络服插件教程/4-插件制作/images/code-12.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
mconline/30-网络服插件教程/4-插件制作/images/code-13.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
mconline/30-网络服插件教程/4-插件制作/images/code-14.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
mconline/30-网络服插件教程/4-插件制作/images/code-15.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
mconline/30-网络服插件教程/4-插件制作/images/code-2.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
mconline/30-网络服插件教程/4-插件制作/images/code-3.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
mconline/30-网络服插件教程/4-插件制作/images/code-4.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
mconline/30-网络服插件教程/4-插件制作/images/code-5.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
mconline/30-网络服插件教程/4-插件制作/images/code-6.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
mconline/30-网络服插件教程/4-插件制作/images/code-7.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
mconline/30-网络服插件教程/4-插件制作/images/code-8.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
mconline/30-网络服插件教程/4-插件制作/images/code-9.png
Normal file
|
After Width: | Height: | Size: 915 KiB |
BIN
mconline/30-网络服插件教程/4-插件制作/images/img01.png
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
mconline/30-网络服插件教程/4-插件制作/images/mod-1.png
Normal file
|
After Width: | Height: | Size: 226 KiB |
BIN
mconline/30-网络服插件教程/4-插件制作/images/mod-2.png
Normal file
|
After Width: | Height: | Size: 147 KiB |
BIN
mconline/30-网络服插件教程/4-插件制作/images/xmind.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
54
mconline/30-网络服插件教程/4-插件制作/设计文档示例.md
Normal file
@@ -0,0 +1,54 @@
|
||||
## 示例文档:随身仓库插件
|
||||
|
||||
### 1、功能描述
|
||||
|
||||
1)点击界面入口可打开随身仓库。(作为本需求的示例,入口可通过官方的主菜单插件打开即可)
|
||||
2)仓库的列数固定为7,行数可动态适配,最多32行,超出显示的部分通过翻页或滚动实现。
|
||||
3)随身仓库的界面示意,可参考下图。
|
||||
a、配置为初始行的仓库格子,状态是已解锁。其余格子是上锁状态。
|
||||
b、点击任意上锁状态的格子,弹出二次确认弹窗,弹窗内容通过本行解锁提示配置获取。
|
||||
c、上述弹窗选择确认,但物品、货币不足的情况下,用官方插件neteaseAlert进行弹窗提示“解锁所需物品或货币不足!”
|
||||
d、上述弹窗选择确认且物品、货币足够的情况下,扣除响应物品、货币并开启下一行格子。用官方插件neteaseAlert进行弹窗
|
||||
提示“成功解锁第%s行仓库格子”
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 2、配置说明
|
||||
|
||||
1)随身仓库初始行数,最小值是0。
|
||||
2)随身仓库最大行数。
|
||||
3)非初始行的解锁消耗:支持物品+货币两种形式,其中货币需要将官方的经济插件作为前置。若解锁配置设为 -1,则由开发者根据自己写的判断进行解锁。
|
||||
配置说明举例:
|
||||
初始行数 0
|
||||
最大行数 10
|
||||
解锁配置 [行数区间]:(货币dough_id,货币数量,[(物品1identifier:aux,物品数量1),(物品2identifier:aux,物品数量2),...],"本行解锁提示")
|
||||
|
||||
### 3、API需求
|
||||
|
||||
1)服务端API:某uid玩家打开随身仓库。
|
||||
2)服务端API:设置某uid玩家随身仓库解锁多少行。(假如当前已解锁3行,调用这个API,解锁行数设为3,则第4~6行进行解锁)
|
||||
3)服务端API:查询某uid玩家随身仓库已解锁的行数。
|
||||
4)服务端API:删除仓库中某一格的物品。
|
||||
5)服务端API:删除仓库中所有的物品。
|
||||
|
||||
|
||||
|
||||
### 4、事件需求
|
||||
|
||||
1)服务端事件:点击任意上锁状态格子时抛出,参数包含:玩家uid、
|
||||
2)服务端事件:成功解锁时抛出,参数包含:玩家uid、当前解锁的行数集合
|
||||
|
||||
|
||||
|
||||
### 5、运营指令
|
||||
|
||||
1)某uid玩家打开随身仓库。
|
||||
2)设置某uid玩家随身仓库解锁多少行。
|
||||
3)查询某uid玩家随身仓库已解锁的行数。
|
||||
4)设置某uid玩家随身仓库上锁多少行。原来已解锁格子中的物品,设为上锁状态后将无法放入但可以取出,直到重新解锁。
|
||||
5)删除仓库中某一格的物品。
|
||||
6)删除仓库中所有的物品。
|
||||