1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2024-11-21 21:22:44 +00:00

various tweaks, dskinmeta format

This commit is contained in:
Alula 2021-06-21 13:12:58 +02:00
parent 937d12c87b
commit c1bd334844
No known key found for this signature in database
GPG key ID: 3E00485503A1D8BA
13 changed files with 398 additions and 103 deletions

View file

@ -64,6 +64,7 @@ sdl2 = { version = "=0.34.2", optional = true, features = ["unsafe_textures", "b
sdl2-sys = { version = "=0.34.2", optional = true, features = ["bundled", "static-link"] }
serde = { version = "1", features = ["derive"] }
serde_derive = "1"
serde_json = "1.0"
serde_yaml = "0.8"
strum = "0.20"
strum_macros = "0.20"

View file

@ -0,0 +1,113 @@
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"title": "JSON schema for doukutsu-rs .dskinmeta files.",
"type": "object",
"properties": {
"name": {
"description": "Name of the skin.",
"type": "string"
},
"description": {
"description": "Optional description of the skin.",
"type": "string"
},
"author": {
"description": "Optional author of the skin.",
"type": "string"
},
"gunOffsetX": {
"description": "Weapon offset from center in X axis.",
"type": "number",
"minimum": -32768,
"maximum": 32767
},
"gunOffsetY": {
"description": "Weapon offset from center in Y axis.",
"type": "number",
"minimum": -32768,
"maximum": 32767
},
"frameSizeWidth": {
"description": "Width of skin animation frame.",
"type": "number",
"minimum": 0,
"maximum": 256
},
"frameSizeHeight": {
"description": "Height of skin animation frame.",
"type": "number",
"minimum": 0,
"maximum": 256
},
"hitBox": {
"description": "Hit box of the skin. A [front, up, back, down] tuple specifying offsets from center in pixels (as in 1/16 of vanilla tile size, 0x200 as internal fix9 number).",
"type": "array",
"minItems": 4,
"maxItems": 4,
"items": [
{
"type": "number",
"minimum": 0,
"maximum": 65535
},
{
"type": "number",
"minimum": 0,
"maximum": 65535
},
{
"type": "number",
"minimum": 0,
"maximum": 65535
},
{
"type": "number",
"minimum": 0,
"maximum": 65535
}
]
},
"displayBox": {
"description": "Display box of the skin. A [front, up, back, down] tuple specifying offsets from center in pixels (as in 1/16 of vanilla tile size, 0x200 as internal fix9 number).",
"type": "array",
"minItems": 4,
"maxItems": 4,
"items": [
{
"type": "number",
"minimum": 0,
"maximum": 65535
},
{
"type": "number",
"minimum": 0,
"maximum": 65535
},
{
"type": "number",
"minimum": 0,
"maximum": 65535
},
{
"type": "number",
"minimum": 0,
"maximum": 65535
}
]
},
"custom": {
"description": "This field contains an optional object, meant for storing additional metadata not covered by this specification. Any keys with names that are unrecognized by application which is currently parsing the file must be ignored. To avoid value conflicts, we suggest attaching a prefix to every custom key eg. \"d-rs__gun_hitbox\".",
"type": "object"
},
"version": {
"type": "number",
"enum": [
1
]
}
},
"required": [
"name",
"version"
]
}

View file

@ -327,6 +327,18 @@ impl<T: Num + PartialOrd + Copy + Serialize> Serialize for Rect<T> {
state.end()
}
}
impl<T: Num + PartialOrd + Copy + Serialize> Default for Rect<T> {
fn default() -> Self {
Rect {
left: num_traits::zero(),
top: num_traits::zero(),
right: num_traits::zero(),
bottom: num_traits::zero()
}
}
}
macro_rules! rect_deserialze {
($num_type: ident) => {
impl<'de> Deserialize<'de> for Rect<$num_type> {

View file

@ -38,8 +38,6 @@ pub struct BoosterConsts {
#[derive(Debug, Copy, Clone)]
pub struct PlayerConsts {
pub display_bounds: Rect<u32>,
pub hit_bounds: Rect<u32>,
pub life: u16,
pub max_life: u16,
pub control_mode: ControlMode,
@ -288,8 +286,6 @@ impl EngineConstants {
is_switch: false,
supports_og_textures: false,
player: PlayerConsts {
display_bounds: Rect { left: 0x1000, top: 0x1000, right: 0x1000, bottom: 0x1000 },
hit_bounds: Rect { left: 5 * 0x200, top: 0x1000, right: 5 * 0x200, bottom: 0x1000 },
life: 3,
max_life: 3,
control_mode: ControlMode::Normal,
@ -1413,7 +1409,7 @@ impl EngineConstants {
menu_right: Rect { left: 236, top: 8, right: 244, bottom: 16 },
},
inventory_dim_color: Color::from_rgba(0, 0, 0, 0),
font_path: "builtin/builtin_font.fnt".to_string(),
font_path: "csfont.fnt".to_string(),
font_scale: 1.0,
font_space_offset: 0.0,
soundtracks: HashMap::new(),

View file

@ -240,7 +240,17 @@ pub fn init() -> GameResult {
}
#[cfg(not(target_os = "android"))]
mount_user_vfs(&mut context, Box::new(PhysicalFS::new(project_dirs.data_local_dir(), false)));
{
if let Ok(_) = crate::framework::filesystem::open(&mut context, "/.drs_localstorage") {
let mut user_dir = resource_dir.clone();
user_dir.push("_drs_profile");
let _ = std::fs::create_dir_all(&user_dir);
mount_user_vfs(&mut context, Box::new(PhysicalFS::new(&user_dir, false)));
} else {
mount_user_vfs(&mut context, Box::new(PhysicalFS::new(project_dirs.data_local_dir(), false)));
}
}
let game = UnsafeCell::new(Game::new(&mut context)?);
let state_ref = unsafe { &mut *((&mut *game.get()).state.get()) };

View file

@ -2,7 +2,9 @@ use crate::common::Rect;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::input::combined_menu_controller::CombinedMenuController;
use crate::player::skin::basic::BasicPlayerSkin;
use crate::shared_game_state::SharedGameState;
use std::cell::Cell;
pub struct MenuSaveInfo {}
@ -48,13 +50,25 @@ pub struct Menu {
entry_y: u16,
anim_num: u16,
anim_wait: u16,
custom_cursor: Cell<bool>,
}
static QUOTE_FRAMES: [u16; 4] = [0, 1, 0, 2];
impl Menu {
pub fn new(x: isize, y: isize, width: u16, height: u16) -> Menu {
Menu { x, y, width, height, selected: 0, entry_y: 0, anim_num: 0, anim_wait: 0, entries: Vec::new() }
Menu {
x,
y,
width,
height,
selected: 0,
entry_y: 0,
anim_num: 0,
anim_wait: 0,
entries: Vec::new(),
custom_cursor: Cell::new(true),
}
}
pub fn push_entry(&mut self, entry: MenuEntry) {
@ -165,16 +179,33 @@ impl Menu {
batch.draw(ctx)?;
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "MyChar")?;
if self.custom_cursor.get() {
if let Ok(batch) = state.texture_set.get_or_load_batch(ctx, &state.constants, "MenuCursor") {
rect.left = self.anim_num * 16;
rect.top = 16;
rect.right = rect.left + 16;
rect.bottom = rect.top + 16;
rect.left = QUOTE_FRAMES[self.anim_num as usize] * 16;
rect.top = 16;
rect.right = rect.left + 16;
rect.bottom = rect.top + 16;
batch.add_rect(self.x as f32, self.y as f32 + 3.0 + self.entry_y as f32, &rect);
batch.add_rect(self.x as f32, self.y as f32 + 2.0 + self.entry_y as f32, &rect);
batch.draw(ctx)?;
} else {
self.custom_cursor.set(false);
}
}
batch.draw(ctx)?;
if !self.custom_cursor.get() {
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "MyChar")?;
rect.left = QUOTE_FRAMES[self.anim_num as usize] * 16;
rect.top = 16;
rect.right = rect.left + 16;
rect.bottom = rect.top + 16;
batch.add_rect(self.x as f32, self.y as f32 + 2.0 + self.entry_y as f32, &rect);
batch.draw(ctx)?;
}
y = self.y as f32 + 6.0;
for entry in self.entries.iter() {

View file

@ -6,14 +6,29 @@ use crate::shared_game_state::SharedGameState;
use crate::stage::Stage;
use crate::npc::list::NPCList;
// -1 0 1 2
// +------------
// -1 | 10 14 15 16
// 0 | 11 1 2 5
// 1 | 12 3 4 6
// 2 | 13 8 9 7
pub const OFF_X: [i32; 16] = [0, 1, 0, 1, 2, 2, 2, 0, 1, -1, -1, -1, -1, 0, 1, 2];
pub const OFF_Y: [i32; 16] = [0, 0, 1, 1, 0, 1, 2, 2, 2, -1, 0, 1, 2, -1, -1, -1];
// -2 -1 0 1 2 3
// +------------------
// -2 | 26 32 33 34 35 36
// -1 | 27 10 14 15 16 18
// 0 | 28 11 1 2 5 19
// 1 | 29 12 3 4 6 20
// 2 | 30 13 8 9 7 21
// 3 | 31 22 23 24 25 17
pub const OFF_X: [i32; 36] = [
0, 1, 0, 1, 2, 2,
2, 0, 1, -1, -1, -1,
-1, 0, 1, 2, 3, 3,
3, 3, 3, -1, 0, 1,
2, -2, -2, -2, -2, -2,
-2, -1, 0, 1, 2, 3 ];
pub const OFF_Y: [i32; 36] = [
0, 0, 1, 1, 0, 1,
2, 2, 2, -1, 0, 1,
2, -1, -1, -1, 3, -1,
0, 1, 2, 3, 3, 3,
3, -2, -1, 0, 1, 2,
3, -2, -2, -2, -2, -2 ];
pub trait PhysicalEntity {
fn x(&self) -> i32;
@ -43,14 +58,16 @@ pub trait PhysicalEntity {
fn test_block_hit(&mut self, state: &mut SharedGameState, x: i32, y: i32) {
let bounds_x = if self.is_player() { 0x600 } else { 0x600 };
let bounds_y = if self.is_player() { 0x800 } else { 0x600 };
let bounds_top = if self.is_player() { 0x800 } else { 0x600 };
let bounds_bottom = if self.is_player() { 0x800 } else { 0x600 };
let half_tile_size = 16 * 0x100;
// left wall
if (self.y() - self.hit_bounds().top as i32) < ((y * 2 + 1) * 0x1000 - bounds_y)
&& (self.y() + self.hit_bounds().bottom as i32) > ((y * 2 - 1) * 0x1000 + bounds_y)
&& (self.x() - self.hit_bounds().right as i32) < (x * 2 + 1) * 0x1000
&& (self.x() - self.hit_bounds().right as i32) > x * 0x2000 {
self.set_x(((x * 2 + 1) * 0x1000) + self.hit_bounds().right as i32);
if (self.y() - self.hit_bounds().top as i32) < ((y * 2 + 1) * half_tile_size - bounds_top)
&& (self.y() + self.hit_bounds().bottom as i32) > ((y * 2 - 1) * half_tile_size + bounds_bottom)
&& (self.x() - self.hit_bounds().right as i32) < (x * 2 + 1) * half_tile_size
&& (self.x() - self.hit_bounds().right as i32) > (x * 2) * half_tile_size {
self.set_x(((x * 2 + 1) * half_tile_size) + self.hit_bounds().right as i32);
if self.is_player() {
if self.vel_x() < -0x180 {
@ -66,11 +83,11 @@ pub trait PhysicalEntity {
}
// right wall
if (self.y() - self.hit_bounds().top as i32) < ((y * 2 + 1) * 0x1000 - bounds_y)
&& (self.y() + self.hit_bounds().bottom as i32) > ((y * 2 - 1) * 0x1000 + bounds_y)
&& (self.x() + self.hit_bounds().right as i32) > (x * 2 - 1) * 0x1000
&& (self.x() + self.hit_bounds().right as i32) < x * 0x2000 {
self.set_x(((x * 2 - 1) * 0x1000) - self.hit_bounds().right as i32);
if (self.y() - self.hit_bounds().top as i32) < ((y * 2 + 1) * half_tile_size - bounds_top)
&& (self.y() + self.hit_bounds().bottom as i32) > ((y * 2 - 1) * half_tile_size + bounds_bottom)
&& (self.x() + self.hit_bounds().right as i32) > (x * 2 - 1) * half_tile_size
&& (self.x() + self.hit_bounds().right as i32) < (x * 2) * half_tile_size {
self.set_x(((x * 2 - 1) * half_tile_size) - self.hit_bounds().right as i32);
if self.is_player() {
if self.vel_x() > 0x180 {
@ -86,11 +103,11 @@ pub trait PhysicalEntity {
}
// ceiling
if ((self.x() - self.hit_bounds().right as i32) < (x * 2 + 1) * 0x1000 - bounds_x)
&& ((self.x() + self.hit_bounds().right as i32) > (x * 2 - 1) * 0x1000 + bounds_x)
&& (self.y() - self.hit_bounds().top as i32) < (y * 2 + 1) * 0x1000
&& (self.y() - self.hit_bounds().top as i32) > y * 0x2000 {
self.set_y(((y * 2 + 1) * 0x1000) + self.hit_bounds().top as i32);
if ((self.x() - self.hit_bounds().right as i32) < (x * 2 + 1) * half_tile_size - bounds_x)
&& ((self.x() + self.hit_bounds().right as i32) > (x * 2 - 1) * half_tile_size + bounds_x)
&& (self.y() - self.hit_bounds().top as i32) < (y * 2 + 1) * half_tile_size
&& (self.y() - self.hit_bounds().top as i32) > (y * 2) * half_tile_size {
self.set_y(((y * 2 + 1) * half_tile_size) + self.hit_bounds().top as i32);
if self.is_player() {
if !self.cond().hidden() && self.vel_y() < -0x200 {
@ -110,11 +127,11 @@ pub trait PhysicalEntity {
}
// floor
if ((self.x() - self.hit_bounds().right as i32) < (x * 2 + 1) * 0x1000 - bounds_x)
&& ((self.x() + self.hit_bounds().right as i32) > (x * 2 - 1) * 0x1000 + bounds_x)
&& ((self.y() + self.hit_bounds().bottom as i32) > ((y * 2 - 1) * 0x1000))
&& ((self.y() + self.hit_bounds().bottom as i32) < (y * 0x2000)) {
self.set_y(((y * 2 - 1) * 0x1000) - self.hit_bounds().bottom as i32);
if ((self.x() - self.hit_bounds().right as i32) < (x * 2 + 1) * half_tile_size - bounds_x)
&& ((self.x() + self.hit_bounds().right as i32) > (x * 2 - 1) * half_tile_size + bounds_x)
&& ((self.y() + self.hit_bounds().bottom as i32) > ((y * 2 - 1) * half_tile_size))
&& ((self.y() + self.hit_bounds().bottom as i32) < (y * 2) * half_tile_size) {
self.set_y(((y * 2 - 1) * half_tile_size) - self.hit_bounds().bottom as i32);
if self.is_player() {
if self.vel_y() > 0x400 {

View file

@ -89,8 +89,9 @@ pub struct Player {
}
impl Player {
pub fn new(state: &mut SharedGameState) -> Player {
pub fn new(state: &mut SharedGameState, ctx: &mut Context) -> Player {
let constants = &state.constants;
let skin = Box::new(BasicPlayerSkin::new("MyChar".to_string(), state, ctx));
Player {
x: 0,
@ -107,8 +108,8 @@ impl Player {
flags: Flag(0),
equip: Equipment(0),
direction: Direction::Right,
display_bounds: constants.player.display_bounds,
hit_bounds: constants.player.hit_bounds,
display_bounds: skin.get_display_bounds(),
hit_bounds: skin.get_hit_bounds(),
control_mode: constants.player.control_mode,
question: false,
booster_fuel: 0,
@ -126,7 +127,7 @@ impl Player {
damage: 0,
air_counter: 0,
air: 0,
skin: Box::new(BasicPlayerSkin::new("MyChar".to_string())),
skin,
controller: Box::new(DummyPlayerController::new()),
popup: NumberPopup::new(),
damage_counter: 0,
@ -738,7 +739,7 @@ impl GameEntity<&NPCList> for Player {
}
self.popup.x = self.x;
self.popup.y = self.y;
self.popup.y = self.y - self.display_bounds.top as i32 + 0x1000;
self.popup.tick(state, ())?;
self.cond.set_increase_acceleration(false);
@ -808,8 +809,8 @@ impl GameEntity<&NPCList> for Player {
) + if self.direction == Direction::Left { -8.0 } else { 0.0 }
- frame_x,
interpolate_fix9_scale(
self.prev_y - self.display_bounds.left as i32,
self.y - self.display_bounds.left as i32,
self.prev_y - self.display_bounds.top as i32,
self.y - self.display_bounds.top as i32,
state.frame_time,
) + self.weapon_offset_y as f32
- frame_y,
@ -829,8 +830,8 @@ impl GameEntity<&NPCList> for Player {
state.frame_time,
) - frame_x,
interpolate_fix9_scale(
self.prev_y - self.display_bounds.left as i32,
self.y - self.display_bounds.left as i32,
self.prev_y - self.display_bounds.top as i32,
self.y - self.display_bounds.top as i32,
state.frame_time,
) - frame_y,
&self.anim_rect,

View file

@ -35,7 +35,11 @@ impl PhysicalEntity for Player {
}
fn hit_rect_size(&self) -> usize {
2
if self.hit_bounds.top > 0x1000 || self.hit_bounds.bottom > 0x1000 || self.hit_bounds.right > 0x1000 {
4
} else {
2
}
}
#[inline(always)]

View file

@ -1,5 +1,70 @@
use lazy_static::lazy_static;
use crate::common::{Color, Direction, Rect};
use crate::framework::context::Context;
use crate::framework::filesystem;
use crate::framework::filesystem::File;
use crate::player::skin::{PlayerAnimationState, PlayerAppearanceState, PlayerSkin};
use crate::shared_game_state::SharedGameState;
#[derive(Default, Clone, serde_derive::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SkinMeta {
#[serde(default)]
pub name: String,
#[serde(default)]
pub description: String,
#[serde(default)]
pub author: String,
#[serde(default = "skinmeta_ret0i")]
pub gun_offset_x: i16,
#[serde(default = "skinmeta_ret0i")]
pub gun_offset_y: i16,
#[serde(default = "skinmeta_ret16")]
pub frame_size_width: u16,
#[serde(default = "skinmeta_ret16")]
pub frame_size_height: u16,
#[serde(default = "skinmeta_default_hit_box")]
pub hit_box: Rect<u16>,
#[serde(default = "skinmeta_default_display_box")]
pub display_box: Rect<u16>,
#[serde(default)]
pub version: u8,
}
const fn skinmeta_ret0i() -> i16 {
0
}
const fn skinmeta_ret16() -> u16 {
16
}
const fn skinmeta_default_hit_box() -> Rect<u16> {
Rect { left: 5, top: 8, right: 5, bottom: 8 }
}
const fn skinmeta_default_display_box() -> Rect<u16> {
Rect { left: 8, top: 8, right: 8, bottom: 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(),
gun_offset_x: 0,
gun_offset_y: 0,
frame_size_width: 16,
frame_size_height: 16,
hit_box: skinmeta_default_hit_box(),
display_box: skinmeta_default_display_box(),
version: 1
};
}
#[derive(Clone)]
pub struct BasicPlayerSkin {
@ -8,17 +73,36 @@ pub struct BasicPlayerSkin {
state: PlayerAnimationState,
appearance: PlayerAppearanceState,
direction: Direction,
metadata: SkinMeta,
tick: u16,
}
impl BasicPlayerSkin {
pub fn new(texture_name: String) -> BasicPlayerSkin {
pub fn new(texture_name: String, state: &SharedGameState, ctx: &mut Context) -> BasicPlayerSkin {
let metapath = format!("{}/{}.dskinmeta", state.base_path, texture_name);
let mut metadata = DEFAULT_SKINMETA.clone();
if let Ok(file) = filesystem::open(ctx, metapath) {
match serde_json::from_reader::<File, SkinMeta>(file) {
Ok(meta) if SUPPORTED_SKINMETA_VERSIONS.contains(&meta.version) => {
metadata = meta;
}
Ok(meta) => {
log::warn!("Unsupported skin metadata file version: {}", meta.version);
}
Err(err) => {
log::warn!("Failed to load skin metadata file: {:?}", err);
}
}
}
BasicPlayerSkin {
texture_name,
color: Color::new(1.0, 1.0, 1.0, 1.0),
state: PlayerAnimationState::Idle,
appearance: PlayerAppearanceState::Default,
direction: Direction::Left,
metadata,
tick: 0,
}
}
@ -49,14 +133,19 @@ impl PlayerSkin for BasicPlayerSkin {
PlayerAnimationState::FallingUpsideDown => 10,
};
let y_offset = if direction == Direction::Left { 0 } else { 16 }
let y_offset = if direction == Direction::Left { 0 } else { self.metadata.frame_size_height }
+ match self.appearance {
PlayerAppearanceState::Default => 0,
PlayerAppearanceState::MimigaMask => 32,
PlayerAppearanceState::Custom(i) => (i as u16).saturating_mul(16),
PlayerAppearanceState::MimigaMask => self.metadata.frame_size_height.saturating_mul(2),
PlayerAppearanceState::Custom(i) => (i as u16).saturating_mul(self.metadata.frame_size_height),
};
Rect::new_size(frame_id * 16, y_offset, 16, 16)
Rect::new_size(
frame_id.saturating_mul(self.metadata.frame_size_width),
y_offset,
self.metadata.frame_size_width,
self.metadata.frame_size_height,
)
}
fn animation_frame(&self) -> Rect<u16> {
@ -111,4 +200,26 @@ impl PlayerSkin for BasicPlayerSkin {
fn get_mask_texture_name(&self) -> &str {
""
}
fn get_hit_bounds(&self) -> Rect<u32> {
let ubox = &self.metadata.hit_box;
Rect {
left: ubox.left as u32 * 0x200,
top: ubox.top as u32 * 0x200,
right: ubox.right as u32 * 0x200,
bottom: ubox.bottom as u32 * 0x200,
}
}
fn get_display_bounds(&self) -> Rect<u32> {
let ubox = &self.metadata.display_box;
Rect {
left: ubox.left as u32 * 0x200,
top: ubox.top as u32 * 0x200,
right: ubox.right as u32 * 0x200,
bottom: ubox.bottom as u32 * 0x200,
}
}
}

View file

@ -79,6 +79,12 @@ pub trait PlayerSkin: PlayerSkinClone {
/// Returns the name of skin color mask texture.
fn get_mask_texture_name(&self) -> &str;
/// Returns hit bounds of skin.
fn get_hit_bounds(&self) -> Rect<u32>;
/// Returns display bounds of skin.
fn get_display_bounds(&self) -> Rect<u32>;
}
pub trait PlayerSkinClone {

View file

@ -104,8 +104,8 @@ impl GameScene {
stage,
water_params,
water_renderer,
player1: Player::new(state),
player2: Player::new(state),
player1: Player::new(state, ctx),
player2: Player::new(state, ctx),
inventory_player1: Inventory::new(),
inventory_player2: Inventory::new(),
boss_life_bar: BossLifeBar::new(),
@ -333,7 +333,19 @@ impl GameScene {
return Ok(());
}
FadeState::Hidden => {
graphics::clear(ctx, Color::from_rgb(0, 0, 32));
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Fade")?;
let mut rect = Rect::new(0, 0, 16, 16);
let frame = 15;
rect.left = frame * 16;
rect.right = rect.left + 16;
for x in 0..(state.canvas_size.0 as i32 / 16 + 1) {
for y in 0..(state.canvas_size.1 as i32 / 16 + 1) {
batch.add_rect(x as f32 * 16.0, y as f32 * 16.0, &rect);
}
}
batch.draw(ctx)?;
}
FadeState::FadeIn(tick, direction) | FadeState::FadeOut(tick, direction) => {
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Fade")?;
@ -617,7 +629,7 @@ impl GameScene {
(x * state.scale) as isize,
(y * state.scale) as isize,
(5.0 * state.scale) as isize,
(10.0 * state.scale) as isize,
(state.font.line_height(&state.constants) * state.scale) as isize,
),
Color::from_rgb(255, 255, 255),
)?;

View file

@ -1,10 +1,9 @@
use std::collections::HashMap;
use lazy_static::lazy_static;
use num_traits::clamp;
use vec_mut_scan::VecMutScan;
use crate::sound::pixtone_sfx::{DEFAULT_PIXTONE_TABLE};
use crate::sound::pixtone_sfx::DEFAULT_PIXTONE_TABLE;
use crate::sound::stuff::cubic_interp;
lazy_static! {
@ -110,33 +109,10 @@ impl Channel {
Channel {
enabled: false,
length: 0,
carrier: Waveform {
waveform_type: 0,
pitch: 0.0,
level: 0,
offset: 0,
},
frequency: Waveform {
waveform_type: 0,
pitch: 0.0,
level: 0,
offset: 0,
},
amplitude: Waveform {
waveform_type: 0,
pitch: 0.0,
level: 0,
offset: 0,
},
envelope: Envelope {
initial: 0,
time_a: 0,
value_a: 0,
time_b: 0,
value_b: 0,
time_c: 0,
value_c: 0,
},
carrier: Waveform { waveform_type: 0, pitch: 0.0, level: 0, offset: 0 },
frequency: Waveform { waveform_type: 0, pitch: 0.0, level: 0, offset: 0 },
amplitude: Waveform { waveform_type: 0, pitch: 0.0, level: 0, offset: 0 },
envelope: Envelope { initial: 0, time_a: 0, value_a: 0, time_b: 0, value_b: 0, time_c: 0, value_c: 0 },
}
}
}
@ -149,7 +125,7 @@ pub struct PixToneParameters {
impl PixToneParameters {
pub const fn empty() -> PixToneParameters {
PixToneParameters {
channels: [Channel::disabled(), Channel::disabled(), Channel::disabled(), Channel::disabled()]
channels: [Channel::disabled(), Channel::disabled(), Channel::disabled(), Channel::disabled()],
}
}
@ -162,7 +138,9 @@ impl PixToneParameters {
let mut samples = vec![0i16; length];
for channel in self.channels.iter() {
if !channel.enabled { continue; }
if !channel.enabled {
continue;
}
let mut phase = channel.carrier.offset as f32;
let delta = 256.0 * channel.carrier.pitch as f32 / channel.length as f32;
@ -178,10 +156,17 @@ impl PixToneParameters {
let s = |p: f32| -> f32 { 256.0 * p * i as f32 / channel.length as f32 };
let carrier = carrier_wave[0xff & phase as usize] as i32 * channel.carrier.level;
let freq = frequency_wave[0xff & (channel.frequency.offset as f32 + s(channel.frequency.pitch)) as usize] as i32 * channel.frequency.level;
let amp = amplitude_wave[0xff & (channel.amplitude.offset as f32 + s(channel.amplitude.pitch)) as usize] as i32 * channel.amplitude.level;
let freq = frequency_wave
[0xff & (channel.frequency.offset as f32 + s(channel.frequency.pitch)) as usize]
as i32
* channel.frequency.level;
let amp = amplitude_wave[0xff & (channel.amplitude.offset as f32 + s(channel.amplitude.pitch)) as usize]
as i32
* channel.amplitude.level;
*result = clamp((*result as i32) + (carrier * (amp + 4096) / 4096 * channel.envelope.evaluate(s(1.0) as i32) / 4096) * 256, -32767, 32767) as i16;
*result = ((*result as i32)
+ (carrier * (amp + 4096) / 4096 * channel.envelope.evaluate(s(1.0) as i32) / 4096) * 256)
.clamp(-32767, 32767) as i16;
phase += delta * (1.0 + (freq as f32 / (if freq < 0 { 8192.0 } else { 2048.0 })));
}
@ -208,11 +193,7 @@ impl PixTonePlayback {
table[i] = *params;
}
PixTonePlayback {
samples: HashMap::new(),
playback_state: vec![],
table,
}
PixTonePlayback { samples: HashMap::new(), playback_state: vec![], table }
}
pub fn create_samples(&mut self) {
@ -266,8 +247,8 @@ impl PixTonePlayback {
} else {
let pos = state.1 as usize;
let s1 = (sample[pos] as f32) / 32768.0;
let s2 = (sample[clamp(pos + 1, 0, sample.len() - 1)] as f32) / 32768.0;
let s3 = (sample[clamp(pos + 2, 0, sample.len() - 1)] as f32) / 32768.0;
let s2 = (sample[(pos + 1).clamp(0, sample.len() - 1)] as f32) / 32768.0;
let s3 = (sample[(pos + 2).clamp(0, sample.len() - 1)] as f32) / 32768.0;
let s4 = (sample[pos.saturating_sub(1)] as f32) / 32768.0;
let s = cubic_interp(s1, s2, s4, s3, state.1.fract()) * 32768.0;