Skip to content

Commit

Permalink
Feat: v0.6.0,一个基本可用的版本
Browse files Browse the repository at this point in the history
主要变化如下:

* 重构代码结构,拆分 Lua/MUD/UI 三部分代码,调整目录结构以支持 go get
* 开始支持通过配置文件和命令行选项的方式来设置程序参数
* Lua 可选启动
* 智能渲染表格线,也可通过配置文件指定,使得不同环境下的表格始终能够对齐
* 基本能够正确渲染颜色,但仍有不足
* 实现了一个 IAC 处理的基本框架

已知问题:

* 由于 tview.TextView 和 tview.ANSIWriter 在颜色处理方面的 BUG
  因此本项目直接集成了这两个模块的修正版
* 由于 tview.TextView 的性能问题,在屏幕内容超过一万行时,有明显的卡顿

以上修改应当可以:

Close #1 #2 #3 #16 #17 #18
  • Loading branch information
dzpao committed Sep 6, 2019
1 parent abbd676 commit 13a73ac
Show file tree
Hide file tree
Showing 10 changed files with 2,602 additions and 488 deletions.
37 changes: 24 additions & 13 deletions Makefile
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/
2 changes: 1 addition & 1 deletion lua
Submodule lua updated from af4ede to 04aed5
337 changes: 337 additions & 0 deletions lua-api/lua.go
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)
}
}
Loading

0 comments on commit 13a73ac

Please sign in to comment.