diff --git a/css/gui/personalLog.css b/css/gui/personalLog.css new file mode 100644 index 0000000000000000000000000000000000000000..1839ff1af007c695da1b0fc1cc93ebf422851d9c --- /dev/null +++ b/css/gui/personalLog.css @@ -0,0 +1,37 @@ +.personal-log-dialog .log-entry-save { + text-align: right; +} + +.personal-log-dialog dl { + list-style-type: decimal; +} +.personal-log-dialog dt { + font-weight: normal; + list-style-type: decimal; +} + +.personal-log-dialog dt .log-entry-created { + font-weight: bold; +} + +.personal-log-dialog dt .log-entry-edited { + font-style: italic; + margin-left: 0.5rem; +} + +.personal-log-dialog dt .choices-strip { + margin-left: 1.5rem; +} + +.personal-log-dialog dt::after { + content: ""; +} + +.personal-log-dialog dd { + margin-left: 2rem; +} + +.personal-log-dialog #log-entry-editor { + width: 95%; + height: 10rem; +} \ No newline at end of file diff --git a/js/001-lib/marked.min.js b/js/001-lib/marked.min.js new file mode 100644 index 0000000000000000000000000000000000000000..fcd6c09035ba0ecd341063e9dfd05ca019dedcb3 --- /dev/null +++ b/js/001-lib/marked.min.js @@ -0,0 +1,6 @@ +/** + * marked v14.1.1 - a markdown parser + * Copyright (c) 2011-2024, Christopher Jeffrey. (MIT Licensed) + * https://github.com/markedjs/marked + */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).marked={})}(this,(function(e){"use strict";function t(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}function n(t){e.defaults=t}e.defaults={async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null};const s=/[&<>"']/,r=new RegExp(s.source,"g"),i=/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,l=new RegExp(i.source,"g"),o={"&":"&","<":"<",">":">",'"':""","'":"'"},a=e=>o[e];function c(e,t){if(t){if(s.test(e))return e.replace(r,a)}else if(i.test(e))return e.replace(l,a);return e}const h=/(^|[^\[])\^/g;function p(e,t){let n="string"==typeof e?e:e.source;t=t||"";const s={replace:(e,t)=>{let r="string"==typeof t?t:t.source;return r=r.replace(h,"$1"),n=n.replace(e,r),s},getRegex:()=>new RegExp(n,t)};return s}function u(e){try{e=encodeURI(e).replace(/%25/g,"%")}catch{return null}return e}const k={exec:()=>null};function g(e,t){const n=e.replace(/\|/g,((e,t,n)=>{let s=!1,r=t;for(;--r>=0&&"\\"===n[r];)s=!s;return s?"|":" |"})).split(/ \|/);let s=0;if(n[0].trim()||n.shift(),n.length>0&&!n[n.length-1].trim()&&n.pop(),t)if(n.length>t)n.splice(t);else for(;n.length<t;)n.push("");for(;s<n.length;s++)n[s]=n[s].trim().replace(/\\\|/g,"|");return n}function f(e,t,n){const s=e.length;if(0===s)return"";let r=0;for(;r<s;){const i=e.charAt(s-r-1);if(i!==t||n){if(i===t||!n)break;r++}else r++}return e.slice(0,s-r)}function d(e,t,n,s){const r=t.href,i=t.title?c(t.title):null,l=e[1].replace(/\\([\[\]])/g,"$1");if("!"!==e[0].charAt(0)){s.state.inLink=!0;const e={type:"link",raw:n,href:r,title:i,text:l,tokens:s.inlineTokens(l)};return s.state.inLink=!1,e}return{type:"image",raw:n,href:r,title:i,text:c(l)}}class x{options;rules;lexer;constructor(t){this.options=t||e.defaults}space(e){const t=this.rules.block.newline.exec(e);if(t&&t[0].length>0)return{type:"space",raw:t[0]}}code(e){const t=this.rules.block.code.exec(e);if(t){const e=t[0].replace(/^(?: {1,4}| {0,3}\t)/gm,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?e:f(e,"\n")}}}fences(e){const t=this.rules.block.fences.exec(e);if(t){const e=t[0],n=function(e,t){const n=e.match(/^(\s+)(?:```)/);if(null===n)return t;const s=n[1];return t.split("\n").map((e=>{const t=e.match(/^\s+/);if(null===t)return e;const[n]=t;return n.length>=s.length?e.slice(s.length):e})).join("\n")}(e,t[3]||"");return{type:"code",raw:e,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:n}}}heading(e){const t=this.rules.block.heading.exec(e);if(t){let e=t[2].trim();if(/#$/.test(e)){const t=f(e,"#");this.options.pedantic?e=t.trim():t&&!/ $/.test(t)||(e=t.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:e,tokens:this.lexer.inline(e)}}}hr(e){const t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:f(t[0],"\n")}}blockquote(e){const t=this.rules.block.blockquote.exec(e);if(t){let e=f(t[0],"\n").split("\n"),n="",s="";const r=[];for(;e.length>0;){let t=!1;const i=[];let l;for(l=0;l<e.length;l++)if(/^ {0,3}>/.test(e[l]))i.push(e[l]),t=!0;else{if(t)break;i.push(e[l])}e=e.slice(l);const o=i.join("\n"),a=o.replace(/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,"\n $1").replace(/^ {0,3}>[ \t]?/gm,"");n=n?`${n}\n${o}`:o,s=s?`${s}\n${a}`:a;const c=this.lexer.state.top;if(this.lexer.state.top=!0,this.lexer.blockTokens(a,r,!0),this.lexer.state.top=c,0===e.length)break;const h=r[r.length-1];if("code"===h?.type)break;if("blockquote"===h?.type){const t=h,i=t.raw+"\n"+e.join("\n"),l=this.blockquote(i);r[r.length-1]=l,n=n.substring(0,n.length-t.raw.length)+l.raw,s=s.substring(0,s.length-t.text.length)+l.text;break}if("list"!==h?.type);else{const t=h,i=t.raw+"\n"+e.join("\n"),l=this.list(i);r[r.length-1]=l,n=n.substring(0,n.length-h.raw.length)+l.raw,s=s.substring(0,s.length-t.raw.length)+l.raw,e=i.substring(r[r.length-1].raw.length).split("\n")}}return{type:"blockquote",raw:n,tokens:r,text:s}}}list(e){let t=this.rules.block.list.exec(e);if(t){let n=t[1].trim();const s=n.length>1,r={type:"list",raw:"",ordered:s,start:s?+n.slice(0,-1):"",loose:!1,items:[]};n=s?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=s?n:"[*+-]");const i=new RegExp(`^( {0,3}${n})((?:[\t ][^\\n]*)?(?:\\n|$))`);let l=!1;for(;e;){let n=!1,s="",o="";if(!(t=i.exec(e)))break;if(this.rules.block.hr.test(e))break;s=t[0],e=e.substring(s.length);let a=t[2].split("\n",1)[0].replace(/^\t+/,(e=>" ".repeat(3*e.length))),c=e.split("\n",1)[0],h=!a.trim(),p=0;if(this.options.pedantic?(p=2,o=a.trimStart()):h?p=t[1].length+1:(p=t[2].search(/[^ ]/),p=p>4?1:p,o=a.slice(p),p+=t[1].length),h&&/^[ \t]*$/.test(c)&&(s+=c+"\n",e=e.substring(c.length+1),n=!0),!n){const t=new RegExp(`^ {0,${Math.min(3,p-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ \t][^\\n]*)?(?:\\n|$))`),n=new RegExp(`^ {0,${Math.min(3,p-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),r=new RegExp(`^ {0,${Math.min(3,p-1)}}(?:\`\`\`|~~~)`),i=new RegExp(`^ {0,${Math.min(3,p-1)}}#`);for(;e;){const l=e.split("\n",1)[0];let u;if(c=l,this.options.pedantic?(c=c.replace(/^ {1,4}(?=( {4})*[^ ])/g," "),u=c):u=c.replace(/\t/g," "),r.test(c))break;if(i.test(c))break;if(t.test(c))break;if(n.test(c))break;if(u.search(/[^ ]/)>=p||!c.trim())o+="\n"+u.slice(p);else{if(h)break;if(a.replace(/\t/g," ").search(/[^ ]/)>=4)break;if(r.test(a))break;if(i.test(a))break;if(n.test(a))break;o+="\n"+c}h||c.trim()||(h=!0),s+=l+"\n",e=e.substring(l.length+1),a=u.slice(p)}}r.loose||(l?r.loose=!0:/\n[ \t]*\n[ \t]*$/.test(s)&&(l=!0));let u,k=null;this.options.gfm&&(k=/^\[[ xX]\] /.exec(o),k&&(u="[ ] "!==k[0],o=o.replace(/^\[[ xX]\] +/,""))),r.items.push({type:"list_item",raw:s,task:!!k,checked:u,loose:!1,text:o,tokens:[]}),r.raw+=s}r.items[r.items.length-1].raw=r.items[r.items.length-1].raw.trimEnd(),r.items[r.items.length-1].text=r.items[r.items.length-1].text.trimEnd(),r.raw=r.raw.trimEnd();for(let e=0;e<r.items.length;e++)if(this.lexer.state.top=!1,r.items[e].tokens=this.lexer.blockTokens(r.items[e].text,[]),!r.loose){const t=r.items[e].tokens.filter((e=>"space"===e.type)),n=t.length>0&&t.some((e=>/\n.*\n/.test(e.raw)));r.loose=n}if(r.loose)for(let e=0;e<r.items.length;e++)r.items[e].loose=!0;return r}}html(e){const t=this.rules.block.html.exec(e);if(t){return{type:"html",block:!0,raw:t[0],pre:"pre"===t[1]||"script"===t[1]||"style"===t[1],text:t[0]}}}def(e){const t=this.rules.block.def.exec(e);if(t){const e=t[1].toLowerCase().replace(/\s+/g," "),n=t[2]?t[2].replace(/^<(.*)>$/,"$1").replace(this.rules.inline.anyPunctuation,"$1"):"",s=t[3]?t[3].substring(1,t[3].length-1).replace(this.rules.inline.anyPunctuation,"$1"):t[3];return{type:"def",tag:e,raw:t[0],href:n,title:s}}}table(e){const t=this.rules.block.table.exec(e);if(!t)return;if(!/[:|]/.test(t[2]))return;const n=g(t[1]),s=t[2].replace(/^\||\| *$/g,"").split("|"),r=t[3]&&t[3].trim()?t[3].replace(/\n[ \t]*$/,"").split("\n"):[],i={type:"table",raw:t[0],header:[],align:[],rows:[]};if(n.length===s.length){for(const e of s)/^ *-+: *$/.test(e)?i.align.push("right"):/^ *:-+: *$/.test(e)?i.align.push("center"):/^ *:-+ *$/.test(e)?i.align.push("left"):i.align.push(null);for(let e=0;e<n.length;e++)i.header.push({text:n[e],tokens:this.lexer.inline(n[e]),header:!0,align:i.align[e]});for(const e of r)i.rows.push(g(e,i.header.length).map(((e,t)=>({text:e,tokens:this.lexer.inline(e),header:!1,align:i.align[t]}))));return i}}lheading(e){const t=this.rules.block.lheading.exec(e);if(t)return{type:"heading",raw:t[0],depth:"="===t[2].charAt(0)?1:2,text:t[1],tokens:this.lexer.inline(t[1])}}paragraph(e){const t=this.rules.block.paragraph.exec(e);if(t){const e="\n"===t[1].charAt(t[1].length-1)?t[1].slice(0,-1):t[1];return{type:"paragraph",raw:t[0],text:e,tokens:this.lexer.inline(e)}}}text(e){const t=this.rules.block.text.exec(e);if(t)return{type:"text",raw:t[0],text:t[0],tokens:this.lexer.inline(t[0])}}escape(e){const t=this.rules.inline.escape.exec(e);if(t)return{type:"escape",raw:t[0],text:c(t[1])}}tag(e){const t=this.rules.inline.tag.exec(e);if(t)return!this.lexer.state.inLink&&/^<a /i.test(t[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&/^<\/a>/i.test(t[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&/^<(pre|code|kbd|script)(\s|>)/i.test(t[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&/^<\/(pre|code|kbd|script)(\s|>)/i.test(t[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:t[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:t[0]}}link(e){const t=this.rules.inline.link.exec(e);if(t){const e=t[2].trim();if(!this.options.pedantic&&/^</.test(e)){if(!/>$/.test(e))return;const t=f(e.slice(0,-1),"\\");if((e.length-t.length)%2==0)return}else{const e=function(e,t){if(-1===e.indexOf(t[1]))return-1;let n=0;for(let s=0;s<e.length;s++)if("\\"===e[s])s++;else if(e[s]===t[0])n++;else if(e[s]===t[1]&&(n--,n<0))return s;return-1}(t[2],"()");if(e>-1){const n=(0===t[0].indexOf("!")?5:4)+t[1].length+e;t[2]=t[2].substring(0,e),t[0]=t[0].substring(0,n).trim(),t[3]=""}}let n=t[2],s="";if(this.options.pedantic){const e=/^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(n);e&&(n=e[1],s=e[3])}else s=t[3]?t[3].slice(1,-1):"";return n=n.trim(),/^</.test(n)&&(n=this.options.pedantic&&!/>$/.test(e)?n.slice(1):n.slice(1,-1)),d(t,{href:n?n.replace(this.rules.inline.anyPunctuation,"$1"):n,title:s?s.replace(this.rules.inline.anyPunctuation,"$1"):s},t[0],this.lexer)}}reflink(e,t){let n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){const e=t[(n[2]||n[1]).replace(/\s+/g," ").toLowerCase()];if(!e){const e=n[0].charAt(0);return{type:"text",raw:e,text:e}}return d(n,e,n[0],this.lexer)}}emStrong(e,t,n=""){let s=this.rules.inline.emStrongLDelim.exec(e);if(!s)return;if(s[3]&&n.match(/[\p{L}\p{N}]/u))return;if(!(s[1]||s[2]||"")||!n||this.rules.inline.punctuation.exec(n)){const n=[...s[0]].length-1;let r,i,l=n,o=0;const a="*"===s[0][0]?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(a.lastIndex=0,t=t.slice(-1*e.length+n);null!=(s=a.exec(t));){if(r=s[1]||s[2]||s[3]||s[4]||s[5]||s[6],!r)continue;if(i=[...r].length,s[3]||s[4]){l+=i;continue}if((s[5]||s[6])&&n%3&&!((n+i)%3)){o+=i;continue}if(l-=i,l>0)continue;i=Math.min(i,i+l+o);const t=[...s[0]][0].length,a=e.slice(0,n+s.index+t+i);if(Math.min(n,i)%2){const e=a.slice(1,-1);return{type:"em",raw:a,text:e,tokens:this.lexer.inlineTokens(e)}}const c=a.slice(2,-2);return{type:"strong",raw:a,text:c,tokens:this.lexer.inlineTokens(c)}}}}codespan(e){const t=this.rules.inline.code.exec(e);if(t){let e=t[2].replace(/\n/g," ");const n=/[^ ]/.test(e),s=/^ /.test(e)&&/ $/.test(e);return n&&s&&(e=e.substring(1,e.length-1)),e=c(e,!0),{type:"codespan",raw:t[0],text:e}}}br(e){const t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}}del(e){const t=this.rules.inline.del.exec(e);if(t)return{type:"del",raw:t[0],text:t[2],tokens:this.lexer.inlineTokens(t[2])}}autolink(e){const t=this.rules.inline.autolink.exec(e);if(t){let e,n;return"@"===t[2]?(e=c(t[1]),n="mailto:"+e):(e=c(t[1]),n=e),{type:"link",raw:t[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}url(e){let t;if(t=this.rules.inline.url.exec(e)){let e,n;if("@"===t[2])e=c(t[0]),n="mailto:"+e;else{let s;do{s=t[0],t[0]=this.rules.inline._backpedal.exec(t[0])?.[0]??""}while(s!==t[0]);e=c(t[0]),n="www."===t[1]?"http://"+t[0]:t[0]}return{type:"link",raw:t[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}inlineText(e){const t=this.rules.inline.text.exec(e);if(t){let e;return e=this.lexer.state.inRawBlock?t[0]:c(t[0]),{type:"text",raw:t[0],text:e}}}}const b=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,w=/(?:[*+-]|\d{1,9}[.)])/,m=p(/^(?!bull |blockCode|fences|blockquote|heading|html)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html))+?)\n {0,3}(=+|-+) *(?:\n+|$)/).replace(/bull/g,w).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).getRegex(),y=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,$=/(?!\s*\])(?:\\.|[^\[\]\\])+/,z=p(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",$).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),T=p(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,w).getRegex(),R="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",_=/<!--(?:-?>|[\s\S]*?(?:-->|$))/,A=p("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|<![A-Z][\\s\\S]*?(?:>\\n*|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$))","i").replace("comment",_).replace("tag",R).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),S=p(y).replace("hr",b).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",R).getRegex(),I={blockquote:p(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph",S).getRegex(),code:/^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/,def:z,fences:/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,heading:/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,hr:b,html:A,lheading:m,list:T,newline:/^(?:[ \t]*(?:\n|$))+/,paragraph:S,table:k,text:/^[^\n]+/},E=p("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr",b).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code","(?: {4}| {0,3}\t)[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",R).getRegex(),q={...I,table:E,paragraph:p(y).replace("hr",b).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",E).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",R).getRegex()},Z={...I,html:p("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)|<tag(?:\"[^\"]*\"|'[^']*'|\\s[^'\"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",_).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:k,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:p(y).replace("hr",b).replace("heading"," *#{1,6} *[^\n]").replace("lheading",m).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},P=/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,L=/^( {2,}|\\)\n(?!\s*$)/,v="\\p{P}\\p{S}",Q=p(/^((?![*_])[\spunctuation])/,"u").replace(/punctuation/g,v).getRegex(),B=p(/^(?:\*+(?:((?!\*)[punct])|[^\s*]))|^_+(?:((?!_)[punct])|([^\s_]))/,"u").replace(/punct/g,v).getRegex(),M=p("^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)[punct](\\*+)(?=[\\s]|$)|[^punct\\s](\\*+)(?!\\*)(?=[punct\\s]|$)|(?!\\*)[punct\\s](\\*+)(?=[^punct\\s])|[\\s](\\*+)(?!\\*)(?=[punct])|(?!\\*)[punct](\\*+)(?!\\*)(?=[punct])|[^punct\\s](\\*+)(?=[^punct\\s])","gu").replace(/punct/g,v).getRegex(),O=p("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)[punct](_+)(?=[\\s]|$)|[^punct\\s](_+)(?!_)(?=[punct\\s]|$)|(?!_)[punct\\s](_+)(?=[^punct\\s])|[\\s](_+)(?!_)(?=[punct])|(?!_)[punct](_+)(?!_)(?=[punct])","gu").replace(/punct/g,v).getRegex(),j=p(/\\([punct])/,"gu").replace(/punct/g,v).getRegex(),D=p(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),C=p(_).replace("(?:--\x3e|$)","--\x3e").getRegex(),H=p("^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>").replace("comment",C).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),U=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,X=p(/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/).replace("label",U).replace("href",/<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),F=p(/^!?\[(label)\]\[(ref)\]/).replace("label",U).replace("ref",$).getRegex(),N=p(/^!?\[(ref)\](?:\[\])?/).replace("ref",$).getRegex(),G={_backpedal:k,anyPunctuation:j,autolink:D,blockSkip:/\[[^[\]]*?\]\([^\(\)]*?\)|`[^`]*?`|<[^<>]*?>/g,br:L,code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,del:k,emStrongLDelim:B,emStrongRDelimAst:M,emStrongRDelimUnd:O,escape:P,link:X,nolink:N,punctuation:Q,reflink:F,reflinkSearch:p("reflink|nolink(?!\\()","g").replace("reflink",F).replace("nolink",N).getRegex(),tag:H,text:/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/,url:k},J={...G,link:p(/^!?\[(label)\]\((.*?)\)/).replace("label",U).getRegex(),reflink:p(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",U).getRegex()},K={...G,escape:p(P).replace("])","~|])").getRegex(),url:p(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,"i").replace("email",/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/},V={...K,br:p(L).replace("{2,}","*").getRegex(),text:p(K.text).replace("\\b_","\\b_| {2,}\\n").replace(/\{2,\}/g,"*").getRegex()},W={normal:I,gfm:q,pedantic:Z},Y={normal:G,gfm:K,breaks:V,pedantic:J};class ee{tokens;options;state;tokenizer;inlineQueue;constructor(t){this.tokens=[],this.tokens.links=Object.create(null),this.options=t||e.defaults,this.options.tokenizer=this.options.tokenizer||new x,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};const n={block:W.normal,inline:Y.normal};this.options.pedantic?(n.block=W.pedantic,n.inline=Y.pedantic):this.options.gfm&&(n.block=W.gfm,this.options.breaks?n.inline=Y.breaks:n.inline=Y.gfm),this.tokenizer.rules=n}static get rules(){return{block:W,inline:Y}}static lex(e,t){return new ee(t).lex(e)}static lexInline(e,t){return new ee(t).inlineTokens(e)}lex(e){e=e.replace(/\r\n|\r/g,"\n"),this.blockTokens(e,this.tokens);for(let e=0;e<this.inlineQueue.length;e++){const t=this.inlineQueue[e];this.inlineTokens(t.src,t.tokens)}return this.inlineQueue=[],this.tokens}blockTokens(e,t=[],n=!1){let s,r,i;for(this.options.pedantic&&(e=e.replace(/\t/g," ").replace(/^ +$/gm,""));e;)if(!(this.options.extensions&&this.options.extensions.block&&this.options.extensions.block.some((n=>!!(s=n.call({lexer:this},e,t))&&(e=e.substring(s.raw.length),t.push(s),!0)))))if(s=this.tokenizer.space(e))e=e.substring(s.raw.length),1===s.raw.length&&t.length>0?t[t.length-1].raw+="\n":t.push(s);else if(s=this.tokenizer.code(e))e=e.substring(s.raw.length),r=t[t.length-1],!r||"paragraph"!==r.type&&"text"!==r.type?t.push(s):(r.raw+="\n"+s.raw,r.text+="\n"+s.text,this.inlineQueue[this.inlineQueue.length-1].src=r.text);else if(s=this.tokenizer.fences(e))e=e.substring(s.raw.length),t.push(s);else if(s=this.tokenizer.heading(e))e=e.substring(s.raw.length),t.push(s);else if(s=this.tokenizer.hr(e))e=e.substring(s.raw.length),t.push(s);else if(s=this.tokenizer.blockquote(e))e=e.substring(s.raw.length),t.push(s);else if(s=this.tokenizer.list(e))e=e.substring(s.raw.length),t.push(s);else if(s=this.tokenizer.html(e))e=e.substring(s.raw.length),t.push(s);else if(s=this.tokenizer.def(e))e=e.substring(s.raw.length),r=t[t.length-1],!r||"paragraph"!==r.type&&"text"!==r.type?this.tokens.links[s.tag]||(this.tokens.links[s.tag]={href:s.href,title:s.title}):(r.raw+="\n"+s.raw,r.text+="\n"+s.raw,this.inlineQueue[this.inlineQueue.length-1].src=r.text);else if(s=this.tokenizer.table(e))e=e.substring(s.raw.length),t.push(s);else if(s=this.tokenizer.lheading(e))e=e.substring(s.raw.length),t.push(s);else{if(i=e,this.options.extensions&&this.options.extensions.startBlock){let t=1/0;const n=e.slice(1);let s;this.options.extensions.startBlock.forEach((e=>{s=e.call({lexer:this},n),"number"==typeof s&&s>=0&&(t=Math.min(t,s))})),t<1/0&&t>=0&&(i=e.substring(0,t+1))}if(this.state.top&&(s=this.tokenizer.paragraph(i)))r=t[t.length-1],n&&"paragraph"===r?.type?(r.raw+="\n"+s.raw,r.text+="\n"+s.text,this.inlineQueue.pop(),this.inlineQueue[this.inlineQueue.length-1].src=r.text):t.push(s),n=i.length!==e.length,e=e.substring(s.raw.length);else if(s=this.tokenizer.text(e))e=e.substring(s.raw.length),r=t[t.length-1],r&&"text"===r.type?(r.raw+="\n"+s.raw,r.text+="\n"+s.text,this.inlineQueue.pop(),this.inlineQueue[this.inlineQueue.length-1].src=r.text):t.push(s);else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}return this.state.top=!0,t}inline(e,t=[]){return this.inlineQueue.push({src:e,tokens:t}),t}inlineTokens(e,t=[]){let n,s,r,i,l,o,a=e;if(this.tokens.links){const e=Object.keys(this.tokens.links);if(e.length>0)for(;null!=(i=this.tokenizer.rules.inline.reflinkSearch.exec(a));)e.includes(i[0].slice(i[0].lastIndexOf("[")+1,-1))&&(a=a.slice(0,i.index)+"["+"a".repeat(i[0].length-2)+"]"+a.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;null!=(i=this.tokenizer.rules.inline.blockSkip.exec(a));)a=a.slice(0,i.index)+"["+"a".repeat(i[0].length-2)+"]"+a.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);for(;null!=(i=this.tokenizer.rules.inline.anyPunctuation.exec(a));)a=a.slice(0,i.index)+"++"+a.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);for(;e;)if(l||(o=""),l=!1,!(this.options.extensions&&this.options.extensions.inline&&this.options.extensions.inline.some((s=>!!(n=s.call({lexer:this},e,t))&&(e=e.substring(n.raw.length),t.push(n),!0)))))if(n=this.tokenizer.escape(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.tag(e))e=e.substring(n.raw.length),s=t[t.length-1],s&&"text"===n.type&&"text"===s.type?(s.raw+=n.raw,s.text+=n.text):t.push(n);else if(n=this.tokenizer.link(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.reflink(e,this.tokens.links))e=e.substring(n.raw.length),s=t[t.length-1],s&&"text"===n.type&&"text"===s.type?(s.raw+=n.raw,s.text+=n.text):t.push(n);else if(n=this.tokenizer.emStrong(e,a,o))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.codespan(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.br(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.del(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.autolink(e))e=e.substring(n.raw.length),t.push(n);else if(this.state.inLink||!(n=this.tokenizer.url(e))){if(r=e,this.options.extensions&&this.options.extensions.startInline){let t=1/0;const n=e.slice(1);let s;this.options.extensions.startInline.forEach((e=>{s=e.call({lexer:this},n),"number"==typeof s&&s>=0&&(t=Math.min(t,s))})),t<1/0&&t>=0&&(r=e.substring(0,t+1))}if(n=this.tokenizer.inlineText(r))e=e.substring(n.raw.length),"_"!==n.raw.slice(-1)&&(o=n.raw.slice(-1)),l=!0,s=t[t.length-1],s&&"text"===s.type?(s.raw+=n.raw,s.text+=n.text):t.push(n);else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}else e=e.substring(n.raw.length),t.push(n);return t}}class te{options;parser;constructor(t){this.options=t||e.defaults}space(e){return""}code({text:e,lang:t,escaped:n}){const s=(t||"").match(/^\S*/)?.[0],r=e.replace(/\n$/,"")+"\n";return s?'<pre><code class="language-'+c(s)+'">'+(n?r:c(r,!0))+"</code></pre>\n":"<pre><code>"+(n?r:c(r,!0))+"</code></pre>\n"}blockquote({tokens:e}){return`<blockquote>\n${this.parser.parse(e)}</blockquote>\n`}html({text:e}){return e}heading({tokens:e,depth:t}){return`<h${t}>${this.parser.parseInline(e)}</h${t}>\n`}hr(e){return"<hr>\n"}list(e){const t=e.ordered,n=e.start;let s="";for(let t=0;t<e.items.length;t++){const n=e.items[t];s+=this.listitem(n)}const r=t?"ol":"ul";return"<"+r+(t&&1!==n?' start="'+n+'"':"")+">\n"+s+"</"+r+">\n"}listitem(e){let t="";if(e.task){const n=this.checkbox({checked:!!e.checked});e.loose?e.tokens.length>0&&"paragraph"===e.tokens[0].type?(e.tokens[0].text=n+" "+e.tokens[0].text,e.tokens[0].tokens&&e.tokens[0].tokens.length>0&&"text"===e.tokens[0].tokens[0].type&&(e.tokens[0].tokens[0].text=n+" "+e.tokens[0].tokens[0].text)):e.tokens.unshift({type:"text",raw:n+" ",text:n+" "}):t+=n+" "}return t+=this.parser.parse(e.tokens,!!e.loose),`<li>${t}</li>\n`}checkbox({checked:e}){return"<input "+(e?'checked="" ':"")+'disabled="" type="checkbox">'}paragraph({tokens:e}){return`<p>${this.parser.parseInline(e)}</p>\n`}table(e){let t="",n="";for(let t=0;t<e.header.length;t++)n+=this.tablecell(e.header[t]);t+=this.tablerow({text:n});let s="";for(let t=0;t<e.rows.length;t++){const r=e.rows[t];n="";for(let e=0;e<r.length;e++)n+=this.tablecell(r[e]);s+=this.tablerow({text:n})}return s&&(s=`<tbody>${s}</tbody>`),"<table>\n<thead>\n"+t+"</thead>\n"+s+"</table>\n"}tablerow({text:e}){return`<tr>\n${e}</tr>\n`}tablecell(e){const t=this.parser.parseInline(e.tokens),n=e.header?"th":"td";return(e.align?`<${n} align="${e.align}">`:`<${n}>`)+t+`</${n}>\n`}strong({tokens:e}){return`<strong>${this.parser.parseInline(e)}</strong>`}em({tokens:e}){return`<em>${this.parser.parseInline(e)}</em>`}codespan({text:e}){return`<code>${e}</code>`}br(e){return"<br>"}del({tokens:e}){return`<del>${this.parser.parseInline(e)}</del>`}link({href:e,title:t,tokens:n}){const s=this.parser.parseInline(n),r=u(e);if(null===r)return s;let i='<a href="'+(e=r)+'"';return t&&(i+=' title="'+t+'"'),i+=">"+s+"</a>",i}image({href:e,title:t,text:n}){const s=u(e);if(null===s)return n;let r=`<img src="${e=s}" alt="${n}"`;return t&&(r+=` title="${t}"`),r+=">",r}text(e){return"tokens"in e&&e.tokens?this.parser.parseInline(e.tokens):e.text}}class ne{strong({text:e}){return e}em({text:e}){return e}codespan({text:e}){return e}del({text:e}){return e}html({text:e}){return e}text({text:e}){return e}link({text:e}){return""+e}image({text:e}){return""+e}br(){return""}}class se{options;renderer;textRenderer;constructor(t){this.options=t||e.defaults,this.options.renderer=this.options.renderer||new te,this.renderer=this.options.renderer,this.renderer.options=this.options,this.renderer.parser=this,this.textRenderer=new ne}static parse(e,t){return new se(t).parse(e)}static parseInline(e,t){return new se(t).parseInline(e)}parse(e,t=!0){let n="";for(let s=0;s<e.length;s++){const r=e[s];if(this.options.extensions&&this.options.extensions.renderers&&this.options.extensions.renderers[r.type]){const e=r,t=this.options.extensions.renderers[e.type].call({parser:this},e);if(!1!==t||!["space","hr","heading","code","table","blockquote","list","html","paragraph","text"].includes(e.type)){n+=t||"";continue}}const i=r;switch(i.type){case"space":n+=this.renderer.space(i);continue;case"hr":n+=this.renderer.hr(i);continue;case"heading":n+=this.renderer.heading(i);continue;case"code":n+=this.renderer.code(i);continue;case"table":n+=this.renderer.table(i);continue;case"blockquote":n+=this.renderer.blockquote(i);continue;case"list":n+=this.renderer.list(i);continue;case"html":n+=this.renderer.html(i);continue;case"paragraph":n+=this.renderer.paragraph(i);continue;case"text":{let r=i,l=this.renderer.text(r);for(;s+1<e.length&&"text"===e[s+1].type;)r=e[++s],l+="\n"+this.renderer.text(r);n+=t?this.renderer.paragraph({type:"paragraph",raw:l,text:l,tokens:[{type:"text",raw:l,text:l}]}):l;continue}default:{const e='Token with "'+i.type+'" type was not found.';if(this.options.silent)return console.error(e),"";throw new Error(e)}}}return n}parseInline(e,t){t=t||this.renderer;let n="";for(let s=0;s<e.length;s++){const r=e[s];if(this.options.extensions&&this.options.extensions.renderers&&this.options.extensions.renderers[r.type]){const e=this.options.extensions.renderers[r.type].call({parser:this},r);if(!1!==e||!["escape","html","link","image","strong","em","codespan","br","del","text"].includes(r.type)){n+=e||"";continue}}const i=r;switch(i.type){case"escape":case"text":n+=t.text(i);break;case"html":n+=t.html(i);break;case"link":n+=t.link(i);break;case"image":n+=t.image(i);break;case"strong":n+=t.strong(i);break;case"em":n+=t.em(i);break;case"codespan":n+=t.codespan(i);break;case"br":n+=t.br(i);break;case"del":n+=t.del(i);break;default:{const e='Token with "'+i.type+'" type was not found.';if(this.options.silent)return console.error(e),"";throw new Error(e)}}}return n}}class re{options;block;constructor(t){this.options=t||e.defaults}static passThroughHooks=new Set(["preprocess","postprocess","processAllTokens"]);preprocess(e){return e}postprocess(e){return e}processAllTokens(e){return e}provideLexer(){return this.block?ee.lex:ee.lexInline}provideParser(){return this.block?se.parse:se.parseInline}}class ie{defaults={async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null};options=this.setOptions;parse=this.parseMarkdown(!0);parseInline=this.parseMarkdown(!1);Parser=se;Renderer=te;TextRenderer=ne;Lexer=ee;Tokenizer=x;Hooks=re;constructor(...e){this.use(...e)}walkTokens(e,t){let n=[];for(const s of e)switch(n=n.concat(t.call(this,s)),s.type){case"table":{const e=s;for(const s of e.header)n=n.concat(this.walkTokens(s.tokens,t));for(const s of e.rows)for(const e of s)n=n.concat(this.walkTokens(e.tokens,t));break}case"list":{const e=s;n=n.concat(this.walkTokens(e.items,t));break}default:{const e=s;this.defaults.extensions?.childTokens?.[e.type]?this.defaults.extensions.childTokens[e.type].forEach((s=>{const r=e[s].flat(1/0);n=n.concat(this.walkTokens(r,t))})):e.tokens&&(n=n.concat(this.walkTokens(e.tokens,t)))}}return n}use(...e){const t=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach((e=>{const n={...e};if(n.async=this.defaults.async||n.async||!1,e.extensions&&(e.extensions.forEach((e=>{if(!e.name)throw new Error("extension name required");if("renderer"in e){const n=t.renderers[e.name];t.renderers[e.name]=n?function(...t){let s=e.renderer.apply(this,t);return!1===s&&(s=n.apply(this,t)),s}:e.renderer}if("tokenizer"in e){if(!e.level||"block"!==e.level&&"inline"!==e.level)throw new Error("extension level must be 'block' or 'inline'");const n=t[e.level];n?n.unshift(e.tokenizer):t[e.level]=[e.tokenizer],e.start&&("block"===e.level?t.startBlock?t.startBlock.push(e.start):t.startBlock=[e.start]:"inline"===e.level&&(t.startInline?t.startInline.push(e.start):t.startInline=[e.start]))}"childTokens"in e&&e.childTokens&&(t.childTokens[e.name]=e.childTokens)})),n.extensions=t),e.renderer){const t=this.defaults.renderer||new te(this.defaults);for(const n in e.renderer){if(!(n in t))throw new Error(`renderer '${n}' does not exist`);if(["options","parser"].includes(n))continue;const s=n,r=e.renderer[s],i=t[s];t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n||""}}n.renderer=t}if(e.tokenizer){const t=this.defaults.tokenizer||new x(this.defaults);for(const n in e.tokenizer){if(!(n in t))throw new Error(`tokenizer '${n}' does not exist`);if(["options","rules","lexer"].includes(n))continue;const s=n,r=e.tokenizer[s],i=t[s];t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n}}n.tokenizer=t}if(e.hooks){const t=this.defaults.hooks||new re;for(const n in e.hooks){if(!(n in t))throw new Error(`hook '${n}' does not exist`);if(["options","block"].includes(n))continue;const s=n,r=e.hooks[s],i=t[s];re.passThroughHooks.has(n)?t[s]=e=>{if(this.defaults.async)return Promise.resolve(r.call(t,e)).then((e=>i.call(t,e)));const n=r.call(t,e);return i.call(t,n)}:t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n}}n.hooks=t}if(e.walkTokens){const t=this.defaults.walkTokens,s=e.walkTokens;n.walkTokens=function(e){let n=[];return n.push(s.call(this,e)),t&&(n=n.concat(t.call(this,e))),n}}this.defaults={...this.defaults,...n}})),this}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,t){return ee.lex(e,t??this.defaults)}parser(e,t){return se.parse(e,t??this.defaults)}parseMarkdown(e){return(t,n)=>{const s={...n},r={...this.defaults,...s},i=this.onError(!!r.silent,!!r.async);if(!0===this.defaults.async&&!1===s.async)return i(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(null==t)return i(new Error("marked(): input parameter is undefined or null"));if("string"!=typeof t)return i(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(t)+", string expected"));r.hooks&&(r.hooks.options=r,r.hooks.block=e);const l=r.hooks?r.hooks.provideLexer():e?ee.lex:ee.lexInline,o=r.hooks?r.hooks.provideParser():e?se.parse:se.parseInline;if(r.async)return Promise.resolve(r.hooks?r.hooks.preprocess(t):t).then((e=>l(e,r))).then((e=>r.hooks?r.hooks.processAllTokens(e):e)).then((e=>r.walkTokens?Promise.all(this.walkTokens(e,r.walkTokens)).then((()=>e)):e)).then((e=>o(e,r))).then((e=>r.hooks?r.hooks.postprocess(e):e)).catch(i);try{r.hooks&&(t=r.hooks.preprocess(t));let e=l(t,r);r.hooks&&(e=r.hooks.processAllTokens(e)),r.walkTokens&&this.walkTokens(e,r.walkTokens);let n=o(e,r);return r.hooks&&(n=r.hooks.postprocess(n)),n}catch(e){return i(e)}}}onError(e,t){return n=>{if(n.message+="\nPlease report this to https://github.com/markedjs/marked.",e){const e="<p>An error occurred:</p><pre>"+c(n.message+"",!0)+"</pre>";return t?Promise.resolve(e):e}if(t)return Promise.reject(n);throw n}}}const le=new ie;function oe(e,t){return le.parse(e,t)}oe.options=oe.setOptions=function(e){return le.setOptions(e),oe.defaults=le.defaults,n(oe.defaults),oe},oe.getDefaults=t,oe.defaults=e.defaults,oe.use=function(...e){return le.use(...e),oe.defaults=le.defaults,n(oe.defaults),oe},oe.walkTokens=function(e,t){return le.walkTokens(e,t)},oe.parseInline=le.parseInline,oe.Parser=se,oe.parser=se.parse,oe.Renderer=te,oe.TextRenderer=ne,oe.Lexer=ee,oe.lexer=ee.lex,oe.Tokenizer=x,oe.Hooks=re,oe.parse=oe;const ae=oe.options,ce=oe.setOptions,he=oe.use,pe=oe.walkTokens,ue=oe.parseInline,ke=oe,ge=se.parse,fe=ee.lex;e.Hooks=re,e.Lexer=ee,e.Marked=ie,e.Parser=se,e.Renderer=te,e.TextRenderer=ne,e.Tokenizer=x,e.getDefaults=t,e.lexer=fe,e.marked=oe,e.options=ae,e.parse=ke,e.parseInline=ue,e.parser=ge,e.setOptions=ce,e.use=he,e.walkTokens=pe})); diff --git a/js/002-config/fc-js-init.js b/js/002-config/fc-js-init.js index d7680f020b63a8dfbc5765ef3103c518ca5b66aa..9397d78df5dbc217bdd20f12df78d7320cd2270d 100644 --- a/js/002-config/fc-js-init.js +++ b/js/002-config/fc-js-init.js @@ -105,6 +105,7 @@ App.UI.DressingRoom = {}; App.UI.SlaveInteract = {}; App.UI.SlaveInteract.SlaveBot = {}; App.UI.SlaveInteract.SlaveBot.Generate = {}; +App.UI.ChildInteract = {}; App.UI.View = {}; App.Update = {}; App.Utils = {}; diff --git a/js/003-data/gameVariableData.js b/js/003-data/gameVariableData.js index a678f1cf0cfc4f2b122844ba72e54007177a57ad..70abfea84a614f42f2b88cfb4df31df570742538 100644 --- a/js/003-data/gameVariableData.js +++ b/js/003-data/gameVariableData.js @@ -251,6 +251,11 @@ App.Data.defaultGameStateVariables = { * * 1: ComfyUi */ aiUserInterface: 0, + aiPonyExperimental: false, // temporary switch variable to try using the Pony Diffusion prompt and workflows + aiPonyTurbo: false, + aiPonyLoraStack: `{ "Styles\\Concept Art DarkSide Style LoRA_Pony XL v6.safetensors": 0.5, "Styles\\Digital Art Style SDXL_LoRA_Pony Diffusion V6 XL.safetensors": 0.2, "Styles\\Photo 2 Style SDXL_LoRA_Pony Diffusion V6 XL.safetensors": 0.2 }`, + aiPonyHiResPass: false, + aiPonyFaceDetailer: false, aiWidth: 512, aiAgeFilter: true, customClothesPrompts: {}, @@ -662,6 +667,8 @@ App.Data.resetOnNGPlus = { /** @type {Array<FC.ReminderEntry>} */ reminders: [], + personalLog: [], + /** @type {FC.Zeroable<FC.SlaveState>} */ boomerangSlave: 0, boomerangWeeks: 0, diff --git a/src/004-base/basePitFight.js b/src/004-base/basePitFight.js index e24727bd98aafd9e2c26516a6db8d1d008c332a9..9172994ff6e5907ca879997367f38ed7fa51e3ff 100644 --- a/src/004-base/basePitFight.js +++ b/src/004-base/basePitFight.js @@ -100,12 +100,34 @@ App.Facilities.Pit.Fights.BaseFight = class BaseFight { * default implementation should suffice for child classes with a fixed number of actors; may be overridden for fights with variable actor count. * @returns {boolean} - return false if sufficient qualified actors could not be found (cancel the fight) */ - castActors() { + castActors(toFight = {firstFighter: -1, secondFighter: -1}) { const prereqs = this.actorPrerequisites(); this.actors = [...this.forcedActors()]; - + switch (this.actors.length) { + // add both custom fighters if they're not invalid + case 0: + if (toFight.firstFighter >= 0) { + this.actors.push(toFight.firstFighter); + } + if (toFight.secondFighter >= 0) { + this.actors.push(toFight.secondFighter); + } + break; + // add either fighter, defaulting to first + case 1: + if (toFight.firstFighter >= 0) { + this.actors.push(toFight.firstFighter); + } else if (toFight.secondFighter >= 0) { + this.actors.push(toFight.secondFighter); + } + break; + } + // if not fully chosen, select until we have 2 actors for (let i = 0; i < prereqs.length; ++i) { + if (this.actors.length >= 2) { + continue; + } if (this.allowTrainees) { if (this._selectActor(prereqs[i], ...App.Entity.facilities.pit.job("trainee").employeesIDs())) { continue; @@ -115,7 +137,6 @@ App.Facilities.Pit.Fights.BaseFight = class BaseFight { return false; } } - return true; // all actors cast } diff --git a/src/004-base/basePrompt.js b/src/004-base/basePrompt.js index 8c76b433ff8eed95dd9f2fc521feb81feee6fd2f..851035bbada50fe2f3a816d37b69777709424459 100644 --- a/src/004-base/basePrompt.js +++ b/src/004-base/basePrompt.js @@ -13,7 +13,7 @@ App.Art.GenAI.PromptPart = class PromptPart { * @abstract */ positive() { - throw new Error("not implemented"); + return undefined; } /** @@ -22,7 +22,25 @@ App.Art.GenAI.PromptPart = class PromptPart { * @abstract */ negative() { - throw new Error("not implemented"); + return undefined; + } + + /** + * Keywords for a high detail pass -- useful once the first pass has established image structure + * @returns {string|undefined} + * @abstract + */ + detailer() { + return undefined; // hopefully just returns an empty prompt if nothing is specified here... + } + + /** + * Facial features -- separated for adetailer face pass + * @returns {string|undefined} + * @abstract + */ + face() { + return undefined; } }; @@ -38,17 +56,31 @@ App.Art.GenAI.Prompt = class Prompt { * @returns {string} */ positive() { - let parts = this.parts.map(part => part.positive()); - parts = parts.filter(part => part); - return parts.join(", "); + let parts = this.parts.map(part => part.positive() || "").filter(part => part); + return parts.length ? parts.join(", ") : ""; } /** * @returns {string} */ negative() { - let parts = this.parts.map(part => part.negative()); - parts = parts.filter(part => part); - return parts.join(", "); + let parts = this.parts.map(part => part.negative() || "").filter(part => part); + return parts.length ? parts.join(", ") : ""; + } + + /** + * @returns {string} + */ + detailer() { + let parts = this.parts.map(part => part.detailer() || "").filter(part => part); + return parts.length ? parts.join(", ") : ""; + } + + /** + * @returns {string} + */ + face() { + let parts = this.parts.map(part => part.face() || "").filter(part => part); + return parts.length ? parts.join(", ") : ""; } }; diff --git a/src/art/genAI/buildPromptPony.js b/src/art/genAI/buildPromptPony.js new file mode 100644 index 0000000000000000000000000000000000000000..459c5ba4ebc31488786cc80b3899a56505a65f31 --- /dev/null +++ b/src/art/genAI/buildPromptPony.js @@ -0,0 +1,16 @@ +/** + * @param {FC.SlaveState} slave + * @returns {App.Art.GenAI.Prompt} + */ +// eslint-disable-next-line no-unused-vars +function buildPromptPony(slave) { + let prompts = [ + new App.Art.GenAI.StructurePromptPart(slave), + new App.Art.GenAI.NationalityPromptPart(slave), + new App.Art.GenAI.HairPromptPart(slave), + new App.Art.GenAI.DemographicsPromptPart(slave), + // new App.Art.GenAI.ClothesPromptPart(slave) + ]; + + return new App.Art.GenAI.Prompt(prompts); +} diff --git a/src/art/genAI/ponyPrompts/demographicsPromptPart.js b/src/art/genAI/ponyPrompts/demographicsPromptPart.js new file mode 100644 index 0000000000000000000000000000000000000000..b8c3d2108f64c6cf077a1e2974003c25e47a25c4 --- /dev/null +++ b/src/art/genAI/ponyPrompts/demographicsPromptPart.js @@ -0,0 +1,326 @@ +App.Art.GenAI.DemographicsPromptPart = class DemographicsPromptPart extends App.Art.GenAI.PromptPart { + get isFeminine() { + if (V.aiGenderHint === 1) { // Hormone balance + const hormoneTransitionThreshold = 100; + if (this.slave.hormoneBalance >= hormoneTransitionThreshold) { + return true; // transwoman (or hormone-boosted natural woman) + } + return this.slave.genes === "XX" && (this.slave.hormoneBalance > -hormoneTransitionThreshold); // natural woman, and NOT transman + } else if (V.aiGenderHint === 2) { // Perceived gender + return perceivedGender(this.slave) > 1; + } else if (V.aiGenderHint === 3) { // Pronouns + return this.slave.pronoun === App.Data.Pronouns.Kind.female; + } else { + return false; + } + } + + get isMasculine() { + if (V.aiGenderHint === 1) { // Hormone balance + return !this.isFeminine; + } else if (V.aiGenderHint === 2) { // Perceived gender + return perceivedGender(this.slave) < -1; + } else if (V.aiGenderHint === 3) { // Pronouns + return this.slave.pronoun === App.Data.Pronouns.Kind.male; + } else { + return false; + } + } + + /** + * @override + */ + positive() { + const slave = asSlave(this.slave); + const parts = []; + + // height, age, gender + let height = ''; + + if (slave?.height < 150) { + height = 'short '; + } else if (slave?.height > 180) { + height = 'tall '; + } + + if (this.isFeminine) { + if (slave?.visualAge >= 40) { + parts.push(height + 'mature'); + } else if (slave?.visualAge <= 20) { + parts.push(height + 'young woman'); + } else if ((slave?.visualAge < 18) && V.aiAgeFilter) { + parts.push(height + 'young girl'); + } + + if (perceivedGender(this.slave) < -1) { + parts.push('butch'); + } + } else if (this.isMasculine) { + if (slave?.visualAge >= 40) { + parts.push(height + 'older man'); + } else if (slave?.visualAge <= 20) { + parts.push(height + 'young man'); + } else if ((slave?.visualAge < 18) && V.aiAgeFilter) { + parts.push(height + 'young boy'); + } + + if (perceivedGender(this.slave) > 1) { + parts.push('feminine'); + } + } + + if (slave?.visualAge >= 60) { + parts.push('elderly'); + } + + if (slave?.visualAge <= 16 && V.aiAgeFilter) { + parts.push('teenager'); + } + + // weight + if (slave?.weight < -95) { + parts.push('skinny, emaciated'); + } else if (slave?.weight < -30) { + parts.push('skinny'); + } else if (slave?.weight < -10) { + parts.push('slim'); + } else if (slave?.weight > 10) { + parts.push('curvy'); + } else if (slave?.weight > 30) { + parts.push('fat'); + } else if (slave?.weight > 95) { + parts.push('fat, obese'); + } + + // skin colour + if (this.slave.geneticQuirks.albinism === 2) { + parts.push("albino"); + } + + switch (slave?.skin) { + case "pure white": + case "ivory": + case "white": + case "extremely fair": + parts.push("white skin"); + break; + case "extremely pale": + case "very pale": + case "pale": + case "light beige": + case "very fair": + parts.push("pale skin"); + break; + case "fair": + case "light": + case "beige": + parts.push("fair skin"); + break; + case "light olive": + case "sun tanned": + case "spray tanned": + case "tan": + parts.push("tan skin"); + break; + case "olive": + case "bronze": + case "dark beige": + parts.push("olive skin"); + break; + case "dark olive": + case "light brown": + case "brown": + parts.push("brown skin"); + break; + case "dark": + case "dark brown": + case "black": + case "ebony": + parts.push("dark skin"); + break; + case "pure black": + parts.push("black skin"); + break; + default: + parts.push(`${this.slave.skin} skin`); + } + + // muscles + if (slave?.muscles > 95) { + parts.push('huge muscles'); + } else if (slave?.muscles > 50) { + parts.push('muscles'); + } else if (slave?.muscles > 30) { + parts.push('toned'); + } + + // breasts + let breastShape = ''; + switch (slave?.boobShape) { + case "perky": + case "torpedo-shaped": + breastShape = 'perky '; + break; + case "downward-facing": + case "saggy": + case "wide-set": + breastShape = 'saggy '; + break; + } + + if (slave?.boobs < 300) { + breastShape += 'flat chest'; + } else if (slave?.boobs < 400) { + breastShape += 'flat breasts'; + } else if (slave?.boobs < 500) { + breastShape += 'small '; + } else if (slave?.boobs < 650 || (this.slave.visualAge < 6 && !V.aiAgeFilter)) { + breastShape += 'medium '; + } else if (slave?.boobs < 800 || (this.slave.visualAge < 10 && !V.aiAgeFilter)) { + breastShape += 'large '; + } else if (slave?.boobs < 1000 || (this.slave.visualAge < 18 && !V.aiAgeFilter)) { + breastShape += 'huge '; + } else if (slave?.boobs < 1400) { + breastShape += 'giant breasts, huge '; + } + + // breast/nipple exposure for clothing + const clothingBreasts = [ + "no clothing", "chains", "body oil", "uncomfortable straps", "shibari robes", "striped panties", "clubslut netting", "striped underwear", "a thong", "a skimpy loincloth", "boyshorts", "panties", "cutoffs", "sport shorts", "harem gauze", "slutty jewelry", "a slutty pony outfit" + ]; + const clothingCleavage = [ + "conservative clothing", "Western clothing", "a slutty qipao", "spats and a tank top", "a latex catsuit", "attractive lingerie", "attractive lingerie for a pregnant woman", "kitty lingerie", "a maternity dress", "stretch pants and a crop-top", "a succubus outfit", "a fallen nuns habit", "a cheerleader outfit", "cutoffs and a t-shirt", "slutty business attire", "a ball gown", "a slave gown", "a halter top dress", "an evening dress", "a mini dress", "a bunny outfit", "a slutty maid outfit", "a slutty nurse outfit", "a dirndl", "a gothic lolita dress", "a button-up shirt and panties", "a button-up shirt", "a t-shirt", "a tank-top", "a tube top", "a bra", "a sports bra", "a striped bra", "a tube top and thong", "a tank-top and panties", "a t-shirt and thong", "leather pants and a tube top", "a bimbo outfit", "a slutty outfit", "a courtesan dress" + ]; + const clothingSideboob = [ + "a toga", "a chattel habit", "a scalemail bikini", "a slave gown", "a leotard", "an apron", "overalls", "a courtesan dress" + ]; + const clothingUnderboob = [ + "stretch pants and a crop-top", "a succubus outfit", "a penitent nuns habit", "a t-shirt", "a tank-top", "a tube top", "a tube top and thong", "a tank-top and panties", "a t-shirt and thong", "a t-shirt and panties", "leather pants and a tube top", "harem gauze", "a bimbo outfit", "a slutty outfit" + ]; + + const clothingBreastParts = []; + + if (clothingBreasts.includes(slave?.clothes)) { + if (slave?.boobsImplant === 0) { + clothingBreastParts.push('breasts'); + } else { + clothingBreastParts.push('fake tits'); + } + } + if (clothingCleavage.includes(slave?.clothes)) { + clothingBreastParts.push('cleavage'); + } + if (clothingSideboob.includes(slave?.clothes)) { + clothingBreastParts.push('sideboob'); + } + if (clothingUnderboob.includes(slave?.clothes)) { + clothingBreastParts.push('underboob'); + } + if (clothingBreastParts.length === 0) { + if (slave?.boobsImplant === 0) { + clothingBreastParts.push('breasts'); + } else { + clothingBreastParts.push('fake tits'); + } + } + + parts.push(breastShape += clothingBreastParts.join(', ')); + + // display nipple if uncovered, or covered nipples if large + // nipple piercings also handled here + if (clothingBreasts.includes(slave?.clothes)) { + switch (slave?.nipples) { + case "huge": + parts.push('big nipples'); + break; + case "puffy": + parts.push('puffy nipples'); + break; + case "fuckable": + case "inverted": + case "partially inverted": + parts.push('inverted nipples'); + break; + } + + if ((slave?.piercing.areola.weight > 0 || slave?.piercing.nipple.weight > 0) && (slave?.visualAge < 18 && V.aiAgeFilter)) { + parts.push('nipple piercings'); // the models can only handle this in a very basic way + } + } else { + switch (slave?.nipples) { + case "huge": + case "puffy": + parts.push('covered nipples'); + break; + default: + if (slave?.visualAge < 18 && V.aiAgeFilter) { + parts.push('covered nipples'); + } + } + } + + // waist and hips + if (slave?.waist < -40) { + parts.push('narrow waist'); + } + + if (slave?.hips > 1) { + parts.push('wide hips'); + } + if (slave?.hips > 2) { + parts.push('huge ass'); // causes preference for behind view and conflicts with other parts of the prompt -- consider removing if troublesome + } + + return parts.join(', '); + } + + /** + * @override + */ + negative() { + const slave = asSlave(this.slave); + const parts = []; + + if (slave?.visualAge < 18 && V.aiAgeFilter) { + parts.push('source_nsfw, nude'); + } + + // handles masculine female phenotype + if (this.isFeminine && perceivedGender(this.slave) < -1) { + parts.push('woman'); + } + + if (slave?.weight < -30) { + parts.push('fat'); + } + + if (slave?.muscles < -30) { + parts.push('muscles'); + } + + switch (slave?.boobShape) { + case "perky": + case "torpedo-shaped": + case "spherical": + parts.push('saggy'); + break; + default: + if (slave?.boobsImplant > 1) { + parts.push('saggy'); + } + } + + if (slave?.waist > 10) { + parts.push('narrow waist'); + } + + if (slave?.hips < -1) { + parts.push('wide hips'); + } + if (slave?.hips < -2) { + parts.push('huge ass'); + } + + return parts.join(', '); + } +}; diff --git a/src/art/genAI/ponyPrompts/structurePromptPart.js b/src/art/genAI/ponyPrompts/structurePromptPart.js new file mode 100644 index 0000000000000000000000000000000000000000..55039c6e618afc3e844763bbee974dfd0c181772 --- /dev/null +++ b/src/art/genAI/ponyPrompts/structurePromptPart.js @@ -0,0 +1,61 @@ +App.Art.GenAI.StructurePromptPart = class StructurePromptPart extends App.Art.GenAI.PromptPart { + /** + * @override + */ + positive() { + // define stance and image composition + + const slave = asSlave(this.slave); + const parts = []; + + parts.push('realistic, portrait, solo'); // portrait structure prompt may become unnecessary once rest of body part prompts are in? + + if (isQuadrupedal(this.slave)) { + return 'all fours'; // too hard to do anything else with this stance + } else if (canStand(this.slave)) { + parts.push('standing'); + + if (slave?.trust < -20) { + parts.push('look away, look down'); + } else if (slave?.devotion < -20) { + parts.push('defiant, head up'); + } + } else { + parts.push('kneeling'); + + if ((slave?.trust < -20) && (slave?.devotion < 51)) { + parts.push('look down, bowing'); + } else if (slave?.devotion > 95) { + parts.push('from side, look up'); + } + } + + if ((V.arcologies[0].FSChattelReligionist > 0) && (slave?.devotion > 95)) { + parts.push('praying'); + } else if ((slave?.need > 100) && (slave?.fetishKnown === 1) && (slave?.fetishStrength > 60)) { + switch (slave?.fetish) { + case "boobs": + parts.push('holding breasts'); + break; + case "pregnancy": + parts.push('holding waist'); + break; + case "cumslut": + parts.push('holding mouth'); + break; + case "buttslut": + parts.push('holding ass'); + break; + case "dom": + parts.push('hands on hips'); + break; + } + } + + return parts.join(', '); + } + + negative() { + return undefined; + } +}; diff --git a/src/art/genAI/stableDiffusion.js b/src/art/genAI/stableDiffusion.js index 37a33addaaa3f2603a6b35354a2dade41e7f1a48..f635632242b6bd9199f56089bfa89d43e340139d 100644 --- a/src/art/genAI/stableDiffusion.js +++ b/src/art/genAI/stableDiffusion.js @@ -77,7 +77,7 @@ App.Art.GenAI.StableDiffusionSettings = class { App.Art.GenAI.ComfyUISettings = class { /** - * @param {Object} prompt + * @param {object} prompt */ constructor(prompt) { this.prompt = prompt; @@ -95,7 +95,7 @@ App.Art.GenAI.ComfyUISettings = class { async function fetchWithTimeout(url, timeout, options) { const controller = new AbortController(); const id = setTimeout(() => controller.abort(), timeout); - const response = await fetch(url, { signal: controller.signal, ...options }); + const response = await fetch(url, {signal: controller.signal, ...options}); clearTimeout(id); return response; } @@ -153,24 +153,24 @@ App.Art.GenAI.StableDiffusionClientQueue = class { let client_id = Math.random().toExponential(); let api_url = V.aiApiUrl.match(/\/\/(.*)/)[1]; let ws = new WebSocket(`ws://${api_url}/ws?clientId=${client_id}`); - let obj = { images: [] }; + let obj = {images: []}; let img_blob; ws.onopen = () => { let req_body = JSON.parse(options.body); - req_body['client_id'] = client_id; - options.body = JSON.stringify(req_body) + req_body.client_id = client_id; + options.body = JSON.stringify(req_body); fetch(`${V.aiApiUrl}/prompt`, options).then(res => { if (res.status === 400) { res.json().then(res => { - this.resetWorkingOnProperties(); + // this.resetWorkingOnProperties(); top.rejects.forEach(reject => reject(`${top.slaveID}: Error sending prompt to ComfyUi - error message: ${res.error.message}`)); // console.log(res.node_errors); ws.close(); - }) + }); } - }) - } + }); + }; ws.onmessage = (ev) => { let msg = ev.data; @@ -188,7 +188,7 @@ App.Art.GenAI.StableDiffusionClientQueue = class { } blobToBase64(img_blob).then(res => { - obj['images'] = [res]; + obj.images = [res]; return obj; }).then(obj => { top.resolves.forEach(resolve => resolve(obj)); @@ -198,7 +198,6 @@ App.Art.GenAI.StableDiffusionClientQueue = class { top.rejects.forEach(reject => reject(`${top.slaveID}: Error fetching Stable Diffusion image - status: ${err}`)); }); ws.close(); - } if (message.type === 'execution_error') { this.resetWorkingOnProperties(); @@ -213,17 +212,18 @@ App.Art.GenAI.StableDiffusionClientQueue = class { } else { img_blob = msg; } - } + }; ws.onerror = () => { this.resetWorkingOnProperties(); top.rejects.forEach(reject => reject(`${top.slaveID}: Error connecting to ComfyUi`)); - } + }; ws.onclose = (ev) => { + this.resetWorkingOnProperties(); this.updateQueueCounts(); this.process(); - } + }; } /** @@ -276,7 +276,7 @@ App.Art.GenAI.StableDiffusionClientQueue = class { this.process(); }); } else { - this.openWebSocket(top, options) + this.openWebSocket(top, options); } } catch (err) { this.resetWorkingOnProperties(); @@ -416,7 +416,7 @@ App.Art.GenAI.StableDiffusionClientQueue = class { "Content-Type": "application/json", }, }; - let relative_url = V.aiUserInterface === 0 ? '/sdapi/v1/interrupt' : '/interrupt' + let relative_url = V.aiUserInterface === 0 ? '/sdapi/v1/interrupt' : '/interrupt'; fetchWithTimeout(`${V.aiApiUrl}${relative_url}`, 1000, options) .then(() => { console.log("Stable Diffusion: Interrupt Sent."); @@ -445,7 +445,7 @@ App.Art.GenAI.StableDiffusionClient = class { */ async buildStableDiffusionSettings(slave, steps) { if (V.aiUserInterface === 0) { - const prompt = buildPrompt(slave); + const prompt = V.aiPonyExperimental ? buildPromptPony(slave) : buildPrompt(slave); // TODO: Add more config options to ADetailer, and add ReActor const alwaysOnScripts = {}; @@ -508,10 +508,11 @@ App.Art.GenAI.StableDiffusionClient = class { }); return settings; } else { - const prompt = buildPrompt(slave); - let prompt_wrkflw = buildBPrompt(); + const prompt = V.aiPonyExperimental ? buildPromptPony(slave) : buildPrompt(slave); + let prompt_wrkflw = V.aiPonyExperimental ? buildPonyWorkflow() : buildComfyWorkflow(); - function buildBPrompt() { + // eslint-disable-next-line no-inner-declarations + function buildComfyWorkflow() { let prompt_wrkflw = { "1": { "inputs": { @@ -642,7 +643,7 @@ App.Art.GenAI.StableDiffusionClient = class { "title": "SaveImageWebsocket" } } - } + }; if (V.aiLoraPack) { prompt_wrkflw[19] = { @@ -653,7 +654,7 @@ App.Art.GenAI.StableDiffusionClient = class { "_meta": { "title": "Lora Text Extractor Positive Prompt" } - } + }; prompt_wrkflw[20] = { "inputs": { "text": ['19', 1], @@ -670,9 +671,9 @@ App.Art.GenAI.StableDiffusionClient = class { "_meta": { "title": "MultiLora Loader" } - } + }; // @ts-ignore - prompt_wrkflw[4]['inputs']['text'] = ['19', 0] + prompt_wrkflw[4].inputs.text = ['19', 0]; } if (V.aiDynamicCfgEnabled) { @@ -698,8 +699,8 @@ App.Art.GenAI.StableDiffusionClient = class { "_meta": { "title": "DynamicThresholdingFull" } - } - prompt_wrkflw['6']['inputs']['model'][0] = "10" + }; + prompt_wrkflw['6'].inputs.model[0] = "10"; } if (V.aiAdetailerFace) { @@ -764,7 +765,7 @@ App.Art.GenAI.StableDiffusionClient = class { "_meta": { "title": "FaceDetailer" } - } + }; prompt_wrkflw[29] = { "inputs": { "model_name": "bbox/face_yolov8m.pt" @@ -773,20 +774,20 @@ App.Art.GenAI.StableDiffusionClient = class { "_meta": { "title": "UltralyticsDetectorProvider" } - } - prompt_wrkflw['8']['inputs']['images'][0] = "30" - prompt_wrkflw['9']['inputs']['images'][0] = "30" + }; + prompt_wrkflw['8'].inputs.images[0] = "30"; + prompt_wrkflw['9'].inputs.images[0] = "30"; if (V.aiLoraPack) { - prompt_wrkflw[30]['inputs']['model'][0] = "20" + prompt_wrkflw[30].inputs.model[0] = "20"; } if (V.aiDynamicCfgEnabled) { - prompt_wrkflw[30]['inputs']['model'][0] = "10" + prompt_wrkflw[30].inputs.model[0] = "10"; } } if (V.aiUpscale) { if (!V.aiUpscaler.endsWith('.pth')) { - V.aiUpscaler = V.aiUpscaler.concat('.pth') + V.aiUpscaler = V.aiUpscaler.concat('.pth'); } prompt_wrkflw[48] = { "inputs": { @@ -796,7 +797,7 @@ App.Art.GenAI.StableDiffusionClient = class { "_meta": { "title": "Load Upscale Model" } - } + }; prompt_wrkflw[49] = { "inputs": { "upscale_model": [ @@ -812,7 +813,7 @@ App.Art.GenAI.StableDiffusionClient = class { "_meta": { "title": "Upscale Image (using Model)" } - } + }; prompt_wrkflw[50] = { "inputs": { "upscale_method": "area", @@ -828,49 +829,649 @@ App.Art.GenAI.StableDiffusionClient = class { "_meta": { "title": "Upscale Image" } + }; + prompt_wrkflw['8'].inputs.images[0] = "50"; + prompt_wrkflw['9'].inputs.images[0] = "50"; + } + return prompt_wrkflw; + } + + // eslint-disable-next-line no-inner-declarations + function buildPonyWorkflow() { + let loraStackOutput = ''; + + try { + let loraStackInput = JSON.parse(V.aiPonyLoraStack); + + let i = 0; + for (let [key, value] of Object.entries(loraStackInput)) { + i++; + loraStackOutput += `"lora_${i}": {"on": true, "lora": "` + key + `", "strength": ` + value + `},`; } - prompt_wrkflw['8']['inputs']['images'][0] = "50" - prompt_wrkflw['9']['inputs']['images'][0] = "50" + } catch (error) { + loraStackOutput = ` + "lora_1": { + "on": true, + "lora": "Styles\\Concept Art DarkSide Style LoRA_Pony XL v6.safetensors", + "strength": 0.5 + }, + "lora_2": { + "on": true, + "lora": "Styles\\Digital Art Style SDXL_LoRA_Pony Diffusion V6 XL.safetensors", + "strength": 0.2 + }, + "lora_3": { + "on": true, + "lora": "Styles\\Photo 2 Style SDXL_LoRA_Pony Diffusion V6 XL.safetensors", + "strength": 0.2 + } + `; } - return prompt_wrkflw + + let ponyCheckpoint = V.aiPonyTurbo ? "ponyDiffusionV6XL_v6TurboMerge.safetensors" : "ponyDiffusionV6XL_v6StartWithThisOne.safetensors"; + let ponySamplerName = V.aiPonyTurbo ? "dpmpp_sde_gpu" : "ipndm_v"; + let ponySamplerSteps = V.aiPonyTurbo ? 8 : 20; + let ponySCFG = V.aiPonyTurbo ? 3 : 5; + + // flow controls for hiRes and Face Detailer passes + let hiResPass = V.aiPonyHiResPass ? "19" : "17"; + let faceDetailPass = V.aiPonyFaceDetailer ? "28" : "20"; + + let prompt_wrkflw = { + "1": { + "inputs": { + "ckpt_name": ponyCheckpoint + }, + "class_type": "CheckpointLoaderSimple", + "_meta": { + "title": "Load Checkpoint" + } + }, + "2": { + "inputs": { + "PowerLoraLoaderHeaderWidget": { + "type": "PowerLoraLoaderHeaderWidget" + }, + loraStackOutput, + "➕ Add Lora": "", + "model": [ + "8", + 0 + ], + "clip": [ + "1", + 1 + ] + }, + "class_type": "Power Lora Loader (rgthree)", + "_meta": { + "title": "Art Style" + } + }, + "3": { + "inputs": { + "seed": slave?.natural.artSeed + }, + "class_type": "Seed (rgthree)", + "_meta": { + "title": "Seed" + } + }, + "4": { + "inputs": { + "text": "score_9, score_8_up, score_7_up" + }, + "class_type": "Text Multiline", + "_meta": { + "title": "High Quality Preset" + } + }, + "5": { + "inputs": { + "delimiter": ", ", + "clean_whitespace": "true", + "text_a": [ + "4", + 0 + ], + "text_b": [ + "6", + 0 + ] + }, + "class_type": "Text Concatenate", + "_meta": { + "title": "Preset Concat" + } + }, + "6": { + "inputs": { + "text": "BREAK" + }, + "class_type": "Text Multiline", + "_meta": { + "title": "Preset Break" + } + }, + "7": { + "inputs": { + "text": [ + "5", + 0 + ], + "clip": [ + "2", + 1 + ] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "Preset Encode" + } + }, + "8": { + "inputs": { + "Skimming_CFG": ponySCFG, + "model": [ + "1", + 0 + ] + }, + "class_type": "Skimmed CFG - linear interpolation", + "_meta": { + "title": "S-CFG" + } + }, + "9": { + "inputs": { + "width": V.aiWidth, + "height": V.aiHeight, + "batch_size": 1 + }, + "class_type": "EmptyLatentImage", + "_meta": { + "title": "Empty Latent Image" + } + }, + "10": { + "inputs": { + "steps_total": ponySamplerSteps, + "refiner_step": 3, + "cfg": V.aiCfgScale, + "sampler_name": ponySamplerName, + "scheduler": "karras" + }, + "class_type": "KSampler Config (rgthree)", + "_meta": { + "title": "KSampler Config" + } + }, + "11": { + "inputs": { + "op": "Add", + "a": [ + "10", + 2 + ], + "b": 2 + }, + "class_type": "CM_FloatBinaryOperation", + "_meta": { + "title": "SamplerCFGMod" + } + }, + "12": { + "inputs": { + "scheduler": [ + "10", + 4 + ], + "extra_scheduler": "AYS SDXL" + }, + "class_type": "ImpactSchedulerAdapter", + "_meta": { + "title": "Scheduler Config" + } + }, + "13": { + "inputs": { + "conditioning1": [ + "7", + 0 + ], + "conditioning2": [ + "14", + 0 + ] + }, + "class_type": "ImpactConcatConditionings", + "_meta": { + "title": "Conditioning Concat" + } + }, + "14": { + "inputs": { + "text": prompt.positive(), + "clip": [ + "2", + 1 + ] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "Positive Prompt" + } + }, + "15": { + "inputs": { + "text": prompt.negative(), + "clip": [ + "2", + 1 + ] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "Negative Prompt" + } + }, + "16": { + "inputs": { + "model": [ + "2", + 0 + ], + "clip": [ + "2", + 1 + ], + "vae": [ + "1", + 2 + ], + "positive": [ + "13", + 0 + ], + "negative": [ + "15", + 0 + ] + }, + "class_type": "ToBasicPipe", + "_meta": { + "title": "ToBasicPipe" + } + }, + "17": { + "inputs": { + "seed": [ + "3", + 0 + ], + "steps": [ + "10", + 0 + ], + "cfg": [ + "10", + 2 + ], + "sampler_name": [ + "10", + 3 + ], + "scheduler": [ + "12", + 0 + ], + "denoise": 1, + "basic_pipe": [ + "16", + 0 + ], + "latent_image": [ + "9", + 0 + ] + }, + "class_type": "ImpactKSamplerBasicPipe", + "_meta": { + "title": "KSampler" + } + }, + "18": { + "inputs": { + "version": "SDXL", + "upscale": 1.5, + "latent": [ + "17", + 1 + ] + }, + "class_type": "NNLatentUpscale", + "_meta": { + "title": "NNLatentUpscale" + } + }, + "19": { + "inputs": { + "add_noise": true, + "noise_seed": [ + "3", + 0 + ], + "steps": [ + "10", + 0 + ], + "cfg": [ + "11", + 0 + ], + "sampler_name": [ + "10", + 3 + ], + "scheduler": [ + "12", + 0 + ], + "start_at_step": [ + "10", + 1 + ], + "end_at_step": [ + "10", + 0 + ], + "return_with_leftover_noise": false, + "basic_pipe": [ + "24", + 0 + ], + "latent_image": [ + "18", + 0 + ] + }, + "class_type": "ImpactKSamplerAdvancedBasicPipe", + "_meta": { + "title": "KSampler 2" + } + }, + "20": { + "inputs": { + "samples": [ + hiResPass, + 1 + ], + "vae": [ + hiResPass, + 2 + ] + }, + "class_type": "VAEDecode", + "_meta": { + "title": "VAE Decode" + } + }, + "21": { + "inputs": { + "images": [ + faceDetailPass, + 0 + ] + }, + "class_type": "SaveImageWebsocket", + "_meta": { + "title": "SaveImageWebsocket" + } + }, + "22": { + "inputs": { + "text": prompt.detailer(), + "clip": [ + "2", + 1 + ] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "High Res Prompt" + } + }, + "23": { + "inputs": { + "conditioning1": [ + "7", + 0 + ], + "conditioning2": [ + "22", + 0 + ], + "conditioning3": [ + "13", + 0 + ] + }, + "class_type": "ImpactConcatConditionings", + "_meta": { + "title": "Conditioning Concat" + } + }, + "24": { + "inputs": { + "basic_pipe": [ + "17", + 0 + ], + "positive": [ + "23", + 0 + ] + }, + "class_type": "EditBasicPipe", + "_meta": { + "title": "Edit BasicPipe" + } + }, + "25": { + "inputs": { + "bbox_threshold": 0.5, + "bbox_dilation": 0, + "crop_factor": 3, + "drop_size": 10, + "sub_threshold": 0.5, + "sub_dilation": 0, + "sub_bbox_expansion": 0, + "sam_mask_hint_threshold": 0.7, + "post_dilation": 0, + "bbox_detector": [ + "26", + 0 + ], + "image": [ + "20", + 0 + ], + "sam_model_opt": [ + "27", + 0 + ] + }, + "class_type": "ImpactSimpleDetectorSEGS", + "_meta": { + "title": "Face Detector" + } + }, + "26": { + "inputs": { + "model_name": "bbox/face_yolov8m.pt" + }, + "class_type": "UltralyticsDetectorProvider", + "_meta": { + "title": "Face BBox" + } + }, + "27": { + "inputs": { + "model_name": "sam_vit_b_01ec64.pth", + "device_mode": "AUTO" + }, + "class_type": "SAMLoader", + "_meta": { + "title": "SAM Loader" + } + }, + "28": { + "inputs": { + "guide_size": 512, + "guide_size_for": true, + "max_size": 1024, + "seed": [ + "3", + 0 + ], + "steps": [ + "10", + 0 + ], + "cfg": [ + "10", + 2 + ], + "sampler_name": [ + "10", + 3 + ], + "scheduler": [ + "12", + 0 + ], + "denoise": 0.5, + "feather": 5, + "noise_mask": true, + "force_inpaint": true, + "wildcard": "", + "refiner_ratio": 0.2, + "cycle": 1, + "inpaint_model": false, + "noise_mask_feather": 20, + "image": [ + "20", + 0 + ], + "segs": [ + "25", + 0 + ], + "basic_pipe": [ + "29", + 0 + ] + }, + "class_type": "DetailerForEachPipe", + "_meta": { + "title": "Face Detailer" + } + }, + "29": { + "inputs": { + "basic_pipe": [ + hiResPass, + 0 + ], + "positive": [ + "31", + 0 + ] + }, + "class_type": "EditBasicPipe", + "_meta": { + "title": "Edit BasicPipe" + } + }, + "30": { + "inputs": { + "text": prompt.face(), + "clip": [ + "2", + 1 + ] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "Face Prompt" + } + }, + "31": { + "inputs": { + "conditioning1": [ + "7", + 0 + ], + "conditioning2": [ + "30", + 0 + ], + "conditioning3": [ + "22", + 0 + ], + "conditioning4": [ + "13", + 0 + ] + }, + "class_type": "ImpactConcatConditionings", + "_meta": { + "title": "Conditioning Concat" + } + } + }; + + return prompt_wrkflw; } + // custom workflow handling try { if (V.aiComfyWrkflw !== '') { let wrkflw_name = V.aiComfyWrkflw; if (!wrkflw_name.endsWith('.json')) { - wrkflw_name = wrkflw_name.concat('.json') + wrkflw_name = wrkflw_name.concat('.json'); } prompt_wrkflw = await fetch(`resources/workflows/${wrkflw_name}`).then(wrkflw => { return wrkflw.json(); }).then(val => { return val; - }) + }); - let prompt_pos_node = get_nodeId(prompt_wrkflw, 'positive prompt') - let prompt_neg_node = get_nodeId(prompt_wrkflw, 'negative prompt') - let seed_node = get_nodeId(prompt_wrkflw, 'seed') + let prompt_pos_node = get_nodeId(prompt_wrkflw, 'positive prompt'); + let prompt_neg_node = get_nodeId(prompt_wrkflw, 'negative prompt'); + let seed_node = get_nodeId(prompt_wrkflw, 'seed'); - prompt_wrkflw[prompt_pos_node]['inputs']['text'] = prompt.positive(); - prompt_wrkflw[prompt_neg_node]['inputs']['text'] = prompt.negative(); - prompt_wrkflw[seed_node]['inputs']['seed'] = slave.natural.artSeed; + prompt_wrkflw[prompt_pos_node].inputs.text = prompt.positive(); + prompt_wrkflw[prompt_neg_node].inputs.text = prompt.negative(); + prompt_wrkflw[seed_node].inputs.seed = slave.natural.artSeed; function get_nodeId(wrkflw, exp) { let node; for (let prompt_id in wrkflw) { - node = wrkflw[prompt_id] + node = wrkflw[prompt_id]; - let node_title = node["_meta"]["title"].toLowerCase() + let node_title = node._meta.title.toLowerCase(); if (node_title.match(exp)) { - return prompt_id + return prompt_id; } } } } } catch (err) { console.error(`Error interpreting Workflow .json - status: ${err}`); - prompt_wrkflw = buildBPrompt() + prompt_wrkflw = V.aiPonyExperimental ? buildPonyWorkflow() : buildComfyWorkflow(); } const settings = new App.Art.GenAI.ComfyUISettings(prompt_wrkflw); return settings; @@ -884,7 +1485,7 @@ App.Art.GenAI.StableDiffusionClient = class { * @returns {Promise<Response>} */ async fetchAPIQuery(relativeUrl, method = "GET") { - return fetchWithTimeout(`${V.aiApiUrl}${relativeUrl}`, 30000, { method: method }); + return fetchWithTimeout(`${V.aiApiUrl}${relativeUrl}`, 30000, {method: method}); } /** @@ -908,7 +1509,7 @@ App.Art.GenAI.StableDiffusionClient = class { * @returns {Promise<string[]>} */ async getUpscalerList() { - let relative_url = V.aiUserInterface === 0 ? `/sdapi/v1/upscalers` : `/object_info/UpscaleModelLoader` + let relative_url = V.aiUserInterface === 0 ? `/sdapi/v1/upscalers` : `/object_info/UpscaleModelLoader`; return this.fetchAPIQuery(`${relative_url}`) .then((value) => { return value.json(); @@ -917,7 +1518,7 @@ App.Art.GenAI.StableDiffusionClient = class { if (V.aiUserInterface === 0) { return list.map(o => o.name); } else { - return list['UpscaleModelLoader']['input']['required']['model_name']['0']; + return list.UpscaleModelLoader.input.required.model_name['0']; } }) .catch(err => { @@ -930,7 +1531,7 @@ App.Art.GenAI.StableDiffusionClient = class { * @returns {Promise<string[]>} */ async getSamplerList() { - let relative_url = V.aiUserInterface === 0 ? `/sdapi/v1/samplers` : `/object_info/KSampler` + let relative_url = V.aiUserInterface === 0 ? `/sdapi/v1/samplers` : `/object_info/KSampler`; return this.fetchAPIQuery(`${relative_url}`) .then((value) => { return value.json(); @@ -939,7 +1540,7 @@ App.Art.GenAI.StableDiffusionClient = class { if (V.aiUserInterface === 0) { return list.map(o => o.name); } else { - return list['KSampler']['input']['required']['sampler_name']['0']; + return list.KSampler.input.required.sampler_name['0']; } }) .catch(err => { @@ -953,7 +1554,7 @@ App.Art.GenAI.StableDiffusionClient = class { * @returns {Promise<string[]>} */ async getSchedulerList() { - let relative_url = V.aiUserInterface === 0 ? `/sdapi/v1/schedulers` : `/object_info/KSampler` + let relative_url = V.aiUserInterface === 0 ? `/sdapi/v1/schedulers` : `/object_info/KSampler`; return this.fetchAPIQuery(`${relative_url}`) .then((value) => { return value.json(); @@ -962,7 +1563,7 @@ App.Art.GenAI.StableDiffusionClient = class { if (V.aiUserInterface === 0) { return list.map(o => o.label); } else { - return list['KSampler']['input']['required']['scheduler']['0']; + return list.KSampler.input.required.scheduler['0']; } }) .catch(err => { @@ -979,7 +1580,7 @@ App.Art.GenAI.StableDiffusionClient = class { async getSysInfo() { return this.fetchAPIQuery(`/internal/sysinfo`) .then((value) => { - return value.json() + return value.json(); }) .catch(err => { console.log(`Failed to get sysinfo from Stable Diffusion.`); @@ -1136,7 +1737,7 @@ App.Art.GenAI.StableDiffusionClient = class { if (this.#loraCachedList !== undefined) { return (!V.aiLoraPack) ? [] : this.#loraCachedList; } - let relative_url = V.aiUserInterface === 0 ? `/sdapi/v1/loras` : `/object_info/LoraLoader` + let relative_url = V.aiUserInterface === 0 ? `/sdapi/v1/loras` : `/object_info/LoraLoader`; /** @type {string[]} */ let list = await this.fetchAPIQuery(`${relative_url}`) // cSpell:enable @@ -1150,7 +1751,7 @@ App.Art.GenAI.StableDiffusionClient = class { } }); } else { - list['LoraLoader']['input']['required']['lora_name']['0'].forEach((item) => { + list.LoraLoader.input.required.lora_name['0'].forEach((item) => { if (!entries.includes(item)) { entries.push(item.match(/([^\\]+)\./)[1]); } @@ -1190,7 +1791,7 @@ App.Art.GenAI.StableDiffusionClient = class { return (this.#loraCachedList.includes(loraName)); } - /** + /** * @returns {Promise<boolean>} */ async hasImpactPack() { @@ -1273,7 +1874,7 @@ App.Art.GenAI.StaticCaching = class { let settingsSlave = slave; if (V.aiUseRAForEvents && isEventImage) { settingsSlave = structuredClone(slave); - DefaultRules(settingsSlave, { aiPromptsOnly: true }); + DefaultRules(settingsSlave, {aiPromptsOnly: true}); } const settings = await App.Art.GenAI.sdClient.buildStableDiffusionSettings(settingsSlave, steps); const body = JSON.stringify(settings); @@ -1341,7 +1942,7 @@ App.Art.GenAI.StaticCaching = class { } // If new image, add or replace it in if (imagePreexisting === -1) { - const imageId = await App.Art.GenAI.staticImageDB.putImage({ data: imageData, week: V.week }); + const imageId = await App.Art.GenAI.staticImageDB.putImage({data: imageData, week: V.week}); if (replacementImageIndex !== null) { await App.Art.GenAI.staticImageDB.removeImage(slave.custom.aiImageIds[replacementImageIndex]); slave.custom.aiImageIds[replacementImageIndex] = imageId; @@ -1361,11 +1962,11 @@ App.Art.GenAI.StaticCaching = class { * @param {(progress: number) => void} fn A function to call when there's progress * @returns {FC.PromiseWithProgress<void>} */ - onProgress(fn) { - progressFns.push(fn); - return result; + onProgress(fn) { + progressFns.push(fn); + return result; + } } - } ); const interval = setInterval(async () => { @@ -1417,7 +2018,7 @@ App.Art.GenAI.ReactiveCaching = class { let settingsSlave = slave; if (V.aiUseRAForEvents && isEventImage) { settingsSlave = structuredClone(slave); - DefaultRules(settingsSlave, { aiPromptsOnly: true }); + DefaultRules(settingsSlave, {aiPromptsOnly: true}); } const settings = await App.Art.GenAI.sdClient.buildStableDiffusionSettings(settingsSlave, steps); const body = JSON.stringify(settings); @@ -1477,7 +2078,7 @@ App.Art.GenAI.ReactiveCaching = class { } // If new image, add or replace it in if (imagePreexisting === -1) { - const imageId = await App.Art.GenAI.reactiveImageDB.putImage({ data: imageData }); + const imageId = await App.Art.GenAI.reactiveImageDB.putImage({data: imageData}); if (replacementImageIndex !== null) { await App.Art.GenAI.reactiveImageDB.removeImage(slave.custom.aiImageIds[replacementImageIndex]); slave.custom.aiImageIds[replacementImageIndex] = imageId; @@ -1522,7 +2123,7 @@ async function compareExistingImages(slave, newImageData) { */ function getImageData(base64Image) { if (V.aiUserInterface === 1) { - return base64Image + return base64Image; } const mimeType = getMimeType(base64Image); return `data:${mimeType};base64,${base64Image}`; diff --git a/src/endWeek/reports/masterSuiteReport.js b/src/endWeek/reports/masterSuiteReport.js index 6d387f94bcc209d34ff7baad34a55093fe239463..99caf43a4b2f9a1fa162e3d9400437d9d64d0794 100644 --- a/src/endWeek/reports/masterSuiteReport.js +++ b/src/endWeek/reports/masterSuiteReport.js @@ -430,7 +430,7 @@ App.EndWeek.masterSuiteReport = function() { } const partTime = App.SlaveAssignment.PartTime.saPartTime(slave); if (partTime.length > 0) { - App.Events.addNode(el, partTime, "div", "indent"); + App.Events.addNode(frag, partTime, "div", "indent"); } App.Events.addNode(frag, [ App.SlaveAssignment.choosesOwnClothes(slave), diff --git a/src/events/scheduled/pitFight.js b/src/events/scheduled/pitFight.js index 36bebbc909cf58a4c1aa3b944e4e0719da0e053f..518173fa4859dcf1dfa7786451896eaa8962e8a9 100644 --- a/src/events/scheduled/pitFight.js +++ b/src/events/scheduled/pitFight.js @@ -18,7 +18,10 @@ App.Events.SEPitFight = class SEPitFight extends App.Events.BaseEvent { let completedFights = 0; let totalSuccess = 0; let lethalFights = 0; - + let toFight = { + firstFighter: -1, + secondFighter: -1 + }; const fighterMap = new App.Facilities.Pit.Fights.FighterMap(); const interactionSpan = document.createElement("span"); @@ -29,10 +32,35 @@ App.Events.SEPitFight = class SEPitFight extends App.Events.BaseEvent { const f = new DocumentFragment(); App.Events.addParagraph(f, [`Select a fight. You have ${maxFights - completedFights} left.`]); - + const slaveIDs = [...App.Entity.facilities.pit.job("fighter").employeesIDs()]; + if (slaveIDs.length - 2 > 0) { // subtract 2 to account for just bodyguard / only 1 possible fight + SlaveSort.IDs(slaveIDs); + const options = new App.UI.OptionsGroup().customRefresh(() => { refreshInteractionSpan(selectFight()); }); + const firstoption = options.addOption(`First Fighter`, "firstFighter", toFight); + const secondoption = options.addOption(`Second Fighter`, "secondFighter", toFight); + let firstArray = []; + let secondArray = []; + for (const id of slaveIDs) { + if (id === V.BodyguardID) { // skip bodyguard as option + continue; + } + const slave = getSlave(id); + // only add if not the other fighter + firstoption.addValue(SlaveFullName(slave), id).addCallback((id) => id === toFight.secondFighter ? toFight.secondFighter = -1 : undefined); + secondoption.addValue(SlaveFullName(slave), id).addCallback((id) => id === toFight.firstFighter ? toFight.firstFighter = -1 : undefined); + if (id !== toFight.firstFighter) { + secondArray.push(id); + } + if (id !== toFight.secondFighter) { + firstArray.push(id); + } + } + firstoption.customButton("Random", () => toFight.firstFighter = firstArray[(Math.floor(Math.random() * firstArray.length))], ""); + secondoption.customButton("Random", () => toFight.secondFighter = secondArray[(Math.floor(Math.random() * secondArray.length))], ""); + f.append(options.render()); + } const availableFights = App.Facilities.Pit.getFights() - .filter(f => f.fightPrerequisites().every(p => p()) && f.castActors()); - + .filter(f => f.fightPrerequisites().every(p => p()) && f.castActors(toFight)); const choices = []; for (const fight of availableFights) { App.UI.DOM.appendNewElement("div", f, fight.fightDescription()); @@ -67,7 +95,7 @@ App.Events.SEPitFight = class SEPitFight extends App.Events.BaseEvent { if (availableFights.length === 0) { App.UI.DOM.appendNewElement("div", f, "No fights available"); } - App.UI.DOM.appendNewElement("div", f, App.UI.DOM.link("Select different fighters", () => refreshInteractionSpan(selectFight()))); + // App.UI.DOM.appendNewElement("div", f, App.UI.DOM.link("Select different fighters", () => refreshInteractionSpan(selectFight()))); App.UI.DOM.appendNewElement("div", f, App.UI.DOM.link("Head back to the penthouse and cancel all the remaining fights", () => refreshInteractionSpan(finishEvent()))); App.Events.addResponses(f, choices); @@ -120,6 +148,11 @@ App.Events.SEPitFight = class SEPitFight extends App.Events.BaseEvent { } if (completedFights < maxFights) { + // reset fighter choices for next round + toFight = { + firstFighter: -1, + secondFighter: -1 + }; f.append(selectFight()); } else { f.append(finishEvent()); diff --git a/src/facilities/nursery/nurseryDatatypeCleanup.js b/src/facilities/nursery/nurseryDatatypeCleanup.js index 30bba382c3f4cd69195492891476822f248d3d42..70808cba1d7344c34aa36808cbc5df6046a597c5 100644 --- a/src/facilities/nursery/nurseryDatatypeCleanup.js +++ b/src/facilities/nursery/nurseryDatatypeCleanup.js @@ -285,7 +285,7 @@ App.Facilities.Nursery.ChildDatatypeCleanup = function(child) { function childCosmeticsDatatypeCleanup(child) { child.makeup = Math.clamp(+child.makeup, 0, 8) || 0; child.nails = Math.clamp(+child.nails, 0, 9) || 0; - child.scars = Math.clamp(+child.scars, 0, 6) || 0; + delete child.scars; // The scars property is deprecated child.chastityAnus = Math.clamp(+child.chastityAnus, 0, 1) || 0; child.chastityPenis = Math.clamp(+child.chastityPenis, 0, 1) || 0; child.chastityVagina = Math.clamp(+child.chastityVagina, 0, 1) || 0; diff --git a/src/facilities/nursery/utils/nurseryUtils.js b/src/facilities/nursery/utils/nurseryUtils.js index 00514a21e62810759dd80dde9318e9ddd5af1b5a..d870a4b5c352a7eefa477b2c88b9f5d668bfe359 100644 --- a/src/facilities/nursery/utils/nurseryUtils.js +++ b/src/facilities/nursery/utils/nurseryUtils.js @@ -36,6 +36,7 @@ App.Facilities.Nursery.childList = function childList() { } V.readySlave = cribs.pluck(); + delete V.readySlave.targetLocation; r.push(`<<goto "Nursery Retrieval Workaround">>`); return r; } @@ -48,6 +49,7 @@ App.Facilities.Nursery.childList = function childList() { r.push(` ${He} is ready to leave ${V.nurseryName} and ${child.targetLocation === "slavery" ? `join your ménage` : `become a free citizen`}.`); $(list).append(App.UI.DOM.passageLink(targetText, "Nursery Retrieval Workaround", () => { V.readySlave = child; + delete V.readySlave.targetLocation; V.cribs.delete(child); })); } else { diff --git a/src/gui/options/options.js b/src/gui/options/options.js index 1d4f3b4409b4f4eaff114caf25f7b18772ccac5e..e2605b0a27dd7c272b93023468ca6f847493ba5b 100644 --- a/src/gui/options/options.js +++ b/src/gui/options/options.js @@ -1,6 +1,6 @@ // cSpell:ignore SSAA -App.UI.optionsPassage = function () { +App.UI.optionsPassage = function() { const el = new DocumentFragment(); App.UI.DOM.appendNewElement("h1", el, `Game Options`); App.Utils.PassageSwitchHandler.set(App.EventHandlers.optionsChanged); @@ -846,7 +846,7 @@ App.UI.optionsPassage = function () { * @param {boolean} isIntro * @returns {DocumentFragment} */ -App.Intro.display = function (isIntro) { +App.Intro.display = function(isIntro) { const el = new DocumentFragment(); let options; let r; @@ -1043,7 +1043,7 @@ App.Intro.display = function (isIntro) { * @param {boolean} isIntro * @returns {DocumentFragment} */ -App.Intro.contentAndFlavor = function (isIntro) { +App.Intro.contentAndFlavor = function(isIntro) { const el = new DocumentFragment(); let r; let options; @@ -1487,20 +1487,23 @@ App.UI.aiLoraList = () => { /** * @param {InstanceType<App.UI.OptionsGroup>} options */ -App.UI.aiPromptingOptions = function (options) { +App.UI.aiPromptingOptions = function(options) { options.addOption("AI Model", "aiUserInterface").addValueList([ ["A1111", 0], ["ComfyUi", 1] ]).addCallback(res => { - console.log(res) - if (res === 1) { - V.aiOpenPose = false // remove when finished open pose integration + console.log(res); + if (res === 1 || res === 2) { + V.aiOpenPose = false; // remove when finished open pose integration if (!App.Art.GenAI.sdClient.hasLoraLoader()) { - V.aiLoraPack = false // loras work differently using comfyui without an extension + V.aiLoraPack = false; // loras work differently using comfyui without an extension } } }); + options.addOption("Pony Diffusion (Experimental)", "aiPonyExperimental").addValue("Enabled", true).on().addValue("Disabled", false).off() + .addComment("Enable experimental Pony Diffusion workflow"); + options.addOption("AI style prompting", "aiStyle") .addValueList([ ["Photorealistic", 1], @@ -1509,9 +1512,9 @@ App.UI.aiPromptingOptions = function (options) { ]); if (V.aiStyle === 0) { - options.addOption("AI custom style positive prompt", "aiCustomStylePos").showTextBox({ large: true, forceString: true }) + options.addOption("AI custom style positive prompt", "aiCustomStylePos").showTextBox({large: true, forceString: true}) .addComment("Include desired LoRA triggers (<code><lora:LowRA:0.5></code>) and general style prompts relevant to your chosen model ('<code>hand drawn, dark theme, black background</code>'), but no slave-specific prompts"); - options.addOption("AI custom style negative prompt", "aiCustomStyleNeg").showTextBox({ large: true, forceString: true }) + options.addOption("AI custom style negative prompt", "aiCustomStyleNeg").showTextBox({large: true, forceString: true}) .addComment("Include undesired general style prompts relevant to your chosen model ('<code>greyscale, photography, forest, low camera angle</code>'), but no slave-specific prompts"); } else if (V.aiStyle === 1) { options.addComment("For best results, use an appropriately-trained photorealistic base model, such as MajicMIX or Life Like Diffusion."); @@ -1528,15 +1531,18 @@ App.UI.aiPromptingOptions = function (options) { loraSpan.textContent = `Fetching lora-loader, please wait...`; App.Art.GenAI.sdClient.hasLoraLoader().then(result => { if (!result) { - loraSpan.textContent = 'Couldn\'t find lora-loader script' + loraSpan.textContent = 'Couldn\'t find lora-loader script'; loraSpan.classList.add('error'); } else { - loraSpan.textContent = '' + loraSpan.textContent = ''; } - }) + }); + } + + if (!V.aiPonyExperimental) { + options.addOption("LoRA models are", "aiLoraPack") + .addValue("Enabled", true).on().addValue("Disabled", false).off().addComment(loraSpan); } - options.addOption("LoRA models are", "aiLoraPack") - .addValue("Enabled", true).on().addValue("Disabled", false).off().addComment(loraSpan); if (V.aiLoraPack) { options.addCustom(App.UI.aiLoraList()); } @@ -1546,14 +1552,12 @@ App.UI.aiPromptingOptions = function (options) { options.addOption("Gender hints come from", "aiGenderHint") .addValue("Hormone balance", 1).addValue("Perceived gender", 2).addValue("Pronouns", 3) .addComment("How to determine whether to include words like \"woman\" or \"man\" in a prompt."); - - }; /** * @param {InstanceType<App.UI.OptionsGroup>} options */ -App.UI.aiArtOptions = function (options) { +App.UI.aiArtOptions = function(options) { options.addComment("This is experimental. Please follow the setup instructions below."); options.addCustom(App.UI.stableDiffusionInstallationGuide("Stable Diffusion Installation Guide")); options.addCustom(App.UI.comfyUIInstallationGuide("ComfyUI Installation Guide")); @@ -1590,11 +1594,11 @@ App.UI.aiArtOptions = function (options) { options.addCustom("Advanced Config"); - if (V.aiUserInterface === 1) { - options.addOption("Workflow", "aiComfyWrkflw").showTextBox({ unit: '', forceString: true }).addComment('Load a custom workflow. Only usuable with locally hosted files. Must be placed in resources/workflows/') + if (V.aiUserInterface === 1 && !V.aiPonyExperimental) { + options.addOption("Workflow", "aiComfyWrkflw").showTextBox({unit: '', forceString: true}).addComment('Load a custom workflow. Only usuable with locally hosted files. Must be placed in resources/workflows/'); } if (V.aiComfyWrkflw === '' || V.aiUserInterface === 0) { - if (V.aiUserInterface === 1) { + if (V.aiUserInterface === 1 && !V.aiPonyExperimental) { const checkpointSpan = App.UI.DOM.makeElement('span', `Fetching options, please wait...`); App.Art.GenAI.sdClient.getCheckpointList().then(list => { if (list.length === 0) { @@ -1609,7 +1613,7 @@ App.UI.aiArtOptions = function (options) { } }); options.addOption("Checkpoint Model", "aiCheckpoint").showTextBox() - .addComment(App.UI.DOM.combineNodes(`The checkpoint method used by AI. `, checkpointSpan)) + .addComment(App.UI.DOM.combineNodes(`The checkpoint method used by AI. `, checkpointSpan)); } const samplerListSpan = App.UI.DOM.makeElement('span', `Fetching options, please wait...`); App.Art.GenAI.sdClient.getSamplerList().then(list => { @@ -1624,55 +1628,55 @@ App.UI.aiArtOptions = function (options) { } } }); - options.addOption("Sampling Method", "aiSamplingMethod").showTextBox() - .addComment(App.UI.DOM.combineNodes(`The sampling method used by AI. `, samplerListSpan)); + if (!V.aiPonyExperimental) { + options.addOption("Sampling Method", "aiSamplingMethod").showTextBox() + .addComment(App.UI.DOM.combineNodes(`The sampling method used by AI. `, samplerListSpan)); - const schedulerListSpan = App.UI.DOM.makeElement('span', `Fetching options, please wait...`); - App.Art.GenAI.sdClient.getSchedulerList().then(list => { - if (list.length === 0) { - schedulerListSpan.textContent = `Could not fetch valid schedulers. Check your configuration.`; - schedulerListSpan.classList.add('error'); - } else { - schedulerListSpan.textContent = `Valid options on your Stable Diffusion installation: ${toSentence(list)}.`; - if (!list.includes(V.aiSchedulingMethod)) { + const schedulerListSpan = App.UI.DOM.makeElement('span', `Fetching options, please wait...`); + App.Art.GenAI.sdClient.getSchedulerList().then(list => { + if (list.length === 0) { + schedulerListSpan.textContent = `Could not fetch valid schedulers. Check your configuration.`; schedulerListSpan.classList.add('error'); - schedulerListSpan.textContent = "ERROR: " + schedulerListSpan.textContent; + } else { + schedulerListSpan.textContent = `Valid options on your Stable Diffusion installation: ${toSentence(list)}.`; + if (!list.includes(V.aiSchedulingMethod)) { + schedulerListSpan.classList.add('error'); + schedulerListSpan.textContent = "ERROR: " + schedulerListSpan.textContent; + } } - } - }); - options.addOption("Scheduling Method", "aiSchedulingMethod").showTextBox() - .addComment(App.UI.DOM.combineNodes(`The scheduling method used by AI. `, schedulerListSpan)); - + }); + options.addOption("Scheduling Method", "aiSchedulingMethod").showTextBox() + .addComment(App.UI.DOM.combineNodes(`The scheduling method used by AI. `, schedulerListSpan)); + } + // temporarily leaving CFG scale exposed: haven't used S-CFG with the Turbo model enough to know how it affects CFG if (V.aiCfgScale < 1) { V.aiCfgScale = 1; } options.addOption("CFG Scale", "aiCfgScale").showTextBox() - .addComment("The higher this number, the more the prompt influences the image. Generally between 5 to 12."); - if (V.aiTimeoutPerStep < 0.01) { - V.aiTimeoutPerStep = 0.01; - } + .addComment("The higher this number, the more the prompt influences the image. Generally between 5 to 12. [for Pony Diffusion, suggest values around 5 to 8]"); + if (!V.aiPonyExperimental) { + if (V.aiTimeoutPerStep < 0.01) { + V.aiTimeoutPerStep = 0.01; + } - options.addOption("Seconds per Step", "aiTimeoutPerStep").showTextBox() - .addComment("The maximum number of Seconds (per Step) your system takes to render an image. This time is from the time the request is sent to the time it is saved divided by the number of Sampling Steps. Please set this at as small a value as reasonable to avoid the game from waiting longer than you are for images to generate."); - if (V.aiSamplingSteps < 2) { - V.aiSamplingSteps = 2; - } - options.addOption("Sampling Steps", "aiSamplingSteps").showTextBox() - .addComment("The number of steps used when generating the image. More steps might reduce artifacts but increases generation time. Generally between 20 to 50, but may be as high as 500 if you don't mind long queues in the background."); - if (V.aiSamplingStepsEvent < 2) { - V.aiSamplingStepsEvent = 2; - } - options.addOption("Event Sampling Steps", "aiSamplingStepsEvent").showTextBox() - .addComment("The number of steps used when generating an image during events. Generally between 20 to 50 to maintain a reasonable speed."); - if (V.aiHeight < 10) { - V.aiHeight = 10; + options.addOption("Seconds per Step", "aiTimeoutPerStep").showTextBox() + .addComment("The maximum number of Seconds (per Step) your system takes to render an image. This time is from the time the request is sent to the time it is saved divided by the number of Sampling Steps. Please set this at as small a value as reasonable to avoid the game from waiting longer than you are for images to generate."); + if (V.aiSamplingSteps < 2) { + V.aiSamplingSteps = 2; + } + options.addOption("Sampling Steps", "aiSamplingSteps").showTextBox() + .addComment("The number of steps used when generating the image. More steps might reduce artifacts but increases generation time. Generally between 20 to 50, but may be as high as 500 if you don't mind long queues in the background."); + if (V.aiSamplingStepsEvent < 2) { + V.aiSamplingStepsEvent = 2; + } + options.addOption("Event Sampling Steps", "aiSamplingStepsEvent").showTextBox() + .addComment("The number of steps used when generating an image during events. Generally between 20 to 50 to maintain a reasonable speed."); } + V.aiHeight = Math.max(V.aiHeight, 10); options.addOption("Height", "aiHeight").showTextBox() .addComment("The height of the image."); - if (V.aiWidth < 10) { - V.aiWidth = 10; - } + V.aiWidth = Math.max(V.aiWidth, 10); options.addOption("Width", "aiWidth").showTextBox() .addComment("The width of the image."); @@ -1707,7 +1711,7 @@ App.UI.aiArtOptions = function (options) { options.addOption("ADetailer restore face", "aiAdetailerFace") .addValue("Enabled", true).on().addValue("Disabled", false).off() .addComment(App.UI.DOM.combineNodes("Use AI to recognize and re-render faces with better detail. Much better than Restore Faces, but requires more technical setup. ", adCheckSpan)); - } else { + } else if (V.aiUserInterface === 1 && !V.aiPonyExperimental) { const adCheckSpan = App.UI.DOM.makeElement('span', `Validating Impact-Pack setup...`); App.Art.GenAI.sdClient.hasImpactPack().then(result => { if (result) { @@ -1722,28 +1726,30 @@ App.UI.aiArtOptions = function (options) { .addComment(App.UI.DOM.combineNodes("Use AI to recognize and re-render faces with better detail. Much better than without, but requires Impact-Pack. ", adCheckSpan)); } - options.addOption("Upscaling/highres fix", "aiUpscale") - .addValue("Enabled", true).on().addValue("Disabled", false).off() - .addComment("Use AI upscaling to produce higher-resolution images. Significantly increases both time to generate and image quality."); - if (V.aiUpscale) { - options.addOption("Upscaling size", "aiUpscaleScale").showTextBox() - .addComment("Scales the dimensions of the image by this factor. Defaults to 1.75."); + if (!V.aiPonyExperimental) { + options.addOption("Upscaling/highres fix", "aiUpscale") + .addValue("Enabled", true).on().addValue("Disabled", false).off() + .addComment("Use AI upscaling to produce higher-resolution images. Significantly increases both time to generate and image quality."); + if (V.aiUpscale) { + options.addOption("Upscaling size", "aiUpscaleScale").showTextBox() + .addComment("Scales the dimensions of the image by this factor. Defaults to 1.75."); - const upscalerListSpan = App.UI.DOM.makeElement('span', `Fetching options, please wait...`); - App.Art.GenAI.sdClient.getUpscalerList().then(list => { - if (list.length === 0) { - upscalerListSpan.textContent = `Could not fetch valid upscalers. Check your configuration.`; - upscalerListSpan.classList.add('error'); - } else { - upscalerListSpan.textContent = `Valid options on your Stable Diffusion installation: ${toSentence(list)}.`; - if (!list.includes(V.aiUpscaler)) { + const upscalerListSpan = App.UI.DOM.makeElement('span', `Fetching options, please wait...`); + App.Art.GenAI.sdClient.getUpscalerList().then(list => { + if (list.length === 0) { + upscalerListSpan.textContent = `Could not fetch valid upscalers. Check your configuration.`; upscalerListSpan.classList.add('error'); - upscalerListSpan.textContent = "ERROR: " + upscalerListSpan.textContent; + } else { + upscalerListSpan.textContent = `Valid options on your Stable Diffusion installation: ${toSentence(list)}.`; + if (!list.includes(V.aiUpscaler)) { + upscalerListSpan.classList.add('error'); + upscalerListSpan.textContent = "ERROR: " + upscalerListSpan.textContent; + } } - } - }); - options.addOption("Upscaling method", "aiUpscaler").showTextBox() - .addComment(App.UI.DOM.combineNodes(`The method used for upscaling the image. `, upscalerListSpan)); + }); + options.addOption("Upscaling method", "aiUpscaler").showTextBox() + .addComment(App.UI.DOM.combineNodes(`The method used for upscaling the image. `, upscalerListSpan)); + } } if (V.aiUserInterface === 0) { @@ -1791,21 +1797,34 @@ App.UI.aiArtOptions = function (options) { // } // }) // } - options.addOption("CFG Scale Fix", "aiDynamicCfgEnabled") - .addValue("Enabled", true).on().addValue("Disabled", false).off().addComment(cfgCheckSpan); - - if (V.aiDynamicCfgEnabled) { - options.addOption("CFG Scale Fix: Mimicked Number", "aiDynamicCfgMimic").showTextBox() - .addComment("If CFG Scale Fix is on, then set this number to a CFG scale to mimic a normal CFG (5 to 12), and then set your actual CFG to something high (20, 30, etc.)"); - if (V.aiDynamicCfgMimic < 0) { - V.aiDynamicCfgMimic = 0; - } - options.addOption("CFG Scale Fix: Minimum Scale", "aiDynamicCfgMinimum").showTextBox() - .addComment("CFG Scheduler minimums. Set to around 3 or 4 for best results."); - if (V.aiDynamicCfgMinimum < 0) { - V.aiDynamicCfgMinimum = 0; + if (!V.aiPonyExperimental) { + options.addOption("CFG Scale Fix", "aiDynamicCfgEnabled") + .addValue("Enabled", true).on().addValue("Disabled", false).off().addComment(cfgCheckSpan); + + if (V.aiDynamicCfgEnabled) { + options.addOption("CFG Scale Fix: Mimicked Number", "aiDynamicCfgMimic").showTextBox() + .addComment("If CFG Scale Fix is on, then set this number to a CFG scale to mimic a normal CFG (5 to 12), and then set your actual CFG to something high (20, 30, etc.)"); + if (V.aiDynamicCfgMimic < 0) { + V.aiDynamicCfgMimic = 0; + } + options.addOption("CFG Scale Fix: Minimum Scale", "aiDynamicCfgMinimum").showTextBox() + .addComment("CFG Scheduler minimums. Set to around 3 or 4 for best results."); + if (V.aiDynamicCfgMinimum < 0) { + V.aiDynamicCfgMinimum = 0; + } } } + + if (V.aiPonyExperimental) { + options.addOption("Pony Diffusion Turbo Model", "aiPonyTurbo").addValue("Enabled", true).on().addValue("Disabled", false).off() + .addComment("Use preset for the faster (but noticeably lower fidelity) Pony Turbo model"); + options.addOption("Custom LoRA list", "aiPonyLoraStack").showTextBox({large: true, forceString: true}) + .addComment(`Enter a valid JSON with the name of each LoRA and its weight, e.g. { "Style LoRA Example 1.safetensors": 1.0, "Style LoRA Example 2.safetensors": 0.5 }`); + options.addOption("High Res Pass", "aiPonyHiResPass").addValue("Enabled", true).on().addValue("Disabled", false).off() + .addComment("Applies latent upscaling and then an img2img high res pass -- similar to A111's hiRes fix option"); + options.addOption("Face Detailer", "aiPonyFaceDetailer").addValue("Enabled", true).on().addValue("Disabled", false).off() + .addComment("Face detailer for Pony workflow"); + } } const renderQueueOption = async (clicked = false) => { @@ -1863,7 +1882,7 @@ App.UI.aiArtOptions = function (options) { } }; -App.UI.artOptions = function () { +App.UI.artOptions = function() { const el = new DocumentFragment(); let options = new App.UI.OptionsGroup(); diff --git a/src/gui/storyCaption.js b/src/gui/storyCaption.js index adb5cade4759c573d0d19e6b302b469ddfaa02f7..8e3ced7a6d1adccf6228144899195a7cb7040052 100644 --- a/src/gui/storyCaption.js +++ b/src/gui/storyCaption.js @@ -86,10 +86,16 @@ App.UI.StoryCaption.render = function() { } } - const reminders = App.UI.DOM.appendNewElement("div", fragment, App.UI.DOM.link("Reminders", () => App.Reminders.dialog())); + const reminders = App.UI.DOM.makeElement("span", App.UI.DOM.link("Reminders", () => App.Reminders.dialog()), null); if (V.reminders.find(r => r.week <= V.week)) { reminders.append(" ", App.UI.DOM.makeElement("span", String.fromCharCode(0xe80c), ["icons", "noteworthy"])); } + fragment.append( + App.UI.DOM.generateLinksStrip([ + reminders, + App.UI.DOM.link("Log", App.personalLog.dialog) + ]) + ); } else if (pass === "Starting Girls") { fragment.append(startingGirls()); } diff --git a/src/interaction/siCustom.js b/src/interaction/siCustom.js index 146f11b68d3fd09bef2f2a07637b33d0338d973b..19c7ac691780b79e68be5f68c55c890e2fe05488 100644 --- a/src/interaction/siCustom.js +++ b/src/interaction/siCustom.js @@ -1036,7 +1036,7 @@ App.UI.SlaveInteract.custom = function(slave, refresh) { el.appendChild(document.createElement('h4')).textContent = `Image generation AI (eg. Stable Diffusion):`; - let prompt = buildPrompt(slave); + let prompt = V.aiPonyExperimental ? buildPromptPony(slave) : buildPrompt(slave); el.appendChild(document.createElement('h5')).textContent = `Positive prompt`; el.appendChild(document.createElement('kbd')).textContent = prompt.positive(); el.appendChild(document.createElement('h5')).textContent = `Negative prompt`; diff --git a/src/js/extendedFamilyModeJS.js b/src/js/extendedFamilyModeJS.js index dae3f54618f4962d9a0f769920377480d05c3238..27cf00cf22eb7ac690d7511e786f29bb15269ba3 100644 --- a/src/js/extendedFamilyModeJS.js +++ b/src/js/extendedFamilyModeJS.js @@ -270,6 +270,21 @@ globalThis.areRelated = function(slave1, slave2) { return (slave1.father === slave2.ID || slave1.mother === slave2.ID || slave2.father === slave1.ID || slave2.mother === slave1.ID || areSisters(slave1, slave2) > 0); }; + +/** Returns whether two entities are *distantly* related. + * @param {Relative} slave1 + * @param {Relative} slave2 + * @returns {boolean} + */ +globalThis.areDistantlyRelated = function(slave1, slave2) { + return ( + areCousins(slave1, slave2) || + isAunt(slave1, slave2) || + isAunt(slave2, slave1) || + isGrandparentP(slave1, slave2) || + isGrandparentP(slave2, slave1)) +} + /** Returns the total number of relatives that a slave has * @param {FC.SlaveState} slave * @returns {number} diff --git a/src/js/personalLog.js b/src/js/personalLog.js new file mode 100644 index 0000000000000000000000000000000000000000..0e338827a54099d753b31fa8755031b806e393eb --- /dev/null +++ b/src/js/personalLog.js @@ -0,0 +1,70 @@ +App.personalLog = class PersonalLog { + /** + * Opens the Personal Log dialog + */ + static dialog() { + if (Dialog.isOpen()) { + Dialog.close(); + } + Dialog.setup("Personal Log", "personal-log-dialog"); + Dialog.append(`<p>You can write your entries using <a href="https://www.markdownguide.org/cheat-sheet/" target="_blank" rel="noopener noreferrer" class="link-external">Markdown</a>.</p>`); + let log = App.UI.DOM.makeElement("textarea", "", "log-entry-editor"); + log.id = 'log-entry-editor'; + log.placeholder = "Add a new log entry"; + Dialog.append(log); + + Dialog.append(App.UI.DOM.makeElement("div", App.UI.DOM.link("Save", () => { + if (V.personalLog === undefined) { + V.personalLog = []; + } + let log = document.getElementById('log-entry'); + if (log.dataset.index && log.value) { + V.personalLog[log.dataset.index].log = log.value; + V.personalLog[log.dataset.index].edited = new Date().toISOString(); + } else if (log.value) { + V.personalLog.push({ + "timestamp": new Date().toISOString(), + "edited": null, + "log": log.value + }); + } + + // The passage reload will force Twine to save state, if the passage is safe to jump to/from. + // In unsafe passages (weekend, events, recruitements, slave passages etc), there will be a risk + // to lose modifications if the browser is closed or the page reloads, even if a manual save is made. + if (Story.lookup("tags", "jump-from-safe").map(passage => passage.title).includes(passage()) && + Story.lookup("tags", "jump-to-safe").map(passage => passage.title).includes(passage())) { + App.UI.reload(); + } + this.dialog(); + }), "log-entry-save")); + + if (V.personalLog) { + let dl = App.UI.DOM.makeElement("dl", null, null); + for (const [i, log] of V.personalLog.entries()) { + let dt = App.UI.DOM.makeElement("dt", null, null); + dt.append(App.UI.DOM.makeElement("span", new Date(Date.parse(log.timestamp)).toLocaleString(), "log-entry-created")); + if (log.edited) { + dt.append(App.UI.DOM.makeElement("small", `Edited ${new Date(Date.parse(log.edited)).toLocaleString()}`, "log-entry-edited")); + } + dt.append(App.UI.DOM.generateLinksStrip([ + App.UI.DOM.link("edit", () => { + document.getElementById('log-entry-editor').value = V.personalLog[i].log; + document.getElementById('log-entry-editor').dataset.timestamp = V.personalLog[i].timestamp; + document.getElementById('log-entry-editor').dataset.index = i.toString(); + }), + App.UI.DOM.link("delete", () => { + V.personalLog.splice(i, 1); + this.dialog(); + }) + ])); + dl.append(dt); + let dd = App.UI.DOM.makeElement("dd", null, null); + dd.innerHTML = marked.parse(log.log); + dl.append(dd); + } + Dialog.append(dl); + } + Dialog.open(); + } +}; diff --git a/src/js/slaveSummaryHelpers.js b/src/js/slaveSummaryHelpers.js index 46b824f37ddf6fe1f4dc8be64708807b3e1bf309..5c2b79dff9c3235dd94b09c8e086fdfc976d3e3c 100644 --- a/src/js/slaveSummaryHelpers.js +++ b/src/js/slaveSummaryHelpers.js @@ -237,6 +237,15 @@ App.UI.SlaveSummaryImpl = function() { const bits = short ? shortFamilyBits : longFamilyBits; const block = makeBlock(); const cssClassName = "lightgreen"; + if (areRelated(V.PC, slave) || areDistantlyRelated(V.PC, slave)) { + addText(block, `Your `); + if (slave.relationship < -1) { + makeSpan(block, bits.makeBit(toSentence([...relativeTerms(V.PC, slave), PCrelationshipTerm(slave)])), cssClassName); + handled = 1; + } else { + makeSpan(block, bits.makeBit(relativeTerm(V.PC, slave)), cssClassName); + } + } if (slave.mother > 0) { const ssj = V.slaves.find(s => s.ID === slave.mother); if (ssj) { @@ -249,14 +258,6 @@ App.UI.SlaveSummaryImpl = function() { } makeSpan(block, bits.makeBit(spanText), cssClassName); } - } else if (slave.mother === -1) { - addText(block, `Your `); - if (slave.relationship < -1) { - makeSpan(block, bits.makeBit(`${getPronouns(slave).daughter}${bits.and}${PCrelationshipTerm(slave)}`), cssClassName); - handled = 1; - } else { - makeSpan(block, bits.makeBit(getPronouns(slave).daughter), cssClassName); - } } else if (slave.mother in V.missingTable && V.showMissingSlavesSD && V.showMissingSlaves) { addText(block, `${V.missingTable[slave.mother].fullName}'s `); makeSpan(block, bits.makeBit(getPronouns(slave).daughter), cssClassName); @@ -273,27 +274,10 @@ App.UI.SlaveSummaryImpl = function() { } makeSpan(block, bits.makeBit(spanText), cssClassName); } - } else if (slave.father === -1 && slave.father !== slave.mother) { - addText(block, `Your `); - if (slave.relationship < -1) { - makeSpan(block, bits.makeBit(`${getPronouns(slave).daughter}${bits.and}${PCrelationshipTerm(slave)}`), cssClassName); - handled = 1; - } else { - makeSpan(block, bits.makeBit(getPronouns(slave).daughter), cssClassName); - } } else if (slave.father in V.missingTable && slave.father !== slave.mother && V.showMissingSlavesSD && V.showMissingSlaves) { addText(block, `${V.missingTable[slave.father].fullName}'s `); makeSpan(block, bits.makeBit(getPronouns(slave).daughter), cssClassName); } - if (areSisters(V.PC, slave) > 0) { - addText(block, `Your `); - if (slave.relationship < -1) { - makeSpan(block, bits.makeBit(`${siblingTerm(V.PC, slave)}${bits.and}${PCrelationshipTerm(slave)}`), cssClassName); - handled = 1; - } else { - makeSpan(block, bits.makeBit(siblingTerm(V.PC, slave)), cssClassName); - } - } if (slave.daughters === 1) { const ssj = V.slaves.find(s => s.mother === slave.ID || s.father === slave.ID); if (ssj) { diff --git a/src/npc/children/childInteract.tw b/src/npc/children/childInteract.tw deleted file mode 100644 index 706bffdf04aba5bc4e74f4e2be4357564cadc6dc..0000000000000000000000000000000000000000 --- a/src/npc/children/childInteract.tw +++ /dev/null @@ -1,1559 +0,0 @@ -:: Child Interact [nobr] - -<<if $cheatMode>> - <center><i>[[Cheat Edit Child Alternative|MOD_Edit Child Cheat New][$cheater = 1]]</i></center> -<</if>> - -<<set _i = App.Facilities.Nursery.childIndexForID($activeChild.ID)>> - -<<set $nextButton = "Confirm changes", $nextLink = "AC Dump", $returnTo = "Nursery", _SL = $slaves.length, _CL = $cribs.length, $cribs[_i] = $activeChild>> - -<<setLocalPronouns $activeChild>> -<<run Enunciate($activeChild)>> -<<set _dairyNameCaps = capFirstChar($dairyName)>> - -/* TODO: the encyclopedia will most likely need to be updated for children as well */ -<<set $encyclopedia = either("Costs Summary", "Disease in the Free Cities", "Drugs and Their Effects", "From Rebellious to Devoted", "Gender", "Modern Anal", "Nymphomania", "Slave Couture")>> -<<if $activeChild.dick > 0>> - <<set $encyclopedia = "Gender">> -<</if>> - -<center> -<span class="hotkey"> - <<print App.UI.Hotkeys.hotkeys("prev-child")>> -</span> -<span id="prev-child"> - <b> - <<link "Prev" "Previous Child In Line">><</link>> - </b> -</span> - <b><u>@@.pink;$activeChild.slaveName@@</u></b> -<span id="next-child"> - <b> - <<link "Next" "Next Child In Line">><</link>> - </b> -</span> -<span class="hotkey"> - <<print App.UI.Hotkeys.hotkeys("next-child")>> -</span> -</center> -<br> - -<<if $seeDetails>> - <span id="LCD"> - <<= App.Facilities.Nursery.LongChildDescription($activeChild)>> - </span> - <br><i> - [[Options][$nextLink = passage()]] | - [[Hide descriptions|Child Interact][$seeDetails = 0]] | - [[Customize|Customize Child]] | - <b> - <<link "Update">> - <<replace "#LCD">> - <<= App.Facilities.Nursery.LongChildDescription($activeChild)>> - <</replace>> - <</link>> - </b></i> -<<else>> - <u>[[Show descriptions|Child Interact][$seeDetails = 1]]</u> -<</if>> -/* -FIXME: -<br><br>__Take slave to another room:__ -[[Wardrobe|Wardrobe Use][$degradation = 0]] -| [[Auto salon|Salon][$degradation = 0, $tattooChoice = "", $piercingLevel = ""]] -| [[Body mod studio|Body Modification][$degradation = 0, $tattooChoice = undefined]] -| [[Remote surgery|Remote Surgery][$degradation = 0]] -| [[Configure cybernetics|Prosthetics Configuration][$AS = $activeChild.ID, $prostheticsConfig = "main"]] -*/ - -/*TODO: These will most likely need to be reworked*/ -<<if $extremeUnderage && $childSex>> - <br> - <span id="mini-scene"><i>Or use $him here:</i></span> - <span id="sexoption"> - <<if ($activeChild.vagina > -1)>> - <<if canDoVaginal($activeChild)>> - <<link "Fuck $him">> - <<replace "#mini-scene">><<include "FVagina">><br> <</replace>> - <</link>> | - <<if canDoAnal($activeChild)>> - <<link "Use $his holes">> - <<replace "#mini-scene">><<include "FButt">><br> <</replace>> - <</link>> | - <</if>> - <<else>> - <i>Remove $his chastity belt if you wish to fuck $him</i> - <</if>> - <</if>> - </span> - <span id="analsexoption"> - <<if canDoAnal($activeChild)>> - <<link "Fuck $his ass">> - <<replace "#mini-scene">><<include "FAnus">><br> <</replace>> - <</link>> | - <<else>> - | <i>Remove $his chastity belt if you wish to fuck $his ass</i> - <</if>> - </span> - <<link "Use $his mouth">> - <<replace "#mini-scene">><<include "FLips">><br> <</replace>> - <</link>> | - <<link "Kiss $him">> - <<replace "#mini-scene">><<include "FKiss">><br> <</replace>> - <</link>> | - <<link "Play with $his tits">> - <<replace "#mini-scene">><<include "FBoobs">><br> <</replace>> - <</link>> | - <<link "Caress $him">> - <<replace "#mini-scene">><<include "FCaress">><br> <</replace>> - <</link>> | - <<link "Give $him a hug">> - <<replace "#mini-scene">><<include "FEmbrace">><br> <</replace>> - <</link>> | - <<if $cheatMode>> - <<link "Pat $his head">> - <<replace "#mini-scene">><<include "FPat">><br> <</replace>> - <</link>> | - <</if>> - <<link "Grope $his boobs">> - <<replace "#mini-scene">><<include "FondleChildBoobs">><br> <</replace>> - <</link>> | - <<if $activeChild.nipples == "fuckable" && $PC.dick>> - <<link "Fuck $his nipples">> - <<replace "#mini-scene">><<include "FNippleFuck">><br> <</replace>> - <</link>> | - <</if>> - <<if $activeChild.lactation && $activeChild.boobs >= 2000 && $activeChild.belly < 60000>> - <<link "Drink $his milk">> - <<replace "#mini-scene">><<include "FSuckle">><br> <</replace>> - <</link>> | - <</if>> - <span id="analgropeoption"> - <<if canDoAnal($activeChild)>> - <<link "Grope $his butt">> - <<replace "#mini-scene">><<include "FondleChildButt">><br> <</replace>> - <</link>> | - <</if>> - </span> - <span id="gropeoption"> - <<if ($activeChild.vagina > -1)>> - <<if canDoVaginal($activeChild)>> - <<link "Grope $his pussy">> - <<replace "#mini-scene">><<include "FondleChildVagina">><br> <</replace>> - <</link>> | - <<link "Eat $him out">> - <<replace "#mini-scene">><<include "FLickPussy">><br> <</replace>> - <</link>> | - <</if>> - <</if>> - </span> - <span id="dickgropeoption"> - <<if $activeChild.dick>> - <<if !$activeChild.chastityPenis>> - <<link "Grope $his dick">> - <<replace "#mini-scene">><<include "FondleChildDick">><br> <</replace>> - <</link>> | - <<if canPenetrate($activeChild) && $policies.sexualOpenness == 1>> - <<link "Ride $his dick">> - <<replace "#mini-scene">><<include "FDick">><br> <</replace>> - <</link>> | - <</if>> - <<else>> - | //Remove $his dick chastity belt if you wish to play with $his cock// - <</if>> - <</if>> - </span> - <<if (hasAnyLegs($activeChild)) && $PC.dick != 0>> - <<link "Get a footjob">><<replace "#mini-scene">><<set $childSex = 1>><<include "FFeet">><br> <</replace>><</link>> | - <</if>> - - /* TODO: will children be able to be inflated? */ - <<if $activeChild.assignment != "work in the dairy" && $activeChild.assignment != "be confined in the arcade" && $activeChild.assignment != "be confined in the cellblock">> - <<if $boughtItem.toys.enema>> - <<if ($activeChild.inflation < 3 && $activeChild.pregKnown == 0 && $activeChild.bellyImplant < 1500) || ($activeChild.inflation < 1)>> - <<if $activeChild.inflationType == "water" || $activeChild.inflationType == "none">> - | <<link "Fill $his ass with water">> - <<set $activeChild.inflationType = "water">> - <<set $activeChild.inflationMethod = 2>> - <<replace "#mini-scene">> - <<include "FillUpButt">><br><</replace>> - <</link>> - <</if>> - <<if $boughtItem.toys.medicalEnema>> - <<if $activeChild.inflationType == "aphrodisiac" || $activeChild.inflationType == "none">> - | <<link "Fill $his ass with aphrodisiacs">> - <<set $activeChild.inflationType = "aphrodisiac">> - <<set $activeChild.inflationMethod = 2>> - <<replace "#mini-scene">> - <<include "FillUpButt">><br><</replace>> - <</link>> - <</if>> - <<if $activeChild.inflationType == "curative" || $activeChild.inflationType == "none">> - | <<link "Fill $his ass with curatives">> - <<set $activeChild.inflationType = "curative">> - <<set $activeChild.inflationMethod = 2>> - <<replace "#mini-scene">> - <<include "FillUpButt">><br><</replace>> - <</link>> - <</if>> - <<if $activeChild.inflationType == "tightener" || $activeChild.inflationType == "none">> - | <<link "Fill $his ass with rectal tighteners">> - <<set $activeChild.inflationType = "tightener">> - <<set $activeChild.inflationMethod = 2>> - <<replace "#mini-scene">> - <<include "FillUpButt">><br><</replace>> - <</link>> - <</if>> - <</if>> - <</if>> - <</if>> - <<if $dairyPiping>> - <<if ($milkPipeline > 88 && $milkPipeline != 0) || $arcologies[0].FSPastoralistLaw>> - <<if ($activeChild.inflation < 3 && $activeChild.pregKnown == 0 && $activeChild.bellyImplant < 1500) || ($activeChild.inflation < 1)>> - <<if $activeChild.inflationType == "milk" || $activeChild.inflationType == "none">> - | <<link "Fill $his ass with milk">> - <<set $activeChild.inflationType = "milk">> - <<set $activeChild.inflationMethod = 2>> - <<replace "#mini-scene">> - <<include "FillUpButt">><br><</replace>> - <</link>> - | <<link "Force-feed $him milk">> - <<set $activeChild.inflationType = "milk">> - <<set $activeChild.inflationMethod = 1>> - <<replace "#mini-scene">> - <<include "FillUpFace">><br><</replace>> - <</link>> - <</if>> - <</if>> - <<else>> - | //_dairyNameCaps is not producing enough milk to pump through the pipes// - <</if>> - <<if ($cumPipeline > 88 && $cumPipeline != 0) || $arcologies[0].FSPastoralistLaw>> - <<if ($activeChild.inflation < 3 && $activeChild.pregKnown == 0 && $activeChild.bellyImplant < 1500) || ($activeChild.inflation < 1)>> - <<if $activeChild.inflationType == "cum" || $activeChild.inflationType == "none">> - | <<link "Fill $his ass with cum">> - <<set $activeChild.inflationType = "cum">> - <<set $activeChild.inflationMethod = 2>> - <<replace "#mini-scene">> - <<include "FillUpButt">><br><</replace>> - <</link>> - | <<link "Force-feed $him cum">> - <<set $activeChild.inflationType = "cum">> - <<set $activeChild.inflationMethod = 1>> - <<replace "#mini-scene">> - <<include "FillUpFace">><br><</replace>> - <</link>> - <</if>> - <</if>> - <<else>> - | //_dairyNameCaps is not producing enough cum to pump through the pipes// - <</if>> - <</if>> - <<if $wcPiping == 1>> - <<if ($activeChild.inflation < 3 && $activeChild.pregKnown == 0 && $activeChild.bellyImplant < 1500) || ($activeChild.inflation < 1)>> - <<if $activeChild.inflationType == "urine" || $activeChild.inflationType == "none">> - | <<link "Fill $his ass with urine">> - <<set $activeChild.inflationType = "urine">> - <<set $activeChild.inflationMethod = 2>> - <<replace "#mini-scene">><<include "FillUpButt">><br><</replace>> - <</link>> - <</if>> - <</if>> - <</if>> - <</if>> - <<if $activeChild.inflation == 0 && $activeChild.bellyImplant < 1500>> - | <<link "Use another slave to force-feed $him" "SlaveOnSlaveFeedingWorkAround">> - <</link>> - <<if $boughtItem.toys.buckets>> - | <<link "Make $him consume two liters of slave food">> - <<set $activeChild.inflationType = "food">> - <<set $activeChild.inflationMethod = 1>> - <<set $activeChild.inflation = 1>> - <<replace "#mini-scene">><<include "ForceFeeding">><br><</replace>> - <</link>> - <<if $activeChild.pregKnown == 0>> - | <<link "Force $him to consume a gallon of slave food">> - <<set $activeChild.inflationType = "food">> - <<set $activeChild.inflationMethod = 1>> - <<set $activeChild.inflation = 2>> - <<replace "#mini-scene">><<include "ForceFeeding">><br><</replace>> - <</link>> - | <<link "Force two gallons of slave food into $him">> - <<set $activeChild.inflationType = "food">> - <<set $activeChild.inflationMethod = 1>> - <<set $activeChild.inflation = 3>> - <<replace "#mini-scene">><<include "ForceFeeding">><br><</replace>> - <</link>> - <</if>> - <</if>> - <</if>> - <<if canDoVaginal($activeChild)>> - <<link "Have another slave fuck $his pussy" "FSlaveSlaveVag">> - <</link>> | - <</if>> - <<if canPenetrate($activeChild)>> - <<link "Have another slave ride $his cock" "FSlaveSlaveDick">> - <</link>> | - <<elseif $activeChild.clit >= 4>> - <<link "Have another slave ride $his clit-dick" "FSlaveSlaveDick">> - <</link>> | - <</if>> - <<if $seeBestiality>> - <<if $farmyardKennels > 0 && $active.canine>> - <<link "Have a $active.canine.species mount $him">> - <<set $animalType = "canine">> - <<replace "#mini-scene">><<include "BeastFucked">><br> <</replace>> - <</link>> | - <</if>> - <<if $farmyardStables > 0 && $active.hooved.species>> - <<link "Let a $active.hooved.species mount $him">> - <<set $animalType = "hooved">> - <<replace "#mini-scene">><<include "BeastFucked">><br> <</replace>> - <</link>> | - <</if>> - <<if $farmyardCages > 0 && $active.feline>> - <<link "Have a $active.feline.species mount $him">> - <<set $animalType = "feline">> - <<replace "#mini-scene">><<include "BeastFucked">><br> <</replace>> - <</link>> | - <</if>> - <</if>> - <<if $cheatMode>> - <<link "Check $his deadliness">> - <<replace "#mini-scene">><<include "Deadliness">><br> <</replace>> - <</link>> | - <</if>> - <<link "Abuse $him">> - <<replace "#mini-scene">><<include "FAbuse">><</replace>> - <</link>> - <<if $seeIncest == 1>> - <<set _availRelatives = availableRelatives($activeChild)>> - <<if _availRelatives.mother>> - | <<link "Fuck $him with $his mother">> - <<replace "#mini-scene">> - <<set $partner = "mother">> - <<include "FRelation">><br> <</replace>> - <</link>> - <<elseif _availRelatives.motherName !== null>> - //$His mother, _availRelatives.motherName, is unavailable// - <</if>> - /* - <<if _availRelatives.father>> - | <<link "Fuck $him with $his father">> - <<replace "#mini-scene">><<set $partner = "father">> - <<include "FRelation">><br> <</replace>> - <</link>> - <<elseif _availRelatives.fatherName !== null>> - //$His father, _availRelatives.fatherName, is unavailable// - <</if>> - */ - <<if $activeChild.sisters > 0>> - <<if _availRelatives.sisters == 0>> - <<if $activeChild.sisters == 1>> - //$His _sister2 is unavailable// - <<else>> - //$His sisters are unavailable// - <</if>> - <<else>> - <<if $activeChild.sisters == 1>> - | <<link "Fuck $him with $his _sister2">> - <<replace "#mini-scene">> - <<set $partner = "sister">> - <<include "FRelation">><br> <</replace>> - <</link>> - <<else>> - | <<link "Fuck $him with one of $his sisters">> - <<replace "#mini-scene">> - <<set $partner = "sister">> - <<include "FRelation">><br> <</replace>> - <</link>> - <</if>> - <</if>> - <</if>> - <</if>> - <<if ($activeChild.relationship > 0)>> - <<set _si = $slaveIndices[$activeChild.relationshipTarget]>> - <<if isSlaveAvailable($slaves[_si])>> - <<setLocalPronouns $slaves[_si] 2>> - <<if ($activeChild.relationship)>> - | <<link `"Fuck $him with $his friend <<= SlaveFullName($slaves[_si])>>"`>> - <<replace "#mini-scene">> - <<set $partner = "relationship">> - <<include "FRelation">><br> <</replace>> - <</link>> - <<elseif ($activeChild.relationship == 2)>> - | <<link `"Fuck $him with $his best friend <<= SlaveFullName($slaves[_si])>>"`>> - <<replace "#mini-scene">> - <<set $partner = "relationship">> - <<include "FRelation">><br> <</replace>> - <</link>> - <<elseif ($activeChild.relationship == 3)>> - | <<link `"Fuck $him with $his FWB <<= SlaveFullName($slaves[_si])>>"`>> - <<replace "#mini-scene">> - <<set $partner = "relationship">> - <<include "FRelation">><br> <</replace>> - <</link>> - <<elseif ($activeChild.relationship == 4)>> - | <<link `"Fuck $him with $his lover <<= SlaveFullName($slaves[_si])>>"`>> - <<replace "#mini-scene">> - <<set $partner = "relationship">> - <<include "FRelation">><br> <</replace>> - <</link>> - <<else>> - | <<link `"Fuck $him with $his slave _wife2 <<= SlaveFullName($slaves[_si])>>"`>> - <<replace "#mini-scene">> - <<set $partner = "relationship">> - <<include "FRelation">><br> <</replace>> - <</link>> - <</if>> - <</if>> - <</if>> - <<if ($activeChild.rivalryTarget != 0) && canWalk($activeChild)>> - | <<link "Abuse $his rival with $him">> - <<replace "#mini-scene">><<include "FRival">><br> <</replace>> - <</link>> - <</if>> - <<if ($activeChild.fetish != Fetish.MINDBROKEN) && (canTalk($activeChild) || hasAnyArms($activeChild))>> - | <<link "Ask $him about $his feelings">><<replace "#mini-scene">><<set $childSex = 1>><<include "FFeelings">><br> <</replace>><</link>> - <</if>> - <<if $activeChild.devotion >= 100 && $activeChild.relationship < 0 && $activeChild.relationship > -3>> - | <<link "Talk to $him about relationships" "Matchmaking">> - <<set $AS = $activeChild.ID>> - <</link>> - <</if>> -<</if>> - -<br><br> -<span id="family"> - <span id="family-tree-link"> - <<link "Pull up the file on $his family tree.">> - <<replace #family-tree-link>> - /* TODO: this may need to be updated */<<includeDOM renderFamilyTree($slaves, $activeChild.ID)>> - <</replace>> - <</link>> - </span> -</span> - -<br><br> -/* TODO: the RA may need to be reworked to work with children in the Nursery */ -<<if $activeChild.useRulesAssistant == 0>> - @@.gray;''Not subject'' to the Rules Assistant.@@ - <<link "Include $him" "Child Interact">> - <<set $activeChild.useRulesAssistant = 1>> - <</link>> - <br> -<<else>> - __Rules Assistant:__ [[Rules Assistant Options|Rules Assistant]] - - <<if (def $activeChild.currentRules) && ($activeChild.currentRules.length > 0)>> - <ul> - <<= $defaultRules.filter(x => ruleApplied($activeChild, x)).map(x => `<li>Rule "${x.name}" applied</li>`).join(" ") >> - </ul> - <</if>> | - <<link "//Apply rules//">> - <<silently>> - <<run DefaultRules($activeChild)>> - <</silently>> - <<goto "Child Interact">> - <</link>> | - <<link "Exempt $him" "Child Interact">> - <<set $activeChild.useRulesAssistant = 0>> - <</link>> -<</if>> - -/* TODO: move all wardrobe options to a wardrobe passage */ - - - <br><br>__Appearance:__ - <br>Clothes: <b><span id="clothes">$activeChild.clothes.</span></b> - <<link "Let $him choose">> - <<set $activeChild.clothes = "choosing her own clothes", $activeChild.choosesOwnClothes = 1>> - <<replace "#clothes">>$activeChild.clothes<</replace>> - <</link>> - - <br> - <i>Nice:</i> - <<link "Ballgown">> - <<set $activeChild.clothes = "a ball gown", $activeChild.choosesOwnClothes = 0>> - <<replace "#clothes">>$activeChild.clothes<</replace>> - <</link>> | - <<link "Mini dress">> - <<set $activeChild.clothes = "a mini dress", $activeChild.choosesOwnClothes = 0>> - <<replace "#clothes">>$activeChild.clothes<</replace>> - <</link>> | - <<link "Oversized t-shirt">> - <<set $activeChild.clothes = "an oversized t-shirt", $activeChild.choosesOwnClothes = 0>> - <<replace "#clothes">>$activeChild.clothes<</replace>> - <</link>> | - <<link "Schoolgirl">> - <<set $activeChild.clothes = "a schoolgirl outfit", $activeChild.choosesOwnClothes = 0>> - <<replace "#clothes">>$activeChild.clothes<</replace>> - <</link>> | - <<link "Slave gown">> - <<set $activeChild.clothes = "a slave gown", $activeChild.choosesOwnClothes = 0>> - <<replace "#clothes">>$activeChild.clothes<</replace>> - <</link>> | - <<link "Sweater">> - <<set $activeChild.clothes = "a sweater", $activeChild.choosesOwnClothes = 0>> - <<replace "#clothes">>$activeChild.clothes<</replace>> - <</link>> | - <<link "T-shirt">> - <<set $activeChild.clothes = "a t-shirt", $activeChild.choosesOwnClothes = 0>> - <<replace "#clothes">>$activeChild.clothes<</replace>> - <</link>> - - <br> - <i>Harsh:</i> - <<link "Go naked">> - <<set $activeChild.clothes = "no clothing", $activeChild.choosesOwnClothes = 0>> - <<replace "#clothes">>$activeChild.clothes<</replace>> - <</link>> - - <br>Collar: <b><span id="collar">$activeChild.collar.</span></b> - <<link "None">> - <<set $activeChild.collar = "none">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> - <br> //Nice:// - <<link "Stylish leather">> - <<set $activeChild.collar = "stylish leather">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> | - <<link "Satin choker">> - <<set $activeChild.collar = "satin choker">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> | - <<link "Silken ribbon">> - <<set $activeChild.collar = "silk ribbon">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> | - <<link "Heavy gold">> - <<set $activeChild.collar = "heavy gold">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> | - <<link "Pretty jewelry">> - <<set $activeChild.collar = "pretty jewelry">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> | - <<if $seeAge>> - <<link "Nice retirement counter">> - <<set $activeChild.collar = "nice retirement counter">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> | - <</if>> - <<link "Bell">> - <<set $activeChild.collar = "bell collar">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> | - <<link "Cowbell">> - <<set $activeChild.collar = "leather with cowbell">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> - <<if isItemAccessible.entry("bowtie", "collar")>> - | //FS// <<link "Bowtie collar">> - <<set $activeChild.collar = "bowtie">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> - <</if>> - <<if isItemAccessible.entry("ancient Egyptian", "collar")>> - | //FS// <<link "Ancient Egyptian">> - <<set $activeChild.collar = "ancient Egyptian">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> - <</if>> - <br> - <i>Harsh:</i> - <<link "Tight steel">> - <<set $activeChild.collar = "tight steel">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> | - <<if $seeAge>> - <<link "Cruel retirement counter">> - <<set $activeChild.collar = "cruel retirement counter">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> | - <</if>> - <<link "Uncomfortable leather">> - <<set $activeChild.collar = "uncomfortable leather">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> | - <<if $activeChild.preg > -1 && $seePreg != 0>> - <<link "Pregnancy biometrics">> - <<set $activeChild.collar = "preg biometrics">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> | - <</if>> - <<link "Shock punishment">> - <<set $activeChild.collar = "shock punishment">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> | - <<link "Dildo gag">> - <<set $activeChild.collar = "dildo gag">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> | - <<if isItemAccessible.entry("massive dildo gag", "collar") && $activeChild.skill.oral > 50>> - <<link "Massive dildo gag">> - <<set $activeChild.collar = "massive dildo gag">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> | - <</if>> - <<link "Ball gag">> - <<set $activeChild.collar = "ball gag">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> | - <<link "Bit gag">> - <<set $activeChild.collar = "bit gag">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> | - <<link "Neck corset">> - <<set $activeChild.collar = "neck corset">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> | - <<link "Porcelain mask">> - <<set $activeChild.collar = "porcelain mask">> - <<replace "#collar">>$activeChild.collar<</replace>> - <</link>> - - <<if hasAnyArms($activeChild)>> - <br>Arm accessory: <b><span id="armAccessory">$activeChild.armAccessory.</span></b> - <<link "None">> - <<set $activeChild.armAccessory = "none">> - <<replace "#armAccessory">>$activeChild.armAccessory<</replace>> - <</link>> | - <<link "Hand Gloves">> - <<set $activeChild.armAccessory = "hand gloves">> - <<replace "#armAccessory">>$activeChild.armAccessory<</replace>> - <</link>> | - <<link "Elbow Gloves">> - <<set $activeChild.armAccessory = "elbow gloves">> - <<replace "#armAccessory">>$activeChild.armAccessory<</replace>> - <</link>> - <</if>> - - <<if hasAnyLegs($activeChild)>> - <br>Shoes: <b><span id="shoes">$activeChild.shoes.</span></b> - <<link "Go barefoot">> - <<set $activeChild.shoes = "none">> - <<replace "#shoes">>$activeChild.shoes<</replace>> - <</link>> | - <<link "Flats">> - <<set $activeChild.shoes = "flats">> - <<replace "#shoes">>$activeChild.shoes<</replace>> - <</link>> | - <<link "Heels">> - <<set $activeChild.shoes = "heels">> - <<replace "#shoes">>$activeChild.shoes<</replace>> - <</link>> | - <<link "Pumps">> - <<set $activeChild.shoes = "pumps">> - <<replace "#shoes">>$activeChild.shoes<</replace>> - <</link>> | - <<link "Thigh boots">> - <<set $activeChild.shoes = "boots">> - <<replace "#shoes">>$activeChild.shoes<</replace>> - <</link>> | - <<link "Painfully extreme heels">> - <<set $activeChild.shoes = "extreme heels">> - <<replace "#shoes">>$activeChild.shoes<</replace>> - <</link>> - - <br>Leg accessory: <b><span id="legAccessory">$activeChild.legAccessory.</span></b> - <<link "None">> - <<set $activeChild.legAccessory = "none">> - <<replace "#legAccessory">>$activeChild.legAccessory<</replace>> - <</link>> | - <<link "Short Stockings">> - <<set $activeChild.legAccessory = "short stockings">> - <<replace "#legAccessory">>$activeChild.legAccessory<</replace>> - <</link>> | - <<link "Long Stockings">> - <<set $activeChild.legAccessory = "long stockings">> - <<replace "#legAccessory">>$activeChild.legAccessory<</replace>> - <</link>> - <</if>> - - <br>Torso accessory: <b><span id="bellyAccessory">$activeChild.bellyAccessory.</span></b> - <<link "None">> - <<set $activeChild.bellyAccessory = "none">> - <<replace "#bellyAccessory">>$activeChild.bellyAccessory<</replace>> - <</link>> | - <<link "Tight corset">> - <<set $activeChild.bellyAccessory = "a corset">> - <<replace "#bellyAccessory">>$activeChild.bellyAccessory<</replace>> - <</link>> - <<if ($activeChild.breedingMark != 1 || !$propOutcome || $eugenicsFullControl)>> | - | <<link "Extreme corset">> - <<set $activeChild.bellyAccessory = "an extreme corset">> - <<replace "#bellyAccessory">>$activeChild.bellyAccessory<</replace>> - <</link>> - <</if>> - <<if ($activeChild.belly > 10000)>> - | <<link "Supportive band">> - <<set $activeChild.bellyAccessory = "a support band">> - <<replace "#bellyAccessory">>$activeChild.bellyAccessory<</replace>> - <</link>> - <</if>> - <<if isItemAccessible.entry("a small empathy belly", "bellyAccessory")>> /* works for all of them */ - <<if $activeChild.belly < 1500 && $activeChild.weight < 130>> | - | <<link "1st Trimester belly">> - <<set $activeChild.bellyAccessory = "a small empathy belly">> - <<replace "#bellyAccessory">>$activeChild.bellyAccessory<</replace>> - <</link>> - | <<link "2nd Trimester belly">> - <<set $activeChild.bellyAccessory = "a medium empathy belly">> - <<replace "#bellyAccessory">>$activeChild.bellyAccessory<</replace>> - <</link>> - | <<link "3rd Trimester belly">> - <<set $activeChild.bellyAccessory = "a large empathy belly">> - <<replace "#bellyAccessory">>$activeChild.bellyAccessory<</replace>> - <</link>> - | <<link "3rd Trimester twins belly">> - <<set $activeChild.bellyAccessory = "a huge empathy belly">> - <<replace "#bellyAccessory">>$activeChild.bellyAccessory<</replace>> - <</link>> - <<else>> - | $His stomach is too big to fit an empathy belly around. - <</if>> - <</if>> - - <br>Anal accessory: <b><span id="buttplug">$activeChild.buttplug.</span></b> - <<link "None">> - <<set $activeChild.buttplug = "none", $activeChild.buttplugAttachment = "none">> - <<replace "#buttplug">>$activeChild.buttplug<</replace>> - <</link>> | - <<link "Normal">> - <<set $activeChild.buttplug = "plug">> - <<replace "#buttplug">>$activeChild.buttplug<</replace>> - <</link>> - <<if isItemAccessible.entry("long plug", "buttPlug") && ($activeChild.breedingMark != 1 || !$propOutcome || $eugenicsFullControl)>> - <<link "Long">> - <<set $activeChild.buttplug = "long plug">> - <<replace "#buttplug">>$activeChild.buttplug<</replace>> - <</link>> - <</if>> - <<link "Large">> - <<set $activeChild.buttplug = "large plug">> - <<replace "#buttplug">>$activeChild.buttplug<</replace>> - <</link>> - <<if isItemAccessible.entry("long, large plug", "buttPlug") && ($activeChild.breedingMark != 1 || !$propOutcome || $eugenicsFullControl)>> - <<link "Long and large">> - <<set $activeChild.buttplug = "long, large plug">> - <<replace "#buttplug">>$activeChild.buttplug<</replace>> - <</link>> | - <</if>> - <<if $activeChild.anus >= 2>> - <<link "Huge">> - <<set $activeChild.buttplug = "huge plug">> - <<replace "#buttplug">>$activeChild.buttplug<</replace>> - <</link>> - <</if>> - <<if isItemAccessible.entry("long, huge plug", "buttPlug")>> - <<if ($activeChild.breedingMark != 1 || !$propOutcome || $eugenicsFullControl) && $activeChild.anus >= 2>> - <<link "Long and huge">> - <<set $activeChild.buttplug = "long, huge plug">> - <<replace "#buttplug">>$activeChild.buttplug<</replace>> - <</link>> | - <</if>> - <</if>> - <<if isItemAccessible.entry("tail", "buttplugAttachment") && $activeChild.buttplug != "none">> - Anal accessory attachment: <b><span id="buttplugAttach">$activeChild.buttplugAttachment.</span></b> - <<link "None">> - <<set $activeChild.buttplugAttachment = "none">> - <<replace "#buttplugAttach">>$activeChild.buttplugAttachment<</replace>> - <</link>> - <<if $boughtItem.clothing.buttPlugTails>> - <<link "Tail">> - <<set $activeChild.buttplugAttachment = "tail">> - <<replace "#buttplugAttach">>$activeChild.buttplugAttachment<</replace>> - <</link>> | - <<link "Cat tail">> - <<set $activeChild.buttplugAttachment = "cat tail">> - <<replace "#buttplugAttach">>$activeChild.buttplugAttachment<</replace>> - <</link>> | - <<link "Fox tail">> - <<set $activeChild.buttplugAttachment = "fox tail">> - <<replace "#buttplugAttach">>$activeChild.buttplugAttachment<</replace>> - <</link>> - <</if>> - <</if>> - <<if $activeChild.vagina > -1>> - <br>Vaginal accessory: <b><span id="vaginalAccessory">$activeChild.vaginalAccessory.</span></b> - <<link "None">> - <<set $activeChild.vaginalAccessory = "none">> - <<replace "#vaginalAccessory">>$activeChild.vaginalAccessory<</replace>> - /* TODO: replace these */ - <</link>> | - <<link "Dildo">> - <<set $activeChild.vaginalAccessory = "dildo">> - <<replace "#vaginalAccessory">>$activeChild.vaginalAccessory<</replace>> - <</link>> | - <<if isItemAccessible.entry("long dildo", "vaginalAccessory") && ($activeChild.breedingMark != 1 || !$propOutcome || $eugenicsFullControl)>> - <<link "Long dildo">> - <<set $activeChild.vaginalAccessory = "long dildo">> - <<replace "#vaginalAccessory">>$activeChild.vaginalAccessory<</replace>> - <</link>> | - <</if>> - <<link "Large dildo">> - <<set $activeChild.vaginalAccessory = "large dildo">> - <<replace "#vaginalAccessory">>$activeChild.vaginalAccessory<</replace>> - <</link>> - <<if isItemAccessible.entry("long, large dildo", "vaginalAccessory") && ($activeChild.breedingMark != 1 || !$propOutcome || $eugenicsFullControl)>> - | <<link "Large and long dildo">> - <<set $activeChild.vaginalAccessory = "long, large dildo">> - <<replace "#vaginalAccessory">>$activeChild.vaginalAccessory<</replace>> - <</link>> - <</if>> - <<if $activeChild.vagina >= 2>> - | <<link "Huge dildo">> - <<set $activeChild.vaginalAccessory = "huge dildo">> - <<replace "#vaginalAccessory">>$activeChild.vaginalAccessory<</replace>> - <</link>> - <</if>> - <<if isItemAccessible.entry("long, huge dildo", "vaginalAccessory") && ($activeChild.breedingMark != 1 || !$propOutcome || $eugenicsFullControl)>> - | <<if $activeChild.vagina >= 2>> - <<link "Huge and long dildo">> - <<set $activeChild.vaginalAccessory = "long, huge dildo">> - <<replace "#vaginalAccessory">>$activeChild.vaginalAccessory<</replace>> - <</link>> - <</if>> - <</if>> - <</if>> - <<if $activeChild.dick > 0>> - <br>Dick accessory: <b><span id="dickAccessory">$activeChild.dickAccessory.</span></b> - <<link "None">> - <<set $activeChild.dickAccessory = "none">> - <<replace "#dickAccessory">>$activeChild.dickAccessory<</replace>> - <</link>> - <<if isItemAccessible.entry("bullet vibrator", "dickAccessory") && $boughtItem.clothing.vaginalAttachments>> - | <<link "Bullet vibrator">> - <<set $activeChild.dickAccessory = "bullet vibrator">> - <<replace "#dickAccessory">>$activeChild.dickAccessory<</replace>> - <</link>> - <</if>> - <<if isItemAccessible.entry("smart bullet vibrator", "dickAccessory") && $boughtItem.clothing.vaginalAttachments>> - | <<link "Smart bullet vibrator">> - <<set $activeChild.dickAccessory = "smart bullet vibrator">> - <<replace "#dickAccessory">>$activeChild.dickAccessory<</replace>> - <</link>> - <</if>> - <</if>> - - /* TODO: redo all this */ - /* TODO: will children have chastity options? - <br>Chastity device: <b><span id="chastity"><<if $activeChild.chastityAnus && $activeChild.chastityPenis && $activeChild.chastityVagina>>Full Chastity<<elseif $activeChild.chastityPenis && $activeChild.chastityVagina>>Genital Chastity<<elseif $activeChild.chastityAnus && $activeChild.chastityPenis>>Combined Chastity Cage<<elseif $activeChild.chastityAnus && $activeChild.chastityVagina>>Combined Chastity Belt<<elseif $activeChild.chastityVagina>>Chastity Belt<<elseif $activeChild.chastityPenis>>Chastity Cage<<elseif $activeChild.chastityAnus>>Anal Chastity<<elseif $activeChild.chastityAnus == 0 && $activeChild.chastityPenis == 0 && $activeChild.chastityVagina == 0>>None<<else>>THERE HAS BEEN AN ERROR.</span></b><</if>> - - <<link "None">> - <<set $activeChild.chastityAnus = 0>> - <<set $activeChild.chastityPenis = 0>> - <<set $activeChild.chastityVagina = 0>> - <<replace "#chastity">>None<</replace>> - <</link>> | - <<link "Anal Chastity">> - <<set $activeChild.choosesOwnChastity = 0>> - <<set $activeChild.chastityAnus = 1>> - <<set $activeChild.chastityPenis = 0>> - <<set $activeChild.chastityVagina = 0>> - <<replace "#chastity">>Anal Chastity<</replace>> - <</link>> | - <<if $activeChild.vagina > -1>> - <<link "Chastity Belt">> - <<set $activeChild.choosesOwnChastity = 0>> - <<set $activeChild.chastityAnus = 0>> - <<set $activeChild.chastityPenis = 0>> - <<set $activeChild.chastityVagina = 1>> - <<replace "#chastity">>Vaginal Chastity<</replace>> - <</link>> | - <<link "Combined Chastity Belt">> - <<set $activeChild.choosesOwnChastity = 0>> - <<set $activeChild.chastityAnus = 0>> - <<set $activeChild.chastityPenis = 0>> - <<set $activeChild.chastityVagina = 0>> - <<replace "#chastity">>Combined Chastity Belt<</replace>> - <</link>> - <</if>> - <<if $activeChild.dick > 0>> - | <<link "Chastity Cage">> - <<set $activeChild.choosesOwnChastity = 0>> - <<set $activeChild.chastityAnus = 0>> - <<set $activeChild.chastityPenis = 1>> - <<set $activeChild.chastityVagina = 0>> - <<replace "#chastity">>Chastity Cage<</replace>> - <</link>> | - <<link "Combined Chastity Cage">> - <<set $activeChild.choosesOwnChastity = 0>> - <<set $activeChild.chastityAnus = 1>> - <<set $activeChild.chastityPenis = 1>> - <<set $activeChild.chastityVagina = 0>> - <<replace "#chastity">>Combined Chastity Cage<</replace>> - <</link>> - <<if $activeChild.vagina > -1>> - | <<link "Genital Chastity">> - <<set $activeChild.choosesOwnChastity = 0>> - <<set $activeChild.chastityAnus = 0>> - <<set $activeChild.chastityPenis = 1>> - <<set $activeChild.chastityVagina = 1>> - <<replace "#chastity">>Genital Chastity<</replace>> - <</link>> | - <<link "Full Chastity">> - <<set $activeChild.choosesOwnChastity = 0>> - <<set $activeChild.chastityAnus = 1>> - <<set $activeChild.chastityPenis = 1>> - <<set $activeChild.chastityVagina = 1>> - <<replace "#chastity">>Full Chastity<</replace>> - <</link>> - <</if>> - <</if>> - - <<if FutureSocieties.isActive('FSRestart') && $activeChild.devotion > 20 && $activeChild.trust > 0 && $activeChild.choosesOwnClothes>> - <br> - <<if $activeChild.choosesOwnChastity>> - $He is allowed to decide whether $he to wear chastity devices. - [[Withdraw Privilege|Child Interact][$activeChild.choosesOwnChastity = 0]] - <<else>> - $He is not allowed to choose whether to wear chastity devices. - [[Allow Choice|Child Interact][$activeChild.choosesOwnChastity = 1]] - <</if>> - <</if>> -*/ -/* CLOSES WARDROBE */ - -<br><br>__Physical Regimen:__ -<span id="drugs"> -/* TODO: will children be able to be put on drugs? if so, which drugs? */ - <br>Drugs: <span id="drugs"><b>$activeChild.drugs</b>.</span> - <<link "None">> - <<set $activeChild.drugs = "no drugs">> - <</link>> - <<if $activeChild.drugs == "intensive breast injections" || $activeChild.drugs == "intensive butt injections" || $activeChild.drugs == "intensive penis enhancement" || $activeChild.drugs == "intensive testicle enhancement">> - | <<link "Moderate">> - <<switch $activeChild.drugs>> - <<case "intensive breast injections">> - <<set $activeChild.drugs = "breast injections">> - <<case "intensive butt injections">> - <<set $activeChild.drugs = "butt injections">> - <<case "intensive penis enhancement">> - <<set $activeChild.drugs = "penis enhancement">> - <<case "intensive testicle enhancement">> - <<set $activeChild.drugs = "testicle enhancement">> - <</switch>> - <</link>> - <<elseif $activeChild.drugs == "breast injections" || $activeChild.drugs == "butt injections" || $activeChild.drugs == "penis enhancement" || $activeChild.drugs == "testicle enhancement">> - | <<link "Intensify">> - <<set $activeChild.drugs = "intensive " + $activeChild.drugs>> - <</link>> - <</if>> - <<if $activeChild.intelligence > -100>> - | <<link "Psychosuppressants">> - <<set $activeChild.drugs = "psychosuppressants">> - <</link>> - <<else>> - | Psychosuppressants - <</if>> - <<if ($activeChild.boobs < 48000)>> - | <<link "Breasts">> - <<set $activeChild.drugs = "breast injections">> - <</link>> - <<else>> - | Breasts - <</if>> - <<if $arcologies[0].FSAssetExpansionistResearch>> - <<if ($activeChild.boobs < 25000)>> - | <<link "Hyper-Breasts">> - <<set $activeChild.drugs = "hyper breast injections">> - <</link>> - <<else>> - | Hyper Breasts - <</if>> - <</if>> - <<if $dispensary == 1>> - <<if (["cute", "flat", "inverted", "partially inverted", "puffy", "tiny"].includes($activeChild.nipples))>> - | <<link "Nipple enhancers">> - <<set $activeChild.drugs = "nipple enhancers">> - <</link>> - <<else>> - | Nipple enhancers - <</if>> - <</if>> - <<if $arcologies[0].FSSlimnessEnthusiastResearch>> - <<if ($activeChild.boobs-$activeChild.boobsImplant > 100)>> - | <<link "Breast reducers">> - <<set $activeChild.drugs = "breast redistributors">> - <</link>> - <<else>> - | Breast reducers - <</if>> - <<if ($activeChild.nipples == "huge" || $activeChild.nipples == "puffy" || $activeChild.nipples == "cute")>> - | <<link "Nipple reducers">> - <<set $activeChild.drugs = "nipple atrophiers">> - <</link>> - <<else>> - | Nipple reducers - <</if>> - <</if>> - <<if ($activeChild.butt < 9)>> - | <<link "Buttocks">> - <<set $activeChild.drugs = "butt injections">> - <</link>> - <<else>> - | Buttocks - <</if>> - <<if $arcologies[0].FSAssetExpansionistResearch>> - <<if ($activeChild.butt < 20)>> - | <<link "Hyper-Buttocks">> - <<set $activeChild.drugs = "hyper butt injections">> - <</link>> - <<else>> - | Hyper Buttocks - <</if>> - <</if>> - <<if $arcologies[0].FSSlimnessEnthusiastResearch>> - <<if ($activeChild.butt-$activeChild.buttImplant > 0)>> - | <<link "Butt reducers">> - <<set $activeChild.drugs = "butt redistributors">> - <</link>> - <<else>> - | Butt reducers - <</if>> - <</if>> - <<if ($activeChild.lips <= 95) || (($activeChild.lips <= 85) && ($seeExtreme != 1))>> - | <<link "Lips">> - <<set $activeChild.drugs = "lip injections">> - <</link>> - <<else>> - | Lips - <</if>> - <<if $arcologies[0].FSSlimnessEnthusiastResearch>> - <<if ($activeChild.lips-$activeChild.lipsImplant > 0)>> - | <<link "Lip reducers">> - <<set $activeChild.drugs = "lip atrophiers">> - <</link>> - <<else>> - | Lip reducers - <</if>> - <</if>> - <<if $growthStim>> - <<if canImproveHeight($activeChild)>> - | <<link "Growth Stimulants">> - <<set $activeChild.drugs = "growth stimulants">> - <</link>> - <<else>> - | Growth Stimulants - <</if>> - <</if>> - <<if ($activeChild.dick > 0)>> - <<if ($activeChild.dick < 10)>> - | <<link "Penis enhancement">> - <<set $activeChild.drugs = "penis enhancement">> - <</link>> - <<else>> - | Penis enhancement - <</if>> - <<else>> - <<if ($activeChild.clit < 5)>> - | <<link "Clitoris enhancement">> - <<set $activeChild.drugs = "penis enhancement">> - <</link>> - <<else>> - | Clitoris enhancement - <</if>> - <</if>> - <<if $arcologies[0].FSAssetExpansionistResearch>> - <<if ($activeChild.dick > 0)>> - <<if ($activeChild.dick < 31)>> - | <<link "Hyper penis enhancement">> - <<set $activeChild.drugs = "hyper penis enhancement">> - <</link>> - <<else>> - | Hyper penis enhancement - <</if>> - <<else>> - <<if ($activeChild.clit < 5)>> - | <<link "Hyper clitoris enhancement">> - <<set $activeChild.drugs = "penis enhancement">> - <</link>> - <<else>> - | Hyper clitoris enhancement - <</if>> - <</if>> - <</if>> - <<if $arcologies[0].FSSlimnessEnthusiastResearch>> - <<if ($activeChild.dick > 1)>> - | <<link "Penis reducers">> - <<set $activeChild.drugs = "penis atrophiers">> - <</link>> - <<elseif $activeChild.dick>> - | Penis reducers - <</if>> - <</if>> - <<if ($activeChild.balls > 0)>> - | <<link "Testicle enhancement">> - <<set $activeChild.drugs = "testicle enhancement">> - <</link>> - <<if $arcologies[0].FSAssetExpansionistResearch>> - | <<link "Hyper testicle enhancement">> - <<set $activeChild.drugs = "hyper testicle enhancement">> - <</link>> - <</if>> - <</if>> - <<if $arcologies[0].FSSlimnessEnthusiastResearch>> - <<if ($activeChild.balls > 1)>> - | <<link "Testicle reducers">> - <<set $activeChild.drugs = "testicle atrophiers">> - <</link>> - <<elseif $activeChild.balls>> - | Testicle reducers - <</if>> - <<if ($activeChild.clit > 0)>> - | <<link "Clitoris reducers">> - <<set $activeChild.drugs = "clitoris atrophiers">> - <</link>> - <</if>> - <<if $activeChild.labia > 0 && $activeChild.vagina > -1>> - | <<link "Labia reducers">> - <<set $activeChild.drugs = "labia atrophiers">> - <</link>> - <</if>> - <</if>> - <<if $arcologies[0].FSYouthPreferentialistResearch>> - <<if ($activeChild.visualAge > 18)>> - | <<link "Anti-aging cream">> - <<set $activeChild.drugs = "anti-aging cream">> - <</link>> - <<else>> - | Anti-aging cream - <</if>> - <</if>> - | <<link "Steroids">> - <<set $activeChild.drugs = "steroids">> - <</link>> - <<if $arcologies[0].FSSlimnessEnthusiastResearch>> - <<if ($activeChild.weight > -95)>> - | <<link "Weight loss pills">> - <<set $activeChild.drugs = "appetite suppressors">> - <</link>> - <<else>> - | Weight loss pills - <</if>> - <</if>> - <<if $precociousPuberty && $pubertyHormones && ($activeChild.breedingMark != 1 || !$propOutcome || $eugenicsFullControl)>> - <<if ($activeChild.ovaries || $activeChild.mpreg) && $activeChild.pubertyXX == 0>> - | <<link "Female hormone injections">> - <<set $activeChild.drugs = "female hormone injections">> - <</link>> - <</if>> - <<if $activeChild.balls > 0 && $activeChild.pubertyXY == 0>> - | <<link "Male hormone injections">> - <<set $activeChild.drugs = "male hormone injections">> - <</link>> - <</if>> - <</if>> - | <<link "Hormone enhancers">> - <<set $activeChild.drugs = "hormone enhancers">> - <</link>> - | <<link "Hormone blockers">> - <<set $activeChild.drugs = "hormone blockers">> - <</link>> - <<if $activeChild.boobs > 250 && $activeChild.boobShape != "saggy" && $purchasedSagBGone>> - | <<link "Sag-B-Gone breast lifting cream">> - <<set $activeChild.drugs = "sag-B-gone">> - <</link>> - <</if>> -</span> - -<br> - -Health: <b><span id="curatives">$activeChild.curatives.</span></b> -<<link "None">> - <<set $activeChild.curatives = "none">> - <<replace "#curatives">>$activeChild.curatives<</replace>> -<</link>> | -<<link "Preventatives">> - <<set $activeChild.curatives = "preventatives">> - <<replace "#curatives">>$activeChild.curatives<</replace>> -<</link>> | -<<link "Curatives">> - <<set $activeChild.curatives = "curatives">> - <<replace "#curatives">>$activeChild.curatives<</replace>> -<</link>> - - - -/* TODO: will children be able to be put on aphrodisiacs? */ -Aphrodisiacs: <b><span id="aphrodisiacs">$activeChild.aphrodisiacs.</span></b> -<<link "None">> - <<set $activeChild.aphrodisiacs = "none">> - <<replace "#aphrodisiacs">>$activeChild.aphrodisiacs<</replace>> -<</link>> | -<<link "Apply">> - <<set $activeChild.aphrodisiacs = "applied">> - <<replace "#aphrodisiacs">>$activeChild.aphrodisiacs<</replace>> -<</link>> | -<<link "Extreme">> - <<set $activeChild.aphrodisiacs = "extreme">> - <<replace "#aphrodisiacs">>$activeChild.aphrodisiacs<</replace>> -<</link>> | -<<link "Anaphrodisiacs">> - <<set $activeChild.aphrodisiacs = "anaphrodisiacs">> - <<replace "#aphrodisiacs">>$activeChild.aphrodisiacs<</replace>> -<</link>> - -/* TODO: will children be able to be bloated*/ -<span id="bloating"> -<<if $activeChild.inflation > 0>> - <br> - __Required Bloating__: <b><span id="inflate"><<if $activeChild.inflation == 3>><<print "$He is required to keep 2 gallons of $activeChild.inflationType in $him at all times">><<elseif $activeChild.inflation == 2>><<print "$He is required to keep 4 liters of $activeChild.inflationType in $him at all times">><<elseif $activeChild.inflation>><<print "$He is required to keep 2 liters of $activeChild.inflationType in $him at all times">><</if>></span></b>. - <<link "Let $him deflate">> - <<run deflate($activeChild)>> - <</link>> -<</if>> -</span> - -<br> -Hormones: <b><span id="hormones">$activeChild.hormones.</span></b> -<<link "Intensive Female">> - <<set $activeChild.hormones = "intensive female">> - <<replace "#hormones">>$activeChild.hormones<</replace>> -<</link>> | -<<link "Female">> - <<set $activeChild.hormones = "female">> - <<replace "#hormones">>$activeChild.hormones<</replace>> -<</link>> | -<<link "None">> - <<set $activeChild.hormones = "none">> - <<replace "#hormones">>$activeChild.hormones<</replace>> -<</link>> | -<<link "Male">> - <<set $activeChild.hormones = "male">> - <<replace "#hormones">>$activeChild.hormones<</replace>> -<</link>> | -<<link "Intensive Male">> - <<set $activeChild.hormones = "intensive male">> - <<replace "#hormones">>$activeChild.hormones<</replace>> -<</link>> - -<br>Diet: <b><span id="diet">$activeChild.diet.</span></b> -<<link "Healthy">> - <<set $activeChild.diet = "healthy">> - <<replace "#diet">>$activeChild.diet<</replace>> -<</link>> -<<if ($activeChild.health.condition < 90) && ($activeChild.chem >= 10) && ($dietCleanse)>> -| <<link "Cleanse">> - <<set $activeChild.diet = "cleansing">> - <<replace "#diet">>$activeChild.diet<</replace>> - <</link>> | -<<elseif ($dietCleanse)>> - | //$He is already healthy// -<</if>> -<<if ($activeChild.balls > 0) && ($cumProDiet)>> -| <<link "Cum production">> - <<set $activeChild.diet = "cum production">> - <<replace "#diet">>$activeChild.diet<</replace>> - <</link>> -<</if>> -<<if canGetPregnant($activeChild) && ($dietFertility)>> -| <<link "Fertility">> - <<set $activeChild.diet = "fertility">> - <<replace "#diet">>$activeChild.diet<</replace>> - <</link>> -<</if>> -<<if ($activeChild.weight >= -95)>> -| <<link "Lose weight">> - <<set $activeChild.diet = "restricted">> - <<replace "#diet">>$activeChild.diet<</replace>> - <</link>> -<<else>> - | //$He is already underweight// -<</if>> -<<if $activeChild.weight <= 200>> -| <<link "Fatten">> - <<set $activeChild.diet = "fattening">> - <<replace "#diet">>$activeChild.diet<</replace>> - <</link>> -<<else>> -| //$He is already extremely overweight// -<</if>> -<<if $feeder>> -| <<link "Estrogen enriched">> - <<set $activeChild.diet = "XX">> - <<replace "#diet">>$activeChild.diet<</replace>> - <</link>> -| <<link "Testosterone enriched">> - <<set $activeChild.diet = "XY">> - <<replace "#diet">>$activeChild.diet<</replace>> - <</link>> - <<if $dietXXY && $activeChild.balls > 0 && ($activeChild.ovaries || $activeChild.mpreg)>> - | <<link "Herm hormone blend">> - <<set $activeChild.diet = "XXY">> - <<replace "#diet">>$activeChild.diet<</replace>> - <</link>> - <</if>> -<</if>> -<<if ($activeChild.muscles < 100) && !isAmputee($activeChild)>> -| <<link "Build muscle">><<set $activeChild.diet = "muscle building">><<replace "#diet">>$activeChild.diet<</replace>><</link>> -<<elseif $activeChild.muscles >= 100 && !isAmputee($activeChild)>> - | //$He is maintaining $his enormous musculature// -<<else>> - | //$He has no limbs and thus can't effectively build muscle// -<</if>> -<<if $activeChild.muscles > 0 && canWalk($activeChild)>> -| <<link "Slim down">> - <<set $activeChild.diet = "slimming">> - <<replace "#diet">>$activeChild.diet<</replace>> - <</link>> -<<elseif !canWalk($activeChild)>> - | //$He can't move and thus can't trim down// -<</if>> - -/* TODO: replace these with strings */ -<br>Diet Base: - <b><span id="diet-base"> - <<if $activeChild.dietCum == 2>> - Cum Based - <<elseif ($activeChild.dietCum) && ($activeChild.dietMilk == 0)>> - Cum Added - <<elseif ($activeChild.dietCum) && ($activeChild.dietMilk)>> - Cum and Milk Added - <<elseif ($activeChild.dietMilk) && ($activeChild.dietCum == 0)>> - Milk Added - <<elseif ($activeChild.dietMilk == 2)>> - Milk Based - <<elseif ($activeChild.dietCum == 0) && ($activeChild.dietMilk == 0)>> - Normal - <<else>> - THERE HAS BEEN AN ERROR - <</if>> - </span></b> -<<link "Normal">> - <<set $activeChild.dietCum = 0>> - <<set $activeChild.dietMilk = 0>> - <<replace "#diet-base">>Normal<</replace>> -<</link>> | -<<link "Cum Added">> - <<set $activeChild.dietCum = 1>> - <<set $activeChild.dietMilk = 0>> - <<replace "#diet-base">>Cum Added<</replace>> -<</link>> | -<<link "Milk Added">> - <<set $activeChild.dietCum = 0>> - <<set $activeChild.dietMilk = 1>> - <<replace "#diet-base">>Milk Added<</replace>> -<</link>> | -<<link "Cum & Milk Added">> - <<set $activeChild.dietCum = 1>> - <<set $activeChild.dietMilk = 1>> - <<replace "#diet-base">>Cum & Milk Added<</replace>> -<</link>> | -<<link "Cum Based">> - <<set $activeChild.dietCum = 2>> - <<set $activeChild.dietMilk = 0>> - <<replace "#diet-base">>Cum Based<</replace>> -<</link>> | -<<link "Milk Based">> - <<set $activeChild.dietCum = 0>> - <<set $activeChild.dietMilk = 2>> - <<replace "#diet-base">>Milk Based<</replace>> -<</link>> - -<br><br>__Behavior__:<br> -Living standard: <b><span id="livingRules">$activeChild.rules.living.</span></b> -//$His living conditions are managed by $nurseryName's décor.// - -<br>Typical punishment: <b><span id="standardPunishment">$activeChild.rules.punishment.</span></b> -<<link "Confinement">> - <<set $activeChild.rules.punishment = "confinement">> - <<replace "#standardPunishment">>$activeChild.rules.punishment<</replace>> -<</link>> | -<<link "Whipping">> - <<set $activeChild.rules.punishment = "whipping">> - <<replace "#standardPunishment">>$activeChild.rules.punishment<</replace>> -<</link>> | -<<link "Chastity">> - <<set $activeChild.rules.punishment = "chastity">> - <<replace "#standardPunishment">>$activeChild.rules.punishment<</replace>> -<</link>> | -<<link "Situational">> - <<set $activeChild.rules.punishment = "situational">> - <<replace "#standardPunishment">>$activeChild.rules.punishment<</replace>> -<</link>> - -Typical reward: ''<span id="standardReward">$activeChild.rules.reward.</span>'' -<<link "Relaxation">><<set $activeChild.rules.reward = "relaxation">><<replace "#standardReward">>$activeChild.rules.reward<</replace>><</link>> | -<<link "Drugs">><<set $activeChild.rules.reward = "drugs">><<replace "#standardReward">>$activeChild.rules.reward<</replace>><</link>> | -<<link "Orgasm">><<set $activeChild.rules.reward = "orgasm">><<replace "#standardReward">>$activeChild.rules.reward<</replace>><</link>> | -<<link "Situational">><<set $activeChild.rules.reward = "situational">><<replace "#standardReward">>$activeChild.rules.reward<</replace>><</link>> - -<br><br>Non-assignment orgasm rules:z -<div style="text-indent:2em"> -Masturbation is ''<span id="relMasturbation"><<if $activeChild.rules.release.masturbation === 1>>allowed<<else>>forbidden<</if>>.</span>'' - <<link "Allow">><<set $activeChild.rules.release.masturbation = 1>><<replace "#relMasturbation">>allowed<</replace>><</link>> | - <<link "Forbid">><<set $activeChild.rules.release.masturbation = 0>><<replace "#relMasturbation">>forbidden<</replace>><</link>> -</div> -<div style="text-indent:2em"> -Sex with romantic partner is ''<span id="relPartner"><<if $activeChild.rules.release.partner === 1>>allowed<<else>>forbidden<</if>>.</span>'' - <<link "Allow">><<set $activeChild.rules.release.partner = 1>><<replace "#relPartner">>allowed<</replace>><</link>> | - <<link "Forbid">><<set $activeChild.rules.release.partner = 0>><<replace "#relPartner">>forbidden<</replace>><</link>> -</div> -<<if $seeIncest == 1>> - <div style="text-indent:2em"> - Sex with close family is ''<span id="relFamily"><<if $activeChild.rules.release.family === 1>>allowed<<else>>forbidden<</if>>.</span>'' - <<link "Allow">><<set $activeChild.rules.release.family = 1>><<replace "#relFamily">>allowed<</replace>><</link>> | - <<link "Forbid">><<set $activeChild.rules.release.family = 0>><<replace "#relFamily">>forbidden<</replace>><</link>> - </div> -<</if>> -<div style="text-indent:2em"> -Sex with other slaves is ''<span id="relSlaves"><<if $activeChild.rules.release.slaves === 1>>allowed<<else>>forbidden<</if>>.</span>'' - <<link "Allow">><<set $activeChild.rules.release.slaves = 1>><<replace "#relSlaves">>allowed<</replace>><</link>> | - <<link "Forbid">><<set $activeChild.rules.release.slaves = 0>><<replace "#relSlaves">>forbidden<</replace>><</link>> -</div> -<div style="text-indent:2em"> -Routine sex with <<= properMaster()>> is ''<span id="relMaster"><<if $activeChild.rules.release.master === 1>>granted<<else>>denied<</if>>.</span>'' - <<link "Grant">><<set $activeChild.rules.release.master = 1>><<replace "#relMaster">>granted<</replace>><</link>> | - <<link "Deny">><<set $activeChild.rules.release.master = 0>><<replace "#relMaster">>denied<</replace>><</link>> -</div> - -/* TODO: will children be able to have clit piercings? */ -<<if $activeChild.clitPiercing == 3>> - <div> - <<if $activeChild.dick < 1>> - $His smart clit piercing is set to - <<else>>$His smart frenulum piercing is set to - <</if>> - <b><span id="setting">$activeChild.clitSetting.</span></b> - <<link "Vanilla">> - <<set $activeChild.clitSetting = "vanilla">> - <<replace "#setting">>$activeChild.clitSetting<</replace>> - <</link>> | - <<link "Oral">> - <<set $activeChild.clitSetting = "oral">> - <<replace "#setting">>$activeChild.clitSetting<</replace>> - <</link>> | - <<link "Anal">> - <<set $activeChild.clitSetting = "anal">> - <<replace "#setting">>$activeChild.clitSetting<</replace>> - <</link>> | - <<link "Boobs">> - <<set $activeChild.clitSetting = "boobs">> - <<replace "#setting">>$activeChild.clitSetting<</replace>> - <</link>> | - <<link "Sub">> - <<set $activeChild.clitSetting = "submissive">> - <<replace "#setting">>$activeChild.clitSetting<</replace>> - <</link>> | - <<link "Dom">> - <<set $activeChild.clitSetting = "dom">> - <<replace "#setting">>$activeChild.clitSetting<</replace>> - <</link>> | - <<link "Humiliation">> - <<set $activeChild.clitSetting = "humiliation">> - <<replace "#setting">>$activeChild.clitSetting<</replace>> - <</link>> | - <<if $seePreg != 0>> - <<link "Preg">> - <<set $activeChild.clitSetting = "pregnancy">> - <<replace "#setting">>$activeChild.clitSetting<</replace>> - <</link>> | - <</if>> - <<link "Pain">> - <<set $activeChild.clitSetting = "masochist">> - <<replace "#setting">>$activeChild.clitSetting<</replace>> - <</link>> | - <<link "Sadism">> - <<set $activeChild.clitSetting = "sadist">> - <<replace "#setting">>$activeChild.clitSetting<</replace>> - <</link>> | - <<link "Men">> - <<set $activeChild.clitSetting = "men">> - <<replace "#setting">>$activeChild.clitSetting<</replace>> - <</link>> | - <<link "Anti-men">> - <<set $activeChild.clitSetting = "anti-men">> - <<replace "#setting">>$activeChild.clitSetting<</replace>> - <</link>> | - <<link "Women">> - <<set $activeChild.clitSetting = "women">> - <<replace "#setting">>$activeChild.clitSetting<</replace>> - <</link>> | - <<link "Anti-women">> - <<set $activeChild.clitSetting = "anti-women">> - <<replace "#setting">>$activeChild.clitSetting<</replace>> - <</link>> | - <<link "All sex">> - <<set $activeChild.clitSetting = "all">> - <<replace "#setting">>$activeChild.clitSetting<</replace>> - <</link>> | - <<link "No sex">> - <<set $activeChild.clitSetting = "none">> - <<replace "#setting">>$activeChild.clitSetting<</replace>> - <</link>> - </div> -<</if>> - -<<if $activeChild.voice != 0>> - <br>Speech rules: <b><span id="speechRules">$activeChild.rules.speech.</span></b> - <<link "Restrictive">> - <<set $activeChild.rules.speech = "restrictive">> - <<replace "#speechRules">>$activeChild.rules.speech<</replace>> - <</link>> | - <<link "Permissive">> - <<set $activeChild.rules.speech = "permissive">> - <<replace "#speechRules">>$activeChild.rules.speech<</replace>> - <</link>> - <<if $activeChild.accent.isBetween(0, 4)>>| <<link "Accent elimination">> - <<set $activeChild.rules.speech = "accent elimination">> - <<replace "#speechRules">>$activeChild.rules.speech<</replace>> - <</link>> - <<elseif $activeChild.accent > 3>>| <<link "Language lessons">> - <<set $activeChild.rules.speech = "language lessons">> - <<replace "#speechRules">>$activeChild.rules.speech<</replace>> - <</link>> - <</if>> -<</if>> - -<br> -Relationship rules: <b><span id="relationshipRules">$activeChild.rules.relationship.</span></b> -<<link "Restrictive">> - <<set $activeChild.rules.relationship = "restrictive">> - <<replace "#relationshipRules">>$activeChild.rules.relationship<</replace>> -<</link>> | -<<link "Just friends">> - <<set $activeChild.rules.relationship = "just friends">> - <<replace "#relationshipRules">>$activeChild.rules.relationship<</replace>> -<</link>> | -<<link "Permissive">> - <<set $activeChild.rules.relationship = "permissive">> - <<replace "#relationshipRules">>$activeChild.rules.relationship<</replace>> -<</link>> - -<br> -Target destination: <b><span id="targetLocation">$activeChild.targetLocation.</span></b> -<<link "Slavery">> - <<set $activeChild.targetLocation = "slavery">> - <<replace "#targetLocation">>$activeChild.targetLocation<</replace>> -<</link>> | -<<link "Freedom">> - <<set $activeChild.targetLocation = "freedom">> - <<replace "#targetLocation">>$activeChild.targetLocation<</replace>> -<</link>> - -<br><br> - -<<link "Export this child" "Export Slave">> -<</link>> -<<if $cheatMode>> - | <<link "Import a child" "Import Slave">> - <</link>> -<</if>> -<<if $cheatMode>> - <br>''Cheatmode:'' - <<link "Retrieve immediately" "Nursery Retrieval Workaround">> - <<set $readySlave = $cribs.pluck([$i], [$i])>> - <</link>> -<</if>> -<br> -<<if $activeChild.actualAge < $minimumSlaveAge>> - <<link "Remove from $nurseryName" "Nursery Discard Workaround">><</link>> - <br>//Children that are younger than <<print $minimumSlaveAge>> will be given to another arcology to be raised// -<<else>> - <<link "Remove from $nurseryName" "Nursery Retrieval Workaround">><</link>> -<</if>> diff --git a/src/npc/children/childPassages.js b/src/npc/children/childPassages.js new file mode 100644 index 0000000000000000000000000000000000000000..5fefcb46c16cbf99f58bcc585990005d0e8f8ba6 --- /dev/null +++ b/src/npc/children/childPassages.js @@ -0,0 +1,8 @@ +new App.DomPassage("Child Interact", + () => { + V.nextButton = "Confirm changes"; + V.nextLink = "Nursery"; + + return App.UI.ChildInteract.mainPage(V.activeChild); + }, ["jump-from-safe"] +); \ No newline at end of file diff --git a/src/npc/children/interactions/childInteract.js b/src/npc/children/interactions/childInteract.js new file mode 100644 index 0000000000000000000000000000000000000000..1d6b44d4c28ba4c3272f66a3c9fe75d743d84b56 --- /dev/null +++ b/src/npc/children/interactions/childInteract.js @@ -0,0 +1,81 @@ +/** + * @param {App.Facilities.Nursery.ChildState} child + * @returns {DocumentFragment} + */ +App.UI.ChildInteract.mainPage = function(child) { + const el = new DocumentFragment(); + + App.Utils.scheduleSidebarRefresh(); + + el.append(App.UI.ChildInteract.navigation(child)); + + /** + * @typedef {object} siCategory + * @property {string} title + * @property {string} id + * @property {DocumentFragment|HTMLElement} node + */ + + /** @type {Array<siCategory>} */ + const tabs = [ + { + title: "Description", + id: "description", + get node() { return App.UI.ChildInteract.description(child); } + }, + { + title: "Customize", + id: "customize", + get node() { return App.UI.SlaveInteract.custom(child, refresh); } + }, + { + title: "Family", + id: "family-tab", + get node() { return App.UI.SlaveInteract.family(child); } + } + ]; + + const contentHolder = document.createElement("span"); + contentHolder.append(content()); + el.append(contentHolder); + + return el; + + function content() { + if (V.slaveInteractLongForm) { + return displayWithoutTabs(); + } else { + return displayWithTabs(); + } + } + + function refresh() { + jQuery(contentHolder).empty().append(content()); + } + + /** + * @returns {DocumentFragment} + */ + function displayWithTabs() { + const tabBar = new App.UI.Tabs.TabBar("ChildInteract"); + const f = new DocumentFragment(); + tabBar.customNode = f; + + for (const tab of tabs) { + tabBar.addTab(tab.title, tab.id, tab.node); + } + return tabBar.render(); + } + + /** + * @returns {DocumentFragment} + */ + function displayWithoutTabs() { + const el = new DocumentFragment(); + for (const tab of tabs) { + App.UI.DOM.appendNewElement("h2", el, tab.title); + el.append(tab.node); + } + return el; + } +}; \ No newline at end of file diff --git a/src/npc/children/interactions/ciDescription.js b/src/npc/children/interactions/ciDescription.js new file mode 100644 index 0000000000000000000000000000000000000000..fb400a2b0c50d2f801c943c77b7afc8592e74f45 --- /dev/null +++ b/src/npc/children/interactions/ciDescription.js @@ -0,0 +1,56 @@ +/** + * @param {App.Facilities.Nursery.ChildState} child + * @returns {DocumentFragment} + */ +App.UI.ChildInteract.description = function(child) { + const el = new DocumentFragment(); + const descriptionLink = document.createElement("div"); + descriptionLink.style.fontStyle = "italic"; + descriptionLink.id = "description-link"; + + if (V.seeDetails === 1) { + const descriptionOptions = document.createElement("div"); + + descriptionOptions.id = "description-options"; + el.append(descriptionOptions); + + el.append(App.Desc.longSlave(child, {noArt: true, links: true})); + + descriptionLink.append(showOptions()); + el.append(descriptionLink); + } else { + descriptionLink.append( + App.UI.DOM.link( + "Show descriptions", + () => { + V.seeDetails = 1; + }, + [], + "Child Interact" + ) + ); + el.append(descriptionLink); + } + + return el; + + function showOptions() { + return App.UI.DOM.link( + "Description Options", + () => { + jQuery("#description-link").empty().append(hideOptions()); + jQuery("#description-options").empty().append(App.UI.descriptionOptions()); + } + ); + } + + function hideOptions() { + return App.UI.DOM.link( + "Description Options", + () => { + jQuery("#description-link").empty().append(showOptions()); + jQuery("#description-options").empty().append(); + } + ); + } +}; diff --git a/src/npc/children/interactions/ciNavigation.js b/src/npc/children/interactions/ciNavigation.js new file mode 100644 index 0000000000000000000000000000000000000000..cf142d93ef33a29051fe0a612b52db575f4b1c95 --- /dev/null +++ b/src/npc/children/interactions/ciNavigation.js @@ -0,0 +1,75 @@ +/** + * @param {App.Facilities.Nursery.ChildState} child + * @returns {DocumentFragment} + */ +App.UI.ChildInteract.navigation = function(child) { + const f = new DocumentFragment(); + + /* + if (V.cheatMode) { + App.UI.DOM.appendNewElement("div", f, + App.UI.DOM.passageLink("Cheat Edit Slave", "Cheat Edit Actor", () => { + V.cheater = 1; + delete V.tempSlave; + delete V.entityType; + V.tempSlave = clone(getSlave(V.AS)); + V.entityType = "slave"; + }), + "cheat-menu" + ); + } + */ + + const p = document.createElement("p"); + p.classList.add("center"); + + // const placeInLine = App.UI.SlaveInteract.placeInLine(child); + const div = App.UI.DOM.appendNewElement("div", p, null); + const previous = App.UI.DOM.makeElement("span", null, ['margin-right']); + const next = App.UI.DOM.makeElement("span", null, ['margin-left']); + const name = App.UI.DOM.makeElement("span", child.slaveName, ['slave-name', "si-header"]); + + /* + previous.id = "prev-slave"; + next.id = "next-slave"; + + previous.append( + App.UI.DOM.makeElement("span", App.UI.Hotkeys.hotkeys("prev-slave"), ['hotkey']), + " ", + App.UI.DOM.makeElement("span", App.UI.DOM.passageLink("Prev", "Slave Interact", () => { + V.AS = placeInLine[0]; + }), ["adjacent-slave"]), + ); + next.append( + App.UI.DOM.makeElement("span", App.UI.DOM.passageLink("Next", "Slave Interact", () => { + V.AS = placeInLine[1]; + }), ["adjacent-slave"]), + " ", + App.UI.DOM.makeElement("span", App.UI.Hotkeys.hotkeys("next-slave"), ['hotkey']), + ); + */ + + function content() { + const frag = new DocumentFragment(); + + frag.append( + previous, + name, + ' ', + /* + App.UI.DOM.makeElement("span", App.UI.favoriteToggle(child, () => { + App.UI.DOM.replace(div, content()); + }), ['si-header']), + */ + next, + ); + + return frag; + } + + div.append(content()); + + f.append(p); + + return f; +}; diff --git a/src/npc/children/longChildDescription.js b/src/npc/children/longChildDescription.js index 54ec594c66c06ed24920b62af6a29baed59d15ff..f0b7f89278862f3aed316e17bc432ff158a59321 100644 --- a/src/npc/children/longChildDescription.js +++ b/src/npc/children/longChildDescription.js @@ -814,7 +814,7 @@ App.Facilities.Nursery.LongChildDescription = function(child, {market = 0, event function collar(child) { let r = ``; let daddy; - let pregCollar = jsEither(1, 2, 3); + let pregCollar = jsEither([1, 2, 3]); switch (child.collar) { case "uncomfortable leather":