开发教程 [插件编写]TShock5插件编写入门教程

凌筱柒

Lv1
LV
0
 
IP属地
浙江省
2023/10/31
3
0
  • · 发布于浙江省
可以基于直播间弹幕或者礼物生产各种实体或者事件吗?类似于我的世界那种互动玩法,比方说直播间发出史莱姆王的弹幕在服务器生成一只史莱姆王
 

Cai233

Lv3
LV
0
 
IP属地
福建省
2021/08/18
77
26
  • · 发布于陕西省
可以基于直播间弹幕或者礼物生产各种实体或者事件吗?类似于我的世界那种互动玩法,比方说直播间发出史莱姆王的弹幕在服务器生成一只史莱姆王
可以实现
 
最后编辑:

Cai233

Lv3
LV
0
 
IP属地
福建省
2021/08/18
77
26
  • · 发布于香港
1
 
最后编辑:

Cai233

Lv3
LV
0
 
IP属地
福建省
2021/08/18
77
26
  • · 发布于陕西省

Part 5.玩家对象

本章你将学到:
  • 学习TSPlayer类的字段、方法等
  • 学会Player类的常用字段、方法等

什么是TSPlayer类?

TSPlayerTShock的一个类,属于"TShock"的东西。这个类往往包含TShock服务器最常用玩家数据和方法,其中的方法字段几乎全部对服务端都是有效的

什么是Player类?

PlayerTerraria的一个类,属于"Terraria"的东西。这个类包含Terraria常用的玩家数据,这个类里的字段、方法繁多且杂乱,而且还包含了许多客户端才有效的字段、方法,只有部分方法、字段服务端可用

TSPlayer对象

1.获取TSPlayer对象
获取玩家对象的方法有多种,主要根据你的需要选择,下面的代码通过不同的方式获取TSPlayer玩家对象
TSPlayer.FindByNameOrID()方法 (主要用于命令)

TSPlayer.FindByNameOrID()会返回一个列表,包含所有匹配的玩家对象,当然没有匹配结果会返回一个空列表
C#:
private void Test(CommandArgs args)
{
    TSPlayer plr;
    List<TSPlayer> plrs = TSPlayer.FindByNameOrID(args.Parameters[0]);
    //args.Parameters[0]为玩家名字或索引
    //如果参数为 tsi:2 则表示索引为2的玩家
    //如果参数为 tsn:233 则表示名字为233的玩家
    if (plrs.Count == 0)
    {
        args.Player.SendErrorMessage("玩家不存在!"); //发送错误消息
        return;
    }
    else if (plrs.Count > 1)
    {
        args.Player.SendMultipleMatchError(plrs.Select(p => p.Name)); //发送多个匹配结果错误
        //会向玩家发送:
        //找到多个匹配项-无法判断哪个是正确的:
        //玩家1,玩家2....
        //用"半角双引号"包裹关键字来搜索名字带有空格的物品
        //使用 tsi:序号 或者 tsn:名称 来区分含有数字的名称.
        return;
    }
    else
    {
        plr = plrs[0];
        //找到匹配的玩家对象
    }
}
•Index索引获取
C#:
TSPlayer plr= TShock.Players[index]; //index为玩家索引
这种方法非常重要,常常用在ServerApi钩子获取TSPlayer对象
C#:
//插件加载时执行的代码
public override void Initialize()
{
    ServerApi.Hooks.ServerChat.Register(this, OnChat);
    Commands.ChatCommands.Add(new Command(Test, "test"));
}

private void OnChat(ServerChatEventArgs args)
{
    TSPlayer plr = TShock.Players[args.Who]; //获取钩子触发者的TSPlayer对象
}

//插件卸载时执行的代码
protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        ServerApi.Hooks.ServerChat.Deregister(this, OnChat);
    }
    base.Dispose(disposing);
}
•通过Where进行搜索
这种方法用于寻找特定条件的玩家,是一种万能的方法,示例代码如下: (p!=null 很重要,请务必写上)
C#:
string name = "233"; //假设的量
string acc = "233";
string ip = "127.0.0.1";

