diff --git a/assets/worlds/mainworld.json b/assets/worlds/mainworld.json index ca552aa..40d085e 100644 --- a/assets/worlds/mainworld.json +++ b/assets/worlds/mainworld.json @@ -8,13 +8,6 @@ "y": 50.0 }, "fish": [ - { - "x": 500.0, - "y": 300.0 - }, - { - "x": 800.0, - "y": 200.0 - } + {"x":49,"y":801},{"x":570,"y":594},{"x":761,"y":186},{"x":760,"y":940},{"x":241,"y":32},{"x":501,"y":18},{"x":487,"y":37},{"x":802,"y":849},{"x":864,"y":43},{"x":544,"y":886},{"x":987,"y":710},{"x":949,"y":404},{"x":694,"y":32},{"x":364,"y":899},{"x":26,"y":849},{"x":253,"y":627},{"x":39,"y":547},{"x":307,"y":730},{"x":133,"y":967},{"x":861,"y":76},{"x":199,"y":229},{"x":617,"y":532},{"x":391,"y":388},{"x":491,"y":816},{"x":539,"y":243},{"x":222,"y":288},{"x":81,"y":784},{"x":432,"y":830},{"x":741,"y":737},{"x":426,"y":480},{"x":591,"y":437},{"x":903,"y":380},{"x":653,"y":349},{"x":684,"y":235},{"x":797,"y":438},{"x":546,"y":615},{"x":497,"y":523},{"x":406,"y":468},{"x":173,"y":183},{"x":641,"y":187},{"x":517,"y":294},{"x":527,"y":650},{"x":962,"y":237},{"x":25,"y":868},{"x":16,"y":369},{"x":434,"y":712},{"x":632,"y":315},{"x":172,"y":421},{"x":450,"y":53},{"x":731,"y":220},{"x":532,"y":467},{"x":816,"y":497},{"x":948,"y":539},{"x":467,"y":829},{"x":533,"y":809},{"x":146,"y":989},{"x":850,"y":245},{"x":989,"y":214},{"x":203,"y":354},{"x":466,"y":611},{"x":382,"y":376},{"x":111,"y":148},{"x":411,"y":77},{"x":124,"y":418},{"x":154,"y":611},{"x":56,"y":732},{"x":800,"y":488},{"x":851,"y":668},{"x":240,"y":220},{"x":1000,"y":62},{"x":95,"y":784},{"x":700,"y":428},{"x":735,"y":517},{"x":259,"y":843},{"x":647,"y":268},{"x":668,"y":823},{"x":198,"y":241},{"x":243,"y":422},{"x":838,"y":433},{"x":642,"y":97},{"x":563,"y":974},{"x":386,"y":548},{"x":646,"y":482},{"x":691,"y":794},{"x":167,"y":485},{"x":978,"y":622},{"x":845,"y":431},{"x":529,"y":719},{"x":963,"y":145},{"x":6,"y":412},{"x":381,"y":830},{"x":918,"y":118},{"x":915,"y":27},{"x":618,"y":262},{"x":250,"y":635},{"x":100,"y":500},{"x":442,"y":321},{"x":769,"y":767},{"x":714,"y":204},{"x":506,"y":872},{"x":575,"y":178},{"x":256,"y":411},{"x":921,"y":617},{"x":971,"y":214},{"x":726,"y":702},{"x":103,"y":450},{"x":501,"y":134},{"x":265,"y":993},{"x":31,"y":63},{"x":502,"y":448},{"x":46,"y":457},{"x":809,"y":184},{"x":763,"y":962},{"x":632,"y":873},{"x":916,"y":761},{"x":710,"y":720},{"x":873,"y":222},{"x":256,"y":861},{"x":246,"y":482},{"x":390,"y":812},{"x":28,"y":247},{"x":516,"y":523},{"x":869,"y":43},{"x":680,"y":740},{"x":406,"y":65},{"x":657,"y":196},{"x":692,"y":635},{"x":97,"y":993},{"x":616,"y":490},{"x":515,"y":955},{"x":412,"y":502},{"x":743,"y":565},{"x":16,"y":499},{"x":324,"y":582},{"x":871,"y":62},{"x":128,"y":476},{"x":716,"y":525},{"x":627,"y":2},{"x":730,"y":913},{"x":704,"y":522},{"x":242,"y":934},{"x":172,"y":277},{"x":651,"y":948},{"x":349,"y":263},{"x":731,"y":967},{"x":382,"y":762},{"x":217,"y":15},{"x":49,"y":25},{"x":583,"y":110},{"x":700,"y":620},{"x":230,"y":537},{"x":285,"y":978},{"x":4,"y":791},{"x":939,"y":866},{"x":371,"y":342},{"x":759,"y":870},{"x":892,"y":103},{"x":57,"y":129},{"x":233,"y":383},{"x":171,"y":472},{"x":173,"y":842},{"x":516,"y":464},{"x":407,"y":458},{"x":963,"y":231},{"x":526,"y":253},{"x":815,"y":857},{"x":175,"y":909},{"x":993,"y":255},{"x":129,"y":390},{"x":76,"y":997},{"x":833,"y":174},{"x":501,"y":396},{"x":897,"y":218},{"x":876,"y":601},{"x":41,"y":165},{"x":993,"y":473},{"x":606,"y":308},{"x":831,"y":382},{"x":517,"y":828},{"x":984,"y":26},{"x":286,"y":712},{"x":422,"y":311},{"x":448,"y":103},{"x":260,"y":229},{"x":5,"y":738},{"x":283,"y":346},{"x":744,"y":463},{"x":634,"y":719},{"x":446,"y":977},{"x":220,"y":89},{"x":745,"y":866},{"x":851,"y":860},{"x":369,"y":940},{"x":828,"y":577},{"x":350,"y":337},{"x":334,"y":378},{"x":203,"y":248},{"x":665,"y":788},{"x":334,"y":927},{"x":307,"y":764},{"x":500,"y":763},{"x":613,"y":843},{"x":384,"y":253},{"x":956,"y":569},{"x":846,"y":137},{"x":105,"y":728},{"x":686,"y":226},{"x":657,"y":52},{"x":592,"y":433},{"x":997,"y":820},{"x":746,"y":389},{"x":405,"y":448},{"x":973,"y":19},{"x":538,"y":518},{"x":790,"y":275},{"x":633,"y":738},{"x":128,"y":484},{"x":603,"y":371},{"x":932,"y":21},{"x":582,"y":445},{"x":438,"y":793},{"x":963,"y":69},{"x":158,"y":263},{"x":988,"y":297},{"x":249,"y":227},{"x":245,"y":466},{"x":131,"y":495},{"x":620,"y":266},{"x":505,"y":384},{"x":813,"y":647},{"x":113,"y":66},{"x":757,"y":10},{"x":2,"y":707},{"x":540,"y":140},{"x":562,"y":691},{"x":484,"y":433},{"x":859,"y":455},{"x":248,"y":117},{"x":36,"y":432},{"x":798,"y":754},{"x":611,"y":291},{"x":664,"y":770},{"x":299,"y":788},{"x":433,"y":920},{"x":540,"y":739},{"x":201,"y":829},{"x":972,"y":362},{"x":811,"y":120},{"x":941,"y":670},{"x":186,"y":448},{"x":549,"y":611},{"x":206,"y":387},{"x":973,"y":437},{"x":700,"y":709},{"x":472,"y":243},{"x":971,"y":518},{"x":184,"y":540},{"x":271,"y":257},{"x":290,"y":895},{"x":546,"y":7},{"x":256,"y":542},{"x":418,"y":553},{"x":816,"y":875},{"x":908,"y":547},{"x":315,"y":354},{"x":266,"y":471},{"x":242,"y":88},{"x":785,"y":52},{"x":497,"y":47},{"x":466,"y":279},{"x":750,"y":690},{"x":329,"y":296},{"x":545,"y":715},{"x":508,"y":562},{"x":993,"y":467},{"x":703,"y":733},{"x":824,"y":11},{"x":419,"y":337},{"x":393,"y":229},{"x":898,"y":261},{"x":264,"y":708},{"x":711,"y":768},{"x":568,"y":409},{"x":473,"y":342},{"x":329,"y":53},{"x":95,"y":815},{"x":783,"y":977},{"x":48,"y":551},{"x":635,"y":931},{"x":653,"y":86},{"x":9,"y":153},{"x":955,"y":660},{"x":480,"y":716},{"x":936,"y":622},{"x":607,"y":221},{"x":423,"y":545},{"x":507,"y":668},{"x":676,"y":957},{"x":253,"y":515},{"x":327,"y":495},{"x":965,"y":808},{"x":2,"y":807},{"x":276,"y":199},{"x":584,"y":75},{"x":770,"y":51},{"x":667,"y":717},{"x":944,"y":913},{"x":982,"y":977},{"x":618,"y":482},{"x":372,"y":545},{"x":507,"y":518},{"x":604,"y":492},{"x":772,"y":730},{"x":350,"y":141},{"x":783,"y":437},{"x":282,"y":714},{"x":269,"y":691},{"x":991,"y":386},{"x":234,"y":196},{"x":908,"y":635},{"x":785,"y":340},{"x":125,"y":712},{"x":466,"y":210},{"x":280,"y":185},{"x":995,"y":466},{"x":589,"y":258},{"x":700,"y":120},{"x":855,"y":323},{"x":690,"y":355},{"x":755,"y":353},{"x":378,"y":970},{"x":865,"y":270},{"x":220,"y":62},{"x":685,"y":848},{"x":670,"y":907},{"x":710,"y":671},{"x":209,"y":68},{"x":642,"y":470},{"x":104,"y":642},{"x":631,"y":328},{"x":898,"y":424},{"x":909,"y":427},{"x":189,"y":141},{"x":259,"y":993},{"x":332,"y":791},{"x":842,"y":778},{"x":63,"y":390},{"x":146,"y":895},{"x":230,"y":274},{"x":316,"y":447},{"x":603,"y":59},{"x":377,"y":841},{"x":602,"y":119},{"x":728,"y":557},{"x":395,"y":514},{"x":379,"y":754},{"x":822,"y":840},{"x":860,"y":478},{"x":695,"y":360},{"x":156,"y":784},{"x":241,"y":353},{"x":195,"y":199},{"x":284,"y":110},{"x":484,"y":966},{"x":889,"y":370},{"x":246,"y":684},{"x":710,"y":345},{"x":382,"y":635},{"x":447,"y":948},{"x":741,"y":274},{"x":224,"y":883},{"x":99,"y":37},{"x":472,"y":803},{"x":141,"y":397},{"x":371,"y":602},{"x":7,"y":482},{"x":184,"y":990},{"x":555,"y":313},{"x":573,"y":886},{"x":167,"y":365},{"x":810,"y":721},{"x":958,"y":767},{"x":891,"y":561},{"x":314,"y":987},{"x":156,"y":95},{"x":349,"y":542},{"x":775,"y":35},{"x":121,"y":655},{"x":311,"y":242},{"x":534,"y":135},{"x":71,"y":134},{"x":367,"y":896},{"x":447,"y":524},{"x":120,"y":421},{"x":878,"y":398},{"x":469,"y":822},{"x":483,"y":966},{"x":240,"y":880},{"x":759,"y":980},{"x":531,"y":759},{"x":395,"y":118},{"x":354,"y":360},{"x":173,"y":924},{"x":550,"y":958},{"x":888,"y":379},{"x":244,"y":448},{"x":999,"y":554},{"x":941,"y":455},{"x":798,"y":916},{"x":134,"y":123},{"x":90,"y":440},{"x":923,"y":263},{"x":405,"y":595},{"x":194,"y":387},{"x":370,"y":697},{"x":943,"y":888},{"x":607,"y":336},{"x":168,"y":105},{"x":874,"y":66},{"x":675,"y":50},{"x":601,"y":242},{"x":925,"y":728},{"x":643,"y":609},{"x":769,"y":713},{"x":410,"y":913},{"x":153,"y":776},{"x":775,"y":949},{"x":184,"y":93},{"x":624,"y":632},{"x":899,"y":804},{"x":909,"y":327},{"x":371,"y":510},{"x":663,"y":415},{"x":337,"y":542},{"x":248,"y":104},{"x":925,"y":450},{"x":310,"y":925},{"x":4,"y":550},{"x":559,"y":652},{"x":671,"y":296},{"x":414,"y":60},{"x":972,"y":505},{"x":221,"y":147},{"x":318,"y":592},{"x":861,"y":656},{"x":258,"y":675},{"x":565,"y":390},{"x":703,"y":236},{"x":227,"y":76},{"x":989,"y":252},{"x":924,"y":419},{"x":983,"y":971},{"x":795,"y":244},{"x":256,"y":498},{"x":517,"y":674},{"x":89,"y":197},{"x":366,"y":234},{"x":41,"y":952},{"x":487,"y":981},{"x":939,"y":922},{"x":384,"y":315},{"x":958,"y":57},{"x":499,"y":152},{"x":716,"y":167},{"x":167,"y":301},{"x":781,"y":964},{"x":101,"y":215},{"x":605,"y":396},{"x":31,"y":973},{"x":128,"y":831},{"x":685,"y":701},{"x":150,"y":507},{"x":663,"y":77},{"x":792,"y":561},{"x":398,"y":281},{"x":168,"y":936},{"x":8,"y":266},{"x":19,"y":723},{"x":377,"y":975},{"x":68,"y":114},{"x":191,"y":784},{"x":94,"y":222},{"x":986,"y":578},{"x":474,"y":160},{"x":936,"y":945},{"x":603,"y":778},{"x":105,"y":845},{"x":955,"y":583},{"x":832,"y":905},{"x":264,"y":132},{"x":219,"y":747},{"x":515,"y":562},{"x":178,"y":198},{"x":999,"y":1},{"x":470,"y":345},{"x":450,"y":490},{"x":967,"y":306},{"x":257,"y":360},{"x":632,"y":26},{"x":916,"y":382},{"x":631,"y":194},{"x":492,"y":235},{"x":479,"y":373},{"x":887,"y":154},{"x":65,"y":181},{"x":956,"y":879},{"x":567,"y":578},{"x":718,"y":617},{"x":464,"y":243},{"x":545,"y":410},{"x":923,"y":340},{"x":978,"y":716},{"x":277,"y":261},{"x":462,"y":600},{"x":687,"y":507} ] } \ No newline at end of file diff --git a/src/entities/enemy/base.rs b/src/entities/enemy/base.rs new file mode 100644 index 0000000..c330af7 --- /dev/null +++ b/src/entities/enemy/base.rs @@ -0,0 +1,7 @@ + + +pub trait EnemyBase { + fn render(); + fn handle_logic(); + fn handle_getting_attacked(); +} \ No newline at end of file diff --git a/src/entities/enemy/mod.rs b/src/entities/enemy/mod.rs new file mode 100644 index 0000000..307b359 --- /dev/null +++ b/src/entities/enemy/mod.rs @@ -0,0 +1 @@ +pub mod base; \ No newline at end of file diff --git a/src/entities/fish.rs b/src/entities/fish.rs index 452d9ed..8982d0b 100644 --- a/src/entities/fish.rs +++ b/src/entities/fish.rs @@ -1,30 +1,51 @@ -use rand::{Rng, prelude::ThreadRng}; +use rand::{prelude::ThreadRng, Rng}; use raylib::prelude::*; -use crate::{gamecore::GameCore, lib::utils::triangles::rotate_vector, player::Player, world::World}; +use crate::{ + gamecore::GameCore, lib::utils::triangles::rotate_vector, player::Player, world::World, +}; const FISH_FOLLOW_PLAYER_DISTANCE: f32 = 30.0; const FISH_FOLLOW_PLAYER_SPEED: f32 = 1.8; const FISH_FOLLOW_PLAYER_SPEED_FAST: f32 = FISH_FOLLOW_PLAYER_SPEED * 3.0; const FISH_ATTACH_RADIUS: f32 = 20.0; +const FISH_VISION: f32 = 25.0; +const FISH_MAX_SPEED: f32 = 2.0; +const FISH_MAX_FORCE: f32 = 0.05; +const FISH_FACTOR_ATTRACTION: f32 = 1.0; +const FISH_FACTOR_PLAYER: f32 = 0.1; +const FISH_FACTOR_COHESION: f32 = 0.1; +const FISH_SEPARATION_DISTANCE: f32 = 15.0; +const FISH_FACTOR_SEPARATION: f32 = 1.5; + #[derive(Debug, Clone)] pub struct FishEntity { position: Vector2, direction: Vector2, + velocity: Vector2, pub following_player: bool, size: Vector2, - rng: ThreadRng + rng: ThreadRng, + color: Color, } impl FishEntity { pub fn new(position: Vector2) -> Self { + let mut rng = rand::thread_rng(); Self { position: position, direction: Vector2::zero(), + velocity: Vector2::zero(), following_player: false, size: Vector2 { x: 5.0, y: 8.0 }, - rng: rand::thread_rng() + color: Color { + r: rng.gen_range(128..225), + g: rng.gen_range(128..225), + b: rng.gen_range(128..225), + a: 140, + }, + rng, } } @@ -37,33 +58,80 @@ impl FishEntity { } pub fn handle_follow_player(&mut self, player: &Player, dt: f64, other_fish: &Vec) { - // Distance and direction to player - let dist_to_player = player.position - self.position; - let dist_to_player_lin = self.position.distance_to(player.position); - let mut direction_to_player = dist_to_player; - direction_to_player.normalize(); + let mut acceleration: Vector2 = Vector2::zero(); - // Fish movement - let movement; - - // Random variance - let variance = self.rng.gen_range(500.0..1000.0) / 1000.0; - - // If the fish is double its follow distance from the player - if dist_to_player_lin.abs() > (FISH_FOLLOW_PLAYER_DISTANCE * 2.0) { - movement = direction_to_player * FISH_FOLLOW_PLAYER_SPEED_FAST * variance; - } else { - // Move slowly in the direction of the player unless too close - if dist_to_player_lin.abs() > FISH_FOLLOW_PLAYER_DISTANCE { - movement = direction_to_player * FISH_FOLLOW_PLAYER_SPEED * variance; - } else { - movement = Vector2::zero(); + let mut steer: Vector2 = Vector2::zero(); + let mut count1: u16 = 0; + let mut sum1: Vector2 = Vector2::zero(); + let mut count2: u16 = 0; + let mut sum2: Vector2 = Vector2::zero(); + let mut count3: u16 = 0; + // separation + for i in other_fish { + let dist = (self.position - i.position).length(); + if dist < FISH_SEPARATION_DISTANCE && dist > 0.0 { + let mut diff: Vector2 = self.position - i.position; + diff.normalize(); + diff /= dist; + steer += diff; + count1 += 1; + } + if dist < FISH_VISION && dist > 0.0 { + sum1 += i.direction; + count2 += 1; + sum2 += i.position; + count3 += 1; } } + if count1 > 0 { + steer /= count1 as f32; + } + if steer.x != 0.0 || steer.y != 0.0 { + steer.normalize(); + steer *= FISH_MAX_SPEED; + steer -= self.velocity; + steer.x = f32::min(f32::max(steer.x, -FISH_MAX_FORCE), FISH_MAX_FORCE); + steer.y = f32::min(f32::max(steer.y, -FISH_MAX_FORCE), FISH_MAX_FORCE); + acceleration += steer * FISH_FACTOR_SEPARATION; + } + + // attraction + if count2 > 0 { + sum1 /= count2 as f32; + sum1.normalize(); + sum1 *= FISH_MAX_SPEED; + sum1 -= self.velocity; + sum1.x = f32::min(f32::max(sum1.x, -FISH_MAX_FORCE), FISH_MAX_FORCE); + sum1.y = f32::min(f32::max(sum1.y, -FISH_MAX_FORCE), FISH_MAX_FORCE); + acceleration += sum1 * FISH_FACTOR_ATTRACTION; + } + + // cohesion + if count3 > 0 { + sum2 /= count3 as f32; + let mut desired: Vector2 = sum2 - self.position; + + desired.normalize(); + desired *= FISH_MAX_SPEED; + + desired.x = f32::min(f32::max(desired.x, -FISH_MAX_FORCE), FISH_MAX_FORCE); + desired.y = f32::min(f32::max(desired.y, -FISH_MAX_FORCE), FISH_MAX_FORCE); + + acceleration += desired * FISH_FACTOR_COHESION; + } + + // turn to player + let mut player_factor: Vector2 = player.position - self.position; + player_factor.normalize(); + acceleration += player_factor * FISH_FACTOR_PLAYER; // Move the fish - self.direction = direction_to_player; - self.position += movement; + self.direction = self.velocity.normalized(); + self.velocity += acceleration; + + self.velocity.x = f32::min(f32::max(self.velocity.x, -FISH_MAX_SPEED), FISH_MAX_SPEED); + self.velocity.y = f32::min(f32::max(self.velocity.y, -FISH_MAX_SPEED), FISH_MAX_SPEED); + self.position += self.velocity; } pub fn handle_free_movement(&mut self, player: &mut Player, dt: f64) { @@ -76,6 +144,7 @@ impl FishEntity { // Handle player picking up fish if player.position.distance_to(self.position).abs() <= player.size.y * 2.2 { self.following_player = true; + self.velocity = self.direction.normalized(); // Add currency to the player player.coins += 1; @@ -127,7 +196,7 @@ impl FishEntity { self.position + fish_front, self.position + fish_bl, self.position + fish_br, - Color::BLACK, + self.color, ); } } diff --git a/src/entities/mod.rs b/src/entities/mod.rs index e948307..5bf6e50 100644 --- a/src/entities/mod.rs +++ b/src/entities/mod.rs @@ -1 +1,2 @@ -pub mod fish; \ No newline at end of file +pub mod fish; +pub mod enemy; \ No newline at end of file diff --git a/src/gamecore.rs b/src/gamecore.rs index d8216b7..19b9592 100644 --- a/src/gamecore.rs +++ b/src/gamecore.rs @@ -6,7 +6,11 @@ use raylib::{ camera::Camera2D, math::Vector2, prelude::RaylibDrawHandle, RaylibHandle, RaylibThread, }; -use crate::{items::ShopItems, player::Player, resources::GlobalResources, world::World}; +use crate::{ + player::{Player, PlayerInventory}, + resources::GlobalResources, + world::World, +}; use failure::Error; use log::debug; @@ -31,10 +35,10 @@ impl fmt::Display for GameState { #[derive(Debug, Serialize, Deserialize, Default)] pub struct GameProgress { - coins: u32, - max_depth: f32, - fastest_time: Option, - inventory: Vec, + pub coins: u32, + pub max_depth: f32, + pub fastest_time: Option, + pub inventory: PlayerInventory, } impl GameProgress { diff --git a/src/items.rs b/src/items.rs index 153cd84..7971866 100644 --- a/src/items.rs +++ b/src/items.rs @@ -1,4 +1,26 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; + +// #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +// #[serde(tag = "t", content = "c")] +// pub enum ShopItems { +// StunGun, +// AirBag, +// Flashlight { power: u8 }, +// Flippers { speed_increase: u8 }, +// } + + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct StunGun { + pub range: f32, + pub duration: f64 +} + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct AirBag; + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct Flashlight; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[serde(tag = "t", content = "c")] @@ -53,5 +75,7 @@ impl ShopItems{ } } - +} +pub struct Flippers { + pub speed_increase: f32 } \ No newline at end of file diff --git a/src/lib/utils/mod.rs b/src/lib/utils/mod.rs index 9d6dd04..baba8af 100644 --- a/src/lib/utils/mod.rs +++ b/src/lib/utils/mod.rs @@ -1,2 +1,12 @@ pub mod profiler; -pub mod triangles; \ No newline at end of file +pub mod triangles; + +pub fn calculate_linear_slide(playthrough_percent: f64) -> f64 { + if playthrough_percent < 0.25 { + return playthrough_percent / 0.25; + } else if playthrough_percent > 0.75 { + return 1.0 - ((playthrough_percent - 0.75) / 0.25); + } else { + return 1.0; + } +} \ No newline at end of file diff --git a/src/lib/utils/profiler.rs b/src/lib/utils/profiler.rs index c813885..e8af3ea 100644 --- a/src/lib/utils/profiler.rs +++ b/src/lib/utils/profiler.rs @@ -1,3 +1,4 @@ +use raylib::math::Vector2; use serde_json::json; use serialstudio::{ data::{DataGroup, DataSet, TelemetryFrame}, @@ -21,7 +22,8 @@ pub struct ProfilerData { // Player pub player_coins: u32, pub player_boost_percent: f32, - pub player_breath_percent: f32 + pub player_breath_percent: f32, + pub player_pose: Vector2 } /// The development profiler @@ -147,6 +149,20 @@ impl GameProfiler { unit: Some("%".to_string()), w_type: None, }, + DataSet { + title: Some("X".to_string()), + value: json!(self.data.player_pose.x), + graph: Some(false), + unit: Some("pixels".to_string()), + w_type: None, + }, + DataSet { + title: Some("Y".to_string()), + value: json!(self.data.player_pose.y), + graph: Some(false), + unit: Some("pixels".to_string()), + w_type: None, + }, ], }, ], diff --git a/src/logic/ingame/hud.rs b/src/logic/ingame/hud.rs index 6a70ff9..e7e508d 100644 --- a/src/logic/ingame/hud.rs +++ b/src/logic/ingame/hud.rs @@ -8,13 +8,7 @@ pub fn render_hud( window_center: Vector2, ) { // Get the relevant data - let dist_from_player_to_end = game_core - .player - .position - .distance_to(game_core.world.end_position); - let dist_from_start_to_end = Vector2::zero().distance_to(game_core.world.end_position); - let progress = ((dist_from_start_to_end - dist_from_player_to_end) / dist_from_start_to_end) - .clamp(0.0, 1.0); + let progress = game_core.player.calculate_depth_percent(&game_core.world); // Determine the progress slider position let slider_bound_height = 20.0; diff --git a/src/logic/ingame/mod.rs b/src/logic/ingame/mod.rs index 0ab477d..d96e605 100644 --- a/src/logic/ingame/mod.rs +++ b/src/logic/ingame/mod.rs @@ -147,7 +147,8 @@ impl Screen for InGameScreen { } // Render Player - playerlogic::render_player(&mut context_2d, game_core); + // playerlogic::render_player(&mut context_2d, game_core); + game_core.player.render(&mut context_2d, &mut game_core.resources, dt); } diff --git a/src/logic/ingame/playerlogic.rs b/src/logic/ingame/playerlogic.rs index 6446ec5..afd21f5 100644 --- a/src/logic/ingame/playerlogic.rs +++ b/src/logic/ingame/playerlogic.rs @@ -76,6 +76,10 @@ pub fn update_player_movement( let user_request_boost = draw_handle.is_mouse_button_down(MouseButton::MOUSE_LEFT_BUTTON); let user_request_action = draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_RIGHT_BUTTON); + if user_request_action { + game_core.player.begin_attack(); + } + // Move the player in their direction let speed_multiplier; if user_request_boost && game_core.player.boost_percent >= 0.0 { @@ -128,7 +132,8 @@ pub fn update_player_movement( // Only do this if the mouse is far enough away let player_real_movement = game_core.player.direction * speed_multiplier; - if raw_movement_direction.distance_to(Vector2::zero()) > game_core.player.size.y / 2.0 { + let player_stunned = game_core.player.stun_timer > 0.0; + if raw_movement_direction.distance_to(Vector2::zero()) > game_core.player.size.y / 2.0 || player_stunned{ game_core.player.is_moving = true; game_core.player.position += player_real_movement; @@ -145,6 +150,11 @@ pub fn update_player_movement( } } else { game_core.player.is_moving = false; + + // Handle updating the stun timer + if player_stunned { + game_core.player.stun_timer -= dt; + } } // Move the camera to follow the player @@ -169,60 +179,3 @@ pub fn update_player_movement( // } } -pub fn render_player(context_2d: &mut RaylibMode2D, game_core: &mut GameCore) { - // Get the player - let player = &game_core.player; - - // Convert the player direction to a rotation - let player_rotation = Vector2::zero().angle_to(player.direction); - - // Render the player's boost ring - // This functions both as a breath meter, and as a boost meter - let boost_ring_max_radius = player.size.x + 5.0; - context_2d.draw_circle( - player.position.x as i32, - player.position.y as i32, - boost_ring_max_radius * player.boost_percent, - TRANSLUCENT_WHITE_64, - ); - context_2d.draw_ring( - Vector2 { - x: player.position.x as i32 as f32, - y: player.position.y as i32 as f32, - }, - boost_ring_max_radius, - boost_ring_max_radius + 1.0, - 0, - (360.0 * player.breath_percent) as i32, - 0, - TRANSLUCENT_WHITE_96, - ); - - // Render the player based on what is happening - if player.is_boost_charging { - game_core.resources.player_animation_boost_charge.draw( - context_2d, - player.position, - player_rotation.to_degrees() - 90.0, - ); - } else if player.is_boosting { - game_core.resources.player_animation_boost.draw( - context_2d, - player.position, - player_rotation.to_degrees() - 90.0, - ); - } else if player.is_moving { - game_core.resources.player_animation_regular.draw( - context_2d, - player.position, - player_rotation.to_degrees() - 90.0, - ); - } else { - game_core.resources.player_animation_regular.draw_frame( - context_2d, - player.position, - player_rotation.to_degrees() - 90.0, - 0, - ); - } -} diff --git a/src/logic/loadingscreen.rs b/src/logic/loadingscreen.rs index 184322b..06056c1 100644 --- a/src/logic/loadingscreen.rs +++ b/src/logic/loadingscreen.rs @@ -1,9 +1,6 @@ use raylib::prelude::*; -use crate::{ - gamecore::{GameCore, GameState}, - lib::wrappers::audio::player::AudioPlayer, -}; +use crate::{gamecore::{GameCore, GameState}, lib::{utils::calculate_linear_slide, wrappers::audio::player::AudioPlayer}}; use super::screen::Screen; @@ -32,14 +29,7 @@ impl LoadingScreen { fn get_logo_mask(&self, playthrough_percent: f64) -> Color { // Determine the alpha - let alpha; - if playthrough_percent < 0.25 { - alpha = playthrough_percent / 0.25 - } else if playthrough_percent > 0.75 { - alpha = 1.0 - ((playthrough_percent - 0.75) / 0.25); - } else { - alpha = 1.0; - } + let alpha = calculate_linear_slide(playthrough_percent); // Build a color mask Color { diff --git a/src/main.rs b/src/main.rs index 75a8769..aa48d4a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -134,6 +134,7 @@ fn main() { profiler.data.player_coins = game_core.player.coins; profiler.data.player_boost_percent = game_core.player.boost_percent; profiler.data.player_breath_percent = game_core.player.breath_percent; + profiler.data.player_pose = game_core.player.position; // Send telemetry data profiler.update(); diff --git a/src/player.rs b/src/player.rs index 01c5343..6562a32 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,6 +1,17 @@ -use raylib::math::{Rectangle, Vector2}; +use raylib::prelude::*; +use serde::{Serialize, Deserialize}; +use crate::{gamecore::{GameCore, GameProgress}, items::{AirBag, Flashlight, Flippers, StunGun}, lib::utils::{calculate_linear_slide}, pallette::{TRANSLUCENT_WHITE_64, TRANSLUCENT_WHITE_96}, resources::GlobalResources, world::World}; -use crate::lib::utils::triangles::rotate_vector; +const AOE_RING_MAX_RADIUS: f32 = 60.0; +const STUN_ATTACK_TIME: f64 = 0.75; + +#[derive(Debug, Serialize, Deserialize, Default, Clone)] +pub struct PlayerInventory { + stun_gun: Option, + air_bag: Option, + flashlight: Option, + flippers: Option +} #[derive(Debug, Default)] pub struct Player { @@ -13,6 +24,9 @@ pub struct Player { pub is_moving: bool, pub is_boosting: bool, pub is_boost_charging: bool, + pub inventory: PlayerInventory, + pub stun_timer: f64, + pub attacking_timer: f64, } impl Player { @@ -56,4 +70,109 @@ impl Player { return rectangle.check_collision_circle_rec(self.position, (self.size.y * 0.5) / 2.0); } + + /// Stun the player + pub fn set_stun_seconds(&mut self, seconds: f64) { + self.stun_timer = seconds; + } + + /// Try to attack with the stun gun + pub fn begin_attack(&mut self) { + if true || self.inventory.stun_gun.is_some() && self.stun_timer == 0.0 { + self.attacking_timer = self.inventory.stun_gun.as_ref().unwrap().duration; + } + } + + /// Calculate how far the player is + pub fn calculate_depth_percent(&self, world: &World) -> f32 { + let dist_from_player_to_end = self.position.distance_to(world.end_position); + let dist_from_start_to_end = Vector2::zero().distance_to(world.end_position); + return ((dist_from_start_to_end - dist_from_player_to_end) / dist_from_start_to_end) + .clamp(0.0, 1.0); + } + + /// Create GameProgress from the current life + pub fn create_statistics(&self, game_core: &GameCore, current_time: f64) -> GameProgress { + GameProgress { + coins: self.coins, + inventory: self.inventory.clone(), + max_depth: self.calculate_depth_percent(&game_core.world), + fastest_time: Some(current_time - game_core.last_state_change_time), + } + } + + /// Render the player + pub fn render( + &mut self, + context_2d: &mut RaylibMode2D, + resources: &mut GlobalResources, + dt: f64, + ) { + // Convert the player direction to a rotation + let player_rotation = Vector2::zero().angle_to(self.direction); + + // Render the player's boost ring + // This functions both as a breath meter, and as a boost meter + let boost_ring_max_radius = self.size.x + 5.0; + context_2d.draw_circle( + self.position.x as i32, + self.position.y as i32, + boost_ring_max_radius * self.boost_percent, + TRANSLUCENT_WHITE_64, + ); + context_2d.draw_ring( + Vector2 { + x: self.position.x as i32 as f32, + y: self.position.y as i32 as f32, + }, + boost_ring_max_radius, + boost_ring_max_radius + 1.0, + 0, + (360.0 * self.breath_percent) as i32, + 0, + TRANSLUCENT_WHITE_96, + ); + + // Calculate AOE ring + if self.attacking_timer != 0.0 { + let aoe_ring = calculate_linear_slide( self.attacking_timer / STUN_ATTACK_TIME) as f32; + self.attacking_timer = (self.attacking_timer - dt).max(0.0); + + // Render attack AOE + context_2d.draw_circle_lines( + self.position.x as i32, + self.position.y as i32, + AOE_RING_MAX_RADIUS * aoe_ring, + TRANSLUCENT_WHITE_64, + ); + } + + // Render the player based on what is happening + if self.is_boost_charging { + resources.player_animation_boost_charge.draw( + context_2d, + self.position, + player_rotation.to_degrees() - 90.0, + ); + } else if self.is_boosting { + resources.player_animation_boost.draw( + context_2d, + self.position, + player_rotation.to_degrees() - 90.0, + ); + } else if self.is_moving { + resources.player_animation_regular.draw( + context_2d, + self.position, + player_rotation.to_degrees() - 90.0, + ); + } else { + resources.player_animation_regular.draw_frame( + context_2d, + self.position, + player_rotation.to_degrees() - 90.0, + 0, + ); + } + } }