-
-
Notifications
You must be signed in to change notification settings - Fork 819
Feature: Custom Entity API #6010
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
onebeastchris
wants to merge
81
commits into
master
Choose a base branch
from
feature/custom-entities-api
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+3,897
−1,281
Open
Changes from all commits
Commits
Show all changes
81 commits
Select commit
Hold shift + click to select a range
5a7bf1b
Start working on custom entity API (for real this time)
eclipseisoffline f4156c6
Split EntityDefinition up into EntityDefinition and VanillaEntityDefi…
eclipseisoffline 0b8678e
Allow creating custom entity types from API
eclipseisoffline d843745
Further refactor EntityDefinition, create a Base class
eclipseisoffline 7fe8992
Work on exposing custom entity definitions in the API
eclipseisoffline e398c54
Add EntityDefinition#is
eclipseisoffline a3869cf
Implement CustomEntityDefinition, part one
eclipseisoffline 11e6b50
Move CustomEntityDefinition into custom package
onebeastchris 6fab587
CustomEntityDefinition should extend GeyserEntityDefinition
onebeastchris 3392510
Separate BedrockEntityDefinition from Entity classes
onebeastchris c57f250
EntityDefinition -> EntityTypeDefinition
onebeastchris 142cbae
Continue splitting bedrock entities from java types
onebeastchris 8ff763b
Width, height and offset should be tied to the Java entity type; and …
onebeastchris b2739e1
More work on events, closer to working
onebeastchris 52b76f5
Fix startup
onebeastchris 8f8e3be
Expose geyserId in API, add SessionAttachParrotsEvent
onebeastchris 9e6b133
Rename SessionAttachParrotsEvent -> ServerAttachParrotsEvent, fill in…
onebeastchris 75d5f07
Apply some suggestions
onebeastchris 5a16517
Another couple changes, start designing an entity data system to set …
onebeastchris 460305a
Api design goes brrr
onebeastchris b26b9d1
Implement dirty entities to send updates once a tick
onebeastchris 37f1285
Fix NPE in test
onebeastchris a648650
Attempt to add feature branch publishing
onebeastchris a497d3d
API polishing; properly initialize height/width
onebeastchris a3b3c72
Refactor: unify offset handling, always store Java entity position
onebeastchris f3070f7
Remove unused offsets
onebeastchris 8a79001
Remove debug print, validate that entity definitions have been regist…
onebeastchris d4f0713
Set up feature branch publishing and vertical offset applying
onebeastchris 899bd29
correct javadoc
onebeastchris a55e7b8
Merge branch 'master' into feature/custom-entities-api
onebeastchris 8509bf1
Merge branch 'master' into feature/custom-entities-api
onebeastchris 7efcda1
Merge remote-tracking branch 'upstream/feature/custom-entities-api' i…
onebeastchris b1c1c42
Merge remote-tracking branch 'upstream/master' into this-is-the-entit…
onebeastchris d5bdb7f
Merge branch 'master' into feature/custom-entities-api
onebeastchris a1d4f3e
Change entity definition log level to debug for missing definitions (…
QuickGlare 494420e
Fix villager inventories not showing up
onebeastchris ec23593
Fix interaction range check
onebeastchris 9f05f1e
Merge remote-tracking branch 'upstream/master' into feature/custom-en…
onebeastchris 199737d
Expose entity hitboxes in API, store bedrock position to avoid re-cal…
onebeastchris 2916f3f
Minor cleanup
onebeastchris adf4063
Log unknown implementations of GeyserEntityDefinition
onebeastchris 927b72f
Use position setter instead of directly setting entity position
onebeastchris 9aefb36
Fix client crashing related to sending & unlocking smithing recipes
onebeastchris ac29b54
Merge in upstream, we are on 1.21.11 now
onebeastchris a2003d1
Merge remote-tracking branch 'upstream/master' into feature/custom-en…
onebeastchris 7ef5998
Merge upstream
onebeastchris 053a505
Merge remote-tracking branch 'upstream/master' into feature/custom-en…
onebeastchris ea8d896
Fix hitbox querying, fix offset handling for boats
onebeastchris a52405c
Fix entity lerping
onebeastchris 6f31e26
Merge remote-tracking branch 'upstream/master' into feature/custom-en…
onebeastchris 9bb0d31
Merge remote-tracking branch 'upstream/master' into feature/custom-en…
onebeastchris af9a456
Merge remote-tracking branch 'upstream/master' into feature/custom-en…
onebeastchris c0cac81
Remove bedrockPosition variable again, simpler to only store one sour…
onebeastchris 3e6fdbc
Merge remote-tracking branch 'upstream/master' into feature/custom-en…
onebeastchris cdd11e7
Address issues
onebeastchris 25ddf22
Merge remote-tracking branch 'upstream/master' into feature/custom-en…
onebeastchris 241cb4c
Bump mcpl
onebeastchris 513a921
Merge branch 'master' into feature/custom-entities-api
onebeastchris 03b3463
Merge remote-tracking branch 'upstream/master' into feature/custom-en…
onebeastchris 8d39a39
make it build again
onebeastchris 906dd28
Merge remote-tracking branch 'upstream/master' into feature/custom-en…
onebeastchris d792f0e
Partially address review
onebeastchris 91a4c19
Merge remote-tracking branch 'upstream/master' into feature/custom-en…
onebeastchris b964780
fix le test
onebeastchris 5b9d645
Merge branch 'master' into feature/custom-entities-api
onebeastchris beb2498
Fix: ResourcePackClientResponsePacket's may contain empty pack lists …
onebeastchris 7d1a16a
Fix debug log formatting calls
onebeastchris 45d98e8
Merge remote-tracking branch 'upstream/master' into feature/custom-en…
onebeastchris bddacf7
Minor fixes, reviews, etc
onebeastchris c35cd1d
Support setting empty hitbox
onebeastchris 05c8c39
Merge branch 'master' into feature/custom-entities-api
onebeastchris 43a3fef
Merge remote-tracking branch 'upstream/master' into feature/custom-en…
onebeastchris 0f9ad38
Reviews and comments
onebeastchris d9e0c6b
Improve height / width handling
onebeastchris a21d9e8
Allow custom height / width to be 0
onebeastchris 61fbc7a
Merge remote-tracking branch 'upstream/master' into feature/custom-en…
onebeastchris 93ddfc7
Merge remote-tracking branch 'upstream/master' into feature/custom-en…
onebeastchris 34b1d98
Likely fix entity registration
onebeastchris ee0a40a
Merge branch 'master' into feature/custom-entities-api
onebeastchris e634ed6
Merge remote-tracking branch 'upstream/master' into feature/custom-en…
onebeastchris ee610d3
Merge remote-tracking branch 'upstream/feature/custom-entities-api' i…
onebeastchris File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletions
65
api/src/main/java/org/geysermc/geyser/api/entity/custom/CustomEntityDefinition.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| /* | ||
| * Copyright (c) 2025 GeyserMC. http://geysermc.org | ||
| * | ||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| * of this software and associated documentation files (the "Software"), to deal | ||
| * in the Software without restriction, including without limitation the rights | ||
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| * copies of the Software, and to permit persons to whom the Software is | ||
| * furnished to do so, subject to the following conditions: | ||
| * | ||
| * The above copyright notice and this permission notice shall be included in | ||
| * all copies or substantial portions of the Software. | ||
| * | ||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
| * THE SOFTWARE. | ||
| * | ||
| * @author GeyserMC | ||
| * @link https://github.com/GeyserMC/Geyser | ||
| */ | ||
|
|
||
| package org.geysermc.geyser.api.entity.custom; | ||
|
|
||
| import org.checkerframework.checker.nullness.qual.NonNull; | ||
| import org.geysermc.geyser.api.GeyserApi; | ||
| import org.geysermc.geyser.api.entity.definition.GeyserEntityDefinition; | ||
| import org.geysermc.geyser.api.util.Identifier; | ||
|
|
||
| /** | ||
| * Represents a custom entity definition for a non-vanilla, custom Bedrock entity. | ||
| */ | ||
| public interface CustomEntityDefinition extends GeyserEntityDefinition { | ||
|
|
||
| @Override | ||
| default boolean vanilla() { | ||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Creates or retrieves a GeyserEntityDefinition by the Bedrock entity type identifier. | ||
| * | ||
| * @param identifier the Bedrock entity identifier | ||
| * @return the CustomEntityDefinition | ||
| */ | ||
| static @NonNull CustomEntityDefinition of(@NonNull Identifier identifier) { | ||
| if (identifier.vanilla()) { | ||
| throw new IllegalArgumentException("Use GeyserEntityDefinition#of for vanilla entity lookups!"); | ||
| } | ||
| return GeyserApi.api().provider(CustomEntityDefinition.class, identifier); | ||
| } | ||
|
|
||
| /** | ||
| * Creates or retrieves a GeyserEntityDefinition by the Bedrock entity type identifier. | ||
| * | ||
| * @param identifier the Bedrock entity identifier, in string format | ||
| * @return the CustomEntityDefinition | ||
| */ | ||
| static @NonNull CustomEntityDefinition of(@NonNull String identifier) { | ||
| return of(Identifier.of(identifier)); | ||
| } | ||
| } | ||
89 changes: 89 additions & 0 deletions
89
api/src/main/java/org/geysermc/geyser/api/entity/custom/CustomJavaEntityType.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| /* | ||
| * Copyright (c) 2025 GeyserMC. http://geysermc.org | ||
| * | ||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| * of this software and associated documentation files (the "Software"), to deal | ||
| * in the Software without restriction, including without limitation the rights | ||
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| * copies of the Software, and to permit persons to whom the Software is | ||
| * furnished to do so, subject to the following conditions: | ||
| * | ||
| * The above copyright notice and this permission notice shall be included in | ||
| * all copies or substantial portions of the Software. | ||
| * | ||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
| * THE SOFTWARE. | ||
| * | ||
| * @author GeyserMC | ||
| * @link https://github.com/GeyserMC/Geyser | ||
| */ | ||
|
|
||
| package org.geysermc.geyser.api.entity.custom; | ||
|
|
||
| import org.checkerframework.checker.index.qual.NonNegative; | ||
| import org.checkerframework.checker.nullness.qual.Nullable; | ||
| import org.geysermc.geyser.api.entity.definition.GeyserEntityDefinition; | ||
| import org.geysermc.geyser.api.entity.definition.JavaEntityType; | ||
| import org.geysermc.geyser.api.event.lifecycle.GeyserDefineEntitiesEvent; | ||
| import org.geysermc.geyser.api.util.Identifier; | ||
|
|
||
| /** | ||
| * Represents a custom Minecraft: Java Edition entity type. | ||
| * This can only be used with modded servers! | ||
| */ | ||
| public interface CustomJavaEntityType extends JavaEntityType { | ||
|
|
||
| @Override | ||
| default boolean vanilla() { | ||
| return false; | ||
| } | ||
|
|
||
| interface Builder { | ||
|
|
||
| /** | ||
| * The entity type's identifier. It cannot be in the Minecraft namespace | ||
| * for custom entities! | ||
| * | ||
| * @param entityType the identifier | ||
| * @return this builder | ||
| */ | ||
| Builder type(Identifier entityType); | ||
|
|
||
| /** | ||
| * The entity type's numeric network id. | ||
| * @param javaId the java id | ||
| * @return this builder | ||
| */ | ||
| Builder javaId(int javaId); | ||
|
|
||
| /** | ||
| * The width of this entity. | ||
| * @param width the width of this entity | ||
| * @return this builder | ||
| */ | ||
| Builder width(@NonNegative float width); | ||
|
|
||
| /** | ||
| * The height of this entity | ||
| * @param height the height | ||
| * @return this builder | ||
| */ | ||
| Builder height(@NonNegative float height); | ||
|
|
||
| /** | ||
| * The default Bedrock edition entity definition. | ||
| * You can define custom Bedrock entities, or use vanilla definitions | ||
| * obtainable via the {@link GeyserDefineEntitiesEvent#entities()} collection. | ||
| * This entity has to be registered before calling this method! | ||
| * | ||
| * @param defaultBedrockDefinition the default Bedrock definition | ||
| * @return this builder | ||
| */ | ||
| Builder definition(@Nullable GeyserEntityDefinition defaultBedrockDefinition); | ||
| } | ||
| } |
68 changes: 68 additions & 0 deletions
68
api/src/main/java/org/geysermc/geyser/api/entity/data/GeyserEntityDataType.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| /* | ||
| * Copyright (c) 2025 GeyserMC. http://geysermc.org | ||
| * | ||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| * of this software and associated documentation files (the "Software"), to deal | ||
| * in the Software without restriction, including without limitation the rights | ||
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| * copies of the Software, and to permit persons to whom the Software is | ||
| * furnished to do so, subject to the following conditions: | ||
| * | ||
| * The above copyright notice and this permission notice shall be included in | ||
| * all copies or substantial portions of the Software. | ||
| * | ||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
| * THE SOFTWARE. | ||
| * | ||
| * @author GeyserMC | ||
| * @link https://github.com/GeyserMC/Geyser | ||
| */ | ||
|
|
||
| package org.geysermc.geyser.api.entity.data; | ||
|
|
||
| import org.checkerframework.checker.nullness.qual.NonNull; | ||
| import org.geysermc.geyser.api.GeyserApi; | ||
| import org.geysermc.geyser.api.entity.type.GeyserEntity; | ||
|
|
||
| /** | ||
| * Represents a type of entity data that can be sent for an entity. | ||
| * <p> | ||
| * Entity data types define the kind of value stored for a particular piece of metadata, | ||
| * such as a {@code Byte}, {@code Integer}, {@code Float}; and the name associated with them. | ||
| * <p> | ||
| * Unlike custom items or blocks, it is possible to update entity metadata at runtime, | ||
| * which can be done using {@link GeyserEntity#update(GeyserEntityDataType, Object)}. | ||
| * | ||
| * @param <T> the value type associated with this entity data type | ||
| */ | ||
| public interface GeyserEntityDataType<T> { | ||
|
|
||
| /** | ||
| * Gets the Java class representing the value type associated with this data type. | ||
| * | ||
| * @return the class of the value used by this entity data type | ||
| */ | ||
| @NonNull Class<T> typeClass(); | ||
|
|
||
| /** | ||
| * Gets the unique name of this data type. | ||
| * <p> | ||
| * The name is used internally to identify and register the data type so it can be | ||
| * referenced when reading or writing entity metadata. | ||
| * | ||
| * @return the name of this entity data type | ||
| */ | ||
| @NonNull String name(); | ||
|
|
||
| /** | ||
| * For API usage only; use the types defined in {@link GeyserEntityDataTypes} | ||
| */ | ||
| static <T> GeyserEntityDataType<T> of(@NonNull Class<T> typeClass, @NonNull String name) { | ||
| return GeyserApi.api().provider(GeyserEntityDataType.class, typeClass, name); | ||
| } | ||
| } |
87 changes: 87 additions & 0 deletions
87
api/src/main/java/org/geysermc/geyser/api/entity/data/GeyserEntityDataTypes.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| /* | ||
| * Copyright (c) 2025 GeyserMC. http://geysermc.org | ||
| * | ||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| * of this software and associated documentation files (the "Software"), to deal | ||
| * in the Software without restriction, including without limitation the rights | ||
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| * copies of the Software, and to permit persons to whom the Software is | ||
| * furnished to do so, subject to the following conditions: | ||
| * | ||
| * The above copyright notice and this permission notice shall be included in | ||
| * all copies or substantial portions of the Software. | ||
| * | ||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
| * THE SOFTWARE. | ||
| * | ||
| * @author GeyserMC | ||
| * @link https://github.com/GeyserMC/Geyser | ||
| */ | ||
|
|
||
| package org.geysermc.geyser.api.entity.data; | ||
onebeastchris marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| import org.geysermc.geyser.api.entity.data.types.Hitbox; | ||
|
|
||
| /** | ||
| * Contains commonly used {@link GeyserEntityDataType} constants for built-in entity | ||
| * metadata fields. | ||
| * <p> | ||
| * These data types define the structure of certain primitive or numeric metadata | ||
| * values that can be used for Bedrock entities. Each constant is backed by a | ||
| * pre-registered entity data type that can be used when reading or writing metadata | ||
| * through the Geyser API. | ||
| */ | ||
| public final class GeyserEntityDataTypes { | ||
|
|
||
| /** | ||
| * Represents a single-byte value used for color types | ||
| * (e.g., sheep wool color). | ||
| */ | ||
| public static final GeyserEntityDataType<Byte> COLOR = | ||
| GeyserEntityDataType.of(Byte.class, "color"); | ||
|
|
||
| /** | ||
| * Represents a numeric variant index that can be queried in resource packs. | ||
| */ | ||
| public static final GeyserEntityDataType<Integer> VARIANT = | ||
| GeyserEntityDataType.of(Integer.class, "variant"); | ||
|
|
||
| /** | ||
| * Represents the entity's width. | ||
| */ | ||
| public static final GeyserEntityDataType<Float> WIDTH = | ||
| GeyserEntityDataType.of(Float.class, "width"); | ||
|
|
||
| /** | ||
| * Represents the entity's height. | ||
| */ | ||
| public static final GeyserEntityDataType<Float> HEIGHT = | ||
| GeyserEntityDataType.of(Float.class, "height"); | ||
|
|
||
| /** | ||
| * Represents the entity's vertical offset. | ||
| */ | ||
| public static final GeyserEntityDataType<Float> VERTICAL_OFFSET = | ||
| GeyserEntityDataType.of(Float.class, "vertical_offset"); | ||
|
|
||
| /** | ||
| * Represents the scale multiplier. | ||
| */ | ||
| public static final GeyserEntityDataType<Float> SCALE = | ||
| GeyserEntityDataType.of(Float.class, "scale"); | ||
|
|
||
| /** | ||
| * Represents custom hitboxes for entities | ||
| */ | ||
| public static final GeyserListEntityDataType<Hitbox> HITBOXES = | ||
| GeyserListEntityDataType.of(Hitbox.class, "hitboxes"); | ||
|
|
||
| private GeyserEntityDataTypes() { | ||
| // no-op | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.