TSPlayer plr;
//var是推断类型的意思,使用var编译器会自己判断变量的类型,下面是一种偷懒的写法,完整的应该是IEnumerable<TSPlayer> plrs
var plrs= TShock.Players.Where(p => p!=null && p.Name == name); //通过玩家名字获取玩家对象
//var plrs = TShock.Players.Where(p => p != null && p.Account.Name == acc); //通过账号名字获取玩家对象
//var plrs = TShock.Players.Where(p => p != null && p.IP == ip); //通过IP获取玩家对象
//var plrs = TShock.Players.Where(p => p != null && p.Account.Name == acc && p.IP == ip ); //通过IP和账号名字获取玩家对象
//当然如果你需要的是单个玩家对象,你需要使用下列的代码进行判断
//如果你需要的符合条件的所有玩家,那么plrs就是结果
if (plrs.Count() == 0)
{
    //玩家对象不存在
    return;
}
else if (plrs.Count() > 1)
{
    //玩家对象存在多个
    return;
}
else
{
    plr = plrs.First();
    //plr即为玩家对象
}
2.TSPlayer类中的常用属性(加粗的要求记忆,其他的参考就好)
名称类型作用/含义
Namestring玩家角色名
Indexint玩家索引
GroupGroup玩家所处的组对象
AccountUserAccount玩家的账号对象
TPlayerPlayer玩家的Player对象
UUIDstring玩家客户端的UUID
IPstring玩家IP地址
Xfloat玩家的X坐标
Yfloat玩家的Y坐标
TileXint玩家所处图格的X坐标(TileX=[取整]X/16)
TileYint玩家所处图格的Y坐标(TileY=[取整]X/16)
Teamint玩家队伍(无队伍0,红队1,绿队2,蓝队3,黄队4,粉队5)
RealPlayerbool正常玩家(true);控制台或REST(false) [主要用于在命令中区分玩家和控制台]
RespawnTimerint玩家重生计时器(单位:秒)
LoginAttemptsint玩家登录尝试次数
Activebool玩家的活动状态(通常为true)
ConnectionAlivebool玩家的连接状态(正常为true)
DataWhenJoinedPlayerData玩家加入时的人物数据(非SSC)
InventoryIEnumerable<Item>玩家背包(前5行)
AccessoriesIEnumerable<Item>玩家的配饰
InventorySlotAvailablebool玩家背包是否有多余
SelectedItemItem玩家当前手持的物品
Stateint玩家的客户端状态(与加入服务器有关)
LastThreatDateTime玩家上次被冻结(Disable)的时间(Utc)
PaintThresholdint玩家在上一秒漆墙的数量
ProjectileThresholdint玩家在上一秒生成弹幕的数量
TileKillThresholdint玩家在上一秒破坏图格的数量
TileLiquidThresholdint玩家在上一秒放置液体的数量
TilePlaceThresholdint玩家在上一秒放置图格的数量
HealOtherThresholdint玩家在上一秒治疗玩家的次数
TilesCreatedDictionary<Vector2, ITile>玩家放置的待摧毁图格(与图格回弹有关)
TilesDestroyedDictionary<Vector2, ITile>玩家摧毁的待放置图格(与图格回弹有关)
AwaitingNamebool等待玩家获取区域名字
AwaitingNameParameterstring[]获取区域名字时,区域的控制字符(-u 包含保护的区域,-z 包含区域Z索引,-p 玩家将持续接受区域编辑信息?)
AwaitingTempPointint等待玩家设置区域临时点(0未等待,1点1等待中,2点2等待中)
 
最后编辑:

Cai233

Lv3
LV
0
 
IP属地
福建省
2021/08/18
77
26
  • · 发布于香港
