hlockey 5 → 7
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.
- 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
|