An unofficial collection of APIs used in FreeJam games and mods
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

225 lines
7.4KB

  1. use base64::{decode_config_buf, STANDARD};
  2. use std::io::Read;
  3. // TODO(maybe) parse iteratively instead of one-shot
  4. /// A collection of cube data
  5. ///
  6. /// This holds all data parsed from cube_data and colour_data.
  7. /// Individual Cube structs can be iterated through.
  8. #[derive(Clone)]
  9. pub struct Cubes {
  10. /// Parsed cube count (the first 32 bits of data parsed to `u32`)
  11. pub provided_len: u32,
  12. cubes: Vec<Cube>,
  13. }
  14. impl Cubes {
  15. /// Process the raw bytes containing block data from a Robocraft CRF bot
  16. ///
  17. /// `cube_data` and `colour_data` correspond to the `cube_data` and `colour_data` fields of FactoryRobotGetInfo.
  18. /// In general, you should use `Cubes::from<FactoryRobotGetInfo>(data)` instead of this lower-level function.
  19. pub fn parse(cube_data: &mut Vec<u8>, colour_data: &mut Vec<u8>) -> Result<Self, ()> {
  20. // read first 4 bytes (cube count) from both arrays and make sure they match
  21. let mut cube_buf = [0; 4];
  22. let mut colour_buf = [0; 4];
  23. let mut cube_slice = cube_data.as_slice();
  24. let mut colour_slice = colour_data.as_slice();
  25. if let Ok(len) = cube_slice.read(&mut cube_buf) {
  26. if len != 4 {
  27. //println!("Failed reading cube_data len");
  28. return Err(());
  29. }
  30. } else {
  31. //println!("Failed to read cube_data");
  32. return Err(());
  33. }
  34. if let Ok(len) = colour_slice.read(&mut colour_buf) {
  35. if len != 4 {
  36. //println!("Failed reading colour_data len");
  37. return Err(());
  38. }
  39. } else {
  40. //println!("Failed to read colour_data");
  41. return Err(());
  42. }
  43. if !(cube_buf[0] == colour_buf[0]
  44. && cube_buf[1] == colour_buf[1]
  45. && cube_buf[2] == colour_buf[2]
  46. && cube_buf[3] == colour_buf[3]) {
  47. //println!("Values do not match");
  48. return Err(());
  49. }
  50. let mut cube_i = 4;
  51. let mut colour_i = 4;
  52. let mut parsed_cubes = Vec::with_capacity(cube_data.len() / 8);
  53. while cube_i < cube_data.len() && colour_i < colour_data.len() {
  54. let mut new_cube = Cube::default();
  55. if let Ok(cube_add) = new_cube.parse_cube_data(&mut cube_slice) {
  56. if let Ok(colour_add) = new_cube.parse_colour_data(&mut colour_slice) {
  57. cube_i += cube_add;
  58. colour_i += colour_add;
  59. parsed_cubes.push(new_cube);
  60. } else {
  61. // colour_data read error
  62. return Err(());
  63. }
  64. } else {
  65. // cube_data read error
  66. return Err(());
  67. }
  68. }
  69. Ok(Self {
  70. provided_len: u32::from_le_bytes(cube_buf),
  71. cubes: parsed_cubes,
  72. })
  73. }
  74. /// Dump the raw bytes containing block data for a Robocraft bot.
  75. ///
  76. /// The first tuple item is cube data, and the second item is colour data.
  77. /// Use this to write a modified robot to file.
  78. /// This is the inverse of `Cubes::parse(...)`.
  79. ///
  80. /// I'm not sure what this would actually be useful for...
  81. pub fn dump(&self) -> (Vec<u8>, Vec<u8>) {
  82. let mut cube_buf = Vec::new();
  83. let mut colour_buf = Vec::new();
  84. cube_buf.extend(&self.provided_len.to_le_bytes());
  85. colour_buf.extend(&self.provided_len.to_le_bytes());
  86. for c in self.into_iter() {
  87. cube_buf.extend(&c.dump_cube_data());
  88. colour_buf.extend(&c.dump_colour_data());
  89. }
  90. (cube_buf, colour_buf)
  91. }
  92. /// Get the actual amount of cubes.
  93. ///
  94. /// This differs from `provided_len` by being the amount of cubes parsed (successfully), instead of something parsed from block data.
  95. /// For any valid robot data, `data.provided_len == data.len()`.
  96. pub fn len(&self) -> usize {
  97. self.cubes.len()
  98. }
  99. }
  100. impl<'a> std::iter::IntoIterator for &'a Cubes {
  101. type Item = &'a Cube;
  102. type IntoIter = std::slice::Iter<'a, Cube>;
  103. fn into_iter(self) -> Self::IntoIter {
  104. self.cubes.iter()
  105. }
  106. }
  107. /// A single block in a Robocraft robot.
  108. ///
  109. /// From the front of a Robocraft garage bay, looking at the back, all positions are measured from the back bottom right corner.
  110. #[derive(Copy, Clone)]
  111. pub struct Cube {
  112. /// The cube id
  113. pub id: u32,
  114. /// The cube's x position (left to right)
  115. pub x: u8, // left to right
  116. /// The cube's y position (bottom to top)
  117. pub y: u8, // bottom to top
  118. /// The cube's z position (back to front)
  119. pub z: u8, // back to front
  120. /// The cube's orientation
  121. pub orientation: u8,
  122. /// The cube's colour, one of the 24 possible colours in Robocraft
  123. pub colour: u8,
  124. }
  125. impl Cube {
  126. fn parse_cube_data(&mut self, reader: &mut dyn Read) -> Result<usize, ()> {
  127. let mut buf = [0; 4];
  128. // read cube id
  129. if let Ok(len) = reader.read(&mut buf) {
  130. if len != 4 {
  131. return Err(());
  132. }
  133. self.id = u32::from_le_bytes(buf);
  134. } else {
  135. return Err(());
  136. }
  137. // read x, y, z, orientation
  138. if let Ok(len) = reader.read(&mut buf) {
  139. if len != 4 {
  140. return Err(());
  141. }
  142. self.x = buf[0];
  143. self.y = buf[1];
  144. self.z = buf[2];
  145. self.orientation = buf[3];
  146. } else {
  147. return Err(());
  148. }
  149. Ok(8)
  150. }
  151. fn parse_colour_data(&mut self, reader: &mut dyn Read) -> Result<usize, ()> {
  152. let mut buf = [0; 4];
  153. if let Ok(len) = reader.read(&mut buf) {
  154. if len != 4 {
  155. return Err(());
  156. }
  157. self.colour = buf[0];
  158. } else {
  159. return Err(());
  160. }
  161. Ok(4)
  162. }
  163. /// Dump the raw cube data as used in the Robocraft CRF.
  164. ///
  165. /// This is useless by itself, use `Cubes.dump()` for a valid robot.
  166. pub fn dump_cube_data(&self) -> [u8; 8] {
  167. let id_buf = self.id.to_le_bytes();
  168. [id_buf[0], id_buf[1], id_buf[2], id_buf[3], self.x, self.y, self.z, self.orientation]
  169. }
  170. /// Dump the raw colour data as used in the Robocraft CRF.
  171. ///
  172. /// This is useless by itself, use `Cubes.dump()` for a valid robot.
  173. pub fn dump_colour_data(&self) -> [u8; 4] {
  174. [self.colour, self.x, self.y, self.z]
  175. }
  176. }
  177. impl std::default::Default for Cube {
  178. fn default() -> Self {
  179. Self {
  180. id: 0,
  181. x: 0,
  182. y: 0,
  183. z: 0,
  184. orientation: 0,
  185. colour: 0,
  186. }
  187. }
  188. }
  189. impl std::convert::From<crate::robocraft::FactoryRobotGetInfo> for Cubes {
  190. fn from(other: crate::robocraft::FactoryRobotGetInfo) -> Self {
  191. let mut cube_buf = Vec::new();
  192. let mut colour_buf = Vec::new();
  193. decode_config_buf(other.cube_data, STANDARD, &mut cube_buf).unwrap();
  194. decode_config_buf(other.colour_data, STANDARD, &mut colour_buf).unwrap();
  195. Self::parse(&mut cube_buf, &mut colour_buf).unwrap()
  196. }
  197. }
  198. impl std::convert::From<crate::robocraft::FactoryInfo<crate::robocraft::FactoryRobotGetInfo>> for Cubes {
  199. fn from(other: crate::robocraft::FactoryInfo<crate::robocraft::FactoryRobotGetInfo>) -> Self {
  200. Self::from(other.response)
  201. }
  202. }
  203. impl std::string::ToString for Cube {
  204. fn to_string(&self) -> String {
  205. format!("{{x: {}, y: {}, z: {}}} ({})", self.x, self.y, self.z, self.id)
  206. }
  207. }