2.TSPlayer类中的字段(加粗的要求记忆,其他的参考就好)
字段名类型作用/含义
AcceptingWhispersbool玩家是否接受耳语(私聊) (/w)
ActiveChestint玩家当前打开的箱子ID (玩家没有开箱子则未-1)
AwaitingResponseDictionary<string, Action<object>>玩家等待特定命令的响应的列表(?)
Confusedbool玩家是否处于控制左右颠倒(已失效)
Countrystring玩家的国家代码(需要在Config开启EnableGeoIP)(无数据N/A, 代理A1)
CurrentRegionRegion玩家当前所处的区域
Deadbool玩家是否处于死亡状态
Difficultyint玩家的角色难度(软核0,中核1,硬核2,旅行3)
DisplayLogsbool是否向玩家发送服务器日志
GodModebool玩家是否已启用上帝模式(直接修改不影响玩家的无敌状态)
HasBeenNaggedAboutLoggingInbool玩家是否已经被提示过修改区域需要登录
IceTilesList<Point>玩家放置冰砖(冰杖)的点
IsDisabledForBannedWearablebool玩家是否由于携带禁用物品而被限制行动(Disable)
IsDisabledForSSCbool玩家是否由于没有登录以获取SSC数据而被限制行动(Disable)
IsDisabledForStackDetectionbool玩家是否由于恶意修改物品堆叠而被限制行动(Disable)
IsDisabledPendingTrashRemovalbool玩家是否由于在登录前在垃圾桶放置物品而被限制行动(Disable)
IsLoggedInbool玩家是否已经登录
ItemInHandItem玩家手持的物品
LastKilledProjectileint玩家上一次试图清除的最后一个弹幕
LastNetPositionVector2玩家最后更新时的图格坐标(TileXY)
lastPermissionWarninglong玩家上次被提示无权建筑警告的时间戳
LastPvPTeamChangeDateTime玩家上次切换队伍或PVP状态的时间
LastWhisperTSPlayer玩家上次进行耳语(私聊或被私聊)的对象
LoginHarassedbool玩家是否因为登录而被限制行动(?)
LoginMSlong玩家加入服务器时的时间戳
mutebool玩家是否处于禁言状态
PlayerDataPlayerData玩家的SSC人物数据
RecentFuseint玩家使用炸药的倒计时,即距离下次可用正常使用炸弹剩余时间(单位: 秒)
RecentlyCreatedProjectilesList<GetDataHandlers.ProjectileStruct>跟踪这个玩家最近创建的弹幕OnSecondUpdate() 在异步任务中删除这个。超过5秒的投射物将从这个集合中清除,因为它们不再是“最近的”。
RequestedSectionbool玩家是否已请求过获取地图区块
RequiresPasswordbool玩家是否被要求输入密码(加入服务器前)
RPPendingint玩家传送到上次离开服务器位置的倒计时(单位: 秒)
SilentJoinInProgressbool玩家是否静默加入服务器(不发送玩家加入服务器的提示)
SilentKickInProgressbool玩家是否静默踢出服务器(不发送玩家踢出服务器的提示)
sXint玩家出生点的X坐标
sYint玩家出生点的Y坐标
TeleportCoordsVector2不明,在TShock已弃用
tempGroupGroup玩家当前的临时组(会覆盖原来的组)
tempGroupTimerTimer玩家临时组有效期的计时器
TempPointsPoint[2]玩家设置的区域临时点
TPAllowbool玩家是否允许其他玩家对他使用TP类命令(tshock.tp.override忽略此限制)
2.TSPlayer类中的方法
方法名参数返回值作用
Banstring reason(封禁理由), string adminUserName = null(管理用户名)bool(封禁是否成功)封禁玩家(通常使用TShock.Bans.InsertBan和Kick组合封禁玩家)
DamagePlayerint damage(造成伤害点数)void对玩家造成伤害(会造成击退)[如果你只是想扣除玩家血量,请直接修改他的生命值]
Disablestring reason = ""(理由), DisableFlags flags = DisableFlags.WriteToLog(日志记录模式)void限制玩家行动(石化玩家)
Disconnectstring reason(理由)void断开玩家连接
GiveItemint type(物品ID), int stack(物品堆叠), int prefix = 0(物品修饰语ID)void给予玩家物品
GiveItemCheckint type(物品ID), string name(物品全名[用于检测是否属于封禁物品]), int stack(物品堆叠), int prefix = 0(物品修饰语ID)bool(检查结果)判断玩家是否能被给予某物品
HasBuildPermissionint x(TileX), int y(TileY), bool shouldWarnPlayer = true(是否警告玩家)bool(检查结果)判断玩家是否能编辑此图格
HasBuildPermissionForTileObjectint x(TileX), int y(TileY), int width(宽度), int height(高度), bool shouldWarnPlayer = true(是否警告玩家)bool(检查结果)判断玩家是否能编辑以(X,Y)为左下角,宽度为width,高度为height的矩形(只要范围内有一个图格玩家不能编辑就会返回false)
HasHackedItemStacksbool shouldWarnPlayer = false(是否警告玩家)bool(检查结果)检测玩家是否作弊堆叠(例如:堆叠999999土块)
HasModifiedIceSuccessfullyint x(TileX), int y(TileY), short tileType(图格类型), GetDataHandlers.EditAction editAction(编辑操作类型)bool(检查结果)判断玩家是否成功编辑冰(冰杖生成的)
HasPaintPermissionint x(TileX), int y(TileY)bool(检查结果)判断玩家是否能为此图格涂漆
HasPermissionstring permission(权限)bool(检查结果)判断玩家是否拥有某个权限
(优先级如下:
1.权限钩子修改的结果
2.临时组的权限
3.玩家当前组的权限)
[临时组和玩家组的权限不叠加]
Healint health = 600(治疗点数)void治疗一个玩家
IsBeingDisabledbool(检查结果)判断玩家是否被限制行动
IsBouncerThrottledbool(检查结果)判断玩家是否被Bouncer限制(?)
IsInRangeint x(TileX), int y(TileY), int range = 32(检查半径)bool(检查结果)判断玩家是否在以(X,Y)为圆点range为半径的圆内[当Config的RangeChecks被设为false后无论结果只返回true]
Kickstring reason(理由), bool force = false(强制踢出[无视tshock.admin.nokick权限]), bool silent = false(不发送广播), string adminUserName = null(管理员名), bool saveSSI = false(保存玩家SSC数据)bool(踢出结果)踢出一名玩家
KillPlayervoid杀死玩家(对上帝模式玩家无效)[原理是对玩家造成99999点伤害=DamagePlayer(99999)]
Logoutvoid使玩家登出
RemoveProjectileint index(弹幕ID), int owner(弹幕发射者索引)void移除一个弹幕(?)
SaveServerCharacterbool(保存结果)保存玩家的SSC云存档(服务器没有打开SSC时总返回false)
SetBuffint type(增益ID), int time = 3600(持续帧[1秒=60帧]), bool bypass = false(忽略Bouncer对玩家的限制)void添加玩家BUFF(若玩家已有该BUFF且持续时间更长,你添加的BUFF会被忽略)[BUFF时间不会叠加]
SetPvPbool mode(true打开,false关闭), bool withMsg = false(是否广播PVP打开提示)void切换玩家PVP状态
SetTeamint team(无队伍0,红队1,绿队2,蓝队3,黄队4,粉队5)void切换玩家队伍(不发送广播)
SpawnPlayerSpawnContext context(玩家生成的原因), int? respawnTimer = null(重生倒计时[似乎无效])void复活一个玩家
Spawnint tilex, int tiley, PlayerSpawnContext context, int? respawnTimer = null, short? numberOfDeathsPVE = null, short? numberOfDeathsPVP = nullvoid复活玩家,大部分参数无效,不做解释
Teleportfloat x(X坐标), float y(Y坐标), byte style = 1(TP类型[不做解释])void将玩家传送到某一位置
Whoopieobject time(其实是个int,单位:秒)void发出烦人的声音
!!!需要另开线程使用,否则会卡死主线程
TempGroupTimerElapsed不做解释不做解释不应该被Pubic的内部方法
*部分方法例如:SendDate另做介绍
 
最后编辑:

凌筱柒

Lv1
LV
0
 
IP属地
浙江省
2023/10/31
3
0
  • · 发布于浙江省
可以实现
那像我这样的小白应该从哪方面入手啊,或者是现在已经有成熟的模板了
 

Cai233

Lv3
LV
0
 
IP属地
福建省
2021/08/18
77
26
  • · 发布于香港
