Skip to main content

tengu_api/state/
scenes.rs

1use super::DojosAccount;
2use crate::consts::{SCENE_COUNT, SCENE_COUNT_MAX, SCENE_SECTIONS_PER_SCENE};
3use solana_program::program_error::ProgramError;
4use steel::*;
5
6#[repr(C)]
7#[derive(Clone, Copy, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
8pub struct Scenes {
9    pub dojo: Pubkey,
10    pub section_counts: [[u64; SCENE_SECTIONS_PER_SCENE]; SCENE_COUNT_MAX],
11    pub reserved1: u64,
12    pub reserved2: u64,
13    pub reserved3: u64,
14    pub reserved4: u64,
15}
16
17account!(DojosAccount, Scenes);
18
19impl Scenes {
20    pub fn assert_dojo(&self, dojo_pda: &Pubkey) -> Result<(), ProgramError> {
21        if self.dojo != *dojo_pda {
22            return Err(ProgramError::InvalidAccountData);
23        }
24        Ok(())
25    }
26
27    pub fn is_unlocked(&self, scene_id: u64) -> bool {
28        if scene_id as usize >= SCENE_COUNT_MAX {
29            return false;
30        }
31        self.section_counts[scene_id as usize]
32            .iter()
33            .all(|&c| c >= 1)
34    }
35
36    pub fn can_salvage(&self, scene_id: u64, section_id: u64) -> bool {
37        self.can_salvage_count(scene_id, section_id, 1)
38    }
39
40    /// Can salvage `count` duplicates? Requires section_counts >= 1 + count (keep at least 1).
41    pub fn can_salvage_count(&self, scene_id: u64, section_id: u64, count: u64) -> bool {
42        if scene_id as usize >= SCENE_COUNT_MAX {
43            return false;
44        }
45        if section_id as usize >= SCENE_SECTIONS_PER_SCENE {
46            return false;
47        }
48        if count == 0 {
49            return false;
50        }
51        self.section_counts[scene_id as usize][section_id as usize] >= 1 + count
52    }
53
54    pub fn increment_section(&mut self, scene_id: u64, section_id: u64) {
55        if (scene_id as usize) < SCENE_COUNT_MAX && (section_id as usize) < SCENE_SECTIONS_PER_SCENE
56        {
57            self.section_counts[scene_id as usize][section_id as usize] =
58                self.section_counts[scene_id as usize][section_id as usize].saturating_add(1);
59        }
60    }
61
62    pub fn decrement_section(&mut self, scene_id: u64, section_id: u64) {
63        self.decrement_section_by(scene_id, section_id, 1);
64    }
65
66    pub fn decrement_section_by(&mut self, scene_id: u64, section_id: u64, n: u64) {
67        if (scene_id as usize) < SCENE_COUNT_MAX && (section_id as usize) < SCENE_SECTIONS_PER_SCENE
68        {
69            let c = &mut self.section_counts[scene_id as usize][section_id as usize];
70            *c = c.saturating_sub(n);
71        }
72    }
73
74    /// Unlock entire scene (all 12 sections = 1). Used for BuyScene (scenes 6–8).
75    pub fn unlock_scene(&mut self, scene_id: u64) {
76        if (scene_id as usize) < SCENE_COUNT_MAX {
77            for s in 0..SCENE_SECTIONS_PER_SCENE {
78                self.section_counts[scene_id as usize][s] = 1;
79            }
80        }
81    }
82
83    /// Derive scene_id and section_id from 32-byte hash. Roll pool excludes Green Mountain.
84    /// scene_id ∈ [1, SCENE_COUNT), section_id ∈ [0, 12)
85    pub fn derive_scene_section(hash: &[u8; 32]) -> (u64, u64) {
86        let v0 = u64::from_le_bytes(hash[0..8].try_into().unwrap());
87        let v1 = u64::from_le_bytes(hash[8..16].try_into().unwrap());
88        let scene_id = 1 + (v0 % SCENE_COUNT);
89        let section_id = v1 % SCENE_SECTIONS_PER_SCENE as u64;
90        (scene_id, section_id)
91    }
92}