mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-07-30 17:21:01 +00:00
Compare commits
7 commits
4d08158427
...
ecabff27a8
Author | SHA1 | Date | |
---|---|---|---|
|
ecabff27a8 | ||
|
ad6a330ae0 | ||
|
d83f58fe1b | ||
|
4f34e33b57 | ||
|
0db2e02181 | ||
|
490f02f2fe | ||
|
d6316aa718 |
|
@ -77,6 +77,19 @@ impl BMFontRenderer {
|
|||
self.draw_colored_text(iter, x, y, (255, 255, 255, 255), constants, texture_set, ctx)
|
||||
}
|
||||
|
||||
pub fn draw_text_with_shadow<I: Iterator<Item = char> + Clone>(
|
||||
&self,
|
||||
iter: I,
|
||||
x: f32,
|
||||
y: f32,
|
||||
constants: &EngineConstants,
|
||||
texture_set: &mut TextureSet,
|
||||
ctx: &mut Context,
|
||||
) -> GameResult {
|
||||
self.draw_colored_text(iter.clone(), x + 1.0, y + 1.0, (0, 0, 0, 150), constants, texture_set, ctx)?;
|
||||
self.draw_colored_text(iter, x, y, (255, 255, 255, 255), constants, texture_set, ctx)
|
||||
}
|
||||
|
||||
pub fn draw_colored_text_scaled<I: Iterator<Item = char>>(
|
||||
&self,
|
||||
iter: I,
|
||||
|
|
|
@ -24,6 +24,7 @@ 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,
|
||||
|
@ -176,6 +177,7 @@ impl BackendEventLoop for SDL2EventLoop {
|
|||
{
|
||||
let mutex = GAME_SUSPENDED.lock().unwrap();
|
||||
if *mutex {
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ pub struct OpenGLTexture {
|
|||
texture_id: u32,
|
||||
framebuffer_id: u32,
|
||||
locs: Locs,
|
||||
program: GLuint,
|
||||
vbo: GLuint,
|
||||
vertices: Vec<VertexData>,
|
||||
context_active: Arc<RefCell<bool>>,
|
||||
|
@ -187,6 +188,7 @@ impl BackendTexture for OpenGLTexture {
|
|||
|
||||
gl.gl.BindTexture(gl::TEXTURE_2D, self.texture_id);
|
||||
gl.gl.BindBuffer(gl::ARRAY_BUFFER, self.vbo);
|
||||
gl.gl.UseProgram(self.program);
|
||||
gl.gl.EnableVertexAttribArray(self.locs.position);
|
||||
gl.gl.EnableVertexAttribArray(self.locs.uv);
|
||||
gl.gl.EnableVertexAttribArray(self.locs.color);
|
||||
|
@ -347,8 +349,10 @@ struct Locs {
|
|||
|
||||
struct ImguiData {
|
||||
initialized: bool,
|
||||
program: GLuint,
|
||||
locs: Locs,
|
||||
program_tex: GLuint,
|
||||
program_fill: GLuint,
|
||||
tex_locs: Locs,
|
||||
fill_locs: Locs,
|
||||
vbo: GLuint,
|
||||
ebo: GLuint,
|
||||
font_texture: GLuint,
|
||||
|
@ -359,8 +363,10 @@ impl ImguiData {
|
|||
fn new() -> Self {
|
||||
ImguiData {
|
||||
initialized: false,
|
||||
program: 0,
|
||||
locs: Locs { texture: 0, proj_mtx: 0, position: 0, uv: 0, color: 0 },
|
||||
program_tex: 0,
|
||||
program_fill: 0,
|
||||
tex_locs: Locs { texture: 0, proj_mtx: 0, position: 0, uv: 0, color: 0 },
|
||||
fill_locs: Locs { texture: 0, proj_mtx: 0, position: 0, uv: 0, color: 0 },
|
||||
vbo: 0,
|
||||
ebo: 0,
|
||||
font_texture: 0,
|
||||
|
@ -372,37 +378,61 @@ impl ImguiData {
|
|||
self.initialized = true;
|
||||
|
||||
let vert_sources = [VERTEX_SHADER_BASIC.as_ptr() as *const GLchar];
|
||||
let frag_sources = [FRAGMENT_SHADER_TEXTURED.as_ptr() as *const GLchar];
|
||||
let frag_sources_tex = [FRAGMENT_SHADER_TEXTURED.as_ptr() as *const GLchar];
|
||||
let frag_sources_fill = [FRAGMENT_SHADER_COLOR.as_ptr() as *const GLchar];
|
||||
let vert_sources_len = [VERTEX_SHADER_BASIC.len() as GLint - 1];
|
||||
let frag_sources_len = [FRAGMENT_SHADER_TEXTURED.len() as GLint - 1];
|
||||
let frag_sources_tex_len = [FRAGMENT_SHADER_TEXTURED.len() as GLint - 1];
|
||||
let frag_sources_fill_len = [FRAGMENT_SHADER_COLOR.len() as GLint - 1];
|
||||
|
||||
unsafe {
|
||||
self.program = gl.gl.CreateProgram();
|
||||
self.program_tex = gl.gl.CreateProgram();
|
||||
self.program_fill = gl.gl.CreateProgram();
|
||||
let vert_shader = gl.gl.CreateShader(gl::VERTEX_SHADER);
|
||||
let frag_shader = gl.gl.CreateShader(gl::FRAGMENT_SHADER);
|
||||
gl.gl.ShaderSource(vert_shader, 1, vert_sources.as_ptr(), vert_sources_len.as_ptr());
|
||||
gl.gl.ShaderSource(frag_shader, 1, frag_sources.as_ptr(), frag_sources_len.as_ptr());
|
||||
gl.gl.CompileShader(vert_shader);
|
||||
let frag_shader_tex = gl.gl.CreateShader(gl::FRAGMENT_SHADER);
|
||||
let frag_shader_fill = gl.gl.CreateShader(gl::FRAGMENT_SHADER);
|
||||
|
||||
gl.gl.CompileShader(frag_shader);
|
||||
gl.gl.AttachShader(self.program, vert_shader);
|
||||
gl.gl.AttachShader(self.program, frag_shader);
|
||||
gl.gl.LinkProgram(self.program);
|
||||
gl.gl.ShaderSource(vert_shader, 1, vert_sources.as_ptr(), vert_sources_len.as_ptr());
|
||||
gl.gl.ShaderSource(frag_shader_tex, 1, frag_sources_tex.as_ptr(), frag_sources_tex_len.as_ptr());
|
||||
gl.gl.ShaderSource(frag_shader_fill, 1, frag_sources_fill.as_ptr(), frag_sources_fill_len.as_ptr());
|
||||
|
||||
gl.gl.CompileShader(vert_shader);
|
||||
gl.gl.CompileShader(frag_shader_tex);
|
||||
gl.gl.CompileShader(frag_shader_fill);
|
||||
|
||||
if !check_shader_compile_status(vert_shader, gl) {
|
||||
gl.gl.DeleteShader(vert_shader);
|
||||
}
|
||||
|
||||
if !check_shader_compile_status(frag_shader, gl) {
|
||||
gl.gl.DeleteShader(frag_shader);
|
||||
if !check_shader_compile_status(frag_shader_tex, gl) {
|
||||
gl.gl.DeleteShader(frag_shader_tex);
|
||||
}
|
||||
|
||||
self.locs = Locs {
|
||||
texture: gl.gl.GetUniformLocation(self.program, b"Texture\0".as_ptr() as _),
|
||||
proj_mtx: gl.gl.GetUniformLocation(self.program, b"ProjMtx\0".as_ptr() as _),
|
||||
position: gl.gl.GetAttribLocation(self.program, b"Position\0".as_ptr() as _) as _,
|
||||
uv: gl.gl.GetAttribLocation(self.program, b"UV\0".as_ptr() as _) as _,
|
||||
color: gl.gl.GetAttribLocation(self.program, b"Color\0".as_ptr() as _) as _,
|
||||
if !check_shader_compile_status(frag_shader_fill, gl) {
|
||||
gl.gl.DeleteShader(frag_shader_fill);
|
||||
}
|
||||
|
||||
gl.gl.AttachShader(self.program_tex, vert_shader);
|
||||
gl.gl.AttachShader(self.program_tex, frag_shader_tex);
|
||||
gl.gl.LinkProgram(self.program_tex);
|
||||
|
||||
gl.gl.AttachShader(self.program_fill, vert_shader);
|
||||
gl.gl.AttachShader(self.program_fill, frag_shader_fill);
|
||||
gl.gl.LinkProgram(self.program_fill);
|
||||
|
||||
self.tex_locs = Locs {
|
||||
texture: gl.gl.GetUniformLocation(self.program_tex, b"Texture\0".as_ptr() as _),
|
||||
proj_mtx: gl.gl.GetUniformLocation(self.program_tex, b"ProjMtx\0".as_ptr() as _),
|
||||
position: gl.gl.GetAttribLocation(self.program_tex, b"Position\0".as_ptr() as _) as _,
|
||||
uv: gl.gl.GetAttribLocation(self.program_tex, b"UV\0".as_ptr() as _) as _,
|
||||
color: gl.gl.GetAttribLocation(self.program_tex, b"Color\0".as_ptr() as _) as _,
|
||||
};
|
||||
|
||||
self.fill_locs = Locs {
|
||||
texture: gl.gl.GetUniformLocation(self.program_fill, b"Texture\0".as_ptr() as _),
|
||||
proj_mtx: gl.gl.GetUniformLocation(self.program_fill, b"ProjMtx\0".as_ptr() as _),
|
||||
position: gl.gl.GetAttribLocation(self.program_fill, b"Position\0".as_ptr() as _) as _,
|
||||
uv: gl.gl.GetAttribLocation(self.program_fill, b"UV\0".as_ptr() as _) as _,
|
||||
color: gl.gl.GetAttribLocation(self.program_fill, b"Color\0".as_ptr() as _) as _,
|
||||
};
|
||||
|
||||
self.vbo = return_param(|x| gl.gl.GenBuffers(1, x));
|
||||
|
@ -581,9 +611,11 @@ impl BackendRenderer for OpenGLRenderer {
|
|||
|
||||
gl.gl.BindBuffer(gl::ARRAY_BUFFER, 0);
|
||||
gl.gl.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
|
||||
gl.gl.UseProgram(self.imgui_data.program);
|
||||
gl.gl.Uniform1i(self.imgui_data.locs.texture, 0);
|
||||
gl.gl.UniformMatrix4fv(self.imgui_data.locs.proj_mtx, 1, gl::FALSE, self.def_matrix.as_ptr() as _);
|
||||
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.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 _);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -635,7 +667,8 @@ impl BackendRenderer for OpenGLRenderer {
|
|||
width,
|
||||
height,
|
||||
vertices: Vec::new(),
|
||||
locs: self.imgui_data.locs,
|
||||
locs: self.imgui_data.tex_locs,
|
||||
program: self.imgui_data.program_tex,
|
||||
vbo: self.imgui_data.vbo,
|
||||
context_active: self.context_active.clone(),
|
||||
}))
|
||||
|
@ -674,7 +707,8 @@ impl BackendRenderer for OpenGLRenderer {
|
|||
width,
|
||||
height,
|
||||
vertices: Vec::new(),
|
||||
locs: self.imgui_data.locs,
|
||||
locs: self.imgui_data.tex_locs,
|
||||
program: self.imgui_data.program_tex,
|
||||
vbo: self.imgui_data.vbo,
|
||||
context_active: self.context_active.clone(),
|
||||
}))
|
||||
|
@ -719,11 +753,19 @@ impl BackendRenderer for OpenGLRenderer {
|
|||
[0.0, 0.0, -1.0, 0.0],
|
||||
[-1.0, -1.0, 0.0, 1.0],
|
||||
];
|
||||
gl.gl.UniformMatrix4fv(self.imgui_data.locs.proj_mtx, 1, gl::FALSE, matrix.as_ptr() as _);
|
||||
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.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.BindFramebuffer(gl::FRAMEBUFFER, gl_texture.framebuffer_id);
|
||||
} else {
|
||||
gl.gl.UniformMatrix4fv(self.imgui_data.locs.proj_mtx, 1, gl::FALSE, self.def_matrix.as_ptr() as _);
|
||||
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.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.BindFramebuffer(gl::FRAMEBUFFER, 0);
|
||||
}
|
||||
}
|
||||
|
@ -755,13 +797,14 @@ impl BackendRenderer for OpenGLRenderer {
|
|||
gl.gl.BindSampler(0, 0);
|
||||
}
|
||||
|
||||
gl.gl.UseProgram(self.imgui_data.program_fill);
|
||||
gl.gl.BindBuffer(gl::ARRAY_BUFFER, self.imgui_data.vbo);
|
||||
gl.gl.EnableVertexAttribArray(self.imgui_data.locs.position);
|
||||
gl.gl.EnableVertexAttribArray(self.imgui_data.locs.uv);
|
||||
gl.gl.EnableVertexAttribArray(self.imgui_data.locs.color);
|
||||
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.locs.position,
|
||||
self.imgui_data.fill_locs.position,
|
||||
2,
|
||||
gl::FLOAT,
|
||||
gl::FALSE,
|
||||
|
@ -770,7 +813,7 @@ impl BackendRenderer for OpenGLRenderer {
|
|||
);
|
||||
|
||||
gl.gl.VertexAttribPointer(
|
||||
self.imgui_data.locs.uv,
|
||||
self.imgui_data.fill_locs.uv,
|
||||
2,
|
||||
gl::FLOAT,
|
||||
gl::FALSE,
|
||||
|
@ -779,7 +822,7 @@ impl BackendRenderer for OpenGLRenderer {
|
|||
);
|
||||
|
||||
gl.gl.VertexAttribPointer(
|
||||
self.imgui_data.locs.color,
|
||||
self.imgui_data.fill_locs.color,
|
||||
4,
|
||||
gl::UNSIGNED_BYTE,
|
||||
gl::TRUE,
|
||||
|
@ -842,9 +885,9 @@ 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);
|
||||
gl.gl.Uniform1i(self.imgui_data.locs.texture, 0);
|
||||
gl.gl.UniformMatrix4fv(self.imgui_data.locs.proj_mtx, 1, gl::FALSE, 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 _);
|
||||
|
||||
if gl.gl.BindSampler.is_loaded() {
|
||||
gl.gl.BindSampler(0, 0);
|
||||
|
@ -853,12 +896,12 @@ impl BackendRenderer for OpenGLRenderer {
|
|||
// let vao = return_param(|x| gl.gl.GenVertexArrays(1, x));
|
||||
//gl.gl.BindVertexArray(vao);
|
||||
gl.gl.BindBuffer(gl::ARRAY_BUFFER, self.imgui_data.vbo);
|
||||
gl.gl.EnableVertexAttribArray(self.imgui_data.locs.position);
|
||||
gl.gl.EnableVertexAttribArray(self.imgui_data.locs.uv);
|
||||
gl.gl.EnableVertexAttribArray(self.imgui_data.locs.color);
|
||||
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.locs.position,
|
||||
self.imgui_data.tex_locs.position,
|
||||
2,
|
||||
gl::FLOAT,
|
||||
gl::FALSE,
|
||||
|
@ -867,7 +910,7 @@ impl BackendRenderer for OpenGLRenderer {
|
|||
);
|
||||
|
||||
gl.gl.VertexAttribPointer(
|
||||
self.imgui_data.locs.uv,
|
||||
self.imgui_data.tex_locs.uv,
|
||||
2,
|
||||
gl::FLOAT,
|
||||
gl::FALSE,
|
||||
|
@ -876,7 +919,7 @@ impl BackendRenderer for OpenGLRenderer {
|
|||
);
|
||||
|
||||
gl.gl.VertexAttribPointer(
|
||||
self.imgui_data.locs.color,
|
||||
self.imgui_data.tex_locs.color,
|
||||
4,
|
||||
gl::UNSIGNED_BYTE,
|
||||
gl::TRUE,
|
||||
|
|
|
@ -222,7 +222,8 @@ impl BossNPC {
|
|||
}
|
||||
113 => {
|
||||
if self.parts[0].shock != 0 {
|
||||
if self.parts[0].action_counter2 / 2 % 2 != 0 {
|
||||
self.parts[0].action_counter2 += 1;
|
||||
if (self.parts[0].action_counter2 / 2) & 1 != 0 {
|
||||
self.parts[0].anim_num = 4;
|
||||
} else {
|
||||
self.parts[0].anim_num = 3;
|
||||
|
@ -309,9 +310,9 @@ impl BossNPC {
|
|||
if self.parts[0].anim_counter > 4 {
|
||||
self.parts[0].action_num = 124;
|
||||
self.parts[0].anim_num = 5;
|
||||
self.parts[0].vel_x = -5 * 0x200;
|
||||
self.parts[0].display_bounds.top = 64 * 0x200;
|
||||
self.parts[0].display_bounds.bottom = 24 * 0x200;
|
||||
self.parts[0].vel_y = -0xa00;
|
||||
self.parts[0].display_bounds.top = 0x8000;
|
||||
self.parts[0].display_bounds.bottom = 0x3000;
|
||||
|
||||
state.sound_manager.play_sfx(25);
|
||||
}
|
||||
|
@ -320,8 +321,8 @@ impl BossNPC {
|
|||
if self.parts[0].flags.hit_bottom_wall() {
|
||||
self.parts[0].action_num = 100;
|
||||
self.parts[0].anim_num = 1;
|
||||
self.parts[0].display_bounds.top = 48 * 0x200;
|
||||
self.parts[0].display_bounds.bottom = 16 * 0x200;
|
||||
self.parts[0].display_bounds.top = 0x6000;
|
||||
self.parts[0].display_bounds.bottom = 0x2000;
|
||||
|
||||
let mut npc = NPC::create(104, &state.npc_table);
|
||||
for _ in 0..2 {
|
||||
|
@ -363,7 +364,7 @@ impl BossNPC {
|
|||
|
||||
if self.parts[0].direction == Direction::Right && self.parts[0].x > player.x {
|
||||
self.parts[0].action_num = 110;
|
||||
self.parts[0].direction = Direction::Right;
|
||||
self.parts[0].direction = Direction::Left;
|
||||
}
|
||||
|
||||
state.sound_manager.play_sfx(26);
|
||||
|
@ -419,7 +420,7 @@ impl BossNPC {
|
|||
}
|
||||
132 => {
|
||||
self.parts[0].action_counter += 1;
|
||||
if (self.parts[0].action_counter / 2 % 2) != 0 {
|
||||
if (self.parts[0].action_counter / 2) & 1 != 0 {
|
||||
self.parts[0].anim_num = 6;
|
||||
self.parts[0].display_bounds = Rect {
|
||||
left: 20 * 0x200,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::io;
|
||||
|
||||
use byteorder::{ReadBytesExt, WriteBytesExt, BE, LE};
|
||||
use byteorder::{BE, LE, ReadBytesExt, WriteBytesExt};
|
||||
use num_traits::{clamp, FromPrimitive};
|
||||
|
||||
use crate::common::{Direction, FadeState};
|
||||
|
@ -43,6 +43,7 @@ pub struct GameProfile {
|
|||
pub weapon_data: [WeaponData; 8],
|
||||
pub items: [u32; 32],
|
||||
pub teleporter_slots: [TeleporterSlotData; 8],
|
||||
pub map_flags: [u8; 128],
|
||||
pub flags: [u8; 1000],
|
||||
}
|
||||
|
||||
|
@ -93,6 +94,10 @@ impl GameProfile {
|
|||
state.teleporter_slots.push((slot.index as u16, slot.event_num as u16));
|
||||
}
|
||||
|
||||
for (idx, &flag) in self.map_flags.iter().enumerate() {
|
||||
state.set_map_flag(idx, flag != 0);
|
||||
}
|
||||
|
||||
for (idx, &flags) in self.flags.iter().enumerate() {
|
||||
if flags & 0b00000001 != 0 {
|
||||
state.game_flags.set(idx * 8, true);
|
||||
|
@ -197,6 +202,15 @@ impl GameProfile {
|
|||
}
|
||||
}
|
||||
|
||||
let mut map_flags = [0u8; 128];
|
||||
for (idx, map_flag) in state.map_flags.iter().enumerate() {
|
||||
if let Some(out) = map_flags.get_mut(idx) {
|
||||
*out = if *map_flag { 1 } else { 0 };
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut bidx = 0;
|
||||
let mut flags = [0u8; 1000];
|
||||
for bits in state.game_flags.as_raw_slice() {
|
||||
|
@ -204,6 +218,8 @@ impl GameProfile {
|
|||
for b in bytes.iter() {
|
||||
if let Some(out) = flags.get_mut(bidx) {
|
||||
*out = *b;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
bidx += 1;
|
||||
}
|
||||
|
@ -226,6 +242,7 @@ impl GameProfile {
|
|||
weapon_data,
|
||||
items,
|
||||
teleporter_slots,
|
||||
map_flags,
|
||||
flags,
|
||||
}
|
||||
}
|
||||
|
@ -333,8 +350,8 @@ impl GameProfile {
|
|||
slot.event_num = data.read_u32::<LE>()?;
|
||||
}
|
||||
|
||||
let mut something = [0u8; 0x80];
|
||||
data.read_exact(&mut something)?;
|
||||
let mut map_flags = [0u8; 0x80];
|
||||
data.read_exact(&mut map_flags)?;
|
||||
|
||||
if data.read_u32::<BE>()? != 0x464c4147 {
|
||||
return Err(ResourceLoadError(str!("Invalid FLAG signature")));
|
||||
|
@ -360,6 +377,7 @@ impl GameProfile {
|
|||
weapon_data,
|
||||
items,
|
||||
teleporter_slots,
|
||||
map_flags,
|
||||
flags,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -20,9 +20,9 @@ use crate::framework::graphics::{BlendMode, draw_rect, FilterMode};
|
|||
use crate::framework::ui::Components;
|
||||
use crate::input::touch_controls::TouchControlType;
|
||||
use crate::inventory::{Inventory, TakeExperienceResult};
|
||||
use crate::npc::{NPC, NPCLayer};
|
||||
use crate::npc::boss::BossNPC;
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::npc::{NPC, NPCLayer};
|
||||
use crate::physics::PhysicalEntity;
|
||||
use crate::player::{Player, TargetPlayer};
|
||||
use crate::rng::XorShift;
|
||||
|
@ -57,6 +57,7 @@ pub struct GameScene {
|
|||
tex_background_name: String,
|
||||
tex_tileset_name: String,
|
||||
map_name_counter: u16,
|
||||
skip_counter: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
|
||||
|
@ -71,6 +72,7 @@ const FACE_TEX: &str = "Face";
|
|||
const SWITCH_FACE_TEX: [&str; 4] = ["Face1", "Face2", "Face3", "Face4"];
|
||||
const P2_LEFT_TEXT: &str = "< P2";
|
||||
const P2_RIGHT_TEXT: &str = "P2 >";
|
||||
const CUTSCENE_SKIP_WAIT: u16 = 50;
|
||||
|
||||
impl GameScene {
|
||||
pub fn new(state: &mut SharedGameState, ctx: &mut Context, id: usize) -> GameResult<Self> {
|
||||
|
@ -112,6 +114,7 @@ impl GameScene {
|
|||
tex_background_name,
|
||||
tex_tileset_name,
|
||||
map_name_counter: 0,
|
||||
skip_counter: 0,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -212,18 +215,19 @@ impl GameScene {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_npc_layer(&self, state: &mut SharedGameState, ctx: &mut Context, layer: NPCLayer) -> GameResult {
|
||||
fn draw_npc_layer(&self, state: &mut SharedGameState, ctx: &mut Context, layer: NPCLayer) -> GameResult {
|
||||
for npc in self.npc_list.iter_alive() {
|
||||
if npc.layer != layer || npc.x < (self.frame.x - 128 * 0x200 - npc.display_bounds.width() as i32 * 0x200)
|
||||
if npc.layer != layer
|
||||
|| npc.x < (self.frame.x - 128 * 0x200 - npc.display_bounds.width() as i32 * 0x200)
|
||||
|| npc.x
|
||||
> (self.frame.x
|
||||
+ 128 * 0x200
|
||||
+ (state.canvas_size.0 as i32 + npc.display_bounds.width() as i32) * 0x200)
|
||||
&& npc.y < (self.frame.y - 128 * 0x200 - npc.display_bounds.height() as i32 * 0x200)
|
||||
> (self.frame.x
|
||||
+ 128 * 0x200
|
||||
+ (state.canvas_size.0 as i32 + npc.display_bounds.width() as i32) * 0x200)
|
||||
&& npc.y < (self.frame.y - 128 * 0x200 - npc.display_bounds.height() as i32 * 0x200)
|
||||
|| npc.y
|
||||
> (self.frame.y
|
||||
+ 128 * 0x200
|
||||
+ (state.canvas_size.1 as i32 + npc.display_bounds.height() as i32) * 0x200)
|
||||
> (self.frame.y
|
||||
+ 128 * 0x200
|
||||
+ (state.canvas_size.1 as i32 + npc.display_bounds.height() as i32) * 0x200)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
@ -412,10 +416,15 @@ impl GameScene {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let (off_left, off_top, off_right, off_bottom) = crate::framework::graphics::screen_insets_scaled(ctx, state.scale);
|
||||
let (off_left, off_top, off_right, off_bottom) =
|
||||
crate::framework::graphics::screen_insets_scaled(ctx, state.scale);
|
||||
|
||||
let center = ((state.canvas_size.0 - off_left - off_right) / 2.0).floor();
|
||||
let top_pos = if state.textscript_vm.flags.position_top() { 32.0 + off_top } else { state.canvas_size.1 as f32 - off_bottom - 66.0 };
|
||||
let top_pos = if state.textscript_vm.flags.position_top() {
|
||||
32.0 + off_top
|
||||
} else {
|
||||
state.canvas_size.1 as f32 - off_bottom - 66.0
|
||||
};
|
||||
let left_pos = off_left + center - 122.0;
|
||||
|
||||
{
|
||||
|
@ -446,7 +455,7 @@ impl GameScene {
|
|||
);
|
||||
batch.add_rect(
|
||||
center + 32.0,
|
||||
state.canvas_size.1 - off_bottom- 104.0,
|
||||
state.canvas_size.1 - off_bottom - 104.0,
|
||||
&state.constants.textscript.get_item_right,
|
||||
);
|
||||
batch.add_rect(
|
||||
|
@ -468,11 +477,7 @@ impl GameScene {
|
|||
state.canvas_size.1 - off_bottom - 96.0
|
||||
};
|
||||
|
||||
batch.add_rect(
|
||||
center + 56.0,
|
||||
pos_y,
|
||||
&state.constants.textscript.textbox_rect_yes_no,
|
||||
);
|
||||
batch.add_rect(center + 56.0, pos_y, &state.constants.textscript.textbox_rect_yes_no);
|
||||
|
||||
if wait == 0 {
|
||||
let pos_x = if selection == ConfirmSelection::No { 41.0 } else { 0.0 };
|
||||
|
@ -617,7 +622,15 @@ impl GameScene {
|
|||
)
|
||||
}
|
||||
|
||||
fn draw_light_raycast(&self, world_point_x: i32, world_point_y: i32, (br, bg, bb): (u8, u8, u8), att: u8, angle: Range<i32>, batch: &mut SizedBatch) {
|
||||
fn draw_light_raycast(
|
||||
&self,
|
||||
world_point_x: i32,
|
||||
world_point_y: i32,
|
||||
(br, bg, bb): (u8, u8, u8),
|
||||
att: u8,
|
||||
angle: Range<i32>,
|
||||
batch: &mut SizedBatch,
|
||||
) {
|
||||
let px = world_point_x as f32 / 512.0;
|
||||
let py = world_point_y as f32 / 512.0;
|
||||
|
||||
|
@ -649,43 +662,43 @@ impl GameScene {
|
|||
&& x >= (bx * 16 - 8) as f32
|
||||
&& x <= (bx * 16 + 8) as f32
|
||||
&& y >= (by * 16 - 8) as f32
|
||||
&& y <= (by * 16 + 8) as f32) ||
|
||||
((tile == 0x50 || tile == 0x70)
|
||||
&& y <= (by * 16 + 8) as f32)
|
||||
|| ((tile == 0x50 || tile == 0x70)
|
||||
&& x >= (bx * 16 - 8) as f32
|
||||
&& x <= (bx * 16 + 8) as f32
|
||||
&& y <= ((by as f32 * 16.0) - (x - bx as f32 * 16.0) / 2.0 + 4.0)
|
||||
&& y >= (by * 16 - 8) as f32) ||
|
||||
((tile == 0x51 || tile == 0x71)
|
||||
&& y >= (by * 16 - 8) as f32)
|
||||
|| ((tile == 0x51 || tile == 0x71)
|
||||
&& x >= (bx * 16 - 8) as f32
|
||||
&& x <= (bx * 16 + 8) as f32
|
||||
&& y <= ((by as f32 * 16.0) - (x - bx as f32 * 16.0) / 2.0 - 4.0)
|
||||
&& y >= (by * 16 - 8) as f32) ||
|
||||
((tile == 0x52 || tile == 0x72)
|
||||
&& y >= (by * 16 - 8) as f32)
|
||||
|| ((tile == 0x52 || tile == 0x72)
|
||||
&& x >= (bx * 16 - 8) as f32
|
||||
&& x <= (bx * 16 + 8) as f32
|
||||
&& y <= ((by as f32 * 16.0) + (x - bx as f32 * 16.0) / 2.0 - 4.0)
|
||||
&& y >= (by * 16 - 8) as f32) ||
|
||||
((tile == 0x53 || tile == 0x73)
|
||||
&& y >= (by * 16 - 8) as f32)
|
||||
|| ((tile == 0x53 || tile == 0x73)
|
||||
&& x >= (bx * 16 - 8) as f32
|
||||
&& x <= (bx * 16 + 8) as f32
|
||||
&& y <= ((by as f32 * 16.0) + (x - bx as f32 * 16.0) / 2.0 + 4.0)
|
||||
&& y >= (by * 16 - 8) as f32) ||
|
||||
((tile == 0x54 || tile == 0x74)
|
||||
&& y >= (by * 16 - 8) as f32)
|
||||
|| ((tile == 0x54 || tile == 0x74)
|
||||
&& x >= (bx * 16 - 8) as f32
|
||||
&& x <= (bx * 16 + 8) as f32
|
||||
&& y >= ((by as f32 * 16.0) + (x - bx as f32 * 16.0) / 2.0 - 4.0)
|
||||
&& y <= (by * 16 + 8) as f32) ||
|
||||
((tile == 0x55 || tile == 0x75)
|
||||
&& y <= (by * 16 + 8) as f32)
|
||||
|| ((tile == 0x55 || tile == 0x75)
|
||||
&& x >= (bx * 16 - 8) as f32
|
||||
&& x <= (bx * 16 + 8) as f32
|
||||
&& y >= ((by as f32 * 16.0) + (x - bx as f32 * 16.0) / 2.0 + 4.0)
|
||||
&& y <= (by * 16 + 8) as f32) ||
|
||||
((tile == 0x56 || tile == 0x76)
|
||||
&& y <= (by * 16 + 8) as f32)
|
||||
|| ((tile == 0x56 || tile == 0x76)
|
||||
&& x >= (bx * 16 - 8) as f32
|
||||
&& x <= (bx * 16 + 8) as f32
|
||||
&& y >= ((by as f32 * 16.0) - (x - bx as f32 * 16.0) / 2.0 + 4.0)
|
||||
&& y <= (by * 16 + 8) as f32) ||
|
||||
((tile == 0x57 || tile == 0x77)
|
||||
&& y <= (by * 16 + 8) as f32)
|
||||
|| ((tile == 0x57 || tile == 0x77)
|
||||
&& x >= (bx * 16 - 8) as f32
|
||||
&& x <= (bx * 16 + 8) as f32
|
||||
&& y >= ((by as f32 * 16.0) - (x - bx as f32 * 16.0) / 2.0 - 4.0)
|
||||
|
@ -724,7 +737,9 @@ impl GameScene {
|
|||
let scale = state.scale;
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "builtin/lightmap/spot")?;
|
||||
|
||||
for (player, inv) in [(&self.player1, &self.inventory_player1), (&self.player2, &self.inventory_player2)].iter() {
|
||||
for (player, inv) in
|
||||
[(&self.player1, &self.inventory_player1), (&self.player2, &self.inventory_player2)].iter()
|
||||
{
|
||||
if player.cond.alive() && !player.cond.hidden() && inv.get_current_weapon().is_some() {
|
||||
let range = match () {
|
||||
_ if player.up => 60..120,
|
||||
|
@ -1496,42 +1511,73 @@ impl Scene for GameScene {
|
|||
}
|
||||
}
|
||||
|
||||
match state.textscript_vm.mode {
|
||||
ScriptMode::Map if state.control_flags.tick_world() => self.tick_world(state)?,
|
||||
ScriptMode::StageSelect => self.stage_select.tick(state, (ctx, &self.player1, &self.player2))?,
|
||||
ScriptMode::Inventory => self.inventory_ui.tick(state, (ctx, &mut self.player1, &mut self.inventory_player1))?,
|
||||
_ => {}
|
||||
match state.textscript_vm.state {
|
||||
TextScriptExecutionState::Running(_, _)
|
||||
| TextScriptExecutionState::WaitTicks(_, _, _)
|
||||
| TextScriptExecutionState::WaitInput(_, _, _)
|
||||
| TextScriptExecutionState::Msg(_, _, _, _)
|
||||
if !state.control_flags.control_enabled() && !state.textscript_vm.flags.cutscene_skip() =>
|
||||
{
|
||||
if self.player1.controller.inventory() {
|
||||
self.skip_counter += 1;
|
||||
if self.skip_counter >= CUTSCENE_SKIP_WAIT {
|
||||
state.textscript_vm.flags.set_cutscene_skip(true);
|
||||
}
|
||||
} else if self.skip_counter > 0 {
|
||||
self.skip_counter -= 1;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.skip_counter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if self.map_name_counter > 0 {
|
||||
self.map_name_counter -= 1;
|
||||
let mut ticks = 1;
|
||||
if state.textscript_vm.mode == ScriptMode::Map && state.textscript_vm.flags.cutscene_skip() {
|
||||
ticks = 4;
|
||||
}
|
||||
|
||||
match state.fade_state {
|
||||
FadeState::FadeOut(tick, direction) if tick < 15 => {
|
||||
state.fade_state = FadeState::FadeOut(tick + 1, direction);
|
||||
for _ in 0..ticks {
|
||||
match state.textscript_vm.mode {
|
||||
ScriptMode::Map if state.control_flags.tick_world() => self.tick_world(state)?,
|
||||
ScriptMode::StageSelect => self.stage_select.tick(state, (ctx, &self.player1, &self.player2))?,
|
||||
ScriptMode::Inventory => {
|
||||
self.inventory_ui.tick(state, (ctx, &mut self.player1, &mut self.inventory_player1))?
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
FadeState::FadeOut(tick, _) if tick == 15 => {
|
||||
state.fade_state = FadeState::Hidden;
|
||||
|
||||
if self.map_name_counter > 0 {
|
||||
self.map_name_counter -= 1;
|
||||
}
|
||||
FadeState::FadeIn(tick, direction) if tick > -15 => {
|
||||
state.fade_state = FadeState::FadeIn(tick - 1, direction);
|
||||
|
||||
match state.fade_state {
|
||||
FadeState::FadeOut(tick, direction) if tick < 15 => {
|
||||
state.fade_state = FadeState::FadeOut(tick + 1, direction);
|
||||
}
|
||||
FadeState::FadeOut(tick, _) if tick == 15 => {
|
||||
state.fade_state = FadeState::Hidden;
|
||||
}
|
||||
FadeState::FadeIn(tick, direction) if tick > -15 => {
|
||||
state.fade_state = FadeState::FadeIn(tick - 1, direction);
|
||||
}
|
||||
FadeState::FadeIn(tick, _) if tick == -15 => {
|
||||
state.fade_state = FadeState::Visible;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
FadeState::FadeIn(tick, _) if tick == -15 => {
|
||||
state.fade_state = FadeState::Visible;
|
||||
|
||||
self.flash.tick(state, ())?;
|
||||
TextScriptVM::run(state, self, ctx)?;
|
||||
|
||||
#[cfg(feature = "scripting")]
|
||||
state.lua.scene_tick(self);
|
||||
|
||||
if state.control_flags.control_enabled() {
|
||||
self.tick = self.tick.wrapping_add(1);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.flash.tick(state, ())?;
|
||||
TextScriptVM::run(state, self, ctx)?;
|
||||
|
||||
#[cfg(feature = "scripting")]
|
||||
state.lua.scene_tick(self);
|
||||
|
||||
if state.control_flags.control_enabled() {
|
||||
self.tick = self.tick.wrapping_add(1);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1692,6 +1738,36 @@ impl Scene for GameScene {
|
|||
}
|
||||
|
||||
self.draw_text_boxes(state, ctx)?;
|
||||
if self.skip_counter > 0 {
|
||||
let text = format!("Hold {:?} to skip the cutscene", state.settings.player1_key_map.inventory);
|
||||
let width = state.font.text_width(text.chars(), &state.constants);
|
||||
let pos_x = state.canvas_size.0 - width - 20.0;
|
||||
let pos_y = 0.0;
|
||||
let line_height = state.font.line_height(&state.constants);
|
||||
let w = (self.skip_counter as f32 / CUTSCENE_SKIP_WAIT as f32) * (width + 20.0) / 2.0;
|
||||
let mut rect = Rect::new_size((pos_x * state.scale) as isize,
|
||||
(pos_y * state.scale) as isize,
|
||||
((20.0 + width) * state.scale) as isize,
|
||||
((20.0 + line_height) * state.scale) as isize);
|
||||
|
||||
draw_rect(ctx, rect, Color::from_rgb(0, 0, 32))?;
|
||||
|
||||
rect.right = rect.left + (w * state.scale) as isize;
|
||||
draw_rect(ctx, rect, Color::from_rgb(160, 181, 222))?;
|
||||
|
||||
rect.left = ((state.canvas_size.0 - w) * state.scale) as isize;
|
||||
rect.right = rect.left + (w * state.scale) as isize;
|
||||
draw_rect(ctx, rect, Color::from_rgb(160, 181, 222))?;
|
||||
|
||||
state.font.draw_text_with_shadow(
|
||||
text.chars(),
|
||||
pos_x + 10.0,
|
||||
pos_y + 10.0,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
|
||||
if state.settings.debug_outlines {
|
||||
self.draw_debug_outlines(state, ctx)?;
|
||||
|
|
|
@ -222,6 +222,7 @@ impl Scene for TitleScene {
|
|||
},
|
||||
CurrentMenu::StartGame => {
|
||||
if self.tick == 10 {
|
||||
state.reset_skip_flags();
|
||||
state.start_new_game(ctx)?;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ doukutsu._handlers = setmetatable({
|
|||
end,
|
||||
})
|
||||
|
||||
doukutsu._initialize_script = function(script)
|
||||
doukutsu._initializeScript = function(script)
|
||||
-- for compatibility with Lua 5.2+, copy-pasted from Lua mailing list
|
||||
-- http://lua-users.org/lists/lua-l/2010-06/msg00313.html
|
||||
local _setfenv = setfenv or function(f, t)
|
||||
|
@ -45,12 +45,12 @@ doukutsu._initialize_script = function(script)
|
|||
script()
|
||||
end
|
||||
|
||||
doukutsu.play_sfx = function(id)
|
||||
__doukutsu:play_sfx(id)
|
||||
doukutsu.playSfx = function(id)
|
||||
__doukutsu:playSfx(id)
|
||||
end
|
||||
|
||||
doukutsu.play_song = function(id)
|
||||
__doukutsu:play_song(id)
|
||||
doukutsu.playSong = function(id)
|
||||
__doukutsu:playSong(id)
|
||||
end
|
||||
|
||||
doukutsu.on = function(event, handler)
|
||||
|
@ -66,7 +66,7 @@ doukutsu.on = function(event, handler)
|
|||
return handler
|
||||
end
|
||||
|
||||
doukutsu.remove_handler = function(event, handler)
|
||||
doukutsu.removeHandler = function(event, handler)
|
||||
assert(type(event) == "string", "event type must be a string.")
|
||||
assert(type(handler) == "function", "event handler must be a function.")
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ impl LuaScriptingState {
|
|||
}
|
||||
|
||||
state.get_global("doukutsu");
|
||||
state.get_field(-1, "_initialize_script");
|
||||
state.get_field(-1, "_initializeScript");
|
||||
state.push_value(-3);
|
||||
|
||||
let res = state.pcall(1, 0, 0);
|
||||
|
|
|
@ -126,10 +126,10 @@ impl LuaObject for LuaPlayer {
|
|||
vec![
|
||||
lua_method!("x", LuaPlayer, LuaPlayer::lua_get_x),
|
||||
lua_method!("y", LuaPlayer, LuaPlayer::lua_get_y),
|
||||
lua_method!("vel_x", LuaPlayer, LuaPlayer::lua_get_vel_x),
|
||||
lua_method!("vel_y", LuaPlayer, LuaPlayer::lua_get_vel_y),
|
||||
lua_method!("set_vel_x", LuaPlayer, LuaPlayer::lua_set_vel_x),
|
||||
lua_method!("set_vel_y", LuaPlayer, LuaPlayer::lua_set_vel_y),
|
||||
lua_method!("velX", LuaPlayer, LuaPlayer::lua_get_vel_x),
|
||||
lua_method!("velX", LuaPlayer, LuaPlayer::lua_get_vel_y),
|
||||
lua_method!("setVelX", LuaPlayer, LuaPlayer::lua_set_vel_x),
|
||||
lua_method!("setVelY", LuaPlayer, LuaPlayer::lua_set_vel_y),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ use crate::stage::StageData;
|
|||
use crate::str;
|
||||
use crate::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
|
||||
use crate::texture_set::TextureSet;
|
||||
use bitvec::array::BitArray;
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
pub enum TimingMode {
|
||||
|
@ -89,6 +90,8 @@ pub struct SharedGameState {
|
|||
pub timing_mode: TimingMode,
|
||||
pub control_flags: ControlFlags,
|
||||
pub game_flags: BitVec,
|
||||
pub skip_flags: BitVec,
|
||||
pub map_flags: BitVec,
|
||||
pub fade_state: FadeState,
|
||||
/// RNG used by game state, using it for anything else might cause unintended side effects and break replays.
|
||||
pub game_rng: XorShift,
|
||||
|
@ -160,6 +163,8 @@ impl SharedGameState {
|
|||
timing_mode: TimingMode::_50Hz,
|
||||
control_flags: ControlFlags(0),
|
||||
game_flags: bitvec::bitvec![0; 8000],
|
||||
skip_flags: bitvec::bitvec![0; 64],
|
||||
map_flags: bitvec::bitvec![0; 64],
|
||||
fade_state: FadeState::Hidden,
|
||||
game_rng: XorShift::new(0),
|
||||
effect_rng: XorShift::new(123),
|
||||
|
@ -233,6 +238,8 @@ impl SharedGameState {
|
|||
next_scene.player1.cond.set_alive(true);
|
||||
next_scene.player1.x = 10 * 16 * 0x200;
|
||||
next_scene.player1.y = 8 * 16 * 0x200;
|
||||
|
||||
self.reset_map_flags();
|
||||
self.fade_state = FadeState::Hidden;
|
||||
self.textscript_vm.state = TextScriptExecutionState::Running(200, 0);
|
||||
|
||||
|
@ -250,6 +257,8 @@ impl SharedGameState {
|
|||
next_scene.player1.x = 3 * 16 * 0x200;
|
||||
next_scene.player1.y = 3 * 16 * 0x200;
|
||||
next_scene.intro_mode = true;
|
||||
|
||||
self.reset_map_flags();
|
||||
self.fade_state = FadeState::Hidden;
|
||||
self.textscript_vm.state = TextScriptExecutionState::Running(100, 0);
|
||||
|
||||
|
@ -364,4 +373,44 @@ impl SharedGameState {
|
|||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_skip_flags(&mut self) {
|
||||
self.skip_flags = bitvec::bitvec![0; 64];
|
||||
}
|
||||
|
||||
pub fn set_skip_flag(&mut self, id: usize, value: bool) {
|
||||
if id < self.skip_flags.len() {
|
||||
self.skip_flags.set(id, value);
|
||||
} else {
|
||||
log::warn!("Attempted to set an out-of-bounds skip flag {}:", id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_skip_flag(&self, id: usize) -> bool {
|
||||
if let Some(flag) = self.skip_flags.get(id) {
|
||||
*flag
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_map_flags(&mut self) {
|
||||
self.map_flags = bitvec::bitvec![0; 128];
|
||||
}
|
||||
|
||||
pub fn set_map_flag(&mut self, id: usize, value: bool) {
|
||||
if id < self.map_flags.len() {
|
||||
self.map_flags.set(id, value);
|
||||
} else {
|
||||
log::warn!("Attempted to set an out-of-bounds map flag {}:", id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_map_flag(&self, id: usize) -> bool {
|
||||
if let Some(flag) = self.map_flags.get(id) {
|
||||
*flag
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -262,16 +262,18 @@ pub enum OpCode {
|
|||
SSS,
|
||||
|
||||
// ---- Cave Story+ specific opcodes ----
|
||||
/// <ACHxxxx, triggers a Steam achievement.
|
||||
/// <ACHxxxx, triggers a Steam achievement. No-op in EGS/Humble Bundle version.
|
||||
ACH,
|
||||
|
||||
// ---- Cave Story+ (Switch) specific opcodes ----
|
||||
/// <HM2, HMC for non-executor player.
|
||||
/// <HM2, HMC only for executor player.
|
||||
HM2,
|
||||
/// <2MVxxxx, Put another player near the player who executed the event.
|
||||
/// 0000 - puts player on left side of executor player
|
||||
/// 0001 - puts player on right side of executor player
|
||||
/// other values - Unknown purpose for now
|
||||
/// 0002-0010 - unused
|
||||
/// 0011.. - the first 3 digits are distance in pixels, the last digit is a flag
|
||||
/// - if it's 1 put the player on right side of the player, otherwise put it on left
|
||||
#[strum(serialize = "2MV")]
|
||||
S2MV,
|
||||
/// <INJxxxx:yyyy:zzzz, Jumps to event zzzz if amount of item xxxx equals yyyy
|
||||
|
@ -279,7 +281,7 @@ pub enum OpCode {
|
|||
/// <I+Nxxxx:yyyy, Adds item xxxx with maximum amount of yyyy
|
||||
#[strum(serialize = "I+N")]
|
||||
IpN,
|
||||
/// <FF-xxxx:yyyy, Set flags in range xxxx-yyyy to false
|
||||
/// <FF-xxxx:yyyy, Sets first flag in range xxxx-yyyy to false
|
||||
#[strum(serialize = "FF-")]
|
||||
FFm,
|
||||
/// <PSHxxxx, Pushes text script state to stack and starts event xxxx
|
||||
|
@ -301,6 +303,7 @@ bitfield! {
|
|||
pub fast, set_fast: 4;
|
||||
pub position_top, set_position_top: 5;
|
||||
pub perma_fast, set_perma_fast: 6;
|
||||
pub cutscene_skip, set_cutscene_skip: 7;
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
|
@ -602,10 +605,7 @@ impl TextScriptVM {
|
|||
}
|
||||
|
||||
if remaining > 1 {
|
||||
let ticks = if state.textscript_vm.flags.fast()
|
||||
|| game_scene.player1.controller.skip()
|
||||
|| game_scene.player2.controller.skip()
|
||||
{
|
||||
let ticks = if state.textscript_vm.flags.fast() || state.textscript_vm.flags.cutscene_skip() {
|
||||
0
|
||||
} else if game_scene.player1.controller.jump()
|
||||
|| game_scene.player1.controller.shoot()
|
||||
|
@ -642,6 +642,8 @@ impl TextScriptVM {
|
|||
}
|
||||
}
|
||||
TextScriptExecutionState::WaitConfirmation(event, ip, no_event, wait, selection) => {
|
||||
state.textscript_vm.flags.set_cutscene_skip(false);
|
||||
|
||||
if wait > 0 {
|
||||
state.textscript_vm.state =
|
||||
TextScriptExecutionState::WaitConfirmation(event, ip, no_event, wait - 1, selection);
|
||||
|
@ -729,12 +731,11 @@ impl TextScriptVM {
|
|||
state.touch_controls.control_type = TouchControlType::Dialog;
|
||||
}
|
||||
|
||||
if game_scene.player1.controller.trigger_jump()
|
||||
if state.textscript_vm.flags.cutscene_skip()
|
||||
|| game_scene.player1.controller.trigger_jump()
|
||||
|| game_scene.player1.controller.trigger_shoot()
|
||||
|| game_scene.player1.controller.skip()
|
||||
|| game_scene.player2.controller.trigger_jump()
|
||||
|| game_scene.player2.controller.trigger_shoot()
|
||||
|| game_scene.player2.controller.skip()
|
||||
{
|
||||
state.textscript_vm.state = TextScriptExecutionState::Running(event, ip);
|
||||
}
|
||||
|
@ -757,7 +758,8 @@ impl TextScriptVM {
|
|||
break;
|
||||
}
|
||||
TextScriptExecutionState::Reset => {
|
||||
state.start_intro(ctx)?;
|
||||
state.reset();
|
||||
state.start_new_game(ctx)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -806,9 +808,11 @@ impl TextScriptVM {
|
|||
}
|
||||
}
|
||||
OpCode::_END => {
|
||||
state.textscript_vm.flags.set_cutscene_skip(false);
|
||||
exec_state = TextScriptExecutionState::Ended;
|
||||
}
|
||||
OpCode::END => {
|
||||
state.textscript_vm.flags.set_cutscene_skip(false);
|
||||
state.control_flags.set_tick_world(true);
|
||||
state.control_flags.set_control_enabled(true);
|
||||
|
||||
|
@ -937,8 +941,13 @@ impl TextScriptVM {
|
|||
exec_state = TextScriptExecutionState::WaitInput(event, cursor.position() as u32, 0);
|
||||
}
|
||||
OpCode::FLp | OpCode::FLm => {
|
||||
let flag_num = read_cur_varint(&mut cursor)? as usize;
|
||||
state.game_flags.set(flag_num, op == OpCode::FLp);
|
||||
let flag_num = read_cur_varint(&mut cursor)? as u16;
|
||||
state.set_flag(flag_num as usize, op == OpCode::FLp);
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
OpCode::SKp | OpCode::SKm => {
|
||||
let flag_num = read_cur_varint(&mut cursor)? as u16;
|
||||
state.set_skip_flag(flag_num as usize, op == OpCode::SKp);
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
OpCode::FFm => {
|
||||
|
@ -966,6 +975,16 @@ impl TextScriptVM {
|
|||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
}
|
||||
OpCode::MPJ => {
|
||||
let event_num = read_cur_varint(&mut cursor)? as u16;
|
||||
|
||||
if state.get_map_flag(game_scene.stage_id) {
|
||||
state.textscript_vm.clear_text_box();
|
||||
exec_state = TextScriptExecutionState::Running(event_num, 0);
|
||||
} else {
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
}
|
||||
OpCode::ITJ => {
|
||||
let item_id = read_cur_varint(&mut cursor)? as u16;
|
||||
let event_num = read_cur_varint(&mut cursor)? as u16;
|
||||
|
@ -1023,6 +1042,17 @@ impl TextScriptVM {
|
|||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
}
|
||||
OpCode::SKJ => {
|
||||
let flag_id = read_cur_varint(&mut cursor)? as u16;
|
||||
let event_num = read_cur_varint(&mut cursor)? as u16;
|
||||
|
||||
if state.get_skip_flag(flag_id as usize) {
|
||||
state.textscript_vm.clear_text_box();
|
||||
exec_state = TextScriptExecutionState::Running(event_num, 0);
|
||||
} else {
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
}
|
||||
OpCode::EVE => {
|
||||
let event_num = read_cur_varint(&mut cursor)? as u16;
|
||||
|
||||
|
@ -1035,7 +1065,7 @@ impl TextScriptVM {
|
|||
let saved_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
state.textscript_vm.stack.push(saved_state);
|
||||
|
||||
// TODO: it's a jump but we don't know if it cleans the textbox yet.
|
||||
state.textscript_vm.clear_text_box();
|
||||
exec_state = TextScriptExecutionState::Running(event_num, 0);
|
||||
}
|
||||
OpCode::POP => {
|
||||
|
@ -1071,8 +1101,8 @@ impl TextScriptVM {
|
|||
npc.x = pos_x as i32 * 16 * 0x200;
|
||||
npc.y = pos_y as i32 * 16 * 0x200;
|
||||
|
||||
game_scene.npc_list.spawn(0x100, npc.clone())?;
|
||||
game_scene.npc_list.spawn(0x100, npc)?;
|
||||
let _ = game_scene.npc_list.spawn(0x100, npc.clone());
|
||||
let _ = game_scene.npc_list.spawn(0x100, npc);
|
||||
}
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
|
@ -1189,8 +1219,10 @@ impl TextScriptVM {
|
|||
new_scene.player2.x = pos_x;
|
||||
new_scene.player2.y = pos_y;
|
||||
|
||||
let skip = state.textscript_vm.flags.cutscene_skip();
|
||||
state.control_flags.set_tick_world(true);
|
||||
state.textscript_vm.flags.0 = 0;
|
||||
state.textscript_vm.flags.set_cutscene_skip(skip);
|
||||
state.textscript_vm.face = 0;
|
||||
state.textscript_vm.item = 0;
|
||||
state.textscript_vm.current_line = TextScriptLine::Line1;
|
||||
|
@ -1231,11 +1263,31 @@ impl TextScriptVM {
|
|||
partner.x = executor.x + if param == 0 { -16 * 0x200 } else { 16 * 0x200 };
|
||||
partner.y = executor.y;
|
||||
}
|
||||
_ => {
|
||||
log::warn!("stub: <2MV unknown param");
|
||||
2..=10 => {
|
||||
log::warn!("<2MV unknown param");
|
||||
}
|
||||
// what the fuck
|
||||
i => {
|
||||
let distance = i as i32 / 10;
|
||||
|
||||
partner.vel_x = 0;
|
||||
partner.vel_y = 0;
|
||||
partner.x =
|
||||
executor.x + if (param % 10) == 1 { distance * 0x200 } else { -distance * 0x200 };
|
||||
partner.y = executor.y;
|
||||
}
|
||||
}
|
||||
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = partner.x;
|
||||
npc.y = partner.y;
|
||||
|
||||
let _ = game_scene.npc_list.spawn(0x100, npc.clone());
|
||||
let _ = game_scene.npc_list.spawn(0x100, npc.clone());
|
||||
let _ = game_scene.npc_list.spawn(0x100, npc.clone());
|
||||
let _ = game_scene.npc_list.spawn(0x100, npc);
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
OpCode::UNI => {
|
||||
|
@ -1499,7 +1551,7 @@ impl TextScriptVM {
|
|||
npc.direction = direction;
|
||||
}
|
||||
|
||||
game_scene.npc_list.spawn(0x100, npc)?;
|
||||
let _ = game_scene.npc_list.spawn(0x100, npc);
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
|
@ -1515,7 +1567,7 @@ impl TextScriptVM {
|
|||
let item_id = read_cur_varint(&mut cursor)? as u16;
|
||||
|
||||
state.sound_manager.play_sfx(38);
|
||||
|
||||
|
||||
if !game_scene.inventory_player1.has_item(item_id) {
|
||||
game_scene.inventory_player1.add_item(item_id);
|
||||
}
|
||||
|
@ -1639,6 +1691,13 @@ impl TextScriptVM {
|
|||
|
||||
exec_state = TextScriptExecutionState::LoadProfile;
|
||||
}
|
||||
OpCode::MPp => {
|
||||
let stage_id = read_cur_varint(&mut cursor)? as u16;
|
||||
|
||||
state.set_map_flag(stage_id as usize, true);
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
// unimplemented opcodes
|
||||
// Zero operands
|
||||
OpCode::CIL
|
||||
|
@ -1656,28 +1715,11 @@ impl TextScriptVM {
|
|||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
// One operand codes
|
||||
OpCode::MPp
|
||||
| OpCode::SKm
|
||||
| OpCode::SKp
|
||||
| OpCode::UNJ
|
||||
| OpCode::MPJ
|
||||
| OpCode::XX1
|
||||
| OpCode::SIL
|
||||
| OpCode::SSS
|
||||
| OpCode::ACH => {
|
||||
OpCode::UNJ | OpCode::XX1 | OpCode::SIL | OpCode::SSS | OpCode::ACH => {
|
||||
let par_a = read_cur_varint(&mut cursor)?;
|
||||
|
||||
log::warn!("unimplemented opcode: {:?} {}", op, par_a);
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
// Two operand codes
|
||||
OpCode::SKJ => {
|
||||
let par_a = read_cur_varint(&mut cursor)?;
|
||||
let par_b = read_cur_varint(&mut cursor)?;
|
||||
|
||||
log::warn!("unimplemented opcode: {:?} {} {}", op, par_a, par_b);
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
}
|
||||
|
@ -1802,7 +1844,7 @@ impl TextScript {
|
|||
) -> GameResult<Vec<u8>> {
|
||||
let mut bytecode = Vec::new();
|
||||
let mut char_buf = Vec::with_capacity(16);
|
||||
let mut allow_next_event = false;
|
||||
let mut allow_next_event = true;
|
||||
|
||||
while let Some(&chr) = iter.peek() {
|
||||
match chr {
|
||||
|
|
Loading…
Reference in a new issue