•发送消息
字段名参数返回值作用/含义
SendMessagestring msg(内容), Color colorvoid发送自定义颜色消息
SendMessage/SendMessageFromPlayerstring msg(内容), byte red, byte green, byte blue(十进制颜色代码)void发送自定义颜色消息
SendInfoMessagestring msg(内容), params object[] argsvoid发送黄色提示消息
SendSuccessMessagestring msg(内容), params object[] argsvoid发送绿色成功消息
SendWarningMessagestring msg(内容), params object[] argsvoid发送橙色警告消息
SendErrorMessagestring msg(内容), params object[] argsvoid发送红色错误消息
SendFileTextAsMessagestring file(文件路径)void将文本文件内容发送给玩家
(支持替换:
%map%:地图名
%players%:在线玩家
%specifier%:命令标识符
%onlineplayers%:在线玩家数
%serverslots%:最大玩家数)
SendMultipleMatchErrorIEnumerable<object> matches(不好解释)void发送多个匹配错误信息
效果:
1.SendMessage/SendInfoMessage/SendSuccessMessage/SendWarningMessage/SendErrorMessage
C#:
args.Player.SendMessage("我是SendMessage~", 255, 155, 15);
args.Player.SendInfoMessage("我是SendInfoMessage~,你的Index是{0}",args.Player.Index);
args.Player.SendSuccessMessage("我是SendSuccessMessage~,你现在在{0}地图游玩,所在路径是{1}",Main.worldName,Main.worldPathName);
args.Player.SendWarningMessage("我是SendWarningMessage~");
args.Player.SendErrorMessage("我是SendErrorMessage~");
1704009143876.png
2.SendFileTextAsMessage
C#:
args.Player.SendFileTextAsMessage("tshock/motd.txt"); //相对路径
//args.Player.SendFileTextAsMessage("C:/Users/13110/Desktop/code/TShock144/tshock/motd.txt"); //绝对路径
代码:
#motd.txt
欢迎加入[c/ffff00:%map%]在[c/7ddff8:T][c/81dbf6:S][c/86d7f4:h][c/8ad3f3:o][c/8ecef1:c][c/93caef:k]上运行 [c/55d284:T][c/62d27a:e][c/6fd16f:r][c/7cd165:r][c/89d15a:a][c/95d150:r][c/a4d145:i][c/b1d03b:a]服务器.
[c/FFFFFF:在线玩家 (%onlineplayers%/%serverslots%):] [c/FFFF00:%players%]
输入 [c/55D284:%specifier%][c/62D27A:h][c/6FD16F:e][c/7CD165:l][c/89D15A:p] 获取更多帮助信息.
1704009311526.png

3.SendMultipleMatchError
C#:
TSPlayer plr;
List<TSPlayer> plrs = TSPlayer.FindByNameOrID(args.Parameters[0]);
//args.Parameters[0]为玩家名字或索引
//如果参数为 tsi:2 则表示索引为2的玩家
//如果参数为 tsn:233 则表示名字为233的玩家
if (plrs.Count == 0)
{
    args.Player.SendErrorMessage("玩家不存在!"); //发送错误消息
    return;
}
else if (plrs.Count > 1)
{
    args.Player.SendMultipleMatchError(plrs.Select(p => $"{p.Name}({p.Index})")); //发送多个匹配结果错误
                                                                  //会向玩家发送:
                                                                  //找到多个匹配项-无法判断哪个是正确的:
                                                                  //玩家1(0),玩家2(1)....
                                                                  //用"半角双引号"包裹关键字来搜索名字带有空格的物品
                                                                  //使用 tsi:序号 或者 tsn:名称 来区分含有数字的名称.
    return;
}
else
{
    plr = plrs[0];
    args.Player.SendSuccessMessage("找到匹配项!");
    //找到匹配的玩家对象
}
1704009050484.png1704009058632.png
 
最后编辑:

Cai233

Lv3
LV
0
 
IP属地
福建省
2021/08/18
77
26
  • · 发布于香港

Player对象

1.获取Player对象
通过TSPlayer获取(推荐)

C#:
TSPlayer plr;
...(通过上面的方法找到TSPlayer对象)
Player tplr = plr.TPlayer;
•直接通过索引
C#:
Player tplr = Main.player[index];
•通过Where进行搜索(其实建议先找TSPlayer的...)
这种方法用于寻找特定条件的玩家,是一种万能的方法,示例代码如下: (p!=null 很重要,请务必写上)

C#:
string name = "233"; //假设的量
Player tplr;

//var是推断类型的意思,使用var编译器会自己判断变量的类型,下面是一种偷懒的写法,完整的应该是IEnumerable<Player> plrs
var tplrs = Main.player.Where(p => p != null && p.name == name); //通过玩家名字获取玩家对象
if (tplrs.Count() == 0)
{
    //玩家对象不存在
    return;
}
else if (tplrs.Count() > 1)
{
    //玩家对象存在多个
    return;
}
else
{
    tplr = tplrs.First();
    //plr即为玩家对象
}

2.Player对象常用字段
•生命/魔力

字段名类型作用/意义
statLifeint玩家当前生命值
statLifeMaxint玩家生命上限(不包括生命力药水、饰品等额外增加的生命)
statLifeMax2int玩家生命上限(包括生命力药水、饰品等额外增加的生命)[不可修改]
statManaint玩家当前魔力值
statManaMaxint玩家魔力上限(不包括水晶球、饰品等额外增加的魔力)
statManaMax2int玩家魔力上限(包括水晶球、饰品等额外增加的魔力)[不可修改]
•永久增益
字段名类型作用/意义
extraAccessorybool恶魔之心(增加饰品格位)
unlockedBiomeTorchesbool火把神徽章(环境火把)
ateArtisanBreadbool工匠面包(扩大制作站范围)
usedAegisCrystalbool生命水晶(永久强化生命再生)
usedAegisFruitbool埃癸斯果(永久提高防御力)
usedArcaneCrystalbool奥术水晶(永久提高魔力再生)
usedGalaxyPearlbool银河珍珠(永久增加运气)
usedGummyWormbool黏性蠕虫(永久提高钓鱼技能)
usedAmbrosiabool珍馐(永久提高采矿和建造速度)
unlockedSuperCartbool矿车升级包
•背包库存相关
字段名类型 (长度)作用/意义
inventoryItem[] (59)背包(0-49:背包 ; 50-53:钱币 ; 54-58:弹药)
trashItemItem垃圾桶物品
bankChest (item:40)猪猪储钱罐(bank.item)
bank2Chest (item:40)保险箱(bank2.item)
bank3Chest (item:40)护卫熔炉(bank3.item)
bank4Chest (item:40)虚空保险箱(bank4.item)
armorItem[] (20)装备栏
(0:头盔 ; 1:上衣 ; 2裤子 ; 3-9: 配饰)
(10:时装头盔 ; 11:时装上衣 ; 12时装裤子 ; 13-19: 时装配饰)
dyeItem[] (10)染料
CurrentLoadoutIndexint当前玩家装备栏索引
LoadoutsEquipmentLoadout(3)
(Armor:20;Dye:10)
玩家装备栏,eg:
C#:
Item[] armor = tplr.Loadouts[1].Armor;
Item[] dye = tplr.Loadouts[1].Dye;
•群系&环境相关

