tengu_api/state/
mission.rs1use super::DojosAccount;
4use solana_program::keccak;
5use steel::*;
6
7#[repr(C)]
9#[derive(Clone, Copy, Debug, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
10pub struct MissionEntry {
11 pub slots_on_mission: u64,
13 pub mission_type: u64,
14 pub started_slot: u64,
15 pub duration_slots: u64,
16}
17
18#[repr(C)]
20#[derive(Clone, Copy, Debug, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
21pub struct Mission {
22 pub dojo: Pubkey,
23 pub entries: [MissionEntry; crate::consts::MAX_MISSION_ENTRIES],
24 pub buffer: [u64; 4],
25}
26
27account!(DojosAccount, Mission);
28
29impl Mission {
30 pub fn slots_on_any_active_mission(&self, now: u64) -> u64 {
32 self.entries
33 .iter()
34 .filter(|e| e.slots_on_mission != 0 && now < e.started_slot.saturating_add(e.duration_slots))
35 .fold(0u64, |acc, e| acc | e.slots_on_mission)
36 }
37
38 pub fn slots_required(mission_type: u64) -> Option<u64> {
40 match mission_type {
41 crate::consts::MISSION_TYPE_SHORT => Some(crate::consts::MISSION_SLOTS_SHORT),
42 crate::consts::MISSION_TYPE_MEDIUM => Some(crate::consts::MISSION_SLOTS_MEDIUM),
43 crate::consts::MISSION_TYPE_LONG => Some(crate::consts::MISSION_SLOTS_LONG),
44 _ => None,
45 }
46 }
47
48 pub fn duration(mission_type: u64) -> Option<u64> {
50 match mission_type {
51 crate::consts::MISSION_TYPE_SHORT => Some(crate::consts::MISSION_DURATION_SHORT),
52 crate::consts::MISSION_TYPE_MEDIUM => Some(crate::consts::MISSION_DURATION_MEDIUM),
53 crate::consts::MISSION_TYPE_LONG => Some(crate::consts::MISSION_DURATION_LONG),
54 _ => None,
55 }
56 }
57
58 pub fn cost(mission_type: u64) -> Option<u64> {
60 match mission_type {
61 crate::consts::MISSION_TYPE_SHORT => Some(crate::consts::MISSION_COST_SHORT),
62 crate::consts::MISSION_TYPE_MEDIUM => Some(crate::consts::MISSION_COST_MEDIUM),
63 crate::consts::MISSION_TYPE_LONG => Some(crate::consts::MISSION_COST_LONG),
64 _ => None,
65 }
66 }
67
68 pub fn slot_mask_popcount(mask: u64) -> u32 {
70 mask.count_ones()
71 }
72
73 pub fn reward_caps(mission_type: u64) -> Option<(u64, u64, u64)> {
75 match mission_type {
76 crate::consts::MISSION_TYPE_SHORT => Some((
77 crate::consts::MISSION_REWARD_SHARDS_SHORT,
78 crate::consts::MISSION_REWARD_TICKETS_SHORT,
79 crate::consts::MISSION_REWARD_FREE_ROLLS_SHORT,
80 )),
81 crate::consts::MISSION_TYPE_MEDIUM => Some((
82 crate::consts::MISSION_REWARD_SHARDS_MEDIUM,
83 crate::consts::MISSION_REWARD_TICKETS_MEDIUM,
84 crate::consts::MISSION_REWARD_FREE_ROLLS_MEDIUM,
85 )),
86 crate::consts::MISSION_TYPE_LONG => Some((
87 crate::consts::MISSION_REWARD_SHARDS_LONG,
88 crate::consts::MISSION_REWARD_TICKETS_LONG,
89 crate::consts::MISSION_REWARD_FREE_ROLLS_LONG,
90 )),
91 _ => None,
92 }
93 }
94
95 pub fn three_scrolls_rewards(
97 seed: [u8; 32],
98 mission_index: u64,
99 mission_type: u64,
100 ) -> (u64, u64, u64) {
101 let (cap_shards, cap_tickets, cap_rolls) = Self::reward_caps(mission_type).unwrap_or((0, 0, 0));
102
103 let scroll = |buf: &mut [u8; 48], i: u8| {
104 buf[40] = i;
105 let h = keccak::hashv(&[&buf[..41]]).to_bytes();
106 u32::from_le_bytes([h[0], h[1], h[2], h[3]]) as u64
107 };
108
109 let mut buf = [0u8; 48];
110 buf[0..32].copy_from_slice(&seed);
111 buf[32..40].copy_from_slice(&mission_index.to_le_bytes());
112
113 let p = scroll(&mut buf, 0) % 100;
114 let pct_score = if p < 70 {
115 30 * p / 70
116 } else if p < 90 {
117 30 + 40 * (p - 70) / 20
118 } else {
119 70 + 30 * (p - 90) / 10
120 };
121 let shards = cap_shards * pct_score / 100;
122
123 let tickets = scroll(&mut buf, 1) % (cap_tickets + 1);
124 let free_rolls = scroll(&mut buf, 2) % (cap_rolls + 1);
125
126 if shards == 0 && tickets == 0 && free_rolls == 0 {
127 (0, 1, 0)
128 } else {
129 (shards, tickets, free_rolls)
130 }
131 }
132}