from __future__ import annotations import random from typing import Any try: from .hardcore_role_anal import build_anal_or_double_role_graph from .hardcore_role_climax import build_climax_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`. from hardcore_role_anal import build_anal_or_double_role_graph from hardcore_role_climax import build_climax_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 = " ".join((item_axis_values or {}).values()).lower() 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) def support_sentence(exclude: set[str]) -> str: extras = [person for person in people if person not in exclude] if not extras: return "" extra = rng.choice(extras) actions = [ "kisses and grips the nearest body", "holds hips open for the camera", "touches breasts, thighs, and stomach", "keeps one hand on a partner's ass", "watches close and joins the body contact", "presses in from the side with hands on skin", ] return f" {extra} {rng.choice(actions)}." if people_count == 1: solo = people[0] if women_count == 1: if "manual_stimulation" in slug: return build_manual_role_graph(solo, item_text=item_text, item_axis_values=item_axis_values) if "camera_performance" in slug: return f"{solo} faces the camera and presents her body with hands framing the exposed skin in a solo creator-shot pose." if "cumshot" in slug or "climax" in slug: return f"{solo} is shown in a solo explicit orgasm pose with thighs open, one hand on her body, and visible arousal on skin and sheets." return f"{solo} is shown in a solo explicit adult pose with self-touch, open body framing, and direct camera awareness." if "cumshot" in slug or "climax" in slug: return f"{solo} is shown in a solo visible ejaculation pose with one hand on his penis, body angled toward the camera, and semen visible." return f"{solo} is shown in a solo explicit adult pose with direct camera awareness and clear body framing." 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 "manual_stimulation" in slug: graph = build_manual_role_graph(a, b, item_text, item_axis_values) elif "group_coordination" in slug and c: graph = build_group_coordination_role_graph(a, b, c, item_text=item_text, item_axis_values=item_axis_values) used.add(c) elif 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) elif "outercourse" in slug: graph = f"{a} kneels close to {b}'s body and uses mouth, hands, breasts, or feet for explicit non-penetrative contact." elif "oral" in slug: graph = f"{a} kneels between {b}'s spread thighs and uses tongue and fingers on her pussy." elif "anal" in slug or "double" in slug: graph = f"{a} uses a strap-on on {b} while keeping her hips held open." elif "threesome" in slug or "group" in slug or "orgy" in slug: helper = c or any_woman({a}) graph = f"{a} uses a strap-on on {b} while {helper} gives oral contact and touches both bodies." used.add(helper) elif "cumshot" in slug or "climax" in slug: graph = f"{a} brings {b} to orgasm with mouth and fingers while wetness is visible on thighs and sheets." else: graph = f"{a} uses a strap-on on {b} while their bodies stay pressed together." return graph + support_sentence(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 "" used = {a, b} if "manual_stimulation" in slug: graph = f"{a} and {b} sit or recline close together with hands visibly stimulating bodies in a manual sex setup." elif "group_coordination" in slug and c: graph = build_group_coordination_role_graph(a, b, c, item_text=item_text, item_axis_values=item_axis_values) used.add(c) elif any(token in slug for token in ("foreplay", "body_worship", "clothing_position", "dominant_guidance", "camera_performance", "aftercare")): graph = f"{a} and {b} press close together, kissing and caressing skin while clothing is pulled aside." elif "foreplay" in slug: graph = f"{a} and {b} press close together, kissing and caressing skin while clothing is pulled aside." elif "outercourse" in slug: graph = f"{a} and {b} keep explicit non-penetrative penis contact visible with hands, mouth, or feet." elif "oral" in slug: graph = f"{a} kneels and takes {b}'s penis in his mouth while holding his hips." elif "anal" in slug or "double" in slug or "penetrative" in slug: graph = f"{a} penetrates {b} anally while {b}'s hips are held open." elif "threesome" in slug or "group" in slug or "orgy" in slug: helper = c or any_man({a}) graph = f"{a} penetrates {b} anally while {helper} gives oral contact from the front." used.add(helper) elif "cumshot" in slug or "climax" in slug: graph = f"{a} ejaculates semen over {b}'s body while {b} keeps eye contact and one hand on his penis." else: graph = f"{a} and {b} keep explicit penis and anal contact visible." return graph + support_sentence(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: graph = f"{man} thrusts his penis into {woman} while {third or any_person({woman, man})} uses mouth and hands on the exposed body." elif "group" in slug or "orgy" in slug: graph = f"{man} thrusts his penis into {woman} while surrounding partners give oral contact and keep hands on hips, breasts, and thighs." 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 + support_sentence({woman, man, third} if third else {woman, man})