Skip to main content

tengu_api/state/
scenes.rs

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