字段名类型环境/区域/群系
ZoneSkyHeightbool太空
ZoneOverworldHeightbool地表
ZoneDirtLayerHeightbool地下
ZoneRockLayerHeightbool洞穴
ZoneUnderworldHeightbool地狱
ZoneCorruptbool腐化之地
ZoneCrimsonbool猩红之地
ZoneHallowbool神圣之地
ZoneDesertbool沙漠
ZoneUndergroundDesertbool地下沙漠
ZoneJunglebool丛林(雨林)
ZoneLihzhardTemplebool丛林神庙
ZoneBeachbool沙滩
ZoneMarblebool大理石洞
ZoneShimmerbool微光
ZoneGraveyardbool墓地
ZoneDungeonbool地牢
ZoneMeteorbool陨石群系
ZoneGlowshroombool发光蘑菇地
ZoneGranitebool花岗岩洞
ZoneGemCavebool宝石洞
ZoneHivebool蜂巢
ZoneShadowCandlebool暗影蜡烛
ZonePeaceCandlebool和平蜡烛
ZoneWaterCandlebool水蜡烛
ZoneRainbool下雨
ZoneSnowbool雪地
ZoneSandstormbool沙尘暴
ZoneOldOneArmybool撒旦军队
ZoneTowerNebulabool星云柱
ZoneTowerSolarbool日耀柱
ZoneTowerStardustbool星尘柱
ZoneTowerVortexbool星旋柱
•其他
字段名类型作用/意义
ghostbool玩家幽灵状态
luckfloat玩家幸运值
anglerQuestsFinishedint玩家已完成渔夫任务数
golferScoreAccumulatedint玩家已经获得的高尔夫球分数
difficultybyte玩家角色难度(软核0,中核1,硬核2,旅行3)
SelectedItemint玩家当前手持物品的slot(背包索引)
 
最后编辑:

Cai233

Lv3
LV
0
 
IP属地
福建省
2021/08/18
77
26
  • · 发布于香港

习题:

1.编写指令/dead,执行后返回当前死亡玩家列表(绿色成功消息)​

1704012547985.png
C#:
using System.Reflection;
using Terraria;
using TerrariaApi.Server;
using TShockAPI;

namespace Plugin
{
    [ApiVersion(2, 1)]
    public class Plugin : TerrariaPlugin
    {
        //定义插件的作者名称
        public override string Author => "Cai";
        //插件的一句话描述
        public override string Description => "Player";
        //插件的名称
        public override string Name => "Player";
        //插件的版本
        public override Version Version => Assembly.GetExecutingAssembly().GetName().Version;


        //插件的构造器
        public Plugin(Main game) : base(game)
        {
        }

        //插件加载时执行的代码
        public override void Initialize()
        {
            Commands.ChatCommands.Add(new Command(Dead, "dead"));
        }

        private void Dead(CommandArgs args)
        {
            var plrs = TShock.Players.Where(p => p != null && p.Dead); //通过获取死亡的玩家对象
            if (plrs.Count() == 0)
            {
                //玩家对象不存在
                args.Player.SendSuccessMessage($"当前没有玩家死亡");
                return;
            }
            args.Player.SendSuccessMessage($"当前死亡玩家: {string.Join(',',plrs.Select(p=>p.Name))}");
        }


        //插件卸载时执行的代码
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                var asm = Assembly.GetExecutingAssembly();
                Commands.ChatCommands.RemoveAll(c => c.CommandDelegate.Method?.DeclaringType?.Assembly == asm); //卸载命令
            }
            base.Dispose(disposing);
        }
    }
}

2.编写指令"/看看你 {玩家名} ",执行后返回玩家的 生命/生命最大值(魔力无加成最大值)、魔力/魔力最大值(魔力无加成最大值)、玩家的难度(旅行、软、中、硬核)、幸运值luck(绿色成功消息)​

1704012556825.png
C#:
using System.Reflection;
using Terraria;
using TerrariaApi.Server;
using TShockAPI;

namespace Plugin
{
    [ApiVersion(2, 1)]
    public class Plugin : TerrariaPlugin
    {
        //定义插件的作者名称
        public override string Author => "Cai";
        //插件的一句话描述
        public override string Description => "Player";
        //插件的名称
        public override string Name => "Player";
        //插件的版本
        public override Version Version => Assembly.GetExecutingAssembly().GetName().Version;

        //插件的构造器
        public Plugin(Main game) : base(game)
        {
        }

        //插件加载时执行的代码
        public override void Initialize()
        {
            Commands.ChatCommands.Add(new Command(See, "看看你"));
        }

