Merge: Mock Github API tests
[nit.git] / contrib / nitrpg / src / achievements.nit
index 625bfc2..2097ec3 100644 (file)
@@ -32,22 +32,28 @@ redef class GameEntity
        #
        # TODO should update the achievement?
        fun add_achievement(achievement: Achievement) do
-               var key = self.key / achievement.key
-               if game.store.has_key(key) then return
                stats.inc("achievements")
-               achievement.save_in(self)
-               save
+               achievement.owner = self
+               achievement.save
        end
 
+       # Is `a` unlocked for this `Player`?
+       fun has_achievement(a: Achievement): Bool do return load_achievement(a.id) != null
+
        # Load the event from its `id`.
        #
        # Looks for the event save file in game data.
        # Returns `null` if the event cannot be found.
        fun load_achievement(id: String): nullable Achievement do
-               var key = self.key / "achievements" / id
-               if not game.store.has_key(key) then return null
-               var json = game.store.load_object(key)
-               return new Achievement.from_json(game, json)
+               var req = new JsonObject
+               req["id"] = id
+               req["game"] = game.key
+               req["owner"] = key
+               var obj = game.db.collection("achievements").find(req)
+               if obj isa JsonObject then
+                       return new Achievement.from_json(game, obj)
+               end
+               return null
        end
 
        # List all events registered in this entity.
@@ -56,12 +62,13 @@ redef class GameEntity
        #
        # To add events see `add_event`.
        fun load_achievements: MapRead[String, Achievement] do
+               var req = new JsonObject
+               req["game"] = game.key
+               req["owner"] = key
                var res = new HashMap[String, Achievement]
-               var key = self.key / "achievements"
-               if not game.store.has_collection(key) then return res
-               var coll = game.store.list_collection(key)
-               for id in coll do
-                       res[id.to_s] = load_achievement(id.to_s).as(not null)
+               for obj in game.db.collection("achievements").find_all(req) do
+                       var achievement = new Achievement.from_json(game, obj)
+                       res[achievement.id] = achievement
                end
                return res
        end
@@ -74,10 +81,16 @@ end
 class Achievement
        super GameEntity
 
-       redef var key is lazy do return "achievements" / id
+       redef var collection_name = "achievements"
 
        redef var game
 
+       redef fun key do
+               var owner = self.owner
+               if owner == null then return id
+               return "{owner.key}-{id}"
+       end
+
        # Uniq ID for this achievement.
        var id: String
 
@@ -93,34 +106,34 @@ class Achievement
        # Is this achievement unlocked by somebody?
        var is_unlocked: Bool is lazy do return not load_events.is_empty
 
+       # Game entity this achievement is about.
+       var owner: nullable GameEntity = null
+
        # Init `self` from a `json` object.
        #
        # Used to load achievements from storage.
        init from_json(game: Game, json: JsonObject) do
-               self.game = game
-               id = json["id"].to_s
-               name = json["name"].to_s
-               desc = json["desc"].to_s
-               reward = json["reward"].as(Int)
+               init(game,
+                       json["id"].as(String),
+                       json["name"].as(String),
+                       json["desc"].as(String),
+                       json["reward"].as(Int))
        end
 
-       redef fun to_json do
+       redef fun to_json_object do
                var json = super
                json["id"] = id
                json["name"] = name
                json["desc"] = desc
                json["reward"] = reward
+               json["game"] = game.key
+               var owner = self.owner
+               if owner != null then json["owner"] = owner.key
                return json
        end
 end
 
 redef class Player
-
-       # Is `a` unlocked for this `Player`?
-       fun has_achievement(a: Achievement): Bool do
-               return load_achievement(a.id) != null
-       end
-
        # Unlocks an achievement for this Player based on a GithubEvent.
        #
        # Register the achievement and adds the achievement reward to the player
@@ -134,6 +147,7 @@ redef class Player
                nitcoins += a.reward
                add_achievement(a)
                trigger_unlock_event(a, event)
+               save
        end
 
        # Create a new event that marks the achievement unlocking.
@@ -142,7 +156,7 @@ redef class Player
                obj["player"] = name
                obj["reward"] = achievement.reward
                obj["achievement"] = achievement.id
-               obj["github_event"] = event.json
+               obj["github_event"] = event
                var ge = new GameEvent(game, "achievement_unlocked", obj)
                add_event(ge)
                game.add_event(ge)
@@ -350,7 +364,7 @@ abstract class PlayerXCommits
                if not event.action == "closed" then return
                if not event.pull.merged then return
                var player = event.pull.user.player(game)
-               if player.stats["commits"] == threshold then
+               if player.stats["commits"] >= threshold then
                        var a = new_achievement(game)
                        player.unlock_achievement(a, event)
                end
@@ -400,3 +414,119 @@ class Player10KCommits
        redef var reward = 10000
        redef var threshold = 10000
 end
+
+#####################
+### Issue Comments
+#####################
+
+# Unlock achievement after X issue comments.
+#
+# Used to factorize behavior.
+abstract class PlayerXComments
+       super AchievementReactor
+
+       # Number of comments required to unlock the achievement.
+       var threshold: Int is noinit
+
+       redef fun react_event(game, event) do
+               if not event isa IssueCommentEvent then return
+               if not event.action == "created" then return
+               var player = event.comment.user.player(game)
+               if player.stats["comments"] == threshold then
+                       var a = new_achievement(game)
+                       player.unlock_achievement(a, event)
+               end
+       end
+end
+
+# Player author his first comment in issues.
+class Player1Comment
+       super PlayerXComments
+
+       redef var id = "player_1_comment"
+       redef var name = "From lurker to member"
+       redef var desc = "Comment on an issue."
+       redef var reward = 10
+       redef var threshold = 1
+end
+
+# Player author 100 issue comments.
+class Player100Comments
+       super PlayerXComments
+
+       redef var id = "player_100_comments"
+       redef var name = "Chatter"
+       redef var desc = "Comment 100 times on issues."
+       redef var reward = 100
+       redef var threshold = 100
+end
+
+# Player author 1000 issue comments.
+class Player1KComments
+       super PlayerXComments
+
+       redef var id = "player_1000_comments"
+       redef var name = "You sir, talk a lot!"
+       redef var desc = "Comment 1000 times on issues."
+       redef var reward = 1000
+       redef var threshold = 1000
+end
+
+# Ping @privat in a comment.
+class PlayerPingGod
+       super AchievementReactor
+
+       redef var id = "player_ping_god"
+       redef var name = "Ping god"
+       redef var desc = "Ping the owner of the repo for the first time."
+       redef var reward = 50
+
+       redef fun react_event(game, event) do
+               if not event isa IssueCommentEvent then return
+               var owner = game.repo.owner.login
+               if event.comment.body.has("@{owner}".to_re) then
+                       var player = event.comment.user.player(game)
+                       var a = new_achievement(game)
+                       player.unlock_achievement(a, event)
+               end
+       end
+end
+
+# Give your first +1
+class PlayerFirstReview
+       super AchievementReactor
+
+       redef var id = "player_first_review"
+       redef var name = "First +1"
+       redef var desc = "Give a +1 for the first time."
+       redef var reward = 10
+
+       redef fun react_event(game, event) do
+               if not event isa IssueCommentEvent then return
+               # FIXME use a more precise way to locate reviews
+               if event.comment.is_ack then
+                       var player = event.comment.user.player(game)
+                       var a = new_achievement(game)
+                       player.unlock_achievement(a, event)
+               end
+       end
+end
+
+# Talk about nitcoin in issue comments.
+class PlayerSaysNitcoin
+       super AchievementReactor
+
+       redef var id = "player_says_nitcoin"
+       redef var name = "Talking about money"
+       redef var desc = "Say something about nitcoins in a comment."
+       redef var reward = 10
+
+       redef fun react_event(game, event) do
+               if not event isa IssueCommentEvent then return
+               if event.comment.body.has("(n|N)itcoin".to_re) then
+                       var player = event.comment.user.player(game)
+                       var a = new_achievement(game)
+                       player.unlock_achievement(a, event)
+               end
+       end
+end