gemwarrior 0.15.10 → 0.15.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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