        private void See(CommandArgs args)
        {
            TSPlayer plr;
            if (args.Parameters.Count == 0)
            {
                args.Player.SendErrorMessage($"格式错误!正确格式:{TShock.Config.Settings.CommandSpecifier}看看你 玩家名");
                return;
            }
            List<TSPlayer> plrs = TSPlayer.FindByNameOrID(args.Parameters[0]);
            //args.Parameters[0]为玩家名字或索引
            //如果参数为 tsi:2 则表示索引为2的玩家
            //如果参数为 tsn:233 则表示名字为233的玩家
            if (plrs.Count == 0)
            {
                args.Player.SendErrorMessage("玩家不存在!"); //发送错误消息
                return;
            }
            else if (plrs.Count > 1)
            {
                args.Player.SendMultipleMatchError(plrs.Select(p => p.Name)); //发送多个匹配结果错误
                                                                              //会向玩家发送:
                                                                              //找到多个匹配项-无法判断哪个是正确的:
                                                                              //玩家1,玩家2....
                                                                              //用"半角双引号"包裹关键字来搜索名字带有空格的物品
                                                                              //使用 tsi:序号 或者 tsn:名称 来区分含有数字的名称.
                return;
            }
            else
            {
                plr = plrs[0];
                string difficult = "";
                switch (plr.Difficulty)
                {
                    case 3:
                        difficult = "旅行";
                        break;
                    case 0:
                        difficult = "软核";
                        break;
                    case 1:
                        difficult = "中核";
                        break;
                    case 2:
                        difficult = "硬核";
                        break;
                }
                args.Player.SendSuccessMessage($"{plr.Name}:");
                args.Player.SendSuccessMessage($"生命:{plr.TPlayer.statLife}/{plr.TPlayer.statLifeMax2}({plr.TPlayer.statLifeMax})");
                args.Player.SendSuccessMessage($"魔力:{plr.TPlayer.statMana}/{plr.TPlayer.statManaMax2}({plr.TPlayer.statManaMax})");
                args.Player.SendSuccessMessage($"难度:{difficult}");
                args.Player.SendSuccessMessage($"幸运值:{plr.TPlayer.luck}");
                //找到匹配的玩家对象
            }
        }
        //插件卸载时执行的代码
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                var asm = Assembly.GetExecutingAssembly();
                Commands.ChatCommands.RemoveAll(c => c.CommandDelegate.Method?.DeclaringType?.Assembly == asm); //卸载命令
            }
            base.Dispose(disposing);
        }
    }
}

3.编写指令"/看看你的 {玩家名} ",执行后返回玩家猪猪储蓄罐的钱币详情​

1704013371999.png
C#:
using System.Reflection;
using Terraria;
using Terraria.ID;
using TerrariaApi.Server;
using TShockAPI;

namespace Plugin
{
    [ApiVersion(2, 1)]
    public class Plugin : TerrariaPlugin
    {
        //定义插件的作者名称
        public override string Author => "Cai";

        //插件的一句话描述
        public override string Description => "Player";

        //插件的名称
        public override string Name => "Player";

        //插件的版本
        public override Version Version => Assembly.GetExecutingAssembly().GetName().Version;

        //插件的构造器
        public Plugin(Main game) : base(game)
        {
        }

        //插件加载时执行的代码
        public override void Initialize()
        {
            Commands.ChatCommands.Add(new Command(See, "看看你的"));
        }

        private void See(CommandArgs args)
        {
            TSPlayer plr;
            if (args.Parameters.Count == 0)
            {
                args.Player.SendErrorMessage($"格式错误!正确格式:{TShock.Config.Settings.CommandSpecifier}看看你的 玩家名");
                return;
            }
            List<TSPlayer> plrs = TSPlayer.FindByNameOrID(args.Parameters[0]);
            //args.Parameters[0]为玩家名字或索引
            //如果参数为 tsi:2 则表示索引为2的玩家
            //如果参数为 tsn:233 则表示名字为233的玩家
            if (plrs.Count == 0)
            {
                args.Player.SendErrorMessage("玩家不存在!"); //发送错误消息
                return;
            }
            else if (plrs.Count > 1)
            {
                args.Player.SendMultipleMatchError(plrs.Select(p => p.Name)); //发送多个匹配结果错误
                                                                              //会向玩家发送:
                                                                              //找到多个匹配项-无法判断哪个是正确的:
                                                                              //玩家1,玩家2....
                                                                              //用"半角双引号"包裹关键字来搜索名字带有空格的物品
                                                                              //使用 tsi:序号 或者 tsn:名称 来区分含有数字的名称.
                return;
            }
            else
            {
                plr = plrs[0];
                int copper = 0, silver = 0, gold = 0, platinum = 0;
                foreach (Item item in plr.TPlayer.bank.item)
                {
                    switch (item.netID)
                    {
                        case ItemID.CopperCoin:
                            copper += item.stack;
                            break;
                        case ItemID.SilverCoin:
                            silver += item.stack;
                            break;
                        case ItemID.GoldCoin:
                            gold += item.stack;
                            break;
                        case ItemID.PlatinumCoin:
                            platinum += item.stack;
                            break;
                    }
                }
                args.Player.SendSuccessMessage($"{plr.Name}的小猪储蓄罐硬币情况如下:");
                args.Player.SendSuccessMessage($"[i:{ItemID.PlatinumCoin}]{platinum}");
                args.Player.SendSuccessMessage($"[i:{ItemID.GoldCoin}]{gold}");
                args.Player.SendSuccessMessage($"[i:{ItemID.SilverCoin}]{silver}");
                args.Player.SendSuccessMessage($"[i:{ItemID.CopperCoin}]{copper}");
            }
        }

