├── .gitignore ├── README.md ├── audios ├── bgm │ ├── 132546__theworkingbamboo__etude.ogg │ └── the_field_of_dreams.ogg └── sound │ ├── 336545__giddster__closing-door.ogg │ ├── plasterbrain_Cute_Anime_Jumps.mp3 │ ├── 关灯.ogg │ ├── 关门.ogg │ ├── 哼(小生气).ogg │ ├── 嗯哼哼~~.ogg │ ├── 开水.ogg │ ├── 思考.ogg │ ├── 打脸(啪啪).ogg │ ├── 拉屎.ogg │ ├── 拉屎音.ogg │ ├── 欢呼.ogg │ ├── 电报.ogg │ ├── 稍微生气.ogg │ └── 鼓掌.ogg ├── extension_cuna.ink ├── extern_function.ink ├── images ├── pexels-manuel-geissinger-325229.jpg ├── 粉色.png ├── 绿色.png ├── 绿色M.jpg └── 蓝色.png ├── index.html ├── ink-full.js ├── main.js ├── nt3club_game.ink └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | audios/bgm/the_field_of_dreams.mp3 3 | audios/sound/plasterbrain_Cute_Anime_Jumps.flac 4 | audios/bgm/132546__theworkingbamboo__etude.mp3 5 | *.mp3 6 | audios/sound/Telegram_Tone.ogg 7 | audios/sound/plasterbrain_Cute_Anime_Jumps.ogg 8 | *.wav 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TraceWindGame 2 | 3 | ## 简介 4 | 5 | 这是一款以 TraceWind 为主角的文字冒险游戏,基于 [Ink](https://www.inklestudios.com/ink/) 引擎开发。 6 | 7 | 项目仍在开发中。 8 | 9 | ## 获取 10 | 11 | 克隆本仓库获取游戏脚本: 12 | 13 | ```bash 14 | git clone https://github.com/NT3Games/TraceWindGame.git 15 | cd TraceWindGame 16 | ``` 17 | 18 | 或在 [Releases](https://github.com/NT3Games/TraceWindGame/releases) 页面下载对应版本的源码。 19 | 20 | ### 升级 21 | 22 | 克隆本仓库后,进入仓库根目录,运行: 23 | 24 | ```bash 25 | git pull origin main 26 | ``` 27 | 28 | 或在 [Releases](https://github.com/NT3Games/TraceWindGame/releases/latest) 页面下载最新版本的源码。 29 | 30 | ## 使用 31 | 32 | ### Inky 等官方工具 33 | 34 | ink 官方提供了诸多可以运行本项目的工具(例如 [Inky](https://github.com/inkle/inky))。参见[本文档](https://github.com/inkle/ink#ink-inky-ink-unity-integration-inkjs-inklecate-inkle-oh-my)以了解官方工具的用法。 35 | 36 | 37 | 38 | ### Web 服务器 39 | 40 | 获取项目源码后,可以使用 Web 服务器加载本项目。以 Arch Linux 环境下的 Nginx 为例: 41 | 42 | 在终端中运行 `sudo pacman -S nginx` 安装 Nginx。再使用偏好的编辑器打开 Nginx 配置文件(例如 `sudo vim /etc/nginx/nginx.conf`),在默认的 `http` 域内添加如下条目: 43 | 44 | ```nginx 45 | server { 46 | listen 8080; # 端口任选 47 | server_name localhost; 48 | location / { 49 | root /usr/share/nginx/TraceWindGame; # 源码路径,不建议随意修改权限 50 | index index.html; 51 | } 52 | } 53 | ``` 54 | 55 | 将源码复制到指定路径处: 56 | 57 | ```bash 58 | sudo cp -r TraceWindGame /usr/share/nginx 59 | ``` 60 | 61 | 确认无误后,启动 Nginx 服务: 62 | 63 | ```bash 64 | sudo systemctl start nginx 65 | ``` 66 | 67 | 在浏览器中访问 http://localhost:8080 即可。 68 | 69 | ## 演示 70 | 71 | 72 | 73 | 备用地址: 74 | 75 | ## 贡献 76 | 77 | 为保证游戏脚本安全性,本项目暂不允许项目制作组外人员进入实时协作平台,但可以通过提交 issues 或 pull requests 参与贡献。 78 | 79 | ## 开源许可证 80 | 81 | ### ink 82 | 83 | 84 | 85 | ```text 86 | MIT License 87 | 88 | Copyright (c) 2017 inkle Ltd. 89 | 90 | Permission is hereby granted, free of charge, to any person obtaining a copy 91 | of this software and associated documentation files (the "Software"), to deal 92 | in the Software without restriction, including without limitation the rights 93 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 94 | copies of the Software, and to permit persons to whom the Software is 95 | furnished to do so, subject to the following conditions: 96 | 97 | The above copyright notice and this permission notice shall be included in all 98 | copies or substantial portions of the Software. 99 | 100 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 101 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 102 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 103 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 104 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 105 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 106 | SOFTWARE. 107 | ``` 108 | 109 | ### 本项目 110 | 111 | 本项目(包括但不限于游戏脚本)的一切权利归 [NT3 Games](https://github.com/NT3Games) 全体成员所有。 112 | -------------------------------------------------------------------------------- /audios/bgm/132546__theworkingbamboo__etude.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/audios/bgm/132546__theworkingbamboo__etude.ogg -------------------------------------------------------------------------------- /audios/bgm/the_field_of_dreams.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/audios/bgm/the_field_of_dreams.ogg -------------------------------------------------------------------------------- /audios/sound/336545__giddster__closing-door.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/audios/sound/336545__giddster__closing-door.ogg -------------------------------------------------------------------------------- /audios/sound/plasterbrain_Cute_Anime_Jumps.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/audios/sound/plasterbrain_Cute_Anime_Jumps.mp3 -------------------------------------------------------------------------------- /audios/sound/关灯.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/audios/sound/关灯.ogg -------------------------------------------------------------------------------- /audios/sound/关门.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/audios/sound/关门.ogg -------------------------------------------------------------------------------- /audios/sound/哼(小生气).ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/audios/sound/哼(小生气).ogg -------------------------------------------------------------------------------- /audios/sound/嗯哼哼~~.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/audios/sound/嗯哼哼~~.ogg -------------------------------------------------------------------------------- /audios/sound/开水.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/audios/sound/开水.ogg -------------------------------------------------------------------------------- /audios/sound/思考.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/audios/sound/思考.ogg -------------------------------------------------------------------------------- /audios/sound/打脸(啪啪).ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/audios/sound/打脸(啪啪).ogg -------------------------------------------------------------------------------- /audios/sound/拉屎.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/audios/sound/拉屎.ogg -------------------------------------------------------------------------------- /audios/sound/拉屎音.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/audios/sound/拉屎音.ogg -------------------------------------------------------------------------------- /audios/sound/欢呼.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/audios/sound/欢呼.ogg -------------------------------------------------------------------------------- /audios/sound/电报.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/audios/sound/电报.ogg -------------------------------------------------------------------------------- /audios/sound/稍微生气.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/audios/sound/稍微生气.ogg -------------------------------------------------------------------------------- /audios/sound/鼓掌.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/audios/sound/鼓掌.ogg -------------------------------------------------------------------------------- /extension_cuna.ink: -------------------------------------------------------------------------------- 1 | === cuna === 2 | 3 | = eat_poop 4 | 5 | 风痕走向了厕所,看到了还没冲的屎, 6 | 7 | * [【继续阅读】] 8 | - 9 | * [【继续阅读】] 10 | 11 | - 你已断开与风痕世界的连接…… 12 | 13 | -> ENDs 14 | -------------------------------------------------------------------------------- /extern_function.ink: -------------------------------------------------------------------------------- 1 | EXTERNAL console_log(x) 2 | EXTERNAL obtained_ending(name) 3 | EXTERNAL new_ending(name) 4 | EXTERNAL clear_ending() 5 | 6 | === function obtained_ending(name) === 7 | ~ return false 8 | 9 | === function new_ending(name) === 10 | ~ return 11 | 12 | === function clear_ending() === 13 | ~ return 14 | 15 | === function console_log(x) === 16 | ~ return 17 | -------------------------------------------------------------------------------- /images/pexels-manuel-geissinger-325229.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/images/pexels-manuel-geissinger-325229.jpg -------------------------------------------------------------------------------- /images/粉色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/images/粉色.png -------------------------------------------------------------------------------- /images/绿色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/images/绿色.png -------------------------------------------------------------------------------- /images/绿色M.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/images/绿色M.jpg -------------------------------------------------------------------------------- /images/蓝色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NT3Games/TraceWindGame/563a94e37c0d4a072a5cb5f955cb626f13e0689a/images/蓝色.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 风之痕 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |

WRITTEN IN INK

19 | 20 |
21 | restart 22 | save 23 | load 24 | theme 25 |
26 | 27 |
28 |
29 |

风之痕

30 | 31 |
32 |
33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const FILES = ['nt3club_game.ink', 'extension_cuna.ink', 'extern_function.ink'] 2 | 3 | // compile from https://github.com/y-lohse/inkjs/blob/master/src/compiler/FileHandler/JsonFileHandler.ts 4 | class JsonFileHandler { 5 | constructor(fileHierarchy) { 6 | this.fileHierarchy = fileHierarchy; 7 | this.ResolveInkFilename = (filename) => { 8 | if (Object.keys(this.fileHierarchy).includes(filename)) 9 | return filename; 10 | throw new Error(`Cannot locate ${filename}. Are you trying a relative import ? This is not yet implemented.`); 11 | }; 12 | this.LoadInkFileContents = (filename) => { 13 | if (Object.keys(this.fileHierarchy).includes(filename)) { 14 | return this.fileHierarchy[filename]; 15 | } 16 | else { 17 | throw new Error(`Cannot open ${filename}.`); 18 | } 19 | }; 20 | } 21 | } 22 | 23 | function CreateAudio() { 24 | let audio = undefined; 25 | 26 | function cancelAudio() { 27 | if (audio instanceof Audio) { 28 | audio.pause(); 29 | audio.removeAttribute('src'); 30 | audio.load(); 31 | } 32 | } 33 | 34 | return ( 35 | function applyAudio(appendProperties = {}) { 36 | cancelAudio(); 37 | 38 | audio = new Audio(); 39 | Object.assign(audio, appendProperties); 40 | audio.load(); 41 | audio.play(); 42 | } 43 | ) 44 | } 45 | 46 | (async function () { 47 | let inks = {}; 48 | 49 | for (const filename of FILES) { 50 | inks[filename] = await fetch(filename).then(res => res.text()) 51 | } 52 | 53 | let options = new inkjs.CompilerOptions(null, [], false, null, new JsonFileHandler(inks)) 54 | var story = new inkjs.Compiler(inks['nt3club_game.ink'], options).Compile(); 55 | 56 | story.BindExternalFunction("obtained_ending", function (name) { 57 | try { 58 | let endings_str = window.localStorage.getItem('endings') || '[]'; 59 | let endings = JSON.parse(endings_str); 60 | return endings.includes(name); 61 | } catch (e) { 62 | console.debug("Couldn't load save state"); 63 | return false; 64 | } 65 | }, true); 66 | 67 | story.BindExternalFunction("new_ending", function (name) { 68 | try { 69 | let endings_str = window.localStorage.getItem('endings') || '[]'; 70 | let endings = new Set(JSON.parse(endings_str)); 71 | endings.add(name); 72 | window.localStorage.setItem('endings', JSON.stringify(Array.from(endings))) 73 | } catch (e) { 74 | console.debug("Couldn't load save state"); 75 | } 76 | }, false); 77 | 78 | story.BindExternalFunction("clear_ending", function () { 79 | try { 80 | window.localStorage.setItem('endings', '[]') 81 | } catch (e) { 82 | console.debug("Couldn't load save state"); 83 | } 84 | }, false); 85 | 86 | story.BindExternalFunction("console_log", function (text) { 87 | console.log(text) 88 | }, false); 89 | 90 | var savePoint = ""; 91 | 92 | let savedTheme; 93 | let globalTagTheme; 94 | 95 | // Global tags - those at the top of the ink file 96 | // We support: 97 | // # theme: dark 98 | // # author: Your Name 99 | var globalTags = story.globalTags; 100 | if (globalTags) { 101 | for (var i = 0; i < story.globalTags.length; i++) { 102 | var globalTag = story.globalTags[i]; 103 | var splitTag = splitPropertyTag(globalTag); 104 | 105 | // THEME: dark 106 | if (splitTag && splitTag.property == "theme") { 107 | globalTagTheme = splitTag.val; 108 | } 109 | 110 | // author: Your Name 111 | else if (splitTag && splitTag.property == "author") { 112 | var byline = document.querySelector('.byline'); 113 | byline.innerHTML = "by " + splitTag.val; 114 | } 115 | } 116 | } 117 | 118 | var storyContainer = document.querySelector('#story'); 119 | var outerScrollContainer = document.querySelector('.outerContainer'); 120 | 121 | // page features setup 122 | setupTheme(globalTagTheme); 123 | var hasSave = loadSavePoint(); 124 | setupButtons(hasSave); 125 | 126 | // Set initial save point 127 | savePoint = story.state.toJson(); 128 | 129 | // init audio 130 | const applyAudio = CreateAudio(); 131 | 132 | // Kick off the start of the story! 133 | continueStory(true); 134 | 135 | // Main story processing function. Each time this is called it generates 136 | // all the next content up as far as the next set of choices. 137 | function continueStory(firstTime) { 138 | 139 | var paragraphIndex = 0; 140 | var delay = 0.0; 141 | 142 | // Don't over-scroll past new content 143 | var previousBottomEdge = firstTime ? 0 : contentBottomEdgeY(); 144 | 145 | // Generate story text - loop through available content 146 | while (story.canContinue) { 147 | 148 | // Get ink to generate the next paragraph 149 | var paragraphText = story.Continue(); 150 | var tags = story.currentTags; 151 | 152 | // Any special tags included with this line 153 | var customClasses = []; 154 | for (var i = 0; i < tags.length; i++) { 155 | var tag = tags[i]; 156 | 157 | // Detect tags of the form "X: Y". Currently used for IMAGE and CLASS but could be 158 | // customised to be used for other things too. 159 | var splitTag = splitPropertyTag(tag); 160 | 161 | // AUDIO: src 162 | if (splitTag && splitTag.property == "AUDIO") { 163 | applyAudio({ src: splitTag.val, loop: false }); 164 | } 165 | 166 | // AUDIOLOOP: src 167 | else if (splitTag && splitTag.property == "AUDIOLOOP") { 168 | applyAudio({ src: splitTag.val, loop: true }); 169 | } 170 | 171 | // IMAGE: src 172 | if (splitTag && splitTag.property == "IMAGE") { 173 | var imageElement = document.createElement('img'); 174 | imageElement.src = splitTag.val; 175 | storyContainer.appendChild(imageElement); 176 | 177 | showAfter(delay, imageElement); 178 | delay += 200.0; 179 | } 180 | 181 | // LINK: url 182 | else if (splitTag && splitTag.property == "LINK") { 183 | window.location.href = splitTag.val; 184 | } 185 | 186 | // LINKOPEN: url 187 | else if (splitTag && splitTag.property == "LINKOPEN") { 188 | window.open(splitTag.val); 189 | } 190 | 191 | // BACKGROUND: src 192 | else if (splitTag && splitTag.property == "BACKGROUND") { 193 | outerScrollContainer.style.backgroundImage = 'url(' + splitTag.val + ')'; 194 | } 195 | 196 | // CLASS: className 197 | else if (splitTag && splitTag.property == "CLASS") { 198 | customClasses.push(splitTag.val); 199 | } 200 | 201 | // CLEAR - removes all existing content. 202 | // RESTART - clears everything and restarts the story from the beginning 203 | else if (tag == "CLEAR" || tag == "RESTART") { 204 | removeAll("p"); 205 | removeAll("img"); 206 | 207 | // Comment out this line if you want to leave the header visible when clearing 208 | // setVisible(".header", false); 209 | 210 | if (tag == "RESTART") { 211 | restart(); 212 | return; 213 | } 214 | } 215 | } 216 | 217 | // Create paragraph element (initially hidden) 218 | var paragraphElement = document.createElement('p'); 219 | paragraphElement.innerHTML = paragraphText; 220 | storyContainer.appendChild(paragraphElement); 221 | 222 | // Add any custom classes derived from ink tags 223 | for (var i = 0; i < customClasses.length; i++) 224 | paragraphElement.classList.add(customClasses[i]); 225 | 226 | // Fade in paragraph after a short delay 227 | showAfter(delay, paragraphElement); 228 | delay += 200.0; 229 | } 230 | 231 | // Create HTML choices from ink choices 232 | story.currentChoices.forEach(function (choice) { 233 | 234 | // Create paragraph with anchor element 235 | var choiceParagraphElement = document.createElement('p'); 236 | choiceParagraphElement.classList.add("choice"); 237 | choiceParagraphElement.innerHTML = `${choice.text}` 238 | storyContainer.appendChild(choiceParagraphElement); 239 | 240 | // Fade choice in after a short delay 241 | showAfter(delay, choiceParagraphElement); 242 | delay += 200.0; 243 | 244 | // Click on choice 245 | var choiceAnchorEl = choiceParagraphElement.querySelectorAll("a")[0]; 246 | choiceAnchorEl.addEventListener("click", function (event) { 247 | 248 | // Don't follow link 249 | event.preventDefault(); 250 | 251 | // Remove all existing choices 252 | removeAll(".choice"); 253 | 254 | // Tell the story where to go next 255 | story.ChooseChoiceIndex(choice.index); 256 | 257 | // This is where the save button will save from 258 | savePoint = story.state.toJson(); 259 | 260 | // Aaand loop 261 | continueStory(); 262 | }); 263 | }); 264 | 265 | // Extend height to fit 266 | // We do this manually so that removing elements and creating new ones doesn't 267 | // cause the height (and therefore scroll) to jump backwards temporarily. 268 | storyContainer.style.height = contentBottomEdgeY() + "px"; 269 | 270 | if (!firstTime) 271 | scrollDown(previousBottomEdge); 272 | 273 | } 274 | 275 | function restart() { 276 | story.ResetState(); 277 | 278 | setVisible(".header", true); 279 | 280 | // set save point to here 281 | savePoint = story.state.toJson(); 282 | 283 | continueStory(true); 284 | 285 | outerScrollContainer.scrollTo(0, 0); 286 | } 287 | 288 | // ----------------------------------- 289 | // Various Helper functions 290 | // ----------------------------------- 291 | 292 | // Fades in an element after a specified delay 293 | function showAfter(delay, el) { 294 | el.classList.add("hide"); 295 | setTimeout(function () { el.classList.remove("hide") }, delay); 296 | } 297 | 298 | // Scrolls the page down, but no further than the bottom edge of what you could 299 | // see previously, so it doesn't go too far. 300 | function scrollDown(previousBottomEdge) { 301 | 302 | // Line up top of screen with the bottom of where the previous content ended 303 | var target = previousBottomEdge; 304 | 305 | // Can't go further than the very bottom of the page 306 | var limit = outerScrollContainer.scrollHeight - outerScrollContainer.clientHeight; 307 | if (target > limit) target = limit; 308 | 309 | var start = outerScrollContainer.scrollTop; 310 | 311 | var dist = target - start; 312 | var duration = 300 + 300 * dist / 100; 313 | var startTime = null; 314 | function step(time) { 315 | if (startTime == null) startTime = time; 316 | var t = (time - startTime) / duration; 317 | var lerp = 3 * t * t - 2 * t * t * t; // ease in/out 318 | outerScrollContainer.scrollTo(0, (1.0 - lerp) * start + lerp * target); 319 | if (t < 1) requestAnimationFrame(step); 320 | } 321 | requestAnimationFrame(step); 322 | } 323 | 324 | // The Y coordinate of the bottom end of all the story content, used 325 | // for growing the container, and deciding how far to scroll. 326 | function contentBottomEdgeY() { 327 | var bottomElement = storyContainer.lastElementChild; 328 | return bottomElement ? bottomElement.offsetTop + bottomElement.offsetHeight : 0; 329 | } 330 | 331 | // Remove all elements that match the given selector. Used for removing choices after 332 | // you've picked one, as well as for the CLEAR and RESTART tags. 333 | function removeAll(selector) { 334 | var allElements = storyContainer.querySelectorAll(selector); 335 | for (var i = 0; i < allElements.length; i++) { 336 | var el = allElements[i]; 337 | el.parentNode.removeChild(el); 338 | } 339 | } 340 | 341 | // Used for hiding and showing the header when you CLEAR or RESTART the story respectively. 342 | function setVisible(selector, visible) { 343 | var allElements = storyContainer.querySelectorAll(selector); 344 | for (var i = 0; i < allElements.length; i++) { 345 | var el = allElements[i]; 346 | if (!visible) 347 | el.classList.add("invisible"); 348 | else 349 | el.classList.remove("invisible"); 350 | } 351 | } 352 | 353 | // Helper for parsing out tags of the form: 354 | // # PROPERTY: value 355 | // e.g. IMAGE: source path 356 | function splitPropertyTag(tag) { 357 | var propertySplitIdx = tag.indexOf(":"); 358 | if (propertySplitIdx != null) { 359 | var property = tag.substr(0, propertySplitIdx).trim(); 360 | var val = tag.substr(propertySplitIdx + 1).trim(); 361 | return { 362 | property: property, 363 | val: val 364 | }; 365 | } 366 | 367 | return null; 368 | } 369 | 370 | // Loads save state if exists in the browser memory 371 | function loadSavePoint() { 372 | 373 | try { 374 | let savedState = window.localStorage.getItem('save-state'); 375 | if (savedState) { 376 | story.state.LoadJson(savedState); 377 | return true; 378 | } 379 | } catch (e) { 380 | console.debug("Couldn't load save state"); 381 | } 382 | return false; 383 | } 384 | 385 | // Detects which theme (light or dark) to use 386 | function setupTheme(globalTagTheme) { 387 | 388 | // load theme from browser memory 389 | var savedTheme; 390 | try { 391 | savedTheme = window.localStorage.getItem('theme'); 392 | } catch (e) { 393 | console.debug("Couldn't load saved theme"); 394 | } 395 | 396 | // Check whether the OS/browser is configured for dark mode 397 | var browserDark = window.matchMedia("(prefers-color-scheme: dark)").matches; 398 | 399 | if (savedTheme === "dark" 400 | || (savedTheme == undefined && globalTagTheme === "dark") 401 | || (savedTheme == undefined && globalTagTheme == undefined && browserDark)) 402 | document.body.classList.add("dark"); 403 | } 404 | 405 | // Used to hook up the functionality for global functionality buttons 406 | function setupButtons(hasSave) { 407 | 408 | let rewindEl = document.getElementById("rewind"); 409 | if (rewindEl) rewindEl.addEventListener("click", function (event) { 410 | removeAll("p"); 411 | removeAll("img"); 412 | setVisible(".header", false); 413 | restart(); 414 | }); 415 | 416 | let saveEl = document.getElementById("save"); 417 | if (saveEl) saveEl.addEventListener("click", function (event) { 418 | try { 419 | window.localStorage.setItem('save-state', savePoint); 420 | document.getElementById("reload").removeAttribute("disabled"); 421 | window.localStorage.setItem('theme', document.body.classList.contains("dark") ? "dark" : ""); 422 | } catch (e) { 423 | console.warn("Couldn't save state"); 424 | } 425 | 426 | }); 427 | 428 | let reloadEl = document.getElementById("reload"); 429 | if (!hasSave) { 430 | reloadEl.setAttribute("disabled", "disabled"); 431 | } 432 | reloadEl.addEventListener("click", function (event) { 433 | if (reloadEl.getAttribute("disabled")) 434 | return; 435 | 436 | removeAll("p"); 437 | removeAll("img"); 438 | try { 439 | let savedState = window.localStorage.getItem('save-state'); 440 | if (savedState) story.state.LoadJson(savedState); 441 | } catch (e) { 442 | console.debug("Couldn't load save state"); 443 | } 444 | continueStory(true); 445 | }); 446 | 447 | let themeSwitchEl = document.getElementById("theme-switch"); 448 | if (themeSwitchEl) themeSwitchEl.addEventListener("click", function (event) { 449 | document.body.classList.add("switched"); 450 | document.body.classList.toggle("dark"); 451 | }); 452 | } 453 | 454 | })(); 455 | -------------------------------------------------------------------------------- /nt3club_game.ink: -------------------------------------------------------------------------------- 1 | # title: 风之痕 2 | # author: NT3 Games 3 | # theme: dark 4 | // 游戏地址:https://tracewindgame.netlify.app // 5 | // 流程图:https://hackmd.io/H3N_GcVcRAW5zvNTY41Kzg?view 6 | 7 | // All of including file should list in the main.js FILES array. 8 | INCLUDE extension_cuna.ink 9 | INCLUDE extern_function.ink 10 | 11 | VAR extension = false 12 | VAR sound = false 13 | VAR splash = 0 14 | 15 | -> start 16 | 17 | === start === 18 | 19 | 风之痕 0.1.2-rc.2{extension:-扩展分支} 20 | 21 | ~ splash = RANDOM(1, 6) 22 | 23 | // { splash : 24 | // - 1: 1% sugar! 25 | // - 2: 100% pure! 26 | // - 3: 150 bpm for 400000 minutes! 27 | // - 4: 90% bug free! 28 | // - 5: Afraid of the big, black bat! 29 | // - 6: Kind of dragon free! 30 | // - else: 错误 31 | // } 32 | 33 | // # IMAGE: images/pexels-manuel-geissinger-325229.jpg 34 | // 只是测试用的图片,之后会替换为游戏封面? 35 | // 图片来自:https://www.pexels.com/photo/black-server-racks-on-a-room-325229/ 36 | 37 | { sound == true : 38 | (游戏已添加音效,请注意音量。) 39 | } 40 | 41 | + [开始游戏] 42 | -> episode_1 43 | + [已解锁结局] 44 | -> endings 45 | + [设置] 46 | -> setting 47 | + [关于游戏] 48 | -> about 49 | + [开发者模式] 50 | -> debug_mod 51 | 52 | === about === 53 | 54 | # CLEAR 55 | # CLASS: menu_title 56 | 关于 57 | 58 | 改编自真人真事,部分内容属虚构。 59 | 60 |
61 | 62 | 此为开源游戏。
63 | GitHub: https:\/\/github.com\/NT3Games\/TraceWindGame 64 | 65 |
66 | 67 | # CLASS: menu_title 68 | 制作人员名单 69 | 70 | # CLASS: staff_text 71 | NT³ 72 | 73 | # CLASS: staff_text 74 | TraceWind 75 | 76 | # CLASS: staff_text 77 | LittleYe233 78 | 79 | # CLASS: staff_text 80 | 可名 81 | 82 | # CLASS: staff_text 83 | gledos 84 | 85 | # CLASS: menu_title 86 | 特别鸣谢 87 | 88 | # CLASS: staff_text 89 | 「NT³的众吹众擂」群组的全体成员 90 | 91 | + [回到标题页面] 92 | # CLEAR 93 | -> start 94 | 95 | === setting === 96 | 97 | # CLEAR 98 | # CLASS: menu_title 99 | 设置 100 | 101 | 扩展分支:{extension:开|关};语音支持:{sound:开|关} 102 | 103 | + {not extension} [打开 扩展分支] 104 | 警告:扩展分支包含猎奇、超展开等内容,不建议首次游玩时打开。 105 | 106 | + + [确定] 107 | ~ extension = true 108 | # CLEAR 109 | -> setting 110 | + + [取消] 111 | # CLEAR 112 | -> setting 113 | 114 | + {extension} [关闭 扩展分支] 115 | 警告:扩展分支包含猎奇、超展开等内容,不建议首次游玩时打开。 116 | 117 | ~ extension = false 118 | # CLEAR 119 | -> setting 120 | 121 | + {not sound} [打开 语音支持] 122 | 注意:由于音效是及时加载的,所以网络延迟较高时体验会较差。 123 | 124 | + + [确定] 125 | ~ sound = true 126 | # CLEAR 127 | -> setting 128 | + + [取消] 129 | # CLEAR 130 | -> setting 131 | 132 | + {sound} [关闭 语音支持] 133 | 注意:由于音效是及时加载的,所以网络延迟较高时体验会较差。 134 | 135 | ~ sound = false 136 | # CLEAR 137 | -> setting 138 | 139 | + [清空已解锁结局] 140 | 警告:此举将会清空所有已解锁的结局和病历条目,其无法恢复! 141 | 142 | + + [确定] 143 | ~ clear_ending() 144 | # CLEAR 145 | -> setting 146 | + + [取消] 147 | # CLEAR 148 | -> setting 149 | 150 | + [回到标题页面] 151 | # CLEAR 152 | -> start 153 | 154 | 155 | === endings === 156 | 157 | # CLASS: menu_title 158 | 已解锁结局 159 | 160 | + {obtained_ending("sleeping")} 161 | 睡美人 # CLEAR 162 | # IMAGE: images/粉色.png 163 | + {obtained_ending("yuki")} 164 | 汤化雪 # CLEAR 165 | 病历条目:忌冲热水凉、泡热水澡。 166 | # IMAGE: images/蓝色.png 167 | + {obtained_ending("butterfly")} 168 | 羽化梦 # CLEAR 169 | 病历条目:忌食高脂肪、酒、油炸食品。 170 | # IMAGE: images/绿色.png 171 | + [回到标题页面] 172 | # CLEAR 173 | # RESTART 174 | -> start 175 | - -> endings 176 | 177 | === episode_1 === 178 | 179 | // 开幕,这里是游戏开始后的第一个场景 180 | 181 | // # AUDIOLOOP: audios/bgm/132546__theworkingbamboo__etude.ogg 182 | // 音乐来自:https://freesound.org/people/TheWorkingBamboo/sounds/132546/ 183 | 184 | 我全知!所以知道屎。
我全能!所以能拉屎! 185 | 186 | 住院长达46天,今日终于结束的风痕,回到了家。 187 | 188 | + 「我回来了!」 189 | 190 | - ……………… 191 | 192 | { sound == true : 193 | # AUDIO: audios/sound/关门.ogg 194 | } 195 | 196 | 风痕笑着,放下装着病历、CT、需按时服用的药与几张收据的挎包后,打开了家中的窗。 197 | 198 | 家里凝固了一个半月的空气霎时间从窗冲出。 199 | 200 | 风痕望了望坐在床头上的小屎屎公仔,它一如既往地笑着看着风痕。 201 | 202 | 风痕笑道: 203 | 204 | + 「嗯哼哼~~ 从今天开始,每天都有很多时间呢!」 205 | 206 | { sound == true : 207 | # AUDIO: audios/sound/嗯哼哼~~.ogg 208 | } 209 | 210 | - 风痕自言自语:
「还有很多事情想做呢!」
「嗯……但不知道从什么开始做起?」 211 | 212 | VAR pooping = false 213 | 214 | -> episode_1.stage_1 215 | 216 | = stage_1 217 | 218 | * (poop) \ {tg or update:该拉屎了!|总而言之,先拉屎吧!} 219 | 220 | ~ pooping = true 221 | 222 | 风痕的家不大,以风痕的身高来看,走个4步不到便会碰到墙角。 223 | 224 | 供她拉屎的厕所,就在入口的右侧。 225 | 226 | + + 「嗯……?」 227 | 228 | - - 这不是美少女游戏吗?怎么会拉屎呢?
算了…… 229 | 230 | 风痕脱下了小胖次,坐了下来。 231 | 232 | 想看什么的人,什么也没能看到。 233 | 234 | 风痕在马桶上伸了伸懒腰: 235 | 236 | + + 「还是自家的屎坑拉起来最舒服!!!>_<」 237 | 238 | - -   239 | 240 | { sound == true : 241 | # AUDIO: audios/sound/拉屎.ogg 242 | } 243 | 244 | {tg or update: 245 | 拉完屎后,风痕回到了房间。 246 | ~ pooping = false 247 | - else: 248 | -> episode_1.poop_owari 249 | } 250 | 251 | -> episode_1.stage_1 252 | 253 | * (tg) {not (tg or update)} 对了对了,要上tg找群友拉屎 254 | 255 | { sound == true : 256 | # AUDIO: audios/sound/电报.ogg 257 | } 258 | 259 | 260 | 打开tg的风痕,快速阅览了几个群组。 261 | 262 | 今天也是没人@她的一天。 263 | 264 | 她去几个常去的群组,发了几组表情与『早上好』,还回复了好几句话后,
盯着屏幕空看了几分钟。 265 | 266 | {not pooping: -> episode_1.stage_1} 267 | 268 | + + 「小心痔疮!是时候出厕所了」 269 | ~ pooping = false 270 | 271 | + + + [之前说住院结束后,要直播游戏来着……] -> episode_1.stage_1.livestream 272 | 273 | * (update) {not (tg or update)} 啊!今天频道还没更新! 274 | 275 | 打开tg的风痕快速阅览了几个大频道。 276 | 277 | 那些频道都早已过几万人订阅,可作为tg梗图频道的开山鼻祖——『Foolish TraceWind』。
这几年还是在1万人徘徊。 278 | 279 | 其他的频道凭借着发色图、将他人制作的梗图据为己有、联动色情与白嫖频道以此扩大订阅量。 280 | 281 | + + 「我才不和他们同流合污呢,哼。」 282 | 283 | { sound == true : 284 | # AUDIO: audios/sound/哼(小生气).ogg 285 | } 286 | 287 | - - 风痕看了下群中的几个投稿,又打开了常去的BBS网站,转发了几张过时的梗图到自己的万人频道中。 288 | 289 | + + 「本雀只想让大家点进傻痕频道后觉得开心,便够了」 290 | 291 | - - {not pooping: -> episode_1.stage_1} 292 | 293 | + + 「小心痔疮!是时候出厕所了」 294 | ~ pooping = false 295 | 296 | -> episode_1.stage_1 297 | 298 | * (livestream) {not pooping} 之前说住院结束后,要直播游戏来着…… 299 | 300 | - 风痕打开了显示器与电脑后,望着电脑屏幕,望得有点出神。 301 | 302 | 风痕摇了摇头,拍了拍脸:
「是时候遵守承诺,直播游戏了!」 303 | 304 | { sound == true : 305 | # AUDIO: audios/sound/打脸(啪啪).ogg 306 | } 307 | 308 | 风痕又望了望在房间角落的路由器,苦笑: 309 | + 「说起来不在家一个多月,网费还是要照交……」 310 | 311 | - 风痕打开了叔叔的网站。
调好配置与麦克风后,打开了她在打折时买的解密游戏。 312 | 313 | 其实风痕也不知道这游戏玩的是什么,但她知道的是:渡鸦最喜欢解密游戏。 314 | 315 | 风痕还记得读高中时,渡鸦和她做了一款解密游戏。 316 | 317 | 虽然整款游戏从图像到关卡都很屎,
但在风痕的脑中,最记忆犹新的是:一起做这游戏时,玩得很开心。 318 | 319 | + 风痕就是这样爱上解密游戏的 320 | 321 | - 风痕在自己的直播通知群中发了正在直播的通知。 322 | 323 | 不过那通知群本身就没有几个人。
这几个人中,还有几个是 bot。 324 | 325 | 风痕对着麦高呼:
Hello!一个半月没有相见,我回来啦! 326 | 327 | 弹幕:†升天† 328 | 329 | + 风痕在麦前欢呼 330 | 331 | { sound == true : 332 | # AUDIO: audios/sound/欢呼.ogg 333 | } 334 | 335 | - 风痕其实不算特别聪明。
一直以来,解密游戏的通关凭靠的不是思考,而是耐性。 336 | 337 | 风痕苦笑: 338 | 啊啦啦……这不行吗? 339 | 340 | + 「难道是这样?」 341 | 342 | - + 「嗯…………是这样吗?」 343 | 344 | - + 「啊!我知道了,一定是这样!」 345 | 346 | - 「嗯……不行啊……」 347 | 348 | 这也难怪没什么观众看了。 349 | 350 | + 「啊!是这样………………好!过关了!!」<> 351 | - 风痕自己鼓起了掌来。 352 | 353 | { sound == true : 354 | # AUDIO: audios/sound/鼓掌.ogg 355 | } 356 | 357 | 可弹幕却一片寂静。 358 | 359 | 风痕苦笑,看了看荧幕的右下角:
「那个……今天时间也不早了,是时候和大家说再见了,哈哈哈」 360 | 361 | 弹幕仍一片寂静。 362 | 363 | 「啊,那个……直播的录像之后会上传,没来得及看直播的朋友要记得看哦!」 364 | 365 | 风痕对着镜头笑了下,那皮套也跟着这表情笑了起来。 366 | 367 | + 「谢谢大家!」 368 | 369 | - 风痕,下线了。 370 | 371 | ………………………… 372 | 373 | -> episode_1.stage_2 374 | 375 | 376 | 377 | = poop_owari 378 | 379 | + 风痕掏出手机,打算趁着拉屎的时候做些什么。 380 | 381 | -> episode_1.stage_1 382 | 383 | 384 | 385 | = stage_2 386 | 387 | // 第二幕——吃饭还是洗澡? 388 | 389 | …………………… 390 | 391 | 风痕的眼神从屏幕移开。 392 | 393 | 这时,放在桌面上的笔筒已经被黑暗所吞噬,本就不大的房间被时钟的行走声淹没。 394 | 395 | 风痕迅速地打开了灯。 396 | 397 | { sound == true : 398 | # AUDIO: audios/sound/关灯.ogg 399 | } 400 | 401 | 风痕:
嗯……虽然直男医生嘱咐我要多喝热水,早点睡。
可是现在还早吧! 402 | 403 | + (eat) 说起来,今天还未吃饭饭呢! 404 | 405 | 风痕从背囊中拿出了几罐药。 406 | 407 | 风痕看着药罐的贴纸:
…………这些药是一日两次,并且是饭后食用…… 408 | 409 | 风痕苦笑:
不过我今天还未吃午餐,哈哈。 410 | 411 | + + 风痕转为开朗的声音 412 | 413 | - - 事到如今,还担心这些做什么! 414 | 415 | 今晚吃什么? 416 | 417 | + + 木曜日!当然是叫『疯狂星期四』!! 418 | -> episode_1.crazy_thursday 419 | 420 | + + 去冰箱里看看还有什么吧 421 | -> episode_1.foot_option 422 | 423 | + + {extension and poop} 记得还有刚才拉的屎 424 | 425 | -> cuna.eat_poop 426 | 427 | + (bath) 是不是该洗白白了? 428 | 429 | { sound == true : 430 | # AUDIO: audios/sound/稍微生气.ogg 431 | } 432 | 433 | 风痕:
干,干什么!!!>_<
不准看女孩子洗澡澡!! 434 | 435 | + + 风痕虽然这么说,可她还是走向了衣柜 436 | -> yuki_end 437 | 438 | 439 | 440 | = crazy_thursday 441 | 442 | 风痕笑着:
大家一直都说这个,一定很好吃吧! 443 | 444 | 风痕不愿打电话,她打开了外卖专用网站。 445 | 446 | 正要下单时,风痕才注意到自己并没有注册过网站会员。 447 | 448 | 风痕皱起眉头:
我的生日,是几月几号来着……? 449 | 450 | { sound == true : 451 | # AUDIO: audios/sound/思考.ogg 452 | } 453 | 454 | 风痕:
算了算了,今日就是我的生日! 455 | 456 | 疯狂星期四很慷慨地送了『新注册会员优惠卷』与『生日优惠卷』 457 | 458 | + 风痕下单了八个蛋挞和6块吮指原味鸡 459 | 460 | - 风痕开心:
有优惠卷,不点白不点! 461 | 462 | 这餐估计是她这辈子吃得最多的一餐。 463 | 464 | + …………………… 465 | 466 | - 但也不知怎的,好似店铺就开在隔壁一样,5分钟不到就把这餐送到了。 467 | 468 | 虽然房间里只有一个人,但风痕吃得很开心。 469 | 470 | 还在网上找了部动画开始看起来。 471 | 472 | 风痕:
诶……都出了这么多集了啊。 473 | 474 | 但风痕并没有浪费时间衔接回之前看到的集数,而是随心地看起了最新一集。 475 | 476 | + [【继续阅读】] 477 | 478 | - 风卷残云后,动画也开始播放起片尾曲,食物也刚好吃完了。 479 | 480 | 风痕看着两边油腻腻得手,不由得笑了起来。 481 | 482 | 她想起以前小学去同学生日会的情形,同学们都会叫肯德基和麦当劳大餐招待朋友们。 483 | 484 | + [【继续阅读】] 485 | 486 | - 虽然这种快餐式的派对几乎都是男孩子聚集的场所,但她身为女孩子却总是被招待,也算是一种特例。 487 | 488 | 风痕清理了桌上的残渣后,去厕所洗了洗手,洗漱、并擦了擦身。 489 | 490 | + 吃下药后,便准备睡觉了。 491 | -> butterfly_end 492 | 493 | 494 | 495 | = foot_option 496 | 497 | 风痕打开了冰箱。 498 | 499 | 但一个半月都在医院的她,自家的冰箱里又能有什么呢? 500 | 501 | 望了一圈后,冰箱中只有朱古力和剩下的几只鸡蛋。 502 | 503 | + [吃朱古力] 504 | 505 | 风痕打开了包装,吃了下那还未过期的朱古力。 506 | 507 | 她记得这个口感,这和她上高中时收到的情人节朱古力一模一样。 508 | 509 | 但那不是义理,也不是本命。 510 | 511 | 同学们的桌面和抽屉里塞满了互相赠送的朱古力。 512 | 513 | + + [【继续阅读】] 514 | 515 | - - 金色的、白色的、冰蓝色的、橙红色的。
球形的、币形的、心形的、古灵精怪形的。
夹杂着糖果的、附带了礼物的、包含了水果的。 516 | 517 | 风痕看回自己桌。
被刀与笔涂画不清的桌面上有着一块朱古力。 518 | 519 | + + [【继续阅读】] 520 | 521 | - - 是学校组织赠送的。 522 | 523 | 风痕反复咀嚼着嘴中的朱古力,希望那味能在口中多残留一会。 524 | 525 | 风痕笑了起来:
还不坏。 526 | 527 | 回到房间后服用了药物,就准备睡觉了。 528 | -> sleeping_end ("药物不能和咖啡因混吃") 529 | 530 | + [吃鸡蛋] 531 | 532 | 风痕苦笑:
打开后不会是小鸡尸体吧…… 533 | 534 | 风痕接了壶水,准备将鸡蛋煮沸后吃。 535 | 536 | 这咕噜咕噜往上冒的水花,其声掩盖了时钟的行走声。 537 | 538 | + + 风痕打开了手机,想着和群友吹水打发这段时间。 539 | 540 | - - 看了下几个群,在这段时间内消息已走了几百条。 541 | 542 | 随手点开一个后,映入眼中的是自己发的『早上好』和表情包。
往下一滑,发的消息不是回复风痕的,而是回复在风痕之上的人。 543 | 544 | + + 嗒一声,水花声褪去。 545 | 546 | { sound == true : 547 | # AUDIO: audios/sound/开水.ogg 548 | } 549 | 550 | - - 风痕把一部分的开水倒入杯中,准备后药物以便服用。 551 | 552 | 再等了一会,把鸡蛋取出,敲掉壳后露出的仍然是雪白滑嫩的蛋白。 553 | 554 | 风痕松了口气,吃下鸡蛋后再服用了药物。 555 | 556 | 去厕所洗了洗手,洗漱、并擦了下身。 557 | 558 | + + 准备睡觉了。 559 | -> stage_4 560 | 561 | 562 | = end 563 | 564 | // EP1 结尾部分,结尾会跳转到 EP2(即第二天) 565 | // 待写内容。 566 | 567 | 568 | -> episode_2 569 | 570 | 571 | 572 | = stage_4 573 | 574 | 风痕爬上了床。
把床头小屎屎公仔紧紧地搂在怀里。 575 | 576 | 风痕高兴:
虽然屎屎的,但香香的、软软的~哼哼~ 577 | 578 | { sound == true : 579 | # AUDIO: audios/sound/嗯哼哼~~.ogg 580 | } 581 | 582 | 风痕:
一个半月我没有在家,自己有没有乖乖帮忙看家啊? 583 | 584 | + [【继续阅读】] 585 | 586 | - 风痕边说,边捏了捏公仔的头。
随后把脸埋在松软香绵的公仔怀里,睡着了。 587 | 588 | + 风痕睁开了眼 589 | 590 | - 在一个漆黑的的地方,她伸出手。
似乎希望能摸到些什么,但没有任何物质愿意回馈。 591 | 592 | 风痕并没有惊慌,她就像来过这很多次一样,试着朝前走了一段距离。 593 | 594 | + [【继续阅读】] 595 | 596 | - 风痕两手互相抱着,有点颤抖:
好冷…………小屎屎呢………… 597 | 598 | 她抱紧了自己,她知道,有什么要来了。 599 | 600 | 霎时,一道电击从心脏开始蔓延。
她跌坐在地上,头部嗡嗡地绞着。 601 | 602 | 『风痕,醒醒……风痕……』 603 | 604 | + 爸爸?! 605 | 606 | 风痕尽力睁开了眼。 607 | 608 | 映入的是臂弯挽着不知名女人的父亲。 609 | 610 | 她知道,那是父亲的新欢。
父亲和她牵着一个小男孩,三人脸上都溢满幸福。 611 | 612 | 没有人看她一眼。 613 | 614 | 风痕低下了头,最后的光也淡入了这片黑暗。 615 | -> sleeping_end ("建议多向朋友倾诉") 616 | 617 | + 妈妈?! 618 | 619 | 风痕尽力睁开了眼。
母亲蹲在她面前,笑着看着她。 620 | 621 | 风痕正想站起,背后的父亲走了过来。 622 | 623 | 母亲站起,与父亲谈论起风痕。 624 | 625 | 担忧的话语逐渐转为了激烈的言辞,委婉的声音渐渐越来越大。 626 | 627 | 他们吵了起来,没有人看她一眼。 628 | 629 | 风痕低下了头,最后的光也淡入了这片黑暗。 630 | -> sleeping_end ("建议多向朋友倾诉") 631 | 632 | + 渡鸦?! 633 | 634 | 风痕尽力睁开了眼。 635 | 636 | 渡鸦正在不远处学习。 637 | 638 | 风痕跌跌撞撞地往他靠近,手臂向前微微张开。 639 | 640 | 风痕走了很久。 641 | 却无论如何都无法靠近渡鸦。 642 | 643 | 风痕垂下了双臂,低下了头,最后的光也淡入了这片黑暗。 644 | -> sleeping_end ("建议多向朋友倾诉") 645 | 646 | + 小屎屎?! 647 | 648 | 小屎屎公仔就在那里。 649 | 650 | 它静静地看着风痕,还是露出着那副笑脸。 651 | 652 | 风痕跌跌撞撞地靠近它,它也慢慢地向风痕靠近。 653 | 654 | 风痕张开了双臂,小屎屎便跃到风痕的怀中。 655 | 656 | 它微笑着,静静地看着风痕。 657 | 658 | 它就这么静静地看着风痕,全神贯注。
什么要求也没有提,什么话也没有说。 659 | 660 | 它抱着风痕。 661 | -> episode_2 662 | 663 | 664 | 665 | 666 | 667 | === sleeping_end(tips) === 668 | 669 | // 睡美人 END 670 | 671 | + [【继续阅读】] 672 | 673 | 晨光侵入那宽跨的窗帘,抚过风痕白皙的皮肤。 674 | 675 | 躺在床上的风痕双眼微闭,胸部随着呼吸一起一伏,发丝在阳光的照耀下泛着浅浅的橙色。 676 | 677 | 她安静地睡着。 678 | 679 | 直到永远。 680 | 681 | ~ new_ending("sleeping") 682 | 683 | # CLASS: game_end 684 | 睡美人 END 685 | 686 |
687 | 688 | # CLASS: tips_title 689 | 获得条例 690 | 691 | # CLASS: tips_text 692 | {tips} 693 | 694 | -> bite_the_dust.sleeping 695 | 696 | 697 | 698 | 699 | 700 | === yuki_end === 701 | 702 | 打开衣柜后,密封空间中凝固的热气就如刚入房间时的空气一般。
只不过这次带着了一股衣物的潮霉味。 703 | 704 | 风痕衣柜中的衣物不多,实在不像她这年纪下女孩子的衣柜。 705 | 706 | 风痕看了看衣柜中她最喜欢的绿色带帽外套。 707 | 708 | 这外套已显得有点旧了,除了绿色不均匀外,还左沾了点蓝色,右沾了点紫色。 709 | 710 | 有些混起来的地方,还成了点红色。 711 | 712 | 风痕笑了笑,拿出了套自家用的衣物后,关上了衣柜。 713 | 714 | + 去厕所洗澡了 715 | 716 | - 风痕很难得地准备了一大缸热水,为此还花了半小时打扫了下浴缸。 717 | 718 | 脱去衣物她,照了照镜子后,苦笑地挠了挠头。 719 | 720 | 浸入了热水中。 721 | 722 | ……………… 723 | 724 | + 风痕在热气中静静地望着天花板。 725 | 726 | - 风痕似乎很享受这段宁静的时间,她望着这雾中的水珠们成群结队地往上升。 727 | 728 | 风痕耳边似乎渐渐有了点声音,是小时候便在一起玩的近邻朋友们打闹的笑声。 729 | 730 | 幼儿园时他们经常一起东跑西跳,小学时也总是去大家家里一起玩。 731 | 732 | 风痕苦笑了声,但与之前相比似乎有点无力。 733 | 734 | + 她想起了以前去朋友家联机玩对战游戏。<> 735 | 玩一局输一局,排名一出后是垫底的日子。 736 | 737 | - 但即便如此,风痕还是很开心。 738 | 739 | 风痕嘀咕:
至少那时……还有人约我出去玩…… 740 | 741 | 但也不知道,大家是不想当垫底,还是真的喜欢和风痕玩才极力邀请风痕去做客就是了。 742 | 743 | 风痕耳边的笑声愈渐愈远,就像那些水珠一般,升走了。 744 | 745 | ~ new_ending("yuki") 746 | 747 | # CLASS: game_end 748 | 汤化雪 END 749 | 750 |
751 | 752 | # CLASS: tips_title 753 | 获得条例 754 | 755 | # CLASS: tips_text 756 | 忌冲热水凉、泡热水澡 757 | 758 | -> bite_the_dust.yuki 759 | 760 | 761 | 762 | 763 | 764 | === butterfly_end 765 | 766 | // 羽化梦 END 767 | 768 | + [【继续阅读】] 769 | 770 | - 风痕就这样沉入了梦乡。 771 | 772 | 意识朦胧,一阵窸窸窣窣的声音在房间内响起。 773 | 774 | 风痕不觉意地走到房间中央。 775 | 776 | 声音在动,围绕着风痕所动。 777 | 778 | + 月色下<> 779 | - 风痕瞟到了镜子中的自己。 780 | 781 | 影中的她显得与平常不太一样:轮廓有点大,举动也更灵敏了。 782 | 783 | 她发觉了,她头上长出了羽毛。
她手臂变成了翅膀,身后出现了尾巴。 784 | 785 | 洁白的月色下,棕白相间的她变成了一只麻雀。 786 | 787 | 风痕打开了窗,风便随着云雾吹过她的羽毛。 788 | 789 | + 她张开了双翼 790 | 791 | - 她自由了。 792 | 793 | ~ new_ending("butterfly") 794 | 795 | # CLASS: game_end 796 | 羽化梦 END 797 | 798 |
799 | 800 | # CLASS: tips_title 801 | 获得条例 802 | 803 | # CLASS: tips_text 804 | 忌食高脂肪、酒、油炸食品 805 | 806 | -> bite_the_dust.butterfly 807 | 808 | 809 | 810 | 811 | 812 | === episode_2 === 813 | 814 | // 这里是第二天的内容 815 | 816 | # CLASS: game_end 817 | TRUE END 818 | 819 | -> ENDs.nfy 820 | 821 | 822 | 823 | 824 | 825 | === ENDs === 826 | 827 | # CLASS: end 828 | 游戏结束 829 | 830 | -> ENDs.rest 831 | 832 | 833 | 834 | = nfy 835 | 836 | # CLASS: end 837 | 游戏尚未结束,但文字已结束。 838 | 839 | -> ENDs.rest 840 | 841 | 842 | 843 | = rest 844 | 845 | + [再次进行游戏] 846 | # CLEAR 847 | # RESTART 848 | -> start 849 | 850 | 851 | 852 | 853 | 854 | === bite_the_dust 855 | 856 | = yuki 857 | 858 |


859 | 860 | + [结束游戏?] -> ENDs 861 | + [回到之前的选择?] 862 | + + [风痕:
嗯……虽然直男医生嘱咐我要多喝热水,早点睡。
可是现在还早吧!] 863 | # CLEAR 864 | -> episode_1.stage_2 865 | 866 | 867 | = sleeping 868 | 869 |


870 | 871 | + [结束游戏?] -> ENDs 872 | + [回到之前的选择?] 873 | + + [风痕:
嗯……虽然直男医生嘱咐我要多喝热水,早点睡。
可是现在还早吧!] 874 | # CLEAR 875 | -> episode_1.stage_2 876 | + + [说起来,今天还没吃饭饭呢!] 877 | # CLEAR 878 | -> episode_1.stage_2.eat 879 | 880 | 881 | = butterfly 882 | 883 |


884 | 885 | + [结束游戏?] -> ENDs 886 | + [回到之前的选择?] 887 | + + [风痕:
嗯……虽然直男医生嘱咐我要多喝热水,早点睡。
可是现在还早吧!] 888 | # CLEAR 889 | -> episode_1.stage_2 890 | + + [说起来,今天还没吃饭饭呢!] 891 | # CLEAR 892 | -> episode_1.stage_2.eat 893 | + + 准备睡觉了。 894 | # CLEAR 895 | -> episode_1.stage_4 896 | 897 | 898 | 899 | 900 | 901 | 902 | 903 | 904 | // 未使用的音乐: 905 | 906 | // audios/bgm/the_field_of_dreams.ogg 907 | // 音乐来自:https://opengameart.org/content/the-field-of-dreams 908 | 909 | // audios/sound/plasterbrain_Cute_Anime_Jumps.mp3 910 | // 音乐来自:https://freesound.org/people/plasterbrain/sounds/396196/ 911 | 912 | // 关门的音效 913 | // # AUDIO: audios/sound/336545__giddster__closing-door.ogg 914 | // 声音来自:https://freesound.org/people/giddster/sounds/336545/ 915 | 916 | 917 | // 旧版内容已被移动到: 918 | // https://hackmd.io/HXYxZkODRaGwe9Elo70H3Q 919 | // https://gist.githubusercontent.com/gledos/46707b2a6e57a3d67cf6f051b3d3c676/raw/2ddf5e97d597308e64788b19733bda997845807e/CLEAR.md 920 | 921 | 922 | 923 | === debug_mod === 924 | 925 | # CLEAR 926 | # CLASS: menu_title 927 | 开发者模式 928 | 929 | 这里是开发者模式,可以跳转到: 930 | 931 | + [Episode 1] -> debug_mod.episode_1_debug 932 | 933 | = episode_1_debug 934 | 935 | + [Stage 1] ->episode_1.stage_1 936 | + [Stage 2] ->episode_1.stage_2 937 | + [Stage 4] ->episode_1.stage_4 938 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | /* @import url('https://fonts.googleapis.com/css?family=Open+Sans:300,700|Quattrocento:700'); */ 2 | 3 | body { 4 | /* font-family: 'Open Sans', sans-serif; */ 5 | /* font-weight: lighter; */ 6 | background: white; 7 | overflow: hidden; 8 | font-family: -apple-system, "Noto Sans", "Helvetica Neue", Helvetica, "Nimbus Sans L", Arial, "Liberation Sans", "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN", "Microsoft YaHei", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", "ST Heiti", SimHei, "WenQuanYi Zen Hei Sharp", sans-serif; 9 | /* https://zenozeng.github.io/fonts.css/ */ 10 | /* 11 | Copyright (C) 2013-2018 Zeno Zeng and contributors 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | */ 19 | } 20 | 21 | body.switched { 22 | transition: color 0.6s, background-color 0.6s; 23 | } 24 | 25 | h1, 26 | h2 { 27 | text-align: center; 28 | /* font-family: "Quattrocento", Georgia, 'Times New Roman', Times, serif; */ 29 | margin: 0; 30 | padding: 0; 31 | } 32 | 33 | h1 { 34 | font-size: 30pt; 35 | margin-bottom: 10px; 36 | } 37 | 38 | h2 { 39 | font-size: 14pt; 40 | font-style: italic; 41 | /* font-family: sans-serif; */ 42 | /* font-weight: lighter; */ 43 | color: #BBB; 44 | } 45 | 46 | .header { 47 | padding-top: 3em; 48 | padding-bottom: 3em; 49 | } 50 | 51 | /* 52 | Built-in class: 53 | # author: Name 54 | */ 55 | .byline { 56 | font-style: italic; 57 | color: black; 58 | } 59 | 60 | .written-in-ink { 61 | z-index: 3; 62 | font-size: 9pt; 63 | /* font-family: sans-serif; */ 64 | text-align: center; 65 | /* font-weight: 700; */ 66 | position: fixed; 67 | display: block; 68 | width: 100%; 69 | background: white; 70 | transition: color 0.6s, background 0.6s; 71 | margin: 0; 72 | padding-top: 6px; 73 | padding-bottom: 6px; 74 | height: 14px; 75 | top: 0; 76 | } 77 | 78 | /* 79 | Enables