mirror of
https://git.ethanthesleepy.one/ethanaobrien/ew
synced 2026-10-12 09:34:28 +08:00
Restore beginner missions
This commit is contained in:
@@ -7,6 +7,7 @@ pub mod user;
|
|||||||
pub mod purchase;
|
pub mod purchase;
|
||||||
pub mod tutorial;
|
pub mod tutorial;
|
||||||
pub mod mission;
|
pub mod mission;
|
||||||
|
pub mod beginner_mission;
|
||||||
pub mod home;
|
pub mod home;
|
||||||
pub mod lottery;
|
pub mod lottery;
|
||||||
pub mod friend;
|
pub mod friend;
|
||||||
|
|||||||
@@ -0,0 +1,315 @@
|
|||||||
|
use jzon::{array, object, JsonValue};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
use super::{databases, items};
|
||||||
|
use databases::csv::{self, Region};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref CELLS: JsonValue = csv::table(Region::Jp, "beginner_mission");
|
||||||
|
static ref SETTINGS: JsonValue = csv::table(Region::Jp, "beginner_mission_reward_setting");
|
||||||
|
static ref REWARDS: JsonValue = csv::table(Region::Jp, "beginner_mission_reward");
|
||||||
|
static ref CARD_LEVELS: JsonValue = csv::table(Region::Jp, "card_level");
|
||||||
|
static ref SKILL_LEVELS: JsonValue = csv::table(Region::Jp, "card_skill_level");
|
||||||
|
}
|
||||||
|
|
||||||
|
// JP /api/mission + /api/mission/receive captures, 2024-03-31:
|
||||||
|
// all sheets accumulate progress; only reached sheets can be claimed. Line
|
||||||
|
// rewards are milestones in the number of completed lines, not named lines.
|
||||||
|
const LINES: [[i64; 3]; 8] = [
|
||||||
|
[1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 4, 7],
|
||||||
|
[2, 5, 8], [3, 6, 9], [1, 5, 9], [3, 5, 7],
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn contains(id: i64) -> bool {
|
||||||
|
CELLS.members().any(|c| c["masterMissionId"].as_i64() == Some(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn level(id: i64) -> i64 {
|
||||||
|
CELLS.members().find(|c| c["masterMissionId"].as_i64() == Some(id))
|
||||||
|
.and_then(|c| c["level"].as_i64()).unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_counter(id: i64) -> bool {
|
||||||
|
contains(id) && matches!(databases::MISSION_LIST[id.to_string()]["conditionType"].as_i64(), Some(5 | 6 | 53 | 62 | 63))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ensure(missions: &mut JsonValue) {
|
||||||
|
for cell in CELLS.members() {
|
||||||
|
let id = cell["masterMissionId"].as_i64().unwrap();
|
||||||
|
let target = databases::MISSION_LIST[id.to_string()]["conditionNumber"].as_i64().unwrap();
|
||||||
|
if let Some(mission) = missions.members_mut().find(|m| m["master_mission_id"].as_i64() == Some(id)) {
|
||||||
|
// The old login hook kept incrementing an already completed cell.
|
||||||
|
// The client subtracts one to animate an unclaimed completion, so
|
||||||
|
// over-target progress would incorrectly make its "before" sheet clear.
|
||||||
|
let progress = if mission["status"].as_i64().unwrap_or(1) >= 2 { target }
|
||||||
|
else { mission["progress"].as_i64().unwrap_or(0).clamp(0, target) };
|
||||||
|
mission["progress"] = progress.into();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
missions.push(object! {
|
||||||
|
master_mission_id: id, status: 1, progress: 0,
|
||||||
|
expire_date_time: 0, not_visible: 0
|
||||||
|
}).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_progress(id: i64, progress: i64, missions: &mut JsonValue) -> bool {
|
||||||
|
ensure(missions);
|
||||||
|
let target = databases::MISSION_LIST[id.to_string()]["conditionNumber"].as_i64().unwrap();
|
||||||
|
for mission in missions.members_mut() {
|
||||||
|
if mission["master_mission_id"].as_i64() != Some(id) { continue; }
|
||||||
|
if mission["status"].as_i64().unwrap_or(1) >= 2 { return false; }
|
||||||
|
let progress = progress.max(mission["progress"].as_i64().unwrap_or(0)).min(target);
|
||||||
|
mission["progress"] = progress.into();
|
||||||
|
mission["status"] = if progress >= target { 2 } else { 1 }.into();
|
||||||
|
return progress >= target;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance(condition: i64, value: Option<i64>, amount: i64, missions: &mut JsonValue) -> JsonValue {
|
||||||
|
ensure(missions);
|
||||||
|
let mut cleared = array![];
|
||||||
|
for cell in CELLS.members() {
|
||||||
|
let id = cell["masterMissionId"].as_i64().unwrap();
|
||||||
|
let mst = &databases::MISSION_LIST[id.to_string()];
|
||||||
|
if mst["conditionType"].as_i64() != Some(condition) { continue; }
|
||||||
|
if let Some(value) = value {
|
||||||
|
if mst["conditionValues"][0].as_i64() != Some(value) { continue; }
|
||||||
|
}
|
||||||
|
let old = items::get_mission_status(id, missions)["progress"].as_i64().unwrap_or(0);
|
||||||
|
if set_progress(id, old + amount, missions) { cleared.push(id).unwrap(); }
|
||||||
|
}
|
||||||
|
cleared
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh(user: &JsonValue, missions: &mut JsonValue) -> JsonValue {
|
||||||
|
ensure(missions);
|
||||||
|
let mut cleared = array![];
|
||||||
|
for cell in CELLS.members() {
|
||||||
|
let id = cell["masterMissionId"].as_i64().unwrap();
|
||||||
|
let mst = &databases::MISSION_LIST[id.to_string()];
|
||||||
|
let value = mst["conditionValues"][0].as_i64().unwrap_or(0);
|
||||||
|
let progress = match mst["conditionType"].as_i64().unwrap() {
|
||||||
|
14 | 64 => {
|
||||||
|
let skill = mst["conditionType"] == 64;
|
||||||
|
user["card_list"].members().filter(|card| {
|
||||||
|
let master = super::custom_card::card_info(card["master_card_id"].as_i64().unwrap_or(0));
|
||||||
|
let curve = if skill { databases::CARD_RARITY[master["rarity"].to_string()]["masterCardSkillLevelId"].as_i64() }
|
||||||
|
else { master["masterCardLevelId"].as_i64() };
|
||||||
|
let levels = if skill { &*SKILL_LEVELS } else { &*CARD_LEVELS };
|
||||||
|
levels.members().any(|l| l["id"].as_i64() == curve && l["level"].as_i64() == Some(value)
|
||||||
|
&& card[if skill { "skill_exp" } else { "exp" }].as_i64().unwrap_or(0) >= l["exp"].as_i64().unwrap())
|
||||||
|
}).count() as i64
|
||||||
|
}
|
||||||
|
17 => user["card_list"].members().filter(|c| !c["evolve"].is_empty()).count() as i64,
|
||||||
|
58 => user["character_list"].members().filter_map(|c| c["exp"].as_i64()).max().unwrap_or(0),
|
||||||
|
30 | 60 => {
|
||||||
|
let mut best = 0;
|
||||||
|
for deck in user["deck_list"].members() {
|
||||||
|
let mut types = [0i64; 3];
|
||||||
|
let mut points = [0i64; 3];
|
||||||
|
for slot in deck["main_card_ids"].members() {
|
||||||
|
let Some(card) = user["card_list"].members().find(|c| c["id"] == *slot) else { continue; };
|
||||||
|
let master = super::custom_card::card_info(card["master_card_id"].as_i64().unwrap_or(0));
|
||||||
|
if let Some(t) = master["type"].as_usize().filter(|t| (1..=3).contains(t)) { types[t-1] += 1; }
|
||||||
|
let level = CARD_LEVELS.members().filter(|l| l["id"] == master["masterCardLevelId"]
|
||||||
|
&& l["exp"].as_i64().unwrap() <= card["exp"].as_i64().unwrap_or(0))
|
||||||
|
.max_by_key(|l| l["level"].as_i64());
|
||||||
|
if let Some(l) = level {
|
||||||
|
for (i, name) in ["smile", "pure", "cool"].iter().enumerate() {
|
||||||
|
points[i] += master[*name].as_i64().unwrap_or(0) * l[format!("{}Ratio",name)].as_i64().unwrap_or(0) / 10000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let p = if mst["conditionType"] == 30 {
|
||||||
|
let needed = mst["conditionValues"][1].as_i64().unwrap();
|
||||||
|
(types.iter().any(|n| *n >= needed)) as i64
|
||||||
|
} else { *points.iter().max().unwrap() };
|
||||||
|
best = best.max(p);
|
||||||
|
}
|
||||||
|
best
|
||||||
|
}
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
if set_progress(id, progress, missions) { cleared.push(id).unwrap(); }
|
||||||
|
}
|
||||||
|
cleared
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh_account(user: &JsonValue, missions: &mut JsonValue) -> JsonValue {
|
||||||
|
let mut cleared = refresh(user, missions);
|
||||||
|
// Existing accounts may have prepared transfer credentials before missions
|
||||||
|
// were enabled. Merely requesting a code leaves an empty password.
|
||||||
|
if let Some(uid) = user["user"]["id"].as_i64() {
|
||||||
|
if super::userdata::has_transfer_password(uid) {
|
||||||
|
for id in advance(25, None, 1, missions).members() { cleared.push(id.clone()).unwrap(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cleared
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sheet_cells(group: i64, level: i64) -> impl Iterator<Item = &'static JsonValue> {
|
||||||
|
CELLS.members().filter(move |c| c["id"].as_i64() == Some(group) && c["level"].as_i64() == Some(level))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn claimed(group: i64, level: i64, missions: &JsonValue) -> Vec<i64> {
|
||||||
|
sheet_cells(group, level).filter(|c| {
|
||||||
|
items::get_mission_status(c["masterMissionId"].as_i64().unwrap(), missions)["status"] == 3
|
||||||
|
}).filter_map(|c| c["number"].as_i64()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn claimable(id: i64, missions: &JsonValue) -> bool {
|
||||||
|
let Some(cell) = CELLS.members().find(|c| c["masterMissionId"].as_i64() == Some(id)) else { return false; };
|
||||||
|
CELLS.members().filter(|c| c["id"] == cell["id"] && c["level"].as_i64() < cell["level"].as_i64())
|
||||||
|
.all(|c| items::get_mission_status(c["masterMissionId"].as_i64().unwrap(), missions)["status"] == 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn home_status(missions: &JsonValue) -> (usize, bool) {
|
||||||
|
let all = CELLS.members().all(|c| items::get_mission_status(c["masterMissionId"].as_i64().unwrap(), missions)["status"] == 3);
|
||||||
|
let count = CELLS.members().filter(|c| {
|
||||||
|
let id = c["masterMissionId"].as_i64().unwrap();
|
||||||
|
items::get_mission_status(id, missions)["status"] == 2 && claimable(id, missions)
|
||||||
|
}).count();
|
||||||
|
(count, all)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_rewards(before: &JsonValue, after: &JsonValue) -> Vec<JsonValue> {
|
||||||
|
let mut settings: Vec<_> = SETTINGS.members().collect();
|
||||||
|
// Captures return line milestones before the full-sheet reward.
|
||||||
|
settings.sort_by_key(|s| (s["masterBeginnerMissionId"].as_i64(), s["level"].as_i64(),
|
||||||
|
if s["number"] == 0 { 9 } else { s["number"].as_i64().unwrap() }));
|
||||||
|
let mut result = Vec::new();
|
||||||
|
for setting in settings {
|
||||||
|
let group = setting["masterBeginnerMissionId"].as_i64().unwrap();
|
||||||
|
let level = setting["level"].as_i64().unwrap();
|
||||||
|
let number = setting["number"].as_i64().unwrap();
|
||||||
|
// JP cross-sheet claims award bonuses only for sheets unlocked before
|
||||||
|
// the request (recorded claims 8 and 11 in the replay fixture).
|
||||||
|
let first = sheet_cells(group, level).next().unwrap()["masterMissionId"].as_i64().unwrap();
|
||||||
|
if !claimable(first, before) { continue; }
|
||||||
|
let achieved = |missions: &JsonValue| {
|
||||||
|
let cells = claimed(group, level, missions);
|
||||||
|
if number == 0 { cells.len() == 9 }
|
||||||
|
else { LINES.iter().filter(|line| line.iter().all(|n| cells.contains(n))).count() >= number as usize }
|
||||||
|
};
|
||||||
|
if !achieved(before) && achieved(after) {
|
||||||
|
result.extend(REWARDS.members().filter(|r| r["id"] == setting["masterBeginnerMissionRewardId"]).cloned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn current_inventory_drives_training_bond_and_deck_goals() {
|
||||||
|
let mut user = object! { card_list: [], character_list: [{exp: 30}], deck_list: [] };
|
||||||
|
let master = super::super::custom_card::card_info(10010001);
|
||||||
|
let exp = CARD_LEVELS.members().find(|l| l["id"] == master["masterCardLevelId"] && l["level"] == 30).unwrap()["exp"].clone();
|
||||||
|
let curve = &databases::CARD_RARITY[master["rarity"].to_string()]["masterCardSkillLevelId"];
|
||||||
|
let skill_exp = SKILL_LEVELS.members().find(|l| l["id"] == *curve && l["level"] == 2).unwrap()["exp"].clone();
|
||||||
|
for id in 1..=5 {
|
||||||
|
user["card_list"].push(object! {id: id, master_card_id: 10010001,
|
||||||
|
exp: exp.clone(), skill_exp: skill_exp.clone(), evolve: [{type: 2, count: 1}]}).unwrap();
|
||||||
|
}
|
||||||
|
user["deck_list"].push(object! {main_card_ids: [1,2,3,4,5,0,0,0,0]}).unwrap();
|
||||||
|
let mut missions = array![];
|
||||||
|
let cleared = refresh(&user, &mut missions);
|
||||||
|
for id in [1614001,1614002,1614003,1664001,1664002,1617001,1658001,1658002,1658003,1630001] {
|
||||||
|
assert!(cleared.contains(id), "mission {}", id);
|
||||||
|
}
|
||||||
|
assert!(refresh(&user, &mut missions).is_empty());
|
||||||
|
assert_eq!(items::get_mission_status(1605001, &missions)["progress"], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn counters_match_values_and_home_counts_only_unlocked_sheet() {
|
||||||
|
let mut missions = array![];
|
||||||
|
assert!(advance(63, Some(1110001), 20, &mut missions).is_empty());
|
||||||
|
assert_eq!(advance(63, Some(8110001), 10, &mut missions), array![1663001]);
|
||||||
|
assert_eq!(items::get_mission_status(1663002, &missions)["progress"], 10);
|
||||||
|
assert_eq!(advance(63, Some(8110001), 10, &mut missions), array![1663002]);
|
||||||
|
assert!(advance(6, Some(1), 1, &mut missions).is_empty());
|
||||||
|
assert_eq!(advance(6, Some(2), 1, &mut missions), array![1606001]);
|
||||||
|
advance(53, None, 3, &mut missions);
|
||||||
|
assert_eq!(home_status(&missions), (2, false));
|
||||||
|
for row in missions.members_mut() { row["status"] = 3.into(); }
|
||||||
|
assert_eq!(home_status(&missions), (0, true));
|
||||||
|
assert!(advance(53, None, 1, &mut missions).is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn all_recorded_jp_beginner_claims_match() {
|
||||||
|
let cases = jzon::parse(include_str!("beginner_mission_claims.json")).unwrap();
|
||||||
|
assert_eq!(cases.len(), 15);
|
||||||
|
for (i, case) in cases.members().enumerate() {
|
||||||
|
let mut before = array![];
|
||||||
|
ensure(&mut before);
|
||||||
|
for row in before.members_mut() {
|
||||||
|
if case["before"].contains(row["master_mission_id"].as_i64().unwrap()) { row["status"] = 3.into(); }
|
||||||
|
}
|
||||||
|
let mut after = before.clone();
|
||||||
|
for row in after.members_mut() {
|
||||||
|
if case["claim"].contains(row["master_mission_id"].as_i64().unwrap()) { row["status"] = 3.into(); }
|
||||||
|
}
|
||||||
|
let expected: Vec<_> = case["bonus"].members().map(|x| x.as_i64().unwrap()).collect();
|
||||||
|
let actual: Vec<_> = new_rewards(&before, &after).iter().map(|x| x["amount"].as_i64().unwrap()).collect();
|
||||||
|
assert_eq!(actual, expected, "JP claim {}", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn progress_is_capped_and_claims_are_not_reset() {
|
||||||
|
let mut m = array![];
|
||||||
|
advance(5, None, 12, &mut m);
|
||||||
|
assert_eq!(items::get_mission_status(1605001, &m)["progress"], 1);
|
||||||
|
assert_eq!(items::get_mission_status(1605002, &m)["progress"], 10);
|
||||||
|
assert_eq!(items::get_mission_status(1605003, &m)["progress"], 12);
|
||||||
|
assert!(!claimable(1605002, &m));
|
||||||
|
for row in m.members_mut() { if row["master_mission_id"] == 1605001 { row["status"] = 3.into(); } }
|
||||||
|
advance(5, None, 1, &mut m);
|
||||||
|
assert_eq!(items::get_mission_status(1605001, &m)["status"], 3);
|
||||||
|
for row in m.members_mut() {
|
||||||
|
if row["master_mission_id"] == 1653001 {
|
||||||
|
row["status"] = 2.into();
|
||||||
|
row["progress"] = 50.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ensure(&mut m);
|
||||||
|
assert_eq!(items::get_mission_status(1653001, &m)["progress"], 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn captured_first_sheet_claim_awards_one_line() {
|
||||||
|
let mut before = array![];
|
||||||
|
ensure(&mut before);
|
||||||
|
let mut after = before.clone();
|
||||||
|
for row in after.members_mut() {
|
||||||
|
if [1653001,1605001,1663001,1666001,1658001].contains(&row["master_mission_id"].as_i64().unwrap()) {
|
||||||
|
row["status"] = 3.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let r = new_rewards(&before, &after);
|
||||||
|
assert_eq!(r.len(), 1);
|
||||||
|
assert_eq!(r[0]["amount"], 50);
|
||||||
|
assert!(new_rewards(&after, &after).is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn full_sheet_gives_eight_lines_and_sheet_once() {
|
||||||
|
let mut before = array![];
|
||||||
|
ensure(&mut before);
|
||||||
|
let mut after = before.clone();
|
||||||
|
for row in after.members_mut() {
|
||||||
|
if sheet_cells(1,1).any(|c| c["masterMissionId"] == row["master_mission_id"]) { row["status"] = 3.into(); }
|
||||||
|
}
|
||||||
|
let r = new_rewards(&before, &after);
|
||||||
|
assert_eq!(r.len(), 9);
|
||||||
|
assert_eq!(r.iter().map(|r| r["amount"].as_i64().unwrap()).sum::<i64>(), 1000);
|
||||||
|
assert!(claimable(1605002, &after));
|
||||||
|
assert!(!home_status(&after).1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"before": [],
|
||||||
|
"claim": [
|
||||||
|
1653001
|
||||||
|
],
|
||||||
|
"bonus": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"before": [
|
||||||
|
1653001,
|
||||||
|
1660001,
|
||||||
|
1666001
|
||||||
|
],
|
||||||
|
"claim": [
|
||||||
|
1605001,
|
||||||
|
1663001,
|
||||||
|
1614001,
|
||||||
|
1658001,
|
||||||
|
1661001
|
||||||
|
],
|
||||||
|
"bonus": [
|
||||||
|
50,
|
||||||
|
50,
|
||||||
|
50,
|
||||||
|
50,
|
||||||
|
50
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"before": [],
|
||||||
|
"claim": [
|
||||||
|
1653001
|
||||||
|
],
|
||||||
|
"bonus": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"before": [],
|
||||||
|
"claim": [
|
||||||
|
1653001
|
||||||
|
],
|
||||||
|
"bonus": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"before": [],
|
||||||
|
"claim": [
|
||||||
|
1653001
|
||||||
|
],
|
||||||
|
"bonus": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"before": [
|
||||||
|
1653001
|
||||||
|
],
|
||||||
|
"claim": [
|
||||||
|
1605001,
|
||||||
|
1663001,
|
||||||
|
1660001,
|
||||||
|
1614001
|
||||||
|
],
|
||||||
|
"bonus": [
|
||||||
|
50
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"before": [],
|
||||||
|
"claim": [
|
||||||
|
1653001,
|
||||||
|
1605001,
|
||||||
|
1663001,
|
||||||
|
1666001,
|
||||||
|
1658001
|
||||||
|
],
|
||||||
|
"bonus": [
|
||||||
|
50
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"before": [
|
||||||
|
1653001,
|
||||||
|
1605001,
|
||||||
|
1663001,
|
||||||
|
1666001,
|
||||||
|
1658001
|
||||||
|
],
|
||||||
|
"claim": [
|
||||||
|
1614001
|
||||||
|
],
|
||||||
|
"bonus": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"before": [
|
||||||
|
1653001,
|
||||||
|
1605001,
|
||||||
|
1663001,
|
||||||
|
1614001,
|
||||||
|
1666001,
|
||||||
|
1658001
|
||||||
|
],
|
||||||
|
"claim": [
|
||||||
|
1662001,
|
||||||
|
1660001,
|
||||||
|
1661001,
|
||||||
|
1653002,
|
||||||
|
1658002,
|
||||||
|
1630001,
|
||||||
|
1663002
|
||||||
|
],
|
||||||
|
"bonus": [
|
||||||
|
50,
|
||||||
|
50,
|
||||||
|
50,
|
||||||
|
50,
|
||||||
|
50,
|
||||||
|
100,
|
||||||
|
100,
|
||||||
|
500
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"before": [
|
||||||
|
1653001,
|
||||||
|
1605001,
|
||||||
|
1662001,
|
||||||
|
1663001,
|
||||||
|
1660001,
|
||||||
|
1614001,
|
||||||
|
1666001,
|
||||||
|
1658001,
|
||||||
|
1661001,
|
||||||
|
1653002,
|
||||||
|
1658002,
|
||||||
|
1630001,
|
||||||
|
1663002
|
||||||
|
],
|
||||||
|
"claim": [
|
||||||
|
1665001
|
||||||
|
],
|
||||||
|
"bonus": [
|
||||||
|
50
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"before": [
|
||||||
|
1653001,
|
||||||
|
1605001,
|
||||||
|
1662001,
|
||||||
|
1663001,
|
||||||
|
1660001,
|
||||||
|
1614001,
|
||||||
|
1666001,
|
||||||
|
1658001,
|
||||||
|
1661001,
|
||||||
|
1653002,
|
||||||
|
1665001,
|
||||||
|
1658002,
|
||||||
|
1630001,
|
||||||
|
1663002
|
||||||
|
],
|
||||||
|
"claim": [
|
||||||
|
1664001,
|
||||||
|
1614002
|
||||||
|
],
|
||||||
|
"bonus": [
|
||||||
|
50
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"before": [
|
||||||
|
1653001,
|
||||||
|
1605001,
|
||||||
|
1662001,
|
||||||
|
1663001,
|
||||||
|
1660001,
|
||||||
|
1614001,
|
||||||
|
1666001,
|
||||||
|
1658001,
|
||||||
|
1661001,
|
||||||
|
1653002,
|
||||||
|
1665001,
|
||||||
|
1664001,
|
||||||
|
1658002,
|
||||||
|
1630001,
|
||||||
|
1614002,
|
||||||
|
1663002
|
||||||
|
],
|
||||||
|
"claim": [
|
||||||
|
1605002,
|
||||||
|
1606001,
|
||||||
|
1653003,
|
||||||
|
1660002,
|
||||||
|
1617001,
|
||||||
|
1658003,
|
||||||
|
1625001
|
||||||
|
],
|
||||||
|
"bonus": [
|
||||||
|
50,
|
||||||
|
50,
|
||||||
|
50,
|
||||||
|
100,
|
||||||
|
100,
|
||||||
|
500
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"before": [
|
||||||
|
1653001,
|
||||||
|
1605001,
|
||||||
|
1662001,
|
||||||
|
1663001,
|
||||||
|
1660001,
|
||||||
|
1614001,
|
||||||
|
1666001,
|
||||||
|
1658001,
|
||||||
|
1661001,
|
||||||
|
1653002,
|
||||||
|
1605002,
|
||||||
|
1665001,
|
||||||
|
1664001,
|
||||||
|
1658002,
|
||||||
|
1630001,
|
||||||
|
1606001,
|
||||||
|
1614002,
|
||||||
|
1663002,
|
||||||
|
1653003,
|
||||||
|
1660002,
|
||||||
|
1617001,
|
||||||
|
1658003,
|
||||||
|
1625001
|
||||||
|
],
|
||||||
|
"claim": [
|
||||||
|
1626001
|
||||||
|
],
|
||||||
|
"bonus": [
|
||||||
|
50,
|
||||||
|
50
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"before": [
|
||||||
|
1653001,
|
||||||
|
1605001,
|
||||||
|
1662001,
|
||||||
|
1663001,
|
||||||
|
1660001,
|
||||||
|
1614001,
|
||||||
|
1666001,
|
||||||
|
1658001,
|
||||||
|
1661001,
|
||||||
|
1653002,
|
||||||
|
1605002,
|
||||||
|
1665001,
|
||||||
|
1664001,
|
||||||
|
1658002,
|
||||||
|
1630001,
|
||||||
|
1606001,
|
||||||
|
1614002,
|
||||||
|
1663002,
|
||||||
|
1653003,
|
||||||
|
1660002,
|
||||||
|
1617001,
|
||||||
|
1658003,
|
||||||
|
1625001,
|
||||||
|
1626001
|
||||||
|
],
|
||||||
|
"claim": [
|
||||||
|
1614003,
|
||||||
|
1664002
|
||||||
|
],
|
||||||
|
"bonus": [
|
||||||
|
50,
|
||||||
|
50,
|
||||||
|
50
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"before": [
|
||||||
|
1653001,
|
||||||
|
1605001,
|
||||||
|
1662001,
|
||||||
|
1663001,
|
||||||
|
1660001,
|
||||||
|
1614001,
|
||||||
|
1666001,
|
||||||
|
1658001,
|
||||||
|
1661001,
|
||||||
|
1653002,
|
||||||
|
1605002,
|
||||||
|
1665001,
|
||||||
|
1664001,
|
||||||
|
1658002,
|
||||||
|
1630001,
|
||||||
|
1606001,
|
||||||
|
1614002,
|
||||||
|
1663002,
|
||||||
|
1653003,
|
||||||
|
1660002,
|
||||||
|
1617001,
|
||||||
|
1658003,
|
||||||
|
1625001,
|
||||||
|
1614003,
|
||||||
|
1664002,
|
||||||
|
1626001
|
||||||
|
],
|
||||||
|
"claim": [
|
||||||
|
1605003
|
||||||
|
],
|
||||||
|
"bonus": [
|
||||||
|
100,
|
||||||
|
100,
|
||||||
|
500
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -134,6 +134,11 @@ async fn reinforce(Session { key, body }: Session) -> impl Responder {
|
|||||||
let mut clear_mission_ids = array![];
|
let mut clear_mission_ids = array![];
|
||||||
|
|
||||||
let card = do_reinforce(&mut user, &body, "exp", 1, false, &mut array![], &mut array![], &mut clear_mission_ids);
|
let card = do_reinforce(&mut user, &body, "exp", 1, false, &mut array![], &mut array![], &mut clear_mission_ids);
|
||||||
|
let mut missions = userdata::get_acc_missions(&key);
|
||||||
|
for id in super::beginner_mission::refresh(&user, &mut missions).members() {
|
||||||
|
clear_mission_ids.push(id.clone()).unwrap();
|
||||||
|
}
|
||||||
|
userdata::save_acc_missions(&key, missions);
|
||||||
|
|
||||||
userdata::save_acc(&key, user.clone());
|
userdata::save_acc(&key, user.clone());
|
||||||
|
|
||||||
@@ -150,6 +155,11 @@ async fn skill_reinforce(Session { key, body }: Session) -> impl Responder {
|
|||||||
let mut clear_mission_ids = array![];
|
let mut clear_mission_ids = array![];
|
||||||
|
|
||||||
let card = do_reinforce(&mut user, &body, "skill_exp", 10, false, &mut array![], &mut array![], &mut clear_mission_ids);
|
let card = do_reinforce(&mut user, &body, "skill_exp", 10, false, &mut array![], &mut array![], &mut clear_mission_ids);
|
||||||
|
let mut missions = userdata::get_acc_missions(&key);
|
||||||
|
for id in super::beginner_mission::refresh(&user, &mut missions).members() {
|
||||||
|
clear_mission_ids.push(id.clone()).unwrap();
|
||||||
|
}
|
||||||
|
userdata::save_acc_missions(&key, missions);
|
||||||
|
|
||||||
userdata::save_acc(&key, user.clone());
|
userdata::save_acc(&key, user.clone());
|
||||||
|
|
||||||
@@ -168,6 +178,7 @@ async fn evolve(Session { key, body }: Session) -> impl Responder {
|
|||||||
let mut clear_mission_ids = array![];
|
let mut clear_mission_ids = array![];
|
||||||
|
|
||||||
let card = do_reinforce(&mut user, &body, "", 0, true, &mut missions, &mut chats, &mut clear_mission_ids);
|
let card = do_reinforce(&mut user, &body, "", 0, true, &mut missions, &mut chats, &mut clear_mission_ids);
|
||||||
|
for id in super::beginner_mission::refresh(&user, &mut missions).members() { clear_mission_ids.push(id.clone()).unwrap(); }
|
||||||
|
|
||||||
userdata::save_acc(&key, user.clone());
|
userdata::save_acc(&key, user.clone());
|
||||||
userdata::save_acc_chats(&key, chats);
|
userdata::save_acc_chats(&key, chats);
|
||||||
|
|||||||
@@ -324,6 +324,11 @@ async fn migration_password_register(req: HttpRequest, body: String) -> impl Res
|
|||||||
let pass = decrypt_transfer_password(&body["migration_password"].to_string());
|
let pass = decrypt_transfer_password(&body["migration_password"].to_string());
|
||||||
|
|
||||||
userdata::user::migration::save_acc_transfer(user["user"]["id"].as_i64().unwrap(), &pass);
|
userdata::user::migration::save_acc_transfer(user["user"]["id"].as_i64().unwrap(), &pass);
|
||||||
|
if !pass.is_empty() {
|
||||||
|
let mut missions = userdata::get_acc_missions(&uid);
|
||||||
|
super::beginner_mission::advance(25, None, 1, &mut missions);
|
||||||
|
userdata::save_acc_missions(&uid, missions);
|
||||||
|
}
|
||||||
|
|
||||||
let resp = object!{
|
let resp = object!{
|
||||||
result: "OK"
|
result: "OK"
|
||||||
|
|||||||
+11
-5
@@ -23,6 +23,10 @@ async fn preset(Session { key, body }: Session) -> impl Responder {
|
|||||||
}
|
}
|
||||||
userdata::save_acc_home(&key, user);
|
userdata::save_acc_home(&key, user);
|
||||||
|
|
||||||
|
let mut missions = userdata::get_acc_missions(&key);
|
||||||
|
super::beginner_mission::advance(61, None, 1, &mut missions);
|
||||||
|
userdata::save_acc_missions(&key, missions);
|
||||||
|
|
||||||
Api(Some(array![]))
|
Api(Some(array![]))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,12 +93,13 @@ async fn home(Login(key): Login) -> impl Responder {
|
|||||||
check_gifts(&mut user);
|
check_gifts(&mut user);
|
||||||
|
|
||||||
let mut user_missions = userdata::get_acc_missions(&key);
|
let mut user_missions = userdata::get_acc_missions(&key);
|
||||||
let clear = items::completed_daily_mission(1253003, &mut user_missions);
|
let mut clear = super::beginner_mission::refresh_account(&userdata::get_acc(&key), &mut user_missions);
|
||||||
|
for id in items::completed_daily_mission(1253003, &mut user_missions).members() {
|
||||||
|
clear.push(id.clone()).unwrap();
|
||||||
|
}
|
||||||
userdata::save_acc_home(&key, user.clone());
|
userdata::save_acc_home(&key, user.clone());
|
||||||
user["clear_mission_ids"] = clear;
|
user["clear_mission_ids"] = clear;
|
||||||
if !user["clear_mission_ids"].is_empty() {
|
|
||||||
userdata::save_acc_missions(&key, user_missions.clone());
|
userdata::save_acc_missions(&key, user_missions.clone());
|
||||||
}
|
|
||||||
|
|
||||||
let daily_missions = array![1224003, 1253003, 1273009, 1273010, 1273011, 1273012];
|
let daily_missions = array![1224003, 1253003, 1273009, 1273010, 1273011, 1273012];
|
||||||
|
|
||||||
@@ -128,8 +133,9 @@ async fn home(Login(key): Login) -> impl Responder {
|
|||||||
let new_announcement = crate::database::announcements::latest_published_at() > seen_at;
|
let new_announcement = crate::database::announcements::latest_published_at() > seen_at;
|
||||||
user["home"]["new_announcement_flag"] = (new_announcement as i32).into();
|
user["home"]["new_announcement_flag"] = (new_announcement as i32).into();
|
||||||
|
|
||||||
//todo
|
let (ready, complete) = super::beginner_mission::home_status(&user_missions);
|
||||||
user["home"]["beginner_mission_complete"] = 1.into();
|
user["home"]["clear_beginner_mission_count"] = ready.into();
|
||||||
|
user["home"]["beginner_mission_complete"] = (complete as i32).into();
|
||||||
|
|
||||||
Api(Some(user))
|
Api(Some(user))
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -545,8 +545,8 @@ pub fn completed_daily_mission(id: i64, missions: &mut JsonValue) -> JsonValue {
|
|||||||
let mut rv = array![];
|
let mut rv = array![];
|
||||||
if id == 1253003 {
|
if id == 1253003 {
|
||||||
rv = advance_variable_mission(1153001, 1153019, 1, missions);
|
rv = advance_variable_mission(1153001, 1153019, 1, missions);
|
||||||
if update_mission_status(1653001, 0, true, false, 1, missions).is_some() {
|
for id in super::beginner_mission::advance(53, None, 1, missions).members() {
|
||||||
rv.push(1653001).unwrap();
|
rv.push(id.clone()).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for mission in missions.members_mut() {
|
for mission in missions.members_mut() {
|
||||||
|
|||||||
@@ -727,6 +727,17 @@ pub fn live_end_ex(req: &HttpRequest, key: &str, body: &JsonValue, skipped: bool
|
|||||||
|
|
||||||
//1273009, 1273010, 1273011, 1273012
|
//1273009, 1273010, 1273011, 1273012
|
||||||
let mut cleared_missions = items::advance_variable_mission(1105001, 1105017, 1, &mut user_missions);
|
let mut cleared_missions = items::advance_variable_mission(1105001, 1105017, 1, &mut user_missions);
|
||||||
|
for id in super::beginner_mission::advance(5, None, 1, &mut user_missions).members() {
|
||||||
|
cleared_missions.push(id.clone()).unwrap();
|
||||||
|
}
|
||||||
|
if !skipped {
|
||||||
|
let started = get_started_live(key, body).unwrap_or(JsonValue::Null);
|
||||||
|
if started["is_omakase"] == 1 {
|
||||||
|
for id in super::beginner_mission::advance(62, None, 1, &mut user_missions).members() { cleared_missions.push(id.clone()).unwrap(); }
|
||||||
|
}
|
||||||
|
let boost = started["live_boost"].as_i64().or(body["live_boost"].as_i64()).unwrap_or(0);
|
||||||
|
for id in super::beginner_mission::advance(6, Some(boost), 1, &mut user_missions).members() { cleared_missions.push(id.clone()).unwrap(); }
|
||||||
|
}
|
||||||
if body["master_live_id"].to_string().len() > 1 {
|
if body["master_live_id"].to_string().len() > 1 {
|
||||||
let id = body["master_live_id"].to_string().split("").collect::<Vec<_>>()[2].parse::<i64>().unwrap_or(0);
|
let id = body["master_live_id"].to_string().split("").collect::<Vec<_>>()[2].parse::<i64>().unwrap_or(0);
|
||||||
if (1..=4).contains(&id) {
|
if (1..=4).contains(&id) {
|
||||||
@@ -822,6 +833,7 @@ pub fn live_end_ex(req: &HttpRequest, key: &str, body: &JsonValue, skipped: bool
|
|||||||
|
|
||||||
let deck_slot = get_end_live_deck_id(&key, &body).unwrap_or(body["deck_slot"].as_i32().unwrap_or(user["user"]["main_deck_slot"].as_i32().unwrap()));
|
let deck_slot = get_end_live_deck_id(&key, &body).unwrap_or(body["deck_slot"].as_i32().unwrap_or(user["user"]["main_deck_slot"].as_i32().unwrap()));
|
||||||
let characters = get_live_character_list(lp_used, deck_slot, &mut user, &mut user_missions, &mut cleared_missions, &mut chats);
|
let characters = get_live_character_list(lp_used, deck_slot, &mut user, &mut user_missions, &mut cleared_missions, &mut chats);
|
||||||
|
for id in super::beginner_mission::refresh(&user, &mut user_missions).members() { cleared_missions.push(id.clone()).unwrap(); }
|
||||||
|
|
||||||
userdata::save_acc(&key, user.clone());
|
userdata::save_acc(&key, user.clone());
|
||||||
userdata::save_acc_home(&key, user2.clone());
|
userdata::save_acc_home(&key, user2.clone());
|
||||||
|
|||||||
@@ -390,6 +390,9 @@ async fn lottery_post(req: HttpRequest, Session { key, body }: Session) -> impl
|
|||||||
}
|
}
|
||||||
|
|
||||||
add_draw_count(&mut user, lottery_id, price_number);
|
add_draw_count(&mut user, lottery_id, price_number);
|
||||||
|
for id in super::beginner_mission::advance(63, Some(lottery_id), price["count"].as_i64().unwrap_or(0), &mut missions).members() {
|
||||||
|
cleared_missions.push(id.clone()).unwrap();
|
||||||
|
}
|
||||||
let mut new_count = get_draw_count(&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 {
|
if is_stepup(lottery_id) && price_number == 1 {
|
||||||
new_count += 1;
|
new_count += 1;
|
||||||
|
|||||||
+89
-6
@@ -12,6 +12,50 @@ pub fn routes(cfg: &mut web::ServiceConfig) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn beginner_claims_persist_rewards_and_reject_repeats() {
|
||||||
|
let _test = crate::runtime::lock_test_data_path();
|
||||||
|
let (uid, key) = userdata::starter::create("Beginner test").unwrap();
|
||||||
|
let request = || actix_web::test::TestRequest::default()
|
||||||
|
.insert_header(("aoharu-user-id", uid.to_string())).to_http_request();
|
||||||
|
let first = [1653001,1605001,1662001,1663001,1660001,1614001,1666001,1658001,1661001];
|
||||||
|
let mut missions = userdata::get_acc_missions(&key);
|
||||||
|
for id in first {
|
||||||
|
super::super::beginner_mission::set_progress(id, i64::MAX, &mut missions);
|
||||||
|
}
|
||||||
|
super::super::beginner_mission::set_progress(1653002, 2, &mut missions);
|
||||||
|
userdata::save_acc_missions(&key, missions);
|
||||||
|
let blocked = receive(request(), Session { key: key.clone(), body: object! {master_mission_ids: [1653002]} }).await.0.unwrap();
|
||||||
|
assert!(blocked["reward_list"].is_empty());
|
||||||
|
let gem_before = userdata::get_acc(&key)["gem"]["total"].as_i64().unwrap();
|
||||||
|
let mut ids: Vec<i64> = first.into();
|
||||||
|
ids.push(first[0]);
|
||||||
|
let body = object! {master_mission_ids: ids};
|
||||||
|
let response = receive(request(), Session { key: key.clone(), body: body.clone() }).await.0.unwrap();
|
||||||
|
assert_eq!(response["mission_list"].len(), 9);
|
||||||
|
assert_eq!(response["reward_list"].len(), 18);
|
||||||
|
let bonuses: i64 = response["reward_list"].members().filter(|r| r["type"] == 1)
|
||||||
|
.map(|r| r["amount"].as_i64().unwrap()).sum();
|
||||||
|
assert_eq!(bonuses, 1000);
|
||||||
|
assert_eq!(userdata::get_acc(&key)["gem"]["total"].as_i64().unwrap(), gem_before + bonuses);
|
||||||
|
let items = &response["updated_value_list"]["item_list"];
|
||||||
|
let unique: std::collections::HashSet<_> = items.members().map(|i| i["master_item_id"].as_i64()).collect();
|
||||||
|
assert_eq!(items.len(), unique.len());
|
||||||
|
let saved = userdata::get_acc(&key);
|
||||||
|
let replay = receive(request(), Session { key: key.clone(), body }).await.0.unwrap();
|
||||||
|
assert!(replay["reward_list"].is_empty());
|
||||||
|
assert_eq!(userdata::get_acc(&key), saved);
|
||||||
|
let second = receive(request(), Session { key: key.clone(), body: object! {master_mission_ids: [1653002, 1605002]} }).await.0.unwrap();
|
||||||
|
assert_eq!(second["mission_list"].len(), 1);
|
||||||
|
assert_eq!(second["mission_list"][0]["master_mission_id"], 1653002);
|
||||||
|
assert_eq!(second["mission_list"][0]["progress"], 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const VARIABLE_MISSIONS: [[i64; 2]; 5] = [[1153001, 1153019], [1105001, 1105017], [1101001, 1101030], [1121001, 1121019], [1112001, 1112033]];
|
const VARIABLE_MISSIONS: [[i64; 2]; 5] = [[1153001, 1153019], [1105001, 1105017], [1101001, 1101030], [1121001, 1121019], [1112001, 1112033]];
|
||||||
|
|
||||||
async fn mission(Login(key): Login) -> impl Responder {
|
async fn mission(Login(key): Login) -> impl Responder {
|
||||||
@@ -25,9 +69,9 @@ async fn mission(Login(key): Login) -> impl Responder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut missions = userdata::get_acc_missions(&key);
|
let mut missions = userdata::get_acc_missions(&key);
|
||||||
if items::refresh_dailies(&mut missions, global::timestamp()) {
|
items::refresh_dailies(&mut missions, global::timestamp());
|
||||||
|
super::beginner_mission::refresh_account(&user, &mut missions);
|
||||||
userdata::save_acc_missions(&key, missions.clone());
|
userdata::save_acc_missions(&key, missions.clone());
|
||||||
}
|
|
||||||
|
|
||||||
Api(Some(object!{
|
Api(Some(object!{
|
||||||
"mission_list": missions
|
"mission_list": missions
|
||||||
@@ -39,6 +83,10 @@ async fn clear(Session { key, body }: Session) -> impl Responder {
|
|||||||
|
|
||||||
let mut cleared = array![];
|
let mut cleared = array![];
|
||||||
for id in body["master_mission_ids"].members() {
|
for id in body["master_mission_ids"].members() {
|
||||||
|
if super::beginner_mission::contains(id.as_i64().unwrap_or(0)) {
|
||||||
|
let condition = databases::MISSION_LIST[id.to_string()]["conditionType"].as_i64();
|
||||||
|
if !matches!(condition, Some(26 | 65 | 66)) { continue; }
|
||||||
|
}
|
||||||
let mission = items::get_mission_status(id.as_i64().unwrap(), &missions);
|
let mission = items::get_mission_status(id.as_i64().unwrap(), &missions);
|
||||||
if mission.is_empty() || mission["status"].as_i32().unwrap_or(0) >= 2 {
|
if mission.is_empty() || mission["status"].as_i32().unwrap_or(0) >= 2 {
|
||||||
continue;
|
continue;
|
||||||
@@ -54,7 +102,11 @@ async fn clear(Session { key, body }: Session) -> impl Responder {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(req: HttpRequest, Session { key, body }: Session) -> impl Responder {
|
static CLAIM_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||||
|
|
||||||
|
async fn receive(req: HttpRequest, Session { key, body }: Session) -> Api {
|
||||||
|
// Keep the read/grant/save sequence exclusive across HTTP workers.
|
||||||
|
let _claim = crate::lock_onto_mutex!(CLAIM_LOCK);
|
||||||
let uid = global::get_uid(req.headers());
|
let uid = global::get_uid(req.headers());
|
||||||
let now = global::timestamp();
|
let now = global::timestamp();
|
||||||
|
|
||||||
@@ -66,9 +118,20 @@ async fn receive(req: HttpRequest, Session { key, body }: Session) -> impl Respo
|
|||||||
let mut touched_gem = false;
|
let mut touched_gem = false;
|
||||||
let mut touched_coin = false;
|
let mut touched_coin = false;
|
||||||
let mut touched_items = array![];
|
let mut touched_items = array![];
|
||||||
|
let before = missions.clone();
|
||||||
|
let mut requested: Vec<_> = body["master_mission_ids"].members().filter_map(|id| id.as_i64()).collect();
|
||||||
|
let mut seen = std::collections::HashSet::new();
|
||||||
|
requested.retain(|id| seen.insert(*id));
|
||||||
|
// Process beginner cells in sheet order so a single claim can unlock the next sheet.
|
||||||
|
requested.sort_by_key(|id| super::beginner_mission::level(*id));
|
||||||
|
|
||||||
for mission in body["master_mission_ids"].members() {
|
for mid in requested {
|
||||||
let mid = mission.as_i64().unwrap();
|
if items::get_mission_status(mid, &missions)["status"] != 2 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if super::beginner_mission::contains(mid) && !super::beginner_mission::claimable(mid, &missions) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let mission_info = databases::MISSION_LIST[mid.to_string()].clone();
|
let mission_info = databases::MISSION_LIST[mid.to_string()].clone();
|
||||||
for master in databases::MISSION_REWARDS[mission_info["masterMissionRewardId"].to_string()].members() {
|
for master in databases::MISSION_REWARDS[mission_info["masterMissionRewardId"].to_string()].members() {
|
||||||
let reward_type = master["type"].as_i64().unwrap();
|
let reward_type = master["type"].as_i64().unwrap();
|
||||||
@@ -135,13 +198,28 @@ async fn receive(req: HttpRequest, Session { key, body }: Session) -> impl Respo
|
|||||||
user_id: uid,
|
user_id: uid,
|
||||||
master_mission_id: mid,
|
master_mission_id: mid,
|
||||||
status: 3,
|
status: 3,
|
||||||
progress: if expire != 0 { m["progress"].clone() } else { JsonValue::Null },
|
progress: if expire != 0 || super::beginner_mission::is_counter(mid) { m["progress"].clone() } else { JsonValue::Null },
|
||||||
expire_date: if expire != 0 { global::format_datetime(expire).into() } else { JsonValue::Null },
|
expire_date: if expire != 0 { global::format_datetime(expire).into() } else { JsonValue::Null },
|
||||||
received_date: global::format_datetime(now)
|
received_date: global::format_datetime(now)
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for master in super::beginner_mission::new_rewards(&before, &missions) {
|
||||||
|
let reward_type = master["type"].as_i64().unwrap();
|
||||||
|
rewards.push(object! {
|
||||||
|
give_type: master["giveType"].clone(), type: master["type"].clone(),
|
||||||
|
value: master["value"].clone(), level: master["level"].clone(), amount: master["amount"].clone()
|
||||||
|
}).unwrap();
|
||||||
|
items::give_gift(&object! {
|
||||||
|
reward_type: reward_type, value: master["value"].clone(), amount: master["amount"].clone()
|
||||||
|
}, &mut user, &mut missions, &mut array![], &mut chats);
|
||||||
|
match reward_type {
|
||||||
|
1 => touched_gem = true,
|
||||||
|
4 => touched_coin = true,
|
||||||
|
_ => { touched_items.push(master["value"].clone()).unwrap(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
userdata::save_acc(&key, user.clone());
|
userdata::save_acc(&key, user.clone());
|
||||||
userdata::save_acc_chats(&key, chats);
|
userdata::save_acc_chats(&key, chats);
|
||||||
userdata::save_acc_missions(&key, missions);
|
userdata::save_acc_missions(&key, missions);
|
||||||
@@ -152,7 +230,9 @@ async fn receive(req: HttpRequest, Session { key, body }: Session) -> impl Respo
|
|||||||
}
|
}
|
||||||
if !touched_items.is_empty() {
|
if !touched_items.is_empty() {
|
||||||
let mut item_list = array![];
|
let mut item_list = array![];
|
||||||
|
let mut seen = std::collections::HashSet::new();
|
||||||
for id in touched_items.members() {
|
for id in touched_items.members() {
|
||||||
|
if !seen.insert(id.as_i64()) { continue; }
|
||||||
for item in user["item_list"].members() {
|
for item in user["item_list"].members() {
|
||||||
if item["master_item_id"] == *id {
|
if item["master_item_id"] == *id {
|
||||||
item_list.push(item.clone()).unwrap();
|
item_list.push(item.clone()).unwrap();
|
||||||
@@ -176,6 +256,9 @@ async fn receive(req: HttpRequest, Session { key, body }: Session) -> impl Respo
|
|||||||
updated_value_list["point_list"] = point_list;
|
updated_value_list["point_list"] = point_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut sorted: Vec<_> = mission_list.members().cloned().collect();
|
||||||
|
sorted.sort_by_key(|m| m["master_mission_id"].as_i64());
|
||||||
|
let mission_list: JsonValue = sorted.into();
|
||||||
Api(Some(object!{
|
Api(Some(object!{
|
||||||
"reward_list": rewards,
|
"reward_list": rewards,
|
||||||
"gift_list": array![],
|
"gift_list": array![],
|
||||||
|
|||||||
@@ -404,6 +404,7 @@ async fn end(req: HttpRequest, Session { key, body }: Session) -> impl Responder
|
|||||||
// live_end scales exp / gold / bond / mission rewards off.
|
// live_end scales exp / gold / bond / mission rewards off.
|
||||||
let mut end_body = body.clone();
|
let mut end_body = body.clone();
|
||||||
end_body["use_lp"] = lp_used.into();
|
end_body["use_lp"] = lp_used.into();
|
||||||
|
end_body["live_boost"] = live_boost.into();
|
||||||
if end_body["deck_slot"].is_null() {
|
if end_body["deck_slot"].is_null() {
|
||||||
if let Some(slot) = started.and_then(|s| s["deck_slot"].as_i32()) {
|
if let Some(slot) = started.and_then(|s| s["deck_slot"].as_i32()) {
|
||||||
end_body["deck_slot"] = slot.into();
|
end_body["deck_slot"] = slot.into();
|
||||||
|
|||||||
+9
-1
@@ -48,6 +48,9 @@ async fn deck(Session { key, body }: Session) -> impl Responder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
userdata::save_acc(&key, user.clone());
|
userdata::save_acc(&key, user.clone());
|
||||||
|
let mut missions = userdata::get_acc_missions(&key);
|
||||||
|
let cleared = super::beginner_mission::refresh(&user, &mut missions);
|
||||||
|
userdata::save_acc_missions(&key, missions);
|
||||||
|
|
||||||
Api(Some(object!{
|
Api(Some(object!{
|
||||||
"deck": {
|
"deck": {
|
||||||
@@ -55,7 +58,7 @@ async fn deck(Session { key, body }: Session) -> impl Responder {
|
|||||||
"leader_role": 0,
|
"leader_role": 0,
|
||||||
"main_card_ids": body["main_card_ids"].clone()
|
"main_card_ids": body["main_card_ids"].clone()
|
||||||
},
|
},
|
||||||
"clear_mission_ids": []
|
"clear_mission_ids": cleared
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,6 +215,11 @@ async fn register_password(Session { key, body }: Session) -> impl Responder {
|
|||||||
let user = userdata::get_acc(&key);
|
let user = userdata::get_acc(&key);
|
||||||
|
|
||||||
userdata::user::migration::save_acc_transfer(user["user"]["id"].as_i64().unwrap(), &body["pass"].to_string());
|
userdata::user::migration::save_acc_transfer(user["user"]["id"].as_i64().unwrap(), &body["pass"].to_string());
|
||||||
|
if !body["pass"].as_str().unwrap_or("").is_empty() {
|
||||||
|
let mut missions = userdata::get_acc_missions(&key);
|
||||||
|
super::beginner_mission::advance(25, None, 1, &mut missions);
|
||||||
|
userdata::save_acc_missions(&key, missions);
|
||||||
|
}
|
||||||
|
|
||||||
Api(Some(array![]))
|
Api(Some(array![]))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -357,7 +357,9 @@ pub fn get_acc_home(auth_key: &str) -> JsonValue {
|
|||||||
user
|
user
|
||||||
}
|
}
|
||||||
pub fn get_acc_missions(auth_key: &str) -> JsonValue {
|
pub fn get_acc_missions(auth_key: &str) -> JsonValue {
|
||||||
get_data(auth_key, "missions")
|
let mut missions = get_data(auth_key, "missions");
|
||||||
|
super::beginner_mission::ensure(&mut missions);
|
||||||
|
missions
|
||||||
}
|
}
|
||||||
pub fn get_acc_loginbonus(auth_key: &str) -> JsonValue {
|
pub fn get_acc_loginbonus(auth_key: &str) -> JsonValue {
|
||||||
get_data(auth_key, "loginbonus")
|
get_data(auth_key, "loginbonus")
|
||||||
|
|||||||
Reference in New Issue
Block a user