Skip to content

Commit

Permalink
add support for alt styles
Browse files Browse the repository at this point in the history
  • Loading branch information
diceroll123 committed Aug 1, 2024
1 parent 7c5e6be commit 4256245
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 23 deletions.
4 changes: 4 additions & 0 deletions dti/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ async def fetch_neopet(
*,
species: int | str | Species,
color: int | str | Color,
alt_style_id: int | None = None,
item_names: list[str] | None = None,
item_ids: list[int] | None = None,
size: LayerImageSize | None = None,
Expand All @@ -238,6 +239,8 @@ async def fetch_neopet(
The name, or ID, or Species object of the desired Species. Case-insensitive.
color: Union[:class:`int`, :class:`str`, :class:`Color`]
The name, or ID, or Color object of the desired Color. Case-insensitive.
alt_style_id: Optional[:class:`int`]
The ID of the alternative/nostalgic style you'd like to use. If one is not supplied, it defaults to `None`.
item_names: Optional[List[:class:`str`]]
A list of item names to search for + add to the items of the Neopet.
item_ids: Optional[List[:class:`int`]]
Expand Down Expand Up @@ -287,6 +290,7 @@ async def fetch_neopet(
return await Neopet._fetch_assets_for( # type: ignore
species=species,
color=color,
alt_style_id=alt_style_id,
item_names=item_names,
item_ids=item_ids,
size=size,
Expand Down
12 changes: 6 additions & 6 deletions dti/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,11 @@

SEARCH_TO_FIT = (
"""
query($query: String!, $fitsPet: FitsPetSearchFilter!, $speciesId: ID!, $colorId: ID!, $itemKind: ItemKindSearchFilter, $offset: Int, $limit: Int, $size: LayerImageSize!) {
query($query: String!, $fitsPet: FitsPetSearchFilter!, $speciesId: ID!, $colorId: ID!, $altStyleId: ID, $itemKind: ItemKindSearchFilter, $offset: Int, $limit: Int, $size: LayerImageSize!) {
itemSearch(query: $query, fitsPet: $fitsPet, itemKind: $itemKind, offset: $offset, limit: $limit) {
items {
...ItemProperties
appearanceOn(speciesId: $speciesId, colorId: $colorId) {
appearanceOn(speciesId: $speciesId, colorId: $colorId, altStyleId: $altStyleId) {
...ItemAppearanceForOutfitPreview
}
}
Expand Down Expand Up @@ -144,13 +144,13 @@
# grab pet appearances
GRAB_PET_APPEARANCE_WITH_ITEMS_BY_IDS = (
"""
query ($allItemIds: [ID!]!, $speciesId: ID!, $colorId: ID!, $size: LayerImageSize!, $pose: Pose!) {
query ($allItemIds: [ID!]!, $speciesId: ID!, $colorId: ID!, $altStyleId: ID, $size: LayerImageSize!, $pose: Pose!) {
petAppearance(speciesId: $speciesId, colorId: $colorId, pose: $pose) {
...PetAppearanceForOutfitPreview
}
items(ids: $allItemIds) {
...ItemProperties
appearanceOn(speciesId: $speciesId, colorId: $colorId) {
appearanceOn(speciesId: $speciesId, colorId: $colorId, altStyleId: $altStyleId) {
...ItemAppearanceForOutfitPreview
}
}
Expand All @@ -162,13 +162,13 @@

GRAB_PET_APPEARANCE_WITH_ITEMS_BY_NAMES = (
"""
query ($names: [String!]!, $speciesId: ID!, $colorId: ID!, $size: LayerImageSize!, $pose: Pose!) {
query ($names: [String!]!, $speciesId: ID!, $colorId: ID!, $altStyleId: ID, $size: LayerImageSize!, $pose: Pose!) {
petAppearance(speciesId: $speciesId, colorId: $colorId, pose: $pose) {
...PetAppearanceForOutfitPreview
}
items: itemsByName(names: $names) {
...ItemProperties
appearanceOn(speciesId: $speciesId, colorId: $colorId) {
appearanceOn(speciesId: $speciesId, colorId: $colorId, altStyleId: $altStyleId) {
...ItemAppearanceForOutfitPreview
}
}
Expand Down
2 changes: 2 additions & 0 deletions dti/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ async def fetch_assets_for(
*,
species: Species,
color: Color,
alt_style_id: int | None = None,
pose: PetPose,
item_ids: Sequence[ID] | None = None,
item_names: Sequence[str] | None = None,
Expand All @@ -235,6 +236,7 @@ async def fetch_assets_for(
"colorId": color.id,
"size": str(size),
"pose": str(pose),
"altStyleId": alt_style_id,
}

if item_names:
Expand Down
155 changes: 140 additions & 15 deletions dti/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import TYPE_CHECKING, Any, overload
from urllib.parse import urlencode


from . import utils
from .enums import (
AppearanceLayerKnownGlitch,
Expand All @@ -28,8 +29,6 @@
import os
from collections.abc import Sequence

from dti.types import FetchAssetsPayload, FetchedNeopetPayload

from .state import BitField, State
from .types import (
ID,
Expand All @@ -41,6 +40,10 @@
PetAppearancePayload,
SpeciesPayload,
ZonePayload,
FetchAssetsPayload,
FetchedNeopetPayload,
BodyPayload,
AltStylePayload,
)

__all__: tuple[str, ...] = (
Expand Down Expand Up @@ -426,6 +429,7 @@ class PetAppearance(Object):
"restricted_zones",
"size",
"species",
"alt_style_id",
)

def __init__(
Expand All @@ -434,21 +438,35 @@ def __init__(
state: State,
size: LayerImageSize,
data: PetAppearancePayload,
alt_style_id: int | None = None,
) -> None:
self._state = state
self.id: int = int(data["id"])
self.body_id: int = int(data["bodyId"])
self.is_glitched: bool = data["isGlitched"]
self.size: LayerImageSize = size
self.alt_style_id: int | None = alt_style_id

# create new, somewhat temporary colors from this data since we don't have async access
self.color: Color = Color(data=data["color"], state=state)
self.species: Species = Species(data=data["species"], state=state)

self.pose: PetPose = PetPose(data["pose"])
self.layers: list[AppearanceLayer] = [
AppearanceLayer(parent=self, data=layer) for layer in data["layers"]
]

alt_style = None
if self.alt_style_id is not None:
alt_style = state.get_alt_style_by_id(self.alt_style_id)

if alt_style is None:
self.layers: list[AppearanceLayer] = [
AppearanceLayer(parent=self, data=layer) for layer in data["layers"]
]
else:
self.layers: list[AppearanceLayer] = [
AppearanceLayer(parent=self, data=layer)
for layer in alt_style.layers_list
]

self.restricted_zones: list[Zone] = [
Zone(restricted) for restricted in data["restrictedZones"]
]
Expand All @@ -461,7 +479,12 @@ def has_glitches(self) -> bool:
@property
def url(self) -> str:
""":class:`str`: The URL of this pet appearance as an editable outfit."""
return f"https://impress.openneo.net/outfits/new?species={self.species.id}&color={self.color.id}&pose={self.pose.name}&state={self.id}"
url = f"https://impress.openneo.net/outfits/new?species={self.species.id}&color={self.color.id}&pose={self.pose.name}&state={self.id}"

if self.alt_style_id:
url += f"&style={self.alt_style_id}"

return url

def __repr__(self) -> str:
attrs: list[tuple[str, Any]] = [
Expand Down Expand Up @@ -851,6 +874,8 @@ class Neopet:
A list of the items that will be applied to the pet. Can be empty.
name: Optional[:class:`str`]
The name of the Neopet, if one is supplied.
alt_style_id: Optional[:class:`int`]
The ID of the alternative style of the pet, if one is supplied.
"""

Expand All @@ -864,6 +889,7 @@ class Neopet:
"pose",
"size",
"species",
"alt_style_id",
)

def __init__(
Expand All @@ -877,6 +903,7 @@ def __init__(
items: Sequence[Item] | None = None,
size: LayerImageSize | None = None,
name: str | None = None,
alt_style_id: int | None = None,
state: State,
) -> None:
self._state: State = state
Expand All @@ -888,13 +915,15 @@ def __init__(
self.size: LayerImageSize = size or LayerImageSize.SIZE_600
self.pose: PetPose = pose
self._valid_poses: BitField = valid_poses
self.alt_style_id: int | None = alt_style_id

@classmethod
async def _fetch_assets_for(
cls,
*,
species: Species,
color: Color,
alt_style_id: int | None = None,
pose: PetPose,
item_ids: Sequence[ID] | None = None,
item_names: Sequence[str] | None = None,
Expand All @@ -911,9 +940,19 @@ async def _fetch_assets_for(

size = size or LayerImageSize.SIZE_600

if alt_style_id is None:
alt_style = state.get_alt_style_by_species_color_pose(
species_id=species.id,
color_id=color.id,
pose=pose,
)
if alt_style:
alt_style_id = alt_style.id

data: FetchAssetsPayload = await state.http.fetch_assets_for(
species=species,
color=color,
alt_style_id=alt_style_id,
pose=pose,
item_ids=item_ids,
item_names=item_names,
Expand All @@ -924,7 +963,12 @@ async def _fetch_assets_for(
Item(data=item, state=state) for item in data["items"] if item is not None
]

appearance = PetAppearance(data=data["petAppearance"], size=size, state=state)
appearance = PetAppearance(
data=data["petAppearance"],
size=size,
state=state,
alt_style_id=alt_style_id,
)

bit: BitField = await state._get_bit(species_id=species.id, color_id=color.id) # type: ignore

Expand All @@ -937,6 +981,7 @@ async def _fetch_assets_for(
appearance=appearance,
name=name,
size=size,
alt_style_id=alt_style_id,
state=state,
)

Expand All @@ -948,8 +993,7 @@ async def _from_appearance(
/,
*,
item: Item | None = None,
) -> Neopet:
...
) -> Neopet: ...

@overload
@classmethod
Expand All @@ -959,8 +1003,7 @@ async def _from_appearance(
/,
*,
items: Sequence[Item] | None = None,
) -> Neopet:
...
) -> Neopet: ...

@classmethod
async def _from_appearance(
Expand Down Expand Up @@ -1062,7 +1105,9 @@ def closet_url(self) -> str:
objects, closet = _render_items(self.items)
params["objects[]"] = [item.id for item in objects]
params["closet[]"] = [item.id for item in closet]
return f"https://impress.openneo.net/outfits/new?{urlencode(params, doseq=True)}"
return (
f"https://impress.openneo.net/outfits/new?{urlencode(params, doseq=True)}"
)

@property
def worn_items(self) -> list[Item]:
Expand Down Expand Up @@ -1157,7 +1202,12 @@ async def render(
pose=pose,
size=self.size,
)
pet_appearance = PetAppearance(data=data, size=self.size, state=self._state)
pet_appearance = PetAppearance(
data=data,
size=self.size,
state=self._state,
alt_style_id=self.alt_style_id,
)

await pet_appearance.render(fp, items=self.items, seek_begin=seek_begin)

Expand Down Expand Up @@ -1321,7 +1371,7 @@ def legacy_url(self) -> str:
@property
def url(self) -> str:
""":class:`str`: Returns the outfit URL for the ID provided.
Since the 2020 site is soon to be deprecated, this will redirect to the legacy URL.
"""
return self.legacy_url
Expand Down Expand Up @@ -1437,7 +1487,82 @@ def __repr__(self) -> str:
return f"<Outfit {joined}>"


# utility functions below
class AltStyle(Object):
"""Represents an Alternative Style for a Neopet.
Attributes
----------
id: :class:`int`
The alternative style's ID.
species_id: :class:`int`
The alternative style's species ID.
body_id: :class:`int`
The alternative style's body ID.
color_id: :class:`int`
The alternative style's color ID.
series_name: :class:`str`
The alternative style's series name. `Nostalgic` for example.
thumbnail_url: :class:`str`
The alternative style's thumbnail URL.
adjective_name: :class:`str`
The alternative style's adjective name. `Nostalgic Robot` for example.
swf_assets: List[:class:`dict`]
The alternative style's SWF assets.
"""

__slots__ = (
"id",
"body_id",
"species_id",
"color_id",
"series_name",
"thumbnail_url",
"adjective_name",
"swf_assets",
)

def __init__(self, data: AltStylePayload):
self.id = data["id"]
self.species_id = data["species_id"]
self.body_id = data["body_id"]
self.color_id = data["color_id"]
self.series_name = data["series_name"]
self.thumbnail_url = data["thumbnail_url"]
self.adjective_name = data["adjective_name"]
self.swf_assets = data["swf_assets"]

@property
def layers_list(self) -> list[AppearanceLayerPayload]:
layers = []
for asset in self.swf_assets:
layers.append(
{
"id": self.id,
"zone": asset["zone"],
"remoteId": asset["id"],
"bodyId": asset["body_id"],
"imageUrlV2": asset["urls"]["png"], # type: ignore
"knownGlitches": asset["known_glitches"],
}
)
return layers

def __repr__(self) -> str:
attrs: list[tuple[str, Any]] = [
("id", self.id),
("species_id", self.species_id),
("body_id", self.body_id),
("color_id", self.color_id),
("series_name", self.series_name),
("adjective_name", self.adjective_name),
("thumbnail_url", self.thumbnail_url),
]
joined = " ".join(starmap("{}={!r}".format, attrs))
return f"<AltStyle {joined}>"


# MARK: Utility Functions


def _render_items(items: Sequence[Item]) -> tuple[list[Item], list[Item]]:
Expand Down
Loading

0 comments on commit 4256245

Please sign in to comment.