├── .gitignore
├── src
├── hidden.png
├── webpack.config.js
├── play.html
├── polyfill.js
├── package.json
├── achievements.css
├── index.html
└── achievements.js
├── patch.sh
├── .github
└── workflows
│ └── publish.yml
├── README.md
├── LICENSE
└── download.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | /download
2 | /game
--------------------------------------------------------------------------------
/src/hidden.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ading2210/sheepy-web-port/HEAD/src/hidden.png
--------------------------------------------------------------------------------
/src/webpack.config.js:
--------------------------------------------------------------------------------
1 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
2 |
3 | module.exports = {
4 | entry: [
5 | './scripts/main.js',
6 | './scripts/c3runtime.js'
7 | ],
8 | plugins: [
9 | new NodePolyfillPlugin()
10 | ],
11 | target: 'web',
12 | resolve: {
13 | fallback: {
14 | "fs": false,
15 | "child_process": false
16 | }
17 | },
18 | };
--------------------------------------------------------------------------------
/patch.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #apply patches to the game
4 |
5 | #don't load the original c3runtime
6 | sed -i 's/"scripts\/c3runtime.js"//' game/scripts/main.js
7 |
8 | #custom callback for achievements
9 | sed -i -r '/ActivateAchievement\(achievement\)/a \{activate_achievement(achievement)\}, _()' game/scripts/c3runtime.js
10 |
11 | #prevent memory leak when loading audio
12 | sed -i 's/!isMusic||audioDomHandler.IsPlayMusicAsSound()||needsSoftwareDecode/false/' game/scripts/main.js
13 |
14 | #copy modified assets in src folder
15 | cp src/* game/
16 |
17 | #bundle it for the web
18 | cd game
19 | npm i
20 | npx webpack
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | on: [push]
2 |
3 | jobs:
4 | publish:
5 | runs-on: ubuntu-latest
6 | permissions:
7 | contents: read
8 | deployments: write
9 | name: Publish to Cloudflare Pages
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v3
13 |
14 | - name: Build
15 | run: |
16 | sudo apt-get install -y jq curl wget pcregrep
17 | ./build.sh
18 |
19 | - name: Publish to Cloudflare Pages
20 | uses: cloudflare/pages-action@v1
21 | with:
22 | apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
23 | accountId: b8079ffa92c97010f2a8d759e24cc782
24 | projectName: sheepy
25 | directory: game
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sheepy: A Short Adventure - Unofficial Web Port
2 |
3 | This is a port of the game [Sheepy: A Short Adventure](https://store.steampowered.com/app/1568400/Sheepy_A_Short_Adventure/) to the web. Since the original game uses the [Construct](https://www.construct.net/en) game engine with no native code, only minimal changes are needed to get it to run on web browsers.
4 |
5 | ## Features:
6 | - Local achievements
7 | - Reduced memory usage (3GB -> 300MB on the title screen)
8 | - Runs in Firefox/Chrome on any OS
9 | - Controller support
10 |
11 | ## Building:
12 | To build this port for yourself, run `./build.sh`. You must be on Linux with `pcregrep`, `curl`, `jq`, and NodeJS installed.
13 |
14 | ## License:
15 | The copyright for the original game belongs to MrSuicideSheep, but the patches for this port are under the MIT license.
16 |
17 | This repository does not contain any of the game's code, only the modifications needed to build this port are included.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2024 ading2210
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/src/play.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Sheepy: A Short Adventure - Unofficial Web Port
6 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
This content requires JavaScript
20 |
21 | JavaScript appears to be disabled. Please enable it to view this
22 | content.
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/download.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #download the game from itch.io
4 |
5 | mkdir -p download
6 | mkdir -p game
7 |
8 | main_html="$(curl "https://mrsuicidesheep.itch.io/sheepy")"
9 | csrf="$(echo "$main_html" | pcregrep -o1 ' ')"
10 | api_url="https://mrsuicidesheep.itch.io/sheepy/file/9759425?source=game_download&after_download_lightbox=1&as_props=1"
11 |
12 | game_json="$(curl "$api_url" \
13 | --compressed -X POST -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0' \
14 | -H 'Accept: */*' \
15 | -H 'Accept-Language: en-US,en;q=0.5' \
16 | -H 'Accept-Encoding: gzip, deflate, br' \
17 | -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
18 | -H 'X-Requested-With: XMLHttpRequest' \
19 | -H 'Origin: https://mrsuicidesheep.itch.io' \
20 | -H 'Sec-Fetch-Dest: empty' \
21 | -H 'Sec-Fetch-Mode: cors' \
22 | -H 'Sec-Fetch-Site: same-origin' \
23 | --data-raw "csrf_token=$csrf")"
24 | download_url="$(echo $game_json | jq -r '.url')"
25 | zip_file="download/sheepy.zip"
26 |
27 | wget -c $download_url -O $zip_file
28 |
29 | #extract the nw.js bundle from the game files
30 |
31 | bundle_name="SHEEPY A Short Adventure/package.nw"
32 | bundle_file="download/bundle.zip"
33 | unzip -p "$zip_file" "$bundle_name" > $bundle_file
34 | unzip -o $bundle_file -d game
--------------------------------------------------------------------------------
/src/polyfill.js:
--------------------------------------------------------------------------------
1 | window.process = {
2 | platform: "linux",
3 | execPath: "/",
4 | mainModule: {
5 | filename: "/",
6 | },
7 | };
8 |
9 | window.nw = {
10 | App: {
11 | clearCache() {},
12 | manifest: {
13 | "c3-app-icons": [
14 | {
15 | height: 256,
16 | src: "icons\\icon-256.png",
17 | width: 256,
18 | },
19 | ],
20 | "c3-stream-mode": true,
21 | name: "SheepyAShortAdventure",
22 | version: "1.0.0",
23 | window: {
24 | frame: true,
25 | height: 360,
26 | icon: "icons/icon-256.png",
27 | kiosk: false,
28 | position: "center",
29 | resizable: true,
30 | show: true,
31 | title: "SheepyAShortAdventure",
32 | width: 640,
33 | },
34 | },
35 | },
36 |
37 | Window: {
38 | get() {
39 | return {
40 | title: "SheepyAShortAdventure",
41 | x: 0,
42 | y: 0,
43 | get width() {
44 | return window.innerWidth;
45 | },
46 | get height() {
47 | return window.innerHeight;
48 | },
49 | on() {},
50 | };
51 | },
52 | },
53 |
54 | Clipboard: {
55 | get() {
56 | return {
57 | get() {
58 | return "";
59 | },
60 | };
61 | },
62 | },
63 |
64 | Shell: {
65 | openExternal(url) {
66 | window.open(url);
67 | }
68 | }
69 | };
70 |
--------------------------------------------------------------------------------
/src/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "index.html",
3 | "name": "sheepy-web-port",
4 | "description": "",
5 | "version": "1.0.0",
6 | "window": {
7 | "icon": "icons/icon-256.png",
8 | "title": "SheepyAShortAdventure",
9 | "width": 640,
10 | "height": 360,
11 | "position": "center",
12 | "resizable": true,
13 | "frame": true,
14 | "kiosk": false,
15 | "show": true
16 | },
17 | "user-agent": "Mozilla/5.0 (%osinfo) AppleWebKit/%webkit_ver (KHTML, like Gecko, Chrome, Safari) NWjs/%nwver",
18 | "chromium-args": "--enable-node-worker --disable-plugins --disable-internal-flash --disable-popup-blocking --allow-file-access-from-files --disable-features=HardwareMediaKeyHandling --ignore-gpu-blacklist --in-process-gpu --disable-windows10-custom-titlebar --auto-open-devtools-for-tabs",
19 | "c3-steam-mode": true,
20 | "c3-app-icons": [
21 | {
22 | "src": "icons\\icon-16.png",
23 | "width": 16,
24 | "height": 16
25 | },
26 | {
27 | "src": "icons\\icon-32.png",
28 | "width": 32,
29 | "height": 32
30 | },
31 | {
32 | "src": "icons\\icon-114.png",
33 | "width": 114,
34 | "height": 114
35 | },
36 | {
37 | "src": "icons\\icon-128.png",
38 | "width": 128,
39 | "height": 128
40 | },
41 | {
42 | "src": "icons\\icon-256.png",
43 | "width": 256,
44 | "height": 256
45 | }
46 | ],
47 | "dependencies": {
48 | "node-polyfill-webpack-plugin": "^3.0.0",
49 | "webpack": "^5.90.2",
50 | "webpack-cli": "^5.1.4"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/achievements.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: "04b03";
3 | src: url("fonts/04b_03__.ttf");
4 | }
5 |
6 | * {
7 | font-family: "04b03", monospace;
8 | color: white;
9 | box-sizing: border-box;
10 | }
11 |
12 | ::-webkit-scrollbar {
13 | width: 8px;
14 | }
15 | ::-webkit-scrollbar-track {
16 | background: #101010;
17 | }
18 | ::-webkit-scrollbar-thumb {
19 | background: #cccccc;
20 | }
21 |
22 | body {
23 | overflow-x: hidden;
24 | }
25 |
26 | #achievements_container {
27 | height: 300px;
28 | width: 100%;
29 | overflow: auto;
30 | border: 1px solid white;
31 | padding: 8px;
32 | margin-bottom: 30px;
33 | }
34 | #achievements_container > h3 {
35 | margin: 0px;
36 | }
37 |
38 | .achievements_grid {
39 | display: none;
40 | width: 100%;
41 | grid-template-columns: repeat(3, 1fr);
42 | gap: 8px;
43 | margin-top: 4px;
44 | margin-bottom: 16px;
45 | }
46 |
47 | .achievement_img {
48 | width: 64px;
49 | height: 64px;
50 | }
51 |
52 | .achievement_div {
53 | display: flex;
54 | }
55 |
56 | .achievement_text > p {
57 | margin: 0px;
58 | }
59 | .achievement_text {
60 | padding: 4px;
61 | padding-left: 8px;
62 | }
63 | .achievement_description {
64 | font-size: 15px;
65 | }
66 |
67 | @keyframes notification_animation {
68 | 0% {right: -270px;}
69 | 10% {right: 0px;}
70 | 90% {right: 0px;}
71 | 100% {right: -270px;}
72 | }
73 |
74 | .notification {
75 | position: absolute;
76 | display: flex;
77 | bottom: 0px;
78 | right: -270px;
79 | width: 270px;
80 | padding: 8px;
81 | background-color: #101010;
82 | animation-name: notification_animation;
83 | animation-duration: 5s;
84 | }
85 |
86 | .notification_img {
87 | width: 48px;
88 | height: 48px;
89 | }
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Sheepy: A Short Adventure - Unofficial Web Port
9 |
55 |
56 |
57 |
58 | Sheepy: A Short Adventure - Unofficial Web Port
59 |
60 |
61 |
62 |
63 |
64 | Launch Game
65 |
66 |
67 | Achievements:
68 |
74 |
75 |
76 | Disclaimer:
77 |
78 |
This is an fan made port and it is unaffiliated with MrSuicideSheep . Any bug reports should be directed here instead of towards the original developers.
79 |
Please support the original developers by visiting the Itch.io page and the Steam listing for the original game.
80 |
81 |
82 |
--------------------------------------------------------------------------------
/src/achievements.js:
--------------------------------------------------------------------------------
1 | //achievements list taken from the steam web api
2 | const achievements = {
3 | vinyl0: {
4 | defaultvalue: 0,
5 | displayName: "Vinyl #0",
6 | hidden: 1,
7 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/afe81040f9fe00f80756d191141e36967cdaee80.jpg",
8 | icongray:
9 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/17b14ae91518763b1219334fcbf7e58baa87d073.jpg",
10 | },
11 | vinyl1: {
12 | defaultvalue: 0,
13 | displayName: "Vinyl #1",
14 | hidden: 1,
15 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/c2c57cdedbbae835042ae6c31a9140b70e00f247.jpg",
16 | icongray:
17 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/17b14ae91518763b1219334fcbf7e58baa87d073.jpg",
18 | },
19 | vinyl2: {
20 | defaultvalue: 0,
21 | displayName: "Vinyl #2",
22 | hidden: 1,
23 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/423960b689a6c4685cd234dd504eb497b864582f.jpg",
24 | icongray:
25 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/17b14ae91518763b1219334fcbf7e58baa87d073.jpg",
26 | },
27 | vinyl3: {
28 | defaultvalue: 0,
29 | displayName: "Vinyl #3",
30 | hidden: 1,
31 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/f670e671a4e93483e3b708166afddbcc773ede90.jpg",
32 | icongray:
33 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/17b14ae91518763b1219334fcbf7e58baa87d073.jpg",
34 | },
35 | vinyl4: {
36 | defaultvalue: 0,
37 | displayName: "Vinyl #4",
38 | hidden: 1,
39 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/c3e65e902f37446c2c14a5cc702667a2f6e97066.jpg",
40 | icongray:
41 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/17b14ae91518763b1219334fcbf7e58baa87d073.jpg",
42 | },
43 | vinyl5: {
44 | defaultvalue: 0,
45 | displayName: "Vinyl #5",
46 | hidden: 1,
47 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/ea47a9dac3ee03c7399a398e53193909d28145a9.jpg",
48 | icongray:
49 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/17b14ae91518763b1219334fcbf7e58baa87d073.jpg",
50 | },
51 | phone: {
52 | defaultvalue: 0,
53 | displayName: "Hello?",
54 | hidden: 0,
55 | description: '"Your estimated wait time is forever."',
56 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/70aad69cdf709b79e9c37ecd5cfbb1ea1affb77e.jpg",
57 | icongray:
58 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/958137ceb9cb55380792f35be3e210f1c238cb5b.jpg",
59 | },
60 | fly: {
61 | defaultvalue: 0,
62 | displayName: "I believe I can fly",
63 | hidden: 0,
64 | description: "...but no you can't, because you're a sheep.",
65 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/52f20c3538aa36fceae73d24875e700b70384300.jpg",
66 | icongray:
67 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/06331c2e6081b48a1ec489024381d72d9e9d7114.jpg",
68 | },
69 | skill1: {
70 | defaultvalue: 0,
71 | displayName: "Double jump!",
72 | hidden: 0,
73 | description: '"Taking You Higher"',
74 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/fc99bbd5ca67df2681020c31a53e55a7ca6d46ac.jpg",
75 | icongray:
76 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/2b8c474865546ffe7fd8d38d23889ecf96741bc0.jpg",
77 | },
78 | skill2: {
79 | defaultvalue: 0,
80 | displayName: "Time to run!",
81 | hidden: 0,
82 | description: "GOTTA GO FAST",
83 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/4dcee3b7a7da712f7367a7f00613f245e871bb2a.jpg",
84 | icongray:
85 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/7844c98da2f6759f5dd34636e1ab2c04522f7c24.jpg",
86 | },
87 | skill3: {
88 | defaultvalue: 0,
89 | displayName: "Weird magic...",
90 | hidden: 0,
91 | description: "But now you can fly.",
92 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/35bdac75e830dcf56cda9af1514df6b0f5bb8b8a.jpg",
93 | icongray:
94 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/a3f9ed201b9e4849b199d6dc50a8a441b31ab746.jpg",
95 | },
96 | scary: {
97 | defaultvalue: 0,
98 | displayName: "What?",
99 | hidden: 0,
100 | description: "Something happened here...",
101 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/fa542b5828d7d5695c58186fad5930a6de67b612.jpg",
102 | icongray:
103 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/09843cec25d32987fa493c89eb73862cca7c5fe5.jpg",
104 | },
105 | patches1: {
106 | defaultvalue: 0,
107 | displayName: "Hi Patches!",
108 | hidden: 0,
109 | description: "You made a new friend!",
110 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/a8593c37b19ff5541ef610ccffea7f9468a8bbf6.jpg",
111 | icongray:
112 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/fe39040b0c219c70bed0f4fb0147b29fa7e9e436.jpg",
113 | },
114 | elevator: {
115 | defaultvalue: 0,
116 | displayName: "It's working!",
117 | hidden: 0,
118 | description: "Such an engineer.",
119 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/8a5610af54930a6473cad7ec7bf387c6d5182d2e.jpg",
120 | icongray:
121 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/b46e2ddd272fcf75e6639d0b0813c9a1f5ce8c9d.jpg",
122 | },
123 | crystals: {
124 | defaultvalue: 0,
125 | displayName: "Shine bright like a crystal",
126 | hidden: 0,
127 | description: "You lit all the crystals!",
128 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/b3d745e33fcc9d68ffebf7533007b3ed07dfaabe.jpg",
129 | icongray:
130 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/4f411906def1757627dc420f1f31c5d4ee8bbe38.jpg",
131 | },
132 | chair: {
133 | defaultvalue: 0,
134 | displayName: "You spin me right 'round",
135 | hidden: 0,
136 | description: "BABY, RIGHT 'ROUND",
137 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/bb5f856f9f5d3adcb12b5a2129ab9a77ab7c2f8a.jpg",
138 | icongray:
139 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/efc6374ff23a7bbf9cf74f33742baf5161962489.jpg",
140 | },
141 | speedrun1: {
142 | defaultvalue: 0,
143 | displayName: "That's a record!",
144 | hidden: 0,
145 | description: "You beat the game in 45 minutes.",
146 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/75b31f64095c93704f6bf7b290c38c3a091c22a2.jpg",
147 | icongray:
148 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/ba93178f33d687d74825930c0561d0f1227e6e8b.jpg",
149 | },
150 | speedrun2: {
151 | defaultvalue: 0,
152 | displayName: "Hello, speedrunner.",
153 | hidden: 1,
154 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/f857d0e0e192bd5b331917f844bc54dc1cf5b468.jpg",
155 | icongray:
156 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/ba93178f33d687d74825930c0561d0f1227e6e8b.jpg",
157 | },
158 | flower: {
159 | defaultvalue: 0,
160 | displayName: "It's just a flower...",
161 | hidden: 0,
162 | description: "It feels nostalgic here.",
163 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/630b370e045df6f4f0ab898aa29847cd07f68c95.jpg",
164 | icongray:
165 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/01535c76d710a05b8808cea7abe6e419e871a869.jpg",
166 | },
167 | door: {
168 | defaultvalue: 0,
169 | displayName: "Open the door!",
170 | hidden: 0,
171 | description: "You have activated the 6 levers.",
172 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/09a771d48b2edc13cb5f21fc3732838365a60e57.jpg",
173 | icongray:
174 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/b1dded870974f751892b5fe7f520c7014c089274.jpg",
175 | },
176 | records: {
177 | defaultvalue: 0,
178 | displayName: "Lore master",
179 | hidden: 0,
180 | description: "You listened to all the tapes.",
181 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/d5f451648bd89cbe53e635a41cabd7cfcf5a46ee.jpg",
182 | icongray:
183 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/499dc4fd289f8f5738a917e6c8d28086cc057009.jpg",
184 | },
185 | trap: {
186 | defaultvalue: 0,
187 | displayName: "Ancient trap",
188 | hidden: 0,
189 | description: "Archaeologist is a dangerous job.",
190 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/c6599dc96e651a628352a9ff39bdcbb9bdcad453.jpg",
191 | icongray:
192 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/33c4e634589932c2feb3c243c318542364aae797.jpg",
193 | },
194 | run: {
195 | defaultvalue: 0,
196 | displayName: "The way of the rabbit",
197 | hidden: 0,
198 | description: "Run as fast as you can.",
199 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/a6279dd4f5e74229918f5c7c36bada8ea43247e5.jpg",
200 | icongray:
201 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/60997977b01ae56380304f1d904307429820bd68.jpg",
202 | },
203 | patches2: {
204 | defaultvalue: 0,
205 | displayName: "Complicated relationship",
206 | hidden: 0,
207 | description: "You showed Patches who's the boss.",
208 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/de39309fd2e46573943ea765786a85d2721e657f.jpg",
209 | icongray:
210 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/d801cd3dd5be37e587ed6d25b5c2d28aa4c6a86a.jpg",
211 | },
212 | paris: {
213 | defaultvalue: 0,
214 | displayName: "Oh!",
215 | hidden: 0,
216 | description: "I know this place!",
217 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/40cc45e6e8448bac7c8062b052874cb44736d7b4.jpg",
218 | icongray:
219 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/0c177147f90e1553101244b91481f547a06f972b.jpg",
220 | },
221 | easteregg: {
222 | defaultvalue: 0,
223 | displayName: "You found Sheepy!",
224 | hidden: 0,
225 | description: "Wait, another Sheepy?",
226 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/2033c5f047b0cf2248ceea18d7ca6783b652cbcc.jpg",
227 | icongray:
228 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/c3ec492cfa67722e7e46599ded2f63ccb9db15e9.jpg",
229 | },
230 | portal: {
231 | defaultvalue: 0,
232 | displayName: "The End",
233 | hidden: 0,
234 | description: "You finished the game.",
235 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/06238508f23f31998858d8fddf6f73a6f8ae1e82.jpg",
236 | icongray:
237 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/ee9399e24a07f5c971ceed446172275bc15fce18.jpg",
238 | },
239 | nodeath: {
240 | defaultvalue: 0,
241 | displayName: "SHEEPY STRONG",
242 | hidden: 0,
243 | description: "You finished the game without dying.",
244 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/74753d1df9dbbc9b1f899934992b83a37683b0d3.jpg",
245 | icongray:
246 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/f8586a9fd0319a260b8a84c91727455872388b0f.jpg",
247 | },
248 | dontgiveup: {
249 | defaultvalue: 0,
250 | displayName: "It's a hard one...",
251 | hidden: 0,
252 | description: "You can do it!",
253 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/a3cb3f2456fc678f7c052f03091caea8e321a669.jpg",
254 | icongray:
255 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/629665d2f9ca555d00ce011becd8bd641bf4c61c.jpg",
256 | },
257 | voice: {
258 | defaultvalue: 0,
259 | displayName: "Your destiny...",
260 | hidden: 0,
261 | description: "... is awaiting you.",
262 | icon: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/81d66f545121ddf2c53b775c1011ad0e3d85d421.jpg",
263 | icongray:
264 | "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1568400/3e9c3d39a927f4d8bf8ffc955f20fbd932493bbb.jpg",
265 | },
266 | };
267 |
268 | const cached_imgs = {};
269 | const channel = new BroadcastChannel("achievements");
270 | var achievement_status = {};
271 |
272 | function load_achievements() {
273 | if (!localStorage.getItem("achievements")) {
274 | for (let achievement_name in achievements) {
275 | achievement_status[achievement_name] = false;
276 | }
277 | save_achievements();
278 | }
279 | else {
280 | achievement_status = JSON.parse(localStorage.getItem("achievements"));
281 | }
282 | }
283 |
284 | function save_achievements() {
285 | localStorage.setItem("achievements", JSON.stringify(achievement_status));
286 | }
287 |
288 | function activate_achievement(name) {
289 | if (achievement_status[name]) return;
290 | achievement_status[name] = true;
291 | let achievement = achievements[name];
292 | save_achievements();
293 |
294 | let notification = document.createElement("div");
295 | notification.className = "notification";
296 |
297 | let img = cached_imgs[name];
298 | img.className = "notification_img";
299 | let text_div = document.createElement("div");
300 | text_div.className = "achievement_text";
301 | let title_element = document.createElement("p");
302 | title_element.className = "achievement_title";
303 | let description_element = document.createElement("p");
304 | description_element.className = "achievement_description";
305 |
306 | title_element.innerText = "Achievement Unlocked!";
307 | description_element.innerText = achievement.displayName;
308 |
309 | text_div.append(title_element);
310 | text_div.append(description_element);
311 | notification.append(img);
312 | notification.append(text_div);
313 |
314 | document.body.append(notification);
315 | setTimeout(() => {
316 | notification.remove();
317 | }, 6000)
318 |
319 | //notify other tabs
320 | channel.postMessage(name);
321 | }
322 |
323 | function achievement_div(achievement) {
324 | let name = achievement.name;
325 | let div = document.createElement("div");
326 | div.className = "achievement_div";
327 | let img = document.createElement("img");
328 | img.className = "achievement_img"
329 |
330 | if (achievement_status[name])
331 | img.src = achievement.icon;
332 | else
333 | img.src = achievement.icongray;
334 |
335 | let text_div = document.createElement("div");
336 | text_div.className = "achievement_text";
337 | let title_element = document.createElement("p");
338 | title_element.className = "achievement_title";
339 | let description_element = document.createElement("p");
340 | description_element.className = "achievement_description";
341 |
342 | if (achievement.hidden && !achievement_status[name]) {
343 | title_element.innerText = "Hidden Achievement";
344 | img.src = "hidden.png";
345 | }
346 | else {
347 | title_element.innerText = achievement.displayName;
348 | description_element.innerText = achievement.description || "";
349 | }
350 |
351 | text_div.append(title_element);
352 | text_div.append(description_element);
353 | div.append(img);
354 | div.append(text_div);
355 |
356 | return div;
357 | }
358 |
359 | function populate_grid(achievements_list, grid) {
360 | grid.innerHTML = "";
361 | if (!achievements_list.length) {
362 | grid.style.display = "none";
363 | return;
364 | }
365 |
366 | for (let achievement of achievements_list) {
367 | let div = achievement_div(achievement);
368 | grid.append(div);
369 | }
370 | grid.style.display = "grid";
371 | }
372 |
373 | function populate_achievements() {
374 | //reorder achievements
375 | let unlocked = [];
376 | let locked = [];
377 | let hidden = [];
378 |
379 | for (let name in achievements) {
380 | let achievement = achievements[name];
381 | achievement.name = name;
382 |
383 | if (achievement_status[name])
384 | unlocked.push(achievement);
385 | else if (achievement.hidden)
386 | hidden.push(achievement);
387 | else
388 | locked.push(achievement);
389 | }
390 | locked = locked.concat(hidden);
391 |
392 | let unlocked_header = document.getElementById("unlocked_header");
393 | let locked_header = document.getElementById("locked_header");
394 |
395 | unlocked_header.style.display = unlocked.length ? "block" : "none";
396 | locked_header.style.display = locked.length ? "block" : "none";
397 |
398 | populate_grid(unlocked, document.getElementById("unlocked_grid"));
399 | populate_grid(locked, document.getElementById("locked_grid"));
400 | }
401 |
402 | function preload_images() {
403 | for (let [name, achievement] of Object.entries(achievements)) {
404 | let img = new Image();
405 | img.src = achievement.icon;
406 | cached_imgs[name] = img;
407 | }
408 | }
409 |
410 | function index_load() {
411 | populate_achievements();
412 | channel.onmessage = () => {
413 | load_achievements();
414 | populate_achievements();
415 | };
416 | }
417 |
418 | //disable back/forward cache to reduce memory usage
419 | window.addEventListener("unload", function(){});
420 | window.addEventListener("beforeunload", function(){});
421 |
422 | load_achievements();
--------------------------------------------------------------------------------