samp | open.mp 联机社区论坛
[转发][库] tdialogs 异步处理对话框 - 打印版本

+- samp | open.mp 联机社区论坛 (https://open-mp.cn)
+-- 板块: SA-MP (https://open-mp.cn/forumdisplay.php?fid=12)
+--- 板块: 发布 (https://open-mp.cn/forumdisplay.php?fid=18)
+---- 板块: 库 (https://open-mp.cn/forumdisplay.php?fid=20)
+---- 主题: [转发][库] tdialogs 异步处理对话框 (/showthread.php?tid=12)



[转发][库] tdialogs 异步处理对话框 - XiaoNiao - 03-02-2026

tdialogs

tdialogs 是一个库,它通过 PawnPlus 任务系统的强大功能,提供了多种函数来在 open.mp 服务器中异步处理对话框响应。

作者: TommyB123
GitHub地址: https://github.com/TommyB123/tdialogs

依赖项

本库需要以下依赖项。 * PawnPlus * sscanf

安装

只需安装到您的项目中:

代码:
sampctl install TommyB123/tdialogs

在您的代码中包含并开始使用该库:

代码:
#include <tdialogs>

介绍

如上所述,tdialogs 是一个基于 PawnPlus 的库,可用于在单个 PAWN 函数中异步处理对话框响应。这里有一个简单的示例。

代码:
MyCoolFunction()
{
    yield 1;
    new response[DIALOG_RESPONSE];
    await_arr(response) ShowAsyncDialog(playerid, DIALOG_STYLE_LIST, "酷炫对话框", "条目 1\n条目 2\n条目 3", "不错!", "返回");
    if(response[DIALOG_RESPONSE_RESPONSE])
    {
        SendClientMessage(playerid, -1, "您点击了第 %i 行,并收到了消息:%s",
            response[DIALOG_RESPONSE_LISTITEM], response[DIALOG_RESPONSE_INPUTTEXT]); // 值可能分别为 1 和 "条目 2"
    }
    else
    {
        SendClientMessage(playerid, -1, "您关闭了对话框");
    }
}

如果您熟悉所有可用的对话框处理库,您可能会想知道这个库与它们有何不同。简单的答案是,我包含了大量函数来快速从对话框响应中获取特定/单个值。您只需要显示一个快速的是/否确认对话框吗?没问题。使用 ShowAsyncConfirmationDialog。需要从游戏内商店购买用户输入的数量吗?ShowAsyncNumberInputDialog 可以满足您的要求。示例用法可以在这里找到。

从 1.1.0 版本开始,自动分页对话框也被包含在内,可以通过 AddPaginatedDialogRow 和 ShowAsyncPaginatedDialog 创建。完整的函数参数和示例如下。

对话框数据

tdialogs 还有一个虽小但实用的功能,用于在整个对话框流程中跟踪 ID 或其他数据。每个玩家在登录时都会获得一个动态的 PawnPlus 列表 分配给他们。这个列表可用于存储诸如房屋索引、车辆 ID 或其他与对话框内文本行匹配的数据。这样做是为了保留标识符,并避免进行不必要的循环查找或其他成本较高的方法来获取您已经找到的数据。

除了这个列表之外,还有一个专门结合其使用而设计的对话框函数。当您格式化了一个对话框并将相应的实体 ID(例如房屋 ID)推送到 DialogData 列表后,可以使用 ShowAsyncEntityIndexDialog 通过一行代码获取玩家点击的房屋 ID。您可以在下面的示例部分看到它的实际应用。

需要注意的是,在显示新对话框之前,必须使用 list_clear 清理该列表。否则,先前显示的对话框中的数据会混入后续的对话框显示中。

常量


 常量                                              默认值                               描述 
  • TDIALOG_DIALOG_ID_BEGIN           1234                                  该库为每种对话框类型(不要与对话框样式混淆)预留了 9 个对话框 ID TDIALOG_DIALOG_ID_BEGIN 是第一个将被使用的 ID,随后依次是接下来的 8 个数字。请注意您已有的任何对话框 ID 并相应地进行调整
  • PAGINATED_NEXT_TEXT                 "--> 下一页"                        当分页对话框提示您查看下一页时将显示的字符串。[/td][/tr]
  • PAGINATED_PREVIOUS_TEXT          "<-- 上一页"                        当您在分页对话框中可以返回上一页时将显示的字符串。[/td][/tr]


前提条件

建议您在包含 PawnPlus 之前添加 #define PP_SYNTAX_AWAIT。这允许您使用本库编写时所考虑的正确的 await 方法。

同时建议也添加 #define PP_SYNTAX_YIELD。这是 task_yield 的一个快速别名,用于在函数等待任务时将临时值让出给函数。例如,如果您在命令中没有 yield 1;,您很可能会在聊天中收到错误的未知命令错误。

函数

以下所有函数都有 PawnPlus 字符串变体。ShowAsyncDialog 对应 ShowAsyncDialog_s,以此类推。

代码:
ShowPlayerDialog_s(playerid, dialogid, DIALOG_STYLE:style, ConstAmxString:title, ConstAmxString:body, const button1[], const button2[])

ShowPlayerDialog 的简单 PawnPlus 字符串封装。

代码:
ShowAsyncDialog(playerid, DIALOG_STYLE:style, const title[], const body[], const button1[], const button2[] = "")

显示一个异步对话框,返回完整的对话框响应数组。

与 await_arr(response) ShowAsyncDialog(...) 一起使用。

代码:
ShowAsyncNumberInputDialog(playerid, const title[], const body[], const button1[], const button2[])

显示一个专门用于解析数字输入的异步对话框。

如果未接收到整数,对话框将简单地再次显示。

如果玩家关闭对话框/按下 ESC,则返回 cellmin。

与 new number = await ShowAsyncNumberInputDialog(...) 一起使用。

代码:
ShowAsyncFloatInputDialog(playerid, const title[], const body[], const button1[], const button2[])

显示一个专门用于解析数字输入的异步对话框。

如果未接收到浮点数,对话框将简单地再次显示。

如果玩家关闭对话框/按下 ESC,则返回 FLOAT_NAN。

与 new Float:number = Float:await ShowAsyncFloatInputDialog(...) 一起使用。

代码:
ShowAsyncStringInputDialog(playerid, const title[], const body[], const button1[], const button2[])

显示一个专门用于解析字符串输入的异步对话框。

如果接收到空字符串,对话框将简单地再次显示。

如果玩家关闭对话框/按下 ESC,则返回空字符串。使用 isnull 进行检查。

与 await_str(string) ShowAsyncStringInputDialog(...) 一起使用。

代码:
ShowAsyncPasswordDialog(playerid, const title[], const body[], const button1[], const button2[])

与 ShowAsyncStringInputDialog 相同,但使用密码对话框样式。

代码:
ShowAsyncListitemTextDialog(playerid, DIALOG_STYLE:style, const title[], const body[], const button1[], const button2[])

显示一个异步对话框,仅返回通过列表项传递的文本。

如果玩家关闭对话框/按下 ESC,则返回空字符串。使用 isnull 进行检查。

与 await_str(string) ShowAsyncListitemTextDialog(...) 一起使用。

代码:
ShowAsyncListitemIndexDialog(playerid, DIALOG_STYLE:style, const title[], const body[], const button1[], const button2[])

显示一个异步对话框,仅返回被点击的列表项的索引。

如果玩家关闭对话框/按下 ESC,则返回 -1。

与 new index = await ShowAsyncListitemIndexDialog(...) 一起使用。

代码:
ShowAsyncConfirmationDialog(playerid, const title[], const body[], const button1[], const button2[] = "")

显示一个异步对话框,仅将响应状态作为布尔值返回。

与 new bool:confirm = bool:await ShowAsyncConfirmationDialog(...) 一起使用。

代码:
ShowAsyncEntityIndexDialog(playerid, DIALOG_STYLE:style, const title[], const body[], const button1[], const button2[])

显示一个异步对话框,返回在玩家 DialogData 列表中位于 listitem 位置的值。在此处或下面的示例中阅读更多信息。

如果玩家关闭对话框/按下 ESC,则返回 -1。

与 new id = await ShowAsyncEntityIndexDialog(...) 一起使用。

代码:
ShowAsyncPaginatedDialog(playerid, DIALOG_STYLE:style, rows_per_page, const title[], const button1[], const button2[], const tablist_header_text[] = "")

显示一个异步对话框,根据提供的每页行数对提供的文本进行分页。返回完整的对话框响应。

此函数仅兼容 DIALOG_STYLE_TABLIST_HEADERS、DIALOG_STYLE_TABLIST 和 DIALOG_STYLE_LIST。

tablist_header_text 参数仅用于 DIALOG_STYLE_TABLIST_HEADERS,顾名思义,是对话框的标题头文本。

与 await_arr(response) ShowAsyncPaginatedDialog 一起使用。

代码:
AddPaginatedDialogRow(playerid, const text[], extraid = 0)

向分页对话框添加一行。extraid 参数可用于传递与您追加的文本行相关的额外 ID。

构建分页对话框时必需。

示例

这里有一些稍微深入一点的示例。

快速确认用户是否想出售他们的房子。

代码:
CMD:sellmyhouse(playerid)
{
    if(PlayerOwnsHouse(playerid))
    {
        task_yield(1);
        new bool:confirm = bool:await ShowAsyncConfirmationDialog(playerid, "出售您的房子?", "您确定要出售您的房子吗?", "是", "否");
        if(confirm)
        {
            SellPlayerHouse(playerid);
        }
    }
}

输入您想从商店购买多少鱼饵。

代码:
new baitamount = await ShowAsyncNumberInputDialog(playerid, "鱼饵数量", "在下面输入您想购买的鱼饵数量", "购买", "取消");
if(baitamount == cellmin) return false; // 当玩家取消对话框时返回 cellmin
if(baitamount <= 0 || baitamount > 10000)
{
    SendClientMessage(playerid, -1, "无效的鱼饵数量。");
    return false;
}
GivePlayerFishBait(playerid, baitamount);
return true;

通过使用 DialogData 列表从对话框中解析出房屋 ID。

代码:
ShowHouseTeleportDialog(playerid)
{
    new string[256], substring[64];
    list_clear(DialogData[playerid]); // 清空持久的 DialogData 列表,使其与我们即将显示的对话框中的行数匹配
    for(new i = 0; i < MAX_HOUSES; ++i)
    {
        if(HouseData[i][HouseOwner] == playerid)
        {
            // 将房屋名称追加到字符串,并将房屋索引添加到 DialogData 列表。
            // 这样做是为了使房屋名称行与任意的房屋 ID 保持一致。
            // 可能有 500 栋房子,但玩家只拥有其中少数(甚至一栋)。以这种方式存储
            // 房屋索引让我们能够快速获取正确的 ID,而无需进行额外的、不必要的查找。
            format(substring, sizeof(substring), "%s\n", HouseData[i][HouseAddress]);
            strcat(string, substring);
            list_add(DialogData[playerid], i);
        }
    }
    new houseid = await ShowAsyncEntityIndexDialog(playerid, DIALOG_STYLE_LIST, "传送到您的家", string, "传送!", "算了");
    if(houseid != -1) // 如果玩家取消对话框,返回 -1
    {
        SetPlayerPos(playerid, HouseData[houseid][HouseX], HouseData[houseid][HouseY], HouseData[houseid][HouseZ]);
    }
}

显示一个每页 10 行的分页对话框。

代码:
ShowOwnedVehiclesDialog(playerid)
{
    new string[128];
    list_clear(DialogData[playerid]);
    for(new i = 1; i <= MAX_VEHICLES; ++i)
    {
        if(PlayerOwnsVehicle(playerid, i))
        {
            format(string, sizeof(string), "%s\t%s\n", ReturnVehicleName(i), ReturnVehicleLocation(i));
            AddPaginatedDialogRow(playerid, string, i);
        }
    }
    new response[DIALOG_RESPONSE];
    await_arr(response) ShowAsyncPaginatedDialog(playerid, DIALOG_STYLE_TABLIST_HEADERS, 10, "您的车辆", "传送", "取消", "车辆\t位置");
    if(!response[DIALOG_RESPONSE_RESPONSE]) return false;
    new vehicleid = response[DIALOG_RESPONSE_EXTRAID], Float:x, Float:y, Float:z;
    GetVehiclePos(vehicleid, x, y, z);
    SetPlayerPos(playerid, x, y, z);
    SendClientMessage(playerid, -1, "您已传送到您的 %s", ReturnVehicleName(vehicleid));
}

致谢

TommyB123

特别感谢 Graber。在使用他的原创 async dialogs 库多年后,我才受到启发制作了自己的库。如果您不需要我的库提供的额外功能,可以考虑使用他的库。


RE: [转发][库] tdialogs 异步处理对话框 - siwode - 03-03-2026

这个很有帮助!