523 lines
20 KiB
Go
523 lines
20 KiB
Go
package handler
|
||
|
||
import (
|
||
"fmt"
|
||
"math"
|
||
"time"
|
||
|
||
"past/internal/httpapi"
|
||
"past/internal/store"
|
||
)
|
||
|
||
func registerPet(r *httpapi.Router) {
|
||
r.Handle("pet_info", petInfo)
|
||
r.Handle("pet_adopt", petAdopt)
|
||
r.Handle("pet_daily_action", petDailyAction)
|
||
r.Handle("pet_daily_status", petDailyStatus)
|
||
r.Handle("pet_achievements", petAchievements)
|
||
r.Handle("pet_rename", petRename)
|
||
r.Handle("pet_equip", petEquip)
|
||
r.Handle("pet_dynamics", petDynamics)
|
||
}
|
||
|
||
type petAch struct{ Key, Name, Desc, Icon string }
|
||
|
||
// allPetAchievements 对应 getAllPetAchievements()(保持顺序)
|
||
var allPetAchievements = []petAch{
|
||
{"first_adopt", "初次相遇", "领养第一只宠物", "🐣"},
|
||
{"level_5", "成长之路", "宠物达到5级", "⭐"},
|
||
{"level_10", "小有成就", "宠物达到10级", "🌟"},
|
||
{"level_20", "资深铲屎官", "宠物达到20级", "👑"},
|
||
{"level_30", "终极陪伴", "宠物达到30级", "💎"},
|
||
{"stage_2", "少年初长成", "宠物进入少年期", "🐕"},
|
||
{"stage_3", "青春活力", "宠物进入青年期", "🦮"},
|
||
{"stage_4", "成熟稳重", "宠物进入成年期", "🐩"},
|
||
{"stage_5", "终极形态", "宠物进入终极期", "🦁"},
|
||
{"all_done_7", "一周全勤", "连续7天完成所有任务", "📅"},
|
||
{"all_done_30", "月度之星", "连续30天完成所有任务", "🏆"},
|
||
{"feed_100", "大胃王", "累计喂食100次", "🍖"},
|
||
{"walk_50", "散步达人", "累计散步50次", "🚶"},
|
||
{"bath_30", "爱干净", "累计洗澡30次", "🛁"},
|
||
{"play_50", "玩伴", "累计玩耍50次", "🎾"},
|
||
{"happiness_100", "快乐满分", "宠物心情达到100", "😊"},
|
||
{"rename_first", "取名大师", "第一次给宠物改名", "✏️"},
|
||
{"adopt_7days", "一周陪伴", "领养满7天", "📆"},
|
||
{"adopt_30days", "一月相伴", "领养满30天", "🗓️"},
|
||
{"adopt_100days", "百日纪念", "领养满100天", "💯"},
|
||
}
|
||
|
||
func petAchByKey(key string) petAch {
|
||
for _, a := range allPetAchievements {
|
||
if a.Key == key {
|
||
return a
|
||
}
|
||
}
|
||
return petAch{}
|
||
}
|
||
|
||
// consecutiveDoneDays 从按日期倒序的 all_done 记录统计连续天数
|
||
func consecutiveDoneDays(rows []store.Row) int64 {
|
||
var consecutive int64
|
||
var prev time.Time
|
||
for i, r := range rows {
|
||
cur, _ := parseDate(r.Str("date"))
|
||
if i == 0 {
|
||
consecutive = 1
|
||
} else {
|
||
if math.Round(prev.Sub(cur).Hours()/24) == 1 {
|
||
consecutive++
|
||
} else {
|
||
break
|
||
}
|
||
}
|
||
prev = cur
|
||
}
|
||
return consecutive
|
||
}
|
||
|
||
// checkPetAchievements 对应 checkPetAchievements($db, $coupleId, $level, $stage, $happiness, $extra)
|
||
// happiness < 0 表示 null(不检查心情成就)
|
||
func checkPetAchievements(c *httpapi.Ctx, coupleID, level, stage, happiness int64, rename bool) {
|
||
db := c.DB
|
||
rs, _ := db.Query("SELECT achievement_key FROM pet_achievements WHERE couple_id = ?", coupleID)
|
||
unlocked := map[string]bool{}
|
||
for _, r := range rs {
|
||
unlocked[r.Str("achievement_key")] = true
|
||
}
|
||
var toUnlock []string
|
||
add := func(cond bool, key string) {
|
||
if cond && !unlocked[key] {
|
||
toUnlock = append(toUnlock, key)
|
||
}
|
||
}
|
||
add(true, "first_adopt")
|
||
add(level >= 5, "level_5")
|
||
add(level >= 10, "level_10")
|
||
add(level >= 20, "level_20")
|
||
add(level >= 30, "level_30")
|
||
add(stage >= 2, "stage_2")
|
||
add(stage >= 3, "stage_3")
|
||
add(stage >= 4, "stage_4")
|
||
add(stage >= 5, "stage_5")
|
||
if p, _ := db.QueryOne("SELECT adopted_at FROM pets WHERE couple_id = ?", coupleID); p != nil && p.Str("adopted_at") != "" {
|
||
if t, err := parseDate(p.Str("adopted_at")); err == nil {
|
||
days := int64(math.Floor(time.Since(t).Seconds() / 86400))
|
||
add(days >= 7, "adopt_7days")
|
||
add(days >= 30, "adopt_30days")
|
||
add(days >= 100, "adopt_100days")
|
||
}
|
||
}
|
||
add(happiness >= 0 && happiness >= 100, "happiness_100")
|
||
if !unlocked["feed_100"] || !unlocked["walk_50"] || !unlocked["bath_30"] || !unlocked["play_50"] {
|
||
if cnt, _ := db.QueryOne("SELECT SUM(feed_u1 + feed_u2) as feed_cnt, SUM(walk_u1 + walk_u2) as walk_cnt, SUM(bath_u1 + bath_u2) as bath_cnt, SUM(play_u1 + play_u2) as play_cnt FROM pet_dailies WHERE couple_id = ?", coupleID); cnt != nil {
|
||
add(cnt.Int("feed_cnt") >= 100, "feed_100")
|
||
add(cnt.Int("walk_cnt") >= 50, "walk_50")
|
||
add(cnt.Int("bath_cnt") >= 30, "bath_30")
|
||
add(cnt.Int("play_cnt") >= 50, "play_50")
|
||
}
|
||
}
|
||
if !unlocked["all_done_7"] || !unlocked["all_done_30"] {
|
||
done, _ := db.Query("SELECT date, all_done FROM pet_dailies WHERE couple_id = ? AND all_done = 1 ORDER BY date DESC", coupleID)
|
||
consecutive := consecutiveDoneDays(done)
|
||
add(consecutive >= 7, "all_done_7")
|
||
add(consecutive >= 30, "all_done_30")
|
||
}
|
||
add(rename, "rename_first")
|
||
if len(toUnlock) == 0 {
|
||
return
|
||
}
|
||
// pet_dynamics.user_id 有外键指向 users,而系统动态写 user_id=0:PHP 版本在此处因外键失败而 500,
|
||
// Go 版本在独占连接上临时关闭外键检查写入(语义即"系统"动态)。
|
||
db.ExecNoFK(func(q store.Querier) error {
|
||
for _, key := range toUnlock {
|
||
q.Exec("INSERT IGNORE INTO pet_achievements (couple_id, achievement_key) VALUES (?, ?)", coupleID, key)
|
||
a := petAchByKey(key)
|
||
q.Exec("INSERT INTO pet_dynamics (couple_id, user_id, action, message) VALUES (?, 0, 'achievement', ?)", coupleID, "解锁成就:"+a.Name+" "+a.Icon)
|
||
}
|
||
return nil
|
||
})
|
||
}
|
||
|
||
// coupleUserIDsOrdered 对应 SELECT id FROM users WHERE couple_id = ? ORDER BY id ASC LIMIT 2 → [user1, user2](缺省 0)
|
||
func coupleUserIDsOrdered(c *httpapi.Ctx, coupleID int64) (int64, int64) {
|
||
r1, r2 := coupleUserIDsRaw(c, coupleID)
|
||
return store.ToInt(r1), store.ToInt(r2)
|
||
}
|
||
|
||
// coupleUserIDsRaw 同上,但保留 PHP 的原始值类型($users[0] ?? 0:存在为行值,缺省为整数 0)
|
||
func coupleUserIDsRaw(c *httpapi.Ctx, coupleID int64) (any, any) {
|
||
rs, _ := c.DB.Query("SELECT id FROM users WHERE couple_id = ? ORDER BY id ASC LIMIT 2", coupleID)
|
||
var u1, u2 any = int64(0), int64(0)
|
||
if len(rs) > 0 {
|
||
u1 = rs[0]["id"]
|
||
}
|
||
if len(rs) > 1 {
|
||
u2 = rs[1]["id"]
|
||
}
|
||
return u1, u2
|
||
}
|
||
|
||
func petInfo(c *httpapi.Ctx) error {
|
||
uid := c.UID()
|
||
coupleID, err := requireCoupleID(c)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
pet, err := c.DB.QueryOne("SELECT * FROM pets WHERE couple_id = ?", coupleID)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if pet == nil {
|
||
return c.OK(M{"has_pet": false, "pet": nil})
|
||
}
|
||
if fa, _ := c.DB.QueryOne("SELECT id FROM pet_achievements WHERE couple_id = ? AND achievement_key = 'first_adopt'", coupleID); fa == nil {
|
||
c.DB.ExecNoFK(func(q store.Querier) error {
|
||
q.Exec("INSERT IGNORE INTO pet_achievements (couple_id, achievement_key) VALUES (?, 'first_adopt')", coupleID)
|
||
q.Exec("INSERT INTO pet_dynamics (couple_id, user_id, action, message) VALUES (?, 0, 'achievement', ?)", coupleID, "解锁成就:初次相遇 🐣")
|
||
return nil
|
||
})
|
||
}
|
||
daily, _ := c.DB.QueryOne("SELECT * FROM pet_dailies WHERE couple_id = ? AND date = ?", coupleID, today())
|
||
achs, _ := c.DB.Query("SELECT achievement_key, unlocked_at FROM pet_achievements WHERE couple_id = ?", coupleID)
|
||
achKeys := []any{}
|
||
for _, a := range achs {
|
||
achKeys = append(achKeys, a["achievement_key"])
|
||
}
|
||
equipped, _ := c.DB.Query("SELECT item_key, item_type FROM pet_items WHERE couple_id = ? AND equipped = 1", coupleID)
|
||
dynamics, _ := c.DB.Query("SELECT pd.*, u.nickname FROM pet_dynamics pd LEFT JOIN users u ON pd.user_id = u.id WHERE pd.couple_id = ? ORDER BY pd.created_at DESC LIMIT 20", coupleID)
|
||
u1raw, u2raw := coupleUserIDsRaw(c, coupleID)
|
||
u1, u2 := store.ToInt(u1raw), store.ToInt(u2raw)
|
||
expForNext := pet.Int("level") * 100
|
||
var expProgress any = 0
|
||
if expForNext > 0 {
|
||
p := math.Round(float64(pet.Int("exp")) / float64(expForNext) * 100)
|
||
if p > 100 {
|
||
p = 100
|
||
}
|
||
expProgress = p
|
||
}
|
||
counts := M{"feed": 0, "walk": 0, "bath": 0, "play": 0}
|
||
if ac, _ := c.DB.QueryOne("SELECT SUM(feed_u1 + feed_u2) as feed_cnt, SUM(walk_u1 + walk_u2) as walk_cnt, SUM(bath_u1 + bath_u2) as bath_cnt, SUM(play_u1 + play_u2) as play_cnt FROM pet_dailies WHERE couple_id = ?", coupleID); ac != nil {
|
||
counts = M{"feed": ac.Int("feed_cnt"), "walk": ac.Int("walk_cnt"), "bath": ac.Int("bath_cnt"), "play": ac.Int("play_cnt")}
|
||
}
|
||
done, _ := c.DB.Query("SELECT date, all_done FROM pet_dailies WHERE couple_id = ? AND all_done = 1 ORDER BY date DESC", coupleID)
|
||
var dailyOut any
|
||
if daily != nil {
|
||
dailyOut = daily
|
||
}
|
||
return c.OK(M{
|
||
"has_pet": true, "pet": pet, "daily": dailyOut, "achievements": achKeys, "equipped": rows(equipped), "dynamics": rows(dynamics),
|
||
"user1_id": u1raw, "user2_id": u2raw, "is_user1": uid == u1, "is_user2": uid == u2,
|
||
"exp_for_next_level": expForNext, "exp_progress": expProgress, "action_counts": counts,
|
||
"consecutive_done_days": consecutiveDoneDays(done),
|
||
})
|
||
}
|
||
|
||
func petAdopt(c *httpapi.Ctx) error {
|
||
uid := c.UID()
|
||
coupleID, err := requireCoupleID(c)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
name := c.InTrim("name")
|
||
if !c.InHas("name") {
|
||
name = "小狗狗"
|
||
}
|
||
breed := c.InTrim("breed")
|
||
if !c.InHas("breed") {
|
||
breed = "golden"
|
||
}
|
||
gender := int64(1)
|
||
if c.InHas("gender") {
|
||
gender = c.InInt("gender")
|
||
}
|
||
switch breed {
|
||
case "golden", "husky", "corgi", "shiba":
|
||
default:
|
||
breed = "golden"
|
||
}
|
||
if ex, _ := c.DB.QueryOne("SELECT id FROM pets WHERE couple_id = ?", coupleID); ex != nil {
|
||
return c.Err(400, "已领养过宠物")
|
||
}
|
||
petID, err := c.DB.Insert("INSERT INTO pets (couple_id, name, breed, gender, stage, level, exp, happiness, hunger, energy, cleanliness, adopted_at, created_by) VALUES (?, ?, ?, ?, 1, 1, 0, 100, 100, 100, 100, ?, ?)",
|
||
coupleID, name, breed, gender, today(), uid)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
c.DB.Exec("INSERT INTO pet_dynamics (couple_id, user_id, action, message) VALUES (?, ?, 'adopt', ?)", coupleID, uid, "领养了"+name+"!🎉")
|
||
actID := logActivity(c.DB, coupleID, uid, "pet_adopt", "领养了宠物"+name, "")
|
||
c.DB.Exec("INSERT INTO pet_items (couple_id, item_key, item_type, quantity, equipped) VALUES (?, 'collar_basic', 'collar', 1, 1)", coupleID)
|
||
checkPetAchievements(c, coupleID, 1, 1, 100, false)
|
||
if partnerID := partnerUserId(c.DB, uid); partnerID != 0 {
|
||
// 使用活动ID记录发送事件,供发送日志和去重查询使用。
|
||
c.App.Wechat.SendSubscribeMessage(partnerID, "notify", map[string]string{
|
||
"thing1": "领养了新宠物 🐾", "thing2": "TA领养了「" + name + "」,快来一起照顾吧", "date3": today(), "thing4": "点击查看宠物详情 💕",
|
||
}, "pages/pet/pet", actID, uid)
|
||
}
|
||
return c.OK(M{"success": true, "pet_id": fmt.Sprint(petID)})
|
||
}
|
||
|
||
var petActionEffects = map[string]map[string]int64{
|
||
"feed": {"exp": 10, "happiness": 3, "hunger": 15},
|
||
"water": {"exp": 8, "happiness": 2, "energy": 5},
|
||
"walk": {"exp": 15, "happiness": 8, "energy": -10, "cleanliness": -5},
|
||
"bath": {"exp": 12, "happiness": 5, "cleanliness": 20, "energy": -5},
|
||
"sleep": {"exp": 5, "energy": 20, "happiness": 3},
|
||
"play": {"exp": 12, "happiness": 10, "energy": -8},
|
||
}
|
||
|
||
var petActionMessages = map[string]string{
|
||
"feed": "喂食了宠物 🍖", "water": "给宠物喝了水 💧", "walk": "带宠物去散步 🐾",
|
||
"bath": "给宠物洗了澡 🛁", "sleep": "哄宠物睡觉了 😴", "play": "和宠物玩耍了 🎾",
|
||
}
|
||
|
||
var petDailyFields = []string{"feed_u1", "feed_u2", "water_u1", "water_u2", "walk_u1", "walk_u2", "bath_u1", "bath_u2", "sleep_u1", "sleep_u2", "play_u1", "play_u2"}
|
||
|
||
func clamp100(v int64) int64 {
|
||
if v < 0 {
|
||
return 0
|
||
}
|
||
if v > 100 {
|
||
return 100
|
||
}
|
||
return v
|
||
}
|
||
|
||
func petDailyAction(c *httpapi.Ctx) error {
|
||
uid := c.UID()
|
||
coupleID, err := requireCoupleID(c)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
action := c.InTrim("action")
|
||
effects, ok := petActionEffects[action]
|
||
if !ok {
|
||
return c.Err(400, "无效的动作")
|
||
}
|
||
pet, _ := c.DB.QueryOne("SELECT * FROM pets WHERE couple_id = ?", coupleID)
|
||
if pet == nil {
|
||
return c.Err(400, "请先领养宠物")
|
||
}
|
||
u1, _ := coupleUserIDsOrdered(c, coupleID)
|
||
suffix := "u2"
|
||
if uid == u1 {
|
||
suffix = "u1"
|
||
}
|
||
td := today()
|
||
daily, _ := c.DB.QueryOne("SELECT * FROM pet_dailies WHERE couple_id = ? AND date = ?", coupleID, td)
|
||
if daily == nil {
|
||
if _, err := c.DB.Exec("INSERT INTO pet_dailies (couple_id, date) VALUES (?, ?)", coupleID, td); err != nil {
|
||
return err
|
||
}
|
||
daily, _ = c.DB.QueryOne("SELECT * FROM pet_dailies WHERE couple_id = ? AND date = ?", coupleID, td)
|
||
}
|
||
field := action + "_" + suffix
|
||
if daily.Int(field) == 1 {
|
||
return c.JSON(400, M{"error": "你今天已完成此任务", "already_done": true})
|
||
}
|
||
if _, err := c.DB.Exec("UPDATE pet_dailies SET "+field+" = 1 WHERE id = ?", daily.Int("id")); err != nil {
|
||
return err
|
||
}
|
||
allDone := true
|
||
for _, f := range petDailyFields {
|
||
if daily.Int(f) == 0 && f != field {
|
||
row, _ := c.DB.QueryOne("SELECT "+f+" FROM pet_dailies WHERE id = ?", daily.Int("id"))
|
||
if row == nil || row.Int(f) == 0 {
|
||
allDone = false
|
||
break
|
||
}
|
||
}
|
||
}
|
||
newHappiness := clamp100(pet.Int("happiness") + effects["happiness"])
|
||
newHunger := clamp100(pet.Int("hunger") + effects["hunger"])
|
||
newEnergy := clamp100(pet.Int("energy") + effects["energy"])
|
||
newClean := clamp100(pet.Int("cleanliness") + effects["cleanliness"])
|
||
newExp := pet.Int("exp") + effects["exp"]
|
||
newLevel := pet.Int("level")
|
||
// PHP: $newLevel = $pet['level'] 为行值(8.0 下是字符串),只有 ++ 后才变为整数;$newStage 同理
|
||
var levelOut, stageOut any = pet["level"], pet["stage"]
|
||
expNeeded := newLevel * 100
|
||
for newExp >= expNeeded {
|
||
newExp -= expNeeded
|
||
newLevel++
|
||
levelOut = newLevel
|
||
expNeeded = newLevel * 100
|
||
}
|
||
newStage := pet.Int("stage")
|
||
switch {
|
||
case newLevel >= 30:
|
||
newStage = 5
|
||
case newLevel >= 20:
|
||
newStage = 4
|
||
case newLevel >= 12:
|
||
newStage = 3
|
||
case newLevel >= 5:
|
||
newStage = 2
|
||
}
|
||
if newStage != pet.Int("stage") || newLevel >= 5 {
|
||
stageOut = newStage
|
||
}
|
||
if _, err := c.DB.Exec("UPDATE pets SET happiness = ?, hunger = ?, energy = ?, cleanliness = ?, exp = ?, level = ?, stage = ?, updated_at = NOW() WHERE id = ?",
|
||
newHappiness, newHunger, newEnergy, newClean, newExp, newLevel, newStage, pet.Int("id")); err != nil {
|
||
return err
|
||
}
|
||
msg := petActionMessages[action]
|
||
c.DB.Exec("INSERT INTO pet_dynamics (couple_id, user_id, action, message) VALUES (?, ?, ?, ?)", coupleID, uid, action, msg)
|
||
if allDone {
|
||
c.DB.Exec("UPDATE pet_dailies SET all_done = 1 WHERE id = ?", daily.Int("id"))
|
||
c.DB.Exec("UPDATE pets SET exp = exp + 20, happiness = happiness + 10, updated_at = NOW() WHERE id = ?", pet.Int("id"))
|
||
c.DB.Exec("INSERT INTO pet_dynamics (couple_id, user_id, action, message) VALUES (?, ?, 'all_done', '今日所有任务完成!太棒了 🎉')", coupleID, uid)
|
||
logActivity(c.DB, coupleID, uid, "pet_all_done", "今日宠物所有任务完成!", "")
|
||
}
|
||
checkPetAchievements(c, coupleID, newLevel, newStage, newHappiness, false)
|
||
actID := logActivity(c.DB, coupleID, uid, "pet_daily", msg, "")
|
||
if partnerID := partnerUserId(c.DB, uid); partnerID != 0 {
|
||
notifyMsg := msg
|
||
if allDone {
|
||
notifyMsg = msg + ",今日所有任务完成!🎉"
|
||
}
|
||
c.App.Wechat.SendSubscribeMessage(partnerID, "notify", map[string]string{
|
||
"thing1": "宠物日常 🐾", "thing2": notifyMsg, "date3": td, "thing4": "点击查看宠物详情 💕",
|
||
}, "pages/pet/pet", actID, uid)
|
||
}
|
||
return c.OK(M{
|
||
"success": true, "exp_gain": effects["exp"], "happiness": newHappiness, "hunger": newHunger, "energy": newEnergy,
|
||
"cleanliness": newClean, "level": levelOut, "stage": stageOut, "all_done": allDone,
|
||
})
|
||
}
|
||
|
||
func petDailyStatus(c *httpapi.Ctx) error {
|
||
uid := c.UID()
|
||
coupleID, err := requireCoupleID(c)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
date := c.Q("date")
|
||
if !c.QHas("date") {
|
||
date = today()
|
||
}
|
||
daily, err := c.DB.QueryOne("SELECT * FROM pet_dailies WHERE couple_id = ? AND date = ?", coupleID, date)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
u1, _ := coupleUserIDsOrdered(c, coupleID)
|
||
suffix, partnerSuffix := "u2", "u1"
|
||
if uid == u1 {
|
||
suffix, partnerSuffix = "u1", "u2"
|
||
}
|
||
var myDone, partnerDone any = []any{}, []any{}
|
||
var allDone any = false
|
||
if daily != nil {
|
||
md, pd := M{}, M{}
|
||
for _, a := range []string{"feed", "water", "walk", "bath", "sleep", "play"} {
|
||
md[a] = daily.Int(a+"_"+suffix) == 1
|
||
pd[a] = daily.Int(a+"_"+partnerSuffix) == 1
|
||
}
|
||
myDone, partnerDone = md, pd
|
||
allDone = daily["all_done"]
|
||
}
|
||
return c.OK(M{"date": date, "has_record": daily != nil, "my_done": myDone, "partner_done": partnerDone, "all_done": allDone})
|
||
}
|
||
|
||
func petAchievements(c *httpapi.Ctx) error {
|
||
coupleID, err := requireCoupleID(c)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
unlocked, err := c.DB.Query("SELECT achievement_key, unlocked_at FROM pet_achievements WHERE couple_id = ? ORDER BY unlocked_at ASC", coupleID)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
at := map[string]any{}
|
||
for _, u := range unlocked {
|
||
k := u.Str("achievement_key")
|
||
if _, seen := at[k]; !seen {
|
||
at[k] = u["unlocked_at"]
|
||
}
|
||
}
|
||
result := make([]M, 0, len(allPetAchievements))
|
||
for _, a := range allPetAchievements {
|
||
ua, ok := at[a.Key]
|
||
if !ok {
|
||
ua = nil
|
||
}
|
||
result = append(result, M{"key": a.Key, "name": a.Name, "desc": a.Desc, "icon": a.Icon, "unlocked": ok, "unlocked_at": ua})
|
||
}
|
||
return c.OK(M{"achievements": result})
|
||
}
|
||
|
||
func petRename(c *httpapi.Ctx) error {
|
||
uid := c.UID()
|
||
coupleID, err := requireCoupleID(c)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
name := c.InTrim("name")
|
||
if !store.Truthy(name) || len([]rune(name)) > 20 {
|
||
return c.Err(400, "名称长度1-20个字符")
|
||
}
|
||
if _, err := c.DB.Exec("UPDATE pets SET name = ?, updated_at = NOW() WHERE couple_id = ?", name, coupleID); err != nil {
|
||
return err
|
||
}
|
||
c.DB.Exec("INSERT INTO pet_dynamics (couple_id, user_id, action, message) VALUES (?, ?, 'rename', ?)", coupleID, uid, "给宠物改名为"+name+" ✏️")
|
||
actID := logActivity(c.DB, coupleID, uid, "pet_rename", "给宠物改名为"+name, "")
|
||
info, _ := c.DB.QueryOne("SELECT level, stage, happiness FROM pets WHERE couple_id = ?", coupleID)
|
||
var level, stage, happiness int64
|
||
if info != nil {
|
||
level, stage, happiness = info.Int("level"), info.Int("stage"), info.Int("happiness")
|
||
}
|
||
checkPetAchievements(c, coupleID, level, stage, happiness, true)
|
||
if partnerID := partnerUserId(c.DB, uid); partnerID != 0 {
|
||
c.App.Wechat.SendSubscribeMessage(partnerID, "notify", map[string]string{
|
||
"thing1": "宠物改名 ✏️", "thing2": "TA给宠物改名为「" + name + "」", "date3": today(), "thing4": "点击查看新名字 💕",
|
||
}, "pages/pet/pet", actID, uid)
|
||
}
|
||
return c.OK(M{"success": true, "name": name})
|
||
}
|
||
|
||
func petEquip(c *httpapi.Ctx) error {
|
||
coupleID, err := requireCoupleID(c)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
itemKey := c.InTrim("item_key")
|
||
equip := int64(1)
|
||
if c.InHas("equip") {
|
||
equip = c.InInt("equip")
|
||
}
|
||
if !store.Truthy(itemKey) {
|
||
return c.Err(400, "缺少物品标识")
|
||
}
|
||
item, _ := c.DB.QueryOne("SELECT * FROM pet_items WHERE couple_id = ? AND item_key = ?", coupleID, itemKey)
|
||
if item == nil {
|
||
return c.Err(400, "物品不存在")
|
||
}
|
||
if equip != 0 {
|
||
c.DB.Exec("UPDATE pet_items SET equipped = 0 WHERE couple_id = ? AND item_type = ? AND item_key != ?", coupleID, item["item_type"], itemKey)
|
||
c.DB.Exec("UPDATE pet_items SET equipped = 1 WHERE couple_id = ? AND item_key = ?", coupleID, itemKey)
|
||
} else {
|
||
c.DB.Exec("UPDATE pet_items SET equipped = 0 WHERE couple_id = ? AND item_key = ?", coupleID, itemKey)
|
||
}
|
||
return c.OK(M{"success": true, "equipped": equip == 1})
|
||
}
|
||
|
||
func petDynamics(c *httpapi.Ctx) error {
|
||
coupleID, err := requireCoupleID(c)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
page := int64(1)
|
||
if c.QHas("page") {
|
||
page = c.QInt("page")
|
||
}
|
||
limit := int64(20)
|
||
offset := (page - 1) * limit
|
||
dynamics, err := c.DB.Query("SELECT pd.*, u.nickname, u.avatar FROM pet_dynamics pd LEFT JOIN users u ON pd.user_id = u.id WHERE pd.couple_id = ? ORDER BY pd.created_at DESC LIMIT ? OFFSET ?", coupleID, limit, offset)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
totalRaw := countRaw(c, "total", "SELECT COUNT(*) as total FROM pet_dynamics WHERE couple_id = ?", coupleID)
|
||
total := store.ToInt(totalRaw)
|
||
return c.OK(M{"dynamics": rows(dynamics), "total": totalRaw, "page": page, "has_more": (offset + limit) < total})
|
||
}
|