Skip to content
Snippets Groups Projects
Commit b45e23e2 authored by pumapaul's avatar pumapaul
Browse files

Add combat stats and scaling to enemies, Add treasure

parent bed8b4fc
Branches
No related tags found
No related merge requests found
......@@ -13,7 +13,7 @@ use tetra::graphics::{self, Color, DrawParams, Texture};
use tetra::input::{self, Key};
use run::Run;
use textures::Textures;
use crate::game_state::tile::Tile;
use crate::game_state::tile::{Tile, TreasureType};
use tetra::math::Vec2;
use std::cmp::{min, max};
use std::usize;
......@@ -98,17 +98,47 @@ impl GameState {
let key = GameState::get_pressed_key(ctx);
let is_player_alive = self.current_run.player.current_hp > 0;
let is_treasure_choice = self.current_run.combat_log.treasure_choice != None;
return match key {
Key::CapsLock => false,
Key::N => !is_player_alive && self.create_new_run(),
Key::N =>
!is_player_alive && self.create_new_run(),
Key::W | Key::A | Key::S | Key::D | Key::Down | Key::Up | Key::Right | Key::Left =>
is_player_alive && self.handle_user_movement(key),
Key::Space => is_player_alive && self.do_wait(),
is_player_alive && !is_treasure_choice && self.handle_user_movement(key),
Key::Space =>
is_player_alive && !is_treasure_choice && self.do_wait(),
Key::Num1 | Key::Num2 | Key::NumPad1 | Key::NumPad2 =>
is_player_alive && is_treasure_choice && self.handle_treasure_choice(key),
_ => false
};
}
fn handle_treasure_choice(&mut self, key: Key) -> bool {
if let Some(treasure) = self.current_run.combat_log.treasure_choice {
match key {
Key::Num1 | Key::NumPad1 => self.take_treasure(treasure.0),
Key::Num2 | Key::NumPad2 => self.take_treasure(treasure.1),
_ => ()
}
}
true
}
fn take_treasure(&mut self, treasure: TreasureType) {
match treasure {
TreasureType::Health => {
let potential_new_hp = self.current_run.player.current_hp + config::HEALTH_POTION;
let new_hp = min(potential_new_hp, self.current_run.player.max_hp);
self.current_run.player.current_hp = new_hp;
}
TreasureType::Strength => self.current_run.player.strength += config::STRENGTH,
TreasureType::Weapon => self.current_run.player.weapon += config::WEAPON,
TreasureType::Vitality => self.current_run.player.max_hp += config::VITALITY
}
self.current_run.combat_log.treasure_choice = None;
}
fn do_wait(&mut self) -> bool {
self.current_run.combat_log.player_action = Option::from(Wait);
true
......@@ -136,21 +166,32 @@ impl GameState {
Tile::Wall => return false,
Tile::Enemy(id) => self.attack_enemy(id, target_position),
Tile::Exit => return self.progress_level(),
Tile::Treasure(a, b) => return self.open_chest(a, b, target_position),
_ => ()
}
true
}
fn open_chest(&mut self, option_a: TreasureType, option_b: TreasureType, position: Point) -> bool {
self.current_run.level.move_player_to(position);
self.current_run.combat_log.treasure_choice = Option::from((option_a, option_b));
false
}
fn progress_level(&mut self) -> bool {
self.current_run.level_count += 1;
if self.current_run.player.current_hp < self.current_run.player.max_hp {
self.current_run.player.current_hp += config::LEVEL_HEAL;
}
self.current_run.level = Level::new(self.current_run.level_count);
self.current_run.combat_log.player_action = Option::from(PlayerAction::Level);
false
}
fn attack_enemy(&mut self, enemy_id: usize, enemy_position: Point) {
let damage = self.current_run.player.attack_power + GameState::roll_dice(6);
match self.current_run.level.enemies.get_mut(enemy_id) {
Some(enemy) => {
let damage = self.current_run.player.strength + GameState::roll_dice(self.current_run.player.weapon);
if let Some(enemy) = self.current_run.level.enemies.get_mut(enemy_id) {
if enemy.hp <= damage {
self.current_run.level.move_player_to(enemy_position);
self.current_run.combat_log.player_action = Option::from(PlayerAction::Attack(damage, 0));
......@@ -159,8 +200,6 @@ impl GameState {
self.current_run.combat_log.player_action = Option::from(PlayerAction::Attack(damage, enemy.hp));
}
}
None => ()
}
}
fn roll_dice(sides: usize) -> usize {
......@@ -209,7 +248,7 @@ impl GameState {
for (id, position) in self.current_run.level.get_current_enemies() {
if GameState::check_is_neighbor(player_position, position) {
total_damage += self.attack_player();
total_damage += self.attack_player(id);
} else {
self.move_enemy(id, position);
}
......@@ -248,13 +287,17 @@ impl GameState {
false
}
fn attack_player(&mut self) -> usize {
let damage = 1 + GameState::roll_dice(4);
fn attack_player(&mut self, enemy_id: usize) -> usize {
let mut damage = 0;
if let Some(enemy) = self.current_run.level.enemies.get(enemy_id) {
damage = enemy.strength + GameState::roll_dice(enemy.weapon_strength);
if self.current_run.player.current_hp <= damage {
self.current_run.player.current_hp = 0
} else {
self.current_run.player.current_hp -= damage
}
}
damage
}
......@@ -346,14 +389,23 @@ impl GameState {
let draw_y = y + 1;
match tile {
Tile::Empty => GameState::draw_tile(self, Color::WHITE, draw_x, draw_y, ctx),
Tile::Wall => GameState::draw_wall(self, Color::WHITE, draw_x, draw_y, ctx),
Tile::Wall => GameState::draw_wall(self, draw_x, draw_y, ctx),
Tile::Player => GameState::draw_player(self, draw_x, draw_y, ctx),
Tile::Enemy(_) => GameState::draw_enemy(self, draw_x, draw_y, ctx),
Tile::Exit => GameState::draw_tile(self, Color::RED, draw_x, draw_y, ctx),
_ => {}
Tile::Treasure(_, _) => GameState::draw_treasure(self, draw_x, draw_y, ctx),
}
}
}
}
fn draw_treasure(&mut self, x: usize, y: usize, ctx: &mut Context) {
self.textures.treasure.draw(ctx, DrawParams {
position: Vec2::new(x as f32 * 33.0, y as f32 * 33.0),
scale: Vec2::new(1.0, 1.0),
origin: Vec2::new(0.0, 0.0),
rotation: 0.0,
color: Color::GREEN,
})
}
fn draw_tile(&mut self, color: Color, x: usize, y: usize, ctx: &mut Context) {
......@@ -366,7 +418,7 @@ impl GameState {
})
}
fn draw_wall(&mut self, color: Color, x: usize, y: usize, ctx: &mut Context) {
fn draw_wall(&mut self, x: usize, y: usize, ctx: &mut Context) {
let val = self.calc_walltype(x - 1, y - 1);
let mut tex: Texture = self.textures.cross.clone();
match val {
......@@ -380,7 +432,7 @@ impl GameState {
scale: Vec2::new(1.0, 1.0),
origin: Vec2::new(0.0, 0.0),
rotation: 0.0,
color,
color: Color::WHITE,
})
}
......@@ -392,35 +444,35 @@ impl GameState {
let mut result: u32 = 0;
match board[x - 1][y - 1] {
Tile::Wall => result += 10000000,
_ => {},
_ => {}
}
match board[x][y - 1] {
Tile::Wall => result += 1000000,
_ => {},
_ => {}
}
match board[x + 1][y - 1] {
Tile::Wall => result += 100000,
_ => {},
_ => {}
}
match board[x + 1][y] {
Tile::Wall => result += 10000,
_ => {},
_ => {}
}
match board[x + 1][y + 1] {
Tile::Wall => result += 1000,
_ => {},
_ => {}
}
match board[x][y + 1] {
Tile::Wall => result += 100,
_ => {},
_ => {}
}
match board[x - 1][y + 1] {
Tile::Wall => result += 10,
_ => {},
_ => {}
}
match board[x - 1][y] {
Tile::Wall => result += 1,
_ => {},
_ => {}
}
result
......@@ -452,31 +504,65 @@ impl GameState {
fn draw_stats(&self, ctx: &mut Context) {
let text_top = GameState::calculate_text_top();
let health_string = format!("Health: {} / {}", self.current_run.player.current_hp, self.current_run.player.max_hp);
let stats_string = format!(
"Health: {} / {} Damage: {} + 1d{}",
self.current_run.player.current_hp,
self.current_run.player.max_hp,
self.current_run.player.strength,
self.current_run.player.weapon
);
let turn_string = format!("Turn: {}", self.current_run.level.turn_count);
let level_string = format!("Level: {} High Score: {}", self.current_run.level_count, self.high_score);
self.draw_string(health_string, 50.0, text_top, ctx);
self.draw_string(stats_string, 50.0, text_top, ctx);
self.draw_string(turn_string, 50.0, text_top + 25.0, ctx);
self.draw_string(level_string, 50.0, text_top + 50.0, ctx);
}
fn draw_combat_log(&self, ctx: &mut Context) {
if let Some(treasure_choice) = self.current_run.combat_log.treasure_choice {
self.draw_treasure_choice(treasure_choice.0, treasure_choice.1, ctx);
} else {
self.draw_player_action(ctx);
self.draw_enemy_action(ctx);
}
}
fn draw_treasure_choice(&self, option_a: TreasureType, option_b: TreasureType, ctx: &mut Context) {
let text_x = 500.0;
let text_top = GameState::calculate_text_top();
let a = GameState::treasure_string_for(option_a);
let b = GameState::treasure_string_for(option_b);
let str = format!("You have found some treasure. Choose between:\n [ 1 ] {} or\n [ 2 ] {}", a, b);
self.draw_string(str, text_x, text_top, ctx);
}
fn treasure_string_for(treasure: TreasureType) -> String {
match treasure {
TreasureType::Health => format!("a health potion (+{} HP)", config::HEALTH_POTION),
TreasureType::Vitality => format!("some melange spice (+{} max HP)", config::VITALITY),
TreasureType::Weapon => format!("a sharpening stone (+{} max damage)", config::WEAPON),
TreasureType::Strength => format!("some steroids (+{} min damage)", config::STRENGTH),
}
}
fn draw_player_action(&self, ctx: &mut Context) {
let text_x = 500.0;
let text_top = GameState::calculate_text_top();
match self.current_run.combat_log.player_action {
Some(PlayerAction::Level) => {
let str = format!("You reached level {} and healed +{} HP.", self.current_run.level_count, config::LEVEL_HEAL);
self.draw_string(str, text_x, text_top, ctx);
}
Some(PlayerAction::Move) =>
self.draw_string("You moved.".parse().unwrap(), 300.0, text_top, ctx),
self.draw_string("You moved.".parse().unwrap(), text_x, text_top, ctx),
Some(PlayerAction::Wait) =>
self.draw_string("You waited.".parse().unwrap(), 300.0, text_top, ctx),
self.draw_string("You waited.".parse().unwrap(), text_x, text_top, ctx),
Some(PlayerAction::Attack(damage_done, remaining_hp)) =>
self.draw_string(
format!("You attacked for {} damage. The enemy has {} hit points remaining.", damage_done, remaining_hp),
300.0,
text_x,
text_top,
ctx,
),
......@@ -486,12 +572,14 @@ impl GameState {
fn draw_enemy_action(&self, ctx: &mut Context) {
let text_top = GameState::calculate_text_top() + 25.0;
match self.current_run.combat_log.damage_taken {
Some(damage) =>
self.draw_string(format!("The enemy attacked you for {} damage.", damage), 300.0, text_top, ctx),
None => ()
let text_x = 500.0;
if let Some(PlayerAction::Level) = self.current_run.combat_log.player_action {
let hp = self.current_run.level.enemies[0].hp;
let wep = self.current_run.level.enemies[0].weapon_strength;
let str = self.current_run.level.enemies[0].strength;
self.draw_string(format!("Enemies now have {} HP and do {} + 1d{} damage", hp, str, wep), text_x, text_top, ctx);
} else if let Some(damage) = self.current_run.combat_log.damage_taken {
self.draw_string(format!("The enemy attacked you for {} damage.", damage), text_x, text_top, ctx);
};
}
......
use crate::game_state::tile::TreasureType;
#[derive(Debug)]
pub struct CombatLog {
pub player_action: Option<PlayerAction>,
pub damage_taken: Option<usize>
pub damage_taken: Option<usize>,
pub treasure_choice: Option<(TreasureType, TreasureType)>,
}
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum PlayerAction {
Move,
Wait,
Attack(usize, usize) // damage, remaining hp
Level,
Attack(usize, usize), // damage, remaining hp
}
\ No newline at end of file
......@@ -5,5 +5,13 @@ pub const BOARD_HEIGHT: usize = 28;
pub const PLAYER_STARTING_AP: usize = 1;
pub const PLAYER_STARTING_HP: usize = 10;
pub const PLAYER_STARTING_WEAPON: usize = 6;
pub const CHEST_COUNT: usize = 2;
pub const HEALTH_POTION: usize = 5;
pub const VITALITY: usize = 2;
pub const WEAPON: usize = 1;
pub const STRENGTH: usize = 1;
pub const LEVEL_HEAL: usize = 1;
pub const ROOM_NUMBER: usize = 20;
\ No newline at end of file
pub struct Enemy {
pub hp: usize
pub hp: usize,
pub strength: usize,
pub weapon_strength: usize,
}
impl Enemy {
pub fn new() -> Enemy {
pub fn new(difficulty: usize) -> Enemy {
let hp = (difficulty as f32 * 0.5 + 6.0).floor() as usize;
let strength = (difficulty as f32 * 0.5 + 1.0).floor() as usize;
let weapon_strength = (difficulty as f32 * 0.66 + 4.0).floor() as usize;
Enemy {
hp: 6
hp,
strength,
weapon_strength,
}
}
}
\ No newline at end of file
use crate::game_state::{config, Point};
use crate::game_state::enemy::Enemy;
use crate::game_state::tile::Tile;
use crate::game_state::tile::{Tile, TreasureType};
use rand::{thread_rng, Rng};
use tetra::math::Vec2;
use std::cmp;
use std::collections::HashMap;
use crate::game_state::tile::TreasureType::{Strength, Vitality, Health};
type Board = [[Tile; config::BOARD_HEIGHT as usize]; config::BOARD_WIDTH as usize];
......@@ -85,13 +86,13 @@ impl Level {
let mut result = Vec::new();
for _i in 0..enemy_count {
result.push(Enemy::new());
result.push(Enemy::new(difficulty));
}
result
}
fn calc_enemy_count(difficulty: usize) -> usize {
difficulty + 2
(difficulty as f32 * 1.5) as usize + 5
}
}
......@@ -99,26 +100,48 @@ impl Level {
impl Level {
fn create_board(enemy_count: usize) -> Board {
let board = [[Tile::Wall; config::BOARD_HEIGHT]; config::BOARD_WIDTH];
//let with_walls = Level::add_walls(board);
let with_rooms = Level::add_rooms(board);
let with_player = Level::add_player(with_rooms);
let with_enemies = Level::add_enemies(enemy_count, with_player);
let with_exit = Level::add_exit(with_enemies);
return with_exit;
let with_treasure = Level::add_treasure(with_exit);
return with_treasure;
}
fn add_walls(board: Board) -> Board {
fn add_treasure(board: Board) -> Board {
let mut board = board.clone();
for x in 0..board.len() {
for y in 0..board[x].len() {
let n = thread_rng().gen_range(0, 10);
if n < 2 {
board[x][y] = Tile::Wall;
for _i in 0..config::CHEST_COUNT {
board = Level::add_entity(Level::create_treasure_chest(), board);
}
board
}
fn create_treasure_chest() -> Tile {
const TREASURE_COUNT: usize = 2;
let mut treasures = [TreasureType::Health; TREASURE_COUNT];
for i in 0..TREASURE_COUNT {
let mut treasure = Level::randomize_treasure();
if i > 0 {
while treasure == treasures[i - 1] {
treasure = Level::randomize_treasure();
}
}
treasures[i] = treasure;
}
board
Tile::Treasure(treasures[0], treasures[1])
}
fn randomize_treasure() -> TreasureType {
let rnd = thread_rng().gen_range(0, 20);
match rnd {
0..=3 => TreasureType::Weapon,
4..=7 => Strength,
8..=12 => Vitality,
_ => Health
}
}
fn add_exit(board: Board) -> Board {
......@@ -171,8 +194,7 @@ impl Level {
}
if entity_position == 0 {
board[x][y] = entity;
println!("Add {:?} at {:?}, coords: {}, {}", entity, entity_position, x, y);
return board
return board;
}
}
}
......@@ -202,7 +224,7 @@ impl Level {
for y in 0..board[x].len() {
match board[x][y] {
Tile::Empty => empty_skipped += 1,
_ => {},
_ => {}
}
if empty_skipped == empty_position {
return Vec2::new(x, y);
......@@ -227,7 +249,7 @@ impl Room{
let y = thread_rng().gen_range(1, config::BOARD_HEIGHT - 2);
let w = thread_rng().gen_range(1, cmp::min(6, config::BOARD_WIDTH - x));
let h = thread_rng().gen_range(1, cmp::min(6, config::BOARD_HEIGHT - y));
Room{x,y,w,h,}
Room { x, y, w, h }
}
fn carve(&self, board: Board) -> Board {
let mut board = board.clone();
......@@ -250,7 +272,6 @@ impl Path{
Path { start, goal }
}
fn carve(&self, board: Board) -> Board {
let mut board = board.clone();
for pos in self.plot_path().iter() {
board[pos.x][pos.y] = Tile::Empty;
......@@ -267,5 +288,4 @@ impl Path{
}
path
}
}
pub struct Player {
pub current_hp: usize,
pub max_hp: usize,
pub attack_power: usize,
pub strength: usize,
pub weapon: usize,
}
impl Player {
pub fn new(attack_power: usize, hit_points: usize) -> Player {
pub fn new(strength: usize, hit_points: usize, weapon: usize) -> Player {
Player {
current_hp: hit_points,
max_hp: hit_points,
attack_power,
strength,
weapon,
}
}
}
\ No newline at end of file
......@@ -7,22 +7,24 @@ pub struct Run {
pub level_count: usize,
pub player: Player,
pub level: Level,
pub combat_log: CombatLog
pub combat_log: CombatLog,
}
impl Run {
pub fn new() -> Run {
let hp = config::PLAYER_STARTING_HP;
let ap = config::PLAYER_STARTING_AP;
let weapon = config::PLAYER_STARTING_WEAPON;
Run {
level_count: 0,
player: Player::new(ap, hp),
player: Player::new(ap, hp, weapon),
level: Level::new(0),
combat_log: CombatLog {
player_action: None,
damage_taken: None
}
damage_taken: None,
treasure_choice: None,
},
}
}
}
\ No newline at end of file
......@@ -8,6 +8,7 @@ pub struct Textures {
pub cross: Texture,
pub player: Texture,
pub enemy: Texture,
pub treasure: Texture,
}
impl Textures {
......@@ -19,6 +20,7 @@ impl Textures {
cross: Texture::new(ctx, "./resources/cross.png")?,
player: Texture::new(ctx, "./resources/player_new.png")?,
enemy: Texture::new(ctx, "./resources/foe.png")?,
treasure: Texture::new(ctx, "./resources/tile.png")?,
})
}
}
\ No newline at end of file
......@@ -3,7 +3,15 @@ pub enum Tile {
Enemy(usize),
Exit,
Empty,
Health,
Treasure(TreasureType, TreasureType),
Player,
Wall,
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum TreasureType {
Health,
Strength,
Weapon,
Vitality
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment