| @@ -0,0 +1 @@ | |||
| *.ogg filter=lfs diff=lfs merge=lfs -text | |||
| @@ -1,2 +1,18 @@ | |||
| media/ | |||
| .vscode | |||
| # Created by https://www.gitignore.io/api/visualstudiocode | |||
| # Edit at https://www.gitignore.io/?templates=visualstudiocode | |||
| ### VisualStudioCode ### | |||
| .vscode/* | |||
| !.vscode/settings.json | |||
| !.vscode/tasks.json | |||
| !.vscode/launch.json | |||
| !.vscode/extensions.json | |||
| ### VisualStudioCode Patch ### | |||
| # Ignore all local history of files | |||
| .history | |||
| # End of https://www.gitignore.io/api/visualstudiocode | |||
| !.vscode/templates.code-snippets | |||
| @@ -0,0 +1,167 @@ | |||
| { | |||
| // Place your satiate workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and | |||
| // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope | |||
| // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is | |||
| // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: | |||
| // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. | |||
| // Placeholders with the same ids are connected. | |||
| // Example: | |||
| // "Print to console": { | |||
| // "scope": "javascript,typescript", | |||
| // "prefix": "log", | |||
| // "body": [ | |||
| // "console.log('$1');", | |||
| // "$2" | |||
| // ], | |||
| // "description": "Log output to console" | |||
| // } | |||
| "Room": { | |||
| "prefix": "room", | |||
| "body": [ | |||
| "\"$1\": {", | |||
| " \"id\": \"$1\",", | |||
| " \"name\": \"$2\",", | |||
| " \"desc\": \"$3\",", | |||
| " \"move\": (room, state) => {", | |||
| "", | |||
| " },", | |||
| " \"enter\": (room, state) => {", | |||
| "", | |||
| " },", | |||
| " \"exit\": (room, state) => {", | |||
| "", | |||
| " },", | |||
| " \"actions\": [", | |||
| "", | |||
| " ],", | |||
| " \"exits\": {", | |||
| "", | |||
| " },", | |||
| " \"hooks\": [", | |||
| "", | |||
| " ],", | |||
| " \"data\": {", | |||
| " \"stats\": {", | |||
| " ", | |||
| " }", | |||
| " }", | |||
| "}," | |||
| ], | |||
| "description": "Room" | |||
| }, | |||
| "Action": { | |||
| "prefix": "action", | |||
| "body": [ | |||
| "{", | |||
| " name: \"$1\",", | |||
| " desc: \"$2\",", | |||
| " execute: (room, state) => {", | |||
| "", | |||
| " },", | |||
| " show: [", | |||
| "", | |||
| " ],", | |||
| " conditions: [", | |||
| "", | |||
| " ]", | |||
| "}," | |||
| ], | |||
| "description": "Action" | |||
| }, | |||
| "Exit": { | |||
| "prefix": "exit", | |||
| "body": [ | |||
| "\"$1\": {", | |||
| " \"target\": \"$2\",", | |||
| " \"desc\": \"$3\",", | |||
| " \"show\": [", | |||
| " ", | |||
| " ],", | |||
| " \"conditions\": [", | |||
| "", | |||
| " ],", | |||
| " \"hooks\": [", | |||
| " ", | |||
| " ]", | |||
| "}," | |||
| ], | |||
| "description": "Exit" | |||
| }, | |||
| "Condition": { | |||
| "prefix": "cond", | |||
| "body": [ | |||
| "(room, state) => {", | |||
| " return ${1:true}", | |||
| "}", | |||
| "" | |||
| ], | |||
| "description": "Condition" | |||
| }, | |||
| "Hook": { | |||
| "prefix": "hook", | |||
| "body": [ | |||
| "(room, exit, state) => {", | |||
| " return ${1:true}", | |||
| "}", | |||
| "" | |||
| ], | |||
| "description": "Condition" | |||
| }, | |||
| "Stat": { | |||
| "prefix": "stat", | |||
| "body": [ | |||
| "{\"name\": \"$1\", \"type\": \"${2|foo,bar|}\", \"value\": $3, \"max\": $4, \"color\": \"rgb(0,0,0)\" }" | |||
| ], | |||
| "description": "Stat" | |||
| }, | |||
| "Timer": { | |||
| "prefix": "timer", | |||
| "body": [ | |||
| "startTimer({", | |||
| " id: \"$1\",", | |||
| " func: state => {", | |||
| " return true;", | |||
| " },", | |||
| " delay: $2,", | |||
| " loop: $3,", | |||
| " classes: [", | |||
| "", | |||
| " ]", | |||
| "}, state);", | |||
| "" | |||
| ], | |||
| "description": "Timer" | |||
| }, | |||
| "Story": { | |||
| "prefix": "story", | |||
| "body": [ | |||
| "stories.push({", | |||
| " \"id\": \"$1\",", | |||
| " \"name\": \"$2\",", | |||
| " \"tags\": [", | |||
| "", | |||
| " ],", | |||
| " \"intro\": {", | |||
| " \"start\": \"$3\",", | |||
| " \"setup\": state => {", | |||
| "", | |||
| " },", | |||
| " \"intro\": state => {", | |||
| "", | |||
| " }", | |||
| " },", | |||
| " \"sounds\": [", | |||
| "", | |||
| " ],", | |||
| " \"preload\": [", | |||
| "", | |||
| " ],", | |||
| " \"world\": {", | |||
| " ", | |||
| " }", | |||
| "});", | |||
| "" | |||
| ], | |||
| "description": "Story" | |||
| } | |||
| } | |||
| @@ -0,0 +1,3 @@ | |||
| big monch | |||
| https://satiate.crux.sexy | |||
| @@ -55,6 +55,10 @@ function initGamePostSetup(state) { | |||
| createStatDisplays(state.player.stats, "player"); | |||
| } | |||
| function getStat(stat, state) { | |||
| return state.player.stats[stat].value; | |||
| } | |||
| function changeStat(stat, amount, state) { | |||
| let value = state.player.stats[stat].value; | |||
| value += amount; | |||
| @@ -98,7 +102,6 @@ function updateStatDisplay(stats, statType) { | |||
| func: the function to invoke | |||
| delay: how long to wait between invocations | |||
| loop: false = no looping, true = loop forever | |||
| room: the room associated with the timer | |||
| } | |||
| Returns the timeout id - but you still need to cancel it through stopTimer! | |||
| @@ -126,8 +129,8 @@ function startTimer(config, state) { | |||
| } | |||
| }, config.delay); | |||
| state.timers.push({id: config.id, timeout: timeout, room: config.room, classes: config.classes || []}); | |||
| state.timers.push({id: config.id, timeout: timeout, classes: config.classes || []}); | |||
| return timeout; | |||
| } | |||
| @@ -139,13 +142,6 @@ function stopTimer(id, state) { | |||
| state.timers = state.timers.filter(timer => timer.id != id); | |||
| } | |||
| function stopRoomTimers(room, state) { | |||
| const matches = state.timers.filter(timer => timer.room == room); | |||
| matches.forEach(timer => clearTimeout(timer.timeout)); | |||
| state.timers = state.timers.filter(timer => timer.room != room); | |||
| } | |||
| function stopClassTimers(timerClass, state, inverse) { | |||
| const matches = state.timers.filter(timer => timer.classes.includes(timerClass)); | |||
| const others = state.timers.filter(timer => !timer.classes.includes(timerClass)); | |||
| oid sha256:e3476f4fd3e3eacb3fcc7c6616c3573c35289c8486f60e9e64c262d38aa290fb | |||
| size 8461 |
| oid sha256:cad1673ea5f4a85a7eb2cd125073ac9daf1bc27d105520e847af06549bd44abb | |||
| size 2698636 |
| oid sha256:7f7645ce0b6fed13c6222edc1c66b73aa5c71c7f9fd8df6d651d28e1cfeaa94c | |||
| size 3251342 |
| oid sha256:0dcd827ff7f1daafaed20af0b8f34f5aa249b005e60380ff75ce5e356eeafa9d | |||
| size 5468962 |
| oid sha256:353212c0b88d5acf1cba7795462b516e07c5743117e8773f8694d7e1e1d52490 | |||
| size 294651 |
| oid sha256:545cbe12f08f53c70c841c7fa1c1d55283804a0d72b39667c9763a24c7acb842 | |||
| size 248493 |
| oid sha256:a81d487034696805b5862119b4b0e71336ac9fbc4eee22e89b6d20e82f16d967 | |||
| size 176136 |
| oid sha256:1c3494e1f10d3c8839d666c2fec151e92b0b17bf96ddb08693db059f02050fbb | |||
| size 170395 |
| oid sha256:9d0420d895f80cb90a3950ebf9d021684b03db28fd1f1a8c450f4a0d72845b6b | |||
| size 271976 |
| oid sha256:cb38da98e5d9761d49b985472cb3ba857fc5f87740417e296a013d2877848ea9 | |||
| size 192147 |
| oid sha256:54234c188544a284d98ce95620428614b56d5d7c599b5f8ad63a990566beb84f | |||
| size 344218 |
| oid sha256:fd78e3b1e6ef70d937368e3b27a794d0091c8ed41f0b3d1e4f5152f79a67b956 | |||
| size 204429 |
| oid sha256:00bef9333b297f6d8b4e56360f7eb45a886f353a9e218adf850e288e2c18b8ba | |||
| size 249950 |
| oid sha256:54fcf614d14a8dd17512a69963bc5807bfd9429a0090fb81842f50c3bf904a1a | |||
| size 205753 |
| oid sha256:515dc49dc92add3ab47d0e1444dbe40ebb147245f6ed738b71037a183ee1c1ea | |||
| size 193869 |
| @@ -350,6 +350,11 @@ a:hover { | |||
| border: none; | |||
| } | |||
| .missing { | |||
| background-color: #444; | |||
| color: #aaa !important; | |||
| } | |||
| #move-up-left { | |||
| position: absolute; | |||
| left: -140px; | |||
| @@ -16,8 +16,8 @@ | |||
| <meta name="description" content="A text adventure!" /> | |||
| <meta property="og:title" content="Satiate" /> | |||
| <meta property="og:description" content="A text adventure!" /> | |||
| <meta property="og:image" content="/satiate.png" /> | |||
| <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" /> | |||
| <meta property="og:image" content="https://crux.sexy/images/satiate.png" /> | |||
| <link rel="icon" href="https://crux.sexy/images/satiate.ico"> | |||
| </head> | |||
| <body> | |||
| @@ -25,6 +25,8 @@ | |||
| <div id="pick-blurb"> | |||
| <p>Welcome to Satiate</p> | |||
| <a href="https://discord.gg/dSwNN8T">Discord Server</a> | |||
| <br> | |||
| <a href="https://crux.sexy/changelog#satiate">Changelog</a> | |||
| <p class="version">Version: </p> | |||
| <p><b>This game contains 18+ content</b></p> | |||
| <p><b>Most of the games have sound!</b></p> | |||
| @@ -3,7 +3,7 @@ | |||
| let activeModal = null; | |||
| const newline = String.fromCharCode(160); | |||
| const version = "0.1.1"; | |||
| const version = "0.1.2"; | |||
| let state; | |||
| @@ -22,6 +22,11 @@ function print(lines) { | |||
| log.scrollTop = log.scrollHeight; | |||
| } | |||
| function printRandom(list) { | |||
| let choice = Math.floor(Math.random() * list.length) | |||
| print(list[choice]) | |||
| } | |||
| function refresh() { | |||
| updateRoom(state); | |||
| updateStatDisplay(state.info, "world"); | |||
| @@ -46,15 +46,14 @@ stories.push({ | |||
| delay: 1000, | |||
| loop: true, | |||
| classes: [ | |||
| "Home" | |||
| ], | |||
| room: "Home", | |||
| }, state); | |||
| }, | |||
| "exit": (room, state) => { | |||
| print(["You are exiting your house"]); | |||
| stopRoomTimers("Home", state); | |||
| stopClassTimers("Home", state); | |||
| }, | |||
| "actions": [ | |||
| { | |||
| @@ -99,9 +98,10 @@ stories.push({ | |||
| }, | |||
| "descend": { | |||
| "target": "Stairs", | |||
| "desc": "Dare you go down the stiars?", | |||
| "desc": "Dare you go down the stairs?", | |||
| "hooks": [ | |||
| (room, state) => { | |||
| (room, exit, state) => { | |||
| console.log(state) | |||
| print(["You're very concerned that you'll fall down all these stairs."]); | |||
| return false; | |||
| } | |||
| @@ -32,6 +32,22 @@ stories.push({ | |||
| state.player.stats.health = {name: "Health", type: "meter", value: 100, min: 0, max: 100, color: "rgb(255,0,0)"}; | |||
| state.player.stats.stamina = {name: "Stamina", type: "meter", value: 100, min: 0, max: 100, color: "rgb(100,255,0)"}; | |||
| startTimer({ | |||
| id: "taunting", | |||
| func: state => { | |||
| printRandom([ | |||
| ["The crux strokes over his snarling gut, enjoying your dwindling squirms."], | |||
| ["\"Enjoying yourself, meal?\"", "Your captor's oh-so-smug voice booms in your ears."] | |||
| ]) | |||
| return 25000 + Math.random() * 10000; | |||
| }, | |||
| delay: 5000, | |||
| loop: true, | |||
| classes: [ | |||
| "alive" | |||
| ] | |||
| }, state); | |||
| startTimer({ | |||
| id: "movement", | |||
| func: state => { | |||
| @@ -79,6 +95,36 @@ stories.push({ | |||
| if (state.player.stats.health.value <= 0) { | |||
| state.player.stats.health.value = 0; | |||
| const start = 86400 - 60 * 25 - 25; | |||
| const end = state.info.time.value; | |||
| const time_survived = end - start; | |||
| const minutes = Math.floor(time_survived / 60); | |||
| const seconds = time_survived % 60; | |||
| const minute_text = minutes + (minutes == 1 ? " minute" : " minutes"); | |||
| const second_text = seconds + (seconds == 1 ? " second" : " seconds"); | |||
| let time_text; | |||
| if (minutes == 0) | |||
| time_text = "You survived for " + second_text + "."; | |||
| else | |||
| time_text = "You survived for " + minute_text + " and " + second_text + "."; | |||
| startTimer({ | |||
| id: "time-text", | |||
| func: state => { | |||
| print([time_text]); | |||
| return true; | |||
| }, | |||
| delay: 2000, | |||
| loop: false, | |||
| classes: [ | |||
| ] | |||
| }, state); | |||
| if (location.startsWith("stomach")) { | |||
| goToRoom("digested-stomach", state); | |||
| } | |||
| @@ -109,11 +155,11 @@ stories.push({ | |||
| let bonus = state.player.flags.submission ? -3 : 0; | |||
| bonus += state.player.flags.pinned ? -0.5 : 0; | |||
| if (location.startsWith("stomach")) { | |||
| changeStat("stamina", 1 + bonus, state); | |||
| changeStat("stamina", 0.75 + bonus, state); | |||
| } | |||
| if (location.startsWith("intestines")) { | |||
| changeStat("stamina", 0.5 + bonus, state); | |||
| changeStat("stamina", 1.25 + bonus, state); | |||
| } | |||
| if (location.startsWith("bowels")) { | |||
| @@ -144,7 +190,7 @@ stories.push({ | |||
| }, state); | |||
| }, | |||
| intro: state => { | |||
| print(["Hot, slimy walls ripple and squeeze, the stomach of your captor stewing you in a churning bath of chyme and acid. The blue crux made a late-night meal out of you with ease. You've only been trapped under the Fen's pelt for a few minutes...and it doesn't seem like you'll last much longer than that, either."]); | |||
| print(["Hot, slimy walls ripple and squeeze, the stomach of your captor stewing you in a churning bath of chyme and acid. The purple crux made a late-night meal out of you with ease. You've only been trapped under the Fen's pelt for a few minutes...but it doesn't seem like you'll make it until midnight."]); | |||
| playSfx("sfx/stomach-churn.ogg"); | |||
| } | |||
| }, | |||
| @@ -174,15 +220,15 @@ stories.push({ | |||
| }, | |||
| delay: 10000, | |||
| loop: true, | |||
| room: "stomach", | |||
| classes: [ | |||
| "alive" | |||
| "alive", | |||
| "stomach" | |||
| ] | |||
| }, state); | |||
| }, | |||
| exit: (room, state) => { | |||
| stopLoop("loop/fen-stomach.ogg"); | |||
| stopRoomTimers("stomach", state); | |||
| stopClassTimers("stomach", state); | |||
| }, | |||
| hooks: [ | |||
| @@ -196,6 +242,22 @@ stories.push({ | |||
| print(["You rub all over your prison's walls. Fen's stomach gurgles in response."]); | |||
| } | |||
| }, | |||
| { | |||
| name: "Thrash", | |||
| desc: "Kick and struggle", | |||
| execute: (room, state) => { | |||
| changeStat("health", -10, state); | |||
| changeStat("stamina", -35, state); | |||
| print(["Your thrash and kick and punch - and, for your insolence, you are rewarded with a crushing CLENCH. Fen's grinding guts smother and squeeze your softening body, nearly popping you like a grape."]); | |||
| playSfx("sfx/stomach-churn.ogg"); | |||
| }, | |||
| show: [ | |||
| ], | |||
| conditions: [ | |||
| ] | |||
| }, | |||
| { | |||
| name: "Submit", | |||
| desc: "Let Fen digest you", | |||
| @@ -264,7 +326,7 @@ stories.push({ | |||
| startTimer({ | |||
| id: "intestines-churns", | |||
| func: state => { | |||
| if (Math.random() > 0.6) { | |||
| if (Math.random() > 0.5) { | |||
| if (state.player.stats.stamina.value > 50) { | |||
| changeStat("stamina", -25, state); | |||
| print(["Your prison's walls ripple and grind, shoving you against the valve leading to the crux's boiling stomach - but you manage to resist the powerful pull."]); | |||
| @@ -274,20 +336,25 @@ stories.push({ | |||
| playSfx("sfx/intestines-to-stomach-forced.ogg"); | |||
| goToRoom("stomach", state); | |||
| } | |||
| } else { | |||
| changeStat("stamina", -25, state); | |||
| print(["Overwhelming peristalsis grips your body and crams it into the beast's hot, life-sapping bowels."]); | |||
| playSfx("sfx/intestines-to-stomach-forced.ogg"); | |||
| goToRoom("bowels", state); | |||
| } | |||
| return true; | |||
| return 10000 + Math.random() * 5000; | |||
| }, | |||
| delay: 10000, | |||
| loop: true, | |||
| room: "intestines", | |||
| classes: [ | |||
| "alive" | |||
| "alive", | |||
| "intestines" | |||
| ] | |||
| }, state); | |||
| }, | |||
| exit: (room, state) => { | |||
| stopLoop("loop/fen-intestines.ogg"); | |||
| stopRoomTimers("intestines", state); | |||
| stopClassTimers("intestines", state); | |||
| }, | |||
| exits: { | |||
| "up": { | |||
| @@ -332,7 +399,7 @@ stories.push({ | |||
| desc: "Knead on the muscular folds that surround your tasty little body", | |||
| execute: (room, state) => { | |||
| changeStat("stamina", -20, state); | |||
| print(["You rub all over your prison's walls. Fen's guts barely move."]); | |||
| print(["You rub all over your prison's walls. Fen's intestines quiver and clench in response."]); | |||
| } | |||
| }, | |||
| { | |||
| @@ -340,7 +407,7 @@ stories.push({ | |||
| desc: "Let Fen digest you", | |||
| execute: (room, state) => { | |||
| state.player.flags.submission = true; | |||
| print(["You go limp in the crux's intestines, letting the winding, worryingly-tight guts take you in..."]); | |||
| print(["You go limp in the crux's intestines, letting the winding guts take you in..."]); | |||
| }, | |||
| show: [ | |||
| (room, state) => { | |||
| @@ -385,9 +452,9 @@ stories.push({ | |||
| }, | |||
| delay: 1000, | |||
| loop: true, | |||
| room: "bowels", | |||
| classes: [ | |||
| "alive" | |||
| "alive", | |||
| "bowels" | |||
| ] | |||
| }, state); | |||
| @@ -399,15 +466,15 @@ stories.push({ | |||
| }, | |||
| delay: 10000, | |||
| loop: true, | |||
| room: "bowels", | |||
| classes: [ | |||
| "alive" | |||
| "alive", | |||
| "bowels" | |||
| ] | |||
| }, state); | |||
| }, | |||
| exit: (room, state) => { | |||
| stopLoop("loop/fen-bowels.ogg"); | |||
| stopRoomTimers("bowels", state); | |||
| stopClassTimers("bowels", state); | |||
| }, | |||
| exits: { | |||
| "up": { | |||
| @@ -435,7 +502,33 @@ stories.push({ | |||
| return true; | |||
| } | |||
| ] | |||
| } | |||
| }, | |||
| "down": { | |||
| "target": "freedom", | |||
| "desc": "Try to squirm out of the beast", | |||
| "show": [ | |||
| ], | |||
| "conditions": [ | |||
| (room, state) => { | |||
| return !state.player.flags.submission; | |||
| }, | |||
| (room, state) => { | |||
| return !state.player.flags.pinned; | |||
| }, | |||
| (room, state) => { | |||
| return state.player.stats.stamina.value > 25; | |||
| } | |||
| ], | |||
| "hooks": [ | |||
| (room, exit, state) => { | |||
| print(["You make a desperate bid for freedom, grasping at the walls of Fen's sweltering bowels and dragging yourself forward. The predator growls in pleasure...and, after humoring your squirms for a long minute, crushes you within an inch of your life with a swift, staggeringly-strong clench. Every inch of escape is erased in a heartbeat...as is any hope of ending the evening as anything other than a permanent contribution to the crux's body."]); | |||
| changeStat("stamina", -100, state); | |||
| return false; | |||
| } | |||
| ] | |||
| }, | |||
| }, | |||
| hooks: [ | |||
| @@ -470,6 +563,34 @@ stories.push({ | |||
| } | |||
| } | |||
| }, | |||
| "freedom": { | |||
| "id": "freedom", | |||
| "name": "Freedom", | |||
| "desc": "A place you'll never reach!", | |||
| "move": (room, state) => { | |||
| }, | |||
| "enter": (room, state) => { | |||
| }, | |||
| "exit": (room, state) => { | |||
| }, | |||
| "actions": [ | |||
| ], | |||
| "exits": { | |||
| }, | |||
| "hooks": [ | |||
| ], | |||
| "data": { | |||
| "stats": { | |||
| } | |||
| } | |||
| }, | |||
| "digested-stomach": { | |||
| id: "digested-stomach", | |||
| name: "Fen's Belly", | |||
| @@ -513,7 +634,7 @@ stories.push({ | |||
| playSfx("sfx/digested-test.ogg"); | |||
| playSfx("sfx/bowels-churn-danger.ogg"); | |||
| stopClassTimers("alive", state); | |||
| print(["A powerful ripple of muscle pins you in a vice-grip of flesh - and within seconds, you're part of Fen's bowels.",newline,"Nothing's left but a bit of padding on your predator's ass..."]); | |||
| print(["A powerful ripple of muscle pins you in a vice-grip of flesh. Your exhausted form is molded like putty..and then, over the course of no more than seconds, Fen's bowels assimilate you. Numbness races through your body as you're absorbed like water into a sponge.",newline,"Nothing's left of you beyond some heft on his ass..."]); | |||
| }, | |||
| "data": { | |||
| "stats": { | |||
| @@ -40,7 +40,7 @@ function resetControls(state) { | |||
| const button = document.createElement("button"); | |||
| button.classList.add("move-button") | |||
| button.id = "move-" + dir; | |||
| button.classList.add("disabled"); | |||
| button.classList.add("missing"); | |||
| button.setAttribute("disabled", "true"); | |||
| button.textContent = dirs[dir]; | |||
| moveHolder.appendChild(button); | |||
| @@ -177,6 +177,8 @@ function updateRoom(state) { | |||
| } | |||
| } | |||
| button.classList.remove("missing"); | |||
| button.classList.add("disabled"); | |||
| button.textContent = dest.name; | |||
| // if any condition fails, don't enable/add a listener | |||