oakdex-battle 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,27 @@
1
+ require 'forwardable'
2
+
3
+ module Oakdex
4
+ class Battle
5
+ # Represents Pokemon Move with PP
6
+ class Move
7
+ extend Forwardable
8
+
9
+ attr_reader :max_pp
10
+ attr_accessor :pp
11
+
12
+ def_delegators :@move_type, :target, :priority, :accuracy,
13
+ :category, :power, :type, :stat_modifiers,
14
+ :in_battle_properties
15
+
16
+ def initialize(move_type, pp, max_pp)
17
+ @move_type = move_type
18
+ @pp = pp
19
+ @max_pp = max_pp
20
+ end
21
+
22
+ def name
23
+ @move_type.names['en']
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,148 @@
1
+ require 'forwardable'
2
+
3
+ module Oakdex
4
+ class Battle
5
+ # Represents one Action. One turn has many actions.
6
+ class MoveExecution
7
+ RECALL_PRIORITY = 6
8
+
9
+ extend Forwardable
10
+
11
+ def_delegators :@action, :battle, :turn, :pokemon,
12
+ :move, :trainer
13
+
14
+ attr_reader :action, :target
15
+
16
+ def initialize(action, target)
17
+ @action = action
18
+ @target = target
19
+ end
20
+
21
+ def hitting_probability
22
+ ((move.accuracy / 100.0) * (pokemon.accuracy / target.evasion)) * 1000
23
+ end
24
+
25
+ def hitting?
26
+ @hitting = rand(1..1000) <= hitting_probability ? 1 : 0
27
+ @hitting == 1
28
+ end
29
+
30
+ def execute
31
+ return if prevented_by_status_condition?
32
+ pokemon.change_pp_by(move.name, -1)
33
+ if hitting?
34
+ add_uses_move_log
35
+ execute_damage
36
+ execute_stat_modifiers
37
+ execute_status_conditions
38
+ execute_status_condition_callbacks
39
+ else
40
+ add_move_does_not_hit_log
41
+ end
42
+
43
+ battle.remove_fainted
44
+ end
45
+
46
+ private
47
+
48
+ def prevented_by_status_condition?
49
+ pokemon.status_conditions.reduce(false) do |res, condition|
50
+ res || condition.prevents_move?(self)
51
+ end
52
+ end
53
+
54
+ def execute_status_condition_callbacks
55
+ return if move.power.zero?
56
+ target.status_conditions.each do |c|
57
+ c.after_received_damage(self)
58
+ end
59
+ end
60
+
61
+ def execute_status_conditions
62
+ status_conditions.each do |condition|
63
+ execute_status_condition(condition)
64
+ end
65
+ end
66
+
67
+ def status_conditions
68
+ return [] if move.in_battle_properties.nil?
69
+ move.in_battle_properties['status_conditions'] || []
70
+ end
71
+
72
+ def execute_status_condition(condition)
73
+ return unless rand(1..100) <= condition['probability']
74
+ add_target_condition_added(condition['condition'])
75
+ target.add_status_condition(condition['condition'])
76
+ end
77
+
78
+ def execute_stat_modifiers
79
+ return if move.stat_modifiers.empty?
80
+ move.stat_modifiers.each do |stat_modifier|
81
+ modifier_target = stat_modifier['affects_user'] ? pokemon : target
82
+ stat = stat_modifier['stat']
83
+ stat = random_stat if stat == 'random'
84
+ if modifier_target.change_stat_by(stat.to_sym,
85
+ stat_modifier['change_by'])
86
+ add_changes_stat_log(modifier_target, stat,
87
+ stat_modifier['change_by'])
88
+ else
89
+ add_changes_no_stat_log(modifier_target, stat,
90
+ stat_modifier['change_by'])
91
+ end
92
+ end
93
+ end
94
+
95
+ def execute_damage
96
+ return if move.power.zero?
97
+ @damage = Damage.new(turn, self)
98
+ if @damage.damage > 0
99
+ add_received_damage_log
100
+ target.change_hp_by(-@damage.damage)
101
+ else
102
+ add_received_no_damage_log
103
+ end
104
+ end
105
+
106
+ def random_stat
107
+ (Pokemon::BATTLE_STATS + Pokemon::OTHER_STATS).sample
108
+ end
109
+
110
+ def add_log(*args)
111
+ battle.add_to_log(*args)
112
+ end
113
+
114
+ def add_changes_stat_log(target, stat, change_by)
115
+ add_log 'changes_stat', target.trainer.name, target.name,
116
+ stat, change_by
117
+ end
118
+
119
+ def add_changes_no_stat_log(target, stat, change_by)
120
+ add_log 'changes_no_stat', target.trainer.name, target.name,
121
+ stat, change_by
122
+ end
123
+
124
+ def add_target_condition_added(condition_name)
125
+ add_log 'target_condition_added', target.trainer.name, target.name,
126
+ condition_name
127
+ end
128
+
129
+ def add_uses_move_log
130
+ add_log 'uses_move', trainer.name, pokemon.name, move.name
131
+ end
132
+
133
+ def add_move_does_not_hit_log
134
+ add_log 'move_does_not_hit', trainer.name, pokemon.name, move.name
135
+ end
136
+
137
+ def add_received_damage_log
138
+ add_log 'received_damage', target.trainer.name, target.name,
139
+ move.name, @damage.damage
140
+ end
141
+
142
+ def add_received_no_damage_log
143
+ add_log 'received_no_damage', target.trainer.name, target.name,
144
+ move.name
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,168 @@
1
+ require 'forwardable'
2
+ require 'oakdex/battle/pokemon_stat'
3
+ require 'oakdex/battle/move'
4
+ require 'oakdex/battle/pokemon_factory'
5
+ require 'oakdex/battle/status_conditions'
6
+
7
+ module Oakdex
8
+ class Battle
9
+ # Represents detailed pokemon instance
10
+ class Pokemon
11
+ extend Forwardable
12
+
13
+ BATTLE_STATS = %i[hp atk def sp_atk sp_def speed]
14
+ OTHER_STATS = %i[accuracy evasion critical_hit]
15
+ STATUS_CONDITIONS = {
16
+ 'poison' => StatusConditions::Poison,
17
+ 'burn' => StatusConditions::Burn,
18
+ 'freeze' => StatusConditions::Freeze,
19
+ 'paralysis' => StatusConditions::Paralysis,
20
+ 'badly_poisoned' => StatusConditions::BadlyPoisoned,
21
+ 'sleep' => StatusConditions::Sleep
22
+ }
23
+
24
+ def_delegators :@species, :types
25
+
26
+ attr_accessor :trainer
27
+
28
+ def self.create(species_name, options = {})
29
+ species = Oakdex::Pokedex::Pokemon.find!(species_name)
30
+ Oakdex::Battle::PokemonFactory.create(species, options)
31
+ end
32
+
33
+ def initialize(species, attributes = {})
34
+ @species = species
35
+ @attributes = attributes
36
+ @attributes[:status_conditions] ||= []
37
+ reset_stats
38
+ end
39
+
40
+ def name
41
+ @species.names['en']
42
+ end
43
+
44
+ def moves
45
+ @attributes[:moves]
46
+ end
47
+
48
+ def current_hp
49
+ @attributes[:hp]
50
+ end
51
+
52
+ def status_conditions
53
+ @attributes[:status_conditions]
54
+ end
55
+
56
+ def moves_with_pp
57
+ moves.select { |m| m.pp > 0 }
58
+ end
59
+
60
+ def change_hp_by(hp_change)
61
+ @attributes[:hp] = if hp_change < 0
62
+ [@attributes[:hp] + hp_change, 0].max
63
+ else
64
+ [@attributes[:hp] + hp_change, hp].min
65
+ end
66
+ end
67
+
68
+ def change_pp_by(move_name, pp_change)
69
+ move = moves.find { |m| m.name == move_name }
70
+ return unless move
71
+ move.pp = if pp_change < 0
72
+ [move.pp + pp_change, 0].max
73
+ else
74
+ [move.pp + pp_change, move.max_pp].min
75
+ end
76
+ end
77
+
78
+ def change_stat_by(stat, change_by)
79
+ modifiers = stage_multipliers(stat)
80
+ stat_before = @stat_modifiers[stat]
81
+ min_value = modifiers.keys.first
82
+ max_value = modifiers.keys.last
83
+ @stat_modifiers[stat] = if change_by < 0
84
+ [stat_before + change_by, min_value].max
85
+ else
86
+ [stat_before + change_by, max_value].min
87
+ end
88
+ stat_before != @stat_modifiers[stat]
89
+ end
90
+
91
+ def add_status_condition(condition_name)
92
+ @attributes[:status_conditions] << status_condition(condition_name)
93
+ end
94
+
95
+ def remove_status_condition(condition)
96
+ @attributes[:status_conditions] = @attributes[:status_conditions]
97
+ .reject { |s| s == condition }
98
+ end
99
+
100
+ def reset_stats
101
+ @stat_modifiers = (BATTLE_STATS + OTHER_STATS - %i[hp]).map do |stat|
102
+ [stat, 0]
103
+ end.to_h
104
+ end
105
+
106
+ def level
107
+ PokemonStat.level_by_exp(@species.leveling_rate, @attributes[:exp])
108
+ end
109
+
110
+ def accuracy
111
+ stage(:accuracy)
112
+ end
113
+
114
+ def evasion
115
+ stage(:evasion)
116
+ end
117
+
118
+ def critical_hit_prob
119
+ stage(:critical_hit)
120
+ end
121
+
122
+ BATTLE_STATS.each do |stat|
123
+ define_method stat do
124
+ (initial_stat(stat) * stage(stat) *
125
+ status_condition_modifier(stat)).to_i
126
+ end
127
+ end
128
+
129
+ private
130
+
131
+ def status_condition_modifier(stat)
132
+ status_conditions.reduce(1.0) do |modifier, condition|
133
+ condition.stat_modifier(stat) * modifier
134
+ end
135
+ end
136
+
137
+ def status_condition(condition_name)
138
+ STATUS_CONDITIONS[condition_name].new(self)
139
+ end
140
+
141
+ def stage(stat)
142
+ multipliers = stage_multipliers(stat)
143
+ multipliers[@stat_modifiers[stat] || 0]
144
+ end
145
+
146
+ def initial_stat(stat)
147
+ PokemonStat.initial_stat(stat,
148
+ level: level,
149
+ nature: @attributes[:nature],
150
+ iv: @attributes[:iv],
151
+ ev: @attributes[:ev],
152
+ base_stats: @species.base_stats
153
+ )
154
+ end
155
+
156
+ def stage_multipliers(stat)
157
+ case stat
158
+ when :evasion, :accuracy
159
+ PokemonStat::STAGE_MULTIPLIERS_ACC_EVA
160
+ when :critical_hit
161
+ PokemonStat::STAGE_MULTIPLIERS_CRITICAL_HIT
162
+ else
163
+ PokemonStat::STAGE_MULTIPLIERS
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,118 @@
1
+ module Oakdex
2
+ class Battle
3
+ # Creates Pokemon instance and prefills attributes
4
+ class PokemonFactory
5
+ ATTRIBUTES = %i[exp gender ability nature hp iv ev moves]
6
+
7
+ class << self
8
+ def create(species, options = {})
9
+ factory = new(species, options)
10
+ attributes = Hash[ATTRIBUTES.map do |attr|
11
+ [attr, factory.send(attr)]
12
+ end]
13
+ Pokemon.new(species, attributes)
14
+ end
15
+ end
16
+
17
+ def initialize(species, options = {})
18
+ @species = species
19
+ @options = options
20
+ end
21
+
22
+ private
23
+
24
+ def moves
25
+ if @options[:moves]
26
+ @options[:moves].map do |move_data|
27
+ Move.new(
28
+ Oakdex::Pokedex::Move.find!(move_data[0]),
29
+ move_data[1],
30
+ move_data[2]
31
+ )
32
+ end
33
+ else
34
+ available_moves.sample(4).map do |move_name|
35
+ move_type = Oakdex::Pokedex::Move.find!(move_name)
36
+ Move.new(move_type, move_type.pp, move_type.pp)
37
+ end
38
+ end
39
+ end
40
+
41
+ def available_moves
42
+ @species.learnset.map do |m|
43
+ m['move'] if m['level'] && m['level'] <= level
44
+ end.compact
45
+ end
46
+
47
+ def ability
48
+ if @options['ability']
49
+ Oakdex::Pokedex::Ability.find!(@options['ability'])
50
+ else
51
+ Oakdex::Pokedex::Ability.find!(abilities.sample['name'])
52
+ end
53
+ end
54
+
55
+ def abilities
56
+ @species.abilities.select { |a| !a['hidden'] && !a['mega'] }
57
+ end
58
+
59
+ def exp
60
+ @options[:exp] || PokemonStat.exp_by_level(
61
+ @species.leveling_rate,
62
+ @options[:level]
63
+ )
64
+ end
65
+
66
+ def level
67
+ PokemonStat.level_by_exp(@species.leveling_rate, exp)
68
+ end
69
+
70
+ def hp
71
+ return @options[:hp] if @options[:hp]
72
+ PokemonStat.initial_stat(:hp,
73
+ level: level,
74
+ iv: iv,
75
+ ev: ev,
76
+ base_stats: @species.base_stats,
77
+ nature: nature
78
+ )
79
+ end
80
+
81
+ def iv
82
+ return @options[:iv] if @options[:iv]
83
+ @iv ||= Hash[Pokemon::BATTLE_STATS.map do |stat|
84
+ [stat, rand(0..31)]
85
+ end]
86
+ end
87
+
88
+ def ev
89
+ return @options[:ev] if @options[:ev]
90
+ @ev ||= Hash[Pokemon::BATTLE_STATS.map do |stat|
91
+ [stat, 0]
92
+ end]
93
+ end
94
+
95
+ def gender
96
+ return @options[:gender] if @options[:gender]
97
+ return 'neuter' unless @species.gender_ratios
98
+ calculate_gender
99
+ end
100
+
101
+ def calculate_gender
102
+ if rand(1..1000) <= @species.gender_ratios['male'] * 10
103
+ 'male'
104
+ else
105
+ 'female'
106
+ end
107
+ end
108
+
109
+ def nature(options = {})
110
+ @nature ||= if options[:nature]
111
+ Oakdex::Pokedex::Nature.find!(options[:nature])
112
+ else
113
+ Oakdex::Pokedex::Nature.all.values.sample
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end