src/gui.rs
1	use rltk::{ RGB, Rltk, VirtualKeyCode};
2 use specs::prelude::*;
3 use super::{CombatStats, Player, GameLog,
4 MAPHEIGHT, Map, Name,
5 Position, Point, InBackpack,
6 State, Viewshed, RunState,
7 Equipped};
8
9 const GUI_HEIGHT: usize = 50 - MAPHEIGHT - 1;
10
11 #[derive(PartialEq, Copy, Clone)]
12 pub enum MainMenuSelection {
13 NewGame,
14 LoadGame,
15 Quit
16 }
17
18 #[derive(PartialEq, Copy, Clone)]
19 pub enum MainMenuResult {
20 NoSelection{
21 selected: MainMenuSelection
22 },
23 Selected{
24 selected: MainMenuSelection
25 }
26 }
27
28 pub fn main_menu(gs : &mut State, ctx : &mut Rltk) -> MainMenuResult {
29 let save_exists = super::saveload_system::does_save_exist();
30 let runstate = gs.ecs.fetch::<RunState>();
31
32 ctx.print_color_centered(15, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "St. Antony's Fire");
33
34 if let RunState::MainMenu{ menu_selection : selection } = *runstate {
35 if selection == MainMenuSelection::NewGame {
36 ctx.print_color_centered(24, RGB::named(rltk::MAGENTA), RGB::named(rltk::BLACK), "Begin New Game");
37 } else {
38 ctx.print_color_centered(24, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "Begin New Game");
39 }
40
41 if save_exists {
42 if selection == MainMenuSelection::LoadGame {
43 ctx.print_color_centered(25, RGB::named(rltk::MAGENTA), RGB::named(rltk::BLACK), "Load Game");
44 } else {
45 ctx.print_color_centered(25, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "Load Game");
46 }
47 }
48
49 if selection == MainMenuSelection::Quit {
50 ctx.print_color_centered(26, RGB::named(rltk::MAGENTA), RGB::named(rltk::BLACK), "Quit");
51 } else {
52 ctx.print_color_centered(26, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "Quit");
53 }
54
55 match ctx.key {
56 None => return MainMenuResult::NoSelection{ selected: selection },
57 Some(key) => {
58 match key {
59 VirtualKeyCode::Escape => { return MainMenuResult::NoSelection{ selected: MainMenuSelection::Quit } }
60 VirtualKeyCode::Up => {
61 let mut newselection;
62 match selection {
63 MainMenuSelection::NewGame => newselection = MainMenuSelection::Quit,
64 MainMenuSelection::LoadGame => newselection = MainMenuSelection::NewGame,
65 MainMenuSelection::Quit => newselection = MainMenuSelection::LoadGame
66 }
67 if newselection == MainMenuSelection::LoadGame && !save_exists {
68 newselection = MainMenuSelection::NewGame;
69 }
70 return MainMenuResult::NoSelection{ selected: newselection }
71 }
72 VirtualKeyCode::Down => {
73 let mut newselection;
74 match selection {
75 MainMenuSelection::NewGame => newselection = MainMenuSelection::LoadGame,
76 MainMenuSelection::LoadGame => newselection = MainMenuSelection::Quit,
77 MainMenuSelection::Quit => newselection = MainMenuSelection::NewGame
78 }
79 if newselection == MainMenuSelection::LoadGame && !save_exists {
80 newselection = MainMenuSelection::Quit;
81 }
82 return MainMenuResult::NoSelection{ selected: newselection }
83 }
84 VirtualKeyCode::Return => return MainMenuResult::Selected{ selected : selection },
85 _ => return MainMenuResult::NoSelection{ selected: selection }
86 }
87 }
88 }
89 }
90
91 MainMenuResult::NoSelection { selected: MainMenuSelection::NewGame }
92 }
93
94 #[derive(PartialEq, Copy, Clone)]
95 pub enum GameOverResult { NoSelection, QuitToMenu }
96
97 pub fn game_over(ctx : &mut Rltk) -> GameOverResult {
98 ctx.print_color_centered(15, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "Your journey has ended!");
99 ctx.print_color_centered(17, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "One day, we'll tell you all about how you did.");
100 ctx.print_color_centered(18, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "That day, sadly, is not in this chapter..");
101
102 ctx.print_color_centered(20, RGB::named(rltk::MAGENTA), RGB::named(rltk::BLACK), "Press any key to return to the menu.");
103
104 match ctx.key {
105 None => GameOverResult::NoSelection,
106 Some(_) => GameOverResult::QuitToMenu
107 }
108 }
109
110 pub fn ranged_target(gs : &mut State, ctx : &mut Rltk, range : i32) -> (ItemMenuResult, Option<Point>) {
111 let player_entity = gs.ecs.fetch::<Entity>();
112 let player_pos = gs.ecs.fetch::<Point>();
113 let viewsheds = gs.ecs.read_storage::<Viewshed>();
114
115 ctx.print_color(5, 0, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "Select Target:");
116
117 // Highlight available target cells
118 let mut available_cells = Vec::new();
119 let visible = viewsheds.get(*player_entity);
120 if let Some(visible) = visible {
121 // We have a viewshed
122 for idx in visible.visible_tiles.iter() {
123 let distance = rltk::DistanceAlg::Pythagoras.distance2d(*player_pos, *idx);
124 if distance <= range as f32 {
125 ctx.set_bg(idx.x, idx.y, RGB::named(rltk::BLUE));
126 available_cells.push(idx);
127 }
128 }
129 } else {
130 return (ItemMenuResult::Cancel, None);
131 }
132
133 // Draw mouse cursor
134 let mouse_pos = ctx.mouse_pos();
135 let mut valid_target = false;
136 for idx in available_cells.iter() { if idx.x == mouse_pos.0 && idx.y == mouse_pos.1 { valid_target = true; } }
137 if valid_target {
138 ctx.set_bg(mouse_pos.0, mouse_pos.1, RGB::named(rltk::CYAN));
139 if ctx.left_click {
140 return (ItemMenuResult::Selected, Some(Point::new(mouse_pos.0, mouse_pos.1)));
141 }
142 } else {
143 ctx.set_bg(mouse_pos.0, mouse_pos.1, RGB::named(rltk::RED));
144 if ctx.left_click {
145 return (ItemMenuResult::Cancel, None);
146 }
147 }
148
149 (ItemMenuResult::NoResponse, None)
150 }
151
152 pub fn draw_ui(ecs: &World, ctx : &mut Rltk) {
153 ctx.draw_box(0, 38, 79, GUI_HEIGHT, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK));
154
155 let map = ecs.fetch::<Map>();
156 let depth = format!("Depth: {}", map.depth);
157 ctx.print_color(2, 38, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), &depth);
158
159 let combat_stats = ecs.read_storage::<CombatStats>();
160 let players = ecs.read_storage::<Player>();
161 for (_player, stats) in (&players, &combat_stats).join() {
162 let health = format!("Faith: {} / {} ", stats.hp, stats.max_hp);
163 ctx.print_color(12, 38, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), &health);
164
165 ctx.draw_bar_horizontal(28, 38, 51, stats.hp, stats.max_hp, RGB::named(rltk::RED), RGB::named(rltk::BLACK));
166 }
167
168 let log = ecs.fetch::<GameLog>();
169
170 let mut y = 40;
171 for s in log.entries.iter().rev() {
172 if y < 49 { ctx.print(2, y, s); }
173 y += 1;
174 }
175
176 // Draw mouse cursor
177 let mouse_pos = ctx.mouse_pos();
178 ctx.set_bg(mouse_pos.0, mouse_pos.1, RGB::named(rltk::ANTIQUE_WHITE));
179
180 draw_tooltips(ecs, ctx);
181
182 }
183
184 #[derive(PartialEq, Copy, Clone)]
185 pub enum ItemMenuResult {
186 Cancel,
187 NoResponse,
188 Selected
189 }
190
191 pub fn remove_item_menu(gs : &mut State, ctx : &mut Rltk) -> (ItemMenuResult, Option<Entity>) {
192 let player_entity = gs.ecs.fetch::<Entity>();
193 let names = gs.ecs.read_storage::<Name>();
194 let backpack = gs.ecs.read_storage::<Equipped>();
195 let entities = gs.ecs.entities();
196
197 let inventory = (&backpack, &names).join().filter(|item| item.0.owner == *player_entity );
198 let count = inventory.count();
199
200 let mut y = (25 - (count / 2)) as i32;
201 ctx.draw_box(15, y-2, 31, (count+3) as i32, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK));
202 ctx.print_color(18, y-2, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "Remove Which Item?");
203 ctx.print_color(18, y+count as i32+1, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "ESCAPE to cancel");
204
205 let mut equippable : Vec<Entity> = Vec::new();
206 let mut j = 0;
207 for (entity, _pack, name) in (&entities, &backpack, &names).join().filter(|item| item.1.owner == *player_entity ) {
208 ctx.set(17, y, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), rltk::to_cp437('('));
209 ctx.set(18, y, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), 97+j as rltk::FontCharType);
210 ctx.set(19, y, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), rltk::to_cp437(')'));
211
212 ctx.print(21, y, &name.name.to_string());
213 equippable.push(entity);
214 y += 1;
215 j += 1;
216 }
217
218 match ctx.key {
219 None => (ItemMenuResult::NoResponse, None),
220 Some(key) => {
221 match key {
222 VirtualKeyCode::Escape => { (ItemMenuResult::Cancel, None) }
223 _ => {
224 let selection = rltk::letter_to_option(key);
225 if selection > -1 && selection < count as i32 {
226 return (ItemMenuResult::Selected, Some(equippable[selection as usize]));
227 }
228 (ItemMenuResult::NoResponse, None)
229 }
230 }
231 }
232 }
233 }
234
235 pub fn show_inventory(gs : &mut State, ctx : &mut Rltk) -> (ItemMenuResult, Option<Entity>) {
236 let player_entity = gs.ecs.fetch::<Entity>();
237 let names = gs.ecs.read_storage::<Name>();
238 let backpack = gs.ecs.read_storage::<InBackpack>();
239 let entities = gs.ecs.entities();
240
241 let inventory = (&backpack, &names).join().filter(|item| item.0.owner == *player_entity );
242 let count = inventory.count();
243
244 let mut y = (25 - (count / 2)) as i32;
245 ctx.draw_box(15, y-2, 31, (count+3) as i32, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK));
246 ctx.print_color(18, y-2, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "Inventory");
247 ctx.print_color(18, y+count as i32+1, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "ESCAPE to cancel");
248
249 let mut equippable : Vec<Entity> = Vec::new();
250 let mut j = 0;
251 for (entity, _pack, name) in (&entities, &backpack, &names).join().filter(|item| item.1.owner == *player_entity ) {
252 ctx.set(17, y, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), rltk::to_cp437('('));
253 ctx.set(18, y, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), 97+j as rltk::FontCharType);
254 ctx.set(19, y, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), rltk::to_cp437(')'));
255
256 ctx.print(21, y, &name.name.to_string());
257 equippable.push(entity);
258 y += 1;
259 j += 1;
260 }
261
262 match ctx.key {
263 None => (ItemMenuResult::NoResponse, None),
264 Some(key) => {
265 match key {
266 VirtualKeyCode::Escape => { (ItemMenuResult::Cancel, None) }
267 _ => {
268 let selection = rltk::letter_to_option(key);
269 if selection > -1 && selection < count as i32 {
270 return (ItemMenuResult::Selected, Some(equippable[selection as usize]));
271 }
272 (ItemMenuResult::NoResponse, None)
273 }
274 }
275 }
276 }
277 }
278
279 pub fn drop_item_menu(gs : &mut State, ctx : &mut Rltk) -> (ItemMenuResult, Option<Entity>) {
280 let player_entity = gs.ecs.fetch::<Entity>();
281 let names = gs.ecs.read_storage::<Name>();
282 let backpack = gs.ecs.read_storage::<InBackpack>();
283 let entities = gs.ecs.entities();
284
285 let inventory = (&backpack, &names).join().filter(|item| item.0.owner == *player_entity );
286 let count = inventory.count();
287
288 let mut y = (25 - (count / 2)) as i32;
289 ctx.draw_box(15, y-2, 31, (count+3) as i32, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK));
290 ctx.print_color(18, y-2, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "Drop Which Item?");
291 ctx.print_color(18, y+count as i32+1, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "ESCAPE to cancel");
292
293 let mut equippable : Vec<Entity> = Vec::new();
294 let mut j = 0;
295 for (entity, _pack, name) in (&entities, &backpack, &names).join().filter(|item| item.1.owner == *player_entity ) {
296 ctx.set(17, y, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), rltk::to_cp437('('));
297 ctx.set(18, y, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), 97+j as rltk::FontCharType);
298 ctx.set(19, y, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), rltk::to_cp437(')'));
299
300 ctx.print(21, y, &name.name.to_string());
301 equippable.push(entity);
302 y += 1;
303 j += 1;
304 }
305
306 match ctx.key {
307 None => (ItemMenuResult::NoResponse, None),
308 Some(key) => {
309 match key {
310 VirtualKeyCode::Escape => { (ItemMenuResult::Cancel, None) }
311 _ => {
312 let selection = rltk::letter_to_option(key);
313 if selection > -1 && selection < count as i32 {
314 return (ItemMenuResult::Selected, Some(equippable[selection as usize]));
315 }
316 (ItemMenuResult::NoResponse, None)
317 }
318 }
319 }
320 }
321 }
322
323 fn draw_tooltips(ecs: &World, ctx : &mut Rltk) {
324 let map = ecs.fetch::<Map>();
325 let names = ecs.read_storage::<Name>();
326 let positions = ecs.read_storage::<Position>();
327
328 let mouse_pos = ctx.mouse_pos();
329 if mouse_pos.0 >= map.width || mouse_pos.1 >= map.height { return; }
330 let mut tooltip : Vec<String> = Vec::new();
331 for (name, position) in (&names, &positions).join() {
332 let idx = map.xy_idx(position.x, position.y);
333 if position.x == mouse_pos.0 && position.y == mouse_pos.1 && map.visible_tiles[idx] {
334 tooltip.push(name.name.to_string());
335 }
336 }
337
338 if !tooltip.is_empty() {
339 let mut width :i32 = 0;
340 for s in tooltip.iter() {
341 if width < s.len() as i32 { width = s.len() as i32; }
342 }
343 width += 3;
344
345 if mouse_pos.0 > 40 {
346 let arrow_pos = Point::new(mouse_pos.0 - 2, mouse_pos.1);
347 let left_x = mouse_pos.0 - width;
348 let mut y = mouse_pos.1;
349 for s in tooltip.iter() {
350 ctx.print_color(left_x, y, RGB::named(rltk::WHITE), RGB::named(rltk::GREY), s);
351 let padding = (width - s.len() as i32)-1;
352 for i in 0..padding {
353 ctx.print_color(arrow_pos.x - i, y, RGB::named(rltk::WHITE), RGB::named(rltk::GREY), &" ".to_string());
354 }
355 y += 1;
356 }
357 ctx.print_color(arrow_pos.x, arrow_pos.y, RGB::named(rltk::WHITE), RGB::named(rltk::GREY), &"->".to_string());
358 } else {
359 let arrow_pos = Point::new(mouse_pos.0 + 1, mouse_pos.1);
360 let left_x = mouse_pos.0 +3;
361 let mut y = mouse_pos.1;
362 for s in tooltip.iter() {
363 ctx.print_color(left_x + 1, y, RGB::named(rltk::WHITE), RGB::named(rltk::GREY), s);
364 let padding = (width - s.len() as i32)-1;
365 for i in 0..padding {
366 ctx.print_color(arrow_pos.x + 1 + i, y, RGB::named(rltk::WHITE), RGB::named(rltk::GREY), &" ".to_string());
367 }
368 y += 1;
369 }
370 ctx.print_color(arrow_pos.x, arrow_pos.y, RGB::named(rltk::WHITE), RGB::named(rltk::GREY), &" ".to_string());
371 }
372 }
373 }