1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.org>
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 # `nitrpg` achievements.
19 # Players can unlock achievements by performing remarkable actions on the repo.
20 # Achievements are rewarded by nitcoins.
26 redef class GameEntity
28 # Register a new achievement for this game entity.
30 # Saves the achievement in game data.
31 # Do nothing is the achievement is already registered.
33 # TODO should update the achievement?
34 fun add_achievement
(achievement
: Achievement) do
35 var key
= self.key
/ achievement
.key
36 if game
.store
.has_key
(key
) then return
37 stats
.inc
("achievements")
38 achievement
.save_in
(self.key
)
42 # Load the event from its `id`.
44 # Looks for the event save file in game data.
45 # Returns `null` if the event cannot be found.
46 fun load_achievement
(id
: String): nullable Achievement do
47 var key
= self.key
/ "achievements" / id
48 if not game
.store
.has_key
(key
) then return null
49 var json
= game
.store
.load_object
(key
)
50 return new Achievement.from_json
(game
, json
)
53 # List all events registered in this entity.
55 # This list is reloaded from game data each time its called.
57 # To add events see `add_event`.
58 fun load_achievements
: MapRead[String, Achievement] do
59 var res
= new HashMap[String, Achievement]
60 var key
= self.key
/ "achievements"
61 if not game
.store
.has_collection
(key
) then return res
62 var coll
= game
.store
.list_collection
(key
)
64 res
[id
.to_s
] = load_achievement
(id
.to_s
).as(not null)
70 # Achievements are rewarded by `nitcoins`.
72 # An achievement represents a notable action performed by a `Player`.
73 # Player that `unlock` achievements are rewarded by nitcoins.
77 redef var key
is lazy
do return "achievements" / id
81 # Uniq ID for this achievement.
84 # Name of this achievement.
87 # Description of the achievement.
90 # Reward that this achievement give in nitcoins.
93 # Is this achievement unlocked by somebody?
94 var is_unlocked
: Bool is lazy
do return not load_events
.is_empty
96 # Init `self` from a `json` object.
98 # Used to load achievements from storage.
99 init from_json
(game
: Game, json
: JsonObject) do
100 init(game
, json
["id"].to_s
, json
["name"].to_s
, json
["desc"].to_s
, json
["reward"].as(Int))
108 json
["reward"] = reward
115 # Is `a` unlocked for this `Player`?
116 fun has_achievement
(a
: Achievement): Bool do
117 return load_achievement
(a
.id
) != null
120 # Unlocks an achievement for this Player based on a GithubEvent.
122 # Register the achievement and adds the achievement reward to the player
125 # Do nothing is this player has already unlocked the achievement.
127 # TODO: add abstraction so achievements do not depend on GithubEvent.
128 fun unlock_achievement
(a
: Achievement, event
: GithubEvent) do
129 if has_achievement
(a
) then return
132 trigger_unlock_event
(a
, event
)
135 # Create a new event that marks the achievement unlocking.
136 fun trigger_unlock_event
(achievement
: Achievement, event
: GithubEvent) do
137 var obj
= new JsonObject
139 obj
["reward"] = achievement
.reward
140 obj
["achievement"] = achievement
.id
141 obj
["github_event"] = event
.json
142 var ge
= new GameEvent(game
, "achievement_unlocked", obj
)
145 achievement
.add_event
(ge
)
149 # `GameReactor` dedicated to achievements unlocking.
150 interface AchievementReactor
153 # Unic ID of the achievement this reactor unlocks.
154 fun id
: String is abstract
156 # Name of the achievement this reactor unlocks.
157 fun name
: String is abstract
159 # Description of the achievement this reactor unlocks.
160 fun desc
: String is abstract
162 # Amount of nitcoins rewarded for unlocking the achievement.
163 fun reward
: Int is abstract
165 # Return a new instance of the achievement to unlock.
166 fun new_achievement
(game
: Game): Achievement do
167 var achievement
= new Achievement(game
, id
, name
, desc
, reward
)
168 game
.add_achievement
(achievement
)
173 #####################
175 #####################
177 # Unlock achievement after X issues.
179 # Used to factorize behavior.
180 abstract class PlayerXIssues
181 super AchievementReactor
183 # Number of PR required to unlock the achievement.
184 var threshold
: Int is noinit
186 redef fun react_event
(game
, event
) do
187 if not event
isa IssuesEvent then return
188 if not event
.action
== "opened" then return
189 var player
= event
.issue
.user
.player
(game
)
190 if player
.stats
["issues"] == threshold
then
191 var a
= new_achievement
(game
)
192 player
.unlock_achievement
(a
, event
)
197 # Player open his first issue.
201 redef var id
= "player_1_issue"
202 redef var name
= "First complaint"
203 redef var desc
= "Open your first issue."
204 redef var reward
= 10
205 redef var threshold
= 1
208 # Player open 100 issues.
209 class Player100Issues
212 redef var id
= "player_100_issues"
213 redef var name
= "Mature whiner"
214 redef var desc
= "Open 100 issues in the game."
215 redef var reward
= 100
216 redef var threshold
= 100
219 # Player open 1 000 issues.
223 redef var id
= "player_1000_issues"
224 redef var name
= "You, sir, complain a lot"
225 redef var desc
= "Open 1000 issues in the game."
226 redef var reward
= 1000
227 redef var threshold
= 1000
230 # Player open an issue about nitdoc.
231 class IssueAboutNitdoc
232 super AchievementReactor
234 redef var id
= "issue_about_nitdoc"
235 redef var name
= "Say nitdoc again, I double dare you!"
236 redef var desc
= "Open an issue with \"nitdoc\
" in the title."
237 redef var reward
= 10
239 redef fun react_event
(game
, event
) do
240 if not event
isa IssuesEvent then return
241 if not event
.action
== "opened" then return
242 var player
= event
.issue
.user
.player
(game
)
243 var re
= "nitdoc".to_re
244 re
.ignore_case
= true
245 if event
.issue
.title
.has
(re
) then
246 var a
= new_achievement
(game
)
247 player
.unlock_achievement
(a
, event
)
252 # Player open an issue about FFI.
256 redef var id
= "issue_about_ffi"
257 redef var name
= "Polyglot what?"
258 redef var desc
= "Open an issue with `ffi` in the title."
259 redef var reward
= 10
261 redef fun react_event
(game
, event
) do
262 if not event
isa IssuesEvent then return
263 if not event
.action
== "opened" then return
264 var player
= event
.issue
.user
.player
(game
)
265 var re
= "\\bffi\\b".to_re
266 re
.ignore_case
= true
267 if event
.issue
.title
.has
(re
) then
268 var a
= new_achievement
(game
)
269 player
.unlock_achievement
(a
, event
)
274 #####################
276 #####################
278 # Unlock achievement after X pull requests.
280 # Used to factorize behavior.
281 abstract class PlayerXPulls
282 super AchievementReactor
284 # Number of PR required to unlock the achievement.
285 var threshold
: Int is noinit
287 redef fun react_event
(game
, event
) do
288 if not event
isa PullRequestEvent then return
289 if not event
.action
== "opened" then return
290 var player
= event
.pull
.user
.player
(game
)
291 if player
.stats
["pulls"] == threshold
then
292 var a
= new_achievement
(game
)
293 player
.unlock_achievement
(a
, event
)
298 # Open your first pull request.
302 redef var id
= "player_1_pull"
303 redef var name
= "First PR"
304 redef var desc
= "Open your first pull request."
305 redef var reward
= 10
306 redef var threshold
= 1
309 # Author 100 pull requests.
313 redef var id
= "player_100_pulls"
314 redef var name
= "100 pull requests!!!"
315 redef var desc
= "Open 100 pull requests in the game."
316 redef var reward
= 100
317 redef var threshold
= 100
320 # Author 1000 pull requests.
324 redef var id
= "player_1000_pulls"
325 redef var name
= "1000 PULL REQUESTS!!!"
326 redef var desc
= "Open 1000 pull requests in the game."
327 redef var reward
= 1000
328 redef var threshold
= 1000
331 #####################
333 #####################
335 # Unlock achievement after X merged commits.
337 # Used to factorize behavior.
338 abstract class PlayerXCommits
339 super AchievementReactor
341 # Number of PR required to unlock the achievement.
342 var threshold
: Int is noinit
344 redef fun react_event
(game
, event
) do
345 if not event
isa PullRequestEvent then return
346 if not event
.action
== "closed" then return
347 if not event
.pull
.merged
then return
348 var player
= event
.pull
.user
.player
(game
)
349 if player
.stats
["commits"] == threshold
then
350 var a
= new_achievement
(game
)
351 player
.unlock_achievement
(a
, event
)
356 # Author your first commit in the game.
360 redef var id
= "player_1_commit"
361 redef var name
= "First blood"
362 redef var desc
= "Author your first commit in the game."
363 redef var reward
= 10
364 redef var threshold
= 1
367 # Author 100 commits.
368 class Player100Commits
371 redef var id
= "player_100_commits"
372 redef var name
= "100 commits"
373 redef var desc
= "Author 100 commits in the game."
374 redef var reward
= 100
375 redef var threshold
= 100
378 # Author 1 000 commits.
379 class Player1KCommits
382 redef var id
= "player_1000_commits"
383 redef var name
= "1000 commits!!!"
384 redef var desc
= "Author 1000 commits in the game."
385 redef var reward
= 1000
386 redef var threshold
= 1000
389 # Author 10 000 commits.
390 class Player10KCommits
393 redef var id
= "player_10000_commits"
394 redef var name
= "10000 COMMITS!!!"
395 redef var desc
= "Author 10000 commits in the game."
396 redef var reward
= 10000
397 redef var threshold
= 10000
400 #####################
402 #####################
404 # Unlock achievement after X issue comments.
406 # Used to factorize behavior.
407 abstract class PlayerXComments
408 super AchievementReactor
410 # Number of comments required to unlock the achievement.
411 var threshold
: Int is noinit
413 redef fun react_event
(game
, event
) do
414 if not event
isa IssueCommentEvent then return
415 if not event
.action
== "created" then return
416 var player
= event
.comment
.user
.player
(game
)
417 if player
.stats
["comments"] == threshold
then
418 var a
= new_achievement
(game
)
419 player
.unlock_achievement
(a
, event
)
424 # Player author his first comment in issues.
426 super PlayerXComments
428 redef var id
= "player_1_comment"
429 redef var name
= "From lurker to member"
430 redef var desc
= "Comment on an issue."
431 redef var reward
= 10
432 redef var threshold
= 1
435 # Player author 100 issue comments.
436 class Player100Comments
437 super PlayerXComments
439 redef var id
= "player_100_comments"
440 redef var name
= "Chatter"
441 redef var desc
= "Comment 100 times on issues."
442 redef var reward
= 100
443 redef var threshold
= 100
446 # Player author 1000 issue comments.
447 class Player1KComments
448 super PlayerXComments
450 redef var id
= "player_1000__comments"
451 redef var name
= "You sir, talk a lot!"
452 redef var desc
= "Comment 1000 times on issues."
453 redef var reward
= 1000
454 redef var threshold
= 1000
457 # Ping @privat in a comment.
459 super AchievementReactor
461 redef var id
= "player_ping_god"
462 redef var name
= "Ping god"
463 redef var desc
= "Ping the owner of the repo for the first time."
464 redef var reward
= 50
466 redef fun react_event
(game
, event
) do
467 if not event
isa IssueCommentEvent then return
468 var owner
= game
.repo
.owner
.login
469 if event
.comment
.body
.has
("@{owner}".to_re
) then
470 var player
= event
.comment
.user
.player
(game
)
471 var a
= new_achievement
(game
)
472 player
.unlock_achievement
(a
, event
)
478 class PlayerFirstReview
479 super AchievementReactor
481 redef var id
= "player_first_review"
482 redef var name
= "First +1"
483 redef var desc
= "Give a +1 for the first time."
484 redef var reward
= 10
486 redef fun react_event
(game
, event
) do
487 if not event
isa IssueCommentEvent then return
488 # FIXME use a more precise way to locate reviews
489 if event
.comment
.is_ack
then
490 var player
= event
.comment
.user
.player
(game
)
491 var a
= new_achievement
(game
)
492 player
.unlock_achievement
(a
, event
)
497 # Talk about nitcoin in issue comments.
498 class PlayerSaysNitcoin
499 super AchievementReactor
501 redef var id
= "player_says_nitcoin"
502 redef var name
= "Talking about money"
503 redef var desc
= "Say something about nitcoins in a comment."
504 redef var reward
= 10
506 redef fun react_event
(game
, event
) do
507 if not event
isa IssueCommentEvent then return
508 if event
.comment
.body
.has
("(n|N)itcoin".to_re
) then
509 var player
= event
.comment
.user
.player
(game
)
510 var a
= new_achievement
(game
)
511 player
.unlock_achievement
(a
, event
)