Skip to content

update:CGive修复更新#1093

Merged
ACaiCat merged 9 commits intoUnrealMultiple:masterfrom
THEXN:new
Mar 1, 2026
Merged

update:CGive修复更新#1093
ACaiCat merged 9 commits intoUnrealMultiple:masterfrom
THEXN:new

Conversation

@THEXN
Copy link
Member

@THEXN THEXN commented Mar 1, 2026

更新插件/修复BUG

  • 插件已修改版本号
  • 更新插件README.md中的更新日志
  • 插件可以正常工作

其他

  • ❤️leader我喜欢你
  • 修复 SQLite database is locked 错误
  • 合并 personal/all 子命令为统一的 add 子命令

Summary by Sourcery

将 CGive 子命令统一为单一的基于 add 的工作流,并提升在不同存储后端下离线指令执行的可靠性。

新功能:

  • 引入统一的 /cgive add 子命令,以更清晰的参数语义同时覆盖个人和全体玩家的离线指令。

错误修复:

  • 通过为 CGive 插件使用独立的数据库连接和查询辅助工具,修复 SQLite 锁定问题。
  • 确保离线指令只会为正确的在线玩家执行,并且指令占位符统一使用 {name} 格式。
  • 修正删除逻辑,使移除某个离线指令时同时清理其关联的 Given 记录。

增强改进:

  • 优化玩家问候处理逻辑,在玩家登录应用待执行离线指令时复用共享的执行路径。
  • 简化 CGive 的获取 API,使其返回所有已配置的离线指令,而不再区分目标类型。
  • 改进 /cgive 的指令帮助信息、用法校验以及未知子命令的反馈。
  • 更新作者元数据和 README 文档,包括使用示例和新的更新日志条目。
Original summary in English

Summary by Sourcery

Unify CGive subcommands into a single add-based workflow and improve offline command execution reliability across storage backends.

New Features:

  • Introduce a unified /cgive add subcommand that covers both personal and all-player offline commands with clearer parameter semantics.

Bug Fixes:

  • Fix SQLite locking issues by using a dedicated database connection and query helpers for the CGive plugin.
  • Ensure offline commands are executed only for the correct online players and that command placeholders use a consistent {name} format.
  • Correct deletion logic so removing an offline command also cleans up its associated Given records.

Enhancements:

  • Refine player greeting handling to reuse a shared execution path when applying pending offline commands on login.
  • Simplify CGive retrieval APIs to return all configured offline commands regardless of target.
  • Improve command help messages, usage validation, and unknown subcommand feedback for /cgive.
  • Update author metadata and README documentation, including usage examples and a new changelog entry.

@THEXN THEXN requested a review from a team as a code owner March 1, 2026 12:43
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个问题,并留下了一些高层次的反馈:

  • 新的 Data 抽象用自定义的 _db 连接替换了 TShock.DB,但从未显式打开或释放连接,并在所有调用之间共享单个 IDbConnection 实例,这可能带来连接生命周期和并发方面的问题(尤其是对 SQLite);建议直接使用 TShock.DB,或者为每次操作创建/释放连接,而不是使用长生命周期的静态连接。
  • CGive.Execute 中,你先调用 Save(),然后通过匹配 executer/cmd/who 查询最后插入的 id,但没有使用事务或任何唯一约束,如果同时有多个相似的命令被并发添加,就会产生竞争条件;使用数据库自增主键获取机制(例如 LAST_INSERT_ID() / last_insert_rowid())或事务会让 ID 分配更可靠。
  • OnGreetPlayer 现在通过 CGive.GetCGive() 加载所有记录,然后在内存中按玩家过滤,而之前的 API 是按 who 查询;对于大表来说,每次登录都这样会比较昂贵,所以可能值得恢复按条件过滤的查询,或者添加一个只返回相关命令的 GetCGiveForPlayer(string who)
面向 AI 代理的提示
Please address the comments from this code review:

