use jzon::{array, object, JsonValue}; use actix_web::{web, HttpRequest, Responder}; use rand::RngExt; use crate::router::{global, userdata, items, databases, Body, Login, Session, Api}; pub fn routes(cfg: &mut web::ServiceConfig) { cfg.service( web::scope("/lottery") .service(web::resource("").route(web::get().to(lottery)).route(web::post().to(lottery_post))) .route("/get_tutorial", web::post().to(tutorial)) ); } async fn tutorial(Body(body): Body) -> impl Responder { let id = body["master_character_id"].to_string(); let user = &id[id.len() - 2..].parse::().unwrap(); let mut lotteryid = 9110000; if id.starts_with('2') { lotteryid += 9; //muse } else if id.starts_with('3') { lotteryid += 9 + 9; //aquors } else if id.starts_with('4') { lotteryid += 9 + 9 + 12; //nijigasaki } lotteryid += user; Api(Some(object!{ "lottery_list": [ { "master_lottery_id": lotteryid, "master_lottery_price_number": 1, "count": 0, "daily_count": 0, "last_count_date": "" } ], "item_list": [] })) } fn get_card_master_id(lottery_id: String, lottery_number: String) -> Option { databases::CARDS[lottery_id][lottery_number]["value"].as_i64() } fn get_card(lottery_id: String, lottery_number: String) -> JsonValue { databases::CARDS[lottery_id][lottery_number].clone() } fn get_random_card(item: &JsonValue, rv: &mut JsonValue, rng: &mut rand::rngs::ThreadRng) { let lottery_id = item["masterLotteryItemId"].as_i64().unwrap(); let mut random_id = 0; while random_id == 0 { let card = rng.random_range(1..databases::POOL[lottery_id.to_string()][databases::POOL[lottery_id.to_string()].len() - 1].as_i64().unwrap() + 1); if get_card_master_id(lottery_id.to_string(), card.to_string()).is_some() { random_id = card; break; } } let to_push = object!{ "id": get_card_master_id(lottery_id.to_string(), random_id.to_string()).unwrap(), "master_card_id": get_card_master_id(lottery_id.to_string(), random_id.to_string()).unwrap(), "master_lottery_item_id": lottery_id, "master_lottery_item_number": random_id }; rv.push(to_push).unwrap(); } fn get_random_cards(id: i64, mut count: usize) -> JsonValue { let rarity = &databases::RARITY[id.to_string()]; let total_ratio: i64 = rarity.members().map(|item| if item["ensured"].as_i32().unwrap() == 1 { 0 } else { item["ratio"].as_i64().unwrap() }).sum(); let ensured_ratio: i64 = rarity.members().map(|item| if item["ensured"].as_i32().unwrap() == 1 { item["ratio"].as_i64().unwrap() } else { 0 }).sum(); let mut rng = rand::rng(); let mut rv = array![]; if count > 1 && ensured_ratio > 0 { let random_number: i64 = rng.random_range(1..ensured_ratio + 1); let mut cumulative_ratio = 0; for item in rarity.members() { if item["ensured"].as_i32().unwrap() != 1 { continue; } cumulative_ratio += item["ratio"].as_i64().unwrap(); if random_number <= cumulative_ratio { get_random_card(item, &mut rv, &mut rng); count -= 1; break; } } } for _i in 0..count { let random_number: i64 = rng.random_range(1..total_ratio + 1); let mut cumulative_ratio = 0; for item in rarity.members() { if item["ensured"].as_i32().unwrap() == 1 { continue; } cumulative_ratio += item["ratio"].as_i64().unwrap(); if random_number <= cumulative_ratio { get_random_card(item, &mut rv, &mut rng); break; } } } rv } fn lottery_day() -> i64 { (global::timestamp() as i64 + 32400) / 86400 } fn get_draw_count(user: &JsonValue, lottery_id: i64, price_number: i64) -> i64 { for data in user["lottery_list"].members() { if data["master_lottery_id"].as_i64() == Some(lottery_id) && data["master_lottery_price_number"].as_i64() == Some(price_number) { return data["count"].as_i64().unwrap_or(0); } } 0 } fn add_draw_count(user: &mut JsonValue, lottery_id: i64, price_number: i64) { let today = lottery_day(); if !user["lottery_list"].is_array() { user["lottery_list"] = array![]; } for data in user["lottery_list"].members_mut() { if data["master_lottery_id"].as_i64() == Some(lottery_id) && data["master_lottery_price_number"].as_i64() == Some(price_number) { let daily = if data["last_count_date"].as_i64() == Some(today) { data["daily_count"].as_i64().unwrap_or(0) } else { 0 }; data["count"] = (data["count"].as_i64().unwrap_or(0) + 1).into(); data["daily_count"] = (daily + 1).into(); data["last_count_date"] = today.into(); return; } } user["lottery_list"].push(object!{ "master_lottery_id": lottery_id, "master_lottery_price_number": price_number, "count": 1, "daily_count": 1, "last_count_date": today }).unwrap(); } fn is_stepup(lottery_id: i64) -> bool { databases::LOTTERY[lottery_id.to_string()]["type"].as_i64() == Some(2) } fn stepup_step(lottery_id: i64, draws: i64) -> JsonValue { let steps = &databases::STEPUP[lottery_id.to_string()]; let step = draws % steps.len() as i64 + 1; steps.members().find(|n| n["count"].as_i64() == Some(step)).unwrap().clone() } fn get_lottery_list(user: &JsonValue) -> JsonValue { let today = lottery_day(); let mut rv = array![]; for data in user["lottery_list"].members() { let lottery_id = data["master_lottery_id"].as_i64().unwrap_or(0); let price_number = data["master_lottery_price_number"].as_i64().unwrap_or(0); let mut count = data["count"].as_i64().unwrap_or(0); if price_number == 1 && is_stepup(lottery_id) { count += 1; } let daily = if data["last_count_date"].as_i64() == Some(today) { data["daily_count"].as_i64().unwrap_or(0) } else { 0 }; rv.push(object!{ "master_lottery_id": lottery_id, "master_lottery_price_number": price_number, "count": count, "daily_count": daily, "last_count_date": "" }).unwrap(); } for entry in databases::STEPUP.entries() { let lottery_id = entry.0.parse::().unwrap(); if rv.members().any(|data| data["master_lottery_id"].as_i64() == Some(lottery_id) && data["master_lottery_price_number"].as_i64() == Some(1)) { continue; } rv.push(object!{ "master_lottery_id": lottery_id, "master_lottery_price_number": 1, "count": 1, "daily_count": 0, "last_count_date": "" }).unwrap(); } rv } async fn lottery(Login(key): Login) -> impl Responder { let user = userdata::get_acc(&key); Api(Some(object!{ "lottery_list": get_lottery_list(&user) })) } async fn lottery_post(req: HttpRequest, Session { key, body }: Session) -> impl Responder { //println!("lottery: {}", body); let mut user = userdata::get_acc(&key); let user2 = userdata::get_acc(&key); let mut missions = userdata::get_acc_missions(&key); let mut chats = userdata::get_acc_chats(&key); let mut cleared_missions = array![]; let lottery_id = body["master_lottery_id"].as_i64().unwrap(); let price_number = body["master_lottery_price_number"].as_i64().unwrap(); // Custom lotteries live in the 6M band (below TUTORIAL_MIN_LOTTERY_ID; 1-5M/7M/8M official) if (6_000_000..7_000_000).contains(&lottery_id) && !crate::router::card::client_supports_custom_cards(&req) { return global::api_error(&req, global::RESULT_GAME_VERSION_UPDATED); } let lottery = &databases::LOTTERY[lottery_id.to_string()]; let lottery_type = lottery["category"].as_i32().unwrap(); let exchange_id = lottery["exchangeMasterItemId"].as_i64().unwrap_or(0); let (price_id, rarity_id) = if is_stepup(lottery_id) && price_number == 1 { let step = stepup_step(lottery_id, get_draw_count(&user, lottery_id, 1)); (step["masterLotteryPriceId"].as_i64().unwrap(), step["masterLotteryRarityId"].as_i64().unwrap()) } else { (lottery["masterLotteryPriceId"].as_i64().unwrap_or(lottery_id), lottery["masterLotteryRarityId"].as_i64().unwrap_or(lottery_id)) }; let price = databases::PRICE[price_id.to_string()][price_number.to_string()].clone(); items::use_item(&object!{ value: price["masterItemId"].clone(), amount: price["price"].clone(), consumeType: price["consumeType"].clone() }, 1, &mut user); let count = price["count"].as_usize().unwrap(); let cardstogive = get_random_cards(rarity_id, count); let mut new_cards = array![]; let mut lottery_list = array![]; if lottery_type == 1 { for data in cardstogive.members() { let mut is_new = true; if !items::give_character(data["master_card_id"].as_i64().unwrap(), &mut user, &mut missions, &mut cleared_missions, &mut chats) { is_new = false; } if is_new { let to_push = object!{ "id": data["master_card_id"].clone(), "master_card_id": data["master_card_id"].clone(), "exp": 0, "skill_exp": 0, "evolve": [], "created_date_time": global::timestamp() }; new_cards.push(to_push).unwrap(); } let mut to_push = object!{ "master_lottery_item_id": data["master_lottery_item_id"].clone(), "master_lottery_item_number": data["master_lottery_item_number"].clone(), "is_new": if is_new { 1 } else { 0 } }; if !is_new { //given by global::give_character call let character_rarity = items::get_rarity(data["master_card_id"].as_i64().unwrap()); let amount = if character_rarity == 1 { 20 } else if character_rarity == 2 { 50 } else if character_rarity == 3 { 500 } else { 0 }; to_push["exchange_item"] = object!{ master_item_id: 19100001, amount: amount }; } lottery_list.push(to_push).unwrap(); } } else if lottery_type == 2 { for data in cardstogive.members() { let info = get_card(data["master_lottery_item_id"].to_string(), data["master_lottery_item_number"].to_string()); items::give_gift_basic(info["type"].as_i32().unwrap(), info["value"].as_i64().unwrap(), info["amount"].as_i64().unwrap(), &mut user, &mut missions, &mut cleared_missions, &mut chats); let to_push = object!{ "master_lottery_item_id": data["master_lottery_item_id"].clone(), "master_lottery_item_number": data["master_lottery_item_number"].clone(), "is_new": 0 }; lottery_list.push(to_push).unwrap(); } } if exchange_id != 0 { items::give_gift_basic(3, exchange_id, 10, &mut user, &mut missions, &mut cleared_missions, &mut chats); } add_draw_count(&mut user, lottery_id, price_number); let mut new_count = get_draw_count(&user, lottery_id, price_number); if is_stepup(lottery_id) && price_number == 1 { new_count += 1; } userdata::save_acc(&key, user.clone()); userdata::save_acc_chats(&key, chats); userdata::save_acc_missions(&key, missions); global::api(&req, Some(object!{ "lottery_item_list": lottery_list, "updated_value_list": { "card_list": new_cards, "item_list": user["item_list"].clone() }, "gift_list": user2["home"]["gift_list"].clone(), "clear_mission_ids": cleared_missions, "draw_count_list": [ { "number": price_number, "count": new_count } ] })) } #[cfg(test)] mod tests { #[test] fn custom_banner_draw() { for lid in [6110001i64, 6110002, 6110003, 6110004] { let cards = super::get_random_cards(lid, 11); assert_eq!(cards.len(), 11, "banner {lid}"); for c in cards.members() { let id = c["master_card_id"].as_i64().unwrap(); let rarity = crate::router::items::get_rarity(id); assert!((1..=3).contains(&rarity), "card {id} rarity {rarity}"); } } } }