1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2024-10-31 19:44:20 +00:00

editor shit

This commit is contained in:
Alula 2022-01-06 02:11:17 +01:00
parent e2afafdfa3
commit ef84379b62
No known key found for this signature in database
GPG key ID: 3E00485503A1D8BA
15 changed files with 820 additions and 132 deletions

View file

@ -6,7 +6,6 @@ authors = ["Alula"]
edition = "2018"
[lib]
#crate-type = ["lib", "cdylib"]
crate-type = ["lib"]
[[bin]]
@ -57,6 +56,7 @@ case_insensitive_hashmap = "1.0.0"
chrono = "0.4"
cpal = { git = "https://github.com/doukutsu-rs/cpal.git", rev = "4218ff23242834d36bcdcc0c2e3883985c15b5e0" }
directories = "3"
downcast = "0.11"
funty = "=1.1.0" # https://github.com/bitvecto-rs/bitvec/issues/105
glutin = { git = "https://github.com/doukutsu-rs/glutin.git", rev = "8dd457b9adb7dbac7ade337246b6356c784272d9", optional = true, default_features = false, features = ["x11"] }
imgui = "0.8.0"
@ -76,7 +76,7 @@ serde = { version = "1", features = ["derive"] }
serde_derive = "1"
serde_cbor = { version = "0.11.2", optional = true }
serde_json = "1.0"
simple_logger = { version = "1.13" }
simple_logger = { version = "1.16", features = ["colors", "threads"] }
strum = "0.20"
strum_macros = "0.20"
tokio = { version = "1.12.0", features = ["net"], optional = true }

View file

