top: -99999px;
opacity: 0;
}
- .ui-page > button {
+ .ui-page > button, .ui-page.outro .area button {
padding: 0;
- width: 8em;
+ min-width: 8em;
color: #000;
font-family: Cookie;
font-size: 2.5em;
border-image: url('textures/button-standard.png') 20 / .75em round;
border-image-outset: .2em;
}
- .ui-page > button:hover {
+ .ui-page > button:hover, .ui-page.outro .area button:hover {
background: #e9ce8a;
border-image-source: url('textures/button-hover.png');
}
- .ui-page > button:active {
+ .ui-page > button:active, .ui-page.outro .area button:active {
background: #f9c8d5;
border-image-source: url('textures/button-pressed.png');
}
font-size: 1em;
text-shadow: -.15em -.15em 0 #fff, .15em -.15em 0 #fff, .15em .15em 0 #fff, -.15em .15em 0 #fff, 0 -.19em 0 #fff, .19em 0 0 #fff, 0 .19em 0 #fff, -.19em 0 0 #fff;
}
+ .font-atkinson .ui-page.gameplay p, .font-opendyslexic .ui-page.gameplay p {
+ font-size-adjust: 1;
+ line-height: 2.5;
+ }
.ui-page.credits {
padding: 1em;
display: flex;
align-items: center;
gap: 1em;
}
+ .ui-page.outro .area {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ font-size: 2em;
+ text-align: center;
+ gap: 1em;
+ }
+ .ui-page.outro .area div.examples {
+ box-sizing: border-box;
+ width: 100%;
+ border-radius: 1em;
+ padding: 2em;
+ background: #ece3d5;
+ display: flex;
+ flex-direction: column;
+ gap: 1.5em;
+ color: #000;
+ font-family: Cookie;
+ font-size: .3em;
+ line-height: 3em;
+ }
+ .font-atkinson .ui-page.outro .area div.examples,
+ .font-opendyslexic .ui-page.outro .area div.examples {
+ padding: .5em;
+ gap: 0.5em;
+ font-size: 1em;
+ line-height: 1em;
+ }
+ .font-opendyslexic .ui-page.outro .area > p {
+ font-size: 0.9em;
+ }
+ .ui-page.outro .area strong {
+ font-weight: normal;
+ color: #d53c59;
+ }
+ .ui-page.outro .area div.examples strong {
+ font-weight: normal;
+ color: #c50031;
+ }
+ .ui-page.outro .area > p:first-child {
+ display: flex;
+ align-items: center;
+ gap: 1ex;
+ }
+ .ui-page.outro .count {
+ font-size: 2em;
+ color: #d53c59;
+ }
+ .ui-page.outro .area button {
+ width: max-content;
+ min-width: unset;
+ margin: 0;
+ padding: 0 .5em;
+ border-width: .3em;
+ font-size: 1em;
+ }
.ui-page.pause {
background: #000d;
padding: 1em;
<h4>Engine</h4>
<p><a href="https://threejs.org/" target="_blank">three.js</a> v169 by mrdoob and contributors</p>
<h4>Inspiration</h4>
-<p>Game concept inspired by: “<a href="https://www.ferryhalim.com/orisinal/g3/high.htm" target="_blank">High Delivery</a>” created by <a href="https://www.ferryhalim.com/" target="_blank">Ferry Halim</a></p>
+<p>Game concept inspired by: “<a href="https://www.ferryhalim.com/orisinal/g3/high.htm" target="_blank">High Delivery</a>” by <a href="https://www.ferryhalim.com/" target="_blank">Ferry Halim</a></p>
<p class="seealso">See <a href="README.txt" target="_blank">README.txt</a> for detailed licensing information.</p>
</div>
<button class="goto title">Back</button>
<div class="ui-page openingcutscene"></div>
<div class="ui-page endingcutscene"></div>
<div class="ui-page outro">
+<div class="area outro">
+<p>You collected <span class="count">0</span> <span class="optionalPlural">words.</span></p>
+<p class="rating"></p>
+<p class="examples">Here are a few sentences using some of them:</p>
+<div class="examples">
+</div>
+<button class="examples">More</button>
+<p>Can you think of anyone to whom you might need to write something along those lines? When was the last time your feelings were left <strong>up in the air</strong>?</p>
+</div>
<button class="goto title">Return to Title Screen</button>
</div>
<div class="ui-page pause">
import { FontLoader } from 'three/addons/loaders/FontLoader.js';
import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
-function getRandomWord(game) {
- return game.assets.words[Math.floor(Math.random() * game.assets.words.length)];
-}
-
function playRandomSound(game) {
if(!game.view || !game.view.audioListener) {
return;
function loadAllAssets(game, renderProgressCallback) {
game.assets = {};
- game.assets.words = [
- "Alpaca", "Bat", "Butterfly", "Deer", "Duck", "Elk", "Giraffe", "Horse",
- "Lynx", "Llama", "Owl", "Ocelot", "Pig", "Sheep", "Tapir", "Walrus",
+ game.assets.words = {
+ 'thanks': ['thank you', 'thanks'],
+ 'sorry': ['sorry', 'apologize'],
+ 'emotion': ['blessed', 'fortunate', 'glad', 'happy', 'joyous', 'lucky', 'overjoyed', 'thankful'],
+ 'verb_general': ['adore', 'appreciate', 'cherish', 'enjoy', 'like', 'love', 'treasure', 'value'],
+ 'verb_person': ['admire', 'honor', 'love', 'respect', 'treasure', 'value'],
+ 'trait': ['amazing', 'compassionate', 'delightful', 'genuine', 'generous', 'incredible', 'joyful', 'kind', 'passionate', 'patient', 'principled', 'refreshing', 'sweet'],
+ };
+ game.assets.wordList = [...new Set([].concat.apply([], Object.values(game.assets.words)))]; // no need to be sorted
+ game.assets.sentences = [
+ '{thanks} for always listening.',
+ '{thanks} for being there.',
+ '{thanks} for helping me when I needed it most.',
+ '{thanks} for being with me.',
+ '{thanks} for believing in me.',
+ '{thanks} for not giving up.',
+ '{thanks} for believing in me when I myself couldn’t.',
+ '{thanks} for standing by my side.',
+ '{sorry} for what I said.',
+ '{sorry} for not being there.',
+ '{sorry} for forgetting.',
+ '{sorry} for not telling you.',
+ '{sorry} for what I did.',
+ '{sorry} for back then.',
+ '{sorry} for not being honest.',
+ 'Just being around you makes me feel {emotion}.',
+ 'I have no words for how {emotion} you make me feel.',
+ 'I always feel {emotion} in your presence.',
+ 'I’m honestly {emotion}.',
+ 'I feel {emotion} just for knowing you.',
+ 'Every moment with you makes me feel {emotion}.',
+ 'I {verb_person} you.',
+ 'I {verb_person} you more than anything.',
+ 'I deeply {verb_person} you.',
+ 'I honestly {verb_person} you.',
+ 'I really do {verb_person} you.',
+ 'I {verb_general} every moment with you.',
+ 'I {verb_general} the way you see the world.',
+ 'I {verb_general} you the way you are.',
+ 'I {verb_general} how {trait} you are.',
+ 'I {verb_general} how {trait} you are.',
+ 'I {verb_general} how {trait} you are.',
+ 'I always {verb_general} how {trait} you are.',
+ 'I deeply {verb_general} how {trait} you are.',
+ 'Thinking about how {trait} you are always improves my mood.',
+ 'You are the most {trait} person I know.',
+ 'You are the most {trait} person I know.',
+ 'Your {trait} personality always makes my day.',
+ 'Your {trait} personality is my sunshine.',
+ 'Your {trait} personality gives me strength.',
+ 'I’m astonished how {trait} you are.',
+ 'I hope I can learn to be as {trait} as you.',
];
return new Promise((resolve, reject) => {
let todoList = {
game.objects.words.collectedCount = 0;
const interWordDistance = new THREE.Vector3();
let placementSuccess;
+ let wordList = [];
for(let i = 0; i < 100; i++) {
let angleInCourse;
let clusteringFunction = (val) => Math.max(0.15 + val / 2, 0.7 - 3 * Math.pow(0.75 - 2 * val, 2), 1 - 3 * Math.pow(1 - val, 2));
let randomCameraX = game.courseRadius * Math.sin(angleInCourse * 2 * Math.PI);
let randomCameraY = game.courseRadius * -Math.cos(angleInCourse * 2 * Math.PI);
let word = new THREE.Group();
- word.text = getRandomWord(game);
+ if(wordList.length == 0) {
+ wordList.push(...game.assets.wordList);
+ }
+ let wordIndex = Math.floor(Math.random() * wordList.length);
+ word.text = wordList.splice(wordIndex, 1)[0];
prepareWordMesh(game, word);
word.randomAnimOffset = Math.random();
const vFOV = THREE.MathUtils.degToRad(game.view.camera.fov);
});
if(game.settings['graphics'] <= 2) {
game.assets.fonts.geometry = {};
- for(let letter of [...new Set(game.assets.words.join(''))]) {
+ for(let letter of [...new Set(game.assets.wordList.join(''))]) {
if(game.settings['graphics'] == 1) {
game.assets.fonts.geometry[letter] = new TextGeometry(letter, {
font: game.assets.fonts.cookie,
minimalLetterGeometry.dx = 0;
minimalLetterGeometry.dy = 0;
game.assets.fonts.geometry = {};
- for(let letter of [...new Set(game.assets.words.join(''))]) {
+ for(let letter of [...new Set(game.assets.wordList.join(''))]) {
game.assets.fonts.geometry[letter] = minimalLetterGeometry;
}
for(let r = 0; r < game.courseRadius / 3; r++) {
highcontrastLetterGeometry.dx = 0;
highcontrastLetterGeometry.dy = 0;
game.assets.fonts.geometry = {};
- for(let letter of [...new Set(game.assets.words.join(''))]) {
+ for(let letter of [...new Set(game.assets.wordList.join(''))]) {
game.assets.fonts.geometry[letter] = highcontrastLetterGeometry;
}
const highContrastBackdropMaterial = new THREE.MeshBasicMaterial({
delete game.view.music.timeoutID;
}
}
+ if(target == 'outro') {
+ if(game.view.music.isPlaying) {
+ game.view.music.stop();
+ }
+ let collectedWords = game.objects.words.filter(w => w.collected).map(w => w.text);
+ game.ui.root.querySelector('.ui-page.outro .count').innerText = game.objects.words.collectedCount;
+ game.ui.root.querySelector('.ui-page.outro .optionalPlural').innerText = 'word' + ((game.objects.words.collectedCount == 1) ? '' : 's') + '.';
+ let ratingElem = game.ui.root.querySelector('.ui-page.outro .rating');
+ let exampleElems = game.ui.root.querySelectorAll('.ui-page.outro .examples');
+ ratingElem.style.display = 'none';
+ exampleElems.forEach(elem => { elem.style.display = 'none'; });
+ if(game.objects.words.collectedCount > 0) {
+ if(game.objects.words.collectedCount == 100) {
+ ratingElem.style.display = 'block';
+ ratingElem.innerText = 'Wow, you managed to collect all of them. Congratulations!';
+ } else {
+ let generateExampleSentences = (wordList) => {
+ let container = game.ui.root.querySelector('.ui-page.outro div.examples');
+ while(container.children.length > 0) {
+ container.children[0].remove();
+ }
+ let words = {};
+ for(let category of Object.keys(game.assets.words)) {
+ words[category] = [];
+ for(let word of game.assets.words[category]) {
+ if(wordList.includes(word)) {
+ words[category].push(word);
+ }
+ }
+ }
+ let result = [];
+ let failedAttempts = 0;
+ while(result.length < 3 && failedAttempts < 1000) {
+ let sentence = game.assets.sentences[Math.floor(Math.random() * game.assets.sentences.length)];
+ while(sentence.indexOf('{') > -1) {
+ let areWeStuck = true;
+ for(let category of Object.keys(words)) {
+ if(sentence.includes('{' + category + '}')) {
+ if(words[category].length == 0) {
+ break;
+ }
+ let choice = words[category][Math.floor(Math.random() * words[category].length)];
+ if(category == 'sorry') {
+ if(choice == 'sorry') {
+ sentence = sentence.replace('{sorry}', 'I’m {sorry}');
+ }
+ if(choice == 'apologize') {
+ sentence = sentence.replace('{sorry}', 'I {sorry}');
+ }
+ }
+ if(sentence.indexOf('{' + category + '}') == 0) {
+ choice = choice[0].toUpperCase() + choice.slice(1);
+ }
+ sentence = sentence.replace('{' + category + '}', '<strong>' + choice + '</strong>');
+ words[category].splice(words[category].indexOf(choice), 1);
+ areWeStuck = false;
+ }
+ }
+ if(areWeStuck) {
+ break;
+ }
+ }
+ if(sentence.indexOf('{') == -1 && !result.includes(sentence)) {
+ result.push(sentence);
+ failedAttempts = 0;
+ }
+ failedAttempts += 1;
+ }
+ for(let sentence of result) {
+ let elem = document.createElement('p');
+ elem.innerHTML = sentence;
+ container.appendChild(elem);
+ }
+ };
+ generateExampleSentences(collectedWords);
+ game.ui.root.querySelector('.ui-page.outro button.examples').addEventListener('click', () => { generateExampleSentences(collectedWords); });
+ exampleElems.forEach(elem => { elem.style.display = 'flex'; });
+ }
+ } else {
+ ratingElem.style.display = 'block';
+ ratingElem.innerText = 'You completed the course while dodging every word. That’s an achievement on its own. Respect!';
+ }
+ }
if(target == 'options') {
game.ui.root.querySelectorAll('.options .areatabs button').forEach((btn) => {
if(btn.classList.contains('general')) {