#nop vim: set filetype=tt:; /* 本文件属于 PaoTin++ 的一部分。 PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp ) 享有并保留一切法律权利 你可以在遵照 GPLv3 协议的基础之上使用、修改及重新分发本程序。 */ #var lib_ui_prompt[META] { {NAME} {提示栏插件} {DESC} {支持丰富的自定义选项,用户可用此模块定制自己的 UI。} {AUTHOR} {担子炮} }; ///=== { ///// prompt 是 PaoTin++ 中非常重要的 UI 插件,现行 UI 的主要元素都是由 prompt 来支撑的。 ///// ///// prompt 插件把整个 TinTin++ 屏幕划分为七个区域,从上到下依次为: ///// ///// Top line1 ///// Top line2 ///// ... ///// Top lineN ///// ----------- TopSepBar -------------- ///// (游戏区) ///// ----------- MidSepBar -------------- ///// Bot line1 ///// Bot line2 ///// ... ///// Bot lineN ///// ----------- BotSepBar -------------- ///// <提示符>:(输入区) ///// ///// 输入区前面的提示符部分可以用 API prompt.Change 来修改。 ///// ///// 除输入区和游戏区之外,剩下五个区域都可以定制,用来显示信息内容(以下称为字段)。 ///// 每一行都可以有多个字段。字段在视觉上由两部分组成,字段标签和字段内容。 ///// 其显示样式分别可以控制,并受到几个 prompt 特性的影响,下面会分别予以介绍。 ///// ///// 字段的编排是高度可定制的,每个字段的标签、内容、颜色、显示风格、行为属性, ///// 都可以通过自己修改 #list prompt-fields 来修改。下面是对 #list prompt-fields ///// 的格式说明,但不建议直接在此处修改变量,请通过 var/etc/ui-settings.tin 来修改。 ///// prompt 模块在启动时会自动加载该文件。 ///// ///// #list prompt-fields 是一个列表,其中中每个元素是一个 tt++ table,代表一个字段。 ///// tt++ table 由一组选项和与之对应的选项值来组成,用来说明想要定制的字段的选项。 ///// ///// 下面是所有可供设置的字段选项: ///// ///// 1. place 枚举值 可选项: {Top|TopSepBar|MidSepBar|Bot|BotSepBar} ///// 希望将字段显示在屏幕上的什么位置。 ///// 其中 {Top} 和 {Bot} 支持多行,其它三个只有一行。 ///// 默认为 BotSepBar。 ///// ///// 2. line 自然数 ///// 只有 place={Top|Bot} 才支持 line,代表第几行,最上面是第一行。 ///// 默认为 1。 ///// ///// 3. order 自然数 ///// 在同一行中的次序,默认按照在 list 中出现的顺序来排序。 ///// ///// 4. label 中英文字符串 ///// 字段标签,每个字段都可以有一个标签,显示在内容前面。 ///// 默认为空白。 ///// ///// 5. color tintin 格式的颜色代码,例如 ///// 字段值的颜色,通过该选项可以为字段值指定一个与配色主题不一样的颜色。 ///// 一般来说只有极个别选项才需要特别的颜色,没必要为每个选项都指定颜色, ///// 因为那样的话你还不如去设置配色主题,参见下面配色主题的设置。 ///// 默认为空白,代表采用配色主题。 ///// ///// 6. name 英文字符串 不能为空 ///// 字段名称,必须唯一,不能重复。 ///// ///// 7. width 整数 ///// 字段内容的宽度,如果字段内容长度变化较大,可以设置一个固定宽度,以免晃动。 ///// 默认为 0,表示按照内容长度自动适配。 ///// ///// 8. visibility 枚举值 {HideEmpty|HideCool|HideZero|HideLabel|Always} ///// 含义如下: ///// * HideEmpty 如果字段内容为空则不予显示。 ///// * HideCool 如果内容已陈旧则不予显示。请参考 cooldown 选项。 ///// * HideZero 如果倒计时归零则不予显示。请参考 countdown 选项。 ///// * HideLabel 仅显示字段内容,不显示字段标签。 ///// * Always 总是显示该字段的标签和内容。 ///// 所有的 Hide* 选项自动拥有 HideEmpty 语义。 ///// 默认为 HideEmpty。 ///// ///// 9. cooldown 非负整数 ///// 该字段的有效时间。超过有效时间没有更新的字段将在视觉上予以弱化显示,以提醒 ///// 用户。也就是说,如果 cooldown 不为 0,则该字段被更新时其标签将会以颜色主题 ///// 中 HotLabel 所指定的风格显示(意指新鲜的内容),之后持续若干秒后,转为由 ///// CoolLabel 所指定的风格显示(意指陈旧的内容)。 ///// 持续时间由 cooldown 字段的值来决定,单位为秒。 ///// 陈旧的内容遇到 visibility=HideCool 时则不予显示。 ///// 如果 cooldown 为 0,则不会以 CoolLabel 风格显示。 ///// 默认值为 0。 ///// ///// 10. countdown 枚举值 {Auto|Clock|Seconds} ///// 倒计时类型的字段。其内容为一个非负整数,代表需要倒计时的秒数,或者也可以夹杂 ///// 于文字内容当中,此时文字内容中所有形如 (%d) 的内容将会被替换成倒计时显示。 ///// 本插件会自动为该字段更新其内容,使得用户能够看到倒计时的效果。 ///// 倒计时有两种显示样式,本选项设置为 Clock 时,显示为时钟样式(N天HH:MM:SS), ///// 设置为 Seconds 时,显示为读秒样式(N秒)。设置为 Auto 时则根据剩余时间长短, ///// 自动切换两种显示效果。 ///// 倒计时类型的字段,如果同时设置了 visibility=HideZero,那么倒计时归零后会自动 ///// 隐藏该字段。 ///// 在倒计时存续期间,该字段的 cooldown 属性将会被抑制,直到倒计时归零后才起作用。 ///// 这条规则确保倒计时字段即使设置了 visibility=HideCool,也至少会完成倒计时,而 ///// 倒计时结束后,如果 cooldown 时间比倒计时时间还要长,则还会继续显示一段时间之 ///// 后才会被隐藏。 // }; VAR {信息栏的字段列表} prompt-fields {}; VAR {信息栏的默认配色主题} prompt-theme { {Disable}{} {BusyColor}{<239>} {BattleColor}{<119>} {BattleBusyColor}{<599><119>} {TopSepBar}{<229>} {MidSepBar}{<229>} {BotSepBar}{<229>} {HotLabel}{<199>} {CoolLabel}{<109>} {Value}{<299>} }; VAR {常用图标} prompt-icon { {DisableRefresh}{🚫} }; VAR {热键绑定} global-key-bindings { {1} {{key}{\cos} {action}{prompt.ToggleSwitch}} }; VAR {信息栏的顶部高度(自动计算)} prompt-top-max-line {0}; VAR {信息栏的底部高度(自动计算)} prompt-bot-max-line {0}; VAR {屏幕的宽度(自动更新)} prompt-screen-width {0}; VAR {所有的字段值的字典} prompt-dict {}; VAR {上次实际绘制屏幕的时间} prompt-refresh { {global}{false} {lines} {false} {prompt}{false} }; VAR {命令输入提示符} prompt-prompt {Input}; #func {lib_ui_prompt.Init} { load-file {etc/ui-settings.tin}; #local _ @prompt.ScreenWidth{}; prompt.Set {}; prompt.bindKey; prompt.Enable; #return true; }; #nop 设置字段值,被设置的字段值将立即显示在屏幕上; #alias {prompt.Set} { #local fields {%1}; #local now {}; #format now {%T}; #local field {}; #foreach {*fields[]} {field} { #var prompt-dict[$field] { {updateTime}{$now} {showTime}{$now} {value}{$fields[$field]} }; }; }; #nop 设置提示符,提示符位于屏幕最下方输入区的首部,用作指示整个游戏所处的状态; #alias {prompt.Change} { #local text {%1}; #var prompt-prompt {$text}; #echo {{\r\e[K$prompt-prompt}{-1}}; }; #nop 设置字段值,被设置的字段值将立即显示在屏幕上,但会在一段时间后消失; #alias {prompt.SetAwhile} { #local id {%1}; #local value {%2}; #local secs {%3}; #if { "$secs" == "" } { #local secs {5}; }; prompt.Set {{$id}{$value}}; #line sub {VARIABLES} { #delay prompt-set-awhile-$id { prompt.Set {{$id}{}}; } {$secs}; }; }; #nop 显示字段值; #alias {prompt.refresh} { #local topMaxLine {0}; #local botMaxLine {0}; #local topLines {}; #local botLines {}; #local topSepBar {}; #local midSepBar {}; #local botSepBar {}; #foreach {*{prompt-fields[]}} {idx} { #local field {${prompt-fields[$idx]}}; #if { "$field[visibility]" == "" } { #local field[visibility] {HideEmpty}; }; #switch {"$field[place]"} { #case {"Top"} { #while {1} { #if { $field[line] <= $topMaxLine } { #break; }; #math topMaxLine {$topMaxLine + 1}; #local topLines[$topMaxLine] {}; }; #if { "$field[order]" == "" } { #local field[order] {@math.Eval{ @math.Max{0;*topLines[$field[line]][]} + 1 }}; }; #local topLines[$field[line]][$field[order]] {$field}; }; #case {"Bot"} { #while {1} { #if { $field[line] <= $botMaxLine } { #break; }; #math botMaxLine {$botMaxLine + 1}; #local botLines[$botMaxLine] {}; }; #if { "$field[order]" == "" } { #local field[order] {@math.Eval{ @math.Max{0;*botLines[$field[line]][]} + 1 }}; }; #local botLines[$field[line]][$field[order]] {$field}; }; #case {"TopSepBar"} { #if { "$field[order]" == "" } { #local field[order] {@math.Eval{ @math.Max{0;*topSepBar[]} + 1 }}; }; #local topSepBar[$field[order]] {$field}; }; #case {"MidSepBar"} { #if { "$field[order]" == "" } { #local field[order] {@math.Eval{ @math.Max{0;*midSepBar[]} + 1 }}; }; #local midSepBar[$field[order]] {$field}; }; #case {"BotSepBar"} { #if { "$field[order]" == "" } { #local field[order] {@math.Eval{ @math.Max{0;*botSepBar[]} + 1 }}; }; #local botSepBar[$field[order]] {$field}; }; #default { #echo {配置有误,请检查。place=[%s]} {$field[place]}; }; }; }; #local allBarColor {}; #if { "${prompt-dict[busy][value]}" == "true" } { #local allBarColor {${prompt-theme[BusyColor]}}; }; #if { "${prompt-dict[battle][value]}" == "true" } { #local allBarColor {${prompt-theme[BattleColor]}}; }; #if { "${prompt-dict[battle][value]}" == "true" && "${prompt-dict[busy][value]}" == "true" } { #local allBarColor {${prompt-theme[BattleBusyColor]}}; }; #if { "${prompt-dict[disable][value]}" != "" } { #local allBarColor {${prompt-theme[Disable]}}; }; #if { &topSepBar[] > 0 } { #math topMaxLine {$topMaxLine + 1}; }; #if { $botMaxLine == 0 && ( &midSepBar[] == 0 || &midSepBar[] == 0 ) } { #local botMaxLine {1}; }; #else { #math botMaxLine {$botMaxLine + 2}; }; #local content {}; #list content create {}; #local line {}; #local delta {0}; #foreach {*topLines[]} {line} { #local fields {$topLines[$line]}; #local text {@__prompt_build_line__{{$fields}}}; #if { $text[width] > 0 } { #local realLine {}; #math realLine {$line - $delta}; #list content {add} {{{line}{$realLine}{text}{$text[text]}}}; }; #else { #math delta {$delta + 1}; #math topMaxLine {$topMaxLine - 1}; }; }; #if { &topSepBar[] > 0 } { #local text {@__prompt_build_line__{{$topSepBar}}}; #if { $text[width] == 0 && $topMaxLine == 1 } { #math topMaxLine {$topMaxLine - 1}; }; #else { #local barColor {${prompt-theme[TopSepBar]}}; #if { "$allBarColor" != "" } {#local barColor {$allBarColor}}; #local text {@__prompt_fill_line__{{$text[text]};{$text[width]};$barColor}}; #list content {add} {{{line}{$topMaxLine}{text}{$text}}}; }; }; #if { &botSepBar[] > 0 || $botMaxLine > 0 } { #local text {@__prompt_build_line__{{$botSepBar}}}; #local barColor {${prompt-theme[BotSepBar]}}; #if { "$allBarColor" != "" } {#local barColor {$allBarColor}}; #local text {@__prompt_fill_line__{{$text[text]};{$text[width]};$barColor}}; #list content {add} {{{line}{-2}{text}{$text}}}; }; #local delta {0}; #local line {0}; #if { &botLines[] > 0 } { #loop {&botLines[]} {1} {line} { #local fields {$botLines[$line]}; #local text {@__prompt_build_line__{{$fields}}}; #if { $text[width] > 0 } { #local realLine {}; #math realLine {$line - $botMaxLine - 1}; #list content {add} {{{line}{$realLine}{text}{$text[text]}}}; }; #else { #math botMaxLine {$botMaxLine - 1}; }; }; }; #if { &midSepBar[] > 0 || &botLines[] > 0 } { #local text {@__prompt_build_line__{{$midSepBar}}}; #local barColor {${prompt-theme[MidSepBar]}}; #if { "$allBarColor" != "" } {#local barColor {$allBarColor}}; #local text {@__prompt_fill_line__{{$text[text]};{$text[width]};$barColor}}; #math line {$botMaxLine + 1}; #list content {add} {{{line}{-$line}{text}{$text}}}; }; #if { "${prompt-top-max-line}" != "$topMaxLine" || "${prompt-bot-max-line}" != "$botMaxLine" } { #local lineWidth {@prompt.ScreenWidth{}}; #local spaceLine {}; #format spaceLine {%-${lineWidth}s} {}; #local newMax {$topMaxLine}; #while { $newMax < ${prompt-top-max-line} } { #math newMax {$newMax + 1}; #echo {{$spaceLine}{$newMax}}; }; #local newMax {$botMaxLine + 1}; #while { $newMax < ${prompt-bot-max-line} } { #math newMax {$newMax + 1}; #echo {{$spaceLine}{-$newMax}}; }; #var prompt-top-max-line {$topMaxLine}; #var prompt-bot-max-line {$botMaxLine}; #split {$topMaxLine} {$botMaxLine}; #buffer end; }; #local idx {}; #foreach {*content[]} {idx} { #local line {$content[$idx]}; #echo {{%s}{$line[line]}} {$line[text]}; }; #local prompt {${prompt-prompt}}; #if { "$prompt" != "" } { #local prompt {$prompt}; #echo {{$prompt}{-1}}; }; }; #func {__prompt_build_line__} { #local fields {%1}; #local text {}; #local order {}; #local lineWidth {0}; #foreach {*fields[]} {order} { #local field {$fields[$order]}; #local name {$field[name]}; #local label {$field[label]}; #local color {$field[color]}; #local width {$field[width]}; #local value {${prompt-dict[$name]}}; #local now {}; #format now {%T}; #nop 所有的 Hide* 选项自动拥有 HideEmpty 语义。; #if { "$field[visibility]" == "Hide%*" && "$value[value]" == "" } { #continue; }; #nop 检查是否是倒计时,以及倒计时是否已经归零; #local zero {undef}; #if { "$field[countdown]" != "" } { #local seconds {}; #math seconds {$now - ${prompt-dict[$name][showTime]}}; #replace value[value] {^%+1..d$} {@__prompt_countdown__{&1;$seconds}}; #replace value[value] {(%+1..d)} {(@__prompt_countdown__{&1;$seconds})}; #if { "$value[value]" != "{[1-9][0-9]*|.*\([1-9][0-9]*\).*}" } { #local zero {true}; }; #else { #local zero {false}; }; #var {prompt-dict[$name][showTime]} {$now}; #var {prompt-dict[$name][value]} {$value[value]}; #replace value[value] {^%+1..d$} {@__prompt_show_countdown__{$field[countdown];&1}}; #replace value[value] {(%+1..d)} {(@__prompt_show_countdown__{$field[countdown];&1})}; }; #nop 如果设置了 HideZero,那么倒计时归零时,不予显示该字段。; #if { "$field[visibility]" == "HideZero" && "$zero" == "true" } { #continue; }; #nop 检查是否已经是冷却了的字段,倒计时字段当倒计时持续时不会变为冷却; #local cool {false}; #if { $field[cooldown] > 0 && $now - $value[updateTime] > $field[cooldown] && "$zero" != "false" } { #local cool {true}; }; #nop 如果设置了 HideCool,那么内容冷却之后则不予显示; #if { "$field[visibility]" == "HideCool" && "$cool" == "true" } { #continue; }; #nop 如果设置了 HideLabel,那么不显示标签; #if { "$field[visibility]" == "HideLabel" } { #local label {}; }; #if { "$label" != "" } { #nop 如果全局开关已经禁用,则忽略所有配色,全部显示为禁用色; #if { "${prompt-dict[disable][value]}" != "" } { #replace label {^<{[a-zA-Z0-9]+}>} {}; #local label {${prompt-theme[Disable]}$label}; }; #else { #nop 否则根据内容的新鲜程度自动改变颜色; #if { "$cool" == "true" } { #replace label {^<{[a-zA-Z0-9]+}>} {}; #local label {${prompt-theme[CoolLabel]}$label}; }; #else { #local label {${prompt-theme[HotLabel]}$label}; }; }; #local label {$label<299> }; }; #if { "$field[visibility]" == "HideLabel" } { #local label {}; }; #if { "$color" == "" } { #local color {${prompt-theme[Value]}}; }; #format value {$color%-${width}s<299>} {$value[value]}; #math lineWidth {$lineWidth + @str.Width{$label} + @str.Width{$value}}; #if { "$text" == "" } { #local text {$label$value}; }; #elseif { "$label$value" != "" } { #local text {$text $label$value}; #math lineWidth {$lineWidth + 1}; }; }; #return {{width}{$lineWidth}{text}{$text}}; }; #func {__prompt_countdown__} { #local value {%1}; #local count {%2}; #math value {$value - $count}; #if { $value < 0 } { #local value {0}; }; #return {$value}; }; #func {__prompt_show_countdown__} { #local type {%1}; #local secs {%2}; #local ret {}; #if { "$type" == "Auto" } { #if { $secs < 600 } { #local type {Seconds}; }; #else { #local type {Clock}; }; }; #if { "$type" == "Seconds" } { #local ret {${secs}s}; }; #elseif { "$type" == "Clock" } { #if { $secs > 86400 } { #math ret {$secs / 86400}; #local ret {$ret天}; #math secs {$secs % 86400}; }; #local hour {}; #local mins {}; #math hour {$secs / 3600}; #math secs {$secs % 3600}; #math mins {$secs / 60}; #math secs {$secs % 60}; #format ret {%s%%02d:%%02d:%%02d} {$ret} {$hour} {$mins} {$secs}; }; #return {$ret}; }; #func {__prompt_fill_line__} { #local text {%1}; #local width {%2}; #local color {%3}; #local newText {@str.Trim{$text}}; #math width {$width + @str.Width{$newText} - @str.Width{$text}}; #local screenWidth {$prompt-screen-width}; #local leftLen {0}; #local rightLen {0}; #math leftLen {($screenWidth - $width) / 2 - 1}; #math rightLen {$screenWidth - $leftLen - $width - 2}; #local left {}; #format {left} {%${leftLen}s} {}; #replace {left} { } {─}; #local right {}; #format {right} {%${rightLen}s} {}; #replace {right} { } {─}; #if { $leftLen < 0 } { #format {newText} {%$screenWidth.${screenWidth}s} {$newText}; #return {$newText}; }; #elseif { "$newText" == "" } { #return {$color$left──$right<099>}; }; #else { #return {$color$left<299> $newText $color$right<099>}; }; }; #event {SCREEN RESIZE} { #var prompt-top-max-line {0}; #var prompt-bot-max-line {0}; #var prompt-screen-width {0}; #local _ @prompt.ScreenWidth{}; prompt.refresh; #buffer end; }; #event {CATCH IAC SB NAWS} {#0}; #alias {prompt.Disable} { prompt.Set {{disable}{${prompt-icon[DisableRefresh]}}}; prompt.refresh; #untick prompt.refresh; }; #alias {prompt.Enable} { prompt.Set {{disable}{}}; Tick prompt.refresh {prompt.refresh} 1; }; #alias {prompt.ToggleSwitch} { #if { "${prompt-dict[disable][value]}" == "" } { prompt.Disable; }; #else { prompt.Enable; }; }; #alias {prompt.bindKey} { #local idx {}; #foreach {*{global-key-bindings[]}} {idx} { #local key {${global-key-bindings[$idx][key]}}; #local code {${global-key-bindings[$idx][action]}}; #line sub var #macro {$key} {$code}; }; }; ///=== { // ## prompt.HotKeys // 显示系统已配置的热键清单。 // 本命令也可写作 HOTKEYS,作用相同。 // }; #alias {HOTKEYS} {prompt.HotKeys}; #alias {prompt.HotKeys} { #echo; #echo { <128>%-20s %-30s %-40s} {快捷键} {含义} {对应代码}; #draw Yellow scroll line 1 1 1 90; #local idx {}; #foreach {*{global-key-bindings[]}} {idx} { #local key {${global-key-bindings[$idx][key]}}; #local meaning {${global-key-bindings[$idx][meaning]}}; #local code {${global-key-bindings[$idx][action]}}; #replace key {\\c} {Ctrl+}; #replace key {Ctrl+%.%+1..*} {Ctrl+&1 &2}; #echo { %-20s %-30s %-40s} {$key} {$meaning} {$code}; }; #echo; okLog 快捷键中如果存在多个按键序列的,带加号的为一组,需要同时按下,其余依次按下即可。请注意区分大小写。; #echo; }; #alias {NOTE} {prompt.Note}; #alias {prompt.Note} { prompt.Set {{note}{%0}}; #if { @isEmpty{%0} } { okLog 你轻轻地从笔记本上撕下一页,捏成一个纸团丢到了垃圾桶里。; }; #else { okLog 你抬头看着远方,若有所思,接着又奋笔疾书,在笔记本上记下一些东西。; }; }; #func {prompt.ScreenWidth} { #if { $prompt-screen-width == 0 } { #screen get COLS prompt-screen-width; }; #return {$prompt-screen-width}; }; #alias {prompt.test} { #local fullme {<219>很久没有进行机器人检查(fullme)了,任务奖励将受到影响。<299>}; #local fullme {http://pkuxkx.com/antirobot/robot.php?filename=1576762922984895}; prompt.Set { {URL}{$fullme} }; prompt.Set { {pot}{357.75万} {exp}{7510.56万} {expSpd}{11.88万/小时} {profit}{308金币/小时} }; prompt.Set { {job}{<171>乔峰} {stage}{寻找NPC} {area}{建康} {room}{中城} {npc}{庞九公} {type}{杀死} }; };