@ -48,12 +48,13 @@ impl Background {
BackgroundType::TiledStatic => {
graphics::clear(ctx, stage.data.background_color);
let count_x = state.canvas_size.0 as usize / batch.width() + 1;
let count_y = state.canvas_size.1 as usize / batch.height() + 1;
let (bg_width, bg_height) = (batch.width() as i32, batch.height() as i32);
let count_x = state.canvas_size.0 as i32 / bg_width + 1;
let count_y = state.canvas_size.1 as i32 / bg_height + 1;
for y in 0..count_y {
for x in 0..count_x {
batch.add((x * batch.width()) as f32, (y * batch.height()) as f32);
for y in -1..count_y {
for x in -1..count_x {
batch.add((x * bg_width) as f32, (y * bg_height) as f32);
}
}
}
@ -69,12 +70,13 @@ impl Background {
)
};
let count_x = state.canvas_size.0 as usize / batch.width() + 2;
let count_y = state.canvas_size.1 as usize / batch.height() + 2;
let (bg_width, bg_height) = (batch.width() as i32, batch.height() as i32);
let count_x = state.canvas_size.0 as i32 / bg_width + 2;
let count_y = state.canvas_size.1 as i32 / bg_height + 2;
for y in 0..count_y {
for x in 0..count_x {
batch.add((x * batch.width()) as f32 - off_x, (y * batch.height()) as f32 - off_y);
for y in -1..count_y {
for x in -1..count_x {
batch.add((x * bg_width) as f32 - off_x, (y * bg_height) as f32 - off_y);
}
}
}

285
src/editor/mod.rs Normal file
View file

@ -0,0 +1,285 @@
use std::cell::RefCell;
use std::ops::Deref;
use std::rc::Rc;
use imgui::{Image, MouseButton, Window, WindowFlags};
use crate::common::{Color, Rect};
use crate::components::background::Background;
use crate::components::tilemap::{TileLayer, Tilemap};
use crate::frame::Frame;
use crate::stage::{Stage, StageTexturePaths};
use crate::{graphics, Context, GameResult, SharedGameState, I_MAG};
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum CurrentTool {
Move,
Brush,
Fill,
Rectangle,
}
pub struct EditorInstance {
pub stage: Stage,
pub stage_id: usize,
pub frame: Frame,
pub background: Background,
pub stage_textures: Rc<RefCell<StageTexturePaths>>,
pub tilemap: Tilemap,
pub zoom: f32,
pub current_tile: u8,
pub mouse_pos: (f32, f32),
pub want_capture_mouse: bool,
}
impl EditorInstance {
pub fn new(stage_id: usize, stage: Stage) -> EditorInstance {
let stage_textures = {
let mut textures = StageTexturePaths::new();
textures.update(&stage);
Rc::new(RefCell::new(textures))
};
let mut frame = Frame::new();
frame.x = -16 * 0x200;
frame.y = -48 * 0x200;
EditorInstance {
stage,
stage_id,
frame,
background: Background::new(),
stage_textures,
tilemap: Tilemap::new(),
zoom: 2.0,
current_tile: 0,
mouse_pos: (0.0, 0.0),
want_capture_mouse: true,
}
}
pub fn process(&mut self, state: &mut SharedGameState, ctx: &mut Context, ui: &mut imgui::Ui, tool: CurrentTool) {
self.frame.prev_x = self.frame.x;
self.frame.prev_y = self.frame.y;
self.mouse_pos = (ui.io().mouse_pos[0], ui.io().mouse_pos[1]);
self.want_capture_mouse = ui.io().want_capture_mouse;
let mut drag = false;
match tool {
CurrentTool::Move => {
if ui.io().want_capture_mouse {
return;
}
drag |= ui.is_mouse_down(MouseButton::Left) || ui.is_mouse_down(MouseButton::Right);
}
CurrentTool::Brush => {
self.palette_window(state, ctx, ui);
if ui.io().want_capture_mouse {
return;
}
drag |= ui.is_mouse_down(MouseButton::Right);
if !drag && ui.is_mouse_down(MouseButton::Left) {
let tile_size = self.stage.map.tile_size.as_int();
let halft = tile_size / 2;
let stage_mouse_x = (self.frame.x / 0x200) + halft + (self.mouse_pos.0 / self.zoom) as i32;
let stage_mouse_y = (self.frame.y / 0x200) + halft + (self.mouse_pos.1 / self.zoom) as i32;
let tile_x = stage_mouse_x / tile_size;
let tile_y = stage_mouse_y / tile_size;
if tile_x >= 0
&& tile_y >= 0
&& tile_x < self.stage.map.width as i32
&& tile_y < self.stage.map.height as i32
{
self.stage.change_tile(tile_x as usize, tile_y as usize, self.current_tile);
}
}
}
CurrentTool::Fill => {
self.palette_window(state, ctx, ui);
drag |= ui.is_mouse_down(MouseButton::Right);
}
CurrentTool::Rectangle => {
self.palette_window(state, ctx, ui);
drag |= ui.is_mouse_down(MouseButton::Right);
}
}
if drag {
self.frame.x -= (512.0 * ui.io().mouse_delta[0] as f32 / self.zoom) as i32;
self.frame.y -= (512.0 * ui.io().mouse_delta[1] as f32 / self.zoom) as i32;
self.frame.prev_x = self.frame.x;
self.frame.prev_y = self.frame.y;
}
}
fn tile_cursor(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
if self.want_capture_mouse {
return Ok(());
}
let tile_size = self.stage.map.tile_size.as_int();
let halft = tile_size / 2;
let stage_mouse_x = (self.frame.x / 0x200) + halft + (self.mouse_pos.0 / self.zoom) as i32;
let stage_mouse_y = (self.frame.y / 0x200) + halft + (self.mouse_pos.1 / self.zoom) as i32;
let tile_x = stage_mouse_x / tile_size;
let tile_y = stage_mouse_y / tile_size;
let frame_x = self.frame.x as f32 / 512.0;
let frame_y = self.frame.y as f32 / 512.0;
if tile_x < 0 || tile_y < 0 || tile_x >= self.stage.map.width as i32 || tile_y >= self.stage.map.height as i32 {
return Ok(());
}
let name = &self.stage_textures.deref().borrow().tileset_fg;
if let Ok(batch) = state.texture_set.get_or_load_batch(ctx, &state.constants, name) {
let tile_size16 = tile_size as u16;
let rect = Rect::new_size(
(self.current_tile as u16 % 16) * tile_size16,
(self.current_tile as u16 / 16) * tile_size16,
tile_size16,
tile_size16,
);
batch.add_rect_tinted(
(tile_x * tile_size - halft) as f32 - frame_x,
(tile_y * tile_size - halft) as f32 - frame_y,
(255, 255, 255, 192),
&rect,
);
batch.draw(ctx)?;
}
Ok(())
}
fn palette_window(&mut self, state: &mut SharedGameState, ctx: &mut Context, ui: &imgui::Ui) {
Window::new("Palette")
.size([260.0, 260.0], imgui::Condition::Always)
.position(ui.io().display_size, imgui::Condition::FirstUseEver)
.position_pivot([1.0, 1.0])
.resizable(false)
.build(ui, || {
let name = &self.stage_textures.deref().borrow().tileset_fg;
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, name);
let pos = ui.cursor_screen_pos();
let tile_size = self.stage.map.tile_size.as_float();
if let Ok(batch) = batch {
let (scale_x, scale_y) = batch.scale();
if let Some(tex) = batch.get_texture() {
let (width, height) = tex.dimensions();
let (width, height) = (width as f32 / scale_x, height as f32 / scale_y);
if let Ok(tex_id) = graphics::imgui_texture_id(ctx, tex) {
Image::new(tex_id, [width, height]).build(ui);
}
ui.set_cursor_screen_pos(pos);
ui.invisible_button("##tiles", [width, height]);
}
}
let draw_list = ui.get_window_draw_list();
let cur_pos1 = [
pos[0].floor() + tile_size * (self.current_tile % 16) as f32,
pos[1].floor() + tile_size * (self.current_tile / 16) as f32,
];
let cur_pos2 = [cur_pos1[0] + tile_size, cur_pos1[1] + tile_size];
draw_list.add_rect(cur_pos1, cur_pos2, [1.0, 0.0, 0.0, 1.0]).thickness(2.0).build();
if ui.is_mouse_down(MouseButton::Left) {
let mouse_pos = ui.io().mouse_pos;
let x = (mouse_pos[0] - pos[0]) / tile_size;
let y = (mouse_pos[1] - pos[1]) / tile_size;
if x >= 0.0 && x < 16.0 && y >= 0.0 && y < 16.0 {
self.current_tile = (y as u8 * 16 + x as u8) as u8;
}
}
});
}
pub fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, tool: CurrentTool) -> GameResult {
let old_scale = state.scale;
set_scale(state, self.zoom);
let paths = self.stage_textures.deref().borrow();
self.background.draw(state, ctx, &self.frame, &*paths, &self.stage)?;
self.tilemap.draw(state, ctx, &self.frame, TileLayer::Background, &*paths, &self.stage)?;
self.tilemap.draw(state, ctx, &self.frame, TileLayer::Middleground, &*paths, &self.stage)?;
self.tilemap.draw(state, ctx, &self.frame, TileLayer::Foreground, &*paths, &self.stage)?;
self.tilemap.draw(state, ctx, &self.frame, TileLayer::Snack, &*paths, &self.stage)?;
self.draw_black_bars(state, ctx)?;
match tool {
CurrentTool::Move => (),
CurrentTool::Brush | CurrentTool::Fill | CurrentTool::Rectangle => {
self.tile_cursor(state, ctx)?;
}
}
set_scale(state, old_scale);
Ok(())
}
fn draw_black_bars(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
let color = Color::from_rgba(0, 0, 0, 128);
let (x, y) = self.frame.xy_interpolated(state.frame_time);
let (x, y) = (x * state.scale, y * state.scale);
let canvas_w_scaled = state.canvas_size.0 as f32 * state.scale;
let canvas_h_scaled = state.canvas_size.1 as f32 * state.scale;
let level_width = (self.stage.map.width as f32 - 1.0) * self.stage.map.tile_size.as_float();
let level_height = (self.stage.map.height as f32 - 1.0) * self.stage.map.tile_size.as_float();
let left_side = -x;
let right_side = -x + level_width * state.scale;
let upper_side = -y;
let lower_side = -y + level_height * state.scale;
if left_side > 0.0 {
let rect = Rect::new(0, upper_side as isize, left_side as isize, lower_side as isize);
graphics::draw_rect(ctx, rect, color)?;
}
if right_side < canvas_w_scaled {
let rect = Rect::new(
right_side as isize,
upper_side as isize,
(state.canvas_size.0 * state.scale) as isize,
lower_side as isize,
);
graphics::draw_rect(ctx, rect, color)?;
}
if upper_side > 0.0 {
let rect = Rect::new(0, 0, canvas_w_scaled as isize, upper_side as isize);
graphics::draw_rect(ctx, rect, color)?;
}
if lower_side < canvas_h_scaled {
let rect = Rect::new(0, lower_side as isize, canvas_w_scaled as isize, canvas_h_scaled as isize);
graphics::draw_rect(ctx, rect, color)?;
}
Ok(())
}
}
fn set_scale(state: &mut SharedGameState, scale: f32) {
state.scale = scale;
unsafe {
I_MAG = state.scale;
state.canvas_size = (state.screen_size.0 / state.scale, state.screen_size.1 / state.scale);
}
}

View file

@ -23,6 +23,19 @@ pub struct Frame {
}
impl Frame {
pub fn new() -> Frame {
Frame {
x: 0,
y: 0,
prev_x: 0,
prev_y: 0,
update_target: UpdateTarget::Player,
target_x: 0,
target_y: 0,
wait: 16,
}
}
pub fn xy_interpolated(&self, frame_time: f64) -> (f32, f32) {
if self.prev_x == self.x && self.prev_y == self.y {
return (fix9_scale(self.x), fix9_scale(self.y));

View file

@ -32,15 +32,17 @@ mod builtin_fs;
mod caret;
mod common;
mod components;
#[cfg(feature = "editor")]
mod editor;
mod encoding;
mod engine_constants;
mod entity;
mod frame;
mod framework;
mod input;
mod inventory;
#[cfg(feature = "hooks")]
mod hooks;
mod input;
mod inventory;
mod live_debugger;
mod macros;
mod map;
@ -65,6 +67,7 @@ mod weapon;
pub struct LaunchOptions {
pub server_mode: bool,
pub editor: bool,
}
lazy_static! {
@ -100,11 +103,12 @@ impl Game {
if let Some(scene) = self.scene.as_mut() {
let state_ref = unsafe { &mut *self.state.get() };
let speed = if state_ref.textscript_vm.mode == ScriptMode::Map && state_ref.textscript_vm.flags.cutscene_skip() {
4.0 * state_ref.settings.speed
} else {
1.0 * state_ref.settings.speed
};
let speed =
if state_ref.textscript_vm.mode == ScriptMode::Map && state_ref.textscript_vm.flags.cutscene_skip() {
4.0 * state_ref.settings.speed
} else {
1.0 * state_ref.settings.speed
};
match state_ref.settings.timing_mode {
TimingMode::_50Hz | TimingMode::_60Hz => {
@ -114,8 +118,7 @@ impl Game {
if (speed - 1.0).abs() < 0.01 {
self.next_tick += state_ref.settings.timing_mode.get_delta() as u128;
} else {
self.next_tick +=
(state_ref.settings.timing_mode.get_delta() as f64 / speed) as u128;
self.next_tick += (state_ref.settings.timing_mode.get_delta() as f64 / speed) as u128;
}
self.loops += 1;
}
@ -123,8 +126,8 @@ impl Game {
if self.loops == 10 {
log::warn!("Frame skip is way too high, a long system lag occurred?");
self.last_tick = self.start_time.elapsed().as_nanos();
self.next_tick = self.last_tick
+ (state_ref.settings.timing_mode.get_delta() as f64 / speed) as u128;
self.next_tick =
self.last_tick + (state_ref.settings.timing_mode.get_delta() as f64 / speed) as u128;
self.loops = 0;
}
@ -198,7 +201,11 @@ impl Game {
}
pub fn init(options: LaunchOptions) -> GameResult {
let _ = simple_logger::init_with_level(log::Level::Info);
let _ = simple_logger::SimpleLogger::new()
.without_timestamps()
.with_colors(true)
.with_level(log::Level::Info.to_level_filter())
.init();
#[cfg(not(target_os = "android"))]
let resource_dir = if let Ok(data_dir) = env::var("CAVESTORY_DATA_DIR") {
@ -264,17 +271,17 @@ pub fn init(options: LaunchOptions) -> GameResult {
}
#[cfg(not(target_os = "android"))]
{
if let Ok(_) = crate::framework::filesystem::open(&mut context, "/.drs_localstorage") {
let mut user_dir = resource_dir.clone();
user_dir.push("_drs_profile");
{
if let Ok(_) = crate::framework::filesystem::open(&mut context, "/.drs_localstorage") {
let mut user_dir = resource_dir.clone();
user_dir.push("_drs_profile");
let _ = std::fs::create_dir_all(&user_dir);
mount_user_vfs(&mut context, Box::new(PhysicalFS::new(&user_dir, false)));
} else {
mount_user_vfs(&mut context, Box::new(PhysicalFS::new(project_dirs.data_local_dir(), false)));
}
let _ = std::fs::create_dir_all(&user_dir);
mount_user_vfs(&mut context, Box::new(PhysicalFS::new(&user_dir, false)));
} else {
mount_user_vfs(&mut context, Box::new(PhysicalFS::new(project_dirs.data_local_dir(), false)));
}
}
if options.server_mode {
log::info!("Running in server mode...");

View file

@ -5,13 +5,23 @@ use std::process::exit;
fn main() {
let args = std::env::args();
let mut options = doukutsu_rs::LaunchOptions {
server_mode: false
server_mode: false,
editor: false,
};
for arg in args {
if arg == "--server-mode" {
options.server_mode = true;
}
if arg == "--editor" {
options.editor = true;
}
}
if options.server_mode && options.editor {
eprintln!("Cannot run in server mode and editor mode at the same time.");
exit(1);
}
let result = doukutsu_rs::init(options);

View file

@ -17,6 +17,7 @@ use crate::stage::{PxPackScroll, PxPackStageData, StageData};
static SUPPORTED_PXM_VERSIONS: [u8; 1] = [0x10];
static SUPPORTED_PXE_VERSIONS: [u8; 2] = [0, 0x10];
#[derive(Clone)]
pub struct Map {
pub width: u16,
pub height: u16,

View file

@ -1,19 +1,134 @@
use imgui::MenuItem;
use std::cell::RefCell;
use std::rc::Rc;
use downcast::Downcast;
use imgui::{Condition, MenuItem, TabItem, TabItemFlags, Window};
use crate::editor::{CurrentTool, EditorInstance};
use crate::framework::keyboard;
use crate::framework::keyboard::ScanCode;
use crate::framework::ui::Components;
use crate::scene::game_scene::GameScene;
use crate::scene::title_scene::TitleScene;
use crate::stage::Stage;
use crate::{Context, GameResult, Scene, SharedGameState};
pub struct EditorScene {}
struct ErrorList {
errors: Vec<String>,
}
impl ErrorList {
fn new() -> ErrorList {
Self { errors: Vec::new() }
}
fn try_or_push_error(&mut self, func: impl FnOnce() -> GameResult<()>) {
if let Err(err) = func() {
self.errors.push(err.to_string());
}
}
}
fn catch(error_list: Rc<RefCell<ErrorList>>, func: impl FnOnce() -> GameResult<()>) {
error_list.borrow_mut().try_or_push_error(func);
}
pub struct EditorScene {
stage_list: StageListWindow,
error_list: Rc<RefCell<ErrorList>>,
instances: Vec<EditorInstance>,
subscene: Option<Box<GameScene>>,
current_tool: CurrentTool,
selected_instance: usize,
switch_tab: bool,
}
impl EditorScene {
pub fn new() -> Self {
EditorScene {}
EditorScene {
stage_list: StageListWindow::new(),
error_list: Rc::new(RefCell::new(ErrorList::new())),
instances: Vec::new(),
subscene: None,
current_tool: CurrentTool::Move,
selected_instance: 0,
switch_tab: false,
}
}
fn exit_editor(&mut self, state: &mut SharedGameState) {
state.next_scene = Some(Box::new(TitleScene::new()));
}
fn open_stage(&mut self, state: &mut SharedGameState, ctx: &mut Context, stage_id: usize) {
catch(self.error_list.clone(), || {
for (idx, instance) in self.instances.iter().enumerate() {
if instance.stage_id == stage_id {
self.selected_instance = idx;
self.switch_tab = true;
return Ok(());
}
}
if let Some(stage) = state.stages.get(stage_id) {
let stage = Stage::load(&state.base_path, stage, ctx)?;
let new_instance = EditorInstance::new(stage_id, stage);
self.instances.push(new_instance);
self.selected_instance = self.instances.len() - 1;
self.switch_tab = true;
}
Ok(())
});
}
fn test_stage(&mut self, state: &mut SharedGameState, ctx: &mut Context) {
catch(self.error_list.clone(), || {
if let Some(instance) = self.instances.get(self.selected_instance) {
state.reset();
state.textscript_vm.start_script(94);
let mut game_scene = GameScene::from_stage(state, ctx, instance.stage.clone(), instance.stage_id)?;
game_scene.init(state, ctx)?;
game_scene.player1.cond.set_alive(true);
game_scene.player1.x = instance.frame.x + (state.canvas_size.0 * 256.0) as i32;
game_scene.player1.y = instance.frame.y + (state.canvas_size.1 * 256.0) as i32;
state.control_flags.set_control_enabled(true);
state.control_flags.set_tick_world(true);
state.textscript_vm.suspend = false;
self.subscene = Some(Box::new(game_scene));
}
Ok(())
});
}
fn perform_actions(&mut self, state: &mut SharedGameState, ctx: &mut Context) {
let actions = std::mem::take(&mut self.stage_list.actions);
for action in actions.iter() {
match action {
StageListAction::OpenStage(idx) => self.open_stage(state, ctx, *idx),
}
}
}
}
trait ExtraWidgetsExt {
fn tool_button(&self, label: impl AsRef<str>, active: bool) -> bool;
}
impl ExtraWidgetsExt for imgui::Ui<'_> {
fn tool_button(&self, label: impl AsRef<str>, active: bool) -> bool {
if active {
let color = self.style_color(imgui::StyleColor::ButtonActive);
let _token1 = self.push_style_color(imgui::StyleColor::Button, color);
let _token2 = self.push_style_color(imgui::StyleColor::ButtonHovered, color);
let ret = self.button(label);
ret
} else {
self.button(label)
}
}
}
impl Scene for EditorScene {
@ -23,7 +138,64 @@ impl Scene for EditorScene {
Ok(())
}
fn draw(&self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult {
fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
let subscene_ref = &mut self.subscene;
if subscene_ref.is_some() {
subscene_ref.as_mut().unwrap().tick(state, ctx)?;
if keyboard::is_key_pressed(ctx, ScanCode::Escape) {
*subscene_ref = None;
}
// hijack scene switches
let next_scene = std::mem::take(&mut state.next_scene);
if let Some(next_scene) = next_scene {
*subscene_ref = if let Ok(game_scene) = next_scene.downcast() {
let mut game_scene: Box<GameScene> = game_scene;
game_scene.init(state, ctx)?;
Some(game_scene)
} else {
None
};
}
if subscene_ref.is_none() {
state.sound_manager.play_song(0, &state.constants, &state.settings, ctx)?;
}
return Ok(());
}
Ok(())
}
fn draw_tick(&mut self, state: &mut SharedGameState) -> GameResult {
if let Some(scene) = &mut self.subscene {
scene.draw_tick(state)?;
return Ok(());
}
Ok(())
}
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
if let Some(scene) = &self.subscene {
scene.draw(state, ctx)?;
state.font.draw_text(
"Press [ESC] to return.".chars(),
4.0,
4.0,
&state.constants,
&mut state.texture_set,
ctx,
)?;
return Ok(());
}
if let Some(instance) = self.instances.get(self.selected_instance) {
instance.draw(state, ctx, self.current_tool)?;
}
Ok(())
}
@ -31,12 +203,25 @@ impl Scene for EditorScene {
&mut self,
_game_ui: &mut Components,
state: &mut SharedGameState,
_ctx: &mut Context,
ctx: &mut Context,
ui: &mut imgui::Ui,
) -> GameResult {
self.perform_actions(state, ctx);
if let Some(_) = self.subscene {
return Ok(());
}
let mut menu_bar_size = (0.0, 0.0);
if let Some(menu_bar) = ui.begin_main_menu_bar() {
let [menu_bar_w, menu_bar_h] = ui.window_size();
menu_bar_size = (menu_bar_w, menu_bar_h);
if let Some(menu) = ui.begin_menu("File") {
MenuItem::new("Open stage").shortcut("Ctrl+O").build(ui);
if MenuItem::new("Open stage").shortcut("Ctrl+O").build(ui) {
self.stage_list.show();
}
ui.separator();
if MenuItem::new("Exit editor").build(ui) {
@ -48,6 +233,117 @@ impl Scene for EditorScene {
menu_bar.end();
}
Window::new("Toolbar")
.title_bar(false)
.resizable(false)
.position([0.0, menu_bar_size.1], Condition::Always)
.size([menu_bar_size.0, 0.0], Condition::Always)
.build(ui, || {
if ui.tool_button("Move", self.current_tool == CurrentTool::Move) {
self.current_tool = CurrentTool::Move;
}
ui.same_line();
if ui.tool_button("Brush", self.current_tool == CurrentTool::Brush) {
self.current_tool = CurrentTool::Brush;
}
ui.same_line();
if ui.tool_button("Fill", self.current_tool == CurrentTool::Fill) {
self.current_tool = CurrentTool::Fill;
}
ui.same_line();
if ui.tool_button("Rectangle", self.current_tool == CurrentTool::Rectangle) {
self.current_tool = CurrentTool::Rectangle;
}
ui.same_line();
ui.text("|");
ui.same_line();
if ui.button("Test Stage") {
self.test_stage(state, ctx);
}
if let Some(tab) = ui.tab_bar("Stages") {
for (idx, inst) in self.instances.iter().enumerate() {
let mut flags = TabItemFlags::NO_CLOSE_WITH_MIDDLE_MOUSE_BUTTON;
if self.switch_tab && self.selected_instance == idx {
self.switch_tab = false;
flags |= TabItemFlags::SET_SELECTED;
}
if let Some(item) = TabItem::new(&inst.stage.data.name).flags(flags).begin(ui) {
if !self.switch_tab {
self.selected_instance = idx;
}
item.end();
}
}
tab.end();
}
});
self.stage_list.action(state, ctx, ui);
if let Some(instance) = self.instances.get_mut(self.selected_instance) {
instance.process(state, ctx, ui, self.current_tool);
}
Ok(())
}
}
struct StageListWindow {
visible: bool,
selected_stage: i32,
actions: Vec<StageListAction>,
}
enum StageListAction {
OpenStage(usize),
}
impl StageListWindow {
fn new() -> Self {
StageListWindow { visible: false, selected_stage: 0, actions: Vec::new() }
}
fn show(&mut self) {
self.visible = true;
}
fn action(&mut self, state: &mut SharedGameState, ctx: &mut Context, ui: &mut imgui::Ui) {
if !self.visible {
return;
}
Window::new("Stage list")
.resizable(false)
.collapsible(false)
.position_pivot([0.5, 0.5])
.size([300.0, 352.0], Condition::FirstUseEver)
.build(ui, || {
let mut stages = Vec::with_capacity(state.stages.len());
for stage in state.stages.iter() {
stages.push(stage.name.as_str());
}
ui.push_item_width(-1.0);
ui.list_box("", &mut self.selected_stage, &stages, 14);
ui.disabled(self.selected_stage < 0, || {
if ui.button("Open") {
self.actions.push(StageListAction::OpenStage(self.selected_stage as usize));
}
ui.same_line();
if ui.button("Edit table entry") {}
});
ui.same_line();
if ui.button("Cancel") {
self.visible = false;
}
});
}
}

View file

@ -5,7 +5,7 @@ use std::rc::Rc;
use log::info;
use crate::caret::CaretType;
use crate::common::{interpolate_fix9_scale, Color, Direction, Rect};
use crate::common::{Color, Direction, interpolate_fix9_scale, Rect};
use crate::components::background::Background;
use crate::components::boss_life_bar::BossLifeBar;
use crate::components::credits::Credits;
@ -21,30 +21,30 @@ use crate::components::tilemap::{TileLayer, Tilemap};
use crate::components::water_renderer::WaterRenderer;
use crate::entity::GameEntity;
use crate::frame::{Frame, UpdateTarget};
use crate::framework::{filesystem, graphics};
use crate::framework::backend::SpriteBatchCommand;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::graphics::{draw_rect, BlendMode, FilterMode};
use crate::framework::graphics::{BlendMode, draw_rect, FilterMode};
use crate::framework::ui::Components;
use crate::framework::{filesystem, graphics};
use crate::input::touch_controls::TouchControlType;
use crate::inventory::{Inventory, TakeExperienceResult};
use crate::map::WaterParams;
use crate::npc::{NPC, NPCLayer};
use crate::npc::boss::BossNPC;
use crate::npc::list::NPCList;
use crate::npc::{NPCLayer, NPC};
use crate::physics::{PhysicalEntity, OFFSETS};
use crate::physics::{OFFSETS, PhysicalEntity};
use crate::player::{Player, TargetPlayer};
use crate::rng::XorShift;
use crate::scene::title_scene::TitleScene;
use crate::scene::Scene;
use crate::scene::title_scene::TitleScene;
use crate::scripting::tsc::credit_script::CreditScriptVM;
use crate::scripting::tsc::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
use crate::shared_game_state::{SharedGameState, TileSize};
use crate::stage::{BackgroundType, Stage, StageTexturePaths};
use crate::texture_set::SpriteBatch;
use crate::weapon::bullet::BulletManager;
use crate::weapon::{Weapon, WeaponType};
use crate::weapon::bullet::BulletManager;
pub struct GameScene {
pub tick: u32,
@ -96,11 +96,16 @@ impl GameScene {
info!("Loading stage {} ({})", id, &state.stages[id].map);
let stage = Stage::load(&state.base_path, &state.stages[id], ctx)?;
info!("Loaded stage: {}", stage.data.name);
GameScene::from_stage(state, ctx, stage, id)
}
pub fn from_stage(state: &mut SharedGameState, ctx: &mut Context, stage: Stage, id: usize) -> GameResult<Self> {
let mut water_params = WaterParams::new();
let mut water_renderer = WaterRenderer::new();
if let Ok(water_param_file) =
filesystem::open(ctx, [&state.base_path, "Stage/", &state.stages[id].tileset.name, ".pxw"].join(""))
filesystem::open(ctx, [&state.base_path, "Stage/", &state.stages[id].tileset.name, ".pxw"].join(""))
{
water_params.load_from(water_param_file)?;
info!("Loaded water parameters file.");
@ -109,23 +114,9 @@ impl GameScene {
}
let stage_textures = {
let background = stage.data.background.filename();
let (tileset_fg, tileset_mg, tileset_bg) = if let Some(pxpack_data) = stage.data.pxpack_data.as_ref() {
let t_fg = ["Stage/", &pxpack_data.tileset_fg].join("");
let t_mg = ["Stage/", &pxpack_data.tileset_mg].join("");
let t_bg = ["Stage/", &pxpack_data.tileset_bg].join("");
(t_fg, t_mg, t_bg)
} else {
let tex_tileset_name = ["Stage/", &stage.data.tileset.filename()].join("");
(tex_tileset_name.clone(), tex_tileset_name.clone(), tex_tileset_name)
};
let npc1 = ["Npc/", &stage.data.npc1.filename()].join("");
let npc2 = ["Npc/", &stage.data.npc2.filename()].join("");
Rc::new(RefCell::new(StageTexturePaths { background, tileset_fg, tileset_mg, tileset_bg, npc1, npc2 }))
let mut textures = StageTexturePaths::new();
textures.update(&stage);
Rc::new(RefCell::new(textures))
};
Ok(Self {
@ -149,16 +140,7 @@ impl GameScene {
tilemap: Tilemap::new(),
text_boxes: TextBoxes::new(),
fade: Fade::new(),
frame: Frame {
x: 0,
y: 0,
prev_x: 0,
prev_y: 0,
update_target: UpdateTarget::Player,
target_x: 0,
target_y: 0,
wait: 16,
},
frame: Frame::new(),
stage_id: id,
npc_list: NPCList::new(),
boss: BossNPC::new(),

View file

@ -5,9 +5,9 @@ use crate::npc::NPCTable;
use crate::scene::no_data_scene::NoDataScene;
use crate::scene::Scene;
use crate::scripting::tsc::credit_script::CreditScript;
use crate::scripting::tsc::text_script::TextScript;
use crate::shared_game_state::SharedGameState;
use crate::stage::StageData;
use crate::scripting::tsc::text_script::TextScript;
pub struct LoadingScene {
tick: usize,
@ -15,9 +15,7 @@ pub struct LoadingScene {
impl LoadingScene {
pub fn new() -> Self {
Self {
tick: 0,
}
Self { tick: 0 }
}
fn load_stuff(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
@ -71,8 +69,10 @@ impl Scene for LoadingScene {
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
match state.texture_set.get_or_load_batch(ctx, &state.constants, "Loading") {
Ok(batch) => {
batch.add(((state.canvas_size.0 - batch.width() as f32) / 2.0).floor(),
((state.canvas_size.1 - batch.height() as f32) / 2.0).floor());
batch.add(
((state.canvas_size.0 - batch.width() as f32) / 2.0).floor(),
((state.canvas_size.1 - batch.height() as f32) / 2.0).floor(),
);
batch.draw(ctx)?;
}
Err(err) => {

View file

@ -1,30 +1,48 @@
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::shared_game_state::SharedGameState;
use crate::framework::ui::Components;
use crate::shared_game_state::SharedGameState;
#[cfg(feature = "editor")]
pub mod editor_scene;
pub mod game_scene;
pub mod loading_scene;
pub mod no_data_scene;
pub mod title_scene;
pub mod editor_scene;
/// Implement this trait on any object that represents an interactive game screen.
pub trait Scene {
pub trait Scene: downcast::Any {
/// Called when the scene is shown.
fn init(&mut self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult { Ok(()) }
fn init(&mut self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult {
Ok(())
}
/// Called at game tick. Perform any game state updates there.
fn tick(&mut self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult { Ok(()) }
fn tick(&mut self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult {
Ok(())
}
/// Called before draws between two ticks to update previous positions used for interpolation.
/// DO NOT perform updates of the game state there.
fn draw_tick(&mut self, _state: &mut SharedGameState) -> GameResult { Ok(()) }
fn draw_tick(&mut self, _state: &mut SharedGameState) -> GameResult {
Ok(())
}
/// Called during frame rendering operation.
fn draw(&self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult { Ok(()) }
fn draw(&self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult {
Ok(())
}
/// Independent draw meant for debug overlay, that lets you mutate the game state.
fn imgui_draw(&mut self, _game_ui: &mut Components, _state: &mut SharedGameState, _ctx: &mut Context, _frame: &mut imgui::Ui) -> GameResult { Ok(()) }
fn imgui_draw(
&mut self,
_game_ui: &mut Components,
_state: &mut SharedGameState,
_ctx: &mut Context,
_frame: &mut imgui::Ui,
) -> GameResult {
Ok(())
}
}
downcast::impl_downcast!(dyn Scene);

View file

@ -1,6 +1,5 @@
use crate::framework::context::Context;
use crate::framework::error::{GameResult, GameError};
use crate::framework::error::{GameError, GameResult};
use crate::scene::Scene;
use crate::shared_game_state::SharedGameState;
@ -27,23 +26,23 @@ impl Scene for NoDataScene {
#[allow(unused)]
fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
#[cfg(target_os = "android")]
{
use crate::common::Rect;
{
use crate::common::Rect;
if !self.flag {
self.flag = true;
let _ = std::fs::create_dir("/sdcard/doukutsu/");
let _ = std::fs::write("/sdcard/doukutsu/extract game data here.txt", REL_URL);
let _ = std::fs::write("/sdcard/doukutsu/.nomedia", b"");
}
if !self.flag {
self.flag = true;
let _ = std::fs::create_dir("/sdcard/doukutsu/");
let _ = std::fs::write("/sdcard/doukutsu/extract game data here.txt", REL_URL);
let _ = std::fs::write("/sdcard/doukutsu/.nomedia", b"");
}
let screen = Rect::new(0, 0, state.canvas_size.0 as isize, state.canvas_size.1 as isize);
if state.touch_controls.consume_click_in(screen) {
if let Err(err) = webbrowser::open(REL_URL) {
self.err = err.to_string();
}
let screen = Rect::new(0, 0, state.canvas_size.0 as isize, state.canvas_size.1 as isize);
if state.touch_controls.consume_click_in(screen) {
if let Err(err) = webbrowser::open(REL_URL) {
self.err = err.to_string();
}
}
}
Ok(())
}
@ -51,43 +50,82 @@ impl Scene for NoDataScene {
{
let die = "doukutsu-rs internal error";
let die_width = state.font.text_width(die.chars().clone(), &state.constants);
state.font.draw_colored_text(die.chars(), (state.canvas_size.0 - die_width) / 2.0, 10.0,
(255, 100, 100, 255), &state.constants, &mut state.texture_set, ctx)?;
state.font.draw_colored_text(
die.chars(),
(state.canvas_size.0 - die_width) / 2.0,
10.0,
(255, 100, 100, 255),
&state.constants,
&mut state.texture_set,
ctx,
)?;
}
{
let ftl = "Failed to load game data.";
let ftl_width = state.font.text_width(ftl.chars().clone(), &state.constants);
state.font.draw_colored_text(ftl.chars(), (state.canvas_size.0 - ftl_width) / 2.0, 30.0,
(255, 100, 100, 255), &state.constants, &mut state.texture_set, ctx)?;
state.font.draw_colored_text(
ftl.chars(),
(state.canvas_size.0 - ftl_width) / 2.0,
30.0,
(255, 100, 100, 255),
&state.constants,
&mut state.texture_set,
ctx,
)?;
}
#[cfg(target_os = "android")]
{
let ftl = "It's likely that you haven't extracted the game data properly.";
let ftl2 = "Click here to open the guide.";
let ftl_width = state.font.text_width(ftl.chars().clone(), &state.constants);
let ftl2_width = state.font.text_width(ftl2.chars().clone(), &state.constants);
let ftl3_width = state.font.text_width(REL_URL.chars().clone(), &state.constants);
{
let ftl = "It's likely that you haven't extracted the game data properly.";
let ftl2 = "Click here to open the guide.";
let ftl_width = state.font.text_width(ftl.chars().clone(), &state.constants);
let ftl2_width = state.font.text_width(ftl2.chars().clone(), &state.constants);
let ftl3_width = state.font.text_width(REL_URL.chars().clone(), &state.constants);
state.font.draw_colored_text(ftl.chars(), (state.canvas_size.0 - ftl_width) / 2.0, 60.0,
(255, 255, 0, 255), &state.constants, &mut state.texture_set, ctx)?;
state.font.draw_colored_text(
ftl.chars(),
(state.canvas_size.0 - ftl_width) / 2.0,
60.0,
(255, 255, 0, 255),
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.draw_colored_text(
ftl2.chars(),
(state.canvas_size.0 - ftl2_width) / 2.0,
80.0,
(255, 255, 0, 255),
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.draw_colored_text(ftl2.chars(), (state.canvas_size.0 - ftl2_width) / 2.0, 80.0,
(255, 255, 0, 255), &state.constants, &mut state.texture_set, ctx)?;
state.font.draw_colored_text(REL_URL.chars(), (state.canvas_size.0 - ftl3_width) / 2.0, 100.0,
(255, 255, 0, 255), &state.constants, &mut state.texture_set, ctx)?;
}
state.font.draw_colored_text(
REL_URL.chars(),
(state.canvas_size.0 - ftl3_width) / 2.0,
100.0,
(255, 255, 0, 255),
&state.constants,
&mut state.texture_set,
ctx,
)?;
}
{
let err_width = state.font.text_width(self.err.chars().clone(), &state.constants);
state.font.draw_text(self.err.chars(), (state.canvas_size.0 - err_width) / 2.0, 140.0,
&state.constants, &mut state.texture_set, ctx)?;
state.font.draw_text(
self.err.chars(),
(state.canvas_size.0 - err_width) / 2.0,
140.0,
&state.constants,
&mut state.texture_set,
ctx,
)?;
}
Ok(())
}
}

View file

@ -4,8 +4,8 @@ use crate::framework::error::GameResult;
use crate::framework::graphics;
use crate::input::combined_menu_controller::CombinedMenuController;
use crate::input::touch_controls::TouchControlType;
use crate::menu::settings_menu::SettingsMenu;
use crate::menu::{Menu, MenuEntry, MenuSelectionResult};
use crate::menu::settings_menu::SettingsMenu;
use crate::scene::Scene;
use crate::shared_game_state::SharedGameState;
@ -144,10 +144,10 @@ impl Scene for TitleScene {
}
MenuSelectionResult::Selected(3, _) => {
#[cfg(feature = "editor")]
{
use crate::scene::editor_scene::EditorScene;
state.next_scene = Some(Box::new(EditorScene::new()));
}
{
use crate::scene::editor_scene::EditorScene;
state.next_scene = Some(Box::new(EditorScene::new()));
}
}
MenuSelectionResult::Selected(4, _) => {
state.shutdown();

View file

@ -1,8 +1,8 @@
use std::io::{Cursor, Read};
use std::str::from_utf8;
use byteorder::LE;
use byteorder::ReadBytesExt;
use byteorder::LE;
use log::info;
use crate::common::Color;
@ -511,6 +511,7 @@ impl StageData {
}
}
#[derive(Clone)]
pub struct Stage {
pub map: Map,
pub data: StageData,
@ -609,4 +610,25 @@ impl StageTexturePaths {
npc2: "Npc/Npc0".to_owned(),
}
}
pub fn update(&mut self, stage: &Stage) {
self.background = stage.data.background.filename();
let (tileset_fg, tileset_mg, tileset_bg) = if let Some(pxpack_data) = stage.data.pxpack_data.as_ref() {
let t_fg = ["Stage/", &pxpack_data.tileset_fg].join("");
let t_mg = ["Stage/", &pxpack_data.tileset_mg].join("");
let t_bg = ["Stage/", &pxpack_data.tileset_bg].join("");
(t_fg, t_mg, t_bg)
} else {
let tex_tileset_name = ["Stage/", &stage.data.tileset.filename()].join("");
(tex_tileset_name.clone(), tex_tileset_name.clone(), tex_tileset_name)
};
self.tileset_fg = tileset_fg;
self.tileset_mg = tileset_mg;
self.tileset_bg = tileset_bg;
self.npc1 = ["Npc/", &stage.data.npc1.filename()].join("");
self.npc2 = ["Npc/", &stage.data.npc2.filename()].join("");
}
}

View file

@ -69,6 +69,8 @@ pub trait SpriteBatch {
fn draw(&mut self, ctx: &mut Context) -> GameResult;
fn draw_filtered(&mut self, _filter: FilterMode, _ctx: &mut Context) -> GameResult;
fn get_texture(&self) -> Option<&Box<dyn BackendTexture>>;
}
pub struct DummyBatch;
@ -136,6 +138,10 @@ impl SpriteBatch for DummyBatch {
fn draw_filtered(&mut self, _filter: FilterMode, _ctx: &mut Context) -> GameResult {
Ok(())
}
fn get_texture(&self) -> Option<&Box<dyn BackendTexture>> {
None
}
}
pub struct SubBatch {
@ -309,6 +315,10 @@ impl SpriteBatch for SubBatch {
self.batch.clear();
Ok(())
}
fn get_texture(&self) -> Option<&Box<dyn BackendTexture>> {
Some(&self.batch)
}
}
impl SpriteBatch for CombinedBatch {
@ -395,6 +405,10 @@ impl SpriteBatch for CombinedBatch {
fn draw_filtered(&mut self, filter: FilterMode, ctx: &mut Context) -> GameResult {
self.main_batch.draw_filtered(filter, ctx)
}
fn get_texture(&self) -> Option<&Box<dyn BackendTexture>> {
self.main_batch.get_texture()
}
}
pub struct TextureSet {