同步官网文档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,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删除仓库中所有的物品。