use crate::enemies;
use crate::random;
use crate::sprites;
use crate::weapons;
use crate::win;
const MAX_ENEMIES: usize = 200;
const PERK_POWER: usize = 0;
const PERK_HEALTH: usize = 1;
const PERK_SPEED: usize = 2;
const PERK_RADIUS: usize = 3;
const PERK_HEAL: usize = 4;
const PERK_RECOVER: usize = 5;
const PERK_ATTRACT: usize = 6;
const PERK_XP: usize = 7;
const PERK_COOLDOWN: usize = 8;
const PERK_AXE: usize = 9;
const PERK_KNIFE: usize = 10;
const PERK_STAR: usize = 11;
const PERK_WIND: usize = 12;
#[derive(PartialEq, Clone, Copy)]
pub enum Dir {
Up,
Right,
Down,
Left,
}
#[derive(Clone, Copy)]
pub struct Pos {
pub x: f32,
pub y: f32,
}
impl Pos {
pub fn in_radius(&self, other: &Self, d: f32) -> bool {
let dx = self.x - other.x;
let dy = self.y - other.y;
return dx * dx + dy * dy < d * d;
}
}
pub struct Player {
pub p: Pos,
pub dir: Option
,
pub face: Dir,
pub speed: f32,
pub size: f32,
pub health: f32,
pub health_max: f32,
pub health_recover: f32,
pub power: f32,
pub weapons: Vec,
pub damage_radius: f32,
pub diamond_radius: f32,
pub xp: f32,
pub xp_factor: f32,
pub cooldown_factor: f32,
pub last_level: f32,
pub next_level: f32,
}
impl Player {
pub fn new() -> Self {
return Self {
p: Pos { x: 0.0, y: 0.0 },
dir: None,
face: Dir::Right,
speed: 30.0,
size: 9.0,
health: 100.0,
health_max: 100.0,
health_recover: 0.0,
power: 6.0,
weapons: weapons::create_weapons(),
damage_radius: 30.0,
diamond_radius: 15.0,
xp: 0.0,
xp_factor: 1.0,
cooldown_factor: 1.0,
last_level: 0.0,
next_level: 8.0,
};
}
pub fn recover(&mut self, dt: f32) {
self.health = (self.health + self.health_recover * dt).min(self.health_max);
}
pub fn levelup(&mut self, rng: &mut random::Rng) {
while self.xp >= self.next_level {
let current_level = self.next_level;
self.next_level += (current_level - self.last_level) * 1.2;
self.last_level = current_level;
match rng.gen_range(0, 13) {
PERK_POWER => self.power *= 1.1,
PERK_HEALTH => self.health_max *= 1.1,
PERK_SPEED => self.speed *= 1.1,
PERK_RADIUS => self.damage_radius *= 1.1,
PERK_HEAL => self.health = self.health_max,
PERK_RECOVER => self.health_recover += 0.2,
PERK_ATTRACT => self.diamond_radius *= 1.1,
PERK_XP => self.xp_factor *= 1.1,
PERK_COOLDOWN => self.cooldown_factor *= 0.9,
PERK_AXE => self.weapons[0].amount += 1,
PERK_KNIFE => self.weapons[1].amount += 1,
PERK_STAR => self.weapons[2].amount += 1,
PERK_WIND => self.weapons[3].amount += 1,
_ => unreachable!(),
}
}
}
}
pub struct Game {
pub player: Player,
pub diamonds: Vec,
pub enemies: Vec,
pub i_enemy: usize,
rng: random::Rng,
}
impl Game {
pub fn new() -> Self {
return Self {
enemies: vec![],
diamonds: vec![],
i_enemy: 0,
player: Player::new(),
rng: random::Rng::new(),
};
}
fn move_player(&mut self, dt: f32) {
match self.player.dir {
Some(Dir::Up) => self.player.p.y -= self.player.speed * dt,
Some(Dir::Right) => self.player.p.x += self.player.speed * dt,
Some(Dir::Down) => self.player.p.y += self.player.speed * dt,
Some(Dir::Left) => self.player.p.x -= self.player.speed * dt,
None => {}
};
}
fn move_enemies(&mut self, dt: f32) {
for i in 0..self.enemies.len() {
let enemy = &self.enemies[i];
let dxp = self.player.p.x - enemy.p.x;
let dyp = self.player.p.y - enemy.p.y;
let dp = (dxp * dxp + dyp * dyp).sqrt();
let mut dx = dxp / dp;
let mut dy = dyp / dp;
for j in 0..self.enemies.len() {
if i != j {
let other = &self.enemies[j];
let dxm = other.p.x - enemy.p.x;
let dym = other.p.y - enemy.p.y;
let dm = (dxm * dxm + dym * dym).sqrt();
if dm < enemy.t.size + other.t.size {
dx -= dxm / dm;
dy -= dym / dm;
}
}
}
let d = (dx * dx + dy * dy).sqrt();
dx /= d;
dy /= d;
let enemy = &mut self.enemies[i];
enemy.p.x += dx * enemy.t.speed * dt;
enemy.p.y += dy * enemy.t.speed * dt;
}
}
fn move_projectiles(&mut self, dt: f32) {
for weapon in self.player.weapons.iter_mut() {
for projectile in weapon.projectiles.iter_mut() {
(weapon._move)(projectile, &self.player.p, weapon.speed, dt);
}
}
}
fn spawn_enemies(&mut self, dt: f32, width: f32, height: f32) {
let sprite_height = win::iconvert_y(sprites::HEIGHT);
let sprite_width = win::iconvert_x(sprites::WIDTH);
for (t, p) in enemies::get_wave(self.i_enemy) {
if self.enemies.len() < MAX_ENEMIES && self.rng.gen_f32() < dt * p {
let (spawn_x, spawn_y) = match self.rng.gen_range(0, 4) {
0 => (self.rng.gen_f32() * width, -sprite_height),
1 => (width + sprite_width, self.rng.gen_f32() * height),
2 => (self.rng.gen_f32() * width, height + sprite_height),
3 => (-sprite_width, self.rng.gen_f32() * height),
_ => unreachable!(),
};
self.enemies.push(enemies::Enemy {
p: Pos {
x: spawn_x + self.player.p.x - width / 2.0,
y: spawn_y + self.player.p.y - height / 2.0,
},
health: t.health,
t: t,
});
self.i_enemy += 1;
}
}
}
fn despawn_enemies(&mut self, width: f32, height: f32) {
self.enemies = std::mem::take(&mut self.enemies)
.into_iter()
.filter(|e| {
(e.p.y - self.player.p.y).abs() < height && (e.p.x - self.player.p.x).abs() < width
})
.collect();
}
fn spawn_projectiles(&mut self, dt: f32) {
for weapon in self.player.weapons.iter_mut() {
weapon.last += dt;
if weapon.last > weapon.cooldown * self.player.cooldown_factor {
weapon.last -= weapon.cooldown * self.player.cooldown_factor;
for _ in 0..weapon.amount {
weapon.projectiles.push(weapons::Projectile {
p: Pos {
x: self.player.p.x + (self.rng.gen_f32() - 0.5) * weapons::SPAWN_RADIUS,
y: self.player.p.y + (self.rng.gen_f32() - 0.5) * weapons::SPAWN_RADIUS,
},
dir: match &self.player.dir {
Some(dir) => dir.clone(),
None => self.player.face,
},
});
}
}
}
}
fn despawn_projectiles(&mut self, width: f32, height: f32) {
for weapon in self.player.weapons.iter_mut() {
weapon.projectiles = std::mem::take(&mut weapon.projectiles)
.into_iter()
.filter(|proj| {
(proj.p.y - self.player.p.y).abs() < height
&& (proj.p.x - self.player.p.x).abs() < width
})
.collect();
}
}
fn apply_damage(&mut self, dt: f32) {
for enemy in self.enemies.iter_mut() {
let dx = self.player.p.x - enemy.p.x;
let dy = self.player.p.y - enemy.p.y;
let dx2 = dx * dx;
let dy2 = dy * dy;
let size = enemy.t.size + self.player.size;
if dx2 + dy2 * 4.0 < size * size {
self.player.health -= enemy.t.power * dt;
let d = (dx2 + dy2).sqrt();
enemy.p.x -= dx / d * 3.0;
enemy.p.y -= dy / d * 3.0;
}
if dx2 + dy2 < self.player.damage_radius * self.player.damage_radius {
enemy.health -= self.player.power * dt;
}
for weapon in self.player.weapons.iter() {
for projectile in weapon.projectiles.iter() {
let projectile_size = enemy.t.size + weapon.size;
if projectile.p.in_radius(&enemy.p, projectile_size) {
enemy.health -= weapon.damage * self.player.power * dt;
let dx = projectile.p.x - enemy.p.x;
let dy = projectile.p.y - enemy.p.y;
let d = (dx * dx + dy * dy).sqrt();
enemy.p.x -= dx / d * 3.0;
enemy.p.y -= dy / d * 3.0;
}
}
}
}
self.enemies = std::mem::take(&mut self.enemies)
.into_iter()
.filter(|enemy| {
if enemy.health <= 0.0 {
self.diamonds.push(Pos {
x: enemy.p.x,
y: enemy.p.y,
});
return false;
} else {
return true;
}
})
.collect();
}
fn pick_diamonds(&mut self) {
self.diamonds = std::mem::take(&mut self.diamonds)
.into_iter()
.filter(|diamond| {
if self.player.p.in_radius(&diamond, self.player.diamond_radius) {
self.player.xp += self.player.xp_factor;
return false;
} else {
return true;
}
})
.collect();
}
pub fn step(&mut self, dt: f32, width: f32, height: f32) {
self.move_player(dt);
self.move_enemies(dt);
self.move_projectiles(dt);
self.despawn_enemies(width, height);
self.despawn_projectiles(width, height);
self.apply_damage(dt);
self.pick_diamonds();
self.player.recover(dt);
self.player.levelup(&mut self.rng);
self.spawn_enemies(dt, width, height);
self.spawn_projectiles(dt);
}
pub fn render(&mut self, win: &mut win::Window) {
let height = win::iconvert_y(win.height);
let width = win::iconvert_x(win.width);
let dx = width / 2.0 - self.player.p.x;
let dy = height / 2.0 - self.player.p.y;
win.fill([0x33, 0x88, 0x22]);
win.circle(
width / 2.0,
height / 2.0,
self.player.damage_radius,
[0x00, 0xff, 0x00],
);
for diamond in self.diamonds.iter() {
win.sprite(
diamond.x + dx,
diamond.y + dy,
&sprites::DIAMOND,
Dir::Right,
);
}
let mut player_rendered = false;
self.enemies.sort_unstable_by_key(|e| e.p.y as i32);
for enemy in self.enemies.iter() {
if !player_rendered && enemy.p.y > self.player.p.y {
win.sprite(
width / 2.0,
height / 2.0,
&sprites::PLAYER,
self.player.face,
);
player_rendered = true;
}
win.sprite(
enemy.p.x + dx,
enemy.p.y + dy,
enemy.t.sprite,
if enemy.p.x > self.player.p.x {
Dir::Left
} else {
Dir::Right
},
);
}
if !player_rendered {
win.sprite(
width / 2.0,
height / 2.0,
&sprites::PLAYER,
self.player.face,
);
}
for weapon in self.player.weapons.iter() {
for projectile in weapon.projectiles.iter() {
win.sprite(
projectile.p.x + dx,
projectile.p.y + dy,
weapon.sprite,
projectile.dir,
);
}
}
}
}