2022-03-15 01:54:03 +00:00
|
|
|
use std::collections::HashMap;
|
2022-02-27 10:21:43 +00:00
|
|
|
use std::{cmp, ops::Div};
|
2020-09-20 15:27:31 +00:00
|
|
|
|
|
|
|
use bitvec::vec::BitVec;
|
2020-11-07 17:17:01 +00:00
|
|
|
use chrono::{Datelike, Local};
|
2020-09-20 15:27:31 +00:00
|
|
|
|
|
|
|
use crate::bmfont_renderer::BMFontRenderer;
|
|
|
|
use crate::caret::{Caret, CaretType};
|
2020-11-28 19:25:51 +00:00
|
|
|
use crate::common::{ControlFlags, Direction, FadeState};
|
2022-02-25 22:00:14 +00:00
|
|
|
use crate::components::draw_common::{draw_number, Alignment};
|
2020-09-20 15:27:31 +00:00
|
|
|
use crate::engine_constants::EngineConstants;
|
2021-02-10 20:14:09 +00:00
|
|
|
use crate::framework::backend::BackendTexture;
|
2021-01-27 18:20:47 +00:00
|
|
|
use crate::framework::context::Context;
|
|
|
|
use crate::framework::error::GameResult;
|
2021-02-10 20:14:09 +00:00
|
|
|
use crate::framework::graphics::{create_texture_mutable, set_render_target};
|
|
|
|
use crate::framework::keyboard::ScanCode;
|
2021-01-27 18:20:47 +00:00
|
|
|
use crate::framework::vfs::OpenOptions;
|
2022-02-25 22:00:14 +00:00
|
|
|
use crate::framework::{filesystem, graphics};
|
2021-03-29 21:19:07 +00:00
|
|
|
#[cfg(feature = "hooks")]
|
|
|
|
use crate::hooks::init_hooks;
|
2022-03-15 01:54:03 +00:00
|
|
|
use crate::i18n::Locale;
|
2020-11-28 19:25:51 +00:00
|
|
|
use crate::input::touch_controls::TouchControls;
|
2022-02-10 09:21:28 +00:00
|
|
|
use crate::mod_list::ModList;
|
2022-03-15 22:18:25 +00:00
|
|
|
use crate::mod_requirements::ModRequirements;
|
2020-12-25 22:39:41 +00:00
|
|
|
use crate::npc::NPCTable;
|
2020-09-22 21:28:36 +00:00
|
|
|
use crate::profile::GameProfile;
|
2020-12-25 22:39:41 +00:00
|
|
|
use crate::rng::XorShift;
|
2020-09-22 21:28:36 +00:00
|
|
|
use crate::scene::game_scene::GameScene;
|
2022-02-10 07:54:20 +00:00
|
|
|
use crate::scene::title_scene::TitleScene;
|
2022-02-25 22:00:14 +00:00
|
|
|
use crate::scene::Scene;
|
2021-10-15 14:36:05 +00:00
|
|
|
#[cfg(feature = "scripting-lua")]
|
|
|
|
use crate::scripting::lua::LuaScriptingState;
|
2022-02-10 07:54:20 +00:00
|
|
|
use crate::scripting::tsc::credit_script::{CreditScript, CreditScriptVM};
|
|
|
|
use crate::scripting::tsc::text_script::{ScriptMode, TextScript, TextScriptExecutionState, TextScriptVM};
|
2020-11-28 19:25:51 +00:00
|
|
|
use crate::settings::Settings;
|
2020-09-20 15:27:31 +00:00
|
|
|
use crate::sound::SoundManager;
|
|
|
|
use crate::stage::StageData;
|
2021-01-27 18:20:47 +00:00
|
|
|
use crate::texture_set::TextureSet;
|
2020-09-22 21:28:36 +00:00
|
|
|
|
2021-10-14 07:43:17 +00:00
|
|
|
#[derive(PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
2020-09-22 21:28:36 +00:00
|
|
|
pub enum TimingMode {
|
|
|
|
_50Hz,
|
|
|
|
_60Hz,
|
|
|
|
FrameSynchronized,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TimingMode {
|
|
|
|
pub fn get_delta(self) -> usize {
|
|
|
|
match self {
|
2021-02-10 20:14:09 +00:00
|
|
|
TimingMode::_50Hz => 1000000000 / 50,
|
|
|
|
TimingMode::_60Hz => 1000000000 / 60,
|
|
|
|
TimingMode::FrameSynchronized => 0,
|
2020-09-22 21:28:36 +00:00
|
|
|
}
|
|
|
|
}
|
2020-10-30 22:47:29 +00:00
|
|
|
|
|
|
|
pub fn get_delta_millis(self) -> f64 {
|
|
|
|
match self {
|
2021-02-10 20:14:09 +00:00
|
|
|
TimingMode::_50Hz => 1000.0 / 50.0,
|
|
|
|
TimingMode::_60Hz => 1000.0 / 60.0,
|
|
|
|
TimingMode::FrameSynchronized => 0.0,
|
2020-10-30 22:47:29 +00:00
|
|
|
}
|
|
|
|
}
|
2020-11-24 23:08:27 +00:00
|
|
|
|
|
|
|
pub fn get_tps(self) -> usize {
|
|
|
|
match self {
|
2021-02-10 20:14:09 +00:00
|
|
|
TimingMode::_50Hz => 50,
|
|
|
|
TimingMode::_60Hz => 60,
|
|
|
|
TimingMode::FrameSynchronized => 0,
|
2020-11-24 23:08:27 +00:00
|
|
|
}
|
|
|
|
}
|
2020-09-22 21:28:36 +00:00
|
|
|
}
|
2020-09-20 15:27:31 +00:00
|
|
|
|
2022-02-27 15:27:34 +00:00
|
|
|
#[derive(PartialEq, Eq, Copy, Clone, num_derive::FromPrimitive)]
|
2022-02-27 10:21:43 +00:00
|
|
|
pub enum GameDifficulty {
|
2022-02-27 15:27:34 +00:00
|
|
|
Normal = 0,
|
|
|
|
Easy = 2,
|
|
|
|
Hard = 4,
|
2022-02-27 10:21:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl GameDifficulty {
|
2022-02-27 15:27:34 +00:00
|
|
|
pub fn from_primitive(val: u8) -> GameDifficulty {
|
|
|
|
return num_traits::FromPrimitive::from_u8(val).unwrap_or(GameDifficulty::Normal);
|
2022-02-27 10:21:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-15 01:54:03 +00:00
|
|
|
#[derive(PartialEq, Eq, Copy, Clone, Hash, num_derive::FromPrimitive, serde::Serialize, serde::Deserialize)]
|
|
|
|
pub enum Language {
|
|
|
|
English,
|
|
|
|
Japanese,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Language {
|
|
|
|
pub fn to_language_code(self) -> &'static str {
|
|
|
|
match self {
|
|
|
|
Language::English => "en",
|
|
|
|
Language::Japanese => "jp",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn to_string(self) -> String {
|
|
|
|
match self {
|
|
|
|
Language::English => "English".to_string(),
|
|
|
|
Language::Japanese => "Japanese".to_string(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn font(self) -> FontData {
|
|
|
|
match self {
|
|
|
|
Language::English => FontData::new("csfont.fnt".to_owned(), 0.5, 0.0),
|
|
|
|
// TODO: implement JP font rendering
|
|
|
|
Language::Japanese => FontData::new("0.fnt".to_owned(), 1.0, 0.0),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn from_primitive(val: usize) -> Language {
|
|
|
|
return num_traits::FromPrimitive::from_usize(val).unwrap_or(Language::English);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn values() -> Vec<Language> {
|
|
|
|
vec![Language::English, Language::Japanese]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct FontData {
|
|
|
|
pub path: String,
|
|
|
|
pub scale: f32,
|
|
|
|
pub space_offset: f32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FontData {
|
|
|
|
pub fn new(path: String, scale: f32, space_offset: f32) -> FontData {
|
|
|
|
FontData { path, scale, space_offset }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-26 01:37:45 +00:00
|
|
|
pub struct Fps {
|
|
|
|
pub frame_count: u32,
|
|
|
|
pub fps: u32,
|
|
|
|
last_capture: u128,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Fps {
|
|
|
|
pub fn new() -> Fps {
|
|
|
|
Fps { frame_count: 0, fps: 0, last_capture: 0 }
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn act(&mut self, state: &mut SharedGameState, ctx: &mut Context, time: u128) -> GameResult {
|
|
|
|
if time - self.last_capture > 1000000000 {
|
|
|
|
self.fps = self.frame_count;
|
|
|
|
self.frame_count = 0;
|
|
|
|
self.last_capture = time;
|
|
|
|
} else {
|
|
|
|
self.frame_count += 1;
|
|
|
|
}
|
|
|
|
draw_number(state.canvas_size.0 - 8.0, 8.0, self.fps as usize, Alignment::Right, state, ctx)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-02 01:38:39 +00:00
|
|
|
#[derive(PartialEq, Eq, Copy, Clone)]
|
|
|
|
pub enum Season {
|
|
|
|
None,
|
|
|
|
Halloween,
|
|
|
|
Christmas,
|
2022-03-13 19:58:22 +00:00
|
|
|
PixelBirthday,
|
2020-11-02 01:38:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2022-03-13 22:06:08 +00:00
|
|
|
} else if now.month() == 4 && now.day() == 29 {
|
2022-03-13 19:58:22 +00:00
|
|
|
Season::PixelBirthday
|
2020-11-02 01:38:39 +00:00
|
|
|
} else {
|
|
|
|
Season::None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-28 21:17:00 +00:00
|
|
|
#[derive(PartialEq, Eq, Copy, Clone)]
|
|
|
|
pub enum MenuCharacter {
|
|
|
|
Quote,
|
|
|
|
Curly,
|
|
|
|
Toroko,
|
|
|
|
King,
|
|
|
|
Sue,
|
|
|
|
}
|
|
|
|
|
2022-03-04 23:37:25 +00:00
|
|
|
#[derive(PartialEq, Eq, Copy, Clone)]
|
|
|
|
pub enum ReplayState {
|
|
|
|
None,
|
|
|
|
Recording,
|
|
|
|
Playback,
|
|
|
|
}
|
|
|
|
|
2021-06-27 01:06:56 +00:00
|
|
|
#[derive(PartialEq, Eq, Copy, Clone)]
|
|
|
|
pub enum TileSize {
|
|
|
|
Tile8x8,
|
|
|
|
Tile16x16,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TileSize {
|
|
|
|
pub const fn as_float(self) -> f32 {
|
|
|
|
match self {
|
|
|
|
TileSize::Tile8x8 => 8.0,
|
|
|
|
TileSize::Tile16x16 => 16.0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub const fn as_int(self) -> i32 {
|
|
|
|
match self {
|
|
|
|
TileSize::Tile8x8 => 8,
|
|
|
|
TileSize::Tile16x16 => 16,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-20 15:27:31 +00:00
|
|
|
pub struct SharedGameState {
|
|
|
|
pub control_flags: ControlFlags,
|
|
|
|
pub game_flags: BitVec,
|
2021-05-02 00:09:54 +00:00
|
|
|
pub skip_flags: BitVec,
|
2021-05-02 02:04:52 +00:00
|
|
|
pub map_flags: BitVec,
|
2020-09-20 15:27:31 +00:00
|
|
|
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.
|
2020-12-25 22:39:41 +00:00
|
|
|
pub game_rng: XorShift,
|
2020-10-27 01:05:49 +00:00
|
|
|
/// RNG used by graphics effects that aren't dependent on game's state.
|
2020-12-25 22:39:41 +00:00
|
|
|
pub effect_rng: XorShift,
|
2021-06-27 01:06:56 +00:00
|
|
|
pub tile_size: TileSize,
|
2020-09-20 15:27:31 +00:00
|
|
|
pub quake_counter: u16,
|
2022-01-13 16:32:33 +00:00
|
|
|
pub super_quake_counter: u16,
|
2020-09-29 20:19:47 +00:00
|
|
|
pub teleporter_slots: Vec<(u16, u16)>,
|
2020-09-20 15:27:31 +00:00
|
|
|
pub carets: Vec<Caret>,
|
2020-10-20 20:45:56 +00:00
|
|
|
pub touch_controls: TouchControls,
|
2022-02-10 07:54:20 +00:00
|
|
|
pub mod_path: Option<String>,
|
2022-02-10 09:21:28 +00:00
|
|
|
pub mod_list: ModList,
|
2020-09-20 15:27:31 +00:00
|
|
|
pub npc_table: NPCTable,
|
2021-01-01 01:46:01 +00:00
|
|
|
pub npc_super_pos: (i32, i32),
|
2021-05-05 12:04:59 +00:00
|
|
|
pub npc_curly_target: (i32, i32),
|
|
|
|
pub npc_curly_counter: u16,
|
2021-10-08 02:41:31 +00:00
|
|
|
pub water_level: i32,
|
2020-09-20 15:27:31 +00:00
|
|
|
pub stages: Vec<StageData>,
|
2020-10-30 22:47:29 +00:00
|
|
|
pub frame_time: f64,
|
2021-02-10 11:53:49 +00:00
|
|
|
pub debugger: bool,
|
2020-09-20 15:27:31 +00:00
|
|
|
pub scale: f32,
|
|
|
|
pub canvas_size: (f32, f32),
|
|
|
|
pub screen_size: (f32, f32),
|
2021-06-28 11:07:20 +00:00
|
|
|
pub preferred_viewport_size: (f32, f32),
|
2020-09-20 15:27:31 +00:00
|
|
|
pub next_scene: Option<Box<dyn Scene>>,
|
|
|
|
pub textscript_vm: TextScriptVM,
|
2021-10-16 02:37:42 +00:00
|
|
|
pub creditscript_vm: CreditScriptVM,
|
2021-02-05 09:47:30 +00:00
|
|
|
pub lightmap_canvas: Option<Box<dyn BackendTexture>>,
|
2020-11-02 01:38:39 +00:00
|
|
|
pub season: Season,
|
2022-01-28 21:17:00 +00:00
|
|
|
pub menu_character: MenuCharacter,
|
2020-11-02 01:38:39 +00:00
|
|
|
pub constants: EngineConstants,
|
|
|
|
pub font: BMFontRenderer,
|
|
|
|
pub texture_set: TextureSet,
|
2021-10-15 14:36:05 +00:00
|
|
|
#[cfg(feature = "scripting-lua")]
|
2021-01-01 01:46:01 +00:00
|
|
|
pub lua: LuaScriptingState,
|
2020-11-02 01:38:39 +00:00
|
|
|
pub sound_manager: SoundManager,
|
|
|
|
pub settings: Settings,
|
2022-02-06 17:23:24 +00:00
|
|
|
pub save_slot: usize,
|
2022-02-27 10:21:43 +00:00
|
|
|
pub difficulty: GameDifficulty,
|
2022-03-04 23:37:25 +00:00
|
|
|
pub replay_state: ReplayState,
|
2022-03-15 22:18:25 +00:00
|
|
|
pub mod_requirements: ModRequirements,
|
2021-02-10 20:14:09 +00:00
|
|
|
pub shutdown: bool,
|
2020-09-20 15:27:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl SharedGameState {
|
|
|
|
pub fn new(ctx: &mut Context) -> GameResult<SharedGameState> {
|
|
|
|
let mut constants = EngineConstants::defaults();
|
2021-06-20 22:42:10 +00:00
|
|
|
let sound_manager = SoundManager::new(ctx)?;
|
2020-11-28 19:25:51 +00:00
|
|
|
let settings = Settings::load(ctx)?;
|
2022-03-15 22:18:25 +00:00
|
|
|
let mod_requirements = ModRequirements::load(ctx)?;
|
2020-09-20 15:27:31 +00:00
|
|
|
|
2022-03-15 01:54:03 +00:00
|
|
|
constants.load_locales(ctx)?;
|
|
|
|
|
2022-01-22 01:19:18 +00:00
|
|
|
if filesystem::exists(ctx, "/base/lighting.tbl") {
|
2020-09-20 15:27:31 +00:00
|
|
|
info!("Cave Story+ (Switch) data files detected.");
|
2021-10-16 12:59:27 +00:00
|
|
|
ctx.size_hint = (854, 480);
|
2021-06-20 22:42:10 +00:00
|
|
|
constants.apply_csplus_patches(&sound_manager);
|
2020-09-20 15:27:31 +00:00
|
|
|
constants.apply_csplus_nx_patches();
|
2022-02-13 19:39:28 +00:00
|
|
|
constants.load_nx_stringtable(ctx)?;
|
2022-03-11 22:13:11 +00:00
|
|
|
} else if filesystem::exists(ctx, "/base/ogph/SellScreen.bmp") {
|
|
|
|
error!("WiiWare DEMO data files detected. !UNSUPPORTED!"); //Missing credits.tsc and crashes due to missing Stage 13 (Start)
|
|
|
|
} else if filesystem::exists(ctx, "/base/strap_a_en.bmp") {
|
|
|
|
info!("WiiWare data files detected."); //Missing Challenges and Remastered Soundtrack but identical to CS+ PC otherwise
|
|
|
|
constants.apply_csplus_patches(&sound_manager);
|
|
|
|
} else if filesystem::exists(ctx, "/root/buid_time.txt") {
|
|
|
|
error!("DSiWare data files detected. !UNSUPPORTED!"); //Freeware 2.0, sprites are arranged VERY differently + separate drowned carets
|
|
|
|
} else if filesystem::exists(ctx, "/darken.tex") || filesystem::exists(ctx, "/darken.png") {
|
|
|
|
error!("EShop data files detected. !UNSUPPORTED!"); //Ditto, drowned carets finally part of mychar, the turning point towards CS+
|
|
|
|
} else if filesystem::exists(ctx, "/data/stage3d/") {
|
|
|
|
error!("CS3D data files detected. !UNSUPPORTED!"); //Sprites are technically all there but filenames differ, + no n3ddta support
|
2022-01-22 01:19:18 +00:00
|
|
|
} else if filesystem::exists(ctx, "/base/Nicalis.bmp") || filesystem::exists(ctx, "/base/Nicalis.png") {
|
|
|
|
info!("Cave Story+ (PC) data files detected.");
|
|
|
|
constants.apply_csplus_patches(&sound_manager);
|
2020-09-20 15:27:31 +00:00
|
|
|
} 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.");
|
|
|
|
}
|
|
|
|
|
2022-03-07 13:47:37 +00:00
|
|
|
for soundtrack in constants.soundtracks.iter_mut() {
|
|
|
|
if filesystem::exists(ctx, &soundtrack.path) {
|
|
|
|
info!("Enabling soundtrack {} from {}.", soundtrack.name, soundtrack.path);
|
|
|
|
soundtrack.available = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-02 01:38:39 +00:00
|
|
|
let season = Season::current();
|
2022-02-10 07:54:20 +00:00
|
|
|
constants.rebuild_path_list(None, season, &settings);
|
2020-11-02 01:38:39 +00:00
|
|
|
|
2022-03-15 01:54:03 +00:00
|
|
|
let active_locale = constants.locales.get(&settings.locale.to_string()).unwrap();
|
|
|
|
|
|
|
|
if constants.is_cs_plus {
|
|
|
|
constants.font_scale = active_locale.font.scale;
|
|
|
|
}
|
|
|
|
|
|
|
|
let font = BMFontRenderer::load(&constants.base_paths, &active_locale.font.path, ctx).or_else(|e| {
|
2022-02-10 09:21:28 +00:00
|
|
|
log::warn!("Failed to load font, using built-in: {}", e);
|
|
|
|
BMFontRenderer::load(&vec!["/".to_owned()], "/builtin/builtin_font.fnt", ctx)
|
|
|
|
})?;
|
|
|
|
|
2022-03-26 09:21:08 +00:00
|
|
|
let mod_list = ModList::load(ctx, &constants.string_table)?;
|
2020-11-02 01:38:39 +00:00
|
|
|
|
2021-06-20 22:42:10 +00:00
|
|
|
for i in 0..0xffu8 {
|
|
|
|
let path = format!("/pxt/fx{:02x}.pxt", i);
|
2022-02-10 07:54:20 +00:00
|
|
|
if let Ok(file) = filesystem::open_find(ctx, &constants.base_paths, path) {
|
2021-06-20 22:42:10 +00:00
|
|
|
sound_manager.set_sample_params_from_file(i, file)?;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
let path = format!("/PixTone/{:03}.pxt", i);
|
2022-02-10 07:54:20 +00:00
|
|
|
if let Ok(file) = filesystem::open_find(ctx, &constants.base_paths, path) {
|
2021-06-20 22:42:10 +00:00
|
|
|
sound_manager.set_sample_params_from_file(i, file)?;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-03 03:09:29 +00:00
|
|
|
sound_manager.set_song_volume(settings.bgm_volume);
|
|
|
|
sound_manager.set_sfx_volume(settings.sfx_volume);
|
|
|
|
|
2021-03-29 21:19:07 +00:00
|
|
|
#[cfg(feature = "hooks")]
|
|
|
|
init_hooks();
|
|
|
|
|
2020-09-20 15:27:31 +00:00
|
|
|
Ok(SharedGameState {
|
|
|
|
control_flags: ControlFlags(0),
|
|
|
|
game_flags: bitvec::bitvec![0; 8000],
|
2021-05-02 00:09:54 +00:00
|
|
|
skip_flags: bitvec::bitvec![0; 64],
|
2021-05-02 02:04:52 +00:00
|
|
|
map_flags: bitvec::bitvec![0; 64],
|
2020-09-20 15:27:31 +00:00
|
|
|
fade_state: FadeState::Hidden,
|
2020-12-25 22:39:41 +00:00
|
|
|
game_rng: XorShift::new(0),
|
2021-01-27 18:20:47 +00:00
|
|
|
effect_rng: XorShift::new(123),
|
2021-06-27 01:06:56 +00:00
|
|
|
tile_size: TileSize::Tile16x16,
|
2020-09-20 15:27:31 +00:00
|
|
|
quake_counter: 0,
|
2022-01-13 16:32:33 +00:00
|
|
|
super_quake_counter: 0,
|
2020-09-29 20:19:47 +00:00
|
|
|
teleporter_slots: Vec::with_capacity(8),
|
2020-09-20 15:27:31 +00:00
|
|
|
carets: Vec::with_capacity(32),
|
2020-10-20 20:45:56 +00:00
|
|
|
touch_controls: TouchControls::new(),
|
2022-02-10 07:54:20 +00:00
|
|
|
mod_path: None,
|
2022-02-10 09:21:28 +00:00
|
|
|
mod_list,
|
2020-09-20 15:27:31 +00:00
|
|
|
npc_table: NPCTable::new(),
|
2020-10-27 01:05:49 +00:00
|
|
|
npc_super_pos: (0, 0),
|
2021-05-05 12:04:59 +00:00
|
|
|
npc_curly_target: (0, 0),
|
|
|
|
npc_curly_counter: 0,
|
2021-10-08 02:41:31 +00:00
|
|
|
water_level: 0,
|
2020-09-20 15:27:31 +00:00
|
|
|
stages: Vec::with_capacity(96),
|
2020-10-30 22:47:29 +00:00
|
|
|
frame_time: 0.0,
|
2021-02-10 11:53:49 +00:00
|
|
|
debugger: false,
|
2021-01-28 22:33:43 +00:00
|
|
|
scale: 2.0,
|
|
|
|
screen_size: (640.0, 480.0),
|
|
|
|
canvas_size: (320.0, 240.0),
|
2021-06-28 11:07:20 +00:00
|
|
|
preferred_viewport_size: (320.0, 240.0),
|
2020-09-20 15:27:31 +00:00
|
|
|
next_scene: None,
|
|
|
|
textscript_vm: TextScriptVM::new(),
|
2021-10-16 02:37:42 +00:00
|
|
|
creditscript_vm: CreditScriptVM::new(),
|
2021-02-05 09:47:30 +00:00
|
|
|
lightmap_canvas: None,
|
2020-11-02 01:38:39 +00:00
|
|
|
season,
|
2022-01-28 21:17:00 +00:00
|
|
|
menu_character: MenuCharacter::Quote,
|
2020-11-02 01:38:39 +00:00
|
|
|
constants,
|
|
|
|
font,
|
2022-02-10 07:54:20 +00:00
|
|
|
texture_set: TextureSet::new(),
|
2021-10-15 14:36:05 +00:00
|
|
|
#[cfg(feature = "scripting-lua")]
|
2021-01-01 01:46:01 +00:00
|
|
|
lua: LuaScriptingState::new(),
|
2021-06-20 22:42:10 +00:00
|
|
|
sound_manager,
|
2020-11-02 01:38:39 +00:00
|
|
|
settings,
|
2022-02-06 17:23:24 +00:00
|
|
|
save_slot: 1,
|
2022-02-27 10:21:43 +00:00
|
|
|
difficulty: GameDifficulty::Normal,
|
2022-03-04 23:37:25 +00:00
|
|
|
replay_state: ReplayState::None,
|
2022-03-15 22:18:25 +00:00
|
|
|
mod_requirements,
|
2020-09-20 15:27:31 +00:00
|
|
|
shutdown: false,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-02-10 11:53:49 +00:00
|
|
|
pub fn process_debug_keys(&mut self, key_code: ScanCode) {
|
|
|
|
match key_code {
|
2021-02-10 20:14:09 +00:00
|
|
|
ScanCode::F3 => self.settings.god_mode = !self.settings.god_mode,
|
|
|
|
ScanCode::F4 => self.settings.infinite_booster = !self.settings.infinite_booster,
|
|
|
|
ScanCode::F5 => self.settings.subpixel_coords = !self.settings.subpixel_coords,
|
2021-03-29 21:19:07 +00:00
|
|
|
ScanCode::F6 => self.settings.motion_interpolation = !self.settings.motion_interpolation,
|
2021-02-10 20:14:09 +00:00
|
|
|
ScanCode::F7 => self.set_speed(1.0),
|
2021-02-10 11:53:49 +00:00
|
|
|
ScanCode::F8 => {
|
|
|
|
if self.settings.speed > 0.2 {
|
|
|
|
self.set_speed(self.settings.speed - 0.1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ScanCode::F9 => {
|
|
|
|
if self.settings.speed < 3.0 {
|
|
|
|
self.set_speed(self.settings.speed + 0.1);
|
|
|
|
}
|
|
|
|
}
|
2021-02-10 20:14:09 +00:00
|
|
|
ScanCode::F10 => self.settings.debug_outlines = !self.settings.debug_outlines,
|
2022-01-26 01:37:45 +00:00
|
|
|
ScanCode::F11 => self.settings.fps_counter = !self.settings.fps_counter,
|
2021-04-19 19:13:18 +00:00
|
|
|
ScanCode::F12 => self.debugger = !self.debugger,
|
2021-02-10 11:53:49 +00:00
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-10 07:54:20 +00:00
|
|
|
pub fn reload_resources(&mut self, ctx: &mut Context) -> GameResult {
|
|
|
|
self.constants.rebuild_path_list(self.mod_path.clone(), self.season, &self.settings);
|
2022-02-12 09:12:30 +00:00
|
|
|
self.constants.special_treatment_for_csplus_mods(self.mod_path.as_ref());
|
2022-02-10 07:54:20 +00:00
|
|
|
self.constants.load_csplus_tables(ctx)?;
|
|
|
|
self.constants.load_animated_faces(ctx)?;
|
2022-02-26 19:07:35 +00:00
|
|
|
self.constants.load_texture_size_hints(ctx)?;
|
2022-03-15 01:54:03 +00:00
|
|
|
let stages = StageData::load_stage_table(ctx, &self.constants.base_paths, self.constants.is_switch)?;
|
2022-02-10 07:54:20 +00:00
|
|
|
self.stages = stages;
|
2020-11-02 01:38:39 +00:00
|
|
|
|
2022-02-10 07:54:20 +00:00
|
|
|
let npc_tbl = filesystem::open_find(ctx, &self.constants.base_paths, "/npc.tbl")?;
|
|
|
|
let npc_table = NPCTable::load_from(npc_tbl)?;
|
|
|
|
self.npc_table = npc_table;
|
|
|
|
|
|
|
|
let head_tsc = filesystem::open_find(ctx, &self.constants.base_paths, "/Head.tsc")?;
|
|
|
|
let head_script = TextScript::load_from(head_tsc, &self.constants)?;
|
|
|
|
self.textscript_vm.set_global_script(head_script);
|
|
|
|
|
|
|
|
let arms_item_tsc = filesystem::open_find(ctx, &self.constants.base_paths, "/ArmsItem.tsc")?;
|
|
|
|
let arms_item_script = TextScript::load_from(arms_item_tsc, &self.constants)?;
|
|
|
|
self.textscript_vm.set_inventory_script(arms_item_script);
|
|
|
|
|
|
|
|
let stage_select_tsc = filesystem::open_find(ctx, &self.constants.base_paths, "/StageSelect.tsc")?;
|
|
|
|
let stage_select_script = TextScript::load_from(stage_select_tsc, &self.constants)?;
|
|
|
|
self.textscript_vm.set_stage_select_script(stage_select_script);
|
|
|
|
|
|
|
|
let credit_tsc = filesystem::open_find(ctx, &self.constants.base_paths, "/Credit.tsc")?;
|
|
|
|
let credit_script = CreditScript::load_from(credit_tsc, &self.constants)?;
|
|
|
|
self.creditscript_vm.set_script(credit_script);
|
|
|
|
|
|
|
|
self.texture_set.unload_all();
|
|
|
|
|
2022-03-06 22:49:25 +00:00
|
|
|
self.sound_manager.load_custom_sound_effects(ctx, &self.constants.base_paths)?;
|
|
|
|
|
2022-02-10 07:54:20 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
2020-11-02 01:38:39 +00:00
|
|
|
|
2022-02-10 07:54:20 +00:00
|
|
|
pub fn reload_graphics(&mut self) {
|
|
|
|
self.constants.rebuild_path_list(self.mod_path.clone(), self.season, &self.settings);
|
|
|
|
self.texture_set.unload_all();
|
2020-11-02 01:38:39 +00:00
|
|
|
}
|
|
|
|
|
2022-03-15 01:54:03 +00:00
|
|
|
pub fn reload_fonts(&mut self, ctx: &mut Context) {
|
|
|
|
let active_locale = self.get_active_locale();
|
|
|
|
|
|
|
|
let font = BMFontRenderer::load(&self.constants.base_paths, &active_locale.font.path, ctx)
|
|
|
|
.or_else(|e| {
|
|
|
|
log::warn!("Failed to load font, using built-in: {}", e);
|
|
|
|
BMFontRenderer::load(&vec!["/".to_owned()], "/builtin/builtin_font.fnt", ctx)
|
|
|
|
})
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
if self.constants.is_cs_plus {
|
|
|
|
self.constants.font_scale = active_locale.font.scale;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.font = font;
|
|
|
|
}
|
|
|
|
|
2021-02-24 08:28:47 +00:00
|
|
|
pub fn graphics_reset(&mut self) {
|
2022-02-10 07:54:20 +00:00
|
|
|
self.texture_set.unload_all();
|
2021-02-24 08:28:47 +00:00
|
|
|
}
|
|
|
|
|
2020-09-22 00:01:55 +00:00
|
|
|
pub fn start_new_game(&mut self, ctx: &mut Context) -> GameResult {
|
2021-06-28 05:27:36 +00:00
|
|
|
self.reset();
|
2021-10-15 14:36:05 +00:00
|
|
|
#[cfg(feature = "scripting-lua")]
|
2021-06-28 11:07:20 +00:00
|
|
|
self.lua.reload_scripts(ctx)?;
|
2021-06-27 21:14:36 +00:00
|
|
|
|
|
|
|
let mut next_scene = GameScene::new(self, ctx, self.constants.game.new_game_stage as usize)?;
|
2020-12-04 17:37:47 +00:00
|
|
|
next_scene.player1.cond.set_alive(true);
|
2021-06-28 11:07:20 +00:00
|
|
|
let (pos_x, pos_y) = self.constants.game.new_game_player_pos;
|
2021-06-27 21:14:36 +00:00
|
|
|
next_scene.player1.x = pos_x as i32 * next_scene.stage.map.tile_size.as_int() * 0x200;
|
|
|
|
next_scene.player1.y = pos_y as i32 * next_scene.stage.map.tile_size.as_int() * 0x200;
|
2021-05-02 02:04:52 +00:00
|
|
|
|
|
|
|
self.reset_map_flags();
|
2022-02-14 03:39:53 +00:00
|
|
|
self.control_flags.set_control_enabled(true);
|
|
|
|
self.control_flags.set_tick_world(true);
|
2020-09-22 00:01:55 +00:00
|
|
|
self.fade_state = FadeState::Hidden;
|
2021-06-27 21:14:36 +00:00
|
|
|
self.textscript_vm.state = TextScriptExecutionState::Running(self.constants.game.new_game_event, 0);
|
2021-01-01 01:46:01 +00:00
|
|
|
|
2020-09-22 00:01:55 +00:00
|
|
|
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 {
|
2021-10-15 14:36:05 +00:00
|
|
|
#[cfg(feature = "scripting-lua")]
|
2021-06-28 11:07:20 +00:00
|
|
|
self.lua.reload_scripts(ctx)?;
|
2021-06-27 21:14:36 +00:00
|
|
|
|
|
|
|
let start_stage_id = self.constants.game.intro_stage as usize;
|
2021-06-20 19:41:09 +00:00
|
|
|
|
|
|
|
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(());
|
|
|
|
}
|
|
|
|
|
2021-06-27 21:14:36 +00:00
|
|
|
let mut next_scene = GameScene::new(self, ctx, start_stage_id)?;
|
2020-11-28 19:25:51 +00:00
|
|
|
next_scene.player1.cond.set_hidden(true);
|
2021-06-28 11:07:20 +00:00
|
|
|
let (pos_x, pos_y) = self.constants.game.intro_player_pos;
|
2021-06-27 21:14:36 +00:00
|
|
|
next_scene.player1.x = pos_x as i32 * next_scene.stage.map.tile_size.as_int() * 0x200;
|
|
|
|
next_scene.player1.y = pos_y as i32 * next_scene.stage.map.tile_size.as_int() * 0x200;
|
2020-10-30 08:49:30 +00:00
|
|
|
next_scene.intro_mode = true;
|
2021-05-02 02:04:52 +00:00
|
|
|
|
|
|
|
self.reset_map_flags();
|
2020-10-30 08:49:30 +00:00
|
|
|
self.fade_state = FadeState::Hidden;
|
2021-06-27 21:14:36 +00:00
|
|
|
self.textscript_vm.state = TextScriptExecutionState::Running(self.constants.game.intro_event, 0);
|
2021-01-01 01:46:01 +00:00
|
|
|
|
2020-10-30 08:49:30 +00:00
|
|
|
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 {
|
2022-02-25 22:00:14 +00:00
|
|
|
if let Some(save_path) = self.get_save_filename(self.save_slot) {
|
|
|
|
if let Ok(data) = filesystem::open_options(ctx, save_path, OpenOptions::new().write(true).create(true)) {
|
|
|
|
let profile = GameProfile::dump(self, game_scene);
|
|
|
|
profile.write_save(data)?;
|
|
|
|
} else {
|
|
|
|
log::warn!("Cannot open save file.");
|
|
|
|
}
|
2020-10-30 01:29:53 +00:00
|
|
|
} else {
|
2022-02-25 22:00:14 +00:00
|
|
|
log::info!("Mod has saves disabled.");
|
2020-10-30 01:29:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-09-22 00:01:55 +00:00
|
|
|
pub fn load_or_start_game(&mut self, ctx: &mut Context) -> GameResult {
|
2022-02-25 22:00:14 +00:00
|
|
|
if let Some(save_path) = self.get_save_filename(self.save_slot) {
|
|
|
|
if let Ok(data) = filesystem::user_open(ctx, save_path) {
|
|
|
|
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);
|
|
|
|
|
|
|
|
#[cfg(feature = "scripting-lua")]
|
|
|
|
self.lua.reload_scripts(ctx)?;
|
|
|
|
|
|
|
|
self.next_scene = Some(Box::new(next_scene));
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
log::warn!("Failed to load save game, starting new one: {}", e);
|
|
|
|
}
|
2020-09-22 00:01:55 +00:00
|
|
|
}
|
2022-02-25 22:00:14 +00:00
|
|
|
} else {
|
|
|
|
log::warn!("No save game found, starting new one...");
|
2020-09-22 00:01:55 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-02-25 22:00:14 +00:00
|
|
|
log::info!("Mod has saves disabled.");
|
2020-09-22 00:01:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
self.start_new_game(ctx)
|
|
|
|
}
|
|
|
|
|
2020-09-20 15:27:31 +00:00
|
|
|
pub fn reset(&mut self) {
|
|
|
|
self.control_flags.0 = 0;
|
|
|
|
self.game_flags = bitvec::bitvec![0; 8000];
|
|
|
|
self.fade_state = FadeState::Hidden;
|
2020-12-25 22:39:41 +00:00
|
|
|
self.game_rng = XorShift::new(0);
|
2020-09-29 20:19:47 +00:00
|
|
|
self.teleporter_slots.clear();
|
2020-09-20 15:27:31 +00:00
|
|
|
self.quake_counter = 0;
|
|
|
|
self.carets.clear();
|
2020-09-29 20:19:47 +00:00
|
|
|
self.textscript_vm.set_mode(ScriptMode::Map);
|
2020-09-20 15:27:31 +00:00
|
|
|
self.textscript_vm.suspend = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_resize(&mut self, ctx: &mut Context) -> GameResult {
|
2021-01-28 22:33:43 +00:00
|
|
|
self.screen_size = graphics::screen_size(ctx);
|
2021-06-28 11:07:20 +00:00
|
|
|
let scale_x = self.screen_size.1.div(self.preferred_viewport_size.1).floor().max(1.0);
|
|
|
|
let scale_y = self.screen_size.0.div(self.preferred_viewport_size.0).floor().max(1.0);
|
|
|
|
|
|
|
|
self.scale = f32::min(scale_x, scale_y);
|
2021-03-29 21:19:07 +00:00
|
|
|
self.canvas_size = (self.screen_size.0 / self.scale, self.screen_size.1 / self.scale);
|
2020-09-20 15:27:31 +00:00
|
|
|
|
2021-02-05 09:47:30 +00:00
|
|
|
let (width, height) = (self.screen_size.0 as u16, self.screen_size.1 as u16);
|
|
|
|
|
|
|
|
// ensure no texture is bound before destroying them.
|
|
|
|
set_render_target(ctx, None)?;
|
|
|
|
self.lightmap_canvas = Some(create_texture_mutable(ctx, width, height)?);
|
|
|
|
|
2020-09-20 15:27:31 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn tick_carets(&mut self) {
|
2022-01-17 22:29:30 +00:00
|
|
|
for caret in &mut self.carets {
|
2020-09-20 15:27:31 +00:00
|
|
|
caret.tick(&self.effect_rng, &self.constants);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.carets.retain(|c| !c.is_dead());
|
|
|
|
}
|
|
|
|
|
2021-01-01 01:46:01 +00:00
|
|
|
pub fn create_caret(&mut self, x: i32, y: i32, ctype: CaretType, direct: Direction) {
|
2021-03-29 21:19:07 +00:00
|
|
|
self.carets.push(Caret::new(x, y, ctype, direct, &self.constants));
|
2020-09-20 15:27:31 +00:00
|
|
|
}
|
|
|
|
|
2020-10-29 12:22:56 +00:00
|
|
|
pub fn set_speed(&mut self, value: f64) {
|
2021-03-29 21:19:07 +00:00
|
|
|
self.settings.speed = value.clamp(0.1, 3.0);
|
2020-10-30 22:47:29 +00:00
|
|
|
self.frame_time = 0.0;
|
2020-09-20 15:27:31 +00:00
|
|
|
}
|
|
|
|
|
2020-11-24 23:08:27 +00:00
|
|
|
pub fn current_tps(&self) -> f64 {
|
2021-10-14 07:43:17 +00:00
|
|
|
self.settings.timing_mode.get_tps() as f64 * self.settings.speed
|
2020-11-24 23:08:27 +00:00
|
|
|
}
|
|
|
|
|
2020-09-20 15:27:31 +00:00
|
|
|
pub fn shutdown(&mut self) {
|
|
|
|
self.shutdown = true;
|
|
|
|
}
|
2021-02-10 20:14:09 +00:00
|
|
|
|
|
|
|
pub fn set_flag(&mut self, id: usize, value: bool) {
|
|
|
|
if id < self.game_flags.len() {
|
|
|
|
self.game_flags.set(id, value);
|
|
|
|
} else {
|
2022-01-26 04:41:21 +00:00
|
|
|
log::warn!("Attempted to set an out-of-bounds flag: {} to {}.", id, value);
|
2021-02-10 20:14:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_flag(&self, id: usize) -> bool {
|
|
|
|
if let Some(flag) = self.game_flags.get(id) {
|
|
|
|
*flag
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2021-05-02 00:09:54 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2021-05-02 02:04:52 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2022-02-06 17:23:24 +00:00
|
|
|
|
2022-02-25 22:00:14 +00:00
|
|
|
pub fn get_save_filename(&mut self, slot: usize) -> Option<String> {
|
2022-02-10 23:02:45 +00:00
|
|
|
if let Some(mod_path) = &self.mod_path {
|
2022-02-23 00:46:49 +00:00
|
|
|
let save_slot = self.mod_list.get_save_from_path(mod_path.to_string());
|
|
|
|
if save_slot < 0 {
|
2022-02-25 22:00:14 +00:00
|
|
|
return None;
|
2022-02-23 00:46:49 +00:00
|
|
|
} else if save_slot > 0 {
|
2022-02-25 22:00:14 +00:00
|
|
|
return Some(format!("/Mod{}_Profile{}.dat", save_slot, slot));
|
2022-02-23 00:46:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if slot == 1 {
|
2022-02-25 22:00:14 +00:00
|
|
|
return Some("/Profile.dat".to_owned());
|
|
|
|
} else {
|
|
|
|
return Some(format!("/Profile{}.dat", slot));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-04 23:37:25 +00:00
|
|
|
pub fn get_rec_filename(&self) -> String {
|
2022-02-25 22:00:14 +00:00
|
|
|
if let Some(mod_path) = &self.mod_path {
|
|
|
|
let name = self.mod_list.get_name_from_path(mod_path.to_string());
|
2022-03-04 23:37:25 +00:00
|
|
|
return format!("/{}", name);
|
2022-02-06 17:23:24 +00:00
|
|
|
} else {
|
2022-03-04 23:37:25 +00:00
|
|
|
return "/290".to_string();
|
2022-02-06 17:23:24 +00:00
|
|
|
}
|
|
|
|
}
|
2022-02-27 10:21:43 +00:00
|
|
|
|
2022-03-04 23:37:25 +00:00
|
|
|
pub fn has_replay_data(&self, ctx: &mut Context) -> bool {
|
|
|
|
filesystem::user_exists(ctx, [self.get_rec_filename(), ".rep".to_string()].join(""))
|
|
|
|
}
|
|
|
|
|
2022-02-27 10:21:43 +00:00
|
|
|
pub fn get_damage(&self, hp: i32) -> i32 {
|
|
|
|
match self.difficulty {
|
|
|
|
GameDifficulty::Easy => cmp::max(hp / 2, 1),
|
|
|
|
GameDifficulty::Normal | GameDifficulty::Hard => hp,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_skinsheet_offset(&self) -> u16 {
|
2022-03-01 02:45:43 +00:00
|
|
|
if !self.constants.is_cs_plus {
|
2022-02-27 10:21:43 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.settings.seasonal_textures {
|
|
|
|
let season = Season::current();
|
|
|
|
|
|
|
|
if season == Season::Halloween {
|
|
|
|
return 3; // Edgy Quote
|
|
|
|
}
|
|
|
|
|
|
|
|
if season == Season::Christmas {
|
|
|
|
return 4; // Furry Quote
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-27 15:27:34 +00:00
|
|
|
return self.difficulty as u16;
|
2022-02-27 10:21:43 +00:00
|
|
|
}
|
2022-03-15 01:54:03 +00:00
|
|
|
|
|
|
|
pub fn get_active_locale(&self) -> &Locale {
|
|
|
|
let active_locale = self.constants.locales.get(&self.settings.locale.to_string()).unwrap();
|
|
|
|
return active_locale;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn t(&self, key: &str) -> String {
|
|
|
|
return self.get_active_locale().t(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn tt(&self, key: &str, args: HashMap<String, String>) -> String {
|
|
|
|
return self.get_active_locale().tt(key, args);
|
|
|
|
}
|
2020-09-20 15:27:31 +00:00
|
|
|
}
|