## Overall Comments
- The new `Data` abstraction replaces `TShock.DB` with a custom `_db` connection but never explicitly opens or disposes it and shares a single `IDbConnection` instance across all calls, which can introduce connection‑lifecycle and concurrency issues (especially for SQLite); consider using `TShock.DB` directly or creating/disposing a per‑operation connection instead of a long‑lived static one.
- In `CGive.Execute` you call `Save()` and then query the last inserted `id` by matching `executer/cmd/who` without any transaction or uniqueness, which can race if multiple similar commands are added concurrently; using database identity retrieval (e.g., `LAST_INSERT_ID()`/`last_insert_rowid()`) or a transaction would make the ID assignment more reliable.
- `OnGreetPlayer` now uses `CGive.GetCGive()` to load all records and then filters per player in memory, whereas the previous API queried by `who`; for large tables this can become expensive on each login, so it may be worth restoring a filtered query or adding a `GetCGiveForPlayer(string who)` that only returns relevant commands.

## Individual Comments

### Comment 1
<location path="src/CGive/CGive.cs" line_range="51" />
<code_context>
-        var list2 = TSPlayer.FindByNameOrID(this.who);
-        if (list2.Count > 0)
+
+        // personal ģʽ����ȷƥ���������
+        var target = TShock.Players.FirstOrDefault(p =>
+            p != null && p.Active && p.Name == this.who);
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Personal execution uses a case-sensitive name comparison, inconsistent with other case-insensitive comparisons.

Here `target` is resolved with `p.Name == this.who`, which is case-sensitive, while other checks (e.g. `Executer.Equals("server", StringComparison.OrdinalIgnoreCase)` and the name comparison in `ExecuteForPlayer`) are case-insensitive. This inconsistency can cause commands for `Foo` to fail for `foo`. Please switch to a case-insensitive comparison here (e.g. `StringComparison.OrdinalIgnoreCase`) for consistency and correctness.
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得我们的评审有帮助,请考虑分享 ✨
帮我变得更有用!请对每条评论点 👍 或 👎,我会根据你的反馈改进后续评审。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • The new Data abstraction replaces TShock.DB with a custom _db connection but never explicitly opens or disposes it and shares a single IDbConnection instance across all calls, which can introduce connection‑lifecycle and concurrency issues (especially for SQLite); consider using TShock.DB directly or creating/disposing a per‑operation connection instead of a long‑lived static one.
  • In CGive.Execute you call Save() and then query the last inserted id by matching executer/cmd/who without any transaction or uniqueness, which can race if multiple similar commands are added concurrently; using database identity retrieval (e.g., LAST_INSERT_ID()/last_insert_rowid()) or a transaction would make the ID assignment more reliable.
  • OnGreetPlayer now uses CGive.GetCGive() to load all records and then filters per player in memory, whereas the previous API queried by who; for large tables this can become expensive on each login, so it may be worth restoring a filtered query or adding a GetCGiveForPlayer(string who) that only returns relevant commands.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `Data` abstraction replaces `TShock.DB` with a custom `_db` connection but never explicitly opens or disposes it and shares a single `IDbConnection` instance across all calls, which can introduce connection‑lifecycle and concurrency issues (especially for SQLite); consider using `TShock.DB` directly or creating/disposing a per‑operation connection instead of a long‑lived static one.
- In `CGive.Execute` you call `Save()` and then query the last inserted `id` by matching `executer/cmd/who` without any transaction or uniqueness, which can race if multiple similar commands are added concurrently; using database identity retrieval (e.g., `LAST_INSERT_ID()`/`last_insert_rowid()`) or a transaction would make the ID assignment more reliable.
- `OnGreetPlayer` now uses `CGive.GetCGive()` to load all records and then filters per player in memory, whereas the previous API queried by `who`; for large tables this can become expensive on each login, so it may be worth restoring a filtered query or adding a `GetCGiveForPlayer(string who)` that only returns relevant commands.

## Individual Comments

