doukutsu-rs/src/shared_game_state.rs

392 lines
12 KiB
Rust
Raw Normal View History

use std::ops::Div;
2020-11-07 17:17:01 +00:00
use std::time::Instant;
use bitvec::vec::BitVec;
2020-11-07 17:17:01 +00:00
use chrono::{Datelike, Local};
use gfx::{self, *};
use ggez::{Context, filesystem, GameResult, graphics};
use ggez::filesystem::OpenOptions;
use ggez::graphics::{Canvas, Shader};
2020-11-24 23:08:27 +00:00
use num_traits::clamp;
use crate::bmfont_renderer::BMFontRenderer;
use crate::caret::{Caret, CaretType};
use crate::common::{ControlFlags, Direction, FadeState, KeyState};
use crate::engine_constants::EngineConstants;
use crate::npc::{NPC, NPCTable};
use crate::profile::GameProfile;
use crate::rng::RNG;
use crate::scene::game_scene::GameScene;
use crate::scene::Scene;
use crate::sound::SoundManager;
use crate::stage::StageData;
use crate::str;
2020-10-27 01:05:49 +00:00
use crate::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
use crate::texture_set::{TextureSet, g_mag};
2020-10-20 20:45:56 +00:00
use crate::touch_controls::TouchControls;
#[derive(PartialEq, Eq, Copy, Clone)]
pub enum TimingMode {
_50Hz,
_60Hz,
FrameSynchronized,
}
impl TimingMode {
pub fn get_delta(self) -> usize {
match self {
TimingMode::_50Hz => { 1000000000 / 50 }
TimingMode::_60Hz => { 1000000000 / 60 }
TimingMode::FrameSynchronized => { 0 }
}
}
pub fn get_delta_millis(self) -> f64 {
match self {
TimingMode::_50Hz => { 1000.0 / 50.0 }
TimingMode::_60Hz => { 1000.0 / 60.0 }
TimingMode::FrameSynchronized => { 0.0 }
}
}
2020-11-24 23:08:27 +00:00
pub fn get_tps(self) -> usize {
match self {
TimingMode::_50Hz => { 50 }
TimingMode::_60Hz => { 60 }
TimingMode::FrameSynchronized => { 0 }
}
}
}
2020-11-02 01:38:39 +00:00
#[derive(PartialEq, Eq, Copy, Clone)]
pub enum Season {
None,
Halloween,
Christmas,
}
impl Season {
pub fn current() -> Season {
let now = Local::now();
if (now.month() == 10 && now.day() > 25) || (now.month() == 11 && now.day() < 3) {
Season::Halloween
} else if (now.month() == 12 && now.day() > 23) || (now.month() == 0 && now.day() < 7) {
Season::Christmas
} else {
Season::None
}
}
}
2020-09-29 00:43:55 +00:00
pub struct Settings {
pub god_mode: bool,
2020-11-01 19:05:29 +00:00
pub infinite_booster: bool,
pub speed: f64,
2020-11-02 01:38:39 +00:00
pub seasonal_textures: bool,
2020-09-29 00:43:55 +00:00
pub original_textures: bool,
2020-11-07 17:17:01 +00:00
pub shader_effects: bool,
pub motion_interpolation: bool,
2020-09-30 03:11:25 +00:00
pub debug_outlines: bool,
2020-10-20 20:45:56 +00:00
pub touch_controls: bool,
2020-09-29 00:43:55 +00:00
}
2020-11-07 17:17:01 +00:00
gfx_defines! {
constant WaterShaderParams {
resolution: [f32; 2] = "u_Resolution",
t: f32 = "u_Tick",
}
}
pub struct Shaders {
pub water_shader: Shader<WaterShaderParams>,
pub water_shader_params: WaterShaderParams,
}
impl Shaders {
pub fn new(ctx: &mut Context) -> GameResult<Shaders> {
let water_shader_params = WaterShaderParams {
t: 0.0,
resolution: [0.0, 0.0],
};
Ok(Shaders {
water_shader: Shader::new(
ctx,
"/builtin/shaders/basic_150.vert.glsl",
"/builtin/shaders/water_150.frag.glsl",
water_shader_params,
"WaterShaderParams",
None,
)?,
water_shader_params,
})
}
}
pub struct SharedGameState {
pub timing_mode: TimingMode,
pub control_flags: ControlFlags,
pub game_flags: BitVec,
pub fade_state: FadeState,
2020-10-27 01:05:49 +00:00
/// RNG used by game state, using it for anything else might cause unintended side effects and break replays.
pub game_rng: RNG,
2020-10-27 01:05:49 +00:00
/// RNG used by graphics effects that aren't dependent on game's state.
pub effect_rng: RNG,
pub quake_counter: u16,
2020-09-29 20:19:47 +00:00
pub teleporter_slots: Vec<(u16, u16)>,
pub carets: Vec<Caret>,
pub key_state: KeyState,
pub key_trigger: KeyState,
2020-10-20 20:45:56 +00:00
pub touch_controls: TouchControls,
pub base_path: String,
pub npc_table: NPCTable,
2020-10-27 01:05:49 +00:00
pub npc_super_pos: (isize, isize),
pub stages: Vec<StageData>,
pub new_npcs: Vec<NPC>,
pub frame_time: f64,
pub scale: f32,
2020-11-07 17:17:01 +00:00
pub shaders: Shaders,
pub tmp_canvas: Canvas,
pub game_canvas: Canvas,
2020-09-25 19:25:40 +00:00
pub lightmap_canvas: Canvas,
pub canvas_size: (f32, f32),
pub screen_size: (f32, f32),
pub next_scene: Option<Box<dyn Scene>>,
pub textscript_vm: TextScriptVM,
2020-11-02 01:38:39 +00:00
pub season: Season,
pub constants: EngineConstants,
pub font: BMFontRenderer,
pub texture_set: TextureSet,
pub sound_manager: SoundManager,
pub settings: Settings,
pub shutdown: bool,
key_old: u16,
}
impl SharedGameState {
pub fn new(ctx: &mut Context) -> GameResult<SharedGameState> {
let screen_size = graphics::drawable_size(ctx);
2020-11-02 01:38:39 +00:00
let scale = screen_size.1.div(235.0).floor().max(1.0);
unsafe { g_mag = scale };
let canvas_size = (screen_size.0 / scale, screen_size.1 / scale);
let mut constants = EngineConstants::defaults();
let mut base_path = "/";
2020-11-02 01:38:39 +00:00
let settings = SharedGameState::load_settings(ctx)?;
if filesystem::exists(ctx, "/base/Nicalis.bmp") {
info!("Cave Story+ (PC) data files detected.");
constants.apply_csplus_patches();
base_path = "/base/";
} else if filesystem::exists(ctx, "/base/lighting.tbl") {
info!("Cave Story+ (Switch) data files detected.");
constants.apply_csplus_patches();
constants.apply_csplus_nx_patches();
base_path = "/base/";
} else if filesystem::exists(ctx, "/mrmap.bin") {
info!("CSE2E data files detected.");
} else if filesystem::exists(ctx, "/stage.dat") {
info!("NXEngine-evo data files detected.");
}
let font = BMFontRenderer::load(base_path, &constants.font_path, ctx)
.or_else(|_| BMFontRenderer::load("/", "builtin/builtin_font.fnt", ctx))?;
2020-11-02 01:38:39 +00:00
let season = Season::current();
let mut texture_set = TextureSet::new(base_path);
if constants.is_cs_plus {
texture_set.apply_seasonal_content(season, &settings);
}
println!("lookup path: {:#?}", texture_set.paths);
Ok(SharedGameState {
timing_mode: TimingMode::_50Hz,
control_flags: ControlFlags(0),
game_flags: bitvec::bitvec![0; 8000],
fade_state: FadeState::Hidden,
game_rng: RNG::new(0),
effect_rng: RNG::new(Instant::now().elapsed().as_nanos() as i32),
quake_counter: 0,
2020-09-29 20:19:47 +00:00
teleporter_slots: Vec::with_capacity(8),
carets: Vec::with_capacity(32),
key_state: KeyState(0),
key_trigger: KeyState(0),
2020-10-20 20:45:56 +00:00
touch_controls: TouchControls::new(),
base_path: str!(base_path),
npc_table: NPCTable::new(),
2020-10-27 01:05:49 +00:00
npc_super_pos: (0, 0),
stages: Vec::with_capacity(96),
new_npcs: Vec::with_capacity(8),
frame_time: 0.0,
scale,
2020-11-07 17:17:01 +00:00
shaders: Shaders::new(ctx)?,
tmp_canvas: Canvas::with_window_size(ctx)?,
game_canvas: Canvas::with_window_size(ctx)?,
2020-09-25 19:25:40 +00:00
lightmap_canvas: Canvas::with_window_size(ctx)?,
screen_size,
canvas_size,
next_scene: None,
textscript_vm: TextScriptVM::new(),
2020-11-02 01:38:39 +00:00
season,
constants,
font,
texture_set,
sound_manager: SoundManager::new(ctx)?,
settings,
shutdown: false,
2020-11-02 01:38:39 +00:00
key_old: 0,
})
}
fn load_settings(ctx: &mut Context) -> GameResult<Settings> {
Ok(Settings {
god_mode: false,
infinite_booster: false,
speed: 1.0,
seasonal_textures: true,
original_textures: false,
2020-11-07 17:17:01 +00:00
shader_effects: true,
motion_interpolation: true,
2020-11-02 01:38:39 +00:00
debug_outlines: false,
touch_controls: cfg!(target_os = "android"),
})
}
2020-11-02 01:38:39 +00:00
pub fn reload_textures(&mut self) {
let mut texture_set = TextureSet::new(self.base_path.as_str());
if self.constants.is_cs_plus {
texture_set.apply_seasonal_content(self.season, &self.settings);
}
self.texture_set = texture_set;
}
2020-09-22 00:01:55 +00:00
pub fn start_new_game(&mut self, ctx: &mut Context) -> GameResult {
let mut next_scene = GameScene::new(self, ctx, 13)?;
next_scene.player.x = 10 * 16 * 0x200;
next_scene.player.y = 8 * 16 * 0x200;
self.fade_state = FadeState::Hidden;
self.textscript_vm.state = TextScriptExecutionState::Running(200, 0);
self.next_scene = Some(Box::new(next_scene));
Ok(())
}
2020-10-30 08:49:30 +00:00
pub fn start_intro(&mut self, ctx: &mut Context) -> GameResult {
let mut next_scene = GameScene::new(self, ctx, 72)?;
next_scene.player.cond.set_hidden(true);
next_scene.player.x = 3 * 16 * 0x200;
next_scene.player.y = 3 * 16 * 0x200;
next_scene.intro_mode = true;
self.fade_state = FadeState::Hidden;
self.textscript_vm.state = TextScriptExecutionState::Running(100, 0);
self.next_scene = Some(Box::new(next_scene));
Ok(())
}
2020-10-30 01:29:53 +00:00
pub fn save_game(&mut self, game_scene: &mut GameScene, ctx: &mut Context) -> GameResult {
if let Ok(data) = filesystem::open_options(ctx, "/Profile.dat", OpenOptions::new().write(true).create(true)) {
let profile = GameProfile::dump(self, game_scene);
profile.write_save(data)?;
} else {
log::warn!("Cannot open save file.");
}
Ok(())
}
2020-09-22 00:01:55 +00:00
pub fn load_or_start_game(&mut self, ctx: &mut Context) -> GameResult {
2020-10-30 01:29:53 +00:00
if let Ok(data) = filesystem::user_open(ctx, "/Profile.dat") {
2020-09-22 00:01:55 +00:00
match GameProfile::load_from_save(data) {
Ok(profile) => {
self.reset();
let mut next_scene = GameScene::new(self, ctx, profile.current_map as usize)?;
profile.apply(self, &mut next_scene, ctx);
self.next_scene = Some(Box::new(next_scene));
return Ok(());
}
Err(e) => {
log::warn!("Failed to load save game, starting new one: {}", e);
}
}
} else {
log::warn!("No save game found, starting new one...");
}
self.start_new_game(ctx)
}
pub fn reset(&mut self) {
self.control_flags.0 = 0;
self.game_flags = bitvec::bitvec![0; 8000];
self.fade_state = FadeState::Hidden;
self.game_rng = RNG::new(0);
2020-09-29 20:19:47 +00:00
self.teleporter_slots.clear();
self.quake_counter = 0;
self.carets.clear();
self.key_state.0 = 0;
self.key_trigger.0 = 0;
self.key_old = 0;
self.new_npcs.clear();
2020-09-29 20:19:47 +00:00
self.textscript_vm.set_mode(ScriptMode::Map);
self.textscript_vm.suspend = true;
}
pub fn handle_resize(&mut self, ctx: &mut Context) -> GameResult {
self.screen_size = graphics::drawable_size(ctx);
self.scale = self.screen_size.1.div(240.0).floor().max(1.0);
self.canvas_size = (self.screen_size.0 / self.scale, self.screen_size.1 / self.scale);
unsafe { g_mag = self.scale };
graphics::set_screen_coordinates(ctx, graphics::Rect::new(0.0, 0.0, self.screen_size.0, self.screen_size.1))?;
Ok(())
}
pub fn update_key_trigger(&mut self) {
let mut trigger = self.key_state.0 ^ self.key_old;
trigger &= self.key_state.0;
self.key_old = self.key_state.0;
self.key_trigger = KeyState(trigger);
}
pub fn tick_carets(&mut self) {
for caret in self.carets.iter_mut() {
caret.tick(&self.effect_rng, &self.constants);
}
self.carets.retain(|c| !c.is_dead());
}
pub fn create_caret(&mut self, x: isize, y: isize, ctype: CaretType, direct: Direction) {
self.carets.push(Caret::new(x, y, ctype, direct, &self.constants));
}
pub fn set_speed(&mut self, value: f64) {
2020-11-24 23:08:27 +00:00
self.settings.speed = clamp(value, 0.1, 3.0);
self.frame_time = 0.0;
if let Err(err) = self.sound_manager.set_speed(value as f32) {
log::error!("Error while sending a message to sound manager: {}", err);
}
}
2020-11-24 23:08:27 +00:00
pub fn current_tps(&self) -> f64 {
self.timing_mode.get_tps() as f64 * self.settings.speed
}
pub fn shutdown(&mut self) {
self.shutdown = true;
}
}