src/main.rs
1	use rltk::{GameState, Rltk, Point};
2 use specs::prelude::*;
3 use specs::saveload::{SimpleMarker, SimpleMarkerAllocator};
4
5 extern crate serde;
6
7 mod components;
8 pub use components::*;
9 mod map;
10 pub use map::*;
11 mod player;
12 use player::*;
13 mod rect;
14 pub use rect::Rect;
15 mod visibility_system;
16 use visibility_system::VisibilitySystem;
17 mod monster_ai_system;
18 use monster_ai_system::MonsterAI;
19 mod map_indexing_system;
20 use map_indexing_system::MapIndexingSystem;
21 mod melee_combat_system;
22 use melee_combat_system::MeleeCombatSystem;
23 mod damage_system;
24 use damage_system::DamageSystem;
25 mod gui;
26 mod gamelog;
27 use gamelog::GameLog;
28 mod spawner;
29 mod inventory_system;
30 use inventory_system::*;
31 mod saveload_system;
32 mod random_table;
33 use random_table::*;
34 mod particle_system;
35 use particle_system::ParticleBuilder;
36
37 // ***** //
38 // STATE //
39
40 #[derive(PartialEq, Copy, Clone)]
41 pub enum RunState {
42 AwaitingInput,
43 PreRun,
44 PlayerTurn,
45 MonsterTurn,
46 ShowInventory,
47 ShowDropItem,
48 ShowTargeting {
49 range : i32,
50 item : Entity
51 },
52 MainMenu {
53 menu_selection: gui::MainMenuSelection
54 },
55 SaveGame,
56 NextLevel,
57 ShowRemoveItem,
58 GameOver,
59 }
60
61 pub struct State {
62 pub ecs: World,
63 }
64
65 impl State {
66 fn run_systems(&mut self) {
67 let mut vis = VisibilitySystem{};
68 vis.run_now(&self.ecs);
69 let mut mob = MonsterAI{};
70 mob.run_now(&self.ecs);
71 let mut mapindex = MapIndexingSystem{};
72 mapindex.run_now(&self.ecs);
73 let mut mcs = MeleeCombatSystem{};
74 mcs.run_now(&self.ecs);
75 let mut dmgs = DamageSystem{};
76 dmgs.run_now(&self.ecs);
77 let mut pickup = ItemCollectionSystem{};
78 pickup.run_now(&self.ecs);
79 let mut items = ItemUseSystem{};
80 items.run_now(&self.ecs);
81 let mut drop_items = ItemDropSystem{};
82 drop_items.run_now(&self.ecs);
83 let mut item_remove = ItemRemoveSystem{};
84 item_remove.run_now(&self.ecs);
85 let mut particles = particle_system::ParticleSpawnSystem{};
86 particles.run_now(&self.ecs);
87 self.ecs.maintain();
88 }
89
90 fn entities_to_remove_on_level_change(&mut self) -> Vec<Entity> {
91 let entities = self.ecs.entities();
92 let player = self.ecs.read_storage::<Player>();
93 let backpack = self.ecs.read_storage::<InBackpack>();
94 let player_entity = self.ecs.fetch::<Entity>();
95 let equipped = self.ecs.read_storage::<Equipped>();
96
97 let mut to_delete : Vec<Entity> = Vec::new();
98 for entity in entities.join() {
99 let mut should_delete = true;
100
101 // Don't delete the player
102 let p = player.get(entity);
103 if let Some(_p) = p {
104 should_delete = false;
105 }
106
107 // Don't delete the player's equipment
108 let bp = backpack.get(entity);
109 if let Some(bp) = bp {
110 if bp.owner == *player_entity {
111 should_delete = false;
112 }
113 }
114
115 // Don't delete items currently equipped by the player
116 let eq = equipped.get(entity);
117 if let Some(eq) = eq {
118 if eq.owner == *player_entity {
119 should_delete = false;
120 }
121 }
122
123 if should_delete {
124 to_delete.push(entity);
125 }
126 }
127
128 to_delete
129 }
130
131 fn goto_next_level(&mut self) {
132 // Delete entities that aren't the player or his/her equipment
133 let to_delete = self.entities_to_remove_on_level_change();
134 for target in to_delete {
135 self.ecs.delete_entity(target).expect("Unable to delete entity");
136 }
137
138 // Build a new map and place the player
139 let worldmap;
140 let current_depth;
141 {
142 let mut worldmap_resource = self.ecs.write_resource::<Map>();
143 current_depth = worldmap_resource.depth;
144 *worldmap_resource = Map::new_map_rooms_and_corridors(current_depth + 1);
145 worldmap = worldmap_resource.clone();
146 }
147
148 // Spawn bad guys
149 for room in worldmap.rooms.iter().skip(1) {
150 spawner::spawn_room(&mut self.ecs, room, current_depth);
151 }
152
153 // Place the player and update resources
154 let (player_x, player_y) = worldmap.rooms[0].center();
155 let mut player_position = self.ecs.write_resource::<Point>();
156 *player_position = Point::new(player_x, player_y);
157 let mut position_components = self.ecs.write_storage::<Position>();
158 let player_entity = self.ecs.fetch::<Entity>();
159 let player_pos_comp = position_components.get_mut(*player_entity);
160 if let Some(player_pos_comp) = player_pos_comp {
161 player_pos_comp.x = player_x;
162 player_pos_comp.y = player_y;
163 }
164
165 // Mark the player's visibility as dirty
166 let mut viewshed_components = self.ecs.write_storage::<Viewshed>();
167 let vs = viewshed_components.get_mut(*player_entity);
168 if let Some(vs) = vs {
169 vs.dirty = true;
170 }
171
172 // Notify the player and give them some health
173 let mut gamelog = self.ecs.fetch_mut::<gamelog::GameLog>();
174 gamelog.entries.push("You descend to the next level, and take a moment to heal.".to_string());
175 let mut player_health_store = self.ecs.write_storage::<CombatStats>();
176 let player_health = player_health_store.get_mut(*player_entity);
177 if let Some(player_health) = player_health {
178 player_health.hp = i32::max(player_health.hp, player_health.max_hp / 2);
179 }
180 }
181
182 fn game_over_cleanup(&mut self) {
183 // Delete everything
184 let mut to_delete = Vec::new();
185 for e in self.ecs.entities().join() {
186 to_delete.push(e);
187 }
188 for del in to_delete.iter() {
189 self.ecs.delete_entity(*del).expect("Deletion failed");
190 }
191
192 // Build a new map and place the player
193 let worldmap;
194 {
195 let mut worldmap_resource = self.ecs.write_resource::<Map>();
196 *worldmap_resource = Map::new_map_rooms_and_corridors(1);
197 worldmap = worldmap_resource.clone();
198 }
199
200 // Spawn bad guys
201 for room in worldmap.rooms.iter().skip(1) {
202 spawner::spawn_room(&mut self.ecs, room, 1);
203 }
204
205 // Place the player and update resources
206 let (player_x, player_y) = worldmap.rooms[0].center();
207 let player_entity = spawner::player(&mut self.ecs, player_x, player_y);
208 let mut player_position = self.ecs.write_resource::<Point>();
209 *player_position = Point::new(player_x, player_y);
210 let mut position_components = self.ecs.write_storage::<Position>();
211 let mut player_entity_writer = self.ecs.write_resource::<Entity>();
212 *player_entity_writer = player_entity;
213 let player_pos_comp = position_components.get_mut(player_entity);
214 if let Some(player_pos_comp) = player_pos_comp {
215 player_pos_comp.x = player_x;
216 player_pos_comp.y = player_y;
217 }
218
219 // Mark the player's visibility as dirty
220 let mut viewshed_components = self.ecs.write_storage::<Viewshed>();
221 let vs = viewshed_components.get_mut(player_entity);
222 if let Some(vs) = vs {
223 vs.dirty = true;
224 }
225 }
226 }
227
228 impl GameState for State {
229 fn tick(&mut self, ctx : &mut Rltk) {
230 let mut newrunstate;
231 {
232 let runstate = self.ecs.fetch::<RunState>();
233 newrunstate = *runstate;
234 }
235
236 ctx.cls();
237 particle_system::cull_dead_particles(&mut self.ecs, ctx);
238
239 match newrunstate {
240 RunState::MainMenu{..} => {}
241 _ => {
242 draw_map(&self.ecs, ctx);
243
244 {
245 let positions = self.ecs.read_storage::<Position>();
246 let renderables = self.ecs.read_storage::<Renderable>();
247 let map = self.ecs.fetch::<Map>();
248
249 let mut data = (&positions, &renderables).join().collect::<Vec<_>>();
250 data.sort_by(|&a, &b| b.1.render_order.cmp(&a.1.render_order) );
251 for (pos, render) in data.iter() {
252 let idx = map.xy_idx(pos.x, pos.y);
253 if map.visible_tiles[idx] { ctx.set(pos.x, pos.y, render.fg, render.bg, render.glyph) }
254 }
255
256 gui::draw_ui(&self.ecs, ctx);
257 }
258 }
259 }
260
261 match newrunstate {
262 RunState::PreRun => {
263 self.run_systems();
264 self.ecs.maintain();
265 newrunstate = RunState::AwaitingInput;
266 }
267 RunState::AwaitingInput => {
268 newrunstate = player_input(self, ctx);
269 }
270 RunState::PlayerTurn => {
271 self.run_systems();
272 self.ecs.maintain();
273 newrunstate = RunState::MonsterTurn;
274 }
275 RunState::MonsterTurn => {
276 self.run_systems();
277 self.ecs.maintain();
278 newrunstate = RunState::AwaitingInput;
279 }
280 RunState::ShowInventory => {
281 let result = gui::show_inventory(self, ctx);
282 match result.0 {
283 gui::ItemMenuResult::Cancel => newrunstate = RunState::AwaitingInput,
284 gui::ItemMenuResult::NoResponse => {}
285 gui::ItemMenuResult::Selected => {
286 let item_entity = result.1.unwrap();
287 let is_ranged = self.ecs.read_storage::<Ranged>();
288 let is_item_ranged = is_ranged.get(item_entity);
289 if let Some(is_item_ranged) = is_item_ranged {
290 newrunstate = RunState::ShowTargeting{ range: is_item_ranged.range, item: item_entity };
291 } else {
292 let mut intent = self.ecs.write_storage::<WantsToUseItem>();
293 intent.insert(*self.ecs.fetch::<Entity>(), WantsToUseItem{ item: item_entity, target: None }).expect("Unable to insert intent");
294 newrunstate = RunState::PlayerTurn;
295 }
296 }
297 }
298 }
299 RunState::ShowTargeting{range, item} => {
300 let result = gui::ranged_target(self, ctx, range);
301 match result.0 {
302 gui::ItemMenuResult::Cancel => newrunstate = RunState::AwaitingInput,
303 gui::ItemMenuResult::NoResponse => {}
304 gui::ItemMenuResult::Selected => {
305 let mut intent = self.ecs.write_storage::<WantsToUseItem>();
306 intent.insert(*self.ecs.fetch::<Entity>(), WantsToUseItem{ item, target: result.1 }).expect("Unable to insert intent");
307 newrunstate = RunState::PlayerTurn;
308 }
309 }
310 }
311 RunState::ShowDropItem => {
312 let result = gui::drop_item_menu(self, ctx);
313 match result.0 {
314 gui::ItemMenuResult::Cancel => newrunstate = RunState::AwaitingInput,
315 gui::ItemMenuResult::NoResponse => {}
316 gui::ItemMenuResult::Selected => {
317 let item_entity = result.1.unwrap();
318 let mut intent = self.ecs.write_storage::<WantsToDropItem>();
319 intent.insert(*self.ecs.fetch::<Entity>(), WantsToDropItem{ item: item_entity }).expect("Unable to insert intent");
320 newrunstate = RunState::PlayerTurn;
321 }
322 }
323 }
324 RunState::MainMenu{ .. } => {
325 let result = gui::main_menu(self, ctx);
326 match result {
327 gui::MainMenuResult::NoSelection{ selected } => newrunstate = RunState::MainMenu{ menu_selection: selected },
328 gui::MainMenuResult::Selected{ selected } => {
329 match selected {
330 gui::MainMenuSelection::NewGame => newrunstate = RunState::PreRun,
331 gui::MainMenuSelection::LoadGame => {
332 saveload_system::load_game(&mut self.ecs);
333 newrunstate = RunState::AwaitingInput;
334 saveload_system::delete_save();
335 }
336 gui::MainMenuSelection::Quit => { ::std::process::exit(0); }
337 }
338 }
339 }
340 }
341 RunState::SaveGame => {
342 saveload_system::save_game(&mut self.ecs);
343 newrunstate = RunState::MainMenu{ menu_selection : gui::MainMenuSelection::LoadGame };
344 }
345 RunState::NextLevel => {
346 self.goto_next_level();
347 newrunstate = RunState::PreRun;
348 }
349 RunState::ShowRemoveItem => {
350 let result = gui::remove_item_menu(self, ctx);
351 match result.0 {
352 gui::ItemMenuResult::Cancel => newrunstate = RunState::AwaitingInput,
353 gui::ItemMenuResult::NoResponse => {}
354 gui::ItemMenuResult::Selected => {
355 let item_entity = result.1.unwrap();
356 let mut intent = self.ecs.write_storage::<WantsToRemoveItem>();
357 intent.insert(*self.ecs.fetch::<Entity>(), WantsToRemoveItem{ item: item_entity }).expect("Unable to insert intent");
358 newrunstate = RunState::PlayerTurn;
359 }
360 }
361 }
362 RunState::GameOver => {
363 let result = gui::game_over(ctx);
364 match result {
365 gui::GameOverResult::NoSelection => {}
366 gui::GameOverResult::QuitToMenu => {
367 self.game_over_cleanup();
368 newrunstate = RunState::MainMenu{ menu_selection: gui::MainMenuSelection::NewGame };
369 }
370 }
371 }
372 }
373
374 {
375 let mut runwriter = self.ecs.write_resource::<RunState>();
376 *runwriter = newrunstate;
377 }
378
379 damage_system::delete_the_dead(&mut self.ecs);
380
381 }
382 }
383
384 // ********
385 // MAIN
386
387 fn main() -> rltk::BError {
388 use rltk::RltkBuilder;
389 let context = RltkBuilder::simple80x50()
390 .with_title("Saint Antony's Fire")
391 .build()?;
392 // Add scanlines
393 //context.with_post_scanlines(true);
394
395 let mut gs = State{
396 ecs: World::new(),
397 };
398
399 gs.ecs.register::<Position>();
400 gs.ecs.register::<Renderable>();
401 gs.ecs.register::<Player>();
402 gs.ecs.register::<Viewshed>();
403 gs.ecs.register::<Monster>();
404 gs.ecs.register::<Name>();
405 gs.ecs.register::<BlocksTile>();
406 gs.ecs.register::<CombatStats>();
407 gs.ecs.register::<WantsToMelee>();
408 gs.ecs.register::<SufferDamage>();
409 gs.ecs.register::<Item>();
410 gs.ecs.register::<InBackpack>();
411 gs.ecs.register::<WantsToPickupItem>();
412 gs.ecs.register::<WantsToUseItem>();
413 gs.ecs.register::<ProvidesHealing>();
414 gs.ecs.register::<WantsToDropItem>();
415 gs.ecs.register::<Consumable>();
416 gs.ecs.register::<Ranged>();
417 gs.ecs.register::<InflictsDamage>();
418 gs.ecs.register::<AreaOfEffect>();
419 gs.ecs.register::<SimpleMarker<SerializeMe>>();
420 gs.ecs.register::<SerializationHelper>();
421 gs.ecs.register::<Equippable>();
422 gs.ecs.register::<Equipped>();
423 gs.ecs.register::<MeleePowerBonus>();
424 gs.ecs.register::<DefenseBonus>();
425 gs.ecs.register::<WantsToRemoveItem>();
426 gs.ecs.register::<ParticleLifetime>();
427
428 // Resource to dispatch particle effects requests
429 gs.ecs.insert(particle_system::ParticleBuilder::new());
430
431 gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());
432
433 let map = Map::new_map_rooms_and_corridors(1);
434 let (player_x, player_y) = map.rooms[0].center();
435
436 let player_entity = spawner::player(&mut gs.ecs, player_x, player_y);
437
438 gs.ecs.insert(player_entity);
439
440 let rng = rltk::RandomNumberGenerator::new();
441 gs.ecs.insert(rng);
442
443 for room in map.rooms.iter().skip(1) {
444 spawner::spawn_room(&mut gs.ecs, room, 1);
445 }
446
447 gs.ecs.insert(map);
448 gs.ecs.insert(Point::new(player_x, player_y));
449 gs.ecs.insert(RunState::MainMenu{ menu_selection: gui::MainMenuSelection::NewGame });
450
451 gs.ecs.insert(gamelog::GameLog{ entries : vec!["Saint Antony casts out...".to_string()] });
452
453 rltk::main_loop(context, gs)
454 }