### Comment 1
<location path="src/CGive/CGive.cs" line_range="51" />
<code_context>
-        var list2 = TSPlayer.FindByNameOrID(this.who);
-        if (list2.Count > 0)
+
+        // personal ģʽ����ȷƥ���������
+        var target = TShock.Players.FirstOrDefault(p =>
+            p != null && p.Active && p.Name == this.who);
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Personal execution uses a case-sensitive name comparison, inconsistent with other case-insensitive comparisons.

Here `target` is resolved with `p.Name == this.who`, which is case-sensitive, while other checks (e.g. `Executer.Equals("server", StringComparison.OrdinalIgnoreCase)` and the name comparison in `ExecuteForPlayer`) are case-insensitive. This inconsistency can cause commands for `Foo` to fail for `foo`. Please switch to a case-insensitive comparison here (e.g. `StringComparison.OrdinalIgnoreCase`) for consistency and correctness.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

该 PR 主要围绕 CGive 插件的离线命令工作流做整理与可靠性增强:将原 personal/all 子命令合并为统一的 /cgive add,并尝试通过独立数据连接与辅助方法缓解 SQLite 锁库问题,同时调整离线命令在玩家登录时的执行路径与占位符格式({name})。

Changes:

  • /cgive personal/cgive all 合并为 /cgive add,并更新帮助信息与 README 示例/更新日志
  • 调整离线命令登录触发执行逻辑,引入按玩家查询的 GetCGiveForPlayer
  • 引入 Data 辅助类封装查询、并调整 Given 表写入逻辑以记录 (name, id)

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/CGive/README.md 更新命令文档为 /cgive add,补充参数说明与更新日志
src/CGive/Main.cs 登录触发执行路径重构;指令入口更新为 add/del/list/reset,并补充未知子命令提示
src/CGive/CGive.cs 执行逻辑改为 {name} 占位符;新增按玩家查询接口;GetCGive 接口调整
src/CGive/Data.cs 新增独立 DB 连接与 Query/QueryReader/LastInsertId 辅助方法;Given 表结构调整
src/CGive/Given.cs Given 写入/查询改为走 Data,并改为插入 (name, id)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 79 to 82
/// <summary>
/// ֻ��ѯ��ָ���������صļ�¼��personal ģʽ���� all ģʽ��¼������ȫ��ɨ��
/// </summary>
public static IEnumerable<CGive> GetCGiveForPlayer(string playerName)
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XML <summary> 内容同样是乱码,生成文档或在 IDE 中悬浮提示时不可读。建议改为可读文本,并统一该文件的编码/字体以避免再次出现乱码。

Copilot uses AI. Check for mistakes.
Comment on lines 129 to 136
var temp = new CGive
{
Executer = "Server",
who = player.Name,
cmd = item.cmd,
id = item.id
};
if (temp.Execute())
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

item.who == "-1" 的离线指令在玩家登录时被包装成 temp 执行,但这里把 Executer 硬编码为 Server,会忽略数据库里原本保存的执行者身份,并可能导致指令以更高权限执行(与 /cgive add [执行者] ... 的语义不一致)。建议改为沿用 item.Executer(或明确限制 all(-1) 只能用 Server 并在入库/帮助里校验与说明)。

Copilot uses AI. Check for mistakes.
Comment on lines 11 to 52
@@ -16,17 +24,31 @@ public static void Init()
);

var givenTable = new SqlTable("Given",
new SqlColumn("id", MySqlDbType.Int32) { Primary = true, AutoIncrement = true },
new SqlColumn("name", MySqlDbType.Text));

new SqlColumn("name", MySqlDbType.Text),
new SqlColumn("id", MySqlDbType.Int32)
);

SqlTableCreator creator = new(TShock.DB, TShock.DB.GetSqlQueryBuilder());
var creator = new SqlTableCreator(_db, _db.GetSqlQueryBuilder());
creator.EnsureTableStructure(cgiveTable);
creator.EnsureTableStructure(givenTable);
}

public static void Command(string cmd, params object[] args)
{
TShock.DB.Query(cmd, args);
_db!.Query(cmd, args);
}

public static QueryResult QueryReader(string query, params object[] args)
{
return _db!.QueryReader(query, args);
}

