同步官网文档8m_25d

This commit is contained in:
kwiilh
2025-08-25 18:36:29 +08:00
parent 4dc0ecf18d
commit 9e8855eeb4
5089 changed files with 8798 additions and 4799 deletions

View 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中的内容发送到客户端去并于服务器进行双向通信。
这样做可以防止代码遭到逆向工程爆破,从而保护了服务端插件的代码。
并且插件也可以相互调用,互相依赖,将服务器玩法进行模块化拆分。
## 为什么采用插件
这么做的目的是——**效率**。
将服务器需要用到的一个个小功能做成一个个插件。例如将领地保护功能、权限管理功能、箱子锁功能······纷纷做成一个个的小插件。这些功能虽然不能为服务器带来核心玩法,但是却是一个我的世界网络服的基石。编写插件,就可以让这些基础功能在每一个服务器上都能够安装,管理,减少重复开发。
同时一个个的小插件,正对应着模块化设计的理念。
哪个模块出现问题,需要改进,就可以轻松定位,轻松修复。大大提升了开发效率。

View File

@@ -0,0 +1,15 @@
# 必备知识
如果掌握了下方任意一种开发技能那么掌握Apollo插件开发将会事半功倍。
## Java服插件开发基础
Java服插件开发即使用Java或Kotlin等jvm语言使用Spigot等类似服务端进行玩法开发。
虽然中国版Apollo插件的开发是使用Python进行开发但是相信在掌握了jvm语言的前提下会更容易上手并且更容易理解Apollo插件的开发逻辑。
## 中国版Mod开发
如果已经掌握中国版Mod开发那么学习Apollo插件的开发将会更加轻松。因为Apollo插件的开发就是在ModSDK的基础上新增了一系列服务器类型的概念和专用的接口并且提供了高性能的数据库后端供数据存取。关于Apollo框架的结构将会在后面的章节讲到。
对于已经会编写中国版Mod的开发者来说学习Apollo插件编写需要将更多的精力放在了解服务器插件的工作流程上。

View File

@@ -0,0 +1,42 @@
# 网络服的概念
## 简介
网络服,即运行在远程服务器上的一个游戏服务器,通过网络供玩家连接。
每个网络游戏都需要一个专用的服务器来进行游戏内部数据的计算,并将运算结果发送给客户端,呈现给玩家。
下面介绍的是一些经典的网络服软件,供读者了解。
### Minecraft Server
Mojang官方编写的我的世界Java版服务器软件。
### Spigot
一个使用Java编写的我的世界Java版服务器软件。
### Bedrock Server
Mojang官方编写的我的世界基岩版服务器软件。
### Nukkit
一个由国人使用Java编写的我的世界基岩版服务器软件。
## 网络服和Mod的区别
### 架构差异
我的世界的联机功能和单人游戏功能其实都是在房主的终端下新建了一个内部服务器再使用客户端部分进行游戏画面的渲染。Mod的服务器运算部分就是运行在房主的终端上的。
而网络服软件,专门将游戏的服务器运算部分,从客户端中分离出来,并将其运行在一个相对于玩家终端来说更加高性能的服务器上,来实现更高的玩家承载量和稳定性。
换而言之在网络服上游玩的玩家的数据全部会交给专业服务器进行处理。而Mod联机的情况下所有玩家的数据都会在房主的设备上进行处理如果设备的算力不够就会非常影响游戏体验。
### 数据存储
在Mod联机的情况下产生的数据大多都会存储到地图文件中并由读取地图时一并加载到游戏中。
而在网络服中可以使用高性能的数据库例如MySQL,MongoDB,Redis来存储数据并快速筛选出需要的数据进行缓存。数据库软件各有各的优势开发者可以根据需要自行决定数据的存储位置。

View File

@@ -0,0 +1,90 @@
# 数据库的概念
在服务器插件的开发中,大多数数据都会被存储到数据库。
## 简介
数据库在很多服务器软件的开发中它起到了非常重要的数据存储和筛选的作用在Apollo插件的开发中也不例外。
Apollo目前支持3种数据库MySQL、Redis和MongoDB它们各有各的特点下面将进行依次介绍。
### MySQL
MySQL是一种关系型数据库它的数据存储格式和我们常用的excel非常相似。
每个MySQL的数据表中都有提前设定好的列用来代表这一列的数据的类型和名称。各个列的数据组合叫做行。位于同一行的数据可以一同被选择。
为了更好的理解下面举一个Excel和MySQL对比的例子。
![](./images/mysql-1.png)
如上图所示这个是一个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。
![](./images/navicat-1.png)
2. 在“常规”标签页下,输入连接名(任意,用于区分连接),用户名 *minecraft*,密码 *minecraft* (以上均为默认密码)。
![](./images/navicat-2.png)
3. 在“SSH”标签页下勾选使用SSH隧道填写主机端口*32200*,用户名*fuzhu*,验证方法*公钥*选择私钥路径。通行短语输入你创建RSA密钥时输入的密码。勾选保存通行短语并保存。
![](./images/navicat-3.png)
4. 随后可在左边区域看到你新建的连接,双击即可进行连接。
![](./images/navicat-4.png)

