package model import ( "fmt" "strings" ) // EnglishAdventureLogFallback returns a readable English line for SQL/admin when Message is empty. // Keep roughly aligned with frontend adventureLogFormat.ts (EN branch). Sync new event codes here. func EnglishAdventureLogFallback(ev *AdventureLogEvent) string { if ev == nil { return "" } a := ev.Args switch ev.Code { case LogCombatSwing: return englishCombatSwing(a) case LogDefeatedEnemy: return fmt.Sprintf("Defeated %s (+%v XP, +%v gold).", englishEnemyLogName(a), logArgFloat(a, "xp"), logArgFloat(a, "gold")) case LogLeveledUp: return fmt.Sprintf("Reached level %d!", logArgInt(a, "level")) case LogEquippedNew: return fmt.Sprintf("Equipped new %s: %s.", englishSlotName(logArgStr(a, "slot")), logArgStr(a, "itemName")) case LogInventoryFullDropped: return fmt.Sprintf("Inventory full — dropped %s (%s).", logArgStr(a, "itemName"), englishRarityName(logArgStr(a, "rarity"))) case LogBuffActivated: return fmt.Sprintf("%s activated.", englishBuffName(logArgStr(a, "buffType"))) case LogHeroRevived: return "You revived." case LogWanderingMerchant: return "A hooded merchant blocks your path, jingling a pouch of odd trinkets." case LogEncounteredEnemy: return fmt.Sprintf("You encounter %s.", englishEnemyLogName(a)) case LogDiedFighting: return fmt.Sprintf("You died fighting %s.", englishEnemyLogName(a)) case LogAutoReviveHours: return "Hours passed; you revived in town." case LogAutoReviveAfterSec: return fmt.Sprintf("Auto-revived after %ds offline.", logArgInt(a, "seconds")) case LogPurchasedBuffRefill: return fmt.Sprintf("Refilled charges: %s.", englishBuffName(logArgStr(a, "buffType"))) case LogPurchasedBuffRefillRub: return fmt.Sprintf("Purchased refill for %s (%d RUB).", englishBuffName(logArgStr(a, "buffType")), logArgInt(a, "priceRub")) case LogSubscribed: dk := logArgStr(a, "durationKey") price := logArgInt(a, "priceRub") dur := dk if dk == "subscription.week" { dur = "one week of subscription" } return fmt.Sprintf("Subscribed: %s (%d RUB).", dur, price) case LogUsedHealingPotion: return fmt.Sprintf("Used healing potion (+%d HP).", logArgInt(a, "amount")) case LogAchievementUnlocked: title := logArgStr(a, "title") rt := logArgStr(a, "rewardType") ra := logArgInt(a, "rewardAmount") switch rt { case "gold": return fmt.Sprintf("Achievement: %s (+%d gold).", title, ra) case "potion": return fmt.Sprintf("Achievement: %s (+%d potions).", title, ra) default: return fmt.Sprintf("Achievement: %s.", title) } case LogMetNPC: return fmt.Sprintf("Met %s in %s.", logArgStr(a, "npcKey"), logArgStr(a, "townKey")) case LogWanderingAlmsEquipped: return fmt.Sprintf("Equipped from the merchant: %s.", logArgStr(a, "itemName")) case LogWanderingAlmsDropped: return fmt.Sprintf("Dropped %s (%s) — no room.", logArgStr(a, "itemName"), englishRarityName(logArgStr(a, "rarity"))) case LogWanderingAlmsStashed: return fmt.Sprintf("Stashed %s in your inventory.", logArgStr(a, "itemName")) case LogHealedFullTown: return "Paid for a full heal." case LogBoughtPotionTown: return "Bought a potion in town." case LogSoldItemsMerchant: return fmt.Sprintf("Sold %d items to %s (+%v gold).", logArgInt(a, "count"), logArgStr(a, "npcKey"), logArgFloat(a, "gold")) case LogNPCSkippedVisit: return fmt.Sprintf("Skipped visiting %s.", logArgStr(a, "npcKey")) case LogThoughtRoadside: idx := logArgInt(a, "idx") return fmt.Sprintf("Roadside thought (%d).", idx) case LogPurchasedPotionFromNPC: return fmt.Sprintf("Bought a potion from %s.", logArgStr(a, "npcKey")) case LogPaidHealerFull: return fmt.Sprintf("Paid %s for a full heal.", logArgStr(a, "npcKey")) case LogQuestGiverChecked: return fmt.Sprintf("Checked in with %s — no new quests.", logArgStr(a, "npcKey")) case LogQuestAccepted: return fmt.Sprintf("Accepted quest: %s.", logArgStr(a, "title")) case LogTownNPCVisitLine: npcType := logArgStr(a, "npcType") line := logArgInt(a, "line") return fmt.Sprintf("Town visit (%s): beat %d/6.", npcType, line+1) default: if ev.Code != "" { return fmt.Sprintf("[%s]", ev.Code) } return "" } } func englishCombatSwing(a map[string]any) string { source := logArgStr(a, "source") outcome := logArgStr(a, "outcome") damage := logArgInt(a, "damage") isCrit := logArgBool(a, "isCrit") enemyName := englishEnemyLogName(a) critSuffix := "" if isCrit { critSuffix = " (crit)" } var msg string switch source { case "hero": switch outcome { case "stun": msg = "You are stunned and cannot attack." case "dodge": msg = enemyName + " dodged your attack." default: msg = fmt.Sprintf("You hit %s for %d damage%s.", enemyName, damage, critSuffix) } case "enemy": switch outcome { case "block": msg = fmt.Sprintf("You block %s's attack.", enemyName) default: msg = fmt.Sprintf("%s hits you for %d damage%s.", enemyName, damage, critSuffix) } } debuff := logArgStr(a, "debuffType") if debuff != "" { msg += " " + englishDebuffName(debuff) + " applied." } return msg } // englishEnemyLogName prefers arg enemyName (DB) when present; else template name from slug. func englishEnemyLogName(a map[string]any) string { if a == nil { return "enemy" } if n := strings.TrimSpace(logArgStr(a, "enemyName")); n != "" { return n } return englishEnemyDisplayName(logArgStr(a, "enemyType")) } func englishEnemyDisplayName(slug string) string { slug = strings.TrimSpace(slug) if slug == "" { return "enemy" } if e, ok := EnemyBySlug(slug); ok && strings.TrimSpace(e.Name) != "" { return e.Name } return slug } func englishDebuffName(debuffType string) string { dt, ok := ValidDebuffType(debuffType) if !ok { return debuffType } if def, ok := DebuffDefinition(dt); ok && def.Name != "" { return def.Name } return debuffType } func englishBuffName(raw string) string { raw = strings.ToLower(strings.TrimSpace(raw)) bt, ok := ValidBuffType(raw) if !ok { return raw } if b, ok := BuffDefinition(bt); ok && b.Name != "" { return b.Name } return raw } func englishSlotName(raw string) string { raw = strings.ToLower(strings.TrimSpace(raw)) m := map[string]string{ "main_hand": "weapon", "off_hand": "off-hand", "head": "head", "chest": "chest", "legs": "legs", "feet": "feet", "cloak": "cloak", "neck": "neck", "finger": "ring", "wrist": "wrist", "hands": "hands", "quiver": "quiver", } if s, ok := m[raw]; ok { return s } return raw } func englishRarityName(raw string) string { raw = strings.ToLower(strings.TrimSpace(raw)) m := map[string]string{ "common": "common", "uncommon": "uncommon", "rare": "rare", "epic": "epic", "legendary": "legendary", } if s, ok := m[raw]; ok { return s } return raw } func logArgStr(a map[string]any, key string) string { if a == nil { return "" } v, ok := a[key] if !ok || v == nil { return "" } switch x := v.(type) { case string: return x case fmt.Stringer: return x.String() default: return fmt.Sprint(x) } } func logArgFloat(a map[string]any, key string) float64 { if a == nil { return 0 } v, ok := a[key] if !ok || v == nil { return 0 } switch x := v.(type) { case float64: return x case float32: return float64(x) case int: return float64(x) case int64: return float64(x) case uint64: return float64(x) default: return 0 } } func logArgInt(a map[string]any, key string) int { return int(logArgFloat(a, key)) } func logArgBool(a map[string]any, key string) bool { if a == nil { return false } v, ok := a[key] if !ok || v == nil { return false } switch x := v.(type) { case bool: return x case string: return strings.EqualFold(x, "true") || x == "1" default: return false } }