gemwarrior 0.15.10 → 0.15.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,690 +1,690 @@
1
- # lib/gemwarrior/battle.rb
2
- # Monster battle
3
-
4
- require_relative 'game_options'
5
-
6
- module Gemwarrior
7
- class Battle
8
- # CONSTANTS
9
- ERROR_ATTACK_OPTION_INVALID = 'That will not do anything against the monster.'
10
- BEAST_MODE_ATTACK_LO = 100
11
- BEAST_MODE_ATTACK_HI = 200
12
- ATTEMPT_SUCCESS_LO_DEFAULT = 0
13
- ATTEMPT_SUCCESS_HI_DEFAULT = 100
14
- MISS_CAP_DEFAULT = 20
15
- LV4_ROCK_SLIDE_MOD_LO = 6
16
- LV5_MOD_LO = 7
17
- LV5_MOD_HI = 14
18
- ESCAPE_TEXT = '** POOF **'
19
-
20
- attr_accessor :world,
21
- :player,
22
- :monster,
23
- :player_is_defending
24
-
25
- def initialize(options)
26
- self.world = options.fetch(:world)
27
- self.player = options.fetch(:player)
28
- self.monster = options.fetch(:monster)
29
- self.player_is_defending = false
30
- end
31
-
32
- def start(is_arena = false, is_ambush = false)
33
- # begin battle!
34
- Audio.play_synth(:battle_start)
35
- print_battle_header unless is_arena
36
-
37
- # print opponent announcement, depending on reason for battle
38
- if is_arena
39
- print ' Your opponent is now...'
40
- Animation.run(phrase: "#{monster.name.upcase}", speed: :slow, oneline: true)
41
- print "!\n"
42
- elsif is_ambush
43
- puts " You are ambushed by #{monster.name}!".colorize(:yellow)
44
- else
45
- puts " You decide to attack #{monster.name}!"
46
- end
47
-
48
- puts " #{monster.name} cries out: \"#{monster.battlecry}\""
49
-
50
- # first strike! (unless arena or emerald)
51
- # shifty woman time, if emerald
52
- if monster.name.eql?('emerald')
53
- if world.shifty_to_jewel && !world.shifty_has_jeweled
54
- puts
55
- puts ' Suddenly, the Shifty Woman appears out of nowhere, and stands between you and Emerald!'
56
- STDIN.getc
57
- print ' '
58
- Person.new.speak('Hey, friend. Now it is time to finally get my revenge on ol\' Emer!')
59
- STDIN.getc
60
- puts ' She brandishes a now-sharpened-to-a-fine-point-and-glowing version of the Sand Jewel you gave to her and plunges it deep into Emerald\'s side, causing him to scream in agony.'
61
- puts
62
- print ' '
63
- Person.new.speak('THAT was for murdering my parents!')
64
- STDIN.getc
65
- puts ' She uses the hand not gripping the shiny blade to conjure up a small bolt of lightning, which she then flings directly at Emerald\'s chest.'
66
- puts
67
- print ' '
68
- Person.new.speak('And THAT was for stealing the ShinyThing(tm)!')
69
- STDIN.getc
70
- puts ' Emerald growls mightily, looking quite put off at the turn of events.'
71
- STDIN.getc
72
- puts ' He is not without strength, however, and pulls the weapon from his body with one hand while conjuring a fireball with his other, sending it right back at the Shifty Woman. Her glee at delivering a seemingly crushing blow is thwarted as she crumples to the floor.'
73
- STDIN.getc
74
- puts ' Both combatants are breathing heavily and groaning through their injuries. The Shifty Woman looks pretty hurt, and begins eyeing an exit from this mess.'
75
- STDIN.getc
76
- print ' '
77
- Person.new.speak('I may not have been able to finish you off, but my friend here will succeed where I and the land of Jool have failed. Goodbye and good luck!')
78
- puts ' The Shifty Woman regains her compsure just enough to limp off to a back door, disappearing.'
79
- STDIN.getc
80
-
81
- if GameOptions.data['debug_mode']
82
- puts
83
- puts ' Emerald Stats Before Altercation'
84
- puts " HP : #{monster.hp_cur}"
85
- puts " DEF : #{monster.defense}"
86
- puts " A_LO : #{monster.atk_lo}"
87
- puts " A_HI : #{monster.atk_hi}"
88
- end
89
-
90
- monster.hp_cur -= (monster.hp_cur * 0.3).floor
91
- monster.defense -= (monster.defense * 0.25).floor
92
- monster.atk_lo -= (monster.atk_lo * 0.2).floor
93
- monster.atk_hi -= (monster.atk_hi * 0.2).floor
94
-
95
- if GameOptions.data['debug_mode']
96
- puts
97
- puts ' Emerald Stats After Altercation'
98
- puts " HP : #{monster.hp_cur}"
99
- puts " DEF : #{monster.defense}"
100
- puts " A_LO : #{monster.atk_lo}"
101
- puts " A_HI : #{monster.atk_hi}"
102
- puts
103
- end
104
-
105
- puts ' Emerald has been wounded, but he is not done with this world yet. You approach him, wits about you, ready for battle.'
106
- world.shifty_has_jeweled = true
107
- end
108
- elsif monster_strikes_first?(is_arena, is_ambush)
109
- puts " #{monster.name} strikes first!".colorize(:yellow)
110
- monster_attacks_player
111
- end
112
-
113
- # LV6:STONE_FACE modifier (chance to auto-win)
114
- # Doesn't work against bosses, nor if battle is an event or in the arena
115
- if player.special_abilities.include?(:stone_face) && !monster.is_boss && !is_ambush && !is_arena
116
- level_diff = (player.level - monster.level) * 4
117
- chance_range = 0..(30 + level_diff)
118
- roll = rand(0..100)
119
-
120
- if GameOptions.data['debug_mode']
121
- puts
122
- puts " (MOD) LV6: Stone Face"
123
- puts " SUCCESS_RANGE: #{chance_range}"
124
- puts " SUCCESS_ROLL : #{roll}"
125
- end
126
-
127
- if chance_range.include?(roll)
128
- puts " You use your STONE FACE to tell the #{monster.name} you mean business, and #{monster.name} just gives up!".colorize(:green)
129
- return monster_death
130
- end
131
- end
132
-
133
- # main battle loop
134
- loop do
135
- skip_next_monster_attack = false
136
-
137
- # 1. check for death to end battle
138
- if monster_dead?
139
- result = monster_death
140
- return result
141
- elsif player_dead?
142
- player_death
143
- return 'death'
144
- end
145
-
146
- # 2. print general info and display options prompt
147
- print_near_death_info
148
- print_combatants_health_info
149
- print_battle_options_prompt
150
-
151
- # 3. get player action
152
- self.player_is_defending = false
153
- player_action = STDIN.gets.chomp.downcase
154
-
155
- # 4. parse player action
156
- case player_action
157
- when 'fight', 'f', 'attack', 'a'
158
- can_attack = true
159
-
160
- # gun exception
161
- if player.has_weapon_equipped?
162
- if player.inventory.weapon.name.eql?('gun')
163
- if player.inventory.contains_item?('bullet')
164
- player.inventory.remove_item('bullet')
165
- else
166
- puts ' Your gun is out of bullets! Running is your best option now.'.colorize(:red)
167
- can_attack = false
168
- end
169
- end
170
- end
171
-
172
- if can_attack
173
- puts " You attack #{monster.name}#{player.cur_weapon_name}!"
174
- dmg = calculate_damage(player, monster)
175
- if dmg > 0
176
- Audio.play_synth(:battle_player_attack)
177
-
178
- take_damage(monster, dmg)
179
- if monster_dead?
180
- result = monster_death
181
- return result
182
- end
183
- else
184
- Audio.play_synth(:battle_player_miss)
185
-
186
- puts ' You do no damage!'.colorize(:yellow)
187
- end
188
- end
189
- when 'defend', 'd'
190
- puts ' You dig in and defend this round.'
191
- self.player_is_defending = true
192
- when 'item', 'i'
193
- if player.inventory.is_empty?
194
- puts ' You have no items!'
195
- next
196
- elsif player.inventory.contains_battle_item?
197
- puts ' Which item do you want to use?'
198
- puts
199
- b_items = player.inventory.list_battle_items
200
- count = 0
201
- b_items.each do |bi|
202
- puts " (#{count + 1}) #{bi.name}"
203
- count += 1
204
- end
205
- puts ' (x) exit'
206
-
207
- loop do
208
- print_battle_prompt
209
- answer = gets.chomp.downcase
210
-
211
- case answer
212
- when 'x', 'q'
213
- skip_next_monster_attack = true
214
- break
215
- else
216
- begin
217
- item = b_items[answer.to_i - 1]
218
- if item
219
- result = item.use(world)
220
- player.hp_cur += result[:data]
221
- player.inventory.remove_item(item.name) if item.consumable
222
- break
223
- end
224
- rescue
225
- puts ' That is not a valid item choice.'
226
- puts
227
- next
228
- end
229
- end
230
- end
231
- else
232
- puts ' You have no battle items!'
233
- end
234
- when 'look', 'l'
235
- print " #{monster.name}".colorize(:white)
236
- print " (#{monster.hp_cur}/#{monster.hp_max} HP): #{monster.description}\n"
237
- puts " It has some distinguishing features, too: face is #{monster.face.colorize(:yellow)}, hands are #{monster.hands.colorize(:yellow)}, and mood, currently, is #{monster.mood.colorize(:yellow)}."
238
- if GameOptions.data['debug_mode']
239
- puts ' If defeated, will receive:'
240
- puts " >> XP : #{monster.xp}"
241
- puts " >> ROX : #{monster.rox}"
242
- puts " >> ITEMS: #{monster.inventory.contents}"
243
- next
244
- end
245
- when 'pass', 'p'
246
- puts ' You decide to pass your turn for some reason. Brave!'
247
- when 'run', 'r'
248
- if player_escape?(is_arena)
249
- monster.hp_cur = monster.hp_max
250
- puts " You successfully elude #{monster.name}!".colorize(:green)
251
- print_escape_text
252
- print_battle_line
253
- return
254
- else
255
- puts ' You were not able to run away! :-('.colorize(:yellow)
256
- end
257
- else
258
- puts " #{ERROR_ATTACK_OPTION_INVALID}"
259
- next
260
- end
261
-
262
- # 5. parse monster action
263
- monster_attacks_player unless skip_next_monster_attack
264
- end
265
- end
266
-
267
- private
268
-
269
- # NEUTRAL
270
- def calculate_damage(attacker, defender)
271
- # things that modify attack success and damage
272
- a_dex = attacker.dexterity
273
- d_dex = defender.dexterity
274
- d_def = defender.defense
275
-
276
- # player attacking monster
277
- if defender.eql?(monster)
278
- # attack success defined by attacker's and defender's dexterity
279
- attempt_success_lo = ATTEMPT_SUCCESS_LO_DEFAULT
280
- attempt_success_hi = ATTEMPT_SUCCESS_HI_DEFAULT
281
-
282
- # success range to hit based on dexterity
283
- attempt_success_hi += rand((a_dex)..(a_dex+2))
284
- attempt_success_hi -= rand((d_dex)..(d_dex+2))
285
-
286
- # weapon can change dexterity
287
- if attacker.has_weapon_equipped?
288
- attempt_success_hi += attacker.inventory.weapon.dex_mod
289
- end
290
-
291
- # compute attempt success
292
- attempt = rand(attempt_success_lo..attempt_success_hi)
293
- miss_cap = MISS_CAP_DEFAULT
294
-
295
- ######################
296
- # ACCURACY MODIFIERS #
297
- # vvvvvvvvvvvvvvvvvv #
298
- ######################
299
-
300
- ### LV5:GRANITON modifier (more accuracy)
301
- if player.special_abilities.include?(:graniton)
302
- miss_cap -= rand(LV5_MOD_LO..LV5_MOD_HI)
303
- end
304
-
305
- ######################
306
- # ^^^^^^^^^^^^^^^^^^ #
307
- # ACCURACY MODIFIERS #
308
- ######################
309
-
310
- # number to beat for attack success
311
- success_min = rand(miss_cap..miss_cap + 5)
312
-
313
- if GameOptions.data['debug_mode']
314
- puts
315
- puts ' Player Roll for Attack:'.colorize(:green)
316
- puts " ATTEMPT_RANGE: #{attempt_success_lo}..#{attempt_success_hi}"
317
- puts " MUST_BEAT : #{success_min}"
318
- if attempt > success_min
319
- puts " ATTEMPT_ROLL : #{attempt.to_s.colorize(:green)}"
320
- else
321
- puts " ATTEMPT_ROLL : #{attempt.to_s.colorize(:red)}"
322
- end
323
- end
324
-
325
- # no miss, so attack
326
- if attempt > success_min
327
- # base player damage range
328
- base_atk_lo = player.atk_lo
329
- base_atk_hi = player.atk_hi
330
-
331
- # weapon increases damage range
332
- if player.has_weapon_equipped?
333
- base_atk_lo += player.inventory.weapon.atk_lo
334
- base_atk_hi += player.inventory.weapon.atk_hi
335
- end
336
-
337
- # non-modified damage range
338
- dmg_range = base_atk_lo..base_atk_hi
339
-
340
- ####################
341
- # DAMAGE MODIFIERS #
342
- # vvvvvvvvvvvvvvvv #
343
- ####################
344
-
345
- ### DEBUG:BEAST_MODE modifier (ludicrously higher damage range)
346
- if GameOptions.data['beast_mode']
347
- dmg_range = BEAST_MODE_ATTACK_LO..BEAST_MODE_ATTACK_HI
348
- ### LV4:ROCK_SLIDE modifier (increased damage range)
349
- elsif player.special_abilities.include?(:rock_slide)
350
- lo_boost = rand(0..9)
351
- hi_boost = lo_boost + rand(5..10)
352
-
353
- if GameOptions.data['debug_mode']
354
- puts
355
- puts ' (MOD) LV4: Rock Slide'
356
- puts " Rock Slide lo_boost: #{lo_boost}"
357
- puts " Rock Slide hi_boost: #{hi_boost}"
358
- end
359
-
360
- if lo_boost >= LV4_ROCK_SLIDE_MOD_LO
361
- puts " You use Rock Slide for added damage!"
362
- dmg_range = (player.atk_lo + lo_boost)..(player.atk_hi + hi_boost)
363
- else
364
- puts " Rock Slide failed :(" if GameOptions.data['debug_mode']
365
- end
366
- end
367
-
368
- ####################
369
- # ^^^^^^^^^^^^^^^^ #
370
- # DAMAGE MODIFIERS #
371
- ####################
372
-
373
- # get base damage to apply
374
- base_dmg = rand(dmg_range)
375
-
376
- # lower value due to defender's defense
377
- dmg = base_dmg - d_def
378
-
379
- # 50% chance of doing 1 point of damage even if you were going to do no damage
380
- hail_mary = [dmg, 1].sample
381
- dmg = hail_mary if dmg <= 0
382
-
383
- if GameOptions.data['debug_mode']
384
- puts
385
- puts ' Player Roll for Damage:'.colorize(:green)
386
- puts " DMG_RANGE : #{dmg_range}"
387
- puts " DMG_ROLL : #{base_dmg}"
388
- puts " MONSTER_DEF: #{d_def}"
389
- puts " HAIL_MARY : #{hail_mary}"
390
- if dmg > 0
391
- puts " NET_DMG : #{dmg.to_s.colorize(:green)}"
392
- else
393
- puts " NET_DMG : #{dmg.to_s.colorize(:red)}"
394
- end
395
- end
396
-
397
- return dmg
398
- # missed? no damage
399
- else
400
- return 0
401
- end
402
- # monster attacking player
403
- else
404
- # attack success defined by attacker's and defender's dexterity
405
- attempt_success_lo = ATTEMPT_SUCCESS_LO_DEFAULT
406
- attempt_success_hi = ATTEMPT_SUCCESS_HI_DEFAULT
407
-
408
- # success range to hit based on dexterity
409
- attempt_success_hi += rand((a_dex)..(a_dex+2))
410
- attempt_success_hi -= rand((d_dex)..(d_dex+2))
411
-
412
- # compute attempt success
413
- attempt = rand(attempt_success_lo..attempt_success_hi)
414
- miss_cap = MISS_CAP_DEFAULT
415
-
416
- # number to beat for attack success
417
- success_min = rand(miss_cap..miss_cap + 5)
418
-
419
- if GameOptions.data['debug_mode']
420
- puts
421
- puts ' Monster Roll for Attack:'.colorize(:red)
422
- puts " ATTEMPT_RANGE: #{attempt_success_lo}..#{attempt_success_hi}"
423
- puts " MUST_BEAT : #{success_min}"
424
- if attempt > success_min
425
- puts " ATTEMPT_ROLL : #{attempt.to_s.colorize(:green)}"
426
- else
427
- puts " ATTEMPT_ROLL : #{attempt.to_s.colorize(:red)}"
428
- end
429
- end
430
-
431
- # no miss, so attack
432
- if attempt > success_min
433
- dmg_range = attacker.atk_lo..attacker.atk_lo
434
-
435
- # get base damage to apply
436
- base_dmg = rand(dmg_range)
437
-
438
- # lower value for defender's defense (and further if actively defending)
439
- if player_is_defending
440
- dmg = base_dmg - [(d_def * 1.5).floor, (d_def * 1.5).ceil].sample
441
- else
442
- dmg = base_dmg - d_def
443
- end
444
-
445
- # 25% chance of doing 1 point of damage even if monster was going to do no damage
446
- hail_mary = [dmg, dmg, dmg, 1].sample
447
- dmg = hail_mary if dmg <= 0
448
-
449
- if GameOptions.data['debug_mode']
450
- puts
451
- puts ' Monster Roll for Damage:'.colorize(:red)
452
- puts " DMG_RANGE : #{dmg_range}"
453
- puts " DMG_ROLL : #{base_dmg}"
454
- puts " PLAYER_DEF : #{d_def}"
455
- puts " HAIL_MARY : #{hail_mary}"
456
- if dmg > 0
457
- puts " NET_DMG : #{dmg.to_s.colorize(:green)}"
458
- else
459
- puts " NET_DMG : #{dmg.to_s.colorize(:red)}"
460
- end
461
- end
462
-
463
- return dmg
464
- # missed? no damage
465
- else
466
- return 0
467
- end
468
- end
469
- end
470
-
471
- def take_damage(entity, dmg)
472
- entity.hp_cur = entity.hp_cur.to_i - dmg.to_i
473
- who_gets_wounded_start = ''
474
-
475
- if entity.eql?(monster)
476
- who_gets_wounded_start = " > You wound #{monster.name} for ".colorize(:green)
477
- who_gets_wounded_end = " point(s)!\n".colorize(:green)
478
- else
479
- who_gets_wounded_start = ' > You are wounded for '.colorize(:red)
480
- who_gets_wounded_end = " point(s)!\n".colorize(:red)
481
- end
482
-
483
- print who_gets_wounded_start
484
- Animation.run(phrase: dmg.to_s, speed: :slow, oneline: true, alpha: false, random: false)
485
- print who_gets_wounded_end
486
- end
487
-
488
- # MONSTER
489
- def monster_strikes_first?(arena_battle = false, event_battle = false)
490
- if event_battle || arena_battle
491
- return false
492
- elsif player.special_abilities.include?(:stone_face)
493
- return false
494
- elsif (monster.dexterity > player.dexterity)
495
- if GameOptions.data['debug_mode']
496
- puts
497
- puts " MONSTER_DEX: #{monster.dexterity}, PLAYER_DEX: #{player.dexterity}"
498
- end
499
-
500
- return true
501
- else
502
- dex_diff = player.dexterity - monster.dexterity
503
- rand_dex = rand(0..dex_diff)
504
-
505
- if GameOptions.data['debug_mode']
506
- puts
507
- puts " DEX_DIFF : #{dex_diff}"
508
- puts " RAND_DEX : #{rand_dex}"
509
- puts " RAND_DEX % 2: #{rand_dex % 2}"
510
- end
511
-
512
- if rand_dex % 2 > 0
513
- return true
514
- else
515
- return false
516
- end
517
- end
518
- end
519
-
520
- def monster_attacks_player
521
- puts " #{monster.name} attacks you!".colorize(:yellow)
522
-
523
- dmg = calculate_damage(monster, player)
524
- if dmg > 0
525
- Audio.play_synth(:battle_monster_attack)
526
-
527
- take_damage(player, dmg)
528
- else
529
- Audio.play_synth(:battle_monster_miss)
530
-
531
- puts " #{monster.name} does no damage!".colorize(:yellow)
532
- end
533
- end
534
-
535
- def monster_near_death?
536
- ((monster.hp_cur.to_f / monster.hp_max.to_f) < 0.10)
537
- end
538
-
539
- def monster_dead?
540
- monster.hp_cur <= 0
541
- end
542
-
543
- def monster_death
544
- puts " YOU HAVE DEFEATED #{monster.name.upcase}!\n".colorize(:green)
545
-
546
- if monster.is_boss
547
- # stats
548
- world.player.bosses_killed += 1
549
-
550
- if monster.name.eql?('emerald')
551
- puts ' You just beat THE FINAL BOSS! Unbelievable!'
552
- puts
553
-
554
- reward_player(monster)
555
-
556
- # game ending: initiate
557
- return monster.initiate_ending(world)
558
- elsif monster.name.eql?('jaspern')
559
- Audio.play_synth(:battle_win_boss)
560
-
561
- puts ' You just beat a MINIBOSS! Fantastic!'
562
- puts
563
-
564
- reward_player(monster)
565
-
566
- # river bridge boss: initiate
567
- return monster.river_bridge_success(world)
568
- else
569
- Audio.play_synth(:battle_win_boss)
570
-
571
- reward_player(monster)
572
-
573
- world.location_by_coords(player.cur_coords).remove_monster(monster.name)
574
- end
575
- else
576
- Audio.play_synth(:battle_win_monster)
577
-
578
- # stats
579
- world.player.monsters_killed += 1
580
-
581
- reward_player(monster)
582
-
583
- world.location_by_coords(player.cur_coords).remove_monster(monster.name)
584
- end
585
- end
586
-
587
- def reward_player(monster)
588
- puts ' You get the following spoils of war:'
589
- puts " XP : #{monster.xp}".colorize(:green)
590
- puts " ROX : #{monster.rox}".colorize(:green)
591
- unless monster.inventory.nil?
592
- puts " ITEMS: #{monster.inventory.contents}".colorize(:green) unless monster.inventory.items.empty?
593
- end
594
- print_battle_line
595
- world.player.update_stats(reason: :monster, value: monster)
596
- puts
597
- end
598
-
599
- # PLAYER
600
- def player_near_death?
601
- ((player.hp_cur.to_f / player.hp_max.to_f) < 0.10 && !GameOptions.data['god_mode'])
602
- end
603
-
604
- def player_dead?
605
- (player.hp_cur <= 0 && !GameOptions.data['god_mode'])
606
- end
607
-
608
- def player_death
609
- puts " You are dead, slain by the #{monster.name}!".colorize(:red)
610
- print_battle_line
611
- end
612
-
613
- def player_escape?(is_arena)
614
- unless is_arena
615
- if player.dexterity > monster.dexterity
616
- return true
617
- else
618
- dex_diff = monster.dexterity - player.dexterity
619
- rand_dex = rand(0..dex_diff)
620
- rand_dex += rand(0..1) # slight extra chance modifier
621
-
622
- if rand_dex % 2 > 0
623
- return true
624
- else
625
- return false
626
- end
627
- end
628
- end
629
- false
630
- end
631
-
632
- # STATUS TEXT
633
- def print_near_death_info
634
- puts " You are almost dead!\n".colorize(:yellow) if player_near_death?
635
- puts " #{monster.name} is almost dead!\n".colorize(:yellow) if monster_near_death?
636
- puts
637
- end
638
-
639
- def print_combatants_health_info
640
- print " #{player.name.upcase.ljust(12).colorize(:green)} :: #{player.hp_cur.to_s.rjust(3)} HP"
641
- print " (LVL: #{player.level})" if GameOptions.data['debug_mode']
642
- print "\n"
643
-
644
- print " #{monster.name.upcase.ljust(12).colorize(:red)} :: "
645
- if GameOptions.data['debug_mode'] || player.special_abilities.include?(:rocking_vision)
646
- print "#{monster.hp_cur.to_s.rjust(3)}"
647
- else
648
- print '???'
649
- end
650
- print ' HP'
651
- print " (LVL: #{monster.level})" if GameOptions.data['debug_mode']
652
- print "\n"
653
- puts
654
- end
655
-
656
- def print_escape_text
657
- print ' '
658
- Animation.run(phrase: ESCAPE_TEXT, oneline: false)
659
- end
660
-
661
- def print_battle_line
662
- GameOptions.data['wrap_width'].times { print '*'.colorize(background: :red, color: :white) }
663
- print "\n"
664
- end
665
-
666
- def print_battle_options_prompt
667
- puts ' What do you do?'
668
- print ' '
669
- print "#{'['.colorize(:yellow)}"
670
- print "#{'F'.colorize(:green)}#{'ight]['.colorize(:yellow)}"
671
- print "#{'D'.colorize(:green)}#{'efend]['.colorize(:yellow)}"
672
- print "#{'L'.colorize(:green)}#{'ook]['.colorize(:yellow)}"
673
- print "#{'I'.colorize(:green)}#{'tem]['.colorize(:yellow)}"
674
- print "#{'P'.colorize(:green)}#{'ass]['.colorize(:yellow)}"
675
- print "#{'R'.colorize(:green)}#{'un]'.colorize(:yellow)}"
676
- print "\n"
677
- print_battle_prompt
678
- end
679
-
680
- def print_battle_prompt
681
- print ' [BATTLE]> '
682
- end
683
-
684
- def print_battle_header
685
- print_battle_line
686
- puts ' BATTLE BEGINS!'.ljust(GameOptions.data['wrap_width']).colorize(background: :red, color: :white)
687
- print_battle_line
688
- end
689
- end
690
- end
1
+ # lib/gemwarrior/battle.rb
2
+ # Monster battle
3
+
4
+ require_relative 'game_options'
5
+
6
+ module Gemwarrior
7
+ class Battle
8
+ # CONSTANTS
9
+ ERROR_ATTACK_OPTION_INVALID = 'That will not do anything against the monster.'
10
+ BEAST_MODE_ATTACK_LO = 100
11
+ BEAST_MODE_ATTACK_HI = 200
12
+ ATTEMPT_SUCCESS_LO_DEFAULT = 0
13
+ ATTEMPT_SUCCESS_HI_DEFAULT = 100
14
+ MISS_CAP_DEFAULT = 20
15
+ LV4_ROCK_SLIDE_MOD_LO = 6
16
+ LV5_MOD_LO = 7
17
+ LV5_MOD_HI = 14
18
+ ESCAPE_TEXT = '** POOF **'
19
+
20
+ attr_accessor :world,
21
+ :player,
22
+ :monster,
23
+ :player_is_defending
24
+
25
+ def initialize(options)
26
+ self.world = options.fetch(:world)
27
+ self.player = options.fetch(:player)
28
+ self.monster = options.fetch(:monster)
29
+ self.player_is_defending = false
30
+ end
31
+
32
+ def start(is_arena = false, is_ambush = false)
33
+ # begin battle!
34
+ Audio.play_synth(:battle_start)
35
+ print_battle_header unless is_arena
36
+
37
+ # print opponent announcement, depending on reason for battle
38
+ if is_arena
39
+ print ' Your opponent is now...'
40
+ Animation.run(phrase: "#{monster.name.upcase}", speed: :slow, oneline: true)
41
+ print "!\n"
42
+ elsif is_ambush
43
+ puts " You are ambushed by #{monster.name}!".colorize(:yellow)
44
+ else
45
+ puts " You decide to attack #{monster.name}!"
46
+ end
47
+
48
+ puts " #{monster.name} cries out: \"#{monster.battlecry}\""
49
+
50
+ # first strike! (unless arena or emerald)
51
+ # shifty woman time, if emerald
52
+ if monster.name.eql?('emerald')
53
+ if world.shifty_to_jewel && !world.shifty_has_jeweled
54
+ puts
55
+ puts ' Suddenly, the Shifty Woman appears out of nowhere, and stands between you and Emerald!'
56
+ STDIN.getc
57
+ print ' '
58
+ Person.new.speak('Hey, friend. Now it is time to finally get my revenge on ol\' Emer!')
59
+ STDIN.getc
60
+ puts ' She brandishes a now-sharpened-to-a-fine-point-and-glowing version of the Sand Jewel you gave to her and plunges it deep into Emerald\'s side, causing him to scream in agony.'
61
+ puts
62
+ print ' '
63
+ Person.new.speak('THAT was for murdering my parents!')
64
+ STDIN.getc
65
+ puts ' She uses the hand not gripping the shiny blade to conjure up a small bolt of lightning, which she then flings directly at Emerald\'s chest.'
66
+ puts
67
+ print ' '
68
+ Person.new.speak('And THAT was for stealing the ShinyThing(tm)!')
69
+ STDIN.getc
70
+ puts ' Emerald growls mightily, looking quite put off at the turn of events.'
71
+ STDIN.getc
72
+ puts ' He is not without strength, however, and pulls the weapon from his body with one hand while conjuring a fireball with his other, sending it right back at the Shifty Woman. Her glee at delivering a seemingly crushing blow is thwarted as she crumples to the floor.'
73
+ STDIN.getc
74
+ puts ' Both combatants are breathing heavily and groaning through their injuries. The Shifty Woman looks pretty hurt, and begins eyeing an exit from this mess.'
75
+ STDIN.getc
76
+ print ' '
77
+ Person.new.speak('I may not have been able to finish you off, but my friend here will succeed where I and the land of Jool have failed. Goodbye and good luck!')
78
+ puts ' The Shifty Woman regains her compsure just enough to limp off to a back door, disappearing.'
79
+ STDIN.getc
80
+
81
+ if GameOptions.data['debug_mode']
82
+ puts
83
+ puts ' Emerald Stats Before Altercation'
84
+ puts " HP : #{monster.hp_cur}"
85
+ puts " DEF : #{monster.defense}"
86
+ puts " A_LO : #{monster.atk_lo}"
87
+ puts " A_HI : #{monster.atk_hi}"
88
+ end
89
+
90
+ monster.hp_cur -= (monster.hp_cur * 0.3).floor
91
+ monster.defense -= (monster.defense * 0.25).floor
92
+ monster.atk_lo -= (monster.atk_lo * 0.2).floor
93
+ monster.atk_hi -= (monster.atk_hi * 0.2).floor
94
+
95
+ if GameOptions.data['debug_mode']
96
+ puts
97
+ puts ' Emerald Stats After Altercation'
98
+ puts " HP : #{monster.hp_cur}"
99
+ puts " DEF : #{monster.defense}"
100
+ puts " A_LO : #{monster.atk_lo}"
101
+ puts " A_HI : #{monster.atk_hi}"
102
+ puts
103
+ end
104
+
105
+ puts ' Emerald has been wounded, but he is not done with this world yet. You approach him, wits about you, ready for battle.'
106
+ world.shifty_has_jeweled = true
107
+ end
108
+ elsif monster_strikes_first?(is_arena, is_ambush)
109
+ puts " #{monster.name} strikes first!".colorize(:yellow)
110
+ monster_attacks_player
111
+ end
112
+
113
+ # LV6:STONE_FACE modifier (chance to auto-win)
114
+ # Doesn't work against bosses, nor if battle is an event or in the arena
115
+ if player.special_abilities.include?(:stone_face) && !monster.is_boss && !is_ambush && !is_arena
116
+ level_diff = (player.level - monster.level) * 4
117
+ chance_range = 0..(30 + level_diff)
118
+ roll = rand(0..100)
119
+
120
+ if GameOptions.data['debug_mode']
121
+ puts
122
+ puts " (MOD) LV6: Stone Face"
123
+ puts " SUCCESS_RANGE: #{chance_range}"
124
+ puts " SUCCESS_ROLL : #{roll}"
125
+ end
126
+
127
+ if chance_range.include?(roll)
128
+ puts " You use your STONE FACE to tell the #{monster.name} you mean business, and #{monster.name} just gives up!".colorize(:green)
129
+ return monster_death
130
+ end
131
+ end
132
+
133
+ # main battle loop
134
+ loop do
135
+ skip_next_monster_attack = false
136
+
137
+ # 1. check for death to end battle
138
+ if monster_dead?
139
+ result = monster_death
140
+ return result
141
+ elsif player_dead?
142
+ player_death
143
+ return 'death'
144
+ end
145
+
146
+ # 2. print general info and display options prompt
147
+ print_near_death_info
148
+ print_combatants_health_info
149
+ print_battle_options_prompt
150
+
151
+ # 3. get player action
152
+ self.player_is_defending = false
153
+ player_action = STDIN.gets.chomp.downcase
154
+
155
+ # 4. parse player action
156
+ case player_action
157
+ when 'fight', 'f', 'attack', 'a'
158
+ can_attack = true
159
+
160
+ # gun exception
161
+ if player.has_weapon_equipped?
162
+ if player.inventory.weapon.name.eql?('gun')
163
+ if player.inventory.contains_item?('bullet')
164
+ player.inventory.remove_item('bullet')
165
+ else
166
+ puts ' Your gun is out of bullets! Running is your best option now.'.colorize(:red)
167
+ can_attack = false
168
+ end
169
+ end
170
+ end
171
+
172
+ if can_attack
173
+ puts " You attack #{monster.name}#{player.cur_weapon_name}!"
174
+ dmg = calculate_damage(player, monster)
175
+ if dmg > 0
176
+ Audio.play_synth(:battle_player_attack)
177
+
178
+ take_damage(monster, dmg)
179
+ if monster_dead?
180
+ result = monster_death
181
+ return result
182
+ end
183
+ else
184
+ Audio.play_synth(:battle_player_miss)
185
+
186
+ puts ' You do no damage!'.colorize(:yellow)
187
+ end
188
+ end
189
+ when 'defend', 'd'
190
+ puts ' You dig in and defend this round.'
191
+ self.player_is_defending = true
192
+ when 'item', 'i'
193
+ if player.inventory.is_empty?
194
+ puts ' You have no items!'
195
+ next
196
+ elsif player.inventory.contains_battle_item?
197
+ puts ' Which item do you want to use?'
198
+ puts
199
+ b_items = player.inventory.list_battle_items
200
+ count = 0
201
+ b_items.each do |bi|
202
+ puts " (#{count + 1}) #{bi.name}"
203
+ count += 1
204
+ end
205
+ puts ' (x) exit'
206
+
207
+ loop do
208
+ print_battle_prompt
209
+ answer = gets.chomp.downcase
210
+
211
+ case answer
212
+ when 'x', 'q'
213
+ skip_next_monster_attack = true
214
+ break
215
+ else
216
+ begin
217
+ item = b_items[answer.to_i - 1]
218
+ if item
219
+ result = item.use(world)
220
+ player.hp_cur += result[:data]
221
+ player.inventory.remove_item(item.name) if item.consumable
222
+ break
223
+ end
224
+ rescue
225
+ puts ' That is not a valid item choice.'
226
+ puts
227
+ next
228
+ end
229
+ end
230
+ end
231
+ else
232
+ puts ' You have no battle items!'
233
+ end
234
+ when 'look', 'l'
235
+ print " #{monster.name}".colorize(:white)
236
+ print " (#{monster.hp_cur}/#{monster.hp_max} HP): #{monster.description}\n"
237
+ puts " It has some distinguishing features, too: face is #{monster.face.colorize(:yellow)}, hands are #{monster.hands.colorize(:yellow)}, and mood, currently, is #{monster.mood.colorize(:yellow)}."
238
+ if GameOptions.data['debug_mode']
239
+ puts ' If defeated, will receive:'
240
+ puts " >> XP : #{monster.xp}"
241
+ puts " >> ROX : #{monster.rox}"
242
+ puts " >> ITEMS: #{monster.inventory.contents}"
243
+ next
244
+ end
245
+ when 'pass', 'p'
246
+ puts ' You decide to pass your turn for some reason. Brave!'
247
+ when 'run', 'r'
248
+ if player_escape?(is_arena)
249
+ monster.hp_cur = monster.hp_max
250
+ puts " You successfully elude #{monster.name}!".colorize(:green)
251
+ print_escape_text
252
+ print_battle_line
253
+ return
254
+ else
255
+ puts ' You were not able to run away! :-('.colorize(:yellow)
256
+ end
257
+ else
258
+ puts " #{ERROR_ATTACK_OPTION_INVALID}"
259
+ next
260
+ end
261
+
262
+ # 5. parse monster action
263
+ monster_attacks_player unless skip_next_monster_attack
264
+ end
265
+ end
266
+
267
+ private
268
+
269
+ # NEUTRAL
270
+ def calculate_damage(attacker, defender)
271
+ # things that modify attack success and damage
272
+ a_dex = attacker.dexterity
273
+ d_dex = defender.dexterity
274
+ d_def = defender.defense
275
+
276
+ # player attacking monster
277
+ if defender.eql?(monster)
278
+ # attack success defined by attacker's and defender's dexterity
279
+ attempt_success_lo = ATTEMPT_SUCCESS_LO_DEFAULT
280
+ attempt_success_hi = ATTEMPT_SUCCESS_HI_DEFAULT
281
+
282
+ # success range to hit based on dexterity
283
+ attempt_success_hi += rand((a_dex)..(a_dex+2))
284
+ attempt_success_hi -= rand((d_dex)..(d_dex+2))
285
+
286
+ # weapon can change dexterity
287
+ if attacker.has_weapon_equipped?
288
+ attempt_success_hi += attacker.inventory.weapon.dex_mod
289
+ end
290
+
291
+ # compute attempt success
292
+ attempt = rand(attempt_success_lo..attempt_success_hi)
293
+ miss_cap = MISS_CAP_DEFAULT
294
+
295
+ ######################
296
+ # ACCURACY MODIFIERS #
297
+ # vvvvvvvvvvvvvvvvvv #
298
+ ######################
299
+
300
+ ### LV5:GRANITON modifier (more accuracy)
301
+ if player.special_abilities.include?(:graniton)
302
+ miss_cap -= rand(LV5_MOD_LO..LV5_MOD_HI)
303
+ end
304
+
305
+ ######################
306
+ # ^^^^^^^^^^^^^^^^^^ #
307
+ # ACCURACY MODIFIERS #
308
+ ######################
309
+
310
+ # number to beat for attack success
311
+ success_min = rand(miss_cap..miss_cap + 5)
312
+
313
+ if GameOptions.data['debug_mode']
314
+ puts
315
+ puts ' Player Roll for Attack:'.colorize(:green)
316
+ puts " ATTEMPT_RANGE: #{attempt_success_lo}..#{attempt_success_hi}"
317
+ puts " MUST_BEAT : #{success_min}"
318
+ if attempt > success_min
319
+ puts " ATTEMPT_ROLL : #{attempt.to_s.colorize(:green)}"
320
+ else
321
+ puts " ATTEMPT_ROLL : #{attempt.to_s.colorize(:red)}"
322
+ end
323
+ end
324
+
325
+ # no miss, so attack
326
+ if attempt > success_min
327
+ # base player damage range
328
+ base_atk_lo = player.atk_lo
329
+ base_atk_hi = player.atk_hi
330
+
331
+ # weapon increases damage range
332
+ if player.has_weapon_equipped?
333
+ base_atk_lo += player.inventory.weapon.atk_lo
334
+ base_atk_hi += player.inventory.weapon.atk_hi
335
+ end
336
+
337
+ # non-modified damage range
338
+ dmg_range = base_atk_lo..base_atk_hi
339
+
340
+ ####################
341
+ # DAMAGE MODIFIERS #
342
+ # vvvvvvvvvvvvvvvv #
343
+ ####################
344
+
345
+ ### DEBUG:BEAST_MODE modifier (ludicrously higher damage range)
346
+ if GameOptions.data['beast_mode']
347
+ dmg_range = BEAST_MODE_ATTACK_LO..BEAST_MODE_ATTACK_HI
348
+ ### LV4:ROCK_SLIDE modifier (increased damage range)
349
+ elsif player.special_abilities.include?(:rock_slide)
350
+ lo_boost = rand(0..9)
351
+ hi_boost = lo_boost + rand(5..10)
352
+
353
+ if GameOptions.data['debug_mode']
354
+ puts
355
+ puts ' (MOD) LV4: Rock Slide'
356
+ puts " Rock Slide lo_boost: #{lo_boost}"
357
+ puts " Rock Slide hi_boost: #{hi_boost}"
358
+ end
359
+
360
+ if lo_boost >= LV4_ROCK_SLIDE_MOD_LO
361
+ puts " You use Rock Slide for added damage!"
362
+ dmg_range = (player.atk_lo + lo_boost)..(player.atk_hi + hi_boost)
363
+ else
364
+ puts " Rock Slide failed :(" if GameOptions.data['debug_mode']
365
+ end
366
+ end
367
+
368
+ ####################
369
+ # ^^^^^^^^^^^^^^^^ #
370
+ # DAMAGE MODIFIERS #
371
+ ####################
372
+
373
+ # get base damage to apply
374
+ base_dmg = rand(dmg_range)
375
+
376
+ # lower value due to defender's defense
377
+ dmg = base_dmg - d_def
378
+
379
+ # 50% chance of doing 1 point of damage even if you were going to do no damage
380
+ hail_mary = [dmg, 1].sample
381
+ dmg = hail_mary if dmg <= 0
382
+
383
+ if GameOptions.data['debug_mode']
384
+ puts
385
+ puts ' Player Roll for Damage:'.colorize(:green)
386
+ puts " DMG_RANGE : #{dmg_range}"
387
+ puts " DMG_ROLL : #{base_dmg}"
388
+ puts " MONSTER_DEF: #{d_def}"
389
+ puts " HAIL_MARY : #{hail_mary}"
390
+ if dmg > 0
391
+ puts " NET_DMG : #{dmg.to_s.colorize(:green)}"
392
+ else
393
+ puts " NET_DMG : #{dmg.to_s.colorize(:red)}"
394
+ end
395
+ end
396
+
397
+ return dmg
398
+ # missed? no damage
399
+ else
400
+ return 0
401
+ end
402
+ # monster attacking player
403
+ else
404
+ # attack success defined by attacker's and defender's dexterity
405
+ attempt_success_lo = ATTEMPT_SUCCESS_LO_DEFAULT
406
+ attempt_success_hi = ATTEMPT_SUCCESS_HI_DEFAULT
407
+
408
+ # success range to hit based on dexterity
409
+ attempt_success_hi += rand((a_dex)..(a_dex+2))
410
+ attempt_success_hi -= rand((d_dex)..(d_dex+2))
411
+
412
+ # compute attempt success
413
+ attempt = rand(attempt_success_lo..attempt_success_hi)
414
+ miss_cap = MISS_CAP_DEFAULT
415
+
416
+ # number to beat for attack success
417
+ success_min = rand(miss_cap..miss_cap + 5)
418
+
419
+ if GameOptions.data['debug_mode']
420
+ puts
421
+ puts ' Monster Roll for Attack:'.colorize(:red)
422
+ puts " ATTEMPT_RANGE: #{attempt_success_lo}..#{attempt_success_hi}"
423
+ puts " MUST_BEAT : #{success_min}"
424
+ if attempt > success_min
425
+ puts " ATTEMPT_ROLL : #{attempt.to_s.colorize(:green)}"
426
+ else
427
+ puts " ATTEMPT_ROLL : #{attempt.to_s.colorize(:red)}"
428
+ end
429
+ end
430
+
431
+ # no miss, so attack
432
+ if attempt > success_min
433
+ dmg_range = attacker.atk_lo..attacker.atk_lo
434
+
435
+ # get base damage to apply
436
+ base_dmg = rand(dmg_range)
437
+
438
+ # lower value for defender's defense (and further if actively defending)
439
+ if player_is_defending
440
+ dmg = base_dmg - [(d_def * 1.5).floor, (d_def * 1.5).ceil].sample
441
+ else
442
+ dmg = base_dmg - d_def
443
+ end
444
+
445
+ # 25% chance of doing 1 point of damage even if monster was going to do no damage
446
+ hail_mary = [dmg, dmg, dmg, 1].sample
447
+ dmg = hail_mary if dmg <= 0
448
+
449
+ if GameOptions.data['debug_mode']
450
+ puts
451
+ puts ' Monster Roll for Damage:'.colorize(:red)
452
+ puts " DMG_RANGE : #{dmg_range}"
453
+ puts " DMG_ROLL : #{base_dmg}"
454
+ puts " PLAYER_DEF : #{d_def}"
455
+ puts " HAIL_MARY : #{hail_mary}"
456
+ if dmg > 0
457
+ puts " NET_DMG : #{dmg.to_s.colorize(:green)}"
458
+ else
459
+ puts " NET_DMG : #{dmg.to_s.colorize(:red)}"
460
+ end
461
+ end
462
+
463
+ return dmg
464
+ # missed? no damage
465
+ else
466
+ return 0
467
+ end
468
+ end
469
+ end
470
+
471
+ def take_damage(entity, dmg)
472
+ entity.hp_cur = entity.hp_cur.to_i - dmg.to_i
473
+ who_gets_wounded_start = ''
474
+
475
+ if entity.eql?(monster)
476
+ who_gets_wounded_start = " > You wound #{monster.name} for ".colorize(:green)
477
+ who_gets_wounded_end = " point(s)!\n".colorize(:green)
478
+ else
479
+ who_gets_wounded_start = ' > You are wounded for '.colorize(:red)
480
+ who_gets_wounded_end = " point(s)!\n".colorize(:red)
481
+ end
482
+
483
+ print who_gets_wounded_start
484
+ Animation.run(phrase: dmg.to_s, speed: :slow, oneline: true, alpha: false, random: false)
485
+ print who_gets_wounded_end
486
+ end
487
+
488
+ # MONSTER
489
+ def monster_strikes_first?(arena_battle = false, event_battle = false)
490
+ if event_battle || arena_battle
491
+ return false
492
+ elsif player.special_abilities.include?(:stone_face)
493
+ return false
494
+ elsif (monster.dexterity > player.dexterity)
495
+ if GameOptions.data['debug_mode']
496
+ puts
497
+ puts " MONSTER_DEX: #{monster.dexterity}, PLAYER_DEX: #{player.dexterity}"
498
+ end
499
+
500
+ return true
501
+ else
502
+ dex_diff = player.dexterity - monster.dexterity
503
+ rand_dex = rand(0..dex_diff)
504
+
505
+ if GameOptions.data['debug_mode']
506
+ puts
507
+ puts " DEX_DIFF : #{dex_diff}"
508
+ puts " RAND_DEX : #{rand_dex}"
509
+ puts " RAND_DEX % 2: #{rand_dex % 2}"
510
+ end
511
+
512
+ if rand_dex % 2 > 0
513
+ return true
514
+ else
515
+ return false
516
+ end
517
+ end
518
+ end
519
+
520
+ def monster_attacks_player
521
+ puts " #{monster.name} attacks you!".colorize(:yellow)
522
+
523
+ dmg = calculate_damage(monster, player)
524
+ if dmg > 0
525
+ Audio.play_synth(:battle_monster_attack)
526
+
527
+ take_damage(player, dmg)
528
+ else
529
+ Audio.play_synth(:battle_monster_miss)
530
+
531
+ puts " #{monster.name} does no damage!".colorize(:yellow)
532
+ end
533
+ end
534
+
535
+ def monster_near_death?
536
+ ((monster.hp_cur.to_f / monster.hp_max.to_f) < 0.10)
537
+ end
538
+
539
+ def monster_dead?
540
+ monster.hp_cur <= 0
541
+ end
542
+
543
+ def monster_death
544
+ puts " YOU HAVE DEFEATED #{monster.name.upcase}!\n".colorize(:green)
545
+
546
+ if monster.is_boss
547
+ # stats
548
+ world.player.bosses_killed += 1
549
+
550
+ if monster.name.eql?('emerald')
551
+ puts ' You just beat THE FINAL BOSS! Unbelievable!'
552
+ puts
553
+
554
+ reward_player(monster)
555
+
556
+ # game ending: initiate
557
+ return monster.initiate_ending(world)
558
+ elsif monster.name.eql?('jaspern')
559
+ Audio.play_synth(:battle_win_boss)
560
+
561
+ puts ' You just beat a MINIBOSS! Fantastic!'
562
+ puts
563
+
564
+ reward_player(monster)
565
+
566
+ # river bridge boss: initiate
567
+ return monster.river_bridge_success(world)
568
+ else
569
+ Audio.play_synth(:battle_win_boss)
570
+
571
+ reward_player(monster)
572
+
573
+ world.location_by_coords(player.cur_coords).remove_monster(monster.name)
574
+ end
575
+ else
576
+ Audio.play_synth(:battle_win_monster)
577
+
578
+ # stats
579
+ world.player.monsters_killed += 1
580
+
581
+ reward_player(monster)
582
+
583
+ world.location_by_coords(player.cur_coords).remove_monster(monster.name)
584
+ end
585
+ end
586
+
587
+ def reward_player(monster)
588
+ puts ' You get the following spoils of war:'
589
+ puts " XP : #{monster.xp}".colorize(:green)
590
+ puts " ROX : #{monster.rox}".colorize(:green)
591
+ unless monster.inventory.nil?
592
+ puts " ITEMS: #{monster.inventory.contents}".colorize(:green) unless monster.inventory.items.empty?
593
+ end
594
+ print_battle_line
595
+ world.player.update_stats(reason: :monster, value: monster)
596
+ puts
597
+ end
598
+
599
+ # PLAYER
600
+ def player_near_death?
601
+ ((player.hp_cur.to_f / player.hp_max.to_f) < 0.10 && !GameOptions.data['god_mode'])
602
+ end
603
+
604
+ def player_dead?
605
+ (player.hp_cur <= 0 && !GameOptions.data['god_mode'])
606
+ end
607
+
608
+ def player_death
609
+ puts " You are dead, slain by the #{monster.name}!".colorize(:red)
610
+ print_battle_line
611
+ end
612
+
613
+ def player_escape?(is_arena)
614
+ unless is_arena
615
+ if player.dexterity > monster.dexterity
616
+ return true
617
+ else
618
+ dex_diff = monster.dexterity - player.dexterity
619
+ rand_dex = rand(0..dex_diff)
620
+ rand_dex += rand(0..1) # slight extra chance modifier
621
+
622
+ if rand_dex % 2 > 0
623
+ return true
624
+ else
625
+ return false
626
+ end
627
+ end
628
+ end
629
+ false
630
+ end
631
+
632
+ # STATUS TEXT
633
+ def print_near_death_info
634
+ puts " You are almost dead!\n".colorize(:yellow) if player_near_death?
635
+ puts " #{monster.name} is almost dead!\n".colorize(:yellow) if monster_near_death?
636
+ puts
637
+ end
638
+
639
+ def print_combatants_health_info
640
+ print " #{player.name.upcase.ljust(12).colorize(:green)} :: #{player.hp_cur.to_s.rjust(3)} HP"
641
+ print " (LVL: #{player.level})" if GameOptions.data['debug_mode']
642
+ print "\n"
643
+
644
+ print " #{monster.name.upcase.ljust(12).colorize(:red)} :: "
645
+ if GameOptions.data['debug_mode'] || player.special_abilities.include?(:rocking_vision)
646
+ print "#{monster.hp_cur.to_s.rjust(3)}"
647
+ else
648
+ print '???'
649
+ end
650
+ print ' HP'
651
+ print " (LVL: #{monster.level})" if GameOptions.data['debug_mode']
652
+ print "\n"
653
+ puts
654
+ end
655
+
656
+ def print_escape_text
657
+ print ' '
658
+ Animation.run(phrase: ESCAPE_TEXT, oneline: false)
659
+ end
660
+
661
+ def print_battle_line
662
+ GameOptions.data['wrap_width'].times { print '*'.colorize(background: :red, color: :white) }
663
+ print "\n"
664
+ end
665
+
666
+ def print_battle_options_prompt
667
+ puts ' What do you do?'
668
+ print ' '
669
+ print "#{'['.colorize(:yellow)}"
670
+ print "#{'F'.colorize(:green)}#{'ight]['.colorize(:yellow)}"
671
+ print "#{'D'.colorize(:green)}#{'efend]['.colorize(:yellow)}"
672
+ print "#{'L'.colorize(:green)}#{'ook]['.colorize(:yellow)}"
673
+ print "#{'I'.colorize(:green)}#{'tem]['.colorize(:yellow)}"
674
+ print "#{'P'.colorize(:green)}#{'ass]['.colorize(:yellow)}"
675
+ print "#{'R'.colorize(:green)}#{'un]'.colorize(:yellow)}"
676
+ print "\n"
677
+ print_battle_prompt
678
+ end
679
+
680
+ def print_battle_prompt
681
+ print ' [BATTLE]> '
682
+ end
683
+
684
+ def print_battle_header
685
+ print_battle_line
686
+ puts ' BATTLE BEGINS!'.ljust(GameOptions.data['wrap_width']).colorize(background: :red, color: :white)
687
+ print_battle_line
688
+ end
689
+ end
690
+ end