hlockey 5 → 7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/data/election.json +23 -0
- data/data/infinileague.json +189 -0
- data/data/information.json +6 -0
- data/data/league.json +2042 -0
- data/data/links.json +5 -0
- data/data/messages.json +46 -0
- data/data/previous_election_results.json +91 -0
- data/lib/hlockey/actions.rb +22 -0
- data/lib/hlockey/data.rb +6 -5
- data/lib/hlockey/game/fight.rb +100 -40
- data/lib/hlockey/game/weather/audacity.rb +1 -1
- data/lib/hlockey/game/weather/chicken.rb +3 -2
- data/lib/hlockey/game/weather/incline.rb +2 -1
- data/lib/hlockey/game/weather/stars.rb +1 -1
- data/lib/hlockey/game/weather/sunset.rb +2 -4
- data/lib/hlockey/game/weather/waves.rb +9 -5
- data/lib/hlockey/game/weather/weatherable.rb +9 -9
- data/lib/hlockey/game.rb +182 -116
- data/lib/hlockey/league.rb +90 -73
- data/lib/hlockey/message.rb +23 -153
- data/lib/hlockey/mod/fencebuilder.rb +15 -0
- data/lib/hlockey/mod/fencedestroyer.rb +16 -0
- data/lib/hlockey/mod/handholding.rb +57 -0
- data/lib/hlockey/mod/immaterial.rb +28 -0
- data/lib/hlockey/mod/locked.rb +28 -0
- data/lib/hlockey/mod/moddable.rb +70 -0
- data/lib/hlockey/mod/nonconfrontational.rb +17 -0
- data/lib/hlockey/mod/powernapper.rb +50 -0
- data/lib/hlockey/mod/punchy.rb +21 -0
- data/lib/hlockey/mod.rb +25 -0
- data/lib/hlockey/selfdescribable.rb +7 -0
- data/lib/hlockey/team/player.rb +40 -58
- data/lib/hlockey/team/stadium.rb +10 -7
- data/lib/hlockey/team.rb +56 -41
- data/lib/hlockey/utils.rb +36 -4
- data/lib/hlockey/version.rb +1 -1
- data/lib/hlockey.rb +15 -0
- metadata +30 -18
- data/data/election.yaml +0 -21
- data/data/external/names.txt +0 -19948
- data/data/information.yaml +0 -24
- data/data/league.yaml +0 -1694
- data/data/links.yaml +0 -3
- data/data/previous_election_results.yaml +0 -65
- data/lib/hlockey/game/actions.rb +0 -24
data/lib/hlockey/game.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
require("hlockey/game/actions")
|
2
1
|
require("hlockey/game/fight")
|
3
2
|
require("hlockey/game/weather")
|
3
|
+
require("hlockey/actions")
|
4
4
|
require("hlockey/constants")
|
5
5
|
require("hlockey/message")
|
6
6
|
require("hlockey/utils")
|
@@ -14,9 +14,12 @@ module Hlockey
|
|
14
14
|
# @return [Team] the teams in the game
|
15
15
|
attr_reader(:home, :away)
|
16
16
|
|
17
|
-
# @return [
|
17
|
+
# @return [Utils::Rng] the RNG the game is getting numbers from
|
18
18
|
attr_reader(:prng)
|
19
19
|
|
20
|
+
# @return [Hash<Symbol => Integer>] the score of each team
|
21
|
+
attr_reader(:score)
|
22
|
+
|
20
23
|
# @return [Team::Stadium] the stadium this game is located at
|
21
24
|
attr_reader(:stadium)
|
22
25
|
|
@@ -26,11 +29,9 @@ module Hlockey
|
|
26
29
|
# @return [Array<Message>] messages about game events
|
27
30
|
attr_reader(:stream)
|
28
31
|
|
29
|
-
# @return [Hash<Symbol => Integer>] the score of each team
|
30
|
-
attr_reader(:score)
|
31
|
-
|
32
32
|
# @return [Boolean] if the game is still in progress
|
33
33
|
attr_reader(:in_progress)
|
34
|
+
alias in_progress? in_progress
|
34
35
|
|
35
36
|
# @return [Integer] what period the game is in
|
36
37
|
attr_reader(:period)
|
@@ -38,20 +39,31 @@ module Hlockey
|
|
38
39
|
# @return [Fight, nil] the current fight, if there is any
|
39
40
|
attr_reader(:fight)
|
40
41
|
|
42
|
+
# @return [Hash<Player => Hash<Symbol => Number>>] stats of players before changes
|
43
|
+
attr_reader(:pre_tmp_change_stats)
|
44
|
+
|
41
45
|
# @param home [Team]
|
42
46
|
# @param away [Team]
|
43
|
-
# @param
|
44
|
-
|
47
|
+
# @param rng_seed [Integer] number to seed the game's RNG with
|
48
|
+
# @param weather [Class<Weatherable>]
|
49
|
+
def initialize(home, away, rng_seed, weather = nil)
|
45
50
|
@home = home
|
46
51
|
@away = away
|
47
|
-
@prng =
|
52
|
+
@prng = Utils::Rng.new(rng_seed)
|
53
|
+
|
48
54
|
@stadium = @home.stadium
|
49
|
-
|
50
|
-
|
55
|
+
|
56
|
+
if weather.nil?
|
57
|
+
weather = Weather::WEATHERS.sample(random: prng)
|
58
|
+
@rng_seed = @prng.state
|
59
|
+
end
|
60
|
+
|
61
|
+
@weather = weather.new(self)
|
51
62
|
@score = { home: 0, away: 0 }
|
52
63
|
@in_progress = true
|
53
64
|
@actions = 0
|
54
65
|
@period = 1
|
66
|
+
@stream = [Message.new(:game_start)]
|
55
67
|
@faceoff = true
|
56
68
|
@faceoff_won = false
|
57
69
|
@team_with_puck = nil
|
@@ -60,38 +72,140 @@ module Hlockey
|
|
60
72
|
@shooting_chance = 0
|
61
73
|
@fight = nil
|
62
74
|
@fight_chance = 0
|
63
|
-
@
|
64
|
-
@partying_teams = [@home, @away].select {
|
75
|
+
@pre_tmp_change_stats = {}
|
76
|
+
@partying_teams = [@home, @away].select { _1.status == :partying }
|
77
|
+
@do_final_cleanup = false
|
65
78
|
|
79
|
+
set_mods_game
|
66
80
|
@weather.on_game_start
|
67
81
|
end
|
68
82
|
|
69
|
-
# @return [
|
70
|
-
def
|
71
|
-
|
83
|
+
# @return [Hash]
|
84
|
+
def to_h(simple: false, stream_start_idx: 0, show_events: true, do_color: false)
|
85
|
+
res = {
|
86
|
+
home: @home.name,
|
87
|
+
away: @away.name,
|
88
|
+
stadium: @stadium.to_s,
|
89
|
+
weather: @weather.to_s,
|
90
|
+
score: @score,
|
91
|
+
in_progress: @in_progress,
|
92
|
+
period: @period
|
93
|
+
}
|
94
|
+
unless simple
|
95
|
+
if show_events
|
96
|
+
res[:events] = @stream[stream_start_idx..]&.map(&:event)
|
97
|
+
else
|
98
|
+
res[:stream] = @stream[stream_start_idx..]&.map { _1.to_s(do_color:) }
|
99
|
+
end
|
100
|
+
|
101
|
+
res[:team_with_puck] = @team_with_puck&.name
|
102
|
+
res[:shooting_chance] = @shooting_chance
|
103
|
+
end
|
104
|
+
|
105
|
+
res
|
72
106
|
end
|
73
107
|
|
108
|
+
# @return [String]
|
109
|
+
def to_s(do_color: true) =
|
110
|
+
"#{Message.color(@home, do_color:)} vs #{Message.color(@away, do_color:)}"
|
111
|
+
|
74
112
|
# Update the game state by one action
|
75
113
|
def update
|
76
|
-
|
114
|
+
if @do_final_cleanup
|
115
|
+
@in_progress = false
|
116
|
+
set_mods_game(unset: true)
|
117
|
+
return
|
118
|
+
end
|
77
119
|
|
78
120
|
do_action
|
79
121
|
handle_parties
|
80
122
|
end
|
81
123
|
|
124
|
+
# Makes a player pass in the game, regardless of if it would normally occur
|
125
|
+
def pass
|
126
|
+
sender = puck_holder
|
127
|
+
receiver_pos = pass_reciever_pos
|
128
|
+
interceptor_pos = defensive_pos
|
129
|
+
|
130
|
+
@stream << Message.new(:game_pass,
|
131
|
+
sender:,
|
132
|
+
receiver: @team_with_puck.roster[receiver_pos])
|
133
|
+
|
134
|
+
if !@faceoff && try_take_puck(interceptor_pos, dis: 10 - @shooting_chance)
|
135
|
+
@stream << Message.new(:game_pass_intercept,
|
136
|
+
sender:,
|
137
|
+
receiver: @puckless_team.roster[receiver_pos],
|
138
|
+
interceptor: @team_with_puck.roster[interceptor_pos])
|
139
|
+
return
|
140
|
+
end
|
141
|
+
|
142
|
+
@puck_holder_pos = receiver_pos
|
143
|
+
@shooting_chance += 1
|
144
|
+
end
|
145
|
+
|
146
|
+
# Makes a player check in the game, regardless of if it would normally occur
|
147
|
+
def check
|
148
|
+
defender_pos = defensive_pos
|
149
|
+
defender = @puckless_team.roster[defender_pos]
|
150
|
+
|
151
|
+
@stream << Message.new(:game_hit, defender:, puck_holder:)
|
152
|
+
if !puck_holder.mods_do(:on_got_hit, defender) &&
|
153
|
+
try_take_puck(defender_pos, stat: :defense)
|
154
|
+
@stream << Message.new(:game_possession_change,
|
155
|
+
player: defender, new_puck_team: defender.team)
|
156
|
+
end
|
157
|
+
|
158
|
+
@fight_chance += 0.1
|
159
|
+
end
|
160
|
+
|
161
|
+
# Makes a player shoot in the game, regardless of if it would normally occur
|
162
|
+
# @param audacity [Boolean] if the function was called by Audacity weather
|
163
|
+
def shoot(audacity: false)
|
164
|
+
return if @puck_holder_pos.nil?
|
165
|
+
|
166
|
+
shooter = puck_holder
|
167
|
+
|
168
|
+
@stream << Message.new(audacity ? :game_shot_audacious : :game_shot, shooter:)
|
169
|
+
|
170
|
+
blocking_pos = @prng.rand(2).zero? ? :ldef : :rdef
|
171
|
+
return if try_block_shot(blocking_pos) || try_block_shot(:goalie)
|
172
|
+
|
173
|
+
# Goal scored
|
174
|
+
|
175
|
+
scoring_team = @team_with_puck == @home ? :home : :away
|
176
|
+
@score[scoring_team] += 1
|
177
|
+
weather.on_goal(scoring_team)
|
178
|
+
|
179
|
+
@stream << Message.new(:game_shot_scored, shooter:)
|
180
|
+
|
181
|
+
start_fight if @prng.rand(3 + @fight_chance) > 3
|
182
|
+
|
183
|
+
start_faceoff
|
184
|
+
@actions = ACTIONS_PER_PERIOD - 1 if @period > NON_OT_PERIODS # Sudden death OT
|
185
|
+
end
|
186
|
+
|
187
|
+
# Starts a fight in the game
|
188
|
+
# @param starting_player [Player, nil] the player that started the fight (optional)
|
189
|
+
def start_fight(starting_player = nil)
|
190
|
+
@fight = Fight.new(self, starting_player)
|
191
|
+
@stream << Message.new(:fight_start,
|
192
|
+
home_player: @fight.players[:home].first,
|
193
|
+
away_player: @fight.players[:away].first)
|
194
|
+
end
|
195
|
+
|
82
196
|
private
|
83
197
|
|
84
198
|
# Does an action in the game
|
85
199
|
def do_action
|
86
|
-
@stream << Message.
|
200
|
+
@stream << Message.new(:game_period_start, period: @period) if @actions.zero?
|
87
201
|
@actions += 1
|
88
202
|
|
89
203
|
@weather.on_action
|
204
|
+
(@home.roster.values + @away.roster.values).each { _1.mods_do(:on_action) }
|
90
205
|
|
91
206
|
unless @fight.nil?
|
92
|
-
|
93
|
-
@stream
|
94
|
-
return unless fight_message.event == :FightEnded
|
207
|
+
@fight.next_action
|
208
|
+
return unless @stream.last.event == :fight_end
|
95
209
|
|
96
210
|
morale_change_amount = (@fight.score[:home] - @fight.score[:away]) / 10.0
|
97
211
|
change_morale(@home, morale_change_amount)
|
@@ -128,86 +242,30 @@ module Hlockey
|
|
128
242
|
player = team.roster.values.sample(random: @prng)
|
129
243
|
stat = player.stats.keys.sample(random: @prng)
|
130
244
|
player.stats[stat] += 0.1
|
131
|
-
@stream << Message.
|
132
|
-
next if @
|
245
|
+
@stream << Message.new(:game_partying, player:)
|
246
|
+
next if @pre_tmp_change_stats[player].nil?
|
133
247
|
|
134
|
-
@
|
248
|
+
@pre_tmp_change_stats[player][stat] += 0.1
|
135
249
|
end
|
136
250
|
end
|
137
251
|
|
138
|
-
# Makes a player pass in the game, regardless of if it would normally occur
|
139
|
-
def pass
|
140
|
-
sender = puck_holder
|
141
|
-
receiver = pass_reciever
|
142
|
-
interceptor = defensive_pos
|
143
|
-
|
144
|
-
if !@faceoff && try_take_puck(interceptor, 7 - @shooting_chance, :agility)
|
145
|
-
@stream << Message.Pass(sender,
|
146
|
-
@puckless_team.roster[receiver],
|
147
|
-
@shooting_chance,
|
148
|
-
@team_with_puck.roster[interceptor],
|
149
|
-
@team_with_puck)
|
150
|
-
return
|
151
|
-
end
|
152
|
-
|
153
|
-
@puck_holder_pos = receiver
|
154
|
-
@shooting_chance += 1
|
155
|
-
@stream << Message.Pass(sender, @team_with_puck.roster[receiver], @shooting_chance)
|
156
|
-
end
|
157
|
-
|
158
|
-
# Makes a player check in the game, regardless of if it would normally occur
|
159
|
-
def check
|
160
|
-
defender_pos = defensive_pos
|
161
|
-
defender = @puckless_team.roster[defender_pos]
|
162
|
-
@stream << Message.Hit(puck_holder, defender,
|
163
|
-
try_take_puck(defender_pos), @team_with_puck,
|
164
|
-
@shooting_chance)
|
165
|
-
@fight_chance += 0.1
|
166
|
-
end
|
167
|
-
|
168
|
-
# Makes a player shoot in the game, regardless of if it would normally occur
|
169
|
-
# This is called outside of the class by Audacity weather (using #send)
|
170
|
-
# @param audacity [Boolean] if the function was called by Audacity weather
|
171
|
-
def shoot(audacity: false)
|
172
|
-
return if @puck_holder_pos.nil?
|
173
|
-
|
174
|
-
if @shooting_chance < 5 &&
|
175
|
-
try_block_shot(@prng.rand(2).zero? ? :ldef : :rdef, audacity: audacity)
|
176
|
-
return
|
177
|
-
end
|
178
|
-
return if try_block_shot(:goalie, audacity: audacity)
|
179
|
-
|
180
|
-
# Goal scored
|
181
|
-
|
182
|
-
scoring_team = @team_with_puck == @home ? :home : :away
|
183
|
-
@score[scoring_team] += 1
|
184
|
-
weather.on_goal(scoring_team)
|
185
|
-
|
186
|
-
@stream << Message.ShootScore(puck_holder, @home, @away, *@score.values, audacity)
|
187
|
-
|
188
|
-
start_fight if @prng.rand(3 + @fight_chance) > 3
|
189
|
-
|
190
|
-
start_faceoff
|
191
|
-
@actions = ACTIONS_PER_PERIOD - 1 if @period > NON_OT_PERIODS # Sudden death OT
|
192
|
-
end
|
193
|
-
|
194
252
|
def end_period
|
195
253
|
@weather.on_period_end
|
196
|
-
@stream << Message.
|
254
|
+
@stream << Message.new(:game_period_end, period: @period)
|
197
255
|
@actions = 0
|
198
256
|
@period += 1
|
199
257
|
start_faceoff
|
200
258
|
|
201
259
|
if @period > NON_OT_PERIODS &&
|
202
|
-
|
260
|
+
(@score[:home] != @score[:away] || @period > TOTAL_PERIODS)
|
203
261
|
# Game is over
|
204
|
-
@
|
262
|
+
@pre_tmp_change_stats.each { |player, stats| player.stats = stats }
|
205
263
|
@weather.on_game_end
|
206
|
-
@in_progress = false
|
207
264
|
winner, loser = @score[:away] > @score[:home] ? [@away, @home] : [@home, @away]
|
208
|
-
@stream << Message.
|
265
|
+
@stream << Message.new(:game_end, winning_team: winner)
|
209
266
|
winner.wins += 1
|
210
267
|
loser.losses += 1
|
268
|
+
@do_final_cleanup = true
|
211
269
|
end
|
212
270
|
end
|
213
271
|
|
@@ -237,7 +295,7 @@ module Hlockey
|
|
237
295
|
@shooting_chance = 2
|
238
296
|
@puck_holder_pos = :center
|
239
297
|
@faceoff_won = true
|
240
|
-
@stream << Message.
|
298
|
+
@stream << Message.new(:game_faceoff, winning_player: puck_holder)
|
241
299
|
end
|
242
300
|
|
243
301
|
def switch_team_with_puck(team_with_puck = @team_with_puck)
|
@@ -254,7 +312,7 @@ module Hlockey
|
|
254
312
|
# @param dis [Integer] disadvantage against taking puck
|
255
313
|
# @param stat [Symbol] stat compared to change the odds of success/failure
|
256
314
|
# @return [Boolean] if taking the puck succeeded
|
257
|
-
def try_take_puck(pos, dis
|
315
|
+
def try_take_puck(pos, dis: 0, stat: :agility)
|
258
316
|
return false unless action_succeeds?(@puckless_team.roster[pos].stats[stat] - dis,
|
259
317
|
puck_holder.stats[stat])
|
260
318
|
|
@@ -265,66 +323,74 @@ module Hlockey
|
|
265
323
|
end
|
266
324
|
|
267
325
|
# @return [Boolean] if blocking the shot succeeded
|
268
|
-
def try_block_shot(pos
|
326
|
+
def try_block_shot(pos)
|
269
327
|
blocker = @puckless_team.roster[pos]
|
270
328
|
|
271
|
-
|
272
|
-
|
329
|
+
defense = if pos == :goalie
|
330
|
+
blocker.stats[:defense] * 2
|
331
|
+
else
|
332
|
+
blocker.stats[:defense] - @shooting_chance
|
333
|
+
end
|
334
|
+
return false unless action_succeeds?(defense, puck_holder.stats[:offense])
|
273
335
|
|
274
336
|
@shooting_chance += 1
|
275
|
-
@stream << Message.
|
276
|
-
|
277
|
-
|
337
|
+
@stream << Message.new(:game_shot_blocked, blocker:)
|
338
|
+
|
339
|
+
if action_succeeds?(defense, 0)
|
340
|
+
# blocker takes puck
|
341
|
+
switch_team_with_puck
|
342
|
+
@puck_holder_pos = pos
|
343
|
+
else
|
344
|
+
@puck_holder_pos = Utils.weighted_random(weights(true), @prng)
|
345
|
+
try_take_puck(Utils.weighted_random(weights(false).tap { _1.delete(pos) }, @prng))
|
346
|
+
end
|
347
|
+
|
348
|
+
@stream << Message.new(:game_possession_change,
|
349
|
+
player: puck_holder, new_puck_team: @team_with_puck)
|
278
350
|
|
279
351
|
true
|
280
352
|
end
|
281
353
|
|
282
|
-
|
283
|
-
|
284
|
-
@
|
285
|
-
@fight.players[:away].first)
|
286
|
-
end
|
354
|
+
# @param unset [Boolean] if the game should be set to nil (done when game is over)
|
355
|
+
def set_mods_game(unset: false) =
|
356
|
+
(@home.players + @away.players).each { _1.mods_do(:game=, unset ? nil : self) }
|
287
357
|
|
358
|
+
# Add amount to each stat of each player of team until the end of the game
|
288
359
|
# @param team [Team]
|
289
360
|
# @param amount [Numeric]
|
290
361
|
def change_morale(team, amount)
|
291
362
|
return if amount.zero?
|
292
363
|
|
293
364
|
team.roster.each_value do |player|
|
294
|
-
|
295
|
-
|
296
|
-
end
|
297
|
-
|
298
|
-
player.stats.transform_values! { |stat| stat + amount }
|
365
|
+
@pre_tmp_change_stats[player] ||= player.stats.clone
|
366
|
+
player.stats.transform_values! { _1 + amount }
|
299
367
|
end
|
300
368
|
|
301
|
-
@stream <<
|
369
|
+
@stream << if amount.positive?
|
370
|
+
Message.new(:fight_end_morale_gain, team:, num: amount)
|
371
|
+
else
|
372
|
+
Message.new(:fight_end_morale_loss, team:, num: -amount)
|
373
|
+
end
|
302
374
|
end
|
303
375
|
|
304
|
-
# @return [Player]
|
305
|
-
def puck_holder
|
306
|
-
@team_with_puck.roster[@puck_holder_pos]
|
307
|
-
end
|
376
|
+
# @return [Team::Player]
|
377
|
+
def puck_holder = @team_with_puck.roster[@puck_holder_pos]
|
308
378
|
|
309
379
|
# @return [Symbol]
|
310
|
-
def
|
380
|
+
def pass_reciever_pos
|
311
381
|
w = weights(@shooting_chance > 3)
|
312
382
|
w.delete(@puck_holder_pos)
|
313
383
|
Utils.weighted_random(w, @prng)
|
314
384
|
end
|
315
385
|
|
316
386
|
# @return [Symbol]
|
317
|
-
def defensive_pos
|
318
|
-
Utils.weighted_random(weights(@shooting_chance < 3), @prng)
|
319
|
-
end
|
387
|
+
def defensive_pos = Utils.weighted_random(weights(@shooting_chance < 3), @prng)
|
320
388
|
|
321
389
|
# @return [Hash<Symbol => Integer>]
|
322
390
|
def weights(offensive)
|
323
|
-
if offensive
|
324
|
-
|
325
|
-
|
326
|
-
{ lwing: 1, center: 1, rwing: 1, ldef: 3, rdef: 3 }
|
327
|
-
end
|
391
|
+
return { lwing: 2, center: 2, rwing: 2, ldef: 1, rdef: 1 } if offensive
|
392
|
+
|
393
|
+
{ lwing: 1, center: 1, rwing: 1, ldef: 3, rdef: 3 }
|
328
394
|
end
|
329
395
|
end
|
330
396
|
end
|