diff --git a/README.md b/README.md
index 1c788dccb3..f015babcf2 100644
--- a/README.md
+++ b/README.md
@@ -959,6 +959,23 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 吟唱提示[xxxx]
+
+
+ 钓鱼模拟器
+
+ `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/mcfish"`
+
+ - [x] 钓鱼商店
+ - [x] 购买xxx [数量]
+ - [x] 出售xxx [数量]
+ - [x] 钓鱼背包
+ - [x] 装备[xx竿|三叉戟|美西螈]
+ - [x] 附魔[诱钓|海之眷顾]
+ - [x] 修复鱼竿
+ - [x] 合成[xx竿|三叉戟]
+ - [x] 进行钓鱼
+ - [x] 进行n次钓鱼
+
简易midi音乐制作
diff --git a/plugin/mcfish/fish.go b/plugin/mcfish/fish.go
new file mode 100644
index 0000000000..4045b3eea6
--- /dev/null
+++ b/plugin/mcfish/fish.go
@@ -0,0 +1,329 @@
+// Package mcfish 钓鱼模拟器
+package mcfish
+
+import (
+ "math/rand"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/FloatTech/AnimeAPI/wallet"
+ "github.com/FloatTech/zbputils/ctxext"
+ "github.com/sirupsen/logrus"
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+)
+
+func init() {
+ engine.OnRegex(`^进行(([1-5]\d|[1-9])次)?钓鱼$`, getdb).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
+ uid := ctx.Event.UserID
+ fishNumber := 1
+ info := ctx.State["regex_matched"].([]string)[2]
+ if info != "" {
+ number, err := strconv.Atoi(info)
+ if err != nil || number > FishLimit {
+ ctx.SendChain(message.Text("请输入正确的次数"))
+ return
+ }
+ fishNumber = number
+ }
+ equipInfo, err := dbdata.getUserEquip(uid)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at fish.go.2]:", err))
+ return
+ }
+ if equipInfo == (equip{}) {
+ ok, err := dbdata.checkEquipFor(uid)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at fish.go.2.1]:", err))
+ return
+ }
+ if !ok {
+ ctx.SendChain(message.At(uid), message.Text("请装备鱼竿后钓鱼", err))
+ return
+ }
+ ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("你尚未装备鱼竿,是否花费100购买鱼竿?\n回答\"是\"或\"否\""))
+ // 等待用户下一步选择
+ recv, cancel := zero.NewFutureEvent("message", 999, false, zero.RegexRule(`^(是|否)$`), zero.CheckUser(ctx.Event.UserID)).Repeat()
+ defer cancel()
+ buy := false
+ for {
+ select {
+ case <-time.After(time.Second * 120):
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("等待超时,取消钓鱼")))
+ return
+ case e := <-recv:
+ nextcmd := e.Event.Message.String()
+ if nextcmd == "否" {
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("已取消购买")))
+ return
+ }
+ money := wallet.GetWalletOf(uid)
+ if money < 100 {
+ ctx.SendChain(message.Text("你钱包当前只有", money, "ATRI币,无法完成支付"))
+ return
+ }
+ err = wallet.InsertWalletOf(uid, -100)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at fish.go.3]:", err))
+ return
+ }
+ equipInfo = equip{
+ ID: uid,
+ Equip: "木竿",
+ Durable: 30,
+ }
+ err = dbdata.updateUserEquip(equipInfo)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at fish.go.4]:", err))
+ return
+ }
+ err = dbdata.setEquipFor(uid)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at fish.go.4]:", err))
+ return
+ }
+ buy = true
+ }
+ if buy {
+ break
+ }
+ }
+ }
+ if equipInfo.Durable < fishNumber {
+ fishNumber = equipInfo.Durable
+ }
+ residue, err := dbdata.updateFishInfo(uid, fishNumber)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at fish.go.1]:", err))
+ return
+ }
+ if residue == 0 {
+ ctx.SendChain(message.Text("今天你已经进行", FishLimit, "次钓鱼了.\n游戏虽好,但请不要沉迷。"))
+ return
+ }
+ fishNumber = residue
+ msg := ""
+ if equipInfo.Equip != "美西螈" {
+ equipInfo.Durable -= fishNumber
+ err = dbdata.updateUserEquip(equipInfo)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at fish.go.5]:", err))
+ return
+ }
+ if equipInfo.Durable < 10 || equipInfo.Durable > 0 {
+ msg = "(你的鱼竿耐久仅剩" + strconv.Itoa(equipInfo.Durable) + ")"
+ } else if equipInfo.Durable <= 0 {
+ msg = "(你的鱼竿耐已销毁)"
+ }
+ } else {
+ fishNmaes, err := dbdata.pickFishFor(uid, fishNumber)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at fish.go.5.1]:", err))
+ return
+ }
+ if len(fishNmaes) == 0 {
+ equipInfo.Durable = 0
+ err = dbdata.updateUserEquip(equipInfo)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at fish.go.5]:", err))
+ }
+ ctx.SendChain(message.Text("美西螈因为没吃到鱼,钓鱼时一直没回来,你失去了美西螈"))
+ return
+ }
+ msg = "(美西螈吃掉了"
+ fishNumber = 0
+ for name, number := range fishNmaes {
+ fishNumber += number
+ msg += strconv.Itoa(number) + name + "、"
+ }
+ msg += ")"
+ }
+ waitTime := 120 / (equipInfo.Induce + 1)
+ ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("你开始去钓鱼了,请耐心等待鱼上钩(预计要", time.Second*time.Duration(waitTime), ")"))
+ timer := time.NewTimer(time.Second * time.Duration(rand.Intn(waitTime)+1))
+ for {
+ <-timer.C
+ timer.Stop()
+ break
+ }
+ // 钓到鱼的范围
+ number, err := dbdata.getNumberFor(uid, "鱼")
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at fish.go.5.1]:", err))
+ return
+ }
+ if number > 100 || equipInfo.Equip == "美西螈" { //放大概率
+ probabilities["treasure"] = probabilityLimit{
+ Min: 0,
+ Max: 2,
+ }
+ probabilities["pole"] = probabilityLimit{
+ Min: 2,
+ Max: 10,
+ }
+ probabilities["fish"] = probabilityLimit{
+ Min: 10,
+ Max: 45,
+ }
+ probabilities["waste"] = probabilityLimit{
+ Min: 45,
+ Max: 90,
+ }
+ }
+ for name, info := range probabilities {
+ switch name {
+ case "treasure":
+ info.Max += equipInfo.Favor
+ probabilities[name] = info
+ case "pole":
+ info.Min += equipInfo.Favor
+ info.Max += equipInfo.Favor * 2
+ probabilities[name] = info
+ case "fish":
+ info.Min += equipInfo.Favor * 2
+ info.Max += equipInfo.Favor * 3
+ probabilities[name] = info
+ case "waste":
+ info.Min += equipInfo.Favor * 3
+ probabilities[name] = info
+ }
+ }
+ // 钓鱼结算
+ picName := ""
+ thingNameList := make(map[string]int)
+ for i := fishNumber; i > 0; i-- {
+ thingName := ""
+ typeOfThing := ""
+ number := 1
+ dice := rand.Intn(100)
+ switch {
+ case dice <= probabilities["waste"].Min && dice < probabilities["waste"].Max: // 垃圾
+ typeOfThing = "waste"
+ thingName = wasteList[rand.Intn(len(wasteList))]
+ picName = thingName
+ case dice <= probabilities["treasure"].Min && dice < probabilities["treasure"].Max: // 宝藏
+ dice = rand.Intn(100)
+ switch {
+ case dice <= probabilities["美西螈"].Min && dice < probabilities["美西螈"].Max:
+ typeOfThing = "pole"
+ picName = "美西螈"
+ thingName = "美西螈"
+ case dice <= probabilities["唱片"].Min && dice < probabilities["唱片"].Max:
+ typeOfThing = "article"
+ picName = "唱片"
+ thingName = "唱片"
+ case dice <= probabilities["海之眷顾"].Min && dice < probabilities["海之眷顾"].Max:
+ typeOfThing = "article"
+ picName = "book"
+ thingName = "海之眷顾"
+ default:
+ typeOfThing = "article"
+ picName = "book"
+ thingName = "诱钓"
+ }
+ case dice <= probabilities["pole"].Min && dice < probabilities["pole"].Max: // 宝藏
+ typeOfThing = "pole"
+ dice := rand.Intn(100)
+ switch {
+ case dice <= probabilities["铁竿"].Min && dice < probabilities["铁竿"].Max:
+ thingName = "铁竿"
+ case dice <= probabilities["金竿"].Min && dice < probabilities["金竿"].Max:
+ thingName = "金竿"
+ case dice <= probabilities["钻石竿"].Min && dice < probabilities["钻石竿"].Max:
+ thingName = "钻石竿"
+ case dice <= probabilities["下界合金竿竿竿"].Min && dice < probabilities["下界合金竿竿竿"].Max:
+ thingName = "下界合金竿竿竿"
+ default:
+ thingName = "木竿"
+ }
+ picName = thingName
+ case dice <= probabilities["fish"].Min && dice < probabilities["fish"].Max:
+ typeOfThing = "fish"
+ dice = rand.Intn(100)
+ switch {
+ case dice <= probabilities["墨鱼"].Min && dice < probabilities["墨鱼"].Max:
+ thingName = "墨鱼"
+ case dice <= probabilities["鳕鱼"].Min && dice < probabilities["鳕鱼"].Max:
+ thingName = "鳕鱼"
+ case dice <= probabilities["鲑鱼"].Min && dice < probabilities["鲑鱼"].Max:
+ thingName = "鲑鱼"
+ case dice <= probabilities["热带鱼"].Min && dice < probabilities["热带鱼"].Max:
+ thingName = "热带鱼"
+ case dice <= probabilities["河豚"].Min && dice < probabilities["河豚"].Max:
+ thingName = "河豚"
+ default:
+ thingName = "鹦鹉螺"
+ }
+ picName = thingName
+ default:
+ thingNameList["赛博空气"]++
+ }
+ if thingName != "" {
+ newThing := article{}
+ if strings.Contains(thingName, "竿") {
+ info := strconv.Itoa(rand.Intn(discountList[thingName])+1) +
+ "/" + strconv.Itoa(rand.Intn(10)) + "/" +
+ strconv.Itoa(rand.Intn(3)) + "/" + strconv.Itoa(rand.Intn(2))
+ newThing = article{
+ Duration: time.Now().Unix()*100 + int64(i),
+ Type: typeOfThing,
+ Name: thingName,
+ Number: number,
+ Other: info,
+ }
+ } else {
+ thingInfo, err := dbdata.getUserThingInfo(uid, thingName)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at fish.go.6]:", err))
+ return
+ }
+ if len(thingInfo) == 0 {
+ newThing = article{
+ Duration: time.Now().Unix()*100 + int64(i),
+ Type: typeOfThing,
+ Name: thingName,
+ }
+ } else {
+ newThing = thingInfo[0]
+ }
+ if equipInfo.Equip == "美西螈" && thingName != "美西螈" {
+ number += 2
+ }
+ newThing.Number += number
+ }
+ err = dbdata.updateUserThingInfo(uid, newThing)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at fish.go.7]:", err))
+ return
+ }
+ thingNameList[thingName] += number
+ }
+ }
+ if len(thingNameList) == 1 {
+ thingName := ""
+ for name := range thingNameList {
+ thingName = name
+ }
+ if picName != "" {
+ pic, err := engine.GetLazyData(picName+".png", false)
+ if err != nil {
+ logrus.Warnln("[mcfish]error:", err)
+ ctx.SendChain(message.At(uid), message.Text("恭喜你钓到了", thingName, "\n", msg))
+ return
+ }
+ ctx.SendChain(message.At(uid), message.Text("恭喜你钓到了", thingName, "\n", msg), message.ImageBytes(pic))
+ return
+ }
+ ctx.SendChain(message.At(uid), message.Text("恭喜你钓到了", thingName, "\n", msg))
+ return
+ }
+ msgInfo := make(message.Message, 0, 3+len(thingNameList))
+ msgInfo = append(msgInfo, message.Reply(ctx.Event.MessageID), message.Text("你进行了", fishNumber, "次钓鱼,结果如下:\n"))
+ for name, number := range thingNameList {
+ msgInfo = append(msgInfo, message.Text(name, " : ", number, "\n"))
+ }
+ msgInfo = append(msgInfo, message.Text(msg))
+ ctx.Send(msgInfo)
+ })
+}
diff --git a/plugin/mcfish/main.go b/plugin/mcfish/main.go
new file mode 100644
index 0000000000..726d892ecf
--- /dev/null
+++ b/plugin/mcfish/main.go
@@ -0,0 +1,586 @@
+// Package mcfish 钓鱼模拟器
+package mcfish
+
+import (
+ "encoding/json"
+ "math/rand"
+ "os"
+ "strconv"
+ "sync"
+ "time"
+
+ fcext "github.com/FloatTech/floatbox/ctxext"
+ sql "github.com/FloatTech/sqlite"
+ ctrl "github.com/FloatTech/zbpctrl"
+ "github.com/FloatTech/zbputils/control"
+ "github.com/FloatTech/zbputils/ctxext"
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+)
+
+type fishdb struct {
+ db *sql.Sqlite
+ sync.RWMutex
+}
+
+// FishLimit 钓鱼次数上限
+const FishLimit = 50
+
+// 各物品信息
+type jsonInfo struct {
+ ZoneInfo []zoneInfo `json:"分类"` // 区域概率
+ ArticleInfo []articleInfo `json:"物品"` // 物品信息
+}
+type zoneInfo struct {
+ Name string `json:"类型"` //类型
+ Probability int `json:"概率[0-100)"` // 概率
+}
+type articleInfo struct {
+ Name string `json:"名称"` //名称
+ Type string `json:"类型"` // 类型
+ Probability int `json:"概率[0-100),omitempty"` // 概率
+ Durable int `json:"耐久上限,omitempty"` // 耐久
+ Price int `json:"价格"` // 价格
+}
+
+type probabilityLimit struct {
+ Min int
+ Max int
+}
+
+type equip struct {
+ ID int64 // 用户
+ Equip string // 装备名称
+ Durable int // 耐久
+ Maintenance int // 维修次数
+ Induce int // 诱钓等级
+ Favor int // 眷顾等级
+}
+
+type article struct {
+ Duration int64
+ Name string
+ Number int
+ Other string // 耐久/维修次数/诱钓/眷顾
+ Type string
+}
+
+type store struct {
+ Duration int64
+ Name string
+ Number int
+ Price int
+ Other string // 耐久/维修次数/诱钓/眷顾
+ Type string
+}
+
+type fishState struct {
+ ID int64
+ Duration int64
+ Fish int
+ Equip int
+}
+
+type storeDiscount struct {
+ Name string
+ Discount int
+}
+
+var (
+ articlesInfo = jsonInfo{} // 物品信息
+ thingList = make([]string, 0, 100) // 竿列表
+ poleList = make([]string, 0, 10) // 竿列表
+ fishList = make([]string, 0, 10) // 鱼列表
+ treasureList = make([]string, 0, 10) // 鱼列表
+ wasteList = make([]string, 0, 10) // 垃圾列表
+ probabilities = make(map[string]probabilityLimit, 50) // 概率分布
+ priceList = make(map[string]int, 50) // 价格分布
+ durationList = make(map[string]int, 50) // 装备耐久分布
+ discountList = make(map[string]int, 50) // 价格波动信息
+ enchantLevel = []string{"0", "Ⅰ", "Ⅱ", "Ⅲ"}
+ dbdata = &fishdb{
+ db: &sql.Sqlite{},
+ }
+)
+
+var (
+ engine = control.Register("mcfish", &ctrl.Options[*zero.Ctx]{
+ DisableOnDefault: false,
+ Brief: "钓鱼",
+ Help: "一款钓鱼模拟器\n----------指令----------\n" +
+ "- 钓鱼看板/钓鱼商店\n- 购买xxx\n- 购买xxx [数量]\n- 出售xxx\n- 出售xxx [数量]\n" +
+ "- 钓鱼背包\n- 装备[xx竿|三叉戟|美西螈]\n- 附魔[诱钓|海之眷顾]\n- 修复鱼竿\n- 合成[xx竿|三叉戟]\n" +
+ "- 进行钓鱼\n- 进行n次钓鱼\n" +
+ "规则:\n1.每日的商店价格是波动的!!如何最大化收益自己考虑一下喔\n" +
+ "2.装备信息:\n-> 木竿 : 耐久上限:30 均价:100 上钩概率:0.7%\n-> 铁竿 : 耐久上限:50 均价:300 上钩概率:0.2%\n-> 金竿 : 耐久上限:70 均价700 上钩概率:0.06%\n" +
+ "-> 钻石竿 : 耐久上限:100 均价1500 上钩概率:0.03%\n-> 下界合金竿 : 耐久上限:150 均价3100 上钩概率:0.01%\n-> 三叉戟 : 可使钓的鱼类物品数量变成3 耐久上限:300 均价4000 只能合成和交易\n" +
+ "3.附魔书信息:\n-> 诱钓 : 减少上钩时间. 均价:1000, 上钩概率:0.59%\n-> 海之眷顾 : 增加宝藏上钩概率. 均价:2500, 上钩概率:0.39%\n" +
+ "4.稀有物品:\n-> 唱片 : 出售物品时使用该物品使价格翻倍. 均价:3000, 上钩概率:0.01%\n-> 美西螈 : 可装备,获得隐形[钓鱼佬]buff,并让钓到除鱼竿和美西螈外的物品数量变成3,无耐久上限.不可修复/附魔,每次钓鱼消耗任意一鱼类物品. 均价:3000, 上钩概率:0.01%\n" +
+ "5.鱼类信息:\n-> 鳕鱼 : 均价:10 上钩概率:0.69%\n-> 鲑鱼 : 均价:50 上钩概率:0.2%\n-> 热带鱼 : 均价:100 上钩概率:0.06%\n-> 河豚 : 均价:300 上钩概率:0.03%\n-> 鹦鹉螺 : 均价:500 上钩概率:0.01%\n-> 墨鱼 : 均价:500 上钩概率:0.01%\n" +
+ "6.垃圾:\n-> 均价:10 上钩概率:30%\n" +
+ "7.物品BUFF:\n-> 钓鱼佬 : 当背包名字含有'鱼'的物品数量超过100时激活,钓到物品概率提高至90%\n-> 修复大师 : 当背包鱼竿数量超过10时激活,修复物品时耐久百分百继承\n" +
+ "8.合成:\n-> 铁竿 : 3x木竿\n-> 金竿 : 3x铁竿\n-> 钻石竿 : 3x金竿\n-> 下界合金竿 : 3x钻石竿\n-> 三叉戟 : 3x下界合金竿\n注:合成成功率90%,继承附魔等级合/3的等级\n" +
+ "9.杂项:\n-> 无装备的情况下,每人最多可以购买3次100块钱的鱼竿\n-> 默认状态钓鱼上钩概率为60%(理论值!!!)\n-> 附魔的鱼竿会因附魔变得昂贵,每个附魔最高3级\n-> 三叉戟不算鱼竿",
+ PublicDataFolder: "McFish",
+ }).ApplySingle(ctxext.DefaultSingle)
+ getdb = fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
+ dbdata.db.DBPath = engine.DataFolder() + "fishdata.db"
+ err := dbdata.db.Open(time.Hour * 24)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at main.go.1]:", err))
+ return false
+ }
+ return true
+ })
+)
+
+func init() {
+ //go func() {
+ _, err := engine.GetLazyData("articlesInfo.json", false)
+ if err != nil {
+ panic(err)
+ }
+ reader, err := os.Open(engine.DataFolder() + "articlesInfo.json")
+ if err == nil {
+ err = json.NewDecoder(reader).Decode(&articlesInfo)
+ }
+ if err == nil {
+ err = reader.Close()
+ }
+ if err != nil {
+ panic(err)
+ }
+ probableList := make([]int, 4)
+ for _, info := range articlesInfo.ZoneInfo {
+ switch info.Name {
+ case "treasure":
+ probableList[0] = info.Probability
+ case "pole":
+ probableList[1] = info.Probability
+ case "fish":
+ probableList[2] = info.Probability
+ case "waste":
+ probableList[3] = info.Probability
+ }
+ }
+ probabilities["treasure"] = probabilityLimit{
+ Min: 0,
+ Max: probableList[0],
+ }
+ probabilities["pole"] = probabilityLimit{
+ Min: probableList[0],
+ Max: probableList[1],
+ }
+ probabilities["fish"] = probabilityLimit{
+ Min: probableList[1],
+ Max: probableList[2],
+ }
+ probabilities["waste"] = probabilityLimit{
+ Min: probableList[2],
+ Max: probableList[3],
+ }
+ min := make(map[string]int, 4)
+ for _, info := range articlesInfo.ArticleInfo {
+ switch {
+ case info.Type == "pole":
+ poleList = append(poleList, info.Name)
+ case info.Type == "fish":
+ fishList = append(fishList, info.Name)
+ case info.Type == "waste":
+ wasteList = append(wasteList, info.Name)
+ case info.Type == "treasure":
+ treasureList = append(treasureList, info.Name)
+ }
+ thingList = append(thingList, info.Name)
+ priceList[info.Name] = info.Price
+ if info.Durable != 0 {
+ durationList[info.Name] = info.Durable
+ }
+ probabilities[info.Name] = probabilityLimit{
+ Min: min[info.Type],
+ Max: min[info.Type] + info.Probability,
+ }
+ min[info.Type] += info.Probability
+ }
+ //}()
+}
+
+// 更新上限信息
+func (sql *fishdb) updateFishInfo(uid int64, number int) (residue int, err error) {
+ sql.Lock()
+ defer sql.Unlock()
+ userInfo := fishState{ID: uid}
+ err = sql.db.Create("fishState", &userInfo)
+ if err != nil {
+ return 0, err
+ }
+ _ = sql.db.Find("fishState", &userInfo, "where ID = "+strconv.FormatInt(uid, 10))
+ if time.Unix(userInfo.Duration, 0).Day() != time.Now().Day() {
+ userInfo.Fish = 0
+ userInfo.Duration = time.Now().Unix()
+ }
+ if userInfo.Fish >= FishLimit {
+ return 0, nil
+ }
+ residue = number
+ if userInfo.Fish+number > FishLimit {
+ residue = FishLimit - userInfo.Fish
+ number = residue
+ }
+ userInfo.Fish += number
+ err = sql.db.Insert("fishState", &userInfo)
+ return
+}
+
+/*********************************************************/
+/************************装备相关函数***********************/
+/*********************************************************/
+
+func (sql *fishdb) checkEquipFor(uid int64) (ok bool, err error) {
+ sql.Lock()
+ defer sql.Unlock()
+ userInfo := fishState{ID: uid}
+ err = sql.db.Create("fishState", &userInfo)
+ if err != nil {
+ return false, err
+ }
+ if !sql.db.CanFind("fishState", "where ID = "+strconv.FormatInt(uid, 10)) {
+ return true, nil
+ }
+ err = sql.db.Find("fishState", &userInfo, "where ID = "+strconv.FormatInt(uid, 10))
+ if err != nil {
+ return false, err
+ }
+ if userInfo.Equip > 3 {
+ return false, nil
+ }
+ return true, nil
+}
+
+func (sql *fishdb) setEquipFor(uid int64) (err error) {
+ sql.Lock()
+ defer sql.Unlock()
+ userInfo := fishState{ID: uid}
+ err = sql.db.Create("fishState", &userInfo)
+ if err != nil {
+ return err
+ }
+ _ = sql.db.Find("fishState", &userInfo, "where ID = "+strconv.FormatInt(uid, 10))
+ if err != nil {
+ return err
+ }
+ userInfo.Equip++
+ return sql.db.Insert("fishState", &userInfo)
+}
+
+// 获取装备信息
+func (sql *fishdb) getUserEquip(uid int64) (userInfo equip, err error) {
+ sql.Lock()
+ defer sql.Unlock()
+ err = sql.db.Create("equips", &userInfo)
+ if err != nil {
+ return
+ }
+ if !sql.db.CanFind("equips", "where ID = "+strconv.FormatInt(uid, 10)) {
+ return
+ }
+ err = sql.db.Find("equips", &userInfo, "where ID = "+strconv.FormatInt(uid, 10))
+ return
+}
+
+// 更新装备信息
+func (sql *fishdb) updateUserEquip(userInfo equip) (err error) {
+ sql.Lock()
+ defer sql.Unlock()
+ err = sql.db.Create("equips", &userInfo)
+ if err != nil {
+ return
+ }
+ if userInfo.Durable == 0 {
+ return sql.db.Del("equips", "where ID = "+strconv.FormatInt(userInfo.ID, 10))
+ }
+ return sql.db.Insert("equips", &userInfo)
+}
+
+func (sql *fishdb) pickFishFor(uid int64, number int) (fishNames map[string]int, err error) {
+ fishNames = make(map[string]int, 6)
+ name := strconv.FormatInt(uid, 10) + "Pack"
+ sql.Lock()
+ defer sql.Unlock()
+ userInfo := article{}
+ err = sql.db.Create(name, &userInfo)
+ if err != nil {
+ return
+ }
+ count, err := sql.db.Count(name)
+ if err != nil {
+ return
+ }
+ if count == 0 {
+ return
+ }
+ if !sql.db.CanFind(name, "where Type is 'fish'") {
+ return
+ }
+ fishTypes := make([]article, 0, count)
+ fishInfo := article{}
+ err = sql.db.FindFor(name, &fishInfo, "where Type is 'fish'", func() error {
+ fishTypes = append(fishTypes, fishInfo)
+ return nil
+ })
+ if err != nil {
+ return
+ }
+ if len(fishTypes) == 0 {
+ return
+ }
+ max := 0
+ for _, info := range fishTypes {
+ max += info.Number
+ }
+ if max < number {
+ number = max
+ }
+ for i := number; i > 0; i-- {
+ randNumber := rand.Intn(len(fishTypes))
+ fishTypes[randNumber].Number--
+ err = sql.db.Insert(name, &fishTypes[randNumber])
+ if err != nil {
+ return
+ }
+ fishNames[fishTypes[randNumber].Name]++
+ }
+ return
+}
+
+/*********************************************************/
+/************************背包相关函数***********************/
+/*********************************************************/
+
+// 获取用户背包信息
+func (sql *fishdb) getUserPack(uid int64) (thingInfos []article, err error) {
+ sql.Lock()
+ defer sql.Unlock()
+ userInfo := article{}
+ err = sql.db.Create(strconv.FormatInt(uid, 10)+"Pack", &userInfo)
+ if err != nil {
+ return
+ }
+ count, err := sql.db.Count(strconv.FormatInt(uid, 10) + "Pack")
+ if err != nil {
+ return
+ }
+ if count == 0 {
+ return
+ }
+ err = sql.db.FindFor(strconv.FormatInt(uid, 10)+"Pack", &userInfo, "ORDER by Type, Name, Other ASC", func() error {
+ thingInfos = append(thingInfos, userInfo)
+ return nil
+ })
+ return
+}
+
+// 获取用户物品信息
+func (sql *fishdb) getUserThingInfo(uid int64, thing string) (thingInfos []article, err error) {
+ name := strconv.FormatInt(uid, 10) + "Pack"
+ sql.Lock()
+ defer sql.Unlock()
+ userInfo := article{}
+ err = sql.db.Create(name, &userInfo)
+ if err != nil {
+ return
+ }
+ count, err := sql.db.Count(name)
+ if err != nil {
+ return
+ }
+ if count == 0 {
+ return
+ }
+ if !sql.db.CanFind(name, "where Name = '"+thing+"'") {
+ return
+ }
+ err = sql.db.FindFor(name, &userInfo, "where Name = '"+thing+"'", func() error {
+ thingInfos = append(thingInfos, userInfo)
+ return nil
+ })
+ return
+}
+
+// 更新用户物品信息
+func (sql *fishdb) updateUserThingInfo(uid int64, userInfo article) (err error) {
+ name := strconv.FormatInt(uid, 10) + "Pack"
+ sql.Lock()
+ defer sql.Unlock()
+ err = sql.db.Create(name, &userInfo)
+ if err != nil {
+ return
+ }
+ if userInfo.Number == 0 {
+ return sql.db.Del(name, "where Duration = "+strconv.FormatInt(userInfo.Duration, 10))
+ }
+ return sql.db.Insert(name, &userInfo)
+}
+
+// 获取某关键字的数量
+func (sql *fishdb) getNumberFor(uid int64, thing string) (number int, err error) {
+ name := strconv.FormatInt(uid, 10) + "Pack"
+ sql.Lock()
+ defer sql.Unlock()
+ userInfo := article{}
+ err = sql.db.Create(name, &userInfo)
+ if err != nil {
+ return
+ }
+ count, err := sql.db.Count(name)
+ if err != nil {
+ return
+ }
+ if count == 0 {
+ return
+ }
+ if !sql.db.CanFind(name, "where Name glob '*"+thing+"*'") {
+ return
+ }
+ info := article{}
+ err = sql.db.FindFor(name, &info, "where Name glob '*"+thing+"*'", func() error {
+ number += info.Number
+ return nil
+ })
+ return
+}
+
+/*********************************************************/
+/************************商店相关函数***********************/
+/*********************************************************/
+
+// 刷新商店信息
+func (sql *fishdb) refreshStroeInfo() (ok bool, err error) {
+ sql.Lock()
+ defer sql.Unlock()
+ err = sql.db.Create("stroeDiscount", &storeDiscount{})
+ if err != nil {
+ return false, err
+ }
+ lastTime := storeDiscount{}
+ _ = sql.db.Find("stroeDiscount", &lastTime, "where Name = 'lastTime'")
+ refresh := false
+ timeNow := time.Now().Day()
+ if timeNow != lastTime.Discount {
+ lastTime = storeDiscount{
+ Name: "lastTime",
+ Discount: timeNow,
+ }
+ err = sql.db.Insert("stroeDiscount", &lastTime)
+ if err != nil {
+ return false, err
+ }
+ refresh = true
+ }
+ for name := range priceList {
+ thing := storeDiscount{}
+ switch refresh {
+ case true:
+ thingDiscount := 50 + rand.Intn(150)
+ thing = storeDiscount{
+ Name: name,
+ Discount: thingDiscount,
+ }
+ err = sql.db.Insert("stroeDiscount", &thing)
+ if err != nil {
+ return
+ }
+ default:
+ _ = sql.db.Find("stroeDiscount", &thing, "where Name = '"+name+"'")
+ }
+ if thing.Discount != 0 {
+ discountList[name] = thing.Discount
+ } else {
+ discountList[name] = 100
+ }
+ }
+ if refresh { // 每天调控1种鱼
+ thingInfo := store{}
+ err = sql.db.Create("store", &thingInfo)
+ if err != nil {
+ return
+ }
+ fish := fishList[rand.Intn(len(fishList))]
+ _ = sql.db.Find("store", &thingInfo, "where Name = '"+fish+"'")
+ if thingInfo == (store{}) {
+ thingInfo.Duration = time.Now().Unix()
+ thingInfo.Type = "fish"
+ thingInfo.Name = fish
+ thingInfo.Price = priceList[fish] * discountList[fish] / 100
+ }
+ thingInfo.Number += (100 - discountList[fish])
+ if thingInfo.Number < 1 {
+ thingInfo.Number = 1
+ }
+ _ = sql.db.Insert("store", &thingInfo)
+ }
+ return true, nil
+}
+
+// 获取商店信息
+func (sql *fishdb) getStoreInfo() (thingInfos []store, err error) {
+ sql.Lock()
+ defer sql.Unlock()
+ thingInfo := store{}
+ err = sql.db.Create("store", &thingInfo)
+ if err != nil {
+ return
+ }
+ count, err := sql.db.Count("store")
+ if err != nil {
+ return
+ }
+ if count == 0 {
+ return
+ }
+ err = sql.db.FindFor("store", &thingInfo, "ORDER by Type, Name, Price ASC", func() error {
+ thingInfos = append(thingInfos, thingInfo)
+ return nil
+ })
+ return
+}
+
+// 获取商店物品信息
+func (sql *fishdb) getStoreThingInfo(thing string) (thingInfos []store, err error) {
+ sql.Lock()
+ defer sql.Unlock()
+ thingInfo := store{}
+ err = sql.db.Create("store", &thingInfo)
+ if err != nil {
+ return
+ }
+ count, err := sql.db.Count("store")
+ if err != nil {
+ return
+ }
+ if count == 0 {
+ return
+ }
+ if !sql.db.CanFind("store", "where Name = '"+thing+"'") {
+ return
+ }
+ err = sql.db.FindFor("store", &thingInfo, "where Name = '"+thing+"'", func() error {
+ thingInfos = append(thingInfos, thingInfo)
+ return nil
+ })
+ return
+}
+
+// 更新商店信息
+func (sql *fishdb) updateStoreInfo(thingInfo store) (err error) {
+ sql.Lock()
+ defer sql.Unlock()
+ err = sql.db.Create("store", &thingInfo)
+ if err != nil {
+ return
+ }
+ if thingInfo.Number == 0 {
+ return sql.db.Del("store", "where Duration = "+strconv.FormatInt(thingInfo.Duration, 10))
+ }
+ return sql.db.Insert("store", &thingInfo)
+}
diff --git a/plugin/mcfish/pack.go b/plugin/mcfish/pack.go
new file mode 100644
index 0000000000..053d9a6dc6
--- /dev/null
+++ b/plugin/mcfish/pack.go
@@ -0,0 +1,454 @@
+// Package mcfish 钓鱼模拟器
+package mcfish
+
+import (
+ "bytes"
+ "errors"
+ "image"
+ "image/color"
+ "strconv"
+ "strings"
+ "sync"
+
+ "github.com/FloatTech/AnimeAPI/wallet"
+ "github.com/FloatTech/floatbox/file"
+ "github.com/FloatTech/floatbox/math"
+ "github.com/FloatTech/gg"
+ "github.com/FloatTech/imgfactory"
+ "github.com/FloatTech/zbputils/control"
+ "github.com/FloatTech/zbputils/ctxext"
+ "github.com/FloatTech/zbputils/img/text"
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+)
+
+func init() {
+ engine.OnFullMatch("钓鱼背包", getdb).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
+ uid := ctx.Event.UserID
+ equipInfo, err := dbdata.getUserEquip(uid)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pack.go.1]:", err))
+ return
+ }
+ articles, err := dbdata.getUserPack(uid)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pack.go.2]:", err))
+ return
+ }
+ pic, err := drawPackImage(uid, equipInfo, articles)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pack.go.3]:", err))
+ return
+ }
+ ctx.SendChain(message.ImageBytes(pic))
+ })
+ engine.OnFullMatch("当前装备概率明细", getdb).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
+ uid := ctx.Event.UserID
+ equipInfo, err := dbdata.getUserEquip(uid)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pack.go.1]:", err))
+ return
+ }
+ number, err := dbdata.getNumberFor(uid, "鱼")
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at fish.go.5.1]:", err))
+ return
+ }
+ msg := make(message.Message, 0, 20+len(thingList))
+ msg = append(msg, message.At(uid), message.Text("\n大类概率:\n"))
+ probableList := make([]int, 4)
+ for _, info := range articlesInfo.ZoneInfo {
+ switch info.Name {
+ case "treasure":
+ probableList[0] = info.Probability
+ case "pole":
+ probableList[1] = info.Probability
+ case "fish":
+ probableList[2] = info.Probability
+ case "waste":
+ probableList[3] = info.Probability
+ }
+ }
+ if number > 100 || equipInfo.Equip == "美西螈" { //放大概率
+ probableList = []int{2, 8, 35, 45}
+ }
+ if equipInfo.Favor > 0 {
+ probableList[0] += equipInfo.Favor
+ probableList[1] += equipInfo.Favor
+ probableList[2] += equipInfo.Favor
+ probableList[3] -= equipInfo.Favor * 3
+ }
+ probable := probableList[0]
+ msg = append(msg, message.Text("宝藏 : ", probableList[0], "%\n"))
+ probable += probableList[1]
+ msg = append(msg, message.Text("鱼竿 : ", probableList[1], "%\n"))
+ probable += probableList[2]
+ msg = append(msg, message.Text("鱼类 : ", probableList[2], "%\n"))
+ probable += probableList[3]
+ msg = append(msg, message.Text("垃圾 : ", probableList[3], "%\n"))
+ msg = append(msg, message.Text("合计 : ", probable, "%\n"))
+ msg = append(msg, message.Text("-----------\n宝藏概率:\n"))
+ for _, name := range treasureList {
+ msg = append(msg, message.Text(name, " : ",
+ strconv.FormatFloat(float64(probabilities[name].Max-probabilities[name].Min)*float64(probableList[0])/100, 'f', 2, 64),
+ "%\n"))
+ }
+ msg = append(msg, message.Text("-----------\n鱼竿概率:\n"))
+ for _, name := range poleList {
+ msg = append(msg, message.Text(name, " : ",
+ strconv.FormatFloat(float64(probabilities[name].Max-probabilities[name].Min)*float64(probableList[1])/100, 'f', 2, 64),
+ "%\n"))
+ }
+ msg = append(msg, message.Text("-----------\n鱼类概率:\n"))
+ for _, name := range fishList {
+ msg = append(msg, message.Text(name, " : ",
+ strconv.FormatFloat(float64(probabilities[name].Max-probabilities[name].Min)*float64(probableList[2])/100, 'f', 2, 64),
+ "%\n"))
+ }
+ msg = append(msg, message.Text("-----------"))
+ ctx.Send(msg)
+ })
+}
+
+func drawPackImage(uid int64, equipInfo equip, articles []article) (imagePicByte []byte, err error) {
+ fontdata, err := file.GetLazyData(text.BoldFontFile, control.Md5File, true)
+ if err != nil {
+ return nil, err
+ }
+ var (
+ wg sync.WaitGroup
+ equipBlock image.Image // 装备信息
+ packBlock image.Image // 背包信息
+ )
+ wg.Add(1)
+ // 绘制ID
+ go func() {
+ defer wg.Done()
+ if equipInfo == (equip{}) {
+ equipBlock, err = drawEquipEmptyBlock(fontdata)
+ } else {
+ equipBlock, err = drawEquipInfoBlock(equipInfo, fontdata)
+ }
+ if err != nil {
+ return
+ }
+ }()
+ wg.Add(1)
+ // 绘制基本信息
+ go func() {
+ defer wg.Done()
+ if len(articles) == 0 {
+ packBlock, err = drawArticleEmptyBlock(fontdata)
+ } else {
+ packBlock, err = drawArticleInfoBlock(uid, articles, fontdata)
+ }
+ if err != nil {
+ return
+ }
+ }()
+ wg.Wait()
+ if equipBlock == nil || packBlock == nil {
+ err = errors.New("生成图片失败,数据缺失")
+ return
+ }
+ // 计算图片高度
+ backDX := 1020
+ backDY := 10 + equipBlock.Bounds().Dy() + 10 + packBlock.Bounds().Dy() + 10
+ canvas := gg.NewContext(backDX, backDY)
+
+ // 画底色
+ canvas.DrawRectangle(0, 0, float64(backDX), float64(backDY))
+ canvas.SetRGBA255(150, 150, 150, 255)
+ canvas.Fill()
+ canvas.DrawRectangle(10, 10, float64(backDX-20), float64(backDY-20))
+ canvas.SetRGBA255(255, 255, 255, 255)
+ canvas.Fill()
+
+ canvas.DrawImage(equipBlock, 10, 10)
+ canvas.DrawImage(packBlock, 10, 10+equipBlock.Bounds().Dy()+10)
+
+ return imgfactory.ToBytes(canvas.Image())
+}
+
+// 绘制装备栏区域
+func drawEquipEmptyBlock(fontdata []byte) (image.Image, error) {
+ canvas := gg.NewContext(1000, 300)
+ // 画底色
+ canvas.DrawRectangle(0, 0, 1000, 300)
+ canvas.SetRGBA255(255, 255, 255, 150)
+ canvas.Fill()
+ // 边框框
+ canvas.DrawRectangle(0, 0, 1000, 300)
+ canvas.SetLineWidth(3)
+ canvas.SetRGBA255(0, 0, 0, 255)
+ canvas.Stroke()
+
+ canvas.SetColor(color.Black)
+ err := canvas.ParseFontFace(fontdata, 100)
+ if err != nil {
+ return nil, err
+ }
+ textW, textH := canvas.MeasureString("装备信息")
+ canvas.DrawString("装备信息", 10, 10+textH)
+ canvas.DrawLine(10, textH*1.2, textW, textH*1.2)
+ canvas.SetLineWidth(3)
+ canvas.SetRGBA255(0, 0, 0, 255)
+ canvas.Stroke()
+ if err = canvas.ParseFontFace(fontdata, 50); err != nil {
+ return nil, err
+ }
+ canvas.DrawString("没有装备任何鱼竿", 50, 10+textH*2+50)
+ return canvas.Image(), nil
+}
+func drawEquipInfoBlock(equipInfo equip, fontdata []byte) (image.Image, error) {
+ canvas := gg.NewContext(1, 1)
+ err := canvas.ParseFontFace(fontdata, 100)
+ if err != nil {
+ return nil, err
+ }
+ _, titleH := canvas.MeasureString("装备信息")
+ err = canvas.ParseFontFace(fontdata, 50)
+ if err != nil {
+ return nil, err
+ }
+ _, textH := canvas.MeasureString("装备信息")
+
+ backDY := math.Max(int(10+titleH*2+(textH*2)*4+10), 300)
+
+ canvas = gg.NewContext(1000, backDY)
+ // 画底色
+ canvas.DrawRectangle(0, 0, 1000, float64(backDY))
+ canvas.SetRGBA255(255, 255, 255, 150)
+ canvas.Fill()
+ // 边框框
+ canvas.DrawRectangle(0, 0, 1000, float64(backDY))
+ canvas.SetLineWidth(3)
+ canvas.SetRGBA255(0, 0, 0, 255)
+ canvas.Stroke()
+ getAvatar, err := engine.GetLazyData(equipInfo.Equip+".png", false)
+ if err != nil {
+ return nil, err
+ }
+ equipPic, _, err := image.Decode(bytes.NewReader(getAvatar))
+ if err != nil {
+ return nil, err
+ }
+ picDy := float64(backDY) - 10 - titleH*2
+ equipPic = imgfactory.Size(equipPic, int(picDy)-10, int(picDy)-10).Image()
+ canvas.DrawImage(equipPic, 10, 10+int(titleH)*2)
+
+ // 放字
+ canvas.SetColor(color.Black)
+ if err = canvas.ParseFontFace(fontdata, 100); err != nil {
+ return nil, err
+ }
+ titleW, titleH := canvas.MeasureString("装备信息")
+ canvas.DrawString("装备信息", 10, 10+titleH*1.2)
+ canvas.DrawLine(10, titleH*1.6, titleW, titleH*1.6)
+ canvas.SetLineWidth(3)
+ canvas.SetRGBA255(0, 0, 0, 255)
+ canvas.Stroke()
+
+ textDx := picDy + 10
+ textDy := 10 + titleH*2
+ if err = canvas.ParseFontFace(fontdata, 75); err != nil {
+ return nil, err
+ }
+ textW, textH := canvas.MeasureString(equipInfo.Equip)
+ canvas.DrawStringAnchored(equipInfo.Equip, textDx+textW/2, textDy+textH/2, 0.5, 0.5)
+
+ textDy += textH * 1.5
+ if err = canvas.ParseFontFace(fontdata, 50); err != nil {
+ return nil, err
+ }
+ textW, textH = canvas.MeasureString("维修次数")
+ durable := strconv.Itoa(equipInfo.Durable)
+ valueW, _ := canvas.MeasureString("100")
+ barW := 1000 - textDx - textW - 10 - valueW - 10
+
+ canvas.DrawStringAnchored("装备耐久", textDx+textW/2, textDy+textH/2, 0.5, 0.5)
+ canvas.DrawRectangle(textDx+textW+5, textDy, barW, textH*1.2)
+ canvas.SetRGB255(150, 150, 150)
+ canvas.Fill()
+ canvas.SetRGB255(0, 0, 0)
+ durableW := barW * float64(equipInfo.Durable) / float64(durationList[equipInfo.Equip])
+ canvas.DrawRectangle(textDx+textW+5, textDy, durableW, textH*1.2)
+ canvas.SetRGB255(102, 102, 102)
+ canvas.Fill()
+ canvas.SetColor(color.Black)
+ canvas.DrawStringAnchored(durable, textDx+textW+5+barW+5+valueW/2, textDy+textH/2, 0.5, 0.5)
+
+ textDy += textH * 2
+ maintenance := strconv.Itoa(equipInfo.Maintenance)
+ canvas.DrawStringAnchored("维修次数", textDx+textW/2, textDy+textH/2, 0.5, 0.5)
+ canvas.DrawRectangle(textDx+textW+5, textDy, barW, textH*1.2)
+ canvas.SetRGB255(150, 150, 150)
+ canvas.Fill()
+ canvas.SetRGB255(0, 0, 0)
+ canvas.DrawRectangle(textDx+textW+5, textDy, barW*float64(equipInfo.Maintenance)/10, textH*1.2)
+ canvas.SetRGB255(102, 102, 102)
+ canvas.Fill()
+ canvas.SetColor(color.Black)
+ canvas.DrawStringAnchored(maintenance, textDx+textW+5+barW+5+valueW/2, textDy+textH/2, 0.5, 0.5)
+
+ textDy += textH * 3
+ canvas.DrawString(" 附魔: 诱钓"+enchantLevel[equipInfo.Induce]+" 海之眷顾"+enchantLevel[equipInfo.Favor], textDx, textDy)
+ return canvas.Image(), nil
+}
+
+// 绘制背包信息区域
+func drawArticleEmptyBlock(fontdata []byte) (image.Image, error) {
+ canvas := gg.NewContext(1000, 300)
+ // 画底色
+ canvas.DrawRectangle(0, 0, 1000, 300)
+ canvas.SetRGBA255(255, 255, 255, 150)
+ canvas.Fill()
+ // 边框框
+ canvas.DrawRectangle(0, 0, 1000, 300)
+ canvas.SetLineWidth(3)
+ canvas.SetRGBA255(0, 0, 0, 255)
+ canvas.Stroke()
+
+ canvas.SetColor(color.Black)
+ err := canvas.ParseFontFace(fontdata, 100)
+ if err != nil {
+ return nil, err
+ }
+ textW, textH := canvas.MeasureString("背包信息")
+ canvas.DrawString("背包信息", 10, 10+textH*1.2)
+ canvas.DrawLine(10, textH*1.6, textW, textH*1.6)
+ canvas.SetLineWidth(3)
+ canvas.SetRGBA255(0, 0, 0, 255)
+ canvas.Stroke()
+ if err = canvas.ParseFontFace(fontdata, 50); err != nil {
+ return nil, err
+ }
+ canvas.DrawStringAnchored("背包没有存放任何东西", 500, 10+textH*2+50, 0.5, 0)
+ return canvas.Image(), nil
+}
+func drawArticleInfoBlock(uid int64, articles []article, fontdata []byte) (image.Image, error) {
+ canvas := gg.NewContext(1, 1)
+ err := canvas.ParseFontFace(fontdata, 100)
+ if err != nil {
+ return nil, err
+ }
+ titleW, titleH := canvas.MeasureString("背包信息")
+ front := 45.0
+ err = canvas.ParseFontFace(fontdata, front)
+ if err != nil {
+ return nil, err
+ }
+ _, textH := canvas.MeasureString("高度")
+
+ nameWOfFiest := 0.0
+ nameWOfSecond := 0.0
+ for i, info := range articles {
+ textW, _ := canvas.MeasureString(info.Name + "(" + info.Other + ")")
+ if i%2 == 0 && textW > nameWOfFiest {
+ nameWOfFiest = textW
+ } else if textW > nameWOfSecond {
+ nameWOfSecond = textW
+ }
+ }
+ valueW, _ := canvas.MeasureString("10000")
+
+ if (10+nameWOfFiest+10+valueW+10)+(10+nameWOfSecond+10+valueW+10) > 980 {
+ front = 32.0
+ err = canvas.ParseFontFace(fontdata, front)
+ if err != nil {
+ return nil, err
+ }
+ _, textH = canvas.MeasureString("高度")
+
+ nameWOfFiest = 0
+ nameWOfSecond = 0
+ for i, info := range articles {
+ textW, _ := canvas.MeasureString(info.Name + "(" + info.Other + ")")
+ if i%2 == 0 && textW > nameWOfFiest {
+ nameWOfFiest = textW
+ } else if textW > nameWOfSecond {
+ nameWOfSecond = textW
+ }
+ }
+ valueW, _ = canvas.MeasureString("10000")
+ }
+ wallW := (980 - (10 + nameWOfFiest + 10 + valueW + 10) - (10 + nameWOfSecond + 10 + valueW + 10)) / 2
+ backY := math.Max(10+int(titleH*1.6)+10+int(textH*2)*(math.Ceil(len(articles), 2)+1), 500)
+ canvas = gg.NewContext(1000, backY)
+ // 画底色
+ canvas.DrawRectangle(0, 0, 1000, float64(backY))
+ canvas.SetRGBA255(255, 255, 255, 150)
+ canvas.Fill()
+ // 边框框
+ canvas.DrawRectangle(0, 0, 1000, float64(backY))
+ canvas.SetLineWidth(3)
+ canvas.SetRGBA255(0, 0, 0, 255)
+ canvas.Stroke()
+
+ // 放字
+ canvas.SetColor(color.Black)
+ err = canvas.ParseFontFace(fontdata, 100)
+ if err != nil {
+ return nil, err
+ }
+ canvas.DrawString("背包信息", 10, 10+titleH*1.2)
+ canvas.DrawLine(10, titleH*1.6, titleW, titleH*1.6)
+ canvas.SetLineWidth(3)
+ canvas.SetRGBA255(0, 0, 0, 255)
+ canvas.Stroke()
+
+ textDy := 10 + titleH*1.7
+ if err = canvas.ParseFontFace(fontdata, front); err != nil {
+ return nil, err
+ }
+ canvas.SetColor(color.Black)
+ numberOfFish := 0
+ numberOfEquip := 0
+ canvas.DrawStringAnchored("名称", wallW+20+nameWOfFiest/2, textDy+textH/2, 0.5, 0.5)
+ canvas.DrawStringAnchored("数量", wallW+20+nameWOfFiest+10+valueW/2, textDy+textH/2, 0.5, 0.5)
+ canvas.DrawStringAnchored("名称", wallW+20+nameWOfFiest+10+valueW+10+10+nameWOfSecond/2, textDy+textH/2, 0.5, 0.5)
+ canvas.DrawStringAnchored("数量", wallW+20+nameWOfFiest+10+valueW+10+10+nameWOfSecond+10+valueW/2, textDy+textH/2, 0.5, 0.5)
+ textDy += textH * 2
+ for i, info := range articles {
+ name := info.Name
+ if info.Other != "" {
+ if strings.Contains(info.Name, "竿") {
+ numberOfEquip++
+ }
+ name += "(" + info.Other + ")"
+ } else if strings.Contains(name, "鱼") {
+ numberOfFish += info.Number
+ }
+ valueStr := strconv.Itoa(info.Number)
+ if i%2 == 0 {
+ if i != 0 {
+ textDy += textH * 2
+ }
+ canvas.DrawStringAnchored(name, wallW+20+nameWOfFiest/2, textDy+textH/2, 0.5, 0.5)
+ canvas.DrawStringAnchored(valueStr, wallW+20+nameWOfFiest+10+valueW/2, textDy+textH/2, 0.5, 0.5)
+ } else {
+ canvas.DrawStringAnchored(name, wallW+20+nameWOfFiest+10+valueW+10+10+nameWOfSecond/2, textDy+textH/2, 0.5, 0.5)
+ canvas.DrawStringAnchored(valueStr, wallW+20+nameWOfFiest+10+valueW+10+10+nameWOfSecond+10+valueW/2, textDy+textH/2, 0.5, 0.5)
+ }
+ }
+ if err = canvas.ParseFontFace(fontdata, 30); err != nil {
+ return nil, err
+ }
+ textDy = 10
+ text := "钱包余额: " + strconv.Itoa(wallet.GetWalletOf(uid))
+ textW, textH := canvas.MeasureString(text)
+ w, _ := canvas.MeasureString("维修大师[已激活]")
+ if w > textW {
+ textW = w
+ }
+ canvas.DrawStringAnchored(text, 980-textW/2, textDy+textH/2, 0.5, 0.5)
+ textDy += textH * 1.5
+ if numberOfFish > 100 {
+ canvas.DrawStringAnchored("钓鱼佬[已激活]", 980-textW/2, textDy+textH/2, 0.5, 0.5)
+ textDy += textH * 1.5
+ }
+ if numberOfEquip > 10 {
+ canvas.DrawStringAnchored("维修大师[已激活]", 980-textW/2, textDy+textH/2, 0.5, 0.5)
+ }
+ return canvas.Image(), nil
+}
diff --git a/plugin/mcfish/pole.go b/plugin/mcfish/pole.go
new file mode 100644
index 0000000000..3cf1a5fad2
--- /dev/null
+++ b/plugin/mcfish/pole.go
@@ -0,0 +1,485 @@
+// Package mcfish 钓鱼模拟器
+package mcfish
+
+import (
+ "math/rand"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/FloatTech/zbputils/ctxext"
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+)
+
+func init() {
+ engine.OnRegex(`^装备(`+strings.Join(poleList, "|")+`)$`, getdb).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
+ uid := ctx.Event.UserID
+ equipInfo, err := dbdata.getUserEquip(uid)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pole.go.1]:", err))
+ return
+ }
+ thingName := ctx.State["regex_matched"].([]string)[1]
+ articles, err := dbdata.getUserThingInfo(uid, thingName)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pole.go.2]:", err))
+ return
+ }
+ if len(articles) == 0 {
+ ctx.SendChain(message.Text("你的背包不存在该物品"))
+ return
+ }
+ poles := make([]equip, 0, len(articles))
+ if thingName != "美西螈" {
+ for _, info := range articles {
+ poleInfo := strings.Split(info.Other, "/")
+ durable, _ := strconv.Atoi(poleInfo[0])
+ maintenance, _ := strconv.Atoi(poleInfo[1])
+ induceLevel, _ := strconv.Atoi(poleInfo[2])
+ favorLevel, _ := strconv.Atoi(poleInfo[3])
+ poles = append(poles, equip{
+ ID: uid,
+ Equip: info.Name,
+ Durable: durable,
+ Maintenance: maintenance,
+ Induce: induceLevel,
+ Favor: favorLevel,
+ })
+ }
+ } else {
+ poles = append(poles, equip{
+ ID: uid,
+ Equip: thingName,
+ Durable: 999,
+ })
+ }
+ check := false
+ index := 0
+ if len(poles) > 1 {
+ msg := make(message.Message, 0, 3+len(articles))
+ msg = append(msg, message.Reply(ctx.Event.MessageID), message.Text("找到以下鱼竿:\n"))
+ for i, info := range poles {
+ msg = append(msg, message.Text("[", i, "] ", info.Equip, " : 耐", info.Durable, "/修", info.Maintenance,
+ "/诱", enchantLevel[info.Induce], "/眷顾", enchantLevel[info.Favor], "\n"))
+ }
+ msg = append(msg, message.Text("————————\n输入对应序号进行装备,或回复“取消”取消"))
+ ctx.Send(msg)
+ // 等待用户下一步选择
+ recv, cancel := zero.NewFutureEvent("message", 999, false, zero.RegexRule(`^(取消|\d+)$`), zero.CheckUser(ctx.Event.UserID)).Repeat()
+ defer cancel()
+ for {
+ select {
+ case <-time.After(time.Second * 120):
+ ctx.Send(
+ message.ReplyWithMessage(ctx.Event.MessageID,
+ message.Text("等待超时,取消装备"),
+ ),
+ )
+ return
+ case e := <-recv:
+ nextcmd := e.Event.Message.String()
+ if nextcmd == "取消" {
+ ctx.Send(
+ message.ReplyWithMessage(ctx.Event.MessageID,
+ message.Text("已取消装备"),
+ ),
+ )
+ return
+ }
+ index, err = strconv.Atoi(nextcmd)
+ if err != nil || index > len(articles)-1 {
+ ctx.SendChain(message.At(ctx.Event.UserID), message.Text("请输入正确的序号"))
+ continue
+ }
+ check = true
+ }
+ if check {
+ break
+ }
+ }
+ }
+ newEquipInfo := poles[index]
+ packEquip := articles[index]
+ packEquip.Number--
+ err = dbdata.updateUserThingInfo(uid, packEquip)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pole.go.3]:", err))
+ return
+ }
+ err = dbdata.updateUserEquip(newEquipInfo)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pole.go.3.1]:", err))
+ return
+ }
+ oldthing := article{}
+ if equipInfo != (equip{}) && equipInfo.Equip != "美西螈" {
+ oldthing = article{
+ Duration: time.Now().Unix(),
+ Type: "pole",
+ Name: equipInfo.Equip,
+ Number: 1,
+ Other: strconv.Itoa(equipInfo.Durable) + "/" + strconv.Itoa(equipInfo.Maintenance) + "/" + strconv.Itoa(equipInfo.Induce) + "/" + strconv.Itoa(equipInfo.Favor),
+ }
+ } else if equipInfo.Equip == "美西螈" {
+ articles, err = dbdata.getUserThingInfo(uid, "美西螈")
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pole.go.3.2]:", err))
+ return
+ }
+ if len(articles) == 0 {
+ oldthing = article{
+ Duration: time.Now().Unix(),
+ Type: "pole",
+ Name: equipInfo.Equip,
+ Number: 1,
+ }
+ } else {
+ oldthing = articles[0]
+ oldthing.Number++
+ }
+ }
+ err = dbdata.updateUserThingInfo(uid, oldthing)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pole.go.4]:", err))
+ return
+ }
+ ctx.Send(
+ message.ReplyWithMessage(ctx.Event.MessageID,
+ message.Text("装备成功"),
+ ),
+ )
+ })
+ engine.OnFullMatchGroup([]string{"修复鱼竿", "维修鱼竿"}, getdb).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
+ uid := ctx.Event.UserID
+ equipInfo, err := dbdata.getUserEquip(uid)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pole.go.5]:", err))
+ return
+ }
+ if equipInfo.Equip == "" || equipInfo.Equip == "美西螈" || equipInfo.Equip == "三叉戟" {
+ ctx.SendChain(message.Text("仅能修复装备中的鱼竿"))
+ return
+ }
+ if equipInfo.Maintenance >= 10 {
+ ctx.SendChain(message.Text("装备的鱼竿已经达到修复上限"))
+ return
+ }
+ articles, err := dbdata.getUserThingInfo(uid, equipInfo.Equip)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pole.go.6]:", err))
+ return
+ }
+ if len(articles) == 0 {
+ ctx.SendChain(message.Text("你的背包不存在相同鱼竿进行修复"))
+ return
+ }
+ poles := make([]equip, 0, len(articles))
+ for _, info := range articles {
+ poleInfo := strings.Split(info.Other, "/")
+ durable, _ := strconv.Atoi(poleInfo[0])
+ maintenance, _ := strconv.Atoi(poleInfo[1])
+ induceLevel, _ := strconv.Atoi(poleInfo[2])
+ favorLevel, _ := strconv.Atoi(poleInfo[3])
+ poles = append(poles, equip{
+ ID: uid,
+ Equip: info.Name,
+ Durable: durable,
+ Maintenance: maintenance,
+ Induce: induceLevel,
+ Favor: favorLevel,
+ })
+ }
+ index := 0
+ check := false
+ if len(articles) > 1 {
+ msg := make(message.Message, 0, 3+len(articles))
+ msg = append(msg, message.Text("找到以下鱼竿:\n"))
+ for i, info := range poles {
+ msg = append(msg, message.Text("[", i, "] ", info.Equip, " : 耐", info.Durable, "/修", info.Maintenance,
+ "/诱", enchantLevel[info.Induce], "/眷顾", enchantLevel[info.Favor], "\n"))
+ }
+ msg = append(msg, message.Text("————————\n输入对应序号进行修复,或回复“取消”取消"))
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, msg...))
+ // 等待用户下一步选择
+ recv, cancel := zero.NewFutureEvent("message", 999, false, zero.RegexRule(`^(取消|\d+)$`), zero.CheckUser(ctx.Event.UserID)).Repeat()
+ defer cancel()
+ for {
+ select {
+ case <-time.After(time.Second * 120):
+ ctx.Send(
+ message.ReplyWithMessage(ctx.Event.MessageID,
+ message.Text("等待超时,取消修复"),
+ ),
+ )
+ return
+ case e := <-recv:
+ nextcmd := e.Event.Message.String()
+ if nextcmd == "取消" {
+ ctx.Send(
+ message.ReplyWithMessage(ctx.Event.MessageID,
+ message.Text("已取消修复"),
+ ),
+ )
+ return
+ }
+ index, err = strconv.Atoi(nextcmd)
+ if err != nil || index > len(articles)-1 {
+ ctx.SendChain(message.At(ctx.Event.UserID), message.Text("请输入正确的序号"))
+ continue
+ }
+ check = true
+ }
+ if check {
+ break
+ }
+ }
+ }
+ newEquipInfo := poles[index]
+ number, err := dbdata.getNumberFor(uid, "竿")
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at fish.go.5.1]:", err))
+ return
+ }
+ if number <= 10 {
+ number = 8
+ } else {
+ number = 10
+ }
+ equipInfo.Durable += newEquipInfo.Durable * number / 10
+ if equipInfo.Durable > durationList[equipInfo.Equip] {
+ equipInfo.Durable = durationList[equipInfo.Equip]
+ }
+ msg := ""
+ if newEquipInfo.Induce != 0 && rand.Intn(100) < 50 {
+ equipInfo.Induce += newEquipInfo.Induce
+ if equipInfo.Induce > 3 {
+ equipInfo.Induce = 3
+ }
+ msg += ",诱钓等级提升至" + enchantLevel[equipInfo.Induce]
+ }
+ if newEquipInfo.Favor != 0 && rand.Intn(100) < 50 {
+ equipInfo.Favor += newEquipInfo.Favor
+ if equipInfo.Favor > 3 {
+ equipInfo.Favor = 3
+ }
+ msg += ",海之眷顾等级提升至" + enchantLevel[equipInfo.Favor]
+ }
+ thingInfo := articles[index]
+ thingInfo.Number = 0
+ err = dbdata.updateUserThingInfo(uid, thingInfo)
+ if err == nil {
+ equipInfo.Maintenance++
+ err = dbdata.updateUserEquip(equipInfo)
+ }
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pole.go.7]:", err))
+ return
+ }
+ ctx.Send(
+ message.ReplyWithMessage(ctx.Event.MessageID,
+ message.Text("鱼竿修复成功,耐久提高至", equipInfo.Durable, msg),
+ ),
+ )
+ })
+ engine.OnRegex(`^附魔(诱钓|海之眷顾)$`, getdb).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
+ uid := ctx.Event.UserID
+ equipInfo, err := dbdata.getUserEquip(uid)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pole.go.7]:", err))
+ return
+ }
+ if equipInfo.Equip == "" || equipInfo.Equip == "美西螈" {
+ ctx.SendChain(message.Text("仅可对装备中的进行附魔"))
+ return
+ }
+ book := ctx.State["regex_matched"].([]string)[1]
+ books, err := dbdata.getUserThingInfo(uid, book)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pole.go.8]:", err))
+ return
+ }
+ if len(books) == 0 {
+ ctx.SendChain(message.Text("你的背包不存在", book, "进行附魔"))
+ return
+ }
+ bookInfo := books[0]
+ bookInfo.Number--
+ err = dbdata.updateUserThingInfo(uid, bookInfo)
+ number := 0
+ if err == nil {
+ if rand.Intn(100) > 50 {
+ ctx.SendChain(message.Text("附魔失败了"))
+ return
+ }
+ switch book {
+ case "诱钓":
+ equipInfo.Induce++
+ if equipInfo.Induce > 3 {
+ equipInfo.Induce = 3
+ }
+ number = equipInfo.Induce
+ case "海之眷顾":
+ equipInfo.Favor++
+ if equipInfo.Favor > 3 {
+ equipInfo.Favor = 3
+ }
+ number = equipInfo.Favor
+ default:
+ ctx.SendChain(message.Text("附魔失败了"))
+ return
+ }
+ err = dbdata.updateUserEquip(equipInfo)
+ }
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pole.go.9]:", err))
+ return
+ }
+ ctx.SendChain(message.Text("附魔成功,", book, "等级提高至", enchantLevel[number]))
+ })
+ engine.OnRegex(`^合成(.+竿|三叉戟)$`, getdb).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
+ uid := ctx.Event.UserID
+ thingList := []string{"木竿", "铁竿", "金竿", "钻石竿", "下界合金竿", "三叉戟"}
+ thingName := ctx.State["regex_matched"].([]string)[1]
+ indexOfMaterial := -1
+ for i, name := range thingList {
+ if thingName == name {
+ indexOfMaterial = (i - 1)
+ break
+ }
+ }
+ if indexOfMaterial < 0 {
+ return
+ }
+ articles, err := dbdata.getUserThingInfo(uid, thingList[indexOfMaterial])
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pole.go.10]:", err))
+ return
+ }
+ max := len(articles)
+ if max < 3 {
+ ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("你的合成材料不足"))
+ return
+ }
+ poles := make([]equip, 0, max)
+ for _, info := range articles {
+ poleInfo := strings.Split(info.Other, "/")
+ durable, _ := strconv.Atoi(poleInfo[0])
+ maintenance, _ := strconv.Atoi(poleInfo[1])
+ induceLevel, _ := strconv.Atoi(poleInfo[2])
+ favorLevel, _ := strconv.Atoi(poleInfo[3])
+ poles = append(poles, equip{
+ ID: uid,
+ Equip: info.Name,
+ Durable: durable,
+ Maintenance: maintenance,
+ Induce: induceLevel,
+ Favor: favorLevel,
+ })
+ }
+ list := []int{0, 1, 2}
+ check := false
+ if len(articles) > 3 {
+ msg := make(message.Message, 0, 3+len(articles))
+ msg = append(msg, message.Text("找到以下鱼竿:\n"))
+ for i, info := range poles {
+ msg = append(msg, message.Text("[", i, "] ", info.Equip, " : 耐", info.Durable, "/修", info.Maintenance,
+ "/诱", enchantLevel[info.Induce], "/眷顾", enchantLevel[info.Favor], "\n"))
+ }
+ msg = append(msg, message.Text("————————\n输入3个序号进行合成(用空格分割),或回复“取消”取消"))
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, msg...))
+ // 等待用户下一步选择
+ recv, cancel := zero.NewFutureEvent("message", 999, false, zero.RegexRule(`^(取消|\d+ \d+ \d+)$`), zero.CheckUser(ctx.Event.UserID)).Repeat()
+ defer cancel()
+ for {
+ select {
+ case <-time.After(time.Second * 120):
+ ctx.Send(
+ message.ReplyWithMessage(ctx.Event.MessageID,
+ message.Text("等待超时,取消合成"),
+ ),
+ )
+ return
+ case e := <-recv:
+ nextcmd := e.Event.Message.String()
+ if nextcmd == "取消" {
+ ctx.Send(
+ message.ReplyWithMessage(ctx.Event.MessageID,
+ message.Text("已取消合成"),
+ ),
+ )
+ return
+ }
+ chooseList := strings.Split(nextcmd, " ")
+ if list[0] == list[1] || list[0] == list[2] || list[1] == list[2] {
+ ctx.SendChain(message.At(ctx.Event.UserID), message.Text("[0]请输入正确的序号\n", list))
+ continue
+ }
+ first, err := strconv.Atoi(chooseList[0])
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pole.go.11.1]:", err))
+ return
+ }
+ second, err := strconv.Atoi(chooseList[1])
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pole.go.11.2]:", err))
+ return
+ }
+ third, err := strconv.Atoi(chooseList[2])
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pole.go.11.3]:", err))
+ return
+ }
+ if first > max || second > max || third > max {
+ ctx.SendChain(message.At(ctx.Event.UserID), message.Text("[", max, "]请输入正确的序号\n", list))
+ continue
+ }
+ list = []int{first, second, third}
+ check = true
+ }
+ if check {
+ break
+ }
+ }
+ }
+ favorLevel := 0
+ induceLevel := 0
+ for _, index := range list {
+ thingInfo := articles[index]
+ thingInfo.Number = 0
+ err = dbdata.updateUserThingInfo(uid, thingInfo)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pole.go.12]:", err))
+ return
+ }
+ favorLevel += poles[index].Favor
+ induceLevel += poles[index].Induce
+ }
+ if rand.Intn(100) >= 90 {
+ ctx.Send(
+ message.ReplyWithMessage(ctx.Event.MessageID,
+ message.Text("合成失败,材料已销毁"),
+ ),
+ )
+ return
+ }
+ attribute := strconv.Itoa(durationList[thingName]) + "/0/" + strconv.Itoa(induceLevel/3) + "/" + strconv.Itoa(favorLevel/3)
+ newthing := article{
+ Duration: time.Now().Unix(),
+ Type: "pole",
+ Name: thingName,
+ Number: 1,
+ Other: attribute,
+ }
+ err = dbdata.updateUserThingInfo(uid, newthing)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pole.go.12]:", err))
+ return
+ }
+ ctx.Send(
+ message.ReplyWithMessage(ctx.Event.MessageID,
+ message.Text(thingName, "合成成功\n属性: ", attribute),
+ ),
+ )
+ })
+}
diff --git a/plugin/mcfish/store.go b/plugin/mcfish/store.go
new file mode 100644
index 0000000000..696240e04b
--- /dev/null
+++ b/plugin/mcfish/store.go
@@ -0,0 +1,593 @@
+// Package mcfish 钓鱼模拟器
+package mcfish
+
+import (
+ "image"
+ "image/color"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/FloatTech/AnimeAPI/wallet"
+ "github.com/FloatTech/floatbox/file"
+ "github.com/FloatTech/floatbox/math"
+ "github.com/FloatTech/gg"
+ "github.com/FloatTech/imgfactory"
+ "github.com/FloatTech/zbputils/control"
+ "github.com/FloatTech/zbputils/ctxext"
+ "github.com/FloatTech/zbputils/img/text"
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+)
+
+var (
+ refresh = false
+ timeNow = 0
+ refreshFish = func(ctx *zero.Ctx) bool {
+ if refresh && timeNow == time.Now().Day() {
+ return true
+ }
+ refresh, err := dbdata.refreshStroeInfo()
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at store.go.1]:", err))
+ return refresh
+ }
+ timeNow = time.Now().Day()
+ return refresh
+ }
+)
+
+func init() {
+ engine.OnFullMatchGroup([]string{"钓鱼看板", "钓鱼商店"}, getdb, refreshFish).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
+ infos, err := dbdata.getStoreInfo()
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at store.go.2]:", err))
+ return
+ }
+ var picImage image.Image
+ if len(infos) == 0 {
+ picImage, err = drawStroeEmptyImage()
+ } else {
+ picImage, err = drawStroeInfoImage(infos)
+ }
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at store.go.3]:", err))
+ return
+ }
+ pic, err := imgfactory.ToBytes(picImage)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at store.go.4]:", err))
+ return
+ }
+ ctx.SendChain(message.ImageBytes(pic))
+ })
+ engine.OnRegex(`^出售(`+strings.Join(thingList, "|")+`)\s*(\d*)$`, getdb, refreshFish).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
+ uid := ctx.Event.UserID
+ thingName := ctx.State["regex_matched"].([]string)[1]
+ number, _ := strconv.Atoi(ctx.State["regex_matched"].([]string)[2])
+ if number == 0 {
+ number = 1
+ }
+ articles, err := dbdata.getUserThingInfo(uid, thingName)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at store.go.5]:", err))
+ return
+ }
+ if len(articles) == 0 {
+ ctx.SendChain(message.Text("你的背包不存在该物品"))
+ return
+ }
+ index := 0
+ thing := article{}
+ if len(articles) > 1 {
+ msg := make(message.Message, 0, 3+len(articles))
+ msg = append(msg, message.Reply(ctx.Event.MessageID), message.Text("找到以下物品:\n"))
+ for i, info := range articles {
+ if info.Other != "" && info.Name != "美西螈" {
+ msg = append(msg, message.Text("[", i, "] ", info.Name, "(", info.Other, ")\n"))
+ } else {
+ msg = append(msg, message.Text(
+ "[", i, "]", info.Name, " 数量: ", info.Number, "\n"))
+ }
+
+ }
+ msg = append(msg, message.Reply(ctx.Event.MessageID), message.Text("————————\n输入对应序号进行装备,或回复“取消”取消"))
+ ctx.Send(msg)
+ // 等待用户下一步选择
+ sell := false
+ recv, cancel := zero.NewFutureEvent("message", 999, false, zero.RegexRule(`^(取消|\d+)$`), zero.CheckUser(ctx.Event.UserID)).Repeat()
+ defer cancel()
+ for {
+ select {
+ case <-time.After(time.Second * 120):
+ ctx.Send(
+ message.ReplyWithMessage(ctx.Event.MessageID,
+ message.Text("等待超时,取消出售"),
+ ),
+ )
+ return
+ case e := <-recv:
+ nextcmd := e.Event.Message.String()
+ if nextcmd == "取消" {
+ ctx.Send(
+ message.ReplyWithMessage(ctx.Event.MessageID,
+ message.Text("已取消出售"),
+ ),
+ )
+ return
+ }
+ index, err = strconv.Atoi(nextcmd)
+ if err != nil || index > len(articles)-1 {
+ ctx.SendChain(message.At(ctx.Event.UserID), message.Text("请输入正确的序号"))
+ continue
+ }
+ sell = true
+ }
+ if sell {
+ break
+ }
+ }
+ }
+
+ thing = articles[index]
+ if thing.Number < number {
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("背包数量不足")))
+ return
+ }
+
+ var pice int
+ if strings.Contains(thingName, "竿") || thingName == "三叉戟" {
+ poleInfo := strings.Split(articles[index].Other, "/")
+ durable, _ := strconv.Atoi(poleInfo[0])
+ maintenance, _ := strconv.Atoi(poleInfo[1])
+ induceLevel, _ := strconv.Atoi(poleInfo[2])
+ favorLevel, _ := strconv.Atoi(poleInfo[3])
+ pice = (priceList[thingName] - (durationList[thingName] - durable) - maintenance*2 + induceLevel*600 + favorLevel*1800) * discountList[thingName] / 100
+ } else {
+ pice = priceList[thingName] * discountList[thingName] / 100
+ }
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("是否接受商店将以", pice*number*8/10, "收购", number, "个", thingName, "?\n回答\"是\"或\"否\"")))
+ // 等待用户下一步选择
+ recv, cancel1 := zero.NewFutureEvent("message", 999, false, zero.RegexRule(`^(是|否)$`), zero.CheckUser(ctx.Event.UserID)).Repeat()
+ defer cancel1()
+ buy := false
+ for {
+ select {
+ case <-time.After(time.Second * 60):
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("等待超时,取消钓鱼")))
+ return
+ case e := <-recv:
+ nextcmd := e.Event.Message.String()
+ if nextcmd == "否" {
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("已取消购买")))
+ return
+ }
+ buy = true
+ }
+ if buy {
+ break
+ }
+ }
+
+ records, err := dbdata.getUserThingInfo(uid, "唱片")
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at store.go.9.1]:", err))
+ return
+ }
+ if len(records) != 0 {
+ recordInfo := records[0]
+ numberOfRecord := recordInfo.Number
+ if thingName == "唱片" {
+ numberOfRecord--
+ }
+ if numberOfRecord > 0 {
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("是否使用唱片让价格翻倍?\n回答\"是\"或\"否\"")))
+ // 等待用户下一步选择
+ recv, cancel2 := zero.NewFutureEvent("message", 999, false, zero.RegexRule(`^(是|否)$`), zero.CheckUser(ctx.Event.UserID)).Repeat()
+ defer cancel2()
+ use := false
+ checkTime := false
+ for {
+ select {
+ case <-time.After(time.Second * 60):
+ checkTime = true
+ case e := <-recv:
+ nextcmd := e.Event.Message.String()
+ if nextcmd == "是" {
+ use = true
+ }
+ checkTime = true
+ }
+ if checkTime {
+ break
+ }
+ }
+ if use {
+ pice *= 2
+ recordInfo.Number--
+ err = dbdata.updateUserThingInfo(uid, recordInfo)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at store.go.9.2]:", err))
+ return
+ }
+ }
+ }
+ }
+ thing.Number -= number
+ err = dbdata.updateUserThingInfo(uid, thing)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at store.go.6]:", err))
+ return
+ }
+ newCommodity := store{}
+ if strings.Contains(thingName, "竿") || thingName == "三叉戟" {
+ if pice >= priceList[thingName]*4/5 { // 不值钱的删了
+ newCommodity = store{
+ Duration: time.Now().Unix(),
+ Type: "pole",
+ Name: thingName,
+ Number: 1,
+ Price: pice,
+ Other: thing.Other,
+ }
+ }
+ } else {
+ things, err1 := dbdata.getStoreThingInfo(thingName)
+ if err1 != nil {
+ ctx.SendChain(message.Text("[ERROR at store.go.8]:", err1))
+ return
+ }
+ if len(things) == 0 {
+ things = append(things, store{
+ Duration: time.Now().Unix(),
+ Name: thingName,
+ Number: 0,
+ Price: pice,
+ })
+ switch {
+ case thingName == "海之眷顾" || thingName == "诱钓" || thingName == "唱片":
+ things[0].Type = "article"
+ case thingName == "美西螈":
+ things[0].Type = "pole"
+ default:
+ things[0].Type = "fish"
+ }
+ }
+ newCommodity = things[0]
+ newCommodity.Number += number
+ }
+ err = dbdata.updateStoreInfo(newCommodity)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at store.go.9]:", err))
+ return
+ }
+ pice = pice * 8 / 10
+ err = wallet.InsertWalletOf(uid, pice*number)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at store.go.10]:", err))
+ return
+ }
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("出售成功,你赚到了", pice*number)))
+ })
+ engine.OnRegex(`^购买(`+strings.Join(thingList, "|")+`)\s*(\d*)$`, getdb, refreshFish).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
+ uid := ctx.Event.UserID
+ thingName := ctx.State["regex_matched"].([]string)[1]
+ number, _ := strconv.Atoi(ctx.State["regex_matched"].([]string)[2])
+ if number == 0 {
+ number = 1
+ }
+ thingInfos, err := dbdata.getStoreThingInfo(thingName)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at store.go.11]:", err))
+ return
+ }
+ if len(thingInfos) == 0 {
+ ctx.SendChain(message.Text("当前商店并没有上架该物品"))
+ return
+ }
+ index := 0
+ pice := make([]int, 0, len(thingInfos))
+ for _, info := range thingInfos {
+ if strings.Contains(thingName, "竿") || thingName == "三叉戟" {
+ poleInfo := strings.Split(info.Other, "/")
+ durable, _ := strconv.Atoi(poleInfo[0])
+ maintenance, _ := strconv.Atoi(poleInfo[1])
+ induceLevel, _ := strconv.Atoi(poleInfo[2])
+ favorLevel, _ := strconv.Atoi(poleInfo[3])
+ thingPice := (priceList[info.Name] - (durationList[info.Name] - durable) - maintenance*2 + induceLevel*600 + favorLevel*1800) * discountList[info.Name] / 100
+ pice = append(pice, thingPice)
+ } else {
+ thingPice := priceList[info.Name] * discountList[info.Name] / 100
+ pice = append(pice, thingPice)
+ }
+
+ }
+ if len(thingInfos) > 1 {
+ msg := make(message.Message, 0, 3+len(thingInfos))
+ msg = append(msg, message.Text("找到以下物品:\n"))
+ for i, info := range thingInfos {
+ if strings.Contains(thingName, "竿") || thingName == "三叉戟" {
+ msg = append(msg, message.Text(
+ "[", i, "]", info.Name, "(", info.Other, ") 价格:", pice[i], "\n"))
+ } else {
+ msg = append(msg, message.Text(
+ "[", i, "]", info.Name, " 数量:", info.Number, " 价格:", pice[i], "\n"))
+ }
+
+ }
+ msg = append(msg, message.Text("————————\n输入对应序号进行装备,或回复“取消”取消"))
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, msg...))
+ // 等待用户下一步选择
+ sell := false
+ recv, cancel := zero.NewFutureEvent("message", 999, false, zero.RegexRule(`^(取消|\d+)$`), zero.CheckUser(ctx.Event.UserID)).Repeat()
+ defer cancel()
+ for {
+ select {
+ case <-time.After(time.Second * 120):
+ ctx.Send(
+ message.ReplyWithMessage(ctx.Event.MessageID,
+ message.Text("等待超时,取消购买"),
+ ),
+ )
+ return
+ case e := <-recv:
+ nextcmd := e.Event.Message.String()
+ if nextcmd == "取消" {
+ ctx.Send(
+ message.ReplyWithMessage(ctx.Event.MessageID,
+ message.Text("已取消购买"),
+ ),
+ )
+ return
+ }
+ index, err = strconv.Atoi(nextcmd)
+ if err != nil || index > len(thingInfos)-1 {
+ ctx.SendChain(message.At(ctx.Event.UserID), message.Text("请输入正确的序号"))
+ continue
+ }
+ sell = true
+ }
+ if sell {
+ break
+ }
+ }
+ }
+
+ thing := thingInfos[index]
+ if thing.Number < number {
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("商店数量不足")))
+ return
+ }
+
+ money := wallet.GetWalletOf(uid)
+ if money < pice[index]*number {
+ ctx.SendChain(message.Text("你身上的钱(", money, ")不够支付"))
+ return
+ }
+
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("你确定花费", pice[index]*number, "购买", number, "个", thingName, "?\n回答\"是\"或\"否\"")))
+ // 等待用户下一步选择
+ recv, cancel1 := zero.NewFutureEvent("message", 999, false, zero.RegexRule(`^(是|否)$`), zero.CheckUser(ctx.Event.UserID)).Repeat()
+ defer cancel1()
+ buy := false
+ for {
+ select {
+ case <-time.After(time.Second * 60):
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("等待超时,取消购买")))
+ return
+ case e := <-recv:
+ nextcmd := e.Event.Message.String()
+ if nextcmd == "否" {
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("已取消购买")))
+ return
+ }
+ buy = true
+ }
+ if buy {
+ break
+ }
+ }
+
+ thing.Number -= number
+ err = dbdata.updateStoreInfo(thing)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at store.go.12]:", err))
+ return
+ }
+ err = wallet.InsertWalletOf(uid, -pice[index]*number)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at store.go.13]:", err))
+ return
+ }
+ newCommodity := article{}
+ if strings.Contains(thingName, "竿") || thingName == "三叉戟" {
+ newCommodity = article{
+ Duration: time.Now().Unix(),
+ Type: "pole",
+ Name: thingName,
+ Number: 1,
+ Other: thing.Other,
+ }
+ } else {
+ things, err1 := dbdata.getUserThingInfo(uid, thingName)
+ if err1 != nil {
+ ctx.SendChain(message.Text("[ERROR at store.go.15]:", err1))
+ return
+ }
+ if len(things) == 0 {
+ things = append(things, article{
+ Duration: time.Now().Unix(),
+ Name: thingName,
+ Number: 0,
+ })
+ switch {
+ case thingName == "海之眷顾" || thingName == "诱钓" || thingName == "唱片":
+ things[0].Type = "article"
+ case thingName == "美西螈":
+ things[0].Type = "pole"
+ default:
+ things[0].Type = "fish"
+ }
+ }
+ newCommodity = things[0]
+ newCommodity.Number += number
+ }
+ err = dbdata.updateUserThingInfo(uid, newCommodity)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at store.go.14]:", err))
+ return
+ }
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("购买成功")))
+ })
+}
+
+func drawStroeEmptyImage() (picImage image.Image, err error) {
+ fontdata, err := file.GetLazyData(text.BoldFontFile, control.Md5File, true)
+ if err != nil {
+ return nil, err
+ }
+ canvas := gg.NewContext(1000, 300)
+ // 画底色
+ canvas.DrawRectangle(0, 0, 1000, 300)
+ canvas.SetRGBA255(255, 255, 255, 150)
+ canvas.Fill()
+ // 边框框
+ canvas.DrawRectangle(0, 0, 1000, 300)
+ canvas.SetLineWidth(3)
+ canvas.SetRGBA255(0, 0, 0, 255)
+ canvas.Stroke()
+
+ canvas.SetColor(color.Black)
+ err = canvas.ParseFontFace(fontdata, 100)
+ if err != nil {
+ return nil, err
+ }
+ textW, textH := canvas.MeasureString("价格信息")
+ canvas.DrawString("价格信息", 10, 10+textH*1.2)
+ canvas.DrawLine(10, textH*1.6, textW, textH*1.6)
+ canvas.SetLineWidth(3)
+ canvas.SetRGBA255(0, 0, 0, 255)
+ canvas.Stroke()
+ if err = canvas.ParseFontFace(fontdata, 50); err != nil {
+ return nil, err
+ }
+ canvas.DrawStringAnchored("当前商店并没有上架任何物品", 500, 10+textH*2+50, 0.5, 0)
+ return canvas.Image(), nil
+}
+
+func drawStroeInfoImage(stroeInfo []store) (picImage image.Image, err error) {
+ fontdata, err := file.GetLazyData(text.BoldFontFile, control.Md5File, true)
+ if err != nil {
+ return nil, err
+ }
+ canvas := gg.NewContext(1, 1)
+ err = canvas.ParseFontFace(fontdata, 100)
+ if err != nil {
+ return nil, err
+ }
+ titleW, titleH := canvas.MeasureString("价格信息")
+
+ err = canvas.ParseFontFace(fontdata, 50)
+ if err != nil {
+ return nil, err
+ }
+ _, textH := canvas.MeasureString("高度")
+ nameW, _ := canvas.MeasureString("下界合金竿(100/100/0/0)")
+ numberW, _ := canvas.MeasureString("10000")
+ priceW, _ := canvas.MeasureString("10000")
+
+ bolckW := int(10 + nameW + 50 + numberW + 50 + priceW + 10)
+ backY := 10 + int(titleH*2+10)*2 + 10 + (len(stroeInfo)+len(discountList)/2+2)*int(textH*2) + 10
+ canvas = gg.NewContext(bolckW, math.Max(backY, 500))
+ // 画底色
+ canvas.DrawRectangle(0, 0, float64(bolckW), float64(backY))
+ canvas.SetRGBA255(150, 150, 150, 255)
+ canvas.Fill()
+
+ // 放字
+ canvas.SetColor(color.Black)
+ err = canvas.ParseFontFace(fontdata, 100)
+ if err != nil {
+ return nil, err
+ }
+ canvas.DrawString("今日波动", 10, 10+titleH*1.2)
+ canvas.DrawLine(10, titleH*1.6, titleW, titleH*1.6)
+ canvas.SetLineWidth(3)
+ canvas.SetRGBA255(0, 0, 0, 255)
+ canvas.Stroke()
+
+ textDy := 10 + titleH*1.7
+ if err = canvas.ParseFontFace(fontdata, 35); err != nil {
+ return nil, err
+ }
+ textDx, textDh := canvas.MeasureString("下界合金竿(均价1000)")
+ valueDx, _ := canvas.MeasureString("+100%")
+ i := 0
+ for name, info := range discountList {
+ text := name + "(均价" + strconv.Itoa(priceList[name]) + ") "
+
+ if i == 2 {
+ i = 0
+ textDy += textDh * 2
+ }
+ canvas.SetColor(color.Black)
+ canvas.DrawStringAnchored(text, 20+(textDx+valueDx+10)*float64(i)+10, textDy+textDh/2, 0, 0.5)
+ if info-100 > 0 {
+ canvas.SetRGBA255(200, 50, 50, 255)
+ text = "+" + strconv.Itoa(info-100) + "%"
+ } else {
+ canvas.SetRGBA255(63, 133, 55, 255)
+ text = strconv.Itoa(info-100) + "%"
+ }
+ canvas.DrawStringAnchored(text, 20+(textDx+valueDx+10)*float64(i)+10+textDx+10, textDy+textDh/2, 0, 0.5)
+ i++
+ }
+ canvas.SetColor(color.Black)
+ textDy += textDh * 2
+ canvas.DrawStringAnchored("注:出售商品将会额外扣除20%的税收,附魔鱼竿请按实际价格", 10, textDy+10+textDh/2, 0, 0.5)
+
+ textDy += textH * 2
+ err = canvas.ParseFontFace(fontdata, 100)
+ if err != nil {
+ return nil, err
+ }
+ canvas.DrawString("上架内容", 10, textDy+titleH*1.2)
+ canvas.DrawLine(10, textDy+titleH*1.6, titleW, textDy+titleH*1.6)
+ canvas.SetLineWidth(3)
+ canvas.SetRGBA255(0, 0, 0, 255)
+ canvas.Stroke()
+
+ textDy += 10 + titleH*1.7
+ if err = canvas.ParseFontFace(fontdata, 50); err != nil {
+ return nil, err
+ }
+
+ canvas.DrawStringAnchored("名称", 10+nameW/2, textDy+textH/2, 0.5, 0.5)
+ canvas.DrawStringAnchored("数量", 10+nameW+10+numberW/2, textDy+textH/2, 0.5, 0.5)
+ canvas.DrawStringAnchored("价格", 10+nameW+10+numberW+50+priceW/2, textDy+textH/2, 0.5, 0.5)
+
+ for _, info := range stroeInfo {
+ textDy += textH * 2
+ name := info.Name
+ if info.Other != "" && info.Name != "美西螈" {
+ name += "(" + info.Other + ")"
+ }
+ numberStr := strconv.Itoa(info.Number)
+ pice := 0
+ if strings.Contains(name, "竿") {
+ poleInfo := strings.Split(info.Other, "/")
+ durable, _ := strconv.Atoi(poleInfo[0])
+ maintenance, _ := strconv.Atoi(poleInfo[1])
+ induceLevel, _ := strconv.Atoi(poleInfo[2])
+ favorLevel, _ := strconv.Atoi(poleInfo[3])
+ pice = (priceList[info.Name] - (durationList[info.Name] - durable) - maintenance*2 + induceLevel*600 + favorLevel*1800) * discountList[info.Name] / 100
+ } else {
+ pice = priceList[info.Name] * discountList[info.Name] / 100
+ }
+
+ canvas.DrawStringAnchored(name, 10+nameW/2, textDy+textH/2, 0.5, 0.5)
+ canvas.DrawStringAnchored(numberStr, 10+nameW+10+numberW/2, textDy+textH/2, 0.5, 0.5)
+ canvas.DrawStringAnchored(strconv.Itoa(pice), 10+nameW+10+numberW+50+priceW/2, textDy+textH/2, 0.5, 0.5)
+ }
+ return canvas.Image(), nil
+}