        //插件卸载时执行的代码
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                var asm = Assembly.GetExecutingAssembly();
                Commands.ChatCommands.RemoveAll(c => c.CommandDelegate.Method?.DeclaringType?.Assembly == asm); //卸载命令
            }
            base.Dispose(disposing);
        }
    }
}
 
最后编辑:

Cai233

Lv3
LV
0
 
IP属地
福建省
2021/08/18
77
26
  • · 发布于香港

4.实现自动队伍,当玩家登录或加入世界,玩家有以下权限就切换对应的队伍​

权限队伍
team.red红队(1)
team.green绿队(2)
team.blue蓝队(3)
team.yellow黄队(4)
team.pink粉队(5)
钩子:TShockAPI.Hooks.PlayerHooks.PlayerPostLogin、ServerApi.Hooks.NetGreetPlayer
设置队伍:TSPlayer.SetTeam(int team) (无队伍0,红队1,绿队2,蓝队3,黄队4,粉队5)
C#:
using System.Reflection;
using Terraria;
using Terraria.ID;
using TerrariaApi.Server;
using TShockAPI;
using TShockAPI.Hooks;


namespace Plugin
{
    [ApiVersion(2, 1)]
    public class Plugin : TerrariaPlugin
    {
        //定义插件的作者名称
        public override string Author => "Cai";


        //插件的一句话描述
        public override string Description => "自动队伍";


        //插件的名称
        public override string Name => "AutoTeam";


        //插件的版本
        public override Version Version => Assembly.GetExecutingAssembly().GetName().Version;


        //插件的构造器
        public Plugin(Main game) : base(game)
        {
        }


        //插件加载时执行的代码
        public override void Initialize()
        {
            PlayerHooks.PlayerPostLogin+=OnLogin;
            ServerApi.Hooks.NetGreetPlayer.Register(this,OnGreetPlayers);
        }


        private void OnGreetPlayers(GreetPlayerEventArgs args)
        {
            CheckTeam(TShock.Players[args.Who]);
        }


        private void OnLogin(PlayerPostLoginEventArgs e)
        {
           CheckTeam(e.Player);
        }


        private void CheckTeam(TSPlayer plr)
        {
            if (plr.HasPermission("*"))
            {
                return;
            }
            int team = 0;
            int count = 0;
            if (plr.HasPermission("team.red"))
            {
                team = 1;
                count++;
            }
            if (plr.HasPermission("team.green"))
            {
                team = 2;
                count++;
            }
            if (plr.HasPermission("team.blue"))
            {
                team = 3;
                count++;
            }
            if (plr.HasPermission("team.yellow"))
            {
                team = 4;
                count++;
            }
            if (plr.HasPermission("team.pink"))
            {
                team = 5;
                count++;
            }
            if (count == 0)
            {
                plr.SendWarningMessage("[自动队伍]你所处的玩家组没有配置自动队伍!");
                return;
            }
            if (count > 1)
            {
                plr.SendErrorMessage("[自动队伍]你所处的玩家组配置了多个队伍!");
                return;
            }
            plr.SetTeam(team);
        }


        //插件卸载时执行的代码
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                PlayerHooks.PlayerPostLogin -= OnLogin;
                ServerApi.Hooks.NetGreetPlayer.Deregister(this, OnGreetPlayers);
            }
            base.Dispose(disposing);
        }
    }
}
5.当玩家在墓地环境移动时,通过TSPlayer.DamagePlayer()对其造成5点伤害
钩子:
GetDataHandlers.PlayerUpdate
C#:
using System.Reflection;
using Terraria;
using Terraria.ID;
using TerrariaApi.Server;
using TShockAPI;
using TShockAPI.Hooks;

namespace Plugin
{
    [ApiVersion(2, 1)]
    public class Plugin : TerrariaPlugin
    {
        //定义插件的作者名称
        public override string Author => "Cai";

        //插件的一句话描述
        public override string Description => "墓地";

        //插件的名称
        public override string Name => "ZoneGraveyard";

        //插件的版本
        public override Version Version => Assembly.GetExecutingAssembly().GetName().Version;

        //插件的构造器
        public Plugin(Main game) : base(game)
        {
        }

        //插件加载时执行的代码
        public override void Initialize()
        {
            GetDataHandlers.PlayerUpdate.Register(OnPlayerUpdatePhysics);
        }

        private void OnPlayerUpdatePhysics(object? sender, GetDataHandlers.PlayerUpdateEventArgs e)
        {
            if (e.Player.TPlayer.ZoneGraveyard)
            {
                e.Player.DamagePlayer(5);
            }
        }


        //插件卸载时执行的代码
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                GetDataHandlers.PlayerUpdate.UnRegister(OnPlayerUpdatePhysics);
            }
            base.Dispose(disposing);
        }
    }
}
 

cmgypang

Lv2
LV
0
 
IP属地
福建省
2023/06/02
8
0
  • · 发布于福建省
一个小错误,字段ZoneSnow是雪地,下雨&&雪地写出来才是下雪
 

Cai233

Lv3
LV
0
 
IP属地
福建省
2021/08/18
77
26
  • · 发布于香港

Cai233

Lv3
LV
0
 
IP属地
福建省
2021/08/18
77
26
  • · 发布于陕西省

Part 6.发送数据包

本章你将学到:
  • 如何向玩家发送数据包
  • 研究不同数据包参数的方法

什么是数据包?

顾名思义,数据包就是存放数据的包。这些包会告诉玩家的客户端一些游戏内的信息(例如物品掉落、地图时间等),当然服务器也可以接受来自玩家的数据包

为什么要发送数据包?

当你直接修改Terraria服务端中的属性、变量等(例如:玩家最大血量、下雨等),你会发现你的修改没办法马上生效,这时候就需要向玩家发送一个数据包用来告诉客户端你修改的信息