View 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位
![](./images/rsa-2.png)
3. 输入一个方便区分的密钥名称,并为密钥设置密码,此密码需要牢记,丢失后只能重新创建密钥,**无法找回**
![](./images/rsa-3.png)
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
访问后效果如下,稍候即可连接服务器。
![](./images/white-1.png)
## 私钥使用
### 视频教程
<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连接后的示意。
![](./images/ssh-6.png)
### 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%;" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

@@ -0,0 +1,37 @@
# 插件下载
本节将展示官方插件,第三方插件的使用流程,供读者了解。
## 下载入口
在基岩版网络服的插件标签页下,右上角可以看到官方插件和更多插件按钮。
![](./images/download-1.png)
目前官方插件和第三方插件涵盖了大部分的类型。包括但不限于:
- 聊天
- 工具
- 世界管理
- 经济
- 机制
未来官方会继续上传更多官方插件并扶持、培养更多第三方开发者扩充插件库降低Apollo的开服门槛。
## 插件展示
### neteaseDaily
该插件给服务器带来了每日登录奖励的功能。
效果如图。
![](./images/intro-1.png)
### neteaseMenus
该插件为服务器提供了快捷菜单功能。
效果如图。
![](./images/intro-2.png)

View 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. 点击官方插件
![](./images/deploy-1.png)
3. 点击官方插件,找到每日登陆奖励,点击全部下载,点击后效果如图。
<img src="./images/deploy-2.png" style="zoom:150%;" />
4. 关闭官方插件界面找到neteaseDailyService点击更多打开目录复制mod.sql文件。
![](./images/deploy-3.png)
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。网络服开发 中,找到你创建的网络服。右键点击,选择服务器配置。
![](./images/deploy-6.png)
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)。

View 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命令也会被服务器拦截。**
![](./images/op-2.png)
3. 在原生指令下,选择服务器(这里以lobby为例),输入```op 玩家名```。
<img src="./images/op-3.png" style="zoom:200%;" />
完成以上操作,即可在游戏中使用作弊命令。
## 注意事项
但是上方操作重新部署后会失效,后期可以自行制作插件,在服务器启动时执行上方代码,即可保持开启作弊模式。

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View 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服插件开发者们提前了解并转变思想。
## 架构
![](./images/structure-1.png)
如上图所示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)。

