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 mission_start_slot_for(&self, slot_index: usize, _now: u64) -> Option<u64> {
42 let bit = 1u64 << slot_index;
43 for e in &self.entries {
44 if e.slots_on_mission != 0 && (e.slots_on_mission & bit) != 0 {
45 return Some(e.started_slot);
46 }
47 }
48 None
49 }
50
51 pub fn slots_required(mission_type: u64) -> Option<u64> {
53 match mission_type {
54 crate::consts::MISSION_TYPE_SHORT => Some(crate::consts::MISSION_SLOTS_SHORT),
55 crate::consts::MISSION_TYPE_MEDIUM => Some(crate::consts::MISSION_SLOTS_MEDIUM),
56 crate::consts::MISSION_TYPE_LONG => Some(crate::consts::MISSION_SLOTS_LONG),
57 _ => None,
58 }
59 }
60
61 pub fn duration(mission_type: u64) -> Option<u64> {
63 match mission_type {
64 crate::consts::MISSION_TYPE_SHORT => Some(crate::consts::MISSION_DURATION_SHORT),
65 crate::consts::MISSION_TYPE_MEDIUM => Some(crate::consts::MISSION_DURATION_MEDIUM),
66 crate::consts::MISSION_TYPE_LONG => Some(crate::consts::MISSION_DURATION_LONG),
67 _ => None,
68 }
69 }
70
71 pub fn cost(mission_type: u64) -> Option<u64> {
73 match mission_type {
74 crate::consts::MISSION_TYPE_SHORT => Some(crate::consts::MISSION_COST_SHORT),
75 crate::consts::MISSION_TYPE_MEDIUM => Some(crate::consts::MISSION_COST_MEDIUM),
76 crate::consts::MISSION_TYPE_LONG => Some(crate::consts::MISSION_COST_LONG),
77 _ => None,
78 }
79 }
80
81 pub fn slot_mask_popcount(mask: u64) -> u32 {
83 mask.count_ones()
84 }
85
86 pub fn reward_caps(mission_type: u64) -> Option<(u64, u64, u64)> {
88 match mission_type {
89 crate::consts::MISSION_TYPE_SHORT => Some((
90 crate::consts::MISSION_REWARD_SHARDS_SHORT,
91 crate::consts::MISSION_REWARD_TICKETS_SHORT,
92 crate::consts::MISSION_REWARD_FREE_ROLLS_SHORT,
93 )),
94 crate::consts::MISSION_TYPE_MEDIUM => Some((
95 crate::consts::MISSION_REWARD_SHARDS_MEDIUM,
96 crate::consts::MISSION_REWARD_TICKETS_MEDIUM,
97 crate::consts::MISSION_REWARD_FREE_ROLLS_MEDIUM,
98 )),
99 crate::consts::MISSION_TYPE_LONG => Some((
100 crate::consts::MISSION_REWARD_SHARDS_LONG,
101 crate::consts::MISSION_REWARD_TICKETS_LONG,
102 crate::consts::MISSION_REWARD_FREE_ROLLS_LONG,
103 )),
104 _ => None,
105 }
106 }
107
108 pub fn three_scrolls_rewards(
110 seed: [u8; 32],
111 mission_index: u64,
112 mission_type: u64,
113 ) -> (u64, u64, u64) {
114 let (cap_shards, cap_tickets, cap_rolls) = Self::reward_caps(mission_type).unwrap_or((0, 0, 0));
115
116 let scroll = |buf: &mut [u8; 48], i: u8| {
117 buf[40] = i;
118 let h = keccak::hashv(&[&buf[..41]]).to_bytes();
119 u32::from_le_bytes([h[0], h[1], h[2], h[3]]) as u64
120 };
121
122 let mut buf = [0u8; 48];
123 buf[0..32].copy_from_slice(&seed);
124 buf[32..40].copy_from_slice(&mission_index.to_le_bytes());
125
126 let p = scroll(&mut buf, 0) % 100;
127 let pct_score = if p < 70 {
128 30 * p / 70
129 } else if p < 90 {
130 30 + 40 * (p - 70) / 20
131 } else {
132 70 + 30 * (p - 90) / 10
133 };
134 let shards = cap_shards * pct_score / 100;
135
136 let tickets = scroll(&mut buf, 1) % (cap_tickets + 1);
137 let free_rolls = scroll(&mut buf, 2) % (cap_rolls + 1);
138
139 if shards == 0 && tickets == 0 && free_rolls == 0 {
140 (0, 1, 0)
141 } else {
142 (shards, tickets, free_rolls)
143 }
144 }
145}