mirror of
https://github.com/Noratrieb/game-wip-dontplay.git
synced 2026-01-14 19:55:02 +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 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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
200
src/debug.rs
200
src/debug.rs
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
174
src/game.rs
174
src/game.rs
|
|
@ -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,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
57
src/input.rs
57
src/input.rs
|
|
@ -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 {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
104
src/inventory.rs
104
src/inventory.rs
|
|
@ -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;
|
||||||
|
|
|
||||||
76
src/math.rs
76
src/math.rs
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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:?}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
188
src/world.rs
188
src/world.rs
|
|
@ -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(®_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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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(®_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,
|
||||||
|
|
|
||||||
|
|
@ -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(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(®_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:?}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue