500 lines
14 KiB
Go
500 lines
14 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"regexp"
|
|
"strconv"
|
|
"sync"
|
|
)
|
|
|
|
var (
|
|
muxObjItem sync.RWMutex
|
|
objItems []ChatWarsItem
|
|
cacheObjItem map[string]int64
|
|
cacheObjItemId map[int64]int64
|
|
cacheObjItemCraft map[string]int64
|
|
)
|
|
|
|
func addObjItem(code string, name string, itemTypeID64 int64, weight int64, exchange string, auction bool, craftable bool) (int64, error) {
|
|
tx, err := db.Begin()
|
|
logOnError(err, "addObjItem : start transaction")
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
res, err := tx.Exec(`INSERT INTO obj (obj_type_id, obj_sub_type_id)
|
|
VALUES (` + strconv.FormatInt(cacheObjType[`item`], 10) + `,` + fmt.Sprintf("%d", itemTypeID64) + `);`)
|
|
logOnError(err, "addObjItem : exec insert obj ("+code+", "+name+")")
|
|
if err != nil {
|
|
err2 := tx.Rollback()
|
|
logOnError(err2, "addObjItem : rollback insert obj")
|
|
return 0, err
|
|
}
|
|
|
|
objId, err := res.LastInsertId()
|
|
if err != nil {
|
|
err2 := tx.Rollback()
|
|
logOnError(err2, "addObjItem : rollback get lastInsertId")
|
|
return 0, err
|
|
}
|
|
|
|
stmt, err := tx.Prepare(`INSERT INTO obj_item (obj_id, intl_id, weight, exchange, auction, craftable)
|
|
VALUES (?, ?, ?, ?, ?, ?);`)
|
|
logOnError(err, "addObjItem : prepare insert obj_item")
|
|
if err != nil {
|
|
err2 := tx.Rollback()
|
|
logOnError(err2, "addObjItem : rollback prepare insert obj_item")
|
|
return 0, err
|
|
}
|
|
defer stmt.Close()
|
|
|
|
var e, a, c int = 0, 0, 0
|
|
if exchange != `` {
|
|
e = 1
|
|
}
|
|
if auction {
|
|
a = 1
|
|
}
|
|
if craftable {
|
|
c = 1
|
|
}
|
|
|
|
_, err = stmt.Exec(objId, code, weight, e, a, c)
|
|
logOnError(err, "addObjItem : exec insert obj_item")
|
|
if err != nil {
|
|
err2 := tx.Rollback()
|
|
logOnError(err2, "addObjItem : rollback exec insert obj_item")
|
|
return 0, err
|
|
}
|
|
|
|
err = tx.Commit()
|
|
logOnError(err, "addObjItem : commit")
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
err = objAddName(objId, name)
|
|
logOnError(err, "addObjItem : add name")
|
|
|
|
return objId, nil
|
|
}
|
|
|
|
func getObjItem(objID64 int64) (*ChatWarsItem, error) {
|
|
muxObjItem.RLock()
|
|
defer muxObjItem.RUnlock()
|
|
if id, ok := cacheObjItemId[objID64]; ok {
|
|
//log.Printf("Matching item name %s with %s.\n", name, obj.Name)
|
|
return &objItems[id], nil
|
|
} else {
|
|
return nil, errors.New("Item not found.")
|
|
}
|
|
}
|
|
|
|
func getObjItemID(c string, n string) int64 {
|
|
return getVerboseObjItemID(c, n)
|
|
}
|
|
|
|
func getVerboseObjItemID(c string, n string) int64 {
|
|
i := getSilentObjItemID(c, n)
|
|
if i == 0 {
|
|
w := TGCommand{
|
|
Type: commandSendMsg,
|
|
Text: fmt.Sprintf("Object unknown : %s - %s\n", c, n),
|
|
ToUserID64: cfg.Bot.Admin,
|
|
}
|
|
TGCmdQueue <- w
|
|
}
|
|
return i
|
|
}
|
|
|
|
func getSilentObjItemID(code string, name string) int64 {
|
|
muxObjItem.RLock()
|
|
defer muxObjItem.RUnlock()
|
|
if len(code) > 0 {
|
|
if id, ok := cacheObjItem[code]; ok {
|
|
//log.Printf("Matching item code %s with %s.\n", code, obj.Code)
|
|
return objItems[id].ObjID64
|
|
}
|
|
if ok, _ := regexp.MatchString(`^(a|w)[0-9]+[a-e]$`, code); ok {
|
|
// log.Printf("Matching quality item code %s with %s.\n", code, code[:len(code)-1])
|
|
if id, ok := cacheObjItem[code[:len(code)-1]]; ok {
|
|
return objItems[id].ObjID64
|
|
}
|
|
}
|
|
if ok, _ := regexp.MatchString(`^u[0-9]+`, code); !ok {
|
|
return 0
|
|
}
|
|
}
|
|
if len(name) == 0 {
|
|
return 0
|
|
}
|
|
if id, ok := cacheObjItem[name]; ok {
|
|
//log.Printf("Matching item name %s with %s.\n", name, obj.Name)
|
|
return objItems[id].ObjID64
|
|
}
|
|
if ok, _ := regexp.MatchString(`^((u|e)[0-9]+|(a|w)[0-9]+[a-e]{0,1})$`, code); ok || len(code) == 0 {
|
|
if ok, _ := regexp.MatchString(`^(Mystery|Unidentified) (amulet|ring) lvl.[0-9]+$`, name); ok {
|
|
return objItems[cacheObjItem[`u000`]].ObjID64
|
|
}
|
|
|
|
r := regexp.MustCompile(`^((?P<Modifier>⚡\+[0-9]+) ){0,1}(?P<BaseName>.+?)( \+(?P<Atk>[0-9]+)⚔){0,1}( \+(?P<Def>[0-9]+)🛡){0,1}( \+(?P<Mana>[0-9]+)💧){0,1}$`)
|
|
basename := r.ReplaceAllString(name, "${BaseName}")
|
|
if id, ok := cacheObjItem[basename]; ok && len(basename) > 0 {
|
|
//log.Printf("Matching item full basename %s with %s.\n", basename, obj.Name)
|
|
return objItems[id].ObjID64
|
|
}
|
|
i := int64(-1)
|
|
for _, id := range cacheObjItem {
|
|
if ok, _ := regexp.MatchString(`^(a|e|w)[0-9]+$`, objItems[id].Code); ok { //only gear can be custom named
|
|
m := fmt.Sprintf("^((%s.*)|(.*%s))$", regexp.QuoteMeta(objItems[id].Names[0]), regexp.QuoteMeta(objItems[id].Names[0]))
|
|
if ok, _ := regexp.MatchString(m, basename); ok {
|
|
//log.Printf("LOOP : Matching item modified basename %s with %s (%d).\n", basename, item.Name, item.ObjID64)
|
|
i = id
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if i != -1 {
|
|
//log.Printf("RETURN : Matching item modified basename %s with %s (%d).\n", basename, item.Name, item.ObjID64)
|
|
return objItems[i].ObjID64
|
|
} else {
|
|
/*
|
|
fmt.Printf("silentGetObjItemID : Modifier : `%s`\n", r.ReplaceAllString(name, "${Modifier}"))
|
|
fmt.Printf("silentGetObjItemID : BaseName : `%s`\n", r.ReplaceAllString(name, "${BaseName}"))
|
|
fmt.Printf("silentGetObjItemID : Atk : `%s`\n", r.ReplaceAllString(name, "${Atk}"))
|
|
fmt.Printf("silentGetObjItemID : Def : `%s`\n", r.ReplaceAllString(name, "${Def}"))
|
|
fmt.Printf("silentGetObjItemID : Mana : `%s`\n", r.ReplaceAllString(name, "${Mana}"))
|
|
*/
|
|
}
|
|
|
|
}
|
|
return 0
|
|
|
|
}
|
|
|
|
func setObjItemCraftable(objID64 int64, craftable bool) error {
|
|
var c int = 0
|
|
if craftable {
|
|
c = 1
|
|
}
|
|
|
|
stmt, err := db.Prepare(`UPDATE obj_item oi SET oi.craftable = ? WHERE oi.obj_id = ?;`)
|
|
logOnError(err, "setObjItemCraftable : prepare update obj_item")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer stmt.Close()
|
|
|
|
_, err = stmt.Exec(c, objID64)
|
|
logOnError(err, fmt.Sprintf("setObjItemCraftable : update obj_item(%d)", objID64))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func setObjItemWeight(objID64 int64, weight int64) error {
|
|
stmt, err := db.Prepare(`UPDATE obj_item oi SET oi.weight = ? WHERE oi.obj_id = ?;`)
|
|
logOnError(err, "setObjItemWeight : prepare update obj_item")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer stmt.Close()
|
|
|
|
_, err = stmt.Exec(weight, objID64)
|
|
logOnError(err, fmt.Sprintf("setObjItemWeight : update obj_item(%d)", objID64))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func setObjItemCraft(objID64 int64, cmd string, mana int64) error {
|
|
stmt, err := db.Prepare(`INSERT INTO obj_craft (obj_id, cmd, mana)
|
|
VALUES (?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE cmd = ?, mana = ?;`)
|
|
|
|
logOnError(err, "setObjItemCraft : prepare update obj_craft")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer stmt.Close()
|
|
|
|
_, err = stmt.Exec(objID64, cmd, mana, cmd, mana)
|
|
logOnError(err, fmt.Sprintf("setObjItemCraft : update obj_craft(%d)", objID64))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func emptyObjItemCraftItem(objID64 int64) error {
|
|
stmt, err := db.Prepare(`DELETE FROM obj_craft_item WHERE obj_id = ?;`)
|
|
logOnError(err, "emptyObjItemCraftItem : prepare delete obj_craft_item")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer stmt.Close()
|
|
|
|
_, err = stmt.Exec(objID64)
|
|
logOnError(err, fmt.Sprintf("setObjItemWeight : delete obj_craft_item(%d)", objID64))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func addObjItemCraftItem(objID64 int64, itemID64 int64, quantity int64) error {
|
|
stmt, err := db.Prepare(`INSERT INTO obj_craft_item (obj_id, item_id, quantity)
|
|
VALUES (?, ?, ?);`)
|
|
|
|
logOnError(err, "addObjItemCraftItem : prepare update obj_craft_item")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer stmt.Close()
|
|
|
|
_, err = stmt.Exec(objID64, itemID64, quantity)
|
|
logOnError(err, fmt.Sprintf("addObjItemCraftItem : update obj_craft_item(%d)", objID64))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getCraftItemID(cmd string) (int64, error) {
|
|
muxObjItem.RLock()
|
|
defer muxObjItem.RUnlock()
|
|
|
|
if id, ok := cacheObjItemCraft[cmd]; ok {
|
|
return objItems[id].ObjID64, nil
|
|
} else {
|
|
return 0, nil
|
|
}
|
|
|
|
return 0, nil
|
|
}
|
|
|
|
func loadObjItem() error {
|
|
var (
|
|
obj_id int64
|
|
type_id int64
|
|
intl_id string
|
|
name string
|
|
weight int64
|
|
craftable int
|
|
objItemsBuf []ChatWarsItem
|
|
)
|
|
|
|
muxObjItem.Lock()
|
|
defer muxObjItem.Unlock()
|
|
cacheObjItem = make(map[string]int64)
|
|
cacheObjItemId = make(map[int64]int64)
|
|
cacheObjItemCraft = make(map[string]int64)
|
|
objItems = make([]ChatWarsItem, 0)
|
|
|
|
for _, a := range AssetNames() {
|
|
m, err := regexp.MatchString("data/obj_item/.*\\.json", a)
|
|
logOnError(err, "loadObjItem : MatchString")
|
|
|
|
if m {
|
|
b, err := Asset(a)
|
|
logOnError(err, "loadObjItem : load Asset("+a+")")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
objItemsBuf = make([]ChatWarsItem, 0)
|
|
err = json.Unmarshal(b, &objItemsBuf)
|
|
for _, i := range objItemsBuf {
|
|
objItems = append(objItems, i)
|
|
}
|
|
log.Printf("%d items parsed from %s.\n", len(objItemsBuf), a)
|
|
}
|
|
}
|
|
|
|
var k int64
|
|
for k = 0; k < int64(len(objItems)); k++ {
|
|
if len(objItems[k].Names) == 0 {
|
|
log.Printf("loadObjItem : %s : name missing.\n", objItems[k].Code)
|
|
} else {
|
|
if idc, okc := cacheObjItem[objItems[k].Code]; okc {
|
|
log.Printf("loadObjItem : %s : duplicate code found. Belong to %s\n", objItems[idc].Code, objItems[idc].Names[0])
|
|
} else {
|
|
cacheObjItem[objItems[k].Code] = k
|
|
objItems[k].ItemTypeID = cacheObjSubType[objItems[k].ItemType]
|
|
|
|
for _, n := range objItems[k].Names {
|
|
if idn, okn := cacheObjItem[n]; okn {
|
|
log.Printf("loadObjItem : %s - %s : duplicate name found. Belongs to %s\n", objItems[k].Code, n, objItems[idn].Code)
|
|
} else {
|
|
cacheObjItem[n] = k
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
log.Printf("Duplicate check done...\n")
|
|
|
|
objs, err := db.Query(`SELECT oi.obj_id, o.obj_sub_type_id, oi.intl_id, oi.weight, oi.craftable FROM obj o, obj_item oi WHERE o.id = oi.obj_id;`)
|
|
if err != nil {
|
|
logOnError(err, "loadObjItem : querying items")
|
|
return err
|
|
}
|
|
defer objs.Close()
|
|
|
|
for objs.Next() {
|
|
err = objs.Scan(&obj_id, &type_id, &intl_id, &weight, &craftable)
|
|
if err != nil {
|
|
logOnError(err, "loadObjItem : scanning items")
|
|
return err
|
|
}
|
|
|
|
if id, ok := cacheObjItem[intl_id]; !ok {
|
|
log.Printf("loadObjItem : %s : orphaned item in database (id : %d)\n", intl_id, obj_id)
|
|
} else {
|
|
objItems[id].ObjID64 = obj_id
|
|
|
|
if weight != objItems[id].Weight {
|
|
log.Printf("loadObjItem : %s - %s : weight changed : %d => %d\n", objItems[id].Code, objItems[id].Names[0], weight, objItems[id].Weight)
|
|
setObjItemWeight(obj_id, objItems[id].Weight)
|
|
}
|
|
|
|
if (craftable == 0 && objItems[id].Craftable) || (craftable == 1 && !objItems[id].Craftable) {
|
|
log.Printf("loadObjItem : %s - %s : craftable changed : %v => %v\n", objItems[id].Code, objItems[id].Names[0], !objItems[id].Craftable, objItems[id].Craftable)
|
|
setObjItemCraftable(obj_id, objItems[id].Craftable)
|
|
}
|
|
|
|
cacheObjItemId[obj_id] = id
|
|
}
|
|
}
|
|
|
|
log.Printf("Loading existing items done...\n")
|
|
|
|
names, err := db.Query(`SELECT oi.obj_id, obn.name FROM obj_item oi, obj_name obn WHERE oi.obj_id = obn.obj_id;`)
|
|
if err != nil {
|
|
logOnError(err, "loadObjItem : querying names")
|
|
return err
|
|
}
|
|
defer names.Close()
|
|
|
|
for names.Next() {
|
|
err = names.Scan(&obj_id, &name)
|
|
if err != nil {
|
|
logOnError(err, "loadObjItem : scanning names")
|
|
return err
|
|
}
|
|
if _, ok := cacheObjItem[name]; !ok {
|
|
if id, ok := cacheObjItemId[obj_id]; ok {
|
|
log.Printf("loadObjItem : %s : orphaned name in database for item %s\n", name, objItems[id].Code)
|
|
}
|
|
}
|
|
}
|
|
|
|
log.Printf("Loading existing names done...\n")
|
|
|
|
for _, i := range cacheObjItem {
|
|
if objItems[i].ObjID64 == 0 {
|
|
id, err := addObjItem(objItems[i].Code, objItems[i].Names[0], objItems[i].ItemTypeID, objItems[i].Weight, objItems[i].Exchange, objItems[i].Auction, objItems[i].Craftable)
|
|
logOnError(err, "loadObjItem : addObjItem")
|
|
objItems[i].ObjID64 = id
|
|
cacheObjItemId[id] = i
|
|
for _, n := range objItems[i].Names[1:] {
|
|
err = objAddName(id, n)
|
|
logOnError(err, "loadObjItem : objAddName")
|
|
cacheObjItem[n] = i
|
|
}
|
|
}
|
|
}
|
|
|
|
log.Printf("Adding new objects done...\n")
|
|
|
|
for _, i := range cacheObjItem {
|
|
if objItems[i].Craft != nil {
|
|
cacheObjItemCraft[objItems[i].Craft.Command] = i
|
|
setObjItemCraft(objItems[i].ObjID64, objItems[i].Craft.Command, objItems[i].Craft.Mana)
|
|
emptyObjItemCraftItem(objItems[i].ObjID64)
|
|
for k, o := range objItems[i].Craft.Items {
|
|
if id, ok := cacheObjItem[o.Code]; !ok {
|
|
log.Printf("loadObjItem : %s : unknown item %s for recipe.\n", objItems[i].Code, o.Code)
|
|
} else {
|
|
objItems[i].Craft.Items[k].ItemID64 = objItems[id].ObjID64
|
|
}
|
|
|
|
}
|
|
for _, o := range objItems[i].Craft.Items {
|
|
addObjItemCraftItem(objItems[i].ObjID64, o.ItemID64, o.Quantity)
|
|
}
|
|
}
|
|
}
|
|
|
|
log.Printf("Adding new crafts done...\n")
|
|
|
|
log.Printf("%d items loaded.\n", len(objItems))
|
|
|
|
/*
|
|
for _, v := range cacheObjItemId {
|
|
log.Printf("Item cached : %d\n", v.ObjID64)
|
|
for _, n := range v.Names {
|
|
log.Printf("cacheObjItemId[%d] : %s : %s.\n", v.ObjID64, v.Names[0], n)
|
|
}
|
|
}
|
|
*/
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
func getObjItemVal(objID64 int64, quality string) float64 {
|
|
var val float64
|
|
item, err := getObjItem(objID64)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
if !item.Auction {
|
|
return 0
|
|
}
|
|
row := db.QueryRow(`SELECT avg(omaa.price)
|
|
FROM obj_msg_auction_announce omaa
|
|
WHERE omaa.item_id = ?
|
|
AND omaa.quality = ?
|
|
AND status = 'Finished'`, objID64, quality)
|
|
err = row.Scan(&val)
|
|
if err != nil {
|
|
logOnError(err, "getObjItemVal : row.Scan")
|
|
return 0
|
|
}
|
|
return val
|
|
}
|
|
|
|
func getObjItemValDet(objID64 int64, quality string, days int) (float64, int64) {
|
|
var (
|
|
val float64
|
|
qty int64
|
|
)
|
|
item, err := getObjItem(objID64)
|
|
if err != nil {
|
|
return 0, 0
|
|
}
|
|
if !item.Auction {
|
|
return 0, 0
|
|
}
|
|
row := db.QueryRow(`SELECT COALESCE(avg(omaa.price), 0)
|
|
,COALESCE(count(*), 0)
|
|
FROM obj_msg_auction_announce omaa
|
|
WHERE omaa.item_id = ?
|
|
AND omaa.quality = ?
|
|
AND omaa.end > DATE_ADD(CURRENT_TIMESTAMP, INTERVAL -? DAY)
|
|
AND omaa.status = 'Finished'`, objID64, quality, days)
|
|
err = row.Scan(&val, &qty)
|
|
if err != nil {
|
|
logOnError(err, "getObjItemValDet : row.Scan")
|
|
return 0, 0
|
|
}
|
|
return val, qty
|
|
}
|