osrscache/definition/osrs/
npc_def.rs

1use std::{collections::HashMap, io, io::BufReader};
2
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6use super::Definition;
7use crate::{extension::ReadExt, util};
8
9/// Contains all the information about a certain npc fetched from the cache through
10/// the [NpcLoader](../../loader/osrs/struct.NpcLoader.html).
11#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
12#[derive(Clone, Eq, PartialEq, Debug, Default)]
13pub struct NpcDefinition {
14    pub id: u16,
15    pub name: String,
16    pub size: usize,
17    pub actions: [String; 5],
18    pub visible_on_minimap: bool,
19    pub combat_level: Option<u16>,
20    pub configs: Vec<u16>,
21    pub varbit_id: Option<u16>,
22    pub varp_index: Option<u16>,
23    pub interactable: bool,
24    pub pet: bool,
25    pub params: HashMap<u32, String>,
26    pub model_data: NpcModelData,
27    pub animation_data: NpcAnimationData,
28}
29
30#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
31#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
32pub struct NpcModelData {
33    pub models: Vec<u16>,
34    pub chat_head_models: Vec<u16>,
35    pub recolor_find: Vec<u16>,
36    pub recolor_replace: Vec<u16>,
37    pub retexture_find: Vec<u16>,
38    pub retexture_replace: Vec<u16>,
39    pub width_scale: u16,
40    pub height_scale: u16,
41    pub render_priority: bool,
42    pub ambient: u8,
43    pub contrast: u8,
44    pub head_icon: Option<u16>,
45    pub rotate_speed: u16,
46    pub rotate_flag: bool,
47}
48
49#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
50#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
51pub struct NpcAnimationData {
52    pub standing: Option<u16>,
53    pub walking: Option<u16>,
54    pub rotate_left: Option<u16>,
55    pub rotate_right: Option<u16>,
56    pub rotate_180: Option<u16>,
57    pub rotate_90_left: Option<u16>,
58    pub rotate_90_right: Option<u16>,
59}
60
61impl Definition for NpcDefinition {
62    fn new(id: u16, buffer: &[u8]) -> crate::Result<Self> {
63        let mut reader = BufReader::new(buffer);
64        let npc_def = decode_buffer(id, &mut reader)?;
65
66        Ok(npc_def)
67    }
68}
69
70#[allow(clippy::too_many_lines)]
71fn decode_buffer(id: u16, reader: &mut BufReader<&[u8]>) -> io::Result<NpcDefinition> {
72    let mut npc_def = NpcDefinition {
73        id,
74        interactable: true,
75        visible_on_minimap: true,
76        model_data: NpcModelData {
77            rotate_flag: true,
78            width_scale: 128,
79            height_scale: 128,
80            rotate_speed: 32,
81            ..NpcModelData::default()
82        },
83        ..NpcDefinition::default()
84    };
85
86    loop {
87        let opcode = reader.read_u8()?;
88
89        match opcode {
90            0 => break,
91            1 => {
92                let len = reader.read_u8()?;
93                for _ in 0..len {
94                    npc_def.model_data.models.push(reader.read_u16()?);
95                }
96            }
97            2 => {
98                npc_def.name = reader.read_string()?;
99            }
100            12 => {
101                npc_def.size = reader.read_u8()? as usize;
102            }
103            13 => {
104                npc_def.animation_data.standing = Some(reader.read_u16()?);
105            }
106            14 => {
107                npc_def.animation_data.walking = Some(reader.read_u16()?);
108            }
109            15 => {
110                npc_def.animation_data.rotate_left = Some(reader.read_u16()?);
111            }
112            16 => {
113                npc_def.animation_data.rotate_right = Some(reader.read_u16()?);
114            }
115            17 => {
116                npc_def.animation_data.walking = Some(reader.read_u16()?);
117                npc_def.animation_data.rotate_180 = Some(reader.read_u16()?);
118                npc_def.animation_data.rotate_90_right = Some(reader.read_u16()?);
119                npc_def.animation_data.rotate_90_left = Some(reader.read_u16()?);
120            }
121            30..=34 => {
122                npc_def.actions[opcode as usize - 30] = reader.read_string()?;
123            }
124            40 => {
125                let len = reader.read_u8()?;
126                for _ in 0..len {
127                    npc_def.model_data.recolor_find.push(reader.read_u16()?);
128                    npc_def.model_data.recolor_replace.push(reader.read_u16()?);
129                }
130            }
131            41 => {
132                let len = reader.read_u8()?;
133                for _ in 0..len {
134                    npc_def.model_data.retexture_find.push(reader.read_u16()?);
135                    npc_def
136                        .model_data
137                        .retexture_replace
138                        .push(reader.read_u16()?);
139                }
140            }
141            60 => {
142                let len = reader.read_u8()?;
143                for _ in 0..len {
144                    npc_def.model_data.chat_head_models.push(reader.read_u16()?);
145                }
146            }
147            93 => npc_def.visible_on_minimap = true,
148            95 => {
149                npc_def.combat_level = Some(reader.read_u16()?);
150            }
151            97 => {
152                npc_def.model_data.width_scale = reader.read_u16()?;
153            }
154            98 => {
155                npc_def.model_data.height_scale = reader.read_u16()?;
156            }
157            99 => npc_def.model_data.render_priority = true,
158            100 => {
159                npc_def.model_data.ambient = reader.read_u8()?;
160            }
161            101 => {
162                npc_def.model_data.contrast = reader.read_u8()?;
163            }
164            102 => {
165                npc_def.model_data.head_icon = Some(reader.read_u16()?);
166            }
167            103 => {
168                npc_def.model_data.rotate_speed = reader.read_u16()?;
169            }
170            106 => {
171                let varbit_id = reader.read_u16()?;
172                npc_def.varbit_id = if varbit_id == std::u16::MAX {
173                    None
174                } else {
175                    Some(varbit_id)
176                };
177
178                let varp_index = reader.read_u16()?;
179                npc_def.varp_index = if varp_index == std::u16::MAX {
180                    None
181                } else {
182                    Some(varp_index)
183                };
184
185                npc_def.configs = Vec::new();
186                let len = reader.read_u8()?;
187                for _ in 0..=len {
188                    npc_def.configs.push(reader.read_u16()?);
189                }
190            }
191            107 => npc_def.interactable = false,
192            109 => npc_def.model_data.rotate_flag = false,
193            111 => npc_def.pet = true,
194            118 => {
195                let varbit_id = reader.read_u16()?;
196                npc_def.varbit_id = if varbit_id == std::u16::MAX {
197                    None
198                } else {
199                    Some(varbit_id)
200                };
201
202                let varp_index = reader.read_u16()?;
203                npc_def.varp_index = if varp_index == std::u16::MAX {
204                    None
205                } else {
206                    Some(varp_index)
207                };
208
209                // should append var at end
210                let _var = reader.read_u16()?;
211
212                npc_def.configs = Vec::new();
213                let len = reader.read_u8()?;
214                for _ in 0..=len {
215                    npc_def.configs.push(reader.read_u16()?);
216                }
217            }
218            249 => {
219                npc_def.params = util::read_parameters(reader)?;
220            }
221            _ => unreachable!(),
222        }
223    }
224
225    Ok(npc_def)
226}