Seasonal textures support
This commit is contained in:
parent
8e01166ab0
commit
c0f39e7f18
|
@ -44,6 +44,7 @@ bitflags = "1"
|
|||
bitvec = "0.17.4"
|
||||
byteorder = "1.3"
|
||||
case_insensitive_hashmap = "1.0.0"
|
||||
chrono = "0.4"
|
||||
cpal = {git = "https://github.com/doukutsu-rs/cpal.git", branch = "android-support"}
|
||||
directories = "2"
|
||||
gfx = "0.18"
|
||||
|
|
|
@ -5,7 +5,7 @@ use num_traits::clamp;
|
|||
use num_traits::FromPrimitive;
|
||||
|
||||
use crate::caret::CaretType;
|
||||
use crate::common::{Condition, Direction, Equipment, fix9_scale, Flag, interpolate_fix9_scale, Rect};
|
||||
use crate::common::{Condition, Direction, Equipment, Flag, interpolate_fix9_scale, Rect};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::ggez::{Context, GameResult};
|
||||
|
@ -19,6 +19,18 @@ pub enum ControlMode {
|
|||
IronHead,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive)]
|
||||
#[repr(u8)]
|
||||
/// Cave Story+ player skins
|
||||
pub enum PlayerAppearance {
|
||||
Quote = 0,
|
||||
YellowQuote,
|
||||
HumanQuote,
|
||||
HalloweenQuote,
|
||||
ReindeerQuote,
|
||||
Curly,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Player {
|
||||
pub x: isize,
|
||||
|
@ -48,6 +60,7 @@ pub struct Player {
|
|||
pub damage: u16,
|
||||
pub air_counter: u16,
|
||||
pub air: u16,
|
||||
pub appearance: PlayerAppearance,
|
||||
weapon_offset_y: i8,
|
||||
index_x: isize,
|
||||
index_y: isize,
|
||||
|
@ -99,6 +112,7 @@ impl Player {
|
|||
damage: 0,
|
||||
air_counter: 0,
|
||||
air: 0,
|
||||
appearance: PlayerAppearance::Quote,
|
||||
bubble: 0,
|
||||
damage_counter: 0,
|
||||
damage_taken: 0,
|
||||
|
@ -109,6 +123,10 @@ impl Player {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_texture_offset(&self) -> usize {
|
||||
self.appearance as usize * 64 + if self.equip.has_mimiga_mask() { 32 } else { 0 }
|
||||
}
|
||||
|
||||
fn tick_normal(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
if !state.control_flags.interactions_disabled() && state.control_flags.control_enabled() {
|
||||
if self.equip.has_air_tank() {
|
||||
|
@ -557,6 +575,10 @@ impl Player {
|
|||
if self.anim_num == 1 || self.anim_num == 3 || self.anim_num == 6 || self.anim_num == 8 {
|
||||
self.weapon_rect.top += 1;
|
||||
}
|
||||
|
||||
let offset = self.get_texture_offset();
|
||||
self.anim_rect.top += offset;
|
||||
self.anim_rect.bottom += offset;
|
||||
}
|
||||
|
||||
pub fn damage(&mut self, hp: isize, state: &mut SharedGameState) {
|
||||
|
|
|
@ -12,11 +12,11 @@ use crate::ggez::nalgebra::clamp;
|
|||
use crate::inventory::{Inventory, TakeExperienceResult};
|
||||
use crate::npc::NPCMap;
|
||||
use crate::physics::PhysicalEntity;
|
||||
use crate::player::Player;
|
||||
use crate::player::{Player, PlayerAppearance};
|
||||
use crate::rng::RNG;
|
||||
use crate::scene::Scene;
|
||||
use crate::scene::title_scene::TitleScene;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::shared_game_state::{SharedGameState, Season};
|
||||
use crate::stage::{BackgroundType, Stage};
|
||||
use crate::text_script::{ConfirmSelection, ScriptMode, TextScriptExecutionState, TextScriptVM};
|
||||
use crate::texture_set::SizedBatch;
|
||||
|
@ -1189,6 +1189,15 @@ impl Scene for GameScene {
|
|||
state.npc_table.tex_npc1_name = ["Npc/", &self.stage.data.npc1.filename()].join("");
|
||||
state.npc_table.tex_npc2_name = ["Npc/", &self.stage.data.npc2.filename()].join("");
|
||||
|
||||
if state.constants.is_cs_plus {
|
||||
match state.season {
|
||||
Season::Halloween => self.player.appearance = PlayerAppearance::HalloweenQuote,
|
||||
Season::Christmas => self.player.appearance = PlayerAppearance::ReindeerQuote,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
self.npc_map.boss_map.boss_type = self.stage.data.boss_no as u16;
|
||||
self.frame.target_x = self.player.x;
|
||||
self.frame.target_y = self.player.y;
|
||||
self.frame.immediate_update(state, &self.stage);
|
||||
|
|
|
@ -97,8 +97,10 @@ impl Scene for TitleScene {
|
|||
self.option_menu.push_entry(MenuEntry::Toggle("Lighting effects".to_string(), state.settings.lighting_efects));
|
||||
if state.constants.is_cs_plus {
|
||||
self.option_menu.push_entry(MenuEntry::Toggle("Freeware textures".to_string(), state.settings.original_textures));
|
||||
self.option_menu.push_entry(MenuEntry::Toggle("Seasonal textures".to_string(), state.settings.seasonal_textures));
|
||||
} else {
|
||||
self.option_menu.push_entry(MenuEntry::Disabled("Freeware textures".to_string()));
|
||||
self.option_menu.push_entry(MenuEntry::Disabled("Seasonal textures".to_string()));
|
||||
}
|
||||
self.option_menu.push_entry(MenuEntry::Active("Join our Discord".to_string()));
|
||||
self.option_menu.push_entry(MenuEntry::Disabled(DISCORD_LINK.to_owned()));
|
||||
|
@ -168,23 +170,25 @@ impl Scene for TitleScene {
|
|||
MenuSelectionResult::Selected(3, toggle) => {
|
||||
if let MenuEntry::Toggle(_, value) = toggle {
|
||||
state.settings.original_textures = !state.settings.original_textures;
|
||||
|
||||
let path = if state.settings.original_textures {
|
||||
"/base/ogph/"
|
||||
} else {
|
||||
"/base/"
|
||||
};
|
||||
state.texture_set = TextureSet::new(path);
|
||||
state.reload_textures();
|
||||
|
||||
*value = state.settings.original_textures;
|
||||
}
|
||||
}
|
||||
MenuSelectionResult::Selected(4, _) => {
|
||||
MenuSelectionResult::Selected(4, toggle) => {
|
||||
if let MenuEntry::Toggle(_, value) = toggle {
|
||||
state.settings.seasonal_textures = !state.settings.seasonal_textures;
|
||||
state.reload_textures();
|
||||
|
||||
*value = state.settings.seasonal_textures;
|
||||
}
|
||||
}
|
||||
MenuSelectionResult::Selected(5, _) => {
|
||||
if let Err(e) = webbrowser::open(DISCORD_LINK) {
|
||||
log::warn!("Error opening web browser: {}", e);
|
||||
}
|
||||
}
|
||||
MenuSelectionResult::Selected(6, _) | MenuSelectionResult::Canceled => {
|
||||
MenuSelectionResult::Selected(7, _) | MenuSelectionResult::Canceled => {
|
||||
self.current_menu = CurrentMenu::MainMenu;
|
||||
}
|
||||
_ => {}
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
use std::io::Seek;
|
||||
use std::ops::Div;
|
||||
use std::time::Instant;
|
||||
use std::time::{Instant, SystemTime};
|
||||
|
||||
use bitvec::vec::BitVec;
|
||||
use chrono::{Local, Datelike};
|
||||
|
||||
use crate::bmfont_renderer::BMFontRenderer;
|
||||
use crate::caret::{Caret, CaretType};
|
||||
use crate::common::{ControlFlags, Direction, FadeState, KeyState};
|
||||
use crate::engine_constants::EngineConstants;
|
||||
use crate::ggez::{Context, filesystem, GameResult, graphics};
|
||||
use crate::ggez::filesystem::OpenOptions;
|
||||
use crate::ggez::graphics::Canvas;
|
||||
use crate::npc::{NPC, NPCTable};
|
||||
use crate::profile::GameProfile;
|
||||
|
@ -20,8 +23,6 @@ use crate::str;
|
|||
use crate::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
|
||||
use crate::texture_set::TextureSet;
|
||||
use crate::touch_controls::TouchControls;
|
||||
use crate::ggez::filesystem::OpenOptions;
|
||||
use std::io::Seek;
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
pub enum TimingMode {
|
||||
|
@ -48,10 +49,33 @@ impl TimingMode {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
pub enum Season {
|
||||
None,
|
||||
Halloween,
|
||||
Christmas,
|
||||
}
|
||||
|
||||
impl Season {
|
||||
pub fn current() -> Season {
|
||||
let now = Local::now();
|
||||
|
||||
if (now.month() == 10 && now.day() > 25) || (now.month() == 11 && now.day() < 3) {
|
||||
Season::Halloween
|
||||
} else if (now.month() == 12 && now.day() > 23) || (now.month() == 0 && now.day() < 7) {
|
||||
Season::Christmas
|
||||
} else {
|
||||
Season::None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Settings {
|
||||
pub god_mode: bool,
|
||||
pub infinite_booster: bool,
|
||||
pub speed: f64,
|
||||
pub seasonal_textures: bool,
|
||||
pub original_textures: bool,
|
||||
pub lighting_efects: bool,
|
||||
pub debug_outlines: bool,
|
||||
|
@ -73,15 +97,10 @@ pub struct SharedGameState {
|
|||
pub key_state: KeyState,
|
||||
pub key_trigger: KeyState,
|
||||
pub touch_controls: TouchControls,
|
||||
pub font: BMFontRenderer,
|
||||
pub texture_set: TextureSet,
|
||||
pub base_path: String,
|
||||
pub npc_table: NPCTable,
|
||||
pub npc_super_pos: (isize, isize),
|
||||
pub stages: Vec<StageData>,
|
||||
pub sound_manager: SoundManager,
|
||||
pub settings: Settings,
|
||||
pub constants: EngineConstants,
|
||||
pub new_npcs: Vec<NPC>,
|
||||
pub frame_time: f64,
|
||||
pub prev_frame_time: f64,
|
||||
|
@ -91,6 +110,12 @@ pub struct SharedGameState {
|
|||
pub screen_size: (f32, f32),
|
||||
pub next_scene: Option<Box<dyn Scene>>,
|
||||
pub textscript_vm: TextScriptVM,
|
||||
pub season: Season,
|
||||
pub constants: EngineConstants,
|
||||
pub font: BMFontRenderer,
|
||||
pub texture_set: TextureSet,
|
||||
pub sound_manager: SoundManager,
|
||||
pub settings: Settings,
|
||||
pub shutdown: bool,
|
||||
key_old: u16,
|
||||
}
|
||||
|
@ -98,11 +123,12 @@ pub struct SharedGameState {
|
|||
impl SharedGameState {
|
||||
pub fn new(ctx: &mut Context) -> GameResult<SharedGameState> {
|
||||
let screen_size = graphics::drawable_size(ctx);
|
||||
let scale = screen_size.1.div(240.0).floor().max(1.0);
|
||||
let scale = screen_size.1.div(235.0).floor().max(1.0);
|
||||
let canvas_size = (screen_size.0 / scale, screen_size.1 / scale);
|
||||
|
||||
let mut constants = EngineConstants::defaults();
|
||||
let mut base_path = "/";
|
||||
let settings = SharedGameState::load_settings(ctx)?;
|
||||
|
||||
if filesystem::exists(ctx, "/base/Nicalis.bmp") {
|
||||
info!("Cave Story+ (PC) data files detected.");
|
||||
|
@ -121,6 +147,14 @@ impl SharedGameState {
|
|||
|
||||
let font = BMFontRenderer::load(base_path, &constants.font_path, ctx)
|
||||
.or_else(|_| BMFontRenderer::load("/", "builtin/builtin_font.fnt", ctx))?;
|
||||
let season = Season::current();
|
||||
let mut texture_set = TextureSet::new(base_path);
|
||||
|
||||
if constants.is_cs_plus {
|
||||
texture_set.apply_seasonal_content(season, &settings);
|
||||
}
|
||||
|
||||
println!("lookup path: {:#?}", texture_set.paths);
|
||||
|
||||
Ok(SharedGameState {
|
||||
timing_mode: TimingMode::_50Hz,
|
||||
|
@ -135,23 +169,10 @@ impl SharedGameState {
|
|||
key_state: KeyState(0),
|
||||
key_trigger: KeyState(0),
|
||||
touch_controls: TouchControls::new(),
|
||||
font,
|
||||
texture_set: TextureSet::new(base_path),
|
||||
base_path: str!(base_path),
|
||||
npc_table: NPCTable::new(),
|
||||
npc_super_pos: (0, 0),
|
||||
stages: Vec::with_capacity(96),
|
||||
sound_manager: SoundManager::new(ctx)?,
|
||||
settings: Settings {
|
||||
god_mode: false,
|
||||
infinite_booster: false,
|
||||
speed: 1.0,
|
||||
original_textures: false,
|
||||
lighting_efects: true,
|
||||
debug_outlines: false,
|
||||
touch_controls: cfg!(target_os = "android"),
|
||||
},
|
||||
constants,
|
||||
new_npcs: Vec::with_capacity(8),
|
||||
frame_time: 0.0,
|
||||
prev_frame_time: 0.0,
|
||||
|
@ -161,11 +182,40 @@ impl SharedGameState {
|
|||
canvas_size,
|
||||
next_scene: None,
|
||||
textscript_vm: TextScriptVM::new(),
|
||||
key_old: 0,
|
||||
season,
|
||||
constants,
|
||||
font,
|
||||
texture_set,
|
||||
sound_manager: SoundManager::new(ctx)?,
|
||||
settings,
|
||||
shutdown: false,
|
||||
key_old: 0,
|
||||
})
|
||||
}
|
||||
|
||||
fn load_settings(ctx: &mut Context) -> GameResult<Settings> {
|
||||
Ok(Settings {
|
||||
god_mode: false,
|
||||
infinite_booster: false,
|
||||
speed: 1.0,
|
||||
seasonal_textures: true,
|
||||
original_textures: false,
|
||||
lighting_efects: true,
|
||||
debug_outlines: false,
|
||||
touch_controls: cfg!(target_os = "android"),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn reload_textures(&mut self) {
|
||||
let mut texture_set = TextureSet::new(self.base_path.as_str());
|
||||
|
||||
if self.constants.is_cs_plus {
|
||||
texture_set.apply_seasonal_content(self.season, &self.settings);
|
||||
}
|
||||
|
||||
self.texture_set = texture_set;
|
||||
}
|
||||
|
||||
pub fn start_new_game(&mut self, ctx: &mut Context) -> GameResult {
|
||||
let mut next_scene = GameScene::new(self, ctx, 13)?;
|
||||
next_scene.player.x = 10 * 16 * 0x200;
|
||||
|
|
|
@ -10,10 +10,11 @@ use crate::common::FILE_TYPES;
|
|||
use crate::engine_constants::EngineConstants;
|
||||
use crate::ggez::{Context, GameError, GameResult, graphics};
|
||||
use crate::ggez::filesystem;
|
||||
use crate::ggez::graphics::{Drawable, DrawMode, DrawParam, FilterMode, Image, Mesh, Rect, Color};
|
||||
use crate::ggez::graphics::{Color, Drawable, DrawMode, DrawParam, FilterMode, Image, Mesh, Rect};
|
||||
use crate::ggez::graphics::spritebatch::SpriteBatch;
|
||||
use crate::ggez::nalgebra::{Point2, Vector2};
|
||||
use crate::str;
|
||||
use crate::shared_game_state::{Season, Settings};
|
||||
|
||||
pub struct SizedBatch {
|
||||
pub batch: SpriteBatch,
|
||||
|
@ -90,7 +91,7 @@ impl SizedBatch {
|
|||
self.batch.add(param);
|
||||
}
|
||||
|
||||
pub fn add_rect_scaled_tinted(&mut self, x: f32, y: f32, color: (u8,u8,u8), scale_x: f32, scale_y: f32, rect: &common::Rect<usize>) {
|
||||
pub fn add_rect_scaled_tinted(&mut self, x: f32, y: f32, color: (u8, u8, u8), scale_x: f32, scale_y: f32, rect: &common::Rect<usize>) {
|
||||
if (rect.right - rect.left) == 0 || (rect.bottom - rect.top) == 0 {
|
||||
return;
|
||||
}
|
||||
|
@ -122,14 +123,26 @@ impl SizedBatch {
|
|||
|
||||
pub struct TextureSet {
|
||||
pub tex_map: HashMap<String, SizedBatch>,
|
||||
base_path: String,
|
||||
pub paths: Vec<String>,
|
||||
}
|
||||
|
||||
impl TextureSet {
|
||||
pub fn new(base_path: &str) -> TextureSet {
|
||||
TextureSet {
|
||||
tex_map: HashMap::new(),
|
||||
base_path: base_path.to_string(),
|
||||
paths: vec![base_path.to_string(), "".to_string()],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_seasonal_content(&mut self, season: Season, settings: &Settings) {
|
||||
if settings.original_textures {
|
||||
self.paths.insert(0, "/base/ogph/".to_string())
|
||||
} else if settings.seasonal_textures {
|
||||
match season {
|
||||
Season::Halloween => self.paths.insert(0, "/Halloween/season/".to_string()),
|
||||
Season::Christmas => self.paths.insert(0, "/Christmas/season/".to_string()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,15 +188,14 @@ impl TextureSet {
|
|||
}
|
||||
|
||||
pub fn load_texture(&self, ctx: &mut Context, constants: &EngineConstants, name: &str) -> GameResult<SizedBatch> {
|
||||
let path = FILE_TYPES
|
||||
let path = self.paths.iter().find_map(|s| FILE_TYPES
|
||||
.iter()
|
||||
.map(|ext| [&self.base_path, name, ext].join(""))
|
||||
.find(|path| filesystem::exists(ctx, path))
|
||||
.or_else(|| FILE_TYPES
|
||||
.iter()
|
||||
.map(|ext| [name, ext].join(""))
|
||||
.find(|path| filesystem::exists(ctx, path)))
|
||||
.ok_or_else(|| GameError::ResourceLoadError(format!("Texture {:?} does not exist.", name)))?;
|
||||
.map(|ext| [s, name, ext].join(""))
|
||||
.find(|path| {
|
||||
println!("{}", path);
|
||||
filesystem::exists(ctx, path)
|
||||
})
|
||||
).ok_or_else(|| GameError::ResourceLoadError(format!("Texture {} does not exist.", name)))?;
|
||||
|
||||
info!("Loading texture: {}", path);
|
||||
|
||||
|
|
Loading…
Reference in New Issue