-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
主要变化如下: * 重构代码结构,拆分 Lua/MUD/UI 三部分代码,调整目录结构以支持 go get * 开始支持通过配置文件和命令行选项的方式来设置程序参数 * Lua 可选启动 * 智能渲染表格线,也可通过配置文件指定,使得不同环境下的表格始终能够对齐 * 基本能够正确渲染颜色,但仍有不足 * 实现了一个 IAC 处理的基本框架 已知问题: * 由于 tview.TextView 和 tview.ANSIWriter 在颜色处理方面的 BUG 因此本项目直接集成了这两个模块的修正版 * 由于 tview.TextView 的性能问题,在屏幕内容超过一万行时,有明显的卡顿 以上修改应当可以: Close #1 #2 #3 #16 #17 #18
- Loading branch information
Showing
10 changed files
with
2,602 additions
and
488 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,35 @@ | ||
default: all | ||
|
||
ALL=go-mud-linux-amd64 go-mud-linux-arm64 go-mud-macOS-amd64 go-mud-windows-amd64.exe | ||
all: $(ALL) | ||
ALL=go-mud-macOS-amd64 \ | ||
go-mud-linux-amd64 \ | ||
go-mud-linux-arm8 \ | ||
go-mud-linux-arm7 \ | ||
go-mud-windows-x86.exe \ | ||
go-mud-windows-amd64.exe | ||
|
||
all: $(patsubst %,dist/%,$(ALL)) | ||
|
||
GOOPTS=-trimpath | ||
|
||
SRCS=main.go | ||
|
||
go-mud-linux-amd64: $(SRC) | ||
GOOS=linux GOARCH=amd64 go build -o $@ main.go | ||
dist/go-mud-linux-amd64: $(SRC) | ||
GOOS=linux GOARCH=amd64 go build $(GOOPTS) -o $@ main.go | ||
|
||
dist/go-mud-linux-arm7: $(SRC) | ||
GOOS=linux GOARM=7 GOARCH=arm go build $(GOOPTS) -o $@ main.go | ||
|
||
go-mud-linux-arm64: $(SRC) | ||
GOOS=linux GOARCH=arm64 go build -o $@ main.go | ||
dist/go-mud-linux-arm8: $(SRC) | ||
GOOS=linux GOARCH=arm64 go build $(GOOPTS) -o $@ main.go | ||
|
||
go-mud-macOS-amd64: $(SRC) | ||
GOOS=darwin GOARCH=amd64 go build -o $@ main.go | ||
dist/go-mud-macOS-amd64: $(SRC) | ||
GOOS=darwin GOARCH=amd64 go build $(GOOPTS) -o $@ main.go | ||
|
||
go-mud-windows-amd64.exe: $(SRC) | ||
GOOS=windows GOARCH=amd64 go build -o $@ main.go | ||
dist/go-mud-windows-amd64.exe: $(SRC) | ||
GOOS=windows GOARCH=amd64 go build $(GOOPTS) -o $@ main.go | ||
|
||
zip: all | ||
zip go-mud.zip go-mud-{linux,macOS,windows}-* *.lua | ||
dist/go-mud-windows-x86.exe: $(SRC) | ||
GOOS=windows GOARCH=386 go build $(GOOPTS) -o $@ main.go | ||
|
||
clean: | ||
rm -f $(ALL) | ||
rm -rf dist/ |
Submodule lua
updated
from af4ede to 04aed5
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,337 @@ | ||
package lua | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"io" | ||
"log" | ||
"os" | ||
"path" | ||
"regexp" | ||
"sync" | ||
"time" | ||
|
||
"github.com/flw-cn/printer" | ||
lua "github.com/yuin/gopher-lua" | ||
) | ||
|
||
type LuaRobotConfig struct { | ||
Enable bool `flag:"|true|是否加载 Lua 机器人"` | ||
Path string `flag:"p|lua|Lua 插件路径 {path}"` | ||
} | ||
|
||
type LuaRobot struct { | ||
printer.SimplePrinter | ||
|
||
config LuaRobotConfig | ||
|
||
screen io.Writer | ||
mud io.Writer | ||
|
||
lstate *lua.LState | ||
onReceive lua.P | ||
onSend lua.P | ||
|
||
timer sync.Map | ||
} | ||
|
||
func NewLuaRobot(config LuaRobotConfig) *LuaRobot { | ||
return &LuaRobot{ | ||
config: config, | ||
screen: os.Stdout, | ||
} | ||
} | ||
|
||
func (l *LuaRobot) Init() { | ||
if !l.config.Enable { | ||
return | ||
} | ||
|
||
if err := l.Reload(); err != nil { | ||
l.Println("Lua 初始化失败。") | ||
return | ||
} | ||
} | ||
|
||
func (l *LuaRobot) SetScreen(w io.Writer) { | ||
l.screen = w | ||
l.SetOutput(w) | ||
} | ||
|
||
func (l *LuaRobot) SetMud(w io.Writer) { | ||
l.mud = w | ||
} | ||
|
||
func (l *LuaRobot) Reload() error { | ||
mainFile := path.Join(l.config.Path, "main.lua") | ||
if _, err := os.Open(mainFile); err != nil { | ||
l.Printf("Load error: %s\n", err) | ||
l.Println("无法打开 lua 主程序,请检查你的配置。") | ||
return err | ||
} | ||
|
||
if l.lstate != nil { | ||
l.lstate.Close() | ||
l.Println("Lua 环境已关闭。") | ||
} | ||
|
||
l.Println("初始化 Lua 环境...") | ||
|
||
luaPath := path.Join(l.config.Path, "?.lua") | ||
os.Setenv(lua.LuaPath, luaPath+";;") | ||
|
||
l.lstate = lua.NewState() | ||
|
||
l.lstate.SetGlobal("RegEx", l.lstate.NewFunction(l.RegEx)) | ||
l.lstate.SetGlobal("Echo", l.lstate.NewFunction(l.Echo)) | ||
l.lstate.SetGlobal("Print", l.lstate.NewFunction(l.Print)) | ||
l.lstate.SetGlobal("Run", l.lstate.NewFunction(l.Run)) | ||
l.lstate.SetGlobal("Send", l.lstate.NewFunction(l.Send)) | ||
l.lstate.SetGlobal("AddTimer", l.lstate.NewFunction(l.AddTimer)) | ||
l.lstate.SetGlobal("AddMSTimer", l.lstate.NewFunction(l.AddTimer)) | ||
l.lstate.SetGlobal("DelTimer", l.lstate.NewFunction(l.DelTimer)) | ||
l.lstate.SetGlobal("DelMSTimer", l.lstate.NewFunction(l.DelTimer)) | ||
|
||
l.lstate.Panic = func(*lua.LState) { | ||
l.Panic(errors.New("LUA Panic")) | ||
return | ||
} | ||
|
||
if err := l.lstate.DoFile(mainFile); err != nil { | ||
l.lstate.Close() | ||
l.lstate = nil | ||
return err | ||
} | ||
|
||
l.onReceive = lua.P{ | ||
Fn: l.lstate.GetGlobal("OnReceive"), | ||
NRet: 0, | ||
Protect: true, | ||
} | ||
|
||
l.onSend = lua.P{ | ||
Fn: l.lstate.GetGlobal("OnSend"), | ||
NRet: 1, | ||
Protect: true, | ||
} | ||
|
||
l.Println("Lua 环境初始化完成。") | ||
|
||
return nil | ||
} | ||
|
||
func (l *LuaRobot) OnReceive(raw, input string) { | ||
if l.lstate == nil { | ||
return | ||
} | ||
|
||
L := l.lstate | ||
err := L.CallByParam(l.onReceive, lua.LString(raw), lua.LString(input)) | ||
if err != nil { | ||
l.Panic(err) | ||
} | ||
} | ||
|
||
func (l *LuaRobot) OnSend(cmd string) bool { | ||
if l.lstate == nil { | ||
return true | ||
} | ||
|
||
L := l.lstate | ||
err := L.CallByParam(l.onSend, lua.LString(cmd)) | ||
if err != nil { | ||
l.Panic(err) | ||
} | ||
|
||
ret := L.Get(-1) | ||
L.Pop(1) | ||
|
||
if ret == lua.LFalse { | ||
return false | ||
} else { | ||
return true | ||
} | ||
} | ||
|
||
func (l *LuaRobot) Panic(err error) { | ||
l.Printf("Lua error: [%v]\n", err) | ||
} | ||
|
||
func (l *LuaRobot) RegEx(L *lua.LState) int { | ||
text := L.ToString(1) | ||
regex := L.ToString(2) | ||
|
||
re, err := regexp.Compile(regex) | ||
if err != nil { | ||
L.Push(lua.LString("0")) | ||
return 1 | ||
} | ||
|
||
matchs := re.FindAllStringSubmatch(text, -1) | ||
if matchs == nil { | ||
L.Push(lua.LString("0")) | ||
return 1 | ||
} | ||
|
||
subs := matchs[0] | ||
length := len(subs) | ||
if length == 1 { | ||
L.Push(lua.LString("-1")) | ||
return 1 | ||
} | ||
|
||
L.Push(lua.LString(fmt.Sprintf("%d", length-1))) | ||
|
||
for i := 1; i < length; i++ { | ||
L.Push(lua.LString(subs[i])) | ||
} | ||
|
||
return length | ||
} | ||
|
||
func (l *LuaRobot) Print(L *lua.LState) int { | ||
text := L.ToString(1) | ||
l.Println(text) | ||
return 0 | ||
} | ||
|
||
func (l *LuaRobot) Echo(L *lua.LState) int { | ||
text := L.ToString(1) | ||
|
||
re := regexp.MustCompile(`\$(BLK|NOR|RED|HIR|GRN|HIG|YEL|HIY|BLU|HIB|MAG|HIM|CYN|HIC|WHT|HIW|BNK|REV|U)\$`) | ||
text = re.ReplaceAllStringFunc(text, func(code string) string { | ||
switch code { | ||
case "$BLK$": | ||
return "[black::]" | ||
case "$NOR$": | ||
return "[-:-:-]" | ||
case "$RED$": | ||
return "[red::]" | ||
case "$HIR$": | ||
return "[red::b]" | ||
case "$GRN$": | ||
return "[green::]" | ||
case "$HIG$": | ||
return "[green::b]" | ||
case "$YEL$": | ||
return "[yellow::]" | ||
case "$HIY$": | ||
return "[yellow::b]" | ||
case "$BLU$": | ||
return "[blue::]" | ||
case "$HIB$": | ||
return "[blue::b]" | ||
case "$MAG$": | ||
return "[darkmagenta::]" | ||
case "$HIM$": | ||
return "[#ff00ff::]" | ||
case "$CYN$": | ||
return "[dardcyan::]" | ||
case "$HIC$": | ||
return "[#00ffff::]" | ||
case "$WHT$": | ||
return "[white::]" | ||
case "$HIW$": | ||
return "[#ffffff::]" | ||
case "$BNK$": | ||
return "[::l]" | ||
case "$REV$": | ||
return "[::7]" | ||
case "$U$": | ||
return "[::u]" | ||
default: | ||
l.Printf("Find Unknown Color Code: %s\n", code) | ||
} | ||
return "" | ||
}) | ||
|
||
l.Println(text) | ||
|
||
// TODO: 这里暂时不支持 ANSI 到 PLAIN 的转换 | ||
l.OnReceive(text, text) | ||
|
||
return 0 | ||
} | ||
|
||
func (l *LuaRobot) Run(L *lua.LState) int { | ||
text := L.ToString(1) | ||
fmt.Fprintln(l.screen, text) | ||
return 0 | ||
} | ||
|
||
func (l *LuaRobot) Send(L *lua.LState) int { | ||
text := L.ToString(1) | ||
fmt.Fprintln(l.mud, text) | ||
return 0 | ||
} | ||
|
||
func (l *LuaRobot) AddTimer(L *lua.LState) int { | ||
id := L.ToString(1) | ||
code := L.ToString(2) | ||
delay := L.ToInt(3) | ||
times := L.ToInt(4) | ||
|
||
go func() { | ||
count := 0 | ||
quit := make(chan bool, 1) | ||
timer := Timer{ | ||
id: id, | ||
code: code, | ||
delay: delay, | ||
maxTimes: times, | ||
times: 0, | ||
quit: quit, | ||
} | ||
v, exists := l.timer.LoadOrStore(id, timer) | ||
if exists { | ||
v.(Timer).quit <- true | ||
l.timer.Store(id, timer) | ||
} | ||
|
||
for { | ||
select { | ||
case <-quit: | ||
return | ||
case <-time.After(time.Millisecond * time.Duration(delay)): | ||
timer.Emit(l) | ||
count++ | ||
if times > 0 && times >= count { | ||
return | ||
} | ||
} | ||
} | ||
}() | ||
|
||
return 0 | ||
} | ||
|
||
func (l *LuaRobot) DelTimer(L *lua.LState) int { | ||
id := L.ToString(1) | ||
v, ok := l.timer.Load(id) | ||
if ok { | ||
v.(Timer).quit <- true | ||
} | ||
l.timer.Delete(id) | ||
return 0 | ||
} | ||
|
||
func (l *LuaRobot) Logf(format string, a ...interface{}) { | ||
log.Printf(format, a...) | ||
return | ||
} | ||
|
||
type Timer struct { | ||
id string | ||
code string | ||
delay int | ||
maxTimes int | ||
times int | ||
quit chan<- bool | ||
} | ||
|
||
func (t *Timer) Emit(l *LuaRobot) { | ||
err := l.lstate.DoString(`call_timer_actions("` + t.id + `")`) | ||
if err != nil { | ||
l.Printf("Lua Error: %s\n", err) | ||
} | ||
} |
Oops, something went wrong.