use crate::entities::tee::TEE_PROXIMITY;
use crate::entities::{SpawnOrder, SpawnOrderEntity, SpawnableEntity};
use crate::ids::PlayerUid;
use crate::map::{coord, TuneZone};
use crate::state::{closest_point_on_line, BugKind, GameState};
use crate::{Bug, SnapOuter};
use std::ops::Not;
use twgame_core::normalize;
use twgame_core::twsnap::enums::LaserType;
use twgame_core::twsnap::enums::Sound;
use twgame_core::twsnap::time::{Duration, Instant};
use twgame_core::twsnap::{items, vec2_from_bits, Snap, SnapId};
use vek::Vec2;
#[derive(Debug, Clone, Copy)]
pub enum Kind {
Rifle,
Shotgun,
}
impl From<Kind> for LaserType {
fn from(val: Kind) -> Self {
match val {
Kind::Rifle => LaserType::Rifle,
Kind::Shotgun => LaserType::Shotgun,
}
}
}
#[derive(Debug, Clone)]
pub struct Laser {
snap_id: SnapId,
spawn_order: SpawnOrder,
tele_pos: Option<Vec2<f32>>,
from: Vec2<f32>,
pos: Vec2<f32>,
prev_pos: Vec2<f32>,
dir: Vec2<f32>,
energy: f32,
tune_zone: TuneZone,
bounces: i32,
eval_tick: Instant,
owner: PlayerUid,
kind: Kind,
zero_energy_bounce_in_last_tick: bool,
is_marked_for_destroy: bool,
}
impl Laser {
#[allow(clippy::too_many_arguments)]
pub fn new(
now: Instant,
game_state: &mut GameState,
snap_outer: &mut SnapOuter,
pos: Vec2<f32>,
dir: Vec2<f32>,
energy: f32,
tune_zone: TuneZone,
owner: PlayerUid,
kind: Kind,
) -> Self {
let spawn_order = SpawnOrder::new(owner, now, SpawnOrderEntity::Projectile);
let mut laser = Self {
snap_id: snap_outer.id_generator.next_projectile(),
spawn_order,
tele_pos: None,
from: Vec2::new(0.0, 0.0),
pos,
prev_pos: Vec2::new(0.0, 0.0),
dir,
energy,
tune_zone,
bounces: 0,
owner,
eval_tick: Instant::zero(),
kind,
zero_energy_bounce_in_last_tick: false,
is_marked_for_destroy: false,
};
laser.do_bounce(now, game_state);
laser
}
fn get_tee_collision(
&self,
game_state: &GameState,
to: Vec2<f32>,
) -> Option<(PlayerUid, Vec2<f32>)> {
let hit_self = self.bounces != 0 || self.tele_pos.is_some();
let hit_others = game_state
.tee_cores
.get_tee(self.owner)
.map(|tee| !tee.is_solo())
.unwrap_or(false);
if !hit_self && !hit_others {
return None;
}
if !hit_others {
let owner = game_state.tee_cores.get_tee(self.owner)?;
let closest_point = closest_point_on_line((self.pos, to), owner.pos())?;
if closest_point.distance(owner.pos()) < TEE_PROXIMITY {
Some((self.owner, closest_point))
} else {
None
}
} else {
let ignore = hit_self.not().then_some(self.owner);
game_state
.tee_cores
.intersect_tees(&game_state.tee_order, ignore, self.pos, to, 0.0)
}
}
fn hit_tee(&mut self, now: Instant, game_state: &mut GameState, to: Vec2<f32>) -> bool {
if let Some((tee_uid, pos)) = self.get_tee_collision(game_state, to) {
self.from = self.pos;
self.pos = pos;
self.energy = -1.0;
let tee = game_state.tee_cores.get_tee_mut(tee_uid).unwrap();
match self.kind {
Kind::Shotgun => {
let hit_pos = tee.pos();
if self.prev_pos != hit_pos {
tee.impact(
normalize(self.prev_pos - hit_pos)
* game_state.map.tuning(self.tune_zone).shotgun_strength,
false,
);
} else {
game_state.bugs.push(Bug {
time: now,
kind: BugKind::Shotgun,
pos: coord::to_int(pos),
});
tee.impact(Vec2::new(-2147483648.0, -2147483648.0), false);
}
}
Kind::Rifle => {
tee.unfreeze();
}
}
true
} else {
false
}
}
fn do_bounce(&mut self, now: Instant, game_state: &mut GameState) {
self.eval_tick = now;
if self.energy < 0.0 {
self.is_marked_for_destroy = true;
return;
}
if let Some(tele_pos) = self.tele_pos {
self.pos = tele_pos;
}
self.prev_pos = self.pos;
let to = self.pos + self.dir * self.energy;
if let Some((barrier, tele_id)) = game_state.map.intersect_laser(self.pos, to) {
if !self.hit_tee(now, game_state, barrier) {
self.from = self.pos;
let (pos, dir) = game_state.map.reflect_laser(barrier, self.dir * 4.0);
self.pos = pos;
self.dir = normalize(dir);
let distance = self.from.distance(self.pos);
if distance == 0.0 && self.zero_energy_bounce_in_last_tick {
self.energy = -1.0;
} else {
self.energy -=
distance + game_state.map.tuning(self.tune_zone).laser_bounce_cost;
}
self.zero_energy_bounce_in_last_tick = distance == 0.0;
if let Some(tele_id) = tele_id {
if let Some(tele_out) =
game_state
.map
.select_tele_out(now, &mut game_state.prng, tele_id)
{
self.tele_pos = Some(tele_out);
} else {
self.tele_pos = None;
self.bounces += 1;
}
} else {
self.tele_pos = None;
self.bounces += 1;
}
if self.bounces > game_state.map.tuning(self.tune_zone).laser_bounce_num as i32 {
self.energy = -1.0;
}
game_state.events.create_sound(self.pos, Sound::RifleBounce);
}
} else if !self.hit_tee(now, game_state, to) {
self.from = self.pos;
self.pos = to;
self.energy = -1.0;
}
}
}
impl SpawnableEntity for Laser {
fn tick(&mut self, now: Instant, game_state: &mut GameState, _snap_outer: &mut SnapOuter) {
if now.duration_since(self.eval_tick).unwrap()
> Duration::from_secs_f32(
game_state.map.tuning(self.tune_zone).laser_bounce_delay / 1000.0,
)
{
self.do_bounce(now, game_state);
}
}
fn snap(&self, _now: Instant, _game_state: &GameState, snapshot: &mut Snap) {
snapshot.lasers.insert(
self.snap_id,
items::Laser {
to: vec2_from_bits(self.pos),
from: vec2_from_bits(self.from),
start_tick: self.eval_tick,
owner: None, kind: self.kind.into(),
switch_number: 0, flags: Default::default(),
},
)
}
fn is_marked_for_destroy(&self) -> bool {
self.is_marked_for_destroy
}
fn spawn_order(&self) -> SpawnOrder {
self.spawn_order
}
fn player_uid(&self) -> PlayerUid {
self.owner
}
fn on_tee_swap(&mut self, pid1: PlayerUid, pid2: PlayerUid) {
if self.owner == pid1 {
self.owner = pid2;
} else if self.owner == pid2 {
self.owner = pid1;
}
}
}