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⚡\+[0-9]+) ){0,1}(?P.+?)( \+(?P[0-9]+)⚔){0,1}( \+(?P[0-9]+)🛡){0,1}( \+(?P[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 }