{"action":"closed","number":712,"pull_request":{"url":"https://api.github.com/repos/SpongePowered/SpongeAPI/pulls/712","id":36787314,"html_url":"https://github.com/SpongePowered/SpongeAPI/pull/712","diff_url":"https://github.com/SpongePowered/SpongeAPI/pull/712.diff","patch_url":"https://github.com/SpongePowered/SpongeAPI/pull/712.patch","issue_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/issues/712","number":712,"state":"closed","locked":false,"title":"[WIP] Cause Enhancements","user":{"login":"gabizou","id":1203877,"avatar_url":"https://avatars.githubusercontent.com/u/1203877?v=3","gravatar_id":"","url":"https://api.github.com/users/gabizou","html_url":"https://github.com/gabizou","followers_url":"https://api.github.com/users/gabizou/followers","following_url":"https://api.github.com/users/gabizou/following{/other_user}","gists_url":"https://api.github.com/users/gabizou/gists{/gist_id}","starred_url":"https://api.github.com/users/gabizou/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gabizou/subscriptions","organizations_url":"https://api.github.com/users/gabizou/orgs","repos_url":"https://api.github.com/users/gabizou/repos","events_url":"https://api.github.com/users/gabizou/events{/privacy}","received_events_url":"https://api.github.com/users/gabizou/received_events","type":"User","site_admin":false},"body":"So far, [`Cause`](https://github.com/SpongePowered/SpongeAPI/blob/master/src/main/java/org/spongepowered/api/event/cause/Cause.java) as it stands is somewhat confusing with the inclusion of [`Reason`](https://github.com/SpongePowered/SpongeAPI/blob/master/src/main/java/org/spongepowered/api/event/cause/reason/Reason.java) such that it isn't clear as to what should be considered a `Cause` and what should be considered a `Reason`.\r\n\r\nThis PR aims to achieve the following:\r\n- `Cause` becomes a container object of all related objects associated with the cause for a `CauseTracked` event\r\n- \"Reasons\" or additional causes aiding the direct cause for an event are included in `Cause`, the closer the association to the direct cause, the lower the index that object will be\r\n- Include `EntityDamageEvent` and `DamageSource`s while keeping in line with the primary `Cause` goal. Any additional associated objects aiding the direct `DamageSource` are included in the `Cause` associated with the `EntityDamageEvent`\r\n- Introduce `EntityPreDamageEvent` where the damage calculations for modifying the raw damage are included, this is primarily used for modifying the outgoing damage BEFORE the `EntityDamageEvent` is fired, this is equivalent to having an event to modify the damage a `Player` will output when using an `ItemStack` of `ItemTypes.DIAMOND_SWORD` such that all modifiers are included (such as the enchantments on the sword, the potion effects, and any other `AttributeModifier`s or alternative `DamageModifier`s relating to the final raw damage output from the `Player`\r\n- For many `CauseTrack`ed events, the `Cause` now is always available, but a `Cause.empty()` may be included\r\n\r\n\r\n#### So what does this really mean for `CauseTracked` events?\r\n\r\nWhat this means is that a vanilla interaction throwing a `CauseTracked` event will have as much information as possible within the `Cause`. With the following addition to `Cause`:\r\n\r\n```java\r\n<T> Optional<T> getFirst(Class<T>);\r\n\r\n<T> Optional<T> getLast(Class<T>);\r\n```\r\n\r\na plugin is easily able to determine whether a `BlockState` was the cause for an event, a `Player`, or if a `TileEntity` was part of the mix. \r\n\r\nThe one issue with `Cause` previously was that it was always optional. Passing in a null to the [`SpongeEventFactory`](https://github.com/SpongePowered/SpongeAPI/blob/master/src/main/java/org/spongepowered/api/event/SpongeEventFactory.java#L341) is that the `Cause` is always optional, even when it should not be.  In the PR, I change this to where `Cause` can be \"empty\". Some might think: *Wait! Isn't that making `Cause` just become a similar object to `Optional`?* To which the answer is yes. The one issue with `Optional` however is that  `Optional` can only hold a single value. There is no way to hold multiple values, except if the `Optional` is declared like so: `Optional<Object[]>`, which eliminates any utility methods provided by `getFirst(Class<T>)` and `getAllOf(Class<T>)` from `Cause`.\r\n\r\n#### Ok, so where does this come into play with actual events? I want to know the cause for a `BlockChangeEvent`!\r\n\r\nWell, see, this is where the uniqueness comes into play. Since we always have a `Cause`, we can always call `getFirst(Player.class)` which will return a present `Player` if and only if a `Player` was involved in the `BlockChangeEvent`. How the objects are ordered in the `Cause` is simple: The direct cause of the event comes first, any additional \"helpers\" that aid the *root cause* are indexed afterwards.\r\n\r\nI'll give an example here of how a `BlockChangeEvent` could have the order of objects in the cause, for the case of a `Player` placing a `ItemTypes.SAPPLING`.\r\n\r\nFirst, the `Player` is the root cause, because the player is right clicking with the `ItemStack`.\r\nSecond, the `ItemStack` follows because the `ItemStack` is a part of the \"reason\" why the `BlockChangeEvent` is being called.\r\n\r\n#### Now, what I haven't seen is how this affects something like `EntityDamageEvent`, care to elaborate?\r\n\r\nOf course! With an `EntityDamageEvent`, I'll have to pull the same description from #707 \r\n\r\n\r\n> Skeleton shoots bow launching an arrow hitting a Player.\r\n>\r\n>This is pretty plain and simple to understand what happens, but we need to look at it much deeper to fully understand what the final damage comes out to:\r\n\r\n>I'll give an order of how the damage is calculated:\r\n1) Skeleton is shooting the arrow, by default it will apply say 3 damage to the Arrow\r\n2) The bow, if enchanted applies a bonus amount of damage to the arrow, say 1.5 additional damage\r\nThe final raw damage of the arrow is now 4.5.\r\n\r\n>Now here comes the fun part:\r\nThe Player is hit with the arrow\r\n1) The damage is modified based on the game difficulty, in this case, it's on `HARD` difficulty, so the damage is multiplied by 1.5 (damage is now 6.75)\r\n2) Armor calculations are factored in, Say that the player has full iron armor, and the iron armor reduces the damage by 2 (damage is now at 4.75)\r\n3) Potion effects add to resistances, so damage is reduced by multiplying damage by 20 and dividing by 25 (damage is now at 3.8)\r\n4) Armor Enchantments reduce the damage by 20% for example (the damage is now at 3.04)\r\n5) The Absorption potion effect is now factored in to find out how much actual health is removed from the entity, say we have an additional 2 health from absorption, (damage is now at 1.04)\r\n\r\n>The end result is a damage value of 1.04 damage dealt directly to the entity, where 4.5 damage originated.\r\n\r\n>The reason why I walked you through all of this is the following: I've been contemplating that we should have a LivingPreAttackEntityEvent of which we can trace the modifiers for the damage being dealt to the entity (in the case of the skeleton shooting, there's the modifier of the bow and the modifier base damage of the skeleton). After that event, we'd need a proper `WeightedCollection` to store the `DamageModifier`s and their `Function`s being applied to the incoming damage for an `EntityDamageEvent`. Considering the limitless possibilities of being able to identify the `DamageModifier`s and the `Cause` for each modifier, I do believe this would make an excellent change with Causes PR in the near future.\r\n\r\nWhat HAS been achieved however, is slightly different. Instead of just including the associated `Entity` as a *root* to the `Cause`, a `DamageSource` is now the *root* cause. A `DamageSource` is essentially a wrapper around any object with a descriptive `DamageType` with various *attributes* of the `DamageSource` including whether the damage is *absolute*, *ignores armor*, *magical*, *explosive*, or even *scaled with difficulty*. The included `DamageSource`s in this PR facilitate understanding the *type* of damage being dealt, and the parties involved in the `DamageSource`.\r\n\r\n#### I'm a little confused about `DamageSource`, can you give me an example?\r\n\r\nOf course! Say you have an `EntityPreDamageEvent`:\r\n\r\nThe `DamageSource` in this case is actually a `ProjectileDamageSource`, where the `Projectile` is an `Arrow`, and the `ProjectileSource` is a `Skeleton`. Given that the `ProjectileDamageSource` is the *root* cause to the `EntityPreDamageEvent`, we can then use this information to know that the `Cause` should have a related `ItemStack` used as a *bow* to shoot the *Arrow*. With the *bow*, we know that it can have no `Enchantment`s or it can have some `Enchantment`s. Of course, with these objects in the `Cause`, it makes it plain and clear to use them:\r\n\r\n```java\r\n@Subscribe\r\npublic void cancelAnvils(EntityPreDamageEvent event) {\r\n    if (event.getCause().isEmpty()) {\r\n        return;\r\n    }\r\n    final Cause cause = event.getCause();\r\n    final Optional<BlockDamageSource> damageSource = cause.getFirst(BlockDamageSource.class);\r\n    if (damageSource.isPresent()) {\r\n        if (damageSource.get().getBlockState().getType() == BlockTypes.ANVIL) {\r\n            event.setCancelled(true);\r\n        }\r\n    }\r\n}\r\n\r\n@Subscribe\r\npublic void skeletonArrows(EntityPreDamageEvent event) {\r\n    if (event.getCause().isEmpty()) {\r\n        return;\r\n    }\r\n    final Cause cause = event.getCause();\r\n    final Optional<ProjectileDamageSource> projectileDamageSource = cause.getFirst(ProjectileDamageSource.class);\r\n    if (projectileDamageSource.isPresent()) {\r\n        final ProjectileSource source = projectileDamageSource.get().getShooter();\r\n        if (source instanceof Skeleton) {\r\n            final SkeletonType skeletonType = ((Skeleton) source).getData(SkeletonData.class).get().getValue();\r\n            if (skeletonType == SkeletonTypes.WITHER) {\r\n                event.setDamageFunction(new MyDamageModifier(Cause.of(source, this)), new Function<Double, Double>() {\r\n                    @Nullable\r\n                    @Override\r\n                    public Double apply(Double input) {\r\n                        return input * 3 / 2;\r\n                    }\r\n                });\r\n            } else if (skeletonType == SkeletonTypes.NORMAL) {\r\n                event.setDamageFunction(new MyDamageModifier(Cause.of(source, this)), new Function<Double, Double>() {\r\n                    @Nullable\r\n                    @Override\r\n                    public Double apply(Double input) {\r\n                        return input * 3 / 4;\r\n                    }\r\n                });\r\n            }\r\n        }\r\n    }\r\n    for (final Tuple<DamageModifier, Function<? super Double, Double>> damageModifier : event.getModifiers()) {\r\n        if (damageModifier.getFirst().getCause().getFirst(ItemStack.class).isPresent()) {\r\n            // We know that we have an ItemStack that was used in the DamageModifier cause, we can suspect that\r\n            // the ItemStack has some sort of enchantments, or not\r\n            final ItemStack itemStack = damageModifier.getFirst().getCause().getFirst(ItemStack.class).get();\r\n            final Optional<EnchantmentData> dataOptional = itemStack.getData(EnchantmentData.class);\r\n            if (dataOptional.isPresent() && dataOptional.get().get(Enchantments.POWER).isPresent()) {\r\n                event.setDamageFunction(damageModifier.getFirst(), new Function<Double, Double>() {\r\n                    @Nullable\r\n                    @Override\r\n                    public Double apply(@Nullable Double input) {\r\n                        return input * 1.25 * dataOptional.get().get(Enchantments.POWER).get().doubleValue();\r\n                    }\r\n                });\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\npublic final class MyDamageModifier implements DamageModifier {\r\n    private Cause cause;\r\n\r\n    public MyDamageModifier(Cause cause) {\r\n        this.cause = checkNotNull(cause);\r\n    }\r\n\r\n    @Override\r\n    public Cause getCause() {\r\n        return this.cause;\r\n    }\r\n\r\n    @Override\r\n    public String getId() {\r\n        return \"com.gabizou.MyDamageModifier\";\r\n    }\r\n\r\n    @Override\r\n    public String getName() {\r\n        return \"MyDamageModifier\";\r\n    }\r\n}\r\n``` \r\n\r\n#### Holy cow batman! Why so complicated?!\r\n\r\nUnfortunately, there is A LOT that goes into an `EntityDamageEvent`, even more so if you want to be able to manipulate the damage *coming in*, essentially the `EntityPreDamageEvent`. To fully unlock the power available with this, the possibilities to associate new `DamageModifier`s with a damage event are nigh limitless. Since we can expose the underlying functions, as they placed in order from vanilla mechanics, we can successfully interpret that an `EntityPreDamageEvent` is augmented not only by the `Skeleton`, but also that the `ItemStack` used by the `Skeleton` affects the total damage applied onto the `Arrow`. With that in mind, we can also assume that we can interpret the following `EntityDamageEvent` with similar processing for `DamageModifier`s when an `Entity` is being *damaged*, dictating the *final* damage actually being *applied* to the `Entity`, and not just interpret the *raw* incoming damage.\r\n\r\nOf course, there are so many factors that can go into the `EntityDamageEvent` that I can't possibly explain it all in a simple code snippet, but I can explain in layman terms here.\r\n\r\nWith the `EntityDamageEvent` being fired only while the `Entity` is being damaged, we can gather all `DamageModifier`s that would further process the raw incoming damage, such as `ItemStack`s that are considered to be *armor*, `PotionEffect`s that are absorbing damage, heck, we can even apply our own custom `DamageModifier`s if we had a party plugin that had a \"reducing damage\" effect based on the number of players joined in the party! All of this is as extensible as possible.\r\n\r\n#### Ok... well, it's clear that you've given a lot of thought about `EntityDamageEvent`, but what about other `CauseTracked` events?\r\n\r\nSimilar to the `EntityDamageEvent` and `EntityPreDamageEvent`, we can safely say that most `CauseTracked` events are going to receive similar treatment with regards to adding additional *sources*/*causes*/*reasons* whatever you want to call them. So far, I've only been able to seriously work on the damage events, however, my thought process is that with `EntitySpawnEvent`, it is very easy to apply the same idea of having the following:\r\n\r\n`EntitySpawnEvent`:\r\n - `SpawnCause` : An abstract *root cause* that aids in helping further understand the `Cause` for why an `Entity` is being spawned\r\n - `SpawnType` : Another `CatalogType` that aims to further simplify the *type* of `SpawnCause`, more specifically, when an `Entity` is spawned by a `MobSpawner`, the `SpawnCause` would be an instance of a `MobSpawnerSpawnCause` with the associated `MobSpawnerData` that defined the `Entity` to be spawned\r\n\r\n`EntityTeleportEvent`:\r\n- `TeleportType` : Yet again, a helper `CatalogType` that aims to make it understandable why an `Entity` is being teleported\r\n- `TeleportCause` : Another *root cause* for the `Entity` to be teleporting, can range from `EntityTeleportCause` (an `Enderman` teleporting due to rain), `TeleporterTeleportCause` (a portal performing the teleport as per vanilla mechanics)\r\n\r\nThe list will grow as the PR is worked on and time goes on.\r\n\r\n#### So, will this make it easy for me to know if a `BlockChangeEvent` was caused by a `Player` using some bonemeal on a sapling?\r\n\r\nIn a short answer: yes. \r\nThe long answer: The `Cause` would indeed have a `Player` object, and the secondary `cause` would be the `ItemStack` that \"aided\" in the cause for the `BlockChangeEvent` to take place. What is better is that with the knowledge of the `Player`, the `ItemStack`, and any other objects placed into the `Cause`, it becomes very simple for you to decide on changing the `BlockChangeEvent` for whatever purpose.\r\n\r\n#### How about throwing custom events? I don't really understand what I'm supposed to do here if I want to allow plugins to hook into my custom `EntityTeleportEvent`.\r\n\r\nTo be honest, that is why I simplified `Cause` to replicate `Optional`. When you are wanting to teleport an `Entity` with a `Cause`, you can include the *root* cause, say the `Entity` called a `Command`, and the `Command` was entered through a `Sign`, you now have *two* objects to include in your `Cause`. However, let's say you wanted to expand and provide some custom `TeleportCause`s, you could extend `TeleportCause` and provide the extended `Cause` to be the `Sign`, and likewise you could include your own custom marker object so that in the event you have a listener for `EntityTeleportEvent`, you can safely ignore the event if your custom marker object is included.\r\n\r\n#### How about X event?\r\n\r\nWell, as I said before, the PR is still a work in progress, so I haven't finished designing the `Cause` for every `CauseTracked` event.","created_at":"2015-06-03T00:05:40Z","updated_at":"2015-08-23T17:11:36Z","closed_at":"2015-08-23T17:11:36Z","merged_at":null,"merge_commit_sha":"7a29282f5b6fa92bbf7213ce1642e64148fc9bdd","assignee":{"login":"gabizou","id":1203877,"avatar_url":"https://avatars.githubusercontent.com/u/1203877?v=3","gravatar_id":"","url":"https://api.github.com/users/gabizou","html_url":"https://github.com/gabizou","followers_url":"https://api.github.com/users/gabizou/followers","following_url":"https://api.github.com/users/gabizou/following{/other_user}","gists_url":"https://api.github.com/users/gabizou/gists{/gist_id}","starred_url":"https://api.github.com/users/gabizou/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gabizou/subscriptions","organizations_url":"https://api.github.com/users/gabizou/orgs","repos_url":"https://api.github.com/users/gabizou/repos","events_url":"https://api.github.com/users/gabizou/events{/privacy}","received_events_url":"https://api.github.com/users/gabizou/received_events","type":"User","site_admin":false},"milestone":{"url":"https://api.github.com/repos/SpongePowered/SpongeAPI/milestones/4","html_url":"https://github.com/SpongePowered/SpongeAPI/milestones/Revision%203.0","labels_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/milestones/4/labels","id":1082492,"number":4,"title":"Revision 3.0","description":"This is the third stable revision of the Sponge API, 3.0 ","creator":{"login":"Zidane","id":744482,"avatar_url":"https://avatars.githubusercontent.com/u/744482?v=3","gravatar_id":"","url":"https://api.github.com/users/Zidane","html_url":"https://github.com/Zidane","followers_url":"https://api.github.com/users/Zidane/followers","following_url":"https://api.github.com/users/Zidane/following{/other_user}","gists_url":"https://api.github.com/users/Zidane/gists{/gist_id}","starred_url":"https://api.github.com/users/Zidane/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Zidane/subscriptions","organizations_url":"https://api.github.com/users/Zidane/orgs","repos_url":"https://api.github.com/users/Zidane/repos","events_url":"https://api.github.com/users/Zidane/events{/privacy}","received_events_url":"https://api.github.com/users/Zidane/received_events","type":"User","site_admin":false},"open_issues":14,"closed_issues":36,"state":"open","created_at":"2015-04-24T16:02:08Z","updated_at":"2015-08-23T17:11:36Z","due_on":null,"closed_at":null},"commits_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/pulls/712/commits","review_comments_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/pulls/712/comments","review_comment_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/pulls/comments{/number}","comments_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/issues/712/comments","statuses_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/statuses/09ac13d931bfd566563a41b95aae72c10e1769aa","head":{"label":"SpongePowered:feature/cause","ref":"feature/cause","sha":"09ac13d931bfd566563a41b95aae72c10e1769aa","user":{"login":"SpongePowered","id":8683473,"avatar_url":"https://avatars.githubusercontent.com/u/8683473?v=3","gravatar_id":"","url":"https://api.github.com/users/SpongePowered","html_url":"https://github.com/SpongePowered","followers_url":"https://api.github.com/users/SpongePowered/followers","following_url":"https://api.github.com/users/SpongePowered/following{/other_user}","gists_url":"https://api.github.com/users/SpongePowered/gists{/gist_id}","starred_url":"https://api.github.com/users/SpongePowered/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SpongePowered/subscriptions","organizations_url":"https://api.github.com/users/SpongePowered/orgs","repos_url":"https://api.github.com/users/SpongePowered/repos","events_url":"https://api.github.com/users/SpongePowered/events{/privacy}","received_events_url":"https://api.github.com/users/SpongePowered/received_events","type":"Organization","site_admin":false},"repo":{"id":23770421,"name":"SpongeAPI","full_name":"SpongePowered/SpongeAPI","owner":{"login":"SpongePowered","id":8683473,"avatar_url":"https://avatars.githubusercontent.com/u/8683473?v=3","gravatar_id":"","url":"https://api.github.com/users/SpongePowered","html_url":"https://github.com/SpongePowered","followers_url":"https://api.github.com/users/SpongePowered/followers","following_url":"https://api.github.com/users/SpongePowered/following{/other_user}","gists_url":"https://api.github.com/users/SpongePowered/gists{/gist_id}","starred_url":"https://api.github.com/users/SpongePowered/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SpongePowered/subscriptions","organizations_url":"https://api.github.com/users/SpongePowered/orgs","repos_url":"https://api.github.com/users/SpongePowered/repos","events_url":"https://api.github.com/users/SpongePowered/events{/privacy}","received_events_url":"https://api.github.com/users/SpongePowered/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/SpongePowered/SpongeAPI","description":"A Minecraft plugin API","fork":false,"url":"https://api.github.com/repos/SpongePowered/SpongeAPI","forks_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/forks","keys_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/keys{/key_id}","collaborators_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/teams","hooks_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/hooks","issue_events_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/issues/events{/number}","events_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/events","assignees_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/assignees{/user}","branches_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/branches{/branch}","tags_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/tags","blobs_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/git/refs{/sha}","trees_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/git/trees{/sha}","statuses_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/statuses/{sha}","languages_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/languages","stargazers_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/stargazers","contributors_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/contributors","subscribers_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/subscribers","subscription_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/subscription","commits_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/commits{/sha}","git_commits_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/git/commits{/sha}","comments_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/comments{/number}","issue_comment_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/issues/comments{/number}","contents_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/contents/{+path}","compare_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/compare/{base}...{head}","merges_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/merges","archive_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/downloads","issues_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/issues{/number}","pulls_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/pulls{/number}","milestones_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/milestones{/number}","notifications_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/labels{/name}","releases_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/releases{/id}","created_at":"2014-09-07T20:39:25Z","updated_at":"2015-08-22T16:05:26Z","pushed_at":"2015-08-23T09:23:14Z","git_url":"git://github.com/SpongePowered/SpongeAPI.git","ssh_url":"git@github.com:SpongePowered/SpongeAPI.git","clone_url":"https://github.com/SpongePowered/SpongeAPI.git","svn_url":"https://github.com/SpongePowered/SpongeAPI","homepage":"http://www.spongepowered.org/","size":25224,"stargazers_count":790,"watchers_count":790,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":344,"mirror_url":null,"open_issues_count":100,"forks":344,"open_issues":100,"watchers":790,"default_branch":"master"}},"base":{"label":"SpongePowered:master","ref":"master","sha":"aaf4e8ad2d9ee97da3f839f2ff896c62801a3e6b","user":{"login":"SpongePowered","id":8683473,"avatar_url":"https://avatars.githubusercontent.com/u/8683473?v=3","gravatar_id":"","url":"https://api.github.com/users/SpongePowered","html_url":"https://github.com/SpongePowered","followers_url":"https://api.github.com/users/SpongePowered/followers","following_url":"https://api.github.com/users/SpongePowered/following{/other_user}","gists_url":"https://api.github.com/users/SpongePowered/gists{/gist_id}","starred_url":"https://api.github.com/users/SpongePowered/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SpongePowered/subscriptions","organizations_url":"https://api.github.com/users/SpongePowered/orgs","repos_url":"https://api.github.com/users/SpongePowered/repos","events_url":"https://api.github.com/users/SpongePowered/events{/privacy}","received_events_url":"https://api.github.com/users/SpongePowered/received_events","type":"Organization","site_admin":false},"repo":{"id":23770421,"name":"SpongeAPI","full_name":"SpongePowered/SpongeAPI","owner":{"login":"SpongePowered","id":8683473,"avatar_url":"https://avatars.githubusercontent.com/u/8683473?v=3","gravatar_id":"","url":"https://api.github.com/users/SpongePowered","html_url":"https://github.com/SpongePowered","followers_url":"https://api.github.com/users/SpongePowered/followers","following_url":"https://api.github.com/users/SpongePowered/following{/other_user}","gists_url":"https://api.github.com/users/SpongePowered/gists{/gist_id}","starred_url":"https://api.github.com/users/SpongePowered/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SpongePowered/subscriptions","organizations_url":"https://api.github.com/users/SpongePowered/orgs","repos_url":"https://api.github.com/users/SpongePowered/repos","events_url":"https://api.github.com/users/SpongePowered/events{/privacy}","received_events_url":"https://api.github.com/users/SpongePowered/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/SpongePowered/SpongeAPI","description":"A Minecraft plugin API","fork":false,"url":"https://api.github.com/repos/SpongePowered/SpongeAPI","forks_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/forks","keys_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/keys{/key_id}","collaborators_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/teams","hooks_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/hooks","issue_events_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/issues/events{/number}","events_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/events","assignees_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/assignees{/user}","branches_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/branches{/branch}","tags_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/tags","blobs_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/git/refs{/sha}","trees_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/git/trees{/sha}","statuses_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/statuses/{sha}","languages_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/languages","stargazers_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/stargazers","contributors_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/contributors","subscribers_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/subscribers","subscription_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/subscription","commits_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/commits{/sha}","git_commits_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/git/commits{/sha}","comments_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/comments{/number}","issue_comment_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/issues/comments{/number}","contents_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/contents/{+path}","compare_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/compare/{base}...{head}","merges_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/merges","archive_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/downloads","issues_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/issues{/number}","pulls_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/pulls{/number}","milestones_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/milestones{/number}","notifications_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/labels{/name}","releases_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/releases{/id}","created_at":"2014-09-07T20:39:25Z","updated_at":"2015-08-22T16:05:26Z","pushed_at":"2015-08-23T09:23:14Z","git_url":"git://github.com/SpongePowered/SpongeAPI.git","ssh_url":"git@github.com:SpongePowered/SpongeAPI.git","clone_url":"https://github.com/SpongePowered/SpongeAPI.git","svn_url":"https://github.com/SpongePowered/SpongeAPI","homepage":"http://www.spongepowered.org/","size":25224,"stargazers_count":790,"watchers_count":790,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":344,"mirror_url":null,"open_issues_count":100,"forks":344,"open_issues":100,"watchers":790,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/SpongePowered/SpongeAPI/pulls/712"},"html":{"href":"https://github.com/SpongePowered/SpongeAPI/pull/712"},"issue":{"href":"https://api.github.com/repos/SpongePowered/SpongeAPI/issues/712"},"comments":{"href":"https://api.github.com/repos/SpongePowered/SpongeAPI/issues/712/comments"},"review_comments":{"href":"https://api.github.com/repos/SpongePowered/SpongeAPI/pulls/712/comments"},"review_comment":{"href":"https://api.github.com/repos/SpongePowered/SpongeAPI/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/SpongePowered/SpongeAPI/pulls/712/commits"},"statuses":{"href":"https://api.github.com/repos/SpongePowered/SpongeAPI/statuses/09ac13d931bfd566563a41b95aae72c10e1769aa"}},"merged":false,"mergeable":true,"mergeable_state":"unstable","merged_by":null,"comments":9,"review_comments":22,"commits":1,"additions":2140,"deletions":104,"changed_files":54}}