mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2024-11-26 16:56:11 +00:00
[QOL] UI Improvement - Shiny Icon (#2372)
* Test changes to change variant label color based on Tint
* Revert "Test changes to change variant label"
This reverts commit 0ad0f62930
.
* minimal recovery changes
* Finalized recovery and implemented ShinyIcon
* add setY in MockSprite
* added back some unintentional removed code
* added shiny_icons
* Initial test for tier change
* implemented Shiny Icon full functionality
* slight setY change for non-Base starters
This commit is contained in:
parent
8f7f46c6f2
commit
c8db3bff4c
78
public/images/ui/shiny_icons.json
Normal file
78
public/images/ui/shiny_icons.json
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"textures": [
|
||||||
|
{
|
||||||
|
"image": "shiny_icons.png",
|
||||||
|
"format": "RGBA8888",
|
||||||
|
"size": {
|
||||||
|
"w": 15,
|
||||||
|
"h": 14
|
||||||
|
},
|
||||||
|
"scale": 1,
|
||||||
|
"frames": [
|
||||||
|
{
|
||||||
|
"filename": "0",
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"sourceSize": {
|
||||||
|
"w": 15,
|
||||||
|
"h": 14
|
||||||
|
},
|
||||||
|
"spriteSourceSize": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 15,
|
||||||
|
"h": 14
|
||||||
|
},
|
||||||
|
"frame": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 15,
|
||||||
|
"h": 14
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "1",
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"sourceSize": {
|
||||||
|
"w": 15,
|
||||||
|
"h": 14
|
||||||
|
},
|
||||||
|
"spriteSourceSize": {
|
||||||
|
"x": 15,
|
||||||
|
"y": 0,
|
||||||
|
"w": 15,
|
||||||
|
"h": 14
|
||||||
|
},
|
||||||
|
"frame": {
|
||||||
|
"x": 15,
|
||||||
|
"y": 0,
|
||||||
|
"w": 15,
|
||||||
|
"h": 14
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "2",
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"sourceSize": {
|
||||||
|
"w": 15,
|
||||||
|
"h": 14
|
||||||
|
},
|
||||||
|
"spriteSourceSize": {
|
||||||
|
"x": 30,
|
||||||
|
"y": 0,
|
||||||
|
"w": 15,
|
||||||
|
"h": 14
|
||||||
|
},
|
||||||
|
"frame": {
|
||||||
|
"x": 30,
|
||||||
|
"y": 0,
|
||||||
|
"w": 15,
|
||||||
|
"h": 14
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
BIN
public/images/ui/shiny_icons.png
Normal file
BIN
public/images/ui/shiny_icons.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 296 B |
@ -1,3 +1,5 @@
|
|||||||
|
import { VariantTier } from "#app/enums/variant-tier.js";
|
||||||
|
|
||||||
export type Variant = 0 | 1 | 2;
|
export type Variant = 0 | 1 | 2;
|
||||||
|
|
||||||
export type VariantSet = [Variant, Variant, Variant];
|
export type VariantSet = [Variant, Variant, Variant];
|
||||||
@ -16,3 +18,14 @@ export function getVariantTint(variant: Variant): integer {
|
|||||||
return 0xe81048;
|
return 0xe81048;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getVariantIcon(variant: Variant): integer {
|
||||||
|
switch (variant) {
|
||||||
|
case 0:
|
||||||
|
return VariantTier.STANDARD;
|
||||||
|
case 1:
|
||||||
|
return VariantTier.RARE;
|
||||||
|
case 2:
|
||||||
|
return VariantTier.EPIC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
5
src/enums/variant-tier.ts
Normal file
5
src/enums/variant-tier.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export enum VariantTier {
|
||||||
|
STANDARD,
|
||||||
|
RARE,
|
||||||
|
EPIC
|
||||||
|
}
|
@ -90,6 +90,7 @@ export class LoadingScene extends SceneBase {
|
|||||||
this.loadImage("shiny_star_small", "ui", "shiny_small.png");
|
this.loadImage("shiny_star_small", "ui", "shiny_small.png");
|
||||||
this.loadImage("shiny_star_small_1", "ui", "shiny_small_1.png");
|
this.loadImage("shiny_star_small_1", "ui", "shiny_small_1.png");
|
||||||
this.loadImage("shiny_star_small_2", "ui", "shiny_small_2.png");
|
this.loadImage("shiny_star_small_2", "ui", "shiny_small_2.png");
|
||||||
|
this.loadAtlas("shiny_icons", "ui");
|
||||||
this.loadImage("ha_capsule", "ui", "ha_capsule.png");
|
this.loadImage("ha_capsule", "ui", "ha_capsule.png");
|
||||||
this.loadImage("champion_ribbon", "ui", "champion_ribbon.png");
|
this.loadImage("champion_ribbon", "ui", "champion_ribbon.png");
|
||||||
this.loadImage("icon_spliced", "ui");
|
this.loadImage("icon_spliced", "ui");
|
||||||
|
@ -122,6 +122,10 @@ export default class MockSprite {
|
|||||||
return this.phaserSprite.setPositionRelative(source, x, y);
|
return this.phaserSprite.setPositionRelative(source, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setY(y) {
|
||||||
|
return this.phaserSprite.setY(y);
|
||||||
|
}
|
||||||
|
|
||||||
setCrop(x, y, width, height) {
|
setCrop(x, y, width, height) {
|
||||||
// Sets the crop size of this Game Object.
|
// Sets the crop size of this Game Object.
|
||||||
return this.phaserSprite.setCrop(x, y, width, height);
|
return this.phaserSprite.setCrop(x, y, width, height);
|
||||||
|
41
src/ui/settings/shiny_icons.json
Normal file
41
src/ui/settings/shiny_icons.json
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"textures": [
|
||||||
|
{
|
||||||
|
"image": "shiny_icons.png",
|
||||||
|
"format": "RGBA8888",
|
||||||
|
"size": {
|
||||||
|
"w": 45,
|
||||||
|
"h": 14
|
||||||
|
},
|
||||||
|
"scale": 1,
|
||||||
|
"frames": [
|
||||||
|
{
|
||||||
|
"filename": "0",
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"sourceSize": {
|
||||||
|
"w": 45,
|
||||||
|
"h": 14
|
||||||
|
},
|
||||||
|
"spriteSourceSize": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 15,
|
||||||
|
"h": 14
|
||||||
|
},
|
||||||
|
"frame": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 15,
|
||||||
|
"h": 14
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"app": "https://www.codeandweb.com/texturepacker",
|
||||||
|
"version": "3.0",
|
||||||
|
"smartupdate": "$TexturePacker:SmartUpdate:a3275c7504a9b35b288265e191b1d14c:420725f3fb73c2cac0ab3bbc0a46f2e1:3a8b8ca0f0e4be067dd46c07b78ee013$"
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { BattleSceneEventType, CandyUpgradeNotificationChangedEvent } from "../events/battle-scene";
|
import { BattleSceneEventType, CandyUpgradeNotificationChangedEvent } from "../events/battle-scene";
|
||||||
import { pokemonPrevolutions } from "#app/data/pokemon-evolutions";
|
import { pokemonPrevolutions } from "#app/data/pokemon-evolutions";
|
||||||
import { Variant, getVariantTint } from "#app/data/variant";
|
import { Variant, getVariantTint, getVariantIcon } from "#app/data/variant";
|
||||||
import { argbFromRgba } from "@material/material-color-utilities";
|
import { argbFromRgba } from "@material/material-color-utilities";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
|
import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
|
||||||
@ -199,6 +199,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
private pokemonCaughtCountText: Phaser.GameObjects.Text;
|
private pokemonCaughtCountText: Phaser.GameObjects.Text;
|
||||||
private pokemonHatchedIcon : Phaser.GameObjects.Sprite;
|
private pokemonHatchedIcon : Phaser.GameObjects.Sprite;
|
||||||
private pokemonHatchedCountText: Phaser.GameObjects.Text;
|
private pokemonHatchedCountText: Phaser.GameObjects.Text;
|
||||||
|
private pokemonShinyIcon: Phaser.GameObjects.Sprite;
|
||||||
private genOptionsText: Phaser.GameObjects.Text;
|
private genOptionsText: Phaser.GameObjects.Text;
|
||||||
|
|
||||||
private instructionsContainer: Phaser.GameObjects.Container;
|
private instructionsContainer: Phaser.GameObjects.Container;
|
||||||
@ -616,6 +617,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
this.pokemonHatchedIcon.setScale(0.8);
|
this.pokemonHatchedIcon.setScale(0.8);
|
||||||
this.pokemonCaughtHatchedContainer.add(this.pokemonHatchedIcon);
|
this.pokemonCaughtHatchedContainer.add(this.pokemonHatchedIcon);
|
||||||
|
|
||||||
|
this.pokemonShinyIcon = this.scene.add.sprite(14, 76, "shiny_icons");
|
||||||
|
this.pokemonShinyIcon.setOrigin(0.15, 0.2);
|
||||||
|
this.pokemonShinyIcon.setScale(1);
|
||||||
|
this.pokemonCaughtHatchedContainer.add ((this.pokemonShinyIcon));
|
||||||
|
|
||||||
this.pokemonHatchedCountText = addTextObject(this.scene, 24, 19, "0", TextStyle.SUMMARY_ALT);
|
this.pokemonHatchedCountText = addTextObject(this.scene, 24, 19, "0", TextStyle.SUMMARY_ALT);
|
||||||
this.pokemonHatchedCountText.setOrigin(0, 0);
|
this.pokemonHatchedCountText.setOrigin(0, 0);
|
||||||
this.pokemonCaughtHatchedContainer.add(this.pokemonHatchedCountText);
|
this.pokemonCaughtHatchedContainer.add(this.pokemonHatchedCountText);
|
||||||
@ -1506,11 +1512,17 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
switch (button) {
|
switch (button) {
|
||||||
case Button.CYCLE_SHINY:
|
case Button.CYCLE_SHINY:
|
||||||
if (this.canCycleShiny) {
|
if (this.canCycleShiny) {
|
||||||
starterAttributes.variant = !props.shiny ? props.variant : -1; // update shiny setting
|
const newVariant = props.variant;
|
||||||
this.setSpeciesDetails(this.lastSpecies, !props.shiny, undefined, undefined, props.shiny ? 0 : undefined, undefined, undefined);
|
this.setSpeciesDetails(this.lastSpecies, !props.shiny, undefined, undefined, props.shiny ? 0 : undefined, undefined, undefined);
|
||||||
if (this.dexAttrCursor & DexAttr.SHINY) {
|
if (this.dexAttrCursor & DexAttr.SHINY) {
|
||||||
this.scene.playSound("sparkle");
|
this.scene.playSound("sparkle");
|
||||||
|
// Set the variant label to the shiny tint
|
||||||
|
const tint = getVariantTint(newVariant);
|
||||||
|
this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant));
|
||||||
|
this.pokemonShinyIcon.setTint(tint);
|
||||||
|
this.pokemonShinyIcon.setVisible(true);
|
||||||
} else {
|
} else {
|
||||||
|
this.pokemonShinyIcon.setVisible(false);
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1593,13 +1605,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (newVariant !== props.variant);
|
} while (newVariant !== props.variant);
|
||||||
starterAttributes.variant = newVariant; // store the selected variant
|
|
||||||
this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, newVariant, undefined, undefined);
|
this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, newVariant, undefined, undefined);
|
||||||
|
|
||||||
// Cycle tint based on current sprite tint
|
// Cycle tint based on current sprite tint
|
||||||
const tint = getVariantTint(newVariant);
|
const tint = getVariantTint(newVariant);
|
||||||
this.variantLabel.setTint(tint);
|
this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant));
|
||||||
|
this.pokemonShinyIcon.setTint(tint);
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -1875,8 +1885,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
const defaultProps = this.scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
|
const defaultProps = this.scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
|
||||||
const variant = defaultProps.variant;
|
const variant = defaultProps.variant;
|
||||||
const tint = getVariantTint(variant);
|
const tint = getVariantTint(variant);
|
||||||
|
this.pokemonShinyIcon.setFrame(getVariantIcon(variant));
|
||||||
this.variantLabel.setTint(tint);
|
this.pokemonShinyIcon.setTint(tint);
|
||||||
this.setSpecies(species);
|
this.setSpecies(species);
|
||||||
this.updateInstructions();
|
this.updateInstructions();
|
||||||
}
|
}
|
||||||
@ -1924,6 +1934,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
setSpecies(species: PokemonSpecies) {
|
setSpecies(species: PokemonSpecies) {
|
||||||
this.speciesStarterDexEntry = species ? this.scene.gameData.dexData[species.speciesId] : null;
|
this.speciesStarterDexEntry = species ? this.scene.gameData.dexData[species.speciesId] : null;
|
||||||
this.dexAttrCursor = species ? this.scene.gameData.getSpeciesDefaultDexAttr(species, false, true) : 0n;
|
this.dexAttrCursor = species ? this.scene.gameData.getSpeciesDefaultDexAttr(species, false, true) : 0n;
|
||||||
@ -2045,9 +2057,17 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
this.pokemonHatchedIcon.setFrame(getEggTierForSpecies(species));
|
this.pokemonHatchedIcon.setFrame(getEggTierForSpecies(species));
|
||||||
}
|
}
|
||||||
this.pokemonHatchedCountText.setText(`${this.speciesStarterDexEntry.hatchedCount}`);
|
this.pokemonHatchedCountText.setText(`${this.speciesStarterDexEntry.hatchedCount}`);
|
||||||
|
const defaultDexAttr = this.scene.gameData.getSpeciesDefaultDexAttr(species, false, true);
|
||||||
|
const defaultProps = this.scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
|
||||||
|
const variant = defaultProps.variant;
|
||||||
|
const tint = getVariantTint(variant);
|
||||||
|
this.pokemonShinyIcon.setFrame(getVariantIcon(variant));
|
||||||
|
this.pokemonShinyIcon.setTint(tint);
|
||||||
this.pokemonCaughtHatchedContainer.setVisible(true);
|
this.pokemonCaughtHatchedContainer.setVisible(true);
|
||||||
if (pokemonPrevolutions.hasOwnProperty(species.speciesId)) {
|
if (pokemonPrevolutions.hasOwnProperty(species.speciesId)) {
|
||||||
this.pokemonCaughtHatchedContainer.setY(16);
|
this.pokemonCaughtHatchedContainer.setY(16);
|
||||||
|
this.pokemonShinyIcon.setY(135);
|
||||||
|
this.pokemonShinyIcon.setFrame(getVariantIcon(variant));
|
||||||
[
|
[
|
||||||
this.pokemonCandyIcon,
|
this.pokemonCandyIcon,
|
||||||
this.pokemonCandyOverlayIcon,
|
this.pokemonCandyOverlayIcon,
|
||||||
@ -2056,12 +2076,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
this.pokemonHatchedIcon,
|
this.pokemonHatchedIcon,
|
||||||
this.pokemonHatchedCountText
|
this.pokemonHatchedCountText
|
||||||
].map(c => c.setVisible(false));
|
].map(c => c.setVisible(false));
|
||||||
this.pokemonFormText.setY(25);
|
|
||||||
} else if (species.speciesId === Species.ETERNATUS) {
|
} else if (species.speciesId === Species.ETERNATUS) {
|
||||||
this.pokemonHatchedIcon.setVisible(false);
|
this.pokemonHatchedIcon.setVisible(false);
|
||||||
this.pokemonHatchedCountText.setVisible(false);
|
this.pokemonHatchedCountText.setVisible(false);
|
||||||
} else {
|
} else {
|
||||||
this.pokemonCaughtHatchedContainer.setY(25);
|
this.pokemonCaughtHatchedContainer.setY(25);
|
||||||
|
this.pokemonShinyIcon.setY(117);
|
||||||
this.pokemonCandyIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(colorScheme[0])));
|
this.pokemonCandyIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(colorScheme[0])));
|
||||||
this.pokemonCandyIcon.setVisible(true);
|
this.pokemonCandyIcon.setVisible(true);
|
||||||
this.pokemonCandyOverlayIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(colorScheme[1])));
|
this.pokemonCandyOverlayIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(colorScheme[1])));
|
||||||
@ -2070,8 +2090,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
this.pokemonCandyCountText.setText(`x${this.scene.gameData.starterData[species.speciesId].candyCount}`);
|
this.pokemonCandyCountText.setText(`x${this.scene.gameData.starterData[species.speciesId].candyCount}`);
|
||||||
this.pokemonCandyCountText.setVisible(true);
|
this.pokemonCandyCountText.setVisible(true);
|
||||||
this.pokemonFormText.setVisible(true);
|
this.pokemonFormText.setVisible(true);
|
||||||
this.pokemonFormText.setY(42);
|
|
||||||
this.pokemonHatchedIcon.setVisible(true);
|
this.pokemonHatchedIcon.setVisible(true);
|
||||||
|
this.pokemonShinyIcon.setVisible(true);
|
||||||
this.pokemonHatchedCountText.setVisible(true);
|
this.pokemonHatchedCountText.setVisible(true);
|
||||||
|
|
||||||
let currentFriendship = this.scene.gameData.starterData[this.lastSpecies.speciesId].friendship;
|
let currentFriendship = this.scene.gameData.starterData[this.lastSpecies.speciesId].friendship;
|
||||||
@ -2124,17 +2144,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
this.setSpeciesDetails(species, props.shiny, props.formIndex, props.female, props.variant, this.starterAbilityIndexes[starterIndex], this.starterNatures[starterIndex]);
|
this.setSpeciesDetails(species, props.shiny, props.formIndex, props.female, props.variant, this.starterAbilityIndexes[starterIndex], this.starterNatures[starterIndex]);
|
||||||
} else {
|
} else {
|
||||||
const defaultDexAttr = this.scene.gameData.getSpeciesDefaultDexAttr(species, false, true);
|
const defaultDexAttr = this.scene.gameData.getSpeciesDefaultDexAttr(species, false, true);
|
||||||
const defaultAbilityIndex = starterAttributes?.ability ?? this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species);
|
const defaultAbilityIndex = this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species);
|
||||||
// load default nature from stater save data, if set
|
const defaultNature = this.scene.gameData.getSpeciesDefaultNature(species);
|
||||||
const defaultNature = starterAttributes?.nature || this.scene.gameData.getSpeciesDefaultNature(species);
|
|
||||||
props = this.scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
|
props = this.scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
|
||||||
if (!isNaN(starterAttributes?.variant)) {
|
|
||||||
if (props.shiny = (starterAttributes.variant >= 0)) {
|
|
||||||
props.variant = starterAttributes.variant as Variant;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
props.formIndex = starterAttributes?.form ?? props.formIndex;
|
|
||||||
props.female = starterAttributes?.female ?? props.female;
|
|
||||||
|
|
||||||
this.setSpeciesDetails(species, props.shiny, props.formIndex, props.female, props.variant, defaultAbilityIndex, defaultNature);
|
this.setSpeciesDetails(species, props.shiny, props.formIndex, props.female, props.variant, defaultAbilityIndex, defaultNature);
|
||||||
}
|
}
|
||||||
@ -2197,6 +2209,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
setSpeciesDetails(species: PokemonSpecies, shiny: boolean, formIndex: integer, female: boolean, variant: Variant, abilityIndex: integer, natureIndex: integer, forSeen: boolean = false): void {
|
setSpeciesDetails(species: PokemonSpecies, shiny: boolean, formIndex: integer, female: boolean, variant: Variant, abilityIndex: integer, natureIndex: integer, forSeen: boolean = false): void {
|
||||||
const oldProps = species ? this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor) : null;
|
const oldProps = species ? this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor) : null;
|
||||||
const oldAbilityIndex = this.abilityCursor > -1 ? this.abilityCursor : this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species);
|
const oldAbilityIndex = this.abilityCursor > -1 ? this.abilityCursor : this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species);
|
||||||
|
Loading…
Reference in New Issue
Block a user