View 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发送玩家的uidService收到请求后查询玩家今天是否登陆过并返回数据给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=Trueargs= {'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>查看。

View 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-手机网络游戏/课程2Apollo基础知识/第3节服务器Mod.html" rel="noopenner"> 第3节服务器Mod </a>中说明。
开发者可下载[生成插件模板工具](https://g79.gdl.netease.com/template.zip),在实际应用中快捷生成插件目录。

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

View 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
1MenusNavigateEvent
适用范围:客户端
命名空间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中搜索“经济插件”并把对应的获取货币和更新货币的地方换成自己的接口管理即可
- 交易物品:玩家可以通过选择背包中的物品及数量进行交易
```

View File

@@ -0,0 +1,98 @@
# 知识点拆解
本节将通过官方插件的分析,加强概念的理解。
## 文件结构
按照下列流程找到之前举例安装的neteaseDaily的文件夹。
1. 基岩版服务器
2. 插件
3. neteaseDaily
4. 右键,打开目录
在弹出的文件夹中可以看到插件目录下有4个文件夹。
![](./images/1.png)
那么behavior_packs中所包含的就是发送到玩家设备上运行的客户端moddeveloper_mods中所包含的是运行在服务器上的modresource_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查找到对应的背包信息。

View File

@@ -0,0 +1,36 @@
# 插件编写——需求篇
在开始编写插件之前我们需要首先明确插件需要什么功能具体细节是什么样的以及各种用于二次开发的API需求和事件需求。
在接下来的篇幅中,将以抽奖插件为例,按照流程制作一个完整的插件。
## 插件需求
每日进行1期抽奖每位玩家可随机领取3个不同的号码号码取值是1~10000领完为止。每日2000开奖通过邮件官方neteaseAnnounced插件告知中奖玩家并发放奖励。
## 细节需求
- 玩家可在game服或lobby服输入cp1领取号码成功领取后用官方neteaseAlert插件提示“本次领取的号码是%s”
- 奖励号码每日开奖后重置,玩家可重新领取。
- 若超过当日可领取的上限3输入cp1领取时用官方neteaseAlert插件提示“今日领取的号码已超过上限”
- 每日开奖时间之前上一天的2001~当天2000输入cp2可查询已领取的号码。
- 官方neteaseAlert插件提示已领取的号码情况“今日已领取%s数量个号码%s、%s、%s”
- 从1~10000中随机抽取5个号码作为本期中奖号码。
- 中奖的邮件提示:本期中奖号码:%s、%s、%s、%s、%s。恭喜你在本期抽奖活动中中奖请再接再厉。邮件附件内容黑曜石*5
- 没中奖的邮件提示:本期中奖号码:%s、%s、%s、%s、%s。很遗憾在本期抽奖活动中未能中奖请继续努力。邮件附件内容铁锭*3
- 当天没有领取任何号码的玩家无需发放邮件。
## API需求
- 给指定玩家随机获得1个号码必须是本期活动没有获得过的如果没有号码则返回-1)
- 返回玩家已获取的号码数量
- 返回玩家已获取的号码列表
- 重置某个玩家的号码
- 重置所有号码
- 执行发奖,返回获奖的号码列表以及获奖玩家列表
## 事件需求
- 发奖时刻之前触发——返回中奖号码,可修改中奖号码
- 发奖时刻之后触发——返回中奖号码与中奖玩家

View File

@@ -0,0 +1,87 @@
# 插件编写——制作篇(上)
## 需求分析
之前提到过Apollo插件拥有3种不同的类型我们在开始编写前首先需要思考哪些需求可以运行在哪个类型的服务器上。
结构图如下
![](./images/xmind.png)
### Game/Lobby
- 输入cp1领取号码成功领取后用官方neteaseAlert插件提示“本次领取的号码是%s”
- 若超过当日可领取的上限3输入cp1领取时用官方neteaseAlert插件提示“今日领取的号码已超过上限”
- 每日开奖时间之前上一天的2001~当天2000输入cp2可查询已领取的号码。
- 官方neteaseAlert插件提示已领取的号码情况“今日已领取%s数量个号码%s、%s、%s”
> 为什么聊天输入相关的监听需要做在Game/Lobby服务器
因为玩家只会在Game/Lobby上在线只有在这两种服务器上才能监听玩家的聊天信息。
### Service
- 每日2000开奖通过邮件官方neteaseAnnounce插件告知中奖玩家并发放奖励。
- 中奖的邮件提示:本期中奖号码:%s、%s、%s、%s、%s。恭喜你在本期抽奖活动中中奖请再接再厉。邮件附件内容黑曜石*5
- 没中奖的邮件提示:本期中奖号码:%s、%s、%s、%s、%s。很遗憾在本期抽奖活动中未能中奖请继续努力。邮件附件内容铁锭*3
- 服务器API——给指定玩家随机获得1个号码必须是本期活动没有获得过的如果没有号码则返回-1)
- 服务器API——返回玩家已获取的号码数量
- 服务器API——返回玩家已获取的号码列表
- 服务器API——重置某个玩家的号码
- 服务器API——重置所有号码
- 服务器API——执行发奖返回获奖的号码列表以及获奖玩家列表
- 发奖时刻之前触发——返回中奖号码,可修改中奖号码
- 发奖时刻之后触发——返回中奖号码与中奖玩家
> 为什么“每日2000开奖通过邮件官方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为例
![](./images/code-1.png)
点击启动编辑会打开编辑器。但是我们创建项目的目的是为了快速生成一个Apollo插件模板所以在弹出编辑器窗口后关闭即可。
随后在基岩版组件中找到我们刚刚创建的名为Lottery的AddOn右键选择点击转换为服务器Mod。
<img src="./images/code-2.png" style="zoom:150%;" />
弹出的窗口中,选项不需要做特别修改,直接点击转换。
随后在基岩版服务器插件中找到刚刚创建的Lottery右键打开目录。因为我们之前已经将需求归类总结出需要Game/Lobby服和Service服的插件所以我们将文件夹复制并重命名成符合规范的。这里我们复制一份Lottery并重命名成soldierLottery再将原来的文件夹重命名成soldierLotteryService。
操作完成后文件夹的样子应该如截图所示。
![](./images/code-3.png)

View File

@@ -0,0 +1,271 @@
# 插件编写——制作篇(中)
## Service编写
根据之前对Service的了解Service中是只应该有developer_mods文件夹的而我们现在的soldierLotteryService文件夹因为是从Game/Lobby复制而来的所以需要手动将没有用的文件夹删除。只留下developer_mods文件夹删除下图中选择的文件夹。
![](./images/code-4.png)
然后我们根据官方插件规范将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是否开奖
![](./images/code-7.png)
因为这张表我们只需要通过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。
![](./images/code-5.png)
由于功能上需要查询指定抽奖id的玩家拥有的号码同时需要检索player和lottery两列数据所以我们需要给它们加上索引来提高后续的搜索速度。
索引添加如下图。索引涉及到的知识要求相对较高如果不理解可以在MySQL相关教程处学习这里我们简单理解成需要检索哪一列就给哪一列添加索引
![](./images/code-6.png)
随后在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)
随后我们就可以部署并测试啦!

View 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)
随后我们就可以部署并测试啦!

View 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 领取号码
![](./images/code-9.png)
聊天框输入cp2 查看已领取的号码
![](./images/code-10.png)
## 调试工具与功能展示
### 服务器日志
在开发者工具中,对服务器右键,可以看到查看服务器日志选项。
点击即可查看所有服务器的日志。
在服务器脚本开发中,使用```print "msg"```语句,即可将日志信息打印到其中。
![](./images/code-11.png)
### 脚本日志
脚本日志即客户端日志。在客户端启动后会随之出现。界面如图。
在客户端脚本开发中,使用```print "msg"```语句,即可将日志信息打印到其中。
![](./images/code-14.png)
如果关闭后需要重新打开,可以对服务器右键,点击“查看脚本测试日志”重新打开。
### 控制台调试
控制台调试中分为脚本原生指令POST指令三个标签。
#### 脚本
在指定服务器执行python脚本。
例如在本示例插件中在Service服务器中执行下方代码即可强制抽奖进行开奖。
```python
import mod.server.extraServiceApi as serviceApi
system = serviceApi.GetSystem("soldierLottery","soldierLotteryService")
system.FinishLottery()
```
![](./images/code-12.png)
#### 原生指令
原生指令即Minecraft自带的指令可以执行op,gamemode等类似指令。
![](./images/code-15.png)
#### Post指令
Post指令即Master服务器提供的HTTP指令。可以在面板中选择或输入你需要执行的指令例如查看在线玩家列表。
![](./images/code-13.png)

View File

@@ -0,0 +1,67 @@
# 插件二次开发
在本节,我们将会修改之前写好的插件,对其进行二次开发。
## 需求
使用之前的抽奖插件,改造一个挖宝插件。
> 前一天2001~当天2000是挖宝时间。玩家破坏泥土方块有10%概率获得1个号码每日每名玩家最多获得3个号码。每日2000开奖通过邮件官方neteaseAnnounce插件中包含告知中奖玩家并发放奖励。
- 废弃原插件输入cp1领取号码的功能
- 玩家可在game服破坏泥土方块有10%概率获得号码若成功获得用官方neteaseAlert插件提示“本次领取的号码是%s”
- 奖励号码每日开奖后重置,玩家可重新通过破坏泥土方块获得号码。
- 若超过当日可领取的上限3不能再通过挖掘泥土方块获得号码。
- 每日开奖时间之前上一天的2001~当天2000输入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服中挖掘泥土可以看到有概率成功领取到号码。
![](./images/mod-1.png)
测试开奖,与预期一致
![](./images/mod-2.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 915 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

View File

@@ -0,0 +1,54 @@
## 示例文档:随身仓库插件
### 1、功能描述
1点击界面入口可打开随身仓库。作为本需求的示例入口可通过官方的主菜单插件打开即可
2仓库的列数固定为7行数可动态适配最多32行超出显示的部分通过翻页或滚动实现。
3随身仓库的界面示意可参考下图。
a、配置为初始行的仓库格子状态是已解锁。其余格子是上锁状态。
b、点击任意上锁状态的格子弹出二次确认弹窗弹窗内容通过本行解锁提示配置获取。
c、上述弹窗选择确认但物品、货币不足的情况下用官方插件neteaseAlert进行弹窗提示“解锁所需物品或货币不足
d、上述弹窗选择确认且物品、货币足够的情况下扣除响应物品、货币并开启下一行格子。用官方插件neteaseAlert进行弹窗
提示“成功解锁第%s行仓库格子”
![](./images/img01.png)
### 2、配置说明
1随身仓库初始行数最小值是0。
2随身仓库最大行数。
3非初始行的解锁消耗支持物品+货币两种形式,其中货币需要将官方的经济插件作为前置。若解锁配置设为 -1则由开发者根据自己写的判断进行解锁。
配置说明举例:
初始行数 0
最大行数 10
解锁配置 [行数区间]:(货币dough_id,货币数量,[(物品1identifieraux物品数量1),(物品2identifieraux物品数量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删除仓库中所有的物品。