如何发送数据包?

我们可以调用TSPlayer对象中的SendData方法来发送数据包
首先我们需要获取对应的TSPlayer对象,前面教程已经有了,就不在过多赘述
注: 如果你需要发送全服数据包玩家对象就是TSPlayer.All
然后我们就要使用SendData方法
C#:
player.SendData(数据包类型,字符串参数,参数1,参数2...);
以修改玩家最大生命值为例:
C#:
private void TestCmd(CommandArgs args) //以命令为例
{
    var player = args.Player;  //获取玩家的对象
    player.TPlayer.statLifeMax = 11451;  //修改玩家最大生命值(注意: statLifeMax2是加上药水饰品等的最大生命值)
    player.SendData(PacketTypes.PlayerHp,"",player.Index);  //向玩家发送数据包更新
}
此时你应该会想问:
WTF?
为什么数据包类型PlayerHp,为什么参数1玩家的索引(player.Index),为什么SendData的参数没有我要改的血量11451?

发送数据包(SendData)的参数:

这里有一个奇怪的误区,SendData的参数并不一定是写进数据包的值(上面例子中:参数1是player.Index而不是TPlayer.statLifeMax),对于不同的数据包SendData的参数也会有所不同。想要知道具体参数作用,我们需要反编译NetMessage.orig_SendData,方法就是直接在可以执行语句的地方打一个NetMessage.orig_SendData,然后右键orig_SendData,点击转到定义,等待一下反编译我们就能看到NetMessage.orig_SendData的代码了
1708706875142.png
往下翻,我们会发现有一个switch-case结构, 其中case中的数字就是数据包的编号
1708706943347.png
以修改玩家最大生命为例(修改了玩家的statLifeMax) 我们直接按Ctrl+F搜索statLifeMax
此时搜索到16号数据包的case,所以我们需要发送的就是16号数据包(PacketTypes.PlayerHp)

注: 你可以用类似的方法查看PacketTypes的定义来找到数据包类型(16=>PacketTypes.PlayerHp)
如果你嫌麻烦也可以直接使用"(PacketTypes)数据包号"
[例如: (PacketTypes)16]
我们仔细查看这个case部分不难看出, number(参数1),就是玩家的索引(number为0时发送索引为0的玩家的生命数据,number为1时发送索引为1的玩家的生命数据...) [number2对应参数2,number3对应参数3,有些数据包需要用到字符串参数(例如: 弹幕文字、断开连接等)]
1708707136699.png
得出:
C#:
player.SendData(PacketTypes.PlayerHp,"",玩家索引);

再分析一个数据包试试

假设我们现在想把服务器中的世纪之花设置为已击败状态(NPC.downedPlantBoss=true),我们现在需要告诉所有的玩家世纪之花已被击败而不是执行命令的人,所以我们需要使用TSPlayer.All作为对象
C#:
private void TestCmd(CommandArgs args) //测试命令 (这里只是方便演示)
{
    NPC.downedPlantBoss = true; //设置世纪之花为已击败状态
    TSPlayer.All.SendData(数据包,"", 参数1, 参数2..); //向所有玩家发送数据包
}
然后我们转到NetMessage.orig_SendData()搜索downedPlantBoss,找到对应的位置后,详细的阅读这个case中的代码找number,
发现这个case中没有使用任何number或者text (不需要任何参数和文本)
1708708614064.png
所以我们直接调用下面的方法就可以发送数据包了 (由于7号数据包没有使用任何参数和文本,所以我们直接只填入包类型就好)
C#:
private void TestCmd(CommandArgs args)
{
    NPC.downedPlantBoss = true; //设置世纪之花为已击败状态
    TSPlayer.All.SendData(PacketTypes.WorldInfo);
//通过查看PacketTypes的定义可以找到数据包编号(7)对应的和PacketTypes(PacketTypes.WorldInfo)
    //也可以直接使用TSPlayer.All.SendData((PacketTypes)7); 但是PacketTypes.WorldInfo的代码可读性更高
}
奇怪的东西
有一个编写插件常常用到的数据包PlayerSlot(5号),这个数据包的作用是修改玩家背包的物品(包括装备栏的物品)
我们转到NetMessage.orig_SendData() 找到5号的对应代码
1708711116817.png
从上面的代码我们能发现number(参数1)玩家的索引(Index), number2(参数2)是对应格子的索引,而number3(参数3)却很难看出他的作用,此时我们可以通过查看MessageBuffer.GetData的定义来找到number3的作用
1708710659940.png
根据NetMessage.orig_SendData()中代码number3第4次Write进数据包, 那么number3MessageBuffer.GetData中也会在第4次Read进变量中,所以MessageBuffer.GetData中的num9就是NetMessage.orig_SendData()中的number3(参数3),然后我们发现,在使用Prefix方法的时候使用了num9(也就是number3),从wiki上我们可以知道prefix是修饰语的意思,所以number3就是修饰语ID
1708711049758.png
得出:
C#:
player.SendData(PacketTypes.PlayerSlot,"",玩家索引,背包格子索引,物品修饰语ID);
 

附件

  • 1708710808284.png
    1708710808284.png
    231.5 KB · 查看: 0
  • 1708710388321.png
    1708710388321.png
    117.3 KB · 查看: 0
  • 1708709255272.png
    1708709255272.png
    34.5 KB · 查看: 0
  • 1708706702888.png
    1708706702888.png
    42.1 KB · 查看: 0
最后编辑:
  • 标签
    插件制作 插件教程
  • * 这是一则由 Google AdSense 自动推荐的广告,与本站无关,不对其真实性与可靠性负责

    顶部