This commit is contained in:
nora 2023-04-17 19:35:19 +02:00
parent 40e412c024
commit 98dd54f1f2
18 changed files with 196 additions and 1445 deletions

View file

@ -1,5 +1,4 @@
use std::fmt::Write; use std::fmt::Write;
use anyhow::Context; use anyhow::Context;
use directories::ProjectDirs; use directories::ProjectDirs;
use egui_sfml::SfEgui; use egui_sfml::SfEgui;
@ -7,26 +6,21 @@ use gamedebug_core::{imm, imm_dbg};
use sfml::{ use sfml::{
audio::SoundSource, audio::SoundSource,
graphics::{ graphics::{
BlendMode, Color, Rect, RectangleShape, RenderStates, RenderTarget, RenderTexture, BlendMode, Color, Rect, RectangleShape, RenderStates, RenderTarget,
RenderWindow, Shape, Sprite, Transformable, View, RenderTexture, RenderWindow, Shape, Sprite, Transformable, View,
}, },
system::{Vector2, Vector2u}, system::{Vector2, Vector2u},
window::{Event, Key}, window::{Event, Key},
}; };
use crate::{ use crate::{
command::{Cmd, CmdVec}, command::{Cmd, CmdVec},
debug::{self, DebugState}, debug::{self, DebugState},
game::{for_each_tile_on_screen, Biome, GameState}, game::{for_each_tile_on_screen, Biome, GameState},
graphics::{self, ScreenSc, ScreenVec}, graphics::{self, ScreenSc, ScreenVec},
input::Input, input::Input, inventory::{ItemId, Slot, TileLayer, UseAction},
inventory::{ItemId, Slot, TileLayer, UseAction},
math::{center_offset, TILE_SIZE}, math::{center_offset, TILE_SIZE},
res::Res, res::Res, tiles::TileId, CliArgs,
tiles::TileId,
CliArgs,
}; };
/// Application level state (includes game and ui state, etc.) /// Application level state (includes game and ui state, etc.)
pub struct App { pub struct App {
pub rw: RenderWindow, pub rw: RenderWindow,
@ -45,39 +39,10 @@ pub struct App {
pub project_dirs: ProjectDirs, pub project_dirs: ProjectDirs,
pub cmdvec: CmdVec, pub cmdvec: CmdVec,
} }
impl App { impl App {
pub fn new(args: CliArgs) -> anyhow::Result<Self> { pub fn new(args: CliArgs) -> anyhow::Result<Self> {
let rw = graphics::make_window(); loop {}
let sf_egui = SfEgui::new(&rw);
let mut res = Res::load()?;
res.surf_music.set_looping(true);
res.surf_music.set_volume(10.0);
res.surf_music.play();
let rw_size = rw.size();
let rt =
RenderTexture::new(rw_size.x, rw_size.y).context("Failed to create render texture")?;
let light_map = RenderTexture::new(rw_size.x, rw_size.y)
.context("Failed to create lightmap texture")?;
let project_dirs = ProjectDirs::from("", "", "mantle-diver").unwrap();
let worlds_dir = project_dirs.data_dir().join("worlds");
let path = worlds_dir.join(&args.world_name);
Ok(Self {
rw,
should_quit: false,
game: GameState::new(args.world_name, path, &res),
res,
sf_egui,
input: Input::default(),
debug: DebugState::default(),
scale: 1,
rt,
light_map,
project_dirs,
cmdvec: CmdVec::default(),
})
} }
pub fn do_game_loop(&mut self) { pub fn do_game_loop(&mut self) {
while !self.should_quit { while !self.should_quit {
self.do_event_handling(); self.do_event_handling();
@ -89,252 +54,15 @@ impl App {
self.game.tile_db.try_save(); self.game.tile_db.try_save();
self.game.world.save(); self.game.world.save();
} }
fn do_event_handling(&mut self) { fn do_event_handling(&mut self) {
while let Some(ev) = self.rw.poll_event() { loop {}
self.sf_egui.add_event(&ev);
{
let ctx = self.sf_egui.context();
self.input.update_from_event(
&ev,
ctx.wants_keyboard_input(),
ctx.wants_pointer_input(),
);
}
match ev {
Event::Closed => self.should_quit = true,
Event::Resized { width, height } => {
self.rt =
RenderTexture::new(width / self.scale as u32, height / self.scale as u32)
.unwrap();
self.light_map =
RenderTexture::new(width / self.scale as u32, height / self.scale as u32)
.unwrap();
let view = View::from_rect(Rect::new(0., 0., width as f32, height as f32));
self.rw.set_view(&view);
}
Event::KeyPressed { code, .. } => match code {
Key::F11 => {
self.debug.console.show ^= true;
self.debug.console.just_opened = true;
}
Key::F12 => self.debug.panel ^= true,
_ => {}
},
_ => {}
}
}
} }
fn do_update(&mut self) { fn do_update(&mut self) {
let rt_size = self.rt.size(); loop {}
if self.debug.freecam {
self.do_freecam();
} else {
let spd = if self.input.down(Key::LShift) {
8.0
} else if self.input.down(Key::LControl) {
128.0
} else {
3.0
};
self.game.world.player.hspeed = 0.;
if self.input.down(Key::A) {
self.game.world.player.hspeed = -spd;
}
if self.input.down(Key::D) {
self.game.world.player.hspeed = spd;
}
if self.input.down(Key::W) && self.game.world.player.can_jump() {
self.game.world.player.vspeed = -10.0;
self.game.world.player.jumps_left = 0;
}
self.game.world.player.down_intent = self.input.down(Key::S);
let terminal_velocity = 60.0;
self.game.world.player.vspeed = self
.game
.world
.player
.vspeed
.clamp(-terminal_velocity, terminal_velocity);
let mut on_screen_tile_ents = Vec::new();
for_each_tile_on_screen(self.game.camera_offset, self.rt.size(), |tp, _sp| {
let tile = self.game.world.tile_at_mut(tp, &self.game.worldgen).mid;
if tile.empty() {
return;
}
let tdef = &self.game.tile_db[tile];
let Some(bb) = tdef.layer.bb else {
return;
};
let x = tp.x as i32 * TILE_SIZE as i32;
let y = tp.y as i32 * TILE_SIZE as i32;
let en = s2dc::Entity::from_rect_corners(
x + bb.x as i32,
y + bb.y as i32,
x + bb.w as i32,
y + bb.h as i32,
);
on_screen_tile_ents.push(TileColEn {
col: en,
platform: tdef.layer.platform,
});
});
imm_dbg!(on_screen_tile_ents.len());
self.game.world.player.col_en.move_y(
self.game.world.player.vspeed,
|player_en, off| {
let mut col = false;
for en in &on_screen_tile_ents {
if player_en.would_collide(&en.col, off) {
if en.platform {
if self.game.world.player.vspeed < 0. {
continue;
}
// If the player's feet are below the top of the platform,
// collision shouldn't happen
let player_feet = player_en.pos.y + player_en.bb.y;
if player_feet > en.col.pos.y || self.game.world.player.down_intent
{
continue;
}
}
col = true;
if self.game.world.player.vspeed > 0. {
self.game.world.player.jumps_left = 1;
}
self.game.world.player.vspeed = 0.;
}
}
col
},
);
self.game.world.player.col_en.move_x(
self.game.world.player.hspeed,
|player_en, off| {
let mut col = false;
for en in &on_screen_tile_ents {
if en.platform {
continue;
}
if player_en.would_collide(&en.col, off) {
col = true;
self.game.world.player.hspeed = 0.;
}
}
col
},
);
self.game.world.player.vspeed += self.game.gravity;
let (x, y, _w, _h) = self.game.world.player.col_en.en.xywh();
self.game.camera_offset.x = (x - rt_size.x as i32 / 2).try_into().unwrap_or(0);
self.game.camera_offset.y = (y - rt_size.y as i32 / 2).try_into().unwrap_or(0);
}
let mut loc = self.input.mouse_down_loc;
let vco = viewport_center_offset(self.rw.size(), rt_size, self.scale);
loc.x -= vco.x;
loc.y -= vco.y;
loc.x /= self.scale as ScreenSc;
loc.y /= self.scale as ScreenSc;
let mut wpos = self.game.camera_offset;
wpos.x = wpos.x.saturating_add_signed(loc.x.into());
wpos.y = wpos.y.saturating_add_signed(loc.y.into());
let mouse_tpos = wpos.tile_pos();
imm!(
"Mouse @ tile {}, {} ({:?})",
mouse_tpos.x,
mouse_tpos.y,
self.game.world.tile_at_mut(mouse_tpos, &self.game.worldgen)
);
let m_chk = mouse_tpos.to_chunk();
imm!("@ chunk {}, {}", m_chk.x, m_chk.y);
let (m_chk_x, m_chk_y) = m_chk.region();
imm!("@ region {m_chk_x}, {m_chk_y}");
if self.debug.freecam && self.input.pressed(Key::P) {
self.game.world.player.col_en.en.pos.x = wpos.x as i32;
self.game.world.player.col_en.en.pos.y = wpos.y as i32;
}
'item_use: {
if !self.input.lmb_down {
break 'item_use;
}
let Some(active_slot) = self.game.inventory.slots.get(self.game.selected_inv_slot) else {
log::error!("Selected slot {} out of bounds", self.game.selected_inv_slot);
break 'item_use;
};
if active_slot.qty == 0 {
break 'item_use;
}
let def = &self.game.itemdb.db[active_slot.id as usize];
let t = self.game.world.tile_at_mut(mouse_tpos, &self.game.worldgen);
match &def.use_action {
UseAction::PlaceBgTile { id } => {
if t.bg.empty() {
t.bg = *id
}
}
UseAction::PlaceMidTile { id } => {
if t.mid.empty() {
t.mid = *id
}
}
UseAction::PlaceFgTile { id } => {
if t.fg.empty() {
t.fg = *id
}
}
UseAction::RemoveTile { layer } => match layer {
TileLayer::Bg => t.bg = TileId::EMPTY,
TileLayer::Mid => t.mid = TileId::EMPTY,
TileLayer::Fg => t.fg = TileId::EMPTY,
},
}
}
if self.game.camera_offset.y > 643_000 {
self.game.current_biome = Biome::Underground;
} else {
self.game.current_biome = Biome::Surface;
}
if self.game.current_biome != self.game.prev_biome {
self.game.prev_biome = self.game.current_biome;
match self.game.current_biome {
Biome::Surface => {
self.res.und_music.stop();
self.res.surf_music.play();
}
Biome::Underground => {
self.res.surf_music.stop();
self.res.und_music.set_volume(self.res.surf_music.volume());
self.res.und_music.set_looping(true);
self.res.und_music.play();
}
}
}
self.game.update(&self.input);
} }
fn do_freecam(&mut self) { fn do_freecam(&mut self) {
let spd = if self.input.down(Key::LShift) { loop {}
100
} else if self.input.down(Key::LControl) {
1000
} else {
2
};
if self.input.down(Key::A) {
self.game.camera_offset.x = self.game.camera_offset.x.saturating_sub(spd);
}
if self.input.down(Key::D) {
self.game.camera_offset.x = self.game.camera_offset.x.saturating_add(spd);
}
if self.input.down(Key::W) {
self.game.camera_offset.y = self.game.camera_offset.y.saturating_sub(spd);
}
if self.input.down(Key::S) {
self.game.camera_offset.y = self.game.camera_offset.y.saturating_add(spd);
}
} }
fn do_rendering(&mut self) { fn do_rendering(&mut self) {
self.game.light_pass(&mut self.light_map, &self.res); self.game.light_pass(&mut self.light_map, &self.res);
self.rt.clear(Color::rgb(55, 221, 231)); self.rt.clear(Color::rgb(55, 221, 231));
@ -347,14 +75,12 @@ impl App {
spr.set_position((vco.x as f32, vco.y as f32)); spr.set_position((vco.x as f32, vco.y as f32));
self.rw.clear(Color::rgb(40, 10, 70)); self.rw.clear(Color::rgb(40, 10, 70));
self.rw.draw(&spr); self.rw.draw(&spr);
// Draw light overlay with multiply blending
let mut rst = RenderStates::default(); let mut rst = RenderStates::default();
rst.blend_mode = BlendMode::MULTIPLY; rst.blend_mode = BlendMode::MULTIPLY;
self.light_map.display(); self.light_map.display();
spr.set_texture(self.light_map.texture(), false); spr.set_texture(self.light_map.texture(), false);
self.rw.draw_with_renderstates(&spr, &rst); self.rw.draw_with_renderstates(&spr, &rst);
drop(spr); drop(spr);
// Draw ui on top of in-game scene
self.rt.clear(Color::TRANSPARENT); self.rt.clear(Color::TRANSPARENT);
let ui_dims = Vector2 { let ui_dims = Vector2 {
x: (self.rw.size().x / self.scale as u32) as f32, x: (self.rw.size().x / self.scale as u32) as f32,
@ -380,7 +106,9 @@ impl App {
if self.debug.show_atlas { if self.debug.show_atlas {
let atlas = &self.res.atlas.tex; let atlas = &self.res.atlas.tex;
let size = atlas.size(); let size = atlas.size();
let mut rs = RectangleShape::from_rect(Rect::new(0., 0., size.x as f32, size.y as f32)); let mut rs = RectangleShape::from_rect(
Rect::new(0., 0., size.x as f32, size.y as f32),
);
rs.set_fill_color(Color::MAGENTA); rs.set_fill_color(Color::MAGENTA);
self.rw.draw(&rs); self.rw.draw(&rs);
self.rw.draw(&Sprite::with_texture(atlas)); self.rw.draw(&Sprite::with_texture(atlas));
@ -390,58 +118,15 @@ impl App {
drop(spr); drop(spr);
self.execute_commands(); self.execute_commands();
} }
fn execute_commands(&mut self) { fn execute_commands(&mut self) {
for cmd in self.cmdvec.drain(..) { loop {}
match cmd {
Cmd::QuitApp => self.should_quit = true,
Cmd::ToggleFreecam => self.debug.freecam ^= true,
Cmd::TeleportPlayer { pos, relative } => {
if relative {
let s2dc = pos.to_s2dc();
self.game.world.player.col_en.en.pos.x += s2dc.x;
self.game.world.player.col_en.en.pos.y += s2dc.y;
} else {
self.game.world.player.col_en.en.pos = pos.to_s2dc()
}
}
Cmd::TeleportPlayerSpawn => {
self.game.world.player.col_en.en.pos = self.game.spawn_point.to_s2dc()
}
Cmd::GiveItemByName(name) => {
for (i, item) in self.game.itemdb.db.iter().enumerate() {
if item.name == name {
self.game.inventory.slots.push(Slot {
id: i as ItemId,
qty: 1,
});
return;
}
}
writeln!(
&mut self.debug.console.log,
"Item with name '{name}' not found"
)
.unwrap();
}
}
}
} }
} }
/// Tile collision entity for doing physics /// Tile collision entity for doing physics
struct TileColEn { struct TileColEn {
col: s2dc::Entity, col: s2dc::Entity,
platform: bool, platform: bool,
} }
fn viewport_center_offset(rw_size: Vector2u, rt_size: Vector2u, scale: u8) -> ScreenVec { fn viewport_center_offset(rw_size: Vector2u, rt_size: Vector2u, scale: u8) -> ScreenVec {
let rw_size = rw_size; loop {}
let rt_size = rt_size * scale as u32;
let x = center_offset(rt_size.x as i32, rw_size.x as i32);
let y = center_offset(rt_size.y as i32, rw_size.y as i32);
ScreenVec {
x: x as ScreenSc,
y: y as ScreenSc,
}
} }

View file

@ -1,58 +1,22 @@
use std::ops::{BitAndAssign, BitOrAssign}; use std::ops::{BitAndAssign, BitOrAssign};
use num_traits::PrimInt; use num_traits::PrimInt;
pub fn nth_bit_set<N: PrimInt>(number: N, n: usize) -> bool { pub fn nth_bit_set<N: PrimInt>(number: N, n: usize) -> bool {
(number & (N::one() << n)) != N::zero() loop {}
} }
pub fn set_nth_bit<N: PrimInt + BitOrAssign + BitAndAssign>(
pub fn set_nth_bit<N: PrimInt + BitOrAssign + BitAndAssign>(number: &mut N, n: usize, set: bool) { number: &mut N,
let mask = N::one() << n; n: usize,
if set { set: bool,
*number |= mask; ) {
} else { loop {}
*number &= !mask;
}
} }
#[test] #[test]
#[allow(clippy::bool_assert_comparison)] #[allow(clippy::bool_assert_comparison)]
fn test_nth_bit_set() { fn test_nth_bit_set() {
let number: u8 = 0b0100_0100; loop {}
assert_eq!(nth_bit_set(number, 0), false);
assert_eq!(nth_bit_set(number, 1), false);
assert_eq!(nth_bit_set(number, 2), true);
assert_eq!(nth_bit_set(number, 3), false);
assert_eq!(nth_bit_set(number, 4), false);
assert_eq!(nth_bit_set(number, 5), false);
assert_eq!(nth_bit_set(number, 6), true);
assert_eq!(nth_bit_set(number, 7), false);
assert_eq!(nth_bit_set(0u64, 0), false);
assert_eq!(nth_bit_set(u64::MAX, 63), true);
} }
#[test] #[test]
#[allow(clippy::bool_assert_comparison)] #[allow(clippy::bool_assert_comparison)]
fn test_set_nth_bit() { fn test_set_nth_bit() {
let mut number: u8 = 0b0000_0000; loop {}
set_nth_bit(&mut number, 0, true);
assert_eq!(number, 0b0000_0001);
set_nth_bit(&mut number, 1, true);
assert_eq!(number, 0b0000_0011);
set_nth_bit(&mut number, 2, true);
assert_eq!(number, 0b0000_0111);
set_nth_bit(&mut number, 0, false);
assert_eq!(number, 0b0000_0110);
let mut all_bits_set: u64 = 0;
for i in 0..64 {
set_nth_bit(&mut all_bits_set, i, true);
assert_eq!(nth_bit_set(all_bits_set, i), true);
}
let mut no_bits_set: u64 = u64::MAX;
for i in 0..64 {
set_nth_bit(&mut no_bits_set, i, false);
assert_eq!(nth_bit_set(no_bits_set, i), false);
}
} }

View file

@ -1,7 +1,5 @@
use clap::Parser; use clap::Parser;
use crate::{command::Cmd, math::WorldPos}; use crate::{command::Cmd, math::WorldPos};
#[derive(Parser)] #[derive(Parser)]
pub enum CmdLine { pub enum CmdLine {
Quit, Quit,
@ -11,7 +9,6 @@ pub enum CmdLine {
Spawn, Spawn,
Give(Give), Give(Give),
} }
#[derive(Parser)] #[derive(Parser)]
pub struct Tp { pub struct Tp {
x: u32, x: u32,
@ -22,40 +19,22 @@ pub struct Tp {
} }
impl Tp { impl Tp {
fn to_world_pos(&self) -> WorldPos { fn to_world_pos(&self) -> WorldPos {
WorldPos { loop {}
x: self.x,
y: self.y,
}
} }
} }
#[derive(Parser)] #[derive(Parser)]
pub struct Give { pub struct Give {
name: String, name: String,
} }
pub enum Dispatch { pub enum Dispatch {
Cmd(Cmd), Cmd(Cmd),
ClearConsole, ClearConsole,
} }
impl CmdLine { impl CmdLine {
pub fn parse_cmdline(cmdline: &str) -> anyhow::Result<Self> { pub fn parse_cmdline(cmdline: &str) -> anyhow::Result<Self> {
let words = std::iter::once(" ").chain(cmdline.split_whitespace()); loop {}
Ok(Self::try_parse_from(words)?)
} }
pub(crate) fn dispatch(self) -> Dispatch { pub(crate) fn dispatch(self) -> Dispatch {
match self { loop {}
CmdLine::Quit => Dispatch::Cmd(Cmd::QuitApp),
CmdLine::Freecam => Dispatch::Cmd(Cmd::ToggleFreecam),
CmdLine::Clear => Dispatch::ClearConsole,
CmdLine::Tp(tp) => Dispatch::Cmd(Cmd::TeleportPlayer {
pos: tp.to_world_pos(),
relative: tp.rel,
}),
CmdLine::Spawn => Dispatch::Cmd(Cmd::TeleportPlayerSpawn),
CmdLine::Give(give) => Dispatch::Cmd(Cmd::GiveItemByName(give.name)),
}
} }
} }

View file

@ -1,20 +1,13 @@
use std::fmt::Write; use std::fmt::Write;
use egui::TextBuffer; use egui::TextBuffer;
use egui_inspect::{derive::Inspect, inspect}; use egui_inspect::{derive::Inspect, inspect};
use sfml::audio::SoundSource; use sfml::audio::SoundSource;
use crate::{ use crate::{
cmdline::CmdLine, cmdline::CmdLine, command::CmdVec, game::GameState,
command::CmdVec,
game::GameState,
math::{px_per_frame_to_km_h, WorldPos}, math::{px_per_frame_to_km_h, WorldPos},
res::Res, res::Res, stringfmt::LengthDisp, texture_atlas::AtlasBundle,
stringfmt::LengthDisp,
texture_atlas::AtlasBundle,
tiles::tiledb_edit_ui::TileDbEdit, tiles::tiledb_edit_ui::TileDbEdit,
}; };
#[derive(Default, Debug, Inspect)] #[derive(Default, Debug, Inspect)]
pub struct DebugState { pub struct DebugState {
pub panel: bool, pub panel: bool,
@ -23,7 +16,6 @@ pub struct DebugState {
pub show_atlas: bool, pub show_atlas: bool,
pub console: Console, pub console: Console,
} }
#[derive(Default, Debug, Inspect)] #[derive(Default, Debug, Inspect)]
pub struct Console { pub struct Console {
pub show: bool, pub show: bool,
@ -32,7 +24,6 @@ pub struct Console {
pub just_opened: bool, pub just_opened: bool,
pub history: Vec<String>, pub history: Vec<String>,
} }
fn debug_panel_ui( fn debug_panel_ui(
mut debug: &mut DebugState, mut debug: &mut DebugState,
mut game: &mut GameState, mut game: &mut GameState,
@ -40,75 +31,92 @@ fn debug_panel_ui(
res: &mut Res, res: &mut Res,
mut scale: &mut u8, mut scale: &mut u8,
) { ) {
egui::Window::new("Debug (F12)").show(ctx, |ui| { egui::Window::new("Debug (F12)")
if debug.freecam { .show(
ui.label("Cam x"); ctx,
ui.add(egui::DragValue::new(&mut game.camera_offset.x)); |ui| {
ui.label("Cam y"); if debug.freecam {
ui.add(egui::DragValue::new(&mut game.camera_offset.y)); ui.label("Cam x");
let co = game.camera_offset; ui.add(egui::DragValue::new(&mut game.camera_offset.x));
ui.label(format!( ui.label("Cam y");
"Cam Depth: {}", ui.add(egui::DragValue::new(&mut game.camera_offset.y));
LengthDisp(co.y as f32 - WorldPos::SURFACE as f32) let co = game.camera_offset;
)); ui.label(
ui.label(format!( format!(
"Cam offset from center: {}", "Cam Depth: {}", LengthDisp(co.y as f32 - WorldPos::SURFACE
LengthDisp(co.x as f32 - WorldPos::CENTER as f32) as f32)
)); ),
} else { );
ui.label(format!( ui.label(
"Player Depth: {}", format!(
LengthDisp(game.world.player.feet_y() as f32 - WorldPos::SURFACE as f32) "Cam offset from center: {}", LengthDisp(co.x as f32 -
)); WorldPos::CENTER as f32)
ui.label(format!( ),
"Player offset from center: {}", );
LengthDisp(game.world.player.col_en.en.pos.x as f32 - WorldPos::CENTER as f32) } else {
)); ui.label(
ui.label(format!( format!(
"Hspeed: {} ({} km/h)", "Player Depth: {}", LengthDisp(game.world.player.feet_y() as
game.world.player.hspeed, f32 - WorldPos::SURFACE as f32)
px_per_frame_to_km_h(game.world.player.hspeed) ),
)); );
ui.label(format!( ui.label(
"Vspeed: {} ({} km/h)", format!(
game.world.player.vspeed, "Player offset from center: {}", LengthDisp(game.world.player
px_per_frame_to_km_h(game.world.player.vspeed) .col_en.en.pos.x as f32 - WorldPos::CENTER as f32)
)); ),
} );
ui.label("Music volume"); ui.label(
let mut vol = res.surf_music.volume(); format!(
ui.add(egui::DragValue::new(&mut vol)); "Hspeed: {} ({} km/h)", game.world.player.hspeed,
res.surf_music.set_volume(vol); px_per_frame_to_km_h(game.world.player.hspeed)
ui.separator(); ),
egui::ScrollArea::both() );
.id_source("insp_scroll") ui.label(
.max_height(240.) format!(
.max_width(340.0) "Vspeed: {} ({} km/h)", game.world.player.vspeed,
.show(ui, |ui| { px_per_frame_to_km_h(game.world.player.vspeed)
inspect! { ),
ui, );
scale,
game,
debug
} }
}); ui.label("Music volume");
if ui.button("Reload graphics").clicked() { let mut vol = res.surf_music.volume();
res.atlas = AtlasBundle::new().unwrap(); ui.add(egui::DragValue::new(&mut vol));
game.tile_db.update_rects(&res.atlas.rects); res.surf_music.set_volume(vol);
} ui.separator();
ui.separator(); egui::ScrollArea::both()
egui::ScrollArea::vertical().show(ui, |ui| { .id_source("insp_scroll")
gamedebug_core::for_each_imm(|info| match info { .max_height(240.)
gamedebug_core::Info::Msg(msg) => { .max_width(340.0)
ui.label(msg); .show(
ui,
|ui| {
inspect! {
ui, scale, game, debug
}
},
);
if ui.button("Reload graphics").clicked() {
res.atlas = AtlasBundle::new().unwrap();
game.tile_db.update_rects(&res.atlas.rects);
} }
gamedebug_core::Info::Rect(_, _, _, _, _) => todo!(), ui.separator();
}); egui::ScrollArea::vertical()
}); .show(
gamedebug_core::clear_immediates(); ui,
}); |ui| {
gamedebug_core::for_each_imm(|info| match info {
gamedebug_core::Info::Msg(msg) => {
ui.label(msg);
}
gamedebug_core::Info::Rect(_, _, _, _, _) => todo!(),
});
},
);
gamedebug_core::clear_immediates();
},
);
} }
pub(crate) fn do_debug_ui( pub(crate) fn do_debug_ui(
ctx: &egui::Context, ctx: &egui::Context,
debug: &mut DebugState, debug: &mut DebugState,
@ -125,42 +133,6 @@ pub(crate) fn do_debug_ui(
console_ui(ctx, debug, cmd); console_ui(ctx, debug, cmd);
} }
} }
fn console_ui(ctx: &egui::Context, debug: &mut DebugState, cmd: &mut CmdVec) { fn console_ui(ctx: &egui::Context, debug: &mut DebugState, cmd: &mut CmdVec) {
egui::Window::new("Console (F11)").show(ctx, |ui| { loop {}
let up_arrow =
ui.input_mut(|inp| inp.consume_key(egui::Modifiers::default(), egui::Key::ArrowUp));
let re =
ui.add(egui::TextEdit::singleline(&mut debug.console.cmdline).hint_text("Command"));
if debug.console.just_opened {
re.request_focus();
}
if re.lost_focus() && ui.input(|inp| inp.key_pressed(egui::Key::Enter)) {
re.request_focus();
let cmdline = match CmdLine::parse_cmdline(&debug.console.cmdline) {
Ok(cmd) => cmd,
Err(e) => {
writeln!(&mut debug.console.log, "{e}").unwrap();
debug.console.history.push(debug.console.cmdline.take());
return;
}
};
debug.console.history.push(debug.console.cmdline.take());
match cmdline.dispatch() {
crate::cmdline::Dispatch::Cmd(command) => cmd.push(command),
crate::cmdline::Dispatch::ClearConsole => debug.console.log.clear(),
}
}
if up_arrow {
if let Some(line) = debug.console.history.pop() {
debug.console.cmdline = line;
}
}
egui::ScrollArea::vertical()
.stick_to_bottom(true)
.show(ui, |ui| {
ui.add(egui::TextEdit::multiline(&mut &debug.console.log[..]));
});
});
debug.console.just_opened = false;
} }

View file

@ -1,26 +1,21 @@
use std::path::PathBuf; use std::path::PathBuf;
use derivative::Derivative; use derivative::Derivative;
use egui_inspect::derive::Inspect; use egui_inspect::derive::Inspect;
use sfml::{ use sfml::{
graphics::{ graphics::{
Color, Rect, RectangleShape, RenderTarget, RenderTexture, Shape, Sprite, Transformable, Color, Rect, RectangleShape, RenderTarget, RenderTexture, Shape, Sprite,
Transformable,
}, },
system::{Vector2f, Vector2u}, system::{Vector2f, Vector2u},
window::Key, window::Key,
}; };
use crate::{ use crate::{
graphics::{ScreenSc, ScreenVec}, graphics::{ScreenSc, ScreenVec},
input::Input, input::Input, inventory::{Inventory, ItemDb},
inventory::{Inventory, ItemDb},
math::{smoothwave, wp_to_tp, WorldPos}, math::{smoothwave, wp_to_tp, WorldPos},
res::Res, res::Res, tiles::TileDb, world::{TilePos, World},
tiles::TileDb,
world::{TilePos, World},
worldgen::Worldgen, worldgen::Worldgen,
}; };
#[derive(Derivative, Inspect)] #[derive(Derivative, Inspect)]
#[derivative(Debug)] #[derivative(Debug)]
pub struct GameState { pub struct GameState {
@ -40,186 +35,39 @@ pub struct GameState {
pub selected_inv_slot: usize, pub selected_inv_slot: usize,
pub spawn_point: WorldPos, pub spawn_point: WorldPos,
} }
#[derive(Debug, Inspect)] #[derive(Debug, Inspect)]
pub struct LightSource { pub struct LightSource {
pub pos: ScreenVec, pub pos: ScreenVec,
} }
#[derive(PartialEq, Eq, Clone, Copy, Debug, Inspect)] #[derive(PartialEq, Eq, Clone, Copy, Debug, Inspect)]
pub enum Biome { pub enum Biome {
Surface, Surface,
Underground, Underground,
} }
impl GameState { impl GameState {
pub fn update(&mut self, input: &Input) { pub fn update(&mut self, input: &Input) {
if input.pressed(Key::Num1) { loop {}
self.selected_inv_slot = 0;
}
if input.pressed(Key::Num2) {
self.selected_inv_slot = 1;
}
if input.pressed(Key::Num3) {
self.selected_inv_slot = 2;
}
if input.pressed(Key::Num4) {
self.selected_inv_slot = 3;
}
if input.pressed(Key::Num5) {
self.selected_inv_slot = 4;
}
if input.pressed(Key::Num6) {
self.selected_inv_slot = 5;
}
if input.pressed(Key::Num7) {
self.selected_inv_slot = 6;
}
if input.pressed(Key::Num8) {
self.selected_inv_slot = 7;
}
if input.pressed(Key::Num9) {
self.selected_inv_slot = 8;
}
if input.pressed(Key::Num0) {
self.selected_inv_slot = 9;
}
self.world.ticks += 1;
} }
pub(crate) fn draw_world(&mut self, rt: &mut RenderTexture, res: &mut Res) { pub(crate) fn draw_world(&mut self, rt: &mut RenderTexture, res: &mut Res) {
self.light_sources.clear(); loop {}
let mut s = Sprite::with_texture(&res.atlas.tex);
for_each_tile_on_screen(self.camera_offset, rt.size(), |tp, sp| {
let tile = self.world.tile_at_mut(tp, &self.worldgen);
s.set_position(sp.to_sf_vec());
if !tile.bg.empty() {
s.set_texture_rect(self.tile_db[tile.bg].tex_rect.to_sf());
rt.draw(&s);
}
if !tile.mid.empty() {
s.set_texture_rect(self.tile_db[tile.mid].tex_rect.to_sf());
if let Some(light) = self.tile_db[tile.mid].light {
let pos = ScreenVec {
x: sp.x + light.x,
y: sp.y + light.y,
};
self.light_sources.push(LightSource { pos });
}
rt.draw(&s);
}
if !tile.fg.empty() {
s.set_texture_rect(self.tile_db[tile.fg].tex_rect.to_sf());
rt.draw(&s);
}
});
} }
pub fn draw_entities(&mut self, rt: &mut RenderTexture) { pub fn draw_entities(&mut self, rt: &mut RenderTexture) {
let (x, y, w, h) = self.world.player.col_en.en.xywh(); loop {}
let mut rect_sh = RectangleShape::new();
rect_sh.set_position((
(x - self.camera_offset.x as i32) as f32,
(y - self.camera_offset.y as i32) as f32,
));
rect_sh.set_size((w as f32, h as f32));
rt.draw(&rect_sh);
rect_sh.set_size((2., 2.));
rect_sh.set_fill_color(Color::RED);
rect_sh.set_position((
(self.world.player.col_en.en.pos.x - self.camera_offset.x as i32) as f32,
(self.world.player.col_en.en.pos.y - self.camera_offset.y as i32) as f32,
));
rt.draw(&rect_sh);
} }
pub fn draw_ui(&mut self, rt: &mut RenderTexture, res: &Res, ui_dims: Vector2f) { pub fn draw_ui(&mut self, rt: &mut RenderTexture, res: &Res, ui_dims: Vector2f) {
let mut s = Sprite::with_texture(&res.atlas.tex); loop {}
let mut rs = RectangleShape::from_rect(Rect::new(0., 0., 32., 32.));
rs.set_outline_color(Color::YELLOW);
rs.set_outline_thickness(1.0);
rs.set_fill_color(Color::TRANSPARENT);
for (i, slot) in self.inventory.slots.iter().enumerate() {
s.set_texture_rect(res.atlas.rects["ui/invframe"].to_sf());
let pos = ((i * 38) as f32 + 8.0, (ui_dims.y - 38.));
s.set_position(pos);
rt.draw(&s);
let item_def = &self.itemdb.db[slot.id as usize];
if let Some(rect) = res.atlas.rects.get(&item_def.graphic_name) {
s.set_texture_rect(rect.to_sf());
rt.draw(&s);
} else {
log::error!("Missing rect for item {}", item_def.name);
}
if i == self.selected_inv_slot {
rs.set_position(pos);
rt.draw(&rs);
}
}
} }
pub(crate) fn light_pass(&mut self, lightmap: &mut RenderTexture, res: &Res) { pub(crate) fn light_pass(&mut self, lightmap: &mut RenderTexture, res: &Res) {
let how_much_below_surface = self.camera_offset.y.saturating_sub(WorldPos::SURFACE); loop {}
let light_dropoff = (how_much_below_surface / 8).min(255) as u8;
let daylight = 255u8;
self.ambient_light = daylight.saturating_sub(light_dropoff);
// Clear light map
// You can clear to a brighter color to increase ambient light level
lightmap.clear(Color::rgba(
self.ambient_light,
self.ambient_light,
self.ambient_light,
255,
));
let mut s = Sprite::with_texture(&res.atlas.tex);
s.set_texture_rect(res.atlas.rects["light/1"].to_sf());
for ls in &self.light_sources {
let flicker = smoothwave(self.world.ticks, 40) as f32 / 64.0;
s.set_scale((4. + flicker, 4. + flicker));
s.set_origin((128., 128.));
s.set_position((ls.pos.x.into(), ls.pos.y.into()));
lightmap.draw(&s);
}
} }
pub(crate) fn new(world_name: String, path: PathBuf, res: &Res) -> GameState { pub(crate) fn new(world_name: String, path: PathBuf, res: &Res) -> GameState {
let mut spawn_point = WorldPos::SURFACE_CENTER; loop {}
spawn_point.y -= 1104;
let mut tile_db = TileDb::load_or_default();
tile_db.update_rects(&res.atlas.rects);
Self {
camera_offset: spawn_point,
world: World::new(spawn_point, &world_name, path),
gravity: 0.55,
current_biome: Biome::Surface,
prev_biome: Biome::Surface,
worldgen: Worldgen::from_seed(0),
ambient_light: 0,
light_sources: Vec::new(),
tile_db,
inventory: Inventory::new_debug(),
itemdb: ItemDb::default(),
selected_inv_slot: 0,
spawn_point,
}
} }
} }
pub fn for_each_tile_on_screen( pub fn for_each_tile_on_screen(
camera_offset: WorldPos, camera_offset: WorldPos,
rt_size: Vector2u, rt_size: Vector2u,
mut f: impl FnMut(TilePos, ScreenVec), mut f: impl FnMut(TilePos, ScreenVec),
) { ) {
for y in (-32..(rt_size.y as i16) + 32).step_by(32) { loop {}
for x in (-32..(rt_size.x as i16) + 32).step_by(32) {
f(
TilePos {
x: wp_to_tp(camera_offset.x.saturating_add(x.try_into().unwrap_or(0))),
y: wp_to_tp(camera_offset.y.saturating_add(y.try_into().unwrap_or(0))),
},
ScreenVec {
x: ((x as i64) - ((camera_offset.x as i64) % 32)) as ScreenSc,
y: ((y as i64) - ((camera_offset.y as i64) % 32)) as ScreenSc,
},
)
}
}
} }

View file

@ -1,55 +1,33 @@
use egui_inspect::derive::Inspect; use egui_inspect::derive::Inspect;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sfml::{ use sfml::{
graphics::RenderWindow, graphics::RenderWindow, system::Vector2f, window::{ContextSettings, Style, VideoMode},
system::Vector2f,
window::{ContextSettings, Style, VideoMode},
}; };
use sfml_xt::graphics::RenderWindowExt; use sfml_xt::graphics::RenderWindowExt;
use crate::math::FPS_TARGET; use crate::math::FPS_TARGET;
pub struct ScreenRes { pub struct ScreenRes {
pub w: u16, pub w: u16,
pub h: u16, pub h: u16,
} }
impl ScreenRes { impl ScreenRes {
fn to_sf(&self) -> VideoMode { fn to_sf(&self) -> VideoMode {
VideoMode { loop {}
width: self.w as _,
height: self.h as _,
bits_per_pixel: 32,
}
} }
} }
#[derive(Default, Clone, Copy, Debug, Inspect, Serialize, Deserialize)] #[derive(Default, Clone, Copy, Debug, Inspect, Serialize, Deserialize)]
pub struct ScreenVec { pub struct ScreenVec {
pub x: ScreenSc, pub x: ScreenSc,
pub y: ScreenSc, pub y: ScreenSc,
} }
/// Screen position/offset scalar /// Screen position/offset scalar
/// We assume this game won't be played above 32767*32767 resolution /// We assume this game won't be played above 32767*32767 resolution
pub type ScreenSc = i16; pub type ScreenSc = i16;
impl ScreenVec { impl ScreenVec {
pub fn to_sf_vec(self) -> Vector2f { pub fn to_sf_vec(self) -> Vector2f {
Vector2f::new(self.x.into(), self.y.into()) loop {}
} }
} }
const DEFAULT_RES: ScreenRes = ScreenRes { w: 960, h: 540 }; const DEFAULT_RES: ScreenRes = ScreenRes { w: 960, h: 540 };
pub fn make_window() -> RenderWindow { pub fn make_window() -> RenderWindow {
let mut rw = RenderWindow::new( loop {}
DEFAULT_RES.to_sf(),
"Mantle Diver",
Style::DEFAULT,
&ContextSettings::default(),
);
rw.set_framerate_limit(FPS_TARGET.into());
rw.center();
rw
} }

View file

@ -1,8 +1,6 @@
use fnv::FnvHashSet; use fnv::FnvHashSet;
use sfml::window::{mouse, Event, Key}; use sfml::window::{mouse, Event, Key};
use crate::graphics::ScreenVec; use crate::graphics::ScreenVec;
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Input { pub struct Input {
down: FnvHashSet<Key>, down: FnvHashSet<Key>,
@ -12,65 +10,18 @@ pub struct Input {
pub mouse_down_loc: ScreenVec, pub mouse_down_loc: ScreenVec,
pub mid_pressed: bool, pub mid_pressed: bool,
} }
impl Input { impl Input {
pub fn update_from_event(&mut self, ev: &Event, egui_kbd: bool, egui_ptr: bool) { pub fn update_from_event(&mut self, ev: &Event, egui_kbd: bool, egui_ptr: bool) {
match ev { loop {}
&Event::KeyPressed { code, .. } => {
self.pressed.insert(code);
self.down.insert(code);
}
Event::KeyReleased { code, .. } => {
self.down.remove(code);
}
&Event::MouseButtonPressed { button, x, y } => {
self.mouse_down_loc = ScreenVec {
x: x as i16,
y: y as i16,
};
if button == mouse::Button::Left {
self.lmb_down = true;
}
if button == mouse::Button::Right {
self.rmb_down = true;
}
if button == mouse::Button::Middle {
self.mid_pressed = true;
}
}
&Event::MouseButtonReleased { button, .. } => {
if button == mouse::Button::Left {
self.lmb_down = false;
}
if button == mouse::Button::Right {
self.rmb_down = false;
}
}
&Event::MouseMoved { x, y } => {
self.mouse_down_loc.x = x as i16;
self.mouse_down_loc.y = y as i16;
}
_ => {}
}
if egui_kbd {
self.pressed.clear();
self.down.clear();
}
if egui_ptr {
self.lmb_down = false;
self.rmb_down = false;
self.mid_pressed = false;
}
} }
/// Pressed event should be cleared every frame /// Pressed event should be cleared every frame
pub fn clear_pressed(&mut self) { pub fn clear_pressed(&mut self) {
self.mid_pressed = false; loop {}
self.pressed.clear();
} }
pub fn down(&self, key: Key) -> bool { pub fn down(&self, key: Key) -> bool {
self.down.contains(&key) loop {}
} }
pub fn pressed(&self, key: Key) -> bool { pub fn pressed(&self, key: Key) -> bool {
self.pressed.contains(&key) loop {}
} }
} }

View file

@ -1,22 +1,15 @@
use egui_inspect::derive::Inspect; use egui_inspect::derive::Inspect;
use crate::{math::IntRect, tiles::{BgTileId, FgTileId, MidTileId}};
use crate::{
math::IntRect,
tiles::{BgTileId, FgTileId, MidTileId},
};
/// We won't have more than 65535 different items /// We won't have more than 65535 different items
pub type ItemId = u16; pub type ItemId = u16;
/// We won't have more than 65535 item quantity in a single slot /// We won't have more than 65535 item quantity in a single slot
pub type ItemQty = u16; pub type ItemQty = u16;
/// Inventory slot /// Inventory slot
#[derive(Debug, Inspect)] #[derive(Debug, Inspect)]
pub struct Slot { pub struct Slot {
pub id: ItemId, pub id: ItemId,
pub qty: ItemQty, pub qty: ItemQty,
} }
#[derive(Debug, Inspect)] #[derive(Debug, Inspect)]
pub struct Inventory { pub struct Inventory {
pub slots: Vec<Slot>, pub slots: Vec<Slot>,
@ -24,37 +17,9 @@ pub struct Inventory {
impl Inventory { impl Inventory {
/// A new inventory filled with some debug items /// A new inventory filled with some debug items
pub(crate) fn new_debug() -> Inventory { pub(crate) fn new_debug() -> Inventory {
Self { loop {}
slots: vec![
Slot {
id: items::WOOD_PICK,
qty: 1,
},
Slot {
id: items::DIRT_BLOCK,
qty: 100,
},
Slot {
id: items::TORCH,
qty: 100,
},
Slot {
id: items::PLATFORM,
qty: 100,
},
Slot {
id: items::STONE_WALL,
qty: 100,
},
Slot {
id: items::PANZERIUM,
qty: 100,
},
],
}
} }
} }
#[derive(Debug, Inspect)] #[derive(Debug, Inspect)]
pub struct ItemDef { pub struct ItemDef {
pub name: String, pub name: String,
@ -64,14 +29,12 @@ pub struct ItemDef {
pub use_action: UseAction, pub use_action: UseAction,
pub consumable: bool, pub consumable: bool,
} }
#[derive(Debug, Inspect, PartialEq)] #[derive(Debug, Inspect, PartialEq)]
pub enum TileLayer { pub enum TileLayer {
Bg, Bg,
Mid, Mid,
Fg, Fg,
} }
#[derive(Debug)] #[derive(Debug)]
pub enum UseAction { pub enum UseAction {
PlaceBgTile { id: BgTileId }, PlaceBgTile { id: BgTileId },
@ -79,78 +42,17 @@ pub enum UseAction {
PlaceFgTile { id: FgTileId }, PlaceFgTile { id: FgTileId },
RemoveTile { layer: TileLayer }, RemoveTile { layer: TileLayer },
} }
#[derive(Debug, Inspect)] #[derive(Debug, Inspect)]
pub struct ItemDb { pub struct ItemDb {
pub db: Vec<ItemDef>, pub db: Vec<ItemDef>,
} }
impl Default for ItemDb { impl Default for ItemDb {
fn default() -> Self { fn default() -> Self {
Self { loop {}
db: vec![
ItemDef {
name: String::from("Dirt Block"),
graphic_name: String::from("tiles/dirt"),
tex_rect: IntRect::default(),
use_action: UseAction::PlaceMidTile {
id: MidTileId::DIRT,
},
consumable: true,
},
ItemDef {
name: String::from("Torch"),
graphic_name: String::from("tiles/torch"),
tex_rect: IntRect::default(),
use_action: UseAction::PlaceMidTile {
id: MidTileId::TORCH,
},
consumable: true,
},
ItemDef {
name: String::from("Platform"),
graphic_name: String::from("tiles/platform"),
tex_rect: IntRect::default(),
use_action: UseAction::PlaceMidTile {
id: MidTileId::PLATFORM,
},
consumable: true,
},
ItemDef {
name: String::from("Wood Pick"),
graphic_name: String::from("items/woodpick"),
tex_rect: IntRect::default(),
use_action: UseAction::RemoveTile {
layer: TileLayer::Mid,
},
consumable: true,
},
ItemDef {
name: String::from("Panzerium"),
graphic_name: String::from("tiles/panzerium"),
tex_rect: IntRect::default(),
use_action: UseAction::PlaceMidTile {
id: MidTileId::PANZERIUM,
},
consumable: true,
},
ItemDef {
name: String::from("Stone wall"),
graphic_name: String::from("tiles/stoneback"),
tex_rect: IntRect::default(),
use_action: UseAction::PlaceBgTile {
id: BgTileId::STONE,
},
consumable: true,
},
],
}
} }
} }
pub mod items { pub mod items {
use super::ItemId; use super::ItemId;
pub const DIRT_BLOCK: ItemId = 0; pub const DIRT_BLOCK: ItemId = 0;
pub const TORCH: ItemId = 1; pub const TORCH: ItemId = 1;
pub const PLATFORM: ItemId = 2; pub const PLATFORM: ItemId = 2;

View file

@ -1,19 +1,14 @@
use std::fmt::Debug; use std::fmt::Debug;
use egui_inspect::derive::Inspect; use egui_inspect::derive::Inspect;
use num_traits::{Num, Signed}; use num_traits::{Num, Signed};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::world::{TPosSc, TilePos}; use crate::world::{TPosSc, TilePos};
pub type WPosSc = u32; pub type WPosSc = u32;
#[derive(Clone, Copy, Debug, Inspect)] #[derive(Clone, Copy, Debug, Inspect)]
pub struct WorldPos { pub struct WorldPos {
pub x: WPosSc, pub x: WPosSc,
pub y: WPosSc, pub y: WPosSc,
} }
/// Tile size in pixels /// Tile size in pixels
pub const TILE_SIZE: u8 = 32; pub const TILE_SIZE: u8 = 32;
/// Pixels per meter. /// Pixels per meter.
@ -21,25 +16,17 @@ pub const PX_PER_M: f32 = TILE_SIZE as f32 * 2.;
/// Meters per pixel /// Meters per pixel
pub const M_PER_PX: f32 = 1. / PX_PER_M; pub const M_PER_PX: f32 = 1. / PX_PER_M;
pub const FPS_TARGET: u8 = 60; pub const FPS_TARGET: u8 = 60;
pub fn px_per_frame_to_m_per_s(px_per_frame: f32) -> f32 { pub fn px_per_frame_to_m_per_s(px_per_frame: f32) -> f32 {
let m_per_frame = px_per_frame / PX_PER_M; loop {}
m_per_frame * FPS_TARGET as f32
} }
pub fn px_per_frame_to_km_h(px_per_frame: f32) -> f32 { pub fn px_per_frame_to_km_h(px_per_frame: f32) -> f32 {
px_per_frame_to_m_per_s(px_per_frame) * 3.6 loop {}
} }
/// World extent in tiles. Roughly 50km*50km. /// World extent in tiles. Roughly 50km*50km.
pub const WORLD_EXTENT: TPosSc = 100_000; pub const WORLD_EXTENT: TPosSc = 100_000;
impl WorldPos { impl WorldPos {
pub fn tile_pos(&self) -> TilePos { pub fn tile_pos(&self) -> TilePos {
TilePos { loop {}
x: wp_to_tp(self.x),
y: wp_to_tp(self.y),
}
} }
/// Horizontal center of the world /// Horizontal center of the world
pub const CENTER: WPosSc = (WORLD_EXTENT / 2) * TILE_SIZE as WPosSc; pub const CENTER: WPosSc = (WORLD_EXTENT / 2) * TILE_SIZE as WPosSc;
@ -50,47 +37,20 @@ impl WorldPos {
x: Self::CENTER, x: Self::CENTER,
y: Self::SURFACE, y: Self::SURFACE,
}; };
pub(crate) fn to_s2dc(self) -> s2dc::Vec2 { pub(crate) fn to_s2dc(self) -> s2dc::Vec2 {
s2dc::Vec2 { loop {}
x: self.x as i32,
y: self.y as i32,
}
} }
} }
pub fn wp_to_tp(wp: WPosSc) -> TPosSc { pub fn wp_to_tp(wp: WPosSc) -> TPosSc {
(wp / TILE_SIZE as WPosSc) as TPosSc loop {}
} }
// Get the offset required to center an object of `xw` width inside an object of `yw` width.
//
// For example, let's say `xw` (+) is 10 and we want to center it inside `yw` (-), which is 20
//
// ++++++++++ (x uncentered)
// -------------------- (y)
// ++++++++++ (x centered)
//
// In this case, we needed to add 5 to x to achieve centering.
// This is the offset that this function calculates.
//
// We can calulate it by subtracting `xw` from `yw` (10), and dividing it by 2.
pub fn center_offset<N: From<u8> + Copy + Signed>(xw: N, yw: N) -> N { pub fn center_offset<N: From<u8> + Copy + Signed>(xw: N, yw: N) -> N {
let diff = yw - xw; loop {}
diff / N::from(2)
} }
/// A smooth triangle-wave like transform of the input value, oscillating between 0 and the ceiling. /// A smooth triangle-wave like transform of the input value, oscillating between 0 and the ceiling.
pub fn smoothwave<T: Num + From<u8> + PartialOrd + Copy>(input: T, max: T) -> T { pub fn smoothwave<T: Num + From<u8> + PartialOrd + Copy>(input: T, max: T) -> T {
let period = max * T::from(2); loop {}
let value = input % period;
if value < max {
value
} else {
period - value
}
} }
#[derive(Serialize, Deserialize, Debug, Inspect, Default, Clone, Copy)] #[derive(Serialize, Deserialize, Debug, Inspect, Default, Clone, Copy)]
pub struct IntRect { pub struct IntRect {
pub x: i32, pub x: i32,
@ -100,30 +60,14 @@ pub struct IntRect {
} }
impl IntRect { impl IntRect {
pub(crate) fn to_sf(self) -> sfml::graphics::Rect<i32> { pub(crate) fn to_sf(self) -> sfml::graphics::Rect<i32> {
sfml::graphics::Rect::<i32> { loop {}
left: self.x,
top: self.y,
width: self.w,
height: self.h,
}
} }
} }
#[test] #[test]
fn test_smooth_wave() { fn test_smooth_wave() {
assert_eq!(smoothwave(0, 100), 0); loop {}
assert_eq!(smoothwave(50, 100), 50);
assert_eq!(smoothwave(125, 100), 75);
assert_eq!(smoothwave(150, 100), 50);
assert_eq!(smoothwave(175, 100), 25);
assert_eq!(smoothwave(199, 100), 1);
assert_eq!(smoothwave(200, 100), 0);
assert_eq!(smoothwave(201, 100), 1);
} }
#[test] #[test]
fn test_wp_to_tp() { fn test_wp_to_tp() {
assert_eq!(wp_to_tp(0), 0); loop {}
assert_eq!(wp_to_tp(1), 0);
assert_eq!(wp_to_tp(33), 1);
} }

View file

@ -1,14 +1,11 @@
use std::path::Path; use std::path::Path;
use egui_inspect::{derive::Inspect, inspect}; use egui_inspect::{derive::Inspect, inspect};
use s2dc::{vec2, MobileEntity}; use s2dc::{vec2, MobileEntity};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
math::{WorldPos, TILE_SIZE}, math::{WorldPos, TILE_SIZE},
world::{TPosSc, TilePos}, world::{TPosSc, TilePos},
}; };
#[derive(Debug, Inspect, Serialize, Deserialize)] #[derive(Debug, Inspect, Serialize, Deserialize)]
pub struct Player { pub struct Player {
#[inspect_with(inspect_mobile_entity)] #[inspect_with(inspect_mobile_entity)]
@ -19,43 +16,24 @@ pub struct Player {
/// true if the player wants to jump down from a platform /// true if the player wants to jump down from a platform
pub down_intent: bool, pub down_intent: bool,
} }
fn inspect_mobile_entity(en: &mut MobileEntity, ui: &mut egui::Ui, _id_src: u64) { fn inspect_mobile_entity(en: &mut MobileEntity, ui: &mut egui::Ui, _id_src: u64) {
inspect! { loop {}
ui,
en.en.pos.x,
en.en.pos.y,
en.en.bb.x,
en.en.bb.y
}
} }
impl Player { impl Player {
pub fn new_at(pos: WorldPos) -> Self { pub fn new_at(pos: WorldPos) -> Self {
Self { loop {}
col_en: MobileEntity::from_pos_and_bb(vec2(pos.x as i32, pos.y as i32), vec2(20, 46)),
vspeed: 0.0,
hspeed: 0.0,
jumps_left: 0,
down_intent: false,
}
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn center_tp(&self) -> TilePos { pub fn center_tp(&self) -> TilePos {
TilePos { loop {}
x: (self.col_en.en.pos.x / TILE_SIZE as i32) as TPosSc,
y: (self.col_en.en.pos.y / TILE_SIZE as i32) as TPosSc,
}
} }
pub fn can_jump(&self) -> bool { pub fn can_jump(&self) -> bool {
self.jumps_left > 0 loop {}
} }
pub fn feet_y(&self) -> i32 { pub fn feet_y(&self) -> i32 {
self.col_en.en.pos.y + self.col_en.en.bb.y loop {}
} }
pub(crate) fn save(&self, path: &Path) { pub(crate) fn save(&self, path: &Path) {
let result = std::fs::write(path.join("player.dat"), rmp_serde::to_vec(self).unwrap()); loop {}
log::info!("{result:?}");
} }
} }

View file

@ -1,20 +1,13 @@
use sfml::audio::Music; use sfml::audio::Music;
use crate::texture_atlas::AtlasBundle; use crate::texture_atlas::AtlasBundle;
#[derive(Debug)] #[derive(Debug)]
pub struct Res { pub struct Res {
pub atlas: AtlasBundle, pub atlas: AtlasBundle,
pub surf_music: Music<'static>, pub surf_music: Music<'static>,
pub und_music: Music<'static>, pub und_music: Music<'static>,
} }
impl Res { impl Res {
pub fn load() -> anyhow::Result<Self> { pub fn load() -> anyhow::Result<Self> {
Ok(Self { loop {}
atlas: AtlasBundle::new()?,
surf_music: Music::from_file("res/music/music.ogg").unwrap(),
und_music: Music::from_file("res/music/cave2.ogg").unwrap(),
})
} }
} }

View file

@ -1,22 +1,8 @@
use std::fmt; use std::fmt;
use crate::math::M_PER_PX; use crate::math::M_PER_PX;
pub struct LengthDisp(pub f32); pub struct LengthDisp(pub f32);
impl fmt::Display for LengthDisp { impl fmt::Display for LengthDisp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let meters = self.0 * M_PER_PX; loop {}
if meters.abs() > 1000. {
let km = if meters.is_sign_negative() {
(meters / 1000.).ceil()
} else {
(meters / 1000.).floor()
};
let m = meters % 1000.;
write!(f, "{km} km, {m} m")
} else {
write!(f, "{meters} m")
}
} }
} }

View file

@ -1,106 +1,28 @@
use std::{collections::HashMap, path::Path}; use std::{collections::HashMap, path::Path};
use crate::math::IntRect; use crate::math::IntRect;
use sfml::{graphics::Texture, SfBox}; use sfml::{graphics::Texture, SfBox};
use texture_packer::{texture::Texture as _, TexturePacker, TexturePackerConfig}; use texture_packer::{texture::Texture as _, TexturePacker, TexturePackerConfig};
pub type RectMap = HashMap<String, IntRect>; pub type RectMap = HashMap<String, IntRect>;
#[derive(Debug)] #[derive(Debug)]
pub struct AtlasBundle { pub struct AtlasBundle {
pub tex: SfBox<Texture>, pub tex: SfBox<Texture>,
// Key could be `tiles/dirt` for example, derived from folder+filename without extension
pub rects: RectMap, pub rects: RectMap,
} }
impl AtlasBundle { impl AtlasBundle {
pub fn new() -> anyhow::Result<Self> { pub fn new() -> anyhow::Result<Self> {
let cfg = TexturePackerConfig { loop {}
max_width: 512,
max_height: 512,
allow_rotation: false,
border_padding: 0,
texture_padding: 0,
texture_extrusion: 0,
trim: true,
texture_outlines: false,
};
let mut packer = TexturePacker::new_skyline(cfg);
walk_graphics(|path| {
let img = image::open(path).unwrap();
let key = path_img_key(path);
packer.pack_own(key, img).unwrap();
dbg!(path);
});
let mut rects = HashMap::new();
let mut tex = Texture::new().unwrap();
log::info!(
"Texture atlas size is: {}x{}",
packer.width(),
packer.height()
);
if !tex.create(packer.width(), packer.height()) {
panic!("Failed to create texture");
}
let pixbuf = make_pix_buf(&packer);
unsafe {
tex.update_from_pixels(&pixbuf, packer.width(), packer.height(), 0, 0);
}
for (k, frame) in packer.get_frames() {
rects.insert(
k.clone(),
IntRect {
x: frame.frame.x as i32,
y: frame.frame.y as i32,
w: frame.frame.w as i32,
h: frame.frame.h as i32,
},
);
}
Ok(AtlasBundle { tex, rects })
} }
} }
fn make_pix_buf(packer: &TexturePacker<image::DynamicImage, String>) -> Vec<u8> { fn make_pix_buf(packer: &TexturePacker<image::DynamicImage, String>) -> Vec<u8> {
let (w, h) = (packer.width(), packer.height()); loop {}
let px_size = 4;
let mut vec = vec![0; w as usize * h as usize * px_size as usize];
for y in 0..h {
for x in 0..w {
let idx = ((y * w + x) * px_size) as usize;
if let Some(px) = packer.get(x, y) {
vec[idx..idx + px_size as usize].copy_from_slice(&px.0);
}
}
}
vec
} }
fn path_img_key(path: &Path) -> String { fn path_img_key(path: &Path) -> String {
let mut rev_iter = path.components().rev(); loop {}
let fname = rev_iter.next().unwrap();
let folder = rev_iter.next().unwrap();
let fname: &Path = fname.as_ref();
let folder: &Path = folder.as_ref();
folder
.join(fname.file_stem().unwrap())
.display()
.to_string()
} }
#[test] #[test]
fn test_path_img_key() { fn test_path_img_key() {
assert_eq!( loop {}
&path_img_key("/home/person/res/graphics/tiles/foo.png".as_ref()),
"tiles/foo"
);
} }
fn walk_graphics(mut f: impl FnMut(&Path)) { fn walk_graphics(mut f: impl FnMut(&Path)) {
for en in walkdir::WalkDir::new("res/graphics") { loop {}
let en = en.unwrap();
if en.file_type().is_file() {
f(en.path());
}
}
} }

View file

@ -1,12 +1,9 @@
use std::fmt::Debug; use std::fmt::Debug;
use egui_inspect::{derive::Inspect, Inspect}; use egui_inspect::{derive::Inspect, Inspect};
use crate::{ use crate::{
graphics::{ScreenSc, ScreenVec}, graphics::{ScreenSc, ScreenVec},
math::TILE_SIZE, math::TILE_SIZE,
}; };
#[derive(Debug, Default, Inspect)] #[derive(Debug, Default, Inspect)]
pub struct TileDbEdit { pub struct TileDbEdit {
open: bool, open: bool,
@ -15,109 +12,39 @@ pub struct TileDbEdit {
} }
impl TileDbEdit { impl TileDbEdit {
pub(crate) fn ui(&mut self, ctx: &egui::Context, tile_db: &mut TileDb) { pub(crate) fn ui(&mut self, ctx: &egui::Context, tile_db: &mut TileDb) {
if !self.open { loop {}
return;
}
egui::Window::new("Tiledb editor").show(ctx, |ui| {
ui.horizontal(|ui| {
ui.selectable_value(&mut self.layer, Layer::Bg, "Bg");
ui.selectable_value(&mut self.layer, Layer::Mid, "Mid");
ui.selectable_value(&mut self.layer, Layer::Fg, "Fg");
});
ui.separator();
egui::ScrollArea::vertical()
.max_height(400.0)
.show(ui, |ui| match self.layer {
Layer::Bg => db_ui(&mut tile_db.bg, ui),
Layer::Fg => db_ui(&mut tile_db.fg, ui),
Layer::Mid => db_ui(&mut tile_db.mid, ui),
});
});
} }
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
enum Layer { enum Layer {
Bg, Bg,
Fg, Fg,
Mid, Mid,
} }
impl Default for Layer { impl Default for Layer {
fn default() -> Self { fn default() -> Self {
Self::Bg loop {}
} }
} }
use super::{Bg, Fg, Mid, TileDb, TileDef, TileLayer, DEFAULT_TILE_BB}; use super::{Bg, Fg, Mid, TileDb, TileDef, TileLayer, DEFAULT_TILE_BB};
trait SpecialUi { trait SpecialUi {
fn special_ui(&mut self, ui: &mut egui::Ui); fn special_ui(&mut self, ui: &mut egui::Ui);
} }
impl SpecialUi for TileDef<Mid> { impl SpecialUi for TileDef<Mid> {
fn special_ui(&mut self, ui: &mut egui::Ui) { fn special_ui(&mut self, ui: &mut egui::Ui) {
match &mut self.layer.bb { loop {}
Some(bb) => {
ui.horizontal(|ui| {
ui.label("x");
ui.add(egui::DragValue::new(&mut bb.x));
ui.label("y");
ui.add(egui::DragValue::new(&mut bb.y));
ui.label("w");
ui.add(egui::DragValue::new(&mut bb.w));
ui.label("h");
ui.add(egui::DragValue::new(&mut bb.h));
});
}
None => {
if ui.button("Insert bb").clicked() {
self.layer.bb = Some(DEFAULT_TILE_BB);
}
}
}
ui.checkbox(&mut self.layer.platform, "platform");
} }
} }
impl SpecialUi for TileDef<Bg> { impl SpecialUi for TileDef<Bg> {
fn special_ui(&mut self, _ui: &mut egui::Ui) {} fn special_ui(&mut self, _ui: &mut egui::Ui) {}
} }
impl SpecialUi for TileDef<Fg> { impl SpecialUi for TileDef<Fg> {
fn special_ui(&mut self, _ui: &mut egui::Ui) {} fn special_ui(&mut self, _ui: &mut egui::Ui) {}
} }
fn db_ui<Layer: TileLayer + Debug>(db: &mut Vec<TileDef<Layer>>, ui: &mut egui::Ui) fn db_ui<Layer: TileLayer + Debug>(db: &mut Vec<TileDef<Layer>>, ui: &mut egui::Ui)
where where
<Layer as TileLayer>::SpecificDef: Debug + Default + Inspect, <Layer as TileLayer>::SpecificDef: Debug + Default + Inspect,
TileDef<Layer>: SpecialUi, TileDef<Layer>: SpecialUi,
{ {
for (i, def) in db.iter_mut().enumerate() { loop {}
ui.label(i.to_string());
ui.text_edit_singleline(&mut def.graphic_name);
match &mut def.light {
Some(light) => {
ui.horizontal(|ui| {
ui.label("x");
ui.add(egui::DragValue::new(&mut light.x));
ui.label("y");
ui.add(egui::DragValue::new(&mut light.y));
});
}
None => {
if ui.button("Insert light emit").clicked() {
def.light = Some(ScreenVec {
x: TILE_SIZE as ScreenSc / 2,
y: TILE_SIZE as ScreenSc / 2,
});
}
}
}
def.special_ui(ui);
ui.separator();
}
if ui.button("Add new default").clicked() {
db.push(Default::default());
}
} }

View file

@ -1,52 +1,30 @@
mod reg_chunk_existence; mod reg_chunk_existence;
mod serialization; mod serialization;
use std::{fmt::Debug, fs::File, io::Seek, path::{Path, PathBuf}};
use std::{
fmt::Debug,
fs::File,
io::Seek,
path::{Path, PathBuf},
};
use egui_inspect::derive::Inspect; use egui_inspect::derive::Inspect;
use fnv::FnvHashMap; use fnv::FnvHashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
math::WorldPos, math::WorldPos, player::Player, tiles::{BgTileId, FgTileId, MidTileId, TileId},
player::Player, world::reg_chunk_existence::ExistenceBitset, worldgen::Worldgen,
tiles::{BgTileId, FgTileId, MidTileId, TileId},
world::reg_chunk_existence::ExistenceBitset,
worldgen::Worldgen,
}; };
use self::serialization::save_chunk; use self::serialization::save_chunk;
pub type ChkPosSc = u16; pub type ChkPosSc = u16;
#[derive(Hash, PartialEq, Eq, Debug, Clone, Copy, Inspect)] #[derive(Hash, PartialEq, Eq, Debug, Clone, Copy, Inspect)]
pub struct ChunkPos { pub struct ChunkPos {
pub x: ChkPosSc, pub x: ChkPosSc,
pub y: ChkPosSc, pub y: ChkPosSc,
} }
impl ChunkPos { impl ChunkPos {
/// Returns the region this chunk position belongs to /// Returns the region this chunk position belongs to
pub fn region(&self) -> (u8, u8) { pub fn region(&self) -> (u8, u8) {
( loop {}
(self.x / REGION_CHUNK_EXTENT as ChkPosSc) as u8,
(self.y / REGION_CHUNK_EXTENT as ChkPosSc) as u8,
)
} }
/// Returns the local position in the region (0-7) /// Returns the local position in the region (0-7)
pub fn local(&self) -> (u8, u8) { pub fn local(&self) -> (u8, u8) {
( loop {}
(self.x % REGION_CHUNK_EXTENT as ChkPosSc) as u8,
(self.y % REGION_CHUNK_EXTENT as ChkPosSc) as u8,
)
} }
} }
#[derive(Debug, Inspect)] #[derive(Debug, Inspect)]
pub struct World { pub struct World {
/// The currently loaded chunks /// The currently loaded chunks
@ -59,220 +37,108 @@ pub struct World {
#[opaque] #[opaque]
pub path: PathBuf, pub path: PathBuf,
} }
impl World { impl World {
pub fn new(spawn_point: WorldPos, name: &str, path: PathBuf) -> Self { pub fn new(spawn_point: WorldPos, name: &str, path: PathBuf) -> Self {
Self { loop {}
chunks: Default::default(),
ticks: Default::default(),
player: Player::new_at(spawn_point),
name: name.to_string(),
path,
}
} }
/// Get mutable access to the tile at `pos`. /// Get mutable access to the tile at `pos`.
/// ///
/// Loads or generates the containing chunk if necessary. /// Loads or generates the containing chunk if necessary.
pub fn tile_at_mut(&mut self, pos: TilePos, worldgen: &Worldgen) -> &mut Tile { pub fn tile_at_mut(&mut self, pos: TilePos, worldgen: &Worldgen) -> &mut Tile {
let (chk, local) = pos.to_chunk_and_local(); loop {}
let chk = self
.chunks
.entry(chk)
.or_insert_with(|| Chunk::load_or_gen(chk, worldgen, &self.path));
chk.at_mut(local)
} }
pub fn save(&self) { pub fn save(&self) {
let result = std::fs::create_dir_all(&self.path); loop {}
log::info!("{result:?}");
self.save_meta();
self.player.save(&self.path);
self.save_chunks();
} }
pub fn save_meta(&self) { pub fn save_meta(&self) {
let meta = WorldMetaSave { loop {}
name: self.name.clone(),
ticks: self.ticks,
};
let result = std::fs::write(
self.path.join("world.dat"),
rmp_serde::to_vec(&meta).unwrap(),
);
log::info!("{result:?}");
} }
pub fn save_chunks(&self) { pub fn save_chunks(&self) {
for (pos, chk) in self.chunks.iter() { loop {}
save_chunk(pos, chk, &self.path);
}
} }
} }
fn loc_byte_idx_xy(x: u8, y: u8) -> usize { fn loc_byte_idx_xy(x: u8, y: u8) -> usize {
loc_byte_idx(loc_idx(y, x)) loop {}
} }
fn loc_byte_idx(loc_idx: u8) -> usize { fn loc_byte_idx(loc_idx: u8) -> usize {
loc_idx as usize * CHUNK_BYTES loop {}
} }
fn loc_idx(loc_y: u8, loc_x: u8) -> u8 { fn loc_idx(loc_y: u8, loc_x: u8) -> u8 {
(loc_y * REGION_CHUNK_EXTENT) + loc_x loop {}
} }
fn format_reg_file_name((x, y): (u8, u8)) -> String { fn format_reg_file_name((x, y): (u8, u8)) -> String {
format!("{x}.{y}.rgn") loop {}
} }
const CHUNK_BYTES: usize = CHUNK_N_TILES * TILE_BYTES; const CHUNK_BYTES: usize = CHUNK_N_TILES * TILE_BYTES;
const TILE_BYTES: usize = 3 * 2; const TILE_BYTES: usize = 3 * 2;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct WorldMetaSave { struct WorldMetaSave {
name: String, name: String,
ticks: u64, ticks: u64,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct TilePos { pub struct TilePos {
pub x: TPosSc, pub x: TPosSc,
pub y: TPosSc, pub y: TPosSc,
} }
#[derive(Debug, PartialEq, Eq, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct ChunkLocalTilePos { pub struct ChunkLocalTilePos {
pub x: ChkLocalTPosSc, pub x: ChkLocalTPosSc,
pub y: ChkLocalTPosSc, pub y: ChkLocalTPosSc,
} }
/// Chunk-local tile position scalar. Supports up to 256 tiles per chunk. /// Chunk-local tile position scalar. Supports up to 256 tiles per chunk.
type ChkLocalTPosSc = u8; type ChkLocalTPosSc = u8;
impl TilePos { impl TilePos {
pub fn to_chunk_and_local(self) -> (ChunkPos, ChunkLocalTilePos) { pub fn to_chunk_and_local(self) -> (ChunkPos, ChunkLocalTilePos) {
let chk = ChunkPos { loop {}
x: chk_pos(self.x),
y: chk_pos(self.y),
};
let local = ChunkLocalTilePos {
x: chunk_local(self.x),
y: chunk_local(self.y),
};
(chk, local)
} }
pub(crate) fn to_chunk(self) -> ChunkPos { pub(crate) fn to_chunk(self) -> ChunkPos {
self.to_chunk_and_local().0 loop {}
} }
} }
fn chk_pos(tile: TPosSc) -> ChkPosSc { fn chk_pos(tile: TPosSc) -> ChkPosSc {
(tile / CHUNK_EXTENT as TPosSc) as ChkPosSc loop {}
} }
#[test] #[test]
fn test_chk_pos() { fn test_chk_pos() {
assert_eq!(chk_pos(0), 0); loop {}
assert_eq!(chk_pos(1), 0);
assert_eq!(chk_pos(127), 0);
assert_eq!(chk_pos(128), 1);
} }
fn chunk_local(global: TPosSc) -> ChkLocalTPosSc { fn chunk_local(global: TPosSc) -> ChkLocalTPosSc {
(global % CHUNK_EXTENT as TPosSc) as ChkLocalTPosSc loop {}
} }
#[test] #[test]
fn test_chunk_local() { fn test_chunk_local() {
assert_eq!(chunk_local(0), 0); loop {}
} }
#[test] #[test]
fn test_to_chunk_and_local() { fn test_to_chunk_and_local() {
assert_eq!( loop {}
TilePos { x: 0, y: 0 }.to_chunk_and_local(),
(ChunkPos { x: 0, y: 0 }, ChunkLocalTilePos { x: 0, y: 0 })
);
assert_eq!(
TilePos { x: 1, y: 1 }.to_chunk_and_local(),
(ChunkPos { x: 0, y: 0 }, ChunkLocalTilePos { x: 1, y: 1 })
);
} }
// Need to support at least 4 million tiles long
pub type TPosSc = u32; pub type TPosSc = u32;
pub const CHUNK_EXTENT: u16 = 128; pub const CHUNK_EXTENT: u16 = 128;
const CHUNK_N_TILES: usize = CHUNK_EXTENT as usize * CHUNK_EXTENT as usize; const CHUNK_N_TILES: usize = CHUNK_EXTENT as usize * CHUNK_EXTENT as usize;
type ChunkTiles = [Tile; CHUNK_N_TILES]; type ChunkTiles = [Tile; CHUNK_N_TILES];
fn default_chunk_tiles() -> ChunkTiles { fn default_chunk_tiles() -> ChunkTiles {
[Tile { loop {}
bg: TileId::EMPTY,
mid: TileId::EMPTY,
fg: TileId::EMPTY,
}; CHUNK_N_TILES]
} }
#[derive(Debug, Inspect)] #[derive(Debug, Inspect)]
pub struct Chunk { pub struct Chunk {
tiles: ChunkTiles, tiles: ChunkTiles,
} }
impl Chunk { impl Chunk {
pub fn gen(pos: ChunkPos, worldgen: &Worldgen) -> Self { pub fn gen(pos: ChunkPos, worldgen: &Worldgen) -> Self {
let mut tiles = default_chunk_tiles(); loop {}
let noise = worldgen.chunk_noise(pos);
if pos.y >= 156 {
for (i, t) in tiles.iter_mut().enumerate() {
let x = i % CHUNK_EXTENT as usize;
let y = i / CHUNK_EXTENT as usize;
let noise = noise[x][y];
*t = noise;
}
}
// Unbreakable layer at bottom
if pos.y > 798 {
for b in &mut tiles {
b.mid = MidTileId::UNBREAKANIUM;
}
}
Self { tiles }
} }
pub fn load_or_gen(chk: ChunkPos, worldgen: &Worldgen, world_path: &Path) -> Chunk { pub fn load_or_gen(chk: ChunkPos, worldgen: &Worldgen, world_path: &Path) -> Chunk {
log::info!("Loading chunk {chk:?} (reg: {:?})", chk.region()); loop {}
let reg_filename = world_path.join(format_reg_file_name(chk.region()));
if chunk_exists(&reg_filename, chk) {
log::info!("Chunk exists, loading");
let mut f = File::open(&reg_filename).unwrap();
let bitset = ExistenceBitset::read_from_file(&mut f);
log::info!("Existence bitset: {bitset:?}");
assert_eq!(f.stream_position().unwrap(), 8);
let decomp_data = zstd::decode_all(f).unwrap();
assert_eq!(decomp_data.len(), REGION_BYTES);
let local_pos = chk.local();
Chunk::load_from_region(&decomp_data, local_pos.0, local_pos.1)
} else {
log::warn!("Chunk at {:?} doesn't exist, generating.", chk);
Chunk::gen(chk, worldgen)
}
} }
fn at_mut(&mut self, local: ChunkLocalTilePos) -> &mut Tile { fn at_mut(&mut self, local: ChunkLocalTilePos) -> &mut Tile {
&mut self.tiles[CHUNK_EXTENT as usize * local.y as usize + local.x as usize] loop {}
} }
} }
fn chunk_exists(reg_path: &Path, pos: ChunkPos) -> bool { fn chunk_exists(reg_path: &Path, pos: ChunkPos) -> bool {
if !Path::new(&reg_path).exists() { loop {}
return false;
}
let bitset = ExistenceBitset::read_from_fs(reg_path);
let local = pos.local();
let idx = loc_idx(local.1, local.0);
crate::bitmanip::nth_bit_set(bitset.0, idx as usize)
} }
#[derive(Clone, Copy, Debug, Inspect)] #[derive(Clone, Copy, Debug, Inspect)]
pub struct Tile { pub struct Tile {
/// Background wall behind entities /// Background wall behind entities
@ -282,12 +148,10 @@ pub struct Tile {
/// A layer on top of the mid wall. Usually ores or decorative pieces. /// A layer on top of the mid wall. Usually ores or decorative pieces.
pub fg: FgTileId, pub fg: FgTileId,
} }
pub const REGION_CHUNK_EXTENT: u8 = 8; pub const REGION_CHUNK_EXTENT: u8 = 8;
pub const REGION_N_CHUNKS: u8 = REGION_CHUNK_EXTENT * REGION_CHUNK_EXTENT; pub const REGION_N_CHUNKS: u8 = REGION_CHUNK_EXTENT * REGION_CHUNK_EXTENT;
/// This is the uncompressed byte length of a region /// This is the uncompressed byte length of a region
pub const REGION_BYTES: usize = REGION_N_CHUNKS as usize * CHUNK_BYTES; pub const REGION_BYTES: usize = REGION_N_CHUNKS as usize * CHUNK_BYTES;
#[allow(clippy::assertions_on_constants)] #[allow(clippy::assertions_on_constants)]
const _: () = assert!( const _: () = assert!(
REGION_N_CHUNKS <= 64, REGION_N_CHUNKS <= 64,

View file

@ -1,37 +1,17 @@
use std::{fs::File, io::Read, path::Path}; use std::{fs::File, io::Read, path::Path};
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct ExistenceBitset(pub u64); pub struct ExistenceBitset(pub u64);
impl ExistenceBitset { impl ExistenceBitset {
pub const EMPTY: Self = Self(0); pub const EMPTY: Self = Self(0);
pub fn read_from_file(f: &mut File) -> ExistenceBitset { pub fn read_from_file(f: &mut File) -> ExistenceBitset {
let mut buf = [0; 8]; loop {}
f.read_exact(&mut buf).unwrap();
ExistenceBitset(u64::from_le_bytes(buf))
} }
pub fn read_from_fs(path: &Path) -> ExistenceBitset { pub fn read_from_fs(path: &Path) -> ExistenceBitset {
let mut f = File::open(path).unwrap(); loop {}
Self::read_from_file(&mut f)
} }
} }
impl std::fmt::Debug for ExistenceBitset { impl std::fmt::Debug for ExistenceBitset {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f)?; loop {}
for i in 0..64 {
let chr = if crate::bitmanip::nth_bit_set(self.0, i) {
'X'
} else {
'_'
};
write!(f, "{chr}")?;
if (i + 1) % 8 == 0 {
writeln!(f)?;
}
}
Ok(())
} }
} }

View file

@ -1,93 +1,22 @@
use std::{ use std::{
fs::OpenOptions, fs::OpenOptions, io::{Seek, Write},
io::{Seek, Write},
path::Path, path::Path,
}; };
use crate::world::{ use crate::world::{
format_reg_file_name, loc_byte_idx, loc_idx, reg_chunk_existence::ExistenceBitset, format_reg_file_name, loc_byte_idx, loc_idx, reg_chunk_existence::ExistenceBitset,
REGION_BYTES, TILE_BYTES, REGION_BYTES, TILE_BYTES,
}; };
use super::{default_chunk_tiles, loc_byte_idx_xy, Chunk, ChunkPos}; use super::{default_chunk_tiles, loc_byte_idx_xy, Chunk, ChunkPos};
pub(super) fn save_chunk(pos: &ChunkPos, chk: &Chunk, world_dir: &Path) { pub(super) fn save_chunk(pos: &ChunkPos, chk: &Chunk, world_dir: &Path) {
let reg_file_name = world_dir.join(format_reg_file_name(pos.region())); loop {}
let reg_file_exists = Path::new(&reg_file_name).exists();
if !reg_file_exists {
log::warn!("Region file doesn't exist. Going to create one.");
}
let mut f = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&reg_file_name)
.unwrap();
let mut existence_bitset = if reg_file_exists {
ExistenceBitset::read_from_file(&mut f)
} else {
ExistenceBitset::EMPTY
};
let mut region_tile_data = if reg_file_exists {
assert_eq!(f.stream_position().unwrap(), 8);
zstd::decode_all(&mut f).unwrap()
} else {
vec![0; REGION_BYTES]
};
// Even the zstd decompressed data should be exactly REGION_BYTES size
assert_eq!(region_tile_data.len(), REGION_BYTES);
let (loc_x, loc_y) = pos.local();
let loc_idx = loc_idx(loc_y, loc_x);
crate::bitmanip::set_nth_bit(&mut existence_bitset.0, loc_idx as usize, true);
let byte_idx = loc_byte_idx(loc_idx);
for (i, tile) in chk.tiles.iter().enumerate() {
let off = byte_idx + (i * TILE_BYTES);
region_tile_data[off..off + 2].copy_from_slice(&tile.bg.0.to_le_bytes());
region_tile_data[off + 2..off + 4].copy_from_slice(&tile.mid.0.to_le_bytes());
region_tile_data[off + 4..off + 6].copy_from_slice(&tile.fg.0.to_le_bytes());
}
f.rewind().unwrap();
f.write_all(&u64::to_le_bytes(existence_bitset.0)[..])
.unwrap();
assert_eq!(f.stream_position().unwrap(), 8);
assert_eq!(region_tile_data.len(), REGION_BYTES);
let result = f.write_all(&zstd::encode_all(&region_tile_data[..], COMP_LEVEL).unwrap());
let cursor = f.stream_position().unwrap();
f.set_len(cursor).unwrap();
log::info!("{result:?}");
} }
const COMP_LEVEL: i32 = 9; const COMP_LEVEL: i32 = 9;
impl Chunk { impl Chunk {
pub fn load_from_region(data: &[u8], x: u8, y: u8) -> Self { pub fn load_from_region(data: &[u8], x: u8, y: u8) -> Self {
let byte_idx = loc_byte_idx_xy(x, y); loop {}
let mut tiles = default_chunk_tiles();
for (i, t) in tiles.iter_mut().enumerate() {
let off = byte_idx + (i * TILE_BYTES);
t.bg.0 = u16::from_le_bytes(data[off..off + 2].try_into().unwrap());
t.mid.0 = u16::from_le_bytes(data[off + 2..off + 4].try_into().unwrap());
t.fg.0 = u16::from_le_bytes(data[off + 4..off + 6].try_into().unwrap());
}
Self { tiles }
} }
} }
#[test] #[test]
fn test_chunk_seri() { fn test_chunk_seri() {
env_logger::builder() loop {}
.filter_level(log::LevelFilter::Info)
.init();
let _ = std::fs::create_dir("testworld");
let mut chk = Chunk {
tiles: super::default_chunk_tiles(),
};
for t in &mut chk.tiles {
t.bg = crate::tiles::BgTileId::DIRT;
}
save_chunk(&ChunkPos { x: 2, y: 0 }, &chk, "testworld".as_ref());
save_chunk(&ChunkPos { x: 3, y: 0 }, &chk, "testworld".as_ref());
let raw = std::fs::read("testworld/0.0.rgn").unwrap();
zstd::decode_all(&raw[8..]).unwrap();
std::fs::remove_dir_all("testworld").unwrap();
} }

View file

@ -1,74 +1,23 @@
use worldgen::{ use worldgen::{
constraint, constraint, noise::perlin::PerlinNoise,
noise::perlin::PerlinNoise,
noisemap::{NoiseMap, NoiseMapGenerator, Seed, Step}, noisemap::{NoiseMap, NoiseMapGenerator, Seed, Step},
world::{ world::{
tile::{Constraint, ConstraintType}, tile::{Constraint, ConstraintType},
Size, Tile, World, Size, Tile, World,
}, },
}; };
use crate::{ use crate::{
tiles::{BgTileId, FgTileId, MidTileId, TileId}, tiles::{BgTileId, FgTileId, MidTileId, TileId},
world::{ChunkPos, Tile as Tl, CHUNK_EXTENT}, world::{ChunkPos, Tile as Tl, CHUNK_EXTENT},
}; };
pub struct Worldgen { pub struct Worldgen {
world: World<crate::world::Tile>, world: World<crate::world::Tile>,
} }
impl Worldgen { impl Worldgen {
pub fn from_seed(seed: i64) -> Self { pub fn from_seed(seed: i64) -> Self {
let noise = PerlinNoise::new(); loop {}
let nm1 = NoiseMap::new(noise)
.set(Seed::of(seed))
.set(Step::of(0.005, 0.005));
let nm2 = NoiseMap::new(noise)
.set(Seed::of(seed))
.set(Step::of(0.05, 0.05));
let nm = Box::new(nm1 + nm2 * 3);
let world = World::new()
.set(Size::of(CHUNK_EXTENT as i64, CHUNK_EXTENT as i64))
// Dirt coal
.add(
Tile::new(Tl {
bg: BgTileId::DIRT,
mid: MidTileId::DIRT,
fg: FgTileId::COAL,
})
.when(constraint!(nm.clone(), < -0.8)),
)
// Dirt
.add(
Tile::new(Tl {
bg: BgTileId::DIRT,
mid: MidTileId::DIRT,
fg: TileId::EMPTY,
})
.when(constraint!(nm.clone(), < -0.1)),
)
// Stone
.add(
Tile::new(Tl {
bg: BgTileId::STONE,
mid: MidTileId::STONE,
fg: TileId::EMPTY,
})
.when(constraint!(nm, < 0.45)),
)
// Dirt wall
.add(Tile::new(Tl {
bg: BgTileId::DIRT,
mid: TileId::EMPTY,
fg: TileId::EMPTY,
}));
Self { world }
} }
pub fn chunk_noise(&self, pos: ChunkPos) -> Vec<Vec<Tl>> { pub fn chunk_noise(&self, pos: ChunkPos) -> Vec<Vec<Tl>> {
self.world.generate(pos.x as i64, pos.y as i64).unwrap() loop {}
} }
} }