cs+ inspired water thing
This commit is contained in:
parent
d68a248292
commit
3fe8e132e5
|
@ -293,8 +293,12 @@ impl<T: Num + PartialOrd + Copy> Rect<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn has_point(&self, x: T, y: T) -> bool {
|
||||
self.left.ge(&x) && self.right.le(&x) && self.top.ge(&y) && self.bottom.le(&y)
|
||||
}
|
||||
|
||||
pub fn width(&self) -> T {
|
||||
if let Some(Ordering::Greater) = self.left.partial_cmp(&self.right) {
|
||||
if self.left.gt(&self.right) {
|
||||
self.left.sub(self.right)
|
||||
} else {
|
||||
self.right.sub(self.left)
|
||||
|
@ -302,7 +306,7 @@ impl<T: Num + PartialOrd + Copy> Rect<T> {
|
|||
}
|
||||
|
||||
pub fn height(&self) -> T {
|
||||
if let Some(Ordering::Greater) = self.top.partial_cmp(&self.bottom) {
|
||||
if self.top.gt(&self.bottom) {
|
||||
self.top.sub(self.bottom)
|
||||
} else {
|
||||
self.bottom.sub(self.top)
|
||||
|
|
|
@ -5,3 +5,4 @@ pub mod hud;
|
|||
pub mod inventory;
|
||||
pub mod number_popup;
|
||||
pub mod stage_select;
|
||||
pub mod water_renderer;
|
||||
|
|
|
@ -0,0 +1,225 @@
|
|||
use crate::common::{Color, Rect};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::backend::{BackendShader, VertexData};
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics;
|
||||
use crate::map::WaterRegionType;
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::framework::graphics::BlendMode;
|
||||
|
||||
const TENSION: f32 = 0.03;
|
||||
const DAMPENING: f32 = 0.01;
|
||||
const SPREAD: f32 = 0.02;
|
||||
|
||||
struct DynamicWaterColumn {
|
||||
target_height: f32,
|
||||
height: f32,
|
||||
speed: f32,
|
||||
}
|
||||
|
||||
impl DynamicWaterColumn {
|
||||
pub fn new() -> DynamicWaterColumn {
|
||||
DynamicWaterColumn { target_height: 8.0, height: 8.0, speed: 0.0 }
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, dampening: f32, tension: f32) {
|
||||
self.speed += tension * (self.target_height - self.height) - self.speed * dampening;
|
||||
self.height += self.speed;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DynamicWater {
|
||||
x: u16,
|
||||
y: u16,
|
||||
end_x: u16,
|
||||
columns: Vec<DynamicWaterColumn>,
|
||||
}
|
||||
|
||||
impl DynamicWater {
|
||||
pub fn new(x: u16, y: u16, length: u16) -> DynamicWater {
|
||||
let mut columns = Vec::new();
|
||||
let count = length as usize * 8 + 1;
|
||||
|
||||
for _ in 0..count {
|
||||
columns.push(DynamicWaterColumn::new());
|
||||
}
|
||||
|
||||
DynamicWater { x, y, end_x: x + length, columns }
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
for col in self.columns.iter_mut() {
|
||||
col.tick(DAMPENING, TENSION);
|
||||
}
|
||||
|
||||
static mut l_deltas: Vec<f32> = Vec::new();
|
||||
static mut r_deltas: Vec<f32> = Vec::new();
|
||||
|
||||
// we assume tick() is never called from other threads.
|
||||
unsafe {
|
||||
l_deltas.resize(self.columns.len(), 0.0);
|
||||
r_deltas.resize(self.columns.len(), 0.0);
|
||||
|
||||
for _ in 0..2 {
|
||||
for i in 0..self.columns.len() {
|
||||
if i > 0 {
|
||||
l_deltas[i] = SPREAD * (self.columns[i].height - self.columns[i - 1].height);
|
||||
self.columns[i - 1].speed += l_deltas[i];
|
||||
}
|
||||
|
||||
if i < self.columns.len() - 1 {
|
||||
r_deltas[i] = SPREAD * (self.columns[i].height - self.columns[i + 1].height);
|
||||
self.columns[i + 1].speed += r_deltas[i];
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..self.columns.len() {
|
||||
if i > 0 {
|
||||
self.columns[i - 1].height += l_deltas[i];
|
||||
}
|
||||
|
||||
if i < self.columns.len() - 1 {
|
||||
self.columns[i + 1].height += r_deltas[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WaterRenderer {
|
||||
depth_regions: Vec<Rect<u16>>,
|
||||
surf_regions: Vec<Rect<u16>>,
|
||||
water_surfaces: Vec<DynamicWater>,
|
||||
}
|
||||
|
||||
impl WaterRenderer {
|
||||
pub fn new() -> WaterRenderer {
|
||||
WaterRenderer { depth_regions: Vec::new(), surf_regions: Vec::new(), water_surfaces: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn initialize(&mut self, regions: Vec<(WaterRegionType, Rect<u16>)>) {
|
||||
for (reg_type, bounds) in regions {
|
||||
match reg_type {
|
||||
WaterRegionType::WaterLine => {
|
||||
self.surf_regions.push(bounds);
|
||||
self.water_surfaces.push(DynamicWater::new(bounds.left, bounds.top, bounds.width() + 1));
|
||||
}
|
||||
WaterRegionType::WaterDepth => {
|
||||
self.depth_regions.push(bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<(&Player)> for WaterRenderer {
|
||||
fn tick(&mut self, state: &mut SharedGameState, (player): (&Player)) -> GameResult<()> {
|
||||
let player_x = player.x as f32 / 512.0 + 8.0;
|
||||
let player_y = player.y as f32 / 512.0 + 8.0;
|
||||
|
||||
for surf in self.water_surfaces.iter_mut() {
|
||||
let line_x = surf.x as f32 * 16.0;
|
||||
let line_y = surf.y as f32 * 16.0;
|
||||
if (player.vel_y > 0x80 || player.vel_y < -0x80)
|
||||
&& player_x > line_x
|
||||
&& player_x < surf.end_x as f32 * 16.0
|
||||
&& player_y > line_y - 5.0
|
||||
&& player_y < line_y + 4.0
|
||||
{
|
||||
let col_idx_center = (((player_x - line_x) / 2.0) as i32).clamp(0, surf.columns.len() as i32);
|
||||
let col_idx_left = (col_idx_center - (player.hit_bounds.left as i32 / (8 * 0x200)))
|
||||
.clamp(0, surf.columns.len() as i32) as usize;
|
||||
let col_idx_right = (col_idx_center + (player.hit_bounds.left as i32 / (8 * 0x200)))
|
||||
.clamp(0, surf.columns.len() as i32) as usize;
|
||||
|
||||
for col in surf.columns[col_idx_left..=col_idx_right].iter_mut() {
|
||||
col.speed = player.vel_y as f32 / 512.0;
|
||||
}
|
||||
}
|
||||
|
||||
surf.tick();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, frame: &Frame) -> GameResult<()> {
|
||||
let mut out_rect = Rect::new(0, 0, 0, 0);
|
||||
let (o_x, o_y) = frame.xy_interpolated(state.frame_time);
|
||||
let water_color_top = Color::from_rgba(102, 153, 204, 150);
|
||||
let water_color = Color::from_rgba(102, 153, 204, 75);
|
||||
|
||||
for region in self.depth_regions.iter() {
|
||||
out_rect.left = ((region.left as f32 * 16.0 - o_x - 8.0) * state.scale) as isize;
|
||||
out_rect.top = ((region.top as f32 * 16.0 - o_y - 8.0) * state.scale) as isize;
|
||||
out_rect.right = ((region.right as f32 * 16.0 - o_x + 8.0) * state.scale) as isize;
|
||||
out_rect.bottom = ((region.bottom as f32 * 16.0 - o_y + 8.0) * state.scale) as isize;
|
||||
graphics::draw_rect(ctx, out_rect, water_color)?;
|
||||
}
|
||||
|
||||
if !graphics::supports_vertex_draw(ctx)? {
|
||||
for region in self.surf_regions.iter() {
|
||||
out_rect.left = ((region.left as f32 * 16.0 - o_x - 8.0) * state.scale) as isize;
|
||||
out_rect.top = ((region.top as f32 * 16.0 - o_y - 5.0) * state.scale) as isize;
|
||||
out_rect.right = ((region.right as f32 * 16.0 - o_x + 8.0) * state.scale) as isize;
|
||||
out_rect.bottom = ((region.bottom as f32 * 16.0 - o_y + 8.0) * state.scale) as isize;
|
||||
graphics::draw_rect(ctx, out_rect, water_color)?;
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let uv = (0.0, 0.0);
|
||||
let color_top_rgba = water_color_top.to_rgba();
|
||||
let color_mid_rgba = water_color.to_rgba();
|
||||
let color_btm_rgba = water_color.to_rgba();
|
||||
|
||||
for surf in self.water_surfaces.iter() {
|
||||
let pos_x = surf.x as f32 * 16.0;
|
||||
let pos_y = surf.y as f32 * 16.0;
|
||||
|
||||
if (pos_x - o_x - 16.0) > state.canvas_size.0
|
||||
|| (pos_x - o_x + 16.0 + surf.end_x as f32 * 16.0) < 0.0
|
||||
|| (pos_y - o_y - 16.0) > state.canvas_size.1
|
||||
|| (pos_y - o_y + 16.0) < 0.0
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut vertices = vec![];
|
||||
vertices.reserve(12 * surf.columns.len());
|
||||
|
||||
let bottom = (pos_y - o_y + 8.0) * state.scale;
|
||||
for i in 1..surf.columns.len() {
|
||||
let x_right = (pos_x - 8.0 - o_x + i as f32 * 2.0) * state.scale;
|
||||
let x_left = x_right - 2.0 * state.scale;
|
||||
let top_left = (pos_y - o_y - 13.0 + surf.columns[i - 1].height) * state.scale;
|
||||
let top_right = (pos_y - o_y - 13.0 + surf.columns[i].height) * state.scale;
|
||||
let middle_left = top_left + 6.0 * state.scale;
|
||||
let middle_right = top_left + 6.0 * state.scale;
|
||||
|
||||
vertices.push(VertexData { position: (x_left, middle_left), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (x_left, top_left), uv, color: color_top_rgba });
|
||||
vertices.push(VertexData { position: (x_right, top_right), uv, color: color_top_rgba });
|
||||
vertices.push(VertexData { position: (x_left, middle_left), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (x_right, top_right), uv, color: color_top_rgba });
|
||||
vertices.push(VertexData { position: (x_right, middle_right), uv, color: color_mid_rgba });
|
||||
|
||||
vertices.push(VertexData { position: (x_left, bottom), uv, color: color_btm_rgba });
|
||||
vertices.push(VertexData { position: (x_left, middle_left), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (x_right, middle_right), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (x_left, bottom), uv, color: color_btm_rgba });
|
||||
vertices.push(VertexData { position: (x_right, middle_right), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (x_right, bottom), uv, color: color_btm_rgba });
|
||||
}
|
||||
|
||||
graphics::draw_triangle_list(ctx, vertices, None, BackendShader::Fill);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ use case_insensitive_hashmap::CaseInsensitiveHashMap;
|
|||
use log::info;
|
||||
|
||||
use crate::case_insensitive_hashmap;
|
||||
use crate::common::{BulletFlag, Rect};
|
||||
use crate::common::{BulletFlag, Color, Rect};
|
||||
use crate::engine_constants::npcs::NPCConsts;
|
||||
use crate::player::ControlMode;
|
||||
use crate::str;
|
||||
|
@ -192,6 +192,9 @@ pub struct TextScriptConsts {
|
|||
pub cursor_inventory_weapon: [Rect<u16>; 2],
|
||||
pub cursor_inventory_item: [Rect<u16>; 2],
|
||||
pub inventory_item_count_x: u8,
|
||||
pub text_shadow: bool,
|
||||
pub text_speed_normal: u8,
|
||||
pub text_speed_fast: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -241,6 +244,7 @@ pub struct EngineConstants {
|
|||
pub tex_sizes: CaseInsensitiveHashMap<(u16, u16)>,
|
||||
pub textscript: TextScriptConsts,
|
||||
pub title: TitleConsts,
|
||||
pub inventory_dim_color: Color,
|
||||
pub font_path: String,
|
||||
pub font_scale: f32,
|
||||
pub font_space_offset: f32,
|
||||
|
@ -264,6 +268,7 @@ impl Clone for EngineConstants {
|
|||
tex_sizes: self.tex_sizes.clone(),
|
||||
textscript: self.textscript,
|
||||
title: self.title.clone(),
|
||||
inventory_dim_color: self.inventory_dim_color,
|
||||
font_path: self.font_path.clone(),
|
||||
font_scale: self.font_scale,
|
||||
font_space_offset: self.font_space_offset,
|
||||
|
@ -1388,6 +1393,9 @@ impl EngineConstants {
|
|||
Rect { left: 80, top: 104, right: 112, bottom: 120 },
|
||||
],
|
||||
inventory_item_count_x: 6,
|
||||
text_shadow: false,
|
||||
text_speed_normal: 4,
|
||||
text_speed_fast: 1
|
||||
},
|
||||
title: TitleConsts {
|
||||
intro_text: "Studio Pixel presents".to_string(),
|
||||
|
@ -1402,6 +1410,7 @@ impl EngineConstants {
|
|||
menu_left: Rect { left: 0, top: 8, right: 8, bottom: 16 },
|
||||
menu_right: Rect { left: 236, top: 8, right: 244, bottom: 16 },
|
||||
},
|
||||
inventory_dim_color: Color::from_rgba(0, 0, 0, 0),
|
||||
font_path: "builtin/builtin_font.fnt".to_string(),
|
||||
font_scale: 1.0,
|
||||
font_space_offset: 0.0,
|
||||
|
@ -1483,9 +1492,13 @@ impl EngineConstants {
|
|||
self.tex_sizes.insert(str!("bkMoon"), (427, 240));
|
||||
self.tex_sizes.insert(str!("bkFog"), (427, 240));
|
||||
self.title.logo_rect = Rect { left: 0, top: 0, right: 214, bottom: 62 };
|
||||
self.inventory_dim_color = Color::from_rgba(0, 0, 32, 150);
|
||||
self.textscript.encoding = TextScriptEncoding::UTF8;
|
||||
self.textscript.encrypted = false;
|
||||
self.textscript.animated_face_pics = true;
|
||||
self.textscript.text_shadow = true;
|
||||
self.textscript.text_speed_normal = 1;
|
||||
self.textscript.text_speed_fast = 0;
|
||||
self.soundtracks.insert("Famitracks".to_string(), "/base/ogg17/".to_string());
|
||||
self.soundtracks.insert("Ridiculon".to_string(), "/base/ogg_ridic/".to_string());
|
||||
}
|
||||
|
|
|
@ -6,6 +6,20 @@ use crate::framework::error::GameResult;
|
|||
use crate::framework::graphics::BlendMode;
|
||||
use crate::Game;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct VertexData {
|
||||
pub position: (f32, f32),
|
||||
pub uv: (f32, f32),
|
||||
pub color: (u8, u8, u8, u8),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum BackendShader {
|
||||
Fill,
|
||||
Texture
|
||||
}
|
||||
|
||||
pub trait Backend {
|
||||
fn create_event_loop(&self) -> GameResult<Box<dyn BackendEventLoop>>;
|
||||
}
|
||||
|
@ -42,6 +56,12 @@ pub trait BackendRenderer {
|
|||
fn imgui(&self) -> GameResult<&mut imgui::Context>;
|
||||
|
||||
fn render_imgui(&mut self, draw_data: &DrawData) -> GameResult;
|
||||
|
||||
fn supports_vertex_draw(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn draw_triangle_list(&mut self, vertices: Vec<VertexData>, texture: Option<&Box<dyn BackendTexture>>, shader: BackendShader) -> GameResult;
|
||||
}
|
||||
|
||||
pub trait BackendTexture {
|
||||
|
|
|
@ -345,7 +345,7 @@ impl BackendEventLoop for GlutinEventLoop {
|
|||
*user_data = Rc::into_raw(refs) as *mut c_void;
|
||||
}
|
||||
|
||||
let gl_context = GLContext { get_proc_address, swap_buffers, user_data };
|
||||
let gl_context = GLContext { gles2_mode: true, get_proc_address, swap_buffers, user_data };
|
||||
|
||||
Ok(Box::new(OpenGLRenderer::new(gl_context, UnsafeCell::new(imgui))))
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::framework::backend::{Backend, BackendEventLoop, BackendRenderer, BackendTexture, SpriteBatchCommand};
|
||||
use crate::framework::backend::{Backend, BackendEventLoop, BackendRenderer, BackendTexture, SpriteBatchCommand, VertexData, BackendShader};
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::context::Context;
|
||||
use crate::Game;
|
||||
|
@ -125,4 +125,8 @@ impl BackendRenderer for NullRenderer {
|
|||
fn render_imgui(&mut self, _draw_data: &DrawData) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_triangle_list(&mut self, vertices: Vec<VertexData>, texture: Option<&Box<dyn BackendTexture>>, shader: BackendShader) -> GameResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::cell::{RefCell, UnsafeCell};
|
|||
use std::collections::HashMap;
|
||||
use std::ffi::c_void;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use imgui::internal::RawWrapper;
|
||||
use imgui::{ConfigFlags, DrawCmd, DrawData, ImString, Key, MouseCursor, TextureId};
|
||||
|
@ -15,7 +16,9 @@ use sdl2::video::WindowContext;
|
|||
use sdl2::{keyboard, pixels, EventPump, Sdl, VideoSubsystem};
|
||||
|
||||
use crate::common::{Color, Rect};
|
||||
use crate::framework::backend::{Backend, BackendEventLoop, BackendRenderer, BackendTexture, SpriteBatchCommand};
|
||||
use crate::framework::backend::{
|
||||
Backend, BackendEventLoop, BackendRenderer, BackendShader, BackendTexture, SpriteBatchCommand, VertexData,
|
||||
};
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::{GameError, GameResult};
|
||||
use crate::framework::graphics::BlendMode;
|
||||
|
@ -24,7 +27,6 @@ use crate::framework::render_opengl::{GLContext, OpenGLRenderer};
|
|||
use crate::framework::ui::init_imgui;
|
||||
use crate::Game;
|
||||
use crate::GAME_SUSPENDED;
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct SDL2Backend {
|
||||
context: Sdl,
|
||||
|
@ -234,8 +236,7 @@ impl BackendEventLoop for SDL2EventLoop {
|
|||
refs.video.gl_get_proc_address(name) as *const _
|
||||
};
|
||||
|
||||
log::info!("gl proc {} -> {:?}", name, result);
|
||||
|
||||
// log::info!("gl proc {} -> {:?}", name, result);
|
||||
*user_data = Rc::into_raw(refs) as *mut c_void;
|
||||
|
||||
result
|
||||
|
@ -253,7 +254,7 @@ impl BackendEventLoop for SDL2EventLoop {
|
|||
*user_data = Rc::into_raw(refs) as *mut c_void;
|
||||
}
|
||||
|
||||
let gl_context = GLContext { get_proc_address, swap_buffers, user_data };
|
||||
let gl_context = GLContext { gles2_mode: false, get_proc_address, swap_buffers, user_data };
|
||||
|
||||
return Ok(Box::new(OpenGLRenderer::new(gl_context, UnsafeCell::new(imgui))));
|
||||
}
|
||||
|
@ -369,7 +370,7 @@ fn max3(x: f32, y: f32, z: f32) -> f32 {
|
|||
|
||||
impl BackendRenderer for SDL2Renderer {
|
||||
fn renderer_name(&self) -> String {
|
||||
"SDL2_Renderer".to_owned()
|
||||
"SDL2_Renderer (fallback)".to_owned()
|
||||
}
|
||||
|
||||
fn clear(&mut self, color: Color) {
|
||||
|
@ -465,7 +466,9 @@ impl BackendRenderer for SDL2Renderer {
|
|||
|
||||
let (r, g, b, a) = color.to_rgba();
|
||||
|
||||
let blend = refs.blend_mode;
|
||||
refs.canvas.set_draw_color(pixels::Color::RGBA(r, g, b, a));
|
||||
refs.canvas.set_blend_mode(blend);
|
||||
refs.canvas
|
||||
.fill_rect(sdl2::rect::Rect::new(
|
||||
rect.left as i32,
|
||||
|
@ -483,7 +486,9 @@ impl BackendRenderer for SDL2Renderer {
|
|||
|
||||
let (r, g, b, a) = color.to_rgba();
|
||||
|
||||
let blend = refs.blend_mode;
|
||||
refs.canvas.set_draw_color(pixels::Color::RGBA(r, g, b, a));
|
||||
refs.canvas.set_blend_mode(blend);
|
||||
|
||||
match line_width {
|
||||
0 => {} // no-op
|
||||
|
@ -650,6 +655,15 @@ impl BackendRenderer for SDL2Renderer {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_triangle_list(
|
||||
&mut self,
|
||||
vertices: Vec<VertexData>,
|
||||
texture: Option<&Box<dyn BackendTexture>>,
|
||||
shader: BackendShader,
|
||||
) -> GameResult<()> {
|
||||
Err(GameError::RenderError("Unsupported operation".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
struct SDL2Texture {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::common::{Color, Rect};
|
||||
use crate::framework::backend::{BackendShader, BackendTexture, VertexData};
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::{GameResult, GameError};
|
||||
use crate::framework::backend::BackendTexture;
|
||||
use crate::framework::error::{GameError, GameResult};
|
||||
|
||||
pub enum FilterMode {
|
||||
Nearest,
|
||||
|
@ -123,3 +123,24 @@ pub fn prepare_draw(ctx: &mut Context) -> GameResult {
|
|||
|
||||
Err(GameError::RenderError("Rendering backend hasn't been initialized yet.".to_string()))
|
||||
}
|
||||
|
||||
pub fn supports_vertex_draw(ctx: &Context) -> GameResult<bool> {
|
||||
if let Some(renderer) = ctx.renderer.as_ref() {
|
||||
return Ok(renderer.supports_vertex_draw())
|
||||
}
|
||||
|
||||
Err(GameError::RenderError("Rendering backend hasn't been initialized yet.".to_string()))
|
||||
}
|
||||
|
||||
pub fn draw_triangle_list(
|
||||
ctx: &mut Context,
|
||||
vertices: Vec<VertexData>,
|
||||
texture: Option<&Box<dyn BackendTexture>>,
|
||||
shader: BackendShader,
|
||||
) -> GameResult {
|
||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
||||
return renderer.draw_triangle_list(vertices, texture, shader);
|
||||
}
|
||||
|
||||
Err(GameError::RenderError("Rendering backend hasn't been initialized yet.".to_string()))
|
||||
}
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
use std::cell::{RefCell, UnsafeCell};
|
||||
use std::ffi::{c_void, CStr};
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::sync::Arc;
|
||||
|
||||
use imgui::{DrawCmd, DrawCmdParams, DrawData, DrawIdx, DrawVert};
|
||||
|
||||
use crate::common::{Color, Rect};
|
||||
use crate::framework::backend::{BackendRenderer, BackendTexture, SpriteBatchCommand};
|
||||
use crate::framework::backend::{BackendRenderer, BackendShader, BackendTexture, SpriteBatchCommand, VertexData};
|
||||
use crate::framework::error::GameError::RenderError;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::gl;
|
||||
use crate::framework::gl::types::*;
|
||||
use crate::framework::graphics::BlendMode;
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
pub struct GLContext {
|
||||
pub gles2_mode: bool,
|
||||
pub get_proc_address: unsafe fn(user_data: &mut *mut c_void, name: &str) -> *const c_void,
|
||||
pub swap_buffers: unsafe fn(user_data: &mut *mut c_void),
|
||||
pub user_data: *mut c_void,
|
||||
|
@ -33,14 +33,6 @@ pub struct OpenGLTexture {
|
|||
context_active: Arc<RefCell<bool>>,
|
||||
}
|
||||
|
||||
// #[repr(C)] // since we determine field offset dynamically, doesn't really matter
|
||||
#[derive(Copy, Clone)]
|
||||
struct VertexData {
|
||||
position: (f32, f32),
|
||||
uv: (f32, f32),
|
||||
color: (u8, u8, u8, u8),
|
||||
}
|
||||
|
||||
impl BackendTexture for OpenGLTexture {
|
||||
fn dimensions(&self) -> (u16, u16) {
|
||||
(self.width, self.height)
|
||||
|
@ -510,6 +502,7 @@ pub struct OpenGLRenderer {
|
|||
imgui_data: ImguiData,
|
||||
context_active: Arc<RefCell<bool>>,
|
||||
def_matrix: [[f32; 4]; 4],
|
||||
curr_matrix: [[f32; 4]; 4],
|
||||
}
|
||||
|
||||
impl OpenGLRenderer {
|
||||
|
@ -520,11 +513,12 @@ impl OpenGLRenderer {
|
|||
imgui_data: ImguiData::new(),
|
||||
context_active: Arc::new(RefCell::new(true)),
|
||||
def_matrix: [[0.0; 4]; 4],
|
||||
curr_matrix: [[0.0; 4]; 4],
|
||||
}
|
||||
}
|
||||
|
||||
fn get_context(&mut self) -> Option<(&mut GLContext, &'static Gl)> {
|
||||
let imgui = unsafe { &mut *self.imgui.get() };
|
||||
let imgui = unsafe { &mut *self.imgui.get() };
|
||||
|
||||
let gl = load_gl(&mut self.refs);
|
||||
|
||||
|
@ -562,7 +556,11 @@ where
|
|||
|
||||
impl BackendRenderer for OpenGLRenderer {
|
||||
fn renderer_name(&self) -> String {
|
||||
"OpenGL(ES)".to_owned()
|
||||
if self.refs.gles2_mode {
|
||||
"OpenGL ES 2.0".to_string()
|
||||
} else {
|
||||
"OpenGL 3".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn clear(&mut self, color: Color) {
|
||||
|
@ -608,6 +606,7 @@ impl BackendRenderer for OpenGLRenderer {
|
|||
[0.0, 0.0, -1.0, 0.0],
|
||||
[-1.0, 1.0, 0.0, 1.0],
|
||||
];
|
||||
self.curr_matrix = self.def_matrix;
|
||||
|
||||
gl.gl.BindBuffer(gl::ARRAY_BUFFER, 0);
|
||||
gl.gl.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
|
||||
|
@ -747,25 +746,38 @@ impl BackendRenderer for OpenGLRenderer {
|
|||
if let Some(texture) = texture {
|
||||
let gl_texture: &Box<OpenGLTexture> = std::mem::transmute(texture);
|
||||
|
||||
let matrix = [
|
||||
self.curr_matrix = [
|
||||
[2.0 / (gl_texture.width as f32), 0.0, 0.0, 0.0],
|
||||
[0.0, 2.0 / (gl_texture.height as f32), 0.0, 0.0],
|
||||
[0.0, 0.0, -1.0, 0.0],
|
||||
[-1.0, -1.0, 0.0, 1.0],
|
||||
];
|
||||
|
||||
gl.gl.UseProgram(self.imgui_data.program_fill);
|
||||
gl.gl.UniformMatrix4fv(self.imgui_data.fill_locs.proj_mtx, 1, gl::FALSE, matrix.as_ptr() as _);
|
||||
gl.gl.UniformMatrix4fv(self.imgui_data.fill_locs.proj_mtx, 1, gl::FALSE, self.curr_matrix.as_ptr() as _);
|
||||
gl.gl.UseProgram(self.imgui_data.program_tex);
|
||||
gl.gl.Uniform1i(self.imgui_data.tex_locs.texture, 0);
|
||||
gl.gl.UniformMatrix4fv(self.imgui_data.tex_locs.proj_mtx, 1, gl::FALSE, matrix.as_ptr() as _);
|
||||
gl.gl.UniformMatrix4fv(self.imgui_data.tex_locs.proj_mtx, 1, gl::FALSE, self.curr_matrix.as_ptr() as _);
|
||||
|
||||
gl.gl.BindFramebuffer(gl::FRAMEBUFFER, gl_texture.framebuffer_id);
|
||||
} else {
|
||||
self.curr_matrix = self.def_matrix;
|
||||
|
||||
gl.gl.UseProgram(self.imgui_data.program_fill);
|
||||
gl.gl.UniformMatrix4fv(self.imgui_data.fill_locs.proj_mtx, 1, gl::FALSE, self.def_matrix.as_ptr() as _);
|
||||
gl.gl.UniformMatrix4fv(
|
||||
self.imgui_data.fill_locs.proj_mtx,
|
||||
1,
|
||||
gl::FALSE,
|
||||
self.def_matrix.as_ptr() as _,
|
||||
);
|
||||
gl.gl.UseProgram(self.imgui_data.program_tex);
|
||||
gl.gl.Uniform1i(self.imgui_data.tex_locs.texture, 0);
|
||||
gl.gl.UniformMatrix4fv(self.imgui_data.tex_locs.proj_mtx, 1, gl::FALSE, self.def_matrix.as_ptr() as _);
|
||||
gl.gl.UniformMatrix4fv(
|
||||
self.imgui_data.tex_locs.proj_mtx,
|
||||
1,
|
||||
gl::FALSE,
|
||||
self.def_matrix.as_ptr() as _,
|
||||
);
|
||||
gl.gl.BindFramebuffer(gl::FRAMEBUFFER, 0);
|
||||
}
|
||||
}
|
||||
|
@ -885,6 +897,7 @@ impl BackendRenderer for OpenGLRenderer {
|
|||
[0.0, 0.0, -1.0, 0.0],
|
||||
[-1.0, 1.0, 0.0, 1.0],
|
||||
];
|
||||
|
||||
gl.gl.UseProgram(self.imgui_data.program_tex);
|
||||
gl.gl.Uniform1i(self.imgui_data.tex_locs.texture, 0);
|
||||
gl.gl.UniformMatrix4fv(self.imgui_data.tex_locs.proj_mtx, 1, gl::FALSE, matrix.as_ptr() as _);
|
||||
|
@ -985,6 +998,130 @@ impl BackendRenderer for OpenGLRenderer {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_triangle_list(
|
||||
&mut self,
|
||||
vertices: Vec<VertexData>,
|
||||
texture: Option<&Box<dyn BackendTexture>>,
|
||||
shader: BackendShader,
|
||||
) -> GameResult<()> {
|
||||
unsafe { self.draw_arrays(gl::TRIANGLES, vertices, texture, shader) }
|
||||
}
|
||||
|
||||
fn supports_vertex_draw(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenGLRenderer {
|
||||
unsafe fn draw_arrays(
|
||||
&mut self,
|
||||
vert_type: GLenum,
|
||||
vertices: Vec<VertexData>,
|
||||
texture: Option<&Box<dyn BackendTexture>>,
|
||||
shader: BackendShader,
|
||||
) -> GameResult<()> {
|
||||
if vertices.len() == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(gl) = GL_PROC.as_ref() {
|
||||
match shader {
|
||||
BackendShader::Fill => {
|
||||
gl.gl.UseProgram(self.imgui_data.program_fill);
|
||||
gl.gl.BindBuffer(gl::ARRAY_BUFFER, self.imgui_data.vbo);
|
||||
gl.gl.EnableVertexAttribArray(self.imgui_data.fill_locs.position);
|
||||
gl.gl.EnableVertexAttribArray(self.imgui_data.fill_locs.uv);
|
||||
gl.gl.EnableVertexAttribArray(self.imgui_data.fill_locs.color);
|
||||
|
||||
gl.gl.VertexAttribPointer(
|
||||
self.imgui_data.fill_locs.position,
|
||||
2,
|
||||
gl::FLOAT,
|
||||
gl::FALSE,
|
||||
mem::size_of::<VertexData>() as _,
|
||||
field_offset::<VertexData, _, _>(|v| &v.position) as _,
|
||||
);
|
||||
|
||||
gl.gl.VertexAttribPointer(
|
||||
self.imgui_data.fill_locs.uv,
|
||||
2,
|
||||
gl::FLOAT,
|
||||
gl::FALSE,
|
||||
mem::size_of::<VertexData>() as _,
|
||||
field_offset::<VertexData, _, _>(|v| &v.uv) as _,
|
||||
);
|
||||
|
||||
gl.gl.VertexAttribPointer(
|
||||
self.imgui_data.fill_locs.color,
|
||||
4,
|
||||
gl::UNSIGNED_BYTE,
|
||||
gl::TRUE,
|
||||
mem::size_of::<VertexData>() as _,
|
||||
field_offset::<VertexData, _, _>(|v| &v.color) as _,
|
||||
);
|
||||
}
|
||||
BackendShader::Texture => {
|
||||
gl.gl.UseProgram(self.imgui_data.program_tex);
|
||||
gl.gl.BindBuffer(gl::ARRAY_BUFFER, self.imgui_data.vbo);
|
||||
gl.gl.EnableVertexAttribArray(self.imgui_data.tex_locs.position);
|
||||
gl.gl.EnableVertexAttribArray(self.imgui_data.tex_locs.uv);
|
||||
gl.gl.EnableVertexAttribArray(self.imgui_data.tex_locs.color);
|
||||
|
||||
gl.gl.VertexAttribPointer(
|
||||
self.imgui_data.tex_locs.position,
|
||||
2,
|
||||
gl::FLOAT,
|
||||
gl::FALSE,
|
||||
mem::size_of::<VertexData>() as _,
|
||||
field_offset::<VertexData, _, _>(|v| &v.position) as _,
|
||||
);
|
||||
|
||||
gl.gl.VertexAttribPointer(
|
||||
self.imgui_data.tex_locs.uv,
|
||||
2,
|
||||
gl::FLOAT,
|
||||
gl::FALSE,
|
||||
mem::size_of::<VertexData>() as _,
|
||||
field_offset::<VertexData, _, _>(|v| &v.uv) as _,
|
||||
);
|
||||
|
||||
gl.gl.VertexAttribPointer(
|
||||
self.imgui_data.tex_locs.color,
|
||||
4,
|
||||
gl::UNSIGNED_BYTE,
|
||||
gl::TRUE,
|
||||
mem::size_of::<VertexData>() as _,
|
||||
field_offset::<VertexData, _, _>(|v| &v.color) as _,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let texture_id = if let Some(texture) = texture {
|
||||
let gl_texture: &Box<OpenGLTexture> = std::mem::transmute(texture);
|
||||
gl_texture.texture_id
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
gl.gl.BindTexture(gl::TEXTURE_2D, texture_id);
|
||||
gl.gl.BufferData(
|
||||
gl::ARRAY_BUFFER,
|
||||
(vertices.len() * mem::size_of::<VertexData>()) as _,
|
||||
vertices.as_ptr() as _,
|
||||
gl::STREAM_DRAW,
|
||||
);
|
||||
|
||||
gl.gl.DrawArrays(vert_type, 0, vertices.len() as _);
|
||||
|
||||
gl.gl.BindTexture(gl::TEXTURE_2D, 0);
|
||||
gl.gl.BindBuffer(gl::ARRAY_BUFFER, 0);
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(RenderError("No OpenGL context available!".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for OpenGLRenderer {
|
||||
|
|
279
src/map.rs
279
src/map.rs
|
@ -1,11 +1,14 @@
|
|||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::io::{BufRead, BufReader, Error};
|
||||
use std::sync::Arc;
|
||||
|
||||
use byteorder::{LE, ReadBytesExt};
|
||||
use byteorder::{ReadBytesExt, LE};
|
||||
|
||||
use crate::framework::error::GameResult;
|
||||
|
||||
use crate::str;
|
||||
use crate::common::{Color, Rect};
|
||||
use crate::framework::error::GameError::ResourceLoadError;
|
||||
use crate::framework::error::{GameError, GameResult};
|
||||
use crate::str;
|
||||
|
||||
static SUPPORTED_PXM_VERSIONS: [u8; 1] = [0x10];
|
||||
static SUPPORTED_PXE_VERSIONS: [u8; 2] = [0, 0x10];
|
||||
|
@ -17,6 +20,16 @@ pub struct Map {
|
|||
pub attrib: [u8; 0x100],
|
||||
}
|
||||
|
||||
static SOLID_TILES: [u8; 8] = [0x05, 0x41, 0x43, 0x46, 0x54, 0x55, 0x56, 0x57];
|
||||
static WATER_TILES: [u8; 16] =
|
||||
[0x02, 0x60, 0x61, 0x62, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0xa0, 0xa1, 0xa2, 0xa3];
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum WaterRegionType {
|
||||
WaterLine,
|
||||
WaterDepth,
|
||||
}
|
||||
|
||||
impl Map {
|
||||
pub fn load_from<R: io::Read>(mut map_data: R, mut attrib_data: R) -> GameResult<Map> {
|
||||
let mut magic = [0; 3];
|
||||
|
@ -45,12 +58,7 @@ impl Map {
|
|||
log::warn!("Map attribute data is shorter than 256 bytes!");
|
||||
}
|
||||
|
||||
Ok(Map {
|
||||
width,
|
||||
height,
|
||||
tiles,
|
||||
attrib,
|
||||
})
|
||||
Ok(Map { width, height, tiles, attrib })
|
||||
}
|
||||
|
||||
pub fn get_attribute(&self, x: usize, y: usize) -> u8 {
|
||||
|
@ -60,6 +68,153 @@ impl Map {
|
|||
|
||||
self.attrib[*self.tiles.get(self.width as usize * y + x).unwrap_or_else(|| &0u8) as usize]
|
||||
}
|
||||
|
||||
pub fn find_water_regions(&self) -> Vec<(WaterRegionType, Rect<u16>)> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
if self.height == 0 || self.width == 0 {
|
||||
return result;
|
||||
}
|
||||
|
||||
let mut walked = vec![false; self.width as usize * self.height as usize];
|
||||
let mut rects = Vec::<Rect<u16>>::new();
|
||||
|
||||
for x in 0..self.width {
|
||||
for y in 0..self.height {
|
||||
let idx = self.width as usize * y as usize + x as usize;
|
||||
if walked[idx] {
|
||||
continue;
|
||||
}
|
||||
|
||||
let attr = self.get_attribute(x as usize, y as usize);
|
||||
|
||||
if !WATER_TILES.contains(&attr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
walked[idx] = true;
|
||||
let mut rect = Rect::new(x, y, x, y);
|
||||
let mut queue = Vec::new();
|
||||
queue.push((0b1100, x, y));
|
||||
|
||||
while let Some((flow_flags, fx, fy)) = queue.pop() {
|
||||
let idx = self.width as usize * fy as usize + fx as usize;
|
||||
|
||||
walked[idx] = true;
|
||||
|
||||
if fx < rect.left {
|
||||
rect.left = fx;
|
||||
|
||||
for y in rect.top..rect.bottom {
|
||||
walked[self.width as usize * y as usize + rect.left as usize] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if fx > rect.right {
|
||||
rect.right = fx;
|
||||
|
||||
for y in rect.top..rect.bottom {
|
||||
walked[self.width as usize * y as usize + rect.right as usize] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if fy < rect.top {
|
||||
rect.top = fy;
|
||||
|
||||
for x in rect.left..rect.right {
|
||||
walked[self.width as usize * rect.top as usize + x as usize] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if fy > rect.bottom {
|
||||
rect.bottom = fy;
|
||||
|
||||
for x in rect.left..rect.right {
|
||||
walked[self.width as usize * rect.bottom as usize + x as usize] = true;
|
||||
}
|
||||
}
|
||||
|
||||
let mut check = |flow_flags: u8, ex: i32, ey: i32| {
|
||||
if ex < 0 || ex >= self.width as i32 || ey < 0 || ey >= self.height as i32 {
|
||||
return;
|
||||
}
|
||||
|
||||
if walked[self.width as usize * ey as usize + ex as usize] {
|
||||
return;
|
||||
}
|
||||
|
||||
let attr = self.get_attribute(ex as usize, ey as usize);
|
||||
if WATER_TILES.contains(&attr) {
|
||||
queue.push((flow_flags, ex as u16, ey as u16));
|
||||
}
|
||||
};
|
||||
|
||||
if flow_flags & 0b0001 != 0 { check(0b1011, fx as i32 - 1, fy as i32); }
|
||||
if flow_flags & 0b0100 != 0 { check(0b1110, fx as i32 + 1, fy as i32); }
|
||||
if flow_flags & 0b0010 != 0 { check(0b0111, fx as i32, fy as i32 - 1); }
|
||||
if flow_flags & 0b1000 != 0 { check(0b1101, fx as i32, fy as i32 + 1); }
|
||||
}
|
||||
|
||||
rects.push(rect);
|
||||
}
|
||||
}
|
||||
|
||||
walked.fill(false);
|
||||
|
||||
for mut rect in rects {
|
||||
let line = rect.top;
|
||||
let line_up = rect.top - 1;
|
||||
let min_x = rect.left;
|
||||
let max_x = rect.right;
|
||||
|
||||
rect.top += 1;
|
||||
result.push((WaterRegionType::WaterDepth, rect));
|
||||
|
||||
let mut x = min_x;
|
||||
let mut length = 0;
|
||||
let mut make_water_line = false;
|
||||
|
||||
loop {
|
||||
let idx = self.width as usize * line as usize + x as usize;
|
||||
let attr = self.get_attribute(x as usize, line as usize);
|
||||
let attr_up = if rect.top > 0 { self.get_attribute(x as usize, line_up as usize) } else { 0x41 };
|
||||
|
||||
if !SOLID_TILES.contains(&attr_up) && !WATER_TILES.contains(&attr_up) {
|
||||
make_water_line = true;
|
||||
}
|
||||
|
||||
if !walked[idx] && WATER_TILES.contains(&attr) {
|
||||
length += 1;
|
||||
} else if length != 0 {
|
||||
let bounds = Rect::new(x - length, line, x, line);
|
||||
result.push((
|
||||
if make_water_line { WaterRegionType::WaterLine } else { WaterRegionType::WaterDepth },
|
||||
bounds,
|
||||
));
|
||||
length = 0;
|
||||
} else {
|
||||
length = 0;
|
||||
}
|
||||
|
||||
walked[idx] = true;
|
||||
x += 1;
|
||||
|
||||
if x >= max_x {
|
||||
if length != 0 {
|
||||
let bounds = Rect::new(x - length, line, x, line);
|
||||
result.push((
|
||||
if make_water_line { WaterRegionType::WaterLine } else { WaterRegionType::WaterDepth },
|
||||
bounds,
|
||||
));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -103,18 +258,102 @@ impl NPCData {
|
|||
// booster's lab also specifies a layer field in version 0x10, prob for multi-layered maps
|
||||
let layer = if version == 0x10 { data.read_u8()? } else { 0 };
|
||||
|
||||
npcs.push(NPCData {
|
||||
id: 170 + i as u16,
|
||||
x,
|
||||
y,
|
||||
flag_num,
|
||||
event_num,
|
||||
npc_type,
|
||||
flags,
|
||||
layer,
|
||||
})
|
||||
npcs.push(NPCData { id: 170 + i as u16, x, y, flag_num, event_num, npc_type, flags, layer })
|
||||
}
|
||||
|
||||
Ok(npcs)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct WaterParamEntry {
|
||||
pub color_top: Color,
|
||||
pub color_middle: Color,
|
||||
pub color_bottom: Color,
|
||||
}
|
||||
|
||||
pub struct WaterParams {
|
||||
entries: HashMap<u8, WaterParamEntry>,
|
||||
}
|
||||
|
||||
impl WaterParams {
|
||||
pub fn new() -> WaterParams {
|
||||
WaterParams { entries: HashMap::new() }
|
||||
}
|
||||
|
||||
pub fn load_from<R: io::Read>(&mut self, data: R) -> GameResult {
|
||||
fn next_u8(s: &mut std::str::Split<&str>, error_msg: &str) -> GameResult<u8> {
|
||||
match s.next() {
|
||||
None => Err(GameError::ParseError("Out of range.".to_string())),
|
||||
Some(v) => v.trim().parse::<u8>().map_err(|_| GameError::ParseError(error_msg.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
for line in BufReader::new(data).lines() {
|
||||
match line {
|
||||
Ok(line) => {
|
||||
let mut splits = line.split(":");
|
||||
|
||||
if splits.clone().count() != 5 {
|
||||
return Err(GameError::ParseError("Invalid count of delimiters.".to_string()));
|
||||
}
|
||||
|
||||
let tile_min = next_u8(&mut splits, "Invalid minimum tile value.")?;
|
||||
let tile_max = next_u8(&mut splits, "Invalid maximum tile value.")?;
|
||||
|
||||
if tile_min > tile_max {
|
||||
return Err(GameError::ParseError("tile_min > tile_max".to_string()));
|
||||
}
|
||||
|
||||
let mut read_color = || -> GameResult<Color> {
|
||||
let cstr = splits.next().unwrap().trim();
|
||||
if !cstr.starts_with("[") || !cstr.ends_with("]") {
|
||||
return Err(GameError::ParseError("Invalid format of color value.".to_string()));
|
||||
}
|
||||
|
||||
let mut csplits = cstr[1..cstr.len() - 1].split(",");
|
||||
|
||||
if csplits.clone().count() != 4 {
|
||||
return Err(GameError::ParseError("Invalid count of delimiters.".to_string()));
|
||||
}
|
||||
|
||||
let r = next_u8(&mut csplits, "Invalid red value.")?;
|
||||
let g = next_u8(&mut csplits, "Invalid green value.")?;
|
||||
let b = next_u8(&mut csplits, "Invalid blue value.")?;
|
||||
let a = next_u8(&mut csplits, "Invalid alpha value.")?;
|
||||
|
||||
Ok(Color::from_rgba(r, g, b, a))
|
||||
};
|
||||
|
||||
let color_top = read_color()?;
|
||||
let color_middle = read_color()?;
|
||||
let color_bottom = read_color()?;
|
||||
|
||||
let entry = WaterParamEntry { color_top, color_middle, color_bottom };
|
||||
|
||||
for i in tile_min..=tile_max {
|
||||
self.entries.insert(i, entry);
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(GameError::IOError(Arc::new(e))),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn loaded(&self) -> bool {
|
||||
!self.entries.is_empty()
|
||||
}
|
||||
|
||||
pub fn get_entry(&self, tile: u8) -> &WaterParamEntry {
|
||||
static DEFAULT_ENTRY: WaterParamEntry = WaterParamEntry {
|
||||
color_top: Color::new(1.0, 1.0, 1.0, 1.0),
|
||||
color_middle: Color::new(1.0, 1.0, 1.0, 1.0),
|
||||
color_bottom: Color::new(1.0, 1.0, 1.0, 1.0),
|
||||
};
|
||||
|
||||
self.entries.get(&tile).unwrap_or(&DEFAULT_ENTRY)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,41 +3,45 @@ use std::ops::Range;
|
|||
use log::info;
|
||||
|
||||
use crate::caret::CaretType;
|
||||
use crate::common::{Color, Direction, FadeDirection, FadeState, fix9_scale, interpolate_fix9_scale, Rect};
|
||||
use crate::common::{fix9_scale, interpolate_fix9_scale, Color, Direction, FadeDirection, FadeState, Rect};
|
||||
use crate::components::boss_life_bar::BossLifeBar;
|
||||
use crate::components::draw_common::Alignment;
|
||||
use crate::components::flash::Flash;
|
||||
use crate::components::hud::HUD;
|
||||
use crate::components::inventory::InventoryUI;
|
||||
use crate::components::stage_select::StageSelect;
|
||||
use crate::components::water_renderer::WaterRenderer;
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::{Frame, UpdateTarget};
|
||||
use crate::framework::backend::SpriteBatchCommand;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics;
|
||||
use crate::framework::graphics::{BlendMode, draw_rect, FilterMode};
|
||||
use crate::framework::graphics::{draw_rect, BlendMode, FilterMode};
|
||||
use crate::framework::ui::Components;
|
||||
use crate::framework::{filesystem, graphics};
|
||||
use crate::input::touch_controls::TouchControlType;
|
||||
use crate::inventory::{Inventory, TakeExperienceResult};
|
||||
use crate::npc::{NPC, NPCLayer};
|
||||
use crate::map::WaterParams;
|
||||
use crate::npc::boss::BossNPC;
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::npc::{NPCLayer, NPC};
|
||||
use crate::physics::PhysicalEntity;
|
||||
use crate::player::{Player, TargetPlayer};
|
||||
use crate::rng::XorShift;
|
||||
use crate::scene::Scene;
|
||||
use crate::scene::title_scene::TitleScene;
|
||||
use crate::scene::Scene;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::stage::{BackgroundType, Stage};
|
||||
use crate::text_script::{ConfirmSelection, ScriptMode, TextScriptExecutionState, TextScriptLine, TextScriptVM};
|
||||
use crate::texture_set::SizedBatch;
|
||||
use crate::weapon::{Weapon, WeaponType};
|
||||
use crate::weapon::bullet::BulletManager;
|
||||
use crate::weapon::{Weapon, WeaponType};
|
||||
|
||||
pub struct GameScene {
|
||||
pub tick: u32,
|
||||
pub stage: Stage,
|
||||
pub water_params: WaterParams,
|
||||
pub water_renderer: WaterRenderer,
|
||||
pub boss_life_bar: BossLifeBar,
|
||||
pub stage_select: StageSelect,
|
||||
pub flash: Flash,
|
||||
|
@ -58,6 +62,7 @@ pub struct GameScene {
|
|||
tex_tileset_name: String,
|
||||
map_name_counter: u16,
|
||||
skip_counter: u16,
|
||||
inventory_dim: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
|
||||
|
@ -79,6 +84,17 @@ 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);
|
||||
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(""))
|
||||
{
|
||||
water_params.load_from(water_param_file)?;
|
||||
info!("Loaded water parameters file.");
|
||||
|
||||
water_renderer.initialize(stage.map.find_water_regions());
|
||||
}
|
||||
|
||||
let tex_background_name = stage.data.background.filename();
|
||||
let tex_tileset_name = ["Stage/", &stage.data.tileset.filename()].join("");
|
||||
|
@ -86,6 +102,8 @@ impl GameScene {
|
|||
Ok(Self {
|
||||
tick: 0,
|
||||
stage,
|
||||
water_params,
|
||||
water_renderer,
|
||||
player1: Player::new(state),
|
||||
player2: Player::new(state),
|
||||
inventory_player1: Inventory::new(),
|
||||
|
@ -115,6 +133,7 @@ impl GameScene {
|
|||
tex_tileset_name,
|
||||
map_name_counter: 0,
|
||||
skip_counter: 0,
|
||||
inventory_dim: 0.0,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -544,37 +563,34 @@ impl GameScene {
|
|||
|
||||
let text_offset = if state.textscript_vm.face == 0 { 0.0 } else { 56.0 };
|
||||
|
||||
if !state.textscript_vm.line_1.is_empty() {
|
||||
state.font.draw_text(
|
||||
state.textscript_vm.line_1.iter().copied(),
|
||||
left_pos + text_offset + 14.0,
|
||||
top_pos + 10.0,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
let lines = [
|
||||
&state.textscript_vm.line_1,
|
||||
&state.textscript_vm.line_2,
|
||||
&state.textscript_vm.line_3,
|
||||
];
|
||||
|
||||
if !state.textscript_vm.line_2.is_empty() {
|
||||
state.font.draw_text(
|
||||
state.textscript_vm.line_2.iter().copied(),
|
||||
left_pos + text_offset + 14.0,
|
||||
top_pos + 10.0 + 16.0,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
|
||||
if !state.textscript_vm.line_3.is_empty() {
|
||||
state.font.draw_text(
|
||||
state.textscript_vm.line_3.iter().copied(),
|
||||
left_pos + text_offset + 14.0,
|
||||
top_pos + 10.0 + 32.0,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
for (idx, line) in lines.iter().enumerate() {
|
||||
if !line.is_empty() {
|
||||
if state.constants.textscript.text_shadow {
|
||||
state.font.draw_text_with_shadow(
|
||||
line.iter().copied(),
|
||||
left_pos + text_offset + 14.0,
|
||||
top_pos + 10.0 + idx as f32 * 16.0,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
} else {
|
||||
state.font.draw_text(
|
||||
line.iter().copied(),
|
||||
left_pos + text_offset + 14.0,
|
||||
top_pos + 10.0 + idx as f32 * 16.0,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let TextScriptExecutionState::WaitInput(_, _, tick) = state.textscript_vm.state {
|
||||
|
@ -1023,40 +1039,6 @@ impl GameScene {
|
|||
);
|
||||
}
|
||||
}
|
||||
299 => {
|
||||
self.draw_light(
|
||||
interpolate_fix9_scale(
|
||||
npc.prev_x - self.frame.prev_x,
|
||||
npc.x - self.frame.x,
|
||||
state.frame_time,
|
||||
),
|
||||
interpolate_fix9_scale(
|
||||
npc.prev_y - self.frame.prev_y,
|
||||
npc.y - self.frame.y,
|
||||
state.frame_time,
|
||||
),
|
||||
4.0,
|
||||
(30, 30, 200),
|
||||
batch,
|
||||
);
|
||||
}
|
||||
300 => {
|
||||
self.draw_light(
|
||||
interpolate_fix9_scale(
|
||||
npc.prev_x - self.frame.prev_x,
|
||||
npc.x - self.frame.x,
|
||||
state.frame_time,
|
||||
),
|
||||
interpolate_fix9_scale(
|
||||
npc.prev_y - self.frame.prev_y,
|
||||
npc.y - self.frame.y,
|
||||
state.frame_time,
|
||||
),
|
||||
1.5,
|
||||
(200, 10, 10),
|
||||
batch,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -1476,6 +1458,8 @@ impl GameScene {
|
|||
}
|
||||
}
|
||||
|
||||
self.water_renderer.tick(state, (&self.player1))?;
|
||||
|
||||
if self.map_name_counter > 0 {
|
||||
self.map_name_counter -= 1;
|
||||
}
|
||||
|
@ -1767,18 +1751,19 @@ impl Scene for GameScene {
|
|||
self.draw_water(state, ctx)?;
|
||||
}*/
|
||||
|
||||
self.water_renderer.draw(state, ctx, &self.frame)?;
|
||||
self.draw_tiles(state, ctx, TileLayer::Foreground)?;
|
||||
self.draw_tiles(state, ctx, TileLayer::Snack)?;
|
||||
self.draw_carets(state, ctx)?;
|
||||
self.player1.popup.draw(state, ctx, &self.frame)?;
|
||||
self.player2.popup.draw(state, ctx, &self.frame)?;
|
||||
|
||||
if state.settings.shader_effects
|
||||
&& (self.stage.data.background_type == BackgroundType::Black
|
||||
|| self.stage.data.background.name() == "bkBlack")
|
||||
{
|
||||
self.draw_light_map(state, ctx)?;
|
||||
}
|
||||
// if !self.intro_mode && state.settings.shader_effects
|
||||
// && (self.stage.data.background_type == BackgroundType::Black
|
||||
// || self.stage.data.background.name() == "bkBlack")
|
||||
// {
|
||||
// self.draw_light_map(state, ctx)?;
|
||||
// }
|
||||
self.flash.draw(state, ctx, &self.frame)?;
|
||||
|
||||
/*graphics::set_canvas(ctx, None);
|
||||
|
@ -1852,8 +1837,7 @@ impl Scene for GameScene {
|
|||
|
||||
self.draw_fade(state, ctx)?;
|
||||
if state.textscript_vm.mode == ScriptMode::Map && self.map_name_counter > 0 {
|
||||
let map_name =
|
||||
if self.intro_mode { state.constants.title.intro_text.chars() } else { self.stage.data.name.chars() };
|
||||
let map_name = if self.stage.data.name == "u" { state.constants.title.intro_text.chars() } else { self.stage.data.name.chars() };
|
||||
let width = state.font.text_width(map_name.clone(), &state.constants);
|
||||
|
||||
state.font.draw_text_with_shadow(
|
||||
|
|
|
@ -31,6 +31,7 @@ use crate::str;
|
|||
use crate::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
|
||||
use crate::texture_set::TextureSet;
|
||||
use bitvec::array::BitArray;
|
||||
use crate::scene::title_scene::TitleScene;
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
pub enum TimingMode {
|
||||
|
@ -256,6 +257,14 @@ impl SharedGameState {
|
|||
}
|
||||
|
||||
pub fn start_intro(&mut self, ctx: &mut Context) -> GameResult {
|
||||
let start_stage_id = 72;
|
||||
|
||||
if self.stages.len() < start_stage_id {
|
||||
log::warn!("Intro scene out of bounds in stage table, skipping to title...");
|
||||
self.next_scene = Some(Box::new(TitleScene::new()));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut next_scene = GameScene::new(self, ctx, 72)?;
|
||||
next_scene.player1.cond.set_hidden(true);
|
||||
next_scene.player1.x = 3 * 0x2000;
|
||||
|
|
|
@ -40,59 +40,21 @@ pub static PIXTONE_TABLE: [PixToneParameters; 160] = [
|
|||
Channel::disabled(),
|
||||
],
|
||||
},
|
||||
// PixToneParameters { // fx2 (CS+)
|
||||
// channels: [
|
||||
// Channel {
|
||||
// enabled: true,
|
||||
// length: 2000,
|
||||
// carrier: Waveform {
|
||||
// waveform_type: 0,
|
||||
// pitch: 92.000000,
|
||||
// level: 32,
|
||||
// offset: 0,
|
||||
// },
|
||||
// frequency: Waveform {
|
||||
// waveform_type: 0,
|
||||
// pitch: 3.000000,
|
||||
// level: 44,
|
||||
// offset: 0,
|
||||
// },
|
||||
// amplitude: Waveform {
|
||||
// waveform_type: 0,
|
||||
// pitch: 0.000000,
|
||||
// level: 32,
|
||||
// offset: 0,
|
||||
// },
|
||||
// envelope: Envelope {
|
||||
// initial: 7,
|
||||
// time_a: 2,
|
||||
// value_a: 18,
|
||||
// time_b: 128,
|
||||
// value_b: 0,
|
||||
// time_c: 255,
|
||||
// value_c: 0,
|
||||
// },
|
||||
// },
|
||||
// Channel::disabled(),
|
||||
// Channel::disabled(),
|
||||
// Channel::disabled(),
|
||||
// ],
|
||||
// },
|
||||
PixToneParameters { // fx2
|
||||
PixToneParameters { // fx2 (CS+)
|
||||
channels: [
|
||||
Channel {
|
||||
enabled: true,
|
||||
length: 4000,
|
||||
length: 2000,
|
||||
carrier: Waveform {
|
||||
waveform_type: 1,
|
||||
pitch: 54.000000,
|
||||
waveform_type: 0,
|
||||
pitch: 92.000000,
|
||||
level: 32,
|
||||
offset: 0,
|
||||
},
|
||||
frequency: Waveform {
|
||||
waveform_type: 5,
|
||||
pitch: 0.100000,
|
||||
level: 33,
|
||||
waveform_type: 0,
|
||||
pitch: 3.000000,
|
||||
level: 44,
|
||||
offset: 0,
|
||||
},
|
||||
amplitude: Waveform {
|
||||
|
@ -102,11 +64,11 @@ pub static PIXTONE_TABLE: [PixToneParameters; 160] = [
|
|||
offset: 0,
|
||||
},
|
||||
envelope: Envelope {
|
||||
initial: 53,
|
||||
time_a: 57,
|
||||
value_a: 44,
|
||||
initial: 7,
|
||||
time_a: 2,
|
||||
value_a: 18,
|
||||
time_b: 128,
|
||||
value_b: 24,
|
||||
value_b: 0,
|
||||
time_c: 255,
|
||||
value_c: 0,
|
||||
},
|
||||
|
@ -116,6 +78,44 @@ pub static PIXTONE_TABLE: [PixToneParameters; 160] = [
|
|||
Channel::disabled(),
|
||||
],
|
||||
},
|
||||
// PixToneParameters { // fx2
|
||||
// channels: [
|
||||
// Channel {
|
||||
// enabled: true,
|
||||
// length: 4000,
|
||||
// carrier: Waveform {
|
||||
// waveform_type: 1,
|
||||
// pitch: 54.000000,
|
||||
// level: 32,
|
||||
// offset: 0,
|
||||
// },
|
||||
// frequency: Waveform {
|
||||
// waveform_type: 5,
|
||||
// pitch: 0.100000,
|
||||
// level: 33,
|
||||
// offset: 0,
|
||||
// },
|
||||
// amplitude: Waveform {
|
||||
// waveform_type: 0,
|
||||
// pitch: 0.000000,
|
||||
// level: 32,
|
||||
// offset: 0,
|
||||
// },
|
||||
// envelope: Envelope {
|
||||
// initial: 53,
|
||||
// time_a: 57,
|
||||
// value_a: 44,
|
||||
// time_b: 128,
|
||||
// value_b: 24,
|
||||
// time_c: 255,
|
||||
// value_c: 0,
|
||||
// },
|
||||
// },
|
||||
// Channel::disabled(),
|
||||
// Channel::disabled(),
|
||||
// Channel::disabled(),
|
||||
// ],
|
||||
// },
|
||||
PixToneParameters { // fx3
|
||||
channels: [
|
||||
Channel {
|
||||
|
|
|
@ -37,7 +37,7 @@ impl NpcType {
|
|||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Tileset {
|
||||
name: String,
|
||||
pub(crate) name: String,
|
||||
}
|
||||
|
||||
impl Clone for Tileset {
|
||||
|
@ -205,7 +205,6 @@ fn from_shift_jis(s: &[u8]) -> String {
|
|||
}
|
||||
|
||||
impl StageData {
|
||||
// todo: refactor to make it less repetitive.
|
||||
pub fn load_stage_table(ctx: &mut Context, root: &str) -> GameResult<Vec<Self>> {
|
||||
let stage_tbl_path = [root, "stage.tbl"].join("");
|
||||
let stage_sect_path = [root, "stage.sect"].join("");
|
||||
|
|
|
@ -607,14 +607,14 @@ impl TextScriptVM {
|
|||
if remaining > 1 {
|
||||
let ticks = if state.textscript_vm.flags.fast() || state.textscript_vm.flags.cutscene_skip() {
|
||||
0
|
||||
} else if game_scene.player1.controller.jump()
|
||||
} else if remaining != 2 && (game_scene.player1.controller.jump()
|
||||
|| game_scene.player1.controller.shoot()
|
||||
|| game_scene.player2.controller.jump()
|
||||
|| game_scene.player2.controller.shoot()
|
||||
|| game_scene.player2.controller.shoot())
|
||||
{
|
||||
1
|
||||
state.constants.textscript.text_speed_fast
|
||||
} else {
|
||||
4
|
||||
state.constants.textscript.text_speed_normal
|
||||
};
|
||||
|
||||
if ticks > 0 {
|
||||
|
|
Loading…
Reference in New Issue