public static int GetLastInsertId()
{
var sql = TShock.Config.Settings.StorageType.Equals("mysql", StringComparison.OrdinalIgnoreCase)
? "SELECT LAST_INSERT_ID()"
: "SELECT last_insert_rowid()";
using var re = _db!.QueryReader(sql);
return re.Read() ? re.Reader.GetInt32(0) : 0;
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Data 使用单例 _db 连接并在 Command/QueryReader/GetLastInsertId 中直接复用;在 REST 请求、玩家登录事件、命令处理等可能并发的场景下,这会导致连接的并发访问问题(多数 IDbConnection/Provider 都不是线程安全的),同时也会让 GetLastInsertId() 读到其他并发插入的结果。建议为所有 DB 调用加全局锁/队列串行化,或改为“每次操作创建新连接(依赖连接池)”并把 last insert id 获取与插入放在同一连接/事务范围内。

Copilot uses AI. Check for mistakes.
Comment on lines 26 to +29
var givenTable = new SqlTable("Given",
new SqlColumn("id", MySqlDbType.Int32) { Primary = true, AutoIncrement = true },
new SqlColumn("name", MySqlDbType.Text));

new SqlColumn("name", MySqlDbType.Text),
new SqlColumn("id", MySqlDbType.Int32)
);
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given 表结构从“自增主键 id + name”改成了“name + id(外键)”且不再有主键/唯一约束。对于已经存在旧表结构的数据,EnsureTableStructure 通常不会重命名/迁移冲突列名,这可能导致运行时插入失败或与旧 id 值冲突;同时缺少 (name,id) 唯一约束会让去重完全依赖应用层。建议:保留旧自增主键(例如改列名为 cgive_id),并为 (name, cgive_id) 加唯一索引/主键;如需变更结构,加入显式迁移逻辑。

Copilot uses AI. Check for mistakes.
Comment on lines 13 to +18
public static void Init()
{
_db = TShock.Config.Settings.StorageType.Equals("mysql", StringComparison.OrdinalIgnoreCase)
? new MySqlConnection(TShock.Config.Settings.MySqlConnectionString)
: new SqliteConnection("Data Source=" + Path.Combine(TShock.SavePath, "CGive.sqlite"));

Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里创建了新的 _db 连接,但当前类没有任何关闭/释放逻辑;如果插件支持热重载/卸载或服务器长时间运行,这可能导致连接泄漏或 SQLite 文件句柄长期占用。建议增加 Data.Dispose/Close 并在插件 Dispose 时调用(同时避免在 Init 多次调用时重复创建连接)。

Copilot uses AI. Check for mistakes.
Comment on lines 44 to 46
// personal ģʽ����Сд�����о�ȷƥ��
var target = TShock.Players.FirstOrDefault(p =>
p != null && p.Active && p.Name.Equals(this.who, StringComparison.OrdinalIgnoreCase));
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

新增的注释/文档字符串出现乱码(例如 personal ģʽ...、XML summary),这会降低可维护性且影响后续阅读/生成文档。建议将该注释改为正常的 UTF-8 文本(中文或英文均可),并确认源码文件编码为 UTF-8。

Copilot uses AI. Check for mistakes.
# 玩家leader上线就kill她一次(leader 离线时保存,上线自动执行)
/cgive add server leader /kill {name}

# 任意玩家上线就kill他一次(包括未来登录的玩家,每人只一次)
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

README 示例注释写的是“上线就kill他一次”,但实际示例命令是 /give {name} 4956;这会误导使用者。建议统一示例说明与命令内容(要么改注释,要么把命令改成 /kill {name})。

Suggested change
# 任意玩家上线就kill他一次(包括未来登录的玩家,每人只一次)
# 任意玩家上线就送他一次物品 4956(包括未来登录的玩家,每人只一次)

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Member

@ACaiCat ACaiCat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TL

@ACaiCat ACaiCat added this pull request to the merge queue Mar 1, 2026
Merged via the queue into UnrealMultiple:master with commit b8248e2 Mar 1, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants