You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

570 lines
19 KiB

  1. use std::mem::take;
  2. use std::time::Instant;
  3. use glc::{
  4. math::{clamp, linear_step},
  5. matrix::Matrix4f,
  6. misc::BindGuard,
  7. shader::{Program, Type, Uniform},
  8. vector::{Vector2f, Vector4f},
  9. };
  10. use shrev::{EventChannel, ReaderId};
  11. use space_crush_common::{
  12. components::{Fleet, FleetOrbiting, MeetingPoint, Position, Shape, ShipCount},
  13. misc::{LogResult, WorldHelper as _},
  14. resources::Global,
  15. };
  16. use specs::{prelude::*, ReadExpect, ReadStorage, System, World};
  17. use crate::{
  18. constants::{
  19. FLEET_SELECT_ANIMATION_TIME, FLEET_SELECT_DETAIL_TIMEOUT, FLEET_SELECT_OFFSET,
  20. FLEET_SELECT_TEXT_OFFSET, FLEET_SELECT_TEXT_SIZE, FLEET_SELECT_WIDTH,
  21. UNIFORM_BUFFER_INDEX_CAMERA,
  22. },
  23. misc::{
  24. HorizontalAlign, MouseEvent, Text, TextCache, TextManager, VerticalAlign, WorldHelper as _,
  25. },
  26. resources::{Camera, Config, GameState, Geometry, InputState, Selection},
  27. Error,
  28. };
  29. pub struct FleetSelect {
  30. program_simple: Program,
  31. program_detail: Program,
  32. cache: TextCache,
  33. text_total: Text,
  34. text_fighter: Text,
  35. text_bomber: Text,
  36. text_transporter: Text,
  37. mouse_event_id: ReaderId<MouseEvent>,
  38. select_mode: SelectMode,
  39. camera_counter: usize,
  40. need_value_update: bool,
  41. values_changed_once: bool,
  42. is_new_selection: bool,
  43. mouse_pos: Vector2f,
  44. count: ShipCount,
  45. values: Vector4f,
  46. marker: Vector2f,
  47. shape_size: f32,
  48. ring0: f32,
  49. ring1: f32,
  50. zoom: f32,
  51. }
  52. #[derive(Copy, Clone, Debug)]
  53. enum SelectMode {
  54. None,
  55. Init(Instant),
  56. Simple(f32),
  57. Detail(f32),
  58. SimpleClose(f32),
  59. DetailClose(f32),
  60. }
  61. #[derive(SystemData)]
  62. pub struct FleetSelectData<'a> {
  63. game_state: WriteExpect<'a, GameState>,
  64. camera: ReadExpect<'a, Camera>,
  65. mouse_events: ReadExpect<'a, EventChannel<MouseEvent>>,
  66. input_state: ReadExpect<'a, InputState>,
  67. geometry: ReadExpect<'a, Geometry>,
  68. global: ReadExpect<'a, Global>,
  69. config: ReadExpect<'a, Config>,
  70. positions: ReadStorage<'a, Position>,
  71. shapes: ReadStorage<'a, Shape>,
  72. meeting_points: ReadStorage<'a, MeetingPoint>,
  73. fleets: ReadStorage<'a, Fleet>,
  74. fleets_orbiting: ReadStorage<'a, FleetOrbiting>,
  75. }
  76. impl FleetSelect {
  77. pub fn new(world: &World, text_manager: &TextManager) -> Result<Self, Error> {
  78. let program_simple = world.load_program(vec![
  79. (Type::Vertex, "resources/shader/fleet_select/vert.glsl"),
  80. (
  81. Type::Fragment,
  82. "resources/shader/fleet_select/simple_frag.glsl",
  83. ),
  84. ])?;
  85. program_simple.uniform_block_binding("Camera", UNIFORM_BUFFER_INDEX_CAMERA)?;
  86. let program_detail = world.load_program(vec![
  87. (Type::Vertex, "resources/shader/fleet_select/vert.glsl"),
  88. (
  89. Type::Fragment,
  90. "resources/shader/fleet_select/detail_frag.glsl",
  91. ),
  92. ])?;
  93. program_detail.uniform_block_binding("Camera", UNIFORM_BUFFER_INDEX_CAMERA)?;
  94. let cache = text_manager.create_cache()?;
  95. let text_total = new_text(&cache)?;
  96. let text_fighter = new_text(&cache)?;
  97. let text_bomber = new_text(&cache)?;
  98. let text_transporter = new_text(&cache)?;
  99. let select_mode = SelectMode::None;
  100. let mouse_event_id = world.register_event_reader::<MouseEvent>()?;
  101. Ok(Self {
  102. program_simple,
  103. program_detail,
  104. cache,
  105. text_total,
  106. text_fighter,
  107. text_bomber,
  108. text_transporter,
  109. mouse_event_id,
  110. select_mode,
  111. camera_counter: 0,
  112. need_value_update: true,
  113. values_changed_once: false,
  114. is_new_selection: true,
  115. mouse_pos: Default::default(),
  116. count: Default::default(),
  117. values: Default::default(),
  118. marker: Default::default(),
  119. shape_size: Default::default(),
  120. ring0: Default::default(),
  121. ring1: Default::default(),
  122. zoom: Default::default(),
  123. })
  124. }
  125. fn update_camera(&mut self, d: &FleetSelectData<'_>) {
  126. let camera_counter = d.camera.update_counter();
  127. if self.camera_counter != camera_counter {
  128. self.camera_counter = camera_counter;
  129. self.need_value_update = true;
  130. }
  131. }
  132. fn handle_events(&mut self, d: &mut FleetSelectData<'_>) {
  133. let events = d.mouse_events.read(&mut self.mouse_event_id);
  134. for event in events {
  135. match event {
  136. MouseEvent::ButtonDown(button) if button == &d.config.input.fleet_select_button => {
  137. let pos = d.camera.view_to_world(d.input_state.mouse_pos);
  138. let selection = d.game_state.selection_mut().take();
  139. for (position, meeting_point) in (&d.positions, &d.meeting_points).join() {
  140. let r = meeting_point.max() * meeting_point.max();
  141. if (position.get() - pos).length_sqr() <= r {
  142. let player_index = d.game_state.player_index();
  143. let fleet_id = match meeting_point.fleet(player_index) {
  144. Some(fleet_id) => fleet_id,
  145. None => continue,
  146. };
  147. *d.game_state.selection_mut() = match selection {
  148. Some(s) if s.fleet == fleet_id => {
  149. self.is_new_selection = false;
  150. Some(s)
  151. }
  152. _ => {
  153. self.is_new_selection = true;
  154. Some(Selection {
  155. fleet: fleet_id,
  156. count: ShipCount::none(),
  157. })
  158. }
  159. };
  160. let selection = d.game_state.selection().as_ref().unwrap();
  161. let fleet = d.fleets.get(selection.fleet).unwrap();
  162. let timeout = Instant::now() + FLEET_SELECT_DETAIL_TIMEOUT;
  163. self.mouse_pos = d.input_state.mouse_pos;
  164. self.select_mode = SelectMode::Init(timeout);
  165. self.values_changed_once = false;
  166. self.set_count(selection.count, &fleet.count());
  167. break;
  168. }
  169. }
  170. }
  171. MouseEvent::ButtonUp(button) if button == &d.config.input.fleet_select_button => {
  172. self.select_mode = match self.select_mode {
  173. SelectMode::Simple(progress) => {
  174. d.game_state.selection_mut().as_mut().unwrap().count = self.count;
  175. self.mouse_pos = d.input_state.mouse_pos;
  176. SelectMode::SimpleClose(progress)
  177. }
  178. SelectMode::Detail(progress) => {
  179. d.game_state.selection_mut().as_mut().unwrap().count = self.count;
  180. self.mouse_pos = d.input_state.mouse_pos;
  181. SelectMode::DetailClose(progress)
  182. }
  183. SelectMode::Init(_) if self.is_new_selection => {
  184. d.game_state.selection_mut().as_mut().unwrap().count = ShipCount::all();
  185. SelectMode::None
  186. }
  187. _ => SelectMode::None,
  188. }
  189. }
  190. MouseEvent::Move(_, _) if self.select_mode.is_init() => {
  191. self.need_value_update = true;
  192. self.values_changed_once = false;
  193. self.mouse_pos = d.input_state.mouse_pos;
  194. self.select_mode = SelectMode::Simple(0.0);
  195. }
  196. MouseEvent::Move(_, _) if self.select_mode.is_active() => {
  197. self.need_value_update = true;
  198. self.mouse_pos = d.input_state.mouse_pos;
  199. }
  200. _ => (),
  201. }
  202. }
  203. }
  204. fn update(&mut self, d: &FleetSelectData<'_>) {
  205. if !self.select_mode.is_active() {
  206. return;
  207. }
  208. if !take(&mut self.need_value_update) {
  209. return;
  210. }
  211. /* calculate values */
  212. let selection = d.game_state.selection().as_ref().unwrap();
  213. let fleet_id = selection.fleet;
  214. let fleet = d.fleets.get(fleet_id).unwrap();
  215. let fleet_orbiting = d.fleets_orbiting.get(fleet_id).unwrap();
  216. let meeting_point_id = fleet_orbiting.meeting_point();
  217. let position = d.positions.get(meeting_point_id).unwrap();
  218. let shape = d.shapes.get(meeting_point_id).unwrap();
  219. self.marker = d.camera.view_to_world(self.mouse_pos) - position.get();
  220. self.zoom = d.camera.view().axis_x.as_vec3().length();
  221. self.shape_size = shape.radius();
  222. self.ring0 = self.shape_size + FLEET_SELECT_OFFSET / self.zoom;
  223. self.ring1 = self.ring0 + FLEET_SELECT_WIDTH / self.zoom;
  224. const VECTOR2F_POS_X: Vector2f = Vector2f::new(1.0, 0.0);
  225. let is_simple = self.select_mode.is_simple();
  226. let angle = self
  227. .marker
  228. .angle2(&VECTOR2F_POS_X)
  229. .normalize()
  230. .into_deg()
  231. .into_inner();
  232. let sector = match angle {
  233. x if (135.0 >= x && x > 45.0) || is_simple => 3,
  234. x if 45.0 >= x && x > -45.0 => 0,
  235. x if -45.0 >= x && x > -135.0 => 1,
  236. _ => 2,
  237. };
  238. /* calulate ship value */
  239. let value = self.marker.length();
  240. if value > self.ring1 {
  241. self.values_changed_once = true;
  242. match sector {
  243. x @ 0..=2 => {
  244. let mut count = selection.count;
  245. count[x] = usize::MAX;
  246. self.set_count(count, fleet.count());
  247. self.values[x] = 1.0;
  248. self.update_values(fleet.count());
  249. }
  250. _ => {
  251. self.count = ShipCount::all();
  252. self.values = Vector4f::new(1.0, 1.0, 1.0, 1.0);
  253. }
  254. }
  255. } else if value > self.shape_size {
  256. let value = linear_step(self.ring0, self.ring1, value);
  257. self.values_changed_once = true;
  258. match sector {
  259. x @ 0..=2 => {
  260. let mut count = selection.count;
  261. count[x] = (fleet.count()[x] as f32 * value).round() as usize;
  262. self.set_count(count, fleet.count());
  263. self.values[x] = value;
  264. self.update_values(fleet.count());
  265. }
  266. _ => {
  267. self.count = *fleet.count() * value;
  268. self.values = value.into();
  269. }
  270. }
  271. } else if self.values_changed_once {
  272. match sector {
  273. x @ 0..=2 => {
  274. let mut count = selection.count;
  275. count[x] = 0;
  276. self.set_count(count, fleet.count());
  277. self.values[x] = 0.0;
  278. self.update_values(fleet.count());
  279. }
  280. _ => {
  281. self.count = Default::default();
  282. self.values = Default::default();
  283. }
  284. }
  285. }
  286. /* update texts */
  287. let c = self.count.merge(fleet.count());
  288. let _guard = self.cache.begin_update();
  289. self.text_total
  290. .update(0, c.total().to_string())
  291. .error("Unable to update text for ship count (total)");
  292. self.text_fighter
  293. .update(0, c.fighter.to_string())
  294. .error("Unable to update text for ship count (fighter)");
  295. self.text_bomber
  296. .update(0, c.bomber.to_string())
  297. .error("Unable to update text for ship count (bomber)");
  298. self.text_transporter
  299. .update(0, c.transporter.to_string())
  300. .error("Unable to update text for ship count (transporter)");
  301. }
  302. fn progress(&mut self, delta: f32) -> Option<f32> {
  303. match &mut self.select_mode {
  304. SelectMode::Init(detail_timeout) if *detail_timeout < Instant::now() => {
  305. self.need_value_update = true;
  306. self.values_changed_once = false;
  307. self.select_mode = SelectMode::Detail(0.0);
  308. Some(0.0)
  309. }
  310. SelectMode::Simple(progress) => {
  311. *progress += delta / FLEET_SELECT_ANIMATION_TIME;
  312. *progress = clamp(0.0, 1.0, *progress);
  313. Some(*progress)
  314. }
  315. SelectMode::SimpleClose(progress) => {
  316. *progress += delta / FLEET_SELECT_ANIMATION_TIME;
  317. if *progress > 2.0 {
  318. self.select_mode = SelectMode::None;
  319. Some(2.0)
  320. } else {
  321. Some(*progress)
  322. }
  323. }
  324. SelectMode::Detail(progress) => {
  325. *progress += delta / FLEET_SELECT_ANIMATION_TIME;
  326. *progress = clamp(0.0, 1.0, *progress);
  327. Some(*progress)
  328. }
  329. SelectMode::DetailClose(progress) => {
  330. *progress += delta / FLEET_SELECT_ANIMATION_TIME;
  331. if *progress > 2.0 {
  332. self.select_mode = SelectMode::None;
  333. Some(2.0)
  334. } else {
  335. Some(*progress)
  336. }
  337. }
  338. _ => None,
  339. }
  340. }
  341. fn render(&mut self, progress: f32, d: &FleetSelectData<'_>) {
  342. /* select program */
  343. let is_simple = self.select_mode.is_simple();
  344. let program = if is_simple {
  345. &self.program_simple
  346. } else {
  347. &self.program_detail
  348. };
  349. /* extract system data */
  350. let selection = d.game_state.selection().as_ref().unwrap();
  351. let fleet_id = selection.fleet;
  352. let fleet_orbiting = d
  353. .fleets_orbiting
  354. .get(fleet_id)
  355. .expect("Selection contains invalid fleet!");
  356. let meeting_point_id = fleet_orbiting.meeting_point();
  357. let position = d.positions.get(meeting_point_id).unwrap();
  358. /* calculate shared values */
  359. let size = self.ring1 + 50.0;
  360. let rings = Vector2f::new(self.ring0 / size, self.ring1 / size);
  361. let p = position.get();
  362. let m = Matrix4f::new(
  363. Vector4f::new(size, 0.0, 0.0, 0.0),
  364. Vector4f::new(0.0, size, 0.0, 0.0),
  365. Vector4f::new(0.0, 0.0, size, 0.0),
  366. Vector4f::new(p.x, p.y, 0.0, 1.0),
  367. );
  368. /* update uniforms */
  369. let guard = BindGuard::new(&program);
  370. program
  371. .uniform(0, Uniform::Matrix4f(&m))
  372. .error("Error while updating model matrix");
  373. program
  374. .uniform(1, Uniform::Vector2f(&rings))
  375. .error("Error while updating rings");
  376. program
  377. .uniform(2, Uniform::Vector2f(&self.marker))
  378. .error("Error while updating marker");
  379. program
  380. .uniform(3, Uniform::Vector4f(&self.values))
  381. .error("Error while updating value");
  382. program
  383. .uniform(4, Uniform::Float(progress))
  384. .error("Error while updating progress");
  385. program
  386. .uniform(5, Uniform::Float(self.shape_size / size))
  387. .error("Error while updating size");
  388. /* render selection menu */
  389. d.geometry.render_quad();
  390. drop(guard);
  391. /* render text */
  392. let alpha = if progress <= 1.0 {
  393. progress
  394. } else {
  395. 2.0 - progress
  396. };
  397. if is_simple {
  398. self.text_total
  399. .color(Vector4f::new(1.0, 1.0, 1.0, alpha))
  400. .render_offset(&self.text_pos(self.marker, *position.get(), d));
  401. } else {
  402. self.text_total
  403. .color(Vector4f::new(1.0, 1.0, 1.0, alpha))
  404. .render_offset(&self.text_pos((0.0, 1.0), *position.get(), d));
  405. self.text_fighter
  406. .color(Vector4f::new(1.0, 1.0, 1.0, alpha))
  407. .render_offset(&self.text_pos((1.0, 0.0), *position.get(), d));
  408. self.text_bomber
  409. .color(Vector4f::new(1.0, 1.0, 1.0, alpha))
  410. .render_offset(&self.text_pos((0.0, -1.0), *position.get(), d));
  411. self.text_transporter
  412. .color(Vector4f::new(1.0, 1.0, 1.0, alpha))
  413. .render_offset(&self.text_pos((-1.0, 0.0), *position.get(), d));
  414. }
  415. }
  416. fn set_count(&mut self, count: ShipCount, fleet_count: &ShipCount) {
  417. let c = count.merge(fleet_count);
  418. let f = &fleet_count;
  419. self.count = count;
  420. self.values = Vector4f::new(
  421. clamp(0.0, 1.0, c.fighter as f32 / f.fighter as f32),
  422. clamp(0.0, 1.0, c.bomber as f32 / f.bomber as f32),
  423. clamp(0.0, 1.0, c.transporter as f32 / f.transporter as f32),
  424. clamp(0.0, 1.0, c.total() as f32 / f.total() as f32),
  425. );
  426. }
  427. fn update_values(&mut self, fleet_count: &ShipCount) {
  428. let total_max = fleet_count.total() as f32;
  429. let sum = fleet_count[0] as f32 * self.values[0]
  430. + fleet_count[1] as f32 * self.values[1]
  431. + fleet_count[2] as f32 * self.values[2];
  432. self.values[3] = sum / total_max;
  433. }
  434. fn text_pos<T: Into<Vector2f>>(
  435. &self,
  436. dir: T,
  437. fleet_pos: Vector2f,
  438. d: &FleetSelectData<'_>,
  439. ) -> Vector2f {
  440. let text_offset = self.ring1 + FLEET_SELECT_TEXT_OFFSET / self.zoom.sqrt();
  441. let pos = fleet_pos + T::into(dir).normalize() * text_offset;
  442. d.camera.world_to_window(pos)
  443. }
  444. }
  445. impl<'a> System<'a> for FleetSelect {
  446. type SystemData = FleetSelectData<'a>;
  447. fn run(&mut self, mut data: Self::SystemData) {
  448. self.update_camera(&data);
  449. self.handle_events(&mut data);
  450. self.update(&data);
  451. let progress = self.progress(data.global.delta);
  452. if let Some(progress) = progress {
  453. gl::enable(gl::BLEND);
  454. gl::blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
  455. self.render(progress, &data);
  456. gl::disable(gl::BLEND);
  457. }
  458. }
  459. }
  460. impl SelectMode {
  461. pub fn is_init(&self) -> bool {
  462. matches!(self, Self::Init { .. })
  463. }
  464. pub fn is_simple(&self) -> bool {
  465. match self {
  466. Self::Simple { .. } => true,
  467. Self::SimpleClose { .. } => true,
  468. _ => false,
  469. }
  470. }
  471. pub fn is_active(&self) -> bool {
  472. match self {
  473. Self::Simple { .. } => true,
  474. Self::Detail { .. } => true,
  475. _ => false,
  476. }
  477. }
  478. }
  479. fn new_text(cache: &TextCache) -> Result<Text, Error> {
  480. cache
  481. .new_text()
  482. .scale(FLEET_SELECT_TEXT_SIZE)
  483. .font("resources/fonts/DroidSansMono.ttf")
  484. .color(1.0, 1.0, 1.0, 0.75)
  485. .nowrap()
  486. .vert_align(VerticalAlign::Center)
  487. .horz_align(HorizontalAlign::Center)
  488. .text("0")
  489. .build()
  490. }