Extract Insta pair cast context
This commit is contained in:
@@ -144,18 +144,18 @@ Owner today: `build_insta_of_pair`.
|
|||||||
|
|
||||||
Keep here:
|
Keep here:
|
||||||
|
|
||||||
- continuity policy;
|
- pair route sequencing;
|
||||||
- softcore cast policy;
|
- top-level continuity option handoff between row, camera, clothing, and output
|
||||||
|
adapters.
|
||||||
Improve later:
|
|
||||||
|
|
||||||
- split remaining pair cast/descriptor policy out of `build_insta_of_pair`.
|
|
||||||
|
|
||||||
Already isolated:
|
Already isolated:
|
||||||
|
|
||||||
- soft/hard row creation lives in `pair_rows.py`, including softcore expression
|
- soft/hard row creation lives in `pair_rows.py`, including softcore expression
|
||||||
override resolution, Woman A slot context application, soft outfit/pose
|
override resolution, Woman A slot context application, soft outfit/pose
|
||||||
overrides, POV row fields, and hardcore row creation.
|
overrides, POV row fields, and hardcore row creation.
|
||||||
|
- pair-level cast/display context lives in `pair_cast.py`, including shared
|
||||||
|
descriptors, same-cast softcore descriptor text, partner styling, platform and
|
||||||
|
level labels, softcore cast presence text, and hard cast summary text.
|
||||||
- pair-level camera routing lives in `pair_camera.py`, including soft/hard
|
- pair-level camera routing lives in `pair_camera.py`, including soft/hard
|
||||||
camera config selection, same-as-softcore mode, camera-detail override,
|
camera config selection, same-as-softcore mode, camera-detail override,
|
||||||
same-room hard scene continuity, camera-aware composition mutation, POV camera
|
same-room hard scene continuity, camera-aware composition mutation, POV camera
|
||||||
|
|||||||
@@ -370,11 +370,16 @@ flowchart TD
|
|||||||
L[location_config] --> P
|
L[location_config] --> P
|
||||||
M[composition_config] --> P
|
M[composition_config] --> P
|
||||||
H[hardcore_position_config] --> P
|
H[hardcore_position_config] --> P
|
||||||
P --> A[soft_row via build_prompt]
|
P --> R[pair_rows.py]
|
||||||
P --> B[hard_row via build_prompt]
|
R --> A[soft_row via build_prompt]
|
||||||
A --> X[pair metadata]
|
R --> B[hard_row via build_prompt]
|
||||||
B --> X
|
A --> D[pair_cast.py]
|
||||||
X --> K[Krea2/SDXL/Naturalizer]
|
B --> D
|
||||||
|
D --> X[pair metadata]
|
||||||
|
B --> Y[pair_camera.py + pair_clothing.py]
|
||||||
|
Y --> X
|
||||||
|
X --> Z[pair_output.py]
|
||||||
|
Z --> K[Krea2/SDXL/Naturalizer]
|
||||||
```
|
```
|
||||||
|
|
||||||
Softcore row:
|
Softcore row:
|
||||||
@@ -383,8 +388,8 @@ Softcore row:
|
|||||||
- Outfit comes from character slot `softcore_outfit` if present, otherwise
|
- Outfit comes from character slot `softcore_outfit` if present, otherwise
|
||||||
`INSTA_OF_SOFTCORE_OUTFITS`.
|
`INSTA_OF_SOFTCORE_OUTFITS`.
|
||||||
- Soft pose comes from `INSTA_OF_SOFTCORE_POSES`.
|
- Soft pose comes from `INSTA_OF_SOFTCORE_POSES`.
|
||||||
- Partner styling comes from `_insta_of_partner_styling` when softcore cast is
|
- Partner styling is resolved through `pair_cast.py` using
|
||||||
`same_as_hardcore`.
|
`_insta_of_partner_styling` when softcore cast is `same_as_hardcore`.
|
||||||
|
|
||||||
Hardcore row:
|
Hardcore row:
|
||||||
|
|
||||||
@@ -455,12 +460,12 @@ plain prompt text. When debugging, inspect these fields before editing pools.
|
|||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `mode` | `build_insta_of_pair` | Formatters | `Insta/OF` selects pair formatter branches. |
|
| `mode` | `build_insta_of_pair` | Formatters | `Insta/OF` selects pair formatter branches. |
|
||||||
| `options` | `SxCP Insta/OF Options` | Formatters/debug | Soft/hard level, cast mode, continuity, camera modes, expression settings. |
|
| `options` | `SxCP Insta/OF Options` | Formatters/debug | Soft/hard level, cast mode, continuity, camera modes, expression settings. |
|
||||||
| `shared_descriptor` | Soft row descriptor | Pair formatters | Primary creator descriptor. |
|
| `shared_descriptor` | `pair_cast.py` | Pair formatters | Primary creator descriptor. |
|
||||||
| `shared_cast_descriptors` | Cast descriptor builder | Pair formatters | Full cast descriptor list. |
|
| `shared_cast_descriptors` | `pair_cast.py` | Pair formatters | Full cast descriptor list. |
|
||||||
| `softcore_row`, `hardcore_row` | Pair route | Pair formatters | Full normal metadata rows for each side. |
|
| `softcore_row`, `hardcore_row` | Pair route | Pair formatters | Full normal metadata rows for each side. |
|
||||||
| `softcore_prompt`, `hardcore_prompt` | `pair_output.py` | Direct output/fallback | Raw pair prompts before formatter rewrite. |
|
| `softcore_prompt`, `hardcore_prompt` | `pair_output.py` | Direct output/fallback | Raw pair prompts before formatter rewrite. |
|
||||||
| `softcore_negative_prompt`, `hardcore_negative_prompt` | `pair_output.py` | Formatter negatives | Separate negatives for each side. |
|
| `softcore_negative_prompt`, `hardcore_negative_prompt` | `pair_output.py` | Formatter negatives | Separate negatives for each side. |
|
||||||
| `softcore_partner_styling` | `_insta_of_partner_styling` | Krea/SDXL pair branch | Partner softcore clothing and pose when same-cast softcore is enabled. |
|
| `softcore_partner_styling` | `pair_cast.py` | Krea/SDXL pair branch | Partner softcore clothing and pose when same-cast softcore is enabled. |
|
||||||
| `character_hardcore_clothing` | Character slots | Krea pair branch | Explicit per-character hardcore clothing state. |
|
| `character_hardcore_clothing` | Character slots | Krea pair branch | Explicit per-character hardcore clothing state. |
|
||||||
| `default_man_hardcore_clothing` | Pair fallback | Krea pair branch | Auto clothing for visible men without configured clothing. |
|
| `default_man_hardcore_clothing` | Pair fallback | Krea pair branch | Auto clothing for visible men without configured clothing. |
|
||||||
| `hardcore_clothing_state` | Pair clothing continuity | Krea/SDXL pair branch | Final hard clothing/body exposure sentence before Krea cleanup. |
|
| `hardcore_clothing_state` | Pair clothing continuity | Krea/SDXL pair branch | Final hard clothing/body exposure sentence before Krea cleanup. |
|
||||||
|
|||||||
+111
@@ -0,0 +1,111 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
|
|
||||||
|
DescriptorFromRow = Callable[[dict[str, Any]], str]
|
||||||
|
CastDescriptors = Callable[..., list[str]]
|
||||||
|
PartnerStyling = Callable[..., dict[str, Any]]
|
||||||
|
CastPhrase = Callable[[int, int], str]
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_insta_pair_cast_context(
|
||||||
|
*,
|
||||||
|
soft_row: dict[str, Any],
|
||||||
|
options: dict[str, Any],
|
||||||
|
parsed_seed_config: dict[str, int],
|
||||||
|
seed: int,
|
||||||
|
row_number: int,
|
||||||
|
ethnicity: str,
|
||||||
|
figure: str,
|
||||||
|
no_plus_women: bool,
|
||||||
|
no_black: bool,
|
||||||
|
hard_women_count: int,
|
||||||
|
hard_men_count: int,
|
||||||
|
character_slots: list[dict[str, Any]],
|
||||||
|
character_slot_map: dict[str, dict[str, Any]],
|
||||||
|
pov_character_labels: list[str],
|
||||||
|
platform_styles: dict[str, str],
|
||||||
|
soft_levels: dict[str, str],
|
||||||
|
hardcore_levels: dict[str, str],
|
||||||
|
descriptor_from_row: DescriptorFromRow,
|
||||||
|
build_cast_descriptors: CastDescriptors,
|
||||||
|
prompt_cast_descriptors: Callable[[str], str],
|
||||||
|
partner_styling: PartnerStyling,
|
||||||
|
cast_phrase: CastPhrase,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
descriptor = descriptor_from_row(soft_row)
|
||||||
|
cast_descriptors = build_cast_descriptors(
|
||||||
|
descriptor,
|
||||||
|
parsed_seed_config,
|
||||||
|
seed,
|
||||||
|
row_number,
|
||||||
|
ethnicity,
|
||||||
|
figure,
|
||||||
|
no_plus_women,
|
||||||
|
no_black,
|
||||||
|
hard_women_count,
|
||||||
|
hard_men_count,
|
||||||
|
character_slots,
|
||||||
|
)
|
||||||
|
cast_descriptor_text = prompt_cast_descriptors("; ".join(cast_descriptors))
|
||||||
|
same_softcore_cast = options["softcore_cast"] == "same_as_hardcore"
|
||||||
|
soft_cast_descriptor_text = cast_descriptor_text if same_softcore_cast else f"Woman A: {descriptor}"
|
||||||
|
soft_partner_styling = partner_styling(
|
||||||
|
parsed_seed_config,
|
||||||
|
seed,
|
||||||
|
row_number,
|
||||||
|
hard_women_count if same_softcore_cast else 1,
|
||||||
|
hard_men_count if same_softcore_cast else 0,
|
||||||
|
pov_character_labels if same_softcore_cast else [],
|
||||||
|
character_slot_map,
|
||||||
|
)
|
||||||
|
if not same_softcore_cast:
|
||||||
|
soft_partner_styling = {"outfits": [], "pose": ""}
|
||||||
|
soft_partner_outfit_text = "; ".join(soft_partner_styling["outfits"])
|
||||||
|
|
||||||
|
soft_cast = (
|
||||||
|
"solo creator setup with Woman A alone"
|
||||||
|
if options["softcore_cast"] == "solo"
|
||||||
|
else f"soft creator-teaser setup with {cast_phrase(hard_women_count, hard_men_count)}"
|
||||||
|
)
|
||||||
|
soft_cast_presence = (
|
||||||
|
(
|
||||||
|
"Frame Woman A from the POV participant's first-person camera in a soft creator-teaser setup; "
|
||||||
|
"keep the POV participant off-camera as the viewpoint and implied by camera perspective or foreground cues. "
|
||||||
|
)
|
||||||
|
if same_softcore_cast and pov_character_labels
|
||||||
|
else (
|
||||||
|
"Place Woman A and the listed partners together in a soft creator-teaser pose. "
|
||||||
|
if same_softcore_cast
|
||||||
|
else "Keep the softcore version focused on Woman A alone. "
|
||||||
|
)
|
||||||
|
)
|
||||||
|
soft_cast_styling_sentence = (
|
||||||
|
f"Partner softcore styling: {soft_partner_outfit_text}. Cast pose: {soft_partner_styling['pose']}. "
|
||||||
|
if same_softcore_cast and soft_partner_outfit_text
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
hard_cast = cast_phrase(hard_women_count, hard_men_count)
|
||||||
|
soft_descriptor_sentence = (
|
||||||
|
f"Cast descriptors: {soft_cast_descriptor_text}. "
|
||||||
|
if same_softcore_cast
|
||||||
|
else f"Woman A: {descriptor}. "
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"descriptor": descriptor,
|
||||||
|
"cast_descriptors": cast_descriptors,
|
||||||
|
"cast_descriptor_text": cast_descriptor_text,
|
||||||
|
"soft_cast_descriptor_text": soft_cast_descriptor_text,
|
||||||
|
"soft_partner_styling": soft_partner_styling,
|
||||||
|
"soft_partner_outfit_text": soft_partner_outfit_text,
|
||||||
|
"platform_style": platform_styles[options["platform_style"]],
|
||||||
|
"soft_level": soft_levels[options["softcore_level"]],
|
||||||
|
"hard_level": hardcore_levels[options["hardcore_level"]],
|
||||||
|
"soft_cast": soft_cast,
|
||||||
|
"soft_cast_presence": soft_cast_presence,
|
||||||
|
"soft_cast_styling_sentence": soft_cast_styling_sentence,
|
||||||
|
"hard_cast": hard_cast,
|
||||||
|
"soft_descriptor_sentence": soft_descriptor_sentence,
|
||||||
|
}
|
||||||
+38
-62
@@ -27,6 +27,7 @@ try:
|
|||||||
from . import generate_prompt_batches as g
|
from . import generate_prompt_batches as g
|
||||||
from . import pair_clothing
|
from . import pair_clothing
|
||||||
from . import pair_camera
|
from . import pair_camera
|
||||||
|
from . import pair_cast
|
||||||
from . import pair_output
|
from . import pair_output
|
||||||
from . import pair_rows
|
from . import pair_rows
|
||||||
from . import scene_camera_adapters
|
from . import scene_camera_adapters
|
||||||
@@ -60,6 +61,7 @@ except ImportError: # Allows local smoke tests with `python -c`.
|
|||||||
import generate_prompt_batches as g
|
import generate_prompt_batches as g
|
||||||
import pair_clothing
|
import pair_clothing
|
||||||
import pair_camera
|
import pair_camera
|
||||||
|
import pair_cast
|
||||||
import pair_output
|
import pair_output
|
||||||
import pair_rows
|
import pair_rows
|
||||||
import scene_camera_adapters
|
import scene_camera_adapters
|
||||||
@@ -7037,41 +7039,38 @@ def build_insta_of_pair(
|
|||||||
hard_row = row_route["hard_row"]
|
hard_row = row_route["hard_row"]
|
||||||
hard_content_rng = row_route["hard_content_rng"]
|
hard_content_rng = row_route["hard_content_rng"]
|
||||||
|
|
||||||
descriptor = _insta_of_descriptor(soft_row)
|
cast_context = pair_cast.resolve_insta_pair_cast_context(
|
||||||
cast_descriptors = _insta_of_cast_descriptors(
|
soft_row=soft_row,
|
||||||
descriptor,
|
options=options,
|
||||||
parsed_seed_config,
|
parsed_seed_config=parsed_seed_config,
|
||||||
seed,
|
seed=seed,
|
||||||
row_number,
|
row_number=row_number,
|
||||||
ethnicity,
|
ethnicity=ethnicity,
|
||||||
figure,
|
figure=figure,
|
||||||
no_plus_women,
|
no_plus_women=no_plus_women,
|
||||||
no_black,
|
no_black=no_black,
|
||||||
hard_women_count,
|
hard_women_count=hard_women_count,
|
||||||
hard_men_count,
|
hard_men_count=hard_men_count,
|
||||||
character_slots,
|
character_slots=character_slots,
|
||||||
|
character_slot_map=character_slot_map,
|
||||||
|
pov_character_labels=pov_character_labels,
|
||||||
|
platform_styles=INSTA_OF_PLATFORM_STYLES,
|
||||||
|
soft_levels=INSTA_OF_SOFT_LEVELS,
|
||||||
|
hardcore_levels=INSTA_OF_HARDCORE_LEVELS,
|
||||||
|
descriptor_from_row=_insta_of_descriptor,
|
||||||
|
build_cast_descriptors=_insta_of_cast_descriptors,
|
||||||
|
prompt_cast_descriptors=_insta_of_prompt_cast_descriptors,
|
||||||
|
partner_styling=_insta_of_partner_styling,
|
||||||
|
cast_phrase=_insta_of_cast_phrase,
|
||||||
)
|
)
|
||||||
cast_descriptor_text = _insta_of_prompt_cast_descriptors("; ".join(cast_descriptors))
|
descriptor = cast_context["descriptor"]
|
||||||
soft_cast_descriptor_text = (
|
cast_descriptors = cast_context["cast_descriptors"]
|
||||||
cast_descriptor_text
|
cast_descriptor_text = cast_context["cast_descriptor_text"]
|
||||||
if options["softcore_cast"] == "same_as_hardcore"
|
soft_partner_styling = cast_context["soft_partner_styling"]
|
||||||
else f"Woman A: {descriptor}"
|
soft_partner_outfit_text = cast_context["soft_partner_outfit_text"]
|
||||||
)
|
platform_style = cast_context["platform_style"]
|
||||||
soft_partner_styling = _insta_of_partner_styling(
|
soft_level = cast_context["soft_level"]
|
||||||
parsed_seed_config,
|
hard_level = cast_context["hard_level"]
|
||||||
seed,
|
|
||||||
row_number,
|
|
||||||
hard_women_count if options["softcore_cast"] == "same_as_hardcore" else 1,
|
|
||||||
hard_men_count if options["softcore_cast"] == "same_as_hardcore" else 0,
|
|
||||||
pov_character_labels if options["softcore_cast"] == "same_as_hardcore" else [],
|
|
||||||
character_slot_map,
|
|
||||||
)
|
|
||||||
if options["softcore_cast"] != "same_as_hardcore":
|
|
||||||
soft_partner_styling = {"outfits": [], "pose": ""}
|
|
||||||
soft_partner_outfit_text = "; ".join(soft_partner_styling["outfits"])
|
|
||||||
platform_style = INSTA_OF_PLATFORM_STYLES[options["platform_style"]]
|
|
||||||
soft_level = INSTA_OF_SOFT_LEVELS[options["softcore_level"]]
|
|
||||||
hard_level = INSTA_OF_HARDCORE_LEVELS[options["hardcore_level"]]
|
|
||||||
camera_route = pair_camera.resolve_insta_pair_camera(
|
camera_route = pair_camera.resolve_insta_pair_camera(
|
||||||
soft_row=soft_row,
|
soft_row=soft_row,
|
||||||
hard_row=hard_row,
|
hard_row=hard_row,
|
||||||
@@ -7104,29 +7103,10 @@ def build_insta_of_pair(
|
|||||||
hard_camera_scene_sentence = camera_route["hard_camera_scene_sentence"]
|
hard_camera_scene_sentence = camera_route["hard_camera_scene_sentence"]
|
||||||
soft_camera_sentence = camera_route["soft_camera_sentence"]
|
soft_camera_sentence = camera_route["soft_camera_sentence"]
|
||||||
hard_camera_sentence = camera_route["hard_camera_sentence"]
|
hard_camera_sentence = camera_route["hard_camera_sentence"]
|
||||||
soft_cast = (
|
soft_cast = cast_context["soft_cast"]
|
||||||
"solo creator setup with Woman A alone"
|
soft_cast_presence = cast_context["soft_cast_presence"]
|
||||||
if options["softcore_cast"] == "solo"
|
soft_cast_styling_sentence = cast_context["soft_cast_styling_sentence"]
|
||||||
else f"soft creator-teaser setup with {_insta_of_cast_phrase(hard_women_count, hard_men_count)}"
|
hard_cast = cast_context["hard_cast"]
|
||||||
)
|
|
||||||
soft_cast_presence = (
|
|
||||||
(
|
|
||||||
"Frame Woman A from the POV participant's first-person camera in a soft creator-teaser setup; "
|
|
||||||
"keep the POV participant off-camera as the viewpoint and implied by camera perspective or foreground cues. "
|
|
||||||
)
|
|
||||||
if options["softcore_cast"] == "same_as_hardcore" and pov_character_labels
|
|
||||||
else (
|
|
||||||
"Place Woman A and the listed partners together in a soft creator-teaser pose. "
|
|
||||||
if options["softcore_cast"] == "same_as_hardcore"
|
|
||||||
else "Keep the softcore version focused on Woman A alone. "
|
|
||||||
)
|
|
||||||
)
|
|
||||||
soft_cast_styling_sentence = (
|
|
||||||
f"Partner softcore styling: {soft_partner_outfit_text}. Cast pose: {soft_partner_styling['pose']}. "
|
|
||||||
if options["softcore_cast"] == "same_as_hardcore" and soft_partner_outfit_text
|
|
||||||
else ""
|
|
||||||
)
|
|
||||||
hard_cast = _insta_of_cast_phrase(hard_women_count, hard_men_count)
|
|
||||||
character_hardcore_clothing_entries = _character_hardcore_clothing_entries(
|
character_hardcore_clothing_entries = _character_hardcore_clothing_entries(
|
||||||
character_slot_map,
|
character_slot_map,
|
||||||
hard_women_count,
|
hard_women_count,
|
||||||
@@ -7160,11 +7140,7 @@ def build_insta_of_pair(
|
|||||||
"dense": "Use dense but coherent motion, contact, and aftermath detail while keeping one readable body position. ",
|
"dense": "Use dense but coherent motion, contact, and aftermath detail while keeping one readable body position. ",
|
||||||
}[hard_detail_density]
|
}[hard_detail_density]
|
||||||
pov_directive = _pov_prompt_directive(pov_character_labels)
|
pov_directive = _pov_prompt_directive(pov_character_labels)
|
||||||
soft_descriptor_sentence = (
|
soft_descriptor_sentence = cast_context["soft_descriptor_sentence"]
|
||||||
f"Cast descriptors: {soft_cast_descriptor_text}. "
|
|
||||||
if options["softcore_cast"] == "same_as_hardcore"
|
|
||||||
else f"Woman A: {descriptor}. "
|
|
||||||
)
|
|
||||||
|
|
||||||
return pair_output.assemble_insta_pair_metadata(
|
return pair_output.assemble_insta_pair_metadata(
|
||||||
active_trigger=active_trigger,
|
active_trigger=active_trigger,
|
||||||
|
|||||||
@@ -630,6 +630,8 @@ def _expect_pair(pair: dict[str, Any], name: str) -> None:
|
|||||||
_expect(pair.get("mode") == "Insta/OF", f"{name}.mode should be Insta/OF")
|
_expect(pair.get("mode") == "Insta/OF", f"{name}.mode should be Insta/OF")
|
||||||
_expect_row_base(pair.get("softcore_row") or {}, f"{name}.softcore_row")
|
_expect_row_base(pair.get("softcore_row") or {}, f"{name}.softcore_row")
|
||||||
_expect_custom_row(pair.get("hardcore_row") or {}, f"{name}.hardcore_row")
|
_expect_custom_row(pair.get("hardcore_row") or {}, f"{name}.hardcore_row")
|
||||||
|
_expect_text(f"{name}.shared_descriptor", pair.get("shared_descriptor"), 12)
|
||||||
|
_expect(pair.get("shared_cast_descriptors"), f"{name}.shared_cast_descriptors should not be empty")
|
||||||
_expect_text(f"{name}.softcore_prompt", pair.get("softcore_prompt"), 20)
|
_expect_text(f"{name}.softcore_prompt", pair.get("softcore_prompt"), 20)
|
||||||
_expect_text(f"{name}.hardcore_prompt", pair.get("hardcore_prompt"), 20)
|
_expect_text(f"{name}.hardcore_prompt", pair.get("hardcore_prompt"), 20)
|
||||||
_expect_trigger_once(f"{name}.softcore_prompt", pair.get("softcore_prompt"), Trigger)
|
_expect_trigger_once(f"{name}.softcore_prompt", pair.get("softcore_prompt"), Trigger)
|
||||||
@@ -662,6 +664,9 @@ def smoke_insta_pair() -> None:
|
|||||||
clothing_state = _clean_key(pair.get("hardcore_clothing_state"))
|
clothing_state = _clean_key(pair.get("hardcore_clothing_state"))
|
||||||
_expect("body is fully exposed" in clothing_state, "explicit nude pair should keep body exposure state")
|
_expect("body is fully exposed" in clothing_state, "explicit nude pair should keep body exposure state")
|
||||||
_expect("teaser outfit detail" not in clothing_state, "explicit nude pair should not repeat softcore outfit detail")
|
_expect("teaser outfit detail" not in clothing_state, "explicit nude pair should not repeat softcore outfit detail")
|
||||||
|
partner_styling = pair.get("softcore_partner_styling") or {}
|
||||||
|
_expect(partner_styling.get("outfits"), "same-cast pair should keep partner softcore outfit styling")
|
||||||
|
_expect_text("insta_pair_same_cast.partner_pose", partner_styling.get("pose"), 12)
|
||||||
|
|
||||||
|
|
||||||
def smoke_krea_pair_clothing_state() -> None:
|
def smoke_krea_pair_clothing_state() -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user