from __future__ import annotations import random from typing import Any try: from . import item_axis_policy from .hardcore_role_anal import build_anal_or_double_role_graph from .hardcore_role_climax import build_climax_role_graph from .hardcore_role_fallback import ( build_men_only_role_graph, build_mixed_group_fallback_role_graph, build_solo_role_graph, build_support_sentence, build_women_only_role_graph, ) from .hardcore_role_interaction import ( build_foreplay_role_graph, build_group_coordination_role_graph, build_interaction_role_graph, build_manual_role_graph, ) from .hardcore_role_oral import build_oral_role_graph from .hardcore_role_outercourse import build_outercourse_role_graph from .hardcore_role_penetration import build_penetration_role_graph except ImportError: # Allows local smoke tests with `python -c`. import item_axis_policy from hardcore_role_anal import build_anal_or_double_role_graph from hardcore_role_climax import build_climax_role_graph from hardcore_role_fallback import ( build_men_only_role_graph, build_mixed_group_fallback_role_graph, build_solo_role_graph, build_support_sentence, build_women_only_role_graph, ) from hardcore_role_interaction import ( build_foreplay_role_graph, build_group_coordination_role_graph, build_interaction_role_graph, build_manual_role_graph, ) from hardcore_role_oral import build_oral_role_graph from hardcore_role_outercourse import build_outercourse_role_graph from hardcore_role_penetration import build_penetration_role_graph def _lettered(prefix: str, count: int) -> list[str]: letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" return [f"{prefix.capitalize()} {letters[index]}" for index in range(max(0, count))] def _pick_distinct(rng: random.Random, items: list[str], count: int) -> list[str]: if not items: return [] if len(items) >= count: return rng.sample(items, count) picked = list(items) while len(picked) < count: picked.append(items[rng.randrange(len(items))]) return picked def _participant_context(women_count: int, men_count: int) -> dict[str, list[str]]: women = _lettered("woman", women_count) men = _lettered("man", men_count) return {"women": women, "men": men, "people": women + men} def build_hardcore_role_graph( rng: random.Random, subcategory: dict[str, Any], context: dict[str, Any], item_axis_values: dict[str, Any] | None = None, pov_labels: list[str] | None = None, ) -> str: if context.get("subject_type") != "configured_cast": return "" women_count = int(context.get("women_count") or 0) men_count = int(context.get("men_count") or 0) people_count = women_count + men_count if people_count <= 0: return "" participants = _participant_context(women_count, men_count) women = participants["women"] men = participants["men"] people = participants["people"] slug = str(subcategory.get("slug") or subcategory.get("name") or "").lower() item_text = item_axis_policy.context_text(axis_values=item_axis_values) def any_person(exclude: set[str] | None = None) -> str: exclude = exclude or set() pool = [person for person in people if person not in exclude] or people return rng.choice(pool) def any_woman(exclude: set[str] | None = None) -> str: exclude = exclude or set() pool = [person for person in women if person not in exclude] or [person for person in people if person not in exclude] or people return rng.choice(pool) def any_man(exclude: set[str] | None = None) -> str: exclude = exclude or set() pool = [person for person in men if person not in exclude] or [person for person in people if person not in exclude] or people return rng.choice(pool) if people_count == 1: return build_solo_role_graph(people[0], women_count, slug, item_text, item_axis_values) if women_count > 0 and men_count == 0: a, b = _pick_distinct(rng, women, 2) c = any_woman({a, b}) if len(women) >= 3 else "" used = {a, b} if any(token in slug for token in ("foreplay", "body_worship", "clothing_position", "dominant_guidance", "camera_performance", "aftercare")): graph = build_interaction_role_graph(a, b, c, slug, item_text, item_axis_values) if c and "camera_performance" in slug: used.add(c) elif "foreplay" in slug: graph = build_foreplay_role_graph(a, b, item_text, item_axis_values) else: graph, used = build_women_only_role_graph( slug, a, b, c, c or any_woman({a}), item_text, item_axis_values, ) return graph + build_support_sentence(rng, people, used) if men_count > 0 and women_count == 0: a, b = _pick_distinct(rng, men, 2) c = any_man({a, b}) if len(men) >= 3 else "" graph, used = build_men_only_role_graph( slug, a, b, c, c or any_man({a}), item_text, item_axis_values, ) return graph + build_support_sentence(rng, people, used) woman = any_woman() man = any_man() third = any_person({woman, man}) if people_count >= 3 else "" if "manual_stimulation" in slug: graph = build_manual_role_graph(woman, man, item_text, item_axis_values) elif "group_coordination" in slug: graph = build_group_coordination_role_graph( woman, man, third, any_person({woman, man}) if not third else "", item_text, item_axis_values, ) elif any(token in slug for token in ("foreplay", "body_worship", "clothing_position", "dominant_guidance", "camera_performance", "aftercare")): graph = build_interaction_role_graph(woman, man, third, slug, item_text, item_axis_values) elif "foreplay" in slug: graph = build_foreplay_role_graph(woman, man, item_text, item_axis_values) elif "outercourse" in slug: graph = build_outercourse_role_graph(woman, man, item_text, item_axis_values, pov_labels) elif "oral" in slug: graph = build_oral_role_graph(woman, man, item_text, item_axis_values, pov_labels) elif "anal" in slug or "double" in slug: graph = build_anal_or_double_role_graph(woman, man, third, people_count, item_text, item_axis_values) elif "threesome" in slug or "group" in slug or "orgy" in slug: graph = build_mixed_group_fallback_role_graph(woman, man, third, any_person({woman, man}), slug) elif "cumshot" in slug or "climax" in slug: graph = build_climax_role_graph(woman, man, third, item_text, item_axis_values) else: graph = build_penetration_role_graph(woman, man, item_text, item_axis_values) return graph + build_support_sentence(rng, people, {woman, man, third} if third else {woman, man})