#nop vim: set filetype=tt:; /* 本文件属于 PaoTin++ 的一部分 =========== PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp ) 享有并保留一切法律权利 你可以在遵照 GPLv3 协议的基础之上使用、修改及重新分发本程序。 =========== */ VAR {逍遥行地图数据} map.xiaoyao.map {}; VAR {逍遥行房间数据} map.xiaoyao.room {}; load-module basic/busy; event.HandleOnce {map/init} {map/xiaoyao} {map} {xiaoyao.Init}; VAR {逍遥行正在赶路} {xiaoyao.under-way} {0}; #alias {xiaoyao.Init} { event.Handle {map/GotRoomInfo} {xiaoyao.try-locate} {map/xiaoyao} {xiaoyao.try-locate}; storage.Load {map-xiaoyao} {map.xiaoyao.map;map.xiaoyao.room}; load-config xiaoyao; xiaoyao.fixMap; }; #nop fixMap 只修复连接,不允许添加新的节点; #alias {xiaoyao.fixMap} { #local node {}; #foreach {*map.xiaoyao.Special[]} {node} { #if { &map.xiaoyao.map[$node][] == 0 } { #continue; }; #local target {}; #foreach {*map.xiaoyao.Special[$node][]} {target} { #if { &map.xiaoyao.map[$target][] == 0 } { #continue; }; #local link {$map.xiaoyao.Special[$node][$target]}; #if { "$link" == "DELETE" } { #unvar map.xiaoyao.map[$node][$target]; }; #else { #var map.xiaoyao.map[$node][$target] {$link}; }; }; }; }; #alias {xiaoyao.checkMap} { #local node {}; #local areaMap {}; #foreach {*map.xiaoyao.map[]} {node} { #local next {}; #if { &map.xiaoyao.map[$node][] == 1 && "$map.xiaoyao.map[$node][DOCK]" != "" } { warnLog 这是个纯粹的码头 => $node; }; #foreach {*map.xiaoyao.map[$node][]} {next} { #regex {$node} {%*(%*的%*)} { #format {areaMap[&2][$node]} {true}; }; #local link {$map.xiaoyao.map[$node][$next]}; #if { "$next" == "DOCK" || "$link" == "TODO" } { errLog $node => $next 尚未联通。; }; }; }; #local maxNodeNum {0}; #local maxNodeNumArea {}; #local area {}; #foreach {*areaMap[]} {area} { okLog $area(&areaMap[$area][]): @slist.Join{{@slist.FromList{@list.FromSlist{*areaMap[$area][]}}};、}; #if { &areaMap[$area][] > $maxNodeNum } { #local maxNodeNum {&areaMap[$area][]}; #local maxNodeNumArea {$area}; }; }; okLog 地图连接性检查正常,一共包含 &map.xiaoyao.map[] 个节点,&areaMap[] 个区域,节点最多的区域是「$maxNodeNumArea」,共有 $maxNodeNum 个节点。; }; #alias {xiaoyao.SimpleMap} { #local bigArea {}; #local areaMap {}; #local area {}; #local node {}; #foreach {*map.xiaoyao.map[]} {node} { #regex {$node} {%*(%*的%*)} { #format {area} {%s} {&2}; }; #local bigArea {@map.AreaColor{$area}}; #list {areaMap[$bigArea][$area]} add {$node}; }; #local screenWidth {}; #screen get cols screenWidth; #local lines {}; #local line {}; #local color {}; #local lineWidth {0}; #local buttons {}; #local bigAreaCount {0}; #local areaCount {0}; #local nodeCount {0}; #loop {1} {&areaMap[]} {bigArea} { #math bigAreaCount {$bigAreaCount + 1}; #local bgColor *areaMap[+$bigArea]; #local fgColor {0}; #loop {1} {&areaMap[+$bigArea][]} {area} { #math areaCount {$areaCount + 1}; #math fgColor {($fgColor + 1) % 2}; #local index {$bgColor}; #replace index {%*4%+1d%*} {&2}; #if { "$bgColor" == "46" } { #local color {\e[${bgColor};@math.Eval{30 + $fgColor * 4}m}; }; #elseif { $index == 3 } { #local color {\e[${bgColor};@math.Eval{30 + $fgColor * 4}m}; }; #else { #local color {\e[${bgColor};@math.Eval{37 - $fgColor * 4}m}; }; #cat line {$color}; #loop {1} {&areaMap[+$bigArea][+$area][]} {node} { #local node {$areaMap[+$bigArea][+$area][+$node]}; #local city {$node}; #replace city {%S(%S)} {&1}; #local width {@math.Eval{ ( @math.Int{@str.Width{$city} * 1.0 / 4 + 0.4} + 1 ) * 4 }}; #if { $lineWidth + $width > $screenWidth } { #if { $screenWidth > $lineWidth } { #cat line {@str.Space{@math.Eval{$screenWidth - $lineWidth}}}; }; #local lineWidth {0}; #list lines add {{ {text}{$line} {buttons}{$buttons} }}; #local line {$color}; #local buttons {}; }; #cat line {@str.AlignLeft{{$city};$width}}; #list buttons add {{ {begin}{@math.Eval{$lineWidth + 1}} {end}{@math.Eval{$lineWidth + $width}} {node}{$node} }}; #local lineWidth {@math.Eval{ $lineWidth + $width }}; }; }; }; #list lines add {{ {text}{$line} {buttons}{$buttons} }}; #class xiaoyao.Map kill; #class xiaoyao.Map open; #local lineNo {1}; #loop {1} {&lines[]} {lineNo} { #echo {%s} {$lines[$lineNo][text]}; #local button {}; #foreach {$lines[$lineNo][buttons][]} {button} { #local row {@math.Eval{$lineNo - 4 - &lines[] - $prompt-bot-max-line}}; #line sub var #button {$row;$button[begin];$row;$button[end]} { #class xiaoyao.Map kill; #buffer lock off; #buffer end; xiaoyao.Goto $button[node]; }; }; }; #line oneshot #event {RECEIVED INPUT CHARACTER} { okLog 你略作观察后收起了地图继续赶路。; #class xiaoyao.Map kill; #buffer end; }; #class xiaoyao.Map close; okLog 共包含 &map.xiaoyao.map[] 个节点,$bigAreaCount 个大区,$areaCount 个区域。; #buffer lock on; }; #alias {xiaoyao.Map} { #local retry {@defaultNum{%1;0}}; #local args {%2}; #if { "$args" != "" } { xtt.Send {map $args}; #return; }; #local retry {@defaultNum{%1;0}}; #if { $retry == 0 && ( "$gMapRoom[node]$gMapRoom[dock]" == "" || &gMapRoom[area][] == 0 || "@map.GetArea{}" == "" ) } { #line sub {func;var} event.HandleOnce {map/GotArea} {xiaoyao/Map} {map} {xiaoyao.Map {@math.Eval{$retry + 1}} $args}; map.GetArea; #return; }; #local here {@xiaoyao.Locate{}}; #if { "$here" == "" } { xtt.Send {map}; #return; }; #class xiaoyao.Map open; #alias {xiaoyao.Map.open} { #class xiaoyao.Map open; #var xiaoyao.Map.lines {0}; #action {^%*{|ID=map}$} {#math xiaoyao.Map.lines {$xiaoyao.Map.lines + 1}} {2.5}; #sub {~{*UTF8};4;44m{\p{Han}+}} {;4;44m@mslp.Exec{xiaoyao.Map.Goto %%%1;%%%1}}; #action {担子炮修订时间} {xiaoyao.Map.close} {2.0}; #class xiaoyao.Map close; #if { @existsFile{var/data/map.txt} } { #scan txt var/data/map.txt; }; #elseif { @existsFile{mud/$gCurrentMUDLIB/data/map.txt} } { #scan txt mud/$gCurrentMUDLIB/data/map.txt; }; #elseif { @existsFile{data/map.txt} } { #scan txt data/map.txt; }; #else { errLog 缺少 data/map.txt 文件。; xtt.Send {map}; }; }; #alias {xiaoyao.Map.close} { #local lines {}; #screen get rows lines; #if { $prompt-bot-max-line > 0 } { #math lines {$lines - $prompt-bot-max-line - 1}; }; #if { $prompt-top-max-line > 0 } { #math lines {$lines - $prompt-top-max-line - 1}; }; #buffer end; keyboard.LessMode; #math lines {$xiaoyao.Map.lines - $lines + 2}; #if { $lines > 0 } { #buffer up $lines; }; #class xiaoyao.Map kill; }; okLog <560>你展开地图,发现不知为什么许多地方似乎被人涂成了蓝色。<099>; xiaoyao.Map.open; #class xiaoyao.Map close; }; #alias {xiaoyao.Map.Goto} { #local node {%1}; keyboard.NormalMode; #if { &map.xiaoyao.map[$node(%*)] == 1 } { #local node *map.xiaoyao.map[$node(%*)]; }; xiaoyao.Goto $node; }; #alias {map} { #local width {0}; #screen get cols width; #if { $width > 132 } { xiaoyao.Map 0 {%0}; }; #else { xiaoyao.SimpleMap; }; }; ///=== { // ## xiaoyao.Goto <目的节点> // 逍遥行快速行走,可以从一个城市移动到另一个城市。支持自动坐船、自动过河。 // // 逍遥行底层采用的是系统 walk 命令,这要求你必须站在逍遥行节点才能使用本别名。 // 但是本别名<169>可以自动连续 walk<299>,达到长途行走的目的。 // // 为避免重复,完整的逍遥行节点名称采用「<169>节点名(区域的房间名)<299>」格式表达。 // 目的地暂时仅支持中文,但允许模糊查询。举例来说,假如你想前往「全真派(全真教的宫门)」, // 那么你输入「全真派」、「全真教」、「宫门」、「全真」、甚至「教的宫」都是可以的。 // // 本别名存在三个变体,你可以用 HELP 进一步了解: // - xiaoyao.GotoThen: 允许调用者在行走完成之后,执行一段代码。 // - xiaoyao.GotoEmit: 允许调用者在行走完成之后,发射一个事件。 // - xy: 专供命令行使用并为此做过特别优化的别名,不要在脚本中使用它。 // // 关于 walk 命令的细节可以参考 help walk。 // }; #alias {xiaoyao.Goto} { xiaoyao.goto {%1} {%2} {xiaoyao.Goto}; }; ///=== { // ## xy <目的地> [<回调代码>] // 通过逍遥行前往目的地。如果指定了回调代码,那么到达目的地之后,会执行它。 // 参见 HELP xiaoyao.Goto。 // }; #alias {xy} { #local target {%1}; #local callback {%22}; #info arguments save; #local count &info[ARGUMENTS][]; #unvar info[ARGUMENTS]; #if { $count == 1 } { xtt.Usage xiaoyao.Goto {<169>这里是 PaoTin++ 逍遥行}; #return; }; #if { $count > 3 } { #local callback {%20}; #local old {%20}; #replace {callback} {^$target } {}; #if { "$callback" === "$old" } { #replace {callback} {^\\x7B$target\\x7D } {}; }; }; #line sub {escapes;var} xiaoyao.GotoThen {$target} {$callback}; }; ///=== { // ## xiaoyao.GotoThen <目的地> [<回调代码>] // 通过逍遥行前往目的地。如果指定了回调代码,那么到达目的地之后,会执行它。 // 参见 HELP xiaoyao.Goto。 // }; #alias {xiaoyao.GotoThen} { #local target {%1}; #local callback {%22}; #local hook {map/xiaoyao/@uuid{}}; #class xiaoyao.Goto open; #if { "$callback" != "" } { #line sub {escapes;var} #alias {xiaoyao.Goto.end} {#class xiaoyao.Goto kill; $callback}; event.ClassHandleOnce {map/walk/continue} {$hook} {map/xiaoyao} {xiaoyao.Goto.end}; event.ClassHandleOnce {map/walk/failed} {$hook} {map/xiaoyao} {xiaoyao.Goto.end}; }; #class xiaoyao.Goto close; xiaoyao.goto {$target} {$hook} {xiaoyao.GotoThen}; }; ///=== { // ## xiaoyao.GotoEmit <目的地> [<回调钩子名称>] // 通过逍遥行前往目的地。 // 如果你事先注册了指定名称的回调钩子在事件 map/walk/continue 上,那么到达目的地之后,会唤醒它。 // 参见 HELP xiaoyao.Goto。 // }; #alias {xiaoyao.GotoEmit} { #local target {%1}; #local hook {%2}; xiaoyao.goto {$target} {$hook} {xiaoyao.GotoEmit}; }; #alias {xiaoyao.goto} { #local target {%1}; #local hook {@default{%2;xiaoyao/goto/end}}; #local name {@default{%3;xiaoyao.GotoEmit}}; #local retry {@defaultNum{%4;0}}; #if { "$target" == "" } { xtt.Usage $name {<169>这里是 PaoTin++ 逍遥行}; xiaoyao.goto.cancel {$hook}; #return; }; #if { &map.xiaoyao.map[] == 0 } { errLog 加载逍遥行节点数据文件失败。; okLog 请确保逍遥行数据文件 var/data/map-xiaoyao.tin 或 data/map-xiaoyao.tin 正确无误。; xiaoyao.goto.cancel {$hook}; #return; }; #if { $retry > 1 } { errLog 请先前往逍遥行节点。所有的码头、walk 节点均为逍遥行节点。; xiaoyao.goto.cancel {$hook}; #return; }; #if { "$gMapRoom[node]$gMapRoom[dock]" == "" || &gMapRoom[area][] == 0 || "@map.GetArea{}" == "" } { #nop 获取区域的过程中可能无法产生积极的结果,那么就以角色移动为失败标志,取消走路。; #class xiaoyao.goto.locate open; #line sub {func;var} event.ClassHandleOnce map/GotArea {xiaoyao/goto/locate} {xiaoyao} { #class xiaoyao.goto.locate kill; xiaoyao.goto {$target} {$hook} {@math.Eval{$retry + 1}}; }; #line sub {func;var} event.ClassHandleOnce GMCP.Move {xiaoyao/goto/locate} {xiaoyao} { xiaoyao.goto.cancel {$hook}; #class xiaoyao.goto.locate kill; }; map.GetArea; #class xiaoyao.goto.locate close; #return; }; #local here {@xiaoyao.Locate{}}; #if { "$here" == "" } { errLog 请先前往逍遥行节点。所有的码头、walk 节点均为逍遥行节点。; xiaoyao.goto.cancel {$hook}; #return; }; #if { "$here" == "%*$target%*" } { #if { "$hook" != "" } { okLog 你已经来到了 $here; event.DelayEmit map/walk/continue {$hook}; }; #return; }; infoLog 计算从<129>$here<299>到<139>$target<299>的路径。; #local target {@xiaoyao.findPath{$here;"NODE" == "%*$target%*"}}; #if { "$target" == "" } { errLog 找不到路径。; xiaoyao.goto.cancel {$hook}; #return; }; #if { "$target[path]" == "" } { errLog 找不到路径,似乎安装出错了,请联系开发者。; xiaoyao.goto.cancel {$hook}; #return; }; okLog 计算结果: {$target[path]}; #replace {target[route]} {(%*)} {}; okLog 途经节点: $target[route]; prompt.Set {{walk}{<139>正在前往 <129>$target[room]<139>...<299>}}; #line sub var event.HandleOnce map/walk/continue {xiaoyao/goto} {map/xiaoyao} {xiaoyao.walk-end $hook}; #var xiaoyao.under-way {1}; map.WalkNodes {$target[path]} {xiaoyao/goto}; }; #alias {xiaoyao.goto.cancel} { #local hook {%1}; #if { "$hook" != "" } { event.DelayEmit {map/walk/failed} {$hook}; }; }; #alias {xiaoyao.walk-end} { #local hook {%1}; okLog 行走完成。; #if { "$hook" != "" } { event.DelayEmit map/walk/continue {$hook}; }; #var xiaoyao.under-way {0}; #nop 配合 try-locate 进行定位。; look; }; #alias {xiaoyao.try-locate} { #nop 移动中,位置随时会变。; #if { ! @ga.AllDone{} || $xiaoyao.under-way } { #return; }; #nop 探索中,数据内容不全。 ; #if { @isTrue{$xiaoyao.explore} } { #return; }; #nop 光看房间名就就不像是节点。; #local nodes {$map.xiaoyao.room[$gMapRoom[name]]}; #if { "$nodes" == "" } { #if { &map.xiaoyao.map[] > 0 } { prompt.Set {{walk}{<139>逍遥行已启动,可识别 &map.xiaoyao.map[] 个节点,目前工作正常。<299>}}; }; #return; }; dbgLog map => 空闲,没有移动,看名字($gMapRoom[name])可能是节点,那么决定调查一下。; event.HandleOnce {map/GotArea} {xiaoyao.locate} {map/xiaoyao} {xiaoyao.locate}; map.GetArea; }; #alias {xiaoyao.locate} { #local here {@xiaoyao.Locate{}}; #if { "$here" == "" } { #return; }; okLog 这里是 $here; #local links {@table.Keys{map.xiaoyao.map[$here];%*}}; #local buttons {}; #local link {}; #foreach {$links} {link} { #local short {@str.Replace{$link;{%*(%*的%*)};{&1}}}; #local button {【@mslp.Exec{{xiaoyao.Goto $link};<139>$short<299>}】}; #cat buttons {$button}; }; prompt.Set {{walk}{$buttons}}; }; #func {xiaoyao.Locate} { #local room {$gMapRoom[name]}; #local area {@map.GetArea{}}; #local node {$gMapRoom[node]}; #local dock {$gMapRoom[dock]}; #local pattern {$area的$room}; #nop 光看名字就长得不像,那肯定不是了。; #local nodes {$map.xiaoyao.room[$room]}; #if { "$nodes" == "" } { #return {}; }; #if { "$area" == "" } { #local pattern {%*的$room}; }; #if { "$node" != "" || @slist.Contains{{$gMapRoom[lookable]};{}} } { #nop 节点以 walk 节点名称标记; #local pattern {@default{$node;%*}($pattern)}; }; #elseif { "$dock" != "" } { #nop 没有节点的码头以区域名称加码头标记。; #local pattern {@default{$area;%*}码头($pattern)}; }; #elseif { "$area" != "" && "@sset.Intersection{{$gMapRoom[mark]};{★;☆}}" != "" } { #nop 既不是节点,又不是码头,那么如果有地域信息大概也是可以的。; #local pattern {%*($pattern)}; }; #else { #return {}; }; #if { "$pattern" != "%*\%*%*" } { #return {$pattern}; }; #nop 否则参考数据库来确定,当且仅当数据库中只有一条匹配记录时,才能断定; #if { &map.xiaoyao.map[$pattern][] != 1 } { #return {}; }; #local location @table.Keys{map.xiaoyao.map;{$pattern}}; #nop 如果房间名和节点名已经获得,那么可以据此更新地区名; #if { "$area" == "" && "$node" != "" } { #local area {$location}; #replace area {%*(%*的%*)} {&2}; #var gMapRoom[area][RESOLVED] {$area}; event.Emit map/GotArea; }; #return {$location}; }; #nop 计算路径; #func {xiaoyao.findPath} { #local src {%1}; #local cond {%2}; #local dst {}; #replace cond {NODE} {\$node}; #replace cond {LINK} {\$map.xiaoyao.map[\$node]}; #local routeMap { {$src}{START} }; #local checkList {{1}{$src}}; #while {1} { #if { &checkList[] == 0 || ( "$dst" != "" && "$routeMap[$dst]" != "" ) } { #break; }; #nop 遍历所有新发现的节点; #local nodes {$checkList}; #local checkList {}; #local node {}; #foreach {$nodes[]} {node} { #local next {}; #local c {}; #line sub {var;functions;escapes} #format c {%s} {$cond}; #if { $c } { #nop 满足条件的节点。; #local dst {$node}; #break; }; #foreach {*map.xiaoyao.map[$node][]} {next} { #if { "$routeMap[$next]" != "" } { #nop 已经检索过的节点。; #continue; }; #local link {$map.xiaoyao.map[$node][$next]}; #if { "$link" == "TODO" } { #nop BUG: 不完整的连接。; #continue; }; #list {checkList} {add} {$next}; #local routeMap[$next] {$node}; }; }; }; #if { "$dst" == "" || "$routeMap[$dst]" == "" } { #return {}; }; #local route {$dst}; #local path {}; #local node {$dst}; #while { "$node" != "$src" } { #local prev {$routeMap[$node]}; #local link {$map.xiaoyao.map[$prev][$node]}; #local node {$prev}; #format route {%s-%s} {$node} {$route}; #list path insert 1 {$link}; }; #list path {simplify}; #return { {room}{$dst} {route}{$route} {path}{$path} }; }; #func {xiaoyao.locateByName} { #local name {%1}; #if { "$name" == "" } { #return {}; }; #if { &map.xiaoyao.map[=$name] > 0 } { #return {$name}; }; #local nodes {@table.Keys{map.xiaoyao.map;%*$name%*}}; #local best {}; #local better {}; #local normal {}; #local node {}; #foreach {$nodes} {node} { #if { "$node" == "$name(%*)" } { #return {$node}; }; #if { "$node" == "%*(%*的$name)" && "$node" != "%*{津|渡|渡口})" } { #local best {$node}; }; #elseif { "$node" == "%*($name的%*)" && "$node" != "%*{津|渡|渡口})" } { #local better {$node}; }; #elseif { "$node" == "%*(%*$name%*)" } { #local normal {$node}; }; }; #if { "$best" != "" } { #return {$best}; }; #elseif { "$better" != "" } { #return {$better}; }; #else { #return {$normal}; }; }; ///=== { // ## xiaoyao.Query <出发节点> <目的节点> // 计算逍遥行路径。 // 出发节点和目的节点都支持模糊查询。 // // 本别名也可简写为 <139>xyq<299>。 // }; #alias {xiaoyao.Query} { #local begin {@str.Format{%U}}; #local origin {@xiaoyao.locateByName{%1}}; #local target {@xiaoyao.locateByName{%2}}; #if { "$origin" == "" || "$target" == "" } { xtt.Usage xiaoyao.Query {<169>这里是 PaoTin++ 逍遥行路径查询工具}; #return; }; infoLog 计算从<129>$origin<299>到<139>$target<299>的路径。; #local target {@xiaoyao.findPath{$origin;"NODE" == "%*$target%*"}}; #if { "$target" == "" } { errLog 找不到路径。; #return; }; #if { "$target[path]" == "" } { okLog 你已经来到了 $target[room]; #return; }; #local end {@str.Format{%U}}; #local elapsed {@math.Eval{($end * 1.000 - $begin * 1.000) / 1000.000}}; okLog 计算结果: {$target[path]}; #replace {target[route]} {(%*)} {}; okLog 途经节点: $target[route]; infoLog 计算耗时: $elapsed 毫秒。; infoLog; infoLog PaoTin++ 用户使用 <120>xy %2<299> 即可完成行走,支持自动坐船过河。; infoLog 下载地址: <488><149>https://pkuxkx.net/wiki/tools/paotin<299>; infoLog; }; #alias {xyq} {xiaoyao.Query}; ///=== { // ## xiaoyao.LoadData // 加载逍遥行依赖的数据文件。 // }; #alias {xiaoyao.LoadData} { storage.Load {map-xiaoyao} {map.xiaoyao.map;map.xiaoyao.room}; storage.Load {map-area} {map.area.dict}; };