Split ship movement in several systems: - ShipControl to handle events (mostly state transitions) of single ships - ShipsMovement to handle the basic movement of the ship (this will update the position of the ship) - ShipsMoving to handle ships that are in the 'moving' state - ShipsOrbiting to handle ships that are in the 'orbiting' statemaster
| @@ -100,6 +100,15 @@ impl_op_ex!(/ <T: Numeric> |a: &Angle<T>, b: &T| -> Angle<T> { a._mul(T::one() / | |||
| impl_op_ex!(+= <T: Numeric> |a: &mut Angle<T>, b: &Angle<T>| { *a = a._add(b); }); | |||
| impl_op_ex!(-= <T: Numeric> |a: &mut Angle<T>, b: &Angle<T>| { *a = a._add(&b._neg()); }); | |||
| impl<T> Default for Angle<T> | |||
| where | |||
| T: Numeric, | |||
| { | |||
| fn default() -> Self { | |||
| Self::Deg(T::zero()) | |||
| } | |||
| } | |||
| impl<T, S> PartialEq<Angle<S>> for Angle<T> | |||
| where | |||
| T: Numeric + PartialEq<S>, | |||
| @@ -1,28 +1,31 @@ | |||
| x -> needed | |||
| o -> optional | |||
| ___________________________________________________________ | |||
| WorldPersistence________________________________________ \ | |||
| Planet_______________________________________________ \ \ | |||
| Asteroid__________________________________________ \ \ \ | |||
| MeetingPoint___________________________________ \ \ \ \ | |||
| Obstacle____________________________________ \ \ \ \ \ | |||
| Shape____________________________________ \ \ \ \ \ \ | |||
| MeetingPointOwned_____________________ \ \ \ \ \ \ \ | |||
| Fleet______________________________ \ \ \ \ \ \ \ \ | |||
| Ship____________________________ \ \ \ \ \ \ \ \ \ | |||
| FleetOwned___________________ \ \ \ \ \ \ \ \ \ \ | |||
| Position__________________ \ \ \ \ \ \ \ \ \ \ \ | |||
| PlayerOwned____________ \ \ \ \ \ \ \ \ \ \ \ \ | |||
| Player______________ \ \ \ \ \ \ \ \ \ \ \ \ \ | |||
| \ \ \ \ \ \ \ \ \ \ \ \ \ \ | |||
| +--------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |||
| | Game Objects | | | | | | | | | | | | | | | |||
| +--------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |||
| | Player | x | | | | | | | | | | | | x | | |||
| | Ship | | x | x | x | x | | | | | | | | x | | |||
| | Fleet | | x | | | | x | x | | | | | | x | | |||
| | Asteroid | | o | x | | | | | x | x | x | x | | x | | |||
| | Planet | | o | x | | | | | x | x | x | | x | x | | |||
| | MeetingPoint | | | x | | | | | | | x | | | x | | |||
| +--------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |||
| ____________________________________________________________________ | |||
| WorldPersistence_________________________________________________ \ | |||
| Planet________________________________________________________ \ \ | |||
| Asteroid___________________________________________________ \ \ \ | |||
| MeetingPoint____________________________________________ \ \ \ \ | |||
| Obstacle_____________________________________________ \ \ \ \ \ | |||
| Shape_____________________________________________ \ \ \ \ \ \ | |||
| FleetOrbiting__________________________________ \ \ \ \ \ \ \ | |||
| FleetMoving_________________________________ \ \ \ \ \ \ \ \ | |||
| Fleet____________________________________ \ \ \ \ \ \ \ \ \ | |||
| ShipOrbiting__________________________ \ \ \ \ \ \ \ \ \ \ | |||
| ShipMoving_________________________ \ \ \ \ \ \ \ \ \ \ \ | |||
| Ship____________________________ \ \ \ \ \ \ \ \ \ \ \ \ | |||
| FleetOwned___________________ \ \ \ \ \ \ \ \ \ \ \ \ \ | |||
| Position__________________ \ \ \ \ \ \ \ \ \ \ \ \ \ \ | |||
| PlayerOwned____________ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ | |||
| Player______________ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ | |||
| \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ | |||
| +--------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |||
| | Game Objects | | | | | | | | | | | | | | | | | | |||
| +--------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |||
| | Player | x | | | | | | | | | | | | | | | x | | |||
| | Ship | | x | x | x | x | o | o | | | | | | | | | x | | |||
| | Fleet | | x | | | | | | x | o | o | | | | | | x | | |||
| | Asteroid | | o | x | | | | | | | | x | x | x | x | | x | | |||
| | Planet | | o | x | | | | | | | | x | x | x | | x | x | | |||
| | MeetingPoint | | | x | | | | | | | | | | x | | | x | | |||
| +--------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |||
| @@ -1,8 +1,5 @@ | |||
| use glc::vector::{Angle, Vector2f, Vector4f}; | |||
| use space_crush_common::{ | |||
| components::{MeetingPoint, Position}, | |||
| constants::SHIP_ORBIT_DISTANCE_MAX, | |||
| }; | |||
| use space_crush_common::components::{MeetingPoint, Position}; | |||
| use specs::{prelude::*, ReadStorage, System, World, WriteExpect}; | |||
| use crate::resources::Geometry; | |||
| @@ -40,13 +37,6 @@ impl<'a> System<'a> for MeetingPoints { | |||
| Vector4f::new(0.5, 0.5, 0.5, 0.05), | |||
| &create_circle(position.get(), meeting_point.max()), | |||
| ); | |||
| geometry.render_lines( | |||
| Vector4f::new(0.5, 0.5, 0.5, 0.05), | |||
| &create_circle( | |||
| position.get(), | |||
| SHIP_ORBIT_DISTANCE_MAX * meeting_point.max(), | |||
| ), | |||
| ); | |||
| } | |||
| gl::blend_equation(gl::FUNC_ADD); | |||
| @@ -1,5 +1,7 @@ | |||
| use glc::vector::Vector4f; | |||
| use space_crush_common::components::{Player, PlayerOwned, Position, Ship, ShipObstacle}; | |||
| use space_crush_common::components::{ | |||
| Player, PlayerOwned, Position, Ship, ShipMoving, ShipObstacle, ShipOrbiting, | |||
| }; | |||
| use specs::{prelude::*, ReadStorage, System, World, WriteExpect}; | |||
| use crate::resources::Geometry; | |||
| @@ -14,6 +16,8 @@ pub struct ShipsData<'a> { | |||
| player_owned: ReadStorage<'a, PlayerOwned>, | |||
| players: ReadStorage<'a, Player>, | |||
| ships: ReadStorage<'a, Ship>, | |||
| ships_orbiting: ReadStorage<'a, ShipOrbiting>, | |||
| ships_moving: ReadStorage<'a, ShipMoving>, | |||
| } | |||
| impl<'a> System<'a> for Ships { | |||
| @@ -26,12 +30,22 @@ impl<'a> System<'a> for Ships { | |||
| player_owned, | |||
| players, | |||
| ships, | |||
| ships_orbiting, | |||
| ships_moving, | |||
| } = data; | |||
| gl::enable(gl::BLEND); | |||
| gl::blend_func(gl::SRC_ALPHA, gl::ONE); | |||
| for (position, player_owned, ship) in (&positions, &player_owned, &ships).join() { | |||
| let data = ( | |||
| &positions, | |||
| &player_owned, | |||
| &ships, | |||
| ships_orbiting.maybe(), | |||
| ships_moving.maybe(), | |||
| ); | |||
| for (position, player_owned, ship, ship_orbiting, ship_moving) in data.join() { | |||
| let ship_pos = position.get(); | |||
| let type_ = ship.type_(); | |||
| let player_id = player_owned.owner(); | |||
| @@ -42,22 +56,27 @@ impl<'a> System<'a> for Ships { | |||
| Vector4f::new(0.0, 0.0, 1.0, 0.2), | |||
| &[*ship_pos, ship_pos + ship.dir() * ship_data.speed], | |||
| ); | |||
| geometry.render_lines( | |||
| Vector4f::new(1.0, 0.0, 0.0, 0.2), | |||
| &[*ship_pos, ship_pos + ship.target_dir() * 100.0], | |||
| ); | |||
| geometry.render_lines( | |||
| Vector4f::new(1.0, 1.0, 1.0, 0.2), | |||
| &[*ship_pos, *ship.target_pos()], | |||
| ); | |||
| if let ShipObstacle::Known(obstacle) = ship.obstacle() { | |||
| let obstacle_pos = positions.get(obstacle).unwrap().get(); | |||
| if let Some(ship_orbiting) = &ship_orbiting { | |||
| geometry.render_lines( | |||
| Vector4f::new(0.0, 1.0, 0.0, 0.2), | |||
| &[*ship_pos, *obstacle_pos], | |||
| Vector4f::new(1.0, 0.0, 0.0, 0.2), | |||
| &[*ship_pos, ship_pos + ship_orbiting.target_dir() * 100.0], | |||
| ); | |||
| geometry.render_lines( | |||
| Vector4f::new(1.0, 1.0, 1.0, 0.2), | |||
| &[*ship_pos, *ship_orbiting.target_pos()], | |||
| ); | |||
| } | |||
| if let Some(ship_moving) = ship_moving { | |||
| if let ShipObstacle::Known(obstacle_id) = ship_moving.obstacle() { | |||
| let obstacle_pos = positions.get(*obstacle_id).unwrap().get(); | |||
| geometry.render_lines( | |||
| Vector4f::new(0.0, 1.0, 0.0, 0.2), | |||
| &[*ship_pos, *obstacle_pos], | |||
| ); | |||
| } | |||
| } | |||
| } | |||
| @@ -94,7 +94,7 @@ fn create_world(world: &mut World, player_id: Entity) { | |||
| let fleet_id = Fleet::builder() | |||
| .player(player_id) | |||
| .meeting_point(planets[1]) | |||
| .orbiting(planets[1]) | |||
| .build(world) | |||
| .unwrap(); | |||
| @@ -84,9 +84,7 @@ impl<'a> System<'a> for FleetMove { | |||
| Some(target) => target, | |||
| None => continue, | |||
| }; | |||
| let player = game_state.player_id(); | |||
| let event = FleetControlEvent::MoveToMeetingPoint { | |||
| player, | |||
| fleet, | |||
| count, | |||
| target, | |||
| @@ -11,7 +11,6 @@ use glc::{ | |||
| use shrev::{EventChannel, ReaderId}; | |||
| use space_crush_common::{ | |||
| components::{Fleet, FleetOrbiting, MeetingPoint, Position, Shape, ShipCount}, | |||
| constants::VECTOR_2F_POS_X, | |||
| misc::{LogResult, WorldHelper as _}, | |||
| resources::Global, | |||
| }; | |||
| @@ -260,10 +259,12 @@ impl FleetSelect { | |||
| self.ring0 = self.shape_size + FLEET_SELECT_OFFSET / self.zoom; | |||
| self.ring1 = self.ring0 + FLEET_SELECT_WIDTH / self.zoom; | |||
| const VECTOR2F_POS_X: Vector2f = Vector2f::new(1.0, 0.0); | |||
| let is_simple = self.select_mode.is_simple(); | |||
| let angle = self | |||
| .marker | |||
| .angle2(&VECTOR_2F_POS_X) | |||
| .angle2(&VECTOR2F_POS_X) | |||
| .normalize() | |||
| .into_deg() | |||
| .into_inner(); | |||
| @@ -1,7 +1,10 @@ | |||
| use specs::{saveload::MarkedBuilder, Builder, Entity, World, WorldExt}; | |||
| use specs::{ | |||
| saveload::MarkedBuilder, world::EntitiesRes, Builder, Entity, LazyUpdate, World, WorldExt, | |||
| WriteStorage, | |||
| }; | |||
| use crate::{ | |||
| components::{Fleet, FleetOrbiting, PlayerOwned}, | |||
| components::{Fleet, FleetMoving, FleetOrbiting, PlayerOwned}, | |||
| misc::{Persistence, WorldPersistence}, | |||
| }; | |||
| @@ -10,28 +13,71 @@ use super::Error; | |||
| #[derive(Default, Clone)] | |||
| pub struct FleetBuilder { | |||
| player: Option<Entity>, | |||
| meeting_point: Option<Entity>, | |||
| state: Option<State>, | |||
| } | |||
| #[derive(Clone)] | |||
| enum State { | |||
| Orbiting(Entity), | |||
| Moving(Entity), | |||
| } | |||
| impl FleetBuilder { | |||
| pub fn build(&self, world: &mut World) -> Result<Entity, Error> { | |||
| let player = self.player.ok_or(Error::MissingValue("player"))?; | |||
| let meeting_point = self | |||
| .meeting_point | |||
| .ok_or(Error::MissingValue("meeting_point"))?; | |||
| let state = self.state.as_ref().ok_or(Error::MissingValue("state"))?; | |||
| let player_owned = PlayerOwned::new(player); | |||
| let fleet_orbiting = FleetOrbiting::new(meeting_point); | |||
| let fleet = Fleet::new(); | |||
| let entity = world | |||
| let builder = world | |||
| .create_entity() | |||
| .marked::<<WorldPersistence as Persistence>::Marker>() | |||
| .with(player_owned) | |||
| .with(fleet_orbiting) | |||
| .with(fleet) | |||
| .with(fleet); | |||
| let builder = match state { | |||
| State::Moving(target) => builder.with(FleetMoving::new(*target)), | |||
| State::Orbiting(meeting_point) => builder.with(FleetOrbiting::new(*meeting_point)), | |||
| }; | |||
| let entity = builder.build(); | |||
| Ok(entity) | |||
| } | |||
| pub fn lazy( | |||
| &self, | |||
| lazy: &LazyUpdate, | |||
| entities: &EntitiesRes, | |||
| players_owned: &mut WriteStorage<'_, PlayerOwned>, | |||
| fleets: &mut WriteStorage<'_, Fleet>, | |||
| fleets_moving: &mut WriteStorage<'_, FleetMoving>, | |||
| fleets_orbiting: &mut WriteStorage<'_, FleetOrbiting>, | |||
| ) -> Result<Entity, Error> { | |||
| let player = self.player.ok_or(Error::MissingValue("player"))?; | |||
| let state = self.state.as_ref().ok_or(Error::MissingValue("state"))?; | |||
| let player_owned = PlayerOwned::new(player); | |||
| let fleet = Fleet::new(); | |||
| let entity = lazy | |||
| .create_entity(entities) | |||
| .marked::<<WorldPersistence as Persistence>::Marker>() | |||
| .build(); | |||
| players_owned.insert(entity, player_owned)?; | |||
| fleets.insert(entity, fleet)?; | |||
| match state { | |||
| State::Moving(target) => { | |||
| fleets_moving.insert(entity, FleetMoving::new(*target))?; | |||
| } | |||
| State::Orbiting(meeting_point) => { | |||
| fleets_orbiting.insert(entity, FleetOrbiting::new(*meeting_point))?; | |||
| } | |||
| }; | |||
| Ok(entity) | |||
| } | |||
| @@ -41,8 +87,14 @@ impl FleetBuilder { | |||
| self | |||
| } | |||
| pub fn meeting_point(mut self, meeting_point: Entity) -> Self { | |||
| self.meeting_point = Some(meeting_point); | |||
| pub fn orbiting(mut self, meeting_point: Entity) -> Self { | |||
| self.state = Some(State::Orbiting(meeting_point)); | |||
| self | |||
| } | |||
| pub fn moving(mut self, target: Entity) -> Self { | |||
| self.state = Some(State::Moving(target)); | |||
| self | |||
| } | |||
| @@ -0,0 +1,65 @@ | |||
| use specs::{ | |||
| saveload::MarkedBuilder, world::EntitiesRes, Builder, Entity, LazyUpdate, World, WorldExt, | |||
| WriteStorage, | |||
| }; | |||
| use glc::vector::Vector2f; | |||
| use crate::{ | |||
| components::{MeetingPoint, Position}, | |||
| misc::{Persistence, WorldPersistence}, | |||
| }; | |||
| use super::Error; | |||
| #[derive(Default, Clone)] | |||
| pub struct MeetingPointBuilder { | |||
| position: Option<Vector2f>, | |||
| } | |||
| impl MeetingPointBuilder { | |||
| pub fn build(&self, world: &mut World) -> Result<Entity, Error> { | |||
| let position = self.position.ok_or(Error::MissingValue("position"))?; | |||
| let position = Position::new(position); | |||
| let meeting_point = MeetingPoint::new(0.0, 10.0); | |||
| let entity = world | |||
| .create_entity() | |||
| .marked::<<WorldPersistence as Persistence>::Marker>() | |||
| .with(position) | |||
| .with(meeting_point) | |||
| .build(); | |||
| Ok(entity) | |||
| } | |||
| pub fn lazy( | |||
| &self, | |||
| lazy: &LazyUpdate, | |||
| entities: &EntitiesRes, | |||
| positions: &mut WriteStorage<'_, Position>, | |||
| meeting_points: &mut WriteStorage<'_, MeetingPoint>, | |||
| ) -> Result<Entity, Error> { | |||
| let position = self.position.ok_or(Error::MissingValue("position"))?; | |||
| let position = Position::new(position); | |||
| let meeting_point = MeetingPoint::new(0.0, 10.0); | |||
| let entity = lazy | |||
| .create_entity(entities) | |||
| .marked::<<WorldPersistence as Persistence>::Marker>() | |||
| .build(); | |||
| positions.insert(entity, position)?; | |||
| meeting_points.insert(entity, meeting_point)?; | |||
| Ok(entity) | |||
| } | |||
| pub fn position<V: Into<Vector2f>>(mut self, value: V) -> Self { | |||
| self.position = Some(value.into()); | |||
| self | |||
| } | |||
| } | |||
| @@ -1,11 +1,13 @@ | |||
| mod asteroid; | |||
| mod fleet; | |||
| mod meeting_point; | |||
| mod planet; | |||
| mod player; | |||
| mod ship; | |||
| pub use asteroid::AsteroidBuilder; | |||
| pub use fleet::FleetBuilder; | |||
| pub use meeting_point::MeetingPointBuilder; | |||
| pub use planet::PlanetBuilder; | |||
| pub use player::PlayerBuilder; | |||
| pub use ship::ShipBuilder; | |||
| @@ -3,7 +3,7 @@ use rand::random; | |||
| use specs::{saveload::MarkedBuilder, Builder, Entity, World, WorldExt}; | |||
| use crate::{ | |||
| components::{FleetOwned, PlayerOwned, Position, Ship, ShipType}, | |||
| components::{FleetOwned, PlayerOwned, Position, Ship, ShipOrbiting, ShipType}, | |||
| misc::{Persistence, WorldPersistence}, | |||
| }; | |||
| @@ -30,6 +30,7 @@ impl ShipBuilder { | |||
| let fleet_owned = FleetOwned::new(fleet); | |||
| let position = Position::new(position); | |||
| let ship = Ship::new(type_, direction); | |||
| let ship_orbiting = ShipOrbiting::new(); | |||
| let entity = world | |||
| .create_entity() | |||
| @@ -38,6 +39,7 @@ impl ShipBuilder { | |||
| .with(fleet_owned) | |||
| .with(position) | |||
| .with(ship) | |||
| .with(ship_orbiting) | |||
| .build(); | |||
| Ok(entity) | |||
| @@ -30,6 +30,11 @@ pub struct FleetOrbiting { | |||
| meeting_point: Entity, | |||
| } | |||
| #[derive(Debug, Clone)] | |||
| pub struct FleetMoving { | |||
| target: Entity, | |||
| } | |||
| #[derive(Serialize, Deserialize)] | |||
| pub struct FleetOwnedData<M> { | |||
| pub owner: M, | |||
| @@ -40,6 +45,11 @@ pub struct FleetOrbitingData<M> { | |||
| pub meeting_point: M, | |||
| } | |||
| #[derive(Serialize, Deserialize)] | |||
| pub struct FleetMovingData<M> { | |||
| pub target: M, | |||
| } | |||
| /* Fleet */ | |||
| impl Fleet { | |||
| @@ -57,6 +67,11 @@ impl Fleet { | |||
| pub fn count(&self) -> &ShipCount { | |||
| &self.count | |||
| } | |||
| #[inline] | |||
| pub fn is_empty(&self) -> bool { | |||
| self.count.total() == 0 | |||
| } | |||
| } | |||
| impl Fleet { | |||
| @@ -177,3 +192,47 @@ where | |||
| Ok(FleetOrbiting { meeting_point }) | |||
| } | |||
| } | |||
| /* FleetMoving */ | |||
| impl FleetMoving { | |||
| pub fn target(&self) -> Entity { | |||
| self.target | |||
| } | |||
| } | |||
| impl FleetMoving { | |||
| pub(crate) fn new(target: Entity) -> Self { | |||
| Self { target } | |||
| } | |||
| } | |||
| impl Component for FleetMoving { | |||
| type Storage = FlaggedStorage<Self, HashMapStorage<Self>>; | |||
| } | |||
| impl<M> ConvertSaveload<M> for FleetMoving | |||
| where | |||
| for<'de> M: Marker + Serialize + Deserialize<'de>, | |||
| { | |||
| type Data = FleetMovingData<M>; | |||
| type Error = NoError; | |||
| fn convert_into<F>(&self, mut ids: F) -> Result<Self::Data, Self::Error> | |||
| where | |||
| F: FnMut(Entity) -> Option<M>, | |||
| { | |||
| let target = ids(self.target).unwrap(); | |||
| Ok(FleetMovingData { target }) | |||
| } | |||
| fn convert_from<F>(data: Self::Data, mut ids: F) -> Result<Self, Self::Error> | |||
| where | |||
| F: FnMut(M) -> Option<Entity>, | |||
| { | |||
| let target = ids(data.target).unwrap(); | |||
| Ok(FleetMoving { target }) | |||
| } | |||
| } | |||
| @@ -7,7 +7,7 @@ use specs::{ | |||
| Component, Entity, HashMapStorage, | |||
| }; | |||
| use crate::misc::FlaggedStorage; | |||
| use crate::{builder::MeetingPointBuilder, misc::FlaggedStorage}; | |||
| /// Point in the universe fleets can meet | |||
| #[derive(Clone, Debug, Default)] | |||
| @@ -40,6 +40,11 @@ type Fleets = SmallVec<[Option<Entity>; 8]>; | |||
| /* MeetingPoint */ | |||
| impl MeetingPoint { | |||
| #[inline] | |||
| pub fn builder() -> MeetingPointBuilder { | |||
| MeetingPointBuilder::default() | |||
| } | |||
| #[inline] | |||
| pub fn min(&self) -> f32 { | |||
| self.min | |||
| @@ -9,11 +9,13 @@ mod shape; | |||
| mod ship; | |||
| pub use asteroid::{Asteroid, Type as AsteroidType}; | |||
| pub use fleet::{Fleet, FleetOrbiting, FleetOwned}; | |||
| pub use fleet::{Fleet, FleetMoving, FleetOrbiting, FleetOwned}; | |||
| pub use meeting_point::MeetingPoint; | |||
| pub use obstacle::Obstacle; | |||
| pub use planet::Planet; | |||
| pub use player::{Player, PlayerOwned, Race}; | |||
| pub use position::Position; | |||
| pub use shape::Shape; | |||
| pub use ship::{Ship, ShipCount, ShipData, ShipObstacle, ShipType, ShipsData}; | |||
| pub use ship::{ | |||
| Ship, ShipCount, ShipData, ShipMoving, ShipObstacle, ShipOrbiting, ShipType, ShipsData, | |||
| }; | |||
| @@ -61,6 +61,11 @@ impl Player { | |||
| pub fn ship_data(&self, type_: ShipType) -> &ShipData { | |||
| &self.ships_data[type_] | |||
| } | |||
| #[inline] | |||
| pub fn ships_data(&self) -> &ShipsData { | |||
| &self.ships_data | |||
| } | |||
| } | |||
| impl Player { | |||
| @@ -1,7 +1,7 @@ | |||
| use std::cmp::min; | |||
| use std::ops::{Index, IndexMut, Mul}; | |||
| use glc::vector::Vector2f; | |||
| use glc::vector::{Angle, Vector2f}; | |||
| use serde::{Deserialize, Serialize}; | |||
| use specs::{Component, Entity, VecStorage}; | |||
| @@ -11,13 +11,18 @@ use crate::{builder::ShipBuilder, misc::FlaggedStorage}; | |||
| pub struct Ship { | |||
| type_: ShipType, | |||
| dir: Vector2f, | |||
| } | |||
| #[derive(Clone, Debug, Serialize, Deserialize)] | |||
| pub struct ShipMoving { | |||
| #[serde(skip)] | |||
| obstacle: ShipObstacle, | |||
| } | |||
| #[derive(Clone, Debug, Serialize, Deserialize)] | |||
| pub struct ShipOrbiting { | |||
| #[serde(skip)] | |||
| target_pos: Vector2f, | |||
| #[serde(skip)] | |||
| target_dir: Vector2f, | |||
| } | |||
| @@ -46,6 +51,7 @@ pub struct ShipsData { | |||
| #[derive(Clone, Default, Debug)] | |||
| pub struct ShipData { | |||
| pub speed: f32, | |||
| pub agility: Angle<f32>, | |||
| } | |||
| #[derive(Copy, Clone, Debug, Serialize, Deserialize)] | |||
| @@ -72,30 +78,65 @@ impl Ship { | |||
| pub fn dir(&self) -> &Vector2f { | |||
| &self.dir | |||
| } | |||
| } | |||
| impl Ship { | |||
| #[inline] | |||
| pub fn target_pos(&self) -> &Vector2f { | |||
| &self.target_pos | |||
| pub(crate) fn new(type_: ShipType, dir: Vector2f) -> Self { | |||
| Self { type_, dir } | |||
| } | |||
| #[inline] | |||
| pub fn target_dir(&self) -> &Vector2f { | |||
| &self.target_dir | |||
| pub(crate) fn dir_mut(&mut self) -> &mut Vector2f { | |||
| &mut self.dir | |||
| } | |||
| } | |||
| impl Component for Ship { | |||
| type Storage = FlaggedStorage<Self, VecStorage<Self>>; | |||
| } | |||
| /* ShipMoving */ | |||
| impl ShipMoving { | |||
| pub fn obstacle(&self) -> &ShipObstacle { | |||
| &self.obstacle | |||
| } | |||
| } | |||
| impl ShipMoving { | |||
| #[inline] | |||
| pub fn obstacle(&self) -> ShipObstacle { | |||
| self.obstacle | |||
| pub(crate) fn new() -> Self { | |||
| Self { | |||
| obstacle: ShipObstacle::Search, | |||
| } | |||
| } | |||
| pub(crate) fn obstacle_mut(&mut self) -> &mut ShipObstacle { | |||
| &mut self.obstacle | |||
| } | |||
| } | |||
| impl Ship { | |||
| impl Component for ShipMoving { | |||
| type Storage = VecStorage<Self>; | |||
| } | |||
| /* ShipOrbiting */ | |||
| impl ShipOrbiting { | |||
| pub fn target_pos(&self) -> &Vector2f { | |||
| &self.target_pos | |||
| } | |||
| pub fn target_dir(&self) -> &Vector2f { | |||
| &self.target_dir | |||
| } | |||
| } | |||
| impl ShipOrbiting { | |||
| #[inline] | |||
| pub(crate) fn new(type_: ShipType, dir: Vector2f) -> Self { | |||
| pub(crate) fn new() -> Self { | |||
| Self { | |||
| type_, | |||
| dir, | |||
| obstacle: Default::default(), | |||
| target_pos: Default::default(), | |||
| target_dir: Default::default(), | |||
| } | |||
| @@ -106,20 +147,10 @@ impl Ship { | |||
| self.target_pos = pos; | |||
| self.target_dir = dir; | |||
| } | |||
| #[inline] | |||
| pub(crate) fn dir_mut(&mut self) -> &mut Vector2f { | |||
| &mut self.dir | |||
| } | |||
| #[inline] | |||
| pub(crate) fn obstacle_mut(&mut self) -> &mut ShipObstacle { | |||
| &mut self.obstacle | |||
| } | |||
| } | |||
| impl Component for Ship { | |||
| type Storage = FlaggedStorage<Self, VecStorage<Self>>; | |||
| impl Component for ShipOrbiting { | |||
| type Storage = VecStorage<Self>; | |||
| } | |||
| /* Obstacle */ | |||
| @@ -1,35 +1,61 @@ | |||
| use glc::{matrix::Angle, vector::Vector2f}; | |||
| use glc::matrix::Angle; | |||
| use crate::components::{ShipData, ShipsData}; | |||
| /// Distance to orbit before ship is handled as "in orbit" in % | |||
| pub const SHIP_ORBIT_DISTANCE_MAX: f32 = 1.10; | |||
| /* Constants for ships in orbit */ | |||
| /// Minimum angle between old and new target position in orbit | |||
| pub const SHIP_ORBIT_ANGLE_DELTA_MIN: Angle<f32> = Angle::Deg(5000.0); | |||
| /// Random angle between old and new target position in orbit | |||
| pub const SHIP_ORBIT_ANGLE_DELTA_RND: Angle<f32> = Angle::Deg(5000.0); | |||
| pub const SHIP_ORBIT_DISTANCE_MAX: f32 = 1.10; // % | |||
| /// Agility of ships inside orbit | |||
| pub const SHIP_ORBIT_AGILITY: Angle<f32> = Angle::Deg(90.0); | |||
| pub const SHIP_DATA_IN_ORBIT: ShipData = ShipData { | |||
| speed: 100.0, | |||
| agility: Angle::Deg(60.0), | |||
| }; | |||
| pub const VECTOR_2F_POS_X: Vector2f = Vector2f::new(1.0, 0.0); | |||
| /* Constants for different ship types */ | |||
| pub const SHIPS_DATA_FIGHTER: ShipsData = ShipsData { | |||
| fighter: ShipData { speed: 120.0 }, | |||
| bomber: ShipData { speed: 80.0 }, | |||
| transporter: ShipData { speed: 100.0 }, | |||
| fighter: ShipData { | |||
| speed: 120.0, | |||
| agility: Angle::Deg(120.0), | |||
| }, | |||
| bomber: ShipData { | |||
| speed: 80.0, | |||
| agility: Angle::Deg(80.0), | |||
| }, | |||
| transporter: ShipData { | |||
| speed: 100.0, | |||
| agility: Angle::Deg(100.0), | |||
| }, | |||
| }; | |||
| pub const SHIPS_DATA_TRADER: ShipsData = ShipsData { | |||
| fighter: ShipData { speed: 100.0 }, | |||
| bomber: ShipData { speed: 70.0 }, | |||
| transporter: ShipData { speed: 120.0 }, | |||
| fighter: ShipData { | |||
| speed: 100.0, | |||
| agility: Angle::Deg(100.0), | |||
| }, | |||
| bomber: ShipData { | |||
| speed: 70.0, | |||
| agility: Angle::Deg(70.0), | |||
| }, | |||
| transporter: ShipData { | |||
| speed: 120.0, | |||
| agility: Angle::Deg(120.0), | |||
| }, | |||
| }; | |||
| pub const SHIPS_DATA_RESEARCHER: ShipsData = ShipsData { | |||
| fighter: ShipData { speed: 90.0 }, | |||
| bomber: ShipData { speed: 60.0 }, | |||
| transporter: ShipData { speed: 100.0 }, | |||
| fighter: ShipData { | |||
| speed: 90.0, | |||
| agility: Angle::Deg(90.0), | |||
| }, | |||
| bomber: ShipData { | |||
| speed: 60.0, | |||
| agility: Angle::Deg(60.0), | |||
| }, | |||
| transporter: ShipData { | |||
| speed: 100.0, | |||
| agility: Angle::Deg(100.0), | |||
| }, | |||
| }; | |||
| @@ -3,7 +3,10 @@ use specs::{Dispatcher as Inner, DispatcherBuilder, World, WorldExt}; | |||
| use crate::{ | |||
| components::Player, | |||
| resources::Global, | |||
| systems::{FleetControl, FleetOrbitingUpdate, FleetOwnedUpdate, Process, ShipMovement, Ships}, | |||
| systems::{ | |||
| FleetControl, FleetOrbitingUpdate, FleetOwnedUpdate, Process, ShipControl, ShipsMovement, | |||
| ShipsMoving, ShipsOrbiting, | |||
| }, | |||
| Error, | |||
| }; | |||
| @@ -19,8 +22,10 @@ impl<'a, 'b> Dispatcher<'a, 'b> { | |||
| let mut dispatcher = DispatcherBuilder::new() | |||
| .with(Process::default(), "process", &[]) | |||
| .with(ShipMovement::default(), "ship_movement", &[]) | |||
| .with(Ships::new(world), "ships", &[]) | |||
| .with(ShipControl::new(world)?, "ship_control", &[]) | |||
| .with(ShipsOrbiting::default(), "ships_orbiting", &[]) | |||
| .with(ShipsMoving::default(), "ships_moving", &[]) | |||
| .with(ShipsMovement::default(), "ships_movement", &[]) | |||
| .with(FleetControl::new(world)?, "fleet_control", &[]) | |||
| .with(FleetOwnedUpdate::new(world), "fleet_owned_update", &[]) | |||
| .with(FleetOrbitingUpdate::new(world), "fleet_orbiting", &[]) | |||
| @@ -8,7 +8,7 @@ use specs::{saveload::SimpleMarker, World}; | |||
| use crate::{ | |||
| components::{ | |||
| Asteroid, FleetOrbiting, FleetOwned, MeetingPoint, Obstacle, Planet, Player, PlayerOwned, | |||
| Position, Ship, | |||
| Position, Ship, ShipMoving, ShipOrbiting, | |||
| }, | |||
| Error, | |||
| }; | |||
| @@ -96,6 +96,8 @@ impl Persistence for WorldPersistence { | |||
| FleetOwned, | |||
| FleetOrbiting, | |||
| Ship, | |||
| ShipMoving, | |||
| ShipOrbiting, | |||
| Obstacle, | |||
| MeetingPoint, | |||
| Planet, | |||
| @@ -1,9 +1,4 @@ | |||
| // TODO | |||
| #![allow(dead_code)] | |||
| #![allow(unused_mut)] | |||
| #![allow(unused_imports)] | |||
| #![allow(unused_variables)] | |||
| use glc::vector::Vector2f; | |||
| use shrev::{EventChannel, ReaderId}; | |||
| use specs::{ | |||
| prelude::*, Entities, Entity, LazyUpdate, ReadExpect, ReadStorage, System, World, WriteStorage, | |||
| @@ -11,9 +6,11 @@ use specs::{ | |||
| use crate::{ | |||
| components::{ | |||
| Fleet, FleetOrbiting, FleetOwned, MeetingPoint, Player, PlayerOwned, Ship, ShipCount, | |||
| Fleet, FleetMoving, FleetOrbiting, MeetingPoint, PlayerOwned, Position, Ship, ShipCount, | |||
| ShipType, | |||
| }, | |||
| misc::WorldHelper, | |||
| misc::{LogResult, WorldHelper}, | |||
| systems::ShipControlEvent, | |||
| Error, | |||
| }; | |||
| @@ -24,24 +21,50 @@ pub struct FleetControl { | |||
| #[derive(Debug)] | |||
| pub enum FleetControlEvent { | |||
| MoveToMeetingPoint { | |||
| player: Entity, | |||
| fleet: Entity, | |||
| count: ShipCount, | |||
| target: Entity, | |||
| }, | |||
| MoveToPosition { | |||
| fleet: Entity, | |||
| count: ShipCount, | |||
| target: Vector2f, | |||
| }, | |||
| } | |||
| #[derive(SystemData)] | |||
| pub struct FleetControlData<'a> { | |||
| fleet_control_events: ReadExpect<'a, EventChannel<FleetControlEvent>>, | |||
| lazy: Read<'a, LazyUpdate>, | |||
| entities: Entities<'a>, | |||
| fleet_control_events: ReadExpect<'a, EventChannel<FleetControlEvent>>, | |||
| player_owned: WriteStorage<'a, PlayerOwned>, | |||
| fleet_orbiting: WriteStorage<'a, FleetOrbiting>, | |||
| fleet_owned: WriteStorage<'a, FleetOwned>, | |||
| ship_control: WriteExpect<'a, EventChannel<ShipControlEvent>>, | |||
| fleets: WriteStorage<'a, Fleet>, | |||
| fleets_moving: WriteStorage<'a, FleetMoving>, | |||
| fleets_orbiting: WriteStorage<'a, FleetOrbiting>, | |||
| positions: WriteStorage<'a, Position>, | |||
| meeting_points: WriteStorage<'a, MeetingPoint>, | |||
| ships: ReadStorage<'a, Ship>, | |||
| } | |||
| struct Processor<'a> { | |||
| lazy: Read<'a, LazyUpdate>, | |||
| entities: Entities<'a>, | |||
| player_owned: WriteStorage<'a, PlayerOwned>, | |||
| ship_control: WriteExpect<'a, EventChannel<ShipControlEvent>>, | |||
| fleets: WriteStorage<'a, Fleet>, | |||
| players: ReadStorage<'a, Player>, | |||
| fleets_moving: WriteStorage<'a, FleetMoving>, | |||
| fleets_orbiting: WriteStorage<'a, FleetOrbiting>, | |||
| positions: WriteStorage<'a, Position>, | |||
| meeting_points: WriteStorage<'a, MeetingPoint>, | |||
| ships: ReadStorage<'a, Ship>, | |||
| } | |||
| @@ -62,77 +85,114 @@ impl<'a> System<'a> for FleetControl { | |||
| fn run(&mut self, data: Self::SystemData) { | |||
| let FleetControlData { | |||
| fleet_control_events, | |||
| lazy, | |||
| entities, | |||
| fleet_control_events, | |||
| mut player_owned, | |||
| mut fleet_orbiting, | |||
| mut fleet_owned, | |||
| mut meeting_points, | |||
| mut fleets, | |||
| players, | |||
| player_owned, | |||
| ship_control, | |||
| fleets, | |||
| fleets_moving, | |||
| fleets_orbiting, | |||
| positions, | |||
| meeting_points, | |||
| ships, | |||
| } = data; | |||
| /* TODO | |||
| let mut processor = Processor { | |||
| lazy, | |||
| entities, | |||
| player_owned, | |||
| ship_control, | |||
| fleets, | |||
| fleets_moving, | |||
| fleets_orbiting, | |||
| positions, | |||
| meeting_points, | |||
| ships, | |||
| }; | |||
| let events = fleet_control_events.read(&mut self.fleet_control_event_id); | |||
| for event in events { | |||
| match event { | |||
| FleetControlEvent::Move(args) if args.count.is_all() => { | |||
| let orbit_owned = continue_if_none!(fleet_orbiting.get_mut(args.fleet)); | |||
| orbit_owned.set_owner(args.target); | |||
| FleetControlEvent::MoveToPosition { | |||
| fleet, | |||
| count, | |||
| target, | |||
| } => { | |||
| processor.move_to_position(*fleet, *count, *target); | |||
| } | |||
| FleetControlEvent::MoveToMeetingPoint { | |||
| fleet, | |||
| count, | |||
| target, | |||
| } => { | |||
| processor.move_to_meeting_point(*fleet, *count, *target); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| impl Processor<'_> { | |||
| fn move_to_position(&mut self, fleet: Entity, count: ShipCount, target: Vector2f) { | |||
| let target = self.create_meeting_point(target); | |||
| self.move_to_meeting_point(fleet, count, target); | |||
| } | |||
| fn move_to_meeting_point(&mut self, fleet: Entity, mut count: ShipCount, target: Entity) { | |||
| let player_owned = self.player_owned.get(fleet).unwrap(); | |||
| let player_id = player_owned.owner(); | |||
| let target_fleet = self.create_fleet(player_id, target); | |||
| let source_fleet = self.fleets.get(fleet).unwrap(); | |||
| let data = (&self.entities, source_fleet.owned(), &self.ships); | |||
| for (id, _, ship) in data.join() { | |||
| match ship.type_() { | |||
| ShipType::Fighter if count.fighter > 0 => { | |||
| count.fighter -= 1; | |||
| } | |||
| ShipType::Bomber if count.bomber > 0 => { | |||
| count.bomber -= 1; | |||
| } | |||
| FleetControlEvent::Move(args) => { | |||
| let target_orbit = continue_if_none!(meeting_points.get_mut(args.target)); | |||
| let player = continue_if_none!(players.get(args.player)); | |||
| let mut count = args.count; | |||
| let target_fleet = match target_orbit.fleet_mut(player.index()) { | |||
| Some(fleet) => *fleet, | |||
| f @ None => { | |||
| let fleet = lazy | |||
| .create_entity(&entities) | |||
| .marked::<<WorldPersistence as Persistence>::Marker>() | |||
| .build(); | |||
| player_owned | |||
| .insert(fleet, PlayerOwned::new(args.player)) | |||
| .error("Unable to insert component: PlayerOwned"); | |||
| fleet_orbiting | |||
| .insert(fleet, MeetingPointOwned::new(args.target)) | |||
| .error("Unable to insert component: MeetingPointOwned"); | |||
| fleets | |||
| .insert(fleet, Fleet::default()) | |||
| .error("Unable to insert component: Fleet"); | |||
| *f = Some(fleet); | |||
| fleet | |||
| } | |||
| }; | |||
| let source_fleet = continue_if_none!(fleets.get(args.fleet)); | |||
| let data = (&mut fleet_owned, &ships, source_fleet.owned()); | |||
| for (fleet_owned, ship, _) in data.join() { | |||
| match ship.type_() { | |||
| ShipType::Fighter if count.fighter > 0 => { | |||
| count.fighter -= 1; | |||
| fleet_owned.set_owner(target_fleet); | |||
| } | |||
| ShipType::Bomber if count.bomber > 0 => { | |||
| count.bomber -= 1; | |||
| fleet_owned.set_owner(target_fleet); | |||
| } | |||
| ShipType::Transporter if count.transporter > 0 => { | |||
| count.transporter -= 1; | |||
| fleet_owned.set_owner(target_fleet); | |||
| } | |||
| _ => (), | |||
| } | |||
| } | |||
| ShipType::Transporter if count.transporter > 0 => { | |||
| count.transporter -= 1; | |||
| } | |||
| _ => continue, | |||
| } | |||
| let event = ShipControlEvent::SetMoving { | |||
| ship: id, | |||
| fleet: target_fleet, | |||
| }; | |||
| self.ship_control.single_write(event); | |||
| } | |||
| */ | |||
| } | |||
| fn create_meeting_point(&mut self, pos: Vector2f) -> Entity { | |||
| MeetingPoint::builder() | |||
| .position(pos) | |||
| .lazy( | |||
| &self.lazy, | |||
| &self.entities, | |||
| &mut self.positions, | |||
| &mut self.meeting_points, | |||
| ) | |||
| .panic("Unable to create new entity: MeetingPoint") | |||
| } | |||
| fn create_fleet(&mut self, player: Entity, target: Entity) -> Entity { | |||
| Fleet::builder() | |||
| .player(player) | |||
| .moving(target) | |||
| .lazy( | |||
| &self.lazy, | |||
| &self.entities, | |||
| &mut self.player_owned, | |||
| &mut self.fleets, | |||
| &mut self.fleets_moving, | |||
| &mut self.fleets_orbiting, | |||
| ) | |||
| .panic("Unable to create new entitiy: Fleet") | |||
| } | |||
| } | |||
| @@ -4,8 +4,8 @@ use shrev::ReaderId; | |||
| use specs::{prelude::*, world::Index, Entities, ReadStorage, System, World, WriteStorage}; | |||
| use crate::{ | |||
| components::{Fleet, FleetOwned, Ship}, | |||
| misc::{ComponentEvent, StorageHelper, StorageHelperMut}, | |||
| components::{Fleet, FleetMoving, FleetOwned, Ship}, | |||
| misc::{ComponentEvent, LogResult, StorageHelper, StorageHelperMut}, | |||
| }; | |||
| pub struct FleetOwnedUpdate { | |||
| @@ -40,6 +40,7 @@ pub struct FleetOwnedUpdateData<'a> { | |||
| entities: Entities<'a>, | |||
| ships: ReadStorage<'a, Ship>, | |||
| fleets: WriteStorage<'a, Fleet>, | |||
| fleets_moving: ReadStorage<'a, FleetMoving>, | |||
| fleet_owned: ReadStorage<'a, FleetOwned>, | |||
| } | |||
| @@ -51,6 +52,7 @@ impl<'a> System<'a> for FleetOwnedUpdate { | |||
| entities, | |||
| ships, | |||
| mut fleets, | |||
| fleets_moving, | |||
| fleet_owned, | |||
| } = data; | |||
| @@ -103,6 +105,11 @@ impl<'a> System<'a> for FleetOwnedUpdate { | |||
| if old_match && !new_match { | |||
| fleet.remove_ship(ship_id, ship); | |||
| if fleet.is_empty() && fleets_moving.contains(fleet_id) { | |||
| entities | |||
| .delete(fleet_id) | |||
| .panic("Unable to delete entity for moving fleet!"); | |||
| } | |||
| } else if !old_match && new_match { | |||
| fleet.add_ship(ship_id, ship); | |||
| } | |||
| @@ -2,12 +2,16 @@ mod fleet_control; | |||
| mod fleet_orbiting_update; | |||
| mod fleet_owned_update; | |||
| mod process; | |||
| mod ship_movement; | |||
| mod ships; | |||
| mod ship_control; | |||
| mod ships_movement; | |||
| mod ships_moving; | |||
| mod ships_orbiting; | |||
| pub use fleet_control::{FleetControl, FleetControlEvent}; | |||
| pub use fleet_orbiting_update::FleetOrbitingUpdate; | |||
| pub use fleet_owned_update::FleetOwnedUpdate; | |||
| pub use process::Process; | |||
| pub use ship_movement::ShipMovement; | |||
| pub use ships::Ships; | |||
| pub use ship_control::{ShipControl, ShipControlEvent}; | |||
| pub use ships_movement::ShipsMovement; | |||
| pub use ships_moving::ShipsMoving; | |||
| pub use ships_orbiting::ShipsOrbiting; | |||
| @@ -0,0 +1,166 @@ | |||
| use shrev::{EventChannel, ReaderId}; | |||
| use specs::{ | |||
| prelude::*, Entities, Entity, LazyUpdate, ReadExpect, ReadStorage, System, World, WriteStorage, | |||
| }; | |||
| use crate::{ | |||
| components::{ | |||
| Fleet, FleetMoving, FleetOrbiting, FleetOwned, MeetingPoint, Player, PlayerOwned, | |||
| ShipMoving, ShipOrbiting, | |||
| }, | |||
| misc::{LogResult, WorldHelper}, | |||
| Error, | |||
| }; | |||
| pub struct ShipControl { | |||
| ship_control_event_id: ReaderId<ShipControlEvent>, | |||
| } | |||
| #[derive(Debug)] | |||
| pub enum ShipControlEvent { | |||
| SetMoving { ship: Entity, fleet: Entity }, | |||
| SetOrbiting { ship: Entity, target: Entity }, | |||
| } | |||
| #[derive(SystemData)] | |||
| pub struct ShipControlData<'a> { | |||
| ship_control_events: ReadExpect<'a, EventChannel<ShipControlEvent>>, | |||
| lazy: Read<'a, LazyUpdate>, | |||
| entities: Entities<'a>, | |||
| players: ReadStorage<'a, Player>, | |||
| player_owned: WriteStorage<'a, PlayerOwned>, | |||
| fleets: WriteStorage<'a, Fleet>, | |||
| fleets_moving: WriteStorage<'a, FleetMoving>, | |||
| fleets_orbiting: WriteStorage<'a, FleetOrbiting>, | |||
| meeting_points: WriteStorage<'a, MeetingPoint>, | |||
| ships_orbiting: WriteStorage<'a, ShipOrbiting>, | |||
| ships_moving: WriteStorage<'a, ShipMoving>, | |||
| fleet_owned: WriteStorage<'a, FleetOwned>, | |||
| } | |||
| struct Processor<'a> { | |||
| lazy: Read<'a, LazyUpdate>, | |||
| entities: Entities<'a>, | |||
| players: ReadStorage<'a, Player>, | |||
| player_owned: WriteStorage<'a, PlayerOwned>, | |||
| fleets: WriteStorage<'a, Fleet>, | |||
| fleets_moving: WriteStorage<'a, FleetMoving>, | |||
| fleets_orbiting: WriteStorage<'a, FleetOrbiting>, | |||
| meeting_points: WriteStorage<'a, MeetingPoint>, | |||
| ships_orbiting: WriteStorage<'a, ShipOrbiting>, | |||
| ships_moving: WriteStorage<'a, ShipMoving>, | |||
| fleet_owned: WriteStorage<'a, FleetOwned>, | |||
| } | |||
| impl ShipControl { | |||
| pub fn new(world: &mut World) -> Result<Self, Error> { | |||
| world.insert(EventChannel::<ShipControlEvent>::default()); | |||
| let ship_control_event_id = world.register_event_reader::<ShipControlEvent>()?; | |||
| Ok(Self { | |||
| ship_control_event_id, | |||
| }) | |||
| } | |||
| } | |||
| impl<'a> System<'a> for ShipControl { | |||
| type SystemData = ShipControlData<'a>; | |||
| fn run(&mut self, data: Self::SystemData) { | |||
| let ShipControlData { | |||
| ship_control_events, | |||
| lazy, | |||
| entities, | |||
| players, | |||
| player_owned, | |||
| fleets, | |||
| fleets_moving, | |||
| fleets_orbiting, | |||
| meeting_points, | |||
| ships_orbiting, | |||
| ships_moving, | |||
| fleet_owned, | |||
| } = data; | |||
| let mut processor = Processor { | |||
| lazy, | |||
| entities, | |||
| players, | |||
| player_owned, | |||
| fleets, | |||
| fleets_moving, | |||
| fleets_orbiting, | |||
| meeting_points, | |||
| ships_orbiting, | |||
| ships_moving, | |||
| fleet_owned, | |||
| }; | |||
| let events = ship_control_events.read(&mut self.ship_control_event_id); | |||
| for event in events { | |||
| match event { | |||
| ShipControlEvent::SetMoving { ship, fleet } => processor.set_moving(*ship, *fleet), | |||
| ShipControlEvent::SetOrbiting { ship, target } => { | |||
| processor.set_orbiting(*ship, *target) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| impl Processor<'_> { | |||
| fn set_moving(&mut self, ship_id: Entity, fleet_id: Entity) { | |||
| let fleet_owned = self.fleet_owned.get_mut(ship_id).unwrap(); | |||
| fleet_owned.set_owner(fleet_id); | |||
| self.ships_orbiting.remove(ship_id); | |||
| self.ships_moving | |||
| .insert(ship_id, ShipMoving::new()) | |||
| .panic("Unable to create component 'ShipsMoving'"); | |||
| } | |||
| fn set_orbiting(&mut self, ship_id: Entity, target_id: Entity) { | |||
| let player_owned = self.player_owned.get(ship_id).unwrap(); | |||
| let player_id = player_owned.owner(); | |||
| let player = self.players.get(player_id).unwrap(); | |||
| let meeting_point = self.meeting_points.get_mut(target_id).unwrap(); | |||
| let fleet_id = match meeting_point.fleet(player.index()) { | |||
| Some(fleet_id) => fleet_id, | |||
| None => { | |||
| let fleet_id = Fleet::builder() | |||
| .player(player_id) | |||
| .orbiting(target_id) | |||
| .lazy( | |||
| &self.lazy, | |||
| &self.entities, | |||
| &mut self.player_owned, | |||
| &mut self.fleets, | |||
| &mut self.fleets_moving, | |||
| &mut self.fleets_orbiting, | |||
| ) | |||
| .panic("Unable to create new entitiy: Fleet"); | |||
| meeting_point.add_fleet(player.index(), fleet_id); | |||
| fleet_id | |||
| } | |||
| }; | |||
| let fleet_owned = self.fleet_owned.get_mut(ship_id).unwrap(); | |||
| fleet_owned.set_owner(fleet_id); | |||
| self.ships_moving.remove(ship_id); | |||
| self.ships_orbiting | |||
| .insert(ship_id, ShipOrbiting::new()) | |||
| .panic("Unable to create component 'ShipOrbiting'"); | |||
| } | |||
| } | |||
| @@ -1,47 +0,0 @@ | |||
| #![allow(dead_code)] | |||
| use specs::{prelude::*, ParJoin, Read, ReadStorage, System, WriteStorage}; | |||
| use crate::{ | |||
| components::{Player, PlayerOwned, Position, Ship}, | |||
| resources::Global, | |||
| }; | |||
| #[derive(Default)] | |||
| pub struct ShipMovement; | |||
| #[derive(SystemData)] | |||
| pub struct ShipMovementData<'a> { | |||
| positions: WriteStorage<'a, Position>, | |||
| ships: ReadStorage<'a, Ship>, | |||
| player_owned: ReadStorage<'a, PlayerOwned>, | |||
| players: ReadStorage<'a, Player>, | |||
| global: Read<'a, Global>, | |||
| } | |||
| impl<'a> System<'a> for ShipMovement { | |||
| type SystemData = ShipMovementData<'a>; | |||
| fn run(&mut self, data: Self::SystemData) { | |||
| let ShipMovementData { | |||
| mut positions, | |||
| ships, | |||
| player_owned, | |||
| players, | |||
| global, | |||
| } = data; | |||
| (&mut positions, &player_owned, &ships).par_join().for_each( | |||
| |(position, player_owned, ship)| { | |||
| let delta = global.delta * global.world_speed; | |||
| let position = position.get_mut(); | |||
| let type_ = ship.type_(); | |||
| let player_id = player_owned.owner(); | |||
| let player = players.get(player_id).unwrap(); | |||
| let ship_data = player.ship_data(type_); | |||
| *position += ship.dir() * ship_data.speed * delta; | |||
| }, | |||
| ); | |||
| } | |||
| } | |||
| @@ -1,297 +0,0 @@ | |||
| use glc::{ | |||
| math::{linear_step, sqr}, | |||
| matrix::Matrix3f, | |||
| vector::{Angle, Vector2f, Vector3f}, | |||
| }; | |||
| use rand::random; | |||
| use shrev::ReaderId; | |||
| use specs::{ | |||
| hibitset::BitSet, prelude::*, world::Index, Entities, ParJoin, Read, ReadStorage, System, | |||
| WriteStorage, | |||
| }; | |||
| use crate::{ | |||
| components::{ | |||
| FleetOrbiting, FleetOwned, MeetingPoint, Obstacle, Position, Shape, Ship, ShipObstacle, | |||
| }, | |||
| constants::{ | |||
| SHIP_ORBIT_AGILITY, SHIP_ORBIT_ANGLE_DELTA_MIN, SHIP_ORBIT_ANGLE_DELTA_RND, | |||
| SHIP_ORBIT_DISTANCE_MAX, VECTOR_2F_POS_X, | |||
| }, | |||
| misc::{ComponentEvent, StorageHelper, StorageHelperMut}, | |||
| resources::Global, | |||
| }; | |||
| pub struct Ships { | |||
| need_update: BitSet, | |||
| fleet_owned_id: ReaderId<ComponentEvent<FleetOwned>>, | |||
| } | |||
| #[derive(SystemData)] | |||
| pub struct ShipsData<'a> { | |||
| global: Read<'a, Global>, | |||
| entities: Entities<'a>, | |||
| ships: WriteStorage<'a, Ship>, | |||
| fleet_owned: ReadStorage<'a, FleetOwned>, | |||
| fleet_orbiting: ReadStorage<'a, FleetOrbiting>, | |||
| positions: ReadStorage<'a, Position>, | |||
| shapes: ReadStorage<'a, Shape>, | |||
| obstacles: ReadStorage<'a, Obstacle>, | |||
| meeting_points: ReadStorage<'a, MeetingPoint>, | |||
| } | |||
| struct Processor<'a> { | |||
| need_update: &'a BitSet, | |||
| entities: &'a Entities<'a>, | |||
| positions: &'a ReadStorage<'a, Position>, | |||
| shapes: &'a ReadStorage<'a, Shape>, | |||
| obstacles: &'a ReadStorage<'a, Obstacle>, | |||
| meeting_points: &'a ReadStorage<'a, MeetingPoint>, | |||
| fleet_orbiting: &'a ReadStorage<'a, FleetOrbiting>, | |||
| delta: f32, | |||
| } | |||
| impl Ships { | |||
| pub fn new(world: &mut World) -> Self { | |||
| WriteStorage::<FleetOwned>::setup(world); | |||
| let need_update = BitSet::new(); | |||
| let fleet_owned_id = world | |||
| .system_data::<WriteStorage<FleetOwned>>() | |||
| .register_event_reader(); | |||
| Self { | |||
| need_update, | |||
| fleet_owned_id, | |||
| } | |||
| } | |||
| fn progress_events(&mut self, fleet_owned: &ReadStorage<'_, FleetOwned>) { | |||
| self.need_update.clear(); | |||
| let events = fleet_owned.channel().read(&mut self.fleet_owned_id); | |||
| for event in events { | |||
| let id = match event { | |||
| ComponentEvent::Inserted(id, _) => id, | |||
| ComponentEvent::Modified(id, _) => id, | |||
| ComponentEvent::Removed(id, _) => id, | |||
| }; | |||
| self.need_update.add(*id); | |||
| } | |||
| } | |||
| } | |||
| impl<'a> System<'a> for Ships { | |||
| type SystemData = ShipsData<'a>; | |||
| fn run(&mut self, data: Self::SystemData) { | |||
| let ShipsData { | |||
| global, | |||
| entities, | |||
| mut ships, | |||
| fleet_owned, | |||
| fleet_orbiting, | |||
| positions, | |||
| shapes, | |||
| obstacles, | |||
| meeting_points, | |||
| } = data; | |||
| self.progress_events(&fleet_owned); | |||
| /* update ships */ | |||
| let processor = Processor { | |||
| need_update: &self.need_update, | |||
| entities: &entities, | |||
| positions: &positions, | |||
| shapes: &shapes, | |||
| obstacles: &obstacles, | |||
| fleet_orbiting: &fleet_orbiting, | |||
| meeting_points: &meeting_points, | |||
| delta: global.delta * global.world_speed, | |||
| }; | |||
| ships.set_event_emission(false); | |||
| let data = (positions.mask(), &mut ships, &positions, &fleet_owned); | |||
| data.par_join() | |||
| .for_each(|(id, ship, position, fleet_owned)| { | |||
| processor.progress_ship(id, ship, position, fleet_owned); | |||
| }); | |||
| ships.set_event_emission(true); | |||
| } | |||
| } | |||
| impl Processor<'_> { | |||
| fn progress_ship( | |||
| &self, | |||
| id: Index, | |||
| ship: &mut Ship, | |||
| position: &Position, | |||
| fleet_owned: &FleetOwned, | |||
| ) { | |||
| let fleet_id = fleet_owned.owner(); | |||
| let fleet_orbiting = self.fleet_orbiting.get(fleet_id).unwrap(); | |||
| let meeting_point_id = fleet_orbiting.meeting_point(); | |||
| let meeting_point = self.meeting_points.get(meeting_point_id).unwrap(); | |||
| let meeting_point_pos = self.positions.get(meeting_point_id).unwrap().get(); | |||
| let ship_pos = position.get(); | |||
| let target_pos = ship.target_pos(); | |||
| let target_dir = ship.target_dir(); | |||
| let meeting_point_to_target = target_pos - meeting_point_pos; | |||
| let meeting_point_to_ship = ship_pos - meeting_point_pos; | |||
| let mut ship_to_target = target_pos - ship_pos; | |||
| let r_ship = meeting_point_to_ship.length_sqr(); | |||
| let r_target = meeting_point_to_target.length_sqr(); | |||
| let meeting_point_min = meeting_point.min(); | |||
| let meeting_point_max = meeting_point.max(); | |||
| let target_in_meeting_point = | |||
| (r_target <= sqr(meeting_point_max)) && (r_target >= sqr(meeting_point_min)); | |||
| let ship_in_meeting_point = r_ship < sqr(SHIP_ORBIT_DISTANCE_MAX * meeting_point_max); | |||
| let need_update = self.need_update.contains(id); | |||
| let has_target = target_dir.length_sqr() != 0.0; | |||
| let passed_target = target_dir * ship_to_target <= 0.0; | |||
| /* check and update target posistion */ | |||
| if need_update | |||
| || !has_target | |||
| || passed_target | |||
| || ship_in_meeting_point != target_in_meeting_point | |||
| { | |||
| let target_pos = if ship_in_meeting_point && meeting_point_max > 0.0 { | |||
| let meeting_point_to_ship_vec3 = | |||
| Vector3f::new(meeting_point_to_ship.x, meeting_point_to_ship.y, 0.0); | |||
| let ship_dir_vec3 = ship.dir().into_vec3(); | |||
| let dir = if meeting_point_to_ship_vec3.cross(&ship_dir_vec3).z > 0.0 { | |||
| 1.0 | |||
| } else { | |||
| -1.0 | |||
| }; | |||
| let meeting_point_min = meeting_point.min(); | |||
| let meeting_point_max = meeting_point.max(); | |||
| let add = SHIP_ORBIT_ANGLE_DELTA_MIN + SHIP_ORBIT_ANGLE_DELTA_RND * random::<f32>(); | |||
| let angle = meeting_point_to_ship.angle2(&VECTOR_2F_POS_X) | |||
| + (add * dir / meeting_point_max); | |||
| let radius = | |||
| meeting_point_min + (meeting_point_max - meeting_point_min) * random::<f32>(); | |||
| Vector2f::new( | |||
| meeting_point_pos.x + radius * angle.cos(), | |||
| meeting_point_pos.y + radius * angle.sin(), | |||
| ) | |||
| } else { | |||
| *ship.obstacle_mut() = ShipObstacle::Search; | |||
| *meeting_point_pos | |||
| }; | |||
| ship.set_target(target_pos, (target_pos - ship_pos).normalize()); | |||
| ship_to_target = target_pos - ship_pos; | |||
| } | |||
| /* check if obstacle is still valid */ | |||
| if ship_in_meeting_point { | |||
| *ship.obstacle_mut() = ShipObstacle::Done; | |||
| } else if let ShipObstacle::Known(obstacle) = ship.obstacle() { | |||
| if let Some(position) = self.positions.get(obstacle) { | |||
| let obstacle_meeting_point = self.meeting_points.get(obstacle).unwrap(); | |||
| let obstacle_pos = position.get(); | |||
| let ship_to_obstacle = obstacle_pos - ship_pos; | |||
| let obstacle_angle = ship_to_target | |||
| .angle2(&ship_to_obstacle) | |||
| .into_deg() | |||
| .into_inner() | |||
| .abs(); | |||
| let meeting_point_sqr = obstacle_meeting_point.max() * obstacle_meeting_point.max(); | |||
| if (obstacle_angle > 90.0 && ship_to_obstacle.length_sqr() > meeting_point_sqr) | |||
| || obstacle_angle > 170.0 | |||
| { | |||
| *ship.obstacle_mut() = ShipObstacle::Search; | |||
| } | |||
| } else { | |||
| *ship.obstacle_mut() = ShipObstacle::Search; | |||
| } | |||
| } | |||
| /* find obstacle */ | |||
| if !ship_in_meeting_point && ship.obstacle() == ShipObstacle::Search { | |||
| let mut dist_sqr = f32::MAX; | |||
| for (e, position, _) in (self.entities, self.positions, self.obstacles).join() { | |||
| let obstacle_pos = position.get(); | |||
| let ship_to_obstacle = obstacle_pos - ship_pos; | |||
| if ship_to_target * ship_to_obstacle < 0.0 { | |||
| continue; // obstacle is behind the ship | |||
| } | |||
| let len_sqr = ship_to_obstacle.length_sqr(); | |||
| if len_sqr < dist_sqr { | |||
| dist_sqr = len_sqr; | |||
| *ship.obstacle_mut() = ShipObstacle::Known(e); | |||
| } | |||
| } | |||
| if let ShipObstacle::Known(e) = ship.obstacle() { | |||
| if e == fleet_owned.owner() { | |||
| *ship.obstacle_mut() = ShipObstacle::Done; | |||
| } | |||
| } | |||
| } | |||
| /* check the obstacle */ | |||
| let mut expected_dir = ship_to_target; | |||
| if let ShipObstacle::Known(obstacle) = ship.obstacle() { | |||
| let obstacle_pos = self.positions.get(obstacle).unwrap(); | |||
| let obstacle_shape = self.shapes.get(obstacle).unwrap(); | |||
| let obstacle_meeting_point = self.meeting_points.get(obstacle).unwrap(); | |||
| let ship_to_obstacle = obstacle_pos.get() - ship_pos; | |||
| let meeting_point_min = obstacle_meeting_point.min(); | |||
| let meeting_point_max = obstacle_meeting_point.max(); | |||
| let meeting_point = ship_to_obstacle.length(); | |||
| if meeting_point < meeting_point_max { | |||
| let mut tangent = Vector2f::new(-ship_to_obstacle.y, ship_to_obstacle.x); | |||
| let radius = obstacle_shape.radius(); | |||
| let mut adjust_low = linear_step(meeting_point_min, radius, meeting_point); | |||
| let adjust_high = | |||
| 1.0 - linear_step(meeting_point_max, meeting_point_min, meeting_point); | |||
| if ship_to_target * tangent < 0.0 { | |||
| tangent = -tangent; | |||
| } else { | |||
| adjust_low = -adjust_low; | |||
| } | |||
| let a_low = Angle::Deg(45.0); | |||
| let a_high = tangent.angle2(&ship_to_target); | |||
| let mat = Matrix3f::rotate(a_low * adjust_low + a_high * adjust_high); | |||
| expected_dir = mat.transform(tangent); | |||
| } | |||
| } | |||
| /* update ship direction */ | |||
| let angle = expected_dir.angle2(ship.dir()); | |||
| if angle.into_inner().abs() > 0.0001 { | |||
| let dir = angle.into_inner() / angle.abs().into_inner(); | |||
| let agility = SHIP_ORBIT_AGILITY; | |||
| let rot_speed = agility * linear_step(0.0, 45.0, angle.abs().into_deg().into_inner()); | |||
| *ship.dir_mut() *= Matrix3f::rotate(rot_speed * -dir * self.delta); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,82 @@ | |||
| #![allow(dead_code)] | |||
| use specs::{prelude::*, ParJoin, Read, ReadStorage, System, WriteStorage}; | |||
| use crate::{ | |||
| components::{Fleet, FleetOwned, Player, PlayerOwned, Position, Ship, ShipMoving, ShipType}, | |||
| resources::Global, | |||
| }; | |||
| #[derive(Default)] | |||
| pub struct ShipsMovement; | |||
| #[derive(SystemData)] | |||
| pub struct ShipsMovementData<'a> { | |||
| positions: WriteStorage<'a, Position>, | |||
| ships: ReadStorage<'a, Ship>, | |||
| ships_moving: ReadStorage<'a, ShipMoving>, | |||
| fleet_owned: ReadStorage<'a, FleetOwned>, | |||
| fleets: ReadStorage<'a, Fleet>, | |||
| player_owned: ReadStorage<'a, PlayerOwned>, | |||
| players: ReadStorage<'a, Player>, | |||
| global: Read<'a, Global>, | |||
| } | |||
| impl<'a> System<'a> for ShipsMovement { | |||
| type SystemData = ShipsMovementData<'a>; | |||
| fn run(&mut self, data: Self::SystemData) { | |||
| let ShipsMovementData { | |||
| mut positions, | |||
| ships, | |||
| ships_moving, | |||
| fleet_owned, | |||
| fleets, | |||
| player_owned, | |||
| players, | |||
| global, | |||
| } = data; | |||
| ( | |||
| &mut positions, | |||
| &player_owned, | |||
| &fleet_owned, | |||
| &ships, | |||
| ships_moving.maybe(), | |||
| ) | |||
| .par_join() | |||
| .for_each(|(position, player_owned, fleet_owned, ship, ship_moving)| { | |||
| let delta = global.delta * global.world_speed; | |||
| let position = position.get_mut(); | |||
| let player_id = player_owned.owner(); | |||
| let player = players.get(player_id).unwrap(); | |||
| let ships_data = player.ships_data(); | |||
| let type_ = ship.type_(); | |||
| let speed = if ship_moving.is_some() { | |||
| let fleet_id = fleet_owned.owner(); | |||
| let fleet = fleets.get(fleet_id).unwrap(); | |||
| let mut speed = ships_data[type_].speed; | |||
| macro_rules! merge { | |||
| ($t:path) => { | |||
| if fleet.count()[$t] > 0 { | |||
| speed = speed.min(ships_data[$t].speed); | |||
| } | |||
| }; | |||
| } | |||
| merge!(ShipType::Fighter); | |||
| merge!(ShipType::Bomber); | |||
| merge!(ShipType::Transporter); | |||
| speed | |||
| } else { | |||
| ships_data[type_].speed | |||
| }; | |||
| *position += ship.dir() * speed * delta; | |||
| }); | |||
| } | |||
| } | |||
| @@ -0,0 +1,239 @@ | |||
| use glc::{ | |||
| math::{linear_step, sqr}, | |||
| matrix::Matrix3f, | |||
| vector::{Angle, Vector2f}, | |||
| }; | |||
| use shrev::EventChannel; | |||
| use specs::{prelude::*, Entities, Read, ReadStorage, System, WriteStorage}; | |||
| use crate::{ | |||
| components::{ | |||
| FleetMoving, FleetOwned, MeetingPoint, Obstacle, Player, PlayerOwned, Position, Shape, | |||
| Ship, ShipMoving, ShipObstacle, | |||
| }, | |||
| constants::SHIP_ORBIT_DISTANCE_MAX, | |||
| misc::StorageHelperMut, | |||
| resources::Global, | |||
| systems::ShipControlEvent, | |||
| }; | |||
| #[derive(Default)] | |||
| pub struct ShipsMoving {} | |||
| #[derive(SystemData)] | |||
| pub struct ShipsData<'a> { | |||
| global: Read<'a, Global>, | |||
| entities: Entities<'a>, | |||
| positions: ReadStorage<'a, Position>, | |||
| ships: WriteStorage<'a, Ship>, | |||
| ships_moving: WriteStorage<'a, ShipMoving>, | |||
| ship_control: WriteExpect<'a, EventChannel<ShipControlEvent>>, | |||
| fleet_owned: ReadStorage<'a, FleetOwned>, | |||
| fleet_moving: ReadStorage<'a, FleetMoving>, | |||
| meeting_points: ReadStorage<'a, MeetingPoint>, | |||
| obstacles: ReadStorage<'a, Obstacle>, | |||
| shapes: ReadStorage<'a, Shape>, | |||
| players: ReadStorage<'a, Player>, | |||
| player_owned: ReadStorage<'a, PlayerOwned>, | |||
| } | |||
| struct Processor<'a> { | |||
| delta: f32, | |||
| entities: Entities<'a>, | |||
| positions: ReadStorage<'a, Position>, | |||
| shapes: ReadStorage<'a, Shape>, | |||
| obstacles: ReadStorage<'a, Obstacle>, | |||
| meeting_points: ReadStorage<'a, MeetingPoint>, | |||
| fleet_moving: ReadStorage<'a, FleetMoving>, | |||
| players: ReadStorage<'a, Player>, | |||
| } | |||
| impl<'a> System<'a> for ShipsMoving { | |||
| type SystemData = ShipsData<'a>; | |||
| fn run(&mut self, data: Self::SystemData) { | |||
| let ShipsData { | |||
| global, | |||
| entities, | |||
| positions, | |||
| mut ships, | |||
| mut ships_moving, | |||
| mut ship_control, | |||
| fleet_owned, | |||
| fleet_moving, | |||
| meeting_points, | |||
| obstacles, | |||
| shapes, | |||
| players, | |||
| player_owned, | |||
| } = data; | |||
| /* update ships */ | |||
| let processor = Processor { | |||
| delta: global.delta * global.world_speed, | |||
| entities, | |||
| positions, | |||
| shapes, | |||
| obstacles, | |||
| meeting_points, | |||
| fleet_moving, | |||
| players, | |||
| }; | |||
| ships.set_event_emission(false); | |||
| let data = ( | |||
| &processor.entities, | |||
| &processor.positions, | |||
| &mut ships, | |||
| &mut ships_moving, | |||
| &fleet_owned, | |||
| &player_owned, | |||
| ); | |||
| for (id, position, ship, ship_moving, fleet_owned, player_owned) in data.join() { | |||
| let target = | |||
| processor.progress_ship(position, ship, ship_moving, fleet_owned, player_owned); | |||
| if let Some(target) = target { | |||
| let event = ShipControlEvent::SetOrbiting { ship: id, target }; | |||
| ship_control.single_write(event); | |||
| } | |||
| } | |||
| ships.set_event_emission(true); | |||
| } | |||
| } | |||
| impl Processor<'_> { | |||
| fn progress_ship( | |||
| &self, | |||
| position: &Position, | |||
| ship: &mut Ship, | |||
| ship_moving: &mut ShipMoving, | |||
| fleet_owned: &FleetOwned, | |||
| player_owned: &PlayerOwned, | |||
| ) -> Option<Entity> { | |||
| let player_id = player_owned.owner(); | |||
| let player = self.players.get(player_id).unwrap(); | |||
| let fleet_id = fleet_owned.owner(); | |||
| let fleet_moving = self.fleet_moving.get(fleet_id).unwrap(); | |||
| let target_id = fleet_moving.target(); | |||
| let target_orbit = self.meeting_points.get(target_id).unwrap().max(); | |||
| let target_pos = self.positions.get(target_id).unwrap().get(); | |||
| let ship_pos = position.get(); | |||
| let ship_data = player.ship_data(ship.type_()); | |||
| let meeting_point_to_ship = ship_pos - target_pos; | |||
| let ship_to_target = target_pos - ship_pos; | |||
| let r_ship = meeting_point_to_ship.length_sqr(); | |||
| if r_ship < sqr(SHIP_ORBIT_DISTANCE_MAX * target_orbit) { | |||
| return Some(target_id); | |||
| } | |||
| /* check if obstacle is still valid */ | |||
| if let ShipObstacle::Known(obstacle_id) = ship_moving.obstacle() { | |||
| let obstacle_id = *obstacle_id; | |||
| let obstacle_pos = self.positions.get(obstacle_id).unwrap().get(); | |||
| let obstacle_meeting_point = self.meeting_points.get(obstacle_id).unwrap(); | |||
| let ship_to_obstacle = obstacle_pos - ship_pos; | |||
| let obstacle_angle = ship_to_target | |||
| .angle2(&ship_to_obstacle) | |||
| .into_deg() | |||
| .into_inner() | |||
| .abs(); | |||
| let meeting_point_sqr = obstacle_meeting_point.max() * obstacle_meeting_point.max(); | |||
| if (obstacle_angle > 90.0 && ship_to_obstacle.length_sqr() > meeting_point_sqr) | |||
| || obstacle_angle > 170.0 | |||
| { | |||
| *ship_moving.obstacle_mut() = ShipObstacle::Search; | |||
| } | |||
| } | |||
| /* find obstacle */ | |||
| if ship_moving.obstacle() == &ShipObstacle::Search { | |||
| let mut dist_sqr = f32::MAX; | |||
| for (obstacle_id, position, _) in | |||
| (&self.entities, &self.positions, &self.obstacles).join() | |||
| { | |||
| let obstacle_pos = position.get(); | |||
| let ship_to_obstacle = obstacle_pos - ship_pos; | |||
| if ship_to_target * ship_to_obstacle < 0.0 { | |||
| continue; // obstacle is behind the ship | |||
| } | |||
| let len_sqr = ship_to_obstacle.length_sqr(); | |||
| if len_sqr < dist_sqr { | |||
| dist_sqr = len_sqr; | |||
| *ship_moving.obstacle_mut() = ShipObstacle::Known(obstacle_id); | |||
| } | |||
| } | |||
| if let ShipObstacle::Known(obstacle_id) = ship_moving.obstacle() { | |||
| if *obstacle_id == fleet_owned.owner() { | |||
| *ship_moving.obstacle_mut() = ShipObstacle::Done; | |||
| } | |||
| } | |||
| } | |||
| /* check the obstacle */ | |||
| let mut expected_dir = ship_to_target; | |||
| if let ShipObstacle::Known(obstacle) = ship_moving.obstacle() { | |||
| let obstacle_pos = self.positions.get(*obstacle).unwrap(); | |||
| let obstacle_shape = self.shapes.get(*obstacle).unwrap(); | |||
| let obstacle_meeting_point = self.meeting_points.get(*obstacle).unwrap(); | |||
| let ship_to_obstacle = obstacle_pos.get() - ship_pos; | |||
| let meeting_point_min = obstacle_meeting_point.min(); | |||
| let meeting_point_max = obstacle_meeting_point.max(); | |||
| let meeting_point = ship_to_obstacle.length(); | |||
| if meeting_point < meeting_point_max { | |||
| let mut tangent = Vector2f::new(-ship_to_obstacle.y, ship_to_obstacle.x); | |||
| let radius = obstacle_shape.radius(); | |||
| let mut adjust_low = linear_step(meeting_point_min, radius, meeting_point); | |||
| let adjust_high = | |||
| 1.0 - linear_step(meeting_point_max, meeting_point_min, meeting_point); | |||
| if ship_to_target * tangent < 0.0 { | |||
| tangent = -tangent; | |||
| } else { | |||
| adjust_low = -adjust_low; | |||
| } | |||
| let a_low = Angle::Deg(45.0); | |||
| let a_high = tangent.angle2(&ship_to_target); | |||
| let mat = Matrix3f::rotate(a_low * adjust_low + a_high * adjust_high); | |||
| expected_dir = mat.transform(tangent); | |||
| } | |||
| } | |||
| /* update ship direction */ | |||
| let angle = expected_dir.angle2(ship.dir()); | |||
| if angle.into_inner().abs() > 0.0001 { | |||
| let dir = angle.into_inner() / angle.abs().into_inner(); | |||
| let rot_speed = | |||
| ship_data.agility * linear_step(0.0, 45.0, angle.abs().into_deg().into_inner()); | |||
| *ship.dir_mut() *= Matrix3f::rotate(rot_speed * -dir * self.delta); | |||
| } | |||
| None | |||
| } | |||
| } | |||
| @@ -0,0 +1,138 @@ | |||
| use glc::{ | |||
| math::linear_step, | |||
| matrix::Matrix3f, | |||
| vector::{Vector2f, Vector3f}, | |||
| }; | |||
| use rand::random; | |||
| use specs::{prelude::*, ParJoin, Read, ReadStorage, System, WriteStorage}; | |||
| use crate::{ | |||
| components::{FleetOrbiting, FleetOwned, MeetingPoint, Position, Ship, ShipOrbiting}, | |||
| constants::{SHIP_DATA_IN_ORBIT, SHIP_ORBIT_ANGLE_DELTA_MIN, SHIP_ORBIT_ANGLE_DELTA_RND}, | |||
| misc::StorageHelperMut, | |||
| resources::Global, | |||
| }; | |||
| #[derive(Default)] | |||
| pub struct ShipsOrbiting; | |||
| #[derive(SystemData)] | |||
| pub struct ShipsData<'a> { | |||
| global: Read<'a, Global>, | |||
| positions: ReadStorage<'a, Position>, | |||
| fleet_owned: ReadStorage<'a, FleetOwned>, | |||
| ships: WriteStorage<'a, Ship>, | |||
| ships_orbiting: WriteStorage<'a, ShipOrbiting>, | |||
| fleets_orbiting: ReadStorage<'a, FleetOrbiting>, | |||
| meeting_points: ReadStorage<'a, MeetingPoint>, | |||
| } | |||
| struct Processor<'a> { | |||
| delta: f32, | |||
| positions: &'a ReadStorage<'a, Position>, | |||
| meeting_points: &'a ReadStorage<'a, MeetingPoint>, | |||
| fleets_orbiting: &'a ReadStorage<'a, FleetOrbiting>, | |||
| } | |||
| impl<'a> System<'a> for ShipsOrbiting { | |||
| type SystemData = ShipsData<'a>; | |||
| fn run(&mut self, data: Self::SystemData) { | |||
| let ShipsData { | |||
| global, | |||
| positions, | |||
| fleet_owned, | |||
| mut ships, | |||
| mut ships_orbiting, | |||
| fleets_orbiting, | |||
| meeting_points, | |||
| } = data; | |||
| /* update ships */ | |||
| let processor = Processor { | |||
| delta: global.delta * global.world_speed, | |||
| positions: &positions, | |||
| fleets_orbiting: &fleets_orbiting, | |||
| meeting_points: &meeting_points, | |||
| }; | |||
| ships.set_event_emission(false); | |||
| let data = (&positions, &fleet_owned, &mut ships, &mut ships_orbiting); | |||
| data.par_join() | |||
| .for_each(|(position, fleet_owned, ship, ship_orbiting)| { | |||
| processor.progress_ship(position, fleet_owned, ship, ship_orbiting); | |||
| }); | |||
| ships.set_event_emission(true); | |||
| } | |||
| } | |||
| impl Processor<'_> { | |||
| fn progress_ship( | |||
| &self, | |||
| position: &Position, | |||
| fleet_owned: &FleetOwned, | |||
| ship: &mut Ship, | |||
| ship_orbiting: &mut ShipOrbiting, | |||
| ) { | |||
| let fleet_id = fleet_owned.owner(); | |||
| let fleet_orbiting = self.fleets_orbiting.get(fleet_id).unwrap(); | |||
| let meeting_point_id = fleet_orbiting.meeting_point(); | |||
| let meeting_point = self.meeting_points.get(meeting_point_id).unwrap(); | |||
| let meeting_point_pos = self.positions.get(meeting_point_id).unwrap().get(); | |||
| let ship_pos = position.get(); | |||
| let target_pos = *ship_orbiting.target_pos(); | |||
| let target_dir = ship_orbiting.target_dir(); | |||
| let meeting_point_to_ship = ship_pos - meeting_point_pos; | |||
| let mut ship_to_target = target_pos - ship_pos; | |||
| let has_target = target_dir.length_sqr() != 0.0; | |||
| let passed_target = target_dir * ship_to_target <= 0.0; | |||
| /* check and update target posistion */ | |||
| if !has_target || passed_target { | |||
| let meeting_point_to_ship_vec3 = | |||
| Vector3f::new(meeting_point_to_ship.x, meeting_point_to_ship.y, 0.0); | |||
| let ship_dir_vec3 = ship.dir().into_vec3(); | |||
| let dir = if meeting_point_to_ship_vec3.cross(&ship_dir_vec3).z > 0.0 { | |||
| 1.0 | |||
| } else { | |||
| -1.0 | |||
| }; | |||
| let meeting_point_min = meeting_point.min(); | |||
| let meeting_point_max = meeting_point.max(); | |||
| const VECTOR2F_POS_X: Vector2f = Vector2f::new(1.0, 0.0); | |||
| let add = SHIP_ORBIT_ANGLE_DELTA_MIN + SHIP_ORBIT_ANGLE_DELTA_RND * random::<f32>(); | |||
| let angle = | |||
| meeting_point_to_ship.angle2(&VECTOR2F_POS_X) + (add * dir / meeting_point_max); | |||
| let radius = | |||
| meeting_point_min + (meeting_point_max - meeting_point_min) * random::<f32>(); | |||
| let target_pos = Vector2f::new( | |||
| meeting_point_pos.x + radius * angle.cos(), | |||
| meeting_point_pos.y + radius * angle.sin(), | |||
| ); | |||
| ship_to_target = target_pos - ship_pos; | |||
| let target_dir = ship_to_target.normalize(); | |||
| ship_orbiting.set_target(target_pos, target_dir); | |||
| } | |||
| /* update ship direction */ | |||
| let angle = ship_to_target.angle2(ship.dir()); | |||
| if angle.into_inner().abs() > 0.0001 { | |||
| let dir = angle.into_inner() / angle.abs().into_inner(); | |||
| let agility = SHIP_DATA_IN_ORBIT.agility; | |||
| let rot_speed = agility * linear_step(0.0, 45.0, angle.abs().into_deg().into_inner()); | |||
| *ship.dir_mut() *= Matrix3f::rotate(rot_speed * -dir * self.delta); | |||
| } | |||
| } | |||
| } | |||