Add hardcore foreplay action pool
This commit is contained in:
@@ -265,6 +265,7 @@ NODE_INPUT_TOOLTIPS = {
|
||||
"allow_toys": "Allow toy/strap-on wording in hardcore actions.",
|
||||
"allow_double": "Allow double-penetration or second-contact wording.",
|
||||
"allow_penetration": "Allow vaginal/penetrative sex subcategories.",
|
||||
"allow_foreplay": "Allow hardcore teasing/foreplay setup actions such as kissing, caressing, breast/face touching, and undressing.",
|
||||
"allow_oral": "Allow oral sex subcategories.",
|
||||
"allow_outercourse": "Allow non-penetrative penis-contact acts such as boobjob/titjob, footjob, penis licking, and testicle sucking.",
|
||||
"allow_anal": "Allow anal subcategories.",
|
||||
@@ -1939,6 +1940,7 @@ class SxCPHardcoreActionFilter:
|
||||
"allow_toys": ("BOOLEAN", {"default": False}),
|
||||
"allow_double": ("BOOLEAN", {"default": False}),
|
||||
"allow_penetration": ("BOOLEAN", {"default": True}),
|
||||
"allow_foreplay": ("BOOLEAN", {"default": True}),
|
||||
"allow_oral": ("BOOLEAN", {"default": True}),
|
||||
"allow_outercourse": ("BOOLEAN", {"default": True}),
|
||||
"allow_anal": ("BOOLEAN", {"default": True}),
|
||||
@@ -1960,6 +1962,7 @@ class SxCPHardcoreActionFilter:
|
||||
allow_toys,
|
||||
allow_double,
|
||||
allow_penetration,
|
||||
allow_foreplay,
|
||||
allow_oral,
|
||||
allow_outercourse,
|
||||
allow_anal,
|
||||
@@ -1972,6 +1975,7 @@ class SxCPHardcoreActionFilter:
|
||||
allow_toys=allow_toys,
|
||||
allow_double=allow_double,
|
||||
allow_penetration=allow_penetration,
|
||||
allow_foreplay=allow_foreplay,
|
||||
allow_oral=allow_oral,
|
||||
allow_outercourse=allow_outercourse,
|
||||
allow_anal=allow_anal,
|
||||
|
||||
@@ -195,6 +195,22 @@
|
||||
"intense shameless eye contact",
|
||||
"spent satisfied expression"
|
||||
],
|
||||
"hardcore_foreplay_expressions": [
|
||||
"heavy-lidded teasing stare",
|
||||
"breathless close-range expression",
|
||||
"soft moan while being touched",
|
||||
"heated kissing expression",
|
||||
"hungry eye contact during undressing",
|
||||
"parted lips before a kiss",
|
||||
"flushed anticipation",
|
||||
"controlled teasing smile",
|
||||
"dazed aroused stare",
|
||||
"eyes half-closed during caressing",
|
||||
"mouth open against a kiss",
|
||||
"shameless pre-sex grin",
|
||||
"focused stare while clothing is pulled aside",
|
||||
"close-range lustful eye contact"
|
||||
],
|
||||
"hardcore_penetration_expressions": [
|
||||
"controlled eye contact during penetration",
|
||||
"focused adult pleasure face during thrusting",
|
||||
@@ -455,6 +471,18 @@
|
||||
"dramatic overhead studio crop",
|
||||
"tight vertical frame with props and body centered"
|
||||
],
|
||||
"foreplay_compositions": [
|
||||
{"text": "close body-contact frame centered on kissing, hands, and exposed skin", "min_people": 2, "max_people": 3},
|
||||
{"text": "standing close-range foreplay frame with faces and hand placement readable", "min_people": 2, "max_people": 3},
|
||||
{"text": "bed-edge undressing composition with clothing movement visible", "min_people": 2, "max_people": 3},
|
||||
{"text": "mirror-view undressing frame with faces, hands, and exposed skin visible", "min_people": 2, "max_people": 3},
|
||||
{"text": "tight crop on hands touching breasts, face, waist, and clothing", "min_people": 2, "max_people": 3},
|
||||
{"text": "couch foreplay composition with bodies pressed close", "min_people": 2, "max_people": 3},
|
||||
{"text": "wall-pressed kissing composition with hands on face and hips", "min_people": 2, "max_people": 3},
|
||||
{"text": "seated lap teasing composition with clothing being pulled aside", "min_people": 2, "max_people": 3},
|
||||
{"text": "close candid creator-shot frame centered on undressing and body contact", "min_people": 2, "max_people": 3},
|
||||
{"text": "full-body pre-sex foreplay frame with faces, hands, and clothes readable", "min_people": 2, "max_people": 3}
|
||||
],
|
||||
"hardcore_explicit_compositions": [
|
||||
{"text": "full-body explicit sex frame with all adult bodies visible", "min_people": 2},
|
||||
{"text": "bed-level camera angle focused on genital contact and faces", "min_people": 2, "max_people": 3},
|
||||
|
||||
@@ -84,6 +84,125 @@
|
||||
}
|
||||
],
|
||||
"subcategories": [
|
||||
{
|
||||
"name": "Foreplay and teasing",
|
||||
"slug": "foreplay_teasing",
|
||||
"min_people": 2,
|
||||
"min_women": 1,
|
||||
"inherit_expressions": false,
|
||||
"inherit_compositions": false,
|
||||
"weight": 0.75,
|
||||
"item_label": "Foreplay action",
|
||||
"positive_suffix": "Use clear adult body contact, readable hands and faces, visible undressing, exposed skin, warm erotic lighting, crisp comic linework, detailed hatching, and tactile textured paper.",
|
||||
"prompt_template": "{subject_phrase}, all 21+ consenting adults: {style}. Cast: {cast_summary}. Role graph: {role_graph} Foreplay action: {item}. Setting: {scene}. Composition: {composition}. Facial expressions: {expression}. Make the scene explicit through adult body contact, kissing, caressing, undressing, visible arousal, exposed skin, and readable hand placement. {positive_suffix} Avoid: {negative_prompt}.",
|
||||
"caption_template": "{trigger}, {scene_kind}, {cast_summary}, {role_graph}, foreplay teasing action: {item}, {scene}, {composition}, explicit consensual adult erotic foreplay illustration",
|
||||
"scene_pools": ["hardcore_private_scenes", "hardcore_bed_scenes", "hardcore_mirror_scenes"],
|
||||
"expression_pools": ["hardcore_foreplay_expressions"],
|
||||
"composition_pools": ["foreplay_compositions"],
|
||||
"item_templates": [
|
||||
"{tease_act} in {position}, with {touch_detail}, {clothing_detail}, and {mood_detail}",
|
||||
"{position} featuring {tease_act}, {body_contact}, {touch_detail}, and {visibility}",
|
||||
"hardcore foreplay setup: {tease_act}, {clothing_detail}, {face_detail}, and {body_contact}",
|
||||
"{tease_act} on {surface}, with {touch_detail}, {mood_detail}, and {visibility}",
|
||||
"{position} while {tease_act}, with {face_detail}, {clothing_detail}, and {touch_detail}"
|
||||
],
|
||||
"item_axes": {
|
||||
"position": [
|
||||
"standing kissing position",
|
||||
"bed-edge undressing position",
|
||||
"seated lap teasing position",
|
||||
"kneeling clothing-removal position",
|
||||
"side-lying caressing position",
|
||||
"mirror undressing position",
|
||||
"couch foreplay position",
|
||||
"wall-pressed kissing position"
|
||||
],
|
||||
"tease_act": [
|
||||
"deep kissing before sex",
|
||||
"mouth-to-mouth kissing with bodies pressed close",
|
||||
"slow caressing over bare skin",
|
||||
"hands roaming over the body before sex",
|
||||
"touching and squeezing breasts during foreplay",
|
||||
"one hand cupping a breast while kissing",
|
||||
"hand on the cheek and jaw during a heated kiss",
|
||||
"fingers under the chin guiding the face closer",
|
||||
"pulling clothing aside before sex",
|
||||
"slowly removing clothing with visible skin revealed",
|
||||
"sliding straps off shoulders and exposing the chest",
|
||||
"unbuttoning clothing while bodies press together"
|
||||
],
|
||||
"touch_detail": [
|
||||
"hands cupping breasts",
|
||||
"thumbs brushing nipples through parted clothing",
|
||||
"fingers tracing the waist and hips",
|
||||
"one hand gripping the ass",
|
||||
"one hand on the cheek and jaw",
|
||||
"fingers under the chin",
|
||||
"hands sliding under fabric",
|
||||
"hands pulling bodies closer",
|
||||
"fingers tangled in hair",
|
||||
"hands on the lower back"
|
||||
],
|
||||
"clothing_detail": [
|
||||
"clothing partly removed with fabric bunched at the waist",
|
||||
"shirt pulled open and straps sliding off shoulders",
|
||||
"bra or top pushed aside with chest exposed",
|
||||
"lower garments loosened while bodies stay pressed close",
|
||||
"clothing being pulled off one shoulder",
|
||||
"open shirt exposing bare chest and stomach",
|
||||
"fabric lifted to reveal skin while kissing",
|
||||
"discarded clothing visible nearby"
|
||||
],
|
||||
"face_detail": [
|
||||
"foreheads close and lips nearly touching",
|
||||
"mouths pressed together in a deep kiss",
|
||||
"cheek held in one hand",
|
||||
"chin lifted by fingertips",
|
||||
"heavy eye contact at close range",
|
||||
"breathless parted lips",
|
||||
"face turned toward the partner's hand",
|
||||
"hair pulled back gently from the face"
|
||||
],
|
||||
"body_contact": [
|
||||
"chests pressed together",
|
||||
"hips pressed close",
|
||||
"bodies leaning into each other",
|
||||
"one body pinned gently against the wall",
|
||||
"torso arched into the touch",
|
||||
"thighs pressed together",
|
||||
"standing close with bodies aligned",
|
||||
"seated close with legs interlaced"
|
||||
],
|
||||
"mood_detail": [
|
||||
"slow teasing buildup",
|
||||
"heated pre-sex tension",
|
||||
"intimate close-contact anticipation",
|
||||
"urgent hands and heavy breathing",
|
||||
"consensual teasing before the explicit act",
|
||||
"breathless pause before sex",
|
||||
"visible arousal before sex",
|
||||
"adult foreplay tension"
|
||||
],
|
||||
"visibility": [
|
||||
"hands, faces, and exposed skin clearly readable",
|
||||
"clothing movement and body contact visible",
|
||||
"breasts, hands, and faces centered",
|
||||
"face-touching and undressing action readable",
|
||||
"body contact visible before sex",
|
||||
"teasing hand placement clearly visible"
|
||||
],
|
||||
"surface": [
|
||||
"rumpled bed sheets",
|
||||
"a wide couch",
|
||||
"against a wall",
|
||||
"beside a mirror",
|
||||
"a hotel bed",
|
||||
"floor cushions",
|
||||
"a dressing-room bench",
|
||||
"a low mattress"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Penetrative sex",
|
||||
"slug": "penetrative_sex",
|
||||
|
||||
+36
-1
@@ -875,9 +875,42 @@ def _is_oral_text(*parts: Any) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def _is_foreplay_text(*parts: Any) -> bool:
|
||||
text = " ".join(_clean(part).lower() for part in parts if _clean(part))
|
||||
if not text:
|
||||
return False
|
||||
return any(
|
||||
term in text
|
||||
for term in (
|
||||
"foreplay",
|
||||
"pre-sex",
|
||||
"before sex",
|
||||
"before penetration",
|
||||
"kissing",
|
||||
"deep kiss",
|
||||
"mouth-to-mouth",
|
||||
"lips pressed",
|
||||
"caressing",
|
||||
"hands roaming",
|
||||
"stroking skin",
|
||||
"touching breasts",
|
||||
"cupping a breast",
|
||||
"hand on the cheek",
|
||||
"cheek and jaw",
|
||||
"fingers under the chin",
|
||||
"undressing",
|
||||
"removing clothing",
|
||||
"removing clothes",
|
||||
"pulling clothing",
|
||||
"sliding straps",
|
||||
"unbuttoning",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _is_vaginal_penetration_text(*parts: Any) -> bool:
|
||||
text = " ".join(_clean(part).lower() for part in parts if _clean(part))
|
||||
if not text or _is_outercourse_text(text) or _is_oral_text(text):
|
||||
if not text or _is_outercourse_text(text) or _is_oral_text(text) or _is_foreplay_text(text):
|
||||
return False
|
||||
if any(term in text for term in ("anal", "double penetration", "double-penetration", "toy-assisted", "strap-on")):
|
||||
return False
|
||||
@@ -952,6 +985,8 @@ def _hardcore_pose_anchor(role_graph: str, hard_item: str, composition: str = ""
|
||||
position_text = _clean(axis_values.get("position", "")).lower()
|
||||
if not text:
|
||||
return ""
|
||||
if _is_foreplay_text(role_graph, hard_item, composition, _axis_values_text(axis_values)):
|
||||
return ""
|
||||
if _is_outercourse_text(role_graph, hard_item, composition, _axis_values_text(axis_values)):
|
||||
if any(term in text for term in ("boobjob", "titjob", "breast sex", "breast-sex")):
|
||||
return "breast-sex outercourse pose"
|
||||
|
||||
+103
-5
@@ -299,6 +299,7 @@ HARDCORE_DETAIL_DENSITY_CHOICES = ["compact", "balanced", "dense"]
|
||||
HARDCORE_POSITION_FAMILY_CHOICES = [
|
||||
"any",
|
||||
"penetrative",
|
||||
"foreplay",
|
||||
"oral",
|
||||
"outercourse",
|
||||
"anal",
|
||||
@@ -309,6 +310,7 @@ HARDCORE_POSITION_FAMILY_CHOICES = [
|
||||
HARDCORE_POSITION_FOCUS_CHOICES = [
|
||||
"keep_pool",
|
||||
"penetration_only",
|
||||
"foreplay_only",
|
||||
"oral_only",
|
||||
"outercourse_only",
|
||||
"anal_only",
|
||||
@@ -334,6 +336,11 @@ HARDCORE_POSITION_KEY_CHOICES = [
|
||||
"straddled_oral",
|
||||
"spread_leg_oral",
|
||||
"chair_oral",
|
||||
"kissing",
|
||||
"caressing",
|
||||
"breast_touch",
|
||||
"face_touch",
|
||||
"undressing",
|
||||
"boobjob",
|
||||
"testicle_sucking",
|
||||
"penis_licking",
|
||||
@@ -345,6 +352,7 @@ HARDCORE_POSITION_KEY_CHOICES = [
|
||||
HARDCORE_POSITION_FAMILY_SUBCATEGORIES = {
|
||||
"any": [
|
||||
"penetrative_sex",
|
||||
"foreplay_teasing",
|
||||
"oral_sex",
|
||||
"outercourse_sex",
|
||||
"anal_double_penetration",
|
||||
@@ -353,6 +361,7 @@ HARDCORE_POSITION_FAMILY_SUBCATEGORIES = {
|
||||
"cumshot_climax",
|
||||
],
|
||||
"penetrative": ["penetrative_sex"],
|
||||
"foreplay": ["foreplay_teasing"],
|
||||
"oral": ["oral_sex"],
|
||||
"outercourse": ["outercourse_sex"],
|
||||
"anal": ["anal_double_penetration"],
|
||||
@@ -378,6 +387,11 @@ HARDCORE_POSITION_KEY_MATCHES = {
|
||||
"straddled_oral": ("straddled oral",),
|
||||
"spread_leg_oral": ("spread-leg", "spread leg", "reclining cunnilingus"),
|
||||
"chair_oral": ("chair oral",),
|
||||
"kissing": ("kiss", "kissing", "mouth-to-mouth", "mouth to mouth", "lips pressed"),
|
||||
"caressing": ("caress", "caressing", "hands roaming", "stroking skin", "hands sliding"),
|
||||
"breast_touch": ("breast", "breasts", "nipple", "cupping breasts", "touching breasts"),
|
||||
"face_touch": ("face", "cheek", "jaw", "chin", "hand on the cheek", "fingers under the chin"),
|
||||
"undressing": ("undressing", "removing clothing", "removing clothes", "pulling clothing", "sliding straps", "unbuttoning"),
|
||||
"boobjob": ("boobjob", "titjob", "breast-sex", "breast sex"),
|
||||
"testicle_sucking": ("testicle", "balls-licking", "balls licking", "balls and mouth"),
|
||||
"penis_licking": ("penis-licking", "penis licking", "tongue along", "tongue licking"),
|
||||
@@ -2332,6 +2346,7 @@ def _empty_hardcore_position_config() -> dict[str, Any]:
|
||||
"allow_toys": True,
|
||||
"allow_double": True,
|
||||
"allow_penetration": True,
|
||||
"allow_foreplay": True,
|
||||
"allow_oral": True,
|
||||
"allow_outercourse": True,
|
||||
"allow_anal": True,
|
||||
@@ -2356,7 +2371,16 @@ def _parse_hardcore_position_config(value: str | dict[str, Any] | None) -> dict[
|
||||
parsed["family"] = _normalize_hardcore_position_family(parsed.get("family"))
|
||||
parsed["positions"] = _normalize_hardcore_position_values(parsed.get("positions"))
|
||||
parsed["require_position"] = not _is_false(parsed.get("require_position", False))
|
||||
for key in ("allow_toys", "allow_double", "allow_penetration", "allow_oral", "allow_outercourse", "allow_anal", "allow_climax"):
|
||||
for key in (
|
||||
"allow_toys",
|
||||
"allow_double",
|
||||
"allow_penetration",
|
||||
"allow_foreplay",
|
||||
"allow_oral",
|
||||
"allow_outercourse",
|
||||
"allow_anal",
|
||||
"allow_climax",
|
||||
):
|
||||
parsed[key] = not _is_false(parsed.get(key, True))
|
||||
return parsed
|
||||
|
||||
@@ -2376,6 +2400,7 @@ def _hardcore_position_summary(config: dict[str, Any]) -> str:
|
||||
("allow_toys", "toys"),
|
||||
("allow_double", "double"),
|
||||
("allow_penetration", "penetration"),
|
||||
("allow_foreplay", "foreplay"),
|
||||
("allow_oral", "oral"),
|
||||
("allow_outercourse", "outercourse"),
|
||||
("allow_anal", "anal"),
|
||||
@@ -2420,6 +2445,7 @@ def build_hardcore_action_filter_json(
|
||||
allow_toys: bool = False,
|
||||
allow_double: bool = False,
|
||||
allow_penetration: bool = True,
|
||||
allow_foreplay: bool = True,
|
||||
allow_oral: bool = True,
|
||||
allow_outercourse: bool = True,
|
||||
allow_anal: bool = True,
|
||||
@@ -2430,6 +2456,7 @@ def build_hardcore_action_filter_json(
|
||||
focus = str(focus or "keep_pool").strip()
|
||||
focus_family = {
|
||||
"penetration_only": "penetrative",
|
||||
"foreplay_only": "foreplay",
|
||||
"oral_only": "oral",
|
||||
"outercourse_only": "outercourse",
|
||||
"anal_only": "anal",
|
||||
@@ -2442,6 +2469,7 @@ def build_hardcore_action_filter_json(
|
||||
config["allow_toys"] = bool(allow_toys)
|
||||
config["allow_double"] = bool(allow_double)
|
||||
config["allow_penetration"] = bool(allow_penetration)
|
||||
config["allow_foreplay"] = bool(allow_foreplay)
|
||||
config["allow_oral"] = bool(allow_oral)
|
||||
config["allow_outercourse"] = bool(allow_outercourse)
|
||||
config["allow_anal"] = bool(allow_anal)
|
||||
@@ -2452,6 +2480,7 @@ def build_hardcore_action_filter_json(
|
||||
family
|
||||
for enabled, family in (
|
||||
(config["allow_penetration"], "penetrative"),
|
||||
(config["allow_foreplay"], "foreplay"),
|
||||
(config["allow_oral"], "oral"),
|
||||
(config["allow_outercourse"], "outercourse"),
|
||||
(config["allow_anal"], "anal"),
|
||||
@@ -2462,7 +2491,9 @@ def build_hardcore_action_filter_json(
|
||||
if config["family"] in enabled_action_families and len(enabled_action_families) > 1:
|
||||
config["family"] = "any"
|
||||
|
||||
if focus == "oral_only":
|
||||
if focus == "foreplay_only":
|
||||
config["allow_foreplay"] = True
|
||||
elif focus == "oral_only":
|
||||
config["allow_oral"] = True
|
||||
config["allow_penetration"] = False
|
||||
elif focus == "outercourse_only":
|
||||
@@ -2497,6 +2528,8 @@ def _hardcore_allowed_subcategory_slugs(config: dict[str, Any]) -> set[str]:
|
||||
allowed = set(HARDCORE_POSITION_FAMILY_SUBCATEGORIES.get(family, HARDCORE_POSITION_FAMILY_SUBCATEGORIES["any"]))
|
||||
if not config.get("allow_penetration", True):
|
||||
allowed.difference_update({"penetrative_sex", "anal_double_penetration", "threesomes", "group_sex_orgy"})
|
||||
if not config.get("allow_foreplay", True):
|
||||
allowed.discard("foreplay_teasing")
|
||||
if not config.get("allow_oral", True):
|
||||
allowed.discard("oral_sex")
|
||||
if not config.get("allow_outercourse", True):
|
||||
@@ -2567,6 +2600,32 @@ def _hardcore_text_blocked_by_action(text: str, axis_name: str, config: dict[str
|
||||
or any(term in text for term in ("penetration", "penetrative", "thrust", "penis entering", "vaginal sex", "anal sex"))
|
||||
):
|
||||
return True
|
||||
if not config.get("allow_foreplay", True) and (
|
||||
axis_name in ("tease_act", "touch_detail", "clothing_detail", "foreplay_detail", "face_detail", "body_contact", "mood_detail")
|
||||
or any(
|
||||
term in text
|
||||
for term in (
|
||||
"kiss",
|
||||
"kissing",
|
||||
"mouth-to-mouth",
|
||||
"caress",
|
||||
"caressing",
|
||||
"stroking skin",
|
||||
"hands roaming",
|
||||
"touching breasts",
|
||||
"cupping breasts",
|
||||
"hand on the cheek",
|
||||
"fingers under the chin",
|
||||
"undressing",
|
||||
"removing clothing",
|
||||
"removing clothes",
|
||||
"pulling clothing",
|
||||
"sliding straps",
|
||||
"unbuttoning",
|
||||
)
|
||||
)
|
||||
):
|
||||
return True
|
||||
if not config.get("allow_climax", True) and (
|
||||
axis_name in ("climax_act", "climax_hint", "climax_detail", "fluid_detail", "fluid_location")
|
||||
or any(term in text for term in ("climax", "cum", "semen", "ejaculat", "creampie", "post-orgasm", "post-penetration"))
|
||||
@@ -5975,6 +6034,39 @@ def _role_graph(
|
||||
]
|
||||
return f" {extra} {rng.choice(actions)}."
|
||||
|
||||
def foreplay_position_graph(primary: str, partner: str) -> str:
|
||||
text = " ".join(
|
||||
str(part or "").lower()
|
||||
for part in (
|
||||
item_text,
|
||||
*((item_axis_values or {}).values()),
|
||||
)
|
||||
)
|
||||
if any(term in text for term in ("undressing", "removing clothing", "removing clothes", "pulling clothing", "sliding straps", "unbuttoning")):
|
||||
return (
|
||||
f"{primary} and {partner} stand close while {partner}'s hands pull clothing aside from {primary}'s body; "
|
||||
f"{primary}'s exposed skin and the clothing being removed stay clearly visible."
|
||||
)
|
||||
if any(term in text for term in ("breast", "breasts", "nipple", "cupping breasts", "touching breasts")):
|
||||
return (
|
||||
f"{primary} and {partner} press their bodies close while {partner}'s hand cups {primary}'s breast; "
|
||||
f"their faces stay close and the breast-touching gesture is clear."
|
||||
)
|
||||
if any(term in text for term in ("face", "cheek", "jaw", "chin", "hand on the cheek", "fingers under the chin")):
|
||||
return (
|
||||
f"{primary} and {partner} stand face-to-face at close range while one hand holds {primary}'s cheek and jaw; "
|
||||
f"their lips are close and the face-touching gesture is clear."
|
||||
)
|
||||
if any(term in text for term in ("kiss", "kissing", "mouth-to-mouth", "lips pressed")):
|
||||
return (
|
||||
f"{primary} and {partner} press their bodies together and kiss deeply, "
|
||||
f"with hands on each other's face, waist, and hips."
|
||||
)
|
||||
return (
|
||||
f"{primary} and {partner} are pressed close in a heated foreplay setup, "
|
||||
f"hands caressing skin while clothing is pulled aside."
|
||||
)
|
||||
|
||||
def mentions_ass(text: str) -> bool:
|
||||
return bool(
|
||||
re.search(
|
||||
@@ -6265,7 +6357,9 @@ def _role_graph(
|
||||
a, b = _pick_distinct(rng, women, 2)
|
||||
c = any_woman({a, b}) if len(women) >= 3 else ""
|
||||
used = {a, b}
|
||||
if "outercourse" in slug:
|
||||
if "foreplay" in slug:
|
||||
graph = foreplay_position_graph(a, b)
|
||||
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."
|
||||
@@ -6285,7 +6379,9 @@ def _role_graph(
|
||||
a, b = _pick_distinct(rng, men, 2)
|
||||
c = any_man({a, b}) if len(men) >= 3 else ""
|
||||
used = {a, b}
|
||||
if "outercourse" in slug:
|
||||
if "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."
|
||||
@@ -6305,7 +6401,9 @@ def _role_graph(
|
||||
woman = any_woman()
|
||||
man = any_man()
|
||||
third = any_person({woman, man}) if people_count >= 3 else ""
|
||||
if "outercourse" in slug:
|
||||
if "foreplay" in slug:
|
||||
graph = foreplay_position_graph(woman, man)
|
||||
elif "outercourse" in slug:
|
||||
graph = outercourse_position_graph(woman, man)
|
||||
elif "oral" in slug:
|
||||
graph = oral_position_graph(woman, man)
|
||||
|
||||
Reference in New Issue
Block a user