# 文明6lua计时器 **Repository Path**: XPPK/pk-civ6-LuaTimer ## Basic Information - **Project Name**: 文明6lua计时器 - **Description**: 在文明6实现更好的lua计时器 - **Primary Language**: Lua - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2023-06-06 - **Last Updated**: 2024-12-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 文明6lua计时器 - [Githp链接](https://github.com/X-PPK/pk-civ6-LuaTimer) - [Gitee链接](https://gitee.com/XPPK/pk-civ6-LuaTimer) #### 介绍 在文明6实现更好的lua计时器,用于定时触发其他lua函数,这将是我的大UI框架项目的一部分 - 目前推荐两个方法,一个是通过UI动画控件实现 - 另一个是通过Events.GameCoreEventPublishComplete.Add()实现,该方法来自[“号码菌”](https://steamcommunity.com/profiles/76561198147378701) ## 项目说明 当我想要实现更加复杂的外交动画那么计时器就是必要的,因为我想控控制贴图切换的速度 这个项目旨在试图解决这个问题 这里将一点一点记录我这个项目的实现过程,如果他实现了,也就将进行公开 - xxx全搜,指用类似AgentRansack对后缀是xxx的文件全盘搜索(个人习惯) - 不要嫌弃我这个啰嗦,一开始就是我的日记总结,第一用途就是保证日后我如果要继续这个项目研究,不会因为时间太久而忘记太多细节,最后导致只能放弃,本人容易忘细节 ## 项目的开始 ### 1. 2023/5/?具体时间忘记了 当我有了计时器的想法后第一时间想到了一个lua中的while循环+os.time() - os.time()是lua获取系统当前时间 - 具体代码如下 - 注意这个方法已经被弃用,可以跳过 我使用 `os.time()` 函数来获取当前时间,再根据当前时间和起始时间的差值来计算时间间隔。 #### 使用 while 循环实现计时器 ```lua function timer(seconds) local start_time = os.time() -- 获取当前时间 local elapsed_time = 0 while elapsed_time < seconds do -- 执行计时任务 print("计时中...", elapsed_time, "秒") -- 等待一秒 os.execute("sleep 1") -- 更新已经流逝的时间 elapsed_time = get_time() - start_time end print("计时完成!") end -- 测试计时器 timer(10) -- 计时 10 秒 ``` 在上面的代码中,我们定义了一个 `get_time()` 函数来获取当前时间,然后定义了一个 `timer()` 函数来实现计时任务。 首先,在 `timer()` 函数中获取当前时间作为计时器的起始时间,并初始化流逝的时间为零。然后,在 `while` 循环中执行计时任务,每次循环等待一秒,同时更新已经流逝的时间。当流逝的时间达到指定的计时时间时,退出循环,并输出计时完成的提示。 需要注意的是,由于 `os.execute()` 函数在不同操作系统中可能存在差异,请根据实际情况修改代码。此外,计时器的精度可能受到操作系统、硬件等多方面因素的影响,因此在实际应用中需要进行多次测试以确保结果的准确性。 ## CIV6动画控件计时器 - 由于os.execute()可能不太稳定,所以我希望是否可以用CIV6lua系统支持的东西实现 - 我就想到了CIV6的一种UI控件————AlphaAnim(动画UI控件) 我在之前对官方UI研究中知道AlphaAnim有参数Speed可以控制动画控件的动画变动速度 - 同时AlphaAnim控件可以注册动画回调函数,让动画变动中插入运行函数——RegisterAnimCallback - 所以理论上完全可行,那么就是实践了 -- 这个我已经实现了,但暂时没整理,有空整理 ### 具体例子 - 2023/8/11 - 因为用到的是文明6UI的控件,所以是UI的xml和lua - 当然一如既往的详细备注,以帮助大家理解 - 这里写了一个CIV6动画控件计时器测试mod,文件可以自己取:[计时器测试mod](https://gitee.com/XPPK/pk-civ6-LuaTimer/tree/master/计时器测试mod) 首先下面两个例子(构造计时循环和定时器)的xml相同我就放一起了 ```xml ``` #### 构造计时循环 - 例如你希望这个函数每1秒运行一次 ```lua -- 官方例子Speed明确为最小值0.0015(也就是这至少可以取到小数点后4位) -- 官方还有一些不明确数值例子是计算值我没有测试也没时间取探索,不清楚Speed具体能取到多少 -- ...省略 -- speed = 0.005 --此时动画控件1秒内完成动画播放1/100次(既100秒 运行1次函数) -- speed = 0.01 --此时动画控件1秒内完成动画播放1/50次 (既50秒 运行1次函数) -- speed = 0.05 --此时动画控件1秒内完成动画播放1/10次 (既10秒 运行1次函数) -- speed = 0.1 --此时动画控件1秒内完成动画播放1/5次 (既5秒 运行1次函数) -- speed = 0.25 --此时动画控件1秒内完成动画播放1/2次 (既2秒 运行1次函数) local speed = 0.5 --此时动画控件1秒内完成动画播放1次 (既1秒 运行1次函数) -- speed = 1 --此时动画控件1秒内完成动画播放2次 (既0.5秒 运行1次函数) -- speed = 2 --此时动画控件1秒内完成动画播放4次 (既0.25秒运行1次函数) -- speed = 30 --此时动画控件1秒内完成动画播放60次 (既1/60秒运行1次函数) -- ...省略 -- speed = a --此时动画控件1秒内完成动画播放2*a次 (既1/2a秒运行1次函数) function function1() -- 省略你具体按speed设定速度循环要工作的代码程序 end local function play() -- 动画控件播放动画 Controls.TimerAnim:SetToBeginning() Controls.TimerAnim:Play() end -- 这里主要还是关闭循环的函数,亦或者提前关闭延迟的函数。 function stop() -- 动画控件停止动画 Controls.TimerAnim:SetToBeginning() Controls.TimerAnim:Stop() end function AddTimer(func, iSpeed) Controls.TimerAnim:SetSpeed(iSpeed); Controls.TimerAnim:RegisterEndCallback(function() func() play() -- 接着循环 end); end function AdjustSpeed(iSpeed) -- 调整function1这个循环函数循环速度时 function3() Controls.TimerAnim:SetSpeed(iSpeed); play() end --------------------- AddTimer(function1, speed) -- 现在function1这个循环函数每1秒运行一次 ``` #### 定时器 - 例如你希望这个函数延迟10秒后运行 ```lua local speed = 0.5 --此时动画控件1秒内完成动画播放1次 (既1秒运行1次函数) local AuxiliaryTiming = 0 -- 用于记录延迟函数运行次数 function function1() -- 省略你具体按speed设定速度循环要工作的代码程序 end end local function play() -- 动画控件播放动画 Controls.TimerAnim:SetToBeginning() Controls.TimerAnim:Play() end -- 这里主要还是关闭循环的函数,亦或者提前关闭延迟的函数。 function stop() -- 动画控件停止动画 Controls.TimerAnim:SetToBeginning() Controls.TimerAnim:Stop() end function AddTimer(func, iSpeed, StopTime) Controls.TimerAnim:SetSpeed(iSpeed); Controls.TimerAnim:RegisterEndCallback(function() func() if AuxiliaryTiming == StopTime then --达到要触发的时候 stop()-- 结束 return end play() -- 接着循环 end); end function AdjustSpeed(iSpeed) -- 调整function1这个循环函数循环速度时 function3() Controls.TimerAnim:SetSpeed(iSpeed); play() end ``` #### 计时器框架 ##### lua部分 ```lua include("InstanceManager"); local TimerIM = InstanceManager:new("TimerTool", "TimerCon", Controls.TimeStack); --TimerIM:ResetInstances(); CallbackDict = {}; CallbackSpeed = {}; AuxiliaryTiming = {}; ATnum = 0; -- 用于检测FuncID是否重复,或者说也是检测该FuncID函数是否还在运行 function CheckRun(FuncID) if AuxiliaryTiming[FuncID] ~= nil then return true end return false end -- callbackFunc被延迟/循环的函数 -- loop是则为循环函数,不是或为nil则函数为延迟函数 -- speed是计时器速度,如果为nil则默认速度为1秒运行函数1次,既speed=0.5 -- FuncID用于额外的Remove停止循环/延时函数,记得不要为大于0的整数,给予其他字符串 -- StopTime结束时刻,当为延迟函数时,第StopTime次时结束并运行callbackFunc函数,当是循环函数该参数无效 -- Values是函数参数,注意这个参数类型一定要是表,否则报错的,可以为nil,使用详情看这段代码最后面注释 -- Replace布尔值参数用于确认是否替换原有函数,如果为true则替换原FuncID函数,如果为false则不替换FuncID同名函数 -- 也就是你完全可以通过调控speed和StopTime来确定速度 function AddTimer(callbackFunc, loop, speed, FuncID, StopTime, Values, Replace) ATnum = ATnum + 1; -- 如果loop为nil loop = loop or false; local num = tonumber(FuncID); -- 如果FuncID为数值字符串且大于0是整数 if num and num > 0 and math.floor(num) == num then print("FuncID应当不为大于0的整数,应当给予其他字符串"); print("FuncID改为"..ATnum); FuncID = ATnum; end -- 如果FuncID发生重复 if CheckRepeat(FuncID) then if Replace then print("FuncID发生重复,给予替换,清除之前的相同FuncID函数"); CallbackDict[FuncID]() else print("FuncID发生重复,请给予其他字符串"); print("FuncID改为"..ATnum); FuncID = ATnum; end end -- 如果FuncID为nil FuncID = FuncID or ATnum; print("FuncID为:" ..FuncID) local TIM = TimerIM:GetInstance(); --为了实现架构化,采用实例批量生产动画控件 local TAnim = TIM.TimerAnim; speed = speed or 0.5; -- 设置动画控件速度公式:Speed="a" 则一秒动画2 * a次 TAnim:SetSpeed(speed); AuxiliaryTiming[FuncID] = 0; local function play() -- 动画控件播放动画 TAnim:SetToBeginning() TAnim:Play() end local function stop() -- 动画控件停止动画 TAnim:SetToBeginning() TAnim:Stop() TimerIM:ReleaseInstance(TIM)--删除该实例 AuxiliaryTiming[FuncID] = nil--同步清除 end -- callbackFunc插入到定时循环中 local function CreateLoopFunc() return function() callbackFunc(unpack(Values));-- 运行需要循环的函数 play() -- 接着循环 end end -- callbackFunc插入到延时触发中 local function CreateFunc() return function() AuxiliaryTiming[FuncID] = AuxiliaryTiming[FuncID] + 1; if AuxiliaryTiming[FuncID] == StopTime then --达到要触发的时候 callbackFunc(unpack(Values)); CallbackDict[FuncID] = nil; stop()-- 结束 return end play() -- 接着循环 end end local func = loop and CreateLoopFunc() or CreateFunc(); TAnim:RegisterEndCallback( func ) --在动画结束注册回调函数 play() -- 直接构造好对应的关闭循环的函数,亦或者提前关闭延迟的函数以免找不到对应func CallbackDict[FuncID] = function() stop() CallbackDict[FuncID] = nil; CallbackSpeed[FuncID] = nil; end CallbackSpeed[FuncID] = function(iSpeed) TAnim:SetToBeginning() TAnim:Stop() --暂停 TAnim:SetSpeed(iSpeed); --修改速度 play() --重新启动控件 end return ATnum; end -- 这里主要还是关闭循环的函数,亦或者提前关闭延迟的函数。 function RemoveTimer(FuncID) if CallbackDict[FuncID] then CallbackDict[FuncID](); end end -- 调整函数循环速度 function AdjustSpeed(FuncID, iSpeed) if CallbackSpeed[FuncID] then CallbackSpeed[FuncID](iSpeed); end end ------------------------------------------------ -- 下面就是测试 -- 注意该测试结果需要可实时加载lua.log,我是使用官方SDK的FireTuner实时查看lua.log -- 使用官方SDK的FireTuner运行LuaEvents.PK_TestCode()即可 function TestCode() AddTimer(function(a) time = time + 1; print(a, time) end, true, 0.5, "A", nil, {"报时第: "}); -- 循环函数A 用于报时间 AddTimer(function() next = next + 1; print("第几次: ",next) end, true, 0.5, "B"); -- 循环函数B 用于测试该速度 AddTimer(function() print("10秒B变速"); AdjustSpeed("B", 1); end, false, 0.5, "C", 10); -- 延时函数C,在第10秒调整函数B速度 AddTimer( function() print("20秒都结束"); RemoveTimer("A"); RemoveTimer("B"); RemoveTimer("C"); RemoveTimer("D"); end, false, 0.5, "D", 20 ); -- 延时函数D,停止所有函数 end LuaEvents.PK_TestCode.Add(TestCode); ------------------------------------------------ -- 补充,首先感谢号码菌的建议,我前面没有考虑到这一点,现在更新改动加入Values参数 -- 这里额外讲述一下Values的使用: -- 1. 首先Values可以为空(nil)或者是一个表table,千万不要其他类型否则unpack(Values)会保错 -- unpack(Values)会自动拆Values表把里面元素作为参数传给callbackFunc -- 所以你需要把你要给的参数都放在Values表里,会自动unpack(Values)给予callbackFunc -- 2. 当Values为空(nil)或者是一个空表"{}"时,会给予callbackFunc一个nil参数 -- 所以要确保你的callbackFunc,要么一开始就不需要参数,要么能接受nil参数,不会导致什么BUG产生 -- 如果callbackFunc本身不需要参数,为nil即可,不会影响callbackFunc函数正常运行 -- 3. 然后如果你要给予callbackFunc参数就是一个表,一定要套一层表,抵消unpack ``` ##### xml部分 ```xml ``` - 测试结果如下 ![](计时器测试mod/2image.png) #### 小扩展 - 然后动画控件随你游戏设置帧率刷新,而这我发现动画控件还可以插入一个随着帧率刷新调用函数 - 具体细节忘记了也懒得总结,难不成你要按帧率运行函数??,大部分玩家电脑顶不住的啊大哥,如果是为了按帧率切换图片实现播放视频,我想说没必要用这个,我有更好方法来实现,直接播放视频的UI控件 ```lua Controls.CountdownTimerAnim:RegisterAnimCallback( OnUpdateTimers );--测试这个看样子是每帧回调 Controls.CountdownTimerAnim:ClearAnimCallback();--对应的结束运行 ``` ## Events计时器 - 2024/3/22 - 官方Events.GameCoreEventPublishComplete计时 - 该接口使用由“号码菌”带佬分享 - 总之虽然代码是大家自行安排,但个建议该Remove时务必Remove - 注意该Events官方案例都是UI环境的lua,暂不确定Game环境是否可用 - 下面是就此Events接口,皮凯的的实操例子 ### 具体例子 #### 构造计时循环 - 例如你希望这个函数每10秒运行一次 ```lua -- 因为GameCoreEventPublishComplete会每秒60次无限循环运行你添加的函数,所以10秒需要*60 local timing = 10 * 60 function function1() timing = timing - 1; --当 timing = 0时既10秒已过 if timing == 0 then --省略你具体每10秒要工作的代码程序 timing = 10 * 60; -- 重置时间 end end function function2()-- 当需要function1这个循环函数工作时,触发function2(),添加function1 Events.GameCoreEventPublishComplete.Add(function1) end function function3() -- 当不需要function1这个循环函数工作时,触发function3(),结束function1 Events.GameCoreEventPublishComplete.Add(function1) end ``` #### 定时器 - 例如你希望这个函数延迟10秒后运行 ```lua local timing = 10 * 60 function function1() timing = timing - 1; --当 timing = 0时既10秒已过 if timing == 0 then --省略你具体延迟10秒后要运行的代码程序 Events.GameCoreEventPublishComplete.Remove(function1) -- GameCoreEventPublishComplete里删除该函数,否则会一种运行它,增加没有必要的游戏运算 end end function function2()-- 当需要function1这个定时器函数工作时,触发function2(),添加function1 Events.GameCoreEventPublishComplete.Add(function1) end function function3() -- 当需要打断function1这个定时器函数工作时,触发function3(),结束function1 Events.GameCoreEventPublishComplete.Add(function1) end ``` #### 计时器框架 - 如果你的mod很多地方用到计时器为了节省写重复的代码那么你可以使用它 - 在这里皮凯提供一个较为完善的框架 ```lua CallbackDict = {}; AuxiliaryTiming = {}; ATnum = 0; -- 用于检测FuncID是否重复,或者说也是检测该FuncID函数是否还在运行 function CheckRun(FuncID) if AuxiliaryTiming[FuncID] ~= nil then return true end return false end -- TimeInSeconds需要定时执行的函数和对应时间 -- callbackFunc被延迟/循环的函数 -- loop是则为循环函数,不是或为nil则函数为延迟函数 -- FuncID用于额外的Remove停止循环/延时函数,记得不要为大于0的整数,给予其他字符串 -- Values是函数参数,注意这个参数类型一定要是表,否则报错的,可以为nil,使用详情看这段代码最后面注释 -- Replace布尔值参数用于确认是否替换原有函数,如果为true则替换原FuncID函数,如果为false则不替换FuncID同名函数 function AddTimer(TimeInSeconds, callbackFunc, loop, FuncID, Values, Replace) ATnum = ATnum + 1; -- 如果loop为nil loop = loop or false; local num = tonumber(FuncID); -- 如果FuncID为数值字符串且大于0是整数 if num and num > 0 and math.floor(num) == num then print("FuncID应当不为大于0的整数,应当给予其他字符串"); print("FuncID改为"..ATnum); FuncID = ATnum; end -- 如果FuncID发生重复 if CheckRepeat(FuncID) then if Replace then print("FuncID发生重复,给予替换,清除之前的相同FuncID函数"); CallbackDict[FuncID]() else print("FuncID发生重复,请给予其他字符串"); print("FuncID改为"..ATnum); FuncID = ATnum; end end -- 如果FuncID为nil FuncID = FuncID or ATnum; print("FuncID为:" ..FuncID) AuxiliaryTiming[FuncID] = TimeInSeconds; Values = Values or {}; -- callbackFunc插入到定时循环中 local function CreateLoopFunc() return function() AuxiliaryTiming[FuncID] = AuxiliaryTiming[FuncID] - 1; if AuxiliaryTiming[FuncID] == 0 then callbackFunc(unpack(Values)); AuxiliaryTiming[FuncID] = TimeInSeconds; end end end -- callbackFunc插入到延时触发中 local function CreateFunc() return function() AuxiliaryTiming[FuncID] = AuxiliaryTiming[FuncID] - 1; if AuxiliaryTiming[FuncID] == 0 then callbackFunc(unpack(Values)); CallbackDict[FuncID]() end end end local func = loop and CreateLoopFunc() or CreateFunc(); Events.GameCoreEventPublishComplete.Add(func); -- 直接构造好对应的关闭循环的函数,亦或者提前关闭延迟的函数以免找不到对应func CallbackDict[FuncID] = function() Events.GameCoreEventPublishComplete.Remove(func); CallbackDict[FuncID] = nil; AuxiliaryTiming[FuncID] = nil; end return ATnum; end -- 这里主要还是关闭循环的函数,亦或者提前关闭延迟的函数, -- 如果不需要这个功能请看后续删减版本 function RemoveTimer(FuncID) if CallbackDict[FuncID] then -- 还是要检测一下否则为nil又报错 CallbackDict[FuncID](); end end -------------------------------------- -- 接下来就是框架被使用, -- 建议作为一个基础性的lua脚本然后被使用 -- 两种跨lua文件使用方法 -- 直接LuaEvents或者是include(这个lua脚本) -- 我个人推荐是include -- 因为我不确定luaevent函数传过去一定正常工作 -- 相当于你函数作为参数跑另一个lua房间不确定能否正常调动原lua文件独有的东西 -- include是肯定稳的,总之如果你是使用直接LuaEvents方法麻烦和我说一下情况 -- 这个我感觉没必要直接,但还是讲一下 -- XXXAddTimer改成你独有id因为LuaEvents.这里ID是跨UILUA文件互通的,以免相同冲突 LuaEvents.XXXAddTimer.Add(AddTimer) LuaEvents.XXXRemoveTimer.Add(AddTimer) -- 当你在其他lua文件或使用该框架例子如下 LuaEvents.XXXAddTimer(180, function(a) print(a) end, true, "SSS", {"4秒循环"}, false); -- 循环函数 LuaEvents.XXXAddTimer(1200, function() print("20秒后执行"); LuaEvents.XXXRemoveTimer("SSS"); end, false, "AAA",{}, false); -- 延时函数 -- 而include了直接引用XXXAddTimer和XXXRemoveTimer函数 -------------------------------------- -- 这里额外讲述一下Values的使用: -- 1. 首先Values可以为空(nil)或者是一个表table,千万不要其他类型否则unpack(Values)会保错 -- unpack(Values)会自动拆Values表把里面元素作为参数传给callbackFunc -- 所以你需要把你要给的参数都放在Values表里,会自动unpack(Values)给予callbackFunc -- 2. 当Values为空(nil)或者是一个空表"{}"时,会给予callbackFunc一个nil参数 -- 所以要确保你的callbackFunc,要么一开始就不需要参数,要么能接受nil参数,不会导致什么BUG产生 -- 如果callbackFunc本身不需要参数,为nil即可,不会影响callbackFunc函数正常运行 -- 3. 然后如果你要给予callbackFunc参数就是一个表,一定要套一层表,抵消unpack ------------------------------------------------ -- 下面就是测试 -- 注意该测试结果需要可实时加载lua.log,我是使用官方SDK的FireTuner实时查看lua.log -- 使用官方SDK的FireTuner运行LuaEvents.PK_TestCode()即可 function TestCode2() AddTimer(180, function(a) print(a) end, true, "SSS", {"4秒循环"}, false); -- 循环函数 AddTimer(1200, function() print("20秒后执行"); LuaEvents.XXXRemoveTimer("SSS"); end, false, "AAA",{}, false); -- 延时函数 end LuaEvents.PK_TestCode2.Add(TestCode2); ``` ![](计时器测试mod/1image.png) - 最后补充,你可以根据自己实际需求更改 - 例如你不需要额外的结束循环,你需要它一直工作,你可以删除CallbackDict和FuncID相关的 - 例子如下 ```lua -- TimeInSeconds需要定时执行的函数和对应时间 -- callbackFunc被延迟/循环的函数 -- loop是则为循环函数,不是或为nil则函数为延迟函数 AuxiliaryTiming = {}; ATnum = 0; function AddTimer(TimeInSeconds, callbackFunc, loop) ATnum = ATnum + 1; -- 如果loop为nil loop = loop or false; -- callbackFunc插入到定时循环中 local function CreateLoopFunc() return function() AuxiliaryTiming[ATnum] = AuxiliaryTiming[ATnum] - 1; if AuxiliaryTiming[ATnum] == 0 then callbackFunc(); AuxiliaryTiming[ATnum] = TimeInSeconds; end end end -- callbackFunc插入到延时触发中 local function CreateFunc() return function() AuxiliaryTiming[ATnum] = AuxiliaryTiming[ATnum] - 1; if AuxiliaryTiming[ATnum] == 0 then callbackFunc(); CallbackDict[ATnum]() end end end local func = loop and CreateLoopFunc() or CreateFunc(); Events.GameCoreEventPublishComplete.Add(func); CallbackDict[ATnum] = function() Events.GameCoreEventPublishComplete.Remove(func); CallbackDict[ATnum] = nil; end end ``` - 总之结合自己需求,甚至你只需求延时,那么你完全可以删除循环亦或者你已经完成lua代码确保不会填错参数,那么关于我上面代码参数报错部分可以删除 ## 总结, - 两种方法都准时的,结合实际自行选择 - 暂时没有考虑存档退出游戏情况下,自动被清除的“在运行中的计时器的函数”在重新加载存档的情况如何自动重新启动,我个人暂时不需要这个功能,你可以使用SetProperty自行改进我的代码,也欢迎来找我进行交流,或者加入我这个项目 ### UI控件计时优缺点 - 最大优点是可以直接修改动画控件速度(“SetSpeed”) - 你如果本身就是UImod,动画控件计时也是方便的 - 缺点难以并发,例如希望两个动画控件同步开始 ### Events计时器优缺点 - 便利性不用设置UI控件,直接可以用 - 很有可能实现并发,或者减小并发误差 - 缺点速度固定,如果希望超越1秒60次是无法达到,只能是1/60秒正整数倍数秒运行一次 ## 热烈欢迎各路带佬参与到俺的文明6总结项目里 1. 可以Fork 本项目 2. 新建分支 3. 提交代码 - 联系我的方式,QQ群519747236/QQ频道-频道号: h4g7x81cj7/还可以[哔哩联系俺](https://space.bilibili.com/1440305287) ## 传送门 - [皮凯文明6总结(PK-CIV6)](https://gitee.com/XPPK/pk-civ6)