From 7e3ceab2a2f57d6ccaa0ff182e60e315d908d7b6 Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Mon, 31 Jan 2022 15:00:00 -0500 Subject: [PATCH] Finish implmentation of ranged weapons, completing section 5.28 --- Makefile | 21 ++-- help-system.mk | 19 ++++ src/ai/visible_ai_system.rs | 52 ++++++--- src/camera.rs | 16 +-- src/player.rs | 208 +++++++++++++++++++++--------------- 5 files changed, 196 insertions(+), 120 deletions(-) create mode 100644 help-system.mk diff --git a/Makefile b/Makefile index dff0d92..522c0cc 100644 --- a/Makefile +++ b/Makefile @@ -1,29 +1,28 @@ -run: +include help-system.mk + +run: $(call print-help, run, Runs the binary in develop mode) cargo run -run-pi: +run-pi: $(call print-help, run-pi, Sets appropriate flags so that the game runs on a Raspberry Pi) MESA_GL_VERSION_OVERRIDE=3.0 MESA_GLSL_VERSION_OVERRIDE=330 cargo run -clean: +clean: $(call print-help, clean, Removes save file and compilation artifacts) rm -f savegame.json cargo clean -check: +check: $(call print-help, check, Check code syntax) cargo check -lint: +lint: $(call print-help, lint, Check code syntax and style) cargo clippy -fmt: +fmt: $(call print-help, fmt, Runs formatter on code) cargo +nightly fmt fix: fmt cargo fix --allow-dirty --allow-staged -docs: +docs: $(call print-help, docs, Generates code docs) cargo doc -open-docs: - cargo doc --open - -.phony: run-pi clean check run fmt fix lint docs open-docs \ No newline at end of file +.phony: run-pi clean check run fmt fix lint docs \ No newline at end of file diff --git a/help-system.mk b/help-system.mk new file mode 100644 index 0000000..4d377e9 --- /dev/null +++ b/help-system.mk @@ -0,0 +1,19 @@ +help: + @echo $(if $(need-help),,Type \'$(MAKE)$(dash-f) help\' to get help) + +need-help := $(filter help,$(MAKECMDGOALS)) + +define print-help +$(if $(need-help),$(warning $1 -- $2)) +endef + +define last-element +$(word $(words $1),$1) +endef + +this-makefile := $(call last-element,$(MAKEFILE_LIST)) +other-makefiles := $(filter-out $(this-makefile),$(MAKEFILE_LIST)) +parent-makefile := $(call last-element,$(other-makefiles)) + +dash-f := $(if $(filter-out Makefile makefile GNUmakefile,\ +$(parent-makefile)), -f $(parent-makefile)) \ No newline at end of file diff --git a/src/ai/visible_ai_system.rs b/src/ai/visible_ai_system.rs index 019b8eb..6445576 100644 --- a/src/ai/visible_ai_system.rs +++ b/src/ai/visible_ai_system.rs @@ -1,9 +1,9 @@ -use ::rltk::RandomNumberGenerator; -use ::specs::prelude::*; +use rltk::{console, DistanceAlg, Point, RandomNumberGenerator}; +use specs::prelude::*; use crate::components::{ - Chasing, Faction, MyTurn, Name, Position, SpecialAbilities, SpellTemplate, Viewshed, - WantsToApproach, WantsToCastSpell, WantsToFlee, + Chasing, Equipped, Faction, MyTurn, Name, Position, SpecialAbilities, SpellTemplate, Viewshed, + WantsToApproach, WantsToCastSpell, WantsToFlee, WantsToShoot, Weapon, }; use crate::raws::{self, find_spell_entity_by_name, Reaction, RAWS}; use crate::{spatial, Map}; @@ -28,10 +28,12 @@ impl<'a> System<'a> for VisibleAI { WriteStorage<'a, WantsToCastSpell>, ReadStorage<'a, Name>, ReadStorage<'a, SpellTemplate>, + ReadStorage<'a, Equipped>, + ReadStorage<'a, Weapon>, + WriteStorage<'a, WantsToShoot>, ); fn run(&mut self, data: Self::SystemData) { - use ::rltk::{DistanceAlg, Point}; let ( turns, factions, @@ -48,6 +50,9 @@ impl<'a> System<'a> for VisibleAI { mut casting, names, spells, + equipped, + weapons, + mut wants_shoot, ) = data; for (entity, _turn, my_faction, pos, viewshed) in @@ -68,14 +73,14 @@ impl<'a> System<'a> for VisibleAI { for reaction in reactions.iter() { match reaction.1 { Reaction::Attack => { + let range = DistanceAlg::Pythagoras.distance2d( + Point::from(*pos), + Point::new( + reaction.0 as i32 % map.width, + reaction.0 as i32 / map.width, + ), + ); if let Some(abilities) = abilities.get(entity) { - let range = DistanceAlg::Pythagoras.distance2d( - Point::from(*pos), - Point::new( - reaction.0 as i32 % map.width, - reaction.0 as i32 / map.width, - ), - ); for ability in abilities.abilities.iter() { if range >= ability.min_range && range <= ability.range @@ -104,6 +109,29 @@ impl<'a> System<'a> for VisibleAI { } } + if !done { + for (weapon, equip) in (&weapons, &equipped).join() { + if let Some(wrange) = weapon.range { + if equip.owner == entity { + console::log(format!( + "Owner found. Ranges: {}/{}", + wrange, range + )); + if wrange >= range as i32 { + console::log("Inserting shoot"); + wants_shoot + .insert( + entity, + WantsToShoot { target: reaction.2 }, + ) + .expect("Unable to insert intent to shoot"); + done = true; + } + } + } + } + } + if !done { want_approach .insert( diff --git a/src/camera.rs b/src/camera.rs index 78f3904..8dfafc6 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,6 +1,6 @@ //! Handle rendering of the viewport -use ::rltk::{Point, Rltk}; -use ::specs::prelude::*; +use rltk::{Point, Rltk}; +use specs::prelude::*; use crate::components::{Hidden, Position, Renderable, Target, TileSize}; use crate::map::tile_glyph; @@ -104,8 +104,8 @@ pub fn render_camera(ecs: &World, ctx: &mut Rltk) { && entity_screen_y < map_height { ctx.set( - entity_screen_x + 1, - entity_screen_y + 1, + entity_screen_x, + entity_screen_y, render.fg, render.bg, render.glyph, @@ -118,15 +118,15 @@ pub fn render_camera(ecs: &World, ctx: &mut Rltk) { let entity_screen_x = pos.x - min_x; let entity_screen_y = pos.y - min_y; ctx.set( - entity_screen_x, - entity_screen_y + 1, + entity_screen_x - 1, + entity_screen_y, colors::RED, colors::YELLOW, ::rltk::to_cp437('['), ); ctx.set( - entity_screen_x + 2, - entity_screen_y + 1, + entity_screen_x + 1, + entity_screen_y, colors::RED, colors::YELLOW, ::rltk::to_cp437(']'), diff --git a/src/player.rs b/src/player.rs index c5ef998..6012447 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,17 +1,131 @@ use std::cmp::{max, min}; -use ::rltk::{DistanceAlg, Point, Rltk, VirtualKeyCode}; -use ::specs::prelude::*; +use rltk::{DistanceAlg, Point, Rltk, VirtualKeyCode}; +use specs::prelude::*; use crate::components::{ Attributes, BlocksTile, BlocksVisibility, Door, EntityMoved, Equipped, Faction, HungerClock, - HungerState, Item, Player, Pools, Position, Renderable, Target, Vendor, Viewshed, WantsToMelee, - WantsToPickupItem, + HungerState, Item, Name, Player, Pools, Position, Renderable, Target, Vendor, Viewshed, + WantsToMelee, WantsToPickupItem, WantsToShoot, }; use crate::game_log::GameLog; use crate::raws::{self, Reaction, RAWS}; use crate::{spatial, Map, RunState, State, TileType, VendorMode, WantsToCastSpell, Weapon}; +fn get_player_target_list(ecs: &mut World) -> Vec<(f32, Entity)> { + let mut possible_targets = Vec::new(); + let viewsheds = ecs.read_storage::(); + let player_entity = ecs.fetch::(); + let equipped = ecs.read_storage::(); + let weapon = ecs.read_storage::(); + let map = ecs.fetch::(); + let positions = ecs.read_storage::(); + let factions = ecs.read_storage::(); + + for (equipped, weapon) in (&equipped, &weapon).join() { + if equipped.owner == *player_entity { + if let Some(range) = weapon.range { + if let Some(vs) = viewsheds.get(*player_entity) { + let player_pos = positions.get(*player_entity).unwrap(); + + for tile_point in vs.visible_tiles.iter() { + let tile_idx = map.xy_idx(tile_point.x, tile_point.y); + let distance_to_target = DistanceAlg::Pythagoras + .distance2d(*tile_point, Point::from(*player_pos)); + if distance_to_target < range as f32 { + spatial::for_each_tile_content(tile_idx, |possible_target| { + if possible_target != *player_entity + && factions.get(possible_target).is_some() + { + possible_targets.push((distance_to_target, possible_target)); + } + }); + } + } + } + } + } + } + + possible_targets.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); + + possible_targets +} + +pub fn end_turn_targeting(ecs: &mut World) { + let possible_targets = get_player_target_list(ecs); + let mut targets = ecs.write_storage::(); + targets.clear(); + + if !possible_targets.is_empty() { + targets + .insert(possible_targets[0].1, Target {}) + .expect("Failed to insert Target tag"); + } +} + +fn fire_on_target(ecs: &mut World) -> RunState { + let targets = ecs.write_storage::(); + let entities = ecs.entities(); + let mut current_target: Option = None; + let mut log = ecs.fetch_mut::(); + + for (e, _t) in (&entities, &targets).join() { + current_target = Some(e); + } + + if let Some(target) = current_target { + let player_entity = ecs.fetch::(); + let mut shoot_store = ecs.write_storage::(); + let names = ecs.read_storage::(); + if let Some(name) = names.get(target) { + log.entries.push(format!("You fire at {}", name.name)); + } + shoot_store + .insert(*player_entity, WantsToShoot { target }) + .expect("Insert Fail"); + + RunState::Ticking + } else { + log.entries + .push("You don't have a target selected!".to_string()); + RunState::AwaitingInput + } +} + +fn cycle_target(ecs: &mut World) { + let possible_targets = get_player_target_list(ecs); + let mut targets = ecs.write_storage::(); + let entities = ecs.entities(); + let mut current_target: Option = None; + + for (e, _t) in (&entities, &targets).join() { + current_target = Some(e); + } + + targets.clear(); + if let Some(current_target) = current_target { + if !possible_targets.len() > 1 { + let mut index = 0; + for (i, target) in possible_targets.iter().enumerate() { + if target.1 == current_target { + index = i; + } + } + + if index > possible_targets.len() - 2 { + targets + .insert(possible_targets[0].1, Target {}) + .expect("Failed to insert Target tag"); + } else { + targets + .insert(possible_targets[index + 1].1, Target {}) + .expect("Failed to insert Target tag"); + } + } + } +} + pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> RunState { let mut positions = ecs.write_storage::(); let players = ecs.read_storage::(); @@ -348,91 +462,6 @@ fn use_spell_hotkey(gs: &mut State, key: i32) -> RunState { RunState::Ticking } -fn get_player_target_list(ecs: &mut World) -> Vec<(f32, Entity)> { - let mut possible_targets = Vec::new(); - let viewsheds = ecs.read_storage::(); - let player_entity = ecs.fetch::(); - let equipped = ecs.read_storage::(); - let weapon = ecs.read_storage::(); - let map = ecs.fetch::(); - let positions = ecs.read_storage::(); - let factions = ecs.read_storage::(); - - for (equipped, weapon) in (&equipped, &weapon).join() { - if equipped.owner == *player_entity { - if let Some(range) = weapon.range { - if let Some(vs) = viewsheds.get(*player_entity) { - let player_pos = positions.get(*player_entity).unwrap(); - - for tile_point in vs.visible_tiles.iter() { - let tile_idx = map.xy_idx(tile_point.x, tile_point.y); - let distance_to_target = DistanceAlg::Pythagoras - .distance2d(*tile_point, Point::from(*player_pos)); - if distance_to_target < range as f32 { - spatial::for_each_tile_content(tile_idx, |possible_target| { - if possible_target != *player_entity - && factions.get(possible_target).is_some() - { - possible_targets.push((distance_to_target, possible_target)); - } - }); - } - } - } - } - } - } - - possible_targets.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); - - possible_targets -} - -fn cycle_target(ecs: &mut World) { - let possible_targets = get_player_target_list(ecs); - let mut targets = ecs.write_storage::(); - let entities = ecs.entities(); - let mut current_target: Option = None; - - for (e, _t) in (&entities, &targets).join() { - current_target = Some(e); - } - - targets.clear(); - if let Some(current_target) = current_target { - if !possible_targets.len() > 1 { - let mut index = 0; - for (i, target) in possible_targets.iter().enumerate() { - if target.1 == current_target { - index = i; - } - } - - if index > possible_targets.len() - 2 { - targets - .insert(possible_targets[0].1, Target {}) - .expect("Failed to insert Target tag"); - } else { - targets - .insert(possible_targets[index + 1].1, Target {}) - .expect("Failed to insert Target tag"); - } - } - } -} - -pub fn end_turn_targeting(ecs: &mut World) { - let possible_targets = get_player_target_list(ecs); - let mut targets = ecs.write_storage::(); - targets.clear(); - - if !possible_targets.is_empty() { - targets - .insert(possible_targets[0].1, Target {}) - .expect("Failed to insert Target tag"); - } -} - pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { // Hotkeys if (ctx.shift || ctx.control) && ctx.key.is_some() { @@ -545,6 +574,7 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { return RunState::AwaitingInput; } + VirtualKeyCode::F => return fire_on_target(&mut gs.ecs), _ => return RunState::AwaitingInput, },