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_toys": "Allow toy/strap-on wording in hardcore actions.",
|
||||||
"allow_double": "Allow double-penetration or second-contact wording.",
|
"allow_double": "Allow double-penetration or second-contact wording.",
|
||||||
"allow_penetration": "Allow vaginal/penetrative sex subcategories.",
|
"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_oral": "Allow oral sex subcategories.",
|
||||||
"allow_outercourse": "Allow non-penetrative penis-contact acts such as boobjob/titjob, footjob, penis licking, and testicle sucking.",
|
"allow_outercourse": "Allow non-penetrative penis-contact acts such as boobjob/titjob, footjob, penis licking, and testicle sucking.",
|
||||||
"allow_anal": "Allow anal subcategories.",
|
"allow_anal": "Allow anal subcategories.",
|
||||||
@@ -1939,6 +1940,7 @@ class SxCPHardcoreActionFilter:
|
|||||||
"allow_toys": ("BOOLEAN", {"default": False}),
|
"allow_toys": ("BOOLEAN", {"default": False}),
|
||||||
"allow_double": ("BOOLEAN", {"default": False}),
|
"allow_double": ("BOOLEAN", {"default": False}),
|
||||||
"allow_penetration": ("BOOLEAN", {"default": True}),
|
"allow_penetration": ("BOOLEAN", {"default": True}),
|
||||||
|
"allow_foreplay": ("BOOLEAN", {"default": True}),
|
||||||
"allow_oral": ("BOOLEAN", {"default": True}),
|
"allow_oral": ("BOOLEAN", {"default": True}),
|
||||||
"allow_outercourse": ("BOOLEAN", {"default": True}),
|
"allow_outercourse": ("BOOLEAN", {"default": True}),
|
||||||
"allow_anal": ("BOOLEAN", {"default": True}),
|
"allow_anal": ("BOOLEAN", {"default": True}),
|
||||||
@@ -1960,6 +1962,7 @@ class SxCPHardcoreActionFilter:
|
|||||||
allow_toys,
|
allow_toys,
|
||||||
allow_double,
|
allow_double,
|
||||||
allow_penetration,
|
allow_penetration,
|
||||||
|
allow_foreplay,
|
||||||
allow_oral,
|
allow_oral,
|
||||||
allow_outercourse,
|
allow_outercourse,
|
||||||
allow_anal,
|
allow_anal,
|
||||||
@@ -1972,6 +1975,7 @@ class SxCPHardcoreActionFilter:
|
|||||||
allow_toys=allow_toys,
|
allow_toys=allow_toys,
|
||||||
allow_double=allow_double,
|
allow_double=allow_double,
|
||||||
allow_penetration=allow_penetration,
|
allow_penetration=allow_penetration,
|
||||||
|
allow_foreplay=allow_foreplay,
|
||||||
allow_oral=allow_oral,
|
allow_oral=allow_oral,
|
||||||
allow_outercourse=allow_outercourse,
|
allow_outercourse=allow_outercourse,
|
||||||
allow_anal=allow_anal,
|
allow_anal=allow_anal,
|
||||||
|
|||||||
@@ -195,6 +195,22 @@
|
|||||||
"intense shameless eye contact",
|
"intense shameless eye contact",
|
||||||
"spent satisfied expression"
|
"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": [
|
"hardcore_penetration_expressions": [
|
||||||
"controlled eye contact during penetration",
|
"controlled eye contact during penetration",
|
||||||
"focused adult pleasure face during thrusting",
|
"focused adult pleasure face during thrusting",
|
||||||
@@ -455,6 +471,18 @@
|
|||||||
"dramatic overhead studio crop",
|
"dramatic overhead studio crop",
|
||||||
"tight vertical frame with props and body centered"
|
"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": [
|
"hardcore_explicit_compositions": [
|
||||||
{"text": "full-body explicit sex frame with all adult bodies visible", "min_people": 2},
|
{"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},
|
{"text": "bed-level camera angle focused on genital contact and faces", "min_people": 2, "max_people": 3},
|
||||||
|
|||||||
@@ -84,6 +84,125 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"subcategories": [
|
"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",
|
"name": "Penetrative sex",
|
||||||
"slug": "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:
|
def _is_vaginal_penetration_text(*parts: Any) -> bool:
|
||||||
text = " ".join(_clean(part).lower() for part in parts if _clean(part))
|
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
|
return False
|
||||||
if any(term in text for term in ("anal", "double penetration", "double-penetration", "toy-assisted", "strap-on")):
|
if any(term in text for term in ("anal", "double penetration", "double-penetration", "toy-assisted", "strap-on")):
|
||||||
return False
|
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()
|
position_text = _clean(axis_values.get("position", "")).lower()
|
||||||
if not text:
|
if not text:
|
||||||
return ""
|
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 _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")):
|
if any(term in text for term in ("boobjob", "titjob", "breast sex", "breast-sex")):
|
||||||
return "breast-sex outercourse pose"
|
return "breast-sex outercourse pose"
|
||||||
|
|||||||
+103
-5
@@ -299,6 +299,7 @@ HARDCORE_DETAIL_DENSITY_CHOICES = ["compact", "balanced", "dense"]
|
|||||||
HARDCORE_POSITION_FAMILY_CHOICES = [
|
HARDCORE_POSITION_FAMILY_CHOICES = [
|
||||||
"any",
|
"any",
|
||||||
"penetrative",
|
"penetrative",
|
||||||
|
"foreplay",
|
||||||
"oral",
|
"oral",
|
||||||
"outercourse",
|
"outercourse",
|
||||||
"anal",
|
"anal",
|
||||||
@@ -309,6 +310,7 @@ HARDCORE_POSITION_FAMILY_CHOICES = [
|
|||||||
HARDCORE_POSITION_FOCUS_CHOICES = [
|
HARDCORE_POSITION_FOCUS_CHOICES = [
|
||||||
"keep_pool",
|
"keep_pool",
|
||||||
"penetration_only",
|
"penetration_only",
|
||||||
|
"foreplay_only",
|
||||||
"oral_only",
|
"oral_only",
|
||||||
"outercourse_only",
|
"outercourse_only",
|
||||||
"anal_only",
|
"anal_only",
|
||||||
@@ -334,6 +336,11 @@ HARDCORE_POSITION_KEY_CHOICES = [
|
|||||||
"straddled_oral",
|
"straddled_oral",
|
||||||
"spread_leg_oral",
|
"spread_leg_oral",
|
||||||
"chair_oral",
|
"chair_oral",
|
||||||
|
"kissing",
|
||||||
|
"caressing",
|
||||||
|
"breast_touch",
|
||||||
|
"face_touch",
|
||||||
|
"undressing",
|
||||||
"boobjob",
|
"boobjob",
|
||||||
"testicle_sucking",
|
"testicle_sucking",
|
||||||
"penis_licking",
|
"penis_licking",
|
||||||
@@ -345,6 +352,7 @@ HARDCORE_POSITION_KEY_CHOICES = [
|
|||||||
HARDCORE_POSITION_FAMILY_SUBCATEGORIES = {
|
HARDCORE_POSITION_FAMILY_SUBCATEGORIES = {
|
||||||
"any": [
|
"any": [
|
||||||
"penetrative_sex",
|
"penetrative_sex",
|
||||||
|
"foreplay_teasing",
|
||||||
"oral_sex",
|
"oral_sex",
|
||||||
"outercourse_sex",
|
"outercourse_sex",
|
||||||
"anal_double_penetration",
|
"anal_double_penetration",
|
||||||
@@ -353,6 +361,7 @@ HARDCORE_POSITION_FAMILY_SUBCATEGORIES = {
|
|||||||
"cumshot_climax",
|
"cumshot_climax",
|
||||||
],
|
],
|
||||||
"penetrative": ["penetrative_sex"],
|
"penetrative": ["penetrative_sex"],
|
||||||
|
"foreplay": ["foreplay_teasing"],
|
||||||
"oral": ["oral_sex"],
|
"oral": ["oral_sex"],
|
||||||
"outercourse": ["outercourse_sex"],
|
"outercourse": ["outercourse_sex"],
|
||||||
"anal": ["anal_double_penetration"],
|
"anal": ["anal_double_penetration"],
|
||||||
@@ -378,6 +387,11 @@ HARDCORE_POSITION_KEY_MATCHES = {
|
|||||||
"straddled_oral": ("straddled oral",),
|
"straddled_oral": ("straddled oral",),
|
||||||
"spread_leg_oral": ("spread-leg", "spread leg", "reclining cunnilingus"),
|
"spread_leg_oral": ("spread-leg", "spread leg", "reclining cunnilingus"),
|
||||||
"chair_oral": ("chair oral",),
|
"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"),
|
"boobjob": ("boobjob", "titjob", "breast-sex", "breast sex"),
|
||||||
"testicle_sucking": ("testicle", "balls-licking", "balls licking", "balls and mouth"),
|
"testicle_sucking": ("testicle", "balls-licking", "balls licking", "balls and mouth"),
|
||||||
"penis_licking": ("penis-licking", "penis licking", "tongue along", "tongue licking"),
|
"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_toys": True,
|
||||||
"allow_double": True,
|
"allow_double": True,
|
||||||
"allow_penetration": True,
|
"allow_penetration": True,
|
||||||
|
"allow_foreplay": True,
|
||||||
"allow_oral": True,
|
"allow_oral": True,
|
||||||
"allow_outercourse": True,
|
"allow_outercourse": True,
|
||||||
"allow_anal": 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["family"] = _normalize_hardcore_position_family(parsed.get("family"))
|
||||||
parsed["positions"] = _normalize_hardcore_position_values(parsed.get("positions"))
|
parsed["positions"] = _normalize_hardcore_position_values(parsed.get("positions"))
|
||||||
parsed["require_position"] = not _is_false(parsed.get("require_position", False))
|
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))
|
parsed[key] = not _is_false(parsed.get(key, True))
|
||||||
return parsed
|
return parsed
|
||||||
|
|
||||||
@@ -2376,6 +2400,7 @@ def _hardcore_position_summary(config: dict[str, Any]) -> str:
|
|||||||
("allow_toys", "toys"),
|
("allow_toys", "toys"),
|
||||||
("allow_double", "double"),
|
("allow_double", "double"),
|
||||||
("allow_penetration", "penetration"),
|
("allow_penetration", "penetration"),
|
||||||
|
("allow_foreplay", "foreplay"),
|
||||||
("allow_oral", "oral"),
|
("allow_oral", "oral"),
|
||||||
("allow_outercourse", "outercourse"),
|
("allow_outercourse", "outercourse"),
|
||||||
("allow_anal", "anal"),
|
("allow_anal", "anal"),
|
||||||
@@ -2420,6 +2445,7 @@ def build_hardcore_action_filter_json(
|
|||||||
allow_toys: bool = False,
|
allow_toys: bool = False,
|
||||||
allow_double: bool = False,
|
allow_double: bool = False,
|
||||||
allow_penetration: bool = True,
|
allow_penetration: bool = True,
|
||||||
|
allow_foreplay: bool = True,
|
||||||
allow_oral: bool = True,
|
allow_oral: bool = True,
|
||||||
allow_outercourse: bool = True,
|
allow_outercourse: bool = True,
|
||||||
allow_anal: bool = True,
|
allow_anal: bool = True,
|
||||||
@@ -2430,6 +2456,7 @@ def build_hardcore_action_filter_json(
|
|||||||
focus = str(focus or "keep_pool").strip()
|
focus = str(focus or "keep_pool").strip()
|
||||||
focus_family = {
|
focus_family = {
|
||||||
"penetration_only": "penetrative",
|
"penetration_only": "penetrative",
|
||||||
|
"foreplay_only": "foreplay",
|
||||||
"oral_only": "oral",
|
"oral_only": "oral",
|
||||||
"outercourse_only": "outercourse",
|
"outercourse_only": "outercourse",
|
||||||
"anal_only": "anal",
|
"anal_only": "anal",
|
||||||
@@ -2442,6 +2469,7 @@ def build_hardcore_action_filter_json(
|
|||||||
config["allow_toys"] = bool(allow_toys)
|
config["allow_toys"] = bool(allow_toys)
|
||||||
config["allow_double"] = bool(allow_double)
|
config["allow_double"] = bool(allow_double)
|
||||||
config["allow_penetration"] = bool(allow_penetration)
|
config["allow_penetration"] = bool(allow_penetration)
|
||||||
|
config["allow_foreplay"] = bool(allow_foreplay)
|
||||||
config["allow_oral"] = bool(allow_oral)
|
config["allow_oral"] = bool(allow_oral)
|
||||||
config["allow_outercourse"] = bool(allow_outercourse)
|
config["allow_outercourse"] = bool(allow_outercourse)
|
||||||
config["allow_anal"] = bool(allow_anal)
|
config["allow_anal"] = bool(allow_anal)
|
||||||
@@ -2452,6 +2480,7 @@ def build_hardcore_action_filter_json(
|
|||||||
family
|
family
|
||||||
for enabled, family in (
|
for enabled, family in (
|
||||||
(config["allow_penetration"], "penetrative"),
|
(config["allow_penetration"], "penetrative"),
|
||||||
|
(config["allow_foreplay"], "foreplay"),
|
||||||
(config["allow_oral"], "oral"),
|
(config["allow_oral"], "oral"),
|
||||||
(config["allow_outercourse"], "outercourse"),
|
(config["allow_outercourse"], "outercourse"),
|
||||||
(config["allow_anal"], "anal"),
|
(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:
|
if config["family"] in enabled_action_families and len(enabled_action_families) > 1:
|
||||||
config["family"] = "any"
|
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_oral"] = True
|
||||||
config["allow_penetration"] = False
|
config["allow_penetration"] = False
|
||||||
elif focus == "outercourse_only":
|
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"]))
|
allowed = set(HARDCORE_POSITION_FAMILY_SUBCATEGORIES.get(family, HARDCORE_POSITION_FAMILY_SUBCATEGORIES["any"]))
|
||||||
if not config.get("allow_penetration", True):
|
if not config.get("allow_penetration", True):
|
||||||
allowed.difference_update({"penetrative_sex", "anal_double_penetration", "threesomes", "group_sex_orgy"})
|
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):
|
if not config.get("allow_oral", True):
|
||||||
allowed.discard("oral_sex")
|
allowed.discard("oral_sex")
|
||||||
if not config.get("allow_outercourse", True):
|
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"))
|
or any(term in text for term in ("penetration", "penetrative", "thrust", "penis entering", "vaginal sex", "anal sex"))
|
||||||
):
|
):
|
||||||
return True
|
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 (
|
if not config.get("allow_climax", True) and (
|
||||||
axis_name in ("climax_act", "climax_hint", "climax_detail", "fluid_detail", "fluid_location")
|
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"))
|
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)}."
|
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:
|
def mentions_ass(text: str) -> bool:
|
||||||
return bool(
|
return bool(
|
||||||
re.search(
|
re.search(
|
||||||
@@ -6265,7 +6357,9 @@ def _role_graph(
|
|||||||
a, b = _pick_distinct(rng, women, 2)
|
a, b = _pick_distinct(rng, women, 2)
|
||||||
c = any_woman({a, b}) if len(women) >= 3 else ""
|
c = any_woman({a, b}) if len(women) >= 3 else ""
|
||||||
used = {a, b}
|
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."
|
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:
|
elif "oral" in slug:
|
||||||
graph = f"{a} kneels between {b}'s spread thighs and uses tongue and fingers on her pussy."
|
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)
|
a, b = _pick_distinct(rng, men, 2)
|
||||||
c = any_man({a, b}) if len(men) >= 3 else ""
|
c = any_man({a, b}) if len(men) >= 3 else ""
|
||||||
used = {a, b}
|
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."
|
graph = f"{a} and {b} keep explicit non-penetrative penis contact visible with hands, mouth, or feet."
|
||||||
elif "oral" in slug:
|
elif "oral" in slug:
|
||||||
graph = f"{a} kneels and takes {b}'s penis in his mouth while holding his hips."
|
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()
|
woman = any_woman()
|
||||||
man = any_man()
|
man = any_man()
|
||||||
third = any_person({woman, man}) if people_count >= 3 else ""
|
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)
|
graph = outercourse_position_graph(woman, man)
|
||||||
elif "oral" in slug:
|
elif "oral" in slug:
|
||||||
graph = oral_position_graph(woman, man)
|
graph = oral_position_graph(woman, man)
|
||||||
|
|||||||
Reference in New Issue
Block a user