mirror of
https://github.com/Noratrieb/game-wip-dontplay.git
synced 2026-01-14 11:45:01 +01:00
lööp
This commit is contained in:
parent
40e412c024
commit
98dd54f1f2
18 changed files with 196 additions and 1445 deletions
341
src/app.rs
341
src/app.rs
|
|
@ -1,5 +1,4 @@
|
|||
use std::fmt::Write;
|
||||
|
||||
use anyhow::Context;
|
||||
use directories::ProjectDirs;
|
||||
use egui_sfml::SfEgui;
|
||||
|
|
@ -7,26 +6,21 @@ use gamedebug_core::{imm, imm_dbg};
|
|||
use sfml::{
|
||||
audio::SoundSource,
|
||||
graphics::{
|
||||
BlendMode, Color, Rect, RectangleShape, RenderStates, RenderTarget, RenderTexture,
|
||||
RenderWindow, Shape, Sprite, Transformable, View,
|
||||
BlendMode, Color, Rect, RectangleShape, RenderStates, RenderTarget,
|
||||
RenderTexture, RenderWindow, Shape, Sprite, Transformable, View,
|
||||
},
|
||||
system::{Vector2, Vector2u},
|
||||
window::{Event, Key},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
command::{Cmd, CmdVec},
|
||||
debug::{self, DebugState},
|
||||
game::{for_each_tile_on_screen, Biome, GameState},
|
||||
graphics::{self, ScreenSc, ScreenVec},
|
||||
input::Input,
|
||||
inventory::{ItemId, Slot, TileLayer, UseAction},
|
||||
input::Input, inventory::{ItemId, Slot, TileLayer, UseAction},
|
||||
math::{center_offset, TILE_SIZE},
|
||||
res::Res,
|
||||
tiles::TileId,
|
||||
CliArgs,
|
||||
res::Res, tiles::TileId, CliArgs,
|
||||
};
|
||||
|
||||
/// Application level state (includes game and ui state, etc.)
|
||||
pub struct App {
|
||||
pub rw: RenderWindow,
|
||||
|
|
@ -45,39 +39,10 @@ pub struct App {
|
|||
pub project_dirs: ProjectDirs,
|
||||
pub cmdvec: CmdVec,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new(args: CliArgs) -> anyhow::Result<Self> {
|
||||
let rw = graphics::make_window();
|
||||
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(),
|
||||
})
|
||||
loop {}
|
||||
}
|
||||
|
||||
pub fn do_game_loop(&mut self) {
|
||||
while !self.should_quit {
|
||||
self.do_event_handling();
|
||||
|
|
@ -89,252 +54,15 @@ impl App {
|
|||
self.game.tile_db.try_save();
|
||||
self.game.world.save();
|
||||
}
|
||||
|
||||
fn do_event_handling(&mut self) {
|
||||
while let Some(ev) = self.rw.poll_event() {
|
||||
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,
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
|
||||
fn do_update(&mut self) {
|
||||
let rt_size = self.rt.size();
|
||||
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);
|
||||
loop {}
|
||||
}
|
||||
|
||||
fn do_freecam(&mut self) {
|
||||
let spd = if self.input.down(Key::LShift) {
|
||||
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);
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
|
||||
fn do_rendering(&mut self) {
|
||||
self.game.light_pass(&mut self.light_map, &self.res);
|
||||
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));
|
||||
self.rw.clear(Color::rgb(40, 10, 70));
|
||||
self.rw.draw(&spr);
|
||||
// Draw light overlay with multiply blending
|
||||
let mut rst = RenderStates::default();
|
||||
rst.blend_mode = BlendMode::MULTIPLY;
|
||||
self.light_map.display();
|
||||
spr.set_texture(self.light_map.texture(), false);
|
||||
self.rw.draw_with_renderstates(&spr, &rst);
|
||||
drop(spr);
|
||||
// Draw ui on top of in-game scene
|
||||
self.rt.clear(Color::TRANSPARENT);
|
||||
let ui_dims = Vector2 {
|
||||
x: (self.rw.size().x / self.scale as u32) as f32,
|
||||
|
|
@ -380,7 +106,9 @@ impl App {
|
|||
if self.debug.show_atlas {
|
||||
let atlas = &self.res.atlas.tex;
|
||||
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);
|
||||
self.rw.draw(&rs);
|
||||
self.rw.draw(&Sprite::with_texture(atlas));
|
||||
|
|
@ -390,58 +118,15 @@ impl App {
|
|||
drop(spr);
|
||||
self.execute_commands();
|
||||
}
|
||||
|
||||
fn execute_commands(&mut self) {
|
||||
for cmd in self.cmdvec.drain(..) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tile collision entity for doing physics
|
||||
struct TileColEn {
|
||||
col: s2dc::Entity,
|
||||
platform: bool,
|
||||
}
|
||||
|
||||
fn viewport_center_offset(rw_size: Vector2u, rt_size: Vector2u, scale: u8) -> ScreenVec {
|
||||
let rw_size = rw_size;
|
||||
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,
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,58 +1,22 @@
|
|||
use std::ops::{BitAndAssign, BitOrAssign};
|
||||
|
||||
use num_traits::PrimInt;
|
||||
|
||||
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>(number: &mut N, n: usize, set: bool) {
|
||||
let mask = N::one() << n;
|
||||
if set {
|
||||
*number |= mask;
|
||||
} else {
|
||||
*number &= !mask;
|
||||
}
|
||||
pub fn set_nth_bit<N: PrimInt + BitOrAssign + BitAndAssign>(
|
||||
number: &mut N,
|
||||
n: usize,
|
||||
set: bool,
|
||||
) {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::bool_assert_comparison)]
|
||||
fn test_nth_bit_set() {
|
||||
let number: u8 = 0b0100_0100;
|
||||
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);
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::bool_assert_comparison)]
|
||||
fn test_set_nth_bit() {
|
||||
let mut number: u8 = 0b0000_0000;
|
||||
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);
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
use clap::Parser;
|
||||
|
||||
use crate::{command::Cmd, math::WorldPos};
|
||||
|
||||
#[derive(Parser)]
|
||||
pub enum CmdLine {
|
||||
Quit,
|
||||
|
|
@ -11,7 +9,6 @@ pub enum CmdLine {
|
|||
Spawn,
|
||||
Give(Give),
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
pub struct Tp {
|
||||
x: u32,
|
||||
|
|
@ -22,40 +19,22 @@ pub struct Tp {
|
|||
}
|
||||
impl Tp {
|
||||
fn to_world_pos(&self) -> WorldPos {
|
||||
WorldPos {
|
||||
x: self.x,
|
||||
y: self.y,
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
pub struct Give {
|
||||
name: String,
|
||||
}
|
||||
|
||||
pub enum Dispatch {
|
||||
Cmd(Cmd),
|
||||
ClearConsole,
|
||||
}
|
||||
|
||||
impl CmdLine {
|
||||
pub fn parse_cmdline(cmdline: &str) -> anyhow::Result<Self> {
|
||||
let words = std::iter::once(" ").chain(cmdline.split_whitespace());
|
||||
Ok(Self::try_parse_from(words)?)
|
||||
loop {}
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch(self) -> Dispatch {
|
||||
match self {
|
||||
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)),
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
200
src/debug.rs
200
src/debug.rs
|
|
@ -1,20 +1,13 @@
|
|||
use std::fmt::Write;
|
||||
|
||||
use egui::TextBuffer;
|
||||
use egui_inspect::{derive::Inspect, inspect};
|
||||
use sfml::audio::SoundSource;
|
||||
|
||||
use crate::{
|
||||
cmdline::CmdLine,
|
||||
command::CmdVec,
|
||||
game::GameState,
|
||||
cmdline::CmdLine, command::CmdVec, game::GameState,
|
||||
math::{px_per_frame_to_km_h, WorldPos},
|
||||
res::Res,
|
||||
stringfmt::LengthDisp,
|
||||
texture_atlas::AtlasBundle,
|
||||
res::Res, stringfmt::LengthDisp, texture_atlas::AtlasBundle,
|
||||
tiles::tiledb_edit_ui::TileDbEdit,
|
||||
};
|
||||
|
||||
#[derive(Default, Debug, Inspect)]
|
||||
pub struct DebugState {
|
||||
pub panel: bool,
|
||||
|
|
@ -23,7 +16,6 @@ pub struct DebugState {
|
|||
pub show_atlas: bool,
|
||||
pub console: Console,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Inspect)]
|
||||
pub struct Console {
|
||||
pub show: bool,
|
||||
|
|
@ -32,7 +24,6 @@ pub struct Console {
|
|||
pub just_opened: bool,
|
||||
pub history: Vec<String>,
|
||||
}
|
||||
|
||||
fn debug_panel_ui(
|
||||
mut debug: &mut DebugState,
|
||||
mut game: &mut GameState,
|
||||
|
|
@ -40,75 +31,92 @@ fn debug_panel_ui(
|
|||
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.world.player.feet_y() as f32 - WorldPos::SURFACE as f32)
|
||||
));
|
||||
ui.label(format!(
|
||||
"Player offset from center: {}",
|
||||
LengthDisp(game.world.player.col_en.en.pos.x as f32 - WorldPos::CENTER as f32)
|
||||
));
|
||||
ui.label(format!(
|
||||
"Hspeed: {} ({} km/h)",
|
||||
game.world.player.hspeed,
|
||||
px_per_frame_to_km_h(game.world.player.hspeed)
|
||||
));
|
||||
ui.label(format!(
|
||||
"Vspeed: {} ({} km/h)",
|
||||
game.world.player.vspeed,
|
||||
px_per_frame_to_km_h(game.world.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
|
||||
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.world.player.feet_y() as
|
||||
f32 - WorldPos::SURFACE as f32)
|
||||
),
|
||||
);
|
||||
ui.label(
|
||||
format!(
|
||||
"Player offset from center: {}", LengthDisp(game.world.player
|
||||
.col_en.en.pos.x as f32 - WorldPos::CENTER as f32)
|
||||
),
|
||||
);
|
||||
ui.label(
|
||||
format!(
|
||||
"Hspeed: {} ({} km/h)", game.world.player.hspeed,
|
||||
px_per_frame_to_km_h(game.world.player.hspeed)
|
||||
),
|
||||
);
|
||||
ui.label(
|
||||
format!(
|
||||
"Vspeed: {} ({} km/h)", game.world.player.vspeed,
|
||||
px_per_frame_to_km_h(game.world.player.vspeed)
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
if ui.button("Reload graphics").clicked() {
|
||||
res.atlas = AtlasBundle::new().unwrap();
|
||||
game.tile_db.update_rects(&res.atlas.rects);
|
||||
}
|
||||
ui.separator();
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
gamedebug_core::for_each_imm(|info| match info {
|
||||
gamedebug_core::Info::Msg(msg) => {
|
||||
ui.label(msg);
|
||||
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
|
||||
}
|
||||
},
|
||||
);
|
||||
if ui.button("Reload graphics").clicked() {
|
||||
res.atlas = AtlasBundle::new().unwrap();
|
||||
game.tile_db.update_rects(&res.atlas.rects);
|
||||
}
|
||||
gamedebug_core::Info::Rect(_, _, _, _, _) => todo!(),
|
||||
});
|
||||
});
|
||||
gamedebug_core::clear_immediates();
|
||||
});
|
||||
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,
|
||||
|
|
@ -125,42 +133,6 @@ pub(crate) fn do_debug_ui(
|
|||
console_ui(ctx, debug, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
fn console_ui(ctx: &egui::Context, debug: &mut DebugState, cmd: &mut CmdVec) {
|
||||
egui::Window::new("Console (F11)").show(ctx, |ui| {
|
||||
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;
|
||||
loop {}
|
||||
}
|
||||
|
|
|
|||
174
src/game.rs
174
src/game.rs
|
|
@ -1,26 +1,21 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use derivative::Derivative;
|
||||
use egui_inspect::derive::Inspect;
|
||||
use sfml::{
|
||||
graphics::{
|
||||
Color, Rect, RectangleShape, RenderTarget, RenderTexture, Shape, Sprite, Transformable,
|
||||
Color, Rect, RectangleShape, RenderTarget, RenderTexture, Shape, Sprite,
|
||||
Transformable,
|
||||
},
|
||||
system::{Vector2f, Vector2u},
|
||||
window::Key,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
graphics::{ScreenSc, ScreenVec},
|
||||
input::Input,
|
||||
inventory::{Inventory, ItemDb},
|
||||
input::Input, inventory::{Inventory, ItemDb},
|
||||
math::{smoothwave, wp_to_tp, WorldPos},
|
||||
res::Res,
|
||||
tiles::TileDb,
|
||||
world::{TilePos, World},
|
||||
res::Res, tiles::TileDb, world::{TilePos, World},
|
||||
worldgen::Worldgen,
|
||||
};
|
||||
|
||||
#[derive(Derivative, Inspect)]
|
||||
#[derivative(Debug)]
|
||||
pub struct GameState {
|
||||
|
|
@ -40,186 +35,39 @@ pub struct GameState {
|
|||
pub selected_inv_slot: usize,
|
||||
pub spawn_point: WorldPos,
|
||||
}
|
||||
|
||||
#[derive(Debug, Inspect)]
|
||||
pub struct LightSource {
|
||||
pub pos: ScreenVec,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug, Inspect)]
|
||||
pub enum Biome {
|
||||
Surface,
|
||||
Underground,
|
||||
}
|
||||
|
||||
impl GameState {
|
||||
pub fn update(&mut self, input: &Input) {
|
||||
if input.pressed(Key::Num1) {
|
||||
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;
|
||||
loop {}
|
||||
}
|
||||
pub(crate) fn draw_world(&mut self, rt: &mut RenderTexture, res: &mut Res) {
|
||||
self.light_sources.clear();
|
||||
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);
|
||||
}
|
||||
});
|
||||
loop {}
|
||||
}
|
||||
pub fn draw_entities(&mut self, rt: &mut RenderTexture) {
|
||||
let (x, y, w, h) = self.world.player.col_en.en.xywh();
|
||||
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);
|
||||
loop {}
|
||||
}
|
||||
|
||||
pub fn draw_ui(&mut self, rt: &mut RenderTexture, res: &Res, ui_dims: Vector2f) {
|
||||
let mut s = Sprite::with_texture(&res.atlas.tex);
|
||||
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);
|
||||
}
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
|
||||
pub(crate) fn new(world_name: String, path: PathBuf, res: &Res) -> GameState {
|
||||
let mut spawn_point = WorldPos::SURFACE_CENTER;
|
||||
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,
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn for_each_tile_on_screen(
|
||||
camera_offset: WorldPos,
|
||||
rt_size: Vector2u,
|
||||
mut f: impl FnMut(TilePos, ScreenVec),
|
||||
) {
|
||||
for y in (-32..(rt_size.y as i16) + 32).step_by(32) {
|
||||
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,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,55 +1,33 @@
|
|||
use egui_inspect::derive::Inspect;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sfml::{
|
||||
graphics::RenderWindow,
|
||||
system::Vector2f,
|
||||
window::{ContextSettings, Style, VideoMode},
|
||||
graphics::RenderWindow, system::Vector2f, window::{ContextSettings, Style, VideoMode},
|
||||
};
|
||||
use sfml_xt::graphics::RenderWindowExt;
|
||||
|
||||
use crate::math::FPS_TARGET;
|
||||
|
||||
pub struct ScreenRes {
|
||||
pub w: u16,
|
||||
pub h: u16,
|
||||
}
|
||||
|
||||
impl ScreenRes {
|
||||
fn to_sf(&self) -> VideoMode {
|
||||
VideoMode {
|
||||
width: self.w as _,
|
||||
height: self.h as _,
|
||||
bits_per_pixel: 32,
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy, Debug, Inspect, Serialize, Deserialize)]
|
||||
pub struct ScreenVec {
|
||||
pub x: ScreenSc,
|
||||
pub y: ScreenSc,
|
||||
}
|
||||
|
||||
/// Screen position/offset scalar
|
||||
/// We assume this game won't be played above 32767*32767 resolution
|
||||
pub type ScreenSc = i16;
|
||||
|
||||
impl ScreenVec {
|
||||
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 };
|
||||
|
||||
pub fn make_window() -> RenderWindow {
|
||||
let mut rw = RenderWindow::new(
|
||||
DEFAULT_RES.to_sf(),
|
||||
"Mantle Diver",
|
||||
Style::DEFAULT,
|
||||
&ContextSettings::default(),
|
||||
);
|
||||
rw.set_framerate_limit(FPS_TARGET.into());
|
||||
rw.center();
|
||||
rw
|
||||
loop {}
|
||||
}
|
||||
|
|
|
|||
57
src/input.rs
57
src/input.rs
|
|
@ -1,8 +1,6 @@
|
|||
use fnv::FnvHashSet;
|
||||
use sfml::window::{mouse, Event, Key};
|
||||
|
||||
use crate::graphics::ScreenVec;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Input {
|
||||
down: FnvHashSet<Key>,
|
||||
|
|
@ -12,65 +10,18 @@ pub struct Input {
|
|||
pub mouse_down_loc: ScreenVec,
|
||||
pub mid_pressed: bool,
|
||||
}
|
||||
|
||||
impl Input {
|
||||
pub fn update_from_event(&mut self, ev: &Event, egui_kbd: bool, egui_ptr: bool) {
|
||||
match ev {
|
||||
&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;
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
/// Pressed event should be cleared every frame
|
||||
pub fn clear_pressed(&mut self) {
|
||||
self.mid_pressed = false;
|
||||
self.pressed.clear();
|
||||
loop {}
|
||||
}
|
||||
pub fn down(&self, key: Key) -> bool {
|
||||
self.down.contains(&key)
|
||||
loop {}
|
||||
}
|
||||
pub fn pressed(&self, key: Key) -> bool {
|
||||
self.pressed.contains(&key)
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
104
src/inventory.rs
104
src/inventory.rs
|
|
@ -1,22 +1,15 @@
|
|||
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
|
||||
pub type ItemId = u16;
|
||||
/// We won't have more than 65535 item quantity in a single slot
|
||||
pub type ItemQty = u16;
|
||||
|
||||
/// Inventory slot
|
||||
#[derive(Debug, Inspect)]
|
||||
pub struct Slot {
|
||||
pub id: ItemId,
|
||||
pub qty: ItemQty,
|
||||
}
|
||||
|
||||
#[derive(Debug, Inspect)]
|
||||
pub struct Inventory {
|
||||
pub slots: Vec<Slot>,
|
||||
|
|
@ -24,37 +17,9 @@ pub struct Inventory {
|
|||
impl Inventory {
|
||||
/// A new inventory filled with some debug items
|
||||
pub(crate) fn new_debug() -> Inventory {
|
||||
Self {
|
||||
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,
|
||||
},
|
||||
],
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Inspect)]
|
||||
pub struct ItemDef {
|
||||
pub name: String,
|
||||
|
|
@ -64,14 +29,12 @@ pub struct ItemDef {
|
|||
pub use_action: UseAction,
|
||||
pub consumable: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Inspect, PartialEq)]
|
||||
pub enum TileLayer {
|
||||
Bg,
|
||||
Mid,
|
||||
Fg,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum UseAction {
|
||||
PlaceBgTile { id: BgTileId },
|
||||
|
|
@ -79,78 +42,17 @@ pub enum UseAction {
|
|||
PlaceFgTile { id: FgTileId },
|
||||
RemoveTile { layer: TileLayer },
|
||||
}
|
||||
|
||||
#[derive(Debug, Inspect)]
|
||||
pub struct ItemDb {
|
||||
pub db: Vec<ItemDef>,
|
||||
}
|
||||
|
||||
impl Default for ItemDb {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
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,
|
||||
},
|
||||
],
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod items {
|
||||
use super::ItemId;
|
||||
|
||||
pub const DIRT_BLOCK: ItemId = 0;
|
||||
pub const TORCH: ItemId = 1;
|
||||
pub const PLATFORM: ItemId = 2;
|
||||
|
|
|
|||
76
src/math.rs
76
src/math.rs
|
|
@ -1,19 +1,14 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use egui_inspect::derive::Inspect;
|
||||
use num_traits::{Num, Signed};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::world::{TPosSc, TilePos};
|
||||
|
||||
pub type WPosSc = u32;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Inspect)]
|
||||
pub struct WorldPos {
|
||||
pub x: WPosSc,
|
||||
pub y: WPosSc,
|
||||
}
|
||||
|
||||
/// Tile size in pixels
|
||||
pub const TILE_SIZE: u8 = 32;
|
||||
/// Pixels per meter.
|
||||
|
|
@ -21,25 +16,17 @@ pub const PX_PER_M: f32 = TILE_SIZE as f32 * 2.;
|
|||
/// Meters per pixel
|
||||
pub const M_PER_PX: f32 = 1. / PX_PER_M;
|
||||
pub const FPS_TARGET: u8 = 60;
|
||||
|
||||
pub fn px_per_frame_to_m_per_s(px_per_frame: f32) -> f32 {
|
||||
let m_per_frame = px_per_frame / PX_PER_M;
|
||||
m_per_frame * FPS_TARGET as f32
|
||||
loop {}
|
||||
}
|
||||
|
||||
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.
|
||||
pub const WORLD_EXTENT: TPosSc = 100_000;
|
||||
|
||||
impl WorldPos {
|
||||
pub fn tile_pos(&self) -> TilePos {
|
||||
TilePos {
|
||||
x: wp_to_tp(self.x),
|
||||
y: wp_to_tp(self.y),
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
/// Horizontal center of the world
|
||||
pub const CENTER: WPosSc = (WORLD_EXTENT / 2) * TILE_SIZE as WPosSc;
|
||||
|
|
@ -50,47 +37,20 @@ impl WorldPos {
|
|||
x: Self::CENTER,
|
||||
y: Self::SURFACE,
|
||||
};
|
||||
|
||||
pub(crate) fn to_s2dc(self) -> s2dc::Vec2 {
|
||||
s2dc::Vec2 {
|
||||
x: self.x as i32,
|
||||
y: self.y as i32,
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
let diff = yw - xw;
|
||||
diff / N::from(2)
|
||||
loop {}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
let period = max * T::from(2);
|
||||
let value = input % period;
|
||||
if value < max {
|
||||
value
|
||||
} else {
|
||||
period - value
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Inspect, Default, Clone, Copy)]
|
||||
pub struct IntRect {
|
||||
pub x: i32,
|
||||
|
|
@ -100,30 +60,14 @@ pub struct IntRect {
|
|||
}
|
||||
impl IntRect {
|
||||
pub(crate) fn to_sf(self) -> sfml::graphics::Rect<i32> {
|
||||
sfml::graphics::Rect::<i32> {
|
||||
left: self.x,
|
||||
top: self.y,
|
||||
width: self.w,
|
||||
height: self.h,
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smooth_wave() {
|
||||
assert_eq!(smoothwave(0, 100), 0);
|
||||
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);
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wp_to_tp() {
|
||||
assert_eq!(wp_to_tp(0), 0);
|
||||
assert_eq!(wp_to_tp(1), 0);
|
||||
assert_eq!(wp_to_tp(33), 1);
|
||||
loop {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
use std::path::Path;
|
||||
|
||||
use egui_inspect::{derive::Inspect, inspect};
|
||||
use s2dc::{vec2, MobileEntity};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
math::{WorldPos, TILE_SIZE},
|
||||
world::{TPosSc, TilePos},
|
||||
};
|
||||
|
||||
#[derive(Debug, Inspect, Serialize, Deserialize)]
|
||||
pub struct Player {
|
||||
#[inspect_with(inspect_mobile_entity)]
|
||||
|
|
@ -19,43 +16,24 @@ pub struct Player {
|
|||
/// true if the player wants to jump down from a platform
|
||||
pub down_intent: bool,
|
||||
}
|
||||
|
||||
fn inspect_mobile_entity(en: &mut MobileEntity, ui: &mut egui::Ui, _id_src: u64) {
|
||||
inspect! {
|
||||
ui,
|
||||
en.en.pos.x,
|
||||
en.en.pos.y,
|
||||
en.en.bb.x,
|
||||
en.en.bb.y
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
|
||||
impl Player {
|
||||
pub fn new_at(pos: WorldPos) -> Self {
|
||||
Self {
|
||||
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,
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn center_tp(&self) -> TilePos {
|
||||
TilePos {
|
||||
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,
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
pub fn can_jump(&self) -> bool {
|
||||
self.jumps_left > 0
|
||||
loop {}
|
||||
}
|
||||
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) {
|
||||
let result = std::fs::write(path.join("player.dat"), rmp_serde::to_vec(self).unwrap());
|
||||
log::info!("{result:?}");
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,13 @@
|
|||
use sfml::audio::Music;
|
||||
|
||||
use crate::texture_atlas::AtlasBundle;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Res {
|
||||
pub atlas: AtlasBundle,
|
||||
pub surf_music: Music<'static>,
|
||||
pub und_music: Music<'static>,
|
||||
}
|
||||
|
||||
impl Res {
|
||||
pub fn load() -> anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
atlas: AtlasBundle::new()?,
|
||||
surf_music: Music::from_file("res/music/music.ogg").unwrap(),
|
||||
und_music: Music::from_file("res/music/cave2.ogg").unwrap(),
|
||||
})
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,8 @@
|
|||
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")
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,106 +1,28 @@
|
|||
use std::{collections::HashMap, path::Path};
|
||||
|
||||
use crate::math::IntRect;
|
||||
use sfml::{graphics::Texture, SfBox};
|
||||
use texture_packer::{texture::Texture as _, TexturePacker, TexturePackerConfig};
|
||||
|
||||
pub type RectMap = HashMap<String, IntRect>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AtlasBundle {
|
||||
pub tex: SfBox<Texture>,
|
||||
// Key could be `tiles/dirt` for example, derived from folder+filename without extension
|
||||
pub rects: RectMap,
|
||||
}
|
||||
|
||||
impl AtlasBundle {
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
let cfg = TexturePackerConfig {
|
||||
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 })
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_pix_buf(packer: &TexturePacker<image::DynamicImage, String>) -> Vec<u8> {
|
||||
let (w, h) = (packer.width(), packer.height());
|
||||
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
|
||||
loop {}
|
||||
}
|
||||
|
||||
fn path_img_key(path: &Path) -> String {
|
||||
let mut rev_iter = path.components().rev();
|
||||
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()
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_img_key() {
|
||||
assert_eq!(
|
||||
&path_img_key("/home/person/res/graphics/tiles/foo.png".as_ref()),
|
||||
"tiles/foo"
|
||||
);
|
||||
loop {}
|
||||
}
|
||||
|
||||
fn walk_graphics(mut f: impl FnMut(&Path)) {
|
||||
for en in walkdir::WalkDir::new("res/graphics") {
|
||||
let en = en.unwrap();
|
||||
if en.file_type().is_file() {
|
||||
f(en.path());
|
||||
}
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use egui_inspect::{derive::Inspect, Inspect};
|
||||
|
||||
use crate::{
|
||||
graphics::{ScreenSc, ScreenVec},
|
||||
math::TILE_SIZE,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Inspect)]
|
||||
pub struct TileDbEdit {
|
||||
open: bool,
|
||||
|
|
@ -15,109 +12,39 @@ pub struct TileDbEdit {
|
|||
}
|
||||
impl TileDbEdit {
|
||||
pub(crate) fn ui(&mut self, ctx: &egui::Context, tile_db: &mut TileDb) {
|
||||
if !self.open {
|
||||
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),
|
||||
});
|
||||
});
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum Layer {
|
||||
Bg,
|
||||
Fg,
|
||||
Mid,
|
||||
}
|
||||
|
||||
impl Default for Layer {
|
||||
fn default() -> Self {
|
||||
Self::Bg
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
use super::{Bg, Fg, Mid, TileDb, TileDef, TileLayer, DEFAULT_TILE_BB};
|
||||
|
||||
trait SpecialUi {
|
||||
fn special_ui(&mut self, ui: &mut egui::Ui);
|
||||
}
|
||||
|
||||
impl SpecialUi for TileDef<Mid> {
|
||||
fn special_ui(&mut self, ui: &mut egui::Ui) {
|
||||
match &mut self.layer.bb {
|
||||
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");
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpecialUi for TileDef<Bg> {
|
||||
fn special_ui(&mut self, _ui: &mut egui::Ui) {}
|
||||
}
|
||||
|
||||
impl SpecialUi for TileDef<Fg> {
|
||||
fn special_ui(&mut self, _ui: &mut egui::Ui) {}
|
||||
}
|
||||
|
||||
fn db_ui<Layer: TileLayer + Debug>(db: &mut Vec<TileDef<Layer>>, ui: &mut egui::Ui)
|
||||
where
|
||||
<Layer as TileLayer>::SpecificDef: Debug + Default + Inspect,
|
||||
TileDef<Layer>: SpecialUi,
|
||||
{
|
||||
for (i, def) in db.iter_mut().enumerate() {
|
||||
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());
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
|
|
|
|||
188
src/world.rs
188
src/world.rs
|
|
@ -1,52 +1,30 @@
|
|||
mod reg_chunk_existence;
|
||||
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 fnv::FnvHashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
math::WorldPos,
|
||||
player::Player,
|
||||
tiles::{BgTileId, FgTileId, MidTileId, TileId},
|
||||
world::reg_chunk_existence::ExistenceBitset,
|
||||
worldgen::Worldgen,
|
||||
math::WorldPos, player::Player, tiles::{BgTileId, FgTileId, MidTileId, TileId},
|
||||
world::reg_chunk_existence::ExistenceBitset, worldgen::Worldgen,
|
||||
};
|
||||
|
||||
use self::serialization::save_chunk;
|
||||
|
||||
pub type ChkPosSc = u16;
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Debug, Clone, Copy, Inspect)]
|
||||
pub struct ChunkPos {
|
||||
pub x: ChkPosSc,
|
||||
pub y: ChkPosSc,
|
||||
}
|
||||
|
||||
impl ChunkPos {
|
||||
/// Returns the region this chunk position belongs to
|
||||
pub fn region(&self) -> (u8, u8) {
|
||||
(
|
||||
(self.x / REGION_CHUNK_EXTENT as ChkPosSc) as u8,
|
||||
(self.y / REGION_CHUNK_EXTENT as ChkPosSc) as u8,
|
||||
)
|
||||
loop {}
|
||||
}
|
||||
/// Returns the local position in the region (0-7)
|
||||
pub fn local(&self) -> (u8, u8) {
|
||||
(
|
||||
(self.x % REGION_CHUNK_EXTENT as ChkPosSc) as u8,
|
||||
(self.y % REGION_CHUNK_EXTENT as ChkPosSc) as u8,
|
||||
)
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Inspect)]
|
||||
pub struct World {
|
||||
/// The currently loaded chunks
|
||||
|
|
@ -59,220 +37,108 @@ pub struct World {
|
|||
#[opaque]
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
impl World {
|
||||
pub fn new(spawn_point: WorldPos, name: &str, path: PathBuf) -> Self {
|
||||
Self {
|
||||
chunks: Default::default(),
|
||||
ticks: Default::default(),
|
||||
player: Player::new_at(spawn_point),
|
||||
name: name.to_string(),
|
||||
path,
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
/// Get mutable access to the tile at `pos`.
|
||||
///
|
||||
/// Loads or generates the containing chunk if necessary.
|
||||
pub fn tile_at_mut(&mut self, pos: TilePos, worldgen: &Worldgen) -> &mut Tile {
|
||||
let (chk, local) = pos.to_chunk_and_local();
|
||||
let chk = self
|
||||
.chunks
|
||||
.entry(chk)
|
||||
.or_insert_with(|| Chunk::load_or_gen(chk, worldgen, &self.path));
|
||||
chk.at_mut(local)
|
||||
loop {}
|
||||
}
|
||||
pub fn save(&self) {
|
||||
let result = std::fs::create_dir_all(&self.path);
|
||||
log::info!("{result:?}");
|
||||
self.save_meta();
|
||||
self.player.save(&self.path);
|
||||
self.save_chunks();
|
||||
loop {}
|
||||
}
|
||||
pub fn save_meta(&self) {
|
||||
let meta = WorldMetaSave {
|
||||
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:?}");
|
||||
loop {}
|
||||
}
|
||||
pub fn save_chunks(&self) {
|
||||
for (pos, chk) in self.chunks.iter() {
|
||||
save_chunk(pos, chk, &self.path);
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
loc_idx as usize * CHUNK_BYTES
|
||||
loop {}
|
||||
}
|
||||
|
||||
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 {
|
||||
format!("{x}.{y}.rgn")
|
||||
loop {}
|
||||
}
|
||||
|
||||
const CHUNK_BYTES: usize = CHUNK_N_TILES * TILE_BYTES;
|
||||
const TILE_BYTES: usize = 3 * 2;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct WorldMetaSave {
|
||||
name: String,
|
||||
ticks: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct TilePos {
|
||||
pub x: TPosSc,
|
||||
pub y: TPosSc,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct ChunkLocalTilePos {
|
||||
pub x: ChkLocalTPosSc,
|
||||
pub y: ChkLocalTPosSc,
|
||||
}
|
||||
|
||||
/// Chunk-local tile position scalar. Supports up to 256 tiles per chunk.
|
||||
type ChkLocalTPosSc = u8;
|
||||
|
||||
impl TilePos {
|
||||
pub fn to_chunk_and_local(self) -> (ChunkPos, ChunkLocalTilePos) {
|
||||
let chk = ChunkPos {
|
||||
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)
|
||||
loop {}
|
||||
}
|
||||
|
||||
pub(crate) fn to_chunk(self) -> ChunkPos {
|
||||
self.to_chunk_and_local().0
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
fn chk_pos(tile: TPosSc) -> ChkPosSc {
|
||||
(tile / CHUNK_EXTENT as TPosSc) as ChkPosSc
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chk_pos() {
|
||||
assert_eq!(chk_pos(0), 0);
|
||||
assert_eq!(chk_pos(1), 0);
|
||||
assert_eq!(chk_pos(127), 0);
|
||||
assert_eq!(chk_pos(128), 1);
|
||||
loop {}
|
||||
}
|
||||
|
||||
fn chunk_local(global: TPosSc) -> ChkLocalTPosSc {
|
||||
(global % CHUNK_EXTENT as TPosSc) as ChkLocalTPosSc
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chunk_local() {
|
||||
assert_eq!(chunk_local(0), 0);
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_chunk_and_local() {
|
||||
assert_eq!(
|
||||
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 })
|
||||
);
|
||||
loop {}
|
||||
}
|
||||
|
||||
// Need to support at least 4 million tiles long
|
||||
pub type TPosSc = u32;
|
||||
|
||||
pub const CHUNK_EXTENT: u16 = 128;
|
||||
const CHUNK_N_TILES: usize = CHUNK_EXTENT as usize * CHUNK_EXTENT as usize;
|
||||
|
||||
type ChunkTiles = [Tile; CHUNK_N_TILES];
|
||||
|
||||
fn default_chunk_tiles() -> ChunkTiles {
|
||||
[Tile {
|
||||
bg: TileId::EMPTY,
|
||||
mid: TileId::EMPTY,
|
||||
fg: TileId::EMPTY,
|
||||
}; CHUNK_N_TILES]
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[derive(Debug, Inspect)]
|
||||
pub struct Chunk {
|
||||
tiles: ChunkTiles,
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
pub fn gen(pos: ChunkPos, worldgen: &Worldgen) -> Self {
|
||||
let mut tiles = default_chunk_tiles();
|
||||
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 }
|
||||
loop {}
|
||||
}
|
||||
|
||||
pub fn load_or_gen(chk: ChunkPos, worldgen: &Worldgen, world_path: &Path) -> Chunk {
|
||||
log::info!("Loading chunk {chk:?} (reg: {:?})", chk.region());
|
||||
let reg_filename = world_path.join(format_reg_file_name(chk.region()));
|
||||
if chunk_exists(®_filename, chk) {
|
||||
log::info!("Chunk exists, loading");
|
||||
let mut f = File::open(®_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)
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
|
||||
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 {
|
||||
if !Path::new(®_path).exists() {
|
||||
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)
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Inspect)]
|
||||
pub struct Tile {
|
||||
/// Background wall behind entities
|
||||
|
|
@ -282,12 +148,10 @@ pub struct Tile {
|
|||
/// A layer on top of the mid wall. Usually ores or decorative pieces.
|
||||
pub fg: FgTileId,
|
||||
}
|
||||
|
||||
pub const REGION_CHUNK_EXTENT: u8 = 8;
|
||||
pub const REGION_N_CHUNKS: u8 = REGION_CHUNK_EXTENT * REGION_CHUNK_EXTENT;
|
||||
/// This is the uncompressed byte length of a region
|
||||
pub const REGION_BYTES: usize = REGION_N_CHUNKS as usize * CHUNK_BYTES;
|
||||
|
||||
#[allow(clippy::assertions_on_constants)]
|
||||
const _: () = assert!(
|
||||
REGION_N_CHUNKS <= 64,
|
||||
|
|
|
|||
|
|
@ -1,37 +1,17 @@
|
|||
use std::{fs::File, io::Read, path::Path};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ExistenceBitset(pub u64);
|
||||
|
||||
impl ExistenceBitset {
|
||||
pub const EMPTY: Self = Self(0);
|
||||
|
||||
pub fn read_from_file(f: &mut File) -> ExistenceBitset {
|
||||
let mut buf = [0; 8];
|
||||
f.read_exact(&mut buf).unwrap();
|
||||
ExistenceBitset(u64::from_le_bytes(buf))
|
||||
loop {}
|
||||
}
|
||||
|
||||
pub fn read_from_fs(path: &Path) -> ExistenceBitset {
|
||||
let mut f = File::open(path).unwrap();
|
||||
Self::read_from_file(&mut f)
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ExistenceBitset {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f)?;
|
||||
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(())
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,93 +1,22 @@
|
|||
use std::{
|
||||
fs::OpenOptions,
|
||||
io::{Seek, Write},
|
||||
fs::OpenOptions, io::{Seek, Write},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use crate::world::{
|
||||
format_reg_file_name, loc_byte_idx, loc_idx, reg_chunk_existence::ExistenceBitset,
|
||||
REGION_BYTES, TILE_BYTES,
|
||||
};
|
||||
|
||||
use super::{default_chunk_tiles, loc_byte_idx_xy, Chunk, ChunkPos};
|
||||
|
||||
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()));
|
||||
let reg_file_exists = Path::new(®_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(®_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(®ion_tile_data[..], COMP_LEVEL).unwrap());
|
||||
let cursor = f.stream_position().unwrap();
|
||||
f.set_len(cursor).unwrap();
|
||||
log::info!("{result:?}");
|
||||
loop {}
|
||||
}
|
||||
|
||||
const COMP_LEVEL: i32 = 9;
|
||||
|
||||
impl Chunk {
|
||||
pub fn load_from_region(data: &[u8], x: u8, y: u8) -> Self {
|
||||
let byte_idx = loc_byte_idx_xy(x, y);
|
||||
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 }
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chunk_seri() {
|
||||
env_logger::builder()
|
||||
.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();
|
||||
loop {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,74 +1,23 @@
|
|||
use worldgen::{
|
||||
constraint,
|
||||
noise::perlin::PerlinNoise,
|
||||
constraint, noise::perlin::PerlinNoise,
|
||||
noisemap::{NoiseMap, NoiseMapGenerator, Seed, Step},
|
||||
world::{
|
||||
tile::{Constraint, ConstraintType},
|
||||
Size, Tile, World,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
tiles::{BgTileId, FgTileId, MidTileId, TileId},
|
||||
world::{ChunkPos, Tile as Tl, CHUNK_EXTENT},
|
||||
};
|
||||
|
||||
pub struct Worldgen {
|
||||
world: World<crate::world::Tile>,
|
||||
}
|
||||
|
||||
impl Worldgen {
|
||||
pub fn from_seed(seed: i64) -> Self {
|
||||
let noise = PerlinNoise::new();
|
||||
|
||||
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 }
|
||||
loop {}
|
||||
}
|
||||
pub fn chunk_noise(&self, pos: ChunkPos) -> Vec<Vec<Tl>> {
|
||||
self.world.generate(pos.x as i64, pos.y as i64).unwrap()
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue