mirror of
https://git.ethanthesleepy.one/ethanaobrien/ew
synced 2026-08-26 15:02:18 +08:00
Add custom card handling probably
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
use jzon::{array, object, JsonValue};
|
||||
use lazy_static::lazy_static;
|
||||
use std::collections::HashMap;
|
||||
use crate::router::userdata;
|
||||
use crate::router::global;
|
||||
use crate::router::{card, custom_card, databases};
|
||||
|
||||
fn get_clear_count(user: &JsonValue, level: i32) -> i64 {
|
||||
let mut rv = 0;
|
||||
@@ -67,34 +70,66 @@ pub enum UserView {
|
||||
|
||||
const DEFAULT_CARD: i64 = 10010001;
|
||||
|
||||
fn proxy_card_id(id: i64) -> i64 {
|
||||
let prefix = id / 10000;
|
||||
if prefix < 10000 {
|
||||
return id;
|
||||
}
|
||||
let rv = if prefix < 14000 {
|
||||
(prefix - 9000) * 10000 + 1
|
||||
} else {
|
||||
DEFAULT_CARD
|
||||
lazy_static! {
|
||||
// Each character's lowest official card id: the stand-in shown to viewers
|
||||
// who can't resolve a custom card of that character
|
||||
static ref OFFICIAL_CARD_BY_CHARACTER: HashMap<i64, i64> = {
|
||||
let mut rv: HashMap<i64, i64> = HashMap::new();
|
||||
for entry in databases::CARD_LIST.entries() {
|
||||
let Some(id) = entry.1["id"].as_i64() else { continue; };
|
||||
if card::is_custom(id) {
|
||||
continue;
|
||||
}
|
||||
let Some(character) = entry.1["masterCharacterId"].as_i64() else { continue; };
|
||||
let slot = rv.entry(character).or_insert(id);
|
||||
if id < *slot {
|
||||
*slot = id;
|
||||
}
|
||||
}
|
||||
rv
|
||||
};
|
||||
// Not every prefix has a real character behind it
|
||||
if crate::router::databases::CARD_LIST[rv.to_string()].is_empty() {
|
||||
return DEFAULT_CARD;
|
||||
}
|
||||
rv
|
||||
}
|
||||
|
||||
pub fn proxy_user_cards(user: &mut JsonValue) {
|
||||
// The character behind any card id: baked masterdata first, then the runtime
|
||||
// custom-card db, then the imported band's id arithmetic (prefix - 9000)
|
||||
fn card_character(id: i64) -> Option<i64> {
|
||||
let card = &databases::CARD_LIST[id.to_string()];
|
||||
if !card.is_empty() {
|
||||
return card["masterCharacterId"].as_i64();
|
||||
}
|
||||
if custom_card::is_custom_runtime(id) {
|
||||
return custom_card::character_of(id);
|
||||
}
|
||||
Some(id / 10000 - 9000)
|
||||
}
|
||||
|
||||
// A custom card the viewer can't resolve shows as its character's base
|
||||
// official card - the right face, never a crash. Characters with no official
|
||||
// card (custom ones included) fall back to the default
|
||||
fn proxy_card_id(id: i64) -> i64 {
|
||||
if !card::is_custom(id) {
|
||||
return id;
|
||||
}
|
||||
let Some(character) = card_character(id) else {
|
||||
return DEFAULT_CARD;
|
||||
};
|
||||
let Some(rv) = OFFICIAL_CARD_BY_CHARACTER.get(&character) else {
|
||||
return DEFAULT_CARD;
|
||||
};
|
||||
*rv
|
||||
}
|
||||
|
||||
pub fn proxy_user_cards(user: &mut JsonValue, protocol: u32) {
|
||||
for key in ["favorite_master_card_id", "guest_smile_master_card_id", "guest_cool_master_card_id", "guest_pure_master_card_id"] {
|
||||
let id = user["user"][key].as_i64().unwrap_or(0);
|
||||
if crate::router::card::is_custom(id) {
|
||||
if !custom_card::viewer_can_resolve(id, protocol) {
|
||||
user["user"][key] = proxy_card_id(id).into();
|
||||
}
|
||||
}
|
||||
// A card's id is its master_card_id, so both sides need proxying
|
||||
for key in ["favorite_card", "guest_smile_card", "guest_cool_card", "guest_pure_card"] {
|
||||
let id = user[key]["master_card_id"].as_i64().unwrap_or(0);
|
||||
if crate::router::card::is_custom(id) {
|
||||
if !custom_card::viewer_can_resolve(id, protocol) {
|
||||
user[key]["id"] = proxy_card_id(id).into();
|
||||
user[key]["master_card_id"] = proxy_card_id(id).into();
|
||||
}
|
||||
@@ -102,7 +137,8 @@ pub fn proxy_user_cards(user: &mut JsonValue) {
|
||||
if !user["main_deck_detail"].is_empty() {
|
||||
let mut used = array![];
|
||||
for id in user["main_deck_detail"]["deck"]["main_card_ids"].members_mut() {
|
||||
let card = proxy_card_id(id.as_i64().unwrap_or(0));
|
||||
let raw = id.as_i64().unwrap_or(0);
|
||||
let card = if custom_card::viewer_can_resolve(raw, protocol) { raw } else { proxy_card_id(raw) };
|
||||
// Whole characters share one proxy, and the client can't hold the
|
||||
// same card twice
|
||||
if card == 0 || used.contains(card) {
|
||||
@@ -116,7 +152,7 @@ pub fn proxy_user_cards(user: &mut JsonValue) {
|
||||
let mut ids = array![];
|
||||
for card in user["main_deck_detail"]["card_list"].members() {
|
||||
let id = card["master_card_id"].as_i64().unwrap_or(0);
|
||||
let proxy = proxy_card_id(id);
|
||||
let proxy = if custom_card::viewer_can_resolve(id, protocol) { id } else { proxy_card_id(id) };
|
||||
if ids.contains(proxy) {
|
||||
continue;
|
||||
}
|
||||
@@ -132,7 +168,7 @@ pub fn proxy_user_cards(user: &mut JsonValue) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_user(id: i64, friends: &JsonValue, view: UserView, custom_cards: bool) -> JsonValue {
|
||||
pub fn get_user(id: i64, friends: &JsonValue, view: UserView, protocol: u32) -> JsonValue {
|
||||
let user = userdata::get_acc_from_uid(id);
|
||||
if !user["error"].is_empty() {
|
||||
return object!{};
|
||||
@@ -182,9 +218,124 @@ pub fn get_user(id: i64, friends: &JsonValue, view: UserView, custom_cards: bool
|
||||
}
|
||||
}
|
||||
|
||||
if !custom_cards {
|
||||
proxy_user_cards(&mut rv);
|
||||
}
|
||||
proxy_user_cards(&mut rv, protocol);
|
||||
|
||||
rv
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::database::custom_card as db;
|
||||
|
||||
fn wipe(owner: i64) {
|
||||
for card in db::get_cards_by_owner(owner).members() {
|
||||
db::delete_card(card["master_card_id"].as_i64().unwrap());
|
||||
}
|
||||
for character in db::get_characters_by_owner(owner).members() {
|
||||
db::delete_character(character["master_character_id"].as_i64().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
// The imported band proxies to its own character's base card, not to a
|
||||
// single default (the old prefix arithmetic collapsed 14000+ to Honoka)
|
||||
#[test]
|
||||
fn proxies_resolve_through_the_character() {
|
||||
let _lock = crate::runtime::lock_test_data_path();
|
||||
wipe(5101);
|
||||
|
||||
assert_eq!(proxy_card_id(10010001), 10010001);
|
||||
assert_eq!(proxy_card_id(0), 0);
|
||||
assert_eq!(proxy_card_id(100010001), 10010001);
|
||||
assert_eq!(proxy_card_id(100090001), 10090001);
|
||||
assert_eq!(proxy_card_id(110010001), 20010001);
|
||||
assert_eq!(proxy_card_id(130010001), 40010001);
|
||||
assert_eq!(proxy_card_id(140010001), DEFAULT_CARD);
|
||||
|
||||
// A runtime card on an official character proxies to that character
|
||||
let id = db::next_card_id();
|
||||
db::insert_card(id, 2003, 5101, &jzon::object!{ "master_card_id": id, "rarity": 1 }, true, false);
|
||||
assert_eq!(proxy_card_id(id), 20030001);
|
||||
// On a custom character (no official card) it falls to the default
|
||||
let orphan = db::next_card_id();
|
||||
db::insert_card(orphan, db::FIRST_CHARACTER_ID, 5101, &jzon::object!{ "master_card_id": orphan, "rarity": 1 }, true, false);
|
||||
assert_eq!(proxy_card_id(orphan), DEFAULT_CARD);
|
||||
// A deleted/unknown runtime id can't resolve a character either
|
||||
let unknown = db::next_card_id() + 5000;
|
||||
assert_eq!(proxy_card_id(unknown), DEFAULT_CARD);
|
||||
// A proxy is never zero and always a real row
|
||||
for id in [100010001i64, 141720001, 149990001, id, orphan, unknown] {
|
||||
let proxy = proxy_card_id(id);
|
||||
assert_ne!(proxy, 0, "id {}", id);
|
||||
assert!(!databases::CARD_LIST[proxy.to_string()].is_empty(), "id {}", id);
|
||||
}
|
||||
|
||||
wipe(5101);
|
||||
}
|
||||
|
||||
// Even a protocol-3 viewer gets a proxy for a draft - published is what
|
||||
// makes a runtime card resolvable, and older viewers proxy everything
|
||||
#[test]
|
||||
fn drafts_and_old_viewers_get_proxies() {
|
||||
let _lock = crate::runtime::lock_test_data_path();
|
||||
wipe(5102);
|
||||
|
||||
let published = db::next_card_id();
|
||||
db::insert_card(published, 2003, 5102, &jzon::object!{ "master_card_id": published, "rarity": 1 }, true, false);
|
||||
let draft = db::next_card_id();
|
||||
db::insert_card(draft, 2003, 5102, &jzon::object!{ "master_card_id": draft, "rarity": 1 }, false, false);
|
||||
|
||||
assert!(custom_card::viewer_can_resolve(published, custom_card::PROTOCOL_VERSION));
|
||||
assert!(!custom_card::viewer_can_resolve(draft, custom_card::PROTOCOL_VERSION));
|
||||
|
||||
let mut user = jzon::object!{
|
||||
"user": {
|
||||
"favorite_master_card_id": draft,
|
||||
"guest_smile_master_card_id": published
|
||||
},
|
||||
"favorite_card": { "id": draft, "master_card_id": draft },
|
||||
"guest_smile_card": { "id": published, "master_card_id": published },
|
||||
"main_deck_detail": {
|
||||
"deck": { "main_card_ids": [draft, published, 10010001, 0, 0] },
|
||||
"card_list": [
|
||||
{ "id": draft, "master_card_id": draft },
|
||||
{ "id": published, "master_card_id": published },
|
||||
{ "id": 10010001, "master_card_id": 10010001 }
|
||||
]
|
||||
}
|
||||
};
|
||||
proxy_user_cards(&mut user, custom_card::PROTOCOL_VERSION);
|
||||
assert_eq!(user["user"]["favorite_master_card_id"].as_i64(), Some(20030001));
|
||||
assert_eq!(user["user"]["guest_smile_master_card_id"].as_i64(), Some(published));
|
||||
assert_eq!(user["favorite_card"]["master_card_id"].as_i64(), Some(20030001));
|
||||
assert_eq!(user["guest_smile_card"]["master_card_id"].as_i64(), Some(published));
|
||||
assert_eq!(user["main_deck_detail"]["deck"]["main_card_ids"][0].as_i64(), Some(20030001));
|
||||
assert_eq!(user["main_deck_detail"]["deck"]["main_card_ids"][1].as_i64(), Some(published));
|
||||
assert_eq!(user["main_deck_detail"]["deck"]["main_card_ids"][2].as_i64(), Some(10010001));
|
||||
assert_eq!(user["main_deck_detail"]["card_list"].len(), 3);
|
||||
for card in user["main_deck_detail"]["card_list"].members() {
|
||||
assert_ne!(card["master_card_id"].as_i64(), Some(draft));
|
||||
}
|
||||
|
||||
// Protocol 0-2 viewers proxy even the published runtime card
|
||||
for protocol in [0u32, 1, 2] {
|
||||
let mut user = jzon::object!{
|
||||
"user": { "favorite_master_card_id": published },
|
||||
"favorite_card": { "id": published, "master_card_id": published }
|
||||
};
|
||||
proxy_user_cards(&mut user, protocol);
|
||||
assert_eq!(user["user"]["favorite_master_card_id"].as_i64(), Some(20030001), "protocol {}", protocol);
|
||||
}
|
||||
// The baked import band needs only protocol 2
|
||||
let mut user = jzon::object!{
|
||||
"user": { "favorite_master_card_id": 100010001 },
|
||||
"favorite_card": { "id": 100010001, "master_card_id": 100010001 }
|
||||
};
|
||||
proxy_user_cards(&mut user, 2);
|
||||
assert_eq!(user["user"]["favorite_master_card_id"].as_i64(), Some(100010001));
|
||||
proxy_user_cards(&mut user, 1);
|
||||
assert_eq!(user["user"]["favorite_master_card_id"].as_i64(), Some(10010001));
|
||||
|
||||
wipe(5102);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user