cs+ inspired water thing

This commit is contained in:
Alula 2021-06-20 21:41:09 +02:00
parent d68a248292
commit 3fe8e132e5
No known key found for this signature in database
GPG Key ID: 3E00485503A1D8BA
16 changed files with 854 additions and 184 deletions

View File

@ -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)

View File

@ -5,3 +5,4 @@ pub mod hud;
pub mod inventory;
pub mod number_popup;
pub mod stage_select;
pub mod water_renderer;

View File

@ -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(())
}
}

View File

@ -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());
}

View File

@ -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 {

View File

@ -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))))
}

View File

@ -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(())
}
}

View File

@ -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 {

View File

@ -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()))
}

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -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(

View File

@ -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;

View File

@ -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 {

View File

@ -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("");

View File

@ -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 {