use crate::{
MAGIC_NUMBER, SUPPORTED_VERSIONS, array_len,
banner::Banner,
entity::{Entity, EntityType},
error::{Error, Result},
remap, round_division_i128,
tile::{LiquidType, Tile, TileShape, TileType, WallType},
world::{
BestiaryEntry, Chest, Difficulty, Evil, Home, Invasion, Sign, TILE_IS_IMPORTANT, TILE_SIZE,
TeamSpawns, TileEntity, TileEntityType, World,
},
};
use bounded_vector::BoundedVec;
use byteorder::LittleEndian;
use byteorder::WriteBytesExt;
use cs_datetime_parse::DateTimeCs;
use cs_string_rw::WriteCsStrExt;
use grid::Grid;
use std::{
collections::{HashMap, HashSet},
io::{self, Seek, Write},
};
use terra_items::WorldItemSlot;
use terra_types::{InPixels, InTiles, Rain, TerrariaFileType, Unit, Vec2};
use time::{Duration, OffsetDateTime, Time};
use uuid::Uuid;
pub const OFFSET_COUNT: usize = 11;
impl World {
pub fn write<W: Write + Seek>(&self, writer: W, version: i32) -> Result<()> {
WorldWriter::new(writer, version)?.write_world(self)
}
}
pub struct WorldWriter<W: Write + Seek> {
version: i32,
writer: W,
offsets_offset: u64,
tile_is_important: Vec<bool>,
}
impl<W: Write + Seek> WorldWriter<W> {
pub fn new(writer: W, version: i32) -> Result<WorldWriter<W>> {
if !SUPPORTED_VERSIONS.contains(&version) {
return Err(Error::UnsupportedVersion(version));
}
Ok(WorldWriter {
version,
writer,
offsets_offset: 0,
tile_is_important: vec![],
})
}
pub fn write_world(&mut self, wld: &World) -> Result<()> {
self.tile_is_important = get_tile_is_important(&wld.tiles);
let mut offsets = [0; OFFSET_COUNT];
self.write_first_header(wld)?;
offsets[0] = self.writer.stream_position()? as i32;
self.write_second_header(wld)?;
offsets[1] = self.writer.stream_position()? as i32;
self.write_tiles(&wld.tiles)?;
offsets[2] = self.writer.stream_position()? as i32;
self.write_chests(&wld.chests)?;
offsets[3] = self.writer.stream_position()? as i32;
self.write_signs(&wld.signs)?;
offsets[4] = self.writer.stream_position()? as i32;
self.write_npc_is_shimmer(wld)?;
self.write_entities(&wld.entities)?;
offsets[5] = self.writer.stream_position()? as i32;
self.write_title_entities(&wld.tile_entities)?;
offsets[6] = self.writer.stream_position()? as i32;
self.write_weighted_pressure_plates(&wld.weighted_pressure_plates)?;
offsets[7] = self.writer.stream_position()? as i32;
self.write_homes(&wld.homes)?;
offsets[8] = self.writer.stream_position()? as i32;
self.write_bestiary(&wld.bestiary)?;
offsets[9] = self.writer.stream_position()? as i32;
self.write_journey_powers(wld)?;
offsets[10] = self.writer.stream_position()? as i32;
self.write_footer(wld)?;
self.write_offsets(offsets).map_err(|x| x.into())
}
fn write_offsets(&mut self, offsets: [i32; 11]) -> io::Result<()> {
if self.version < 88 {
return Ok(());
}
let pos: u64 = self.writer.stream_position()?;
self.writer.seek(io::SeekFrom::Start(self.offsets_offset))?;
for offset in offsets {
self.write_i32(offset)?;
}
self.writer.seek(io::SeekFrom::Start(pos)).map(|_| ())
}
fn write_tiles(&mut self, tiles: &Grid<Tile>) -> io::Result<()> {
if self.version >= 88 {
self.write_tiles_new(tiles)
} else {
self.write_tiles_old(tiles)
}
}
fn write_tiles_new(&mut self, tiles: &Grid<Tile>) -> io::Result<()> {
let is_flag4_enabled = self.version > 194;
for col_idx in 0..tiles.size().1 {
let mut col = tiles.iter_col(col_idx).peekable();
while let Some(world_tile) = col.next() {
let mut repeats: i16 = 0;
while let Some(next_tile) = col.peek() {
if *next_tile != world_tile {
break;
}
if repeats == i16::MAX {
break;
}
_ = col.next();
repeats += 1;
}
let flag4 = if is_flag4_enabled {
(world_tile.tile.is_some_and(|x| x.is_invisible) as u8) << 1
| (world_tile.wall.is_some_and(|x| x.is_invisible) as u8) << 2
| (world_tile.tile.is_some_and(|x| x.is_full_bright) as u8) << 3
| (world_tile.wall.is_some_and(|x| x.is_full_bright) as u8) << 4
} else {
0
};
let is_tile_paint = self.version < 258
&& world_tile.tile.is_some_and(|x| x.is_full_bright)
|| world_tile.tile.is_some_and(|x| x.paint.is_some());
let is_wall_paint = self.version < 258
&& world_tile.wall.is_some_and(|x| x.is_full_bright)
|| world_tile.wall.is_some_and(|x| x.paint.is_some());
let flag3 = (flag4 != 0) as u8
| (world_tile.is_actuator as u8) << 1
| (world_tile.tile.is_some_and(|x| x.is_actuated) as u8) << 2
| (is_tile_paint as u8) << 3
| (is_wall_paint as u8) << 4
| (world_tile.is_yellow_wire as u8) << 5
| (world_tile.wall.is_some_and(|x| x.wall.to_id() > 255) as u8) << 6
| (world_tile
.liquid
.is_some_and(|x| x.liquid == LiquidType::Shimmer) as u8)
<< 7;
let shape = world_tile
.tile
.map(|x| match x.shape {
TileShape::Full => 0,
TileShape::Half => 1,
TileShape::SlopeDownRight => 2,
TileShape::SlopeDownLeft => 3,
TileShape::SlopeUpRight => 4,
TileShape::SlopeUpLeft => 5,
})
.unwrap_or(0);
let flag2 = (flag3 != 0) as u8
| (world_tile.is_red_wire as u8) << 1
| (world_tile.is_blue_wire as u8) << 2
| (world_tile.is_green_wire as u8) << 3
| shape << 4;
let liquid = world_tile
.liquid
.map(|x| match x.liquid {
LiquidType::Water | LiquidType::Shimmer => 1,
LiquidType::Lava => 2,
LiquidType::Honey => 3,
})
.unwrap_or(0);
let repeat_flag = match repeats {
..=0 => 0,
1..=255 => 1,
256.. => 2,
};
let flag1 = ((flag2 != 0) as u8)
| (world_tile.tile.is_some() as u8) << 1
| (world_tile.wall.is_some() as u8) << 2
| (world_tile.tile.is_some_and(|x| x.tile.to_id() > 255) as u8) << 5
| liquid << 3 | repeat_flag << 6;
self.write_u8(flag1)?;
if flag2 != 0 {
self.write_u8(flag2)?;
}
if flag3 != 0 {
self.write_u8(flag3)?;
}
if flag4 != 0 {
self.write_u8(flag4)?;
}
if let Some(tile) = world_tile.tile {
let tile_id = tile.tile.to_id();
if tile_id < 256 {
self.write_u8(tile_id as u8)?;
} else {
self.write_u16(tile_id)?;
}
if self
.tile_is_important
.get(tile_id as usize)
.is_some_and(|x| *x)
{
let offset = tile.texture_coords.unwrap_or(Vec2::new(0, 0));
self.write_i16(offset.x)?;
self.write_i16(offset.y)?;
}
if let Some(paint) = tile.paint {
self.write_u8(paint.to_id())?;
} else if self.version < 258 && tile.is_full_bright {
self.write_u8(31)?;
}
}
if let Some(wall) = world_tile.wall {
let wall_id = wall.wall.to_id();
self.write_u8((wall_id & 0xFF) as u8)?;
if let Some(paint) = wall.paint {
self.write_u8(paint.to_id())?;
} else if self.version < 258 && wall.is_full_bright {
self.write_u8(31)?;
}
}
if let Some(liquid) = world_tile.liquid {
self.write_u8(liquid.level.get())?;
}
if let Some(wall_id) = world_tile.wall.map(|x| x.wall.to_id())
&& wall_id > 255
{
self.write_u8((wall_id >> 8) as u8)?;
}
if repeats > 255 {
self.write_i16(repeats)?;
} else if repeats > 0 {
self.write_u8(repeats as u8)?;
}
}
}
Ok(())
}
fn write_tile_frame_old(
&mut self,
tile: TileType,
texture_coords: Option<Vec2<InPixels, i16>>,
) -> io::Result<()> {
let texture_coords = texture_coords.unwrap_or(Vec2::new(0, 0));
if self.version < 72 && tile == TileType::Chandeliers {
self.write_i16(texture_coords.x % (TILE_SIZE * 6))?;
self.write_i16(texture_coords.y % (TILE_SIZE * 3))?;
return Ok(());
}
if !TILE_IS_IMPORTANT
.get(tile.to_id() as usize)
.is_some_and(|x| *x)
{
return Ok(());
}
if self.version < 28 && tile == TileType::Torches {
return Ok(());
}
if self.version < 40 && tile == TileType::Platforms {
return Ok(());
}
if tile == TileType::WaterCandle {
return Ok(());
}
self.write_i16(texture_coords.x)?;
self.write_i16(texture_coords.y)
}
fn write_tiles_old(&mut self, tiles: &Grid<Tile>) -> io::Result<()> {
for col_idx in 0..tiles.size().1 {
let mut col = tiles.iter_col(col_idx).peekable();
while let Some(world_tile) = col.next() {
let mut repeats: i16 = 0;
if self.version >= 25 {
while let Some(next_tile) = col.peek() {
if *next_tile != world_tile {
break;
}
if repeats == i16::MAX {
break;
}
_ = col.next();
repeats += 1;
}
}
if let Some(tile) = world_tile.tile {
self.write_bool(true)?;
let tile_id = if self.version < 72 && tile.tile == TileType::Chandeliers {
let texture_y = tile.texture_coords.map(|x| x.y).unwrap_or(0).max(0);
[34, 35, 36, 170, 171, 172]
.get((texture_y / (TILE_SIZE * 3)) as usize)
.cloned()
.unwrap_or(34)
} else {
tile.tile.to_id()
};
if self.version >= 78 {
self.write_u16(tile_id)?;
} else if let Ok(tile_id) = u8::try_from(tile_id) {
self.write_u8(tile_id)?;
} else {
self.write_u8(TileType::Dirt.to_id() as u8)?;
}
self.write_tile_frame_old(tile.tile, tile.texture_coords)?;
if self.version >= 48 {
if let Some(paint) = tile.paint {
self.write_bool(true)?;
self.write_u8(paint.to_id())?;
} else {
self.write_bool(false)?;
}
}
} else {
self.write_bool(false)?;
}
if self.version <= 25 {
const IS_LIGHTED: bool = true;
self.write_bool(IS_LIGHTED)?;
}
if let Some(wall) = world_tile.wall {
self.write_bool(true)?;
self.write_u8(
u8::try_from(wall.wall.to_id()).unwrap_or(WallType::Dirt.to_id() as u8),
)?;
if self.version >= 48 {
if let Some(paint) = wall.paint {
self.write_bool(true)?;
self.write_u8(paint.to_id())?;
} else {
self.write_bool(false)?;
}
}
} else {
self.write_bool(false)?;
}
if let Some(liquid) = world_tile.liquid {
self.write_bool(true)?;
self.write_u8(liquid.level.get())?;
self.write_bool(liquid.liquid == LiquidType::Lava)?;
if self.version >= 51 {
self.write_bool(liquid.liquid == LiquidType::Honey)?;
}
} else {
self.write_bool(false)?;
}
if self.version >= 33 {
self.write_bool(world_tile.is_red_wire)?;
}
if self.version >= 43 {
self.write_bool(world_tile.is_blue_wire)?;
}
if self.version >= 43 {
self.write_bool(world_tile.is_green_wire)?;
}
let shape = world_tile.tile.map(|x| x.shape).unwrap_or(TileShape::Full);
match self.version {
..=40 => {}
41..=48 => {
self.write_bool(shape == TileShape::Half)?;
}
49.. => {
self.write_bool(shape == TileShape::Half)?;
self.write_u8(match shape {
TileShape::Full | TileShape::Half => 0,
TileShape::SlopeDownRight => 1,
TileShape::SlopeDownLeft => 2,
TileShape::SlopeUpRight => 3,
TileShape::SlopeUpLeft => 4,
})?;
}
}
if self.version >= 42 {
self.write_bool(world_tile.is_actuator)?;
}
if self.version >= 42 {
self.write_bool(world_tile.tile.is_some_and(|x| x.is_actuated))?;
}
if self.version >= 25 {
self.write_i16(repeats)?;
}
}
}
Ok(())
}
fn write_first_header(&mut self, wld: &World) -> io::Result<()> {
self.write_i32(self.version)?;
if self.version >= 135 {
self.writer.write_all(MAGIC_NUMBER)?;
self.write_u8(TerrariaFileType::World.to_id().expect("File type id"))?;
self.write_u32(wld.times_saved)?;
self.write_u64(wld.is_favorite as u8 as u64)?;
}
if self.version >= 88 {
self.write_i16(OFFSET_COUNT as i16)?;
self.offsets_offset = self.writer.stream_position()?;
for _ in 0..OFFSET_COUNT {
self.write_i32(0)?;
}
self.write_i16(self.tile_is_important.len() as i16)?;
let mut byte = 0;
for (i, x) in self.tile_is_important.iter().enumerate() {
byte >>= 1;
byte |= (*x as u8) * 0x80;
if i % 8 == 7 {
self.writer.write_u8(byte)?;
byte = 0;
}
}
if !self.tile_is_important.len().is_multiple_of(8) {
self.writer.write_u8(byte)?;
}
}
Ok(())
}
fn write_second_header(&mut self, wld: &World) -> Result<()> {
self.write_string(&wld.name)?;
if self.version > 179 {
self.write_string(wld.seed.as_deref().unwrap_or(""))?;
}
if self.version == 179 {
self.write_i32(wld.seed.as_ref().and_then(|x| x.parse().ok()).unwrap_or(0))?;
}
if self.version >= 179 {
self.write_u64(wld.generator_version.unwrap_or(0))?;
}
if self.version >= 181 {
self.writer.write_all(&Uuid::new_v4().to_bytes_le())?;
}
self.write_i32(wld.id)?;
self.write_i32(wld.left_border.value())?;
self.write_i32(wld.right_border.value())?;
self.write_i32(wld.top_border.value())?;
self.write_i32(wld.bottom_border.value())?;
self.write_i32(wld.tiles.rows() as i32)?;
self.write_i32(wld.tiles.cols() as i32)?;
match self.version {
209.. => self.write_i32(wld.difficulty.to_id())?,
208 => {
self.write_bool(wld.difficulty == Difficulty::Expert)?;
self.write_bool(wld.difficulty == Difficulty::Master)?;
}
112..208 => self.write_bool(
wld.difficulty == Difficulty::Expert || wld.difficulty == Difficulty::Master,
)?,
_ => {}
}
if self.version >= 222 {
self.write_bool(wld.is_drunk_world)?;
}
if self.version >= 227 {
self.write_bool(wld.is_for_the_worthy_world)?;
}
if self.version >= 238 {
self.write_bool(wld.is_tenth_anniversary_world)?;
}
if self.version >= 239 {
self.write_bool(wld.is_dont_starve_world)?;
}
if self.version >= 241 {
self.write_bool(wld.is_not_the_bees_world)?;
}
if self.version >= 249 {
self.write_bool(wld.is_remix_world)?;
}
if self.version >= 266 {
self.write_bool(wld.is_no_traps_world)?;
}
if self.version >= 267 {
self.write_bool(wld.is_zenith_world)?;
}
if self.version >= 302 {
self.write_bool(wld.is_skyblock_world)?;
}
if self.version >= 141 {
self.write_i64(
wld.creation_time
.unwrap_or_else(|| DateTimeCs::Utc(OffsetDateTime::now_utc()))
.to_binary()
.map_err(Error::CreationTimeWrite)?,
)?;
}
if self.version >= 284 {
self.write_i64(
wld.last_played
.unwrap_or_else(|| DateTimeCs::Utc(OffsetDateTime::now_utc()))
.to_binary()
.map_err(Error::LastPlayedWrite)?,
)?;
}
if self.version >= 63 {
self.write_u8(wld.moon_type.to_id())?;
}
if self.version >= 44 {
for i in 0..3 {
self.write_i32(wld.tree_style_changes[i].value())?;
}
for i in 0..4 {
self.write_i32(wld.tree_styles[i])?;
}
}
if self.version >= 60 {
for i in 0..3 {
self.write_i32(wld.cave_back_style_changes[i].value())?;
}
for i in 0..4 {
self.write_i32(wld.cave_back_styles[i])?;
}
self.write_i32(wld.ice_cave_background)?;
}
if self.version >= 61 {
self.write_i32(wld.jungle_cave_background)?;
self.write_i32(wld.hell_background)?;
}
self.write_vec2_i32(wld.spawn)?;
self.write_f64(wld.surface_level.value())?;
self.write_f64(wld.cavern_level.value())?;
self.write_in_game_time(wld.time)?;
self.write_i32(wld.moon_phase.to_id() as i32)?;
self.write_bool(wld.is_blood_moon)?;
if self.version >= 70 {
self.write_bool(wld.is_solar_eclipse)?;
}
self.write_vec2_i32(wld.dungeon)?;
if self.version >= 56 {
self.write_bool(wld.evil == Evil::Crimson)?;
}
self.write_bool(wld.is_eye_of_cthulhu_downed)?;
self.write_bool(wld.is_eater_of_worlds_or_brain_of_cthulhu_downed)?;
self.write_bool(wld.is_skeleton_downed)?;
if self.version >= 66 {
self.write_bool(wld.is_queen_bee_downed)?;
}
if self.version >= 44 {
self.write_bool(wld.is_destroyer_downed)?;
self.write_bool(wld.is_twins_downed)?;
self.write_bool(wld.is_skeletron_prime_downed)?;
self.write_bool(wld.is_any_mechanical_boss_downed)?;
}
if self.version >= 64 {
self.write_bool(wld.is_plantera_downed)?;
self.write_bool(wld.is_golem_downed)?;
}
if self.version >= 118 {
self.write_bool(wld.is_king_slime_downed)?;
}
if self.version >= 29 {
self.write_bool(wld.is_goblin_tinkerer_saved)?;
self.write_bool(wld.is_wizard_saved)?;
}
if self.version >= 34 {
self.write_bool(wld.is_mechanic_saved)?;
}
if self.version >= 80 && self.version <= 87 {
self.write_bool(wld.is_stylist_saved)?;
}
if self.version >= 29 {
self.write_bool(wld.is_goblin_army_downed)?;
}
if self.version >= 32 {
self.write_bool(wld.is_clown_downed)?;
}
if self.version >= 37 {
self.write_bool(wld.is_frost_legion_downed)?;
}
if self.version >= 56 {
self.write_bool(wld.is_pirate_invasion_downed)?;
}
self.write_bool(wld.is_shadow_orb_or_crimson_heart_destroyed)?;
self.write_bool(wld.is_meteor_to_spawn)?;
self.write_u8(wld.destroyed_shadow_orb_or_crimson_heart_count)?;
if self.version >= 23 {
self.write_i32(wld.destroyed_altar_count)?;
self.write_bool(wld.is_hardmode)?;
}
if self.version >= 257 {
self.write_bool(wld.is_after_party_of_doom)?;
}
self.write_i32(wld.invasion_cooldown.value())?;
self.write_invasion(wld.invasion)?;
if self.version >= 118 {
self.write_duration_terra_ticks_f64(wld.slime_rain_time)?;
}
if self.version >= 113 {
self.write_u8(wld.sundial_cooldown.value())?;
}
if self.version >= 53 {
self.write_rain(wld.rain)?;
}
if self.version >= 54 {
self.write_i32(wld.tier_1_hardmode_ore.unwrap_or(TileType::Cobalt).to_id() as i32)?;
self.write_i32(wld.tier_2_hardmode_ore.unwrap_or(TileType::Mythril).to_id() as i32)?;
self.write_i32(
wld.tier_3_hardmode_ore
.unwrap_or(TileType::Adamantite)
.to_id() as i32,
)?;
}
if self.version >= 55 {
self.write_u8(wld.forest_background)?;
self.write_u8(wld.corruption_background)?;
self.write_u8(wld.jungle_background)?;
}
if self.version >= 60 {
self.write_u8(wld.snow_background)?;
self.write_u8(wld.hallow_background)?;
self.write_u8(wld.crimson_background)?;
self.write_u8(wld.desert_background)?;
self.write_u8(wld.ocean_background)?;
}
if self.version >= 60 {
self.write_i32(wld.cloud_bg_active)?;
}
if self.version >= 62 {
self.write_i16(wld.clouds_count)?;
self.write_f32(wld.wind.value() / 50.0)?;
}
if self.version >= 95 {
self.write_i32(wld.players_finished_angler_quest_today.len() as i32)?;
for name in &wld.players_finished_angler_quest_today {
self.write_string(name)?;
}
}
if self.version >= 99 {
self.write_bool(wld.is_angler_saved)?;
}
if self.version >= 101 {
self.write_i32(wld.angler_quest.to_id())?;
}
if self.version >= 104 {
self.write_bool(wld.is_stylist_saved)?;
}
if self.version >= 129 {
self.write_bool(wld.is_tax_collector_saved)?;
}
if self.version >= 201 {
self.write_bool(wld.is_golfer_saved)?;
}
if self.version >= 107 {
self.write_i32(wld.invasion.map(|x| x.start_size).unwrap_or(0))?;
}
if self.version >= 108 {
self.write_duration_terra_ticks_i32(wld.cultist_delay)?;
}
if self.version >= 109 {
let max_id = wld.kill_count.keys().map(|x| x.to_id()).max().unwrap_or(0);
self.write_i16(max_id + 1)?;
for i in 0..=max_id {
self.write_i32(
Banner::from_id(i)
.and_then(|x| wld.kill_count.get(&x).copied())
.unwrap_or(0),
)?;
}
}
if self.version >= 289 {
let max_id = wld.kill_count.keys().map(|x| x.to_id()).max().unwrap_or(0);
self.write_i16(max_id + 1)?;
for i in 0..=max_id {
self.write_u16(
Banner::from_id(i)
.and_then(|x| wld.claimable_banners.get(&x).copied())
.unwrap_or(0),
)?;
}
}
if self.version >= 128 {
self.write_bool(wld.is_sundial_forward_time_active)?;
}
if self.version >= 131 {
self.write_bool(wld.is_fishron_downed)?;
self.write_bool(wld.is_martian_madness_downed)?;
self.write_bool(wld.is_ancient_cultist_downed)?;
self.write_bool(wld.is_moonlord_downed)?;
self.write_bool(wld.is_pumpking_downed)?;
self.write_bool(wld.is_mourning_wood_downed)?;
self.write_bool(wld.is_ice_queen_downed)?;
self.write_bool(wld.is_santa_nk1_downed)?;
self.write_bool(wld.is_everscream_downed)?;
}
if self.version >= 140 {
self.write_bool(wld.is_solar_pillar_downed)?;
self.write_bool(wld.is_vortex_pillar_downed)?;
self.write_bool(wld.is_nebula_pillar_downed)?;
self.write_bool(wld.is_stardust_pillar_downed)?;
self.write_bool(wld.is_solar_pillar_active)?;
self.write_bool(wld.is_vortex_pillar_active)?;
self.write_bool(wld.is_nebula_pillar_active)?;
self.write_bool(wld.is_stardust_pillar_active)?;
self.write_bool(wld.is_lunar_apocalypse)?;
}
if self.version >= 170 {
if let Some(party) = &wld.party {
self.write_bool(party.is_mannual)?;
self.write_bool(!party.is_mannual)?;
} else {
self.write_bool(false)?;
self.write_bool(false)?;
}
self.write_duration_terra_ticks_i32(wld.party_cooldown)?;
let npcs = wld
.party
.as_ref()
.map(|x| &x.celebrating_npcs[..])
.unwrap_or(&[][..]);
self.write_i32(npcs.len() as i32)?;
for npc in npcs {
self.write_i32(npc.to_id())?;
}
}
if self.version >= 174 {
if let Some(sandstorm) = wld.sandstorm {
self.write_bool(true)?;
self.write_duration_terra_ticks_i32(sandstorm.time)?;
self.write_f32(sandstorm.current_severity)?;
self.write_f32(sandstorm.intended_severity)?;
} else {
self.write_bool(false)?;
self.write_duration_terra_ticks_i32(Duration::ZERO)?;
self.write_f32(0.0)?;
self.write_f32(0.0)?;
}
}
if self.version >= 178 {
self.write_bool(wld.is_bartender_saved)?;
self.write_bool(wld.is_dd2_t1_downed)?;
self.write_bool(wld.is_dd2_t2_downed)?;
self.write_bool(wld.is_dd2_t3_downed)?;
}
if self.version >= 195 {
self.write_u8(wld.mushroom_background)?;
}
if self.version >= 215 {
self.write_u8(wld.underworld_background)?;
}
if self.version >= 196 {
self.writer.write_all(&wld.tree_backgrounds)?;
}
if self.version >= 204 {
self.write_bool(wld.is_combat_book_used)?;
}
if self.version >= 207 {
self.write_i32(wld.lantern_night_cooldown.value())?;
self.write_bool(wld.lantern_night.is_some_and(|x| !x.is_mannual))?;
self.write_bool(wld.lantern_night.is_some_and(|x| x.is_mannual))?;
self.write_bool(wld.is_next_night_lantern_night)?;
}
if self.version >= 211 {
let tree_tops = [
wld.tree_tops_forest[0],
wld.tree_tops_forest[1],
wld.tree_tops_forest[2],
wld.tree_tops_forest[3],
wld.tree_tops_corruption,
wld.tree_tops_jungle,
wld.tree_tops_snow,
wld.tree_tops_hallow,
wld.tree_tops_crimson,
wld.tree_tops_desert,
wld.tree_tops_ocean,
wld.tree_tops_glowing_mushroom,
wld.tree_tops_underworld,
];
self.write_i32(array_len(&tree_tops) as i32)?;
for tree_top in tree_tops {
self.write_i32(tree_top)?;
}
}
if self.version >= 212 {
self.write_bool(wld.is_hallowen_forced_today)?;
self.write_bool(wld.is_xmas_forced_today)?;
}
if self.version >= 216 {
self.write_i32(wld.tier_1_ore.map(|x| x.to_id() as i32).unwrap_or(-1))?;
self.write_i32(wld.tier_2_ore.map(|x| x.to_id() as i32).unwrap_or(-1))?;
self.write_i32(wld.tier_3_ore.map(|x| x.to_id() as i32).unwrap_or(-1))?;
self.write_i32(wld.tier_4_ore.map(|x| x.to_id() as i32).unwrap_or(-1))?;
}
if self.version >= 217 {
self.write_bool(wld.is_cat_bought)?;
self.write_bool(wld.is_dog_bought)?;
self.write_bool(wld.is_bunny_bought)?;
}
if self.version >= 223 {
self.write_bool(wld.is_empress_of_light_downed)?;
self.write_bool(wld.is_queen_slime_downed)?;
}
if self.version >= 240 {
self.write_bool(wld.is_deerclops_downed)?;
}
if self.version >= 250 {
self.write_bool(wld.is_blue_slime_spawn_unlocked)?;
}
if self.version >= 251 {
self.write_bool(wld.is_merchant_spawn_unlocked)?;
self.write_bool(wld.is_demolitionist_spawn_unlocked)?;
self.write_bool(wld.is_party_girl_spawn_unlocked)?;
self.write_bool(wld.is_dye_trader_spawn_unlocked)?;
self.write_bool(wld.is_truffle_spawn_unlocked)?;
self.write_bool(wld.is_arms_dealer_spawn_unlocked)?;
self.write_bool(wld.is_nurse_spawn_unlocked)?;
self.write_bool(wld.is_princess_spawn_unlocked)?;
}
if self.version >= 259 {
self.write_bool(wld.is_combat_book_volume_two_used)?;
}
if self.version >= 260 {
self.write_bool(wld.is_peddlers_satchel_used)?;
}
if self.version >= 261 {
self.write_bool(wld.is_green_slime_spawn_unlocked)?;
self.write_bool(wld.is_old_slime_spawn_unlocked)?;
self.write_bool(wld.is_purple_slime_spawn_unlocked)?;
self.write_bool(wld.is_rainbow_slime_spawn_unlocked)?;
self.write_bool(wld.is_red_slime_unlocked)?;
self.write_bool(wld.is_yellow_slime_spawn_unlocked)?;
self.write_bool(wld.is_copper_slime_spawn_unlocked)?;
}
if self.version >= 264 {
self.write_bool(wld.is_moondial_forward_time_active)?;
self.write_u8(wld.moondial_cooldown.value())?;
}
if self.version >= 287 {
self.write_bool(wld.is_hallowen_forced_forever)?;
self.write_bool(wld.is_xmas_forced_forever)?;
}
if self.version >= 288 {
self.write_bool(wld.is_vampire_seed)?;
}
if self.version >= 296 {
self.write_bool(wld.is_infected_seed)?;
}
if self.version >= 291 {
self.write_i32(wld.meteor_shower_remaining)?;
self.write_i32(wld.coin_rain_remaining)?;
}
if self.version >= 297 {
self.write_bool(wld.team_spawns.is_some())?;
if let Some(team_spawns) = wld.team_spawns {
self.write_u8(TeamSpawns::COUNT)?;
self.write_vec2_i16(team_spawns.no_team)?;
self.write_vec2_i16(team_spawns.red)?;
self.write_vec2_i16(team_spawns.blue)?;
self.write_vec2_i16(team_spawns.green)?;
self.write_vec2_i16(team_spawns.yellow)?;
self.write_vec2_i16(team_spawns.pink)?;
} else {
self.write_u8(0)?;
}
}
if self.version >= 304 {
self.write_bool(wld.is_dual_dungeon_seed)?;
}
if self.version >= 299 && self.version < 313 {
self.write_u32(0)?; }
if self.version >= 299 {
self.write_string(
serde_json::to_string(&wld.manifest)
.map_err(Error::SerdeError)?
.as_str(),
)?
}
Ok(())
}
fn write_chests(&mut self, chests: &BoundedVec<Chest, 0, 8000>) -> io::Result<()> {
if self.version >= 88 {
self.write_chests_new_version(chests)
} else {
self.write_chests_old_version(chests)
}
}
fn write_chests_new_version(&mut self, chests: &BoundedVec<Chest, 0, 8000>) -> io::Result<()> {
self.write_i16(chests.len() as i16)?;
let max_slot_count = if self.version >= 294 {
i32::MAX as usize
} else {
let max_slot = chests
.iter()
.map(|x| x.slots.len())
.max()
.unwrap_or(40)
.max(i16::MAX as usize);
self.write_i16(max_slot as i16)?;
max_slot
};
for chest in chests.iter() {
self.write_vec2_i32(chest.position)?;
self.write_string(&chest.name)?;
let slot_count = if self.version >= 294 {
self.write_i32(chest.slots.len() as i32)?;
chest.slots.len()
} else {
max_slot_count
};
for slot in chest
.slots
.iter()
.chain(Some(None).iter().cycle())
.take(slot_count)
{
let Some(slot) = slot else {
self.write_i16(0)?;
continue;
};
self.write_i16(slot.count.get())?;
self.write_i32(slot.item.to_id(self.version))?;
self.write_u8(slot.prefix.map(|x| x.to_id()).unwrap_or(0))?;
}
}
Ok(())
}
fn write_chests_old_version(&mut self, chests: &[Chest]) -> io::Result<()> {
let slot_count = if self.version >= 58 { 40 } else { 20 };
for i in 0..1000 {
let Some(chest) = chests.get(i) else {
self.write_bool(false)?;
continue;
};
self.write_bool(true)?;
self.write_i32(chest.position.x)?;
self.write_i32(chest.position.y)?;
if self.version >= 85 {
self.write_string(&chest.name)?;
}
for slot in chest
.slots
.iter()
.chain(Some(None).iter().cycle())
.take(slot_count)
{
let Some(slot) = slot else {
if self.version >= 59 {
self.write_i16(0)?;
} else {
self.write_u8(0)?;
}
continue;
};
if self.version >= 59 {
self.write_i16(slot.count.get())?;
} else {
self.write_u8(slot.count.get().min(255) as u8)?;
}
if self.version >= 38 {
self.write_i32(slot.item.to_id(self.version))?;
} else {
self.write_string(slot.item.to_old_id(self.version).unwrap_or(""))?;
}
if self.version >= 36 {
self.write_u8(slot.prefix.map(|x| x.to_id()).unwrap_or(0))?;
}
}
}
Ok(())
}
fn write_signs(&mut self, signs: &BoundedVec<Sign, 0, 32000>) -> io::Result<()> {
if self.version >= 88 {
self.write_signs_new_version(signs)
} else {
self.write_signs_old_version(signs)
}
}
fn write_signs_new_version(&mut self, signs: &[Sign]) -> io::Result<()> {
let max = if self.version >= 315 { 32000 } else { 8000 };
self.write_i16(signs.len().min(max) as i16)?;
for sign in signs.iter().take(max) {
self.write_string(&sign.text)?;
self.write_i32(sign.position.x)?;
self.write_i32(sign.position.y)?;
}
Ok(())
}
fn write_signs_old_version(&mut self, signs: &[Sign]) -> io::Result<()> {
for i in 0..1000 {
let Some(sign) = signs.get(i) else {
self.write_bool(false)?;
continue;
};
self.write_bool(true)?;
self.write_string(&sign.text)?;
self.write_i32(sign.position.x)?;
self.write_i32(sign.position.y)?;
}
Ok(())
}
fn write_npc_is_shimmer(&mut self, wld: &World) -> io::Result<()> {
if self.version < 268 {
return Ok(());
}
let npcs = [
(EntityType::Merchant, wld.is_merchant_shimmered),
(EntityType::Nurse, wld.is_nurse_shimmered),
(EntityType::ArmsDealer, wld.is_arms_dealer_shimmered),
(EntityType::Dryad, wld.is_dryad_shimmered),
(EntityType::Guide, wld.is_guide_shimmered),
(EntityType::OldMan, wld.is_old_man_shimmered),
(EntityType::Demolitionist, wld.is_demolitionist_shimmered),
(EntityType::Clothier, wld.is_clothier_shimmered),
(EntityType::GoblinTinkerer, wld.is_goblin_tinkerer_shimmered),
(EntityType::Wizard, wld.is_wizard_shimmered),
(EntityType::Mechanic, wld.is_mechanic_shimmered),
(EntityType::SantaClaus, wld.is_santa_claus_shimmered),
(EntityType::Truffle, wld.is_truffle_shimmered),
(EntityType::Steampunker, wld.is_steampunker_shimmered),
(EntityType::DyeTrader, wld.is_dye_trader_shimmered),
(EntityType::PartyGirl, wld.is_party_girl_shimmered),
(EntityType::Cyborg, wld.is_cyborg_shimmered),
(EntityType::Painter, wld.is_painter_shimmered),
(EntityType::WitchDoctor, wld.is_witch_doctor_shimmered),
(EntityType::Pirate, wld.is_pirate_shimmered),
(EntityType::Stylist, wld.is_stylist_shimmered),
(
EntityType::TravellingMerchant,
wld.is_travelling_merchant_shimmered,
),
(EntityType::Angler, wld.is_angler_shimmered),
(EntityType::TaxCollector, wld.is_tax_collector_shimmered),
(EntityType::Dd2Bartender, wld.is_bartender_shimmered),
(EntityType::Golfer, wld.is_golfer_shimmered),
(EntityType::BestiaryGirl, wld.is_zoologist_shimmered),
(EntityType::TownCat, wld.is_town_cat_shimmered),
(EntityType::TownDog, wld.is_town_dog_shimmered),
(EntityType::TownBunny, wld.is_town_bunny_shimmered),
(EntityType::Princess, wld.is_princess_shimmered),
(EntityType::TownSlimeBlue, wld.is_town_slime_blue_shimmered),
(
EntityType::TownSlimeGreen,
wld.is_town_slime_green_shimmered,
),
(EntityType::TownSlimeOld, wld.is_town_slime_old_shimmered),
(
EntityType::TownSlimePurple,
wld.is_town_slime_purple_shimmered,
),
(
EntityType::TownSlimeRainbow,
wld.is_town_slime_rainbow_shimmered,
),
(EntityType::TownSlimeRed, wld.is_town_slime_red_shimmered),
(
EntityType::TownSlimeYellow,
wld.is_town_slime_yellow_shimmered,
),
(
EntityType::TownSlimeCopper,
wld.is_town_slime_copper_shimmered,
),
(
EntityType::SkeletonMerchant,
wld.is_skeleton_merchant_shimmered,
),
];
self.write_i32(npcs.iter().filter(|x| x.1).count() as i32)?;
for (npc, _) in npcs.iter().filter(|x| x.1) {
self.write_i32(npc.to_id())?;
}
Ok(())
}
fn write_entity_type(&mut self, entity_type: EntityType) -> io::Result<()> {
if self.version >= 190 {
self.write_i32(entity_type.to_id())
} else {
self.write_string(entity_type.to_old_id().unwrap_or(""))
}
}
fn write_entities(&mut self, entities: &BoundedVec<Entity, 0, 201>) -> io::Result<()> {
let count = entities.len()
- if self.version >= 140 {
entities.iter().rev().take_while(|x| !x.is_npc()).count()
} else {
0
};
for entity in entities.iter().take(count) {
self.write_bool(true)?;
match entity {
Entity::Npc {
entity_type,
name,
position,
home,
is_shimmered,
is_homeless_despawn,
} => {
self.write_entity_type(*entity_type)?;
if self.version >= 83 {
self.write_string(name)?;
}
self.write_vec2_f32(*position)?;
self.write_bool(home.is_none())?;
self.write_vec2_i32(home.unwrap_or(Vec2::new(-1, -1)))?;
if self.version >= 213 {
if *is_shimmered {
self.write_bool(true)?;
self.write_i32(1)?;
} else {
self.write_bool(false)?;
}
}
if self.version >= 315 {
self.write_bool(*is_homeless_despawn)?;
}
}
Entity::NonNpc {
entity_type,
position,
} => {
self.write_entity_type(*entity_type)?;
if self.version >= 83 {
self.write_string("")?;
}
self.write_vec2_f32(*position)?;
self.write_bool(false)?;
self.write_i32(-1)?;
self.write_i32(-1)?;
if self.version >= 213 {
self.write_bool(false)?;
}
}
}
}
self.write_bool(false)?;
if self.version >= 140 {
for entity in entities.iter().skip(count) {
self.write_bool(true)?;
let Entity::NonNpc {
entity_type,
position,
} = entity
else {
panic!("Expected NonNpc got Npc")
};
self.write_entity_type(*entity_type)?;
self.write_vec2_f32(*position)?;
}
self.write_bool(false)?;
}
if !(31..=83).contains(&self.version) {
return Ok(());
}
let names = entities
.iter()
.filter_map(|x| x.name().map(|name| (x.entity_type(), name.as_str())))
.collect::<HashMap<_, _>>();
self.write_string(names.get(&EntityType::Merchant).unwrap_or(&""))?;
self.write_string(names.get(&EntityType::Nurse).unwrap_or(&""))?;
self.write_string(names.get(&EntityType::ArmsDealer).unwrap_or(&""))?;
self.write_string(names.get(&EntityType::Dryad).unwrap_or(&""))?;
self.write_string(names.get(&EntityType::Guide).unwrap_or(&""))?;
self.write_string(names.get(&EntityType::Clothier).unwrap_or(&""))?;
self.write_string(names.get(&EntityType::Demolitionist).unwrap_or(&""))?;
self.write_string(names.get(&EntityType::GoblinTinkerer).unwrap_or(&""))?;
self.write_string(names.get(&EntityType::Wizard).unwrap_or(&""))?;
if self.version >= 35 {
self.write_string(names.get(&EntityType::Mechanic).unwrap_or(&""))?;
}
if self.version >= 65 {
self.write_string(names.get(&EntityType::Truffle).unwrap_or(&""))?;
self.write_string(names.get(&EntityType::Steampunker).unwrap_or(&""))?;
self.write_string(names.get(&EntityType::DyeTrader).unwrap_or(&""))?;
self.write_string(names.get(&EntityType::PartyGirl).unwrap_or(&""))?;
self.write_string(names.get(&EntityType::Cyborg).unwrap_or(&""))?;
self.write_string(names.get(&EntityType::Painter).unwrap_or(&""))?;
self.write_string(names.get(&EntityType::WitchDoctor).unwrap_or(&""))?;
self.write_string(names.get(&EntityType::Pirate).unwrap_or(&""))?;
}
if self.version >= 79 {
self.write_string(names.get(&EntityType::Stylist).unwrap_or(&""))?;
}
Ok(())
}
fn write_title_entities(&mut self, tile_entities: &[TileEntity]) -> io::Result<()> {
if self.version < 116 {
return Ok(());
}
let offset = self.writer.stream_position()?;
self.write_i32(0)?;
let mut count = 0;
if self.version >= 122 {
for entity in tile_entities {
if self.version < entity.entity.supported_version() {
continue;
}
self.write_tile_entity(*entity)?;
count += 1;
}
} else {
for entity in tile_entities
.iter()
.filter(|x| matches!(x.entity, TileEntityType::TrainingDummy { entity_idx: _ }))
.take(1000)
{
self.write_i16(entity.position.x)?;
self.write_i16(entity.position.y)?;
count += 1;
}
}
let new_offset = self.writer.stream_position()?;
self.writer.seek(io::SeekFrom::Start(offset))?;
self.write_i32(count)?;
self.writer
.seek(io::SeekFrom::Start(new_offset))
.map(|_| ())
}
fn write_tile_entity(&mut self, tile_entity: TileEntity) -> io::Result<()> {
self.write_u8(tile_entity.entity.id())?;
self.write_i32(0)?;
self.write_i16(tile_entity.position.x)?;
self.write_i16(tile_entity.position.y)?;
match tile_entity.entity {
TileEntityType::TrainingDummy { entity_idx } => self.write_i16(entity_idx),
TileEntityType::ItemFrame(item) => self.write_item(item),
TileEntityType::LogicSensor { check, is_on } => {
self.write_u8(check.map(|x| x.to_id()).unwrap_or(0))?;
self.write_bool(is_on)
}
TileEntityType::DisplayDoll { .. } => self.write_display_doll(tile_entity.entity),
TileEntityType::WeaponRack(item) => self.write_item(item),
TileEntityType::HatRack { upper, lower } => {
let items = [upper.item, lower.item, upper.dye, lower.dye];
let mut flags = 0;
for x in items {
flags >>= 1;
flags |= (x.is_some() as u8) << 7;
}
self.write_u8(flags)?;
for item in items.iter().filter_map(|x| *x) {
self.write_item(Some(item))?;
}
Ok(())
}
TileEntityType::FoodPlatter(item) => self.write_item(item),
TileEntityType::Pylon => Ok(()),
TileEntityType::DisplayJar(item) => self.write_item(item),
TileEntityType::CritterAnchor(item) => {
self.write_i16(item.map(|x| x.to_id(self.version) as i16).unwrap_or(0))
}
TileEntityType::KiteAnchor(item) => {
self.write_i16(item.map(|x| x.to_id(self.version) as i16).unwrap_or(0))
}
}
}
fn write_display_doll(&mut self, display_doll: TileEntityType) -> io::Result<()> {
let TileEntityType::DisplayDoll {
pose,
helmet,
breastplate,
pants,
accessories,
mount,
hand,
} = display_doll
else {
panic!("You passed wrong enum variant")
};
let armor_accessories_iter = Some(helmet)
.into_iter()
.chain(Some(breastplate))
.chain(Some(pants))
.chain(accessories);
let flag1 = armor_accessories_iter
.clone()
.map(|x| (x.item.is_some() as u8) << 7)
.fold(0, |acc, x| (acc >> 1) | x);
self.write_u8(flag1)?;
let flag2 = armor_accessories_iter
.clone()
.map(|x| (x.dye.is_some() as u8) << 7)
.fold(0, |acc, x| (acc >> 1) | x);
self.write_u8(flag2)?;
if self.version >= 307 {
self.write_u8(pose.to_id())?;
}
if self.version >= 308 {
let flag3 = (hand.is_some() as u8)
| ((mount.item.is_some() as u8) << 1)
| ((mount.dye.is_some() as u8) << 2);
self.write_u8(flag3)?;
}
let equipable_iter = armor_accessories_iter
.chain(Some(mount))
.take(if self.version > 311 { 9 } else { 8 });
for slot in equipable_iter.clone() {
if let Some(item) = slot.item {
self.write_item(Some(item))?;
}
}
for slot in equipable_iter {
if let Some(item) = slot.dye {
self.write_item(Some(item))?;
}
}
if let Some(item) = hand
&& self.version >= 308
{
self.write_item(Some(item))?;
}
if let Some(item) = mount.item
&& self.version == 311
{
self.write_item(Some(item))?
}
Ok(())
}
fn write_homes(&mut self, homes: &[Home]) -> io::Result<()> {
if self.version < 189 {
return Ok(());
}
self.write_i32(homes.len() as i32)?;
for home in homes {
self.write_i32(home.resident.to_id())?;
self.write_i32(home.position.x)?;
self.write_i32(home.position.y)?;
}
Ok(())
}
fn write_weighted_pressure_plates(
&mut self,
pressure_plates: &HashSet<Vec2<InTiles, i32>>,
) -> io::Result<()> {
if self.version < 170 {
return Ok(());
}
self.write_i32(pressure_plates.len() as i32)?;
for position in pressure_plates {
self.write_i32(position.x)?;
self.write_i32(position.y)?;
}
Ok(())
}
fn write_bestiary(&mut self, bestiary: &HashMap<EntityType, BestiaryEntry>) -> io::Result<()> {
if self.version < 210 {
return Ok(());
}
self.write_i32(bestiary.iter().filter(|x| x.1.killed_count != 0).count() as i32)?;
for (entity, entry) in bestiary.iter() {
if entry.killed_count == 0 {
continue;
}
self.write_string(entity.to_bestiary_id())?;
self.write_i32(entry.killed_count)?;
}
self.write_i32(bestiary.iter().filter(|x| x.1.is_seen).count() as i32)?;
for (entity, _) in bestiary.iter().filter(|x| x.1.is_seen) {
self.write_string(entity.to_bestiary_id())?;
}
self.write_i32(bestiary.iter().filter(|x| x.1.is_chatted_with).count() as i32)?;
for (entity, _) in bestiary.iter().filter(|x| x.1.is_chatted_with) {
self.write_string(entity.to_bestiary_id())?;
}
Ok(())
}
fn write_journey_powers(&mut self, wld: &World) -> io::Result<()> {
if self.version < 220 {
return Ok(());
}
self.write_bool(true)?;
self.write_u16(0)?;
self.write_bool(wld.is_time_frozen)?;
self.write_bool(true)?;
self.write_u16(8)?;
self.write_f32(remap(wld.time_rate_power, 1.0..=24.0, 0.0..=1.0))?;
self.write_bool(true)?;
self.write_u16(9)?;
self.write_bool(wld.is_rain_frozen)?;
self.write_bool(true)?;
self.write_u16(10)?;
self.write_bool(wld.is_wind_frozen)?;
self.write_bool(true)?;
self.write_u16(12)?;
if wld.difficulty_power <= 1.0 {
self.write_f32(remap(wld.difficulty_power, 0.5..=1.0, 0.0..=0.33))?;
} else {
self.write_f32(remap(wld.difficulty_power, 1.0..=3.0, 0.33..=1.0))?;
}
self.write_bool(true)?;
self.write_u16(13)?;
self.write_bool(wld.is_biome_spread_disabled)?;
self.write_bool(false)
}
fn write_footer(&mut self, wld: &World) -> io::Result<()> {
if self.version < 7 {
return Ok(());
}
self.write_bool(true)?;
self.write_string(&wld.name)?;
self.write_i32(wld.id)
}
fn write_in_game_time(&mut self, v: Time) -> io::Result<()> {
const START_DAY: Time = if let Ok(time) = Time::from_hms(4, 30, 0) {
time
} else {
panic!("Invalid time")
};
const START_NIGHT: Time = if let Ok(time) = Time::from_hms(19, 30, 0) {
time
} else {
panic!("Invalid time")
};
let is_day = (START_DAY..START_NIGHT).contains(&v);
let mut time = v - if is_day { START_DAY } else { START_NIGHT };
if time.is_negative() {
time += Duration::DAY;
}
self.write_f64(time.as_seconds_f64())?;
self.write_bool(is_day)
}
fn write_item(&mut self, item: Option<WorldItemSlot>) -> io::Result<()> {
let Some(item) = item else {
self.write_i16(0)?;
self.write_u8(0)?;
return self.write_i16(0);
};
self.write_i16(item.item.to_id(self.version) as i16)?;
self.write_u8(item.prefix.map(|x| x.to_id()).unwrap_or(0))?;
self.write_i16(item.count.get())
}
fn write_vec2_i16<U: Unit>(&mut self, v: Vec2<U, i16>) -> io::Result<()> {
self.write_i16(v.x)?;
self.write_i16(v.y)
}
fn write_vec2_i32<U: Unit>(&mut self, v: Vec2<U, i32>) -> io::Result<()> {
self.write_i32(v.x)?;
self.write_i32(v.y)
}
fn write_vec2_f32<U: Unit>(&mut self, v: Vec2<U, f32>) -> io::Result<()> {
self.write_f32(v.x)?;
self.write_f32(v.y)
}
fn write_i64(&mut self, v: i64) -> io::Result<()> {
self.writer.write_i64::<LittleEndian>(v)
}
fn write_u64(&mut self, v: u64) -> io::Result<()> {
self.writer.write_u64::<LittleEndian>(v)
}
fn write_i32(&mut self, v: i32) -> io::Result<()> {
self.writer.write_i32::<LittleEndian>(v)
}
fn write_u32(&mut self, v: u32) -> io::Result<()> {
self.writer.write_u32::<LittleEndian>(v)
}
fn write_i16(&mut self, v: i16) -> io::Result<()> {
self.writer.write_i16::<LittleEndian>(v)
}
fn write_u16(&mut self, v: u16) -> io::Result<()> {
self.writer.write_u16::<LittleEndian>(v)
}
fn write_u8(&mut self, v: u8) -> io::Result<()> {
self.writer.write_u8(v)
}
fn write_f64(&mut self, v: f64) -> io::Result<()> {
self.writer.write_f64::<LittleEndian>(v)
}
fn write_f32(&mut self, v: f32) -> io::Result<()> {
self.writer.write_f32::<LittleEndian>(v)
}
fn write_bool(&mut self, v: bool) -> io::Result<()> {
self.write_u8(v as u8)
}
fn write_string(&mut self, v: &str) -> io::Result<()> {
self.writer.write_cs_string(v)
}
fn write_duration_terra_ticks_f64(&mut self, v: Duration) -> io::Result<()> {
self.write_f64(v.as_seconds_f64() * 60.0)
}
fn write_duration_terra_ticks_i32(&mut self, v: Duration) -> io::Result<()> {
self.write_i32(
round_division_i128(v.whole_nanoseconds().saturating_mul(60), 1000000000)
.clamp(i32::MIN as i128, i32::MAX as i128) as i32,
)
}
fn write_invasion(&mut self, v: Option<Invasion>) -> io::Result<()> {
let Some(invasion) = v else {
self.write_i32(0)?;
self.write_i32(0)?;
return self.write_f64(0.0);
};
self.write_i32(invasion.size)?;
self.write_i32(invasion.invasion_type.to_id())?;
self.write_f64(invasion.position.value())
}
fn write_rain(&mut self, v: Option<Rain>) -> io::Result<()> {
let Some(rain) = v else {
self.write_bool(false)?;
self.write_duration_terra_ticks_i32(Duration::ZERO)?;
return self.write_f32(0.0);
};
self.write_bool(true)?;
self.write_duration_terra_ticks_i32(rain.time)?;
self.write_f32(rain.max)
}
}
fn get_tile_is_important(tiles: &Grid<Tile>) -> Vec<bool> {
let mut tile_is_important = Vec::new();
tile_is_important.resize(TileType::MAX_ID as usize, false);
for tile in tiles {
let Some(tile) = tile.tile else {
continue;
};
if tile.texture_coords.is_some() {
tile_is_important[tile.tile.to_id() as usize] = true;
}
}
tile_is_important
}