Add torches

This commit is contained in:
crumblingstatue 2023-04-08 01:26:44 +02:00
parent 704819a988
commit 77099ab8a6
13 changed files with 427 additions and 133 deletions

View file

@ -1,8 +1,5 @@
use std::fmt::{self};
use anyhow::Context;
use egui_inspect::inspect;
use egui_sfml::{egui, SfEgui};
use egui_sfml::SfEgui;
use gamedebug_core::{imm, imm_dbg};
use sfml::{
audio::SoundSource,
@ -15,13 +12,13 @@ use sfml::{
};
use crate::{
debug::DebugState,
game::{for_each_tile_on_screen, Biome, GameState, LightSource},
debug::{self, DebugState},
game::{for_each_tile_on_screen, Biome, GameState},
graphics::{self, ScreenPos, ScreenPosScalar},
input::Input,
math::{center_offset, px_per_frame_to_km_h, WorldPos, M_PER_PX, TILE_SIZE},
math::{center_offset, TILE_SIZE},
res::Res,
world::{Tile, TilePosScalar, CHUNK_EXTENT},
world::{TilePosScalar, CHUNK_EXTENT},
};
/// Application level state (includes game and ui state, etc.)
@ -76,6 +73,7 @@ impl App {
self.input.clear_pressed();
gamedebug_core::inc_frame();
}
self.game.tile_db.try_save();
}
fn do_event_handling(&mut self) {
@ -132,7 +130,7 @@ impl App {
let mut on_screen_tile_ents = Vec::new();
for_each_tile_on_screen(self.game.camera_offset, self.rt.size(), |tp, _sp| {
let tid = self.game.world.tile_at_mut(tp, &self.game.worldgen).mid;
if tid == Tile::EMPTY {
if !self.game.tile_db[tid].solid {
return;
}
let tsize = TILE_SIZE as i32;
@ -186,7 +184,12 @@ impl App {
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);
imm!(
"Mouse @ tile {}, {} ({:?})",
mouse_tpos.x,
mouse_tpos.y,
self.game.world.tile_at_mut(mouse_tpos, &self.game.worldgen)
);
imm!(
"@ chunk {}, {}",
mouse_tpos.x / CHUNK_EXTENT as TilePosScalar,
@ -207,8 +210,6 @@ impl App {
} else {
t.bg = self.game.tile_to_place;
}
} else if self.input.mid_pressed {
self.game.light_sources.push(LightSource { pos: wpos });
}
if self.game.camera_offset.y > 643_000 {
self.game.current_biome = Biome::Underground;
@ -274,15 +275,13 @@ impl App {
self.rw.draw_with_renderstates(&spr, &rst);
self.sf_egui
.do_frame(|ctx| {
if self.debug.panel {
debug_panel_ui(
&mut self.debug,
&mut self.game,
ctx,
&mut self.res,
&mut self.scale,
);
}
debug::do_debug_ui(
ctx,
&mut self.debug,
&mut self.game,
&mut self.res,
&mut self.scale,
);
})
.unwrap();
self.sf_egui.draw(&mut self.rw, None);
@ -300,94 +299,3 @@ fn viewport_center_offset(rw_size: Vector2u, rt_size: Vector2u, scale: u8) -> Sc
y: y as ScreenPosScalar,
}
}
fn debug_panel_ui(
debug: &mut DebugState,
mut game: &mut GameState,
ctx: &egui::Context,
res: &mut Res,
mut scale: &mut u8,
) {
egui::Window::new("Debug (F12)").show(ctx, |ui| {
if debug.freecam {
ui.label("Cam x");
ui.add(egui::DragValue::new(&mut game.camera_offset.x));
ui.label("Cam y");
ui.add(egui::DragValue::new(&mut game.camera_offset.y));
let co = game.camera_offset;
ui.label(format!(
"Cam Depth: {}",
LengthDisp(co.y as f32 - WorldPos::SURFACE as f32)
));
ui.label(format!(
"Cam offset from center: {}",
LengthDisp(co.x as f32 - WorldPos::CENTER as f32)
));
} else {
let tp = game.player.center_tp();
imm_dbg!(tp);
ui.label(format!(
"Player Depth: {}",
LengthDisp(game.player.feet_y() as f32 - WorldPos::SURFACE as f32)
));
ui.label(format!(
"Player offset from center: {}",
LengthDisp(game.player.col_en.en.pos.x as f32 - WorldPos::CENTER as f32)
));
ui.label(format!(
"Hspeed: {} ({} km/h)",
game.player.hspeed,
px_per_frame_to_km_h(game.player.hspeed)
));
ui.label(format!(
"Vspeed: {} ({} km/h)",
game.player.vspeed,
px_per_frame_to_km_h(game.player.vspeed)
));
}
ui.label("Music volume");
let mut vol = res.surf_music.volume();
ui.add(egui::DragValue::new(&mut vol));
res.surf_music.set_volume(vol);
ui.separator();
egui::ScrollArea::both()
.id_source("insp_scroll")
.max_height(240.)
.max_width(340.0)
.show(ui, |ui| {
inspect! {
ui,
scale, game
}
});
ui.separator();
egui::ScrollArea::vertical().show(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();
});
}
struct LengthDisp(f32);
impl fmt::Display for LengthDisp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let meters = self.0 * M_PER_PX;
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,11 +1,20 @@
use sfml::window::Key;
use egui_inspect::inspect;
use sfml::{audio::SoundSource, window::Key};
use crate::input::Input;
use crate::{
game::GameState,
input::Input,
math::{px_per_frame_to_km_h, WorldPos},
res::Res,
stringfmt::LengthDisp,
tiles::tiledb_edit_ui::tiledb_edit_ui,
};
#[derive(Default, Debug)]
pub struct DebugState {
pub panel: bool,
panel: bool,
pub freecam: bool,
tiledb_edit: bool,
}
impl DebugState {
@ -18,3 +27,90 @@ impl DebugState {
}
}
}
fn debug_panel_ui(
debug: &mut DebugState,
mut game: &mut GameState,
ctx: &egui::Context,
res: &mut Res,
mut scale: &mut u8,
) {
egui::Window::new("Debug (F12)").show(ctx, |ui| {
if debug.freecam {
ui.label("Cam x");
ui.add(egui::DragValue::new(&mut game.camera_offset.x));
ui.label("Cam y");
ui.add(egui::DragValue::new(&mut game.camera_offset.y));
let co = game.camera_offset;
ui.label(format!(
"Cam Depth: {}",
LengthDisp(co.y as f32 - WorldPos::SURFACE as f32)
));
ui.label(format!(
"Cam offset from center: {}",
LengthDisp(co.x as f32 - WorldPos::CENTER as f32)
));
} else {
ui.label(format!(
"Player Depth: {}",
LengthDisp(game.player.feet_y() as f32 - WorldPos::SURFACE as f32)
));
ui.label(format!(
"Player offset from center: {}",
LengthDisp(game.player.col_en.en.pos.x as f32 - WorldPos::CENTER as f32)
));
ui.label(format!(
"Hspeed: {} ({} km/h)",
game.player.hspeed,
px_per_frame_to_km_h(game.player.hspeed)
));
ui.label(format!(
"Vspeed: {} ({} km/h)",
game.player.vspeed,
px_per_frame_to_km_h(game.player.vspeed)
));
}
ui.label("Music volume");
let mut vol = res.surf_music.volume();
ui.add(egui::DragValue::new(&mut vol));
res.surf_music.set_volume(vol);
ui.separator();
egui::ScrollArea::both()
.id_source("insp_scroll")
.max_height(240.)
.max_width(340.0)
.show(ui, |ui| {
inspect! {
ui,
scale,
game,
debug.tiledb_edit
}
});
ui.separator();
egui::ScrollArea::vertical().show(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(
ctx: &egui::Context,
debug: &mut DebugState,
game: &mut GameState,
res: &mut Res,
scale: &mut u8,
) {
if debug.panel {
debug_panel_ui(debug, game, ctx, res, scale);
}
if debug.tiledb_edit {
tiledb_edit_ui(ctx, &mut game.tile_db);
}
}

View file

@ -3,17 +3,16 @@ mod player;
use derivative::Derivative;
use egui_inspect::derive::Inspect;
use sfml::{
graphics::{
Color, Rect, RectangleShape, RenderTarget, RenderTexture, Shape, Sprite, Transformable,
},
graphics::{Color, RectangleShape, RenderTarget, RenderTexture, Shape, Sprite, Transformable},
system::{Clock, Vector2u},
SfBox,
};
use crate::{
graphics::{ScreenPos, ScreenPosScalar},
math::{wp_to_tp, WorldPos},
math::{wp_to_tp, WorldPos, TILE_SIZE},
res::Res,
tiles::TileDb,
world::{Tile, TileId, TilePos, World},
worldgen::Worldgen,
};
@ -37,11 +36,12 @@ pub struct GameState {
#[opaque]
pub clock: SfBox<Clock>,
pub light_sources: Vec<LightSource>,
pub tile_db: TileDb,
}
#[derive(Debug, Inspect)]
pub struct LightSource {
pub pos: WorldPos,
pub pos: ScreenPos,
}
#[derive(PartialEq, Eq, Clone, Copy, Debug, Inspect)]
@ -52,20 +52,27 @@ pub enum Biome {
impl GameState {
pub(crate) fn draw_world(&mut self, rt: &mut RenderTexture, res: &mut Res) {
self.light_sources.clear();
let mut s = Sprite::with_texture(&res.tile_atlas);
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 != Tile::EMPTY {
s.set_texture_rect(Rect::new((tile.bg - 1) as i32 * 32, 0, 32, 32));
s.set_texture_rect(self.tile_db[tile.bg].atlas_offset.to_sf_rect());
rt.draw(&s);
}
if tile.mid != Tile::EMPTY {
s.set_texture_rect(Rect::new((tile.mid - 1) as i32 * 32, 0, 32, 32));
s.set_texture_rect(self.tile_db[tile.mid].atlas_offset.to_sf_rect());
if self.tile_db[tile.mid].emits_light {
let mut pos = sp;
pos.x += (TILE_SIZE / 2) as i16;
pos.y += (TILE_SIZE / 2) as i16;
self.light_sources.push(LightSource { pos: sp });
}
rt.draw(&s);
}
if tile.fg != Tile::EMPTY {
s.set_texture_rect(Rect::new((tile.fg - 1) as i32 * 32, 0, 32, 32));
s.set_texture_rect(self.tile_db[tile.fg].atlas_offset.to_sf_rect());
rt.draw(&s);
}
});
@ -102,14 +109,10 @@ impl GameState {
255,
));
for ls in &self.light_sources {
let (x, y) = (
ls.pos.x as i32 - self.camera_offset.x as i32,
ls.pos.y as i32 - self.camera_offset.y as i32,
);
let mut s = Sprite::with_texture(&res.light_texture);
s.set_scale((4., 4.));
s.set_origin((128., 128.));
s.set_position((x as f32, y as f32));
s.set_position((ls.pos.x.into(), ls.pos.y.into()));
lightmap.draw(&s);
}
}
@ -152,6 +155,7 @@ impl Default for GameState {
ambient_light: 0,
clock: Clock::start(),
light_sources: Vec::new(),
tile_db: TileDb::load_or_default(),
}
}
}

View file

@ -34,6 +34,7 @@ impl Player {
jumps_left: 0,
}
}
#[allow(dead_code)]
pub fn center_tp(&self) -> TilePos {
TilePos {
x: (self.col_en.en.pos.x / TILE_SIZE as i32) as TilePosScalar,

View file

@ -1,3 +1,4 @@
use egui_inspect::derive::Inspect;
use sfml::{
graphics::RenderWindow,
system::Vector2f,
@ -23,7 +24,7 @@ impl ScreenRes {
}
// We assume this game won't be played above 32767*32767 resolution
#[derive(Default, Clone, Copy, Debug)]
#[derive(Default, Clone, Copy, Debug, Inspect)]
pub struct ScreenPos {
pub x: ScreenPosScalar,
pub y: ScreenPosScalar,

View file

@ -5,6 +5,8 @@ mod graphics;
mod input;
mod math;
mod res;
mod stringfmt;
mod tiles;
mod world;
mod worldgen;
@ -18,6 +20,9 @@ fn try_main() -> anyhow::Result<()> {
}
fn main() {
env_logger::builder()
.filter_level(log::LevelFilter::Info)
.init();
if let Err(e) = try_main() {
rfd::MessageDialog::new()
.set_title("Fatal error")

22
src/stringfmt.rs Normal file
View file

@ -0,0 +1,22 @@
use std::fmt;
use crate::math::M_PER_PX;
pub struct LengthDisp(pub f32);
impl fmt::Display for LengthDisp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let meters = self.0 * M_PER_PX;
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")
}
}
}

106
src/tiles.rs Normal file
View file

@ -0,0 +1,106 @@
pub mod tiledb_edit_ui;
use std::ops::Index;
use egui_inspect::derive::Inspect;
use log::warn;
use serde::{Deserialize, Serialize};
use sfml::graphics::IntRect;
use crate::{math::TILE_SIZE, world::TileId};
#[derive(Serialize, Deserialize, Default, Debug, Inspect)]
pub struct TileDef {
pub solid: bool,
pub emits_light: bool,
pub atlas_offset: AtlasOffset,
}
#[derive(Serialize, Deserialize, Debug, Inspect)]
pub struct TileDb {
db: Vec<TileDef>,
}
impl Default for TileDb {
fn default() -> Self {
Self {
// Add empty/air tile
db: vec![EMPTY],
}
}
}
const EMPTY: TileDef = TileDef {
solid: false,
emits_light: false,
// Rendering empty tile is actually special cased, and no rendering is done.
// But just in case, put the offset to UNKNOWN
atlas_offset: UNKNOWN_ATLAS_OFF,
};
impl Index<TileId> for TileDb {
type Output = TileDef;
fn index(&self, index: TileId) -> &Self::Output {
self.db.get(index as usize).unwrap_or(&UNKNOWN_TILE)
}
}
#[derive(Debug, Inspect, Serialize, Deserialize)]
pub struct AtlasOffset {
pub x: u16,
pub y: u16,
}
impl AtlasOffset {
pub(crate) fn to_sf_rect(&self) -> IntRect {
IntRect {
left: self.x as i32,
top: self.y as i32,
width: TILE_SIZE as i32,
height: TILE_SIZE as i32,
}
}
}
impl Default for AtlasOffset {
fn default() -> Self {
UNKNOWN_ATLAS_OFF
}
}
const UNKNOWN_ATLAS_OFF: AtlasOffset = AtlasOffset { x: 320, y: 0 };
static UNKNOWN_TILE: TileDef = TileDef {
solid: true,
emits_light: true,
atlas_offset: UNKNOWN_ATLAS_OFF,
};
const PATH: &str = "tiles.dat";
impl TileDb {
pub fn load_or_default() -> Self {
match std::fs::read(PATH) {
Ok(data) => match rmp_serde::from_slice(&data) {
Ok(db) => db,
Err(e) => {
warn!("Failed to load tile database: {e}\nCreating default.");
Default::default()
}
},
Err(e) => {
warn!("Failed to load tile database: {e}\nCreating default.");
Default::default()
}
}
}
pub fn try_save(&self) {
match rmp_serde::to_vec(self) {
Ok(vec) => match std::fs::write(PATH, vec) {
Ok(()) => {}
Err(e) => warn!("Failed to save tile db: {e}"),
},
Err(e) => warn!("Failed to save tile db: {e}"),
}
}
}

View file

@ -0,0 +1,9 @@
use super::TileDb;
pub fn tiledb_edit_ui(ctx: &egui::Context, tile_db: &mut TileDb) {
egui::Window::new("Tiledb editor").show(ctx, |ui| {
if ui.button("Add new default").clicked() {
tile_db.db.push(super::TileDef::default());
}
});
}