# lib/gemwarrior/battle.rb # Monster battle require_relative 'misc/player_levels' require_relative 'game_options' module Gemwarrior class Battle # CONSTANTS ERROR_ATTACK_OPTION_INVALID = 'That will not do anything against the monster.' BEAST_MODE_ATTACK = 100 ESCAPE_TEXT = ' ** POOF **' attr_accessor :world, :player, :monster, :player_is_defending def initialize(options) self.world = options.fetch(:world) self.player = options.fetch(:player) self.monster = options.fetch(:monster) self.player_is_defending = false end def start(is_arena = nil, is_event = nil) Music::cue([ { frequencies: 'G4', duration: 50 }, { frequencies: 'G#4', duration: 50 }, { frequencies: 'G4', duration: 50 }, { frequencies: 'G#4', duration: 50 }, { frequencies: 'G4', duration: 50 }, { frequencies: 'G#4', duration: 50 } ]) # begin battle! print_battle_header unless is_arena if is_arena print ' Your opponent is now...' Animation::run(phrase: "#{monster.name.upcase}!", speed: :slow) elsif is_event puts " You are ambushed by #{monster.name}!".colorize(:yellow) else puts " You decide to attack #{monster.name}!" end puts " #{monster.name} cries out: \"#{monster.battlecry}\"" # first strike! unless is_arena if monster_strikes_first?(is_event) puts " #{monster.name} strikes first!".colorize(:yellow) monster_attacks_player end end # main battle loop loop do if monster_dead? result = monster_death return result elsif player_dead? player_death return 'death' end # check for near death if player_near_death? puts " You are almost dead!\n".colorize(:yellow) end if monster_near_death? puts " #{monster.name} is almost dead!\n".colorize(:yellow) end puts # print health info print " #{player.name.upcase.ljust(12)} :: #{player.hp_cur.to_s.rjust(3)} HP" print " (LVL: #{player.level})" if GameOptions.data['debug_mode'] print "\n" print " #{monster.name.upcase.ljust(12)} :: " if GameOptions.data['debug_mode'] || player.special_abilities.include?(:rocking_vision) print "#{monster.hp_cur.to_s.rjust(3)}" else print '???' end print ' HP' print " (LVL: #{monster.level})" if GameOptions.data['debug_mode'] print "\n" puts self.player_is_defending = false # battle options prompt puts ' What do you do?' print ' ['.colorize(:yellow) print 'F'.colorize(:green) print 'ight/'.colorize(:yellow) print 'A'.colorize(:green) print 'ttack]['.colorize(:yellow) print 'D'.colorize(:green) print 'efend]['.colorize(:yellow) print 'L'.colorize(:green) print 'ook]['.colorize(:yellow) print 'P'.colorize(:green) print 'ass]['.colorize(:yellow) print 'R'.colorize(:green) print 'un]'.colorize(:yellow) print "\n" print ' [BATTLE]> ' player_action = STDIN.gets.chomp.downcase # player action case player_action when 'fight', 'f', 'attack', 'a' puts " You attack #{monster.name}#{player.cur_weapon_name}!" dmg = calculate_damage_to(monster) if dmg > 0 Music::cue([{ frequencies: 'A4,E4,B5', duration: 75 }]) take_damage(monster, dmg) if monster_dead? result = monster_death return result end else Music::cue([{ frequencies: 'A4', duration: 75 }]) puts ' You miss entirely!'.colorize(:yellow) end when 'defend', 'd' puts ' You dig in and defend this round.' self.player_is_defending = true when 'pass', 'p' puts ' You decide to pass your turn for some reason. Brave!' when 'look', 'l' print " #{monster.name}".colorize(:white) print " (#{monster.hp_cur}/#{monster.hp_max} HP): #{monster.description}\n" puts " It has some distinguishing features, too: face is #{monster.face}, hands are #{monster.hands}, and general mood is #{monster.mood}." if GameOptions.data['debug_mode'] puts ' If defeated, will receive:' puts " >> XP : #{monster.xp}" puts " >> ROX : #{monster.rox}" puts " >> ITEMS: #{monster.inventory.list_contents}" next end when 'run', 'r' if player_escape?(is_arena) monster.hp_cur = monster.hp_max puts " You successfully elude #{monster.name}!".colorize(:green) print_escape_text print_battle_line return 'escaped' else puts ' You were not able to run away! :-('.colorize(:yellow) end else puts ERROR_ATTACK_OPTION_INVALID next end # monster action monster_attacks_player end end private # NEUTRAL def calculate_damage_to(entity) miss = rand(0..(100 + entity.defense)) if miss < 15 0 else if entity.eql?(monster) # base attack range base_atk_lo = player.atk_lo base_atk_hi = player.atk_hi if player.has_weapon_equipped? base_atk_lo += player.inventory.weapon.atk_lo base_atk_hi += player.inventory.weapon.atk_hi end atk_range = base_atk_lo..base_atk_hi # beast mode modifier if GameOptions.data['beast_mode'] atk_range = BEAST_MODE_ATTACK..BEAST_MODE_ATTACK # level 3 ability modifier elsif player.special_abilities.include?(:rock_slide) lo_boost = rand(0..9) if lo_boost >= 7 puts "#{player.name} uses Rock Slide for added damage!" hi_boost = lo_boost + rand(0..5) atk_range = (player.atk_lo + lo_boost)..(player.atk_hi + hi_boost) end end # return attack range return rand(atk_range) else dmg = rand(monster.atk_lo..monster.atk_hi) dmg -= (entity.defense / 2) if player_is_defending return dmg end end end def take_damage(entity, dmg) entity.hp_cur = entity.hp_cur.to_i - dmg.to_i who_gets_wounded_start = '' if entity.eql?(monster) who_gets_wounded_start = " > You wound #{monster.name} for " who_gets_wounded_end = " point(s)!\n".colorize(:green) else who_gets_wounded_start = ' > You are wounded for '.colorize(:red) who_gets_wounded_end = " point(s)!\n".colorize(:red) end print who_gets_wounded_start Animation::run(phrase: dmg.to_s, speed: :slow, oneline: true, alpha: false, random: false) print who_gets_wounded_end end # MONSTER def monster_strikes_first?(is_event = nil) if (monster.dexterity > player.dexterity) || is_event return true else dex_diff = player.dexterity - monster.dexterity rand_dex = rand(0..dex_diff) if rand_dex % 2 > 0 return true else return false end end end def monster_attacks_player puts " #{monster.name} attacks you!".colorize(:yellow) dmg = calculate_damage_to(player) if dmg > 0 Music::cue([{ frequencies: 'B4,E#5,A5', duration: 75 }]) take_damage(player, dmg) else Music::cue([{ frequencies: 'B4', duration: 75 }]) puts " #{monster.name} misses entirely!".colorize(:yellow) end end def monster_near_death? ((monster.hp_cur.to_f / monster.hp_max.to_f) < 0.10) end def monster_dead? monster.hp_cur <= 0 end def monster_death puts " YOU HAVE DEFEATED #{monster.name.upcase}!\n".colorize(:green) # stats world.player.monsters_killed += 1 if monster.is_boss # end game boss! if monster.name.eql?('Emerald') Music::cue([ { frequencies: 'G3', duration: 250 }, { frequencies: 'A3', duration: 50 }, { frequencies: 'B3', duration: 50 }, { frequencies: 'C4', duration: 50 }, { frequencies: 'D4', duration: 250 }, { frequencies: 'E4', duration: 50 }, { frequencies: 'F#4', duration: 50 }, { frequencies: 'G4', duration: 50 }, { frequencies: 'A4', duration: 250 }, { frequencies: 'B4', duration: 50 }, { frequencies: 'C5', duration: 50 }, { frequencies: 'D5', duration: 50 }, { frequencies: 'E5', duration: 50 }, { frequencies: 'F#5', duration: 50 }, { frequencies: 'G5', duration: 1000 } ]) puts monster.defeated_text gets return 'exit' else puts ' You just beat a boss monster. Way to go!' puts " XP : #{monster.xp}".colorize(:green) puts " ROX: #{monster.rox}".colorize(:green) print_battle_line player.update_stats(reason: :monster, value: monster) world.location_by_coords(player.cur_coords).remove_monster(monster.name) end else puts ' You get the following spoils of war:' puts " XP : #{monster.xp}".colorize(:green) puts " ROX : #{monster.rox}".colorize(:green) unless monster.inventory.nil? puts " ITEMS: #{monster.inventory.list_contents}".colorize(:green) unless monster.inventory.items.empty? end print_battle_line player.update_stats(reason: :monster, value: monster) world.location_by_coords(player.cur_coords).remove_monster(monster.name) end end # PLAYER def player_near_death? ((player.hp_cur.to_f / player.hp_max.to_f) < 0.10 && !GameOptions.data['god_mode']) end def player_dead? (player.hp_cur <= 0 && !GameOptions.data['god_mode']) end def player_death puts " You are dead, slain by the #{monster.name}!".colorize(:red) print_battle_line end def player_escape?(is_arena) unless is_arena if player.dexterity > monster.dexterity return true else dex_diff = monster.dexterity - player.dexterity rand_dex = rand(0..dex_diff) rand_dex += rand(0..1) # slight extra chance modifier if rand_dex % 2 > 0 return true else return false end end end return false end # STATUS TEXT def print_escape_text Animation::run(phrase: ESCAPE_TEXT, oneline: false) end def print_battle_line GameOptions.data['wrap_width'].times { print '*'.colorize(background: :red, color: :white) } print "\n" end def print_battle_header print_battle_line puts ' BATTLE BEGINS!'.ljust(GameOptions.data['wrap_width']).colorize(background: :red, color: :white) print_battle_line end end end