Font rendering refactor

This commit is contained in:
Alula 2022-11-20 20:38:36 +01:00
parent 0fca898c54
commit e74b586dd1
No known key found for this signature in database
GPG Key ID: 3E00485503A1D8BA
26 changed files with 1304 additions and 1383 deletions

View File

@ -16,7 +16,7 @@ bench = false
required-features = ["exe"]
[profile.release]
lto = "off"
lto = "thin"
panic = "abort"
[profile.dev.package."*"]
@ -71,6 +71,7 @@ paste = "1.0"
pelite = ">=0.9.2"
sdl2 = { git = "https://github.com/doukutsu-rs/rust-sdl2.git", rev = "95bcf63768abf422527f86da41da910649b9fcc9", optional = true, features = ["unsafe_textures", "bundled", "static-link"] }
sdl2-sys = { git = "https://github.com/doukutsu-rs/rust-sdl2.git", rev = "95bcf63768abf422527f86da41da910649b9fcc9", optional = true, features = ["bundled", "static-link"] }
rc-box = "1.2.0"
serde = { version = "1", features = ["derive"] }
serde_derive = "1"
serde_cbor = { version = "0.11", optional = true }

View File

@ -4,8 +4,9 @@ use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::graphics;
use crate::game::frame::Frame;
use crate::game::shared_game_state::SharedGameState;
use crate::game::scripting::tsc::text_script::IllustrationState;
use crate::game::shared_game_state::SharedGameState;
use crate::graphics::font::Font;
pub struct Credits {}
@ -102,16 +103,22 @@ impl GameEntity<()> for Credits {
}
for line in &state.creditscript_vm.lines {
let text =
if state.more_rust { line.text.replace("Sue Sakamoto", "Crabby Sue") } else { line.text.clone() };
let mut text_ovr = None;
state.font.draw_text_with_shadow(
text.chars(),
line.pos_x,
line.pos_y,
if state.more_rust {
text_ovr = Some(line.text.replace("Sue Sakamoto", "Crabby Sue"));
}
let mut text = line.text.as_str();
if let Some(ovr) = text_ovr.as_ref() {
text = ovr.as_str();
}
state.font.builder().position(line.pos_x, line.pos_y).shadow(true).draw(
text,
ctx,
&state.constants,
&mut state.texture_set,
ctx,
)?;
}

View File

@ -5,11 +5,12 @@ use crate::framework::backend::{BackendTexture, SpriteBatchCommand};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::graphics;
use crate::game::shared_game_state::SharedGameState;
use crate::game::stage::Stage;
use crate::input::touch_controls::TouchControlType;
use crate::game::player::Player;
use crate::game::scripting::tsc::text_script::TextScriptExecutionState;
use crate::game::shared_game_state::SharedGameState;
use crate::game::stage::Stage;
use crate::graphics::font::Font;
use crate::input::touch_controls::TouchControlType;
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum MapSystemState {
@ -185,7 +186,7 @@ impl MapSystem {
}
let (scr_w, scr_h) = (state.canvas_size.0 * state.scale, state.canvas_size.1 * state.scale);
let text_height = state.font.line_height(&state.constants);
let text_height = state.font.line_height();
let rect_black_bar = Rect::new_size(
0,
(7.0 * state.scale) as _,
@ -198,15 +199,17 @@ impl MapSystem {
}
let map_name = if state.constants.is_cs_plus && state.settings.locale == "jp" {
stage.data.name_jp.chars()
stage.data.name_jp.as_str()
} else {
stage.data.name.chars()
stage.data.name.as_str()
};
let map_name_width = state.font.text_width(map_name.clone(), &state.constants);
let map_name_off_x = (state.canvas_size.0 - map_name_width) / 2.0;
state.font.draw_text(map_name, map_name_off_x, 9.0, &state.constants, &mut state.texture_set, ctx)?;
state.font.builder().center(state.canvas_size.0).y(9.0).draw(
map_name,
ctx,
&state.constants,
&mut state.texture_set,
)?;
let mut map_rect = Rect::new(0.0, 0.0, self.last_size.0 as f32, self.last_size.1 as f32);

View File

@ -12,6 +12,7 @@ use crate::game::frame::Frame;
use crate::game::shared_game_state::{ReplayKind, ReplayState, SharedGameState};
use crate::input::replay_player_controller::{KeyState, ReplayController};
use crate::game::player::Player;
use crate::graphics::font::Font;
#[derive(Clone)]
pub struct Replay {
@ -171,17 +172,14 @@ impl GameEntity<(&mut Context, &mut Player)> for Replay {
match state.replay_state {
ReplayState::None => {}
ReplayState::Playback(_) => {
state.font.draw_text_with_shadow(
"PLAY".chars(),
x,
y,
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.builder()
.position(x, y)
.draw("PLAY", ctx, &state.constants, &mut state.texture_set)?;
}
ReplayState::Recording => {
state.font.draw_text_with_shadow("REC".chars(), x, y, &state.constants, &mut state.texture_set, ctx)?;
state.font.builder()
.position(x, y)
.draw("REC", ctx, &state.constants, &mut state.texture_set)?;
}
}

View File

@ -5,8 +5,9 @@ use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::graphics;
use crate::game::frame::Frame;
use crate::game::shared_game_state::SharedGameState;
use crate::game::scripting::tsc::text_script::{ConfirmSelection, TextScriptExecutionState, TextScriptLine};
use crate::game::shared_game_state::SharedGameState;
use crate::graphics::font::{Font, Symbols};
pub struct TextBoxes {
pub slide_in: u8,
@ -226,60 +227,36 @@ impl GameEntity<()> for TextBoxes {
graphics::set_clip_rect(ctx, Some(clip_rect))?;
for (idx, line) in lines.iter().enumerate() {
if !line.is_empty() {
if state.constants.textscript.text_shadow {
state.font.draw_text_with_shadow_and_rects(
line.iter().copied(),
left_pos + text_offset + 14.0,
top_pos + 10.0 + idx as f32 * 16.0 - y_offset,
&state.constants,
&mut state.texture_set,
&state.textscript_vm.substitution_rect_map,
Some("TextBox".into()),
ctx,
)?;
} else {
state.font.draw_text_with_rects(
line.iter().copied(),
left_pos + text_offset + 14.0,
top_pos + 10.0 + idx as f32 * 16.0 - y_offset,
&state.constants,
&mut state.texture_set,
&state.textscript_vm.substitution_rect_map,
Some("TextBox".into()),
ctx,
)?;
}
let symbols = Symbols { symbols: &state.textscript_vm.substitution_rect_map, texture: "TextBox" };
state
.font
.builder()
.position(left_pos + text_offset + 14.0, top_pos + 10.0 + idx as f32 * 16.0 - y_offset)
.shadow(state.constants.textscript.text_shadow)
.with_symbols(Some(symbols))
.draw_iter(line.iter().copied(), ctx, &state.constants, &mut state.texture_set)?;
}
}
graphics::set_clip_rect(ctx, None)?;
if let TextScriptExecutionState::WaitInput(_, _, tick) = state.textscript_vm.state {
if tick > 10 {
let builder = state
.font
.builder()
.with_symbols(Some(Symbols { symbols: &state.textscript_vm.substitution_rect_map, texture: "" }));
let (mut x, y) = match state.textscript_vm.current_line {
TextScriptLine::Line1 => (
state.font.text_width_with_rects(
state.textscript_vm.line_1.iter().copied(),
&state.textscript_vm.substitution_rect_map,
&state.constants,
),
top_pos + 10.0,
),
TextScriptLine::Line2 => (
state.font.text_width_with_rects(
state.textscript_vm.line_2.iter().copied(),
&state.textscript_vm.substitution_rect_map,
&state.constants,
),
top_pos + 10.0 + 16.0,
),
TextScriptLine::Line3 => (
state.font.text_width_with_rects(
state.textscript_vm.line_3.iter().copied(),
&state.textscript_vm.substitution_rect_map,
&state.constants,
),
top_pos + 10.0 + 32.0,
),
TextScriptLine::Line1 => {
(builder.compute_width_iter(state.textscript_vm.line_1.iter().copied()), top_pos + 10.0)
}
TextScriptLine::Line2 => {
(builder.compute_width_iter(state.textscript_vm.line_2.iter().copied()), top_pos + 10.0 + 16.0)
}
TextScriptLine::Line3 => {
(builder.compute_width_iter(state.textscript_vm.line_3.iter().copied()), top_pos + 10.0 + 32.0)
}
};
x += left_pos + text_offset + 14.0;
@ -289,7 +266,7 @@ impl GameEntity<()> for TextBoxes {
(x * state.scale) as isize,
(y * state.scale) as isize,
(5.0 * state.scale) as isize,
(state.font.line_height(&state.constants) * state.scale) as isize,
(state.font.line_height() * state.scale) as isize,
),
Color::from_rgb(255, 255, 255),
)?;

View File

@ -1,7 +1,7 @@
use std::collections::HashMap;
use std::io::{BufRead, BufReader, Cursor, Read};
use byteorder::{LE, ReadBytesExt};
use byteorder::{ReadBytesExt, LE};
use case_insensitive_hashmap::CaseInsensitiveHashMap;
use xmltree::Element;
@ -12,11 +12,11 @@ use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::filesystem;
use crate::framework::gamepad::{Axis, Button};
use crate::game::player::ControlMode;
use crate::game::scripting::tsc::text_script::TextScriptEncoding;
use crate::game::settings::Settings;
use crate::game::shared_game_state::{FontData, Season};
use crate::i18n::Locale;
use crate::game::player::ControlMode;
use crate::game::scripting::tsc::text_script::TextScriptEncoding;
use crate::sound::pixtone::{Channel, Envelope, PixToneParameters, Waveform};
use crate::sound::SoundManager;
@ -332,7 +332,6 @@ pub struct EngineConstants {
pub title: TitleConsts,
pub inventory_dim_color: Color,
pub font_path: String,
pub font_scale: f32,
pub font_space_offset: f32,
pub soundtracks: Vec<ExtraSoundtrack>,
pub music_table: Vec<String>,
@ -365,7 +364,6 @@ impl Clone for EngineConstants {
title: self.title.clone(),
inventory_dim_color: self.inventory_dim_color,
font_path: self.font_path.clone(),
font_scale: self.font_scale,
font_space_offset: self.font_space_offset,
soundtracks: self.soundtracks.clone(),
music_table: self.music_table.clone(),
@ -1604,7 +1602,6 @@ impl EngineConstants {
},
inventory_dim_color: Color::from_rgba(0, 0, 0, 0),
font_path: "csfont.fnt".to_owned(),
font_scale: 1.0,
font_space_offset: 0.0,
soundtracks: vec![
ExtraSoundtrack { name: "Remastered".to_owned(), path: "/base/Ogg11/".to_owned(), available: false },
@ -1664,7 +1661,7 @@ impl EngineConstants {
"/Resource/ORG/".to_owned(), // CSE2E
],
credit_illustration_paths: vec![
"".to_owned(),
String::new(),
"Resource/BITMAP/".to_owned(), // CSE2E
"endpic/".to_owned(), // NXEngine
],

View File

@ -59,8 +59,8 @@ pub static SUPPORTED_SKINMETA_VERSIONS: [u8; 1] = [1];
lazy_static! {
pub static ref DEFAULT_SKINMETA: SkinMeta = SkinMeta {
name: "Player".to_string(),
description: "".to_string(),
author: "".to_string(),
description: String::new(),
author: String::new(),
gun_offset_x: 0,
gun_offset_y: 0,
frame_size_width: 16,

View File

@ -26,6 +26,7 @@ use crate::game::scripting::tsc::opcodes::TSCOpCode;
use crate::game::shared_game_state::ReplayState;
use crate::game::shared_game_state::SharedGameState;
use crate::game::weapon::WeaponType;
use crate::graphics::font::{Font, Symbols};
use crate::input::touch_controls::TouchControlType;
use crate::scene::game_scene::GameScene;
@ -354,6 +355,10 @@ impl TextScriptVM {
cursor.seek(SeekFrom::Start(ip as u64))?;
let chr = std::char::from_u32(read_cur_varint(&mut cursor)? as u32).unwrap_or('\u{fffd}');
let builder = state.font.builder().with_symbols(Some(Symbols {
symbols: &state.textscript_vm.substitution_rect_map,
texture: "",
}));
match chr {
'\n' if state.textscript_vm.current_line == TextScriptLine::Line1 => {
@ -370,11 +375,7 @@ impl TextScriptVM {
state.textscript_vm.prev_char = chr;
state.textscript_vm.line_1.push(chr);
let text_len = state.font.text_width_with_rects(
state.textscript_vm.line_1.iter().copied(),
&state.textscript_vm.substitution_rect_map,
&state.constants,
);
let text_len = builder.compute_width_iter(state.textscript_vm.line_1.iter().copied());
if text_len >= 284.0 {
state.textscript_vm.current_line = TextScriptLine::Line2;
}
@ -383,11 +384,7 @@ impl TextScriptVM {
state.textscript_vm.prev_char = chr;
state.textscript_vm.line_2.push(chr);
let text_len = state.font.text_width_with_rects(
state.textscript_vm.line_2.iter().copied(),
&state.textscript_vm.substitution_rect_map,
&state.constants,
);
let text_len = builder.compute_width_iter(state.textscript_vm.line_2.iter().copied());
if text_len >= 284.0 {
state.textscript_vm.current_line = TextScriptLine::Line3;
}
@ -396,11 +393,7 @@ impl TextScriptVM {
state.textscript_vm.prev_char = chr;
state.textscript_vm.line_3.push(chr);
let text_len = state.font.text_width_with_rects(
state.textscript_vm.line_3.iter().copied(),
&state.textscript_vm.substitution_rect_map,
&state.constants,
);
let text_len = builder.compute_width_iter(state.textscript_vm.line_3.iter().copied());
if text_len >= 284.0 {
new_line = true;
}
@ -414,9 +407,9 @@ impl TextScriptVM {
0
} else if remaining != 2
&& (game_scene.player1.controller.jump()
|| game_scene.player1.controller.shoot()
|| game_scene.player2.controller.jump()
|| game_scene.player2.controller.shoot())
|| game_scene.player1.controller.shoot()
|| game_scene.player2.controller.jump()
|| game_scene.player2.controller.shoot())
{
state.constants.textscript.text_speed_fast
} else {
@ -640,7 +633,7 @@ impl TextScriptVM {
cursor.seek(SeekFrom::Start(ip as u64))?;
let op: TSCOpCode = if let Some(op) =
FromPrimitive::from_i32(read_cur_varint(&mut cursor).unwrap_or_else(|_| TSCOpCode::END as i32))
FromPrimitive::from_i32(read_cur_varint(&mut cursor).unwrap_or_else(|_| TSCOpCode::END as i32))
{
op
} else {
@ -700,7 +693,7 @@ impl TextScriptVM {
state.textscript_vm.set_mode(ScriptMode::StageSelect);
let event_num = if let Some(slot) =
state.teleporter_slots.get(game_scene.stage_select.current_teleport_slot as usize)
state.teleporter_slots.get(game_scene.stage_select.current_teleport_slot as usize)
{
1000 + slot.0
} else {

View File

@ -3,15 +3,15 @@ use std::{cmp, ops::Div};
use chrono::{Datelike, Local};
use crate::common::{ControlFlags, Direction, FadeState};
use crate::components::draw_common::{Alignment, draw_number};
use crate::components::draw_common::{draw_number, Alignment};
use crate::data::vanilla::VanillaExtractor;
use crate::engine_constants::EngineConstants;
use crate::framework::{filesystem, graphics};
use crate::framework::backend::BackendTexture;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::graphics::{create_texture_mutable, set_render_target};
use crate::framework::vfs::OpenOptions;
use crate::framework::{filesystem, graphics};
use crate::game::caret::{Caret, CaretType};
use crate::game::npc::NPCTable;
use crate::game::profile::GameProfile;
@ -21,15 +21,15 @@ use crate::game::scripting::tsc::credit_script::{CreditScript, CreditScriptVM};
use crate::game::scripting::tsc::text_script::{ScriptMode, TextScript, TextScriptExecutionState, TextScriptVM};
use crate::game::settings::Settings;
use crate::game::stage::StageData;
use crate::graphics::bmfont_renderer::BMFontRenderer;
use crate::graphics::bmfont::BMFont;
use crate::graphics::texture_set::TextureSet;
use crate::i18n::Locale;
use crate::input::touch_controls::TouchControls;
use crate::mod_list::ModList;
use crate::mod_requirements::ModRequirements;
use crate::scene::game_scene::GameScene;
use crate::scene::Scene;
use crate::scene::title_scene::TitleScene;
use crate::scene::Scene;
use crate::sound::SoundManager;
use crate::util::bitvec::BitVec;
use crate::util::rng::XorShift;
@ -291,7 +291,7 @@ pub struct SharedGameState {
pub season: Season,
pub menu_character: MenuCharacter,
pub constants: EngineConstants,
pub font: BMFontRenderer,
pub font: BMFont,
pub texture_set: TextureSet,
#[cfg(feature = "scripting-lua")]
pub lua: LuaScriptingState,
@ -304,6 +304,7 @@ pub struct SharedGameState {
pub player2_skin: u16,
pub replay_state: ReplayState,
pub mod_requirements: ModRequirements,
pub loc: Locale,
pub tutorial_counter: u16,
pub more_rust: bool,
pub shutdown: bool,
@ -375,15 +376,11 @@ impl SharedGameState {
constants.load_locales(ctx)?;
let active_locale = SharedGameState::active_locale(settings.locale.clone(), constants.clone());
let locale = SharedGameState::get_locale(&constants, &settings.locale).unwrap_or_default();
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| {
let font = BMFont::load(&constants.base_paths, &locale.font.path, ctx, locale.font.scale).or_else(|e| {
log::warn!("Failed to load font, using built-in: {}", e);
BMFontRenderer::load(&vec!["/".to_owned()], "builtin/builtin_font.fnt", ctx)
BMFont::load(&vec!["/".to_owned()], "builtin/builtin_font.fnt", ctx, 1.0)
})?;
let mod_list = ModList::load(ctx, &constants.string_table)?;
@ -460,6 +457,7 @@ impl SharedGameState {
player2_skin: 0,
replay_state: ReplayState::None,
mod_requirements,
loc: locale,
tutorial_counter: 0,
more_rust,
shutdown: false,
@ -515,20 +513,18 @@ impl SharedGameState {
self.texture_set.unload_all();
}
pub fn reload_fonts(&mut self, ctx: &mut Context) {
let active_locale = self.get_active_locale();
pub fn update_locale(&mut self, ctx: &mut Context) {
if let Some(locale) = SharedGameState::get_locale(&self.constants, &self.settings.locale) {
self.loc = locale;
}
let font = BMFontRenderer::load(&self.constants.base_paths, &active_locale.font.path, ctx)
let font = BMFont::load(&self.constants.base_paths, &self.loc.font.path, ctx, self.loc.font.scale)
.or_else(|e| {
log::warn!("Failed to load font, using built-in: {}", e);
BMFontRenderer::load(&vec!["/".to_owned()], "builtin/builtin_font.fnt", ctx)
BMFont::load(&vec!["/".to_owned()], "builtin/builtin_font.fnt", ctx, 1.0)
})
.unwrap();
if self.constants.is_cs_plus {
self.constants.font_scale = active_locale.font.scale;
}
self.font = font;
}
@ -539,7 +535,7 @@ impl SharedGameState {
pub fn start_new_game(&mut self, ctx: &mut Context) -> GameResult {
self.reset();
#[cfg(feature = "scripting-lua")]
self.lua.reload_scripts(ctx)?;
self.lua.reload_scripts(ctx)?;
let mut next_scene = GameScene::new(self, ctx, self.constants.game.new_game_stage as usize)?;
next_scene.player1.cond.set_alive(true);
@ -561,7 +557,7 @@ impl SharedGameState {
pub fn start_intro(&mut self, ctx: &mut Context) -> GameResult {
#[cfg(feature = "scripting-lua")]
self.lua.reload_scripts(ctx)?;
self.lua.reload_scripts(ctx)?;
let start_stage_id = self.constants.game.intro_stage as usize;
@ -613,7 +609,7 @@ impl SharedGameState {
profile.apply(self, &mut next_scene, ctx);
#[cfg(feature = "scripting-lua")]
self.lua.reload_scripts(ctx)?;
self.lua.reload_scripts(ctx)?;
self.next_scene = Some(Box::new(next_scene));
return Ok(());
@ -813,37 +809,24 @@ impl SharedGameState {
return self.difficulty as u16;
}
fn active_locale(user_locale: String, constants: EngineConstants) -> Locale {
let mut active_locale: Option<Locale> = None;
let mut en_locale: Option<Locale> = None;
fn get_locale(constants: &EngineConstants, user_locale: &str) -> Option<Locale> {
let mut out_locale = None;
for locale in &constants.locales {
if locale.code == "en" {
en_locale = Some(locale.clone());
out_locale = Some(locale.clone());
}
if locale.code == user_locale {
active_locale = Some(locale.clone());
out_locale = Some(locale.clone());
break;
}
}
match active_locale {
Some(locale) => locale,
None => en_locale.unwrap(),
}
}
pub fn get_active_locale(&self) -> Locale {
let locale = SharedGameState::active_locale(self.settings.locale.clone(), self.constants.clone());
locale.clone()
}
pub fn t(&self, key: &str) -> String {
return self.get_active_locale().t(key);
out_locale
}
pub fn tt(&self, key: &str, args: &[(&str, &str)]) -> String {
return self.get_active_locale().tt(key, args);
return self.loc.tt(key, args);
}
}

View File

@ -1,40 +1,71 @@
use std::collections::HashMap;
use std::collections::HashSet;
use std::io;
use std::path::PathBuf;
use byteorder::{LE, ReadBytesExt};
use crate::common::{FILE_TYPES, Rect};
use crate::engine_constants::EngineConstants;
use crate::framework::context::Context;
use crate::framework::error::GameError::ResourceLoadError;
use crate::framework::error::GameResult;
use crate::framework::filesystem;
use crate::graphics::font::{EMPTY_SYMBOLS, Font, Symbols, TextBuilderFlag};
use crate::graphics::texture_set::TextureSet;
#[derive(Debug)]
pub struct BmChar {
pub struct BMChar {
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
pub xoffset: i16,
pub yoffset: i16,
pub xadvance: i16,
pub x_offset: i16,
pub y_offset: i16,
pub x_advance: i16,
pub page: u8,
pub chnl: u8,
pub channel: u8,
}
#[derive(Debug)]
pub struct BMFont {
pub struct BMFontMetadata {
pub pages: u16,
pub font_size: i16,
pub line_height: u16,
pub base: u16,
pub chars: HashMap<char, BmChar>,
pub chars: HashMap<char, BMChar>,
}
#[repr(u8)]
pub enum BMFontBlockType {
Unknown = 0,
Info = 1,
Common = 2,
Pages = 3,
Chars = 4,
KerningPairs = 5,
}
impl From<u8> for BMFontBlockType {
fn from(value: u8) -> Self {
match value {
1 => BMFontBlockType::Info,
2 => BMFontBlockType::Common,
3 => BMFontBlockType::Pages,
4 => BMFontBlockType::Chars,
5 => BMFontBlockType::KerningPairs,
_ => BMFontBlockType::Unknown,
}
}
}
const MAGIC: [u8; 4] = [b'B', b'M', b'F', 3];
impl BMFont {
impl BMFontMetadata {
pub fn load_from<R: io::Read + io::Seek>(mut data: R) -> GameResult<Self> {
let mut magic = [0u8; 4];
let mut pages = 0u16;
let mut chars = HashMap::with_capacity(128);
let mut chars = HashMap::new();
let mut font_size = 0i16;
let mut line_height = 0u16;
let mut base = 0u16;
@ -47,13 +78,13 @@ impl BMFont {
while let Ok(block_type) = data.read_u8() {
let length = data.read_u32::<LE>()?;
match block_type {
1 => {
match BMFontBlockType::from(block_type) {
BMFontBlockType::Info => {
font_size = data.read_i16::<LE>()?;
data.seek(io::SeekFrom::Current(length as i64 - 2))?;
}
2 => {
BMFontBlockType::Common => {
line_height = data.read_u16::<LE>()?;
base = data.read_u16::<LE>()?;
data.seek(io::SeekFrom::Current(4))?;
@ -61,34 +92,286 @@ impl BMFont {
data.seek(io::SeekFrom::Current(length as i64 - 10))?;
}
3 | 5 => {
data.seek(io::SeekFrom::Current(length as i64))?;
}
4 => {
BMFontBlockType::Chars => {
let count = length / 20;
chars.reserve(count as usize);
for _ in 0..count {
let id = data.read_u32::<LE>()?;
let x = data.read_u16::<LE>()?;
let y = data.read_u16::<LE>()?;
let width = data.read_u16::<LE>()?;
let height = data.read_u16::<LE>()?;
let xoffset = data.read_i16::<LE>()?;
let yoffset = data.read_i16::<LE>()?;
let xadvance = data.read_i16::<LE>()?;
let x_offset = data.read_i16::<LE>()?;
let y_offset = data.read_i16::<LE>()?;
let x_advance = data.read_i16::<LE>()?;
let page = data.read_u8()?;
let chnl = data.read_u8()?;
let channel = data.read_u8()?;
if let Some(chr) = std::char::from_u32(id) {
chars.insert(chr, BmChar { x, y, width, height, xoffset, yoffset, xadvance, page, chnl });
chars.insert(
chr,
BMChar { x, y, width, height, x_offset, y_offset, x_advance, page, channel },
);
}
}
}
_ => {
BMFontBlockType::Unknown => {
return Err(ResourceLoadError("Unknown block type.".to_owned()));
}
_ => {
data.seek(io::SeekFrom::Current(length as i64))?;
}
}
}
Ok(Self { pages, font_size, line_height, base, chars })
}
}
pub struct BMFont {
font: BMFontMetadata,
font_scale: f32,
pages: Vec<String>,
}
impl Font for BMFont {
fn line_height(&self) -> f32 {
self.font.line_height as f32 * self.font_scale
}
fn compute_width(&self, text: &mut dyn Iterator<Item = char>, symbols: Option<&Symbols>) -> f32 {
let mut offset_x = 0.0;
if let Some(syms) = symbols {
for chr in text {
let rect_map_entry = syms.symbols.iter().find(|(c, _)| *c == chr);
if let Some((_, rect)) = rect_map_entry {
offset_x += rect.width() as f32;
} else if let Some(glyph) = self.font.chars.get(&chr) {
offset_x += glyph.x_advance as f32 * self.font_scale;
}
}
} else {
for chr in text {
if let Some(glyph) = self.font.chars.get(&chr) {
offset_x += glyph.x_advance as f32 * self.font_scale;
}
}
}
offset_x
}
fn draw(
&self,
text: &mut dyn Iterator<Item = char>,
mut x: f32,
y: f32,
scale: f32,
box_width: f32,
shadow_color: (u8, u8, u8, u8),
color: (u8, u8, u8, u8),
flags: TextBuilderFlag,
constants: &EngineConstants,
texture_set: &mut TextureSet,
symbols: Option<Symbols>,
ctx: &mut Context,
) -> GameResult {
unsafe {
static mut TEXT_BUF: Vec<char> = Vec::new();
TEXT_BUF.clear();
for c in text {
TEXT_BUF.push(c);
}
if flags.centered() {
let text_width = self.compute_width(&mut TEXT_BUF.iter().copied(), symbols.as_ref());
x += (box_width - text_width) * 0.5;
}
if flags.shadow() {
self.draw_text_line(
&mut TEXT_BUF.iter().copied(),
x + scale,
y + scale,
scale,
shadow_color,
constants,
texture_set,
symbols.as_ref(),
ctx,
)?;
}
self.draw_text_line(
&mut TEXT_BUF.iter().copied(),
x,
y,
scale,
color,
constants,
texture_set,
symbols.as_ref(),
ctx,
)?;
}
Ok(())
}
}
impl BMFont {
pub fn load(roots: &Vec<String>, desc_path: &str, ctx: &mut Context, font_scale: f32) -> GameResult<BMFont> {
let full_path = PathBuf::from(desc_path);
let desc_stem =
full_path.file_stem().ok_or_else(|| ResourceLoadError("Cannot extract the file stem.".to_owned()))?;
let stem = full_path.parent().unwrap_or(&full_path).join(desc_stem);
let font = BMFontMetadata::load_from(filesystem::open_find(ctx, roots, &full_path)?)?;
let mut pages = Vec::new();
let (zeros, _, _) = FILE_TYPES
.iter()
.map(|ext| (1, ext, format!("{}_0{}", stem.to_string_lossy(), ext)))
.find(|(_, _, path)| filesystem::exists_find(ctx, roots, &path))
.or_else(|| {
FILE_TYPES
.iter()
.map(|ext| (2, ext, format!("{}_00{}", stem.to_string_lossy(), ext)))
.find(|(_, _, path)| filesystem::exists_find(ctx, roots, &path))
})
.ok_or_else(|| ResourceLoadError(format!("Cannot find glyph atlas 0 for font: {:?}", desc_path)))?;
for i in 0..font.pages {
let page_path = format!("{}_{:02$}", stem.to_string_lossy(), i, zeros);
pages.push(page_path);
}
Ok(Self { font, font_scale, pages })
}
fn draw_text_line(
&self,
iter: &mut dyn Iterator<Item = char>,
x: f32,
y: f32,
scale: f32,
color: (u8, u8, u8, u8),
constants: &EngineConstants,
texture_set: &mut TextureSet,
symbols: Option<&Symbols>,
ctx: &mut Context,
) -> GameResult {
unsafe {
static mut RECTS_BUF: Vec<(f32, f32, *const Rect<u16>)> = Vec::new();
let syms = symbols.unwrap_or(&EMPTY_SYMBOLS);
RECTS_BUF.clear();
if self.pages.len() == 1 {
let batch = texture_set.get_or_load_batch(ctx, constants, self.pages.get(0).unwrap())?;
let mut offset_x = x;
for chr in iter {
if let Some(glyph) = self.font.chars.get(&chr) {
let rect_map_entry = syms.symbols.iter().find(|(c, _)| *c == chr);
if let Some((_, rect)) = rect_map_entry {
RECTS_BUF.push((
offset_x,
y + self.line_height() / 2.0 - rect.height() as f32 / 2.0,
rect as *const _,
));
offset_x += rect.width() as f32;
} else {
batch.add_rect_scaled_tinted(
offset_x + (glyph.x_offset as f32 * self.font_scale),
y + (glyph.y_offset as f32 * self.font_scale),
color,
self.font_scale * scale,
self.font_scale * scale,
&Rect::new_size(
glyph.x as u16,
glyph.y as u16,
glyph.width as u16,
glyph.height as u16,
),
);
offset_x += glyph.x_advance as f32 * self.font_scale * scale;
}
}
}
batch.draw(ctx)?;
} else {
let mut pages = HashSet::new();
let mut chars = Vec::new();
for chr in iter {
if let Some(glyph) = self.font.chars.get(&chr) {
pages.insert(glyph.page);
chars.push((chr, glyph));
}
}
for page in pages {
let page_tex = if let Some(p) = self.pages.get(page as usize) {
p
} else {
continue;
};
let batch = texture_set.get_or_load_batch(ctx, constants, page_tex)?;
let mut offset_x = x;
for (chr, glyph) in chars.iter() {
let rect_map_entry = syms.symbols.iter().find(|(c, _)| *c == *chr);
if let Some((_, rect)) = rect_map_entry {
RECTS_BUF.push((offset_x, y + self.line_height() / 2.0 - rect.height() as f32 / 2.0, rect));
offset_x += rect.width() as f32;
} else {
if glyph.page == page {
batch.add_rect_scaled_tinted(
offset_x + (glyph.x_offset as f32 * self.font_scale),
y + (glyph.y_offset as f32 * self.font_scale),
color,
self.font_scale * scale,
self.font_scale * scale,
&Rect::new_size(
glyph.x as u16,
glyph.y as u16,
glyph.width as u16,
glyph.height as u16,
),
);
}
offset_x += scale * (glyph.x_advance as f32 * self.font_scale);
}
}
batch.draw(ctx)?;
}
}
if !RECTS_BUF.is_empty() && !syms.texture.is_empty() {
let sprite_batch = texture_set.get_or_load_batch(ctx, constants, syms.texture)?;
for &(x, y, rect) in RECTS_BUF.iter() {
sprite_batch.add_rect_scaled(x, y, scale, scale, &*rect);
}
sprite_batch.draw(ctx)?;
}
}
Ok(())
}
}

View File

@ -1,402 +0,0 @@
use std::collections::HashSet;
use std::path::PathBuf;
use crate::common::{FILE_TYPES, Rect};
use crate::engine_constants::EngineConstants;
use crate::framework::context::Context;
use crate::framework::error::GameError::ResourceLoadError;
use crate::framework::error::GameResult;
use crate::framework::filesystem;
use crate::graphics::bmfont::BMFont;
use crate::graphics::texture_set::TextureSet;
pub struct BMFontRenderer {
font: BMFont,
pages: Vec<String>,
}
impl BMFontRenderer {
pub fn load(roots: &Vec<String>, desc_path: &str, ctx: &mut Context) -> GameResult<BMFontRenderer> {
let full_path = PathBuf::from(desc_path);
let desc_stem =
full_path.file_stem().ok_or_else(|| ResourceLoadError("Cannot extract the file stem.".to_owned()))?;
let stem = full_path.parent().unwrap_or(&full_path).join(desc_stem);
let font = BMFont::load_from(filesystem::open_find(ctx, roots, &full_path)?)?;
let mut pages = Vec::new();
let (zeros, _, _) = FILE_TYPES
.iter()
.map(|ext| (1, ext, format!("{}_0{}", stem.to_string_lossy(), ext)))
.find(|(_, _, path)| filesystem::exists_find(ctx, roots, &path))
.or_else(|| {
FILE_TYPES
.iter()
.map(|ext| (2, ext, format!("{}_00{}", stem.to_string_lossy(), ext)))
.find(|(_, _, path)| filesystem::exists_find(ctx, roots, &path))
})
.ok_or_else(|| ResourceLoadError(format!("Cannot find glyph atlas 0 for font: {:?}", desc_path)))?;
for i in 0..font.pages {
let page_path = format!("{}_{:02$}", stem.to_string_lossy(), i, zeros);
pages.push(page_path);
}
Ok(Self { font, pages })
}
pub fn line_height(&self, constants: &EngineConstants) -> f32 {
self.font.line_height as f32 * constants.font_scale
}
pub fn text_width<I: Iterator<Item=char>>(&self, iter: I, constants: &EngineConstants) -> f32 {
let mut offset_x = 0.0;
for chr in iter {
if let Some(glyph) = self.font.chars.get(&chr) {
offset_x += glyph.xadvance as f32 * constants.font_scale;
}
}
offset_x
}
pub fn text_width_with_rects<I: Iterator<Item=char> + Clone>(
&self,
iter: I,
rect_map: &[(char, Rect<u16>)],
constants: &EngineConstants,
) -> f32 {
let mut width = self.text_width(iter.clone(), constants);
for chr in iter {
let rect_map_entry = rect_map.iter().find(|(c, _)| *c == chr);
if let Some((_, rect)) = rect_map_entry {
if let Some(glyph) = self.font.chars.get(&chr) {
width += rect.width() as f32;
width -= glyph.xadvance as f32 * constants.font_scale;
}
}
}
width
}
pub fn draw_text<I: Iterator<Item=char>>(
&self,
iter: I,
x: f32,
y: f32,
constants: &EngineConstants,
texture_set: &mut TextureSet,
ctx: &mut Context,
) -> GameResult {
self.draw_colored_text(iter, x, y, (255, 255, 255, 255), constants, texture_set, ctx)
}
pub fn draw_text_with_rects<I: Iterator<Item=char>>(
&self,
iter: I,
x: f32,
y: f32,
constants: &EngineConstants,
texture_set: &mut TextureSet,
rect_map: &[(char, Rect<u16>)],
sprite_batch_name: Option<&str>,
ctx: &mut Context,
) -> GameResult {
self.draw_colored_text_with_rects(
iter,
x,
y,
(255, 255, 255, 255),
constants,
texture_set,
rect_map,
sprite_batch_name,
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_text_with_shadow_and_rects<I: Iterator<Item=char> + Clone>(
&self,
iter: I,
x: f32,
y: f32,
constants: &EngineConstants,
texture_set: &mut TextureSet,
rect_map: &[(char, Rect<u16>)],
sprite_batch_name: Option<&str>,
ctx: &mut Context,
) -> GameResult {
self.draw_colored_text_with_rects(
iter.clone(),
x + 1.0,
y + 1.0,
(0, 0, 0, 150),
constants,
texture_set,
rect_map,
None,
ctx,
)?;
self.draw_colored_text_with_rects(
iter,
x,
y,
(255, 255, 255, 255),
constants,
texture_set,
rect_map,
sprite_batch_name,
ctx,
)
}
pub fn draw_colored_text_with_shadow_scaled<I: Iterator<Item=char> + Clone>(
&self,
iter: I,
x: f32,
y: f32,
scale: f32,
color: (u8, u8, u8, u8),
constants: &EngineConstants,
texture_set: &mut TextureSet,
ctx: &mut Context,
) -> GameResult {
self.draw_colored_text_scaled(
iter.clone(),
x + scale,
y + scale,
scale,
(0, 0, 0, 150),
constants,
texture_set,
ctx,
)?;
self.draw_colored_text_scaled(iter, x, y, scale, color, constants, texture_set, ctx)
}
pub fn draw_colored_text_with_shadow_and_rects_scaled<I: Iterator<Item=char> + Clone>(
&self,
iter: I,
x: f32,
y: f32,
scale: f32,
color: (u8, u8, u8, u8),
constants: &EngineConstants,
texture_set: &mut TextureSet,
rect_map: &[(char, Rect<u16>)],
sprite_batch_name: Option<&str>,
ctx: &mut Context,
) -> GameResult {
self.draw_colored_text_with_rects_scaled(
iter.clone(),
x + scale,
y + scale,
scale,
(0, 0, 0, 150),
constants,
texture_set,
rect_map,
None,
ctx,
)?;
self.draw_colored_text_with_rects_scaled(
iter,
x,
y,
scale,
color,
constants,
texture_set,
rect_map,
sprite_batch_name,
ctx,
)
}
pub fn draw_colored_text_scaled<I: Iterator<Item=char>>(
&self,
iter: I,
x: f32,
y: f32,
scale: f32,
color: (u8, u8, u8, u8),
constants: &EngineConstants,
texture_set: &mut TextureSet,
ctx: &mut Context,
) -> GameResult {
self.draw_colored_text_with_rects_scaled(iter, x, y, scale, color, constants, texture_set, &[], None, ctx)
}
pub fn draw_colored_text_with_rects_scaled<I: Iterator<Item=char>>(
&self,
iter: I,
x: f32,
y: f32,
scale: f32,
color: (u8, u8, u8, u8),
constants: &EngineConstants,
texture_set: &mut TextureSet,
rect_map: &[(char, Rect<u16>)],
sprite_batch_name: Option<&str>,
ctx: &mut Context,
) -> GameResult {
let mut sprite_rects: Vec<(f32, f32, &Rect<u16>)> = Vec::new();
if self.pages.len() == 1 {
let batch = texture_set.get_or_load_batch(ctx, constants, self.pages.get(0).unwrap())?;
let mut offset_x = x;
for chr in iter {
if let Some(glyph) = self.font.chars.get(&chr) {
let rect_map_entry = rect_map.iter().find(|(c, _)| *c == chr);
if let Some((_, rect)) = rect_map_entry {
sprite_rects.push((
offset_x,
y + self.line_height(constants) / 2.0 - rect.height() as f32 / 2.0,
rect,
));
offset_x += rect.width() as f32;
} else {
batch.add_rect_scaled_tinted(
offset_x + (glyph.xoffset as f32 * constants.font_scale),
y + (glyph.yoffset as f32 * constants.font_scale),
color,
constants.font_scale * scale,
constants.font_scale * scale,
&Rect::new_size(glyph.x as u16, glyph.y as u16, glyph.width as u16, glyph.height as u16),
);
offset_x += glyph.xadvance as f32 * constants.font_scale * scale;
}
}
}
batch.draw(ctx)?;
} else {
let mut pages = HashSet::new();
let mut chars = Vec::new();
for chr in iter {
if let Some(glyph) = self.font.chars.get(&chr) {
pages.insert(glyph.page);
chars.push((chr, glyph));
}
}
for page in pages {
let page_tex = if let Some(p) = self.pages.get(page as usize) {
p
} else {
continue;
};
let batch = texture_set.get_or_load_batch(ctx, constants, page_tex)?;
let mut offset_x = x;
for (chr, glyph) in chars.iter() {
let rect_map_entry = rect_map.iter().find(|(c, _)| *c == *chr);
if let Some((_, rect)) = rect_map_entry {
sprite_rects.push((
offset_x,
y + self.line_height(constants) / 2.0 - rect.height() as f32 / 2.0,
rect,
));
offset_x += rect.width() as f32;
} else {
if glyph.page == page {
batch.add_rect_scaled_tinted(
offset_x + (glyph.xoffset as f32 * constants.font_scale),
y + (glyph.yoffset as f32 * constants.font_scale),
color,
constants.font_scale * scale,
constants.font_scale * scale,
&Rect::new_size(
glyph.x as u16,
glyph.y as u16,
glyph.width as u16,
glyph.height as u16,
),
);
}
offset_x += scale * (glyph.xadvance as f32 * constants.font_scale);
}
}
batch.draw(ctx)?;
}
}
if let Some(sprite_batch_name) = sprite_batch_name {
if !sprite_rects.is_empty() {
let sprite_batch = texture_set.get_or_load_batch(ctx, constants, sprite_batch_name)?;
for (x, y, rect) in sprite_rects {
sprite_batch.add_rect_scaled(x, y, scale, scale, rect);
}
sprite_batch.draw(ctx)?;
}
}
Ok(())
}
pub fn draw_colored_text<I: Iterator<Item=char>>(
&self,
iter: I,
x: f32,
y: f32,
color: (u8, u8, u8, u8),
constants: &EngineConstants,
texture_set: &mut TextureSet,
ctx: &mut Context,
) -> GameResult {
self.draw_colored_text_scaled(iter, x, y, 1.0, color, constants, texture_set, ctx)
}
pub fn draw_colored_text_with_rects<I: Iterator<Item=char>>(
&self,
iter: I,
x: f32,
y: f32,
color: (u8, u8, u8, u8),
constants: &EngineConstants,
texture_set: &mut TextureSet,
rect_map: &[(char, Rect<u16>)],
sprite_batch_name: Option<&str>,
ctx: &mut Context,
) -> GameResult {
self.draw_colored_text_with_rects_scaled(
iter,
x,
y,
1.0,
color,
constants,
texture_set,
rect_map,
sprite_batch_name,
ctx,
)
}
}

208
src/graphics/font.rs Normal file
View File

@ -0,0 +1,208 @@
use crate::bitfield;
use crate::common::Rect;
use crate::engine_constants::EngineConstants;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::graphics::texture_set::TextureSet;
bitfield! {
#[derive(Clone, Copy)]
#[repr(C)]
pub struct TextBuilderFlag(u8);
pub shadow, set_shadow: 0;
pub centered, set_centered: 1;
}
#[derive(Copy, Clone)]
pub struct Symbols<'a> {
pub symbols: &'a [(char, Rect<u16>)],
pub texture: &'a str,
}
pub static EMPTY_SYMBOLS: Symbols = Symbols { symbols: &[], texture: "" };
pub trait Font {
fn builder(&self) -> TextBuilder
where
Self: Sized,
{
TextBuilder::new(self)
}
fn line_height(&self) -> f32;
fn compute_width(&self, text: &mut dyn Iterator<Item = char>, symbols: Option<&Symbols>) -> f32;
fn draw(
&self,
text: &mut dyn Iterator<Item = char>,
x: f32,
y: f32,
scale: f32,
box_width: f32,
shadow_color: (u8, u8, u8, u8),
color: (u8, u8, u8, u8),
flags: TextBuilderFlag,
constants: &EngineConstants,
texture_set: &mut TextureSet,
symbols: Option<Symbols>,
ctx: &mut Context,
) -> GameResult;
}
pub struct TextBuilder<'a, 'b> {
font: &'a dyn Font,
x: f32,
y: f32,
scale: f32,
shadow_color: (u8, u8, u8, u8),
color: (u8, u8, u8, u8),
flags: TextBuilderFlag,
box_width: f32,
symbols: Option<Symbols<'b>>,
}
#[allow(dead_code)]
impl<'a, 'b> TextBuilder<'a, 'b> {
#[inline]
pub fn new(font: &'a dyn Font) -> Self {
Self {
font,
x: 0.0,
y: 0.0,
scale: 1.0,
shadow_color: (0, 0, 0, 150),
color: (255, 255, 255, 255),
flags: TextBuilderFlag(0),
box_width: 0.0,
symbols: None,
}
}
#[inline]
pub fn position(mut self, x: f32, y: f32) -> Self {
self.x = x;
self.y = y;
self
}
#[inline]
pub fn x(mut self, x: f32) -> Self {
self.x = x;
self
}
#[inline]
pub fn y(mut self, y: f32) -> Self {
self.y = y;
self
}
#[inline]
pub fn get_position(&self) -> (f32, f32) {
(self.x, self.y)
}
#[inline]
pub const fn color(mut self, color: (u8, u8, u8, u8)) -> Self {
self.color = color;
self
}
#[inline]
pub const fn get_color(&self) -> (u8, u8, u8, u8) {
self.color
}
#[inline]
pub const fn shadow_color(mut self, color: (u8, u8, u8, u8)) -> Self {
self.shadow_color = color;
self
}
#[inline]
pub const fn get_shadow_color(&self) -> (u8, u8, u8, u8) {
self.shadow_color
}
#[inline]
pub const fn scale(mut self, scale: f32) -> Self {
self.scale = scale;
self
}
#[inline]
pub const fn get_scale(&self) -> f32 {
self.scale
}
#[inline]
pub fn shadow(mut self, shadow: bool) -> Self {
self.flags.set_shadow(shadow);
self
}
#[inline]
pub fn get_shadow(&self) -> bool {
self.flags.shadow()
}
#[inline]
pub fn with_symbols(mut self, symbols: Option<Symbols<'b>>) -> Self {
self.symbols = symbols;
self
}
#[inline]
pub fn center(mut self, box_width: f32) -> Self {
self.box_width = box_width;
self.flags.set_centered(true);
self
}
#[inline]
pub fn compute_width(&self, text: &str) -> f32 {
self.compute_width_iter(text.chars())
}
#[inline]
pub fn compute_width_iter(&self, mut text: impl Iterator<Item = char>) -> f32 {
self.font.compute_width(&mut text, self.symbols.as_ref())
}
#[inline]
pub fn draw(
self,
text: &str,
ctx: &mut Context,
constants: &EngineConstants,
texture_set: &mut TextureSet,
) -> GameResult {
self.draw_iter(text.chars(), ctx, constants, texture_set)
}
pub fn draw_iter(
self,
mut text: impl Iterator<Item = char>,
ctx: &mut Context,
constants: &EngineConstants,
texture_set: &mut TextureSet,
) -> GameResult {
self.font.draw(
&mut text,
self.x,
self.y,
self.scale,
self.box_width,
self.shadow_color,
self.color,
self.flags,
constants,
texture_set,
self.symbols,
ctx,
)
}
}

View File

@ -1,3 +1,3 @@
pub mod bmfont;
pub mod bmfont_renderer;
pub mod font;
pub mod texture_set;

View File

@ -271,6 +271,40 @@ impl SpriteBatch for SubBatch {
self.add_rect_scaled_tinted(x, y, color, 1.0, 1.0, rect)
}
fn add_rect_flip_tinted(
&mut self,
x: f32,
y: f32,
flip_x: bool,
flip_y: bool,
color: (u8, u8, u8, u8),
rect: &common::Rect<u16>,
) {
if (rect.right.saturating_sub(rect.left)) == 0 || (rect.bottom.saturating_sub(rect.top)) == 0 {
return;
}
let mag = unsafe { I_MAG };
self.batch.add(SpriteBatchCommand::DrawRectFlipTinted(
Rect {
left: rect.left as f32 / self.scale_x,
top: rect.top as f32 / self.scale_y,
right: rect.right as f32 / self.scale_x,
bottom: rect.bottom as f32 / self.scale_y,
},
Rect {
left: x * mag,
top: y * mag,
right: (x + rect.width() as f32) * mag,
bottom: (y + rect.height() as f32) * mag,
},
flip_x,
flip_y,
color.into(),
));
}
fn add_rect_scaled(&mut self, x: f32, y: f32, scale_x: f32, scale_y: f32, rect: &common::Rect<u16>) {
if (rect.right.saturating_sub(rect.left)) == 0 || (rect.bottom.saturating_sub(rect.top)) == 0 {
return;
@ -326,40 +360,6 @@ impl SpriteBatch for SubBatch {
));
}
fn add_rect_flip_tinted(
&mut self,
x: f32,
y: f32,
flip_x: bool,
flip_y: bool,
color: (u8, u8, u8, u8),
rect: &common::Rect<u16>,
) {
if (rect.right.saturating_sub(rect.left)) == 0 || (rect.bottom.saturating_sub(rect.top)) == 0 {
return;
}
let mag = unsafe { I_MAG };
self.batch.add(SpriteBatchCommand::DrawRectFlipTinted(
Rect {
left: rect.left as f32 / self.scale_x,
top: rect.top as f32 / self.scale_y,
right: rect.right as f32 / self.scale_x,
bottom: rect.bottom as f32 / self.scale_y,
},
Rect {
left: x * mag,
top: y * mag,
right: (x + rect.width() as f32) * mag,
bottom: (y + rect.height() as f32) * mag,
},
flip_x,
flip_y,
color.into(),
));
}
#[inline(always)]
fn draw(&mut self, ctx: &mut Context) -> GameResult {
self.draw_filtered(FilterMode::Nearest, ctx)
@ -434,6 +434,18 @@ impl SpriteBatch for CombinedBatch {
self.main_batch.add_rect_tinted(x, y, color, rect)
}
fn add_rect_flip_tinted(
&mut self,
x: f32,
y: f32,
flip_x: bool,
flip_y: bool,
color: (u8, u8, u8, u8),
rect: &common::Rect<u16>,
) {
self.main_batch.add_rect_flip_tinted(x, y, flip_x, flip_y, color, rect)
}
fn add_rect_scaled(&mut self, x: f32, y: f32, scale_x: f32, scale_y: f32, rect: &Rect<u16>) {
self.main_batch.add_rect_scaled(x, y, scale_x, scale_y, rect)
}
@ -450,18 +462,6 @@ impl SpriteBatch for CombinedBatch {
self.main_batch.add_rect_scaled_tinted(x, y, color, scale_x, scale_y, rect)
}
fn add_rect_flip_tinted(
&mut self,
x: f32,
y: f32,
flip_x: bool,
flip_y: bool,
color: (u8, u8, u8, u8),
rect: &common::Rect<u16>,
) {
self.main_batch.add_rect_flip_tinted(x, y, flip_x, flip_y, color, rect)
}
fn draw(&mut self, ctx: &mut Context) -> GameResult {
self.main_batch.draw(ctx)
}

View File

@ -12,6 +12,21 @@ pub struct Locale {
strings: HashMap<String, String>,
}
impl Default for Locale {
fn default() -> Self {
Locale {
code: "en".to_owned(),
name: "English".to_owned(),
font: FontData {
path: String::new(),
scale: 1.0,
space_offset: 0.0
},
strings: HashMap::new(),
}
}
}
impl Locale {
pub fn new(ctx: &mut Context, base_paths: &Vec<String>, code: &str) -> Locale {
let file = filesystem::open_find(ctx, base_paths, &format!("locale/{}.json", code)).unwrap();
@ -50,12 +65,16 @@ impl Locale {
strings
}
pub fn t(&self, key: &str) -> String {
self.strings.get(key).unwrap_or(&key.to_owned()).to_owned()
pub fn t<'a: 'b, 'b>(&'a self, key: &'b str) -> &'b str {
if let Some(str) = self.strings.get(key) {
str
} else {
key
}
}
pub fn tt(&self, key: &str, args: &[(&str, &str)]) -> String {
let mut string = self.t(key);
let mut string = self.t(key).to_owned();
for (key, value) in args.iter() {
string = string.replace(&format!("{{{}}}", key), &value);

View File

@ -3,7 +3,7 @@ use crate::framework::error::GameResult;
use crate::framework::gamepad::{self, Axis, AxisDirection, Button, PlayerControllerInputType};
use crate::framework::keyboard::ScanCode;
use crate::game::settings::{
ControllerType, p1_default_keymap, p2_default_keymap, player_default_controller_button_map,
p1_default_keymap, p2_default_keymap, player_default_controller_button_map, ControllerType,
PlayerControllerButtonMap, PlayerKeyMap,
};
use crate::game::shared_game_state::SharedGameState;
@ -126,21 +126,22 @@ enum ControlEntry {
impl ControlEntry {
fn to_string(&self, state: &SharedGameState) -> String {
match self {
ControlEntry::Left => state.t("menus.controls_menu.rebind_menu.left"),
ControlEntry::Up => state.t("menus.controls_menu.rebind_menu.up"),
ControlEntry::Right => state.t("menus.controls_menu.rebind_menu.right"),
ControlEntry::Down => state.t("menus.controls_menu.rebind_menu.down"),
ControlEntry::PrevWeapon => state.t("menus.controls_menu.rebind_menu.prev_weapon"),
ControlEntry::NextWeapon => state.t("menus.controls_menu.rebind_menu.next_weapon"),
ControlEntry::Jump => state.t("menus.controls_menu.rebind_menu.jump"),
ControlEntry::Shoot => state.t("menus.controls_menu.rebind_menu.shoot"),
ControlEntry::Skip => state.t("menus.controls_menu.rebind_menu.skip"),
ControlEntry::Inventory => state.t("menus.controls_menu.rebind_menu.inventory"),
ControlEntry::Map => state.t("menus.controls_menu.rebind_menu.map"),
ControlEntry::Strafe => state.t("menus.controls_menu.rebind_menu.strafe"),
ControlEntry::MenuOk => state.t("menus.controls_menu.rebind_menu.menu_ok"),
ControlEntry::MenuBack => state.t("menus.controls_menu.rebind_menu.menu_back"),
ControlEntry::Left => state.loc.t("menus.controls_menu.rebind_menu.left"),
ControlEntry::Up => state.loc.t("menus.controls_menu.rebind_menu.up"),
ControlEntry::Right => state.loc.t("menus.controls_menu.rebind_menu.right"),
ControlEntry::Down => state.loc.t("menus.controls_menu.rebind_menu.down"),
ControlEntry::PrevWeapon => state.loc.t("menus.controls_menu.rebind_menu.prev_weapon"),
ControlEntry::NextWeapon => state.loc.t("menus.controls_menu.rebind_menu.next_weapon"),
ControlEntry::Jump => state.loc.t("menus.controls_menu.rebind_menu.jump"),
ControlEntry::Shoot => state.loc.t("menus.controls_menu.rebind_menu.shoot"),
ControlEntry::Skip => state.loc.t("menus.controls_menu.rebind_menu.skip"),
ControlEntry::Inventory => state.loc.t("menus.controls_menu.rebind_menu.inventory"),
ControlEntry::Map => state.loc.t("menus.controls_menu.rebind_menu.map"),
ControlEntry::Strafe => state.loc.t("menus.controls_menu.rebind_menu.strafe"),
ControlEntry::MenuOk => state.loc.t("menus.controls_menu.rebind_menu.menu_ok"),
ControlEntry::MenuBack => state.loc.t("menus.controls_menu.rebind_menu.menu_back"),
}
.to_owned()
}
}
@ -197,27 +198,34 @@ impl ControlsMenu {
self.main.push_entry(
MainMenuEntry::SelectedPlayer,
MenuEntry::Options(
state.t("menus.controls_menu.select_player.entry"),
state.loc.t("menus.controls_menu.select_player.entry").to_owned(),
self.selected_player as usize,
vec![
state.t("menus.controls_menu.select_player.player_1"),
state.t("menus.controls_menu.select_player.player_2"),
state.loc.t("menus.controls_menu.select_player.player_1").to_owned(),
state.loc.t("menus.controls_menu.select_player.player_2").to_owned(),
],
),
);
self.main
.push_entry(MainMenuEntry::Controller, MenuEntry::Active(state.t("menus.controls_menu.controller.entry")));
self.main.push_entry(MainMenuEntry::Rebind, MenuEntry::Active(state.t("menus.controls_menu.rebind")));
self.main.push_entry(
MainMenuEntry::Controller,
MenuEntry::Active(state.loc.t("menus.controls_menu.controller.entry").to_owned()),
);
self.main.push_entry(
MainMenuEntry::Rebind,
MenuEntry::Active(state.loc.t("menus.controls_menu.rebind").to_owned()),
);
self.main.push_entry(MainMenuEntry::Rumble, MenuEntry::Hidden);
self.main.push_entry(MainMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
self.main.push_entry(MainMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
self.confirm_reset.push_entry(
ConfirmResetMenuEntry::Title,
MenuEntry::Disabled(state.t("menus.controls_menu.reset_confirm_menu_title")),
MenuEntry::Disabled(state.loc.t("menus.controls_menu.reset_confirm_menu_title").to_owned()),
);
self.confirm_reset.push_entry(ConfirmResetMenuEntry::Yes, MenuEntry::Active(state.t("common.yes")));
self.confirm_reset.push_entry(ConfirmResetMenuEntry::No, MenuEntry::Active(state.t("common.no")));
self.confirm_reset
.push_entry(ConfirmResetMenuEntry::Yes, MenuEntry::Active(state.loc.t("common.yes").to_owned()));
self.confirm_reset
.push_entry(ConfirmResetMenuEntry::No, MenuEntry::Active(state.loc.t("common.no").to_owned()));
self.player1_key_map = self.init_key_map(&state.settings.player1_key_map);
self.player2_key_map = self.init_key_map(&state.settings.player2_key_map);
@ -375,8 +383,11 @@ impl ControlsMenu {
}
}
self.rebind.push_entry(RebindMenuEntry::Reset, MenuEntry::Active(state.t("menus.controls_menu.reset_confirm")));
self.rebind.push_entry(RebindMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
self.rebind.push_entry(
RebindMenuEntry::Reset,
MenuEntry::Active(state.loc.t("menus.controls_menu.reset_confirm").to_owned()),
);
self.rebind.push_entry(RebindMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
}
fn update_controller_options(&mut self, state: &SharedGameState, ctx: &Context) {
@ -384,7 +395,7 @@ impl ControlsMenu {
self.select_controller.push_entry(
SelectControllerMenuEntry::Keyboard,
MenuEntry::Active(state.t("menus.controls_menu.controller.keyboard")),
MenuEntry::Active(state.loc.t("menus.controls_menu.controller.keyboard").to_owned()),
);
let gamepads = gamepad::get_gamepads(ctx);
@ -410,7 +421,8 @@ impl ControlsMenu {
);
}
self.select_controller.push_entry(SelectControllerMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
self.select_controller
.push_entry(SelectControllerMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
let controller_type = match self.selected_player {
Player::Player1 => state.settings.player1_controller_type,
@ -428,8 +440,10 @@ impl ControlsMenu {
self.main.set_entry(MainMenuEntry::Rumble, MenuEntry::Hidden);
} else {
self.selected_controller = controller_type;
self.main
.set_entry(MainMenuEntry::Rumble, MenuEntry::Toggle(state.t("menus.controls_menu.rumble"), rumble));
self.main.set_entry(
MainMenuEntry::Rumble,
MenuEntry::Toggle(state.loc.t("menus.controls_menu.rumble").to_owned(), rumble),
);
}
} else {
self.selected_controller = controller_type;
@ -456,8 +470,10 @@ impl ControlsMenu {
&[("control", control.to_string(state).as_str())],
)),
);
self.confirm_rebind
.push_entry(1, MenuEntry::Disabled(state.t("menus.controls_menu.rebind_confirm_menu.cancel")));
self.confirm_rebind.push_entry(
1,
MenuEntry::Disabled(state.loc.t("menus.controls_menu.rebind_confirm_menu.cancel").to_owned()),
);
}
None => {}
}

View File

@ -59,23 +59,23 @@ impl PlayerCountMenu {
self.coop_menu = Menu::new(0, 0, 130, 0);
self.skin_menu = Menu::new(0, 0, 130, 0);
self.coop_menu.push_entry(CoopMenuEntry::Title, MenuEntry::Disabled(state.t("menus.coop_menu.title")));
self.coop_menu.push_entry(CoopMenuEntry::One, MenuEntry::Active(state.t("menus.coop_menu.one")));
self.coop_menu.push_entry(CoopMenuEntry::Two, MenuEntry::Active(state.t("menus.coop_menu.two")));
self.coop_menu.push_entry(CoopMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
self.coop_menu.push_entry(CoopMenuEntry::Title, MenuEntry::Disabled(state.loc.t("menus.coop_menu.title").to_owned()));
self.coop_menu.push_entry(CoopMenuEntry::One, MenuEntry::Active(state.loc.t("menus.coop_menu.one").to_owned()));
self.coop_menu.push_entry(CoopMenuEntry::Two, MenuEntry::Active(state.loc.t("menus.coop_menu.two").to_owned()));
self.coop_menu.push_entry(CoopMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
self.coop_menu.selected = CoopMenuEntry::One;
self.skin_menu.push_entry(SkinMenuEntry::Title, MenuEntry::Disabled(state.t("menus.skin_menu.title")));
self.skin_menu.push_entry(SkinMenuEntry::Title, MenuEntry::Disabled(state.loc.t("menus.skin_menu.title").to_owned()));
self.skin_menu.push_entry(SkinMenuEntry::Skin, MenuEntry::PlayerSkin);
if self.on_title {
self.skin_menu.push_entry(SkinMenuEntry::Start, MenuEntry::Active(state.t("menus.main_menu.start")));
self.skin_menu.push_entry(SkinMenuEntry::Start, MenuEntry::Active(state.loc.t("menus.main_menu.start").to_owned()));
} else {
self.skin_menu.push_entry(SkinMenuEntry::Add, MenuEntry::Active(state.t("menus.pause_menu.add_player2")));
self.skin_menu.push_entry(SkinMenuEntry::Add, MenuEntry::Active(state.loc.t("menus.pause_menu.add_player2").to_owned()));
}
self.skin_menu.push_entry(SkinMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
self.skin_menu.push_entry(SkinMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
self.skin_menu.selected = SkinMenuEntry::Skin;

View File

@ -1,11 +1,12 @@
use std::cell::Cell;
use crate::common::{Color, Rect};
use crate::components::draw_common::{Alignment, draw_number};
use crate::components::draw_common::{draw_number, Alignment};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::graphics;
use crate::game::shared_game_state::{GameDifficulty, MenuCharacter, SharedGameState};
use crate::graphics::font::Font;
use crate::input::combined_menu_controller::CombinedMenuController;
use crate::menu::save_select_menu::MenuSaveInfo;
@ -147,22 +148,21 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
match entry {
MenuEntry::Hidden => {}
MenuEntry::Active(entry) | MenuEntry::DisabledWhite(entry) | MenuEntry::Disabled(entry) => {
let entry_width = state.font.text_width(entry.chars(), &state.constants) + 32.0;
let entry_width = state.font.builder().compute_width(&entry) + 32.0;
width = width.max(entry_width);
}
MenuEntry::Toggle(entry, _) => {
let mut entry_with_option = entry.clone();
entry_with_option.push_str(" ");
let longest_option_width = if state.t("common.off").len() > state.t("common.on").len() {
state.font.text_width(state.t("common.off").chars(), &state.constants)
let longest_option_width = if state.loc.t("common.off").len() > state.loc.t("common.on").len() {
state.font.builder().compute_width(state.loc.t("common.off"))
} else {
state.font.text_width(state.t("common.on").chars(), &state.constants)
state.font.builder().compute_width(state.loc.t("common.on"))
};
let entry_width = state.font.text_width(entry_with_option.chars(), &state.constants)
+ longest_option_width
+ 32.0;
let entry_width =
state.font.builder().compute_width(&entry_with_option) + longest_option_width + 32.0;
width = width.max(entry_width);
}
MenuEntry::Options(entry, _, options) => {
@ -172,7 +172,7 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
let longest_option = options.iter().max_by(|&a, &b| a.len().cmp(&b.len())).unwrap();
entry_with_option.push_str(longest_option);
let entry_width = state.font.text_width(entry_with_option.chars(), &state.constants) + 32.0;
let entry_width = state.font.builder().compute_width(&entry_with_option) + 32.0;
width = width.max(entry_width);
}
MenuEntry::DescriptiveOptions(entry, _, options, descriptions) => {
@ -182,16 +182,16 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
let longest_option = options.iter().max_by(|&a, &b| a.len().cmp(&b.len())).unwrap();
entry_with_option.push_str(longest_option);
let entry_width = state.font.text_width(entry_with_option.chars(), &state.constants) + 32.0;
let entry_width = state.font.builder().compute_width(&entry_with_option) + 32.0;
width = width.max(entry_width);
let longest_description = descriptions.iter().max_by(|&a, &b| a.len().cmp(&b.len())).unwrap();
let description_width = state.font.text_width(longest_description.chars(), &state.constants) + 32.0;
let description_width = state.font.builder().compute_width(longest_description) + 32.0;
width = width.max(description_width);
}
MenuEntry::OptionsBar(entry, _) => {
let bar_width = if state.constants.is_switch { 81.0 } else { 109.0 };
let entry_width = state.font.text_width(entry.chars(), &state.constants) + 32.0 + bar_width;
let entry_width = state.font.builder().compute_width(entry) + 32.0 + bar_width;
width = width.max(entry_width);
}
MenuEntry::SaveData(_) => {}
@ -375,112 +375,62 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
for (_, entry) in &self.entries {
match entry {
MenuEntry::Active(name) | MenuEntry::DisabledWhite(name) => {
state.font.draw_text(
name.chars(),
self.x as f32 + 20.0,
y,
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.builder()
.position(self.x as f32 + 20.0, y)
.draw(name, ctx, &state.constants, &mut state.texture_set)?;
}
MenuEntry::Disabled(name) => {
state.font.draw_colored_text(
name.chars(),
self.x as f32 + 20.0,
y,
(0xa0, 0xa0, 0xff, 0xff),
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.builder()
.position(self.x as f32 + 20.0, y)
.color((0xa0, 0xa0, 0xff, 0xff))
.draw(name, ctx, &state.constants, &mut state.texture_set)?;
}
MenuEntry::Toggle(name, value) => {
let value_text = if *value { "ON" } else { "OFF" };
let name_text_len = state.font.text_width(name.chars(), &state.constants);
let name_text_len = state.font.builder().compute_width(name);
state.font.draw_text(
name.chars(),
self.x as f32 + 20.0,
y,
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.builder()
.position(self.x as f32 + 20.0, y)
.draw(name, ctx, &state.constants, &mut state.texture_set)?;
state.font.draw_text(
value_text.chars(),
self.x as f32 + 25.0 + name_text_len,
y,
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.builder()
.position(self.x as f32 + 25.0 + name_text_len, y)
.draw(value_text, ctx, &state.constants, &mut state.texture_set)?;
}
MenuEntry::Options(name, index, value) => {
let value_text = if let Some(text) = value.get(*index) { text } else { "???" };
let name_text_len = state.font.text_width(name.chars(), &state.constants);
let name_text_len = state.font.builder().compute_width(name);
state.font.draw_text(
name.chars(),
self.x as f32 + 20.0,
y,
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.builder()
.position(self.x as f32 + 20.0, y)
.draw(name, ctx, &state.constants, &mut state.texture_set)?;
state.font.draw_text(
value_text.chars(),
self.x as f32 + 25.0 + name_text_len,
y,
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.builder()
.position(self.x as f32 + 25.0 + name_text_len, y)
.draw(value_text, ctx, &state.constants, &mut state.texture_set)?;
}
MenuEntry::DescriptiveOptions(name, index, value, description) => {
let value_text = if let Some(text) = value.get(*index) { text } else { "???" };
let description_text = if let Some(text) = description.get(*index) { text } else { "???" };
let name_text_len = state.font.text_width(name.chars(), &state.constants);
let name_text_len = state.font.builder().compute_width(name);
state.font.draw_text(
name.chars(),
self.x as f32 + 20.0,
y,
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.builder()
.position(self.x as f32 + 20.0, y)
.draw(name, ctx, &state.constants, &mut state.texture_set)?;
state.font.draw_text(
value_text.chars(),
self.x as f32 + 25.0 + name_text_len,
y,
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.builder()
.position(self.x as f32 + 25.0 + name_text_len, y)
.draw(value_text, ctx, &state.constants, &mut state.texture_set)?;
state.font.draw_colored_text(
description_text.chars(),
self.x as f32 + 20.0,
y + 16.0,
(0xc0, 0xc0, 0xff, 0xff),
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.builder()
.position(self.x as f32 + 20.0, y + 16.0)
.color((0xc0, 0xc0, 0xff, 0xff))
.draw(description_text, ctx, &state.constants, &mut state.texture_set)?;
}
MenuEntry::OptionsBar(name, percent) => {
state.font.draw_text(
name.chars(),
self.x as f32 + 20.0,
y,
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.builder()
.position(self.x as f32 + 20.0, y)
.draw(name, ctx, &state.constants, &mut state.texture_set)?;
if state.constants.is_switch || state.constants.is_cs_plus {
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "ui")?;
@ -519,24 +469,14 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
}
}
MenuEntry::NewSave => {
state.font.draw_text(
state.t("menus.save_menu.new").chars(),
self.x as f32 + 20.0,
y,
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.builder()
.position(self.x as f32 + 20.0, y)
.draw(state.loc.t("menus.save_menu.new"), ctx, &state.constants, &mut state.texture_set)?;
}
MenuEntry::PlayerSkin => {
state.font.draw_text(
state.t("menus.skin_menu.label").chars(),
self.x as f32 + 20.0,
y,
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.builder()
.position(self.x as f32 + 20.0, y)
.draw(state.loc.t("menus.skin_menu.label"), ctx, &state.constants, &mut state.texture_set)?;
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "MyChar")?;
batch.add_rect(
@ -549,21 +489,16 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
MenuEntry::SaveData(save) | MenuEntry::SaveDataSingle(save) => {
let valid_save = state.stages.get(save.current_map as usize).is_some();
let name = if valid_save {
state.stages.get(save.current_map as usize).unwrap().name.clone()
state.stages.get(save.current_map as usize).unwrap().name.as_str()
} else {
state.t("menus.save_menu.invalid_save")
state.loc.t("menus.save_menu.invalid_save")
};
let bar_width = (save.life as f32 / save.max_life as f32 * 39.0) as u16;
let right_edge = self.x as f32 + self.width as f32 - 4.0;
state.font.draw_text(
name.chars(),
self.x as f32 + 20.0,
y,
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.builder()
.position(self.x as f32 + 20.0, y)
.draw(name, ctx, &state.constants, &mut state.texture_set)?;
if valid_save {
// Lifebar
@ -594,14 +529,9 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
_ => difficulty_name.push_str("(unknown)"),
}
state.font.draw_text(
difficulty_name.chars(),
self.x as f32 + 20.0,
y + 10.0,
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.builder()
.position(self.x as f32 + 20.0, y + 10.0)
.draw(difficulty_name.as_str(), ctx, &state.constants, &mut state.texture_set)?;
}
// Weapons
@ -624,31 +554,21 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
}
}
MenuEntry::Control(name, data) => {
state.font.draw_text(
name.chars(),
self.x as f32 + 20.0,
y,
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.builder()
.position(self.x as f32 + 20.0, y)
.draw(name, ctx, &state.constants, &mut state.texture_set)?;
match data {
ControlMenuData::String(value) => {
let text_width = state.font.text_width(value.chars(), &state.constants);
let text_width = state.font.builder().compute_width(value);
state.font.draw_text(
value.chars(),
self.x as f32 + self.width as f32 - 5.0 - text_width,
y,
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.builder()
.position(self.x as f32 + self.width as f32 - 5.0 - text_width, y)
.draw(value, ctx, &state.constants, &mut state.texture_set)?;
}
ControlMenuData::Rect(value) => {
let rect_width = value.width() as f32;
let y = y + rect.height() as f32 / 2.0 - state.font.line_height(&state.constants) + 4.0;
let y = y + rect.height() as f32 / 2.0 - state.font.line_height() + 4.0;
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "buttons")?;
batch.add_rect(self.x as f32 + self.width as f32 - 5.0 - rect_width, y, &value);
@ -750,41 +670,41 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
| MenuEntry::SaveData(_)
| MenuEntry::NewSave
| MenuEntry::PlayerSkin
if (self.selected == idx && controller.trigger_ok())
|| state.touch_controls.consume_click_in(entry_bounds) =>
{
state.sound_manager.play_sfx(18);
self.selected = idx.clone();
return MenuSelectionResult::Selected(idx, entry);
}
if (self.selected == idx && controller.trigger_ok())
|| state.touch_controls.consume_click_in(entry_bounds) =>
{
state.sound_manager.play_sfx(18);
self.selected = idx.clone();
return MenuSelectionResult::Selected(idx, entry);
}
MenuEntry::Options(_, _, _) | MenuEntry::OptionsBar(_, _)
if (self.selected == idx && controller.trigger_left())
|| state.touch_controls.consume_click_in(left_entry_bounds) =>
{
state.sound_manager.play_sfx(1);
return MenuSelectionResult::Left(self.selected.clone(), entry, -1);
}
if (self.selected == idx && controller.trigger_left())
|| state.touch_controls.consume_click_in(left_entry_bounds) =>
{
state.sound_manager.play_sfx(1);
return MenuSelectionResult::Left(self.selected.clone(), entry, -1);
}
MenuEntry::Options(_, _, _) | MenuEntry::OptionsBar(_, _)
if (self.selected == idx && controller.trigger_right())
|| state.touch_controls.consume_click_in(right_entry_bounds) =>
{
state.sound_manager.play_sfx(1);
return MenuSelectionResult::Right(self.selected.clone(), entry, 1);
}
if (self.selected == idx && controller.trigger_right())
|| state.touch_controls.consume_click_in(right_entry_bounds) =>
{
state.sound_manager.play_sfx(1);
return MenuSelectionResult::Right(self.selected.clone(), entry, 1);
}
MenuEntry::DescriptiveOptions(_, _, _, _)
if (self.selected == idx && controller.trigger_left())
|| state.touch_controls.consume_click_in(left_entry_bounds) =>
{
state.sound_manager.play_sfx(1);
return MenuSelectionResult::Left(self.selected.clone(), entry, -1);
}
if (self.selected == idx && controller.trigger_left())
|| state.touch_controls.consume_click_in(left_entry_bounds) =>
{
state.sound_manager.play_sfx(1);
return MenuSelectionResult::Left(self.selected.clone(), entry, -1);
}
MenuEntry::DescriptiveOptions(_, _, _, _) | MenuEntry::SaveData(_)
if (self.selected == idx && controller.trigger_right())
|| state.touch_controls.consume_click_in(right_entry_bounds) =>
{
state.sound_manager.play_sfx(1);
return MenuSelectionResult::Right(self.selected.clone(), entry, 1);
}
if (self.selected == idx && controller.trigger_right())
|| state.touch_controls.consume_click_in(right_entry_bounds) =>
{
state.sound_manager.play_sfx(1);
return MenuSelectionResult::Right(self.selected.clone(), entry, 1);
}
MenuEntry::Control(_, _) => {
if self.selected == idx && controller.trigger_ok()
|| state.touch_controls.consume_click_in(entry_bounds)

View File

@ -84,17 +84,17 @@ impl PauseMenu {
self.controller.add(state.settings.create_player1_controller());
self.controller.add(state.settings.create_player2_controller());
self.pause_menu.push_entry(PauseMenuEntry::Resume, MenuEntry::Active(state.t("menus.pause_menu.resume")));
self.pause_menu.push_entry(PauseMenuEntry::Retry, MenuEntry::Active(state.t("menus.pause_menu.retry")));
self.pause_menu.push_entry(PauseMenuEntry::Resume, MenuEntry::Active(state.loc.t("menus.pause_menu.resume").to_owned()));
self.pause_menu.push_entry(PauseMenuEntry::Retry, MenuEntry::Active(state.loc.t("menus.pause_menu.retry").to_owned()));
self.pause_menu.push_entry(PauseMenuEntry::AddPlayer2, MenuEntry::Hidden);
self.pause_menu.push_entry(PauseMenuEntry::DropPlayer2, MenuEntry::Hidden);
self.pause_menu.push_entry(PauseMenuEntry::Settings, MenuEntry::Active(state.t("menus.pause_menu.options")));
self.pause_menu.push_entry(PauseMenuEntry::Title, MenuEntry::Active(state.t("menus.pause_menu.title")));
self.pause_menu.push_entry(PauseMenuEntry::Quit, MenuEntry::Active(state.t("menus.pause_menu.quit")));
self.pause_menu.push_entry(PauseMenuEntry::Settings, MenuEntry::Active(state.loc.t("menus.pause_menu.options").to_owned()));
self.pause_menu.push_entry(PauseMenuEntry::Title, MenuEntry::Active(state.loc.t("menus.pause_menu.title").to_owned()));
self.pause_menu.push_entry(PauseMenuEntry::Quit, MenuEntry::Active(state.loc.t("menus.pause_menu.quit").to_owned()));
self.confirm_menu.push_entry(ConfirmMenuEntry::Empty, MenuEntry::Disabled("".to_owned()));
self.confirm_menu.push_entry(ConfirmMenuEntry::Yes, MenuEntry::Active(state.t("common.yes")));
self.confirm_menu.push_entry(ConfirmMenuEntry::No, MenuEntry::Active(state.t("common.no")));
self.confirm_menu.push_entry(ConfirmMenuEntry::Empty, MenuEntry::Disabled(String::new()));
self.confirm_menu.push_entry(ConfirmMenuEntry::Yes, MenuEntry::Active(state.loc.t("common.yes").to_owned()));
self.confirm_menu.push_entry(ConfirmMenuEntry::No, MenuEntry::Active(state.loc.t("common.no").to_owned()));
self.confirm_menu.selected = ConfirmMenuEntry::Yes;
@ -129,7 +129,7 @@ impl PauseMenu {
match state.player_count {
PlayerCount::One => {
self.pause_menu
.set_entry(PauseMenuEntry::AddPlayer2, MenuEntry::Active(state.t("menus.pause_menu.add_player2")));
.set_entry(PauseMenuEntry::AddPlayer2, MenuEntry::Active(state.loc.t("menus.pause_menu.add_player2").to_owned()));
self.pause_menu.set_entry(PauseMenuEntry::DropPlayer2, MenuEntry::Hidden);
if self.pause_menu.selected == PauseMenuEntry::DropPlayer2 {
@ -140,7 +140,7 @@ impl PauseMenu {
self.pause_menu.set_entry(PauseMenuEntry::AddPlayer2, MenuEntry::Hidden);
self.pause_menu.set_entry(
PauseMenuEntry::DropPlayer2,
MenuEntry::Active(state.t("menus.pause_menu.drop_player2")),
MenuEntry::Active(state.loc.t("menus.pause_menu.drop_player2").to_owned()),
);
if self.pause_menu.selected == PauseMenuEntry::AddPlayer2 {
@ -211,14 +211,14 @@ impl PauseMenu {
MenuSelectionResult::Selected(PauseMenuEntry::Title, _) => {
self.confirm_menu.set_entry(
ConfirmMenuEntry::Empty,
MenuEntry::Disabled(state.t("menus.pause_menu.title_confirm")),
MenuEntry::Disabled(state.loc.t("menus.pause_menu.title_confirm").to_owned()),
);
self.current_menu = CurrentMenu::ConfirmMenu;
}
MenuSelectionResult::Selected(PauseMenuEntry::Quit, _) => {
self.confirm_menu.set_entry(
ConfirmMenuEntry::Empty,
MenuEntry::Disabled(state.t("menus.pause_menu.quit_confirm")),
MenuEntry::Disabled(state.loc.t("menus.pause_menu.quit_confirm").to_owned()),
);
self.current_menu = CurrentMenu::ConfirmMenu;
}

View File

@ -127,7 +127,7 @@ impl SaveSelectMenu {
let mut should_mutate_selection = true;
for (iter, save) in self.saves.iter_mut().enumerate() {
if let Ok(data) = filesystem::user_open(ctx, state.get_save_filename(iter + 1).unwrap_or("".to_string())) {
if let Ok(data) = filesystem::user_open(ctx, state.get_save_filename(iter + 1).unwrap_or(String::new())) {
let loaded_save = GameProfile::load_from_save(data)?;
save.current_map = loaded_save.current_map;
@ -153,39 +153,39 @@ impl SaveSelectMenu {
}
}
self.save_menu.push_entry(SaveMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
self.save_menu.push_entry(SaveMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
self.difficulty_menu
.push_entry(DifficultyMenuEntry::Title, MenuEntry::Disabled(state.t("menus.difficulty_menu.title")));
.push_entry(DifficultyMenuEntry::Title, MenuEntry::Disabled(state.loc.t("menus.difficulty_menu.title").to_owned()));
self.difficulty_menu.push_entry(
DifficultyMenuEntry::Difficulty(GameDifficulty::Easy),
MenuEntry::Active(state.t("menus.difficulty_menu.easy")),
MenuEntry::Active(state.loc.t("menus.difficulty_menu.easy").to_owned()),
);
self.difficulty_menu.push_entry(
DifficultyMenuEntry::Difficulty(GameDifficulty::Normal),
MenuEntry::Active(state.t("menus.difficulty_menu.normal")),
MenuEntry::Active(state.loc.t("menus.difficulty_menu.normal").to_owned()),
);
self.difficulty_menu.push_entry(
DifficultyMenuEntry::Difficulty(GameDifficulty::Hard),
MenuEntry::Active(state.t("menus.difficulty_menu.hard")),
MenuEntry::Active(state.loc.t("menus.difficulty_menu.hard").to_owned()),
);
self.difficulty_menu.push_entry(DifficultyMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
self.difficulty_menu.push_entry(DifficultyMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
self.difficulty_menu.selected = DifficultyMenuEntry::Difficulty(GameDifficulty::Normal);
//self.coop_menu.init(state, ctx);
self.delete_confirm
.push_entry(DeleteConfirmMenuEntry::Title, MenuEntry::Disabled(state.t("menus.save_menu.delete_confirm")));
self.delete_confirm.push_entry(DeleteConfirmMenuEntry::Yes, MenuEntry::Active(state.t("common.yes")));
self.delete_confirm.push_entry(DeleteConfirmMenuEntry::No, MenuEntry::Active(state.t("common.no")));
.push_entry(DeleteConfirmMenuEntry::Title, MenuEntry::Disabled(state.loc.t("menus.save_menu.delete_confirm").to_owned()));
self.delete_confirm.push_entry(DeleteConfirmMenuEntry::Yes, MenuEntry::Active(state.loc.t("common.yes").to_owned()));
self.delete_confirm.push_entry(DeleteConfirmMenuEntry::No, MenuEntry::Active(state.loc.t("common.no").to_owned()));
self.delete_confirm.selected = DeleteConfirmMenuEntry::No;
self.load_confirm.push_entry(LoadConfirmMenuEntry::Start, MenuEntry::Active(state.t("menus.main_menu.start")));
self.load_confirm.push_entry(LoadConfirmMenuEntry::Start, MenuEntry::Active(state.loc.t("menus.main_menu.start").to_owned()));
self.load_confirm
.push_entry(LoadConfirmMenuEntry::Delete, MenuEntry::Active(state.t("menus.save_menu.delete_confirm")));
self.load_confirm.push_entry(LoadConfirmMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
.push_entry(LoadConfirmMenuEntry::Delete, MenuEntry::Active(state.loc.t("menus.save_menu.delete_confirm").to_owned()));
self.load_confirm.push_entry(LoadConfirmMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
self.save_detailed.draw_cursor = false;
@ -255,7 +255,7 @@ impl SaveSelectMenu {
state.save_slot = slot + 1;
if let Ok(_) =
filesystem::user_open(ctx, state.get_save_filename(state.save_slot).unwrap_or("".to_string()))
filesystem::user_open(ctx, state.get_save_filename(state.save_slot).unwrap_or(String::new()))
{
if let (_, MenuEntry::SaveData(save)) = self.save_menu.entries[slot] {
self.save_detailed.entries.clear();
@ -295,7 +295,7 @@ impl SaveSelectMenu {
match self.save_menu.selected {
SaveMenuEntry::Load(slot) => {
state.sound_manager.play_sfx(17); // Player Death sfx
filesystem::user_delete(ctx, state.get_save_filename(slot + 1).unwrap_or("".to_string()))?;
filesystem::user_delete(ctx, state.get_save_filename(slot + 1).unwrap_or(String::new()))?;
}
_ => (),
}

View File

@ -1,13 +1,14 @@
use itertools::Itertools;
use crate::framework::{filesystem, graphics};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::graphics::VSyncMode;
use crate::framework::{filesystem, graphics};
use crate::game::shared_game_state::{CutsceneSkipMode, ScreenShakeIntensity, SharedGameState, TimingMode, WindowMode};
use crate::graphics::font::Font;
use crate::input::combined_menu_controller::CombinedMenuController;
use crate::menu::{Menu, MenuSelectionResult};
use crate::menu::MenuEntry;
use crate::menu::{Menu, MenuSelectionResult};
use crate::scene::title_scene::TitleScene;
use crate::sound::InterpolationMode;
@ -181,14 +182,14 @@ impl SettingsMenu {
pub fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
#[cfg(not(target_os = "android"))]
self.graphics.push_entry(
self.graphics.push_entry(
GraphicsMenuEntry::WindowMode,
MenuEntry::Options(
state.t("menus.options_menu.graphics_menu.window_mode.entry"),
state.loc.t("menus.options_menu.graphics_menu.window_mode.entry").to_owned(),
state.settings.window_mode as usize,
vec![
state.t("menus.options_menu.graphics_menu.window_mode.windowed"),
state.t("menus.options_menu.graphics_menu.window_mode.fullscreen"),
state.loc.t("menus.options_menu.graphics_menu.window_mode.windowed").to_owned(),
state.loc.t("menus.options_menu.graphics_menu.window_mode.fullscreen").to_owned(),
],
),
);
@ -196,58 +197,61 @@ impl SettingsMenu {
self.graphics.push_entry(
GraphicsMenuEntry::VSyncMode,
MenuEntry::DescriptiveOptions(
state.t("menus.options_menu.graphics_menu.vsync_mode.entry"),
state.loc.t("menus.options_menu.graphics_menu.vsync_mode.entry").to_owned(),
state.settings.vsync_mode as usize,
vec![
state.t("menus.options_menu.graphics_menu.vsync_mode.uncapped"),
state.t("menus.options_menu.graphics_menu.vsync_mode.vsync"),
state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_1x"),
state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_2x"),
state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_3x"),
state.loc.t("menus.options_menu.graphics_menu.vsync_mode.uncapped").to_owned(),
state.loc.t("menus.options_menu.graphics_menu.vsync_mode.vsync").to_owned(),
state.loc.t("menus.options_menu.graphics_menu.vsync_mode.vrr_1x").to_owned(),
state.loc.t("menus.options_menu.graphics_menu.vsync_mode.vrr_2x").to_owned(),
state.loc.t("menus.options_menu.graphics_menu.vsync_mode.vrr_3x").to_owned(),
],
vec![
state.t("menus.options_menu.graphics_menu.vsync_mode.uncapped_desc"),
state.t("menus.options_menu.graphics_menu.vsync_mode.vsync_desc"),
state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_1x_desc"),
state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_2x_desc"),
state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_3x_desc"),
state.loc.t("menus.options_menu.graphics_menu.vsync_mode.uncapped_desc").to_owned(),
state.loc.t("menus.options_menu.graphics_menu.vsync_mode.vsync_desc").to_owned(),
state.loc.t("menus.options_menu.graphics_menu.vsync_mode.vrr_1x_desc").to_owned(),
state.loc.t("menus.options_menu.graphics_menu.vsync_mode.vrr_2x_desc").to_owned(),
state.loc.t("menus.options_menu.graphics_menu.vsync_mode.vrr_3x_desc").to_owned(),
],
),
);
self.graphics.push_entry(
GraphicsMenuEntry::LightingEffects,
MenuEntry::Toggle(
state.t("menus.options_menu.graphics_menu.lighting_effects"),
state.loc.t("menus.options_menu.graphics_menu.lighting_effects").to_owned(),
state.settings.shader_effects,
),
);
self.graphics.push_entry(
GraphicsMenuEntry::WeaponLightCone,
MenuEntry::Toggle(state.t("menus.options_menu.graphics_menu.weapon_light_cone"), state.settings.light_cone),
MenuEntry::Toggle(
state.loc.t("menus.options_menu.graphics_menu.weapon_light_cone").to_owned(),
state.settings.light_cone,
),
);
self.graphics.push_entry(
GraphicsMenuEntry::ScreenShake,
MenuEntry::Options(
state.t("menus.options_menu.graphics_menu.screen_shake.entry"),
state.loc.t("menus.options_menu.graphics_menu.screen_shake.entry").to_owned(),
state.settings.screen_shake_intensity as usize,
vec![
state.t("menus.options_menu.graphics_menu.screen_shake.full"),
state.t("menus.options_menu.graphics_menu.screen_shake.half"),
state.t("menus.options_menu.graphics_menu.screen_shake.off"),
state.loc.t("menus.options_menu.graphics_menu.screen_shake.full").to_owned(),
state.loc.t("menus.options_menu.graphics_menu.screen_shake.half").to_owned(),
state.loc.t("menus.options_menu.graphics_menu.screen_shake.off").to_owned(),
],
),
);
self.graphics.push_entry(
GraphicsMenuEntry::MotionInterpolation,
MenuEntry::Toggle(
state.t("menus.options_menu.graphics_menu.motion_interpolation"),
state.loc.t("menus.options_menu.graphics_menu.motion_interpolation").to_owned(),
state.settings.motion_interpolation,
),
);
self.graphics.push_entry(
GraphicsMenuEntry::SubpixelScrolling,
MenuEntry::Toggle(
state.t("menus.options_menu.graphics_menu.subpixel_scrolling"),
state.loc.t("menus.options_menu.graphics_menu.subpixel_scrolling").to_owned(),
state.settings.subpixel_coords,
),
);
@ -258,14 +262,14 @@ impl SettingsMenu {
self.graphics.push_entry(
GraphicsMenuEntry::OriginalTextures,
MenuEntry::Toggle(
state.t("menus.options_menu.graphics_menu.original_textures"),
state.loc.t("menus.options_menu.graphics_menu.original_textures").to_owned(),
state.settings.original_textures,
),
);
} else {
self.graphics.push_entry(
GraphicsMenuEntry::OriginalTextures,
MenuEntry::Disabled(state.t("menus.options_menu.graphics_menu.original_textures")),
MenuEntry::Disabled(state.loc.t("menus.options_menu.graphics_menu.original_textures").to_owned()),
);
}
}
@ -274,7 +278,7 @@ impl SettingsMenu {
self.graphics.push_entry(
GraphicsMenuEntry::SeasonalTextures,
MenuEntry::Toggle(
state.t("menus.options_menu.graphics_menu.seasonal_textures"),
state.loc.t("menus.options_menu.graphics_menu.seasonal_textures").to_owned(),
state.settings.seasonal_textures,
),
);
@ -284,37 +288,47 @@ impl SettingsMenu {
GraphicsMenuEntry::Renderer,
MenuEntry::Disabled(format!(
"{} {}",
state.t("menus.options_menu.graphics_menu.renderer"),
state.loc.t("menus.options_menu.graphics_menu.renderer").to_owned(),
ctx.renderer.as_ref().unwrap().renderer_name()
)),
);
self.graphics.push_entry(GraphicsMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
self.graphics.push_entry(GraphicsMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
self.main.push_entry(MainMenuEntry::Graphics, MenuEntry::Active(state.t("menus.options_menu.graphics")));
self.main.push_entry(MainMenuEntry::Sound, MenuEntry::Active(state.t("menus.options_menu.sound")));
self.main
.push_entry(MainMenuEntry::Graphics, MenuEntry::Active(state.loc.t("menus.options_menu.graphics").to_owned()));
self.main.push_entry(MainMenuEntry::Sound, MenuEntry::Active(state.loc.t("menus.options_menu.sound").to_owned()));
#[cfg(not(target_os = "android"))]
self.main.push_entry(MainMenuEntry::Controls, MenuEntry::Active(state.t("menus.options_menu.controls")));
self.main
.push_entry(MainMenuEntry::Controls, MenuEntry::Active(state.loc.t("menus.options_menu.controls").to_owned()));
self.language.push_entry(LanguageMenuEntry::Title, MenuEntry::Disabled(state.t("menus.options_menu.language")));
self.language.push_entry(
LanguageMenuEntry::Title,
MenuEntry::Disabled(state.loc.t("menus.options_menu.language").to_owned()),
);
for locale in &state.constants.locales {
self.language
.push_entry(LanguageMenuEntry::Language(locale.code.clone()), MenuEntry::Active(locale.name.clone()));
}
self.language.push_entry(LanguageMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
self.language.push_entry(LanguageMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
if self.on_title {
self.main.push_entry(MainMenuEntry::Language, MenuEntry::Active(state.t("menus.options_menu.language")));
self.main.push_entry(
MainMenuEntry::Language,
MenuEntry::Active(state.loc.t("menus.options_menu.language").to_owned()),
);
}
self.main.push_entry(MainMenuEntry::Behavior, MenuEntry::Active(state.t("menus.options_menu.behavior")));
self.main
.push_entry(MainMenuEntry::Behavior, MenuEntry::Active(state.loc.t("menus.options_menu.behavior").to_owned()));
self.main.push_entry(MainMenuEntry::Links, MenuEntry::Active(state.t("menus.options_menu.links")));
self.main.push_entry(MainMenuEntry::Links, MenuEntry::Active(state.loc.t("menus.options_menu.links").to_owned()));
self.links.push_entry(LinksMenuEntry::Title, MenuEntry::Disabled(state.t("menus.options_menu.links")));
self.links
.push_entry(LinksMenuEntry::Title, MenuEntry::Disabled(state.loc.t("menus.options_menu.links").to_owned()));
self.links.push_entry(LinksMenuEntry::Link(DISCORD_LINK), MenuEntry::Active("doukutsu-rs Discord".to_owned()));
self.links.push_entry(LinksMenuEntry::Link(GITHUB_LINK), MenuEntry::Active("doukutsu-rs GitHub".to_owned()));
self.links.push_entry(LinksMenuEntry::Link(DOCS_LINK), MenuEntry::Active("doukutsu-rs Docs".to_owned()));
@ -327,35 +341,35 @@ impl SettingsMenu {
);
self.links.push_entry(LinksMenuEntry::Link(GETPLUS_LINK), MenuEntry::Active("Get Cave Story+".to_owned()));
self.main.push_entry(MainMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
self.main.push_entry(MainMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
self.sound.push_entry(
SoundMenuEntry::MusicVolume,
MenuEntry::OptionsBar(state.t("menus.options_menu.sound_menu.music_volume"), state.settings.bgm_volume),
MenuEntry::OptionsBar(state.loc.t("menus.options_menu.sound_menu.music_volume").to_owned(), state.settings.bgm_volume),
);
self.sound.push_entry(
SoundMenuEntry::EffectsVolume,
MenuEntry::OptionsBar(state.t("menus.options_menu.sound_menu.effects_volume"), state.settings.sfx_volume),
MenuEntry::OptionsBar(state.loc.t("menus.options_menu.sound_menu.effects_volume").to_owned(), state.settings.sfx_volume),
);
self.sound.push_entry(
SoundMenuEntry::BGMInterpolation,
MenuEntry::DescriptiveOptions(
state.t("menus.options_menu.sound_menu.bgm_interpolation.entry"),
state.loc.t("menus.options_menu.sound_menu.bgm_interpolation.entry").to_owned(),
state.settings.organya_interpolation as usize,
vec![
state.t("menus.options_menu.sound_menu.bgm_interpolation.nearest"),
state.t("menus.options_menu.sound_menu.bgm_interpolation.linear"),
state.t("menus.options_menu.sound_menu.bgm_interpolation.cosine"),
state.t("menus.options_menu.sound_menu.bgm_interpolation.cubic"),
state.t("menus.options_menu.sound_menu.bgm_interpolation.linear_lp"),
state.loc.t("menus.options_menu.sound_menu.bgm_interpolation.nearest").to_owned(),
state.loc.t("menus.options_menu.sound_menu.bgm_interpolation.linear").to_owned(),
state.loc.t("menus.options_menu.sound_menu.bgm_interpolation.cosine").to_owned(),
state.loc.t("menus.options_menu.sound_menu.bgm_interpolation.cubic").to_owned(),
state.loc.t("menus.options_menu.sound_menu.bgm_interpolation.linear_lp").to_owned(),
],
vec![
state.t("menus.options_menu.sound_menu.bgm_interpolation.nearest_desc"),
state.t("menus.options_menu.sound_menu.bgm_interpolation.linear_desc"),
state.t("menus.options_menu.sound_menu.bgm_interpolation.cosine_desc"),
state.t("menus.options_menu.sound_menu.bgm_interpolation.cubic_desc"),
state.t("menus.options_menu.sound_menu.bgm_interpolation.linear_lp_desc"),
state.loc.t("menus.options_menu.sound_menu.bgm_interpolation.nearest_desc").to_owned(),
state.loc.t("menus.options_menu.sound_menu.bgm_interpolation.linear_desc").to_owned(),
state.loc.t("menus.options_menu.sound_menu.bgm_interpolation.cosine_desc").to_owned(),
state.loc.t("menus.options_menu.sound_menu.bgm_interpolation.cubic_desc").to_owned(),
state.loc.t("menus.options_menu.sound_menu.bgm_interpolation.linear_lp_desc").to_owned(),
],
),
);
@ -368,7 +382,7 @@ impl SettingsMenu {
),
),
);
self.sound.push_entry(SoundMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
self.sound.push_entry(SoundMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
let mut soundtrack_entries =
state.constants.soundtracks.iter().filter(|s| s.available).map(|s| s.name.to_owned()).collect_vec();
@ -394,21 +408,21 @@ impl SettingsMenu {
self.soundtrack.width = soundtrack_entries
.into_iter()
.map(|str| state.font.text_width(str.chars(), &state.constants))
.map(|str| state.font.builder().compute_width(&str))
.max_by(|a, b| a.abs().partial_cmp(&b.abs()).unwrap())
.unwrap_or(self.soundtrack.width as f32) as u16
+ 32;
self.soundtrack.push_entry(SoundtrackMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
self.soundtrack.push_entry(SoundtrackMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
self.behavior.push_entry(
BehaviorMenuEntry::GameTiming,
MenuEntry::Options(
state.t("menus.options_menu.behavior_menu.game_timing.entry"),
state.loc.t("menus.options_menu.behavior_menu.game_timing.entry").to_owned(),
if state.settings.timing_mode == TimingMode::_50Hz { 0 } else { 1 },
vec![
state.t("menus.options_menu.behavior_menu.game_timing.50tps"),
state.t("menus.options_menu.behavior_menu.game_timing.60tps"),
state.loc.t("menus.options_menu.behavior_menu.game_timing.50tps").to_owned(),
state.loc.t("menus.options_menu.behavior_menu.game_timing.60tps").to_owned(),
],
),
);
@ -416,7 +430,7 @@ impl SettingsMenu {
self.behavior.push_entry(
BehaviorMenuEntry::PauseOnFocusLoss,
MenuEntry::Toggle(
state.t("menus.options_menu.behavior_menu.pause_on_focus_loss"),
state.loc.t("menus.options_menu.behavior_menu.pause_on_focus_loss").to_owned(),
state.settings.pause_on_focus_loss,
),
);
@ -424,18 +438,18 @@ impl SettingsMenu {
self.behavior.push_entry(
BehaviorMenuEntry::CutsceneSkipMode,
MenuEntry::Options(
state.t("menus.options_menu.behavior_menu.cutscene_skip_method.entry"),
state.loc.t("menus.options_menu.behavior_menu.cutscene_skip_method.entry").to_owned(),
if state.settings.cutscene_skip_mode == CutsceneSkipMode::Hold { 0 } else { 1 },
vec![
state.t("menus.options_menu.behavior_menu.cutscene_skip_method.hold"),
state.t("menus.options_menu.behavior_menu.cutscene_skip_method.fastforward"),
state.loc.t("menus.options_menu.behavior_menu.cutscene_skip_method.hold").to_owned(),
state.loc.t("menus.options_menu.behavior_menu.cutscene_skip_method.fastforward").to_owned(),
],
),
);
self.behavior.push_entry(BehaviorMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
self.behavior.push_entry(BehaviorMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
self.links.push_entry(LinksMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
self.links.push_entry(LinksMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
self.controls_menu.init(state, ctx)?;
@ -747,7 +761,7 @@ impl SettingsMenu {
self.current = CurrentMenu::MainMenu;
} else {
state.settings.locale = new_locale;
state.reload_fonts(ctx);
state.update_locale(ctx);
let _ = state.settings.save(ctx);

View File

@ -4,11 +4,13 @@ use std::rc::Rc;
use downcast::Downcast;
use imgui::{Condition, MenuItem, TabItem, TabItemFlags, Window};
use crate::{Context, GameResult, Scene, SharedGameState};
use crate::editor::{CurrentTool, EditorInstance};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::keyboard;
use crate::framework::keyboard::ScanCode;
use crate::framework::ui::Components;
use crate::game::shared_game_state::SharedGameState;
use crate::game::stage::Stage;
use crate::scene::game_scene::GameScene;
use crate::scene::title_scene::TitleScene;
@ -181,14 +183,13 @@ impl Scene for EditorScene {
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
if let Some(scene) = &self.subscene {
scene.draw(state, ctx)?;
state.font.draw_text(
"Press [ESC] to return.".chars(),
4.0,
4.0,
state.font.builder().position(4.0, 4.0).draw(
"Press [ESC] to return.",
ctx,
&state.constants,
&mut state.texture_set,
ctx,
)?;
);
return Ok(());
}

View File

@ -4,7 +4,7 @@ use std::rc::Rc;
use log::info;
use crate::common::{Color, Direction, interpolate_fix9_scale, Rect};
use crate::common::{interpolate_fix9_scale, Color, Direction, Rect};
use crate::components::background::Background;
use crate::components::boss_life_bar::BossLifeBar;
use crate::components::credits::Credits;
@ -23,35 +23,36 @@ use crate::components::tilemap::{TileLayer, Tilemap};
use crate::components::water_renderer::{WaterLayer, WaterRenderer};
use crate::components::whimsical_star::WhimsicalStar;
use crate::entity::GameEntity;
use crate::framework::{filesystem, gamepad, graphics};
use crate::framework::backend::SpriteBatchCommand;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::graphics::{BlendMode, draw_rect, FilterMode};
use crate::framework::graphics::{draw_rect, BlendMode, FilterMode};
use crate::framework::keyboard::ScanCode;
use crate::framework::ui::Components;
use crate::framework::{filesystem, gamepad, graphics};
use crate::game::caret::CaretType;
use crate::game::frame::{Frame, UpdateTarget};
use crate::game::inventory::{Inventory, TakeExperienceResult};
use crate::game::map::WaterParams;
use crate::game::physics::{OFFSETS, PhysicalEntity};
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::{NPCLayer, NPC};
use crate::game::physics::{PhysicalEntity, OFFSETS};
use crate::game::player::{ControlMode, Player, TargetPlayer};
use crate::game::scripting::tsc::credit_script::CreditScriptVM;
use crate::game::scripting::tsc::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
use crate::game::settings::ControllerType;
use crate::game::shared_game_state::{CutsceneSkipMode, PlayerCount, ReplayState, SharedGameState, TileSize};
use crate::game::stage::{BackgroundType, Stage, StageTexturePaths};
use crate::game::weapon::bullet::BulletManager;
use crate::game::weapon::{Weapon, WeaponType};
use crate::graphics::font::{Font, Symbols};
use crate::graphics::texture_set::SpriteBatch;
use crate::input::touch_controls::TouchControlType;
use crate::menu::pause_menu::PauseMenu;
use crate::game::npc::{NPC, NPCLayer};
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::player::{ControlMode, Player, TargetPlayer};
use crate::scene::Scene;
use crate::scene::title_scene::TitleScene;
use crate::game::scripting::tsc::credit_script::CreditScriptVM;
use crate::game::scripting::tsc::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
use crate::scene::Scene;
use crate::util::rng::RNG;
use crate::game::weapon::{Weapon, WeaponType};
use crate::game::weapon::bullet::BulletManager;
pub struct GameScene {
pub tick: u32,
@ -199,14 +200,14 @@ impl GameScene {
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;
}
@ -420,45 +421,45 @@ impl GameScene {
&& y >= bymth
&& y <= bypth)
|| ((tile == 0x50 || tile == 0x70)
&& x >= bxmth
&& x <= bxpth
&& y <= ((by as f32 * tf) - (x - bx as f32 * tf) / 2.0 + tfq)
&& y >= bymth)
&& x >= bxmth
&& x <= bxpth
&& y <= ((by as f32 * tf) - (x - bx as f32 * tf) / 2.0 + tfq)
&& y >= bymth)
|| ((tile == 0x51 || tile == 0x71)
&& x >= bxmth
&& x <= bxpth
&& y <= ((by as f32 * tf) - (x - bx as f32 * tf) / 2.0 - tfq)
&& y >= bymth)
&& x >= bxmth
&& x <= bxpth
&& y <= ((by as f32 * tf) - (x - bx as f32 * tf) / 2.0 - tfq)
&& y >= bymth)
|| ((tile == 0x52 || tile == 0x72)
&& x >= bxmth
&& x <= bxpth
&& y <= ((by as f32 * tf) + (x - bx as f32 * tf) / 2.0 - tfq)
&& y >= bymth)
&& x >= bxmth
&& x <= bxpth
&& y <= ((by as f32 * tf) + (x - bx as f32 * tf) / 2.0 - tfq)
&& y >= bymth)
|| ((tile == 0x53 || tile == 0x73)
&& x >= bxmth
&& x <= bxpth
&& y <= ((by as f32 * tf) + (x - bx as f32 * tf) / 2.0 + tfq)
&& y >= bymth)
&& x >= bxmth
&& x <= bxpth
&& y <= ((by as f32 * tf) + (x - bx as f32 * tf) / 2.0 + tfq)
&& y >= bymth)
|| ((tile == 0x54 || tile == 0x74)
&& x >= bxmth
&& x <= bxpth
&& y >= ((by as f32 * tf) + (x - bx as f32 * tf) / 2.0 - tfq)
&& y <= bypth)
&& x >= bxmth
&& x <= bxpth
&& y >= ((by as f32 * tf) + (x - bx as f32 * tf) / 2.0 - tfq)
&& y <= bypth)
|| ((tile == 0x55 || tile == 0x75)
&& x >= bxmth
&& x <= bxpth
&& y >= ((by as f32 * tf) + (x - bx as f32 * tf) / 2.0 + tfq)
&& y <= bypth)
&& x >= bxmth
&& x <= bxpth
&& y >= ((by as f32 * tf) + (x - bx as f32 * tf) / 2.0 + tfq)
&& y <= bypth)
|| ((tile == 0x56 || tile == 0x76)
&& x >= bxmth
&& x <= bxpth
&& y >= ((by as f32 * tf) - (x - bx as f32 * tf) / 2.0 + tfq)
&& y <= bypth)
&& x >= bxmth
&& x <= bxpth
&& y >= ((by as f32 * tf) - (x - bx as f32 * tf) / 2.0 + tfq)
&& y <= bypth)
|| ((tile == 0x57 || tile == 0x77)
&& x >= bxmth
&& x <= bxpth
&& y >= ((by as f32 * tf) - (x - bx as f32 * tf) / 2.0 - tfq)
&& y <= bypth)
&& x >= bxmth
&& x <= bxpth
&& y >= ((by as f32 * tf) - (x - bx as f32 * tf) / 2.0 - tfq)
&& y <= bypth)
{
continue 'ray;
}
@ -501,14 +502,14 @@ impl GameScene {
for npc in self.npc_list.iter_alive() {
if 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;
}
@ -520,7 +521,7 @@ impl GameScene {
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "builtin/lightmap/spot")?;
'cc: for (player, inv) in
[(&self.player1, &self.inventory_player1), (&self.player2, &self.inventory_player2)].iter()
[(&self.player1, &self.inventory_player1), (&self.player2, &self.inventory_player2)].iter()
{
if player.cond.alive() && !player.cond.hidden() && inv.get_current_weapon().is_some() {
if state.settings.light_cone {
@ -615,15 +616,15 @@ impl GameScene {
for npc in self.npc_list.iter_alive() {
if npc.cond.hidden()
|| (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)
|| npc.y
> (self.frame.y
+ 128 * 0x200
+ (state.canvas_size.1 as i32 + npc.display_bounds.height() 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)
|| npc.y
> (self.frame.y
+ 128 * 0x200
+ (state.canvas_size.1 as i32 + npc.display_bounds.height() as i32) * 0x200))
{
continue;
}
@ -1236,10 +1237,10 @@ impl GameScene {
&& (npc.y - npc.hit_bounds.top as i32) < (bullet.y + bullet.enemy_hit_height as i32)
&& (npc.y + npc.hit_bounds.bottom as i32) > (bullet.y - bullet.enemy_hit_height as i32))
|| (npc.npc_flags.invulnerable()
&& (npc.x - npc.hit_bounds.right as i32) < (bullet.x + bullet.hit_bounds.right as i32)
&& (npc.x + npc.hit_bounds.right as i32) > (bullet.x - bullet.hit_bounds.left as i32)
&& (npc.y - npc.hit_bounds.top as i32) < (bullet.y + bullet.hit_bounds.bottom as i32)
&& (npc.y + npc.hit_bounds.bottom as i32) > (bullet.y - bullet.hit_bounds.top as i32));
&& (npc.x - npc.hit_bounds.right as i32) < (bullet.x + bullet.hit_bounds.right as i32)
&& (npc.x + npc.hit_bounds.right as i32) > (bullet.x - bullet.hit_bounds.left as i32)
&& (npc.y - npc.hit_bounds.top as i32) < (bullet.y + bullet.hit_bounds.bottom as i32)
&& (npc.y + npc.hit_bounds.bottom as i32) > (bullet.y - bullet.hit_bounds.top as i32));
if !hit {
continue;
@ -1544,10 +1545,10 @@ impl GameScene {
) -> GameResult {
if entity.x() < (self.frame.x - 128 - entity.display_bounds().width() as i32 * 0x200)
|| entity.x()
> (self.frame.x + 128 + (state.canvas_size.0 as i32 + entity.display_bounds().width() as i32) * 0x200)
&& entity.y() < (self.frame.y - 128 - entity.display_bounds().height() as i32 * 0x200)
> (self.frame.x + 128 + (state.canvas_size.0 as i32 + entity.display_bounds().width() as i32) * 0x200)
&& entity.y() < (self.frame.y - 128 - entity.display_bounds().height() as i32 * 0x200)
|| entity.y()
> (self.frame.y + 128 + (state.canvas_size.1 as i32 + entity.display_bounds().height() as i32) * 0x200)
> (self.frame.y + 128 + (state.canvas_size.1 as i32 + entity.display_bounds().height() as i32) * 0x200)
{
return Ok(());
}
@ -1597,16 +1598,14 @@ impl GameScene {
self.draw_debug_object(npc, state, ctx)?;
let text = format!("{}:{}:{}", npc.id, npc.npc_type, npc.action_num);
state.font.draw_colored_text_with_shadow_scaled(
text.chars(),
((npc.x - self.frame.x) / 0x200) as f32,
((npc.y - self.frame.y) / 0x200) as f32,
0.5,
(255, 255, 0, 255),
&state.constants,
&mut state.texture_set,
ctx,
)?;
state
.font
.builder()
.position(((npc.x - self.frame.x) / 0x200) as f32, ((npc.y - self.frame.y) / 0x200) as f32)
.scale(0.5)
.shadow(true)
.color((255, 255, 0, 255))
.draw(&text, ctx, &state.constants, &mut state.texture_set)?;
Ok(())
}
@ -1653,7 +1652,7 @@ impl Scene for GameScene {
state.textscript_vm.suspend = false;
state.tile_size = self.stage.map.tile_size;
#[cfg(feature = "scripting-lua")]
state.lua.set_game_scene(self as *mut _);
state.lua.set_game_scene(self as *mut _);
self.player1.controller = state.settings.create_player1_controller();
self.player2.controller = state.settings.create_player2_controller();
@ -1702,24 +1701,24 @@ impl Scene for GameScene {
_ if self.intro_mode => LightingMode::None,
_ if !state.constants.is_switch
&& (self.stage.data.background_type == BackgroundType::Black
|| self.stage.data.background.name() == "bkBlack") =>
{
LightingMode::Ambient
}
|| self.stage.data.background.name() == "bkBlack") =>
{
LightingMode::Ambient
}
_ if state.constants.is_switch
&& (self.stage.data.background_type == BackgroundType::Black
|| self.stage.data.background.name() == "bkBlack") =>
{
LightingMode::None
}
|| self.stage.data.background.name() == "bkBlack") =>
{
LightingMode::None
}
_ if self.stage.data.background.name() == "bkFall" => LightingMode::None,
_ if self.stage.data.background_type != BackgroundType::Black
&& self.stage.data.background_type != BackgroundType::Outside
&& self.stage.data.background_type != BackgroundType::OutsideWind
&& self.stage.data.background.name() != "bkBlack" =>
{
LightingMode::BackgroundOnly
}
{
LightingMode::BackgroundOnly
}
_ => LightingMode::None,
};
@ -1795,31 +1794,31 @@ impl Scene for GameScene {
| TextScriptExecutionState::Msg(_, _, _, _)
| TextScriptExecutionState::MsgNewLine(_, _, _, _, _)
| TextScriptExecutionState::FallingIsland(_, _, _, _, _, _)
if !state.control_flags.control_enabled() =>
{
state.touch_controls.control_type = TouchControlType::Dialog;
match state.settings.cutscene_skip_mode {
CutsceneSkipMode::Hold if !state.textscript_vm.flags.cutscene_skip() => {
if self.player1.controller.skip() {
self.skip_counter += 1;
if self.skip_counter >= CUTSCENE_SKIP_WAIT {
state.textscript_vm.flags.set_cutscene_skip(true);
state.tutorial_counter = 0;
}
} else if self.skip_counter > 0 {
self.skip_counter -= 1;
}
}
CutsceneSkipMode::FastForward => {
if self.player1.controller.skip() {
if !state.control_flags.control_enabled() =>
{
state.touch_controls.control_type = TouchControlType::Dialog;
match state.settings.cutscene_skip_mode {
CutsceneSkipMode::Hold if !state.textscript_vm.flags.cutscene_skip() => {
if self.player1.controller.skip() {
self.skip_counter += 1;
if self.skip_counter >= CUTSCENE_SKIP_WAIT {
state.textscript_vm.flags.set_cutscene_skip(true);
} else {
state.textscript_vm.flags.set_cutscene_skip(false);
state.tutorial_counter = 0;
}
} else if self.skip_counter > 0 {
self.skip_counter -= 1;
}
_ => (),
}
CutsceneSkipMode::FastForward => {
if self.player1.controller.skip() {
state.textscript_vm.flags.set_cutscene_skip(true);
} else {
state.textscript_vm.flags.set_cutscene_skip(false);
}
}
_ => (),
}
}
_ => {
self.skip_counter = 0;
}
@ -1864,7 +1863,7 @@ impl Scene for GameScene {
self.text_boxes.tick(state, ())?;
#[cfg(feature = "scripting-lua")]
state.lua.scene_tick();
state.lua.scene_tick();
if state.control_flags.tick_world() {
self.tick = self.tick.wrapping_add(1);
@ -1938,13 +1937,13 @@ impl Scene for GameScene {
self.inventory_dim += 0.1
* if state.textscript_vm.mode == ScriptMode::Inventory
|| state.textscript_vm.state == TextScriptExecutionState::MapSystem
|| self.pause_menu.is_paused()
{
state.frame_time as f32
} else {
-(state.frame_time as f32)
};
|| state.textscript_vm.state == TextScriptExecutionState::MapSystem
|| self.pause_menu.is_paused()
{
state.frame_time as f32
} else {
-(state.frame_time as f32)
};
self.inventory_dim = self.inventory_dim.clamp(0.0, 1.0);
self.background.draw_tick()?;
@ -2031,37 +2030,22 @@ impl Scene for GameScene {
state.frame_time,
);
let x = x.clamp(
8.0,
state.canvas_size.0 - 8.0 * scale - state.font.line_height(&state.constants),
);
let x = x.clamp(8.0, state.canvas_size.0 - 8.0 * scale - state.font.line_height());
state.font.draw_colored_text_scaled(
P2_OFFSCREEN_TEXT.chars(),
x + 1.0,
9.0,
scale,
(0, 0, 130, 255),
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.draw_colored_text_scaled(
P2_OFFSCREEN_TEXT.chars(),
x,
8.0,
scale,
(96, 96, 255, 255),
&state.constants,
&mut state.texture_set,
ctx,
)?;
state
.font
.builder()
.position(x, 8.0)
.scale(scale)
.shadow_color((0, 0, 130, 255))
.color((96, 96, 255, 255))
.shadow(true)
.draw(P2_OFFSCREEN_TEXT, ctx, &state.constants, &mut state.texture_set)?;
} else if self.player2.y - 0x1000 > self.frame.y + state.canvas_size.1 as i32 * 0x200 {
let scale = 1.0
+ (self.player2.y as f32 / (self.frame.y as f32 + state.canvas_size.1 * 0x200 as f32)
- 0.5)
.clamp(0.0, 2.0);
- 0.5)
.clamp(0.0, 2.0);
let x = interpolate_fix9_scale(
self.player2.prev_x - self.frame.prev_x,
@ -2069,32 +2053,17 @@ impl Scene for GameScene {
state.frame_time,
);
let x = x.clamp(
8.0,
state.canvas_size.0 - 8.0 * scale - state.font.line_height(&state.constants),
);
let x = x.clamp(8.0, state.canvas_size.0 - 8.0 * scale - state.font.line_height());
state.font.draw_colored_text_scaled(
P2_OFFSCREEN_TEXT.chars(),
x + 1.0,
state.canvas_size.1 - 8.0 * scale - state.font.line_height(&state.constants),
scale,
(0, 0, 130, 255),
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.draw_colored_text_scaled(
P2_OFFSCREEN_TEXT.chars(),
x,
state.canvas_size.1 - 8.0 * scale - state.font.line_height(&state.constants) - 1.0,
scale,
(96, 96, 255, 255),
&state.constants,
&mut state.texture_set,
ctx,
)?;
state
.font
.builder()
.position(x, state.canvas_size.1 - 8.0 * scale - state.font.line_height())
.scale(scale)
.shadow_color((0, 0, 130, 255))
.color((96, 96, 255, 255))
.shadow(true)
.draw(P2_OFFSCREEN_TEXT, ctx, &state.constants, &mut state.texture_set)?;
} else if self.player2.x + 0x1000 < self.frame.x {
let scale = 1.0 + (self.frame.x as f32 / self.player2.x as f32 / 2.0 - 0.5).clamp(0.0, 2.0);
@ -2103,71 +2072,41 @@ impl Scene for GameScene {
self.player2.y - self.frame.y,
state.frame_time,
);
let y = y.clamp(
8.0,
state.canvas_size.1 - 8.0 * scale - state.font.line_height(&state.constants),
);
let y = y.clamp(8.0, state.canvas_size.1 - 8.0 * scale - state.font.line_height());
state.font.draw_colored_text_scaled(
P2_OFFSCREEN_TEXT.chars(),
9.0,
y + 1.0,
scale,
(0, 0, 130, 255),
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.draw_colored_text_scaled(
P2_OFFSCREEN_TEXT.chars(),
8.0,
y,
scale,
(96, 96, 255, 255),
&state.constants,
&mut state.texture_set,
ctx,
)?;
state
.font
.builder()
.position(8.0, y)
.scale(scale)
.shadow_color((0, 0, 130, 255))
.color((96, 96, 255, 255))
.shadow(true)
.draw(P2_OFFSCREEN_TEXT, ctx, &state.constants, &mut state.texture_set)?;
} else if self.player2.x - 0x1000 > self.frame.x + state.canvas_size.0 as i32 * 0x200 {
let scale = 1.0
+ (self.player2.x as f32 / (self.frame.x as f32 + state.canvas_size.0 * 0x200 as f32)
- 0.5)
.clamp(0.0, 2.0);
- 0.5)
.clamp(0.0, 2.0);
let y = interpolate_fix9_scale(
self.player2.prev_y - self.frame.prev_y,
self.player2.y - self.frame.y,
state.frame_time,
);
let y = y.clamp(
8.0,
state.canvas_size.1 - 8.0 * scale - state.font.line_height(&state.constants),
);
let y = y.clamp(8.0, state.canvas_size.1 - 8.0 * scale - state.font.line_height());
let width = state.font.text_width(P2_OFFSCREEN_TEXT.chars(), &state.constants);
let width = state.font.builder().compute_width(P2_OFFSCREEN_TEXT);
state.font.draw_colored_text_scaled(
P2_OFFSCREEN_TEXT.chars(),
state.canvas_size.0 - width - 8.0 * scale + 1.0,
y + 1.0,
scale,
(0, 0, 130, 255),
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.draw_colored_text_scaled(
P2_OFFSCREEN_TEXT.chars(),
state.canvas_size.0 - width - 8.0 * scale,
y,
scale,
(96, 96, 255, 255),
&state.constants,
&mut state.texture_set,
ctx,
)?;
state
.font
.builder()
.shadow_color((0, 0, 130, 255))
.color((96, 96, 255, 255))
.shadow(true)
.position(state.canvas_size.0 - width - 8.0 * scale, y)
.scale(scale)
.draw(P2_OFFSCREEN_TEXT, ctx, &state.constants, &mut state.texture_set)?;
}
}
}
@ -2189,23 +2128,20 @@ impl Scene for GameScene {
&& self.map_name_counter > 0
{
let map_name = if self.stage.data.name == "u" {
state.constants.title.intro_text.chars()
state.constants.title.intro_text.as_str()
} else {
if state.constants.is_cs_plus && state.settings.locale == "jp" {
self.stage.data.name_jp.chars()
self.stage.data.name_jp.as_str()
} else {
self.stage.data.name.chars()
self.stage.data.name.as_str()
}
};
let width = state.font.text_width(map_name.clone(), &state.constants);
state.font.draw_text_with_shadow(
state.font.builder().shadow(true).y(80.0).center(state.canvas_size.0).draw(
map_name,
((state.canvas_size.0 - width) / 2.0).floor(),
80.0,
ctx,
&state.constants,
&mut state.texture_set,
ctx,
)?;
}
@ -2235,15 +2171,19 @@ impl Scene for GameScene {
ControllerType::Gamepad(index) => ctx.gamepad_context.get_gamepad_sprite_offset(index as usize),
};
let rect_map = [(
'=',
state.settings.player1_controller_button_map.skip.get_rect(gamepad_sprite_offset, &state.constants),
)];
let symbols = Symbols {
symbols: &[(
'=',
state.settings.player1_controller_button_map.skip.get_rect(gamepad_sprite_offset, &state.constants),
)],
texture: "buttons",
};
let width = state.font.text_width_with_rects(text.chars(), &rect_map, &state.constants);
// let width = state.font.text_width_with_rects(text.chars(), &rect_map, &state.constants);
let width = state.font.builder().with_symbols(Some(symbols)).compute_width(&text);
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 line_height = state.font.line_height();
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,
@ -2261,15 +2201,11 @@ impl Scene for GameScene {
rect.right = rect.left + (w * state.scale).ceil() as isize;
draw_rect(ctx, rect, Color::from_rgb(128, 128, 160))?;
state.font.draw_text_with_shadow_and_rects(
text.chars(),
pos_x + 10.0,
pos_y + 5.0,
state.font.builder().position(pos_x + 10.0, pos_y + 5.0).shadow(true).with_symbols(Some(symbols)).draw(
&text,
ctx,
&state.constants,
&mut state.texture_set,
&rect_map,
Some("buttons".into()),
ctx,
)?;
}
@ -2279,50 +2215,46 @@ impl Scene for GameScene {
if state.settings.god_mode {
let debug_name = "GOD";
state.font.draw_text_with_shadow(
debug_name.chars(),
state.canvas_size.0 - state.font.text_width(debug_name.chars(), &state.constants) - 10.0,
20.0,
&state.constants,
&mut state.texture_set,
ctx,
)?;
state
.font
.builder()
.x(state.canvas_size.0 - state.font.builder().compute_width(debug_name) - 10.0)
.y(20.0)
.shadow(true)
.draw(debug_name, ctx, &state.constants, &mut state.texture_set)?;
}
if state.settings.infinite_booster {
let debug_name = "INF.B";
state.font.draw_text_with_shadow(
debug_name.chars(),
state.canvas_size.0 - state.font.text_width(debug_name.chars(), &state.constants) - 10.0,
32.0,
&state.constants,
&mut state.texture_set,
ctx,
)?;
state
.font
.builder()
.x(state.canvas_size.0 - state.font.builder().compute_width(debug_name) - 10.0)
.y(32.0)
.shadow(true)
.draw(debug_name, ctx, &state.constants, &mut state.texture_set)?;
}
if state.settings.speed != 1.0 {
let tick_spd_mod = format!("{:.1}x SPD", state.settings.speed);
state.font.draw_text_with_shadow(
tick_spd_mod.chars(),
state.canvas_size.0 - state.font.text_width(tick_spd_mod.chars(), &state.constants) - 10.0,
44.0,
&state.constants,
&mut state.texture_set,
ctx,
)?;
let debug_name = &format!("{:.1}x SPD", state.settings.speed);
state
.font
.builder()
.x(state.canvas_size.0 - state.font.builder().compute_width(debug_name) - 10.0)
.y(44.0)
.shadow(true)
.draw(debug_name, ctx, &state.constants, &mut state.texture_set)?;
}
if state.settings.noclip {
let debug_name = "NOCLIP";
state.font.draw_text_with_shadow(
debug_name.chars(),
state.canvas_size.0 - state.font.text_width(debug_name.chars(), &state.constants) - 10.0,
56.0,
&state.constants,
&mut state.texture_set,
ctx,
)?;
state
.font
.builder()
.x(state.canvas_size.0 - state.font.builder().compute_width(debug_name) - 10.0)
.y(56.0)
.shadow(true)
.draw(debug_name, ctx, &state.constants, &mut state.texture_set)?;
}
self.replay.draw(state, ctx, &self.frame)?;

View File

@ -11,9 +11,10 @@ use crate::game::map::Map;
use crate::game::settings::ControllerType;
use crate::game::shared_game_state::{SharedGameState, TileSize};
use crate::game::stage::{BackgroundType, NpcType, Stage, StageData, StageTexturePaths, Tileset};
use crate::graphics::font::Font;
use crate::input::combined_menu_controller::CombinedMenuController;
use crate::scene::Scene;
use crate::scene::title_scene::TitleScene;
use crate::scene::Scene;
pub struct JukeboxScene {
selected_song: u16,
@ -33,9 +34,9 @@ impl JukeboxScene {
let fake_stage = Stage {
map: Map { width: 0, height: 0, tiles: vec![], attrib: [0; 0x100], tile_size: TileSize::Tile16x16 },
data: StageData {
name: "".to_string(),
name_jp: "".to_string(),
map: "".to_string(),
name: String::new(),
name_jp: String::new(),
map: String::new(),
boss_no: 0,
tileset: Tileset { name: "0".to_string() },
pxpack_data: None,
@ -114,16 +115,16 @@ impl Scene for JukeboxScene {
let mut song = self.selected_song as i16
+ if self.controller.trigger_right() {
1
} else if self.controller.trigger_left() {
-1
} else if self.controller.trigger_down() {
8
} else if self.controller.trigger_up() {
-8
} else {
0
};
1
} else if self.controller.trigger_left() {
-1
} else if self.controller.trigger_down() {
8
} else if self.controller.trigger_up() {
-8
} else {
0
};
if song < 0 {
song += self.song_list.len() as i16;
@ -281,15 +282,11 @@ impl Scene for JukeboxScene {
// Write Soundtrack name
let text = &state.settings.soundtrack;
let width = state.font.text_width(text.chars(), &state.constants);
state.font.draw_text(
text.chars(),
((state.canvas_size.0 - width) / 2.0).floor(),
20.0,
state.font.builder().center(state.canvas_size.0).y(20.0).shadow(true).draw(
text,
ctx,
&state.constants,
&mut state.texture_set,
ctx,
)?;
// Write soundtrack switch indicators
@ -297,15 +294,19 @@ impl Scene for JukeboxScene {
if state.settings.touch_controls || state.settings.player1_controller_type == ControllerType::Keyboard {
let prev_chevron = "<";
let next_chevron = ">";
let next_chev_width = state.font.builder().compute_width(next_chevron);
state.font.draw_text(prev_chevron.chars(), init_x, 20.0, &state.constants, &mut state.texture_set, ctx)?;
state.font.draw_text(
next_chevron.chars(),
state.canvas_size.0 - init_x - state.font.text_width(next_chevron.chars(), &state.constants),
20.0,
state.font.builder().position(init_x, 20.0).draw(
prev_chevron,
ctx,
&state.constants,
&mut state.texture_set,
)?;
state.font.builder().position(state.canvas_size.0 - init_x - next_chev_width, 20.0).draw(
next_chevron,
ctx,
&state.constants,
&mut state.texture_set,
)?;
} else {
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "buttons")?;

View File

@ -1,6 +1,7 @@
use crate::framework::context::Context;
use crate::framework::error::{GameError, GameResult};
use crate::game::shared_game_state::SharedGameState;
use crate::graphics::font::Font;
use crate::scene::Scene;
pub struct NoDataScene {
@ -26,103 +27,70 @@ impl Scene for NoDataScene {
#[allow(unused)]
fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
#[cfg(target_os = "android")]
{
use crate::common::Rect;
{
use crate::common::Rect;
if !self.flag {
self.flag = true;
let _ = std::fs::create_dir("/sdcard/doukutsu/");
let _ = std::fs::write("/sdcard/doukutsu/extract game data here.txt", REL_URL);
let _ = std::fs::write("/sdcard/doukutsu/.nomedia", b"");
}
if !self.flag {
self.flag = true;
let _ = std::fs::create_dir("/sdcard/doukutsu/");
let _ = std::fs::write("/sdcard/doukutsu/extract game data here.txt", REL_URL);
let _ = std::fs::write("/sdcard/doukutsu/.nomedia", b"");
}
let screen = Rect::new(0, 0, state.canvas_size.0 as isize, state.canvas_size.1 as isize);
if state.touch_controls.consume_click_in(screen) {
if let Err(err) = webbrowser::open(REL_URL) {
self.err = err.to_string();
}
let screen = Rect::new(0, 0, state.canvas_size.0 as isize, state.canvas_size.1 as isize);
if state.touch_controls.consume_click_in(screen) {
if let Err(err) = webbrowser::open(REL_URL) {
self.err = err.to_string();
}
}
}
Ok(())
}
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
{
let die = "doukutsu-rs internal error";
let die_width = state.font.text_width(die.chars().clone(), &state.constants);
state.font.draw_colored_text(
die.chars(),
(state.canvas_size.0 - die_width) / 2.0,
10.0,
(255, 100, 100, 255),
&state.constants,
&mut state.texture_set,
ctx,
)?;
}
state.font.builder().center(state.canvas_size.0).y(10.0).color((255, 100, 100, 255)).draw(
"doukutsu-rs internal error",
ctx,
&state.constants,
&mut state.texture_set,
)?;
{
let ftl = "Failed to load game data.";
let ftl_width = state.font.text_width(ftl.chars().clone(), &state.constants);
state.font.draw_colored_text(
ftl.chars(),
(state.canvas_size.0 - ftl_width) / 2.0,
30.0,
(255, 100, 100, 255),
&state.constants,
&mut state.texture_set,
ctx,
)?;
}
state.font.builder().center(state.canvas_size.0).y(30.0).color((255, 100, 100, 255)).draw(
"Failed to load game data.",
ctx,
&state.constants,
&mut state.texture_set,
)?;
#[cfg(target_os = "android")]
{
let ftl = "It's likely that you haven't extracted the game data properly.";
let ftl2 = "Click here to open the guide.";
let ftl_width = state.font.text_width(ftl.chars().clone(), &state.constants);
let ftl2_width = state.font.text_width(ftl2.chars().clone(), &state.constants);
let ftl3_width = state.font.text_width(REL_URL.chars().clone(), &state.constants);
state.font.draw_colored_text(
ftl.chars(),
(state.canvas_size.0 - ftl_width) / 2.0,
60.0,
(255, 255, 0, 255),
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.draw_colored_text(
ftl2.chars(),
(state.canvas_size.0 - ftl2_width) / 2.0,
80.0,
(255, 255, 0, 255),
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.draw_colored_text(
REL_URL.chars(),
(state.canvas_size.0 - ftl3_width) / 2.0,
100.0,
(255, 255, 0, 255),
&state.constants,
&mut state.texture_set,
ctx,
)?;
}
{
let err_width = state.font.text_width(self.err.chars().clone(), &state.constants);
state.font.draw_text(
self.err.chars(),
(state.canvas_size.0 - err_width) / 2.0,
140.0,
let yellow = (255, 255, 0, 255);
state.font.builder().center(state.canvas_size.0).y(60.0).color(yellow).draw(
"It's likely that you haven't extracted the game data properly.",
ctx,
&state.constants,
&mut state.texture_set,
)?;
state.font.builder().center(state.canvas_size.0).y(80.0).color(yellow).draw(
"Click here to open the guide.",
ctx,
&state.constants,
&mut state.texture_set,
)?;
state.font.builder().center(state.canvas_size.0).y(100.0).color(yellow).draw(
REL_URL,
ctx,
&state.constants,
&mut state.texture_set,
)?;
}
{
state.font.builder().center(state.canvas_size.0).y(140.0).draw(
&self.err,
ctx,
&state.constants,
&mut state.texture_set,
)?;
}

View File

@ -10,12 +10,13 @@ use crate::game::shared_game_state::{
GameDifficulty, MenuCharacter, ReplayKind, ReplayState, Season, SharedGameState, TileSize,
};
use crate::game::stage::{BackgroundType, NpcType, Stage, StageData, StageTexturePaths, Tileset};
use crate::graphics::font::Font;
use crate::input::combined_menu_controller::CombinedMenuController;
use crate::input::touch_controls::TouchControlType;
use crate::menu::{Menu, MenuEntry, MenuSelectionResult};
use crate::menu::coop_menu::PlayerCountMenu;
use crate::menu::save_select_menu::SaveSelectMenu;
use crate::menu::settings_menu::SettingsMenu;
use crate::menu::{Menu, MenuEntry, MenuSelectionResult};
use crate::scene::jukebox_scene::JukeboxScene;
use crate::scene::Scene;
@ -96,9 +97,9 @@ impl TitleScene {
let fake_stage = Stage {
map: Map { width: 0, height: 0, tiles: vec![], attrib: [0; 0x100], tile_size: TileSize::Tile16x16 },
data: StageData {
name: "".to_string(),
name_jp: "".to_string(),
map: "".to_string(),
name: String::new(),
name_jp: String::new(),
map: String::new(),
boss_no: 0,
tileset: Tileset { name: "0".to_string() },
pxpack_data: None,
@ -134,15 +135,11 @@ impl TitleScene {
}
fn draw_text_centered(&self, text: &str, y: f32, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
let width = state.font.text_width(text.chars(), &state.constants);
state.font.draw_text(
text.chars(),
((state.canvas_size.0 - width) / 2.0).floor(),
y,
&state.constants,
&mut state.texture_set,
ctx,
)?;
state.font.builder()
.center(state.canvas_size.0)
.y(y)
.shadow(true)
.draw(text, ctx, &state.constants, &mut state.texture_set)?;
Ok(())
}
@ -198,24 +195,29 @@ impl Scene for TitleScene {
self.controller.add(state.settings.create_player1_controller());
self.controller.add(state.settings.create_player2_controller());
self.main_menu.push_entry(MainMenuEntry::Start, MenuEntry::Active(state.t("menus.main_menu.start")));
self.main_menu.push_entry(MainMenuEntry::Start, MenuEntry::Active(state.loc.t("menus.main_menu.start").to_owned()));
if !state.mod_list.mods.is_empty() {
self.main_menu
.push_entry(MainMenuEntry::Challenges, MenuEntry::Active(state.t("menus.main_menu.challenges")));
self.main_menu.push_entry(
MainMenuEntry::Challenges,
MenuEntry::Active(state.loc.t("menus.main_menu.challenges").to_owned()),
);
}
self.main_menu.push_entry(MainMenuEntry::Options, MenuEntry::Active(state.t("menus.main_menu.options")));
self.main_menu
.push_entry(MainMenuEntry::Options, MenuEntry::Active(state.loc.t("menus.main_menu.options").to_owned()));
if cfg!(feature = "editor") {
self.main_menu.push_entry(MainMenuEntry::Editor, MenuEntry::Active(state.t("menus.main_menu.editor")));
self.main_menu
.push_entry(MainMenuEntry::Editor, MenuEntry::Active(state.loc.t("menus.main_menu.editor").to_owned()));
}
if state.constants.is_switch {
self.main_menu.push_entry(MainMenuEntry::Jukebox, MenuEntry::Active(state.t("menus.main_menu.jukebox")));
self.main_menu
.push_entry(MainMenuEntry::Jukebox, MenuEntry::Active(state.loc.t("menus.main_menu.jukebox").to_owned()));
}
self.main_menu.push_entry(MainMenuEntry::Quit, MenuEntry::Active(state.t("menus.main_menu.quit")));
self.main_menu.push_entry(MainMenuEntry::Quit, MenuEntry::Active(state.loc.t("menus.main_menu.quit").to_owned()));
self.settings_menu.init(state, ctx)?;
@ -246,16 +248,19 @@ impl Scene for TitleScene {
.push_entry(ChallengesMenuEntry::Challenge(idx), MenuEntry::Disabled("???".to_owned()));
}
}
self.challenges_menu.push_entry(ChallengesMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
self.challenges_menu
.push_entry(ChallengesMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
self.challenges_menu.selected = selected;
self.confirm_menu.push_entry(ConfirmMenuEntry::Title, MenuEntry::Disabled("".to_owned()));
self.confirm_menu
.push_entry(ConfirmMenuEntry::StartChallenge, MenuEntry::Active(state.t("menus.challenge_menu.start")));
self.confirm_menu.push_entry(ConfirmMenuEntry::Title, MenuEntry::Disabled(String::new()));
self.confirm_menu.push_entry(
ConfirmMenuEntry::StartChallenge,
MenuEntry::Active(state.loc.t("menus.challenge_menu.start").to_owned()),
);
self.confirm_menu.push_entry(ConfirmMenuEntry::Replay(ReplayKind::Best), MenuEntry::Hidden);
self.confirm_menu.push_entry(ConfirmMenuEntry::Replay(ReplayKind::Last), MenuEntry::Hidden);
self.confirm_menu.push_entry(ConfirmMenuEntry::DeleteReplay, MenuEntry::Hidden);
self.confirm_menu.push_entry(ConfirmMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
self.confirm_menu.push_entry(ConfirmMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
self.confirm_menu.selected = ConfirmMenuEntry::StartChallenge;
self.controller.update(state, ctx)?;
@ -304,10 +309,10 @@ impl Scene for TitleScene {
MenuSelectionResult::Selected(MainMenuEntry::Editor, _) => {
// this comment is just there because rustfmt removes parenthesis around the match case and breaks compilation
#[cfg(feature = "editor")]
{
use crate::scene::editor_scene::EditorScene;
state.next_scene = Some(Box::new(EditorScene::new()));
}
{
use crate::scene::editor_scene::EditorScene;
state.next_scene = Some(Box::new(EditorScene::new()));
}
}
MenuSelectionResult::Selected(MainMenuEntry::Jukebox, _) => {
state.next_scene = Some(Box::new(JukeboxScene::new()));
@ -356,18 +361,18 @@ impl Scene for TitleScene {
} else {
let mod_name = mod_info.name.clone();
self.confirm_menu.width =
(state.font.text_width(mod_name.chars(), &state.constants).max(50.0) + 32.0) as u16;
(state.font.builder().compute_width(&mod_name).max(50.0) + 32.0) as u16;
self.confirm_menu.set_entry(ConfirmMenuEntry::Title, MenuEntry::Disabled(mod_name));
if state.has_replay_data(ctx, ReplayKind::Best) {
self.confirm_menu.set_entry(
ConfirmMenuEntry::Replay(ReplayKind::Best),
MenuEntry::Active(state.t("menus.challenge_menu.replay_best")),
MenuEntry::Active(state.loc.t("menus.challenge_menu.replay_best").to_owned()),
);
self.confirm_menu.set_entry(
ConfirmMenuEntry::DeleteReplay,
MenuEntry::Active(state.t("menus.challenge_menu.delete_replay")),
MenuEntry::Active(state.loc.t("menus.challenge_menu.delete_replay").to_owned()),
);
} else {
self.confirm_menu
@ -378,7 +383,7 @@ impl Scene for TitleScene {
if state.has_replay_data(ctx, ReplayKind::Last) {
self.confirm_menu.set_entry(
ConfirmMenuEntry::Replay(ReplayKind::Last),
MenuEntry::Active(state.t("menus.challenge_menu.replay_last")),
MenuEntry::Active(state.loc.t("menus.challenge_menu.replay_last").to_owned()),
);
} else {
self.confirm_menu
@ -460,22 +465,19 @@ impl Scene for TitleScene {
batch.draw(ctx)?;
} else {
let window_title = match self.current_menu {
CurrentMenu::ChallengesMenu => state.t("menus.main_menu.challenges"),
CurrentMenu::ChallengeConfirmMenu | CurrentMenu::SaveSelectMenu => state.t("menus.main_menu.start"),
CurrentMenu::OptionMenu => state.t("menus.main_menu.options"),
CurrentMenu::ChallengesMenu => state.loc.t("menus.main_menu.challenges"),
CurrentMenu::ChallengeConfirmMenu | CurrentMenu::SaveSelectMenu => state.loc.t("menus.main_menu.start"),
CurrentMenu::OptionMenu => state.loc.t("menus.main_menu.options"),
CurrentMenu::MainMenu => unreachable!(),
CurrentMenu::PlayerCountMenu => state.t("menus.main_menu.start"),
CurrentMenu::PlayerCountMenu => state.loc.t("menus.main_menu.start"),
};
state.font.draw_colored_text_with_shadow_scaled(
window_title.chars(),
state.canvas_size.0 / 2.0 - state.font.text_width(window_title.chars(), &state.constants) / 2.0,
state.font.line_height(&state.constants), //im sure there is a better way to shift this into place
1.0,
(0xff, 0xff, 0xff, 0xff),
&state.constants,
&mut state.texture_set,
ctx,
)?;
state
.font
.builder()
.shadow(true)
.position(0.0, state.font.line_height())
.center(state.canvas_size.0)
.draw(&window_title, ctx, &state.constants, &mut state.texture_set)?;
}
if self.current_menu == CurrentMenu::MainMenu {