text_rts/state/
mod.rs

1#[cfg(not(target_arch = "wasm32"))]
2use std::time::Instant;
3
4use bracket_lib::prelude::*;
5
6use legion::*;
7
8use crate::{
9    components::{GameCell, Unit},
10    types::{CtrlGroups, Mode, Race, UnitKind},
11};
12
13const WHITE: (u8, u8, u8) = (255, 255, 255);
14const DARK_GRAY: (u8, u8, u8) = (100, 100, 100);
15const BROWN: (u8, u8, u8) = (170, 30, 0);
16const GREEN: (u8, u8, u8) = (0, 170, 0);
17const DARK_GREEN: (u8, u8, u8) = (0, 120, 0);
18
19#[derive(Clone, Debug)]
20pub enum CurrentState {
21    Menu,
22    Playing,
23    Quitting,
24}
25
26pub struct State {
27    curr_state: CurrentState,
28    world: World,
29    schedule: Schedule,
30    window_size: (u32, u32),
31    tic: u8,
32    dt: f32,
33    #[cfg(not(target_arch = "wasm32"))]
34    instant: Instant,
35    offset: (i32, i32),
36    mouse: Point,
37    mouse_click: Option<(usize, bool)>,
38    mouse_pressed: (usize, bool, bool),
39    mode: Mode,
40    cursor: String,
41    selection: Rect,
42    selected: Vec<Entity>,
43    ctrl_groups: CtrlGroups,
44}
45
46impl State {
47    pub fn new(w: u32, h: u32) -> Self {
48        let mut world = World::default();
49
50        let mut units = Vec::new();
51        for x in 0..20 {
52            units.push((
53                GameCell::new(10 - (x & 1), x + 5, 'V', RGB::named(GREEN)),
54                Unit::new(Race::Bionic, UnitKind::Blademaster, 30)
55                    .with_damage(5)
56                    .with_speed(14.5),
57            ));
58            units.push((
59                GameCell::new(7 - (x & 1), x + 5, 'Y', RGB::named(DARK_GREEN)),
60                Unit::new(Race::Bionic, UnitKind::Strider, 40)
61                    .with_damage(5)
62                    .with_range(10, 13),
63            ));
64        }
65        for _ in 0..5 {
66            for y in 0..20 {
67                units.push((
68                    GameCell::new(45, 5 + y, '*', RGB::named(BROWN)),
69                    Unit::new(Race::Bug, UnitKind::FleshSpider, 15),
70                ));
71            }
72        }
73        world.extend(units);
74
75        let bump_units = SystemBuilder::new("bump_units")
76            .with_query(<(Read<GameCell>,)>::query().filter(component::<Unit>()))
77            .with_query(<(Read<GameCell>,)>::query())
78            .write_component::<GameCell>()
79            .build(|_, world, _, (query, inner_query)| {
80                let mut bumped = Vec::new();
81                for chunk in query.iter_chunks(world) {
82                    for (e, (cell,)) in chunk.into_iter_entities() {
83                        for inner_chunk in inner_query.iter_chunks(world) {
84                            for (e2, (cell2,)) in inner_chunk.into_iter_entities() {
85                                if !cell.is_holding() && e != e2 && cell.point() == cell2.point() {
86                                    bumped.push(e);
87                                    break;
88                                }
89                            }
90                        }
91                    }
92                }
93                for e in bumped.iter() {
94                    if let Ok(cell) = world.entry_mut(*e).unwrap().get_component_mut::<GameCell>() {
95                        cell.bump();
96                    }
97                }
98            });
99
100        let attack_units = SystemBuilder::new("attack_units")
101            .with_query(<(Read<GameCell>, Read<Unit>)>::query())
102            .with_query(<(Read<GameCell>, Read<Unit>)>::query())
103            .with_query(<(Read<GameCell>, Read<Unit>)>::query())
104            .write_component::<GameCell>()
105            .write_component::<Unit>()
106            .build(|_, world, _, (query, attack_query, moving_query)| {
107                let mut attacked_units = Vec::new();
108                let mut moving_units = Vec::new();
109                for chunk in query.iter_chunks(world) {
110                    for (e, (cell, unit)) in chunk.into_iter_entities() {
111                        let mut attacked = false;
112                        for attack_chunk in attack_query.iter_chunks(world) {
113                            for (e2, (cell2, unit2)) in attack_chunk.into_iter_entities() {
114                                if e != e2
115                                    && unit.race() != unit2.race()
116                                    && cell.range_rect(unit.range()).point_in_rect(cell2.point())
117                                {
118                                    if let Some(damage) = unit.attack() {
119                                        attacked_units.push((e, e2, damage));
120                                    }
121                                    attacked = true;
122                                    break;
123                                }
124                            }
125                        }
126                        if !attacked {
127                            for moving_chunk in moving_query.iter_chunks(world) {
128                                for (e2, (cell2, unit2)) in moving_chunk.into_iter_entities() {
129                                    if e != e2
130                                        && !cell.is_holding()
131                                        && unit.race() != unit2.race()
132                                        && cell
133                                            .range_rect(unit.follow_dist())
134                                            .point_in_rect(cell2.point())
135                                    {
136                                        moving_units.push((e, cell2.point()));
137                                        break;
138                                    }
139                                }
140                            }
141                        }
142                    }
143                }
144                for (e, e2, dmg) in attacked_units.iter() {
145                    if let Ok(cell) = world.entry_mut(*e).unwrap().get_component_mut::<GameCell>() {
146                        cell.stop_moving();
147                    }
148                    if let Ok(unit) = world.entry_mut(*e).unwrap().get_component_mut::<Unit>() {
149                        unit.reset_tic();
150                    }
151                    if let Ok(cell2) = world
152                        .entry_mut(*e2)
153                        .unwrap()
154                        .get_component_mut::<GameCell>()
155                    {
156                        cell2.set_harmed();
157                    }
158                    if let Ok(unit2) = world.entry_mut(*e2).unwrap().get_component_mut::<Unit>() {
159                        unit2.harm(*dmg);
160                    }
161                }
162                for (e, pt) in moving_units.iter() {
163                    if let Ok(cell) = world.entry_mut(*e).unwrap().get_component_mut::<GameCell>() {
164                        cell.move_towards(*pt);
165                    }
166                }
167            });
168
169        let clear_units = SystemBuilder::new("clear_units")
170            .with_query(<(Read<Unit>,)>::query().filter(maybe_changed::<Unit>()))
171            .write_component::<Unit>()
172            .build(|commands, world, _, query| {
173                let mut deleted = Vec::new();
174                for chunk in query.iter_chunks(world) {
175                    for (e, (unit,)) in chunk.into_iter_entities() {
176                        if unit.hp() <= 0 {
177                            deleted.push(e);
178                        }
179                    }
180                }
181                for e in deleted.iter() {
182                    commands.remove(*e);
183                }
184            });
185
186        let schedule = Schedule::builder()
187            .add_system(bump_units)
188            .add_system(attack_units)
189            .add_system(clear_units)
190            .flush()
191            .build();
192
193        Self {
194            curr_state: CurrentState::Menu,
195            world,
196            schedule,
197            window_size: (w, h),
198            dt: 0.016,
199            #[cfg(not(target_arch = "wasm32"))]
200            instant: Instant::now(),
201            tic: 0,
202            offset: (0, 0),
203            mouse: Point::new(0, 0),
204            mouse_click: None,
205            mouse_pressed: (0, false, false),
206            mode: Mode::Select,
207            cursor: String::from("<"),
208            selection: Rect::default(),
209            selected: Vec::new(),
210            ctrl_groups: CtrlGroups::new(),
211        }
212    }
213
214    fn menu_state(&mut self, ctx: &mut BTerm) {
215        ctx.print_centered(self.window_size.1 as i32 / 2 - 1, "TextRTS");
216        ctx.print_centered(
217            self.window_size.1 as i32 / 2 + 1,
218            "Press the spacebar to start",
219        );
220
221        if let Some(VirtualKeyCode::Space) = ctx.key {
222            self.curr_state = CurrentState::Playing;
223        }
224    }
225
226    fn play_state(&mut self, ctx: &mut BTerm) {
227        let mut resources = Resources::default();
228        self.schedule.execute(&mut self.world, &mut resources);
229
230        self.print_grid(ctx);
231
232        ctx.print_color(
233            self.mouse.x,
234            self.mouse.y,
235            RGB::named((0, 155 + self.tic, 0)),
236            RGB::new(),
237            &self.cursor,
238        );
239
240        self.render_cells(ctx);
241
242        self.print_mode(ctx);
243
244        self.mouse_input();
245
246        self.key_input(ctx);
247
248        self.draw_highlight_box(ctx);
249    }
250
251    fn mode(&self) -> Mode {
252        self.mode
253    }
254    fn set_mode(&mut self, mode: Mode) {
255        self.mode = mode;
256    }
257
258    fn mouse_input(&mut self) {
259        if self.mouse.x <= 0 {
260            self.offset.0 += 1;
261        } else if self.mouse.x >= self.window_size.0 as i32 - 1 {
262            self.offset.0 -= 1;
263        }
264        if self.mouse.y <= 0 {
265            self.offset.1 += 1;
266        } else if self.mouse.y >= self.window_size.1 as i32 - 1 {
267            self.offset.1 -= 1;
268        }
269
270        match self.mouse_click {
271            Some((0, false)) => match self.mode() {
272                Mode::Select => self.select_cells(),
273                Mode::Move | Mode::Attack => {
274                    self.move_cells();
275                    self.set_mode(Mode::Select);
276                }
277                Mode::Ctrl => self.select_same(),
278                _ => (),
279            },
280            Some((1, false)) => {
281                self.move_cells();
282                self.set_mode(Mode::Select);
283            }
284            _ => (),
285        }
286    }
287
288    fn key_num(key: VirtualKeyCode) -> Option<usize> {
289        match key {
290            VirtualKeyCode::Key0 => Some(0),
291            VirtualKeyCode::Key1 => Some(1),
292            VirtualKeyCode::Key2 => Some(2),
293            VirtualKeyCode::Key3 => Some(3),
294            VirtualKeyCode::Key4 => Some(4),
295            VirtualKeyCode::Key5 => Some(5),
296            VirtualKeyCode::Key6 => Some(6),
297            VirtualKeyCode::Key7 => Some(7),
298            VirtualKeyCode::Key8 => Some(8),
299            VirtualKeyCode::Key9 => Some(9),
300            _ => None,
301        }
302    }
303
304    fn key_input(&mut self, ctx: &mut BTerm) {
305        if let Some(key) = ctx.key {
306            match self.mode {
307                Mode::Ctrl => {
308                    if let Some(n) = State::key_num(key) {
309                        self.ctrl_groups.bind(n, self.selected.clone());
310                    }
311                    self.set_mode(Mode::Select);
312                }
313                Mode::Add => {
314                    if let Some(n) = State::key_num(key) {
315                        self.ctrl_groups.add(n, &mut self.selected.clone());
316                    }
317                    self.set_mode(Mode::Select);
318                }
319                _ => match key {
320                    VirtualKeyCode::M => self.set_mode(Mode::Move),
321                    VirtualKeyCode::A => self.set_mode(Mode::Attack),
322                    VirtualKeyCode::B => self.set_mode(Mode::Build),
323                    VirtualKeyCode::S => self.stop_cells(),
324                    VirtualKeyCode::H => self.hold_cells(),
325                    VirtualKeyCode::F => self.focus_cell(),
326
327                    VirtualKeyCode::LControl | VirtualKeyCode::RControl => {
328                        self.set_mode(Mode::Ctrl)
329                    }
330                    VirtualKeyCode::LShift | VirtualKeyCode::RShift => self.set_mode(Mode::Add),
331
332                    VirtualKeyCode::Escape => self.set_mode(Mode::Select),
333                    VirtualKeyCode::Up => self.offset.1 += 1,
334                    VirtualKeyCode::Down => self.offset.1 -= 1,
335                    VirtualKeyCode::Left => self.offset.0 += 1,
336                    VirtualKeyCode::Right => self.offset.0 -= 1,
337                    VirtualKeyCode::End => self.curr_state = CurrentState::Quitting,
338                    _ => {
339                        if let Some(n) = State::key_num(key) {
340                            if let Some(group) = self.ctrl_groups.group(n) {
341                                self.selected = group.clone();
342                                self.load_ctrl_group();
343                            }
344                        }
345                    }
346                },
347            }
348        }
349    }
350
351    fn draw_highlight_box(&mut self, ctx: &mut BTerm) {
352        if let Mode::Select = self.mode {
353            self.selection.x2 = self.mouse.x;
354            self.selection.y2 = self.mouse.y;
355            if self.mouse_pressed.0 == 0 && self.mouse_pressed.1 {
356                let x = if self.selection.x1 <= self.selection.x2 {
357                    self.selection.x1
358                } else {
359                    self.selection.x2
360                };
361                let y = if self.selection.y1 <= self.selection.y2 {
362                    self.selection.y1
363                } else {
364                    self.selection.y2
365                };
366                ctx.draw_hollow_box(
367                    x,
368                    y,
369                    self.selection.width(),
370                    self.selection.height(),
371                    RGB::named(GREEN),
372                    RGB::new(),
373                );
374            } else {
375                self.selection.x1 = self.mouse.x;
376                self.selection.y1 = self.mouse.y;
377            }
378        }
379    }
380
381    fn print_grid(&mut self, ctx: &mut BTerm) {
382        for x in 0..self.window_size.0 {
383            for y in 0..self.window_size.1 - 1 {
384                ctx.print_color(x as i32, y as i32, RGB::named(DARK_GRAY), RGB::new(), ".")
385            }
386        }
387    }
388
389    fn print_mode(&mut self, ctx: &mut BTerm) {
390        let mut w = 0;
391        let mut color = RGB::new();
392        let mut s = "";
393        match self.mode() {
394            Mode::Move => {
395                w = 5;
396                color = RGB::from_u8(0, 175, 0);
397                s = "Move";
398            }
399            Mode::Attack => {
400                w = 7;
401                color = RGB::from_u8(175, 0, 0);
402                s = "Attack";
403            }
404            Mode::Build => {
405                w = 6;
406                color = RGB::from_u8(0, 0, 175);
407                s = "Build";
408            }
409            Mode::Ctrl => {
410                w = 5;
411                color = RGB::from_u8(75, 75, 75);
412                s = "Ctrl"
413            }
414            Mode::Add => {
415                w = 4;
416                color = RGB::from_u8(75, 75, 75);
417                s = "Add"
418            }
419            _ => (),
420        }
421        ctx.draw_box(0, 0, w, 2, color, color);
422        ctx.print_color(1, 1, RGB::named(WHITE), color, s)
423    }
424
425    fn render_cells(&mut self, ctx: &mut BTerm) {
426        let mut query = <(Write<GameCell>, Write<Unit>)>::query();
427
428        for (cell, unit) in query.iter_mut(&mut self.world) {
429            if Rect::with_exact(
430                -self.offset.0,
431                -self.offset.1,
432                self.window_size.0 as i32 - self.offset.0,
433                self.window_size.1 as i32 - self.offset.1,
434            )
435            .point_in_rect(cell.point())
436            {
437                ctx.print_color(
438                    cell.x() + self.offset.0,
439                    cell.y() + self.offset.1,
440                    if self.mouse.x - self.offset.0 == cell.x()
441                        && self.mouse.y - self.offset.1 == cell.y()
442                    {
443                        cell.color_bright()
444                    } else {
445                        cell.color()
446                    },
447                    cell.bg_color(),
448                    &cell.symbol().to_string(),
449                );
450            }
451
452            cell.update(self.dt, unit.speed());
453            unit.tic(self.dt);
454        }
455    }
456
457    fn load_ctrl_group(&mut self) {
458        let mut query = <(Write<GameCell>,)>::query();
459
460        for chunk in query.iter_chunks_mut(&mut self.world) {
461            for (e, (cell,)) in chunk.into_iter_entities() {
462                if self.selected.contains(&e) {
463                    cell.select();
464                } else {
465                    cell.deselect();
466                }
467            }
468        }
469    }
470
471    fn select_cells(&mut self) {
472        let mut query = <(Write<GameCell>,)>::query();
473
474        self.selected = Vec::new();
475
476        if self.selection.width() == 0 || self.selection.height() == 0 {
477            for chunk in query.iter_chunks_mut(&mut self.world) {
478                for (e, (cell,)) in chunk.into_iter_entities() {
479                    if self.mouse.x == cell.x() + self.offset.0
480                        && self.mouse.y == cell.y() + self.offset.1
481                    {
482                        cell.select();
483                        self.selected.push(e);
484                        break;
485                    } else {
486                        cell.deselect();
487                    }
488                }
489            }
490        } else {
491            for chunk in query.iter_chunks_mut(&mut self.world) {
492                for (e, (cell,)) in chunk.into_iter_entities() {
493                    let x = if self.selection.x1 <= self.selection.x2 {
494                        self.selection.x1
495                    } else {
496                        self.selection.x2
497                    };
498                    let y = if self.selection.y1 <= self.selection.y2 {
499                        self.selection.y1
500                    } else {
501                        self.selection.y2
502                    };
503                    if Rect::with_size(
504                        x,
505                        y,
506                        self.selection.width() + 1,
507                        self.selection.height() + 1,
508                    )
509                    .point_in_rect(Point::new(
510                        cell.x() + self.offset.0,
511                        cell.y() + self.offset.1,
512                    )) {
513                        cell.select();
514                        self.selected.push(e);
515                    } else {
516                        cell.deselect();
517                    }
518                }
519            }
520        }
521    }
522
523    fn select_same(&mut self) {
524        let mut query = <(Write<GameCell>, Read<Unit>)>::query();
525
526        self.selected = Vec::new();
527
528        let mut kind = None;
529
530        if self.selection.width() == 0 || self.selection.height() == 0 {
531            for (cell, unit) in query.iter_mut(&mut self.world) {
532                if self.mouse.x == cell.x() + self.offset.0
533                    && self.mouse.y == cell.y() + self.offset.1
534                {
535                    kind = Some(unit.kind());
536                    break;
537                }
538            }
539        }
540        if let Some(kind) = kind {
541            for chunk in query.iter_chunks_mut(&mut self.world) {
542                for (e, (cell, unit)) in chunk.into_iter_entities() {
543                    if kind == unit.kind()
544                        && cell.x() + self.offset.0 > 0
545                        && cell.y() + self.offset.1 > 0
546                        && cell.x() + self.offset.0 < self.window_size.0 as i32
547                        && cell.y() + self.offset.1 < self.window_size.1 as i32
548                    {
549                        cell.select();
550                        self.selected.push(e);
551                    } else {
552                        cell.deselect();
553                    }
554                }
555            }
556        }
557
558        self.mode = Mode::Select;
559    }
560
561    fn move_cells(&mut self) {
562        let mut query = <(Write<GameCell>, Write<Unit>)>::query();
563
564        for (cell, unit) in query.iter_mut(&mut self.world) {
565            if cell.selected() {
566                cell.move_pos(Point::new(
567                    self.mouse.x - self.offset.0,
568                    self.mouse.y - self.offset.1,
569                ));
570                unit.reset_tic();
571            }
572        }
573    }
574
575    fn stop_cells(&mut self) {
576        let mut query = <(Write<GameCell>, Write<Unit>)>::query();
577
578        for (cell, _) in query.iter_mut(&mut self.world) {
579            if cell.selected() {
580                cell.stop_moving();
581            }
582        }
583    }
584
585    fn hold_cells(&mut self) {
586        let mut query = <(Write<GameCell>, Write<Unit>)>::query();
587
588        for (cell, _) in query.iter_mut(&mut self.world) {
589            if cell.selected() {
590                cell.stop_moving();
591                cell.hold();
592            }
593        }
594    }
595
596    fn focus_cell(&mut self) {
597        let mut query = <(Write<GameCell>, Write<Unit>)>::query();
598
599        for (cell, _) in query.iter_mut(&mut self.world) {
600            if cell.selected() {
601                self.offset = (
602                    -cell.x() + self.window_size.0 as i32 / 2,
603                    -cell.y() + self.window_size.1 as i32 / 2,
604                );
605            }
606        }
607    }
608
609    fn quit_state(&mut self, ctx: &mut BTerm) {
610        ctx.print(5, 5, "Are you sure you want to quit? (y/n)");
611
612        if let Some(VirtualKeyCode::Y) = ctx.key {
613            ctx.quit();
614        } else if let Some(VirtualKeyCode::N) = ctx.key {
615            self.curr_state = CurrentState::Playing;
616        }
617    }
618
619    #[cfg(target_arch = "wasm32")]
620    fn update_dt(&self) {}
621    #[cfg(not(target_arch = "wasm32"))]
622    fn update_dt(&mut self) {
623        self.dt = Instant::now().duration_since(self.instant).as_secs_f32();
624        self.instant = Instant::now();
625    }
626
627    #[cfg(target_arch = "wasm32")]
628    fn get_input(&mut self) {
629        self.mouse_pressed.2 = false;
630
631        let mut input = INPUT.lock();
632
633        input.for_each_message(|event| match event {
634            BEvent::MouseButtonUp { button } => {
635                self.mouse_pressed = (button, false, self.mouse_pressed.1)
636            }
637            BEvent::MouseButtonDown { button } => {
638                self.mouse_pressed = (button, true, self.mouse_pressed.1)
639            }
640            _ => (),
641        });
642
643        if !self.mouse_pressed.1 && self.mouse_pressed.2 {
644            self.mouse_click = Some((self.mouse_pressed.0, false))
645        }
646    }
647    #[cfg(not(target_arch = "wasm32"))]
648    fn get_input(&mut self) {
649        let mut input = INPUT.lock();
650
651        input.for_each_message(|event| match event {
652            BEvent::MouseClick { button, pressed } => self.mouse_click = Some((button, pressed)),
653            BEvent::MouseButtonUp { button } => self.mouse_pressed = (button, false, false),
654            BEvent::MouseButtonDown { button } => self.mouse_pressed = (button, true, false),
655            _ => (),
656        });
657    }
658}
659
660impl GameState for State {
661    fn tick(&mut self, ctx: &mut BTerm) {
662        self.update_dt();
663
664        ctx.cls();
665
666        self.get_input();
667
668        self.tic += 4;
669        if self.tic > 99 {
670            self.tic = 0;
671        }
672
673        self.mouse = ctx.mouse_point();
674
675        match self.curr_state {
676            CurrentState::Menu => self.menu_state(ctx),
677            CurrentState::Playing => self.play_state(ctx),
678            CurrentState::Quitting => self.quit_state(ctx),
679        }
680
681        self.mouse_click = None;
682    }
683}