oakdex-battle 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +103 -0
- data/lib/oakdex.rb +3 -0
- data/lib/oakdex/battle.rb +111 -0
- data/lib/oakdex/battle/action.rb +115 -0
- data/lib/oakdex/battle/damage.rb +99 -0
- data/lib/oakdex/battle/in_battle_pokemon.rb +159 -0
- data/lib/oakdex/battle/move.rb +27 -0
- data/lib/oakdex/battle/move_execution.rb +148 -0
- data/lib/oakdex/battle/pokemon.rb +168 -0
- data/lib/oakdex/battle/pokemon_factory.rb +118 -0
- data/lib/oakdex/battle/pokemon_stat.rb +109 -0
- data/lib/oakdex/battle/side.rb +76 -0
- data/lib/oakdex/battle/status_conditions.rb +16 -0
- data/lib/oakdex/battle/status_conditions/badly_poisoned.rb +36 -0
- data/lib/oakdex/battle/status_conditions/base.rb +36 -0
- data/lib/oakdex/battle/status_conditions/burn.rb +26 -0
- data/lib/oakdex/battle/status_conditions/freeze.rb +36 -0
- data/lib/oakdex/battle/status_conditions/non_volatile.rb +12 -0
- data/lib/oakdex/battle/status_conditions/paralysis.rb +26 -0
- data/lib/oakdex/battle/status_conditions/poison.rb +22 -0
- data/lib/oakdex/battle/status_conditions/sleep.rb +37 -0
- data/lib/oakdex/battle/trainer.rb +50 -0
- data/lib/oakdex/battle/turn.rb +70 -0
- data/lib/oakdex/battle/valid_action_service.rb +89 -0
- data/lib/oakdex/battle/version.rb +5 -0
- metadata +82 -0
@@ -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
|